diff --git a/pkg/connector/group.go b/pkg/connector/group.go index d82dbfc..e64c0d8 100644 --- a/pkg/connector/group.go +++ b/pkg/connector/group.go @@ -27,6 +27,8 @@ const ( groupMemberUIDFilter = `(&` + userFilter + `(uid=%s))` groupMemberCommonNameFilter = `(&` + userFilter + `(cn=%s))` + groupMemberGidNumber = `(&` + userFilter + `(gidNumber=%s))` + attrGroupCommonName = "cn" attrGroupIdPosix = "gidNumber" attrGroupMember = "member" @@ -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() diff --git a/pkg/connector/group_test.go b/pkg/connector/group_test.go index a42a744..dbfab22 100644 --- a/pkg/connector/group_test.go +++ b/pkg/connector/group_test.go @@ -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) +} diff --git a/pkg/connector/testfixtures/primary_groups.ldif b/pkg/connector/testfixtures/primary_groups.ldif new file mode 100644 index 0000000..25c422b --- /dev/null +++ b/pkg/connector/testfixtures/primary_groups.ldif @@ -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 \ No newline at end of file