Commit 747a7b6f authored by Jose Ivan Vargas's avatar Jose Ivan Vargas

Merge branch '333233-api-fuzzing-snippet-improvement' into 'master'

Move API fuzzing YAML generation to the client

See merge request gitlab-org/gitlab!74514
parents 0b887c2b 6e204daf
...@@ -13,6 +13,7 @@ export const initApiFuzzingConfiguration = () => { ...@@ -13,6 +13,7 @@ export const initApiFuzzingConfiguration = () => {
const { const {
securityConfigurationPath, securityConfigurationPath,
fullPath, fullPath,
gitlabCiYamlEditPath,
apiFuzzingDocumentationPath, apiFuzzingDocumentationPath,
apiFuzzingAuthenticationDocumentationPath, apiFuzzingAuthenticationDocumentationPath,
ciVariablesDocumentationPath, ciVariablesDocumentationPath,
...@@ -26,6 +27,7 @@ export const initApiFuzzingConfiguration = () => { ...@@ -26,6 +27,7 @@ export const initApiFuzzingConfiguration = () => {
provide: { provide: {
securityConfigurationPath, securityConfigurationPath,
fullPath, fullPath,
gitlabCiYamlEditPath,
apiFuzzingDocumentationPath, apiFuzzingDocumentationPath,
apiFuzzingAuthenticationDocumentationPath, apiFuzzingAuthenticationDocumentationPath,
ciVariablesDocumentationPath, ciVariablesDocumentationPath,
......
...@@ -10,7 +10,6 @@ import { ...@@ -10,7 +10,6 @@ import {
GlLink, GlLink,
GlSprintf, GlSprintf,
} from '@gitlab/ui'; } from '@gitlab/ui';
import * as Sentry from '@sentry/browser';
import ConfigurationSnippetModal from 'ee/security_configuration/components/configuration_snippet_modal.vue'; import ConfigurationSnippetModal from 'ee/security_configuration/components/configuration_snippet_modal.vue';
import { CONFIGURATION_SNIPPET_MODAL_ID } from 'ee/security_configuration/components/constants'; import { CONFIGURATION_SNIPPET_MODAL_ID } from 'ee/security_configuration/components/constants';
import { isEmptyValue } from '~/lib/utils/forms'; import { isEmptyValue } from '~/lib/utils/forms';
...@@ -20,8 +19,7 @@ import DropdownInput from '../../components/dropdown_input.vue'; ...@@ -20,8 +19,7 @@ import DropdownInput from '../../components/dropdown_input.vue';
import DynamicFields from '../../components/dynamic_fields.vue'; import DynamicFields from '../../components/dynamic_fields.vue';
import FormInput from '../../components/form_input.vue'; import FormInput from '../../components/form_input.vue';
import { SCAN_MODES } from '../constants'; import { SCAN_MODES } from '../constants';
import apiFuzzingCiConfigurationCreate from '../graphql/api_fuzzing_ci_configuration_create.mutation.graphql'; import { buildConfigurationSnippet } from '../utils';
import { insertTips } from '../utils';
export default { export default {
CONFIGURATION_SNIPPET_MODAL_ID, CONFIGURATION_SNIPPET_MODAL_ID,
...@@ -44,6 +42,7 @@ export default { ...@@ -44,6 +42,7 @@ export default {
inject: [ inject: [
'securityConfigurationPath', 'securityConfigurationPath',
'fullPath', 'fullPath',
'gitlabCiYamlEditPath',
'apiFuzzingAuthenticationDocumentationPath', 'apiFuzzingAuthenticationDocumentationPath',
'ciVariablesDocumentationPath', 'ciVariablesDocumentationPath',
'projectCiSettingsPath', 'projectCiSettingsPath',
...@@ -57,8 +56,6 @@ export default { ...@@ -57,8 +56,6 @@ export default {
}, },
data() { data() {
return { return {
isLoading: false,
isErrorVisible: false,
targetUrl: { targetUrl: {
field: 'targetUrl', field: 'targetUrl',
label: s__('APIFuzzing|Target URL'), label: s__('APIFuzzing|Target URL'),
...@@ -119,7 +116,6 @@ export default { ...@@ -119,7 +116,6 @@ export default {
}), }),
), ),
}, },
ciYamlEditPath: '',
configurationYaml: '', configurationYaml: '',
}; };
}, },
...@@ -159,80 +155,23 @@ export default { ...@@ -159,80 +155,23 @@ export default {
} }
return fields.some(({ value }) => isEmptyValue(value)); return fields.some(({ value }) => isEmptyValue(value));
}, },
configurationYamlWithTips() {
if (!this.configurationYaml) {
return '';
}
return insertTips(this.configurationYaml, [
{
tip: s__('APIFuzzing|Tip: Insert this part below all stages'),
// eslint-disable-next-line @gitlab/require-i18n-strings
token: 'stages:',
},
{
tip: s__('APIFuzzing|Tip: Insert this part below all include'),
// eslint-disable-next-line @gitlab/require-i18n-strings
token: 'include:',
},
{
tip: s__(
'APIFuzzing|Tip: Insert the following variables anywhere below stages and include',
),
// eslint-disable-next-line @gitlab/require-i18n-strings
token: 'variables:',
},
]);
},
}, },
methods: { methods: {
async onSubmit() { onSubmit() {
this.isLoading = true; const options = {
this.dismissError(); projectPath: this.fullPath,
try { target: this.targetUrl.value,
const input = { scanMode: this.scanMode.value,
projectPath: this.fullPath, apiSpecificationFile: this.apiSpecificationFile.value,
target: this.targetUrl.value, scanProfile: this.scanProfile.value,
scanMode: this.scanMode.value, };
apiSpecificationFile: this.apiSpecificationFile.value, if (this.authenticationEnabled) {
scanProfile: this.scanProfile.value, const [authUsername, authPassword] = this.authenticationSettings;
}; options.authUsername = authUsername.value;
if (this.authenticationEnabled) { options.authPassword = authPassword.value;
const [authUsername, authPassword] = this.authenticationSettings;
input.authUsername = authUsername.value;
input.authPassword = authPassword.value;
}
const {
data: {
apiFuzzingCiConfigurationCreate: {
gitlabCiYamlEditPath,
configurationYaml,
errors = [],
},
},
} = await this.$apollo.mutate({
mutation: apiFuzzingCiConfigurationCreate,
variables: { input },
});
if (errors.length) {
this.showError();
} else {
this.ciYamlEditPath = gitlabCiYamlEditPath;
this.configurationYaml = configurationYaml;
this.$refs[CONFIGURATION_SNIPPET_MODAL_ID].show();
}
} catch (e) {
this.showError();
Sentry.captureException(e);
} finally {
this.isLoading = false;
} }
}, this.configurationYaml = buildConfigurationSnippet(options);
showError() { this.$refs[CONFIGURATION_SNIPPET_MODAL_ID].show();
this.isErrorVisible = true;
window.scrollTo({ top: 0 });
},
dismissError() {
this.isErrorVisible = false;
}, },
}, },
SCAN_MODES, SCAN_MODES,
...@@ -241,10 +180,6 @@ export default { ...@@ -241,10 +180,6 @@ export default {
<template> <template>
<form @submit.prevent="onSubmit"> <form @submit.prevent="onSubmit">
<gl-alert v-if="isErrorVisible" variant="danger" class="gl-mb-5" @dismiss="dismissError">
{{ s__('APIFuzzing|Code snippet could not be generated. Try again later.') }}
</gl-alert>
<form-input v-model="targetUrl.value" v-bind="targetUrl" class="gl-mb-7" /> <form-input v-model="targetUrl.value" v-bind="targetUrl" class="gl-mb-7" />
<dropdown-input v-model="scanMode.value" v-bind="scanMode" /> <dropdown-input v-model="scanMode.value" v-bind="scanMode" />
...@@ -312,7 +247,6 @@ export default { ...@@ -312,7 +247,6 @@ export default {
<gl-button <gl-button
:disabled="someFieldEmpty" :disabled="someFieldEmpty"
:loading="isLoading"
type="submit" type="submit"
variant="confirm" variant="confirm"
class="js-no-auto-disable" class="js-no-auto-disable"
...@@ -320,7 +254,6 @@ export default { ...@@ -320,7 +254,6 @@ export default {
>{{ s__('APIFuzzing|Generate code snippet') }}</gl-button >{{ s__('APIFuzzing|Generate code snippet') }}</gl-button
> >
<gl-button <gl-button
:disabled="isLoading"
:href="securityConfigurationPath" :href="securityConfigurationPath"
data-testid="api-fuzzing-configuration-cancel-button" data-testid="api-fuzzing-configuration-cancel-button"
>{{ __('Cancel') }}</gl-button >{{ __('Cancel') }}</gl-button
...@@ -328,8 +261,8 @@ export default { ...@@ -328,8 +261,8 @@ export default {
<configuration-snippet-modal <configuration-snippet-modal
:ref="$options.CONFIGURATION_SNIPPET_MODAL_ID" :ref="$options.CONFIGURATION_SNIPPET_MODAL_ID"
:ci-yaml-edit-url="ciYamlEditPath" :ci-yaml-edit-url="gitlabCiYamlEditPath"
:yaml="configurationYamlWithTips" :yaml="configurationYaml"
:redirect-param="$options.CODE_SNIPPET_SOURCE_API_FUZZING" :redirect-param="$options.CODE_SNIPPET_SOURCE_API_FUZZING"
scan-type="API Fuzzing" scan-type="API Fuzzing"
/> />
......
...@@ -26,3 +26,32 @@ export const SCAN_MODES = { ...@@ -26,3 +26,32 @@ export const SCAN_MODES = {
), ),
}, },
}; };
export const API_FUZZING_TARGET_URL_PLACEHOLDER = '#API_FUZZING_TARGET_URL_PLACEHOLDER';
export const API_FUZZING_SCAN_MODE_PLACEHOLDER = '#API_FUZZING_SCAN_MODE_PLACEHOLDER';
export const API_FUZZING_SPECIFICATION_FILE_PATH_PLACEHOLDER =
'#API_FUZZING_SPECIFICATION_FILE_PATH_PLACEHOLDER';
export const API_FUZZING_PROFILE_PLACEHOLDER = '#API_FUZZING_PROFILE_PLACEHOLDER';
export const API_FUZZING_AUTH_PASSWORD_VAR_PLACEHOLDER =
'#API_FUZZING_AUTH_PASSWORD_VAR_PLACEHOLDER';
export const API_FUZZING_AUTH_USERNAME_VAR_PLACEHOLDER =
'#API_FUZZING_AUTH_PASSWORD_VAR_PLACEHOLDER';
export const API_FUZZING_YAML_CONFIGURATION_TEMPLATE = `---
# ${s__('APIFuzzing|Tip: Insert this part below all stages')}
stages:
- fuzz
# ${s__('APIFuzzing|Tip: Insert this part below all include')}
include:
- template: Security/API-Fuzzing.gitlab-ci.yml
# ${s__('APIFuzzing|Tip: Insert the following variables anywhere below stages and include')}
variables:
FUZZAPI_TARGET_URL: ${API_FUZZING_TARGET_URL_PLACEHOLDER}
FUZZAPI_${API_FUZZING_SCAN_MODE_PLACEHOLDER}: ${API_FUZZING_SPECIFICATION_FILE_PATH_PLACEHOLDER}
FUZZAPI_PROFILE: ${API_FUZZING_PROFILE_PLACEHOLDER}`;
export const API_FUZZING_YAML_CONFIGURATION_AUTH_TEMPLATE = `
FUZZAPI_HTTP_USERNAME: "${API_FUZZING_AUTH_USERNAME_VAR_PLACEHOLDER}"
FUZZAPI_HTTP_PASSWORD: "${API_FUZZING_AUTH_PASSWORD_VAR_PLACEHOLDER}"`;
mutation createApiFuzzingCiConfiguration($input: ApiFuzzingCiConfigurationCreateInput!) {
apiFuzzingCiConfigurationCreate(input: $input) {
configurationYaml
gitlabCiYamlEditPath
errors
}
}
/* eslint-disable @gitlab/require-i18n-strings */ import {
import { isString } from 'lodash'; API_FUZZING_TARGET_URL_PLACEHOLDER,
API_FUZZING_SCAN_MODE_PLACEHOLDER,
API_FUZZING_SPECIFICATION_FILE_PATH_PLACEHOLDER,
API_FUZZING_PROFILE_PLACEHOLDER,
API_FUZZING_AUTH_PASSWORD_VAR_PLACEHOLDER,
API_FUZZING_AUTH_USERNAME_VAR_PLACEHOLDER,
API_FUZZING_YAML_CONFIGURATION_TEMPLATE,
API_FUZZING_YAML_CONFIGURATION_AUTH_TEMPLATE,
} from './constants';
export const insertTip = ({ snippet, tip, token }) => { export const buildConfigurationSnippet = ({
if (!isString(snippet)) { target,
throw new Error('snippet must be a string'); scanMode,
apiSpecificationFile,
scanProfile,
authUsername,
authPassword,
} = {}) => {
if (!target || !scanMode || !apiSpecificationFile || !scanProfile) {
return '';
} }
if (!isString(tip)) {
throw new Error('tip must be a string'); let template = API_FUZZING_YAML_CONFIGURATION_TEMPLATE.replace(
} API_FUZZING_TARGET_URL_PLACEHOLDER,
if (!isString(token)) { target,
throw new Error('token must be a string'); )
} .replace(API_FUZZING_SCAN_MODE_PLACEHOLDER, scanMode)
const lines = snippet.split('\n'); .replace(API_FUZZING_SPECIFICATION_FILE_PATH_PLACEHOLDER, apiSpecificationFile)
for (let i = 0; i < lines.length; i += 1) { .replace(API_FUZZING_PROFILE_PLACEHOLDER, scanProfile);
if (lines[i].includes(token)) {
const indent = lines[i].match(/^[ \t]+/)?.[0] ?? ''; if (authUsername && authPassword) {
lines[i] = lines[i].replace(token, `# ${tip}\n${indent}${token}`); template += API_FUZZING_YAML_CONFIGURATION_AUTH_TEMPLATE.replace(
break; API_FUZZING_AUTH_USERNAME_VAR_PLACEHOLDER,
} authUsername,
).replace(API_FUZZING_AUTH_PASSWORD_VAR_PLACEHOLDER, authPassword);
} }
return lines.join('\n'); return template;
}; };
export const insertTips = (snippet, tips = []) =>
tips.reduce(
(snippetWithTips, { tip, token }) => insertTip({ snippet: snippetWithTips, tip, token }),
snippet,
);
...@@ -5,6 +5,7 @@ module Projects::Security::ApiFuzzingConfigurationHelper ...@@ -5,6 +5,7 @@ module Projects::Security::ApiFuzzingConfigurationHelper
{ {
security_configuration_path: project_security_configuration_path(project), security_configuration_path: project_security_configuration_path(project),
full_path: project.full_path, full_path: project.full_path,
gitlab_ci_yaml_edit_path: Rails.application.routes.url_helpers.project_ci_pipeline_editor_path(project),
api_fuzzing_documentation_path: help_page_path('user/application_security/api_fuzzing/index'), api_fuzzing_documentation_path: help_page_path('user/application_security/api_fuzzing/index'),
api_fuzzing_authentication_documentation_path: help_page_path('user/application_security/api_fuzzing/index', anchor: 'authentication'), api_fuzzing_authentication_documentation_path: help_page_path('user/application_security/api_fuzzing/index', anchor: 'authentication'),
ci_variables_documentation_path: help_page_path('ci/variables/index'), ci_variables_documentation_path: help_page_path('ci/variables/index'),
......
import { GlAlert } from '@gitlab/ui';
import { mount } from '@vue/test-utils'; import { mount } from '@vue/test-utils';
import { merge } from 'lodash'; import { merge } from 'lodash';
import ConfigurationForm from 'ee/security_configuration/api_fuzzing/components/configuration_form.vue'; import ConfigurationForm from 'ee/security_configuration/api_fuzzing/components/configuration_form.vue';
...@@ -10,12 +9,12 @@ import DynamicFields from 'ee/security_configuration/components/dynamic_fields.v ...@@ -10,12 +9,12 @@ import DynamicFields from 'ee/security_configuration/components/dynamic_fields.v
import FormInput from 'ee/security_configuration/components/form_input.vue'; import FormInput from 'ee/security_configuration/components/form_input.vue';
import { stripTypenames } from 'helpers/graphql_helpers'; import { stripTypenames } from 'helpers/graphql_helpers';
import { extendedWrapper } from 'helpers/vue_test_utils_helper'; import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import waitForPromises from 'helpers/wait_for_promises';
import { CODE_SNIPPET_SOURCE_API_FUZZING } from '~/pipeline_editor/components/code_snippet_alert/constants'; import { CODE_SNIPPET_SOURCE_API_FUZZING } from '~/pipeline_editor/components/code_snippet_alert/constants';
import { import { apiFuzzingConfigurationQueryResponse } from '../mock_data';
apiFuzzingConfigurationQueryResponse,
createApiFuzzingConfigurationMutationResponse, jest.mock('ee/security_configuration/api_fuzzing/utils', () => ({
} from '../mock_data'; buildConfigurationSnippet: jest.fn().mockReturnValue('configuration YAML'),
}));
describe('EE - ApiFuzzingConfigurationForm', () => { describe('EE - ApiFuzzingConfigurationForm', () => {
let wrapper; let wrapper;
...@@ -24,7 +23,6 @@ describe('EE - ApiFuzzingConfigurationForm', () => { ...@@ -24,7 +23,6 @@ describe('EE - ApiFuzzingConfigurationForm', () => {
apiFuzzingConfigurationQueryResponse.data.project.apiFuzzingCiConfiguration, apiFuzzingConfigurationQueryResponse.data.project.apiFuzzingCiConfiguration,
); );
const findAlert = () => wrapper.find(GlAlert);
const findEnableAuthenticationCheckbox = () => const findEnableAuthenticationCheckbox = () =>
wrapper.findByTestId('api-fuzzing-enable-authentication-checkbox'); wrapper.findByTestId('api-fuzzing-enable-authentication-checkbox');
const findTargetUrlInput = () => wrapper.findAll(FormInput).at(0); const findTargetUrlInput = () => wrapper.findAll(FormInput).at(0);
...@@ -57,6 +55,7 @@ describe('EE - ApiFuzzingConfigurationForm', () => { ...@@ -57,6 +55,7 @@ describe('EE - ApiFuzzingConfigurationForm', () => {
{ {
provide: { provide: {
fullPath: 'namespace/project', fullPath: 'namespace/project',
gitlabCiYamlEditPath: '/ci/editor',
securityConfigurationPath: '/security/configuration', securityConfigurationPath: '/security/configuration',
apiFuzzingAuthenticationDocumentationPath: apiFuzzingAuthenticationDocumentationPath:
'api_fuzzing_authentication/documentation/path', 'api_fuzzing_authentication/documentation/path',
...@@ -223,80 +222,20 @@ describe('EE - ApiFuzzingConfigurationForm', () => { ...@@ -223,80 +222,20 @@ describe('EE - ApiFuzzingConfigurationForm', () => {
expect(findSubmitButton().props('disabled')).toBe(false); expect(findSubmitButton().props('disabled')).toBe(false);
}); });
it('triggers the createApiFuzzingConfiguration mutation on submit and opens the modal with the correct props', async () => { it('opens the modal with the correct props', async () => {
createWrapper(); createWrapper();
jest
.spyOn(wrapper.vm.$apollo, 'mutate')
.mockResolvedValue(createApiFuzzingConfigurationMutationResponse);
jest.spyOn(wrapper.vm.$refs[CONFIGURATION_SNIPPET_MODAL_ID], 'show'); jest.spyOn(wrapper.vm.$refs[CONFIGURATION_SNIPPET_MODAL_ID], 'show');
await setFormData(); await setFormData();
wrapper.find('form').trigger('submit'); wrapper.find('form').trigger('submit');
await waitForPromises(); await wrapper.vm.$nextTick();
expect(findAlert().exists()).toBe(false);
expect(wrapper.vm.$apollo.mutate).toHaveBeenCalled();
expect(wrapper.vm.$refs[CONFIGURATION_SNIPPET_MODAL_ID].show).toHaveBeenCalled(); expect(wrapper.vm.$refs[CONFIGURATION_SNIPPET_MODAL_ID].show).toHaveBeenCalled();
expect(findConfigurationSnippetModal().props()).toEqual({ expect(findConfigurationSnippetModal().props()).toEqual({
ciYamlEditUrl: ciYamlEditUrl: '/ci/editor',
createApiFuzzingConfigurationMutationResponse.data.apiFuzzingCiConfigurationCreate yaml: 'configuration YAML',
.gitlabCiYamlEditPath,
yaml: `---
# Tip: Insert this part below all stages
stages:
- fuzz
# Tip: Insert this part below all include
include:
- template: template.gitlab-ci.yml
# Tip: Insert the following variables anywhere below stages and include
variables:
- FOO: bar`,
redirectParam: CODE_SNIPPET_SOURCE_API_FUZZING, redirectParam: CODE_SNIPPET_SOURCE_API_FUZZING,
scanType: 'API Fuzzing', scanType: 'API Fuzzing',
}); });
}); });
it('shows an error on top-level error', async () => {
createWrapper({
mocks: {
$apollo: {
mutate: jest.fn().mockRejectedValue(),
},
},
});
await setFormData();
expect(findAlert().exists()).toBe(false);
wrapper.find('form').trigger('submit');
await waitForPromises();
expect(findAlert().exists()).toBe(true);
expect(window.scrollTo).toHaveBeenCalledWith({ top: 0 });
});
it('shows an error on error-as-data', async () => {
createWrapper({
mocks: {
$apollo: {
mutate: jest.fn().mockResolvedValue({
data: {
apiFuzzingCiConfigurationCreate: {
errors: ['error#1'],
},
},
}),
},
},
});
await setFormData();
expect(findAlert().exists()).toBe(false);
wrapper.find('form').trigger('submit');
await waitForPromises();
expect(findAlert().exists()).toBe(true);
expect(window.scrollTo).toHaveBeenCalledWith({ top: 0 });
});
}); });
}); });
...@@ -39,20 +39,3 @@ export const apiFuzzingConfigurationQueryResponse = { ...@@ -39,20 +39,3 @@ export const apiFuzzingConfigurationQueryResponse = {
}, },
}, },
}; };
export const createApiFuzzingConfigurationMutationResponse = {
data: {
apiFuzzingCiConfigurationCreate: {
configurationYaml: `---
stages:
- fuzz
include:
- template: template.gitlab-ci.yml
variables:
- FOO: bar`,
gitlabCiYamlEditPath: '/ci/editor',
errors: [],
__typename: 'ApiFuzzingCiConfiguration',
},
},
};
import { insertTip, insertTips } from 'ee/security_configuration/api_fuzzing/utils'; import { omit } from 'lodash';
import { buildConfigurationSnippet } from 'ee/security_configuration/api_fuzzing/utils';
const nonStringValues = [1, {}, null]; describe('buildConfigurationSnippet', () => {
const basicOptions = {
target: '/api/fuzzing/target/url',
scanMode: 'SCANMODE',
apiSpecificationFile: '/api/specification/file',
scanProfile: 'ScanProfile-1',
};
const authOptions = {
authUsername: '$USERNAME',
authPassword: '$PASSWORD',
};
describe('insertTip', () => { it('returns an empty string if basic options are missing', () => {
describe.each(['snippet', 'tip', 'token'])('throws when %s is', (arg) => { expect(buildConfigurationSnippet()).toBe('');
const validValues = {
snippet: 'snippet',
tip: 'tip',
token: 'token',
};
it.each(nonStringValues)('%s', (value) => {
expect(() => {
insertTip({ ...validValues, [arg]: value });
}).toThrowError(`${arg} must be a string`);
});
}); });
it('returns snippet as is if token can not be found', () => { it.each(Object.keys(basicOptions))(
const snippet = 'some code snippet'; 'returns an empty string if %s option is missing',
expect( (option) => {
insertTip({ const options = omit(basicOptions, option);
snippet,
token: 'ghost',
tip: 'a very helpful tip',
}),
).toBe(snippet);
});
const tip = 'a very helpful tip'; expect(buildConfigurationSnippet(options)).toBe('');
it.each` },
snippet | token | expected );
${'some code snippet'} | ${'code'} | ${`some # ${tip}\ncode snippet`}
${'some code snippet'} | ${'some'} | ${`# ${tip}\nsome code snippet`}
${'some code snippet'} | ${'e'} | ${`som# ${tip}\ne code snippet`}
`('inserts the tip on the line before the first found token', ({ snippet, token, expected }) => {
expect(
insertTip({
snippet,
token,
tip,
}),
).toBe(expected);
});
it('preserves indentation', () => { it('returns basic configuration YAML', () => {
const snippet = `--- expect(buildConfigurationSnippet(basicOptions)).toBe(`---
default: # Tip: Insert this part below all stages
artifacts: stages:
expire_in: 30 days`; - fuzz
const expected = `--- # Tip: Insert this part below all include
default: include:
artifacts: - template: Security/API-Fuzzing.gitlab-ci.yml
# a very helpful tip
expire_in: 30 days`;
expect( # Tip: Insert the following variables anywhere below stages and include
insertTip({ variables:
snippet, FUZZAPI_TARGET_URL: /api/fuzzing/target/url
token: 'expire_in:', FUZZAPI_SCANMODE: /api/specification/file
tip, FUZZAPI_PROFILE: ScanProfile-1`);
}),
).toBe(expected);
}); });
});
describe('insertTips', () => { it.each(Object.keys(authOptions))(
const validTips = [ 'does not include authentication variables if %s option is missing',
{ tip: 'Tip 1', token: 'default:' }, (option) => {
{ tip: 'Tip 2', token: 'artifacts:' }, const options = omit({ ...basicOptions, ...authOptions }, option);
{ tip: 'Tip 3', token: 'expire_in:' }, const output = buildConfigurationSnippet(options);
{ tip: 'Tip 4', token: 'tags:' },
];
it.each(nonStringValues)('throws if snippet is not a string', (snippet) => { expect(output).toBeTruthy();
expect(() => { expect(output).not.toContain('FUZZAPI_HTTP_PASSWORD');
insertTips(snippet, validTips); expect(output).not.toContain('FUZZAPI_HTTP_USERNAME');
}).toThrowError('snippet must be a string'); },
}); );
describe.each(['tip', 'token'])('throws if %s', (prop) => {
it.each(nonStringValues)('is %s', (value) => {
expect(() => {
insertTips('some code snippet', [
{
...validTips[0],
[prop]: value,
},
]);
}).toThrowError(`${prop} must be a string`);
});
});
it('returns snippet as is if token can not be found', () => { it('adds authentication variables if both options are provided', () => {
const snippet = 'some code snippet'; const output = buildConfigurationSnippet({ ...basicOptions, ...authOptions });
expect(insertTips(snippet, validTips)).toBe(snippet);
});
it('returns the snippet with properly inserted tips', () => { expect(output).toContain(` FUZZAPI_HTTP_USERNAME: "$USERNAME"`);
const snippet = `default: expect(output).toContain(` FUZZAPI_HTTP_PASSWORD: "$PASSWORD"`);
artifacts:
expire_in: 30 days
tags:
- gitlab-org`;
const expected = `# Tip 1
default:
# Tip 2
artifacts:
# Tip 3
expire_in: 30 days
# Tip 4
tags:
- gitlab-org`;
expect(insertTips(snippet, validTips)).toBe(expected);
}); });
}); });
...@@ -6,7 +6,6 @@ import ConfigurationSnippetModal from 'ee/security_configuration/components/conf ...@@ -6,7 +6,6 @@ import ConfigurationSnippetModal from 'ee/security_configuration/components/conf
import { extendedWrapper } from 'helpers/vue_test_utils_helper'; import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import { redirectTo } from '~/lib/utils/url_utility'; import { redirectTo } from '~/lib/utils/url_utility';
import SourceEditor from '~/vue_shared/components/source_editor.vue'; import SourceEditor from '~/vue_shared/components/source_editor.vue';
import { createApiFuzzingConfigurationMutationResponse } from '../api_fuzzing/mock_data';
jest.mock('clipboard', () => jest.mock('clipboard', () =>
jest.fn().mockImplementation(() => ({ jest.fn().mockImplementation(() => ({
...@@ -24,10 +23,8 @@ jest.mock('~/lib/utils/url_utility', () => { ...@@ -24,10 +23,8 @@ jest.mock('~/lib/utils/url_utility', () => {
}; };
}); });
const { const gitlabCiYamlEditPath = '/ci/editor';
gitlabCiYamlEditPath, const configurationYaml = 'YAML';
configurationYaml,
} = createApiFuzzingConfigurationMutationResponse.data.apiFuzzingCiConfigurationCreate;
const redirectParam = 'foo'; const redirectParam = 'foo';
describe('EE - SecurityConfigurationSnippetModal', () => { describe('EE - SecurityConfigurationSnippetModal', () => {
......
...@@ -8,6 +8,7 @@ RSpec.describe Projects::Security::ApiFuzzingConfigurationHelper do ...@@ -8,6 +8,7 @@ RSpec.describe Projects::Security::ApiFuzzingConfigurationHelper do
let(:security_configuration_path) { project_security_configuration_path(project) } let(:security_configuration_path) { project_security_configuration_path(project) }
let(:full_path) { project.full_path } let(:full_path) { project.full_path }
let(:gitlab_ci_yaml_edit_path) { Rails.application.routes.url_helpers.project_ci_pipeline_editor_path(project) }
let(:api_fuzzing_documentation_path) { help_page_path('user/application_security/api_fuzzing/index') } let(:api_fuzzing_documentation_path) { help_page_path('user/application_security/api_fuzzing/index') }
let(:api_fuzzing_authentication_documentation_path) { help_page_path('user/application_security/api_fuzzing/index', anchor: 'authentication') } let(:api_fuzzing_authentication_documentation_path) { help_page_path('user/application_security/api_fuzzing/index', anchor: 'authentication') }
let(:ci_variables_documentation_path) { help_page_path('ci/variables/index') } let(:ci_variables_documentation_path) { help_page_path('ci/variables/index') }
...@@ -29,6 +30,7 @@ RSpec.describe Projects::Security::ApiFuzzingConfigurationHelper do ...@@ -29,6 +30,7 @@ RSpec.describe Projects::Security::ApiFuzzingConfigurationHelper do
is_expected.to eq( is_expected.to eq(
security_configuration_path: security_configuration_path, security_configuration_path: security_configuration_path,
full_path: full_path, full_path: full_path,
gitlab_ci_yaml_edit_path: gitlab_ci_yaml_edit_path,
api_fuzzing_documentation_path: api_fuzzing_documentation_path, api_fuzzing_documentation_path: api_fuzzing_documentation_path,
api_fuzzing_authentication_documentation_path: api_fuzzing_authentication_documentation_path, api_fuzzing_authentication_documentation_path: api_fuzzing_authentication_documentation_path,
ci_variables_documentation_path: ci_variables_documentation_path, ci_variables_documentation_path: ci_variables_documentation_path,
...@@ -47,6 +49,7 @@ RSpec.describe Projects::Security::ApiFuzzingConfigurationHelper do ...@@ -47,6 +49,7 @@ RSpec.describe Projects::Security::ApiFuzzingConfigurationHelper do
is_expected.to eq( is_expected.to eq(
security_configuration_path: security_configuration_path, security_configuration_path: security_configuration_path,
full_path: full_path, full_path: full_path,
gitlab_ci_yaml_edit_path: gitlab_ci_yaml_edit_path,
api_fuzzing_documentation_path: api_fuzzing_documentation_path, api_fuzzing_documentation_path: api_fuzzing_documentation_path,
api_fuzzing_authentication_documentation_path: api_fuzzing_authentication_documentation_path, api_fuzzing_authentication_documentation_path: api_fuzzing_authentication_documentation_path,
ci_variables_documentation_path: ci_variables_documentation_path, ci_variables_documentation_path: ci_variables_documentation_path,
......
...@@ -1590,9 +1590,6 @@ msgstr "" ...@@ -1590,9 +1590,6 @@ msgstr ""
msgid "APIFuzzing|Choose a profile" msgid "APIFuzzing|Choose a profile"
msgstr "" msgstr ""
msgid "APIFuzzing|Code snippet could not be generated. Try again later."
msgstr ""
msgid "APIFuzzing|Configure HTTP basic authentication values. Other authentication methods are supported. %{linkStart}Learn more%{linkEnd}." msgid "APIFuzzing|Configure HTTP basic authentication values. Other authentication methods are supported. %{linkStart}Learn more%{linkEnd}."
msgstr "" 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