From b1d07a32ee537d52cb7b999bcd298c8d1df8781a Mon Sep 17 00:00:00 2001 From: Paul Querna Date: Tue, 15 Oct 2024 12:58:11 -0700 Subject: [PATCH] add tests to groups --- compose.yaml | 12 +-- pkg/connector/connector_test.go | 104 ++++++++++++++++++++++++ pkg/connector/group.go | 9 +- pkg/connector/group_test.go | 87 ++++++++++++++++++++ pkg/connector/testfixtures/simple..ldif | 41 ++++++++++ 5 files changed, 246 insertions(+), 7 deletions(-) create mode 100644 pkg/connector/connector_test.go create mode 100644 pkg/connector/group_test.go create mode 100644 pkg/connector/testfixtures/simple..ldif diff --git a/compose.yaml b/compose.yaml index 9a6a5d6b..1b2908ea 100644 --- a/compose.yaml +++ b/compose.yaml @@ -1,4 +1,4 @@ -version: '3.7' +version: '3' services: openldap: image: osixia/openldap:latest @@ -8,10 +8,10 @@ services: - "389:389" - "636:636" # Uncomment these lines if you want to persist data. - # volumes: - # - ./data/certificates:/container/service/slapd/assets/certs - # - ./data/slapd/database:/var/lib/ldap - # - ./data/slapd/config:/etc/ldap/slapd.d + volumes: + - ./data/certificates:/container/service/slapd/assets/certs + - ./data/slapd/database:/var/lib/ldap + - ./data/slapd/config:/etc/ldap/slapd.d environment: # If you want to persist data, set the UID/GID to the owner user/groups in your volume mounts # - LDAP_OPENLDAP_UID=501 @@ -39,7 +39,7 @@ services: container_name: phpldapadmin hostname: phpldapadmin ports: - - "80:80" + - "8080:80" environment: - PHPLDAPADMIN_LDAP_HOSTS=openldap - PHPLDAPADMIN_HTTPS=false diff --git a/pkg/connector/connector_test.go b/pkg/connector/connector_test.go new file mode 100644 index 00000000..f3a8c98f --- /dev/null +++ b/pkg/connector/connector_test.go @@ -0,0 +1,104 @@ +package connector + +import ( + "context" + "os" + "path/filepath" + + "embed" + "net/url" + "testing" + + "github.com/conductorone/baton-ldap/pkg/config" + "github.com/go-ldap/ldap/v3" + "github.com/stretchr/testify/require" + "github.com/testcontainers/testcontainers-go" + "github.com/testcontainers/testcontainers-go/modules/openldap" + + _ "embed" +) + +//go:embed testfixtures/*.ldif +var fixtures embed.FS + +func TestValidate(t *testing.T) { + ctx, done := context.WithCancel(context.Background()) + defer done() + + connector, err := createConnector(ctx, t, "") + require.NoError(t, err) + + _, err = connector.Validate(ctx) + require.NoError(t, err) +} + +func createConnector(ctx context.Context, t *testing.T, fixtureName string) (*LDAP, error) { + opts := []testcontainers.ContainerCustomizer{ + openldap.WithAdminUsername("admin"), + openldap.WithAdminPassword("hunter2"), + } + + if fixtureName != "" { + data, err := fixtures.ReadFile(filepath.Join("testfixtures", fixtureName)) + if err != nil { + return nil, err + } + fd, err := os.CreateTemp("", "ldif") + if err != nil { + return nil, err + } + fdPath := fd.Name() + fd.Close() + t.Cleanup(func() { + os.Remove(fdPath) + }) + + err = os.WriteFile(fdPath, data, 0600) + if err != nil { + return nil, err + } + opts = append(opts, openldap.WithInitialLdif(fdPath)) + } + + container, err := openldap.Run(ctx, + "bitnami/openldap:2.6.6", + opts..., + ) + if err != nil { + return nil, err + } + t.Cleanup(func() { + require.NoError(t, container.Terminate(context.Background())) + }) + + serverURL, err := container.ConnectionString(ctx) + if err != nil { + return nil, err + } + + sux, err := url.Parse(serverURL) + if err != nil { + return nil, err + } + + bindDN, err := ldap.ParseDN("cn=admin,dc=example,dc=org") + if err != nil { + return nil, err + } + + cf := &config.Config{ + ServerURL: sux, + BindDN: bindDN, + BaseDN: mustParseDN(t, "dc=example,dc=org"), + GroupSearchDN: mustParseDN(t, "ou=groups,dc=example,dc=org"), + UserSearchDN: mustParseDN(t, "ou=users,dc=example,dc=org"), + BindPassword: "hunter2", + } + return New(ctx, cf) +} + +func mustParseDN(t *testing.T, input string) *ldap.DN { + dn, err := ldap.ParseDN(input) + require.NoError(t, err) + return dn +} diff --git a/pkg/connector/group.go b/pkg/connector/group.go index b592df47..43a37d15 100644 --- a/pkg/connector/group.go +++ b/pkg/connector/group.go @@ -2,6 +2,7 @@ package connector import ( "context" + "errors" "fmt" "github.com/conductorone/baton-ldap/pkg/ldap" @@ -92,7 +93,7 @@ func (g *groupResourceType) List(ctx context.Context, _ *v2.ResourceId, pt *pagi ResourcesPageSize, ) if err != nil { - return nil, "", nil, fmt.Errorf("ldap-connector: failed to list groups: %w", err) + return nil, "", nil, fmt.Errorf("ldap-connector: failed to list groups in '%s': %w", g.groupSearchDN.String(), err) } pageToken, err := bag.NextToken(nextPage) @@ -358,6 +359,12 @@ func (g *groupResourceType) Revoke(ctx context.Context, grant *v2.Grant) (annota modifyRequest, ) if err != nil { + var lerr *ldap3.Error + if errors.As(err, &lerr) { + if lerr.ResultCode == ldap3.LDAPResultNoSuchAttribute { + return nil, nil + } + } return nil, fmt.Errorf("ldap-connector: failed to revoke group membership from user: %w", err) } diff --git a/pkg/connector/group_test.go b/pkg/connector/group_test.go new file mode 100644 index 00000000..b76b6fc8 --- /dev/null +++ b/pkg/connector/group_test.go @@ -0,0 +1,87 @@ +package connector + +import ( + "context" + "testing" + + v2 "github.com/conductorone/baton-sdk/pb/c1/connector/v2" + "github.com/conductorone/baton-sdk/pkg/pagination" + "github.com/stretchr/testify/require" +) + +func TestGroupGrantRevoke(t *testing.T) { + ctx, done := context.WithCancel(context.Background()) + defer done() + + connector, err := createConnector(ctx, t, "simple..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, 2) + require.Empty(t, pt) + + staffGroup := pluck(groups, func(g *v2.Resource) bool { + return g.GetDisplayName() == "staff" + }) + require.NotNil(t, staffGroup) + + testGroup := pluck(groups, func(g *v2.Resource) bool { + return g.GetDisplayName() == "test" + }) + require.NotNil(t, testGroup) + + ents, pt, _, err := gb.Entitlements(ctx, testGroup, &pagination.Token{}) + require.NoError(t, err) + require.Empty(t, pt) + require.Len(t, ents, 1) + + membershipEnt := ents[0] + + grants, pt, _, err := gb.Grants(ctx, testGroup, &pagination.Token{}) + require.NoError(t, err) + require.Empty(t, pt) + require.Len(t, grants, 1) + + rogerGrant := grants[0] + _, err = gb.Revoke(ctx, rogerGrant) + require.NoError(t, err) + // test double revoke doesn't cause a hard error + _, err = gb.Revoke(ctx, rogerGrant) + require.NoError(t, err) + + // verify 0 grants + grants, pt, _, err = gb.Grants(ctx, testGroup, &pagination.Token{}) + require.NoError(t, err) + require.Empty(t, pt) + require.Len(t, grants, 0) + + _, err = gb.Grant(ctx, rogerGrant.Principal, membershipEnt) + require.NoError(t, err) + // test double revoke doesn't cause a hard error + _, err = gb.Grant(ctx, rogerGrant.Principal, membershipEnt) + require.NoError(t, err) + + // verify 1 grant + grants, pt, _, err = gb.Grants(ctx, testGroup, &pagination.Token{}) + require.NoError(t, err) + require.Empty(t, pt) + require.Len(t, grants, 1) + + // verify its roger! + require.EqualExportedValues(t, rogerGrant.Principal, grants[0].Principal) + require.EqualExportedValues(t, rogerGrant.Entitlement, grants[0].Entitlement) + require.Equal(t, rogerGrant.Id, grants[0].Id) +} + +func pluck[T any](slice []T, fn func(v T) bool) T { + var emptyT T + for _, v := range slice { + if fn(v) { + return v + } + } + return emptyT +} diff --git a/pkg/connector/testfixtures/simple..ldif b/pkg/connector/testfixtures/simple..ldif new file mode 100644 index 00000000..78490d04 --- /dev/null +++ b/pkg/connector/testfixtures/simple..ldif @@ -0,0 +1,41 @@ +# simple ldif file for testing +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=test,ou=groups,dc=example,dc=org +cn: test +gidnumber: 501 +memberUid: roger +objectclass: posixGroup +objectclass: top + +# Entry 5: ou=users,dc=example,dc=org +# dn: ou=users,dc=example,dc=org +# objectclass: organizationalUnit +# objectclass: top +# ou: users + +# Entry 6: cn=roger,ou=users,dc=example,dc=org +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/eC8cXiD0WHlNBT33DDlHlgkP.eiOM5t6VrF1iDj8kYUMPwkT0