Commit dcaada67 authored by GitLab Bot's avatar GitLab Bot

Automatic merge of gitlab-org/gitlab master

parents 0993bbd2 0aa5aead
...@@ -3,7 +3,6 @@ ...@@ -3,7 +3,6 @@
import { mapActions, mapGetters, mapState } from 'vuex'; import { mapActions, mapGetters, mapState } from 'vuex';
import { GlButton } from '@gitlab/ui'; import { GlButton } from '@gitlab/ui';
import NoteableNote from '~/notes/components/noteable_note.vue'; import NoteableNote from '~/notes/components/noteable_note.vue';
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import PublishButton from './publish_button.vue'; import PublishButton from './publish_button.vue';
export default { export default {
...@@ -12,7 +11,6 @@ export default { ...@@ -12,7 +11,6 @@ export default {
PublishButton, PublishButton,
GlButton, GlButton,
}, },
mixins: [glFeatureFlagsMixin()],
props: { props: {
draft: { draft: {
type: Object, type: Object,
...@@ -63,14 +61,14 @@ export default { ...@@ -63,14 +61,14 @@ export default {
this.isEditingDraft = false; this.isEditingDraft = false;
}, },
handleMouseEnter(draft) { handleMouseEnter(draft) {
if (this.glFeatures.multilineComments && draft.position) { if (draft.position) {
this.setSelectedCommentPositionHover(draft.position.line_range); this.setSelectedCommentPositionHover(draft.position.line_range);
} }
}, },
handleMouseLeave(draft) { handleMouseLeave(draft) {
// Even though position isn't used here we still don't want to unecessarily call a mutation // Even though position isn't used here we still don't want to unnecessarily call a mutation
// The lack of position tells us that highlighting is irrelevant in this context // The lack of position tells us that highlighting is irrelevant in this context
if (this.glFeatures.multilineComments && draft.position) { if (draft.position) {
this.setSelectedCommentPositionHover(); this.setSelectedCommentPositionHover();
} }
}, },
......
...@@ -3,7 +3,6 @@ import { mapGetters } from 'vuex'; ...@@ -3,7 +3,6 @@ import { mapGetters } from 'vuex';
import { GlSprintf, GlIcon } from '@gitlab/ui'; import { GlSprintf, GlIcon } from '@gitlab/ui';
import { IMAGE_DIFF_POSITION_TYPE } from '~/diffs/constants'; import { IMAGE_DIFF_POSITION_TYPE } from '~/diffs/constants';
import { sprintf, __ } from '~/locale'; import { sprintf, __ } from '~/locale';
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import { import {
getStartLineNumber, getStartLineNumber,
getEndLineNumber, getEndLineNumber,
...@@ -16,7 +15,7 @@ export default { ...@@ -16,7 +15,7 @@ export default {
GlIcon, GlIcon,
GlSprintf, GlSprintf,
}, },
mixins: [resolvedStatusMixin, glFeatureFlagsMixin()], mixins: [resolvedStatusMixin],
props: { props: {
draft: { draft: {
type: Object, type: Object,
...@@ -71,6 +70,10 @@ export default { ...@@ -71,6 +70,10 @@ export default {
return this.draft.position || this.discussion.position; return this.draft.position || this.discussion.position;
}, },
startLineNumber() { startLineNumber() {
if (this.position?.position_type === IMAGE_DIFF_POSITION_TYPE) {
// eslint-disable-next-line @gitlab/require-i18n-strings
return `${this.position.x}x ${this.position.y}y`;
}
return getStartLineNumber(this.position?.line_range); return getStartLineNumber(this.position?.line_range);
}, },
endLineNumber() { endLineNumber() {
...@@ -90,16 +93,12 @@ export default { ...@@ -90,16 +93,12 @@ export default {
<span> <span>
<span class="review-preview-item-header"> <span class="review-preview-item-header">
<gl-icon class="flex-shrink-0" :name="iconName" /> <gl-icon class="flex-shrink-0" :name="iconName" />
<span <span class="bold text-nowrap gl-align-items-center">
class="bold text-nowrap"
:class="{ 'gl-align-items-center': glFeatures.multilineComments }"
>
<span class="review-preview-item-header-text block-truncated"> <span class="review-preview-item-header-text block-truncated">
{{ titleText }} {{ titleText }}
</span> </span>
<template v-if="showLinePosition"> <template v-if="showLinePosition">
<template v-if="!glFeatures.multilineComments">:{{ linePosition }}</template> <template v-if="startLineNumber === endLineNumber">
<template v-else-if="startLineNumber === endLineNumber">
:<span :class="getLineClasses(startLineNumber)">{{ startLineNumber }}</span> :<span :class="getLineClasses(startLineNumber)">{{ startLineNumber }}</span>
</template> </template>
<gl-sprintf v-else :message="__(':%{startLine} to %{endLine}')"> <gl-sprintf v-else :message="__(':%{startLine} to %{endLine}')">
......
...@@ -165,7 +165,7 @@ export default { ...@@ -165,7 +165,7 @@ export default {
<template> <template>
<div class="content discussion-form discussion-form-container discussion-notes"> <div class="content discussion-form discussion-form-container discussion-notes">
<div v-if="glFeatures.multilineComments" class="gl-mb-3 gl-text-gray-500 gl-pb-3"> <div class="gl-mb-3 gl-text-gray-500 gl-pb-3">
<multiline-comment-form <multiline-comment-form
v-model="commentLineStart" v-model="commentLineStart"
:line="line" :line="line"
......
...@@ -4,7 +4,6 @@ import { __ } from '~/locale'; ...@@ -4,7 +4,6 @@ import { __ } from '~/locale';
import PlaceholderNote from '~/vue_shared/components/notes/placeholder_note.vue'; import PlaceholderNote from '~/vue_shared/components/notes/placeholder_note.vue';
import PlaceholderSystemNote from '~/vue_shared/components/notes/placeholder_system_note.vue'; import PlaceholderSystemNote from '~/vue_shared/components/notes/placeholder_system_note.vue';
import SystemNote from '~/vue_shared/components/notes/system_note.vue'; import SystemNote from '~/vue_shared/components/notes/system_note.vue';
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import { SYSTEM_NOTE } from '../constants'; import { SYSTEM_NOTE } from '../constants';
import NoteableNote from './noteable_note.vue'; import NoteableNote from './noteable_note.vue';
import ToggleRepliesWidget from './toggle_replies_widget.vue'; import ToggleRepliesWidget from './toggle_replies_widget.vue';
...@@ -18,7 +17,6 @@ export default { ...@@ -18,7 +17,6 @@ export default {
NoteEditedText, NoteEditedText,
DiscussionNotesRepliesWrapper, DiscussionNotesRepliesWrapper,
}, },
mixins: [glFeatureFlagsMixin()],
props: { props: {
discussion: { discussion: {
type: Object, type: Object,
...@@ -96,14 +94,14 @@ export default { ...@@ -96,14 +94,14 @@ export default {
return note.isPlaceholderNote ? note.notes[0] : note; return note.isPlaceholderNote ? note.notes[0] : note;
}, },
handleMouseEnter(discussion) { handleMouseEnter(discussion) {
if (this.glFeatures.multilineComments && discussion.position) { if (discussion.position) {
this.setSelectedCommentPositionHover(discussion.position.line_range); this.setSelectedCommentPositionHover(discussion.position.line_range);
} }
}, },
handleMouseLeave(discussion) { handleMouseLeave(discussion) {
// Even though position isn't used here we still don't want to unecessarily call a mutation // Even though position isn't used here we still don't want to unnecessarily call a mutation
// The lack of position tells us that highlighting is irrelevant in this context // The lack of position tells us that highlighting is irrelevant in this context
if (this.glFeatures.multilineComments && discussion.position) { if (discussion.position) {
this.setSelectedCommentPositionHover(); this.setSelectedCommentPositionHover();
} }
}, },
......
...@@ -3,7 +3,6 @@ import $ from 'jquery'; ...@@ -3,7 +3,6 @@ import $ from 'jquery';
import { mapGetters, mapActions } from 'vuex'; import { mapGetters, mapActions } from 'vuex';
import { escape } from 'lodash'; import { escape } from 'lodash';
import { GlSprintf, GlSafeHtmlDirective as SafeHtml } from '@gitlab/ui'; import { GlSprintf, GlSafeHtmlDirective as SafeHtml } from '@gitlab/ui';
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import { truncateSha } from '~/lib/utils/text_utility'; import { truncateSha } from '~/lib/utils/text_utility';
import TimelineEntryItem from '~/vue_shared/components/notes/timeline_entry_item.vue'; import TimelineEntryItem from '~/vue_shared/components/notes/timeline_entry_item.vue';
import httpStatusCodes from '~/lib/utils/http_status'; import httpStatusCodes from '~/lib/utils/http_status';
...@@ -38,7 +37,7 @@ export default { ...@@ -38,7 +37,7 @@ export default {
directives: { directives: {
SafeHtml, SafeHtml,
}, },
mixins: [noteable, resolvable, glFeatureFlagsMixin()], mixins: [noteable, resolvable],
props: { props: {
note: { note: {
type: Object, type: Object,
...@@ -160,7 +159,6 @@ export default { ...@@ -160,7 +159,6 @@ export default {
}, },
showMultiLineComment() { showMultiLineComment() {
if ( if (
!this.glFeatures.multilineComments ||
!this.discussionRoot || !this.discussionRoot ||
this.startLineNumber.length === 0 || this.startLineNumber.length === 0 ||
this.endLineNumber.length === 0 this.endLineNumber.length === 0
......
import { __ } from '~/locale'; import { s__ } from '~/locale';
import { deprecatedCreateFlash as flash } from '../flash'; import { deprecatedCreateFlash as flash } from '../flash';
import axios from '../lib/utils/axios_utils'; import axios from '../lib/utils/axios_utils';
import ProtectedTagAccessDropdown from './protected_tag_access_dropdown'; import ProtectedTagAccessDropdown from './protected_tag_access_dropdown';
...@@ -48,11 +48,8 @@ export default class ProtectedTagEdit { ...@@ -48,11 +48,8 @@ export default class ProtectedTagEdit {
.catch(() => { .catch(() => {
this.$allowedToCreateDropdownButton.enable(); this.$allowedToCreateDropdownButton.enable();
flash( window.scrollTo({ top: 0, behavior: 'smooth' });
__('Failed to update tag!'), flash(s__('ProjectSettings|Failed to update tag!'));
'alert',
document.querySelector('.js-protected-tags-list'),
);
}); });
} }
} }
...@@ -69,9 +69,6 @@ table { ...@@ -69,9 +69,6 @@ table {
} }
} }
td {
border-color: $white-normal;
}
} }
.thead-white { .thead-white {
......
...@@ -30,7 +30,6 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo ...@@ -30,7 +30,6 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
before_action :authenticate_user!, only: [:assign_related_issues] before_action :authenticate_user!, only: [:assign_related_issues]
before_action :check_user_can_push_to_source_branch!, only: [:rebase] before_action :check_user_can_push_to_source_branch!, only: [:rebase]
before_action only: [:show] do before_action only: [:show] do
push_frontend_feature_flag(:multiline_comments, @project, default_enabled: true)
push_frontend_feature_flag(:file_identifier_hash) push_frontend_feature_flag(:file_identifier_hash)
push_frontend_feature_flag(:batch_suggestions, @project, default_enabled: true) push_frontend_feature_flag(:batch_suggestions, @project, default_enabled: true)
push_frontend_feature_flag(:approvals_commented_by, @project, default_enabled: true) push_frontend_feature_flag(:approvals_commented_by, @project, default_enabled: true)
......
...@@ -23,7 +23,6 @@ ...@@ -23,7 +23,6 @@
%th %th
%tbody %tbody
%tr %tr
%td.flash-container{ colspan: 4 }
= yield = yield
= paginate @protected_tags, theme: 'gitlab' = paginate @protected_tags, theme: 'gitlab'
---
title: Fix protected branches/tags border
merge_request: 52816
author:
type: changed
---
name: multiline_comments
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/37114
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/211255
milestone: '13.2'
type: development
group: group::code review
default_enabled: true
...@@ -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
...@@ -195,12 +195,7 @@ to expand the diff lines and leave a comment, just as you would for a changed li ...@@ -195,12 +195,7 @@ to expand the diff lines and leave a comment, just as you would for a changed li
### Commenting on multiple lines ### Commenting on multiple lines
> - [Introduced](https://gitlab.com/gitlab-org/ux-research/-/issues/870) in GitLab 13.2. > - [Introduced](https://gitlab.com/gitlab-org/ux-research/-/issues/870) in GitLab 13.2.
> - It's deployed behind a feature flag, enabled by default. > - [Feature flag removed](https://gitlab.com/gitlab-org/gitlab/-/issues/299121) in GitLab 13.9.
> - [Became enabled by default](https://gitlab.com/gitlab-org/gitlab/-/issues/221268) on GitLab 13.3.
> - It's enabled on GitLab.com.
> - It can be disabled or enabled per-project.
> - It's recommended for production use.
> - For GitLab self-managed instances, GitLab administrators can opt to [disable it](#enable-or-disable-multiline-comments). **(FREE SELF)**
GitLab provides a way to select which lines of code a comment refers to. After starting a comment GitLab provides a way to select which lines of code a comment refers to. After starting a comment
a dropdown selector is shown to select the first line that this comment refers to. a dropdown selector is shown to select the first line that this comment refers to.
...@@ -216,25 +211,6 @@ above it. ...@@ -216,25 +211,6 @@ above it.
![Multiline comment selection displayed above comment](img/multiline-comment-saved.png) ![Multiline comment selection displayed above comment](img/multiline-comment-saved.png)
### Enable or disable multiline comments **(FREE SELF)**
The multiline comments feature is under development but ready for production use.
It is deployed behind a feature flag that is **disabled by default**.
[GitLab administrators with access to the GitLab Rails console](../../../administration/feature_flags.md)
can opt to enable it for your instance.
To disable it:
```ruby
Feature.disable(:multiline_comments)
```
To enable it:
```ruby
Feature.enable(:multiline_comments)
```
## Pipeline status in merge requests widgets ## Pipeline status in merge requests widgets
If you've set up [GitLab CI/CD](../../../ci/README.md) in your project, If you've set up [GitLab CI/CD](../../../ci/README.md) in your project,
......
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) }
...@@ -38,6 +38,7 @@ describe('EE DiffLineNoteForm', () => { ...@@ -38,6 +38,7 @@ describe('EE DiffLineNoteForm', () => {
diff_refs: { diff_refs: {
head_sha: headSha || null, head_sha: headSha || null,
}, },
highlighted_diff_lines: [],
})), })),
}, },
}, },
......
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
...@@ -536,15 +536,14 @@ RSpec.describe API::Issues, :mailer do ...@@ -536,15 +536,14 @@ RSpec.describe API::Issues, :mailer do
include WorkhorseHelpers include WorkhorseHelpers
using RSpec::Parameterized::TableSyntax using RSpec::Parameterized::TableSyntax
include_context 'workhorse headers'
let(:issue) { create(:incident, project: project) } let(:issue) { create(:incident, project: project) }
let(:file) { fixture_file_upload('spec/fixtures/rails_sample.jpg', 'image/jpg') } let(:file) { fixture_file_upload('spec/fixtures/rails_sample.jpg', 'image/jpg') }
let(:file_name) { 'rails_sample.jpg' } let(:file_name) { 'rails_sample.jpg' }
let(:url) { 'http://gitlab.com' } let(:url) { 'http://gitlab.com' }
let(:workhorse_token) { JWT.encode({ 'iss' => 'gitlab-workhorse' }, Gitlab::Workhorse.secret, 'HS256') }
let(:workhorse_header) { { 'GitLab-Workhorse' => '1.0', Gitlab::Workhorse::INTERNAL_API_REQUEST_HEADER => workhorse_token } }
let(:params) { { url: url } } let(:params) { { url: url } }
subject do subject do
...@@ -553,7 +552,7 @@ RSpec.describe API::Issues, :mailer do ...@@ -553,7 +552,7 @@ RSpec.describe API::Issues, :mailer do
method: :post, method: :post,
file_key: :file, file_key: :file,
params: params.merge(file: file), params: params.merge(file: file),
headers: workhorse_header, headers: workhorse_headers,
send_rewritten_field: true send_rewritten_field: true
) )
end end
......
...@@ -6,15 +6,14 @@ RSpec.describe API::ProjectImport do ...@@ -6,15 +6,14 @@ RSpec.describe API::ProjectImport do
include ExternalAuthorizationServiceHelpers include ExternalAuthorizationServiceHelpers
include WorkhorseHelpers include WorkhorseHelpers
include_context 'workhorse headers'
let(:user) { create(:user) } let(:user) { create(:user) }
let(:namespace) { create(:group) } let(:namespace) { create(:group) }
let(:file) { File.join('spec', 'features', 'projects', 'import_export', 'test_project_export.tar.gz') } let(:file) { File.join('spec', 'features', 'projects', 'import_export', 'test_project_export.tar.gz') }
let(:file_name) { 'project_export.tar.gz' } let(:file_name) { 'project_export.tar.gz' }
let(:workhorse_token) { JWT.encode({ 'iss' => 'gitlab-workhorse' }, Gitlab::Workhorse.secret, 'HS256') }
let(:workhorse_headers) { { 'GitLab-Workhorse' => '1.0', Gitlab::Workhorse::INTERNAL_API_REQUEST_HEADER => workhorse_token } }
let(:file_upload) { fixture_file_upload(file) } let(:file_upload) { fixture_file_upload(file) }
before do before do
......
...@@ -5,10 +5,10 @@ require 'spec_helper' ...@@ -5,10 +5,10 @@ require 'spec_helper'
RSpec.describe Projects::RequirementsManagement::RequirementsController do RSpec.describe Projects::RequirementsManagement::RequirementsController do
include WorkhorseHelpers include WorkhorseHelpers
include_context 'workhorse headers'
let_it_be(:user) { create(:user) } let_it_be(:user) { create(:user) }
let_it_be(:project) { create(:project, :public) } let_it_be(:project) { create(:project, :public) }
let(:workhorse_token) { JWT.encode({ 'iss' => 'gitlab-workhorse' }, Gitlab::Workhorse.secret, 'HS256') }
let(:workhorse_headers) { { 'GitLab-Workhorse' => '1.0', Gitlab::Workhorse::INTERNAL_API_REQUEST_HEADER => workhorse_token } }
shared_examples 'response with 404 status' do shared_examples 'response with 404 status' do
it 'returns 404' do it 'returns 404' do
......
...@@ -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 ""
...@@ -4141,6 +4225,9 @@ msgstr "" ...@@ -4141,6 +4225,9 @@ msgstr ""
msgid "Authenticating" msgid "Authenticating"
msgstr "" msgstr ""
msgid "Authentication"
msgstr ""
msgid "Authentication Failure" msgid "Authentication Failure"
msgstr "" msgstr ""
...@@ -11853,6 +11940,9 @@ msgstr "" ...@@ -11853,6 +11940,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 ""
...@@ -12300,9 +12390,6 @@ msgstr "" ...@@ -12300,9 +12390,6 @@ msgstr ""
msgid "Failed to update issues, please try again." msgid "Failed to update issues, please try again."
msgstr "" msgstr ""
msgid "Failed to update tag!"
msgstr ""
msgid "Failed to update the Canary Ingress." msgid "Failed to update the Canary Ingress."
msgstr "" msgstr ""
...@@ -14513,6 +14600,12 @@ msgstr "" ...@@ -14513,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 ""
...@@ -20670,6 +20763,12 @@ msgstr "" ...@@ -20670,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 ""
......
...@@ -21,14 +21,11 @@ describe('Batch comments draft note component', () => { ...@@ -21,14 +21,11 @@ describe('Batch comments draft note component', () => {
const getList = () => getByRole(wrapper.element, 'list'); const getList = () => getByRole(wrapper.element, 'list');
const createComponent = (propsData = { draft }, features = {}) => { const createComponent = (propsData = { draft }) => {
wrapper = shallowMount(localVue.extend(DraftNote), { wrapper = shallowMount(localVue.extend(DraftNote), {
store, store,
propsData, propsData,
localVue, localVue,
provide: {
glFeatures: { multilineComments: true, ...features },
},
}); });
jest.spyOn(wrapper.vm.$store, 'dispatch').mockImplementation(); jest.spyOn(wrapper.vm.$store, 'dispatch').mockImplementation();
...@@ -145,16 +142,14 @@ describe('Batch comments draft note component', () => { ...@@ -145,16 +142,14 @@ describe('Batch comments draft note component', () => {
describe('multiline comments', () => { describe('multiline comments', () => {
describe.each` describe.each`
desc | props | features | event | expectedCalls desc | props | event | expectedCalls
${'with `draft.position`'} | ${draftWithLineRange} | ${{}} | ${'mouseenter'} | ${[['setSelectedCommentPositionHover', LINE_RANGE]]} ${'with `draft.position`'} | ${draftWithLineRange} | ${'mouseenter'} | ${[['setSelectedCommentPositionHover', LINE_RANGE]]}
${'with `draft.position`'} | ${draftWithLineRange} | ${{}} | ${'mouseleave'} | ${[['setSelectedCommentPositionHover']]} ${'with `draft.position`'} | ${draftWithLineRange} | ${'mouseleave'} | ${[['setSelectedCommentPositionHover']]}
${'with `draft.position`'} | ${draftWithLineRange} | ${{ multilineComments: false }} | ${'mouseenter'} | ${[]} ${'without `draft.position`'} | ${{}} | ${'mouseenter'} | ${[]}
${'with `draft.position`'} | ${draftWithLineRange} | ${{ multilineComments: false }} | ${'mouseleave'} | ${[]} ${'without `draft.position`'} | ${{}} | ${'mouseleave'} | ${[]}
${'without `draft.position`'} | ${{}} | ${{}} | ${'mouseenter'} | ${[]} `('$desc', ({ props, event, expectedCalls }) => {
${'without `draft.position`'} | ${{}} | ${{}} | ${'mouseleave'} | ${[]}
`('$desc and features $features', ({ props, event, features, expectedCalls }) => {
beforeEach(() => { beforeEach(() => {
createComponent({ draft: { ...draft, ...props } }, features); createComponent({ draft: { ...draft, ...props } });
jest.spyOn(store, 'dispatch'); jest.spyOn(store, 'dispatch');
}); });
......
...@@ -56,17 +56,30 @@ describe('Batch comments draft preview item component', () => { ...@@ -56,17 +56,30 @@ describe('Batch comments draft preview item component', () => {
createComponent(false, { createComponent(false, {
file_path: 'index.js', file_path: 'index.js',
file_hash: 'abc', file_hash: 'abc',
position: { new_line: 1 }, position: {
line_range: {
start: {
new_line: 1,
type: 'new',
},
},
},
}); });
expect(vm.$el.querySelector('.bold').textContent).toContain(':1'); expect(vm.$el.querySelector('.bold').textContent).toContain(':+1');
}); });
it('renders old line position', () => { it('renders old line position', () => {
createComponent(false, { createComponent(false, {
file_path: 'index.js', file_path: 'index.js',
file_hash: 'abc', file_hash: 'abc',
position: { old_line: 2 }, position: {
line_range: {
start: {
old_line: 2,
},
},
},
}); });
expect(vm.$el.querySelector('.bold').textContent).toContain(':2'); expect(vm.$el.querySelector('.bold').textContent).toContain(':2');
......
...@@ -17,6 +17,7 @@ describe('DiffLineNoteForm', () => { ...@@ -17,6 +17,7 @@ describe('DiffLineNoteForm', () => {
const store = createStore(); const store = createStore();
store.state.notes.userData.id = 1; store.state.notes.userData.id = 1;
store.state.notes.noteableData = noteableDataMock; store.state.notes.noteableData = noteableDataMock;
store.state.diffs.diffFiles = [diffFile];
store.replaceState({ ...store.state, ...args.state }); store.replaceState({ ...store.state, ...args.state });
......
...@@ -23,7 +23,7 @@ describe('DiscussionNotes', () => { ...@@ -23,7 +23,7 @@ describe('DiscussionNotes', () => {
let wrapper; let wrapper;
const getList = () => getByRole(wrapper.element, 'list'); const getList = () => getByRole(wrapper.element, 'list');
const createComponent = (props, features = {}) => { const createComponent = (props) => {
wrapper = shallowMount(DiscussionNotes, { wrapper = shallowMount(DiscussionNotes, {
store, store,
propsData: { propsData: {
...@@ -38,9 +38,6 @@ describe('DiscussionNotes', () => { ...@@ -38,9 +38,6 @@ describe('DiscussionNotes', () => {
slots: { slots: {
'avatar-badge': '<span class="avatar-badge-slot-content" />', 'avatar-badge': '<span class="avatar-badge-slot-content" />',
}, },
provide: {
glFeatures: { multilineComments: true, ...features },
},
}); });
}; };
...@@ -177,16 +174,14 @@ describe('DiscussionNotes', () => { ...@@ -177,16 +174,14 @@ describe('DiscussionNotes', () => {
}); });
describe.each` describe.each`
desc | props | features | event | expectedCalls desc | props | event | expectedCalls
${'with `discussion.position`'} | ${{ discussion: DISCUSSION_WITH_LINE_RANGE }} | ${{}} | ${'mouseenter'} | ${[['setSelectedCommentPositionHover', LINE_RANGE]]} ${'with `discussion.position`'} | ${{ discussion: DISCUSSION_WITH_LINE_RANGE }} | ${'mouseenter'} | ${[['setSelectedCommentPositionHover', LINE_RANGE]]}
${'with `discussion.position`'} | ${{ discussion: DISCUSSION_WITH_LINE_RANGE }} | ${{}} | ${'mouseleave'} | ${[['setSelectedCommentPositionHover']]} ${'with `discussion.position`'} | ${{ discussion: DISCUSSION_WITH_LINE_RANGE }} | ${'mouseleave'} | ${[['setSelectedCommentPositionHover']]}
${'with `discussion.position`'} | ${{ discussion: DISCUSSION_WITH_LINE_RANGE }} | ${{ multilineComments: false }} | ${'mouseenter'} | ${[]} ${'without `discussion.position`'} | ${{}} | ${'mouseenter'} | ${[]}
${'with `discussion.position`'} | ${{ discussion: DISCUSSION_WITH_LINE_RANGE }} | ${{ multilineComments: false }} | ${'mouseleave'} | ${[]} ${'without `discussion.position`'} | ${{}} | ${'mouseleave'} | ${[]}
${'without `discussion.position`'} | ${{}} | ${{}} | ${'mouseenter'} | ${[]} `('$desc', ({ props, event, expectedCalls }) => {
${'without `discussion.position`'} | ${{}} | ${{}} | ${'mouseleave'} | ${[]}
`('$desc and features $features', ({ props, event, features, expectedCalls }) => {
beforeEach(() => { beforeEach(() => {
createComponent(props, features); createComponent(props);
jest.spyOn(store, 'dispatch'); jest.spyOn(store, 'dispatch');
}); });
......
...@@ -8,15 +8,6 @@ import NoteActions from '~/notes/components/note_actions.vue'; ...@@ -8,15 +8,6 @@ import NoteActions from '~/notes/components/note_actions.vue';
import NoteBody from '~/notes/components/note_body.vue'; import NoteBody from '~/notes/components/note_body.vue';
import { noteableDataMock, notesDataMock, note } from '../mock_data'; import { noteableDataMock, notesDataMock, note } from '../mock_data';
jest.mock('~/vue_shared/mixins/gl_feature_flags_mixin', () => () => ({
inject: {
glFeatures: {
from: 'glFeatures',
default: () => ({ multilineComments: true }),
},
},
}));
describe('issue_note', () => { describe('issue_note', () => {
let store; let store;
let wrapper; let wrapper;
......
...@@ -6,6 +6,8 @@ RSpec.describe API::GenericPackages do ...@@ -6,6 +6,8 @@ RSpec.describe API::GenericPackages do
include HttpBasicAuthHelpers include HttpBasicAuthHelpers
using RSpec::Parameterized::TableSyntax using RSpec::Parameterized::TableSyntax
include_context 'workhorse headers'
let_it_be(:personal_access_token) { create(:personal_access_token) } let_it_be(:personal_access_token) { create(:personal_access_token) }
let_it_be(:project, reload: true) { create(:project) } let_it_be(:project, reload: true) { create(:project) }
let_it_be(:deploy_token_rw) { create(:deploy_token, read_package_registry: true, write_package_registry: true) } let_it_be(:deploy_token_rw) { create(:deploy_token, read_package_registry: true, write_package_registry: true) }
...@@ -14,8 +16,6 @@ RSpec.describe API::GenericPackages do ...@@ -14,8 +16,6 @@ RSpec.describe API::GenericPackages do
let_it_be(:project_deploy_token_ro) { create(:project_deploy_token, deploy_token: deploy_token_ro, project: project) } let_it_be(:project_deploy_token_ro) { create(:project_deploy_token, deploy_token: deploy_token_ro, project: project) }
let_it_be(:deploy_token_wo) { create(:deploy_token, read_package_registry: false, write_package_registry: true) } let_it_be(:deploy_token_wo) { create(:deploy_token, read_package_registry: false, write_package_registry: true) }
let_it_be(:project_deploy_token_wo) { create(:project_deploy_token, deploy_token: deploy_token_wo, project: project) } let_it_be(:project_deploy_token_wo) { create(:project_deploy_token, deploy_token: deploy_token_wo, project: project) }
let(:workhorse_token) { JWT.encode({ 'iss' => 'gitlab-workhorse' }, Gitlab::Workhorse.secret, 'HS256') }
let(:workhorse_header) { { 'GitLab-Workhorse' => '1.0', Gitlab::Workhorse::INTERNAL_API_REQUEST_HEADER => workhorse_token } }
let(:user) { personal_access_token.user } let(:user) { personal_access_token.user }
let(:ci_build) { create(:ci_build, :running, user: user) } let(:ci_build) { create(:ci_build, :running, user: user) }
...@@ -129,7 +129,7 @@ RSpec.describe API::GenericPackages do ...@@ -129,7 +129,7 @@ RSpec.describe API::GenericPackages do
end end
it "responds with #{params[:expected_status]}" do it "responds with #{params[:expected_status]}" do
authorize_upload_file(workhorse_header.merge(auth_header)) authorize_upload_file(workhorse_headers.merge(auth_header))
expect(response).to have_gitlab_http_status(expected_status) expect(response).to have_gitlab_http_status(expected_status)
end end
...@@ -144,7 +144,7 @@ RSpec.describe API::GenericPackages do ...@@ -144,7 +144,7 @@ RSpec.describe API::GenericPackages do
with_them do with_them do
it "responds with #{params[:expected_status]}" do it "responds with #{params[:expected_status]}" do
authorize_upload_file(workhorse_header.merge(deploy_token_auth_header)) authorize_upload_file(workhorse_headers.merge(deploy_token_auth_header))
expect(response).to have_gitlab_http_status(expected_status) expect(response).to have_gitlab_http_status(expected_status)
end end
...@@ -162,7 +162,7 @@ RSpec.describe API::GenericPackages do ...@@ -162,7 +162,7 @@ RSpec.describe API::GenericPackages do
end end
with_them do with_them do
subject { authorize_upload_file(workhorse_header.merge(personal_access_token_header), param_name => param_value) } subject { authorize_upload_file(workhorse_headers.merge(personal_access_token_header), param_name => param_value) }
it_behaves_like 'secure endpoint' it_behaves_like 'secure endpoint'
end end
...@@ -173,7 +173,7 @@ RSpec.describe API::GenericPackages do ...@@ -173,7 +173,7 @@ RSpec.describe API::GenericPackages do
stub_feature_flags(generic_packages: false) stub_feature_flags(generic_packages: false)
project.add_developer(user) project.add_developer(user)
authorize_upload_file(workhorse_header.merge(personal_access_token_header)) authorize_upload_file(workhorse_headers.merge(personal_access_token_header))
expect(response).to have_gitlab_http_status(:not_found) expect(response).to have_gitlab_http_status(:not_found)
end end
...@@ -239,7 +239,7 @@ RSpec.describe API::GenericPackages do ...@@ -239,7 +239,7 @@ RSpec.describe API::GenericPackages do
end end
it "responds with #{params[:expected_status]}" do it "responds with #{params[:expected_status]}" do
headers = workhorse_header.merge(auth_header) headers = workhorse_headers.merge(auth_header)
upload_file(params, headers) upload_file(params, headers)
...@@ -254,7 +254,7 @@ RSpec.describe API::GenericPackages do ...@@ -254,7 +254,7 @@ RSpec.describe API::GenericPackages do
with_them do with_them do
it "responds with #{params[:expected_status]}" do it "responds with #{params[:expected_status]}" do
headers = workhorse_header.merge(deploy_token_auth_header) headers = workhorse_headers.merge(deploy_token_auth_header)
upload_file(params, headers) upload_file(params, headers)
...@@ -270,7 +270,7 @@ RSpec.describe API::GenericPackages do ...@@ -270,7 +270,7 @@ RSpec.describe API::GenericPackages do
shared_examples 'creates a package and package file' do shared_examples 'creates a package and package file' do
it 'creates a package and package file' do it 'creates a package and package file' do
headers = workhorse_header.merge(auth_header) headers = workhorse_headers.merge(auth_header)
expect { upload_file(params, headers) } expect { upload_file(params, headers) }
.to change { project.packages.generic.count }.by(1) .to change { project.packages.generic.count }.by(1)
...@@ -324,26 +324,26 @@ RSpec.describe API::GenericPackages do ...@@ -324,26 +324,26 @@ RSpec.describe API::GenericPackages do
end end
context 'event tracking' do context 'event tracking' do
subject { upload_file(params, workhorse_header.merge(personal_access_token_header)) } subject { upload_file(params, workhorse_headers.merge(personal_access_token_header)) }
it_behaves_like 'a gitlab tracking event', described_class.name, 'push_package' it_behaves_like 'a gitlab tracking event', described_class.name, 'push_package'
end end
it 'rejects request without a file from workhorse' do it 'rejects request without a file from workhorse' do
headers = workhorse_header.merge(personal_access_token_header) headers = workhorse_headers.merge(personal_access_token_header)
upload_file({}, headers) upload_file({}, headers)
expect(response).to have_gitlab_http_status(:bad_request) expect(response).to have_gitlab_http_status(:bad_request)
end end
it 'rejects request without an auth token' do it 'rejects request without an auth token' do
upload_file(params, workhorse_header) upload_file(params, workhorse_headers)
expect(response).to have_gitlab_http_status(:unauthorized) expect(response).to have_gitlab_http_status(:unauthorized)
end end
it 'rejects request without workhorse rewritten fields' do it 'rejects request without workhorse rewritten fields' do
headers = workhorse_header.merge(personal_access_token_header) headers = workhorse_headers.merge(personal_access_token_header)
upload_file(params, headers, send_rewritten_field: false) upload_file(params, headers, send_rewritten_field: false)
expect(response).to have_gitlab_http_status(:bad_request) expect(response).to have_gitlab_http_status(:bad_request)
...@@ -354,7 +354,7 @@ RSpec.describe API::GenericPackages do ...@@ -354,7 +354,7 @@ RSpec.describe API::GenericPackages do
allow(uploaded_file).to receive(:size).and_return(project.actual_limits.generic_packages_max_file_size + 1) allow(uploaded_file).to receive(:size).and_return(project.actual_limits.generic_packages_max_file_size + 1)
end end
headers = workhorse_header.merge(personal_access_token_header) headers = workhorse_headers.merge(personal_access_token_header)
upload_file(params, headers) upload_file(params, headers)
expect(response).to have_gitlab_http_status(:bad_request) expect(response).to have_gitlab_http_status(:bad_request)
...@@ -378,7 +378,7 @@ RSpec.describe API::GenericPackages do ...@@ -378,7 +378,7 @@ RSpec.describe API::GenericPackages do
end end
with_them do with_them do
subject { upload_file(params, workhorse_header.merge(personal_access_token_header), param_name => param_value) } subject { upload_file(params, workhorse_headers.merge(personal_access_token_header), param_name => param_value) }
it_behaves_like 'secure endpoint' it_behaves_like 'secure endpoint'
end end
......
...@@ -5,13 +5,13 @@ require 'spec_helper' ...@@ -5,13 +5,13 @@ require 'spec_helper'
RSpec.describe API::GroupImport do RSpec.describe API::GroupImport do
include WorkhorseHelpers include WorkhorseHelpers
include_context 'workhorse headers'
let_it_be(:user) { create(:user) } let_it_be(:user) { create(:user) }
let_it_be(:group) { create(:group) } let_it_be(:group) { create(:group) }
let(:path) { '/groups/import' } let(:path) { '/groups/import' }
let(:file) { File.join('spec', 'fixtures', 'group_export.tar.gz') } let(:file) { File.join('spec', 'fixtures', 'group_export.tar.gz') }
let(:export_path) { "#{Dir.tmpdir}/group_export_spec" } let(:export_path) { "#{Dir.tmpdir}/group_export_spec" }
let(:workhorse_token) { JWT.encode({ 'iss' => 'gitlab-workhorse' }, Gitlab::Workhorse.secret, 'HS256') }
let(:workhorse_headers) { { 'GitLab-Workhorse' => '1.0', Gitlab::Workhorse::INTERNAL_API_REQUEST_HEADER => workhorse_token } }
before do before do
allow_next_instance_of(Gitlab::ImportExport) do |import_export| allow_next_instance_of(Gitlab::ImportExport) do |import_export|
......
...@@ -4,6 +4,8 @@ require 'spec_helper' ...@@ -4,6 +4,8 @@ require 'spec_helper'
RSpec.describe API::MavenPackages do RSpec.describe API::MavenPackages do
include WorkhorseHelpers include WorkhorseHelpers
include_context 'workhorse headers'
let_it_be_with_refind(:package_settings) { create(:namespace_package_setting, :group) } let_it_be_with_refind(:package_settings) { create(:namespace_package_setting, :group) }
let_it_be(:group) { package_settings.namespace } let_it_be(:group) { package_settings.namespace }
let_it_be(:user) { create(:user) } let_it_be(:user) { create(:user) }
...@@ -20,8 +22,7 @@ RSpec.describe API::MavenPackages do ...@@ -20,8 +22,7 @@ RSpec.describe API::MavenPackages do
let_it_be(:group_deploy_token) { create(:group_deploy_token, deploy_token: deploy_token_for_group, group: group) } let_it_be(:group_deploy_token) { create(:group_deploy_token, deploy_token: deploy_token_for_group, group: group) }
let(:package_name) { 'com/example/my-app' } let(:package_name) { 'com/example/my-app' }
let(:workhorse_token) { JWT.encode({ 'iss' => 'gitlab-workhorse' }, Gitlab::Workhorse.secret, 'HS256') } let(:headers) { workhorse_headers }
let(:headers) { { 'GitLab-Workhorse' => '1.0', Gitlab::Workhorse::INTERNAL_API_REQUEST_HEADER => workhorse_token } }
let(:headers_with_token) { headers.merge('Private-Token' => personal_access_token.token) } let(:headers_with_token) { headers.merge('Private-Token' => personal_access_token.token) }
let(:group_deploy_token_headers) { { Gitlab::Auth::AuthFinders::DEPLOY_TOKEN_HEADER => deploy_token_for_group.token } } let(:group_deploy_token_headers) { { Gitlab::Auth::AuthFinders::DEPLOY_TOKEN_HEADER => deploy_token_for_group.token } }
...@@ -548,8 +549,8 @@ RSpec.describe API::MavenPackages do ...@@ -548,8 +549,8 @@ RSpec.describe API::MavenPackages do
end end
describe 'PUT /api/v4/projects/:id/packages/maven/*path/:file_name' do describe 'PUT /api/v4/projects/:id/packages/maven/*path/:file_name' do
let(:workhorse_token) { JWT.encode({ 'iss' => 'gitlab-workhorse' }, Gitlab::Workhorse.secret, 'HS256') } include_context 'workhorse headers'
let(:workhorse_header) { { 'GitLab-Workhorse' => '1.0', Gitlab::Workhorse::INTERNAL_API_REQUEST_HEADER => workhorse_token } }
let(:send_rewritten_field) { true } let(:send_rewritten_field) { true }
let(:file_upload) { fixture_file_upload('spec/fixtures/packages/maven/my-app-1.0-20180724.124855-1.jar') } let(:file_upload) { fixture_file_upload('spec/fixtures/packages/maven/my-app-1.0-20180724.124855-1.jar') }
...@@ -602,7 +603,7 @@ RSpec.describe API::MavenPackages do ...@@ -602,7 +603,7 @@ RSpec.describe API::MavenPackages do
end end
context 'without workhorse header' do context 'without workhorse header' do
let(:workhorse_header) { {} } let(:workhorse_headers) { {} }
subject { upload_file_with_token(params: params) } subject { upload_file_with_token(params: params) }
......
...@@ -144,8 +144,8 @@ RSpec.describe API::NugetProjectPackages do ...@@ -144,8 +144,8 @@ RSpec.describe API::NugetProjectPackages do
end end
describe 'PUT /api/v4/projects/:id/packages/nuget/authorize' do describe 'PUT /api/v4/projects/:id/packages/nuget/authorize' do
let_it_be(:workhorse_token) { JWT.encode({ 'iss' => 'gitlab-workhorse' }, Gitlab::Workhorse.secret, 'HS256') } include_context 'workhorse headers'
let_it_be(:workhorse_header) { { 'GitLab-Workhorse' => '1.0', Gitlab::Workhorse::INTERNAL_API_REQUEST_HEADER => workhorse_token } }
let(:url) { "/projects/#{target.id}/packages/nuget/authorize" } let(:url) { "/projects/#{target.id}/packages/nuget/authorize" }
let(:headers) { {} } let(:headers) { {} }
...@@ -176,7 +176,7 @@ RSpec.describe API::NugetProjectPackages do ...@@ -176,7 +176,7 @@ RSpec.describe API::NugetProjectPackages do
with_them do with_them do
let(:token) { user_token ? personal_access_token.token : 'wrong' } let(:token) { user_token ? personal_access_token.token : 'wrong' }
let(:user_headers) { user_role == :anonymous ? {} : basic_auth_header(user.username, token) } let(:user_headers) { user_role == :anonymous ? {} : basic_auth_header(user.username, token) }
let(:headers) { user_headers.merge(workhorse_header) } let(:headers) { user_headers.merge(workhorse_headers) }
before do before do
update_visibility_to(Gitlab::VisibilityLevel.const_get(visibility_level, false)) update_visibility_to(Gitlab::VisibilityLevel.const_get(visibility_level, false))
...@@ -194,8 +194,8 @@ RSpec.describe API::NugetProjectPackages do ...@@ -194,8 +194,8 @@ RSpec.describe API::NugetProjectPackages do
end end
describe 'PUT /api/v4/projects/:id/packages/nuget' do describe 'PUT /api/v4/projects/:id/packages/nuget' do
let(:workhorse_token) { JWT.encode({ 'iss' => 'gitlab-workhorse' }, Gitlab::Workhorse.secret, 'HS256') } include_context 'workhorse headers'
let(:workhorse_header) { { 'GitLab-Workhorse' => '1.0', Gitlab::Workhorse::INTERNAL_API_REQUEST_HEADER => workhorse_token } }
let_it_be(:file_name) { 'package.nupkg' } let_it_be(:file_name) { 'package.nupkg' }
let(:url) { "/projects/#{target.id}/packages/nuget" } let(:url) { "/projects/#{target.id}/packages/nuget" }
let(:headers) { {} } let(:headers) { {} }
...@@ -239,7 +239,7 @@ RSpec.describe API::NugetProjectPackages do ...@@ -239,7 +239,7 @@ RSpec.describe API::NugetProjectPackages do
with_them do with_them do
let(:token) { user_token ? personal_access_token.token : 'wrong' } let(:token) { user_token ? personal_access_token.token : 'wrong' }
let(:user_headers) { user_role == :anonymous ? {} : basic_auth_header(user.username, token) } let(:user_headers) { user_role == :anonymous ? {} : basic_auth_header(user.username, token) }
let(:headers) { user_headers.merge(workhorse_header) } let(:headers) { user_headers.merge(workhorse_headers) }
before do before do
update_visibility_to(Gitlab::VisibilityLevel.const_get(visibility_level, false)) update_visibility_to(Gitlab::VisibilityLevel.const_get(visibility_level, false))
...@@ -256,7 +256,7 @@ RSpec.describe API::NugetProjectPackages do ...@@ -256,7 +256,7 @@ RSpec.describe API::NugetProjectPackages do
it_behaves_like 'rejects nuget access with invalid target id' it_behaves_like 'rejects nuget access with invalid target id'
context 'file size above maximum limit' do context 'file size above maximum limit' do
let(:headers) { basic_auth_header(deploy_token.username, deploy_token.token).merge(workhorse_header) } let(:headers) { basic_auth_header(deploy_token.username, deploy_token.token).merge(workhorse_headers) }
before do before do
allow_next_instance_of(UploadedFile) do |uploaded_file| allow_next_instance_of(UploadedFile) do |uploaded_file|
......
...@@ -5,13 +5,12 @@ require 'spec_helper' ...@@ -5,13 +5,12 @@ require 'spec_helper'
RSpec.describe API::ProjectImport do RSpec.describe API::ProjectImport do
include WorkhorseHelpers include WorkhorseHelpers
include_context 'workhorse headers'
let(:user) { create(:user) } let(:user) { create(:user) }
let(:file) { File.join('spec', 'features', 'projects', 'import_export', 'test_project_export.tar.gz') } let(:file) { File.join('spec', 'features', 'projects', 'import_export', 'test_project_export.tar.gz') }
let(:namespace) { create(:group) } let(:namespace) { create(:group) }
let(:workhorse_token) { JWT.encode({ 'iss' => 'gitlab-workhorse' }, Gitlab::Workhorse.secret, 'HS256') }
let(:workhorse_headers) { { 'GitLab-Workhorse' => '1.0', Gitlab::Workhorse::INTERNAL_API_REQUEST_HEADER => workhorse_token } }
before do before do
namespace.add_owner(user) namespace.add_owner(user)
end end
......
...@@ -74,8 +74,8 @@ RSpec.describe API::PypiPackages do ...@@ -74,8 +74,8 @@ RSpec.describe API::PypiPackages do
end end
describe 'POST /api/v4/projects/:id/packages/pypi/authorize' do describe 'POST /api/v4/projects/:id/packages/pypi/authorize' do
let_it_be(:workhorse_token) { JWT.encode({ 'iss' => 'gitlab-workhorse' }, Gitlab::Workhorse.secret, 'HS256') } include_context 'workhorse headers'
let_it_be(:workhorse_header) { { 'GitLab-Workhorse' => '1.0', Gitlab::Workhorse::INTERNAL_API_REQUEST_HEADER => workhorse_token } }
let(:url) { "/projects/#{project.id}/packages/pypi/authorize" } let(:url) { "/projects/#{project.id}/packages/pypi/authorize" }
let(:headers) { {} } let(:headers) { {} }
...@@ -106,7 +106,7 @@ RSpec.describe API::PypiPackages do ...@@ -106,7 +106,7 @@ RSpec.describe API::PypiPackages do
with_them do with_them do
let(:token) { user_token ? personal_access_token.token : 'wrong' } let(:token) { user_token ? personal_access_token.token : 'wrong' }
let(:user_headers) { user_role == :anonymous ? {} : basic_auth_header(user.username, token) } let(:user_headers) { user_role == :anonymous ? {} : basic_auth_header(user.username, token) }
let(:headers) { user_headers.merge(workhorse_header) } let(:headers) { user_headers.merge(workhorse_headers) }
before do before do
project.update!(visibility_level: Gitlab::VisibilityLevel.const_get(project_visibility_level, false)) project.update!(visibility_level: Gitlab::VisibilityLevel.const_get(project_visibility_level, false))
...@@ -124,8 +124,8 @@ RSpec.describe API::PypiPackages do ...@@ -124,8 +124,8 @@ RSpec.describe API::PypiPackages do
end end
describe 'POST /api/v4/projects/:id/packages/pypi' do describe 'POST /api/v4/projects/:id/packages/pypi' do
let(:workhorse_token) { JWT.encode({ 'iss' => 'gitlab-workhorse' }, Gitlab::Workhorse.secret, 'HS256') } include_context 'workhorse headers'
let(:workhorse_header) { { 'GitLab-Workhorse' => '1.0', Gitlab::Workhorse::INTERNAL_API_REQUEST_HEADER => workhorse_token } }
let_it_be(:file_name) { 'package.whl' } let_it_be(:file_name) { 'package.whl' }
let(:url) { "/projects/#{project.id}/packages/pypi" } let(:url) { "/projects/#{project.id}/packages/pypi" }
let(:headers) { {} } let(:headers) { {} }
...@@ -170,7 +170,7 @@ RSpec.describe API::PypiPackages do ...@@ -170,7 +170,7 @@ RSpec.describe API::PypiPackages do
with_them do with_them do
let(:token) { user_token ? personal_access_token.token : 'wrong' } let(:token) { user_token ? personal_access_token.token : 'wrong' }
let(:user_headers) { user_role == :anonymous ? {} : basic_auth_header(user.username, token) } let(:user_headers) { user_role == :anonymous ? {} : basic_auth_header(user.username, token) }
let(:headers) { user_headers.merge(workhorse_header) } let(:headers) { user_headers.merge(workhorse_headers) }
before do before do
project.update!(visibility_level: Gitlab::VisibilityLevel.const_get(project_visibility_level, false)) project.update!(visibility_level: Gitlab::VisibilityLevel.const_get(project_visibility_level, false))
...@@ -184,7 +184,7 @@ RSpec.describe API::PypiPackages do ...@@ -184,7 +184,7 @@ RSpec.describe API::PypiPackages do
let(:requires_python) { 'x' * 256 } let(:requires_python) { 'x' * 256 }
let(:token) { personal_access_token.token } let(:token) { personal_access_token.token }
let(:user_headers) { basic_auth_header(user.username, token) } let(:user_headers) { basic_auth_header(user.username, token) }
let(:headers) { user_headers.merge(workhorse_header) } let(:headers) { user_headers.merge(workhorse_headers) }
before do before do
project.update!(visibility_level: Gitlab::VisibilityLevel::PRIVATE) project.update!(visibility_level: Gitlab::VisibilityLevel::PRIVATE)
...@@ -196,7 +196,7 @@ RSpec.describe API::PypiPackages do ...@@ -196,7 +196,7 @@ RSpec.describe API::PypiPackages do
context 'with an invalid package' do context 'with an invalid package' do
let(:token) { personal_access_token.token } let(:token) { personal_access_token.token }
let(:user_headers) { basic_auth_header(user.username, token) } let(:user_headers) { basic_auth_header(user.username, token) }
let(:headers) { user_headers.merge(workhorse_header) } let(:headers) { user_headers.merge(workhorse_headers) }
before do before do
params[:name] = '.$/@!^*' params[:name] = '.$/@!^*'
...@@ -213,7 +213,7 @@ RSpec.describe API::PypiPackages do ...@@ -213,7 +213,7 @@ RSpec.describe API::PypiPackages do
it_behaves_like 'rejects PyPI access with unknown project id' it_behaves_like 'rejects PyPI access with unknown project id'
context 'file size above maximum limit' do context 'file size above maximum limit' do
let(:headers) { basic_auth_header(deploy_token.username, deploy_token.token).merge(workhorse_header) } let(:headers) { basic_auth_header(deploy_token.username, deploy_token.token).merge(workhorse_headers) }
before do before do
allow_next_instance_of(UploadedFile) do |uploaded_file| allow_next_instance_of(UploadedFile) do |uploaded_file|
......
...@@ -5,12 +5,10 @@ require 'spec_helper' ...@@ -5,12 +5,10 @@ require 'spec_helper'
RSpec.describe Import::GitlabGroupsController do RSpec.describe Import::GitlabGroupsController do
include WorkhorseHelpers include WorkhorseHelpers
include_context 'workhorse headers'
let_it_be(:user) { create(:user) } let_it_be(:user) { create(:user) }
let(:import_path) { "#{Dir.tmpdir}/gitlab_groups_controller_spec" } let(:import_path) { "#{Dir.tmpdir}/gitlab_groups_controller_spec" }
let(:workhorse_token) { JWT.encode({ 'iss' => 'gitlab-workhorse' }, Gitlab::Workhorse.secret, 'HS256') }
let(:workhorse_headers) do
{ 'GitLab-Workhorse' => '1.0', Gitlab::Workhorse::INTERNAL_API_REQUEST_HEADER => workhorse_token }
end
before do before do
allow_next_instance_of(Gitlab::ImportExport) do |import_export| allow_next_instance_of(Gitlab::ImportExport) do |import_export|
......
...@@ -5,8 +5,7 @@ require 'spec_helper' ...@@ -5,8 +5,7 @@ require 'spec_helper'
RSpec.describe Import::GitlabProjectsController do RSpec.describe Import::GitlabProjectsController do
include WorkhorseHelpers include WorkhorseHelpers
let(:workhorse_token) { JWT.encode({ 'iss' => 'gitlab-workhorse' }, Gitlab::Workhorse.secret, 'HS256') } include_context 'workhorse headers'
let(:workhorse_headers) { { 'GitLab-Workhorse' => '1.0', Gitlab::Workhorse::INTERNAL_API_REQUEST_HEADER => workhorse_token } }
let_it_be(:namespace) { create(:namespace) } let_it_be(:namespace) { create(:namespace) }
let_it_be(:user) { namespace.owner } let_it_be(:user) { namespace.owner }
......
...@@ -67,9 +67,9 @@ RSpec.shared_context 'conan file upload endpoints' do ...@@ -67,9 +67,9 @@ RSpec.shared_context 'conan file upload endpoints' do
include WorkhorseHelpers include WorkhorseHelpers
include HttpBasicAuthHelpers include HttpBasicAuthHelpers
include_context 'workhorse headers'
let(:jwt) { build_jwt(personal_access_token) } let(:jwt) { build_jwt(personal_access_token) }
let(:workhorse_token) { JWT.encode({ 'iss' => 'gitlab-workhorse' }, Gitlab::Workhorse.secret, 'HS256') } let(:headers_with_token) { build_token_auth_header(jwt.encoded).merge(workhorse_headers) }
let(:workhorse_header) { { 'GitLab-Workhorse' => '1.0', Gitlab::Workhorse::INTERNAL_API_REQUEST_HEADER => workhorse_token } }
let(:headers_with_token) { build_token_auth_header(jwt.encoded).merge(workhorse_header) }
let(:recipe_path) { "foo/bar/#{project.full_path.tr('/', '+')}/baz"} let(:recipe_path) { "foo/bar/#{project.full_path.tr('/', '+')}/baz"}
end end
# frozen_string_literal: true
RSpec.shared_context 'workhorse headers' do
let(:workhorse_token) { JWT.encode({ 'iss' => 'gitlab-workhorse' }, Gitlab::Workhorse.secret, 'HS256') }
let(:workhorse_headers) { { 'GitLab-Workhorse' => '1.0', Gitlab::Workhorse::INTERNAL_API_REQUEST_HEADER => workhorse_token } }
end
# frozen_string_literal: true # frozen_string_literal: true
RSpec.shared_context 'Debian repository shared context' do |object_type| RSpec.shared_context 'Debian repository shared context' do |object_type|
include_context 'workhorse headers'
before do before do
stub_feature_flags(debian_packages: true) stub_feature_flags(debian_packages: true)
end end
...@@ -37,16 +39,15 @@ RSpec.shared_context 'Debian repository shared context' do |object_type| ...@@ -37,16 +39,15 @@ RSpec.shared_context 'Debian repository shared context' do |object_type|
let(:params) { workhorse_params } let(:params) { workhorse_params }
let(:auth_headers) { {} } let(:auth_headers) { {} }
let(:workhorse_token) { JWT.encode({ 'iss' => 'gitlab-workhorse' }, Gitlab::Workhorse.secret, 'HS256') } let(:wh_headers) do
let(:workhorse_headers) do
if method == :put if method == :put
{ 'GitLab-Workhorse' => '1.0', Gitlab::Workhorse::INTERNAL_API_REQUEST_HEADER => workhorse_token } workhorse_headers
else else
{} {}
end end
end end
let(:headers) { auth_headers.merge(workhorse_headers) } let(:headers) { auth_headers.merge(wh_headers) }
let(:send_rewritten_field) { true } let(:send_rewritten_field) { true }
......
...@@ -123,7 +123,7 @@ RSpec.shared_examples 'process nuget workhorse authorization' do |user_type, sta ...@@ -123,7 +123,7 @@ RSpec.shared_examples 'process nuget workhorse authorization' do |user_type, sta
context 'with a request that bypassed gitlab-workhorse' do context 'with a request that bypassed gitlab-workhorse' do
let(:headers) do let(:headers) do
basic_auth_header(user.username, personal_access_token.token) basic_auth_header(user.username, personal_access_token.token)
.merge(workhorse_header) .merge(workhorse_headers)
.tap { |h| h.delete(Gitlab::Workhorse::INTERNAL_API_REQUEST_HEADER) } .tap { |h| h.delete(Gitlab::Workhorse::INTERNAL_API_REQUEST_HEADER) }
end end
......
...@@ -24,7 +24,7 @@ end ...@@ -24,7 +24,7 @@ end
RSpec.shared_examples 'deploy token for package uploads' do RSpec.shared_examples 'deploy token for package uploads' do
context 'with deploy token headers' do context 'with deploy token headers' do
let(:headers) { basic_auth_header(deploy_token.username, deploy_token.token).merge(workhorse_header) } let(:headers) { basic_auth_header(deploy_token.username, deploy_token.token).merge(workhorse_headers) }
before do before do
project.update!(visibility_level: Gitlab::VisibilityLevel::PRIVATE) project.update!(visibility_level: Gitlab::VisibilityLevel::PRIVATE)
...@@ -35,7 +35,7 @@ RSpec.shared_examples 'deploy token for package uploads' do ...@@ -35,7 +35,7 @@ RSpec.shared_examples 'deploy token for package uploads' do
end end
context 'invalid token' do context 'invalid token' do
let(:headers) { basic_auth_header(deploy_token.username, 'bar').merge(workhorse_header) } let(:headers) { basic_auth_header(deploy_token.username, 'bar').merge(workhorse_headers) }
it_behaves_like 'returning response status', :unauthorized it_behaves_like 'returning response status', :unauthorized
end end
...@@ -102,7 +102,7 @@ end ...@@ -102,7 +102,7 @@ end
RSpec.shared_examples 'job token for package uploads' do RSpec.shared_examples 'job token for package uploads' do
context 'with job token headers' do context 'with job token headers' do
let(:headers) { basic_auth_header(::Gitlab::Auth::CI_JOB_USER, job.token).merge(workhorse_header) } let(:headers) { basic_auth_header(::Gitlab::Auth::CI_JOB_USER, job.token).merge(workhorse_headers) }
before do before do
project.update!(visibility_level: Gitlab::VisibilityLevel::PRIVATE) project.update!(visibility_level: Gitlab::VisibilityLevel::PRIVATE)
...@@ -114,13 +114,13 @@ RSpec.shared_examples 'job token for package uploads' do ...@@ -114,13 +114,13 @@ RSpec.shared_examples 'job token for package uploads' do
end end
context 'invalid token' do context 'invalid token' do
let(:headers) { basic_auth_header(::Gitlab::Auth::CI_JOB_USER, 'bar').merge(workhorse_header) } let(:headers) { basic_auth_header(::Gitlab::Auth::CI_JOB_USER, 'bar').merge(workhorse_headers) }
it_behaves_like 'returning response status', :unauthorized it_behaves_like 'returning response status', :unauthorized
end end
context 'invalid user' do context 'invalid user' do
let(:headers) { basic_auth_header('foo', job.token).merge(workhorse_header) } let(:headers) { basic_auth_header('foo', job.token).merge(workhorse_headers) }
it_behaves_like 'returning response status', :unauthorized it_behaves_like 'returning response status', :unauthorized
end end
......
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