Commit 6c8c0c0e authored by Kushal Pandya's avatar Kushal Pandya

Merge branch 'djadmin-edit-new-dast-site-profile' into 'master'

Add ability to edit DAST site profiles with new fields

See merge request gitlab-org/gitlab!51583
parents 8febfb1c 7291ce5a
<script> <script>
import { GlFormGroup, GlFormInput, GlFormCheckbox } from '@gitlab/ui'; import { GlFormGroup, GlFormInput, GlFormCheckbox } from '@gitlab/ui';
import { initFormField } from 'ee/security_configuration/utils'; import { initFormField } from 'ee/security_configuration/utils';
import { __ } from '~/locale';
import validation from '~/vue_shared/directives/validation'; import validation from '~/vue_shared/directives/validation';
export default { export default {
...@@ -13,7 +14,7 @@ export default { ...@@ -13,7 +14,7 @@ export default {
validation: validation(), validation: validation(),
}, },
props: { props: {
fields: { value: {
type: Object, type: Object,
required: false, required: false,
default: () => ({}), default: () => ({}),
...@@ -26,32 +27,41 @@ export default { ...@@ -26,32 +27,41 @@ export default {
}, },
data() { data() {
const { const {
authEnabled, enabled,
authenticationUrl, url,
userName, username,
password, password,
// default to commonly used names for `userName` and `password` fields in authentcation forms // default to commonly used names for `username` and `password` fields in authentcation forms
userNameFormField = 'username', usernameField = 'username',
passwordFormField = 'password', passwordField = 'password',
} = this.fields; } = this.value.fields;
const isEditMode = Object.keys(this.value.fields).length > 0;
return { return {
form: { form: {
state: false, state: false,
fields: { fields: {
authEnabled: initFormField({ value: authEnabled, skipValidation: true }), enabled: initFormField({ value: enabled, skipValidation: true }),
authenticationUrl: initFormField({ value: authenticationUrl }), url: initFormField({ value: url }),
userName: initFormField({ value: userName }), username: initFormField({ value: username }),
password: initFormField({ value: password }), password: isEditMode
userNameFormField: initFormField({ value: userNameFormField }), ? initFormField({ value: password, required: false, skipValidation: true })
passwordFormField: initFormField({ value: passwordFormField }), : initFormField({ value: password }),
usernameField: initFormField({ value: usernameField }),
passwordField: initFormField({ value: passwordField }),
}, },
}, },
isEditMode,
isSensitiveFieldRequired: !isEditMode,
}; };
}, },
computed: { computed: {
showValidationOrInEditMode() { showValidationOrInEditMode() {
return this.showValidation || Object.keys(this.fields).length > 0; return this.showValidation || this.isEditMode;
},
sensitiveFieldPlaceholder() {
return this.isEditMode ? __('[Unchanged]') : '';
}, },
}, },
watch: { watch: {
...@@ -68,41 +78,41 @@ export default { ...@@ -68,41 +78,41 @@ export default {
<template> <template>
<section> <section>
<gl-form-group :label="s__('DastProfiles|Authentication')"> <gl-form-group :label="s__('DastProfiles|Authentication')">
<gl-form-checkbox v-model="form.fields.authEnabled.value">{{ <gl-form-checkbox v-model="form.fields.enabled.value" data-testid="auth-enable-checkbox">{{
s__('DastProfiles|Enable Authentication') s__('DastProfiles|Enable Authentication')
}}</gl-form-checkbox> }}</gl-form-checkbox>
</gl-form-group> </gl-form-group>
<div v-if="form.fields.authEnabled.value" data-testid="auth-form"> <div v-if="form.fields.enabled.value" data-testid="auth-form">
<div class="row"> <div class="row">
<gl-form-group <gl-form-group
:label="s__('DastProfiles|Authentication URL')" :label="s__('DastProfiles|Authentication URL')"
:invalid-feedback="form.fields.authenticationUrl.feedback" :invalid-feedback="form.fields.url.feedback"
class="col-md-6" class="col-md-6"
> >
<gl-form-input <gl-form-input
v-model="form.fields.authenticationUrl.value" v-model="form.fields.url.value"
v-validation:[showValidationOrInEditMode] v-validation:[showValidationOrInEditMode]
name="authenticationUrl" name="url"
type="url" type="url"
required required
:state="form.fields.authenticationUrl.state" :state="form.fields.url.state"
/> />
</gl-form-group> </gl-form-group>
</div> </div>
<div class="row"> <div class="row">
<gl-form-group <gl-form-group
:label="s__('DastProfiles|Username')" :label="s__('DastProfiles|Username')"
:invalid-feedback="form.fields.userName.feedback" :invalid-feedback="form.fields.username.feedback"
class="col-md-6" class="col-md-6"
> >
<gl-form-input <gl-form-input
v-model="form.fields.userName.value" v-model="form.fields.username.value"
v-validation:[showValidationOrInEditMode] v-validation:[showValidationOrInEditMode]
autocomplete="off" autocomplete="off"
name="userName" name="username"
type="text" type="text"
required required
:state="form.fields.userName.state" :state="form.fields.username.state"
/> />
</gl-form-group> </gl-form-group>
<gl-form-group <gl-form-group
...@@ -116,7 +126,8 @@ export default { ...@@ -116,7 +126,8 @@ export default {
autocomplete="off" autocomplete="off"
name="password" name="password"
type="password" type="password"
required :placeholder="sensitiveFieldPlaceholder"
:required="isSensitiveFieldRequired"
:state="form.fields.password.state" :state="form.fields.password.state"
/> />
</gl-form-group> </gl-form-group>
...@@ -124,30 +135,30 @@ export default { ...@@ -124,30 +135,30 @@ export default {
<div class="row"> <div class="row">
<gl-form-group <gl-form-group
:label="s__('DastProfiles|Username form field')" :label="s__('DastProfiles|Username form field')"
:invalid-feedback="form.fields.userNameFormField.feedback" :invalid-feedback="form.fields.usernameField.feedback"
class="col-md-6" class="col-md-6"
> >
<gl-form-input <gl-form-input
v-model="form.fields.userNameFormField.value" v-model="form.fields.usernameField.value"
v-validation:[showValidationOrInEditMode] v-validation:[showValidationOrInEditMode]
name="userNameFormField" name="usernameField"
type="text" type="text"
required required
:state="form.fields.userNameFormField.state" :state="form.fields.usernameField.state"
/> />
</gl-form-group> </gl-form-group>
<gl-form-group <gl-form-group
:label="s__('DastProfiles|Password form field')" :label="s__('DastProfiles|Password form field')"
:invalid-feedback="form.fields.passwordFormField.feedback" :invalid-feedback="form.fields.passwordField.feedback"
class="col-md-6" class="col-md-6"
> >
<gl-form-input <gl-form-input
v-model="form.fields.passwordFormField.value" v-model="form.fields.passwordField.value"
v-validation:[showValidationOrInEditMode] v-validation:[showValidationOrInEditMode]
name="passwordFormField" name="passwordField"
type="text" type="text"
required required
:state="form.fields.passwordFormField.state" :state="form.fields.passwordField.state"
/> />
</gl-form-group> </gl-form-group>
</div> </div>
......
...@@ -56,7 +56,7 @@ export default { ...@@ -56,7 +56,7 @@ export default {
}, },
}, },
data() { data() {
const { name = '', targetUrl = '', excludedUrls = '', requestHeaders = '' } = const { name = '', targetUrl = '', excludedUrls = '', requestHeaders = '', auth = {} } =
this.siteProfile || {}; this.siteProfile || {};
const form = { const form = {
...@@ -76,7 +76,7 @@ export default { ...@@ -76,7 +76,7 @@ export default {
return { return {
form, form,
authSection: {}, authSection: { fields: auth },
initialFormValues: serializeFormObject(form.fields), initialFormValues: serializeFormObject(form.fields),
isLoading: false, isLoading: false,
hasAlert: false, hasAlert: false,
...@@ -126,7 +126,7 @@ export default { ...@@ -126,7 +126,7 @@ export default {
onSubmit() { onSubmit() {
const isAuthEnabled = const isAuthEnabled =
this.glFeatures.securityDastSiteProfilesAdditionalFields && this.glFeatures.securityDastSiteProfilesAdditionalFields &&
this.authSection.fields.authEnabled.value; this.authSection.fields.enabled.value;
this.form.showValidation = true; this.form.showValidation = true;
...@@ -143,7 +143,7 @@ export default { ...@@ -143,7 +143,7 @@ export default {
fullPath: this.fullPath, fullPath: this.fullPath,
...(this.isEdit ? { id: this.siteProfile.id } : {}), ...(this.isEdit ? { id: this.siteProfile.id } : {}),
...serializeFormObject(this.form.fields), ...serializeFormObject(this.form.fields),
...(isAuthEnabled ? serializeFormObject(this.authSection.fields) : {}), auth: isAuthEnabled ? serializeFormObject(this.authSection.fields) : {},
}, },
}; };
......
...@@ -5,5 +5,7 @@ ...@@ -5,5 +5,7 @@
.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 }.to_json, 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',
auth: { enabled: true, url: 'https://example.com', username: 'admin', usernameField: 'username', passwordField: 'password' }}.to_json,
on_demand_scans_path: Feature.enabled?(:dast_saved_scans, @project, default_enabled: :yaml) ? new_project_on_demand_scan_path(@project) : project_on_demand_scans_path(@project) } } on_demand_scans_path: Feature.enabled?(:dast_saved_scans, @project, default_enabled: :yaml) ? new_project_on_demand_scan_path(@project) : project_on_demand_scans_path(@project) } }
...@@ -528,9 +528,9 @@ describe('OnDemandScansForm', () => { ...@@ -528,9 +528,9 @@ describe('OnDemandScansForm', () => {
describe('site profile summary', () => { describe('site profile summary', () => {
const [authEnabledProfile] = siteProfiles; const [authEnabledProfile] = siteProfiles;
const selectSiteProfile = (profile) => { const selectSiteProfile = async (profile) => {
subject.find(SiteProfileSelector).vm.$emit('input', profile.id); subject.find(SiteProfileSelector).vm.$emit('input', profile.id);
return subject.vm.$nextTick(); await subject.vm.$nextTick();
}; };
beforeEach(() => { beforeEach(() => {
......
...@@ -35,6 +35,7 @@ export const siteProfiles = [ ...@@ -35,6 +35,7 @@ export const siteProfiles = [
usernameField: 'username', usernameField: 'username',
passwordField: 'password', passwordField: 'password',
username: 'admin', username: 'admin',
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',
......
...@@ -6,11 +6,11 @@ import { extendedWrapper } from 'helpers/vue_test_utils_helper'; ...@@ -6,11 +6,11 @@ import { extendedWrapper } from 'helpers/vue_test_utils_helper';
describe('DastSiteAuthSection', () => { describe('DastSiteAuthSection', () => {
let wrapper; let wrapper;
const createComponent = ({ fields } = {}) => { const createComponent = ({ fields = {} } = {}) => {
wrapper = extendedWrapper( wrapper = extendedWrapper(
mount(DastSiteAuthSection, { mount(DastSiteAuthSection, {
propsData: { propsData: {
fields, value: { fields },
}, },
}), }),
); );
...@@ -40,9 +40,9 @@ describe('DastSiteAuthSection', () => { ...@@ -40,9 +40,9 @@ describe('DastSiteAuthSection', () => {
describe('authentication toggle', () => { describe('authentication toggle', () => {
it.each([true, false])( it.each([true, false])(
'is set correctly when the "authEnabled" field is set to "%s"', 'is set correctly when the "enabled" field is set to "%s"',
(authEnabled) => { (authEnabled) => {
createComponent({ fields: { authEnabled } }); createComponent({ fields: { enabled: authEnabled } });
expect(findAuthCheckbox().vm.$attrs.checked).toBe(authEnabled); expect(findAuthCheckbox().vm.$attrs.checked).toBe(authEnabled);
}, },
); );
...@@ -57,7 +57,7 @@ describe('DastSiteAuthSection', () => { ...@@ -57,7 +57,7 @@ describe('DastSiteAuthSection', () => {
'makes the component emit an "input" event when changed', 'makes the component emit an "input" event when changed',
async (enabled) => { async (enabled) => {
await setAuthentication({ enabled }); await setAuthentication({ enabled });
expect(getLatestInputEventPayload().fields.authEnabled.value).toBe(enabled); expect(getLatestInputEventPayload().fields.enabled.value).toBe(enabled);
}, },
); );
}); });
...@@ -68,11 +68,11 @@ describe('DastSiteAuthSection', () => { ...@@ -68,11 +68,11 @@ describe('DastSiteAuthSection', () => {
}); });
const inputFieldsWithValues = { const inputFieldsWithValues = {
authenticationUrl: 'http://www.gitlab.com', url: 'http://www.gitlab.com',
userName: 'foo', username: 'foo',
password: 'foo', password: 'foo',
userNameFormField: 'foo', usernameField: 'foo',
passwordFormField: 'foo', passwordField: 'foo',
}; };
const inputFieldNames = Object.keys(inputFieldsWithValues); const inputFieldNames = Object.keys(inputFieldsWithValues);
......
...@@ -11,6 +11,7 @@ import dastSiteProfileUpdateMutation from 'ee/security_configuration/dast_site_p ...@@ -11,6 +11,7 @@ import dastSiteProfileUpdateMutation from 'ee/security_configuration/dast_site_p
import { siteProfiles } from 'ee_jest/on_demand_scans/mocks/mock_data'; import { siteProfiles } from 'ee_jest/on_demand_scans/mocks/mock_data';
import * as responses from 'ee_jest/security_configuration/dast_site_profiles_form/mock_data/apollo_mock'; import * as responses from 'ee_jest/security_configuration/dast_site_profiles_form/mock_data/apollo_mock';
import { TEST_HOST } from 'helpers/test_constants'; import { TEST_HOST } from 'helpers/test_constants';
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import waitForPromises from 'helpers/wait_for_promises'; import waitForPromises from 'helpers/wait_for_promises';
import * as urlUtility from '~/lib/utils/url_utility'; import * as urlUtility from '~/lib/utils/url_utility';
...@@ -44,24 +45,33 @@ describe('DastSiteProfileForm', () => { ...@@ -44,24 +45,33 @@ describe('DastSiteProfileForm', () => {
const withinComponent = () => within(wrapper.element); const withinComponent = () => within(wrapper.element);
const findForm = () => wrapper.find(GlForm); const findForm = () => wrapper.findComponent(GlForm);
const findByTestId = (testId) => wrapper.find(`[data-testid="${testId}"]`); const findAuthSection = () => wrapper.findComponent(DastSiteAuthSection);
const findProfileNameInput = () => findByTestId('profile-name-input'); const findCancelModal = () => wrapper.findComponent(GlModal);
const findTargetUrlInput = () => findByTestId('target-url-input'); const findByNameAttribute = (name) => wrapper.find(`[name="${name}"]`);
const findAuthSection = () => wrapper.find(DastSiteAuthSection); const findProfileNameInput = () => wrapper.findByTestId('profile-name-input');
const findExcludedUrlsInput = () => findByTestId('excluded-urls-input'); const findTargetUrlInput = () => wrapper.findByTestId('target-url-input');
const findRequestHeadersInput = () => findByTestId('request-headers-input'); const findExcludedUrlsInput = () => wrapper.findByTestId('excluded-urls-input');
const findSubmitButton = () => findByTestId('dast-site-profile-form-submit-button'); const findRequestHeadersInput = () => wrapper.findByTestId('request-headers-input');
const findCancelButton = () => findByTestId('dast-site-profile-form-cancel-button'); const findAuthCheckbox = () => wrapper.findByTestId('auth-enable-checkbox');
const findCancelModal = () => wrapper.find(GlModal); const findSubmitButton = () => wrapper.findByTestId('dast-site-profile-form-submit-button');
const findCancelButton = () => wrapper.findByTestId('dast-site-profile-form-cancel-button');
const findAlert = () => wrapper.findByTestId('dast-site-profile-form-alert');
const submitForm = () => findForm().vm.$emit('submit', { preventDefault: () => {} }); const submitForm = () => findForm().vm.$emit('submit', { preventDefault: () => {} });
const findAlert = () => findByTestId('dast-site-profile-form-alert');
const setFieldValue = async (field, value) => { const setFieldValue = async (field, value) => {
await field.setValue(value); await field.setValue(value);
field.trigger('blur'); field.trigger('blur');
}; };
const setAuthFieldsValues = async ({ enabled, ...fields }) => {
await findAuthCheckbox().setChecked(enabled);
Object.keys(fields).forEach((field) => {
findByNameAttribute(field).setValue(fields[field]);
});
};
const mockClientFactory = (handlers) => { const mockClientFactory = (handlers) => {
const mockClient = createMockClient(); const mockClient = createMockClient();
...@@ -109,7 +119,7 @@ describe('DastSiteProfileForm', () => { ...@@ -109,7 +119,7 @@ describe('DastSiteProfileForm', () => {
}, },
); );
wrapper = mountFn(DastSiteProfileForm, mountOpts); wrapper = extendedWrapper(mountFn(DastSiteProfileForm, mountOpts));
}; };
const createComponent = componentFactory(); const createComponent = componentFactory();
const createFullComponent = componentFactory(mount); const createFullComponent = componentFactory(mount);
...@@ -189,6 +199,7 @@ describe('DastSiteProfileForm', () => { ...@@ -189,6 +199,7 @@ describe('DastSiteProfileForm', () => {
await setFieldValue(findTargetUrlInput(), targetUrl); await setFieldValue(findTargetUrlInput(), targetUrl);
await setFieldValue(findExcludedUrlsInput(), excludedUrls); await setFieldValue(findExcludedUrlsInput(), excludedUrls);
await setFieldValue(findRequestHeadersInput(), requestHeaders); await setFieldValue(findRequestHeadersInput(), requestHeaders);
await setAuthFieldsValues(siteProfileOne.auth);
submitForm(); submitForm();
}; };
...@@ -209,6 +220,7 @@ describe('DastSiteProfileForm', () => { ...@@ -209,6 +220,7 @@ describe('DastSiteProfileForm', () => {
excludedUrls, excludedUrls,
requestHeaders, requestHeaders,
fullPath, fullPath,
auth: siteProfileOne.auth,
...mutationVars, ...mutationVars,
}, },
}); });
......
...@@ -34828,6 +34828,9 @@ msgstr "" ...@@ -34828,6 +34828,9 @@ msgstr ""
msgid "[No reason]" msgid "[No reason]"
msgstr "" msgstr ""
msgid "[Unchanged]"
msgstr ""
msgid "`end_time` should not exceed one month after `start_time`" msgid "`end_time` should not exceed one month after `start_time`"
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