Skip to content
Snippets Groups Projects
Unverified Commit 4aa72d0f authored by Artur Fedorov's avatar Artur Fedorov Committed by Alexander Turinske
Browse files

This MR adds new rule builder

New rule builder for any merge
request for scan result policy on
group and project level

Changelog: changed
EE: true
parent 73307629
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
@@ -41651,9 +41651,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
@@ -42297,6 +42306,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