From d9ab72d6080f594d0b3cae15f14b3ef2c6c638cb Mon Sep 17 00:00:00 2001 From: GitLab Bot Date: Wed, 20 Oct 2021 08:43:02 +0000 Subject: Add latest changes from gitlab-org/gitlab@14-4-stable-ee --- lib/api/api.rb | 25 ++- lib/api/base.rb | 17 +- lib/api/bulk_imports.rb | 2 +- lib/api/ci/helpers/runner.rb | 8 +- lib/api/ci/resource_groups.rb | 52 ++++++ lib/api/ci/runners.rb | 30 +++- lib/api/composer_packages.rb | 4 +- lib/api/container_repositories.rb | 2 + lib/api/entities/basic_project_details.rb | 4 +- .../entities/ci/reset_registration_token_result.rb | 11 -- lib/api/entities/ci/reset_token_result.rb | 11 ++ lib/api/entities/ci/resource_group.rb | 11 ++ lib/api/entities/clusters/agent_authorization.rb | 2 +- lib/api/entities/environment_basic.rb | 2 +- lib/api/entities/feature_flag.rb | 4 +- .../entities/feature_flag/detailed_legacy_scope.rb | 11 -- lib/api/entities/feature_flag/legacy_scope.rb | 16 -- lib/api/entities/group_detail.rb | 4 +- lib/api/entities/issuable_entity.rb | 2 +- lib/api/entities/namespace_basic.rb | 2 +- lib/api/entities/project.rb | 4 +- lib/api/entities/user.rb | 9 +- lib/api/error_tracking.rb | 63 -------- lib/api/error_tracking/client_keys.rb | 50 ++++++ lib/api/error_tracking/collector.rb | 135 ++++++++++++++++ lib/api/error_tracking/project_settings.rb | 63 ++++++++ lib/api/error_tracking_client_keys.rb | 50 ------ lib/api/error_tracking_collector.rb | 135 ---------------- lib/api/group_container_repositories.rb | 1 + lib/api/group_export.rb | 2 - lib/api/groups.rb | 2 +- lib/api/helm_packages.rb | 7 +- lib/api/helpers.rb | 15 +- lib/api/helpers/common_helpers.rb | 2 +- lib/api/helpers/container_registry_helpers.rb | 15 ++ lib/api/helpers/integrations_helpers.rb | 4 +- lib/api/integrations.rb | 178 +++++++++++++++++++++ lib/api/internal/base.rb | 2 +- lib/api/internal/kubernetes.rb | 19 --- lib/api/issues.rb | 39 ++++- lib/api/maven_packages.rb | 3 +- lib/api/merge_requests.rb | 3 + lib/api/notes.rb | 3 +- lib/api/project_container_repositories.rb | 2 + lib/api/project_export.rb | 48 +++++- lib/api/project_import.rb | 1 - lib/api/projects.rb | 7 +- lib/api/projects_relation_builder.rb | 2 + lib/api/repositories.rb | 3 +- lib/api/services.rb | 173 -------------------- lib/api/settings.rb | 1 + lib/api/tags.rb | 2 +- lib/api/unleash.rb | 2 +- lib/api/users.rb | 7 +- lib/api/validations/validators/project_portable.rb | 21 +++ 55 files changed, 740 insertions(+), 553 deletions(-) create mode 100644 lib/api/ci/resource_groups.rb delete mode 100644 lib/api/entities/ci/reset_registration_token_result.rb create mode 100644 lib/api/entities/ci/reset_token_result.rb create mode 100644 lib/api/entities/ci/resource_group.rb delete mode 100644 lib/api/entities/feature_flag/detailed_legacy_scope.rb delete mode 100644 lib/api/entities/feature_flag/legacy_scope.rb delete mode 100644 lib/api/error_tracking.rb create mode 100644 lib/api/error_tracking/client_keys.rb create mode 100644 lib/api/error_tracking/collector.rb create mode 100644 lib/api/error_tracking/project_settings.rb delete mode 100644 lib/api/error_tracking_client_keys.rb delete mode 100644 lib/api/error_tracking_collector.rb create mode 100644 lib/api/helpers/container_registry_helpers.rb create mode 100644 lib/api/integrations.rb delete mode 100644 lib/api/services.rb create mode 100644 lib/api/validations/validators/project_portable.rb (limited to 'lib/api') diff --git a/lib/api/api.rb b/lib/api/api.rb index d0d96858f61..a4d42c735cb 100644 --- a/lib/api/api.rb +++ b/lib/api/api.rb @@ -11,11 +11,12 @@ module API COMMIT_ENDPOINT_REQUIREMENTS = NAMESPACE_OR_PROJECT_REQUIREMENTS.merge(sha: NO_SLASH_URL_PART_REGEX).freeze USER_REQUIREMENTS = { user_id: NO_SLASH_URL_PART_REGEX }.freeze LOG_FILTERS = ::Rails.application.config.filter_parameters + [/^output$/] + LOG_FORMATTER = Gitlab::GrapeLogging::Formatters::LogrageWithTimestamp.new insert_before Grape::Middleware::Error, GrapeLogging::Middleware::RequestLogger, logger: Logger.new(LOG_FILENAME), - formatter: Gitlab::GrapeLogging::Formatters::LogrageWithTimestamp.new, + formatter: LOG_FORMATTER, include: [ GrapeLogging::Loggers::FilterParameters.new(LOG_FILTERS), Gitlab::GrapeLogging::Loggers::ClientEnvLogger.new, @@ -49,16 +50,19 @@ module API before do coerce_nil_params_to_array! - api_endpoint = env['api.endpoint'] + api_endpoint = request.env[Grape::Env::API_ENDPOINT] feature_category = api_endpoint.options[:for].try(:feature_category_for_app, api_endpoint).to_s + # remote_ip is added here and the ContextLogger so that the + # client_id field is set correctly, as the user object does not + # survive between multiple context pushes. Gitlab::ApplicationContext.push( user: -> { @current_user }, project: -> { @project }, namespace: -> { @group }, runner: -> { @current_runner || @runner }, - caller_id: api_endpoint.endpoint_id, remote_ip: request.ip, + caller_id: api_endpoint.endpoint_id, feature_category: feature_category ) end @@ -124,6 +128,11 @@ module API handle_api_exception(exception) end + rescue_from RateLimitedService::RateLimitedError do |exception| + exception.log_request(context.request, context.current_user) + rack_response({ 'message' => { 'error' => exception.message } }.to_json, 429, exception.headers) + end + format :json formatter :json, Gitlab::Json::GrapeFormatter content_type :json, 'application/json' @@ -132,6 +141,7 @@ module API helpers ::API::Helpers helpers ::API::Helpers::CommonHelpers helpers ::API::Helpers::PerformanceBarHelpers + helpers ::API::Helpers::RateLimiter namespace do after do @@ -157,6 +167,7 @@ module API mount ::API::Ci::Jobs mount ::API::Ci::Pipelines mount ::API::Ci::PipelineSchedules + mount ::API::Ci::ResourceGroups mount ::API::Ci::Runner mount ::API::Ci::Runners mount ::API::Ci::Triggers @@ -170,9 +181,9 @@ module API mount ::API::DeployTokens mount ::API::Deployments mount ::API::Environments - mount ::API::ErrorTracking - mount ::API::ErrorTrackingClientKeys - mount ::API::ErrorTrackingCollector + mount ::API::ErrorTracking::ClientKeys + mount ::API::ErrorTracking::Collector + mount ::API::ErrorTracking::ProjectSettings mount ::API::Events mount ::API::FeatureFlags mount ::API::FeatureFlagsUserLists @@ -259,7 +270,7 @@ module API mount ::API::ResourceAccessTokens mount ::API::RubygemPackages mount ::API::Search - mount ::API::Services + mount ::API::Integrations mount ::API::Settings mount ::API::SidekiqMetrics mount ::API::SnippetRepositoryStorageMoves diff --git a/lib/api/base.rb b/lib/api/base.rb index 33e47c18fcd..c245a65b30b 100644 --- a/lib/api/base.rb +++ b/lib/api/base.rb @@ -2,20 +2,33 @@ module API class Base < Grape::API::Instance # rubocop:disable API/Base - include ::Gitlab::WithFeatureCategory + include ::Gitlab::EndpointAttributes class << self def feature_category_for_app(app) feature_category_for_action(path_for_app(app)) end + def urgency_for_app(app) + urgency_for_action(path_for_app(app)) + end + def path_for_app(app) normalize_path(app.namespace, app.options[:path].first) end + def endpoint_id_for_route(route) + "#{route.request_method} #{route.origin}" + end + def route(methods, paths = ['/'], route_options = {}, &block) + actions = Array(paths).map { |path| normalize_path(namespace, path) } if category = route_options.delete(:feature_category) - feature_category(category, Array(paths).map { |path| normalize_path(namespace, path) }) + feature_category(category, actions) + end + + if target = route_options.delete(:urgency) + urgency(target, actions) end super diff --git a/lib/api/bulk_imports.rb b/lib/api/bulk_imports.rb index 0705a8285c1..c732da17166 100644 --- a/lib/api/bulk_imports.rb +++ b/lib/api/bulk_imports.rb @@ -51,7 +51,7 @@ module API end end post do - response = BulkImportService.new( + response = ::BulkImports::CreateService.new( current_user, params[:entities], url: params[:configuration][:url], diff --git a/lib/api/ci/helpers/runner.rb b/lib/api/ci/helpers/runner.rb index b9662b822fb..dabb6c7ab3a 100644 --- a/lib/api/ci/helpers/runner.rb +++ b/lib/api/ci/helpers/runner.rb @@ -42,8 +42,7 @@ module API token = params[:token] if token - ::Gitlab::Database::LoadBalancing::RackMiddleware - .stick_or_unstick(env, :runner, token) + ::Ci::Runner.sticking.stick_or_unstick_request(env, :runner, token) end strong_memoize(:current_runner) do @@ -80,8 +79,9 @@ module API id = params[:id] if id - ::Gitlab::Database::LoadBalancing::RackMiddleware - .stick_or_unstick(env, :build, id) + ::Ci::Build + .sticking + .stick_or_unstick_request(env, :build, id) end strong_memoize(:current_job) do diff --git a/lib/api/ci/resource_groups.rb b/lib/api/ci/resource_groups.rb new file mode 100644 index 00000000000..616bec499d4 --- /dev/null +++ b/lib/api/ci/resource_groups.rb @@ -0,0 +1,52 @@ +# frozen_string_literal: true + +module API + module Ci + class ResourceGroups < ::API::Base + before { authenticate! } + + feature_category :continuous_delivery + + params do + requires :id, type: String, desc: 'The ID of a project' + end + resource :projects, requirements: ::API::API::NAMESPACE_OR_PROJECT_REQUIREMENTS do + desc 'Get a single resource group' do + success Entities::Ci::ResourceGroup + end + params do + requires :key, type: String, desc: 'The key of the resource group' + end + get ':id/resource_groups/:key' do + authorize! :read_resource_group, resource_group + + present resource_group, with: Entities::Ci::ResourceGroup + end + + desc 'Edit a resource group' do + success Entities::Ci::ResourceGroup + end + params do + requires :key, type: String, desc: 'The key of the resource group' + optional :process_mode, type: String, desc: 'The process mode', + values: ::Ci::ResourceGroup.process_modes.keys + end + put ':id/resource_groups/:key' do + authorize! :update_resource_group, resource_group + + if resource_group.update(declared_params(include_missing: false)) + present resource_group, with: Entities::Ci::ResourceGroup + else + render_validation_error!(resource_group) + end + end + end + + helpers do + def resource_group + @resource_group ||= user_project.resource_groups.find_by_key!(params[:key]) + end + end + end + end +end diff --git a/lib/api/ci/runners.rb b/lib/api/ci/runners.rb index 93a40925c21..ef712c84804 100644 --- a/lib/api/ci/runners.rb +++ b/lib/api/ci/runners.rb @@ -130,6 +130,20 @@ module API present paginate(jobs), with: Entities::Ci::JobBasicWithProject end + + desc 'Reset runner authentication token' do + success Entities::Ci::ResetTokenResult + end + params do + requires :id, type: Integer, desc: 'The ID of the runner' + end + post ':id/reset_authentication_token' do + runner = get_runner(params[:id]) + authenticate_update_runner!(runner) + + runner.reset_token! + present runner.token, with: Entities::Ci::ResetTokenResult + end end params do @@ -190,7 +204,7 @@ module API not_found!('Runner') unless runner_project runner = runner_project.runner - forbidden!("Only one project associated with the runner. Please remove the runner instead") if runner.projects.count == 1 + forbidden!("Only one project associated with the runner. Please remove the runner instead") if runner.runner_projects.count == 1 destroy_conditionally!(runner_project) end @@ -226,13 +240,13 @@ module API before { authenticate_non_get! } desc 'Resets runner registration token' do - success Entities::Ci::ResetRegistrationTokenResult + success Entities::Ci::ResetTokenResult end post 'reset_registration_token' do authorize! :update_runners_registration_token ApplicationSetting.current.reset_runners_registration_token! - present ApplicationSetting.current_without_cache.runners_registration_token, with: Entities::Ci::ResetRegistrationTokenResult + present ApplicationSetting.current_without_cache.runners_registration_token, with: Entities::Ci::ResetTokenResult end end @@ -243,14 +257,14 @@ module API before { authenticate_non_get! } desc 'Resets runner registration token' do - success Entities::Ci::ResetRegistrationTokenResult + success Entities::Ci::ResetTokenResult end post ':id/runners/reset_registration_token' do project = find_project! user_project.id authorize! :update_runners_registration_token, project project.reset_runners_token! - present project.runners_token, with: Entities::Ci::ResetRegistrationTokenResult + present project.runners_token, with: Entities::Ci::ResetTokenResult end end @@ -261,14 +275,14 @@ module API before { authenticate_non_get! } desc 'Resets runner registration token' do - success Entities::Ci::ResetRegistrationTokenResult + success Entities::Ci::ResetTokenResult end post ':id/runners/reset_registration_token' do group = find_group! user_group.id authorize! :update_runners_registration_token, group group.reset_runners_token! - present group.runners_token, with: Entities::Ci::ResetRegistrationTokenResult + present group.runners_token, with: Entities::Ci::ResetTokenResult end end @@ -317,7 +331,7 @@ module API def authenticate_delete_runner!(runner) return if current_user.admin? - forbidden!("Runner associated with more than one project") if runner.projects.count > 1 + forbidden!("Runner associated with more than one project") if runner.runner_projects.count > 1 forbidden!("No access granted") unless can?(current_user, :delete_runner, runner) end diff --git a/lib/api/composer_packages.rb b/lib/api/composer_packages.rb index 7b3750b37ee..94cad7e6c65 100644 --- a/lib/api/composer_packages.rb +++ b/lib/api/composer_packages.rb @@ -137,12 +137,12 @@ module API bad_request! end - track_package_event('push_package', :composer, project: authorized_user_project, user: current_user, namespace: authorized_user_project.namespace) - ::Packages::Composer::CreatePackageService .new(authorized_user_project, current_user, declared_params.merge(build: current_authenticated_job)) .execute + track_package_event('push_package', :composer, project: authorized_user_project, user: current_user, namespace: authorized_user_project.namespace) + created! end diff --git a/lib/api/container_repositories.rb b/lib/api/container_repositories.rb index c84527f26e7..9cd3e449687 100644 --- a/lib/api/container_repositories.rb +++ b/lib/api/container_repositories.rb @@ -3,6 +3,8 @@ module API class ContainerRepositories < ::API::Base include Gitlab::Utils::StrongMemoize + include ::API::Helpers::ContainerRegistryHelpers + helpers ::API::Helpers::PackagesHelpers before { authenticate! } diff --git a/lib/api/entities/basic_project_details.rb b/lib/api/entities/basic_project_details.rb index 5c33af86b84..e96504db53e 100644 --- a/lib/api/entities/basic_project_details.rb +++ b/lib/api/entities/basic_project_details.rb @@ -39,11 +39,11 @@ module API # rubocop: disable CodeReuse/ActiveRecord def self.preload_relation(projects_relation, options = {}) # Preloading topics, should be done with using only `:topics`, - # as `:topics` are defined as: `has_many :topics, through: :taggings` + # as `:topics` are defined as: `has_many :topics, through: :project_topics` # N+1 is solved then by using `subject.topics.map(&:name)` # MR describing the solution: https://gitlab.com/gitlab-org/gitlab-foss/merge_requests/20555 projects_relation.preload(:project_feature, :route) - .preload(:import_state, :topics, :topics_acts_as_taggable) + .preload(:import_state, :topics) .preload(:auto_devops) .preload(namespace: [:route, :owner]) end diff --git a/lib/api/entities/ci/reset_registration_token_result.rb b/lib/api/entities/ci/reset_registration_token_result.rb deleted file mode 100644 index 23426432f68..00000000000 --- a/lib/api/entities/ci/reset_registration_token_result.rb +++ /dev/null @@ -1,11 +0,0 @@ -# frozen_string_literal: true - -module API - module Entities - module Ci - class ResetRegistrationTokenResult < Grape::Entity - expose(:token) {|object| object} - end - end - end -end diff --git a/lib/api/entities/ci/reset_token_result.rb b/lib/api/entities/ci/reset_token_result.rb new file mode 100644 index 00000000000..4dbf831582b --- /dev/null +++ b/lib/api/entities/ci/reset_token_result.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +module API + module Entities + module Ci + class ResetTokenResult < Grape::Entity + expose(:token) {|object| object} + end + end + end +end diff --git a/lib/api/entities/ci/resource_group.rb b/lib/api/entities/ci/resource_group.rb new file mode 100644 index 00000000000..0afadfa9e2a --- /dev/null +++ b/lib/api/entities/ci/resource_group.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +module API + module Entities + module Ci + class ResourceGroup < Grape::Entity + expose :id, :key, :process_mode, :created_at, :updated_at + end + end + end +end diff --git a/lib/api/entities/clusters/agent_authorization.rb b/lib/api/entities/clusters/agent_authorization.rb index 6c533fff105..7bbe0f1ec45 100644 --- a/lib/api/entities/clusters/agent_authorization.rb +++ b/lib/api/entities/clusters/agent_authorization.rb @@ -5,7 +5,7 @@ module API module Clusters class AgentAuthorization < Grape::Entity expose :agent_id, as: :id - expose :project, with: Entities::ProjectIdentity, as: :config_project + expose :config_project, with: Entities::ProjectIdentity expose :config, as: :configuration end end diff --git a/lib/api/entities/environment_basic.rb b/lib/api/entities/environment_basic.rb index 061d4739874..d9894eac147 100644 --- a/lib/api/entities/environment_basic.rb +++ b/lib/api/entities/environment_basic.rb @@ -3,7 +3,7 @@ module API module Entities class EnvironmentBasic < Grape::Entity - expose :id, :name, :slug, :external_url + expose :id, :name, :slug, :external_url, :created_at, :updated_at end end end diff --git a/lib/api/entities/feature_flag.rb b/lib/api/entities/feature_flag.rb index f383eabd5dc..9dec3873504 100644 --- a/lib/api/entities/feature_flag.rb +++ b/lib/api/entities/feature_flag.rb @@ -9,7 +9,9 @@ module API expose :version expose :created_at expose :updated_at - expose :scopes, using: FeatureFlag::LegacyScope + expose :scopes do |_ff| + [] + end expose :strategies, using: FeatureFlag::Strategy end end diff --git a/lib/api/entities/feature_flag/detailed_legacy_scope.rb b/lib/api/entities/feature_flag/detailed_legacy_scope.rb deleted file mode 100644 index 47078c1dfde..00000000000 --- a/lib/api/entities/feature_flag/detailed_legacy_scope.rb +++ /dev/null @@ -1,11 +0,0 @@ -# frozen_string_literal: true - -module API - module Entities - class FeatureFlag < Grape::Entity - class DetailedLegacyScope < LegacyScope - expose :name - end - end - end -end diff --git a/lib/api/entities/feature_flag/legacy_scope.rb b/lib/api/entities/feature_flag/legacy_scope.rb deleted file mode 100644 index 7329f71c599..00000000000 --- a/lib/api/entities/feature_flag/legacy_scope.rb +++ /dev/null @@ -1,16 +0,0 @@ -# frozen_string_literal: true - -module API - module Entities - class FeatureFlag < Grape::Entity - class LegacyScope < Grape::Entity - expose :id - expose :active - expose :environment_scope - expose :strategies - expose :created_at - expose :updated_at - end - end - end -end diff --git a/lib/api/entities/group_detail.rb b/lib/api/entities/group_detail.rb index 61f35d0f784..5eaccbc7154 100644 --- a/lib/api/entities/group_detail.rb +++ b/lib/api/entities/group_detail.rb @@ -16,7 +16,7 @@ module API options: { only_owned: true, limit: projects_limit } ).execute - Entities::Project.prepare_relation(projects) + Entities::Project.prepare_relation(projects, options) end expose :shared_projects, using: Entities::Project do |group, options| @@ -26,7 +26,7 @@ module API options: { only_shared: true, limit: projects_limit } ).execute - Entities::Project.prepare_relation(projects) + Entities::Project.prepare_relation(projects, options) end def projects_limit diff --git a/lib/api/entities/issuable_entity.rb b/lib/api/entities/issuable_entity.rb index fd5d6c8137f..e2c674c0b8b 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], replace_methods: false) do |models, loader, args| + BatchLoader.for(object).batch(key: [current_user, :issuable_metadata]) do |models, loader, args| current_user = args[:key].first issuable_metadata = Gitlab::IssuableMetadata.new(current_user, models) diff --git a/lib/api/entities/namespace_basic.rb b/lib/api/entities/namespace_basic.rb index f968a074bd2..2b9dd0b5f4d 100644 --- a/lib/api/entities/namespace_basic.rb +++ b/lib/api/entities/namespace_basic.rb @@ -6,7 +6,7 @@ module API expose :id, :name, :path, :kind, :full_path, :parent_id, :avatar_url expose :web_url do |namespace| - if namespace.user? + if namespace.user_namespace? Gitlab::Routing.url_helpers.user_url(namespace.owner) else namespace.web_url diff --git a/lib/api/entities/project.rb b/lib/api/entities/project.rb index b0e53ac3794..df0c1d7a4c5 100644 --- a/lib/api/entities/project.rb +++ b/lib/api/entities/project.rb @@ -132,7 +132,7 @@ module API def self.preload_relation(projects_relation, options = {}) # Preloading topics, should be done with using only `:topics`, - # as `:topics` are defined as: `has_many :topics, through: :taggings` + # as `:topics` are defined as: `has_many :topics, through: :project_topics` # N+1 is solved then by using `subject.topics.map(&:name)` # MR describing the solution: https://gitlab.com/gitlab-org/gitlab-foss/merge_requests/20555 super(projects_relation).preload(group: :namespace_settings) @@ -144,7 +144,7 @@ module API .preload(project_group_links: { group: :route }, fork_network: :root_project, fork_network_member: :forked_from_project, - forked_from_project: [:route, :topics, :topics_acts_as_taggable, :group, :project_feature, namespace: [:route, :owner]]) + forked_from_project: [:route, :topics, :group, :project_feature, namespace: [:route, :owner]]) end # rubocop: enable CodeReuse/ActiveRecord diff --git a/lib/api/entities/user.rb b/lib/api/entities/user.rb index 5c46233a639..ff711b4dec2 100644 --- a/lib/api/entities/user.rb +++ b/lib/api/entities/user.rb @@ -4,7 +4,7 @@ module API module Entities class User < UserBasic include UsersHelper - include ActionView::Helpers::SanitizeHelper + include TimeZoneHelper expose :created_at, if: ->(user, opts) { Ability.allowed?(opts[:current_user], :read_user_profile, user) } expose :bio, :location, :public_email, :skype, :linkedin, :twitter, :website_url, :organization, :job_title, :pronouns @@ -18,11 +18,8 @@ module API expose :following, if: ->(user, opts) { Ability.allowed?(opts[:current_user], :read_user_profile, user) } do |user| user.followees.size end - - # This is only for multi version compatibility reasons, as we removed user.bio_html - # to be removed in 14.4 - expose :bio_html do |user| - strip_tags(user.bio) + expose :local_time do |user| + local_time(user.timezone) end end end diff --git a/lib/api/error_tracking.rb b/lib/api/error_tracking.rb deleted file mode 100644 index 369efe3bf8c..00000000000 --- a/lib/api/error_tracking.rb +++ /dev/null @@ -1,63 +0,0 @@ -# frozen_string_literal: true - -module API - class ErrorTracking < ::API::Base - before { authenticate! } - - feature_category :error_tracking - - helpers do - def project_setting - @project_setting ||= user_project.error_tracking_setting - end - end - - params do - requires :id, type: String, desc: 'The ID of a project' - end - - resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do - before do - authorize! :admin_operations, user_project - - not_found!('Error Tracking Setting') unless project_setting - end - - desc 'Get error tracking settings for the project' do - detail 'This feature was introduced in GitLab 12.7.' - success Entities::ErrorTracking::ProjectSetting - end - - get ':id/error_tracking/settings' do - present project_setting, with: Entities::ErrorTracking::ProjectSetting - end - - desc 'Enable or disable error tracking settings for the project' do - detail 'This feature was introduced in GitLab 12.8.' - success Entities::ErrorTracking::ProjectSetting - end - params do - requires :active, type: Boolean, desc: 'Specifying whether to enable or disable error tracking settings', allow_blank: false - optional :integrated, type: Boolean, desc: 'Specifying whether to enable or disable integrated error tracking' - end - - patch ':id/error_tracking/settings/' do - update_params = { - error_tracking_setting_attributes: { enabled: params[:active] } - } - - unless params[:integrated].nil? - update_params[:error_tracking_setting_attributes][:integrated] = params[:integrated] - end - - result = ::Projects::Operations::UpdateService.new(user_project, current_user, update_params).execute - - if result[:status] == :success - present project_setting, with: Entities::ErrorTracking::ProjectSetting - else - result - end - end - end - end -end diff --git a/lib/api/error_tracking/client_keys.rb b/lib/api/error_tracking/client_keys.rb new file mode 100644 index 00000000000..e97df03b6f0 --- /dev/null +++ b/lib/api/error_tracking/client_keys.rb @@ -0,0 +1,50 @@ +# frozen_string_literal: true + +module API + class ErrorTracking::ClientKeys < ::API::Base + before { authenticate! } + + feature_category :error_tracking + + params do + requires :id, type: String, desc: 'The ID of a project' + end + + resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do + segment ':id/error_tracking' do + before do + authorize! :admin_operations, user_project + end + + desc 'List all client keys' do + detail 'This feature was introduced in GitLab 14.3.' + success Entities::ErrorTracking::ClientKey + end + get '/client_keys' do + collection = user_project.error_tracking_client_keys + + present paginate(collection), with: Entities::ErrorTracking::ClientKey + end + + desc 'Create a client key' do + detail 'This feature was introduced in GitLab 14.3.' + success Entities::ErrorTracking::ClientKey + end + post '/client_keys' do + key = user_project.error_tracking_client_keys.create! + + present key, with: Entities::ErrorTracking::ClientKey + end + + desc 'Delete a client key' do + detail 'This feature was introduced in GitLab 14.3.' + success Entities::ErrorTracking::ClientKey + end + delete '/client_keys/:key_id' do + key = user_project.error_tracking_client_keys.find(params[:key_id]) + key.destroy! + end + end + end + end +end diff --git a/lib/api/error_tracking/collector.rb b/lib/api/error_tracking/collector.rb new file mode 100644 index 00000000000..22fbd3a1118 --- /dev/null +++ b/lib/api/error_tracking/collector.rb @@ -0,0 +1,135 @@ +# frozen_string_literal: true + +module API + # This API is responsible for collecting error tracking information + # from sentry client. It allows us to use GitLab as an alternative to + # sentry backend. For more details see https://gitlab.com/gitlab-org/gitlab/-/issues/329596. + class ErrorTracking::Collector < ::API::Base + feature_category :error_tracking + + content_type :envelope, 'application/x-sentry-envelope' + content_type :json, 'application/json' + content_type :txt, 'text/plain' + default_format :envelope + + before do + not_found!('Project') unless project + not_found! unless feature_enabled? + not_found! unless active_client_key? + end + + helpers do + def project + @project ||= find_project(params[:id]) + end + + def feature_enabled? + project.error_tracking_setting&.enabled? && + project.error_tracking_setting&.integrated_client? + end + + def find_client_key(public_key) + return unless public_key.present? + + project.error_tracking_client_keys.active.find_by_public_key(public_key) + end + + def active_client_key? + public_key = extract_public_key + + find_client_key(public_key) + end + + def extract_public_key + # Some SDK send public_key as a param. In this case we don't need to parse headers. + return params[:sentry_key] if params[:sentry_key].present? + + begin + ::ErrorTracking::Collector::SentryAuthParser.parse(request)[:public_key] + rescue StandardError + bad_request!('Failed to parse sentry request') + end + end + end + + desc 'Submit error tracking event to the project as envelope' do + detail 'This feature was introduced in GitLab 14.1.' + end + params do + requires :id, type: String, desc: 'The ID of a project' + end + post 'error_tracking/collector/api/:id/envelope' do + # There is a reason why we have such uncommon path. + # We depend on a client side error tracking software which + # modifies URL for its own reasons. + # + # When we give user a URL like this + # HOST/api/v4/error_tracking/collector/123 + # + # Then error tracking software will convert it like this: + # HOST/api/v4/error_tracking/collector/api/123/envelope/ + + begin + parsed_request = ::ErrorTracking::Collector::SentryRequestParser.parse(request) + rescue StandardError + bad_request!('Failed to parse sentry request') + end + + type = parsed_request[:request_type] + + # Sentry sends 2 requests on each exception: transaction and event. + # Everything else is not a desired behavior. + unless type == 'transaction' || type == 'event' + render_api_error!('400 Bad Request', 400) + + break + end + + # We don't have use for transaction request yet, + # so we record only event one. + if type == 'event' + ::ErrorTracking::CollectErrorService + .new(project, nil, event: parsed_request[:event]) + .execute + end + + # Collector should never return any information back. + # Because DSN and public key are designed for public use, + # it is safe only for submission of new events. + no_content! + end + + desc 'Submit error tracking event to the project' do + detail 'This feature was introduced in GitLab 14.1.' + end + params do + requires :id, type: String, desc: 'The ID of a project' + end + post 'error_tracking/collector/api/:id/store' do + # There is a reason why we have such uncommon path. + # We depend on a client side error tracking software which + # modifies URL for its own reasons. + # + # When we give user a URL like this + # HOST/api/v4/error_tracking/collector/123 + # + # Then error tracking software will convert it like this: + # HOST/api/v4/error_tracking/collector/api/123/store/ + + begin + parsed_body = Gitlab::Json.parse(request.body.read) + rescue StandardError + bad_request!('Failed to parse sentry request') + end + + ::ErrorTracking::CollectErrorService + .new(project, nil, event: parsed_body) + .execute + + # Collector should never return any information back. + # Because DSN and public key are designed for public use, + # it is safe only for submission of new events. + no_content! + end + end +end diff --git a/lib/api/error_tracking/project_settings.rb b/lib/api/error_tracking/project_settings.rb new file mode 100644 index 00000000000..74432d1eaec --- /dev/null +++ b/lib/api/error_tracking/project_settings.rb @@ -0,0 +1,63 @@ +# frozen_string_literal: true + +module API + class ErrorTracking::ProjectSettings < ::API::Base + before { authenticate! } + + feature_category :error_tracking + + helpers do + def project_setting + @project_setting ||= user_project.error_tracking_setting + end + end + + params do + requires :id, type: String, desc: 'The ID of a project' + end + + resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do + before do + authorize! :admin_operations, user_project + + not_found!('Error Tracking Setting') unless project_setting + end + + desc 'Get error tracking settings for the project' do + detail 'This feature was introduced in GitLab 12.7.' + success Entities::ErrorTracking::ProjectSetting + end + + get ':id/error_tracking/settings' do + present project_setting, with: Entities::ErrorTracking::ProjectSetting + end + + desc 'Enable or disable error tracking settings for the project' do + detail 'This feature was introduced in GitLab 12.8.' + success Entities::ErrorTracking::ProjectSetting + end + params do + requires :active, type: Boolean, desc: 'Specifying whether to enable or disable error tracking settings', allow_blank: false + optional :integrated, type: Boolean, desc: 'Specifying whether to enable or disable integrated error tracking' + end + + patch ':id/error_tracking/settings/' do + update_params = { + error_tracking_setting_attributes: { enabled: params[:active] } + } + + unless params[:integrated].nil? + update_params[:error_tracking_setting_attributes][:integrated] = params[:integrated] + end + + result = ::Projects::Operations::UpdateService.new(user_project, current_user, update_params).execute + + if result[:status] == :success + present project_setting, with: Entities::ErrorTracking::ProjectSetting + else + result + end + end + end + end +end diff --git a/lib/api/error_tracking_client_keys.rb b/lib/api/error_tracking_client_keys.rb deleted file mode 100644 index eaa84b7186c..00000000000 --- a/lib/api/error_tracking_client_keys.rb +++ /dev/null @@ -1,50 +0,0 @@ -# frozen_string_literal: true - -module API - class ErrorTrackingClientKeys < ::API::Base - before { authenticate! } - - feature_category :error_tracking - - params do - requires :id, type: String, desc: 'The ID of a project' - end - - resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do - segment ':id/error_tracking' do - before do - authorize! :admin_operations, user_project - end - - desc 'List all client keys' do - detail 'This feature was introduced in GitLab 14.3.' - success Entities::ErrorTracking::ClientKey - end - get '/client_keys' do - collection = user_project.error_tracking_client_keys - - present paginate(collection), with: Entities::ErrorTracking::ClientKey - end - - desc 'Create a client key' do - detail 'This feature was introduced in GitLab 14.3.' - success Entities::ErrorTracking::ClientKey - end - post '/client_keys' do - key = user_project.error_tracking_client_keys.create! - - present key, with: Entities::ErrorTracking::ClientKey - end - - desc 'Delete a client key' do - detail 'This feature was introduced in GitLab 14.3.' - success Entities::ErrorTracking::ClientKey - end - delete '/client_keys/:key_id' do - key = user_project.error_tracking_client_keys.find(params[:key_id]) - key.destroy! - end - end - end - end -end diff --git a/lib/api/error_tracking_collector.rb b/lib/api/error_tracking_collector.rb deleted file mode 100644 index b1e0f6a858a..00000000000 --- a/lib/api/error_tracking_collector.rb +++ /dev/null @@ -1,135 +0,0 @@ -# frozen_string_literal: true - -module API - # This API is responsible for collecting error tracking information - # from sentry client. It allows us to use GitLab as an alternative to - # sentry backend. For more details see https://gitlab.com/gitlab-org/gitlab/-/issues/329596. - class ErrorTrackingCollector < ::API::Base - feature_category :error_tracking - - content_type :envelope, 'application/x-sentry-envelope' - content_type :json, 'application/json' - content_type :txt, 'text/plain' - default_format :envelope - - before do - not_found!('Project') unless project - not_found! unless feature_enabled? - not_found! unless active_client_key? - end - - helpers do - def project - @project ||= find_project(params[:id]) - end - - def feature_enabled? - project.error_tracking_setting&.enabled? && - project.error_tracking_setting&.integrated_client? - end - - def find_client_key(public_key) - return unless public_key.present? - - project.error_tracking_client_keys.active.find_by_public_key(public_key) - end - - def active_client_key? - public_key = extract_public_key - - find_client_key(public_key) - end - - def extract_public_key - # Some SDK send public_key as a param. In this case we don't need to parse headers. - return params[:sentry_key] if params[:sentry_key].present? - - begin - ::ErrorTracking::Collector::SentryAuthParser.parse(request)[:public_key] - rescue StandardError - bad_request!('Failed to parse sentry request') - end - end - end - - desc 'Submit error tracking event to the project as envelope' do - detail 'This feature was introduced in GitLab 14.1.' - end - params do - requires :id, type: String, desc: 'The ID of a project' - end - post 'error_tracking/collector/api/:id/envelope' do - # There is a reason why we have such uncommon path. - # We depend on a client side error tracking software which - # modifies URL for its own reasons. - # - # When we give user a URL like this - # HOST/api/v4/error_tracking/collector/123 - # - # Then error tracking software will convert it like this: - # HOST/api/v4/error_tracking/collector/api/123/envelope/ - - begin - parsed_request = ::ErrorTracking::Collector::SentryRequestParser.parse(request) - rescue StandardError - bad_request!('Failed to parse sentry request') - end - - type = parsed_request[:request_type] - - # Sentry sends 2 requests on each exception: transaction and event. - # Everything else is not a desired behavior. - unless type == 'transaction' || type == 'event' - render_api_error!('400 Bad Request', 400) - - break - end - - # We don't have use for transaction request yet, - # so we record only event one. - if type == 'event' - ::ErrorTracking::CollectErrorService - .new(project, nil, event: parsed_request[:event]) - .execute - end - - # Collector should never return any information back. - # Because DSN and public key are designed for public use, - # it is safe only for submission of new events. - no_content! - end - - desc 'Submit error tracking event to the project' do - detail 'This feature was introduced in GitLab 14.1.' - end - params do - requires :id, type: String, desc: 'The ID of a project' - end - post 'error_tracking/collector/api/:id/store' do - # There is a reason why we have such uncommon path. - # We depend on a client side error tracking software which - # modifies URL for its own reasons. - # - # When we give user a URL like this - # HOST/api/v4/error_tracking/collector/123 - # - # Then error tracking software will convert it like this: - # HOST/api/v4/error_tracking/collector/api/123/store/ - - begin - parsed_body = Gitlab::Json.parse(request.body.read) - rescue StandardError - bad_request!('Failed to parse sentry request') - end - - ::ErrorTracking::CollectErrorService - .new(project, nil, event: parsed_body) - .execute - - # Collector should never return any information back. - # Because DSN and public key are designed for public use, - # it is safe only for submission of new events. - no_content! - end - end -end diff --git a/lib/api/group_container_repositories.rb b/lib/api/group_container_repositories.rb index 96175f31696..55e18fd1370 100644 --- a/lib/api/group_container_repositories.rb +++ b/lib/api/group_container_repositories.rb @@ -3,6 +3,7 @@ module API class GroupContainerRepositories < ::API::Base include PaginationParams + include ::API::Helpers::ContainerRegistryHelpers helpers ::API::Helpers::PackagesHelpers diff --git a/lib/api/group_export.rb b/lib/api/group_export.rb index 7e4fdba6033..25cc4e53bd2 100644 --- a/lib/api/group_export.rb +++ b/lib/api/group_export.rb @@ -2,8 +2,6 @@ module API class GroupExport < ::API::Base - helpers Helpers::RateLimiter - before do not_found! unless Feature.enabled?(:group_import_export, user_group, default_enabled: true) diff --git a/lib/api/groups.rb b/lib/api/groups.rb index a1123b6291b..680e3a6e994 100644 --- a/lib/api/groups.rb +++ b/lib/api/groups.rb @@ -92,7 +92,7 @@ module API projects, options = with_custom_attributes(projects, options) - present options[:with].prepare_relation(projects), options + present options[:with].prepare_relation(projects, options), options end def present_groups(params, groups) diff --git a/lib/api/helm_packages.rb b/lib/api/helm_packages.rb index 8a7e84c9f87..4278d17e003 100644 --- a/lib/api/helm_packages.rb +++ b/lib/api/helm_packages.rb @@ -11,7 +11,8 @@ module API feature_category :package_registry PACKAGE_FILENAME = 'package.tgz' - FILE_NAME_REQUIREMENTS = { + HELM_REQUIREMENTS = { + channel: API::NO_SLASH_URL_PART_REGEX, file_name: API::NO_SLASH_URL_PART_REGEX }.freeze @@ -33,7 +34,7 @@ module API requires :id, type: String, desc: 'The ID or full path of a project' end resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do - namespace ':id/packages/helm' do + namespace ':id/packages/helm', requirements: HELM_REQUIREMENTS do desc 'Download a chart index' do detail 'This feature was introduced in GitLab 14.0' end @@ -58,7 +59,7 @@ module API requires :channel, type: String, desc: 'Helm channel', regexp: Gitlab::Regex.helm_channel_regex requires :file_name, type: String, desc: 'Helm package file name' end - get ":channel/charts/:file_name.tgz", requirements: FILE_NAME_REQUIREMENTS do + get ":channel/charts/:file_name.tgz" do authorize_read_package!(authorized_user_project) package_file = Packages::Helm::PackageFilesFinder.new(authorized_user_project, params[:channel], file_name: "#{params[:file_name]}.tgz").most_recent! diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb index 9c347148fd0..f9ba5ba8186 100644 --- a/lib/api/helpers.rb +++ b/lib/api/helpers.rb @@ -75,8 +75,9 @@ module API save_current_user_in_env(@current_user) if @current_user if @current_user - ::Gitlab::Database::LoadBalancing::RackMiddleware - .stick_or_unstick(env, :user, @current_user.id) + ::ApplicationRecord + .sticking + .stick_or_unstick_request(env, :user, @current_user.id) end @current_user @@ -429,8 +430,8 @@ module API render_api_error!('406 Not Acceptable', 406) end - def service_unavailable! - render_api_error!('503 Service Unavailable', 503) + def service_unavailable!(message = nil) + render_api_error!(message || '503 Service Unavailable', 503) end def conflict!(message = nil) @@ -624,6 +625,12 @@ module API {} end + def validate_anonymous_search_access! + return if current_user.present? || Feature.disabled?(:disable_anonymous_search, type: :ops) + + unprocessable_entity!('User must be authenticated to use search') + end + private # rubocop:disable Gitlab/ModuleWithInstanceVariables diff --git a/lib/api/helpers/common_helpers.rb b/lib/api/helpers/common_helpers.rb index 02942820982..855648f2ef0 100644 --- a/lib/api/helpers/common_helpers.rb +++ b/lib/api/helpers/common_helpers.rb @@ -34,7 +34,7 @@ module API end def endpoint_id - "#{request.request_method} #{route.origin}" + ::API::Base.endpoint_id_for_route(route) end end end diff --git a/lib/api/helpers/container_registry_helpers.rb b/lib/api/helpers/container_registry_helpers.rb new file mode 100644 index 00000000000..9c844e364eb --- /dev/null +++ b/lib/api/helpers/container_registry_helpers.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +module API + module Helpers + module ContainerRegistryHelpers + extend ActiveSupport::Concern + + included do + rescue_from Faraday::Error, ContainerRegistry::Path::InvalidRegistryPathError do |e| + service_unavailable!('We are having trouble connecting to the Container Registry. If this error persists, please review the troubleshooting documentation.') + end + end + end + end +end diff --git a/lib/api/helpers/integrations_helpers.rb b/lib/api/helpers/integrations_helpers.rb index 06539772568..e0ef9099104 100644 --- a/lib/api/helpers/integrations_helpers.rb +++ b/lib/api/helpers/integrations_helpers.rb @@ -2,7 +2,7 @@ module API module Helpers - # Helpers module for API::Services + # Helpers module for API::Integrations # # The data structures inside this model are returned using class methods, # allowing EE to extend them where necessary. @@ -340,7 +340,7 @@ module API required: true, name: :webhook, type: String, - desc: 'Discord webhook. e.g. https://discordapp.com/api/webhooks/…' + desc: 'Discord webhook. For example, https://discord.com/api/webhooks/…' } ], 'drone-ci' => [ diff --git a/lib/api/integrations.rb b/lib/api/integrations.rb new file mode 100644 index 00000000000..926cde340a0 --- /dev/null +++ b/lib/api/integrations.rb @@ -0,0 +1,178 @@ +# frozen_string_literal: true +module API + class Integrations < ::API::Base + feature_category :integrations + + integrations = Helpers::IntegrationsHelpers.integrations + integration_classes = Helpers::IntegrationsHelpers.integration_classes + + if Rails.env.development? + integrations['mock-ci'] = [ + { + required: true, + name: :mock_service_url, + type: String, + desc: 'URL to the mock integration' + } + ] + integrations['mock-deployment'] = [] + integrations['mock-monitoring'] = [] + + integration_classes += Helpers::IntegrationsHelpers.development_integration_classes + end + + INTEGRATIONS = integrations.freeze + + integration_classes.each do |integration| + event_names = integration.try(:event_names) || next + event_names.each do |event_name| + INTEGRATIONS[integration.to_param.tr("_", "-")] << { + required: false, + name: event_name.to_sym, + type: String, + desc: IntegrationsHelper.integration_event_description(integration, event_name) + } + end + end + + TRIGGER_INTEGRATIONS = { + 'mattermost-slash-commands' => [ + { + name: :token, + type: String, + desc: 'The Mattermost token' + } + ], + 'slack-slash-commands' => [ + { + name: :token, + type: String, + desc: 'The Slack token' + } + ] + }.freeze + + helpers do + def integration_attributes(integration) + integration.fields.inject([]) do |arr, hash| + arr << hash[:name].to_sym + end + end + end + + # The API officially documents only the `:id/integrations` API paths. + # We support the older `id:/services` path for backwards-compatibility in API V4. + # The support for `:id/services` can be dropped if we create an API V5. + [':id/services', ':id/integrations'].each do |path| + params do + requires :id, type: String, desc: 'The ID of a project' + end + resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do + before { authenticate! } + before { authorize_admin_project } + + desc 'Get all active project integrations' do + success Entities::ProjectIntegrationBasic + end + get path do + integrations = user_project.integrations.active + + present integrations, with: Entities::ProjectIntegrationBasic + end + + INTEGRATIONS.each do |slug, settings| + desc "Set #{slug} integration for project" + params do + settings.each do |setting| + if setting[:required] + requires setting[:name], type: setting[:type], desc: setting[:desc] + else + optional setting[:name], type: setting[:type], desc: setting[:desc] + end + end + end + put "#{path}/#{slug}" do + integration = user_project.find_or_initialize_integration(slug.underscore) + params = declared_params(include_missing: false).merge(active: true) + + if integration.update(params) + present integration, with: Entities::ProjectIntegration + else + render_api_error!('400 Bad Request', 400) + end + end + end + + desc "Delete an integration from a project" + params do + requires :slug, type: String, values: INTEGRATIONS.keys, desc: 'The name of the integration' + end + delete "#{path}/:slug" do + integration = user_project.find_or_initialize_integration(params[:slug].underscore) + + destroy_conditionally!(integration) do + attrs = integration_attributes(integration).index_with { nil }.merge(active: false) + + render_api_error!('400 Bad Request', 400) unless integration.update(attrs) + end + end + + desc 'Get the integration settings for a project' do + success Entities::ProjectIntegration + end + params do + requires :slug, type: String, values: INTEGRATIONS.keys, desc: 'The name of the integration' + end + get "#{path}/:slug" do + integration = user_project.find_or_initialize_integration(params[:slug].underscore) + + not_found!('Integration') unless integration&.persisted? + + present integration, with: Entities::ProjectIntegration + end + end + + TRIGGER_INTEGRATIONS.each do |integration_slug, settings| + helpers do + def slash_command_integration(project, integration_slug, params) + project.integrations.active.find do |integration| + integration.try(:token) == params[:token] && integration.to_param == integration_slug.underscore + end + end + end + + params do + requires :id, type: String, desc: 'The ID of a project' + end + resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do + desc "Trigger a slash command for #{integration_slug}" do + detail 'Added in GitLab 8.13' + end + params do + settings.each do |setting| + requires setting[:name], type: setting[:type], desc: setting[:desc] + end + end + post "#{path}/#{integration_slug.underscore}/trigger" do + project = find_project(params[:id]) + + # This is not accurate, but done to prevent leakage of the project names + not_found!('Integration') unless project + + integration = slash_command_integration(project, integration_slug, params) + result = integration.try(:trigger, params) + + if result + status result[:status] || 200 + present result + else + not_found!('Integration') + end + end + end + end + end + end +end + +API::Integrations.prepend_mod_with('API::Integrations') diff --git a/lib/api/internal/base.rb b/lib/api/internal/base.rb index d740c626557..dc9257ebd62 100644 --- a/lib/api/internal/base.rb +++ b/lib/api/internal/base.rb @@ -164,7 +164,7 @@ module API # # Check whether an SSH key is known to GitLab # - get '/authorized_keys', feature_category: :source_code_management do + get '/authorized_keys', feature_category: :source_code_management, urgency: :high do fingerprint = Gitlab::InsecureKeyFingerprint.new(params.fetch(:key)).fingerprint_sha256 key = Key.find_by_fingerprint_sha256(fingerprint) diff --git a/lib/api/internal/kubernetes.rb b/lib/api/internal/kubernetes.rb index d1ad3c1feb1..f3974236fe3 100644 --- a/lib/api/internal/kubernetes.rb +++ b/lib/api/internal/kubernetes.rb @@ -79,25 +79,6 @@ module API gitaly_repository: gitaly_repository(project) } end - - desc 'Gets project info' do - detail 'Retrieves project info (if authorized)' - end - route_setting :authentication, cluster_agent_token_allowed: true - get '/project_info' do - project = find_project(params[:id]) - - unless Guest.can?(:download_code, project) || agent.has_access_to?(project) - not_found! - end - - status 200 - { - project_id: project.id, - gitaly_info: gitaly_info(project), - gitaly_repository: gitaly_repository(project) - } - end end namespace 'kubernetes/agent_configuration' do diff --git a/lib/api/issues.rb b/lib/api/issues.rb index 39ce6e0b062..43e83bd58fe 100644 --- a/lib/api/issues.rb +++ b/lib/api/issues.rb @@ -4,7 +4,6 @@ module API class Issues < ::API::Base include PaginationParams helpers Helpers::IssuesHelpers - helpers Helpers::RateLimiter before { authenticate_non_get! } @@ -114,6 +113,7 @@ module API end get '/issues_statistics' do authenticate! unless params[:scope] == 'all' + validate_anonymous_search_access! if params[:search].present? present issues_statistics, with: Grape::Presenters::Presenter end @@ -131,6 +131,7 @@ module API end get do authenticate! unless params[:scope] == 'all' + validate_anonymous_search_access! if params[:search].present? issues = paginate(find_issues) options = { @@ -169,6 +170,7 @@ module API optional :non_archived, type: Boolean, desc: 'Return issues from non archived projects', default: true end get ":id/issues" do + validate_anonymous_search_access! if declared_params[:search].present? issues = paginate(find_issues(group_id: user_group.id, include_subgroups: true)) options = { @@ -187,6 +189,8 @@ module API use :issues_stats_params end get ":id/issues_statistics" do + validate_anonymous_search_access! if declared_params[:search].present? + present issues_statistics(group_id: user_group.id, include_subgroups: true), with: Grape::Presenters::Presenter end end @@ -204,6 +208,7 @@ module API use :issues_params end get ":id/issues" do + validate_anonymous_search_access! if declared_params[:search].present? issues = paginate(find_issues(project_id: user_project.id)) options = { @@ -222,6 +227,8 @@ module API use :issues_stats_params end get ":id/issues_statistics" do + validate_anonymous_search_access! if declared_params[:search].present? + present issues_statistics(project_id: user_project.id), with: Grape::Presenters::Presenter end @@ -255,7 +262,7 @@ module API post ':id/issues' do Gitlab::QueryLimiting.disable!('https://gitlab.com/gitlab-org/gitlab/-/issues/21140') - check_rate_limit! :issues_create, [current_user] + check_rate_limit! :issues_create, [current_user] if Feature.disabled?("rate_limited_service_issues_create", user_project, default_enabled: :yaml) authorize! :create_issue, user_project @@ -375,6 +382,34 @@ module API end # rubocop: enable CodeReuse/ActiveRecord + desc 'Clone an existing issue' do + success Entities::Issue + end + params do + requires :issue_iid, type: Integer, desc: 'The internal ID of a project issue' + requires :to_project_id, type: Integer, desc: 'The ID of the new project' + optional :with_notes, type: Boolean, desc: 'Clone issue with notes', default: false + end + # rubocop: disable CodeReuse/ActiveRecord + post ':id/issues/:issue_iid/clone' do + Gitlab::QueryLimiting.disable!('https://gitlab.com/gitlab-org/gitlab/-/issues/340252') + + issue = user_project.issues.find_by(iid: params[:issue_iid]) + not_found!('Issue') unless issue + + target_project = Project.find_by(id: params[:to_project_id]) + not_found!('Project') unless target_project + + begin + issue = ::Issues::CloneService.new(project: user_project, current_user: current_user) + .execute(issue, target_project, with_notes: params[:with_notes]) + present issue, with: Entities::Issue, current_user: current_user, project: target_project + rescue ::Issues::CloneService::CloneError => error + render_api_error!(error.message, 400) + end + end + # rubocop: enable CodeReuse/ActiveRecord + desc 'Delete a project issue' params do requires :issue_iid, type: Integer, desc: 'The internal ID of a project issue' diff --git a/lib/api/maven_packages.rb b/lib/api/maven_packages.rb index 9e5705abe88..5245cd10564 100644 --- a/lib/api/maven_packages.rb +++ b/lib/api/maven_packages.rb @@ -264,8 +264,6 @@ module API when 'md5' '' else - track_package_event('push_package', :maven, user: current_user, project: user_project, namespace: user_project.namespace) if jar_file?(format) - file_params = { file: params[:file], size: params['file.size'], @@ -276,6 +274,7 @@ module API } ::Packages::CreatePackageFileService.new(package, file_params.merge(build: current_authenticated_job)).execute + track_package_event('push_package', :maven, user: current_user, project: user_project, namespace: user_project.namespace) if jar_file?(format) end end end diff --git a/lib/api/merge_requests.rb b/lib/api/merge_requests.rb index 34af9eab511..21c1b7969aa 100644 --- a/lib/api/merge_requests.rb +++ b/lib/api/merge_requests.rb @@ -136,6 +136,7 @@ module API end get feature_category: :code_review do authenticate! unless params[:scope] == 'all' + validate_anonymous_search_access! if params[:search].present? merge_requests = find_merge_requests present merge_requests, serializer_options_for(merge_requests) @@ -155,6 +156,7 @@ module API default: true end get ":id/merge_requests", feature_category: :code_review do + validate_anonymous_search_access! if declared_params[:search].present? merge_requests = find_merge_requests(group_id: user_group.id, include_subgroups: true) present merge_requests, serializer_options_for(merge_requests).merge(group: user_group) @@ -195,6 +197,7 @@ module API end get ":id/merge_requests", feature_category: :code_review do authorize! :read_merge_request, user_project + validate_anonymous_search_access! if declared_params[:search].present? merge_requests = find_merge_requests(project_id: user_project.id) diff --git a/lib/api/notes.rb b/lib/api/notes.rb index 418efe3d1a7..656eaa2b2bb 100644 --- a/lib/api/notes.rb +++ b/lib/api/notes.rb @@ -4,7 +4,6 @@ module API class Notes < ::API::Base include PaginationParams helpers ::API::Helpers::NotesHelpers - helpers Helpers::RateLimiter before { authenticate! } @@ -88,7 +87,7 @@ module API note = create_note(noteable, opts) - if note.errors.keys == [:commands_only] + if note.errors.attribute_names == [:commands_only] status 202 present note, with: Entities::NoteCommands elsif note.valid? diff --git a/lib/api/project_container_repositories.rb b/lib/api/project_container_repositories.rb index 28cfa9e3ae0..82b6082c3fe 100644 --- a/lib/api/project_container_repositories.rb +++ b/lib/api/project_container_repositories.rb @@ -3,6 +3,8 @@ module API class ProjectContainerRepositories < ::API::Base include PaginationParams + include ::API::Helpers::ContainerRegistryHelpers + helpers ::API::Helpers::PackagesHelpers REPOSITORY_ENDPOINT_REQUIREMENTS = API::NAMESPACE_OR_PROJECT_REQUIREMENTS.merge( diff --git a/lib/api/project_export.rb b/lib/api/project_export.rb index 4041e130f9e..e01c195dbc4 100644 --- a/lib/api/project_export.rb +++ b/lib/api/project_export.rb @@ -2,8 +2,6 @@ module API class ProjectExport < ::API::Base - helpers Helpers::RateLimiter - feature_category :importers before do @@ -74,6 +72,52 @@ module API accepted! end + + resource do + before do + not_found! unless ::Feature.enabled?(:bulk_import, default_enabled: :yaml) + end + + desc 'Start relations export' do + detail 'This feature was introduced in GitLab 14.4' + end + post ':id/export_relations' do + response = ::BulkImports::ExportService.new(portable: user_project, user: current_user).execute + + if response.success? + accepted! + else + render_api_error!(message: 'Project relations export could not be started.') + end + end + + desc 'Download relations export' do + detail 'This feature was introduced in GitLab 14.4' + end + params do + requires :relation, + type: String, + project_portable: true, + desc: 'Project relation name' + end + get ':id/export_relations/download' do + export = user_project.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 14.4' + end + get ':id/export_relations/status' do + present user_project.bulk_import_exports, with: Entities::BulkImports::ExportStatus + end + end end end end diff --git a/lib/api/project_import.rb b/lib/api/project_import.rb index 039f7b4be41..d43184ff75d 100644 --- a/lib/api/project_import.rb +++ b/lib/api/project_import.rb @@ -6,7 +6,6 @@ module API helpers Helpers::ProjectsHelpers helpers Helpers::FileUploadHelpers - helpers Helpers::RateLimiter feature_category :importers diff --git a/lib/api/projects.rb b/lib/api/projects.rb index 34e0b528ced..e8a48d6c9f4 100644 --- a/lib/api/projects.rb +++ b/lib/api/projects.rb @@ -182,8 +182,6 @@ module API [options[:with].prepare_relation(projects, options), options] end - Preloaders::UserMaxAccessLevelInProjectsPreloader.new(records, current_user).execute if current_user - present records, options end @@ -658,10 +656,7 @@ module API users = DeclarativePolicy.subject_scope { user_project.team.users } users = users.search(params[:search]) if params[:search].present? users = users.where_not_in(params[:skip_users]) if params[:skip_users].present? - - if Feature.enabled?(:sort_by_project_users_by_project_authorizations_user_id, user_project, default_enabled: :yaml) - users = users.order('project_authorizations.user_id' => :asc) # rubocop: disable CodeReuse/ActiveRecord - end + users = users.order('project_authorizations.user_id' => :asc) # rubocop: disable CodeReuse/ActiveRecord present paginate(users), with: Entities::UserBasic end diff --git a/lib/api/projects_relation_builder.rb b/lib/api/projects_relation_builder.rb index db46602cd90..a4bd06aec10 100644 --- a/lib/api/projects_relation_builder.rb +++ b/lib/api/projects_relation_builder.rb @@ -12,6 +12,8 @@ module API preload_repository_cache(projects_relation) + Preloaders::UserMaxAccessLevelInProjectsPreloader.new(projects_relation, options[:current_user]).execute if options[:current_user] + projects_relation end diff --git a/lib/api/repositories.rb b/lib/api/repositories.rb index 3c9255e3117..1aa76906b3d 100644 --- a/lib/api/repositories.rb +++ b/lib/api/repositories.rb @@ -101,6 +101,7 @@ module API params do optional :sha, type: String, desc: 'The commit sha of the archive to be downloaded' optional :format, type: String, desc: 'The archive format' + optional :path, type: String, desc: 'Subfolder of the repository to be downloaded' end get ':id/repository/archive', requirements: { format: Gitlab::PathRegex.archive_formats_regex } do if archive_rate_limit_reached?(current_user, user_project) @@ -109,7 +110,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 + send_git_archive user_project.repository, ref: params[:sha], format: params[:format], append_sha: true, path: params[:path] rescue StandardError not_found!('File') end diff --git a/lib/api/services.rb b/lib/api/services.rb deleted file mode 100644 index a37b6f4626a..00000000000 --- a/lib/api/services.rb +++ /dev/null @@ -1,173 +0,0 @@ -# frozen_string_literal: true -module API - class Services < ::API::Base - feature_category :integrations - - integrations = Helpers::IntegrationsHelpers.integrations - integration_classes = Helpers::IntegrationsHelpers.integration_classes - - if Rails.env.development? - integrations['mock-ci'] = [ - { - required: true, - name: :mock_service_url, - type: String, - desc: 'URL to the mock service' - } - ] - integrations['mock-deployment'] = [] - integrations['mock-monitoring'] = [] - - integration_classes += Helpers::IntegrationsHelpers.development_integration_classes - end - - INTEGRATIONS = integrations.freeze - - integration_classes.each do |integration| - event_names = integration.try(:event_names) || next - event_names.each do |event_name| - INTEGRATIONS[integration.to_param.tr("_", "-")] << { - required: false, - name: event_name.to_sym, - type: String, - desc: IntegrationsHelper.integration_event_description(integration, event_name) - } - end - end - - TRIGGER_INTEGRATIONS = { - 'mattermost-slash-commands' => [ - { - name: :token, - type: String, - desc: 'The Mattermost token' - } - ], - 'slack-slash-commands' => [ - { - name: :token, - type: String, - desc: 'The Slack token' - } - ] - }.freeze - - params do - requires :id, type: String, desc: 'The ID of a project' - end - resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do - before { authenticate! } - before { authorize_admin_project } - - helpers do - def integration_attributes(integration) - integration.fields.inject([]) do |arr, hash| - arr << hash[:name].to_sym - end - end - end - - desc 'Get all active project integrations' do - success Entities::ProjectIntegrationBasic - end - get ":id/services" do - integrations = user_project.integrations.active - - present integrations, with: Entities::ProjectIntegrationBasic - end - - INTEGRATIONS.each do |slug, settings| - desc "Set #{slug} integration for project" - params do - settings.each do |setting| - if setting[:required] - requires setting[:name], type: setting[:type], desc: setting[:desc] - else - optional setting[:name], type: setting[:type], desc: setting[:desc] - end - end - end - put ":id/services/#{slug}" do - integration = user_project.find_or_initialize_integration(slug.underscore) - params = declared_params(include_missing: false).merge(active: true) - - if integration.update(params) - present integration, with: Entities::ProjectIntegration - else - render_api_error!('400 Bad Request', 400) - end - end - end - - desc "Delete an integration from a project" - params do - requires :slug, type: String, values: INTEGRATIONS.keys, desc: 'The name of the service' - end - delete ":id/services/:slug" do - integration = user_project.find_or_initialize_integration(params[:slug].underscore) - - destroy_conditionally!(integration) do - attrs = integration_attributes(integration).index_with { nil }.merge(active: false) - - render_api_error!('400 Bad Request', 400) unless integration.update(attrs) - end - end - - desc 'Get the integration settings for a project' do - success Entities::ProjectIntegration - end - params do - requires :slug, type: String, values: INTEGRATIONS.keys, desc: 'The name of the service' - end - get ":id/services/:slug" do - integration = user_project.find_or_initialize_integration(params[:slug].underscore) - - not_found!('Service') unless integration&.persisted? - - present integration, with: Entities::ProjectIntegration - end - end - - TRIGGER_INTEGRATIONS.each do |integration_slug, settings| - helpers do - def slash_command_integration(project, integration_slug, params) - project.integrations.active.find do |integration| - integration.try(:token) == params[:token] && integration.to_param == integration_slug.underscore - end - end - end - - params do - requires :id, type: String, desc: 'The ID of a project' - end - resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do - desc "Trigger a slash command for #{integration_slug}" do - detail 'Added in GitLab 8.13' - end - params do - settings.each do |setting| - requires setting[:name], type: setting[:type], desc: setting[:desc] - end - end - post ":id/services/#{integration_slug.underscore}/trigger" do - project = find_project(params[:id]) - - # This is not accurate, but done to prevent leakage of the project names - not_found!('Service') unless project - - integration = slash_command_integration(project, integration_slug, params) - result = integration.try(:trigger, params) - - if result - status result[:status] || 200 - present result - else - not_found!('Service') - end - end - end - end - end -end - -API::Services.prepend_mod_with('API::Services') diff --git a/lib/api/settings.rb b/lib/api/settings.rb index 36f816ae638..12e1d21a00d 100644 --- a/lib/api/settings.rb +++ b/lib/api/settings.rb @@ -177,6 +177,7 @@ module API 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)' optional :user_deactivation_emails_enabled, type: Boolean, desc: 'Send emails to users upon account deactivation' + optional :suggest_pipeline_enabled, type: Boolean, desc: 'Enable pipeline suggestion banner' ApplicationSetting::SUPPORTED_KEY_TYPES.each do |type| optional :"#{type}_key_restriction", diff --git a/lib/api/tags.rb b/lib/api/tags.rb index 395aacced78..f018b421edd 100644 --- a/lib/api/tags.rb +++ b/lib/api/tags.rb @@ -24,7 +24,7 @@ module API use :pagination end get ':id/repository/tags', feature_category: :source_code_management do - tags = ::TagsFinder.new(user_project.repository, + tags, _ = ::TagsFinder.new(user_project.repository, sort: "#{params[:order_by]}_#{params[:sort]}", search: params[:search]).execute diff --git a/lib/api/unleash.rb b/lib/api/unleash.rb index 37fe540cde1..6dadaf4fc54 100644 --- a/lib/api/unleash.rb +++ b/lib/api/unleash.rb @@ -30,7 +30,7 @@ module API end desc 'Get a list of features' - get 'client/features' do + get 'client/features', urgency: :medium do present :version, 1 present :features, feature_flags, with: ::API::Entities::UnleashFeature end diff --git a/lib/api/users.rb b/lib/api/users.rb index 944be990c2f..f16e1148618 100644 --- a/lib/api/users.rb +++ b/lib/api/users.rb @@ -795,7 +795,7 @@ module API use :pagination optional :state, type: String, default: 'all', values: %w[all active inactive], desc: 'Filters (all|active|inactive) impersonation_tokens' end - get feature_category :authentication_and_authorization do + get feature_category: :authentication_and_authorization do present paginate(finder(declared_params(include_missing: false)).execute), with: Entities::ImpersonationToken end @@ -1058,6 +1058,10 @@ module API 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' + requires :credit_card_expiration_month, type: Integer, desc: 'The month the credit card expires' + requires :credit_card_expiration_year, type: Integer, desc: 'The year the credit card expires' + requires :credit_card_holder_name, type: String, desc: 'The credit card holder name' + requires :credit_card_mask_number, type: String, desc: 'The last 4 digits of credit card number' end put ":user_id/credit_card_validation", feature_category: :users do authenticated_as_admin! @@ -1093,7 +1097,6 @@ module API attrs = declared_params(include_missing: false) service = ::UserPreferences::UpdateService.new(current_user, attrs).execute - if service.success? present preferences, with: Entities::UserPreferences else diff --git a/lib/api/validations/validators/project_portable.rb b/lib/api/validations/validators/project_portable.rb new file mode 100644 index 00000000000..3a7ea5ea71e --- /dev/null +++ b/lib/api/validations/validators/project_portable.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +module API + module Validations + module Validators + class ProjectPortable < Grape::Validations::Base + def validate_param!(attr_name, params) + portable = params[attr_name] + + portable_relations = ::BulkImports::FileTransfer.config_for(::Project.new).portable_relations + return if portable_relations.include?(portable) + + raise Grape::Exceptions::Validation.new( + params: [@scope.full_name(attr_name)], + message: "is not portable" + ) + end + end + end + end +end -- cgit v1.2.1