mirror of https://github.com/gogs/gogs.git
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
parent
11ad64f6cb
commit
dbb7e5464b
|
@ -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
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
},
|
||||
|
|
|
@ -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">
|
||||
|
|
|
@ -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 -->
|
||||
|
|
Loading…
Reference in New Issue