lib/api: Add LDAP search filters (fixes #5376) (#6488)

This adds the functionality to run a user search with a filter for LDAP
authentication. The search is done after successful bind, as the binding
user. The typical use case is to limit authentication to users who are
member of a group or under a certain OU. For example, to only match
users in the "Syncthing" group in otherwise default Active Directory
set up for example.com:

    <searchBaseDN>CN=Users,DC=example,DC=com</searchBaseDN>
    <searchFilter>(&amp;(sAMAccountName=%s)(memberOf=CN=Syncthing,CN=Users,DC=example,DC=com))</searchFilter>

The search filter is an "and" of two criteria (with the ampersand being
XML quoted),

- "(sAMAccountName=%s)" matches the user logging in
- "(memberOf=CN=Syncthing,CN=Users,DC=example,DC=com)" matches members
  of the group in question.

Authentication will only proceed if the search filter matches precisely
one user.
This commit is contained in:
Jakob Borg
2020-04-04 11:33:43 +02:00
committed by GitHub
parent f69c0b550c
commit 48f9d323fa
2 changed files with 31 additions and 0 deletions
+29
View File
@@ -166,6 +166,35 @@ func authLDAP(username string, password string, cfg config.LDAPConfiguration) bo
return false
}
if cfg.SearchFilter == "" && cfg.SearchBaseDN == "" {
// We're done here.
return true
}
if cfg.SearchFilter == "" || cfg.SearchBaseDN == "" {
l.Warnln("LDAP configuration: both searchFilter and searchBaseDN must be set, or neither.")
return false
}
// If a search filter and search base is set we do an LDAP search for
// the user. If this matches precisely one user then we are good to go.
// The search filter uses the same %s interpolation as the bind DN.
searchString := fmt.Sprintf(cfg.SearchFilter, username)
const sizeLimit = 2 // we search for up to two users -- we only want to match one, so getting any number >1 is a failure.
const timeLimit = 60 // Search for up to a minute...
searchReq := ldap.NewSearchRequest(cfg.SearchBaseDN, ldap.ScopeWholeSubtree, ldap.DerefFindingBaseObj, sizeLimit, timeLimit, false, searchString, nil, nil)
res, err := connection.Search(searchReq)
if err != nil {
l.Warnln("LDAP Search:", err)
return false
}
if len(res.Entries) != 1 {
l.Infof("Wrong number of LDAP search results, %d != 1", len(res.Entries))
return false
}
return true
}