Ver Fonte

ldap: add check for group membership (#4398)

* Add standard LDAP group membership checking.

* Fix formatting, typo, grammer, and syntax errors

* Debugging done.
Gave up on locale file edits.
aboron há 7 anos atrás
pai
commit
dbb7e5464b

+ 18 - 0
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

+ 72 - 3
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 {

+ 5 - 0
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

+ 5 - 0
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,
 		},

+ 22 - 0
templates/admin/auth/edit.tmpl

@@ -92,6 +92,28 @@
 								<label for="attribute_mail">{{.i18n.Tr "admin.auths.attribute_mail"}}</label>
 								<input id="attribute_mail" name="attribute_mail" value="{{$cfg.AttributeMail}}" placeholder="e.g. mail" required>
 							</div>
+							<div class="inline field">
+								<div class="ui checkbox">
+									<label><strong>Verify group membership in LDAP</strong></label>
+									<input name="groups_enabled" type="checkbox" {{if $cfg.GroupsEnabled}}checked{{end}}>
+								</div>
+							</div>
+							<div class="field">
+								<label for="group_dn">Group search Base DN</label>
+								<input id="group_dn" name="group_dn" value="{{$cfg.GroupDN}}" placeholder="e.g. ou=group,dc=mydomain,dc=com">
+							</div>
+							<div class="field">
+								<label for="group_filter">Valid groups filter</label>
+								<input id="group_filter" name="group_filter" value="{{$cfg.GroupFilter}}" placeholder="e.g. (|(cn=gogs_users)(cn=admins))">
+							</div>
+							<div class="field">
+								<label for="group_member_uid">Group attribute containing list of users</label>
+								<input id="group_member_uid" name="group_member_uid" value="{{$cfg.GroupMemberUid}}" placeholder="e.g. memberUid">
+							</div>
+							<div class="field">
+								<label for="user_uid">User attribute listed in group</label>
+								<input id="user_uid" name="user_uid" value="{{$cfg.UserUID}}" placeholder="e.g. uid">
+							</div>
 							{{if .Source.IsLDAP}}
 								<div class="inline field">
 									<div class="ui checkbox">

+ 22 - 0
templates/admin/auth/new.tmpl

@@ -95,6 +95,28 @@
 								<label for="attribute_mail">{{.i18n.Tr "admin.auths.attribute_mail"}}</label>
 								<input id="attribute_mail" name="attribute_mail" value="{{.attribute_mail}}" placeholder="e.g. mail">
 							</div>
+							<div class="inline field">
+								<div class="ui checkbox">
+									<label><strong>Verify group membership in LDAP</strong></label>
+									<input name="groups_enabled" type="checkbox">
+								</div>
+							</div>
+							<div class="field">
+								<label for="group_dn">Group search Base DN</label>
+								<input id="group_dn" name="group_dn" placeholder="e.g. ou=group,dc=mydomain,dc=com">
+							</div>
+							<div class="field">
+								<label for="group_filter">Valid groups filter</label>
+								<input id="group_filter" name="group_filter" placeholder="e.g. (|(cn=gogs_users)(cn=admins))">
+							</div>
+							<div class="field">
+								<label for="group_member_uid">Group attribute containing list of users</label>
+								<input id="group_member_uid" name="group_member_uid" placeholder="e.g. memberUid">
+							</div>
+							<div class="field">
+								<label for="user_uid">User attribute listed in group</label>
+								<input id="user_uid" name="user_uid" placeholder="e.g. uid">
+							</div>
 						</div>
 
 						<!-- SMTP -->