diff options
Diffstat (limited to 'lib/api')
108 files changed, 1002 insertions, 460 deletions
diff --git a/lib/api/api.rb b/lib/api/api.rb index a287ffbfcd8..54e5cc5c8d0 100644 --- a/lib/api/api.rb +++ b/lib/api/api.rb @@ -130,32 +130,6 @@ module API formatter :json, Gitlab::Json::GrapeFormatter content_type :json, 'application/json' - # Remove the `text/plain+deprecated` with `api_always_use_application_json` feature flag - # There is a small chance some users depend on the old behavior. - # We this change under a feature flag to see if affects GitLab.com users. - # The `+deprecated` is added to distinguish content type - # as defined by `API::API` vs ex. `API::Repositories` - content_type :txt, 'text/plain+deprecated' - - before do - # the feature flag workaround is only for `.txt` - api_format = env[Grape::Env::API_FORMAT] - next unless api_format == :txt - - # get all defined content-types for the endpoint - api_endpoint = env[Grape::Env::API_ENDPOINT] - content_types = api_endpoint&.namespace_stackable_with_hash(:content_types).to_h - - # Only overwrite `text/plain+deprecated` - if content_types[api_format] == 'text/plain+deprecated' - if Feature.enabled?(:api_always_use_application_json, default_enabled: :yaml) - content_type 'application/json' - else - content_type 'text/plain' - end - end - end - # Ensure the namespace is right, otherwise we might load Grape::API::Helpers helpers ::API::Helpers helpers ::API::Helpers::CommonHelpers @@ -267,6 +241,7 @@ module API mount ::API::ProjectTemplates mount ::API::Terraform::State mount ::API::Terraform::StateVersion + mount ::API::Terraform::Modules::V1::Packages mount ::API::PersonalAccessTokens mount ::API::ProtectedBranches mount ::API::ProtectedTags @@ -323,4 +298,4 @@ module API end end -API::API.prepend_ee_mod +API::API.prepend_mod diff --git a/lib/api/award_emoji.rb b/lib/api/award_emoji.rb index 8ea4f32d3eb..c8485054377 100644 --- a/lib/api/award_emoji.rb +++ b/lib/api/award_emoji.rb @@ -4,23 +4,18 @@ module API class AwardEmoji < ::API::Base include PaginationParams + helpers ::API::Helpers::AwardEmoji + before { authenticate! } - AWARDABLES = [ - { type: 'issue', find_by: :iid, feature_category: :issue_tracking }, - { type: 'merge_request', find_by: :iid, feature_category: :code_review }, - { type: 'snippet', find_by: :id, feature_category: :snippets } - ].freeze - - params do - requires :id, type: String, desc: 'The ID of a project' - end - resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do - AWARDABLES.each do |awardable_params| + + Helpers::AwardEmoji.awardables.each do |awardable_params| + resource awardable_params[:resource], requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do awardable_string = awardable_params[:type].pluralize awardable_id_string = "#{awardable_params[:type]}_#{awardable_params[:find_by]}" params do - requires :"#{awardable_id_string}", type: Integer, desc: "The ID of an Issue, Merge Request or Snippet" + requires :id, type: String, desc: "The ID of a #{awardable_params[:resource] == :projects ? 'project' : 'group'}" + requires :"#{awardable_id_string}", type: Integer, desc: Helpers::AwardEmoji.awardable_id_desc end [ @@ -104,25 +99,6 @@ module API awardable.user_can_award?(current_user) end - # rubocop: disable CodeReuse/ActiveRecord - def awardable - @awardable ||= - begin - if params.include?(:note_id) - note_id = params.delete(:note_id) - - awardable.notes.find(note_id) - elsif params.include?(:issue_iid) - user_project.issues.find_by!(iid: params[:issue_iid]) - elsif params.include?(:merge_request_iid) - user_project.merge_requests.find_by!(iid: params[:merge_request_iid]) - else - user_project.snippets.find(params[:snippet_id]) - end - end - end - # rubocop: enable CodeReuse/ActiveRecord - def read_ability(awardable) case awardable when Note diff --git a/lib/api/boards.rb b/lib/api/boards.rb index 79f4b02f26a..9e829dd5e05 100644 --- a/lib/api/boards.rb +++ b/lib/api/boards.rb @@ -5,7 +5,7 @@ module API include BoardsResponses include PaginationParams - prepend_if_ee('EE::API::BoardsResponses') # rubocop: disable Cop/InjectEnterpriseEditionModule + prepend_mod_with('API::BoardsResponses') # rubocop: disable Cop/InjectEnterpriseEditionModule feature_category :boards diff --git a/lib/api/branches.rb b/lib/api/branches.rb index 6842e93a4de..1ee120f982a 100644 --- a/lib/api/branches.rb +++ b/lib/api/branches.rb @@ -38,22 +38,38 @@ module API optional :page_token, type: String, desc: 'Name of branch to start the paginaition from' end get ':id/repository/branches' do - user_project.preload_protected_branches - - repository = user_project.repository - - branches_finder = BranchesFinder.new(repository, declared_params(include_missing: false)) - branches = Gitlab::Pagination::GitalyKeysetPager.new(self, user_project).paginate(branches_finder) - - merged_branch_names = repository.merged_branch_names(branches.map(&:name)) - - present( - branches, - with: Entities::Branch, - current_user: current_user, - project: user_project, - merged_branch_names: merged_branch_names - ) + ff_enabled = Feature.enabled?(:api_caching_rate_limit_branches, user_project, default_enabled: :yaml) + + cache_action_if(ff_enabled, [user_project, :branches, current_user, declared_params], expires_in: 30.seconds) do + user_project.preload_protected_branches + + repository = user_project.repository + + branches_finder = BranchesFinder.new(repository, declared_params(include_missing: false)) + branches = Gitlab::Pagination::GitalyKeysetPager.new(self, user_project).paginate(branches_finder) + + merged_branch_names = repository.merged_branch_names(branches.map(&:name)) + + if Feature.enabled?(:api_caching_branches, user_project, type: :development, default_enabled: :yaml) + present_cached( + branches, + with: Entities::Branch, + current_user: current_user, + project: user_project, + merged_branch_names: merged_branch_names, + expires_in: 10.minutes, + cache_context: -> (branch) { [current_user&.cache_key, merged_branch_names.include?(branch.name)] } + ) + else + present( + branches, + with: Entities::Branch, + current_user: current_user, + project: user_project, + merged_branch_names: merged_branch_names + ) + end + end end resource ':id/repository/branches/:branch', requirements: BRANCH_ENDPOINT_REQUIREMENTS do diff --git a/lib/api/ci/runner.rb b/lib/api/ci/runner.rb index c5249f1377b..33980b38e2b 100644 --- a/lib/api/ci/runner.rb +++ b/lib/api/ci/runner.rb @@ -184,6 +184,8 @@ module API .new(job, declared_params(include_missing: false)) service.execute.then do |result| + track_ci_minutes_usage!(job, current_runner) + header 'X-GitLab-Trace-Update-Interval', result.backoff status result.status body result.status.to_s @@ -214,6 +216,8 @@ module API break error!('416 Range Not Satisfiable', 416, { 'Range' => "0-#{result.stream_size}" }) end + track_ci_minutes_usage!(job, current_runner) + status result.status header 'Job-Status', job.status header 'Range', "0-#{result.stream_size}" diff --git a/lib/api/concerns/packages/debian_endpoints.rb b/lib/api/concerns/packages/debian_endpoints.rb new file mode 100644 index 00000000000..6fc7c439464 --- /dev/null +++ b/lib/api/concerns/packages/debian_endpoints.rb @@ -0,0 +1,133 @@ +# frozen_string_literal: true + +module API + module Concerns + module Packages + module DebianEndpoints + extend ActiveSupport::Concern + + DISTRIBUTION_REGEX = %r{[a-zA-Z0-9][a-zA-Z0-9.-]*}.freeze + COMPONENT_REGEX = %r{[a-z-]+}.freeze + ARCHITECTURE_REGEX = %r{[a-z][a-z0-9]*}.freeze + LETTER_REGEX = %r{(lib)?[a-z0-9]}.freeze + PACKAGE_REGEX = API::NO_SLASH_URL_PART_REGEX + DISTRIBUTION_REQUIREMENTS = { + distribution: DISTRIBUTION_REGEX + }.freeze + COMPONENT_ARCHITECTURE_REQUIREMENTS = { + component: COMPONENT_REGEX, + architecture: ARCHITECTURE_REGEX + }.freeze + COMPONENT_LETTER_SOURCE_PACKAGE_REQUIREMENTS = { + component: COMPONENT_REGEX, + letter: LETTER_REGEX, + source_package: PACKAGE_REGEX + }.freeze + FILE_NAME_REQUIREMENTS = { + file_name: API::NO_SLASH_URL_PART_REGEX + }.freeze + + included do + feature_category :package_registry + + helpers ::API::Helpers::PackagesHelpers + helpers ::API::Helpers::Packages::BasicAuthHelpers + + format :txt + content_type :txt, 'text/plain' + + rescue_from ArgumentError do |e| + render_api_error!(e.message, 400) + end + + rescue_from ActiveRecord::RecordInvalid do |e| + render_api_error!(e.message, 400) + end + + before do + require_packages_enabled! + end + + namespace 'packages/debian' do + params do + requires :distribution, type: String, desc: 'The Debian Codename', regexp: Gitlab::Regex.debian_distribution_regex + end + + namespace 'dists/*distribution', requirements: DISTRIBUTION_REQUIREMENTS do + # GET {projects|groups}/:id/packages/debian/dists/*distribution/Release.gpg + desc 'The Release file signature' do + detail 'This feature was introduced in GitLab 13.5' + end + + route_setting :authentication, deploy_token_allowed: true, basic_auth_personal_access_token: true, job_token_allowed: :basic_auth, authenticate_non_public: true + get 'Release.gpg' do + not_found! + end + + # GET {projects|groups}/:id/packages/debian/dists/*distribution/Release + desc 'The unsigned Release file' do + detail 'This feature was introduced in GitLab 13.5' + end + + route_setting :authentication, deploy_token_allowed: true, basic_auth_personal_access_token: true, job_token_allowed: :basic_auth, authenticate_non_public: true + get 'Release' do + # https://gitlab.com/gitlab-org/gitlab/-/issues/5835#note_414103286 + 'TODO Release' + end + + # GET {projects|groups}/:id/packages/debian/dists/*distribution/InRelease + desc 'The signed Release file' do + detail 'This feature was introduced in GitLab 13.5' + end + + route_setting :authentication, deploy_token_allowed: true, basic_auth_personal_access_token: true, job_token_allowed: :basic_auth, authenticate_non_public: true + get 'InRelease' do + not_found! + end + + params do + requires :component, type: String, desc: 'The Debian Component', regexp: Gitlab::Regex.debian_component_regex + requires :architecture, type: String, desc: 'The Debian Architecture', regexp: Gitlab::Regex.debian_architecture_regex + end + + namespace ':component/binary-:architecture', requirements: COMPONENT_ARCHITECTURE_REQUIREMENTS do + # GET {projects|groups}/:id/packages/debian/dists/*distribution/:component/binary-:architecture/Packages + desc 'The binary files index' do + detail 'This feature was introduced in GitLab 13.5' + end + + route_setting :authentication, deploy_token_allowed: true, basic_auth_personal_access_token: true, job_token_allowed: :basic_auth, authenticate_non_public: true + get 'Packages' do + # https://gitlab.com/gitlab-org/gitlab/-/issues/5835#note_414103286 + 'TODO Packages' + end + end + end + + params do + requires :component, type: String, desc: 'The Debian Component', regexp: Gitlab::Regex.debian_component_regex + requires :letter, type: String, desc: 'The Debian Classification (first-letter or lib-first-letter)' + requires :source_package, type: String, desc: 'The Debian Source Package Name', regexp: Gitlab::Regex.debian_package_name_regex + end + + namespace 'pool/:component/:letter/:source_package', requirements: COMPONENT_LETTER_SOURCE_PACKAGE_REQUIREMENTS do + # GET {projects|groups}/:id/packages/debian/pool/:component/:letter/:source_package/:file_name + params do + requires :file_name, type: String, desc: 'The Debian File Name' + end + desc 'The package' do + detail 'This feature was introduced in GitLab 13.5' + end + + route_setting :authentication, deploy_token_allowed: true, basic_auth_personal_access_token: true, job_token_allowed: :basic_auth, authenticate_non_public: true + get ':file_name', requirements: FILE_NAME_REQUIREMENTS do + # https://gitlab.com/gitlab-org/gitlab/-/issues/5835#note_414103286 + 'TODO File' + end + end + end + end + end + end + end +end diff --git a/lib/api/debian_group_packages.rb b/lib/api/debian_group_packages.rb index f138f400601..06edab662bf 100644 --- a/lib/api/debian_group_packages.rb +++ b/lib/api/debian_group_packages.rb @@ -15,8 +15,8 @@ module API authorize_read_package!(user_group) end - namespace ':id/packages/debian' do - include DebianPackageEndpoints + namespace ':id/-' do + include ::API::Concerns::Packages::DebianEndpoints end end end diff --git a/lib/api/debian_package_endpoints.rb b/lib/api/debian_package_endpoints.rb deleted file mode 100644 index e7689b3feff..00000000000 --- a/lib/api/debian_package_endpoints.rb +++ /dev/null @@ -1,127 +0,0 @@ -# frozen_string_literal: true - -module API - module DebianPackageEndpoints - extend ActiveSupport::Concern - - DISTRIBUTION_REGEX = %r{[a-zA-Z0-9][a-zA-Z0-9.-]*}.freeze - COMPONENT_REGEX = %r{[a-z-]+}.freeze - ARCHITECTURE_REGEX = %r{[a-z][a-z0-9]*}.freeze - LETTER_REGEX = %r{(lib)?[a-z0-9]}.freeze - PACKAGE_REGEX = API::NO_SLASH_URL_PART_REGEX - DISTRIBUTION_REQUIREMENTS = { - distribution: DISTRIBUTION_REGEX - }.freeze - COMPONENT_ARCHITECTURE_REQUIREMENTS = { - component: COMPONENT_REGEX, - architecture: ARCHITECTURE_REGEX - }.freeze - COMPONENT_LETTER_SOURCE_PACKAGE_REQUIREMENTS = { - component: COMPONENT_REGEX, - letter: LETTER_REGEX, - source_package: PACKAGE_REGEX - }.freeze - FILE_NAME_REQUIREMENTS = { - file_name: API::NO_SLASH_URL_PART_REGEX - }.freeze - - included do - feature_category :package_registry - - helpers ::API::Helpers::PackagesHelpers - helpers ::API::Helpers::Packages::BasicAuthHelpers - - format :txt - content_type :txt, 'text/plain' - - rescue_from ArgumentError do |e| - render_api_error!(e.message, 400) - end - - rescue_from ActiveRecord::RecordInvalid do |e| - render_api_error!(e.message, 400) - end - - before do - require_packages_enabled! - end - - params do - requires :distribution, type: String, desc: 'The Debian Codename', regexp: Gitlab::Regex.debian_distribution_regex - end - - namespace 'dists/*distribution', requirements: DISTRIBUTION_REQUIREMENTS do - # GET {projects|groups}/:id/packages/debian/dists/*distribution/Release.gpg - desc 'The Release file signature' do - detail 'This feature was introduced in GitLab 13.5' - end - - route_setting :authentication, deploy_token_allowed: true, basic_auth_personal_access_token: true, job_token_allowed: :basic_auth, authenticate_non_public: true - get 'Release.gpg' do - not_found! - end - - # GET {projects|groups}/:id/packages/debian/dists/*distribution/Release - desc 'The unsigned Release file' do - detail 'This feature was introduced in GitLab 13.5' - end - - route_setting :authentication, deploy_token_allowed: true, basic_auth_personal_access_token: true, job_token_allowed: :basic_auth, authenticate_non_public: true - get 'Release' do - # https://gitlab.com/gitlab-org/gitlab/-/issues/5835#note_414103286 - 'TODO Release' - end - - # GET {projects|groups}/:id/packages/debian/dists/*distribution/InRelease - desc 'The signed Release file' do - detail 'This feature was introduced in GitLab 13.5' - end - - route_setting :authentication, deploy_token_allowed: true, basic_auth_personal_access_token: true, job_token_allowed: :basic_auth, authenticate_non_public: true - get 'InRelease' do - not_found! - end - - params do - requires :component, type: String, desc: 'The Debian Component', regexp: Gitlab::Regex.debian_component_regex - requires :architecture, type: String, desc: 'The Debian Architecture', regexp: Gitlab::Regex.debian_architecture_regex - end - - namespace ':component/binary-:architecture', requirements: COMPONENT_ARCHITECTURE_REQUIREMENTS do - # GET {projects|groups}/:id/packages/debian/dists/*distribution/:component/binary-:architecture/Packages - desc 'The binary files index' do - detail 'This feature was introduced in GitLab 13.5' - end - - route_setting :authentication, deploy_token_allowed: true, basic_auth_personal_access_token: true, job_token_allowed: :basic_auth, authenticate_non_public: true - get 'Packages' do - # https://gitlab.com/gitlab-org/gitlab/-/issues/5835#note_414103286 - 'TODO Packages' - end - end - end - - params do - requires :component, type: String, desc: 'The Debian Component', regexp: Gitlab::Regex.debian_component_regex - requires :letter, type: String, desc: 'The Debian Classification (first-letter or lib-first-letter)' - requires :source_package, type: String, desc: 'The Debian Source Package Name', regexp: Gitlab::Regex.debian_package_name_regex - end - - namespace 'pool/:component/:letter/:source_package', requirements: COMPONENT_LETTER_SOURCE_PACKAGE_REQUIREMENTS do - # GET {projects|groups}/:id/packages/debian/pool/:component/:letter/:source_package/:file_name - params do - requires :file_name, type: String, desc: 'The Debian File Name' - end - desc 'The package' do - detail 'This feature was introduced in GitLab 13.5' - end - - route_setting :authentication, deploy_token_allowed: true, basic_auth_personal_access_token: true, job_token_allowed: :basic_auth, authenticate_non_public: true - get ':file_name', requirements: FILE_NAME_REQUIREMENTS do - # https://gitlab.com/gitlab-org/gitlab/-/issues/5835#note_414103286 - 'TODO File' - end - end - end - end -end diff --git a/lib/api/debian_project_packages.rb b/lib/api/debian_project_packages.rb index 8c0db42a448..0ed828fd639 100644 --- a/lib/api/debian_project_packages.rb +++ b/lib/api/debian_project_packages.rb @@ -15,14 +15,14 @@ module API authorize_read_package! end - namespace ':id/packages/debian' do - include DebianPackageEndpoints + namespace ':id' do + include ::API::Concerns::Packages::DebianEndpoints params do requires :file_name, type: String, desc: 'The file name' end - namespace ':file_name', requirements: FILE_NAME_REQUIREMENTS do + namespace 'packages/debian/:file_name', requirements: FILE_NAME_REQUIREMENTS do content_type :json, Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE # PUT {projects|groups}/:id/packages/debian/:file_name diff --git a/lib/api/deploy_tokens.rb b/lib/api/deploy_tokens.rb index 30ec4e52b2a..e9beeb18d62 100644 --- a/lib/api/deploy_tokens.rb +++ b/lib/api/deploy_tokens.rb @@ -18,6 +18,10 @@ module API result_hash[:read_repository] = scopes.include?('read_repository') result_hash end + + params :filter_params do + optional :active, type: Boolean, desc: 'Limit by active status' + end end desc 'Return all deploy tokens' do @@ -26,11 +30,18 @@ module API end params do use :pagination + use :filter_params end get 'deploy_tokens' do authenticated_as_admin! - present paginate(DeployToken.all), with: Entities::DeployToken + deploy_tokens = ::DeployTokens::TokensFinder.new( + current_user, + :all, + declared_params + ).execute + + present paginate(deploy_tokens), with: Entities::DeployToken end params do @@ -39,6 +50,7 @@ module API resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do params do use :pagination + use :filter_params end desc 'List deploy tokens for a project' do detail 'This feature was introduced in GitLab 12.9' @@ -47,7 +59,13 @@ module API get ':id/deploy_tokens' do authorize!(:read_deploy_token, user_project) - present paginate(user_project.deploy_tokens), with: Entities::DeployToken + deploy_tokens = ::DeployTokens::TokensFinder.new( + current_user, + user_project, + declared_params + ).execute + + present paginate(deploy_tokens), with: Entities::DeployToken end params do @@ -98,6 +116,7 @@ module API resource :groups, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do params do use :pagination + use :filter_params end desc 'List deploy tokens for a group' do detail 'This feature was introduced in GitLab 12.9' @@ -106,7 +125,13 @@ module API get ':id/deploy_tokens' do authorize!(:read_deploy_token, user_group) - present paginate(user_group.deploy_tokens), with: Entities::DeployToken + deploy_tokens = ::DeployTokens::TokensFinder.new( + current_user, + user_group, + declared_params + ).execute + + present paginate(deploy_tokens), with: Entities::DeployToken end params do diff --git a/lib/api/deployments.rb b/lib/api/deployments.rb index 0a6ecf2919c..80a50ded522 100644 --- a/lib/api/deployments.rb +++ b/lib/api/deployments.rb @@ -41,6 +41,8 @@ module API .execute.with_api_entity_associations present paginate(deployments), with: Entities::Deployment + rescue DeploymentsFinder::InefficientQueryError => e + bad_request!(e.message) end desc 'Gets a specific deployment' do diff --git a/lib/api/entities/application_setting.rb b/lib/api/entities/application_setting.rb index 2468c1f9b18..f23fce40468 100644 --- a/lib/api/entities/application_setting.rb +++ b/lib/api/entities/application_setting.rb @@ -36,4 +36,4 @@ module API end end -API::Entities::ApplicationSetting.prepend_if_ee('EE::API::Entities::ApplicationSetting') +API::Entities::ApplicationSetting.prepend_mod_with('API::Entities::ApplicationSetting') diff --git a/lib/api/entities/board.rb b/lib/api/entities/board.rb index fe0182ad772..ee0bea466e0 100644 --- a/lib/api/entities/board.rb +++ b/lib/api/entities/board.rb @@ -16,4 +16,4 @@ module API end end -API::Entities::Board.prepend_if_ee('EE::API::Entities::Board') +API::Entities::Board.prepend_mod_with('API::Entities::Board') diff --git a/lib/api/entities/bulk_imports/export_status.rb b/lib/api/entities/bulk_imports/export_status.rb new file mode 100644 index 00000000000..c9c7f34a16a --- /dev/null +++ b/lib/api/entities/bulk_imports/export_status.rb @@ -0,0 +1,14 @@ +# frozen_string_literal: true + +module API + module Entities + module BulkImports + class ExportStatus < Grape::Entity + expose :relation + expose :status + expose :error + expose :updated_at + end + end + end +end diff --git a/lib/api/entities/ci/job_basic.rb b/lib/api/entities/ci/job_basic.rb index a29788c7abf..c31340f1ff0 100644 --- a/lib/api/entities/ci/job_basic.rb +++ b/lib/api/entities/ci/job_basic.rb @@ -6,7 +6,10 @@ module API class JobBasic < Grape::Entity expose :id, :status, :stage, :name, :ref, :tag, :coverage, :allow_failure expose :created_at, :started_at, :finished_at - expose :duration + expose :duration, + documentation: { type: 'Floating', desc: 'Time spent running' } + expose :queued_duration, + documentation: { type: 'Floating', desc: 'Time spent enqueued' } expose :user, with: ::API::Entities::User expose :commit, with: ::API::Entities::Commit expose :pipeline, with: ::API::Entities::Ci::PipelineBasic diff --git a/lib/api/entities/ci/pipeline.rb b/lib/api/entities/ci/pipeline.rb index 3dd3b9c9eff..11336ae070d 100644 --- a/lib/api/entities/ci/pipeline.rb +++ b/lib/api/entities/ci/pipeline.rb @@ -9,6 +9,7 @@ module API expose :user, with: Entities::UserBasic expose :created_at, :updated_at, :started_at, :finished_at, :committed_at expose :duration + expose :queued_duration expose :coverage expose :detailed_status, using: DetailedStatusEntity do |pipeline, options| pipeline.detailed_status(options[:current_user]) diff --git a/lib/api/entities/deploy_token.rb b/lib/api/entities/deploy_token.rb index 9c5bf54e299..daee104ba6b 100644 --- a/lib/api/entities/deploy_token.rb +++ b/lib/api/entities/deploy_token.rb @@ -4,7 +4,8 @@ module API module Entities class DeployToken < Grape::Entity # exposing :token is a security risk and should be avoided - expose :id, :name, :username, :expires_at, :scopes + expose :id, :name, :username, :expires_at, :scopes, :revoked + expose :expired?, as: :expired end end end diff --git a/lib/api/entities/environment.rb b/lib/api/entities/environment.rb index cb39ce1b13a..91867f3403d 100644 --- a/lib/api/entities/environment.rb +++ b/lib/api/entities/environment.rb @@ -3,9 +3,48 @@ module API module Entities class Environment < Entities::EnvironmentBasic + include RequestAwareEntity + include Gitlab::Utils::StrongMemoize + expose :project, using: Entities::BasicProjectDetails expose :last_deployment, using: Entities::Deployment, if: { last_deployment: true } expose :state + + expose :enable_advanced_logs_querying, if: -> (*) { can_read_pod_logs? } do |environment| + environment.elastic_stack_available? + end + + expose :logs_api_path, if: -> (*) { can_read_pod_logs? } do |environment| + if environment.elastic_stack_available? + elasticsearch_project_logs_path(environment.project, environment_name: environment.name, format: :json) + else + k8s_project_logs_path(environment.project, environment_name: environment.name, format: :json) + end + end + + expose :gitlab_managed_apps_logs_path, if: -> (*) { can_read_pod_logs? && cluster } do |environment| + ::Clusters::ClusterPresenter.new(cluster, current_user: current_user).gitlab_managed_apps_logs_path # rubocop: disable CodeReuse/Presenter + end + + private + + alias_method :environment, :object + + def can_read_pod_logs? + strong_memoize(:can_read_pod_logs) do + current_user&.can?(:read_pod_logs, environment.project) + end + end + + def cluster + strong_memoize(:cluster) do + environment&.last_deployment&.cluster + end + end + + def current_user + options[:current_user] + end end end end diff --git a/lib/api/entities/group.rb b/lib/api/entities/group.rb index e430eba4880..048b7a3c15a 100644 --- a/lib/api/entities/group.rb +++ b/lib/api/entities/group.rb @@ -38,4 +38,4 @@ module API end end -API::Entities::Group.prepend_if_ee('EE::API::Entities::Group', with_descendants: true) +API::Entities::Group.prepend_mod_with('API::Entities::Group', with_descendants: true) diff --git a/lib/api/entities/group_detail.rb b/lib/api/entities/group_detail.rb index 2d9d4ca7992..e63a3fc1334 100644 --- a/lib/api/entities/group_detail.rb +++ b/lib/api/entities/group_detail.rb @@ -39,4 +39,4 @@ module API end end -API::Entities::GroupDetail.prepend_if_ee('EE::API::Entities::GroupDetail') +API::Entities::GroupDetail.prepend_mod_with('API::Entities::GroupDetail') diff --git a/lib/api/entities/identity.rb b/lib/api/entities/identity.rb index 52045b6250a..7c8cda8f9c2 100644 --- a/lib/api/entities/identity.rb +++ b/lib/api/entities/identity.rb @@ -8,4 +8,4 @@ module API end end -API::Entities::Identity.prepend_if_ee('EE::API::Entities::Identity') +API::Entities::Identity.prepend_mod_with('API::Entities::Identity') diff --git a/lib/api/entities/issuable_entity.rb b/lib/api/entities/issuable_entity.rb index e2c674c0b8b..fd5d6c8137f 100644 --- a/lib/api/entities/issuable_entity.rb +++ b/lib/api/entities/issuable_entity.rb @@ -24,7 +24,7 @@ module API # entity according to the current top-level entity options, such # as the current_user. def lazy_issuable_metadata - BatchLoader.for(object).batch(key: [current_user, :issuable_metadata]) do |models, loader, args| + BatchLoader.for(object).batch(key: [current_user, :issuable_metadata], replace_methods: false) do |models, loader, args| current_user = args[:key].first issuable_metadata = Gitlab::IssuableMetadata.new(current_user, models) diff --git a/lib/api/entities/issue.rb b/lib/api/entities/issue.rb index 82102854394..e2506cc596e 100644 --- a/lib/api/entities/issue.rb +++ b/lib/api/entities/issue.rb @@ -48,4 +48,4 @@ module API end end -API::Entities::Issue.prepend_if_ee('EE::API::Entities::Issue') +API::Entities::Issue.prepend_mod_with('API::Entities::Issue') diff --git a/lib/api/entities/issue_basic.rb b/lib/api/entities/issue_basic.rb index cf96c6556ec..d27cc5498bd 100644 --- a/lib/api/entities/issue_basic.rb +++ b/lib/api/entities/issue_basic.rb @@ -3,6 +3,10 @@ module API module Entities class IssueBasic < IssuableEntity + format_with(:upcase) do |item| + item.upcase if item.respond_to?(:upcase) + end + expose :closed_at expose :closed_by, using: Entities::UserBasic @@ -16,6 +20,10 @@ module API expose :milestone, using: Entities::Milestone expose :assignees, :author, using: Entities::UserBasic + expose :issue_type, + as: :type, + format_with: :upcase, + documentation: { type: "String", desc: "One of #{Issue.issue_types.keys.map(&:upcase)}" } expose :assignee, using: ::API::Entities::UserBasic do |issue| issue.assignees.first @@ -28,6 +36,7 @@ module API expose :due_date expose :confidential expose :discussion_locked + expose :issue_type expose :web_url do |issue| Gitlab::UrlBuilder.build(issue) @@ -42,4 +51,4 @@ module API end end -API::Entities::IssueBasic.prepend_if_ee('EE::API::Entities::IssueBasic', with_descendants: true) +API::Entities::IssueBasic.prepend_mod_with('API::Entities::IssueBasic', with_descendants: true) diff --git a/lib/api/entities/job_request/response.rb b/lib/api/entities/job_request/response.rb index bf22ea1e6e2..2e8dfc5bde0 100644 --- a/lib/api/entities/job_request/response.rb +++ b/lib/api/entities/job_request/response.rb @@ -34,4 +34,4 @@ module API end end -API::Entities::JobRequest::Response.prepend_if_ee('EE::API::Entities::JobRequest::Response') +API::Entities::JobRequest::Response.prepend_mod_with('API::Entities::JobRequest::Response') diff --git a/lib/api/entities/label_basic.rb b/lib/api/entities/label_basic.rb index ed52688638e..00ecea26ec3 100644 --- a/lib/api/entities/label_basic.rb +++ b/lib/api/entities/label_basic.rb @@ -3,7 +3,7 @@ module API module Entities class LabelBasic < Grape::Entity - expose :id, :name, :color, :description, :description_html, :text_color + expose :id, :name, :color, :description, :description_html, :text_color, :remove_on_close end end end diff --git a/lib/api/entities/list.rb b/lib/api/entities/list.rb index 480e722c22c..e9d31827e2f 100644 --- a/lib/api/entities/list.rb +++ b/lib/api/entities/list.rb @@ -10,4 +10,4 @@ module API end end -API::Entities::List.prepend_if_ee('EE::API::Entities::List') +API::Entities::List.prepend_mod_with('API::Entities::List') diff --git a/lib/api/entities/member.rb b/lib/api/entities/member.rb index ad62f92e5a0..87f03adba31 100644 --- a/lib/api/entities/member.rb +++ b/lib/api/entities/member.rb @@ -11,4 +11,4 @@ module API end end -API::Entities::Member.prepend_if_ee('EE::API::Entities::Member', with_descendants: true) +API::Entities::Member.prepend_mod_with('API::Entities::Member', with_descendants: true) diff --git a/lib/api/entities/merge_request_basic.rb b/lib/api/entities/merge_request_basic.rb index 88c84c494e2..cf8d03bf176 100644 --- a/lib/api/entities/merge_request_basic.rb +++ b/lib/api/entities/merge_request_basic.rb @@ -89,4 +89,4 @@ module API end end -API::Entities::MergeRequestBasic.prepend_if_ee('EE::API::Entities::MergeRequestBasic', with_descendants: true) +API::Entities::MergeRequestBasic.prepend_mod_with('API::Entities::MergeRequestBasic', with_descendants: true) diff --git a/lib/api/entities/namespace.rb b/lib/api/entities/namespace.rb index a7e06cc3e02..f11303d41a6 100644 --- a/lib/api/entities/namespace.rb +++ b/lib/api/entities/namespace.rb @@ -14,4 +14,4 @@ module API end end -API::Entities::Namespace.prepend_if_ee('EE::API::Entities::Namespace') +API::Entities::Namespace.prepend_mod_with('API::Entities::Namespace') diff --git a/lib/api/entities/package.rb b/lib/api/entities/package.rb index e7153f9bebb..2f60a0bf6bd 100644 --- a/lib/api/entities/package.rb +++ b/lib/api/entities/package.rb @@ -22,6 +22,7 @@ module API expose :version expose :package_type + expose :status expose :_links do expose :web_path do |package| diff --git a/lib/api/entities/package_file.rb b/lib/api/entities/package_file.rb index 2cc2f62a948..e34a6a7aa1d 100644 --- a/lib/api/entities/package_file.rb +++ b/lib/api/entities/package_file.rb @@ -5,7 +5,7 @@ module API class PackageFile < Grape::Entity expose :id, :package_id, :created_at expose :file_name, :size - expose :file_md5, :file_sha1 + expose :file_md5, :file_sha1, :file_sha256 expose :pipelines, if: ->(package_file) { package_file.pipelines.present? }, using: Package::Pipeline end end diff --git a/lib/api/entities/project.rb b/lib/api/entities/project.rb index 690bc5d419d..442013c07dd 100644 --- a/lib/api/entities/project.rb +++ b/lib/api/entities/project.rb @@ -147,4 +147,4 @@ module API end end -API::Entities::Project.prepend_if_ee('EE::API::Entities::Project', with_descendants: true) +API::Entities::Project.prepend_mod_with('API::Entities::Project', with_descendants: true) diff --git a/lib/api/entities/protected_branch.rb b/lib/api/entities/protected_branch.rb index e5dbaffb591..ac44d06e69c 100644 --- a/lib/api/entities/protected_branch.rb +++ b/lib/api/entities/protected_branch.rb @@ -12,4 +12,4 @@ module API end end -API::Entities::ProtectedBranch.prepend_if_ee('EE::API::Entities::ProtectedBranch') +API::Entities::ProtectedBranch.prepend_mod_with('API::Entities::ProtectedBranch') diff --git a/lib/api/entities/protected_ref_access.rb b/lib/api/entities/protected_ref_access.rb index f0185705b06..443277e23cf 100644 --- a/lib/api/entities/protected_ref_access.rb +++ b/lib/api/entities/protected_ref_access.rb @@ -11,4 +11,4 @@ module API end end -API::Entities::ProtectedRefAccess.prepend_if_ee('EE::API::Entities::ProtectedRefAccess') +API::Entities::ProtectedRefAccess.prepend_mod_with('API::Entities::ProtectedRefAccess') diff --git a/lib/api/entities/release.rb b/lib/api/entities/release.rb index f6c3dd5a509..94124352298 100644 --- a/lib/api/entities/release.rb +++ b/lib/api/entities/release.rb @@ -8,7 +8,7 @@ module API expose :name expose :tag, as: :tag_name, if: ->(_, _) { can_download_code? } expose :description - expose :description_html do |entity| + expose :description_html, unless: ->(_, _) { remove_description_html? } do |entity| MarkupHelper.markdown_field(entity, :description, current_user: options[:current_user]) end expose :created_at @@ -28,9 +28,7 @@ module API expose :assets do expose :assets_count, as: :count expose :sources, using: Entities::Releases::Source, if: ->(_, _) { can_download_code? } - expose :links, using: Entities::Releases::Link do |release, options| - release.links.sorted - end + expose :sorted_links, as: :links, using: Entities::Releases::Link end expose :evidences, using: Entities::Releases::Evidence, expose_nil: false, if: ->(_, _) { can_download_code? } expose :_links do @@ -47,6 +45,11 @@ module API def can_read_milestone? Ability.allowed?(options[:current_user], :read_milestone, object.project) end + + def remove_description_html? + ::Feature.enabled?(:remove_description_html_in_release_api, object.project, default_enabled: :yaml) && + ::Feature.disabled?(:remove_description_html_in_release_api_override, object.project) + end end end end diff --git a/lib/api/entities/terraform/module_versions.rb b/lib/api/entities/terraform/module_versions.rb new file mode 100644 index 00000000000..75037039117 --- /dev/null +++ b/lib/api/entities/terraform/module_versions.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +module API + module Entities + module Terraform + class ModuleVersions < Grape::Entity + expose :modules + end + end + end +end diff --git a/lib/api/entities/todo.rb b/lib/api/entities/todo.rb index 0acbb4cb704..8d222db488a 100644 --- a/lib/api/entities/todo.rb +++ b/lib/api/entities/todo.rb @@ -58,4 +58,4 @@ module API end end -API::Entities::Todo.prepend_if_ee('EE::API::Entities::Todo') +API::Entities::Todo.prepend_mod_with('API::Entities::Todo') diff --git a/lib/api/entities/user_basic.rb b/lib/api/entities/user_basic.rb index 80f3ee7b502..b8ee4e5a6e0 100644 --- a/lib/api/entities/user_basic.rb +++ b/lib/api/entities/user_basic.rb @@ -19,4 +19,4 @@ module API end end -API::Entities::UserBasic.prepend_if_ee('EE::API::Entities::UserBasic') +API::Entities::UserBasic.prepend_mod_with('API::Entities::UserBasic') diff --git a/lib/api/entities/user_credit_card_validations.rb b/lib/api/entities/user_credit_card_validations.rb new file mode 100644 index 00000000000..fcd42388b16 --- /dev/null +++ b/lib/api/entities/user_credit_card_validations.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +module API + module Entities + class UserCreditCardValidations < Grape::Entity + expose :user_id, :credit_card_validated_at + end + end +end diff --git a/lib/api/entities/user_details_with_admin.rb b/lib/api/entities/user_details_with_admin.rb index e48b1da7859..3572b677646 100644 --- a/lib/api/entities/user_details_with_admin.rb +++ b/lib/api/entities/user_details_with_admin.rb @@ -11,4 +11,4 @@ module API end end -API::Entities::UserDetailsWithAdmin.prepend_if_ee('EE::API::Entities::UserDetailsWithAdmin') +API::Entities::UserDetailsWithAdmin.prepend_mod_with('API::Entities::UserDetailsWithAdmin') diff --git a/lib/api/entities/user_path.rb b/lib/api/entities/user_path.rb index 3f007659813..ed54857d041 100644 --- a/lib/api/entities/user_path.rb +++ b/lib/api/entities/user_path.rb @@ -13,4 +13,4 @@ module API end end -API::Entities::UserPath.prepend_if_ee('EE::API::Entities::UserPath') +API::Entities::UserPath.prepend_mod_with('API::Entities::UserPath') diff --git a/lib/api/entities/user_public.rb b/lib/api/entities/user_public.rb index 685adb1dd10..78f088d3c1a 100644 --- a/lib/api/entities/user_public.rb +++ b/lib/api/entities/user_public.rb @@ -19,4 +19,4 @@ module API end end -API::Entities::UserPublic.prepend_if_ee('EE::API::Entities::UserPublic', with_descendants: true) +API::Entities::UserPublic.prepend_mod_with('API::Entities::UserPublic', with_descendants: true) diff --git a/lib/api/entities/user_with_admin.rb b/lib/api/entities/user_with_admin.rb index ab7bc738ff8..e148a5c45b5 100644 --- a/lib/api/entities/user_with_admin.rb +++ b/lib/api/entities/user_with_admin.rb @@ -9,4 +9,4 @@ module API end end -API::Entities::UserWithAdmin.prepend_if_ee('EE::API::Entities::UserWithAdmin') +API::Entities::UserWithAdmin.prepend_mod_with('API::Entities::UserWithAdmin') diff --git a/lib/api/environments.rb b/lib/api/environments.rb index b606b2e814d..57e548183b0 100644 --- a/lib/api/environments.rb +++ b/lib/api/environments.rb @@ -26,7 +26,7 @@ module API get ':id/environments' do authorize! :read_environment, user_project - environments = ::EnvironmentsFinder.new(user_project, current_user, params).execute + environments = ::Environments::EnvironmentsFinder.new(user_project, current_user, params).execute present paginate(environments), with: Entities::Environment, current_user: current_user end diff --git a/lib/api/features.rb b/lib/api/features.rb index 57bd7c38ad2..2ce2f7c518f 100644 --- a/lib/api/features.rb +++ b/lib/api/features.rb @@ -120,4 +120,4 @@ module API end end -API::Features.prepend_if_ee('EE::API::Features') +API::Features.prepend_mod_with('API::Features') diff --git a/lib/api/generic_packages.rb b/lib/api/generic_packages.rb index cce55fa92d9..d0680ad7bc5 100644 --- a/lib/api/generic_packages.rb +++ b/lib/api/generic_packages.rb @@ -74,6 +74,8 @@ module API Gitlab::ErrorTracking.track_exception(e, extra: { file_name: params[:file_name], project_id: project.id }) forbidden! + rescue ::Packages::DuplicatePackageError + bad_request!('Duplicate package is not allowed') end desc 'Download package file' do diff --git a/lib/api/group_boards.rb b/lib/api/group_boards.rb index 90632048354..92869f8fbba 100644 --- a/lib/api/group_boards.rb +++ b/lib/api/group_boards.rb @@ -5,7 +5,7 @@ module API include BoardsResponses include PaginationParams - prepend_if_ee('EE::API::BoardsResponses') # rubocop: disable Cop/InjectEnterpriseEditionModule + prepend_mod_with('API::BoardsResponses') # rubocop: disable Cop/InjectEnterpriseEditionModule feature_category :boards diff --git a/lib/api/group_export.rb b/lib/api/group_export.rb index 29ffbea687a..6134515032f 100644 --- a/lib/api/group_export.rb +++ b/lib/api/group_export.rb @@ -43,6 +43,43 @@ module API render_api_error!(message: 'Group export could not be started.') end end + + desc 'Start relations export' do + detail 'This feature was introduced in GitLab 13.12' + end + post ':id/export_relations' do + response = ::BulkImports::ExportService.new(portable: user_group, user: current_user).execute + + if response.success? + accepted! + else + render_api_error!(message: 'Group relations export could not be started.') + end + end + + desc 'Download relations export' do + detail 'This feature was introduced in GitLab 13.12' + end + params do + requires :relation, type: String, desc: 'Group relation name' + end + get ':id/export_relations/download' do + export = user_group.bulk_import_exports.find_by_relation(params[:relation]) + file = export&.upload&.export_file + + if file + present_carrierwave_file!(file) + else + render_api_error!('404 Not found', 404) + end + end + + desc 'Relations export status' do + detail 'This feature was introduced in GitLab 13.12' + end + get ':id/export_relations/status' do + present user_group.bulk_import_exports, with: Entities::BulkImports::ExportStatus + end end end end diff --git a/lib/api/group_milestones.rb b/lib/api/group_milestones.rb index dfffd3b1209..061d0410a9c 100644 --- a/lib/api/group_milestones.rb +++ b/lib/api/group_milestones.rb @@ -96,4 +96,4 @@ module API end end -API::GroupMilestones.prepend_if_ee('EE::API::GroupMilestones') +API::GroupMilestones.prepend_mod_with('API::GroupMilestones') diff --git a/lib/api/groups.rb b/lib/api/groups.rb index 912813d5bb7..1a604e70bf1 100644 --- a/lib/api/groups.rb +++ b/lib/api/groups.rb @@ -401,4 +401,4 @@ module API end end -API::Groups.prepend_if_ee('EE::API::Groups') +API::Groups.prepend_mod_with('API::Groups') diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb index 2d8a4f60e2a..632717e1b73 100644 --- a/lib/api/helpers.rb +++ b/lib/api/helpers.rb @@ -124,12 +124,22 @@ module API def find_project!(id) project = find_project(id) + return forbidden! unless authorized_project_scope?(project) + return project if can?(current_user, :read_project, project) return unauthorized! if authenticate_non_public? not_found!('Project') end + def authorized_project_scope?(project) + return true unless job_token_authentication? + return true unless route_authentication_setting[:job_token_scope] == :project + + ::Feature.enabled?(:ci_job_token_scope, project, default_enabled: :yaml) && + current_authenticated_job.project == project + end + # rubocop: disable CodeReuse/ActiveRecord def find_group(id) if id.to_s =~ /^\d+$/ @@ -308,7 +318,7 @@ module API def verify_workhorse_api! Gitlab::Workhorse.verify_api_request!(request.headers) - rescue => e + rescue StandardError => e Gitlab::ErrorTracking.track_exception(e) forbidden! @@ -549,7 +559,7 @@ module API return unless Feature.enabled?(feature_name) Gitlab::UsageDataCounters.count(event_name) - rescue => error + rescue StandardError => error Gitlab::AppLogger.warn("Redis tracking event failed for event: #{event_name}, message: #{error.message}") end @@ -559,7 +569,7 @@ module API return unless values.present? Gitlab::UsageDataCounters::HLLRedisCounter.track_event(event_name, values: values) - rescue => error + rescue StandardError => error Gitlab::AppLogger.warn("Redis tracking event failed for event: #{event_name}, message: #{error.message}") end @@ -582,18 +592,26 @@ module API def project_finder_params_ce finder_params = project_finder_params_visibility_ce + + finder_params.merge!( + params + .slice(:search, + :custom_attributes, + :last_activity_after, + :last_activity_before, + :repository_storage) + .symbolize_keys + .compact + ) + finder_params[:with_issues_enabled] = true if params[:with_issues_enabled].present? finder_params[:with_merge_requests_enabled] = true if params[:with_merge_requests_enabled].present? finder_params[:without_deleted] = true - finder_params[:search] = params[:search] if params[:search] finder_params[:search_namespaces] = true if params[:search_namespaces].present? finder_params[:user] = params.delete(:user) if params[:user] - finder_params[:custom_attributes] = params[:custom_attributes] if params[:custom_attributes] finder_params[:id_after] = sanitize_id_param(params[:id_after]) if params[:id_after] finder_params[:id_before] = sanitize_id_param(params[:id_before]) if params[:id_before] - finder_params[:last_activity_after] = params[:last_activity_after] if params[:last_activity_after] - finder_params[:last_activity_before] = params[:last_activity_before] if params[:last_activity_before] - finder_params[:repository_storage] = params[:repository_storage] if params[:repository_storage] + finder_params[:tag] = params[:topic] if params[:topic].present? finder_params end @@ -700,4 +718,4 @@ module API end end -API::Helpers.prepend_if_ee('EE::API::Helpers') +API::Helpers.prepend_mod_with('API::Helpers') diff --git a/lib/api/helpers/award_emoji.rb b/lib/api/helpers/award_emoji.rb new file mode 100644 index 00000000000..5b659c4dde7 --- /dev/null +++ b/lib/api/helpers/award_emoji.rb @@ -0,0 +1,40 @@ +# frozen_string_literal: true + +module API + module Helpers + module AwardEmoji + def self.awardables + [ + { type: 'issue', resource: :projects, find_by: :iid, feature_category: :issue_tracking }, + { type: 'merge_request', resource: :projects, find_by: :iid, feature_category: :code_review }, + { type: 'snippet', resource: :projects, find_by: :id, feature_category: :snippets } + ] + end + + def self.awardable_id_desc + "The ID of an Issue, Merge Request or Snippet" + end + + # rubocop: disable CodeReuse/ActiveRecord + def awardable + @awardable ||= + begin + if params.include?(:note_id) + note_id = params.delete(:note_id) + + awardable.notes.find(note_id) + elsif params.include?(:issue_iid) + user_project.issues.find_by!(iid: params[:issue_iid]) + elsif params.include?(:merge_request_iid) + user_project.merge_requests.find_by!(iid: params[:merge_request_iid]) + elsif params.include?(:snippet_id) + user_project.snippets.find(params[:snippet_id]) + end + end + end + # rubocop: enable CodeReuse/ActiveRecord + end + end +end + +API::Helpers::AwardEmoji.prepend_mod_with('API::Helpers::AwardEmoji') diff --git a/lib/api/helpers/caching.rb b/lib/api/helpers/caching.rb index d0f22109879..f24ac7302c1 100644 --- a/lib/api/helpers/caching.rb +++ b/lib/api/helpers/caching.rb @@ -11,6 +11,11 @@ module API # @return [ActiveSupport::Duration] DEFAULT_EXPIRY = 1.day + # @return [Hash] + DEFAULT_CACHE_OPTIONS = { + race_condition_ttl: 5.seconds + }.freeze + # @return [ActiveSupport::Cache::Store] def cache Rails.cache @@ -40,7 +45,7 @@ module API # @param expires_in [ActiveSupport::Duration, Integer] an expiry time for the cache entry # @param presenter_args [Hash] keyword arguments to be passed to the entity # @return [Gitlab::Json::PrecompiledJson] - def present_cached(obj_or_collection, with:, cache_context: -> (_) { current_user.cache_key }, expires_in: DEFAULT_EXPIRY, **presenter_args) + def present_cached(obj_or_collection, with:, cache_context: -> (_) { current_user&.cache_key }, expires_in: DEFAULT_EXPIRY, **presenter_args) json = if obj_or_collection.is_a?(Enumerable) cached_collection( @@ -63,8 +68,59 @@ module API body Gitlab::Json::PrecompiledJson.new(json) end + # Action caching implementation + # + # This allows you to wrap an entire API endpoint call in a cache, useful + # for short TTL caches to effectively rate-limit an endpoint. The block + # will be converted to JSON and cached, and returns a + # `Gitlab::Json::PrecompiledJson` object which will be exported without + # secondary conversion. + # + # @param key [Object] any object that can be converted into a cache key + # @param expires_in [ActiveSupport::Duration, Integer] an expiry time for the cache entry + # @return [Gitlab::Json::PrecompiledJson] + def cache_action(key, **cache_opts) + json = cache.fetch(key, **apply_default_cache_options(cache_opts)) do + response = yield + + if response.is_a?(Gitlab::Json::PrecompiledJson) + response.to_s + else + Gitlab::Json.dump(response.as_json) + end + end + + body Gitlab::Json::PrecompiledJson.new(json) + end + + # Conditionally cache an action + # + # Perform a `cache_action` only if the conditional passes + def cache_action_if(conditional, *opts, **kwargs) + if conditional + cache_action(*opts, **kwargs) do + yield + end + else + yield + end + end + + # Conditionally cache an action + # + # Perform a `cache_action` unless the conditional passes + def cache_action_unless(conditional, *opts, **kwargs) + cache_action_if(!conditional, *opts, **kwargs) do + yield + end + end + private + def apply_default_cache_options(opts = {}) + DEFAULT_CACHE_OPTIONS.merge(opts) + end + # Optionally uses a `Proc` to add context to a cache key # # @param object [Object] must respond to #cache_key @@ -119,8 +175,11 @@ module API objs.flatten! map = multi_key_map(objs, context: context) - cache.fetch_multi(*map.keys, **kwargs) do |key| - yield map[key] + # TODO: `contextual_cache_key` should be constructed based on the guideline https://docs.gitlab.com/ee/development/redis.html#multi-key-commands. + Gitlab::Instrumentation::RedisClusterValidator.allow_cross_slot_commands do + cache.fetch_multi(*map.keys, **kwargs) do |key| + yield map[key] + end end end diff --git a/lib/api/helpers/common_helpers.rb b/lib/api/helpers/common_helpers.rb index 8940cf87f82..02942820982 100644 --- a/lib/api/helpers/common_helpers.rb +++ b/lib/api/helpers/common_helpers.rb @@ -40,4 +40,4 @@ module API end end -API::Helpers::CommonHelpers.prepend_if_ee('EE::API::Helpers::CommonHelpers') +API::Helpers::CommonHelpers.prepend_mod_with('API::Helpers::CommonHelpers') diff --git a/lib/api/helpers/discussions_helpers.rb b/lib/api/helpers/discussions_helpers.rb index 3c0db1d0ea9..cb2feeda1e1 100644 --- a/lib/api/helpers/discussions_helpers.rb +++ b/lib/api/helpers/discussions_helpers.rb @@ -17,4 +17,4 @@ module API end end -API::Helpers::DiscussionsHelpers.prepend_if_ee('EE::API::Helpers::DiscussionsHelpers') +API::Helpers::DiscussionsHelpers.prepend_mod_with('API::Helpers::DiscussionsHelpers') diff --git a/lib/api/helpers/groups_helpers.rb b/lib/api/helpers/groups_helpers.rb index ba07a70ee32..5c5109f3d21 100644 --- a/lib/api/helpers/groups_helpers.rb +++ b/lib/api/helpers/groups_helpers.rb @@ -48,4 +48,4 @@ module API end end -API::Helpers::GroupsHelpers.prepend_if_ee('EE::API::Helpers::GroupsHelpers') +API::Helpers::GroupsHelpers.prepend_mod_with('API::Helpers::GroupsHelpers') diff --git a/lib/api/helpers/headers_helpers.rb b/lib/api/helpers/headers_helpers.rb index 908c57bb04e..56445ccbd0d 100644 --- a/lib/api/helpers/headers_helpers.rb +++ b/lib/api/helpers/headers_helpers.rb @@ -8,7 +8,7 @@ module API def set_http_headers(header_data) header_data.each do |key, value| if value.is_a?(Enumerable) - raise ArgumentError.new("Header value should be a string") + raise ArgumentError, "Header value should be a string" end header "X-Gitlab-#{key.to_s.split('_').collect(&:capitalize).join('-')}", value.to_s diff --git a/lib/api/helpers/internal_helpers.rb b/lib/api/helpers/internal_helpers.rb index 9a1ff2ba8ce..e03f029a6ef 100644 --- a/lib/api/helpers/internal_helpers.rb +++ b/lib/api/helpers/internal_helpers.rb @@ -65,7 +65,7 @@ module API result = Gitlab::Redis::SharedState.with { |redis| redis.ping } result == 'PONG' - rescue => e + rescue StandardError => e Gitlab::AppLogger.warn("GitLab: An unexpected error occurred in pinging to Redis: #{e}") false end diff --git a/lib/api/helpers/issues_helpers.rb b/lib/api/helpers/issues_helpers.rb index 2b1ed479692..b1954f8ece9 100644 --- a/lib/api/helpers/issues_helpers.rb +++ b/lib/api/helpers/issues_helpers.rb @@ -28,7 +28,8 @@ module API :remove_labels, :milestone_id, :state_event, - :title + :title, + :issue_type ] end @@ -47,6 +48,7 @@ module API args[:not][:label_name] ||= args[:not]&.delete(:labels) args[:scope] = args[:scope].underscore if args[:scope] args[:sort] = "#{args[:order_by]}_#{args[:sort]}" + args[:issue_types] ||= args.delete(:issue_type) IssuesFinder.new(current_user, args) end @@ -74,4 +76,4 @@ module API end end -API::Helpers::IssuesHelpers.prepend_if_ee('EE::API::Helpers::IssuesHelpers') +API::Helpers::IssuesHelpers.prepend_mod_with('API::Helpers::IssuesHelpers') diff --git a/lib/api/helpers/label_helpers.rb b/lib/api/helpers/label_helpers.rb index 4018f2dec21..796b8928243 100644 --- a/lib/api/helpers/label_helpers.rb +++ b/lib/api/helpers/label_helpers.rb @@ -5,27 +5,34 @@ module API module LabelHelpers extend Grape::API::Helpers + params :optional_label_params do + optional :description, type: String, desc: 'The description of the label' + optional :remove_on_close, type: Boolean, desc: 'Whether the label should be removed from an issue when the issue is closed' + end + params :label_create_params do requires :name, type: String, desc: 'The name of the label to be created' requires :color, type: String, desc: "The color of the label given in 6-digit hex notation with leading '#' sign (e.g. #FFAABB) or one of the allowed CSS color names" - optional :description, type: String, desc: 'The description of label to be created' + + use :optional_label_params end params :label_update_params do optional :new_name, type: String, desc: 'The new name of the label' optional :color, type: String, desc: "The new color of the label given in 6-digit hex notation with leading '#' sign (e.g. #FFAABB) or one of the allowed CSS color names" - optional :description, type: String, desc: 'The new description of label' + + use :optional_label_params end params :project_label_update_params do use :label_update_params optional :priority, type: Integer, desc: 'The priority of the label', allow_blank: true - at_least_one_of :new_name, :color, :description, :priority + at_least_one_of :new_name, :color, :description, :priority, :remove_on_close end params :group_label_update_params do use :label_update_params - at_least_one_of :new_name, :color, :description + at_least_one_of :new_name, :color, :description, :remove_on_close end def find_label(parent, id_or_title, params = { include_ancestor_groups: true }) @@ -117,7 +124,7 @@ module API else render_api_error!('Failed to promote project label to group label', 400) end - rescue => error + rescue StandardError => error render_api_error!(error.to_s, 400) end end diff --git a/lib/api/helpers/members_helpers.rb b/lib/api/helpers/members_helpers.rb index 2de077b5a3b..bd0c2501220 100644 --- a/lib/api/helpers/members_helpers.rb +++ b/lib/api/helpers/members_helpers.rb @@ -18,7 +18,7 @@ module API # rubocop: disable CodeReuse/ActiveRecord def retrieve_members(source, params:, deep: false) - members = deep ? find_all_members(source) : source_members(source).where.not(user_id: nil) + members = deep ? find_all_members(source) : source_members(source).connected_to_user members = members.includes(:user) members = members.references(:user).merge(User.search(params[:query])) if params[:query].present? members = members.where(user_id: params[:user_ids]) if params[:user_ids].present? @@ -65,4 +65,4 @@ module API end end -API::Helpers::MembersHelpers.prepend_if_ee('EE::API::Helpers::MembersHelpers') +API::Helpers::MembersHelpers.prepend_mod_with('API::Helpers::MembersHelpers') diff --git a/lib/api/helpers/notes_helpers.rb b/lib/api/helpers/notes_helpers.rb index cb938bc8a14..356e4a98c97 100644 --- a/lib/api/helpers/notes_helpers.rb +++ b/lib/api/helpers/notes_helpers.rb @@ -151,4 +151,4 @@ module API end end -API::Helpers::NotesHelpers.prepend_if_ee('EE::API::Helpers::NotesHelpers') +API::Helpers::NotesHelpers.prepend_mod_with('API::Helpers::NotesHelpers') diff --git a/lib/api/helpers/performance_bar_helpers.rb b/lib/api/helpers/performance_bar_helpers.rb index 8430e889dff..0b7fb4308fc 100644 --- a/lib/api/helpers/performance_bar_helpers.rb +++ b/lib/api/helpers/performance_bar_helpers.rb @@ -4,17 +4,17 @@ module API module Helpers module PerformanceBarHelpers def set_peek_enabled_for_current_request - Gitlab::SafeRequestStore.fetch(:peek_enabled) { perf_bar_cookie_enabled? && perf_bar_enabled_for_user? } + Gitlab::SafeRequestStore.fetch(:peek_enabled) { perf_bar_cookie_enabled? && perf_bar_allowed_for_user? } end def perf_bar_cookie_enabled? cookies[:perf_bar_enabled] == 'true' end - def perf_bar_enabled_for_user? + def perf_bar_allowed_for_user? # We cannot use `current_user` here because that method raises an exception when the user # is unauthorized and some API endpoints require that `current_user` is not called. - Gitlab::PerformanceBar.enabled_for_user?(find_user_from_sources) + Gitlab::PerformanceBar.allowed_for_user?(find_user_from_sources) end end end diff --git a/lib/api/helpers/project_snapshots_helpers.rb b/lib/api/helpers/project_snapshots_helpers.rb index e708dbf0156..0b10641571a 100644 --- a/lib/api/helpers/project_snapshots_helpers.rb +++ b/lib/api/helpers/project_snapshots_helpers.rb @@ -3,7 +3,7 @@ module API module Helpers module ProjectSnapshotsHelpers - prepend_if_ee('::EE::API::Helpers::ProjectSnapshotsHelpers') # rubocop: disable Cop/InjectEnterpriseEditionModule + prepend_mod_with('API::Helpers::ProjectSnapshotsHelpers') # rubocop: disable Cop/InjectEnterpriseEditionModule def authorize_read_git_snapshot! authenticated_with_can_read_all_resources! diff --git a/lib/api/helpers/projects_helpers.rb b/lib/api/helpers/projects_helpers.rb index cf2bcace33b..d9c0b4f67c8 100644 --- a/lib/api/helpers/projects_helpers.rb +++ b/lib/api/helpers/projects_helpers.rb @@ -170,4 +170,4 @@ module API end end -API::Helpers::ProjectsHelpers.prepend_if_ee('EE::API::Helpers::ProjectsHelpers') +API::Helpers::ProjectsHelpers.prepend_mod_with('API::Helpers::ProjectsHelpers') diff --git a/lib/api/helpers/protected_branches_helpers.rb b/lib/api/helpers/protected_branches_helpers.rb index 970a3687214..4a968ad1d60 100644 --- a/lib/api/helpers/protected_branches_helpers.rb +++ b/lib/api/helpers/protected_branches_helpers.rb @@ -12,4 +12,4 @@ module API end end -API::Helpers::ProtectedBranchesHelpers.prepend_if_ee('EE::API::Helpers::ProtectedBranchesHelpers') +API::Helpers::ProtectedBranchesHelpers.prepend_mod_with('API::Helpers::ProtectedBranchesHelpers') diff --git a/lib/api/helpers/related_resources_helpers.rb b/lib/api/helpers/related_resources_helpers.rb index 9cdde25fe4e..d0eda68bf52 100644 --- a/lib/api/helpers/related_resources_helpers.rb +++ b/lib/api/helpers/related_resources_helpers.rb @@ -23,10 +23,10 @@ module API # Using a blank component at the beginning of the join we ensure # that the resulted path will start with '/'. If the resulted path - # does not start with '/', URI::Generic#build will fail + # does not start with '/', URI::Generic#new will fail path_with_script_name = File.join('', [script_name, path].select(&:present?)) - URI::Generic.build(scheme: protocol, host: host, port: port, path: path_with_script_name).to_s + URI::Generic.new(protocol, nil, host, port, nil, path_with_script_name, nil, nil, nil, URI::RFC3986_PARSER, true).to_s end private diff --git a/lib/api/helpers/resource_label_events_helpers.rb b/lib/api/helpers/resource_label_events_helpers.rb index ad2733baffc..7e641130062 100644 --- a/lib/api/helpers/resource_label_events_helpers.rb +++ b/lib/api/helpers/resource_label_events_helpers.rb @@ -15,4 +15,4 @@ module API end end -API::Helpers::ResourceLabelEventsHelpers.prepend_if_ee('EE::API::Helpers::ResourceLabelEventsHelpers') +API::Helpers::ResourceLabelEventsHelpers.prepend_mod_with('API::Helpers::ResourceLabelEventsHelpers') diff --git a/lib/api/helpers/runner.rb b/lib/api/helpers/runner.rb index 688cd2da994..6f25cf507bc 100644 --- a/lib/api/helpers/runner.rb +++ b/lib/api/helpers/runner.rb @@ -5,7 +5,7 @@ module API module Runner include Gitlab::Utils::StrongMemoize - prepend_if_ee('EE::API::Helpers::Runner') # rubocop: disable Cop/InjectEnterpriseEditionModule + prepend_mod_with('API::Helpers::Runner') # rubocop: disable Cop/InjectEnterpriseEditionModule JOB_TOKEN_HEADER = 'HTTP_JOB_TOKEN' JOB_TOKEN_PARAM = :token @@ -87,6 +87,10 @@ module API project: -> { current_job.project } ) end + + def track_ci_minutes_usage!(_build, _runner) + # noop: overridden in EE + end end end end diff --git a/lib/api/helpers/search_helpers.rb b/lib/api/helpers/search_helpers.rb index cb5f92fa62a..66321306496 100644 --- a/lib/api/helpers/search_helpers.rb +++ b/lib/api/helpers/search_helpers.rb @@ -25,4 +25,4 @@ module API end end -API::Helpers::SearchHelpers.prepend_if_ee('EE::API::Helpers::SearchHelpers') +API::Helpers::SearchHelpers.prepend_mod_with('API::Helpers::SearchHelpers') diff --git a/lib/api/helpers/services_helpers.rb b/lib/api/helpers/services_helpers.rb index 2f2ad88c942..d123db8e3df 100644 --- a/lib/api/helpers/services_helpers.rb +++ b/lib/api/helpers/services_helpers.rb @@ -420,44 +420,6 @@ module API }, chat_notification_events ].flatten, - 'hipchat' => [ - { - required: true, - name: :token, - type: String, - desc: 'The room token' - }, - { - required: false, - name: :room, - type: String, - desc: 'The room name or ID' - }, - { - required: false, - name: :color, - type: String, - desc: 'The room color' - }, - { - required: false, - name: :notify, - type: Boolean, - desc: 'Enable notifications' - }, - { - required: false, - name: :api_version, - type: String, - desc: 'Leave blank for default (v2)' - }, - { - required: false, - name: :server, - type: String, - desc: 'Leave blank for default. https://hipchat.example.com' - } - ], 'irker' => [ { required: true, @@ -803,7 +765,7 @@ module API required: true, name: :webhook, type: String, - desc: 'The Webex Teams webhook. e.g. https://api.ciscospark.com/v1/webhooks/incoming/…' + desc: 'The Webex Teams webhook. For example, https://api.ciscospark.com/v1/webhooks/incoming/...' }, chat_notification_events ].flatten @@ -812,23 +774,22 @@ module API def self.service_classes [ - ::AsanaService, - ::AssemblaService, - ::BambooService, + ::Integrations::Asana, + ::Integrations::Assembla, + ::Integrations::Bamboo, + ::Integrations::Campfire, + ::Integrations::Confluence, + ::Integrations::Datadog, + ::Integrations::EmailsOnPush, ::BugzillaService, ::BuildkiteService, - ::ConfluenceService, - ::CampfireService, ::CustomIssueTrackerService, - ::DatadogService, ::DiscordService, ::DroneCiService, - ::EmailsOnPushService, ::EwmService, ::ExternalWikiService, ::FlowdockService, ::HangoutsChatService, - ::HipchatService, ::IrkerService, ::JenkinsService, ::JiraService, @@ -858,4 +819,4 @@ module API end end -API::Helpers::ServicesHelpers.prepend_if_ee('EE::API::Helpers::ServicesHelpers') +API::Helpers::ServicesHelpers.prepend_mod_with('API::Helpers::ServicesHelpers') diff --git a/lib/api/helpers/settings_helpers.rb b/lib/api/helpers/settings_helpers.rb index 451e578fdd6..a3ea1057bc8 100644 --- a/lib/api/helpers/settings_helpers.rb +++ b/lib/api/helpers/settings_helpers.rb @@ -19,4 +19,4 @@ module API end end -API::Helpers::SettingsHelpers.prepend_if_ee('EE::API::Helpers::SettingsHelpers') +API::Helpers::SettingsHelpers.prepend_mod_with('API::Helpers::SettingsHelpers') diff --git a/lib/api/helpers/users_helpers.rb b/lib/api/helpers/users_helpers.rb index 2d7b22e66b3..1a019283bc6 100644 --- a/lib/api/helpers/users_helpers.rb +++ b/lib/api/helpers/users_helpers.rb @@ -22,4 +22,4 @@ module API end end -API::Helpers::UsersHelpers.prepend_if_ee('EE::API::Helpers::UsersHelpers') +API::Helpers::UsersHelpers.prepend_mod_with('API::Helpers::UsersHelpers') diff --git a/lib/api/helpers/variables_helpers.rb b/lib/api/helpers/variables_helpers.rb index e2b3372fc33..edbdcb257e7 100644 --- a/lib/api/helpers/variables_helpers.rb +++ b/lib/api/helpers/variables_helpers.rb @@ -24,4 +24,4 @@ module API end end -API::Helpers::VariablesHelpers.prepend_if_ee('EE::API::Helpers::VariablesHelpers') +API::Helpers::VariablesHelpers.prepend_mod_with('API::Helpers::VariablesHelpers') diff --git a/lib/api/helpers/wikis_helpers.rb b/lib/api/helpers/wikis_helpers.rb index 49da1e317ab..4a14dc1f40a 100644 --- a/lib/api/helpers/wikis_helpers.rb +++ b/lib/api/helpers/wikis_helpers.rb @@ -32,4 +32,4 @@ module API end end -API::Helpers::WikisHelpers.prepend_if_ee('EE::API::Helpers::WikisHelpers') +API::Helpers::WikisHelpers.prepend_mod_with('API::Helpers::WikisHelpers') diff --git a/lib/api/internal/base.rb b/lib/api/internal/base.rb index 4dcfc0cf7eb..e16149185c9 100644 --- a/lib/api/internal/base.rb +++ b/lib/api/internal/base.rb @@ -158,7 +158,7 @@ module API status 200 unless actor.key_or_user - raise ActiveRecord::RecordNotFound.new('User not found!') + raise ActiveRecord::RecordNotFound, 'User not found!' end actor.update_last_used_at! @@ -336,4 +336,4 @@ module API end end -API::Internal::Base.prepend_if_ee('EE::API::Internal::Base') +API::Internal::Base.prepend_mod_with('API::Internal::Base') diff --git a/lib/api/internal/kubernetes.rb b/lib/api/internal/kubernetes.rb index af2c53dd778..c28e2181873 100644 --- a/lib/api/internal/kubernetes.rb +++ b/lib/api/internal/kubernetes.rb @@ -107,18 +107,18 @@ module API detail 'Updates usage metrics for agent' end params do - requires :gitops_sync_count, type: Integer, desc: 'The count to increment the gitops_sync metric by' + optional :gitops_sync_count, type: Integer, desc: 'The count to increment the gitops_sync metric by' + optional :k8s_api_proxy_request_count, type: Integer, desc: 'The count to increment the k8s_api_proxy_request_count metric by' end post '/' do - gitops_sync_count = params[:gitops_sync_count] + events = params.slice(:gitops_sync_count, :k8s_api_proxy_request_count) + events.transform_keys! { |event| event.to_s.chomp('_count') } - if gitops_sync_count < 0 - bad_request!('gitops_sync_count must be greater than or equal to zero') - else - Gitlab::UsageDataCounters::KubernetesAgentCounter.increment_gitops_sync(gitops_sync_count) + Gitlab::UsageDataCounters::KubernetesAgentCounter.increment_event_counts(events) - no_content! - end + no_content! + rescue ArgumentError => e + bad_request!(e.message) end end end @@ -126,4 +126,4 @@ module API end end -API::Internal::Kubernetes.prepend_if_ee('EE::API::Internal::Kubernetes') +API::Internal::Kubernetes.prepend_mod_with('API::Internal::Kubernetes') diff --git a/lib/api/issue_links.rb b/lib/api/issue_links.rb index 1cd5bde224b..0b4f4e06d0b 100644 --- a/lib/api/issue_links.rb +++ b/lib/api/issue_links.rb @@ -21,12 +21,12 @@ module API related_issues = source_issue.related_issues(current_user) do |issues| issues.with_api_entity_associations.preload_awardable end - related_issues.each { |issue| issue.lazy_subscription(current_user, user_project) } # preload subscriptions present related_issues, with: Entities::RelatedIssue, current_user: current_user, - project: user_project + project: user_project, + include_subscribed: false end desc 'Relate issues' do diff --git a/lib/api/issues.rb b/lib/api/issues.rb index c844655f0b3..355b5ed3a1f 100644 --- a/lib/api/issues.rb +++ b/lib/api/issues.rb @@ -74,6 +74,7 @@ module API desc: 'Return issues sorted in `asc` or `desc` order.' optional :due_date, type: String, values: %w[0 overdue week month next_month_and_previous_two_weeks] << '', desc: 'Return issues that have no due date (`0`), or whose due date is this week, this month, between two weeks ago and next month, or which are overdue. Accepts: `overdue`, `week`, `month`, `next_month_and_previous_two_weeks`, `0`' + optional :issue_type, type: String, values: Issue.issue_types.keys, desc: "The type of the issue. Accepts: #{Issue.issue_types.keys.join(', ')}" use :issues_stats_params use :pagination @@ -90,6 +91,7 @@ module API optional :due_date, type: String, desc: 'Date string in the format YEAR-MONTH-DAY' optional :confidential, type: Boolean, desc: 'Boolean parameter if the issue should be confidential' optional :discussion_locked, type: Boolean, desc: " Boolean parameter indicating if the issue's discussion is locked" + optional :issue_type, type: String, values: Issue.issue_types.keys, desc: "The type of the issue. Accepts: #{Issue.issue_types.keys.join(', ')}" use :optional_issue_params_ee end @@ -253,9 +255,9 @@ module API issue_params = convert_parameters_from_legacy_format(issue_params) begin - issue = ::Issues::CreateService.new(user_project, - current_user, - issue_params.merge(request: request, api: true)).execute + issue = ::Issues::CreateService.new(project: user_project, + current_user: current_user, + params: issue_params.merge(request: request, api: true)).execute if issue.spam? render_api_error!({ error: 'Spam detected' }, 400) @@ -296,9 +298,9 @@ module API update_params = convert_parameters_from_legacy_format(update_params) - issue = ::Issues::UpdateService.new(user_project, - current_user, - update_params).execute(issue) + issue = ::Issues::UpdateService.new(project: user_project, + current_user: current_user, + params: update_params).execute(issue) render_spam_error! if issue.spam? @@ -326,7 +328,7 @@ module API authorize! :update_issue, issue - if ::Issues::ReorderService.new(user_project, current_user, params).execute(issue) + if ::Issues::ReorderService.new(project: user_project, current_user: current_user, params: params).execute(issue) present issue, with: Entities::Issue, current_user: current_user, project: user_project else render_api_error!({ error: 'Unprocessable Entity' }, 422) @@ -352,7 +354,7 @@ module API not_found!('Project') unless new_project begin - issue = ::Issues::MoveService.new(user_project, current_user).execute(issue, new_project) + issue = ::Issues::MoveService.new(project: user_project, current_user: current_user).execute(issue, new_project) present issue, with: Entities::Issue, current_user: current_user, project: user_project rescue ::Issues::MoveService::MoveError => error render_api_error!(error.message, 400) @@ -372,7 +374,7 @@ module API authorize!(:destroy_issue, issue) destroy_conditionally!(issue) do |issue| - Issuable::DestroyService.new(user_project, current_user).execute(issue) + Issuable::DestroyService.new(project: user_project, current_user: current_user).execute(issue) end end # rubocop: enable CodeReuse/ActiveRecord @@ -386,7 +388,7 @@ module API get ':id/issues/:issue_iid/related_merge_requests' do issue = find_project_issue(params[:issue_iid]) - merge_requests = ::Issues::ReferencedMergeRequestsService.new(user_project, current_user) + merge_requests = ::Issues::ReferencedMergeRequestsService.new(project: user_project, current_user: current_user) .execute(issue) .first @@ -446,4 +448,4 @@ module API end end -API::Issues.prepend_if_ee('EE::API::Issues') +API::Issues.prepend_mod_with('API::Issues') diff --git a/lib/api/job_artifacts.rb b/lib/api/job_artifacts.rb index 3dec0a29181..37199279205 100644 --- a/lib/api/job_artifacts.rb +++ b/lib/api/job_artifacts.rb @@ -13,7 +13,7 @@ module API end end - prepend_if_ee('EE::API::JobArtifacts') # rubocop: disable Cop/InjectEnterpriseEditionModule + prepend_mod_with('API::JobArtifacts') # rubocop: disable Cop/InjectEnterpriseEditionModule params do requires :id, type: String, desc: 'The ID of a project' diff --git a/lib/api/jobs.rb b/lib/api/jobs.rb index 54951f9bd01..cf65bfdfd0e 100644 --- a/lib/api/jobs.rb +++ b/lib/api/jobs.rb @@ -202,4 +202,4 @@ module API end end -API::Jobs.prepend_if_ee('EE::API::Jobs') +API::Jobs.prepend_mod_with('API::Jobs') diff --git a/lib/api/maven_packages.rb b/lib/api/maven_packages.rb index bd1d984719e..22f7b07809b 100644 --- a/lib/api/maven_packages.rb +++ b/lib/api/maven_packages.rb @@ -25,7 +25,7 @@ module API helpers do def path_exists?(path) # return true when FF disabled so that processing the request is not stopped - return true unless Feature.enabled?(:check_maven_path_first) + return true unless Feature.enabled?(:check_maven_path_first, default_enabled: :yaml) return false if path.blank? Packages::Maven::Metadatum.with_path(path) @@ -88,17 +88,13 @@ module API end def fetch_package(file_name:, project: nil, group: nil) - order_by_package_file = false - if Feature.enabled?(:maven_packages_group_level_improvements, default_enabled: :yaml) - order_by_package_file = file_name.include?(::Packages::Maven::Metadata.filename) && - !params[:path].include?(::Packages::Maven::FindOrCreatePackageService::SNAPSHOT_TERM) - end + order_by_package_file = file_name.include?(::Packages::Maven::Metadata.filename) && + !params[:path].include?(::Packages::Maven::FindOrCreatePackageService::SNAPSHOT_TERM) ::Packages::Maven::PackageFinder.new( - params[:path], current_user, - project: project, - group: group, + project || group, + path: params[:path], order_by_package_file: order_by_package_file ).execute! end diff --git a/lib/api/members.rb b/lib/api/members.rb index aaf0e3e1927..a1a733ea7ae 100644 --- a/lib/api/members.rb +++ b/lib/api/members.rb @@ -175,4 +175,4 @@ module API end end -API::Members.prepend_if_ee('EE::API::Members') +API::Members.prepend_mod_with('API::Members') diff --git a/lib/api/merge_request_approvals.rb b/lib/api/merge_request_approvals.rb index 0cdfd8f94b4..83150bb51ca 100644 --- a/lib/api/merge_request_approvals.rb +++ b/lib/api/merge_request_approvals.rb @@ -54,7 +54,7 @@ module API success = ::MergeRequests::ApprovalService - .new(user_project, current_user, params) + .new(project: user_project, current_user: current_user, params: params) .execute(merge_request) unauthorized! unless success @@ -67,7 +67,7 @@ module API merge_request = find_merge_request_with_access(params[:merge_request_iid], :approve_merge_request) success = ::MergeRequests::RemoveApprovalService - .new(user_project, current_user) + .new(project: user_project, current_user: current_user) .execute(merge_request) not_found! unless success @@ -79,4 +79,4 @@ module API end end -API::MergeRequestApprovals.prepend_if_ee('EE::API::MergeRequestApprovals') +API::MergeRequestApprovals.prepend_mod_with('API::MergeRequestApprovals') diff --git a/lib/api/merge_requests.rb b/lib/api/merge_requests.rb index 613de514ffa..931d2322c98 100644 --- a/lib/api/merge_requests.rb +++ b/lib/api/merge_requests.rb @@ -52,7 +52,7 @@ module API ] end - prepend_if_ee('EE::API::MergeRequests') # rubocop: disable Cop/InjectEnterpriseEditionModule + prepend_mod_with('API::MergeRequests') # rubocop: disable Cop/InjectEnterpriseEditionModule helpers do # rubocop: disable CodeReuse/ActiveRecord @@ -201,7 +201,11 @@ module API options = serializer_options_for(merge_requests).merge(project: user_project) options[:project] = user_project - present merge_requests, options + if Feature.enabled?(:api_caching_merge_requests, user_project, type: :development, default_enabled: :yaml) + present_cached merge_requests, expires_in: 10.minutes, **options + else + present merge_requests, options + end end desc 'Create a merge request' do @@ -224,7 +228,7 @@ module API mr_params[:force_remove_source_branch] = mr_params.delete(:remove_source_branch) mr_params = convert_parameters_from_legacy_format(mr_params) - merge_request = ::MergeRequests::CreateService.new(user_project, current_user, mr_params).execute + merge_request = ::MergeRequests::CreateService.new(project: user_project, current_user: current_user, params: mr_params).execute handle_merge_request_errors!(merge_request) @@ -243,7 +247,7 @@ module API authorize!(:destroy_merge_request, merge_request) destroy_conditionally!(merge_request) do |merge_request| - Issuable::DestroyService.new(user_project, current_user).execute(merge_request) + Issuable::DestroyService.new(project: user_project, current_user: current_user).execute(merge_request) end end @@ -335,7 +339,7 @@ module API authorize!(:update_merge_request, merge_request) project = merge_request.target_project - result = ::MergeRequests::AddContextService.new(project, current_user, merge_request: merge_request, commits: commit_ids).execute + result = ::MergeRequests::AddContextService.new(project: project, current_user: current_user, params: { merge_request: merge_request, commits: commit_ids }).execute if result.instance_of?(Array) present result, with: Entities::Commit @@ -398,7 +402,7 @@ module API end post ':id/merge_requests/:merge_request_iid/pipelines', feature_category: :continuous_integration do pipeline = ::MergeRequests::CreatePipelineService - .new(user_project, current_user, allow_duplicate: true) + .new(project: user_project, current_user: current_user, params: { allow_duplicate: true }) .execute(find_merge_request_with_access(params[:merge_request_iid])) if pipeline.nil? @@ -439,7 +443,7 @@ module API ::MergeRequests::UpdateService end - merge_request = service.new(user_project, current_user, mr_params).execute(merge_request) + merge_request = service.new(project: user_project, current_user: current_user, params: mr_params).execute(merge_request) handle_merge_request_errors!(merge_request) @@ -489,7 +493,7 @@ module API if immediately_mergeable ::MergeRequests::MergeService - .new(merge_request.target_project, current_user, merge_params) + .new(project: merge_request.target_project, current_user: current_user, params: merge_params) .execute(merge_request) elsif automatically_mergeable AutoMergeService.new(merge_request.target_project, current_user, merge_params) diff --git a/lib/api/namespaces.rb b/lib/api/namespaces.rb index 465d2f23e9d..9d41c2f148f 100644 --- a/lib/api/namespaces.rb +++ b/lib/api/namespaces.rb @@ -19,7 +19,7 @@ module API end end - prepend_if_ee('EE::API::Namespaces') # rubocop: disable Cop/InjectEnterpriseEditionModule + prepend_mod_with('API::Namespaces') # rubocop: disable Cop/InjectEnterpriseEditionModule resource :namespaces do desc 'Get a namespaces list' do diff --git a/lib/api/package_files.rb b/lib/api/package_files.rb index 4a33f3e8af2..6d0c1f44a36 100644 --- a/lib/api/package_files.rb +++ b/lib/api/package_files.rb @@ -30,6 +30,29 @@ module API present paginate(package.package_files), with: ::API::Entities::PackageFile end + + desc 'Remove a package file' do + detail 'This feature was introduced in GitLab 13.12' + end + params do + requires :package_file_id, type: Integer, desc: 'The ID of a package file' + end + delete ':id/packages/:package_id/package_files/:package_file_id' do + authorize_destroy_package!(user_project) + + # We want to make sure the file belongs to the declared package + # so we look up the package before looking up the file. + package = ::Packages::PackageFinder + .new(user_project, params[:package_id]).execute + + not_found! unless package + + package_file = package.package_files.find_by_id(params[:package_file_id]) + + not_found! unless package_file + + destroy_conditionally!(package_file) + end end end end diff --git a/lib/api/project_container_repositories.rb b/lib/api/project_container_repositories.rb index 3125de88de5..2580f7adbc9 100644 --- a/lib/api/project_container_repositories.rb +++ b/lib/api/project_container_repositories.rb @@ -15,6 +15,7 @@ module API params do requires :id, type: String, desc: 'The ID of a project' end + route_setting :authentication, job_token_allowed: true, job_token_scope: :project resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do desc 'Get a project container repositories' do detail 'This feature was introduced in GitLab 11.8.' diff --git a/lib/api/project_import.rb b/lib/api/project_import.rb index 5f3a574eeee..039f7b4be41 100644 --- a/lib/api/project_import.rb +++ b/lib/api/project_import.rb @@ -14,6 +14,21 @@ module API def import_params declared_params(include_missing: false) end + + def namespace_from(params, current_user) + if params[:namespace] + find_namespace!(params[:namespace]) + else + current_user.namespace + end + end + + def filtered_override_params(params) + override_params = params.delete(:override_params) + filter_attributes_using_license!(override_params) if override_params + + override_params + end end before do @@ -67,34 +82,25 @@ module API check_rate_limit! :project_import, [current_user, :project_import] - Gitlab::QueryLimiting.disable!('https://gitlab.com/gitlab-org/gitlab/-/issues/20823') + Gitlab::QueryLimiting.disable!('https://gitlab.com/gitlab-org/gitlab/-/issues/21041') validate_file! - namespace = if import_params[:namespace] - find_namespace!(import_params[:namespace]) - else - current_user.namespace - end - - project_params = { - path: import_params[:path], - namespace_id: namespace.id, - name: import_params[:name], - file: import_params[:file], - overwrite: import_params[:overwrite] - } - - override_params = import_params.delete(:override_params) - filter_attributes_using_license!(override_params) if override_params - - project = ::Projects::GitlabProjectsImportService.new( - current_user, project_params, override_params + response = ::Import::GitlabProjects::CreateProjectFromUploadedFileService.new( + current_user, + path: import_params[:path], + namespace: namespace_from(import_params, current_user), + name: import_params[:name], + file: import_params[:file], + overwrite: import_params[:overwrite], + override: filtered_override_params(import_params) ).execute - render_api_error!(project.errors.full_messages&.first, 400) unless project.saved? - - present project, with: Entities::ProjectImportStatus + if response.success? + present(response.payload, with: Entities::ProjectImportStatus) + else + render_api_error!(response.message, response.http_status) + end end params do @@ -107,6 +113,44 @@ module API get ':id/import' do present user_project, with: Entities::ProjectImportStatus end + + params do + requires :url, type: String, desc: 'The URL for the file.' + requires :path, type: String, desc: 'The new project path and name' + optional :name, type: String, desc: 'The name of the project to be imported. Defaults to the path of the project if not provided.' + optional :namespace, type: String, desc: "The ID or name of the namespace that the project will be imported into. Defaults to the current user's namespace." + optional :overwrite, type: Boolean, default: false, desc: 'If there is a project in the same namespace and with the same name overwrite it' + optional :override_params, + type: Hash, + desc: 'New project params to override values in the export' do + use :optional_project_params + end + end + desc 'Create a new project import using a remote object storage path' do + detail 'This feature was introduced in GitLab 13.2.' + success Entities::ProjectImportStatus + end + post 'remote-import' do + not_found! unless ::Feature.enabled?(:import_project_from_remote_file) + + check_rate_limit! :project_import, [current_user, :project_import] + + response = ::Import::GitlabProjects::CreateProjectFromRemoteFileService.new( + current_user, + path: import_params[:path], + namespace: namespace_from(import_params, current_user), + name: import_params[:name], + remote_import_url: import_params[:url], + overwrite: import_params[:overwrite], + override: filtered_override_params(import_params) + ).execute + + if response.success? + present(response.payload, with: Entities::ProjectImportStatus) + else + render_api_error!(response.message, response.http_status) + end + end end end end diff --git a/lib/api/project_milestones.rb b/lib/api/project_milestones.rb index 8675de33923..107311ea446 100644 --- a/lib/api/project_milestones.rb +++ b/lib/api/project_milestones.rb @@ -117,4 +117,4 @@ module API end end -API::ProjectMilestones.prepend_if_ee('EE::API::ProjectMilestones') +API::ProjectMilestones.prepend_mod_with('API::ProjectMilestones') diff --git a/lib/api/project_templates.rb b/lib/api/project_templates.rb index fdfdc244cbe..5d6f67ccbae 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 gitlab_ci_syntax_ymls licenses metrics_dashboard_ymls issues merge_requests].freeze + TEMPLATE_TYPES = %w[dockerfiles gitignores gitlab_ci_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|gitlab_ci_syntax_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|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 92f6970e6fc..4e8786fbe1f 100644 --- a/lib/api/projects.rb +++ b/lib/api/projects.rb @@ -1,7 +1,5 @@ # frozen_string_literal: true -require_dependency 'declarative_policy' - module API class Projects < ::API::Base include PaginationParams @@ -119,6 +117,7 @@ module API optional :last_activity_after, type: DateTime, desc: 'Limit results to projects with last_activity after specified time. Format: ISO 8601 YYYY-MM-DDTHH:MM:SSZ' optional :last_activity_before, type: DateTime, desc: 'Limit results to projects with last_activity before specified time. Format: ISO 8601 YYYY-MM-DDTHH:MM:SSZ' optional :repository_storage, type: String, desc: 'Which storage shard the repository is on. Available only to admins' + optional :topic, type: Array[String], coerce_with: ::API::Validations::Types::CommaSeparatedToArray.coerce, desc: 'Comma-separated list of topics. Limit results to projects having all topics' use :optional_filter_params_ee end @@ -619,6 +618,8 @@ module API optional :skip_groups, type: Array[Integer], coerce_with: ::API::Validations::Types::CommaSeparatedToIntegerArray.coerce, desc: 'Array of group ids to exclude from list' optional :with_shared, type: Boolean, default: false, desc: 'Include shared groups' + optional :shared_visible_only, type: Boolean, default: false, + desc: 'Limit to shared groups user has access to' optional :shared_min_access_level, type: Integer, values: Gitlab::Access.all_values, desc: 'Limit returned shared groups by minimum access level to the project' use :pagination @@ -663,4 +664,4 @@ module API end end -API::Projects.prepend_if_ee('EE::API::Projects') +API::Projects.prepend_mod_with('API::Projects') diff --git a/lib/api/protected_branches.rb b/lib/api/protected_branches.rb index 802dfdec511..3cebc308f51 100644 --- a/lib/api/protected_branches.rb +++ b/lib/api/protected_branches.rb @@ -104,4 +104,4 @@ module API end end -API::ProtectedBranches.prepend_if_ee('EE::API::ProtectedBranches') +API::ProtectedBranches.prepend_mod_with('API::ProtectedBranches') diff --git a/lib/api/pypi_packages.rb b/lib/api/pypi_packages.rb index 658c6d13847..73b2f658825 100644 --- a/lib/api/pypi_packages.rb +++ b/lib/api/pypi_packages.rb @@ -24,25 +24,6 @@ module API render_api_error!(e.message, 400) end - helpers do - def packages_finder(project = authorized_user_project) - project - .packages - .pypi - .has_version - .processed - end - - def find_package_versions - packages = packages_finder - .with_normalized_pypi_name(params[:package_name]) - - not_found!('Package') if packages.empty? - - packages - end - end - before do require_packages_enabled! end @@ -71,7 +52,7 @@ module API project = unauthorized_user_project! filename = "#{params[:file_identifier]}.#{params[:format]}" - package = packages_finder(project).by_file_name_and_sha256(filename, params[:sha256]) + package = Packages::Pypi::PackageFinder.new(current_user, project, { filename: filename, sha256: params[:sha256] }).execute package_file = ::Packages::PackageFileFinder.new(package, filename, with_file_name_like: false).execute track_package_event('pull_package', :pypi) @@ -95,7 +76,7 @@ module API track_package_event('list_package', :pypi) - packages = find_package_versions + packages = Packages::Pypi::PackagesFinder.new(current_user, authorized_user_project, { package_name: params[:package_name] }).execute! presenter = ::Packages::Pypi::PackagePresenter.new(packages, authorized_user_project) # Adjusts grape output format diff --git a/lib/api/releases.rb b/lib/api/releases.rb index c20e618efd1..c65a23e334f 100644 --- a/lib/api/releases.rb +++ b/lib/api/releases.rb @@ -6,9 +6,12 @@ module API RELEASE_ENDPOINT_REQUIREMENTS = API::NAMESPACE_OR_PROJECT_REQUIREMENTS .merge(tag_name: API::NO_SLASH_URL_PART_REGEX) + RELEASE_CLI_USER_AGENT = 'GitLab-release-cli' before { authorize_read_releases! } + after { track_release_event } + feature_category :release_orchestration params do @@ -17,6 +20,7 @@ module API resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do desc 'Get a project releases' do detail 'This feature was introduced in GitLab 11.7.' + named 'get_releases' success Entities::Release end params do @@ -29,11 +33,22 @@ module API get ':id/releases' do releases = ::ReleasesFinder.new(user_project, current_user, declared_params.slice(:order_by, :sort)).execute - present paginate(releases), with: Entities::Release, current_user: current_user + # We cache the serialized payload per user in order to avoid repeated renderings. + # Since the cached result could contain sensitive information, + # it will expire in a short interval. + present_cached paginate(releases), + with: Entities::Release, + # `current_user` could be absent if the releases are publicly accesible. + # We should not use `cache_key` for the user because the version/updated_at + # context is unnecessary here. + cache_context: -> (_) { "user:{#{current_user&.id}}" }, + expires_in: 5.minutes, + current_user: current_user end desc 'Get a single project release' do detail 'This feature was introduced in GitLab 11.7.' + named 'get_release' success Entities::Release end params do @@ -47,6 +62,7 @@ module API desc 'Create a new release' do detail 'This feature was introduced in GitLab 11.7.' + named 'create_release' success Entities::Release end params do @@ -84,6 +100,7 @@ module API desc 'Update a release' do detail 'This feature was introduced in GitLab 11.7.' + named 'update_release' success Entities::Release end params do @@ -112,6 +129,7 @@ module API desc 'Delete a release' do detail 'This feature was introduced in GitLab 11.7.' + named 'delete_release' success Entities::Release end params do @@ -176,8 +194,23 @@ module API def log_release_milestones_updated_audit_event # extended in EE end + + def release_cli? + request.env['HTTP_USER_AGENT']&.include?(RELEASE_CLI_USER_AGENT) == true + end + + def event_context + { + release_cli: release_cli? + } + end + + def track_release_event + Gitlab::Tracking.event(options[:for].name, options[:route_options][:named], + project: user_project, user: current_user, **event_context) + end end end end -API::Releases.prepend_if_ee('EE::API::Releases') +API::Releases.prepend_mod_with('API::Releases') diff --git a/lib/api/repositories.rb b/lib/api/repositories.rb index 033cc6744b0..a5234828de3 100644 --- a/lib/api/repositories.rb +++ b/lib/api/repositories.rb @@ -37,7 +37,7 @@ module API begin @blob = Gitlab::Git::Blob.raw(@repo, params[:sha]) @blob.load_all_data!(@repo) - rescue + rescue StandardError not_found! 'Blob' end @@ -106,7 +106,7 @@ module API not_acceptable! if Gitlab::HotlinkingDetector.intercept_hotlinking?(request) send_git_archive user_project.repository, ref: params[:sha], format: params[:format], append_sha: true - rescue + rescue StandardError not_found!('File') end @@ -152,7 +152,7 @@ module API get ':id/repository/contributors' do contributors = ::Kaminari.paginate_array(user_project.repository.contributors(order_by: params[:order_by], sort: params[:sort])) present paginate(contributors), with: Entities::Contributor - rescue + rescue StandardError not_found! end @@ -224,7 +224,7 @@ module API desc: 'The commit message to use when committing the changelog' end post ':id/repository/changelog' do - branch = params[:branch] || user_project.default_branch_or_master + branch = params[:branch] || user_project.default_branch_or_main access = Gitlab::UserAccess.new(current_user, container: user_project) unless access.can_push_to_branch?(branch) diff --git a/lib/api/search.rb b/lib/api/search.rb index 8fabf379d49..3c5801366a8 100644 --- a/lib/api/search.rb +++ b/lib/api/search.rb @@ -138,4 +138,4 @@ module API end end -API::Search.prepend_if_ee('EE::API::Search') +API::Search.prepend_mod_with('API::Search') diff --git a/lib/api/services.rb b/lib/api/services.rb index cfcae13e518..8a7abe721dd 100644 --- a/lib/api/services.rb +++ b/lib/api/services.rb @@ -72,7 +72,7 @@ module API success Entities::ProjectServiceBasic end get ":id/services" do - services = user_project.services.active + services = user_project.integrations.active present services, with: Entities::ProjectServiceBasic end @@ -125,15 +125,18 @@ module API requires :service_slug, type: String, values: SERVICES.keys, desc: 'The name of the service' end get ":id/services/:service_slug" do - service = user_project.find_or_initialize_service(params[:service_slug].underscore) - present service, with: Entities::ProjectService + integration = user_project.find_or_initialize_service(params[:service_slug].underscore) + + not_found!('Service') unless integration&.persisted? + + present integration, with: Entities::ProjectService end end TRIGGER_SERVICES.each do |service_slug, settings| helpers do def slash_command_service(project, service_slug, params) - project.services.active.find do |service| + project.integrations.active.find do |service| service.try(:token) == params[:token] && service.to_param == service_slug.underscore end end @@ -172,4 +175,4 @@ module API end end -API::Services.prepend_if_ee('EE::API::Services') +API::Services.prepend_mod_with('API::Services') diff --git a/lib/api/settings.rb b/lib/api/settings.rb index 95d0c525ced..372bc7b3d8f 100644 --- a/lib/api/settings.rb +++ b/lib/api/settings.rb @@ -154,6 +154,7 @@ module API optional :spam_check_endpoint_enabled, type: Boolean, desc: 'Enable Spam Check via external API endpoint' given spam_check_endpoint_enabled: ->(val) { val } do requires :spam_check_endpoint_url, type: String, desc: 'The URL of the external Spam Check service endpoint' + requires :spam_check_api_key, type: String, desc: 'The API key used by GitLab for accessing the Spam Check service endpoint' end optional :terminal_max_session_time, type: Integer, desc: 'Maximum time for web terminal websocket connection (in seconds). Set to 0 for unlimited time.' optional :usage_ping_enabled, type: Boolean, desc: 'Every week GitLab will report license usage back to GitLab, Inc.' @@ -169,6 +170,8 @@ module API optional :raw_blob_request_limit, type: Integer, desc: "Maximum number of requests per minute for each raw path. Set to 0 for unlimited requests per minute." optional :wiki_page_max_content_bytes, type: Integer, desc: "Maximum wiki page content size in bytes" optional :require_admin_approval_after_user_signup, type: Boolean, desc: 'Require explicit admin approval for new signups' + optional :whats_new_variant, type: String, values: ApplicationSetting.whats_new_variants.keys, desc: "What's new variant, possible values: `all_tiers`, `current_tier`, and `disabled`." + optional :floc_enabled, type: Grape::API::Boolean, desc: 'Enable FloC (Federated Learning of Cohorts)' ApplicationSetting::SUPPORTED_KEY_TYPES.each do |type| optional :"#{type}_key_restriction", @@ -232,4 +235,4 @@ module API end end -API::Settings.prepend_if_ee('EE::API::Settings') +API::Settings.prepend_mod_with('API::Settings') diff --git a/lib/api/templates.rb b/lib/api/templates.rb index bc1e427bcaa..b7fb35eac03 100644 --- a/lib/api/templates.rb +++ b/lib/api/templates.rb @@ -13,9 +13,6 @@ 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/modules/v1/packages.rb b/lib/api/terraform/modules/v1/packages.rb new file mode 100644 index 00000000000..34e77e09800 --- /dev/null +++ b/lib/api/terraform/modules/v1/packages.rb @@ -0,0 +1,200 @@ +# frozen_string_literal: true + +module API + module Terraform + module Modules + module V1 + class Packages < ::API::Base + include ::API::Helpers::Authentication + helpers ::API::Helpers::PackagesHelpers + helpers ::API::Helpers::Packages::BasicAuthHelpers + + SEMVER_REGEX = Gitlab::Regex.semver_regex + + TERRAFORM_MODULE_REQUIREMENTS = { + module_namespace: API::NO_SLASH_URL_PART_REGEX, + module_name: API::NO_SLASH_URL_PART_REGEX, + module_system: API::NO_SLASH_URL_PART_REGEX + }.freeze + + TERRAFORM_MODULE_VERSION_REQUIREMENTS = { + module_version: SEMVER_REGEX + }.freeze + + feature_category :package_registry + + after_validation do + require_packages_enabled! + end + + helpers do + params :module_name do + requires :module_name, type: String, desc: "", regexp: API::NO_SLASH_URL_PART_REGEX + requires :module_system, type: String, regexp: API::NO_SLASH_URL_PART_REGEX + end + + params :module_version do + requires :module_version, type: String, desc: 'Module version', regexp: SEMVER_REGEX + end + + def module_namespace + strong_memoize(:module_namespace) do + find_namespace(params[:module_namespace]) + end + end + + def finder_params + { + package_type: :terraform_module, + package_name: "#{params[:module_name]}/#{params[:module_system]}" + }.tap do |finder_params| + finder_params[:package_version] = params[:module_version] if params.has_key?(:module_version) + end + end + + def packages + strong_memoize(:packages) do + ::Packages::GroupPackagesFinder.new( + current_user, + module_namespace, + finder_params + ).execute + end + end + + def package + strong_memoize(:package) do + packages.first + end + end + + def package_file + strong_memoize(:package_file) do + package.package_files.first + end + end + end + + params do + requires :module_namespace, type: String, desc: "Group's ID or slug", regexp: API::NO_SLASH_URL_PART_REGEX + includes :module_name + end + + namespace 'packages/terraform/modules/v1/:module_namespace/:module_name/:module_system', requirements: TERRAFORM_MODULE_REQUIREMENTS do + authenticate_with do |accept| + accept.token_types(:personal_access_token, :deploy_token, :job_token) + .sent_through(:http_bearer_token) + end + + after_validation do + authorize_read_package!(package || module_namespace) + end + + get 'versions' do + presenter = ::Terraform::ModulesPresenter.new(packages, params[:module_system]) + present presenter, with: ::API::Entities::Terraform::ModuleVersions + end + + params do + includes :module_version + end + + namespace '*module_version', requirements: TERRAFORM_MODULE_VERSION_REQUIREMENTS do + after_validation do + not_found! unless package && package_file + end + + get 'download' do + module_file_path = api_v4_packages_terraform_modules_v1_module_version_file_path( + module_namespace: params[:module_namespace], + module_name: params[:module_name], + module_system: params[:module_system], + module_version: params[:module_version] + ) + + jwt_token = Gitlab::TerraformRegistryToken.from_token(token_from_namespace_inheritable).encoded + + header 'X-Terraform-Get', module_file_path.sub(%r{module_version/file$}, "#{params[:module_version]}/file?token=#{jwt_token}&archive=tgz") + status :no_content + end + + namespace 'file' do + authenticate_with do |accept| + accept.token_types(:deploy_token_from_jwt, :job_token_from_jwt, :personal_access_token_from_jwt).sent_through(:token_param) + end + + get do + track_package_event('pull_package', :terraform_module) + + present_carrierwave_file!(package_file.file) + end + end + end + end + + params do + requires :id, type: String, desc: 'The ID or full path of a project' + includes :module_name + includes :module_version + end + + resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do + namespace ':id/packages/terraform/modules/:module_name/:module_system/*module_version/file' do + authenticate_with do |accept| + accept.token_types(:deploy_token).sent_through(:http_deploy_token_header) + accept.token_types(:job_token).sent_through(:http_job_token_header) + accept.token_types(:personal_access_token).sent_through(:http_private_token_header) + end + + desc 'Workhorse authorize Terraform Module package file' do + detail 'This feature was introduced in GitLab 13.11' + end + + put 'authorize' do + authorize_workhorse!( + subject: authorized_user_project, + maximum_size: authorized_user_project.actual_limits.terraform_module_max_file_size + ) + end + + desc 'Upload Terraform Module package file' do + detail 'This feature was introduced in GitLab 13.11' + end + + params do + requires :file, type: ::API::Validations::Types::WorkhorseFile, desc: 'The package file to be published (generated by Multipart middleware)' + end + + put do + authorize_upload!(authorized_user_project) + bad_request!('File is too large') if authorized_user_project.actual_limits.exceeded?(:terraform_module_max_file_size, params[:file].size) + + create_package_file_params = { + module_name: params['module_name'], + module_system: params['module_system'], + module_version: params['module_version'], + file: params['file'], + build: current_authenticated_job + } + + result = ::Packages::TerraformModule::CreatePackageService + .new(authorized_user_project, current_user, create_package_file_params) + .execute + + render_api_error!(result[:message], result[:http_status]) if result[:status] == :error + + track_package_event('push_package', :terraform_module) + + created! + rescue ObjectStorage::RemoteStoreError => e + Gitlab::ErrorTracking.track_exception(e, extra: { file_name: params[:file_name], project_id: authorized_user_project.id }) + + forbidden! + end + end + end + end + end + end + end +end diff --git a/lib/api/time_tracking_endpoints.rb b/lib/api/time_tracking_endpoints.rb index da234fb5277..969122d7906 100644 --- a/lib/api/time_tracking_endpoints.rb +++ b/lib/api/time_tracking_endpoints.rb @@ -37,7 +37,7 @@ module API custom_params = declared_params(include_missing: false) custom_params.merge!(attrs) - issuable = update_service.new(user_project, current_user, custom_params).execute(load_issuable) + issuable = update_service.new(project: user_project, current_user: current_user, params: custom_params).execute(load_issuable) if issuable.valid? present issuable, with: Entities::IssuableTimeStats else @@ -85,10 +85,15 @@ module API post ":id/#{issuable_collection_name}/:#{issuable_key}/add_spent_time" do authorize! admin_issuable_key, load_issuable - update_issuable(spend_time: { - duration: Gitlab::TimeTrackingFormatter.parse(params.delete(:duration)), - user_id: current_user.id - }) + update_params = { + spend_time: { + duration: Gitlab::TimeTrackingFormatter.parse(params.delete(:duration)), + user_id: current_user.id + } + } + update_params[:use_specialized_service] = true if issuable_name == 'merge_request' + + update_issuable(update_params) end desc "Reset spent time for a project #{issuable_name}" diff --git a/lib/api/todos.rb b/lib/api/todos.rb index afc1525cbe2..a001313a11f 100644 --- a/lib/api/todos.rb +++ b/lib/api/todos.rb @@ -79,7 +79,7 @@ module API next unless collection targets = collection.map(&:target) - options[type] = { issuable_metadata: Gitlab::IssuableMetadata.new(current_user, targets).data } + options[type] = { issuable_metadata: Gitlab::IssuableMetadata.new(current_user, targets).data, include_subscribed: false } end end end @@ -124,4 +124,4 @@ module API end end -API::Todos.prepend_if_ee('EE::API::Todos') +API::Todos.prepend_mod_with('API::Todos') diff --git a/lib/api/triggers.rb b/lib/api/triggers.rb index 84c51e5aeac..a359083a9d2 100644 --- a/lib/api/triggers.rb +++ b/lib/api/triggers.rb @@ -37,7 +37,7 @@ module API result = ::Ci::PipelineTriggerService.new(project, nil, params).execute not_found! unless result - if result[:http_status] + if result.error? render_api_error!(result[:message], result[:http_status]) else present result[:pipeline], with: Entities::Ci::Pipeline diff --git a/lib/api/users.rb b/lib/api/users.rb index 078ba7542a3..565a3544da2 100644 --- a/lib/api/users.rb +++ b/lib/api/users.rb @@ -996,6 +996,30 @@ module API present paginate(current_user.emails), with: Entities::Email end + desc "Update a user's credit_card_validation" do + success Entities::UserCreditCardValidations + end + params do + requires :user_id, type: String, desc: 'The ID or username of the user' + requires :credit_card_validated_at, type: DateTime, desc: 'The time when the user\'s credit card was validated' + end + put ":user_id/credit_card_validation", feature_category: :users do + authenticated_as_admin! + + user = find_user(params[:user_id]) + not_found!('User') unless user + + attrs = declared_params(include_missing: false) + + service = ::Users::UpsertCreditCardValidationService.new(attrs).execute + + if service.success? + present user.credit_card_validation, with: Entities::UserCreditCardValidations + else + render_api_error!('400 Bad Request', 400) + end + end + desc "Update the current user's preferences" do success Entities::UserPreferences detail 'This feature was introduced in GitLab 13.10.' diff --git a/lib/api/validations/validators/check_assignees_count.rb b/lib/api/validations/validators/check_assignees_count.rb index 92ada159b46..15f48c09a4f 100644 --- a/lib/api/validations/validators/check_assignees_count.rb +++ b/lib/api/validations/validators/check_assignees_count.rb @@ -34,4 +34,4 @@ module API end end -API::Validations::Validators::CheckAssigneesCount.prepend_if_ee('EE::API::Validations::Validators::CheckAssigneesCount') +API::Validations::Validators::CheckAssigneesCount.prepend_mod_with('API::Validations::Validators::CheckAssigneesCount') diff --git a/lib/api/validations/validators/file_path.rb b/lib/api/validations/validators/file_path.rb index a6a3c692fd6..246c445658f 100644 --- a/lib/api/validations/validators/file_path.rb +++ b/lib/api/validations/validators/file_path.rb @@ -10,7 +10,7 @@ module API path = params[attr_name] path = Gitlab::Utils.check_path_traversal!(path) Gitlab::Utils.check_allowed_absolute_path!(path, path_allowlist) - rescue + rescue StandardError raise Grape::Exceptions::Validation.new( params: [@scope.full_name(attr_name)], message: "should be a valid file path" |