Commit 447317b2 authored by Vitaly Slobodin's avatar Vitaly Slobodin

Merge branch '321886-disable-policy-dast-profile' into 'master'

Disable policy DAST scan profile modification

See merge request gitlab-org/gitlab!55704
parents df76f97d 3f074de4
......@@ -12,6 +12,7 @@ import {
} from '@gitlab/ui';
import { uniqueId } from 'lodash';
import { visitUrl } from '~/lib/utils/url_utility';
import { s__ } from '~/locale';
export default {
components: {
......@@ -115,9 +116,17 @@ export default {
},
},
methods: {
deleteTitle(item) {
return this.isPolicyProfile(item)
? s__('DastProfiles|This profile is currently being used in a policy.')
: s__('DastProfiles|Delete profile');
},
handleDelete() {
this.$emit('delete-profile', this.toBeDeletedProfileId);
},
isPolicyProfile(item) {
return Boolean(item?.referencedInSecurityPolicies?.length);
},
prepareProfileDeletion(profileId) {
this.toBeDeletedProfileId = profileId;
this.$refs[this.modalId].show();
......@@ -193,15 +202,33 @@ export default {
v-if="item.editPath"
:href="item.editPath"
:title="s__('DastProfiles|Edit profile')"
>{{ __('Edit') }}</gl-dropdown-item
>
{{ __('Edit') }}
</gl-dropdown-item>
<gl-dropdown-item
v-gl-tooltip="{
boundary: 'viewport',
placement: 'bottom',
disabled: !isPolicyProfile(item),
}"
boundary="viewport"
:class="{
'gl-cursor-default': isPolicyProfile(item),
}"
:disabled="isPolicyProfile(item)"
:aria-disabled="isPolicyProfile(item)"
variant="danger"
:title="s__('DastProfiles|Delete profile')"
:title="deleteTitle(item)"
@click="prepareProfileDeletion(item.id)"
>
{{ __('Delete') }}
<span
:class="{
'gl-text-gray-200!': isPolicyProfile(item),
}"
>
{{ __('Delete') }}
</span>
</gl-dropdown-item>
</gl-dropdown>
<gl-button
......@@ -210,18 +237,26 @@ export default {
category="tertiary"
class="gl-ml-3 gl-my-1 gl-md-display-none"
size="small"
>{{ __('Edit') }}</gl-button
>
<gl-button
{{ __('Edit') }}
</gl-button>
<span
v-gl-tooltip.hover.focus
category="tertiary"
icon="remove"
variant="danger"
size="small"
class="gl-mx-3 gl-my-1 gl-md-display-none"
:title="s__('DastProfiles|Delete profile')"
@click="prepareProfileDeletion(item.id)"
/>
:title="deleteTitle(item)"
data-testid="dast-profile-delete-tooltip"
>
<gl-button
category="tertiary"
icon="remove"
variant="danger"
size="small"
class="gl-mx-3 gl-my-1 gl-md-display-none"
data-testid="dast-profile-delete-button"
:disabled="isPolicyProfile(item)"
:aria-disabled="isPolicyProfile(item)"
@click="prepareProfileDeletion(item.id)"
/>
</span>
</div>
</template>
......
......@@ -28,6 +28,7 @@ query DastScannerProfiles(
useAjaxSpider
showDebugMessages
editPath
referencedInSecurityPolicies
}
}
}
......
......@@ -16,6 +16,7 @@ query DastSiteProfiles($fullPath: ID!, $after: String, $before: String, $first:
targetUrl
editPath
validationStatus
referencedInSecurityPolicies
}
}
}
......
......@@ -25,6 +25,7 @@ query DastSiteProfiles($fullPath: ID!, $after: String, $before: String, $first:
}
excludedUrls @client
requestHeaders @client
referencedInSecurityPolicies
}
}
}
......
......@@ -150,7 +150,10 @@ export default {
);
},
isSubmitDisabled() {
return this.formHasErrors || this.requiredFieldEmpty;
return this.formHasErrors || this.requiredFieldEmpty || this.isPolicyProfile;
},
isPolicyProfile() {
return Boolean(this.profile?.referencedInSecurityPolicies?.length);
},
},
......@@ -242,120 +245,140 @@ export default {
<template>
<gl-form @submit.prevent="onSubmit">
<h2 class="gl-mb-6">
{{ i18n.title }}
</h2>
<h2 class="gl-mb-6">{{ i18n.title }}</h2>
<gl-alert
v-if="isPolicyProfile"
data-testid="dast-policy-scanner-profile-alert"
variant="info"
class="gl-mb-5"
:dismissible="false"
>
{{
s__(
'DastProfiles|This scanner profile is currently being used by a policy. To make edits you must remove it from the active policy.',
)
}}
</gl-alert>
<gl-alert v-if="showAlert" variant="danger" class="gl-mb-5" @dismiss="hideErrors">
<gl-alert
v-if="showAlert"
data-testid="dast-scanner-profile-alert"
variant="danger"
class="gl-mb-5"
@dismiss="hideErrors"
>
{{ s__('DastProfiles|Could not create the scanner profile. Please try again.') }}
<ul v-if="errors.length" class="gl-mt-3 gl-mb-0">
<li v-for="error in errors" :key="error" v-text="error"></li>
</ul>
</gl-alert>
<gl-form-group :label="s__('DastProfiles|Profile name')">
<gl-form-input
v-model="form.profileName.value"
class="mw-460"
data-testid="profile-name-input"
type="text"
/>
</gl-form-group>
<hr class="gl-border-gray-100" />
<gl-form-group>
<template #label>
{{ s__('DastProfiles|Scan mode') }}
<tooltip-icon :title="i18n.tooltips.scanMode" />
</template>
<gl-form-group data-testid="dast-scanner-parent-group" :disabled="isPolicyProfile">
<gl-form-group :label="s__('DastProfiles|Profile name')">
<gl-form-input
v-model="form.profileName.value"
class="mw-460"
data-testid="profile-name-input"
type="text"
/>
</gl-form-group>
<gl-form-radio-group
v-model="form.scanType.value"
:options="$options.SCAN_TYPE_OPTIONS"
data-testid="scan-type-option"
/>
</gl-form-group>
<hr class="gl-border-gray-100" />
<div class="row">
<gl-form-group
class="col-md-6 mb-0"
:state="form.spiderTimeout.state"
:invalid-feedback="form.spiderTimeout.feedback"
>
<gl-form-group>
<template #label>
{{ s__('DastProfiles|Spider timeout') }}
<tooltip-icon :title="i18n.tooltips.spiderTimeout" />
{{ s__('DastProfiles|Scan mode') }}
<tooltip-icon :title="i18n.tooltips.scanMode" />
</template>
<gl-form-input-group
v-model.number="form.spiderTimeout.value"
class="mw-460"
data-testid="spider-timeout-input"
type="number"
:min="$options.spiderTimeoutRange.min"
:max="$options.spiderTimeoutRange.max"
@input="validateSpiderTimeout"
<gl-form-radio-group
v-model="form.scanType.value"
:options="$options.SCAN_TYPE_OPTIONS"
data-testid="scan-type-option"
/>
</gl-form-group>
<div class="row">
<gl-form-group
class="col-md-6 mb-0"
:state="form.spiderTimeout.state"
:invalid-feedback="form.spiderTimeout.feedback"
>
<template #append>
<gl-input-group-text>{{ __('Minutes') }}</gl-input-group-text>
<template #label>
{{ s__('DastProfiles|Spider timeout') }}
<tooltip-icon :title="i18n.tooltips.spiderTimeout" />
</template>
</gl-form-input-group>
<div class="gl-text-gray-400 gl-my-2">
{{ s__('DastProfiles|Minimum = 0 (no timeout enabled), Maximum = 2880 minutes') }}
</div>
</gl-form-group>
<gl-form-input-group
v-model.number="form.spiderTimeout.value"
class="mw-460"
data-testid="spider-timeout-input"
type="number"
:min="$options.spiderTimeoutRange.min"
:max="$options.spiderTimeoutRange.max"
@input="validateSpiderTimeout"
>
<template #append>
<gl-input-group-text>{{ __('Minutes') }}</gl-input-group-text>
</template>
</gl-form-input-group>
<div class="gl-text-gray-400 gl-my-2">
{{ s__('DastProfiles|Minimum = 0 (no timeout enabled), Maximum = 2880 minutes') }}
</div>
</gl-form-group>
<gl-form-group
class="col-md-6 mb-0"
:state="form.targetTimeout.state"
:invalid-feedback="form.targetTimeout.feedback"
>
<template #label>
{{ s__('DastProfiles|Target timeout') }}
<tooltip-icon :title="i18n.tooltips.targetTimeout" />
</template>
<gl-form-input-group
v-model.number="form.targetTimeout.value"
class="mw-460"
data-testid="target-timeout-input"
type="number"
:min="$options.targetTimeoutRange.min"
:max="$options.targetTimeoutRange.max"
@input="validateTargetTimeout"
<gl-form-group
class="col-md-6 mb-0"
:state="form.targetTimeout.state"
:invalid-feedback="form.targetTimeout.feedback"
>
<template #append>
<gl-input-group-text>{{ __('Seconds') }}</gl-input-group-text>
<template #label>
{{ s__('DastProfiles|Target timeout') }}
<tooltip-icon :title="i18n.tooltips.targetTimeout" />
</template>
</gl-form-input-group>
<div class="gl-text-gray-400 gl-my-2">
{{ s__('DastProfiles|Minimum = 1 second, Maximum = 3600 seconds') }}
</div>
</gl-form-group>
</div>
<gl-form-input-group
v-model.number="form.targetTimeout.value"
class="mw-460"
data-testid="target-timeout-input"
type="number"
:min="$options.targetTimeoutRange.min"
:max="$options.targetTimeoutRange.max"
@input="validateTargetTimeout"
>
<template #append>
<gl-input-group-text>{{ __('Seconds') }}</gl-input-group-text>
</template>
</gl-form-input-group>
<div class="gl-text-gray-400 gl-my-2">
{{ s__('DastProfiles|Minimum = 1 second, Maximum = 3600 seconds') }}
</div>
</gl-form-group>
</div>
<hr class="gl-border-gray-100" />
<hr class="gl-border-gray-100" />
<div class="row">
<gl-form-group class="col-md-6 mb-0">
<template #label>
{{ s__('DastProfiles|AJAX spider') }}
<tooltip-icon :title="i18n.tooltips.ajaxSpider" />
</template>
<gl-form-checkbox v-model="form.useAjaxSpider.value">{{
s__('DastProfiles|Turn on AJAX spider')
}}</gl-form-checkbox>
</gl-form-group>
<div class="row">
<gl-form-group class="col-md-6 mb-0">
<template #label>
{{ s__('DastProfiles|AJAX spider') }}
<tooltip-icon :title="i18n.tooltips.ajaxSpider" />
</template>
<gl-form-checkbox v-model="form.useAjaxSpider.value">{{
s__('DastProfiles|Turn on AJAX spider')
}}</gl-form-checkbox>
</gl-form-group>
<gl-form-group class="col-md-6 mb-0">
<template #label>
{{ s__('DastProfiles|Debug messages') }}
<tooltip-icon :title="i18n.tooltips.debugMessage" />
</template>
<gl-form-checkbox v-model="form.showDebugMessages.value">{{
s__('DastProfiles|Show debug messages')
}}</gl-form-checkbox>
</gl-form-group>
</div>
<gl-form-group class="col-md-6 mb-0">
<template #label>
{{ s__('DastProfiles|Debug messages') }}
<tooltip-icon :title="i18n.tooltips.debugMessage" />
</template>
<gl-form-checkbox v-model="form.showDebugMessages.value">{{
s__('DastProfiles|Show debug messages')
}}</gl-form-checkbox>
</gl-form-group>
</div>
</gl-form-group>
<hr class="gl-border-gray-100" />
......
......@@ -14,6 +14,11 @@ export default {
validation: validation(),
},
props: {
disabled: {
type: Boolean,
required: false,
default: false,
},
value: {
type: Object,
required: false,
......@@ -77,91 +82,93 @@ export default {
<template>
<section>
<gl-form-group :label="s__('DastProfiles|Authentication')">
<gl-form-checkbox v-model="form.fields.enabled.value" data-testid="auth-enable-checkbox">{{
s__('DastProfiles|Enable Authentication')
}}</gl-form-checkbox>
</gl-form-group>
<div v-if="form.fields.enabled.value" data-testid="auth-form">
<div class="row">
<gl-form-group
:label="s__('DastProfiles|Authentication URL')"
:invalid-feedback="form.fields.url.feedback"
class="col-md-6"
>
<gl-form-input
v-model="form.fields.url.value"
v-validation:[showValidationOrInEditMode]
name="url"
type="url"
required
:state="form.fields.url.state"
/>
</gl-form-group>
</div>
<div class="row">
<gl-form-group
:label="s__('DastProfiles|Username')"
:invalid-feedback="form.fields.username.feedback"
class="col-md-6"
>
<gl-form-input
v-model="form.fields.username.value"
v-validation:[showValidationOrInEditMode]
autocomplete="off"
name="username"
type="text"
required
:state="form.fields.username.state"
/>
</gl-form-group>
<gl-form-group
:label="s__('DastProfiles|Password')"
:invalid-feedback="form.fields.password.feedback"
class="col-md-6"
>
<gl-form-input
v-model="form.fields.password.value"
v-validation:[showValidationOrInEditMode]
autocomplete="off"
name="password"
type="password"
:placeholder="sensitiveFieldPlaceholder"
:required="isSensitiveFieldRequired"
:state="form.fields.password.state"
/>
</gl-form-group>
<gl-form-group data-testid="dast-site-auth-parent-group" :disabled="disabled">
<gl-form-group :label="s__('DastProfiles|Authentication')">
<gl-form-checkbox v-model="form.fields.enabled.value" data-testid="auth-enable-checkbox">{{
s__('DastProfiles|Enable Authentication')
}}</gl-form-checkbox>
</gl-form-group>
<div v-if="form.fields.enabled.value" data-testid="auth-form">
<div class="row">
<gl-form-group
:label="s__('DastProfiles|Authentication URL')"
:invalid-feedback="form.fields.url.feedback"
class="col-md-6"
>
<gl-form-input
v-model="form.fields.url.value"
v-validation:[showValidationOrInEditMode]
name="url"
type="url"
required
:state="form.fields.url.state"
/>
</gl-form-group>
</div>
<div class="row">
<gl-form-group
:label="s__('DastProfiles|Username')"
:invalid-feedback="form.fields.username.feedback"
class="col-md-6"
>
<gl-form-input
v-model="form.fields.username.value"
v-validation:[showValidationOrInEditMode]
autocomplete="off"
name="username"
type="text"
required
:state="form.fields.username.state"
/>
</gl-form-group>
<gl-form-group
:label="s__('DastProfiles|Password')"
:invalid-feedback="form.fields.password.feedback"
class="col-md-6"
>
<gl-form-input
v-model="form.fields.password.value"
v-validation:[showValidationOrInEditMode]
autocomplete="off"
name="password"
type="password"
:placeholder="sensitiveFieldPlaceholder"
:required="isSensitiveFieldRequired"
:state="form.fields.password.state"
/>
</gl-form-group>
</div>
<div class="row">
<gl-form-group
:label="s__('DastProfiles|Username form field')"
:invalid-feedback="form.fields.usernameField.feedback"
class="col-md-6"
>
<gl-form-input
v-model="form.fields.usernameField.value"
v-validation:[showValidationOrInEditMode]
name="usernameField"
type="text"
required
:state="form.fields.usernameField.state"
/>
</gl-form-group>
<gl-form-group
:label="s__('DastProfiles|Password form field')"
:invalid-feedback="form.fields.passwordField.feedback"
class="col-md-6"
>
<gl-form-input
v-model="form.fields.passwordField.value"
v-validation:[showValidationOrInEditMode]
name="passwordField"
type="text"
required
:state="form.fields.passwordField.state"
/>
</gl-form-group>
</div>
</div>
<div class="row">
<gl-form-group
:label="s__('DastProfiles|Username form field')"
:invalid-feedback="form.fields.usernameField.feedback"
class="col-md-6"
>
<gl-form-input
v-model="form.fields.usernameField.value"
v-validation:[showValidationOrInEditMode]
name="usernameField"
type="text"
required
:state="form.fields.usernameField.state"
/>
</gl-form-group>
<gl-form-group
:label="s__('DastProfiles|Password form field')"
:invalid-feedback="form.fields.passwordField.feedback"
class="col-md-6"
>
<gl-form-input
v-model="form.fields.passwordField.value"
v-validation:[showValidationOrInEditMode]
name="passwordField"
type="text"
required
:state="form.fields.passwordField.state"
/>
</gl-form-group>
</div>
</div>
</gl-form-group>
</section>
</template>
......@@ -140,6 +140,9 @@ export default {
formTouched() {
return !isEqual(serializeFormObject(this.form.fields), this.initialFormValues);
},
isPolicyProfile() {
return Boolean(this.siteProfile?.referencedInSecurityPolicies?.length);
},
},
async mounted() {
if (this.isEdit) {
......@@ -244,6 +247,20 @@ export default {
{{ i18n.title }}
</h2>
<gl-alert
v-if="isPolicyProfile"
data-testid="dast-policy-site-profile-form-alert"
variant="info"
class="gl-mb-5"
:dismissible="false"
>
{{
s__(
'DastProfiles|This site profile is currently being used by a policy. To make edits you must remove it from the active policy.',
)
}}
</gl-alert>
<gl-alert
v-if="hasAlert"
variant="danger"
......@@ -257,98 +274,102 @@ export default {
</ul>
</gl-alert>
<gl-form-group
:label="s__('DastProfiles|Profile name')"
:invalid-feedback="form.fields.profileName.feedback"
>
<gl-form-input
v-model="form.fields.profileName.value"
v-validation:[form.showValidation]
name="profileName"
class="mw-460"
data-testid="profile-name-input"
type="text"
required
:state="form.fields.profileName.state"
/>
</gl-form-group>
<hr class="gl-border-gray-100" />
<gl-form-group
data-testid="target-url-input-group"
:invalid-feedback="form.fields.targetUrl.feedback"
:label="s__('DastProfiles|Target URL')"
>
<gl-form-input
v-model="form.fields.targetUrl.value"
v-validation:[form.showValidation]
name="targetUrl"
class="mw-460"
data-testid="target-url-input"
required
type="url"
:state="form.fields.targetUrl.state"
/>
</gl-form-group>
<div v-if="glFeatures.securityDastSiteProfilesAdditionalFields" class="row">
<gl-form-group data-testid="dast-site-parent-group" :disabled="isPolicyProfile">
<gl-form-group
:label="s__('DastProfiles|Excluded URLs (Optional)')"
:invalid-feedback="form.fields.excludedUrls.feedback"
class="col-md-6"
:label="s__('DastProfiles|Profile name')"
:invalid-feedback="form.fields.profileName.feedback"
>
<template #label>
{{ i18n.excludedUrls.label }}
<tooltip-icon :title="i18n.excludedUrls.tooltip" />
<gl-form-text class="gl-mt-3">{{ i18n.excludedUrls.description }}</gl-form-text>
</template>
<gl-form-textarea
v-model="form.fields.excludedUrls.value"
:maxlength="$options.MAX_CHAR_LIMIT_EXCLUDED_URLS"
:placeholder="i18n.excludedUrls.placeholder"
:no-resize="false"
data-testid="excluded-urls-input"
<gl-form-input
v-model="form.fields.profileName.value"
v-validation:[form.showValidation]
name="profileName"
class="mw-460"
data-testid="profile-name-input"
type="text"
required
:state="form.fields.profileName.state"
/>
<gl-form-text>{{
getCharacterLimitText(
form.fields.excludedUrls.value,
$options.MAX_CHAR_LIMIT_EXCLUDED_URLS,
)
}}</gl-form-text>
</gl-form-group>
<gl-form-group :invalid-feedback="form.fields.requestHeaders.feedback" class="col-md-6">
<template #label>
{{ i18n.requestHeaders.label }}
<tooltip-icon :title="i18n.requestHeaders.tooltip" />
<gl-form-text class="gl-mt-3">{{ i18n.requestHeaders.description }}</gl-form-text>
</template>
<gl-form-textarea
v-model="form.fields.requestHeaders.value"
:maxlength="$options.MAX_CHAR_LIMIT_REQUEST_HEADERS"
:placeholder="i18n.requestHeaders.placeholder"
:no-resize="false"
data-testid="request-headers-input"
<hr class="gl-border-gray-100" />
<gl-form-group
data-testid="target-url-input-group"
:invalid-feedback="form.fields.targetUrl.feedback"
:label="s__('DastProfiles|Target URL')"
>
<gl-form-input
v-model="form.fields.targetUrl.value"
v-validation:[form.showValidation]
name="targetUrl"
class="mw-460"
data-testid="target-url-input"
required
type="url"
:state="form.fields.targetUrl.state"
/>
<gl-form-text>{{
getCharacterLimitText(
form.fields.requestHeaders.value,
$options.MAX_CHAR_LIMIT_REQUEST_HEADERS,
)
}}</gl-form-text>
</gl-form-group>
</div>
<div v-if="glFeatures.securityDastSiteProfilesAdditionalFields" class="row">
<gl-form-group
:label="s__('DastProfiles|Excluded URLs (Optional)')"
:invalid-feedback="form.fields.excludedUrls.feedback"
class="col-md-6"
>
<template #label>
{{ i18n.excludedUrls.label }}
<tooltip-icon :title="i18n.excludedUrls.tooltip" />
<gl-form-text class="gl-mt-3">{{ i18n.excludedUrls.description }}</gl-form-text>
</template>
<gl-form-textarea
v-model="form.fields.excludedUrls.value"
:maxlength="$options.MAX_CHAR_LIMIT_EXCLUDED_URLS"
:placeholder="i18n.excludedUrls.placeholder"
:no-resize="false"
data-testid="excluded-urls-input"
/>
<gl-form-text>{{
getCharacterLimitText(
form.fields.excludedUrls.value,
$options.MAX_CHAR_LIMIT_EXCLUDED_URLS,
)
}}</gl-form-text>
</gl-form-group>
<gl-form-group :invalid-feedback="form.fields.requestHeaders.feedback" class="col-md-6">
<template #label>
{{ i18n.requestHeaders.label }}
<tooltip-icon :title="i18n.requestHeaders.tooltip" />
<gl-form-text class="gl-mt-3">{{ i18n.requestHeaders.description }}</gl-form-text>
</template>
<gl-form-textarea
v-model="form.fields.requestHeaders.value"
:maxlength="$options.MAX_CHAR_LIMIT_REQUEST_HEADERS"
:placeholder="i18n.requestHeaders.placeholder"
:no-resize="false"
data-testid="request-headers-input"
/>
<gl-form-text>{{
getCharacterLimitText(
form.fields.requestHeaders.value,
$options.MAX_CHAR_LIMIT_REQUEST_HEADERS,
)
}}</gl-form-text>
</gl-form-group>
</div>
</gl-form-group>
<dast-site-auth-section
v-if="glFeatures.securityDastSiteProfilesAdditionalFields"
v-model="authSection"
:disabled="isPolicyProfile"
:show-validation="form.showValidation"
/>
<hr class="gl-border-gray-100" />
<gl-button
:disabled="isPolicyProfile"
type="submit"
variant="success"
class="js-no-auto-disable"
......
......@@ -8,5 +8,5 @@ profiles_library_path: project_security_configuration_dast_profiles_path(@projec
scanner_profile: { id: @scanner_profile.to_global_id.to_s, name: @scanner_profile.name,
spider_timeout: @scanner_profile.spider_timeout, target_timeout: @scanner_profile.target_timeout,
scan_type: @scanner_profile.scan_type.upcase, use_ajax_spider: @scanner_profile.use_ajax_spider,
show_debug_messages: @scanner_profile.show_debug_messages }.to_json,
show_debug_messages: @scanner_profile.show_debug_messages, referenced_in_security_policies: @scanner_profile.referenced_in_security_policies }.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) } }
......@@ -7,5 +7,5 @@
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,
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,
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: Feature.enabled?(:dast_saved_scans, @project, default_enabled: :yaml) ? new_project_on_demand_scan_path(@project) : project_on_demand_scans_path(@project) } }
---
title: Disable policy DAST scan profile modification
merge_request: 55704
author:
type: changed
......@@ -8,6 +8,7 @@ export const scannerProfiles = [
useAjaxSpider: false,
showDebugMessages: false,
editPath: '/scanner_profile/edit/1',
referencedInSecurityPolicies: [],
},
{
id: 'gid://gitlab/DastScannerProfile/2',
......@@ -18,9 +19,22 @@ export const scannerProfiles = [
useAjaxSpider: true,
showDebugMessages: true,
editPath: '/scanner_profile/edit/2',
referencedInSecurityPolicies: [],
},
];
export const policyScannerProfile = {
id: 'gid://gitlab/DastScannerProfile/3',
profileName: 'Scanner profile #3',
spiderTimeout: 20,
targetTimeout: 150,
scanType: 'ACTIVE',
useAjaxSpider: true,
showDebugMessages: true,
editPath: '/scanner_profile/edit/3',
referencedInSecurityPolicies: ['some_policy'],
};
export const siteProfiles = [
{
id: 'gid://gitlab/DastSiteProfile/1',
......@@ -39,6 +53,7 @@ export const siteProfiles = [
},
excludedUrls: 'https://foo.com/logout,https://foo.com/send_mail',
requestHeaders: 'log-identifier: dast-active-scan',
referencedInSecurityPolicies: [],
},
{
id: 'gid://gitlab/DastSiteProfile/2',
......@@ -52,5 +67,20 @@ export const siteProfiles = [
},
excludedUrls: 'https://bar.com/logout',
requestHeaders: 'auth: gitlab-dast',
referencedInSecurityPolicies: [],
},
];
export const policySiteProfile = {
id: 'gid://gitlab/DastSiteProfile/6',
profileName: 'Profile 6',
targetUrl: 'http://example-6.com',
normalizedTargetUrl: 'http://example-6.com',
editPath: '/6/edit',
validationStatus: 'NONE',
auth: {
enabled: false,
},
excludedUrls: 'https://bar.com/logout',
referencedInSecurityPolicies: ['some_policy'],
};
......@@ -4,7 +4,7 @@ import { mount, shallowMount, createWrapper } from '@vue/test-utils';
import { merge } from 'lodash';
import DastProfilesList from 'ee/security_configuration/dast_profiles/components/dast_profiles_list.vue';
import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
import { siteProfiles as profiles } from '../mocks/mock_data';
import { siteProfiles as profiles, policySiteProfile } from '../mocks/mock_data';
const TEST_ERROR_MESSAGE = 'something went wrong';
......@@ -58,8 +58,9 @@ describe('EE - DastProfilesList', () => {
const getErrorMessage = () => withinComponent().queryByText(TEST_ERROR_MESSAGE);
const getErrorDetails = () => withinComponent().queryByRole('list', { name: /error details/i });
const getDeleteButtonWithin = (element) =>
createWrapper(within(element).queryByRole('button', { name: /delete/i }));
createWrapper(within(element).queryByTestId('dast-profile-delete-button'));
const getModal = () => wrapper.find(GlModal);
const getDeleteTooltip = () => wrapper.find('[data-testid="dast-profile-delete-tooltip"');
afterEach(() => {
wrapper.destroy();
......@@ -133,7 +134,7 @@ describe('EE - DastProfilesList', () => {
expect(profileCell.innerText).toContain(profile.profileName);
expect(targetUrlCell.innerText).toContain(profile.targetUrl);
expect(within(actionsCell).getByRole('button', { name: /delete/i })).not.toBe(null);
expect(within(actionsCell).queryByTestId('dast-profile-delete-button')).not.toBe(null);
const editLink = within(actionsCell).getByRole('link', { name: /edit/i });
expect(editLink).not.toBe(null);
......@@ -194,10 +195,8 @@ describe('EE - DastProfilesList', () => {
getDeleteButtonWithin(getTableRowForProfile(profile));
it('shows a tooltip on the delete button', () => {
expect(getBinding(getCurrentProfileDeleteButton().element, 'gl-tooltip')).not.toBe(
undefined,
);
expect(getCurrentProfileDeleteButton().attributes().title).toBe('Delete profile');
expect(getBinding(getDeleteTooltip().element, 'gl-tooltip')).not.toBe(undefined);
expect(getDeleteTooltip().attributes('title')).toBe('Delete profile');
});
it('opens a modal with the correct title when a delete button is clicked', async () => {
......@@ -246,4 +245,22 @@ describe('EE - DastProfilesList', () => {
expect(within(getErrorDetails()).getByText(errorDetails[1])).not.toBe(null);
});
});
describe('profile referenced in a security policy', () => {
it('disables the delete button', () => {
createFullComponent({ propsData: { profiles: policySiteProfile } });
const disabledRow = getAllTableRows()[0];
const deleteButton = getDeleteButtonWithin(disabledRow);
expect(deleteButton.attributes('disabled')).toBe('disabled');
expect(deleteButton.attributes('aria-disabled')).toBe('true');
});
it('shows the correct tooltip text', () => {
createFullComponent({ propsData: { profiles: policySiteProfile } });
expect(getBinding(getDeleteTooltip().element, 'gl-tooltip')).not.toBe(undefined);
expect(getDeleteTooltip().attributes('title')).toBe(
'This profile is currently being used in a policy.',
);
});
});
});
......@@ -6,6 +6,7 @@ export const siteProfiles = [
normalizedTargetUrl: 'http://example-1.com',
editPath: '/1/edit',
validationStatus: 'PENDING_VALIDATION',
referencedInSecurityPolicies: [],
},
{
id: 'gid://gitlab/DastSiteProfile/2',
......@@ -14,6 +15,7 @@ export const siteProfiles = [
normalizedTargetUrl: 'http://example-2.com',
editPath: '/2/edit',
validationStatus: 'INPROGRESS_VALIDATION',
referencedInSecurityPolicies: [],
},
{
id: 'gid://gitlab/DastSiteProfile/3',
......@@ -22,6 +24,7 @@ export const siteProfiles = [
normalizedTargetUrl: 'http://example-2.com',
editPath: '/3/edit',
validationStatus: 'PASSED_VALIDATION',
referencedInSecurityPolicies: [],
},
{
id: 'gid://gitlab/DastSiteProfile/4',
......@@ -30,6 +33,7 @@ export const siteProfiles = [
normalizedTargetUrl: 'http://example-3.com',
editPath: '/3/edit',
validationStatus: 'FAILED_VALIDATION',
referencedInSecurityPolicies: [],
},
{
id: 'gid://gitlab/DastSiteProfile/5',
......@@ -38,6 +42,19 @@ export const siteProfiles = [
normalizedTargetUrl: 'http://example-5.com',
editPath: '/5/edit',
validationStatus: 'NONE',
referencedInSecurityPolicies: [],
},
];
export const policySiteProfile = [
{
id: 'gid://gitlab/DastSiteProfile/6',
profileName: 'Profile 6',
targetUrl: 'http://example-6.com',
normalizedTargetUrl: 'http://example-6.com',
editPath: '/6/edit',
validationStatus: 'NONE',
referencedInSecurityPolicies: ['some_policy'],
},
];
......
import { GlAlert, GlForm, GlModal } from '@gitlab/ui';
import { GlForm, GlModal } from '@gitlab/ui';
import { within } from '@testing-library/dom';
import { mount, shallowMount } from '@vue/test-utils';
import merge from 'lodash/merge';
......@@ -6,7 +6,7 @@ import DastScannerProfileForm from 'ee/security_configuration/dast_scanner_profi
import { SCAN_TYPE } from 'ee/security_configuration/dast_scanner_profiles/constants';
import dastScannerProfileCreateMutation from 'ee/security_configuration/dast_scanner_profiles/graphql/dast_scanner_profile_create.mutation.graphql';
import dastScannerProfileUpdateMutation from 'ee/security_configuration/dast_scanner_profiles/graphql/dast_scanner_profile_update.mutation.graphql';
import { scannerProfiles } from 'ee_jest/on_demand_scans/mocks/mock_data';
import { scannerProfiles, policyScannerProfile } from 'ee_jest/on_demand_scans/mocks/mock_data';
import { TEST_HOST } from 'helpers/test_constants';
import { redirectTo } from '~/lib/utils/url_utility';
......@@ -40,6 +40,7 @@ describe('DAST Scanner Profile', () => {
const withinComponent = () => within(wrapper.element);
const findByTestId = (testId) => wrapper.find(`[data-testid="${testId}"`);
const findParentFormGroup = () => findByTestId('dast-scanner-parent-group');
const findForm = () => wrapper.find(GlForm);
const findProfileNameInput = () => findByTestId('profile-name-input');
const findSpiderTimeoutInput = () => findByTestId('spider-timeout-input');
......@@ -49,7 +50,8 @@ describe('DAST Scanner Profile', () => {
const findScanType = () => findByTestId('scan-type-option');
const findCancelModal = () => wrapper.find(GlModal);
const findAlert = () => wrapper.find(GlAlert);
const findAlert = () => findByTestId('dast-scanner-profile-alert');
const findPolicyProfileAlert = () => findByTestId('dast-policy-scanner-profile-alert');
const submitForm = () => findForm().vm.$emit('submit', { preventDefault: () => {} });
const componentFactory = (mountFn = shallowMount) => (options) => {
......@@ -281,4 +283,44 @@ describe('DAST Scanner Profile', () => {
});
});
});
describe('when profile does not come from a policy', () => {
beforeEach(() => {
createComponent({
propsData: {
profile: defaultProfile,
},
});
});
it('should enable all form groups', () => {
expect(findParentFormGroup().attributes('disabled')).toBe(undefined);
});
it('should show the policy profile alert', () => {
expect(findPolicyProfileAlert().exists()).toBe(false);
});
});
describe('when profile does comes from a policy', () => {
beforeEach(() => {
createComponent({
propsData: {
profile: policyScannerProfile,
},
});
});
it('should show the policy profile alert', () => {
expect(findPolicyProfileAlert().exists()).toBe(true);
});
it('should disable all form groups', () => {
expect(findParentFormGroup().attributes('disabled')).toBe('true');
});
it('should disable the save button', () => {
expect(findSubmitButton().props('disabled')).toBe(true);
});
});
});
import { GlFormCheckbox } from '@gitlab/ui';
import { mount } from '@vue/test-utils';
import { mount, shallowMount } from '@vue/test-utils';
import DastSiteAuthSection from 'ee/security_configuration/dast_site_profiles_form/components/dast_site_auth_section.vue';
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
describe('DastSiteAuthSection', () => {
let wrapper;
const createComponent = ({ fields = {} } = {}) => {
const createComponent = ({ mountFn = mount, fields = {}, disabled = false } = {}) => {
wrapper = extendedWrapper(
mount(DastSiteAuthSection, {
mountFn(DastSiteAuthSection, {
propsData: {
disabled,
value: { fields },
},
}),
......@@ -24,6 +25,7 @@ describe('DastSiteAuthSection', () => {
wrapper.destroy();
});
const findParentFormGroup = () => wrapper.findByTestId('dast-site-auth-parent-group');
const findByNameAttribute = (name) => wrapper.find(`[name="${name}"]`);
const findAuthForm = () => wrapper.findByTestId('auth-form');
const findAuthCheckbox = () => wrapper.find(GlFormCheckbox);
......@@ -115,5 +117,19 @@ describe('DastSiteAuthSection', () => {
expect(getLatestInputEventPayload().state).toBe(true);
});
});
describe('when profile does not come from a policy', () => {
it('should enable all form groups', () => {
createComponent({ mountFn: shallowMount, fields: { enabled: true } });
expect(findParentFormGroup().attributes('disabled')).toBe(undefined);
});
});
describe('when profile does comes from a policy', () => {
it('should disable all form groups', () => {
createComponent({ mountFn: shallowMount, disabled: true, fields: { enabled: true } });
expect(findParentFormGroup().attributes('disabled')).toBe('true');
});
});
});
});
......@@ -8,7 +8,7 @@ import DastSiteAuthSection from 'ee/security_configuration/dast_site_profiles_fo
import DastSiteProfileForm from 'ee/security_configuration/dast_site_profiles_form/components/dast_site_profile_form.vue';
import dastSiteProfileCreateMutation from 'ee/security_configuration/dast_site_profiles_form/graphql/dast_site_profile_create.mutation.graphql';
import dastSiteProfileUpdateMutation from 'ee/security_configuration/dast_site_profiles_form/graphql/dast_site_profile_update.mutation.graphql';
import { siteProfiles } from 'ee_jest/on_demand_scans/mocks/mock_data';
import { siteProfiles, policySiteProfile } 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 { TEST_HOST } from 'helpers/test_constants';
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
......@@ -46,6 +46,7 @@ describe('DastSiteProfileForm', () => {
const withinComponent = () => within(wrapper.element);
const findForm = () => wrapper.findComponent(GlForm);
const findParentFormGroup = () => wrapper.findByTestId('dast-site-parent-group');
const findAuthSection = () => wrapper.findComponent(DastSiteAuthSection);
const findCancelModal = () => wrapper.findComponent(GlModal);
const findByNameAttribute = (name) => wrapper.find(`[name="${name}"]`);
......@@ -57,6 +58,7 @@ describe('DastSiteProfileForm', () => {
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 findPolicyAlert = () => wrapper.findByTestId('dast-policy-site-profile-form-alert');
const submitForm = () => findForm().vm.$emit('submit', { preventDefault: () => {} });
const setFieldValue = async (field, value) => {
......@@ -333,4 +335,44 @@ describe('DastSiteProfileForm', () => {
expect(findRequestHeadersInput().exists()).toBe(false);
});
});
describe('when profile does not come from a policy', () => {
beforeEach(() => {
createComponent({
propsData: {
siteProfile: siteProfileOne,
},
});
});
it('should enable all form groups', () => {
expect(findParentFormGroup().attributes('disabled')).toBe(undefined);
});
it('should show the policy profile alert', () => {
expect(findPolicyAlert().exists()).toBe(false);
});
});
describe('when profile does comes from a policy', () => {
beforeEach(() => {
createComponent({
propsData: {
siteProfile: policySiteProfile,
},
});
});
it('should show the policy profile alert', () => {
expect(findPolicyAlert().exists()).toBe(true);
});
it('should disable all form groups', () => {
expect(findParentFormGroup().attributes('disabled')).toBe('true');
});
it('should disable the save button', () => {
expect(findSubmitButton().props('disabled')).toBe(true);
});
});
});
......@@ -9637,6 +9637,15 @@ msgstr ""
msgid "DastProfiles|The maximum number of seconds allowed for the site under test to respond to a request."
msgstr ""
msgid "DastProfiles|This profile is currently being used in a policy."
msgstr ""
msgid "DastProfiles|This scanner profile is currently being used by a policy. To make edits you must remove it from the active policy."
msgstr ""
msgid "DastProfiles|This site profile is currently being used by a policy. To make edits you must remove it from the active policy."
msgstr ""
msgid "DastProfiles|Turn on AJAX spider"
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