Commit 7291ce5a authored by Dheeraj Joshi's avatar Dheeraj Joshi Committed by Kushal Pandya

Allow editing DAST site profile fields

This should add support for editing
auth related fields in DAST Site Profile
form
parent 0e9e1d42
<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,
}, },
}); });
......
...@@ -34819,6 +34819,9 @@ msgstr "" ...@@ -34819,6 +34819,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