generated from coulomb/repo-seed
Implement Topaz adapter
This commit is contained in:
303
internal/adapters/topaz/adapter_test.go
Normal file
303
internal/adapters/topaz/adapter_test.go
Normal file
@@ -0,0 +1,303 @@
|
||||
package topaz_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/netkingdom/flex-auth/internal/adapters/topaz"
|
||||
"github.com/netkingdom/flex-auth/internal/policy"
|
||||
"github.com/netkingdom/flex-auth/internal/registry"
|
||||
"github.com/netkingdom/flex-auth/pkg/api"
|
||||
)
|
||||
|
||||
func TestSnapshotToDirectoryMapsCanonicalRegistry(t *testing.T) {
|
||||
store := loadRegistry(t)
|
||||
|
||||
got := topaz.SnapshotToDirectory(store.Snapshot())
|
||||
|
||||
assertObject(t, got, "user", "user:alice")
|
||||
assertObject(t, got, "identity", "identity:user:alice")
|
||||
assertObject(t, got, "group", "group:platform-architecture")
|
||||
assertObject(t, got, "document", "document:internal-note")
|
||||
|
||||
assertRelation(t, got, topaz.DirectoryRelation{
|
||||
ObjectType: "identity",
|
||||
ObjectID: "identity:user:alice",
|
||||
Relation: "identifier",
|
||||
SubjectType: "user",
|
||||
SubjectID: "user:alice",
|
||||
})
|
||||
assertRelation(t, got, topaz.DirectoryRelation{
|
||||
ObjectType: "group",
|
||||
ObjectID: "group:platform-architecture",
|
||||
Relation: "member",
|
||||
SubjectType: "user",
|
||||
SubjectID: "user:alice",
|
||||
})
|
||||
assertRelation(t, got, topaz.DirectoryRelation{
|
||||
ObjectType: "document",
|
||||
ObjectID: "document:internal-note",
|
||||
Relation: "reader",
|
||||
SubjectType: "group",
|
||||
SubjectID: "group:platform-architecture",
|
||||
SubjectRelation: "member",
|
||||
})
|
||||
}
|
||||
|
||||
func TestAdapterCheckWrapsTopazAllowInFlexAuthEnvelope(t *testing.T) {
|
||||
client := &fakeClient{
|
||||
checkResult: topaz.CheckResult{
|
||||
Allowed: true,
|
||||
DirectoryETag: "etag:rel-42",
|
||||
Diagnostics: map[string]any{
|
||||
"topaz_trace": "trace-1",
|
||||
},
|
||||
},
|
||||
}
|
||||
adapter := newAdapter(t, client)
|
||||
|
||||
got, err := adapter.Check(context.Background(), api.CheckRequest{
|
||||
ID: "check:topaz-allow",
|
||||
Subject: api.SubjectRef{ID: "user:alice", Type: api.SubjectTypeHuman},
|
||||
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 != "topaz_directory_allow" {
|
||||
t.Fatalf("decision = %s/%s; want allow/topaz_directory_allow", got.Effect, got.Reason)
|
||||
}
|
||||
if got.Provenance.Evaluator != topaz.EvaluatorName || got.Provenance.Mode != topaz.DelegatedMode {
|
||||
t.Fatalf("provenance = %+v; want delegated Topaz", got.Provenance)
|
||||
}
|
||||
if got.Provenance.DirectoryETag != "etag:rel-42" {
|
||||
t.Fatalf("DirectoryETag = %q", got.Provenance.DirectoryETag)
|
||||
}
|
||||
if got.Diagnostics["topaz_object_type"] != "document" || got.Diagnostics["topaz_subject_type"] != "user" {
|
||||
t.Fatalf("diagnostics = %+v; want Topaz check shape", got.Diagnostics)
|
||||
}
|
||||
if got.Caring == nil || got.Caring.Descriptor == nil {
|
||||
t.Fatal("missing CARING descriptor")
|
||||
}
|
||||
if len(got.Caring.RestrictionsEvaluated) != 1 || got.Caring.RestrictionsEvaluated[0] != api.RestrictionExportBlocked {
|
||||
t.Fatalf("restrictions = %+v; want ExportBlocked", got.Caring.RestrictionsEvaluated)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAdapterCheckFailsClosedOnUnavailableTopaz(t *testing.T) {
|
||||
client := &fakeClient{
|
||||
checkErr: topaz.NewBackendError(topaz.FailureUnavailable, "check", errors.New("connection refused")),
|
||||
}
|
||||
adapter := newAdapter(t, client)
|
||||
|
||||
got, err := adapter.Check(context.Background(), api.CheckRequest{
|
||||
ID: "check:topaz-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 != "topaz_unavailable" {
|
||||
t.Fatalf("decision = %s/%s; want deny/topaz_unavailable", got.Effect, got.Reason)
|
||||
}
|
||||
if got.Diagnostics["topaz_failure"] != "unavailable" {
|
||||
t.Fatalf("diagnostics = %+v; want unavailable failure", got.Diagnostics)
|
||||
}
|
||||
if got.Caring == nil || len(got.Caring.ConformanceFindings) == 0 {
|
||||
t.Fatal("fail-closed decision should carry CARING conformance finding")
|
||||
}
|
||||
if got.Caring.ConformanceFindings[0].Code != "TOPAZ-UNAVAILABLE" {
|
||||
t.Fatalf("finding = %+v", got.Caring.ConformanceFindings[0])
|
||||
}
|
||||
}
|
||||
|
||||
func TestImportDirectoryWritesObjectsAndRelations(t *testing.T) {
|
||||
client := &fakeClient{}
|
||||
adapter := newAdapter(t, client)
|
||||
|
||||
report, err := adapter.ImportDirectory(context.Background(), loadRegistry(t).Snapshot())
|
||||
if err != nil {
|
||||
t.Fatalf("ImportDirectory: %v", err)
|
||||
}
|
||||
|
||||
if report.ObjectsWritten != len(client.objects) {
|
||||
t.Fatalf("ObjectsWritten = %d; client wrote %d", report.ObjectsWritten, len(client.objects))
|
||||
}
|
||||
if report.RelationsWritten != len(client.relations) {
|
||||
t.Fatalf("RelationsWritten = %d; client wrote %d", report.RelationsWritten, len(client.relations))
|
||||
}
|
||||
assertWrittenRelation(t, client.relations, topaz.DirectoryRelation{
|
||||
ObjectType: "document",
|
||||
ObjectID: "document:internal-note",
|
||||
Relation: "reader",
|
||||
SubjectType: "group",
|
||||
SubjectID: "group:platform-architecture",
|
||||
SubjectRelation: "member",
|
||||
})
|
||||
}
|
||||
|
||||
func TestImportPolicyWritesTopazBundle(t *testing.T) {
|
||||
pkg, err := policy.LoadAndValidateFile(context.Background(), filepath.Join("..", "..", "..", "examples", "caring", "policy_package.md"))
|
||||
if err != nil {
|
||||
t.Fatalf("LoadAndValidateFile: %v", err)
|
||||
}
|
||||
|
||||
client := &fakeClient{bundleSink: topaz.FileBundleSink{Root: t.TempDir()}}
|
||||
adapter := newAdapter(t, client)
|
||||
|
||||
report, err := adapter.ImportPolicy(context.Background(), pkg)
|
||||
if err != nil {
|
||||
t.Fatalf("ImportPolicy: %v", err)
|
||||
}
|
||||
if report.BundleID != "markitect.documents.internal-read" || report.Version != "v1" {
|
||||
t.Fatalf("report = %+v", report)
|
||||
}
|
||||
|
||||
modulePath := filepath.Join(client.bundleSink.Root, "policy", "flexauth", "markitect", "documents.rego")
|
||||
data, err := os.ReadFile(modulePath)
|
||||
if err != nil {
|
||||
t.Fatalf("read module: %v", err)
|
||||
}
|
||||
if string(data) != pkg.RegoModule {
|
||||
t.Fatal("policy module changed during Topaz import")
|
||||
}
|
||||
manifest, err := os.ReadFile(filepath.Join(client.bundleSink.Root, ".manifest"))
|
||||
if err != nil {
|
||||
t.Fatalf("read manifest: %v", err)
|
||||
}
|
||||
if !contains(string(manifest), "flexauth/markitect/documents") {
|
||||
t.Fatalf(".manifest = %s", manifest)
|
||||
}
|
||||
}
|
||||
|
||||
func loadRegistry(t *testing.T) *registry.Store {
|
||||
t.Helper()
|
||||
|
||||
store, err := registry.LoadFile(filepath.Join("..", "..", "..", "examples", "caring", "registry_snapshot.json"))
|
||||
if err != nil {
|
||||
t.Fatalf("LoadFile: %v", err)
|
||||
}
|
||||
return store
|
||||
}
|
||||
|
||||
func newAdapter(t *testing.T, client *fakeClient) *topaz.Adapter {
|
||||
t.Helper()
|
||||
|
||||
adapter, err := topaz.New(client, topaz.Options{
|
||||
PolicyPackage: "markitect.documents.internal-read",
|
||||
PolicyVersion: "v1",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("New: %v", err)
|
||||
}
|
||||
return adapter
|
||||
}
|
||||
|
||||
func caringDescriptor() *api.CaringAccessDescriptor {
|
||||
return &api.CaringAccessDescriptor{
|
||||
ID: "descriptor:tenant-alpha-document-reader",
|
||||
Profile: api.CaringProfileCaring040RC2,
|
||||
SubjectType: api.SubjectTypeGroup,
|
||||
OrganizationRelation: api.OrganizationRelationCustomer,
|
||||
CanonicalRole: api.CanonicalRoleDoer,
|
||||
Scope: api.CaringScope{
|
||||
Level: api.ScopeLevelResource,
|
||||
ID: "document:internal-note",
|
||||
Tenant: "tenant:alpha",
|
||||
Resource: "document:internal-note",
|
||||
},
|
||||
Planes: []api.Plane{api.PlaneData},
|
||||
Capabilities: []api.Capability{api.CapabilityView},
|
||||
ExposureModes: []api.ExposureMode{api.ExposureModeMasked, api.ExposureModePlaintext},
|
||||
Restrictions: []api.Restriction{api.RestrictionExportBlocked},
|
||||
}
|
||||
}
|
||||
|
||||
func assertObject(t *testing.T, snapshot topaz.DirectorySnapshot, objectType, objectID string) {
|
||||
t.Helper()
|
||||
for _, object := range snapshot.Objects {
|
||||
if object.Type == objectType && object.ID == objectID {
|
||||
return
|
||||
}
|
||||
}
|
||||
t.Fatalf("object %s:%s not found in %+v", objectType, objectID, snapshot.Objects)
|
||||
}
|
||||
|
||||
func assertRelation(t *testing.T, snapshot topaz.DirectorySnapshot, want topaz.DirectoryRelation) {
|
||||
t.Helper()
|
||||
assertWrittenRelation(t, snapshot.Relations, want)
|
||||
}
|
||||
|
||||
func assertWrittenRelation(t *testing.T, relations []topaz.DirectoryRelation, want topaz.DirectoryRelation) {
|
||||
t.Helper()
|
||||
for _, relation := range relations {
|
||||
if relation == want {
|
||||
return
|
||||
}
|
||||
}
|
||||
t.Fatalf("relation %+v not found in %+v", want, relations)
|
||||
}
|
||||
|
||||
func contains(value, needle string) bool {
|
||||
return len(needle) == 0 || (len(value) >= len(needle) && stringsContains(value, needle))
|
||||
}
|
||||
|
||||
func stringsContains(value, needle string) bool {
|
||||
for i := 0; i+len(needle) <= len(value); i++ {
|
||||
if value[i:i+len(needle)] == needle {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
type fakeClient struct {
|
||||
checkResult topaz.CheckResult
|
||||
checkErr error
|
||||
objects []topaz.DirectoryObject
|
||||
relations []topaz.DirectoryRelation
|
||||
bundleSink topaz.FileBundleSink
|
||||
}
|
||||
|
||||
func (c *fakeClient) Check(context.Context, topaz.DirectoryCheckRequest) (topaz.CheckResult, error) {
|
||||
return c.checkResult, c.checkErr
|
||||
}
|
||||
|
||||
func (c *fakeClient) PutObject(_ context.Context, object topaz.DirectoryObject) error {
|
||||
c.objects = append(c.objects, object)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *fakeClient) PutRelation(_ context.Context, relation topaz.DirectoryRelation) (topaz.WriteResult, error) {
|
||||
c.relations = append(c.relations, relation)
|
||||
return topaz.WriteResult{ETag: "etag:last"}, nil
|
||||
}
|
||||
|
||||
func (c *fakeClient) PutManifest(context.Context, []byte) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *fakeClient) PutPolicyBundle(ctx context.Context, bundle topaz.PolicyBundle) error {
|
||||
if c.bundleSink.Root == "" {
|
||||
return nil
|
||||
}
|
||||
return c.bundleSink.PutPolicyBundle(ctx, bundle)
|
||||
}
|
||||
|
||||
func (c *fakeClient) Health(context.Context) error {
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user