diff options
author | Robert Speicher <rspeicher@gmail.com> | 2021-01-20 13:34:23 -0600 |
---|---|---|
committer | Robert Speicher <rspeicher@gmail.com> | 2021-01-20 13:34:23 -0600 |
commit | 6438df3a1e0fb944485cebf07976160184697d72 (patch) | |
tree | 00b09bfd170e77ae9391b1a2f5a93ef6839f2597 /lib/api | |
parent | 42bcd54d971da7ef2854b896a7b34f4ef8601067 (diff) | |
download | gitlab-ce-6438df3a1e0fb944485cebf07976160184697d72.tar.gz |
Add latest changes from gitlab-org/gitlab@13-8-stable-eev13.8.0-rc42
Diffstat (limited to 'lib/api')
44 files changed, 516 insertions, 148 deletions
diff --git a/lib/api/api.rb b/lib/api/api.rb index 06c2b46a2f2..ada0da28749 100644 --- a/lib/api/api.rb +++ b/lib/api/api.rb @@ -59,6 +59,7 @@ module API project: -> { @project }, namespace: -> { @group }, caller_id: route.origin, + remote_ip: request.ip, feature_category: feature_category ) end @@ -212,6 +213,7 @@ module API mount ::API::GroupPackages mount ::API::PackageFiles mount ::API::NugetProjectPackages + mount ::API::NugetGroupPackages mount ::API::PypiPackages mount ::API::ComposerPackages mount ::API::ConanProjectPackages @@ -251,6 +253,7 @@ module API mount ::API::Services mount ::API::Settings mount ::API::SidekiqMetrics + mount ::API::SnippetRepositoryStorageMoves mount ::API::Snippets mount ::API::Statistics mount ::API::Submodules diff --git a/lib/api/api_guard.rb b/lib/api/api_guard.rb index 0a486307653..8641271f2df 100644 --- a/lib/api/api_guard.rb +++ b/lib/api/api_guard.rb @@ -69,10 +69,15 @@ module API def find_user_from_sources strong_memoize(:find_user_from_sources) do - deploy_token_from_request || - find_user_from_bearer_token || - find_user_from_job_token || - user_from_warden + if try(:namespace_inheritable, :authentication) + user_from_namespace_inheritable || + user_from_warden + else + deploy_token_from_request || + find_user_from_bearer_token || + find_user_from_job_token || + user_from_warden + end end end diff --git a/lib/api/boards.rb b/lib/api/boards.rb index e2d30dd7c2b..5fd4ca3546c 100644 --- a/lib/api/boards.rb +++ b/lib/api/boards.rb @@ -7,10 +7,10 @@ module API prepend_if_ee('EE::API::BoardsResponses') # rubocop: disable Cop/InjectEnterpriseEditionModule - before { authenticate! } - feature_category :boards + before { authenticate! } + helpers do def board_parent user_project diff --git a/lib/api/boards_responses.rb b/lib/api/boards_responses.rb index 89355c84401..5a30de1f766 100644 --- a/lib/api/boards_responses.rb +++ b/lib/api/boards_responses.rb @@ -80,10 +80,20 @@ module API requires :label_id, type: Integer, desc: 'The ID of an existing label' end - params :update_params do + params :update_params_ce do + optional :name, type: String, desc: 'The board name' + optional :hide_backlog_list, type: Grape::API::Boolean, desc: 'Hide the Open list' + optional :hide_closed_list, type: Grape::API::Boolean, desc: 'Hide the Closed list' + end + + params :update_params_ee do # Configurable issue boards are not available in CE/EE Core. # https://docs.gitlab.com/ee/user/project/issue_board.html#configurable-issue-boards - optional :name, type: String, desc: 'The board name' + end + + params :update_params do + use :update_params_ce + use :update_params_ee end end end diff --git a/lib/api/ci/runner.rb b/lib/api/ci/runner.rb index 86e1a939df1..5cfb65e1fbb 100644 --- a/lib/api/ci/runner.rb +++ b/lib/api/ci/runner.rb @@ -180,6 +180,7 @@ module API optional :checksum, type: String, desc: %q(Job's trace CRC32 checksum) optional :bytesize, type: Integer, desc: %q(Job's trace size in bytes) end + optional :exit_code, type: Integer, desc: %q(Job's exit code) end put '/:id' do job = authenticate_job! diff --git a/lib/api/concerns/packages/nuget_endpoints.rb b/lib/api/concerns/packages/nuget_endpoints.rb index 1a03a6a6dad..53b778875fc 100644 --- a/lib/api/concerns/packages/nuget_endpoints.rb +++ b/lib/api/concerns/packages/nuget_endpoints.rb @@ -19,44 +19,49 @@ module API included do helpers do - def find_packages - packages = package_finder.execute + def find_packages(package_name) + packages = package_finder(package_name).execute not_found!('Packages') unless packages.exists? packages end - def find_package - package = package_finder(package_version: params[:package_version]).execute - .first + def find_package(package_name, package_version) + package = package_finder(package_name, package_version).execute + .first not_found!('Package') unless package package end - def package_finder(finder_params = {}) + def package_finder(package_name, package_version = nil) ::Packages::Nuget::PackageFinder.new( - authorized_user_project, - **finder_params.merge(package_name: params[:package_name]) + current_user, + project_or_group, + package_name: package_name, + package_version: package_version ) end + + def search_packages(search_term, search_options) + ::Packages::Nuget::SearchService + .new(current_user, project_or_group, params[:q], search_options) + .execute + end end # https://docs.microsoft.com/en-us/nuget/api/service-index desc 'The NuGet Service Index' do detail 'This feature was introduced in GitLab 12.6' 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) + authorize_read_package!(project_or_group) track_package_event('cli_metadata', :nuget, category: 'API::NugetPackages') - present ::Packages::Nuget::ServiceIndexPresenter.new(authorized_user_project), - with: ::API::Entities::Nuget::ServiceIndex + present ::Packages::Nuget::ServiceIndexPresenter.new(project_or_group), + with: ::API::Entities::Nuget::ServiceIndex end # https://docs.microsoft.com/en-us/nuget/api/registration-base-url-resource @@ -64,18 +69,15 @@ module API 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) + after_validation do + authorize_read_package!(project_or_group) 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), + present ::Packages::Nuget::PackagesMetadataPresenter.new(find_packages(params[:package_name])), with: ::API::Entities::Nuget::PackagesMetadata end @@ -85,11 +87,8 @@ module API 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), + present ::Packages::Nuget::PackageMetadataPresenter.new(find_package(params[:package_name], params[:package_version])), with: ::API::Entities::Nuget::PackageMetadata end end @@ -102,30 +101,26 @@ module API optional :prerelease, type: ::Grape::API::Boolean, desc: 'Include prerelease versions', default: true end namespace '/query' do - before do - authorize_read_package!(authorized_user_project) + after_validation do + authorize_read_package!(project_or_group) 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 + + results = search_packages(params[:q], search_options) track_package_event('search_package', :nuget, category: 'API::NugetPackages') - present ::Packages::Nuget::SearchResultsPresenter.new(search), - with: ::API::Entities::Nuget::SearchResults + present ::Packages::Nuget::SearchResultsPresenter.new(results), + with: ::API::Entities::Nuget::SearchResults end end end diff --git a/lib/api/debian_project_packages.rb b/lib/api/debian_project_packages.rb index bcb4e8c8cbc..f8129c18dff 100644 --- a/lib/api/debian_project_packages.rb +++ b/lib/api/debian_project_packages.rb @@ -21,6 +21,8 @@ module API end namespace 'incoming/:file_name', requirements: FILE_NAME_REQUIREMENTS do + content_type :json, Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE + # PUT {projects|groups}/:id/-/packages/debian/incoming/:file_name params do requires :file, type: ::API::Validations::Types::WorkhorseFile, desc: 'The package file to be published (generated by Multipart middleware)' @@ -42,10 +44,9 @@ module API # PUT {projects|groups}/:id/-/packages/debian/incoming/:file_name/authorize route_setting :authentication, deploy_token_allowed: true, basic_auth_personal_access_token: true, job_token_allowed: :basic_auth - post 'authorize' do + put 'authorize' do authorize_workhorse!( subject: authorized_user_project, - has_length: false, maximum_size: authorized_user_project.actual_limits.debian_max_file_size ) end diff --git a/lib/api/entities/basic_repository_storage_move.rb b/lib/api/entities/basic_repository_storage_move.rb new file mode 100644 index 00000000000..3ee112fb9a2 --- /dev/null +++ b/lib/api/entities/basic_repository_storage_move.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +module API + module Entities + class BasicRepositoryStorageMove < Grape::Entity + expose :id + expose :created_at + expose :human_state_name, as: :state + expose :source_storage_name + expose :destination_storage_name + end + end +end diff --git a/lib/api/entities/basic_snippet.rb b/lib/api/entities/basic_snippet.rb new file mode 100644 index 00000000000..26297514798 --- /dev/null +++ b/lib/api/entities/basic_snippet.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +module API + module Entities + class BasicSnippet < Grape::Entity + expose :id, :title, :description, :visibility + expose :updated_at, :created_at + expose :project_id + expose :web_url do |snippet| + Gitlab::UrlBuilder.build(snippet) + end + expose :raw_url do |snippet| + Gitlab::UrlBuilder.build(snippet, raw: true) + end + expose :ssh_url_to_repo, :http_url_to_repo, if: ->(snippet) { snippet.repository_exists? } + end + end +end diff --git a/lib/api/entities/board.rb b/lib/api/entities/board.rb index b7a50408313..fe0182ad772 100644 --- a/lib/api/entities/board.rb +++ b/lib/api/entities/board.rb @@ -5,6 +5,8 @@ module API class Board < Grape::Entity expose :id expose :name + expose :hide_backlog_list + expose :hide_closed_list expose :project, using: Entities::BasicProjectDetails expose :lists, using: Entities::List do |board| diff --git a/lib/api/entities/note.rb b/lib/api/entities/note.rb index 9a60c04220d..a597aa7bb4a 100644 --- a/lib/api/entities/note.rb +++ b/lib/api/entities/note.rb @@ -23,6 +23,7 @@ module API expose :resolvable?, as: :resolvable expose :resolved?, as: :resolved, if: ->(note, options) { note.resolvable? } expose :resolved_by, using: Entities::UserBasic, if: ->(note, options) { note.resolvable? } + expose :resolved_at, if: ->(note, options) { note.resolvable? } expose :confidential?, as: :confidential diff --git a/lib/api/entities/project.rb b/lib/api/entities/project.rb index 317caefe0a1..6ad6123a20e 100644 --- a/lib/api/entities/project.rb +++ b/lib/api/entities/project.rb @@ -100,6 +100,7 @@ module API end expose :only_allow_merge_if_pipeline_succeeds expose :allow_merge_on_skipped_pipeline + expose :restrict_user_defined_variables expose :request_access_enabled expose :only_allow_merge_if_all_discussions_are_resolved expose :remove_source_branch_after_merge diff --git a/lib/api/entities/project_repository_storage_move.rb b/lib/api/entities/project_repository_storage_move.rb index 25643651a14..191bbaf19d7 100644 --- a/lib/api/entities/project_repository_storage_move.rb +++ b/lib/api/entities/project_repository_storage_move.rb @@ -2,12 +2,7 @@ module API module Entities - class ProjectRepositoryStorageMove < Grape::Entity - expose :id - expose :created_at - expose :human_state_name, as: :state - expose :source_storage_name - expose :destination_storage_name + class ProjectRepositoryStorageMove < BasicRepositoryStorageMove expose :project, using: Entities::ProjectIdentity end end diff --git a/lib/api/entities/release.rb b/lib/api/entities/release.rb index 44a46c5861e..f6c3dd5a509 100644 --- a/lib/api/entities/release.rb +++ b/lib/api/entities/release.rb @@ -16,7 +16,12 @@ module API expose :author, using: Entities::UserBasic, if: -> (release, _) { release.author.present? } expose :commit, using: Entities::Commit, if: ->(_, _) { can_download_code? } expose :upcoming_release?, as: :upcoming_release - expose :milestones, using: Entities::MilestoneWithStats, if: -> (release, _) { release.milestones.present? && can_read_milestone? } + expose :milestones, + using: Entities::MilestoneWithStats, + if: -> (release, _) { release.milestones.present? && can_read_milestone? } do |release, _| + release.milestones.order_by_dates_and_title + end + expose :commit_path, expose_nil: false expose :tag_path, expose_nil: false diff --git a/lib/api/entities/snippet.rb b/lib/api/entities/snippet.rb index 85148c03d18..f05e593a302 100644 --- a/lib/api/entities/snippet.rb +++ b/lib/api/entities/snippet.rb @@ -2,18 +2,8 @@ module API module Entities - class Snippet < Grape::Entity - expose :id, :title, :description, :visibility + class Snippet < BasicSnippet expose :author, using: Entities::UserBasic - expose :updated_at, :created_at - expose :project_id - expose :web_url do |snippet| - Gitlab::UrlBuilder.build(snippet) - end - expose :raw_url do |snippet| - Gitlab::UrlBuilder.build(snippet, raw: true) - end - expose :ssh_url_to_repo, :http_url_to_repo, if: ->(snippet) { snippet.repository_exists? } expose :file_name do |snippet| snippet.file_name_on_repo || snippet.file_name end diff --git a/lib/api/entities/snippet_repository_storage_move.rb b/lib/api/entities/snippet_repository_storage_move.rb new file mode 100644 index 00000000000..ee86816bd14 --- /dev/null +++ b/lib/api/entities/snippet_repository_storage_move.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +module API + module Entities + class SnippetRepositoryStorageMove < BasicRepositoryStorageMove + expose :snippet, using: Entities::BasicSnippet + end + end +end diff --git a/lib/api/generic_packages.rb b/lib/api/generic_packages.rb index 3e1dd044c8d..167531fdaec 100644 --- a/lib/api/generic_packages.rb +++ b/lib/api/generic_packages.rb @@ -21,7 +21,7 @@ module API end resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do - route_setting :authentication, job_token_allowed: true + route_setting :authentication, job_token_allowed: true, basic_auth_personal_access_token: true, deploy_token_allowed: true namespace ':id/packages/generic' do namespace ':package_name/*package_version/:file_name', requirements: GENERIC_PACKAGES_REQUIREMENTS do @@ -29,7 +29,7 @@ module API detail 'This feature was introduced in GitLab 13.5' end - route_setting :authentication, job_token_allowed: true + route_setting :authentication, job_token_allowed: true, basic_auth_personal_access_token: true, deploy_token_allowed: true params do requires :package_name, type: String, desc: 'Package name', regexp: Gitlab::Regex.generic_package_name_regex, file_path: true @@ -52,7 +52,7 @@ module API 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 + route_setting :authentication, job_token_allowed: true, basic_auth_personal_access_token: true, deploy_token_allowed: true put do authorize_upload!(project) @@ -82,7 +82,7 @@ module API requires :file_name, type: String, desc: 'Package file name', regexp: Gitlab::Regex.generic_package_file_name_regex, file_path: true end - route_setting :authentication, job_token_allowed: true + route_setting :authentication, job_token_allowed: true, basic_auth_personal_access_token: true, deploy_token_allowed: true get do authorize_read_package!(project) diff --git a/lib/api/group_boards.rb b/lib/api/group_boards.rb index 2bfd98a5b69..7425e1bd145 100644 --- a/lib/api/group_boards.rb +++ b/lib/api/group_boards.rb @@ -9,9 +9,7 @@ module API feature_category :boards - before do - authenticate! - end + before { authenticate! } helpers do def board_parent @@ -22,28 +20,40 @@ module API params do requires :id, type: String, desc: 'The ID of a group' end - resource :groups, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do segment ':id/boards' do + desc 'Get all group boards' do + detail 'This feature was introduced in 10.6' + success Entities::Board + end + params do + use :pagination + end + get '/' do + authorize!(:read_board, user_group) + present paginate(board_parent.boards.with_associations), with: Entities::Board + end + desc 'Find a group board' do detail 'This feature was introduced in 10.6' - success ::API::Entities::Board + success Entities::Board end get '/:board_id' do authorize!(:read_board, user_group) - present board, with: ::API::Entities::Board + present board, with: Entities::Board end - desc 'Get all group boards' do - detail 'This feature was introduced in 10.6' + desc 'Update a group board' do + detail 'This feature was introduced in 11.0' success Entities::Board end params do - use :pagination + use :update_params end - get '/' do - authorize!(:read_board, user_group) - present paginate(board_parent.boards.with_associations), with: Entities::Board + put '/:board_id' do + authorize!(:admin_board, board_parent) + + update_board end end diff --git a/lib/api/group_packages.rb b/lib/api/group_packages.rb index 31b28c3990f..d482f4d0585 100644 --- a/lib/api/group_packages.rb +++ b/lib/api/group_packages.rb @@ -31,12 +31,14 @@ module API desc: 'Return packages of a certain type' optional :package_name, type: String, desc: 'Return packages with this name' + optional :include_versionless, type: Boolean, + desc: 'Returns packages without a version' end get ':id/packages' do packages = Packages::GroupPackagesFinder.new( current_user, user_group, - declared(params).slice(:exclude_subgroups, :order_by, :sort, :package_type, :package_name) + declared(params).slice(:exclude_subgroups, :order_by, :sort, :package_type, :package_name, :include_versionless) ).execute present paginate(packages), with: ::API::Entities::Package, user: current_user, group: true diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb index 6fe25471289..79af9c37378 100644 --- a/lib/api/helpers.rb +++ b/lib/api/helpers.rb @@ -220,6 +220,10 @@ module API user_project.builds.find(id.to_i) end + def find_job!(id) + user_project.processables.find(id.to_i) + end + def authenticate! unauthorized! unless current_user end @@ -275,6 +279,10 @@ module API authorize! :read_build_trace, build end + def authorize_read_job_artifacts!(build) + authorize! :read_job_artifacts, build + end + def authorize_destroy_artifacts! authorize! :destroy_artifacts, user_project end @@ -364,7 +372,7 @@ module API def forbidden!(reason = nil) message = ['403 Forbidden'] - message << " - #{reason}" if reason + message << "- #{reason}" if reason render_api_error!(message.join(' '), 403) end @@ -513,7 +521,7 @@ module API case headers['X-Sendfile-Type'] when 'X-Sendfile' header['X-Sendfile'] = path - body + body '' # to avoid an error from API::APIGuard::ResponseCoercerMiddleware else sendfile path end @@ -529,7 +537,7 @@ module API else header(*Gitlab::Workhorse.send_url(file.url)) status :ok - body "" + body '' # to avoid an error from API::APIGuard::ResponseCoercerMiddleware end end @@ -562,7 +570,7 @@ module API return unless Feature.enabled?(feature_flag, default_enabled: true) - Gitlab::UsageDataCounters::HLLRedisCounter.track_event(values, event_name) + Gitlab::UsageDataCounters::HLLRedisCounter.track_event(event_name, values: values) rescue => error Gitlab::AppLogger.warn("Redis tracking event failed for event: #{event_name}, message: #{error.message}") end diff --git a/lib/api/helpers/authentication.rb b/lib/api/helpers/authentication.rb new file mode 100644 index 00000000000..a6cfe930190 --- /dev/null +++ b/lib/api/helpers/authentication.rb @@ -0,0 +1,73 @@ +# frozen_string_literal: true + +module API + module Helpers + module Authentication + extend ActiveSupport::Concern + + class_methods do + def authenticate_with(&block) + strategies = ::Gitlab::APIAuthentication::Builder.new.build(&block) + namespace_inheritable :authentication, strategies + end + end + + included do + helpers ::Gitlab::Utils::StrongMemoize + + helpers do + def token_from_namespace_inheritable + strong_memoize(:token_from_namespace_inheritable) do + strategies = namespace_inheritable(:authentication) + next unless strategies&.any? + + # Extract credentials from the request + found = strategies.to_h { |location, _| [location, ::Gitlab::APIAuthentication::TokenLocator.new(location).extract(current_request)] } + found.filter! { |location, raw| raw } + next unless found.any? + + # Specifying multiple credentials is an error + # https://gitlab.com/gitlab-org/gitlab/-/merge_requests/38627#note_475984136 + bad_request!('Found more than one set of credentials') if found.size > 1 + + location, raw = found.first + find_token_from_raw_credentials(strategies[location], raw) + end + + rescue ::Gitlab::Auth::UnauthorizedError + # TODO: this should be rescued and converted by the exception handling middleware + # https://gitlab.com/gitlab-org/gitlab/-/merge_requests/38627#note_475174516 + unauthorized! + end + + def access_token_from_namespace_inheritable + token = token_from_namespace_inheritable + token if token.is_a? PersonalAccessToken + end + + def user_from_namespace_inheritable + token = token_from_namespace_inheritable + return token if token.is_a? DeployToken + + token&.user + end + + private + + def find_token_from_raw_credentials(token_types, raw) + token_types.each do |token_type| + # Resolve a token from the raw credentials + token = ::Gitlab::APIAuthentication::TokenResolver.new(token_type).resolve(raw) + return token if token + end + + # If a request provides credentials via an allowed transport, the + # credentials must be valid. If we reach this point, the credentials + # must not be valid credentials of an allowed type. + raise ::Gitlab::Auth::UnauthorizedError + end + end + end + end + end +end diff --git a/lib/api/helpers/merge_requests_helpers.rb b/lib/api/helpers/merge_requests_helpers.rb index 9b38eeb1e72..f8fe40f7135 100644 --- a/lib/api/helpers/merge_requests_helpers.rb +++ b/lib/api/helpers/merge_requests_helpers.rb @@ -21,6 +21,9 @@ module API coerce_with: Validations::Validators::CheckAssigneesCount.coerce, desc: 'Return merge requests which are assigned to the user with the given username' mutually_exclusive :assignee_id, :assignee_username + optional :reviewer_username, + type: String, + desc: 'Return merge requests which have the user as a reviewer with the given username' optional :labels, type: Array[String], @@ -32,6 +35,11 @@ module API params :merge_requests_base_params do use :merge_requests_negatable_params + optional :reviewer_id, + types: [Integer, String], + integer_none_any: true, + desc: 'Return merge requests which have the user as a reviewer with the given ID' + mutually_exclusive :reviewer_id, :reviewer_username optional :state, type: String, values: %w[opened closed locked merged all], @@ -72,6 +80,10 @@ module API optional :wip, type: String, values: %w[yes no], desc: 'Search merge requests for WIP in the title' optional :not, type: Hash, desc: 'Parameters to negate' do use :merge_requests_negatable_params + optional :reviewer_id, + types: Integer, + desc: 'Return merge requests which have the user as a reviewer with the given ID' + mutually_exclusive :reviewer_id, :reviewer_username end optional :deployed_before, diff --git a/lib/api/helpers/packages/basic_auth_helpers.rb b/lib/api/helpers/packages/basic_auth_helpers.rb index 0784efc11d6..c32ce199dd6 100644 --- a/lib/api/helpers/packages/basic_auth_helpers.rb +++ b/lib/api/helpers/packages/basic_auth_helpers.rb @@ -12,6 +12,7 @@ module API end include Constants + include Gitlab::Utils::StrongMemoize def unauthorized_user_project @unauthorized_user_project ||= find_project(params[:id]) @@ -35,6 +36,18 @@ module API project end + def find_authorized_group! + strong_memoize(:authorized_group) do + group = find_group(params[:id]) + + unless group && can?(current_user, :read_group, group) + next unauthorized_or! { not_found! } + end + + group + end + end + def authorize!(action, subject = :global, reason = nil) return if can?(current_user, action, subject) diff --git a/lib/api/helpers/pagination.rb b/lib/api/helpers/pagination.rb index 227aec224e5..48618e7d26d 100644 --- a/lib/api/helpers/pagination.rb +++ b/lib/api/helpers/pagination.rb @@ -3,8 +3,8 @@ module API module Helpers module Pagination - def paginate(*args) - Gitlab::Pagination::OffsetPagination.new(self).paginate(*args) + def paginate(*args, **kwargs) + Gitlab::Pagination::OffsetPagination.new(self).paginate(*args, **kwargs) end end end diff --git a/lib/api/helpers/projects_helpers.rb b/lib/api/helpers/projects_helpers.rb index f5f45cf7351..cf2bcace33b 100644 --- a/lib/api/helpers/projects_helpers.rb +++ b/lib/api/helpers/projects_helpers.rb @@ -87,6 +87,7 @@ module API params :optional_update_params_ce do optional :ci_forward_deployment_enabled, type: Boolean, desc: 'Skip older deployment jobs that are still pending' + optional :restrict_user_defined_variables, type: Boolean, desc: 'Restrict use of user-defined variables when triggering a pipeline' end params :optional_update_params_ee do @@ -99,7 +100,7 @@ module API params :optional_container_expiration_policy_params do optional :cadence, type: String, desc: 'Container expiration policy cadence for recurring job' - optional :keep_n, type: String, desc: 'Container expiration policy number of images to keep' + optional :keep_n, type: Integer, desc: 'Container expiration policy number of images to keep' optional :older_than, type: String, desc: 'Container expiration policy remove images older than value' optional :name_regex, type: String, desc: 'Container expiration policy regex for image removal' optional :name_regex_keep, type: String, desc: 'Container expiration policy regex for image retention' @@ -141,6 +142,7 @@ module API :repository_access_level, :request_access_enabled, :resolve_outdated_diff_discussions, + :restrict_user_defined_variables, :shared_runners_enabled, :snippets_access_level, :tag_list, diff --git a/lib/api/helpers/services_helpers.rb b/lib/api/helpers/services_helpers.rb index 9d2fd9978d9..6101a8d307e 100644 --- a/lib/api/helpers/services_helpers.rb +++ b/lib/api/helpers/services_helpers.rb @@ -161,7 +161,6 @@ module API def self.services { - 'alerts' => [], 'asana' => [ { required: true, @@ -807,7 +806,6 @@ module API def self.service_classes [ - ::AlertsService, ::AsanaService, ::AssemblaService, ::BambooService, diff --git a/lib/api/internal/base.rb b/lib/api/internal/base.rb index 332f2f1986f..12bb6e77c3e 100644 --- a/lib/api/internal/base.rb +++ b/lib/api/internal/base.rb @@ -16,6 +16,7 @@ module API user: -> { actor&.user }, project: -> { project }, caller_id: route.origin, + remote_ip: request.ip, feature_category: feature_category ) end diff --git a/lib/api/invitations.rb b/lib/api/invitations.rb index be8147908e9..2ab1f97afe6 100644 --- a/lib/api/invitations.rb +++ b/lib/api/invitations.rb @@ -48,6 +48,24 @@ module API present_member_invitations invitations end + + desc 'Removes an invitation from a group or project.' + params do + requires :email, type: String, desc: 'The email address of the invitation' + end + delete ":id/invitations/:email", requirements: { email: /[^\/]+/ } do + source = find_source(source_type, params[:id]) + invite_email = params[:email] + authorize_admin_source!(source_type, source) + + invite = retrieve_member_invitations(source, invite_email).first + not_found! unless invite + + destroy_conditionally!(invite) do + ::Members::DestroyService.new(current_user, params).execute(invite) + unprocessable_entity! unless invite.destroyed? + end + end end end end diff --git a/lib/api/job_artifacts.rb b/lib/api/job_artifacts.rb index 1faa28d6f07..28737f61f61 100644 --- a/lib/api/job_artifacts.rb +++ b/lib/api/job_artifacts.rb @@ -32,6 +32,7 @@ module API authorize_download_artifacts! latest_build = user_project.latest_successful_build_for_ref!(params[:job], params[:ref_name]) + authorize_read_job_artifacts!(latest_build) present_carrierwave_file!(latest_build.artifacts_file) end @@ -50,6 +51,7 @@ module API authorize_download_artifacts! build = user_project.latest_successful_build_for_ref!(params[:job], params[:ref_name]) + authorize_read_job_artifacts!(build) path = Gitlab::Ci::Build::Artifacts::Path .new(params[:artifact_path]) @@ -70,6 +72,7 @@ module API authorize_download_artifacts! build = find_build!(params[:job_id]) + authorize_read_job_artifacts!(build) present_carrierwave_file!(build.artifacts_file) end @@ -82,9 +85,11 @@ module API requires :artifact_path, type: String, desc: 'Artifact path' end get ':id/jobs/:job_id/artifacts/*artifact_path', format: false do - authorize_read_builds! + authorize_download_artifacts! build = find_build!(params[:job_id]) + authorize_read_job_artifacts!(build) + not_found! unless build.artifacts? path = Gitlab::Ci::Build::Artifacts::Path diff --git a/lib/api/jobs.rb b/lib/api/jobs.rb index 44751b3d76c..e14a4a5e680 100644 --- a/lib/api/jobs.rb +++ b/lib/api/jobs.rb @@ -138,25 +138,32 @@ module API present build, with: Entities::Ci::Job end - desc 'Trigger a actionable job (manual, delayed, etc)' do - success Entities::Ci::Job + desc 'Trigger an actionable job (manual, delayed, etc)' do + success Entities::Ci::JobBasic detail 'This feature was added in GitLab 8.11' end params do requires :job_id, type: Integer, desc: 'The ID of a Job' end + post ":id/jobs/:job_id/play" do authorize_read_builds! - build = find_build!(params[:job_id]) + job = find_job!(params[:job_id]) - authorize!(:update_build, build) - bad_request!("Unplayable Job") unless build.playable? + authorize!(:play_job, job) - build.play(current_user) + bad_request!("Unplayable Job") unless job.playable? + + job.play(current_user) status 200 - present build, with: Entities::Ci::Job + + if job.is_a?(::Ci::Build) + present job, with: Entities::Ci::Job + else + present job, with: Entities::Ci::Bridge + end end end diff --git a/lib/api/lint.rb b/lib/api/lint.rb index 58181adaa93..f1f34622187 100644 --- a/lib/api/lint.rb +++ b/lib/api/lint.rb @@ -12,14 +12,13 @@ module API end post '/lint' do result = Gitlab::Ci::YamlProcessor.new(params[:content], user: current_user).execute - error = result.errors.first status 200 - response = if error.blank? + response = if result.errors.empty? { status: 'valid', errors: [], warnings: result.warnings } else - { status: 'invalid', errors: [error], warnings: result.warnings } + { status: 'invalid', errors: result.errors, warnings: result.warnings } end response.tap do |response| diff --git a/lib/api/maven_packages.rb b/lib/api/maven_packages.rb index 7b4e52d18e8..4a5b2ead163 100644 --- a/lib/api/maven_packages.rb +++ b/lib/api/maven_packages.rb @@ -220,9 +220,13 @@ module API file_name, format = extract_format(params[:file_name]) - package = ::Packages::Maven::FindOrCreatePackageService + result = ::Packages::Maven::FindOrCreatePackageService .new(user_project, current_user, params.merge(build: current_authenticated_job)).execute + bad_request!(result.errors.first) if result.error? + + package = result.payload[:package] + case format when 'sha1' # After uploading a file, Maven tries to upload a sha1 and md5 version of it. diff --git a/lib/api/nuget_group_packages.rb b/lib/api/nuget_group_packages.rb new file mode 100644 index 00000000000..e373f051b24 --- /dev/null +++ b/lib/api/nuget_group_packages.rb @@ -0,0 +1,58 @@ +# 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`. +# +# This is the group level API. +module API + class NugetGroupPackages < ::API::Base + helpers ::API::Helpers::PackagesHelpers + helpers ::API::Helpers::Packages::BasicAuthHelpers + include ::API::Helpers::Authentication + + feature_category :package_registry + + default_format :json + + authenticate_with do |accept| + accept.token_types(:personal_access_token, :deploy_token, :job_token) + .sent_through(:http_basic_auth) + end + + rescue_from ArgumentError do |e| + render_api_error!(e.message, 400) + end + + after_validation do + require_packages_enabled! + end + + helpers do + def project_or_group + find_authorized_group! + end + + def require_authenticated! + unauthorized! unless current_user + end + end + + params do + requires :id, type: String, desc: 'The ID of a group', regexp: ::API::Concerns::Packages::NugetEndpoints::POSITIVE_INTEGER_REGEX + end + + resource :groups, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do + namespace ':id/-/packages/nuget' do + after_validation do + # This API can't be accessed anonymously + require_authenticated! + end + + include ::API::Concerns::Packages::NugetEndpoints + end + end + end +end diff --git a/lib/api/nuget_project_packages.rb b/lib/api/nuget_project_packages.rb index b2516cc91f8..2146f4d4b78 100644 --- a/lib/api/nuget_project_packages.rb +++ b/lib/api/nuget_project_packages.rb @@ -5,10 +5,13 @@ # 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`. +# +# This is the project level API. module API class NugetProjectPackages < ::API::Base - helpers ::API::Helpers::PackagesManagerClientsHelpers + helpers ::API::Helpers::PackagesHelpers helpers ::API::Helpers::Packages::BasicAuthHelpers + include ::API::Helpers::Authentication feature_category :package_registry @@ -16,25 +19,29 @@ module API default_format :json + authenticate_with do |accept| + accept.token_types(:personal_access_token, :deploy_token, :job_token) + .sent_through(:http_basic_auth) + end + rescue_from ArgumentError do |e| render_api_error!(e.message, 400) end - before do + after_validation do require_packages_enabled! end + helpers do + def project_or_group + authorized_user_project + end + 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 @@ -46,28 +53,20 @@ module API 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) + authorize_upload!(project_or_group) + bad_request!('File is too large') if project_or_group.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 = ::Packages::Nuget::CreatePackageService.new(project_or_group, current_user, declared_params.merge(build: current_authenticated_job)) + .execute - package_file = ::Packages::CreatePackageFileService.new( - package, - file_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') @@ -75,18 +74,15 @@ module API created! rescue ObjectStorage::RemoteStoreError => e - Gitlab::ErrorTracking.track_exception(e, extra: { file_name: params[:file_name], project_id: authorized_user_project.id }) + Gitlab::ErrorTracking.track_exception(e, extra: { file_name: params[:file_name], project_id: project_or_group.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, + subject: project_or_group, has_length: false, - maximum_size: authorized_user_project.actual_limits.nuget_max_file_size + maximum_size: project_or_group.actual_limits.nuget_max_file_size ) end @@ -95,18 +91,15 @@ module API 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) + after_validation do + authorize_read_package!(project_or_group) end desc 'The NuGet Content Service - index request' do detail 'This feature was introduced in GitLab 12.8' 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), + present ::Packages::Nuget::PackagesVersionsPresenter.new(find_packages(params[:package_name])), with: ::API::Entities::Nuget::PackagesVersions end @@ -117,12 +110,9 @@ module API 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) + package_file = ::Packages::PackageFileFinder.new(find_package(params[:package_name], params[:package_version]), filename, with_file_name_like: true) .execute not_found!('Package') unless package_file diff --git a/lib/api/project_packages.rb b/lib/api/project_packages.rb index 56e94333433..32636662987 100644 --- a/lib/api/project_packages.rb +++ b/lib/api/project_packages.rb @@ -30,11 +30,13 @@ module API desc: 'Return packages of a certain type' optional :package_name, type: String, desc: 'Return packages with this name' + optional :include_versionless, type: Boolean, + desc: 'Returns packages without a version' end get ':id/packages' do packages = ::Packages::PackagesFinder.new( user_project, - declared_params.slice(:order_by, :sort, :package_type, :package_name) + declared_params.slice(:order_by, :sort, :package_type, :package_name, :include_versionless) ).execute present paginate(packages), with: ::API::Entities::Package, user: current_user diff --git a/lib/api/project_templates.rb b/lib/api/project_templates.rb index af5d96969ef..19244ed697f 100644 --- a/lib/api/project_templates.rb +++ b/lib/api/project_templates.rb @@ -4,7 +4,7 @@ module API class ProjectTemplates < ::API::Base include PaginationParams - TEMPLATE_TYPES = %w[dockerfiles gitignores gitlab_ci_ymls licenses metrics_dashboard_ymls issues merge_requests].freeze + TEMPLATE_TYPES = %w[dockerfiles gitignores gitlab_ci_ymls gitlab_ci_syntax_ymls licenses metrics_dashboard_ymls issues merge_requests].freeze # The regex is needed to ensure a period (e.g. agpl-3.0) # isn't confused with a format type. We also need to allow encoded # values (e.g. C%2B%2B for C++), so allow % and + as well. @@ -16,7 +16,7 @@ module API params do requires :id, type: String, desc: 'The ID of a project' - requires :type, type: String, values: TEMPLATE_TYPES, desc: 'The type (dockerfiles|gitignores|gitlab_ci_ymls|licenses|metrics_dashboard_ymls|issues|merge_requests) of the template' + requires :type, type: String, values: TEMPLATE_TYPES, desc: 'The type (dockerfiles|gitignores|gitlab_ci_ymls|gitlab_ci_syntax_ymls|licenses|metrics_dashboard_ymls|issues|merge_requests) of the template' end resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do desc 'Get a list of templates available to this project' do diff --git a/lib/api/projects.rb b/lib/api/projects.rb index 2012c348cd1..2d09ad01757 100644 --- a/lib/api/projects.rb +++ b/lib/api/projects.rb @@ -566,8 +566,8 @@ module API authorize_admin_project begin - ::Projects::HousekeepingService.new(user_project, :gc).execute - rescue ::Projects::HousekeepingService::LeaseTaken => error + ::Repositories::HousekeepingService.new(user_project, :gc).execute + rescue ::Repositories::HousekeepingService::LeaseTaken => error conflict!(error.message) end end diff --git a/lib/api/settings.rb b/lib/api/settings.rb index b3f09b431b0..f329a94adf2 100644 --- a/lib/api/settings.rb +++ b/lib/api/settings.rb @@ -91,6 +91,7 @@ module API optional :import_sources, type: Array[String], coerce_with: Validations::Types::CommaSeparatedToArray.coerce, values: %w[github bitbucket bitbucket_server gitlab google_code fogbugz git gitlab_project gitea manifest phabricator], desc: 'Enabled sources for code import during project creation. OmniAuth must be configured for GitHub, Bitbucket, and GitLab.com' + optional :invisible_captcha_enabled, type: Boolean, desc: 'Enable Invisible Captcha spam detection during signup.' optional :max_artifacts_size, type: Integer, desc: "Set the maximum file size for each job's artifacts" optional :max_attachment_size, type: Integer, desc: 'Maximum attachment size in MB' optional :max_import_size, type: Integer, desc: 'Maximum import size in MB' diff --git a/lib/api/snippet_repository_storage_moves.rb b/lib/api/snippet_repository_storage_moves.rb new file mode 100644 index 00000000000..1a5b41eb1ec --- /dev/null +++ b/lib/api/snippet_repository_storage_moves.rb @@ -0,0 +1,110 @@ +# frozen_string_literal: true + +module API + class SnippetRepositoryStorageMoves < ::API::Base + include PaginationParams + + before { authenticated_as_admin! } + + feature_category :gitaly + + resource :snippet_repository_storage_moves do + desc 'Get a list of all snippet repository storage moves' do + detail 'This feature was introduced in GitLab 13.8.' + success Entities::SnippetRepositoryStorageMove + end + params do + use :pagination + end + get do + storage_moves = SnippetRepositoryStorageMove.order_created_at_desc + + present paginate(storage_moves), with: Entities::SnippetRepositoryStorageMove, current_user: current_user + end + + desc 'Get a snippet repository storage move' do + detail 'This feature was introduced in GitLab 13.8.' + success Entities::SnippetRepositoryStorageMove + end + params do + requires :repository_storage_move_id, type: Integer, desc: 'The ID of a snippet repository storage move' + end + get ':repository_storage_move_id' do + storage_move = SnippetRepositoryStorageMove.find(params[:repository_storage_move_id]) + + present storage_move, with: Entities::SnippetRepositoryStorageMove, current_user: current_user + end + + desc 'Schedule bulk snippet repository storage moves' do + detail 'This feature was introduced in GitLab 13.8.' + 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 + ::Snippets::ScheduleBulkRepositoryShardMovesService.enqueue( + declared_params[:source_storage_name], + declared_params[:destination_storage_name] + ) + + accepted! + end + end + + params do + requires :id, type: String, desc: 'The ID of a snippet' + end + resource :snippets do + helpers do + def user_snippet + Snippet.find_by(id: params[:id]) # rubocop: disable CodeReuse/ActiveRecord + end + end + desc 'Get a list of all snippets repository storage moves' do + detail 'This feature was introduced in GitLab 13.8.' + success Entities::SnippetRepositoryStorageMove + end + params do + use :pagination + end + get ':id/repository_storage_moves' do + storage_moves = user_snippet.repository_storage_moves.order_created_at_desc + + present paginate(storage_moves), with: Entities::SnippetRepositoryStorageMove, current_user: current_user + end + + desc 'Get a snippet repository storage move' do + detail 'This feature was introduced in GitLab 13.8.' + success Entities::SnippetRepositoryStorageMove + end + params do + requires :repository_storage_move_id, type: Integer, desc: 'The ID of a snippet repository storage move' + end + get ':id/repository_storage_moves/:repository_storage_move_id' do + storage_move = user_snippet.repository_storage_moves.find(params[:repository_storage_move_id]) + + present storage_move, with: Entities::SnippetRepositoryStorageMove, current_user: current_user + end + + desc 'Schedule a snippet repository storage move' do + detail 'This feature was introduced in GitLab 13.8.' + success Entities::SnippetRepositoryStorageMove + end + params do + optional :destination_storage_name, type: String, desc: 'The destination storage shard' + end + post ':id/repository_storage_moves' do + storage_move = user_snippet.repository_storage_moves.build( + declared_params.merge(source_storage_name: user_snippet.repository_storage) + ) + + if storage_move.schedule + present storage_move, with: Entities::SnippetRepositoryStorageMove, current_user: current_user + else + render_validation_error!(storage_move) + end + end + end + end +end diff --git a/lib/api/templates.rb b/lib/api/templates.rb index b7fb35eac03..bc1e427bcaa 100644 --- a/lib/api/templates.rb +++ b/lib/api/templates.rb @@ -13,6 +13,9 @@ module API gitlab_ci_ymls: { gitlab_version: 8.9 }, + gitlab_ci_syntax_ymls: { + gitlab_version: 13.8 + }, dockerfiles: { gitlab_version: 8.15 } diff --git a/lib/api/terraform/state.rb b/lib/api/terraform/state.rb index c664c0a4590..f6dfbcafbb6 100644 --- a/lib/api/terraform/state.rb +++ b/lib/api/terraform/state.rb @@ -14,6 +14,8 @@ module API before do authenticate! authorize! :read_terraform_state, user_project + + increment_unique_values('p_terraform_state_api_unique_users', current_user.id) end params do diff --git a/lib/api/usage_data.rb b/lib/api/usage_data.rb index cad2f52e951..c7d63f8d6ac 100644 --- a/lib/api/usage_data.rb +++ b/lib/api/usage_data.rb @@ -4,7 +4,7 @@ module API class UsageData < ::API::Base before { authenticate! } - feature_category :collection + feature_category :usage_ping namespace 'usage_data' do before do diff --git a/lib/api/user_counts.rb b/lib/api/user_counts.rb index 3071f08e1de..31c923a219a 100644 --- a/lib/api/user_counts.rb +++ b/lib/api/user_counts.rb @@ -12,7 +12,9 @@ module API unauthorized! unless current_user { - merge_requests: current_user.assigned_open_merge_requests_count + merge_requests: current_user.assigned_open_merge_requests_count, # @deprecated + assigned_merge_requests: current_user.assigned_open_merge_requests_count, + review_requested_merge_requests: current_user.review_requested_open_merge_requests_count } end end diff --git a/lib/api/users.rb b/lib/api/users.rb index 8b9b82877f7..cee09f60a2b 100644 --- a/lib/api/users.rb +++ b/lib/api/users.rb @@ -87,6 +87,7 @@ module API optional :created_before, type: DateTime, desc: 'Return users created before the specified time' optional :without_projects, type: Boolean, default: false, desc: 'Filters only users without projects' optional :exclude_internal, as: :non_internal, type: Boolean, default: false, desc: 'Filters only non internal users' + optional :admins, type: Boolean, default: false, desc: 'Filters only admin users' all_or_none_of :extern_uid, :provider use :sort_params @@ -745,8 +746,6 @@ module API optional :expires_at, type: Date, desc: 'The expiration date in the format YEAR-MONTH-DAY of the personal access token' end post feature_category: :authentication_and_authorization do - not_found! unless Feature.enabled?(:pat_creation_api_for_admin) - response = ::PersonalAccessTokens::CreateService.new( current_user: current_user, target_user: target_user, params: declared_params(include_missing: false) ).execute |