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.
Manage accounts from the users directory, not the Admin tab
ea21f923d1fabe3a633e3dc4713ca18d211dbfcb · Keenan Nemetz · 4d ago · parent f11854fe
Manage accounts from the users directory, not the Admin tab The Accounts tab rendered every account in the roster, which costs more as an instance grows and duplicates a directory that is already paginated, searchable, filterable, and sortable. The tab now keeps the invite and bot forms and points at /u, and an instance admin gets a Manage action on each directory row that opens the account page. The Manage column is presentation only: the account page still checks the instance-admin role, so a non-admin who types its URL gets the usual 404. Refs #198 (01M6MK0NNZ2XR1D5TQKNWYT4HN).
server/admin_users_test.go +41−5
| @@ -17,12 +17,21 @@ func TestAdminUsersLifecycle(t *testing.T) { | |||
| 17 | 17 | } | |
| 18 | 18 | sess, cookie := f.browserSession(t, f.ownerID) | |
| 19 | 19 | ||
| 20 | // The admin tab creates accounts; it does not list the roster, which is now | ||
| 21 | // the paginated users directory. | ||
| 20 | 22 | resp, body := f.browserGet(t, "/admin?tab=accounts", cookie) | |
| 21 | if resp.StatusCode != 200 || !strings.Contains(body, "Invite a person") || !strings.Contains(body, "keenan") { | ||
| 22 | t.Fatalf("users page = %d:\n%s", resp.StatusCode, body) | ||
| 23 | if resp.StatusCode != 200 || !strings.Contains(body, "Invite a person") || | ||
| 24 | strings.Contains(body, `<a href="/users/keenan">Manage</a>`) { | ||
| 25 | t.Fatalf("accounts tab = %d:\n%s", resp.StatusCode, body) | ||
| 23 | 26 | } | |
| 24 | 27 | if !strings.Contains(body, `href="/admin?tab=accounts" aria-current="page"`) { | |
| 25 | t.Fatalf("users page is missing the active top-nav link:\n%s", body) | ||
| 28 | t.Fatalf("accounts tab is missing the active top-nav link:\n%s", body) | ||
| 29 | } | ||
| 30 | resp, body = f.browserGet(t, "/u", cookie) | ||
| 31 | if resp.StatusCode != 200 || | ||
| 32 | !strings.Contains(body, `<a class="people-name" href="/u/keenan">keenan</a>`) || | ||
| 33 | !strings.Contains(body, `<a href="/users/keenan">Manage</a>`) { | ||
| 34 | t.Fatalf("users directory = %d:\n%s", resp.StatusCode, body) | ||
| 26 | 35 | } | |
| 27 | 36 | if _, home := f.browserGet(t, "/", cookie); !strings.Contains(home, `href="/admin"`) { | |
| 28 | 37 | t.Fatal("the top navigation does not link to Users for an instance admin") | |
| @@ -193,8 +202,9 @@ func TestAdminCreatesBotAccount(t *testing.T) { | |||
| 193 | 202 | if !acct.Bot() || acct.Role != access.Contribute || len(acct.Keys) != 1 || acct.Keys[0].Line != botKey { | |
| 194 | 203 | t.Fatalf("created bot = %+v", acct) | |
| 195 | 204 | } | |
| 196 | if _, list := f.browserGet(t, "/admin?tab=accounts", cookie); !strings.Contains(list, `<a href="/users/ci-runner">ci-runner</a> <span class="state">bot</span>`) { | ||
| 197 | t.Fatalf("the account list does not mark the bot:\n%s", list) | ||
| 205 | if _, list := f.browserGet(t, "/u", cookie); !strings.Contains(list, `<a class="people-name" href="/u/ci-runner">ci-runner</a> <span class="state">bot</span>`) || | ||
| 206 | !strings.Contains(list, `<a href="/users/ci-runner">Manage</a>`) { | ||
| 207 | t.Fatalf("the users directory does not mark the bot or offer Manage:\n%s", list) | ||
| 198 | 208 | } | |
| 199 | 209 | ||
| 200 | 210 | // The page that manages the bot says what it is and offers only actions a bot | |
| @@ -263,3 +273,29 @@ func TestAdminUsersRequiresInstanceAdmin(t *testing.T) { | |||
| 263 | 273 | t.Fatalf("non-admin users page = %d, want 404", resp.StatusCode) | |
| 264 | 274 | } | |
| 265 | 275 | } | |
| 276 | |||
| 277 | // Managing an account is an admin action, so the directory only grows the Manage | ||
| 278 | // column for an instance admin. Everyone else sees the same public roster. | ||
| 279 | func TestUsersDirectoryHidesManageFromNonAdmins(t *testing.T) { | ||
| 280 | t.Parallel() | ||
| 281 | f := browserFixture(t) | ||
| 282 | _, cookie := f.browserSession(t, f.ownerID) | ||
| 283 | |||
| 284 | resp, body := f.browserGet(t, "/u", cookie) | ||
| 285 | if resp.StatusCode != 200 { | ||
| 286 | t.Fatalf("GET /u = %d:\n%s", resp.StatusCode, body) | ||
| 287 | } | ||
| 288 | for _, gone := range []string{"<th>Manage</th>", `href="/users/keenan">Manage</a>`} { | ||
| 289 | if strings.Contains(body, gone) { | ||
| 290 | t.Fatalf("a non-admin sees %q in the users directory:\n%s", gone, body) | ||
| 291 | } | ||
| 292 | } | ||
| 293 | |||
| 294 | if _, err := f.srv.Identity.SetAdmin(f.ownerID, true, "test"); err != nil { | ||
| 295 | t.Fatal(err) | ||
| 296 | } | ||
| 297 | if _, body := f.browserGet(t, "/u", cookie); !strings.Contains(body, "<th>Manage</th>") || | ||
| 298 | !strings.Contains(body, `href="/users/keenan">Manage</a>`) { | ||
| 299 | t.Fatalf("an instance admin has no Manage action in the users directory:\n%s", body) | ||
| 300 | } | ||
| 301 | } | ||