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.
Merge change request 01M70WES5ZZDJ6RP226WB11BBZ: Let people rename and remove their own passkeys
keenan · 23m ago
4b8c9e1d
| 1 | package web |
| 2 | |
| 3 | import ( |
| 4 | "testing" |
| 5 | |
| 6 | "rodent.software/vole/forge/access" |
| 7 | ) |
| 8 | |
| 9 | // The capability rules are the whole authorization story for the browser, so they |
| 10 | // are tested as rules rather than only through the handlers that call them. A |
| 11 | // change here is a change to what every page offers and every write accepts. |
| 12 | |
| 13 | func TestCapabilitiesByRole(t *testing.T) { |
| 14 | acl := access.Empty("owner") |
| 15 | for _, entry := range []struct{ name, role string }{ |
| 16 | {"outsider", "none"}, |
| 17 | {"reader", "read"}, |
| 18 | {"contributor", "contribute"}, |
| 19 | {"maintainer", "maintain"}, |
| 20 | {"owner", "admin"}, |
| 21 | } { |
| 22 | if err := acl.SetEntry(entry.name, entry.role); err != nil { |
| 23 | t.Fatalf("set %s to %s: %v", entry.name, entry.role, err) |
| 24 | } |
| 25 | } |
| 26 | |
| 27 | cases := []struct { |
| 28 | name string |
| 29 | account string |
| 30 | discuss bool |
| 31 | openRequest bool |
| 32 | maintain bool |
| 33 | admin bool |
| 34 | }{ |
| 35 | {"anonymous", "", false, false, false, false}, |
| 36 | {"outsider", "outsider", false, false, false, false}, |
| 37 | {"reader", "reader", false, false, false, false}, |
| 38 | {"contributor", "contributor", true, true, false, false}, |
| 39 | {"maintainer", "maintainer", true, true, true, false}, |
| 40 | {"owner", "owner", true, true, true, true}, |
| 41 | } |
| 42 | for _, c := range cases { |
| 43 | t.Run(c.name, func(t *testing.T) { |
| 44 | if got := canDiscuss(acl, c.account); got != c.discuss { |
| 45 | t.Errorf("canDiscuss = %v, want %v", got, c.discuss) |
| 46 | } |
| 47 | if got := canOpenChangeRequest(acl, c.account); got != c.openRequest { |
| 48 | t.Errorf("canOpenChangeRequest = %v, want %v", got, c.openRequest) |
| 49 | } |
| 50 | if got := canMaintain(acl, c.account); got != c.maintain { |
| 51 | t.Errorf("canMaintain = %v, want %v", got, c.maintain) |
| 52 | } |
| 53 | if got := canAdminister(acl, c.account); got != c.admin { |
| 54 | t.Errorf("canAdminister = %v, want %v", got, c.admin) |
| 55 | } |
| 56 | }) |
| 57 | } |
| 58 | } |
| 59 | |
| 60 | // Triaging somebody else's issue is a maintainer's job; managing your own is not |
| 61 | // a privilege. That is the rule the labels, assignees and state forms rely on. |
| 62 | func TestCanTriageAllowsTheAuthorWithoutMaintainer(t *testing.T) { |
| 63 | acl := access.Empty("owner") |
| 64 | if err := acl.SetEntry("author", "contribute"); err != nil { |
| 65 | t.Fatal(err) |
| 66 | } |
| 67 | if !canTriage(acl, "author", "author") { |
| 68 | t.Error("an author cannot manage their own issue") |
| 69 | } |
| 70 | if canTriage(acl, "author", "somebody-else") { |
| 71 | t.Error("a contributor can triage somebody else's issue") |
| 72 | } |
| 73 | if !canTriage(acl, "owner", "author") { |
| 74 | t.Error("a maintainer cannot triage another account's issue") |
| 75 | } |
| 76 | // An empty author is not a wildcard: a record with no author must not become |
| 77 | // manageable by anyone who can triage. |
| 78 | if canTriage(acl, "", "") { |
| 79 | t.Error("an empty account was treated as the author") |
| 80 | } |
| 81 | } |
| 82 | |
| 83 | // Approving your own change request is refused as its own rule, so a maintainer |
| 84 | // is told why rather than being called unauthorized. |
| 85 | func TestCanApproveRequestRefusesSelfApproval(t *testing.T) { |
| 86 | acl := access.Empty("owner") |
| 87 | if !canApproveRequest(acl, "owner", "someone") { |
| 88 | t.Error("a maintainer cannot approve another account's request") |
| 89 | } |
| 90 | if !canApprove(acl, "owner") { |
| 91 | t.Error("canApprove dropped the capability for a maintainer") |
| 92 | } |
| 93 | if canApproveRequest(acl, "owner", "owner") { |
| 94 | t.Error("a maintainer was allowed to approve their own request") |
| 95 | } |
| 96 | if canApproveRequest(acl, "", "someone") { |
| 97 | t.Error("an anonymous viewer was allowed to approve") |
| 98 | } |
| 99 | } |
| 100 | |
| 101 | // A contributor's branches live in their own namespace; everybody at maintainer |
| 102 | // or above may name any branch. |
| 103 | func TestContributorOnly(t *testing.T) { |
| 104 | acl := access.Empty("owner") |
| 105 | for name, role := range map[string]string{ |
| 106 | "contributor": "contribute", |
| 107 | "maintainer": "maintain", |
| 108 | "reader": "read", |
| 109 | } { |
| 110 | if err := acl.SetEntry(name, role); err != nil { |
| 111 | t.Fatal(err) |
| 112 | } |
| 113 | } |
| 114 | if !contributorOnly(acl, "contributor") { |
| 115 | t.Error("a contribute-only account is not limited to its namespace") |
| 116 | } |
| 117 | if contributorOnly(acl, "maintainer") || contributorOnly(acl, "owner") { |
| 118 | t.Error("a maintainer was confined to a contrib namespace") |
| 119 | } |
| 120 | if contributorOnly(acl, "reader") || contributorOnly(acl, "") { |
| 121 | t.Error("an account that cannot open a request was treated as a contributor") |
| 122 | } |
| 123 | } |
| 124 | |
| 125 | // Deleting somebody else's comment needs the admin role; editing your own does |
| 126 | // not. |
| 127 | func TestCanModerateComment(t *testing.T) { |
| 128 | acl := access.Empty("owner") |
| 129 | if err := acl.SetEntry("maintainer", "maintain"); err != nil { |
| 130 | t.Fatal(err) |
| 131 | } |
| 132 | cases := []struct { |
| 133 | name, account, author string |
| 134 | want bool |
| 135 | }{ |
| 136 | {"own comment", "maintainer", "maintainer", true}, |
| 137 | {"another account's comment", "maintainer", "owner", false}, |
| 138 | {"admin repairing a thread", "owner", "maintainer", true}, |
| 139 | {"anonymous", "", "maintainer", false}, |
| 140 | {"no rules", "maintainer", "maintainer", true}, |
| 141 | } |
| 142 | for _, c := range cases { |
| 143 | t.Run(c.name, func(t *testing.T) { |
| 144 | got := canModerateComment(acl, c.account, c.author) |
| 145 | if got != c.want { |
| 146 | t.Errorf("canModerateComment(%q, %q) = %v, want %v", c.account, c.author, got, c.want) |
| 147 | } |
| 148 | }) |
| 149 | } |
| 150 | // A nil ACL is a repo whose rules could not be read: deny, do not panic. |
| 151 | if canModerateComment(nil, "owner", "owner") { |
| 152 | t.Error("a nil ACL granted moderation") |
| 153 | } |
| 154 | } |
| 155 |