From cac06c2ac741a58c627f9ab9e692e6109036d5c1 Mon Sep 17 00:00:00 2001 From: Jonathan Miller Date: Sun, 5 Jul 2026 17:48:47 -0500 Subject: [PATCH] feat: org branch protection per-user allowlists + Actions-bot toggle (#727) Extend org-level branch protection to support per-user allowlists (resolved from username OR email) and an "allow Actions bot" toggle, alongside the existing team allowlists, for all five categories (push, merge, force-push, delete, approvals). - models/git/org_protected_branch.go: add WhitelistUserIDs, MergeWhitelistUserIDs, ForcePushAllowlistUserIDs, DeleteAllowlistUserIDs, ApprovalsWhitelistUserIDs ([]int64) plus WhitelistActionsUser, MergeWhitelistActionsUser, ForcePushAllowlistActionsUser, DeleteAllowlistActionsUser (bool); copy all 9 into ProtectedBranch in ToProtectedBranch(). - models/migrations/v1_27/v368.go: migration 367 adds the 9 columns. - modules/structs/org_branch.go: add *Usernames []string and *ActionsUser bool to Create/Edit options and the response, matching repo-level json names. - routers/api/v1/org/branch_protection.go: resolveUserIDs (username then email, dedupe, 422 on unknown); wire into Create + Edit and toAPIOrgBranchProtection. - models/git/protected_branch_merge.go: add mergeAllowFlag and merge the four Actions-user flags most-restrictively (org can now express them); deploy-key flags stay repo-only pass-through. - models/git/protected_branch_merge_test.go: mergeAllowFlag truth table, user-ID intersection, deploy-key pass-through. - regenerate swagger v1 + openapi3 specs. Claude-Session: https://claude.ai/code/session_01Wsno14cxE49MstXFs9G5KT --- models/git/org_protected_branch.go | 23 +++- models/git/protected_branch_merge.go | 37 ++++-- models/git/protected_branch_merge_test.go | 100 ++++++++++++++ models/migrations/migrations.go | 1 + models/migrations/v1_27/v368.go | 26 ++++ modules/structs/org_branch.go | 27 ++++ routers/api/v1/org/branch_protection.go | 133 +++++++++++++++++++ templates/swagger/v1_json.tmpl | 153 ++++++++++++++++++++++ templates/swagger/v1_openapi3_json.tmpl | 153 ++++++++++++++++++++++ 9 files changed, 642 insertions(+), 11 deletions(-) create mode 100644 models/git/protected_branch_merge_test.go create mode 100644 models/migrations/v1_27/v368.go diff --git a/models/git/org_protected_branch.go b/models/git/org_protected_branch.go index 881edab6d1..da7b5039d0 100644 --- a/models/git/org_protected_branch.go +++ b/models/git/org_protected_branch.go @@ -28,19 +28,28 @@ type OrgProtectedBranch struct { CanPush bool `xorm:"NOT NULL DEFAULT false"` EnableWhitelist bool `xorm:"NOT NULL DEFAULT false"` WhitelistTeamIDs []int64 `xorm:"JSON TEXT"` + WhitelistUserIDs []int64 `xorm:"JSON TEXT"` + WhitelistActionsUser bool `xorm:"NOT NULL DEFAULT false"` EnableMergeWhitelist bool `xorm:"NOT NULL DEFAULT false"` MergeWhitelistTeamIDs []int64 `xorm:"JSON TEXT"` + MergeWhitelistUserIDs []int64 `xorm:"JSON TEXT"` + MergeWhitelistActionsUser bool `xorm:"NOT NULL DEFAULT false"` CanForcePush bool `xorm:"NOT NULL DEFAULT false"` EnableForcePushAllowlist bool `xorm:"NOT NULL DEFAULT false"` ForcePushAllowlistTeamIDs []int64 `xorm:"JSON TEXT"` + ForcePushAllowlistUserIDs []int64 `xorm:"JSON TEXT"` + ForcePushAllowlistActionsUser bool `xorm:"NOT NULL DEFAULT false"` CanDelete bool `xorm:"NOT NULL DEFAULT false"` EnableDeleteAllowlist bool `xorm:"NOT NULL DEFAULT false"` DeleteAllowlistTeamIDs []int64 `xorm:"JSON TEXT"` + DeleteAllowlistUserIDs []int64 `xorm:"JSON TEXT"` + DeleteAllowlistActionsUser bool `xorm:"NOT NULL DEFAULT false"` EnableStatusCheck bool `xorm:"NOT NULL DEFAULT false"` StatusCheckContexts []string `xorm:"JSON TEXT"` RequiredApprovals int64 `xorm:"NOT NULL DEFAULT 0"` EnableApprovalsWhitelist bool `xorm:"NOT NULL DEFAULT false"` ApprovalsWhitelistTeamIDs []int64 `xorm:"JSON TEXT"` + ApprovalsWhitelistUserIDs []int64 `xorm:"JSON TEXT"` BlockOnRejectedReviews bool `xorm:"NOT NULL DEFAULT false"` BlockOnOfficialReviewRequests bool `xorm:"NOT NULL DEFAULT false"` BlockOnOutdatedBranch bool `xorm:"NOT NULL DEFAULT false"` @@ -84,8 +93,9 @@ func (o *OrgProtectedBranch) Match(branchName string) bool { } // ToProtectedBranch converts an org-level rule to a ProtectedBranch for use -// in the standard enforcement path. Fields that are user-scoped (WhitelistUserIDs etc.) -// are left empty because org rules only reference teams. +// in the standard enforcement path. Both team-scoped and user-scoped allowlists +// (plus the Actions-bot toggles) are carried across; deploy-key allowances remain +// repo-only because an org rule cannot express them. func (o *OrgProtectedBranch) ToProtectedBranch() *ProtectedBranch { return &ProtectedBranch{ ID: o.ID, @@ -94,19 +104,28 @@ func (o *OrgProtectedBranch) ToProtectedBranch() *ProtectedBranch { CanPush: o.CanPush, EnableWhitelist: o.EnableWhitelist, WhitelistTeamIDs: o.WhitelistTeamIDs, + WhitelistUserIDs: o.WhitelistUserIDs, + WhitelistActionsUser: o.WhitelistActionsUser, EnableMergeWhitelist: o.EnableMergeWhitelist, MergeWhitelistTeamIDs: o.MergeWhitelistTeamIDs, + MergeWhitelistUserIDs: o.MergeWhitelistUserIDs, + MergeWhitelistActionsUser: o.MergeWhitelistActionsUser, CanForcePush: o.CanForcePush, EnableForcePushAllowlist: o.EnableForcePushAllowlist, ForcePushAllowlistTeamIDs: o.ForcePushAllowlistTeamIDs, + ForcePushAllowlistUserIDs: o.ForcePushAllowlistUserIDs, + ForcePushAllowlistActionsUser: o.ForcePushAllowlistActionsUser, CanDelete: o.CanDelete, EnableDeleteAllowlist: o.EnableDeleteAllowlist, DeleteAllowlistTeamIDs: o.DeleteAllowlistTeamIDs, + DeleteAllowlistUserIDs: o.DeleteAllowlistUserIDs, + DeleteAllowlistActionsUser: o.DeleteAllowlistActionsUser, EnableStatusCheck: o.EnableStatusCheck, StatusCheckContexts: o.StatusCheckContexts, RequiredApprovals: o.RequiredApprovals, EnableApprovalsWhitelist: o.EnableApprovalsWhitelist, ApprovalsWhitelistTeamIDs: o.ApprovalsWhitelistTeamIDs, + ApprovalsWhitelistUserIDs: o.ApprovalsWhitelistUserIDs, BlockOnRejectedReviews: o.BlockOnRejectedReviews, BlockOnOfficialReviewRequests: o.BlockOnOfficialReviewRequests, BlockOnOutdatedBranch: o.BlockOnOutdatedBranch, diff --git a/models/git/protected_branch_merge.go b/models/git/protected_branch_merge.go index 5f4f6adb0a..103b23350b 100644 --- a/models/git/protected_branch_merge.go +++ b/models/git/protected_branch_merge.go @@ -26,32 +26,32 @@ func mergeMostRestrictive(repoRule, orgRule *ProtectedBranch) *ProtectedBranch { eff.CanPush = repoRule.CanPush && orgRule.CanPush eff.EnableWhitelist, eff.WhitelistUserIDs = mergeAllowlist(repoRule.EnableWhitelist, repoRule.WhitelistUserIDs, orgRule.EnableWhitelist, orgRule.WhitelistUserIDs) _, eff.WhitelistTeamIDs = mergeAllowlist(repoRule.EnableWhitelist, repoRule.WhitelistTeamIDs, orgRule.EnableWhitelist, orgRule.WhitelistTeamIDs) - // Deploy-key and Actions-user allowances are not expressible in an - // OrgProtectedBranch (it is team-only), so the org rule imposes no constraint on - // them; carry the repo values through unchanged. ANDing against the org side's - // always-false zero value would silently lock out every deploy-key and - // Actions-bot push in any org that has a matching branch rule (see #727 review). + // Deploy-key allowances are still not expressible in an OrgProtectedBranch, so the + // org rule imposes no constraint on them; carry the repo value through unchanged. + // ANDing against the org side's always-false zero value would silently lock out + // every deploy-key push in any org that has a matching branch rule (see #727 review). + // The Actions-bot toggle IS now expressible org-side, so merge it most-restrictively. eff.WhitelistDeployKeys = repoRule.WhitelistDeployKeys - eff.WhitelistActionsUser = repoRule.WhitelistActionsUser + eff.WhitelistActionsUser = mergeAllowFlag(repoRule.EnableWhitelist, repoRule.WhitelistActionsUser, orgRule.EnableWhitelist, orgRule.WhitelistActionsUser) // Force push. eff.CanForcePush = repoRule.CanForcePush && orgRule.CanForcePush eff.EnableForcePushAllowlist, eff.ForcePushAllowlistUserIDs = mergeAllowlist(repoRule.EnableForcePushAllowlist, repoRule.ForcePushAllowlistUserIDs, orgRule.EnableForcePushAllowlist, orgRule.ForcePushAllowlistUserIDs) _, eff.ForcePushAllowlistTeamIDs = mergeAllowlist(repoRule.EnableForcePushAllowlist, repoRule.ForcePushAllowlistTeamIDs, orgRule.EnableForcePushAllowlist, orgRule.ForcePushAllowlistTeamIDs) eff.ForcePushAllowlistDeployKeys = repoRule.ForcePushAllowlistDeployKeys - eff.ForcePushAllowlistActionsUser = repoRule.ForcePushAllowlistActionsUser + eff.ForcePushAllowlistActionsUser = mergeAllowFlag(repoRule.EnableForcePushAllowlist, repoRule.ForcePushAllowlistActionsUser, orgRule.EnableForcePushAllowlist, orgRule.ForcePushAllowlistActionsUser) // Delete. eff.CanDelete = repoRule.CanDelete && orgRule.CanDelete eff.EnableDeleteAllowlist, eff.DeleteAllowlistUserIDs = mergeAllowlist(repoRule.EnableDeleteAllowlist, repoRule.DeleteAllowlistUserIDs, orgRule.EnableDeleteAllowlist, orgRule.DeleteAllowlistUserIDs) _, eff.DeleteAllowlistTeamIDs = mergeAllowlist(repoRule.EnableDeleteAllowlist, repoRule.DeleteAllowlistTeamIDs, orgRule.EnableDeleteAllowlist, orgRule.DeleteAllowlistTeamIDs) eff.DeleteAllowlistDeployKeys = repoRule.DeleteAllowlistDeployKeys - eff.DeleteAllowlistActionsUser = repoRule.DeleteAllowlistActionsUser + eff.DeleteAllowlistActionsUser = mergeAllowFlag(repoRule.EnableDeleteAllowlist, repoRule.DeleteAllowlistActionsUser, orgRule.EnableDeleteAllowlist, orgRule.DeleteAllowlistActionsUser) // Merge whitelist. eff.EnableMergeWhitelist, eff.MergeWhitelistUserIDs = mergeAllowlist(repoRule.EnableMergeWhitelist, repoRule.MergeWhitelistUserIDs, orgRule.EnableMergeWhitelist, orgRule.MergeWhitelistUserIDs) _, eff.MergeWhitelistTeamIDs = mergeAllowlist(repoRule.EnableMergeWhitelist, repoRule.MergeWhitelistTeamIDs, orgRule.EnableMergeWhitelist, orgRule.MergeWhitelistTeamIDs) - eff.MergeWhitelistActionsUser = repoRule.MergeWhitelistActionsUser + eff.MergeWhitelistActionsUser = mergeAllowFlag(repoRule.EnableMergeWhitelist, repoRule.MergeWhitelistActionsUser, orgRule.EnableMergeWhitelist, orgRule.MergeWhitelistActionsUser) // Status checks. eff.EnableStatusCheck = repoRule.EnableStatusCheck || orgRule.EnableStatusCheck @@ -94,6 +94,25 @@ func mergeAllowlist(aEnabled bool, aIDs []int64, bEnabled bool, bIDs []int64) (b } } +// mergeAllowFlag combines two "allow the Actions bot" toggles under most-restrictive +// semantics, mirroring mergeAllowlist. A toggle only grants access when its Enable flag +// is set; a disabled allowlist means "everyone", so it imposes no constraint. Therefore: +// if both allowlists are enabled the bot is allowed only when BOTH sides allow it; if +// only one is enabled that side's flag governs; if neither is enabled the flag is +// irrelevant (fall back to the repo/a value). +func mergeAllowFlag(aEnabled, aFlag, bEnabled, bFlag bool) bool { + switch { + case aEnabled && bEnabled: + return aFlag && bFlag + case aEnabled: + return aFlag + case bEnabled: + return bFlag + default: + return aFlag + } +} + func intersectInt64(a, b []int64) []int64 { if len(a) == 0 || len(b) == 0 { return nil diff --git a/models/git/protected_branch_merge_test.go b/models/git/protected_branch_merge_test.go new file mode 100644 index 0000000000..716f3a6174 --- /dev/null +++ b/models/git/protected_branch_merge_test.go @@ -0,0 +1,100 @@ +// Copyright 2026 Moko Consulting +// SPDX-License-Identifier: GPL-3.0-or-later + +package git + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestMergeAllowFlag(t *testing.T) { + cases := []struct { + name string + aEnabled bool + aFlag bool + bEnabled bool + bFlag bool + expected bool + }{ + // Both allowlists enabled: bot allowed only when BOTH allow it. + {"both enabled, both allow", true, true, true, true, true}, + {"both enabled, a denies", true, false, true, true, false}, + {"both enabled, b denies", true, true, true, false, false}, + {"both enabled, both deny", true, false, true, false, false}, + // Only one side enabled: that side governs. + {"only a enabled, a allows", true, true, false, false, true}, + {"only a enabled, a denies", true, false, false, true, false}, + {"only b enabled, b allows", false, false, true, true, true}, + {"only b enabled, b denies", false, true, true, false, false}, + // Neither enabled: flag irrelevant, falls back to a's flag. + {"neither enabled, a true", false, true, false, false, true}, + {"neither enabled, a false", false, false, false, true, false}, + } + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + assert.Equal(t, c.expected, mergeAllowFlag(c.aEnabled, c.aFlag, c.bEnabled, c.bFlag)) + }) + } +} + +func TestMergeMostRestrictiveUserIDsIntersect(t *testing.T) { + repoRule := &ProtectedBranch{ + EnableWhitelist: true, + WhitelistUserIDs: []int64{1, 2, 3}, + // Deploy keys are repo-only and must always pass through unchanged. + WhitelistDeployKeys: true, + ForcePushAllowlistDeployKeys: true, + DeleteAllowlistDeployKeys: true, + } + orgRule := &ProtectedBranch{ + EnableWhitelist: true, + WhitelistUserIDs: []int64{2, 3, 4}, + // Org rule cannot express deploy keys; these zero values must NOT override repo. + } + + eff := mergeMostRestrictive(repoRule, orgRule) + + // User IDs are intersected when both allowlists are enabled. + assert.True(t, eff.EnableWhitelist) + assert.ElementsMatch(t, []int64{2, 3}, eff.WhitelistUserIDs) + + // Deploy-key allowances still pass through from the repo rule. + assert.True(t, eff.WhitelistDeployKeys) + assert.True(t, eff.ForcePushAllowlistDeployKeys) + assert.True(t, eff.DeleteAllowlistDeployKeys) +} + +func TestMergeMostRestrictiveActionsUserFlags(t *testing.T) { + // Both sides enable their allowlist and both allow the bot -> allowed. + repoRule := &ProtectedBranch{ + EnableWhitelist: true, + WhitelistActionsUser: true, + EnableForcePushAllowlist: true, + // force-push: org denies -> effective deny. + ForcePushAllowlistActionsUser: true, + EnableDeleteAllowlist: true, + DeleteAllowlistActionsUser: true, + // merge: repo allowlist disabled, org enabled+denies -> org governs (deny). + EnableMergeWhitelist: false, + MergeWhitelistActionsUser: true, + } + orgRule := &ProtectedBranch{ + EnableWhitelist: true, + WhitelistActionsUser: true, + EnableForcePushAllowlist: true, + ForcePushAllowlistActionsUser: false, + EnableDeleteAllowlist: true, + DeleteAllowlistActionsUser: true, + EnableMergeWhitelist: true, + MergeWhitelistActionsUser: false, + } + + eff := mergeMostRestrictive(repoRule, orgRule) + + assert.True(t, eff.WhitelistActionsUser, "push: both allow") + assert.False(t, eff.ForcePushAllowlistActionsUser, "force-push: org denies") + assert.True(t, eff.DeleteAllowlistActionsUser, "delete: both allow") + assert.False(t, eff.MergeWhitelistActionsUser, "merge: org enabled and denies") +} diff --git a/models/migrations/migrations.go b/models/migrations/migrations.go index 46e6e657ba..24ff05cc46 100644 --- a/models/migrations/migrations.go +++ b/models/migrations/migrations.go @@ -444,6 +444,7 @@ func prepareMigrationTasks() []*migration { newMigration(364, "Add org push policy table", v1_27.AddOrgPushPolicyTable), newMigration(365, "Add org repo defaults table", v1_27.AddOrgRepoDefaultsTable), newMigration(366, "Add org email domain policy table", v1_27.AddOrgEmailDomainPolicyTable), + newMigration(367, "Add user allowlists to org protected branch", v1_27.AddUserAllowlistsToOrgProtectedBranch), } return preparedMigrations } diff --git a/models/migrations/v1_27/v368.go b/models/migrations/v1_27/v368.go new file mode 100644 index 0000000000..654e9c2fda --- /dev/null +++ b/models/migrations/v1_27/v368.go @@ -0,0 +1,26 @@ +// Copyright 2026 Moko Consulting +// SPDX-License-Identifier: GPL-3.0-or-later + +package v1_27 + +import "xorm.io/xorm" + +// AddUserAllowlistsToOrgProtectedBranch adds per-user allowlist columns (username or +// email resolved to user IDs) plus the "allow Actions bot" toggles to org-level branch +// protection rules for the push, merge, force-push and delete categories, mirroring the +// per-repo user allowlists so an org rule can also grant access to individual users and +// the Actions bot. See issue #727. +func AddUserAllowlistsToOrgProtectedBranch(x *xorm.Engine) error { + type OrgProtectedBranch struct { + WhitelistUserIDs []int64 `xorm:"JSON TEXT"` + WhitelistActionsUser bool `xorm:"NOT NULL DEFAULT false"` + MergeWhitelistUserIDs []int64 `xorm:"JSON TEXT"` + MergeWhitelistActionsUser bool `xorm:"NOT NULL DEFAULT false"` + ForcePushAllowlistUserIDs []int64 `xorm:"JSON TEXT"` + ForcePushAllowlistActionsUser bool `xorm:"NOT NULL DEFAULT false"` + DeleteAllowlistUserIDs []int64 `xorm:"JSON TEXT"` + DeleteAllowlistActionsUser bool `xorm:"NOT NULL DEFAULT false"` + ApprovalsWhitelistUserIDs []int64 `xorm:"JSON TEXT"` + } + return x.Sync(new(OrgProtectedBranch)) +} diff --git a/modules/structs/org_branch.go b/modules/structs/org_branch.go index 0b66b47756..a89269391b 100644 --- a/modules/structs/org_branch.go +++ b/modules/structs/org_branch.go @@ -14,19 +14,28 @@ type OrgBranchProtection struct { EnablePush bool `json:"enable_push"` EnablePushWhitelist bool `json:"enable_push_whitelist"` PushWhitelistTeams []string `json:"push_whitelist_teams"` + PushWhitelistUsernames []string `json:"push_whitelist_usernames"` + PushWhitelistActionsUser bool `json:"push_whitelist_actions_user"` EnableForcePush bool `json:"enable_force_push"` EnableForcePushAllowlist bool `json:"enable_force_push_allowlist"` ForcePushAllowlistTeams []string `json:"force_push_allowlist_teams"` + ForcePushAllowlistUsernames []string `json:"force_push_allowlist_usernames"` + ForcePushAllowlistActionsUser bool `json:"force_push_allowlist_actions_user"` EnableDelete bool `json:"enable_delete"` EnableDeleteAllowlist bool `json:"enable_delete_allowlist"` DeleteAllowlistTeams []string `json:"delete_allowlist_teams"` + DeleteAllowlistUsernames []string `json:"delete_allowlist_usernames"` + DeleteAllowlistActionsUser bool `json:"delete_allowlist_actions_user"` EnableMergeWhitelist bool `json:"enable_merge_whitelist"` MergeWhitelistTeams []string `json:"merge_whitelist_teams"` + MergeWhitelistUsernames []string `json:"merge_whitelist_usernames"` + MergeWhitelistActionsUser bool `json:"merge_whitelist_actions_user"` EnableStatusCheck bool `json:"enable_status_check"` StatusCheckContexts []string `json:"status_check_contexts"` RequiredApprovals int64 `json:"required_approvals"` EnableApprovalsWhitelist bool `json:"enable_approvals_whitelist"` ApprovalsWhitelistTeams []string `json:"approvals_whitelist_teams"` + ApprovalsWhitelistUsernames []string `json:"approvals_whitelist_username"` BlockOnRejectedReviews bool `json:"block_on_rejected_reviews"` BlockOnOfficialReviewRequests bool `json:"block_on_official_review_requests"` BlockOnOutdatedBranch bool `json:"block_on_outdated_branch"` @@ -49,19 +58,28 @@ type CreateOrgBranchProtectionOption struct { EnablePush bool `json:"enable_push"` EnablePushWhitelist bool `json:"enable_push_whitelist"` PushWhitelistTeams []string `json:"push_whitelist_teams"` + PushWhitelistUsernames []string `json:"push_whitelist_usernames"` + PushWhitelistActionsUser bool `json:"push_whitelist_actions_user"` EnableForcePush bool `json:"enable_force_push"` EnableForcePushAllowlist bool `json:"enable_force_push_allowlist"` ForcePushAllowlistTeams []string `json:"force_push_allowlist_teams"` + ForcePushAllowlistUsernames []string `json:"force_push_allowlist_usernames"` + ForcePushAllowlistActionsUser bool `json:"force_push_allowlist_actions_user"` EnableDelete bool `json:"enable_delete"` EnableDeleteAllowlist bool `json:"enable_delete_allowlist"` DeleteAllowlistTeams []string `json:"delete_allowlist_teams"` + DeleteAllowlistUsernames []string `json:"delete_allowlist_usernames"` + DeleteAllowlistActionsUser bool `json:"delete_allowlist_actions_user"` EnableMergeWhitelist bool `json:"enable_merge_whitelist"` MergeWhitelistTeams []string `json:"merge_whitelist_teams"` + MergeWhitelistUsernames []string `json:"merge_whitelist_usernames"` + MergeWhitelistActionsUser bool `json:"merge_whitelist_actions_user"` EnableStatusCheck bool `json:"enable_status_check"` StatusCheckContexts []string `json:"status_check_contexts"` RequiredApprovals int64 `json:"required_approvals"` EnableApprovalsWhitelist bool `json:"enable_approvals_whitelist"` ApprovalsWhitelistTeams []string `json:"approvals_whitelist_teams"` + ApprovalsWhitelistUsernames []string `json:"approvals_whitelist_username"` BlockOnRejectedReviews bool `json:"block_on_rejected_reviews"` BlockOnOfficialReviewRequests bool `json:"block_on_official_review_requests"` BlockOnOutdatedBranch bool `json:"block_on_outdated_branch"` @@ -79,19 +97,28 @@ type EditOrgBranchProtectionOption struct { EnablePush *bool `json:"enable_push"` EnablePushWhitelist *bool `json:"enable_push_whitelist"` PushWhitelistTeams []string `json:"push_whitelist_teams"` + PushWhitelistUsernames []string `json:"push_whitelist_usernames"` + PushWhitelistActionsUser *bool `json:"push_whitelist_actions_user"` EnableForcePush *bool `json:"enable_force_push"` EnableForcePushAllowlist *bool `json:"enable_force_push_allowlist"` ForcePushAllowlistTeams []string `json:"force_push_allowlist_teams"` + ForcePushAllowlistUsernames []string `json:"force_push_allowlist_usernames"` + ForcePushAllowlistActionsUser *bool `json:"force_push_allowlist_actions_user"` EnableDelete *bool `json:"enable_delete"` EnableDeleteAllowlist *bool `json:"enable_delete_allowlist"` DeleteAllowlistTeams []string `json:"delete_allowlist_teams"` + DeleteAllowlistUsernames []string `json:"delete_allowlist_usernames"` + DeleteAllowlistActionsUser *bool `json:"delete_allowlist_actions_user"` EnableMergeWhitelist *bool `json:"enable_merge_whitelist"` MergeWhitelistTeams []string `json:"merge_whitelist_teams"` + MergeWhitelistUsernames []string `json:"merge_whitelist_usernames"` + MergeWhitelistActionsUser *bool `json:"merge_whitelist_actions_user"` EnableStatusCheck *bool `json:"enable_status_check"` StatusCheckContexts []string `json:"status_check_contexts"` RequiredApprovals *int64 `json:"required_approvals"` EnableApprovalsWhitelist *bool `json:"enable_approvals_whitelist"` ApprovalsWhitelistTeams []string `json:"approvals_whitelist_teams"` + ApprovalsWhitelistUsernames []string `json:"approvals_whitelist_username"` BlockOnRejectedReviews *bool `json:"block_on_rejected_reviews"` BlockOnOfficialReviewRequests *bool `json:"block_on_official_review_requests"` BlockOnOutdatedBranch *bool `json:"block_on_outdated_branch"` diff --git a/routers/api/v1/org/branch_protection.go b/routers/api/v1/org/branch_protection.go index 010904087b..e06bcb28d3 100644 --- a/routers/api/v1/org/branch_protection.go +++ b/routers/api/v1/org/branch_protection.go @@ -8,6 +8,7 @@ import ( git_model "code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/git" "code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/organization" + user_model "code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/user" api "code.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/structs" "code.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/web" "code.mokoconsulting.tech/MokoConsulting/MokoGitea/services/context" @@ -36,6 +37,18 @@ func toAPIOrgBranchProtection(ctx *context.APIContext, rule *git_model.OrgProtec return names } + resolveUserNames := func(ids []int64) []string { + names := make([]string, 0, len(ids)) + for _, id := range ids { + u, err := user_model.GetUserByID(ctx, id) + if err != nil { + continue + } + names = append(names, u.Name) + } + return names + } + return &api.OrgBranchProtection{ ID: rule.ID, OrgID: rule.OrgID, @@ -44,19 +57,28 @@ func toAPIOrgBranchProtection(ctx *context.APIContext, rule *git_model.OrgProtec EnablePush: rule.CanPush, EnablePushWhitelist: rule.EnableWhitelist, PushWhitelistTeams: resolveTeamNames(rule.WhitelistTeamIDs), + PushWhitelistUsernames: resolveUserNames(rule.WhitelistUserIDs), + PushWhitelistActionsUser: rule.WhitelistActionsUser, EnableForcePush: rule.CanForcePush, EnableForcePushAllowlist: rule.EnableForcePushAllowlist, ForcePushAllowlistTeams: resolveTeamNames(rule.ForcePushAllowlistTeamIDs), + ForcePushAllowlistUsernames: resolveUserNames(rule.ForcePushAllowlistUserIDs), + ForcePushAllowlistActionsUser: rule.ForcePushAllowlistActionsUser, EnableDelete: rule.CanDelete, EnableDeleteAllowlist: rule.EnableDeleteAllowlist, DeleteAllowlistTeams: resolveTeamNames(rule.DeleteAllowlistTeamIDs), + DeleteAllowlistUsernames: resolveUserNames(rule.DeleteAllowlistUserIDs), + DeleteAllowlistActionsUser: rule.DeleteAllowlistActionsUser, EnableMergeWhitelist: rule.EnableMergeWhitelist, MergeWhitelistTeams: resolveTeamNames(rule.MergeWhitelistTeamIDs), + MergeWhitelistUsernames: resolveUserNames(rule.MergeWhitelistUserIDs), + MergeWhitelistActionsUser: rule.MergeWhitelistActionsUser, EnableStatusCheck: rule.EnableStatusCheck, StatusCheckContexts: rule.StatusCheckContexts, RequiredApprovals: rule.RequiredApprovals, EnableApprovalsWhitelist: rule.EnableApprovalsWhitelist, ApprovalsWhitelistTeams: resolveTeamNames(rule.ApprovalsWhitelistTeamIDs), + ApprovalsWhitelistUsernames: resolveUserNames(rule.ApprovalsWhitelistUserIDs), BlockOnRejectedReviews: rule.BlockOnRejectedReviews, BlockOnOfficialReviewRequests: rule.BlockOnOfficialReviewRequests, BlockOnOutdatedBranch: rule.BlockOnOutdatedBranch, @@ -88,6 +110,41 @@ func resolveTeamIDs(ctx *context.APIContext, orgID int64, names []string) ([]int return ids, true } +// resolveUserIDs converts a list of user identifiers (each a username or an email +// address) to deduped user IDs, returning 422 on the first unknown identifier. Each +// entry is looked up by username first, then falls back to an activated email address. +func resolveUserIDs(ctx *context.APIContext, names []string) ([]int64, bool) { + if len(names) == 0 { + return nil, true + } + seen := make(map[int64]struct{}, len(names)) + ids := make([]int64, 0, len(names)) + for _, name := range names { + u, err := user_model.GetUserByName(ctx, name) + if err != nil { + if !user_model.IsErrUserNotExist(err) { + ctx.APIErrorInternal(err) + return nil, false + } + u, err = user_model.GetUserByEmail(ctx, name) + if err != nil { + if user_model.IsErrUserNotExist(err) { + ctx.APIError(http.StatusUnprocessableEntity, "unknown user: "+name) + } else { + ctx.APIErrorInternal(err) + } + return nil, false + } + } + if _, dup := seen[u.ID]; dup { + continue + } + seen[u.ID] = struct{}{} + ids = append(ids, u.ID) + } + return ids, true +} + // ListOrgBranchProtections list org-level branch protection rules func ListOrgBranchProtections(ctx *context.APIContext) { // swagger:operation GET /orgs/{org}/branch_protections organization orgListBranchProtections @@ -218,6 +275,26 @@ func CreateOrgBranchProtection(ctx *context.APIContext) { if !ok { return } + pushUsers, ok := resolveUserIDs(ctx, form.PushWhitelistUsernames) + if !ok { + return + } + forcePushUsers, ok := resolveUserIDs(ctx, form.ForcePushAllowlistUsernames) + if !ok { + return + } + mergeUsers, ok := resolveUserIDs(ctx, form.MergeWhitelistUsernames) + if !ok { + return + } + approvalsUsers, ok := resolveUserIDs(ctx, form.ApprovalsWhitelistUsernames) + if !ok { + return + } + deleteUsers, ok := resolveUserIDs(ctx, form.DeleteAllowlistUsernames) + if !ok { + return + } rule := &git_model.OrgProtectedBranch{ OrgID: orgID, @@ -226,19 +303,28 @@ func CreateOrgBranchProtection(ctx *context.APIContext) { CanPush: form.EnablePush, EnableWhitelist: form.EnablePush && form.EnablePushWhitelist, WhitelistTeamIDs: pushTeams, + WhitelistUserIDs: pushUsers, + WhitelistActionsUser: form.EnablePush && form.EnablePushWhitelist && form.PushWhitelistActionsUser, CanForcePush: form.EnablePush && form.EnableForcePush, EnableForcePushAllowlist: form.EnablePush && form.EnableForcePush && form.EnableForcePushAllowlist, ForcePushAllowlistTeamIDs: forcePushTeams, + ForcePushAllowlistUserIDs: forcePushUsers, + ForcePushAllowlistActionsUser: form.EnablePush && form.EnableForcePush && form.EnableForcePushAllowlist && form.ForcePushAllowlistActionsUser, CanDelete: form.EnableDelete, EnableDeleteAllowlist: form.EnableDelete && form.EnableDeleteAllowlist, DeleteAllowlistTeamIDs: deleteTeams, + DeleteAllowlistUserIDs: deleteUsers, + DeleteAllowlistActionsUser: form.EnableDelete && form.EnableDeleteAllowlist && form.DeleteAllowlistActionsUser, EnableMergeWhitelist: form.EnableMergeWhitelist, MergeWhitelistTeamIDs: mergeTeams, + MergeWhitelistUserIDs: mergeUsers, + MergeWhitelistActionsUser: form.EnableMergeWhitelist && form.MergeWhitelistActionsUser, EnableStatusCheck: form.EnableStatusCheck, StatusCheckContexts: form.StatusCheckContexts, RequiredApprovals: form.RequiredApprovals, EnableApprovalsWhitelist: form.EnableApprovalsWhitelist, ApprovalsWhitelistTeamIDs: approvalsTeams, + ApprovalsWhitelistUserIDs: approvalsUsers, BlockOnRejectedReviews: form.BlockOnRejectedReviews, BlockOnOfficialReviewRequests: form.BlockOnOfficialReviewRequests, BlockOnOutdatedBranch: form.BlockOnOutdatedBranch, @@ -320,6 +406,16 @@ func EditOrgBranchProtection(ctx *context.APIContext) { } rule.WhitelistTeamIDs = ids } + if form.PushWhitelistUsernames != nil { + ids, ok := resolveUserIDs(ctx, form.PushWhitelistUsernames) + if !ok { + return + } + rule.WhitelistUserIDs = ids + } + if form.PushWhitelistActionsUser != nil { + rule.WhitelistActionsUser = *form.PushWhitelistActionsUser + } if form.EnableForcePush != nil { rule.CanForcePush = *form.EnableForcePush } @@ -333,6 +429,16 @@ func EditOrgBranchProtection(ctx *context.APIContext) { } rule.ForcePushAllowlistTeamIDs = ids } + if form.ForcePushAllowlistUsernames != nil { + ids, ok := resolveUserIDs(ctx, form.ForcePushAllowlistUsernames) + if !ok { + return + } + rule.ForcePushAllowlistUserIDs = ids + } + if form.ForcePushAllowlistActionsUser != nil { + rule.ForcePushAllowlistActionsUser = *form.ForcePushAllowlistActionsUser + } if form.EnableDelete != nil { rule.CanDelete = *form.EnableDelete } @@ -346,6 +452,16 @@ func EditOrgBranchProtection(ctx *context.APIContext) { } rule.DeleteAllowlistTeamIDs = ids } + if form.DeleteAllowlistUsernames != nil { + ids, ok := resolveUserIDs(ctx, form.DeleteAllowlistUsernames) + if !ok { + return + } + rule.DeleteAllowlistUserIDs = ids + } + if form.DeleteAllowlistActionsUser != nil { + rule.DeleteAllowlistActionsUser = *form.DeleteAllowlistActionsUser + } if form.EnableMergeWhitelist != nil { rule.EnableMergeWhitelist = *form.EnableMergeWhitelist } @@ -356,6 +472,16 @@ func EditOrgBranchProtection(ctx *context.APIContext) { } rule.MergeWhitelistTeamIDs = ids } + if form.MergeWhitelistUsernames != nil { + ids, ok := resolveUserIDs(ctx, form.MergeWhitelistUsernames) + if !ok { + return + } + rule.MergeWhitelistUserIDs = ids + } + if form.MergeWhitelistActionsUser != nil { + rule.MergeWhitelistActionsUser = *form.MergeWhitelistActionsUser + } if form.EnableStatusCheck != nil { rule.EnableStatusCheck = *form.EnableStatusCheck } @@ -375,6 +501,13 @@ func EditOrgBranchProtection(ctx *context.APIContext) { } rule.ApprovalsWhitelistTeamIDs = ids } + if form.ApprovalsWhitelistUsernames != nil { + ids, ok := resolveUserIDs(ctx, form.ApprovalsWhitelistUsernames) + if !ok { + return + } + rule.ApprovalsWhitelistUserIDs = ids + } if form.BlockOnRejectedReviews != nil { rule.BlockOnRejectedReviews = *form.BlockOnRejectedReviews } diff --git a/templates/swagger/v1_json.tmpl b/templates/swagger/v1_json.tmpl index d56889a7fa..6a1d0dd762 100644 --- a/templates/swagger/v1_json.tmpl +++ b/templates/swagger/v1_json.tmpl @@ -25356,6 +25356,13 @@ }, "x-go-name": "ApprovalsWhitelistTeams" }, + "approvals_whitelist_username": { + "type": "array", + "items": { + "type": "string" + }, + "x-go-name": "ApprovalsWhitelistUsernames" + }, "block_admin_merge_override": { "type": "boolean", "x-go-name": "BlockAdminMergeOverride" @@ -25372,6 +25379,10 @@ "type": "boolean", "x-go-name": "BlockOnRejectedReviews" }, + "delete_allowlist_actions_user": { + "type": "boolean", + "x-go-name": "DeleteAllowlistActionsUser" + }, "delete_allowlist_teams": { "type": "array", "items": { @@ -25379,6 +25390,13 @@ }, "x-go-name": "DeleteAllowlistTeams" }, + "delete_allowlist_usernames": { + "type": "array", + "items": { + "type": "string" + }, + "x-go-name": "DeleteAllowlistUsernames" + }, "dismiss_stale_approvals": { "type": "boolean", "x-go-name": "DismissStaleApprovals" @@ -25419,6 +25437,10 @@ "type": "boolean", "x-go-name": "EnableStatusCheck" }, + "force_push_allowlist_actions_user": { + "type": "boolean", + "x-go-name": "ForcePushAllowlistActionsUser" + }, "force_push_allowlist_teams": { "type": "array", "items": { @@ -25426,10 +25448,21 @@ }, "x-go-name": "ForcePushAllowlistTeams" }, + "force_push_allowlist_usernames": { + "type": "array", + "items": { + "type": "string" + }, + "x-go-name": "ForcePushAllowlistUsernames" + }, "ignore_stale_approvals": { "type": "boolean", "x-go-name": "IgnoreStaleApprovals" }, + "merge_whitelist_actions_user": { + "type": "boolean", + "x-go-name": "MergeWhitelistActionsUser" + }, "merge_whitelist_teams": { "type": "array", "items": { @@ -25437,6 +25470,13 @@ }, "x-go-name": "MergeWhitelistTeams" }, + "merge_whitelist_usernames": { + "type": "array", + "items": { + "type": "string" + }, + "x-go-name": "MergeWhitelistUsernames" + }, "priority": { "type": "integer", "format": "int64", @@ -25446,6 +25486,10 @@ "type": "string", "x-go-name": "ProtectedFilePatterns" }, + "push_whitelist_actions_user": { + "type": "boolean", + "x-go-name": "PushWhitelistActionsUser" + }, "push_whitelist_teams": { "type": "array", "items": { @@ -25453,6 +25497,13 @@ }, "x-go-name": "PushWhitelistTeams" }, + "push_whitelist_usernames": { + "type": "array", + "items": { + "type": "string" + }, + "x-go-name": "PushWhitelistUsernames" + }, "require_signed_commits": { "type": "boolean", "x-go-name": "RequireSignedCommits" @@ -26786,6 +26837,13 @@ }, "x-go-name": "ApprovalsWhitelistTeams" }, + "approvals_whitelist_username": { + "type": "array", + "items": { + "type": "string" + }, + "x-go-name": "ApprovalsWhitelistUsernames" + }, "block_admin_merge_override": { "type": "boolean", "x-go-name": "BlockAdminMergeOverride" @@ -26802,6 +26860,10 @@ "type": "boolean", "x-go-name": "BlockOnRejectedReviews" }, + "delete_allowlist_actions_user": { + "type": "boolean", + "x-go-name": "DeleteAllowlistActionsUser" + }, "delete_allowlist_teams": { "type": "array", "items": { @@ -26809,6 +26871,13 @@ }, "x-go-name": "DeleteAllowlistTeams" }, + "delete_allowlist_usernames": { + "type": "array", + "items": { + "type": "string" + }, + "x-go-name": "DeleteAllowlistUsernames" + }, "dismiss_stale_approvals": { "type": "boolean", "x-go-name": "DismissStaleApprovals" @@ -26849,6 +26918,10 @@ "type": "boolean", "x-go-name": "EnableStatusCheck" }, + "force_push_allowlist_actions_user": { + "type": "boolean", + "x-go-name": "ForcePushAllowlistActionsUser" + }, "force_push_allowlist_teams": { "type": "array", "items": { @@ -26856,10 +26929,21 @@ }, "x-go-name": "ForcePushAllowlistTeams" }, + "force_push_allowlist_usernames": { + "type": "array", + "items": { + "type": "string" + }, + "x-go-name": "ForcePushAllowlistUsernames" + }, "ignore_stale_approvals": { "type": "boolean", "x-go-name": "IgnoreStaleApprovals" }, + "merge_whitelist_actions_user": { + "type": "boolean", + "x-go-name": "MergeWhitelistActionsUser" + }, "merge_whitelist_teams": { "type": "array", "items": { @@ -26867,6 +26951,13 @@ }, "x-go-name": "MergeWhitelistTeams" }, + "merge_whitelist_usernames": { + "type": "array", + "items": { + "type": "string" + }, + "x-go-name": "MergeWhitelistUsernames" + }, "priority": { "type": "integer", "format": "int64", @@ -26876,6 +26967,10 @@ "type": "string", "x-go-name": "ProtectedFilePatterns" }, + "push_whitelist_actions_user": { + "type": "boolean", + "x-go-name": "PushWhitelistActionsUser" + }, "push_whitelist_teams": { "type": "array", "items": { @@ -26883,6 +26978,13 @@ }, "x-go-name": "PushWhitelistTeams" }, + "push_whitelist_usernames": { + "type": "array", + "items": { + "type": "string" + }, + "x-go-name": "PushWhitelistUsernames" + }, "require_signed_commits": { "type": "boolean", "x-go-name": "RequireSignedCommits" @@ -29749,6 +29851,13 @@ }, "x-go-name": "ApprovalsWhitelistTeams" }, + "approvals_whitelist_username": { + "type": "array", + "items": { + "type": "string" + }, + "x-go-name": "ApprovalsWhitelistUsernames" + }, "block_admin_merge_override": { "type": "boolean", "x-go-name": "BlockAdminMergeOverride" @@ -29770,6 +29879,10 @@ "format": "date-time", "x-go-name": "Created" }, + "delete_allowlist_actions_user": { + "type": "boolean", + "x-go-name": "DeleteAllowlistActionsUser" + }, "delete_allowlist_teams": { "type": "array", "items": { @@ -29777,6 +29890,13 @@ }, "x-go-name": "DeleteAllowlistTeams" }, + "delete_allowlist_usernames": { + "type": "array", + "items": { + "type": "string" + }, + "x-go-name": "DeleteAllowlistUsernames" + }, "dismiss_stale_approvals": { "type": "boolean", "x-go-name": "DismissStaleApprovals" @@ -29817,6 +29937,10 @@ "type": "boolean", "x-go-name": "EnableStatusCheck" }, + "force_push_allowlist_actions_user": { + "type": "boolean", + "x-go-name": "ForcePushAllowlistActionsUser" + }, "force_push_allowlist_teams": { "type": "array", "items": { @@ -29824,6 +29948,13 @@ }, "x-go-name": "ForcePushAllowlistTeams" }, + "force_push_allowlist_usernames": { + "type": "array", + "items": { + "type": "string" + }, + "x-go-name": "ForcePushAllowlistUsernames" + }, "id": { "type": "integer", "format": "int64", @@ -29833,6 +29964,10 @@ "type": "boolean", "x-go-name": "IgnoreStaleApprovals" }, + "merge_whitelist_actions_user": { + "type": "boolean", + "x-go-name": "MergeWhitelistActionsUser" + }, "merge_whitelist_teams": { "type": "array", "items": { @@ -29840,6 +29975,13 @@ }, "x-go-name": "MergeWhitelistTeams" }, + "merge_whitelist_usernames": { + "type": "array", + "items": { + "type": "string" + }, + "x-go-name": "MergeWhitelistUsernames" + }, "org_id": { "type": "integer", "format": "int64", @@ -29854,6 +29996,10 @@ "type": "string", "x-go-name": "ProtectedFilePatterns" }, + "push_whitelist_actions_user": { + "type": "boolean", + "x-go-name": "PushWhitelistActionsUser" + }, "push_whitelist_teams": { "type": "array", "items": { @@ -29861,6 +30007,13 @@ }, "x-go-name": "PushWhitelistTeams" }, + "push_whitelist_usernames": { + "type": "array", + "items": { + "type": "string" + }, + "x-go-name": "PushWhitelistUsernames" + }, "require_signed_commits": { "type": "boolean", "x-go-name": "RequireSignedCommits" diff --git a/templates/swagger/v1_openapi3_json.tmpl b/templates/swagger/v1_openapi3_json.tmpl index d8603921a0..d47403d808 100644 --- a/templates/swagger/v1_openapi3_json.tmpl +++ b/templates/swagger/v1_openapi3_json.tmpl @@ -4483,6 +4483,13 @@ "type": "array", "x-go-name": "ApprovalsWhitelistTeams" }, + "approvals_whitelist_username": { + "items": { + "type": "string" + }, + "type": "array", + "x-go-name": "ApprovalsWhitelistUsernames" + }, "block_admin_merge_override": { "type": "boolean", "x-go-name": "BlockAdminMergeOverride" @@ -4499,6 +4506,10 @@ "type": "boolean", "x-go-name": "BlockOnRejectedReviews" }, + "delete_allowlist_actions_user": { + "type": "boolean", + "x-go-name": "DeleteAllowlistActionsUser" + }, "delete_allowlist_teams": { "items": { "type": "string" @@ -4506,6 +4517,13 @@ "type": "array", "x-go-name": "DeleteAllowlistTeams" }, + "delete_allowlist_usernames": { + "items": { + "type": "string" + }, + "type": "array", + "x-go-name": "DeleteAllowlistUsernames" + }, "dismiss_stale_approvals": { "type": "boolean", "x-go-name": "DismissStaleApprovals" @@ -4546,6 +4564,10 @@ "type": "boolean", "x-go-name": "EnableStatusCheck" }, + "force_push_allowlist_actions_user": { + "type": "boolean", + "x-go-name": "ForcePushAllowlistActionsUser" + }, "force_push_allowlist_teams": { "items": { "type": "string" @@ -4553,10 +4575,21 @@ "type": "array", "x-go-name": "ForcePushAllowlistTeams" }, + "force_push_allowlist_usernames": { + "items": { + "type": "string" + }, + "type": "array", + "x-go-name": "ForcePushAllowlistUsernames" + }, "ignore_stale_approvals": { "type": "boolean", "x-go-name": "IgnoreStaleApprovals" }, + "merge_whitelist_actions_user": { + "type": "boolean", + "x-go-name": "MergeWhitelistActionsUser" + }, "merge_whitelist_teams": { "items": { "type": "string" @@ -4564,6 +4597,13 @@ "type": "array", "x-go-name": "MergeWhitelistTeams" }, + "merge_whitelist_usernames": { + "items": { + "type": "string" + }, + "type": "array", + "x-go-name": "MergeWhitelistUsernames" + }, "priority": { "format": "int64", "type": "integer", @@ -4573,6 +4613,10 @@ "type": "string", "x-go-name": "ProtectedFilePatterns" }, + "push_whitelist_actions_user": { + "type": "boolean", + "x-go-name": "PushWhitelistActionsUser" + }, "push_whitelist_teams": { "items": { "type": "string" @@ -4580,6 +4624,13 @@ "type": "array", "x-go-name": "PushWhitelistTeams" }, + "push_whitelist_usernames": { + "items": { + "type": "string" + }, + "type": "array", + "x-go-name": "PushWhitelistUsernames" + }, "require_signed_commits": { "type": "boolean", "x-go-name": "RequireSignedCommits" @@ -5885,6 +5936,13 @@ "type": "array", "x-go-name": "ApprovalsWhitelistTeams" }, + "approvals_whitelist_username": { + "items": { + "type": "string" + }, + "type": "array", + "x-go-name": "ApprovalsWhitelistUsernames" + }, "block_admin_merge_override": { "type": "boolean", "x-go-name": "BlockAdminMergeOverride" @@ -5901,6 +5959,10 @@ "type": "boolean", "x-go-name": "BlockOnRejectedReviews" }, + "delete_allowlist_actions_user": { + "type": "boolean", + "x-go-name": "DeleteAllowlistActionsUser" + }, "delete_allowlist_teams": { "items": { "type": "string" @@ -5908,6 +5970,13 @@ "type": "array", "x-go-name": "DeleteAllowlistTeams" }, + "delete_allowlist_usernames": { + "items": { + "type": "string" + }, + "type": "array", + "x-go-name": "DeleteAllowlistUsernames" + }, "dismiss_stale_approvals": { "type": "boolean", "x-go-name": "DismissStaleApprovals" @@ -5948,6 +6017,10 @@ "type": "boolean", "x-go-name": "EnableStatusCheck" }, + "force_push_allowlist_actions_user": { + "type": "boolean", + "x-go-name": "ForcePushAllowlistActionsUser" + }, "force_push_allowlist_teams": { "items": { "type": "string" @@ -5955,10 +6028,21 @@ "type": "array", "x-go-name": "ForcePushAllowlistTeams" }, + "force_push_allowlist_usernames": { + "items": { + "type": "string" + }, + "type": "array", + "x-go-name": "ForcePushAllowlistUsernames" + }, "ignore_stale_approvals": { "type": "boolean", "x-go-name": "IgnoreStaleApprovals" }, + "merge_whitelist_actions_user": { + "type": "boolean", + "x-go-name": "MergeWhitelistActionsUser" + }, "merge_whitelist_teams": { "items": { "type": "string" @@ -5966,6 +6050,13 @@ "type": "array", "x-go-name": "MergeWhitelistTeams" }, + "merge_whitelist_usernames": { + "items": { + "type": "string" + }, + "type": "array", + "x-go-name": "MergeWhitelistUsernames" + }, "priority": { "format": "int64", "type": "integer", @@ -5975,6 +6066,10 @@ "type": "string", "x-go-name": "ProtectedFilePatterns" }, + "push_whitelist_actions_user": { + "type": "boolean", + "x-go-name": "PushWhitelistActionsUser" + }, "push_whitelist_teams": { "items": { "type": "string" @@ -5982,6 +6077,13 @@ "type": "array", "x-go-name": "PushWhitelistTeams" }, + "push_whitelist_usernames": { + "items": { + "type": "string" + }, + "type": "array", + "x-go-name": "PushWhitelistUsernames" + }, "require_signed_commits": { "type": "boolean", "x-go-name": "RequireSignedCommits" @@ -8856,6 +8958,13 @@ "type": "array", "x-go-name": "ApprovalsWhitelistTeams" }, + "approvals_whitelist_username": { + "items": { + "type": "string" + }, + "type": "array", + "x-go-name": "ApprovalsWhitelistUsernames" + }, "block_admin_merge_override": { "type": "boolean", "x-go-name": "BlockAdminMergeOverride" @@ -8877,6 +8986,10 @@ "type": "string", "x-go-name": "Created" }, + "delete_allowlist_actions_user": { + "type": "boolean", + "x-go-name": "DeleteAllowlistActionsUser" + }, "delete_allowlist_teams": { "items": { "type": "string" @@ -8884,6 +8997,13 @@ "type": "array", "x-go-name": "DeleteAllowlistTeams" }, + "delete_allowlist_usernames": { + "items": { + "type": "string" + }, + "type": "array", + "x-go-name": "DeleteAllowlistUsernames" + }, "dismiss_stale_approvals": { "type": "boolean", "x-go-name": "DismissStaleApprovals" @@ -8924,6 +9044,10 @@ "type": "boolean", "x-go-name": "EnableStatusCheck" }, + "force_push_allowlist_actions_user": { + "type": "boolean", + "x-go-name": "ForcePushAllowlistActionsUser" + }, "force_push_allowlist_teams": { "items": { "type": "string" @@ -8931,6 +9055,13 @@ "type": "array", "x-go-name": "ForcePushAllowlistTeams" }, + "force_push_allowlist_usernames": { + "items": { + "type": "string" + }, + "type": "array", + "x-go-name": "ForcePushAllowlistUsernames" + }, "id": { "format": "int64", "type": "integer", @@ -8940,6 +9071,10 @@ "type": "boolean", "x-go-name": "IgnoreStaleApprovals" }, + "merge_whitelist_actions_user": { + "type": "boolean", + "x-go-name": "MergeWhitelistActionsUser" + }, "merge_whitelist_teams": { "items": { "type": "string" @@ -8947,6 +9082,13 @@ "type": "array", "x-go-name": "MergeWhitelistTeams" }, + "merge_whitelist_usernames": { + "items": { + "type": "string" + }, + "type": "array", + "x-go-name": "MergeWhitelistUsernames" + }, "org_id": { "format": "int64", "type": "integer", @@ -8961,6 +9103,10 @@ "type": "string", "x-go-name": "ProtectedFilePatterns" }, + "push_whitelist_actions_user": { + "type": "boolean", + "x-go-name": "PushWhitelistActionsUser" + }, "push_whitelist_teams": { "items": { "type": "string" @@ -8968,6 +9114,13 @@ "type": "array", "x-go-name": "PushWhitelistTeams" }, + "push_whitelist_usernames": { + "items": { + "type": "string" + }, + "type": "array", + "x-go-name": "PushWhitelistUsernames" + }, "require_signed_commits": { "type": "boolean", "x-go-name": "RequireSignedCommits" -- 2.52.0