aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authoraboron <aboron@users.noreply.github.com>2017-05-29 22:33:50 -0400
committer无闻 <u@gogs.io>2017-05-29 22:33:50 -0400
commitdbb7e5464b6a9cad430b2f36b52e7674211f51cf (patch)
tree8f201849432a65d3a58e04ef2c92b7eedfbf7a24
parent11ad64f6cbdc489a49247604fe697dffaa090ac2 (diff)
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.
-rw-r--r--pkg/auth/ldap/README.md18
-rw-r--r--pkg/auth/ldap/ldap.go75
-rw-r--r--pkg/form/auth.go5
-rw-r--r--routers/admin/auths.go5
-rw-r--r--templates/admin/auth/edit.tmpl22
-rw-r--r--templates/admin/auth/new.tmpl22
6 files changed, 144 insertions, 3 deletions
diff --git a/pkg/auth/ldap/README.md b/pkg/auth/ldap/README.md
index 3a3e0204..b8c95b3b 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 78cd66a4..25fddeb7 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 10dccd00..e045a2df 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 532adb4f..e42bb5e2 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 b06cee8b..0026a4a9 100644
--- a/templates/admin/auth/edit.tmpl
+++ b/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">
diff --git a/templates/admin/auth/new.tmpl b/templates/admin/auth/new.tmpl
index db59856f..1707cd87 100644
--- a/templates/admin/auth/new.tmpl
+++ b/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 -->