diff --git a/pkg/auth/ldap/README.md b/pkg/auth/ldap/README.md index 3a3e02043..b8c95b3b9 100644 --- a/pkg/auth/ldap/README.md +++ b/pkg/auth/ldap/README.md @@ -99,3 +99,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=gogs_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/pkg/auth/ldap/ldap.go b/pkg/auth/ldap/ldap.go index 78cd66a45..25fddeb78 100644 --- a/pkg/auth/ldap/ldap.go +++ b/pkg/auth/ldap/ldap.go @@ -42,6 +42,11 @@ type Source struct { AttributesInBind bool // fetch attributes in bind context (not user) Filter string // Query filter to validate entry AdminFilter string // Query filter to check if user is admin + 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 Enabled bool // if this source is disabled } @@ -67,6 +72,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) if ls.BindDN != "" && ls.BindPassword != "" { @@ -194,15 +221,15 @@ func (ls *Source) SearchEntry(name, passwd string, directBind bool) (string, str return "", "", "", "", false, false } - log.Trace("Fetching attributes '%v', '%v', '%v', '%v' with filter '%s' and base '%s'", ls.AttributeUsername, ls.AttributeName, ls.AttributeSurname, ls.AttributeMail, userFilter, userDN) + log.Trace("Fetching attributes '%v', '%v', '%v', '%v', '%v' with filter '%s' and base '%s'", ls.AttributeUsername, ls.AttributeName, ls.AttributeSurname, ls.AttributeMail, ls.UserUID, userFilter, userDN) search := ldap.NewSearchRequest( userDN, ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false, userFilter, - []string{ls.AttributeUsername, ls.AttributeName, ls.AttributeSurname, ls.AttributeMail}, + []string{ls.AttributeUsername, ls.AttributeName, ls.AttributeSurname, ls.AttributeMail, ls.UserUID}, nil) sr, err := l.Search(search) if err != nil { - log.Error(4, "LDAP search failed: %v", err) + log.Error(4, "LDAP user search failed: %v", err) return "", "", "", "", false, false } else if len(sr.Entries) < 1 { if directBind { @@ -218,6 +245,48 @@ func (ls *Source) SearchEntry(name, passwd string, directBind bool) (string, str 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 "", "", "", "", false, false + } + groupDN, ok := ls.sanitizedGroupDN(ls.GroupDN) + if !ok { + return "", "", "", "", false, false + } + + 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(4, "LDAP group search failed: %v", err) + return "", "", "", "", false, false + } else if len(sr.Entries) < 1 { + log.Error(4, "LDAP group search failed: 0 entries") + return "", "", "", "", false, false + } + + isMember := false + for _,group := range srg.Entries { + for _,member := range group.GetAttributeValues(ls.GroupMemberUid) { + if member == uid { + isMember = true + } + } + } + + if !isMember { + log.Error(4, "LDAP group membership test failed") + return "", "", "", "", false, false + } + } isAdmin := false if len(ls.AdminFilter) > 0 { diff --git a/pkg/form/auth.go b/pkg/form/auth.go index 10dccd001..e045a2dff 100644 --- a/pkg/form/auth.go +++ b/pkg/form/auth.go @@ -26,6 +26,11 @@ type Authentication struct { AttributesInBind bool Filter string AdminFilter string + GroupsEnabled bool + GroupDN string + GroupFilter string + GroupMemberUid string + UserUID string IsActive bool SMTPAuth string SMTPHost string diff --git a/routers/admin/auths.go b/routers/admin/auths.go index 532adb4f0..e42bb5e23 100644 --- a/routers/admin/auths.go +++ b/routers/admin/auths.go @@ -93,6 +93,11 @@ func parseLDAPConfig(f form.Authentication) *models.LDAPConfig { AttributeMail: f.AttributeMail, AttributesInBind: f.AttributesInBind, Filter: f.Filter, + GroupsEnabled: f.GroupsEnabled, + GroupDN: f.GroupDN, + GroupFilter: f.GroupFilter, + GroupMemberUid: f.GroupMemberUid, + UserUID: f.UserUID, AdminFilter: f.AdminFilter, Enabled: true, }, diff --git a/templates/admin/auth/edit.tmpl b/templates/admin/auth/edit.tmpl index b06cee8b8..0026a4a98 100644 --- a/templates/admin/auth/edit.tmpl +++ b/templates/admin/auth/edit.tmpl @@ -92,6 +92,28 @@ +