Skip to content
Snippets Groups Projects
Unverified Commit 30a5dc40 authored by Alexander Turinske's avatar Alexander Turinske
Browse files

Merge branch '420878-policies-merge-request-rule' into 'master'

parents 5f65a98a 4aa72d0f
No related branches found
No related tags found
No related merge requests found
Showing
with 328 additions and 27 deletions
Loading
Loading
@@ -302,7 +302,7 @@ const humanizeRule = (rule) => {
}
};
 
addCriteria(rule.severity_levels.length, () =>
addCriteria(rule.severity_levels?.length, () =>
sprintf(s__('SecurityOrchestration|Severity is %{severity}.'), {
severity: humanizeItems({
items: rule.severity_levels,
Loading
Loading
@@ -310,7 +310,7 @@ const humanizeRule = (rule) => {
}),
);
 
addCriteria(rule.vulnerability_states.length, () =>
addCriteria(rule.vulnerability_states?.length, () =>
sprintf(s__('SecurityOrchestration|Vulnerabilities are %{vulnerabilityStates}.'), {
vulnerabilityStates: humanizeVulnerabilityStates(rule.vulnerability_states),
}),
Loading
Loading
Loading
Loading
@@ -116,6 +116,9 @@ export const ALL_PROTECTED_BRANCHES = {
value: 'protected',
};
 
export const ANY_COMMIT = 'any';
export const ANY_UNSIGNED_COMMIT = 'unsigned';
export const ANY_OPERATOR = 'ANY';
 
export const GREATER_THAN_OPERATOR = 'greater_than';
Loading
Loading
<script>
import { GlSprintf, GlCollapsibleListbox } from '@gitlab/ui';
import { s__ } from '~/locale';
import { ANY_COMMIT, ANY_UNSIGNED_COMMIT, SCAN_RESULT_BRANCH_TYPE_OPTIONS } from '../constants';
import SectionLayout from '../section_layout.vue';
import PolicyRuleBranchSelection from './policy_rule_branch_selection.vue';
import ScanTypeSelect from './base_layout/scan_type_select.vue';
import { getDefaultRule } from './lib';
const COMMIT_LISTBOX_ITEMS = [
{
value: ANY_COMMIT,
text: s__('ScanResultPolicy|any commits'),
},
{
value: ANY_UNSIGNED_COMMIT,
text: s__('ScanResultPolicy|any unsigned commits'),
},
];
export default {
COMMIT_LISTBOX_ITEMS,
i18n: {
anyMergeRequestRuleCopy: s__(
'ScanResultPolicy|When %{scanType} in an open that targets %{branches} with %{commitType}',
),
},
name: 'AnyMergeRequestRuleBuilder',
components: {
ScanTypeSelect,
SectionLayout,
GlCollapsibleListbox,
GlSprintf,
PolicyRuleBranchSelection,
},
inject: ['namespaceType'],
props: {
initRule: {
type: Object,
required: true,
},
},
computed: {
branchTypes() {
return SCAN_RESULT_BRANCH_TYPE_OPTIONS(this.namespaceType);
},
selectedCommitType() {
return this.initRule.commits || ANY_COMMIT;
},
},
methods: {
setBranchType(value) {
this.$emit('changed', value);
},
setScanType(value) {
const rule = getDefaultRule(value);
this.$emit('set-scan-type', rule);
},
setCommitType(type) {
this.triggerChanged({ commits: type });
},
triggerChanged(value) {
this.$emit('changed', { ...this.initRule, ...value });
},
},
};
</script>
<template>
<section-layout :type="initRule.type" :show-remove-button="false">
<template #content>
<section-layout class="gl-bg-white!" :type="initRule.type" @remove="$emit('remove')">
<template #content>
<gl-sprintf :message="$options.i18n.anyMergeRequestRuleCopy">
<template #scanType>
<scan-type-select :scan-type="initRule.type" @select="setScanType" />
</template>
<template #branches>
<policy-rule-branch-selection
:init-rule="initRule"
:branch-types="branchTypes"
@changed="triggerChanged"
@set-branch-type="setBranchType"
/>
</template>
<template #commitType>
<gl-collapsible-listbox
data-testid="commits-type"
:items="$options.COMMIT_LISTBOX_ITEMS"
:selected="selectedCommitType"
@select="setCommitType"
/>
</template>
</gl-sprintf>
</template>
</section-layout>
</template>
</section-layout>
</template>
<script>
import { GlCollapsibleListbox } from '@gitlab/ui';
import { s__ } from '~/locale';
import { SCAN_FINDING, LICENSE_FINDING } from '../lib';
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import { ANY_MERGE_REQUEST, SCAN_FINDING, LICENSE_FINDING } from '../lib';
 
export default {
scanTypeOptions: [
Loading
Loading
@@ -21,6 +22,7 @@ export default {
components: {
GlCollapsibleListbox,
},
mixins: [glFeatureFlagsMixin()],
props: {
scanType: {
type: String,
Loading
Loading
@@ -37,8 +39,18 @@ export default {
scanRuleTypeToggleText() {
return this.scanType ? '' : this.$options.i18n.scanRuleTypeToggleText;
},
anyMergeRequestItem() {
return this.glFeatures.scanResultAnyMergeRequest
? [
{
value: ANY_MERGE_REQUEST,
text: s__('SecurityOrchestration|Any merge request'),
},
]
: [];
},
listBoxItems() {
return [...this.items, ...this.$options.scanTypeOptions];
return [...this.anyMergeRequestItem, ...this.items, ...this.$options.scanTypeOptions];
},
},
methods: {
Loading
Loading
Loading
Loading
@@ -25,6 +25,7 @@ export const fromYaml = ({ manifest, validateRuleMode = false, glFeatures = {} }
'branches',
'branch_type',
'branch_exceptions',
'commits',
'license_states',
'license_types',
'match_on_inclusion',
Loading
Loading
Loading
Loading
@@ -12,6 +12,7 @@ export {
invalidVulnerabilityAge,
humanizeInvalidBranchesError,
invalidVulnerabilityAttributes,
ANY_MERGE_REQUEST,
SCAN_FINDING,
LICENSE_FINDING,
} from './rules';
Loading
Loading
Loading
Loading
@@ -5,6 +5,7 @@ import { REPORT_TYPES_DEFAULT } from 'ee/security_dashboard/store/constants';
import { isPositiveInteger } from '~/lib/utils/number_utils';
import {
ALL_PROTECTED_BRANCHES,
ANY_COMMIT,
BRANCH_TYPE_KEY,
INVALID_PROTECTED_BRANCHES,
VALID_SCAN_RESULT_BRANCH_TYPE_OPTIONS,
Loading
Loading
@@ -26,7 +27,7 @@ export const VULNERABILITY_STATE_KEYS = [
...Object.keys(APPROVAL_VULNERABILITY_STATES[NEWLY_DETECTED]),
...Object.keys(APPROVAL_VULNERABILITY_STATES[PREVIOUSLY_EXISTING]),
];
export const ANY_MERGE_REQUEST = 'any_merge_request';
export const SCAN_FINDING = 'scan_finding';
export const LICENSE_FINDING = 'license_finding';
export const MATCHING = s__('ScanResultPolicy|Matching');
Loading
Loading
@@ -57,6 +58,12 @@ export const licenseScanBuildRule = () => ({
branch_type: ALL_PROTECTED_BRANCHES.value,
});
 
export const anyMergeRequestBuildRule = () => ({
type: ANY_MERGE_REQUEST,
branch_type: ALL_PROTECTED_BRANCHES.value,
commits: ANY_COMMIT,
});
/*
Construct a new rule object for when the licenseScanningPolocies flag is on
*/
Loading
Loading
@@ -156,6 +163,8 @@ export const getDefaultRule = (scanType) => {
return securityScanBuildRule();
case LICENSE_FINDING:
return licenseScanBuildRule();
case ANY_MERGE_REQUEST:
return anyMergeRequestBuildRule();
default:
return emptyBuildRule();
}
Loading
Loading
<script>
import { GlAlert, GlSprintf } from '@gitlab/ui';
import { SCAN_FINDING, LICENSE_FINDING } from './lib';
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import { ANY_MERGE_REQUEST, SCAN_FINDING, LICENSE_FINDING } from './lib';
import AnyMergeRequestRuleBuilder from './any_merge_request_rule_builder.vue';
import SecurityScanRuleBuilder from './security_scan_rule_builder.vue';
import LicenseScanRuleBuilder from './license_scan_rule_builder.vue';
import DefaultRuleBuilder from './default_rule_builder.vue';
Loading
Loading
@@ -10,9 +12,11 @@ export default {
GlAlert,
GlSprintf,
DefaultRuleBuilder,
AnyMergeRequestRuleBuilder,
SecurityScanRuleBuilder,
LicenseScanRuleBuilder,
},
mixins: [glFeatureFlagsMixin()],
props: {
initRule: {
type: Object,
Loading
Loading
@@ -21,6 +25,7 @@ export default {
},
data() {
const previousRules = {
[ANY_MERGE_REQUEST]: null,
[SCAN_FINDING]: null,
[LICENSE_FINDING]: null,
};
Loading
Loading
@@ -39,6 +44,9 @@ export default {
};
},
computed: {
isAnyMergeRequestRule() {
return this.initRule.type === ANY_MERGE_REQUEST;
},
isSecurityRule() {
return this.initRule.type === SCAN_FINDING;
},
Loading
Loading
@@ -92,6 +100,14 @@ export default {
@set-scan-type="setScanType"
/>
 
<any-merge-request-rule-builder
v-else-if="isAnyMergeRequestRule && glFeatures.scanResultAnyMergeRequest"
:init-rule="initRule"
@changed="updateRule"
@remove="removeRule"
@set-scan-type="setScanType"
/>
<security-scan-rule-builder
v-else-if="isSecurityRule"
:init-rule="initRule"
Loading
Loading
Loading
Loading
@@ -11,6 +11,7 @@ class PoliciesController < Groups::ApplicationController
 
before_action do
push_frontend_feature_flag(:scan_result_policy_settings, group)
push_frontend_feature_flag(:scan_result_any_merge_request, group)
end
 
feature_category :security_policy_management
Loading
Loading
Loading
Loading
@@ -12,7 +12,8 @@ class PoliciesController < Projects::ApplicationController
 
before_action do
push_frontend_feature_flag(:security_policies_branch_exceptions, project)
push_frontend_feature_flag(:scan_result_policy_settings, @project)
push_frontend_feature_flag(:scan_result_policy_settings, project)
push_frontend_feature_flag(:scan_result_any_merge_request, project)
end
 
feature_category :security_policy_management
Loading
Loading
---
name: scan_result_any_merge_request
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/130630
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/423988
milestone: '16.4'
type: development
group: group::security policies
default_enabled: false
import { GlSprintf } from '@gitlab/ui';
import AnyMergeRequestRuleBuilder from 'ee/security_orchestration/components/policy_editor/scan_result_policy/any_merge_request_rule_builder.vue';
import SectionLayout from 'ee/security_orchestration/components/policy_editor/section_layout.vue';
import PolicyRuleBranchSelection from 'ee/security_orchestration/components/policy_editor/scan_result_policy/policy_rule_branch_selection.vue';
import ScanTypeSelect from 'ee/security_orchestration/components/policy_editor/scan_result_policy/base_layout/scan_type_select.vue';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import {
ANY_COMMIT,
ANY_UNSIGNED_COMMIT,
PROJECT_DEFAULT_BRANCH,
} from 'ee/security_orchestration/components/policy_editor/constants';
import {
ANY_MERGE_REQUEST,
getDefaultRule,
anyMergeRequestBuildRule,
SCAN_FINDING,
} from 'ee/security_orchestration/components/policy_editor/scan_result_policy/lib/rules';
import { NAMESPACE_TYPES } from 'ee/security_orchestration/constants';
describe('AnyMergeRequestRuleBuilder', () => {
let wrapper;
const UPDATED_RULE = {
...anyMergeRequestBuildRule(),
branch_type: PROJECT_DEFAULT_BRANCH.value,
commits: ANY_UNSIGNED_COMMIT,
};
const createComponent = ({ props = {} } = {}) => {
wrapper = shallowMountExtended(AnyMergeRequestRuleBuilder, {
propsData: {
initRule: anyMergeRequestBuildRule(),
...props,
},
provide: {
namespaceType: NAMESPACE_TYPES.GROUP,
},
stubs: {
GlSprintf,
},
});
};
const findAllBaseLayoutComponent = () => wrapper.findAllComponents(SectionLayout);
const findCommitsTypeListBox = () => wrapper.findByTestId('commits-type');
const findScanTypeSelect = () => wrapper.findComponent(ScanTypeSelect);
const findBranches = () => wrapper.findComponent(PolicyRuleBranchSelection);
describe('initial rendering', () => {
beforeEach(() => {
createComponent();
});
it('renders one field for each attribute of the rule', () => {
expect(findScanTypeSelect().exists()).toBe(true);
expect(findBranches().exists()).toBe(true);
expect(findCommitsTypeListBox().exists()).toBe(true);
});
it('can remove rule builder', () => {
findAllBaseLayoutComponent().at(1).vm.$emit('remove');
expect(wrapper.emitted('remove')).toHaveLength(1);
});
it('can change scan type', () => {
findScanTypeSelect().vm.$emit('select', SCAN_FINDING);
expect(wrapper.emitted('set-scan-type')).toEqual([[getDefaultRule(SCAN_FINDING)]]);
});
it('can change branch_type', () => {
findBranches().vm.$emit('set-branch-type', UPDATED_RULE);
expect(wrapper.emitted('changed')).toEqual([[UPDATED_RULE]]);
});
it('can change commits type', () => {
expect(findCommitsTypeListBox().props('selected')).toBe(ANY_COMMIT);
findCommitsTypeListBox().vm.$emit('select', ANY_UNSIGNED_COMMIT);
expect(wrapper.emitted('changed')).toEqual([
[
{
...getDefaultRule(ANY_MERGE_REQUEST),
commits: ANY_UNSIGNED_COMMIT,
},
],
]);
});
});
it('should display saved rule parameters', () => {
createComponent({
props: {
initRule: {
...UPDATED_RULE,
},
},
});
expect(findBranches().props('initRule')).toEqual(UPDATED_RULE);
expect(findCommitsTypeListBox().props('selected')).toBe(UPDATED_RULE.commits);
expect(findScanTypeSelect().props('scanType')).toBe(ANY_MERGE_REQUEST);
});
});
import { GlCollapsibleListbox, GlListboxItem } from '@gitlab/ui';
import ScanTypeSelect from 'ee/security_orchestration/components/policy_editor/scan_result_policy/base_layout/scan_type_select.vue';
import {
ANY_MERGE_REQUEST,
LICENSE_FINDING,
SCAN_FINDING,
} from 'ee/security_orchestration/components/policy_editor/scan_result_policy/lib';
Loading
Loading
@@ -14,6 +15,11 @@ describe('ScanTypeSelect', () => {
propsData: {
...props,
},
provide: {
glFeatures: {
scanResultAnyMergeRequest: true,
},
},
stubs: {
GlCollapsibleListbox,
},
Loading
Loading
@@ -25,7 +31,12 @@ describe('ScanTypeSelect', () => {
 
it('can render defaultOptions', () => {
createComponent();
expect(findListBoxItems()).toHaveLength(2);
expect(findListBoxItems()).toHaveLength(3);
expect(
findListBox()
.props('items')
.map(({ value }) => value),
).toStrictEqual([ANY_MERGE_REQUEST, SCAN_FINDING, LICENSE_FINDING]);
});
 
it('can select scan type', () => {
Loading
Loading
@@ -40,8 +51,8 @@ describe('ScanTypeSelect', () => {
items: [{ text: 'test', value: 'test' }],
});
 
expect(findListBoxItems()).toHaveLength(3);
expect(findListBoxItems().at(0).text('')).toEqual('test');
expect(findListBoxItems()).toHaveLength(4);
expect(findListBoxItems().at(1).text('')).toEqual('test');
});
 
it('can preselect existing scan', () => {
Loading
Loading
import { GlAlert, GlSprintf } from '@gitlab/ui';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import AnyMergeRequestRuleBuilder from 'ee/security_orchestration/components/policy_editor/scan_result_policy/any_merge_request_rule_builder.vue';
import DefaultRuleBuilder from 'ee/security_orchestration/components/policy_editor/scan_result_policy/default_rule_builder.vue';
import PolicyRuleBuilder from 'ee/security_orchestration/components/policy_editor/scan_result_policy/policy_rule_builder.vue';
import SecurityScanRuleBuilder from 'ee/security_orchestration/components/policy_editor/scan_result_policy/security_scan_rule_builder.vue';
Loading
Loading
@@ -10,6 +11,7 @@ import {
LICENSE_FINDING,
} from 'ee/security_orchestration/components/policy_editor/scan_result_policy/lib';
import {
anyMergeRequestBuildRule,
emptyBuildRule,
securityScanBuildRule,
licenseScanBuildRule,
Loading
Loading
@@ -34,6 +36,10 @@ describe('PolicyRuleBuilder', () => {
license_states: ['newly_detected', 'detected'],
};
 
const ANY_MERGE_REQUEST_RULE = {
...anyMergeRequestBuildRule(),
};
const factory = ({ propsData = {}, provide = {} } = {}) => {
wrapper = shallowMountExtended(PolicyRuleBuilder, {
propsData: {
Loading
Loading
@@ -43,6 +49,9 @@ describe('PolicyRuleBuilder', () => {
provide: {
namespaceId: '1',
namespaceType: NAMESPACE_TYPES.PROJECT,
glFeatures: {
scanResultAnyMergeRequest: true,
},
...provide,
},
stubs: {
Loading
Loading
@@ -52,28 +61,35 @@ describe('PolicyRuleBuilder', () => {
};
 
const findAlert = () => wrapper.findComponent(GlAlert);
const findAnyMergeRequestRule = () => wrapper.findComponent(AnyMergeRequestRuleBuilder);
const findEmptyScanRuleBuilder = () => wrapper.findComponent(DefaultRuleBuilder);
const findSecurityScanRule = () => wrapper.findComponent(SecurityScanRuleBuilder);
const findLicenseScanRule = () => wrapper.findComponent(LicenseScanRuleBuilder);
 
describe('when a rule type is selected', () => {
it.each`
ruleType | rule | showSecurityRule | showLicenseRule
${'unselected'} | ${emptyBuildRule()} | ${false} | ${false}
${'security scan'} | ${SECURITY_SCAN_RULE} | ${true} | ${false}
${'license scan'} | ${LICENSE_SCANNING_RULE} | ${false} | ${true}
`('renders the $ruleType policy', ({ rule, showSecurityRule, showLicenseRule }) => {
factory({ propsData: { initRule: rule } });
expect(findSecurityScanRule().exists()).toBe(showSecurityRule);
expect(findLicenseScanRule().exists()).toBe(showLicenseRule);
});
ruleType | rule | showSecurityRule | showLicenseRule | showAnyMergeRequestRule
${'unselected'} | ${emptyBuildRule()} | ${false} | ${false} | ${false}
${'security scan'} | ${SECURITY_SCAN_RULE} | ${true} | ${false} | ${false}
${'license scan'} | ${LICENSE_SCANNING_RULE} | ${false} | ${true} | ${false}
${'any merge request'} | ${ANY_MERGE_REQUEST_RULE} | ${false} | ${false} | ${true}
`(
'renders the $ruleType policy',
({ rule, showSecurityRule, showLicenseRule, showAnyMergeRequestRule }) => {
factory({ propsData: { initRule: rule } });
expect(findSecurityScanRule().exists()).toBe(showSecurityRule);
expect(findLicenseScanRule().exists()).toBe(showLicenseRule);
expect(findAnyMergeRequestRule().exists()).toBe(showAnyMergeRequestRule);
},
);
});
 
describe('selects correct rule', () => {
describe('change to different rule', () => {
it.each`
initialRule | findComponent | expectedRule
${licenseScanBuildRule()} | ${findLicenseScanRule} | ${securityScanBuildRule()}
${securityScanBuildRule()} | ${findSecurityScanRule} | ${licenseScanBuildRule()}
initialRule | findComponent | expectedRule
${licenseScanBuildRule()} | ${findLicenseScanRule} | ${securityScanBuildRule()}
${securityScanBuildRule()} | ${findSecurityScanRule} | ${licenseScanBuildRule()}
${anyMergeRequestBuildRule()} | ${findAnyMergeRequestRule} | ${licenseScanBuildRule()}
`('selects correct rule for scan type', ({ initialRule, findComponent, expectedRule }) => {
factory({
propsData: {
Loading
Loading
@@ -111,10 +127,11 @@ describe('PolicyRuleBuilder', () => {
 
describe('removing a rule', () => {
it.each`
ruleType | rule | findComponent
${'unselected'} | ${emptyBuildRule()} | ${findEmptyScanRuleBuilder}
${'security scan'} | ${SECURITY_SCAN_RULE} | ${findSecurityScanRule}
${'license scan'} | ${LICENSE_SCANNING_RULE} | ${findLicenseScanRule}
ruleType | rule | findComponent
${'unselected'} | ${emptyBuildRule()} | ${findEmptyScanRuleBuilder}
${'security scan'} | ${SECURITY_SCAN_RULE} | ${findSecurityScanRule}
${'license scan'} | ${LICENSE_SCANNING_RULE} | ${findLicenseScanRule}
${'any merge request'} | ${ANY_MERGE_REQUEST_RULE} | ${findAnyMergeRequestRule}
`(
'emits the remove event when removing the $ruleType rule',
async ({ findComponent, rule }) => {
Loading
Loading
Loading
Loading
@@ -41663,9 +41663,18 @@ msgstr ""
msgid "ScanResultPolicy|When %{scanType} in an open merge request targeting %{branches} %{branchExceptions} and the licenses match all of the following criteria:"
msgstr ""
 
msgid "ScanResultPolicy|When %{scanType} in an open that targets %{branches} with %{commitType}"
msgstr ""
msgid "ScanResultPolicy|When %{scanners} find scanner specified conditions in an open merge request targeting the %{branches} %{branchExceptions} and match %{boldDescription} of the following criteria"
msgstr ""
 
msgid "ScanResultPolicy|any commits"
msgstr ""
msgid "ScanResultPolicy|any unsigned commits"
msgstr ""
msgid "ScanResultPolicy|license status"
msgstr ""
 
Loading
Loading
@@ -42309,6 +42318,9 @@ msgstr ""
msgid "SecurityOrchestration|And scans to be performed:"
msgstr ""
 
msgid "SecurityOrchestration|Any merge request"
msgstr ""
msgid "SecurityOrchestration|Are you sure you want to delete this policy? This action cannot be undone."
msgstr ""
 
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment