From a8c1bc6f757ecacbc3481e52a3f4cefb6c6db5fd Mon Sep 17 00:00:00 2001 From: GitLab Bot Date: Tue, 25 May 2021 15:10:33 +0000 Subject: [PATCH] Add latest changes from gitlab-org/gitlab@master --- .gitlab/ci/review.gitlab-ci.yml | 2 +- app/assets/javascripts/flash.js | 4 +- .../javascripts/lib/utils/number_utils.js | 8 +- app/controllers/projects_controller.rb | 2 +- .../group_or_project_package_finder.rb | 12 +- app/finders/packages/pypi/package_finder.rb | 10 + app/models/namespace.rb | 6 - app/models/project.rb | 12 +- .../packages/pypi/package_presenter.rb | 43 ++- app/workers/all_queues.yml | 18 +- .../project_recalculate_worker.rb | 2 +- .../security_ci_lint_authorization.yml | 8 + config/sidekiq_queues.yml | 2 - ...ner_registry_enabled_to_project_feature.rb | 36 +++ db/schema_migrations/20210513163904 | 1 + doc/administration/postgresql/external.md | 3 + doc/api/lint.md | 11 +- doc/api/packages/pypi.md | 77 ++++- doc/development/i18n/proofreader.md | 3 +- doc/install/postgresql_extensions.md | 22 +- doc/install/requirements.md | 34 ++- .../container_scanning/index.md | 10 +- .../dast/browser_based.md | 10 +- doc/user/application_security/dast/index.md | 8 +- .../dependency_scanning/index.md | 4 +- .../application_security/sast/analyzers.md | 2 +- doc/user/application_security/sast/index.md | 4 +- .../secret_detection/index.md | 6 +- .../compliance/license_compliance/index.md | 28 +- doc/user/markdown.md | 34 ++- doc/user/packages/pypi_repository/index.md | 29 ++ lib/api/debian_project_packages.rb | 15 + .../helpers/packages/basic_auth_helpers.rb | 8 + lib/api/lint.rb | 6 +- lib/api/pypi_packages.rb | 72 ++++- ...Managed-Cluster-Applications.gitlab-ci.yml | 4 +- .../Security/API-Fuzzing.gitlab-ci.yml | 5 +- .../Security/API-Fuzzing.latest.gitlab-ci.yml | 5 +- .../Security/Container-Scanning.gitlab-ci.yml | 3 +- .../Security/Coverage-Fuzzing.gitlab-ci.yml | 5 +- .../templates/Security/DAST-API.gitlab-ci.yml | 5 +- .../ci/templates/Security/DAST.gitlab-ci.yml | 5 +- .../Security/DAST.latest.gitlab-ci.yml | 5 +- .../Dependency-Scanning.gitlab-ci.yml | 5 +- .../Security/License-Scanning.gitlab-ci.yml | 5 +- .../ci/templates/Security/SAST.gitlab-ci.yml | 5 +- .../Security/Secret-Detection.gitlab-ci.yml | 5 +- lib/gitlab/current_settings.rb | 4 + .../email/handler/service_desk_handler.rb | 7 +- lib/gitlab/reactive_cache_set_cache.rb | 14 +- lib/gitlab/repository_set_cache.rb | 5 + lib/gitlab/set_cache.rb | 14 +- locale/gitlab.pot | 20 +- scripts/trigger-build | 37 ++- spec/controllers/projects_controller_spec.rb | 17 ++ .../packages/pypi/package_finder_spec.rb | 10 +- .../commit/pipelines/pipelines_spec.js | 280 ------------------ .../commit/pipelines/pipelines_table_spec.js | 253 ++++++++++++++++ spec/frontend/flash_spec.js | 53 ++-- .../frontend/lib/utils/number_utility_spec.js | 4 + spec/lib/gitlab/current_settings_spec.rb | 36 +++ .../handler/service_desk_handler_spec.rb | 20 +- spec/lib/gitlab/repository_set_cache_spec.rb | 25 ++ ...gistry_enabled_to_project_features_spec.rb | 45 +++ spec/models/namespace_spec.rb | 11 - spec/models/project_spec.rb | 15 +- .../packages/pypi/package_presenter_spec.rb | 55 ++-- .../api/debian_project_packages_spec.rb | 13 +- spec/requests/api/lint_spec.rb | 28 +- spec/requests/api/pypi_packages_spec.rb | 239 ++++++--------- .../api/debian_packages_shared_examples.rb | 11 +- .../api/pypi_packages_shared_examples.rb | 172 ++++++++++- spec/tasks/cache/clear/redis_spec.rb | 39 ++- 73 files changed, 1366 insertions(+), 670 deletions(-) create mode 100644 config/feature_flags/development/security_ci_lint_authorization.yml create mode 100644 db/post_migrate/20210513163904_cleanup_move_container_registry_enabled_to_project_feature.rb create mode 100644 db/schema_migrations/20210513163904 delete mode 100644 spec/frontend/commit/pipelines/pipelines_spec.js create mode 100644 spec/frontend/commit/pipelines/pipelines_table_spec.js create mode 100644 spec/migrations/cleanup_move_container_registry_enabled_to_project_features_spec.rb diff --git a/.gitlab/ci/review.gitlab-ci.yml b/.gitlab/ci/review.gitlab-ci.yml index e9ca16fdd4e..ac9dfe217bd 100644 --- a/.gitlab/ci/review.gitlab-ci.yml +++ b/.gitlab/ci/review.gitlab-ci.yml @@ -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}" diff --git a/app/assets/javascripts/flash.js b/app/assets/javascripts/flash.js index 7a79f8f5bfc..2edb6e79d3b 100644 --- a/app/assets/javascripts/flash.js +++ b/app/assets/javascripts/flash.js @@ -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)); }; /* diff --git a/app/assets/javascripts/lib/utils/number_utils.js b/app/assets/javascripts/lib/utils/number_utils.js index e3500d02a79..f3dedb7726a 100644 --- a/app/assets/javascripts/lib/utils/number_utils.js +++ b/app/assets/javascripts/lib/utils/number_utils.js @@ -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) }); diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb index e66893ac269..fb977a5ee42 100644 --- a/app/controllers/projects_controller.rb +++ b/app/controllers/projects_controller.rb @@ -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 diff --git a/app/finders/packages/group_or_project_package_finder.rb b/app/finders/packages/group_or_project_package_finder.rb index fb8bcfc7d42..5b5f70bf459 100644 --- a/app/finders/packages/group_or_project_package_finder.rb +++ b/app/finders/packages/group_or_project_package_finder.rb @@ -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 diff --git a/app/finders/packages/pypi/package_finder.rb b/app/finders/packages/pypi/package_finder.rb index 574e9770363..3a37e404b79 100644 --- a/app/finders/packages/pypi/package_finder.rb +++ b/app/finders/packages/pypi/package_finder.rb @@ -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 diff --git a/app/models/namespace.rb b/app/models/namespace.rb index 8f03c6145cb..0fd0298cc6d 100644 --- a/app/models/namespace.rb +++ b/app/models/namespace.rb @@ -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 diff --git a/app/models/project.rb b/app/models/project.rb index 4bc16771323..39415769e50 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -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) diff --git a/app/presenters/packages/pypi/package_presenter.rb b/app/presenters/packages/pypi/package_presenter.rb index 1cb11c7be1a..7997c1b9b79 100644 --- a/app/presenters/packages/pypi/package_presenter.rb +++ b/app/presenters/packages/pypi/package_presenter.rb @@ -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 diff --git a/app/workers/all_queues.yml b/app/workers/all_queues.yml index a57b208f476..ea0845b00e8 100644 --- a/app/workers/all_queues.yml +++ b/app/workers/all_queues.yml @@ -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 diff --git a/app/workers/authorized_project_update/project_recalculate_worker.rb b/app/workers/authorized_project_update/project_recalculate_worker.rb index 0ecef8b868b..3f0672992ef 100644 --- a/app/workers/authorized_project_update/project_recalculate_worker.rb +++ b/app/workers/authorized_project_update/project_recalculate_worker.rb @@ -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! diff --git a/config/feature_flags/development/security_ci_lint_authorization.yml b/config/feature_flags/development/security_ci_lint_authorization.yml new file mode 100644 index 00000000000..73b3bd45727 --- /dev/null +++ b/config/feature_flags/development/security_ci_lint_authorization.yml @@ -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 diff --git a/config/sidekiq_queues.yml b/config/sidekiq_queues.yml index 78c073b354f..24d140abada 100644 --- a/config/sidekiq_queues.yml +++ b/config/sidekiq_queues.yml @@ -44,8 +44,6 @@ - 2 - - authorized_project_update - 1 -- - authorized_projects - - 1 - - authorized_projects - 2 - - auto_devops diff --git a/db/post_migrate/20210513163904_cleanup_move_container_registry_enabled_to_project_feature.rb b/db/post_migrate/20210513163904_cleanup_move_container_registry_enabled_to_project_feature.rb new file mode 100644 index 00000000000..665d274a0ee --- /dev/null +++ b/db/post_migrate/20210513163904_cleanup_move_container_registry_enabled_to_project_feature.rb @@ -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 diff --git a/db/schema_migrations/20210513163904 b/db/schema_migrations/20210513163904 new file mode 100644 index 00000000000..dc668704311 --- /dev/null +++ b/db/schema_migrations/20210513163904 @@ -0,0 +1 @@ +3c4905fbe29227da7a2386f73d9df30e82da48efff24a1193ba3db0ac325cfcf \ No newline at end of file diff --git a/doc/administration/postgresql/external.md b/doc/administration/postgresql/external.md index a9d0af952a0..1e346a3b8aa 100644 --- a/doc/administration/postgresql/external.md +++ b/doc/administration/postgresql/external.md @@ -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: diff --git a/doc/api/lint.md b/doc/api/lint.md index 867a5e54663..69267fe51a5 100644 --- a/doc/api/lint.md +++ b/doc/api/lint.md @@ -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 diff --git a/doc/api/packages/pypi.md b/doc/api/packages/pypi.md index 531193e59e2..77ba028c447 100644 --- a/doc/api/packages/pypi.md +++ b/doc/api/packages/pypi.md @@ -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 : "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 : "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 : "https://gitlab.example.com/api/v4/groups/1/packages/pypi/simple/my.pypi.package" +``` + +Example response: + +```html + + + + Links for my.pypi.package + + +

