Files
flex-auth/internal/adapters/directory/resolvers_test.go
tegwick 32933c71f9
Some checks failed
CI / Build and Test (push) Has been cancelled
CI / Lint (push) Has been cancelled
Add directory group resolver adapters
2026-05-17 07:24:50 +02:00

152 lines
5.0 KiB
Go

package directory_test
import (
"context"
"testing"
"time"
"github.com/netkingdom/flex-auth/internal/adapters/directory"
"github.com/netkingdom/flex-auth/pkg/api"
)
func TestGraphResolverDetectsTokenOverage(t *testing.T) {
now := time.Date(2026, 5, 17, 5, 0, 0, 0, time.UTC)
resolver := directory.GraphResolver{
Client: graphClient{groups: []directory.ExternalGroup{{ID: "group:platform", DisplayName: "Platform"}}},
OrganizationRelation: api.OrganizationRelationCustomer,
MaxAge: time.Hour,
}
got, err := resolver.ResolveGroups(context.Background(), directory.ResolveRequest{
Subject: api.SubjectRef{ID: "user:alice"},
Claims: map[string]any{
"_claim_names": map[string]any{"groups": "src1"},
},
Now: now,
})
if err != nil {
t.Fatalf("ResolveGroups: %v", err)
}
if !got.Overage.Detected || got.Overage.Claim != "_claim_names.groups" {
t.Fatalf("overage = %+v", got.Overage)
}
if len(got.Groups) != 1 || got.Groups[0].Source != directory.SourceGraph {
t.Fatalf("groups = %+v", got.Groups)
}
if got.Freshness.ExpiresAt != now.Add(time.Hour) {
t.Fatalf("freshness = %+v", got.Freshness)
}
}
func TestSCIMResolverCarriesDescriptorProvenance(t *testing.T) {
descriptor := caringDescriptor()
resolver := directory.SCIMResolver{
Client: scimClient{groups: []directory.ExternalGroup{{ID: "group:customers", Descriptor: descriptor}}},
OrganizationRelation: api.OrganizationRelationCustomer,
}
got, err := resolver.ResolveGroups(context.Background(), directory.ResolveRequest{Subject: api.SubjectRef{ID: "user:alice"}})
if err != nil {
t.Fatalf("ResolveGroups: %v", err)
}
if got.Groups[0].Descriptor == nil || got.Groups[0].Descriptor.CanonicalRole != api.CanonicalRoleDoer {
t.Fatalf("descriptor = %+v", got.Groups[0].Descriptor)
}
if got.Groups[0].Claim != "groups" {
t.Fatalf("claim = %q", got.Groups[0].Claim)
}
}
func TestLDAPResolverUsesDistinguishedNameClaim(t *testing.T) {
client := ldapClient{groups: []directory.ExternalGroup{{ID: "cn=platform,ou=groups,dc=example,dc=test"}}}
resolver := directory.LDAPResolver{Client: client}
_, err := resolver.ResolveGroups(context.Background(), directory.ResolveRequest{
Subject: api.SubjectRef{ID: "user:alice"},
Claims: map[string]any{"distinguished_name": "cn=alice,ou=users,dc=example,dc=test"},
})
if err != nil {
t.Fatalf("ResolveGroups: %v", err)
}
if client.lastDN != "" {
t.Fatal("value receiver should not update original client")
}
}
func TestMergeResultsAndApplyToSubject(t *testing.T) {
subject := api.SubjectRef{ID: "user:alice"}
enrichment := directory.MergeResults(subject,
directory.ResolveResult{
Source: directory.SourceGraph,
Groups: []directory.GroupGrant{
{ID: "group:b", Source: directory.SourceGraph},
{ID: "group:a", Source: directory.SourceGraph, Descriptor: caringDescriptor()},
},
Overage: directory.OverageMetadata{Detected: true, Claim: "_claim_names.groups"},
},
directory.ResolveResult{
Source: directory.SourceKeycloak,
Groups: []directory.GroupGrant{
{ID: "group:a", Source: directory.SourceKeycloak},
},
Roles: []directory.RoleGrant{
{Role: api.CanonicalRoleDoer, Source: directory.SourceKeycloak},
},
},
)
if len(enrichment.Groups) != 2 || enrichment.Groups[0] != "group:a" || enrichment.Groups[1] != "group:b" {
t.Fatalf("groups = %+v", enrichment.Groups)
}
if len(enrichment.Roles) != 1 || enrichment.Roles[0] != api.CanonicalRoleDoer {
t.Fatalf("roles = %+v", enrichment.Roles)
}
if len(enrichment.Descriptors) != 1 || len(enrichment.Overage) != 1 {
t.Fatalf("enrichment = %+v", enrichment)
}
applied := directory.ApplyToSubject(api.Subject{ID: "user:alice", Groups: []string{"group:existing"}}, enrichment)
if len(applied.Groups) != 3 || applied.Metadata["directory_overage"] == nil {
t.Fatalf("applied subject = %+v", applied)
}
}
func caringDescriptor() *api.CaringAccessDescriptor {
return &api.CaringAccessDescriptor{
ID: "descriptor:directory",
Profile: api.CaringProfileCaring040RC2,
SubjectType: api.SubjectTypeGroup,
OrganizationRelation: api.OrganizationRelationCustomer,
CanonicalRole: api.CanonicalRoleDoer,
Scope: api.CaringScope{Level: api.ScopeLevelTenant, ID: "tenant:alpha"},
Planes: []api.Plane{api.PlaneIdentity},
Capabilities: []api.Capability{api.CapabilityUse},
}
}
type graphClient struct {
groups []directory.ExternalGroup
}
func (c graphClient) GetMemberGroups(context.Context, string) ([]directory.ExternalGroup, directory.OverageMetadata, error) {
return c.groups, directory.OverageMetadata{Total: len(c.groups)}, nil
}
type scimClient struct {
groups []directory.ExternalGroup
}
func (c scimClient) GroupsForUser(context.Context, string) ([]directory.ExternalGroup, error) {
return c.groups, nil
}
type ldapClient struct {
groups []directory.ExternalGroup
lastDN string
}
func (c ldapClient) GroupsForDN(_ context.Context, dn string) ([]directory.ExternalGroup, error) {
c.lastDN = dn
return c.groups, nil
}