aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAlex Auvolat <alex@adnab.me>2023-02-08 13:11:43 +0100
committerAlex Auvolat <alex@adnab.me>2023-02-08 13:11:43 +0100
commit670123df38608c98eadc482b9778ddfffe8560c7 (patch)
tree6b18562f502100a3dc80615a1b0b7d7c5203852f
parentcd415325729fddea26c816291bb33171b9cc4879 (diff)
downloadguichet-670123df38608c98eadc482b9778ddfffe8560c7.tar.gz
guichet-670123df38608c98eadc482b9778ddfffe8560c7.zip
First iteration on mailing list administration interface
-rw-r--r--.envrc1
-rw-r--r--.gitignore1
-rw-r--r--admin.go200
-rw-r--r--flake.nix65
-rw-r--r--invite.go2
-rw-r--r--main.go14
-rw-r--r--profile.go2
-rw-r--r--templates/admin_groups.html5
-rw-r--r--templates/admin_mailing.html32
-rw-r--r--templates/admin_mailing_list.html73
-rw-r--r--templates/home.html1
-rw-r--r--templates/layout.html2
12 files changed, 345 insertions, 53 deletions
diff --git a/.envrc b/.envrc
new file mode 100644
index 0000000..3550a30
--- /dev/null
+++ b/.envrc
@@ -0,0 +1 @@
+use flake
diff --git a/.gitignore b/.gitignore
index 38620a8..e1a6d79 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,3 +2,4 @@ guichet
guichet.static
config.json
result
+.direnv/
diff --git a/admin.go b/admin.go
index 832a815..b057d13 100644
--- a/admin.go
+++ b/admin.go
@@ -117,15 +117,186 @@ func handleAdminGroups(w http.ResponseWriter, r *http.Request) {
templateAdminGroups.Execute(w, data)
}
+type AdminMailingTplData struct {
+ Login *LoginStatus
+ MailingNameAttr string
+ MailingBaseDN string
+ MailingLists EntryList
+}
+
+func handleAdminMailing(w http.ResponseWriter, r *http.Request) {
+ templateAdminMailing := getTemplate("admin_mailing.html")
+
+ login := checkAdminLogin(w, r)
+ if login == nil {
+ return
+ }
+
+ searchRequest := ldap.NewSearchRequest(
+ config.MailingBaseDN,
+ ldap.ScopeSingleLevel, ldap.NeverDerefAliases, 0, 0, false,
+ fmt.Sprintf("(&(objectClass=groupOfNames))"),
+ []string{config.MailingNameAttr, "dn", "description"},
+ nil)
+
+ sr, err := login.conn.Search(searchRequest)
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+
+ data := &AdminMailingTplData{
+ Login: login,
+ MailingNameAttr: config.MailingNameAttr,
+ MailingBaseDN: config.MailingBaseDN,
+ MailingLists: EntryList(sr.Entries),
+ }
+ sort.Sort(data.MailingLists)
+
+ templateAdminMailing.Execute(w, data)
+}
+
+type AdminMailingListTplData struct {
+ Login *LoginStatus
+ MailingNameAttr string
+ MailingBaseDN string
+
+ MailingList *ldap.Entry
+ Members EntryList
+ PossibleNewMembers EntryList
+
+ Error string
+ Success bool
+}
+
+func handleAdminMailingList(w http.ResponseWriter, r *http.Request) {
+ templateAdminMailingList := getTemplate("admin_mailing_list.html")
+
+ login := checkAdminLogin(w, r)
+ if login == nil {
+ return
+ }
+
+ id := mux.Vars(r)["id"]
+ dn := fmt.Sprintf("%s=%s,%s", config.MailingNameAttr, id, config.MailingBaseDN)
+
+ // handle modifications
+ dError := ""
+ dSuccess := false
+
+ if r.Method == "POST" {
+ r.ParseForm()
+ action := strings.Join(r.Form["action"], "")
+ if action == "add-member" {
+ member := strings.Join(r.Form["member"], "")
+ modify_request := ldap.NewModifyRequest(dn, nil)
+ modify_request.Add("member", []string{member})
+
+ err := login.conn.Modify(modify_request)
+ if err != nil {
+ dError = err.Error()
+ } else {
+ dSuccess = true
+ }
+ } else if action == "delete-member" {
+ member := strings.Join(r.Form["member"], "")
+ modify_request := ldap.NewModifyRequest(dn, nil)
+ modify_request.Delete("member", []string{member})
+
+ err := login.conn.Modify(modify_request)
+ if err != nil {
+ dError = err.Error()
+ } else {
+ dSuccess = true
+ }
+ }
+ }
+
+ // Retrieve mailing list
+ searchRequest := ldap.NewSearchRequest(
+ dn,
+ ldap.ScopeBaseObject, ldap.NeverDerefAliases, 0, 0, false,
+ fmt.Sprintf("(objectclass=groupOfNames)"),
+ []string{"dn", config.MailingNameAttr, "member"},
+ nil)
+
+ sr, err := login.conn.Search(searchRequest)
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+
+ if len(sr.Entries) != 1 {
+ http.Error(w, fmt.Sprintf("Object not found: %s", dn), http.StatusNotFound)
+ return
+ }
+
+ ml := sr.Entries[0]
+
+ memberDns := make(map[string]bool)
+ for _, attr := range ml.Attributes {
+ if attr.Name == "member" {
+ for _, v := range attr.Values {
+ memberDns[v] = true
+ }
+ }
+ }
+
+ // Retrieve list of current and possible new members
+ members := []*ldap.Entry{}
+ possibleNewMembers := []*ldap.Entry{}
+
+ searchRequest = ldap.NewSearchRequest(
+ config.UserBaseDN,
+ ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false,
+ fmt.Sprintf("(objectClass=organizationalPerson)"),
+ []string{"dn", "displayname", "mail"},
+ nil)
+ sr, err = login.conn.Search(searchRequest)
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+
+ for _, ent := range sr.Entries {
+ if _, ok := memberDns[ent.DN]; ok {
+ members = append(members, ent)
+ } else {
+ possibleNewMembers = append(possibleNewMembers, ent)
+ }
+ }
+
+ data := &AdminMailingListTplData{
+ Login: login,
+ MailingNameAttr: config.MailingNameAttr,
+ MailingBaseDN: config.MailingBaseDN,
+
+ MailingList: ml,
+ Members: members,
+ PossibleNewMembers: possibleNewMembers,
+
+ Error: dError,
+ Success: dSuccess,
+ }
+ sort.Sort(data.Members)
+ sort.Sort(data.PossibleNewMembers)
+
+ templateAdminMailingList.Execute(w, data)
+}
+
+// ===================================================
+// LDAP EXPLORER
+// ===================================================
+
type AdminLDAPTplData struct {
DN string
- Path []PathItem
- ChildrenOU []Child
- ChildrenOther []Child
- CanAddChild bool
- Props map[string]*PropValues
- CanDelete bool
+ Path []PathItem
+ ChildrenOU []Child
+ ChildrenOther []Child
+ CanAddChild bool
+ Props map[string]*PropValues
+ CanDelete bool
HasMembers bool
Members []EntryName
@@ -523,12 +694,12 @@ func handleAdminLDAP(w http.ResponseWriter, r *http.Request) {
templateAdminLDAP.Execute(w, &AdminLDAPTplData{
DN: dn,
- Path: path,
+ Path: path,
ChildrenOU: childrenOU,
- ChildrenOther: childrenOther,
- Props: props,
- CanAddChild: dn_last_attr == "ou" || isOrganization,
- CanDelete: dn != config.BaseDN && len(childrenOU) == 0 && len(childrenOther) == 0,
+ ChildrenOther: childrenOther,
+ Props: props,
+ CanAddChild: dn_last_attr == "ou" || isOrganization,
+ CanDelete: dn != config.BaseDN && len(childrenOU) == 0 && len(childrenOther) == 0,
HasMembers: len(members) > 0 || hasMembers,
Members: members,
@@ -671,9 +842,12 @@ func handleAdminCreate(w http.ResponseWriter, r *http.Request) {
if err != nil {
data.Error = err.Error()
} else {
- http.Redirect(w, r, "/admin/ldap/"+dn, http.StatusFound)
+ if super_dn == config.MailingBaseDN && data.IdType == config.MailingNameAttr {
+ http.Redirect(w, r, "/admin/mailing/"+data.IdValue, http.StatusFound)
+ } else {
+ http.Redirect(w, r, "/admin/ldap/"+dn, http.StatusFound)
+ }
}
-
}
}
diff --git a/flake.nix b/flake.nix
index 5e2c174..7087410 100644
--- a/flake.nix
+++ b/flake.nix
@@ -1,43 +1,44 @@
{
description = "A simple LDAP web interface for Bottin";
- inputs.nixpkgs.url = "github:nixos/nixpkgs/0244e143dc943bcf661fdaf581f01eb0f5000fcf";
- inputs.gomod2nix.url = "github:tweag/gomod2nix/40d32f82fc60d66402eb0972e6e368aeab3faf58";
+ inputs.nixpkgs.url =
+ "github:nixos/nixpkgs/0244e143dc943bcf661fdaf581f01eb0f5000fcf";
+ inputs.gomod2nix.url =
+ "github:tweag/gomod2nix/40d32f82fc60d66402eb0972e6e368aeab3faf58";
outputs = { self, nixpkgs, gomod2nix }:
- let
- pkgs = import nixpkgs {
- system = "x86_64-linux";
- overlays = [
- (self: super: {
- gomod = super.callPackage "${gomod2nix}/builder/" { };
- })
- ];
- };
- src = ./.;
- bottin = pkgs.gomod.buildGoApplication {
- pname = "guichet";
- version = "0.1.0";
- src = src;
- modules = ./gomod2nix.toml;
+ let
+ pkgs = import nixpkgs {
+ system = "x86_64-linux";
+ overlays = [
+ (self: super: {
+ gomod = super.callPackage "${gomod2nix}/builder/" { };
+ })
+ ];
+ };
+ src = ./.;
+ bottin = pkgs.gomod.buildGoApplication {
+ pname = "guichet";
+ version = "0.1.0";
+ src = src;
+ modules = ./gomod2nix.toml;
- CGO_ENABLED=0;
+ CGO_ENABLED = 0;
- ldflags = [
- "-X main.templatePath=${src + "/templates"}"
- "-X main.staticPath=${src + "/static"}"
- ];
+ ldflags = [
+ "-X main.templatePath=${src + "/templates"}"
+ "-X main.staticPath=${src + "/static"}"
+ ];
- meta = with pkgs.lib; {
- description = "A simple LDAP web interface for Bottin";
- homepage = "https://git.deuxfleurs.fr/Deuxfleurs/guichet";
- license = licenses.gpl3Plus;
- platforms = platforms.linux;
+ meta = with pkgs.lib; {
+ description = "A simple LDAP web interface for Bottin";
+ homepage = "https://git.deuxfleurs.fr/Deuxfleurs/guichet";
+ license = licenses.gpl3Plus;
+ platforms = platforms.linux;
+ };
};
+ in {
+ packages.x86_64-linux.bottin = bottin;
+ devShell.x86_64-linux = pkgs.mkShell { nativeBuildInputs = [ pkgs.go ]; };
};
- in
- {
- packages.x86_64-linux.bottin = bottin;
- packages.x86_64-linux.default = self.packages.x86_64-linux.bottin;
- };
}
diff --git a/invite.go b/invite.go
index ea356bb..1384d70 100644
--- a/invite.go
+++ b/invite.go
@@ -174,7 +174,7 @@ func tryCreateAccount(l *ldap.Conn, data *NewAccountData, pass1 string, pass2 st
if checkFailed {
return
- }
+ }
// Actually create user
req := ldap.NewAddRequest(userDn, nil)
diff --git a/main.go b/main.go
index 137b81c..5577784 100644
--- a/main.go
+++ b/main.go
@@ -23,11 +23,13 @@ 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"`
+ MailingBaseDN string `json:"mailing_list_base_dn"`
+ MailingNameAttr string `json:"mailing_list_name_attr"`
InvitationBaseDN string `json:"invitation_base_dn"`
InvitationNameAttr string `json:"invitation_name_attr"`
@@ -131,6 +133,8 @@ func main() {
r.HandleFunc("/admin/users", handleAdminUsers)
r.HandleFunc("/admin/groups", handleAdminGroups)
+ r.HandleFunc("/admin/mailing", handleAdminMailing)
+ r.HandleFunc("/admin/mailing/{id}", handleAdminMailingList)
r.HandleFunc("/admin/ldap/{dn}", handleAdminLDAP)
r.HandleFunc("/admin/create/{template}/{super_dn}", handleAdminCreate)
diff --git a/profile.go b/profile.go
index e93d09b..a082ad8 100644
--- a/profile.go
+++ b/profile.go
@@ -121,7 +121,7 @@ func handlePasswd(w http.ResponseWriter, r *http.Request) {
data.NoMatchError = true
} else {
modify_request := ldap.NewModifyRequest(login.Info.DN, nil)
- pw, err := SSHAEncode(password);
+ pw, err := SSHAEncode(password)
if err == nil {
modify_request.Replace("userpassword", []string{pw})
err := login.conn.Modify(modify_request)
diff --git a/templates/admin_groups.html b/templates/admin_groups.html
index f6eabfe..ece4128 100644
--- a/templates/admin_groups.html
+++ b/templates/admin_groups.html
@@ -8,6 +8,11 @@
<a class="ml-4 btn btn-info" href="/">Menu principal</a>
</div>
+<div class="alert alert-warning mt-4">
+ Les groupes servent uniquement à contrôler l'accès à différentes fonctionalités de Deuxfleurs.
+ Ce ne sont pas des <a href="/admin/mailing">mailing lists</a>.
+</div>
+
<table class="table mt-4">
<thead>
<th scope="col">Identifiant</th>
diff --git a/templates/admin_mailing.html b/templates/admin_mailing.html
new file mode 100644
index 0000000..d81545f
--- /dev/null
+++ b/templates/admin_mailing.html
@@ -0,0 +1,32 @@
+{{define "title"}}Mailing lists |{{end}}
+
+{{define "body"}}
+
+<div class="d-flex">
+ <h4>Mailing lists</h4>
+ <a class="ml-auto btn btn-success" href="/admin/create/group/{{.MailingBaseDN}}">Nouvelle mailing list</a>
+ <a class="ml-4 btn btn-info" href="/">Menu principal</a>
+</div>
+
+<table class="table mt-4">
+ <thead>
+ <th scope="col">Adresse</th>
+ <th scope="col">Description</th>
+ </thead>
+ <tbody>
+ {{with $root := .}}
+ {{range $ml := $root.MailingLists}}
+ <tr>
+ <td>
+ <a href="/admin/mailing/{{$ml.GetAttributeValue $root.MailingNameAttr}}">
+ {{$ml.GetAttributeValue $root.MailingNameAttr}}
+ </a>
+ </td>
+ <td>{{$ml.GetAttributeValue "description"}}</td>
+ </tr>
+ {{end}}
+ {{end}}
+ </tbody>
+</table>
+
+{{end}}
diff --git a/templates/admin_mailing_list.html b/templates/admin_mailing_list.html
new file mode 100644
index 0000000..c5903b6
--- /dev/null
+++ b/templates/admin_mailing_list.html
@@ -0,0 +1,73 @@
+{{define "title"}}ML {{.MailingList.GetAttributeValue .MailingNameAttr}} |{{end}}
+
+{{define "body"}}
+
+<div class="d-flex">
+ <h4>ML {{.MailingList.GetAttributeValue .MailingNameAttr}}
+ <a class="ml-auto btn btn-sm btn-dark" href="/admin/ldap/{{.MailingList.DN}}">Vue avancée</a>
+ </h4>
+ <a class="ml-auto btn btn-dark" href="/admin/mailing">Liste des ML</a>
+ <a class="ml-4 btn btn-info" href="/">Menu principal</a>
+</div>
+
+{{if .Success}}
+ <div class="alert alert-success mt-2">Modification enregistrée.</div>
+{{end}}
+{{if .Error}}
+ <div class="alert alert-danger mt-2">
+ Impossible d'effectuer la modification.
+ <div style="font-size: 0.8em">{{.Error}}</div>
+ </div>
+{{end}}
+
+<table class="table mt-4">
+ <thead>
+ <th scope="col">Adresse</th>
+ <th scope="col">Nom</th>
+ <th scope="col" style="width: 6em"></th>
+ </thead>
+ <tbody>
+ {{with $root := .}}
+ {{range $member := $root.Members}}
+ <tr>
+ <td>
+ <a href="/admin/ldap/{{$member.DN}}">
+ {{$member.GetAttributeValue "mail"}}
+ </a>
+ </td>
+ <td>{{$member.GetAttributeValue "displayname"}}</td>
+ <td>
+ <form method="POST" onsubmit="return confirm('Supprimer de la ML ?');">
+ <input type="hidden" name="action" value="delete-member" />
+ <input type="hidden" name="member" value="{{.DN}}" />
+ <input type="submit" value="Suppr" class="form-control btn btn-danger btn-sm" />
+ </form>
+ </td>
+ </tr>
+ {{end}}
+ {{end}}
+ </tbody>
+</table>
+
+ <hr class="mt-4" />
+ <h5 class="mt-4">Ajouter un destinataire</h5>
+ <form method="POST">
+ <input type="hidden" name="action" value="add-member" />
+ <div class="row mt-4">
+ <div class="col-md-3"><strong>Utilisateur existant :</strong>
+ </div>
+ <div class="col-md-5">
+ <input class="form-control" type="text" list="users" name="member" placeholder="Utilisateur..." />
+ <datalist id="users">
+ {{range .PossibleNewMembers}}
+ {{if .GetAttributeValue "mail"}}
+ <option value="{{.DN}}">{{if .GetAttributeValue "displayname"}}{{.GetAttributeValue "displayname"}} ({{.GetAttributeValue "mail" }}){{else}}{{.GetAttributeValue "mail"}}{{end}}</option>
+ {{end}}
+ {{end}}
+ </datalist>
+ </div>
+ <div class="col-md-2">
+ <input type="submit" value="Ajouter" class="form-control btn btn-success btn-sm" />
+ </div>
+ </form>
+{{end}}
diff --git a/templates/home.html b/templates/home.html
index 376aefe..afa282f 100644
--- a/templates/home.html
+++ b/templates/home.html
@@ -40,6 +40,7 @@
<div class="list-group list-group-flush">
<a class="list-group-item list-group-item-action" href="/admin/users">Utilisateur·ices</a>
<a class="list-group-item list-group-item-action" href="/admin/groups">Groupes</a>
+ <a class="list-group-item list-group-item-action" href="/admin/mailing">Mailing lists</a>
<a class="list-group-item list-group-item-action" href="/admin/ldap/{{.BaseDN}}">Explorateur LDAP</a>
</div>
</div>
diff --git a/templates/layout.html b/templates/layout.html
index 5f4a315..212ce5e 100644
--- a/templates/layout.html
+++ b/templates/layout.html
@@ -9,7 +9,7 @@
<title>{{template "title"}} Guichet</title>
</head>
<body>
- <div class="container">
+ <div class="container mb-4">
<h1>Guichet Deuxfleurs💮💮</h1>
<hr />
{{template "body" .}}