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 | } | |