This commit is contained in:
wxiaoguang
2026-06-16 08:48:53 +08:00
parent 95a300e1f4
commit 6b416d6135
2 changed files with 29 additions and 22 deletions
+26 -19
View File
@@ -48,28 +48,35 @@ type preReceiveContext struct {
opts *private.HookOptions
// this context should only contain shared variables, mutable variables like "current branch name" shouldn't be put here
canWriteCodeUnitCached *bool
}
// CanWriteCode returns true if pusher can write code
func (ctx *preReceiveContext) CanWriteCode(refFullName git.RefName) bool {
if !ctx.loadPusherAndPermission() {
func (ctx *preReceiveContext) canWriteCodeUnit() bool {
if ctx.canWriteCodeUnitCached == nil {
var canWrite bool
if ctx.loadPusherAndPermission() {
canWrite = ctx.userPerm.CanWrite(unit.TypeCode) || ctx.deployKeyAccessMode >= perm_model.AccessModeWrite
}
ctx.canWriteCodeUnitCached = &canWrite
}
return *ctx.canWriteCodeUnitCached
}
// canWriteCodeRef returns true if pusher can write to the code ref (branch/tag/commit)
func (ctx *preReceiveContext) canWriteCodeRef(refFullName git.RefName) bool {
if ctx.canWriteCodeUnit() {
return true
}
// then check whether if the pusher is a maintainer who can write the PR author's head repo branch
if !refFullName.IsBranch() {
return false
}
// The maintainer-edit grant is scoped to a single PR head branch, so it must be evaluated
// against the exact branch being pushed and only for branch refs. Tags and other refs can
// never match a PR head branch, so they pass an empty name. Deriving this per ref (instead of
// from shared mutable state) prevents a per-branch grant from authorizing writes to other refs
// batched into the same push.
maintainerEditBranch := ""
if refFullName.IsBranch() {
maintainerEditBranch = refFullName.BranchName()
}
return issues_model.CanMaintainerWriteToBranch(ctx, ctx.userPerm, maintainerEditBranch, ctx.user) || ctx.deployKeyAccessMode >= perm_model.AccessModeWrite
return issues_model.CanMaintainerWriteToBranch(ctx, ctx.userPerm, refFullName.BranchName(), ctx.user)
}
// AssertCanWriteCode returns true if pusher can write code
func (ctx *preReceiveContext) AssertCanWriteCode(refFullName git.RefName) bool {
if !ctx.CanWriteCode(refFullName) {
// assertCanWriteRef returns true if pusher can write to the code ref, otherwise it responds with 403 Forbidden and returns false
func (ctx *preReceiveContext) assertCanWriteRef(refFullName git.RefName) bool {
if !ctx.canWriteCodeRef(refFullName) {
if ctx.Written() {
return false
}
@@ -131,7 +138,7 @@ func HookPreReceive(ctx *gitea_context.PrivateContext) {
case git.DefaultFeatures().SupportProcReceive && refFullName.IsFor():
preReceiveFor(ourCtx, refFullName)
default:
ourCtx.AssertCanWriteCode(refFullName)
ourCtx.assertCanWriteRef(refFullName)
}
if ctx.Written() {
return
@@ -144,7 +151,7 @@ func HookPreReceive(ctx *gitea_context.PrivateContext) {
func preReceiveBranch(ctx *preReceiveContext, oldCommitID, newCommitID string, refFullName git.RefName) {
branchName := refFullName.BranchName()
if !ctx.AssertCanWriteCode(refFullName) {
if !ctx.assertCanWriteRef(refFullName) {
return
}
@@ -405,7 +412,7 @@ func preReceiveBranch(ctx *preReceiveContext, oldCommitID, newCommitID string, r
}
func preReceiveTag(ctx *preReceiveContext, refFullName git.RefName) {
if !ctx.AssertCanWriteCode(refFullName) {
if !ctx.assertCanWriteRef(refFullName) {
return
}
+3 -3
View File
@@ -58,13 +58,13 @@ func TestPreReceiveCanWriteCodePerBranch(t *testing.T) {
}
// The granted branch must be writable...
assert.True(t, ctx.CanWriteCode(git.RefNameFromBranch("granted-branch")))
assert.True(t, ctx.canWriteCodeRef(git.RefNameFromBranch("granted-branch")))
// ...but another branch in the same push must NOT inherit that grant.
assert.False(t, ctx.CanWriteCode(git.RefNameFromBranch("master")))
assert.False(t, ctx.canWriteCodeRef(git.RefNameFromBranch("master")))
// ...and a tag sharing the granted branch's name must NOT inherit it either: the grant is
// scoped to PR head branches, so a non-branch ref can never match it. (A tag ref already
// yields an empty branch name, so this guards the per-ref evaluation, not the IsBranch check.)
assert.False(t, ctx.CanWriteCode(git.RefNameFromTag("granted-branch")))
assert.False(t, ctx.canWriteCodeRef(git.RefNameFromTag("granted-branch")))
}