Artifact 6608f098589cab5599a1e7db98573ef5e4f04a140d04791ab45abc9a60032c5e:
- File
internal/oltp/caps_test.go
— part of check-in
[41b085ae25]
at
2026-08-23 00:21:30
on branch trunk
— caps phase 4: imported fossil users sign in, and the store path joins the wire path
Authenticate accepts an imported §6.3 credential once and upgrades the account to argon2, dropping the imported secret; a wrong guess never consumes it. quarry users pull without --url now reads the store's own table (zit#124/#125) — no credential needed for a repo we already serve. Proven end to end against a real fossil: a user who existed only there signed into quarry with their original password. (user: dmestas, size: 9564) [annotate] [blame] [check-ins using]
package oltp import "testing" // The expansion rules are fossil's, kept exactly (RFC 0004 §3): every // visitor holds the nobody set; logging in adds anonymous; u inherits // the reader category, v the developer category — and v does NOT imply // u; a grants everything except s, x, y; s grants everything. func TestEffectiveCapsExpansion(t *testing.T) { db := testDB(t) // Anonymous visitor: stock nobody. eff := db.EffectiveCaps("", "r") for _, c := range []string{"g", "j", "o", "r", "z"} { if !eff.HasAny(c) { t.Fatalf("visitor lacks nobody cap %q (eff=%q)", c, eff) } } if eff.HasAny("h") || eff.HasAny("i") { t.Fatalf("visitor holds login-gated caps: %q", eff) } // A signed-in account with no explicit caps: nobody + anonymous. if err := db.AddAccount("plain", "pw12345678", false); err != nil { t.Fatal(err) } eff = db.EffectiveCaps("plain", "r") for _, c := range []string{"h", "m", "n", "c", "o"} { if !eff.HasAny(c) { t.Fatalf("signed-in lacks %q (eff=%q)", c, eff) } } if eff.HasAny("k") || eff.HasAny("i") { t.Fatalf("signed-in holds category caps unasked: %q", eff) } // u inherits the reader category (stock kptw). if err := db.SetCaps("plain", "r", "u"); err != nil { t.Fatal(err) } eff = db.EffectiveCaps("plain", "r") if !eff.HasAny("k") || !eff.HasAny("w") { t.Fatalf("u did not expand to reader caps: %q", eff) } if eff.HasAny("i") { t.Fatalf("u granted developer caps: %q", eff) } // v inherits developer (stock ei) — and NOT reader. Fossil's trap, // kept deliberately. if err := db.SetCaps("plain", "r", "v"); err != nil { t.Fatal(err) } eff = db.EffectiveCaps("plain", "r") if !eff.HasAny("i") || !eff.HasAny("e") { t.Fatalf("v did not expand to developer caps: %q", eff) } if eff.HasAny("k") || eff.HasAny("w") { t.Fatalf("v implied u — fossil's rule broken: %q", eff) } } func TestAdminAndSetupRules(t *testing.T) { db := testDB(t) if err := db.AddAccount("boss", "pw12345678", false); err != nil { t.Fatal(err) } if err := db.SetCaps("boss", "r", "a"); err != nil { t.Fatal(err) } eff := db.EffectiveCaps("boss", "r") for _, c := range []string{"i", "k", "w", "5", "e"} { if !eff.HasAny(c) { t.Fatalf("a does not grant %q", c) } } for _, c := range []string{"s", "x", "y"} { if eff.HasAny(c) { t.Fatalf("a granted excluded cap %q", c) } } // s grants everything, including the exclusions. if err := db.SetCaps("boss", "r", "s"); err != nil { t.Fatal(err) } eff = db.EffectiveCaps("boss", "r") for _, c := range []string{"a", "s", "x", "y", "i", "5"} { if !eff.HasAny(c) { t.Fatalf("s does not grant %q", c) } } // The site-admin account flag IS s, everywhere. if err := db.AddAccount("root", "pw12345678", true); err != nil { t.Fatal(err) } if !db.EffectiveCaps("root", "anything").HasAny("s") { t.Fatal("site admin lacks s") } } func TestPerRepoCategoryOverride(t *testing.T) { db := testDB(t) // A repo "taken private": nobody and anonymous blanked (fossil's // Security-Audit action). Visitors hold nothing. if err := db.SetCategoryCaps("priv", "nobody", ""); err != nil { t.Fatal(err) } if err := db.SetCategoryCaps("priv", "anonymous", ""); err != nil { t.Fatal(err) } if eff := db.EffectiveCaps("", "priv"); eff.HasAny("gjorz2h") { t.Fatalf("private repo leaks visitor caps: %q", eff) } // Another repo on the same forge keeps stock defaults. if eff := db.EffectiveCaps("", "open"); !eff.HasAny("o") { t.Fatalf("open repo lost stock nobody: %q", eff) } } func TestRoleRowsTranslateToCaps(t *testing.T) { db := testDB(t) must := func(err error) { t.Helper() if err != nil { t.Fatal(err) } } must(db.AddAccount("r1", "pw12345678", false)) must(db.AddAccount("w1", "pw12345678", false)) must(db.AddAccount("a1", "pw12345678", false)) must(db.Grant("r1", "repo", Read)) must(db.Grant("w1", "repo", Write)) must(db.Grant("a1", "repo", RepoAdmin)) must(db.MigrateRolesToCaps()) if eff := db.EffectiveCaps("r1", "repo"); !eff.HasAny("k") || eff.HasAny("i") { t.Fatalf("read→u wrong: %q", eff) } if eff := db.EffectiveCaps("w1", "repo"); !eff.HasAny("k") || !eff.HasAny("i") { t.Fatalf("write→uv wrong: %q", eff) } if eff := db.EffectiveCaps("a1", "repo"); !eff.HasAny("5") || eff.HasAny("s") { t.Fatalf("admin→a wrong: %q", eff) } // Migration is idempotent and never clobbers explicit caps. must(db.SetCaps("r1", "repo", "uv")) must(db.MigrateRolesToCaps()) if eff := db.EffectiveCaps("r1", "repo"); !eff.HasAny("i") { t.Fatalf("migration clobbered explicit caps: %q", eff) } } func TestPurgeRepoSweepsCaps(t *testing.T) { db := testDB(t) if err := db.AddAccount("u1", "pw12345678", false); err != nil { t.Fatal(err) } if err := db.SetCaps("u1", "gone", "uv"); err != nil { t.Fatal(err) } if err := db.SetCategoryCaps("gone", "nobody", ""); err != nil { t.Fatal(err) } if err := db.PurgeRepo("gone"); err != nil { t.Fatal(err) } if caps, _ := db.CapsOf("u1", "gone"); caps != "" { t.Fatalf("caps survived purge: %q", caps) } if eff := db.EffectiveCaps("", "gone"); !eff.HasAny("o") { t.Fatal("category override survived purge (stock should return)") } } // Importing a peer's users creates unknown logins with their fossil // credential, replaces caps on THIS repo, and never takes over an // account quarry already has. func TestImportUsers(t *testing.T) { db := testDB(t) // A login quarry already knows, with its own password. if err := db.AddAccount("alice", "quarrypw123", false); err != nil { t.Fatal(err) } before, _ := db.AccountByName("alice") _ = before created, updated, err := db.ImportUsers("repo", "code123", []ImportedUser{ {Login: "alice", Caps: "ei", PWHash: "peerhashalice"}, {Login: "bob", Caps: "kptw", PWHash: "peerhashbob", Info: "Bob"}, {Login: "nobody", Caps: "gjor"}, }) if err != nil { t.Fatal(err) } if created != 1 { t.Fatalf("created = %d, want 1 (bob)", created) } if updated != 2 { t.Fatalf("updated = %d, want 2 (alice, nobody)", updated) } // Caps arrived verbatim for both. if c, _ := db.CapsOf("alice", "repo"); c != "ei" { t.Fatalf("alice's caps = %q", c) } if c, _ := db.CapsOf("bob", "repo"); c != "kptw" { t.Fatalf("bob's caps = %q", c) } // The category row landed as a category, not as an account. if db.CategoryCaps("repo", "nobody") != "gjor" { t.Fatalf("nobody category = %q", db.CategoryCaps("repo", "nobody")) } if _, found := db.AccountByName("nobody"); found { t.Fatal("a category became an account") } // alice keeps HER credential: an import is not a takeover. if !VerifyPassword(mustPWHash(t, db, "alice"), "quarrypw123") { t.Fatal("import overwrote an existing account's password") } if _, _, ok := db.LegacyCredential("alice"); ok { t.Fatal("an existing account gained an imported credential") } // bob can be verified against the peer's hash until he sets one. h, code, ok := db.LegacyCredential("bob") if !ok || h != "peerhashbob" || code != "code123" { t.Fatalf("bob's legacy credential = %q/%q ok=%v", h, code, ok) } if err := db.ClearLegacyCredential("bob"); err != nil { t.Fatal(err) } if _, _, ok := db.LegacyCredential("bob"); ok { t.Fatal("legacy credential survived being cleared") } // A category the peer never mentioned keeps quarry's stock default. if db.CategoryCaps("repo", "reader") != StockReader { t.Fatal("an unmentioned category was blanked") } } func mustPWHash(t *testing.T, db *DB, name string) string { t.Helper() var h string if err := db.db.QueryRow(`SELECT pwhash FROM account WHERE name = ?`, name).Scan(&h); err != nil { t.Fatal(err) } return h } // An imported fossil user signs in with the password they already had, // once — and the account is upgraded to a native verifier on the way // through, so the imported secret does not outlive its usefulness. func TestImportedUserSignsInWithFossilPassword(t *testing.T) { db := testDB(t) const code, login, pw = "abc123projectcode", "alice", "secretpw" if _, _, err := db.ImportUsers("repo", code, []ImportedUser{ {Login: login, Caps: "ei", PWHash: legacySecret(code, login, pw)}, }); err != nil { t.Fatal(err) } // The fossil password works. if _, ok := db.Authenticate(login, pw); !ok { t.Fatal("an imported user could not sign in with their fossil password") } // It was an upgrade, not a permanent bypass: the imported secret is // gone and a native verifier now stands in its place. if _, _, ok := db.LegacyCredential(login); ok { t.Fatal("the imported credential survived a successful login") } if _, ok := db.Authenticate(login, pw); !ok { t.Fatal("the upgraded account cannot sign in with the same password") } if _, ok := db.Authenticate(login, "wrong"); ok { t.Fatal("a wrong password was accepted") } } // A wrong password never consumes the legacy credential — otherwise a // failed guess would lock the real owner out of their own upgrade path. func TestWrongPasswordLeavesTheLegacyCredential(t *testing.T) { db := testDB(t) const code, login, pw = "code", "bob", "hunter2" if _, _, err := db.ImportUsers("repo", code, []ImportedUser{ {Login: login, PWHash: legacySecret(code, login, pw)}, }); err != nil { t.Fatal(err) } if _, ok := db.Authenticate(login, "nope"); ok { t.Fatal("wrong password accepted") } if _, _, ok := db.LegacyCredential(login); !ok { t.Fatal("a failed guess destroyed the legacy credential") } if _, ok := db.Authenticate(login, pw); !ok { t.Fatal("the real password stopped working after a failed guess") } }