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/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") {