diff options
author | Alex Auvolat <alex@adnab.me> | 2020-02-14 18:57:25 +0100 |
---|---|---|
committer | Alex Auvolat <alex@adnab.me> | 2020-02-14 19:11:16 +0100 |
commit | 768f2de9162bbf3fd0a1005554f3fd595818f1b3 (patch) | |
tree | 2dc1372e23cd78a7ccdfe4628f4962b864ec2a68 | |
parent | ad00d0623e87817370de2ee28fef955290afd897 (diff) | |
download | guichet-768f2de9162bbf3fd0a1005554f3fd595818f1b3.tar.gz guichet-768f2de9162bbf3fd0a1005554f3fd595818f1b3.zip |
Mechanism to create new account
-rw-r--r-- | Makefile | 2 | ||||
-rw-r--r-- | invite.go | 144 | ||||
-rw-r--r-- | main.go | 20 | ||||
-rw-r--r-- | templates/home.html | 15 | ||||
-rw-r--r-- | templates/invite_new_account.html | 76 |
5 files changed, 246 insertions, 11 deletions
@@ -1,5 +1,5 @@ BIN=guichet -SRC=main.go ssha.go profile.go admin.go +SRC=main.go ssha.go profile.go admin.go invite.go DOCKER=lxpz/guichet_amd64 all: $(BIN) diff --git a/invite.go b/invite.go new file mode 100644 index 0000000..d078642 --- /dev/null +++ b/invite.go @@ -0,0 +1,144 @@ +package main + +import ( + "fmt" + "html/template" + "net/http" + "regexp" + "strings" + + "github.com/go-ldap/ldap/v3" +) + +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 +} + +type NewAccountData struct { + Username string + DisplayName string + GivenName string + Surname string + + ErrorUsernameTaken bool + ErrorInvalidUsername bool + ErrorPasswordTooShort bool + ErrorPasswordMismatch bool + ErrorMessage string + WarningMessage string + Success bool +} + +func handleInviteNewAccount(w http.ResponseWriter, r *http.Request) { + templateInviteNewAccount := template.Must(template.ParseFiles("templates/layout.html", "templates/invite_new_account.html")) + + login := checkInviterLogin(w, r) + if login == nil { + return + } + + data := &NewAccountData{} + + if r.Method == "POST" { + r.ParseForm() + + data.Username = strings.TrimSpace(strings.Join(r.Form["username"], "")) + data.DisplayName = strings.TrimSpace(strings.Join(r.Form["displayname"], "")) + data.GivenName = strings.TrimSpace(strings.Join(r.Form["givenname"], "")) + data.Surname = strings.TrimSpace(strings.Join(r.Form["surname"], "")) + + password1 := strings.Join(r.Form["password"], "") + password2 := strings.Join(r.Form["password2"], "") + + tryCreateAccount(login.conn, data, password1, password2) + } + + templateInviteNewAccount.Execute(w, data) +} + +func tryCreateAccount(l *ldap.Conn, data *NewAccountData, pass1 string, pass2 string) { + // Check if username is correct + if match, err := regexp.MatchString("^[a-zA-Z0-9._-]+$", data.Username); !(err == nil && match) { + data.ErrorInvalidUsername = 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.ErrorMessage = err.Error() + return + } + + if len(sr.Entries) > 0 { + data.ErrorUsernameTaken = true + return + } + + // Check that password is long enough + if len(pass1) < 8 { + data.ErrorPasswordTooShort = true + return + } + + if pass1 != pass2 { + data.ErrorPasswordMismatch = true + return + } + + // Actually create user + req := ldap.NewAddRequest(userDn, nil) + req.Attribute("objectclass", []string{"inetOrgPerson", "organizationalPerson", "person", "top"}) + req.Attribute("structuralobjectclass", []string{"inetOrgPerson"}) + req.Attribute("userpassword", []string{SSHAEncode([]byte(pass1))}) + 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.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.WarningMessage += fmt.Sprintf("Cannot add to %s: %s\n", group, err.Error()) + } + } + + data.Success = true +} + +func handleInviteSendCode(w http.ResponseWriter, r *http.Request) { +} @@ -23,11 +23,15 @@ type ConfigFile struct { 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"` + 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"` + InvitationBaseDN string `json:"invitation_base_dn"` + InvitationNameAttr string `json:"invitation_name_attr"` + InvitedMailFormat string `json:"invited_mail_format"` + InvitedAutoGroups []string `json:"invited_auto_groups"` AdminAccount string `json:"admin_account"` GroupCanInvite string `json:"group_can_invite"` @@ -111,6 +115,9 @@ func main() { r.HandleFunc("/profile", handleProfile) r.HandleFunc("/passwd", handlePasswd) + r.HandleFunc("/invite/new_account", handleInviteNewAccount) + r.HandleFunc("/invite/send_code", handleInviteSendCode) + r.HandleFunc("/admin/users", handleAdminUsers) r.HandleFunc("/admin/groups", handleAdminGroups) r.HandleFunc("/admin/ldap/{dn}", handleAdminLDAP) @@ -290,8 +297,7 @@ func handleHome(w http.ResponseWriter, r *http.Request) { func handleLogout(w http.ResponseWriter, r *http.Request) { session, err := store.Get(r, SESSION_NAME) if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return + session, _ = store.New(r, SESSION_NAME) } delete(session.Values, "login_username") diff --git a/templates/home.html b/templates/home.html index 7f227e6..57d6930 100644 --- a/templates/home.html +++ b/templates/home.html @@ -16,12 +16,21 @@ <div class="list-group list-group-flush"> <a class="list-group-item list-group-item-action" href="/profile">Modifier mon profil</a> <a class="list-group-item list-group-item-action" href="/passwd">Modifier mon mot de passe</a> - {{if .Login.CanInvite}} - <a class="list-group-item list-group-item-action" href="/invite">Inviter quelqu'un</a> - {{end}} </div> </div> +{{if .Login.CanInvite}} +<div class="card mt-3"> + <div class="card-header"> + Inviter des gens sur Deuxfleurs + </div> + <div class="list-group list-group-flush"> + <a class="list-group-item list-group-item-action" href="/invite/send_code">Envoyer un code d'invitation</a> + <a class="list-group-item list-group-item-action" href="/invite/new_account">Créer un nouveau compte directement</a> + </div> +</div> +{{end}} + {{if .Login.CanAdmin}} <div class="card mt-3"> <div class="card-header"> diff --git a/templates/invite_new_account.html b/templates/invite_new_account.html new file mode 100644 index 0000000..ab80887 --- /dev/null +++ b/templates/invite_new_account.html @@ -0,0 +1,76 @@ +{{define "title"}}Créer un compte |{{end}} + +{{define "body"}} +<div class="d-flex"> + <h4>Création d'un nouveau compte</h4> + <a class="ml-auto btn btn-info" href="/">Retour</a> +</div> + + {{if .ErrorMessage}} + <div class="alert alert-danger mt-4">Impossible de créer le compte. + <div style="font-size: 0.8em">{{ .ErrorMessage }}</div> + </div> + {{end}} + {{if .WarningMessage}} + <div class="alert alert-danger mt-4">Des erreurs se sont produtes, le compte pourrait ne pas être totalement fonctionnel. + <div style="font-size: 0.8em">{{ .WarningMessage }}</div> + </div> + {{end}} + {{if .Success}} + <div class="alert alert-success mt-4"> + Le compe a été créé ! + Rendez-vous <a href="/logout">sur la page d'accueil</a> pour vous connecter avec ce nouveau compte. + </div> + {{else}} + <form method="POST" class="mt-4"> + <h5>Renseignements obligatoires</h5> + <div class="form-group"> + <label for="username">Nom d'utilisateur souhaité :</label> + <input type="text" id="username" name="username" class="form-control" value="{{ .Username }}" /> + </div> + {{if .ErrorInvalidUsername}} + <div class="alert alert-warning"> + Nom d'utilisateur invalide. Ne peut contenir que les caractères suivants : chiffres, lettres, point, tiret bas (_) et tiret du milieu (-). + </div> + {{end}} + {{if .ErrorUsernameTaken}} + <div class="alert alert-warning"> + Ce nom d'utilisateur est déjà pris. + </div> + {{end}} + <div class="form-group"> + <label for="password">Mot de passe :</label> + <input type="password" id="password" name="password" class="form-control" /> + </div> + {{if .ErrorPasswordTooShort}} + <div class="alert alert-warning"> + Le mot de passe choisi est trop court (minimum 8 caractères). + </div> + {{end}} + <div class="form-group"> + <label for="password2">Répéter le mot de passe :</label> + <input type="password" id="password2" name="password2" class="form-control" /> + </div> + {{if .ErrorPasswordMismatch}} + <div class="alert alert-warning"> + Les deux mots de passe entrés ne correspondent pas. + </div> + {{end}} + + <h5>Renseignements optionnels</h5> + <div class="form-group"> + <label for="displayname">Nom complet :</label> + <input type="text" id="displayname" name="displayname" class="form-control" value="{{ .DisplayName }}" /> + </div> + <div class="form-group"> + <label for="givenname">Prénom :</label> + <input type="text" id="givenname" name="givenname" class="form-control" value="{{ .GivenName }}" /> + </div> + <div class="form-group"> + <label for="surname">Nom de famille :</label> + <input type="text" id="surname" name="surname" class="form-control" value="{{ .Surname }}" /> + </div> + <button type="submit" class="btn btn-primary">Créer le compte</button> + </form> + {{end}} +{{end}} |