ZDDC/zddc/internal/policy/principal_test.go
2026-06-11 13:32:31 -05:00

292 lines
9.4 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package policy
import (
"context"
"testing"
"codeberg.org/VARASYS/ZDDC/zddc/internal/zddc"
)
// TestAllowActionFromChainP_TruthTable pins the principal-aware decider
// across the full {elevated × admin-at-level-N × action} cross-product.
// This is the single bypass site that consolidates every former
// scattered IsAdmin/IsSubtreeAdmin/CanEditZddc check in handler code,
// so its semantics must be locked in by an exhaustive table.
//
// Invariants pinned:
//
// 1. Admin bypass requires BOTH (Email in admins:) AND Elevated.
// - In admins + elevated → bypass (any action returns true)
// - In admins + un-elevated → no bypass (falls through to ACL)
// - Not in admins + elevated → no bypass
// - Empty email + elevated → no bypass (gate() rejects empty)
//
// 2. Bypass is action-agnostic: ActionRead, ActionWrite, ActionCreate,
// ActionDelete, ActionAdmin all behave the same way under bypass.
//
// 3. Admin authority at ANY level on the chain confers bypass
// (root admin gets bypass even on deep paths; subtree admin
// declared at level N gets bypass for level ≥ N).
//
// 4. With no bypass, the cascade ACL governs:
// - rwcd grant → ActionRead/Write/Create/Delete succeed, ActionAdmin denied
// - no grant + has_any_file → all actions denied
// - empty chain → all actions allowed (public default)
func TestAllowActionFromChainP_TruthTable(t *testing.T) {
// Chain shape used throughout: root admins:[root@example.com] +
// level 1 admins:[sub@example.com] + level 1 ACL allowing
// staff@example.com rwcd.
chain := zddc.PolicyChain{
HasAnyFile: true,
Levels: []zddc.ZddcFile{
{Admins: []string{"root@example.com"}},
{
Admins: []string{"sub@example.com"},
ACL: zddc.ACLRules{Permissions: map[string]string{
"staff@example.com": "rwcd",
}},
},
},
}
type want struct {
read, write, create, deleteV, adminV bool
}
allActions := want{true, true, true, true, true}
noAdmin := want{true, true, true, true, false} // staff has rwcd but no `a`
configOnly := want{adminV: true} // standing config-edit, nothing else
cases := []struct {
name string
email string
elevated bool
want want
}{
// ─── BYPASS PATH ────────────────────────────────────────────
{
name: "root admin elevated → bypass on every action",
email: "root@example.com",
elevated: true,
want: allActions,
},
{
name: "subtree admin elevated → bypass on every action",
email: "sub@example.com",
elevated: true,
want: allActions,
},
// ─── ELEVATION GATE ─────────────────────────────────────────
// An admin who hasn't elevated gets the WORM/destructive bypass
// on NOTHING — but config-edit (the `a` verb) is a STANDING
// permission, so ActionAdmin is allowed while r/w/c/d (no ACL
// grant in this fixture) stay denied. Elevation is additive.
{
name: "root admin NOT elevated → standing config-edit only",
email: "root@example.com",
elevated: false,
want: configOnly,
},
{
name: "subtree admin NOT elevated → standing config-edit only",
email: "sub@example.com",
elevated: false,
want: configOnly,
},
// ─── NON-ADMIN PATHS ────────────────────────────────────────
{
name: "non-admin with rwcd grant → ACL governs, admin denied",
email: "staff@example.com",
elevated: false,
want: noAdmin,
},
{
name: "non-admin elevated → elevation alone confers nothing",
email: "staff@example.com",
elevated: true,
want: noAdmin,
},
{
name: "stranger denied across the board",
email: "rando@example.com",
elevated: false,
want: want{},
},
{
name: "stranger elevated still denied",
email: "rando@example.com",
elevated: true,
want: want{},
},
// ─── ANONYMOUS / DEGENERATE ─────────────────────────────────
{
name: "empty email + elevated → gate rejects, no bypass",
email: "",
elevated: true,
want: want{},
},
{
name: "empty email + not elevated → denied",
email: "",
elevated: false,
want: want{},
},
}
d := &InternalDecider{}
ctx := context.Background()
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
p := zddc.Principal{Email: tc.email, Elevated: tc.elevated}
check := func(action string, want bool) {
t.Helper()
got, err := AllowActionFromChainP(ctx, d, chain, p, "/sub/file", action)
if err != nil {
t.Fatalf("%s: unexpected error: %v", action, err)
}
if got != want {
t.Errorf("%s: got %v, want %v", action, got, want)
}
}
check(ActionRead, tc.want.read)
check(ActionWrite, tc.want.write)
check(ActionCreate, tc.want.create)
check(ActionDelete, tc.want.deleteV)
check(ActionAdmin, tc.want.adminV)
})
}
}
// TestAllowActionFromChainP_AdminScopeDepth: admin authority at the
// root level cascades to every depth; subtree admin authority declared
// at level N applies only when level N is on the queried chain. The
// decider doesn't synthesise admin authority — it derives it from
// IsAdminForChain, which walks the chain it was given.
func TestAllowActionFromChainP_AdminScopeDepth(t *testing.T) {
rootOnly := zddc.PolicyChain{
HasAnyFile: true,
Levels: []zddc.ZddcFile{
{Admins: []string{"root@example.com"}},
},
}
rootPlusProject := zddc.PolicyChain{
HasAnyFile: true,
Levels: []zddc.ZddcFile{
{Admins: []string{"root@example.com"}},
{Admins: []string{"alice@example.com"}},
},
}
siblingChain := zddc.PolicyChain{
HasAnyFile: true,
Levels: []zddc.ZddcFile{
{Admins: []string{"root@example.com"}},
// Sibling project — alice is NOT in this chain's admins.
{Admins: []string{"bob@example.com"}},
},
}
d := &InternalDecider{}
ctx := context.Background()
cases := []struct {
name string
chain zddc.PolicyChain
email string
path string
wantPut bool
}{
{
name: "root admin reaches a root-only path",
chain: rootOnly,
email: "root@example.com",
path: "/file",
wantPut: true,
},
{
name: "root admin reaches a deep path",
chain: rootPlusProject,
email: "root@example.com",
path: "/Project-A/file",
wantPut: true,
},
{
name: "subtree admin reaches their own subtree",
chain: rootPlusProject,
email: "alice@example.com",
path: "/Project-A/file",
wantPut: true,
},
{
name: "subtree admin does NOT reach a sibling subtree",
chain: siblingChain,
email: "alice@example.com",
path: "/Project-B/file",
wantPut: false,
},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
p := zddc.Principal{Email: tc.email, Elevated: true}
got, _ := AllowActionFromChainP(ctx, d, tc.chain, p, tc.path, ActionWrite)
if got != tc.wantPut {
t.Errorf("AllowActionFromChainP write: got %v, want %v", got, tc.wantPut)
}
})
}
}
// TestAllowActionFromChainP_BypassWinsOverWorm: an elevated admin's
// bypass fires before WORM evaluation, so a mis-filed document under
// received/ or issued/ can still be corrected. This is the explicit
// human escape hatch documented in the policy package comment.
func TestAllowActionFromChainP_BypassWinsOverWorm(t *testing.T) {
trueP := true
chain := zddc.PolicyChain{
HasAnyFile: true,
Levels: []zddc.ZddcFile{
{Admins: []string{"root@example.com"}},
{
// WORM zone (received/issued style). Without admin bypass,
// every write would be stripped.
Worm: []string{"_doc_controller"},
ACL: zddc.ACLRules{Inherit: &trueP},
},
},
}
d := &InternalDecider{}
ctx := context.Background()
p := zddc.Principal{Email: "root@example.com", Elevated: true}
for _, action := range []string{ActionRead, ActionWrite, ActionCreate, ActionDelete, ActionAdmin} {
t.Run("elevated admin in WORM zone — "+action, func(t *testing.T) {
got, _ := AllowActionFromChainP(ctx, d, chain, p, "/received/x", action)
if !got {
t.Errorf("elevated admin %s denied inside WORM zone", action)
}
})
}
// Negative control: same principal un-elevated must NOT bypass WORM for
// DATA ops. Write/Delete (and Create) of records stay clamped — those
// are the destructive overrides elevation exists for.
pUn := zddc.Principal{Email: "root@example.com", Elevated: false}
for _, action := range []string{ActionWrite, ActionDelete} {
t.Run("un-elevated admin in WORM zone — "+action, func(t *testing.T) {
got, _ := AllowActionFromChainP(ctx, d, chain, pUn, "/received/x", action)
if got {
t.Errorf("un-elevated admin %s allowed inside WORM zone (bypass leaked)", action)
}
})
}
// EXCEPTION: ActionAdmin (config-edit) is a STANDING permission and
// transcends the WORM clamp — a subtree admin may fix the .zddc that
// governs a WORM zone without elevating. This grants only VerbA, never
// write/delete of the WORM records themselves (asserted just above).
if got, _ := AllowActionFromChainP(ctx, d, chain, pUn, "/received/.zddc", ActionAdmin); !got {
t.Errorf("un-elevated admin ActionAdmin denied in WORM zone; config-edit should be standing")
}
}