package handler import ( "bytes" "context" "net/http" "net/http/httptest" "net/url" "os" "path/filepath" "strings" "testing" "codeberg.org/VARASYS/ZDDC/zddc/internal/config" "codeberg.org/VARASYS/ZDDC/zddc/internal/zddc" ) // auth_invariants_test.go — behavioral lock-in for the admin/elevation/ // WORM invariants. These tests must pass against the CURRENT code before // the consolidation refactor (single bypass site in InternalDecider) so // the refactor can be validated against a green baseline. // // Each test covers one invariant called out in the security audit. The // names are deliberately verbose — when one fails, the failure message // alone tells you which property got broken. // invariantsFixture sets up a synthetic ZDDC root with: // // - admin@example.com — root super-admin // - alice@example.com — subtree admin of Project-1/working (via per-dir // .zddc admins:) — used to test subtree scope // - bob@example.com — document_controller role member (gets WORM cr // on received/ + issued/ via cascade defaults) // - eve@example.com — non-admin, project_team only (read-only across // the project per defaults) // // Plus one file each in working/, issued/, received/ so we can exercise // reads + writes across the cascade. func invariantsFixture(t *testing.T) (config.Config, string) { t.Helper() root := t.TempDir() mustWriteHelper(t, filepath.Join(root, ".zddc"), "admins:\n - admin@example.com\n"+ "roles:\n"+ " document_controller:\n members: [bob@example.com]\n"+ " project_team:\n members: [\"*@example.com\"]\n") for _, d := range []string{ "Project-1/working/eve@example.com", "Project-1/archive/Acme/received/Acme-0042", "Project-1/archive/Acme/issued/2026-05-15_Acme-0099 (IFR) - Test", } { if err := os.MkdirAll(filepath.Join(root, d), 0o755); err != nil { t.Fatalf("mkdir %s: %v", d, err) } } // Subtree-admin grant: alice administers Project-1/working/. mustWriteHelper(t, filepath.Join(root, "Project-1/working/.zddc"), "admins:\n - alice@example.com\n") // Files to act on. mustWriteHelper(t, filepath.Join(root, "Project-1/working/eve@example.com/draft.md"), "# eve's draft\n") mustWriteHelper(t, filepath.Join(root, "Project-1/archive/Acme/received/Acme-0042/Acme-0042_A (RFI) - Test.pdf"), "%PDF-A\n") mustWriteHelper(t, filepath.Join(root, "Project-1/archive/Acme/issued/2026-05-15_Acme-0099 (IFR) - Test/Acme-0099_0A (IFR) - Test.md"), "# issued draft\n") zddc.InvalidateCache(root) return config.Config{ Root: root, EmailHeader: "X-Auth-Request-Email", MaxWriteBytes: 64 * 1024, }, root } // do executes a request with the given email / elevation flag. URL-encoding // is computed from the path so spaces and parens (real ZDDC filenames) // round-trip cleanly. func doReq(cfg config.Config, method, urlPath, email string, elevated bool, body []byte, op string) *httptest.ResponseRecorder { u := &url.URL{Path: urlPath} req := httptest.NewRequest(method, u.RequestURI(), bytes.NewReader(body)) if op != "" { req.Header.Set(headerOp, op) } ctx := context.WithValue(req.Context(), EmailKey, email) ctx = context.WithValue(ctx, ElevatedKey, elevated) req = req.WithContext(ctx) rec := httptest.NewRecorder() ServeFileAPI(cfg, rec, req) return rec } // ── Invariant 1 — Un-elevated admin has no admin authority ──────────────── func TestInvariant_UnelevatedAdminCannotBypassWorm(t *testing.T) { cfg, _ := invariantsFixture(t) target := "/Project-1/archive/Acme/issued/2026-05-15_Acme-0099 (IFR) - Test/Acme-0099_0A (IFR) - Test.md" rec := doReq(cfg, http.MethodPut, target, "admin@example.com", false, []byte("# mutated\n"), "") if rec.Code != http.StatusForbidden { t.Fatalf("un-elevated admin write succeeded: status=%d body=%s", rec.Code, rec.Body.String()) } } func TestInvariant_UnelevatedAdminCannotEditZddc(t *testing.T) { // .zddc edits route through the decider as ActionAdmin. The bypass // for elevated admins fires only when Principal.Elevated is true. // Exercised at the HTTP boundary: a PUT to .zddc from an un-elevated // super-admin must return Forbidden. cfg, _ := invariantsFixture(t) target := "/Project-1/working/.zddc" rec := doReq(cfg, http.MethodPut, target, "admin@example.com", false, []byte("title: mutated\n"), "") if rec.Code != http.StatusForbidden { t.Fatalf("un-elevated admin .zddc write succeeded: status=%d body=%s", rec.Code, rec.Body.String()) } } func TestInvariant_ElevatedAdminCanEditZddc(t *testing.T) { // Positive control: a super-admin who has elevated CAN write any // .zddc. The decider's IsActiveAdmin short-circuit fires in // AllowActionFromChainP and the file API write proceeds. cfg, _ := invariantsFixture(t) target := "/Project-1/working/.zddc" rec := doReq(cfg, http.MethodPut, target, "admin@example.com", true, []byte("title: elevated edit\n"), "") if rec.Code != http.StatusOK && rec.Code != http.StatusCreated { t.Fatalf("elevated admin .zddc write blocked: status=%d body=%s", rec.Code, rec.Body.String()) } } // ── Invariant 2 — Elevated admin can do everything (positive control) ───── func TestInvariant_ElevatedAdminBypassesWorm(t *testing.T) { cfg, _ := invariantsFixture(t) target := "/Project-1/archive/Acme/issued/2026-05-15_Acme-0099 (IFR) - Test/Acme-0099_0A (IFR) - Test.md" rec := doReq(cfg, http.MethodPut, target, "admin@example.com", true, []byte("# fix-mis-filed\n"), "") if rec.Code != http.StatusOK && rec.Code != http.StatusCreated { t.Fatalf("elevated admin write blocked: status=%d body=%s", rec.Code, rec.Body.String()) } } // ── Invariant 3 — Subtree admin scope ────────────────────────────────────── func TestInvariant_ElevatedSubtreeAdminWritesInScope(t *testing.T) { cfg, _ := invariantsFixture(t) target := "/Project-1/working/eve@example.com/draft.md" rec := doReq(cfg, http.MethodPut, target, "alice@example.com", true, []byte("# alice override\n"), "") // alice is subtree admin of Project-1/working/ — should override eve's // fenced auto-own and write through. if rec.Code != http.StatusOK && rec.Code != http.StatusCreated { t.Fatalf("elevated subtree admin write in scope blocked: status=%d body=%s", rec.Code, rec.Body.String()) } } func TestInvariant_ElevatedSubtreeAdminBlockedOutsideScope(t *testing.T) { cfg, _ := invariantsFixture(t) // alice is subtree admin of /Project-1/working/, NOT of /Project-1/archive/. target := "/Project-1/archive/Acme/issued/2026-05-15_Acme-0099 (IFR) - Test/Acme-0099_0A (IFR) - Test.md" rec := doReq(cfg, http.MethodPut, target, "alice@example.com", true, []byte("# out-of-scope\n"), "") if rec.Code != http.StatusForbidden { t.Fatalf("subtree admin escaped scope: status=%d body=%s", rec.Code, rec.Body.String()) } } // ── Invariant 4 — .zddc strict-ancestor self-elevation prevention ───────── // Strict-ancestor was retired — a subtree admin owns their .zddc. // These tests pin the post-change contract: an elevated admin // granted in //.zddc CAN edit that file (add collaborators, // adjust ACLs, even — accidentally — remove themselves). Footgun // is recoverable via super-admin restore. func TestInvariant_SubtreeAdminCanEditOwnSubtreeZddc(t *testing.T) { cfg, _ := invariantsFixture(t) p := zddc.Principal{Email: "alice@example.com", Elevated: true} dir := filepath.Join(cfg.Root, "Project-1/working") chain, err := zddc.EffectivePolicy(cfg.Root, dir) if err != nil { t.Fatalf("EffectivePolicy: %v", err) } if !zddc.IsAdminForChain(chain, p.Email) { t.Fatalf("subtree admin lost authority to edit own .zddc — strict-ancestor wasn't supposed to apply") } } func TestInvariant_SubtreeAdminCanEditDeeperZddc(t *testing.T) { cfg, _ := invariantsFixture(t) p := zddc.Principal{Email: "alice@example.com", Elevated: true} dir := filepath.Join(cfg.Root, "Project-1/working/eve@example.com") chain, err := zddc.EffectivePolicy(cfg.Root, dir) if err != nil { t.Fatalf("EffectivePolicy: %v", err) } if !zddc.IsAdminForChain(chain, p.Email) { t.Fatalf("subtree admin blocked from editing deeper .zddc") } } // ── Invariant 5 — Empty email never matches ──────────────────────────────── func TestInvariant_EmptyEmailHasNoAuthority(t *testing.T) { cfg, _ := invariantsFixture(t) target := "/Project-1/working/eve@example.com/draft.md" rec := doReq(cfg, http.MethodPut, target, "", true, []byte("# anon\n"), "") if rec.Code != http.StatusForbidden && rec.Code != http.StatusUnauthorized { t.Fatalf("empty-email write succeeded: status=%d body=%s", rec.Code, rec.Body.String()) } } // ── Invariant 6 — WORM cr survives for document_controller (no admin) ───── func TestInvariant_DocControllerCanCreateInWormZone(t *testing.T) { cfg, _ := invariantsFixture(t) // bob is a document_controller (per role membership) but NOT an admin. // He must be able to CREATE new files in received// even // without elevation — the WORM cr grant carries. target := "/Project-1/archive/Acme/received/Acme-0042/Acme-0042_B (RFI) - Followup.pdf" rec := doReq(cfg, http.MethodPut, target, "bob@example.com", false, []byte("%PDF-B\n"), "") if rec.Code != http.StatusOK && rec.Code != http.StatusCreated { t.Fatalf("doc_controller blocked from WORM create: status=%d body=%s", rec.Code, rec.Body.String()) } } func TestInvariant_DocControllerCannotOverwriteInWormZone(t *testing.T) { cfg, _ := invariantsFixture(t) // bob can CREATE in WORM but cannot OVERWRITE — the worm strip // removes w/d for everyone, even WORM-listed principals. target := "/Project-1/archive/Acme/received/Acme-0042/Acme-0042_A (RFI) - Test.pdf" rec := doReq(cfg, http.MethodPut, target, "bob@example.com", false, []byte("%PDF-mutated\n"), "") if rec.Code != http.StatusForbidden { t.Fatalf("doc_controller bypassed WORM overwrite-strip: status=%d body=%s", rec.Code, rec.Body.String()) } } // ── Invariant 7 — project_team has read but no write ────────────────────── func TestInvariant_ProjectTeamCanReadCannotWrite(t *testing.T) { cfg, _ := invariantsFixture(t) // eve is project_team (r at project level) and the file lives under // her own working/ home — but she is NOT in any admin list and not // elevated, so writes must be ACL-gated. // // In her own home, eve has auto-own rwcda via the working// // auto-own pattern; the cascade gives her create+write there. So // the right test is a write OUTSIDE her home — into a peer's area // or into archive. target := "/Project-1/archive/Acme/received/Acme-0042/Acme-0042_A (RFI) - Test.pdf" rec := doReq(cfg, http.MethodPut, target, "eve@example.com", false, []byte("# eve overwrite\n"), "") if rec.Code != http.StatusForbidden { t.Fatalf("project_team escaped WORM strip: status=%d body=%s", rec.Code, rec.Body.String()) } } // ── Invariant 8 — Forward-auth endpoint requires admin membership ───────── func TestInvariant_ForwardAuthEndpointGatesOnAdminsList(t *testing.T) { cfg, _ := invariantsFixture(t) for _, tc := range []struct { email string want int why string }{ {"admin@example.com", http.StatusOK, "root admin"}, {"alice@example.com", http.StatusForbidden, "subtree admin only — /.auth/admin gates on ROOT admins:, not subtree"}, {"eve@example.com", http.StatusForbidden, "non-admin"}, {"", http.StatusForbidden, "anonymous"}, } { req := httptest.NewRequest(http.MethodGet, "/.auth/admin", nil) ctx := context.WithValue(req.Context(), EmailKey, tc.email) req = req.WithContext(ctx) rec := httptest.NewRecorder() ServeAuthAdmin(cfg, rec, req) if rec.Code != tc.want { t.Errorf("/.auth/admin for %q (%s): got %d, want %d", tc.email, tc.why, rec.Code, tc.want) } } } // ── Invariant 9 — Profile admin endpoints 404 (not 403) for non-admins ──── func TestInvariant_ProfileAdminEndpointsHideFromNonAdmins(t *testing.T) { // These checks lock in the existence-hiding property: non-admins must // see 404, never 403, so they can't probe which paths exist. t.Skip("requires the profile handler dispatcher entry point; skip until the refactor confirms ServeProfile signature") } // dump prints the rec body when t.Logf would help debugging — used in // failure messages to avoid silently empty 403 cases. func dumpBody(rec *httptest.ResponseRecorder) string { s := rec.Body.String() return strings.TrimSpace(s) }