WIP refactor (broken templates)
This commit is contained in:
parent
d0ed765be7
commit
c06f52837e
11 changed files with 534 additions and 455 deletions
98
admin.go
98
admin.go
|
@ -11,18 +11,18 @@ import (
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
)
|
)
|
||||||
|
|
||||||
func checkAdminLogin(w http.ResponseWriter, r *http.Request) *LoginStatus {
|
func checkAdminLogin(w http.ResponseWriter, r *http.Request) *LoggedUser {
|
||||||
login := checkLogin(w, r)
|
user := RequireUserHtml(w, r)
|
||||||
if login == nil {
|
if user == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if !login.CanAdmin {
|
if !user.Capabilities.CanAdmin {
|
||||||
http.Error(w, "Not authorized to perform administrative operations.", http.StatusUnauthorized)
|
http.Error(w, "Not authorized to perform administrative operations.", http.StatusUnauthorized)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return login
|
return user
|
||||||
}
|
}
|
||||||
|
|
||||||
type EntryList []*ldap.Entry
|
type EntryList []*ldap.Entry
|
||||||
|
@ -40,7 +40,7 @@ func (d EntryList) Less(i, j int) bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
type AdminUsersTplData struct {
|
type AdminUsersTplData struct {
|
||||||
Login *LoginStatus
|
User *LoggedUser
|
||||||
UserNameAttr string
|
UserNameAttr string
|
||||||
UserBaseDN string
|
UserBaseDN string
|
||||||
Users EntryList
|
Users EntryList
|
||||||
|
@ -49,8 +49,8 @@ type AdminUsersTplData struct {
|
||||||
func handleAdminUsers(w http.ResponseWriter, r *http.Request) {
|
func handleAdminUsers(w http.ResponseWriter, r *http.Request) {
|
||||||
templateAdminUsers := getTemplate("admin_users.html")
|
templateAdminUsers := getTemplate("admin_users.html")
|
||||||
|
|
||||||
login := checkAdminLogin(w, r)
|
user := checkAdminLogin(w, r)
|
||||||
if login == nil {
|
if user == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -61,14 +61,14 @@ func handleAdminUsers(w http.ResponseWriter, r *http.Request) {
|
||||||
[]string{config.UserNameAttr, "dn", "displayname", "givenname", "sn", "mail"},
|
[]string{config.UserNameAttr, "dn", "displayname", "givenname", "sn", "mail"},
|
||||||
nil)
|
nil)
|
||||||
|
|
||||||
sr, err := login.conn.Search(searchRequest)
|
sr, err := user.Login.conn.Search(searchRequest)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
data := &AdminUsersTplData{
|
data := &AdminUsersTplData{
|
||||||
Login: login,
|
User: user,
|
||||||
UserNameAttr: config.UserNameAttr,
|
UserNameAttr: config.UserNameAttr,
|
||||||
UserBaseDN: config.UserBaseDN,
|
UserBaseDN: config.UserBaseDN,
|
||||||
Users: EntryList(sr.Entries),
|
Users: EntryList(sr.Entries),
|
||||||
|
@ -79,7 +79,7 @@ func handleAdminUsers(w http.ResponseWriter, r *http.Request) {
|
||||||
}
|
}
|
||||||
|
|
||||||
type AdminGroupsTplData struct {
|
type AdminGroupsTplData struct {
|
||||||
Login *LoginStatus
|
User *LoggedUser
|
||||||
GroupNameAttr string
|
GroupNameAttr string
|
||||||
GroupBaseDN string
|
GroupBaseDN string
|
||||||
Groups EntryList
|
Groups EntryList
|
||||||
|
@ -88,8 +88,8 @@ type AdminGroupsTplData struct {
|
||||||
func handleAdminGroups(w http.ResponseWriter, r *http.Request) {
|
func handleAdminGroups(w http.ResponseWriter, r *http.Request) {
|
||||||
templateAdminGroups := getTemplate("admin_groups.html")
|
templateAdminGroups := getTemplate("admin_groups.html")
|
||||||
|
|
||||||
login := checkAdminLogin(w, r)
|
user := checkAdminLogin(w, r)
|
||||||
if login == nil {
|
if user == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -100,14 +100,14 @@ func handleAdminGroups(w http.ResponseWriter, r *http.Request) {
|
||||||
[]string{config.GroupNameAttr, "dn", "description"},
|
[]string{config.GroupNameAttr, "dn", "description"},
|
||||||
nil)
|
nil)
|
||||||
|
|
||||||
sr, err := login.conn.Search(searchRequest)
|
sr, err := user.Login.conn.Search(searchRequest)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
data := &AdminGroupsTplData{
|
data := &AdminGroupsTplData{
|
||||||
Login: login,
|
User: user,
|
||||||
GroupNameAttr: config.GroupNameAttr,
|
GroupNameAttr: config.GroupNameAttr,
|
||||||
GroupBaseDN: config.GroupBaseDN,
|
GroupBaseDN: config.GroupBaseDN,
|
||||||
Groups: EntryList(sr.Entries),
|
Groups: EntryList(sr.Entries),
|
||||||
|
@ -118,7 +118,7 @@ func handleAdminGroups(w http.ResponseWriter, r *http.Request) {
|
||||||
}
|
}
|
||||||
|
|
||||||
type AdminMailingTplData struct {
|
type AdminMailingTplData struct {
|
||||||
Login *LoginStatus
|
User *LoggedUser
|
||||||
MailingNameAttr string
|
MailingNameAttr string
|
||||||
MailingBaseDN string
|
MailingBaseDN string
|
||||||
MailingLists EntryList
|
MailingLists EntryList
|
||||||
|
@ -127,8 +127,8 @@ type AdminMailingTplData struct {
|
||||||
func handleAdminMailing(w http.ResponseWriter, r *http.Request) {
|
func handleAdminMailing(w http.ResponseWriter, r *http.Request) {
|
||||||
templateAdminMailing := getTemplate("admin_mailing.html")
|
templateAdminMailing := getTemplate("admin_mailing.html")
|
||||||
|
|
||||||
login := checkAdminLogin(w, r)
|
user := checkAdminLogin(w, r)
|
||||||
if login == nil {
|
if user == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -139,14 +139,14 @@ func handleAdminMailing(w http.ResponseWriter, r *http.Request) {
|
||||||
[]string{config.MailingNameAttr, "dn", "description"},
|
[]string{config.MailingNameAttr, "dn", "description"},
|
||||||
nil)
|
nil)
|
||||||
|
|
||||||
sr, err := login.conn.Search(searchRequest)
|
sr, err := user.Login.conn.Search(searchRequest)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
data := &AdminMailingTplData{
|
data := &AdminMailingTplData{
|
||||||
Login: login,
|
User: user,
|
||||||
MailingNameAttr: config.MailingNameAttr,
|
MailingNameAttr: config.MailingNameAttr,
|
||||||
MailingBaseDN: config.MailingBaseDN,
|
MailingBaseDN: config.MailingBaseDN,
|
||||||
MailingLists: EntryList(sr.Entries),
|
MailingLists: EntryList(sr.Entries),
|
||||||
|
@ -157,7 +157,7 @@ func handleAdminMailing(w http.ResponseWriter, r *http.Request) {
|
||||||
}
|
}
|
||||||
|
|
||||||
type AdminMailingListTplData struct {
|
type AdminMailingListTplData struct {
|
||||||
Login *LoginStatus
|
User *LoggedUser
|
||||||
MailingNameAttr string
|
MailingNameAttr string
|
||||||
MailingBaseDN string
|
MailingBaseDN string
|
||||||
|
|
||||||
|
@ -173,8 +173,8 @@ type AdminMailingListTplData struct {
|
||||||
func handleAdminMailingList(w http.ResponseWriter, r *http.Request) {
|
func handleAdminMailingList(w http.ResponseWriter, r *http.Request) {
|
||||||
templateAdminMailingList := getTemplate("admin_mailing_list.html")
|
templateAdminMailingList := getTemplate("admin_mailing_list.html")
|
||||||
|
|
||||||
login := checkAdminLogin(w, r)
|
user := checkAdminLogin(w, r)
|
||||||
if login == nil {
|
if user == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -193,7 +193,7 @@ func handleAdminMailingList(w http.ResponseWriter, r *http.Request) {
|
||||||
modify_request := ldap.NewModifyRequest(dn, nil)
|
modify_request := ldap.NewModifyRequest(dn, nil)
|
||||||
modify_request.Add("member", []string{member})
|
modify_request.Add("member", []string{member})
|
||||||
|
|
||||||
err := login.conn.Modify(modify_request)
|
err := user.Login.conn.Modify(modify_request)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
dError = err.Error()
|
dError = err.Error()
|
||||||
} else {
|
} else {
|
||||||
|
@ -209,7 +209,7 @@ func handleAdminMailingList(w http.ResponseWriter, r *http.Request) {
|
||||||
fmt.Sprintf("(&(objectClass=organizationalPerson)(mail=%s))", mail),
|
fmt.Sprintf("(&(objectClass=organizationalPerson)(mail=%s))", mail),
|
||||||
[]string{"dn", "displayname", "mail"},
|
[]string{"dn", "displayname", "mail"},
|
||||||
nil)
|
nil)
|
||||||
sr, err := login.conn.Search(searchRequest)
|
sr, err := user.Login.conn.Search(searchRequest)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
dError = err.Error()
|
dError = err.Error()
|
||||||
} else {
|
} else {
|
||||||
|
@ -222,14 +222,14 @@ func handleAdminMailingList(w http.ResponseWriter, r *http.Request) {
|
||||||
if displayname != "" {
|
if displayname != "" {
|
||||||
req.Attribute("displayname", []string{displayname})
|
req.Attribute("displayname", []string{displayname})
|
||||||
}
|
}
|
||||||
err := login.conn.Add(req)
|
err := user.Login.conn.Add(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
dError = err.Error()
|
dError = err.Error()
|
||||||
} else {
|
} else {
|
||||||
modify_request := ldap.NewModifyRequest(dn, nil)
|
modify_request := ldap.NewModifyRequest(dn, nil)
|
||||||
modify_request.Add("member", []string{guestDn})
|
modify_request.Add("member", []string{guestDn})
|
||||||
|
|
||||||
err := login.conn.Modify(modify_request)
|
err := user.Login.conn.Modify(modify_request)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
dError = err.Error()
|
dError = err.Error()
|
||||||
} else {
|
} else {
|
||||||
|
@ -243,7 +243,7 @@ func handleAdminMailingList(w http.ResponseWriter, r *http.Request) {
|
||||||
modify_request := ldap.NewModifyRequest(dn, nil)
|
modify_request := ldap.NewModifyRequest(dn, nil)
|
||||||
modify_request.Add("member", []string{sr.Entries[0].DN})
|
modify_request.Add("member", []string{sr.Entries[0].DN})
|
||||||
|
|
||||||
err := login.conn.Modify(modify_request)
|
err := user.Login.conn.Modify(modify_request)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
dError = err.Error()
|
dError = err.Error()
|
||||||
} else {
|
} else {
|
||||||
|
@ -258,7 +258,7 @@ func handleAdminMailingList(w http.ResponseWriter, r *http.Request) {
|
||||||
modify_request := ldap.NewModifyRequest(dn, nil)
|
modify_request := ldap.NewModifyRequest(dn, nil)
|
||||||
modify_request.Delete("member", []string{member})
|
modify_request.Delete("member", []string{member})
|
||||||
|
|
||||||
err := login.conn.Modify(modify_request)
|
err := user.Login.conn.Modify(modify_request)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
dError = err.Error()
|
dError = err.Error()
|
||||||
} else {
|
} else {
|
||||||
|
@ -275,7 +275,7 @@ func handleAdminMailingList(w http.ResponseWriter, r *http.Request) {
|
||||||
[]string{"dn", config.MailingNameAttr, "member", "description"},
|
[]string{"dn", config.MailingNameAttr, "member", "description"},
|
||||||
nil)
|
nil)
|
||||||
|
|
||||||
sr, err := login.conn.Search(searchRequest)
|
sr, err := user.Login.conn.Search(searchRequest)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
|
@ -307,7 +307,7 @@ func handleAdminMailingList(w http.ResponseWriter, r *http.Request) {
|
||||||
fmt.Sprintf("(objectClass=organizationalPerson)"),
|
fmt.Sprintf("(objectClass=organizationalPerson)"),
|
||||||
[]string{"dn", "displayname", "mail"},
|
[]string{"dn", "displayname", "mail"},
|
||||||
nil)
|
nil)
|
||||||
sr, err = login.conn.Search(searchRequest)
|
sr, err = user.Login.conn.Search(searchRequest)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
|
@ -322,7 +322,7 @@ func handleAdminMailingList(w http.ResponseWriter, r *http.Request) {
|
||||||
}
|
}
|
||||||
|
|
||||||
data := &AdminMailingListTplData{
|
data := &AdminMailingListTplData{
|
||||||
Login: login,
|
User: user,
|
||||||
MailingNameAttr: config.MailingNameAttr,
|
MailingNameAttr: config.MailingNameAttr,
|
||||||
MailingBaseDN: config.MailingBaseDN,
|
MailingBaseDN: config.MailingBaseDN,
|
||||||
|
|
||||||
|
@ -394,8 +394,8 @@ type PropValues struct {
|
||||||
func handleAdminLDAP(w http.ResponseWriter, r *http.Request) {
|
func handleAdminLDAP(w http.ResponseWriter, r *http.Request) {
|
||||||
templateAdminLDAP := getTemplate("admin_ldap.html")
|
templateAdminLDAP := getTemplate("admin_ldap.html")
|
||||||
|
|
||||||
login := checkAdminLogin(w, r)
|
user := checkAdminLogin(w, r)
|
||||||
if login == nil {
|
if user == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -445,7 +445,7 @@ func handleAdminLDAP(w http.ResponseWriter, r *http.Request) {
|
||||||
modify_request := ldap.NewModifyRequest(dn, nil)
|
modify_request := ldap.NewModifyRequest(dn, nil)
|
||||||
modify_request.Replace(attr, values_filtered)
|
modify_request.Replace(attr, values_filtered)
|
||||||
|
|
||||||
err := login.conn.Modify(modify_request)
|
err := user.Login.conn.Modify(modify_request)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
dError = err.Error()
|
dError = err.Error()
|
||||||
} else {
|
} else {
|
||||||
|
@ -466,7 +466,7 @@ func handleAdminLDAP(w http.ResponseWriter, r *http.Request) {
|
||||||
modify_request := ldap.NewModifyRequest(dn, nil)
|
modify_request := ldap.NewModifyRequest(dn, nil)
|
||||||
modify_request.Add(attr, values_filtered)
|
modify_request.Add(attr, values_filtered)
|
||||||
|
|
||||||
err := login.conn.Modify(modify_request)
|
err := user.Login.conn.Modify(modify_request)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
dError = err.Error()
|
dError = err.Error()
|
||||||
} else {
|
} else {
|
||||||
|
@ -478,7 +478,7 @@ func handleAdminLDAP(w http.ResponseWriter, r *http.Request) {
|
||||||
modify_request := ldap.NewModifyRequest(dn, nil)
|
modify_request := ldap.NewModifyRequest(dn, nil)
|
||||||
modify_request.Replace(attr, []string{})
|
modify_request.Replace(attr, []string{})
|
||||||
|
|
||||||
err := login.conn.Modify(modify_request)
|
err := user.Login.conn.Modify(modify_request)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
dError = err.Error()
|
dError = err.Error()
|
||||||
} else {
|
} else {
|
||||||
|
@ -489,7 +489,7 @@ func handleAdminLDAP(w http.ResponseWriter, r *http.Request) {
|
||||||
modify_request := ldap.NewModifyRequest(group, nil)
|
modify_request := ldap.NewModifyRequest(group, nil)
|
||||||
modify_request.Delete("member", []string{dn})
|
modify_request.Delete("member", []string{dn})
|
||||||
|
|
||||||
err := login.conn.Modify(modify_request)
|
err := user.Login.conn.Modify(modify_request)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
dError = err.Error()
|
dError = err.Error()
|
||||||
} else {
|
} else {
|
||||||
|
@ -500,7 +500,7 @@ func handleAdminLDAP(w http.ResponseWriter, r *http.Request) {
|
||||||
modify_request := ldap.NewModifyRequest(group, nil)
|
modify_request := ldap.NewModifyRequest(group, nil)
|
||||||
modify_request.Add("member", []string{dn})
|
modify_request.Add("member", []string{dn})
|
||||||
|
|
||||||
err := login.conn.Modify(modify_request)
|
err := user.Login.conn.Modify(modify_request)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
dError = err.Error()
|
dError = err.Error()
|
||||||
} else {
|
} else {
|
||||||
|
@ -511,7 +511,7 @@ func handleAdminLDAP(w http.ResponseWriter, r *http.Request) {
|
||||||
modify_request := ldap.NewModifyRequest(dn, nil)
|
modify_request := ldap.NewModifyRequest(dn, nil)
|
||||||
modify_request.Delete("member", []string{member})
|
modify_request.Delete("member", []string{member})
|
||||||
|
|
||||||
err := login.conn.Modify(modify_request)
|
err := user.Login.conn.Modify(modify_request)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
dError = err.Error()
|
dError = err.Error()
|
||||||
} else {
|
} else {
|
||||||
|
@ -519,7 +519,7 @@ func handleAdminLDAP(w http.ResponseWriter, r *http.Request) {
|
||||||
}
|
}
|
||||||
} else if action == "delete-object" {
|
} else if action == "delete-object" {
|
||||||
del_request := ldap.NewDelRequest(dn, nil)
|
del_request := ldap.NewDelRequest(dn, nil)
|
||||||
err := login.conn.Del(del_request)
|
err := user.Login.conn.Del(del_request)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
dError = err.Error()
|
dError = err.Error()
|
||||||
} else {
|
} else {
|
||||||
|
@ -537,7 +537,7 @@ func handleAdminLDAP(w http.ResponseWriter, r *http.Request) {
|
||||||
[]string{},
|
[]string{},
|
||||||
nil)
|
nil)
|
||||||
|
|
||||||
sr, err := login.conn.Search(searchRequest)
|
sr, err := user.Login.conn.Search(searchRequest)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
|
@ -621,7 +621,7 @@ func handleAdminLDAP(w http.ResponseWriter, r *http.Request) {
|
||||||
fmt.Sprintf("(objectClass=organizationalPerson)"),
|
fmt.Sprintf("(objectClass=organizationalPerson)"),
|
||||||
[]string{"dn", "displayname", "description"},
|
[]string{"dn", "displayname", "description"},
|
||||||
nil)
|
nil)
|
||||||
sr, err = login.conn.Search(searchRequest)
|
sr, err = user.Login.conn.Search(searchRequest)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
|
@ -675,7 +675,7 @@ func handleAdminLDAP(w http.ResponseWriter, r *http.Request) {
|
||||||
fmt.Sprintf("(objectClass=groupOfNames)"),
|
fmt.Sprintf("(objectClass=groupOfNames)"),
|
||||||
[]string{"dn", "description"},
|
[]string{"dn", "description"},
|
||||||
nil)
|
nil)
|
||||||
sr, err = login.conn.Search(searchRequest)
|
sr, err = user.Login.conn.Search(searchRequest)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
|
@ -719,7 +719,7 @@ func handleAdminLDAP(w http.ResponseWriter, r *http.Request) {
|
||||||
[]string{"dn", "displayname", "description"},
|
[]string{"dn", "displayname", "description"},
|
||||||
nil)
|
nil)
|
||||||
|
|
||||||
sr, err = login.conn.Search(searchRequest)
|
sr, err = user.Login.conn.Search(searchRequest)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
|
@ -787,8 +787,8 @@ type CreateData struct {
|
||||||
func handleAdminCreate(w http.ResponseWriter, r *http.Request) {
|
func handleAdminCreate(w http.ResponseWriter, r *http.Request) {
|
||||||
templateAdminCreate := getTemplate("admin_create.html")
|
templateAdminCreate := getTemplate("admin_create.html")
|
||||||
|
|
||||||
login := checkAdminLogin(w, r)
|
user := checkAdminLogin(w, r)
|
||||||
if login == nil {
|
if user == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -803,7 +803,7 @@ func handleAdminCreate(w http.ResponseWriter, r *http.Request) {
|
||||||
[]string{},
|
[]string{},
|
||||||
nil)
|
nil)
|
||||||
|
|
||||||
sr, err := login.conn.Search(searchRequest)
|
sr, err := user.Login.conn.Search(searchRequest)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
|
@ -894,7 +894,7 @@ func handleAdminCreate(w http.ResponseWriter, r *http.Request) {
|
||||||
req.Attribute("description", []string{data.Description})
|
req.Attribute("description", []string{data.Description})
|
||||||
}
|
}
|
||||||
|
|
||||||
err := login.conn.Add(req)
|
err := user.Login.conn.Add(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
data.Error = err.Error()
|
data.Error = err.Error()
|
||||||
} else {
|
} else {
|
||||||
|
|
117
api.go
117
api.go
|
@ -2,115 +2,14 @@ package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
//"context"
|
//"context"
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"encoding/json"
|
||||||
garage "git.deuxfleurs.fr/garage-sdk/garage-admin-sdk-golang"
|
garage "git.deuxfleurs.fr/garage-sdk/garage-admin-sdk-golang"
|
||||||
"github.com/go-ldap/ldap/v3"
|
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func checkLoginAPI(w http.ResponseWriter, r *http.Request) (*LoginStatus, error) {
|
|
||||||
username, password, ok := r.BasicAuth()
|
|
||||||
if !ok {
|
|
||||||
w.Header().Set("WWW-Authenticate", `Basic realm="restricted", charset="UTF-8"`)
|
|
||||||
http.Error(w, "Unauthorized", http.StatusUnauthorized)
|
|
||||||
return nil, errors.New("Missing or invalid 'Authenticate: Basic' field")
|
|
||||||
}
|
|
||||||
user_dn := buildUserDN(username)
|
|
||||||
|
|
||||||
login_info := &LoginInfo{
|
|
||||||
DN: user_dn,
|
|
||||||
Username: username,
|
|
||||||
Password: password,
|
|
||||||
}
|
|
||||||
|
|
||||||
l := ldapOpen(w)
|
|
||||||
if l == nil {
|
|
||||||
log.Println("Unable to open LDAP connection")
|
|
||||||
return nil, errors.New("Unable to open LDAP connection")
|
|
||||||
}
|
|
||||||
|
|
||||||
err := l.Bind(login_info.DN, login_info.Password)
|
|
||||||
if err != nil {
|
|
||||||
w.Header().Set("WWW-Authenticate", `Basic realm="restricted", charset="UTF-8"`)
|
|
||||||
http.Error(w, "Unauthorized", http.StatusUnauthorized)
|
|
||||||
return nil, errors.New("Unable to bind this user+password combination on the LDAP server")
|
|
||||||
}
|
|
||||||
|
|
||||||
loginStatus := &LoginStatus{
|
|
||||||
Info: login_info,
|
|
||||||
conn: l,
|
|
||||||
}
|
|
||||||
|
|
||||||
requestKind := "(objectClass=organizationalPerson)"
|
|
||||||
|
|
||||||
if strings.EqualFold(login_info.DN, config.AdminAccount) {
|
|
||||||
requestKind = "(objectclass=*)"
|
|
||||||
}
|
|
||||||
searchRequest := ldap.NewSearchRequest(
|
|
||||||
login_info.DN,
|
|
||||||
ldap.ScopeBaseObject, ldap.NeverDerefAliases, 0, 0, false,
|
|
||||||
requestKind,
|
|
||||||
[]string{
|
|
||||||
"dn",
|
|
||||||
"displayname",
|
|
||||||
"givenname",
|
|
||||||
"sn",
|
|
||||||
"mail",
|
|
||||||
"memberof",
|
|
||||||
"description",
|
|
||||||
"garage_s3_access_key",
|
|
||||||
FIELD_NAME_DIRECTORY_VISIBILITY,
|
|
||||||
FIELD_NAME_PROFILE_PICTURE,
|
|
||||||
},
|
|
||||||
nil)
|
|
||||||
|
|
||||||
sr, err := l.Search(searchRequest)
|
|
||||||
if err != nil {
|
|
||||||
log.Println(err)
|
|
||||||
http.Error(w, "Internal server error", http.StatusInternalServerError)
|
|
||||||
return nil, errors.New("Unable to search essential information about the logged user on LDAP")
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(sr.Entries) != 1 {
|
|
||||||
log.Println(fmt.Sprintf("Unable to find entry for %s", login_info.DN))
|
|
||||||
http.Error(w, "Internal server error", http.StatusInternalServerError)
|
|
||||||
return nil, errors.New("Not enough or too many entries for this user in the LDAP directory (expect a unique result)")
|
|
||||||
}
|
|
||||||
|
|
||||||
loginStatus.UserEntry = sr.Entries[0]
|
|
||||||
|
|
||||||
loginStatus.CanAdmin = strings.EqualFold(loginStatus.Info.DN, config.AdminAccount)
|
|
||||||
loginStatus.CanInvite = false
|
|
||||||
for _, attr := range loginStatus.UserEntry.Attributes {
|
|
||||||
if strings.EqualFold(attr.Name, "memberof") {
|
|
||||||
for _, group := range attr.Values {
|
|
||||||
if config.GroupCanInvite != "" && strings.EqualFold(group, config.GroupCanInvite) {
|
|
||||||
loginStatus.CanInvite = true
|
|
||||||
}
|
|
||||||
if config.GroupCanAdmin != "" && strings.EqualFold(group, config.GroupCanAdmin) {
|
|
||||||
loginStatus.CanAdmin = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return loginStatus, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func checkLoginAndS3API(w http.ResponseWriter, r *http.Request) (*LoginStatus, *garage.KeyInfo, error) {
|
|
||||||
login, err := checkLoginAPI(w, r)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
keyPair, err := checkS3(login)
|
|
||||||
return login, keyPair, err
|
|
||||||
}
|
|
||||||
|
|
||||||
type ApiQuotaView struct {
|
type ApiQuotaView struct {
|
||||||
files *uint64
|
files *uint64
|
||||||
size *uint64
|
size *uint64
|
||||||
|
@ -131,6 +30,7 @@ type BucketRequest struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleAPIGarageBucket(w http.ResponseWriter, r *http.Request) {
|
func handleAPIGarageBucket(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
br, err := buildBucketRequest(w, r)
|
br, err := buildBucketRequest(w, r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
|
@ -151,10 +51,9 @@ func handleAPIGarageBucket(w http.ResponseWriter, r *http.Request) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func buildBucketRequest(w http.ResponseWriter, r *http.Request) (*BucketRequest, error) {
|
func buildBucketRequest(w http.ResponseWriter, r *http.Request) (*BucketRequest, error) {
|
||||||
_, s3key, err := checkLoginAndS3API(w, r)
|
user := RequireUserApi(w, r)
|
||||||
if err != nil {
|
if user == nil {
|
||||||
//http.Error(w, "Unable to connect on LDAP", http.StatusUnauthorized)
|
return nil, errors.New("Unable to fetch user")
|
||||||
return nil, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// FETCH BUCKET ID by iterating over buckets owned by this key
|
// FETCH BUCKET ID by iterating over buckets owned by this key
|
||||||
|
@ -162,6 +61,11 @@ func buildBucketRequest(w http.ResponseWriter, r *http.Request) (*BucketRequest,
|
||||||
var bucketId *string
|
var bucketId *string
|
||||||
var global *bool
|
var global *bool
|
||||||
|
|
||||||
|
s3key, err := user.S3KeyInfo()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
findBucketIdLoop:
|
findBucketIdLoop:
|
||||||
for _, bucket := range s3key.Buckets {
|
for _, bucket := range s3key.Buckets {
|
||||||
for _, localAlias := range bucket.LocalAliases {
|
for _, localAlias := range bucket.LocalAliases {
|
||||||
|
@ -192,6 +96,7 @@ findBucketIdLoop:
|
||||||
global: *global,
|
global: *global,
|
||||||
http: r,
|
http: r,
|
||||||
}, nil
|
}, nil
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func patchGarageBucket(w http.ResponseWriter, br *BucketRequest) {
|
func patchGarageBucket(w http.ResponseWriter, br *BucketRequest) {
|
||||||
|
|
10
directory.go
10
directory.go
|
@ -15,8 +15,8 @@ const FIELD_NAME_DIRECTORY_VISIBILITY = "directoryVisibility"
|
||||||
func handleDirectory(w http.ResponseWriter, r *http.Request) {
|
func handleDirectory(w http.ResponseWriter, r *http.Request) {
|
||||||
templateDirectory := getTemplate("directory.html")
|
templateDirectory := getTemplate("directory.html")
|
||||||
|
|
||||||
login := checkLogin(w, r)
|
user := RequireUserHtml(w, r)
|
||||||
if login == nil {
|
if user == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -49,8 +49,8 @@ func handleDirectorySearch(w http.ResponseWriter, r *http.Request) {
|
||||||
}
|
}
|
||||||
|
|
||||||
//Log to allow the research
|
//Log to allow the research
|
||||||
login := checkLogin(w, r)
|
user := RequireUserHtml(w, r)
|
||||||
if login == nil {
|
if user == nil {
|
||||||
http.Error(w, "Login required", http.StatusUnauthorized)
|
http.Error(w, "Login required", http.StatusUnauthorized)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -69,7 +69,7 @@ func handleDirectorySearch(w http.ResponseWriter, r *http.Request) {
|
||||||
},
|
},
|
||||||
nil)
|
nil)
|
||||||
|
|
||||||
sr, err := login.conn.Search(searchRequest)
|
sr, err := user.Login.conn.Search(searchRequest)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
|
|
100
garage.go
100
garage.go
|
@ -2,16 +2,15 @@ package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
garage "git.deuxfleurs.fr/garage-sdk/garage-admin-sdk-golang"
|
garage "git.deuxfleurs.fr/garage-sdk/garage-admin-sdk-golang"
|
||||||
"github.com/go-ldap/ldap/v3"
|
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
func gadmin() (*garage.APIClient, context.Context) {
|
func gadmin() (*garage.APIClient, context.Context) {
|
||||||
// Set Host and other parameters
|
// Set Host and other parameters
|
||||||
configuration := garage.NewConfiguration()
|
configuration := garage.NewConfiguration()
|
||||||
|
@ -48,7 +47,9 @@ func grgGetKey(accessKey string) (*garage.KeyInfo, error) {
|
||||||
return resp, nil
|
return resp, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func grgCreateWebsite(gkey, bucket string) (*garage.BucketInfo, error) {
|
|
||||||
|
|
||||||
|
func grgCreateWebsite(gkey, bucket string, quotas *UserQuota) (*garage.BucketInfo, error) {
|
||||||
client, ctx := gadmin()
|
client, ctx := gadmin()
|
||||||
|
|
||||||
br := garage.NewCreateBucketRequest()
|
br := garage.NewCreateBucketRequest()
|
||||||
|
@ -79,9 +80,7 @@ func grgCreateWebsite(gkey, bucket string) (*garage.BucketInfo, error) {
|
||||||
wr.SetIndexDocument("index.html")
|
wr.SetIndexDocument("index.html")
|
||||||
wr.SetErrorDocument("error.html")
|
wr.SetErrorDocument("error.html")
|
||||||
|
|
||||||
qr := garage.NewUpdateBucketRequestQuotas()
|
qr := quotas.DefaultWebsiteQuota()
|
||||||
qr.SetMaxSize(1024 * 1024 * 50) // 50MB
|
|
||||||
qr.SetMaxObjects(10000) //10k objects
|
|
||||||
|
|
||||||
ur := garage.NewUpdateBucketRequest()
|
ur := garage.NewUpdateBucketRequest()
|
||||||
ur.SetWebsiteAccess(*wr)
|
ur.SetWebsiteAccess(*wr)
|
||||||
|
@ -153,85 +152,37 @@ func grgGetBucket(bid string) (*garage.BucketInfo, error) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func checkS3(login *LoginStatus) (*garage.KeyInfo, error) {
|
// --- Start page rendering functions
|
||||||
if login == nil {
|
|
||||||
return nil, errors.New("Login can't be nil")
|
|
||||||
}
|
|
||||||
keyID := login.UserEntry.GetAttributeValue("garage_s3_access_key")
|
|
||||||
if keyID == "" {
|
|
||||||
keyPair, err := grgCreateKey(login.Info.Username)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
modify_request := ldap.NewModifyRequest(login.Info.DN, nil)
|
|
||||||
modify_request.Replace("garage_s3_access_key", []string{*keyPair.AccessKeyId})
|
|
||||||
// @FIXME compatibility feature for bagage (SFTP+webdav)
|
|
||||||
// you can remove it once bagage will be updated to fetch the key from garage directly
|
|
||||||
// or when bottin will be able to dynamically fetch it.
|
|
||||||
modify_request.Replace("garage_s3_secret_key", []string{*keyPair.SecretAccessKey})
|
|
||||||
err = login.conn.Modify(modify_request)
|
|
||||||
return keyPair, err
|
|
||||||
}
|
|
||||||
// Note: we could simply return the login info, but LX asked we do not
|
|
||||||
// store the secrets in LDAP in the future.
|
|
||||||
keyPair, err := grgGetKey(keyID)
|
|
||||||
return keyPair, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func checkLoginAndS3(w http.ResponseWriter, r *http.Request) (*LoginStatus, *garage.KeyInfo, error) {
|
|
||||||
login := checkLogin(w, r)
|
|
||||||
if login == nil {
|
|
||||||
return nil, nil, errors.New("LDAP login failed")
|
|
||||||
}
|
|
||||||
keyPair, err := checkS3(login)
|
|
||||||
return login, keyPair, err
|
|
||||||
}
|
|
||||||
|
|
||||||
type keyView struct {
|
|
||||||
Status *LoginStatus
|
|
||||||
Key *garage.KeyInfo
|
|
||||||
}
|
|
||||||
|
|
||||||
func handleGarageKey(w http.ResponseWriter, r *http.Request) {
|
func handleGarageKey(w http.ResponseWriter, r *http.Request) {
|
||||||
login, s3key, err := checkLoginAndS3(w, r)
|
user := RequireUserHtml(w, r)
|
||||||
if err != nil {
|
if user == nil {
|
||||||
log.Println(err)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
view := keyView{Status: login, Key: s3key}
|
|
||||||
|
|
||||||
tKey := getTemplate("garage_key.html")
|
tKey := getTemplate("garage_key.html")
|
||||||
tKey.Execute(w, &view)
|
tKey.Execute(w, user)
|
||||||
}
|
|
||||||
|
|
||||||
type webListView struct {
|
|
||||||
Status *LoginStatus
|
|
||||||
Key *garage.KeyInfo
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleGarageWebsiteList(w http.ResponseWriter, r *http.Request) {
|
func handleGarageWebsiteList(w http.ResponseWriter, r *http.Request) {
|
||||||
login, s3key, err := checkLoginAndS3(w, r)
|
user := RequireUserHtml(w, r)
|
||||||
if err != nil {
|
if user == nil {
|
||||||
log.Println(err)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
view := webListView{Status: login, Key: s3key}
|
|
||||||
|
|
||||||
tWebsiteList := getTemplate("garage_website_list.html")
|
tWebsiteList := getTemplate("garage_website_list.html")
|
||||||
tWebsiteList.Execute(w, &view)
|
tWebsiteList.Execute(w, user)
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleGarageWebsiteNew(w http.ResponseWriter, r *http.Request) {
|
func handleGarageWebsiteNew(w http.ResponseWriter, r *http.Request) {
|
||||||
_, s3key, err := checkLoginAndS3(w, r)
|
user := RequireUserHtml(w, r)
|
||||||
if err != nil {
|
if user == nil {
|
||||||
log.Println(err)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
tWebsiteNew := getTemplate("garage_website_new.html")
|
tWebsiteNew := getTemplate("garage_website_new.html")
|
||||||
if r.Method == "POST" {
|
if r.Method == "POST" {
|
||||||
r.ParseForm()
|
r.ParseForm()
|
||||||
log.Println(r.Form)
|
|
||||||
|
|
||||||
bucket := strings.Join(r.Form["bucket"], "")
|
bucket := strings.Join(r.Form["bucket"], "")
|
||||||
if bucket == "" {
|
if bucket == "" {
|
||||||
|
@ -244,7 +195,15 @@ func handleGarageWebsiteNew(w http.ResponseWriter, r *http.Request) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
binfo, err := grgCreateWebsite(*s3key.AccessKeyId, bucket)
|
keyInfo, err := user.S3KeyInfo()
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
// @FIXME we need to return the error to the user
|
||||||
|
tWebsiteNew.Execute(w, nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
binfo, err := grgCreateWebsite(*keyInfo.AccessKeyId, bucket, user.Quota)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println(err)
|
log.Println(err)
|
||||||
// @FIXME we need to return the error to the user
|
// @FIXME we need to return the error to the user
|
||||||
|
@ -260,8 +219,7 @@ func handleGarageWebsiteNew(w http.ResponseWriter, r *http.Request) {
|
||||||
}
|
}
|
||||||
|
|
||||||
type webInspectView struct {
|
type webInspectView struct {
|
||||||
Status *LoginStatus
|
User *LoggedUser
|
||||||
Key *garage.KeyInfo
|
|
||||||
Bucket *garage.BucketInfo
|
Bucket *garage.BucketInfo
|
||||||
IndexDoc string
|
IndexDoc string
|
||||||
ErrorDoc string
|
ErrorDoc string
|
||||||
|
@ -271,13 +229,14 @@ type webInspectView struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleGarageWebsiteInspect(w http.ResponseWriter, r *http.Request) {
|
func handleGarageWebsiteInspect(w http.ResponseWriter, r *http.Request) {
|
||||||
login, s3key, err := checkLoginAndS3(w, r)
|
user := RequireUserHtml(w, r)
|
||||||
if err != nil {
|
if user == nil {
|
||||||
log.Println(err)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
bucketId := mux.Vars(r)["bucket"]
|
bucketId := mux.Vars(r)["bucket"]
|
||||||
|
// @FIXME check that user owns the bucket....
|
||||||
|
|
||||||
binfo, err := grgGetBucket(bucketId)
|
binfo, err := grgGetBucket(bucketId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println(err)
|
log.Println(err)
|
||||||
|
@ -288,8 +247,7 @@ func handleGarageWebsiteInspect(w http.ResponseWriter, r *http.Request) {
|
||||||
q := binfo.GetQuotas()
|
q := binfo.GetQuotas()
|
||||||
|
|
||||||
view := webInspectView{
|
view := webInspectView{
|
||||||
Status: login,
|
User: user,
|
||||||
Key: s3key,
|
|
||||||
Bucket: binfo,
|
Bucket: binfo,
|
||||||
IndexDoc: (&wc).GetIndexDocument(),
|
IndexDoc: (&wc).GetIndexDocument(),
|
||||||
ErrorDoc: (&wc).GetErrorDocument(),
|
ErrorDoc: (&wc).GetErrorDocument(),
|
||||||
|
|
|
@ -1,19 +1,19 @@
|
||||||
version: '3'
|
version: '3'
|
||||||
services:
|
services:
|
||||||
consul:
|
consul:
|
||||||
image: consul
|
image: hashicorp/consul:1.16
|
||||||
restart: "always"
|
restart: "always"
|
||||||
expose:
|
expose:
|
||||||
- 8500
|
- 8500
|
||||||
bottin:
|
bottin:
|
||||||
image: dxflrs/bottin:dnp41vp8w24h4mbh0xg1mybzr1f46k41
|
image: dxflrs/bottin:7h18i30cckckaahv87d3c86pn4a7q41z
|
||||||
command: "-config /etc/bottin.json"
|
#command: "-config /etc/bottin.json"
|
||||||
restart: "always"
|
restart: "always"
|
||||||
depends_on: ["consul"]
|
depends_on: ["consul"]
|
||||||
ports:
|
ports:
|
||||||
- "389:389"
|
- "389:389"
|
||||||
volumes:
|
volumes:
|
||||||
- "./config/bottin.json:/etc/bottin.json"
|
- "./config/bottin.json:/config.json"
|
||||||
garage:
|
garage:
|
||||||
image: dxflrs/garage:v0.8.2
|
image: dxflrs/garage:v0.8.2
|
||||||
ports:
|
ports:
|
||||||
|
|
36
invite.go
36
invite.go
|
@ -21,29 +21,29 @@ import (
|
||||||
|
|
||||||
var EMAIL_REGEXP = regexp.MustCompile("^[a-zA-Z0-9.!#$%&'*+\\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$")
|
var EMAIL_REGEXP = regexp.MustCompile("^[a-zA-Z0-9.!#$%&'*+\\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$")
|
||||||
|
|
||||||
func checkInviterLogin(w http.ResponseWriter, r *http.Request) *LoginStatus {
|
func checkInviterLogin(w http.ResponseWriter, r *http.Request) *LoggedUser {
|
||||||
login := checkLogin(w, r)
|
user := RequireUserHtml(w, r)
|
||||||
if login == nil {
|
if user == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if !login.CanInvite {
|
if !user.Capabilities.CanInvite {
|
||||||
http.Error(w, "Not authorized to invite new users.", http.StatusUnauthorized)
|
http.Error(w, "Not authorized to invite new users.", http.StatusUnauthorized)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return login
|
return user
|
||||||
}
|
}
|
||||||
|
|
||||||
// New account creation directly from interface
|
// New account creation directly from interface
|
||||||
|
|
||||||
func handleInviteNewAccount(w http.ResponseWriter, r *http.Request) {
|
func handleInviteNewAccount(w http.ResponseWriter, r *http.Request) {
|
||||||
login := checkInviterLogin(w, r)
|
user := checkInviterLogin(w, r)
|
||||||
if login == nil {
|
if user == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
handleNewAccount(w, r, login.conn, login.Info.DN)
|
handleNewAccount(w, r, user.Login.conn, user.Login.Info.DN())
|
||||||
}
|
}
|
||||||
|
|
||||||
// New account creation using code
|
// New account creation using code
|
||||||
|
@ -52,13 +52,13 @@ func handleInvitationCode(w http.ResponseWriter, r *http.Request) {
|
||||||
code := mux.Vars(r)["code"]
|
code := mux.Vars(r)["code"]
|
||||||
code_id, code_pw := readCode(code)
|
code_id, code_pw := readCode(code)
|
||||||
|
|
||||||
l := ldapOpen(w)
|
l, err := NewLdapCon()
|
||||||
if l == nil {
|
if err != nil {
|
||||||
return
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
}
|
}
|
||||||
|
|
||||||
inviteDn := config.InvitationNameAttr + "=" + code_id + "," + config.InvitationBaseDN
|
inviteDn := config.InvitationNameAttr + "=" + code_id + "," + config.InvitationBaseDN
|
||||||
err := l.Bind(inviteDn, code_pw)
|
err = l.Bind(inviteDn, code_pw)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
templateInviteInvalidCode := getTemplate("invite_invalid_code.html")
|
templateInviteInvalidCode := getTemplate("invite_invalid_code.html")
|
||||||
templateInviteInvalidCode.Execute(w, nil)
|
templateInviteInvalidCode.Execute(w, nil)
|
||||||
|
@ -241,8 +241,8 @@ type CodeMailFields struct {
|
||||||
func handleInviteSendCode(w http.ResponseWriter, r *http.Request) {
|
func handleInviteSendCode(w http.ResponseWriter, r *http.Request) {
|
||||||
templateInviteSendCode := getTemplate("invite_send_code.html")
|
templateInviteSendCode := getTemplate("invite_send_code.html")
|
||||||
|
|
||||||
login := checkInviterLogin(w, r)
|
user := checkInviterLogin(w, r)
|
||||||
if login == nil {
|
if user == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -257,14 +257,14 @@ func handleInviteSendCode(w http.ResponseWriter, r *http.Request) {
|
||||||
sendto := strings.Join(r.Form["sendto"], "")
|
sendto := strings.Join(r.Form["sendto"], "")
|
||||||
|
|
||||||
if choice == "display" || choice == "send" {
|
if choice == "display" || choice == "send" {
|
||||||
trySendCode(login, choice, sendto, data)
|
trySendCode(user, choice, sendto, data)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
templateInviteSendCode.Execute(w, data)
|
templateInviteSendCode.Execute(w, data)
|
||||||
}
|
}
|
||||||
|
|
||||||
func trySendCode(login *LoginStatus, choice string, sendto string, data *SendCodeData) {
|
func trySendCode(user *LoggedUser, choice string, sendto string, data *SendCodeData) {
|
||||||
// Generate code
|
// Generate code
|
||||||
code, code_id, code_pw := genCode()
|
code, code_id, code_pw := genCode()
|
||||||
|
|
||||||
|
@ -279,7 +279,7 @@ func trySendCode(login *LoginStatus, choice string, sendto string, data *SendCod
|
||||||
req.Attribute("userpassword", []string{pw})
|
req.Attribute("userpassword", []string{pw})
|
||||||
req.Attribute("objectclass", []string{"top", "invitationCode"})
|
req.Attribute("objectclass", []string{"top", "invitationCode"})
|
||||||
|
|
||||||
err = login.conn.Add(req)
|
err = user.Login.conn.Add(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
data.ErrorMessage = err.Error()
|
data.ErrorMessage = err.Error()
|
||||||
return
|
return
|
||||||
|
@ -303,7 +303,7 @@ func trySendCode(login *LoginStatus, choice string, sendto string, data *SendCod
|
||||||
templateMail.Execute(buf, &CodeMailFields{
|
templateMail.Execute(buf, &CodeMailFields{
|
||||||
To: sendto,
|
To: sendto,
|
||||||
From: config.MailFrom,
|
From: config.MailFrom,
|
||||||
InviteFrom: login.WelcomeName(),
|
InviteFrom: user.WelcomeName(),
|
||||||
Code: code,
|
Code: code,
|
||||||
WebBaseAddress: config.WebAddress,
|
WebBaseAddress: config.WebAddress,
|
||||||
})
|
})
|
||||||
|
|
293
login.go
Normal file
293
login.go
Normal file
|
@ -0,0 +1,293 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/tls"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
garage "git.deuxfleurs.fr/garage-sdk/garage-admin-sdk-golang"
|
||||||
|
"github.com/go-ldap/ldap/v3"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrNotAuthenticatedSession = fmt.Errorf("User has no session")
|
||||||
|
ErrNotAuthenticatedBasic = fmt.Errorf("User has not sent Authentication Basic information")
|
||||||
|
ErrNotAuthenticated = fmt.Errorf("User is not authenticated")
|
||||||
|
ErrWrongLDAPCredentials = fmt.Errorf("LDAP credentials are wrong")
|
||||||
|
ErrLDAPServerUnreachable = fmt.Errorf("Unable to open the LDAP server")
|
||||||
|
ErrLDAPSearchInternalError = fmt.Errorf("LDAP Search of this user failed with an internal error")
|
||||||
|
ErrLDAPSearchNotFound = fmt.Errorf("User is authenticated but its associated data can not be found during search")
|
||||||
|
)
|
||||||
|
|
||||||
|
// --- Login Info ---
|
||||||
|
type LoginInfo struct {
|
||||||
|
Username string
|
||||||
|
Password string
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewLoginInfoFromSession(r *http.Request) (*LoginInfo, error) {
|
||||||
|
session, err := store.Get(r, SESSION_NAME)
|
||||||
|
if err == nil {
|
||||||
|
username, ok_user := session.Values["login_username"]
|
||||||
|
password, ok_pwd := session.Values["login_password"]
|
||||||
|
|
||||||
|
if ok_user && ok_pwd {
|
||||||
|
loginInfo := &LoginInfo{
|
||||||
|
Username: username.(string),
|
||||||
|
Password: password.(string),
|
||||||
|
}
|
||||||
|
return loginInfo, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, errors.Join(ErrNotAuthenticatedSession, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewLoginInfoFromBasicAuth(r *http.Request) (*LoginInfo, error) {
|
||||||
|
username, password, ok := r.BasicAuth()
|
||||||
|
if ok {
|
||||||
|
login_info := &LoginInfo{
|
||||||
|
Username: username,
|
||||||
|
Password: password,
|
||||||
|
}
|
||||||
|
|
||||||
|
return login_info, nil
|
||||||
|
}
|
||||||
|
return nil, ErrNotAuthenticatedBasic
|
||||||
|
}
|
||||||
|
|
||||||
|
func (li *LoginInfo) DN() string {
|
||||||
|
user_dn := fmt.Sprintf("%s=%s,%s", config.UserNameAttr, li.Username, config.UserBaseDN)
|
||||||
|
if strings.EqualFold(li.Username, config.AdminAccount) {
|
||||||
|
user_dn = li.Username
|
||||||
|
}
|
||||||
|
|
||||||
|
return user_dn
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Login Status ---
|
||||||
|
type LoginStatus struct {
|
||||||
|
Info *LoginInfo
|
||||||
|
conn *ldap.Conn
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewLoginStatus(r *http.Request, login_info *LoginInfo) (*LoginStatus, error) {
|
||||||
|
l, err := NewLdapCon()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = l.Bind(login_info.DN(), login_info.Password)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Join(ErrWrongLDAPCredentials, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
loginStatus := &LoginStatus{
|
||||||
|
Info: login_info,
|
||||||
|
conn: l,
|
||||||
|
}
|
||||||
|
return loginStatus, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewLdapCon() (*ldap.Conn, error) {
|
||||||
|
l, err := ldap.DialURL(config.LdapServerAddr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Join(ErrLDAPServerUnreachable, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if config.LdapTLS {
|
||||||
|
err = l.StartTLS(&tls.Config{InsecureSkipVerify: true})
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Join(ErrLDAPServerUnreachable, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return l, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Capabilities ---
|
||||||
|
type Capabilities struct {
|
||||||
|
CanAdmin bool
|
||||||
|
CanInvite bool
|
||||||
|
}
|
||||||
|
func NewCapabilities(login *LoginStatus, entry *ldap.Entry) *Capabilities {
|
||||||
|
// Initialize
|
||||||
|
canAdmin := false
|
||||||
|
canInvite := false
|
||||||
|
|
||||||
|
// Special case for the "admin" account that is de-facto admin
|
||||||
|
canAdmin = strings.EqualFold(login.Info.DN(), config.AdminAccount)
|
||||||
|
|
||||||
|
// Check if this account is part of a group that give capabilities
|
||||||
|
for _, attr := range entry.Attributes {
|
||||||
|
if strings.EqualFold(attr.Name, "memberof") {
|
||||||
|
for _, group := range attr.Values {
|
||||||
|
if config.GroupCanInvite != "" && strings.EqualFold(group, config.GroupCanInvite) {
|
||||||
|
canInvite = true
|
||||||
|
}
|
||||||
|
if config.GroupCanAdmin != "" && strings.EqualFold(group, config.GroupCanAdmin) {
|
||||||
|
canAdmin = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &Capabilities{
|
||||||
|
CanAdmin: canAdmin,
|
||||||
|
CanInvite: canInvite,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Logged User ---
|
||||||
|
type LoggedUser struct {
|
||||||
|
Login *LoginStatus
|
||||||
|
Entry *ldap.Entry
|
||||||
|
Capabilities *Capabilities
|
||||||
|
Quota *UserQuota
|
||||||
|
s3key *garage.KeyInfo
|
||||||
|
}
|
||||||
|
func NewLoggedUser(login *LoginStatus) (*LoggedUser, error) {
|
||||||
|
requestKind := "(objectClass=organizationalPerson)"
|
||||||
|
if strings.EqualFold(login.Info.DN(), config.AdminAccount) {
|
||||||
|
requestKind = "(objectclass=*)"
|
||||||
|
}
|
||||||
|
|
||||||
|
searchRequest := ldap.NewSearchRequest(
|
||||||
|
login.Info.DN(),
|
||||||
|
ldap.ScopeBaseObject, ldap.NeverDerefAliases, 0, 0, false,
|
||||||
|
requestKind,
|
||||||
|
[]string{
|
||||||
|
"dn",
|
||||||
|
"displayname",
|
||||||
|
"givenname",
|
||||||
|
"sn",
|
||||||
|
"mail",
|
||||||
|
"memberof",
|
||||||
|
"description",
|
||||||
|
"garage_s3_access_key",
|
||||||
|
FIELD_NAME_DIRECTORY_VISIBILITY,
|
||||||
|
FIELD_NAME_PROFILE_PICTURE,
|
||||||
|
FIELD_QUOTA_WEBSITE_SIZE_BURSTED,
|
||||||
|
FIELD_QUOTA_WEBSITE_COUNT,
|
||||||
|
},
|
||||||
|
nil)
|
||||||
|
|
||||||
|
sr, err := login.conn.Search(searchRequest)
|
||||||
|
if err != nil {
|
||||||
|
return nil, ErrLDAPSearchInternalError
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(sr.Entries) != 1 {
|
||||||
|
return nil, ErrLDAPSearchNotFound
|
||||||
|
}
|
||||||
|
entry := sr.Entries[0]
|
||||||
|
|
||||||
|
lu := &LoggedUser {
|
||||||
|
Login: login,
|
||||||
|
Entry: entry,
|
||||||
|
Capabilities: NewCapabilities(login, entry),
|
||||||
|
Quota: NewUserQuotaFromEntry(entry),
|
||||||
|
}
|
||||||
|
return lu, nil
|
||||||
|
}
|
||||||
|
func (lu *LoggedUser) WelcomeName() string {
|
||||||
|
ret := lu.Entry.GetAttributeValue("givenname")
|
||||||
|
if ret == "" {
|
||||||
|
ret = lu.Entry.GetAttributeValue("displayname")
|
||||||
|
}
|
||||||
|
if ret == "" {
|
||||||
|
ret = lu.Login.Info.Username
|
||||||
|
}
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
func (lu *LoggedUser) S3KeyInfo() (*garage.KeyInfo, error) {
|
||||||
|
var err error
|
||||||
|
var keyPair *garage.KeyInfo
|
||||||
|
|
||||||
|
if lu.s3key == nil {
|
||||||
|
keyID := lu.Entry.GetAttributeValue("garage_s3_access_key")
|
||||||
|
if keyID == "" {
|
||||||
|
// If there is no S3Key in LDAP, generate it...
|
||||||
|
keyPair, err = grgCreateKey(lu.Login.Info.Username)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
modify_request := ldap.NewModifyRequest(lu.Login.Info.DN(), nil)
|
||||||
|
modify_request.Replace("garage_s3_access_key", []string{*keyPair.AccessKeyId})
|
||||||
|
// @FIXME compatibility feature for bagage (SFTP+webdav)
|
||||||
|
// you can remove it once bagage will be updated to fetch the key from garage directly
|
||||||
|
// or when bottin will be able to dynamically fetch it.
|
||||||
|
modify_request.Replace("garage_s3_secret_key", []string{*keyPair.SecretAccessKey})
|
||||||
|
err = lu.Login.conn.Modify(modify_request)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// There is an S3 key in LDAP, fetch its descriptor...
|
||||||
|
keyPair, err = grgGetKey(keyID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cache the keypair...
|
||||||
|
lu.s3key = keyPair
|
||||||
|
}
|
||||||
|
|
||||||
|
return lu.s3key, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Require User Check
|
||||||
|
func RequireUser(r *http.Request) (*LoggedUser, error) {
|
||||||
|
var login_info *LoginInfo
|
||||||
|
|
||||||
|
if li, err := NewLoginInfoFromSession(r); err == nil {
|
||||||
|
login_info = li
|
||||||
|
} else if li, err := NewLoginInfoFromBasicAuth(r); err == nil {
|
||||||
|
login_info = li
|
||||||
|
} else {
|
||||||
|
return nil, ErrNotAuthenticated
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
loginStatus, err := NewLoginStatus(r, login_info)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return NewLoggedUser(loginStatus)
|
||||||
|
}
|
||||||
|
|
||||||
|
func RequireUserHtml(w http.ResponseWriter, r *http.Request) *LoggedUser {
|
||||||
|
user, err := RequireUser(r)
|
||||||
|
|
||||||
|
if errors.Is(err, ErrNotAuthenticated) || errors.Is(err, ErrWrongLDAPCredentials) {
|
||||||
|
http.Redirect(w, r, "/login", http.StatusFound)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return user
|
||||||
|
}
|
||||||
|
|
||||||
|
func RequireUserApi(w http.ResponseWriter, r *http.Request) *LoggedUser {
|
||||||
|
user, err := RequireUser(r)
|
||||||
|
|
||||||
|
if errors.Is(err, ErrNotAuthenticated) || errors.Is(err, ErrWrongLDAPCredentials) {
|
||||||
|
http.Error(w, err.Error(), http.StatusUnauthorized)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return user
|
||||||
|
}
|
198
main.go
198
main.go
|
@ -2,10 +2,8 @@ package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"crypto/tls"
|
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
|
||||||
"html/template"
|
"html/template"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"log"
|
"log"
|
||||||
|
@ -146,6 +144,7 @@ func server(args []string) {
|
||||||
|
|
||||||
r := mux.NewRouter()
|
r := mux.NewRouter()
|
||||||
r.HandleFunc("/", handleHome)
|
r.HandleFunc("/", handleHome)
|
||||||
|
r.HandleFunc("/login", handleLogin)
|
||||||
r.HandleFunc("/logout", handleLogout)
|
r.HandleFunc("/logout", handleLogout)
|
||||||
|
|
||||||
r.HandleFunc("/api/unstable/garage/bucket/{bucket}", handleAPIGarageBucket)
|
r.HandleFunc("/api/unstable/garage/bucket/{bucket}", handleAPIGarageBucket)
|
||||||
|
@ -183,31 +182,6 @@ func server(args []string) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type LoginInfo struct {
|
|
||||||
Username string
|
|
||||||
DN string
|
|
||||||
Password string
|
|
||||||
}
|
|
||||||
|
|
||||||
type LoginStatus struct {
|
|
||||||
Info *LoginInfo
|
|
||||||
conn *ldap.Conn
|
|
||||||
UserEntry *ldap.Entry
|
|
||||||
CanAdmin bool
|
|
||||||
CanInvite bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func (login *LoginStatus) WelcomeName() string {
|
|
||||||
ret := login.UserEntry.GetAttributeValue("givenname")
|
|
||||||
if ret == "" {
|
|
||||||
ret = login.UserEntry.GetAttributeValue("displayname")
|
|
||||||
}
|
|
||||||
if ret == "" {
|
|
||||||
ret = login.Info.Username
|
|
||||||
}
|
|
||||||
return ret
|
|
||||||
}
|
|
||||||
|
|
||||||
func logRequest(handler http.Handler) http.Handler {
|
func logRequest(handler http.Handler) http.Handler {
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
log.Printf("%s %s %s\n", r.RemoteAddr, r.Method, r.URL)
|
log.Printf("%s %s %s\n", r.RemoteAddr, r.Method, r.URL)
|
||||||
|
@ -215,149 +189,32 @@ func logRequest(handler http.Handler) http.Handler {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func checkLogin(w http.ResponseWriter, r *http.Request) *LoginStatus {
|
|
||||||
var login_info *LoginInfo
|
|
||||||
|
|
||||||
session, err := store.Get(r, SESSION_NAME)
|
|
||||||
if err == nil {
|
|
||||||
username, ok := session.Values["login_username"]
|
|
||||||
password, ok2 := session.Values["login_password"]
|
|
||||||
user_dn, ok3 := session.Values["login_dn"]
|
|
||||||
|
|
||||||
if ok && ok2 && ok3 {
|
|
||||||
login_info = &LoginInfo{
|
|
||||||
DN: user_dn.(string),
|
|
||||||
Username: username.(string),
|
|
||||||
Password: password.(string),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if login_info == nil {
|
|
||||||
login_info = handleLogin(w, r)
|
|
||||||
if login_info == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
l := ldapOpen(w)
|
|
||||||
if l == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
err = l.Bind(login_info.DN, login_info.Password)
|
|
||||||
if err != nil {
|
|
||||||
delete(session.Values, "login_username")
|
|
||||||
delete(session.Values, "login_password")
|
|
||||||
delete(session.Values, "login_dn")
|
|
||||||
|
|
||||||
err = session.Save(r, w)
|
|
||||||
if err != nil {
|
|
||||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return checkLogin(w, r)
|
|
||||||
}
|
|
||||||
|
|
||||||
loginStatus := &LoginStatus{
|
|
||||||
Info: login_info,
|
|
||||||
conn: l,
|
|
||||||
}
|
|
||||||
|
|
||||||
requestKind := "(objectClass=organizationalPerson)"
|
|
||||||
if strings.EqualFold(login_info.DN, config.AdminAccount) {
|
|
||||||
requestKind = "(objectclass=*)"
|
|
||||||
}
|
|
||||||
searchRequest := ldap.NewSearchRequest(
|
|
||||||
login_info.DN,
|
|
||||||
ldap.ScopeBaseObject, ldap.NeverDerefAliases, 0, 0, false,
|
|
||||||
requestKind,
|
|
||||||
[]string{
|
|
||||||
"dn",
|
|
||||||
"displayname",
|
|
||||||
"givenname",
|
|
||||||
"sn",
|
|
||||||
"mail",
|
|
||||||
"memberof",
|
|
||||||
"description",
|
|
||||||
"garage_s3_access_key",
|
|
||||||
FIELD_NAME_DIRECTORY_VISIBILITY,
|
|
||||||
FIELD_NAME_PROFILE_PICTURE,
|
|
||||||
},
|
|
||||||
nil)
|
|
||||||
|
|
||||||
sr, err := l.Search(searchRequest)
|
|
||||||
if err != nil {
|
|
||||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(sr.Entries) != 1 {
|
|
||||||
http.Error(w, fmt.Sprintf("Unable to find entry for %s", login_info.DN), http.StatusInternalServerError)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
loginStatus.UserEntry = sr.Entries[0]
|
|
||||||
|
|
||||||
loginStatus.CanAdmin = strings.EqualFold(loginStatus.Info.DN, config.AdminAccount)
|
|
||||||
loginStatus.CanInvite = false
|
|
||||||
for _, attr := range loginStatus.UserEntry.Attributes {
|
|
||||||
if strings.EqualFold(attr.Name, "memberof") {
|
|
||||||
for _, group := range attr.Values {
|
|
||||||
if config.GroupCanInvite != "" && strings.EqualFold(group, config.GroupCanInvite) {
|
|
||||||
loginStatus.CanInvite = true
|
|
||||||
}
|
|
||||||
if config.GroupCanAdmin != "" && strings.EqualFold(group, config.GroupCanAdmin) {
|
|
||||||
loginStatus.CanAdmin = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return loginStatus
|
|
||||||
}
|
|
||||||
|
|
||||||
func ldapOpen(w http.ResponseWriter) *ldap.Conn {
|
|
||||||
l, err := ldap.DialURL(config.LdapServerAddr)
|
|
||||||
if err != nil {
|
|
||||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if config.LdapTLS {
|
|
||||||
err = l.StartTLS(&tls.Config{InsecureSkipVerify: true})
|
|
||||||
if err != nil {
|
|
||||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return l
|
|
||||||
}
|
|
||||||
|
|
||||||
// Page handlers ----
|
// Page handlers ----
|
||||||
|
|
||||||
|
|
||||||
|
// --- Home Controller
|
||||||
type HomePageData struct {
|
type HomePageData struct {
|
||||||
Login *LoginStatus
|
User *LoggedUser
|
||||||
BaseDN string
|
BaseDN string
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleHome(w http.ResponseWriter, r *http.Request) {
|
func handleHome(w http.ResponseWriter, r *http.Request) {
|
||||||
templateHome := getTemplate("home.html")
|
templateHome := getTemplate("home.html")
|
||||||
|
|
||||||
login := checkLogin(w, r)
|
user := RequireUserHtml(w, r)
|
||||||
if login == nil {
|
if user == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
data := &HomePageData{
|
data := &HomePageData{
|
||||||
Login: login,
|
User: user,
|
||||||
BaseDN: config.BaseDN,
|
BaseDN: config.BaseDN,
|
||||||
}
|
}
|
||||||
|
|
||||||
templateHome.Execute(w, data)
|
templateHome.Execute(w, data)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- Logout Controller
|
||||||
func handleLogout(w http.ResponseWriter, r *http.Request) {
|
func handleLogout(w http.ResponseWriter, r *http.Request) {
|
||||||
session, err := store.Get(r, SESSION_NAME)
|
session, err := store.Get(r, SESSION_NAME)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -374,9 +231,10 @@ func handleLogout(w http.ResponseWriter, r *http.Request) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
http.Redirect(w, r, "/", http.StatusFound)
|
http.Redirect(w, r, "/login", http.StatusFound)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- Login Controller ---
|
||||||
type LoginFormData struct {
|
type LoginFormData struct {
|
||||||
Username string
|
Username string
|
||||||
WrongUser bool
|
WrongUser bool
|
||||||
|
@ -384,34 +242,26 @@ type LoginFormData struct {
|
||||||
ErrorMessage string
|
ErrorMessage string
|
||||||
}
|
}
|
||||||
|
|
||||||
func buildUserDN(username string) string {
|
func handleLogin(w http.ResponseWriter, r *http.Request) {
|
||||||
user_dn := fmt.Sprintf("%s=%s,%s", config.UserNameAttr, username, config.UserBaseDN)
|
|
||||||
if strings.EqualFold(username, config.AdminAccount) {
|
|
||||||
user_dn = username
|
|
||||||
}
|
|
||||||
|
|
||||||
return user_dn
|
|
||||||
}
|
|
||||||
|
|
||||||
func handleLogin(w http.ResponseWriter, r *http.Request) *LoginInfo {
|
|
||||||
templateLogin := getTemplate("login.html")
|
templateLogin := getTemplate("login.html")
|
||||||
|
|
||||||
if r.Method == "GET" {
|
if r.Method == "GET" {
|
||||||
templateLogin.Execute(w, LoginFormData{})
|
templateLogin.Execute(w, LoginFormData{})
|
||||||
return nil
|
return
|
||||||
} else if r.Method == "POST" {
|
} else if r.Method == "POST" {
|
||||||
r.ParseForm()
|
r.ParseForm()
|
||||||
|
|
||||||
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 := buildUserDN(username)
|
loginInfo := LoginInfo { username, password }
|
||||||
|
|
||||||
l := ldapOpen(w)
|
l, err := NewLdapCon()
|
||||||
if l == nil {
|
if err != nil {
|
||||||
return nil
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
err := l.Bind(user_dn, password)
|
err = l.Bind(loginInfo.DN(), loginInfo.Password)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
data := &LoginFormData{
|
data := &LoginFormData{
|
||||||
Username: username,
|
Username: username,
|
||||||
|
@ -424,7 +274,7 @@ func handleLogin(w http.ResponseWriter, r *http.Request) *LoginInfo {
|
||||||
data.ErrorMessage = err.Error()
|
data.ErrorMessage = err.Error()
|
||||||
}
|
}
|
||||||
templateLogin.Execute(w, data)
|
templateLogin.Execute(w, data)
|
||||||
return nil
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Successfully logged in, save it to session
|
// Successfully logged in, save it to session
|
||||||
|
@ -435,21 +285,15 @@ func handleLogin(w http.ResponseWriter, r *http.Request) *LoginInfo {
|
||||||
|
|
||||||
session.Values["login_username"] = username
|
session.Values["login_username"] = username
|
||||||
session.Values["login_password"] = password
|
session.Values["login_password"] = password
|
||||||
session.Values["login_dn"] = user_dn
|
|
||||||
|
|
||||||
err = session.Save(r, w)
|
err = session.Save(r, w)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
return nil
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
return &LoginInfo{
|
http.Redirect(w, r, "/", http.StatusFound)
|
||||||
DN: user_dn,
|
|
||||||
Username: username,
|
|
||||||
Password: password,
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
http.Error(w, "Unsupported method", http.StatusBadRequest)
|
http.Error(w, "Unsupported method", http.StatusBadRequest)
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
10
picture.go
10
picture.go
|
@ -44,7 +44,7 @@ func newMinioClient() (*minio.Client, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Upload image through guichet server.
|
// Upload image through guichet server.
|
||||||
func uploadProfilePicture(w http.ResponseWriter, r *http.Request, login *LoginStatus) (string, error) {
|
func uploadProfilePicture(w http.ResponseWriter, r *http.Request, user *LoggedUser) (string, error) {
|
||||||
file, _, err := r.FormFile("image")
|
file, _, err := r.FormFile("image")
|
||||||
|
|
||||||
if err == http.ErrMissingFile {
|
if err == http.ErrMissingFile {
|
||||||
|
@ -74,7 +74,7 @@ func uploadProfilePicture(w http.ResponseWriter, r *http.Request, login *LoginSt
|
||||||
|
|
||||||
// If a previous profile picture existed, delete it
|
// If a previous profile picture existed, delete it
|
||||||
// (don't care about errors)
|
// (don't care about errors)
|
||||||
if nameConsul := login.UserEntry.GetAttributeValue(FIELD_NAME_PROFILE_PICTURE); nameConsul != "" {
|
if nameConsul := user.Entry.GetAttributeValue(FIELD_NAME_PROFILE_PICTURE); nameConsul != "" {
|
||||||
mc.RemoveObject(context.Background(), config.S3Bucket, nameConsul, minio.RemoveObjectOptions{})
|
mc.RemoveObject(context.Background(), config.S3Bucket, nameConsul, minio.RemoveObjectOptions{})
|
||||||
mc.RemoveObject(context.Background(), config.S3Bucket, nameConsul+"-thumb", minio.RemoveObjectOptions{})
|
mc.RemoveObject(context.Background(), config.S3Bucket, nameConsul+"-thumb", minio.RemoveObjectOptions{})
|
||||||
}
|
}
|
||||||
|
@ -144,9 +144,9 @@ func resizePicture(file multipart.File, buffFull, buffThumb *bytes.Buffer) error
|
||||||
func handleDownloadPicture(w http.ResponseWriter, r *http.Request) {
|
func handleDownloadPicture(w http.ResponseWriter, r *http.Request) {
|
||||||
name := mux.Vars(r)["name"]
|
name := mux.Vars(r)["name"]
|
||||||
|
|
||||||
//Check login
|
// Get user
|
||||||
login := checkLogin(w, r)
|
user := RequireUserHtml(w, r)
|
||||||
if login == nil {
|
if user == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
40
profile.go
40
profile.go
|
@ -8,7 +8,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
type ProfileTplData struct {
|
type ProfileTplData struct {
|
||||||
Status *LoginStatus
|
User *LoggedUser
|
||||||
ErrorMessage string
|
ErrorMessage string
|
||||||
Success bool
|
Success bool
|
||||||
Mail string
|
Mail string
|
||||||
|
@ -23,24 +23,24 @@ type ProfileTplData struct {
|
||||||
func handleProfile(w http.ResponseWriter, r *http.Request) {
|
func handleProfile(w http.ResponseWriter, r *http.Request) {
|
||||||
templateProfile := getTemplate("profile.html")
|
templateProfile := getTemplate("profile.html")
|
||||||
|
|
||||||
login := checkLogin(w, r)
|
user := RequireUserHtml(w, r)
|
||||||
if login == nil {
|
if user == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
data := &ProfileTplData{
|
data := &ProfileTplData{
|
||||||
Status: login,
|
User: user,
|
||||||
ErrorMessage: "",
|
ErrorMessage: "",
|
||||||
Success: false,
|
Success: false,
|
||||||
}
|
}
|
||||||
|
|
||||||
data.Mail = login.UserEntry.GetAttributeValue("mail")
|
data.Mail = user.Entry.GetAttributeValue("mail")
|
||||||
data.DisplayName = login.UserEntry.GetAttributeValue("displayname")
|
data.DisplayName = user.Entry.GetAttributeValue("displayname")
|
||||||
data.GivenName = login.UserEntry.GetAttributeValue("givenname")
|
data.GivenName = user.Entry.GetAttributeValue("givenname")
|
||||||
data.Surname = login.UserEntry.GetAttributeValue("sn")
|
data.Surname = user.Entry.GetAttributeValue("sn")
|
||||||
data.Visibility = login.UserEntry.GetAttributeValue(FIELD_NAME_DIRECTORY_VISIBILITY)
|
data.Visibility = user.Entry.GetAttributeValue(FIELD_NAME_DIRECTORY_VISIBILITY)
|
||||||
data.Description = login.UserEntry.GetAttributeValue("description")
|
data.Description = user.Entry.GetAttributeValue("description")
|
||||||
data.ProfilePicture = login.UserEntry.GetAttributeValue(FIELD_NAME_PROFILE_PICTURE)
|
data.ProfilePicture = user.Entry.GetAttributeValue(FIELD_NAME_PROFILE_PICTURE)
|
||||||
|
|
||||||
if r.Method == "POST" {
|
if r.Method == "POST" {
|
||||||
//5MB maximum size files
|
//5MB maximum size files
|
||||||
|
@ -56,7 +56,7 @@ func handleProfile(w http.ResponseWriter, r *http.Request) {
|
||||||
}
|
}
|
||||||
data.Visibility = visible
|
data.Visibility = visible
|
||||||
|
|
||||||
profilePicture, err := uploadProfilePicture(w, r, login)
|
profilePicture, err := uploadProfilePicture(w, r, user)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
data.ErrorMessage = err.Error()
|
data.ErrorMessage = err.Error()
|
||||||
}
|
}
|
||||||
|
@ -65,7 +65,7 @@ func handleProfile(w http.ResponseWriter, r *http.Request) {
|
||||||
data.ProfilePicture = profilePicture
|
data.ProfilePicture = profilePicture
|
||||||
}
|
}
|
||||||
|
|
||||||
modify_request := ldap.NewModifyRequest(login.Info.DN, nil)
|
modify_request := ldap.NewModifyRequest(user.Login.Info.DN(), nil)
|
||||||
modify_request.Replace("displayname", []string{data.DisplayName})
|
modify_request.Replace("displayname", []string{data.DisplayName})
|
||||||
modify_request.Replace("givenname", []string{data.GivenName})
|
modify_request.Replace("givenname", []string{data.GivenName})
|
||||||
modify_request.Replace("sn", []string{data.Surname})
|
modify_request.Replace("sn", []string{data.Surname})
|
||||||
|
@ -75,7 +75,7 @@ func handleProfile(w http.ResponseWriter, r *http.Request) {
|
||||||
modify_request.Replace(FIELD_NAME_PROFILE_PICTURE, []string{data.ProfilePicture})
|
modify_request.Replace(FIELD_NAME_PROFILE_PICTURE, []string{data.ProfilePicture})
|
||||||
}
|
}
|
||||||
|
|
||||||
err = login.conn.Modify(modify_request)
|
err = user.Login.conn.Modify(modify_request)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
data.ErrorMessage = err.Error()
|
data.ErrorMessage = err.Error()
|
||||||
} else {
|
} else {
|
||||||
|
@ -88,7 +88,7 @@ func handleProfile(w http.ResponseWriter, r *http.Request) {
|
||||||
}
|
}
|
||||||
|
|
||||||
type PasswdTplData struct {
|
type PasswdTplData struct {
|
||||||
Status *LoginStatus
|
User *LoggedUser
|
||||||
ErrorMessage string
|
ErrorMessage string
|
||||||
TooShortError bool
|
TooShortError bool
|
||||||
NoMatchError bool
|
NoMatchError bool
|
||||||
|
@ -98,13 +98,13 @@ type PasswdTplData struct {
|
||||||
func handlePasswd(w http.ResponseWriter, r *http.Request) {
|
func handlePasswd(w http.ResponseWriter, r *http.Request) {
|
||||||
templatePasswd := getTemplate("passwd.html")
|
templatePasswd := getTemplate("passwd.html")
|
||||||
|
|
||||||
login := checkLogin(w, r)
|
user := RequireUserHtml(w, r)
|
||||||
if login == nil {
|
if user == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
data := &PasswdTplData{
|
data := &PasswdTplData{
|
||||||
Status: login,
|
User: user,
|
||||||
ErrorMessage: "",
|
ErrorMessage: "",
|
||||||
Success: false,
|
Success: false,
|
||||||
}
|
}
|
||||||
|
@ -120,11 +120,11 @@ func handlePasswd(w http.ResponseWriter, r *http.Request) {
|
||||||
} else if password2 != password {
|
} else if password2 != password {
|
||||||
data.NoMatchError = true
|
data.NoMatchError = true
|
||||||
} else {
|
} else {
|
||||||
modify_request := ldap.NewModifyRequest(login.Info.DN, nil)
|
modify_request := ldap.NewModifyRequest(user.Login.Info.DN(), nil)
|
||||||
pw, err := SSHAEncode(password)
|
pw, err := SSHAEncode(password)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
modify_request.Replace("userpassword", []string{pw})
|
modify_request.Replace("userpassword", []string{pw})
|
||||||
err := login.conn.Modify(modify_request)
|
err := user.Login.conn.Modify(modify_request)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
data.ErrorMessage = err.Error()
|
data.ErrorMessage = err.Error()
|
||||||
} else {
|
} else {
|
||||||
|
|
79
quotas.go
Normal file
79
quotas.go
Normal file
|
@ -0,0 +1,79 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/go-ldap/ldap/v3"
|
||||||
|
garage "git.deuxfleurs.fr/garage-sdk/garage-admin-sdk-golang"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// --- Default Quota Values ---
|
||||||
|
QUOTA_WEBSITE_SIZE_DEFAULT = 1024 * 1024 * 50 // 50MB
|
||||||
|
QUOTA_WEBSITE_SIZE_BURSTED = 1024 * 1024 * 200 // 200MB
|
||||||
|
QUOTA_WEBSITE_OBJECTS = 10000 // 10k objects
|
||||||
|
QUOTA_WEBSITE_COUNT = 5 // 5 buckets
|
||||||
|
|
||||||
|
// --- Per-user overridable fields ---
|
||||||
|
FIELD_QUOTA_WEBSITE_SIZE_BURSTED = "quota_website_size_bursted"
|
||||||
|
FIELD_QUOTA_WEBSITE_COUNT = "quota_website_count"
|
||||||
|
)
|
||||||
|
|
||||||
|
type UserQuota struct {
|
||||||
|
WebsiteCount int64
|
||||||
|
WebsiteSizeDefault int64
|
||||||
|
WebsiteSizeBursted int64
|
||||||
|
WebsiteObjects int64
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewUserQuota() *UserQuota {
|
||||||
|
return &UserQuota {
|
||||||
|
WebsiteCount: QUOTA_WEBSITE_COUNT,
|
||||||
|
WebsiteSizeDefault: QUOTA_WEBSITE_SIZE_DEFAULT,
|
||||||
|
WebsiteSizeBursted: QUOTA_WEBSITE_SIZE_BURSTED,
|
||||||
|
WebsiteObjects: QUOTA_WEBSITE_OBJECTS,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrQuotaEmpty = fmt.Errorf("No quota is defined for this entry")
|
||||||
|
ErrQuotaInvalid = fmt.Errorf("The defined quota can't be parsed")
|
||||||
|
)
|
||||||
|
|
||||||
|
func entryToQuota(entry *ldap.Entry, field string) (int64, error) {
|
||||||
|
f := entry.GetAttributeValue(field)
|
||||||
|
if f == "" {
|
||||||
|
return -1, ErrQuotaEmpty
|
||||||
|
}
|
||||||
|
|
||||||
|
q, err := strconv.ParseInt(f, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return -1, errors.Join(ErrQuotaInvalid, err)
|
||||||
|
}
|
||||||
|
return q, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewUserQuotaFromEntry(entry *ldap.Entry) *UserQuota {
|
||||||
|
quotas := NewUserQuota()
|
||||||
|
|
||||||
|
if q, err := entryToQuota(entry, FIELD_QUOTA_WEBSITE_COUNT); err != nil {
|
||||||
|
quotas.WebsiteCount = q
|
||||||
|
}
|
||||||
|
|
||||||
|
if q, err := entryToQuota(entry, FIELD_QUOTA_WEBSITE_SIZE_BURSTED); err != nil {
|
||||||
|
quotas.WebsiteSizeBursted = q
|
||||||
|
}
|
||||||
|
|
||||||
|
return quotas
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *UserQuota) DefaultWebsiteQuota() *garage.UpdateBucketRequestQuotas {
|
||||||
|
qr := garage.NewUpdateBucketRequestQuotas()
|
||||||
|
|
||||||
|
qr.SetMaxSize(q.WebsiteSizeDefault)
|
||||||
|
qr.SetMaxObjects(q.WebsiteSizeBursted)
|
||||||
|
|
||||||
|
return qr
|
||||||
|
}
|
Loading…
Reference in a new issue