package api import ( "fmt" "net/http" "archivmail/internal/audit" "archivmail/internal/ldapauth" ldapcfg "archivmail/internal/ldapconfig" ) // syncResult is the JSON response returned by LDAP sync endpoints. type syncResult struct { Synced int `json:"synced"` Errors []string `json:"errors"` } // handleSyncTenantLDAP imports all LDAP users into the tenant (domain_admin). func (s *Server) handleSyncTenantLDAP(w http.ResponseWriter, r *http.Request) { if s.tenantLdapStore == nil { writeError(w, http.StatusServiceUnavailable, "tenant ldap store not available") return } sess := sessionFromCtx(r.Context()) if sess.TenantID == nil { writeError(w, http.StatusBadRequest, "no tenant context") return } res := s.doSyncTenantLDAP(r, *sess.TenantID) s.audlog.Log(audit.Entry{ EventType: "tenant_ldap_sync", Username: sess.Username, IPAddress: s.remoteIP(r), Success: len(res.Errors) == 0, Detail: fmt.Sprintf("%d Benutzer synchronisiert", res.Synced), }) writeJSON(w, http.StatusOK, res) } // handleAdminSyncTenantLDAP imports all LDAP users into any tenant (superadmin). func (s *Server) handleAdminSyncTenantLDAP(w http.ResponseWriter, r *http.Request) { if s.tenantLdapStore == nil { writeError(w, http.StatusServiceUnavailable, "tenant ldap store not available") return } id, err := parseTenantID(r) if err != nil { writeError(w, http.StatusBadRequest, "invalid tenant id") return } res := s.doSyncTenantLDAP(r, id) sess := sessionFromCtx(r.Context()) s.audlog.Log(audit.Entry{ EventType: "tenant_ldap_sync", Username: sess.Username, IPAddress: s.remoteIP(r), Success: len(res.Errors) == 0, Detail: fmt.Sprintf("%d Benutzer synchronisiert (tenant %d)", res.Synced, id), }) writeJSON(w, http.StatusOK, res) } // doSyncTenantLDAP is the shared sync logic: fetch all LDAP users and upsert // them into the local userstore with source='ldap'. func (s *Server) doSyncTenantLDAP(r *http.Request, tenantID int64) syncResult { saved, err := s.tenantLdapStore.GetWithPassword(r.Context(), tenantID) if err != nil || saved == nil { return syncResult{Errors: []string{"keine LDAP-Konfiguration vorhanden"}} } cfg := ldapauth.Config{ URL: saved.URL, BindDN: saved.BindDN, BindPassword: saved.BindPassword, BaseDN: saved.BaseDN, UserFilter: saved.UserFilter, TLS: saved.TLS, TLSSkipVerify: saved.TLSSkipVerify, } ldapUsers, err := ldapauth.FetchUsers(cfg) if err != nil { return syncResult{Errors: []string{err.Error()}} } defaultRole := saved.DefaultRole if defaultRole == "" { defaultRole = "user" } var res syncResult res.Errors = []string{} for _, u := range ldapUsers { mail := u.Mail if mail == "" { mail = u.UID + "@ldap.local" } if _, uErr := s.users.UpsertLDAPUser(u.UID, mail, defaultRole, &tenantID); uErr != nil { res.Errors = append(res.Errors, fmt.Sprintf("%s: %s", u.UID, uErr.Error())) } else { res.Synced++ } } return res } // buildTenantTestConfig constructs an ldapauth.Config for testing, either from // the saved tenant config or from the provided request body. func (s *Server) buildTenantTestConfig(r *http.Request, useSaved bool, tenantID int64, provided ldapcfg.TenantLDAPConfig) *ldapauth.Config { if useSaved { saved, err := s.tenantLdapStore.GetWithPassword(r.Context(), tenantID) if err != nil || saved == nil { return nil } return &ldapauth.Config{ URL: saved.URL, BindDN: saved.BindDN, BindPassword: saved.BindPassword, BaseDN: saved.BaseDN, UserFilter: saved.UserFilter, TLS: saved.TLS, TLSSkipVerify: saved.TLSSkipVerify, } } return &ldapauth.Config{ URL: provided.URL, BindDN: provided.BindDN, BindPassword: provided.BindPassword, BaseDN: provided.BaseDN, UserFilter: provided.UserFilter, TLS: provided.TLS, TLSSkipVerify: provided.TLSSkipVerify, } }