Commit 503533a3 authored by Kushal Pandya's avatar Kushal Pandya

Merge branch 'djadmin-dast-settings-generate-snippet' into 'master'

Add code snippet generation for DAST Configuration

See merge request gitlab-org/gitlab!63530
parents b8375a93 53d17209
...@@ -2,10 +2,16 @@ import { helpPagePath } from '~/helpers/help_page_helper'; ...@@ -2,10 +2,16 @@ import { helpPagePath } from '~/helpers/help_page_helper';
export const CODE_SNIPPET_SOURCE_URL_PARAM = 'code_snippet_copied_from'; export const CODE_SNIPPET_SOURCE_URL_PARAM = 'code_snippet_copied_from';
export const CODE_SNIPPET_SOURCE_API_FUZZING = 'api_fuzzing'; export const CODE_SNIPPET_SOURCE_API_FUZZING = 'api_fuzzing';
export const CODE_SNIPPET_SOURCES = [CODE_SNIPPET_SOURCE_API_FUZZING]; export const CODE_SNIPPET_SOURCE_DAST = 'dast';
export const CODE_SNIPPET_SOURCES = [CODE_SNIPPET_SOURCE_API_FUZZING, CODE_SNIPPET_SOURCE_DAST];
export const CODE_SNIPPET_SOURCE_SETTINGS = { export const CODE_SNIPPET_SOURCE_SETTINGS = {
[CODE_SNIPPET_SOURCE_API_FUZZING]: { [CODE_SNIPPET_SOURCE_API_FUZZING]: {
datasetKey: 'apiFuzzingConfigurationPath', datasetKey: 'apiFuzzingConfigurationPath',
docsPath: helpPagePath('user/application_security/api_fuzzing/index'), docsPath: helpPagePath('user/application_security/api_fuzzing/index'),
}, },
[CODE_SNIPPET_SOURCE_DAST]: {
datasetKey: 'dastConfigurationPath',
docsPath: helpPagePath('user/application_security/dast/index'),
},
}; };
<script> <script>
import { GlModal } from '@gitlab/ui'; import { GlModal, GlSprintf, GlLink } from '@gitlab/ui';
import Clipboard from 'clipboard'; import Clipboard from 'clipboard';
import { getBaseURL, setUrlParams, redirectTo } from '~/lib/utils/url_utility'; import { getBaseURL, setUrlParams, redirectTo } from '~/lib/utils/url_utility';
import { sprintf, s__ } from '~/locale'; import { sprintf, s__, __ } from '~/locale';
import { CODE_SNIPPET_SOURCE_URL_PARAM } from '~/pipeline_editor/components/code_snippet_alert/constants'; import { CODE_SNIPPET_SOURCE_URL_PARAM } from '~/pipeline_editor/components/code_snippet_alert/constants';
import { CONFIGURATION_SNIPPET_MODAL_ID } from './constants'; import { CONFIGURATION_SNIPPET_MODAL_ID } from './constants';
...@@ -10,6 +10,16 @@ export default { ...@@ -10,6 +10,16 @@ export default {
CONFIGURATION_SNIPPET_MODAL_ID, CONFIGURATION_SNIPPET_MODAL_ID,
components: { components: {
GlModal, GlModal,
GlSprintf,
GlLink,
},
i18n: {
helpText: s__(
'This code snippet contains everything reflected in the configuration form. Copy and paste it into %{linkStart}.gitlab-ci.yml%{linkEnd} file and save your changes. Future %{scanType} scans will use these settings.',
),
primaryText: s__('SecurityConfiguration|Copy code and open .gitlab-ci.yml file'),
secondaryText: s__('SecurityConfiguration|Copy code only'),
cancelText: __('Cancel'),
}, },
props: { props: {
ciYamlEditUrl: { ciYamlEditUrl: {
...@@ -70,15 +80,15 @@ export default { ...@@ -70,15 +80,15 @@ export default {
<gl-modal <gl-modal
ref="modal" ref="modal"
:action-primary="{ :action-primary="{
text: s__('SecurityConfiguration|Copy code and open .gitlab-ci.yml file'), text: $options.i18n.primaryText,
attributes: [{ variant: 'confirm' }, { id: 'copy-yaml-snippet-and-edit-button' }], attributes: [{ variant: 'confirm' }, { id: 'copy-yaml-snippet-and-edit-button' }],
}" }"
:action-secondary="{ :action-secondary="{
text: s__('SecurityConfiguration|Copy code only'), text: $options.i18n.secondaryText,
attributes: [{ variant: 'default' }, { id: 'copy-yaml-snippet-button' }], attributes: [{ variant: 'default' }, { id: 'copy-yaml-snippet-button' }],
}" }"
:action-cancel="{ :action-cancel="{
text: __('Cancel'), text: $options.i18n.cancelText,
}" }"
:modal-id="$options.CONFIGURATION_SNIPPET_MODAL_ID" :modal-id="$options.CONFIGURATION_SNIPPET_MODAL_ID"
:title="modalTitle" :title="modalTitle"
...@@ -86,6 +96,19 @@ export default { ...@@ -86,6 +96,19 @@ export default {
@primary="copySnippet" @primary="copySnippet"
@secondary="copySnippet(false)" @secondary="copySnippet(false)"
> >
<p class="gl-text-gray-500" data-testid="configuration-modal-help-text">
<gl-sprintf :message="$options.i18n.helpText">
<template #link="{ content }">
<gl-link :href="ciYamlEditUrl" target="_blank">
{{ content }}
</gl-link>
</template>
<template #scanType>
{{ scanType }}
</template>
</gl-sprintf>
</p>
<pre><code data-testid="configuration-modal-yaml-snippet" v-text="yaml"></code></pre> <pre><code data-testid="configuration-modal-yaml-snippet" v-text="yaml"></code></pre>
</gl-modal> </gl-modal>
</template> </template>
<script> <script>
import { GlLink, GlSprintf } from '@gitlab/ui'; import { GlLink, GlSprintf, GlButton, GlForm } from '@gitlab/ui';
import { s__ } from '~/locale'; import ConfigurationSnippetModal from 'ee/security_configuration/components/configuration_snippet_modal.vue';
import { CONFIGURATION_SNIPPET_MODAL_ID } from 'ee/security_configuration/components/constants';
import { s__, __ } from '~/locale';
import { CODE_SNIPPET_SOURCE_DAST } from '~/pipeline_editor/components/code_snippet_alert/constants';
import { DAST_HELP_PATH } from '~/security_configuration/components/constants'; import { DAST_HELP_PATH } from '~/security_configuration/components/constants';
import {
DAST_YAML_CONFIGURATION_TEMPLATE as template,
DAST_SCANNER_PROFILE_PLACEHOLDER,
DAST_SITE_PROFILE_PLACEHOLDER,
} from '../constants';
export default { export default {
DAST_HELP_PATH,
CONFIGURATION_SNIPPET_MODAL_ID,
CODE_SNIPPET_SOURCE_DAST,
components: { components: {
GlLink, GlLink,
GlSprintf, GlSprintf,
GlButton,
GlForm,
ConfigurationSnippetModal,
}, },
inject: ['gitlabCiYamlEditPath', 'securityConfigurationPath'],
i18n: { i18n: {
helpText: s__(` helpText: s__(`
DastConfig|Customize DAST settings to suit your requirements. Configuration changes made here override those provided by GitLab and are excluded from updates. For details of more advanced configuration options, see the %{docsLinkStart}GitLab DAST documentation%{docsLinkEnd}.`), DastConfig|Customize DAST settings to suit your requirements. Configuration changes made here override those provided by GitLab and are excluded from updates. For details of more advanced configuration options, see the %{docsLinkStart}GitLab DAST documentation%{docsLinkEnd}.`),
submitButtonText: s__('DastConfig|Generate code snippet'),
cancelText: __('Cancel'),
},
data() {
return {
// eslint-disable-next-line @gitlab/require-i18n-strings
selectedScannerProfileName: 'My DAST Scanner Profile',
// eslint-disable-next-line @gitlab/require-i18n-strings
selectedSiteProfileName: 'My DAST Site Profile',
isLoading: false,
};
},
computed: {
configurationYaml() {
return template
.replace(DAST_SITE_PROFILE_PLACEHOLDER, this.selectedSiteProfileName)
.replace(DAST_SCANNER_PROFILE_PLACEHOLDER, this.selectedScannerProfileName);
},
isSubmitDisabled() {
return !this.selectedScannerProfileName || !this.selectedSiteProfileName;
},
},
methods: {
onSubmit() {
this.$refs[CONFIGURATION_SNIPPET_MODAL_ID].show();
},
}, },
DAST_HELP_PATH,
}; };
</script> </script>
<template> <template>
<section class="gl-mt-5"> <gl-form @submit.prevent="onSubmit">
<p> <section class="gl-mt-5">
<gl-sprintf :message="$options.i18n.helpText"> <p>
<template #docsLink="{ content }"> <gl-sprintf :message="$options.i18n.helpText">
<gl-link :href="$options.DAST_HELP_PATH" target="_blank">{{ content }}</gl-link> <template #docsLink="{ content }">
</template> <gl-link :href="$options.DAST_HELP_PATH" target="_blank">{{ content }}</gl-link>
</gl-sprintf> </template>
</p> </gl-sprintf>
</section> </p>
</section>
<gl-button
:disabled="isSubmitDisabled"
:loading="isLoading"
type="submit"
variant="confirm"
class="js-no-auto-disable"
data-testid="dast-configuration-submit-button"
>{{ $options.i18n.submitButtonText }}</gl-button
>
<gl-button
:disabled="isLoading"
:href="securityConfigurationPath"
data-testid="dast-configuration-cancel-button"
>{{ $options.i18n.cancelText }}</gl-button
>
<configuration-snippet-modal
:ref="$options.CONFIGURATION_SNIPPET_MODAL_ID"
:ci-yaml-edit-url="gitlabCiYamlEditPath"
:yaml="configurationYaml"
:redirect-param="$options.CODE_SNIPPET_SOURCE_DAST"
scan-type="DAST"
/>
</gl-form>
</template> </template>
export const DAST_SCANNER_PROFILE_PLACEHOLDER = '#DAST_SCANNER_PROFILE_NAME';
export const DAST_SITE_PROFILE_PLACEHOLDER = '#DAST_SITE_PROFILE_NAME';
export const DAST_YAML_CONFIGURATION_TEMPLATE = `# Add \`dast\` to your \`stages:\` configuration
stages:
- dast
# Include the DAST template
include:
- template: DAST.gitlab-ci.yml
# Your selected site and scanner profiles:
dast:
stage: dast
dast_configuration:
site_profile: "${DAST_SITE_PROFILE_PLACEHOLDER}"
scanner_profile: "${DAST_SCANNER_PROFILE_PLACEHOLDER}"
`;
...@@ -10,7 +10,8 @@ module EE ...@@ -10,7 +10,8 @@ module EE
return super unless project.licensed_feature_available?(:api_fuzzing) return super unless project.licensed_feature_available?(:api_fuzzing)
super.merge( super.merge(
"api-fuzzing-configuration-path" => project_security_configuration_api_fuzzing_path(project) "api-fuzzing-configuration-path" => project_security_configuration_api_fuzzing_path(project),
"dast-configuration-path" => project_security_configuration_dast_path(project)
) )
end end
end end
......
import { GlModal } from '@gitlab/ui'; import { GlModal, GlSprintf } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils'; import { shallowMount } from '@vue/test-utils';
import Clipboard from 'clipboard'; import Clipboard from 'clipboard';
import { merge } from 'lodash'; import { merge } from 'lodash';
...@@ -31,6 +31,7 @@ describe('EE - SecurityConfigurationSnippetModal', () => { ...@@ -31,6 +31,7 @@ describe('EE - SecurityConfigurationSnippetModal', () => {
const findModal = () => wrapper.find(GlModal); const findModal = () => wrapper.find(GlModal);
const findYamlSnippet = () => wrapper.findByTestId('configuration-modal-yaml-snippet'); const findYamlSnippet = () => wrapper.findByTestId('configuration-modal-yaml-snippet');
const helpText = () => wrapper.findByTestId('configuration-modal-help-text');
const createWrapper = (options) => { const createWrapper = (options) => {
wrapper = extendedWrapper( wrapper = extendedWrapper(
...@@ -48,6 +49,9 @@ describe('EE - SecurityConfigurationSnippetModal', () => { ...@@ -48,6 +49,9 @@ describe('EE - SecurityConfigurationSnippetModal', () => {
static: true, static: true,
visible: true, visible: true,
}, },
stubs: {
GlSprintf,
},
}, },
options, options,
), ),
...@@ -67,6 +71,12 @@ describe('EE - SecurityConfigurationSnippetModal', () => { ...@@ -67,6 +71,12 @@ describe('EE - SecurityConfigurationSnippetModal', () => {
expect(findYamlSnippet().text()).toBe(configurationYaml); expect(findYamlSnippet().text()).toBe(configurationYaml);
}); });
it('renders help text correctly', () => {
expect(helpText().exists()).toBe(true);
expect(helpText().text()).not.toBe('');
expect(helpText().html()).toContain(gitlabCiYamlEditPath);
});
it('on primary event, text is copied to the clipbard and user is redirected to CI editor', async () => { it('on primary event, text is copied to the clipbard and user is redirected to CI editor', async () => {
findModal().vm.$emit('primary'); findModal().vm.$emit('primary');
......
import { GlSprintf } from '@gitlab/ui'; import { GlSprintf } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils'; import { mount } from '@vue/test-utils';
import ConfigurationSnippetModal from 'ee/security_configuration/components/configuration_snippet_modal.vue';
import { CONFIGURATION_SNIPPET_MODAL_ID } from 'ee/security_configuration/components/constants';
import ConfigurationForm from 'ee/security_configuration/dast/components/configuration_form.vue'; import ConfigurationForm from 'ee/security_configuration/dast/components/configuration_form.vue';
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import { CODE_SNIPPET_SOURCE_DAST } from '~/pipeline_editor/components/code_snippet_alert/constants';
import { DAST_HELP_PATH } from '~/security_configuration/components/constants'; import { DAST_HELP_PATH } from '~/security_configuration/components/constants';
const securityConfigurationPath = '/security/configuration';
const gitlabCiYamlEditPath = '/ci/editor';
const template = `# Add \`dast\` to your \`stages:\` configuration
stages:
- dast
# Include the DAST template
include:
- template: DAST.gitlab-ci.yml
# Your selected site and scanner profiles:
dast:
stage: dast
dast_configuration:
site_profile: "My DAST Site Profile"
scanner_profile: "My DAST Scanner Profile"
`;
describe('EE - DAST Configuration Form', () => { describe('EE - DAST Configuration Form', () => {
let wrapper; let wrapper;
const findCancelButton = () => wrapper.findByTestId('dast-configuration-cancel-button');
const findConfigurationSnippetModal = () => wrapper.findComponent(ConfigurationSnippetModal);
const createComponent = () => { const createComponent = () => {
wrapper = shallowMount(ConfigurationForm, { wrapper = extendedWrapper(
stubs: { mount(ConfigurationForm, {
GlSprintf, provide: {
}, securityConfigurationPath,
}); gitlabCiYamlEditPath,
},
stubs: {
GlSprintf,
},
}),
);
}; };
beforeEach(() => { beforeEach(() => {
...@@ -29,4 +60,25 @@ describe('EE - DAST Configuration Form', () => { ...@@ -29,4 +60,25 @@ describe('EE - DAST Configuration Form', () => {
it('includes a link to DAST Configuration documentation', () => { it('includes a link to DAST Configuration documentation', () => {
expect(wrapper.html()).toContain(DAST_HELP_PATH); expect(wrapper.html()).toContain(DAST_HELP_PATH);
}); });
describe('form', () => {
it('submit button should open the model with correct props', () => {
jest.spyOn(wrapper.vm.$refs[CONFIGURATION_SNIPPET_MODAL_ID], 'show');
wrapper.find('form').trigger('submit');
expect(wrapper.vm.$refs[CONFIGURATION_SNIPPET_MODAL_ID].show).toHaveBeenCalled();
expect(findConfigurationSnippetModal().props()).toEqual({
ciYamlEditUrl: gitlabCiYamlEditPath,
yaml: template,
redirectParam: CODE_SNIPPET_SOURCE_DAST,
scanType: 'DAST',
});
});
it('cancel button points to Security Configuration page', () => {
expect(findCancelButton().attributes('href')).toBe('/security/configuration');
});
});
}); });
...@@ -25,12 +25,14 @@ RSpec.describe EE::Ci::PipelineEditorHelper do ...@@ -25,12 +25,14 @@ RSpec.describe EE::Ci::PipelineEditorHelper do
it 'returns ee specific values' do it 'returns ee specific values' do
expect(pipeline_editor_data.keys).to include('api-fuzzing-configuration-path') expect(pipeline_editor_data.keys).to include('api-fuzzing-configuration-path')
expect(pipeline_editor_data.keys).to include('dast-configuration-path')
end end
end end
context 'without licensed feature' do context 'without licensed feature' do
it 'does not return the API fuzzing path' do it 'does not return the API fuzzing path' do
expect(pipeline_editor_data.keys).not_to include('api-fuzzing-configuration-path') expect(pipeline_editor_data.keys).not_to include('api-fuzzing-configuration-path')
expect(pipeline_editor_data.keys).not_to include('dast-configuration-path')
end end
end end
end end
......
...@@ -10045,6 +10045,9 @@ msgstr "" ...@@ -10045,6 +10045,9 @@ msgstr ""
msgid "DastConfig|DAST Settings" msgid "DastConfig|DAST Settings"
msgstr "" msgstr ""
msgid "DastConfig|Generate code snippet"
msgstr ""
msgid "DastConfig|Scan Configuration" msgid "DastConfig|Scan Configuration"
msgstr "" msgstr ""
...@@ -33126,6 +33129,9 @@ msgstr "" ...@@ -33126,6 +33129,9 @@ msgstr ""
msgid "This chart could not be displayed" msgid "This chart could not be displayed"
msgstr "" msgstr ""
msgid "This code snippet contains everything reflected in the configuration form. Copy and paste it into %{linkStart}.gitlab-ci.yml%{linkEnd} file and save your changes. Future %{scanType} scans will use these settings."
msgstr ""
msgid "This comment changed after you started editing it. Review the %{startTag}updated comment%{endTag} to ensure information is not lost." msgid "This comment changed after you started editing it. Review the %{startTag}updated comment%{endTag} to ensure information is not lost."
msgstr "" msgstr ""
......
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