diff --git a/ee/app/assets/javascripts/security_orchestration/components/policy_editor/scan_result/editor_component.vue b/ee/app/assets/javascripts/security_orchestration/components/policy_editor/scan_result/editor_component.vue index ec74a22a3d73a4a414eb47ea91c9058c70a31f67..7b48adbb996d857e50267e94b8041747b3a17864 100644 --- a/ee/app/assets/javascripts/security_orchestration/components/policy_editor/scan_result/editor_component.vue +++ b/ee/app/assets/javascripts/security_orchestration/components/policy_editor/scan_result/editor_component.vue @@ -32,7 +32,9 @@ import { buildSettingsList, createPolicyObject, DEFAULT_SCAN_RESULT_POLICY, + DEFAULT_SCAN_RESULT_POLICY_WITH_FALLBACK, DEFAULT_SCAN_RESULT_POLICY_WITH_SCOPE, + DEFAULT_SCAN_RESULT_POLICY_WITH_SCOPE_WITH_FALLBACK, getInvalidBranches, fromYaml, policyToYaml, @@ -113,9 +115,21 @@ export default { }, }, data() { - const newPolicyYaml = isGroup(this.namespaceType) - ? DEFAULT_SCAN_RESULT_POLICY_WITH_SCOPE - : DEFAULT_SCAN_RESULT_POLICY; + let newPolicyYaml; + + const includeFallback = + this.glFeatures.mergeRequestApprovalPoliciesFallbackBehavior || + this.glFeatures.mergeRequestApprovalPoliciesFallbackBehaviorGroup; + + if (isGroup(this.namespaceType)) { + newPolicyYaml = includeFallback + ? DEFAULT_SCAN_RESULT_POLICY_WITH_SCOPE_WITH_FALLBACK + : DEFAULT_SCAN_RESULT_POLICY_WITH_SCOPE; + } else { + newPolicyYaml = includeFallback + ? DEFAULT_SCAN_RESULT_POLICY_WITH_FALLBACK + : DEFAULT_SCAN_RESULT_POLICY; + } const yamlEditorValue = this.existingPolicy ? policyToYaml(this.existingPolicy) : newPolicyYaml; diff --git a/ee/app/assets/javascripts/security_orchestration/components/policy_editor/scan_result/lib/from_yaml.js b/ee/app/assets/javascripts/security_orchestration/components/policy_editor/scan_result/lib/from_yaml.js index 7c93ac5418a807abb5c44e611bd0ddff4702aa3d..522615a71ab12da140828141915dca898651707d 100644 --- a/ee/app/assets/javascripts/security_orchestration/components/policy_editor/scan_result/lib/from_yaml.js +++ b/ee/app/assets/javascripts/security_orchestration/components/policy_editor/scan_result/lib/from_yaml.js @@ -1,7 +1,11 @@ import { safeLoad } from 'js-yaml'; import { isBoolean, isEqual } from 'lodash'; import { addIdsToPolicy, hasInvalidKey, isValidPolicy } from '../../utils'; -import { MATCH_ON_INCLUSION, MATCH_ON_INCLUSION_LICENSE } from '../../constants'; +import { + MATCH_ON_INCLUSION, + MATCH_ON_INCLUSION_LICENSE, + PRIMARY_POLICY_KEYS, +} from '../../constants'; import { VALID_APPROVAL_SETTINGS, PERMITTED_INVALID_SETTINGS, @@ -28,6 +32,8 @@ export const fromYaml = ({ manifest, validateRuleMode = false }) => { ? MATCH_ON_INCLUSION_LICENSE : MATCH_ON_INCLUSION; + const primaryKeys = [...PRIMARY_POLICY_KEYS, 'fallback_behavior']; + const rulesKeys = [ 'type', 'branches', @@ -68,7 +74,7 @@ export const fromYaml = ({ manifest, validateRuleMode = false }) => { ? !Object.values(settings).every((setting) => isBoolean(setting)) : false; - return isValidPolicy({ policy, rulesKeys, actionsKeys }) && + return isValidPolicy({ policy, primaryKeys, rulesKeys, actionsKeys }) && !hasInvalidApprovalSettings && !hasInvalidSettingStructure ? policy diff --git a/ee/app/assets/javascripts/security_orchestration/components/policy_editor/scan_result/lib/index.js b/ee/app/assets/javascripts/security_orchestration/components/policy_editor/scan_result/lib/index.js index fb52ed7cdd3e6e760a5d58427a663031227c994d..7b07c851f3bd39a50ffbbbd38988c4f467e4ba0a 100644 --- a/ee/app/assets/javascripts/security_orchestration/components/policy_editor/scan_result/lib/index.js +++ b/ee/app/assets/javascripts/security_orchestration/components/policy_editor/scan_result/lib/index.js @@ -36,3 +36,11 @@ approval_settings: block_branch_modification: true prevent_pushing_and_force_pushing: true `; + +export const DEFAULT_SCAN_RESULT_POLICY_WITH_FALLBACK = DEFAULT_SCAN_RESULT_POLICY.concat( + 'fallback_behavior:\n fail: closed', +); + +export const DEFAULT_SCAN_RESULT_POLICY_WITH_SCOPE_WITH_FALLBACK = DEFAULT_SCAN_RESULT_POLICY_WITH_SCOPE.concat( + 'fallback_behavior:\n fail: closed', +); diff --git a/ee/app/controllers/groups/security/policies_controller.rb b/ee/app/controllers/groups/security/policies_controller.rb index 16558f1c493e7a4d9c756e704890d68ffda96409..dc166ba023ccafc082bdd3dd23396730fc5f67b1 100644 --- a/ee/app/controllers/groups/security/policies_controller.rb +++ b/ee/app/controllers/groups/security/policies_controller.rb @@ -13,6 +13,7 @@ class PoliciesController < Groups::ApplicationController push_frontend_feature_flag(:compliance_pipeline_in_policies, group) push_frontend_feature_flag(:pipeline_execution_policy_type, group) push_frontend_feature_flag(:security_policies_breaking_changes, group) + push_frontend_feature_flag(:merge_request_approval_policies_fallback_behavior_group, group) end feature_category :security_policy_management diff --git a/ee/app/controllers/projects/security/policies_controller.rb b/ee/app/controllers/projects/security/policies_controller.rb index 41b96e50844d0bd59f19e5a3e890b9d0df249b2b..616ceb56155938032e5bf64e48fe4c594d81fca0 100644 --- a/ee/app/controllers/projects/security/policies_controller.rb +++ b/ee/app/controllers/projects/security/policies_controller.rb @@ -16,6 +16,7 @@ class PoliciesController < Projects::ApplicationController push_frontend_feature_flag(:pipeline_execution_policy_type, project.group) if project.group push_frontend_feature_flag(:security_policies_breaking_changes, project) push_frontend_feature_flag(:approval_policy_disable_bot_comment, project) + push_frontend_feature_flag(:merge_request_approval_policies_fallback_behavior, project) end feature_category :security_policy_management diff --git a/ee/config/feature_flags/gitlab_com_derisk/merge_request_approval_policies_fallback_behavior_group.yml b/ee/config/feature_flags/gitlab_com_derisk/merge_request_approval_policies_fallback_behavior_group.yml new file mode 100644 index 0000000000000000000000000000000000000000..81396aa017a1bc10c0618eaefa0558092fab67fe --- /dev/null +++ b/ee/config/feature_flags/gitlab_com_derisk/merge_request_approval_policies_fallback_behavior_group.yml @@ -0,0 +1,9 @@ +--- +name: merge_request_approval_policies_fallback_behavior_group +feature_issue_url: https://gitlab.com/groups/gitlab-org/-/epics/10816 +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/149151 +rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/457790 +milestone: '17.0' +group: group::security policies +type: gitlab_com_derisk +default_enabled: false diff --git a/ee/spec/frontend/security_orchestration/components/policy_editor/scan_result/editor_component_spec.js b/ee/spec/frontend/security_orchestration/components/policy_editor/scan_result/editor_component_spec.js index 6e26ad7a47d50babef4bb00611fec1b18228fe4f..b27f128b2c393863f95ba733774e157404f80f1b 100644 --- a/ee/spec/frontend/security_orchestration/components/policy_editor/scan_result/editor_component_spec.js +++ b/ee/spec/frontend/security_orchestration/components/policy_editor/scan_result/editor_component_spec.js @@ -8,6 +8,9 @@ import { SCAN_FINDING, ANY_MERGE_REQUEST, DEFAULT_SCAN_RESULT_POLICY, + DEFAULT_SCAN_RESULT_POLICY_WITH_FALLBACK, + DEFAULT_SCAN_RESULT_POLICY_WITH_SCOPE, + DEFAULT_SCAN_RESULT_POLICY_WITH_SCOPE_WITH_FALLBACK, getInvalidBranches, fromYaml, } from 'ee/security_orchestration/components/policy_editor/scan_result/lib'; @@ -172,24 +175,28 @@ describe('EditorComponent', () => { `('should render default policy for a $namespaceType', ({ namespaceType, manifest }) => { factory({ provide: { namespaceType } }); expect(findPolicyEditorLayout().props('policy')).toEqual(manifest); - }); - - it('passes the default yamlEditorValue prop to the PolicyEditorLayout component', () => { - factory(); - expect(findPolicyEditorLayout().props('yamlEditorValue')).toBe(DEFAULT_SCAN_RESULT_POLICY); + expect(findPolicyEditorLayout().props('hasParsingError')).toBe(false); }); it.each` - prop | compareFn | expected - ${'yamlEditorValue'} | ${'toBe'} | ${DEFAULT_SCAN_RESULT_POLICY} - ${'hasParsingError'} | ${'toBe'} | ${false} - ${'policy'} | ${'toStrictEqual'} | ${fromYaml({ manifest: DEFAULT_SCAN_RESULT_POLICY })} + namespaceType | projectFeatureFlag | groupFeatureFlag | manifest + ${NAMESPACE_TYPES.GROUP} | ${false} | ${false} | ${DEFAULT_SCAN_RESULT_POLICY_WITH_SCOPE} + ${NAMESPACE_TYPES.GROUP} | ${false} | ${true} | ${DEFAULT_SCAN_RESULT_POLICY_WITH_SCOPE_WITH_FALLBACK} + ${NAMESPACE_TYPES.PROJECT} | ${false} | ${false} | ${DEFAULT_SCAN_RESULT_POLICY} + ${NAMESPACE_TYPES.PROJECT} | ${true} | ${false} | ${DEFAULT_SCAN_RESULT_POLICY_WITH_FALLBACK} `( - 'passes the correct $prop prop to the PolicyEditorLayout component', - ({ prop, compareFn, expected }) => { - uniqueId.mockRestore(); - factory(); - expect(findPolicyEditorLayout().props(prop))[compareFn](expected); + 'sets the correct default policy yaml for $namespaceType namespace and project feature flag $projectFeatureFlag and group feature flag $groupFeatureFlag', + ({ namespaceType, projectFeatureFlag, groupFeatureFlag, manifest }) => { + factory({ + provide: { + namespaceType, + glFeatures: { + mergeRequestApprovalPoliciesFallbackBehavior: projectFeatureFlag, + mergeRequestApprovalPoliciesFallbackBehaviorGroup: groupFeatureFlag, + }, + }, + }); + expect(findPolicyEditorLayout().props('yamlEditorValue')).toBe(manifest); }, );