Browse Source

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.
pull/4517/merge
aboron 8 years ago committed by 无闻
parent
commit
dbb7e5464b
  1. 18
      pkg/auth/ldap/README.md
  2. 75
      pkg/auth/ldap/ldap.go
  3. 5
      pkg/form/auth.go
  4. 5
      routers/admin/auths.go
  5. 22
      templates/admin/auth/edit.tmpl
  6. 22
      templates/admin/auth/new.tmpl

18
pkg/auth/ldap/README.md

@ -99,3 +99,21 @@ share the following fields:
matching parameter will be substituted with the user's username. matching parameter will be substituted with the user's username.
* Example: (&(objectClass=posixAccount)(cn=%s)) * Example: (&(objectClass=posixAccount)(cn=%s))
* Example: (&(objectClass=posixAccount)(uid=%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

75
pkg/auth/ldap/ldap.go

@ -42,6 +42,11 @@ type Source struct {
AttributesInBind bool // fetch attributes in bind context (not user) AttributesInBind bool // fetch attributes in bind context (not user)
Filter string // Query filter to validate entry Filter string // Query filter to validate entry
AdminFilter string // Query filter to check if user is admin 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 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 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) { func (ls *Source) findUserDN(l *ldap.Conn, name string) (string, bool) {
log.Trace("Search for LDAP user: %s", name) log.Trace("Search for LDAP user: %s", name)
if ls.BindDN != "" && ls.BindPassword != "" { if ls.BindDN != "" && ls.BindPassword != "" {
@ -194,15 +221,15 @@ func (ls *Source) SearchEntry(name, passwd string, directBind bool) (string, str
return "", "", "", "", false, false 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( search := ldap.NewSearchRequest(
userDN, ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false, userFilter, 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) nil)
sr, err := l.Search(search) sr, err := l.Search(search)
if err != nil { if err != nil {
log.Error(4, "LDAP search failed: %v", err) log.Error(4, "LDAP user search failed: %v", err)
return "", "", "", "", false, false return "", "", "", "", false, false
} else if len(sr.Entries) < 1 { } else if len(sr.Entries) < 1 {
if directBind { if directBind {
@ -218,6 +245,48 @@ func (ls *Source) SearchEntry(name, passwd string, directBind bool) (string, str
firstname := sr.Entries[0].GetAttributeValue(ls.AttributeName) firstname := sr.Entries[0].GetAttributeValue(ls.AttributeName)
surname := sr.Entries[0].GetAttributeValue(ls.AttributeSurname) surname := sr.Entries[0].GetAttributeValue(ls.AttributeSurname)
mail := sr.Entries[0].GetAttributeValue(ls.AttributeMail) 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 isAdmin := false
if len(ls.AdminFilter) > 0 { if len(ls.AdminFilter) > 0 {

5
pkg/form/auth.go

@ -26,6 +26,11 @@ type Authentication struct {
AttributesInBind bool AttributesInBind bool
Filter string Filter string
AdminFilter string AdminFilter string
GroupsEnabled bool
GroupDN string
GroupFilter string
GroupMemberUid string
UserUID string
IsActive bool IsActive bool
SMTPAuth string SMTPAuth string
SMTPHost string SMTPHost string

5
routers/admin/auths.go

@ -93,6 +93,11 @@ func parseLDAPConfig(f form.Authentication) *models.LDAPConfig {
AttributeMail: f.AttributeMail, AttributeMail: f.AttributeMail,
AttributesInBind: f.AttributesInBind, AttributesInBind: f.AttributesInBind,
Filter: f.Filter, Filter: f.Filter,
GroupsEnabled: f.GroupsEnabled,
GroupDN: f.GroupDN,
GroupFilter: f.GroupFilter,
GroupMemberUid: f.GroupMemberUid,
UserUID: f.UserUID,
AdminFilter: f.AdminFilter, AdminFilter: f.AdminFilter,
Enabled: true, Enabled: true,
}, },

22
templates/admin/auth/edit.tmpl

@ -92,6 +92,28 @@
<label for="attribute_mail">{{.i18n.Tr "admin.auths.attribute_mail"}}</label> <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> <input id="attribute_mail" name="attribute_mail" value="{{$cfg.AttributeMail}}" placeholder="e.g. mail" required>
</div> </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}} {{if .Source.IsLDAP}}
<div class="inline field"> <div class="inline field">
<div class="ui checkbox"> <div class="ui checkbox">

22
templates/admin/auth/new.tmpl

@ -95,6 +95,28 @@
<label for="attribute_mail">{{.i18n.Tr "admin.auths.attribute_mail"}}</label> <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"> <input id="attribute_mail" name="attribute_mail" value="{{.attribute_mail}}" placeholder="e.g. mail">
</div> </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> </div>
<!-- SMTP --> <!-- SMTP -->

Loading…
Cancel
Save