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 {
ERROR_MESSAGES,
SCANNER_PROFILES_QUERY,
SITE_PROFILES_QUERY,
SITE_PROFILES_EXTENDED_QUERY,
TYPE_SITE_PROFILE,
TYPE_SCANNER_PROFILE,
} from '../settings';
......@@ -101,15 +100,11 @@ export default {
'selectedScannerProfileId',
SCANNER_PROFILES_QUERY,
),
siteProfiles() {
return createProfilesApolloOptions(
'siteProfiles',
'selectedSiteProfileId',
this.glFeatures.securityDastSiteProfilesAdditionalFields
? SITE_PROFILES_EXTENDED_QUERY
: SITE_PROFILES_QUERY,
);
},
siteProfiles: createProfilesApolloOptions(
'siteProfiles',
'selectedSiteProfileId',
SITE_PROFILES_QUERY,
),
},
inject: {
dastSiteValidationDocsPath: {
......@@ -233,6 +228,9 @@ export default {
selectedSiteProfileId,
};
},
hasExcludedUrls() {
return this.selectedSiteProfile.excludedUrls?.length > 0;
},
},
created() {
const params = queryToObject(window.location.search);
......@@ -499,6 +497,10 @@ export default {
:label="s__('DastProfiles|Username')"
:value="selectedSiteProfile.auth.username"
/>
<profile-selector-summary-cell
:label="s__('DastProfiles|Password')"
value="••••••••"
/>
</div>
<div class="row">
<profile-selector-summary-cell
......@@ -513,12 +515,14 @@ export default {
</template>
<div class="row">
<profile-selector-summary-cell
v-if="hasExcludedUrls"
:label="s__('DastProfiles|Excluded URLs')"
:value="selectedSiteProfile.excludedUrls.join($options.EXCLUDED_URLS_SEPARATOR)"
/>
<profile-selector-summary-cell
v-if="selectedSiteProfile.requestHeaders"
:label="s__('DastProfiles|Request headers')"
:value="selectedSiteProfile.requestHeaders"
:value="__('[Redacted]')"
/>
</div>
</template>
......
import Vue from 'vue';
import VueApollo from 'vue-apollo';
import { resolvers } from 'ee/security_configuration/dast_profiles/graphql/provider';
import createDefaultClient from '~/lib/graphql';
Vue.use(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 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';
export const ERROR_RUN_SCAN = 'ERROR_RUN_SCAN';
......@@ -29,10 +28,5 @@ export const SITE_PROFILES_QUERY = {
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_SCANNER_PROFILE = 'DastScannerProfile';
......@@ -17,6 +17,15 @@ query DastSiteProfiles($fullPath: ID!, $after: String, $before: String, $first:
editPath
validationStatus
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';
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({
defaultClient: createDefaultClient({}, { assumeImmutableResults: true }),
defaultClient: createDefaultClient(resolvers, { assumeImmutableResults: true }),
});
<script>
import { GlFormGroup, GlFormInput, GlFormCheckbox } from '@gitlab/ui';
import { initFormField } from 'ee/security_configuration/utils';
import { __ } from '~/locale';
import validation from '~/vue_shared/directives/validation';
export default {
......@@ -65,8 +64,8 @@ export default {
showValidationOrInEditMode() {
return this.showValidation || this.isEditMode;
},
sensitiveFieldPlaceholder() {
return this.isEditMode ? __('[Unchanged]') : '';
passwordFieldPlaceholder() {
return this.isEditMode ? '••••••••' : '';
},
},
watch: {
......@@ -132,7 +131,7 @@ export default {
autocomplete="off"
name="password"
type="password"
:placeholder="sensitiveFieldPlaceholder"
:placeholder="passwordFieldPlaceholder"
:required="isSensitiveFieldRequired"
:state="form.fields.password.state"
/>
......
......@@ -65,8 +65,7 @@ export default {
},
},
data() {
const { name = '', targetUrl = '', excludedUrls = [], requestHeaders = '', auth = {} } =
this.siteProfile || {};
const { name = '', targetUrl = '', excludedUrls = [], auth = {} } = this.siteProfile || {};
const form = {
state: false,
......@@ -80,7 +79,7 @@ export default {
skipValidation: true,
}),
requestHeaders: initFormField({
value: requestHeaders,
value: '',
required: false,
skipValidation: true,
}),
......@@ -108,6 +107,9 @@ export default {
isEdit() {
return Boolean(this.siteProfile?.id);
},
hasRequestHeaders() {
return Boolean(this.siteProfile?.requestHeaders);
},
i18n() {
const { isEdit } = this;
return {
......@@ -138,8 +140,10 @@ export default {
tooltip: s__(
'DastProfiles|Request header names and values. Headers are added to every request made by DAST.',
),
// eslint-disable-next-line @gitlab/require-i18n-strings
placeholder: 'Cache-control: no-cache, User-Agent: DAST/1.0',
placeholder: this.hasRequestHeaders
? __('[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
import { useLocalStorageSpy } from 'helpers/local_storage_helper';
import createApolloProvider from 'helpers/mock_apollo_helper';
import { stubComponent } from 'helpers/stub_component';
import waitForPromises from 'helpers/wait_for_promises';
import { redirectTo, setUrlParams } from '~/lib/utils/url_utility';
import RefSelector from '~/ref/components/ref_selector.vue';
import LocalStorageSync from '~/vue_shared/components/local_storage_sync.vue';
......@@ -113,7 +114,6 @@ describe('OnDemandScansForm', () => {
dastSiteProfiles: jest.fn().mockResolvedValue(responses.dastSiteProfiles()),
...handlers,
};
return createApolloProvider([
[dastScannerProfilesQuery, requestHandlers.dastScannerProfiles],
[dastSiteProfilesQuery, requestHandlers.dastSiteProfiles],
......@@ -499,13 +499,15 @@ describe('OnDemandScansForm', () => {
`('when there is a single $profileType profile', ({ query, selector, profiles }) => {
const [profile] = profiles;
beforeEach(() => {
beforeEach(async () => {
mountShallowSubject(
{},
{
[query]: jest.fn().mockResolvedValue(responses[query]([profile])),
},
);
await waitForPromises();
});
it('automatically selects the only available profile', () => {
......@@ -534,14 +536,17 @@ describe('OnDemandScansForm', () => {
it('renders all fields correctly', async () => {
await selectSiteProfile(authEnabledProfile);
const summary = subject.find(SiteProfileSelector).text();
const defaultPassword = '••••••••';
const defaultRequestHeaders = '[Redacted]';
expect(summary).toMatch(authEnabledProfile.targetUrl);
expect(summary).toMatch(authEnabledProfile.excludedUrls.join(','));
expect(summary).toMatch(authEnabledProfile.requestHeaders);
expect(summary).toMatch(authEnabledProfile.auth.url);
expect(summary).toMatch(authEnabledProfile.auth.username);
expect(summary).toMatch(authEnabledProfile.auth.usernameField);
expect(summary).toMatch(authEnabledProfile.auth.passwordField);
expect(summary).toMatch(defaultPassword);
expect(summary).toMatch(defaultRequestHeaders);
});
});
......
......@@ -64,6 +64,10 @@ export const siteProfiles = [
validationStatus: 'PASSED_VALIDATION',
auth: {
enabled: false,
url: 'https://foo.com/login',
usernameField: 'username',
passwordField: 'password',
username: 'admin',
},
excludedUrls: ['https://bar.com/logout'],
requestHeaders: 'auth: gitlab-dast',
......
......@@ -175,6 +175,33 @@ describe('DastSiteProfileForm', () => {
expect(findExcludedUrlsInput().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`
......
......@@ -35145,7 +35145,7 @@ msgstr ""
msgid "[No reason]"
msgstr ""
msgid "[Unchanged]"
msgid "[Redacted]"
msgstr ""
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