It Compiles !

This commit is contained in:
Chris Mann 2023-12-09 18:23:06 +01:00
parent 40ecbe2cfb
commit 23c77e0e23
19 changed files with 3892 additions and 0 deletions

73
controllers/controller.go Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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

File diff suppressed because it is too large Load diff

58
views/directory.go Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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,
))
}