It Compiles !
This commit is contained in:
parent
40ecbe2cfb
commit
23c77e0e23
19 changed files with 3892 additions and 0 deletions
73
controllers/controller.go
Normal file
73
controllers/controller.go
Normal file
|
@ -0,0 +1,73 @@
|
||||||
|
/*
|
||||||
|
Routes the requests to the app
|
||||||
|
*/
|
||||||
|
package controllers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"guichet/models"
|
||||||
|
"guichet/views"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/gorilla/mux"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
var staticPath = "./static"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
Create the different routes
|
||||||
|
*/
|
||||||
|
func MakeGVRouter() (*mux.Router, error) {
|
||||||
|
r := mux.NewRouter()
|
||||||
|
r.HandleFunc("/", views.HandleHome)
|
||||||
|
|
||||||
|
r.HandleFunc("/session/logout", views.HandleLogout)
|
||||||
|
|
||||||
|
r.HandleFunc("/user", views.HandleUser)
|
||||||
|
r.HandleFunc("/user/new", views.HandleInviteNewAccount)
|
||||||
|
r.HandleFunc("/user/new/", views.HandleInviteNewAccount)
|
||||||
|
r.HandleFunc("/user/wait", views.HandleUserWait)
|
||||||
|
r.HandleFunc("/user/mail", views.HandleUserMail)
|
||||||
|
|
||||||
|
r.HandleFunc("/picture/{name}", views.HandleDownloadPicture)
|
||||||
|
|
||||||
|
r.HandleFunc("/passwd", views.HandlePasswd)
|
||||||
|
r.HandleFunc("/passwd/lost", views.HandleLostPassword)
|
||||||
|
r.HandleFunc("/passwd/lost/{code}", views.HandleFoundPassword)
|
||||||
|
|
||||||
|
r.HandleFunc("/admin", views.HandleHome)
|
||||||
|
r.HandleFunc("/admin/activate", views.HandleAdminActivateUsers)
|
||||||
|
r.HandleFunc("/admin/unactivate/{cn}", views.HandleAdminUnactivateUser)
|
||||||
|
r.HandleFunc("/admin/activate/{cn}", views.HandleAdminActivateUser)
|
||||||
|
r.HandleFunc("/admin/users", views.HandleAdminUsers)
|
||||||
|
r.HandleFunc("/admin/groups", views.HandleAdminGroups)
|
||||||
|
r.HandleFunc("/admin/ldap/{dn}", views.HandleAdminLDAP)
|
||||||
|
r.HandleFunc("/admin/create/{template}/{super_dn}", views.HandleAdminCreate)
|
||||||
|
|
||||||
|
// r.HandleFunc("/directory/search", views.HandleDirectorySearch)
|
||||||
|
// r.HandleFunc("/directory", views.HandleDirectory)
|
||||||
|
// r.HandleFunc("/garage/key", views.HandleGarageKey)
|
||||||
|
// r.HandleFunc("/garage/website", views.HandleGarageWebsiteList)
|
||||||
|
// r.HandleFunc("/garage/website/new", views.HandleGarageWebsiteNew)
|
||||||
|
// r.HandleFunc("/garage/website/b/{bucket}", views.HandleGarageWebsiteInspect)
|
||||||
|
|
||||||
|
// r.HandleFunc("/user/send_code", views.HandleInviteSendCode)
|
||||||
|
|
||||||
|
// r.HandleFunc("/invitation/{code}", views.HandleInvitationCode)
|
||||||
|
|
||||||
|
// r.HandleFunc("/admin-mailing", views.HandleAdminMailing)
|
||||||
|
// r.HandleFunc("/admin/mailing/{id}", views.HandleAdminMailingList)
|
||||||
|
|
||||||
|
staticFiles := http.FileServer(http.Dir(staticPath))
|
||||||
|
r.Handle("/static/{file:.*}", http.StripPrefix("/static/", staticFiles))
|
||||||
|
config_file := models.ReadConfig()
|
||||||
|
|
||||||
|
// log.Printf("Starting HTTP server on %s", config.HttpBindAddr)
|
||||||
|
err := http.ListenAndServe(config_file.HttpBindAddr, views.LogRequest(r))
|
||||||
|
|
||||||
|
return r, err
|
||||||
|
}
|
||||||
|
|
||||||
|
|
239
garage.notgo
Normal file
239
garage.notgo
Normal file
|
@ -0,0 +1,239 @@
|
||||||
|
package models
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
garage "git.deuxfleurs.fr/garage-sdk/garage-admin-sdk-golang"
|
||||||
|
"github.com/go-ldap/ldap/v3"
|
||||||
|
"github.com/gorilla/mux"
|
||||||
|
)
|
||||||
|
|
||||||
|
var config = ReadConfig()
|
||||||
|
|
||||||
|
func gadmin() (*garage.APIClient, context.Context) {
|
||||||
|
// Set Host and other parameters
|
||||||
|
configuration := garage.NewConfiguration()
|
||||||
|
configuration.Host = config.S3AdminEndpoint
|
||||||
|
|
||||||
|
// We can now generate a client
|
||||||
|
client := garage.NewAPIClient(configuration)
|
||||||
|
|
||||||
|
// Authentication is handled through the context pattern
|
||||||
|
ctx := context.WithValue(context.Background(), garage.ContextAccessToken, config.S3AdminToken)
|
||||||
|
return client, ctx
|
||||||
|
}
|
||||||
|
|
||||||
|
func grgCreateKey(name string) (*garage.KeyInfo, error) {
|
||||||
|
client, ctx := gadmin()
|
||||||
|
|
||||||
|
kr := garage.AddKeyRequest{Name: &name}
|
||||||
|
resp, _, err := client.KeyApi.AddKey(ctx).AddKeyRequest(kr).Execute()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("%+v\n", err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return resp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func grgGetKey(accessKey string) (*garage.KeyInfo, error) {
|
||||||
|
client, ctx := gadmin()
|
||||||
|
|
||||||
|
resp, _, err := client.KeyApi.GetKey(ctx, accessKey).Execute()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("%+v\n", err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return resp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func grgCreateWebsite(gkey, bucket string) (*garage.BucketInfo, error) {
|
||||||
|
client, ctx := gadmin()
|
||||||
|
|
||||||
|
br := garage.NewCreateBucketRequest()
|
||||||
|
br.SetGlobalAlias(bucket)
|
||||||
|
|
||||||
|
// Create Bucket
|
||||||
|
binfo, _, err := client.BucketApi.CreateBucket(ctx).CreateBucketRequest(*br).Execute()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("%+v\n", err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Allow user's key
|
||||||
|
ar := garage.AllowBucketKeyRequest{
|
||||||
|
BucketId: *binfo.Id,
|
||||||
|
AccessKeyId: gkey,
|
||||||
|
Permissions: *garage.NewAllowBucketKeyRequestPermissions(true, true, true),
|
||||||
|
}
|
||||||
|
binfo, _, err = client.BucketApi.AllowBucketKey(ctx).AllowBucketKeyRequest(ar).Execute()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("%+v\n", err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Expose website and set quota
|
||||||
|
wr := garage.NewUpdateBucketRequestWebsiteAccess()
|
||||||
|
wr.SetEnabled(true)
|
||||||
|
wr.SetIndexDocument("index.html")
|
||||||
|
wr.SetErrorDocument("error.html")
|
||||||
|
|
||||||
|
qr := garage.NewUpdateBucketRequestQuotas()
|
||||||
|
qr.SetMaxSize(1024 * 1024 * 50) // 50MB
|
||||||
|
qr.SetMaxObjects(10000) //10k objects
|
||||||
|
|
||||||
|
ur := garage.NewUpdateBucketRequest()
|
||||||
|
ur.SetWebsiteAccess(*wr)
|
||||||
|
ur.SetQuotas(*qr)
|
||||||
|
|
||||||
|
binfo, _, err = client.BucketApi.UpdateBucket(ctx, *binfo.Id).UpdateBucketRequest(*ur).Execute()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("%+v\n", err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return updated binfo
|
||||||
|
return binfo, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func grgGetBucket(bid string) (*garage.BucketInfo, error) {
|
||||||
|
client, ctx := gadmin()
|
||||||
|
|
||||||
|
resp, _, err := client.BucketApi.GetBucketInfo(ctx, bid).Execute()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("%+v\n", err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return resp, nil
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkLoginAndS3(w http.ResponseWriter, r *http.Request) (*LoginStatus, *garage.KeyInfo, error) {
|
||||||
|
login := checkLogin(w, r)
|
||||||
|
if login == nil {
|
||||||
|
return nil, nil, errors.New("LDAP login failed")
|
||||||
|
}
|
||||||
|
|
||||||
|
keyID := login.UserEntry.GetAttributeValue("garage_s3_access_key")
|
||||||
|
if keyID == "" {
|
||||||
|
keyPair, err := grgCreateKey(login.Info.Username)
|
||||||
|
if err != nil {
|
||||||
|
return login, nil, err
|
||||||
|
}
|
||||||
|
modify_request := ldap.NewModifyRequest(login.Info.DN, nil)
|
||||||
|
modify_request.Replace("garage_s3_access_key", []string{*keyPair.AccessKeyId})
|
||||||
|
// @FIXME compatibility feature for bagage (SFTP+webdav)
|
||||||
|
// you can remove it once bagage will be updated to fetch the key from garage directly
|
||||||
|
// or when bottin will be able to dynamically fetch it.
|
||||||
|
modify_request.Replace("garage_s3_secret_key", []string{*keyPair.SecretAccessKey})
|
||||||
|
err = login.conn.Modify(modify_request)
|
||||||
|
return login, keyPair, err
|
||||||
|
}
|
||||||
|
// Note: we could simply return the login info, but LX asked we do not
|
||||||
|
// store the secrets in LDAP in the future.
|
||||||
|
keyPair, err := grgGetKey(keyID)
|
||||||
|
return login, keyPair, err
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
func HandleGarageKey(w http.ResponseWriter, r *http.Request) {
|
||||||
|
login, s3key, err := checkLoginAndS3(w, r)
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
view := keyView{Status: login, Key: s3key}
|
||||||
|
|
||||||
|
tKey := getTemplate("garage/key.html")
|
||||||
|
tKey.Execute(w, &view)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
func HandleGarageWebsiteList(w http.ResponseWriter, r *http.Request) {
|
||||||
|
login, s3key, err := checkLoginAndS3(w, r)
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
view := webListView{Status: login, Key: s3key}
|
||||||
|
|
||||||
|
tWebsiteList := getTemplate("garage/website/list.html")
|
||||||
|
tWebsiteList.Execute(w, &view)
|
||||||
|
}
|
||||||
|
|
||||||
|
func HandleGarageWebsiteNew(w http.ResponseWriter, r *http.Request) {
|
||||||
|
_, s3key, err := checkLoginAndS3(w, r)
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
tWebsiteNew := getTemplate("garage/website/new.html")
|
||||||
|
if r.Method == "POST" {
|
||||||
|
r.ParseForm()
|
||||||
|
log.Println(r.Form)
|
||||||
|
|
||||||
|
bucket := strings.Join(r.Form["bucket"], "")
|
||||||
|
if bucket == "" {
|
||||||
|
bucket = strings.Join(r.Form["bucket2"], "")
|
||||||
|
}
|
||||||
|
if bucket == "" {
|
||||||
|
log.Println("Form empty")
|
||||||
|
// @FIXME we need to return the error to the user
|
||||||
|
tWebsiteNew.Execute(w, nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
binfo, err := grgCreateWebsite(*s3key.AccessKeyId, bucket)
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
// @FIXME we need to return the error to the user
|
||||||
|
tWebsiteNew.Execute(w, nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
http.Redirect(w, r, "/garage/website/b/"+*binfo.Id, http.StatusFound)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
tWebsiteNew.Execute(w, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
func HandleGarageWebsiteInspect(w http.ResponseWriter, r *http.Request) {
|
||||||
|
login, s3key, err := checkLoginAndS3(w, r)
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
bucketId := mux.Vars(r)["bucket"]
|
||||||
|
binfo, err := grgGetBucket(bucketId)
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
wc := binfo.GetWebsiteConfig()
|
||||||
|
q := binfo.GetQuotas()
|
||||||
|
|
||||||
|
view := webInspectView{
|
||||||
|
Status: login,
|
||||||
|
Key: s3key,
|
||||||
|
Bucket: binfo,
|
||||||
|
IndexDoc: (&wc).GetIndexDocument(),
|
||||||
|
ErrorDoc: (&wc).GetErrorDocument(),
|
||||||
|
MaxObjects: (&q).GetMaxObjects(),
|
||||||
|
MaxSize: (&q).GetMaxSize(),
|
||||||
|
}
|
||||||
|
|
||||||
|
tWebsiteInspect := getTemplate("garage/website/inspect.html")
|
||||||
|
tWebsiteInspect.Execute(w, &view)
|
||||||
|
}
|
52
models/config.go
Normal file
52
models/config.go
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
/*
|
||||||
|
config Handles reading the config.json file at the root and processing the settings
|
||||||
|
*/
|
||||||
|
package models
|
||||||
|
|
||||||
|
type ConfigFile struct {
|
||||||
|
HttpBindAddr string `json:"http_bind_addr"`
|
||||||
|
LdapServerAddr string `json:"ldap_server_addr"`
|
||||||
|
LdapTLS bool `json:"ldap_tls"`
|
||||||
|
|
||||||
|
BaseDN string `json:"base_dn"`
|
||||||
|
UserBaseDN string `json:"user_base_dn"`
|
||||||
|
UserNameAttr string `json:"user_name_attr"`
|
||||||
|
GroupBaseDN string `json:"group_base_dn"`
|
||||||
|
GroupNameAttr string `json:"group_name_attr"`
|
||||||
|
|
||||||
|
MailingBaseDN string `json:"mailing_list_base_dn"`
|
||||||
|
MailingNameAttr string `json:"mailing_list_name_attr"`
|
||||||
|
MailingGuestsBaseDN string `json:"mailing_list_guest_user_base_dn"`
|
||||||
|
|
||||||
|
InvitationBaseDN string `json:"invitation_base_dn"`
|
||||||
|
InvitationNameAttr string `json:"invitation_name_attr"`
|
||||||
|
InvitedMailFormat string `json:"invited_mail_format"`
|
||||||
|
InvitedAutoGroups []string `json:"invited_auto_groups"`
|
||||||
|
|
||||||
|
WebAddress string `json:"web_address"`
|
||||||
|
MailFrom string `json:"mail_from"`
|
||||||
|
SMTPServer string `json:"smtp_server"`
|
||||||
|
SMTPUsername string `json:"smtp_username"`
|
||||||
|
SMTPPassword string `json:"smtp_password"`
|
||||||
|
|
||||||
|
AdminAccount string `json:"admin_account"`
|
||||||
|
GroupCanInvite string `json:"group_can_invite"`
|
||||||
|
GroupCanAdmin string `json:"group_can_admin"`
|
||||||
|
|
||||||
|
S3AdminEndpoint string `json:"s3_admin_endpoint"`
|
||||||
|
S3AdminToken string `json:"s3_admin_token"`
|
||||||
|
|
||||||
|
S3Endpoint string `json:"s3_endpoint"`
|
||||||
|
S3AccessKey string `json:"s3_access_key"`
|
||||||
|
S3SecretKey string `json:"s3_secret_key"`
|
||||||
|
S3Region string `json:"s3_region"`
|
||||||
|
S3Bucket string `json:"s3_bucket"`
|
||||||
|
|
||||||
|
Org string `json:"org"`
|
||||||
|
DomainName string `json:"domain_name"`
|
||||||
|
NewUserDN string `json:"new_user_dn"`
|
||||||
|
NewUserPassword string `json:"new_user_password"`
|
||||||
|
NewUsersBaseDN string `json:"new_users_base_dn"`
|
||||||
|
NewUserDefaultDomain string `json:"new_user_default_domain"`
|
||||||
|
}
|
||||||
|
|
118
models/ldap.go
Normal file
118
models/ldap.go
Normal file
|
@ -0,0 +1,118 @@
|
||||||
|
/*
|
||||||
|
Utilities related to LDAP
|
||||||
|
*/
|
||||||
|
package models
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/go-ldap/ldap/v3"
|
||||||
|
)
|
||||||
|
|
||||||
|
var config = ReadConfig()
|
||||||
|
const FIELD_NAME_PROFILE_PICTURE = "profilePicture"
|
||||||
|
const FIELD_NAME_DIRECTORY_VISIBILITY = "directoryVisibility"
|
||||||
|
|
||||||
|
type SearchResult struct {
|
||||||
|
DN string
|
||||||
|
Id string
|
||||||
|
DisplayName string
|
||||||
|
Email string
|
||||||
|
Description string
|
||||||
|
ProfilePicture string
|
||||||
|
}
|
||||||
|
type SearchResults struct {
|
||||||
|
Results []SearchResult
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
func (r *SearchResults) Len() int {
|
||||||
|
return len(r.Results)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *SearchResults) Less(i, j int) bool {
|
||||||
|
return r.Results[i].Id < r.Results[j].Id
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *SearchResults) Swap(i, j int) {
|
||||||
|
r.Results[i], r.Results[j] = r.Results[j], r.Results[i]
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
func ContainsI(a string, b string) bool {
|
||||||
|
return strings.Contains(
|
||||||
|
strings.ToLower(a),
|
||||||
|
strings.ToLower(b),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// New account creation directly from interface
|
||||||
|
|
||||||
|
|
||||||
|
func OpenNewUserLdap(config *ConfigFile) (*ldap.Conn, error) {
|
||||||
|
l, err := openLdap(config)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("OpenNewUserLdap 1 : %v %v", err, l)
|
||||||
|
log.Printf("OpenNewUserLdap 1 : %v", config)
|
||||||
|
// data.Common.ErrorMessage = err.Error()
|
||||||
|
}
|
||||||
|
err = l.Bind(config.NewUserDN, config.NewUserPassword)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("OpenNewUserLdap Bind : %v", err)
|
||||||
|
log.Printf("OpenNewUserLdap Bind : %v", config.NewUserDN)
|
||||||
|
log.Printf("OpenNewUserLdap Bind : %v", config.NewUserPassword)
|
||||||
|
log.Printf("OpenNewUserLdap Bind : %v", config)
|
||||||
|
// data.Common.ErrorMessage = err.Error()
|
||||||
|
}
|
||||||
|
return l, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func DoDirectorySearch(ldapConn *ldap.Conn, input string) (SearchResults, error) {
|
||||||
|
//Search values with ldap and filter
|
||||||
|
|
||||||
|
searchRequest := ldap.NewSearchRequest(
|
||||||
|
config.UserBaseDN,
|
||||||
|
ldap.ScopeSingleLevel, ldap.NeverDerefAliases, 0, 0, false,
|
||||||
|
"(&(objectclass=organizationalPerson)("+FIELD_NAME_DIRECTORY_VISIBILITY+"=on))",
|
||||||
|
[]string{
|
||||||
|
config.UserNameAttr,
|
||||||
|
"displayname",
|
||||||
|
"mail",
|
||||||
|
"description",
|
||||||
|
FIELD_NAME_PROFILE_PICTURE,
|
||||||
|
},
|
||||||
|
nil)
|
||||||
|
//Transform the researh's result in a correct struct to send JSON
|
||||||
|
results := []SearchResult{}
|
||||||
|
sr, err := ldapConn.Search(searchRequest)
|
||||||
|
if err != nil {
|
||||||
|
return SearchResults{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
for _, values := range sr.Entries {
|
||||||
|
if input == "" ||
|
||||||
|
ContainsI(values.GetAttributeValue(config.UserNameAttr), input) ||
|
||||||
|
ContainsI(values.GetAttributeValue("displayname"), input) ||
|
||||||
|
ContainsI(values.GetAttributeValue("mail"), input) {
|
||||||
|
results = append(results, SearchResult{
|
||||||
|
DN: values.DN,
|
||||||
|
Id: values.GetAttributeValue(config.UserNameAttr),
|
||||||
|
DisplayName: values.GetAttributeValue("displayname"),
|
||||||
|
Email: values.GetAttributeValue("mail"),
|
||||||
|
Description: values.GetAttributeValue("description"),
|
||||||
|
ProfilePicture: values.GetAttributeValue(FIELD_NAME_PROFILE_PICTURE),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// sort.Sort(&results)
|
||||||
|
search_results := SearchResults{
|
||||||
|
Results: results,
|
||||||
|
}
|
||||||
|
sort.Sort(&search_results)
|
||||||
|
return search_results, nil
|
||||||
|
}
|
67
models/model.go
Normal file
67
models/model.go
Normal file
|
@ -0,0 +1,67 @@
|
||||||
|
/*
|
||||||
|
Centralises the models used in this application
|
||||||
|
*/
|
||||||
|
|
||||||
|
package models
|
||||||
|
|
||||||
|
import (
|
||||||
|
// "crypto/tls"
|
||||||
|
// "log"
|
||||||
|
// "net"
|
||||||
|
|
||||||
|
"github.com/go-ldap/ldap/v3"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
Represents a user
|
||||||
|
*/
|
||||||
|
type User struct {
|
||||||
|
DN string
|
||||||
|
CN string
|
||||||
|
GivenName string
|
||||||
|
DisplayName string
|
||||||
|
Mail string
|
||||||
|
SN string
|
||||||
|
UID string
|
||||||
|
Description string
|
||||||
|
Password string
|
||||||
|
OtherMailbox string
|
||||||
|
CanAdmin bool
|
||||||
|
CanInvite bool
|
||||||
|
UserEntry *ldap.Entry
|
||||||
|
SeeAlso string
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// func openLdap(config *ConfigFile) (*ldap.Conn, error) {
|
||||||
|
// var ldapConn *ldap.Conn
|
||||||
|
// var err error
|
||||||
|
// if config.LdapTLS {
|
||||||
|
// tlsConf := &tls.Config{
|
||||||
|
// ServerName: config.LdapServerAddr,
|
||||||
|
// InsecureSkipVerify: true,
|
||||||
|
// }
|
||||||
|
// ldapConn, err = ldap.DialTLS("tcp", net.JoinHostPort(config.LdapServerAddr, "636"), tlsConf)
|
||||||
|
// } else {
|
||||||
|
// ldapConn, err = ldap.DialURL("ldap://" + config.LdapServerAddr)
|
||||||
|
// }
|
||||||
|
// if err != nil {
|
||||||
|
// log.Printf("openLDAP %v", err)
|
||||||
|
// log.Printf("openLDAP %v", config.LdapServerAddr)
|
||||||
|
// }
|
||||||
|
// return ldapConn, err
|
||||||
|
|
||||||
|
// // l, err := ldap.DialURL(config.LdapServerAddr)
|
||||||
|
// // if err != nil {
|
||||||
|
// // log.Printf(fmt.Sprint("Erreur connect LDAP %v", err))
|
||||||
|
// // log.Printf(fmt.Sprint("Erreur connect LDAP %v", config.LdapServerAddr))
|
||||||
|
// // return nil
|
||||||
|
// // } else {
|
||||||
|
// // return l
|
||||||
|
// // }
|
||||||
|
// }
|
135
models/modelutils.go
Normal file
135
models/modelutils.go
Normal file
|
@ -0,0 +1,135 @@
|
||||||
|
package models
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"crypto/tls"
|
||||||
|
"log"
|
||||||
|
"net"
|
||||||
|
"net/smtp"
|
||||||
|
|
||||||
|
"html/template"
|
||||||
|
|
||||||
|
"github.com/go-ldap/ldap/v3"
|
||||||
|
// "golang.org/x/text/encoding/unicode"
|
||||||
|
"encoding/json"
|
||||||
|
|
||||||
|
"io/ioutil"
|
||||||
|
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"flag"
|
||||||
|
)
|
||||||
|
|
||||||
|
//
|
||||||
|
|
||||||
|
func ReadConfig() ConfigFile {
|
||||||
|
// Default configuration values for certain fields
|
||||||
|
flag.Parse()
|
||||||
|
var configFlag = flag.String("config", "./config.json", "Configuration file path")
|
||||||
|
|
||||||
|
config_file := ConfigFile{
|
||||||
|
HttpBindAddr: ":9991",
|
||||||
|
LdapServerAddr: "ldap://127.0.0.1:389",
|
||||||
|
|
||||||
|
UserNameAttr: "uid",
|
||||||
|
GroupNameAttr: "gid",
|
||||||
|
|
||||||
|
InvitationNameAttr: "cn",
|
||||||
|
InvitedAutoGroups: []string{},
|
||||||
|
|
||||||
|
Org: "ResDigita",
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := os.Stat(*configFlag)
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
log.Fatalf("Could not find Guichet configuration file at %s. Please create this file, for exemple starting with config.json.exemple and customizing it for your deployment.", *configFlag)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
bytes, err := ioutil.ReadFile(*configFlag)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = json.Unmarshal(bytes, &config_file)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return config_file
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
type EmailContentVarsTplData struct {
|
||||||
|
Code string
|
||||||
|
SendAddress string
|
||||||
|
InviteFrom string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Data to be passed to an email for sending
|
||||||
|
type SendMailTplData struct {
|
||||||
|
// Sender of the email
|
||||||
|
To string
|
||||||
|
// Receiver of the email
|
||||||
|
From string
|
||||||
|
// Relative path (without leading /) to the email template in the templates folder
|
||||||
|
// usually ending in .txt
|
||||||
|
RelTemplatePath string
|
||||||
|
// Variables to be included in the template of the email
|
||||||
|
EmailContentVars EmailContentVarsTplData
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
func openLdap(config *ConfigFile) (*ldap.Conn, error) {
|
||||||
|
var ldapConn *ldap.Conn
|
||||||
|
var err error
|
||||||
|
if config.LdapTLS {
|
||||||
|
tlsConf := &tls.Config{
|
||||||
|
ServerName: config.LdapServerAddr,
|
||||||
|
InsecureSkipVerify: true,
|
||||||
|
}
|
||||||
|
ldapConn, err = ldap.DialTLS("tcp", net.JoinHostPort(config.LdapServerAddr, "636"), tlsConf)
|
||||||
|
} else {
|
||||||
|
ldapConn, err = ldap.DialURL("ldap://" + config.LdapServerAddr)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("openLDAP %v", err)
|
||||||
|
log.Printf("openLDAP %v", config.LdapServerAddr)
|
||||||
|
}
|
||||||
|
return ldapConn, err
|
||||||
|
|
||||||
|
// l, err := ldap.DialURL(config.LdapServerAddr)
|
||||||
|
// if err != nil {
|
||||||
|
// log.Printf(fmt.Sprint("Erreur connect LDAP %v", err))
|
||||||
|
// log.Printf(fmt.Sprint("Erreur connect LDAP %v", config.LdapServerAddr))
|
||||||
|
// return nil
|
||||||
|
// } else {
|
||||||
|
// return l
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// Sends an email according to the enclosed information
|
||||||
|
func sendMail(sendMailTplData SendMailTplData) error {
|
||||||
|
log.Printf("sendMail")
|
||||||
|
templateMail := template.Must(template.ParseFiles( "./templates" + sendMailTplData.RelTemplatePath))
|
||||||
|
buf := bytes.NewBuffer([]byte{})
|
||||||
|
err := templateMail.Execute(buf, sendMailTplData)
|
||||||
|
message := buf.Bytes()
|
||||||
|
auth := smtp.PlainAuth("", config.SMTPUsername, config.SMTPPassword, config.SMTPServer)
|
||||||
|
log.Printf("auth: %v", auth)
|
||||||
|
err = smtp.SendMail(config.SMTPServer+":587", auth, config.SMTPUsername, []string{sendMailTplData.To}, message)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("sendMail smtp.SendMail %v", err)
|
||||||
|
log.Printf("sendMail smtp.SendMail %v", sendMailTplData)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
log.Printf("Mail sent.")
|
||||||
|
return err
|
||||||
|
}
|
222
models/passwd.go
Normal file
222
models/passwd.go
Normal file
|
@ -0,0 +1,222 @@
|
||||||
|
/*
|
||||||
|
gpas is GVoisin password reset
|
||||||
|
*/
|
||||||
|
|
||||||
|
package models
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"html/template"
|
||||||
|
"log"
|
||||||
|
"math/rand"
|
||||||
|
|
||||||
|
// "github.com/emersion/go-sasl"
|
||||||
|
// "github.com/emersion/go-smtp"
|
||||||
|
"net/smtp"
|
||||||
|
|
||||||
|
"github.com/go-ldap/ldap/v3"
|
||||||
|
|
||||||
|
// "strings"
|
||||||
|
b64 "encoding/base64"
|
||||||
|
)
|
||||||
|
|
||||||
|
var templatePath = "./templates"
|
||||||
|
|
||||||
|
type CodeMailFields struct {
|
||||||
|
From string
|
||||||
|
To string
|
||||||
|
Code string
|
||||||
|
InviteFrom string
|
||||||
|
WebBaseAddress string
|
||||||
|
Common NestedCommonTplData
|
||||||
|
}
|
||||||
|
type NestedCommonTplData struct {
|
||||||
|
Error string
|
||||||
|
ErrorMessage string
|
||||||
|
CanAdmin bool
|
||||||
|
CanInvite bool
|
||||||
|
LoggedIn bool
|
||||||
|
Success bool
|
||||||
|
WarningMessage string
|
||||||
|
WebsiteName string
|
||||||
|
WebsiteURL string
|
||||||
|
}
|
||||||
|
// type InvitationAccount struct {
|
||||||
|
// UID string
|
||||||
|
// Password string
|
||||||
|
// BaseDN string
|
||||||
|
// }
|
||||||
|
|
||||||
|
// Suggesting a 12 char password with some excentrics
|
||||||
|
func SuggestPassword() string {
|
||||||
|
password := ""
|
||||||
|
chars := "abcdfghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%&*+_-="
|
||||||
|
for i := 0; i < 12; i++ {
|
||||||
|
password += string([]rune(chars)[rand.Intn(len(chars))])
|
||||||
|
}
|
||||||
|
return password
|
||||||
|
}
|
||||||
|
|
||||||
|
// var EMAIL_REGEXP := regexp.MustCompile("^[a-zA-Z0-9.!#$%&'*+\\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$")
|
||||||
|
|
||||||
|
func getInvitationBaseDN(config *ConfigFile) string {
|
||||||
|
return config.InvitationBaseDN
|
||||||
|
}
|
||||||
|
|
||||||
|
func PasswordLost(user User, config *ConfigFile, ldapConn *ldap.Conn) error {
|
||||||
|
if user.CN == "" && user.Mail == "" && user.OtherMailbox == "" {
|
||||||
|
return errors.New("Il n'y a pas de quoi identifier l'utilisateur")
|
||||||
|
}
|
||||||
|
searchFilter := "(|"
|
||||||
|
if user.CN != "" {
|
||||||
|
searchFilter += "(cn=" + user.UID + ")"
|
||||||
|
}
|
||||||
|
if user.Mail != "" {
|
||||||
|
searchFilter += "(mail=" + user.Mail + ")"
|
||||||
|
}
|
||||||
|
if user.OtherMailbox != "" {
|
||||||
|
searchFilter += "(carLicense=" + user.OtherMailbox + ")"
|
||||||
|
}
|
||||||
|
searchFilter += ")"
|
||||||
|
searchReq := ldap.NewSearchRequest(config.UserBaseDN, ldap.ScopeSingleLevel, ldap.NeverDerefAliases, 0, 0, false, searchFilter, []string{"cn", "uid", "mail", "carLicense", "sn", "displayName", "givenName"}, nil)
|
||||||
|
searchRes, err := ldapConn.Search(searchReq)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("PasswordLost search : %v %v", err, ldapConn)
|
||||||
|
log.Printf("PasswordLost search : %v", searchReq)
|
||||||
|
log.Printf("PasswordLost search : %v", searchRes)
|
||||||
|
log.Printf("PasswordLost search: %v", user)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if len(searchRes.Entries) == 0 {
|
||||||
|
log.Printf("Il n'y a pas d'utilisateur qui correspond %v", searchReq)
|
||||||
|
return errors.New("Il n'y a pas d'utilisateur qui correspond")
|
||||||
|
}
|
||||||
|
// log.Printf("PasswordLost 58 : %v", user)
|
||||||
|
// log.Printf("PasswordLost 59 : %v", searchRes.Entries[0])
|
||||||
|
// log.Printf("PasswordLost 60 : %v", searchRes.Entries[0].GetAttributeValue("cn"))
|
||||||
|
// log.Printf("PasswordLost 61 : %v", searchRes.Entries[0].GetAttributeValue("uid"))
|
||||||
|
// log.Printf("PasswordLost 62 : %v", searchRes.Entries[0].GetAttributeValue("mail"))
|
||||||
|
// log.Printf("PasswordLost 63 : %v", searchRes.Entries[0].GetAttributeValue("carLicense"))
|
||||||
|
// Préparation du courriel à envoyer
|
||||||
|
|
||||||
|
delReq := ldap.NewDelRequest("uid="+searchRes.Entries[0].GetAttributeValue("cn")+","+config.InvitationBaseDN, nil)
|
||||||
|
err = ldapConn.Del(delReq)
|
||||||
|
|
||||||
|
user.Password = SuggestPassword()
|
||||||
|
user.DN = "uid=" + searchRes.Entries[0].GetAttributeValue("cn") + "," + config.InvitationBaseDN
|
||||||
|
user.UID = searchRes.Entries[0].GetAttributeValue("cn")
|
||||||
|
user.CN = searchRes.Entries[0].GetAttributeValue("cn")
|
||||||
|
user.Mail = searchRes.Entries[0].GetAttributeValue("mail")
|
||||||
|
user.OtherMailbox = searchRes.Entries[0].GetAttributeValue("carLicense")
|
||||||
|
code := b64.URLEncoding.EncodeToString([]byte(user.UID + ";" + user.Password))
|
||||||
|
/* Check for outstanding invitation */
|
||||||
|
searchReq = ldap.NewSearchRequest(config.InvitationBaseDN, ldap.ScopeSingleLevel,
|
||||||
|
ldap.NeverDerefAliases, 0, 0, false, "(uid="+user.UID+")", []string{"seeAlso"}, nil)
|
||||||
|
searchRes, err = ldapConn.Search(searchReq)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf(fmt.Sprintf("PasswordLost (Check existing invitation) : %v", err))
|
||||||
|
log.Printf(fmt.Sprintf("PasswordLost (Check existing invitation) : %v", user))
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// if len(searchRes.Entries) == 0 {
|
||||||
|
/* Add the invitation */
|
||||||
|
addReq := ldap.NewAddRequest(
|
||||||
|
"uid="+user.UID+","+config.InvitationBaseDN,
|
||||||
|
nil)
|
||||||
|
addReq.Attribute("objectClass", []string{"top", "account", "simpleSecurityObject"})
|
||||||
|
addReq.Attribute("uid", []string{user.UID})
|
||||||
|
addReq.Attribute("userPassword", []string{user.Password})
|
||||||
|
addReq.Attribute("seeAlso", []string{config.UserNameAttr + "=" + user.UID + "," + config.UserBaseDN})
|
||||||
|
// Password invitation may already exist
|
||||||
|
|
||||||
|
//
|
||||||
|
err = ldapConn.Add(addReq)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("PasswordLost 83 : %v", err)
|
||||||
|
log.Printf("PasswordLost 84 : %v", user)
|
||||||
|
|
||||||
|
log.Printf("PasswordLost 84 : %v", addReq)
|
||||||
|
// // log.Printf("PasswordLost 85 : %v", searchRes.Entries[0]))
|
||||||
|
// // For some reason I get here even if the entry exists already
|
||||||
|
// return err
|
||||||
|
}
|
||||||
|
// }
|
||||||
|
ldapNewConn, err := OpenNewUserLdap(config)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("PasswordLost OpenNewUserLdap : %v", err)
|
||||||
|
}
|
||||||
|
err = PassWD(user, config, ldapNewConn)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("PasswordLost PassWD : %v", err)
|
||||||
|
log.Printf("PasswordLost PassWD : %v", user)
|
||||||
|
log.Printf("PasswordLost PassWD : %v", searchRes.Entries[0])
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
templateMail := template.Must(template.ParseFiles(templatePath + "/passwd/lost_password_email.txt"))
|
||||||
|
buf := bytes.NewBuffer([]byte{})
|
||||||
|
templateMail.Execute(buf, &CodeMailFields{
|
||||||
|
To: user.OtherMailbox,
|
||||||
|
From: config.MailFrom,
|
||||||
|
InviteFrom: user.UID,
|
||||||
|
Code: code,
|
||||||
|
WebBaseAddress: config.WebAddress,
|
||||||
|
})
|
||||||
|
// message := []byte("Hi " + user.OtherMailbox)
|
||||||
|
log.Printf("Sending mail to: %s", user.OtherMailbox)
|
||||||
|
// var auth sasl.Client = nil
|
||||||
|
// if config.SMTPUsername != "" {
|
||||||
|
// auth = sasl.NewPlainClient("", config.SMTPUsername, config.SMTPPassword)
|
||||||
|
// }
|
||||||
|
message := buf.Bytes()
|
||||||
|
auth := smtp.PlainAuth("", config.SMTPUsername, config.SMTPPassword, config.SMTPServer)
|
||||||
|
log.Printf("auth: %v", auth)
|
||||||
|
err = smtp.SendMail(config.SMTPServer+":587", auth, config.SMTPUsername, []string{user.OtherMailbox}, message)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("email send error %v", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
log.Printf("Mail sent.")
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func PasswordFound(user User, config *ConfigFile, ldapConn *ldap.Conn) (string, error) {
|
||||||
|
l, err := openLdap(config)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("PasswordFound openLdap %v", err)
|
||||||
|
// log.Printf("PasswordFound openLdap Config : %v", config)
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
if user.DN == "" && user.UID != "" {
|
||||||
|
user.DN = "uid=" + user.UID + "," + config.InvitationBaseDN
|
||||||
|
}
|
||||||
|
err = l.Bind(user.DN, user.Password)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("PasswordFound l.Bind %v", err)
|
||||||
|
log.Printf("PasswordFound l.Bind %v", user.DN)
|
||||||
|
log.Printf("PasswordFound l.Bind %v", user.UID)
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
searchReq := ldap.NewSearchRequest(user.DN, ldap.ScopeBaseObject,
|
||||||
|
ldap.NeverDerefAliases, 0, 0, false, "(uid="+user.UID+")", []string{"seeAlso"}, nil)
|
||||||
|
var searchRes *ldap.SearchResult
|
||||||
|
searchRes, err = ldapConn.Search(searchReq)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("PasswordFound %v", err)
|
||||||
|
log.Printf("PasswordFound %v", searchReq)
|
||||||
|
log.Printf("PasswordFound %v", ldapConn)
|
||||||
|
log.Printf("PasswordFound %v", searchRes)
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
if len(searchRes.Entries) == 0 {
|
||||||
|
log.Printf("PasswordFound %v", err)
|
||||||
|
log.Printf("PasswordFound %v", searchReq)
|
||||||
|
log.Printf("PasswordFound %v", ldapConn)
|
||||||
|
log.Printf("PasswordFound %v", searchRes)
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
delReq := ldap.NewDelRequest("uid="+user.CN+","+config.InvitationBaseDN, nil)
|
||||||
|
ldapConn.Del(delReq)
|
||||||
|
return searchRes.Entries[0].GetAttributeValue("seeAlso"), err
|
||||||
|
}
|
199
models/user.go
Normal file
199
models/user.go
Normal file
|
@ -0,0 +1,199 @@
|
||||||
|
/*
|
||||||
|
Model-User Handles everything having to do with the user.
|
||||||
|
*/
|
||||||
|
package models
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/go-ldap/ldap/v3"
|
||||||
|
)
|
||||||
|
|
||||||
|
func replaceIfContent(modifReq *ldap.ModifyRequest, key string, value string, previousValue string) error {
|
||||||
|
if value != "" {
|
||||||
|
modifReq.Replace(key, []string{value})
|
||||||
|
} else if previousValue != "" {
|
||||||
|
modifReq.Delete(key, []string{previousValue})
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetUser(user User, config *ConfigFile, ldapConn *ldap.Conn) (*User, error) {
|
||||||
|
return get(user, config, ldapConn)
|
||||||
|
}
|
||||||
|
|
||||||
|
func get(user User, config *ConfigFile, ldapConn *ldap.Conn) (*User, error) {
|
||||||
|
searchReq := ldap.NewSearchRequest(
|
||||||
|
user.DN,
|
||||||
|
ldap.ScopeBaseObject,
|
||||||
|
ldap.NeverDerefAliases,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
false,
|
||||||
|
"(objectClass=*)",
|
||||||
|
[]string{
|
||||||
|
"cn",
|
||||||
|
"givenName",
|
||||||
|
"displayName",
|
||||||
|
"uid",
|
||||||
|
"sn",
|
||||||
|
"mail",
|
||||||
|
"description",
|
||||||
|
"carLicense",
|
||||||
|
},
|
||||||
|
nil)
|
||||||
|
searchRes, err := ldapConn.Search(searchReq)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("get User : %v", err)
|
||||||
|
log.Printf("get User : %v", searchReq)
|
||||||
|
log.Printf("get User : %v", searchRes)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
userEntry := searchRes.Entries[0]
|
||||||
|
resUser := User{
|
||||||
|
DN: user.DN,
|
||||||
|
GivenName: searchRes.Entries[0].GetAttributeValue("givenName"),
|
||||||
|
DisplayName: searchRes.Entries[0].GetAttributeValue("displayName"),
|
||||||
|
Description: searchRes.Entries[0].GetAttributeValue("description"),
|
||||||
|
SN: searchRes.Entries[0].GetAttributeValue("sn"),
|
||||||
|
UID: searchRes.Entries[0].GetAttributeValue("uid"),
|
||||||
|
CN: searchRes.Entries[0].GetAttributeValue("cn"),
|
||||||
|
Mail: searchRes.Entries[0].GetAttributeValue("mail"),
|
||||||
|
OtherMailbox: searchRes.Entries[0].GetAttributeValue("carLicense"),
|
||||||
|
CanAdmin: strings.EqualFold(user.DN, config.AdminAccount),
|
||||||
|
CanInvite: true,
|
||||||
|
UserEntry: userEntry,
|
||||||
|
}
|
||||||
|
searchReq.BaseDN = config.GroupCanAdmin
|
||||||
|
searchReq.Filter = "(member=" + user.DN + ")"
|
||||||
|
searchRes, err = ldapConn.Search(searchReq)
|
||||||
|
if err == nil {
|
||||||
|
if len(searchRes.Entries) > 0 {
|
||||||
|
resUser.CanAdmin = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return &resUser, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func AddUser(user User, config *ConfigFile, ldapConn *ldap.Conn) error {
|
||||||
|
return add(user, config, ldapConn)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Adds a new user
|
||||||
|
func add(user User, config *ConfigFile, ldapConn *ldap.Conn) error {
|
||||||
|
log.Printf(fmt.Sprint("Adding New User"))
|
||||||
|
// LDAP Add Object
|
||||||
|
dn := user.DN
|
||||||
|
req := ldap.NewAddRequest(dn, nil)
|
||||||
|
req.Attribute("objectClass", []string{"top", "person", "organizationalPerson", "inetOrgPerson"})
|
||||||
|
if user.DisplayName != "" {
|
||||||
|
req.Attribute("displayName", []string{user.DisplayName})
|
||||||
|
}
|
||||||
|
if user.GivenName != "" {
|
||||||
|
req.Attribute("givenName", []string{user.GivenName})
|
||||||
|
}
|
||||||
|
if user.Mail != "" {
|
||||||
|
req.Attribute("mail", []string{user.Mail})
|
||||||
|
}
|
||||||
|
if user.UID != "" {
|
||||||
|
req.Attribute("uid", []string{user.UID})
|
||||||
|
}
|
||||||
|
// if user.Member != "" {
|
||||||
|
// req.Attribute("member", []string{user.Member})
|
||||||
|
// }
|
||||||
|
if user.SN != "" {
|
||||||
|
req.Attribute("sn", []string{user.SN})
|
||||||
|
}
|
||||||
|
if user.OtherMailbox != "" {
|
||||||
|
req.Attribute("carLicense", []string{user.OtherMailbox})
|
||||||
|
}
|
||||||
|
if user.Description != "" {
|
||||||
|
req.Attribute("description", []string{user.Description})
|
||||||
|
}
|
||||||
|
// Add the User
|
||||||
|
// err := ldapConn.Add(req)
|
||||||
|
// var ldapNewConn *ldap.Conn
|
||||||
|
ldapNewConn, err := OpenNewUserLdap(config)
|
||||||
|
err = ldapNewConn.Add(req)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf(fmt.Sprintf("add(User) ldapconn.Add: %v", err))
|
||||||
|
log.Printf(fmt.Sprintf("add(User) ldapconn.Add: %v", req))
|
||||||
|
log.Printf(fmt.Sprintf("add(User) ldapconn.Add: %v", user))
|
||||||
|
//return err
|
||||||
|
}
|
||||||
|
// passwordModifyRequest := ldap.NewPasswordModifyRequest(user.DN, "", user.Password)
|
||||||
|
// _, err = ldapConn.PasswordModify(passwordModifyRequest)
|
||||||
|
// if err != nil {
|
||||||
|
// return err
|
||||||
|
// }
|
||||||
|
|
||||||
|
// Send the email
|
||||||
|
|
||||||
|
newUserLdapConn, _ := OpenNewUserLdap(config)
|
||||||
|
user.OtherMailbox = ""
|
||||||
|
err = PasswordLost(user, config, newUserLdapConn)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("add User PasswordLost %v", err)
|
||||||
|
log.Printf("add User PasswordLost %v", newUserLdapConn)
|
||||||
|
}
|
||||||
|
|
||||||
|
// sendMailTplData := SendMailTplData{
|
||||||
|
// From: "alice@resdigita.org",
|
||||||
|
// To: user.OtherMailbox,
|
||||||
|
// RelTemplatePath: "user/new.email.txt",
|
||||||
|
// EmailContentVars: EmailContentVarsTplData{
|
||||||
|
// InviteFrom: "alice@resdigita.org",
|
||||||
|
// SendAddress: "https://www.gvoisins.org",
|
||||||
|
// Code: "...",
|
||||||
|
// },
|
||||||
|
// }
|
||||||
|
// err = sendMail(sendMailTplData)
|
||||||
|
// if err != nil {
|
||||||
|
// log.Printf("add(user) sendMail: %v", err)
|
||||||
|
// log.Printf("add(user) sendMail: %v", user)
|
||||||
|
// log.Printf("add(user) sendMail: %v", sendMailTplData)
|
||||||
|
// }
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func ModifyUser(user User, config *ConfigFile, ldapConn *ldap.Conn) error {
|
||||||
|
return modify(user, config, ldapConn)
|
||||||
|
}
|
||||||
|
|
||||||
|
func modify(user User, config *ConfigFile, ldapConn *ldap.Conn) error {
|
||||||
|
modify_request := ldap.NewModifyRequest(user.DN, nil)
|
||||||
|
previousUser, err := get(user, config, ldapConn)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
replaceIfContent(modify_request, "displayName", user.DisplayName, previousUser.DisplayName)
|
||||||
|
replaceIfContent(modify_request, "givenName", user.GivenName, previousUser.GivenName)
|
||||||
|
replaceIfContent(modify_request, "sn", user.SN, previousUser.SN)
|
||||||
|
replaceIfContent(modify_request, "carLicense", user.OtherMailbox, user.OtherMailbox)
|
||||||
|
replaceIfContent(modify_request, "description", user.Description, previousUser.Description)
|
||||||
|
err = ldapConn.Modify(modify_request)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf(fmt.Sprintf("71: %v", err))
|
||||||
|
log.Printf(fmt.Sprintf("72: %v", modify_request))
|
||||||
|
log.Printf(fmt.Sprintf("73: %v", user))
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func PassWD(user User, config *ConfigFile, ldapConn *ldap.Conn) error {
|
||||||
|
passwordModifyRequest := ldap.NewPasswordModifyRequest(user.DN, "", user.Password)
|
||||||
|
_, err := ldapConn.PasswordModify(passwordModifyRequest)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf(fmt.Sprintf("model-user PassWD : %v %v", err, ldapConn))
|
||||||
|
log.Printf(fmt.Sprintf("model-user PassWD : %v", user))
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func Bind(user User, config *ConfigFile, ldapConn *ldap.Conn) error {
|
||||||
|
return ldapConn.Bind(user.DN, user.Password)
|
||||||
|
}
|
||||||
|
|
1029
views/admin.go
Normal file
1029
views/admin.go
Normal file
File diff suppressed because it is too large
Load diff
58
views/directory.go
Normal file
58
views/directory.go
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
package views
|
||||||
|
|
||||||
|
import (
|
||||||
|
"guichet/models"
|
||||||
|
"html/template"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
// "sort"
|
||||||
|
"strings"
|
||||||
|
// "golang.org/x/crypto/openpgp/errors"
|
||||||
|
// "honnef.co/go/tools/analysis/facts/nilness"
|
||||||
|
// "github.com/go-ldap/ldap/v3"
|
||||||
|
)
|
||||||
|
|
||||||
|
const FIELD_NAME_PROFILE_PICTURE = "profilePicture"
|
||||||
|
const FIELD_NAME_DIRECTORY_VISIBILITY = "directoryVisibility"
|
||||||
|
|
||||||
|
func HandleDirectory(w http.ResponseWriter, r *http.Request) {
|
||||||
|
templateDirectory := getTemplate("directory.html")
|
||||||
|
|
||||||
|
login := checkLogin(w, r)
|
||||||
|
if login == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
templateDirectory.Execute(w, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func HandleDirectorySearch(w http.ResponseWriter, r *http.Request) error {
|
||||||
|
templateDirectoryResults := template.Must(template.ParseFiles(templatePath + "/directory_results.html"))
|
||||||
|
|
||||||
|
//Get input value by user
|
||||||
|
r.ParseMultipartForm(1024)
|
||||||
|
input := strings.TrimSpace(strings.Join(r.Form["query"], ""))
|
||||||
|
|
||||||
|
if r.Method != "POST" {
|
||||||
|
http.Error(w, "Invalid request", http.StatusBadRequest)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
//Log to allow the research
|
||||||
|
login := checkLogin(w, r)
|
||||||
|
if login == nil {
|
||||||
|
http.Error(w, "Login required", http.StatusUnauthorized)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
search_results, err := models.DoDirectorySearch(login.conn, input)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
templateDirectoryResults.Execute(w, search_results)
|
||||||
|
return nil
|
||||||
|
|
||||||
|
}
|
43
views/home.go
Normal file
43
views/home.go
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
/*
|
||||||
|
home show the home page
|
||||||
|
*/
|
||||||
|
|
||||||
|
package views
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
func HandleHome(w http.ResponseWriter, r *http.Request) {
|
||||||
|
templateHome := getTemplate("home.html")
|
||||||
|
|
||||||
|
login := checkLogin(w, r)
|
||||||
|
if login == nil {
|
||||||
|
status, _ := HandleLogin(w, r)
|
||||||
|
if status == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
login = checkLogin(w, r)
|
||||||
|
}
|
||||||
|
|
||||||
|
can_admin := false
|
||||||
|
if login != nil {
|
||||||
|
can_admin = login.Common.CanAdmin
|
||||||
|
}
|
||||||
|
|
||||||
|
data := HomePageData{
|
||||||
|
Login: NestedLoginTplData{
|
||||||
|
Login: login,
|
||||||
|
},
|
||||||
|
BaseDN: config.BaseDN,
|
||||||
|
Org: config.Org,
|
||||||
|
Common: NestedCommonTplData{
|
||||||
|
CanAdmin: can_admin,
|
||||||
|
CanInvite: true,
|
||||||
|
LoggedIn: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
execTemplate(w, templateHome, data.Common, data.Login, data)
|
||||||
|
// templateHome.Execute(w, data)
|
||||||
|
|
||||||
|
}
|
18
views/http.go
Normal file
18
views/http.go
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
/*
|
||||||
|
http-utils provide utility functions that interact with http
|
||||||
|
*/
|
||||||
|
|
||||||
|
package views
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
func LogRequest(Handler http.Handler) http.Handler {
|
||||||
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
// log.Printf("%s %s %s\n", r.RemoteAddr, r.Method, r.URL)
|
||||||
|
Handler.ServeHTTP(w, r)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
|
430
views/invite.go
Normal file
430
views/invite.go
Normal file
|
@ -0,0 +1,430 @@
|
||||||
|
package views
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"crypto/rand"
|
||||||
|
"encoding/binary"
|
||||||
|
"encoding/hex"
|
||||||
|
"fmt"
|
||||||
|
"guichet/models"
|
||||||
|
"guichet/utils"
|
||||||
|
"html/template"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
// "crypto/rand"
|
||||||
|
|
||||||
|
// "github.com/emersion/go-smtp"
|
||||||
|
"github.com/go-ldap/ldap/v3"
|
||||||
|
"github.com/gorilla/mux"
|
||||||
|
"golang.org/x/crypto/argon2"
|
||||||
|
)
|
||||||
|
|
||||||
|
var EMAIL_REGEXP = regexp.MustCompile("^[a-zA-Z0-9.!#$%&'*+\\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$")
|
||||||
|
|
||||||
|
func checkInviterLogin(w http.ResponseWriter, r *http.Request) *LoginStatus {
|
||||||
|
|
||||||
|
login := checkLogin(w, r)
|
||||||
|
if login == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// if !login.CanInvite {
|
||||||
|
// http.Error(w, "Not authorized to invite new users.", http.StatusUnauthorized)
|
||||||
|
// return nil
|
||||||
|
// }
|
||||||
|
|
||||||
|
return login
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
func HandleInviteNewAccount(w http.ResponseWriter, r *http.Request) {
|
||||||
|
l, err := ldapOpen(w)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("view-invite.go - HandleInviteNewAccount - ldapOpen : %v", err)
|
||||||
|
log.Printf("view-invite.go - HandleInviteNewAccount - ldapOpen: %v", l)
|
||||||
|
}
|
||||||
|
if l == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = l.Bind(config.NewUserDN, config.NewUserPassword)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("view-invite.go - HandleInviteNewAccount - l.Bind : %v", err)
|
||||||
|
log.Printf("view-invite.go - HandleInviteNewAccount - l.Bind: %v", config.NewUserDN)
|
||||||
|
panic(fmt.Sprintf("view-invite.go - HandleInviteNewAccount - l.Bind : %v", err))
|
||||||
|
}
|
||||||
|
HandleNewAccount(w, r, l, config.NewUserDN)
|
||||||
|
}
|
||||||
|
|
||||||
|
// New account creation using code
|
||||||
|
func HandleInvitationCode(w http.ResponseWriter, r *http.Request) {
|
||||||
|
code := mux.Vars(r)["code"]
|
||||||
|
code_id, code_pw := readCode(code)
|
||||||
|
login := checkLogin(w, r)
|
||||||
|
inviteDn := config.InvitationNameAttr + "=" + code_id + "," + config.InvitationBaseDN
|
||||||
|
err := login.conn.Bind(inviteDn, code_pw)
|
||||||
|
if err != nil {
|
||||||
|
templateInviteInvalidCode := getTemplate("user/code/invalid.html")
|
||||||
|
templateInviteInvalidCode.Execute(w, nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
sReq := ldap.NewSearchRequest(
|
||||||
|
inviteDn,
|
||||||
|
ldap.ScopeBaseObject, ldap.NeverDerefAliases, 0, 0, false,
|
||||||
|
fmt.Sprintf("(objectclass=*)"),
|
||||||
|
[]string{"dn", "creatorsname"},
|
||||||
|
nil)
|
||||||
|
sr, err := login.conn.Search(sReq)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if len(sr.Entries) != 1 {
|
||||||
|
http.Error(w, fmt.Sprintf("Expected 1 entry, got %d", len(sr.Entries)), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
invitedBy := sr.Entries[0].GetAttributeValue("creatorsname")
|
||||||
|
if HandleNewAccount(w, r, login.conn, invitedBy) {
|
||||||
|
del_req := ldap.NewDelRequest(inviteDn, nil)
|
||||||
|
err = login.conn.Del(del_req)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Could not delete invitation %s: %s", inviteDn, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Common functions for new account
|
||||||
|
func HandleNewAccount(w http.ResponseWriter, r *http.Request, l *ldap.Conn, invitedBy string) bool {
|
||||||
|
templateInviteNewAccount := getTemplate("user/new.html")
|
||||||
|
data := NewAccountData{
|
||||||
|
NewUserDefaultDomain: config.NewUserDefaultDomain,
|
||||||
|
}
|
||||||
|
if r.Method == "POST" {
|
||||||
|
r.ParseForm()
|
||||||
|
newUser := models.User{}
|
||||||
|
newUser.DisplayName = strings.TrimSpace(strings.Join(r.Form["displayname"], ""))
|
||||||
|
newUser.GivenName = strings.TrimSpace(strings.Join(r.Form["givenname"], ""))
|
||||||
|
newUser.SN = strings.TrimSpace(strings.Join(r.Form["surname"], ""))
|
||||||
|
newUser.OtherMailbox = strings.TrimSpace(strings.Join(r.Form["othermailbox"], ""))
|
||||||
|
newUser.Mail = strings.TrimSpace(strings.Join(r.Form["mail"], ""))
|
||||||
|
newUser.UID = strings.TrimSpace(strings.Join(r.Form["username"], ""))
|
||||||
|
newUser.CN = strings.TrimSpace(strings.Join(r.Form["username"], ""))
|
||||||
|
newUser.DN = "cn=" + strings.TrimSpace(strings.Join(r.Form["username"], "")) + "," + config.UserBaseDN
|
||||||
|
password1 := strings.Join(r.Form["password"], "")
|
||||||
|
password2 := strings.Join(r.Form["password2"], "")
|
||||||
|
if password1 != password2 {
|
||||||
|
data.Common.Success = false
|
||||||
|
data.ErrorPasswordMismatch = true
|
||||||
|
} else {
|
||||||
|
newUser.Password = password2
|
||||||
|
l.Bind(config.NewUserDN, config.NewUserPassword)
|
||||||
|
err := models.AddUser(newUser, &config, l)
|
||||||
|
if err != nil {
|
||||||
|
data.Common.Success = false
|
||||||
|
data.Common.ErrorMessage = err.Error()
|
||||||
|
}
|
||||||
|
http.Redirect(w, r, "/user/wait", http.StatusFound)
|
||||||
|
}
|
||||||
|
// tryCreateAccount(l, data, password1, password2, invitedBy)
|
||||||
|
} else {
|
||||||
|
data.SuggestPW = fmt.Sprintf("%s", models.SuggestPassword())
|
||||||
|
}
|
||||||
|
data.Common.CanAdmin = false
|
||||||
|
data.Common.LoggedIn = false
|
||||||
|
|
||||||
|
templateInviteNewAccount.Execute(w, data)
|
||||||
|
return data.Common.Success
|
||||||
|
}
|
||||||
|
|
||||||
|
func tryCreateAccount(l *ldap.Conn, data *NewAccountData, pass1 string, pass2 string, invitedBy string) {
|
||||||
|
checkFailed := false
|
||||||
|
// Check if username is correct
|
||||||
|
if match, err := regexp.MatchString("^[a-z0-9._-]+$", data.Username); !(err == nil && match) {
|
||||||
|
data.ErrorInvalidUsername = true
|
||||||
|
checkFailed = true
|
||||||
|
}
|
||||||
|
// Check if user exists
|
||||||
|
userDn := config.UserNameAttr + "=" + data.Username + "," + config.UserBaseDN
|
||||||
|
searchRq := ldap.NewSearchRequest(
|
||||||
|
userDn,
|
||||||
|
ldap.ScopeBaseObject, ldap.NeverDerefAliases, 0, 0, false,
|
||||||
|
"(objectclass=*)",
|
||||||
|
[]string{"dn"},
|
||||||
|
nil)
|
||||||
|
sr, err := l.Search(searchRq)
|
||||||
|
if err != nil {
|
||||||
|
data.Common.ErrorMessage = err.Error()
|
||||||
|
checkFailed = true
|
||||||
|
}
|
||||||
|
if len(sr.Entries) > 0 {
|
||||||
|
data.ErrorUsernameTaken = true
|
||||||
|
checkFailed = true
|
||||||
|
}
|
||||||
|
// Check that password is long enough
|
||||||
|
if len(pass1) < 8 {
|
||||||
|
data.ErrorPasswordTooShort = true
|
||||||
|
checkFailed = true
|
||||||
|
}
|
||||||
|
if pass1 != pass2 {
|
||||||
|
data.ErrorPasswordMismatch = true
|
||||||
|
checkFailed = true
|
||||||
|
}
|
||||||
|
if checkFailed {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Actually create user
|
||||||
|
req := ldap.NewAddRequest(userDn, nil)
|
||||||
|
req.Attribute("objectclass", []string{"inetOrgPerson", "organizationalPerson", "person", "top"})
|
||||||
|
req.Attribute("structuralobjectclass", []string{"inetOrgPerson"})
|
||||||
|
pw, err := utils.SSHAEncode(pass1)
|
||||||
|
if err != nil {
|
||||||
|
data.Common.ErrorMessage = err.Error()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
req.Attribute("userpassword", []string{pw})
|
||||||
|
req.Attribute("invitedby", []string{invitedBy})
|
||||||
|
if len(data.DisplayName) > 0 {
|
||||||
|
req.Attribute("displayname", []string{data.DisplayName})
|
||||||
|
}
|
||||||
|
if len(data.GivenName) > 0 {
|
||||||
|
req.Attribute("givenname", []string{data.GivenName})
|
||||||
|
}
|
||||||
|
if len(data.Surname) > 0 {
|
||||||
|
req.Attribute("sn", []string{data.Surname})
|
||||||
|
}
|
||||||
|
if len(config.InvitedMailFormat) > 0 {
|
||||||
|
email := strings.ReplaceAll(config.InvitedMailFormat, "{}", data.Username)
|
||||||
|
req.Attribute("mail", []string{email})
|
||||||
|
}
|
||||||
|
err = l.Add(req)
|
||||||
|
if err != nil {
|
||||||
|
data.Common.ErrorMessage = err.Error()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for _, group := range config.InvitedAutoGroups {
|
||||||
|
req := ldap.NewModifyRequest(group, nil)
|
||||||
|
req.Add("member", []string{userDn})
|
||||||
|
err = l.Modify(req)
|
||||||
|
if err != nil {
|
||||||
|
data.Common.WarningMessage += fmt.Sprintf("Cannot add to %s: %s\n", group, err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
data.Common.Success = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- Code generation ----
|
||||||
|
func HandleInviteSendCode(w http.ResponseWriter, r *http.Request) {
|
||||||
|
templateInviteSendCode := getTemplate("user/code/send.html")
|
||||||
|
login := checkInviterLogin(w, r)
|
||||||
|
if login == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if r.Method == "POST" {
|
||||||
|
r.ParseForm()
|
||||||
|
data := &SendCodeData{
|
||||||
|
WebBaseAddress: config.WebAddress,
|
||||||
|
}
|
||||||
|
// modify_request := ldap.NewModifyRequest(login.UserEntry.DN, nil)
|
||||||
|
// // choice := strings.Join(r.Form["choice"], "")
|
||||||
|
// // sendto := strings.Join(r.Form["sendto"], "")
|
||||||
|
code, code_id, code_pw := genCode()
|
||||||
|
log.Printf("272: %v %v %v", code, code_id, code_pw)
|
||||||
|
// // Create invitation object in database
|
||||||
|
// modify_request.Add("carLicense", []string{fmt.Sprintf("%s,%s,%s",code, code_id, code_pw)})
|
||||||
|
// err := login.conn.Modify(modify_request)
|
||||||
|
// if err != nil {
|
||||||
|
// data.Common.ErrorMessage = err.Error()
|
||||||
|
// // return
|
||||||
|
// } else {
|
||||||
|
// data.Common.Success = true
|
||||||
|
// data.CodeDisplay = code
|
||||||
|
// }
|
||||||
|
log.Printf("279: %v %v %v", code, code_id, code_pw)
|
||||||
|
addReq := ldap.NewAddRequest("documentIdentifier="+code_id+","+config.InvitationBaseDN, nil)
|
||||||
|
addReq.Attribute("objectClass", []string{"top", "document", "simpleSecurityObject"})
|
||||||
|
addReq.Attribute("cn", []string{code})
|
||||||
|
addReq.Attribute("userPassword", []string{code_pw})
|
||||||
|
addReq.Attribute("documentIdentifier", []string{code_id})
|
||||||
|
log.Printf("285: %v %v %v", code, code_id, code_pw)
|
||||||
|
log.Printf("286: %v", addReq)
|
||||||
|
err := login.conn.Add(addReq)
|
||||||
|
if err != nil {
|
||||||
|
data.Common.ErrorMessage = err.Error()
|
||||||
|
// return
|
||||||
|
} else {
|
||||||
|
data.Common.Success = true
|
||||||
|
data.CodeDisplay = code
|
||||||
|
}
|
||||||
|
data.Common.CanAdmin = login.Common.CanAdmin
|
||||||
|
templateInviteSendCode.Execute(w, data)
|
||||||
|
// if choice == "display" || choice == "send" {
|
||||||
|
// log.Printf("260: %v %v %v %v", login, choice, sendto, data)
|
||||||
|
// trySendCode(login, choice, sendto, data)
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func trySendCode(login *LoginStatus, choice string, sendto string, data *SendCodeData) {
|
||||||
|
log.Printf("269: %v %v %v %v", login, choice, sendto, data)
|
||||||
|
// Generate code
|
||||||
|
code, code_id, code_pw := genCode()
|
||||||
|
log.Printf("272: %v %v %v", code, code_id, code_pw)
|
||||||
|
// Create invitation object in database
|
||||||
|
// len_base_dn := len(strings.Split(config.BaseDN, ","))
|
||||||
|
// dn_split := strings.Split(super_dn, ",")
|
||||||
|
// for i := len_base_dn + 1; i <= len(dn_split); i++ {
|
||||||
|
// path = append(path, PathItem{
|
||||||
|
// DN: strings.Join(dn_split[len(dn_split)-i:len(dn_split)], ","),
|
||||||
|
// Identifier: dn_split[len(dn_split)-i],
|
||||||
|
// })
|
||||||
|
// }
|
||||||
|
// data := &SendCodeData{
|
||||||
|
// WebBaseAddress: config.WebAddress,
|
||||||
|
// }
|
||||||
|
// // Handle data
|
||||||
|
// data := &CreateData{
|
||||||
|
// SuperDN: super_dn,
|
||||||
|
// Path: path,
|
||||||
|
// }
|
||||||
|
// data.IdType = config.UserNameAttr
|
||||||
|
// data.StructuralObjectClass = "inetOrgPerson"
|
||||||
|
// data.ObjectClass = "inetOrgPerson\norganizationalPerson\nperson\ntop"
|
||||||
|
// data.IdValue = strings.TrimSpace(strings.Join(r.Form["idvalue"], ""))
|
||||||
|
// data.DisplayName = strings.TrimSpace(strings.Join(r.Form["displayname"], ""))
|
||||||
|
// data.GivenName = strings.TrimSpace(strings.Join(r.Form["givenname"], ""))
|
||||||
|
// data.Mail = strings.TrimSpace(strings.Join(r.Form["mail"], ""))
|
||||||
|
// data.Member = strings.TrimSpace(strings.Join(r.Form["member"], ""))
|
||||||
|
// data.Description = strings.TrimSpace(strings.Join(r.Form["description"], ""))
|
||||||
|
// data.SN = strings.TrimSpace(strings.Join(r.Form["sn"], ""))
|
||||||
|
// object_class := []string{}
|
||||||
|
// for _, oc := range strings.Split(data.ObjectClass, "\n") {
|
||||||
|
// x := strings.TrimSpace(oc)
|
||||||
|
// if x != "" {
|
||||||
|
// object_class = append(object_class, x)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// dn := data.IdType + "=" + data.IdValue + "," + super_dn
|
||||||
|
// req := ldap.NewAddRequest(dn, nil)
|
||||||
|
// req.Attribute("objectclass", object_class)
|
||||||
|
// // req.Attribute("mail", []string{data.IdValue})
|
||||||
|
// /*
|
||||||
|
// if data.StructuralObjectClass != "" {
|
||||||
|
// req.Attribute("structuralobjectclass", []string{data.StructuralObjectClass})
|
||||||
|
// }
|
||||||
|
// */
|
||||||
|
// if data.DisplayName != "" {
|
||||||
|
// req.Attribute("displayname", []string{data.DisplayName})
|
||||||
|
// }
|
||||||
|
// if data.GivenName != "" {
|
||||||
|
// req.Attribute("givenname", []string{data.GivenName})
|
||||||
|
// }
|
||||||
|
// if data.Mail != "" {
|
||||||
|
// req.Attribute("mail", []string{data.Mail})
|
||||||
|
// }
|
||||||
|
// if data.Member != "" {
|
||||||
|
// req.Attribute("member", []string{data.Member})
|
||||||
|
// }
|
||||||
|
// if data.SN != "" {
|
||||||
|
// req.Attribute("sn", []string{data.SN})
|
||||||
|
// }
|
||||||
|
// if data.Description != "" {
|
||||||
|
// req.Attribute("description", []string{data.Description})
|
||||||
|
// }
|
||||||
|
// err := login.conn.Add(req)
|
||||||
|
// // log.Printf("899: %v",err)
|
||||||
|
// // log.Printf("899: %v",req)
|
||||||
|
// // log.Printf("899: %v",data)
|
||||||
|
// if err != nil {
|
||||||
|
// data.Common.Error = err.Error()
|
||||||
|
// } else {
|
||||||
|
// if template == "ml" {
|
||||||
|
// http.Redirect(w, r, "/admin/mailing/"+data.IdValue, http.StatusFound)
|
||||||
|
// } else {
|
||||||
|
// http.Redirect(w, r, "/admin/ldap/"+dn, http.StatusFound)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// inviteDn := config.InvitationNameAttr + "=" + code_id + "," + config.InvitationBaseDN
|
||||||
|
// req := ldap.NewAddRequest(inviteDn, nil)
|
||||||
|
// pw, err := SSHAEncode(code_pw)
|
||||||
|
// if err != nil {
|
||||||
|
// data.Common.ErrorMessage = err.Error()
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
// req.Attribute("employeeNumber", []string{pw})
|
||||||
|
// req.Attribute("objectclass", []string{"top", "invitationCode"})
|
||||||
|
// err = login.conn.Add(req)
|
||||||
|
// if err != nil {
|
||||||
|
// log.Printf("286: %v", req)
|
||||||
|
// data.Common.ErrorMessage = err.Error()
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
|
||||||
|
// If we want to display it, do so
|
||||||
|
if choice == "display" {
|
||||||
|
data.Common.Success = true
|
||||||
|
data.CodeDisplay = code
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Otherwise, we are sending a mail
|
||||||
|
if !EMAIL_REGEXP.MatchString(sendto) {
|
||||||
|
data.ErrorInvalidEmail = true
|
||||||
|
return
|
||||||
|
}
|
||||||
|
templateMail := template.Must(template.ParseFiles(templatePath + "/invite_mail.txt"))
|
||||||
|
buf := bytes.NewBuffer([]byte{})
|
||||||
|
templateMail.Execute(buf, &models.CodeMailFields{
|
||||||
|
To: sendto,
|
||||||
|
From: config.MailFrom,
|
||||||
|
InviteFrom: login.WelcomeName(),
|
||||||
|
Code: code,
|
||||||
|
WebBaseAddress: config.WebAddress,
|
||||||
|
})
|
||||||
|
log.Printf("Sending mail to: %s", sendto)
|
||||||
|
// var auth sasl.Client = nil
|
||||||
|
// if config.SMTPUsername != "" {
|
||||||
|
// auth = sasl.NewPlainClient("", config.SMTPUsername, config.SMTPPassword)
|
||||||
|
// }
|
||||||
|
// err = smtp.SendMail(config.SMTPServer, auth, config.MailFrom, []string{sendto}, buf)
|
||||||
|
// if err != nil {
|
||||||
|
// data.Common.ErrorMessage = err.Error()
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
// log.Printf("Mail sent.")
|
||||||
|
data.Common.Success = true
|
||||||
|
data.CodeSentTo = sendto
|
||||||
|
}
|
||||||
|
|
||||||
|
func genCode() (code string, code_id string, code_pw string) {
|
||||||
|
random := make([]byte, 32)
|
||||||
|
n, err := rand.Read(random)
|
||||||
|
if err != nil || n != 32 {
|
||||||
|
log.Fatalf("Could not generate random bytes: %s", err)
|
||||||
|
}
|
||||||
|
a := binary.BigEndian.Uint32(random[0:4])
|
||||||
|
b := binary.BigEndian.Uint32(random[4:8])
|
||||||
|
c := binary.BigEndian.Uint32(random[8:12])
|
||||||
|
code = fmt.Sprintf("%03d-%03d-%03d", a%1000, b%1000, c%1000)
|
||||||
|
code_id, code_pw = readCode(code)
|
||||||
|
log.Printf("342: %v %v %v", code, code_id, code_pw)
|
||||||
|
return code, code_id, code_pw
|
||||||
|
}
|
||||||
|
|
||||||
|
func readCode(code string) (code_id string, code_pw string) {
|
||||||
|
// Strip everything that is not a digit
|
||||||
|
code_digits := ""
|
||||||
|
for _, c := range code {
|
||||||
|
if c >= '0' && c <= '9' {
|
||||||
|
code_digits = code_digits + string(c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
id_hash := argon2.IDKey([]byte(code_digits), []byte("Guichet ID"), 2, 64*1024, 4, 32)
|
||||||
|
pw_hash := argon2.IDKey([]byte(code_digits), []byte("Guichet PW"), 2, 64*1024, 4, 32)
|
||||||
|
code_id = hex.EncodeToString(id_hash[:8])
|
||||||
|
code_pw = hex.EncodeToString(pw_hash[:16])
|
||||||
|
return code_id, code_pw
|
||||||
|
}
|
144
views/login.go
Normal file
144
views/login.go
Normal file
|
@ -0,0 +1,144 @@
|
||||||
|
/*
|
||||||
|
login Handles login and current-user verification
|
||||||
|
*/
|
||||||
|
|
||||||
|
package views
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/go-ldap/ldap/v3"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
func HandleLogout(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
|
err := logout(w, r)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
http.Redirect(w, r, "/", http.StatusFound)
|
||||||
|
}
|
||||||
|
|
||||||
|
func HandleLogin(w http.ResponseWriter, r *http.Request) (*LoginInfo, error) {
|
||||||
|
templateLogin := getTemplate("login.html")
|
||||||
|
|
||||||
|
if r.Method == "POST" {
|
||||||
|
// log.Printf("%v", "Parsing Form HandleLogin")
|
||||||
|
r.ParseForm()
|
||||||
|
|
||||||
|
username := strings.Join(r.Form["username"], "")
|
||||||
|
password := strings.Join(r.Form["password"], "")
|
||||||
|
l, _ := ldapOpen(w)
|
||||||
|
|
||||||
|
user_dn := fmt.Sprintf("%s=%s,%s", config.UserNameAttr, username, config.UserBaseDN)
|
||||||
|
|
||||||
|
// log.Printf("%v", user_dn)
|
||||||
|
// log.Printf("%v", username)
|
||||||
|
|
||||||
|
if strings.EqualFold(username, config.AdminAccount) {
|
||||||
|
user_dn = username
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
err := l.Bind(user_dn, password)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("DoLogin : %v", err)
|
||||||
|
log.Printf("DoLogin : %v", user_dn)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// func encodePassword(inPassword string) (string, error) {
|
||||||
|
// utf16 := unicode.UTF16(unicode.LittleEndian, unicode.IgnoreBOM)
|
||||||
|
// return utf16.NewEncoder().String("\"" + inPassword + "\"")
|
||||||
|
// // if err != nil {
|
||||||
|
// // log.Printf("Error encoding password: %s", err)
|
||||||
|
// // return err
|
||||||
|
// // }
|
||||||
|
|
||||||
|
// }
|
||||||
|
|
||||||
|
|
||||||
|
// log.Printf("%v", LoginInfo)
|
||||||
|
if err != nil {
|
||||||
|
data := &LoginFormData{
|
||||||
|
Username: username,
|
||||||
|
Common: NestedCommonTplData{
|
||||||
|
CanAdmin: false,
|
||||||
|
CanInvite: true,
|
||||||
|
LoggedIn: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
if ldap.IsErrorWithCode(err, ldap.LDAPResultInvalidCredentials) {
|
||||||
|
data.WrongPass = true
|
||||||
|
} else if ldap.IsErrorWithCode(err, ldap.LDAPResultNoSuchObject) {
|
||||||
|
data.WrongUser = true
|
||||||
|
} else {
|
||||||
|
log.Printf("%v", err)
|
||||||
|
log.Printf("%v", user_dn)
|
||||||
|
log.Printf("%v", username)
|
||||||
|
data.Common.ErrorMessage = err.Error()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Successfully logged in, save it to session
|
||||||
|
session, err := GuichetSessionStore.Get(r, SESSION_NAME)
|
||||||
|
if err != nil {
|
||||||
|
session, _ = GuichetSessionStore.New(r, SESSION_NAME)
|
||||||
|
}
|
||||||
|
session.Values["login_username"] = username
|
||||||
|
session.Values["login_password"] = password
|
||||||
|
session.Values["login_dn"] = user_dn
|
||||||
|
|
||||||
|
err = session.Save(r, w)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("DoLogin Session Save: %v", err)
|
||||||
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// // templateLogin.Execute(w, data)
|
||||||
|
// execTemplate(w, templateLogin, data.Common, NestedLoginTplData{}, *config, data)
|
||||||
|
|
||||||
|
http.Redirect(w, r, "/", http.StatusTemporaryRedirect)
|
||||||
|
|
||||||
|
} else if r.Method == "GET" {
|
||||||
|
execTemplate(w, templateLogin, NestedCommonTplData{
|
||||||
|
CanAdmin: false,
|
||||||
|
CanInvite: true,
|
||||||
|
LoggedIn: false}, NestedLoginTplData{}, LoginFormData{
|
||||||
|
Common: NestedCommonTplData{
|
||||||
|
CanAdmin: false,
|
||||||
|
CanInvite: true,
|
||||||
|
LoggedIn: false}})
|
||||||
|
// templateLogin.Execute(w, LoginFormData{
|
||||||
|
// Common: NestedCommonTplData{
|
||||||
|
// CanAdmin: false,
|
||||||
|
// CanInvite: true,
|
||||||
|
// LoggedIn: false}})
|
||||||
|
return nil, nil
|
||||||
|
} else {
|
||||||
|
http.Error(w, "Unsupported method", http.StatusBadRequest)
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
// execTemplate(w, templateLogin, data.Common, NestedLoginTplData{}, *config, data)
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// func NotDoLogin(w http.ResponseWriter, r *http.Request, username string, user_dn string, password string) (*LoginInfo, error) {
|
||||||
|
|
||||||
|
|
||||||
|
// }
|
161
views/passwd.go
Normal file
161
views/passwd.go
Normal file
|
@ -0,0 +1,161 @@
|
||||||
|
package views
|
||||||
|
|
||||||
|
import (
|
||||||
|
b64 "encoding/base64"
|
||||||
|
"fmt"
|
||||||
|
"guichet/models"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
// "github.com/go-ldap/ldap/v3"
|
||||||
|
"github.com/gorilla/mux"
|
||||||
|
)
|
||||||
|
|
||||||
|
func HandleLostPassword(w http.ResponseWriter, r *http.Request) {
|
||||||
|
templateLostPasswordPage := getTemplate("passwd/lost.html")
|
||||||
|
if checkLogin(w, r) != nil {
|
||||||
|
http.Redirect(w, r, "/", http.StatusTemporaryRedirect)
|
||||||
|
}
|
||||||
|
|
||||||
|
data := PasswordLostData{
|
||||||
|
Common: NestedCommonTplData{
|
||||||
|
CanAdmin: false,
|
||||||
|
LoggedIn: false},
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.Method == "POST" {
|
||||||
|
r.ParseForm()
|
||||||
|
data.Username = strings.TrimSpace(strings.Join(r.Form["username"], ""))
|
||||||
|
data.Mail = strings.TrimSpace(strings.Join(r.Form["mail"], ""))
|
||||||
|
data.OtherMailbox = strings.TrimSpace(strings.Join(r.Form["othermailbox"], ""))
|
||||||
|
user := models.User{
|
||||||
|
CN: strings.TrimSpace(strings.Join(r.Form["username"], "")),
|
||||||
|
UID: strings.TrimSpace(strings.Join(r.Form["username"], "")),
|
||||||
|
Mail: strings.TrimSpace(strings.Join(r.Form["mail"], "")),
|
||||||
|
OtherMailbox: strings.TrimSpace(strings.Join(r.Form["othermailbox"], "")),
|
||||||
|
}
|
||||||
|
ldapNewConn, err := models.OpenNewUserLdap(&config)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf(fmt.Sprintf("HandleLostPassword 99 : %v %v", err, ldapNewConn))
|
||||||
|
data.Common.ErrorMessage = err.Error()
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
log.Printf(fmt.Sprintf("HandleLostPassword 104 : %v %v", err, ldapNewConn))
|
||||||
|
data.Common.ErrorMessage = err.Error()
|
||||||
|
} else {
|
||||||
|
// err = ldapConn.Bind(config.NewUserDN, config.NewUserPassword)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf(fmt.Sprintf("HandleLostPassword 109 : %v %v", err, ldapNewConn))
|
||||||
|
data.Common.ErrorMessage = err.Error()
|
||||||
|
} else {
|
||||||
|
data.Common.Success = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
err = models.PasswordLost(user, &config, ldapNewConn)
|
||||||
|
}
|
||||||
|
data.Common.CanAdmin = false
|
||||||
|
// templateLostPasswordPage.Execute(w, data)
|
||||||
|
execTemplate(w, templateLostPasswordPage, data.Common, NestedLoginTplData{}, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
func HandleFoundPassword(w http.ResponseWriter, r *http.Request) {
|
||||||
|
templateFoundPasswordPage := getTemplate("passwd.html")
|
||||||
|
data := PasswdTplData{
|
||||||
|
Common: NestedCommonTplData{
|
||||||
|
CanAdmin: false,
|
||||||
|
LoggedIn: false},
|
||||||
|
}
|
||||||
|
code := mux.Vars(r)["code"]
|
||||||
|
// code = strings.TrimSpace(strings.Join([]string{code}, ""))
|
||||||
|
newCode, _ := b64.URLEncoding.DecodeString(code)
|
||||||
|
ldapNewConn, err := models.OpenNewUserLdap(&config)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("HandleFoundPassword OpenNewUserLdap(config) : %v", err)
|
||||||
|
data.Common.ErrorMessage = err.Error()
|
||||||
|
}
|
||||||
|
codeArray := strings.Split(string(newCode), ";")
|
||||||
|
user := models.User{
|
||||||
|
UID: codeArray[0],
|
||||||
|
Password: codeArray[1],
|
||||||
|
DN: "uid=" + codeArray[0] + "," + config.InvitationBaseDN,
|
||||||
|
}
|
||||||
|
user.SeeAlso, err = models.PasswordFound(user, &config, ldapNewConn)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("PasswordFound(models.User, config, ldapConn) %v", err)
|
||||||
|
log.Printf("PasswordFound(models.User, config, ldapConn) %v", user)
|
||||||
|
log.Printf("PasswordFound(models.User, config, ldapConn) %v", ldapNewConn)
|
||||||
|
data.Common.ErrorMessage = err.Error()
|
||||||
|
}
|
||||||
|
if r.Method == "POST" {
|
||||||
|
r.ParseForm()
|
||||||
|
|
||||||
|
password := strings.Join(r.Form["password"], "")
|
||||||
|
password2 := strings.Join(r.Form["password2"], "")
|
||||||
|
|
||||||
|
if len(password) < 8 {
|
||||||
|
data.TooShortError = true
|
||||||
|
} else if password2 != password {
|
||||||
|
data.NoMatchError = true
|
||||||
|
} else {
|
||||||
|
err := models.PassWD(models.User{
|
||||||
|
DN: user.SeeAlso,
|
||||||
|
Password: password,
|
||||||
|
}, &config, ldapNewConn)
|
||||||
|
if err != nil {
|
||||||
|
data.Common.ErrorMessage = err.Error()
|
||||||
|
} else {
|
||||||
|
data.Common.Success = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
data.Common.CanAdmin = false
|
||||||
|
// templateFoundPasswordPage.Execute(w, data)
|
||||||
|
execTemplate(w, templateFoundPasswordPage, data.Common, data.Login, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
func HandlePasswd(w http.ResponseWriter, r *http.Request) {
|
||||||
|
templatePasswd := getTemplate("passwd.html")
|
||||||
|
data := &PasswdTplData{
|
||||||
|
Common: NestedCommonTplData{
|
||||||
|
CanAdmin: false,
|
||||||
|
LoggedIn: true,
|
||||||
|
ErrorMessage: "",
|
||||||
|
Success: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
login := checkLogin(w, r)
|
||||||
|
if login == nil {
|
||||||
|
http.Redirect(w, r, "/", http.StatusFound)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
data.Login.Status = login
|
||||||
|
data.Common.CanAdmin = login.Common.CanAdmin
|
||||||
|
|
||||||
|
if r.Method == "POST" {
|
||||||
|
r.ParseForm()
|
||||||
|
|
||||||
|
password := strings.Join(r.Form["password"], "")
|
||||||
|
password2 := strings.Join(r.Form["password2"], "")
|
||||||
|
|
||||||
|
if len(password) < 8 {
|
||||||
|
data.TooShortError = true
|
||||||
|
} else if password2 != password {
|
||||||
|
data.NoMatchError = true
|
||||||
|
} else {
|
||||||
|
err := models.PassWD(models.User{
|
||||||
|
DN: login.Info.DN,
|
||||||
|
Password: password,
|
||||||
|
}, &config, login.conn)
|
||||||
|
if err != nil {
|
||||||
|
data.Common.ErrorMessage = err.Error()
|
||||||
|
} else {
|
||||||
|
data.Common.Success = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
data.Common.CanAdmin = false
|
||||||
|
// templatePasswd.Execute(w, data)
|
||||||
|
execTemplate(w, templatePasswd, data.Common, data.Login, data)
|
||||||
|
}
|
184
views/picture.go
Normal file
184
views/picture.go
Normal file
|
@ -0,0 +1,184 @@
|
||||||
|
package views
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"image"
|
||||||
|
"image/jpeg"
|
||||||
|
_ "image/png"
|
||||||
|
|
||||||
|
"mime/multipart"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
"github.com/gorilla/mux"
|
||||||
|
"github.com/minio/minio-go/v7"
|
||||||
|
"github.com/minio/minio-go/v7/pkg/credentials"
|
||||||
|
"github.com/nfnt/resize"
|
||||||
|
)
|
||||||
|
|
||||||
|
func newMinioClient() (*minio.Client, error) {
|
||||||
|
endpoint := config.S3Endpoint
|
||||||
|
accessKeyID := config.S3AccessKey
|
||||||
|
secretKeyID := config.S3SecretKey
|
||||||
|
useSSL := true
|
||||||
|
|
||||||
|
//Initialize Minio
|
||||||
|
minioCLient, err := minio.New(endpoint, &minio.Options{
|
||||||
|
Creds: credentials.NewStaticV4(accessKeyID, secretKeyID, ""),
|
||||||
|
Secure: useSSL,
|
||||||
|
Region: config.S3Region,
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return minioCLient, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Upload image through guichet server.
|
||||||
|
func uploadProfilePicture(w http.ResponseWriter, r *http.Request, login *LoginStatus) (string, error) {
|
||||||
|
file, _, err := r.FormFile("image")
|
||||||
|
|
||||||
|
if err == http.ErrMissingFile {
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
err = checkImage(file)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
buffFull := bytes.NewBuffer([]byte{})
|
||||||
|
buffThumb := bytes.NewBuffer([]byte{})
|
||||||
|
err = resizePicture(file, buffFull, buffThumb)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
mc, err := newMinioClient()
|
||||||
|
if err != nil || mc == nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
// If a previous profile picture existed, delete it
|
||||||
|
// (don't care about errors)
|
||||||
|
if nameConsul := login.UserEntry.GetAttributeValue(FIELD_NAME_PROFILE_PICTURE); nameConsul != "" {
|
||||||
|
mc.RemoveObject(context.Background(), config.S3Bucket, nameConsul, minio.RemoveObjectOptions{})
|
||||||
|
mc.RemoveObject(context.Background(), config.S3Bucket, nameConsul+"-thumb", minio.RemoveObjectOptions{})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate new random name for picture
|
||||||
|
nameFull := uuid.New().String()
|
||||||
|
nameThumb := nameFull + "-thumb"
|
||||||
|
|
||||||
|
_, err = mc.PutObject(context.Background(), config.S3Bucket, nameThumb, buffThumb, int64(buffThumb.Len()), minio.PutObjectOptions{
|
||||||
|
ContentType: "image/jpeg",
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = mc.PutObject(context.Background(), config.S3Bucket, nameFull, buffFull, int64(buffFull.Len()), minio.PutObjectOptions{
|
||||||
|
ContentType: "image/jpeg",
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nameFull, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkImage(file multipart.File) error {
|
||||||
|
buff := make([]byte, 512) //Detect read only the first 512 bytes
|
||||||
|
_, err := file.Read(buff)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
file.Seek(0, 0)
|
||||||
|
|
||||||
|
fileType := http.DetectContentType(buff)
|
||||||
|
fileType = strings.Split(fileType, "/")[0]
|
||||||
|
if fileType != "image" {
|
||||||
|
return errors.New("bad type")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func resizePicture(file multipart.File, buffFull, buffThumb *bytes.Buffer) error {
|
||||||
|
file.Seek(0, 0)
|
||||||
|
picture, _, err := image.Decode(file)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
thumbnail := resize.Thumbnail(90, 90, picture, resize.Lanczos3)
|
||||||
|
picture = resize.Thumbnail(480, 480, picture, resize.Lanczos3)
|
||||||
|
|
||||||
|
err = jpeg.Encode(buffFull, picture, &jpeg.Options{
|
||||||
|
Quality: 95,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = jpeg.Encode(buffThumb, thumbnail, &jpeg.Options{
|
||||||
|
Quality: 100,
|
||||||
|
})
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func HandleDownloadPicture(w http.ResponseWriter, r *http.Request) {
|
||||||
|
name := mux.Vars(r)["name"]
|
||||||
|
|
||||||
|
//Check login
|
||||||
|
login := checkLogin(w, r)
|
||||||
|
if login == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
//Get the object after connect MC
|
||||||
|
mc, err := newMinioClient()
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, "MinioClient: "+err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
obj, err := mc.GetObject(context.Background(), "bottin-pictures", name, minio.GetObjectOptions{})
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, "MinioClient: GetObject: "+err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer obj.Close()
|
||||||
|
|
||||||
|
objStat, err := obj.Stat()
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, "MiniObjet: "+err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
//Send JSON through xhttp
|
||||||
|
w.Header().Set("Content-Type", objStat.ContentType)
|
||||||
|
w.Header().Set("Content-Length", strconv.Itoa(int(objStat.Size)))
|
||||||
|
//Copy obj in w
|
||||||
|
writting, err := io.Copy(w, obj)
|
||||||
|
|
||||||
|
if writting != objStat.Size || err != nil {
|
||||||
|
http.Error(w, fmt.Sprintf("WriteBody: %s, bytes wrote %d on %d", err.Error(), writting, objStat.Size), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
177
views/session.go
Normal file
177
views/session.go
Normal file
|
@ -0,0 +1,177 @@
|
||||||
|
/*
|
||||||
|
Handles session login and lougout with HTTP stuff
|
||||||
|
*/
|
||||||
|
package views
|
||||||
|
|
||||||
|
import (
|
||||||
|
"guichet/models"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
func checkLogin(w http.ResponseWriter, r *http.Request) *LoginStatus {
|
||||||
|
var login_info *LoginInfo
|
||||||
|
l, err := ldapOpen(w)
|
||||||
|
if l == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
session, err := GuichetSessionStore.Get(r, SESSION_NAME)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("checkLogin ldapOpen : %v", err)
|
||||||
|
log.Printf("checkLogin ldapOpen : %v", session)
|
||||||
|
log.Printf("checkLogin ldapOpen : %v", session.Values)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
username, ok := session.Values["login_username"]
|
||||||
|
password, ok2 := session.Values["login_password"]
|
||||||
|
user_dn, ok3 := session.Values["login_dn"]
|
||||||
|
|
||||||
|
if ok && ok2 && ok3 {
|
||||||
|
login_info = &LoginInfo{
|
||||||
|
DN: user_dn.(string),
|
||||||
|
Username: username.(string),
|
||||||
|
Password: password.(string),
|
||||||
|
}
|
||||||
|
err = models.Bind(models.User{
|
||||||
|
DN: login_info.DN,
|
||||||
|
Password: login_info.Password,
|
||||||
|
}, &config, l)
|
||||||
|
if err != nil {
|
||||||
|
delete(session.Values, "login_username")
|
||||||
|
delete(session.Values, "login_password")
|
||||||
|
delete(session.Values, "login_dn")
|
||||||
|
|
||||||
|
err = session.Save(r, w)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return checkLogin(w, r)
|
||||||
|
}
|
||||||
|
ldapUser, err := models.GetUser(models.User{
|
||||||
|
DN: login_info.DN,
|
||||||
|
CN: login_info.Username,
|
||||||
|
}, &config, l)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
userEntry := ldapUser.UserEntry
|
||||||
|
loginStatus :=LoginStatus{
|
||||||
|
Info: login_info,
|
||||||
|
conn: l,
|
||||||
|
UserEntry: userEntry,
|
||||||
|
Common: NestedCommonTplData{
|
||||||
|
CanAdmin: ldapUser.CanAdmin,
|
||||||
|
CanInvite: ldapUser.CanInvite,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
return &loginStatus
|
||||||
|
} else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
|
||||||
|
requestKind := "(objectClass=organizationalPerson)"
|
||||||
|
if strings.EqualFold(login_info.DN, config.AdminAccount) {
|
||||||
|
requestKind = "(objectclass=*)"
|
||||||
|
}
|
||||||
|
searchRequest := ldap.NewSearchRequest(
|
||||||
|
login_info.DN,
|
||||||
|
ldap.ScopeBaseObject, ldap.NeverDerefAliases, 0, 0, false,
|
||||||
|
requestKind,
|
||||||
|
[]string{
|
||||||
|
"dn",
|
||||||
|
"displayname",
|
||||||
|
"givenname",
|
||||||
|
"sn",
|
||||||
|
"mail",
|
||||||
|
"cn",
|
||||||
|
"memberof",
|
||||||
|
"description",
|
||||||
|
"garage_s3_access_key",
|
||||||
|
},
|
||||||
|
nil)
|
||||||
|
// FIELD_NAME_DIRECTORY_VISIBILITY,
|
||||||
|
// FIELD_NAME_PROFILE_PICTURE,
|
||||||
|
|
||||||
|
sr, err := l.Search(searchRequest)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(sr.Entries) != 1 {
|
||||||
|
http.Error(w, fmt.Sprintf("Unable to find entry for %s", login_info.DN), http.StatusInternalServerError)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
loginStatus.UserEntry = sr.Entries[0]
|
||||||
|
|
||||||
|
loginStatus.CanAdmin = strings.EqualFold(loginStatus.Info.DN, config.AdminAccount)
|
||||||
|
loginStatus.CanInvite = false
|
||||||
|
|
||||||
|
groups := []EntryName{}
|
||||||
|
searchRequest = ldap.NewSearchRequest(
|
||||||
|
config.GroupBaseDN,
|
||||||
|
ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false,
|
||||||
|
fmt.Sprintf("(&(objectClass=groupOfNames)(member=%s))", login_info.DN),
|
||||||
|
[]string{"dn", "displayName", "cn", "description"},
|
||||||
|
nil)
|
||||||
|
// // log.Printf(fmt.Sprintf("708: %v",searchRequest))
|
||||||
|
sr, err = l.Search(searchRequest)
|
||||||
|
// if err != nil {
|
||||||
|
// http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
//// log.Printf(fmt.Sprintf("303: %v",sr.Entries))
|
||||||
|
for _, ent := range sr.Entries {
|
||||||
|
// log.Printf(fmt.Sprintf("305: %v",ent.DN))
|
||||||
|
groups = append(groups, EntryName{
|
||||||
|
DN: ent.DN,
|
||||||
|
Name: ent.GetAttributeValue("cn"),
|
||||||
|
})
|
||||||
|
// log.Printf(fmt.Sprintf("310: %v",config.GroupCanInvite))
|
||||||
|
if config.GroupCanInvite != "" && strings.EqualFold(ent.DN, config.GroupCanInvite) {
|
||||||
|
loginStatus.CanInvite = true
|
||||||
|
}
|
||||||
|
// log.Printf(fmt.Sprintf("314: %v",config.GroupCanAdmin))
|
||||||
|
if config.GroupCanAdmin != "" && strings.EqualFold(ent.DN, config.GroupCanAdmin) {
|
||||||
|
loginStatus.CanAdmin = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// for _, attr := range loginStatus.UserEntry.Attributes {
|
||||||
|
// if strings.EqualFold(attr.Name, "memberof") {
|
||||||
|
// for _, group := range attr.Values {
|
||||||
|
// if config.GroupCanInvite != "" && strings.EqualFold(group, config.GroupCanInvite) {
|
||||||
|
// loginStatus.CanInvite = true
|
||||||
|
// }
|
||||||
|
// if config.GroupCanAdmin != "" && strings.EqualFold(group, config.GroupCanAdmin) {
|
||||||
|
// loginStatus.CanAdmin = true
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
return loginStatus
|
||||||
|
*/
|
||||||
|
|
||||||
|
func logout(w http.ResponseWriter, r *http.Request) error {
|
||||||
|
session, err := GuichetSessionStore.Get(r, SESSION_NAME)
|
||||||
|
if err != nil {
|
||||||
|
session, _ = GuichetSessionStore.New(r, SESSION_NAME)
|
||||||
|
// return err
|
||||||
|
} else {
|
||||||
|
delete(session.Values, "login_username")
|
||||||
|
delete(session.Values, "login_password")
|
||||||
|
delete(session.Values, "login_dn")
|
||||||
|
|
||||||
|
err = session.Save(r, w)
|
||||||
|
}
|
||||||
|
|
||||||
|
// return err
|
||||||
|
return nil
|
||||||
|
}
|
175
views/user.go
Normal file
175
views/user.go
Normal file
|
@ -0,0 +1,175 @@
|
||||||
|
package views
|
||||||
|
|
||||||
|
import (
|
||||||
|
// b64 "encoding/base64"
|
||||||
|
"fmt"
|
||||||
|
// "log"
|
||||||
|
"guichet/models"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/go-ldap/ldap/v3"
|
||||||
|
// "github.com/gorilla/mux"
|
||||||
|
)
|
||||||
|
|
||||||
|
func HandleUserWait(w http.ResponseWriter, r *http.Request) {
|
||||||
|
templateUser := getTemplate("user/wait.html")
|
||||||
|
templateUser.Execute(w, HomePageData{
|
||||||
|
Common: NestedCommonTplData{
|
||||||
|
CanAdmin: false,
|
||||||
|
LoggedIn: false,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func HandleUserMail(w http.ResponseWriter, r *http.Request) {
|
||||||
|
login := checkLogin(w, r)
|
||||||
|
if login == nil {
|
||||||
|
http.Redirect(w, r, "/", http.StatusFound)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
email := r.FormValue("email")
|
||||||
|
action := r.FormValue("action")
|
||||||
|
var err error
|
||||||
|
if action == "Add" {
|
||||||
|
// Add the new mail value to the entry
|
||||||
|
modifyRequest := ldap.NewModifyRequest(login.Info.DN, nil)
|
||||||
|
modifyRequest.Add("mail", []string{email})
|
||||||
|
|
||||||
|
err = login.conn.Modify(modifyRequest)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, fmt.Sprintf("Error adding the email: %v", modifyRequest), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else if action == "Delete" {
|
||||||
|
modifyRequest := ldap.NewModifyRequest(login.Info.DN, nil)
|
||||||
|
modifyRequest.Delete("mail", []string{email})
|
||||||
|
|
||||||
|
log.Printf("HandleUserMail %v", modifyRequest)
|
||||||
|
err = login.conn.Modify(modifyRequest)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("HandleUserMail DeleteMail %v", err)
|
||||||
|
http.Error(w, fmt.Sprintf("Error deleting the email: %s", err), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
message := fmt.Sprintf("Mail value updated successfully to: %s", email)
|
||||||
|
http.Redirect(w, r, "/user?message="+message, http.StatusSeeOther)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func toInteger(index string) {
|
||||||
|
panic("unimplemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
func HandleUser(w http.ResponseWriter, r *http.Request) {
|
||||||
|
templateUser := getTemplate("user.html")
|
||||||
|
|
||||||
|
login := checkLogin(w, r)
|
||||||
|
if login == nil {
|
||||||
|
http.Redirect(w, r, "/", http.StatusFound)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
data := &ProfileTplData{
|
||||||
|
Login: NestedLoginTplData{
|
||||||
|
Status: login,
|
||||||
|
Login: login,
|
||||||
|
},
|
||||||
|
Common: NestedCommonTplData{
|
||||||
|
CanAdmin: login.Common.CanAdmin,
|
||||||
|
LoggedIn: true,
|
||||||
|
ErrorMessage: "",
|
||||||
|
Success: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
data.Mail = login.UserEntry.GetAttributeValue("mail")
|
||||||
|
data.DisplayName = login.UserEntry.GetAttributeValue("displayName")
|
||||||
|
data.GivenName = login.UserEntry.GetAttributeValue("givenName")
|
||||||
|
data.Surname = login.UserEntry.GetAttributeValue("sn")
|
||||||
|
data.OtherMailbox = login.UserEntry.GetAttributeValue("carLicense")
|
||||||
|
data.MailValues = login.UserEntry.GetAttributeValues("mail")
|
||||||
|
// data.Visibility = login.UserEntry.GetAttributeValue(FIELD_NAME_DIRECTORY_VISIBILITY)
|
||||||
|
data.Description = login.UserEntry.GetAttributeValue("description")
|
||||||
|
//data.ProfilePicture = login.UserEntry.GetAttributeValue(FIELD_NAME_PROFILE_PICTURE)
|
||||||
|
|
||||||
|
if r.Method == "POST" {
|
||||||
|
//5MB maximum size files
|
||||||
|
r.ParseMultipartForm(5 << 20)
|
||||||
|
user := models.User{
|
||||||
|
DN: login.Info.DN,
|
||||||
|
GivenName: strings.TrimSpace(strings.Join(r.Form["given_name"], "")),
|
||||||
|
DisplayName: strings.TrimSpace(strings.Join(r.Form["display_name"], "")),
|
||||||
|
Mail: strings.TrimSpace(strings.Join(r.Form["mail"], "")),
|
||||||
|
SN: strings.TrimSpace(strings.Join(r.Form["surname"], "")),
|
||||||
|
OtherMailbox: strings.TrimSpace(strings.Join(r.Form["othermailbox"], "")),
|
||||||
|
Description: strings.TrimSpace(strings.Join(r.Form["description"], "")),
|
||||||
|
// Password: ,
|
||||||
|
//UID: ,
|
||||||
|
// CN: ,
|
||||||
|
}
|
||||||
|
|
||||||
|
if user.DisplayName != "" {
|
||||||
|
err := models.ModifyUser(user, &config, login.conn)
|
||||||
|
if err != nil {
|
||||||
|
data.Common.ErrorMessage = "HandleUser : " + err.Error()
|
||||||
|
} else {
|
||||||
|
data.Common.Success = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
findUser, err := models.GetUser(user, &config, login.conn)
|
||||||
|
if err != nil {
|
||||||
|
data.Common.ErrorMessage = "HandleUser : " + err.Error()
|
||||||
|
}
|
||||||
|
data.DisplayName = findUser.DisplayName
|
||||||
|
data.GivenName = findUser.GivenName
|
||||||
|
data.Surname = findUser.SN
|
||||||
|
data.Description = findUser.Description
|
||||||
|
data.Mail = findUser.Mail
|
||||||
|
data.Common.LoggedIn = false
|
||||||
|
|
||||||
|
/*
|
||||||
|
visible := strings.TrimSpace(strings.Join(r.Form["visibility"], ""))
|
||||||
|
if visible != "" {
|
||||||
|
visible = "on"
|
||||||
|
} else {
|
||||||
|
visible = "off"
|
||||||
|
}
|
||||||
|
data.Visibility = visible
|
||||||
|
*/
|
||||||
|
/*
|
||||||
|
profilePicture, err := uploadProfilePicture(w, r, login)
|
||||||
|
if err != nil {
|
||||||
|
data.Common.ErrorMessage = err.Error()
|
||||||
|
}
|
||||||
|
if profilePicture != "" {
|
||||||
|
data.ProfilePicture = profilePicture
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
//modify_request.Replace(FIELD_NAME_DIRECTORY_VISIBILITY, []string{data.Visibility})
|
||||||
|
//modify_request.Replace(FIELD_NAME_DIRECTORY_VISIBILITY, []string{"on"})
|
||||||
|
//if data.ProfilePicture != "" {
|
||||||
|
// modify_request.Replace(FIELD_NAME_PROFILE_PICTURE, []string{data.ProfilePicture})
|
||||||
|
// }
|
||||||
|
|
||||||
|
// err := login.conn.Modify(modify_request)
|
||||||
|
// log.Printf(fmt.Sprintf("Profile:079: %v",modify_request))
|
||||||
|
// log.Printf(fmt.Sprintf("Profile:079: %v",err))
|
||||||
|
// log.Printf(fmt.Sprintf("Profile:079: %v",data))
|
||||||
|
// if err != nil {
|
||||||
|
// data.Common.ErrorMessage = err.Error()
|
||||||
|
// } else {
|
||||||
|
// data.Common.Success = true
|
||||||
|
// }
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("HandleUser : %v", data)
|
||||||
|
|
||||||
|
// templateUser.Execute(w, data)
|
||||||
|
execTemplate(w, templateUser, data.Common, data.Login, data)
|
||||||
|
}
|
368
views/view.go
Normal file
368
views/view.go
Normal file
|
@ -0,0 +1,368 @@
|
||||||
|
/*
|
||||||
|
Creates the webpages to be processed by Guichet
|
||||||
|
*/
|
||||||
|
package views
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/tls"
|
||||||
|
"encoding/json"
|
||||||
|
"guichet/models"
|
||||||
|
"io/ioutil"
|
||||||
|
"net"
|
||||||
|
|
||||||
|
"flag"
|
||||||
|
"html/template"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
// "net/http"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/go-ldap/ldap/v3"
|
||||||
|
"github.com/gorilla/sessions"
|
||||||
|
)
|
||||||
|
|
||||||
|
const SESSION_NAME = "guichet_session"
|
||||||
|
|
||||||
|
var templatePath = "./templates"
|
||||||
|
var GuichetSessionStore sessions.Store = nil
|
||||||
|
|
||||||
|
type EntryList []*ldap.Entry
|
||||||
|
type LoginInfo struct {
|
||||||
|
Username string
|
||||||
|
DN string
|
||||||
|
Password string
|
||||||
|
}
|
||||||
|
func ReadConfig() models.ConfigFile {
|
||||||
|
// Default configuration values for certain fields
|
||||||
|
flag.Parse()
|
||||||
|
var configFlag = flag.String("config", "./config.json", "Configuration file path")
|
||||||
|
|
||||||
|
config_file := models.ConfigFile{
|
||||||
|
HttpBindAddr: ":9991",
|
||||||
|
LdapServerAddr: "ldap://127.0.0.1:389",
|
||||||
|
|
||||||
|
UserNameAttr: "uid",
|
||||||
|
GroupNameAttr: "gid",
|
||||||
|
|
||||||
|
InvitationNameAttr: "cn",
|
||||||
|
InvitedAutoGroups: []string{},
|
||||||
|
|
||||||
|
Org: "ResDigita",
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := os.Stat(*configFlag)
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
log.Fatalf("Could not find Guichet configuration file at %s. Please create this file, for exemple starting with config.json.exemple and customizing it for your deployment.", *configFlag)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
bytes, err := ioutil.ReadFile(*configFlag)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = json.Unmarshal(bytes, &config_file)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return config_file
|
||||||
|
}
|
||||||
|
type LoginStatus struct {
|
||||||
|
Info *LoginInfo
|
||||||
|
conn *ldap.Conn
|
||||||
|
UserEntry *ldap.Entry
|
||||||
|
Common NestedCommonTplData
|
||||||
|
}
|
||||||
|
type NestedCommonTplData struct {
|
||||||
|
Error string
|
||||||
|
ErrorMessage string
|
||||||
|
CanAdmin bool
|
||||||
|
CanInvite bool
|
||||||
|
LoggedIn bool
|
||||||
|
Success bool
|
||||||
|
WarningMessage string
|
||||||
|
WebsiteName string
|
||||||
|
WebsiteURL string
|
||||||
|
}
|
||||||
|
type CodeMailFields struct {
|
||||||
|
From string
|
||||||
|
To string
|
||||||
|
Code string
|
||||||
|
InviteFrom string
|
||||||
|
WebBaseAddress string
|
||||||
|
Common NestedCommonTplData
|
||||||
|
}
|
||||||
|
|
||||||
|
var config = ReadConfig()
|
||||||
|
|
||||||
|
func ldapOpen(w http.ResponseWriter) (*ldap.Conn, error) {
|
||||||
|
if config.LdapTLS {
|
||||||
|
tlsConf := &tls.Config{
|
||||||
|
ServerName: config.LdapServerAddr,
|
||||||
|
InsecureSkipVerify: true,
|
||||||
|
}
|
||||||
|
return ldap.DialTLS("tcp", net.JoinHostPort(config.LdapServerAddr, "636"), tlsConf)
|
||||||
|
} else {
|
||||||
|
return ldap.DialURL("ldap://" + config.LdapServerAddr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// if err != nil {
|
||||||
|
// http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
|
// log.Printf(fmt.Sprintf("27: %v %v", err, l))
|
||||||
|
// return nil
|
||||||
|
// }
|
||||||
|
|
||||||
|
// return l
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// type keyView struct {
|
||||||
|
// Status *LoginStatus
|
||||||
|
// Key *garage.KeyInfo
|
||||||
|
// }
|
||||||
|
// type webInspectView struct {
|
||||||
|
// Status *LoginStatus
|
||||||
|
// Key *garage.KeyInfo
|
||||||
|
// Bucket *garage.BucketInfo
|
||||||
|
// IndexDoc string
|
||||||
|
// ErrorDoc string
|
||||||
|
// MaxObjects int64
|
||||||
|
// MaxSize int64
|
||||||
|
// UsedSizePct float64
|
||||||
|
// }
|
||||||
|
// type webListView struct {
|
||||||
|
// Status *LoginStatus
|
||||||
|
// Key *garage.KeyInfo
|
||||||
|
// }
|
||||||
|
type LayoutTemplateData struct {
|
||||||
|
Common NestedCommonTplData
|
||||||
|
Login NestedLoginTplData
|
||||||
|
Data any
|
||||||
|
}
|
||||||
|
type NestedLoginTplData struct {
|
||||||
|
Login *LoginStatus
|
||||||
|
Username string
|
||||||
|
Status *LoginStatus
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
func execTemplate(w http.ResponseWriter, t *template.Template, commonData NestedCommonTplData, loginData NestedLoginTplData, data any) error {
|
||||||
|
commonData.WebsiteURL = config.WebAddress
|
||||||
|
commonData.WebsiteName = config.Org
|
||||||
|
return t.Execute(w, LayoutTemplateData{
|
||||||
|
Common: commonData,
|
||||||
|
Login: loginData,
|
||||||
|
Data: data,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
func (login *LoginStatus) WelcomeName() string {
|
||||||
|
ret := login.UserEntry.GetAttributeValue("givenName")
|
||||||
|
if ret == "" {
|
||||||
|
ret = login.UserEntry.GetAttributeValue("displayName")
|
||||||
|
}
|
||||||
|
if ret == "" {
|
||||||
|
ret = login.Info.Username
|
||||||
|
}
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
type AdminUsersTplData struct {
|
||||||
|
UserNameAttr string
|
||||||
|
UserBaseDN string
|
||||||
|
Users EntryList
|
||||||
|
Common NestedCommonTplData
|
||||||
|
Login NestedLoginTplData
|
||||||
|
}
|
||||||
|
type AdminLDAPTplData struct {
|
||||||
|
DN string
|
||||||
|
|
||||||
|
Path []PathItem
|
||||||
|
ChildrenOU []Child
|
||||||
|
ChildrenOther []Child
|
||||||
|
CanAddChild bool
|
||||||
|
Props map[string]*PropValues
|
||||||
|
CanDelete bool
|
||||||
|
|
||||||
|
HasMembers bool
|
||||||
|
Members []EntryName
|
||||||
|
PossibleNewMembers []EntryName
|
||||||
|
HasGroups bool
|
||||||
|
Groups []EntryName
|
||||||
|
PossibleNewGroups []EntryName
|
||||||
|
|
||||||
|
ListMemGro map[string]string
|
||||||
|
|
||||||
|
Common NestedCommonTplData
|
||||||
|
Login NestedLoginTplData
|
||||||
|
}
|
||||||
|
type AdminMailingListTplData struct {
|
||||||
|
Common NestedCommonTplData
|
||||||
|
Login NestedLoginTplData
|
||||||
|
MailingNameAttr string
|
||||||
|
MailingBaseDN string
|
||||||
|
MailingList *ldap.Entry
|
||||||
|
Members EntryList
|
||||||
|
PossibleNewMembers EntryList
|
||||||
|
AllowGuest bool
|
||||||
|
}
|
||||||
|
type AdminMailingTplData struct {
|
||||||
|
Common NestedCommonTplData
|
||||||
|
Login NestedLoginTplData
|
||||||
|
MailingNameAttr string
|
||||||
|
MailingBaseDN string
|
||||||
|
MailingLists EntryList
|
||||||
|
}
|
||||||
|
type AdminGroupsTplData struct {
|
||||||
|
Common NestedCommonTplData
|
||||||
|
Login NestedLoginTplData
|
||||||
|
GroupNameAttr string
|
||||||
|
GroupBaseDN string
|
||||||
|
Groups EntryList
|
||||||
|
}
|
||||||
|
type EntryName struct {
|
||||||
|
DN string
|
||||||
|
Name string
|
||||||
|
}
|
||||||
|
type Child struct {
|
||||||
|
DN string
|
||||||
|
Identifier string
|
||||||
|
Name string
|
||||||
|
}
|
||||||
|
type PathItem struct {
|
||||||
|
DN string
|
||||||
|
Identifier string
|
||||||
|
Active bool
|
||||||
|
}
|
||||||
|
type PropValues struct {
|
||||||
|
Name string
|
||||||
|
Values []string
|
||||||
|
Editable bool
|
||||||
|
Deletable bool
|
||||||
|
}
|
||||||
|
type CreateData struct {
|
||||||
|
SuperDN string
|
||||||
|
Path []PathItem
|
||||||
|
Template string
|
||||||
|
|
||||||
|
IdType string
|
||||||
|
IdValue string
|
||||||
|
DisplayName string
|
||||||
|
GivenName string
|
||||||
|
Member string
|
||||||
|
Mail string
|
||||||
|
Description string
|
||||||
|
StructuralObjectClass string
|
||||||
|
ObjectClass string
|
||||||
|
SN string
|
||||||
|
OtherMailbox string
|
||||||
|
|
||||||
|
Common NestedCommonTplData
|
||||||
|
Login NestedLoginTplData
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
type HomePageData struct {
|
||||||
|
Common NestedCommonTplData
|
||||||
|
Login NestedLoginTplData
|
||||||
|
BaseDN string
|
||||||
|
Org string
|
||||||
|
}
|
||||||
|
type PasswordFoundData struct {
|
||||||
|
Common NestedCommonTplData
|
||||||
|
Login NestedLoginTplData
|
||||||
|
Username string
|
||||||
|
Mail string
|
||||||
|
OtherMailbox string
|
||||||
|
}
|
||||||
|
type PasswordLostData struct {
|
||||||
|
Common NestedCommonTplData
|
||||||
|
ErrorMessage string
|
||||||
|
Success bool
|
||||||
|
Username string
|
||||||
|
Mail string
|
||||||
|
OtherMailbox string
|
||||||
|
}
|
||||||
|
type NewAccountData struct {
|
||||||
|
Username string
|
||||||
|
DisplayName string
|
||||||
|
GivenName string
|
||||||
|
Surname string
|
||||||
|
Mail string
|
||||||
|
SuggestPW string
|
||||||
|
OtherMailbox string
|
||||||
|
|
||||||
|
ErrorUsernameTaken bool
|
||||||
|
ErrorInvalidUsername bool
|
||||||
|
ErrorPasswordTooShort bool
|
||||||
|
ErrorPasswordMismatch bool
|
||||||
|
Common NestedCommonTplData
|
||||||
|
NewUserDefaultDomain string
|
||||||
|
}
|
||||||
|
type SendCodeData struct {
|
||||||
|
Common NestedCommonTplData
|
||||||
|
ErrorInvalidEmail bool
|
||||||
|
|
||||||
|
CodeDisplay string
|
||||||
|
CodeSentTo string
|
||||||
|
WebBaseAddress string
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
type ProfileTplData struct {
|
||||||
|
Mail string
|
||||||
|
MailValues []string
|
||||||
|
DisplayName string
|
||||||
|
GivenName string
|
||||||
|
Surname string
|
||||||
|
Description string
|
||||||
|
OtherMailbox string
|
||||||
|
Common NestedCommonTplData
|
||||||
|
Login NestedLoginTplData
|
||||||
|
}
|
||||||
|
|
||||||
|
//ProfilePicture string
|
||||||
|
//Visibility string
|
||||||
|
|
||||||
|
type PasswdTplData struct {
|
||||||
|
Common NestedCommonTplData
|
||||||
|
Login NestedLoginTplData
|
||||||
|
TooShortError bool
|
||||||
|
NoMatchError bool
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
type LoginFormData struct {
|
||||||
|
Username string
|
||||||
|
WrongUser bool
|
||||||
|
WrongPass bool
|
||||||
|
Common NestedCommonTplData
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
type WrapperTemplate struct {
|
||||||
|
Template *template.Template
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
func getTemplate(name string) *template.Template {
|
||||||
|
return template.Must(template.New("layout.html").Funcs(template.FuncMap{
|
||||||
|
"contains": strings.Contains,
|
||||||
|
}).ParseFiles(
|
||||||
|
templatePath+"/layout.html",
|
||||||
|
templatePath+"/"+name,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue