Skip to content

Commit 56c5d5e

Browse files
kemzebGiteaBot
andauthored
Restrict branch naming when new change matches with protection rules (#36405)
Resolves #36381 by only allowing admins to perform branch renames that match to branch protection rules. --------- Co-authored-by: Giteabot <teabot@gitea.io>
1 parent e42a1db commit 56c5d5e

File tree

4 files changed

+45
-3
lines changed

4 files changed

+45
-3
lines changed

options/locale/locale_en-US.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2663,7 +2663,7 @@
26632663
"repo.branch.new_branch_from": "Create new branch from \"%s\"",
26642664
"repo.branch.renamed": "Branch %s was renamed to %s.",
26652665
"repo.branch.rename_default_or_protected_branch_error": "Only admins can rename default or protected branches.",
2666-
"repo.branch.rename_protected_branch_failed": "This branch is protected by glob-based protection rules.",
2666+
"repo.branch.rename_protected_branch_failed": "Failed to rename branch due to branch protection rules.",
26672667
"repo.branch.commits_divergence_from": "Commit divergence: %[1]d behind and %[2]d ahead of %[3]s",
26682668
"repo.branch.commits_no_divergence": "The same as branch %[1]s",
26692669
"repo.tag.create_tag": "Create tag %s",

routers/api/v1/repo/branch.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -515,7 +515,7 @@ func RenameBranch(ctx *context.APIContext) {
515515
case repo_model.IsErrUserDoesNotHaveAccessToRepo(err):
516516
ctx.APIError(http.StatusForbidden, "User must be a repo or site admin to rename default or protected branches.")
517517
case errors.Is(err, git_model.ErrBranchIsProtected):
518-
ctx.APIError(http.StatusForbidden, "Branch is protected by glob-based protection rules.")
518+
ctx.APIError(http.StatusForbidden, "Failed to rename branch due to branch protection rules.")
519519
default:
520520
ctx.APIErrorInternal(err)
521521
}

services/repository/branch.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -442,6 +442,15 @@ func RenameBranch(ctx context.Context, repo *repo_model.Repository, doer *user_m
442442
}
443443
}
444444

445+
// We also need to check if "to" matches with a protected branch rule.
446+
rule, err := git_model.GetFirstMatchProtectedBranchRule(ctx, repo.ID, to)
447+
if err != nil {
448+
return "", err
449+
}
450+
if rule != nil && !rule.CanUserPush(ctx, doer) {
451+
return "", git_model.ErrBranchIsProtected
452+
}
453+
445454
if err := git_model.RenameBranch(ctx, repo, from, to, func(ctx context.Context, isDefault bool) error {
446455
err2 := gitrepo.RenameBranch(ctx, repo, from, to)
447456
if err2 != nil {

tests/integration/api_branch_test.go

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -237,7 +237,40 @@ func TestAPIRenameBranch(t *testing.T) {
237237
MakeRequest(t, req, http.StatusCreated)
238238

239239
resp := testAPIRenameBranch(t, "user2", "user2", "repo1", from, "new-branch-name", http.StatusForbidden)
240-
assert.Contains(t, resp.Body.String(), "Branch is protected by glob-based protection rules.")
240+
assert.Contains(t, resp.Body.String(), "Failed to rename branch due to branch protection rules.")
241+
})
242+
t.Run("RenameBranchToMatchProtectionRulesWithAllowedUser", func(t *testing.T) {
243+
// allow an admin (the owner in this case) to rename a regular branch to one that matches a branch protection rule
244+
repoName := "repo1"
245+
ownerName := "user2"
246+
from := "regular-branch-1"
247+
ctx := NewAPITestContext(t, ownerName, repoName, auth_model.AccessTokenScopeWriteRepository)
248+
testAPICreateBranch(t, ctx.Session, ownerName, repoName, "", from, http.StatusCreated)
249+
250+
// NOTE: The protected/** branch protection rule was created in a previous test, with push enabled.
251+
testAPIRenameBranch(t, ownerName, ownerName, repoName, from, "protected/2", http.StatusNoContent)
252+
})
253+
t.Run("RenameBranchToMatchProtectionRulesWithUnauthorizedUser", func(t *testing.T) {
254+
// don't allow renaming a regular branch to a protected branch if the doer is not in the push whitelist
255+
repoName := "repo1"
256+
ownerName := "user2"
257+
pushWhitelist := []string{ownerName}
258+
token := getUserToken(t, "user2", auth_model.AccessTokenScopeWriteRepository)
259+
req := NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/branch_protections", ownerName, repoName),
260+
&api.BranchProtection{
261+
RuleName: "owner-protected/**",
262+
PushWhitelistUsernames: pushWhitelist,
263+
}).AddTokenAuth(token)
264+
MakeRequest(t, req, http.StatusCreated)
265+
266+
from := "regular-branch-2"
267+
ctx := NewAPITestContext(t, ownerName, repoName, auth_model.AccessTokenScopeWriteRepository)
268+
testAPICreateBranch(t, ctx.Session, ownerName, repoName, "", from, http.StatusCreated)
269+
270+
unprivilegedUser := "user40"
271+
resp := testAPIRenameBranch(t, unprivilegedUser, ownerName, repoName, from, "owner-protected/1", http.StatusForbidden)
272+
273+
assert.Contains(t, resp.Body.String(), "Failed to rename branch due to branch protection rules.")
241274
})
242275
t.Run("RenameBranchNormalScenario", func(t *testing.T) {
243276
testAPIRenameBranch(t, "user2", "user2", "repo1", "branch2", "new-branch-name", http.StatusNoContent)

0 commit comments

Comments
 (0)