package handler import ( "bytes" "context" "net/http" "net/http/httptest" "net/url" "os" "path/filepath" "testing" "codeberg.org/VARASYS/ZDDC/zddc/internal/config" "codeberg.org/VARASYS/ZDDC/zddc/internal/zddc" ) // TestPutToIssuedAsUnelevatedNonAdminUserDenied reproduces the bitnest // observation: an un-elevated user@bitnest.cc was able to PUT a markdown // file inside archive//issued// even though the // embedded defaults declare issued/ as WORM and the user is not in the // document_controller role. Expectation: 403. If this test passes (i.e. // 403), the bug is somewhere outside this path; if it fails (200 or // similar), we've reproduced the bypass. func TestPutToIssuedAsUnelevatedNonAdminUserDenied(t *testing.T) { root := t.TempDir() // Mirror bitnest's current root .zddc shape: user@bitnest.cc is in // admins (potential admin) but un-elevated — should NOT have admin // authority. Roles populate document_controller and project_team // (which matches *@bitnest.cc → user@bitnest.cc gets project_team:r // at project level via the embedded defaults). mustWriteHelper(t, filepath.Join(root, ".zddc"), "admins:\n - user@bitnest.cc\n"+ "roles:\n"+ " document_controller:\n members: [alice@example.com, bob@example.com]\n"+ " project_team:\n members: [\"*@example.com\", \"*@bitnest.cc\"]\n") // Materialise the exact path shape from the bitnest log entry. issuedDir := filepath.Join(root, "Project-1/archive/PartyA/issued/2025-09-21_A-FAC2-PM-DRW-0377 (RSB) - Test") if err := os.MkdirAll(issuedDir, 0o755); err != nil { t.Fatalf("mkdir issued: %v", err) } target := filepath.Join(issuedDir, "A-FAC1-EL-SPC-0469_0A (IFR) - Test.md") if err := os.WriteFile(target, []byte("# original\n"), 0o644); err != nil { t.Fatalf("seed target: %v", err) } zddc.InvalidateCache(root) cfg := config.Config{ Root: root, EmailHeader: "X-Auth-Request-Email", MaxWriteBytes: 64 * 1024, } // Construct the PUT exactly as the markdown editor in browse would — // PUT to the file's URL with the modified body. u := &url.URL{Path: "/Project-1/archive/PartyA/issued/2025-09-21_A-FAC2-PM-DRW-0377 (RSB) - Test/A-FAC1-EL-SPC-0469_0A (IFR) - Test.md"} req := httptest.NewRequest(http.MethodPut, u.RequestURI(), bytes.NewReader([]byte("# modified by un-elevated user\n"))) req.Header.Set("Content-Type", "text/markdown") // Critical: emulate ACLMiddleware's effect. user@bitnest.cc, elevation = false. ctx := context.WithValue(req.Context(), EmailKey, "user@bitnest.cc") ctx = context.WithValue(ctx, ElevatedKey, false) req = req.WithContext(ctx) rec := httptest.NewRecorder() ServeFileAPI(cfg, rec, req) if rec.Code == http.StatusOK || rec.Code == http.StatusCreated { t.Errorf("BUG REPRODUCED — un-elevated non-doc-controller wrote to WORM issued/: status=%d body=%q", rec.Code, rec.Body.String()) } else if rec.Code != http.StatusForbidden { t.Errorf("status=%d (want 403); body=%q", rec.Code, rec.Body.String()) } // Bytes-on-disk check too: even if the response says forbidden, the // write must NOT have landed. gotBytes, _ := os.ReadFile(target) if string(gotBytes) != "# original\n" { t.Errorf("file bytes mutated: got %q, want original", string(gotBytes)) } }