generated from coulomb/repo-seed
Add local decision log
This commit is contained in:
@@ -23,6 +23,12 @@ type Engine struct {
|
||||
policy *policy.Package
|
||||
mu sync.RWMutex
|
||||
history map[string]api.DecisionEnvelope
|
||||
log DecisionRecorder
|
||||
}
|
||||
|
||||
// DecisionRecorder persists decision envelopes.
|
||||
type DecisionRecorder interface {
|
||||
Append(api.DecisionEnvelope) error
|
||||
}
|
||||
|
||||
// ListAllowedRequest describes a deterministic list_allowed call.
|
||||
@@ -69,6 +75,13 @@ func NewEngine(store *registry.Store, policyPackage *policy.Package) (*Engine, e
|
||||
}, nil
|
||||
}
|
||||
|
||||
// SetDecisionLog attaches a local decision recorder to the engine.
|
||||
func (e *Engine) SetDecisionLog(log DecisionRecorder) {
|
||||
e.mu.Lock()
|
||||
defer e.mu.Unlock()
|
||||
e.log = log
|
||||
}
|
||||
|
||||
// Check evaluates one subject/action/resource request.
|
||||
func (e *Engine) Check(ctx context.Context, request api.CheckRequest) (api.DecisionEnvelope, error) {
|
||||
normalized, facts := e.normalizeRequest(request)
|
||||
@@ -79,7 +92,9 @@ func (e *Engine) Check(ctx context.Context, request api.CheckRequest) (api.Decis
|
||||
}
|
||||
|
||||
decision := e.envelope(normalized, expectation, facts)
|
||||
e.recordDecision(decision)
|
||||
if err := e.recordDecision(decision); err != nil {
|
||||
return api.DecisionEnvelope{}, err
|
||||
}
|
||||
return decision, nil
|
||||
}
|
||||
|
||||
@@ -286,10 +301,14 @@ func (e *Engine) envelope(request api.CheckRequest, expectation api.DecisionExpe
|
||||
return envelope
|
||||
}
|
||||
|
||||
func (e *Engine) recordDecision(decision api.DecisionEnvelope) {
|
||||
func (e *Engine) recordDecision(decision api.DecisionEnvelope) error {
|
||||
e.mu.Lock()
|
||||
defer e.mu.Unlock()
|
||||
e.history[decision.ID] = decision
|
||||
if e.log != nil {
|
||||
return e.log.Append(decision)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *Engine) caringDecisionMetadata(descriptor *api.CaringAccessDescriptor, findings []api.CaringConformanceFinding) *api.CaringDecisionMetadata {
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
|
||||
"github.com/netkingdom/flex-auth/internal/audit"
|
||||
"github.com/netkingdom/flex-auth/internal/decision"
|
||||
"github.com/netkingdom/flex-auth/internal/policy"
|
||||
"github.com/netkingdom/flex-auth/internal/registry"
|
||||
@@ -188,6 +189,40 @@ func TestExplainUsesRecordedDecision(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheckWritesDecisionLog(t *testing.T) {
|
||||
engine := newTestEngine(t)
|
||||
log := audit.NewJSONLDecisionLog(filepath.Join(t.TempDir(), "decisions.jsonl"))
|
||||
engine.SetDecisionLog(log)
|
||||
|
||||
got, err := engine.Check(context.Background(), api.CheckRequest{
|
||||
ID: "check:logged-deny",
|
||||
Subject: api.SubjectRef{ID: "user:alice"},
|
||||
Action: "read",
|
||||
Resource: api.ResourceRef{
|
||||
ID: "document:missing",
|
||||
Type: "document",
|
||||
System: "markitect-tool",
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("Check: %v", err)
|
||||
}
|
||||
if got.Effect != api.DecisionEffectDeny {
|
||||
t.Fatalf("got.Effect = %q; want deny", got.Effect)
|
||||
}
|
||||
|
||||
decisions, err := log.ReadAll()
|
||||
if err != nil {
|
||||
t.Fatalf("ReadAll: %v", err)
|
||||
}
|
||||
if len(decisions) != 1 {
|
||||
t.Fatalf("len(decisions) = %d; want 1", len(decisions))
|
||||
}
|
||||
if decisions[0].ID != got.ID || decisions[0].Effect != api.DecisionEffectDeny {
|
||||
t.Fatalf("logged decision = %+v; want logged deny %s", decisions[0], got.ID)
|
||||
}
|
||||
}
|
||||
|
||||
func newTestEngine(t *testing.T) *decision.Engine {
|
||||
t.Helper()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user