Skip to content

Commit

Permalink
Merge pull request #75 from ConductorOne/pq/posix_primary_groups
Browse files Browse the repository at this point in the history
Add support for LDAP primary groups
  • Loading branch information
pquerna authored Oct 21, 2024
2 parents 76229c9 + 1627fa4 commit e4ee83d
Show file tree
Hide file tree
Showing 3 changed files with 123 additions and 0 deletions.
49 changes: 49 additions & 0 deletions pkg/connector/group.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ const (
groupMemberUIDFilter = `(&` + userFilter + `(uid=%s))`
groupMemberCommonNameFilter = `(&` + userFilter + `(cn=%s))`

groupMemberGidNumber = `(&` + userFilter + `(gidNumber=%s))`

attrGroupCommonName = "cn"
attrGroupIdPosix = "gidNumber"
attrGroupMember = "member"
Expand Down Expand Up @@ -203,9 +205,56 @@ func (g *groupResourceType) Grants(ctx context.Context, resource *v2.Resource, t
rv = append(rv, g)
}

posixGid := ldapGroup.GetEqualFoldAttributeValue(attrGroupIdPosix)
if posixGid == "" {
return rv, "", nil, nil
}

nextPage := ""
for {
var userEntries []*ldap3.Entry
userEntries, nextPage, err = g.client.LdapSearch(
ctx,
ldap3.ScopeWholeSubtree,
g.userSearchDN,
fmt.Sprintf(groupMemberGidNumber, ldap3.EscapeFilter(posixGid)),
[]string{"dn"},
nextPage, 100,
)
if err != nil {
return nil, "", nil, fmt.Errorf("ldap-connector: failed to list group members: %w", err)
}
for _, userEntry := range userEntries {
userDN, err := ldap.CanonicalizeDN(userEntry.DN)
if err != nil {
l.Error("ldap-connector: invalid user DN", zap.String("user_dn", userEntry.DN), zap.Error(err))
continue
}
g := newGrantFromDN(resource, userDN.String())
rv = append(rv, g)
}
if nextPage == "" {
break
}
}

rv = uniqueGrants(rv)

return rv, "", nil, nil
}

func uniqueGrants(grants []*v2.Grant) []*v2.Grant {
seen := make(map[string]struct{})
var uniqueGrants []*v2.Grant
for _, grant := range grants {
if _, ok := seen[grant.Principal.Id.Resource]; !ok {
uniqueGrants = append(uniqueGrants, grant)
seen[grant.Principal.Id.Resource] = struct{}{}
}
}
return uniqueGrants
}

// findMember: note this function can return an empty string if the member is not found.
func (g *groupResourceType) findMember(ctx context.Context, memberId string) (string, error) {
g.uid2dnMtx.Lock()
Expand Down
32 changes: 32 additions & 0 deletions pkg/connector/group_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,3 +79,35 @@ func TestGroupGrantRevoke(t *testing.T) {
require.EqualExportedValues(t, rogerGrant.Entitlement, grants[0].Entitlement)
require.Equal(t, rogerGrant.Id, grants[0].Id)
}

func TestGroupPosixGidNumber(t *testing.T) {
ctx, done := context.WithCancel(context.Background())
defer done()

ctx = ctxzap.ToContext(ctx, zap.Must(zap.NewDevelopment()))

connector, err := createConnector(ctx, t, "primary_groups.ldif")
require.NoError(t, err)

gb := groupBuilder(connector.client, connector.config.GroupSearchDN, connector.config.UserSearchDN)

groups, pt, _, err := gb.List(ctx, nil, &pagination.Token{})
require.NoError(t, err)
require.Len(t, groups, 1)
require.Empty(t, pt)

staffGroup := pluck(groups, func(g *v2.Resource) bool {
return g.GetDisplayName() == "staff"
})
require.NotNil(t, staffGroup)

ents, pt, _, err := gb.Entitlements(ctx, staffGroup, &pagination.Token{})
require.NoError(t, err)
require.Empty(t, pt)
require.Len(t, ents, 1)

grants, pt, _, err := gb.Grants(ctx, staffGroup, &pagination.Token{})
require.NoError(t, err)
require.Empty(t, pt)
require.Len(t, grants, 2)
}
42 changes: 42 additions & 0 deletions pkg/connector/testfixtures/primary_groups.ldif
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
version: 1

dn: ou=groups,dc=example,dc=org
objectclass: organizationalUnit
objectclass: top
ou: groups

dn: cn=staff,ou=groups,dc=example,dc=org
cn: staff
gidnumber: 500
objectclass: posixGroup
objectclass: top

dn: cn=Humpty Dumpty,ou=users,dc=example,dc=org
cn: Humpty Dumpty
gidnumber: 500
givenname: Humpty
homedirectory: /home/users/ hdumpty
loginshell: /bin/bash
objectclass: inetOrgPerson
objectclass: posixAccount
objectclass: top
sn: Dumpty
uid: hdumpty
uidnumber: 1001
userpassword: {CRYPT}$6$jUJEWaXc$ZK2fUeCkJbhWfIWvXc/Xsjzo3.09DaVWFtFCl1s9qFH
I6X7UcPgDJ8SeNNagObmCNurn5VyWsufmBnac/qO601

dn: cn=roger,ou=users,dc=example,dc=org
cn: roger
gidnumber: 500
givenname: Roger Rabbit
homedirectory: /home/roger
loginshell: /bin/bash
objectclass: inetOrgPerson
objectclass: posixAccount
objectclass: top
sn: Rabbit
uid: roger
uidnumber: 1000
userpassword: {CRYPT}$6$Sy1sX75G$/.nlmUpTeW7REXKsdXjRZVXitOrFPk5uEvGs/eC8cXi
D0WHlNBT33DDlHlgkP.eiOM5t6VrF1iDj8kYUMPwkT0

0 comments on commit e4ee83d

Please sign in to comment.