Repoint handler + dispatch tests to the top-level peer layout: register
parties via ssr/<party>.yaml where party_source gates writes; move
workspace paths out from under archive (incoming/working/staging/reviewing
+ mdl/rsk are top-level, archive/<party>/{received,issued} stay WORM);
rewrite SSR create (writes ssr/<party>.yaml, no archive folder) + SSR
rename (registry-only); accept-transmittal source incoming/<party>/<txn>;
plan-review scaffolds top-level reviewing/staging; tablehandler
classifyVirtualTableDir recognizes <project>/<peer>/<party> (depth-3) for
per-party mdl/rsk tables. Full Go suite green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
88 lines
3.6 KiB
Go
88 lines
3.6 KiB
Go
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/<party>/issued/<transmittal>/ 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")
|
|
|
|
// Register the party (party_source: ssr) so the write reaches the WORM
|
|
// check this test exercises rather than the registration gate.
|
|
if err := os.MkdirAll(filepath.Join(root, "Project-1/ssr"), 0o755); err != nil {
|
|
t.Fatalf("mkdir ssr: %v", err)
|
|
}
|
|
if err := os.WriteFile(filepath.Join(root, "Project-1/ssr/PartyA.yaml"), []byte("kind: SSR\n"), 0o644); err != nil {
|
|
t.Fatalf("register party: %v", err)
|
|
}
|
|
// 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))
|
|
}
|
|
}
|