diff options
author | John Olheiser <john.olheiser@gmail.com> | 2020-09-10 10:30:07 -0500 |
---|---|---|
committer | GitHub <noreply@github.com> | 2020-09-10 16:30:07 +0100 |
commit | c3e8c9441ad0e90bb0567af0bf7e9444aa8f4ad5 (patch) | |
tree | 96c8e00c1ff4b09f616df5805af6a3db11196fda /modules | |
parent | 4c42fce40175b735a689289bf61ca65a8e2266b2 (diff) | |
download | gitea-c3e8c9441ad0e90bb0567af0bf7e9444aa8f4ad5.tar.gz gitea-c3e8c9441ad0e90bb0567af0bf7e9444aa8f4ad5.zip |
Add check for LDAP group membership (#10869)
This is a port of gogs/gogs#4398
The only changes made by myself are:
Add locales
Add some JS to the UI
Otherwise all code credit goes to @aboron
Resolves #10829
Signed-off-by: jolheiser <john.olheiser@gmail.com>
Co-authored-by: zeripath <art27@cantab.net>
Diffstat (limited to 'modules')
-rw-r--r-- | modules/auth/auth_form.go | 5 | ||||
-rw-r--r-- | modules/auth/ldap/README.md | 18 | ||||
-rw-r--r-- | modules/auth/ldap/ldap.go | 80 |
3 files changed, 101 insertions, 2 deletions
diff --git a/modules/auth/auth_form.go b/modules/auth/auth_form.go index 7fc62607e5..1d02c7acf3 100644 --- a/modules/auth/auth_form.go +++ b/modules/auth/auth_form.go @@ -30,6 +30,11 @@ type AuthenticationForm struct { SearchPageSize int Filter string AdminFilter string + GroupsEnabled bool + GroupDN string + GroupFilter string + GroupMemberUID string + UserUID string RestrictedFilter string AllowDeactivateAll bool IsActive bool diff --git a/modules/auth/ldap/README.md b/modules/auth/ldap/README.md index 4f7961da6b..76841f44ae 100644 --- a/modules/auth/ldap/README.md +++ b/modules/auth/ldap/README.md @@ -103,3 +103,21 @@ share the following fields: matching parameter will be substituted with the user's username. * Example: (&(objectClass=posixAccount)(cn=%s)) * Example: (&(objectClass=posixAccount)(uid=%s)) + +**Verify group membership in LDAP** uses the following fields: + +* Group Search Base (optional) + * The LDAP DN used for groups. + * Example: ou=group,dc=mydomain,dc=com + +* Group Name Filter (optional) + * An LDAP filter declaring how to find valid groups in the above DN. + * Example: (|(cn=gitea_users)(cn=admins)) + +* User Attribute in Group (optional) + * Which user LDAP attribute is listed in the group. + * Example: uid + +* Group Attribute for User (optional) + * Which group LDAP attribute contains an array above user attribute names. + * Example: memberUid diff --git a/modules/auth/ldap/ldap.go b/modules/auth/ldap/ldap.go index 66676f2829..7649639d36 100644 --- a/modules/auth/ldap/ldap.go +++ b/modules/auth/ldap/ldap.go @@ -1,4 +1,5 @@ // Copyright 2014 The Gogs Authors. All rights reserved. +// Copyright 2020 The Gitea Authors. All rights reserved. // Use of this source code is governed by a MIT-style // license that can be found in the LICENSE file. @@ -13,7 +14,7 @@ import ( "code.gitea.io/gitea/modules/log" - ldap "gopkg.in/ldap.v3" + "gopkg.in/ldap.v3" ) // SecurityProtocol protocol type @@ -49,6 +50,11 @@ type Source struct { RestrictedFilter string // Query filter to check if user is restricted Enabled bool // if this source is disabled AllowDeactivateAll bool // Allow an empty search response to deactivate all users from this source + GroupsEnabled bool // if the group checking is enabled + GroupDN string // Group Search Base + GroupFilter string // Group Name Filter + GroupMemberUID string // Group Attribute containing array of UserUID + UserUID string // User Attribute listed in Group } // SearchResult : user data @@ -84,6 +90,28 @@ func (ls *Source) sanitizedUserDN(username string) (string, bool) { return fmt.Sprintf(ls.UserDN, username), true } +func (ls *Source) sanitizedGroupFilter(group string) (string, bool) { + // See http://tools.ietf.org/search/rfc4515 + badCharacters := "\x00*\\" + if strings.ContainsAny(group, badCharacters) { + log.Trace("Group filter invalid query characters: %s", group) + return "", false + } + + return group, true +} + +func (ls *Source) sanitizedGroupDN(groupDn string) (string, bool) { + // See http://tools.ietf.org/search/rfc4514: "special characters" + badCharacters := "\x00()*\\'\"#+;<>" + if strings.ContainsAny(groupDn, badCharacters) || strings.HasPrefix(groupDn, " ") || strings.HasSuffix(groupDn, " ") { + log.Trace("Group DN contains invalid query characters: %s", groupDn) + return "", false + } + + return groupDn, true +} + func (ls *Source) findUserDN(l *ldap.Conn, name string) (string, bool) { log.Trace("Search for LDAP user: %s", name) @@ -279,11 +307,14 @@ func (ls *Source) SearchEntry(name, passwd string, directBind bool) *SearchResul var isAttributeSSHPublicKeySet = len(strings.TrimSpace(ls.AttributeSSHPublicKey)) > 0 attribs := []string{ls.AttributeUsername, ls.AttributeName, ls.AttributeSurname, ls.AttributeMail} + if len(strings.TrimSpace(ls.UserUID)) > 0 { + attribs = append(attribs, ls.UserUID) + } if isAttributeSSHPublicKeySet { attribs = append(attribs, ls.AttributeSSHPublicKey) } - log.Trace("Fetching attributes '%v', '%v', '%v', '%v', '%v' with filter %s and base %s", ls.AttributeUsername, ls.AttributeName, ls.AttributeSurname, ls.AttributeMail, ls.AttributeSSHPublicKey, userFilter, userDN) + log.Trace("Fetching attributes '%v', '%v', '%v', '%v', '%v', '%v' with filter '%s' and base '%s'", ls.AttributeUsername, ls.AttributeName, ls.AttributeSurname, ls.AttributeMail, ls.AttributeSSHPublicKey, ls.UserUID, userFilter, userDN) search := ldap.NewSearchRequest( userDN, ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false, userFilter, attribs, nil) @@ -308,6 +339,51 @@ func (ls *Source) SearchEntry(name, passwd string, directBind bool) *SearchResul firstname := sr.Entries[0].GetAttributeValue(ls.AttributeName) surname := sr.Entries[0].GetAttributeValue(ls.AttributeSurname) mail := sr.Entries[0].GetAttributeValue(ls.AttributeMail) + uid := sr.Entries[0].GetAttributeValue(ls.UserUID) + + // Check group membership + if ls.GroupsEnabled { + groupFilter, ok := ls.sanitizedGroupFilter(ls.GroupFilter) + if !ok { + return nil + } + groupDN, ok := ls.sanitizedGroupDN(ls.GroupDN) + if !ok { + return nil + } + + log.Trace("Fetching groups '%v' with filter '%s' and base '%s'", ls.GroupMemberUID, groupFilter, groupDN) + groupSearch := ldap.NewSearchRequest( + groupDN, ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false, groupFilter, + []string{ls.GroupMemberUID}, + nil) + + srg, err := l.Search(groupSearch) + if err != nil { + log.Error("LDAP group search failed: %v", err) + return nil + } else if len(srg.Entries) < 1 { + log.Error("LDAP group search failed: 0 entries") + return nil + } + + isMember := false + Entries: + for _, group := range srg.Entries { + for _, member := range group.GetAttributeValues(ls.GroupMemberUID) { + if (ls.UserUID == "dn" && member == sr.Entries[0].DN) || member == uid { + isMember = true + break Entries + } + } + } + + if !isMember { + log.Error("LDAP group membership test failed") + return nil + } + } + if isAttributeSSHPublicKeySet { sshPublicKey = sr.Entries[0].GetAttributeValues(ls.AttributeSSHPublicKey) } |