Commit f4e5a483 authored by Dheeraj Joshi's avatar Dheeraj Joshi Committed by Scott Hampton

Update summary and placeholders for DAST site profile

This updates DAST request headers and password fields
which are sensitive in nature
parent ff02c4c6
...@@ -38,7 +38,6 @@ import { ...@@ -38,7 +38,6 @@ import {
ERROR_MESSAGES, ERROR_MESSAGES,
SCANNER_PROFILES_QUERY, SCANNER_PROFILES_QUERY,
SITE_PROFILES_QUERY, SITE_PROFILES_QUERY,
SITE_PROFILES_EXTENDED_QUERY,
TYPE_SITE_PROFILE, TYPE_SITE_PROFILE,
TYPE_SCANNER_PROFILE, TYPE_SCANNER_PROFILE,
} from '../settings'; } from '../settings';
...@@ -101,15 +100,11 @@ export default { ...@@ -101,15 +100,11 @@ export default {
'selectedScannerProfileId', 'selectedScannerProfileId',
SCANNER_PROFILES_QUERY, SCANNER_PROFILES_QUERY,
), ),
siteProfiles() { siteProfiles: createProfilesApolloOptions(
return createProfilesApolloOptions(
'siteProfiles', 'siteProfiles',
'selectedSiteProfileId', 'selectedSiteProfileId',
this.glFeatures.securityDastSiteProfilesAdditionalFields SITE_PROFILES_QUERY,
? SITE_PROFILES_EXTENDED_QUERY ),
: SITE_PROFILES_QUERY,
);
},
}, },
inject: { inject: {
dastSiteValidationDocsPath: { dastSiteValidationDocsPath: {
...@@ -233,6 +228,9 @@ export default { ...@@ -233,6 +228,9 @@ export default {
selectedSiteProfileId, selectedSiteProfileId,
}; };
}, },
hasExcludedUrls() {
return this.selectedSiteProfile.excludedUrls?.length > 0;
},
}, },
created() { created() {
const params = queryToObject(window.location.search); const params = queryToObject(window.location.search);
...@@ -499,6 +497,10 @@ export default { ...@@ -499,6 +497,10 @@ export default {
:label="s__('DastProfiles|Username')" :label="s__('DastProfiles|Username')"
:value="selectedSiteProfile.auth.username" :value="selectedSiteProfile.auth.username"
/> />
<profile-selector-summary-cell
:label="s__('DastProfiles|Password')"
value="••••••••"
/>
</div> </div>
<div class="row"> <div class="row">
<profile-selector-summary-cell <profile-selector-summary-cell
...@@ -513,12 +515,14 @@ export default { ...@@ -513,12 +515,14 @@ export default {
</template> </template>
<div class="row"> <div class="row">
<profile-selector-summary-cell <profile-selector-summary-cell
v-if="hasExcludedUrls"
:label="s__('DastProfiles|Excluded URLs')" :label="s__('DastProfiles|Excluded URLs')"
:value="selectedSiteProfile.excludedUrls.join($options.EXCLUDED_URLS_SEPARATOR)" :value="selectedSiteProfile.excludedUrls.join($options.EXCLUDED_URLS_SEPARATOR)"
/> />
<profile-selector-summary-cell <profile-selector-summary-cell
v-if="selectedSiteProfile.requestHeaders"
:label="s__('DastProfiles|Request headers')" :label="s__('DastProfiles|Request headers')"
:value="selectedSiteProfile.requestHeaders" :value="__('[Redacted]')"
/> />
</div> </div>
</template> </template>
......
import Vue from 'vue'; import Vue from 'vue';
import VueApollo from 'vue-apollo'; import VueApollo from 'vue-apollo';
import { resolvers } from 'ee/security_configuration/dast_profiles/graphql/provider';
import createDefaultClient from '~/lib/graphql'; import createDefaultClient from '~/lib/graphql';
Vue.use(VueApollo); Vue.use(VueApollo);
export default new VueApollo({ export default new VueApollo({
defaultClient: createDefaultClient(), defaultClient: createDefaultClient(resolvers),
}); });
import dastScannerProfilesQuery from 'ee/security_configuration/dast_profiles/graphql/dast_scanner_profiles.query.graphql'; import dastScannerProfilesQuery from 'ee/security_configuration/dast_profiles/graphql/dast_scanner_profiles.query.graphql';
import dastSiteProfilesQuery from 'ee/security_configuration/dast_profiles/graphql/dast_site_profiles.query.graphql'; import dastSiteProfilesQuery from 'ee/security_configuration/dast_profiles/graphql/dast_site_profiles.query.graphql';
import dastSiteProfilesExtendedQuery from 'ee/security_configuration/dast_profiles/graphql/dast_site_profiles_extended.query.graphql';
import { s__ } from '~/locale'; import { s__ } from '~/locale';
export const ERROR_RUN_SCAN = 'ERROR_RUN_SCAN'; export const ERROR_RUN_SCAN = 'ERROR_RUN_SCAN';
...@@ -29,10 +28,5 @@ export const SITE_PROFILES_QUERY = { ...@@ -29,10 +28,5 @@ export const SITE_PROFILES_QUERY = {
fetchError: ERROR_FETCH_SITE_PROFILES, fetchError: ERROR_FETCH_SITE_PROFILES,
}; };
export const SITE_PROFILES_EXTENDED_QUERY = {
...SITE_PROFILES_QUERY,
fetchQuery: dastSiteProfilesExtendedQuery,
};
export const TYPE_SITE_PROFILE = 'DastSiteProfile'; export const TYPE_SITE_PROFILE = 'DastSiteProfile';
export const TYPE_SCANNER_PROFILE = 'DastScannerProfile'; export const TYPE_SCANNER_PROFILE = 'DastScannerProfile';
...@@ -17,6 +17,15 @@ query DastSiteProfiles($fullPath: ID!, $after: String, $before: String, $first: ...@@ -17,6 +17,15 @@ query DastSiteProfiles($fullPath: ID!, $after: String, $before: String, $first:
editPath editPath
validationStatus validationStatus
referencedInSecurityPolicies referencedInSecurityPolicies
auth @client {
enabled
url
usernameField
passwordField
username
}
excludedUrls @client
requestHeaders @client
} }
} }
} }
......
#import "~/graphql_shared/fragments/pageInfo.fragment.graphql"
query DastSiteProfiles($fullPath: ID!, $after: String, $before: String, $first: Int, $last: Int) {
project(fullPath: $fullPath) {
siteProfiles: dastSiteProfiles(after: $after, before: $before, first: $first, last: $last)
@connection(key: "dastSiteProfiles") {
pageInfo {
...PageInfo
}
edges {
cursor
node {
id
profileName
normalizedTargetUrl
targetUrl
editPath
validationStatus
auth @client {
enabled
url
usernameField
passwordField
username
}
excludedUrls @client
requestHeaders @client
referencedInSecurityPolicies
}
}
}
}
}
...@@ -4,6 +4,21 @@ import createDefaultClient from '~/lib/graphql'; ...@@ -4,6 +4,21 @@ import createDefaultClient from '~/lib/graphql';
Vue.use(VueApollo); Vue.use(VueApollo);
export const resolvers = {
DastSiteProfile: {
auth: () => ({
__typename: 'DastSiteProfileAuth',
enabled: true,
url: 'http://test.local/users/sign_in',
usernameField: 'username',
passwordField: 'password',
username: 'root',
}),
excludedUrls: () => ['http://test.local/sign_out', 'http://test.local/send_mail'],
requestHeaders: () => 'log-identifier: dast-active-scan',
},
};
export default new VueApollo({ export default new VueApollo({
defaultClient: createDefaultClient({}, { assumeImmutableResults: true }), defaultClient: createDefaultClient(resolvers, { assumeImmutableResults: true }),
}); });
<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 {
...@@ -65,8 +64,8 @@ export default { ...@@ -65,8 +64,8 @@ export default {
showValidationOrInEditMode() { showValidationOrInEditMode() {
return this.showValidation || this.isEditMode; return this.showValidation || this.isEditMode;
}, },
sensitiveFieldPlaceholder() { passwordFieldPlaceholder() {
return this.isEditMode ? __('[Unchanged]') : ''; return this.isEditMode ? '••••••••' : '';
}, },
}, },
watch: { watch: {
...@@ -132,7 +131,7 @@ export default { ...@@ -132,7 +131,7 @@ export default {
autocomplete="off" autocomplete="off"
name="password" name="password"
type="password" type="password"
:placeholder="sensitiveFieldPlaceholder" :placeholder="passwordFieldPlaceholder"
:required="isSensitiveFieldRequired" :required="isSensitiveFieldRequired"
:state="form.fields.password.state" :state="form.fields.password.state"
/> />
......
...@@ -65,8 +65,7 @@ export default { ...@@ -65,8 +65,7 @@ export default {
}, },
}, },
data() { data() {
const { name = '', targetUrl = '', excludedUrls = [], requestHeaders = '', auth = {} } = const { name = '', targetUrl = '', excludedUrls = [], auth = {} } = this.siteProfile || {};
this.siteProfile || {};
const form = { const form = {
state: false, state: false,
...@@ -80,7 +79,7 @@ export default { ...@@ -80,7 +79,7 @@ export default {
skipValidation: true, skipValidation: true,
}), }),
requestHeaders: initFormField({ requestHeaders: initFormField({
value: requestHeaders, value: '',
required: false, required: false,
skipValidation: true, skipValidation: true,
}), }),
...@@ -108,6 +107,9 @@ export default { ...@@ -108,6 +107,9 @@ export default {
isEdit() { isEdit() {
return Boolean(this.siteProfile?.id); return Boolean(this.siteProfile?.id);
}, },
hasRequestHeaders() {
return Boolean(this.siteProfile?.requestHeaders);
},
i18n() { i18n() {
const { isEdit } = this; const { isEdit } = this;
return { return {
...@@ -138,8 +140,10 @@ export default { ...@@ -138,8 +140,10 @@ export default {
tooltip: s__( tooltip: s__(
'DastProfiles|Request header names and values. Headers are added to every request made by DAST.', 'DastProfiles|Request header names and values. Headers are added to every request made by DAST.',
), ),
// eslint-disable-next-line @gitlab/require-i18n-strings placeholder: this.hasRequestHeaders
placeholder: 'Cache-control: no-cache, User-Agent: DAST/1.0', ? __('[Redacted]')
: // eslint-disable-next-line @gitlab/require-i18n-strings
'Cache-control: no-cache, User-Agent: DAST/1.0',
}, },
}; };
}, },
......
...@@ -12,6 +12,7 @@ import dastSiteProfilesQuery from 'ee/security_configuration/dast_profiles/graph ...@@ -12,6 +12,7 @@ import dastSiteProfilesQuery from 'ee/security_configuration/dast_profiles/graph
import { useLocalStorageSpy } from 'helpers/local_storage_helper'; import { useLocalStorageSpy } from 'helpers/local_storage_helper';
import createApolloProvider from 'helpers/mock_apollo_helper'; import createApolloProvider from 'helpers/mock_apollo_helper';
import { stubComponent } from 'helpers/stub_component'; import { stubComponent } from 'helpers/stub_component';
import waitForPromises from 'helpers/wait_for_promises';
import { redirectTo, setUrlParams } from '~/lib/utils/url_utility'; import { redirectTo, setUrlParams } from '~/lib/utils/url_utility';
import RefSelector from '~/ref/components/ref_selector.vue'; import RefSelector from '~/ref/components/ref_selector.vue';
import LocalStorageSync from '~/vue_shared/components/local_storage_sync.vue'; import LocalStorageSync from '~/vue_shared/components/local_storage_sync.vue';
...@@ -113,7 +114,6 @@ describe('OnDemandScansForm', () => { ...@@ -113,7 +114,6 @@ describe('OnDemandScansForm', () => {
dastSiteProfiles: jest.fn().mockResolvedValue(responses.dastSiteProfiles()), dastSiteProfiles: jest.fn().mockResolvedValue(responses.dastSiteProfiles()),
...handlers, ...handlers,
}; };
return createApolloProvider([ return createApolloProvider([
[dastScannerProfilesQuery, requestHandlers.dastScannerProfiles], [dastScannerProfilesQuery, requestHandlers.dastScannerProfiles],
[dastSiteProfilesQuery, requestHandlers.dastSiteProfiles], [dastSiteProfilesQuery, requestHandlers.dastSiteProfiles],
...@@ -499,13 +499,15 @@ describe('OnDemandScansForm', () => { ...@@ -499,13 +499,15 @@ describe('OnDemandScansForm', () => {
`('when there is a single $profileType profile', ({ query, selector, profiles }) => { `('when there is a single $profileType profile', ({ query, selector, profiles }) => {
const [profile] = profiles; const [profile] = profiles;
beforeEach(() => { beforeEach(async () => {
mountShallowSubject( mountShallowSubject(
{}, {},
{ {
[query]: jest.fn().mockResolvedValue(responses[query]([profile])), [query]: jest.fn().mockResolvedValue(responses[query]([profile])),
}, },
); );
await waitForPromises();
}); });
it('automatically selects the only available profile', () => { it('automatically selects the only available profile', () => {
...@@ -534,14 +536,17 @@ describe('OnDemandScansForm', () => { ...@@ -534,14 +536,17 @@ describe('OnDemandScansForm', () => {
it('renders all fields correctly', async () => { it('renders all fields correctly', async () => {
await selectSiteProfile(authEnabledProfile); await selectSiteProfile(authEnabledProfile);
const summary = subject.find(SiteProfileSelector).text(); const summary = subject.find(SiteProfileSelector).text();
const defaultPassword = '••••••••';
const defaultRequestHeaders = '[Redacted]';
expect(summary).toMatch(authEnabledProfile.targetUrl); expect(summary).toMatch(authEnabledProfile.targetUrl);
expect(summary).toMatch(authEnabledProfile.excludedUrls.join(',')); expect(summary).toMatch(authEnabledProfile.excludedUrls.join(','));
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);
expect(summary).toMatch(authEnabledProfile.auth.usernameField); expect(summary).toMatch(authEnabledProfile.auth.usernameField);
expect(summary).toMatch(authEnabledProfile.auth.passwordField); expect(summary).toMatch(authEnabledProfile.auth.passwordField);
expect(summary).toMatch(defaultPassword);
expect(summary).toMatch(defaultRequestHeaders);
}); });
}); });
......
...@@ -64,6 +64,10 @@ export const siteProfiles = [ ...@@ -64,6 +64,10 @@ export const siteProfiles = [
validationStatus: 'PASSED_VALIDATION', validationStatus: 'PASSED_VALIDATION',
auth: { auth: {
enabled: false, enabled: false,
url: 'https://foo.com/login',
usernameField: 'username',
passwordField: 'password',
username: 'admin',
}, },
excludedUrls: ['https://bar.com/logout'], excludedUrls: ['https://bar.com/logout'],
requestHeaders: 'auth: gitlab-dast', requestHeaders: 'auth: gitlab-dast',
......
...@@ -175,6 +175,33 @@ describe('DastSiteProfileForm', () => { ...@@ -175,6 +175,33 @@ describe('DastSiteProfileForm', () => {
expect(findExcludedUrlsInput().attributes('maxlength')).toBe('2048'); expect(findExcludedUrlsInput().attributes('maxlength')).toBe('2048');
expect(findRequestHeadersInput().attributes('maxlength')).toBe('2048'); expect(findRequestHeadersInput().attributes('maxlength')).toBe('2048');
}); });
describe('should have correct placeholders', () => {
const defaultPlaceholder = 'Cache-control: no-cache, User-Agent: DAST/1.0';
it('when creating a new profile', async () => {
expect(findRequestHeadersInput().attributes('placeholder')).toBe(defaultPlaceholder);
});
it('when updating an existing profile with no request headers set', () => {
createFullComponent({
propsData: {
siteProfile: { ...siteProfileOne, requestHeaders: '' },
},
});
expect(findRequestHeadersInput().attributes('placeholder')).toBe(defaultPlaceholder);
});
it('when updating an existing profile', () => {
createFullComponent({
propsData: {
siteProfile: siteProfileOne,
},
});
expect(findRequestHeadersInput().attributes('placeholder')).toBe('[Redacted]');
expect(findByNameAttribute('password').attributes('placeholder')).toBe('••••••••');
});
});
}); });
describe.each` describe.each`
......
...@@ -35145,7 +35145,7 @@ msgstr "" ...@@ -35145,7 +35145,7 @@ msgstr ""
msgid "[No reason]" msgid "[No reason]"
msgstr "" msgstr ""
msgid "[Unchanged]" msgid "[Redacted]"
msgstr "" msgstr ""
msgid "`end_time` should not exceed one month after `start_time`" msgid "`end_time` should not exceed one month after `start_time`"
......
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