diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2020-12-17 11:59:07 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2020-12-17 11:59:07 +0000 |
commit | 8b573c94895dc0ac0e1d9d59cf3e8745e8b539ca (patch) | |
tree | 544930fb309b30317ae9797a9683768705d664c4 /lib/api | |
parent | 4b1de649d0168371549608993deac953eb692019 (diff) | |
download | gitlab-ce-8b573c94895dc0ac0e1d9d59cf3e8745e8b539ca.tar.gz |
Add latest changes from gitlab-org/gitlab@13-7-stable-eev13.7.0-rc42
Diffstat (limited to 'lib/api')
69 files changed, 1000 insertions, 751 deletions
diff --git a/lib/api/admin/instance_clusters.rb b/lib/api/admin/instance_clusters.rb index 679e231b283..b724d3a38dc 100644 --- a/lib/api/admin/instance_clusters.rb +++ b/lib/api/admin/instance_clusters.rb @@ -76,6 +76,7 @@ module API optional :namespace_per_environment, default: true, type: Boolean, desc: 'Deploy each environment to a separate Kubernetes namespace' optional :domain, type: String, desc: 'Cluster base domain' optional :management_project_id, type: Integer, desc: 'The ID of the management project' + optional :managed, type: Boolean, desc: 'Determines if GitLab will manage namespaces and service accounts for this cluster' optional :platform_kubernetes_attributes, type: Hash, desc: %q(Platform Kubernetes data) do optional :api_url, type: String, desc: 'URL to access the Kubernetes API' optional :token, type: String, desc: 'Token to authenticate against Kubernetes' diff --git a/lib/api/api.rb b/lib/api/api.rb index ea149f25584..06c2b46a2f2 100644 --- a/lib/api/api.rb +++ b/lib/api/api.rb @@ -211,7 +211,7 @@ module API mount ::API::ProjectPackages mount ::API::GroupPackages mount ::API::PackageFiles - mount ::API::NugetPackages + mount ::API::NugetProjectPackages mount ::API::PypiPackages mount ::API::ComposerPackages mount ::API::ConanProjectPackages diff --git a/lib/api/boards.rb b/lib/api/boards.rb index f4b23c507f4..e2d30dd7c2b 100644 --- a/lib/api/boards.rb +++ b/lib/api/boards.rb @@ -117,8 +117,6 @@ module API use :list_creation_params end post '/lists' do - authorize_list_type_resource! - authorize!(:admin_list, user_project) create_list diff --git a/lib/api/boards_responses.rb b/lib/api/boards_responses.rb index 2ae82f78e01..89355c84401 100644 --- a/lib/api/boards_responses.rb +++ b/lib/api/boards_responses.rb @@ -45,21 +45,17 @@ module API def create_list create_list_service = - ::Boards::Lists::CreateService.new(board_parent, current_user, create_list_params) + ::Boards::Lists::CreateService.new(board_parent, current_user, declared_params.compact.with_indifferent_access) - list = create_list_service.execute(board) + response = create_list_service.execute(board) - if list.valid? - present list, with: Entities::List + if response.success? + present response.payload[:list], with: Entities::List else - render_validation_error!(list) + render_api_error!({ error: response.errors.first }, 400) end end - def create_list_params - params.slice(:label_id) - end - def move_list(list) move_list_service = ::Boards::Lists::MoveService.new(board_parent, current_user, { position: params[:position].to_i }) @@ -80,14 +76,6 @@ module API end end - # rubocop: disable CodeReuse/ActiveRecord - def authorize_list_type_resource! - unless available_labels_for(board_parent).exists?(params[:label_id]) - render_api_error!({ error: 'Label not found!' }, 400) - end - end - # rubocop: enable CodeReuse/ActiveRecord - params :list_creation_params do requires :label_id, type: Integer, desc: 'The ID of an existing label' end diff --git a/lib/api/ci/runner.rb b/lib/api/ci/runner.rb index 85232b4ae1b..86e1a939df1 100644 --- a/lib/api/ci/runner.rb +++ b/lib/api/ci/runner.rb @@ -176,6 +176,10 @@ module API optional :state, type: String, desc: %q(Job's status: success, failed) optional :checksum, type: String, desc: %q(Job's trace CRC32 checksum) optional :failure_reason, type: String, desc: %q(Job's failure_reason) + optional :output, type: Hash, desc: %q(Build log state) do + optional :checksum, type: String, desc: %q(Job's trace CRC32 checksum) + optional :bytesize, type: Integer, desc: %q(Job's trace size in bytes) + end end put '/:id' do job = authenticate_job! diff --git a/lib/api/composer_packages.rb b/lib/api/composer_packages.rb index 0ac5cc45ccf..1181650fe96 100644 --- a/lib/api/composer_packages.rb +++ b/lib/api/composer_packages.rb @@ -38,6 +38,8 @@ module API packages = ::Packages::Composer::PackagesFinder.new(current_user, user_group).execute if params[:package_name].present? + params[:package_name], params[:sha] = params[:package_name].split('$') + packages = packages.with_name(params[:package_name]) end @@ -93,6 +95,7 @@ module API get ':id/-/packages/composer/*package_name', requirements: COMPOSER_ENDPOINT_REQUIREMENTS, file_path: true do not_found! if packages.empty? + not_found! if params[:sha].blank? presenter.package_versions end @@ -132,7 +135,7 @@ module API track_package_event('push_package', :composer) ::Packages::Composer::CreatePackageService - .new(authorized_user_project, current_user, declared_params) + .new(authorized_user_project, current_user, declared_params.merge(build: current_authenticated_job)) .execute created! diff --git a/lib/api/conan_instance_packages.rb b/lib/api/conan_instance_packages.rb index 08265201328..8c13b580092 100644 --- a/lib/api/conan_instance_packages.rb +++ b/lib/api/conan_instance_packages.rb @@ -4,7 +4,7 @@ module API class ConanInstancePackages < ::API::Base namespace 'packages/conan/v1' do - include ConanPackageEndpoints + include ::API::Concerns::Packages::ConanEndpoints end end end diff --git a/lib/api/conan_package_endpoints.rb b/lib/api/conan_package_endpoints.rb deleted file mode 100644 index 188a42f26f8..00000000000 --- a/lib/api/conan_package_endpoints.rb +++ /dev/null @@ -1,351 +0,0 @@ -# frozen_string_literal: true - -# Conan 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 Conan package manager client when users run commands -# like `conan install` or `conan upload`. The usage of the GitLab Conan repository is documented here: -# https://docs.gitlab.com/ee/user/packages/conan_repository/#installing-a-package -# -# Technical debt: https://gitlab.com/gitlab-org/gitlab/issues/35798 -module API - module ConanPackageEndpoints - extend ActiveSupport::Concern - - PACKAGE_REQUIREMENTS = { - package_name: API::NO_SLASH_URL_PART_REGEX, - package_version: API::NO_SLASH_URL_PART_REGEX, - package_username: API::NO_SLASH_URL_PART_REGEX, - package_channel: API::NO_SLASH_URL_PART_REGEX - }.freeze - - FILE_NAME_REQUIREMENTS = { - file_name: API::NO_SLASH_URL_PART_REGEX - }.freeze - - PACKAGE_COMPONENT_REGEX = Gitlab::Regex.conan_recipe_component_regex - CONAN_REVISION_REGEX = Gitlab::Regex.conan_revision_regex - - CONAN_FILES = (Gitlab::Regex::Packages::CONAN_RECIPE_FILES + Gitlab::Regex::Packages::CONAN_PACKAGE_FILES).freeze - - included do - feature_category :package_registry - - helpers ::API::Helpers::PackagesManagerClientsHelpers - helpers ::API::Helpers::Packages::Conan::ApiHelpers - helpers ::API::Helpers::RelatedResourcesHelpers - - before do - require_packages_enabled! - - # Personal access token will be extracted from Bearer or Basic authorization - # in the overridden find_personal_access_token or find_user_from_job_token helpers - authenticate! - end - - desc 'Ping the Conan API' do - detail 'This feature was introduced in GitLab 12.2' - end - - route_setting :authentication, job_token_allowed: true, basic_auth_personal_access_token: true - - get 'ping' do - header 'X-Conan-Server-Capabilities', [].join(',') - end - - desc 'Search for packages' do - detail 'This feature was introduced in GitLab 12.4' - end - - params do - requires :q, type: String, desc: 'Search query' - end - - route_setting :authentication, job_token_allowed: true, basic_auth_personal_access_token: true - - get 'conans/search' do - service = ::Packages::Conan::SearchService.new(current_user, query: params[:q]).execute - service.payload - end - - namespace 'users' do - format :txt - - desc 'Authenticate user against conan CLI' do - detail 'This feature was introduced in GitLab 12.2' - end - - route_setting :authentication, job_token_allowed: true, basic_auth_personal_access_token: true - - get 'authenticate' do - unauthorized! unless token - - token.to_jwt - end - - desc 'Check for valid user credentials per conan CLI' do - detail 'This feature was introduced in GitLab 12.4' - end - - route_setting :authentication, job_token_allowed: true, basic_auth_personal_access_token: true - - get 'check_credentials' do - authenticate! - :ok - end - end - - params do - requires :package_name, type: String, regexp: PACKAGE_COMPONENT_REGEX, desc: 'Package name' - requires :package_version, type: String, regexp: PACKAGE_COMPONENT_REGEX, desc: 'Package version' - requires :package_username, type: String, regexp: PACKAGE_COMPONENT_REGEX, desc: 'Package username' - requires :package_channel, type: String, regexp: PACKAGE_COMPONENT_REGEX, desc: 'Package channel' - end - namespace 'conans/:package_name/:package_version/:package_username/:package_channel', requirements: PACKAGE_REQUIREMENTS do - # Get the snapshot - # - # the snapshot is a hash of { filename: md5 hash } - # md5 hash is the has of that file. This hash is used to diff the files existing on the client - # to determine which client files need to be uploaded if no recipe exists the snapshot is empty - desc 'Package Snapshot' do - detail 'This feature was introduced in GitLab 12.5' - end - - params do - requires :conan_package_reference, type: String, desc: 'Conan package ID' - end - - route_setting :authentication, job_token_allowed: true, basic_auth_personal_access_token: true - - get 'packages/:conan_package_reference' do - authorize!(:read_package, project) - - presenter = ::Packages::Conan::PackagePresenter.new( - package, - current_user, - project, - conan_package_reference: params[:conan_package_reference] - ) - - present presenter, with: ::API::Entities::ConanPackage::ConanPackageSnapshot - end - - desc 'Recipe Snapshot' do - detail 'This feature was introduced in GitLab 12.5' - end - - route_setting :authentication, job_token_allowed: true, basic_auth_personal_access_token: true - - get do - authorize!(:read_package, project) - - presenter = ::Packages::Conan::PackagePresenter.new(package, current_user, project) - - present presenter, with: ::API::Entities::ConanPackage::ConanRecipeSnapshot - end - - # Get the manifest - # returns the download urls for the existing recipe in the registry - # - # the manifest is a hash of { filename: url } - # where the url is the download url for the file - desc 'Package Digest' do - detail 'This feature was introduced in GitLab 12.5' - end - params do - requires :conan_package_reference, type: String, desc: 'Conan package ID' - end - - route_setting :authentication, job_token_allowed: true, basic_auth_personal_access_token: true - - get 'packages/:conan_package_reference/digest' do - present_package_download_urls - end - - desc 'Recipe Digest' do - detail 'This feature was introduced in GitLab 12.5' - end - - route_setting :authentication, job_token_allowed: true, basic_auth_personal_access_token: true - - get 'digest' do - present_recipe_download_urls - end - - # Get the download urls - # - # returns the download urls for the existing recipe or package in the registry - # - # the manifest is a hash of { filename: url } - # where the url is the download url for the file - desc 'Package Download Urls' do - detail 'This feature was introduced in GitLab 12.5' - end - - params do - requires :conan_package_reference, type: String, desc: 'Conan package ID' - end - - route_setting :authentication, job_token_allowed: true, basic_auth_personal_access_token: true - - get 'packages/:conan_package_reference/download_urls' do - present_package_download_urls - end - - desc 'Recipe Download Urls' do - detail 'This feature was introduced in GitLab 12.5' - end - - route_setting :authentication, job_token_allowed: true, basic_auth_personal_access_token: true - - get 'download_urls' do - present_recipe_download_urls - end - - # Get the upload urls - # - # request body contains { filename: filesize } where the filename is the - # name of the file the conan client is requesting to upload - # - # returns { filename: url } - # where the url is the upload url for the file that the conan client will use - desc 'Package Upload Urls' do - detail 'This feature was introduced in GitLab 12.4' - end - - params do - requires :conan_package_reference, type: String, desc: 'Conan package ID' - end - - route_setting :authentication, job_token_allowed: true, basic_auth_personal_access_token: true - - post 'packages/:conan_package_reference/upload_urls' do - authorize!(:read_package, project) - - status 200 - present package_upload_urls, with: ::API::Entities::ConanPackage::ConanUploadUrls - end - - desc 'Recipe Upload Urls' do - detail 'This feature was introduced in GitLab 12.4' - end - - route_setting :authentication, job_token_allowed: true, basic_auth_personal_access_token: true - - post 'upload_urls' do - authorize!(:read_package, project) - - status 200 - present recipe_upload_urls, with: ::API::Entities::ConanPackage::ConanUploadUrls - end - - desc 'Delete Package' do - detail 'This feature was introduced in GitLab 12.5' - end - - route_setting :authentication, job_token_allowed: true, basic_auth_personal_access_token: true - - delete do - authorize!(:destroy_package, project) - - track_package_event('delete_package', :conan, category: 'API::ConanPackages') - - package.destroy - end - end - - params do - requires :package_name, type: String, regexp: PACKAGE_COMPONENT_REGEX, desc: 'Package name' - requires :package_version, type: String, regexp: PACKAGE_COMPONENT_REGEX, desc: 'Package version' - requires :package_username, type: String, regexp: PACKAGE_COMPONENT_REGEX, desc: 'Package username' - requires :package_channel, type: String, regexp: PACKAGE_COMPONENT_REGEX, desc: 'Package channel' - requires :recipe_revision, type: String, regexp: CONAN_REVISION_REGEX, desc: 'Conan Recipe Revision' - end - namespace 'files/:package_name/:package_version/:package_username/:package_channel/:recipe_revision', requirements: PACKAGE_REQUIREMENTS do - before do - authenticate_non_get! - end - - params do - requires :file_name, type: String, desc: 'Package file name', values: CONAN_FILES - end - namespace 'export/:file_name', requirements: FILE_NAME_REQUIREMENTS do - desc 'Download recipe files' do - detail 'This feature was introduced in GitLab 12.6' - end - - route_setting :authentication, job_token_allowed: true, basic_auth_personal_access_token: true - - get do - download_package_file(:recipe_file) - end - - desc 'Upload recipe package files' do - detail 'This feature was introduced in GitLab 12.6' - end - - params do - requires :file, type: ::API::Validations::Types::WorkhorseFile, desc: 'The package file to be published (generated by Multipart middleware)' - end - - route_setting :authentication, job_token_allowed: true, basic_auth_personal_access_token: true - - put do - upload_package_file(:recipe_file) - end - - desc 'Workhorse authorize the conan recipe file' do - detail 'This feature was introduced in GitLab 12.6' - end - - route_setting :authentication, job_token_allowed: true, basic_auth_personal_access_token: true - - put 'authorize' do - authorize_workhorse!(subject: project, maximum_size: project.actual_limits.conan_max_file_size) - end - end - - params do - requires :conan_package_reference, type: String, desc: 'Conan Package ID' - requires :package_revision, type: String, desc: 'Conan Package Revision' - requires :file_name, type: String, desc: 'Package file name', values: CONAN_FILES - end - namespace 'package/:conan_package_reference/:package_revision/:file_name', requirements: FILE_NAME_REQUIREMENTS do - desc 'Download package files' do - detail 'This feature was introduced in GitLab 12.5' - end - - route_setting :authentication, job_token_allowed: true, basic_auth_personal_access_token: true - - get do - download_package_file(:package_file) - end - - desc 'Workhorse authorize the conan package file' do - detail 'This feature was introduced in GitLab 12.6' - end - - route_setting :authentication, job_token_allowed: true, basic_auth_personal_access_token: true - - put 'authorize' do - authorize_workhorse!(subject: project, maximum_size: project.actual_limits.conan_max_file_size) - end - - desc 'Upload package files' do - detail 'This feature was introduced in GitLab 12.6' - end - - params do - requires :file, type: ::API::Validations::Types::WorkhorseFile, desc: 'The package file to be published (generated by Multipart middleware)' - end - - route_setting :authentication, job_token_allowed: true, basic_auth_personal_access_token: true - - put do - upload_package_file(:package_file) - end - end - end - end - end -end diff --git a/lib/api/conan_project_packages.rb b/lib/api/conan_project_packages.rb index db8cd187811..636b5dca5ed 100644 --- a/lib/api/conan_project_packages.rb +++ b/lib/api/conan_project_packages.rb @@ -9,7 +9,7 @@ module API resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do namespace ':id/packages/conan/v1' do - include ConanPackageEndpoints + include ::API::Concerns::Packages::ConanEndpoints end end end diff --git a/lib/api/concerns/packages/conan_endpoints.rb b/lib/api/concerns/packages/conan_endpoints.rb new file mode 100644 index 00000000000..6c8b3a1ba4a --- /dev/null +++ b/lib/api/concerns/packages/conan_endpoints.rb @@ -0,0 +1,355 @@ +# frozen_string_literal: true + +# Conan 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 Conan package manager client when users run commands +# like `conan install` or `conan upload`. The usage of the GitLab Conan repository is documented here: +# https://docs.gitlab.com/ee/user/packages/conan_repository/#installing-a-package +# +# Technical debt: https://gitlab.com/gitlab-org/gitlab/issues/35798 +module API + module Concerns + module Packages + module ConanEndpoints + extend ActiveSupport::Concern + + PACKAGE_REQUIREMENTS = { + package_name: API::NO_SLASH_URL_PART_REGEX, + package_version: API::NO_SLASH_URL_PART_REGEX, + package_username: API::NO_SLASH_URL_PART_REGEX, + package_channel: API::NO_SLASH_URL_PART_REGEX + }.freeze + + FILE_NAME_REQUIREMENTS = { + file_name: API::NO_SLASH_URL_PART_REGEX + }.freeze + + PACKAGE_COMPONENT_REGEX = Gitlab::Regex.conan_recipe_component_regex + CONAN_REVISION_REGEX = Gitlab::Regex.conan_revision_regex + + CONAN_FILES = (Gitlab::Regex::Packages::CONAN_RECIPE_FILES + Gitlab::Regex::Packages::CONAN_PACKAGE_FILES).freeze + + included do + feature_category :package_registry + + helpers ::API::Helpers::PackagesManagerClientsHelpers + helpers ::API::Helpers::Packages::Conan::ApiHelpers + helpers ::API::Helpers::RelatedResourcesHelpers + + before do + require_packages_enabled! + + # Personal access token will be extracted from Bearer or Basic authorization + # in the overridden find_personal_access_token or find_user_from_job_token helpers + authenticate! + end + + desc 'Ping the Conan API' do + detail 'This feature was introduced in GitLab 12.2' + end + + route_setting :authentication, job_token_allowed: true, basic_auth_personal_access_token: true + + get 'ping' do + header 'X-Conan-Server-Capabilities', [].join(',') + end + + desc 'Search for packages' do + detail 'This feature was introduced in GitLab 12.4' + end + + params do + requires :q, type: String, desc: 'Search query' + end + + route_setting :authentication, job_token_allowed: true, basic_auth_personal_access_token: true + + get 'conans/search' do + service = ::Packages::Conan::SearchService.new(current_user, query: params[:q]).execute + service.payload + end + + namespace 'users' do + format :txt + + desc 'Authenticate user against conan CLI' do + detail 'This feature was introduced in GitLab 12.2' + end + + route_setting :authentication, job_token_allowed: true, basic_auth_personal_access_token: true + + get 'authenticate' do + unauthorized! unless token + + token.to_jwt + end + + desc 'Check for valid user credentials per conan CLI' do + detail 'This feature was introduced in GitLab 12.4' + end + + route_setting :authentication, job_token_allowed: true, basic_auth_personal_access_token: true + + get 'check_credentials' do + authenticate! + :ok + end + end + + params do + requires :package_name, type: String, regexp: PACKAGE_COMPONENT_REGEX, desc: 'Package name' + requires :package_version, type: String, regexp: PACKAGE_COMPONENT_REGEX, desc: 'Package version' + requires :package_username, type: String, regexp: PACKAGE_COMPONENT_REGEX, desc: 'Package username' + requires :package_channel, type: String, regexp: PACKAGE_COMPONENT_REGEX, desc: 'Package channel' + end + namespace 'conans/:package_name/:package_version/:package_username/:package_channel', requirements: PACKAGE_REQUIREMENTS do + # Get the snapshot + # + # the snapshot is a hash of { filename: md5 hash } + # md5 hash is the has of that file. This hash is used to diff the files existing on the client + # to determine which client files need to be uploaded if no recipe exists the snapshot is empty + desc 'Package Snapshot' do + detail 'This feature was introduced in GitLab 12.5' + end + + params do + requires :conan_package_reference, type: String, desc: 'Conan package ID' + end + + route_setting :authentication, job_token_allowed: true, basic_auth_personal_access_token: true + + get 'packages/:conan_package_reference' do + authorize!(:read_package, project) + + presenter = ::Packages::Conan::PackagePresenter.new( + package, + current_user, + project, + conan_package_reference: params[:conan_package_reference] + ) + + present presenter, with: ::API::Entities::ConanPackage::ConanPackageSnapshot + end + + desc 'Recipe Snapshot' do + detail 'This feature was introduced in GitLab 12.5' + end + + route_setting :authentication, job_token_allowed: true, basic_auth_personal_access_token: true + + get do + authorize!(:read_package, project) + + presenter = ::Packages::Conan::PackagePresenter.new(package, current_user, project) + + present presenter, with: ::API::Entities::ConanPackage::ConanRecipeSnapshot + end + + # Get the manifest + # returns the download urls for the existing recipe in the registry + # + # the manifest is a hash of { filename: url } + # where the url is the download url for the file + desc 'Package Digest' do + detail 'This feature was introduced in GitLab 12.5' + end + params do + requires :conan_package_reference, type: String, desc: 'Conan package ID' + end + + route_setting :authentication, job_token_allowed: true, basic_auth_personal_access_token: true + + get 'packages/:conan_package_reference/digest' do + present_package_download_urls + end + + desc 'Recipe Digest' do + detail 'This feature was introduced in GitLab 12.5' + end + + route_setting :authentication, job_token_allowed: true, basic_auth_personal_access_token: true + + get 'digest' do + present_recipe_download_urls + end + + # Get the download urls + # + # returns the download urls for the existing recipe or package in the registry + # + # the manifest is a hash of { filename: url } + # where the url is the download url for the file + desc 'Package Download Urls' do + detail 'This feature was introduced in GitLab 12.5' + end + + params do + requires :conan_package_reference, type: String, desc: 'Conan package ID' + end + + route_setting :authentication, job_token_allowed: true, basic_auth_personal_access_token: true + + get 'packages/:conan_package_reference/download_urls' do + present_package_download_urls + end + + desc 'Recipe Download Urls' do + detail 'This feature was introduced in GitLab 12.5' + end + + route_setting :authentication, job_token_allowed: true, basic_auth_personal_access_token: true + + get 'download_urls' do + present_recipe_download_urls + end + + # Get the upload urls + # + # request body contains { filename: filesize } where the filename is the + # name of the file the conan client is requesting to upload + # + # returns { filename: url } + # where the url is the upload url for the file that the conan client will use + desc 'Package Upload Urls' do + detail 'This feature was introduced in GitLab 12.4' + end + + params do + requires :conan_package_reference, type: String, desc: 'Conan package ID' + end + + route_setting :authentication, job_token_allowed: true, basic_auth_personal_access_token: true + + post 'packages/:conan_package_reference/upload_urls' do + authorize!(:read_package, project) + + status 200 + present package_upload_urls, with: ::API::Entities::ConanPackage::ConanUploadUrls + end + + desc 'Recipe Upload Urls' do + detail 'This feature was introduced in GitLab 12.4' + end + + route_setting :authentication, job_token_allowed: true, basic_auth_personal_access_token: true + + post 'upload_urls' do + authorize!(:read_package, project) + + status 200 + present recipe_upload_urls, with: ::API::Entities::ConanPackage::ConanUploadUrls + end + + desc 'Delete Package' do + detail 'This feature was introduced in GitLab 12.5' + end + + route_setting :authentication, job_token_allowed: true, basic_auth_personal_access_token: true + + delete do + authorize!(:destroy_package, project) + + track_package_event('delete_package', :conan, category: 'API::ConanPackages') + + package.destroy + end + end + + params do + requires :package_name, type: String, regexp: PACKAGE_COMPONENT_REGEX, desc: 'Package name' + requires :package_version, type: String, regexp: PACKAGE_COMPONENT_REGEX, desc: 'Package version' + requires :package_username, type: String, regexp: PACKAGE_COMPONENT_REGEX, desc: 'Package username' + requires :package_channel, type: String, regexp: PACKAGE_COMPONENT_REGEX, desc: 'Package channel' + requires :recipe_revision, type: String, regexp: CONAN_REVISION_REGEX, desc: 'Conan Recipe Revision' + end + namespace 'files/:package_name/:package_version/:package_username/:package_channel/:recipe_revision', requirements: PACKAGE_REQUIREMENTS do + before do + authenticate_non_get! + end + + params do + requires :file_name, type: String, desc: 'Package file name', values: CONAN_FILES + end + namespace 'export/:file_name', requirements: FILE_NAME_REQUIREMENTS do + desc 'Download recipe files' do + detail 'This feature was introduced in GitLab 12.6' + end + + route_setting :authentication, job_token_allowed: true, basic_auth_personal_access_token: true + + get do + download_package_file(:recipe_file) + end + + desc 'Upload recipe package files' do + detail 'This feature was introduced in GitLab 12.6' + end + + params do + requires :file, type: ::API::Validations::Types::WorkhorseFile, desc: 'The package file to be published (generated by Multipart middleware)' + end + + route_setting :authentication, job_token_allowed: true, basic_auth_personal_access_token: true + + put do + upload_package_file(:recipe_file) + end + + desc 'Workhorse authorize the conan recipe file' do + detail 'This feature was introduced in GitLab 12.6' + end + + route_setting :authentication, job_token_allowed: true, basic_auth_personal_access_token: true + + put 'authorize' do + authorize_workhorse!(subject: project, maximum_size: project.actual_limits.conan_max_file_size) + end + end + + params do + requires :conan_package_reference, type: String, desc: 'Conan Package ID' + requires :package_revision, type: String, desc: 'Conan Package Revision' + requires :file_name, type: String, desc: 'Package file name', values: CONAN_FILES + end + namespace 'package/:conan_package_reference/:package_revision/:file_name', requirements: FILE_NAME_REQUIREMENTS do + desc 'Download package files' do + detail 'This feature was introduced in GitLab 12.5' + end + + route_setting :authentication, job_token_allowed: true, basic_auth_personal_access_token: true + + get do + download_package_file(:package_file) + end + + desc 'Workhorse authorize the conan package file' do + detail 'This feature was introduced in GitLab 12.6' + end + + route_setting :authentication, job_token_allowed: true, basic_auth_personal_access_token: true + + put 'authorize' do + authorize_workhorse!(subject: project, maximum_size: project.actual_limits.conan_max_file_size) + end + + desc 'Upload package files' do + detail 'This feature was introduced in GitLab 12.6' + end + + params do + requires :file, type: ::API::Validations::Types::WorkhorseFile, desc: 'The package file to be published (generated by Multipart middleware)' + end + + route_setting :authentication, job_token_allowed: true, basic_auth_personal_access_token: true + + put do + upload_package_file(:package_file) + end + end + end + end + end + end + end +end diff --git a/lib/api/concerns/packages/npm_endpoints.rb b/lib/api/concerns/packages/npm_endpoints.rb index a91db93b182..833288c6013 100644 --- a/lib/api/concerns/packages/npm_endpoints.rb +++ b/lib/api/concerns/packages/npm_endpoints.rb @@ -37,7 +37,7 @@ module API get 'dist-tags', format: false, requirements: ::API::Helpers::Packages::Npm::NPM_ENDPOINT_REQUIREMENTS do package_name = params[:package_name] - bad_request!('Package Name') if package_name.blank? + bad_request_missing_attribute!('Package Name') if package_name.blank? authorize_read_package!(project) @@ -62,9 +62,9 @@ module API version = env['api.request.body'] tag = params[:tag] - bad_request!('Package Name') if package_name.blank? - bad_request!('Version') if version.blank? - bad_request!('Tag') if tag.blank? + bad_request_missing_attribute!('Package Name') if package_name.blank? + bad_request_missing_attribute!('Version') if version.blank? + bad_request_missing_attribute!('Tag') if tag.blank? authorize_create_package!(project) @@ -85,8 +85,8 @@ module API package_name = params[:package_name] tag = params[:tag] - bad_request!('Package Name') if package_name.blank? - bad_request!('Tag') if tag.blank? + bad_request_missing_attribute!('Package Name') if package_name.blank? + bad_request_missing_attribute!('Tag') if tag.blank? authorize_destroy_package!(project) diff --git a/lib/api/concerns/packages/nuget_endpoints.rb b/lib/api/concerns/packages/nuget_endpoints.rb new file mode 100644 index 00000000000..5177c4d23c0 --- /dev/null +++ b/lib/api/concerns/packages/nuget_endpoints.rb @@ -0,0 +1,135 @@ +# 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{\A0|[1-9]\d*\z}.freeze + + included do + helpers do + def find_packages + packages = package_finder.execute + + not_found!('Packages') unless packages.exists? + + packages + end + + def find_package + package = package_finder(package_version: params[:package_version]).execute + .first + + not_found!('Package') unless package + + package + end + + def package_finder(finder_params = {}) + ::Packages::Nuget::PackageFinder.new( + authorized_user_project, + **finder_params.merge(package_name: params[:package_name]) + ) + 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' + end + + route_setting :authentication, deploy_token_allowed: true, job_token_allowed: :basic_auth, basic_auth_personal_access_token: true + + get 'index', format: :json do + authorize_read_package!(authorized_user_project) + track_package_event('cli_metadata', :nuget, category: 'API::NugetPackages') + + present ::Packages::Nuget::ServiceIndexPresenter.new(authorized_user_project), + 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 + end + namespace '/metadata/*package_name' do + before do + authorize_read_package!(authorized_user_project) + end + + desc 'The NuGet Metadata Service - Package name level' do + detail 'This feature was introduced in GitLab 12.8' + end + + route_setting :authentication, deploy_token_allowed: true, job_token_allowed: :basic_auth, basic_auth_personal_access_token: true + + get 'index', format: :json do + present ::Packages::Nuget::PackagesMetadataPresenter.new(find_packages), + 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' + end + params do + requires :package_version, type: String, desc: 'The NuGet package version', regexp: API::NO_SLASH_URL_PART_REGEX + end + + route_setting :authentication, deploy_token_allowed: true, job_token_allowed: :basic_auth, basic_auth_personal_access_token: true + + get '*package_version', format: :json do + present ::Packages::Nuget::PackageMetadataPresenter.new(find_package), + with: ::API::Entities::Nuget::PackageMetadata + end + end + + # https://docs.microsoft.com/en-us/nuget/api/search-query-service-resource + params do + requires :q, type: String, desc: 'The search term' + optional :skip, type: Integer, desc: 'The number of results to skip', default: 0, regexp: NON_NEGATIVE_INTEGER_REGEX + optional :take, type: Integer, desc: 'The number of results to return', default: Kaminari.config.default_per_page, regexp: POSITIVE_INTEGER_REGEX + optional :prerelease, type: ::Grape::API::Boolean, desc: 'Include prerelease versions', default: true + end + namespace '/query' do + before do + authorize_read_package!(authorized_user_project) + end + + desc 'The NuGet Search Service' do + detail 'This feature was introduced in GitLab 12.8' + end + + route_setting :authentication, deploy_token_allowed: true, job_token_allowed: :basic_auth, basic_auth_personal_access_token: true + + get format: :json do + search_options = { + include_prerelease_versions: params[:prerelease], + per_page: params[:take], + padding: params[:skip] + } + search = ::Packages::Nuget::SearchService + .new(authorized_user_project, params[:q], search_options) + .execute + + track_package_event('search_package', :nuget, category: 'API::NugetPackages') + + present ::Packages::Nuget::SearchResultsPresenter.new(search), + with: ::API::Entities::Nuget::SearchResults + end + end + end + end + end + end +end diff --git a/lib/api/discussions.rb b/lib/api/discussions.rb index 4c4ec200060..580d546b360 100644 --- a/lib/api/discussions.rb +++ b/lib/api/discussions.rb @@ -104,6 +104,7 @@ module API position: params[:position], id_key => noteable.id } + opts[:commit_id] = params[:commit_id] if noteable.is_a?(MergeRequest) && type == 'DiffNote' note = create_note(noteable, opts) diff --git a/lib/api/entities/cluster.rb b/lib/api/entities/cluster.rb index 67459092a33..b7e76e763f7 100644 --- a/lib/api/entities/cluster.rb +++ b/lib/api/entities/cluster.rb @@ -3,7 +3,7 @@ module API module Entities class Cluster < Grape::Entity - expose :id, :name, :created_at, :domain + expose :id, :name, :created_at, :domain, :enabled, :managed expose :provider_type, :platform_type, :environment_scope, :cluster_type, :namespace_per_environment expose :user, using: Entities::UserBasic expose :platform_kubernetes, using: Entities::Platform::Kubernetes diff --git a/lib/api/entities/feature.rb b/lib/api/entities/feature.rb index 618a7be9c7b..d1151849cd7 100644 --- a/lib/api/entities/feature.rb +++ b/lib/api/entities/feature.rb @@ -17,6 +17,16 @@ module API { key: gate.key, value: value } end.compact end + + class Definition < Grape::Entity + ::Feature::Definition::PARAMS.each do |param| + expose param + end + end + + expose :definition, using: Definition do |feature| + ::Feature::Definition.definitions[feature.name.to_sym] + end end end end diff --git a/lib/api/entities/feature_flag.rb b/lib/api/entities/feature_flag.rb index 82fdb20af00..f383eabd5dc 100644 --- a/lib/api/entities/feature_flag.rb +++ b/lib/api/entities/feature_flag.rb @@ -6,11 +6,11 @@ module API expose :name expose :description expose :active - expose :version, if: :feature_flags_new_version_enabled + expose :version expose :created_at expose :updated_at expose :scopes, using: FeatureFlag::LegacyScope - expose :strategies, using: FeatureFlag::Strategy, if: :feature_flags_new_version_enabled + expose :strategies, using: FeatureFlag::Strategy end end end diff --git a/lib/api/entities/issue.rb b/lib/api/entities/issue.rb index 5f2609cf68b..82102854394 100644 --- a/lib/api/entities/issue.rb +++ b/lib/api/entities/issue.rb @@ -43,6 +43,7 @@ module API end expose :moved_to_id + expose :service_desk_reply_to end end end diff --git a/lib/api/entities/merge_request_basic.rb b/lib/api/entities/merge_request_basic.rb index 69523e3637b..7f1b5b87725 100644 --- a/lib/api/entities/merge_request_basic.rb +++ b/lib/api/entities/merge_request_basic.rb @@ -27,6 +27,7 @@ module API expose(:downvotes) { |merge_request, options| issuable_metadata.downvotes } expose :author, :assignees, :assignee, using: Entities::UserBasic + expose :reviewers, if: -> (merge_request, _) { merge_request.allows_reviewers? }, using: Entities::UserBasic expose :source_project_id, :target_project_id expose :labels do |merge_request, options| if options[:with_labels_details] diff --git a/lib/api/entities/note.rb b/lib/api/entities/note.rb index f22ab73afd0..9a60c04220d 100644 --- a/lib/api/entities/note.rb +++ b/lib/api/entities/note.rb @@ -14,6 +14,7 @@ module API expose :created_at, :updated_at expose :system?, as: :system expose :noteable_id, :noteable_type + expose :commit_id, if: ->(note, options) { note.noteable_type == "MergeRequest" && note.is_a?(DiffNote) } expose :position, if: ->(note, options) { note.is_a?(DiffNote) } do |note| note.position.to_h diff --git a/lib/api/entities/project.rb b/lib/api/entities/project.rb index 82a44c75382..317caefe0a1 100644 --- a/lib/api/entities/project.rb +++ b/lib/api/entities/project.rb @@ -67,6 +67,8 @@ module API expose(:builds_access_level) { |project, options| project.project_feature.string_access_level(:builds) } expose(:snippets_access_level) { |project, options| project.project_feature.string_access_level(:snippets) } expose(:pages_access_level) { |project, options| project.project_feature.string_access_level(:pages) } + expose(:operations_access_level) { |project, options| project.project_feature.string_access_level(:operations) } + expose(:analytics_access_level) { |project, options| project.project_feature.string_access_level(:analytics) } expose :emails_disabled expose :shared_runners_enabled diff --git a/lib/api/entities/project_import_status.rb b/lib/api/entities/project_import_status.rb index f92593da3fa..e79c1cdf1a2 100644 --- a/lib/api/entities/project_import_status.rb +++ b/lib/api/entities/project_import_status.rb @@ -12,9 +12,8 @@ module API project.import_state&.relation_hard_failures(limit: 100) || [] end - # TODO: Use `expose_nil` once we upgrade the grape-entity gem - expose :import_error, if: lambda { |project, _ops| project.import_state&.last_error } do |project| - project.import_state.last_error + expose :import_error do |project, _options| + project.import_state&.last_error end end end diff --git a/lib/api/entities/project_snippet.rb b/lib/api/entities/project_snippet.rb index 8ed87e51375..253fcfcf38f 100644 --- a/lib/api/entities/project_snippet.rb +++ b/lib/api/entities/project_snippet.rb @@ -1,4 +1,4 @@ -# frozen_String_literal: true +# frozen_string_literal: true module API module Entities diff --git a/lib/api/entities/project_statistics.rb b/lib/api/entities/project_statistics.rb index 32201e88eaf..70980e670b0 100644 --- a/lib/api/entities/project_statistics.rb +++ b/lib/api/entities/project_statistics.rb @@ -10,6 +10,7 @@ module API expose :lfs_objects_size expose :build_artifacts_size, as: :job_artifacts_size expose :snippets_size + expose :packages_size end end end diff --git a/lib/api/entities/related_issue.rb b/lib/api/entities/related_issue.rb index 491c606bd49..60793fed5e0 100644 --- a/lib/api/entities/related_issue.rb +++ b/lib/api/entities/related_issue.rb @@ -5,6 +5,8 @@ module API class RelatedIssue < ::API::Entities::Issue expose :issue_link_id expose :issue_link_type, as: :link_type + expose :issue_link_created_at, as: :link_created_at + expose :issue_link_updated_at, as: :link_updated_at end end end diff --git a/lib/api/feature_flags.rb b/lib/api/feature_flags.rb index 67168ba9be6..6fdc4535be3 100644 --- a/lib/api/feature_flags.rb +++ b/lib/api/feature_flags.rb @@ -62,8 +62,6 @@ module API attrs = declared_params(include_missing: false) - ensure_post_version_2_flags_enabled! if attrs[:version] == 'new_version_flag' - rename_key(attrs, :scopes, :scopes_attributes) rename_key(attrs, :strategies, :strategies_attributes) update_value(attrs, :strategies_attributes) do |strategies| @@ -143,7 +141,7 @@ module API end desc 'Update a feature flag' do - detail 'This feature will be introduced in GitLab 13.1 if feature_flags_new_version feature flag is removed' + detail 'This feature was introduced in GitLab 13.2' success ::API::Entities::FeatureFlag end params do @@ -163,7 +161,6 @@ module API end end put do - not_found! unless feature_flags_new_version_enabled? authorize_update_feature_flag! render_api_error!('PUT operations are not supported for legacy feature flags', :unprocessable_entity) if feature_flag.legacy_flag? @@ -228,32 +225,17 @@ module API def present_entity(result) present result, - with: ::API::Entities::FeatureFlag, - feature_flags_new_version_enabled: feature_flags_new_version_enabled? - end - - def ensure_post_version_2_flags_enabled! - unless feature_flags_new_version_enabled? - render_api_error!('Version 2 flags are not enabled for this project', :unprocessable_entity) - end + with: ::API::Entities::FeatureFlag end def feature_flag - @feature_flag ||= if feature_flags_new_version_enabled? - user_project.operations_feature_flags.find_by_name!(params[:feature_flag_name]) - else - user_project.operations_feature_flags.legacy_flag.find_by_name!(params[:feature_flag_name]) - end + @feature_flag ||= user_project.operations_feature_flags.find_by_name!(params[:feature_flag_name]) end def new_version_flag_present? user_project.operations_feature_flags.new_version_flag.find_by_name(params[:name]).present? end - def feature_flags_new_version_enabled? - Feature.enabled?(:feature_flags_new_version, user_project, default_enabled: true) - end - def rename_key(hash, old_key, new_key) hash[new_key] = hash.delete(old_key) if hash.key?(old_key) hash diff --git a/lib/api/feature_flags_user_lists.rb b/lib/api/feature_flags_user_lists.rb index 086bcbcdc89..8577da173b1 100644 --- a/lib/api/feature_flags_user_lists.rb +++ b/lib/api/feature_flags_user_lists.rb @@ -54,7 +54,7 @@ module API end params do - requires :iid, type: String, desc: 'The internal id of the user list' + requires :iid, type: String, desc: 'The internal ID of the user list' end resource 'feature_flags_user_lists/:iid' do desc 'Get a single feature flag user list belonging to a project' do diff --git a/lib/api/features.rb b/lib/api/features.rb index 2c2e3e3d0c9..57bd7c38ad2 100644 --- a/lib/api/features.rb +++ b/lib/api/features.rb @@ -46,6 +46,15 @@ module API present features, with: Entities::Feature, current_user: current_user end + desc 'Get a list of all feature definitions' do + success Entities::Feature::Definition + end + get :definitions do + definitions = ::Feature::Definition.definitions.values.map(&:to_h) + + present definitions, with: Entities::Feature::Definition, current_user: current_user + end + desc 'Set the gate value for the given feature' do success Entities::Feature end @@ -56,6 +65,7 @@ module API optional :user, type: String, desc: 'A GitLab username' optional :group, type: String, desc: "A GitLab group's path, such as 'gitlab-org'" optional :project, type: String, desc: 'A projects path, like gitlab-org/gitlab-ce' + optional :force, type: Boolean, desc: 'Skip feature flag validation checks, ie. YAML definition' mutually_exclusive :key, :feature_group mutually_exclusive :key, :user @@ -63,9 +73,8 @@ module API mutually_exclusive :key, :project end post ':name' do - validate_feature_flag_name!(params[:name]) + validate_feature_flag_name!(params[:name]) unless params[:force] - feature = Feature.get(params[:name]) # rubocop:disable Gitlab/AvoidFeatureGet targets = gate_targets(params) value = gate_value(params) key = gate_key(params) @@ -73,25 +82,26 @@ module API case value when true if gate_specified?(params) - targets.each { |target| feature.enable(target) } + targets.each { |target| Feature.enable(params[:name], target) } else - feature.enable + Feature.enable(params[:name]) end when false if gate_specified?(params) - targets.each { |target| feature.disable(target) } + targets.each { |target| Feature.disable(params[:name], target) } else - feature.disable + Feature.disable(params[:name]) end else if key == :percentage_of_actors - feature.enable_percentage_of_actors(value) + Feature.enable_percentage_of_actors(params[:name], value) else - feature.enable_percentage_of_time(value) + Feature.enable_percentage_of_time(params[:name], value) end end - present feature, with: Entities::Feature, current_user: current_user + present Feature.get(params[:name]), # rubocop:disable Gitlab/AvoidFeatureGet + with: Entities::Feature, current_user: current_user end desc 'Remove the gate value for the given feature' diff --git a/lib/api/go_proxy.rb b/lib/api/go_proxy.rb index 8fb4c561c40..2d978019f2a 100755 --- a/lib/api/go_proxy.rb +++ b/lib/api/go_proxy.rb @@ -48,7 +48,7 @@ module API not_found! unless Feature.enabled?(:go_proxy, user_project) module_name = case_decode params[:module_name] - bad_request!('Module Name') if module_name.blank? + bad_request_missing_attribute!('Module Name') if module_name.blank? mod = ::Packages::Go::ModuleFinder.new(user_project, module_name).execute diff --git a/lib/api/group_boards.rb b/lib/api/group_boards.rb index ac5a1a2ce94..2bfd98a5b69 100644 --- a/lib/api/group_boards.rb +++ b/lib/api/group_boards.rb @@ -83,8 +83,6 @@ module API use :list_creation_params end post '/lists' do - authorize_list_type_resource! - authorize!(:admin_list, user_group) create_list diff --git a/lib/api/group_clusters.rb b/lib/api/group_clusters.rb index a435b050042..81944a653c8 100644 --- a/lib/api/group_clusters.rb +++ b/lib/api/group_clusters.rb @@ -75,10 +75,12 @@ module API params do requires :cluster_id, type: Integer, desc: 'The cluster ID' optional :name, type: String, desc: 'Cluster name' + optional :enabled, type: Boolean, desc: 'Determines if cluster is active or not' optional :domain, type: String, desc: 'Cluster base domain' optional :environment_scope, type: String, desc: 'The associated environment to the cluster' optional :namespace_per_environment, default: true, type: Boolean, desc: 'Deploy each environment to a separate Kubernetes namespace' optional :management_project_id, type: Integer, desc: 'The ID of the management project' + optional :managed, type: Boolean, desc: 'Determines if GitLab will manage namespaces and service accounts for this cluster' optional :platform_kubernetes_attributes, type: Hash, desc: %q(Platform Kubernetes data) do optional :api_url, type: String, desc: 'URL to access the Kubernetes API' optional :token, type: String, desc: 'Token to authenticate against Kubernetes' diff --git a/lib/api/group_labels.rb b/lib/api/group_labels.rb index bf3ac8800b7..7fbf4445116 100644 --- a/lib/api/group_labels.rb +++ b/lib/api/group_labels.rb @@ -66,7 +66,7 @@ module API success Entities::GroupLabel end params do - optional :label_id, type: Integer, desc: 'The id of the label to be updated' + optional :label_id, type: Integer, desc: 'The ID of the label to be updated' optional :name, type: String, desc: 'The name of the label to be updated' use :group_label_update_params exactly_one_of :label_id, :name diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb index 147d8407142..6fe25471289 100644 --- a/lib/api/helpers.rb +++ b/lib/api/helpers.rb @@ -271,6 +271,10 @@ module API authorize! :read_build, user_project end + def authorize_read_build_trace!(build) + authorize! :read_build_trace, build + end + def authorize_destroy_artifacts! authorize! :destroy_artifacts, user_project end @@ -318,7 +322,7 @@ module API # keys (required) - A hash consisting of keys that must be present def required_attributes!(keys) keys.each do |key| - bad_request!(key) unless params[key].present? + bad_request_missing_attribute!(key) unless params[key].present? end end @@ -364,12 +368,16 @@ module API render_api_error!(message.join(' '), 403) end - def bad_request!(attribute) - message = ["400 (Bad request)"] - message << "\"" + attribute.to_s + "\" not given" if attribute + def bad_request!(reason = nil) + message = ['400 Bad request'] + message << "- #{reason}" if reason render_api_error!(message.join(' '), 400) end + def bad_request_missing_attribute!(attribute) + bad_request!("\"#{attribute}\" not given") + end + def not_found!(resource = nil) message = ["404"] message << resource if resource @@ -536,13 +544,23 @@ module API ) end + def increment_counter(event_name) + feature_name = "usage_data_#{event_name}" + return unless Feature.enabled?(feature_name) + + Gitlab::UsageDataCounters.count(event_name) + rescue => error + Gitlab::AppLogger.warn("Redis tracking event failed for event: #{event_name}, message: #{error.message}") + end + # @param event_name [String] the event name # @param values [Array|String] the values counted def increment_unique_values(event_name, values) return unless values.present? - feature_name = "usage_data_#{event_name}" - return unless Feature.enabled?(feature_name) + feature_flag = "usage_data_#{event_name}" + + return unless Feature.enabled?(feature_flag, default_enabled: true) Gitlab::UsageDataCounters::HLLRedisCounter.track_event(values, event_name) rescue => error diff --git a/lib/api/helpers/internal_helpers.rb b/lib/api/helpers/internal_helpers.rb index 69b53ea6c2f..12b0a053e79 100644 --- a/lib/api/helpers/internal_helpers.rb +++ b/lib/api/helpers/internal_helpers.rb @@ -31,8 +31,7 @@ module API def access_checker_for(actor, protocol) access_checker_klass.new(actor.key_or_user, container, protocol, authentication_abilities: ssh_authentication_abilities, - namespace_path: namespace_path, - repository_path: project_path, + repository_path: repository_path, redirected_path: redirected_path) end @@ -71,18 +70,22 @@ module API false end - def project_path - project&.path || project_path_match[:project_path] - end - - def namespace_path - project&.namespace&.full_path || project_path_match[:namespace_path] - end - private - def project_path_match - @project_path_match ||= params[:project].match(Gitlab::PathRegex.full_project_git_path_regex) || {} + def repository_path + if container + "#{container.full_path}.git" + elsif params[:project] + # When the project doesn't exist, we still need to pass on the path + # to support auto-creation in `GitAccessProject`. + # + # For consistency with the Git HTTP controllers, we normalize the path + # to remove a leading slash and ensure a trailing `.git`. + # + # NOTE: For GitLab Shell, `params[:project]` is the full repository path + # from the SSH command, with an optional trailing `.git`. + "#{params[:project].delete_prefix('/').delete_suffix('.git')}.git" + end end # rubocop:disable Gitlab/ModuleWithInstanceVariables @@ -96,7 +99,7 @@ module API end # rubocop:enable Gitlab/ModuleWithInstanceVariables - # Project id to pass between components that don't share/don't have + # Repository id to pass between components that don't share/don't have # access to the same filesystem mounts def gl_repository repo_type.identifier_for_container(container) @@ -106,8 +109,9 @@ module API repository.full_path end - # Return the repository depending on whether we want the wiki or the - # regular repository + # Return the repository for the detected type and container + # + # @returns [Repository] def repository @repository ||= repo_type.repository_for(container) end diff --git a/lib/api/helpers/members_helpers.rb b/lib/api/helpers/members_helpers.rb index 431001c227d..8aed578905e 100644 --- a/lib/api/helpers/members_helpers.rb +++ b/lib/api/helpers/members_helpers.rb @@ -45,7 +45,7 @@ module API end def find_all_members_for_project(project) - MembersFinder.new(project, current_user).execute(include_relations: [:inherited, :direct, :invited_groups_members]) + MembersFinder.new(project, current_user).execute(include_relations: [:inherited, :direct, :invited_groups]) end def find_all_members_for_group(group) diff --git a/lib/api/helpers/packages/basic_auth_helpers.rb b/lib/api/helpers/packages/basic_auth_helpers.rb index e35a8712131..0784efc11d6 100644 --- a/lib/api/helpers/packages/basic_auth_helpers.rb +++ b/lib/api/helpers/packages/basic_auth_helpers.rb @@ -7,8 +7,8 @@ module API extend ::Gitlab::Utils::Override module Constants - AUTHENTICATE_REALM_HEADER = 'Www-Authenticate: Basic realm' - AUTHENTICATE_REALM_NAME = 'GitLab Packages Registry' + AUTHENTICATE_REALM_HEADER = 'WWW-Authenticate' + AUTHENTICATE_REALM_NAME = 'Basic realm="GitLab Packages Registry"' end include Constants diff --git a/lib/api/helpers/packages/conan/api_helpers.rb b/lib/api/helpers/packages/conan/api_helpers.rb index 934e18bdd0a..39ecfc171a9 100644 --- a/lib/api/helpers/packages/conan/api_helpers.rb +++ b/lib/api/helpers/packages/conan/api_helpers.rb @@ -164,7 +164,11 @@ module API end def find_or_create_package - package || ::Packages::Conan::CreatePackageService.new(project, current_user, params).execute + package || ::Packages::Conan::CreatePackageService.new( + project, + current_user, + params.merge(build: current_authenticated_job) + ).execute end def track_push_package_event @@ -184,7 +188,11 @@ module API def create_package_file_with_type(file_type, current_package) unless params[:file].size == 0 # rubocop: disable Style/ZeroLengthPredicate # conan sends two upload requests, the first has no file, so we skip record creation if file.size == 0 - ::Packages::Conan::CreatePackageFileService.new(current_package, params[:file], params.merge(conan_file_type: file_type)).execute + ::Packages::Conan::CreatePackageFileService.new( + current_package, + params[:file], + params.merge(conan_file_type: file_type, build: current_authenticated_job) + ).execute end end @@ -214,6 +222,7 @@ module API return unless route_authentication_setting[:job_token_allowed] job = find_job_from_token || raise(::Gitlab::Auth::UnauthorizedError) + @current_authenticated_job = job # rubocop:disable Gitlab/ModuleWithInstanceVariables job.user end diff --git a/lib/api/helpers/projects_helpers.rb b/lib/api/helpers/projects_helpers.rb index 0364ba2ad9e..f5f45cf7351 100644 --- a/lib/api/helpers/projects_helpers.rb +++ b/lib/api/helpers/projects_helpers.rb @@ -6,7 +6,7 @@ module API extend ActiveSupport::Concern extend Grape::API::Helpers - STATISTICS_SORT_PARAMS = %w[storage_size repository_size wiki_size].freeze + STATISTICS_SORT_PARAMS = %w[storage_size repository_size wiki_size packages_size].freeze params :optional_project_params_ce do optional :description, type: String, desc: 'The description of the project' @@ -32,6 +32,8 @@ module API optional :builds_access_level, type: String, values: %w(disabled private enabled), desc: 'Builds access level. One of `disabled`, `private` or `enabled`' optional :snippets_access_level, type: String, values: %w(disabled private enabled), desc: 'Snippets access level. One of `disabled`, `private` or `enabled`' optional :pages_access_level, type: String, values: %w(disabled private enabled public), desc: 'Pages access level. One of `disabled`, `private`, `enabled` or `public`' + optional :operations_access_level, type: String, values: %w(disabled private enabled), desc: 'Operations access level. One of `disabled`, `private` or `enabled`' + optional :analytics_access_level, type: String, values: %w(disabled private enabled), desc: 'Analytics access level. One of `disabled`, `private` or `enabled`' optional :emails_disabled, type: Boolean, desc: 'Disable email notifications' optional :show_default_award_emojis, type: Boolean, desc: 'Show default award emojis' diff --git a/lib/api/helpers/services_helpers.rb b/lib/api/helpers/services_helpers.rb index 4adb27a7414..9d2fd9978d9 100644 --- a/lib/api/helpers/services_helpers.rb +++ b/lib/api/helpers/services_helpers.rb @@ -304,6 +304,38 @@ module API desc: 'Project URL' } ], + 'datadog' => [ + { + required: true, + name: :api_key, + type: String, + desc: 'API key used for authentication with Datadog' + }, + { + required: false, + name: :datadog_site, + type: String, + desc: 'Choose the Datadog site to send data to. Set to "datadoghq.eu" to send data to the EU site' + }, + { + required: false, + name: :api_url, + type: String, + desc: '(Advanced) Define the full URL for your Datadog site directly' + }, + { + required: false, + name: :datadog_service, + type: String, + desc: 'Name of this GitLab instance that all data will be tagged with' + }, + { + required: false, + name: :datadog_env, + type: String, + desc: 'The environment tag that traces will be tagged with' + } + ], 'discord' => [ { required: true, @@ -459,6 +491,32 @@ module API desc: 'Colorize messages' } ], + 'jenkins' => [ + { + required: true, + name: :jenkins_url, + type: String, + desc: 'Jenkins root URL like https://jenkins.example.com' + }, + { + required: true, + name: :project_name, + type: String, + desc: 'The URL-friendly project name. Example: my_project_name' + }, + { + required: false, + name: :username, + type: String, + desc: 'A user with access to the Jenkins server, if applicable' + }, + { + required: false, + name: :password, + type: String, + desc: 'The password of the user' + } + ], 'jira' => [ { required: true, @@ -758,6 +816,7 @@ module API ::ConfluenceService, ::CampfireService, ::CustomIssueTrackerService, + ::DatadogService, ::DiscordService, ::DroneCiService, ::EmailsOnPushService, @@ -767,6 +826,7 @@ module API ::HangoutsChatService, ::HipchatService, ::IrkerService, + ::JenkinsService, ::JiraService, ::MattermostSlashCommandsService, ::SlackSlashCommandsService, @@ -787,7 +847,6 @@ module API def self.development_service_classes [ ::MockCiService, - ::MockDeploymentService, ::MockMonitoringService ] end diff --git a/lib/api/helpers/sse_helpers.rb b/lib/api/helpers/sse_helpers.rb new file mode 100644 index 00000000000..c354694f508 --- /dev/null +++ b/lib/api/helpers/sse_helpers.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +module API + module Helpers + module SSEHelpers + def request_from_sse?(project) + return false if request.referer.blank? + + uri = URI.parse(request.referer) + uri.path.starts_with?(::Gitlab::Routing.url_helpers.project_root_sse_path(project)) + rescue URI::InvalidURIError + false + end + end + end +end diff --git a/lib/api/internal/base.rb b/lib/api/internal/base.rb index 61ef1d5bde0..332f2f1986f 100644 --- a/lib/api/internal/base.rb +++ b/lib/api/internal/base.rb @@ -300,7 +300,7 @@ module API post '/two_factor_otp_check', feature_category: :authentication_and_authorization do status 200 - break { success: false } unless Feature.enabled?(:two_factor_for_cli) + break { success: false, message: 'Feature flag is disabled' } unless Feature.enabled?(:two_factor_for_cli) actor.update_last_used_at! user = actor.user @@ -316,6 +316,8 @@ module API otp_validation_result = ::Users::ValidateOtpService.new(user).execute(params.fetch(:otp_attempt)) if otp_validation_result[:status] == :success + ::Gitlab::Auth::Otp::SessionEnforcer.new(actor.key).update_session + { success: true } else { success: false, message: 'Invalid OTP' } diff --git a/lib/api/internal/kubernetes.rb b/lib/api/internal/kubernetes.rb index d4690709de4..73723a96401 100644 --- a/lib/api/internal/kubernetes.rb +++ b/lib/api/internal/kubernetes.rb @@ -85,9 +85,7 @@ module API get '/project_info' do project = find_project(params[:id]) - # TODO sort out authorization for real - # https://gitlab.com/gitlab-org/gitlab/-/issues/220912 - unless Ability.allowed?(nil, :download_code, project) + unless Guest.can?(:download_code, project) || agent.has_access_to?(project) not_found! end @@ -123,3 +121,5 @@ module API end end end + +API::Internal::Kubernetes.prepend_if_ee('EE::API::Internal::Kubernetes') diff --git a/lib/api/internal/pages.rb b/lib/api/internal/pages.rb index 690f52d89f3..8eaeeae26c2 100644 --- a/lib/api/internal/pages.rb +++ b/lib/api/internal/pages.rb @@ -32,26 +32,29 @@ module API requires :host, type: String, desc: 'The host to query for' end get "/" do - serverless_domain_finder = ServerlessDomainFinder.new(params[:host]) - if serverless_domain_finder.serverless? - # Handle Serverless domains - serverless_domain = serverless_domain_finder.execute - no_content! unless serverless_domain - - virtual_domain = Serverless::VirtualDomain.new(serverless_domain) - no_content! unless virtual_domain - - present virtual_domain, with: Entities::Internal::Serverless::VirtualDomain - else - # Handle Pages domains - host = Namespace.find_by_pages_host(params[:host]) || PagesDomain.find_by_domain_case_insensitive(params[:host]) - no_content! unless host - - virtual_domain = host.pages_virtual_domain - no_content! unless virtual_domain - - present virtual_domain, with: Entities::Internal::Pages::VirtualDomain - end + ## + # Serverless domain proxy has been deprecated and disabled as per + # https://gitlab.com/gitlab-org/gitlab-pages/-/issues/467 + # + # serverless_domain_finder = ServerlessDomainFinder.new(params[:host]) + # if serverless_domain_finder.serverless? + # # Handle Serverless domains + # serverless_domain = serverless_domain_finder.execute + # no_content! unless serverless_domain + # + # virtual_domain = Serverless::VirtualDomain.new(serverless_domain) + # no_content! unless virtual_domain + # + # present virtual_domain, with: Entities::Internal::Serverless::VirtualDomain + # end + + host = Namespace.find_by_pages_host(params[:host]) || PagesDomain.find_by_domain_case_insensitive(params[:host]) + no_content! unless host + + virtual_domain = host.pages_virtual_domain + no_content! unless virtual_domain + + present virtual_domain, with: Entities::Internal::Pages::VirtualDomain end end end diff --git a/lib/api/issues.rb b/lib/api/issues.rb index 6a6ee7a4e1c..73e2163248d 100644 --- a/lib/api/issues.rb +++ b/lib/api/issues.rb @@ -435,3 +435,5 @@ module API end end end + +API::Issues.prepend_if_ee('EE::API::Issues') diff --git a/lib/api/jobs.rb b/lib/api/jobs.rb index 51659c2e8a1..44751b3d76c 100644 --- a/lib/api/jobs.rb +++ b/lib/api/jobs.rb @@ -76,6 +76,8 @@ module API build = find_build!(params[:job_id]) + authorize_read_build_trace!(build) if build + header 'Content-Disposition', "infile; filename=\"#{build.id}.log\"" content_type 'text/plain' env['api.format'] = :binary diff --git a/lib/api/labels.rb b/lib/api/labels.rb index a8fc277989e..c9f29865664 100644 --- a/lib/api/labels.rb +++ b/lib/api/labels.rb @@ -57,7 +57,7 @@ module API success Entities::ProjectLabel end params do - optional :label_id, type: Integer, desc: 'The id of the label to be updated' + optional :label_id, type: Integer, desc: 'The ID of the label to be updated' optional :name, type: String, desc: 'The name of the label to be updated' use :project_label_update_params exactly_one_of :label_id, :name @@ -71,7 +71,7 @@ module API success Entities::ProjectLabel end params do - optional :label_id, type: Integer, desc: 'The id of the label to be deleted' + optional :label_id, type: Integer, desc: 'The ID of the label to be deleted' optional :name, type: String, desc: 'The name of the label to be deleted' exactly_one_of :label_id, :name end diff --git a/lib/api/members.rb b/lib/api/members.rb index 803de51651a..9bea74e2ce9 100644 --- a/lib/api/members.rb +++ b/lib/api/members.rb @@ -62,7 +62,7 @@ module API get ":id/members/:user_id" do source = find_source(source_type, params[:id]) - members = source.members + members = source_members(source) member = members.find_by!(user_id: params[:user_id]) present_members member diff --git a/lib/api/merge_request_approvals.rb b/lib/api/merge_request_approvals.rb index 27ef0b9c7cd..00f42703731 100644 --- a/lib/api/merge_request_approvals.rb +++ b/lib/api/merge_request_approvals.rb @@ -4,7 +4,7 @@ module API class MergeRequestApprovals < ::API::Base before { authenticate_non_get! } - feature_category :code_review + feature_category :source_code_management helpers do params :ee_approval_params do diff --git a/lib/api/merge_requests.rb b/lib/api/merge_requests.rb index d17e451093b..ab0e9b95e4a 100644 --- a/lib/api/merge_requests.rb +++ b/lib/api/merge_requests.rb @@ -11,6 +11,7 @@ module API feature_category :code_review helpers Helpers::MergeRequestsHelpers + helpers Helpers::SSEHelpers # EE::API::MergeRequests would override the following helpers helpers do @@ -216,6 +217,8 @@ module API handle_merge_request_errors!(merge_request) + Gitlab::UsageDataCounters::EditorUniqueCounter.track_sse_edit_action(author: current_user) if request_from_sse?(user_project) + present merge_request, with: Entities::MergeRequest, current_user: current_user, project: user_project end diff --git a/lib/api/nuget_packages.rb b/lib/api/nuget_packages.rb deleted file mode 100644 index 65a85f3c930..00000000000 --- a/lib/api/nuget_packages.rb +++ /dev/null @@ -1,247 +0,0 @@ -# frozen_string_literal: true - -# NuGet Package Manager Client API -# -# These API endpoints are not meant to be consumed directly by users. They are -# called by the NuGet package manager client when users run commands -# like `nuget install` or `nuget push`. -module API - class NugetPackages < ::API::Base - helpers ::API::Helpers::PackagesManagerClientsHelpers - helpers ::API::Helpers::Packages::BasicAuthHelpers - - feature_category :package_registry - - POSITIVE_INTEGER_REGEX = %r{\A[1-9]\d*\z}.freeze - NON_NEGATIVE_INTEGER_REGEX = %r{\A0|[1-9]\d*\z}.freeze - - PACKAGE_FILENAME = 'package.nupkg' - - default_format :json - - rescue_from ArgumentError do |e| - render_api_error!(e.message, 400) - end - - helpers do - def find_packages - packages = package_finder.execute - - not_found!('Packages') unless packages.exists? - - packages - end - - def find_package - package = package_finder(package_version: params[:package_version]).execute - .first - - not_found!('Package') unless package - - package - end - - def package_finder(finder_params = {}) - ::Packages::Nuget::PackageFinder.new( - authorized_user_project, - **finder_params.merge(package_name: params[:package_name]) - ) - end - end - - before do - require_packages_enabled! - end - - params do - requires :id, type: String, desc: 'The ID of a project', regexp: POSITIVE_INTEGER_REGEX - end - - route_setting :authentication, deploy_token_allowed: true, job_token_allowed: :basic_auth, basic_auth_personal_access_token: true - - resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do - before do - authorized_user_project - end - - namespace ':id/packages/nuget' 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' - end - - route_setting :authentication, deploy_token_allowed: true, job_token_allowed: :basic_auth, basic_auth_personal_access_token: true - - get 'index', format: :json do - authorize_read_package!(authorized_user_project) - - track_package_event('cli_metadata', :nuget) - - present ::Packages::Nuget::ServiceIndexPresenter.new(authorized_user_project), - with: ::API::Entities::Nuget::ServiceIndex - end - - # 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' - end - - params do - requires :package, type: ::API::Validations::Types::WorkhorseFile, desc: 'The package file to be published (generated by Multipart middleware)' - end - - route_setting :authentication, deploy_token_allowed: true, job_token_allowed: :basic_auth, basic_auth_personal_access_token: true - - put do - authorize_upload!(authorized_user_project) - bad_request!('File is too large') if authorized_user_project.actual_limits.exceeded?(:nuget_max_file_size, params[:package].size) - - file_params = params.merge( - file: params[:package], - file_name: PACKAGE_FILENAME - ) - - package = ::Packages::Nuget::CreatePackageService.new(authorized_user_project, current_user) - .execute - - package_file = ::Packages::CreatePackageFileService.new(package, file_params) - .execute - - track_package_event('push_package', :nuget) - - ::Packages::Nuget::ExtractionWorker.perform_async(package_file.id) # rubocop:disable CodeReuse/Worker - - created! - rescue ObjectStorage::RemoteStoreError => e - Gitlab::ErrorTracking.track_exception(e, extra: { file_name: params[:file_name], project_id: authorized_user_project.id }) - - forbidden! - end - - route_setting :authentication, deploy_token_allowed: true, job_token_allowed: :basic_auth, basic_auth_personal_access_token: true - - put 'authorize' do - authorize_workhorse!( - subject: authorized_user_project, - has_length: false, - maximum_size: authorized_user_project.actual_limits.nuget_max_file_size - ) - end - - params do - requires :package_name, type: String, desc: 'The NuGet package name', regexp: API::NO_SLASH_URL_PART_REGEX - end - namespace '/metadata/*package_name' do - before do - authorize_read_package!(authorized_user_project) - end - - # https://docs.microsoft.com/en-us/nuget/api/registration-base-url-resource - desc 'The NuGet Metadata Service - Package name level' do - detail 'This feature was introduced in GitLab 12.8' - end - - route_setting :authentication, deploy_token_allowed: true, job_token_allowed: :basic_auth, basic_auth_personal_access_token: true - - get 'index', format: :json do - present ::Packages::Nuget::PackagesMetadataPresenter.new(find_packages), - 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' - end - params do - requires :package_version, type: String, desc: 'The NuGet package version', regexp: API::NO_SLASH_URL_PART_REGEX - end - - route_setting :authentication, deploy_token_allowed: true, job_token_allowed: :basic_auth, basic_auth_personal_access_token: true - - get '*package_version', format: :json do - present ::Packages::Nuget::PackageMetadataPresenter.new(find_package), - with: ::API::Entities::Nuget::PackageMetadata - end - 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 - end - namespace '/download/*package_name' do - before do - authorize_read_package!(authorized_user_project) - end - - desc 'The NuGet Content Service - index request' do - detail 'This feature was introduced in GitLab 12.8' - end - - route_setting :authentication, deploy_token_allowed: true, job_token_allowed: :basic_auth, basic_auth_personal_access_token: true - - get 'index', format: :json do - present ::Packages::Nuget::PackagesVersionsPresenter.new(find_packages), - with: ::API::Entities::Nuget::PackagesVersions - end - - desc 'The NuGet Content Service - content request' do - detail 'This feature was introduced in GitLab 12.8' - end - params do - requires :package_version, type: String, desc: 'The NuGet package version', regexp: API::NO_SLASH_URL_PART_REGEX - requires :package_filename, type: String, desc: 'The NuGet package filename', regexp: API::NO_SLASH_URL_PART_REGEX - end - - route_setting :authentication, deploy_token_allowed: true, job_token_allowed: :basic_auth, basic_auth_personal_access_token: true - - get '*package_version/*package_filename', format: :nupkg do - filename = "#{params[:package_filename]}.#{params[:format]}" - package_file = ::Packages::PackageFileFinder.new(find_package, filename, with_file_name_like: true) - .execute - - not_found!('Package') unless package_file - - track_package_event('pull_package', :nuget) - - # nuget and dotnet don't support 302 Moved status codes, supports_direct_download has to be set to false - present_carrierwave_file!(package_file.file, supports_direct_download: false) - end - end - - params do - requires :q, type: String, desc: 'The search term' - optional :skip, type: Integer, desc: 'The number of results to skip', default: 0, regexp: NON_NEGATIVE_INTEGER_REGEX - optional :take, type: Integer, desc: 'The number of results to return', default: Kaminari.config.default_per_page, regexp: POSITIVE_INTEGER_REGEX - optional :prerelease, type: Boolean, desc: 'Include prerelease versions', default: true - end - namespace '/query' do - before do - authorize_read_package!(authorized_user_project) - end - - # https://docs.microsoft.com/en-us/nuget/api/search-query-service-resource - desc 'The NuGet Search Service' do - detail 'This feature was introduced in GitLab 12.8' - end - - route_setting :authentication, deploy_token_allowed: true, job_token_allowed: :basic_auth, basic_auth_personal_access_token: true - - get format: :json do - search_options = { - include_prerelease_versions: params[:prerelease], - per_page: params[:take], - padding: params[:skip] - } - search = Packages::Nuget::SearchService - .new(authorized_user_project, params[:q], search_options) - .execute - - track_package_event('search_package', :nuget) - - present ::Packages::Nuget::SearchResultsPresenter.new(search), - with: ::API::Entities::Nuget::SearchResults - end - end - end - end - end -end diff --git a/lib/api/nuget_project_packages.rb b/lib/api/nuget_project_packages.rb new file mode 100644 index 00000000000..b2516cc91f8 --- /dev/null +++ b/lib/api/nuget_project_packages.rb @@ -0,0 +1,139 @@ +# frozen_string_literal: true + +# NuGet Package Manager Client API +# +# These API endpoints are not meant to be consumed directly by users. They are +# called by the NuGet package manager client when users run commands +# like `nuget install` or `nuget push`. +module API + class NugetProjectPackages < ::API::Base + helpers ::API::Helpers::PackagesManagerClientsHelpers + helpers ::API::Helpers::Packages::BasicAuthHelpers + + feature_category :package_registry + + PACKAGE_FILENAME = 'package.nupkg' + + default_format :json + + rescue_from ArgumentError do |e| + render_api_error!(e.message, 400) + end + + before do + require_packages_enabled! + end + + params do + requires :id, type: String, desc: 'The ID of a project', regexp: ::API::Concerns::Packages::NugetEndpoints::POSITIVE_INTEGER_REGEX + end + + route_setting :authentication, deploy_token_allowed: true, job_token_allowed: :basic_auth, basic_auth_personal_access_token: true + + resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do + before do + authorized_user_project + end + + 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' + end + + params do + requires :package, type: ::API::Validations::Types::WorkhorseFile, desc: 'The package file to be published (generated by Multipart middleware)' + end + + route_setting :authentication, deploy_token_allowed: true, job_token_allowed: :basic_auth, basic_auth_personal_access_token: true + + put do + authorize_upload!(authorized_user_project) + bad_request!('File is too large') if authorized_user_project.actual_limits.exceeded?(:nuget_max_file_size, params[:package].size) + + file_params = params.merge( + file: params[:package], + file_name: PACKAGE_FILENAME + ) + + package = ::Packages::Nuget::CreatePackageService.new( + authorized_user_project, + current_user, + declared_params.merge(build: current_authenticated_job) + ).execute + + package_file = ::Packages::CreatePackageFileService.new( + package, + file_params.merge(build: current_authenticated_job) + ).execute + + track_package_event('push_package', :nuget, category: 'API::NugetPackages') + + ::Packages::Nuget::ExtractionWorker.perform_async(package_file.id) # rubocop:disable CodeReuse/Worker + + created! + rescue ObjectStorage::RemoteStoreError => e + Gitlab::ErrorTracking.track_exception(e, extra: { file_name: params[:file_name], project_id: authorized_user_project.id }) + + forbidden! + end + + route_setting :authentication, deploy_token_allowed: true, job_token_allowed: :basic_auth, basic_auth_personal_access_token: true + + put 'authorize' do + authorize_workhorse!( + subject: authorized_user_project, + has_length: false, + maximum_size: authorized_user_project.actual_limits.nuget_max_file_size + ) + 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 + end + namespace '/download/*package_name' do + before do + authorize_read_package!(authorized_user_project) + end + + desc 'The NuGet Content Service - index request' do + detail 'This feature was introduced in GitLab 12.8' + end + + route_setting :authentication, deploy_token_allowed: true, job_token_allowed: :basic_auth, basic_auth_personal_access_token: true + + get 'index', format: :json do + present ::Packages::Nuget::PackagesVersionsPresenter.new(find_packages), + with: ::API::Entities::Nuget::PackagesVersions + end + + desc 'The NuGet Content Service - content request' do + detail 'This feature was introduced in GitLab 12.8' + end + params do + requires :package_version, type: String, desc: 'The NuGet package version', regexp: API::NO_SLASH_URL_PART_REGEX + requires :package_filename, type: String, desc: 'The NuGet package filename', regexp: API::NO_SLASH_URL_PART_REGEX + end + + route_setting :authentication, deploy_token_allowed: true, job_token_allowed: :basic_auth, basic_auth_personal_access_token: true + + get '*package_version/*package_filename', format: :nupkg do + filename = "#{params[:package_filename]}.#{params[:format]}" + package_file = ::Packages::PackageFileFinder.new(find_package, filename, with_file_name_like: true) + .execute + + not_found!('Package') unless package_file + + track_package_event('pull_package', :nuget, category: 'API::NugetPackages') + + # nuget and dotnet don't support 302 Moved status codes, supports_direct_download has to be set to false + present_carrierwave_file!(package_file.file, supports_direct_download: false) + end + end + end + end + end +end diff --git a/lib/api/project_clusters.rb b/lib/api/project_clusters.rb index cfb0c5fd705..6785b28ddef 100644 --- a/lib/api/project_clusters.rb +++ b/lib/api/project_clusters.rb @@ -83,6 +83,8 @@ module API optional :environment_scope, type: String, desc: 'The associated environment to the cluster' optional :namespace_per_environment, default: true, type: Boolean, desc: 'Deploy each environment to a separate Kubernetes namespace' optional :management_project_id, type: Integer, desc: 'The ID of the management project' + optional :enabled, type: Boolean, desc: 'Determines if cluster is active or not' + optional :managed, type: Boolean, desc: 'Determines if GitLab will manage namespaces and service accounts for this cluster' optional :platform_kubernetes_attributes, type: Hash, desc: %q(Platform Kubernetes data) do optional :api_url, type: String, desc: 'URL to access the Kubernetes API' optional :token, type: String, desc: 'Token to authenticate against Kubernetes' diff --git a/lib/api/project_repository_storage_moves.rb b/lib/api/project_repository_storage_moves.rb index fe6de3ea385..196b7d88500 100644 --- a/lib/api/project_repository_storage_moves.rb +++ b/lib/api/project_repository_storage_moves.rb @@ -34,6 +34,22 @@ module API present storage_move, with: Entities::ProjectRepositoryStorageMove, current_user: current_user end + + desc 'Schedule bulk project repository storage moves' do + detail 'This feature was introduced in GitLab 13.7.' + end + params do + requires :source_storage_name, type: String, desc: 'The source storage shard', values: -> { Gitlab.config.repositories.storages.keys } + optional :destination_storage_name, type: String, desc: 'The destination storage shard', values: -> { Gitlab.config.repositories.storages.keys } + end + post do + ::Projects::ScheduleBulkRepositoryShardMovesService.enqueue( + declared_params[:source_storage_name], + declared_params[:destination_storage_name] + ) + + accepted! + end end params do diff --git a/lib/api/pypi_packages.rb b/lib/api/pypi_packages.rb index 7104fb8d999..658c6d13847 100644 --- a/lib/api/pypi_packages.rb +++ b/lib/api/pypi_packages.rb @@ -127,7 +127,7 @@ module API track_package_event('push_package', :pypi) ::Packages::Pypi::CreatePackageService - .new(authorized_user_project, current_user, declared_params) + .new(authorized_user_project, current_user, declared_params.merge(build: current_authenticated_job)) .execute created! diff --git a/lib/api/release/links.rb b/lib/api/release/links.rb index d3a185a51c8..52c73104bb4 100644 --- a/lib/api/release/links.rb +++ b/lib/api/release/links.rb @@ -57,7 +57,7 @@ module API end params do - requires :link_id, type: String, desc: 'The id of the link' + requires :link_id, type: String, desc: 'The ID of the link' end resource 'links/:link_id' do desc 'Get a link detail of a release' do diff --git a/lib/api/settings.rb b/lib/api/settings.rb index b95856d99d1..b3f09b431b0 100644 --- a/lib/api/settings.rb +++ b/lib/api/settings.rb @@ -52,6 +52,7 @@ module API optional :default_project_visibility, type: String, values: Gitlab::VisibilityLevel.string_values, desc: 'The default project visibility' optional :default_projects_limit, type: Integer, desc: 'The maximum number of personal projects' optional :default_snippet_visibility, type: String, values: Gitlab::VisibilityLevel.string_values, desc: 'The default snippet visibility' + optional :disable_feed_token, type: Boolean, desc: 'Disable display of RSS/Atom and Calendar `feed_tokens`' optional :disabled_oauth_sign_in_sources, type: Array[String], coerce_with: Validations::Types::CommaSeparatedToArray.coerce, desc: 'Disable certain OAuth sign-in sources' optional :domain_denylist_enabled, type: Boolean, desc: 'Enable domain denylist for sign ups' optional :domain_denylist, type: Array[String], coerce_with: Validations::Types::CommaSeparatedToArray.coerce, desc: 'Users with e-mail addresses that match these domain(s) will NOT be able to sign-up. Wildcards allowed. Use separate lines for multiple entries. Ex: domain.com, *.domain.com' @@ -102,6 +103,11 @@ module API optional :performance_bar_allowed_group_id, type: String, desc: 'Deprecated: Use :performance_bar_allowed_group_path instead. Path of the group that is allowed to toggle the performance bar.' # support legacy names, can be removed in v6 optional :performance_bar_allowed_group_path, type: String, desc: 'Path of the group that is allowed to toggle the performance bar.' optional :performance_bar_enabled, type: String, desc: 'Deprecated: Pass `performance_bar_allowed_group_path: nil` instead. Allow enabling the performance.' # support legacy names, can be removed in v6 + optional :personal_access_token_prefix, type: String, desc: 'Prefix to prepend to all personal access tokens' + optional :kroki_enabled, type: Boolean, desc: 'Enable Kroki' + given kroki_enabled: ->(val) { val } do + requires :kroki_url, type: String, desc: 'The Kroki server URL' + end optional :plantuml_enabled, type: Boolean, desc: 'Enable PlantUML' given plantuml_enabled: ->(val) { val } do requires :plantuml_url, type: String, desc: 'The PlantUML server URL' diff --git a/lib/api/statistics.rb b/lib/api/statistics.rb index 1814e1a6782..6818c04fd2e 100644 --- a/lib/api/statistics.rb +++ b/lib/api/statistics.rb @@ -4,7 +4,7 @@ module API class Statistics < ::API::Base before { authenticated_as_admin! } - feature_category :instance_statistics + feature_category :devops_reports COUNTED_ITEMS = [Project, User, Group, ForkNetworkMember, ForkNetwork, Issue, MergeRequest, Note, Snippet, Key, Milestone].freeze diff --git a/lib/api/usage_data.rb b/lib/api/usage_data.rb index 7b038ec74bb..cad2f52e951 100644 --- a/lib/api/usage_data.rb +++ b/lib/api/usage_data.rb @@ -20,6 +20,18 @@ module API requires :event, type: String, desc: 'The event name that should be tracked' end + post 'increment_counter' do + event_name = params[:event] + + increment_counter(event_name) + + status :ok + end + + params do + requires :event, type: String, desc: 'The event name that should be tracked' + end + post 'increment_unique_users' do event_name = params[:event] diff --git a/lib/api/users.rb b/lib/api/users.rb index 501ed629c7e..8b9b82877f7 100644 --- a/lib/api/users.rb +++ b/lib/api/users.rb @@ -534,6 +534,24 @@ module API user.activate end + + desc 'Approve a pending user. Available only for admins.' + params do + requires :id, type: Integer, desc: 'The ID of the user' + end + post ':id/approve', feature_category: :authentication_and_authorization do + user = User.find_by(id: params[:id]) + not_found!('User') unless can?(current_user, :read_user, user) + + result = ::Users::ApproveService.new(current_user).execute(user) + + if result[:success] + result + else + render_api_error!(result[:message], result[:http_status]) + end + end + # rubocop: enable CodeReuse/ActiveRecord desc 'Deactivate an active user. Available only for admins.' params do diff --git a/lib/api/validations/validators/absence.rb b/lib/api/validations/validators/absence.rb index 1f43f3ab126..7858ce7140b 100644 --- a/lib/api/validations/validators/absence.rb +++ b/lib/api/validations/validators/absence.rb @@ -7,7 +7,7 @@ module API def validate_param!(attr_name, params) return if params.respond_to?(:key?) && !params.key?(attr_name) - raise Grape::Exceptions::Validation, params: [@scope.full_name(attr_name)], message: message(:absence) + raise Grape::Exceptions::Validation.new(params: [@scope.full_name(attr_name)], message: message(:absence)) end end end diff --git a/lib/api/validations/validators/array_none_any.rb b/lib/api/validations/validators/array_none_any.rb index 7efb8e6ccee..3732c1f575c 100644 --- a/lib/api/validations/validators/array_none_any.rb +++ b/lib/api/validations/validators/array_none_any.rb @@ -10,8 +10,10 @@ module API return if value.is_a?(Array) || [IssuableFinder::Params::FILTER_NONE, IssuableFinder::Params::FILTER_ANY].include?(value.to_s.downcase) - raise Grape::Exceptions::Validation, params: [@scope.full_name(attr_name)], - message: "should be an array, 'None' or 'Any'" + raise Grape::Exceptions::Validation.new( + params: [@scope.full_name(attr_name)], + message: "should be an array, 'None' or 'Any'" + ) end end end diff --git a/lib/api/validations/validators/check_assignees_count.rb b/lib/api/validations/validators/check_assignees_count.rb index b614058e325..92ada159b46 100644 --- a/lib/api/validations/validators/check_assignees_count.rb +++ b/lib/api/validations/validators/check_assignees_count.rb @@ -18,9 +18,10 @@ module API def validate_param!(attr_name, params) return if param_allowed?(attr_name, params) - raise Grape::Exceptions::Validation, - params: [@scope.full_name(attr_name)], - message: "allows one value, but found #{params[attr_name].size}: #{params[attr_name].join(", ")}" + raise Grape::Exceptions::Validation.new( + params: [@scope.full_name(attr_name)], + message: "allows one value, but found #{params[attr_name].size}: #{params[attr_name].join(", ")}" + ) end private diff --git a/lib/api/validations/validators/email_or_email_list.rb b/lib/api/validations/validators/email_or_email_list.rb index b7f2a0cd443..da665f39130 100644 --- a/lib/api/validations/validators/email_or_email_list.rb +++ b/lib/api/validations/validators/email_or_email_list.rb @@ -11,9 +11,10 @@ module API return if value.split(',').map { |v| ValidateEmail.valid?(v) }.all? - raise Grape::Exceptions::Validation, + raise Grape::Exceptions::Validation.new( params: [@scope.full_name(attr_name)], message: "contains an invalid email address" + ) end end end diff --git a/lib/api/validations/validators/file_path.rb b/lib/api/validations/validators/file_path.rb index 8a815c3b2b8..a6a3c692fd6 100644 --- a/lib/api/validations/validators/file_path.rb +++ b/lib/api/validations/validators/file_path.rb @@ -11,8 +11,10 @@ module API path = Gitlab::Utils.check_path_traversal!(path) Gitlab::Utils.check_allowed_absolute_path!(path, path_allowlist) rescue - raise Grape::Exceptions::Validation, params: [@scope.full_name(attr_name)], - message: "should be a valid file path" + raise Grape::Exceptions::Validation.new( + params: [@scope.full_name(attr_name)], + message: "should be a valid file path" + ) end end end diff --git a/lib/api/validations/validators/git_ref.rb b/lib/api/validations/validators/git_ref.rb index 1dda9d758a7..dcb1db6ca33 100644 --- a/lib/api/validations/validators/git_ref.rb +++ b/lib/api/validations/validators/git_ref.rb @@ -17,8 +17,10 @@ module API return unless invalid_character?(revision) - raise Grape::Exceptions::Validation, params: [@scope.full_name(attr_name)], - message: 'should be a valid reference path' + raise Grape::Exceptions::Validation.new( + params: [@scope.full_name(attr_name)], + message: 'should be a valid reference path' + ) end private diff --git a/lib/api/validations/validators/git_sha.rb b/lib/api/validations/validators/git_sha.rb index 657307db1df..665d1878b4c 100644 --- a/lib/api/validations/validators/git_sha.rb +++ b/lib/api/validations/validators/git_sha.rb @@ -9,8 +9,10 @@ module API return if Commit::EXACT_COMMIT_SHA_PATTERN.match?(sha) - raise Grape::Exceptions::Validation, params: [@scope.full_name(attr_name)], - message: "should be a valid sha" + raise Grape::Exceptions::Validation.new( + params: [@scope.full_name(attr_name)], + message: "should be a valid sha" + ) end end end diff --git a/lib/api/validations/validators/integer_none_any.rb b/lib/api/validations/validators/integer_none_any.rb index aa8c137a6ab..32ab6e19b98 100644 --- a/lib/api/validations/validators/integer_none_any.rb +++ b/lib/api/validations/validators/integer_none_any.rb @@ -3,15 +3,11 @@ module API module Validations module Validators - class IntegerNoneAny < Grape::Validations::Base - def validate_param!(attr_name, params) - value = params[attr_name] + class IntegerNoneAny < IntegerOrCustomValue + private - return if value.is_a?(Integer) || - [IssuableFinder::Params::FILTER_NONE, IssuableFinder::Params::FILTER_ANY].include?(value.to_s.downcase) - - raise Grape::Exceptions::Validation, params: [@scope.full_name(attr_name)], - message: "should be an integer, 'None' or 'Any'" + def extract_custom_values(_options) + [IssuableFinder::Params::FILTER_NONE, IssuableFinder::Params::FILTER_ANY] end end end diff --git a/lib/api/validations/validators/integer_or_custom_value.rb b/lib/api/validations/validators/integer_or_custom_value.rb new file mode 100644 index 00000000000..d2352495948 --- /dev/null +++ b/lib/api/validations/validators/integer_or_custom_value.rb @@ -0,0 +1,33 @@ +# frozen_string_literal: true + +module API + module Validations + module Validators + class IntegerOrCustomValue < Grape::Validations::Base + def initialize(attrs, options, required, scope, **opts) + @custom_values = extract_custom_values(options) + super + end + + def validate_param!(attr_name, params) + value = params[attr_name] + + return if value.is_a?(Integer) + return if @custom_values.map(&:downcase).include?(value.to_s.downcase) + + valid_options = Gitlab::Utils.to_exclusive_sentence(['an integer'] + @custom_values) + raise Grape::Exceptions::Validation.new( + params: [@scope.full_name(attr_name)], + message: "should be #{valid_options}, however got #{value}" + ) + end + + private + + def extract_custom_values(options) + options.is_a?(Hash) ? options[:values] : options + end + end + end + end +end diff --git a/lib/api/validations/validators/limit.rb b/lib/api/validations/validators/limit.rb index 3bb4cee1d75..e8f894849a5 100644 --- a/lib/api/validations/validators/limit.rb +++ b/lib/api/validations/validators/limit.rb @@ -9,9 +9,10 @@ module API return if value.size <= @option - raise Grape::Exceptions::Validation, + raise Grape::Exceptions::Validation.new( params: [@scope.full_name(attr_name)], message: "#{@scope.full_name(attr_name)} must be less than #{@option} characters" + ) end end end diff --git a/lib/api/validations/validators/untrusted_regexp.rb b/lib/api/validations/validators/untrusted_regexp.rb index ec623684e67..3ddea2bd9de 100644 --- a/lib/api/validations/validators/untrusted_regexp.rb +++ b/lib/api/validations/validators/untrusted_regexp.rb @@ -11,7 +11,7 @@ module API Gitlab::UntrustedRegexp.new(value) rescue RegexpError => e message = "is an invalid regexp: #{e.message}" - raise Grape::Exceptions::Validation, params: [@scope.full_name(attr_name)], message: message + raise Grape::Exceptions::Validation.new(params: [@scope.full_name(attr_name)], message: message) end end end |