Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2020-11-02 21:09:10 +00:00
parent 77cf68da37
commit a97f1426db
85 changed files with 1047 additions and 471 deletions

View File

@ -2,6 +2,21 @@
documentation](doc/development/changelog.md) for instructions on adding your own
entry.
## 13.5.2 (2020-11-02)
### Security (9 changes)
- Add CSRF protection to runner pause and resume. !1021
- Do not expose Terraform state record in API.
- Path traversal to RCE via LFS upload.
- Update container_repository_name_regex to prevent catastrophic backtracking.
- Validate nuget package names.
- Prevent private repo from being accessed via internal Kubernetes API.
- Validate each upload param key in multipart.rb.
- Fix XSS vulnerability for job build dependencies.
- Fix unauthorized user is able to access schedule pipeline variables and values.
## 13.5.1 (2020-10-22)
### Other (1 change)
@ -583,6 +598,21 @@ entry.
- Bump cluster applications CI template. !45472
## 13.4.5 (2020-11-02)
### Security (9 changes)
- Add CSRF protection to runner pause and resume. !1021
- Do not expose Terraform state record in API.
- Path traversal to RCE via LFS upload.
- Update container_repository_name_regex to prevent catastrophic backtracking.
- Validate nuget package names.
- Prevent private repo from being accessed via internal Kubernetes API.
- Validate each upload param key in multipart.rb.
- Fix XSS vulnerability for job build dependencies.
- Fix unauthorized user is able to access schedule pipeline variables and values.
## 13.4.4 (2020-10-15)
### Fixed (2 changes)
@ -1241,6 +1271,21 @@ entry.
- Expand the visible highlight for collapsed diffs (re: !41393). !42343
## 13.3.9 (2020-11-02)
### Security (9 changes)
- Add CSRF protection to runner pause and resume. !1021
- Do not expose Terraform state record in API.
- Path traversal to RCE via LFS upload.
- Update container_repository_name_regex to prevent catastrophic backtracking.
- Validate nuget package names.
- Prevent private repo from being accessed via internal Kubernetes API.
- Validate each upload param key in multipart.rb.
- Fix XSS vulnerability for job build dependencies.
- Fix unauthorized user is able to access schedule pipeline variables and values.
## 13.3.8 (2020-10-21)
### Fixed (2 changes)

View File

@ -1 +1 @@
8.52.0
8.53.0

View File

