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.
forge/access/store_test.go +52
| @@ -65,6 +65,58 @@ func TestRulesRoundTripAndReplace(t *testing.T) { | |||
| 65 | 65 | } | |
| 66 | 66 | } | |
| 67 | 67 | ||
| 68 | // The store is where the roster's instance-wide role meets a repository's rules. | ||
| 69 | // A public repository gets the global grant; a private one does not, unless the | ||
| 70 | // account is an instance admin. | ||
| 71 | func TestStoreAttachesInstanceRoles(t *testing.T) { | ||
| 72 | t.Parallel() | ||
| 73 | s, _ := open(t) | ||
| 74 | s.InstanceRole = func(name string) Role { | ||
| 75 | switch name { | ||
| 76 | case "invited": | ||
| 77 | return Contribute | ||
| 78 | case "boss": | ||
| 79 | return Admin | ||
| 80 | } | ||
| 81 | return None | ||
| 82 | } | ||
| 83 | pub := Empty("owner") | ||
| 84 | pub.Default = "read" | ||
| 85 | if err := s.Set("public-repo", pub, "test", time.Now().UTC()); err != nil { | ||
| 86 | t.Fatal(err) | ||
| 87 | } | ||
| 88 | private := Empty("owner") | ||
| 89 | if err := s.Set("private-repo", private, "test", time.Now().UTC()); err != nil { | ||
| 90 | t.Fatal(err) | ||
| 91 | } | ||
| 92 | |||
| 93 | got, err := s.Get("public-repo") | ||
| 94 | if err != nil { | ||
| 95 | t.Fatal(err) | ||
| 96 | } | ||
| 97 | if got.RoleFor("invited") != Contribute { | ||
| 98 | t.Fatalf("public instance role = %v", got.RoleFor("invited")) | ||
| 99 | } | ||
| 100 | if got.RepositoryRoleFor("invited") != Read { | ||
| 101 | t.Fatalf("repository-only role = %v, want the default read", got.RepositoryRoleFor("invited")) | ||
| 102 | } | ||
| 103 | got, err = s.Get("private-repo") | ||
| 104 | if err != nil { | ||
| 105 | t.Fatal(err) | ||
| 106 | } | ||
| 107 | if got.RoleFor("invited") != None { | ||
| 108 | t.Fatalf("private repo admitted instance contributor: %v", got.RoleFor("invited")) | ||
| 109 | } | ||
| 110 | if got.RoleFor("boss") != Admin { | ||
| 111 | t.Fatalf("instance admin on private repo = %v", got.RoleFor("boss")) | ||
| 112 | } | ||
| 113 | // A repository without a row still has to see the instance rules. | ||
| 114 | empty := s.Attach(Empty("")) | ||
| 115 | if empty.RoleFor("boss") != Admin { | ||
| 116 | t.Fatalf("attached empty ACL = %v", empty.RoleFor("boss")) | ||
| 117 | } | ||
| 118 | } | ||
| 119 | |||
| 68 | 120 | // An access change is the one administrative act that has to leave a trace: the copy | |
| 69 | 121 | // that lived in git kept it in the commit message. | |
| 70 | 122 | func TestChangesAreAudited(t *testing.T) { | |