diff --git a/admin.go b/admin.go
index 5a86fe2..e6a55f5 100644
--- a/admin.go
+++ b/admin.go
@@ -1,12 +1,14 @@
package main
import (
+ "strings"
"fmt"
"html/template"
"net/http"
"sort"
"github.com/go-ldap/ldap/v3"
+ "github.com/gorilla/mux"
)
func checkAdminLogin(w http.ResponseWriter, r *http.Request) *LoginStatus {
@@ -30,16 +32,31 @@ func checkAdminLogin(w http.ResponseWriter, r *http.Request) *LoginStatus {
return login
}
+type EntryList []*ldap.Entry
+
+func (d EntryList) Len() int {
+ return len(d)
+}
+
+func (d EntryList) Swap(i, j int) {
+ d[i], d[j] = d[j], d[i]
+}
+
+func (d EntryList) Less(i, j int) bool {
+ return d[i].DN < d[j].DN
+}
+
+
type AdminUsersTplData struct {
Login *LoginStatus
UserNameAttr string
- Users []*ldap.Entry
+ Users EntryList
}
func handleAdminUsers(w http.ResponseWriter, r *http.Request) {
templateAdminUsers := template.Must(template.ParseFiles("templates/layout.html", "templates/admin_users.html"))
- login := checkLogin(w, r)
+ login := checkAdminLogin(w, r)
if login == nil {
return
}
@@ -60,22 +77,270 @@ func handleAdminUsers(w http.ResponseWriter, r *http.Request) {
data := &AdminUsersTplData{
Login: login,
UserNameAttr: config.UserNameAttr,
- Users: sr.Entries,
+ Users: EntryList(sr.Entries),
}
- sort.Sort(data)
+ sort.Sort(data.Users)
templateAdminUsers.Execute(w, data)
}
-func (d *AdminUsersTplData) Len() int {
- return len(d.Users)
+type AdminGroupsTplData struct {
+ Login *LoginStatus
+ GroupNameAttr string
+ Groups EntryList
}
-func (d *AdminUsersTplData) Swap(i, j int) {
- d.Users[i], d.Users[j] = d.Users[j], d.Users[i]
+func handleAdminGroups(w http.ResponseWriter, r *http.Request) {
+ templateAdminGroups := template.Must(template.ParseFiles("templates/layout.html", "templates/admin_groups.html"))
+
+ login := checkAdminLogin(w, r)
+ if login == nil {
+ return
+ }
+
+ searchRequest := ldap.NewSearchRequest(
+ config.GroupBaseDN,
+ ldap.ScopeSingleLevel, ldap.NeverDerefAliases, 0, 0, false,
+ fmt.Sprintf("(&(objectClass=groupOfNames))"),
+ []string{config.GroupNameAttr, "dn", "displayname"},
+ nil)
+
+ sr, err := login.conn.Search(searchRequest)
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+
+ data := &AdminGroupsTplData{
+ Login: login,
+ GroupNameAttr: config.GroupNameAttr,
+ Groups: EntryList(sr.Entries),
+ }
+ sort.Sort(data.Groups)
+
+ templateAdminGroups.Execute(w, data)
}
-func (d *AdminUsersTplData) Less(i, j int) bool {
- return d.Users[i].GetAttributeValue(config.UserNameAttr) <
- d.Users[j].GetAttributeValue(config.UserNameAttr)
+type AdminLDAPTplData struct {
+ DN string
+ Members []string
+ Groups []string
+ Props map[string]*PropValues
+ Children []Child
+ Path []PathItem
+ AddError string
+}
+
+type Child struct {
+ DN string
+ Identifier string
+ DisplayName string
+}
+
+type PathItem struct {
+ DN string
+ Identifier string
+ Active bool
+}
+
+type PropValues struct {
+ Values []string
+ Editable bool
+ ModifySuccess bool
+ ModifyError string
+}
+
+func handleAdminLDAP(w http.ResponseWriter, r *http.Request) {
+ templateAdminLDAP := template.Must(template.ParseFiles("templates/layout.html", "templates/admin_ldap.html"))
+
+ login := checkAdminLogin(w, r)
+ if login == nil {
+ return
+ }
+
+ dn := mux.Vars(r)["dn"]
+
+ modifyAttr := ""
+ modifyError := ""
+ modifySuccess := false
+ addError := ""
+
+ if r.Method == "POST" {
+ r.ParseForm()
+ action := strings.Join(r.Form["action"], "")
+ if action == "modify" {
+ attr := strings.Join(r.Form["attr"], "")
+ values := strings.Split(strings.Join(r.Form["values"], ""), "\n")
+ values_filtered := []string{}
+ for _, v := range values {
+ v2 := strings.TrimSpace(v)
+ if v2 != "" {
+ values_filtered = append(values_filtered, v2)
+ }
+ }
+
+ modifyAttr = attr
+ if len(values_filtered) == 0 {
+ modifyError = "Refusing to delete attribute."
+ } else {
+ modify_request := ldap.NewModifyRequest(dn, nil)
+ modify_request.Replace(attr, values_filtered)
+
+ err := login.conn.Modify(modify_request)
+ if err != nil {
+ modifyError = err.Error()
+ } else {
+ modifySuccess = true
+ }
+ }
+ } else if action == "add" {
+ attr := strings.Join(r.Form["attr"], "")
+ values := strings.Split(strings.Join(r.Form["values"], ""), "\n")
+ values_filtered := []string{}
+ for _, v := range values {
+ v2 := strings.TrimSpace(v)
+ if v2 != "" {
+ values_filtered = append(values_filtered, v2)
+ }
+ }
+
+ modify_request := ldap.NewModifyRequest(dn, nil)
+ modify_request.Add(attr, values_filtered)
+
+ err := login.conn.Modify(modify_request)
+ modifyAttr = attr
+ if err != nil {
+ addError = err.Error()
+ }
+ } else if action == "delete" {
+ attr := strings.Join(r.Form["attr"], "")
+
+ modify_request := ldap.NewModifyRequest(dn, nil)
+ modify_request.Replace(attr, []string{})
+
+ err := login.conn.Modify(modify_request)
+ if err != nil {
+ modifyError = err.Error()
+ }
+ }
+ }
+
+ // Build path
+ path := []PathItem{
+ PathItem{
+ DN: config.BaseDN,
+ Identifier: config.BaseDN,
+ Active: dn == config.BaseDN,
+ },
+ }
+
+ len_base_dn := len(strings.Split(config.BaseDN, ","))
+ dn_split := strings.Split(dn, ",")
+ dn_last_attr := strings.Split(dn_split[0], "=")[0]
+ 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],
+ Active: i == len(dn_split),
+ })
+ }
+
+ // Get object and parse it
+ searchRequest := ldap.NewSearchRequest(
+ dn,
+ ldap.ScopeBaseObject, ldap.NeverDerefAliases, 0, 0, false,
+ fmt.Sprintf("(objectclass=*)"),
+ []string{},
+ 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("%d objects found", len(sr.Entries)), http.StatusInternalServerError)
+ return
+ }
+
+ object := sr.Entries[0]
+
+ props := make(map[string]*PropValues)
+ for _, attr := range object.Attributes {
+ if attr.Name != dn_last_attr {
+ if existing, ok := props[attr.Name]; ok {
+ existing.Values = append(existing.Values, attr.Values...)
+ } else {
+ editable := true
+ for _, restricted := range []string{
+ "creatorsname", "modifiersname", "createtimestamp",
+ "modifytimestamp", "entryuuid",
+ } {
+ if strings.EqualFold(attr.Name, restricted) {
+ editable = false
+ break
+ }
+ }
+ pv := &PropValues{
+ Values: attr.Values,
+ Editable: editable,
+ }
+ if attr.Name == modifyAttr {
+ if modifySuccess {
+ pv.ModifySuccess = true
+ } else if modifyError != "" {
+ pv.ModifyError = modifyError
+ }
+ }
+ props[attr.Name] = pv
+ }
+ }
+ }
+
+ members := []string{}
+ if mp, ok := props["member"]; ok {
+ members = mp.Values
+ delete(props, "member")
+ }
+ groups := []string{}
+ if gp, ok := props["memberof"]; ok {
+ groups = gp.Values
+ delete(props, "memberof")
+ }
+
+ // Get children
+ searchRequest = ldap.NewSearchRequest(
+ dn,
+ ldap.ScopeSingleLevel, ldap.NeverDerefAliases, 0, 0, false,
+ fmt.Sprintf("(objectclass=*)"),
+ []string{"dn", "displayname"},
+ nil)
+
+ sr, err = login.conn.Search(searchRequest)
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+
+ sort.Sort(EntryList(sr.Entries))
+
+ children := []Child{}
+ for _, item := range sr.Entries {
+ children = append(children, Child{
+ DN: item.DN,
+ Identifier: strings.Split(item.DN, ",")[0],
+ DisplayName: item.GetAttributeValue("displayname"),
+ })
+ }
+
+ templateAdminLDAP.Execute(w, &AdminLDAPTplData{
+ DN: dn,
+ Members: members,
+ Groups: groups,
+ Props: props,
+ Children: children,
+ Path: path,
+ AddError: addError,
+ })
}
diff --git a/go.mod b/go.mod
index ab70c69..b11c004 100644
--- a/go.mod
+++ b/go.mod
@@ -5,6 +5,7 @@ go 1.13
require (
github.com/go-ldap/ldap v3.0.3+incompatible
github.com/go-ldap/ldap/v3 v3.1.6
+ github.com/gorilla/mux v1.7.3
github.com/gorilla/sessions v1.2.0
github.com/sirupsen/logrus v1.4.2
)
diff --git a/go.sum b/go.sum
index a0b62e4..8467403 100644
--- a/go.sum
+++ b/go.sum
@@ -5,6 +5,8 @@ github.com/go-ldap/ldap v3.0.3+incompatible h1:HTeSZO8hWMS1Rgb2Ziku6b8a7qRIZZMHj
github.com/go-ldap/ldap v3.0.3+incompatible/go.mod h1:qfd9rJvER9Q0/D/Sqn1DfHRoBp40uXYvFoEVrNEPqRc=
github.com/go-ldap/ldap/v3 v3.1.6 h1:VTihvB7egSAvU6KOagaiA/EvgJMR2jsjRAVIho2ydBo=
github.com/go-ldap/ldap/v3 v3.1.6/go.mod h1:5Zun81jBTabRaI8lzN7E1JjyEl1g6zI6u9pd8luAK4Q=
+github.com/gorilla/mux v1.7.3 h1:gnP5JzjVOuiZD07fKKToCAOjS0yOpj/qPETTXCCS6hw=
+github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ=
github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4=
github.com/gorilla/sessions v1.2.0 h1:S7P+1Hm5V/AT9cjEcUD5uDaQSX0OE577aCXgoaKpYbQ=
diff --git a/main.go b/main.go
index 29da525..a0d5b07 100644
--- a/main.go
+++ b/main.go
@@ -16,6 +16,7 @@ import (
"github.com/go-ldap/ldap/v3"
"github.com/gorilla/sessions"
+ "github.com/gorilla/mux"
)
type ConfigFile struct {
@@ -24,6 +25,7 @@ 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"`
@@ -53,6 +55,7 @@ func readConfig() ConfigFile {
SessionKey: base64.StdEncoding.EncodeToString(key_bytes),
LdapServerAddr: "ldap://127.0.0.1:389",
LdapTLS: false,
+ BaseDN: "dc=example,dc=com",
UserBaseDN: "ou=users,dc=example,dc=com",
UserNameAttr: "uid",
GroupBaseDN: "ou=groups,dc=example,dc=com",
@@ -103,19 +106,20 @@ func main() {
config = &config_file
store = sessions.NewFilesystemStore("", []byte(config.SessionKey))
- http.HandleFunc("/", handleHome)
- http.HandleFunc("/logout", handleLogout)
- http.HandleFunc("/profile", handleProfile)
- http.HandleFunc("/passwd", handlePasswd)
+ r := mux.NewRouter()
+ r.HandleFunc("/", handleHome)
+ r.HandleFunc("/logout", handleLogout)
+ r.HandleFunc("/profile", handleProfile)
+ r.HandleFunc("/passwd", handlePasswd)
- http.HandleFunc("/admin/users", handleAdminUsers)
- //http.HandleFunc("/admin/groups", handleAdminGroups)
- //http.HandleFunc("/admin/ldap", handleAdminLDAP)
+ r.HandleFunc("/admin/users", handleAdminUsers)
+ r.HandleFunc("/admin/groups", handleAdminGroups)
+ r.HandleFunc("/admin/ldap/{dn}", handleAdminLDAP)
staticfiles := http.FileServer(http.Dir("static"))
- http.Handle("/static/", http.StripPrefix("/static/", staticfiles))
+ r.Handle("/static/{file:.*}", http.StripPrefix("/static/", staticfiles))
- err := http.ListenAndServe(config.HttpBindAddr, logRequest(http.DefaultServeMux))
+ err := http.ListenAndServe(config.HttpBindAddr, logRequest(r))
if err != nil {
log.Fatal("Cannot start http server: ", err)
}
@@ -233,6 +237,7 @@ type HomePageData struct {
Login *LoginStatus
CanAdmin bool
CanInvite bool
+ BaseDN string
}
func handleHome(w http.ResponseWriter, r *http.Request) {
@@ -258,6 +263,7 @@ func handleHome(w http.ResponseWriter, r *http.Request) {
Login: login,
CanAdmin: can_admin,
CanInvite: can_invite,
+ BaseDN: config.BaseDN,
})
}
diff --git a/templates/admin_groups.html b/templates/admin_groups.html
new file mode 100644
index 0000000..8b5a4ef
--- /dev/null
+++ b/templates/admin_groups.html
@@ -0,0 +1,31 @@
+{{define "title"}}Liste des groupes |{{end}}
+
+{{define "body"}}
+
+
+
Liste des groupes
+
Retour
+
+
+
+
+{{end}}
diff --git a/templates/admin_ldap.html b/templates/admin_ldap.html
new file mode 100644
index 0000000..5eece8a
--- /dev/null
+++ b/templates/admin_ldap.html
@@ -0,0 +1,134 @@
+{{define "title"}}Explorateur LDAP |{{end}}
+
+{{define "body"}}
+
+
+
+
+
+
+
+
+
+Attributs
+
+ {{range $key, $value := .Props}}
+ {{if $value.Editable}}
+
+
{{$key}}
+
+
+
+ {{if $value.ModifySuccess}}
+
Modification enregistrée.
+ {{end}}
+ {{if $value.ModifyError}}
+
+ Impossible de modifier la valeur.
+
{{$value.ModifyError}}
+
+ {{end}}
+
+
+
+
+
+
+ {{end}}
+ {{end}}
+ {{range $key, $value := .Props}}
+ {{if not $value.Editable}}
+
+
{{$key}}
+
+ {{range $value.Values}}
+
{{.}}
+ {{end}}
+
+
+ {{end}}
+ {{end}}
+
+
+
+{{if .Members}}
+ Membres
+
+ {{range .Members}}
+ - {{.}}
+ {{end}}
+
+{{end}}
+
+{{if .Groups}}
+ Membre de
+
+ {{range .Groups}}
+ - {{.}}
+ {{end}}
+
+{{end}}
+
+
+
+{{end}}
diff --git a/templates/admin_users.html b/templates/admin_users.html
index 39e291c..01d96d2 100644
--- a/templates/admin_users.html
+++ b/templates/admin_users.html
@@ -9,7 +9,7 @@
- {{ .UserNameAttr }} |
+ Identifiant |
Nom complet |
Email |
@@ -17,7 +17,11 @@
{{with $root := .}}
{{range $user := $root.Users}}
- {{$user.GetAttributeValue $root.UserNameAttr}} |
+
+
+ {{$user.GetAttributeValue $root.UserNameAttr}}
+
+ |
{{$user.GetAttributeValue "displayname"}} |
{{$user.GetAttributeValue "mail"}} |
diff --git a/templates/home.html b/templates/home.html
index b4012fd..011dcc1 100644
--- a/templates/home.html
+++ b/templates/home.html
@@ -30,7 +30,7 @@
{{end}}