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/admin_users_test.go +18−7
| @@ -4,6 +4,8 @@ import ( | |||
| 4 | 4 | "net/url" | |
| 5 | 5 | "strings" | |
| 6 | 6 | "testing" | |
| 7 | |||
| 8 | "github.com/rodent-software/vole/forge/access" | ||
| 7 | 9 | ) | |
| 8 | 10 | ||
| 9 | 11 | func TestAdminUsersLifecycle(t *testing.T) { | |
| @@ -26,7 +28,7 @@ func TestAdminUsersLifecycle(t *testing.T) { | |||
| 26 | 28 | } | |
| 27 | 29 | ||
| 28 | 30 | create := url.Values{ | |
| 29 | "csrf": {sess.CSRF}, "name": {"newbie"}, "display": {"New Bie"}, | ||
| 31 | "csrf": {sess.CSRF}, "name": {"newbie"}, "display": {"New Bie"}, "role": {"contribute"}, | ||
| 30 | 32 | } | |
| 31 | 33 | resp, body = f.browserPost(t, "/users", cookie, create) | |
| 32 | 34 | if resp.StatusCode != 200 || !strings.Contains(body, "Enrollment link") || !strings.Contains(body, "newbie") { | |
| @@ -49,7 +51,7 @@ func TestAdminUsersLifecycle(t *testing.T) { | |||
| 49 | 51 | if err != nil { | |
| 50 | 52 | t.Fatal(err) | |
| 51 | 53 | } | |
| 52 | if len(acct.Keys) != 0 || !acct.Active() || acct.Admin { | ||
| 54 | if len(acct.Keys) != 0 || !acct.Active() || acct.Admin || acct.Role != access.Contribute { | ||
| 53 | 55 | t.Fatalf("created account = %+v", acct) | |
| 54 | 56 | } | |
| 55 | 57 | ||
| @@ -65,14 +67,23 @@ func TestAdminUsersLifecycle(t *testing.T) { | |||
| 65 | 67 | t.Fatalf("account page = %d:\n%s", resp.StatusCode, body) | |
| 66 | 68 | } | |
| 67 | 69 | ||
| 68 | resp, _ = f.browserPost(t, "/users/"+acct.Name+"/admin", cookie, url.Values{ | ||
| 69 | "csrf": {sess.CSRF}, "admin": {"true"}, | ||
| 70 | resp, _ = f.browserPost(t, "/users/"+acct.Name+"/role", cookie, url.Values{ | ||
| 71 | "csrf": {sess.CSRF}, "role": {"maintain"}, | ||
| 72 | }) | ||
| 73 | if resp.StatusCode != 303 { | ||
| 74 | t.Fatalf("set role = %d", resp.StatusCode) | ||
| 75 | } | ||
| 76 | if acct, err = f.srv.Identity.Find(acct.Name); err != nil || acct.Role != access.Maintain { | ||
| 77 | t.Fatalf("role after set = %+v, %v", acct, err) | ||
| 78 | } | ||
| 79 | resp, _ = f.browserPost(t, "/users/"+acct.Name+"/role", cookie, url.Values{ | ||
| 80 | "csrf": {sess.CSRF}, "role": {"admin"}, | ||
| 70 | 81 | }) | |
| 71 | 82 | if resp.StatusCode != 303 || !f.srv.Identity.IsAdmin(acct.Name) { | |
| 72 | 83 | t.Fatalf("grant admin = %d, admin=%t", resp.StatusCode, f.srv.Identity.IsAdmin(acct.Name)) | |
| 73 | 84 | } | |
| 74 | resp, _ = f.browserPost(t, "/users/"+acct.Name+"/admin", cookie, url.Values{ | ||
| 75 | "csrf": {sess.CSRF}, "admin": {"false"}, | ||
| 85 | resp, _ = f.browserPost(t, "/users/"+acct.Name+"/role", cookie, url.Values{ | ||
| 86 | "csrf": {sess.CSRF}, "role": {"none"}, | ||
| 76 | 87 | }) | |
| 77 | 88 | if resp.StatusCode != 303 || f.srv.Identity.IsAdmin(acct.Name) { | |
| 78 | 89 | t.Fatalf("revoke admin = %d, admin=%t", resp.StatusCode, f.srv.Identity.IsAdmin(acct.Name)) | |
| @@ -142,7 +153,7 @@ func TestAdminUsersLifecycle(t *testing.T) { | |||
| 142 | 153 | } | |
| 143 | 154 | ||
| 144 | 155 | // CSRF is checked before any mutation. | |
| 145 | if resp, _ := f.browserPost(t, "/users/"+acct.Name+"/admin", cookie, url.Values{"admin": {"true"}}); resp.StatusCode != 403 { | ||
| 156 | if resp, _ := f.browserPost(t, "/users/"+acct.Name+"/role", cookie, url.Values{"role": {"admin"}}); resp.StatusCode != 403 { | ||
| 146 | 157 | t.Fatalf("missing CSRF = %d, want 403", resp.StatusCode) | |
| 147 | 158 | } | |
| 148 | 159 | } | |