Links for my.pypi.package

+ my.pypi.package-0.0.1-py3-none-any.whl
my.pypi.package-0.0.1.tar.gz
+ + +``` + +To write the output to a file: + +```shell +curl --user : "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 : "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. diff --git a/doc/development/i18n/proofreader.md b/doc/development/i18n/proofreader.md index 56767f73c61..6a35c192ce4 100644 --- a/doc/development/i18n/proofreader.md +++ b/doc/development/i18n/proofreader.md @@ -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) diff --git a/doc/install/postgresql_extensions.md b/doc/install/postgresql_extensions.md index 663ec547733..80bbb0671b9 100644 --- a/doc/install/postgresql_extensions.md +++ b/doc/install/postgresql_extensions.md @@ -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 +``` diff --git a/doc/install/requirements.md b/doc/install/requirements.md index 5c723ee06cd..8accf094795 100644 --- a/doc/install/requirements.md +++ b/doc/install/requirements.md @@ -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. diff --git a/doc/user/application_security/container_scanning/index.md b/doc/user/application_security/container_scanning/index.md index 5ab56634d29..e5c984dd7bc 100644 --- a/doc/user/application_security/container_scanning/index.md +++ b/doc/user/application_security/container_scanning/index.md @@ -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 diff --git a/doc/user/application_security/dast/browser_based.md b/doc/user/application_security/dast/browser_based.md index aaf4496076f..880078977b4 100644 --- a/doc/user/application_security/dast/browser_based.md +++ b/doc/user/application_security/dast/browser_based.md @@ -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 diff --git a/doc/user/application_security/dast/index.md b/doc/user/application_security/dast/index.md index 1093e7cfabd..73acb03a04d 100644 --- a/doc/user/application_security/dast/index.md +++ b/doc/user/application_security/dast/index.md @@ -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. diff --git a/doc/user/application_security/dependency_scanning/index.md b/doc/user/application_security/dependency_scanning/index.md index 8e23db89dfd..bf17e62ad77 100644 --- a/doc/user/application_security/dependency_scanning/index.md +++ b/doc/user/application_security/dependency_scanning/index.md @@ -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. diff --git a/doc/user/application_security/sast/analyzers.md b/doc/user/application_security/sast/analyzers.md index 0e69f3b68eb..cbcfda16c35 100644 --- a/doc/user/application_security/sast/analyzers.md +++ b/doc/user/application_security/sast/analyzers.md @@ -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) diff --git a/doc/user/application_security/sast/index.md b/doc/user/application_security/sast/index.md index 886726d5d67..176b3c81960 100644 --- a/doc/user/application_security/sast/index.md +++ b/doc/user/application_security/sast/index.md @@ -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. diff --git a/doc/user/application_security/secret_detection/index.md b/doc/user/application_security/secret_detection/index.md index 02d117b1c0a..758b06c46aa 100644 --- a/doc/user/application_security/secret_detection/index.md +++ b/doc/user/application_security/secret_detection/index.md @@ -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. diff --git a/doc/user/compliance/license_compliance/index.md b/doc/user/compliance/license_compliance/index.md index 43dbafb8f6f..da601416174 100644 --- a/doc/user/compliance/license_compliance/index.md +++ b/doc/user/compliance/license_compliance/index.md @@ -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: diff --git a/doc/user/markdown.md b/doc/user/markdown.md index d8c7b0fd8fe..f642d0bd373 100644 --- a/doc/user/markdown.md +++ b/doc/user/markdown.md @@ -1395,26 +1395,32 @@ while the equation for the theory of relativity is E = mc2. 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 `
` 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 `
` tags to force a cell to have multiple lines: ```markdown | Name | Details | -|------|---------| +| --- | --- | | Item1 | This is on one line | | Item2 | This item has:
- Multiple items
- That we want listed separately | ``` | Name | Details | -|------|---------| +| --- | --- | | Item1 | This is on one line | | Item2 | This item has:
- Multiple items
- 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 |
  • - [ ] Task one
  • - [ ] Task two
| ``` diff --git a/doc/user/packages/pypi_repository/index.md b/doc/user/packages/pypi_repository/index.md index 1708bfdf2e5..a4d17595ddd 100644 --- a/doc/user/packages/pypi_repository/index.md +++ b/doc/user/packages/pypi_repository/index.md @@ -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://:@gitlab.example.com/api/v4/groups//packages/pypi/simple --no-deps +``` + +In this command: + +- `` is the package name. +- `` is a personal access token name with the `read_api` scope. +- `` is a personal access token with the `read_api` scope. +- `` 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://:@gitlab.example.com/api/v4/groups//packages/pypi/simple +``` + ### Package names GitLab looks for packages that use diff --git a/lib/api/debian_project_packages.rb b/lib/api/debian_project_packages.rb index 0ed828fd639..feb83b52695 100644 --- a/lib/api/debian_project_packages.rb +++ b/lib/api/debian_project_packages.rb @@ -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 }) diff --git a/lib/api/helpers/packages/basic_auth_helpers.rb b/lib/api/helpers/packages/basic_auth_helpers.rb index c32ce199dd6..6c381d85cd8 100644 --- a/lib/api/helpers/packages/basic_auth_helpers.rb +++ b/lib/api/helpers/packages/basic_auth_helpers.rb @@ -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 diff --git a/lib/api/lint.rb b/lib/api/lint.rb index e0806674c6a..3580a7b5e24 100644 --- a/lib/api/lint.rb +++ b/lib/api/lint.rb @@ -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 diff --git a/lib/api/pypi_packages.rb b/lib/api/pypi_packages.rb index 73b2f658825..969b619c1cd 100644 --- a/lib/api/pypi_packages.rb +++ b/lib/api/pypi_packages.rb @@ -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. diff --git a/lib/gitlab/ci/templates/Managed-Cluster-Applications.gitlab-ci.yml b/lib/gitlab/ci/templates/Managed-Cluster-Applications.gitlab-ci.yml index c599b261d77..a6d400b6350 100644 --- a/lib/gitlab/ci/templates/Managed-Cluster-Applications.gitlab-ci.yml +++ b/lib/gitlab/ci/templates/Managed-Cluster-Applications.gitlab-ci.yml @@ -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 diff --git a/lib/gitlab/ci/templates/Security/API-Fuzzing.gitlab-ci.yml b/lib/gitlab/ci/templates/Security/API-Fuzzing.gitlab-ci.yml index 90fad1550ff..f3c9a93d9fb 100644 --- a/lib/gitlab/ci/templates/Security/API-Fuzzing.gitlab-ci.yml +++ b/lib/gitlab/ci/templates/Security/API-Fuzzing.gitlab-ci.yml @@ -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 diff --git a/lib/gitlab/ci/templates/Security/API-Fuzzing.latest.gitlab-ci.yml b/lib/gitlab/ci/templates/Security/API-Fuzzing.latest.gitlab-ci.yml index 8fa33026011..0c4c39cbcd6 100644 --- a/lib/gitlab/ci/templates/Security/API-Fuzzing.latest.gitlab-ci.yml +++ b/lib/gitlab/ci/templates/Security/API-Fuzzing.latest.gitlab-ci.yml @@ -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" diff --git a/lib/gitlab/ci/templates/Security/Container-Scanning.gitlab-ci.yml b/lib/gitlab/ci/templates/Security/Container-Scanning.gitlab-ci.yml index 695aaa37924..84d9a92663a 100644 --- a/lib/gitlab/ci/templates/Security/Container-Scanning.gitlab-ci.yml +++ b/lib/gitlab/ci/templates/Security/Container-Scanning.gitlab-ci.yml @@ -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) diff --git a/lib/gitlab/ci/templates/Security/Coverage-Fuzzing.gitlab-ci.yml b/lib/gitlab/ci/templates/Security/Coverage-Fuzzing.gitlab-ci.yml index ab71049bac2..2dbfb80b419 100644 --- a/lib/gitlab/ci/templates/Security/Coverage-Fuzzing.gitlab-ci.yml +++ b/lib/gitlab/ci/templates/Security/Coverage-Fuzzing.gitlab-ci.yml @@ -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. diff --git a/lib/gitlab/ci/templates/Security/DAST-API.gitlab-ci.yml b/lib/gitlab/ci/templates/Security/DAST-API.gitlab-ci.yml index b40c4e982f7..9170e943e9d 100644 --- a/lib/gitlab/ci/templates/Security/DAST-API.gitlab-ci.yml +++ b/lib/gitlab/ci/templates/Security/DAST-API.gitlab-ci.yml @@ -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 diff --git a/lib/gitlab/ci/templates/Security/DAST.gitlab-ci.yml b/lib/gitlab/ci/templates/Security/DAST.gitlab-ci.yml index 7abecfb7e49..b355b6e36a2 100644 --- a/lib/gitlab/ci/templates/Security/DAST.gitlab-ci.yml +++ b/lib/gitlab/ci/templates/Security/DAST.gitlab-ci.yml @@ -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 diff --git a/lib/gitlab/ci/templates/Security/DAST.latest.gitlab-ci.yml b/lib/gitlab/ci/templates/Security/DAST.latest.gitlab-ci.yml index b6282da18a4..693cf1469c2 100644 --- a/lib/gitlab/ci/templates/Security/DAST.latest.gitlab-ci.yml +++ b/lib/gitlab/ci/templates/Security/DAST.latest.gitlab-ci.yml @@ -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 diff --git a/lib/gitlab/ci/templates/Security/Dependency-Scanning.gitlab-ci.yml b/lib/gitlab/ci/templates/Security/Dependency-Scanning.gitlab-ci.yml index 53d68c24d26..8df5ce79fe8 100644 --- a/lib/gitlab/ci/templates/Security/Dependency-Scanning.gitlab-ci.yml +++ b/lib/gitlab/ci/templates/Security/Dependency-Scanning.gitlab-ci.yml @@ -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 diff --git a/lib/gitlab/ci/templates/Security/License-Scanning.gitlab-ci.yml b/lib/gitlab/ci/templates/Security/License-Scanning.gitlab-ci.yml index 21e926ef275..870684c9f1d 100644 --- a/lib/gitlab/ci/templates/Security/License-Scanning.gitlab-ci.yml +++ b/lib/gitlab/ci/templates/Security/License-Scanning.gitlab-ci.yml @@ -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 diff --git a/lib/gitlab/ci/templates/Security/SAST.gitlab-ci.yml b/lib/gitlab/ci/templates/Security/SAST.gitlab-ci.yml index a8d45e80356..65e1046ad0d 100644 --- a/lib/gitlab/ci/templates/Security/SAST.gitlab-ci.yml +++ b/lib/gitlab/ci/templates/Security/SAST.gitlab-ci.yml @@ -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 diff --git a/lib/gitlab/ci/templates/Security/Secret-Detection.gitlab-ci.yml b/lib/gitlab/ci/templates/Security/Secret-Detection.gitlab-ci.yml index c255fb4707a..657ac43b78e 100644 --- a/lib/gitlab/ci/templates/Security/Secret-Detection.gitlab-ci.yml +++ b/lib/gitlab/ci/templates/Security/Secret-Detection.gitlab-ci.yml @@ -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" diff --git a/lib/gitlab/current_settings.rb b/lib/gitlab/current_settings.rb index 7f55734f796..e7ffeeb9849 100644 --- a/lib/gitlab/current_settings.rb +++ b/lib/gitlab/current_settings.rb @@ -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 diff --git a/lib/gitlab/email/handler/service_desk_handler.rb b/lib/gitlab/email/handler/service_desk_handler.rb index cab3538a447..05daa08530e 100644 --- a/lib/gitlab/email/handler/service_desk_handler.rb +++ b/lib/gitlab/email/handler/service_desk_handler.rb @@ -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) diff --git a/lib/gitlab/reactive_cache_set_cache.rb b/lib/gitlab/reactive_cache_set_cache.rb index 8a432edbd78..e62e1172b65 100644 --- a/lib/gitlab/reactive_cache_set_cache.rb +++ b/lib/gitlab/reactive_cache_set_cache.rb @@ -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 diff --git a/lib/gitlab/repository_set_cache.rb b/lib/gitlab/repository_set_cache.rb index f73ac628bce..a20e9845fe6 100644 --- a/lib/gitlab/repository_set_cache.rb +++ b/lib/gitlab/repository_set_cache.rb @@ -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) diff --git a/lib/gitlab/set_cache.rb b/lib/gitlab/set_cache.rb index 0f2b7b194c9..30cd63e80c0 100644 --- a/lib/gitlab/set_cache.rb +++ b/lib/gitlab/set_cache.rb @@ -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 diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 789dedeaa2a..476cf5abf55 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -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 "" diff --git a/scripts/trigger-build b/scripts/trigger-build index 0d8a46bdd2d..f2b7dd0e5a7 100755 --- a/scripts/trigger-build +++ b/scripts/trigger-build @@ -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 = " \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) diff --git a/spec/controllers/projects_controller_spec.rb b/spec/controllers/projects_controller_spec.rb index ffe2d393b1e..fdc687496b7 100644 --- a/spec/controllers/projects_controller_spec.rb +++ b/spec/controllers/projects_controller_spec.rb @@ -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) } diff --git a/spec/finders/packages/pypi/package_finder_spec.rb b/spec/finders/packages/pypi/package_finder_spec.rb index 7d9eb8a5cd1..f065bd21f6d 100644 --- a/spec/finders/packages/pypi/package_finder_spec.rb +++ b/spec/finders/packages/pypi/package_finder_spec.rb @@ -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 diff --git a/spec/frontend/commit/pipelines/pipelines_spec.js b/spec/frontend/commit/pipelines/pipelines_spec.js deleted file mode 100644 index fe928a01acf..00000000000 --- a/spec/frontend/commit/pipelines/pipelines_spec.js +++ /dev/null @@ -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(); - }); - }); - }); -}); diff --git a/spec/frontend/commit/pipelines/pipelines_table_spec.js b/spec/frontend/commit/pipelines/pipelines_table_spec.js new file mode 100644 index 00000000000..4bf6727af3b --- /dev/null +++ b/spec/frontend/commit/pipelines/pipelines_table_spec.js @@ -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.', + ); + }); + }); +}); diff --git a/spec/frontend/flash_spec.js b/spec/frontend/flash_spec.js index 6a5ac76a4d0..28e8522cc12 100644 --- a/spec/frontend/flash_spec.js +++ b/spec/frontend/flash_spec.js @@ -357,27 +357,46 @@ describe('Flash', () => { }); describe('removeFlashClickListener', () => { - beforeEach(() => { - document.body.innerHTML += ` -
-
-
+ let el; + + describe('with close icon', () => { + beforeEach(() => { + el = document.createElement('div'); + el.innerHTML = ` +
+
+
+
-
- `; + `; + }); + + 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 = ` +
+
+
+
+ `; + }); - 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(); }); }); }); diff --git a/spec/frontend/lib/utils/number_utility_spec.js b/spec/frontend/lib/utils/number_utility_spec.js index f4483f5098b..e743678ea90 100644 --- a/spec/frontend/lib/utils/number_utility_spec.js +++ b/spec/frontend/lib/utils/number_utility_spec.js @@ -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'); }); }); diff --git a/spec/lib/gitlab/current_settings_spec.rb b/spec/lib/gitlab/current_settings_spec.rb index f5cb1987c5c..a5ab1047a40 100644 --- a/spec/lib/gitlab/current_settings_spec.rb +++ b/spec/lib/gitlab/current_settings_spec.rb @@ -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? } diff --git a/spec/lib/gitlab/email/handler/service_desk_handler_spec.rb b/spec/lib/gitlab/email/handler/service_desk_handler_spec.rb index 6d26b3e1064..3a60564d8d2 100644 --- a/spec/lib/gitlab/email/handler/service_desk_handler_spec.rb +++ b/spec/lib/gitlab/email/handler/service_desk_handler_spec.rb @@ -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 diff --git a/spec/lib/gitlab/repository_set_cache_spec.rb b/spec/lib/gitlab/repository_set_cache_spec.rb index 881591ae805..9aeb9f11bac 100644 --- a/spec/lib/gitlab/repository_set_cache_spec.rb +++ b/spec/lib/gitlab/repository_set_cache_spec.rb @@ -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 diff --git a/spec/migrations/cleanup_move_container_registry_enabled_to_project_features_spec.rb b/spec/migrations/cleanup_move_container_registry_enabled_to_project_features_spec.rb new file mode 100644 index 00000000000..480730060af --- /dev/null +++ b/spec/migrations/cleanup_move_container_registry_enabled_to_project_features_spec.rb @@ -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 diff --git a/spec/models/namespace_spec.rb b/spec/models/namespace_spec.rb index ec0507db1b3..fd7125e3edc 100644 --- a/spec/models/namespace_spec.rb +++ b/spec/models/namespace_spec.rb @@ -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 )} diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index 8e606a00144..6a0bc714731 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -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 diff --git a/spec/presenters/packages/pypi/package_presenter_spec.rb b/spec/presenters/packages/pypi/package_presenter_spec.rb index e4d234a4688..25aa5c31034 100644 --- a/spec/presenters/packages/pypi/package_presenter_spec.rb +++ b/spec/presenters/packages/pypi/package_presenter_spec.rb @@ -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) { "#{filename}
" } - - before do - package.pypi_metadatum.required_python = python_version + where(:version, :expected_version, :with_package1) do + '>=2.7' | '>=2.7' | true + '">' | '"><script>alert(1)</script>' | true + '>=2.7, !=3.0' | '>=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) { '>=2.7' } - let(:package) { package1 } + context 'for project' do + let(:project_or_group) { project } + let(:expected_file) { "#{filename}
" } + + it_behaves_like 'pypi package presenter' end - it_behaves_like "pypi package presenter" do - let(:python_version) { '">' } - let(:expected_python_version) { '"><script>alert(1)</script>' } - let(:package) { package1 } - end + context 'for group' do + let(:project_or_group) { group } + let(:expected_file) { "#{filename}
" } - it_behaves_like "pypi package presenter" do - let(:python_version) { '>=2.7, !=3.0' } - let(:expected_python_version) { '>=2.7, !=3.0' } - let(:package) { package2 } + it_behaves_like 'pypi package presenter' end end end diff --git a/spec/requests/api/debian_project_packages_spec.rb b/spec/requests/api/debian_project_packages_spec.rb index f400b6e928c..c74c0ea1c2a 100644 --- a/spec/requests/api/debian_project_packages_spec.rb +++ b/spec/requests/api/debian_project_packages_spec.rb @@ -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" } diff --git a/spec/requests/api/lint_spec.rb b/spec/requests/api/lint_spec.rb index f26236e0253..57aa0f36192 100644 --- a/spec/requests/api/lint_spec.rb +++ b/spec/requests/api/lint_spec.rb @@ -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) } diff --git a/spec/requests/api/pypi_packages_spec.rb b/spec/requests/api/pypi_packages_spec.rb index 718004a0087..4e8c50fedb6 100644 --- a/spec/requests/api/pypi_packages_spec.rb +++ b/spec/requests/api/pypi_packages_spec.rb @@ -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 diff --git a/spec/support/shared_examples/requests/api/debian_packages_shared_examples.rb b/spec/support/shared_examples/requests/api/debian_packages_shared_examples.rb index dfd19167dcd..7c47b900dc0 100644 --- a/spec/support/shared_examples/requests/api/debian_packages_shared_examples.rb +++ b/spec/support/shared_examples/requests/api/debian_packages_shared_examples.rb @@ -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') diff --git a/spec/support/shared_examples/requests/api/pypi_packages_shared_examples.rb b/spec/support/shared_examples/requests/api/pypi_packages_shared_examples.rb index bbcf856350d..cf749aab79a 100644 --- a/spec/support/shared_examples/requests/api/pypi_packages_shared_examples.rb +++ b/spec/support/shared_examples/requests/api/pypi_packages_shared_examples.rb @@ -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 diff --git a/spec/tasks/cache/clear/redis_spec.rb b/spec/tasks/cache/clear/redis_spec.rb index d2de068f254..9cbb40a77ef 100644 --- a/spec/tasks/cache/clear/redis_spec.rb +++ b/spec/tasks/cache/clear/redis_spec.rb @@ -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