Artifact 3fae6a8f1256c6f9aef136daf1a012397e909e23d8ce552f4f56689678a82939:
- File
internal/harness/capmatrix_test.go
— part of check-in
[3df10d7f78]
at
2026-08-22 16:28:23
on branch fossil-caps
— caps phase 5b: the capability matrix as executable spec
Each row driven twice — the letter alone must open its surface, and every OTHER letter together must not. Router resolves the repo from the request body for ops that carry it as a field (repos/update). (user: dmestas, size: 4200) [annotate] [blame] [check-ins using] [more...]
package harness import ( "fmt" "io" "net/http" "path/filepath" "strings" "testing" "craftdesign.group/quarry/internal/api" "craftdesign.group/quarry/internal/oltp" "craftdesign.group/quarry/internal/ops" ) // capCase is one cell of the capability matrix (docs/rfcs/0004 §4, and // the two DP-security-matrix diagrams): a surface, the letter that // opens it, and how to drive it. type capCase struct { name string // the surface, as the matrix names it letter string // the capability that opens it (one letter, per matrix) method string path string body string } // The matrix as executable spec. Each row is driven twice: once by an // account holding ONLY that letter (must pass) and once by an account // holding every OTHER letter in the table (must fail). The second half // is what makes this a matrix rather than a smoke test — it proves the // gate reads its own letter and not merely "some capability". var capMatrix = []capCase{ {"timeline", "o", "GET", "/api/v1/r/alpha/timeline", ""}, {"tree", "o", "GET", "/api/v1/r/alpha/tree/trunk/", ""}, {"wiki read", "j", "GET", "/api/v1/r/alpha/wiki", ""}, {"ticket read", "r", "GET", "/api/v1/r/alpha/ticket", ""}, {"forum read", "2", "GET", "/api/v1/r/alpha/forum", ""}, {"forum write", "3", "POST", "/api/v1/r/alpha/forum", "title=t&body=b"}, {"ticket file", "n", "POST", "/api/v1/r/alpha/ticket", "title=t&status=Open"}, {"wiki write", "k", "POST", "/api/v1/r/alpha/wiki/Guide", "body=hello"}, {"moderation queue", "5", "GET", "/api/v1/r/alpha/forum-queue", ""}, {"repo settings", "a", "POST", "/api/v1/repos/update", "repo=alpha&description=d"}, } // everyOtherLetter is the union of the matrix's letters minus one — // the "holds everything except the key" account. s and a are excluded // from the union because they are umbrella caps that legitimately open // everything (RFC 0004 §3). func everyOtherLetter(except string) string { var b strings.Builder for _, c := range capMatrix { if c.letter == except || c.letter == "a" { continue } if !strings.Contains(b.String(), c.letter) { b.WriteString(c.letter) } } return b.String() } func TestCapabilityMatrix(t *testing.T) { stores := map[string]string{"alpha": buildStore(t, "matrixseed")} dataDir := t.TempDir() srv := ingestAndServeAuth(t, stores, dataDir, func(repo, user, pass string, need ops.Auth) api.Decision { if user == "" { return api.Denied } return api.Allowed // capabilities decide from here }) odb, err := oltp.Open(filepath.Join(dataDir, "oltp.db")) if err != nil { t.Fatal(err) } defer odb.Close() // Categories blanked: this test is about EXPLICIT caps, and the // stock categories would hand every account half the matrix. for _, cat := range []string{"nobody", "anonymous", "reader", "developer"} { if err := odb.SetCategoryCaps("alpha", cat, ""); err != nil { t.Fatal(err) } } account := func(name, caps string) { t.Helper() if err := odb.AddAccount(name, "pw12345678", false); err != nil { t.Fatal(err) } if err := odb.SetCaps(name, "alpha", caps); err != nil { t.Fatal(err) } } call := func(user string, c capCase) int { var r io.Reader if c.body != "" { r = strings.NewReader(c.body) } req, _ := http.NewRequest(c.method, srv.URL+c.path, r) req.SetBasicAuth(user, "pw12345678") if c.body != "" { req.Header.Set("Content-Type", "application/x-www-form-urlencoded") } resp, err := srv.Client().Do(req) if err != nil { t.Fatal(err) } resp.Body.Close() return resp.StatusCode } for i, c := range capMatrix { // One pair of accounts per ROW, not per letter: a letter can // open more than one surface. holder := fmt.Sprintf("holds%d", i) account(holder, c.letter) if got := call(holder, c); got == http.StatusForbidden { t.Errorf("%s: cap %q did not open it (403)", c.name, c.letter) } // The negative half: everything except this letter. other := fmt.Sprintf("lacks%d", i) account(other, everyOtherLetter(c.letter)) if got := call(other, c); got != http.StatusForbidden { t.Errorf("%s: opened without %q (status %d, caps %q)", c.name, c.letter, got, everyOtherLetter(c.letter)) } } }