test: lock down elevation gate, .zddc write matrix, audit-log attribution

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>
This commit is contained in:
ZDDC 2026-05-18 16:29:43 -05:00
parent f196205622
commit cff840e225
4 changed files with 660 additions and 0 deletions

View file

@ -2,6 +2,7 @@ package main
import ( import (
"archive/zip" "archive/zip"
"bytes"
"context" "context"
"crypto/ed25519" "crypto/ed25519"
"crypto/rand" "crypto/rand"
@ -288,6 +289,99 @@ func TestDispatchRoutesWritesToFileAPI(t *testing.T) {
} }
} }
// TestDispatchZddcWriteRouting pins the dispatcher's .zddc routing:
// GET/HEAD lands on ServeZddcFile (which serves the YAML view or the
// virtual placeholder), and PUT/DELETE/POST falls through past the
// dot-prefix guard into ServeFileAPI. Before the .zddc-leaf carve-out,
// PUT/DELETE 405'd at ServeZddcFile (or 404'd at the dot-prefix guard)
// and the YAML editor's save flow had no live path.
func TestDispatchZddcWriteRouting(t *testing.T) {
root := t.TempDir()
mustWrite(t, filepath.Join(root, ".zddc"),
"admins:\n - admin@example.com\nacl:\n permissions:\n \"*@example.com\": r\n")
mustMkdir(t, filepath.Join(root, "Project-A"))
idx, err := archive.BuildIndex(root)
if err != nil {
t.Fatalf("BuildIndex: %v", err)
}
cfg := config.Config{
Root: root,
IndexPath: ".archive",
EmailHeader: "X-Auth-Request-Email",
MaxWriteBytes: 1 << 20,
}
ring := handler.NewLogRing(10)
withAuth := func(req *http.Request, email string, elevated bool) *http.Request {
ctx := handler.WithEmail(req.Context(), email)
ctx = handler.WithElevation(ctx, elevated)
return req.WithContext(ctx)
}
// GET routes to ServeZddcFile — serves YAML bytes for an authorised reader.
req := withAuth(httptest.NewRequest(http.MethodGet, "/.zddc", nil), "admin@example.com", true)
rec := httptest.NewRecorder()
dispatch(cfg, idx, ring, nil, nil, rec, req)
if rec.Code != http.StatusOK {
t.Fatalf("GET /.zddc: want 200, got %d body=%s", rec.Code, rec.Body.String())
}
if ct := rec.Header().Get("Content-Type"); !strings.HasPrefix(ct, "application/yaml") {
t.Errorf("GET /.zddc Content-Type = %q, want application/yaml*", ct)
}
// PUT must route to ServeFileAPI (not 405 from ServeZddcFile).
body := []byte("admins:\n - admin@example.com\n - extra@example.com\n")
req = withAuth(httptest.NewRequest(http.MethodPut, "/.zddc", bytes.NewReader(body)), "admin@example.com", true)
rec = httptest.NewRecorder()
dispatch(cfg, idx, ring, nil, nil, rec, req)
if rec.Code != http.StatusOK && rec.Code != http.StatusCreated {
t.Fatalf("PUT /.zddc: want 200/201, got %d body=%s", rec.Code, rec.Body.String())
}
// Read back via GET to confirm the write landed.
req = withAuth(httptest.NewRequest(http.MethodGet, "/.zddc", nil), "admin@example.com", true)
rec = httptest.NewRecorder()
dispatch(cfg, idx, ring, nil, nil, rec, req)
if !strings.Contains(rec.Body.String(), "extra@example.com") {
t.Errorf("GET after PUT: body missing PUT bytes; got %q", rec.Body.String())
}
// Project-level .zddc that doesn't exist yet — PUT creates it.
req = withAuth(httptest.NewRequest(http.MethodPut, "/Project-A/.zddc", bytes.NewReader([]byte("title: A\n"))), "admin@example.com", true)
rec = httptest.NewRecorder()
dispatch(cfg, idx, ring, nil, nil, rec, req)
if rec.Code != http.StatusCreated {
t.Fatalf("PUT /Project-A/.zddc: want 201, got %d body=%s", rec.Code, rec.Body.String())
}
// DELETE removes a .zddc.
req = withAuth(httptest.NewRequest(http.MethodDelete, "/Project-A/.zddc", nil), "admin@example.com", true)
rec = httptest.NewRecorder()
dispatch(cfg, idx, ring, nil, nil, rec, req)
if rec.Code != http.StatusNoContent {
t.Fatalf("DELETE /Project-A/.zddc: want 204, got %d body=%s", rec.Code, rec.Body.String())
}
// Non-admin elevated still 403 on PUT — the carve-out only opens
// the path past the segment guard; the decider gates ActionAdmin.
req = withAuth(httptest.NewRequest(http.MethodPut, "/.zddc", bytes.NewReader([]byte("title: probe\n"))), "stranger@example.com", true)
rec = httptest.NewRecorder()
dispatch(cfg, idx, ring, nil, nil, rec, req)
if rec.Code != http.StatusForbidden {
t.Fatalf("PUT /.zddc by stranger: want 403, got %d body=%s", rec.Code, rec.Body.String())
}
// Intermediate .zddc.d segments stay reserved — only the LEAF .zddc
// is carved through. A PUT to /.zddc.d/foo must 404 at the guard.
req = withAuth(httptest.NewRequest(http.MethodPut, "/.zddc.d/something", bytes.NewReader([]byte("x"))), "admin@example.com", true)
rec = httptest.NewRecorder()
dispatch(cfg, idx, ring, nil, nil, rec, req)
if rec.Code != http.StatusNotFound {
t.Fatalf("PUT /.zddc.d/something: want 404 (reserved segment), got %d", rec.Code)
}
}
// TestDispatchArchiveRedirect: any /<project>/<sub>/.../.archive/... is 302'd // TestDispatchArchiveRedirect: any /<project>/<sub>/.../.archive/... is 302'd
// to the canonical /<project>/.archive/... so all tracking-number references // to the canonical /<project>/.archive/... so all tracking-number references
// converge on a single stable URL per (project, tracking) regardless of the // converge on a single stable URL per (project, tracking) regardless of the

