generated from coulomb/repo-seed
270 lines
8.8 KiB
Go
270 lines
8.8 KiB
Go
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
|
|
}
|