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.

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