LDAP admin GUI + group filter
- LDAP settings configurable from Admin panel (no restart needed) - Required group filter: only users in specified group can login - Supports both memberOf attribute and groupOfNames search - Settings stored in DB (settings table), env vars as fallback - SLDAP supported via ldaps:// URL - Bind password masked in UI
This commit is contained in:
@@ -95,7 +95,7 @@ func (s *Server) handleLogin(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
// Try LDAP if local auth failed
|
||||
if !localAuthOK {
|
||||
ldapCfg := auth.LDAPConfigFromEnv()
|
||||
ldapCfg := auth.LDAPConfigFromDB(s.db)
|
||||
if ldapCfg != nil {
|
||||
email, displayName, ldapErr := auth.LDAPAuth(ldapCfg, req.Email, req.Password)
|
||||
if ldapErr == nil {
|
||||
|
||||
@@ -73,6 +73,8 @@ func NewRouter(db *sql.DB, dataDir, secret string) http.Handler {
|
||||
mux.HandleFunc("POST /api/build/cancel", s.requireAuth(s.handleBuildCancel))
|
||||
|
||||
// Daemon endpoints (admin only)
|
||||
mux.HandleFunc("POST /api/admin/settings/get", s.requireAdmin(s.handleGetSettings))
|
||||
mux.HandleFunc("POST /api/admin/settings/save", s.requireAdmin(s.handleSaveSettings))
|
||||
mux.HandleFunc("POST /api/daemon/poll", s.requireAdmin(s.handleDaemonPoll))
|
||||
mux.HandleFunc("POST /api/daemon/heartbeat", s.requireAdmin(s.handleDaemonHeartbeat))
|
||||
mux.HandleFunc("POST /api/daemon/report", s.requireAdmin(s.handleDaemonReport))
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func (s *Server) handleGetSettings(w http.ResponseWriter, r *http.Request) {
|
||||
keys := []string{
|
||||
"ldap_url", "ldap_bind_dn", "ldap_bind_pass", "ldap_base_dn",
|
||||
"ldap_user_filter", "ldap_group_filter", "ldap_skip_tls",
|
||||
}
|
||||
result := make(map[string]string)
|
||||
for _, k := range keys {
|
||||
var val string
|
||||
s.db.QueryRow("SELECT value FROM settings WHERE key = ?", k).Scan(&val)
|
||||
// Don't expose bind password
|
||||
if k == "ldap_bind_pass" && val != "" {
|
||||
result[k] = "••••••••"
|
||||
} else {
|
||||
result[k] = val
|
||||
}
|
||||
}
|
||||
writeJSON(w, 200, result)
|
||||
}
|
||||
|
||||
func (s *Server) handleSaveSettings(w http.ResponseWriter, r *http.Request) {
|
||||
var req map[string]string
|
||||
if err := decodeBody(r, &req); err != nil {
|
||||
writeJSON(w, 400, map[string]string{"error": "invalid request"})
|
||||
return
|
||||
}
|
||||
|
||||
allowed := map[string]bool{
|
||||
"ldap_url": true, "ldap_bind_dn": true, "ldap_bind_pass": true,
|
||||
"ldap_base_dn": true, "ldap_user_filter": true, "ldap_group_filter": true,
|
||||
"ldap_skip_tls": true,
|
||||
}
|
||||
|
||||
for k, v := range req {
|
||||
if !allowed[k] {
|
||||
continue
|
||||
}
|
||||
// Don't overwrite password with masked value
|
||||
if k == "ldap_bind_pass" && v == "••••••••" {
|
||||
continue
|
||||
}
|
||||
s.db.Exec(
|
||||
`INSERT INTO settings (key, value) VALUES (?, ?) ON CONFLICT(key) DO UPDATE SET value = ?`,
|
||||
k, v, v,
|
||||
)
|
||||
}
|
||||
|
||||
s.audit(getUserID(r), "save_settings", "ldap")
|
||||
writeJSON(w, 200, map[string]string{"status": "saved"})
|
||||
}
|
||||
Reference in New Issue
Block a user