Sign in

Search issue titles and bodies, change requests and comments. Private repositories you cannot read are not included.

vole
Clone

Read only, no credentials:

https://rodent.software/git/vole.git

Write over SSH with a registered key:

git clone ssh://git@0.0.0.0:2222/vole.git

A free and open-source forge that lives in your Git repo.

Code

392 commit(s) on main

Merge change request 01M70WES5ZZDJ6RP226WB11BBZ: Let people rename and remove their own passkeys keenan · 1h ago 4b8c9e1d
history blame raw 2.8 KiB
1package access
2
3// Who may report a CI result is an allowlist, not a role: it answers "may this
4// account's build be believed about this repository", not "may this account push to
5// it". These tests pin the two halves of that rule — the list, and the admins who are
6// implicit in it.
7import "testing"
8
9func TestOnlyRunnersAndAdminsCount(t *testing.T) {
10 t.Parallel()
11 a := Empty("owner-id")
12 a.Entries["writer-id"] = "maintain"
13 a.Entries["reader-id"] = "read"
14 a.SetRunners([]string{"ci-runner", "writer-id"})
15 for _, tc := range []struct {
16 account string
17 want bool
18 why string
19 }{
20 {"owner-id", true, "an admin can rewrite this list, so restricting them here is theatre"},
21 {"writer-id", true, "named as a runner"},
22 {"ci-runner", true, "named as a runner, with no role at all"},
23 {"reader-id", false, "read access is not trust in a build"},
24 {"stranger", false, "nobody"},
25 {"", false, "an unattributed result is answerable to nobody"},
26 } {
27 if got := a.CanReport(tc.account); got != tc.want {
28 t.Fatalf("CanReport(%q) = %v, want %v (%s)", tc.account, got, tc.want, tc.why)
29 }
30 }
31}
32
33// An empty list is not "nobody can report": it is "nobody beyond the admins", and the
34// distinction matters because every repository starts with an empty list.
35func TestAnEmptyRunnerListStillLetsAdminsReport(t *testing.T) {
36 t.Parallel()
37 a := Empty("owner-id")
38 if !a.CanReport("owner-id") {
39 t.Fatal("the owner of a new repository cannot report CI results")
40 }
41 if a.CanReport("writer-id") {
42 t.Fatal("a stranger's result counted")
43 }
44}
45func TestRunnerListIsNormalised(t *testing.T) {
46 t.Parallel()
47 a := Empty("owner-id")
48 a.SetRunners([]string{"b", " a ", "a", "", "c"})
49 if len(a.Runners) != 3 || a.Runners[0] != "a" || a.Runners[1] != "b" || a.Runners[2] != "c" {
50 t.Fatalf("runners = %#v, want a b c with no blanks or duplicates", a.Runners)
51 }
52 // The same rule on the way in from disk, so a hand-edited file cannot leave a
53 // list that reads differently from how it behaves.
54 parsed, err := Parse([]byte(`{"version":1,"default":"none","runners":["b","a","b",""]}`))
55 if err != nil {
56 t.Fatal(err)
57 }
58 if len(parsed.Runners) != 2 || parsed.Runners[0] != "a" || parsed.Runners[1] != "b" {
59 t.Fatalf("parsed runners = %#v", parsed.Runners)
60 }
61}
62
63// The list has to survive a round trip through the ref it is stored on, since that is
64// the only copy.
65func TestRunnerListRoundTrips(t *testing.T) {
66 t.Parallel()
67 a := Empty("owner-id")
68 a.SetRunners([]string{"ci-runner"})
69 a.Protected = []string{"refs/heads/main"}
70 data, err := a.Bytes()
71 if err != nil {
72 t.Fatal(err)
73 }
74 back, err := Parse(data)
75 if err != nil {
76 t.Fatal(err)
77 }
78 if len(back.Runners) != 1 || back.Runners[0] != "ci-runner" || !back.CanReport("ci-runner") {
79 t.Fatalf("runners did not survive: %#v", back.Runners)
80 }
81}
82