From 0b3a1e151bdbdfab6159fa03fcbaf6f799db23c6 Mon Sep 17 00:00:00 2001 From: Moaz Khalifa <mkhalifa@gitlab.com> Date: Mon, 12 Jun 2023 17:09:46 +0000 Subject: [PATCH] Support dotnet nuget api-key option Dotnet users are now able to publish NuGet packages to the NuGet package repository using this command: dotnet nuget push **/*.nupkg --source https://gitlab.com/api/v4/projects/1/packages/nuget/index.json --api-key PAT_TOKEN Changelog: added --- .../packages/nuget/service_index_presenter.rb | 21 +- doc/api/packages/nuget.md | 8 +- doc/user/packages/nuget_repository/index.md | 17 ++ .../packages/nuget/private_endpoints.rb | 153 ++++++++++ .../packages/nuget/public_endpoints.rb | 42 +++ lib/api/concerns/packages/nuget_endpoints.rb | 159 ---------- lib/api/nuget_group_packages.rb | 39 ++- lib/api/nuget_project_packages.rb | 280 ++++++++++-------- .../api_authentication/token_locator.rb | 44 ++- .../api_authentication/token_locator_spec.rb | 23 +- .../requests/api/nuget_group_packages_spec.rb | 8 +- .../api/nuget_endpoints_shared_examples.rb | 100 ++----- .../api/nuget_packages_shared_examples.rb | 146 ++++++--- 13 files changed, 588 insertions(+), 452 deletions(-) create mode 100644 lib/api/concerns/packages/nuget/private_endpoints.rb create mode 100644 lib/api/concerns/packages/nuget/public_endpoints.rb delete mode 100644 lib/api/concerns/packages/nuget_endpoints.rb diff --git a/app/presenters/packages/nuget/service_index_presenter.rb b/app/presenters/packages/nuget/service_index_presenter.rb index 033a1845c1ce..b262735508ce 100644 --- a/app/presenters/packages/nuget/service_index_presenter.rb +++ b/app/presenters/packages/nuget/service_index_presenter.rb @@ -35,12 +35,13 @@ def version end def resources - available_services.map { |service| build_service(service) } - .flatten + available_services.flat_map { |service| build_service(service) } end private + attr_reader :project_or_group + def available_services case scope when :group @@ -77,13 +78,13 @@ def build_service_url(service_type) end def scope - return :project if @project_or_group.is_a?(::Project) - return :group if @project_or_group.is_a?(::Group) + return :project if project_or_group.is_a?(::Project) + return :group if project_or_group.is_a?(::Group) end def download_service_url params = { - id: @project_or_group.id, + id: project_or_group.id, package_name: nil, package_version: nil, package_filename: nil @@ -97,7 +98,7 @@ def download_service_url def metadata_service_url params = { - id: @project_or_group.id, + id: project_or_group.id, package_name: nil, package_version: nil } @@ -119,18 +120,18 @@ def metadata_service_url def search_service_url case scope when :group - api_v4_groups___packages_nuget_query_path(id: @project_or_group.id) + api_v4_groups___packages_nuget_query_path(id: project_or_group.id) when :project - api_v4_projects_packages_nuget_query_path(id: @project_or_group.id) + api_v4_projects_packages_nuget_query_path(id: project_or_group.id) end end def publish_service_url - api_v4_projects_packages_nuget_path(id: @project_or_group.id) + api_v4_projects_packages_nuget_path(id: project_or_group.id) end def symbol_service_url - api_v4_projects_packages_nuget_symbolpackage_path(id: @project_or_group.id) + api_v4_projects_packages_nuget_symbolpackage_path(id: project_or_group.id) end end end diff --git a/doc/api/packages/nuget.md b/doc/api/packages/nuget.md index aa2b4586e9c7..afe384b5a29c 100644 --- a/doc/api/packages/nuget.md +++ b/doc/api/packages/nuget.md @@ -158,9 +158,11 @@ The examples in this document all use the project-level prefix. ## Service Index -> Introduced in GitLab 12.6. +> - Introduced in GitLab 12.6. +> - [Changed](https://gitlab.com/gitlab-org/gitlab/-/issues/214674) to be public in GitLab 16.1. -Returns a list of available API resources: +Returns a list of available API resources. +Authentication is not required: ```plaintext GET <route-prefix>/index @@ -169,7 +171,7 @@ GET <route-prefix>/index Example Request: ```shell -curl --user <username>:<personal_access_token> "https://gitlab.example.com/api/v4/projects/1/packages/nuget/index" +curl "https://gitlab.example.com/api/v4/projects/1/packages/nuget/index" ``` Example response: diff --git a/doc/user/packages/nuget_repository/index.md b/doc/user/packages/nuget_repository/index.md index c97ce9ec5933..ea9bfd7defb9 100644 --- a/doc/user/packages/nuget_repository/index.md +++ b/doc/user/packages/nuget_repository/index.md @@ -316,6 +316,8 @@ nuget push <package_file> -Source <source_name> ### Publish a package with the .NET CLI +> Publishing a package with `--api-key` [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/214674) in GitLab 16.1. + Prerequisites: - [A NuGet package created with .NET CLI](https://learn.microsoft.com/en-us/nuget/create-packages/creating-a-package-dotnet-cli). @@ -336,6 +338,21 @@ For example: dotnet nuget push MyPackage.1.0.0.nupkg --source gitlab ``` +You can publish a package using the `--api-key` option instead of `username` and `password`: + +```shell +dotnet nuget push <package_file> --source <source_url> --api-key <gitlab_personal_access_token, deploy_token or job token> +``` + +- `<package_file>` is your package filename, ending in `.nupkg`. +- `<source_url>` is the URL of the NuGet Package Registry. + +For example: + +```shell +dotnet nuget push MyPackage.1.0.0.nupkg --source https://gitlab.example.com/api/v4/projects/<your_project_id>/packages/nuget/index.json --api-key <gitlab_personal_access_token, deploy_token or job token> +``` + ### Publish a NuGet package by using CI/CD > [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/36424) in GitLab 13.3. diff --git a/lib/api/concerns/packages/nuget/private_endpoints.rb b/lib/api/concerns/packages/nuget/private_endpoints.rb new file mode 100644 index 000000000000..20c02f0a285b --- /dev/null +++ b/lib/api/concerns/packages/nuget/private_endpoints.rb @@ -0,0 +1,153 @@ +# frozen_string_literal: true + +# +# NuGet Package Manager Client API +# +# These API endpoints are not consumed directly by users, so there is no documentation for the +# individual endpoints. They are called by the NuGet package manager client when users run commands +# like `nuget install` or `nuget push`. The usage of the GitLab NuGet registry is documented here: +# https://docs.gitlab.com/ee/user/packages/nuget_repository/ +# +# Technical debt: https://gitlab.com/gitlab-org/gitlab/issues/35798 +module API + module Concerns + module Packages + module Nuget + module PrivateEndpoints + extend ActiveSupport::Concern + + POSITIVE_INTEGER_REGEX = %r{\A[1-9]\d*\z} + NON_NEGATIVE_INTEGER_REGEX = %r{\A(0|[1-9]\d*)\z} + + included do + helpers do + def find_packages(package_name) + packages = package_finder(package_name).execute + + not_found!('Packages') unless packages.exists? + + packages + end + + def find_package(package_name, package_version) + package = package_finder(package_name, package_version).execute + .first + + not_found!('Package') unless package + + package + end + + def package_finder(package_name, package_version = nil) + ::Packages::Nuget::PackageFinder.new( + current_user, + project_or_group, + package_name: package_name, + package_version: package_version + ) + end + + def search_packages(_search_term, search_options) + ::Packages::Nuget::SearchService + .new(current_user, project_or_group, params[:q], search_options) + .execute + end + end + + # https://docs.microsoft.com/en-us/nuget/api/registration-base-url-resource + params do + requires :package_name, type: String, desc: 'The NuGet package name', + regexp: API::NO_SLASH_URL_PART_REGEX, documentation: { example: 'MyNuGetPkg' } + end + namespace '/metadata/*package_name' do + after_validation do + authorize_packages_access!(project_or_group, required_permission) + end + + desc 'The NuGet Metadata Service - Package name level' do + detail 'This feature was introduced in GitLab 12.8' + success code: 200, model: ::API::Entities::Nuget::PackagesMetadata + failure [ + { code: 401, message: 'Unauthorized' }, + { code: 403, message: 'Forbidden' }, + { code: 404, message: 'Not Found' } + ] + tags %w[nuget_packages] + end + get 'index', format: :json, urgency: :low do + present ::Packages::Nuget::PackagesMetadataPresenter.new(find_packages(params[:package_name])), + with: ::API::Entities::Nuget::PackagesMetadata + end + + desc 'The NuGet Metadata Service - Package name and version level' do + detail 'This feature was introduced in GitLab 12.8' + success code: 200, model: ::API::Entities::Nuget::PackageMetadata + failure [ + { code: 401, message: 'Unauthorized' }, + { code: 403, message: 'Forbidden' }, + { code: 404, message: 'Not Found' } + ] + tags %w[nuget_packages] + end + params do + requires :package_version, type: String, desc: 'The NuGet package version', + regexp: API::NO_SLASH_URL_PART_REGEX, documentation: { example: '1.0.0' } + end + get '*package_version', format: :json, urgency: :low do + present ::Packages::Nuget::PackageMetadataPresenter.new( + find_package(params[:package_name], + params[:package_version]) + ), + with: ::API::Entities::Nuget::PackageMetadata + end + end + + # https://docs.microsoft.com/en-us/nuget/api/search-query-service-resource + params do + optional :q, type: String, desc: 'The search term', documentation: { example: 'MyNuGet' } + optional :skip, type: Integer, desc: 'The number of results to skip', default: 0, + regexp: NON_NEGATIVE_INTEGER_REGEX, documentation: { example: 1 } + optional :take, type: Integer, desc: 'The number of results to return', + default: Kaminari.config.default_per_page, regexp: POSITIVE_INTEGER_REGEX, documentation: { example: 1 } + optional :prerelease, type: ::Grape::API::Boolean, desc: 'Include prerelease versions', default: true + end + namespace '/query' do + after_validation do + authorize_packages_access!(project_or_group, required_permission) + end + + desc 'The NuGet Search Service' do + detail 'This feature was introduced in GitLab 12.8' + success code: 200, model: ::API::Entities::Nuget::SearchResults + failure [ + { code: 401, message: 'Unauthorized' }, + { code: 403, message: 'Forbidden' }, + { code: 404, message: 'Not Found' } + ] + tags %w[nuget_packages] + end + get format: :json, urgency: :low do + search_options = { + include_prerelease_versions: params[:prerelease], + per_page: params[:take], + padding: params[:skip] + } + + results = search_packages(params[:q], search_options) + + track_package_event( + 'search_package', + :nuget, + **snowplow_gitlab_standard_context.merge(category: 'API::NugetPackages') + ) + + present ::Packages::Nuget::SearchResultsPresenter.new(results), + with: ::API::Entities::Nuget::SearchResults + end + end + end + end + end + end + end +end diff --git a/lib/api/concerns/packages/nuget/public_endpoints.rb b/lib/api/concerns/packages/nuget/public_endpoints.rb new file mode 100644 index 000000000000..37b503212d92 --- /dev/null +++ b/lib/api/concerns/packages/nuget/public_endpoints.rb @@ -0,0 +1,42 @@ +# frozen_string_literal: true + +# NuGet Package Manager Client API + +# These API endpoints are not consumed directly by users, so there is no documentation for the +# individual endpoints. They are called by the NuGet package manager client when users run commands +# like `nuget install` or `nuget push`. The usage of the GitLab NuGet registry is documented here: +# https://docs.gitlab.com/ee/user/packages/nuget_repository/ + +module API + module Concerns + module Packages + module Nuget + module PublicEndpoints + extend ActiveSupport::Concern + + included do + # https://docs.microsoft.com/en-us/nuget/api/service-index + desc 'The NuGet Service Index' do + detail 'This feature was introduced in GitLab 12.6' + success code: 200, model: ::API::Entities::Nuget::ServiceIndex + failure [ + { code: 404, message: 'Not Found' } + ] + tags %w[nuget_packages] + end + get 'index', format: :json, urgency: :default do + track_package_event( + 'cli_metadata', + :nuget, + **snowplow_gitlab_standard_context_without_auth.merge(category: 'API::NugetPackages') + ) + + present ::Packages::Nuget::ServiceIndexPresenter.new(project_or_group_without_auth), + with: ::API::Entities::Nuget::ServiceIndex + end + end + end + end + end + end +end diff --git a/lib/api/concerns/packages/nuget_endpoints.rb b/lib/api/concerns/packages/nuget_endpoints.rb deleted file mode 100644 index 5f32f0544f45..000000000000 --- a/lib/api/concerns/packages/nuget_endpoints.rb +++ /dev/null @@ -1,159 +0,0 @@ -# frozen_string_literal: true -# -# NuGet Package Manager Client API -# -# These API endpoints are not consumed directly by users, so there is no documentation for the -# individual endpoints. They are called by the NuGet package manager client when users run commands -# like `nuget install` or `nuget push`. The usage of the GitLab NuGet registry is documented here: -# https://docs.gitlab.com/ee/user/packages/nuget_repository/ -# -# Technical debt: https://gitlab.com/gitlab-org/gitlab/issues/35798 -module API - module Concerns - module Packages - module NugetEndpoints - extend ActiveSupport::Concern - - POSITIVE_INTEGER_REGEX = %r{\A[1-9]\d*\z}.freeze - NON_NEGATIVE_INTEGER_REGEX = %r{\A(0|[1-9]\d*)\z}.freeze - - included do - helpers do - def find_packages(package_name) - packages = package_finder(package_name).execute - - not_found!('Packages') unless packages.exists? - - packages - end - - def find_package(package_name, package_version) - package = package_finder(package_name, package_version).execute - .first - - not_found!('Package') unless package - - package - end - - def package_finder(package_name, package_version = nil) - ::Packages::Nuget::PackageFinder.new( - current_user, - project_or_group, - package_name: package_name, - package_version: package_version - ) - end - - def search_packages(search_term, search_options) - ::Packages::Nuget::SearchService - .new(current_user, project_or_group, params[:q], search_options) - .execute - end - end - - # https://docs.microsoft.com/en-us/nuget/api/service-index - desc 'The NuGet Service Index' do - detail 'This feature was introduced in GitLab 12.6' - success code: 200, model: ::API::Entities::Nuget::ServiceIndex - failure [ - { code: 401, message: 'Unauthorized' }, - { code: 403, message: 'Forbidden' }, - { code: 404, message: 'Not Found' } - ] - tags %w[nuget_packages] - end - get 'index', format: :json, urgency: :default do - authorize_packages_access!(project_or_group, required_permission) - - track_package_event('cli_metadata', :nuget, **snowplow_gitlab_standard_context.merge(category: 'API::NugetPackages')) - - present ::Packages::Nuget::ServiceIndexPresenter.new(project_or_group), - with: ::API::Entities::Nuget::ServiceIndex - end - - # https://docs.microsoft.com/en-us/nuget/api/registration-base-url-resource - params do - requires :package_name, type: String, desc: 'The NuGet package name', regexp: API::NO_SLASH_URL_PART_REGEX, documentation: { example: 'MyNuGetPkg' } - end - namespace '/metadata/*package_name' do - after_validation do - authorize_packages_access!(project_or_group, required_permission) - end - - desc 'The NuGet Metadata Service - Package name level' do - detail 'This feature was introduced in GitLab 12.8' - success code: 200, model: ::API::Entities::Nuget::PackagesMetadata - failure [ - { code: 401, message: 'Unauthorized' }, - { code: 403, message: 'Forbidden' }, - { code: 404, message: 'Not Found' } - ] - tags %w[nuget_packages] - end - get 'index', format: :json, urgency: :low do - present ::Packages::Nuget::PackagesMetadataPresenter.new(find_packages(params[:package_name])), - with: ::API::Entities::Nuget::PackagesMetadata - end - - desc 'The NuGet Metadata Service - Package name and version level' do - detail 'This feature was introduced in GitLab 12.8' - success code: 200, model: ::API::Entities::Nuget::PackageMetadata - failure [ - { code: 401, message: 'Unauthorized' }, - { code: 403, message: 'Forbidden' }, - { code: 404, message: 'Not Found' } - ] - tags %w[nuget_packages] - end - params do - requires :package_version, type: String, desc: 'The NuGet package version', regexp: API::NO_SLASH_URL_PART_REGEX, documentation: { example: '1.0.0' } - end - get '*package_version', format: :json, urgency: :low do - present ::Packages::Nuget::PackageMetadataPresenter.new(find_package(params[:package_name], params[:package_version])), - with: ::API::Entities::Nuget::PackageMetadata - end - end - - # https://docs.microsoft.com/en-us/nuget/api/search-query-service-resource - params do - optional :q, type: String, desc: 'The search term', documentation: { example: 'MyNuGet' } - optional :skip, type: Integer, desc: 'The number of results to skip', default: 0, regexp: NON_NEGATIVE_INTEGER_REGEX, documentation: { example: 1 } - optional :take, type: Integer, desc: 'The number of results to return', default: Kaminari.config.default_per_page, regexp: POSITIVE_INTEGER_REGEX, documentation: { example: 1 } - optional :prerelease, type: ::Grape::API::Boolean, desc: 'Include prerelease versions', default: true - end - namespace '/query' do - after_validation do - authorize_packages_access!(project_or_group, required_permission) - end - - desc 'The NuGet Search Service' do - detail 'This feature was introduced in GitLab 12.8' - success code: 200, model: ::API::Entities::Nuget::SearchResults - failure [ - { code: 401, message: 'Unauthorized' }, - { code: 403, message: 'Forbidden' }, - { code: 404, message: 'Not Found' } - ] - tags %w[nuget_packages] - end - get format: :json, urgency: :low do - search_options = { - include_prerelease_versions: params[:prerelease], - per_page: params[:take], - padding: params[:skip] - } - - results = search_packages(params[:q], search_options) - - track_package_event('search_package', :nuget, **snowplow_gitlab_standard_context.merge(category: 'API::NugetPackages')) - - present ::Packages::Nuget::SearchResultsPresenter.new(results), - with: ::API::Entities::Nuget::SearchResults - end - end - end - end - end - end -end diff --git a/lib/api/nuget_group_packages.rb b/lib/api/nuget_group_packages.rb index 2afcb915b06e..229032f7a5ae 100644 --- a/lib/api/nuget_group_packages.rb +++ b/lib/api/nuget_group_packages.rb @@ -17,11 +17,6 @@ class NugetGroupPackages < ::API::Base default_format :json - authenticate_with do |accept| - accept.token_types(:personal_access_token_with_username, :deploy_token_with_username, :job_token_with_username) - .sent_through(:http_basic_auth) - end - rescue_from ArgumentError do |e| render_api_error!(e.message, 400) end @@ -31,10 +26,17 @@ class NugetGroupPackages < ::API::Base end helpers do + include ::Gitlab::Utils::StrongMemoize + def project_or_group find_authorized_group! end + def project_or_group_without_auth + find_group(params[:id]).presence || not_found! + end + strong_memoize_attr :project_or_group_without_auth + def require_authenticated! unauthorized! unless current_user end @@ -43,23 +45,38 @@ def snowplow_gitlab_standard_context { namespace: find_authorized_group! } end + def snowplow_gitlab_standard_context_without_auth + { namespace: project_or_group_without_auth } + end + def required_permission :read_group end end params do - requires :id, types: [Integer, String], desc: 'The group ID or full group path.', regexp: ::API::Concerns::Packages::NugetEndpoints::POSITIVE_INTEGER_REGEX + requires :id, types: [Integer, String], desc: 'The group ID or full group path.', regexp: ::API::Concerns::Packages::Nuget::PrivateEndpoints::POSITIVE_INTEGER_REGEX end resource :groups, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do - namespace ':id/-/packages/nuget' do - after_validation do - # This API can't be accessed anonymously - require_authenticated! + namespace ':id/-/packages' do + namespace '/nuget' do + include ::API::Concerns::Packages::Nuget::PublicEndpoints + end + + authenticate_with do |accept| + accept.token_types(:personal_access_token_with_username, :deploy_token_with_username, :job_token_with_username) + .sent_through(:http_basic_auth) end - include ::API::Concerns::Packages::NugetEndpoints + namespace '/nuget' do + after_validation do + # This API can't be accessed anonymously + require_authenticated! + end + + include ::API::Concerns::Packages::Nuget::PrivateEndpoints + end end end end diff --git a/lib/api/nuget_project_packages.rb b/lib/api/nuget_project_packages.rb index cd16aaf6b5f0..2716d6f0b64a 100644 --- a/lib/api/nuget_project_packages.rb +++ b/lib/api/nuget_project_packages.rb @@ -17,14 +17,10 @@ class NugetProjectPackages < ::API::Base PACKAGE_FILENAME = 'package.nupkg' SYMBOL_PACKAGE_FILENAME = 'package.snupkg' + API_KEY_HEADER = 'X-Nuget-Apikey' default_format :json - authenticate_with do |accept| - accept.token_types(:personal_access_token_with_username, :deploy_token_with_username, :job_token_with_username) - .sent_through(:http_basic_auth) - end - rescue_from ArgumentError do |e| render_api_error!(e.message, 400) end @@ -34,6 +30,8 @@ class NugetProjectPackages < ::API::Base end helpers do + include ::Gitlab::Utils::StrongMemoize + params :file_params do requires :package, type: ::API::Validations::Types::WorkhorseFile, desc: 'The package file to be published (generated by Multipart middleware)', documentation: { type: 'file' } end @@ -42,10 +40,19 @@ def project_or_group authorized_user_project(action: :read_package) end + def project_or_group_without_auth + find_project(params[:id]).presence || not_found! + end + strong_memoize_attr :project_or_group_without_auth + def snowplow_gitlab_standard_context { project: project_or_group, namespace: project_or_group.namespace } end + def snowplow_gitlab_standard_context_without_auth + { project: project_or_group_without_auth, namespace: project_or_group_without_auth.namespace } + end + def authorize_nuget_upload project = project_or_group authorize_workhorse!( @@ -97,115 +104,127 @@ def required_permission end params do - requires :id, types: [String, Integer], desc: 'The ID or URL-encoded path of the project', regexp: ::API::Concerns::Packages::NugetEndpoints::POSITIVE_INTEGER_REGEX + requires :id, types: [String, Integer], desc: 'The ID or URL-encoded path of the project', regexp: ::API::Concerns::Packages::Nuget::PrivateEndpoints::POSITIVE_INTEGER_REGEX end resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do - namespace ':id/packages/nuget' do - include ::API::Concerns::Packages::NugetEndpoints - - # https://docs.microsoft.com/en-us/nuget/api/package-publish-resource - desc 'The NuGet Package Publish endpoint' do - detail 'This feature was introduced in GitLab 12.6' - success code: 201 - failure [ - { code: 400, message: 'Bad Request' }, - { code: 401, message: 'Unauthorized' }, - { code: 403, message: 'Forbidden' }, - { code: 404, message: 'Not Found' } - ] - tags %w[nuget_packages] + namespace ':id/packages' do + namespace '/nuget' do + include ::API::Concerns::Packages::Nuget::PublicEndpoints end - params do - use :file_params + authenticate_with do |accept| + accept.token_types(:personal_access_token_with_username, :deploy_token_with_username, :job_token_with_username) + .sent_through(:http_basic_auth) end - put urgency: :low do - upload_nuget_package_file do |package| - track_package_event( - 'push_package', - :nuget, - category: 'API::NugetPackages', - project: package.project, - namespace: package.project.namespace - ) - end - rescue ObjectStorage::RemoteStoreError => e - Gitlab::ErrorTracking.track_exception(e, extra: { file_name: params[:file_name], project_id: project_or_group.id }) - forbidden! - end + namespace '/nuget' do + include ::API::Concerns::Packages::Nuget::PrivateEndpoints - desc 'The NuGet Package Authorize endpoint' do - detail 'This feature was introduced in GitLab 14.1' - success code: 200 - failure [ - { code: 401, message: 'Unauthorized' }, - { code: 403, message: 'Forbidden' }, - { code: 404, message: 'Not Found' } - ] - tags %w[nuget_packages] - end - put 'authorize', urgency: :low do - authorize_nuget_upload - end - - # https://docs.microsoft.com/en-us/nuget/api/symbol-package-publish-resource - desc 'The NuGet Symbol Package Publish endpoint' do - detail 'This feature was introduced in GitLab 14.1' - success code: 201 - failure [ - { code: 400, message: 'Bad Request' }, - { code: 401, message: 'Unauthorized' }, - { code: 403, message: 'Forbidden' }, - { code: 404, message: 'Not Found' } - ] - tags %w[nuget_packages] - end - params do - use :file_params - end - put 'symbolpackage', urgency: :low do - upload_nuget_package_file(symbol_package: true) do |package| - track_package_event( - 'push_symbol_package', - :nuget, - category: 'API::NugetPackages', - project: package.project, - namespace: package.project.namespace - ) + # https://docs.microsoft.com/en-us/nuget/api/package-base-address-resource + params do + requires :package_name, type: String, desc: 'The NuGet package name', regexp: API::NO_SLASH_URL_PART_REGEX, documentation: { example: 'mynugetpkg.1.3.0.17.nupkg' } + end + namespace '/download/*package_name' do + after_validation do + authorize_read_package!(project_or_group) + end + + desc 'The NuGet Content Service - index request' do + detail 'This feature was introduced in GitLab 12.8' + success code: 200, model: ::API::Entities::Nuget::PackagesVersions + failure [ + { code: 401, message: 'Unauthorized' }, + { code: 403, message: 'Forbidden' }, + { code: 404, message: 'Not Found' } + ] + tags %w[nuget_packages] + end + get 'index', format: :json, urgency: :low do + present ::Packages::Nuget::PackagesVersionsPresenter.new(find_packages(params[:package_name])), + with: ::API::Entities::Nuget::PackagesVersions + end + + desc 'The NuGet Content Service - content request' do + detail 'This feature was introduced in GitLab 12.8' + success code: 200 + failure [ + { code: 401, message: 'Unauthorized' }, + { code: 403, message: 'Forbidden' }, + { code: 404, message: 'Not Found' } + ] + tags %w[nuget_packages] + end + params do + requires :package_version, type: String, desc: 'The NuGet package version', regexp: API::NO_SLASH_URL_PART_REGEX, documentation: { example: '1.3.0.17' } + requires :package_filename, type: String, desc: 'The NuGet package filename', regexp: API::NO_SLASH_URL_PART_REGEX, documentation: { example: 'mynugetpkg.1.3.0.17.nupkg' } + end + get '*package_version/*package_filename', format: [:nupkg, :snupkg], urgency: :low do + filename = "#{params[:package_filename]}.#{params[:format]}" + package_file = ::Packages::PackageFileFinder.new(find_package(params[:package_name], params[:package_version]), filename, with_file_name_like: true) + .execute + + not_found!('Package') unless package_file + + track_package_event( + params[:format] == 'snupkg' ? 'pull_symbol_package' : 'pull_package', + :nuget, + category: 'API::NugetPackages', + project: package_file.project, + namespace: package_file.project.namespace + ) + + # nuget and dotnet don't support 302 Moved status codes, supports_direct_download has to be set to false + present_package_file!(package_file, supports_direct_download: false) + end end - rescue ObjectStorage::RemoteStoreError => e - Gitlab::ErrorTracking.track_exception(e, extra: { file_name: params[:file_name], project_id: project_or_group.id }) - - forbidden! end - desc 'The NuGet Symbol Package Authorize endpoint' do - detail 'This feature was introduced in GitLab 14.1' - success code: 200 - failure [ - { code: 401, message: 'Unauthorized' }, - { code: 403, message: 'Forbidden' }, - { code: 404, message: 'Not Found' } - ] - tags %w[nuget_packages] - end - put 'symbolpackage/authorize', urgency: :low do - authorize_nuget_upload + # To support an additional authentication option for download endpoints, + # we redefine the `authenticate_with` method by combining the previous + # authentication option with the new one. + authenticate_with do |accept| + accept.token_types(:personal_access_token_with_username, :deploy_token_with_username, :job_token_with_username) + .sent_through(:http_basic_auth) + accept.token_types(:personal_access_token, :deploy_token, :job_token) + .sent_through(http_header: API_KEY_HEADER) end - # https://docs.microsoft.com/en-us/nuget/api/package-base-address-resource - params do - requires :package_name, type: String, desc: 'The NuGet package name', regexp: API::NO_SLASH_URL_PART_REGEX, documentation: { example: 'mynugetpkg.1.3.0.17.nupkg' } - end - namespace '/download/*package_name' do - after_validation do - authorize_read_package!(project_or_group) + namespace '/nuget' do + # https://docs.microsoft.com/en-us/nuget/api/package-publish-resource + desc 'The NuGet Package Publish endpoint' do + detail 'This feature was introduced in GitLab 12.6' + success code: 201 + failure [ + { code: 400, message: 'Bad Request' }, + { code: 401, message: 'Unauthorized' }, + { code: 403, message: 'Forbidden' }, + { code: 404, message: 'Not Found' } + ] + tags %w[nuget_packages] + end + + params do + use :file_params + end + put urgency: :low do + upload_nuget_package_file do |package| + track_package_event( + 'push_package', + :nuget, + category: 'API::NugetPackages', + project: package.project, + namespace: package.project.namespace + ) + end + rescue ObjectStorage::RemoteStoreError => e + Gitlab::ErrorTracking.track_exception(e, extra: { file_name: params[:file_name], project_id: project_or_group.id }) + + forbidden! end - desc 'The NuGet Content Service - index request' do - detail 'This feature was introduced in GitLab 12.8' - success code: 200, model: ::API::Entities::Nuget::PackagesVersions + desc 'The NuGet Package Authorize endpoint' do + detail 'This feature was introduced in GitLab 14.1' + success code: 200 failure [ { code: 401, message: 'Unauthorized' }, { code: 403, message: 'Forbidden' }, @@ -213,15 +232,16 @@ def required_permission ] tags %w[nuget_packages] end - get 'index', format: :json, urgency: :low do - present ::Packages::Nuget::PackagesVersionsPresenter.new(find_packages(params[:package_name])), - with: ::API::Entities::Nuget::PackagesVersions + put 'authorize', urgency: :low do + authorize_nuget_upload end - desc 'The NuGet Content Service - content request' do - detail 'This feature was introduced in GitLab 12.8' - success code: 200 + # https://docs.microsoft.com/en-us/nuget/api/symbol-package-publish-resource + desc 'The NuGet Symbol Package Publish endpoint' do + detail 'This feature was introduced in GitLab 14.1' + success code: 201 failure [ + { code: 400, message: 'Bad Request' }, { code: 401, message: 'Unauthorized' }, { code: 403, message: 'Forbidden' }, { code: 404, message: 'Not Found' } @@ -229,26 +249,36 @@ def required_permission tags %w[nuget_packages] end params do - requires :package_version, type: String, desc: 'The NuGet package version', regexp: API::NO_SLASH_URL_PART_REGEX, documentation: { example: '1.3.0.17' } - requires :package_filename, type: String, desc: 'The NuGet package filename', regexp: API::NO_SLASH_URL_PART_REGEX, documentation: { example: 'mynugetpkg.1.3.0.17.nupkg' } + use :file_params + end + put 'symbolpackage', urgency: :low do + upload_nuget_package_file(symbol_package: true) do |package| + track_package_event( + 'push_symbol_package', + :nuget, + category: 'API::NugetPackages', + project: package.project, + namespace: package.project.namespace + ) + end + rescue ObjectStorage::RemoteStoreError => e + Gitlab::ErrorTracking.track_exception(e, extra: { file_name: params[:file_name], project_id: project_or_group.id }) + + forbidden! + end + + desc 'The NuGet Symbol Package Authorize endpoint' do + detail 'This feature was introduced in GitLab 14.1' + success code: 200 + failure [ + { code: 401, message: 'Unauthorized' }, + { code: 403, message: 'Forbidden' }, + { code: 404, message: 'Not Found' } + ] + tags %w[nuget_packages] end - get '*package_version/*package_filename', format: [:nupkg, :snupkg], urgency: :low do - filename = "#{params[:package_filename]}.#{params[:format]}" - package_file = ::Packages::PackageFileFinder.new(find_package(params[:package_name], params[:package_version]), filename, with_file_name_like: true) - .execute - - not_found!('Package') unless package_file - - track_package_event( - params[:format] == 'snupkg' ? 'pull_symbol_package' : 'pull_package', - :nuget, - category: 'API::NugetPackages', - project: package_file.project, - namespace: package_file.project.namespace - ) - - # nuget and dotnet don't support 302 Moved status codes, supports_direct_download has to be set to false - present_package_file!(package_file, supports_direct_download: false) + put 'symbolpackage/authorize', urgency: :low do + authorize_nuget_upload end end end diff --git a/lib/gitlab/api_authentication/token_locator.rb b/lib/gitlab/api_authentication/token_locator.rb index df342905d2ef..5656ea0d1201 100644 --- a/lib/gitlab/api_authentication/token_locator.rb +++ b/lib/gitlab/api_authentication/token_locator.rb @@ -8,22 +8,23 @@ class TokenLocator include ActiveModel::Validations include ActionController::HttpAuthentication::Basic + VALID_LOCATIONS = %i[ + http_basic_auth + http_token + http_bearer_token + http_deploy_token_header + http_job_token_header + http_private_token_header + http_header + token_param + ].freeze + attr_reader :location - validates :location, inclusion: { - in: %i[ - http_basic_auth - http_token - http_bearer_token - http_deploy_token_header - http_job_token_header - http_private_token_header - token_param - ] - } + validates :location, inclusion: { in: VALID_LOCATIONS } def initialize(location) - @location = location + @location = extract_location(location) validate! end @@ -41,6 +42,8 @@ def extract(request) extract_from_http_job_token_header request when :http_private_token_header extract_from_http_private_token_header request + when :http_header + extract_from_http_header request when :token_param extract_from_token_param request end @@ -48,6 +51,16 @@ def extract(request) private + def extract_location(location) + case location + when Symbol + location + when Hash + result, @token_identifier = location.detect { |k, _v| VALID_LOCATIONS.include?(k) } + result + end + end + def extract_from_http_basic_auth(request) username, password = user_name_and_password(request) return unless username.present? && password.present? @@ -96,6 +109,13 @@ def extract_from_token_param(request) UsernameAndPassword.new(nil, password) end + + def extract_from_http_header(request) + password = request.headers[@token_identifier] + return unless password.present? + + UsernameAndPassword.new(nil, password) + end end end end diff --git a/spec/lib/gitlab/api_authentication/token_locator_spec.rb b/spec/lib/gitlab/api_authentication/token_locator_spec.rb index 4b19a3d5846d..9b33d4439601 100644 --- a/spec/lib/gitlab/api_authentication/token_locator_spec.rb +++ b/spec/lib/gitlab/api_authentication/token_locator_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Gitlab::APIAuthentication::TokenLocator do +RSpec.describe Gitlab::APIAuthentication::TokenLocator, feature_category: :system_access do let_it_be(:user) { create(:user) } let_it_be(:project, reload: true) { create(:project, :public) } let_it_be(:personal_access_token) { create(:personal_access_token, user: user) } @@ -157,6 +157,27 @@ end end + context 'with :http_header' do + let(:type) { { http_header: 'Api-Key' } } + + context 'without credentials' do + let(:request) { double(headers: {}) } + + it 'returns nil' do + expect(subject).to be(nil) + end + end + + context 'with credentials' do + let(:password) { 'bar' } + let(:request) { double(headers: { 'Api-Key' => password }) } + + it 'returns the credentials' do + expect(subject.password).to eq(password) + end + end + end + context 'with :token_param' do let(:type) { :token_param } diff --git a/spec/requests/api/nuget_group_packages_spec.rb b/spec/requests/api/nuget_group_packages_spec.rb index facbc01220dd..07199119cb58 100644 --- a/spec/requests/api/nuget_group_packages_spec.rb +++ b/spec/requests/api/nuget_group_packages_spec.rb @@ -26,13 +26,7 @@ def snowplow_context(user_role: :developer) shared_examples 'handling all endpoints' do describe 'GET /api/v4/groups/:id/-/packages/nuget' do - it_behaves_like 'handling nuget service requests', - example_names_with_status: { - anonymous_requests_example_name: 'rejects nuget packages access', - anonymous_requests_status: :unauthorized, - guest_requests_example_name: 'process nuget service index request', - guest_requests_status: :success - } do + it_behaves_like 'handling nuget service requests' do let(:url) { "/groups/#{target.id}/-/packages/nuget/index.json" } end end diff --git a/spec/support/shared_examples/requests/api/nuget_endpoints_shared_examples.rb b/spec/support/shared_examples/requests/api/nuget_endpoints_shared_examples.rb index 7cafe8bb3680..432e67ee21e7 100644 --- a/spec/support/shared_examples/requests/api/nuget_endpoints_shared_examples.rb +++ b/spec/support/shared_examples/requests/api/nuget_endpoints_shared_examples.rb @@ -1,100 +1,40 @@ # frozen_string_literal: true -RSpec.shared_examples 'handling nuget service requests' do |example_names_with_status: {}| - anonymous_requests_example_name = example_names_with_status.fetch(:anonymous_requests_example_name, 'process nuget service index request') - anonymous_requests_status = example_names_with_status.fetch(:anonymous_requests_status, :success) - guest_requests_example_name = example_names_with_status.fetch(:guest_requests_example_name, 'rejects nuget packages access') - guest_requests_status = example_names_with_status.fetch(:guest_requests_status, :forbidden) - +RSpec.shared_examples 'handling nuget service requests' do subject { get api(url) } context 'with valid target' do using RSpec::Parameterized::TableSyntax - context 'personal token' do - where(:visibility_level, :user_role, :member, :user_token, :shared_examples_name, :expected_status) do - 'PUBLIC' | :developer | true | true | 'process nuget service index request' | :success - 'PUBLIC' | :guest | true | true | 'process nuget service index request' | :success - 'PUBLIC' | :developer | true | false | 'rejects nuget packages access' | :unauthorized - 'PUBLIC' | :guest | true | false | 'rejects nuget packages access' | :unauthorized - 'PUBLIC' | :developer | false | true | 'process nuget service index request' | :success - 'PUBLIC' | :guest | false | true | 'process nuget service index request' | :success - 'PUBLIC' | :developer | false | false | 'rejects nuget packages access' | :unauthorized - 'PUBLIC' | :guest | false | false | 'rejects nuget packages access' | :unauthorized - 'PUBLIC' | :anonymous | false | true | anonymous_requests_example_name | anonymous_requests_status - 'PRIVATE' | :developer | true | true | 'process nuget service index request' | :success - 'PRIVATE' | :guest | true | true | guest_requests_example_name | guest_requests_status - 'PRIVATE' | :developer | true | false | 'rejects nuget packages access' | :unauthorized - 'PRIVATE' | :guest | true | false | 'rejects nuget packages access' | :unauthorized - 'PRIVATE' | :developer | false | true | 'rejects nuget packages access' | :not_found - 'PRIVATE' | :guest | false | true | 'rejects nuget packages access' | :not_found - 'PRIVATE' | :developer | false | false | 'rejects nuget packages access' | :unauthorized - 'PRIVATE' | :guest | false | false | 'rejects nuget packages access' | :unauthorized - 'PRIVATE' | :anonymous | false | true | 'rejects nuget packages access' | :unauthorized - end - - with_them do - let(:token) { user_token ? personal_access_token.token : 'wrong' } - let(:headers) { user_role == :anonymous ? {} : basic_auth_header(user.username, token) } - let(:snowplow_gitlab_standard_context) { snowplow_context(user_role: user_role) } - - subject { get api(url), headers: headers } - - before do - update_visibility_to(Gitlab::VisibilityLevel.const_get(visibility_level, false)) - end - - it_behaves_like params[:shared_examples_name], params[:user_role], params[:expected_status], params[:member] - end + where(:visibility_level, :user_role, :member, :shared_examples_name, :expected_status) do + 'PUBLIC' | :developer | true | 'process nuget service index request' | :success + 'PUBLIC' | :guest | true | 'process nuget service index request' | :success + 'PUBLIC' | :developer | false | 'process nuget service index request' | :success + 'PUBLIC' | :guest | false | 'process nuget service index request' | :success + 'PUBLIC' | :anonymous | false | 'process nuget service index request' | :success + 'PRIVATE' | :developer | true | 'process nuget service index request' | :success + 'PRIVATE' | :guest | true | 'process nuget service index request' | :success + 'PRIVATE' | :developer | false | 'process nuget service index request' | :success + 'PRIVATE' | :guest | false | 'process nuget service index request' | :success + 'PRIVATE' | :anonymous | false | 'process nuget service index request' | :success end - context 'with job token' do - where(:visibility_level, :user_role, :member, :user_token, :shared_examples_name, :expected_status) do - 'PUBLIC' | :developer | true | true | 'process nuget service index request' | :success - 'PUBLIC' | :guest | true | true | 'process nuget service index request' | :success - 'PUBLIC' | :developer | true | false | 'rejects nuget packages access' | :unauthorized - 'PUBLIC' | :guest | true | false | 'rejects nuget packages access' | :unauthorized - 'PUBLIC' | :developer | false | true | 'process nuget service index request' | :success - 'PUBLIC' | :guest | false | true | 'process nuget service index request' | :success - 'PUBLIC' | :developer | false | false | 'rejects nuget packages access' | :unauthorized - 'PUBLIC' | :guest | false | false | 'rejects nuget packages access' | :unauthorized - 'PUBLIC' | :anonymous | false | true | anonymous_requests_example_name | anonymous_requests_status - 'PRIVATE' | :developer | true | true | 'process nuget service index request' | :success - 'PRIVATE' | :guest | true | true | guest_requests_example_name | guest_requests_status - 'PRIVATE' | :developer | true | false | 'rejects nuget packages access' | :unauthorized - 'PRIVATE' | :guest | true | false | 'rejects nuget packages access' | :unauthorized - 'PRIVATE' | :developer | false | true | 'rejects nuget packages access' | :not_found - 'PRIVATE' | :guest | false | true | 'rejects nuget packages access' | :not_found - 'PRIVATE' | :developer | false | false | 'rejects nuget packages access' | :unauthorized - 'PRIVATE' | :guest | false | false | 'rejects nuget packages access' | :unauthorized - 'PRIVATE' | :anonymous | false | true | 'rejects nuget packages access' | :unauthorized - end - - with_them do - let(:job) { user_token ? create(:ci_build, project: project, user: user, status: :running) : double(token: 'wrong') } - let(:headers) { user_role == :anonymous ? {} : job_basic_auth_header(job) } - let(:snowplow_gitlab_standard_context) { snowplow_context(user_role: user_role) } - - subject { get api(url), headers: headers } + with_them do + let(:snowplow_gitlab_standard_context) { snowplow_context(user_role: :anonymous) } - before do - update_visibility_to(Gitlab::VisibilityLevel.const_get(visibility_level, false)) - end + subject { get api(url) } - it_behaves_like params[:shared_examples_name], params[:user_role], params[:expected_status], params[:member] + before do + update_visibility_to(Gitlab::VisibilityLevel.const_get(visibility_level, false)) end - end - end - it_behaves_like 'deploy token for package GET requests' do - before do - update_visibility_to(Gitlab::VisibilityLevel::PRIVATE) + it_behaves_like params[:shared_examples_name], params[:user_role], params[:expected_status], params[:member] end end - it_behaves_like 'rejects nuget access with unknown target id' + it_behaves_like 'rejects nuget access with unknown target id', not_found_response: :not_found - it_behaves_like 'rejects nuget access with invalid target id' + it_behaves_like 'rejects nuget access with invalid target id', not_found_response: :not_found end RSpec.shared_examples 'handling nuget metadata requests with package name' do |example_names_with_status: {}| diff --git a/spec/support/shared_examples/requests/api/nuget_packages_shared_examples.rb b/spec/support/shared_examples/requests/api/nuget_packages_shared_examples.rb index 3abe545db594..d6a0055700db 100644 --- a/spec/support/shared_examples/requests/api/nuget_packages_shared_examples.rb +++ b/spec/support/shared_examples/requests/api/nuget_packages_shared_examples.rb @@ -399,7 +399,7 @@ it_behaves_like 'a package tracking event', 'API::NugetPackages', 'search_package' end -RSpec.shared_examples 'rejects nuget access with invalid target id' do +RSpec.shared_examples 'rejects nuget access with invalid target id' do |not_found_response: :unauthorized| context 'with a target id with invalid integers' do using RSpec::Parameterized::TableSyntax @@ -411,7 +411,7 @@ '%20' | :bad_request '%2e%2e%2f' | :bad_request 'NaN' | :bad_request - 00002345 | :unauthorized + 00002345 | not_found_response 'anything25' | :bad_request end @@ -421,12 +421,12 @@ end end -RSpec.shared_examples 'rejects nuget access with unknown target id' do +RSpec.shared_examples 'rejects nuget access with unknown target id' do |not_found_response: :unauthorized| context 'with an unknown target' do let(:target) { double(id: 1234567890) } context 'as anonymous' do - it_behaves_like 'rejects nuget packages access', :anonymous, :unauthorized + it_behaves_like 'rejects nuget packages access', :anonymous, not_found_response end context 'as authenticated user' do @@ -441,30 +441,59 @@ 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 | 'process nuget workhorse authorization' | :success - 'PUBLIC' | :guest | true | true | 'rejects nuget packages access' | :forbidden - 'PUBLIC' | :developer | true | false | 'rejects nuget packages access' | :unauthorized - 'PUBLIC' | :guest | true | false | 'rejects nuget packages access' | :unauthorized - 'PUBLIC' | :developer | false | true | 'rejects nuget packages access' | :forbidden - 'PUBLIC' | :guest | false | true | 'rejects nuget packages access' | :forbidden - 'PUBLIC' | :developer | false | false | 'rejects nuget packages access' | :unauthorized - 'PUBLIC' | :guest | false | false | 'rejects nuget packages access' | :unauthorized - 'PUBLIC' | :anonymous | false | true | 'rejects nuget packages access' | :unauthorized - 'PRIVATE' | :developer | true | true | 'process nuget workhorse authorization' | :success - 'PRIVATE' | :guest | true | true | 'rejects nuget packages access' | :forbidden - 'PRIVATE' | :developer | true | false | 'rejects nuget packages access' | :unauthorized - 'PRIVATE' | :guest | true | false | 'rejects nuget packages access' | :unauthorized - 'PRIVATE' | :developer | false | true | 'rejects nuget packages access' | :not_found - 'PRIVATE' | :guest | false | true | 'rejects nuget packages access' | :not_found - 'PRIVATE' | :developer | false | false | 'rejects nuget packages access' | :unauthorized - 'PRIVATE' | :guest | false | false | 'rejects nuget packages access' | :unauthorized - 'PRIVATE' | :anonymous | false | true | 'rejects nuget packages access' | :unauthorized + where(:visibility_level, :user_role, :member, :user_token, :sent_through, :shared_examples_name, :expected_status) do + 'PUBLIC' | :developer | true | true | :basic_auth | 'process nuget workhorse authorization' | :success + 'PUBLIC' | :guest | true | true | :basic_auth | 'rejects nuget packages access' | :forbidden + 'PUBLIC' | :developer | true | false | :basic_auth | 'rejects nuget packages access' | :unauthorized + 'PUBLIC' | :guest | true | false | :basic_auth | 'rejects nuget packages access' | :unauthorized + 'PUBLIC' | :developer | false | true | :basic_auth | 'rejects nuget packages access' | :forbidden + 'PUBLIC' | :guest | false | true | :basic_auth | 'rejects nuget packages access' | :forbidden + 'PUBLIC' | :developer | false | false | :basic_auth | 'rejects nuget packages access' | :unauthorized + 'PUBLIC' | :guest | false | false | :basic_auth | 'rejects nuget packages access' | :unauthorized + 'PRIVATE' | :developer | true | true | :basic_auth | 'process nuget workhorse authorization' | :success + 'PRIVATE' | :guest | true | true | :basic_auth | 'rejects nuget packages access' | :forbidden + 'PRIVATE' | :developer | true | false | :basic_auth | 'rejects nuget packages access' | :unauthorized + 'PRIVATE' | :guest | true | false | :basic_auth | 'rejects nuget packages access' | :unauthorized + 'PRIVATE' | :developer | false | true | :basic_auth | 'rejects nuget packages access' | :not_found + 'PRIVATE' | :guest | false | true | :basic_auth | 'rejects nuget packages access' | :not_found + 'PRIVATE' | :developer | false | false | :basic_auth | 'rejects nuget packages access' | :unauthorized + 'PRIVATE' | :guest | false | false | :basic_auth | 'rejects nuget packages access' | :unauthorized + + 'PUBLIC' | :developer | true | true | :api_key | 'process nuget workhorse authorization' | :success + 'PUBLIC' | :guest | true | true | :api_key | 'rejects nuget packages access' | :forbidden + 'PUBLIC' | :developer | true | false | :api_key | 'rejects nuget packages access' | :unauthorized + 'PUBLIC' | :guest | true | false | :api_key | 'rejects nuget packages access' | :unauthorized + 'PUBLIC' | :developer | false | true | :api_key | 'rejects nuget packages access' | :forbidden + 'PUBLIC' | :guest | false | true | :api_key | 'rejects nuget packages access' | :forbidden + 'PUBLIC' | :developer | false | false | :api_key | 'rejects nuget packages access' | :unauthorized + 'PUBLIC' | :guest | false | false | :api_key | 'rejects nuget packages access' | :unauthorized + 'PRIVATE' | :developer | true | true | :api_key | 'process nuget workhorse authorization' | :success + 'PRIVATE' | :guest | true | true | :api_key | 'rejects nuget packages access' | :forbidden + 'PRIVATE' | :developer | true | false | :api_key | 'rejects nuget packages access' | :unauthorized + 'PRIVATE' | :guest | true | false | :api_key | 'rejects nuget packages access' | :unauthorized + 'PRIVATE' | :developer | false | true | :api_key | 'rejects nuget packages access' | :not_found + 'PRIVATE' | :guest | false | true | :api_key | 'rejects nuget packages access' | :not_found + 'PRIVATE' | :developer | false | false | :api_key | 'rejects nuget packages access' | :unauthorized + 'PRIVATE' | :guest | false | false | :api_key | 'rejects nuget packages access' | :unauthorized + + 'PUBLIC' | :anonymous | false | true | nil | 'rejects nuget packages access' | :unauthorized + 'PRIVATE' | :anonymous | false | true | nil | 'rejects nuget packages access' | :unauthorized end with_them do let(:token) { user_token ? personal_access_token.token : 'wrong' } - let(:user_headers) { user_role == :anonymous ? {} : basic_auth_header(user.username, token) } + + let(:user_headers) do + case sent_through + when :basic_auth + basic_auth_header(user.username, token) + when :api_key + { 'X-NuGet-ApiKey' => token } + else + {} + end + end + let(:headers) { user_headers.merge(workhorse_headers) } before do @@ -490,30 +519,59 @@ 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 | 'process nuget upload' | :created - 'PUBLIC' | :guest | true | true | 'rejects nuget packages access' | :forbidden - 'PUBLIC' | :developer | true | false | 'rejects nuget packages access' | :unauthorized - 'PUBLIC' | :guest | true | false | 'rejects nuget packages access' | :unauthorized - 'PUBLIC' | :developer | false | true | 'rejects nuget packages access' | :forbidden - 'PUBLIC' | :guest | false | true | 'rejects nuget packages access' | :forbidden - 'PUBLIC' | :developer | false | false | 'rejects nuget packages access' | :unauthorized - 'PUBLIC' | :guest | false | false | 'rejects nuget packages access' | :unauthorized - 'PUBLIC' | :anonymous | false | true | 'rejects nuget packages access' | :unauthorized - 'PRIVATE' | :developer | true | true | 'process nuget upload' | :created - 'PRIVATE' | :guest | true | true | 'rejects nuget packages access' | :forbidden - 'PRIVATE' | :developer | true | false | 'rejects nuget packages access' | :unauthorized - 'PRIVATE' | :guest | true | false | 'rejects nuget packages access' | :unauthorized - 'PRIVATE' | :developer | false | true | 'rejects nuget packages access' | :not_found - 'PRIVATE' | :guest | false | true | 'rejects nuget packages access' | :not_found - 'PRIVATE' | :developer | false | false | 'rejects nuget packages access' | :unauthorized - 'PRIVATE' | :guest | false | false | 'rejects nuget packages access' | :unauthorized - 'PRIVATE' | :anonymous | false | true | 'rejects nuget packages access' | :unauthorized + where(:visibility_level, :user_role, :member, :user_token, :sent_through, :shared_examples_name, :expected_status) do + 'PUBLIC' | :developer | true | true | :basic_auth | 'process nuget upload' | :created + 'PUBLIC' | :guest | true | true | :basic_auth | 'rejects nuget packages access' | :forbidden + 'PUBLIC' | :developer | true | false | :basic_auth | 'rejects nuget packages access' | :unauthorized + 'PUBLIC' | :guest | true | false | :basic_auth | 'rejects nuget packages access' | :unauthorized + 'PUBLIC' | :developer | false | true | :basic_auth | 'rejects nuget packages access' | :forbidden + 'PUBLIC' | :guest | false | true | :basic_auth | 'rejects nuget packages access' | :forbidden + 'PUBLIC' | :developer | false | false | :basic_auth | 'rejects nuget packages access' | :unauthorized + 'PUBLIC' | :guest | false | false | :basic_auth | 'rejects nuget packages access' | :unauthorized + 'PRIVATE' | :developer | true | true | :basic_auth | 'process nuget upload' | :created + 'PRIVATE' | :guest | true | true | :basic_auth | 'rejects nuget packages access' | :forbidden + 'PRIVATE' | :developer | true | false | :basic_auth | 'rejects nuget packages access' | :unauthorized + 'PRIVATE' | :guest | true | false | :basic_auth | 'rejects nuget packages access' | :unauthorized + 'PRIVATE' | :developer | false | true | :basic_auth | 'rejects nuget packages access' | :not_found + 'PRIVATE' | :guest | false | true | :basic_auth | 'rejects nuget packages access' | :not_found + 'PRIVATE' | :developer | false | false | :basic_auth | 'rejects nuget packages access' | :unauthorized + 'PRIVATE' | :guest | false | false | :basic_auth | 'rejects nuget packages access' | :unauthorized + + 'PUBLIC' | :developer | true | true | :api_key | 'process nuget upload' | :created + 'PUBLIC' | :guest | true | true | :api_key | 'rejects nuget packages access' | :forbidden + 'PUBLIC' | :developer | true | false | :api_key | 'rejects nuget packages access' | :unauthorized + 'PUBLIC' | :guest | true | false | :api_key | 'rejects nuget packages access' | :unauthorized + 'PUBLIC' | :developer | false | true | :api_key | 'rejects nuget packages access' | :forbidden + 'PUBLIC' | :guest | false | true | :api_key | 'rejects nuget packages access' | :forbidden + 'PUBLIC' | :developer | false | false | :api_key | 'rejects nuget packages access' | :unauthorized + 'PUBLIC' | :guest | false | false | :api_key | 'rejects nuget packages access' | :unauthorized + 'PRIVATE' | :developer | true | true | :api_key | 'process nuget upload' | :created + 'PRIVATE' | :guest | true | true | :api_key | 'rejects nuget packages access' | :forbidden + 'PRIVATE' | :developer | true | false | :api_key | 'rejects nuget packages access' | :unauthorized + 'PRIVATE' | :guest | true | false | :api_key | 'rejects nuget packages access' | :unauthorized + 'PRIVATE' | :developer | false | true | :api_key | 'rejects nuget packages access' | :not_found + 'PRIVATE' | :guest | false | true | :api_key | 'rejects nuget packages access' | :not_found + 'PRIVATE' | :developer | false | false | :api_key | 'rejects nuget packages access' | :unauthorized + 'PRIVATE' | :guest | false | false | :api_key | 'rejects nuget packages access' | :unauthorized + + 'PUBLIC' | :anonymous | false | true | nil | 'rejects nuget packages access' | :unauthorized + 'PRIVATE' | :anonymous | false | true | nil | 'rejects nuget packages access' | :unauthorized end with_them do let(:token) { user_token ? personal_access_token.token : 'wrong' } - let(:user_headers) { user_role == :anonymous ? {} : basic_auth_header(user.username, token) } + + let(:user_headers) do + case sent_through + when :basic_auth + basic_auth_header(user.username, token) + when :api_key + { 'X-NuGet-ApiKey' => token } + else + {} + end + end + let(:headers) { user_headers.merge(workhorse_headers) } let(:snowplow_gitlab_standard_context) { { project: project, user: user, namespace: project.namespace, property: 'i_package_nuget_user' } } -- GitLab