aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAlex Auvolat <alex@adnab.me>2020-02-14 18:57:25 +0100
committerAlex Auvolat <alex@adnab.me>2020-02-14 19:11:16 +0100
commit768f2de9162bbf3fd0a1005554f3fd595818f1b3 (patch)
tree2dc1372e23cd78a7ccdfe4628f4962b864ec2a68
parentad00d0623e87817370de2ee28fef955290afd897 (diff)
downloadguichet-768f2de9162bbf3fd0a1005554f3fd595818f1b3.tar.gz
guichet-768f2de9162bbf3fd0a1005554f3fd595818f1b3.zip
Mechanism to create new account
-rw-r--r--Makefile2
-rw-r--r--invite.go144
-rw-r--r--main.go20
-rw-r--r--templates/home.html15
-rw-r--r--templates/invite_new_account.html76
5 files changed, 246 insertions, 11 deletions
diff --git a/Makefile b/Makefile
index 26734d2..62311e9 100644
--- a/Makefile
+++ b/Makefile
@@ -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) {
+}
diff --git a/main.go b/main.go
index 0a8e2da..379a889 100644
--- a/main.go
+++ b/main.go
@@ -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}}