This commit is contained in:
Henry Goodman 2024-04-20 09:38:53 +03:00 committed by GitHub
commit fe9b826d8c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 422 additions and 68 deletions

View File

@ -38,10 +38,15 @@ type ProtectedBranch struct {
isPlainName bool `xorm:"-"`
CanPush bool `xorm:"NOT NULL DEFAULT false"`
EnableWhitelist bool
WhitelistUserIDs []int64 `xorm:"JSON TEXT"`
WhitelistTeamIDs []int64 `xorm:"JSON TEXT"`
WhitelistUserIDs []int64 `xorm:"JSON TEXT"`
WhitelistTeamIDs []int64 `xorm:"JSON TEXT"`
WhitelistDeployKeys bool `xorm:"NOT NULL DEFAULT false"`
CanForcePush bool `xorm:"NOT NULL DEFAULT false"`
EnableForcePushWhitelist bool
ForcePushWhitelistUserIDs []int64 `xorm:"JSON TEXT"`
ForcePushWhitelistTeamIDs []int64 `xorm:"JSON TEXT"`
ForcePushWhitelistDeployKeys 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"`
MergeWhitelistTeamIDs []int64 `xorm:"JSON TEXT"`
EnableStatusCheck bool `xorm:"NOT NULL DEFAULT false"`
@ -143,6 +148,33 @@ func (protectBranch *ProtectedBranch) CanUserPush(ctx context.Context, user *use
return in
}
// CanUserForcePush returns if some user could force push to this protected branch
// Since force-push extends normal push, we also check if user has regular push access
func (protectBranch *ProtectedBranch) CanUserForcePush(ctx context.Context, user *user_model.User) bool {
if !protectBranch.CanForcePush {
return false
}
if !protectBranch.EnableForcePushWhitelist {
return protectBranch.CanUserPush(ctx, user)
}
if slices.Contains(protectBranch.ForcePushWhitelistUserIDs, user.ID) {
return protectBranch.CanUserPush(ctx, user)
}
if len(protectBranch.ForcePushWhitelistTeamIDs) == 0 {
return false
}
in, err := organization.IsUserInTeams(ctx, user.ID, protectBranch.ForcePushWhitelistTeamIDs)
if err != nil {
log.Error("IsUserInTeams: %v", err)
return false
}
return in && protectBranch.CanUserPush(ctx, user)
}
// IsUserMergeWhitelisted checks if some user is whitelisted to merge to this branch
func IsUserMergeWhitelisted(ctx context.Context, protectBranch *ProtectedBranch, userID int64, permissionInRepo access_model.Permission) bool {
if !protectBranch.EnableMergeWhitelist {
@ -301,6 +333,9 @@ type WhitelistOptions struct {
UserIDs []int64
TeamIDs []int64
ForcePushUserIDs []int64
ForcePushTeamIDs []int64
MergeUserIDs []int64
MergeTeamIDs []int64
@ -328,6 +363,12 @@ func UpdateProtectBranch(ctx context.Context, repo *repo_model.Repository, prote
}
protectBranch.WhitelistUserIDs = whitelist
whitelist, err = updateUserWhitelist(ctx, repo, protectBranch.ForcePushWhitelistUserIDs, opts.ForcePushUserIDs)
if err != nil {
return err
}
protectBranch.ForcePushWhitelistUserIDs = whitelist
whitelist, err = updateUserWhitelist(ctx, repo, protectBranch.MergeWhitelistUserIDs, opts.MergeUserIDs)
if err != nil {
return err
@ -347,6 +388,12 @@ func UpdateProtectBranch(ctx context.Context, repo *repo_model.Repository, prote
}
protectBranch.WhitelistTeamIDs = whitelist
whitelist, err = updateTeamWhitelist(ctx, repo, protectBranch.ForcePushWhitelistTeamIDs, opts.ForcePushTeamIDs)
if err != nil {
return err
}
protectBranch.ForcePushWhitelistTeamIDs = whitelist
whitelist, err = updateTeamWhitelist(ctx, repo, protectBranch.MergeWhitelistTeamIDs, opts.MergeTeamIDs)
if err != nil {
return err
@ -468,43 +515,58 @@ func DeleteProtectedBranch(ctx context.Context, repo *repo_model.Repository, id
return nil
}
// RemoveUserIDFromProtectedBranch remove all user ids from protected branch options
// removeIDsFromProtectedBranch is a helper function to remove IDs from protected branch options
func removeIDsFromProtectedBranch(ctx context.Context, p *ProtectedBranch, userID, teamID int64, columnNames []string) error {
lenUserIDs, lenForcePushIDs, lenApprovalIDs, lenMergeIDs := len(p.WhitelistUserIDs), len(p.ForcePushWhitelistUserIDs), len(p.ApprovalsWhitelistUserIDs), len(p.MergeWhitelistUserIDs)
lenTeamIDs, lenForcePushTeamIDs, lenApprovalTeamIDs, lenMergeTeamIDs := len(p.WhitelistTeamIDs), len(p.ForcePushWhitelistTeamIDs), len(p.ApprovalsWhitelistTeamIDs), len(p.MergeWhitelistTeamIDs)
if userID > 0 {
p.WhitelistUserIDs = util.SliceRemoveAll(p.WhitelistUserIDs, userID)
p.ForcePushWhitelistUserIDs = util.SliceRemoveAll(p.ForcePushWhitelistUserIDs, userID)
p.ApprovalsWhitelistUserIDs = util.SliceRemoveAll(p.ApprovalsWhitelistUserIDs, userID)
p.MergeWhitelistUserIDs = util.SliceRemoveAll(p.MergeWhitelistUserIDs, userID)
}
if teamID > 0 {
p.WhitelistTeamIDs = util.SliceRemoveAll(p.WhitelistTeamIDs, teamID)
p.ForcePushWhitelistTeamIDs = util.SliceRemoveAll(p.ForcePushWhitelistTeamIDs, teamID)
p.ApprovalsWhitelistTeamIDs = util.SliceRemoveAll(p.ApprovalsWhitelistTeamIDs, teamID)
p.MergeWhitelistTeamIDs = util.SliceRemoveAll(p.MergeWhitelistTeamIDs, teamID)
}
if (lenUserIDs != len(p.WhitelistUserIDs) ||
lenForcePushIDs != len(p.ForcePushWhitelistUserIDs) ||
lenApprovalIDs != len(p.ApprovalsWhitelistUserIDs) ||
lenMergeIDs != len(p.MergeWhitelistUserIDs)) ||
(lenTeamIDs != len(p.WhitelistTeamIDs) ||
lenForcePushTeamIDs != len(p.ForcePushWhitelistTeamIDs) ||
lenApprovalTeamIDs != len(p.ApprovalsWhitelistTeamIDs) ||
lenMergeTeamIDs != len(p.MergeWhitelistTeamIDs)) {
if _, err := db.GetEngine(ctx).ID(p.ID).Cols(columnNames...).Update(p); err != nil {
return fmt.Errorf("updateProtectedBranches: %v", err)
}
}
return nil
}
// RemoveUserIDFromProtectedBranch removes all user ids from protected branch options
func RemoveUserIDFromProtectedBranch(ctx context.Context, p *ProtectedBranch, userID int64) error {
lenIDs, lenApprovalIDs, lenMergeIDs := len(p.WhitelistUserIDs), len(p.ApprovalsWhitelistUserIDs), len(p.MergeWhitelistUserIDs)
p.WhitelistUserIDs = util.SliceRemoveAll(p.WhitelistUserIDs, userID)
p.ApprovalsWhitelistUserIDs = util.SliceRemoveAll(p.ApprovalsWhitelistUserIDs, userID)
p.MergeWhitelistUserIDs = util.SliceRemoveAll(p.MergeWhitelistUserIDs, userID)
if lenIDs != len(p.WhitelistUserIDs) || lenApprovalIDs != len(p.ApprovalsWhitelistUserIDs) ||
lenMergeIDs != len(p.MergeWhitelistUserIDs) {
if _, err := db.GetEngine(ctx).ID(p.ID).Cols(
"whitelist_user_i_ds",
"merge_whitelist_user_i_ds",
"approvals_whitelist_user_i_ds",
).Update(p); err != nil {
return fmt.Errorf("updateProtectedBranches: %v", err)
}
columnNames := []string{
"whitelist_user_i_ds",
"force_push_whitelist_user_i_ds",
"merge_whitelist_user_i_ds",
"approvals_whitelist_user_i_ds",
}
return nil
return removeIDsFromProtectedBranch(ctx, p, userID, 0, columnNames)
}
// RemoveTeamIDFromProtectedBranch remove all team ids from protected branch options
// RemoveTeamIDFromProtectedBranch removes all team ids from protected branch options
func RemoveTeamIDFromProtectedBranch(ctx context.Context, p *ProtectedBranch, teamID int64) error {
lenIDs, lenApprovalIDs, lenMergeIDs := len(p.WhitelistTeamIDs), len(p.ApprovalsWhitelistTeamIDs), len(p.MergeWhitelistTeamIDs)
p.WhitelistTeamIDs = util.SliceRemoveAll(p.WhitelistTeamIDs, teamID)
p.ApprovalsWhitelistTeamIDs = util.SliceRemoveAll(p.ApprovalsWhitelistTeamIDs, teamID)
p.MergeWhitelistTeamIDs = util.SliceRemoveAll(p.MergeWhitelistTeamIDs, teamID)
if lenIDs != len(p.WhitelistTeamIDs) ||
lenApprovalIDs != len(p.ApprovalsWhitelistTeamIDs) ||
lenMergeIDs != len(p.MergeWhitelistTeamIDs) {
if _, err := db.GetEngine(ctx).ID(p.ID).Cols(
"whitelist_team_i_ds",
"merge_whitelist_team_i_ds",
"approvals_whitelist_team_i_ds",
).Update(p); err != nil {
return fmt.Errorf("updateProtectedBranches: %v", err)
}
columnNames := []string{
"whitelist_team_i_ds",
"force_push_whitelist_team_i_ds",
"merge_whitelist_team_i_ds",
"approvals_whitelist_team_i_ds",
}
return nil
return removeIDsFromProtectedBranch(ctx, p, 0, teamID, columnNames)
}

View File

@ -30,6 +30,11 @@ type BranchProtection struct {
PushWhitelistUsernames []string `json:"push_whitelist_usernames"`
PushWhitelistTeams []string `json:"push_whitelist_teams"`
PushWhitelistDeployKeys bool `json:"push_whitelist_deploy_keys"`
EnableForcePush bool `json:"enable_force_push"`
EnableForcePushWhitelist bool `json:"enable_force_push_whitelist"`
ForcePushWhitelistUsernames []string `json:"force_push_whitelist_usernames"`
ForcePushWhitelistTeams []string `json:"force_push_whitelist_teams"`
ForcePushWhitelistDeployKeys bool `json:"force_push_whitelist_deploy_keys"`
EnableMergeWhitelist bool `json:"enable_merge_whitelist"`
MergeWhitelistUsernames []string `json:"merge_whitelist_usernames"`
MergeWhitelistTeams []string `json:"merge_whitelist_teams"`
@ -63,6 +68,11 @@ type CreateBranchProtectionOption struct {
PushWhitelistUsernames []string `json:"push_whitelist_usernames"`
PushWhitelistTeams []string `json:"push_whitelist_teams"`
PushWhitelistDeployKeys bool `json:"push_whitelist_deploy_keys"`
EnableForcePush bool `json:"enable_force_push"`
EnableForcePushWhitelist bool `json:"enable_force_push_whitelist"`
ForcePushWhitelistUsernames []string `json:"force_push_whitelist_usernames"`
ForcePushWhitelistTeams []string `json:"force_push_whitelist_teams"`
ForcePushWhitelistDeployKeys bool `json:"force_push_whitelist_deploy_keys"`
EnableMergeWhitelist bool `json:"enable_merge_whitelist"`
MergeWhitelistUsernames []string `json:"merge_whitelist_usernames"`
MergeWhitelistTeams []string `json:"merge_whitelist_teams"`
@ -89,6 +99,11 @@ type EditBranchProtectionOption struct {
PushWhitelistUsernames []string `json:"push_whitelist_usernames"`
PushWhitelistTeams []string `json:"push_whitelist_teams"`
PushWhitelistDeployKeys *bool `json:"push_whitelist_deploy_keys"`
EnableForcePush *bool `json:"enable_force_push"`
EnableForcePushWhitelist *bool `json:"enable_force_push_whitelist"`
ForcePushWhitelistUsernames []string `json:"force_push_whitelist_usernames"`
ForcePushWhitelistTeams []string `json:"force_push_whitelist_teams"`
ForcePushWhitelistDeployKeys *bool `json:"force_push_whitelist_deploy_keys"`
EnableMergeWhitelist *bool `json:"enable_merge_whitelist"`
MergeWhitelistUsernames []string `json:"merge_whitelist_usernames"`
MergeWhitelistTeams []string `json:"merge_whitelist_teams"`

View File

@ -2269,6 +2269,7 @@ settings.event_wiki_desc = Wiki page created, renamed, edited or deleted.
settings.event_release = Release
settings.event_release_desc = Release published, updated or deleted in a repository.
settings.event_push = Push
settings.event_force_push = Force Push
settings.event_push_desc = Git push to a repository.
settings.event_repository = Repository
settings.event_repository_desc = Repository created or deleted.
@ -2362,8 +2363,14 @@ settings.protect_this_branch = Enable Branch Protection
settings.protect_this_branch_desc = Prevents deletion and restricts Git pushing and merging to the branch.
settings.protect_disable_push = Disable Push
settings.protect_disable_push_desc = No pushing will be allowed to this branch.
settings.protect_disable_force_push = Disable Force Push
settings.protect_disable_force_push_desc = No force pushing will be allowed to this branch.
settings.protect_enable_push = Enable Push
settings.protect_enable_push_desc = Anyone with write access will be allowed to push to this branch (but not force push).
settings.protect_enable_force_push_all = Enable Force Push
settings.protect_enable_force_push_all_desc = Anyone with push access will be allowed to force push to this branch.
settings.protect_enable_force_push_whitelist = Whitelist Restricted Force Push
settings.protect_enable_force_push_whitelist_desc = Only whitelisted users or teams with push access will be allowed to force push to this branch.
settings.protect_enable_merge = Enable Merge
settings.protect_enable_merge_desc = Anyone with write access will be allowed to merge the pull requests into this branch.
settings.protect_whitelist_committers = Whitelist Restricted Push
@ -2371,6 +2378,10 @@ settings.protect_whitelist_committers_desc = Only whitelisted users or teams wil
settings.protect_whitelist_deploy_keys = Whitelist deploy keys with write access to push.
settings.protect_whitelist_users = Whitelisted users for pushing:
settings.protect_whitelist_teams = Whitelisted teams for pushing:
settings.protect_whitelist_search_teams = Search teams…
settings.protect_force_push_whitelist_users = Whitelisted users for force pushing:
settings.protect_force_push_whitelist_teams = Whitelisted teams for force pushing:
settings.protect_force_push_whitelist_deploy_keys = Whitelist deploy keys with push access to force push.
settings.protect_merge_whitelist_committers = Enable Merge Whitelist
settings.protect_merge_whitelist_committers_desc = Allow only whitelisted users or teams to merge pull requests into this branch.
settings.protect_merge_whitelist_users = Whitelisted users for merging:

View File

@ -553,6 +553,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) {
@ -571,7 +580,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 {
@ -582,6 +591,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) {
@ -607,8 +625,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,
@ -626,6 +647,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,
@ -756,6 +779,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
}
@ -808,7 +852,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 {
@ -822,7 +866,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 {
@ -836,7 +892,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 {
@ -851,7 +906,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)
@ -866,6 +921,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 {
@ -897,6 +965,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,

View File

@ -183,6 +183,8 @@ func preReceiveBranch(ctx *preReceiveContext, oldCommitID, newCommitID string, r
return
}
isForcePush := false
// 2. Disallow force pushes to protected branches
if oldCommitID != objectFormat.EmptyObjectID().String() {
output, _, err := git.NewCommand(ctx, "rev-list", "--max-count=1").AddDynamicArguments(oldCommitID, "^"+newCommitID).RunStdString(&git.RunOpts{Dir: repo.RepoPath(), Env: ctx.env})
@ -193,12 +195,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
}
}
}
@ -245,10 +250,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 {
@ -258,7 +268,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
@ -294,6 +308,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 allowed 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),

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) != "" {
@ -237,6 +260,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,

View File

@ -134,6 +134,7 @@ func ToBranchProtection(ctx context.Context, bp *git_model.ProtectedBranch, repo
}
pushWhitelistUsernames := getWhitelistEntities(readers, bp.WhitelistUserIDs)
forcePushWhitelistUsernames := getWhitelistEntities(readers, bp.ForcePushWhitelistUserIDs)
mergeWhitelistUsernames := getWhitelistEntities(readers, bp.MergeWhitelistUserIDs)
approvalsWhitelistUsernames := getWhitelistEntities(readers, bp.ApprovalsWhitelistUserIDs)
@ -143,6 +144,7 @@ func ToBranchProtection(ctx context.Context, bp *git_model.ProtectedBranch, repo
}
pushWhitelistTeams := getWhitelistEntities(teamReaders, bp.WhitelistTeamIDs)
forcePushWhitelistTeams := getWhitelistEntities(teamReaders, bp.ForcePushWhitelistTeamIDs)
mergeWhitelistTeams := getWhitelistEntities(teamReaders, bp.MergeWhitelistTeamIDs)
approvalsWhitelistTeams := getWhitelistEntities(teamReaders, bp.ApprovalsWhitelistTeamIDs)
@ -159,6 +161,11 @@ func ToBranchProtection(ctx context.Context, bp *git_model.ProtectedBranch, repo
PushWhitelistUsernames: pushWhitelistUsernames,
PushWhitelistTeams: pushWhitelistTeams,
PushWhitelistDeployKeys: bp.WhitelistDeployKeys,
EnableForcePush: bp.CanForcePush,
EnableForcePushWhitelist: bp.EnableForcePushWhitelist,
ForcePushWhitelistUsernames: forcePushWhitelistUsernames,
ForcePushWhitelistTeams: forcePushWhitelistTeams,
ForcePushWhitelistDeployKeys: bp.ForcePushWhitelistDeployKeys,
EnableMergeWhitelist: bp.EnableMergeWhitelist,
MergeWhitelistUsernames: mergeWhitelistUsernames,
MergeWhitelistTeams: mergeWhitelistTeams,

View File

@ -195,6 +195,10 @@ type ProtectBranchForm struct {
WhitelistUsers string
WhitelistTeams string
WhitelistDeployKeys bool
EnableForcePush string
ForcePushWhitelistUsers string
ForcePushWhitelistTeams string
ForcePushWhitelistDeployKeys bool
EnableMergeWhitelist bool
MergeWhitelistUsers string
MergeWhitelistTeams string

View File

@ -116,27 +116,25 @@ func IsUserAllowedToUpdate(ctx context.Context, pull *issues_model.PullRequest,
return false, false, err
}
// can't do rebase on protected branch because need force push
if pb == nil {
if err := pr.LoadBaseRepo(ctx); err != nil {
return false, false, err
if err := pr.LoadBaseRepo(ctx); err != nil {
return false, false, err
}
prUnit, err := pr.BaseRepo.GetUnit(ctx, unit.TypePullRequests)
if err != nil {
if repo_model.IsErrUnitTypeNotExist(err) {
return false, false, nil
}
prUnit, err := pr.BaseRepo.GetUnit(ctx, unit.TypePullRequests)
if err != nil {
if repo_model.IsErrUnitTypeNotExist(err) {
return false, false, nil
}
log.Error("pr.BaseRepo.GetUnit(unit.TypePullRequests): %v", err)
return false, false, err
}
rebaseAllowed = prUnit.PullRequestsConfig().AllowRebaseUpdate
log.Error("pr.BaseRepo.GetUnit(unit.TypePullRequests): %v", err)
return false, false, err
}
// Update function need push permission
rebaseAllowed = prUnit.PullRequestsConfig().AllowRebaseUpdate
// If branch protected, disable rebase unless user is whitelisted to force push (which extends regular push)
if pb != nil {
pb.Repo = pull.BaseRepo
if !pb.CanUserPush(ctx, user) {
return false, false, nil
if !pb.CanUserForcePush(ctx, user) {
rebaseAllowed = false
}
}

View File

@ -94,6 +94,69 @@
<p class="help">{{ctx.Locale.Tr "repo.settings.require_signed_commits_desc"}}</p>
</div>
</div>
<h5 class="ui dividing header">{{ctx.Locale.Tr "repo.settings.event_force_push"}}</h5>
<div class="field">
<div class="ui radio checkbox">
<input type="radio" name="enable_force_push" value="none" class="toggle-target-disabled" data-target="#force_push_whitelist_box" {{if not .Rule.CanForcePush}}checked{{end}}>
<label>{{ctx.Locale.Tr "repo.settings.protect_disable_force_push"}}</label>
<p class="help">{{ctx.Locale.Tr "repo.settings.protect_disable_force_push_desc"}}</p>
</div>
</div>
<div class="field">
<div class="ui radio checkbox">
<input type="radio" name="enable_force_push" value="all" class="toggle-target-disabled" data-target="#force_push_whitelist_box" {{if and (.Rule.CanForcePush) (not .Rule.EnableForcePushWhitelist)}}checked{{end}}>
<label>{{ctx.Locale.Tr "repo.settings.protect_enable_force_push_all"}}</label>
<p class="help">{{ctx.Locale.Tr "repo.settings.protect_enable_force_push_all_desc"}}</p>
</div>
</div>
<div class="grouped fields">
<div class="field">
<div class="ui radio checkbox">
<input type="radio" name="enable_force_push" value="whitelist" class="toggle-target-enabled" data-target="#force_push_whitelist_box" {{if and (.Rule.CanForcePush) (.Rule.EnableForcePushWhitelist)}}checked{{end}}>
<label>{{ctx.Locale.Tr "repo.settings.protect_enable_force_push_whitelist"}}</label>
<p class="help">{{ctx.Locale.Tr "repo.settings.protect_enable_force_push_whitelist_desc"}}</p>
</div>
</div>
<div id="force_push_whitelist_box" class="grouped fields {{if not .Rule.EnableForcePushWhitelist}}disabled{{end}}">
<div class="checkbox-sub-item field">
<label>{{ctx.Locale.Tr "repo.settings.protect_force_push_whitelist_users"}}</label>
<div class="ui multiple search selection dropdown">
<input type="hidden" name="force_push_whitelist_users" value="{{.force_push_whitelist_users}}">
<div class="default text">{{ctx.Locale.Tr "repo.settings.protect_whitelist_search_users"}}</div>
<div class="menu">
{{range .Users}}
<div class="item" data-value="{{.ID}}">
{{ctx.AvatarUtils.Avatar . 28 "mini"}}{{template "repo/search_name" .}}
</div>
{{end}}
</div>
</div>
</div>
{{if .Owner.IsOrganization}}
<div class="checkbox-sub-item field">
<label>{{ctx.Locale.Tr "repo.settings.protect_force_push_whitelist_teams"}}</label>
<div class="ui multiple search selection dropdown">
<input type="hidden" name="force_push_whitelist_teams" value="{{.force_push_whitelist_teams}}">
<div class="default text">{{ctx.Locale.Tr "repo.settings.protect_whitelist_search_teams"}}</div>
<div class="menu">
{{range .Teams}}
<div class="item" data-value="{{.ID}}">
{{svg "octicon-people"}}
{{.Name}}
</div>
{{end}}
</div>
</div>
</div>
{{end}}
<div class="checkbox-sub-item field">
<div class="ui checkbox">
<input type="checkbox" name="force_push_whitelist_deploy_keys" {{if .Rule.ForcePushWhitelistDeployKeys}}checked{{end}}>
<label>{{ctx.Locale.Tr "repo.settings.protect_force_push_whitelist_deploy_keys"}}</label>
</div>
</div>
</div>
</div>
<h5 class="ui dividing header">{{ctx.Locale.Tr "repo.settings.event_pull_request_approvals"}}</h5>
<div class="field">
<label>{{ctx.Locale.Tr "repo.settings.protect_required_approvals"}}</label>

View File

@ -18253,6 +18253,14 @@
"type": "boolean",
"x-go-name": "EnableApprovalsWhitelist"
},
"enable_force_push": {
"type": "boolean",
"x-go-name": "EnableForcePush"
},
"enable_force_push_whitelist": {
"type": "boolean",
"x-go-name": "EnableForcePushWhitelist"
},
"enable_merge_whitelist": {
"type": "boolean",
"x-go-name": "EnableMergeWhitelist"
@ -18269,6 +18277,24 @@
"type": "boolean",
"x-go-name": "EnableStatusCheck"
},
"force_push_whitelist_deploy_keys": {
"type": "boolean",
"x-go-name": "ForcePushWhitelistDeployKeys"
},
"force_push_whitelist_teams": {
"type": "array",
"items": {
"type": "string"
},
"x-go-name": "ForcePushWhitelistTeams"
},
"force_push_whitelist_usernames": {
"type": "array",
"items": {
"type": "string"
},
"x-go-name": "ForcePushWhitelistUsernames"
},
"ignore_stale_approvals": {
"type": "boolean",
"x-go-name": "IgnoreStaleApprovals"
@ -18917,6 +18943,14 @@
"type": "boolean",
"x-go-name": "EnableApprovalsWhitelist"
},
"enable_force_push": {
"type": "boolean",
"x-go-name": "EnableForcePush"
},
"enable_force_push_whitelist": {
"type": "boolean",
"x-go-name": "EnableForcePushWhitelist"
},
"enable_merge_whitelist": {
"type": "boolean",
"x-go-name": "EnableMergeWhitelist"
@ -18933,6 +18967,24 @@
"type": "boolean",
"x-go-name": "EnableStatusCheck"
},
"force_push_whitelist_deploy_keys": {
"type": "boolean",
"x-go-name": "ForcePushWhitelistDeployKeys"
},
"force_push_whitelist_teams": {
"type": "array",
"items": {
"type": "string"
},
"x-go-name": "ForcePushWhitelistTeams"
},
"force_push_whitelist_usernames": {
"type": "array",
"items": {
"type": "string"
},
"x-go-name": "ForcePushWhitelistUsernames"
},
"ignore_stale_approvals": {
"type": "boolean",
"x-go-name": "IgnoreStaleApprovals"
@ -20076,6 +20128,14 @@
"type": "boolean",
"x-go-name": "EnableApprovalsWhitelist"
},
"enable_force_push": {
"type": "boolean",
"x-go-name": "EnableForcePush"
},
"enable_force_push_whitelist": {
"type": "boolean",
"x-go-name": "EnableForcePushWhitelist"
},
"enable_merge_whitelist": {
"type": "boolean",
"x-go-name": "EnableMergeWhitelist"
@ -20092,6 +20152,24 @@
"type": "boolean",
"x-go-name": "EnableStatusCheck"
},
"force_push_whitelist_deploy_keys": {
"type": "boolean",
"x-go-name": "ForcePushWhitelistDeployKeys"
},
"force_push_whitelist_teams": {
"type": "array",
"items": {
"type": "string"
},
"x-go-name": "ForcePushWhitelistTeams"
},
"force_push_whitelist_usernames": {
"type": "array",
"items": {
"type": "string"
},
"x-go-name": "ForcePushWhitelistUsernames"
},
"ignore_stale_approvals": {
"type": "boolean",
"x-go-name": "IgnoreStaleApprovals"