vole
Clone
Read only, no credentials:
https://rodent.software/vole.git
Write over SSH with a registered key:
git clone ssh://git@rodent.software:2222/vole.git
A free and open-source forge that lives in your Git repo.
Add instance-wide roles and refine access UI
5b814e9958090cfd1fa826efb2d375bf613547c1 · Keenan Nemetz · 14d ago · parent 9579c1f9
Add instance-wide roles and refine access UI Instance roles now live on accounts, apply to public repositories, and the Admin role is the unified instance-admin permission with access to every repository. Repository ACLs can raise an instance role but not lower it. Adds role assignment to the users UI and CLI, open-registration role config, a reusable role picker, and separates adding a repository account from saving access rules.
server/hook/hook_test.go +42−3
| @@ -11,6 +11,7 @@ import ( | |||
| 11 | 11 | "github.com/rodent-software/vole/ci/runs" | |
| 12 | 12 | "github.com/rodent-software/vole/core/git" | |
| 13 | 13 | "github.com/rodent-software/vole/forge/access" | |
| 14 | "github.com/rodent-software/vole/forge/identity" | ||
| 14 | 15 | "github.com/rodent-software/vole/forge/repo" | |
| 15 | 16 | ) | |
| 16 | 17 | ||
| @@ -42,7 +43,7 @@ func newFixture(t *testing.T, blob string) *fixture { | |||
| 42 | 43 | if err := a.SetEntry("reader", "read"); err != nil { | |
| 43 | 44 | t.Fatal(err) | |
| 44 | 45 | } | |
| 45 | if err := a.SetEntry("contributor", "contributor"); err != nil { | ||
| 46 | if err := a.SetEntry("contributor", "contribute"); err != nil { | ||
| 46 | 47 | t.Fatal(err) | |
| 47 | 48 | } | |
| 48 | 49 | if err := m.SetACL(a, "test"); err != nil { | |
| @@ -141,12 +142,50 @@ func TestContributorCanPushOnlyTheirNamespace(t *testing.T) { | |||
| 141 | 142 | if code == 0 { | |
| 142 | 143 | t.Fatalf("contributor was allowed to push %s", ref) | |
| 143 | 144 | } | |
| 144 | if !strings.Contains(out, "contributors may only push branches under refs/heads/contrib/contributor/") { | ||
| 145 | if !strings.Contains(out, "the contribute role may only push branches under refs/heads/contrib/contributor/") { | ||
| 145 | 146 | t.Fatalf("%s: unclear rejection: %s", ref, out) | |
| 146 | 147 | } | |
| 147 | 148 | } | |
| 148 | 149 | } | |
| 149 | 150 | ||
| 151 | func TestInstanceRoleAppliesOnlyToPublicReposAndAdminEverywhere(t *testing.T) { | ||
| 152 | t.Parallel() | ||
| 153 | f := newFixture(t, "hello\n") | ||
| 154 | ident, err := identity.Open(f.usersDB) | ||
| 155 | if err != nil { | ||
| 156 | t.Fatal(err) | ||
| 157 | } | ||
| 158 | defer ident.Close() | ||
| 159 | if _, _, err := ident.CreateWithInviteRole("invited", access.Contribute, "test", 0); err != nil { | ||
| 160 | t.Fatal(err) | ||
| 161 | } | ||
| 162 | |||
| 163 | own := "refs/heads/contrib/invited/topic" | ||
| 164 | f.setACL(func(a *access.ACL) { a.Default = "read" }) | ||
| 165 | if code, out := f.run("invited", own, 0); code != 0 { | ||
| 166 | t.Fatalf("instance contributor could not push their namespace to a public repo: %s", out) | ||
| 167 | } | ||
| 168 | if code, out := f.run("invited", "refs/heads/main", 0); code == 0 { | ||
| 169 | t.Fatal("instance contributor pushed a shared branch") | ||
| 170 | } else if !strings.Contains(out, "the contribute role may only push branches under") { | ||
| 171 | t.Fatalf("unclear contributor rejection: %s", out) | ||
| 172 | } | ||
| 173 | |||
| 174 | f.setACL(func(a *access.ACL) { a.Default = "none" }) | ||
| 175 | if code, out := f.run("invited", own, 0); code == 0 { | ||
| 176 | t.Fatal("instance contributor pushed to a private repo") | ||
| 177 | } else if !strings.Contains(out, "does not have maintain or contribute access") { | ||
| 178 | t.Fatalf("unclear private-repo rejection: %s", out) | ||
| 179 | } | ||
| 180 | |||
| 181 | if _, err := ident.SetInstanceRole("invited", access.Admin, "test"); err != nil { | ||
| 182 | t.Fatal(err) | ||
| 183 | } | ||
| 184 | if code, out := f.run("invited", "refs/heads/main", 0); code != 0 { | ||
| 185 | t.Fatalf("instance admin could not push to a private repo: %s", out) | ||
| 186 | } | ||
| 187 | } | ||
| 188 | |||
| 150 | 189 | func TestReadOnlyCannotPush(t *testing.T) { | |
| 151 | 190 | t.Parallel() | |
| 152 | 191 | f := newFixture(t, "hello\n") | |
| @@ -154,7 +193,7 @@ func TestReadOnlyCannotPush(t *testing.T) { | |||
| 154 | 193 | if code == 0 { | |
| 155 | 194 | t.Fatal("read-only user was allowed to push") | |
| 156 | 195 | } | |
| 157 | if !strings.Contains(out, "does not have maintain access") { | ||
| 196 | if !strings.Contains(out, "does not have maintain or contribute access") { | ||
| 158 | 197 | t.Fatalf("message should name the reason, got: %s", out) | |
| 159 | 198 | } | |
| 160 | 199 | if !strings.Contains(out, "reader") { | |