Commit e3d22298 authored by Scott Hampton's avatar Scott Hampton

Merge branch 'djadmin-update-placeholders-dast-profile' into 'master'

Update placeholders for DAST Profile sensitive fields

See merge request gitlab-org/gitlab!56496
parents 656a918a f4e5a483
...@@ -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', SITE_PROFILES_QUERY,
this.glFeatures.securityDastSiteProfilesAdditionalFields ),
? 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`
......
...@@ -35160,7 +35160,7 @@ msgstr "" ...@@ -35160,7 +35160,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