Add an option to protected branches to add writing deploy keys to the whitelist for pushing. Please note this is technically a breaking change: previously if the owner of a repository was on the whitelist then any writing deploy key was effectively on the whitelist. This option will now need to be set if that is desired. Closes #8472 Details: * Allow Protected Branches to Whitelist Deploy Keys * Add migration * Ensure that IsDeployKey is set to false on the http pushes * add not null default falsetags/v1.11.0-rc1
reponame := os.Getenv(models.EnvRepoName) | reponame := os.Getenv(models.EnvRepoName) | ||||
userID, _ := strconv.ParseInt(os.Getenv(models.EnvPusherID), 10, 64) | userID, _ := strconv.ParseInt(os.Getenv(models.EnvPusherID), 10, 64) | ||||
prID, _ := strconv.ParseInt(os.Getenv(models.ProtectedBranchPRID), 10, 64) | prID, _ := strconv.ParseInt(os.Getenv(models.ProtectedBranchPRID), 10, 64) | ||||
isDeployKey, _ := strconv.ParseBool(os.Getenv(models.EnvIsDeployKey)) | |||||
buf := bytes.NewBuffer(nil) | buf := bytes.NewBuffer(nil) | ||||
scanner := bufio.NewScanner(os.Stdin) | scanner := bufio.NewScanner(os.Stdin) | ||||
GitObjectDirectory: os.Getenv(private.GitObjectDirectory), | GitObjectDirectory: os.Getenv(private.GitObjectDirectory), | ||||
GitQuarantinePath: os.Getenv(private.GitQuarantinePath), | GitQuarantinePath: os.Getenv(private.GitQuarantinePath), | ||||
ProtectedBranchID: prID, | ProtectedBranchID: prID, | ||||
IsDeployKey: isDeployKey, | |||||
}) | }) | ||||
switch statusCode { | switch statusCode { | ||||
case http.StatusInternalServerError: | case http.StatusInternalServerError: |
os.Setenv(models.EnvPusherID, strconv.FormatInt(results.UserID, 10)) | os.Setenv(models.EnvPusherID, strconv.FormatInt(results.UserID, 10)) | ||||
os.Setenv(models.ProtectedBranchRepoID, strconv.FormatInt(results.RepoID, 10)) | os.Setenv(models.ProtectedBranchRepoID, strconv.FormatInt(results.RepoID, 10)) | ||||
os.Setenv(models.ProtectedBranchPRID, fmt.Sprintf("%d", 0)) | os.Setenv(models.ProtectedBranchPRID, fmt.Sprintf("%d", 0)) | ||||
os.Setenv(models.EnvIsDeployKey, fmt.Sprintf("%t", results.IsDeployKey)) | |||||
os.Setenv(models.EnvKeyID, fmt.Sprintf("%d", results.KeyID)) | |||||
//LFS token authentication | //LFS token authentication | ||||
if verb == lfsAuthenticateVerb { | if verb == lfsAuthenticateVerb { |
WhitelistUserIDs []int64 `xorm:"JSON TEXT"` | WhitelistUserIDs []int64 `xorm:"JSON TEXT"` | ||||
WhitelistTeamIDs []int64 `xorm:"JSON TEXT"` | WhitelistTeamIDs []int64 `xorm:"JSON TEXT"` | ||||
EnableMergeWhitelist bool `xorm:"NOT NULL DEFAULT false"` | EnableMergeWhitelist bool `xorm:"NOT NULL DEFAULT false"` | ||||
WhitelistDeployKeys bool `xorm:"NOT NULL DEFAULT false"` | |||||
MergeWhitelistUserIDs []int64 `xorm:"JSON TEXT"` | MergeWhitelistUserIDs []int64 `xorm:"JSON TEXT"` | ||||
MergeWhitelistTeamIDs []int64 `xorm:"JSON TEXT"` | MergeWhitelistTeamIDs []int64 `xorm:"JSON TEXT"` | ||||
EnableStatusCheck bool `xorm:"NOT NULL DEFAULT false"` | EnableStatusCheck bool `xorm:"NOT NULL DEFAULT false"` |
NewMigration("change length of some external login users columns", changeSomeColumnsLengthOfExternalLoginUser), | NewMigration("change length of some external login users columns", changeSomeColumnsLengthOfExternalLoginUser), | ||||
// v102 -> v103 | // v102 -> v103 | ||||
NewMigration("update migration repositories' service type", dropColumnHeadUserNameOnPullRequest), | NewMigration("update migration repositories' service type", dropColumnHeadUserNameOnPullRequest), | ||||
// v103 -> v104 | |||||
NewMigration("Add WhitelistDeployKeys to protected branch", addWhitelistDeployKeysToBranches), | |||||
} | } | ||||
// Migrate database to current version | // Migrate database to current version |
// Copyright 2019 The Gitea Authors. All rights reserved. | |||||
// Use of this source code is governed by a MIT-style | |||||
// license that can be found in the LICENSE file. | |||||
package migrations | |||||
import ( | |||||
"xorm.io/xorm" | |||||
) | |||||
func addWhitelistDeployKeysToBranches(x *xorm.Engine) error { | |||||
type ProtectedBranch struct { | |||||
ID int64 | |||||
WhitelistDeployKeys bool `xorm:"NOT NULL DEFAULT false"` | |||||
} | |||||
return x.Sync2(new(ProtectedBranch)) | |||||
} |
EnvPusherName = "GITEA_PUSHER_NAME" | EnvPusherName = "GITEA_PUSHER_NAME" | ||||
EnvPusherEmail = "GITEA_PUSHER_EMAIL" | EnvPusherEmail = "GITEA_PUSHER_EMAIL" | ||||
EnvPusherID = "GITEA_PUSHER_ID" | EnvPusherID = "GITEA_PUSHER_ID" | ||||
EnvKeyID = "GITEA_KEY_ID" | |||||
EnvIsDeployKey = "GITEA_IS_DEPLOY_KEY" | |||||
) | ) | ||||
// CommitToPushCommit transforms a git.Commit to PushCommit type. | // CommitToPushCommit transforms a git.Commit to PushCommit type. |
EnableWhitelist bool | EnableWhitelist bool | ||||
WhitelistUsers string | WhitelistUsers string | ||||
WhitelistTeams string | WhitelistTeams string | ||||
WhitelistDeployKeys bool | |||||
EnableMergeWhitelist bool | EnableMergeWhitelist bool | ||||
MergeWhitelistUsers string | MergeWhitelistUsers string | ||||
MergeWhitelistTeams string | MergeWhitelistTeams string |
GitAlternativeObjectDirectories string | GitAlternativeObjectDirectories string | ||||
GitQuarantinePath string | GitQuarantinePath string | ||||
ProtectedBranchID int64 | ProtectedBranchID int64 | ||||
IsDeployKey bool | |||||
} | } | ||||
// HookPreReceive check whether the provided commits are allowed | // HookPreReceive check whether the provided commits are allowed | ||||
func HookPreReceive(ownerName, repoName string, opts HookOptions) (int, string) { | func HookPreReceive(ownerName, repoName string, opts HookOptions) (int, string) { | ||||
reqURL := setting.LocalURL + fmt.Sprintf("api/internal/hook/pre-receive/%s/%s?old=%s&new=%s&ref=%s&userID=%d&gitObjectDirectory=%s&gitAlternativeObjectDirectories=%s&gitQuarantinePath=%s&prID=%d", | |||||
reqURL := setting.LocalURL + fmt.Sprintf("api/internal/hook/pre-receive/%s/%s?old=%s&new=%s&ref=%s&userID=%d&gitObjectDirectory=%s&gitAlternativeObjectDirectories=%s&gitQuarantinePath=%s&prID=%d&isDeployKey=%t", | |||||
url.PathEscape(ownerName), | url.PathEscape(ownerName), | ||||
url.PathEscape(repoName), | url.PathEscape(repoName), | ||||
url.QueryEscape(opts.OldCommitID), | url.QueryEscape(opts.OldCommitID), | ||||
url.QueryEscape(opts.GitAlternativeObjectDirectories), | url.QueryEscape(opts.GitAlternativeObjectDirectories), | ||||
url.QueryEscape(opts.GitQuarantinePath), | url.QueryEscape(opts.GitQuarantinePath), | ||||
opts.ProtectedBranchID, | opts.ProtectedBranchID, | ||||
opts.IsDeployKey, | |||||
) | ) | ||||
resp, err := newInternalRequest(reqURL, "GET").Response() | resp, err := newInternalRequest(reqURL, "GET").Response() |
settings.protect_this_branch_desc = Prevent deletion and disable any Git pushing to the branch. | settings.protect_this_branch_desc = Prevent deletion and disable any Git pushing to the branch. | ||||
settings.protect_whitelist_committers = Enable Push Whitelist | settings.protect_whitelist_committers = Enable Push Whitelist | ||||
settings.protect_whitelist_committers_desc = Allow whitelisted users or teams to push to this branch (but not force push). | settings.protect_whitelist_committers_desc = Allow whitelisted users or teams to push to this branch (but not force push). | ||||
settings.protect_whitelist_deploy_keys = Whitelist deploy keys with write access to push | |||||
settings.protect_whitelist_users = Whitelisted users for pushing: | settings.protect_whitelist_users = Whitelisted users for pushing: | ||||
settings.protect_whitelist_search_users = Search users… | settings.protect_whitelist_search_users = Search users… | ||||
settings.protect_whitelist_teams = Whitelisted teams for pushing: | settings.protect_whitelist_teams = Whitelisted teams for pushing: |
gitAlternativeObjectDirectories := ctx.QueryTrim("gitAlternativeObjectDirectories") | gitAlternativeObjectDirectories := ctx.QueryTrim("gitAlternativeObjectDirectories") | ||||
gitQuarantinePath := ctx.QueryTrim("gitQuarantinePath") | gitQuarantinePath := ctx.QueryTrim("gitQuarantinePath") | ||||
prID := ctx.QueryInt64("prID") | prID := ctx.QueryInt64("prID") | ||||
isDeployKey := ctx.QueryBool("isDeployKey") | |||||
branchName := strings.TrimPrefix(refFullName, git.BranchPrefix) | branchName := strings.TrimPrefix(refFullName, git.BranchPrefix) | ||||
repo, err := models.GetRepositoryByOwnerAndName(ownerName, repoName) | repo, err := models.GetRepositoryByOwnerAndName(ownerName, repoName) | ||||
} | } | ||||
} | } | ||||
canPush := protectBranch.CanUserPush(userID) | |||||
canPush := false | |||||
if isDeployKey { | |||||
canPush = protectBranch.WhitelistDeployKeys | |||||
} else { | |||||
canPush = protectBranch.CanUserPush(userID) | |||||
} | |||||
if !canPush && prID > 0 { | if !canPush && prID > 0 { | ||||
pr, err := models.GetPullRequestByID(prID) | pr, err := models.GetPullRequestByID(prID) | ||||
if err != nil { | if err != nil { |
models.EnvPusherName + "=" + authUser.Name, | models.EnvPusherName + "=" + authUser.Name, | ||||
models.EnvPusherID + fmt.Sprintf("=%d", authUser.ID), | models.EnvPusherID + fmt.Sprintf("=%d", authUser.ID), | ||||
models.ProtectedBranchRepoID + fmt.Sprintf("=%d", repo.ID), | models.ProtectedBranchRepoID + fmt.Sprintf("=%d", repo.ID), | ||||
models.EnvIsDeployKey + "=false", | |||||
} | } | ||||
if !authUser.KeepEmailPrivate { | if !authUser.KeepEmailPrivate { |
protectBranch.EnableStatusCheck = f.EnableStatusCheck | protectBranch.EnableStatusCheck = f.EnableStatusCheck | ||||
protectBranch.StatusCheckContexts = f.StatusCheckContexts | protectBranch.StatusCheckContexts = f.StatusCheckContexts | ||||
protectBranch.WhitelistDeployKeys = f.WhitelistDeployKeys | |||||
protectBranch.RequiredApprovals = f.RequiredApprovals | protectBranch.RequiredApprovals = f.RequiredApprovals | ||||
if strings.TrimSpace(f.ApprovalsWhitelistUsers) != "" { | if strings.TrimSpace(f.ApprovalsWhitelistUsers) != "" { |
</div> | </div> | ||||
</div> | </div> | ||||
{{end}} | {{end}} | ||||
<br> | |||||
<div class="whitelist field"> | |||||
<div class="ui checkbox"> | |||||
<input type="checkbox" name="whitelist_deploy_keys" {{if .Branch.WhitelistDeployKeys}}checked{{end}}> | |||||
<label for="whitelist_deploy_keys">{{.i18n.Tr "repo.settings.protect_whitelist_deploy_keys"}}</label> | |||||
</div> | |||||
</div> | |||||
</div> | </div> | ||||
<div class="field"> | <div class="field"> |