View file

@ -282,6 +282,178 @@ func TestInvariant_ForwardAuthEndpointGatesOnAdminsList(t *testing.T) {
} }
} }
// ── Invariant 10 — .zddc write matrix at root / project / subtree ─────────
// TestInvariant_ZddcPutMatrix exercises every (principal × elevation ×
// target) combination for PUT to a .zddc file. The decider's
// IsActiveAdmin short-circuit is the single bypass; this matrix locks
// down that it only fires for an Elevated principal who is named in
// the admins: list of some level on the target's chain.
//
// Targets:
// - /.zddc — root file (root admins: govern)
// - /Project-1/.zddc — project file (no on-disk .zddc;
// write must materialise it; root
// admins still govern via cascade)
// - /Project-1/working/.zddc — subtree file; alice administers
// this subtree via its own admins:
// list (so alice's write doesn't
// require root-admin authority).
//
// Expected status: 200 or 201 on success; 403 on denial; 404 only when
// resolveTargetPath rejects the path (e.g. empty email gets 403 from
// the decider, not 404).
func TestInvariant_ZddcPutMatrix(t *testing.T) {
type principal struct {
email string
elevated bool
}
rootAdminElevated := principal{"admin@example.com", true}
rootAdminUnelevated := principal{"admin@example.com", false}
subtreeAdminElevated := principal{"alice@example.com", true}
subtreeAdminUnelevated := principal{"alice@example.com", false}
nonAdmin := principal{"eve@example.com", true}
anon := principal{"", true}
const (
ok = http.StatusOK
den = http.StatusForbidden
)
cases := []struct {
name string
target string
who principal
want int
}{
// Root .zddc
{"root admin elevated → root .zddc", "/.zddc", rootAdminElevated, ok},
{"root admin un-elevated → root .zddc", "/.zddc", rootAdminUnelevated, den},
{"subtree admin elevated → root .zddc", "/.zddc", subtreeAdminElevated, den},
{"subtree admin un-elevated → root .zddc", "/.zddc", subtreeAdminUnelevated, den},
{"non-admin → root .zddc", "/.zddc", nonAdmin, den},
{"anonymous → root .zddc", "/.zddc", anon, den},
// Project .zddc (no on-disk file yet — PUT creates it)
{"root admin elevated → project .zddc", "/Project-1/.zddc", rootAdminElevated, http.StatusCreated},
{"root admin un-elevated → project .zddc", "/Project-1/.zddc", rootAdminUnelevated, den},
{"subtree admin elevated (out-of-scope) → project .zddc", "/Project-1/.zddc", subtreeAdminElevated, den},
{"non-admin → project .zddc", "/Project-1/.zddc", nonAdmin, den},
// Subtree .zddc (alice administers this subtree)
{"root admin elevated → subtree .zddc", "/Project-1/working/.zddc", rootAdminElevated, ok},
{"root admin un-elevated → subtree .zddc", "/Project-1/working/.zddc", rootAdminUnelevated, den},
{"subtree admin elevated → subtree .zddc", "/Project-1/working/.zddc", subtreeAdminElevated, ok},
{"subtree admin un-elevated → subtree .zddc", "/Project-1/working/.zddc", subtreeAdminUnelevated, den},
{"non-admin → subtree .zddc", "/Project-1/working/.zddc", nonAdmin, den},
{"anonymous → subtree .zddc", "/Project-1/working/.zddc", anon, den},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
cfg, _ := invariantsFixture(t)
body := []byte("title: matrix probe\n")
rec := doReq(cfg, http.MethodPut, tc.target, tc.who.email, tc.who.elevated, body, "")
if tc.want == den {
if rec.Code != http.StatusForbidden && rec.Code != http.StatusUnauthorized {
t.Fatalf("want denial, got %d body=%s", rec.Code, dumpBody(rec))
}
} else if rec.Code != tc.want {
t.Fatalf("want %d, got %d body=%s", tc.want, rec.Code, dumpBody(rec))
}
})
}
}
// TestInvariant_ZddcDeleteMatrix mirrors ZddcPutMatrix for DELETE. The
// project-level .zddc target is dropped (no on-disk file → 404 lives
// outside the auth surface). The cases that remain pin: only an
// elevated admin with authority over the .zddc's directory can drop
// the file.
func TestInvariant_ZddcDeleteMatrix(t *testing.T) {
type principal struct {
email string
elevated bool
}
rootAdminElevated := principal{"admin@example.com", true}
rootAdminUnelevated := principal{"admin@example.com", false}
subtreeAdminElevated := principal{"alice@example.com", true}
subtreeAdminUnelevated := principal{"alice@example.com", false}
nonAdmin := principal{"eve@example.com", true}
cases := []struct {
name string
target string
who principal
want int
}{
{"root admin elevated → subtree .zddc", "/Project-1/working/.zddc", rootAdminElevated, http.StatusNoContent},
{"root admin un-elevated → subtree .zddc", "/Project-1/working/.zddc", rootAdminUnelevated, http.StatusForbidden},
{"subtree admin elevated → own .zddc", "/Project-1/working/.zddc", subtreeAdminElevated, http.StatusNoContent},
{"subtree admin un-elevated → own .zddc", "/Project-1/working/.zddc", subtreeAdminUnelevated, http.StatusForbidden},
{"non-admin → subtree .zddc", "/Project-1/working/.zddc", nonAdmin, http.StatusForbidden},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
cfg, _ := invariantsFixture(t)
rec := doReq(cfg, http.MethodDelete, tc.target, tc.who.email, tc.who.elevated, nil, "")
if rec.Code != tc.want {
t.Fatalf("want %d, got %d body=%s", tc.want, rec.Code, dumpBody(rec))
}
})
}
}
// ── Invariant 11 — anti-bypass: un-elevated admin gets nothing extra ──────
// TestInvariant_UnelevatedAdminNoSilentBypass is the anti-test for the
// elevation gate. For every (admin-flavour × action) tuple, an
// un-elevated admin must behave exactly like a non-admin: they may
// only do what an explicit ACL grant permits. The fixture's admin and
// alice both have NO baseline ACL grant outside their admin scope, so
// every action below MUST 403 — any pass indicates a bypass leak.
func TestInvariant_UnelevatedAdminNoSilentBypass(t *testing.T) {
cfg, _ := invariantsFixture(t)
type op struct {
method string
path string
body []byte
op string
}
probes := []op{
// .zddc writes (ActionAdmin)
{http.MethodPut, "/.zddc", []byte("title: x\n"), ""},
{http.MethodPut, "/Project-1/working/.zddc", []byte("title: x\n"), ""},
{http.MethodDelete, "/Project-1/working/.zddc", nil, ""},
// WORM writes (ActionWrite / ActionCreate stripped)
{http.MethodPut, "/Project-1/archive/Acme/issued/2026-05-15_Acme-0099 (IFR) - Test/Acme-0099_0A (IFR) - Test.md", []byte("# mutate\n"), ""},
{http.MethodPut, "/Project-1/archive/Acme/received/Acme-0042/new.pdf", []byte("%PDF\n"), ""},
{http.MethodDelete, "/Project-1/archive/Acme/issued/2026-05-15_Acme-0099 (IFR) - Test/Acme-0099_0A (IFR) - Test.md", nil, ""},
// Regular write into someone else's working/ home (no ACL grant)
{http.MethodPut, "/Project-1/working/eve@example.com/draft.md", []byte("# steal\n"), ""},
}
admins := []struct {
name string
email string
}{
{"root super-admin", "admin@example.com"},
{"subtree admin (alice)", "alice@example.com"},
}
for _, a := range admins {
for _, p := range probes {
t.Run(a.name+" "+p.method+" "+p.path, func(t *testing.T) {
rec := doReq(cfg, p.method, p.path, a.email, false, p.body, p.op)
if rec.Code != http.StatusForbidden {
t.Fatalf("BYPASS LEAK: %s un-elevated reached %s %s with status %d body=%s",
a.email, p.method, p.path, rec.Code, dumpBody(rec))
}
})
}
}
}
// ── Invariant 9 — Profile admin endpoints 404 (not 403) for non-admins ──── // ── Invariant 9 — Profile admin endpoints 404 (not 403) for non-admins ────
func TestInvariant_ProfileAdminEndpointsHideFromNonAdmins(t *testing.T) { func TestInvariant_ProfileAdminEndpointsHideFromNonAdmins(t *testing.T) {

View file

@ -2,13 +2,17 @@ package handler
import ( import (
"bytes" "bytes"
"encoding/json"
"log/slog" "log/slog"
"net/http" "net/http"
"net/http/httptest" "net/http/httptest"
"os"
"path/filepath"
"strings" "strings"
"testing" "testing"
"codeberg.org/VARASYS/ZDDC/zddc/internal/config" "codeberg.org/VARASYS/ZDDC/zddc/internal/config"
"codeberg.org/VARASYS/ZDDC/zddc/internal/zddc"
) )
// TestAccessLogReadsEmailFromACLContext is a regression test for a bug where // TestAccessLogReadsEmailFromACLContext is a regression test for a bug where
@ -136,3 +140,113 @@ func TestAccessLogMiddleware_AuditLoggerReceivesSameFields(t *testing.T) {
t.Errorf("audit log missing status code; got: %s", out) t.Errorf("audit log missing status code; got: %s", out)
} }
} }
// TestAccessLog_ChainAdminLevelAttribution pins the audit-log forensic
// invariant: every request record carries `chain_admin_level` matching
// the .zddc admins: level that conferred admin authority on this
// request, or -1 when no admin authority applies. Forensics use this to
// distinguish a root-admin write from a subtree-admin write from a
// non-admin write — three operationally distinct events that used to
// be conflated under a single `is_admin` boolean.
//
// Truth table the middleware must emit:
//
// (elevated, in admins at level N) → chain_admin_level=N, active_admin=true
// (elevated, in admins at no level) → chain_admin_level=-1, active_admin=false
// (not elevated, in admins) → chain_admin_level=-1, active_admin=false
// (anonymous, elevation flag ignored) → chain_admin_level=-1, active_admin=false
func TestAccessLog_ChainAdminLevelAttribution(t *testing.T) {
// Fixture: root admin at level 0; subtree admin at level 1 (Project-1).
root := t.TempDir()
if err := os.WriteFile(filepath.Join(root, ".zddc"),
[]byte("admins:\n - root@example.com\n"), 0o644); err != nil {
t.Fatalf("write root .zddc: %v", err)
}
if err := os.MkdirAll(filepath.Join(root, "Project-1"), 0o755); err != nil {
t.Fatalf("mkdir Project-1: %v", err)
}
if err := os.WriteFile(filepath.Join(root, "Project-1", ".zddc"),
[]byte("admins:\n - alice@example.com\n"), 0o644); err != nil {
t.Fatalf("write subtree .zddc: %v", err)
}
zddc.InvalidateCache(root)
zddc.InvalidateCache(filepath.Join(root, "Project-1"))
cfg := config.Config{Root: root, EmailHeader: "X-Auth-Request-Email"}
noop := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
})
type record struct {
Email string `json:"email"`
Elevated bool `json:"elevated"`
ActiveAdmin bool `json:"active_admin"`
ChainAdminLevel int `json:"chain_admin_level"`
Path string `json:"path"`
}
parse := func(t *testing.T, buf *bytes.Buffer) record {
t.Helper()
var rec record
if err := json.Unmarshal(buf.Bytes(), &rec); err != nil {
t.Fatalf("audit log not valid JSON: %v; raw=%s", err, buf.String())
}
return rec
}
cases := []struct {
name string
email string
elevate bool
path string
wantLevel int
wantActive bool
}{
{"root admin elevated probing root → level 0", "root@example.com", true, "/", 0, true},
{"root admin elevated probing project → level 0 (walks down chain)", "root@example.com", true, "/Project-1/", 0, true},
{"subtree admin elevated probing own subtree → level 1", "alice@example.com", true, "/Project-1/", 1, true},
{"subtree admin elevated probing root → -1 (out of scope)", "alice@example.com", true, "/", -1, false},
{"root admin un-elevated → -1 (no live authority)", "root@example.com", false, "/", -1, false},
{"non-admin elevated → -1 (elevation alone confers nothing)", "stranger@example.com", true, "/", -1, false},
{"anonymous → -1", "", false, "/", -1, false},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
var buf bytes.Buffer
auditLogger := slog.New(slog.NewJSONHandler(&buf, &slog.HandlerOptions{Level: slog.LevelInfo}))
chain := ACLMiddleware(cfg, nil, nil, AccessLogMiddleware(cfg, auditLogger, noop))
req := httptest.NewRequest(http.MethodGet, tc.path, nil)
if tc.email != "" {
req.Header.Set("X-Auth-Request-Email", tc.email)
}
if tc.elevate {
req.AddCookie(&http.Cookie{Name: "zddc-elevate", Value: "1"})
}
chain.ServeHTTP(httptest.NewRecorder(), req)
rec := parse(t, &buf)
if rec.ChainAdminLevel != tc.wantLevel {
t.Errorf("chain_admin_level = %d, want %d", rec.ChainAdminLevel, tc.wantLevel)
}
if rec.ActiveAdmin != tc.wantActive {
t.Errorf("active_admin = %v, want %v", rec.ActiveAdmin, tc.wantActive)
}
// active_admin is the projection of chain_admin_level — these
// two fields must agree on every record. Asserted explicitly
// so a future refactor that drops the chain_admin_level field
// (or recomputes active_admin from a different source) trips
// this test before the forensic invariant rots.
if rec.ActiveAdmin != (rec.ChainAdminLevel >= 0) {
t.Errorf("active_admin must equal (chain_admin_level >= 0); got active=%v level=%d",
rec.ActiveAdmin, rec.ChainAdminLevel)
}
// Elevation flag must round-trip independently — distinguishes
// "tried to elevate, no authority" (elevated=true, active=false)
// from "didn't elevate" (elevated=false, active=false).
if rec.Elevated != tc.elevate {
t.Errorf("elevated = %v, want %v", rec.Elevated, tc.elevate)
}
})
}
}

View file

@ -0,0 +1,280 @@
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)
}
})
}
}