Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2020-11-11 12:09:06 +00:00
parent 3454d4cbe3
commit 9517d0eb2c
102 changed files with 2360 additions and 1446 deletions

View File

@ -38,7 +38,7 @@ review-docs-cleanup:
script:
- ./scripts/trigger-build docs cleanup
docs lint:
docs-lint markdown:
extends:
- .default-retry
- .docs:rules:docs-lint
@ -47,6 +47,15 @@ docs lint:
needs: []
script:
- scripts/lint-doc.sh
docs-lint links:
extends:
- .default-retry
- .docs:rules:docs-lint
image: "registry.gitlab.com/gitlab-org/gitlab-docs/lint:ruby-2.7.2-alpine-3.12-vale-2.4.3-markdownlint-0.24.0"
stage: test
needs: []
script:
# Prepare docs for build
# The path must be 'ee/' because we have hardcoded links relying on it
# https://gitlab.com/gitlab-org/gitlab-docs/-/blob/887850752fc0e72856da6632db132f005ba77f16/content/index.erb#L44-63

View File

@ -1 +1 @@
0926b9f8ff2215874eef0e22f750a74da5fe4321
194a9f58926793ade53152de90b474d66804e21e

View File

@ -1,29 +1,35 @@
<script>
import { GlTooltipDirective } from '@gitlab/ui';
import GlModal from '~/vue_shared/components/gl_modal.vue';
import { GlTooltipDirective, GlModal } from '@gitlab/ui';
import { s__, sprintf } from '~/locale';
import eventHub from '../event_hub';
export default {
id: 'delete-environment-modal',
name: 'DeleteEnvironmentModal',
components: {
GlModal,
},
directives: {
GlTooltip: GlTooltipDirective,
},
props: {
environment: {
type: Object,
required: true,
},
},
computed: {
primaryProps() {
return {
text: s__('Environments|Delete environment'),
attributes: [{ variant: 'danger' }],
};
},
cancelProps() {
return {
text: s__('Cancel'),
};
},
confirmDeleteMessage() {
return sprintf(
s__(
@ -35,8 +41,12 @@ export default {
false,
);
},
modalTitle() {
return sprintf(s__(`Environments|Delete '%{environmentName}'?`), {
environmentName: this.environment.name,
});
},
},
methods: {
onSubmit() {
eventHub.$emit('deleteEnvironment', this.environment);
@ -47,20 +57,12 @@ export default {
<template>
<gl-modal
:id="$options.id"
:footer-primary-button-text="s__('Environments|Delete environment')"
footer-primary-button-variant="danger"
@submit="onSubmit"
:modal-id="$options.id"
:action-primary="primaryProps"
:action-cancel="cancelProps"
:title="modalTitle"
@primary="onSubmit"
>
<template #header>
<h4 class="modal-title d-flex mw-100">
{{ __('Delete') }}
<span v-gl-tooltip :title="environment.name" class="text-truncate mx-1 flex-fill">
{{ environment.name }}?
</span>
</h4>
</template>
<p>{{ confirmDeleteMessage }}</p>
</gl-modal>
</template>

View File

@ -4,7 +4,7 @@
* Used in the environments table.
*/
import { GlTooltipDirective, GlButton } from '@gitlab/ui';
import { GlTooltipDirective, GlButton, GlModalDirective } from '@gitlab/ui';
import { s__ } from '~/locale';
import eventHub from '../event_hub';
@ -14,6 +14,7 @@ export default {
},
directives: {
GlTooltip: GlTooltipDirective,
GlModalDirective,
},
props: {
environment: {
@ -54,6 +55,7 @@ export default {
<template>
<gl-button
v-gl-tooltip="{ id: $options.deleteEnvironmentTooltipId }"
v-gl-modal-directive="'delete-environment-modal'"
:loading="isLoading"
:title="title"
:aria-label="title"
@ -61,8 +63,6 @@ export default {
variant="danger"
category="primary"
icon="remove"
data-toggle="modal"
data-target="#delete-environment-modal"
@click="onClick"
/>
</template>

View File

@ -3,7 +3,7 @@ import { escape } from 'lodash';
import { __, sprintf } from './locale';
import axios from './lib/utils/axios_utils';
import { deprecatedCreateFlash as flash } from './flash';
import { parseBoolean } from './lib/utils/common_utils';
import { parseBoolean, spriteIcon } from './lib/utils/common_utils';
class ImporterStatus {
constructor({ jobsUrl, importUrl, ciCdOnly }) {
@ -108,7 +108,7 @@ class ImporterStatus {
switch (job.import_status) {
case 'finished':
jobItem.removeClass('table-active').addClass('table-success');
statusField.html(`<span><i class="fa fa-check"></i> ${__('Done')}</span>`);
statusField.html(`<span>${spriteIcon('check', 's16')} ${__('Done')}</span>`);
break;
case 'scheduled':
statusField.html(`${spinner} ${__('Scheduled')}`);

View File

@ -0,0 +1,53 @@
const ComponentPerformancePlugin = {
install(Vue, options) {
Vue.mixin({
beforeCreate() {
/** Make sure the component you want to measure has `name` option defined
* and it matches the one you pass as the plugin option. Example:
*
* my_component.vue:
*
* ```
* export default {
* name: 'MyComponent'
* ...
* }
* ```
*
* index.js (where you initialize your Vue app containing <my-component>):
*
* ```
* Vue.use(PerformancePlugin, {
* components: [
* 'MyComponent',
* ]
* });
* ```
*/
const componentName = this.$options.name;
if (options?.components?.indexOf(componentName) !== -1) {
const tagName = `<${componentName}>`;
if (!performance.getEntriesByName(`${tagName}-start`).length) {
performance.mark(`${tagName}-start`);
}
}
},
mounted() {
const componentName = this.$options.name;
if (options?.components?.indexOf(componentName) !== -1) {
this.$nextTick(() => {
window.requestAnimationFrame(() => {
const tagName = `<${componentName}>`;
if (!performance.getEntriesByName(`${tagName}-end`).length) {
performance.mark(`${tagName}-end`);
performance.measure(`${tagName}`, `${tagName}-start`);
}
});
});
}
},
});
},
};
export default ComponentPerformancePlugin;

View File

@ -179,7 +179,7 @@ export default {
<span class="input-group-prepend">
<button
ref="toggleEmojiMenuButton"
v-gl-tooltip.bottom
v-gl-tooltip.bottom.hover
:title="s__('SetStatusModal|Add status emoji')"
:aria-label="s__('SetStatusModal|Add status emoji')"
name="button"

View File

@ -92,10 +92,6 @@
content: '\f0d7';
}
.fa-check::before {
content: '\f00c';
}
.fa-warning::before,
.fa-exclamation-triangle::before {
content: '\f071';

View File

@ -12,6 +12,8 @@ module Packages
end
def execute
return Packages::Package.none unless project
packages
end

View File

@ -118,6 +118,12 @@ module Types
field :severity, Types::IssuableSeverityEnum, null: true,
description: 'Severity level of the incident'
field :moved, GraphQL::BOOLEAN_TYPE, method: :moved?, null: true,
description: 'Indicates if issue got moved from other project'
field :moved_to, Types::IssueType, null: true,
description: 'Updated Issue after it got moved to another project'
def user_notes_count
BatchLoader::GraphQL.for(object.id).batch(key: :issue_user_notes_count) do |ids, loader, args|
counts = Note.count_for_collection(ids, 'Issue').index_by(&:noteable_id)
@ -150,6 +156,10 @@ module Types
Gitlab::Graphql::Loaders::BatchModelLoader.new(Milestone, object.milestone_id).find
end
def moved_to
Gitlab::Graphql::Loaders::BatchModelLoader.new(Issue, object.moved_to_id).find
end
def discussion_locked
!!object.discussion_locked
end

View File

@ -311,6 +311,8 @@ class MergeRequest < ApplicationRecord
includes(:metrics)
end
scope :with_jira_issue_keys, -> { where('title ~ :regex OR merge_requests.description ~ :regex', regex: Gitlab::Regex.jira_issue_key_regex.source) }
after_save :keep_around_commit, unless: :importing?
alias_attribute :project, :target_project

View File

@ -602,7 +602,7 @@ class Project < ApplicationRecord
# Returns a collection of projects that is either public or visible to the
# logged in user.
def self.public_or_visible_to_user(user = nil, min_access_level = nil)
min_access_level = nil if user&.admin?
min_access_level = nil if user&.can_read_all_resources?
return public_to_user unless user
@ -628,7 +628,7 @@ class Project < ApplicationRecord
def self.with_feature_available_for_user(feature, user)
visible = [ProjectFeature::ENABLED, ProjectFeature::PUBLIC]
if user&.admin?
if user&.can_read_all_resources?
with_feature_enabled(feature)
elsif user
min_access_level = ProjectFeature.required_minimum_access_level(feature)

View File

@ -72,6 +72,10 @@ module PolicyActor
def try_obtain_ldap_lease
nil
end
def can_read_all_resources?
false
end
end
PolicyActor.prepend_if_ee('EE::PolicyActor')

View File

@ -3,4 +3,5 @@
class MoveToProjectEntity < Grape::Entity
expose :id
expose :name_with_namespace
expose :full_path
end

View File

@ -3,6 +3,8 @@
module JiraConnectSubscriptions
class CreateService < ::JiraConnectSubscriptions::BaseService
include Gitlab::Utils::StrongMemoize
MERGE_REQUEST_SYNC_BATCH_SIZE = 20
MERGE_REQUEST_SYNC_BATCH_delay = 1.minute.freeze
def execute
unless namespace && can?(current_user, :create_jira_connect_subscription, namespace)
@ -18,6 +20,8 @@ module JiraConnectSubscriptions
subscription = JiraConnectSubscription.new(installation: jira_connect_installation, namespace: namespace)
if subscription.save
schedule_sync_project_jobs
success
else
error(subscription.errors.full_messages.join(', '), 422)
@ -29,5 +33,18 @@ module JiraConnectSubscriptions
Namespace.find_by_full_path(params[:namespace_path])
end
end
def schedule_sync_project_jobs
return unless Feature.enabled?(:jira_connect_full_namespace_sync)
namespace.all_projects.each_batch(of: MERGE_REQUEST_SYNC_BATCH_SIZE) do |projects, index|
JiraConnect::SyncProjectWorker.bulk_perform_in_with_contexts(
index * MERGE_REQUEST_SYNC_BATCH_delay,
projects,
arguments_proc: -> (project) { [project.id, Atlassian::JiraConnect::Client.generate_update_sequence_id] },
context_proc: -> (project) { { project: project } }
)
end
end
end
end

View File

@ -1,5 +1,4 @@
- return unless can?(current_user, :remove_project, project)
- confirm_phrase = s_('DeleteProject|Delete %{name}') % { name: project.full_name }
.sub-section
%h4.danger-title= _('Delete project')
@ -7,4 +6,4 @@
%strong= _('Deleting the project will delete its repository and all related resources including issues, merge requests etc.')
%p
%strong= _('Deleted projects cannot be restored!')
#js-project-delete-button{ data: { form_path: project_path(project), confirm_phrase: confirm_phrase } }
#js-project-delete-button{ data: { form_path: project_path(project), confirm_phrase: project.path } }

View File

@ -827,6 +827,14 @@
:weight: 1
:idempotent:
:tags: []
- :name: jira_connect:jira_connect_sync_project
:feature_category: :integrations
:has_external_dependencies: true
:urgency: :low
:resource_boundary: :unknown
:weight: 1
:idempotent: true
:tags: []
- :name: jira_importer:jira_import_advance_stage
:feature_category: :importers
:has_external_dependencies:

View File

@ -0,0 +1,30 @@
# frozen_string_literal: true
module JiraConnect
class SyncProjectWorker
include ApplicationWorker
queue_namespace :jira_connect
feature_category :integrations
idempotent!
worker_has_external_dependencies!
MERGE_REQUEST_LIMIT = 400
def perform(project_id, update_sequence_id)
project = Project.find_by_id(project_id)
return if project.nil?
JiraConnect::SyncService.new(project).execute(merge_requests: merge_requests_to_sync(project), update_sequence_id: update_sequence_id)
end
private
# rubocop: disable CodeReuse/ActiveRecord
def merge_requests_to_sync(project)
project.merge_requests.with_jira_issue_keys.preload(:author).limit(MERGE_REQUEST_LIMIT).order(id: :desc)
end
# rubocop: enable CodeReuse/ActiveRecord
end
end

View File

@ -0,0 +1,5 @@
---
title: NPM project level API
merge_request: 46867
author:
type: added

View File

@ -0,0 +1,5 @@
---
title: Expose moved and movedTo attributes in Issues query
merge_request: 46447
author:
type: added

View File

@ -0,0 +1,5 @@
---
title: Make delete repo prompts consistent
merge_request: 47117
author:
type: fixed

View File

@ -0,0 +1,5 @@
---
title: Migrate services specs to consider admin mode
merge_request: 45988
author: Diego Louzán
type: other

View File

@ -0,0 +1,5 @@
---
title: Fix status emoji tooltip trigger
merge_request: 47378
author:
type: fixed

View File

@ -0,0 +1,5 @@
---
title: Jira Connect automatically synchronizes up to 400 existing merge requests per project when a namespace is connected.
merge_request: 43880
author:
type: added

View File

@ -0,0 +1,5 @@
---
title: Replace fa-check icon in importer status
merge_request: 47373
author:
type: changed

View File

@ -0,0 +1,7 @@
---
name: jira_connect_full_namespace_sync
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/43880
rollout_issue_url:
type: development
group: group::ecosystem
default_enabled: false

View File

@ -0,0 +1,44 @@
# frozen_string_literal: true
class AddMergeRequestJiraReferenceIndexes < ActiveRecord::Migration[6.0]
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
DESCRIPTION_INDEX_NAME = 'index_merge_requests_on_target_project_id_iid_jira_description'
TITLE_INDEX_NAME = 'index_merge_requests_on_target_project_id_and_iid_jira_title'
JIRA_KEY_REGEX = '[A-Z][A-Z_0-9]+-\d+'
disable_ddl_transaction!
def up
add_concurrent_index(
:merge_requests,
[:target_project_id, :iid],
name: TITLE_INDEX_NAME,
using: :btree,
where: "(merge_requests.title)::text ~ '#{JIRA_KEY_REGEX}'::text"
)
add_concurrent_index(
:merge_requests,
[:target_project_id, :iid],
name: DESCRIPTION_INDEX_NAME,
using: :btree,
where: "(merge_requests.description)::text ~ '#{JIRA_KEY_REGEX}'::text"
)
end
def down
remove_concurrent_index_by_name(
:merge_requests,
TITLE_INDEX_NAME
)
remove_concurrent_index_by_name(
:merge_requests,
DESCRIPTION_INDEX_NAME
)
end
end

View File

@ -0,0 +1 @@
a2dc0d31af6834adf6634f6051d7d451fc48d31492d96efe57547c3e9d61a64d

View File

@ -21150,8 +21150,12 @@ CREATE UNIQUE INDEX index_merge_requests_on_target_project_id_and_iid ON merge_r
CREATE INDEX index_merge_requests_on_target_project_id_and_iid_and_state_id ON merge_requests USING btree (target_project_id, iid, state_id);
CREATE INDEX index_merge_requests_on_target_project_id_and_iid_jira_title ON merge_requests USING btree (target_project_id, iid) WHERE ((title)::text ~ '[A-Z][A-Z_0-9]+-\d+'::text);
CREATE INDEX index_merge_requests_on_target_project_id_and_target_branch ON merge_requests USING btree (target_project_id, target_branch) WHERE ((state_id = 1) AND (merge_when_pipeline_succeeds = true));
CREATE INDEX index_merge_requests_on_target_project_id_iid_jira_description ON merge_requests USING btree (target_project_id, iid) WHERE (description ~ '[A-Z][A-Z_0-9]+-\d+'::text);
CREATE INDEX index_merge_requests_on_title ON merge_requests USING btree (title);
CREATE INDEX index_merge_requests_on_title_trigram ON merge_requests USING gin (title gin_trgm_ops);

View File

@ -260,9 +260,9 @@ The following documentation relates to the DevOps **Verify** stage:
### Package
GitLab Packages allows organizations to utilize GitLab as a private repository
GitLab Packages allows organizations to use GitLab as a private repository
for a variety of common package managers. Users are able to build and publish
packages, which can be easily consumed as a dependency in downstream projects.
packages, which can be consumed as a dependency in downstream projects.
The following documentation relates to the DevOps **Package** stage:

View File

@ -7645,6 +7645,16 @@ type EpicIssue implements CurrentUserTodos & Noteable {
"""
milestone: Milestone
"""
Indicates if issue got moved from other project
"""
moved: Boolean
"""
Updated Issue after it got moved to another project
"""
movedTo: Issue
"""
All notes on this noteable
"""
@ -10169,6 +10179,16 @@ type Issue implements CurrentUserTodos & Noteable {
"""
milestone: Milestone
"""
Indicates if issue got moved from other project
"""
moved: Boolean
"""
Updated Issue after it got moved to another project
"""
movedTo: Issue
"""
All notes on this noteable
"""

View File

@ -21168,6 +21168,34 @@
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "moved",
"description": "Indicates if issue got moved from other project",
"args": [
],
"type": {
"kind": "SCALAR",
"name": "Boolean",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "movedTo",
"description": "Updated Issue after it got moved to another project",
"args": [
],
"type": {
"kind": "OBJECT",
"name": "Issue",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "notes",
"description": "All notes on this noteable",
@ -27821,6 +27849,34 @@
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "moved",
"description": "Indicates if issue got moved from other project",
"args": [
],
"type": {
"kind": "SCALAR",
"name": "Boolean",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "movedTo",
"description": "Updated Issue after it got moved to another project",
"args": [
],
"type": {
"kind": "OBJECT",
"name": "Issue",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "notes",
"description": "All notes on this noteable",

View File

@ -1268,6 +1268,8 @@ Relationship between an epic and an issue.
| `iteration` | Iteration | Iteration of the issue |
| `labels` | LabelConnection | Labels of the issue |
| `milestone` | Milestone | Milestone of the issue |
| `moved` | Boolean | Indicates if issue got moved from other project |
| `movedTo` | Issue | Updated Issue after it got moved to another project |
| `notes` | NoteConnection! | All notes on this noteable |
| `participants` | UserConnection | List of participants in the issue |
| `reference` | String! | Internal reference of the issue. Returned in shortened format by default |
@ -1534,6 +1536,8 @@ Represents a recorded measurement (object count) for the Admins.
| `iteration` | Iteration | Iteration of the issue |
| `labels` | LabelConnection | Labels of the issue |
| `milestone` | Milestone | Milestone of the issue |
| `moved` | Boolean | Indicates if issue got moved from other project |
| `movedTo` | Issue | Updated Issue after it got moved to another project |
| `notes` | NoteConnection! | All notes on this noteable |
| `participants` | UserConnection | List of participants in the issue |
| `reference` | String! | Internal reference of the issue. Returned in shortened format by default |

View File

@ -289,8 +289,7 @@ You can find the schemas for these scanners here:
### Version
This field specifies the version of the report schema you are using. Please reference individual scanner
pages for the specific versions to use.
This field specifies the version of the [Security Report Schemas](https://gitlab.com/gitlab-org/security-products/security-report-schemas) you are using. Please refer to the [releases](https://gitlab.com/gitlab-org/security-products/security-report-schemas/-/releases) of the schemas for the specific versions to use.
### Vulnerabilities

Binary file not shown.

After

Width:  |  Height:  |  Size: 123 KiB

View File

@ -32,7 +32,7 @@ Dependencies are displayed with the following information:
| --------- | ----------- |
| Component | The dependency's name and version |
| Packager | The packager used to install the dependency |
| Location | A link to the packager-specific lock file in your project that declared the dependency |
| Location | A link to the packager-specific lock file in your project that declared the dependency. It also shows the [dependency path](#dependency-paths) to a top-level dependency, if any, and if supported. |
| License | Links to dependency's software licenses |
Dependencies shown are initially sorted by the severity of their known vulnerabilities, if any. They
@ -44,6 +44,18 @@ If a dependency has known vulnerabilities, you can view them by clicking the arr
dependency's name or the badge that indicates how many known vulnerabilities exist. For each
vulnerability, its severity and description then appears below it.
### Dependency Paths
The dependency list shows the path between a dependency and a top-level dependency it's connected
to, if any. There are many possible paths connecting a transient dependency to top-level
dependencies, but the UI only shows one of the shortest paths.
![Dependency Path](img/yarn_dependency_path_v13_6.png)
Dependency Paths are supported for the following package managers:
- [NuGet](https://www.nuget.org/)
## Licenses
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/10536) in GitLab Ultimate 12.3.

View File

@ -62,7 +62,6 @@ The following languages and package managers are supported.
| .NET | [Nuget](https://www.nuget.org/) | The .NET Framework is supported via the [mono project](https://www.mono-project.com/). There are, however, some limitations. The scanner doesn't support Windows-specific dependencies and doesn't report dependencies of your project's listed dependencies. Also, the scanner always marks detected licenses for all dependencies as `unknown`. | [License Finder](https://github.com/pivotal/LicenseFinder) |
| Python | [pip](https://pip.pypa.io/en/stable/) | Python is supported through [requirements.txt](https://pip.pypa.io/en/stable/user_guide/#requirements-files) and [Pipfile.lock](https://github.com/pypa/pipfile#pipfilelock). | [License Finder](https://github.com/pivotal/LicenseFinder) |
| Ruby | [gem](https://rubygems.org/) | | [License Finder](https://github.com/pivotal/LicenseFinder)|
| Objective-C, Swift | [Carthage](https://github.com/Carthage/Carthage) | | [License Finder](https://github.com/pivotal/LicenseFinder) |
NOTE: **Note:**
Java 8 and Gradle 1.x projects are not supported.
@ -78,6 +77,7 @@ which means that the reported licenses might be incomplete or inaccurate.
| JavaScript | [Yarn](https://yarnpkg.com/)|[License Finder](https://github.com/pivotal/LicenseFinder)|
| Go | go get, gvt, glide, dep, trash, govendor |[License Finder](https://github.com/pivotal/LicenseFinder)|
| Erlang | [Rebar](https://www.rebar3.org/) |[License Finder](https://github.com/pivotal/LicenseFinder)|
| Objective-C, Swift | [Carthage](https://github.com/Carthage/Carthage) | | [License Finder](https://github.com/pivotal/LicenseFinder) |
| Objective-C, Swift | [CocoaPods](https://cocoapods.org/) v0.39 and below |[License Finder](https://github.com/pivotal/LicenseFinder)|
| Elixir | [Mix](https://elixir-lang.org/getting-started/mix-otp/introduction-to-mix.html) |[License Finder](https://github.com/pivotal/LicenseFinder)|
| C++/C | [Conan](https://conan.io/) |[License Finder](https://github.com/pivotal/LicenseFinder)|
@ -144,7 +144,7 @@ License Compliance can be configured using environment variables.
| `ASDF_PYTHON_VERSION` | no | Version of Python to use for the scan. |
| `ASDF_RUBY_VERSION` | no | Version of Ruby to use for the scan. |
| `GRADLE_CLI_OPTS` | no | Additional arguments for the gradle executable. If not supplied, defaults to `--exclude-task=test`. |
| `LICENSE_FINDER_CLI_OPTS` | no | Additional arguments for the `license_finder` executable. For example, if your project has both Golang and Ruby code stored in different directories and you want to only scan the Ruby code, you can update your `.gitlab-ci-yml` template to specify which project directories to scan, like `LICENSE_FINDER_CLI_OPTS: '--debug --aggregate-paths=. ruby'`. |
| `LICENSE_FINDER_CLI_OPTS` | no | Additional arguments for the `license_finder` executable. For example, if you have multiple projects in nested directories, you can update your `.gitlab-ci-yml` template to specify a recursive scan, like `LICENSE_FINDER_CLI_OPTS: '--recursive'`. |
| `LM_JAVA_VERSION` | no | Version of Java. If set to `11`, Maven and Gradle use Java 11 instead of Java 8. |
| `LM_PYTHON_VERSION` | no | Version of Python. If set to `3`, dependencies are installed using Python 3 instead of Python 2.7. |
| `MAVEN_CLI_OPTS` | no | Additional arguments for the mvn executable. If not supplied, defaults to `-DskipTests`. |

View File

@ -79,7 +79,18 @@ To create a project:
the [naming convention](#package-naming-convention) and is scoped to the
project or group where the registry exists.
A `package.json` file is created.
A `package.json` file is created.
## Use the GitLab endpoint for NPM packages
To use the GitLab endpoint for NPM packages, choose an option:
- **Project-level**: Use when you have few NPM packages and they are not in
the same GitLab group.
- **Instance-level**: Use when you have many NPM packages in different
GitLab groups or in their own namespace. Be sure to comply with the [package naming convention](#package-naming-convention).
Some features such as [publishing](#publish-an-npm-package) a package is only available on the project-level endpoint.
## Authenticate to the Package Registry
@ -94,20 +105,19 @@ To authenticate to the Package Registry, you must use one of the following:
### Authenticate with a personal access token or deploy token
To authenticate with a [personal access token](../../profile/personal_access_tokens.md) or [deploy token](../../project/deploy_tokens/index.md),
set your NPM configuration:
To authenticate with the Package Registry, you will need a [personal access token](../../profile/personal_access_tokens.md) or [deploy token](../../project/deploy_tokens/index.md).
#### Project-level NPM endpoint
To use the [project-level](#use-the-gitlab-endpoint-for-npm-packages) NPM endpoint, set your NPM configuration:
```shell
# Set URL for your scoped packages
# For example, a package named `@foo/bar` uses this URL for download
npm config set @foo:registry https://gitlab.example.com/api/v4/packages/npm/
# Set URL for your scoped packages.
# For example package with name `@foo/bar` will use this URL for download
npm config set @foo:registry https://gitlab.example.com/api/v4/projects/<your_project_id>/packages/npm/
# Add the token for the scoped packages URL
# Use this to download `@foo/` packages from private projects
npm config set '//gitlab.example.com/api/v4/packages/npm/:_authToken' "<your_token>"
# Add token for to publish to the package registry
# Replace <your_project_id> with the project you want to publish your package to
# Add the token for the scoped packages URL. Replace <your_project_id>
# with the project where your package is located.
npm config set '//gitlab.example.com/api/v4/projects/<your_project_id>/packages/npm/:_authToken' "<your_token>"
```
@ -120,6 +130,28 @@ You should now be able to publish and install NPM packages in your project.
If you encounter an error with [Yarn](https://classic.yarnpkg.com/en/), view
[troubleshooting steps](#troubleshooting).
#### Instance-level NPM endpoint
To use the [instance-level](#use-the-gitlab-endpoint-for-npm-packages) NPM endpoint, set your NPM configuration:
```shell
# Set URL for your scoped packages.
# For example package with name `@foo/bar` will use this URL for download
npm config set @foo:registry https://gitlab.example.com/api/v4/packages/npm/
# Add the token for the scoped packages URL. This will allow you to download
# `@foo/` packages from private projects.
npm config set '//gitlab.example.com/api/v4/packages/npm/:_authToken' "<your_token>"
```
- `<your_token>` is your personal access token or deploy token.
- Replace `gitlab.example.com` with your domain name.
You should now be able to publish and install NPM packages in your project.
If you encounter an error with [Yarn](https://classic.yarnpkg.com/en/), view
[troubleshooting steps](#troubleshooting).
### Authenticate with a CI job token
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/9104) in GitLab Premium 12.5.
@ -128,12 +160,22 @@ If you encounter an error with [Yarn](https://classic.yarnpkg.com/en/), view
If you're using NPM with GitLab CI/CD, a CI job token can be used instead of a personal access token or deploy token.
The token inherits the permissions of the user that generates the pipeline.
Add a corresponding section to your `.npmrc` file:
#### Project-level NPM endpoint
To use the [project-level](#use-the-gitlab-endpoint-for-npm-packages) NPM endpoint, add a corresponding section to your `.npmrc` file:
```ini
@foo:registry=https://gitlab.example.com/api/v4/projects/${CI_PROJECT_ID}/packages/npm/
//gitlab.example.com/api/v4/projects/${CI_PROJECT_ID}/packages/npm/:_authToken=${CI_JOB_TOKEN}
```
#### Instance-level NPM endpoint
To use the [instance-level](#use-the-gitlab-endpoint-for-npm-packages) NPM endpoint, add a corresponding section to your `.npmrc` file:
```ini
@foo:registry=https://gitlab.example.com/api/v4/packages/npm/
//gitlab.example.com/api/v4/packages/npm/:_authToken=${CI_JOB_TOKEN}
//gitlab.example.com/api/v4/projects/${CI_PROJECT_ID}/packages/npm/:_authToken=${CI_JOB_TOKEN}
```
#### Use variables to avoid hard-coding auth token values
@ -184,37 +226,22 @@ In GitLab, this regex validates all package names from all package managers:
This regex allows almost all of the characters that NPM allows, with a few exceptions (for example, `~` is not allowed).
The regex also allows for capital letters, while NPM does not. Capital letters are needed because the scope must be
identical to the root namespace of the project.
identical to the root namespace of the project.
CAUTION: **Caution:**
When you update the path of a user or group, or transfer a subgroup or project,
you must remove any NPM packages first. You cannot update the root namespace
you must remove any NPM packages first. You cannot update the root namespace
of a project with NPM packages. Make sure you update your `.npmrc` files to follow
the naming convention and run `npm publish` if necessary.
## Publish an NPM package
Before you can publish a package, you must specify the registry
for NPM. To do this, add the following section to the bottom of `package.json`:
Prerequisites:
```json
"publishConfig": {
"@foo:registry":"https://gitlab.example.com/api/v4/projects/<your_project_id>/packages/npm/"
}
```
- [Authenticate](#authenticate-to-the-package-registry) to the Package Registry.
- Set a [project-level NPM endpoint](#use-the-gitlab-endpoint-for-npm-packages).
- `<your_project_id>` is your project ID, found on the project's home page.
- `@foo` is your scope.
- Replace `gitlab.example.com` with your domain name.
DANGER: **Warning:**
The `publishConfig` entry in the `package.json` file is not respected, because of a
[bug in NPM](https://github.com/npm/cli/issues/1994) version `7.x` and later. You must
use an earlier version of NPM, or temporarily set your `.npmrc` scope to
`@foo:registry=https://gitlab.example.com/api/v4/projects/<project_id>/packages/npm`.
After you have set up [authentication](#authenticate-to-the-package-registry),
you can upload an NPM package to your project:
To upload an NPM package to your project, run this command:
```shell
npm publish
@ -227,6 +254,11 @@ a given scope, you get a `403 Forbidden!` error.
## Publish an NPM package by using CI/CD
Prerequisites:
- [Authenticate](#authenticate-to-the-package-registry) to the Package Registry.
- Set a [project-level NPM endpoint](#use-the-gitlab-endpoint-for-npm-packages).
To work with NPM commands within [GitLab CI/CD](../../../ci/README.md), you can use
`CI_JOB_TOKEN` in place of the personal access token or deploy token in your commands.
@ -267,7 +299,7 @@ in a JavaScript project.
Replace `@foo` with your scope.
1. Ensure [authentication](#authenticate-to-the-package-registry) is configured.
1. In your project, to install a package, run:
```shell
@ -390,9 +422,6 @@ should look like:
"name": "@foo/my-package",
"version": "1.0.0",
"description": "Example package for GitLab NPM registry",
"publishConfig": {
"@foo:registry":"https://gitlab.example.com/api/v4/projects/<your_project_id>/packages/npm/"
}
}
```

View File

@ -218,7 +218,8 @@ module API
mount ::API::DebianGroupPackages
mount ::API::DebianProjectPackages
mount ::API::MavenPackages
mount ::API::NpmPackages
mount ::API::NpmProjectPackages
mount ::API::NpmInstancePackages
mount ::API::GenericPackages
mount ::API::GoProxy
mount ::API::Pages

View File

@ -0,0 +1,134 @@
# frozen_string_literal: true
# NPM Package Manager Client API
#
# These API endpoints are not consumed directly by users, so there is no documentation for the
# individual endpoints. They are called by the NPM package manager client when users run commands
# like `npm install` or `npm publish`. The usage of the GitLab NPM registry is documented here:
# https://docs.gitlab.com/ee/user/packages/npm_registry/
#
# Technical debt: https://gitlab.com/gitlab-org/gitlab/issues/35798
#
# Caution: This Concern has to be included at the end of the API class
# The last route of this Concern has a globbing wildcard that will match all urls.
# As such, routes declared after the last route of this Concern will not match any url.
module API
module Concerns
module Packages
module NpmEndpoints
extend ActiveSupport::Concern
included do
helpers ::API::Helpers::Packages::DependencyProxyHelpers
before do
require_packages_enabled!
authenticate_non_get!
end
params do
requires :package_name, type: String, desc: 'Package name'
end
namespace '-/package/*package_name' do
desc 'Get all tags for a given an NPM package' do
detail 'This feature was introduced in GitLab 12.7'
success ::API::Entities::NpmPackageTag
end
get 'dist-tags', format: false, requirements: ::API::Helpers::Packages::Npm::NPM_ENDPOINT_REQUIREMENTS do
package_name = params[:package_name]
bad_request!('Package Name') if package_name.blank?
authorize_read_package!(project)
packages = ::Packages::Npm::PackageFinder.new(project, package_name)
.execute
not_found! if packages.empty?
present ::Packages::Npm::PackagePresenter.new(package_name, packages),
with: ::API::Entities::NpmPackageTag
end
params do
requires :tag, type: String, desc: "Package dist-tag"
end
namespace 'dist-tags/:tag', requirements: ::API::Helpers::Packages::Npm::NPM_ENDPOINT_REQUIREMENTS do
desc 'Create or Update the given tag for the given NPM package and version' do
detail 'This feature was introduced in GitLab 12.7'
end
put format: false do
package_name = params[:package_name]
version = env['api.request.body']
tag = params[:tag]
bad_request!('Package Name') if package_name.blank?
bad_request!('Version') if version.blank?
bad_request!('Tag') if tag.blank?
authorize_create_package!(project)
package = ::Packages::Npm::PackageFinder
.new(project, package_name)
.find_by_version(version)
not_found!('Package') unless package
::Packages::Npm::CreateTagService.new(package, tag).execute
no_content!
end
desc 'Deletes the given tag' do
detail 'This feature was introduced in GitLab 12.7'
end
delete format: false do
package_name = params[:package_name]
tag = params[:tag]
bad_request!('Package Name') if package_name.blank?
bad_request!('Tag') if tag.blank?
authorize_destroy_package!(project)
package_tag = ::Packages::TagsFinder
.new(project, package_name, package_type: :npm)
.find_by_name(tag)
not_found!('Package tag') unless package_tag
::Packages::RemoveTagService.new(package_tag).execute
no_content!
end
end
end
desc 'NPM registry metadata endpoint' do
detail 'This feature was introduced in GitLab 11.8'
end
params do
requires :package_name, type: String, desc: 'Package name'
end
route_setting :authentication, job_token_allowed: true, deploy_token_allowed: true
get '*package_name', format: false, requirements: ::API::Helpers::Packages::Npm::NPM_ENDPOINT_REQUIREMENTS do
package_name = params[:package_name]
packages = ::Packages::Npm::PackageFinder.new(project_or_nil, package_name)
.execute
redirect_request = project_or_nil.blank? || packages.empty?
redirect_registry_request(redirect_request, :npm, package_name: package_name) do
authorize_read_package!(project)
not_found!('Packages') if packages.empty?
present ::Packages::Npm::PackagePresenter.new(package_name, packages),
with: ::API::Entities::NpmPackage
end
end
end
end
end
end
end

View File

@ -0,0 +1,62 @@
# frozen_string_literal: true
module API
module Helpers
module Packages
module Npm
include Gitlab::Utils::StrongMemoize
include ::API::Helpers::PackagesHelpers
NPM_ENDPOINT_REQUIREMENTS = {
package_name: API::NO_SLASH_URL_PART_REGEX
}.freeze
def endpoint_scope
params[:id].present? ? :project : :instance
end
def project
strong_memoize(:project) do
case endpoint_scope
when :project
user_project
when :instance
# Simulate the same behavior as #user_project by re-using #find_project!
# but take care if the project_id is nil as #find_project! is not designed
# to handle it.
project_id = project_id_or_nil
not_found!('Project') unless project_id
find_project!(project_id)
end
end
end
def project_or_nil
# mainly used by the metadata endpoint where we need to get a project
# and return nil if not found (no errors should be raised)
strong_memoize(:project_or_nil) do
next unless project_id_or_nil
find_project(project_id_or_nil)
end
end
def project_id_or_nil
strong_memoize(:project_id_or_nil) do
case endpoint_scope
when :project
params[:id]
when :instance
::Packages::Package.npm
.with_name(params[:package_name])
.first
&.project_id
end
end
end
end
end
end
end

View File

@ -0,0 +1,16 @@
# frozen_string_literal: true
module API
class NpmInstancePackages < ::API::Base
helpers ::API::Helpers::Packages::Npm
feature_category :package_registry
rescue_from ActiveRecord::RecordInvalid do |e|
render_api_error!(e.message, 400)
end
namespace 'packages/npm' do
include ::API::Concerns::Packages::NpmEndpoints
end
end
end

View File

@ -1,175 +0,0 @@
# frozen_string_literal: true
module API
class NpmPackages < ::API::Base
helpers ::API::Helpers::PackagesHelpers
helpers ::API::Helpers::Packages::DependencyProxyHelpers
feature_category :package_registry
NPM_ENDPOINT_REQUIREMENTS = {
package_name: API::NO_SLASH_URL_PART_REGEX
}.freeze
rescue_from ActiveRecord::RecordInvalid do |e|
render_api_error!(e.message, 400)
end
before do
require_packages_enabled!
authenticate_non_get!
end
helpers do
def project_by_package_name
strong_memoize(:project_by_package_name) do
::Packages::Package.npm.with_name(params[:package_name]).first&.project
end
end
end
desc 'Get all tags for a given an NPM package' do
detail 'This feature was introduced in GitLab 12.7'
success ::API::Entities::NpmPackageTag
end
params do
requires :package_name, type: String, desc: 'Package name'
end
get 'packages/npm/-/package/*package_name/dist-tags', format: false, requirements: NPM_ENDPOINT_REQUIREMENTS do
package_name = params[:package_name]
bad_request!('Package Name') if package_name.blank?
authorize_read_package!(project_by_package_name)
packages = ::Packages::Npm::PackageFinder.new(project_by_package_name, package_name)
.execute
present ::Packages::Npm::PackagePresenter.new(package_name, packages),
with: ::API::Entities::NpmPackageTag
end
params do
requires :package_name, type: String, desc: 'Package name'
requires :tag, type: String, desc: "Package dist-tag"
end
namespace 'packages/npm/-/package/*package_name/dist-tags/:tag', requirements: NPM_ENDPOINT_REQUIREMENTS do
desc 'Create or Update the given tag for the given NPM package and version' do
detail 'This feature was introduced in GitLab 12.7'
end
put format: false do
package_name = params[:package_name]
version = env['api.request.body']
tag = params[:tag]
bad_request!('Package Name') if package_name.blank?
bad_request!('Version') if version.blank?
bad_request!('Tag') if tag.blank?
authorize_create_package!(project_by_package_name)
package = ::Packages::Npm::PackageFinder
.new(project_by_package_name, package_name)
.find_by_version(version)
not_found!('Package') unless package
::Packages::Npm::CreateTagService.new(package, tag).execute
no_content!
end
desc 'Deletes the given tag' do
detail 'This feature was introduced in GitLab 12.7'
end
delete format: false do
package_name = params[:package_name]
tag = params[:tag]
bad_request!('Package Name') if package_name.blank?
bad_request!('Tag') if tag.blank?
authorize_destroy_package!(project_by_package_name)
package_tag = ::Packages::TagsFinder
.new(project_by_package_name, package_name, package_type: :npm)
.find_by_name(tag)
not_found!('Package tag') unless package_tag
::Packages::RemoveTagService.new(package_tag).execute
no_content!
end
end
desc 'NPM registry endpoint at instance level' do
detail 'This feature was introduced in GitLab 11.8'
end
params do
requires :package_name, type: String, desc: 'Package name'
end
route_setting :authentication, job_token_allowed: true, deploy_token_allowed: true
get 'packages/npm/*package_name', format: false, requirements: NPM_ENDPOINT_REQUIREMENTS do
package_name = params[:package_name]
redirect_registry_request(project_by_package_name.blank?, :npm, package_name: package_name) do
authorize_read_package!(project_by_package_name)
packages = ::Packages::Npm::PackageFinder
.new(project_by_package_name, package_name).execute
present ::Packages::Npm::PackagePresenter.new(package_name, packages),
with: ::API::Entities::NpmPackage
end
end
params do
requires :id, type: String, desc: 'The ID of a project'
end
resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do
desc 'Download the NPM tarball' do
detail 'This feature was introduced in GitLab 11.8'
end
params do
requires :package_name, type: String, desc: 'Package name'
requires :file_name, type: String, desc: 'Package file name'
end
route_setting :authentication, job_token_allowed: true, deploy_token_allowed: true
get ':id/packages/npm/*package_name/-/*file_name', format: false do
authorize_read_package!(user_project)
package = user_project.packages.npm
.by_name_and_file_name(params[:package_name], params[:file_name])
package_file = ::Packages::PackageFileFinder
.new(package, params[:file_name]).execute!
track_package_event('pull_package', package)
present_carrierwave_file!(package_file.file)
end
desc 'Create NPM package' do
detail 'This feature was introduced in GitLab 11.8'
end
params do
requires :package_name, type: String, desc: 'Package name'
requires :versions, type: Hash, desc: 'Package version info'
end
route_setting :authentication, job_token_allowed: true, deploy_token_allowed: true
put ':id/packages/npm/:package_name', requirements: NPM_ENDPOINT_REQUIREMENTS do
authorize_create_package!(user_project)
track_package_event('push_package', :npm)
created_package = ::Packages::Npm::CreatePackageService
.new(user_project, current_user, params.merge(build: current_authenticated_job)).execute
if created_package[:status] == :error
render_api_error!(created_package[:message], created_package[:http_status])
else
created_package
end
end
end
end
end

View File

@ -0,0 +1,66 @@
# frozen_string_literal: true
module API
class NpmProjectPackages < ::API::Base
helpers ::API::Helpers::Packages::Npm
feature_category :package_registry
rescue_from ActiveRecord::RecordInvalid do |e|
render_api_error!(e.message, 400)
end
params do
requires :id, type: String, desc: 'The ID of a project'
end
namespace 'projects/:id/packages/npm' do
desc 'Download the NPM tarball' do
detail 'This feature was introduced in GitLab 11.8'
end
params do
requires :package_name, type: String, desc: 'Package name'
requires :file_name, type: String, desc: 'Package file name'
end
route_setting :authentication, job_token_allowed: true, deploy_token_allowed: true
get '*package_name/-/*file_name', format: false do
authorize_read_package!(project)
package = project.packages.npm
.by_name_and_file_name(params[:package_name], params[:file_name])
not_found!('Package') unless package
package_file = ::Packages::PackageFileFinder
.new(package, params[:file_name]).execute!
track_package_event('pull_package', package, category: 'API::NpmPackages')
present_carrierwave_file!(package_file.file)
end
desc 'Create NPM package' do
detail 'This feature was introduced in GitLab 11.8'
end
params do
requires :package_name, type: String, desc: 'Package name'
requires :versions, type: Hash, desc: 'Package version info'
end
route_setting :authentication, job_token_allowed: true, deploy_token_allowed: true
put ':package_name', requirements: ::API::Helpers::Packages::Npm::NPM_ENDPOINT_REQUIREMENTS do
authorize_create_package!(project)
track_package_event('push_package', :npm, category: 'API::NpmPackages')
created_package = ::Packages::Npm::CreatePackageService
.new(project, current_user, params.merge(build: current_authenticated_job)).execute
if created_package[:status] == :error
render_api_error!(created_package[:message], created_package[:http_status])
else
created_package
end
end
include ::API::Concerns::Packages::NpmEndpoints
end
end
end

View File

@ -1,19 +0,0 @@
# frozen_string_literal: true
module BulkImports
module Common
module Loaders
class EntitiesLoader
def initialize(*args); end
def load(context, entities)
bulk_import = context.entity.bulk_import
entities.each do |entity|
bulk_import.entities.create!(entity)
end
end
end
end
end
end

View File

@ -0,0 +1,15 @@
# frozen_string_literal: true
module BulkImports
module Common
module Loaders
class EntityLoader
def initialize(*args); end
def load(context, entity)
context.entity.bulk_import.entities.create!(entity)
end
end
end
end
end

View File

@ -9,12 +9,9 @@ module BulkImports
def extract(context)
encoded_parent_path = ERB::Util.url_encode(context.entity.source_full_path)
subgroups = []
http_client(context.entity.bulk_import.configuration)
.each_page(:get, "groups/#{encoded_parent_path}/subgroups") do |page|
subgroups << page
end
subgroups
.each_page(:get, "groups/#{encoded_parent_path}/subgroups")
.flat_map(&:itself)
end
private

View File

@ -7,8 +7,8 @@ module BulkImports
include Pipeline
extractor BulkImports::Groups::Extractors::SubgroupsExtractor
transformer BulkImports::Groups::Transformers::SubgroupsToEntitiesTransformer
loader BulkImports::Common::Loaders::EntitiesLoader
transformer BulkImports::Groups::Transformers::SubgroupToEntityTransformer
loader BulkImports::Common::Loaders::EntityLoader
end
end
end

View File

@ -0,0 +1,21 @@
# frozen_string_literal: true
module BulkImports
module Groups
module Transformers
class SubgroupToEntityTransformer
def initialize(*args); end
def transform(context, entry)
{
source_type: :group_entity,
source_full_path: entry['full_path'],
destination_name: entry['name'],
destination_namespace: context.entity.group.full_path,
parent_id: context.entity.id
}
end
end
end
end
end

View File

@ -1,23 +0,0 @@
# frozen_string_literal: true
module BulkImports
module Groups
module Transformers
class SubgroupsToEntitiesTransformer
def initialize(*args); end
def transform(context, data)
data.map do |entry|
{
source_type: :group_entity,
source_full_path: entry['full_path'],
destination_name: entry['name'],
destination_namespace: context.entity.group.full_path,
parent_id: context.entity.id
}
end
end
end
end
end
end

View File

@ -3546,6 +3546,9 @@ msgstr ""
msgid "Archived"
msgstr ""
msgid "Archived (%{movedToStart}moved%{movedToEnd})"
msgstr ""
msgid "Archived in this version"
msgstr ""
@ -5327,6 +5330,9 @@ msgstr ""
msgid "Choose labels"
msgstr ""
msgid "Choose specific groups or storage shards"
msgstr ""
msgid "Choose the top-level group for your repository imports."
msgstr ""
@ -7156,7 +7162,7 @@ msgstr ""
msgid "Container repositories"
msgstr ""
msgid "Container repositories sync capacity"
msgid "Container repositories synchronization concurrency limit"
msgstr ""
msgid "Container repository"
@ -8810,9 +8816,6 @@ msgstr ""
msgid "Delete variable"
msgstr ""
msgid "DeleteProject|Delete %{name}"
msgstr ""
msgid "DeleteProject|Failed to remove project repository. Please try again or contact administrator."
msgstr ""
@ -10423,6 +10426,9 @@ msgstr ""
msgid "Environments|Delete"
msgstr ""
msgid "Environments|Delete '%{environmentName}'?"
msgstr ""
msgid "Environments|Delete environment"
msgstr ""
@ -11754,7 +11760,7 @@ msgstr ""
msgid "File renamed with no changes."
msgstr ""
msgid "File sync capacity"
msgid "File synchronization concurrency limit"
msgstr ""
msgid "File templates"
@ -16053,6 +16059,9 @@ msgstr ""
msgid "Limit namespaces and projects that can be indexed"
msgstr ""
msgid "Limit the number of concurrent operations this secondary node can run in the background."
msgstr ""
msgid "Limited to showing %d event at most"
msgid_plural "Limited to showing %d events at most"
msgstr[0] ""
@ -17685,6 +17694,9 @@ msgstr ""
msgid "Move selection up"
msgstr ""
msgid "Move test case"
msgstr ""
msgid "Move this issue to another project."
msgstr ""
@ -22856,7 +22868,7 @@ msgstr ""
msgid "Repository storage"
msgstr ""
msgid "Repository sync capacity"
msgid "Repository synchronization concurrency limit"
msgstr ""
msgid "Repository: %{counter_repositories} / Wikis: %{counter_wikis} / Build Artifacts: %{counter_build_artifacts} / LFS: %{counter_lfs_objects} / Snippets: %{counter_snippets}"
@ -24559,12 +24571,6 @@ msgstr ""
msgid "Set the milestone to %{milestone_reference}."
msgstr ""
msgid "Set the number of concurrent requests this secondary node will make to the primary node while backfilling."
msgstr ""
msgid "Set the synchronization and verification capacity for the secondary node."
msgstr ""
msgid "Set the timeout in seconds to send a secondary node status to the primary and IPs allowed for the secondary nodes."
msgstr ""
@ -24607,13 +24613,16 @@ msgstr ""
msgid "Set up your project to automatically push and/or pull changes to/from another repository. Branches, tags, and commits will be synced automatically."
msgstr ""
msgid "Set verification limit and frequency."
msgstr ""
msgid "Set weight"
msgstr ""
msgid "Set weight to %{weight}."
msgstr ""
msgid "Set what should be replicated by choosing specific projects or groups by the secondary node."
msgid "Set what should be replicated by this secondary node."
msgstr ""
msgid "SetPasswordToCloneLink|set a password"
@ -26210,6 +26219,9 @@ msgstr ""
msgid "Synchronization disabled"
msgstr ""
msgid "Synchronization settings"
msgstr ""
msgid "Syncing…"
msgstr ""
@ -26489,6 +26501,12 @@ msgstr[1] ""
msgid "Test settings"
msgstr ""
msgid "TestCases|Move test case"
msgstr ""
msgid "TestCases|Moving test case"
msgstr ""
msgid "TestCases|New Test Case"
msgstr ""
@ -26516,6 +26534,9 @@ msgstr ""
msgid "TestCases|Something went wrong while marking test case todo as done."
msgstr ""
msgid "TestCases|Something went wrong while moving test case."
msgstr ""
msgid "TestCases|Something went wrong while updating the test case labels."
msgstr ""
@ -29632,7 +29653,7 @@ msgstr ""
msgid "Various settings that affect GitLab performance."
msgstr ""
msgid "Verification capacity"
msgid "Verification concurrency limit"
msgstr ""
msgid "Verification information"

View File

@ -279,7 +279,7 @@ RSpec.describe 'Project' do
end
it 'deletes a project', :sidekiq_might_not_need_inline do
expect { remove_with_confirm('Delete project', "Delete #{project.full_name}", 'Yes, delete project') }.to change { Project.count }.by(-1)
expect { remove_with_confirm('Delete project', project.path, 'Yes, delete project') }.to change { Project.count }.by(-1)
expect(page).to have_content "Project '#{project.full_name}' is in the process of being deleted."
expect(Project.all.count).to be_zero
expect(project.issues).to be_empty

View File

@ -16,6 +16,12 @@ RSpec.describe ::Packages::Npm::PackageFinder do
it { is_expected.to be_empty }
end
context 'with nil project' do
let(:project) { nil }
it { is_expected.to be_empty }
end
end
describe '#find_by_version' do

View File

@ -17,7 +17,7 @@ RSpec.describe GitlabSchema.types['Issue'] do
fields = %i[id iid title description state reference author assignees updated_by participants labels milestone due_date
confidential discussion_locked upvotes downvotes user_notes_count user_discussions_count web_path web_url relative_position
emails_disabled subscribed time_estimate total_time_spent human_time_estimate human_total_time_spent closed_at created_at updated_at task_completion_status
designs design_collection alert_management_alert severity current_user_todos]
designs design_collection alert_management_alert severity current_user_todos moved moved_to]
fields.each do |field_name|
expect(described_class).to have_graphql_field(field_name)

View File

@ -2,20 +2,20 @@
require 'spec_helper'
RSpec.describe BulkImports::Common::Loaders::EntitiesLoader do
RSpec.describe BulkImports::Common::Loaders::EntityLoader do
describe '#load' do
it "creates entities for the given data" do
group = create(:group, path: "imported-group")
parent_entity = create(:bulk_import_entity, group: group, bulk_import: create(:bulk_import))
context = instance_double(BulkImports::Pipeline::Context, entity: parent_entity)
data = [{
data = {
source_type: :group_entity,
source_full_path: "parent/subgroup",
destination_name: "subgroup",
destination_namespace: parent_entity.group.full_path,
parent_id: parent_entity.id
}]
}
expect { subject.load(context, data) }.to change(BulkImports::Entity, :count).by(1)

View File

@ -35,7 +35,7 @@ RSpec.describe BulkImports::Groups::Pipelines::SubgroupEntitiesPipeline do
before do
allow_next_instance_of(BulkImports::Groups::Extractors::SubgroupsExtractor) do |extractor|
allow(extractor).to receive(:extract).and_return([subgroup_data])
allow(extractor).to receive(:extract).and_return(subgroup_data)
end
parent.add_owner(user)
@ -67,14 +67,14 @@ RSpec.describe BulkImports::Groups::Pipelines::SubgroupEntitiesPipeline do
it 'has transformers' do
expect(described_class.transformers).to contain_exactly(
klass: BulkImports::Groups::Transformers::SubgroupsToEntitiesTransformer,
klass: BulkImports::Groups::Transformers::SubgroupToEntityTransformer,
options: nil
)
end
it 'has loaders' do
expect(described_class.loaders).to contain_exactly(
klass: BulkImports::Common::Loaders::EntitiesLoader,
klass: BulkImports::Common::Loaders::EntityLoader,
options: nil
)
end

View File

@ -2,20 +2,18 @@
require 'spec_helper'
RSpec.describe BulkImports::Groups::Transformers::SubgroupsToEntitiesTransformer do
RSpec.describe BulkImports::Groups::Transformers::SubgroupToEntityTransformer do
describe "#transform" do
it "transforms subgroups data in entity params" do
parent = create(:group)
parent_entity = instance_double(BulkImports::Entity, group: parent, id: 1)
context = instance_double(BulkImports::Pipeline::Context, entity: parent_entity)
subgroup_data = [
{
"name" => "subgroup",
"full_path" => "parent/subgroup"
}
]
subgroup_data = {
"name" => "subgroup",
"full_path" => "parent/subgroup"
}
expect(subject.transform(context, subgroup_data)).to contain_exactly(
expect(subject.transform(context, subgroup_data)).to eq(
source_type: :group_entity,
source_full_path: "parent/subgroup",
destination_name: "subgroup",

View File

@ -5,6 +5,7 @@ require 'spec_helper'
RSpec.describe Gitlab::GitAccessSnippet do
include ProjectHelpers
include TermsHelper
include AdminModeHelper
include_context 'ProjectPolicyTable context'
using RSpec::Parameterized::TableSyntax
@ -207,12 +208,13 @@ RSpec.describe Gitlab::GitAccessSnippet do
let(:snippet) { create(:personal_snippet, snippet_level, :repository) }
let(:user) { membership == :author ? snippet.author : create_user_from_membership(nil, membership) }
where(:snippet_level, :membership, :_expected_count) do
where(:snippet_level, :membership, :admin_mode, :_expected_count) do
permission_table_for_personal_snippet_access
end
with_them do
it "respects accessibility" do
enable_admin_mode!(user) if admin_mode
error_class = described_class::ForbiddenError
if Ability.allowed?(user, :update_snippet, snippet)

View File

@ -79,6 +79,18 @@ RSpec.describe MergeRequest, factory_default: :keep do
end
end
describe '.with_jira_issue_keys' do
let_it_be(:mr_with_jira_title) { create(:merge_request, :unique_branches, title: 'Fix TEST-123') }
let_it_be(:mr_with_jira_description) { create(:merge_request, :unique_branches, description: 'this closes TEST-321') }
let_it_be(:mr_without_jira_reference) { create(:merge_request, :unique_branches) }
subject { described_class.with_jira_issue_keys }
it { is_expected.to contain_exactly(mr_with_jira_title, mr_with_jira_description) }
it { is_expected.not_to include(mr_without_jira_reference) }
end
describe '#squash_in_progress?' do
let(:repo_path) do
Gitlab::GitalyClient::StorageSettings.allow_disk_access do

View File

@ -3996,8 +3996,16 @@ RSpec.describe Project, factory_default: :keep do
context 'when feature is private' do
let(:project) { create(:project, :public, :merge_requests_private) }
it 'returns projects with the project feature private' do
is_expected.to include(project)
context 'when admin mode is enabled', :enable_admin_mode do
it 'returns projects with the project feature private' do
is_expected.to include(project)
end
end
context 'when admin mode is disabled' do
it 'does not return projects with the project feature private' do
is_expected.not_to include(project)
end
end
end
end
@ -4020,7 +4028,7 @@ RSpec.describe Project, factory_default: :keep do
end
end
describe '.filter_by_feature_visibility', :enable_admin_mode do
describe '.filter_by_feature_visibility' do
include_context 'ProjectPolicyTable context'
include ProjectHelpers
using RSpec::Parameterized::TableSyntax
@ -4032,12 +4040,13 @@ RSpec.describe Project, factory_default: :keep do
context 'reporter level access' do
let(:feature) { MergeRequest }
where(:project_level, :feature_access_level, :membership, :expected_count) do
where(:project_level, :feature_access_level, :membership, :admin_mode, :expected_count) do
permission_table_for_reporter_feature_access
end
with_them do
it "respects visibility" do
enable_admin_mode!(user) if admin_mode
update_feature_access_level(project, feature_access_level)
expected_objects = expected_count == 1 ? [project] : []
@ -4052,12 +4061,13 @@ RSpec.describe Project, factory_default: :keep do
context 'issues' do
let(:feature) { Issue }
where(:project_level, :feature_access_level, :membership, :expected_count) do
where(:project_level, :feature_access_level, :membership, :admin_mode, :expected_count) do
permission_table_for_guest_feature_access
end
with_them do
it "respects visibility" do
enable_admin_mode!(user) if admin_mode
update_feature_access_level(project, feature_access_level)
expected_objects = expected_count == 1 ? [project] : []
@ -4072,12 +4082,13 @@ RSpec.describe Project, factory_default: :keep do
context 'wiki' do
let(:feature) { :wiki }
where(:project_level, :feature_access_level, :membership, :expected_count) do
where(:project_level, :feature_access_level, :membership, :admin_mode, :expected_count) do
permission_table_for_guest_feature_access
end
with_them do
it "respects visibility" do
enable_admin_mode!(user) if admin_mode
update_feature_access_level(project, feature_access_level)
expected_objects = expected_count == 1 ? [project] : []
@ -4092,12 +4103,13 @@ RSpec.describe Project, factory_default: :keep do
context 'code' do
let(:feature) { :repository }
where(:project_level, :feature_access_level, :membership, :expected_count) do
where(:project_level, :feature_access_level, :membership, :admin_mode, :expected_count) do
permission_table_for_guest_feature_access_and_non_private_project_only
end
with_them do
it "respects visibility" do
enable_admin_mode!(user) if admin_mode
update_feature_access_level(project, feature_access_level)
expected_objects = expected_count == 1 ? [project] : []

View File

@ -2,7 +2,7 @@
require 'spec_helper'
RSpec.describe BlobPolicy, :enable_admin_mode do
RSpec.describe BlobPolicy do
include_context 'ProjectPolicyTable context'
include ProjectHelpers
using RSpec::Parameterized::TableSyntax
@ -13,12 +13,13 @@ RSpec.describe BlobPolicy, :enable_admin_mode do
subject(:policy) { described_class.new(user, blob) }
where(:project_level, :feature_access_level, :membership, :expected_count) do
where(:project_level, :feature_access_level, :membership, :admin_mode, :expected_count) do
permission_table_for_guest_feature_access_and_non_private_project_only
end
with_them do
it "grants permission" do
enable_admin_mode!(user) if admin_mode
update_feature_access_level(project, feature_access_level)
if expected_count == 1

View File

@ -2,7 +2,7 @@
require 'spec_helper'
RSpec.describe WikiPagePolicy, :enable_admin_mode do
RSpec.describe WikiPagePolicy do
include_context 'ProjectPolicyTable context'
include ProjectHelpers
using RSpec::Parameterized::TableSyntax
@ -13,12 +13,13 @@ RSpec.describe WikiPagePolicy, :enable_admin_mode do
subject(:policy) { described_class.new(user, wiki_page) }
where(:project_level, :feature_access_level, :membership, :expected_count) do
where(:project_level, :feature_access_level, :membership, :admin_mode, :expected_count) do
permission_table_for_guest_feature_access
end
with_them do
it "grants permission" do
enable_admin_mode!(user) if admin_mode
update_feature_access_level(project, feature_access_level)
if expected_count == 1

View File

@ -83,6 +83,25 @@ RSpec.describe 'Query.issue(id)' do
end
end
context 'when issue got moved' do
let_it_be(:issue_fields) { ['moved', 'movedTo { title }'] }
let_it_be(:new_issue) { create(:issue) }
let_it_be(:issue) { create(:issue, project: project, moved_to: new_issue) }
let_it_be(:issue_params) { { 'id' => issue.to_global_id.to_s } }
before_all do
new_issue.project.add_developer(current_user)
end
it 'returns correct attributes' do
post_graphql(query, current_user: current_user)
expect(issue_data.keys).to eq( %w(moved movedTo) )
expect(issue_data['moved']).to eq(true)
expect(issue_data['movedTo']['title']).to eq(new_issue.title)
end
end
context 'when passed a non-Issue gid' do
let(:mr) {create(:merge_request)}

View File

@ -0,0 +1,31 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe API::NpmInstancePackages do
include_context 'npm api setup'
describe 'GET /api/v4/packages/npm/*package_name' do
it_behaves_like 'handling get metadata requests' do
let(:url) { api("/packages/npm/#{package_name}") }
end
end
describe 'GET /api/v4/packages/npm/-/package/*package_name/dist-tags' do
it_behaves_like 'handling get dist tags requests', scope: :instance do
let(:url) { api("/packages/npm/-/package/#{package_name}/dist-tags") }
end
end
describe 'PUT /api/v4/packages/npm/-/package/*package_name/dist-tags/:tag' do
it_behaves_like 'handling create dist tag requests', scope: :instance do
let(:url) { api("/packages/npm/-/package/#{package_name}/dist-tags/#{tag_name}") }
end
end
describe 'DELETE /api/v4/packages/npm/-/package/*package_name/dist-tags/:tag' do
it_behaves_like 'handling delete dist tag requests', scope: :instance do
let(:url) { api("/packages/npm/-/package/#{package_name}/dist-tags/#{tag_name}") }
end
end
end

View File

@ -1,556 +0,0 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe API::NpmPackages do
include PackagesManagerApiSpecHelpers
include HttpBasicAuthHelpers
let_it_be(:user) { create(:user) }
let_it_be(:group) { create(:group) }
let_it_be(:project, reload: true) { create(:project, :public, namespace: group) }
let_it_be(:package, reload: true) { create(:npm_package, project: project) }
let_it_be(:token) { create(:oauth_access_token, scopes: 'api', resource_owner: user) }
let_it_be(:personal_access_token) { create(:personal_access_token, user: user) }
let_it_be(:job, reload: true) { create(:ci_build, user: user, status: :running) }
let_it_be(:deploy_token) { create(:deploy_token, read_package_registry: true, write_package_registry: true) }
let_it_be(:project_deploy_token) { create(:project_deploy_token, deploy_token: deploy_token, project: project) }
before do
project.add_developer(user)
end
shared_examples 'a package that requires auth' do
it 'returns the package info with oauth token' do
get_package_with_token(package)
expect_a_valid_package_response
end
it 'returns the package info with running job token' do
get_package_with_job_token(package)
expect_a_valid_package_response
end
it 'denies request without running job token' do
job.update!(status: :success)
get_package_with_job_token(package)
expect(response).to have_gitlab_http_status(:unauthorized)
end
it 'denies request without oauth token' do
get_package(package)
expect(response).to have_gitlab_http_status(:forbidden)
end
it 'returns the package info with deploy token' do
get_package_with_deploy_token(package)
expect_a_valid_package_response
end
end
describe 'GET /api/v4/packages/npm/*package_name' do
let_it_be(:package_dependency_link1) { create(:packages_dependency_link, package: package, dependency_type: :dependencies) }
let_it_be(:package_dependency_link2) { create(:packages_dependency_link, package: package, dependency_type: :devDependencies) }
let_it_be(:package_dependency_link3) { create(:packages_dependency_link, package: package, dependency_type: :bundleDependencies) }
let_it_be(:package_dependency_link4) { create(:packages_dependency_link, package: package, dependency_type: :peerDependencies) }
shared_examples 'returning the npm package info' do
it 'returns the package info' do
get_package(package)
expect_a_valid_package_response
end
end
shared_examples 'returning forbidden for unknown package' do
context 'with an unknown package' do
it 'returns forbidden' do
get api("/packages/npm/unknown")
expect(response).to have_gitlab_http_status(:forbidden)
end
end
end
context 'a public project' do
it_behaves_like 'returning the npm package info'
context 'with application setting enabled' do
before do
stub_application_setting(npm_package_requests_forwarding: true)
end
it_behaves_like 'returning the npm package info'
context 'with unknown package' do
subject { get api("/packages/npm/unknown") }
it 'returns a redirect' do
subject
expect(response).to have_gitlab_http_status(:found)
expect(response.headers['Location']).to eq('https://registry.npmjs.org/unknown')
end
it_behaves_like 'a gitlab tracking event', described_class.name, 'npm_request_forward'
end
end
context 'with application setting disabled' do
before do
stub_application_setting(npm_package_requests_forwarding: false)
end
it_behaves_like 'returning the npm package info'
it_behaves_like 'returning forbidden for unknown package'
end
context 'project path with a dot' do
before do
project.update!(path: 'foo.bar')
end
it_behaves_like 'returning the npm package info'
end
end
context 'internal project' do
before do
project.team.truncate
project.update!(visibility_level: Gitlab::VisibilityLevel::INTERNAL)
end
it_behaves_like 'a package that requires auth'
end
context 'private project' do
before do
project.update!(visibility_level: Gitlab::VisibilityLevel::PRIVATE)
end
it_behaves_like 'a package that requires auth'
it 'denies request when not enough permissions' do
project.add_guest(user)
get_package_with_token(package)
expect(response).to have_gitlab_http_status(:forbidden)
end
end
def get_package(package, params = {}, headers = {})
get api("/packages/npm/#{package.name}"), params: params, headers: headers
end
def get_package_with_token(package, params = {})
get_package(package, params.merge(access_token: token.token))
end
def get_package_with_job_token(package, params = {})
get_package(package, params.merge(job_token: job.token))
end
def get_package_with_deploy_token(package, params = {})
get_package(package, {}, build_token_auth_header(deploy_token.token))
end
end
describe 'GET /api/v4/projects/:id/packages/npm/*package_name/-/*file_name' do
let_it_be(:package_file) { package.package_files.first }
shared_examples 'a package file that requires auth' do
it 'returns the file with an access token' do
get_file_with_token(package_file)
expect(response).to have_gitlab_http_status(:ok)
expect(response.media_type).to eq('application/octet-stream')
end
it 'returns the file with a job token' do
get_file_with_job_token(package_file)
expect(response).to have_gitlab_http_status(:ok)
expect(response.media_type).to eq('application/octet-stream')
end
it 'denies download with no token' do
get_file(package_file)
expect(response).to have_gitlab_http_status(:not_found)
end
end
context 'a public project' do
subject { get_file(package_file) }
it 'returns the file with no token needed' do
subject
expect(response).to have_gitlab_http_status(:ok)
expect(response.media_type).to eq('application/octet-stream')
end
it_behaves_like 'a package tracking event', described_class.name, 'pull_package'
end
context 'private project' do
before do
project.update!(visibility_level: Gitlab::VisibilityLevel::PRIVATE)
end
it_behaves_like 'a package file that requires auth'
it 'denies download when not enough permissions' do
project.add_guest(user)
get_file_with_token(package_file)
expect(response).to have_gitlab_http_status(:forbidden)
end
end
context 'internal project' do
before do
project.update!(visibility_level: Gitlab::VisibilityLevel::INTERNAL)
end
it_behaves_like 'a package file that requires auth'
end
def get_file(package_file, params = {})
get api("/projects/#{project.id}/packages/npm/" \
"#{package_file.package.name}/-/#{package_file.file_name}"), params: params
end
def get_file_with_token(package_file, params = {})
get_file(package_file, params.merge(access_token: token.token))
end
def get_file_with_job_token(package_file, params = {})
get_file(package_file, params.merge(job_token: job.token))
end
end
describe 'PUT /api/v4/projects/:id/packages/npm/:package_name' do
RSpec.shared_examples 'handling invalid record with 400 error' do
it 'handles an ActiveRecord::RecordInvalid exception with 400 error' do
expect { upload_package_with_token(package_name, params) }
.not_to change { project.packages.count }
expect(response).to have_gitlab_http_status(:bad_request)
end
end
context 'when params are correct' do
context 'invalid package record' do
context 'unscoped package' do
let(:package_name) { 'my_unscoped_package' }
let(:params) { upload_params(package_name: package_name) }
it_behaves_like 'handling invalid record with 400 error'
context 'with empty versions' do
let(:params) { upload_params(package_name: package_name).merge!(versions: {}) }
it 'throws a 400 error' do
expect { upload_package_with_token(package_name, params) }
.not_to change { project.packages.count }
expect(response).to have_gitlab_http_status(:bad_request)
end
end
end
context 'invalid package name' do
let(:package_name) { "@#{group.path}/my_inv@@lid_package_name" }
let(:params) { upload_params(package_name: package_name) }
it_behaves_like 'handling invalid record with 400 error'
end
context 'invalid package version' do
using RSpec::Parameterized::TableSyntax
let(:package_name) { "@#{group.path}/my_package_name" }
where(:version) do
[
'1',
'1.2',
'1./2.3',
'../../../../../1.2.3',
'%2e%2e%2f1.2.3'
]
end
with_them do
let(:params) { upload_params(package_name: package_name, package_version: version) }
it_behaves_like 'handling invalid record with 400 error'
end
end
end
context 'scoped package' do
let(:package_name) { "@#{group.path}/my_package_name" }
let(:params) { upload_params(package_name: package_name) }
context 'with access token' do
subject { upload_package_with_token(package_name, params) }
it_behaves_like 'a package tracking event', described_class.name, 'push_package'
it 'creates npm package with file' do
expect { subject }
.to change { project.packages.count }.by(1)
.and change { Packages::PackageFile.count }.by(1)
.and change { Packages::Tag.count }.by(1)
expect(response).to have_gitlab_http_status(:ok)
end
end
it 'creates npm package with file with job token' do
expect { upload_package_with_job_token(package_name, params) }
.to change { project.packages.count }.by(1)
.and change { Packages::PackageFile.count }.by(1)
expect(response).to have_gitlab_http_status(:ok)
end
context 'with an authenticated job token' do
let!(:job) { create(:ci_build, user: user) }
before do
Grape::Endpoint.before_each do |endpoint|
expect(endpoint).to receive(:current_authenticated_job) { job }
end
end
after do
Grape::Endpoint.before_each nil
end
it 'creates the package metadata' do
upload_package_with_token(package_name, params)
expect(response).to have_gitlab_http_status(:ok)
expect(project.reload.packages.find(json_response['id']).build_info.pipeline).to eq job.pipeline
end
end
end
context 'package creation fails' do
let(:package_name) { "@#{group.path}/my_package_name" }
let(:params) { upload_params(package_name: package_name) }
it 'returns an error if the package already exists' do
create(:npm_package, project: project, version: '1.0.1', name: "@#{group.path}/my_package_name")
expect { upload_package_with_token(package_name, params) }
.not_to change { project.packages.count }
expect(response).to have_gitlab_http_status(:forbidden)
end
end
context 'with dependencies' do
let(:package_name) { "@#{group.path}/my_package_name" }
let(:params) { upload_params(package_name: package_name, file: 'npm/payload_with_duplicated_packages.json') }
it 'creates npm package with file and dependencies' do
expect { upload_package_with_token(package_name, params) }
.to change { project.packages.count }.by(1)
.and change { Packages::PackageFile.count }.by(1)
.and change { Packages::Dependency.count}.by(4)
.and change { Packages::DependencyLink.count}.by(6)
expect(response).to have_gitlab_http_status(:ok)
end
context 'with existing dependencies' do
before do
name = "@#{group.path}/existing_package"
upload_package_with_token(name, upload_params(package_name: name, file: 'npm/payload_with_duplicated_packages.json'))
end
it 'reuses them' do
expect { upload_package_with_token(package_name, params) }
.to change { project.packages.count }.by(1)
.and change { Packages::PackageFile.count }.by(1)
.and not_change { Packages::Dependency.count}
.and change { Packages::DependencyLink.count}.by(6)
end
end
end
end
def upload_package(package_name, params = {})
put api("/projects/#{project.id}/packages/npm/#{package_name.sub('/', '%2f')}"), params: params
end
def upload_package_with_token(package_name, params = {})
upload_package(package_name, params.merge(access_token: token.token))
end
def upload_package_with_job_token(package_name, params = {})
upload_package(package_name, params.merge(job_token: job.token))
end
def upload_params(package_name:, package_version: '1.0.1', file: 'npm/payload.json')
Gitlab::Json.parse(fixture_file("packages/#{file}")
.gsub('@root/npm-test', package_name)
.gsub('1.0.1', package_version))
end
end
describe 'GET /api/v4/packages/npm/-/package/*package_name/dist-tags' do
let_it_be(:package_tag1) { create(:packages_tag, package: package) }
let_it_be(:package_tag2) { create(:packages_tag, package: package) }
let(:package_name) { package.name }
let(:url) { "/packages/npm/-/package/#{package_name}/dist-tags" }
subject { get api(url) }
context 'with public project' do
context 'with authenticated user' do
subject { get api(url, personal_access_token: personal_access_token) }
it_behaves_like 'returns package tags', :maintainer
it_behaves_like 'returns package tags', :developer
it_behaves_like 'returns package tags', :reporter
it_behaves_like 'returns package tags', :guest
end
context 'with unauthenticated user' do
it_behaves_like 'returns package tags', :no_type
end
end
context 'with private project' do
before do
project.update!(visibility_level: Gitlab::VisibilityLevel::PRIVATE)
end
context 'with authenticated user' do
subject { get api(url, personal_access_token: personal_access_token) }
it_behaves_like 'returns package tags', :maintainer
it_behaves_like 'returns package tags', :developer
it_behaves_like 'returns package tags', :reporter
it_behaves_like 'rejects package tags access', :guest, :forbidden
end
context 'with unauthenticated user' do
it_behaves_like 'rejects package tags access', :no_type, :forbidden
end
end
end
describe 'PUT /api/v4/packages/npm/-/package/*package_name/dist-tags/:tag' do
let_it_be(:tag_name) { 'test' }
let(:package_name) { package.name }
let(:version) { package.version }
let(:url) { "/packages/npm/-/package/#{package_name}/dist-tags/#{tag_name}" }
subject { put api(url), env: { 'api.request.body': version } }
context 'with public project' do
context 'with authenticated user' do
subject { put api(url, personal_access_token: personal_access_token), env: { 'api.request.body': version } }
it_behaves_like 'create package tag', :maintainer
it_behaves_like 'create package tag', :developer
it_behaves_like 'rejects package tags access', :reporter, :forbidden
it_behaves_like 'rejects package tags access', :guest, :forbidden
end
context 'with unauthenticated user' do
it_behaves_like 'rejects package tags access', :no_type, :unauthorized
end
end
context 'with private project' do
before do
project.update!(visibility_level: Gitlab::VisibilityLevel::PRIVATE)
end
context 'with authenticated user' do
subject { put api(url, personal_access_token: personal_access_token), env: { 'api.request.body': version } }
it_behaves_like 'create package tag', :maintainer
it_behaves_like 'create package tag', :developer
it_behaves_like 'rejects package tags access', :reporter, :forbidden
it_behaves_like 'rejects package tags access', :guest, :forbidden
end
context 'with unauthenticated user' do
it_behaves_like 'rejects package tags access', :no_type, :unauthorized
end
end
end
describe 'DELETE /api/v4/packages/npm/-/package/*package_name/dist-tags/:tag' do
let_it_be(:package_tag) { create(:packages_tag, package: package) }
let(:package_name) { package.name }
let(:tag_name) { package_tag.name }
let(:url) { "/packages/npm/-/package/#{package_name}/dist-tags/#{tag_name}" }
subject { delete api(url) }
context 'with public project' do
context 'with authenticated user' do
subject { delete api(url, personal_access_token: personal_access_token) }
it_behaves_like 'delete package tag', :maintainer
it_behaves_like 'rejects package tags access', :developer, :forbidden
it_behaves_like 'rejects package tags access', :reporter, :forbidden
it_behaves_like 'rejects package tags access', :guest, :forbidden
end
context 'with unauthenticated user' do
it_behaves_like 'rejects package tags access', :no_type, :unauthorized
end
end
context 'with private project' do
before do
project.update!(visibility_level: Gitlab::VisibilityLevel::PRIVATE)
end
context 'with authenticated user' do
subject { delete api(url, personal_access_token: personal_access_token) }
it_behaves_like 'delete package tag', :maintainer
it_behaves_like 'rejects package tags access', :developer, :forbidden
it_behaves_like 'rejects package tags access', :reporter, :forbidden
it_behaves_like 'rejects package tags access', :guest, :forbidden
end
context 'with unauthenticated user' do
it_behaves_like 'rejects package tags access', :no_type, :unauthorized
end
end
end
def expect_a_valid_package_response
expect(response).to have_gitlab_http_status(:ok)
expect(response.media_type).to eq('application/json')
expect(response).to match_response_schema('public_api/v4/packages/npm_package')
expect(json_response['name']).to eq(package.name)
expect(json_response['versions'][package.version]).to match_schema('public_api/v4/packages/npm_package_version')
::Packages::Npm::PackagePresenter::NPM_VALID_DEPENDENCY_TYPES.each do |dependency_type|
expect(json_response.dig('versions', package.version, dependency_type.to_s)).to be_any
end
expect(json_response['dist-tags']).to match_schema('public_api/v4/packages/npm_package_tags')
end
end

View File

@ -0,0 +1,281 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe API::NpmProjectPackages do
include_context 'npm api setup'
describe 'GET /api/v4/projects/:id/packages/npm/*package_name' do
it_behaves_like 'handling get metadata requests' do
let(:url) { api("/projects/#{project.id}/packages/npm/#{package_name}") }
end
end
describe 'GET /api/v4/projects/:id/packages/npm/-/package/*package_name/dist-tags' do
it_behaves_like 'handling get dist tags requests' do
let(:url) { api("/projects/#{project.id}/packages/npm/-/package/#{package_name}/dist-tags") }
end
end
describe 'PUT /api/v4/projects/:id/packages/npm/-/package/*package_name/dist-tags/:tag' do
it_behaves_like 'handling create dist tag requests' do
let(:url) { api("/projects/#{project.id}/packages/npm/-/package/#{package_name}/dist-tags/#{tag_name}") }
end
end
describe 'DELETE /api/v4/projects/:id/packages/npm/-/package/*package_name/dist-tags/:tag' do
it_behaves_like 'handling delete dist tag requests' do
let(:url) { api("/projects/#{project.id}/packages/npm/-/package/#{package_name}/dist-tags/#{tag_name}") }
end
end
describe 'GET /api/v4/projects/:id/packages/npm/*package_name/-/*file_name' do
let_it_be(:package_file) { package.package_files.first }
let(:params) { {} }
let(:url) { api("/projects/#{project.id}/packages/npm/#{package_file.package.name}/-/#{package_file.file_name}") }
subject { get(url, params: params) }
shared_examples 'a package file that requires auth' do
it 'denies download with no token' do
subject
expect(response).to have_gitlab_http_status(:not_found)
end
context 'with access token' do
let(:params) { { access_token: token.token } }
it 'returns the file' do
subject
expect(response).to have_gitlab_http_status(:ok)
expect(response.media_type).to eq('application/octet-stream')
end
end
context 'with job token' do
let(:params) { { job_token: job.token } }
it 'returns the file' do
subject
expect(response).to have_gitlab_http_status(:ok)
expect(response.media_type).to eq('application/octet-stream')
end
end
end
context 'a public project' do
it 'returns the file with no token needed' do
subject
expect(response).to have_gitlab_http_status(:ok)
expect(response.media_type).to eq('application/octet-stream')
end
it_behaves_like 'a package tracking event', 'API::NpmPackages', 'pull_package'
end
context 'private project' do
before do
project.update!(visibility_level: Gitlab::VisibilityLevel::PRIVATE)
end
it_behaves_like 'a package file that requires auth'
context 'with guest' do
let(:params) { { access_token: token.token } }
it 'denies download when not enough permissions' do
project.add_guest(user)
subject
expect(response).to have_gitlab_http_status(:forbidden)
end
end
end
context 'internal project' do
before do
project.update!(visibility_level: Gitlab::VisibilityLevel::INTERNAL)
end
it_behaves_like 'a package file that requires auth'
end
end
describe 'PUT /api/v4/projects/:id/packages/npm/:package_name' do
RSpec.shared_examples 'handling invalid record with 400 error' do
it 'handles an ActiveRecord::RecordInvalid exception with 400 error' do
expect { upload_package_with_token(package_name, params) }
.not_to change { project.packages.count }
expect(response).to have_gitlab_http_status(:bad_request)
end
end
context 'when params are correct' do
context 'invalid package record' do
context 'unscoped package' do
let(:package_name) { 'my_unscoped_package' }
let(:params) { upload_params(package_name: package_name) }
it_behaves_like 'handling invalid record with 400 error'
context 'with empty versions' do
let(:params) { upload_params(package_name: package_name).merge!(versions: {}) }
it 'throws a 400 error' do
expect { upload_package_with_token(package_name, params) }
.not_to change { project.packages.count }
expect(response).to have_gitlab_http_status(:bad_request)
end
end
end
context 'invalid package name' do
let(:package_name) { "@#{group.path}/my_inv@@lid_package_name" }
let(:params) { upload_params(package_name: package_name) }
it_behaves_like 'handling invalid record with 400 error'
end
context 'invalid package version' do
using RSpec::Parameterized::TableSyntax
let(:package_name) { "@#{group.path}/my_package_name" }
where(:version) do
[
'1',
'1.2',
'1./2.3',
'../../../../../1.2.3',
'%2e%2e%2f1.2.3'
]
end
with_them do
let(:params) { upload_params(package_name: package_name, package_version: version) }
it_behaves_like 'handling invalid record with 400 error'
end
end
end
context 'scoped package' do
let(:package_name) { "@#{group.path}/my_package_name" }
let(:params) { upload_params(package_name: package_name) }
context 'with access token' do
subject { upload_package_with_token(package_name, params) }
it_behaves_like 'a package tracking event', 'API::NpmPackages', 'push_package'
it 'creates npm package with file' do
expect { subject }
.to change { project.packages.count }.by(1)
.and change { Packages::PackageFile.count }.by(1)
.and change { Packages::Tag.count }.by(1)
expect(response).to have_gitlab_http_status(:ok)
end
end
it 'creates npm package with file with job token' do
expect { upload_package_with_job_token(package_name, params) }
.to change { project.packages.count }.by(1)
.and change { Packages::PackageFile.count }.by(1)
expect(response).to have_gitlab_http_status(:ok)
end
context 'with an authenticated job token' do
let!(:job) { create(:ci_build, user: user) }
before do
Grape::Endpoint.before_each do |endpoint|
expect(endpoint).to receive(:current_authenticated_job) { job }
end
end
after do
Grape::Endpoint.before_each nil
end
it 'creates the package metadata' do
upload_package_with_token(package_name, params)
expect(response).to have_gitlab_http_status(:ok)
expect(project.reload.packages.find(json_response['id']).build_info.pipeline).to eq job.pipeline
end
end
end
context 'package creation fails' do
let(:package_name) { "@#{group.path}/my_package_name" }
let(:params) { upload_params(package_name: package_name) }
it 'returns an error if the package already exists' do
create(:npm_package, project: project, version: '1.0.1', name: "@#{group.path}/my_package_name")
expect { upload_package_with_token(package_name, params) }
.not_to change { project.packages.count }
expect(response).to have_gitlab_http_status(:forbidden)
end
end
context 'with dependencies' do
let(:package_name) { "@#{group.path}/my_package_name" }
let(:params) { upload_params(package_name: package_name, file: 'npm/payload_with_duplicated_packages.json') }
it 'creates npm package with file and dependencies' do
expect { upload_package_with_token(package_name, params) }
.to change { project.packages.count }.by(1)
.and change { Packages::PackageFile.count }.by(1)
.and change { Packages::Dependency.count}.by(4)
.and change { Packages::DependencyLink.count}.by(6)
expect(response).to have_gitlab_http_status(:ok)
end
context 'with existing dependencies' do
before do
name = "@#{group.path}/existing_package"
upload_package_with_token(name, upload_params(package_name: name, file: 'npm/payload_with_duplicated_packages.json'))
end
it 'reuses them' do
expect { upload_package_with_token(package_name, params) }
.to change { project.packages.count }.by(1)
.and change { Packages::PackageFile.count }.by(1)
.and not_change { Packages::Dependency.count}
.and change { Packages::DependencyLink.count}.by(6)
end
end
end
end
def upload_package(package_name, params = {})
put api("/projects/#{project.id}/packages/npm/#{package_name.sub('/', '%2f')}"), params: params
end
def upload_package_with_token(package_name, params = {})
upload_package(package_name, params.merge(access_token: token.token))
end
def upload_package_with_job_token(package_name, params = {})
upload_package(package_name, params.merge(job_token: job.token))
end
def upload_params(package_name:, package_version: '1.0.1', file: 'npm/payload.json')
Gitlab::Json.parse(fixture_file("packages/#{file}")
.gsub('@root/npm-test', package_name)
.gsub('1.0.1', package_version))
end
end
end

View File

@ -12,8 +12,12 @@ RSpec.describe MoveToProjectEntity do
expect(subject[:id]).to eq(project.id)
end
it 'includes the full path' do
it 'includes the human-readable full path' do
expect(subject[:name_with_namespace]).to eq(project.name_with_namespace)
end
it 'includes the full path' do
expect(subject[:full_path]).to eq(project.full_path)
end
end
end

View File

@ -3,6 +3,8 @@
require 'spec_helper'
RSpec.describe Auth::ContainerRegistryAuthenticationService do
include AdminModeHelper
let(:current_project) { nil }
let(:current_user) { nil }
let(:current_params) { {} }
@ -696,6 +698,10 @@ RSpec.describe Auth::ContainerRegistryAuthenticationService do
context 'user has access to all projects' do
let_it_be(:current_user) { create(:user, :admin) }
before do
enable_admin_mode!(current_user)
end
it_behaves_like 'a browsable' do
let(:access) do
[

View File

@ -4,13 +4,13 @@ require 'spec_helper'
RSpec.describe Ci::CreatePipelineService do
context 'cache' do
let(:user) { create(:admin) }
let(:project) { create(:project, :custom_repo, files: files) }
let(:user) { project.owner }
let(:ref) { 'refs/heads/master' }
let(:source) { :push }
let(:service) { described_class.new(project, user, { ref: ref }) }
let(:pipeline) { service.execute(source) }
let(:job) { pipeline.builds.find_by(name: 'job') }
let(:project) { create(:project, :custom_repo, files: files) }
before do
stub_ci_pipeline_yaml_file(config)

View File

@ -4,8 +4,8 @@ require 'spec_helper'
RSpec.describe Ci::CreatePipelineService do
describe 'creation errors and warnings' do
let_it_be(:user) { create(:admin) }
let_it_be(:project) { create(:project, :repository, creator: user) }
let_it_be(:project) { create(:project, :repository) }
let_it_be(:user) { project.owner }
let(:ref) { 'refs/heads/master' }
let(:source) { :push }

View File

@ -3,7 +3,7 @@ require 'spec_helper'
RSpec.describe Ci::CreatePipelineService do
let_it_be(:project) { create(:project, :repository) }
let_it_be(:user) { create(:admin) }
let_it_be(:user) { project.owner }
let(:ref) { 'refs/heads/master' }
let(:service) { described_class.new(project, user, { ref: ref }) }

View File

@ -4,7 +4,7 @@ require 'spec_helper'
RSpec.describe Ci::CreatePipelineService do
let_it_be(:project) { create(:project, :repository) }
let_it_be(:user) { create(:admin) }
let_it_be(:user) { project.owner }
let(:ref) { 'refs/heads/master' }
let(:service) { described_class.new(project, user, { ref: ref }) }

View File

@ -4,8 +4,8 @@ require 'spec_helper'
RSpec.describe Ci::CreatePipelineService do
context 'needs' do
let_it_be(:user) { create(:admin) }
let_it_be(:project) { create(:project, :repository, creator: user) }
let_it_be(:project) { create(:project, :repository) }
let_it_be(:user) { project.owner }
let(:ref) { 'refs/heads/master' }
let(:source) { :push }
@ -14,6 +14,7 @@ RSpec.describe Ci::CreatePipelineService do
before do
stub_ci_pipeline_yaml_file(config)
project.add_developer(user)
end
context 'with a valid config' do

View File

@ -4,7 +4,7 @@ require 'spec_helper'
RSpec.describe Ci::CreatePipelineService do
let_it_be(:project) { create(:project, :repository) }
let_it_be(:user) { create(:admin) }
let_it_be(:user) { project.owner }
let(:service) { described_class.new(project, user, { ref: 'refs/heads/master' }) }
let(:content) do
<<~EOY

View File

@ -3,8 +3,8 @@ require 'spec_helper'
RSpec.describe Ci::CreatePipelineService do
describe '.pre/.post stages' do
let_it_be(:user) { create(:admin) }
let_it_be(:project) { create(:project, :repository, creator: user) }
let_it_be(:project) { create(:project, :repository) }
let_it_be(:user) { project.owner }
let(:source) { :push }
let(:service) { described_class.new(project, user, { ref: ref }) }

View File

@ -2,10 +2,10 @@
require 'spec_helper'
RSpec.describe Ci::CreatePipelineService do
let(:user) { create(:admin) }
let(:project) { create(:project, :repository) }
let(:user) { project.owner }
let(:ref) { 'refs/heads/master' }
let(:source) { :push }
let(:project) { create(:project, :repository) }
let(:service) { described_class.new(project, user, { ref: ref }) }
let(:pipeline) { service.execute(source) }
let(:build_names) { pipeline.builds.pluck(:name) }

View File

@ -6,7 +6,7 @@ RSpec.describe Ci::CreatePipelineService do
include ProjectForksHelper
let_it_be(:project, reload: true) { create(:project, :repository) }
let(:user) { create(:admin) }
let_it_be(:user, reload: true) { project.owner }
let(:ref_name) { 'refs/heads/master' }
before do
@ -155,6 +155,11 @@ RSpec.describe Ci::CreatePipelineService do
context 'when merge request target project is different from source project' do
let!(:project) { fork_project(target_project, nil, repository: true) }
let!(:target_project) { create(:project, :repository) }
let!(:user) { create(:user) }
before do
project.add_developer(user)
end
it 'updates head pipeline for merge request', :sidekiq_might_not_need_inline do
merge_request = create(:merge_request, source_branch: 'feature',
@ -1442,6 +1447,11 @@ RSpec.describe Ci::CreatePipelineService do
let(:ref_name) { 'refs/heads/feature' }
let!(:project) { fork_project(target_project, nil, repository: true) }
let!(:target_project) { create(:project, :repository) }
let!(:user) { create(:user) }
before do
project.add_developer(user)
end
it 'creates a legacy detached merge request pipeline in the forked project', :sidekiq_might_not_need_inline do
expect(pipeline).to be_persisted

View File

@ -321,21 +321,40 @@ RSpec.describe Issues::MoveService do
before do
authorized_project.add_developer(user)
authorized_project.add_developer(admin)
authorized_project2.add_developer(user)
authorized_project2.add_developer(admin)
end
context 'multiple related issues' do
it 'moves all related issues and retains permissions' do
new_issue = move_service.execute(old_issue, new_project)
context 'when admin mode is enabled', :enable_admin_mode do
it 'moves all related issues and retains permissions' do
new_issue = move_service.execute(old_issue, new_project)
expect(new_issue.related_issues(admin))
.to match_array([authorized_issue_b, authorized_issue_c, authorized_issue_d, unauthorized_issue])
expect(new_issue.related_issues(admin))
.to match_array([authorized_issue_b, authorized_issue_c, authorized_issue_d, unauthorized_issue])
expect(new_issue.related_issues(user))
.to match_array([authorized_issue_b, authorized_issue_c, authorized_issue_d])
expect(new_issue.related_issues(user))
.to match_array([authorized_issue_b, authorized_issue_c, authorized_issue_d])
expect(authorized_issue_d.related_issues(user))
.to match_array([new_issue])
expect(authorized_issue_d.related_issues(user))
.to match_array([new_issue])
end
end
context 'when admin mode is disabled' do
it 'moves all related issues and retains permissions' do
new_issue = move_service.execute(old_issue, new_project)
expect(new_issue.related_issues(admin))
.to match_array([authorized_issue_b, authorized_issue_c, authorized_issue_d])
expect(new_issue.related_issues(user))
.to match_array([authorized_issue_b, authorized_issue_c, authorized_issue_d])
expect(authorized_issue_d.related_issues(user))
.to match_array([new_issue])
end
end
end
end

View File

@ -74,8 +74,16 @@ RSpec.describe Issues::RelatedBranchesService do
context 'the user has access to otherwise unreadable pipelines' do
let(:user) { create(:admin) }
it 'returns info a developer could not see' do
expect(branch_info.pluck(:pipeline_status)).to include(an_instance_of(Gitlab::Ci::Status::Running))
context 'when admin mode is enabled', :enable_admin_mode do
it 'returns info a developer could not see' do
expect(branch_info.pluck(:pipeline_status)).to include(an_instance_of(Gitlab::Ci::Status::Running))
end
end
context 'when admin mode is disabled' do
it 'does not return info a developer could not see' do
expect(branch_info.pluck(:pipeline_status)).not_to include(an_instance_of(Gitlab::Ci::Status::Running))
end
end
end

View File

@ -32,6 +32,36 @@ RSpec.describe JiraConnectSubscriptions::CreateService do
it 'returns success' do
expect(subject[:status]).to eq(:success)
end
context 'namespace has projects' do
let!(:project_1) { create(:project, group: group) }
let!(:project_2) { create(:project, group: group) }
before do
stub_const("#{described_class}::MERGE_REQUEST_SYNC_BATCH_SIZE", 1)
end
it 'starts workers to sync projects in batches with delay' do
allow(Atlassian::JiraConnect::Client).to receive(:generate_update_sequence_id).and_return(123)
expect(JiraConnect::SyncProjectWorker).to receive(:bulk_perform_in).with(1.minute, [[project_1.id, 123]])
expect(JiraConnect::SyncProjectWorker).to receive(:bulk_perform_in).with(2.minutes, [[project_2.id, 123]])
subject
end
context 'when the jira_connect_full_namespace_sync feature flag is disabled' do
before do
stub_feature_flags(jira_connect_full_namespace_sync: false)
end
specify do
expect(JiraConnect::SyncProjectWorker).not_to receive(:bulk_perform_in_with_contexts)
subject
end
end
end
end
context 'when path is invalid' do

View File

@ -4,7 +4,7 @@ require 'spec_helper'
RSpec.describe Labels::TransferService do
describe '#execute' do
let_it_be(:user) { create(:admin) }
let_it_be(:user) { create(:user) }
let_it_be(:old_group_ancestor) { create(:group) }
let_it_be(:old_group) { create(:group, parent: old_group_ancestor) }
@ -15,6 +15,11 @@ RSpec.describe Labels::TransferService do
subject(:service) { described_class.new(user, old_group, project) }
before do
old_group_ancestor.add_developer(user)
new_group.add_developer(user)
end
it 'recreates missing group labels at project level and assigns them to the issuables' do
old_group_label_1 = create(:group_label, group: old_group)
old_group_label_2 = create(:group_label, group: old_group)

View File

@ -12,10 +12,20 @@ RSpec.describe MergeRequests::AddContextService do
subject(:service) { described_class.new(project, admin, merge_request: merge_request, commits: commits) }
describe "#execute" do
it "adds context commit" do
service.execute
context "when admin mode is enabled", :enable_admin_mode do
it "adds context commit" do
service.execute
expect(merge_request.merge_request_context_commit_diff_files.length).to eq(2)
expect(merge_request.merge_request_context_commit_diff_files.length).to eq(2)
end
end
context "when admin mode is disabled" do
it "doesn't add context commit" do
subject.execute
expect(merge_request.merge_request_context_commit_diff_files.length).to eq(0)
end
end
context "when user doesn't have permission to update merge request" do

View File

@ -3099,12 +3099,26 @@ RSpec.describe NotificationService, :mailer do
subject.new_issue(issue, member)
end
it 'still delivers email to admins' do
member.update!(admin: true)
context 'with admin user' do
before do
member.update!(admin: true)
end
expect(Notify).to receive(:new_issue_email).at_least(:once).with(member.id, issue.id, nil).and_call_original
context 'when admin mode is enabled', :enable_admin_mode do
it 'still delivers email to admins' do
expect(Notify).to receive(:new_issue_email).at_least(:once).with(member.id, issue.id, nil).and_call_original
subject.new_issue(issue, member)
subject.new_issue(issue, member)
end
end
context 'when admin mode is disabled' do
it 'does not send an email' do
expect(Notify).not_to receive(:new_issue_email)
subject.new_issue(issue, member)
end
end
end
end
end

View File

@ -38,7 +38,13 @@ RSpec.describe PersonalAccessTokens::CreateService do
context 'when current_user is an administrator' do
let(:current_user) { create(:admin) }
it_behaves_like 'a successfully created token'
context 'when admin mode is enabled', :enable_admin_mode do
it_behaves_like 'a successfully created token'
end
context 'when admin mode is disabled' do
it_behaves_like 'an unsuccessfully created token'
end
end
context 'when current_user is not an administrator' do

View File

@ -24,10 +24,19 @@ RSpec.describe PersonalAccessTokens::RevokeService do
let(:service) { described_class.new(current_user, token: token) }
context 'when current_user is an administrator' do
let_it_be(:current_user) { create(:admin) }
let_it_be(:token) { create(:personal_access_token) }
context 'when admin mode is enabled', :enable_admin_mode do
let_it_be(:current_user) { create(:admin) }
let_it_be(:token) { create(:personal_access_token) }
it_behaves_like 'a successfully revoked token'
it_behaves_like 'a successfully revoked token'
end
context 'when admin mode is disabled' do
let_it_be(:current_user) { create(:admin) }
let_it_be(:token) { create(:personal_access_token) }
it_behaves_like 'an unsuccessfully revoked token'
end
end
context 'when current_user is not an administrator' do

View File

@ -79,14 +79,28 @@ RSpec.describe Projects::AutocompleteService do
expect(issues.count).to eq 3
end
it 'lists all project issues for admin' do
autocomplete = described_class.new(project, admin)
issues = autocomplete.issues.map(&:iid)
context 'when admin mode is enabled', :enable_admin_mode do
it 'lists all project issues for admin', :enable_admin_mode do
autocomplete = described_class.new(project, admin)
issues = autocomplete.issues.map(&:iid)
expect(issues).to include issue.iid
expect(issues).to include security_issue_1.iid
expect(issues).to include security_issue_2.iid
expect(issues.count).to eq 3
expect(issues).to include issue.iid
expect(issues).to include security_issue_1.iid
expect(issues).to include security_issue_2.iid
expect(issues.count).to eq 3
end
end
context 'when admin mode is disabled' do
it 'does not list project confidential issues for admin' do
autocomplete = described_class.new(project, admin)
issues = autocomplete.issues.map(&:iid)
expect(issues).to include issue.iid
expect(issues).not_to include security_issue_1.iid
expect(issues).not_to include security_issue_2.iid
expect(issues.count).to eq 1
end
end
end
end

View File

@ -72,14 +72,25 @@ RSpec.describe Projects::CreateService, '#execute' do
end
context "admin creates project with other user's namespace_id" do
it 'sets the correct permissions' do
admin = create(:admin)
project = create_project(admin, opts)
context 'when admin mode is enabled', :enable_admin_mode do
it 'sets the correct permissions' do
admin = create(:admin)
project = create_project(admin, opts)
expect(project).to be_persisted
expect(project.owner).to eq(user)
expect(project.team.maintainers).to contain_exactly(user)
expect(project.namespace).to eq(user.namespace)
expect(project).to be_persisted
expect(project.owner).to eq(user)
expect(project.team.maintainers).to contain_exactly(user)
expect(project.namespace).to eq(user.namespace)
end
end
context 'when admin mode is disabled' do
it 'is not allowed' do
admin = create(:admin)
project = create_project(admin, opts)
expect(project).not_to be_persisted
end
end
end
@ -336,7 +347,15 @@ RSpec.describe Projects::CreateService, '#execute' do
)
end
it 'allows a restricted visibility level for admins' do
it 'does not allow a restricted visibility level for admins when admin mode is disabled' do
admin = create(:admin)
project = create_project(admin, opts)
expect(project.errors.any?).to be(true)
expect(project.saved?).to be_falsey
end
it 'allows a restricted visibility level for admins when admin mode is enabled', :enable_admin_mode do
admin = create(:admin)
project = create_project(admin, opts)

View File

@ -127,11 +127,22 @@ RSpec.describe Projects::UpdateService do
end
context 'when updated by an admin' do
it 'updates the project to public' do
result = update_project(project, admin, visibility_level: Gitlab::VisibilityLevel::PUBLIC)
context 'when admin mode is enabled', :enable_admin_mode do
it 'updates the project to public' do
result = update_project(project, admin, visibility_level: Gitlab::VisibilityLevel::PUBLIC)
expect(result).to eq({ status: :success })
expect(project).to be_public
expect(result).to eq({ status: :success })
expect(project).to be_public
end
end
context 'when admin mode is disabled' do
it 'does not update the project to public' do
result = update_project(project, admin, visibility_level: Gitlab::VisibilityLevel::PUBLIC)
expect(result).to eq({ status: :error, message: 'New visibility level not allowed!' })
expect(project).to be_private
end
end
end
end
@ -144,7 +155,7 @@ RSpec.describe Projects::UpdateService do
project.update!(namespace: group, visibility_level: group.visibility_level)
end
it 'does not update project visibility level' do
it 'does not update project visibility level even if admin', :enable_admin_mode do
result = update_project(project, admin, visibility_level: Gitlab::VisibilityLevel::PUBLIC)
expect(result).to eq({ status: :error, message: 'Visibility level public is not allowed in a internal group.' })
@ -181,6 +192,7 @@ RSpec.describe Projects::UpdateService do
describe 'when updating project that has forks' do
let(:project) { create(:project, :internal) }
let(:user) { project.owner }
let(:forked_project) { fork_project(project) }
context 'and unlink forks feature flag is off' do
@ -194,7 +206,7 @@ RSpec.describe Projects::UpdateService do
expect(project).to be_internal
expect(forked_project).to be_internal
expect(update_project(project, admin, opts)).to eq({ status: :success })
expect(update_project(project, user, opts)).to eq({ status: :success })
expect(project).to be_private
expect(forked_project.reload).to be_private
@ -206,7 +218,7 @@ RSpec.describe Projects::UpdateService do
expect(project).to be_internal
expect(forked_project).to be_internal
expect(update_project(project, admin, opts)).to eq({ status: :success })
expect(update_project(project, user, opts)).to eq({ status: :success })
expect(project).to be_public
expect(forked_project.reload).to be_internal
@ -220,7 +232,7 @@ RSpec.describe Projects::UpdateService do
expect(project).to be_internal
expect(forked_project).to be_internal
expect(update_project(project, admin, opts)).to eq({ status: :success })
expect(update_project(project, user, opts)).to eq({ status: :success })
expect(project).to be_private
expect(forked_project.reload).to be_internal
@ -576,15 +588,21 @@ RSpec.describe Projects::UpdateService do
context 'authenticated as admin' do
let(:user) { create(:admin) }
it 'schedules the transfer of the repository to the new storage and locks the project' do
update_project(project, admin, opts)
context 'when admin mode is enabled', :enable_admin_mode do
it 'schedules the transfer of the repository to the new storage and locks the project' do
update_project(project, admin, opts)
expect(project).to be_repository_read_only
expect(project.repository_storage_moves.last).to have_attributes(
state: ::ProjectRepositoryStorageMove.state_machines[:state].states[:scheduled].value,
source_storage_name: 'default',
destination_storage_name: 'test_second_storage'
)
expect(project).to be_repository_read_only
expect(project.repository_storage_moves.last).to have_attributes(
state: ::ProjectRepositoryStorageMove.state_machines[:state].states[:scheduled].value,
source_storage_name: 'default',
destination_storage_name: 'test_second_storage'
)
end
end
context 'when admin mode is disabled' do
it_behaves_like 'the transfer was not scheduled'
end
context 'the repository is read-only' do

View File

@ -46,8 +46,18 @@ RSpec.describe ResourceAccessTokens::CreateService do
end
context 'when created by an admin' do
it_behaves_like 'creates a user that has their email confirmed' do
let(:user) { create(:admin) }
let(:user) { create(:admin) }
context 'when admin mode is enabled', :enable_admin_mode do
it_behaves_like 'creates a user that has their email confirmed'
end
context 'when admin mode is disabled' do
it 'returns error' do
response = subject
expect(response.error?).to be true
end
end
end

View File

@ -49,12 +49,24 @@ RSpec.describe Search::SnippetService do
expect(results.objects('snippet_titles')).to match_array [public_snippet, internal_snippet, private_snippet, project_public_snippet, project_internal_snippet]
end
it 'returns all snippets when user is admin' do
admin = create(:admin)
search = described_class.new(admin, search: 'bar')
results = search.execute
context 'when admin mode is enabled', :enable_admin_mode do
it 'returns all snippets when user is admin' do
admin = create(:admin)
search = described_class.new(admin, search: 'bar')
results = search.execute
expect(results.objects('snippet_titles')).to match_array [public_snippet, internal_snippet, private_snippet, project_public_snippet, project_internal_snippet, project_private_snippet]
expect(results.objects('snippet_titles')).to match_array [public_snippet, internal_snippet, private_snippet, project_public_snippet, project_internal_snippet, project_private_snippet]
end
end
context 'when admin mode is disabled' do
it 'returns only public & internal snippets when user is admin' do
admin = create(:admin)
search = described_class.new(admin, search: 'bar')
results = search.execute
expect(results.objects('snippet_titles')).to match_array [public_snippet, internal_snippet, project_public_snippet, project_internal_snippet]
end
end
end
end

View File

@ -150,7 +150,7 @@ RSpec.describe TodoService do
service.new_issue(issue, author)
should_create_todo(user: member, target: issue, action: Todo::DIRECTLY_ADDRESSED)
should_create_todo(user: admin, target: issue, action: Todo::MENTIONED)
should_not_create_todo(user: admin, target: issue, action: Todo::MENTIONED)
should_create_todo(user: guest, target: issue, action: Todo::MENTIONED)
end
@ -160,7 +160,7 @@ RSpec.describe TodoService do
should_create_todo(user: assignee, target: confidential_issue, author: john_doe, action: Todo::ASSIGNED)
should_create_todo(user: author, target: confidential_issue, author: john_doe, action: Todo::MENTIONED)
should_create_todo(user: member, target: confidential_issue, author: john_doe, action: Todo::MENTIONED)
should_create_todo(user: admin, target: confidential_issue, author: john_doe, action: Todo::MENTIONED)
should_not_create_todo(user: admin, target: confidential_issue, author: john_doe, action: Todo::MENTIONED)
should_not_create_todo(user: guest, target: confidential_issue, author: john_doe, action: Todo::MENTIONED)
should_create_todo(user: john_doe, target: confidential_issue, author: john_doe, action: Todo::MENTIONED)
end
@ -171,7 +171,7 @@ RSpec.describe TodoService do
should_create_todo(user: assignee, target: addressed_confident_issue, author: john_doe, action: Todo::ASSIGNED)
should_create_todo(user: author, target: addressed_confident_issue, author: john_doe, action: Todo::DIRECTLY_ADDRESSED)
should_create_todo(user: member, target: addressed_confident_issue, author: john_doe, action: Todo::DIRECTLY_ADDRESSED)
should_create_todo(user: admin, target: addressed_confident_issue, author: john_doe, action: Todo::DIRECTLY_ADDRESSED)
should_not_create_todo(user: admin, target: addressed_confident_issue, author: john_doe, action: Todo::DIRECTLY_ADDRESSED)
should_not_create_todo(user: guest, target: addressed_confident_issue, author: john_doe, action: Todo::DIRECTLY_ADDRESSED)
should_create_todo(user: john_doe, target: addressed_confident_issue, author: john_doe, action: Todo::DIRECTLY_ADDRESSED)
end
@ -228,7 +228,7 @@ RSpec.describe TodoService do
should_create_todo(user: member, target: issue, action: Todo::DIRECTLY_ADDRESSED)
should_create_todo(user: guest, target: issue, action: Todo::MENTIONED)
should_create_todo(user: admin, target: issue, action: Todo::MENTIONED)
should_not_create_todo(user: admin, target: issue, action: Todo::MENTIONED)
should_not_create_todo(user: skipped, target: issue)
end
@ -273,7 +273,7 @@ RSpec.describe TodoService do
should_create_todo(user: author, target: confidential_issue, author: john_doe, action: Todo::MENTIONED)
should_create_todo(user: assignee, target: confidential_issue, author: john_doe, action: Todo::MENTIONED)
should_create_todo(user: member, target: confidential_issue, author: john_doe, action: Todo::MENTIONED)
should_create_todo(user: admin, target: confidential_issue, author: john_doe, action: Todo::MENTIONED)
should_not_create_todo(user: admin, target: confidential_issue, author: john_doe, action: Todo::MENTIONED)
should_not_create_todo(user: guest, target: confidential_issue, author: john_doe, action: Todo::MENTIONED)
should_create_todo(user: john_doe, target: confidential_issue, author: john_doe, action: Todo::MENTIONED)
end
@ -284,7 +284,7 @@ RSpec.describe TodoService do
should_create_todo(user: author, target: addressed_confident_issue, author: john_doe, action: Todo::DIRECTLY_ADDRESSED)
should_create_todo(user: assignee, target: addressed_confident_issue, author: john_doe, action: Todo::DIRECTLY_ADDRESSED)
should_create_todo(user: member, target: addressed_confident_issue, author: john_doe, action: Todo::DIRECTLY_ADDRESSED)
should_create_todo(user: admin, target: addressed_confident_issue, author: john_doe, action: Todo::DIRECTLY_ADDRESSED)
should_not_create_todo(user: admin, target: addressed_confident_issue, author: john_doe, action: Todo::DIRECTLY_ADDRESSED)
should_not_create_todo(user: guest, target: addressed_confident_issue, author: john_doe, action: Todo::DIRECTLY_ADDRESSED)
should_create_todo(user: john_doe, target: addressed_confident_issue, author: john_doe, action: Todo::DIRECTLY_ADDRESSED)
end
@ -432,7 +432,7 @@ RSpec.describe TodoService do
service.new_note(note, john_doe)
should_create_todo(user: member, target: issue, author: john_doe, action: Todo::DIRECTLY_ADDRESSED, note: note)
should_create_todo(user: admin, target: issue, author: john_doe, action: Todo::MENTIONED, note: note)
should_not_create_todo(user: admin, target: issue, author: john_doe, action: Todo::MENTIONED, note: note)
should_create_todo(user: guest, target: issue, author: john_doe, action: Todo::MENTIONED, note: note)
end
@ -452,7 +452,7 @@ RSpec.describe TodoService do
should_create_todo(user: author, target: confidential_issue, author: john_doe, action: Todo::MENTIONED, note: note_on_confidential_issue)
should_create_todo(user: assignee, target: confidential_issue, author: john_doe, action: Todo::MENTIONED, note: note_on_confidential_issue)
should_create_todo(user: member, target: confidential_issue, author: john_doe, action: Todo::MENTIONED, note: note_on_confidential_issue)
should_create_todo(user: admin, target: confidential_issue, author: john_doe, action: Todo::MENTIONED, note: note_on_confidential_issue)
should_not_create_todo(user: admin, target: confidential_issue, author: john_doe, action: Todo::MENTIONED, note: note_on_confidential_issue)
should_not_create_todo(user: guest, target: confidential_issue, author: john_doe, action: Todo::MENTIONED, note: note_on_confidential_issue)
should_create_todo(user: john_doe, target: confidential_issue, author: john_doe, action: Todo::MENTIONED, note: note_on_confidential_issue)
end
@ -463,7 +463,7 @@ RSpec.describe TodoService do
should_create_todo(user: author, target: confidential_issue, author: john_doe, action: Todo::DIRECTLY_ADDRESSED, note: addressed_note_on_confidential_issue)
should_create_todo(user: assignee, target: confidential_issue, author: john_doe, action: Todo::DIRECTLY_ADDRESSED, note: addressed_note_on_confidential_issue)
should_create_todo(user: member, target: confidential_issue, author: john_doe, action: Todo::DIRECTLY_ADDRESSED, note: addressed_note_on_confidential_issue)
should_create_todo(user: admin, target: confidential_issue, author: john_doe, action: Todo::DIRECTLY_ADDRESSED, note: addressed_note_on_confidential_issue)
should_not_create_todo(user: admin, target: confidential_issue, author: john_doe, action: Todo::DIRECTLY_ADDRESSED, note: addressed_note_on_confidential_issue)
should_not_create_todo(user: guest, target: confidential_issue, author: john_doe, action: Todo::DIRECTLY_ADDRESSED, note: addressed_note_on_confidential_issue)
should_create_todo(user: john_doe, target: confidential_issue, author: john_doe, action: Todo::DIRECTLY_ADDRESSED, note: addressed_note_on_confidential_issue)
end
@ -699,7 +699,7 @@ RSpec.describe TodoService do
service.new_merge_request(mr_assigned, author)
should_create_todo(user: member, target: mr_assigned, action: Todo::DIRECTLY_ADDRESSED)
should_create_todo(user: admin, target: mr_assigned, action: Todo::MENTIONED)
should_not_create_todo(user: admin, target: mr_assigned, action: Todo::MENTIONED)
end
it 'creates a directly addressed todo for each valid addressed user' do
@ -731,7 +731,7 @@ RSpec.describe TodoService do
service.update_merge_request(mr_assigned, author, skip_users)
should_create_todo(user: member, target: mr_assigned, action: Todo::DIRECTLY_ADDRESSED)
should_create_todo(user: admin, target: mr_assigned, action: Todo::MENTIONED)
should_not_create_todo(user: admin, target: mr_assigned, action: Todo::MENTIONED)
should_not_create_todo(user: skipped, target: mr_assigned)
end
@ -997,7 +997,7 @@ RSpec.describe TodoService do
should_create_todo(user: member, target: noteable, action: Todo::DIRECTLY_ADDRESSED)
should_create_todo(user: guest, target: noteable, action: Todo::MENTIONED)
should_create_todo(user: admin, target: noteable, action: Todo::MENTIONED)
should_not_create_todo(user: admin, target: noteable, action: Todo::MENTIONED)
should_not_create_todo(user: skipped, target: noteable)
end

View File

@ -85,7 +85,7 @@ RSpec.describe TwoFactor::DestroyService do
it_behaves_like 'disables two-factor authentication'
end
context 'admin disables the two-factor authentication of another user' do
context 'admin disables the two-factor authentication of another user', :enable_admin_mode do
let(:current_user) { create(:admin) }
let(:user) { create(:user, :two_factor) }

View File

@ -19,85 +19,96 @@ RSpec.describe Users::ApproveService do
end
end
context 'when user is not in pending approval state' do
let(:user) { create(:user, state: 'active') }
context 'when the executor user is an admin not in admin mode' do
it 'returns error result' do
expect(subject[:status]).to eq(:error)
expect(subject[:message])
.to match(/The user you are trying to approve is not pending an approval/)
expect(subject[:message]).to match(/You are not allowed to approve a user/)
end
end
context 'when user cannot be activated' do
let(:user) do
build(:user, state: 'blocked_pending_approval', email: 'invalid email')
context 'when the executor user is an admin in admin mode', :enable_admin_mode do
context 'when user is not in pending approval state' do
let(:user) { create(:user, state: 'active') }
it 'returns error result' do
expect(subject[:status]).to eq(:error)
expect(subject[:message])
.to match(/The user you are trying to approve is not pending an approval/)
end
end
it 'returns error result' do
expect(subject[:status]).to eq(:error)
expect(subject[:message]).to match(/Email is invalid/)
end
context 'when user cannot be activated' do
let(:user) do
build(:user, state: 'blocked_pending_approval', email: 'invalid email')
end
it 'does not change the state of the user' do
expect { subject }.not_to change { user.state }
it 'returns error result' do
expect(subject[:status]).to eq(:error)
expect(subject[:message]).to match(/Email is invalid/)
end
it 'does not change the state of the user' do
expect { subject }.not_to change { user.state }
end
end
end
end
context 'success' do
it 'activates the user' do
expect(subject[:status]).to eq(:success)
expect(user.reload).to be_active
end
context 'when the executor user is an admin in admin mode', :enable_admin_mode do
it 'activates the user' do
expect(subject[:status]).to eq(:success)
expect(user.reload).to be_active
end
context 'email confirmation status' do
context 'user is unconfirmed' do
let(:user) { create(:user, :blocked_pending_approval, :unconfirmed) }
context 'email confirmation status' do
context 'user is unconfirmed' do
let(:user) { create(:user, :blocked_pending_approval, :unconfirmed) }
it 'sends confirmation instructions' do
expect { subject }
.to have_enqueued_mail(DeviseMailer, :confirmation_instructions)
it 'sends confirmation instructions' do
expect { subject }
.to have_enqueued_mail(DeviseMailer, :confirmation_instructions)
end
end
context 'user is confirmed' do
it 'does not send a confirmation email' do
expect { subject }
.not_to have_enqueued_mail(DeviseMailer, :confirmation_instructions)
end
end
end
context 'user is confirmed' do
it 'does not send a confirmation email' do
expect { subject }
.not_to have_enqueued_mail(DeviseMailer, :confirmation_instructions)
context 'pending invitations' do
let!(:project_member_invite) { create(:project_member, :invited, invite_email: user.email) }
let!(:group_member_invite) { create(:group_member, :invited, invite_email: user.email) }
context 'user is unconfirmed' do
let(:user) { create(:user, :blocked_pending_approval, :unconfirmed) }
it 'does not accept pending invites of the user' do
expect(subject[:status]).to eq(:success)
group_member_invite.reload
project_member_invite.reload
expect(group_member_invite).to be_invite
expect(project_member_invite).to be_invite
end
end
end
end
context 'pending invitiations' do
let!(:project_member_invite) { create(:project_member, :invited, invite_email: user.email) }
let!(:group_member_invite) { create(:group_member, :invited, invite_email: user.email) }
context 'user is confirmed' do
it 'accepts pending invites of the user' do
expect(subject[:status]).to eq(:success)
context 'user is unconfirmed' do
let(:user) { create(:user, :blocked_pending_approval, :unconfirmed) }
group_member_invite.reload
project_member_invite.reload
it 'does not accept pending invites of the user' do
expect(subject[:status]).to eq(:success)
group_member_invite.reload
project_member_invite.reload
expect(group_member_invite).to be_invite
expect(project_member_invite).to be_invite
end
end
context 'user is confirmed' do
it 'accepts pending invites of the user' do
expect(subject[:status]).to eq(:success)
group_member_invite.reload
project_member_invite.reload
expect(group_member_invite).not_to be_invite
expect(project_member_invite).not_to be_invite
expect(group_member_invite.user).to eq(user)
expect(project_member_invite.user).to eq(user)
expect(group_member_invite).not_to be_invite
expect(project_member_invite).not_to be_invite
expect(group_member_invite.user).to eq(user)
expect(project_member_invite.user).to eq(user)
end
end
end
end

View File

@ -3,14 +3,14 @@
require 'spec_helper'
RSpec.describe Users::DestroyService do
describe "Deletes a user and all their personal projects" do
let!(:user) { create(:user) }
let!(:admin) { create(:admin) }
let!(:namespace) { user.namespace }
let!(:project) { create(:project, namespace: namespace) }
let(:service) { described_class.new(admin) }
let(:gitlab_shell) { Gitlab::Shell.new }
let!(:user) { create(:user) }
let!(:admin) { create(:admin) }
let!(:namespace) { user.namespace }
let!(:project) { create(:project, namespace: namespace) }
let(:service) { described_class.new(admin) }
let(:gitlab_shell) { Gitlab::Shell.new }
describe "Deletes a user and all their personal projects", :enable_admin_mode do
context 'no options are given' do
it 'deletes the user' do
user_data = service.execute(user)
@ -215,35 +215,6 @@ RSpec.describe Users::DestroyService do
end
end
context "deletion permission checks" do
it 'does not delete the user when user is not an admin' do
other_user = create(:user)
expect { described_class.new(other_user).execute(user) }.to raise_error(Gitlab::Access::AccessDeniedError)
expect(User.exists?(user.id)).to be(true)
end
it 'allows admins to delete anyone' do
described_class.new(admin).execute(user)
expect(User.exists?(user.id)).to be(false)
end
it 'allows users to delete their own account' do
described_class.new(user).execute(user)
expect(User.exists?(user.id)).to be(false)
end
it 'allows user to be deleted if skip_authorization: true' do
other_user = create(:user)
described_class.new(user).execute(other_user, skip_authorization: true)
expect(User.exists?(other_user.id)).to be(false)
end
end
context "migrating associated records" do
let!(:issue) { create(:issue, author: user) }
@ -320,4 +291,43 @@ RSpec.describe Users::DestroyService do
end
end
end
describe "Deletion permission checks" do
it 'does not delete the user when user is not an admin' do
other_user = create(:user)
expect { described_class.new(other_user).execute(user) }.to raise_error(Gitlab::Access::AccessDeniedError)
expect(User.exists?(user.id)).to be(true)
end
context 'when admin mode is enabled', :enable_admin_mode do
it 'allows admins to delete anyone' do
described_class.new(admin).execute(user)
expect(User.exists?(user.id)).to be(false)
end
end
context 'when admin mode is disabled' do
it 'disallows admins to delete anyone' do
expect { described_class.new(admin).execute(user) }.to raise_error(Gitlab::Access::AccessDeniedError)
expect(User.exists?(user.id)).to be(true)
end
end
it 'allows users to delete their own account' do
described_class.new(user).execute(user)
expect(User.exists?(user.id)).to be(false)
end
it 'allows user to be deleted if skip_authorization: true' do
other_user = create(:user)
described_class.new(user).execute(other_user, skip_authorization: true)
expect(User.exists?(other_user.id)).to be(false)
end
end
end

View File

@ -52,7 +52,7 @@ RSpec.describe Users::SetStatusService do
{ emoji: 'taurus', message: 'a random status', user: target_user }
end
context 'the current user is admin' do
context 'the current user is admin', :enable_admin_mode do
let(:current_user) { create(:admin) }
it 'changes the status when the current user is allowed to do that' do

View File

@ -283,12 +283,10 @@ RSpec.configure do |config|
./ee/spec/lib
./ee/spec/requests/admin
./ee/spec/serializers
./ee/spec/services
./ee/spec/support/protected_tags
./ee/spec/support/shared_examples/features
./ee/spec/support/shared_examples/finders/geo
./ee/spec/support/shared_examples/graphql/geo
./ee/spec/support/shared_examples/services
./spec/features
./spec/finders
./spec/frontend
@ -296,7 +294,6 @@ RSpec.configure do |config|
./spec/lib
./spec/requests
./spec/serializers
./spec/services
./spec/support/protected_tags
./spec/support/shared_examples/features
./spec/support/shared_examples/requests

View File

@ -13,6 +13,8 @@ module AdminModeHelper
def enable_admin_mode!(user)
fake_user_mode = instance_double(Gitlab::Auth::CurrentUserMode)
allow(Gitlab::Auth::CurrentUserMode).to receive(:new).and_call_original
allow(Gitlab::Auth::CurrentUserMode).to receive(:new).with(user).and_return(fake_user_mode)
allow(fake_user_mode).to receive(:admin_mode?).and_return(user&.admin?)
end

View File

@ -3,6 +3,8 @@
RSpec.shared_context 'ProjectPolicyTable context' do
using RSpec::Parameterized::TableSyntax
include AdminModeHelper
let(:pendings) { {} }
let(:pending?) do
pendings.include?(
@ -10,106 +12,117 @@ RSpec.shared_context 'ProjectPolicyTable context' do
project_level: project_level,
feature_access_level: feature_access_level,
membership: membership,
admin_mode: admin_mode,
expected_count: expected_count
}
)
end
# rubocop:disable Metrics/AbcSize
# project_level, :feature_access_level, :membership, :expected_count
# project_level, :feature_access_level, :membership, :admin_mode, :expected_count
def permission_table_for_reporter_feature_access
:public | :enabled | :admin | 1
:public | :enabled | :reporter | 1
:public | :enabled | :guest | 1
:public | :enabled | :non_member | 1
:public | :enabled | :anonymous | 1
:public | :enabled | :admin | true | 1
:public | :enabled | :admin | false | 1
:public | :enabled | :reporter | nil | 1
:public | :enabled | :guest | nil | 1
:public | :enabled | :non_member | nil | 1
:public | :enabled | :anonymous | nil | 1
:public | :private | :admin | 1
:public | :private | :reporter | 1
:public | :private | :guest | 0
:public | :private | :non_member | 0
:public | :private | :anonymous | 0
:public | :private | :admin | true | 1
:public | :private | :admin | false | 0
:public | :private | :reporter | nil | 1
:public | :private | :guest | nil | 0
:public | :private | :non_member | nil | 0
:public | :private | :anonymous | nil | 0
:public | :disabled | :reporter | 0
:public | :disabled | :guest | 0
:public | :disabled | :non_member | 0
:public | :disabled | :anonymous | 0
:public | :disabled | :reporter | nil | 0
:public | :disabled | :guest | nil | 0
:public | :disabled | :non_member | nil | 0
:public | :disabled | :anonymous | nil | 0
:internal | :enabled | :admin | 1
:internal | :enabled | :reporter | 1
:internal | :enabled | :guest | 1
:internal | :enabled | :non_member | 1
:internal | :enabled | :anonymous | 0
:internal | :enabled | :admin | true | 1
:internal | :enabled | :admin | false | 1
:internal | :enabled | :reporter | nil | 1
:internal | :enabled | :guest | nil | 1
:internal | :enabled | :non_member | nil | 1
:internal | :enabled | :anonymous | nil | 0
:internal | :private | :admin | 1
:internal | :private | :reporter | 1
:internal | :private | :guest | 0
:internal | :private | :non_member | 0
:internal | :private | :anonymous | 0
:internal | :private | :admin | true | 1
:internal | :private | :admin | false | 0
:internal | :private | :reporter | nil | 1
:internal | :private | :guest | nil | 0
:internal | :private | :non_member | nil | 0
:internal | :private | :anonymous | nil | 0
:internal | :disabled | :reporter | 0
:internal | :disabled | :guest | 0
:internal | :disabled | :non_member | 0
:internal | :disabled | :anonymous | 0
:internal | :disabled | :reporter | nil | 0
:internal | :disabled | :guest | nil | 0
:internal | :disabled | :non_member | nil | 0
:internal | :disabled | :anonymous | nil | 0
:private | :private | :admin | 1
:private | :private | :reporter | 1
:private | :private | :guest | 0
:private | :private | :non_member | 0
:private | :private | :anonymous | 0
:private | :private | :admin | true | 1
:private | :private | :admin | false | 0
:private | :private | :reporter | nil | 1
:private | :private | :guest | nil | 0
:private | :private | :non_member | nil | 0
:private | :private | :anonymous | nil | 0
:private | :disabled | :reporter | 0
:private | :disabled | :guest | 0
:private | :disabled | :non_member | 0
:private | :disabled | :anonymous | 0
:private | :disabled | :reporter | nil | 0
:private | :disabled | :guest | nil | 0
:private | :disabled | :non_member | nil | 0
:private | :disabled | :anonymous | nil | 0
end
# project_level, :feature_access_level, :membership, :expected_count
# project_level, :feature_access_level, :membership, :admin_mode, :expected_count
def permission_table_for_guest_feature_access
:public | :enabled | :admin | 1
:public | :enabled | :reporter | 1
:public | :enabled | :guest | 1
:public | :enabled | :non_member | 1
:public | :enabled | :anonymous | 1
:public | :enabled | :admin | true | 1
:public | :enabled | :admin | false | 1
:public | :enabled | :reporter | nil | 1
:public | :enabled | :guest | nil | 1
:public | :enabled | :non_member | nil | 1
:public | :enabled | :anonymous | nil | 1
:public | :private | :admin | 1
:public | :private | :reporter | 1
:public | :private | :guest | 1
:public | :private | :non_member | 0
:public | :private | :anonymous | 0
:public | :private | :admin | true | 1
:public | :private | :admin | false | 0
:public | :private | :reporter | nil | 1
:public | :private | :guest | nil | 1
:public | :private | :non_member | nil | 0
:public | :private | :anonymous | nil | 0
:public | :disabled | :reporter | 0
:public | :disabled | :guest | 0
:public | :disabled | :non_member | 0
:public | :disabled | :anonymous | 0
:public | :disabled | :reporter | nil | 0
:public | :disabled | :guest | nil | 0
:public | :disabled | :non_member | nil | 0
:public | :disabled | :anonymous | nil | 0
:internal | :enabled | :admin | 1
:internal | :enabled | :reporter | 1
:internal | :enabled | :guest | 1
:internal | :enabled | :non_member | 1
:internal | :enabled | :anonymous | 0
:internal | :enabled | :admin | true | 1
:internal | :enabled | :admin | false | 1
:internal | :enabled | :reporter | nil | 1
:internal | :enabled | :guest | nil | 1
:internal | :enabled | :non_member | nil | 1
:internal | :enabled | :anonymous | nil | 0
:internal | :private | :admin | 1
:internal | :private | :reporter | 1
:internal | :private | :guest | 1
:internal | :private | :non_member | 0
:internal | :private | :anonymous | 0
:internal | :private | :admin | true | 1
:internal | :private | :admin | false | 0
:internal | :private | :reporter | nil | 1
:internal | :private | :guest | nil | 1
:internal | :private | :non_member | nil | 0
:internal | :private | :anonymous | nil | 0
:internal | :disabled | :reporter | 0
:internal | :disabled | :guest | 0
:internal | :disabled | :non_member | 0
:internal | :disabled | :anonymous | 0
:internal | :disabled | :reporter | nil | 0
:internal | :disabled | :guest | nil | 0
:internal | :disabled | :non_member | nil | 0
:internal | :disabled | :anonymous | nil | 0
:private | :private | :admin | 1
:private | :private | :reporter | 1
:private | :private | :guest | 1
:private | :private | :non_member | 0
:private | :private | :anonymous | 0
:private | :private | :admin | true | 1
:private | :private | :admin | false | 0
:private | :private | :reporter | nil | 1
:private | :private | :guest | nil | 1
:private | :private | :non_member | nil | 0
:private | :private | :anonymous | nil | 0
:private | :disabled | :reporter | 0
:private | :disabled | :guest | 0
:private | :disabled | :non_member | 0
:private | :disabled | :anonymous | 0
:private | :disabled | :reporter | nil | 0
:private | :disabled | :guest | nil | 0
:private | :disabled | :non_member | nil | 0
:private | :disabled | :anonymous | nil | 0
end
# This table is based on permission_table_for_guest_feature_access,
@ -121,184 +134,208 @@ RSpec.shared_context 'ProjectPolicyTable context' do
# e.g. `repository` feature has minimum requirement of GUEST,
# but a GUEST are prohibited from reading code if project is private.
#
# project_level, :feature_access_level, :membership, :expected_count
# project_level, :feature_access_level, :membership, :admin_mode, :expected_count
def permission_table_for_guest_feature_access_and_non_private_project_only
:public | :enabled | :admin | 1
:public | :enabled | :reporter | 1
:public | :enabled | :guest | 1
:public | :enabled | :non_member | 1
:public | :enabled | :anonymous | 1
:public | :enabled | :admin | true | 1
:public | :enabled | :admin | false | 1
:public | :enabled | :reporter | nil | 1
:public | :enabled | :guest | nil | 1
:public | :enabled | :non_member | nil | 1
:public | :enabled | :anonymous | nil | 1
:public | :private | :admin | 1
:public | :private | :reporter | 1
:public | :private | :guest | 1
:public | :private | :non_member | 0
:public | :private | :anonymous | 0
:public | :private | :admin | true | 1
:public | :private | :admin | false | 0
:public | :private | :reporter | nil | 1
:public | :private | :guest | nil | 1
:public | :private | :non_member | nil | 0
:public | :private | :anonymous | nil | 0
:public | :disabled | :reporter | 0
:public | :disabled | :guest | 0
:public | :disabled | :non_member | 0
:public | :disabled | :anonymous | 0
:public | :disabled | :reporter | nil | 0
:public | :disabled | :guest | nil | 0
:public | :disabled | :non_member | nil | 0
:public | :disabled | :anonymous | nil | 0
:internal | :enabled | :admin | 1
:internal | :enabled | :reporter | 1
:internal | :enabled | :guest | 1
:internal | :enabled | :non_member | 1
:internal | :enabled | :anonymous | 0
:internal | :enabled | :admin | true | 1
:internal | :enabled | :admin | false | 1
:internal | :enabled | :reporter | nil | 1
:internal | :enabled | :guest | nil | 1
:internal | :enabled | :non_member | nil | 1
:internal | :enabled | :anonymous | nil | 0
:internal | :private | :admin | 1
:internal | :private | :reporter | 1
:internal | :private | :guest | 1
:internal | :private | :non_member | 0
:internal | :private | :anonymous | 0
:internal | :private | :admin | true | 1
:internal | :private | :admin | false | 0
:internal | :private | :reporter | nil | 1
:internal | :private | :guest | nil | 1
:internal | :private | :non_member | nil | 0
:internal | :private | :anonymous | nil | 0
:internal | :disabled | :reporter | 0
:internal | :disabled | :guest | 0
:internal | :disabled | :non_member | 0
:internal | :disabled | :anonymous | 0
:internal | :disabled | :reporter | nil | 0
:internal | :disabled | :guest | nil | 0
:internal | :disabled | :non_member | nil | 0
:internal | :disabled | :anonymous | nil | 0
:private | :private | :admin | 1
:private | :private | :reporter | 1
:private | :private | :guest | 0
:private | :private | :non_member | 0
:private | :private | :anonymous | 0
:private | :private | :admin | true | 1
:private | :private | :admin | false | 0
:private | :private | :reporter | nil | 1
:private | :private | :guest | nil | 0
:private | :private | :non_member | nil | 0
:private | :private | :anonymous | nil | 0
:private | :disabled | :reporter | 0
:private | :disabled | :guest | 0
:private | :disabled | :non_member | 0
:private | :disabled | :anonymous | 0
:private | :disabled | :reporter | nil | 0
:private | :disabled | :guest | nil | 0
:private | :disabled | :non_member | nil | 0
:private | :disabled | :anonymous | nil | 0
end
# :project_level, :issues_access_level, :merge_requests_access_level, :membership, :expected_count
# :project_level, :issues_access_level, :merge_requests_access_level, :membership, :admin_mode, :expected_count
def permission_table_for_milestone_access
:public | :enabled | :enabled | :admin | 1
:public | :enabled | :enabled | :reporter | 1
:public | :enabled | :enabled | :guest | 1
:public | :enabled | :enabled | :non_member | 1
:public | :enabled | :enabled | :anonymous | 1
:public | :enabled | :enabled | :admin | true | 1
:public | :enabled | :enabled | :admin | false | 1
:public | :enabled | :enabled | :reporter | nil | 1
:public | :enabled | :enabled | :guest | nil | 1
:public | :enabled | :enabled | :non_member | nil | 1
:public | :enabled | :enabled | :anonymous | nil | 1
:public | :enabled | :private | :admin | 1
:public | :enabled | :private | :reporter | 1
:public | :enabled | :private | :guest | 1
:public | :enabled | :private | :non_member | 1
:public | :enabled | :private | :anonymous | 1
:public | :enabled | :private | :admin | true | 1
:public | :enabled | :private | :admin | false | 1
:public | :enabled | :private | :reporter | nil | 1
:public | :enabled | :private | :guest | nil | 1
:public | :enabled | :private | :non_member | nil | 1
:public | :enabled | :private | :anonymous | nil | 1
:public | :enabled | :disabled | :admin | 1
:public | :enabled | :disabled | :reporter | 1
:public | :enabled | :disabled | :guest | 1
:public | :enabled | :disabled | :non_member | 1
:public | :enabled | :disabled | :anonymous | 1
:public | :enabled | :disabled | :admin | true | 1
:public | :enabled | :disabled | :admin | false | 1
:public | :enabled | :disabled | :reporter | nil | 1
:public | :enabled | :disabled | :guest | nil | 1
:public | :enabled | :disabled | :non_member | nil | 1
:public | :enabled | :disabled | :anonymous | nil | 1
:public | :private | :enabled | :admin | 1
:public | :private | :enabled | :reporter | 1
:public | :private | :enabled | :guest | 1
:public | :private | :enabled | :non_member | 1
:public | :private | :enabled | :anonymous | 1
:public | :private | :enabled | :admin | true | 1
:public | :private | :enabled | :admin | false | 1
:public | :private | :enabled | :reporter | nil | 1
:public | :private | :enabled | :guest | nil | 1
:public | :private | :enabled | :non_member | nil | 1
:public | :private | :enabled | :anonymous | nil | 1
:public | :private | :private | :admin | 1
:public | :private | :private | :reporter | 1
:public | :private | :private | :guest | 1
:public | :private | :private | :non_member | 0
:public | :private | :private | :anonymous | 0
:public | :private | :private | :admin | true | 1
:public | :private | :private | :admin | false | 0
:public | :private | :private | :reporter | nil | 1
:public | :private | :private | :guest | nil | 1
:public | :private | :private | :non_member | nil | 0
:public | :private | :private | :anonymous | nil | 0
:public | :private | :disabled | :admin | 1
:public | :private | :disabled | :reporter | 1
:public | :private | :disabled | :guest | 1
:public | :private | :disabled | :non_member | 0
:public | :private | :disabled | :anonymous | 0
:public | :private | :disabled | :admin | true | 1
:public | :private | :disabled | :admin | false | 0
:public | :private | :disabled | :reporter | nil | 1
:public | :private | :disabled | :guest | nil | 1
:public | :private | :disabled | :non_member | nil | 0
:public | :private | :disabled | :anonymous | nil | 0
:public | :disabled | :enabled | :admin | 1
:public | :disabled | :enabled | :reporter | 1
:public | :disabled | :enabled | :guest | 1
:public | :disabled | :enabled | :non_member | 1
:public | :disabled | :enabled | :anonymous | 1
:public | :disabled | :enabled | :admin | true | 1
:public | :disabled | :enabled | :admin | false | 1
:public | :disabled | :enabled | :reporter | nil | 1
:public | :disabled | :enabled | :guest | nil | 1
:public | :disabled | :enabled | :non_member | nil | 1
:public | :disabled | :enabled | :anonymous | nil | 1
:public | :disabled | :private | :admin | 1
:public | :disabled | :private | :reporter | 1
:public | :disabled | :private | :guest | 0
:public | :disabled | :private | :non_member | 0
:public | :disabled | :private | :anonymous | 0
:public | :disabled | :private | :admin | true | 1
:public | :disabled | :private | :admin | false | 0
:public | :disabled | :private | :reporter | nil | 1
:public | :disabled | :private | :guest | nil | 0
:public | :disabled | :private | :non_member | nil | 0
:public | :disabled | :private | :anonymous | nil | 0
:public | :disabled | :disabled | :reporter | 0
:public | :disabled | :disabled | :guest | 0
:public | :disabled | :disabled | :non_member | 0
:public | :disabled | :disabled | :anonymous | 0
:public | :disabled | :disabled | :reporter | nil | 0
:public | :disabled | :disabled | :guest | nil | 0
:public | :disabled | :disabled | :non_member | nil | 0
:public | :disabled | :disabled | :anonymous | nil | 0
:internal | :enabled | :enabled | :admin | 1
:internal | :enabled | :enabled | :reporter | 1
:internal | :enabled | :enabled | :guest | 1
:internal | :enabled | :enabled | :non_member | 1
:internal | :enabled | :enabled | :anonymous | 0
:internal | :enabled | :enabled | :admin | true | 1
:internal | :enabled | :enabled | :admin | false | 1
:internal | :enabled | :enabled | :reporter | nil | 1
:internal | :enabled | :enabled | :guest | nil | 1
:internal | :enabled | :enabled | :non_member | nil | 1
:internal | :enabled | :enabled | :anonymous | nil | 0
:internal | :enabled | :private | :admin | 1
:internal | :enabled | :private | :reporter | 1
:internal | :enabled | :private | :guest | 1
:internal | :enabled | :private | :non_member | 1
:internal | :enabled | :private | :anonymous | 0
:internal | :enabled | :private | :admin | true | 1
:internal | :enabled | :private | :admin | false | 1
:internal | :enabled | :private | :reporter | nil | 1
:internal | :enabled | :private | :guest | nil | 1
:internal | :enabled | :private | :non_member | nil | 1
:internal | :enabled | :private | :anonymous | nil | 0
:internal | :enabled | :disabled | :admin | 1
:internal | :enabled | :disabled | :reporter | 1
:internal | :enabled | :disabled | :guest | 1
:internal | :enabled | :disabled | :non_member | 1
:internal | :enabled | :disabled | :anonymous | 0
:internal | :enabled | :disabled | :admin | true | 1
:internal | :enabled | :disabled | :admin | false | 1
:internal | :enabled | :disabled | :reporter | nil | 1
:internal | :enabled | :disabled | :guest | nil | 1
:internal | :enabled | :disabled | :non_member | nil | 1
:internal | :enabled | :disabled | :anonymous | nil | 0
:internal | :private | :enabled | :admin | 1
:internal | :private | :enabled | :reporter | 1
:internal | :private | :enabled | :guest | 1
:internal | :private | :enabled | :non_member | 1
:internal | :private | :enabled | :anonymous | 0
:internal | :private | :enabled | :admin | true | 1
:internal | :private | :enabled | :admin | false | 1
:internal | :private | :enabled | :reporter | nil | 1
:internal | :private | :enabled | :guest | nil | 1
:internal | :private | :enabled | :non_member | nil | 1
:internal | :private | :enabled | :anonymous | nil | 0
:internal | :private | :private | :admin | 1
:internal | :private | :private | :reporter | 1
:internal | :private | :private | :guest | 1
:internal | :private | :private | :non_member | 0
:internal | :private | :private | :anonymous | 0
:internal | :private | :private | :admin | true | 1
:internal | :private | :private | :admin | false | 0
:internal | :private | :private | :reporter | nil | 1
:internal | :private | :private | :guest | nil | 1
:internal | :private | :private | :non_member | nil | 0
:internal | :private | :private | :anonymous | nil | 0
:internal | :private | :disabled | :admin | 1
:internal | :private | :disabled | :reporter | 1
:internal | :private | :disabled | :guest | 1
:internal | :private | :disabled | :non_member | 0
:internal | :private | :disabled | :anonymous | 0
:internal | :private | :disabled | :admin | true | 1
:internal | :private | :disabled | :admin | false | 0
:internal | :private | :disabled | :reporter | nil | 1
:internal | :private | :disabled | :guest | nil | 1
:internal | :private | :disabled | :non_member | nil | 0
:internal | :private | :disabled | :anonymous | nil | 0
:internal | :disabled | :enabled | :admin | 1
:internal | :disabled | :enabled | :reporter | 1
:internal | :disabled | :enabled | :guest | 1
:internal | :disabled | :enabled | :non_member | 1
:internal | :disabled | :enabled | :anonymous | 0
:internal | :disabled | :enabled | :admin | true | 1
:internal | :disabled | :enabled | :admin | false | 1
:internal | :disabled | :enabled | :reporter | nil | 1
:internal | :disabled | :enabled | :guest | nil | 1
:internal | :disabled | :enabled | :non_member | nil | 1
:internal | :disabled | :enabled | :anonymous | nil | 0
:internal | :disabled | :private | :admin | 1
:internal | :disabled | :private | :reporter | 1
:internal | :disabled | :private | :guest | 0
:internal | :disabled | :private | :non_member | 0
:internal | :disabled | :private | :anonymous | 0
:internal | :disabled | :private | :admin | true | 1
:internal | :disabled | :private | :admin | false | 0
:internal | :disabled | :private | :reporter | nil | 1
:internal | :disabled | :private | :guest | nil | 0
:internal | :disabled | :private | :non_member | nil | 0
:internal | :disabled | :private | :anonymous | nil | 0
:internal | :disabled | :disabled | :reporter | 0
:internal | :disabled | :disabled | :guest | 0
:internal | :disabled | :disabled | :non_member | 0
:internal | :disabled | :disabled | :anonymous | 0
:internal | :disabled | :disabled | :reporter | nil | 0
:internal | :disabled | :disabled | :guest | nil | 0
:internal | :disabled | :disabled | :non_member | nil | 0
:internal | :disabled | :disabled | :anonymous | nil | 0
:private | :private | :private | :admin | 1
:private | :private | :private | :reporter | 1
:private | :private | :private | :guest | 1
:private | :private | :private | :non_member | 0
:private | :private | :private | :anonymous | 0
:private | :private | :private | :admin | true | 1
:private | :private | :private | :admin | false | 0
:private | :private | :private | :reporter | nil | 1
:private | :private | :private | :guest | nil | 1
:private | :private | :private | :non_member | nil | 0
:private | :private | :private | :anonymous | nil | 0
:private | :private | :disabled | :admin | 1
:private | :private | :disabled | :reporter | 1
:private | :private | :disabled | :guest | 1
:private | :private | :disabled | :non_member | 0
:private | :private | :disabled | :anonymous | 0
:private | :private | :disabled | :admin | true | 1
:private | :private | :disabled | :admin | false | 0
:private | :private | :disabled | :reporter | nil | 1
:private | :private | :disabled | :guest | nil | 1
:private | :private | :disabled | :non_member | nil | 0
:private | :private | :disabled | :anonymous | nil | 0
:private | :disabled | :private | :admin | 1
:private | :disabled | :private | :reporter | 1
:private | :disabled | :private | :guest | 0
:private | :disabled | :private | :non_member | 0
:private | :disabled | :private | :anonymous | 0
:private | :disabled | :private | :admin | true | 1
:private | :disabled | :private | :admin | false | 0
:private | :disabled | :private | :reporter | nil | 1
:private | :disabled | :private | :guest | nil | 0
:private | :disabled | :private | :non_member | nil | 0
:private | :disabled | :private | :anonymous | nil | 0
:private | :disabled | :disabled | :reporter | 0
:private | :disabled | :disabled | :guest | 0
:private | :disabled | :disabled | :non_member | 0
:private | :disabled | :disabled | :anonymous | 0
:private | :disabled | :disabled | :reporter | nil | 0
:private | :disabled | :disabled | :guest | nil | 0
:private | :disabled | :disabled | :non_member | nil | 0
:private | :disabled | :disabled | :anonymous | nil | 0
end
# :project_level, :membership, :expected_count
@ -321,166 +358,192 @@ RSpec.shared_context 'ProjectPolicyTable context' do
# :snippet_level, :project_level, :feature_access_level, :membership, :expected_count
def permission_table_for_project_snippet_access
:public | :public | :enabled | :admin | 1
:public | :public | :enabled | :reporter | 1
:public | :public | :enabled | :guest | 1
:public | :public | :enabled | :non_member | 1
:public | :public | :enabled | :anonymous | 1
:public | :public | :enabled | :admin | true | 1
:public | :public | :enabled | :admin | false | 1
:public | :public | :enabled | :reporter | nil | 1
:public | :public | :enabled | :guest | nil | 1
:public | :public | :enabled | :non_member | nil | 1
:public | :public | :enabled | :anonymous | nil | 1
:public | :public | :private | :admin | 1
:public | :public | :private | :reporter | 1
:public | :public | :private | :guest | 1
:public | :public | :private | :non_member | 0
:public | :public | :private | :anonymous | 0
:public | :public | :private | :admin | true | 1
:public | :public | :private | :admin | false | 0
:public | :public | :private | :reporter | nil | 1
:public | :public | :private | :guest | nil | 1
:public | :public | :private | :non_member | nil | 0
:public | :public | :private | :anonymous | nil | 0
:public | :public | :disabled | :admin | 1
:public | :public | :disabled | :reporter | 0
:public | :public | :disabled | :guest | 0
:public | :public | :disabled | :non_member | 0
:public | :public | :disabled | :anonymous | 0
:public | :public | :disabled | :admin | true | 1
:public | :public | :disabled | :admin | false | 0
:public | :public | :disabled | :reporter | nil | 0
:public | :public | :disabled | :guest | nil | 0
:public | :public | :disabled | :non_member | nil | 0
:public | :public | :disabled | :anonymous | nil | 0
:public | :internal | :enabled | :admin | 1
:public | :internal | :enabled | :reporter | 1
:public | :internal | :enabled | :guest | 1
:public | :internal | :enabled | :non_member | 1
:public | :internal | :enabled | :anonymous | 0
:public | :internal | :enabled | :admin | true | 1
:public | :internal | :enabled | :admin | false | 1
:public | :internal | :enabled | :reporter | nil | 1
:public | :internal | :enabled | :guest | nil | 1
:public | :internal | :enabled | :non_member | nil | 1
:public | :internal | :enabled | :anonymous | nil | 0
:public | :internal | :private | :admin | 1
:public | :internal | :private | :reporter | 1
:public | :internal | :private | :guest | 1
:public | :internal | :private | :non_member | 0
:public | :internal | :private | :anonymous | 0
:public | :internal | :private | :admin | true | 1
:public | :internal | :private | :admin | false | 0
:public | :internal | :private | :reporter | nil | 1
:public | :internal | :private | :guest | nil | 1
:public | :internal | :private | :non_member | nil | 0
:public | :internal | :private | :anonymous | nil | 0
:public | :internal | :disabled | :admin | 1
:public | :internal | :disabled | :reporter | 0
:public | :internal | :disabled | :guest | 0
:public | :internal | :disabled | :non_member | 0
:public | :internal | :disabled | :anonymous | 0
:public | :internal | :disabled | :admin | true | 1
:public | :internal | :disabled | :admin | false | 0
:public | :internal | :disabled | :reporter | nil | 0
:public | :internal | :disabled | :guest | nil | 0
:public | :internal | :disabled | :non_member | nil | 0
:public | :internal | :disabled | :anonymous | nil | 0
:public | :private | :private | :admin | 1
:public | :private | :private | :reporter | 1
:public | :private | :private | :guest | 1
:public | :private | :private | :non_member | 0
:public | :private | :private | :anonymous | 0
:public | :private | :private | :admin | true | 1
:public | :private | :private | :admin | false | 0
:public | :private | :private | :reporter | nil | 1
:public | :private | :private | :guest | nil | 1
:public | :private | :private | :non_member | nil | 0
:public | :private | :private | :anonymous | nil | 0
:public | :private | :disabled | :reporter | 0
:public | :private | :disabled | :guest | 0
:public | :private | :disabled | :non_member | 0
:public | :private | :disabled | :anonymous | 0
:public | :private | :disabled | :reporter | nil | 0
:public | :private | :disabled | :guest | nil | 0
:public | :private | :disabled | :non_member | nil | 0
:public | :private | :disabled | :anonymous | nil | 0
:internal | :public | :enabled | :admin | 1
:internal | :public | :enabled | :reporter | 1
:internal | :public | :enabled | :guest | 1
:internal | :public | :enabled | :non_member | 1
:internal | :public | :enabled | :anonymous | 0
:internal | :public | :enabled | :admin | true | 1
:internal | :public | :enabled | :admin | false | 1
:internal | :public | :enabled | :reporter | nil | 1
:internal | :public | :enabled | :guest | nil | 1
:internal | :public | :enabled | :non_member | nil | 1
:internal | :public | :enabled | :anonymous | nil | 0
:internal | :public | :private | :admin | 1
:internal | :public | :private | :reporter | 1
:internal | :public | :private | :guest | 1
:internal | :public | :private | :non_member | 0
:internal | :public | :private | :anonymous | 0
:internal | :public | :private | :admin | true | 1
:internal | :public | :private | :admin | false | 0
:internal | :public | :private | :reporter | nil | 1
:internal | :public | :private | :guest | nil | 1
:internal | :public | :private | :non_member | nil | 0
:internal | :public | :private | :anonymous | nil | 0
:internal | :public | :disabled | :admin | 1
:internal | :public | :disabled | :reporter | 0
:internal | :public | :disabled | :guest | 0
:internal | :public | :disabled | :non_member | 0
:internal | :public | :disabled | :anonymous | 0
:internal | :public | :disabled | :admin | true | 1
:internal | :public | :disabled | :admin | false | 0
:internal | :public | :disabled | :reporter | nil | 0
:internal | :public | :disabled | :guest | nil | 0
:internal | :public | :disabled | :non_member | nil | 0
:internal | :public | :disabled | :anonymous | nil | 0
:internal | :internal | :enabled | :admin | 1
:internal | :internal | :enabled | :reporter | 1
:internal | :internal | :enabled | :guest | 1
:internal | :internal | :enabled | :non_member | 1
:internal | :internal | :enabled | :anonymous | 0
:internal | :internal | :enabled | :admin | true | 1
:internal | :internal | :enabled | :admin | false | 1
:internal | :internal | :enabled | :reporter | nil | 1
:internal | :internal | :enabled | :guest | nil | 1
:internal | :internal | :enabled | :non_member | nil | 1
:internal | :internal | :enabled | :anonymous | nil | 0
:internal | :internal | :private | :admin | 1
:internal | :internal | :private | :reporter | 1
:internal | :internal | :private | :guest | 1
:internal | :internal | :private | :non_member | 0
:internal | :internal | :private | :anonymous | 0
:internal | :internal | :private | :admin | true | 1
:internal | :internal | :private | :admin | false | 0
:internal | :internal | :private | :reporter | nil | 1
:internal | :internal | :private | :guest | nil | 1
:internal | :internal | :private | :non_member | nil | 0
:internal | :internal | :private | :anonymous | nil | 0
:internal | :internal | :disabled | :admin | 1
:internal | :internal | :disabled | :reporter | 0
:internal | :internal | :disabled | :guest | 0
:internal | :internal | :disabled | :non_member | 0
:internal | :internal | :disabled | :anonymous | 0
:internal | :internal | :disabled | :admin | true | 1
:internal | :internal | :disabled | :admin | false | 0
:internal | :internal | :disabled | :reporter | nil | 0
:internal | :internal | :disabled | :guest | nil | 0
:internal | :internal | :disabled | :non_member | nil | 0
:internal | :internal | :disabled | :anonymous | nil | 0
:internal | :private | :private | :admin | 1
:internal | :private | :private | :reporter | 1
:internal | :private | :private | :guest | 1
:internal | :private | :private | :non_member | 0
:internal | :private | :private | :anonymous | 0
:internal | :private | :private | :admin | true | 1
:internal | :private | :private | :admin | false | 0
:internal | :private | :private | :reporter | nil | 1
:internal | :private | :private | :guest | nil | 1
:internal | :private | :private | :non_member | nil | 0
:internal | :private | :private | :anonymous | nil | 0
:internal | :private | :disabled | :admin | 1
:internal | :private | :disabled | :reporter | 0
:internal | :private | :disabled | :guest | 0
:internal | :private | :disabled | :non_member | 0
:internal | :private | :disabled | :anonymous | 0
:internal | :private | :disabled | :admin | true | 1
:internal | :private | :disabled | :admin | false | 0
:internal | :private | :disabled | :reporter | nil | 0
:internal | :private | :disabled | :guest | nil | 0
:internal | :private | :disabled | :non_member | nil | 0
:internal | :private | :disabled | :anonymous | nil | 0
:private | :public | :enabled | :admin | 1
:private | :public | :enabled | :reporter | 1
:private | :public | :enabled | :guest | 1
:private | :public | :enabled | :non_member | 0
:private | :public | :enabled | :anonymous | 0
:private | :public | :enabled | :admin | true | 1
:private | :public | :enabled | :admin | false | 0
:private | :public | :enabled | :reporter | nil | 1
:private | :public | :enabled | :guest | nil | 1
:private | :public | :enabled | :non_member | nil | 0
:private | :public | :enabled | :anonymous | nil | 0
:private | :public | :private | :admin | 1
:private | :public | :private | :reporter | 1
:private | :public | :private | :guest | 1
:private | :public | :private | :non_member | 0
:private | :public | :private | :anonymous | 0
:private | :public | :private | :admin | true | 1
:private | :public | :private | :admin | false | 0
:private | :public | :private | :reporter | nil | 1
:private | :public | :private | :guest | nil | 1
:private | :public | :private | :non_member | nil | 0
:private | :public | :private | :anonymous | nil | 0
:private | :public | :disabled | :admin | 1
:private | :public | :disabled | :reporter | 0
:private | :public | :disabled | :guest | 0
:private | :public | :disabled | :non_member | 0
:private | :public | :disabled | :anonymous | 0
:private | :public | :disabled | :admin | true | 1
:private | :public | :disabled | :admin | false | 0
:private | :public | :disabled | :reporter | nil | 0
:private | :public | :disabled | :guest | nil | 0
:private | :public | :disabled | :non_member | nil | 0
:private | :public | :disabled | :anonymous | nil | 0
:private | :internal | :enabled | :admin | 1
:private | :internal | :enabled | :reporter | 1
:private | :internal | :enabled | :guest | 1
:private | :internal | :enabled | :non_member | 0
:private | :internal | :enabled | :anonymous | 0
:private | :internal | :enabled | :admin | true | 1
:private | :internal | :enabled | :admin | false | 0
:private | :internal | :enabled | :reporter | nil | 1
:private | :internal | :enabled | :guest | nil | 1
:private | :internal | :enabled | :non_member | nil | 0
:private | :internal | :enabled | :anonymous | nil | 0
:private | :internal | :private | :admin | 1
:private | :internal | :private | :reporter | 1
:private | :internal | :private | :guest | 1
:private | :internal | :private | :non_member | 0
:private | :internal | :private | :anonymous | 0
:private | :internal | :private | :admin | true | 1
:private | :internal | :private | :admin | false | 0
:private | :internal | :private | :reporter | nil | 1
:private | :internal | :private | :guest | nil | 1
:private | :internal | :private | :non_member | nil | 0
:private | :internal | :private | :anonymous | nil | 0
:private | :internal | :disabled | :admin | 1
:private | :internal | :disabled | :reporter | 0
:private | :internal | :disabled | :guest | 0
:private | :internal | :disabled | :non_member | 0
:private | :internal | :disabled | :anonymous | 0
:private | :internal | :disabled | :admin | true | 1
:private | :internal | :disabled | :admin | false | 0
:private | :internal | :disabled | :reporter | nil | 0
:private | :internal | :disabled | :guest | nil | 0
:private | :internal | :disabled | :non_member | nil | 0
:private | :internal | :disabled | :anonymous | nil | 0
:private | :private | :private | :admin | 1
:private | :private | :private | :reporter | 1
:private | :private | :private | :guest | 1
:private | :private | :private | :non_member | 0
:private | :private | :private | :anonymous | 0
:private | :private | :private | :admin | true | 1
:private | :private | :private | :admin | false | 0
:private | :private | :private | :reporter | nil | 1
:private | :private | :private | :guest | nil | 1
:private | :private | :private | :non_member | nil | 0
:private | :private | :private | :anonymous | nil | 0
:private | :private | :disabled | :admin | 1
:private | :private | :disabled | :reporter | 0
:private | :private | :disabled | :guest | 0
:private | :private | :disabled | :non_member | 0
:private | :private | :disabled | :anonymous | 0
:private | :private | :disabled | :admin | true | 1
:private | :private | :disabled | :admin | false | 0
:private | :private | :disabled | :reporter | nil | 0
:private | :private | :disabled | :guest | nil | 0
:private | :private | :disabled | :non_member | nil | 0
:private | :private | :disabled | :anonymous | nil | 0
end
# :snippet_level, :membership, :expected_count
def permission_table_for_personal_snippet_access
:public | :admin | 1
:public | :author | 1
:public | :non_member | 1
:public | :anonymous | 1
:public | :admin | true | 1
:public | :admin | false | 1
:public | :author | nil | 1
:public | :non_member | nil | 1
:public | :anonymous | nil | 1
:internal | :admin | 1
:internal | :author | 1
:internal | :non_member | 1
:internal | :anonymous | 0
:internal | :admin | true | 1
:internal | :admin | false | 1
:internal | :author | nil | 1
:internal | :non_member | nil | 1
:internal | :anonymous | nil | 0
:private | :admin | 1
:private | :author | 1
:private | :non_member | 0
:private | :anonymous | 0
:private | :admin | true | 1
:private | :admin | false | 0
:private | :author | nil | 1
:private | :non_member | nil | 0
:private | :anonymous | nil | 0
end
# rubocop:enable Metrics/AbcSize
end

View File

@ -0,0 +1,22 @@
# frozen_string_literal: true
RSpec.shared_context 'npm api setup' do
include PackagesManagerApiSpecHelpers
include HttpBasicAuthHelpers
let_it_be(:user) { create(:user) }
let_it_be(:group) { create(:group) }
let_it_be(:project, reload: true) { create(:project, :public, namespace: group) }
let_it_be(:package, reload: true) { create(:npm_package, project: project) }
let_it_be(:token) { create(:oauth_access_token, scopes: 'api', resource_owner: user) }
let_it_be(:personal_access_token) { create(:personal_access_token, user: user) }
let_it_be(:job, reload: true) { create(:ci_build, user: user, status: :running) }
let_it_be(:deploy_token) { create(:deploy_token, read_package_registry: true, write_package_registry: true) }
let_it_be(:project_deploy_token) { create(:project_deploy_token, deploy_token: deploy_token, project: project) }
let(:package_name) { package.name }
before do
project.add_developer(user)
end
end

View File

@ -0,0 +1,270 @@
# frozen_string_literal: true
RSpec.shared_examples 'handling get metadata requests' do
let_it_be(:package_dependency_link1) { create(:packages_dependency_link, package: package, dependency_type: :dependencies) }
let_it_be(:package_dependency_link2) { create(:packages_dependency_link, package: package, dependency_type: :devDependencies) }
let_it_be(:package_dependency_link3) { create(:packages_dependency_link, package: package, dependency_type: :bundleDependencies) }
let_it_be(:package_dependency_link4) { create(:packages_dependency_link, package: package, dependency_type: :peerDependencies) }
let(:params) { {} }
let(:headers) { {} }
subject { get(url, params: params, headers: headers) }
shared_examples 'returning the npm package info' do
it 'returns the package info' do
subject
expect_a_valid_package_response
end
end
shared_examples 'a package that requires auth' do
it 'denies request without oauth token' do
subject
expect(response).to have_gitlab_http_status(:not_found)
end
context 'with oauth token' do
let(:params) { { access_token: token.token } }
it 'returns the package info with oauth token' do
subject
expect_a_valid_package_response
end
end
context 'with job token' do
let(:params) { { job_token: job.token } }
it 'returns the package info with running job token' do
subject
expect_a_valid_package_response
end
it 'denies request without running job token' do
job.update!(status: :success)
subject
expect(response).to have_gitlab_http_status(:unauthorized)
end
end
context 'with deploy token' do
let(:headers) { build_token_auth_header(deploy_token.token) }
it 'returns the package info with deploy token' do
subject
expect_a_valid_package_response
end
end
end
context 'a public project' do
it_behaves_like 'returning the npm package info'
context 'project path with a dot' do
before do
project.update!(path: 'foo.bar')
end
it_behaves_like 'returning the npm package info'
end
context 'with request forward disabled' do
before do
stub_application_setting(npm_package_requests_forwarding: false)
end
it_behaves_like 'returning the npm package info'
context 'with unknown package' do
let(:package_name) { 'unknown' }
it 'returns the proper response' do
subject
expect(response).to have_gitlab_http_status(:not_found)
end
end
end
context 'with request forward enabled' do
before do
stub_application_setting(npm_package_requests_forwarding: true)
end
it_behaves_like 'returning the npm package info'
context 'with unknown package' do
let(:package_name) { 'unknown' }
it 'returns a redirect' do
subject
expect(response).to have_gitlab_http_status(:found)
expect(response.headers['Location']).to eq('https://registry.npmjs.org/unknown')
end
it_behaves_like 'a gitlab tracking event', described_class.name, 'npm_request_forward'
end
end
end
context 'internal project' do
before do
project.update!(visibility_level: Gitlab::VisibilityLevel::INTERNAL)
end
it_behaves_like 'a package that requires auth'
end
context 'private project' do
before do
project.update!(visibility_level: Gitlab::VisibilityLevel::PRIVATE)
end
it_behaves_like 'a package that requires auth'
context 'with guest' do
let(:params) { { access_token: token.token } }
it 'denies request when not enough permissions' do
project.add_guest(user)
subject
expect(response).to have_gitlab_http_status(:forbidden)
end
end
end
def expect_a_valid_package_response
expect(response).to have_gitlab_http_status(:ok)
expect(response.media_type).to eq('application/json')
expect(response).to match_response_schema('public_api/v4/packages/npm_package')
expect(json_response['name']).to eq(package.name)
expect(json_response['versions'][package.version]).to match_schema('public_api/v4/packages/npm_package_version')
::Packages::Npm::PackagePresenter::NPM_VALID_DEPENDENCY_TYPES.each do |dependency_type|
expect(json_response.dig('versions', package.version, dependency_type.to_s)).to be_any
end
expect(json_response['dist-tags']).to match_schema('public_api/v4/packages/npm_package_tags')
end
end
RSpec.shared_examples 'handling get dist tags requests' do
let_it_be(:package_tag1) { create(:packages_tag, package: package) }
let_it_be(:package_tag2) { create(:packages_tag, package: package) }
let(:params) { {} }
subject { get(url, params: params) }
context 'with public project' do
context 'with authenticated user' do
let(:params) { { private_token: personal_access_token.token } }
it_behaves_like 'returns package tags', :maintainer
it_behaves_like 'returns package tags', :developer
it_behaves_like 'returns package tags', :reporter
it_behaves_like 'returns package tags', :guest
end
context 'with unauthenticated user' do
it_behaves_like 'returns package tags', :no_type
end
end
context 'with private project' do
before do
project.update!(visibility_level: Gitlab::VisibilityLevel::PRIVATE)
end
context 'with authenticated user' do
let(:params) { { private_token: personal_access_token.token } }
it_behaves_like 'returns package tags', :maintainer
it_behaves_like 'returns package tags', :developer
it_behaves_like 'returns package tags', :reporter
it_behaves_like 'rejects package tags access', :guest, :forbidden
end
context 'with unauthenticated user' do
it_behaves_like 'rejects package tags access', :no_type, :not_found
end
end
end
RSpec.shared_examples 'handling create dist tag requests' do
let_it_be(:tag_name) { 'test' }
let(:params) { {} }
let(:env) { {} }
let(:version) { package.version }
subject { put(url, env: env, params: params) }
context 'with public project' do
context 'with authenticated user' do
let(:params) { { private_token: personal_access_token.token } }
let(:env) { { 'api.request.body': version } }
it_behaves_like 'create package tag', :maintainer
it_behaves_like 'create package tag', :developer
it_behaves_like 'rejects package tags access', :reporter, :forbidden
it_behaves_like 'rejects package tags access', :guest, :forbidden
end
context 'with unauthenticated user' do
it_behaves_like 'rejects package tags access', :no_type, :unauthorized
end
end
end
RSpec.shared_examples 'handling delete dist tag requests' do
let_it_be(:package_tag) { create(:packages_tag, package: package) }
let(:params) { {} }
let(:tag_name) { package_tag.name }
subject { delete(url, params: params) }
context 'with public project' do
context 'with authenticated user' do
let(:params) { { private_token: personal_access_token.token } }
it_behaves_like 'delete package tag', :maintainer
it_behaves_like 'rejects package tags access', :developer, :forbidden
it_behaves_like 'rejects package tags access', :reporter, :forbidden
it_behaves_like 'rejects package tags access', :guest, :forbidden
end
context 'with unauthenticated user' do
it_behaves_like 'rejects package tags access', :no_type, :unauthorized
end
end
context 'with private project' do
before do
project.update!(visibility_level: Gitlab::VisibilityLevel::PRIVATE)
end
context 'with authenticated user' do
let(:params) { { private_token: personal_access_token.token } }
it_behaves_like 'delete package tag', :maintainer
it_behaves_like 'rejects package tags access', :developer, :forbidden
it_behaves_like 'rejects package tags access', :reporter, :forbidden
it_behaves_like 'rejects package tags access', :guest, :forbidden
end
context 'with unauthenticated user' do
it_behaves_like 'rejects package tags access', :no_type, :unauthorized
end
end
end

Some files were not shown because too many files have changed in this diff Show More