generated from coulomb/repo-seed
Add rule PDP adapter boundary
This commit is contained in:
269
internal/adapters/rule/adapter_test.go
Normal file
269
internal/adapters/rule/adapter_test.go
Normal file
@@ -0,0 +1,269 @@
|
||||
package rule_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/netkingdom/flex-auth/internal/adapters/rule"
|
||||
"github.com/netkingdom/flex-auth/internal/policy"
|
||||
"github.com/netkingdom/flex-auth/pkg/api"
|
||||
)
|
||||
|
||||
func TestCanonicalInputFromCheck(t *testing.T) {
|
||||
adapter := newAdapter(t, &fakeBackend{})
|
||||
decision, err := adapter.Check(context.Background(), api.CheckRequest{
|
||||
ID: "check:input",
|
||||
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("Check: %v", err)
|
||||
}
|
||||
if decision.Diagnostics["input_seen"] != true {
|
||||
t.Fatalf("backend did not receive canonical input: %+v", decision.Diagnostics)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPolicyArtifactFromPackagePreservesRegoAndFixtures(t *testing.T) {
|
||||
pkg := loadPolicy(t)
|
||||
|
||||
artifact := rule.PolicyArtifactFromPackage(pkg)
|
||||
|
||||
if artifact.ID != "markitect.documents.internal-read" || artifact.Version != "v1" {
|
||||
t.Fatalf("artifact metadata = %+v", artifact)
|
||||
}
|
||||
if artifact.Language != rule.LanguageRego {
|
||||
t.Fatalf("Language = %q", artifact.Language)
|
||||
}
|
||||
if artifact.Module != pkg.RegoModule {
|
||||
t.Fatal("Rego module changed during artifact creation")
|
||||
}
|
||||
if len(artifact.Fixtures) != len(pkg.Fixtures) {
|
||||
t.Fatalf("fixtures = %d; want %d", len(artifact.Fixtures), len(pkg.Fixtures))
|
||||
}
|
||||
if artifact.Caring.Profile != api.CaringProfileCaring040RC2 {
|
||||
t.Fatalf("Caring profile = %q", artifact.Caring.Profile)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAdapterCheckWrapsRuleResult(t *testing.T) {
|
||||
backend := &fakeBackend{
|
||||
result: rule.EvaluationResult{
|
||||
Effect: api.DecisionEffectRedact,
|
||||
Reason: "masked_internal_document",
|
||||
MatchedRule: "rule.mask_internal",
|
||||
PolicyVersion: "v2",
|
||||
Obligations: []api.Obligation{
|
||||
{Type: "mask_fields", Parameters: map[string]any{"fields": []string{"email"}}},
|
||||
},
|
||||
Diagnostics: map[string]any{"backend_trace": "trace-1"},
|
||||
CaringDescriptor: caringDescriptor(),
|
||||
ConformanceFindings: []api.CaringConformanceFinding{
|
||||
{Code: "RULE-MASKED", Severity: "info", Message: "masked internal document"},
|
||||
},
|
||||
},
|
||||
}
|
||||
adapter := newAdapter(t, backend)
|
||||
|
||||
got, err := adapter.Check(context.Background(), api.CheckRequest{
|
||||
ID: "check:redact",
|
||||
Subject: api.SubjectRef{ID: "user:alice"},
|
||||
Action: "read",
|
||||
Resource: api.ResourceRef{ID: "document:internal-note", Type: "document", System: "markitect-tool"},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("Check: %v", err)
|
||||
}
|
||||
|
||||
if got.Effect != api.DecisionEffectRedact || got.Reason != "masked_internal_document" {
|
||||
t.Fatalf("decision = %s/%s", got.Effect, got.Reason)
|
||||
}
|
||||
if got.Provenance.Evaluator != "rule-pdp/opa" || got.MatchedPolicyVersion != "v2" {
|
||||
t.Fatalf("provenance = %+v matched=%s", got.Provenance, got.MatchedPolicyVersion)
|
||||
}
|
||||
if len(got.Obligations) != 1 || got.Obligations[0].Type != "mask_fields" {
|
||||
t.Fatalf("obligations = %+v", got.Obligations)
|
||||
}
|
||||
if got.Caring == nil || got.Caring.Descriptor == nil || got.Caring.ExposureEvent == nil {
|
||||
t.Fatalf("CARING metadata = %+v", got.Caring)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAdapterFailsClosedOnStalePolicy(t *testing.T) {
|
||||
backend := &fakeBackend{
|
||||
err: rule.NewBackendError(rule.FailureStalePolicy, "evaluate", errors.New("policy revision too old")),
|
||||
}
|
||||
adapter := newAdapter(t, backend)
|
||||
|
||||
got, err := adapter.Check(context.Background(), api.CheckRequest{
|
||||
ID: "check:stale",
|
||||
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 != "rule_policy_stale" {
|
||||
t.Fatalf("decision = %s/%s; want stale deny", got.Effect, got.Reason)
|
||||
}
|
||||
if got.Diagnostics["rule_failure"] != "stale_policy" {
|
||||
t.Fatalf("diagnostics = %+v", got.Diagnostics)
|
||||
}
|
||||
if got.Caring.ConformanceFindings[0].Code != "RULE-POLICY-STALE" {
|
||||
t.Fatalf("finding = %+v", got.Caring.ConformanceFindings[0])
|
||||
}
|
||||
}
|
||||
|
||||
func TestBatchCheckPreservesOrder(t *testing.T) {
|
||||
backend := &fakeBackend{
|
||||
batch: []rule.EvaluationResult{
|
||||
{Effect: api.DecisionEffectAllow, Reason: "first"},
|
||||
{Effect: api.DecisionEffectDeny, Reason: "second"},
|
||||
},
|
||||
}
|
||||
adapter := newAdapter(t, backend)
|
||||
|
||||
got, err := adapter.BatchCheck(context.Background(), api.BatchCheckRequest{
|
||||
ID: "batch:rule",
|
||||
Subject: api.SubjectRef{ID: "user:alice"},
|
||||
Action: "read",
|
||||
Resources: []api.ResourceRef{
|
||||
{ID: "document:internal-note", Type: "document", System: "markitect-tool"},
|
||||
{ID: "document:missing", Type: "document", System: "markitect-tool"},
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("BatchCheck: %v", err)
|
||||
}
|
||||
if len(got) != 2 || got[0].Reason != "first" || got[1].Reason != "second" {
|
||||
t.Fatalf("batch = %+v", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEvaluateFixturesComparesExpectations(t *testing.T) {
|
||||
backend := &fakeBackend{
|
||||
result: rule.EvaluationResult{Effect: api.DecisionEffectDeny, Reason: "no_matching_rule"},
|
||||
}
|
||||
adapter := newAdapter(t, backend)
|
||||
|
||||
results := adapter.EvaluateFixtures(context.Background(), []api.PolicyFixture{
|
||||
{
|
||||
ID: "fixture:deny",
|
||||
Request: api.CheckRequest{
|
||||
Subject: api.SubjectRef{ID: "user:bob"},
|
||||
Action: "read",
|
||||
Resource: api.ResourceRef{ID: "document:internal-note", Type: "document", System: "markitect-tool"},
|
||||
},
|
||||
Expect: api.DecisionExpectation{Effect: api.DecisionEffectDeny, Reason: "no_matching_rule"},
|
||||
},
|
||||
})
|
||||
if len(results) != 1 || !results[0].Passed {
|
||||
t.Fatalf("fixture results = %+v", results)
|
||||
}
|
||||
}
|
||||
|
||||
func TestImportPolicyDelegatesArtifact(t *testing.T) {
|
||||
backend := &fakeBackend{}
|
||||
adapter := newAdapter(t, backend)
|
||||
pkg := loadPolicy(t)
|
||||
|
||||
report, err := adapter.ImportPolicy(context.Background(), pkg)
|
||||
if err != nil {
|
||||
t.Fatalf("ImportPolicy: %v", err)
|
||||
}
|
||||
if report.ArtifactID != pkg.Metadata.ID || backend.artifact.Module != pkg.RegoModule {
|
||||
t.Fatalf("report = %+v artifact = %+v", report, backend.artifact)
|
||||
}
|
||||
}
|
||||
|
||||
func newAdapter(t *testing.T, backend *fakeBackend) *rule.Adapter {
|
||||
t.Helper()
|
||||
adapter, err := rule.New(backend, rule.Options{
|
||||
BackendName: "opa",
|
||||
PolicyPackage: "markitect.documents.internal-read",
|
||||
PolicyVersion: "v1",
|
||||
Language: rule.LanguageRego,
|
||||
Caring: api.CaringPolicyMetadata{
|
||||
Profile: api.CaringProfileCaring040RC2,
|
||||
CanonicalRoles: []api.CanonicalRole{api.CanonicalRoleDoer},
|
||||
Planes: []api.Plane{api.PlaneData},
|
||||
Capabilities: []api.Capability{api.CapabilityView},
|
||||
ExposureModes: []api.ExposureMode{api.ExposureModeMasked},
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("New: %v", err)
|
||||
}
|
||||
return adapter
|
||||
}
|
||||
|
||||
func loadPolicy(t *testing.T) *policy.Package {
|
||||
t.Helper()
|
||||
pkg, err := policy.LoadAndValidateFile(context.Background(), filepath.Join("..", "..", "..", "examples", "caring", "policy_package.md"))
|
||||
if err != nil {
|
||||
t.Fatalf("LoadAndValidateFile: %v", err)
|
||||
}
|
||||
return pkg
|
||||
}
|
||||
|
||||
func caringDescriptor() *api.CaringAccessDescriptor {
|
||||
return &api.CaringAccessDescriptor{
|
||||
ID: "descriptor:rule-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},
|
||||
ExposureEvent: api.ExposureEventSupport,
|
||||
}
|
||||
}
|
||||
|
||||
type fakeBackend struct {
|
||||
result rule.EvaluationResult
|
||||
err error
|
||||
batch []rule.EvaluationResult
|
||||
artifact rule.PolicyArtifact
|
||||
}
|
||||
|
||||
func (b *fakeBackend) Evaluate(_ context.Context, request rule.EvaluationRequest) (rule.EvaluationResult, error) {
|
||||
if request.Input["subject"] != nil && b.result.Diagnostics == nil {
|
||||
b.result.Diagnostics = map[string]any{"input_seen": true}
|
||||
}
|
||||
return b.result, b.err
|
||||
}
|
||||
|
||||
func (b *fakeBackend) BatchEvaluate(_ context.Context, requests []rule.EvaluationRequest) ([]rule.EvaluationResult, error) {
|
||||
if b.batch != nil {
|
||||
return b.batch, nil
|
||||
}
|
||||
results := make([]rule.EvaluationResult, len(requests))
|
||||
for i := range results {
|
||||
results[i] = b.result
|
||||
}
|
||||
return results, b.err
|
||||
}
|
||||
|
||||
func (b *fakeBackend) ImportPolicy(_ context.Context, artifact rule.PolicyArtifact) (rule.PolicyImportReport, error) {
|
||||
b.artifact = artifact
|
||||
return rule.PolicyImportReport{
|
||||
ArtifactID: artifact.ID,
|
||||
Version: artifact.Version,
|
||||
Language: artifact.Language,
|
||||
BackendRef: "opa:" + artifact.ID,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (b *fakeBackend) Health(context.Context) error {
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user