+113
-35
@@ -107,13 +107,24 @@ func (a *LDAPAuthenticator) AuthenticateAndFetchGroups(ctx context.Context, user
|
||||
Username: username,
|
||||
UserDN: username,
|
||||
}
|
||||
if whoami, err := conn.WhoAmI(nil); err != nil {
|
||||
identity.Diagnostics = append(identity.Diagnostics, fmt.Sprintf("whoami_failed:%v", err))
|
||||
} else if boundDN := strings.TrimSpace(strings.TrimPrefix(strings.TrimSpace(whoami.AuthzID), "dn:")); boundDN != "" {
|
||||
identity.UserDN = boundDN
|
||||
identity.Diagnostics = append(identity.Diagnostics, "whoami_dn_resolved")
|
||||
} else {
|
||||
identity.Diagnostics = append(identity.Diagnostics, "whoami_dn_empty")
|
||||
}
|
||||
|
||||
entry, err := a.lookupUserEntry(conn, username)
|
||||
entry, lookupStrategy, err := a.lookupUserEntry(conn, username, identity.UserDN)
|
||||
if err != nil {
|
||||
return LDAPIdentity{}, err
|
||||
}
|
||||
if entry != nil {
|
||||
identity.Diagnostics = append(identity.Diagnostics, "user_entry_found")
|
||||
if lookupStrategy == "" {
|
||||
lookupStrategy = "unknown"
|
||||
}
|
||||
identity.Diagnostics = append(identity.Diagnostics, "user_entry_found:"+lookupStrategy)
|
||||
if strings.TrimSpace(entry.DN) != "" {
|
||||
identity.UserDN = entry.DN
|
||||
}
|
||||
@@ -140,6 +151,7 @@ func (a *LDAPAuthenticator) AuthenticateAndFetchGroups(ctx context.Context, user
|
||||
}
|
||||
}
|
||||
|
||||
groupFilter := buildGroupMembershipFilter(identity.UserDN, principalCandidates(username))
|
||||
groupEntries, err := conn.Search(ldap.NewSearchRequest(
|
||||
a.baseDN,
|
||||
ldap.ScopeWholeSubtree,
|
||||
@@ -147,11 +159,7 @@ func (a *LDAPAuthenticator) AuthenticateAndFetchGroups(ctx context.Context, user
|
||||
0,
|
||||
0,
|
||||
false,
|
||||
fmt.Sprintf("(|(member=%s)(uniqueMember=%s)(memberUid=%s))",
|
||||
ldap.EscapeFilter(identity.UserDN),
|
||||
ldap.EscapeFilter(identity.UserDN),
|
||||
ldap.EscapeFilter(username),
|
||||
),
|
||||
groupFilter,
|
||||
[]string{"dn"},
|
||||
nil,
|
||||
))
|
||||
@@ -272,10 +280,24 @@ func (a *LDAPAuthenticator) buildTLSConfig() (*tls.Config, error) {
|
||||
return tlsConfig, nil
|
||||
}
|
||||
|
||||
func (a *LDAPAuthenticator) lookupUserEntry(conn *ldap.Conn, username string) (*ldap.Entry, error) {
|
||||
func (a *LDAPAuthenticator) lookupUserEntry(conn *ldap.Conn, username string, userDNHint string) (*ldap.Entry, string, error) {
|
||||
dnCandidates := compactTrimmedStrings([]string{userDNHint})
|
||||
if looksLikeDN(username) {
|
||||
dnCandidates = append(dnCandidates, strings.TrimSpace(username))
|
||||
}
|
||||
seenDN := make(map[string]struct{}, len(dnCandidates))
|
||||
for _, dn := range dnCandidates {
|
||||
key := normalizeDN(dn)
|
||||
if key == "" {
|
||||
continue
|
||||
}
|
||||
if _, ok := seenDN[key]; ok {
|
||||
continue
|
||||
}
|
||||
seenDN[key] = struct{}{}
|
||||
|
||||
searchRes, err := conn.Search(ldap.NewSearchRequest(
|
||||
username,
|
||||
dn,
|
||||
ldap.ScopeBaseObject,
|
||||
ldap.NeverDerefAliases,
|
||||
1,
|
||||
@@ -286,37 +308,41 @@ func (a *LDAPAuthenticator) lookupUserEntry(conn *ldap.Conn, username string) (*
|
||||
nil,
|
||||
))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w: unable to load user entry: %v", ErrLDAPOperationFailed, err)
|
||||
if ldap.IsErrorWithCode(err, ldap.LDAPResultNoSuchObject) {
|
||||
continue
|
||||
}
|
||||
return nil, "", fmt.Errorf("%w: unable to load user entry by dn: %v", ErrLDAPOperationFailed, err)
|
||||
}
|
||||
if len(searchRes.Entries) == 0 {
|
||||
return nil, nil
|
||||
if len(searchRes.Entries) > 0 {
|
||||
return searchRes.Entries[0], "dn", nil
|
||||
}
|
||||
return searchRes.Entries[0], nil
|
||||
}
|
||||
|
||||
searchRes, err := conn.Search(ldap.NewSearchRequest(
|
||||
a.baseDN,
|
||||
ldap.ScopeWholeSubtree,
|
||||
ldap.NeverDerefAliases,
|
||||
2,
|
||||
0,
|
||||
false,
|
||||
fmt.Sprintf("(|(uid=%s)(cn=%s)(sAMAccountName=%s)(userPrincipalName=%s))",
|
||||
ldap.EscapeFilter(username),
|
||||
ldap.EscapeFilter(username),
|
||||
ldap.EscapeFilter(username),
|
||||
ldap.EscapeFilter(username),
|
||||
),
|
||||
[]string{"uid", "sAMAccountName", "userPrincipalName", "cn", "memberOf"},
|
||||
nil,
|
||||
))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w: user lookup failed: %v", ErrLDAPOperationFailed, err)
|
||||
for _, principal := range principalCandidates(username) {
|
||||
searchRes, err := conn.Search(ldap.NewSearchRequest(
|
||||
a.baseDN,
|
||||
ldap.ScopeWholeSubtree,
|
||||
ldap.NeverDerefAliases,
|
||||
2,
|
||||
0,
|
||||
false,
|
||||
fmt.Sprintf("(|(uid=%s)(cn=%s)(sAMAccountName=%s)(userPrincipalName=%s))",
|
||||
ldap.EscapeFilter(principal),
|
||||
ldap.EscapeFilter(principal),
|
||||
ldap.EscapeFilter(principal),
|
||||
ldap.EscapeFilter(principal),
|
||||
),
|
||||
[]string{"uid", "sAMAccountName", "userPrincipalName", "cn", "memberOf"},
|
||||
nil,
|
||||
))
|
||||
if err != nil {
|
||||
return nil, "", fmt.Errorf("%w: user lookup failed: %v", ErrLDAPOperationFailed, err)
|
||||
}
|
||||
if len(searchRes.Entries) > 0 {
|
||||
return searchRes.Entries[0], "principal", nil
|
||||
}
|
||||
}
|
||||
if len(searchRes.Entries) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
return searchRes.Entries[0], nil
|
||||
return nil, "", nil
|
||||
}
|
||||
|
||||
func normalizeDN(value string) string {
|
||||
@@ -352,6 +378,58 @@ func looksLikeDN(value string) bool {
|
||||
return strings.Contains(value, "=") && strings.Contains(value, ",")
|
||||
}
|
||||
|
||||
func principalCandidates(username string) []string {
|
||||
username = strings.TrimSpace(username)
|
||||
if username == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
seen := make(map[string]struct{}, 4)
|
||||
candidates := make([]string, 0, 4)
|
||||
add := func(value string) {
|
||||
value = strings.TrimSpace(value)
|
||||
if value == "" {
|
||||
return
|
||||
}
|
||||
key := strings.ToLower(value)
|
||||
if _, ok := seen[key]; ok {
|
||||
return
|
||||
}
|
||||
seen[key] = struct{}{}
|
||||
candidates = append(candidates, value)
|
||||
}
|
||||
|
||||
add(username)
|
||||
if idx := strings.LastIndex(username, `\`); idx >= 0 && idx < len(username)-1 {
|
||||
add(username[idx+1:])
|
||||
}
|
||||
if idx := strings.Index(username, "@"); idx > 0 {
|
||||
add(username[:idx])
|
||||
}
|
||||
|
||||
return candidates
|
||||
}
|
||||
|
||||
func buildGroupMembershipFilter(userDN string, principals []string) string {
|
||||
clauses := make([]string, 0, 2+len(principals))
|
||||
userDN = strings.TrimSpace(userDN)
|
||||
if userDN != "" {
|
||||
escapedDN := ldap.EscapeFilter(userDN)
|
||||
clauses = append(clauses, "(member="+escapedDN+")", "(uniqueMember="+escapedDN+")")
|
||||
}
|
||||
for _, principal := range principals {
|
||||
principal = strings.TrimSpace(principal)
|
||||
if principal == "" {
|
||||
continue
|
||||
}
|
||||
clauses = append(clauses, "(memberUid="+ldap.EscapeFilter(principal)+")")
|
||||
}
|
||||
if len(clauses) == 0 {
|
||||
return "(objectClass=group)"
|
||||
}
|
||||
return "(|" + strings.Join(clauses, "") + ")"
|
||||
}
|
||||
|
||||
func ctxErr(ctx context.Context) error {
|
||||
if ctx == nil {
|
||||
return nil
|
||||
|
||||
Reference in New Issue
Block a user