Commit c3577731 authored by Paul Slaughter's avatar Paul Slaughter

Merge branch '247106-dast-site-text-file-validation-paths' into 'master'

DAST site profiles: support validation path

See merge request gitlab-org/gitlab!42906
parents cdb43d98 0f692c15
...@@ -16,7 +16,7 @@ function decodeUrlParameter(val) { ...@@ -16,7 +16,7 @@ function decodeUrlParameter(val) {
return decodeURIComponent(val.replace(/\+/g, '%20')); return decodeURIComponent(val.replace(/\+/g, '%20'));
} }
function cleanLeadingSeparator(path) { export function cleanLeadingSeparator(path) {
return path.replace(PATH_SEPARATOR_LEADING_REGEX, ''); return path.replace(PATH_SEPARATOR_LEADING_REGEX, '');
} }
...@@ -435,3 +435,12 @@ export function getHTTPProtocol(url) { ...@@ -435,3 +435,12 @@ export function getHTTPProtocol(url) {
const protocol = url.split(':'); const protocol = url.split(':');
return protocol.length > 1 ? protocol[0] : undefined; return protocol.length > 1 ? protocol[0] : undefined;
} }
/**
* Strips the filename from the given path by removing every non-slash character from the end of the
* passed parameter.
* @param {string} path
*/
export function stripPathTail(path = '') {
return path.replace(/[^/]+$/, '');
}
...@@ -13,6 +13,7 @@ import { ...@@ -13,6 +13,7 @@ import {
GlLoadingIcon, GlLoadingIcon,
} from '@gitlab/ui'; } from '@gitlab/ui';
import download from '~/lib/utils/downloader'; import download from '~/lib/utils/downloader';
import { cleanLeadingSeparator, joinPaths, stripPathTail } from '~/lib/utils/url_utility';
import { import {
DAST_SITE_VALIDATION_METHOD_TEXT_FILE, DAST_SITE_VALIDATION_METHOD_TEXT_FILE,
DAST_SITE_VALIDATION_METHODS, DAST_SITE_VALIDATION_METHODS,
...@@ -91,9 +92,23 @@ export default { ...@@ -91,9 +92,23 @@ export default {
isValidating: false, isValidating: false,
hasValidationError: false, hasValidationError: false,
validationMethod: DAST_SITE_VALIDATION_METHOD_TEXT_FILE, validationMethod: DAST_SITE_VALIDATION_METHOD_TEXT_FILE,
validationPath: '',
}; };
}, },
computed: { computed: {
urlObject() {
try {
return new URL(this.targetUrl);
} catch {
return {};
}
},
origin() {
return this.urlObject.origin ? `${this.urlObject.origin}/` : '';
},
path() {
return cleanLeadingSeparator(this.urlObject.pathname || '');
},
isTextFileValidation() { isTextFileValidation() {
return this.validationMethod === DAST_SITE_VALIDATION_METHOD_TEXT_FILE; return this.validationMethod === DAST_SITE_VALIDATION_METHOD_TEXT_FILE;
}, },
...@@ -109,7 +124,17 @@ export default { ...@@ -109,7 +124,17 @@ export default {
this.hasValidationError = false; this.hasValidationError = false;
}, },
}, },
created() {
this.updateValidationPath();
this.unsubscribe = this.$watch(() => this.token, this.updateValidationPath);
},
methods: { methods: {
updateValidationPath() {
this.validationPath = joinPaths(stripPathTail(this.path), this.textFileName);
},
onValidationPathInput() {
this.unsubscribe();
},
downloadTextFile() { downloadTextFile() {
download({ fileName: this.textFileName, fileData: btoa(this.token) }); download({ fileName: this.textFileName, fileData: btoa(this.token) });
}, },
...@@ -127,6 +152,7 @@ export default { ...@@ -127,6 +152,7 @@ export default {
variables: { variables: {
projectFullPath: this.fullPath, projectFullPath: this.fullPath,
dastSiteTokenId: this.tokenId, dastSiteTokenId: this.tokenId,
validationPath: this.validationPath,
strategy: this.validationMethod, strategy: this.validationMethod,
}, },
}); });
...@@ -186,9 +212,16 @@ export default { ...@@ -186,9 +212,16 @@ export default {
<gl-form-group :label="locationStepLabel" class="mw-460"> <gl-form-group :label="locationStepLabel" class="mw-460">
<gl-form-input-group> <gl-form-input-group>
<template #prepend> <template #prepend>
<gl-input-group-text>{{ targetUrl }}</gl-input-group-text> <gl-input-group-text data-testid="dast-site-validation-path-prefix">{{
origin
}}</gl-input-group-text>
</template> </template>
<gl-form-input class="gl-bg-white!" /> <gl-form-input
v-model="validationPath"
class="gl-bg-white!"
data-testid="dast-site-validation-path-input"
@input="onValidationPathInput()"
/>
</gl-form-input-group> </gl-form-input-group>
</gl-form-group> </gl-form-group>
......
mutation dastSiteValidationCreate( mutation dastSiteValidationCreate(
$projectFullPath: ID! $projectFullPath: ID!
$dastSiteTokenId: DastSiteTokenID! $dastSiteTokenId: DastSiteTokenID!
$validationPath: String!
$validationStrategy: DastSiteValidationStrategyEnum $validationStrategy: DastSiteValidationStrategyEnum
) { ) {
dastSiteValidationCreate( dastSiteValidationCreate(
input: { input: {
projectFullPath: $projectFullPath fullPath: $projectFullPath
dastSiteTokenId: $dastSiteTokenId dastSiteTokenId: $dastSiteTokenId
validationPath: $validationPath
strategy: $validationStrategy strategy: $validationStrategy
} }
) { ) {
id
status status
errors errors
} }
......
...@@ -18,11 +18,13 @@ localVue.use(VueApollo); ...@@ -18,11 +18,13 @@ localVue.use(VueApollo);
const fullPath = 'group/project'; const fullPath = 'group/project';
const targetUrl = 'https://example.com/'; const targetUrl = 'https://example.com/';
const tokenId = '1';
const token = 'validation-token-123'; const token = 'validation-token-123';
const defaultProps = { const defaultProps = {
fullPath, fullPath,
targetUrl, targetUrl,
tokenId,
token, token,
}; };
...@@ -82,9 +84,11 @@ describe('DastSiteValidation', () => { ...@@ -82,9 +84,11 @@ describe('DastSiteValidation', () => {
const createFullComponent = componentFactory(mount); const createFullComponent = componentFactory(mount);
const withinComponent = () => within(wrapper.element); const withinComponent = () => within(wrapper.element);
const findDownloadButton = () => const findByTestId = id => wrapper.find(`[data-testid="${id}"`);
wrapper.find('[data-testid="download-dast-text-file-validation-button"]'); const findDownloadButton = () => findByTestId('download-dast-text-file-validation-button');
const findValidateButton = () => wrapper.find('[data-testid="validate-dast-site-button"]'); const findValidationPathPrefix = () => findByTestId('dast-site-validation-path-prefix');
const findValidationPathInput = () => findByTestId('dast-site-validation-path-input');
const findValidateButton = () => findByTestId('validate-dast-site-button');
const findLoadingIcon = () => wrapper.find(GlLoadingIcon); const findLoadingIcon = () => wrapper.find(GlLoadingIcon);
const findErrorMessage = () => const findErrorMessage = () =>
withinComponent().queryByText( withinComponent().queryByText(
...@@ -121,17 +125,58 @@ describe('DastSiteValidation', () => { ...@@ -121,17 +125,58 @@ describe('DastSiteValidation', () => {
}); });
describe('text file validation', () => { describe('text file validation', () => {
beforeEach(() => {
createComponent();
});
it('clicking on the download button triggers a download of a text file containing the token', () => { it('clicking on the download button triggers a download of a text file containing the token', () => {
createComponent();
findDownloadButton().vm.$emit('click'); findDownloadButton().vm.$emit('click');
expect(download).toHaveBeenCalledWith({ expect(download).toHaveBeenCalledWith({
fileName: `GitLab-DAST-Site-Validation-${token}.txt`, fileName: `GitLab-DAST-Site-Validation-${token}.txt`,
fileData: btoa(token), fileData: btoa(token),
}); });
}); });
describe.each`
targetUrl | expectedPrefix | expectedValue
${'https://example.com'} | ${'https://example.com/'} | ${'GitLab-DAST-Site-Validation-validation-token-123.txt'}
${'https://example.com/'} | ${'https://example.com/'} | ${'GitLab-DAST-Site-Validation-validation-token-123.txt'}
${'https://example.com/foo/bar'} | ${'https://example.com/'} | ${'foo/GitLab-DAST-Site-Validation-validation-token-123.txt'}
${'https://example.com/foo/bar/'} | ${'https://example.com/'} | ${'foo/bar/GitLab-DAST-Site-Validation-validation-token-123.txt'}
${'https://sub.example.com/foo/bar'} | ${'https://sub.example.com/'} | ${'foo/GitLab-DAST-Site-Validation-validation-token-123.txt'}
${'https://example.com/foo/index.html'} | ${'https://example.com/'} | ${'foo/GitLab-DAST-Site-Validation-validation-token-123.txt'}
${'https://example.com/foo/?bar="baz"'} | ${'https://example.com/'} | ${'foo/GitLab-DAST-Site-Validation-validation-token-123.txt'}
${'https://example.com:3000'} | ${'https://example.com:3000/'} | ${'GitLab-DAST-Site-Validation-validation-token-123.txt'}
${''} | ${''} | ${'GitLab-DAST-Site-Validation-validation-token-123.txt'}
`(
'validation path input when target URL is $targetUrl',
({ targetUrl: url, expectedPrefix, expectedValue }) => {
beforeEach(() => {
createFullComponent({
propsData: {
targetUrl: url,
},
});
});
it(`prefix is set to ${expectedPrefix}`, () => {
expect(findValidationPathPrefix().text()).toBe(expectedPrefix);
});
it(`input value defaults to ${expectedValue}`, () => {
expect(findValidationPathInput().element.value).toBe(expectedValue);
});
},
);
it("input value isn't automatically updated if it has been changed manually", async () => {
createFullComponent();
const customValidationPath = 'custom/validation/path.txt';
findValidationPathInput().setValue(customValidationPath);
await wrapper.setProps({
token: 'a-completely-new-token',
});
expect(findValidationPathInput().element.value).toBe(customValidationPath);
});
}); });
describe('validation', () => { describe('validation', () => {
...@@ -149,6 +194,15 @@ describe('DastSiteValidation', () => { ...@@ -149,6 +194,15 @@ describe('DastSiteValidation', () => {
expect(wrapper.text()).toContain('Validating...'); expect(wrapper.text()).toContain('Validating...');
}); });
it('triggers the dastSiteValidationCreate GraphQL mutation', () => {
expect(requestHandlers.dastSiteValidationCreate).toHaveBeenCalledWith({
projectFullPath: fullPath,
dastSiteTokenId: tokenId,
validationPath: wrapper.vm.validationPath,
strategy: wrapper.vm.validationMethod,
});
});
it('on success, emits success event', async () => { it('on success, emits success event', async () => {
await waitForPromises(); await waitForPromises();
......
...@@ -664,6 +664,19 @@ describe('URL utility', () => { ...@@ -664,6 +664,19 @@ describe('URL utility', () => {
}); });
}); });
describe('cleanLeadingSeparator', () => {
it.each`
path | expected
${'/foo/bar'} | ${'foo/bar'}
${'foo/bar'} | ${'foo/bar'}
${'//foo/bar'} | ${'foo/bar'}
${'/./foo/bar'} | ${'./foo/bar'}
${''} | ${''}
`('$path becomes $expected', ({ path, expected }) => {
expect(urlUtils.cleanLeadingSeparator(path)).toBe(expected);
});
});
describe('joinPaths', () => { describe('joinPaths', () => {
it.each` it.each`
paths | expected paths | expected
...@@ -787,4 +800,18 @@ describe('URL utility', () => { ...@@ -787,4 +800,18 @@ describe('URL utility', () => {
expect(urlUtils.getHTTPProtocol(url)).toBe(expectation); expect(urlUtils.getHTTPProtocol(url)).toBe(expectation);
}); });
}); });
describe('stripPathTail', () => {
it.each`
path | expected
${''} | ${''}
${'index.html'} | ${''}
${'/'} | ${'/'}
${'/foo/bar'} | ${'/foo/'}
${'/foo/bar/'} | ${'/foo/bar/'}
${'/foo/bar/index.html'} | ${'/foo/bar/'}
`('strips the filename from $path => $expected', ({ path, expected }) => {
expect(urlUtils.stripPathTail(path)).toBe(expected);
});
});
}); });
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