diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2021-09-20 13:18:24 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2021-09-20 13:18:24 +0000 |
commit | 0653e08efd039a5905f3fa4f6e9cef9f5d2f799c (patch) | |
tree | 4dcc884cf6d81db44adae4aa99f8ec1233a41f55 /lib/api | |
parent | 744144d28e3e7fddc117924fef88de5d9674fe4c (diff) | |
download | gitlab-ce-0653e08efd039a5905f3fa4f6e9cef9f5d2f799c.tar.gz |
Add latest changes from gitlab-org/gitlab@14-3-stable-eev14.3.0-rc42
Diffstat (limited to 'lib/api')
46 files changed, 548 insertions, 167 deletions
diff --git a/lib/api/admin/sidekiq.rb b/lib/api/admin/sidekiq.rb index d91d4a0d4d5..05eb7f8222b 100644 --- a/lib/api/admin/sidekiq.rb +++ b/lib/api/admin/sidekiq.rb @@ -12,11 +12,11 @@ module API namespace 'queues' do desc 'Drop jobs matching the given metadata from the Sidekiq queue' params do - Gitlab::ApplicationContext::KNOWN_KEYS.each do |key| + Gitlab::SidekiqQueue::ALLOWED_KEYS.each do |key| optional key, type: String, allow_blank: false end - at_least_one_of(*Gitlab::ApplicationContext::KNOWN_KEYS) + at_least_one_of(*Gitlab::SidekiqQueue::ALLOWED_KEYS) end delete ':queue_name' do result = diff --git a/lib/api/api.rb b/lib/api/api.rb index 40f1b2fa9d3..d0d96858f61 100644 --- a/lib/api/api.rb +++ b/lib/api/api.rb @@ -171,6 +171,7 @@ module API mount ::API::Deployments mount ::API::Environments mount ::API::ErrorTracking + mount ::API::ErrorTrackingClientKeys mount ::API::ErrorTrackingCollector mount ::API::Events mount ::API::FeatureFlags diff --git a/lib/api/ci/pipelines.rb b/lib/api/ci/pipelines.rb index 4d6d38f2dce..03b59e7e6ad 100644 --- a/lib/api/ci/pipelines.rb +++ b/lib/api/ci/pipelines.rb @@ -7,8 +7,6 @@ module API before { authenticate_non_get! } - feature_category :continuous_integration - params do requires :id, type: String, desc: 'The project ID' end @@ -44,7 +42,6 @@ module API optional :ref, type: String, desc: 'The ref of pipelines' optional :sha, type: String, desc: 'The sha of pipelines' optional :yaml_errors, type: Boolean, desc: 'Returns pipelines with invalid configurations' - optional :name, type: String, desc: '(deprecated) The name of the user who triggered pipelines' optional :username, type: String, desc: 'The username of the user who triggered pipelines' optional :updated_before, type: DateTime, desc: 'Return pipelines updated before the specified datetime. Format: ISO 8601 YYYY-MM-DDTHH:MM:SSZ' optional :updated_after, type: DateTime, desc: 'Return pipelines updated after the specified datetime. Format: ISO 8601 YYYY-MM-DDTHH:MM:SSZ' @@ -54,7 +51,7 @@ module API desc: 'Sort pipelines' optional :source, type: String, values: ::Ci::Pipeline.sources.keys end - get ':id/pipelines' do + get ':id/pipelines', feature_category: :continuous_integration do authorize! :read_pipeline, user_project authorize! :read_build, user_project @@ -70,7 +67,7 @@ module API requires :ref, type: String, desc: 'Reference' optional :variables, Array, desc: 'Array of variables available in the pipeline' end - post ':id/pipeline' do + post ':id/pipeline', feature_category: :continuous_integration do Gitlab::QueryLimiting.disable!('https://gitlab.com/gitlab-org/gitlab/-/issues/20711') authorize! :create_pipeline, user_project @@ -97,7 +94,7 @@ module API params do optional :ref, type: String, desc: 'branch ref of pipeline' end - get ':id/pipelines/latest' do + get ':id/pipelines/latest', feature_category: :continuous_integration do authorize! :read_pipeline, latest_pipeline present latest_pipeline, with: Entities::Ci::Pipeline @@ -110,7 +107,7 @@ module API params do requires :pipeline_id, type: Integer, desc: 'The pipeline ID' end - get ':id/pipelines/:pipeline_id' do + get ':id/pipelines/:pipeline_id', feature_category: :continuous_integration do authorize! :read_pipeline, pipeline present pipeline, with: Entities::Ci::Pipeline @@ -126,7 +123,7 @@ module API use :pagination end - get ':id/pipelines/:pipeline_id/jobs' do + get ':id/pipelines/:pipeline_id/jobs', feature_category: :continuous_integration do authorize!(:read_pipeline, user_project) pipeline = user_project.all_pipelines.find(params[:pipeline_id]) @@ -149,7 +146,7 @@ module API use :pagination end - get ':id/pipelines/:pipeline_id/bridges' do + get ':id/pipelines/:pipeline_id/bridges', feature_category: :pipeline_authoring do authorize!(:read_build, user_project) pipeline = user_project.all_pipelines.find(params[:pipeline_id]) @@ -169,7 +166,7 @@ module API params do requires :pipeline_id, type: Integer, desc: 'The pipeline ID' end - get ':id/pipelines/:pipeline_id/variables' do + get ':id/pipelines/:pipeline_id/variables', feature_category: :pipeline_authoring do authorize! :read_pipeline_variable, pipeline present pipeline.variables, with: Entities::Ci::Variable @@ -182,7 +179,7 @@ module API params do requires :pipeline_id, type: Integer, desc: 'The pipeline ID' end - get ':id/pipelines/:pipeline_id/test_report' do + get ':id/pipelines/:pipeline_id/test_report', feature_category: :code_testing do authorize! :read_build, pipeline present pipeline.test_reports, with: TestReportEntity, details: true @@ -195,7 +192,7 @@ module API params do requires :pipeline_id, type: Integer, desc: 'The pipeline ID' end - get ':id/pipelines/:pipeline_id/test_report_summary' do + get ':id/pipelines/:pipeline_id/test_report_summary', feature_category: :code_testing do authorize! :read_build, pipeline present pipeline.test_report_summary, with: TestReportSummaryEntity @@ -208,7 +205,7 @@ module API params do requires :pipeline_id, type: Integer, desc: 'The pipeline ID' end - delete ':id/pipelines/:pipeline_id' do + delete ':id/pipelines/:pipeline_id', feature_category: :continuous_integration do authorize! :destroy_pipeline, pipeline destroy_conditionally!(pipeline) do @@ -223,7 +220,7 @@ module API params do requires :pipeline_id, type: Integer, desc: 'The pipeline ID' end - post ':id/pipelines/:pipeline_id/retry' do + post ':id/pipelines/:pipeline_id/retry', feature_category: :continuous_integration do authorize! :update_pipeline, pipeline pipeline.retry_failed(current_user) @@ -238,7 +235,7 @@ module API params do requires :pipeline_id, type: Integer, desc: 'The pipeline ID' end - post ':id/pipelines/:pipeline_id/cancel' do + post ':id/pipelines/:pipeline_id/cancel', feature_category: :continuous_integration do authorize! :update_pipeline, pipeline pipeline.cancel_running diff --git a/lib/api/ci/runners.rb b/lib/api/ci/runners.rb index 7f755b1a4d4..93a40925c21 100644 --- a/lib/api/ci/runners.rb +++ b/lib/api/ci/runners.rb @@ -222,6 +222,56 @@ module API end end + resource :runners do + before { authenticate_non_get! } + + desc 'Resets runner registration token' do + success Entities::Ci::ResetRegistrationTokenResult + 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 + 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 { authenticate_non_get! } + + desc 'Resets runner registration token' do + success Entities::Ci::ResetRegistrationTokenResult + 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 + end + end + + params do + requires :id, type: String, desc: 'The ID of a group' + end + resource :groups, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do + before { authenticate_non_get! } + + desc 'Resets runner registration token' do + success Entities::Ci::ResetRegistrationTokenResult + 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 + end + end + helpers do def filter_runners(runners, scope, allowed_scopes: ::Ci::Runner::AVAILABLE_SCOPES) return runners unless scope.present? diff --git a/lib/api/commits.rb b/lib/api/commits.rb index 5d8985455ad..10dc51556b9 100644 --- a/lib/api/commits.rb +++ b/lib/api/commits.rb @@ -336,7 +336,7 @@ module API lines = Gitlab::Diff::Parser.new.parse(diff.diff.each_line) lines.each do |line| - next unless line.new_pos == params[:line] && line.type == params[:line_type] + next unless line.line == params[:line] && line.type == params[:line_type] break opts[:line_code] = Gitlab::Git.diff_line_code(diff.new_path, line.new_pos, line.old_pos) end diff --git a/lib/api/dependency_proxy.rb b/lib/api/dependency_proxy.rb index 3379bb2f029..185b8d5a15d 100644 --- a/lib/api/dependency_proxy.rb +++ b/lib/api/dependency_proxy.rb @@ -15,7 +15,7 @@ module API end end - before do + after_validation do authorize! :admin_group, user_group end @@ -35,6 +35,8 @@ module API # rubocop:disable CodeReuse/Worker PurgeDependencyProxyCacheWorker.perform_async(current_user.id, user_group.id) # rubocop:enable CodeReuse/Worker + + status :accepted end end end diff --git a/lib/api/entities/application_setting.rb b/lib/api/entities/application_setting.rb index f23fce40468..465c5f4112b 100644 --- a/lib/api/entities/application_setting.rb +++ b/lib/api/entities/application_setting.rb @@ -27,6 +27,14 @@ module API expose(*::ApplicationSettingsHelper.external_authorization_service_attributes) + # Also expose these columns under their new attribute names. + # + # TODO: Once we rename the columns, we have to swap this around and keep supporting the old names until v5. + # https://gitlab.com/gitlab-org/gitlab/-/issues/340031 + expose :throttle_unauthenticated_enabled, as: :throttle_unauthenticated_web_enabled + expose :throttle_unauthenticated_period_in_seconds, as: :throttle_unauthenticated_web_period_in_seconds + expose :throttle_unauthenticated_requests_per_period, as: :throttle_unauthenticated_web_requests_per_period + # support legacy names, can be removed in v5 expose :password_authentication_enabled_for_web, as: :password_authentication_enabled expose :password_authentication_enabled_for_web, as: :signin_enabled diff --git a/lib/api/entities/basic_project_details.rb b/lib/api/entities/basic_project_details.rb index 0b231906ccd..5c33af86b84 100644 --- a/lib/api/entities/basic_project_details.rb +++ b/lib/api/entities/basic_project_details.rb @@ -43,12 +43,20 @@ module API # 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) + .preload(:import_state, :topics, :topics_acts_as_taggable) .preload(:auto_devops) .preload(namespace: [:route, :owner]) end # rubocop: enable CodeReuse/ActiveRecord + def self.execute_batch_counting(projects_relation) + # Call the count methods on every project, so the BatchLoader would load them all at + # once when the entities are rendered + projects_relation.each(&:forks_count) + + super + end + private alias_method :project, :object diff --git a/lib/api/entities/blob.rb b/lib/api/entities/blob.rb index b14ef127b68..12700d99865 100644 --- a/lib/api/entities/blob.rb +++ b/lib/api/entities/blob.rb @@ -10,7 +10,7 @@ module API # in the future we can only return the filename here without the leading # directory path. # https://gitlab.com/gitlab-org/gitlab/issues/34521 - expose :filename, &:path + expose :path, as: :filename expose :id expose :ref expose :startline diff --git a/lib/api/entities/ci/pipeline_basic.rb b/lib/api/entities/ci/pipeline_basic.rb index 8086062dc9b..4d56176bdb3 100644 --- a/lib/api/entities/ci/pipeline_basic.rb +++ b/lib/api/entities/ci/pipeline_basic.rb @@ -4,11 +4,9 @@ module API module Entities module Ci class PipelineBasic < Grape::Entity - expose :id, :project_id, :sha, :ref, :status + expose :id, :project_id, :sha, :ref, :status, :source expose :created_at, :updated_at - expose :source, if: ->(pipeline, options) { ::Feature.enabled?(:pipeline_source_filter, options[:project], default_enabled: :yaml) } - expose :web_url do |pipeline, _options| Gitlab::Routing.url_helpers.project_pipeline_url(pipeline.project, pipeline) end diff --git a/lib/api/entities/ci/reset_registration_token_result.rb b/lib/api/entities/ci/reset_registration_token_result.rb new file mode 100644 index 00000000000..23426432f68 --- /dev/null +++ b/lib/api/entities/ci/reset_registration_token_result.rb @@ -0,0 +1,11 @@ +# 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/clusters/agent_authorization.rb b/lib/api/entities/clusters/agent_authorization.rb new file mode 100644 index 00000000000..6c533fff105 --- /dev/null +++ b/lib/api/entities/clusters/agent_authorization.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +module API + module Entities + module Clusters + class AgentAuthorization < Grape::Entity + expose :agent_id, as: :id + expose :project, with: Entities::ProjectIdentity, as: :config_project + expose :config, as: :configuration + end + end + end +end diff --git a/lib/api/entities/commit_note.rb b/lib/api/entities/commit_note.rb index d08b6fc8225..fe91712b48d 100644 --- a/lib/api/entities/commit_note.rb +++ b/lib/api/entities/commit_note.rb @@ -5,7 +5,7 @@ module API class CommitNote < Grape::Entity expose :note expose(:path) { |note| note.diff_file.try(:file_path) if note.diff_note? } - expose(:line) { |note| note.diff_line.try(:new_line) if note.diff_note? } + expose(:line) { |note| note.diff_line.try(:line) if note.diff_note? } expose(:line_type) { |note| note.diff_line.try(:type) if note.diff_note? } expose :author, using: Entities::UserBasic expose :created_at diff --git a/lib/api/entities/compare.rb b/lib/api/entities/compare.rb index fe2f03db2af..75a36d9bb01 100644 --- a/lib/api/entities/compare.rb +++ b/lib/api/entities/compare.rb @@ -20,6 +20,10 @@ module API end expose :same, as: :compare_same_ref + + expose :web_url do |compare, _| + Gitlab::UrlBuilder.build(compare) + end end end end diff --git a/lib/api/entities/error_tracking.rb b/lib/api/entities/error_tracking.rb index a38e00ca295..b55cba05ea0 100644 --- a/lib/api/entities/error_tracking.rb +++ b/lib/api/entities/error_tracking.rb @@ -10,6 +10,13 @@ module API expose :api_url expose :integrated end + + class ClientKey < Grape::Entity + expose :id + expose :active + expose :public_key + expose :sentry_dsn + end end end end diff --git a/lib/api/entities/global_notification_setting.rb b/lib/api/entities/global_notification_setting.rb index f3ca64347f0..f35efad5d01 100644 --- a/lib/api/entities/global_notification_setting.rb +++ b/lib/api/entities/global_notification_setting.rb @@ -4,7 +4,7 @@ module API module Entities class GlobalNotificationSetting < Entities::NotificationSetting expose :notification_email do |notification_setting, options| - notification_setting.user.notification_email + notification_setting.user.notification_email_or_default end end end diff --git a/lib/api/entities/project.rb b/lib/api/entities/project.rb index 890b42ed8c8..b0e53ac3794 100644 --- a/lib/api/entities/project.rb +++ b/lib/api/entities/project.rb @@ -126,6 +126,10 @@ module API expose :keep_latest_artifacts_available?, as: :keep_latest_artifact # rubocop: disable CodeReuse/ActiveRecord + def self.preload_resource(project) + ActiveRecord::Associations::Preloader.new.preload(project, project_group_links: { group: :route }) + end + 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` @@ -140,12 +144,21 @@ module API .preload(project_group_links: { group: :route }, fork_network: :root_project, fork_network_member: :forked_from_project, - forked_from_project: [:route, :topics, :group, :project_feature, namespace: [:route, :owner]]) + forked_from_project: [:route, :topics, :topics_acts_as_taggable, :group, :project_feature, namespace: [:route, :owner]]) end # rubocop: enable CodeReuse/ActiveRecord - def self.forks_counting_projects(projects_relation) - projects_relation + projects_relation.map(&:forked_from_project).compact + def self.execute_batch_counting(projects_relation) + # Call the count methods on every project, so the BatchLoader would load them all at + # once when the entities are rendered + projects_relation.each(&:open_issues_count) + projects_relation.map(&:forked_from_project).compact.each(&:forks_count) + + super + end + + def self.repositories_for_preload(projects_relation) + super + projects_relation.map(&:forked_from_project).compact.map(&:repository) end end end diff --git a/lib/api/entities/user.rb b/lib/api/entities/user.rb index 973e80dd5ef..5c46233a639 100644 --- a/lib/api/entities/user.rb +++ b/lib/api/entities/user.rb @@ -4,8 +4,10 @@ module API module Entities class User < UserBasic include UsersHelper + include ActionView::Helpers::SanitizeHelper + expose :created_at, if: ->(user, opts) { Ability.allowed?(opts[:current_user], :read_user_profile, user) } - expose :bio, :bio_html, :location, :public_email, :skype, :linkedin, :twitter, :website_url, :organization, :job_title, :pronouns + expose :bio, :location, :public_email, :skype, :linkedin, :twitter, :website_url, :organization, :job_title, :pronouns expose :bot?, as: :bot expose :work_information do |user| work_information(user) @@ -16,6 +18,12 @@ 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) + end end end end diff --git a/lib/api/entities/user_public.rb b/lib/api/entities/user_public.rb index 78f088d3c1a..5d0e464abe1 100644 --- a/lib/api/entities/user_public.rb +++ b/lib/api/entities/user_public.rb @@ -14,7 +14,7 @@ module API expose :two_factor_enabled?, as: :two_factor_enabled expose :external expose :private_profile - expose :commit_email + expose :commit_email_or_default, as: :commit_email end end end diff --git a/lib/api/environments.rb b/lib/api/environments.rb index e50da4264b5..c032b80e39b 100644 --- a/lib/api/environments.rb +++ b/lib/api/environments.rb @@ -58,7 +58,8 @@ module API end params do requires :environment_id, type: Integer, desc: 'The environment ID' - optional :name, type: String, desc: 'The new environment name' + # TODO: disallow renaming via the API https://gitlab.com/gitlab-org/gitlab/-/issues/338897 + optional :name, type: String, desc: 'DEPRECATED: Renaming environment can lead to errors, this will be removed in 15.0' optional :external_url, type: String, desc: 'The new URL on which this deployment is viewable' optional :slug, absence: { message: "is automatically generated and cannot be changed" } end diff --git a/lib/api/error_tracking.rb b/lib/api/error_tracking.rb index 3abf2831bd3..369efe3bf8c 100644 --- a/lib/api/error_tracking.rb +++ b/lib/api/error_tracking.rb @@ -6,24 +6,30 @@ module API 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 - authorize! :admin_operations, user_project - - setting = user_project.error_tracking_setting - - not_found!('Error Tracking Setting') unless setting - - present setting, with: Entities::ErrorTracking::ProjectSetting + present project_setting, with: Entities::ErrorTracking::ProjectSetting end desc 'Enable or disable error tracking settings for the project' do @@ -36,12 +42,6 @@ module API end patch ':id/error_tracking/settings/' do - authorize! :admin_operations, user_project - - setting = user_project.error_tracking_setting - - not_found!('Error Tracking Setting') unless setting - update_params = { error_tracking_setting_attributes: { enabled: params[:active] } } @@ -53,7 +53,7 @@ module API result = ::Projects::Operations::UpdateService.new(user_project, current_user, update_params).execute if result[:status] == :success - present setting, with: Entities::ErrorTracking::ProjectSetting + present project_setting, with: Entities::ErrorTracking::ProjectSetting else result 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..eaa84b7186c --- /dev/null +++ b/lib/api/error_tracking_client_keys.rb @@ -0,0 +1,50 @@ +# 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 index 13e8e476808..b1e0f6a858a 100644 --- a/lib/api/error_tracking_collector.rb +++ b/lib/api/error_tracking_collector.rb @@ -8,6 +8,8 @@ module API 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 @@ -33,17 +35,24 @@ module API 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 - public_key = ::ErrorTracking::Collector::SentryAuthParser.parse(request)[:public_key] + ::ErrorTracking::Collector::SentryAuthParser.parse(request)[:public_key] rescue StandardError bad_request!('Failed to parse sentry request') end - - find_client_key(public_key) end end - desc 'Submit error tracking event to the project' do + desc 'Submit error tracking event to the project as envelope' do detail 'This feature was introduced in GitLab 14.1.' end params do @@ -89,5 +98,38 @@ module API # 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/feature_flags.rb b/lib/api/feature_flags.rb index fb5858bc10b..c1f958ac007 100644 --- a/lib/api/feature_flags.rb +++ b/lib/api/feature_flags.rb @@ -118,7 +118,7 @@ module API put do authorize_update_feature_flag! exclude_legacy_flags_check! - render_api_error!('PUT operations are not supported for legacy feature flags', :unprocessable_entity) if feature_flag.legacy_flag? + render_api_error!('PUT operations are not supported for legacy feature flags', :unprocessable_entity) unless feature_flag.new_version_flag? attrs = declared_params(include_missing: false) @@ -207,7 +207,7 @@ module API end def exclude_legacy_flags_check! - if feature_flag.legacy_flag? + unless feature_flag.new_version_flag? not_found! end end diff --git a/lib/api/files.rb b/lib/api/files.rb index f3de7fbe96b..9d2b7cce837 100644 --- a/lib/api/files.rb +++ b/lib/api/files.rb @@ -35,10 +35,9 @@ module API not_found!('Commit') unless @commit @repo = user_project.repository - @blob = @repo.blob_at(@commit.sha, params[:file_path]) + @blob = @repo.blob_at(@commit.sha, params[:file_path], limit: Gitlab::Git::Blob::LFS_POINTER_MAX_SIZE) not_found!('File') unless @blob - @blob.load_all_data! end def commit_response(attrs) @@ -48,13 +47,21 @@ module API } end + def content_sha + Rails.cache.fetch("blob_content_sha256:#{user_project.full_path}:#{@blob.id}") do + @blob.load_all_data! + + Digest::SHA256.hexdigest(@blob.data) + end + end + def blob_data { file_name: @blob.name, file_path: @blob.path, size: @blob.size, encoding: "base64", - content_sha256: Digest::SHA256.hexdigest(@blob.data), + content_sha256: content_sha, ref: params[:ref], blob_id: @blob.id, commit_id: @commit.id, @@ -154,6 +161,8 @@ module API get ":id/repository/files/:file_path", requirements: FILE_ENDPOINT_REQUIREMENTS do assign_file_vars! + @blob.load_all_data! + data = blob_data set_http_headers(data) diff --git a/lib/api/generic_packages.rb b/lib/api/generic_packages.rb index a57d6bbcd2a..5e184d35255 100644 --- a/lib/api/generic_packages.rb +++ b/lib/api/generic_packages.rb @@ -62,7 +62,7 @@ module API authorize_upload!(project) bad_request!('File is too large') if max_file_size_exceeded? - ::Gitlab::Tracking.event(self.options[:for].name, 'push_package', user: current_user, project: project, namespace: project.namespace) + track_package_event('push_package', :generic, project: project, user: current_user, namespace: project.namespace) create_package_file_params = declared_params.merge(build: current_authenticated_job) ::Packages::Generic::CreatePackageFileService @@ -96,7 +96,7 @@ module API package = ::Packages::Generic::PackageFinder.new(project).execute!(params[:package_name], params[:package_version]) package_file = ::Packages::PackageFileFinder.new(package, params[:file_name]).execute! - ::Gitlab::Tracking.event(self.options[:for].name, 'pull_package', user: current_user, project: project, namespace: project.namespace) + track_package_event('pull_package', :generic, project: project, user: current_user, namespace: project.namespace) present_carrierwave_file!(package_file.file) end diff --git a/lib/api/group_variables.rb b/lib/api/group_variables.rb index 13daf05fc78..e726f9b61cc 100644 --- a/lib/api/group_variables.rb +++ b/lib/api/group_variables.rb @@ -6,7 +6,7 @@ module API before { authenticate! } before { authorize! :admin_group, user_group } - feature_category :continuous_integration + feature_category :pipeline_authoring helpers ::API::Helpers::VariablesHelpers diff --git a/lib/api/groups.rb b/lib/api/groups.rb index 0896357cc73..a1123b6291b 100644 --- a/lib/api/groups.rb +++ b/lib/api/groups.rb @@ -108,6 +108,20 @@ module API present paginate(groups), options end + def present_groups_with_pagination_strategies(params, groups) + return present_groups(params, groups) if current_user.present? || Feature.disabled?(:keyset_pagination_for_groups_api) + + options = { + with: Entities::Group, + current_user: nil, + statistics: false + } + + groups, options = with_custom_attributes(groups, options) + + present paginate_with_strategies(groups), options + end + def delete_group(group) destroy_conditionally!(group) do |group| ::Groups::DestroyService.new(group, current_user).async_execute @@ -168,7 +182,7 @@ module API end get do groups = find_groups(declared_params(include_missing: false), params[:id]) - present_groups params, groups + present_groups_with_pagination_strategies params, groups end desc 'Create a group. Available only for users who can create groups.' do diff --git a/lib/api/helm_packages.rb b/lib/api/helm_packages.rb index 4280744d8b4..8a7e84c9f87 100644 --- a/lib/api/helm_packages.rb +++ b/lib/api/helm_packages.rb @@ -44,15 +44,10 @@ module API get ":channel/index.yaml" do authorize_read_package!(authorized_user_project) - package_files = Packages::Helm::PackageFilesFinder.new( - authorized_user_project, - params[:channel], - order_by: 'created_at', - sort: 'desc' - ).execute + packages = Packages::Helm::PackagesFinder.new(authorized_user_project, params[:channel]).execute env['api.format'] = :yaml - present ::Packages::Helm::IndexPresenter.new(authorized_user_project, params[:id], package_files), + present ::Packages::Helm::IndexPresenter.new(params[:id], params[:channel], packages), with: ::API::Entities::Helm::Index end @@ -66,7 +61,7 @@ module API get ":channel/charts/:file_name.tgz", requirements: FILE_NAME_REQUIREMENTS do authorize_read_package!(authorized_user_project) - package_file = Packages::Helm::PackageFilesFinder.new(authorized_user_project, params[:channel], file_name: "#{params[:file_name]}.tgz").execute.last! + package_file = Packages::Helm::PackageFilesFinder.new(authorized_user_project, params[:channel], file_name: "#{params[:file_name]}.tgz").most_recent! track_package_event('pull_package', :helm, project: authorized_user_project, namespace: authorized_user_project.namespace) diff --git a/lib/api/helpers/issues_helpers.rb b/lib/api/helpers/issues_helpers.rb index b1954f8ece9..185a10a250c 100644 --- a/lib/api/helpers/issues_helpers.rb +++ b/lib/api/helpers/issues_helpers.rb @@ -34,7 +34,17 @@ module API end def self.sort_options - %w[created_at updated_at priority due_date relative_position label_priority milestone_due popularity] + %w[ + created_at + due_date + label_priority + milestone_due + popularity + priority + relative_position + title + updated_at + ] end def issue_finder(args = {}) @@ -43,9 +53,11 @@ module API args.delete(:id) args[:not] ||= {} args[:milestone_title] ||= args.delete(:milestone) - args[:not][:milestone_title] ||= args[:not]&.delete(:milestone) + args[:milestone_wildcard_id] ||= args.delete(:milestone_id) + args[:not][:milestone_title] ||= args[:not].delete(:milestone) + args[:not][:milestone_wildcard_id] ||= args[:not].delete(:milestone_id) args[:label_name] ||= args.delete(:labels) - args[:not][:label_name] ||= args[:not]&.delete(:labels) + 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) diff --git a/lib/api/helpers/members_helpers.rb b/lib/api/helpers/members_helpers.rb index e72bbb931f0..1e89f9f97a2 100644 --- a/lib/api/helpers/members_helpers.rb +++ b/lib/api/helpers/members_helpers.rb @@ -50,24 +50,48 @@ module API GroupMembersFinder.new(group).execute end - def create_member(current_user, user, source, params) - source.add_user(user, params[:access_level], current_user: current_user, expires_at: params[:expires_at]) + def present_members(members) + present members, with: Entities::Member, current_user: current_user, show_seat_info: params[:show_seat_info] end - def track_areas_of_focus(member, areas_of_focus) - return unless areas_of_focus + def present_member_invitations(invitations) + present invitations, with: Entities::Invitation, current_user: current_user + end + + def add_single_member_by_user_id(create_service_params) + source = create_service_params[:source] + user_id = create_service_params[:user_ids] + user = User.find_by(id: user_id) # rubocop: disable CodeReuse/ActiveRecord + + if user + conflict!('Member already exists') if member_already_exists?(source, user_id) + + instance = ::Members::CreateService.new(current_user, create_service_params) + instance.execute + + not_allowed! if instance.membership_locked # This currently can only be reached in EE if group membership is locked - areas_of_focus.each do |area_of_focus| - Gitlab::Tracking.event(::Members::CreateService.name, 'area_of_focus', label: area_of_focus, property: member.id.to_s) + member = instance.single_member + render_validation_error!(member) if member.invalid? + + present_members(member) + else + not_found!('User') end end - def present_members(members) - present members, with: Entities::Member, current_user: current_user, show_seat_info: params[:show_seat_info] + def add_multiple_members?(user_id) + user_id.include?(',') end - def present_member_invitations(invitations) - present invitations, with: Entities::Invitation, current_user: current_user + def add_single_member?(user_id) + user_id.present? + end + + private + + def member_already_exists?(source, user_id) + source.members.exists?(user_id: user_id) # rubocop: disable CodeReuse/ActiveRecord end end end diff --git a/lib/api/helpers/packages/npm.rb b/lib/api/helpers/packages/npm.rb index ce5db52fdbc..34e126c73fc 100644 --- a/lib/api/helpers/packages/npm.rb +++ b/lib/api/helpers/packages/npm.rb @@ -57,7 +57,11 @@ module API .by_path(namespace_path) next unless namespace - finder = ::Packages::Npm::PackageFinder.new(package_name, namespace: namespace) + finder = ::Packages::Npm::PackageFinder.new( + package_name, + namespace: namespace, + last_of_each_version: false + ) finder.last&.project_id end diff --git a/lib/api/helpers/pagination_strategies.rb b/lib/api/helpers/pagination_strategies.rb index 61cff37e4ab..8c2186768ea 100644 --- a/lib/api/helpers/pagination_strategies.rb +++ b/lib/api/helpers/pagination_strategies.rb @@ -3,10 +3,16 @@ module API module Helpers module PaginationStrategies - def paginate_with_strategies(relation, request_scope) + def paginate_with_strategies(relation, request_scope = nil) paginator = paginator(relation, request_scope) - yield(paginator.paginate(relation)).tap do |records, _| + result = if block_given? + yield(paginator.paginate(relation)) + else + paginator.paginate(relation) + end + + result.tap do |records, _| paginator.finalize(records) end end @@ -20,17 +26,31 @@ module API private def keyset_paginator(relation) - request_context = Gitlab::Pagination::Keyset::RequestContext.new(self) - unless Gitlab::Pagination::Keyset.available?(request_context, relation) + if cursor_based_keyset_pagination_supported?(relation) + request_context_class = Gitlab::Pagination::Keyset::CursorBasedRequestContext + paginator_class = Gitlab::Pagination::Keyset::CursorPager + availability_checker = Gitlab::Pagination::CursorBasedKeyset + else + request_context_class = Gitlab::Pagination::Keyset::RequestContext + paginator_class = Gitlab::Pagination::Keyset::Pager + availability_checker = Gitlab::Pagination::Keyset + end + + request_context = request_context_class.new(self) + + unless availability_checker.available?(request_context, relation) return error!('Keyset pagination is not yet available for this type of request', 405) end - Gitlab::Pagination::Keyset::Pager.new(request_context) + paginator_class.new(request_context) end def offset_paginator(relation, request_scope) offset_limit = limit_for_scope(request_scope) - if Gitlab::Pagination::Keyset.available_for_type?(relation) && offset_limit_exceeded?(offset_limit) + if (Gitlab::Pagination::Keyset.available_for_type?(relation) || + cursor_based_keyset_pagination_supported?(relation)) && + offset_limit_exceeded?(offset_limit) + return error!("Offset pagination has a maximum allowed offset of #{offset_limit} " \ "for requests that return objects of type #{relation.klass}. " \ "Remaining records can be retrieved using keyset pagination.", 405) @@ -39,6 +59,10 @@ module API Gitlab::Pagination::OffsetPagination.new(self) end + def cursor_based_keyset_pagination_supported?(relation) + Gitlab::Pagination::CursorBasedKeyset.available_for_type?(relation) + end + def keyset_pagination_enabled? params[:pagination] == 'keyset' end diff --git a/lib/api/helpers/settings_helpers.rb b/lib/api/helpers/settings_helpers.rb index a3ea1057bc8..82de4917f0b 100644 --- a/lib/api/helpers/settings_helpers.rb +++ b/lib/api/helpers/settings_helpers.rb @@ -10,10 +10,18 @@ module API end def self.optional_attributes - [*::ApplicationSettingsHelper.visible_attributes, - *::ApplicationSettingsHelper.external_authorization_service_attributes, - *::ApplicationSettingsHelper.deprecated_attributes, - :performance_bar_allowed_group_id].freeze + [ + *::ApplicationSettingsHelper.visible_attributes, + *::ApplicationSettingsHelper.external_authorization_service_attributes, + *::ApplicationSettingsHelper.deprecated_attributes, + :performance_bar_allowed_group_id, + # TODO: Once we rename these columns, we can remove them here and add the old + # names to `ApplicationSettingsHelper.deprecated_attributes` instead. + # https://gitlab.com/gitlab-org/gitlab/-/issues/340031 + :throttle_unauthenticated_web_enabled, + :throttle_unauthenticated_web_period_in_seconds, + :throttle_unauthenticated_web_requests_per_period + ].freeze end end end diff --git a/lib/api/internal/kubernetes.rb b/lib/api/internal/kubernetes.rb index 7af5c2ad2ee..d1ad3c1feb1 100644 --- a/lib/api/internal/kubernetes.rb +++ b/lib/api/internal/kubernetes.rb @@ -100,6 +100,23 @@ module API end end + namespace 'kubernetes/agent_configuration' do + desc 'POST agent configuration' do + detail 'Store configuration for an agent' + end + params do + requires :agent_id, type: Integer, desc: 'ID of the configured Agent' + requires :agent_config, type: JSON, desc: 'Configuration for the Agent' + end + post '/' do + agent = Clusters::Agent.find(params[:agent_id]) + + Clusters::Agents::RefreshAuthorizationService.new(agent, config: params[:agent_config]).execute + + no_content! + end + end + namespace 'kubernetes/usage_metrics' do desc 'POST usage metrics' do detail 'Updates usage metrics for agent' diff --git a/lib/api/issues.rb b/lib/api/issues.rb index a6565f913e3..39ce6e0b062 100644 --- a/lib/api/issues.rb +++ b/lib/api/issues.rb @@ -14,6 +14,10 @@ module API params :negatable_issue_filter_params do optional :labels, type: Array[String], coerce_with: ::API::Validations::Types::CommaSeparatedToArray.coerce, desc: 'Comma-separated list of label names' optional :milestone, type: String, desc: 'Milestone title' + optional :milestone_id, types: String, values: %w[Any None Upcoming Started], + desc: 'Return issues assigned to milestones without the specified timebox value ("Any", "None", "Upcoming" or "Started")' + mutually_exclusive :milestone_id, :milestone + optional :iids, type: Array[Integer], coerce_with: ::API::Validations::Types::CommaSeparatedToIntegerArray.coerce, desc: 'The IID array of issues' optional :author_id, type: Integer, desc: 'Return issues which are not authored by the user with the given ID' @@ -32,9 +36,14 @@ module API params :issues_stats_params do optional :labels, type: Array[String], coerce_with: ::API::Validations::Types::CommaSeparatedToArray.coerce, desc: 'Comma-separated list of label names' optional :milestone, type: String, desc: 'Milestone title' + # 'milestone_id' only accepts wildcard values 'Any', 'None', 'Upcoming', 'Started' + # the param has '_id' in the name to keep consistency (ex. assignee_id accepts id and wildcard values). + optional :milestone_id, types: String, values: %w[Any None Upcoming Started], + desc: 'Return issues assigned to milestones with the specified timebox value ("Any", "None", "Upcoming" or "Started")' optional :iids, type: Array[Integer], coerce_with: ::API::Validations::Types::CommaSeparatedToIntegerArray.coerce, desc: 'The IID array of issues' optional :search, type: String, desc: 'Search issues for text present in the title, description, or any combination of these' optional :in, type: String, desc: '`title`, `description`, or a string joining them with comma' + mutually_exclusive :milestone_id, :milestone optional :author_id, type: Integer, desc: 'Return issues which are authored by the user with the given ID' optional :author_username, type: String, desc: 'Return issues which are authored by the user with the given username' @@ -69,7 +78,7 @@ module API optional :state, type: String, values: %w[opened closed all], default: 'all', desc: 'Return opened, closed, or all issues' optional :order_by, type: String, values: Helpers::IssuesHelpers.sort_options, default: 'created_at', - desc: 'Return issues ordered by `created_at` or `updated_at` fields.' + desc: 'Return issues ordered by `created_at`, `due_date`, `label_priority`, `milestone_due`, `popularity`, `priority`, `relative_position`, `title`, or `updated_at` fields.' optional :sort, type: String, values: %w[asc desc], default: 'desc', 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] << '', diff --git a/lib/api/lint.rb b/lib/api/lint.rb index 945cdf3edb2..fa871b4bc57 100644 --- a/lib/api/lint.rb +++ b/lib/api/lint.rb @@ -13,18 +13,13 @@ module API post '/lint' do unauthorized! if (Gitlab::CurrentSettings.signup_disabled? || Gitlab::CurrentSettings.signup_limited?) && current_user.nil? - result = Gitlab::Ci::YamlProcessor.new(params[:content], user: current_user).execute + result = Gitlab::Ci::Lint.new(project: nil, current_user: current_user) + .validate(params[:content], dry_run: false) status 200 - - response = if result.errors.empty? - { status: 'valid', errors: [], warnings: result.warnings } - else - { status: 'invalid', errors: result.errors, warnings: result.warnings } - end - - response.tap do |response| - response[:merged_yaml] = result.merged_yaml if params[:include_merged_yaml] + Entities::Ci::Lint::Result.represent(result, current_user: current_user).serializable_hash.tap do |presented_result| + presented_result[:status] = presented_result[:valid] ? 'valid' : 'invalid' + presented_result.delete(:merged_yaml) unless params[:include_merged_yaml] end end end diff --git a/lib/api/members.rb b/lib/api/members.rb index 7130635281a..332520ccd26 100644 --- a/lib/api/members.rb +++ b/lib/api/members.rb @@ -96,42 +96,22 @@ module API optional :invite_source, type: String, desc: 'Source that triggered the member creation process', default: 'members-api' optional :areas_of_focus, type: Array[String], coerce_with: Validations::Types::CommaSeparatedToArray.coerce, desc: 'Areas the inviter wants the member to focus upon' end - # rubocop: disable CodeReuse/ActiveRecord + post ":id/members" do ::Gitlab::QueryLimiting.disable!('https://gitlab.com/gitlab-org/gitlab/-/issues/333434') source = find_source(source_type, params[:id]) authorize_admin_source!(source_type, source) - if params[:user_id].to_s.include?(',') - create_service_params = params.except(:user_id).merge({ user_ids: params[:user_id], source: source }) + user_id = params[:user_id].to_s + create_service_params = params.except(:user_id).merge({ user_ids: user_id, source: source }) + if add_multiple_members?(user_id) ::Members::CreateService.new(current_user, create_service_params).execute - elsif params[:user_id].present? - member = source.members.find_by(user_id: params[:user_id]) - conflict!('Member already exists') if member - - user = User.find_by_id(params[:user_id]) - not_found!('User') unless user - - member = create_member(current_user, user, source, params) - - if !member - not_allowed! # This currently can only be reached in EE - elsif member.valid? && member.persisted? - present_members(member) - Gitlab::Tracking.event(::Members::CreateService.name, - 'create_member', - label: params[:invite_source], - property: 'existing_user', - user: current_user) - track_areas_of_focus(member, params[:areas_of_focus]) - else - render_validation_error!(member) - end + elsif add_single_member?(user_id) + add_single_member_by_user_id(create_service_params) end end - # rubocop: enable CodeReuse/ActiveRecord desc 'Updates a member of a group or project.' do success Entities::Member diff --git a/lib/api/merge_requests.rb b/lib/api/merge_requests.rb index 7ab57982907..34af9eab511 100644 --- a/lib/api/merge_requests.rb +++ b/lib/api/merge_requests.rb @@ -202,7 +202,7 @@ module API options[:project] = user_project if Feature.enabled?(:api_caching_merge_requests, user_project, type: :development, default_enabled: :yaml) - present_cached merge_requests, expires_in: 10.minutes, **options + present_cached merge_requests, expires_in: 2.days, **options else present merge_requests, options end diff --git a/lib/api/npm_project_packages.rb b/lib/api/npm_project_packages.rb index 7ff4439ce04..dbfc0a61577 100644 --- a/lib/api/npm_project_packages.rb +++ b/lib/api/npm_project_packages.rb @@ -48,14 +48,13 @@ module API put ':package_name', requirements: ::API::Helpers::Packages::Npm::NPM_ENDPOINT_REQUIREMENTS do authorize_create_package!(project) - track_package_event('push_package', :npm, category: 'API::NpmPackages', project: project, user: current_user, namespace: project.namespace) - created_package = ::Packages::Npm::CreatePackageService .new(project, current_user, params.merge(build: current_authenticated_job)).execute if created_package[:status] == :error render_api_error!(created_package[:message], created_package[:http_status]) else + track_package_event('push_package', :npm, category: 'API::NpmPackages', project: project, user: current_user, namespace: project.namespace) created_package end end diff --git a/lib/api/projects.rb b/lib/api/projects.rb index 28bcb382ecf..a92d904be84 100644 --- a/lib/api/projects.rb +++ b/lib/api/projects.rb @@ -152,6 +152,12 @@ module API ProjectsFinder.new(current_user: current_user, params: project_params).execute end + def present_project(project, options = {}) + options[:with].preload_resource(project) if options[:with].respond_to?(:preload_resource) + + present project, options + end + def present_projects(projects, options = {}) verify_statistics_order_by_projects! @@ -264,9 +270,9 @@ module API project = ::Projects::CreateService.new(current_user, attrs).execute if project.saved? - present project, with: Entities::Project, - user_can_admin_project: can?(current_user, :admin_project, project), - current_user: current_user + present_project project, with: Entities::Project, + user_can_admin_project: can?(current_user, :admin_project, project), + current_user: current_user else if project.errors[:limit_reached].present? error!(project.errors[:limit_reached], 403) @@ -301,9 +307,9 @@ module API project = ::Projects::CreateService.new(user, attrs).execute if project.saved? - present project, with: Entities::Project, - user_can_admin_project: can?(current_user, :admin_project, project), - current_user: current_user + present_project project, with: Entities::Project, + user_can_admin_project: can?(current_user, :admin_project, project), + current_user: current_user else render_validation_error!(project) end @@ -336,7 +342,7 @@ module API project, options = with_custom_attributes(user_project, options) - present project, options + present_project project, options end desc 'Fork new project for the current user or provided namespace.' do @@ -376,9 +382,11 @@ module API if forked_project.errors.any? conflict!(forked_project.errors.messages) else - present forked_project, with: Entities::Project, - user_can_admin_project: can?(current_user, :admin_project, forked_project), - current_user: current_user + present_project forked_project, { + with: Entities::Project, + user_can_admin_project: can?(current_user, :admin_project, forked_project), + current_user: current_user + } end end @@ -427,9 +435,9 @@ module API result = ::Projects::UpdateService.new(user_project, current_user, attrs).execute if result[:status] == :success - present user_project, with: Entities::Project, - user_can_admin_project: can?(current_user, :admin_project, user_project), - current_user: current_user + present_project user_project, with: Entities::Project, + user_can_admin_project: can?(current_user, :admin_project, user_project), + current_user: current_user else render_validation_error!(user_project) end @@ -443,7 +451,7 @@ module API ::Projects::UpdateService.new(user_project, current_user, archived: true).execute - present user_project, with: Entities::Project, current_user: current_user + present_project user_project, with: Entities::Project, current_user: current_user end desc 'Unarchive a project' do @@ -454,7 +462,7 @@ module API ::Projects::UpdateService.new(user_project, current_user, archived: false).execute - present user_project, with: Entities::Project, current_user: current_user + present_project user_project, with: Entities::Project, current_user: current_user end desc 'Star a project' do @@ -467,7 +475,7 @@ module API current_user.toggle_star(user_project) user_project.reset - present user_project, with: Entities::Project, current_user: current_user + present_project user_project, with: Entities::Project, current_user: current_user end end @@ -479,7 +487,7 @@ module API current_user.toggle_star(user_project) user_project.reset - present user_project, with: Entities::Project, current_user: current_user + present_project user_project, with: Entities::Project, current_user: current_user else not_modified! end @@ -528,7 +536,7 @@ module API result = ::Projects::ForkService.new(fork_from_project, current_user).execute(user_project) if result - present user_project.reset, with: Entities::Project, current_user: current_user + present_project user_project.reset, with: Entities::Project, current_user: current_user else render_api_error!("Project already forked", 409) if user_project.forked? end @@ -698,7 +706,7 @@ module API result = ::Projects::TransferService.new(user_project, current_user).execute(namespace) if result - present user_project, with: Entities::Project, current_user: current_user + present_project user_project, with: Entities::Project, current_user: current_user else render_api_error!("Failed to transfer project #{user_project.errors.messages}", 400) end diff --git a/lib/api/projects_relation_builder.rb b/lib/api/projects_relation_builder.rb index 6dfd82d109f..db46602cd90 100644 --- a/lib/api/projects_relation_builder.rb +++ b/lib/api/projects_relation_builder.rb @@ -7,28 +7,35 @@ module API class_methods do def prepare_relation(projects_relation, options = {}) projects_relation = preload_relation(projects_relation, options) + execute_batch_counting(projects_relation) - # Call the forks count method on every project, so the BatchLoader would load them all at - # once when the entities are rendered - projects_relation.each(&:forks_count) + + preload_repository_cache(projects_relation) projects_relation end + # This is overridden by the specific Entity class to + # preload assocations that it needs def preload_relation(projects_relation, options = {}) projects_relation end - def forks_counting_projects(projects_relation) - projects_relation + # This is overridden by the specific Entity class to + # batch load certain counts + def execute_batch_counting(projects_relation) end - def batch_open_issues_counting(projects_relation) - ::Projects::BatchOpenIssuesCountService.new(projects_relation).refresh_cache + def preload_repository_cache(projects_relation) + repositories = repositories_for_preload(projects_relation) + + Gitlab::RepositoryCache::Preloader.new(repositories).preload( # rubocop:disable CodeReuse/ActiveRecord + %i[exists? root_ref has_visible_content? avatar readme_path] + ) end - def execute_batch_counting(projects_relation) - batch_open_issues_counting(projects_relation) + def repositories_for_preload(projects_relation) + projects_relation.map(&:repository) end end end diff --git a/lib/api/repositories.rb b/lib/api/repositories.rb index 20320d1b7ae..3c9255e3117 100644 --- a/lib/api/repositories.rb +++ b/lib/api/repositories.rb @@ -51,18 +51,22 @@ module API optional :ref, type: String, desc: 'The name of a repository branch or tag, if not given the default branch is used' optional :path, type: String, desc: 'The path of the tree' optional :recursive, type: Boolean, default: false, desc: 'Used to get a recursive tree' + use :pagination + optional :pagination, type: String, values: %w(legacy keyset), default: 'legacy', desc: 'Specify the pagination method' + + given pagination: -> (value) { value == 'keyset' } do + optional :page_token, type: String, desc: 'Record from which to start the keyset pagination' + end end get ':id/repository/tree' do - ref = params[:ref] || user_project.default_branch - path = params[:path] || nil + tree_finder = ::Repositories::TreeFinder.new(user_project, declared_params(include_missing: false)) + + not_found!("Tree") unless tree_finder.commit_exists? - commit = user_project.commit(ref) - not_found!('Tree') unless commit + tree = Gitlab::Pagination::GitalyKeysetPager.new(self, user_project).paginate(tree_finder) - tree = user_project.repository.tree(commit.id, path, recursive: params[:recursive]) - entries = ::Kaminari.paginate_array(tree.sorted_entries) - present paginate(entries), with: Entities::TreeObject + present tree, with: Entities::TreeObject end desc 'Get raw blob contents from the repository' diff --git a/lib/api/settings.rb b/lib/api/settings.rb index aac195f0668..36f816ae638 100644 --- a/lib/api/settings.rb +++ b/lib/api/settings.rb @@ -176,6 +176,7 @@ module API 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)' + optional :user_deactivation_emails_enabled, type: Boolean, desc: 'Send emails to users upon account deactivation' ApplicationSetting::SUPPORTED_KEY_TYPES.each do |type| optional :"#{type}_key_restriction", @@ -225,6 +226,16 @@ module API attrs[:asset_proxy_allowlist] = attrs.delete(:asset_proxy_whitelist) end + # Also accept these attributes under their new names. + # + # TODO: Once we rename the columns, we have to swap this around and keep supporting the old names until v5. + # https://gitlab.com/gitlab-org/gitlab/-/issues/340031 + %w[enabled period_in_seconds requests_per_period].each do |suffix| + old_name = :"throttle_unauthenticated_#{suffix}" + new_name = :"throttle_unauthenticated_web_#{suffix}" + attrs[old_name] = attrs.delete(new_name) if attrs.has_key?(new_name) + end + # since 13.0 it's not possible to disable hashed storage - support can be removed in 14.0 attrs.delete(:hashed_storage_enabled) if attrs.has_key?(:hashed_storage_enabled) diff --git a/lib/api/templates.rb b/lib/api/templates.rb index a595129fd6a..85a299c5673 100644 --- a/lib/api/templates.rb +++ b/lib/api/templates.rb @@ -11,7 +11,7 @@ module API }, gitlab_ci_ymls: { gitlab_version: 8.9, - feature_category: :continuous_integration + feature_category: :pipeline_authoring }, dockerfiles: { gitlab_version: 8.15, diff --git a/lib/api/users.rb b/lib/api/users.rb index 2608fb87e22..e3271b8b9b2 100644 --- a/lib/api/users.rb +++ b/lib/api/users.rb @@ -615,6 +615,22 @@ module API end end + desc 'Reject a pending user. Available only for admins.' + params do + requires :id, type: Integer, desc: 'The ID of the user' + end + post ':id/reject', feature_category: :authentication_and_authorization do + user = find_user_by_id(params) + + result = ::Users::RejectService.new(current_user).execute(user) + + if result[:success] + present user + else + render_api_error!(result[:message], result[:http_status]) + end + end + # rubocop: enable CodeReuse/ActiveRecord desc 'Deactivate an active user. Available only for admins.' params do @@ -687,6 +703,38 @@ module API end # rubocop: enable CodeReuse/ActiveRecord + desc 'Ban a user. Available only for admins.' + params do + requires :id, type: Integer, desc: 'The ID of the user' + end + post ':id/ban', feature_category: :authentication_and_authorization do + authenticated_as_admin! + user = find_user_by_id(params) + + result = ::Users::BanService.new(current_user).execute(user) + if result[:status] == :success + true + else + render_api_error!(result[:message], result[:http_status]) + end + end + + desc 'Unban a user. Available only for admins.' + params do + requires :id, type: Integer, desc: 'The ID of the user' + end + post ':id/unban', feature_category: :authentication_and_authorization do + authenticated_as_admin! + user = find_user_by_id(params) + + result = ::Users::UnbanService.new(current_user).execute(user) + if result[:status] == :success + true + else + render_api_error!(result[:message], result[:http_status]) + end + end + desc 'Get memberships' do success Entities::Membership end |