feat(ldap): ad/openldap dual compat and config gating
- multi-round user filters (config filter first, then objectClass=inetOrgPerson, then AD sAMAccountName/userPrincipalName fallback) applied to login search, user listing and batch import; previously pure-AD directories found no users - attribute fallback chains (uid/sAMAccountName/userPrincipalName, cn/displayName, telephoneNumber/mobile) via ldapEntryAttr - import errors explain likely cause instead of empty result - AES-encrypted admin password kept (aiops stores plaintext) - frontend gating per aiops pattern: LDAP account management panel hidden with hint until a config is actually saved (was gated on unsaved form values); batch-import modal hides to/from-LDAP tabs when unconfigured, local CSV always available
This commit is contained in:
1 parent
2c96902fbd
commit
13fb3aceb3
6 files changed
+487
-266
No files matched your search
@@ -227,35 +227,11 @@ func tryLDAPAuth(username, password string) (*ldap.Conn, *LDAPAuthInfo, error) {
|
||||
return nil, nil, fmt.Errorf("LDAP admin bind failed: %w", err)
|
||||
}
|
||||
|
||||
// 4. Build search filter
|
||||
userFilter := ""
|
||||
baseFilter := "(objectClass=inetOrgPerson)"
|
||||
if cfg.SearchFilter != nil && *cfg.SearchFilter != "" {
|
||||
sf := *cfg.SearchFilter
|
||||
if strings.Contains(sf, "%s") {
|
||||
userFilter = strings.ReplaceAll(sf, "%s", ldap.EscapeFilter(username))
|
||||
} else {
|
||||
baseFilter = sf
|
||||
userFilter = fmt.Sprintf("(&%s(uid=%s))", baseFilter, ldap.EscapeFilter(username))
|
||||
}
|
||||
} else {
|
||||
userFilter = fmt.Sprintf("(&%s(uid=%s))", baseFilter, ldap.EscapeFilter(username))
|
||||
// 4-5. 多轮过滤器搜索用户(OpenLDAP + AD 兜底,借鉴 aiops)
|
||||
entry, err := ldapSearchFirstUser(conn, &cfg, username)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
// 5. Search user
|
||||
searchReq := ldap.NewSearchRequest(
|
||||
cfg.BaseDN,
|
||||
ldap.ScopeWholeSubtree, ldap.NeverDerefAliases,
|
||||
1, 10, false,
|
||||
userFilter,
|
||||
[]string{"uid", "cn", "mail", "telephoneNumber"},
|
||||
nil,
|
||||
)
|
||||
sr, err := conn.Search(searchReq)
|
||||
if err != nil || len(sr.Entries) == 0 {
|
||||
return nil, nil, fmt.Errorf("user not found in LDAP")
|
||||
}
|
||||
entry := sr.Entries[0]
|
||||
userDN := entry.DN
|
||||
|
||||
// 6. Verify password by binding as user
|
||||
@@ -273,22 +249,15 @@ func tryLDAPAuth(username, password string) (*ldap.Conn, *LDAPAuthInfo, error) {
|
||||
json.Unmarshal([]byte(*cfg.UserMapping), &userMapping)
|
||||
}
|
||||
|
||||
getAttr := func(key string) string {
|
||||
if v := entry.GetAttributeValue(key); v != "" {
|
||||
return v
|
||||
}
|
||||
return ""
|
||||
}
|
||||
// 7. 属性回退链提取身份(OpenLDAP 与 AD 字段差异由 ldapEntryAttr 抹平)
|
||||
identUser, identName, identEmail := ldapIdentityFromEntry(entry, username)
|
||||
|
||||
info := &LDAPAuthInfo{
|
||||
Username: getAttr(userMapping["username"]),
|
||||
DisplayName: getAttr(userMapping["display_name"]),
|
||||
Username: identUser,
|
||||
DisplayName: identName,
|
||||
}
|
||||
if email := getAttr(userMapping["email"]); email != "" {
|
||||
info.Email = &email
|
||||
}
|
||||
if info.Username == "" {
|
||||
info.Username = username
|
||||
if identEmail != "" {
|
||||
info.Email = &identEmail
|
||||
}
|
||||
|
||||
return nil, info, nil // conn already closed by defer
|
||||
|
||||
Reference in new issue
Block a user