ZDDC/zddc/internal/policy/principal_test.go
ZDDC bd219afeb7 feat(policy): config-edit is a standing permission, not elevation-gated
Editing a .zddc you administer no longer requires toggling admin mode.
Elevation becomes purely additive — it only adds the WORM/destructive
overrides ("things you otherwise couldn't do"), never a prerequisite for
authority you already hold.

Mechanism: a new zddc.IsConfigEditor(chain, email) reports STANDING
config-edit authority — being a subtree admin (admins: cascade) OR holding
the `a` verb — without the elevation gate. InternalDecider.Allow grants
VerbA on that basis ABOVE the WORM clamp: config is not WORM-protected
data, and VerbA only ever authorises .zddc/.zddc.zip/role mutations, never
write/delete of records (those stay clamped + elevation-gated). The full
WORM/ACL bypass (IsActiveAdmin) is unchanged — still admins: + Elevated.

This flows for free to the client: EffectiveVerbsFromChainP loops
ActionAdmin through the decider, so /.profile/access + cap.has(node,'a')
light up the .zddc form editor with no client change, and ServeZddcFile
already gates raw .zddc reads on directory read ACL (config is visible).

A standing subtree admin can thus rewrite their subtree's policy
(admins:/ACL/roles) un-elevated — bounded to their scope (authority
cascades down only, never up), logged, and unable to touch WORM data or
secrets without elevating. That's "admin of X = owns X's policy."

Tests: new TestStandingConfigEdit (decider matrix incl. WORM-transcending
config-edit + data-write still gated); updated the old "un-elevated admin
cannot edit .zddc" invariants (TruthTable, ZddcPut/DeleteMatrix,
NoSilentBypass now scoped to WORM/out-of-scope, profile PathVerbs) to the
new model. Full suite green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 17:00:54 -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")
}
}