Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2021-05-25 15:10:33 +00:00
parent 76ef00aac9
commit a8c1bc6f75
73 changed files with 1366 additions and 670 deletions

View File

@ -39,7 +39,7 @@ review-build-cng:
.review-workflow-base:
extends:
- .default-retry
image: registry.gitlab.com/gitlab-org/gitlab-build-images:gitlab-helm3-kubectl1.14
image: registry.gitlab.com/gitlab-org/gitlab-build-images:gitlab-helm3.5-kubectl1.17
variables:
HOST_SUFFIX: "${CI_ENVIRONMENT_SLUG}"
DOMAIN: "-${CI_ENVIRONMENT_SLUG}.${REVIEW_APPS_DOMAIN}"

View File

@ -60,7 +60,9 @@ const createFlashEl = (message, type) => `
`;
const removeFlashClickListener = (flashEl, fadeTransition) => {
getCloseEl(flashEl).addEventListener('click', () => hideFlash(flashEl, fadeTransition));
// There are some flash elements which do not have a closeEl.
// https://gitlab.com/gitlab-org/gitlab/blob/763426ef344488972eb63ea5be8744e0f8459e6b/ee/app/views/layouts/header/_read_only_banner.html.haml
getCloseEl(flashEl)?.addEventListener('click', () => hideFlash(flashEl, fadeTransition));
};
/*

View File

@ -72,11 +72,13 @@ export function bytesToGiB(number) {
* @returns {String}
*/
export function numberToHumanSize(size) {
if (size < BYTES_IN_KIB) {
const abs = Math.abs(size);
if (abs < BYTES_IN_KIB) {
return sprintf(__('%{size} bytes'), { size });
} else if (size < BYTES_IN_KIB * BYTES_IN_KIB) {
} else if (abs < BYTES_IN_KIB ** 2) {
return sprintf(__('%{size} KiB'), { size: bytesToKiB(size).toFixed(2) });
} else if (size < BYTES_IN_KIB * BYTES_IN_KIB * BYTES_IN_KIB) {
} else if (abs < BYTES_IN_KIB ** 3) {
return sprintf(__('%{size} MiB'), { size: bytesToMiB(size).toFixed(2) });
}
return sprintf(__('%{size} GiB'), { size: bytesToGiB(size).toFixed(2) });

View File

@ -510,7 +510,7 @@ class ProjectsController < Projects::ApplicationController
# `project` calls `find_routable!`, so this will trigger the usual not-found
# behaviour when the user isn't authorized to see the project
return unless project
return if project.nil? || performed?
redirect_to(request.original_url.sub(%r{\.git/?\Z}, ''))
end

View File

@ -26,9 +26,9 @@ module Packages
def base
if project?
packages_for_project(@project_or_group)
project_packages
elsif group?
packages_visible_to_user(@current_user, within_group: @project_or_group)
group_packages
else
::Packages::Package.none
end
@ -41,5 +41,13 @@ module Packages
def group?
@project_or_group.is_a?(::Group)
end
def project_packages
packages_for_project(@project_or_group)
end
def group_packages
packages_visible_to_user(@current_user, within_group: @project_or_group)
end
end
end

View File

@ -12,6 +12,16 @@ module Packages
def packages
base.pypi.has_version
end
def group_packages
# PyPI finds packages without checking permissions.
# The package download endpoint uses obfuscation to secure the file
# instead of authentication. This is behavior the PyPI package
# manager defines and is not something GitLab controls.
::Packages::Package.for_projects(
@project_or_group.all_projects.select(:id)
).installable
end
end
end
end

View File

@ -275,12 +275,6 @@ class Namespace < ApplicationRecord
Project.where(namespace: namespace)
end
# Includes pipelines from this namespace and pipelines from all subgroups
# that belongs to this namespace
def all_pipelines
Ci::Pipeline.where(project: all_projects)
end
def has_parent?
parent_id.present? || parent.present?
end

View File

@ -636,6 +636,12 @@ class Project < ApplicationRecord
scope :with_tracing_enabled, -> { joins(:tracing_setting) }
scope :with_enabled_error_tracking, -> { joins(:error_tracking_setting).where(project_error_tracking_settings: { enabled: true }) }
scope :with_service_desk_key, -> (key) do
# project_key is not indexed for now
# see https://gitlab.com/gitlab-org/gitlab/-/merge_requests/24063#note_282435524 for details
joins(:service_desk_setting).where('service_desk_settings.project_key' => key)
end
enum auto_cancel_pending_pipelines: { disabled: 0, enabled: 1 }
chronic_duration_attr :build_timeout_human_readable, :build_timeout,
@ -837,12 +843,6 @@ class Project < ApplicationRecord
from_union([with_issues_enabled, with_merge_requests_enabled]).select(:id)
end
def find_by_service_desk_project_key(key)
# project_key is not indexed for now
# see https://gitlab.com/gitlab-org/gitlab/-/merge_requests/24063#note_282435524 for details
joins(:service_desk_setting).find_by('service_desk_settings.project_key' => key)
end
end
def initialize(attributes = nil)

View File

@ -7,9 +7,9 @@ module Packages
class PackagePresenter
include API::Helpers::RelatedResourcesHelpers
def initialize(packages, project)
def initialize(packages, project_or_group)
@packages = packages
@project = project
@project_or_group = project_or_group
end
# Returns the HTML body for PyPI simple API.
@ -51,16 +51,27 @@ module Packages
end
def build_pypi_package_path(file)
expose_url(
api_v4_projects_packages_pypi_files_file_identifier_path(
{
id: @project.id,
sha256: file.file_sha256,
file_identifier: file.file_name
},
true
)
) + "#sha256=#{file.file_sha256}"
params = {
id: @project_or_group.id,
sha256: file.file_sha256,
file_identifier: file.file_name
}
if project?
expose_url(
api_v4_projects_packages_pypi_files_file_identifier_path(
params, true
)
) + "#sha256=#{file.file_sha256}"
elsif group?
expose_url(
api_v4_groups___packages_pypi_files_file_identifier_path(
params, true
)
) + "#sha256=#{file.file_sha256}"
else
''
end
end
def name
@ -70,6 +81,14 @@ module Packages
def escape(str)
ERB::Util.html_escape(str)
end
def project?
@project_or_group.is_a?(::Project)
end
def group?
@project_or_group.is_a?(::Group)
end
end
end
end

View File

@ -21,6 +21,15 @@
:weight: 1
:idempotent: true
:tags: []
- :name: authorized_project_update:authorized_project_update_project_recalculate
:worker_name: AuthorizedProjectUpdate::ProjectRecalculateWorker
:feature_category: :authentication_and_authorization
:has_external_dependencies:
:urgency: :high
:resource_boundary: :unknown
:weight: 1
:idempotent: true
:tags: []
- :name: authorized_project_update:authorized_project_update_user_refresh_over_user_range
:worker_name: AuthorizedProjectUpdate::UserRefreshOverUserRangeWorker
:feature_category: :authentication_and_authorization
@ -39,15 +48,6 @@
:weight: 1
:idempotent: true
:tags: []
- :name: authorized_projects:authorized_project_update_project_recalculate
:worker_name: AuthorizedProjectUpdate::ProjectRecalculateWorker
:feature_category: :authentication_and_authorization
:has_external_dependencies:
:urgency: :high
:resource_boundary: :unknown
:weight: 1
:idempotent: true
:tags: []
- :name: auto_devops:auto_devops_disable
:worker_name: AutoDevops::DisableWorker
:feature_category: :auto_devops

View File

@ -7,7 +7,7 @@ module AuthorizedProjectUpdate
feature_category :authentication_and_authorization
urgency :high
queue_namespace :authorized_projects
queue_namespace :authorized_project_update
deduplicate :until_executing, including_scheduled: true
idempotent!

View File

@ -0,0 +1,8 @@
---
name: security_ci_lint_authorization
introduced_by_url: https://gitlab.com/gitlab-org/security/gitlab/-/merge_requests/1279
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/326708
milestone: '14.0'
type: development
group: group::pipeline authoring
default_enabled: false

View File

@ -44,8 +44,6 @@
- 2
- - authorized_project_update
- 1
- - authorized_projects
- 1
- - authorized_projects
- 2
- - auto_devops

View File

@ -0,0 +1,36 @@
# frozen_string_literal: true
class CleanupMoveContainerRegistryEnabledToProjectFeature < ActiveRecord::Migration[6.0]
MIGRATION = 'MoveContainerRegistryEnabledToProjectFeature'
disable_ddl_transaction!
def up
Gitlab::BackgroundMigration.steal(MIGRATION)
bg_migration_job_class = define_background_migration_jobs_class
bg_migration_job_class.where(class_name: MIGRATION, status: bg_migration_job_class.statuses['pending']).each do |job|
Gitlab::BackgroundMigration::MoveContainerRegistryEnabledToProjectFeature.new.perform(*job.arguments)
end
bg_migration_job_class.where(class_name: MIGRATION).delete_all
end
def down
# no-op
end
private
def define_background_migration_jobs_class
Class.new(ActiveRecord::Base) do
self.table_name = 'background_migration_jobs'
self.inheritance_column = :_type_disabled
enum status: {
pending: 0,
succeeded: 1
}
end
end
end

View File

@ -0,0 +1 @@
3c4905fbe29227da7a2386f73d9df30e82da48efff24a1193ba3db0ac325cfcf

View File

@ -23,6 +23,9 @@ If you use a cloud-managed service, or provide your own PostgreSQL instance:
- Amazon RDS requires the [`rds_superuser`](https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/Appendix.PostgreSQL.CommonDBATasks.html#Appendix.PostgreSQL.CommonDBATasks.Roles) role.
- Azure Database for PostgreSQL requires the [`azure_pg_admin`](https://docs.microsoft.com/en-us/azure/postgresql/howto-create-users#how-to-create-additional-admin-users-in-azure-database-for-postgresql) role.
This is for the installation of extensions during installation and upgrades. As an alternative,
[ensure the extensions are installed manually, and read about the problems that may arise during future GitLab upgrades](../../install/postgresql_extensions.md).
1. Configure the GitLab application servers with the appropriate connection details
for your external PostgreSQL service in your `/etc/gitlab/gitlab.rb` file:

View File

@ -13,7 +13,16 @@ info: To determine the technical writer assigned to the Stage/Group associated w
Checks if CI/CD YAML configuration is valid. This endpoint validates basic CI/CD
configuration syntax. It doesn't have any namespace specific context.
Access to this endpoint requires authentication.
Access to this endpoint does not require authentication when the instance
[allows new sign ups](../user/admin_area/settings/sign_up_restrictions.md#disable-new-sign-ups)
and:
- Does not have an [allowlist or denylist](../user/admin_area/settings/sign_up_restrictions.md#allow-or-deny-sign-ups-using-specific-email-domains).
- Does not [require administrator approval for new sign ups](../user/admin_area/settings/sign_up_restrictions.md#require-administrator-approval-for-new-sign-ups).
- Does not have additional [sign up
restrictions](../user/admin_area/settings/sign_up_restrictions.html#sign-up-restrictions).
Otherwise, authentication is required.
```plaintext
POST /ci/lint

View File

@ -20,11 +20,82 @@ These endpoints do not adhere to the standard API authentication methods.
See the [PyPI package registry documentation](../../user/packages/pypi_repository/index.md)
for details on which headers and token types are supported.
## Download a package file
## Download a package file from a group
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/225545) in GitLab 13.12.
Download a PyPI package file. The [simple API](#group-level-simple-api-entry-point)
normally supplies this URL.
```plaintext
GET groups/:id/packages/pypi/files/:sha256/:file_identifier
```
| Attribute | Type | Required | Description |
| ----------------- | ------ | -------- | ----------- |
| `id` | string | yes | The ID or full path of the group. |
| `sha256` | string | yes | The PyPI package file's sha256 checksum. |
| `file_identifier` | string | yes | The PyPI package file's name. |
```shell
curl --user <username>:<personal_access_token> "https://gitlab.example.com/api/v4/groups/1/packages/pypi/files/5y57017232013c8ac80647f4ca153k3726f6cba62d055cd747844ed95b3c65ff/my.pypi.package-0.0.1.tar.gz"
```
To write the output to a file:
```shell
curl --user <username>:<personal_access_token> "https://gitlab.example.com/api/v4/groups/1/packages/pypi/files/5y57017232013c8ac80647f4ca153k3726f6cba62d055cd747844ed95b3c65ff/my.pypi.package-0.0.1.tar.gz" >> my.pypi.package-0.0.1.tar.gz
```
This writes the downloaded file to `my.pypi.package-0.0.1.tar.gz` in the current directory.
## Group level simple API entry point
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/225545) in GitLab 13.12.
Returns the package descriptor as an HTML file:
```plaintext
GET groups/:id/packages/pypi/simple/:package_name
```
| Attribute | Type | Required | Description |
| -------------- | ------ | -------- | ----------- |
| `id` | string | yes | The ID or full path of the group. |
| `package_name` | string | yes | The name of the package. |
```shell
curl --user <username>:<personal_access_token> "https://gitlab.example.com/api/v4/groups/1/packages/pypi/simple/my.pypi.package"
```
Example response:
```html
<!DOCTYPE html>
<html>
<head>
<title>Links for my.pypi.package</title>
</head>
<body>
<h1>Links for my.pypi.package</h1>
<a href="https://gitlab.example.com/api/v4/groups/1/packages/pypi/files/5y57017232013c8ac80647f4ca153k3726f6cba62d055cd747844ed95b3c65ff/my.pypi.package-0.0.1-py3-none-any.whl#sha256=5y57017232013c8ac80647f4ca153k3726f6cba62d055cd747844ed95b3c65ff" data-requires-python="&gt;=3.6">my.pypi.package-0.0.1-py3-none-any.whl</a><br><a href="https://gitlab.example.com/api/v4/groups/1/packages/pypi/files/9s9w01b0bcd52b709ec052084e33a5517ffca96f7728ddd9f8866a30cdf76f2/my.pypi.package-0.0.1.tar.gz#sha256=9s9w011b0bcd52b709ec052084e33a5517ffca96f7728ddd9f8866a30cdf76f2" data-requires-python="&gt;=3.6">my.pypi.package-0.0.1.tar.gz</a><br>
</body>
</html>
```
To write the output to a file:
```shell
curl --user <username>:<personal_access_token> "https://gitlab.example.com/api/v4/groups/1/packages/pypi/simple/my.pypi.package" >> simple.html
```
This writes the downloaded file to `simple.html` in the current directory.
## Download a package file from a project
> Introduced in GitLab 12.10.
Download a PyPI package file. The [simple API](#simple-api-entry-point)
Download a PyPI package file. The [simple API](#project-level-simple-api-entry-point)
normally supplies this URL.
```plaintext
@ -49,7 +120,7 @@ curl --user <username>:<personal_access_token> "https://gitlab.example.com/api/v
This writes the downloaded file to `my.pypi.package-0.0.1.tar.gz` in the current directory.
## Simple API entry point
## Project-level simple API entry point
> Introduced in GitLab 12.10.

View File

@ -87,6 +87,7 @@ are very appreciative of the work done by translators and proofreaders!
- Polish
- Filip Mech - [GitLab](https://gitlab.com/mehenz), [CrowdIn](https://crowdin.com/profile/mehenz)
- Maksymilian Roman - [GitLab](https://gitlab.com/villaincandle), [CrowdIn](https://crowdin.com/profile/villaincandle)
- Jakub Gładykowski - [GitLab](https://gitlab.com/gladykov), [CrowdIn](https://crowdin.com/profile/gladykov)
- Portuguese
- Diogo Trindade - [GitLab](https://gitlab.com/luisdiogo2071317), [CrowdIn](https://crowdin.com/profile/ldiogotrindade)
- Portuguese, Brazilian
@ -108,7 +109,7 @@ are very appreciative of the work done by translators and proofreaders!
- Spanish
- Pedro Garcia - [GitLab](https://gitlab.com/pedgarrod), [CrowdIn](https://crowdin.com/profile/breaking_pitt)
- Swedish
- Proofreaders needed.
- Johannes Nilsson - [GitLab](https://gitlab.com/nlssn), [CrowdIn](https://crowdin.com/profile/nlssn)
- Turkish
- Ali Demirtaş - [GitLab](https://gitlab.com/alidemirtas), [CrowdIn](https://crowdin.com/profile/alidemirtas)
- Rıfat Ünalmış (Rifat Unalmis) - [GitLab](https://gitlab.com/runalmis), [CrowdIn](https://crowdin.com/profile/runalmis)

View File

@ -54,7 +54,19 @@ In order to install a PostgreSQL extension, this procedure should be followed:
On some systems you may need to install an additional package (for example,
`postgresql-contrib`) for certain extensions to become available.
## A typical migration failure scenario
## Typical failure scenarios
The following is an example of a new GitLab installation failing because the extension hasn't been
installed first.
```shell
---- Begin output of "bash" "/tmp/chef-script20210513-52940-d9b1gs" ----
STDOUT: psql:/opt/gitlab/embedded/service/gitlab-rails/db/structure.sql:9: ERROR: permission denied to create extension "btree_gist"
HINT: Must be superuser to create this extension.
rake aborted!
failed to execute:
psql -v ON_ERROR_STOP=1 -q -X -f /opt/gitlab/embedded/service/gitlab-rails/db/structure.sql --single-transaction gitlabhq_production
```
The following is an example of a situation when the extension hasn't been installed before running migrations.
In this scenario, the database migration fails to create the extension `btree_gist` because of insufficient
@ -79,5 +91,9 @@ This query will grant the user superuser permissions, ensuring any database exte
can be installed through migrations.
```
In order to recover from this situation, the extension needs to be installed manually using a superuser, and
the database migration (or GitLab upgrade) can be retried afterwards.
To recover from failed migrations, the extension must be installed manually by a superuser, and the
GitLab upgrade completed by [re-running the database migrations](../administration/raketasks/maintenance.md#run-incomplete-database-migrations):
```shell
sudo gitlab-rake db:migrate
```

View File

@ -117,13 +117,14 @@ the following table) as these were used for development and testing:
| 10.0 | 9.6 |
| 13.0 | 11 |
You must also ensure the following extensions are [loaded into every
GitLab database](postgresql_extensions.html):
You must also ensure the following extensions are loaded into every
GitLab database. [Read more about this requirement, and troubleshooting](postgresql_extensions.md).
| Extension | Minimum GitLab version |
| ------------ | ---------------------- |
| `pg_trgm` | 8.6 |
| `btree_gist` | 13.1 |
| `plpgsql` | 11.7 |
NOTE:
Support for [PostgreSQL 9.6 and 10 was removed in GitLab 13.0](https://about.gitlab.com/releases/2020/05/22/gitlab-13-0-released/#postgresql-11-is-now-the-minimum-required-version-to-install-gitlab) so that GitLab can benefit from PostgreSQL 11 improvements, such as partitioning. For the schedule of transitioning to PostgreSQL 12, see [the related epic](https://gitlab.com/groups/gitlab-org/-/epics/2184).
@ -136,6 +137,35 @@ test based on those. We try to be compatible with most external (not managed by
Omnibus GitLab) databases (for example, [AWS Relational Database Service (RDS)](https://aws.amazon.com/rds/)),
but we can't guarantee compatibility.
#### Gitaly Cluster database requirements
[Read more in the Gitaly Cluster documentation](../administration/gitaly/praefect.md).
#### Exclusive use of GitLab databases
Databases created or used for GitLab, Geo, Gitaly Cluster, or other components should be for the
exclusive use of GitLab. Do not make direct changes to the database, schemas, users, or other
properties except when following procedures in the GitLab documentation or following the directions
of GitLab Support or other GitLab engineers.
- The main GitLab application currently uses three schemas:
- The default `public` schema
- `gitlab_partitions_static` (automatically created)
- `gitlab_partitions_dynamic` (automatically created)
No other schemas should be manually created.
- GitLab may create new schemas as part of Rails database migrations. This happens when performing
a GitLab upgrade. The GitLab database account requires access to do this.
- GitLab creates and modifies tables during the upgrade process, and also as part of normal
operations to manage partitioned tables.
- You should not modify the GitLab schema (for example, adding triggers or modifying tables).
Database migrations are tested against the schema definition in the GitLab code base. GitLab
version upgrades may fail if the schema is modified.
## Puma settings
The recommended settings for Puma are determined by the infrastructure on which it's running.

View File

@ -102,7 +102,7 @@ How you enable container scanning depends on your GitLab version:
`container_scanning` job's [`before_script`](../../../ci/yaml/README.md#before_script)
and [`after_script`](../../../ci/yaml/README.md#after_script)
blocks may not work with the new version. To roll back to the previous [`alpine:3.11.3`](https://hub.docker.com/_/alpine)-based
Docker image, you can specify the major version through the [`CS_MAJOR_VERSION`](#available-variables)
Docker image, you can specify the major version through the [`CS_MAJOR_VERSION`](#available-cicd-variables)
variable.
- GitLab 13.9 [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/322656) integration with
[Trivy](https://github.com/aquasecurity/trivy) by upgrading `CS_MAJOR_VERSION` from `3` to `4`.
@ -159,7 +159,7 @@ include:
There may be cases where you want to customize how GitLab scans your containers. For example, you
may want to enable more verbose output, access a Docker registry that requires
authentication, and more. To change such settings, use the [`variables`](../../../ci/yaml/README.md#variables)
parameter in your `.gitlab-ci.yml` to set [CI/CD variables](#available-variables).
parameter in your `.gitlab-ci.yml` to set [CI/CD variables](#available-cicd-variables).
The variables you set in your `.gitlab-ci.yml` overwrite those in
`Container-Scanning.gitlab-ci.yml`.
@ -201,7 +201,7 @@ variables:
make a change to this heading, make sure to update the documentation URLs used in the"
container scanning tool (https://gitlab.com/gitlab-org/security-products/analyzers/klar)" -->
#### Available variables
#### Available CI/CD variables
You can [configure](#customizing-the-container-scanning-settings) both analyzers by using the following CI/CD variables:
@ -289,7 +289,7 @@ taking the following steps:
that instead of overriding this variable, you can use `CS_MAJOR_VERSION`.
1. Remove any variables that are only applicable to Clair. For a complete list of these variables,
see the [available variables](#available-variables).
see the [available variables](#available-cicd-variables).
1. Make any [necessary customizations](#customizing-the-container-scanning-settings) to the
`Trivy` scanner. We strongly recommended that you minimize customizations, as they
might require changes in future GitLab major releases.
@ -711,7 +711,7 @@ Some vulnerabilities can be fixed by applying the solution that GitLab
automatically generates.
To enable remediation support, the scanning tool _must_ have access to the `Dockerfile` specified by
the [`DOCKERFILE_PATH`](#available-variables) CI/CD variable. To ensure that the scanning tool
the [`DOCKERFILE_PATH`](#available-cicd-variables) CI/CD variable. To ensure that the scanning tool
has access to this
file, it's necessary to set [`GIT_STRATEGY: fetch`](../../../ci/runners/README.md#git-strategy) in
your `.gitlab-ci.yml` file by following the instructions described in this document's

View File

@ -44,7 +44,7 @@ dast:
DAST_BROWSER_SCAN: "true"
```
### Available variables
### Available CI/CD variables
The browser-based crawler can be configured using CI/CD variables.
@ -72,7 +72,7 @@ The browser-based crawler can be configured using CI/CD variables.
| `DAST_BROWSER_AUTH_VERIFICATION_SELECTOR` | selector | `css:.user-photo` | Verifies successful authentication by checking for presence of a selector once the login form has been submitted. |
| `DAST_BROWSER_AUTH_VERIFICATION_LOGIN_FORM` | boolean | `true` | Verifies successful authentication by checking for the lack of a login form once the login form has been submitted. |
The [DAST variables](index.md#available-variables) `SECURE_ANALYZERS_PREFIX`, `DAST_FULL_SCAN_ENABLED`, `DAST_AUTO_UPDATE_ADDONS`, `DAST_EXCLUDE_RULES`, `DAST_REQUEST_HEADERS`, `DAST_HTML_REPORT`, `DAST_MARKDOWN_REPORT`, `DAST_XML_REPORT`,
The [DAST variables](index.md#available-cicd-variables) `SECURE_ANALYZERS_PREFIX`, `DAST_FULL_SCAN_ENABLED`, `DAST_AUTO_UPDATE_ADDONS`, `DAST_EXCLUDE_RULES`, `DAST_REQUEST_HEADERS`, `DAST_HTML_REPORT`, `DAST_MARKDOWN_REPORT`, `DAST_XML_REPORT`,
`DAST_INCLUDE_ALPHA_VULNERABILITIES`, `DAST_PATHS_FILE`, `DAST_PATHS`, `DAST_ZAP_CLI_OPTIONS`, and `DAST_ZAP_LOG_CONFIGURATION` are also compatible with browser-based crawler scans.
#### Selectors
@ -284,9 +284,9 @@ This can come at a cost of increased scan time.
You can manage the trade-off between coverage and scan time with the following measures:
- Limit the number of actions executed by the browser with the [variable](#available-variables) `DAST_BROWSER_MAX_ACTIONS`. The default is `10,000`.
- Limit the page depth that the browser-based crawler will check coverage on with the [variable](#available-variables) `DAST_BROWSER_MAX_DEPTH`. The crawler uses a breadth-first search strategy, so pages with smaller depth are crawled first. The default is `10`.
- Vertically scaling the runner and using a higher number of browsers with [variable](#available-variables) `DAST_BROWSER_NUMBER_OF_BROWSERS`. The default is `3`.
- Limit the number of actions executed by the browser with the [variable](#available-cicd-variables) `DAST_BROWSER_MAX_ACTIONS`. The default is `10,000`.
- Limit the page depth that the browser-based crawler will check coverage on with the [variable](#available-cicd-variables) `DAST_BROWSER_MAX_DEPTH`. The crawler uses a breadth-first search strategy, so pages with smaller depth are crawled first. The default is `10`.
- Vertically scaling the runner and using a higher number of browsers with [variable](#available-cicd-variables) `DAST_BROWSER_NUMBER_OF_BROWSERS`. The default is `3`.
## Debugging scans using logging

View File

@ -270,7 +270,7 @@ authorization credentials. By default, the following headers are masked:
- `Set-Cookie` (values only).
- `Cookie` (values only).
Using the [`DAST_MASK_HTTP_HEADERS` CI/CD variable](#available-variables), you can list the
Using the [`DAST_MASK_HTTP_HEADERS` CI/CD variable](#available-cicd-variables), you can list the
headers whose values you want masked. For details on how to mask headers, see
[Customizing the DAST settings](#customizing-the-dast-settings).
@ -348,7 +348,7 @@ and potentially damage them. You could even take down your production environmen
For that reason, you should use domain validation.
Domain validation is not required by default. It can be required by setting the
[CI/CD variable](#available-variables) `DAST_FULL_SCAN_DOMAIN_VALIDATION_REQUIRED` to `"true"`.
[CI/CD variable](#available-cicd-variables) `DAST_FULL_SCAN_DOMAIN_VALIDATION_REQUIRED` to `"true"`.
```yaml
include:
@ -661,7 +661,7 @@ is no longer supported. When overriding the template, you must use [`rules`](../
The DAST settings can be changed through CI/CD variables by using the
[`variables`](../../../ci/yaml/README.md#variables) parameter in `.gitlab-ci.yml`.
These variables are documented in [available variables](#available-variables).
These variables are documented in [available variables](#available-cicd-variables).
For example:
@ -677,7 +677,7 @@ variables:
Because the template is [evaluated before](../../../ci/yaml/README.md#include) the pipeline
configuration, the last mention of the variable takes precedence.
### Available variables
### Available CI/CD variables
DAST can be [configured](#customizing-the-dast-settings) using CI/CD variables.

View File

@ -112,7 +112,7 @@ always take the latest dependency scanning artifact available.
### Customizing the dependency scanning settings
The dependency scanning settings can be changed through [CI/CD variables](#available-variables) by using the
The dependency scanning settings can be changed through [CI/CD variables](#available-cicd-variables) by using the
[`variables`](../../../ci/yaml/README.md#variables) parameter in `.gitlab-ci.yml`.
For example:
@ -157,7 +157,7 @@ gemnasium-dependency_scanning:
dependencies: ["build"]
```
### Available variables
### Available CI/CD variables
Dependency scanning can be [configured](#customizing-the-dependency-scanning-settings)
using environment variables.

View File

@ -48,7 +48,7 @@ GitLab, but users can also integrate their own **custom images**.
For an analyzer to be considered Generally Available, it is expected to minimally
support the following features:
- [Customizable configuration](index.md#available-variables)
- [Customizable configuration](index.md#available-cicd-variables)
- [Customizable rulesets](index.md#customize-rulesets)
- [Scan projects](index.md#supported-languages-and-frameworks)
- [Multi-project support](index.md#multi-project-support)

View File

@ -202,7 +202,7 @@ page:
### Customizing the SAST settings
The SAST settings can be changed through [CI/CD variables](#available-variables)
The SAST settings can be changed through [CI/CD variables](#available-cicd-variables)
by using the
[`variables`](../../../ci/yaml/README.md#variables) parameter in `.gitlab-ci.yml`.
In the following example, we include the SAST template and at the same time we
@ -411,7 +411,7 @@ the vendored directory. This configuration can vary per analyzer but in the case
can use `MAVEN_REPO_PATH`. See
[Analyzer settings](#analyzer-settings) for the complete list of available options.
### Available variables
### Available CI/CD variables
SAST can be [configured](#customizing-the-sast-settings) using CI/CD variables.

View File

@ -160,7 +160,7 @@ that you can review and merge to complete the configuration.
### Customizing settings
The Secret Detection scan settings can be changed through [CI/CD variables](#available-variables)
The Secret Detection scan settings can be changed through [CI/CD variables](#available-cicd-variables)
by using the
[`variables`](../../../ci/yaml/README.md#variables) parameter in `.gitlab-ci.yml`.
@ -196,7 +196,7 @@ secret_detection:
Because the template is [evaluated before](../../../ci/yaml/README.md#include)
the pipeline configuration, the last mention of the variable takes precedence.
#### Available variables
#### Available CI/CD variables
Secret Detection can be customized by defining available CI/CD variables:
@ -298,7 +298,7 @@ want to perform a full secret scan. Running a secret scan on the full history ca
especially for larger repositories with lengthy Git histories. We recommend not setting this CI/CD variable
as part of your normal job definition.
A new configuration variable ([`SECRET_DETECTION_HISTORIC_SCAN`](#available-variables))
A new configuration variable ([`SECRET_DETECTION_HISTORIC_SCAN`](#available-cicd-variables))
can be set to change the behavior of the GitLab Secret Detection scan to run on the entire Git history of a repository.
We have created a [short video walkthrough](https://youtu.be/wDtc_K00Y0A) showcasing how you can perform a full history secret scan.

View File

@ -121,7 +121,7 @@ always take the latest License Compliance artifact available. Behind the scenes,
[GitLab License Compliance Docker image](https://gitlab.com/gitlab-org/security-products/analyzers/license-finder)
is used to detect the languages/frameworks and in turn analyzes the licenses.
The License Compliance settings can be changed through [CI/CD variables](#available-variables) by using the
The License Compliance settings can be changed through [CI/CD variables](#available-cicd-variables) by using the
[`variables`](../../../ci/yaml/README.md#variables) parameter in `.gitlab-ci.yml`.
### When License Compliance runs
@ -129,7 +129,7 @@ The License Compliance settings can be changed through [CI/CD variables](#availa
When using the GitLab `License-Scanning.gitlab-ci.yml` template, the License Compliance job doesn't
wait for other stages to complete.
### Available variables
### Available CI/CD variables
License Compliance can be configured using CI/CD variables.
@ -265,11 +265,11 @@ license_scanning:
### Custom root certificates for Python
You can supply a custom root certificate to complete TLS verification by using the
`ADDITIONAL_CA_CERT_BUNDLE` [CI/CD variable](#available-variables).
`ADDITIONAL_CA_CERT_BUNDLE` [CI/CD variable](#available-cicd-variables).
#### Using private Python repositories
If you have a private Python repository you can use the `PIP_INDEX_URL` [CI/CD variable](#available-variables)
If you have a private Python repository you can use the `PIP_INDEX_URL` [CI/CD variable](#available-cicd-variables)
to specify its location.
### Configuring npm projects
@ -292,7 +292,7 @@ registry = https://npm.example.com
#### Custom root certificates for npm
You can supply a custom root certificate to complete TLS verification by using the
`ADDITIONAL_CA_CERT_BUNDLE` [CI/CD variable](#available-variables).
`ADDITIONAL_CA_CERT_BUNDLE` [CI/CD variable](#available-cicd-variables).
To disable TLS verification you can provide the [`strict-ssl`](https://docs.npmjs.com/using-npm/config/#strict-ssl)
setting.
@ -323,7 +323,7 @@ npmRegistryServer: "https://npm.example.com"
#### Custom root certificates for Yarn
You can supply a custom root certificate to complete TLS verification by using the
`ADDITIONAL_CA_CERT_BUNDLE` [CI/CD variable](#available-variables).
`ADDITIONAL_CA_CERT_BUNDLE` [CI/CD variable](#available-cicd-variables).
### Configuring Bower projects
@ -347,7 +347,7 @@ For example:
#### Custom root certificates for Bower
You can supply a custom root certificate to complete TLS verification by using the
`ADDITIONAL_CA_CERT_BUNDLE` [CI/CD variable](#available-variables), or by
`ADDITIONAL_CA_CERT_BUNDLE` [CI/CD variable](#available-cicd-variables), or by
specifying a `ca` setting in a [`.bowerrc`](https://bower.io/docs/config/#bowerrc-specification)
file.
@ -368,7 +368,7 @@ source "https://gems.example.com"
#### Custom root certificates for Bundler
You can supply a custom root certificate to complete TLS verification by using the
`ADDITIONAL_CA_CERT_BUNDLE` [CI/CD variable](#available-variables), or by
`ADDITIONAL_CA_CERT_BUNDLE` [CI/CD variable](#available-cicd-variables), or by
specifying a [`BUNDLE_SSL_CA_CERT`](https://bundler.io/v2.0/man/bundle-config.1.html)
[variable](../../../ci/variables/README.md#custom-cicd-variables)
in the job definition.
@ -392,7 +392,7 @@ my-registry = { index = "https://my-intranet:8080/git/index" }
To supply a custom root certificate to complete TLS verification, do one of the following:
- Use the `ADDITIONAL_CA_CERT_BUNDLE` [CI/CD variable](#available-variables).
- Use the `ADDITIONAL_CA_CERT_BUNDLE` [CI/CD variable](#available-cicd-variables).
- Specify a [`CARGO_HTTP_CAINFO`](https://doc.rust-lang.org/cargo/reference/environment-variables.html)
[variable](../../../ci/variables/README.md#custom-cicd-variables)
in the job definition.
@ -425,7 +425,7 @@ For example:
#### Custom root certificates for Composer
You can supply a custom root certificate to complete TLS verification by using the
`ADDITIONAL_CA_CERT_BUNDLE` [CI/CD variable](#available-variables), or by
`ADDITIONAL_CA_CERT_BUNDLE` [CI/CD variable](#available-cicd-variables), or by
specifying a [`COMPOSER_CAFILE`](https://getcomposer.org/doc/03-cli.md#composer-cafile)
[variable](../../../ci/variables/README.md#custom-cicd-variables)
in the job definition.
@ -499,7 +499,7 @@ You can provide custom certificates by adding a `.conan/cacert.pem` file to the
setting [`CA_CERT_PATH`](https://docs.conan.io/en/latest/reference/env_vars.html#conan-cacert-path)
to `.conan/cacert.pem`.
If you specify the `ADDITIONAL_CA_CERT_BUNDLE` [CI/CD variable](#available-variables), this
If you specify the `ADDITIONAL_CA_CERT_BUNDLE` [CI/CD variable](#available-cicd-variables), this
variable's X.509 certificates are installed in the Docker image's default trust store and Conan is
configured to use this as the default `CA_CERT_PATH`.
@ -507,7 +507,7 @@ configured to use this as the default `CA_CERT_PATH`.
To configure [Go modules](https://github.com/golang/go/wiki/Modules)
based projects, specify [CI/CD variables](https://golang.org/pkg/cmd/go/#hdr-Environment_variables)
in the `license_scanning` job's [variables](#available-variables) section in `.gitlab-ci.yml`.
in the `license_scanning` job's [variables](#available-cicd-variables) section in `.gitlab-ci.yml`.
If a project has [vendored](https://golang.org/pkg/cmd/go/#hdr-Vendor_Directories) its modules,
then the combination of the `vendor` directory and `mod.sum` file are used to detect the software
@ -556,7 +556,7 @@ For example:
#### Custom root certificates for NuGet
You can supply a custom root certificate to complete TLS verification by using the
`ADDITIONAL_CA_CERT_BUNDLE` [CI/CD variable](#available-variables).
`ADDITIONAL_CA_CERT_BUNDLE` [CI/CD variable](#available-cicd-variables).
### Migration from `license_management` to `license_scanning`
@ -816,7 +816,7 @@ license_scanning:
ASDF_RUBY_VERSION: '2.7.2'
```
A full list of variables can be found in [CI/CD variables](#available-variables).
A full list of variables can be found in [CI/CD variables](#available-cicd-variables).
To find out what tools are pre-installed in the `license_scanning` Docker image use the following command:

View File

@ -1395,26 +1395,32 @@ while the equation for the theory of relativity is E = mc<sup>2</sup>.
Tables are not part of the core Markdown spec, but they are part of GitLab Flavored Markdown.
1. The first line contains the headers, separated by "pipes" (`|`).
1. The second line separates the headers from the cells, and must contain three or more dashes.
1. The second line separates the headers from the cells.
- The cells can contain only empty spaces, hyphens, and
(optionally) colons for horizontal alignment.
- Each cell must contain at least one hyphen, but adding more hyphens to a
cell does not change the cell's rendering.
- Any content other than hyphens, whitespace, or colons is not allowed
1. The third, and any following lines, contain the cell values.
- You **can't** have cells separated over many lines in the Markdown, they must be kept to single lines,
but they can be very long. You can also include HTML `<br>` tags to force newlines if needed.
- The cell sizes **don't** have to match each other. They are flexible, but must be separated
by pipes (`|`).
- You **can** have blank cells.
1. Column widths are calculated dynamically based on the content of the cells.
Example:
```markdown
| header 1 | header 2 | header 3 |
| --- | ------ |----------|
| --- | --- | --- |
| cell 1 | cell 2 | cell 3 |
| cell 4 | cell 5 is longer | cell 6 is much longer than the others, but that's ok. It eventually wraps the text when the cell is too large for the display size. |
| cell 7 | | cell 9 |
```
| header 1 | header 2 | header 3 |
| --- | ------ |----------|
| --- | --- | --- |
| cell 1 | cell 2 | cell 3 |
| cell 4 | cell 5 is longer | cell 6 is much longer than the others, but that's ok. It eventually wraps the text when the cell is too large for the display size. |
| cell 7 | | cell 9 |
@ -1423,16 +1429,16 @@ Additionally, you can choose the alignment of text in columns by adding colons (
to the sides of the "dash" lines in the second row. This affects every cell in the column:
```markdown
| Left Aligned | Centered | Right Aligned | Left Aligned | Centered | Right Aligned |
| :--- | :---: | ---: | :----------- | :------: | ------------: |
| Cell 1 | Cell 2 | Cell 3 | Cell 4 | Cell 5 | Cell 6 |
| Cell 7 | Cell 8 | Cell 9 | Cell 10 | Cell 11 | Cell 12 |
| Left Aligned | Centered | Right Aligned |
| :--- | :---: | ---: |
| Cell 1 | Cell 2 | Cell 3 |
| Cell 4 | Cell 5 | Cell 6 |
```
| Left Aligned | Centered | Right Aligned | Left Aligned | Centered | Right Aligned |
| :--- | :---: | ---: | :----------- | :------: | ------------: |
| Cell 1 | Cell 2 | Cell 3 | Cell 4 | Cell 5 | Cell 6 |
| Cell 7 | Cell 8 | Cell 9 | Cell 10 | Cell 11 | Cell 12 |
| Left Aligned | Centered | Right Aligned |
| :--- | :---: | ---: |
| Cell 1 | Cell 2 | Cell 3 |
| Cell 4 | Cell 5 | Cell 6 |
[In GitLab itself](https://gitlab.com/gitlab-org/gitlab/blob/master/doc/user/markdown.md#tables),
the headers are always left-aligned in Chrome and Firefox, and centered in Safari.
@ -1442,13 +1448,13 @@ use `<br>` tags to force a cell to have multiple lines:
```markdown
| Name | Details |
|------|---------|
| --- | --- |
| Item1 | This is on one line |
| Item2 | This item has:<br>- Multiple items<br>- That we want listed separately |
```
| Name | Details |
|------|---------|
| --- | --- |
| Item1 | This is on one line |
| Item2 | This item has:<br>- Multiple items<br>- That we want listed separately |
@ -1457,7 +1463,7 @@ but they do not render properly on `docs.gitlab.com`:
```markdown
| header 1 | header 2 |
|----------|----------|
| --- | --- |
| cell 1 | cell 2 |
| cell 3 | <ul><li> - [ ] Task one </li><li> - [ ] Task two </li></ul> |
```

View File

@ -316,6 +316,8 @@ more than once, a `404 Bad Request` error occurs.
## Install a PyPI package
### Install from the project level
To install the latest version of a package, use the following command:
```shell
@ -350,6 +352,33 @@ Installing collected packages: mypypipackage
Successfully installed mypypipackage-0.0.1
```
### Install from the group level
To install the latest version of a package from a group, use the following command:
```shell
pip install --index-url https://<personal_access_token_name>:<personal_access_token>@gitlab.example.com/api/v4/groups/<group_id>/packages/pypi/simple --no-deps <package_name>
```
In this command:
- `<package_name>` is the package name.
- `<personal_access_token_name>` is a personal access token name with the `read_api` scope.
- `<personal_access_token>` is a personal access token with the `read_api` scope.
- `<group_id>` is the group ID.
In these commands, you can use `--extra-index-url` instead of `--index-url`. However, using
`--extra-index-url` makes you vulnerable to dependency confusion attacks because it checks the PyPi
repository for the package before it checks the custom repository. `--extra-index-url` adds the
provided URL as an additional registry which the client checks if the package is present.
`--index-url` tells the client to check for the package at the provided URL only.
If you're following the guide and want to install the `MyPyPiPackage` package, you can run:
```shell
pip install mypypipackage --no-deps --index-url https://<personal_access_token_name>:<personal_access_token>@gitlab.example.com/api/v4/groups/<your_group_id>/packages/pypi/simple
```
### Package names
GitLab looks for packages that use

View File

@ -37,6 +37,21 @@ module API
track_package_event('push_package', :debian)
file_params = {
file: params['file'],
file_name: params['file_name'],
file_sha1: params['file.sha1'],
file_md5: params['file.md5']
}
package = ::Packages::Debian::FindOrCreateIncomingService.new(authorized_user_project, current_user).execute
package_file = ::Packages::Debian::CreatePackageFileService.new(package, file_params).execute
if params['file_name'].end_with? '.changes'
::Packages::Debian::ProcessChangesWorker.perform_async(package_file.id, current_user.id) # rubocop:disable CodeReuse/Worker
end
created!
rescue ObjectStorage::RemoteStoreError => e
Gitlab::ErrorTracking.track_exception(e, extra: { file_name: params[:file_name], project_id: authorized_user_project.id })

View File

@ -22,6 +22,14 @@ module API
unauthorized_user_project || not_found!
end
def unauthorized_user_group
@unauthorized_user_group ||= find_group(params[:id])
end
def unauthorized_user_group!
unauthorized_user_group || not_found!
end
def authorized_user_project
@authorized_user_project ||= authorized_project_find!
end

View File

@ -11,7 +11,11 @@ module API
optional :include_merged_yaml, type: Boolean, desc: 'Whether or not to include merged CI config yaml in the response'
end
post '/lint' do
unauthorized! if Gitlab::CurrentSettings.signup_disabled? && current_user.nil?
if Feature.enabled?(:security_ci_lint_authorization)
unauthorized! if (Gitlab::CurrentSettings.signup_disabled? || Gitlab::CurrentSettings.signup_limited?) && current_user.nil?
else
unauthorized! if Gitlab::CurrentSettings.signup_disabled? && current_user.nil?
end
result = Gitlab::Ci::YamlProcessor.new(params[:content], user: current_user).execute

View File

@ -28,6 +28,73 @@ module API
require_packages_enabled!
end
helpers do
params :package_download do
requires :file_identifier, type: String, desc: 'The PyPi package file identifier', file_path: true
requires :sha256, type: String, desc: 'The PyPi package sha256 check sum'
end
params :package_name do
requires :package_name, type: String, file_path: true, desc: 'The PyPi package name'
end
end
params do
requires :id, type: Integer, desc: 'The ID of a group'
end
resource :groups, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do
after_validation do
unauthorized_user_group!
end
namespace ':id/-/packages/pypi' do
params do
use :package_download
end
route_setting :authentication, deploy_token_allowed: true, basic_auth_personal_access_token: true, job_token_allowed: :basic_auth
get 'files/:sha256/*file_identifier' do
group = unauthorized_user_group!
filename = "#{params[:file_identifier]}.#{params[:format]}"
package = Packages::Pypi::PackageFinder.new(current_user, group, { filename: filename, sha256: params[:sha256] }).execute
package_file = ::Packages::PackageFileFinder.new(package, filename, with_file_name_like: false).execute
track_package_event('pull_package', :pypi)
present_carrierwave_file!(package_file.file, supports_direct_download: true)
end
desc 'The PyPi Simple Endpoint' do
detail 'This feature was introduced in GitLab 12.10'
end
params do
use :package_name
end
# An Api entry point but returns an HTML file instead of JSON.
# PyPi simple API returns the package descriptor as a simple HTML file.
route_setting :authentication, deploy_token_allowed: true, basic_auth_personal_access_token: true, job_token_allowed: :basic_auth
get 'simple/*package_name', format: :txt do
group = find_authorized_group!
authorize_read_package!(group)
track_package_event('list_package', :pypi)
packages = Packages::Pypi::PackagesFinder.new(current_user, group, { package_name: params[:package_name] }).execute!
presenter = ::Packages::Pypi::PackagePresenter.new(packages, group)
# Adjusts grape output format
# to be HTML
content_type "text/html; charset=utf-8"
env['api.format'] = :binary
body presenter.body
end
end
end
params do
requires :id, type: Integer, desc: 'The ID of a project'
end
@ -43,8 +110,7 @@ module API
end
params do
requires :file_identifier, type: String, desc: 'The PyPi package file identifier', file_path: true
requires :sha256, type: String, desc: 'The PyPi package sha256 check sum'
use :package_download
end
route_setting :authentication, deploy_token_allowed: true, basic_auth_personal_access_token: true, job_token_allowed: :basic_auth
@ -65,7 +131,7 @@ module API
end
params do
requires :package_name, type: String, file_path: true, desc: 'The PyPi package name'
use :package_name
end
# An Api entry point but returns an HTML file instead of JSON.

View File

@ -9,8 +9,8 @@ apply:
script:
- gitlab-managed-apps /usr/local/share/gitlab-managed-apps/helmfile.yaml
only:
refs:
- master
variables:
- $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
artifacts:
reports:
cluster_applications: gl-cluster-applications.json

View File

@ -1,8 +1,7 @@
# Read more about this feature here: https://docs.gitlab.com/ee/user/application_security/api_fuzzing/
# Configure the scanning tool through the environment variables.
# List of the variables: https://docs.gitlab.com/ee/user/application_security/api_fuzzing/#available-variables
# How to set: https://docs.gitlab.com/ee/ci/yaml/#variables
# Configure API fuzzing with CI/CD variables (https://docs.gitlab.com/ee/ci/variables/README.html).
# List of available variables: https://docs.gitlab.com/ee/user/application_security/api_fuzzing/#available-cicd-variables
stages:
- build

View File

@ -1,8 +1,7 @@
# Read more about this feature here: https://docs.gitlab.com/ee/user/application_security/api_fuzzing/
# Configure the scanning tool through the environment variables.
# List of the variables: https://docs.gitlab.com/ee/user/application_security/api_fuzzing/#available-variables
# How to set: https://docs.gitlab.com/ee/ci/yaml/#variables
# Configure API fuzzing with CI/CD variables (https://docs.gitlab.com/ee/ci/variables/README.html).
# List of available variables: https://docs.gitlab.com/ee/user/application_security/api_fuzzing/#available-cicd-variables
variables:
FUZZAPI_VERSION: "1"

View File

@ -10,7 +10,8 @@
# - For auto-remediation, a readable Dockerfile in the root of the project or as defined by the
# DOCKERFILE_PATH variable.
#
# For more information, see https://docs.gitlab.com/ee/user/application_security/container_scanning/#available-variables
# Configure container scanning with CI/CD variables (https://docs.gitlab.com/ee/ci/variables/README.html).
# List of available variables: https://docs.gitlab.com/ee/user/application_security/container_scanning/#available-variables
variables:
# Setting this variable will affect all Security templates (e.g.: SAST, Dependency Scanning)

View File

@ -1,8 +1,7 @@
# Read more about this feature https://docs.gitlab.com/ee/user/application_security/coverage_fuzzing
# Configure the fuzzing tool through the environment variables.
# List of the variables: https://docs.gitlab.com/ee/user/application_security/coverage_fuzzing/#available-cicd-variables
# How to set: https://docs.gitlab.com/ee/ci/yaml/#variables
# Configure coverage fuzzing with CI/CD variables (https://docs.gitlab.com/ee/ci/variables/README.html).
# List of available variables: https://docs.gitlab.com/ee/user/application_security/coverage_fuzzing/#available-cicd-variables
variables:
# Which branch we want to run full fledged long running fuzzing jobs.

View File

@ -13,9 +13,8 @@
# Read more about this feature here: https://docs.gitlab.com/ee/user/application_security/dast_api/index.html
# Configure the scanning tool with CI/CD variables (https://docs.gitlab.com/ee/ci/variables/README.html)
# List of variables available to configure the DAST API scanning tool:
# https://docs.gitlab.com/ee/user/application_security/dast_api/index.html#available-cicd-variables
# Configure DAST API scanning with CI/CD variables (https://docs.gitlab.com/ee/ci/variables/README.html).
# List of available variables: https://docs.gitlab.com/ee/user/application_security/dast_api/index.html#available-cicd-variables
variables:
# Setting this variable affects all Security templates

View File

@ -1,8 +1,7 @@
# Read more about this feature here: https://docs.gitlab.com/ee/user/application_security/dast/
# Configure the scanning tool through the environment variables.
# List of the variables: https://docs.gitlab.com/ee/user/application_security/dast/#available-variables
# How to set: https://docs.gitlab.com/ee/ci/yaml/#variables
# Configure DAST with CI/CD variables (https://docs.gitlab.com/ee/ci/variables/README.html).
# List of available variables: https://docs.gitlab.com/ee/user/application_security/dast/#available-variables
stages:
- build

View File

@ -13,9 +13,8 @@
# Read more about this feature here: https://docs.gitlab.com/ee/user/application_security/dast/
# Configure the scanning tool through the environment variables.
# List of the variables: https://docs.gitlab.com/ee/user/application_security/dast/#available-variables
# How to set: https://docs.gitlab.com/ee/ci/yaml/#variables
# Configure DAST with CI/CD variables (https://docs.gitlab.com/ee/ci/variables/README.html).
# List of available variables: https://docs.gitlab.com/ee/user/application_security/dast/#available-variables
variables:
DAST_VERSION: 1

View File

@ -1,8 +1,7 @@
# Read more about this feature here: https://docs.gitlab.com/ee/user/application_security/dependency_scanning/
#
# Configure the scanning tool through the environment variables.
# List of the variables: https://gitlab.com/gitlab-org/security-products/dependency-scanning#settings
# How to set: https://docs.gitlab.com/ee/ci/yaml/#variables
# Configure dependency scanning with CI/CD variables (https://docs.gitlab.com/ee/ci/variables/README.html).
# List of available variables: https://docs.gitlab.com/ee/user/application_security/dependency_scanning/index.html#available-variables
variables:
# Setting this variable will affect all Security templates

View File

@ -1,8 +1,7 @@
# Read more about this feature here: https://docs.gitlab.com/ee/user/compliance/license_compliance/index.html
#
# Configure the scanning tool through the environment variables.
# List of the variables: https://gitlab.com/gitlab-org/security-products/analyzers/license-finder#settings
# How to set: https://docs.gitlab.com/ee/ci/yaml/#variables
# Configure license scanning with CI/CD variables (https://docs.gitlab.com/ee/ci/variables/README.html).
# List of available variables: https://docs.gitlab.com/ee/user/compliance/license_compliance/#available-variables
variables:
# Setting this variable will affect all Security templates

View File

@ -1,8 +1,7 @@
# Read more about this feature here: https://docs.gitlab.com/ee/user/application_security/sast/
#
# Configure the scanning tool through the environment variables.
# List of the variables: https://gitlab.com/gitlab-org/security-products/sast#settings
# How to set: https://docs.gitlab.com/ee/ci/yaml/#variables
# Configure SAST with CI/CD variables (https://docs.gitlab.com/ee/ci/variables/README.html).
# List of available variables: https://docs.gitlab.com/ee/user/application_security/sast/index.html#available-variables
variables:
# Setting this variable will affect all Security templates

View File

@ -1,8 +1,7 @@
# Read more about this feature here: https://docs.gitlab.com/ee/user/application_security/secret_detection
#
# Configure the scanning tool through the environment variables.
# List of the variables: https://docs.gitlab.com/ee/user/application_security/secret_detection/#available-variables
# How to set: https://docs.gitlab.com/ee/ci/yaml/#variables
# Configure secret detection with CI/CD variables (https://docs.gitlab.com/ee/ci/variables/README.html).
# List of available variables: https://docs.gitlab.com/ee/user/application_security/secret_detection/#available-variables
variables:
SECURE_ANALYZERS_PREFIX: "registry.gitlab.com/gitlab-org/security-products/analyzers"

View File

@ -7,6 +7,10 @@ module Gitlab
!signup_enabled?
end
def signup_limited?
domain_allowlist.present? || email_restrictions_enabled? || require_admin_approval_after_user_signup?
end
def current_application_settings
Gitlab::SafeRequestStore.fetch(:current_application_settings) { ensure_application_settings! }
end

View File

@ -65,10 +65,9 @@ module Gitlab
def project_from_key
return unless match = service_desk_key.match(PROJECT_KEY_PATTERN)
project = Project.find_by_service_desk_project_key(match[:key])
return unless valid_project_key?(project, match[:slug])
project
Project.with_service_desk_key(match[:key]).find do |project|
valid_project_key?(project, match[:slug])
end
end
def valid_project_key?(project, slug)

View File

@ -11,12 +11,16 @@ module Gitlab
end
def cache_key(key)
"#{cache_type}:#{key}:set"
"#{cache_namespace}:#{key}:set"
end
def new_cache_key(key)
super(key)
end
def clear_cache!(key)
with do |redis|
keys = read(key).map { |value| "#{cache_type}:#{value}" }
keys = read(key).map { |value| "#{cache_namespace}:#{value}" }
keys << cache_key(key)
redis.pipelined do
@ -24,11 +28,5 @@ module Gitlab
end
end
end
private
def cache_type
Gitlab::Redis::Cache::CACHE_NAMESPACE
end
end
end

View File

@ -17,6 +17,11 @@ module Gitlab
"#{type}:#{namespace}:set"
end
# NOTE Remove as part of #331319
def new_cache_key(type)
super("#{type}:#{namespace}")
end
def write(key, value)
full_key = cache_key(key)

View File

@ -14,15 +14,21 @@ module Gitlab
"#{key}:set"
end
# NOTE Remove as part of https://gitlab.com/gitlab-org/gitlab/-/issues/331319
def new_cache_key(key)
"#{cache_namespace}:#{key}:set"
end
# Returns the number of keys deleted by Redis
def expire(*keys)
return 0 if keys.empty?
with do |redis|
keys = keys.map { |key| cache_key(key) }
keys_to_expire = keys.map { |key| cache_key(key) }
keys_to_expire += keys.map { |key| new_cache_key(key) } # NOTE Remove as part of #331319
Gitlab::Instrumentation::RedisClusterValidator.allow_cross_slot_commands do
redis.unlink(*keys)
redis.unlink(*keys_to_expire)
end
end
end
@ -73,5 +79,9 @@ module Gitlab
def with(&blk)
Gitlab::Redis::Cache.with(&blk) # rubocop:disable CodeReuse/ActiveRecord
end
def cache_namespace
Gitlab::Redis::Cache::CACHE_NAMESPACE
end
end
end

View File

@ -14726,6 +14726,9 @@ msgstr ""
msgid "Geo|Allowed Geo IP should contain valid IP addresses"
msgstr ""
msgid "Geo|Checksummed"
msgstr ""
msgid "Geo|Connection timeout can't be blank"
msgstr ""
@ -14828,6 +14831,9 @@ msgstr ""
msgid "Geo|Next sync scheduled at"
msgstr ""
msgid "Geo|No available replication slots"
msgstr ""
msgid "Geo|Node name can't be blank"
msgstr ""
@ -14840,10 +14846,13 @@ msgstr ""
msgid "Geo|Not synced yet"
msgstr ""
msgid "Geo|Nothing to checksum"
msgstr ""
msgid "Geo|Nothing to synchronize"
msgstr ""
msgid "Geo|Number of %{title}"
msgid "Geo|Nothing to verify"
msgstr ""
msgid "Geo|Offline"
@ -15035,6 +15044,9 @@ msgstr ""
msgid "Geo|Verification status"
msgstr ""
msgid "Geo|Verified"
msgstr ""
msgid "Geo|Waiting for scheduler"
msgstr ""
@ -34915,6 +34927,9 @@ msgstr ""
msgid "Until"
msgstr ""
msgid "Unused"
msgstr ""
msgid "Unused, previous indices: %{index_names} will be deleted after %{time} automatically."
msgstr ""
@ -35398,6 +35413,9 @@ msgstr ""
msgid "Use your smart card to authenticate with the LDAP server."
msgstr ""
msgid "Used"
msgstr ""
msgid "Used by members to sign in to your group in GitLab"
msgstr ""

View File

@ -297,10 +297,43 @@ module Trigger
end
class DatabaseTesting < Base
IDENTIFIABLE_NOTE_TAG = 'gitlab-org/database-team/gitlab-com-database-testing:identifiable-note'
def self.access_token
ENV['GITLABCOM_DATABASE_TESTING_ACCESS_TOKEN']
end
def invoke!(post_comment: false, downstream_job_name: nil)
pipeline = super
gitlab = gitlab_client(:upstream)
project_path = base_variables['TOP_UPSTREAM_SOURCE_PROJECT']
merge_request_id = base_variables['TOP_UPSTREAM_MERGE_REQUEST_IID']
comment = "<!-- #{IDENTIFIABLE_NOTE_TAG} --> \nStarted database testing [pipeline](https://ops.gitlab.net/#{downstream_project_path}/-/pipelines/#{pipeline.id}) " \
"(limited access). This comment will be updated once the pipeline has finished running."
# Look for a note to update
db_testing_notes = gitlab.merge_request_notes(project_path, merge_request_id).auto_paginate.select do |note|
note.body.include?(IDENTIFIABLE_NOTE_TAG)
end
note = db_testing_notes.max_by { |note| Time.parse(note.created_at) }
if note && note.type != 'DiscussionNote'
# The latest note has not led to a discussion. Update it.
gitlab.edit_merge_request_note(project_path, merge_request_id, note.id, comment)
puts "Updated comment:\n"
else
# This is the first note or the latest note has been discussed on the MR.
# Don't update, create new note instead.
note = gitlab.create_merge_request_note(project_path, merge_request_id, comment)
puts "Posted comment to:\n"
end
puts "https://gitlab.com/#{project_path}/-/merge_requests/#{merge_request_id}#note_#{note.id}"
end
private
def gitlab_client(type)
@ -356,6 +389,8 @@ module Trigger
INTERVAL = 60 # seconds
MAX_DURATION = 3600 * 3 # 3 hours
attr_reader :id
def self.unscoped_class_name
name.split('::').last
end
@ -405,7 +440,7 @@ module Trigger
private
attr_reader :project, :id, :gitlab_client, :start_time
attr_reader :project, :gitlab_client, :start_time
end
Job = Class.new(Pipeline)

View File

@ -375,6 +375,23 @@ RSpec.describe ProjectsController do
end
end
context 'when project is moved and git format is requested' do
let(:old_path) { project.path + 'old' }
before do
project.redirect_routes.create!(path: "#{project.namespace.full_path}/#{old_path}")
project.add_developer(user)
sign_in(user)
end
it 'redirects to new project path' do
get :show, params: { namespace_id: project.namespace, id: old_path }, format: :git
expect(response).to redirect_to(project_path(project, format: :git))
end
end
context 'when the project is forked and has a repository', :request_store do
let(:public_project) { create(:project, :public, :repository) }
let(:other_user) { create(:user) }

View File

@ -31,15 +31,7 @@ RSpec.describe Packages::Pypi::PackageFinder do
context 'within a group' do
let(:scope) { group }
it { expect { subject }.to raise_error(ActiveRecord::RecordNotFound) }
context 'user with access' do
before do
project.add_developer(user)
end
it { is_expected.to eq(package2) }
end
it { is_expected.to eq(package2) }
end
end
end

View File

@ -1,280 +0,0 @@
import '~/commons';
import MockAdapter from 'axios-mock-adapter';
import Vue from 'vue';
import mountComponent from 'helpers/vue_mount_component_helper';
import Api from '~/api';
import pipelinesTable from '~/commit/pipelines/pipelines_table.vue';
import axios from '~/lib/utils/axios_utils';
describe('Pipelines table in Commits and Merge requests', () => {
const jsonFixtureName = 'pipelines/pipelines.json';
let pipeline;
let PipelinesTable;
let mock;
let vm;
const props = {
endpoint: 'endpoint.json',
emptyStateSvgPath: 'foo',
errorStateSvgPath: 'foo',
};
const findRunPipelineBtn = () => vm.$el.querySelector('[data-testid="run_pipeline_button"]');
const findRunPipelineBtnMobile = () =>
vm.$el.querySelector('[data-testid="run_pipeline_button_mobile"]');
beforeEach(() => {
mock = new MockAdapter(axios);
const { pipelines } = getJSONFixture(jsonFixtureName);
PipelinesTable = Vue.extend(pipelinesTable);
pipeline = pipelines.find((p) => p.user !== null && p.commit !== null);
});
afterEach(() => {
vm.$destroy();
mock.restore();
});
describe('successful request', () => {
describe('without pipelines', () => {
beforeEach(() => {
mock.onGet('endpoint.json').reply(200, []);
vm = mountComponent(PipelinesTable, props);
});
it('should render the empty state', (done) => {
setImmediate(() => {
expect(vm.$el.querySelector('.empty-state')).toBeDefined();
expect(vm.$el.querySelector('.realtime-loading')).toBe(null);
expect(vm.$el.querySelector('.js-pipelines-error-state')).toBe(null);
done();
});
});
});
describe('with pipelines', () => {
beforeEach(() => {
mock.onGet('endpoint.json').reply(200, [pipeline]);
vm = mountComponent(PipelinesTable, props);
});
it('should render a table with the received pipelines', (done) => {
setImmediate(() => {
expect(vm.$el.querySelectorAll('.ci-table .commit').length).toEqual(1);
expect(vm.$el.querySelector('.realtime-loading')).toBe(null);
expect(vm.$el.querySelector('.empty-state')).toBe(null);
expect(vm.$el.querySelector('.js-pipelines-error-state')).toBe(null);
done();
});
});
describe('with pagination', () => {
it('should make an API request when using pagination', (done) => {
setImmediate(() => {
jest.spyOn(vm, 'updateContent').mockImplementation(() => {});
vm.store.state.pageInfo = {
page: 1,
total: 10,
perPage: 2,
nextPage: 2,
totalPages: 5,
};
vm.$nextTick(() => {
vm.$el.querySelector('.next-page-item').click();
expect(vm.updateContent).toHaveBeenCalledWith({ page: '2' });
done();
});
});
});
});
});
describe('pipeline badge counts', () => {
beforeEach(() => {
mock.onGet('endpoint.json').reply(200, [pipeline]);
});
it('should receive update-pipelines-count event', (done) => {
const element = document.createElement('div');
document.body.appendChild(element);
element.addEventListener('update-pipelines-count', (event) => {
expect(event.detail.pipelines).toEqual([pipeline]);
done();
});
vm = mountComponent(PipelinesTable, props);
element.appendChild(vm.$el);
});
});
});
describe('run pipeline button', () => {
let pipelineCopy;
beforeEach(() => {
pipelineCopy = { ...pipeline };
});
describe('when latest pipeline has detached flag', () => {
it('renders the run pipeline button', (done) => {
pipelineCopy.flags.detached_merge_request_pipeline = true;
pipelineCopy.flags.merge_request_pipeline = true;
mock.onGet('endpoint.json').reply(200, [pipelineCopy]);
vm = mountComponent(PipelinesTable, { ...props });
setImmediate(() => {
expect(findRunPipelineBtn()).not.toBeNull();
expect(findRunPipelineBtnMobile()).not.toBeNull();
done();
});
});
});
describe('when latest pipeline does not have detached flag', () => {
it('does not render the run pipeline button', (done) => {
pipelineCopy.flags.detached_merge_request_pipeline = false;
pipelineCopy.flags.merge_request_pipeline = false;
mock.onGet('endpoint.json').reply(200, [pipelineCopy]);
vm = mountComponent(PipelinesTable, { ...props });
setImmediate(() => {
expect(findRunPipelineBtn()).toBeNull();
expect(findRunPipelineBtnMobile()).toBeNull();
done();
});
});
});
describe('on click', () => {
const findModal = () =>
document.querySelector('#create-pipeline-for-fork-merge-request-modal');
beforeEach((done) => {
pipelineCopy.flags.detached_merge_request_pipeline = true;
mock.onGet('endpoint.json').reply(200, [pipelineCopy]);
vm = mountComponent(PipelinesTable, {
...props,
canRunPipeline: true,
projectId: '5',
mergeRequestId: 3,
});
jest.spyOn(Api, 'postMergeRequestPipeline').mockReturnValue(Promise.resolve());
setImmediate(() => {
done();
});
});
it('on desktop, shows a loading button', (done) => {
findRunPipelineBtn().click();
vm.$nextTick(() => {
expect(findModal()).toBeNull();
expect(findRunPipelineBtn().disabled).toBe(true);
expect(findRunPipelineBtn().querySelector('.gl-spinner')).not.toBeNull();
setImmediate(() => {
expect(findRunPipelineBtn().disabled).toBe(false);
expect(findRunPipelineBtn().querySelector('.gl-spinner')).toBeNull();
done();
});
});
});
it('on mobile, shows a loading button', (done) => {
findRunPipelineBtnMobile().click();
vm.$nextTick(() => {
expect(findModal()).toBeNull();
expect(findModal()).toBeNull();
expect(findRunPipelineBtn().querySelector('.gl-spinner')).not.toBeNull();
setImmediate(() => {
expect(findRunPipelineBtn().disabled).toBe(false);
expect(findRunPipelineBtn().querySelector('.gl-spinner')).toBeNull();
done();
});
});
});
});
describe('on click for fork merge request', () => {
const findModal = () =>
document.querySelector('#create-pipeline-for-fork-merge-request-modal');
beforeEach((done) => {
pipelineCopy.flags.detached_merge_request_pipeline = true;
mock.onGet('endpoint.json').reply(200, [pipelineCopy]);
vm = mountComponent(PipelinesTable, {
...props,
projectId: '5',
mergeRequestId: 3,
canCreatePipelineInTargetProject: true,
sourceProjectFullPath: 'test/parent-project',
targetProjectFullPath: 'test/fork-project',
});
jest.spyOn(Api, 'postMergeRequestPipeline').mockReturnValue(Promise.resolve());
setImmediate(() => {
done();
});
});
it('on desktop, shows a security warning modal', (done) => {
findRunPipelineBtn().click();
vm.$nextTick(() => {
expect(findModal()).not.toBeNull();
done();
});
});
it('on mobile, shows a security warning modal', (done) => {
findRunPipelineBtnMobile().click();
vm.$nextTick(() => {
expect(findModal()).not.toBeNull();
done();
});
});
});
});
describe('unsuccessfull request', () => {
beforeEach(() => {
mock.onGet('endpoint.json').reply(500, []);
vm = mountComponent(PipelinesTable, props);
});
it('should render error state', (done) => {
setImmediate(() => {
expect(vm.$el.querySelector('.js-pipelines-error-state')).toBeDefined();
expect(vm.$el.querySelector('.realtime-loading')).toBe(null);
expect(vm.$el.querySelector('.ci-table')).toBe(null);
done();
});
});
});
});

View File

@ -0,0 +1,253 @@
import { GlEmptyState, GlLoadingIcon, GlModal, GlTable } from '@gitlab/ui';
import { mount } from '@vue/test-utils';
import MockAdapter from 'axios-mock-adapter';
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import waitForPromises from 'helpers/wait_for_promises';
import Api from '~/api';
import PipelinesTable from '~/commit/pipelines/pipelines_table.vue';
import axios from '~/lib/utils/axios_utils';
describe('Pipelines table in Commits and Merge requests', () => {
const jsonFixtureName = 'pipelines/pipelines.json';
let wrapper;
let pipeline;
let mock;
const findRunPipelineBtn = () => wrapper.findByTestId('run_pipeline_button');
const findRunPipelineBtnMobile = () => wrapper.findByTestId('run_pipeline_button_mobile');
const findLoadingState = () => wrapper.findComponent(GlLoadingIcon);
const findEmptyState = () => wrapper.findComponent(GlEmptyState);
const findTable = () => wrapper.findComponent(GlTable);
const findTableRows = () => wrapper.findAllByTestId('pipeline-table-row');
const findModal = () => wrapper.findComponent(GlModal);
const createComponent = (props = {}) => {
wrapper = extendedWrapper(
mount(PipelinesTable, {
propsData: {
endpoint: 'endpoint.json',
emptyStateSvgPath: 'foo',
errorStateSvgPath: 'foo',
...props,
},
}),
);
};
beforeEach(() => {
mock = new MockAdapter(axios);
const { pipelines } = getJSONFixture(jsonFixtureName);
pipeline = pipelines.find((p) => p.user !== null && p.commit !== null);
});
afterEach(() => {
wrapper.destroy();
mock.restore();
});
describe('successful request', () => {
describe('without pipelines', () => {
beforeEach(async () => {
mock.onGet('endpoint.json').reply(200, []);
createComponent();
await waitForPromises();
});
it('should render the empty state', () => {
expect(findTableRows()).toHaveLength(0);
expect(findLoadingState().exists()).toBe(false);
expect(findEmptyState().exists()).toBe(false);
});
});
describe('with pipelines', () => {
beforeEach(async () => {
mock.onGet('endpoint.json').reply(200, [pipeline]);
createComponent();
await waitForPromises();
});
it('should render a table with the received pipelines', () => {
expect(findTable().exists()).toBe(true);
expect(findTableRows()).toHaveLength(1);
expect(findLoadingState().exists()).toBe(false);
expect(findEmptyState().exists()).toBe(false);
});
describe('with pagination', () => {
it('should make an API request when using pagination', async () => {
jest.spyOn(wrapper.vm, 'updateContent').mockImplementation(() => {});
await wrapper.setData({
store: {
state: {
pageInfo: {
page: 1,
total: 10,
perPage: 2,
nextPage: 2,
totalPages: 5,
},
},
},
});
wrapper.find('.next-page-item').trigger('click');
expect(wrapper.vm.updateContent).toHaveBeenCalledWith({ page: '2' });
});
});
describe('pipeline badge counts', () => {
it('should receive update-pipelines-count event', (done) => {
const element = document.createElement('div');
document.body.appendChild(element);
element.addEventListener('update-pipelines-count', (event) => {
expect(event.detail.pipelines).toEqual([pipeline]);
done();
});
createComponent();
element.appendChild(wrapper.vm.$el);
});
});
});
});
describe('run pipeline button', () => {
let pipelineCopy;
beforeEach(() => {
pipelineCopy = { ...pipeline };
});
describe('when latest pipeline has detached flag', () => {
it('renders the run pipeline button', async () => {
pipelineCopy.flags.detached_merge_request_pipeline = true;
pipelineCopy.flags.merge_request_pipeline = true;
mock.onGet('endpoint.json').reply(200, [pipelineCopy]);
createComponent();
await waitForPromises();
expect(findRunPipelineBtn().exists()).toBe(true);
expect(findRunPipelineBtnMobile().exists()).toBe(true);
});
});
describe('when latest pipeline does not have detached flag', () => {
it('does not render the run pipeline button', async () => {
pipelineCopy.flags.detached_merge_request_pipeline = false;
pipelineCopy.flags.merge_request_pipeline = false;
mock.onGet('endpoint.json').reply(200, [pipelineCopy]);
createComponent();
await waitForPromises();
expect(findRunPipelineBtn().exists()).toBe(false);
expect(findRunPipelineBtnMobile().exists()).toBe(false);
});
});
describe('on click', () => {
beforeEach(async () => {
pipelineCopy.flags.detached_merge_request_pipeline = true;
mock.onGet('endpoint.json').reply(200, [pipelineCopy]);
createComponent({
canRunPipeline: true,
projectId: '5',
mergeRequestId: 3,
});
jest.spyOn(Api, 'postMergeRequestPipeline').mockReturnValue(Promise.resolve());
await waitForPromises();
});
it('on desktop, shows a loading button', async () => {
await findRunPipelineBtn().trigger('click');
expect(findRunPipelineBtn().props('loading')).toBe(true);
await waitForPromises();
expect(findRunPipelineBtn().props('loading')).toBe(false);
});
it('on mobile, shows a loading button', async () => {
await findRunPipelineBtnMobile().trigger('click');
expect(findRunPipelineBtn().props('loading')).toBe(true);
await waitForPromises();
expect(findRunPipelineBtn().props('disabled')).toBe(false);
expect(findRunPipelineBtn().props('loading')).toBe(false);
});
});
describe('on click for fork merge request', () => {
beforeEach(async () => {
pipelineCopy.flags.detached_merge_request_pipeline = true;
mock.onGet('endpoint.json').reply(200, [pipelineCopy]);
createComponent({
projectId: '5',
mergeRequestId: 3,
canCreatePipelineInTargetProject: true,
sourceProjectFullPath: 'test/parent-project',
targetProjectFullPath: 'test/fork-project',
});
jest.spyOn(Api, 'postMergeRequestPipeline').mockReturnValue(Promise.resolve());
await waitForPromises();
});
it('on desktop, shows a security warning modal', async () => {
await findRunPipelineBtn().trigger('click');
await wrapper.vm.$nextTick();
expect(findModal()).not.toBeNull();
});
it('on mobile, shows a security warning modal', async () => {
await findRunPipelineBtnMobile().trigger('click');
expect(findModal()).not.toBeNull();
});
});
});
describe('unsuccessfull request', () => {
beforeEach(async () => {
mock.onGet('endpoint.json').reply(500, []);
createComponent();
await waitForPromises();
});
it('should render error state', () => {
expect(findEmptyState().text()).toBe(
'There was an error fetching the pipelines. Try again in a few moments or contact your support team.',
);
});
});
});

View File

@ -357,27 +357,46 @@ describe('Flash', () => {
});
describe('removeFlashClickListener', () => {
beforeEach(() => {
document.body.innerHTML += `
<div class="flash-container">
<div class="flash">
<div class="close-icon js-close-icon"></div>
let el;
describe('with close icon', () => {
beforeEach(() => {
el = document.createElement('div');
el.innerHTML = `
<div class="flash-container">
<div class="flash">
<div class="close-icon js-close-icon"></div>
</div>
</div>
</div>
`;
`;
});
it('removes global flash on click', (done) => {
removeFlashClickListener(el, false);
el.querySelector('.js-close-icon').click();
setImmediate(() => {
expect(document.querySelector('.flash')).toBeNull();
done();
});
});
});
it('removes global flash on click', (done) => {
const flashEl = document.querySelector('.flash');
describe('without close icon', () => {
beforeEach(() => {
el = document.createElement('div');
el.innerHTML = `
<div class="flash-container">
<div class="flash">
</div>
</div>
`;
});
removeFlashClickListener(flashEl, false);
flashEl.querySelector('.js-close-icon').click();
setImmediate(() => {
expect(document.querySelector('.flash')).toBeNull();
done();
it('does not throw', () => {
expect(() => removeFlashClickListener(el, false)).not.toThrow();
});
});
});

View File

@ -80,18 +80,22 @@ describe('Number Utils', () => {
describe('numberToHumanSize', () => {
it('should return bytes', () => {
expect(numberToHumanSize(654)).toEqual('654 bytes');
expect(numberToHumanSize(-654)).toEqual('-654 bytes');
});
it('should return KiB', () => {
expect(numberToHumanSize(1079)).toEqual('1.05 KiB');
expect(numberToHumanSize(-1079)).toEqual('-1.05 KiB');
});
it('should return MiB', () => {
expect(numberToHumanSize(10485764)).toEqual('10.00 MiB');
expect(numberToHumanSize(-10485764)).toEqual('-10.00 MiB');
});
it('should return GiB', () => {
expect(numberToHumanSize(10737418240)).toEqual('10.00 GiB');
expect(numberToHumanSize(-10737418240)).toEqual('-10.00 GiB');
});
});

View File

@ -24,6 +24,42 @@ RSpec.describe Gitlab::CurrentSettings do
end
end
describe '.signup_limited?' do
subject { described_class.signup_limited? }
context 'when there are allowed domains' do
before do
create(:application_setting, domain_allowlist: ['www.gitlab.com'])
end
it { is_expected.to be_truthy }
end
context 'when there are email restrictions' do
before do
create(:application_setting, email_restrictions_enabled: true)
end
it { is_expected.to be_truthy }
end
context 'when the admin has to approve signups' do
before do
create(:application_setting, require_admin_approval_after_user_signup: true)
end
it { is_expected.to be_truthy }
end
context 'when there are no restrictions' do
before do
create(:application_setting, domain_allowlist: [], email_restrictions_enabled: false, require_admin_approval_after_user_signup: false)
end
it { is_expected.to be_falsey }
end
end
describe '.signup_disabled?' do
subject { described_class.signup_disabled? }

View File

@ -168,7 +168,7 @@ RSpec.describe Gitlab::Email::Handler::ServiceDeskHandler do
end
context 'when using service desk key' do
let_it_be(:service_desk_settings) { create(:service_desk_setting, project: project, project_key: 'mykey') }
let_it_be(:service_desk_key) { 'mykey' }
let(:email_raw) { service_desk_fixture('emails/service_desk_custom_address.eml') }
let(:receiver) { Gitlab::Email::ServiceDeskReceiver.new(email_raw) }
@ -176,6 +176,10 @@ RSpec.describe Gitlab::Email::Handler::ServiceDeskHandler do
stub_service_desk_email_setting(enabled: true, address: 'support+%{key}@example.com')
end
before_all do
create(:service_desk_setting, project: project, project_key: service_desk_key)
end
it_behaves_like 'a new issue request'
context 'when there is no project with the key' do
@ -193,6 +197,20 @@ RSpec.describe Gitlab::Email::Handler::ServiceDeskHandler do
expect { receiver.execute }.to raise_error(Gitlab::Email::ProjectNotFound)
end
end
context 'when there are multiple projects with same key' do
let_it_be(:project_with_same_key) { create(:project, group: group, service_desk_enabled: true) }
let(:email_raw) { service_desk_fixture('emails/service_desk_custom_address.eml', slug: project_with_same_key.full_path_slug.to_s) }
before do
create(:service_desk_setting, project: project_with_same_key, project_key: service_desk_key)
end
it 'process email for project with matching slug' do
expect { receiver.execute }.to change { Issue.count }.by(1)
expect(Issue.last.project).to eq(project_with_same_key)
end
end
end
end

View File

@ -7,6 +7,7 @@ RSpec.describe Gitlab::RepositorySetCache, :clean_gitlab_redis_cache do
let(:repository) { project.repository }
let(:namespace) { "#{repository.full_path}:#{project.id}" }
let(:gitlab_cache_namespace) { Gitlab::Redis::Cache::CACHE_NAMESPACE }
let(:cache) { described_class.new(repository) }
describe '#cache_key' do
@ -52,6 +53,24 @@ RSpec.describe Gitlab::RepositorySetCache, :clean_gitlab_redis_cache do
end
end
describe '#write' do
subject(:write_cache) { cache.write('branch_names', ['main']) }
it 'writes the value to the cache' do
write_cache
redis_keys = Gitlab::Redis::Cache.with { |redis| redis.scan(0, match: "*") }.last
expect(redis_keys).to include("branch_names:#{namespace}:set")
expect(cache.fetch('branch_names')).to contain_exactly('main')
end
it 'sets the expiry of the set' do
write_cache
expect(cache.ttl('branch_names')).to be_within(1).of(cache.expires_in.seconds)
end
end
describe '#expire' do
subject { cache.expire(*keys) }
@ -75,6 +94,12 @@ RSpec.describe Gitlab::RepositorySetCache, :clean_gitlab_redis_cache do
expect(cache.read(:foo)).to be_empty
end
it 'expires the new key format' do
expect_any_instance_of(Redis).to receive(:unlink).with(cache.cache_key(:foo), cache.new_cache_key(:foo)) # rubocop:disable RSpec/AnyInstanceOf
subject
end
end
context 'multiple keys' do

View File

@ -0,0 +1,45 @@
# frozen_string_literal: true
require 'spec_helper'
require Rails.root.join('db', 'post_migrate', '20210513163904_cleanup_move_container_registry_enabled_to_project_feature.rb')
RSpec.describe CleanupMoveContainerRegistryEnabledToProjectFeature, :migration do
let(:namespace) { table(:namespaces).create!(name: 'gitlab', path: 'gitlab-org') }
let(:non_null_project_features) { { pages_access_level: 20 } }
let(:bg_class_name) { 'MoveContainerRegistryEnabledToProjectFeature' }
let!(:project1) { table(:projects).create!(namespace_id: namespace.id, name: 'project 1', container_registry_enabled: true) }
let!(:project2) { table(:projects).create!(namespace_id: namespace.id, name: 'project 2', container_registry_enabled: false) }
let!(:project3) { table(:projects).create!(namespace_id: namespace.id, name: 'project 3', container_registry_enabled: nil) }
let!(:project4) { table(:projects).create!(namespace_id: namespace.id, name: 'project 4', container_registry_enabled: true) }
let!(:project5) { table(:projects).create!(namespace_id: namespace.id, name: 'project 5', container_registry_enabled: false) }
let!(:project6) { table(:projects).create!(namespace_id: namespace.id, name: 'project 6', container_registry_enabled: nil) }
let!(:project_feature1) { table(:project_features).create!(project_id: project1.id, container_registry_access_level: 20, **non_null_project_features) }
let!(:project_feature2) { table(:project_features).create!(project_id: project2.id, container_registry_access_level: 0, **non_null_project_features) }
let!(:project_feature3) { table(:project_features).create!(project_id: project3.id, container_registry_access_level: 0, **non_null_project_features) }
let!(:project_feature4) { table(:project_features).create!(project_id: project4.id, container_registry_access_level: 0, **non_null_project_features) }
let!(:project_feature5) { table(:project_features).create!(project_id: project5.id, container_registry_access_level: 20, **non_null_project_features) }
let!(:project_feature6) { table(:project_features).create!(project_id: project6.id, container_registry_access_level: 20, **non_null_project_features) }
let!(:background_migration_job1) { table(:background_migration_jobs).create!(class_name: bg_class_name, arguments: [project4.id, project5.id], status: 0) }
let!(:background_migration_job2) { table(:background_migration_jobs).create!(class_name: bg_class_name, arguments: [project6.id, project6.id], status: 0) }
let!(:background_migration_job3) { table(:background_migration_jobs).create!(class_name: bg_class_name, arguments: [project1.id, project3.id], status: 1) }
it 'steals remaining jobs, updates any remaining rows and deletes background_migration_jobs rows' do
expect(Gitlab::BackgroundMigration).to receive(:steal).with(bg_class_name).and_call_original
migrate!
expect(project_feature1.reload.container_registry_access_level).to eq(20)
expect(project_feature2.reload.container_registry_access_level).to eq(0)
expect(project_feature3.reload.container_registry_access_level).to eq(0)
expect(project_feature4.reload.container_registry_access_level).to eq(20)
expect(project_feature5.reload.container_registry_access_level).to eq(0)
expect(project_feature6.reload.container_registry_access_level).to eq(0)
expect(table(:background_migration_jobs).where(class_name: bg_class_name).count).to eq(0)
end
end

View File

@ -1042,17 +1042,6 @@ RSpec.describe Namespace do
end
end
describe '#all_pipelines' do
let(:group) { create(:group) }
let(:child) { create(:group, parent: group) }
let!(:project1) { create(:project_empty_repo, namespace: group) }
let!(:project2) { create(:project_empty_repo, namespace: child) }
let!(:pipeline1) { create(:ci_empty_pipeline, project: project1) }
let!(:pipeline2) { create(:ci_empty_pipeline, project: project2) }
it { expect(group.all_pipelines.to_a).to match_array([pipeline1, pipeline2]) }
end
describe '#share_with_group_lock with subgroups' do
context 'when creating a subgroup' do
let(:subgroup) { create(:group, parent: root_group )}

View File

@ -1584,19 +1584,20 @@ RSpec.describe Project, factory_default: :keep do
end
end
describe '.find_by_service_desk_project_key' do
it 'returns the correct project' do
describe '.with_service_desk_key' do
it 'returns projects with given key' do
project1 = create(:project)
project2 = create(:project)
create(:service_desk_setting, project: project1, project_key: 'key1')
create(:service_desk_setting, project: project2, project_key: 'key2')
create(:service_desk_setting, project: project2, project_key: 'key1')
create(:service_desk_setting, project_key: 'key2')
create(:service_desk_setting)
expect(Project.find_by_service_desk_project_key('key1')).to eq(project1)
expect(Project.find_by_service_desk_project_key('key2')).to eq(project2)
expect(Project.with_service_desk_key('key1')).to contain_exactly(project1, project2)
end
it 'returns nil if there is no project with the key' do
expect(Project.find_by_service_desk_project_key('some_key')).to be_nil
it 'returns empty if there is no project with the key' do
expect(Project.with_service_desk_key('key1')).to be_empty
end
end

View File

@ -5,45 +5,52 @@ require 'spec_helper'
RSpec.describe ::Packages::Pypi::PackagePresenter do
using RSpec::Parameterized::TableSyntax
let_it_be(:project) { create(:project) }
let_it_be(:group) { create(:group) }
let_it_be(:project) { create(:project, group: group) }
let_it_be(:package_name) { 'sample-project' }
let_it_be(:package1) { create(:pypi_package, project: project, name: package_name, version: '1.0.0') }
let_it_be(:package2) { create(:pypi_package, project: project, name: package_name, version: '2.0.0') }
let(:packages) { [package1, package2] }
let(:presenter) { described_class.new(packages, project) }
let(:file) { package.package_files.first }
let(:filename) { file.file_name }
subject(:presenter) { described_class.new(packages, project_or_group).body}
describe '#body' do
subject { presenter.body}
shared_examples_for "pypi package presenter" do
let(:file) { package.package_files.first }
let(:filename) { file.file_name }
let(:expected_file) { "<a href=\"http://localhost/api/v4/projects/#{project.id}/packages/pypi/files/#{file.file_sha256}/#{filename}#sha256=#{file.file_sha256}\" data-requires-python=\"#{expected_python_version}\">#{filename}</a><br>" }
before do
package.pypi_metadatum.required_python = python_version
where(:version, :expected_version, :with_package1) do
'>=2.7' | '&gt;=2.7' | true
'"><script>alert(1)</script>' | '&quot;&gt;&lt;script&gt;alert(1)&lt;/script&gt;' | true
'>=2.7, !=3.0' | '&gt;=2.7, !=3.0' | false
end
it { is_expected.to include expected_file }
with_them do
let(:python_version) { version }
let(:expected_python_version) { expected_version }
let(:package) { with_package1 ? package1 : package2 }
before do
package.pypi_metadatum.required_python = python_version
end
it { is_expected.to include expected_file }
end
end
it_behaves_like "pypi package presenter" do
let(:python_version) { '>=2.7' }
let(:expected_python_version) { '&gt;=2.7' }
let(:package) { package1 }
context 'for project' do
let(:project_or_group) { project }
let(:expected_file) { "<a href=\"http://localhost/api/v4/projects/#{project.id}/packages/pypi/files/#{file.file_sha256}/#{filename}#sha256=#{file.file_sha256}\" data-requires-python=\"#{expected_python_version}\">#{filename}</a><br>" }
it_behaves_like 'pypi package presenter'
end
it_behaves_like "pypi package presenter" do
let(:python_version) { '"><script>alert(1)</script>' }
let(:expected_python_version) { '&quot;&gt;&lt;script&gt;alert(1)&lt;/script&gt;' }
let(:package) { package1 }
end
context 'for group' do
let(:project_or_group) { group }
let(:expected_file) { "<a href=\"http://localhost/api/v4/groups/#{group.id}/-/packages/pypi/files/#{file.file_sha256}/#{filename}#sha256=#{file.file_sha256}\" data-requires-python=\"#{expected_python_version}\">#{filename}</a><br>" }
it_behaves_like "pypi package presenter" do
let(:python_version) { '>=2.7, !=3.0' }
let(:expected_python_version) { '&gt;=2.7, !=3.0' }
let(:package) { package2 }
it_behaves_like 'pypi package presenter'
end
end
end

View File

@ -40,10 +40,21 @@ RSpec.describe API::DebianProjectPackages do
let(:method) { :put }
let(:url) { "/projects/#{container.id}/packages/debian/#{file_name}" }
it_behaves_like 'Debian repository write endpoint', 'upload request', :created
context 'with a deb' do
let(:file_name) { 'libsample0_1.2.3~alpha2_amd64.deb' }
it_behaves_like 'Debian repository write endpoint', 'upload request', :created
end
context 'with a changes file' do
let(:file_name) { 'sample_1.2.3~alpha2_amd64.changes' }
it_behaves_like 'Debian repository write endpoint', 'upload request', :created
end
end
describe 'PUT projects/:id/packages/debian/:file_name/authorize' do
let(:file_name) { 'libsample0_1.2.3~alpha2_amd64.deb' }
let(:method) { :put }
let(:url) { "/projects/#{container.id}/packages/debian/#{file_name}/authorize" }

View File

@ -27,9 +27,10 @@ RSpec.describe API::Lint do
end
end
context 'when signup settings are enabled' do
context 'when signup is enabled and not limited' do
before do
Gitlab::CurrentSettings.signup_enabled = true
stub_application_setting(domain_allowlist: [], email_restrictions_enabled: false, require_admin_approval_after_user_signup: false)
end
context 'when unauthenticated' do
@ -50,6 +51,31 @@ RSpec.describe API::Lint do
end
end
context 'when limited signup is enabled' do
before do
stub_application_setting(domain_allowlist: ['www.gitlab.com'])
Gitlab::CurrentSettings.signup_enabled = true
end
context 'when unauthenticated' do
it 'returns unauthorized' do
post api('/ci/lint'), params: { content: 'content' }
expect(response).to have_gitlab_http_status(:unauthorized)
end
end
context 'when authenticated' do
let_it_be(:api_user) { create(:user) }
it 'returns authentication success' do
post api('/ci/lint', api_user), params: { content: 'content' }
expect(response).to have_gitlab_http_status(:ok)
end
end
end
context 'when authenticated' do
let_it_be(:api_user) { create(:user) }

View File

@ -8,69 +8,57 @@ RSpec.describe API::PypiPackages do
using RSpec::Parameterized::TableSyntax
let_it_be(:user) { create(:user) }
let_it_be(:project, reload: true) { create(:project, :public) }
let_it_be_with_reload(:group) { create(:group) }
let_it_be_with_reload(:project) { create(:project, :public, group: group) }
let_it_be(:personal_access_token) { create(:personal_access_token, user: user) }
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_it_be(:job) { create(:ci_build, :running, user: user) }
let(:headers) { {} }
describe 'GET /api/v4/projects/:id/packages/pypi/simple/:package_name' do
context 'simple API endpoint' do
let_it_be(:package) { create(:pypi_package, project: project) }
let(:url) { "/projects/#{project.id}/packages/pypi/simple/#{package.name}" }
subject { get api(url) }
subject { get api(url), headers: headers }
context 'with valid project' do
where(:project_visibility_level, :user_role, :member, :user_token, :shared_examples_name, :expected_status) do
'PUBLIC' | :developer | true | true | 'PyPI package versions' | :success
'PUBLIC' | :guest | true | true | 'PyPI package versions' | :success
'PUBLIC' | :developer | true | false | 'PyPI package versions' | :success
'PUBLIC' | :guest | true | false | 'PyPI package versions' | :success
'PUBLIC' | :developer | false | true | 'PyPI package versions' | :success
'PUBLIC' | :guest | false | true | 'PyPI package versions' | :success
'PUBLIC' | :developer | false | false | 'PyPI package versions' | :success
'PUBLIC' | :guest | false | false | 'PyPI package versions' | :success
'PUBLIC' | :anonymous | false | true | 'PyPI package versions' | :success
'PRIVATE' | :developer | true | true | 'PyPI package versions' | :success
'PRIVATE' | :guest | true | true | 'process PyPI api request' | :forbidden
'PRIVATE' | :developer | true | false | 'process PyPI api request' | :unauthorized
'PRIVATE' | :guest | true | false | 'process PyPI api request' | :unauthorized
'PRIVATE' | :developer | false | true | 'process PyPI api request' | :not_found
'PRIVATE' | :guest | false | true | 'process PyPI api request' | :not_found
'PRIVATE' | :developer | false | false | 'process PyPI api request' | :unauthorized
'PRIVATE' | :guest | false | false | 'process PyPI api request' | :unauthorized
'PRIVATE' | :anonymous | false | true | 'process PyPI api request' | :unauthorized
end
describe 'GET /api/v4/groups/:id/-/packages/pypi/simple/:package_name' do
let(:url) { "/groups/#{group.id}/-/packages/pypi/simple/#{package.name}" }
with_them do
let(:token) { user_token ? personal_access_token.token : 'wrong' }
let(:headers) { user_role == :anonymous ? {} : basic_auth_header(user.username, token) }
it_behaves_like 'pypi simple API endpoint'
it_behaves_like 'rejects PyPI access with unknown group id'
subject { get api(url), headers: headers }
context 'deploy tokens' do
let_it_be(:group_deploy_token) { create(:group_deploy_token, deploy_token: deploy_token, group: group) }
before do
project.update!(visibility_level: Gitlab::VisibilityLevel.const_get(project_visibility_level, false))
project.update_column(:visibility_level, Gitlab::VisibilityLevel::PRIVATE)
group.update_column(:visibility_level, Gitlab::VisibilityLevel::PRIVATE)
end
it_behaves_like params[:shared_examples_name], params[:user_role], params[:expected_status], params[:member]
it_behaves_like 'deploy token for package GET requests'
end
context 'job token' do
before do
project.update_column(:visibility_level, Gitlab::VisibilityLevel::PRIVATE)
group.update_column(:visibility_level, Gitlab::VisibilityLevel::PRIVATE)
group.add_developer(user)
end
it_behaves_like 'job token for package GET requests'
end
it_behaves_like 'a pypi user namespace endpoint'
end
context 'with a normalized package name' do
let_it_be(:package) { create(:pypi_package, project: project, name: 'my.package') }
let(:url) { "/projects/#{project.id}/packages/pypi/simple/my-package" }
let(:headers) { basic_auth_header(user.username, personal_access_token.token) }
describe 'GET /api/v4/projects/:id/packages/pypi/simple/:package_name' do
let(:url) { "/projects/#{project.id}/packages/pypi/simple/#{package.name}" }
subject { get api(url), headers: headers }
it_behaves_like 'PyPI package versions', :developer, :success
it_behaves_like 'pypi simple API endpoint'
it_behaves_like 'rejects PyPI access with unknown project id'
it_behaves_like 'deploy token for package GET requests'
it_behaves_like 'job token for package GET requests'
end
it_behaves_like 'deploy token for package GET requests'
it_behaves_like 'job token for package GET requests'
it_behaves_like 'rejects PyPI access with unknown project id'
end
describe 'POST /api/v4/projects/:id/packages/pypi/authorize' do
@ -82,25 +70,25 @@ RSpec.describe API::PypiPackages do
subject { post api(url), headers: headers }
context 'with valid project' do
where(:project_visibility_level, :user_role, :member, :user_token, :shared_examples_name, :expected_status) do
'PUBLIC' | :developer | true | true | 'process PyPI api request' | :success
'PUBLIC' | :guest | true | true | 'process PyPI api request' | :forbidden
'PUBLIC' | :developer | true | false | 'process PyPI api request' | :unauthorized
'PUBLIC' | :guest | true | false | 'process PyPI api request' | :unauthorized
'PUBLIC' | :developer | false | true | 'process PyPI api request' | :forbidden
'PUBLIC' | :guest | false | true | 'process PyPI api request' | :forbidden
'PUBLIC' | :developer | false | false | 'process PyPI api request' | :unauthorized
'PUBLIC' | :guest | false | false | 'process PyPI api request' | :unauthorized
'PUBLIC' | :anonymous | false | true | 'process PyPI api request' | :unauthorized
'PRIVATE' | :developer | true | true | 'process PyPI api request' | :success
'PRIVATE' | :guest | true | true | 'process PyPI api request' | :forbidden
'PRIVATE' | :developer | true | false | 'process PyPI api request' | :unauthorized
'PRIVATE' | :guest | true | false | 'process PyPI api request' | :unauthorized
'PRIVATE' | :developer | false | true | 'process PyPI api request' | :not_found
'PRIVATE' | :guest | false | true | 'process PyPI api request' | :not_found
'PRIVATE' | :developer | false | false | 'process PyPI api request' | :unauthorized
'PRIVATE' | :guest | false | false | 'process PyPI api request' | :unauthorized
'PRIVATE' | :anonymous | false | true | 'process PyPI api request' | :unauthorized
where(:visibility_level, :user_role, :member, :user_token, :shared_examples_name, :expected_status) do
:public | :developer | true | true | 'process PyPI api request' | :success
:public | :guest | true | true | 'process PyPI api request' | :forbidden
:public | :developer | true | false | 'process PyPI api request' | :unauthorized
:public | :guest | true | false | 'process PyPI api request' | :unauthorized
:public | :developer | false | true | 'process PyPI api request' | :forbidden
:public | :guest | false | true | 'process PyPI api request' | :forbidden
:public | :developer | false | false | 'process PyPI api request' | :unauthorized
:public | :guest | false | false | 'process PyPI api request' | :unauthorized
:public | :anonymous | false | true | 'process PyPI api request' | :unauthorized
:private | :developer | true | true | 'process PyPI api request' | :success
:private | :guest | true | true | 'process PyPI api request' | :forbidden
:private | :developer | true | false | 'process PyPI api request' | :unauthorized
:private | :guest | true | false | 'process PyPI api request' | :unauthorized
:private | :developer | false | true | 'process PyPI api request' | :not_found
:private | :guest | false | true | 'process PyPI api request' | :not_found
:private | :developer | false | false | 'process PyPI api request' | :unauthorized
:private | :guest | false | false | 'process PyPI api request' | :unauthorized
:private | :anonymous | false | true | 'process PyPI api request' | :unauthorized
end
with_them do
@ -109,7 +97,7 @@ RSpec.describe API::PypiPackages do
let(:headers) { user_headers.merge(workhorse_headers) }
before do
project.update!(visibility_level: Gitlab::VisibilityLevel.const_get(project_visibility_level, false))
project.update_column(:visibility_level, Gitlab::VisibilityLevel.level_value(visibility_level.to_s))
end
it_behaves_like params[:shared_examples_name], params[:user_role], params[:expected_status], params[:member]
@ -146,25 +134,25 @@ RSpec.describe API::PypiPackages do
end
context 'with valid project' do
where(:project_visibility_level, :user_role, :member, :user_token, :shared_examples_name, :expected_status) do
'PUBLIC' | :developer | true | true | 'PyPI package creation' | :created
'PUBLIC' | :guest | true | true | 'process PyPI api request' | :forbidden
'PUBLIC' | :developer | true | false | 'process PyPI api request' | :unauthorized
'PUBLIC' | :guest | true | false | 'process PyPI api request' | :unauthorized
'PUBLIC' | :developer | false | true | 'process PyPI api request' | :forbidden
'PUBLIC' | :guest | false | true | 'process PyPI api request' | :forbidden
'PUBLIC' | :developer | false | false | 'process PyPI api request' | :unauthorized
'PUBLIC' | :guest | false | false | 'process PyPI api request' | :unauthorized
'PUBLIC' | :anonymous | false | true | 'process PyPI api request' | :unauthorized
'PRIVATE' | :developer | true | true | 'process PyPI api request' | :created
'PRIVATE' | :guest | true | true | 'process PyPI api request' | :forbidden
'PRIVATE' | :developer | true | false | 'process PyPI api request' | :unauthorized
'PRIVATE' | :guest | true | false | 'process PyPI api request' | :unauthorized
'PRIVATE' | :developer | false | true | 'process PyPI api request' | :not_found
'PRIVATE' | :guest | false | true | 'process PyPI api request' | :not_found
'PRIVATE' | :developer | false | false | 'process PyPI api request' | :unauthorized
'PRIVATE' | :guest | false | false | 'process PyPI api request' | :unauthorized
'PRIVATE' | :anonymous | false | true | 'process PyPI api request' | :unauthorized
where(:visibility_level, :user_role, :member, :user_token, :shared_examples_name, :expected_status) do
:public | :developer | true | true | 'PyPI package creation' | :created
:public | :guest | true | true | 'process PyPI api request' | :forbidden
:public | :developer | true | false | 'process PyPI api request' | :unauthorized
:public | :guest | true | false | 'process PyPI api request' | :unauthorized
:public | :developer | false | true | 'process PyPI api request' | :forbidden
:public | :guest | false | true | 'process PyPI api request' | :forbidden
:public | :developer | false | false | 'process PyPI api request' | :unauthorized
:public | :guest | false | false | 'process PyPI api request' | :unauthorized
:public | :anonymous | false | true | 'process PyPI api request' | :unauthorized
:private | :developer | true | true | 'process PyPI api request' | :created
:private | :guest | true | true | 'process PyPI api request' | :forbidden
:private | :developer | true | false | 'process PyPI api request' | :unauthorized
:private | :guest | true | false | 'process PyPI api request' | :unauthorized
:private | :developer | false | true | 'process PyPI api request' | :not_found
:private | :guest | false | true | 'process PyPI api request' | :not_found
:private | :developer | false | false | 'process PyPI api request' | :unauthorized
:private | :guest | false | false | 'process PyPI api request' | :unauthorized
:private | :anonymous | false | true | 'process PyPI api request' | :unauthorized
end
with_them do
@ -173,7 +161,7 @@ RSpec.describe API::PypiPackages do
let(:headers) { user_headers.merge(workhorse_headers) }
before do
project.update!(visibility_level: Gitlab::VisibilityLevel.const_get(project_visibility_level, false))
project.update_column(:visibility_level, Gitlab::VisibilityLevel.level_value(visibility_level.to_s))
end
it_behaves_like params[:shared_examples_name], params[:user_role], params[:expected_status], params[:member]
@ -187,7 +175,7 @@ RSpec.describe API::PypiPackages do
let(:headers) { user_headers.merge(workhorse_headers) }
before do
project.update!(visibility_level: Gitlab::VisibilityLevel::PRIVATE)
project.update_column(:visibility_level, Gitlab::VisibilityLevel::PRIVATE)
end
it_behaves_like 'process PyPI api request', :developer, :bad_request, true
@ -225,84 +213,25 @@ RSpec.describe API::PypiPackages do
end
end
describe 'GET /api/v4/projects/:id/packages/pypi/files/:sha256/*file_identifier' do
context 'file download endpoint' do
let_it_be(:package_name) { 'Dummy-Package' }
let_it_be(:package) { create(:pypi_package, project: project, name: package_name, version: '1.0.0') }
let(:url) { "/projects/#{project.id}/packages/pypi/files/#{package.package_files.first.file_sha256}/#{package_name}-1.0.0.tar.gz" }
subject { get api(url), headers: headers }
subject { get api(url) }
describe 'GET /api/v4/groups/:id/-/packages/pypi/files/:sha256/*file_identifier' do
let(:url) { "/groups/#{group.id}/-/packages/pypi/files/#{package.package_files.first.file_sha256}/#{package_name}-1.0.0.tar.gz" }
context 'with valid project' do
where(:project_visibility_level, :user_role, :member, :user_token, :shared_examples_name, :expected_status) do
'PUBLIC' | :developer | true | true | 'PyPI package download' | :success
'PUBLIC' | :guest | true | true | 'PyPI package download' | :success
'PUBLIC' | :developer | true | false | 'PyPI package download' | :success
'PUBLIC' | :guest | true | false | 'PyPI package download' | :success
'PUBLIC' | :developer | false | true | 'PyPI package download' | :success
'PUBLIC' | :guest | false | true | 'PyPI package download' | :success
'PUBLIC' | :developer | false | false | 'PyPI package download' | :success
'PUBLIC' | :guest | false | false | 'PyPI package download' | :success
'PUBLIC' | :anonymous | false | true | 'PyPI package download' | :success
'PRIVATE' | :developer | true | true | 'PyPI package download' | :success
'PRIVATE' | :guest | true | true | 'PyPI package download' | :success
'PRIVATE' | :developer | true | false | 'PyPI package download' | :success
'PRIVATE' | :guest | true | false | 'PyPI package download' | :success
'PRIVATE' | :developer | false | true | 'PyPI package download' | :success
'PRIVATE' | :guest | false | true | 'PyPI package download' | :success
'PRIVATE' | :developer | false | false | 'PyPI package download' | :success
'PRIVATE' | :guest | false | false | 'PyPI package download' | :success
'PRIVATE' | :anonymous | false | true | 'PyPI package download' | :success
end
with_them do
let(:token) { user_token ? personal_access_token.token : 'wrong' }
let(:headers) { user_role == :anonymous ? {} : basic_auth_header(user.username, token) }
subject { get api(url), headers: headers }
before do
project.update!(visibility_level: Gitlab::VisibilityLevel.const_get(project_visibility_level, false))
end
it_behaves_like params[:shared_examples_name], params[:user_role], params[:expected_status], params[:member]
end
it_behaves_like 'pypi file download endpoint'
it_behaves_like 'rejects PyPI access with unknown group id'
it_behaves_like 'a pypi user namespace endpoint'
end
context 'with deploy token headers' do
let(:headers) { basic_auth_header(deploy_token.username, deploy_token.token) }
describe 'GET /api/v4/projects/:id/packages/pypi/files/:sha256/*file_identifier' do
let(:url) { "/projects/#{project.id}/packages/pypi/files/#{package.package_files.first.file_sha256}/#{package_name}-1.0.0.tar.gz" }
context 'valid token' do
it_behaves_like 'returning response status', :success
end
context 'invalid token' do
let(:headers) { basic_auth_header('foo', 'bar') }
it_behaves_like 'returning response status', :success
end
it_behaves_like 'pypi file download endpoint'
it_behaves_like 'rejects PyPI access with unknown project id'
end
context 'with job token headers' do
let(:headers) { basic_auth_header(::Gitlab::Auth::CI_JOB_USER, job.token) }
context 'valid token' do
it_behaves_like 'returning response status', :success
end
context 'invalid token' do
let(:headers) { basic_auth_header(::Gitlab::Auth::CI_JOB_USER, 'bar') }
it_behaves_like 'returning response status', :success
end
context 'invalid user' do
let(:headers) { basic_auth_header('foo', job.token) }
it_behaves_like 'returning response status', :success
end
end
it_behaves_like 'rejects PyPI access with unknown project id'
end
end

View File

@ -107,10 +107,19 @@ RSpec.shared_examples 'Debian repository upload request' do |status, body = nil|
if status == :created
it 'creates package files', :aggregate_failures do
pending "Debian package creation not implemented"
expect(::Packages::Debian::FindOrCreateIncomingService).to receive(:new).with(container, user).and_call_original
expect(::Packages::Debian::CreatePackageFileService).to receive(:new).with(be_a(Packages::Package), be_an(Hash)).and_call_original
if file_name.end_with? '.changes'
expect(::Packages::Debian::ProcessChangesWorker).to receive(:perform_async)
else
expect(::Packages::Debian::ProcessChangesWorker).not_to receive(:perform_async)
end
expect { subject }
.to change { container.packages.debian.count }.by(1)
.and change { container.packages.debian.where(name: 'incoming').count }.by(1)
.and change { container.package_files.count }.by(1)
expect(response).to have_gitlab_http_status(status)
expect(response.media_type).to eq('text/plain')

View File

@ -110,6 +110,7 @@ RSpec.shared_examples 'PyPI package versions' do |user_type, status, add_member
context "for user type #{user_type}" do
before do
project.send("add_#{user_type}", user) if add_member && user_type != :anonymous
group.send("add_#{user_type}", user) if add_member && user_type != :anonymous
end
it 'returns the package listing' do
@ -127,6 +128,7 @@ RSpec.shared_examples 'PyPI package download' do |user_type, status, add_member
context "for user type #{user_type}" do
before do
project.send("add_#{user_type}", user) if add_member && user_type != :anonymous
group.send("add_#{user_type}", user) if add_member && user_type != :anonymous
end
it 'returns the package listing' do
@ -144,24 +146,184 @@ RSpec.shared_examples 'process PyPI api request' do |user_type, status, add_memb
context "for user type #{user_type}" do
before do
project.send("add_#{user_type}", user) if add_member && user_type != :anonymous
group.send("add_#{user_type}", user) if add_member && user_type != :anonymous
end
it_behaves_like 'returning response status', status
end
end
RSpec.shared_examples 'unknown PyPI scope id' do
context 'as anonymous' do
it_behaves_like 'process PyPI api request', :anonymous, :not_found
end
context 'as authenticated user' do
subject { get api(url), headers: basic_auth_header(user.username, personal_access_token.token) }
it_behaves_like 'process PyPI api request', :anonymous, :not_found
end
end
RSpec.shared_examples 'rejects PyPI access with unknown project id' do
context 'with an unknown project' do
let(:project) { OpenStruct.new(id: 1234567890) }
context 'as anonymous' do
it_behaves_like 'process PyPI api request', :anonymous, :not_found
it_behaves_like 'unknown PyPI scope id'
end
end
RSpec.shared_examples 'rejects PyPI access with unknown group id' do
context 'with an unknown project' do
let(:group) { OpenStruct.new(id: 1234567890) }
it_behaves_like 'unknown PyPI scope id'
end
end
RSpec.shared_examples 'pypi simple API endpoint' do
using RSpec::Parameterized::TableSyntax
context 'with valid project' do
where(:visibility_level, :user_role, :member, :user_token, :shared_examples_name, :expected_status) do
:public | :developer | true | true | 'PyPI package versions' | :success
:public | :guest | true | true | 'PyPI package versions' | :success
:public | :developer | true | false | 'PyPI package versions' | :success
:public | :guest | true | false | 'PyPI package versions' | :success
:public | :developer | false | true | 'PyPI package versions' | :success
:public | :guest | false | true | 'PyPI package versions' | :success
:public | :developer | false | false | 'PyPI package versions' | :success
:public | :guest | false | false | 'PyPI package versions' | :success
:public | :anonymous | false | true | 'PyPI package versions' | :success
:private | :developer | true | true | 'PyPI package versions' | :success
:private | :guest | true | true | 'process PyPI api request' | :forbidden
:private | :developer | true | false | 'process PyPI api request' | :unauthorized
:private | :guest | true | false | 'process PyPI api request' | :unauthorized
:private | :developer | false | true | 'process PyPI api request' | :not_found
:private | :guest | false | true | 'process PyPI api request' | :not_found
:private | :developer | false | false | 'process PyPI api request' | :unauthorized
:private | :guest | false | false | 'process PyPI api request' | :unauthorized
:private | :anonymous | false | true | 'process PyPI api request' | :unauthorized
end
context 'as authenticated user' do
subject { get api(url), headers: basic_auth_header(user.username, personal_access_token.token) }
with_them do
let(:token) { user_token ? personal_access_token.token : 'wrong' }
let(:headers) { user_role == :anonymous ? {} : basic_auth_header(user.username, token) }
it_behaves_like 'process PyPI api request', :anonymous, :not_found
before do
project.update_column(:visibility_level, Gitlab::VisibilityLevel.level_value(visibility_level.to_s))
group.update_column(:visibility_level, Gitlab::VisibilityLevel.level_value(visibility_level.to_s))
end
it_behaves_like params[:shared_examples_name], params[:user_role], params[:expected_status], params[:member]
end
end
context 'with a normalized package name' do
let_it_be(:package) { create(:pypi_package, project: project, name: 'my.package') }
let(:url) { "/projects/#{project.id}/packages/pypi/simple/my-package" }
let(:headers) { basic_auth_header(user.username, personal_access_token.token) }
it_behaves_like 'PyPI package versions', :developer, :success
end
end
RSpec.shared_examples 'pypi file download endpoint' do
using RSpec::Parameterized::TableSyntax
context 'with valid project' do
where(:visibility_level, :user_role, :member, :user_token) do
:public | :developer | true | true
:public | :guest | true | true
:public | :developer | true | false
:public | :guest | true | false
:public | :developer | false | true
:public | :guest | false | true
:public | :developer | false | false
:public | :guest | false | false
:public | :anonymous | false | true
:private | :developer | true | true
:private | :guest | true | true
:private | :developer | true | false
:private | :guest | true | false
:private | :developer | false | true
:private | :guest | false | true
:private | :developer | false | false
:private | :guest | false | false
:private | :anonymous | false | true
end
with_them do
let(:token) { user_token ? personal_access_token.token : 'wrong' }
let(:headers) { user_role == :anonymous ? {} : basic_auth_header(user.username, token) }
before do
project.update_column(:visibility_level, Gitlab::VisibilityLevel.level_value(visibility_level.to_s))
group.update_column(:visibility_level, Gitlab::VisibilityLevel.level_value(visibility_level.to_s))
end
it_behaves_like 'PyPI package download', params[:user_role], :success, params[:member]
end
end
context 'with deploy token headers' do
let(:headers) { basic_auth_header(deploy_token.username, deploy_token.token) }
context 'valid token' do
it_behaves_like 'returning response status', :success
end
context 'invalid token' do
let(:headers) { basic_auth_header('foo', 'bar') }
it_behaves_like 'returning response status', :success
end
end
context 'with job token headers' do
let(:headers) { basic_auth_header(::Gitlab::Auth::CI_JOB_USER, job.token) }
context 'valid token' do
it_behaves_like 'returning response status', :success
end
context 'invalid token' do
let(:headers) { basic_auth_header(::Gitlab::Auth::CI_JOB_USER, 'bar') }
it_behaves_like 'returning response status', :unauthorized
end
context 'invalid user' do
let(:headers) { basic_auth_header('foo', job.token) }
it_behaves_like 'returning response status', :success
end
end
end
RSpec.shared_examples 'a pypi user namespace endpoint' do
using RSpec::Parameterized::TableSyntax
# only group namespaces are supported at this time
where(:visibility_level, :user_role, :expected_status) do
:public | :owner | :not_found
:private | :owner | :not_found
:public | :external | :not_found
:private | :external | :not_found
:public | :anonymous | :not_found
:private | :anonymous | :not_found
end
with_them do
let_it_be_with_reload(:group) { create(:namespace) }
let(:headers) { user_role == :anonymous ? {} : basic_auth_header(user.username, personal_access_token.token) }
before do
group.update_column(:visibility_level, Gitlab::VisibilityLevel.level_value(visibility_level.to_s))
group.update_column(:owner_id, user.id) if user_role == :owner
end
it_behaves_like 'returning response status', params[:expected_status]
end
end

View File

@ -2,11 +2,15 @@
require 'rake_helper'
RSpec.describe 'clearing redis cache' do
RSpec.describe 'clearing redis cache', :clean_gitlab_redis_cache do
before do
Rake.application.rake_require 'tasks/cache'
end
shared_examples 'clears the cache' do
it { expect { run_rake_task('cache:clear:redis') }.to change { redis_keys.size }.by(-1) }
end
describe 'clearing pipeline status cache' do
let(:pipeline_status) do
project = create(:project, :repository)
@ -20,5 +24,38 @@ RSpec.describe 'clearing redis cache' do
it 'clears pipeline status cache' do
expect { run_rake_task('cache:clear:redis') }.to change { pipeline_status.has_cache? }
end
it_behaves_like 'clears the cache'
end
describe 'clearing set caches' do
context 'repository set' do
let(:project) { create(:project) }
let(:repository) { project.repository }
let(:cache) { Gitlab::RepositorySetCache.new(repository) }
before do
pending "Enable as part of https://gitlab.com/gitlab-org/gitlab/-/issues/331319"
cache.write(:foo, [:bar])
end
it_behaves_like 'clears the cache'
end
context 'reactive cache set' do
let(:cache) { Gitlab::ReactiveCacheSetCache.new }
before do
cache.write(:foo, :bar)
end
it_behaves_like 'clears the cache'
end
end
def redis_keys
Gitlab::Redis::Cache.with { |redis| redis.scan(0, match: "*") }.last
end
end