Four targeted test suites that pin the invariants exercised by the
preceding audit refactor. Closes the coverage gaps identified after the
admin-decider consolidation and the .zddc write-path fix.
internal/policy/principal_test.go (NEW)
TestAllowActionFromChainP_TruthTable — 11 cases × 5 actions = 55
assertions covering every (elevated × admin-at-level × action)
combination. Pins the IsActiveAdmin short-circuit: bypass requires
BOTH (in admins) AND Elevated; elevation alone confers nothing;
empty email never matches.
TestAllowActionFromChainP_AdminScopeDepth — root admin reaches every
path; subtree admin matches in their own subtree; subtree admin
does NOT match in a sibling subtree (the chain doesn't carry
sibling admins lists).
TestAllowActionFromChainP_BypassWinsOverWorm — elevated admin
escape hatch in WORM zones, plus the negative control that an
un-elevated admin does NOT bypass WORM.
internal/handler/auth_invariants_test.go (appended)
TestInvariant_ZddcPutMatrix — 16 sub-cases across (root / project /
subtree .zddc) × (root admin / subtree admin / non-admin /
anonymous) × (elevated / un-elevated). Locks down which principal
can PUT which .zddc.
TestInvariant_ZddcDeleteMatrix — 5 DELETE cases.
TestInvariant_UnelevatedAdminNoSilentBypass — 14 anti-bypass probes:
every (admin-flavour × probe-path) tuple where an un-elevated
admin must 403. Single bypass leak → loud test failure.
cmd/zddc-server/main_test.go (appended)
TestDispatchZddcWriteRouting — full dispatcher path coverage:
GET/HEAD route to ServeZddcFile (YAML or virtual placeholder);
PUT/DELETE route through the .zddc-leaf carve-out into
ServeFileAPI; intermediate .zddc.d/ segments still 404 at the
guard.
internal/handler/middleware_test.go (appended)
TestAccessLog_ChainAdminLevelAttribution — 7 cases pinning the
forensic record: root admin → chain_admin_level=0, subtree admin
in scope → chain_admin_level=N, subtree admin out of scope → -1,
un-elevated admin → -1, non-admin → -1, anonymous → -1.
Cross-checks active_admin == (chain_admin_level >= 0) so a future
refactor can't desync them.
92 new sub-cases total. Coverage delta on the policy package:
76.1% → 87.2%; AllowActionFromChainP 0% → 100%;
activeAdminForRequest 7% → 68%.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
280 lines
8.6 KiB
Go
280 lines
8.6 KiB
Go
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`
|
||
|
||
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 MUST be treated as a normal
|
||
// user. They don't carry any baseline ACL grant in this
|
||
// fixture, so every action is denied.
|
||
{
|
||
name: "root admin NOT elevated → no bypass, no ACL grant → all denied",
|
||
email: "root@example.com",
|
||
elevated: false,
|
||
want: want{},
|
||
},
|
||
{
|
||
name: "subtree admin NOT elevated → no bypass, no ACL grant → all denied",
|
||
email: "sub@example.com",
|
||
elevated: false,
|
||
want: want{},
|
||
},
|
||
|
||
// ─── 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.
|
||
pUn := zddc.Principal{Email: "root@example.com", Elevated: false}
|
||
for _, action := range []string{ActionWrite, ActionDelete, ActionAdmin} {
|
||
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)
|
||
}
|
||
})
|
||
}
|
||
}
|