Better tools for initial setup

This commit is contained in:
Alex Auvolat 2020-02-10 15:26:02 +01:00
parent 61a76f624d
commit e2e8a443ae
5 changed files with 73 additions and 22 deletions

View File

@ -18,7 +18,7 @@ func checkAdminLogin(w http.ResponseWriter, r *http.Request) *LoginStatus {
return nil return nil
} }
can_admin := false can_admin := (login.Info.DN == config.AdminAccount)
for _, group := range login.UserEntry.GetAttributeValues("memberof") { for _, group := range login.UserEntry.GetAttributeValues("memberof") {
if config.GroupCanAdmin != "" && group == config.GroupCanAdmin { if config.GroupCanAdmin != "" && group == config.GroupCanAdmin {
can_admin = true can_admin = true
@ -320,7 +320,7 @@ func handleAdminLDAP(w http.ResponseWriter, r *http.Request) {
} }
if len(sr.Entries) != 1 { if len(sr.Entries) != 1 {
http.Error(w, fmt.Sprintf("%d objects found", len(sr.Entries)), http.StatusInternalServerError) http.Error(w, fmt.Sprintf("Object not found: %s", dn), http.StatusNotFound)
return return
} }
@ -509,6 +509,25 @@ func handleAdminCreate(w http.ResponseWriter, r *http.Request) {
template := mux.Vars(r)["template"] template := mux.Vars(r)["template"]
super_dn := mux.Vars(r)["super_dn"] super_dn := mux.Vars(r)["super_dn"]
// Check that base DN exists
searchRequest := ldap.NewSearchRequest(
super_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("Parent object %s does not exist", super_dn), http.StatusNotFound)
return
}
// Build path // Build path
path := []PathItem{ path := []PathItem{
PathItem{ PathItem{
@ -541,6 +560,11 @@ func handleAdminCreate(w http.ResponseWriter, r *http.Request) {
data.StructuralObjectClass = "groupOfNames" data.StructuralObjectClass = "groupOfNames"
data.ObjectClass = "groupOfNames\ntop" data.ObjectClass = "groupOfNames\ntop"
data.IsTemplated = true data.IsTemplated = true
} else if template == "ou" {
data.IdType = "ou"
data.StructuralObjectClass = "organizationalUnit"
data.ObjectClass = "organizationalUnit\ntop"
data.IsTemplated = true
} else { } else {
data.IdType = "cn" data.IdType = "cn"
data.ObjectClass = "top" data.ObjectClass = "top"
@ -549,12 +573,12 @@ func handleAdminCreate(w http.ResponseWriter, r *http.Request) {
if r.Method == "POST" { if r.Method == "POST" {
r.ParseForm() r.ParseForm()
if !data.IsTemplated { if !data.IsTemplated {
data.IdType = strings.Join(r.Form["idtype"], "") data.IdType = strings.TrimSpace(strings.Join(r.Form["idtype"], ""))
data.StructuralObjectClass = strings.Join(r.Form["soc"], "") data.StructuralObjectClass = strings.TrimSpace(strings.Join(r.Form["soc"], ""))
data.ObjectClass = strings.Join(r.Form["oc"], "") data.ObjectClass = strings.Join(r.Form["oc"], "")
} }
data.IdValue = strings.Join(r.Form["idvalue"], "") data.IdValue = strings.TrimSpace(strings.Join(r.Form["idvalue"], ""))
data.DisplayName = strings.Join(r.Form["displayname"], "") data.DisplayName = strings.TrimSpace(strings.Join(r.Form["displayname"], ""))
object_class := []string{} object_class := []string{}
for _, oc := range strings.Split(data.ObjectClass, "\n") { for _, oc := range strings.Split(data.ObjectClass, "\n") {
@ -578,7 +602,10 @@ func handleAdminCreate(w http.ResponseWriter, r *http.Request) {
req.Attribute("objectClass", object_class) req.Attribute("objectClass", object_class)
req.Attribute("structuralObjectClass", req.Attribute("structuralObjectClass",
[]string{data.StructuralObjectClass}) []string{data.StructuralObjectClass})
if data.DisplayName != "" {
req.Attribute("displayname", []string{data.DisplayName}) req.Attribute("displayname", []string{data.DisplayName})
}
err := login.conn.Add(req) err := login.conn.Add(req)
if err != nil { if err != nil {
data.Error = err.Error() data.Error = err.Error()

43
main.go
View File

@ -31,6 +31,7 @@ type ConfigFile struct {
GroupBaseDN string `json:"group_base_dn"` GroupBaseDN string `json:"group_base_dn"`
GroupNameAttr string `json:"group_name_attr"` GroupNameAttr string `json:"group_name_attr"`
AdminAccount string `json:"admin_account"`
GroupCanInvite string `json:"group_can_invite"` GroupCanInvite string `json:"group_can_invite"`
GroupCanAdmin string `json:"group_can_admin"` GroupCanAdmin string `json:"group_can_admin"`
} }
@ -60,6 +61,7 @@ func readConfig() ConfigFile {
UserNameAttr: "uid", UserNameAttr: "uid",
GroupBaseDN: "ou=groups,dc=example,dc=com", GroupBaseDN: "ou=groups,dc=example,dc=com",
GroupNameAttr: "gid", GroupNameAttr: "gid",
AdminAccount: "uid=admin,dc=example,dc=com",
GroupCanInvite: "", GroupCanInvite: "",
GroupCanAdmin: "gid=admin,ou=groups,dc=example,dc=com", GroupCanAdmin: "gid=admin,ou=groups,dc=example,dc=com",
} }
@ -120,6 +122,7 @@ func main() {
staticfiles := http.FileServer(http.Dir("static")) staticfiles := http.FileServer(http.Dir("static"))
r.Handle("/static/{file:.*}", http.StripPrefix("/static/", staticfiles)) r.Handle("/static/{file:.*}", http.StripPrefix("/static/", staticfiles))
log.Printf("Starting HTTP server on %s", config.HttpBindAddr)
err := http.ListenAndServe(config.HttpBindAddr, logRequest(r)) err := http.ListenAndServe(config.HttpBindAddr, logRequest(r))
if err != nil { if err != nil {
log.Fatal("Cannot start http server: ", err) log.Fatal("Cannot start http server: ", err)
@ -189,10 +192,19 @@ func checkLogin(w http.ResponseWriter, r *http.Request) *LoginStatus {
return checkLogin(w, r) return checkLogin(w, r)
} }
loginStatus := &LoginStatus{
Info: login_info,
conn: l,
}
requestKind := "(objectClass=organizationalPerson)"
if login_info.DN == config.AdminAccount {
requestKind = "(objectclass=*)"
}
searchRequest := ldap.NewSearchRequest( searchRequest := ldap.NewSearchRequest(
login_info.DN, login_info.DN,
ldap.ScopeBaseObject, ldap.NeverDerefAliases, 0, 0, false, ldap.ScopeBaseObject, ldap.NeverDerefAliases, 0, 0, false,
fmt.Sprintf("(&(objectClass=organizationalPerson))"), requestKind,
[]string{"dn", "displayname", "givenname", "sn", "mail", "memberof"}, []string{"dn", "displayname", "givenname", "sn", "mail", "memberof"},
nil) nil)
@ -203,15 +215,13 @@ func checkLogin(w http.ResponseWriter, r *http.Request) *LoginStatus {
} }
if len(sr.Entries) != 1 { if len(sr.Entries) != 1 {
http.Error(w, fmt.Sprintf("Multiple entries: %#v", sr.Entries), http.StatusInternalServerError) http.Error(w, fmt.Sprintf("Unable to find entry for %s", login_info.DN), http.StatusInternalServerError)
return nil return nil
} }
return &LoginStatus{ loginStatus.UserEntry = sr.Entries[0]
Info: login_info,
conn: l, return loginStatus
UserEntry: sr.Entries[0],
}
} }
func ldapOpen(w http.ResponseWriter) *ldap.Conn { func ldapOpen(w http.ResponseWriter) *ldap.Conn {
@ -236,6 +246,7 @@ func ldapOpen(w http.ResponseWriter) *ldap.Conn {
type HomePageData struct { type HomePageData struct {
Login *LoginStatus Login *LoginStatus
WelcomeName string
CanAdmin bool CanAdmin bool
CanInvite bool CanInvite bool
BaseDN string BaseDN string
@ -249,7 +260,7 @@ func handleHome(w http.ResponseWriter, r *http.Request) {
return return
} }
can_admin := false can_admin := (login.Info.DN == config.AdminAccount)
can_invite := false can_invite := false
for _, group := range login.UserEntry.GetAttributeValues("memberof") { for _, group := range login.UserEntry.GetAttributeValues("memberof") {
if config.GroupCanInvite != "" && group == config.GroupCanInvite { if config.GroupCanInvite != "" && group == config.GroupCanInvite {
@ -260,12 +271,21 @@ func handleHome(w http.ResponseWriter, r *http.Request) {
} }
} }
templateHome.Execute(w, &HomePageData{ data := &HomePageData{
Login: login, Login: login,
CanAdmin: can_admin, CanAdmin: can_admin,
CanInvite: can_invite, CanInvite: can_invite,
BaseDN: config.BaseDN, BaseDN: config.BaseDN,
}) WelcomeName: login.UserEntry.GetAttributeValue("givenname"),
}
if data.WelcomeName == "" {
data.WelcomeName = login.UserEntry.GetAttributeValue("displayname")
}
if data.WelcomeName == "" {
data.WelcomeName = login.Info.Username
}
templateHome.Execute(w, data)
} }
func handleLogout(w http.ResponseWriter, r *http.Request) { func handleLogout(w http.ResponseWriter, r *http.Request) {
@ -305,6 +325,9 @@ func handleLogin(w http.ResponseWriter, r *http.Request) *LoginInfo {
username := strings.Join(r.Form["username"], "") username := strings.Join(r.Form["username"], "")
password := strings.Join(r.Form["password"], "") password := strings.Join(r.Form["password"], "")
user_dn := fmt.Sprintf("%s=%s,%s", config.UserNameAttr, username, config.UserBaseDN) user_dn := fmt.Sprintf("%s=%s,%s", config.UserNameAttr, username, config.UserBaseDN)
if username == config.AdminAccount {
user_dn = username
}
l := ldapOpen(w) l := ldapOpen(w)
if l == nil { if l == nil {

View File

@ -35,10 +35,10 @@ func handleProfile(w http.ResponseWriter, r *http.Request) {
if r.Method == "POST" { if r.Method == "POST" {
r.ParseForm() r.ParseForm()
data.Mail = strings.Join(r.Form["mail"], "") data.Mail = strings.TrimSpace(strings.Join(r.Form["mail"], ""))
data.DisplayName = strings.Join(r.Form["display_name"], "") data.DisplayName = strings.TrimSpace(strings.Join(r.Form["display_name"], ""))
data.GivenName = strings.Join(r.Form["given_name"], "") data.GivenName = strings.TrimSpace(strings.Join(r.Form["given_name"], ""))
data.Surname = strings.Join(r.Form["surname"], "") data.Surname = strings.TrimSpace(strings.Join(r.Form["surname"], ""))
modify_request := ldap.NewModifyRequest(login.Info.DN, nil) modify_request := ldap.NewModifyRequest(login.Info.DN, nil)
modify_request.Replace("mail", []string{data.Mail}) modify_request.Replace("mail", []string{data.Mail})

View File

@ -40,6 +40,7 @@
<div class="mt-2"> <div class="mt-2">
<a class="btn btn-sm btn-success" href="/admin/create/user/{{.DN}}">+utilisateur</a> <a class="btn btn-sm btn-success" href="/admin/create/user/{{.DN}}">+utilisateur</a>
<a class="ml-4 btn btn-sm btn-success" href="/admin/create/group/{{.DN}}">+groupe</a> <a class="ml-4 btn btn-sm btn-success" href="/admin/create/group/{{.DN}}">+groupe</a>
<a class="ml-4 btn btn-sm btn-success" href="/admin/create/ou/{{.DN}}">+ou</a>
<a class="ml-4 btn btn-sm btn-success" href="/admin/create/generic/{{.DN}}">+objet</a> <a class="ml-4 btn btn-sm btn-success" href="/admin/create/generic/{{.DN}}">+objet</a>
</div> </div>
<hr class="mt-4" /> <hr class="mt-4" />

View File

@ -2,7 +2,7 @@
{{define "body"}} {{define "body"}}
<div class="alert alert-info"> <div class="alert alert-info">
Bienvenue, <strong>{{ .Login.UserEntry.GetAttributeValue "givenname" }}</strong> ! Bienvenue, <strong>{{ .WelcomeName }}</strong> !
</div> </div>
<div class="d-flex"> <div class="d-flex">
<a class="ml-auto btn btn-sm btn-dark" href="/logout">Se déconnecter</a> <a class="ml-auto btn btn-sm btn-dark" href="/logout">Se déconnecter</a>