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.

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/ssh/admin.go +32−13

@@ -132,11 +132,7 @@ func (s *Server) adminUser(action string, args []string, actor string, in io.Rea
132 132 return
133 133 }
134 134 for _, a := range all {
135 role := ""
136 if a.Admin {
137 role = " admin"
138 }
139 fmt.Fprintf(out, "%-32s %-8s%s keys=%d\n", a.Name, a.Status, role, len(a.Keys))
135 fmt.Fprintf(out, "%-32s %-8s %-12s keys=%d\n", a.Name, a.Status, a.Role, len(a.Keys))
140 136 for _, k := range a.Keys {
141 137 fmt.Fprintf(out, " %s %s %s\n", k.Fingerprint, k.Type, k.Comment)
142 138 }
@@ -152,23 +148,29 @@ func (s *Server) adminUser(action string, args []string, actor string, in io.Rea
152 148 return nil, nil, err
153 149 }
154 150 return userData(a), func(out, _ io.Writer) {
155 fmt.Fprintf(out, "name: %s\nstatus: %s\nadmin: %t\ncreated: %s\n",
156 a.Name, a.Status, a.Admin, a.Created.Format(time.RFC3339))
151 fmt.Fprintf(out, "name: %s\nstatus: %s\nrole: %s\nadmin: %t\ncreated: %s\n",
152 a.Name, a.Status, a.Role, a.Admin, a.Created.Format(time.RFC3339))
157 153 for _, k := range a.Keys {
158 154 fmt.Fprintf(out, "key: %s %s %s\n", k.Fingerprint, k.Type, k.Comment)
159 155 }
160 156 }, nil
161 157
162 158 case "create":
163 f, err := parseAdminFlags(args, adminFlagSpec{long: "no-key", boolean: true})
159 f, err := parseAdminFlags(args,
160 adminFlagSpec{long: "no-key", boolean: true},
161 adminFlagSpec{long: "role"})
164 162 if err != nil {
165 163 return nil, nil, err
166 164 }
167 165 if len(f.rest) != 1 {
168 return nil, nil, &adminUsageError{msg: "usage: vole-admin user create [--no-key] <name> (public key on stdin unless --no-key)"}
166 return nil, nil, &adminUsageError{msg: "usage: vole-admin user create [--no-key] [--role <role>] <name> (public key on stdin unless --no-key)"}
167 }
168 role, err := access.ParseRole(f.one("role"))
169 if err != nil {
170 return nil, nil, err
169 171 }
170 172 if f.has("no-key") {
171 a, inv, err := s.cfg.Identity.CreateWithInvite(f.rest[0], actor, 0)
173 a, inv, err := s.cfg.Identity.CreateWithInviteRole(f.rest[0], role, actor, 0)
172 174 if err != nil {
173 175 return nil, nil, err
174 176 }
@@ -182,7 +184,7 @@ func (s *Server) adminUser(action string, args []string, actor string, in io.Rea
182 184 if err != nil {
183 185 return nil, nil, err
184 186 }
185 a, err := s.cfg.Identity.Create(f.rest[0], key, actor)
187 a, err := s.cfg.Identity.CreateWithRole(f.rest[0], role, key, actor)
186 188 if err != nil {
187 189 return nil, nil, err
188 190 }
@@ -193,6 +195,22 @@ func (s *Server) adminUser(action string, args []string, actor string, in io.Rea
193 195 }
194 196 }, nil
195 197
198 case "role":
199 if len(args) != 2 {
200 return nil, nil, &adminUsageError{msg: "usage: vole-admin user role <name> <none|read|contribute|maintain|admin>"}
201 }
202 role, err := access.ParseRole(args[1])
203 if err != nil {
204 return nil, nil, err
205 }
206 a, err := s.cfg.Identity.SetInstanceRole(args[0], role, actor)
207 if err != nil {
208 return nil, nil, err
209 }
210 return userData(a), func(out, _ io.Writer) {
211 fmt.Fprintf(out, "%s: %s\n", a.Name, a.Role)
212 }, nil
213
196 214 case "invite":
197 215 if len(args) != 1 {
198 216 return nil, nil, &adminUsageError{msg: "usage: vole-admin user invite <name>"}
@@ -519,7 +537,7 @@ func (s *Server) adminACL(action string, args []string, actor string) (any, admi
519 537
520 538 case "set":
521 539 if len(args) != 3 {
522 return nil, nil, &adminUsageError{msg: "usage: vole-admin acl set <repo> <user> <none|read|contributor|maintain|admin>"}
540 return nil, nil, &adminUsageError{msg: "usage: vole-admin acl set <repo> <user> <none|read|contribute|maintain|admin>"}
523 541 }
524 542 role, err := access.ParseRole(args[2])
525 543 if err != nil {
@@ -536,7 +554,7 @@ func (s *Server) adminACL(action string, args []string, actor string) (any, admi
536 554
537 555 case "default":
538 556 if len(args) != 2 {
539 return nil, nil, &adminUsageError{msg: "usage: vole-admin acl default <repo> <none|read|contributor|maintain|admin>"}
557 return nil, nil, &adminUsageError{msg: "usage: vole-admin acl default <repo> <none|read|contribute|maintain|admin>"}
540 558 }
541 559 role, err := access.ParseRole(args[1])
542 560 if err != nil {
@@ -760,6 +778,7 @@ func userData(a *identity.Account) map[string]any {
760 778 "name": a.Name,
761 779 "status": a.Status,
762 780 "admin": a.Admin,
781 "role": a.Role.String(),
763 782 "keys": a.Keys,
764 783 "created": a.Created,
765 784 }