Commit e8f23734 authored by Paul Gascou-Vaillancourt's avatar Paul Gascou-Vaillancourt Committed by Enrique Alcántara

Create API fuzzing configuration form

Creates the form for configuring API fuzzing.
parent d4e96792
...@@ -2,3 +2,4 @@ ...@@ -2,3 +2,4 @@
filenames: filenames:
- ee/app/assets/javascripts/on_demand_scans/graphql/dast_scan_create.mutation.graphql - ee/app/assets/javascripts/on_demand_scans/graphql/dast_scan_create.mutation.graphql
- ee/app/assets/javascripts/oncall_schedules/graphql/mutations/update_oncall_schedule_rotation.mutation.graphql - ee/app/assets/javascripts/oncall_schedules/graphql/mutations/update_oncall_schedule_rotation.mutation.graphql
- ee/app/assets/javascripts/security_configuration/api_fuzzing/graphql/api_fuzzing_ci_configuration.query.graphql
import { initApiFuzzingConfiguration } from 'ee/security_configuration/api_fuzzing/api_fuzzing_configuration_init';
initApiFuzzingConfiguration();
import Vue from 'vue';
import { parseBoolean } from '~/lib/utils/common_utils';
import { apolloProvider } from './graphql/provider';
import ApiFuzzingApp from './components/app.vue';
export const initApiFuzzingConfiguration = () => {
const el = document.querySelector('.js-api-fuzzing-configuration');
if (!el) {
return undefined;
}
const {
fullPath,
apiFuzzingDocumentationPath,
apiFuzzingAuthenticationDocumentationPath,
ciVariablesDocumentationPath,
projectCiSettingsPath,
} = el.dataset;
const canSetProjectCiVariables = parseBoolean(el.dataset.canSetProjectCiVariables);
return new Vue({
el,
apolloProvider,
provide: {
fullPath,
apiFuzzingDocumentationPath,
apiFuzzingAuthenticationDocumentationPath,
ciVariablesDocumentationPath,
projectCiSettingsPath,
canSetProjectCiVariables,
},
render(createElement) {
return createElement(ApiFuzzingApp);
},
});
};
<script>
import { GlAlert, GlLink, GlLoadingIcon, GlSprintf } from '@gitlab/ui';
import { s__ } from '~/locale';
import apiFuzzingCiConfigurationQuery from '../graphql/api_fuzzing_ci_configuration.query.graphql';
import ConfigurationForm from './configuration_form.vue';
export default {
components: {
GlAlert,
GlLink,
GlLoadingIcon,
GlSprintf,
ConfigurationForm,
},
inject: {
fullPath: {
from: 'fullPath',
},
apiFuzzingDocumentationPath: {
from: 'apiFuzzingDocumentationPath',
},
},
apollo: {
apiFuzzingCiConfiguration: {
query: apiFuzzingCiConfigurationQuery,
variables() {
return {
fullPath: this.fullPath,
};
},
update({ project: { apiFuzzingCiConfiguration } }) {
return apiFuzzingCiConfiguration;
},
},
},
i18n: {
title: s__('APIFuzzing|API Fuzzing Configuration'),
helpText: s__(`
APIFuzzing|Customize common API fuzzing settings to suit your requirements.
For details of more advanced configuration options, see the
%{docsLinkStart}GitLab API Fuzzing documentation%{docsLinkEnd}.`),
notice: s__(`
APIFuzzing|Use this tool to generate API fuzzing configuration YAML to copy into your
.gitlab-ci.yml file. This tool does not reflect or update your .gitlab-ci.yml file automatically.
`),
},
};
</script>
<template>
<article>
<header class="gl-mt-5 gl-border-b-1 gl-border-b-gray-100 gl-border-b-solid">
<h4>
{{ s__('APIFuzzing|API Fuzzing Configuration') }}
</h4>
<p>
<gl-sprintf :message="$options.i18n.helpText">
<template #docsLink="{ content }">
<gl-link :href="apiFuzzingDocumentationPath" target="_blank">{{ content }}</gl-link>
</template>
</gl-sprintf>
</p>
</header>
<gl-alert :dismissible="false" class="gl-mb-5">
{{ $options.i18n.notice }}
</gl-alert>
<gl-loading-icon v-if="$apollo.loading" size="lg" />
<configuration-form v-else :api-fuzzing-ci-configuration="apiFuzzingCiConfiguration" />
</article>
</template>
<script>
import {
GlAccordion,
GlAccordionItem,
GlAlert,
GlButton,
GlFormGroup,
GlFormText,
GlFormCheckbox,
GlLink,
GlSprintf,
} from '@gitlab/ui';
import { __, s__ } from '~/locale';
import { SCAN_MODES } from '../constants';
import DropdownInput from '../../components/dropdown_input.vue';
import DynamicFields from '../../components/dynamic_fields.vue';
import FormInput from '../../components/form_input.vue';
export default {
components: {
GlAccordion,
GlAccordionItem,
GlAlert,
GlButton,
GlFormGroup,
GlFormText,
GlFormCheckbox,
GlLink,
GlSprintf,
DropdownInput,
DynamicFields,
FormInput,
},
inject: {
apiFuzzingAuthenticationDocumentationPath: {
from: 'apiFuzzingAuthenticationDocumentationPath',
},
ciVariablesDocumentationPath: {
from: 'ciVariablesDocumentationPath',
},
projectCiSettingsPath: {
from: 'projectCiSettingsPath',
},
canSetProjectCiVariables: {
from: 'canSetProjectCiVariables',
},
},
props: {
apiFuzzingCiConfiguration: {
type: Object,
required: true,
},
},
data() {
return {
targetUrl: {
field: 'targetUrl',
label: s__('APIFuzzing|Target URL'),
description: s__('APIFuzzing|Base URL of API fuzzing target.'),
placeholder: __('Ex: Example.com'),
value: '',
},
scanMode: {
field: 'scanMode',
label: s__('APIFuzzing|Scan mode'),
description: s__('APIFuzzing|There are two ways to perform scans.'),
value: '',
defaultText: s__('APIFuzzing|Choose a method'),
options: this.apiFuzzingCiConfiguration.scanModes.map((value) => ({
value,
text: SCAN_MODES[value].scanModeLabel,
})),
},
apiSpecificationFile: {
field: 'apiSpecificationFile',
value: '',
},
authenticationEnabled: false,
authenticationSettings: [
{
type: 'string',
field: 'username',
label: s__('APIFuzzing|Username for basic authentication'),
description: s__(
'APIFuzzing|Instead of entering the username directly, enter the key of the CI variable set to the username.',
),
placeholder: s__('APIFuzzing|Ex: $TestUsername'),
value: '',
},
{
type: 'string',
field: 'password',
label: s__('APIFuzzing|Password for basic authentication'),
description: s__(
'APIFuzzing|Instead of entering the password directly, enter the key of the CI variable set to the password.',
),
placeholder: s__('APIFuzzing|Ex: $TestPassword'),
value: '',
},
],
scanProfile: {
field: 'scanProfile',
label: s__('APIFuzzing|Scan profile'),
description: 'Pre-defined profiles by GitLab.',
value: '',
defaultText: s__('APIFuzzing|Choose a profile'),
options: this.apiFuzzingCiConfiguration.scanProfiles.map(
({ name: value, description: text }) => ({
value,
text,
}),
),
},
};
},
computed: {
authAlertI18n() {
return this.canSetProjectCiVariables
? {
title: s__('APIFuzzing|Make sure your credentials are secured'),
text: s__(
`APIFuzzing|To prevent a security leak, authentication info must be added as a
%{ciVariablesLinkStart}CI variable%{ciVariablesLinkEnd}. As a user with maintainer access
rights, you can manage CI variables in the
%{ciSettingsLinkStart}Settings%{ciSettingsLinkEnd} area.`,
),
}
: {
title: s__("APIFuzzing|You may need a maintainer's help to secure your credentials."),
text: s__(
`APIFuzzing|To prevent a security leak, authentication info must be added as a
%{ciVariablesLinkStart}CI variable%{ciVariablesLinkEnd}. A user with maintainer
access rights can manage CI variables in the
%{ciSettingsLinkStart}Settings%{ciSettingsLinkEnd} area. We detected that you are not
a maintainer. Commit your changes and assign them to a maintainer to update the
credentials before merging.`,
),
};
},
scanProfileYaml() {
return this.apiFuzzingCiConfiguration.scanProfiles.find(
({ name }) => name === this.scanProfile.value,
)?.yaml;
},
},
methods: {
onSubmit() {},
},
SCAN_MODES,
};
</script>
<template>
<form @submit.prevent="onSubmit">
<form-input v-model="targetUrl.value" v-bind="targetUrl" class="gl-mb-7" />
<dropdown-input v-model="scanMode.value" v-bind="scanMode" />
<form-input
v-if="scanMode.value"
v-model="apiSpecificationFile.value"
v-bind="{ ...apiSpecificationFile, ...$options.SCAN_MODES[scanMode.value] }"
/>
<gl-form-group class="gl-my-7">
<template #label>
{{ __('Authentication') }}
<gl-form-text class="gl-mt-3">
<gl-sprintf
:message="
s__(
'APIFuzzing|Authentication is handled by providing HTTP basic authentication token as a header or cookie. %{linkStart}More information%{linkEnd}.',
)
"
>
<template #link="{ content }">
<a :href="apiFuzzingAuthenticationDocumentationPath">
{{ content }}
</a>
</template>
</gl-sprintf>
</gl-form-text>
</template>
<gl-form-checkbox
v-model="authenticationEnabled"
data-testid="api-fuzzing-enable-authentication-checkbox"
>
{{ s__('APIFuzzing|Enable authentication') }}
</gl-form-checkbox>
</gl-form-group>
<template v-if="authenticationEnabled">
<gl-alert
:title="authAlertI18n.title"
:dismissible="false"
variant="warning"
class="gl-mb-5"
data-testid="api-fuzzing-authentication-notice"
>
<gl-sprintf :message="authAlertI18n.text">
<template #ciVariablesLink="{ content }">
<gl-link :href="ciVariablesDocumentationPath" target="_blank">
{{ content }}
</gl-link>
</template>
<template #ciSettingsLink="{ content }">
<gl-link :href="projectCiSettingsPath" target="_blank">
{{ content }}
</gl-link>
</template>
</gl-sprintf>
</gl-alert>
<dynamic-fields v-model="authenticationSettings" />
</template>
<dropdown-input v-model="scanProfile.value" v-bind="scanProfile" />
<template v-if="scanProfileYaml">
<gl-accordion>
<gl-accordion-item :title="s__('APIFuzzing|Show code snippet for the profile')">
<pre data-testid="api-fuzzing-scan-profile-yaml-viewer">{{ scanProfileYaml }}</pre>
</gl-accordion-item>
</gl-accordion>
</template>
<hr />
<gl-button type="submit" variant="confirm">{{
s__('APIFuzzing|Generate code snippet')
}}</gl-button>
<gl-button>{{ __('Cancel') }}</gl-button>
</form>
</template>
import { __, s__ } from '~/locale';
export const SCAN_MODES = {
HAR: {
scanModeLabel: __('HAR (HTTP Archive)'),
label: __('HAR file path'),
placeholder: s__('APIFuzzing|Ex: Project_Test/File/example_fuzz.har'),
description: s__(
"APIFuzzing|HAR files may contain sensitive information such as authentication tokens, API keys, and session cookies. We recommend that you review the HAR files' contents before adding them to a repository.",
),
},
OPENAPI: {
scanModeLabel: __('Open API'),
label: __('Open API specification file path'),
placeholder: s__('APIFuzzing|Ex: Project_Test/File/example_fuzz.json'),
description: s__(
'APIFuzzing|We recommend that you review the JSON specifications file before adding it to a repository.',
),
},
};
query apiFuzzingCiConfiguration($fullPath: ID!) {
project(fullPath: $fullPath) {
apiFuzzingCiConfiguration {
scanModes
scanProfiles {
name
description
yaml
}
}
}
}
import Vue from 'vue';
import VueApollo from 'vue-apollo';
import createDefaultClient from '~/lib/graphql';
Vue.use(VueApollo);
export const apolloProvider = new VueApollo({ defaultClient: createDefaultClient() });
...@@ -51,6 +51,11 @@ export default { ...@@ -51,6 +51,11 @@ export default {
required: false, required: false,
default: false, default: false,
}, },
placeholder: {
type: String,
required: false,
default: '',
},
}, },
computed: { computed: {
showCustomValueMessage() { showCustomValueMessage() {
...@@ -83,6 +88,7 @@ export default { ...@@ -83,6 +88,7 @@ export default {
:size="inputSize" :size="inputSize"
:value="value" :value="value"
:disabled="disabled" :disabled="disabled"
:placeholder="placeholder"
@input="$emit('input', $event)" @input="$emit('input', $event)"
/> />
......
# frozen_string_literal: true
module Projects::Security::ApiFuzzingConfigurationHelper
def api_fuzzing_configuration_data(project)
{
full_path: project.full_path,
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'),
ci_variables_documentation_path: help_page_path('ci/variables/README'),
project_ci_settings_path: project_settings_ci_cd_path(project),
can_set_project_ci_variables: can?(current_user, :admin_pipeline, project).to_s
}
end
end
...@@ -2,4 +2,4 @@ ...@@ -2,4 +2,4 @@
- breadcrumb_title _("API Fuzzing Configuration") - breadcrumb_title _("API Fuzzing Configuration")
- page_title _("API Fuzzing Configuration") - page_title _("API Fuzzing Configuration")
%h1= "API fuzzing configuration" .js-api-fuzzing-configuration{ data: api_fuzzing_configuration_data(@project) }
import Vue from 'vue';
import VueApollo from 'vue-apollo';
import { shallowMount } from '@vue/test-utils';
import { merge } from 'lodash';
import { GlAlert, GlLink, GlLoadingIcon, GlSprintf } from '@gitlab/ui';
import { stripTypenames } from 'helpers/graphql_helpers';
import createMockApollo from 'helpers/mock_apollo_helper';
import apiFuzzingCiConfigurationQuery from 'ee/security_configuration/api_fuzzing/graphql/api_fuzzing_ci_configuration.query.graphql';
import App from 'ee/security_configuration/api_fuzzing/components/app.vue';
import ConfigurationForm from 'ee/security_configuration/api_fuzzing/components/configuration_form.vue';
import { apiFuzzingConfigurationQueryResponse } from '../mock_data';
Vue.use(VueApollo);
describe('EE - ApiFuzzingConfigurationApp', () => {
let wrapper;
const projectFullPath = 'namespace/project';
const pendingHandler = jest.fn(() => new Promise(() => {}));
const successHandler = jest.fn(async () => apiFuzzingConfigurationQueryResponse);
const createMockApolloProvider = (handler) =>
createMockApollo([[apiFuzzingCiConfigurationQuery, handler]]);
const findLoadingSpinner = () => wrapper.find(GlLoadingIcon);
const findConfigurationForm = () => wrapper.find(ConfigurationForm);
const createWrapper = (options) => {
wrapper = shallowMount(
App,
merge(
{
apolloProvider: () => createMockApolloProvider(successHandler),
stubs: {
GlSprintf,
},
provide: {
fullPath: projectFullPath,
apiFuzzingDocumentationPath: '/api_fuzzing/documentation/path',
},
data() {
return {
apiFuzzingCiConfiguration: {},
};
},
},
options,
),
);
};
afterEach(() => {
wrapper.destroy();
});
it('shows a loading spinner while fetching the configuration from the API', () => {
createWrapper({
apolloProvider: createMockApolloProvider(pendingHandler),
});
expect(pendingHandler).toHaveBeenCalledWith({ fullPath: projectFullPath });
expect(findLoadingSpinner().exists()).toBe(true);
expect(findConfigurationForm().exists()).toBe(false);
});
describe('configuration fetched successfully', () => {
beforeEach(() => {
createWrapper();
});
it('shows the form once the configuration has loaded', () => {
expect(findConfigurationForm().exists()).toBe(true);
expect(findLoadingSpinner().exists()).toBe(false);
});
it('passes the configuration to the form', () => {
expect(findConfigurationForm().props('apiFuzzingCiConfiguration')).toEqual(
stripTypenames(apiFuzzingConfigurationQueryResponse.data.project.apiFuzzingCiConfiguration),
);
});
it("shows a notice about the tool's purpose", () => {
const alert = wrapper.find(GlAlert);
expect(alert.exists()).toBe(true);
expect(alert.text()).toBe(
'Use this tool to generate API fuzzing configuration YAML to copy into your .gitlab-ci.yml file. This tool does not reflect or update your .gitlab-ci.yml file automatically.',
);
});
it('includes a link to API fuzzing documentation ', () => {
const link = wrapper.find(GlLink);
expect(link.exists()).toBe(true);
expect(link.attributes('href')).toBe('/api_fuzzing/documentation/path');
});
});
});
import { mount } from '@vue/test-utils';
import { merge } from 'lodash';
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import { SCAN_MODES } from 'ee/security_configuration/api_fuzzing/constants';
import ConfigurationForm from 'ee/security_configuration/api_fuzzing/components/configuration_form.vue';
import FormInput from 'ee/security_configuration/components/form_input.vue';
import DropdownInput from 'ee/security_configuration/components/dropdown_input.vue';
const makeScanProfile = (name) => ({
name,
description: `${name} description`,
yaml: `
---
:Name: ${name}
`.trim(),
});
describe('EE - ApiFuzzingConfigurationForm', () => {
let wrapper;
const apiFuzzingCiConfiguration = {
scanModes: Object.keys(SCAN_MODES),
scanProfiles: [makeScanProfile('Quick-10'), makeScanProfile('Medium-20')],
};
const findEnableAuthenticationCheckbox = () =>
wrapper.findByTestId('api-fuzzing-enable-authentication-checkbox');
const findScanModeInput = () => wrapper.findAll(DropdownInput).at(0);
const findSpecificationFileInput = () => wrapper.findAll(FormInput).at(1);
const findAuthenticationNotice = () => wrapper.findByTestId('api-fuzzing-authentication-notice');
const findScanProfileDropdownInput = () => wrapper.findAll(DropdownInput).at(1);
const findScanProfileYamlViewer = () =>
wrapper.findByTestId('api-fuzzing-scan-profile-yaml-viewer');
const createWrapper = (options = {}) => {
wrapper = extendedWrapper(
mount(
ConfigurationForm,
merge(
{
provide: {
apiFuzzingAuthenticationDocumentationPath:
'api_fuzzing_authentication/documentation/path',
ciVariablesDocumentationPath: '/ci_cd_variables/documentation/path',
projectCiSettingsPath: '/project/settings/ci_cd',
canSetProjectCiVariables: true,
},
propsData: {
apiFuzzingCiConfiguration,
},
},
options,
),
),
);
};
afterEach(() => {
wrapper.destroy();
});
it('includes a link to API fuzzing authentication documentation', () => {
createWrapper();
expect(wrapper.html()).toContain('api_fuzzing_authentication/documentation/path');
});
describe('scan modes', () => {
beforeEach(() => {
createWrapper();
});
it('displays a dropdown option for each scan mode', () => {
findScanModeInput()
.findAll('li')
.wrappers.forEach((item, index) => {
expect(item.text()).toBe(
SCAN_MODES[apiFuzzingCiConfiguration.scanModes[index]].scanModeLabel,
);
});
});
it('by default, the specification file input is hidden', () => {
expect(wrapper.findAll(FormInput)).toHaveLength(1);
});
describe.each(Object.keys(SCAN_MODES))('when %s scan mode is selected', (scanMode) => {
it('the specificationfile input becomes visible and has the correct labels', async () => {
const selectedScanMode = SCAN_MODES[scanMode];
findScanModeInput().vm.$emit('input', scanMode);
await wrapper.vm.$nextTick();
const specificationFileInput = findSpecificationFileInput();
expect(specificationFileInput.exists()).toBe(true);
expect(specificationFileInput.text()).toContain(selectedScanMode.label);
expect(specificationFileInput.text()).toContain(selectedScanMode.description);
expect(specificationFileInput.find('input').attributes('placeholder')).toBe(
selectedScanMode.placeholder,
);
});
});
});
describe('authentication', () => {
it('authentication section is hidden by default', () => {
createWrapper();
expect(findAuthenticationNotice().exists()).toBe(false);
});
it('authentication section becomes visible once checkbox is checked', async () => {
createWrapper();
await findEnableAuthenticationCheckbox().trigger('click');
expect(findAuthenticationNotice().exists()).toBe(true);
});
it('sees the the proper notice as a maintainer', async () => {
createWrapper();
await findEnableAuthenticationCheckbox().trigger('click');
expect(findAuthenticationNotice().text()).toMatchInterpolatedText(
'Make sure your credentials are secured To prevent a security leak, authentication info must be added as a CI variable. As a user with maintainer access rights, you can manage CI variables in the Settings area.',
);
});
it('sees the the proper notice as a developer', async () => {
createWrapper({
provide: {
canSetProjectCiVariables: false,
},
});
await findEnableAuthenticationCheckbox().trigger('click');
expect(findAuthenticationNotice().text()).toMatchInterpolatedText(
"You may need a maintainer's help to secure your credentials. To prevent a security leak, authentication info must be added as a CI variable. A user with maintainer access rights can manage CI variables in the Settings area. We detected that you are not a maintainer. Commit your changes and assign them to a maintainer to update the credentials before merging.",
);
});
});
describe('scan profiles', () => {
beforeEach(() => {
createWrapper();
});
it('displays a dropdown option for each scan profile', () => {
findScanProfileDropdownInput()
.findAll('li')
.wrappers.forEach((item, index) => {
expect(item.text()).toBe(apiFuzzingCiConfiguration.scanProfiles[index].description);
});
});
it('by default, YAML viewer is not visible', () => {
expect(findScanProfileYamlViewer().exists()).toBe(false);
});
it('when a scan profile is selected, its YAML is visible', async () => {
const selectedScanProfile = apiFuzzingCiConfiguration.scanProfiles[0];
wrapper.findAll(DropdownInput).at(1).vm.$emit('input', selectedScanProfile.name);
await wrapper.vm.$nextTick();
expect(findScanProfileYamlViewer().exists()).toBe(true);
expect(findScanProfileYamlViewer().text()).toBe(selectedScanProfile.yaml);
});
});
});
export const apiFuzzingConfigurationQueryResponse = {
data: {
project: {
apiFuzzingCiConfiguration: {
scanModes: ['HAR', 'OPENAPI'],
scanProfiles: [
{
name: 'Quick-10',
description: 'Fuzzing 10 times per parameter',
yaml:
'---\nName: Quick-10\nDefaultProfile: Empty\nRoutes:\n- Route:\n Order: 0\n Url: "**"\n Mutate: true\n SwaggerUrl:\n Script:\n Headers:\n - Pattern: Host\n Mutate: false\n - Pattern: Connection\n Mutate: false\n - Pattern: Content-Length\n Mutate: false\n ApiTokens:\n - Name: Authorization\n Where: Header\n Expiration: 120\n IsSignatureOfRequest: false\n - Name: access_token\n Where: FormData\n Expiration: 120\n IsSignatureOfRequest: false\n - Name: access_token\n Where: Query\n Expiration: 120\n IsSignatureOfRequest: false\n - Name: X-API-Key\n Where: Header\n Expiration: 120\n IsSignatureOfRequest: false\n Checks:\n - Name: FormBodyFuzzingCheck\n Configuration:\n FuzzingCount: 10\n UnicodeFuzzing: true\n - Name: GeneralFuzzingCheck\n Configuration:\n FuzzingCount: 10\n UnicodeFuzzing: true\n HeaderFuzzing: false\n Headers:\n - Name: JsonFuzzingCheck\n Configuration:\n FuzzingCount: 10\n UnicodeFuzzing: true\n - Name: XmlFuzzingCheck\n Configuration:\n FuzzingCount: 10\n UnicodeFuzzing: true\n',
__typename: 'ApiFuzzingScanProfile',
},
{
name: 'Medium-20',
description: 'Fuzzing 20 times per parameter',
yaml:
'---\nName: Medium-20\nDefaultProfile: Empty\nRoutes:\n- Route:\n Order: 0\n Url: "**"\n Mutate: true\n SwaggerUrl:\n Script:\n Headers:\n - Pattern: Host\n Mutate: false\n - Pattern: Connection\n Mutate: false\n - Pattern: Content-Length\n Mutate: false\n ApiTokens:\n - Name: Authorization\n Where: Header\n Expiration: 120\n IsSignatureOfRequest: false\n - Name: access_token\n Where: FormData\n Expiration: 120\n IsSignatureOfRequest: false\n - Name: access_token\n Where: Query\n Expiration: 120\n IsSignatureOfRequest: false\n - Name: X-API-Key\n Where: Header\n Expiration: 120\n IsSignatureOfRequest: false\n Checks:\n - Name: FormBodyFuzzingCheck\n Configuration:\n FuzzingCount: 20\n UnicodeFuzzing: true\n - Name: GeneralFuzzingCheck\n Configuration:\n FuzzingCount: 20\n UnicodeFuzzing: true\n HeaderFuzzing: false\n Headers:\n - Name: JsonFuzzingCheck\n Configuration:\n FuzzingCount: 20\n UnicodeFuzzing: true\n - Name: XmlFuzzingCheck\n Configuration:\n FuzzingCount: 20\n UnicodeFuzzing: true\n',
__typename: 'ApiFuzzingScanProfile',
},
{
name: 'Medium-50',
description: 'Fuzzing 50 times per parameter',
yaml:
'---\nName: Medium-50\nDefaultProfile: Empty\nRoutes:\n- Route:\n Order: 0\n Url: "**"\n Mutate: true\n SwaggerUrl:\n Script:\n Headers:\n - Pattern: Host\n Mutate: false\n - Pattern: Connection\n Mutate: false\n - Pattern: Content-Length\n Mutate: false\n ApiTokens:\n - Name: Authorization\n Where: Header\n Expiration: 120\n IsSignatureOfRequest: false\n - Name: access_token\n Where: FormData\n Expiration: 120\n IsSignatureOfRequest: false\n - Name: access_token\n Where: Query\n Expiration: 120\n IsSignatureOfRequest: false\n - Name: X-API-Key\n Where: Header\n Expiration: 120\n IsSignatureOfRequest: false\n Checks:\n - Name: FormBodyFuzzingCheck\n Configuration:\n FuzzingCount: 50\n UnicodeFuzzing: true\n - Name: GeneralFuzzingCheck\n Configuration:\n FuzzingCount: 50\n UnicodeFuzzing: true\n HeaderFuzzing: false\n Headers:\n - Name: JsonFuzzingCheck\n Configuration:\n FuzzingCount: 50\n UnicodeFuzzing: true\n - Name: XmlFuzzingCheck\n Configuration:\n FuzzingCount: 50\n UnicodeFuzzing: true\n',
__typename: 'ApiFuzzingScanProfile',
},
{
name: 'Long-100',
description: 'Fuzzing 100 times per parameter',
yaml:
'---\nName: Long-100\nDefaultProfile: Empty\nRoutes:\n- Route:\n Order: 0\n Url: "**"\n Mutate: true\n SwaggerUrl:\n Script:\n Headers:\n - Pattern: Host\n Mutate: false\n - Pattern: Connection\n Mutate: false\n - Pattern: Content-Length\n Mutate: false\n ApiTokens:\n - Name: Authorization\n Where: Header\n Expiration: 120\n IsSignatureOfRequest: false\n - Name: access_token\n Where: FormData\n Expiration: 120\n IsSignatureOfRequest: false\n - Name: access_token\n Where: Query\n Expiration: 120\n IsSignatureOfRequest: false\n - Name: X-API-Key\n Where: Header\n Expiration: 120\n IsSignatureOfRequest: false\n Checks:\n - Name: FormBodyFuzzingCheck\n Configuration:\n FuzzingCount: 100\n UnicodeFuzzing: true\n - Name: GeneralFuzzingCheck\n Configuration:\n FuzzingCount: 100\n UnicodeFuzzing: true\n HeaderFuzzing: false\n Headers:\n - Name: JsonFuzzingCheck\n Configuration:\n FuzzingCount: 100\n UnicodeFuzzing: true\n - Name: XmlFuzzingCheck\n Configuration:\n FuzzingCount: 100\n UnicodeFuzzing: true\n',
__typename: 'ApiFuzzingScanProfile',
},
],
__typename: 'ApiFuzzingCiConfiguration',
},
__typename: 'Project',
},
},
};
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Projects::Security::ApiFuzzingConfigurationHelper do
let_it_be(:project) { create(:project) }
let_it_be(:user) { create(:user) }
let(:full_path) { project.full_path }
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(:ci_variables_documentation_path) { help_page_path('ci/variables/README') }
let(:project_ci_settings_path) { project_settings_ci_cd_path(project) }
subject { helper.api_fuzzing_configuration_data(project) }
before do
allow(helper).to receive(:current_user).and_return(user)
end
describe '#api_fuzzing_configuration_data' do
context 'user with admin_pipeline permissions' do
before do
allow(helper).to receive(:can?).with(user, :admin_pipeline, project).and_return(true)
end
it {
is_expected.to eq(
full_path: full_path,
api_fuzzing_documentation_path: api_fuzzing_documentation_path,
api_fuzzing_authentication_documentation_path: api_fuzzing_authentication_documentation_path,
ci_variables_documentation_path: ci_variables_documentation_path,
project_ci_settings_path: project_ci_settings_path,
can_set_project_ci_variables: 'true'
)
}
end
context 'user without admin_pipeline permissions' do
before do
allow(helper).to receive(:can?).with(user, :admin_pipeline, project).and_return(false)
end
it {
is_expected.to eq(
full_path: full_path,
api_fuzzing_documentation_path: api_fuzzing_documentation_path,
api_fuzzing_authentication_documentation_path: api_fuzzing_authentication_documentation_path,
ci_variables_documentation_path: ci_variables_documentation_path,
project_ci_settings_path: project_ci_settings_path,
can_set_project_ci_variables: 'false'
)
}
end
end
end
...@@ -1393,6 +1393,90 @@ msgstr "" ...@@ -1393,6 +1393,90 @@ msgstr ""
msgid "API Token" msgid "API Token"
msgstr "" msgstr ""
msgid "APIFuzzing|API Fuzzing Configuration"
msgstr ""
msgid "APIFuzzing|Authentication is handled by providing HTTP basic authentication token as a header or cookie. %{linkStart}More information%{linkEnd}."
msgstr ""
msgid "APIFuzzing|Base URL of API fuzzing target."
msgstr ""
msgid "APIFuzzing|Choose a method"
msgstr ""
msgid "APIFuzzing|Choose a profile"
msgstr ""
msgid "APIFuzzing|Customize common API fuzzing settings to suit your requirements. For details of more advanced configuration options, see the %{docsLinkStart}GitLab API Fuzzing documentation%{docsLinkEnd}."
msgstr ""
msgid "APIFuzzing|Enable authentication"
msgstr ""
msgid "APIFuzzing|Ex: $TestPassword"
msgstr ""
msgid "APIFuzzing|Ex: $TestUsername"
msgstr ""
msgid "APIFuzzing|Ex: Project_Test/File/example_fuzz.har"
msgstr ""
msgid "APIFuzzing|Ex: Project_Test/File/example_fuzz.json"
msgstr ""
msgid "APIFuzzing|Generate code snippet"
msgstr ""
msgid "APIFuzzing|HAR files may contain sensitive information such as authentication tokens, API keys, and session cookies. We recommend that you review the HAR files' contents before adding them to a repository."
msgstr ""
msgid "APIFuzzing|Instead of entering the password directly, enter the key of the CI variable set to the password."
msgstr ""
msgid "APIFuzzing|Instead of entering the username directly, enter the key of the CI variable set to the username."
msgstr ""
msgid "APIFuzzing|Make sure your credentials are secured"
msgstr ""
msgid "APIFuzzing|Password for basic authentication"
msgstr ""
msgid "APIFuzzing|Scan mode"
msgstr ""
msgid "APIFuzzing|Scan profile"
msgstr ""
msgid "APIFuzzing|Show code snippet for the profile"
msgstr ""
msgid "APIFuzzing|Target URL"
msgstr ""
msgid "APIFuzzing|There are two ways to perform scans."
msgstr ""
msgid "APIFuzzing|To prevent a security leak, authentication info must be added as a %{ciVariablesLinkStart}CI variable%{ciVariablesLinkEnd}. A user with maintainer access rights can manage CI variables in the %{ciSettingsLinkStart}Settings%{ciSettingsLinkEnd} area. We detected that you are not a maintainer. Commit your changes and assign them to a maintainer to update the credentials before merging."
msgstr ""
msgid "APIFuzzing|To prevent a security leak, authentication info must be added as a %{ciVariablesLinkStart}CI variable%{ciVariablesLinkEnd}. As a user with maintainer access rights, you can manage CI variables in the %{ciSettingsLinkStart}Settings%{ciSettingsLinkEnd} area."
msgstr ""
msgid "APIFuzzing|Use this tool to generate API fuzzing configuration YAML to copy into your .gitlab-ci.yml file. This tool does not reflect or update your .gitlab-ci.yml file automatically."
msgstr ""
msgid "APIFuzzing|Username for basic authentication"
msgstr ""
msgid "APIFuzzing|We recommend that you review the JSON specifications file before adding it to a repository."
msgstr ""
msgid "APIFuzzing|You may need a maintainer's help to secure your credentials."
msgstr ""
msgid "AWS Access Key" msgid "AWS Access Key"
msgstr "" msgstr ""
...@@ -4138,6 +4222,9 @@ msgstr "" ...@@ -4138,6 +4222,9 @@ msgstr ""
msgid "Authenticating" msgid "Authenticating"
msgstr "" msgstr ""
msgid "Authentication"
msgstr ""
msgid "Authentication Failure" msgid "Authentication Failure"
msgstr "" msgstr ""
...@@ -11850,6 +11937,9 @@ msgstr "" ...@@ -11850,6 +11937,9 @@ msgstr ""
msgid "Evidence collection" msgid "Evidence collection"
msgstr "" msgstr ""
msgid "Ex: Example.com"
msgstr ""
msgid "Exactly one of %{attributes} is required" msgid "Exactly one of %{attributes} is required"
msgstr "" msgstr ""
...@@ -14510,6 +14600,12 @@ msgstr "" ...@@ -14510,6 +14600,12 @@ msgstr ""
msgid "Guideline" msgid "Guideline"
msgstr "" msgstr ""
msgid "HAR (HTTP Archive)"
msgstr ""
msgid "HAR file path"
msgstr ""
msgid "HTTP Basic: Access denied\\nYou must use a personal access token with 'api' scope for Git over HTTP.\\nYou can generate one at %{profile_personal_access_tokens_url}" msgid "HTTP Basic: Access denied\\nYou must use a personal access token with 'api' scope for Git over HTTP.\\nYou can generate one at %{profile_personal_access_tokens_url}"
msgstr "" msgstr ""
...@@ -20667,6 +20763,12 @@ msgstr "" ...@@ -20667,6 +20763,12 @@ msgstr ""
msgid "Open" msgid "Open"
msgstr "" msgstr ""
msgid "Open API"
msgstr ""
msgid "Open API specification file path"
msgstr ""
msgid "Open Selection" msgid "Open Selection"
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