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/hook/hook.go +16−4
| @@ -16,6 +16,7 @@ import ( | |||
| 16 | 16 | "github.com/rodent-software/vole/core/git" | |
| 17 | 17 | "github.com/rodent-software/vole/core/lfs" | |
| 18 | 18 | "github.com/rodent-software/vole/forge/access" | |
| 19 | "github.com/rodent-software/vole/forge/identity" | ||
| 19 | 20 | "github.com/rodent-software/vole/forge/repo" | |
| 20 | 21 | "github.com/rodent-software/vole/internal/format" | |
| 21 | 22 | "github.com/rodent-software/vole/metadata" | |
| @@ -73,6 +74,17 @@ func PreReceive(cfg Config) int { | |||
| 73 | 74 | return 1 | |
| 74 | 75 | } | |
| 75 | 76 | defer acls.Close() | |
| 77 | // The push gate runs as a separate process, so it opens the roster itself | ||
| 78 | // to learn the actor's instance-wide role. Without this the hook would | ||
| 79 | // enforce only repository entries and silently ignore an invitation that | ||
| 80 | // granted contribute everywhere. | ||
| 81 | ident, err := identity.Open(cfg.UsersDB) | ||
| 82 | if err != nil { | ||
| 83 | reject(cfg.Out, "cannot open the roster: %v", err) | ||
| 84 | return 1 | ||
| 85 | } | ||
| 86 | defer ident.Close() | ||
| 87 | acls.InstanceRole = ident.InstanceRole | ||
| 76 | 88 | // The lock table shares that database, so the push path learns what is held | |
| 77 | 89 | // without a second endpoint and without the client's cooperation. | |
| 78 | 90 | lockTable, err = lfs.OpenLocks(cfg.UsersDB) | |
| @@ -110,8 +122,8 @@ func PreReceive(cfg Config) int { | |||
| 110 | 122 | } | |
| 111 | 123 | ||
| 112 | 124 | role := a.RoleFor(actor) | |
| 113 | if !role.AtLeast(access.Contributor) { | ||
| 114 | reject(cfg.Out, "%s does not have maintain access or contributor access to %s (role: %s)", actor, m.Name, role) | ||
| 125 | if !role.AtLeast(access.Contribute) { | ||
| 126 | reject(cfg.Out, "%s does not have maintain or contribute access to %s (role: %s)", actor, m.Name, role) | ||
| 115 | 127 | return 1 | |
| 116 | 128 | } | |
| 117 | 129 | ||
| @@ -155,7 +167,7 @@ func PreReceive(cfg Config) int { | |||
| 155 | 167 | // only knows that the account may push something. | |
| 156 | 168 | if !role.AtLeast(access.Maintain) && !access.IsContributorRef(actor, c.ref) { | |
| 157 | 169 | problems = append(problems, fmt.Sprintf( | |
| 158 | "%s: contributors may only push branches under %s (role: %s)", | ||
| 170 | "%s: the contribute role may only push branches under %s (role: %s)", | ||
| 159 | 171 | c.ref, access.ContributorBranchPrefix(actor), role)) | |
| 160 | 172 | continue | |
| 161 | 173 | } | |
| @@ -182,7 +194,7 @@ func PreReceive(cfg Config) int { | |||
| 182 | 194 | case role.AtLeast(access.Admin): | |
| 183 | 195 | auditLine(cfg.AuditPath, fmt.Sprintf("force-push %s %s -> %s by %s in %s", | |
| 184 | 196 | c.ref, format.SHA(c.old), format.SHA(c.new), actor, m.Name)) | |
| 185 | case role == access.Contributor && access.IsContributorRef(actor, c.ref): | ||
| 197 | case role == access.Contribute && access.IsContributorRef(actor, c.ref): | ||
| 186 | 198 | // The namespace is theirs: rebasing or deleting their own branch is | |
| 187 | 199 | // not a rewrite of shared history. | |
| 188 | 200 | default: | |