Allow force pushes to protected branches

This commit is contained in:
Henry Goodman
2023-11-24 23:55:11 +11:00
committed by Henry Goodman
parent 6c7744370f
commit 50819b0424
11 changed files with 387 additions and 37 deletions
+76 -6
View File
@@ -552,6 +552,15 @@ func CreateBranchProtection(ctx *context.APIContext) {
ctx.Error(http.StatusInternalServerError, "GetUserIDsByNames", err)
return
}
forcePushWhitelistUsers, err := user_model.GetUserIDsByNames(ctx, form.ForcePushWhitelistUsernames, false)
if err != nil {
if user_model.IsErrUserNotExist(err) {
ctx.Error(http.StatusUnprocessableEntity, "User does not exist", err)
return
}
ctx.Error(http.StatusInternalServerError, "GetUserIDsByNames", err)
return
}
mergeWhitelistUsers, err := user_model.GetUserIDsByNames(ctx, form.MergeWhitelistUsernames, false)
if err != nil {
if user_model.IsErrUserNotExist(err) {
@@ -570,7 +579,7 @@ func CreateBranchProtection(ctx *context.APIContext) {
ctx.Error(http.StatusInternalServerError, "GetUserIDsByNames", err)
return
}
var whitelistTeams, mergeWhitelistTeams, approvalsWhitelistTeams []int64
var whitelistTeams, forcePushWhitelistTeams, mergeWhitelistTeams, approvalsWhitelistTeams []int64
if repo.Owner.IsOrganization() {
whitelistTeams, err = organization.GetTeamIDsByNames(ctx, repo.OwnerID, form.PushWhitelistTeams, false)
if err != nil {
@@ -581,6 +590,15 @@ func CreateBranchProtection(ctx *context.APIContext) {
ctx.Error(http.StatusInternalServerError, "GetTeamIDsByNames", err)
return
}
forcePushWhitelistTeams, err = organization.GetTeamIDsByNames(ctx, repo.OwnerID, form.ForcePushWhitelistTeams, false)
if err != nil {
if organization.IsErrTeamNotExist(err) {
ctx.Error(http.StatusUnprocessableEntity, "Team does not exist", err)
return
}
ctx.Error(http.StatusInternalServerError, "GetTeamIDsByNames", err)
return
}
mergeWhitelistTeams, err = organization.GetTeamIDsByNames(ctx, repo.OwnerID, form.MergeWhitelistTeams, false)
if err != nil {
if organization.IsErrTeamNotExist(err) {
@@ -606,8 +624,11 @@ func CreateBranchProtection(ctx *context.APIContext) {
RuleName: ruleName,
CanPush: form.EnablePush,
EnableWhitelist: form.EnablePush && form.EnablePushWhitelist,
EnableMergeWhitelist: form.EnableMergeWhitelist,
WhitelistDeployKeys: form.EnablePush && form.EnablePushWhitelist && form.PushWhitelistDeployKeys,
CanForcePush: form.EnablePush && form.EnableForcePush,
EnableForcePushWhitelist: form.EnablePush && form.EnableForcePush && form.EnableForcePushWhitelist,
ForcePushWhitelistDeployKeys: form.EnablePush && form.EnableForcePush && form.EnableForcePushWhitelist && form.ForcePushWhitelistDeployKeys,
EnableMergeWhitelist: form.EnableMergeWhitelist,
EnableStatusCheck: form.EnableStatusCheck,
StatusCheckContexts: form.StatusCheckContexts,
EnableApprovalsWhitelist: form.EnableApprovalsWhitelist,
@@ -624,6 +645,8 @@ func CreateBranchProtection(ctx *context.APIContext) {
err = git_model.UpdateProtectBranch(ctx, ctx.Repo.Repository, protectBranch, git_model.WhitelistOptions{
UserIDs: whitelistUsers,
TeamIDs: whitelistTeams,
ForcePushUserIDs: forcePushWhitelistUsers,
ForcePushTeamIDs: forcePushWhitelistTeams,
MergeUserIDs: mergeWhitelistUsers,
MergeTeamIDs: mergeWhitelistTeams,
ApprovalsUserIDs: approvalsWhitelistUsers,
@@ -754,6 +777,27 @@ func EditBranchProtection(ctx *context.APIContext) {
}
}
if form.EnableForcePush != nil {
if !*form.EnableForcePush {
protectBranch.CanForcePush = false
protectBranch.EnableForcePushWhitelist = false
protectBranch.ForcePushWhitelistDeployKeys = false
} else {
protectBranch.CanForcePush = true
if form.EnableForcePushWhitelist != nil {
if !*form.EnableForcePushWhitelist {
protectBranch.EnableForcePushWhitelist = false
protectBranch.ForcePushWhitelistDeployKeys = false
} else {
protectBranch.EnableForcePushWhitelist = true
if form.ForcePushWhitelistDeployKeys != nil {
protectBranch.ForcePushWhitelistDeployKeys = *form.ForcePushWhitelistDeployKeys
}
}
}
}
}
if form.EnableMergeWhitelist != nil {
protectBranch.EnableMergeWhitelist = *form.EnableMergeWhitelist
}
@@ -802,7 +846,7 @@ func EditBranchProtection(ctx *context.APIContext) {
protectBranch.BlockOnOutdatedBranch = *form.BlockOnOutdatedBranch
}
var whitelistUsers []int64
var whitelistUsers, forcePushWhitelistUsers, mergeWhitelistUsers, approvalsWhitelistUsers []int64
if form.PushWhitelistUsernames != nil {
whitelistUsers, err = user_model.GetUserIDsByNames(ctx, form.PushWhitelistUsernames, false)
if err != nil {
@@ -816,7 +860,19 @@ func EditBranchProtection(ctx *context.APIContext) {
} else {
whitelistUsers = protectBranch.WhitelistUserIDs
}
var mergeWhitelistUsers []int64
if form.ForcePushWhitelistUsernames != nil {
forcePushWhitelistUsers, err = user_model.GetUserIDsByNames(ctx, form.ForcePushWhitelistUsernames, false)
if err != nil {
if user_model.IsErrUserNotExist(err) {
ctx.Error(http.StatusUnprocessableEntity, "User does not exist", err)
return
}
ctx.Error(http.StatusInternalServerError, "GetUserIDsByNames", err)
return
}
} else {
forcePushWhitelistUsers = protectBranch.ForcePushWhitelistUserIDs
}
if form.MergeWhitelistUsernames != nil {
mergeWhitelistUsers, err = user_model.GetUserIDsByNames(ctx, form.MergeWhitelistUsernames, false)
if err != nil {
@@ -830,7 +886,6 @@ func EditBranchProtection(ctx *context.APIContext) {
} else {
mergeWhitelistUsers = protectBranch.MergeWhitelistUserIDs
}
var approvalsWhitelistUsers []int64
if form.ApprovalsWhitelistUsernames != nil {
approvalsWhitelistUsers, err = user_model.GetUserIDsByNames(ctx, form.ApprovalsWhitelistUsernames, false)
if err != nil {
@@ -845,7 +900,7 @@ func EditBranchProtection(ctx *context.APIContext) {
approvalsWhitelistUsers = protectBranch.ApprovalsWhitelistUserIDs
}
var whitelistTeams, mergeWhitelistTeams, approvalsWhitelistTeams []int64
var whitelistTeams, forcePushWhitelistTeams, mergeWhitelistTeams, approvalsWhitelistTeams []int64
if repo.Owner.IsOrganization() {
if form.PushWhitelistTeams != nil {
whitelistTeams, err = organization.GetTeamIDsByNames(ctx, repo.OwnerID, form.PushWhitelistTeams, false)
@@ -860,6 +915,19 @@ func EditBranchProtection(ctx *context.APIContext) {
} else {
whitelistTeams = protectBranch.WhitelistTeamIDs
}
if form.ForcePushWhitelistTeams != nil {
forcePushWhitelistTeams, err = organization.GetTeamIDsByNames(ctx, repo.OwnerID, form.ForcePushWhitelistTeams, false)
if err != nil {
if organization.IsErrTeamNotExist(err) {
ctx.Error(http.StatusUnprocessableEntity, "Team does not exist", err)
return
}
ctx.Error(http.StatusInternalServerError, "GetTeamIDsByNames", err)
return
}
} else {
forcePushWhitelistTeams = protectBranch.ForcePushWhitelistTeamIDs
}
if form.MergeWhitelistTeams != nil {
mergeWhitelistTeams, err = organization.GetTeamIDsByNames(ctx, repo.OwnerID, form.MergeWhitelistTeams, false)
if err != nil {
@@ -891,6 +959,8 @@ func EditBranchProtection(ctx *context.APIContext) {
err = git_model.UpdateProtectBranch(ctx, ctx.Repo.Repository, protectBranch, git_model.WhitelistOptions{
UserIDs: whitelistUsers,
TeamIDs: whitelistTeams,
ForcePushUserIDs: forcePushWhitelistUsers,
ForcePushTeamIDs: forcePushWhitelistTeams,
MergeUserIDs: mergeWhitelistUsers,
MergeTeamIDs: mergeWhitelistTeams,
ApprovalsUserIDs: approvalsWhitelistUsers,
+31 -10
View File
@@ -182,7 +182,9 @@ func preReceiveBranch(ctx *preReceiveContext, oldCommitID, newCommitID string, r
return
}
// 2. Disallow force pushes to protected branches
isForcePush := false
// 2. Disallow force pushes to protected branches if the option is unchecked
if git.EmptySHA != oldCommitID {
output, _, err := git.NewCommand(ctx, "rev-list", "--max-count=1").AddDynamicArguments(oldCommitID, "^"+newCommitID).RunStdString(&git.RunOpts{Dir: repo.RepoPath(), Env: ctx.env})
if err != nil {
@@ -192,12 +194,15 @@ func preReceiveBranch(ctx *preReceiveContext, oldCommitID, newCommitID string, r
})
return
} else if len(output) > 0 {
log.Warn("Forbidden: Branch: %s in %-v is protected from force push", branchName, repo)
ctx.JSON(http.StatusForbidden, private.Response{
UserMsg: fmt.Sprintf("branch %s is protected from force push", branchName),
})
return
if protectBranch.CanForcePush {
isForcePush = true
} else {
log.Warn("Forbidden: Branch: %s in %-v is protected from force push", branchName, repo)
ctx.JSON(http.StatusForbidden, private.Response{
UserMsg: fmt.Sprintf("branch %s is protected from force push", branchName),
})
return
}
}
}
@@ -244,10 +249,15 @@ func preReceiveBranch(ctx *preReceiveContext, oldCommitID, newCommitID string, r
}
}
// 5. Check if the doer is allowed to push
// 5. Check if the doer is allowed to push (and force-push if the incoming push is a force-push)
var canPush bool
if ctx.opts.DeployKeyID != 0 {
canPush = !changedProtectedfiles && protectBranch.CanPush && (!protectBranch.EnableWhitelist || protectBranch.WhitelistDeployKeys)
// This flag is only ever true if protectBranch.CanForcePush is true
if isForcePush {
canPush = !changedProtectedfiles && protectBranch.CanPush && (!protectBranch.EnableForcePushWhitelist || protectBranch.ForcePushWhitelistDeployKeys)
} else {
canPush = !changedProtectedfiles && protectBranch.CanPush && (!protectBranch.EnableWhitelist || protectBranch.WhitelistDeployKeys)
}
} else {
user, err := user_model.GetUserByID(ctx, ctx.opts.UserID)
if err != nil {
@@ -257,7 +267,11 @@ func preReceiveBranch(ctx *preReceiveContext, oldCommitID, newCommitID string, r
})
return
}
canPush = !changedProtectedfiles && protectBranch.CanUserPush(ctx, user)
if isForcePush {
canPush = !changedProtectedfiles && protectBranch.CanUserForcePush(ctx, user)
} else {
canPush = !changedProtectedfiles && protectBranch.CanUserPush(ctx, user)
}
}
// 6. If we're not allowed to push directly
@@ -293,6 +307,13 @@ func preReceiveBranch(ctx *preReceiveContext, oldCommitID, newCommitID string, r
}
// Or we're simply not able to push to this protected branch
if isForcePush {
log.Warn("Forbidden: User %d is not allowed to force-push to protected branch: %s in %-v", ctx.opts.UserID, branchName, repo)
ctx.JSON(http.StatusForbidden, private.Response{
UserMsg: fmt.Sprintf("Not allowued to force-push to protected branch %s", branchName),
})
return
}
log.Warn("Forbidden: User %d is not allowed to push to protected branch: %s in %-v", ctx.opts.UserID, branchName, repo)
ctx.JSON(http.StatusForbidden, private.Response{
UserMsg: fmt.Sprintf("Not allowed to push to protected branch %s", branchName),
+26 -1
View File
@@ -77,6 +77,7 @@ func SettingsProtectedBranch(c *context.Context) {
}
c.Data["Users"] = users
c.Data["whitelist_users"] = strings.Join(base.Int64sToStrings(rule.WhitelistUserIDs), ",")
c.Data["force_push_whitelist_users"] = strings.Join(base.Int64sToStrings(rule.ForcePushWhitelistUserIDs), ",")
c.Data["merge_whitelist_users"] = strings.Join(base.Int64sToStrings(rule.MergeWhitelistUserIDs), ",")
c.Data["approvals_whitelist_users"] = strings.Join(base.Int64sToStrings(rule.ApprovalsWhitelistUserIDs), ",")
c.Data["status_check_contexts"] = strings.Join(rule.StatusCheckContexts, "\n")
@@ -91,6 +92,7 @@ func SettingsProtectedBranch(c *context.Context) {
}
c.Data["Teams"] = teams
c.Data["whitelist_teams"] = strings.Join(base.Int64sToStrings(rule.WhitelistTeamIDs), ",")
c.Data["force_push_whitelist_teams"] = strings.Join(base.Int64sToStrings(rule.ForcePushWhitelistTeamIDs), ",")
c.Data["merge_whitelist_teams"] = strings.Join(base.Int64sToStrings(rule.MergeWhitelistTeamIDs), ",")
c.Data["approvals_whitelist_teams"] = strings.Join(base.Int64sToStrings(rule.ApprovalsWhitelistTeamIDs), ",")
}
@@ -149,7 +151,7 @@ func SettingsProtectedBranchPost(ctx *context.Context) {
}
}
var whitelistUsers, whitelistTeams, mergeWhitelistUsers, mergeWhitelistTeams, approvalsWhitelistUsers, approvalsWhitelistTeams []int64
var whitelistUsers, whitelistTeams, forcePushWhitelistUsers, forcePushWhitelistTeams, mergeWhitelistUsers, mergeWhitelistTeams, approvalsWhitelistUsers, approvalsWhitelistTeams []int64
protectBranch.RuleName = f.RuleName
if f.RequiredApprovals < 0 {
ctx.Flash.Error(ctx.Tr("repo.settings.protected_branch_required_approvals_min"))
@@ -178,6 +180,27 @@ func SettingsProtectedBranchPost(ctx *context.Context) {
protectBranch.WhitelistDeployKeys = false
}
switch f.EnableForcePush {
case "all":
protectBranch.CanForcePush = true
protectBranch.EnableForcePushWhitelist = false
protectBranch.ForcePushWhitelistDeployKeys = false
case "whitelist":
protectBranch.CanForcePush = true
protectBranch.EnableForcePushWhitelist = true
protectBranch.ForcePushWhitelistDeployKeys = f.ForcePushWhitelistDeployKeys
if strings.TrimSpace(f.ForcePushWhitelistUsers) != "" {
forcePushWhitelistUsers, _ = base.StringsToInt64s(strings.Split(f.ForcePushWhitelistUsers, ","))
}
if strings.TrimSpace(f.ForcePushWhitelistTeams) != "" {
forcePushWhitelistTeams, _ = base.StringsToInt64s(strings.Split(f.ForcePushWhitelistTeams, ","))
}
default:
protectBranch.CanForcePush = false
protectBranch.EnableForcePushWhitelist = false
protectBranch.ForcePushWhitelistDeployKeys = false
}
protectBranch.EnableMergeWhitelist = f.EnableMergeWhitelist
if f.EnableMergeWhitelist {
if strings.TrimSpace(f.MergeWhitelistUsers) != "" {
@@ -236,6 +259,8 @@ func SettingsProtectedBranchPost(ctx *context.Context) {
err = git_model.UpdateProtectBranch(ctx, ctx.Repo.Repository, protectBranch, git_model.WhitelistOptions{
UserIDs: whitelistUsers,
TeamIDs: whitelistTeams,
ForcePushUserIDs: forcePushWhitelistUsers,
ForcePushTeamIDs: forcePushWhitelistTeams,
MergeUserIDs: mergeWhitelistUsers,
MergeTeamIDs: mergeWhitelistTeams,
ApprovalsUserIDs: approvalsWhitelistUsers,