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/git/vole.git

Write over SSH with a registered key:

git clone ssh://git@0.0.0.0:2222/vole.git

A free and open-source forge that lives in your Git repo.

Let the CLI do what the browser does for the same account

7e0b5950c190caf11089e1aaac10f18678074fed · Keenan Nemetz · 6d ago · parent 6e1fca51

Let the CLI do what the browser does for the same account

A contributor could file an issue, comment, manage the item it wrote, and open a
change from its own branch — but only in the browser, because a push to
`refs/heads/vole` required `maintain`. The role was not the reason: the hook had
no per-path authorization for that branch, so the role gate stood in for one.

The per-path checker is now applied to every session, not only to run
credentials. It asks the ACL the questions the browser's forms ask —
CanDiscuss, CanOpenRequest, CanTriage, CanApprove — so a contributor's key can
write what a contributor may write, and a maintainer's key is unchanged. A
non-maintainer's new file must name that account as its author, which is what the
browser does for it; a maintainer keeps writing an imported issue under its
original author, because that is a trust decision rather than a capability.

Creating the issues branch still needs a maintainer, as it does from the browser,
and an approval still cannot approve its author's own change — the tree validator
already refused that, and now every transport gets the same answer. The one rule
that moved is self-approval for a maintainer's push, which is now refused earlier
rather than only at the landing gate.

With the branch no longer bundled into `maintain`, CONTRIBUTING and the guides stop
telling contributors to use the web. Both transports are one authorization model.

Part of #168, stacked on #167, which built the checker this generalises.

server/hook/hook.go +9−13

@@ -153,11 +153,9 @@ func PreReceive(cfg Config) int {
153 153 }
154 154
155 155 // A session opened with a run credential acts for one job, in one repository,
156 // until that job ends. The role is still the account's; the credential only
157 // changes what a push to the issues branch may contain, because writing there
158 // otherwise requires maintain — a grant that is about vouching for everything,
159 // which a job cannot do.
160 var cred *runs.RunCredential
156 // until that job ends. The role is still the account's and the per-path rules below
157 // are the same ones a key gets; what the credential adds is the binding: this
158 // repository, and no other.
161 159 if keyID := strings.TrimSpace(cfg.KeyID); keyID != "" {
162 160 found, err := lookupRunCredential(cfg, keyID)
163 161 if err != nil {
@@ -173,7 +171,6 @@ func PreReceive(cfg Config) int {
173 171 reject(cfg.Out, "this job's credential is for %s, not %s", found.Repo, m.Name)
174 172 return 1
175 173 }
176 cred = found
177 174 }
178 175 }
179 176
@@ -219,9 +216,9 @@ func PreReceive(cfg Config) int {
219 216 // access. The check is here rather than at the role gate because the gate
220 217 // only knows that the account may push something.
221 218 if !role.AtLeast(access.Maintain) && !access.IsContributorRef(actor, c.ref) &&
222 !(cred != nil && c.ref == metadata.Ref) {
219 c.ref != metadata.Ref {
223 220 problems = append(problems, fmt.Sprintf(
224 "%s: the contribute role may only push branches under %s (role: %s)",
221 "%s: the contribute role may only push branches under %s and the issues branch (role: %s)",
225 222 c.ref, access.ContributorBranchPrefix(actor), role))
226 223 continue
227 224 }
@@ -429,11 +426,10 @@ func PreReceive(cfg Config) int {
429 426 for _, prob := range commentOwnership(g, c, actor, role, cfg.AuditPath, cfg.Out, m.Name) {
430 427 problems = append(problems, prob)
431 428 }
432 // A job acting with a run credential may write to the issues branch with the
433 // capabilities its account has, and nothing more.
434 if cred != nil {
435 problems = append(problems, scopedMetadata(g, c, a, actor)...)
436 }
429 // What this push may write there is the account's capabilities, per path.
430 // The browser applies the same rules one action at a time, and the two
431 // transports have to keep offering the same account the same things.
432 problems = append(problems, writableMetadata(g, c, a, actor)...)
437 433 }
438 434 }
439 435