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.
Let instance admins create bot accounts from the web UI
8c72543b9ef4ac728740a6f0b3a649f9c3102e7a · Keenan Nemetz · 5d ago · parent f422c24f
Let instance admins create bot accounts from the web UI The Accounts page could only invite a person with an enrollment link. A bot has no browser sign-in, so it needs an SSH key at creation instead. Add a bot form that reuses identity.CreateBot, keep admin out of its role picker, and hide the enrollment action on a bot's account page.
server/web/admin_users.go +42−2
| @@ -30,7 +30,11 @@ type adminUserView struct { | |||
| 30 | 30 | Page | |
| 31 | 31 | Account *identity.Account | |
| 32 | 32 | Sessions []session.Session | |
| 33 | Error string | ||
| 33 | // RoleScope and RoleHelp keep the role picker honest for a bot: it may hold | ||
| 34 | // an instance role, but not the instance-admin role. | ||
| 35 | RoleScope string | ||
| 36 | RoleHelp string | ||
| 37 | Error string | ||
| 34 | 38 | } | |
| 35 | 39 | ||
| 36 | 40 | type adminInviteView struct { | |
| @@ -57,7 +61,15 @@ func (w *Web) adminUsersView(r *http.Request, msg string) adminUsersView { | |||
| 57 | 61 | } | |
| 58 | 62 | ||
| 59 | 63 | func (w *Web) adminUserView(r *http.Request, acct *identity.Account, msg string) adminUserView { | |
| 60 | v := adminUserView{Page: w.page(r, acct.Name), Account: acct, Error: msg} | ||
| 64 | v := adminUserView{ | ||
| 65 | Page: w.page(r, acct.Name), Account: acct, Error: msg, | ||
| 66 | RoleScope: "instance", | ||
| 67 | RoleHelp: "Instance roles apply to public repositories. A repository-specific role can grant more on one repository. admin is the exception: it applies to every repository, private ones included.", | ||
| 68 | } | ||
| 69 | if acct.Bot() { | ||
| 70 | v.RoleScope = "bot" | ||
| 71 | v.RoleHelp = "Instance roles apply to public repositories. A repository-specific role can grant more on one repository. A bot cannot be an instance admin." | ||
| 72 | } | ||
| 61 | 73 | if w.Sessions != nil { | |
| 62 | 74 | v.Sessions, _ = w.Sessions.List(acct.Name) | |
| 63 | 75 | } | |
| @@ -119,6 +131,34 @@ func (w *Web) CreateAdminUser(rw http.ResponseWriter, r *http.Request) { | |||
| 119 | 131 | w.render(rw, "admin_invite", w.adminInviteView(r, acct, inv), 0) | |
| 120 | 132 | } | |
| 121 | 133 | ||
| 134 | // CreateAdminBot creates an automation account. A bot has no browser sign-in, so | ||
| 135 | // an enrollment link would be a secret it could never use: the form collects the | ||
| 136 | // SSH key it acts with instead. The identity layer records the same audit line | ||
| 137 | // the CLI does, with the kind and the role. | ||
| 138 | func (w *Web) CreateAdminBot(rw http.ResponseWriter, r *http.Request) { | ||
| 139 | pr, ok := w.beginAdminPost(rw, r, maxSmallForm) | ||
| 140 | if !ok { | ||
| 141 | return | ||
| 142 | } | ||
| 143 | name := strings.TrimSpace(r.PostFormValue("name")) | ||
| 144 | role, err := access.ParseRole(r.PostFormValue("role")) | ||
| 145 | if err != nil { | ||
| 146 | w.render(rw, "admin_users", w.adminUsersView(r, err.Error()), http.StatusBadRequest) | ||
| 147 | return | ||
| 148 | } | ||
| 149 | key := strings.TrimSpace(r.PostFormValue("key")) | ||
| 150 | if key == "" { | ||
| 151 | w.render(rw, "admin_users", w.adminUsersView(r, "Paste a public key for the bot. A bot signs in with a key, not a browser."), http.StatusBadRequest) | ||
| 152 | return | ||
| 153 | } | ||
| 154 | acct, err := w.Ident.CreateBot(name, role, key, pr.account.Name) | ||
| 155 | if err != nil { | ||
| 156 | w.render(rw, "admin_users", w.adminUsersView(r, err.Error()), http.StatusBadRequest) | ||
| 157 | return | ||
| 158 | } | ||
| 159 | http.Redirect(rw, r, "/users/"+acct.Name, http.StatusSeeOther) | ||
| 160 | } | ||
| 161 | |||
| 122 | 162 | func (w *Web) AdminUserPage(rw http.ResponseWriter, r *http.Request) { | |
| 123 | 163 | if _, ok := w.requireInstanceAdmin(rw, r); !ok { | |
| 124 | 164 | return | |