Merge pull request #903 from ericchiang/ldap-groups-on-user

connector/ldap: fix case where groups are listed on the user entity
This commit is contained in:
Eric Chiang 2017-04-11 14:06:42 -07:00 committed by GitHub
commit 3d7b1477e7
2 changed files with 139 additions and 35 deletions

View File

@ -256,18 +256,22 @@ func (c *ldapConnector) do(ctx context.Context, f func(c *ldap.Conn) error) erro
return f(conn) return f(conn)
} }
func getAttr(e ldap.Entry, name string) string { func getAttrs(e ldap.Entry, name string) []string {
for _, a := range e.Attributes { for _, a := range e.Attributes {
if a.Name != name { if a.Name != name {
continue continue
} }
if len(a.Values) == 0 { return a.Values
return ""
}
return a.Values[0]
} }
if name == "DN" { if name == "DN" {
return e.DN return []string{e.DN}
}
return nil
}
func getAttr(e ldap.Entry, name string) string {
if a := getAttrs(e, name); len(a) > 0 {
return a[0]
} }
return "" return ""
} }
@ -454,7 +458,9 @@ func (c *ldapConnector) groups(ctx context.Context, user ldap.Entry) ([]string,
return nil, nil return nil, nil
} }
filter := fmt.Sprintf("(%s=%s)", c.GroupSearch.GroupAttr, ldap.EscapeFilter(getAttr(user, c.GroupSearch.UserAttr))) var groups []*ldap.Entry
for _, attr := range getAttrs(user, c.GroupSearch.UserAttr) {
filter := fmt.Sprintf("(%s=%s)", c.GroupSearch.GroupAttr, ldap.EscapeFilter(attr))
if c.GroupSearch.Filter != "" { if c.GroupSearch.Filter != "" {
filter = fmt.Sprintf("(&%s%s)", c.GroupSearch.Filter, filter) filter = fmt.Sprintf("(&%s%s)", c.GroupSearch.Filter, filter)
} }
@ -466,24 +472,25 @@ func (c *ldapConnector) groups(ctx context.Context, user ldap.Entry) ([]string,
Attributes: []string{c.GroupSearch.NameAttr}, Attributes: []string{c.GroupSearch.NameAttr},
} }
var groups []*ldap.Entry gotGroups := false
if err := c.do(ctx, func(conn *ldap.Conn) error { if err := c.do(ctx, func(conn *ldap.Conn) error {
resp, err := conn.Search(req) resp, err := conn.Search(req)
if err != nil { if err != nil {
return fmt.Errorf("ldap: search failed: %v", err) return fmt.Errorf("ldap: search failed: %v", err)
} }
groups = resp.Entries gotGroups = len(resp.Entries) != 0
groups = append(groups, resp.Entries...)
return nil return nil
}); err != nil { }); err != nil {
return nil, err return nil, err
} }
if len(groups) == 0 { if !gotGroups {
// TODO(ericchiang): Is this going to spam the logs? // TODO(ericchiang): Is this going to spam the logs?
c.logger.Errorf("ldap: groups search with filter %q returned no groups", filter) c.logger.Errorf("ldap: groups search with filter %q returned no groups", filter)
} }
}
var groupNames []string var groupNames []string
for _, group := range groups { for _, group := range groups {
name := getAttr(*group, c.GroupSearch.NameAttr) name := getAttr(*group, c.GroupSearch.NameAttr)
if name == "" { if name == "" {

View File

@ -52,7 +52,7 @@ ou: People
dn: cn=jane,ou=People,dc=example,dc=org dn: cn=jane,ou=People,dc=example,dc=org
objectClass: person objectClass: person
objectClass: iNetOrgPerson objectClass: inetOrgPerson
sn: doe sn: doe
cn: jane cn: jane
mail: janedoe@example.com mail: janedoe@example.com
@ -60,7 +60,7 @@ userpassword: foo
dn: cn=john,ou=People,dc=example,dc=org dn: cn=john,ou=People,dc=example,dc=org
objectClass: person objectClass: person
objectClass: iNetOrgPerson objectClass: inetOrgPerson
sn: doe sn: doe
cn: john cn: john
mail: johndoe@example.com mail: johndoe@example.com
@ -127,7 +127,7 @@ ou: People
dn: cn=jane,ou=People,dc=example,dc=org dn: cn=jane,ou=People,dc=example,dc=org
objectClass: person objectClass: person
objectClass: iNetOrgPerson objectClass: inetOrgPerson
sn: doe sn: doe
cn: jane cn: jane
mail: janedoe@example.com mail: janedoe@example.com
@ -135,7 +135,7 @@ userpassword: foo
dn: cn=john,ou=People,dc=example,dc=org dn: cn=john,ou=People,dc=example,dc=org
objectClass: person objectClass: person
objectClass: iNetOrgPerson objectClass: inetOrgPerson
sn: doe sn: doe
cn: john cn: john
mail: johndoe@example.com mail: johndoe@example.com
@ -201,6 +201,103 @@ member: cn=jane,ou=People,dc=example,dc=org
runTests(t, schema, c, tests) runTests(t, schema, c, tests)
} }
func TestGroupsOnUserEntity(t *testing.T) {
schema := `
dn: dc=example,dc=org
objectClass: dcObject
objectClass: organization
o: Example Company
dc: example
dn: ou=People,dc=example,dc=org
objectClass: organizationalUnit
ou: People
# Groups are enumerated as part of the user entity instead of the members being
# a list on the group entity.
dn: cn=jane,ou=People,dc=example,dc=org
objectClass: person
objectClass: inetOrgPerson
sn: doe
cn: jane
mail: janedoe@example.com
userpassword: foo
departmentNumber: 1000
departmentNumber: 1001
dn: cn=john,ou=People,dc=example,dc=org
objectClass: person
objectClass: inetOrgPerson
sn: doe
cn: john
mail: johndoe@example.com
userpassword: bar
departmentNumber: 1000
departmentNumber: 1002
# Group definitions. Notice that they don't have any "member" field.
dn: ou=Groups,dc=example,dc=org
objectClass: organizationalUnit
ou: Groups
dn: cn=admins,ou=Groups,dc=example,dc=org
objectClass: posixGroup
cn: admins
gidNumber: 1000
dn: cn=developers,ou=Groups,dc=example,dc=org
objectClass: posixGroup
cn: developers
gidNumber: 1001
dn: cn=designers,ou=Groups,dc=example,dc=org
objectClass: posixGroup
cn: designers
gidNumber: 1002
`
c := &Config{}
c.UserSearch.BaseDN = "ou=People,dc=example,dc=org"
c.UserSearch.NameAttr = "cn"
c.UserSearch.EmailAttr = "mail"
c.UserSearch.IDAttr = "DN"
c.UserSearch.Username = "cn"
c.GroupSearch.BaseDN = "ou=Groups,dc=example,dc=org"
c.GroupSearch.UserAttr = "departmentNumber"
c.GroupSearch.GroupAttr = "gidNumber"
c.GroupSearch.NameAttr = "cn"
tests := []subtest{
{
name: "validpassword",
username: "jane",
password: "foo",
groups: true,
want: connector.Identity{
UserID: "cn=jane,ou=People,dc=example,dc=org",
Username: "jane",
Email: "janedoe@example.com",
EmailVerified: true,
Groups: []string{"admins", "developers"},
},
},
{
name: "validpassword2",
username: "john",
password: "bar",
groups: true,
want: connector.Identity{
UserID: "cn=john,ou=People,dc=example,dc=org",
Username: "john",
Email: "johndoe@example.com",
EmailVerified: true,
Groups: []string{"admins", "designers"},
},
},
}
runTests(t, schema, c, tests)
}
// runTests runs a set of tests against an LDAP schema. It does this by // runTests runs a set of tests against an LDAP schema. It does this by
// setting up an OpenLDAP server and injecting the provided scheme. // setting up an OpenLDAP server and injecting the provided scheme.
// //