package keycloak_test import ( "context" "errors" "testing" "github.com/netkingdom/flex-auth/internal/adapters/keycloak" "github.com/netkingdom/flex-auth/pkg/api" ) func TestBuildAuthorizationRequestUsesUMAPermissionShape(t *testing.T) { adapter := newAdapter(t, &fakeClient{}) got, err := adapter.BuildAuthorizationRequest(api.CheckRequest{ ID: "check:keycloak", Subject: api.SubjectRef{ID: "user:alice"}, Action: "read", Resource: api.ResourceRef{ID: "document:internal-note", Type: "document", System: "markitect-tool"}, Context: map[string]any{"purpose": "support"}, CaringContext: caringDescriptor(), }) if err != nil { t.Fatalf("BuildAuthorizationRequest: %v", err) } if got.Realm != "platform" || got.Audience != "markitect-tool" { t.Fatalf("realm/audience = %s/%s", got.Realm, got.Audience) } if got.Permission.ResourceID != "document:internal-note" || got.Permission.Scope != "read" { t.Fatalf("permission = %+v", got.Permission) } if got.ClaimToken["caring_context"] == nil { t.Fatal("claim token missing CARING context") } } func TestResourceRegistrationsFromManifest(t *testing.T) { got := keycloak.ResourceRegistrationsFromManifest(api.ResourceManifest{ ID: "manifest:markitect", System: "markitect-tool", Actions: []string{"read", "export"}, Resources: []api.Resource{ { ID: "document:internal-note", Type: "document", Path: "/docs/internal-note", Labels: []string{"internal"}, TrustZone: "internal", Owner: "team:platform", }, }, }) if len(got) != 1 { t.Fatalf("len = %d", len(got)) } if got[0].ID != "document:internal-note" || got[0].Type != "document" { t.Fatalf("registration = %+v", got[0]) } if len(got[0].Scopes) != 2 || got[0].Attributes["trust_zone"][0] != "internal" { t.Fatalf("registration = %+v", got[0]) } } func TestAdapterCheckWrapsKeycloakAllow(t *testing.T) { client := &fakeClient{ result: keycloak.AuthorizationResult{ Allowed: true, Reason: "uma_permission_granted", RPTTokenID: "rpt:123", PolicyVersion: "kc-v2", Diagnostics: map[string]any{"policy": "document-reader"}, }, } adapter := newAdapter(t, client) got, err := adapter.Check(context.Background(), api.CheckRequest{ ID: "check:allow", Subject: api.SubjectRef{ID: "user:alice"}, Action: "read", Resource: api.ResourceRef{ID: "document:internal-note", Type: "document", System: "markitect-tool"}, CaringContext: caringDescriptor(), }) if err != nil { t.Fatalf("Check: %v", err) } if got.Effect != api.DecisionEffectAllow || got.Reason != "uma_permission_granted" { t.Fatalf("decision = %s/%s", got.Effect, got.Reason) } if got.Provenance.Evaluator != keycloak.EvaluatorName || got.MatchedPolicyVersion != "kc-v2" { t.Fatalf("provenance = %+v matched=%s", got.Provenance, got.MatchedPolicyVersion) } if got.Diagnostics["permission"] != "document:internal-note#read" || got.Diagnostics["rpt_token_id"] != "rpt:123" { t.Fatalf("diagnostics = %+v", got.Diagnostics) } if got.Caring == nil || got.Caring.Descriptor == nil { t.Fatal("missing CARING descriptor") } } func TestAdapterFailsClosedOnUnavailableKeycloak(t *testing.T) { client := &fakeClient{ err: keycloak.NewBackendError(keycloak.FailureUnavailable, "authorize", errors.New("connect refused")), } adapter := newAdapter(t, client) got, err := adapter.Check(context.Background(), api.CheckRequest{ ID: "check:down", Subject: api.SubjectRef{ID: "user:alice"}, Action: "read", Resource: api.ResourceRef{ID: "document:internal-note", Type: "document", System: "markitect-tool"}, CaringContext: caringDescriptor(), }) if err != nil { t.Fatalf("Check: %v", err) } if got.Effect != api.DecisionEffectDeny || got.Reason != "keycloak_unavailable" { t.Fatalf("decision = %s/%s; want fail closed", got.Effect, got.Reason) } if got.Diagnostics["keycloak_failure"] != "unavailable" { t.Fatalf("diagnostics = %+v", got.Diagnostics) } if got.Caring.ConformanceFindings[0].Code != "KEYCLOAK-UNAVAILABLE" { t.Fatalf("finding = %+v", got.Caring.ConformanceFindings[0]) } } func TestRegisterResourceManifestDelegatesToClient(t *testing.T) { client := &fakeClient{} adapter := newAdapter(t, client) report, err := adapter.RegisterResourceManifest(context.Background(), api.ResourceManifest{ System: "markitect-tool", Actions: []string{"read"}, Resources: []api.Resource{ {ID: "document:internal-note", Type: "document"}, }, }) if err != nil { t.Fatalf("RegisterResourceManifest: %v", err) } if report.ResourcesWritten != 1 || len(client.registered) != 1 { t.Fatalf("report = %+v registered = %+v", report, client.registered) } } func newAdapter(t *testing.T, client *fakeClient) *keycloak.Adapter { t.Helper() adapter, err := keycloak.New(client, keycloak.Options{ Realm: "platform", Audience: "markitect-tool", PolicyPackage: "keycloak.authz", PolicyVersion: "v1", }) if err != nil { t.Fatalf("New: %v", err) } return adapter } func caringDescriptor() *api.CaringAccessDescriptor { return &api.CaringAccessDescriptor{ ID: "descriptor:keycloak-reader", Profile: api.CaringProfileCaring040RC2, SubjectType: api.SubjectTypeHuman, OrganizationRelation: api.OrganizationRelationCustomer, CanonicalRole: api.CanonicalRoleDoer, Scope: api.CaringScope{Level: api.ScopeLevelResource, ID: "document:internal-note"}, Planes: []api.Plane{api.PlaneData}, Capabilities: []api.Capability{api.CapabilityView}, ExposureModes: []api.ExposureMode{api.ExposureModeMasked}, Restrictions: []api.Restriction{api.RestrictionExportBlocked}, } } type fakeClient struct { result keycloak.AuthorizationResult err error registered []keycloak.ResourceRegistration } func (c *fakeClient) Authorize(context.Context, keycloak.AuthorizationRequest) (keycloak.AuthorizationResult, error) { return c.result, c.err } func (c *fakeClient) RegisterResources(_ context.Context, resources []keycloak.ResourceRegistration) (keycloak.ResourceImportReport, error) { c.registered = append([]keycloak.ResourceRegistration(nil), resources...) return keycloak.ResourceImportReport{ResourcesWritten: len(resources), ResourceServerID: "rs:markitect"}, nil } func (c *fakeClient) Health(context.Context) error { return nil }