diff --git a/models/fixtures/protected_branch.yml b/models/fixtures/protected_branch.yml index ca780a73aa0c1..13dbb63a90f59 100644 --- a/models/fixtures/protected_branch.yml +++ b/models/fixtures/protected_branch.yml @@ -1 +1,27 @@ -[] # empty +- + id: 1 + repo_id: 1 + branch_name: branch2 # master will be conflicted with TestRenameBranch test + can_push: true + enable_whitelist: false + whitelist_user_i_ds: '[]' + whitelist_team_i_ds: '[]' + enable_merge_whitelist: false + whitelist_deploy_keys: false + merge_whitelist_user_i_ds: '[]' + merge_whitelist_team_i_ds: '[]' + enable_status_check: false + status_check_contexts: '[]' + enable_approvals_whitelist: false + approvals_whitelist_user_i_ds: '[]' + approvals_whitelist_team_i_ds: '[]' + required_approvals: 0 + block_on_rejected_reviews: false + block_on_official_review_requests: false + block_on_outdated_branch: false + dismiss_stale_approvals: false + require_signed_commits: false + protected_file_patterns: 'disallow_push' + unprotected_file_patterns: 'allow_push' + created_unix: 1688973030 + updated_unix: 1688973030 diff --git a/services/repository/files/update.go b/services/repository/files/update.go index cade7ba2bf7db..58dc6c26a160f 100644 --- a/services/repository/files/update.go +++ b/services/repository/files/update.go @@ -547,44 +547,55 @@ func CreateOrUpdateFile(ctx context.Context, t *TemporaryUploadRepository, file return nil } +func checkTreePathProtected(ctx context.Context, protectedBranch *git_model.ProtectedBranch, doer *user_model.User, treePaths []string) error { + globUnprotected := protectedBranch.GetUnprotectedFilePatterns() + globProtected := protectedBranch.GetProtectedFilePatterns() + canUserPush := protectedBranch.CanUserPush(ctx, doer) + for _, treePath := range treePaths { + isUnprotectedFile := false + if len(globUnprotected) != 0 { + isUnprotectedFile = protectedBranch.IsUnprotectedFile(globUnprotected, treePath) + } + if !canUserPush && !isUnprotectedFile { + return ErrUserCannotCommit{ + UserName: doer.LowerName, + } + } + if !isUnprotectedFile && protectedBranch.IsProtectedFile(globProtected, treePath) { + return pull_service.ErrFilePathProtected{ + Path: treePath, + } + } + } + return nil +} + // VerifyBranchProtection verify the branch protection for modifying the given treePath on the given branch func VerifyBranchProtection(ctx context.Context, repo *repo_model.Repository, doer *user_model.User, branchName string, treePaths []string) error { protectedBranch, err := git_model.GetFirstMatchProtectedBranchRule(ctx, repo.ID, branchName) if err != nil { return err } - if protectedBranch != nil { - protectedBranch.Repo = repo - globUnprotected := protectedBranch.GetUnprotectedFilePatterns() - globProtected := protectedBranch.GetProtectedFilePatterns() - canUserPush := protectedBranch.CanUserPush(ctx, doer) - for _, treePath := range treePaths { - isUnprotectedFile := false - if len(globUnprotected) != 0 { - isUnprotectedFile = protectedBranch.IsUnprotectedFile(globUnprotected, treePath) - } - if !canUserPush && !isUnprotectedFile { - return ErrUserCannotCommit{ - UserName: doer.LowerName, - } - } - if protectedBranch.IsProtectedFile(globProtected, treePath) { - return pull_service.ErrFilePathProtected{ - Path: treePath, - } + if protectedBranch == nil { // no matched rule for branch name + return nil + } + + protectedBranch.Repo = repo + if err := checkTreePathProtected(ctx, protectedBranch, doer, treePaths); err != nil { + return err + } + + if protectedBranch.RequireSignedCommits { + _, _, _, err := asymkey_service.SignCRUDAction(ctx, repo.RepoPath(), doer, repo.RepoPath(), branchName) + if err != nil { + if !asymkey_service.IsErrWontSign(err) { + return err } - } - if protectedBranch.RequireSignedCommits { - _, _, _, err := asymkey_service.SignCRUDAction(ctx, repo.RepoPath(), doer, repo.RepoPath(), branchName) - if err != nil { - if !asymkey_service.IsErrWontSign(err) { - return err - } - return ErrUserCannotCommit{ - UserName: doer.LowerName, - } + return ErrUserCannotCommit{ + UserName: doer.LowerName, } } } + return nil } diff --git a/services/repository/files/update_test.go b/services/repository/files/update_test.go new file mode 100644 index 0000000000000..dd50bdd1aca51 --- /dev/null +++ b/services/repository/files/update_test.go @@ -0,0 +1,44 @@ +// Copyright 2023 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package files + +import ( + "testing" + + git_model "code.gitea.io/gitea/models/git" + "code.gitea.io/gitea/models/unittest" + user_model "code.gitea.io/gitea/models/user" + + "github.com/stretchr/testify/assert" +) + +func Test_checkTreePathProtected(t *testing.T) { + unittest.PrepareTestEnv(t) + + user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) + pb := unittest.AssertExistsAndLoadBean(t, &git_model.ProtectedBranch{ID: 1}) + + kases := []struct { + TreePath string + CanPush bool + }{ + { + TreePath: "allow_push", + CanPush: true, + }, + { + TreePath: "disallow_push", + CanPush: false, + }, + } + + for _, kase := range kases { + err := checkTreePathProtected(t.Context(), pb, user2, []string{kase.TreePath}) + if kase.CanPush { + assert.NoError(t, err) + } else { + assert.Error(t, err) + } + } +} diff --git a/tests/integration/api_branch_test.go b/tests/integration/api_branch_test.go index d64dd97f93f0f..5f53e9da40a29 100644 --- a/tests/integration/api_branch_test.go +++ b/tests/integration/api_branch_test.go @@ -293,7 +293,8 @@ func TestAPIBranchProtection(t *testing.T) { // Test branch deletion testAPIDeleteBranch(t, "master", http.StatusForbidden) - testAPIDeleteBranch(t, "branch2", http.StatusNoContent) + testAPIDeleteBranch(t, "branch2", http.StatusForbidden) + testAPIDeleteBranch(t, "bar", http.StatusNoContent) } func TestAPICreateBranchWithSyncBranches(t *testing.T) {