@ -1,8 +1,7 @@
<script>
/* eslint-disable vue/no-v-html */
import { throttle, isEmpty } from 'lodash';
import { mapGetters, mapState, mapActions } from 'vuex';
import { GlLoadingIcon, GlIcon } from '@gitlab/ui';
import { GlLoadingIcon, GlIcon, GlSafeHtmlDirective as SafeHtml } from '@gitlab/ui';
import { GlBreakpointInstance as bp } from '@gitlab/ui/dist/utils';
import { isScrolledToBottom } from '~/lib/utils/scroll_utils';
import { polyfillSticky } from '~/lib/utils/sticky';
@ -36,6 +35,9 @@ export default {
GlLoadingIcon,
SharedRunner: () => import('ee_component/jobs/components/shared_runner_limit_block.vue'),
},
directives: {
SafeHtml,
},
mixins: [delayedJobMixin],
props: {
artifactHelpUrl: {
@ -223,7 +225,7 @@ export default {
</div>
<callout v-if="shouldRenderHeaderCallout">
<div v-html="job.callout_message"></div>
<div v-safe-html="job.callout_message"></div>
</callout>
</header>
<!-- EO Header Section -->

View File

@ -1,7 +1,6 @@
<script>
import { GlIcon, GlTooltipDirective } from '@gitlab/ui';
import timeagoMixin from '~/vue_shared/mixins/timeago';
import { formatTime } from '~/lib/utils/datetime_utility';
export default {
directives: {
@ -27,7 +26,24 @@ export default {
return this.finishedTime !== '';
},
durationFormatted() {
return formatTime(this.duration);
const date = new Date(this.duration * 1000);
let hh = date.getUTCHours();
let mm = date.getUTCMinutes();
let ss = date.getSeconds();
// left pad
if (hh < 10) {
hh = `0${hh}`;
}
if (mm < 10) {
mm = `0${mm}`;
}
if (ss < 10) {
ss = `0${ss}`;
}
return `${hh}:${mm}:${ss}`;
},
},
};

View File

@ -54,9 +54,7 @@ export default {
</script>
<template>
<div
class="gl-display-flex gl-flex-direction-column gl-flex-shrink-0 gl-mr-6 gl-mb-5 js-issues-container"
>
<div class="gl-display-flex gl-flex-direction-column gl-flex-shrink-0 gl-mr-6 gl-mb-5">
<span class="gl-mb-2">
{{ label }}
<gl-badge variant="muted" size="sm">{{ total }}</gl-badge>

View File

@ -1,6 +1,5 @@
<script>
import { GlProgressBar, GlLink, GlButton, GlTooltipDirective } from '@gitlab/ui';
import { sum } from 'lodash';
import { __, n__, sprintf } from '~/locale';
import { MAX_MILESTONES_TO_DISPLAY } from '../constants';
import IssuableStats from './issuable_stats.vue';
@ -31,6 +30,21 @@ export default {
required: false,
default: '',
},
openMergeRequestsPath: {
type: String,
required: false,
default: '',
},
mergedMergeRequestsPath: {
type: String,
required: false,
default: '',
},
closedMergeRequestsPath: {
type: String,
required: false,
default: '',
},
},
data() {
return {
@ -45,17 +59,45 @@ export default {
});
},
percentComplete() {
const percent = Math.round((this.closedIssuesCount / this.totalIssuesCount) * 100);
const percent = Math.round((this.issueCounts.closed / this.issueCounts.total) * 100);
return Number.isNaN(percent) ? 0 : percent;
},
allIssueStats() {
return this.milestones.map(m => m.issueStats || {});
issueCounts() {
return this.milestones
.map(m => m.issueStats || {})
.reduce(
(acc, current) => {
acc.total += current.total || 0;
acc.closed += current.closed || 0;
return acc;
},
{
total: 0,
closed: 0,
},
);
},
totalIssuesCount() {
return sum(this.allIssueStats.map(stats => stats.total || 0));
showMergeRequestStats() {
return this.milestones.some(m => m.mrStats);
},
closedIssuesCount() {
return sum(this.allIssueStats.map(stats => stats.closed || 0));
mergeRequestCounts() {
return this.milestones
.map(m => m.mrStats || {})
.reduce(
(acc, current) => {
acc.total += current.total || 0;
acc.merged += current.merged || 0;
acc.closed += current.closed || 0;
return acc;
},
{
total: 0,
merged: 0,
closed: 0,
},
);
},
milestoneLabelText() {
return n__('Milestone', 'Milestones', this.milestones.length);
@ -98,7 +140,7 @@ export default {
>
<span class="gl-mb-3">{{ percentCompleteText }}</span>
<span class="gl-w-full">
<gl-progress-bar :value="closedIssuesCount" :max="totalIssuesCount" variant="success" />
<gl-progress-bar :value="issueCounts.closed" :max="issueCounts.total" variant="success" />
</span>
</div>
<div
@ -129,10 +171,22 @@ export default {
</div>
<issuable-stats
:label="__('Issues')"
:total="totalIssuesCount"
:closed="closedIssuesCount"
:total="issueCounts.total"
:closed="issueCounts.closed"
:open-path="openIssuesPath"
:closed-path="closedIssuesPath"
data-testid="issue-stats"
/>
<issuable-stats
v-if="showMergeRequestStats"
:label="__('Merge Requests')"
:total="mergeRequestCounts.total"
:merged="mergeRequestCounts.merged"
:closed="mergeRequestCounts.closed"
:open-path="openMergeRequestsPath"
:merged-path="mergedMergeRequestsPath"
:closed-path="closedMergeRequestsPath"
data-testid="merge-request-stats"
/>
</div>
</template>

View File

@ -23,7 +23,7 @@ module Projects
redirect_to namespace_project_settings_access_tokens_path, notice: _("Your new project access token has been created.")
else
render :index
redirect_to namespace_project_settings_access_tokens_path, alert: _("Failed to create new project access token: %{token_response_message}") % { token_response_message: token_response.message }
end
end

View File

@ -37,6 +37,7 @@ class Packages::Package < ApplicationRecord
validate :package_already_taken, if: :npm?
validates :name, format: { with: Gitlab::Regex.conan_recipe_component_regex }, if: :conan?
validates :name, format: { with: Gitlab::Regex.generic_package_name_regex }, if: :generic?
validates :name, format: { with: Gitlab::Regex.nuget_package_name_regex }, if: :nuget?
validates :version, format: { with: Gitlab::Regex.nuget_version_regex }, if: :nuget?
validates :version, format: { with: Gitlab::Regex.conan_recipe_component_regex }, if: :conan?
validates :version, format: { with: Gitlab::Regex.maven_version_regex }, if: -> { version? && maven? }

View File

@ -17,6 +17,7 @@ module Ci
rule { can?(:admin_pipeline) | (can?(:update_build) & owner_of_schedule) }.policy do
enable :update_pipeline_schedule
enable :admin_pipeline_schedule
enable :read_pipeline_schedule_variables
end
rule { can?(:admin_pipeline_schedule) & ~owner_of_schedule }.policy do

View File

@ -136,7 +136,7 @@ class BuildDetailsEntity < JobEntity
docs_url = "https://docs.gitlab.com/ee/ci/yaml/README.html#dependencies"
[
failure_message.html_safe,
failure_message,
help_message(docs_url).html_safe
].join("<br />")
end

View File

@ -32,6 +32,8 @@ module Packages
)
end
end
rescue ActiveRecord::RecordInvalid => e
raise InvalidMetadataError.new(e.message)
end
private

View File

@ -79,11 +79,7 @@ module Projects
# Directories on disk
move_project_folders(project)
# Move missing group labels to project
Labels::TransferService.new(current_user, @old_group, project).execute
# Move missing group milestones
Milestones::TransferService.new(current_user, @old_group, project).execute
transfer_missing_group_resources(@old_group)
# Move uploads
move_project_uploads(project)
@ -107,6 +103,12 @@ module Projects
refresh_permissions
end
def transfer_missing_group_resources(group)
Labels::TransferService.new(current_user, group, project).execute
Milestones::TransferService.new(current_user, group, project).execute
end
def allowed_transfer?(current_user, project)
@new_namespace &&
can?(current_user, :change_namespace, project) &&

View File

@ -15,13 +15,20 @@ module ResourceAccessTokens
user = create_user
return error(user.errors.full_messages.to_sentence) unless user.persisted?
return error("Failed to provide maintainer access") unless provision_access(resource, user)
member = create_membership(resource, user)
unless member.persisted?
delete_failed_user(user)
return error("Could not provision maintainer access to project access token")
end
token_response = create_personal_access_token(user)
if token_response.success?
success(token_response.payload[:personal_access_token])
else
delete_failed_user(user)
error(token_response.message)
end
end
@ -43,6 +50,10 @@ module ResourceAccessTokens
Users::CreateService.new(current_user, default_user_params).execute(skip_authorization: true)
end
def delete_failed_user(user)
DeleteUserWorker.perform_async(current_user.id, user.id, hard_delete: true, skip_authorization: true)
end
def default_user_params
{
name: params[:name] || "#{resource.name.to_s.humanize} bot",
@ -88,7 +99,7 @@ module ResourceAccessTokens
Gitlab::Auth.resource_bot_scopes
end
def provision_access(resource, user)
def create_membership(resource, user)
resource.add_user(user, :maintainer, expires_at: params[:expires_at])
end

View File

@ -23,6 +23,8 @@ module Terraform
state.save! unless state.destroyed?
end
nil
end
def lock!

View File

@ -5,6 +5,10 @@ class GitlabUploader < CarrierWave::Uploader::Base
class_attribute :options
PROTECTED_METHODS = %i(filename cache_dir work_dir store_dir).freeze
ObjectNotReadyError = Class.new(StandardError)
class << self
# DSL setter
def storage_options(options)
@ -33,6 +37,8 @@ class GitlabUploader < CarrierWave::Uploader::Base
delegate :base_dir, :file_storage?, to: :class
before :cache, :protect_from_path_traversal!
def initialize(model, mounted_as = nil, **uploader_context)
super(model, mounted_as)
end
@ -121,6 +127,9 @@ class GitlabUploader < CarrierWave::Uploader::Base
# For example, `FileUploader` builds the storage path based on the associated
# project model's `path_with_namespace` value, which can change when the
# project or its containing namespace is moved or renamed.
#
# When implementing this method, raise `ObjectNotReadyError` if the model
# does not yet exist, as it will be tested in `#protect_from_path_traversal!`
def dynamic_segment
raise(NotImplementedError)
end
@ -138,4 +147,21 @@ class GitlabUploader < CarrierWave::Uploader::Base
def pathname
@pathname ||= Pathname.new(path)
end
# Protect against path traversal attacks
# This takes a list of methods to test for path traversal, e.g. ../../
# and checks each of them. This uses `.send` so that any potential errors
# don't block the entire set from being tested.
#
# @param [CarrierWave::SanitizedFile]
# @return [Nil]
# @raise [Gitlab::Utils::PathTraversalAttackError]
def protect_from_path_traversal!(file)
PROTECTED_METHODS.each do |method|
Gitlab::Utils.check_path_traversal!(self.send(method)) # rubocop: disable GitlabSecurity/PublicSend
rescue ObjectNotReadyError
# Do nothing. This test was attempted before the file was ready for that method
end
end
end

View File

@ -4,7 +4,6 @@ class JobArtifactUploader < GitlabUploader
extend Workhorse::UploadPath
include ObjectStorage::Concern
ObjectNotReadyError = Class.new(StandardError)
UnknownFileLocationError = Class.new(StandardError)
storage_options Gitlab.config.artifacts
@ -24,7 +23,9 @@ class JobArtifactUploader < GitlabUploader
private
def dynamic_segment
raise ObjectNotReadyError, 'JobArtifact is not ready' unless model.id
# This now tests model.created_at because it can for some reason be nil in the test suite,
# and it's not clear if this is intentional or not
raise ObjectNotReadyError, 'JobArtifact is not ready' unless model.id && model.created_at
if model.hashed_path?
hashed_path

View File

@ -20,6 +20,8 @@ class Packages::PackageFileUploader < GitlabUploader
private
def dynamic_segment
raise ObjectNotReadyError, "Package model not ready" unless model.id
Gitlab::HashedPath.new('packages', model.package.id, 'files', model.id, root_hash: model.package.project_id)
end
end

View File

@ -69,10 +69,10 @@
= sprite_icon('pencil')
.btn-group
- if runner.active?
= link_to [:pause, :admin, runner], method: :get, class: 'gl-button btn btn-default btn-svg has-tooltip', title: _('Pause'), ref: 'tooltip', aria: { label: _('Pause') }, data: { placement: 'top', container: 'body', confirm: _('Are you sure?') } do
= link_to [:pause, :admin, runner], method: :post, class: 'gl-button btn btn-default btn-svg has-tooltip', title: _('Pause'), ref: 'tooltip', aria: { label: _('Pause') }, data: { placement: 'top', container: 'body', confirm: _('Are you sure?') } do
= sprite_icon('pause')
- else
= link_to [:resume, :admin, runner], method: :get, class: 'gl-button btn btn-default btn-svg has-tooltip gl-px-3', title: _('Resume'), ref: 'tooltip', aria: { label: _('Resume') }, data: { placement: 'top', container: 'body'} do
= link_to [:resume, :admin, runner], method: :post, class: 'gl-button btn btn-default btn-svg has-tooltip gl-px-3', title: _('Resume'), ref: 'tooltip', aria: { label: _('Resume') }, data: { placement: 'top', container: 'body'} do
= sprite_icon('play')
.btn-group
= link_to [:admin, runner], method: :delete, class: 'gl-button btn btn-danger has-tooltip', title: _('Remove'), ref: 'tooltip', aria: { label: _('Remove') }, data: { placement: 'top', container: 'body', confirm: _('Are you sure?') } do

View File

@ -5,9 +5,9 @@
.label-name.gl-flex-shrink-0.gl-mt-2.gl-mr-3
= render_label(label, tooltip: false)
.label-description.gl-flex-grow-1.gl-overflow-hidden
.gl-display-flex.gl-align-items-center.gl-flex-wrap.gl-mt-2
.description-text.gl-flex-grow-1.gl-overflow-hidden
.label-description.gl-overflow-hidden.gl-w-full
.gl-display-flex.gl-align-items-stretch.gl-flex-wrap.gl-mt-2
.gl-flex-basis-half.gl-flex-grow-1.gl-overflow-hidden.gl-mr-2
- if label.description.present?
= markdown_field(label, :description)
- elsif show_labels_full_path?(@project, @group)

View File

@ -0,0 +1,5 @@
---
title: Improve project labels page card layout consistency
merge_request: 45311
author:
type: fixed

View File

@ -0,0 +1,5 @@
---
title: Skip GMA and SSO validation when creating project access tokens for project bots
merge_request: 46257
author:
type: fixed

View File

@ -0,0 +1,5 @@
---
title: Bump workhorse to 8.53.0
merge_request: 46666
author:
type: other

View File

@ -148,8 +148,8 @@ namespace :admin do
resources :runners, only: [:index, :show, :update, :destroy] do
member do
get :resume
get :pause
post :resume
post :pause
end
collection do

View File

@ -10,7 +10,7 @@ Please check the ~"product analytics" [guide](https://docs.gitlab.com/ee/develop
MSG
UPDATE_METRICS_DEFINITIONS_MESSAGE = <<~MSG
When adding, changing, or updating metrics, please update the [Event dictionary Usage Ping table](https://docs.gitlab.com/ee/development/product_analytics/event_dictionary.html#usage-ping).
When adding, changing, or updating metrics, please update the [Event dictionary Usage Ping table](https://about.gitlab.com/handbook/product/product-analytics-guide#event-dictionary).
MSG

View File

@ -14,7 +14,9 @@ The following documentation enables Okta as a SAML provider.
## Configure the Okta application
1. On Okta go to the admin section and choose to **Add an App**.
The following guidance is based on this Okta article, on adding a [SAML Application with an Okta Developer account](https://support.okta.com/help/s/article/Why-can-t-I-add-a-SAML-Application-with-an-Okta-Developer-account?language=en_US):
1. On Okta admin section, make sure to select Classic UI view in the top left corner. From there, choose to **Add an App**.
1. When the app screen comes up you see another button to **Create an App** and
choose SAML 2.0 on the next screen.
1. Now, very important, add a logo

View File

@ -1,6 +1,6 @@
---
stage: Plan
group: Certify
group: Product Planning
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers
---

View File

@ -1,6 +1,6 @@
---
stage: Plan
group: Portfolio Management
group: Product Planning
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers
---

View File

@ -1,6 +1,6 @@
---
stage: Plan
group: Portfolio Management
group: Product Planning
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers
---

View File

@ -200,17 +200,23 @@ for more information.
### Merge Trains feature flag **(PREMIUM ONLY)**
To enable and disable the Merge Trains feature, use the `:disable_merge_trains` feature flag.
Merge trains are automatically enabled when [pipelines for merged results](../index.md#pipelines-for-merged-results)
are enabled. To use pipelines for merged results without using merge trains, you must
enable a [feature flag](../../../../user/feature_flags.md) that blocks the merge trains
feature.
To check if the feature flag is enabled on your GitLab instance,
ask an administrator to execute the following commands:
[GitLab administrators with access to the GitLab Rails console](../../../../administration/feature_flags.md)
can enable the feature flag to disable merge trains:
```shell
> sudo gitlab-rails console # Login to Rails console of GitLab instance.
> Feature.enabled?(:disable_merge_trains) # Check if it's disabled or not.
> Feature.enable(:disable_merge_trains) # Enable Merge Trains.
> Feature.disable(:disable_merge_trains) # Disable Merge Trains.
```ruby
Feature.enable(:disable_merge_trains)
```
When you disable this feature, all existing merge trains are cancelled and
After you enable this feature flag, all existing merge trains are cancelled and
the **Start/Add to Merge Train** button no longer appears in merge requests.
To disable the feature flag, and enable merge trains again:
```ruby
Feature.disable(:disable_merge_trains)
```

View File

@ -183,7 +183,7 @@ See [database guidelines](database/index.md).
## Product Analytics guides
- [Product Analytics guide](product_analytics/index.md)
- [Product Analytics guide](https://about.gitlab.com/handbook/product/product-analytics-guide/)
- [Usage Ping guide](product_analytics/usage_ping.md)
- [Snowplow guide](product_analytics/snowplow.md)

View File

@ -1392,6 +1392,62 @@ it 'returns a successful response' do
end
```
### Testing tips and tricks
- Avoid false positives:
Authenticating a user with the `current_user:` argument for `post_graphql`
generates more queries on the first request than on subsequent requests on that
same user. If you are testing for N+1 queries using
[QueryRecorder](query_recorder.md), use a **different** user for each request.
The below example shows how a test for avoiding N+1 queries should look:
```ruby
RSpec.describe 'Query.project(fullPath).pipelines' do
include GraphqlHelpers
let(:project) { create(:project) }
let(:query) do
%(
{
project(fullPath: "#{project.full_path}") {
pipelines {
nodes {
id
}
}
}
}
)
end
it 'avoids N+1 queries' do
first_user = create(:user)
second_user = create(:user)
create(:ci_pipeline, project: project)
control_count = ActiveRecord::QueryRecorder.new do
post_graphql(query, current_user: first_user)
end
create(:ci_pipeline, project: project)
expect do
post_graphql(query, current_user: second_user) # use a different user to avoid a false positive from authentication queries
end.not_to exceed_query_limit(control_count)
end
end
```
- Mimic the folder structure of `app/graphql/types`:
For example, tests for fields on `Types::Ci::PipelineType`
in `app/graphql/types/ci/pipeline_type.rb` should live in
`spec/requests/api/graphql/ci/pipeline_spec.rb` regardless of the query being
used to fetch the pipeline data.
## Notes about Query flow and GraphQL infrastructure
GitLab's GraphQL infrastructure can be found in `lib/gitlab/graphql`.

View File

@ -47,7 +47,7 @@ the `author` field. GitLab team members **should not**.
- Any user-facing change **must** have a changelog entry. This includes both visual changes (regardless of how minor), and changes to the rendered DOM which impact how a screen reader may announce the content.
- Any client-facing change to our REST and GraphQL APIs **must** have a changelog entry.
- Performance improvements **should** have a changelog entry.
- Changes that need to be documented in the Product Analytics [Event Dictionary](product_analytics/event_dictionary.md)
- Changes that need to be documented in the Product Analytics [Event Dictionary](https://about.gitlab.com/handbook/product/product-analytics-guide#event-dictionary)
also require a changelog entry.
- _Any_ contribution from a community member, no matter how small, **may** have
a changelog entry regardless of these guidelines if the contributor wants one.
@ -55,7 +55,7 @@ the `author` field. GitLab team members **should not**.
- Any docs-only changes **should not** have a changelog entry.
- Any change behind a disabled feature flag **should not** have a changelog entry.
- Any change behind an enabled feature flag **should** have a changelog entry.
- Any change that adds new usage data metrics and changes that needs to be documented in Product Analytics [Event Dictionary](telemetry/event_dictionary.md) **should** have a changelog entry.
- Any change that adds new usage data metrics and changes that needs to be documented in Product Analytics [Event Dictionary](https://about.gitlab.com/handbook/product/product-analytics-guide#event-dictionary) **should** have a changelog entry.
- A change that [removes a feature flag](feature_flags/development.md) **should** have a changelog entry -
only if the feature flag did not default to true already.
- A fix for a regression introduced and then fixed in the same release (i.e.,

View File

@ -27,7 +27,7 @@ A database review is required for:
database review.
- Changes in usage data metrics that use `count` and `distinct_count`.
These metrics could have complex queries over large tables.
See the [Product Analytics Guide](product_analytics/usage_ping.md#implementing-usage-ping)
See the [Product Analytics Guide](https://about.gitlab.com/handbook/product/product-analytics-guide/)
for implementation details.
A database reviewer is expected to look out for obviously complex

View File

@ -115,7 +115,7 @@ addressed.
To determine whether the experiment is a success or not, we must implement tracking events
to acquire data for analyzing. We can send events to Snowplow via either the backend or frontend.
Read the [product analytics guide](../product_analytics/index.md) for more details.
Read the [product analytics guide](https://about.gitlab.com/handbook/product/product-analytics-guide/) for more details.
#### Track backend events

View File

@ -234,6 +234,23 @@ instead of an `ActiveRecord::Relation`:
<!-- ### External pagination -->
### External pagination
There may be times where you need to return data through the GitLab API that is stored in
another system. In these cases you may have to paginate a third-party's API.
An example of this is with our [Error Tracking](../../operations/error_tracking.md) implementation,
where we proxy [Sentry errors](../../operations/error_tracking.md#sentry-error-tracking) through
the GitLab API. We do this by calling the Sentry API which enforces its own pagination rules.
This means we cannot access the collection within GitLab to perform our own custom pagination.
For consistency, we manually set the pagination cursors based on values returned by the external API, using `Gitlab::Graphql::ExternallyPaginatedArray.new(previous_cursor, next_cursor, *items)`.
You can see an example implementation in the following files:
- [`types/error__tracking/sentry_error_collection_type.rb`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/app/graphql/types/error_tracking/sentry_error_collection_type.rb) which adds an extension to `field :errors`.
- [`resolvers/error_tracking/sentry_errors_resolver.rb`](https://gitlab.com/gitlab-org/gitlab/blob/master/app/graphql/resolvers/error_tracking/sentry_errors_resolver.rb) which returns the data from the resolver.
## Testing
Any GraphQL field that supports pagination and sorting should be tested

Binary file not shown.

Before

Width:  |  Height:  |  Size: 101 KiB

View File

@ -1,32 +1,5 @@
---
stage: Growth
group: Product Analytics
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers
redirect_to: 'https://about.gitlab.com/handbook/product/product-analytics-guide/'
---
# Event Dictionary
**Note: We've temporarily moved the Event Dictionary to a [Google Sheet](https://docs.google.com/spreadsheets/d/1VzE8R72Px_Y_LlE3Z05LxUlG_dumWe3vl-HeUo70TPw/edit?usp=sharing)**. The previous Markdown table exceeded 600 rows making it difficult to manage. In the future, our intention is to move this back into our docs using a [YAML file](https://gitlab.com/gitlab-org/gitlab-docs/-/issues/823).
The event dictionary is a single source of truth for the metrics and events we collect for product usage data. The Event Dictionary lists all the metrics and events we track, why we're tracking them, and where they are tracked.
This is a living document that is updated any time a new event is planned or implemented. It includes the following information.
- Section, stage, or group
- Description
- Implementation status
- Availability by plan type
- Code path
We're currently focusing our Event Dictionary on [Usage Ping](usage_ping.md). In the future, we will also include [Snowplow](snowplow.md). We currently have an initiative across the entire product organization to complete the [Event Dictionary for Usage Ping](https://gitlab.com/groups/gitlab-org/-/epics/4174).
## Instructions
1. Open the Event Dictionary and fill in all the **PM to edit** columns highlighted in yellow.
1. Check that all the metrics and events are assigned to the correct section, stage, or group. If a metric is used across many groups, assign it to the stage. If a metric is used across many stages, assign it to the section. If a metric is incorrectly assigned to another section, stage, or group, let the PM know you have reassigned it. If your group has no assigned metrics and events, check that your metrics and events are not incorrectly assigned to another PM.
1. Add descriptions of what your metrics and events are tracking. Work with your Engineering team or the Product Analytics team if you need help understanding this.
1. Add what plans this metric is available on. Work with your Engineering team or the Product Analytics team if you need help understanding this.
## Planned metrics and events
For future metrics and events you plan to track, please add them to the Event Dictionary and note the status as `Planned`, `In Progress`, or `Implemented`. Once you have confirmed the metric has been implemented and have confirmed the metric data is in our data warehouse, change the status to **Data Available**.
This document was moved to [another location](https://about.gitlab.com/handbook/product/product-analytics-guide/).

View File

@ -1,182 +1,5 @@
---
stage: Growth
group: Product Analytics
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers
redirect_to: 'https://about.gitlab.com/handbook/product/product-analytics-guide/'
---
# Product Analytics Guide
At GitLab, we collect product usage data for the purpose of helping us build a better product. Data helps GitLab understand which parts of the product need improvement and which features we should build next. Product usage data also helps our team better understand the reasons why people use GitLab. With this knowledge we are able to make better product decisions.
We encourage users to enable tracking, and we embrace full transparency with our tracking approach so it can be easily understood and trusted.
By enabling tracking, users can:
- Contribute back to the wider community.
- Help GitLab improve on the product.
## Our tracking tools
We use three methods to gather product usage data:
- [Snowplow](#snowplow)
- [Usage Ping](#usage-ping)
- [Database import](#database-import)
### Snowplow
Snowplow is an enterprise-grade marketing and product analytics platform which helps track the way
users engage with our website and application.
Snowplow consists of two components:
- [Snowplow JS](https://github.com/snowplow/snowplow/wiki/javascript-tracker) tracks client-side
events.
- [Snowplow Ruby](https://github.com/snowplow/snowplow/wiki/ruby-tracker) tracks server-side events.
For more details, read the [Snowplow](snowplow.md) guide.
### Usage Ping
Usage Ping is a method for GitLab Inc to collect usage data on a GitLab instance. Usage Ping is primarily composed of row counts for different tables in the instances database. By comparing these counts month over month (or week over week), we can get a rough sense for how an instance is using the different features within the product. This high-level data is used to help our product, support, and sales teams.
For more details, read the [Usage Ping](usage_ping.md) guide.
### Database import
Database imports are full imports of data into GitLab's data warehouse. For GitLab.com, the PostgreSQL database is loaded into Snowflake data warehouse every 6 hours. For more details, see the [data team handbook](https://about.gitlab.com/handbook/business-ops/data-team/platform/#extract-and-load).
## What data can be tracked
Our different tracking tools allows us to track different types of events. The event types and examples of what data can be tracked are outlined below.
The availability of event types and their tracking tools varies by segment. For example, on Self-Managed Users, we only have reporting using Database records via Usage Ping.
| Event Types | SaaS Instance | SaaS Plan | SaaS Group | SaaS Session | SaaS User | SM Instance | SM Plan | SM Group | SM Session | SM User |
|----------------------------------------|---------------|-----------|------------|--------------|-----------|-------------|---------|----------|------------|---------|
| Snowplow (JS Pageview events) | ✅ | 📅 | 📅 | ✅ | 📅 | 📅 | 📅 | 📅 | 📅 | 📅 |
| Snowplow (JS UI events) | ✅ | 📅 | 📅 | ✅ | 📅 | 📅 | 📅 | 📅 | 📅 | 📅 |
| Snowplow (Ruby Pageview events) | ✅ | 📅 | 📅 | ✅ | 📅 | 📅 | 📅 | 📅 | 📅 | 📅 |
| Snowplow (Ruby CRUD / API events) | ✅ | 📅 | 📅 | ✅ | 📅 | 📅 | 📅 | 📅 | 📅 | 📅 |
| Usage Ping (Redis UI counters) | 🔄 | 🔄 | 🔄 | ✖️ | 🔄 | 🔄 | 🔄 | 🔄 | ✖️ | 🔄 |
| Usage Ping (Redis Pageview counters) | 🔄 | 🔄 | 🔄 | ✖️ | 🔄 | 🔄 | 🔄 | 🔄 | ✖️ | 🔄 |
| Usage Ping (Redis CRUD / API counters) | 🔄 | 🔄 | 🔄 | ✖️ | 🔄 | 🔄 | 🔄 | 🔄 | ✖️ | 🔄 |
| Usage Ping (Database counters) | ✅ | 🔄 | 📅 | ✖️ | ✅ | ✅ | ✅ | ✅ | ✖️ | ✅ |
| Usage Ping (Instance settings) | ✅ | 🔄 | 📅 | ✖️ | ✅ | ✅ | ✅ | ✅ | ✖️ | ✅ |
| Usage Ping (Integration settings) | ✅ | 🔄 | 📅 | ✖️ | ✅ | ✅ | ✅ | ✅ | ✖️ | ✅ |
| Database import (Database records) | ✅ | ✅ | ✅ | ✖️ | ✅ | ✖️ | ✖️ | ✖️ | ✖️ | ✖️ |
[Source file](https://docs.google.com/spreadsheets/d/1e8Afo41Ar8x3JxAXJF3nL83UxVZ3hPIyXdt243VnNuE/edit?usp=sharing)
**Legend**
✅ Available, 🔄 In Progress, 📅 Planned, ✖️ Not Possible
SaaS = GitLab.com. SM = Self-Managed instance
### Pageview events
- Number of sessions that visited the /dashboard/groups page
### UI events
- Number of sessions that clicked on a button or link
- Number of sessions that closed a modal
UI events are any interface-driven actions from the browser including click data.
### CRUD or API events
- Number of Git pushes
- Number of GraphQL queries
- Number of requests to a Rails action or controller
These are backend events that include the creation, read, update, deletion of records, and other events that might be triggered from layers other than those available in the interface.
### Database records
These are raw database records which can be explored using business intelligence tools like Sisense. The full list of available tables can be found in [structure.sql](https://gitlab.com/gitlab-org/gitlab/-/blob/master/db/structure.sql).
### Instance settings
These are settings of your instance such as the instance's Git version and if certain features are enabled such as `container_registry_enabled`.
### Integration settings
These are integrations your GitLab instance interacts with such as an [external storage provider](../../administration/static_objects_external_storage.md) or an [external container registry](../../administration/packages/container_registry.md#use-an-external-container-registry-with-gitlab-as-an-auth-endpoint). These services must be able to send data back into a GitLab instance for data to be tracked.
## Reporting level
Our reporting levels of aggregate or individual reporting varies by segment. For example, on Self-Managed Users, we can report at an aggregate user level using Usage Ping but not on an Individual user level.
| Aggregated Reporting | SaaS Instance | SaaS Plan | SaaS Group | SaaS Session | SaaS User | SM Instance | SM Plan | SM Group | SM Session | SM User |
|----------------------|---------------|-----------|------------|--------------|-----------|-------------|---------|----------|------------|---------|
| Snowplow | ✅ | 📅 | 📅 | ✅ | 📅 | ✅ | 📅 | 📅 | ✅ | 📅 |
| Usage Ping | ✅ | 🔄 | 📅 | 📅 | ✅ | ✅ | ✅ | ✅ | 📅 | ✅ |
| Database import | ✅ | ✅ | ✅ | ✖️ | ✅ | ✖️ | ✖️ | ✖️ | ✖️ | ✖️ |
| Identifiable Reporting | SaaS Instance | SaaS Plan | SaaS Group | SaaS Session | SaaS User | SM Instance | SM Plan | SM Group | SM Session | SM User |
|------------------------|---------------|-----------|------------|--------------|-----------|-------------|---------|----------|------------|---------|
| Snowplow | ✅ | 📅 | 📅 | ✅ | 📅 | ✖️ | ✖️ | ✖️ | ✖️ | ✖️ |
| Usage Ping | ✅ | 🔄 | 📅 | ✖️ | ✖️ | ✅ | ✅ | ✖️ | ✖️ | ✖️ |
| Database import | ✅ | ✅ | ✅ | ✖️ | ✅ | ✖️ | ✖️ | ✖️ | ✖️ | ✖️ |
**Legend**
✅ Available, 🔄 In Progress, 📅 Planned, ✖️ Not Possible
SaaS = GitLab.com. SM = Self-Managed instance
## Reporting time period
Our reporting time periods varies by segment. For example, on Self-Managed Users, we can report all time counts and 28 day counts in Usage Ping.
| Reporting Time Period | All Time | 28 Days | 7 Days | Daily |
|-----------------------|----------|---------|--------|-------|
| Snowplow | ✅ | ✅ | ✅ | ✅ |
| Usage Ping | ✅ | ✅ | 📅 | ✖️ |
| Database import | ✅ | ✅ | ✅ | ✅ |
**Legend**
✅ Available, 🔄 In Progress, 📅 Planned, ✖️ Not Possible
## Systems overview
The systems overview is a simplified diagram showing the interactions between GitLab Inc and self-managed instances.
![Product Analytics Overview](../img/telemetry_system_overview.png)
[Source file](https://app.diagrams.net/#G13DVpN-XnhWGz9tqReIj8pp1UE4ehk_EC)
### GitLab Inc
For Product Analytics purposes, GitLab Inc has three major components:
1. [Data Infrastructure](https://about.gitlab.com/handbook/business-ops/data-team/platform/infrastructure/): This contains everything managed by our data team including Sisense Dashboards for visualization, Snowflake for Data Warehousing, incoming data sources such as PostgreSQL Pipeline and S3 Bucket, and lastly our data collectors [GitLab.com's Snowplow Collector](https://gitlab.com/gitlab-com/gl-infra/readiness/-/tree/master/library/snowplow/) and GitLab's Versions Application.
1. GitLab.com: This is the production GitLab application which is made up of a Client and Server. On the Client or browser side, a Snowplow JS Tracker (Frontend) is used to track client-side events. On the Server or application side, a Snowplow Ruby Tracker (Backend) is used to track server-side events. The server also contains Usage Ping which leverages a PostgreSQL database and a Redis in-memory data store to report on usage data. Lastly, the server also contains System Logs which are generated from running the GitLab application.
1. [Monitoring infrastructure](https://about.gitlab.com/handbook/engineering/monitoring/): This is the infrastructure used to ensure GitLab.com is operating smoothly. System Logs are sent from GitLab.com to our monitoring infrastructure and collected by a FluentD collector. From FluentD, logs are either sent to long term Google Cloud Services cold storage via Stackdriver, or, they are sent to our Elastic Cluster via Cloud Pub/Sub which can be explored in real-time using Kibana.
### Self-managed
For Product Analytics purposes, self-managed instances have two major components:
1. Data infrastructure: Having a data infrastructure setup is optional on self-managed instances. If you'd like to collect Snowplow tracking events for your self-managed instance, you can setup your own self-managed Snowplow collector and configure your Snowplow events to point to your own collector.
1. GitLab: A self-managed GitLab instance contains all of the same components as GitLab.com mentioned above.
### Differences between GitLab Inc and Self-managed
As shown by the orange lines, on GitLab.com Snowplow JS, Snowplow Ruby, Usage Ping, and PostgreSQL database imports all flow into GitLab Inc's data infrastructure. However, on self-managed, only Usage Ping flows into GitLab Inc's data infrastructure.
As shown by the green lines, on GitLab.com system logs flow into GitLab Inc's monitoring infrastructure. On self-managed, there are no logs sent to GitLab Inc's monitoring infrastructure.
Note (1): Snowplow JS and Snowplow Ruby are available on self-managed, however, the Snowplow Collector endpoint is set to a self-managed Snowplow Collector which GitLab Inc does not have access to.
## Additional information
More useful links:
- [Product Analytics Direction](https://about.gitlab.com/direction/product-analytics/)
- [Data Analysis Process](https://about.gitlab.com/handbook/business-ops/data-team/#data-analysis-process/)
- [Data for Product Managers](https://about.gitlab.com/handbook/business-ops/data-team/programs/data-for-product-managers/)
- [Data Infrastructure](https://about.gitlab.com/handbook/business-ops/data-team/platform/infrastructure/)
This document was moved to [another location](https://about.gitlab.com/handbook/product/product-analytics-guide/).

View File

@ -10,7 +10,7 @@ This guide provides an overview of how Snowplow works, and implementation detail
For more information about Product Analytics, see:
- [Product Analytics Guide](index.md)
- [Product Analytics Guide](https://about.gitlab.com/handbook/product/product-analytics-guide/)
- [Usage Ping Guide](usage_ping.md)
More useful links:

View File

@ -15,7 +15,7 @@ This guide describes Usage Ping's purpose and how it's implemented.
For more information about Product Analytics, see:
- [Product Analytics Guide](index.md)
- [Product Analytics Guide](https://about.gitlab.com/handbook/product/product-analytics-guide/)
- [Snowplow Guide](snowplow.md)
More useful links:
@ -613,7 +613,7 @@ We also use `#database-lab` and [explain.depesz.com](https://explain.depesz.com/
### 5. Add the metric definition
When adding, changing, or updating metrics, please update the [Event Dictionary's **Usage Ping** table](event_dictionary.md).
When adding, changing, or updating metrics, please update the [Event Dictionary's **Usage Ping** table](https://about.gitlab.com/handbook/product/product-analytics-guide#event-dictionary).
### 6. Add new metric to Versions Application

View File

@ -498,7 +498,29 @@ include:
Keep in mind that this approach will eventually stop working when the stable repository is removed,
so you must eventually fix your custom chart.
You can find more information in [this issue](https://gitlab.com/gitlab-org/gitlab/-/issues/263778).
To fix your custom chart:
1. In your chart directory, update the `repository` value in your `requirements.yaml` file from :
```yaml
repository: "https://kubernetes-charts.storage.googleapis.com/"
```
to:
```yaml
repository: "https://charts.helm.sh/stable"
```
1. In your chart directory, run `helm dep update .` using the same Helm major version as Auto DevOps.
1. Commit the changes for the `requirements.yaml` file.
1. If you previously had a `requirements.lock` file, commit the changes to the file.
If you did not previously have a `requirements.lock` file in your chart,
you do not need to commit the new one. This file is optional, but when present,
it's used to verify the integrity of the downloaded dependencies.
You can find more information in
[issue #263778, "Migrate PostgreSQL from stable Helm repo"](https://gitlab.com/gitlab-org/gitlab/-/issues/263778).
## Development guides

View File

@ -1,7 +1,7 @@
---
type: reference, howto
stage: Plan
group: Portfolio Management
group: Product Planning
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers
---

View File

@ -1,7 +1,7 @@
---
type: howto
stage: Plan
group: Portfolio Management
group: Product Planning
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers
---

View File

@ -1,7 +1,7 @@
---
type: reference
stage: Plan
group: Portfolio Management
group: Product Planning
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers
---

View File

@ -1,7 +1,7 @@
---
type: reference, howto
stage: Plan
group: Certify
group: Product Planning
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers
---

View File

@ -1,6 +1,6 @@
---
stage: Plan
group: Certify
group: Product Planning
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers
---

View File

@ -38,7 +38,7 @@ module API
requires :pipeline_schedule_id, type: Integer, desc: 'The pipeline schedule id'
end
get ':id/pipeline_schedules/:pipeline_schedule_id' do
present pipeline_schedule, with: Entities::Ci::PipelineScheduleDetails
present pipeline_schedule, with: Entities::Ci::PipelineScheduleDetails, user: current_user
end
desc 'Create a new pipeline schedule' do

View File

@ -5,7 +5,9 @@ module API
module Ci
class PipelineScheduleDetails < PipelineSchedule
expose :last_pipeline, using: ::API::Entities::Ci::PipelineBasic
expose :variables, using: ::API::Entities::Ci::Variable
expose :variables,
using: ::API::Entities::Ci::Variable,
if: ->(schedule, options) { Ability.allowed?(options[:user], :read_pipeline_schedule_variables, schedule) }
end
end
end

View File

@ -87,7 +87,7 @@ module API
# TODO sort out authorization for real
# https://gitlab.com/gitlab-org/gitlab/-/issues/220912
if !project || !project.public?
unless Ability.allowed?(nil, :download_code, project)
not_found!
end

View File

@ -41,7 +41,6 @@ module API
env['api.format'] = :binary # this bypasses json serialization
body state.latest_file.read
status :ok
end
end
@ -55,8 +54,10 @@ module API
remote_state_handler.handle_with_lock do |state|
state.update_file!(CarrierWaveStringFile.new(data), version: params[:serial], build: current_authenticated_job)
status :ok
end
body false
status :ok
end
desc 'Delete a terraform state of a certain name'
@ -66,8 +67,10 @@ module API
remote_state_handler.handle_with_lock do |state|
state.destroy!
status :ok
end
body false
status :ok
end
desc 'Lock a terraform state of a certain name'

View File

@ -31,6 +31,7 @@ module Gitlab
RACK_ENV_KEY = 'HTTP_GITLAB_WORKHORSE_MULTIPART_FIELDS'
JWT_PARAM_SUFFIX = '.gitlab-workhorse-upload'
JWT_PARAM_FIXED_KEY = 'upload'
REWRITTEN_FIELD_NAME_MAX_LENGTH = 10000.freeze
class Handler
def initialize(env, message)
@ -41,6 +42,8 @@ module Gitlab
def with_open_files
@rewritten_fields.each do |field, tmp_path|
raise "invalid field: #{field.inspect}" unless valid_field_name?(field)
parsed_field = Rack::Utils.parse_nested_query(field)
raise "unexpected field: #{field.inspect}" unless parsed_field.count == 1
@ -108,6 +111,17 @@ module Gitlab
private
def valid_field_name?(name)
# length validation
return false if name.size >= REWRITTEN_FIELD_NAME_MAX_LENGTH
# brackets validation
return false if name.include?('[]') || name.start_with?('[', ']')
return false unless ::Gitlab::Utils.valid_brackets?(name, allow_nested: false)
true
end
def package_allowed_paths
packages_config = ::Gitlab.config.packages
return [] unless allow_packages_storage_path?(packages_config)
@ -141,6 +155,8 @@ module Gitlab
class HandlerForJWTParams < Handler
def with_open_files
@rewritten_fields.keys.each do |field|
raise "invalid field: #{field.inspect}" unless valid_field_name?(field)
parsed_field = Rack::Utils.parse_nested_query(field)
raise "unexpected field: #{field.inspect}" unless parsed_field.count == 1

View File

@ -50,6 +50,10 @@ module Gitlab
maven_app_name_regex
end
def nuget_package_name_regex
@nuget_package_name_regex ||= %r{\A[-+\.\_a-zA-Z0-9]+\z}.freeze
end
def nuget_version_regex
@nuget_version_regex ||= /
\A#{_semver_major_minor_patch_regex}(\.\d*)?#{_semver_prerelease_build_regex}\z
@ -208,7 +212,7 @@ module Gitlab
# See https://github.com/docker/distribution/blob/master/reference/regexp.go.
#
def container_repository_name_regex
@container_repository_regex ||= %r{\A[a-z0-9]+((?:[._/]|__|[-]{0,10})[a-z0-9]+)*\Z}
@container_repository_regex ||= %r{\A[a-z0-9]+(([._/]|__|-*)[a-z0-9])*\z}
end
##

View File

@ -174,3 +174,5 @@ module Gitlab
end
end
end
Gitlab::UsageDataCounters::IssueActivityUniqueCounter.prepend_if_ee('EE::Gitlab::UsageDataCounters::IssueActivityUniqueCounter')

View File

@ -315,3 +315,7 @@
category: issues_edit
redis_slot: project_management
aggregation: daily
- name: g_project_management_issue_health_status_changed
category: issues_edit
redis_slot: project_management
aggregation: daily

View File

@ -10,6 +10,8 @@ module Gitlab
# Also see https://gitlab.com/gitlab-org/gitlab/-/merge_requests/24223#note_284122580
# It also checks for ALT_SEPARATOR aka '\' (forward slash)
def check_path_traversal!(path)
return unless path.is_a?(String)
path = decode_path(path)
path_regex = /(\A(\.{1,2})\z|\A\.\.[\/\\]|[\/\\]\.\.\z|[\/\\]\.\.[\/\\]|\n)/
@ -208,5 +210,33 @@ module Gitlab
def stable_sort_by(list)
list.sort_by.with_index { |x, idx| [yield(x), idx] }
end
# Check for valid brackets (`[` and `]`) in a string using this aspects:
# * open brackets count == closed brackets count
# * (optionally) reject nested brackets via `allow_nested: false`
# * open / close brackets coherence, eg. ][[] -> invalid
def valid_brackets?(string = '', allow_nested: true)
# remove everything except brackets
brackets = string.remove(/[^\[\]]/)
return true if brackets.empty?
# balanced counts check
return false if brackets.size.odd?
unless allow_nested
# nested brackets check
return false if brackets.include?('[[') || brackets.include?(']]')
end
# open / close brackets coherence check
untrimmed = brackets
loop do
trimmed = untrimmed.gsub('[]', '')
return true if trimmed.empty?
return false if trimmed == untrimmed
untrimmed = trimmed
end
end
end
end

View File

@ -11072,6 +11072,9 @@ msgstr ""
msgid "Failed to create import label for jira import."
msgstr ""
msgid "Failed to create new project access token: %{token_response_message}"
msgstr ""
msgid "Failed to create repository"
msgstr ""

View File

@ -0,0 +1,52 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe 'Invalid uploads that must be rejected', :api, :js do
include_context 'file upload requests helpers'
let_it_be(:project) { create(:project) }
let_it_be(:user) { create(:user, :admin) }
let_it_be(:personal_access_token) { create(:personal_access_token, user: user) }
context 'invalid upload key', :capybara_ignore_server_errors do
let(:api_path) { "/projects/#{project.id}/packages/nuget/" }
let(:url) { capybara_url(api(api_path)) }
let(:file) { fixture_file_upload('spec/fixtures/dk.png') }
subject do
HTTParty.put(
url,
basic_auth: { user: user.username, password: personal_access_token.token },
body: body
)
end
RSpec.shared_examples 'rejecting invalid keys' do |key_name:, message: nil|
context "with invalid key #{key_name}" do
let(:body) { { key_name => file, 'package[test][name]' => 'test' } }
it { expect { subject }.not_to change { Packages::Package.nuget.count } }
it { expect(subject.code).to eq(500) }
it { expect(subject.body).to include(message.presence || "invalid field: \"#{key_name}\"") }
end
end
RSpec.shared_examples 'by rejecting uploads with an invalid key' do
it_behaves_like 'rejecting invalid keys', key_name: 'package[test'
it_behaves_like 'rejecting invalid keys', key_name: '[]'
it_behaves_like 'rejecting invalid keys', key_name: '[package]test'
it_behaves_like 'rejecting invalid keys', key_name: 'package][test]]'
it_behaves_like 'rejecting invalid keys', key_name: 'package[test[nested]]'
end
# These keys are rejected directly by rack itself.
# The request will not be received by multipart.rb (can't use the 'handling file uploads' shared example)
it_behaves_like 'rejecting invalid keys', key_name: 'x' * 11000, message: 'Puma caught this error: exceeded available parameter key space (RangeError)'
it_behaves_like 'rejecting invalid keys', key_name: 'package[]test', message: 'Puma caught this error: expected Hash (got Array)'
it_behaves_like 'handling file uploads', 'by rejecting uploads with an invalid key'
end
end

View File

@ -1,7 +1,7 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`~/releases/components/issuable_stats.vue matches snapshot 1`] = `
"<div class=\\"gl-display-flex gl-flex-direction-column gl-flex-shrink-0 gl-mr-6 gl-mb-5 js-issues-container\\"><span class=\\"gl-mb-2\\">
"<div class=\\"gl-display-flex gl-flex-direction-column gl-flex-shrink-0 gl-mr-6 gl-mb-5\\"><span class=\\"gl-mb-2\\">
Items
<span class=\\"badge badge-muted badge-pill gl-badge sm\\"><!----> 10</span></span>
<div class=\\"gl-display-flex\\"><span data-testid=\\"open-stat\\" class=\\"gl-white-space-pre-wrap\\">Open: <a href=\\"path/to/open/items\\" class=\\"gl-link\\">1</a></span> <span class=\\"gl-mx-2\\">•</span> <span data-testid=\\"merged-stat\\" class=\\"gl-white-space-pre-wrap\\">Merged: <a href=\\"path/to/merged/items\\" class=\\"gl-link\\">7</a></span> <span class=\\"gl-mx-2\\">•</span> <span data-testid=\\"closed-stat\\" class=\\"gl-white-space-pre-wrap\\">Closed: <a href=\\"path/to/closed/items\\" class=\\"gl-link\\">2</a></span></div>

View File

@ -31,7 +31,8 @@ describe('Release block milestone info', () => {
const milestoneProgressBarContainer = () => wrapper.find('.js-milestone-progress-bar-container');
const milestoneListContainer = () => wrapper.find('.js-milestone-list-container');
const issuesContainer = () => wrapper.find('.js-issues-container');
const issuesContainer = () => wrapper.find('[data-testid="issue-stats"]');
const mergeRequestsContainer = () => wrapper.find('[data-testid="merge-request-stats"]');
describe('with default props', () => {
beforeEach(() => factory({ milestones }));
@ -187,4 +188,33 @@ describe('Release block milestone info', () => {
expectAllZeros();
});
describe('if the API response is missing the "mr_stats" property', () => {
beforeEach(() => factory({ milestones }));
it('does not render merge request stats', () => {
expect(mergeRequestsContainer().exists()).toBe(false);
});
});
describe('if the API response includes the "mr_stats" property', () => {
beforeEach(() => {
milestones = milestones.map(m => ({
...m,
mrStats: {
total: 15,
merged: 12,
closed: 1,
},
}));
return factory({ milestones });
});
it('renders merge request stats', () => {
expect(trimText(mergeRequestsContainer().text())).toBe(
'Merge Requests 30 Open: 4 • Merged: 24 • Closed: 2',
);
});
});
});

View File

@ -88,7 +88,7 @@ RSpec.describe Gitlab::ExclusiveLeaseHelpers, :clean_gitlab_redis_shared_state d
let(:options) { { retries: 0 } }
it 'never sleeps' do
expect(class_instance).not_to receive(:sleep)
expect_any_instance_of(Gitlab::ExclusiveLeaseHelpers::SleepingLock).not_to receive(:sleep)
expect { subject }.to raise_error('Failed to obtain a lock')
end
@ -98,7 +98,7 @@ RSpec.describe Gitlab::ExclusiveLeaseHelpers, :clean_gitlab_redis_shared_state d
let(:options) { { retries: 1, sleep_sec: 0.05.seconds } }
it 'receives the specified argument' do
expect_any_instance_of(Object).to receive(:sleep).with(0.05.seconds).once
expect_any_instance_of(Gitlab::ExclusiveLeaseHelpers::SleepingLock).to receive(:sleep).with(0.05.seconds).once
expect { subject }.to raise_error('Failed to obtain a lock')
end
@ -108,8 +108,8 @@ RSpec.describe Gitlab::ExclusiveLeaseHelpers, :clean_gitlab_redis_shared_state d
let(:options) { { retries: 2, sleep_sec: ->(num) { 0.1 + num } } }
it 'receives the specified argument' do
expect_any_instance_of(Object).to receive(:sleep).with(1.1.seconds).once
expect_any_instance_of(Object).to receive(:sleep).with(2.1.seconds).once
expect_any_instance_of(Gitlab::ExclusiveLeaseHelpers::SleepingLock).to receive(:sleep).with(1.1.seconds).once
expect_any_instance_of(Gitlab::ExclusiveLeaseHelpers::SleepingLock).to receive(:sleep).with(2.1.seconds).once
expect { subject }.to raise_error('Failed to obtain a lock')
end

View File

@ -59,7 +59,7 @@ RSpec.describe Gitlab::ImportExport::AttributesFinder do
end
before do
allow_any_instance_of(Gitlab::ImportExport).to receive(:config_file).and_return(test_config)
allow(Gitlab::ImportExport).to receive(:config_file).and_return(test_config)
end
it 'generates hash from project tree config' do

View File

@ -12,7 +12,7 @@ RSpec.describe Gitlab::ImportExport::Group::LegacyTreeSaver do
before do
group.add_maintainer(user)
allow_any_instance_of(Gitlab::ImportExport).to receive(:storage_path).and_return(export_path)
allow(Gitlab::ImportExport).to receive(:storage_path).and_return(export_path)
end
after do

View File

@ -12,7 +12,7 @@ RSpec.describe Gitlab::ImportExport::Importer do
subject(:importer) { described_class.new(project) }
before do
allow_any_instance_of(Gitlab::ImportExport).to receive(:storage_path).and_return(test_path)
allow(Gitlab::ImportExport).to receive(:storage_path).and_return(test_path)
allow_any_instance_of(Gitlab::ImportExport::FileImporter).to receive(:remove_import_file)
stub_uploads_object_storage(FileUploader)

View File

@ -13,7 +13,7 @@ RSpec.describe Gitlab::ImportExport::LfsRestorer do
subject(:restorer) { described_class.new(project: project, shared: shared) }
before do
allow_any_instance_of(Gitlab::ImportExport).to receive(:storage_path).and_return(export_path)
allow(Gitlab::ImportExport).to receive(:storage_path).and_return(export_path)
FileUtils.mkdir_p(shared.export_path)
end

View File

@ -123,15 +123,46 @@ RSpec.describe Gitlab::Middleware::Multipart do
end
end
context 'with invalid key in parameters' do
context 'with an invalid upload key' do
include_context 'with one temporary file for multipart'
let(:rewritten_fields) { rewritten_fields_hash('file' => uploaded_filepath) }
let(:params) { upload_parameters_for(filepath: uploaded_filepath, key: 'wrong_key', filename: filename, remote_id: remote_id) }
RSpec.shared_examples 'rejecting the invalid key' do |key_in_header:, key_in_upload_params:, error_message:|
let(:rewritten_fields) { rewritten_fields_hash(key_in_header => uploaded_filepath) }
let(:params) { upload_parameters_for(filepath: uploaded_filepath, key: key_in_upload_params, filename: filename, remote_id: remote_id) }
it 'raises an error' do
expect { subject }.to raise_error(RuntimeError, 'Empty JWT param: file.gitlab-workhorse-upload')
it 'raises an error' do
expect { subject }.to raise_error(RuntimeError, error_message)
end
end
it_behaves_like 'rejecting the invalid key',
key_in_header: 'file',
key_in_upload_params: 'wrong_key',
error_message: 'Empty JWT param: file.gitlab-workhorse-upload'
it_behaves_like 'rejecting the invalid key',
key_in_header: 'user[avatar',
key_in_upload_params: 'user[avatar]',
error_message: 'invalid field: "user[avatar"'
it_behaves_like 'rejecting the invalid key',
key_in_header: '[user]avatar',
key_in_upload_params: 'user[avatar]',
error_message: 'invalid field: "[user]avatar"'
it_behaves_like 'rejecting the invalid key',
key_in_header: 'user[]avatar',
key_in_upload_params: 'user[avatar]',
error_message: 'invalid field: "user[]avatar"'
it_behaves_like 'rejecting the invalid key',
key_in_header: 'user[avatar[image[url]]]',
key_in_upload_params: 'user[avatar]',
error_message: 'invalid field: "user[avatar[image[url]]]"'
it_behaves_like 'rejecting the invalid key',
key_in_header: '[]',
key_in_upload_params: 'user[avatar]',
error_message: 'invalid field: "[]"'
it_behaves_like 'rejecting the invalid key',
key_in_header: 'x' * 11000,
key_in_upload_params: 'user[avatar]',
error_message: "invalid field: \"#{'x' * 11000}\""
end
context 'with a modified JWT payload' do

View File

@ -139,6 +139,58 @@ RSpec.describe Gitlab::Middleware::Multipart do
subject
end
end
context 'with invalid key in header' do
include_context 'with one temporary file for multipart'
RSpec.shared_examples 'rejecting the invalid key' do |key_in_header:, key_in_upload_params:, error_message:|
let(:rewritten_fields) { rewritten_fields_hash(key_in_header => uploaded_filepath) }
let(:params) { upload_parameters_for(filepath: uploaded_filepath, key: key_in_upload_params, filename: filename, remote_id: remote_id) }
it 'raises an error' do
expect { subject }.to raise_error(RuntimeError, error_message)
end
end
it_behaves_like 'rejecting the invalid key',
key_in_header: 'user[avatar',
key_in_upload_params: 'user[avatar]',
error_message: 'invalid field: "user[avatar"'
it_behaves_like 'rejecting the invalid key',
key_in_header: '[user]avatar',
key_in_upload_params: 'user[avatar]',
error_message: 'invalid field: "[user]avatar"'
it_behaves_like 'rejecting the invalid key',
key_in_header: 'user[]avatar',
key_in_upload_params: 'user[avatar]',
error_message: 'invalid field: "user[]avatar"'
it_behaves_like 'rejecting the invalid key',
key_in_header: 'user[avatar[image[url]]]',
key_in_upload_params: 'user[avatar]',
error_message: 'invalid field: "user[avatar[image[url]]]"'
it_behaves_like 'rejecting the invalid key',
key_in_header: '[]',
key_in_upload_params: 'user[avatar]',
error_message: 'invalid field: "[]"'
it_behaves_like 'rejecting the invalid key',
key_in_header: 'x' * 11000,
key_in_upload_params: 'user[avatar]',
error_message: "invalid field: \"#{'x' * 11000}\""
end
context 'with key with unbalanced brackets in header' do
include_context 'with one temporary file for multipart'
let(:invalid_key) { 'user[avatar' }
let(:rewritten_fields) { rewritten_fields_hash( invalid_key => uploaded_filepath) }
let(:params) { upload_parameters_for(filepath: uploaded_filepath, key: 'user[avatar]', filename: filename, remote_id: remote_id) }
it 'builds no UploadedFile' do
expect(app).not_to receive(:call)
expect { subject }.to raise_error(RuntimeError, "invalid field: \"#{invalid_key}\"")
end
end
end
end
end

View File

@ -137,11 +137,16 @@ RSpec.describe Gitlab::Regex do
it { is_expected.to match('my/awesome/image-1') }
it { is_expected.to match('my/awesome/image.test') }
it { is_expected.to match('my/awesome/image--test') }
# docker distribution allows for infinite `-`
# https://github.com/docker/distribution/blob/master/reference/regexp.go#L13
# but we have a range of 0,10 to add a reasonable limit.
it { is_expected.not_to match('my/image-----------test') }
it { is_expected.to match('my/image__test') }
# this example tests for catastrophic backtracking
it { is_expected.to match('user1/project/a_bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb------------x') }
it { is_expected.not_to match('user1/project/a_bbbbb-------------') }
it { is_expected.not_to match('my/image-.test') }
it { is_expected.not_to match('my/image___test') }
it { is_expected.not_to match('my/image_.test') }
it { is_expected.not_to match('my/image_-test') }
it { is_expected.not_to match('my/image..test') }
it { is_expected.not_to match('my/image\ntest') }
it { is_expected.not_to match('.my/image') }
it { is_expected.not_to match('my/image.') }
end
@ -372,6 +377,21 @@ RSpec.describe Gitlab::Regex do
it { is_expected.not_to match('%2e%2e%2f1.2.3') }
end
describe '.nuget_package_name_regex' do
subject { described_class.nuget_package_name_regex }
it { is_expected.to match('My.Package') }
it { is_expected.to match('My.Package.Mvc') }
it { is_expected.to match('MyPackage') }
it { is_expected.to match('My.23.Package') }
it { is_expected.to match('My23Package') }
it { is_expected.to match('runtime.my-test64.runtime.package.Mvc') }
it { is_expected.to match('my_package') }
it { is_expected.not_to match('My/package') }
it { is_expected.not_to match('../../../my_package') }
it { is_expected.not_to match('%2e%2e%2fmy_package') }
end
describe '.pypi_version_regex' do
subject { described_class.pypi_version_regex }

View File

@ -8,42 +8,8 @@ RSpec.describe Gitlab::UsageDataCounters::IssueActivityUniqueCounter, :clean_git
let(:user3) { build(:user, id: 3) }
let(:time) { Time.zone.now }
shared_examples 'tracks and counts action' do
before do
stub_application_setting(usage_ping_enabled: true)
end
def count_unique(date_from:, date_to:)
Gitlab::UsageDataCounters::HLLRedisCounter.unique_events(event_names: action, start_date: date_from, end_date: date_to)
end
specify do
aggregate_failures do
expect(track_action(author: user1)).to be_truthy
expect(track_action(author: user1)).to be_truthy
expect(track_action(author: user2)).to be_truthy
expect(track_action(author: user3, time: time - 3.days)).to be_truthy
expect(count_unique(date_from: time, date_to: time)).to eq(2)
expect(count_unique(date_from: time - 5.days, date_to: 1.day.since(time))).to eq(3)
end
end
it 'does not track edit actions if author is not present' do
expect(track_action(author: nil)).to be_nil
end
context 'when feature flag track_issue_activity_actions is disabled' do
it 'does not track edit actions' do
stub_feature_flags(track_issue_activity_actions: false)
expect(track_action(author: user1)).to be_nil
end
end
end
context 'for Issue title edit actions' do
it_behaves_like 'tracks and counts action' do
it_behaves_like 'a tracked issue edit event' do
let(:action) { described_class::ISSUE_TITLE_CHANGED }
def track_action(params)
@ -53,7 +19,7 @@ RSpec.describe Gitlab::UsageDataCounters::IssueActivityUniqueCounter, :clean_git
end
context 'for Issue description edit actions' do
it_behaves_like 'tracks and counts action' do
it_behaves_like 'a tracked issue edit event' do
let(:action) { described_class::ISSUE_DESCRIPTION_CHANGED }
def track_action(params)
@ -63,7 +29,7 @@ RSpec.describe Gitlab::UsageDataCounters::IssueActivityUniqueCounter, :clean_git
end
context 'for Issue assignee edit actions' do
it_behaves_like 'tracks and counts action' do
it_behaves_like 'a tracked issue edit event' do
let(:action) { described_class::ISSUE_ASSIGNEE_CHANGED }
def track_action(params)
@ -73,7 +39,7 @@ RSpec.describe Gitlab::UsageDataCounters::IssueActivityUniqueCounter, :clean_git
end
context 'for Issue make confidential actions' do
it_behaves_like 'tracks and counts action' do
it_behaves_like 'a tracked issue edit event' do
let(:action) { described_class::ISSUE_MADE_CONFIDENTIAL }
def track_action(params)
@ -83,7 +49,7 @@ RSpec.describe Gitlab::UsageDataCounters::IssueActivityUniqueCounter, :clean_git
end
context 'for Issue make visible actions' do
it_behaves_like 'tracks and counts action' do
it_behaves_like 'a tracked issue edit event' do
let(:action) { described_class::ISSUE_MADE_VISIBLE }
def track_action(params)
@ -93,7 +59,7 @@ RSpec.describe Gitlab::UsageDataCounters::IssueActivityUniqueCounter, :clean_git
end
context 'for Issue created actions' do
it_behaves_like 'tracks and counts action' do
it_behaves_like 'a tracked issue edit event' do
let(:action) { described_class::ISSUE_CREATED }
def track_action(params)
@ -103,7 +69,7 @@ RSpec.describe Gitlab::UsageDataCounters::IssueActivityUniqueCounter, :clean_git
end
context 'for Issue closed actions' do
it_behaves_like 'tracks and counts action' do
it_behaves_like 'a tracked issue edit event' do
let(:action) { described_class::ISSUE_CLOSED }
def track_action(params)
@ -113,7 +79,7 @@ RSpec.describe Gitlab::UsageDataCounters::IssueActivityUniqueCounter, :clean_git
end
context 'for Issue reopened actions' do
it_behaves_like 'tracks and counts action' do
it_behaves_like 'a tracked issue edit event' do
let(:action) { described_class::ISSUE_REOPENED }
def track_action(params)
@ -123,7 +89,7 @@ RSpec.describe Gitlab::UsageDataCounters::IssueActivityUniqueCounter, :clean_git
end
context 'for Issue label changed actions' do
it_behaves_like 'tracks and counts action' do
it_behaves_like 'a tracked issue edit event' do
let(:action) { described_class::ISSUE_LABEL_CHANGED }
def track_action(params)
@ -133,7 +99,7 @@ RSpec.describe Gitlab::UsageDataCounters::IssueActivityUniqueCounter, :clean_git
end
context 'for Issue cross-referenced actions' do
it_behaves_like 'tracks and counts action' do
it_behaves_like 'a tracked issue edit event' do
let(:action) { described_class::ISSUE_CROSS_REFERENCED }
def track_action(params)
@ -143,7 +109,7 @@ RSpec.describe Gitlab::UsageDataCounters::IssueActivityUniqueCounter, :clean_git
end
context 'for Issue moved actions' do
it_behaves_like 'tracks and counts action' do
it_behaves_like 'a tracked issue edit event' do
let(:action) { described_class::ISSUE_MOVED }
def track_action(params)
@ -153,7 +119,7 @@ RSpec.describe Gitlab::UsageDataCounters::IssueActivityUniqueCounter, :clean_git
end
context 'for Issue relate actions' do
it_behaves_like 'tracks and counts action' do
it_behaves_like 'a tracked issue edit event' do
let(:action) { described_class::ISSUE_RELATED }
def track_action(params)
@ -163,7 +129,7 @@ RSpec.describe Gitlab::UsageDataCounters::IssueActivityUniqueCounter, :clean_git
end
context 'for Issue unrelate actions' do
it_behaves_like 'tracks and counts action' do
it_behaves_like 'a tracked issue edit event' do
let(:action) { described_class::ISSUE_UNRELATED }
def track_action(params)
@ -173,7 +139,7 @@ RSpec.describe Gitlab::UsageDataCounters::IssueActivityUniqueCounter, :clean_git
end
context 'for Issue marked as duplicate actions' do
it_behaves_like 'tracks and counts action' do
it_behaves_like 'a tracked issue edit event' do
let(:action) { described_class::ISSUE_MARKED_AS_DUPLICATE }
def track_action(params)
@ -183,7 +149,7 @@ RSpec.describe Gitlab::UsageDataCounters::IssueActivityUniqueCounter, :clean_git
end
context 'for Issue locked actions' do
it_behaves_like 'tracks and counts action' do
it_behaves_like 'a tracked issue edit event' do
let(:action) { described_class::ISSUE_LOCKED }
def track_action(params)
@ -193,7 +159,7 @@ RSpec.describe Gitlab::UsageDataCounters::IssueActivityUniqueCounter, :clean_git
end
context 'for Issue unlocked actions' do
it_behaves_like 'tracks and counts action' do
it_behaves_like 'a tracked issue edit event' do
let(:action) { described_class::ISSUE_UNLOCKED }
def track_action(params)
@ -203,7 +169,7 @@ RSpec.describe Gitlab::UsageDataCounters::IssueActivityUniqueCounter, :clean_git
end
context 'for Issue added to epic actions' do
it_behaves_like 'tracks and counts action' do
it_behaves_like 'a tracked issue edit event' do
let(:action) { described_class::ISSUE_ADDED_TO_EPIC}
def track_action(params)
@ -213,7 +179,7 @@ RSpec.describe Gitlab::UsageDataCounters::IssueActivityUniqueCounter, :clean_git
end
context 'for Issue removed from epic actions' do
it_behaves_like 'tracks and counts action' do
it_behaves_like 'a tracked issue edit event' do
let(:action) { described_class::ISSUE_REMOVED_FROM_EPIC}
def track_action(params)
@ -223,7 +189,7 @@ RSpec.describe Gitlab::UsageDataCounters::IssueActivityUniqueCounter, :clean_git
end
context 'for Issue changed epic actions' do
it_behaves_like 'tracks and counts action' do
it_behaves_like 'a tracked issue edit event' do
let(:action) { described_class::ISSUE_CHANGED_EPIC}
def track_action(params)
@ -233,7 +199,7 @@ RSpec.describe Gitlab::UsageDataCounters::IssueActivityUniqueCounter, :clean_git
end
context 'for Issue designs added actions' do
it_behaves_like 'tracks and counts action' do
it_behaves_like 'a tracked issue edit event' do
let(:action) { described_class::ISSUE_DESIGNS_ADDED }
def track_action(params)
@ -243,7 +209,7 @@ RSpec.describe Gitlab::UsageDataCounters::IssueActivityUniqueCounter, :clean_git
end
context 'for Issue designs modified actions' do
it_behaves_like 'tracks and counts action' do
it_behaves_like 'a tracked issue edit event' do
let(:action) { described_class::ISSUE_DESIGNS_MODIFIED }
def track_action(params)
@ -253,7 +219,7 @@ RSpec.describe Gitlab::UsageDataCounters::IssueActivityUniqueCounter, :clean_git
end
context 'for Issue designs removed actions' do
it_behaves_like 'tracks and counts action' do
it_behaves_like 'a tracked issue edit event' do
let(:action) { described_class::ISSUE_DESIGNS_REMOVED }
def track_action(params)
@ -263,7 +229,7 @@ RSpec.describe Gitlab::UsageDataCounters::IssueActivityUniqueCounter, :clean_git
end
context 'for Issue due date changed actions' do
it_behaves_like 'tracks and counts action' do
it_behaves_like 'a tracked issue edit event' do
let(:action) { described_class::ISSUE_DUE_DATE_CHANGED }
def track_action(params)
@ -273,7 +239,7 @@ RSpec.describe Gitlab::UsageDataCounters::IssueActivityUniqueCounter, :clean_git
end
context 'for Issue time estimate changed actions' do
it_behaves_like 'tracks and counts action' do
it_behaves_like 'a tracked issue edit event' do
let(:action) { described_class::ISSUE_TIME_ESTIMATE_CHANGED }
def track_action(params)
@ -283,7 +249,7 @@ RSpec.describe Gitlab::UsageDataCounters::IssueActivityUniqueCounter, :clean_git
end
context 'for Issue time spent changed actions' do
it_behaves_like 'tracks and counts action' do
it_behaves_like 'a tracked issue edit event' do
let(:action) { described_class::ISSUE_TIME_SPENT_CHANGED }
def track_action(params)
@ -293,7 +259,7 @@ RSpec.describe Gitlab::UsageDataCounters::IssueActivityUniqueCounter, :clean_git
end
context 'for Issue comment added actions' do
it_behaves_like 'tracks and counts action' do
it_behaves_like 'a tracked issue edit event' do
let(:action) { described_class::ISSUE_COMMENT_ADDED }
def track_action(params)
@ -303,7 +269,7 @@ RSpec.describe Gitlab::UsageDataCounters::IssueActivityUniqueCounter, :clean_git
end
context 'for Issue comment edited actions' do
it_behaves_like 'tracks and counts action' do
it_behaves_like 'a tracked issue edit event' do
let(:action) { described_class::ISSUE_COMMENT_EDITED }
def track_action(params)
@ -313,7 +279,7 @@ RSpec.describe Gitlab::UsageDataCounters::IssueActivityUniqueCounter, :clean_git
end
context 'for Issue comment removed actions' do
it_behaves_like 'tracks and counts action' do
it_behaves_like 'a tracked issue edit event' do
let(:action) { described_class::ISSUE_COMMENT_REMOVED }
def track_action(params)

View File

@ -3,6 +3,8 @@
require 'spec_helper'
RSpec.describe Gitlab::Utils do
using RSpec::Parameterized::TableSyntax
delegate :to_boolean, :boolean_to_yes_no, :slugify, :random_string, :which,
:ensure_array_from_string, :to_exclusive_sentence, :bytes_to_megabytes,
:append_path, :check_path_traversal!, :allowlisted?, :check_allowed_absolute_path!, :decode_path, :ms_to_round_sec, to: :described_class
@ -50,6 +52,10 @@ RSpec.describe Gitlab::Utils do
expect(check_path_traversal!('dir/..foo.rb')).to eq('dir/..foo.rb')
expect(check_path_traversal!('dir/.foo.rb')).to eq('dir/.foo.rb')
end
it 'does nothing for a non-string' do
expect(check_path_traversal!(nil)).to be_nil
end
end
describe '.allowlisted?' do
@ -448,4 +454,29 @@ RSpec.describe Gitlab::Utils do
end
end
end
describe '.valid_brackets?' do
where(:input, :allow_nested, :valid) do
'no brackets' | true | true
'no brackets' | false | true
'user[avatar]' | true | true
'user[avatar]' | false | true
'user[avatar][friends]' | true | true
'user[avatar][friends]' | false | true
'user[avatar[image[url]]]' | true | true
'user[avatar[image[url]]]' | false | false
'user[avatar[]friends]' | true | true
'user[avatar[]friends]' | false | false
'user[avatar]]' | true | false
'user[avatar]]' | false | false
'user][avatar]]' | true | false
'user][avatar]]' | false | false
'user[avatar' | true | false
'user[avatar' | false | false
end
with_them do
it { expect(described_class.valid_brackets?(input, allow_nested: allow_nested)).to eq(valid) }
end
end
end

View File

@ -122,6 +122,21 @@ RSpec.describe Packages::Package, type: :model do
it { is_expected.not_to allow_value('my file name').for(:name) }
it { is_expected.not_to allow_value('!!().for(:name)().for(:name)').for(:name) }
end
context 'nuget package' do
subject { build_stubbed(:nuget_package) }
it { is_expected.to allow_value('My.Package').for(:name) }
it { is_expected.to allow_value('My.Package.Mvc').for(:name) }
it { is_expected.to allow_value('MyPackage').for(:name) }
it { is_expected.to allow_value('My.23.Package').for(:name) }
it { is_expected.to allow_value('My23Package').for(:name) }
it { is_expected.to allow_value('runtime.my-test64.runtime.package.Mvc').for(:name) }
it { is_expected.to allow_value('my_package').for(:name) }
it { is_expected.not_to allow_value('My/package').for(:name) }
it { is_expected.not_to allow_value('../../../my_package').for(:name) }
it { is_expected.not_to allow_value('%2e%2e%2fmy_package').for(:name) }
end
end
describe '#version' do

View File

@ -137,7 +137,7 @@ RSpec.describe ProjectPolicy do
it 'disallows all permissions except pipeline when the feature is disabled' do
builds_permissions = [
:create_build, :read_build, :update_build, :admin_build, :destroy_build,
:create_pipeline_schedule, :read_pipeline_schedule, :update_pipeline_schedule, :admin_pipeline_schedule, :destroy_pipeline_schedule,
:create_pipeline_schedule, :read_pipeline_schedule_variables, :update_pipeline_schedule, :admin_pipeline_schedule, :destroy_pipeline_schedule,
:create_environment, :read_environment, :update_environment, :admin_environment, :destroy_environment,
:create_cluster, :read_cluster, :update_cluster, :admin_cluster, :destroy_cluster,
:create_deployment, :read_deployment, :update_deployment, :admin_deployment, :destroy_deployment

View File

@ -97,14 +97,50 @@ RSpec.describe API::Ci::PipelineSchedules do
pipeline_schedule.pipelines << build(:ci_pipeline, project: project)
end
context 'authenticated user with valid permissions' do
it 'returns pipeline_schedule details' do
get api("/projects/#{project.id}/pipeline_schedules/#{pipeline_schedule.id}", developer)
matcher :return_pipeline_schedule_sucessfully do
match_unless_raises do |reponse|
expect(response).to have_gitlab_http_status(:ok)
expect(response).to match_response_schema('pipeline_schedule')
end
end
shared_context 'request with project permissions' do
context 'authenticated user with project permisions' do
before do
project.add_maintainer(user)
end
it 'returns pipeline_schedule details' do
get api("/projects/#{project.id}/pipeline_schedules/#{pipeline_schedule.id}", user)
expect(response).to return_pipeline_schedule_sucessfully
expect(json_response).to have_key('variables')
end
end
end
shared_examples 'request with schedule ownership' do
context 'authenticated user with pipeline schedule ownership' do
it 'returns pipeline_schedule details' do
get api("/projects/#{project.id}/pipeline_schedules/#{pipeline_schedule.id}", developer)
expect(response).to return_pipeline_schedule_sucessfully
expect(json_response).to have_key('variables')
end
end
end
shared_examples 'request with unauthenticated user' do
context 'with unauthenticated user' do
it 'does not return pipeline_schedule' do
get api("/projects/#{project.id}/pipeline_schedules/#{pipeline_schedule.id}")
expect(response).to have_gitlab_http_status(:unauthorized)
end
end
end
shared_examples 'request with non-existing pipeline_schedule' do
it 'responds with 404 Not Found if requesting non-existing pipeline_schedule' do
get api("/projects/#{project.id}/pipeline_schedules/-5", developer)
@ -112,31 +148,61 @@ RSpec.describe API::Ci::PipelineSchedules do
end
end
context 'authenticated user with invalid permissions' do
it 'does not return pipeline_schedules list' do
get api("/projects/#{project.id}/pipeline_schedules/#{pipeline_schedule.id}", user)
context 'with private project' do
it_behaves_like 'request with schedule ownership'
it_behaves_like 'request with project permissions'
it_behaves_like 'request with unauthenticated user'
it_behaves_like 'request with non-existing pipeline_schedule'
expect(response).to have_gitlab_http_status(:not_found)
context 'authenticated user with no project permissions' do
it 'does not return pipeline_schedule' do
get api("/projects/#{project.id}/pipeline_schedules/#{pipeline_schedule.id}", user)
expect(response).to have_gitlab_http_status(:not_found)
end
end
context 'authenticated user with insufficient project permissions' do
before do
project.add_guest(user)
end
it 'does not return pipeline_schedule' do
get api("/projects/#{project.id}/pipeline_schedules/#{pipeline_schedule.id}", user)
expect(response).to have_gitlab_http_status(:not_found)
end
end
end
context 'authenticated user with insufficient permissions' do
before do
project.add_guest(user)
context 'with public project' do
let_it_be(:project) { create(:project, :repository, :public, public_builds: false) }
it_behaves_like 'request with schedule ownership'
it_behaves_like 'request with project permissions'
it_behaves_like 'request with unauthenticated user'
it_behaves_like 'request with non-existing pipeline_schedule'
context 'authenticated user with no project permissions' do
it 'returns pipeline_schedule with no variables' do
get api("/projects/#{project.id}/pipeline_schedules/#{pipeline_schedule.id}", user)
expect(response).to return_pipeline_schedule_sucessfully
expect(json_response).not_to have_key('variables')
end
end
it 'does not return pipeline_schedules list' do
get api("/projects/#{project.id}/pipeline_schedules/#{pipeline_schedule.id}", user)
context 'authenticated user with insufficient project permissions' do
before do
project.add_guest(user)
end
expect(response).to have_gitlab_http_status(:not_found)
end
end
it 'returns pipeline_schedule with no variables' do
get api("/projects/#{project.id}/pipeline_schedules/#{pipeline_schedule.id}", user)
context 'unauthenticated user' do
it 'does not return pipeline_schedules list' do
get api("/projects/#{project.id}/pipeline_schedules/#{pipeline_schedule.id}")
expect(response).to have_gitlab_http_status(:unauthorized)
expect(response).to return_pipeline_schedule_sucessfully
expect(json_response).not_to have_key('variables')
end
end
end
end

View File

@ -166,6 +166,16 @@ RSpec.describe API::Internal::Kubernetes do
)
)
end
context 'repository is for project members only' do
let(:project) { create(:project, :public, :repository_private) }
it 'returns 404' do
send_request(params: { id: project.id }, headers: { 'Authorization' => "Bearer #{agent_token.token}" })
expect(response).to have_gitlab_http_status(:not_found)
end
end
end
context 'project is private' do
@ -190,7 +200,7 @@ RSpec.describe API::Internal::Kubernetes do
context 'project does not exist' do
it 'returns 404' do
send_request(params: { id: 0 }, headers: { 'Authorization' => "Bearer #{agent_token.token}" })
send_request(params: { id: non_existing_record_id }, headers: { 'Authorization' => "Bearer #{agent_token.token}" })
expect(response).to have_gitlab_http_status(:not_found)
end

View File

@ -26,7 +26,7 @@ RSpec.describe API::ProjectExport, :clean_gitlab_redis_cache do
let(:export_path) { "#{Dir.tmpdir}/project_export_spec" }
before do
allow_any_instance_of(Gitlab::ImportExport).to receive(:storage_path).and_return(export_path)
allow(Gitlab::ImportExport).to receive(:storage_path).and_return(export_path)
allow_next_instance_of(ProjectExportWorker) do |job|
allow(job).to receive(:jid).and_return(SecureRandom.hex(8))
end

View File

@ -125,6 +125,7 @@ RSpec.describe API::Terraform::State do
expect { request }.to change { Terraform::State.count }.by(0)
expect(response).to have_gitlab_http_status(:ok)
expect(Gitlab::Json.parse(response.body)).to be_empty
end
context 'on Unicorn', :unicorn do
@ -132,6 +133,7 @@ RSpec.describe API::Terraform::State do
expect { request }.to change { Terraform::State.count }.by(0)
expect(response).to have_gitlab_http_status(:ok)
expect(Gitlab::Json.parse(response.body)).to be_empty
end
end
end
@ -167,6 +169,7 @@ RSpec.describe API::Terraform::State do
expect { request }.to change { Terraform::State.count }.by(1)
expect(response).to have_gitlab_http_status(:ok)
expect(Gitlab::Json.parse(response.body)).to be_empty
end
context 'on Unicorn', :unicorn do
@ -174,6 +177,7 @@ RSpec.describe API::Terraform::State do
expect { request }.to change { Terraform::State.count }.by(1)
expect(response).to have_gitlab_http_status(:ok)
expect(Gitlab::Json.parse(response.body)).to be_empty
end
end
end
@ -218,10 +222,11 @@ RSpec.describe API::Terraform::State do
context 'with maintainer permissions' do
let(:current_user) { maintainer }
it 'deletes the state' do
it 'deletes the state and returns empty body' do
expect { request }.to change { Terraform::State.count }.by(-1)
expect(response).to have_gitlab_http_status(:ok)
expect(Gitlab::Json.parse(response.body)).to be_empty
end
end

View File

@ -198,24 +198,26 @@ RSpec.describe Packages::Nuget::UpdatePackageFromMetadataService, :clean_gitlab_
it_behaves_like 'raising an', ::Packages::Nuget::MetadataExtractionService::ExtractionError
end
context 'with package file with a blank package name' do
before do
allow(service).to receive(:package_name).and_return('')
context 'with an invalid package name' do
invalid_names = [
'',
'My/package',
'../../../my_package',
'%2e%2e%2fmy_package'
]
invalid_names.each do |invalid_name|
before do
allow(service).to receive(:package_name).and_return(invalid_name)
end
it_behaves_like 'raising an', ::Packages::Nuget::UpdatePackageFromMetadataService::InvalidMetadataError
end
it_behaves_like 'raising an', ::Packages::Nuget::UpdatePackageFromMetadataService::InvalidMetadataError
end
context 'with package file with a blank package version' do
before do
allow(service).to receive(:package_version).and_return('')
end
it_behaves_like 'raising an', ::Packages::Nuget::UpdatePackageFromMetadataService::InvalidMetadataError
end
context 'with an invalid package version' do
invalid_versions = [
'',
'555',
'1.2',
'1./2.3',
@ -224,13 +226,11 @@ RSpec.describe Packages::Nuget::UpdatePackageFromMetadataService, :clean_gitlab_
]
invalid_versions.each do |invalid_version|
it "raises an error for version #{invalid_version}" do
before do
allow(service).to receive(:package_version).and_return(invalid_version)
expect { subject }.to raise_error(ActiveRecord::RecordInvalid, 'Validation failed: Version is invalid')
expect(package_file.file_name).not_to include(invalid_version)
expect(package_file.file.file.path).not_to include(invalid_version)
end
it_behaves_like 'raising an', ::Packages::Nuget::UpdatePackageFromMetadataService::InvalidMetadataError
end
end
end

View File

@ -11,16 +11,15 @@ RSpec.describe ResourceAccessTokens::CreateService do
describe '#execute' do
# Created shared_examples as it will easy to include specs for group bots in https://gitlab.com/gitlab-org/gitlab/-/issues/214046
shared_examples 'fails when user does not have the permission to create a Resource Bot' do
before_all do
resource.add_developer(user)
shared_examples 'token creation fails' do
let(:resource) { create(:project)}
it 'does not add the project bot as a member' do
expect { subject }.not_to change { resource.members.count }
end
it 'returns error' do
response = subject
expect(response.error?).to be true
expect(response.message).to eq("User does not have permission to create #{resource_type} Access Token")
it 'immediately destroys the bot user if one was created', :sidekiq_inline do
expect { subject }.not_to change { User.bots.count }
end
end
@ -154,24 +153,36 @@ RSpec.describe ResourceAccessTokens::CreateService do
context 'when invalid scope is passed' do
let_it_be(:params) { { scopes: [:invalid_scope] } }
it 'returns error' do
it_behaves_like 'token creation fails'
it 'returns the scope error message' do
response = subject
expect(response.error?).to be true
expect(response.errors).to include("Scopes can only contain available scopes")
end
end
end
end
context 'when access provisioning fails' do
before do
allow(resource).to receive(:add_user).and_return(nil)
end
context "when access provisioning fails" do
let_it_be(:bot_user) { create(:user, :project_bot) }
let(:unpersisted_member) { build(:project_member, source: resource, user: bot_user) }
it 'returns error' do
response = subject
before do
allow_next_instance_of(ResourceAccessTokens::CreateService) do |service|
allow(service).to receive(:create_user).and_return(bot_user)
allow(service).to receive(:create_membership).and_return(unpersisted_member)
end
end
expect(response.error?).to be true
it_behaves_like 'token creation fails'
it 'returns the provisioning error message' do
response = subject
expect(response.error?).to be true
expect(response.errors).to include("Could not provision maintainer access to project access token")
end
end
end
end
@ -180,7 +191,16 @@ RSpec.describe ResourceAccessTokens::CreateService do
let_it_be(:resource_type) { 'project' }
let_it_be(:resource) { project }
it_behaves_like 'fails when user does not have the permission to create a Resource Bot'
context 'when user does not have permission to create a resource bot' do
it_behaves_like 'token creation fails'
it 'returns the permission error message' do
response = subject
expect(response.error?).to be true
expect(response.errors).to include("User does not have permission to create #{resource_type} Access Token")
end
end
context 'user with valid permission' do
before_all do

View File

@ -42,17 +42,17 @@ RSpec.describe Terraform::RemoteStateHandler do
describe '#handle_with_lock' do
it 'allows to modify a state using database locking' do
state = subject.handle_with_lock do |state|
record = nil
subject.handle_with_lock do |state|
record = state
state.name = 'updated-name'
end
expect(state.name).to eq 'updated-name'
expect(record.reload.name).to eq 'updated-name'
end
it 'returns the state object itself' do
state = subject.handle_with_lock
expect(state.name).to eq 'my-state'
it 'returns nil' do
expect(subject.handle_with_lock).to be_nil
end
end
@ -70,11 +70,13 @@ RSpec.describe Terraform::RemoteStateHandler do
it 'handles a locked state using exclusive read lock' do
handler.lock!
state = handler.handle_with_lock do |state|
record = nil
handler.handle_with_lock do |state|
record = state
state.name = 'new-name'
end
expect(state.name).to eq 'new-name'
expect(record.reload.name).to eq 'new-name'
end
it 'raises exception if lock has not been acquired before' do

View File

@ -15,7 +15,7 @@ module ImportExport
export_path = [prefix, 'spec', 'fixtures', 'lib', 'gitlab', 'import_export', name].compact
export_path = File.join(*export_path)
allow_any_instance_of(Gitlab::ImportExport).to receive(:export_path) { export_path }
allow(Gitlab::ImportExport).to receive(:export_path) { export_path }
end
def setup_reader(reader)

View File

@ -0,0 +1,55 @@
# frozen_string_literal: true
# This patch allows stubbing of prepended methods
# Based on https://github.com/rspec/rspec-mocks/pull/1218
module RSpec
module Mocks
module InstanceMethodStasherForPrependedMethods
private
def method_owned_by_klass?
owner = @klass.instance_method(@method).owner
owner = owner.class unless Module === owner
owner == @klass ||
# When `extend self` is used, and not under any instance of
(owner.singleton_class == @klass && !Mocks.space.any_instance_recorder_for(owner, true)) ||
!method_defined_on_klass?(owner)
end
end
end
end
module RSpec
module Mocks
module MethodDoubleForPrependedMethods
def restore_original_method
return show_frozen_warning if object_singleton_class.frozen?
return unless @method_is_proxied
remove_method_from_definition_target
if @method_stasher.method_is_stashed?
@method_stasher.restore
restore_original_visibility
end
@method_is_proxied = false
end
def restore_original_visibility
method_owner.__send__(@original_visibility, @method_name)
end
private
def method_owner
@method_owner ||= Object.instance_method(:method).bind(object).call(@method_name).owner
end
end
end
end
RSpec::Mocks::InstanceMethodStasher.prepend(RSpec::Mocks::InstanceMethodStasherForPrependedMethods)
RSpec::Mocks::MethodDouble.prepend(RSpec::Mocks::MethodDoubleForPrependedMethods)

View File

@ -73,7 +73,23 @@ RSpec.shared_examples 'project access tokens available #create' do
end
end
it { expect(subject).to render_template(:index) }
it 'does not create the token' do
expect { subject }.not_to change { PersonalAccessToken.count }
end
it 'does not add the project bot as a member' do
expect { subject }.not_to change { Member.count }
end
it 'does not create the project bot user' do
expect { subject }.not_to change { User.count }
end
it 'shows a failure alert' do
subject
expect(response.flash[:alert]).to match("Failed to create new project access token: Failed!")
end
end
end

View File

@ -0,0 +1,35 @@
# frozen_string_literal: true
RSpec.shared_examples 'a tracked issue edit event' do |event|
before do
stub_application_setting(usage_ping_enabled: true)
end
def count_unique(date_from:, date_to:)
Gitlab::UsageDataCounters::HLLRedisCounter.unique_events(event_names: action, start_date: date_from, end_date: date_to)
end
specify do
aggregate_failures do
expect(track_action(author: user1)).to be_truthy
expect(track_action(author: user1)).to be_truthy
expect(track_action(author: user2)).to be_truthy
expect(track_action(author: user3, time: time - 3.days)).to be_truthy
expect(count_unique(date_from: time, date_to: time)).to eq(2)
expect(count_unique(date_from: time - 5.days, date_to: 1.day.since(time))).to eq(3)
end
end
it 'does not track edit actions if author is not present' do
expect(track_action(author: nil)).to be_nil
end
context 'when feature flag track_issue_activity_actions is disabled' do
it 'does not track edit actions' do
stub_feature_flags(track_issue_activity_actions: false)
expect(track_action(author: user1)).to be_nil
end
end
end

View File

@ -14,6 +14,7 @@ end
RSpec.shared_examples "builds correct paths" do |**patterns|
let(:patterns) { patterns }
let(:fixture) { File.join('spec', 'fixtures', 'rails_sample.jpg') }
before do
allow(subject).to receive(:filename).and_return('<filename>')
@ -55,4 +56,15 @@ RSpec.shared_examples "builds correct paths" do |**patterns|
let(:target) { subject.class }
end
end
describe "path traversal exploits" do
before do
allow(subject).to receive(:filename).and_return("3bc58d54542d6a5efffa9a87554faac0254f73f675b337899ea869f6d38b7371/122../../../../../../../../.ssh/authorized_keys")
end
it "throws an exception" do
expect { subject.cache!(fixture_file_upload(fixture)) }.to raise_error(Gitlab::Utils::PathTraversalAttackError)
expect { subject.store!(fixture_file_upload(fixture)) }.to raise_error(Gitlab::Utils::PathTraversalAttackError)
end
end
end

View File

@ -24,9 +24,14 @@ RSpec.describe ImportExportUploader do
include_context 'with storage', described_class::Store::REMOTE
it_behaves_like 'builds correct paths',
store_dir: %r[import_export_upload/import_file/],
upload_path: %r[import_export_upload/import_file/]
patterns = {
store_dir: %r[import_export_upload/import_file/],
upload_path: %r[import_export_upload/import_file/]
}
it_behaves_like 'builds correct paths', patterns do
let(:fixture) { File.join('spec', 'fixtures', 'group_export.tar.gz') }
end
describe '#move_to_store' do
it 'returns false' do

View File

@ -13,6 +13,18 @@ RSpec.describe Packages::Nuget::ExtractionWorker, type: :worker do
subject { described_class.new.perform(package_file_id) }
shared_examples 'handling the metadata error' do |exception_class: ::Packages::Nuget::UpdatePackageFromMetadataService::InvalidMetadataError|
it 'removes the package and the package file' do
expect(Gitlab::ErrorTracking).to receive(:log_exception).with(
instance_of(exception_class),
project_id: package.project_id
)
expect { subject }
.to change { Packages::Package.count }.by(-1)
.and change { Packages::PackageFile.count }.by(-1)
end
end
context 'with valid package file' do
it 'updates package and package file' do
expect { subject }
@ -48,46 +60,46 @@ RSpec.describe Packages::Nuget::ExtractionWorker, type: :worker do
allow_any_instance_of(Zip::File).to receive(:glob).and_return([])
end
it 'removes the package and the package file' do
expect(Gitlab::ErrorTracking).to receive(:log_exception).with(
instance_of(::Packages::Nuget::MetadataExtractionService::ExtractionError),
project_id: package.project_id
)
expect { subject }
.to change { Packages::Package.count }.by(-1)
.and change { Packages::PackageFile.count }.by(-1)
it_behaves_like 'handling the metadata error', exception_class: ::Packages::Nuget::MetadataExtractionService::ExtractionError
end
context 'with package with an invalid package name' do
invalid_names = [
'',
'My/package',
'../../../my_package',
'%2e%2e%2fmy_package'
]
invalid_names.each do |invalid_name|
before do
allow_next_instance_of(::Packages::Nuget::UpdatePackageFromMetadataService) do |service|
allow(service).to receive(:package_name).and_return(invalid_name)
end
end
it_behaves_like 'handling the metadata error'
end
end
context 'with package file with a blank package name' do
before do
allow_any_instance_of(::Packages::Nuget::UpdatePackageFromMetadataService).to receive(:package_name).and_return('')
end
context 'with package with an invalid package version' do
invalid_versions = [
'',
'555',
'1.2',
'1./2.3',
'../../../../../1.2.3',
'%2e%2e%2f1.2.3'
]
it 'removes the package and the package file' do
expect(Gitlab::ErrorTracking).to receive(:log_exception).with(
instance_of(::Packages::Nuget::UpdatePackageFromMetadataService::InvalidMetadataError),
project_id: package.project_id
)
expect { subject }
.to change { Packages::Package.count }.by(-1)
.and change { Packages::PackageFile.count }.by(-1)
end
end
invalid_versions.each do |invalid_version|
before do
allow_next_instance_of(::Packages::Nuget::UpdatePackageFromMetadataService) do |service|
allow(service).to receive(:package_version).and_return(invalid_version)
end
end
context 'with package file with a blank package version' do
before do
allow_any_instance_of(::Packages::Nuget::UpdatePackageFromMetadataService).to receive(:package_version).and_return('')
end
it 'removes the package and the package file' do
expect(Gitlab::ErrorTracking).to receive(:log_exception).with(
instance_of(::Packages::Nuget::UpdatePackageFromMetadataService::InvalidMetadataError),
project_id: package.project_id
)
expect { subject }
.to change { Packages::Package.count }.by(-1)
.and change { Packages::PackageFile.count }.by(-1)
it_behaves_like 'handling the metadata error'
end
end
end