Commit 4050e772 authored by Mark Florian's avatar Mark Florian

Merge branch 'djadmin-exclude-url-schema-update' into 'master'

Update schema for excluded urls in DAST site profile form

See merge request gitlab-org/gitlab!56450
parents 1cfc8982 8feb7756
...@@ -17,6 +17,7 @@ import { ...@@ -17,6 +17,7 @@ import {
SCAN_TYPE_LABEL, SCAN_TYPE_LABEL,
SCAN_TYPE, SCAN_TYPE,
} from 'ee/security_configuration/dast_scanner_profiles/constants'; } from 'ee/security_configuration/dast_scanner_profiles/constants';
import { EXCLUDED_URLS_SEPARATOR } from 'ee/security_configuration/dast_site_profiles_form/constants';
import { DAST_SITE_VALIDATION_STATUS } from 'ee/security_configuration/dast_site_validation/constants'; import { DAST_SITE_VALIDATION_STATUS } from 'ee/security_configuration/dast_site_validation/constants';
import { initFormField } from 'ee/security_configuration/utils'; import { initFormField } from 'ee/security_configuration/utils';
import { convertToGraphQLId } from '~/graphql_shared/utils'; import { convertToGraphQLId } from '~/graphql_shared/utils';
...@@ -319,6 +320,7 @@ export default { ...@@ -319,6 +320,7 @@ export default {
}, },
}, },
ON_DEMAND_SCANS_STORAGE_KEY, ON_DEMAND_SCANS_STORAGE_KEY,
EXCLUDED_URLS_SEPARATOR,
}; };
</script> </script>
...@@ -512,7 +514,7 @@ export default { ...@@ -512,7 +514,7 @@ export default {
<div class="row"> <div class="row">
<profile-selector-summary-cell <profile-selector-summary-cell
:label="s__('DastProfiles|Excluded URLs')" :label="s__('DastProfiles|Excluded URLs')"
:value="selectedSiteProfile.excludedUrls" :value="selectedSiteProfile.excludedUrls.join($options.EXCLUDED_URLS_SEPARATOR)"
/> />
<profile-selector-summary-cell <profile-selector-summary-cell
:label="s__('DastProfiles|Request headers')" :label="s__('DastProfiles|Request headers')"
......
...@@ -18,13 +18,15 @@ import { __, s__, n__, sprintf } from '~/locale'; ...@@ -18,13 +18,15 @@ import { __, s__, n__, sprintf } from '~/locale';
import validation from '~/vue_shared/directives/validation'; import validation from '~/vue_shared/directives/validation';
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import tooltipIcon from '../../dast_scanner_profiles/components/tooltip_icon.vue'; import tooltipIcon from '../../dast_scanner_profiles/components/tooltip_icon.vue';
import {
MAX_CHAR_LIMIT_EXCLUDED_URLS,
MAX_CHAR_LIMIT_REQUEST_HEADERS,
EXCLUDED_URLS_SEPARATOR,
} from '../constants';
import dastSiteProfileCreateMutation from '../graphql/dast_site_profile_create.mutation.graphql'; import dastSiteProfileCreateMutation from '../graphql/dast_site_profile_create.mutation.graphql';
import dastSiteProfileUpdateMutation from '../graphql/dast_site_profile_update.mutation.graphql'; import dastSiteProfileUpdateMutation from '../graphql/dast_site_profile_update.mutation.graphql';
import DastSiteAuthSection from './dast_site_auth_section.vue'; import DastSiteAuthSection from './dast_site_auth_section.vue';
const MAX_CHAR_LIMIT_EXCLUDED_URLS = 2048;
const MAX_CHAR_LIMIT_REQUEST_HEADERS = 2048;
export default { export default {
name: 'DastSiteProfileForm', name: 'DastSiteProfileForm',
components: { components: {
...@@ -63,7 +65,7 @@ export default { ...@@ -63,7 +65,7 @@ export default {
}, },
}, },
data() { data() {
const { name = '', targetUrl = '', excludedUrls = '', requestHeaders = '', auth = {} } = const { name = '', targetUrl = '', excludedUrls = [], requestHeaders = '', auth = {} } =
this.siteProfile || {}; this.siteProfile || {};
const form = { const form = {
...@@ -72,7 +74,11 @@ export default { ...@@ -72,7 +74,11 @@ export default {
fields: { fields: {
profileName: initFormField({ value: name }), profileName: initFormField({ value: name }),
targetUrl: initFormField({ value: targetUrl }), targetUrl: initFormField({ value: targetUrl }),
excludedUrls: initFormField({ value: excludedUrls, required: false, skipValidation: true }), excludedUrls: initFormField({
value: excludedUrls.join(EXCLUDED_URLS_SEPARATOR),
required: false,
skipValidation: true,
}),
requestHeaders: initFormField({ requestHeaders: initFormField({
value: requestHeaders, value: requestHeaders,
required: false, required: false,
...@@ -143,6 +149,11 @@ export default { ...@@ -143,6 +149,11 @@ export default {
isPolicyProfile() { isPolicyProfile() {
return Boolean(this.siteProfile?.referencedInSecurityPolicies?.length); return Boolean(this.siteProfile?.referencedInSecurityPolicies?.length);
}, },
parsedExcludedUrls() {
return this.form.fields.excludedUrls.value
.split(EXCLUDED_URLS_SEPARATOR)
.map((url) => url.trim());
},
}, },
async mounted() { async mounted() {
if (this.isEdit) { if (this.isEdit) {
...@@ -165,13 +176,20 @@ export default { ...@@ -165,13 +176,20 @@ export default {
this.hideErrors(); this.hideErrors();
const { errorMessage } = this.i18n; const { errorMessage } = this.i18n;
const { profileName, targetUrl, ...additionalFields } = serializeFormObject(this.form.fields);
const variables = { const variables = {
input: { input: {
fullPath: this.fullPath, fullPath: this.fullPath,
...(this.isEdit ? { id: this.siteProfile.id } : {}), ...(this.isEdit ? { id: this.siteProfile.id } : {}),
...serializeFormObject(this.form.fields), profileName,
targetUrl,
...(this.glFeatures.securityDastSiteProfilesAdditionalFields && { ...(this.glFeatures.securityDastSiteProfilesAdditionalFields && {
...additionalFields,
auth: serializeFormObject(this.authSection.fields), auth: serializeFormObject(this.authSection.fields),
...(additionalFields.excludedUrls && {
excludedUrls: this.parsedExcludedUrls,
}),
}), }),
}, },
}; };
......
export const MAX_CHAR_LIMIT_EXCLUDED_URLS = 2048;
export const MAX_CHAR_LIMIT_REQUEST_HEADERS = 2048;
export const EXCLUDED_URLS_SEPARATOR = ',';
...@@ -6,6 +6,6 @@ ...@@ -6,6 +6,6 @@
.js-dast-site-profile-form{ data: { full_path: @project.path_with_namespace, .js-dast-site-profile-form{ data: { full_path: @project.path_with_namespace,
profiles_library_path: project_security_configuration_dast_profiles_path(@project, anchor: 'site-profiles'), profiles_library_path: project_security_configuration_dast_profiles_path(@project, anchor: 'site-profiles'),
site_profile: { id: @site_profile.to_global_id.to_s, name: @site_profile.name, target_url: @site_profile.dast_site.url, site_profile: { id: @site_profile.to_global_id.to_s, name: @site_profile.name, target_url: @site_profile.dast_site.url,
excluded_urls: 'https://example.com/logout', request_headers: 'new-header', excluded_urls: ['https://example.com/logout', 'https://example.com/send_mail'], request_headers: 'new-header',
auth: { enabled: true, url: 'https://example.com', username: 'admin', usernameField: 'username', passwordField: 'password' }, referenced_in_security_policies: @site_profile.referenced_in_security_policies}.to_json, auth: { enabled: true, url: 'https://example.com', username: 'admin', usernameField: 'username', passwordField: 'password' }, referenced_in_security_policies: @site_profile.referenced_in_security_policies}.to_json,
on_demand_scans_path: new_project_on_demand_scan_path(@project) } } on_demand_scans_path: new_project_on_demand_scan_path(@project) } }
...@@ -536,7 +536,7 @@ describe('OnDemandScansForm', () => { ...@@ -536,7 +536,7 @@ describe('OnDemandScansForm', () => {
const summary = subject.find(SiteProfileSelector).text(); const summary = subject.find(SiteProfileSelector).text();
expect(summary).toMatch(authEnabledProfile.targetUrl); expect(summary).toMatch(authEnabledProfile.targetUrl);
expect(summary).toMatch(authEnabledProfile.excludedUrls); expect(summary).toMatch(authEnabledProfile.excludedUrls.join(','));
expect(summary).toMatch(authEnabledProfile.requestHeaders); expect(summary).toMatch(authEnabledProfile.requestHeaders);
expect(summary).toMatch(authEnabledProfile.auth.url); expect(summary).toMatch(authEnabledProfile.auth.url);
expect(summary).toMatch(authEnabledProfile.auth.username); expect(summary).toMatch(authEnabledProfile.auth.username);
......
...@@ -51,7 +51,7 @@ export const siteProfiles = [ ...@@ -51,7 +51,7 @@ export const siteProfiles = [
username: 'admin', username: 'admin',
password: 'password', password: 'password',
}, },
excludedUrls: 'https://foo.com/logout,https://foo.com/send_mail', excludedUrls: ['https://foo.com/logout', 'https://foo.com/send_mail'],
requestHeaders: 'log-identifier: dast-active-scan', requestHeaders: 'log-identifier: dast-active-scan',
referencedInSecurityPolicies: [], referencedInSecurityPolicies: [],
}, },
...@@ -65,7 +65,7 @@ export const siteProfiles = [ ...@@ -65,7 +65,7 @@ export const siteProfiles = [
auth: { auth: {
enabled: false, enabled: false,
}, },
excludedUrls: 'https://bar.com/logout', excludedUrls: ['https://bar.com/logout'],
requestHeaders: 'auth: gitlab-dast', requestHeaders: 'auth: gitlab-dast',
referencedInSecurityPolicies: [], referencedInSecurityPolicies: [],
}, },
...@@ -81,6 +81,6 @@ export const policySiteProfile = { ...@@ -81,6 +81,6 @@ export const policySiteProfile = {
auth: { auth: {
enabled: false, enabled: false,
}, },
excludedUrls: 'https://bar.com/logout', excludedUrls: ['https://bar.com/logout'],
referencedInSecurityPolicies: ['some_policy'], referencedInSecurityPolicies: ['some_policy'],
}; };
...@@ -24,7 +24,7 @@ const profilesLibraryPath = `${TEST_HOST}/${fullPath}/-/security/configuration/d ...@@ -24,7 +24,7 @@ const profilesLibraryPath = `${TEST_HOST}/${fullPath}/-/security/configuration/d
const onDemandScansPath = `${TEST_HOST}/${fullPath}/-/on_demand_scans`; const onDemandScansPath = `${TEST_HOST}/${fullPath}/-/on_demand_scans`;
const profileName = 'My DAST site profile'; const profileName = 'My DAST site profile';
const targetUrl = 'http://example.com'; const targetUrl = 'http://example.com';
const excludedUrls = 'http://example.com/logout'; const excludedUrls = 'https://foo.com/logout, https://foo.com/send_mail';
const requestHeaders = 'my-new-header=something'; const requestHeaders = 'my-new-header=something';
const defaultProps = { const defaultProps = {
...@@ -224,10 +224,10 @@ describe('DastSiteProfileForm', () => { ...@@ -224,10 +224,10 @@ describe('DastSiteProfileForm', () => {
input: { input: {
profileName, profileName,
targetUrl, targetUrl,
excludedUrls,
requestHeaders, requestHeaders,
fullPath, fullPath,
auth: siteProfileOne.auth, auth: siteProfileOne.auth,
excludedUrls: siteProfileOne.excludedUrls,
...mutationVars, ...mutationVars,
}, },
}); });
...@@ -319,21 +319,55 @@ describe('DastSiteProfileForm', () => { ...@@ -319,21 +319,55 @@ describe('DastSiteProfileForm', () => {
}); });
describe('when feature flag is off', () => { describe('when feature flag is off', () => {
beforeEach(() => { const mountOpts = {
createFullComponent({
provide: { provide: {
glFeatures: { glFeatures: {
securityDastSiteProfilesAdditionalFields: false, securityDastSiteProfilesAdditionalFields: false,
}, },
}, },
}); };
});
const fillAndSubmitForm = async () => {
await setFieldValue(findProfileNameInput(), profileName);
await setFieldValue(findTargetUrlInput(), targetUrl);
submitForm();
};
it('should not render additional fields', () => { it('should not render additional fields', () => {
createFullComponent(mountOpts);
expect(findAuthSection().exists()).toBe(false); expect(findAuthSection().exists()).toBe(false);
expect(findExcludedUrlsInput().exists()).toBe(false); expect(findExcludedUrlsInput().exists()).toBe(false);
expect(findRequestHeadersInput().exists()).toBe(false); expect(findRequestHeadersInput().exists()).toBe(false);
}); });
describe.each`
title | siteProfile | mutationVars | mutationKind
${'New site profile'} | ${null} | ${{}} | ${'dastSiteProfileCreate'}
${'Edit site profile'} | ${siteProfileOne} | ${{ id: siteProfileOne.id }} | ${'dastSiteProfileUpdate'}
`('$title', ({ siteProfile, mutationVars, mutationKind }) => {
beforeEach(() => {
createFullComponent({
propsData: {
siteProfile,
},
...mountOpts,
});
fillAndSubmitForm();
});
it('form submission triggers correct GraphQL mutation', async () => {
await fillAndSubmitForm();
expect(requestHandlers[mutationKind]).toHaveBeenCalledWith({
input: {
profileName,
targetUrl,
fullPath,
...mutationVars,
},
});
});
});
}); });
describe('when profile does not come from a policy', () => { describe('when profile does not come from a policy', () => {
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment