ZDDC/zddc/internal/handler/defaults_matrix_test.go
ZDDC bae8e1f79b test(policy): Layer-2 default-policy matrix — role × path × verb truth table
The executable contract for the shipped defaults (internal/zddc/defaults.zddc.yaml):
~38 cells asserting who-can-do-what across the canonical project folders, routed
through the real decider (InternalDecider: cascade + WORM mask + active-admin
bypass) evaluated at the target's logical parent — the same decision the server
makes. Locks the document-control model so a change to the defaults OR the
engine that resolves them can't silently shift access. Storage-agnostic: if the
defaults later move into a project-root .zddc.zip of per-depth .zddc files, the
test is unchanged (it asserts effective policy, not where the bytes live).

Covers: no-create-at-project-root; DC/team/observer per-peer grants (working/
staging/reviewing/incoming/ssr); team rwc on mdl/rsk; archive WORM (DC
create-once, no write/delete; others read); elevated-admin bypass vs un-elevated
no-bypass; anonymous denied. Complements Layer 1 (engine-follows-policy):
policy.TestInternalDecider_CascadeScenarios + zddc/{acl,roles,worm}_test +
policy/parity_test.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 10:01:29 -05:00

152 lines
7.7 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 handler
// Layer 2 — the SHIPPED DEFAULT POLICY contract.
//
// This is the executable truth table for the embedded defaults
// (internal/zddc/defaults.zddc.yaml): role × canonical-path × verb → allow/deny.
// It pins the document-control access model so a change to the defaults — OR to
// the engine that resolves them — can't silently alter who-can-do-what. (When
// the defaults later move into a project-root .zddc.zip of per-depth .zddc
// files, this test is unchanged: it asserts EFFECTIVE policy, not where the
// bytes live.)
//
// Two layers, deliberately separate:
// - Layer 1 (engine follows whatever policy says): policy.TestInternalDecider_
// CascadeScenarios + internal/zddc/{acl,roles,worm}_test.go (synthetic
// policies) + internal/policy/parity_test.go (InternalDecider ↔ OPA).
// - Layer 2 (the shipped defaults are correct): THIS file.
//
// Decisions go through the same decider the server uses (InternalDecider, which
// applies the cascade + WORM mask + active-admin bypass), evaluated at the
// target's logical parent — mirroring authorizeAction. The HTTP plumbing that
// chooses that path is covered separately by the auth_invariants tests.
import (
"context"
"path/filepath"
"testing"
"codeberg.org/VARASYS/ZDDC/zddc/internal/config"
"codeberg.org/VARASYS/ZDDC/zddc/internal/policy"
"codeberg.org/VARASYS/ZDDC/zddc/internal/zddc"
)
// defaultsMatrixFixture is a minimal operator deployment: it only populates the
// three standard roles (which the embedded defaults ship empty) plus one admin,
// and registers party Acme (party_source: ssr gates the peers). Every grant in
// the matrix below therefore comes from the embedded defaults, not the fixture.
func defaultsMatrixFixture(t *testing.T) config.Config {
t.Helper()
root := t.TempDir()
mustWriteHelper(t, filepath.Join(root, ".zddc"),
"admins:\n - admin@x\n"+
"roles:\n"+
" document_controller:\n members: [dc@x]\n"+
" project_team:\n members: [team@x]\n"+
" observer:\n members: [obs@x]\n")
mustWriteHelper(t, filepath.Join(root, "Proj/ssr/Acme.yaml"), "kind: SSR\n")
zddc.InvalidateCache(root)
return config.Config{Root: root, EmailHeader: "X-Auth-Request-Email", MaxWriteBytes: 64 * 1024}
}
// canDo reports whether <email> (elevated?) may perform <action> on content in
// <dir> — the chain is resolved at <dir> (the logical parent of the child being
// acted on) and routed through the internal decider, exactly as the server's
// authorizeAction does for a create/write/delete/read.
func canDo(t *testing.T, cfg config.Config, email string, elevated bool, dir, action string) bool {
t.Helper()
p := zddc.Principal{Email: email, Elevated: elevated}
chain, err := zddc.EffectivePolicy(cfg.Root, filepath.Join(cfg.Root, filepath.FromSlash(dir)))
if err != nil {
t.Fatalf("EffectivePolicy(%s): %v", dir, err)
}
allowed, _ := policy.AllowActionFromChainP(
context.Background(), &policy.InternalDecider{}, chain, p, "/"+dir+"/probe", action)
return allowed
}
func TestDefaultPolicyMatrix(t *testing.T) {
cfg := defaultsMatrixFixture(t)
const (
R = policy.ActionRead
W = policy.ActionWrite
C = policy.ActionCreate
D = policy.ActionDelete
)
cases := []struct {
note string
who string
elev bool
dir string
action string
want bool
}{
// ── Project root: standard peers only; no create for anyone ──────────
{"team: read project root", "team@x", false, "Proj", R, true},
{"observer: read project root", "obs@x", false, "Proj", R, true},
{"team: NO create at project root", "team@x", false, "Proj", C, false},
{"DC: NO create at project root", "dc@x", false, "Proj", C, false},
// ── working/<party>: DC rwcda, team cr, observer r ───────────────────
{"DC: create in working", "dc@x", false, "Proj/working/Acme", C, true},
{"team: create in working", "team@x", false, "Proj/working/Acme", C, true},
{"team: read working", "team@x", false, "Proj/working/Acme", R, true},
{"observer: read working", "obs@x", false, "Proj/working/Acme", R, true},
{"observer: NO create in working", "obs@x", false, "Proj/working/Acme", C, false},
// nested under working — the path the authorizeAction bug denied
{"DC: create nested in working", "dc@x", false, "Proj/working/Acme/sub", C, true},
{"team: create nested in working", "team@x", false, "Proj/working/Acme/sub", C, true},
// ── staging / reviewing: team cr ─────────────────────────────────────
{"team: create in staging", "team@x", false, "Proj/staging/Acme", C, true},
{"team: create in reviewing", "team@x", false, "Proj/reviewing/Acme", C, true},
// ── incoming: DC rwcd, team read-only ────────────────────────────────
{"DC: create in incoming", "dc@x", false, "Proj/incoming/Acme", C, true},
{"team: NO create in incoming", "team@x", false, "Proj/incoming/Acme", C, false},
{"team: read incoming", "team@x", false, "Proj/incoming/Acme", R, true},
// ── ssr (party registry): DC rwc, team read-only ─────────────────────
{"DC: register party (create in ssr)", "dc@x", false, "Proj/ssr", C, true},
{"team: NO create in ssr", "team@x", false, "Proj/ssr", C, false},
{"team: read ssr", "team@x", false, "Proj/ssr", R, true},
// ── mdl / rsk registers: DC rwcd, team rwc (no delete), observer r ───
{"DC: create mdl row", "dc@x", false, "Proj/mdl/Acme", C, true},
{"DC: delete mdl row", "dc@x", false, "Proj/mdl/Acme", D, true},
{"team: create mdl row", "team@x", false, "Proj/mdl/Acme", C, true},
{"team: edit mdl row", "team@x", false, "Proj/mdl/Acme", W, true},
{"team: NO delete mdl row", "team@x", false, "Proj/mdl/Acme", D, false},
{"observer: NO create mdl row", "obs@x", false, "Proj/mdl/Acme", C, false},
{"team: create rsk row", "team@x", false, "Proj/rsk/Acme", C, true},
{"team: edit rsk row", "team@x", false, "Proj/rsk/Acme", W, true},
{"team: NO delete rsk row", "team@x", false, "Proj/rsk/Acme", D, false},
// ── archive WORM: DC create-once, no write/delete; others read ───────
{"DC: worm-create in received", "dc@x", false, "Proj/archive/Acme/received", C, true},
{"DC: NO write in WORM received", "dc@x", false, "Proj/archive/Acme/received", W, false},
{"DC: NO delete in WORM issued", "dc@x", false, "Proj/archive/Acme/issued", D, false},
{"team: NO create in archive", "team@x", false, "Proj/archive/Acme/issued", C, false},
{"team: read archive", "team@x", false, "Proj/archive/Acme/issued", R, true},
// ── Elevated admin: full bypass (the human escape hatch) ─────────────
{"elevated admin: bypass WORM write", "admin@x", true, "Proj/archive/Acme/issued", W, true},
{"elevated admin: create in working", "admin@x", true, "Proj/working/Acme", C, true},
// ── Un-elevated admin: NO bypass; not in any role → no grant ─────────
{"un-elevated admin: NO WORM bypass", "admin@x", false, "Proj/archive/Acme/issued", W, false},
{"un-elevated admin: NO create in working", "admin@x", false, "Proj/working/Acme", C, false},
// ── Anonymous: nothing (a .zddc exists → no public default) ──────────
{"anon: NO read working", "", false, "Proj/working/Acme", R, false},
{"anon: NO create working", "", false, "Proj/working/Acme", C, false},
}
for _, tc := range cases {
got := canDo(t, cfg, tc.who, tc.elev, tc.dir, tc.action)
if got != tc.want {
t.Errorf("%s — canDo(%q, elevated=%v, %s, %q) = %v, want %v",
tc.note, tc.who, tc.elev, tc.dir, tc.action, got, tc.want)
}
}
}