diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2022-04-20 13:00:54 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2022-04-20 13:00:54 +0300 |
commit | 3cccd102ba543e02725d247893729e5c73b38295 (patch) | |
tree | f36a04ec38517f5deaaacb5acc7d949688d1e187 /lib/api | |
parent | 205943281328046ef7b4528031b90fbda70c75ac (diff) |
Add latest changes from gitlab-org/gitlab@14-10-stable-eev14.10.0-rc42
Diffstat (limited to 'lib/api')
61 files changed, 541 insertions, 124 deletions
diff --git a/lib/api/admin/instance_clusters.rb b/lib/api/admin/instance_clusters.rb index 4aebd9c0d40..d6c212a9886 100644 --- a/lib/api/admin/instance_clusters.rb +++ b/lib/api/admin/instance_clusters.rb @@ -112,7 +112,7 @@ module API helpers do def clusterable_instance - Clusters::Instance.new + ::Clusters::Instance.new end def clusters_for_current_user diff --git a/lib/api/admin/plan_limits.rb b/lib/api/admin/plan_limits.rb index d595b5b2e09..99be30809d2 100644 --- a/lib/api/admin/plan_limits.rb +++ b/lib/api/admin/plan_limits.rb @@ -5,7 +5,7 @@ module API class PlanLimits < ::API::Base before { authenticated_as_admin! } - feature_category :not_owned + feature_category :not_owned # rubocop:todo Gitlab/AvoidFeatureCategoryNotOwned helpers do def current_plan(name) diff --git a/lib/api/admin/sidekiq.rb b/lib/api/admin/sidekiq.rb index 05eb7f8222b..9be432046a5 100644 --- a/lib/api/admin/sidekiq.rb +++ b/lib/api/admin/sidekiq.rb @@ -5,7 +5,7 @@ module API class Sidekiq < ::API::Base before { authenticated_as_admin! } - feature_category :not_owned + feature_category :not_owned # rubocop:todo Gitlab/AvoidFeatureCategoryNotOwned namespace 'admin' do namespace 'sidekiq' do diff --git a/lib/api/alert_management_alerts.rb b/lib/api/alert_management_alerts.rb new file mode 100644 index 00000000000..88230c86247 --- /dev/null +++ b/lib/api/alert_management_alerts.rb @@ -0,0 +1,137 @@ +# frozen_string_literal: true + +module API + class AlertManagementAlerts < ::API::Base + feature_category :incident_management + + params do + requires :id, type: String, desc: 'The ID of a project' + requires :alert_iid, type: Integer, desc: 'The IID of the Alert' + end + + resource :projects, requirements: ::API::API::NAMESPACE_OR_PROJECT_REQUIREMENTS do + namespace ':id/alert_management_alerts/:alert_iid/metric_images' do + post 'authorize' do + authorize!(:upload_alert_management_metric_image, find_project_alert(request.params[:alert_iid])) + + require_gitlab_workhorse! + ::Gitlab::Workhorse.verify_api_request!(request.headers) + status 200 + content_type ::Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE + + params = { + has_length: false, + maximum_size: ::AlertManagement::MetricImage::MAX_FILE_SIZE.to_i + } + + ::MetricImageUploader.workhorse_authorize(**params) + end + + desc 'Upload a metric image for an alert' do + success Entities::MetricImage + end + params do + requires :file, type: ::API::Validations::Types::WorkhorseFile, desc: 'The image file to be uploaded' + optional :url, type: String, desc: 'The url to view more metric info' + optional :url_text, type: String, desc: 'A description of the image or URL' + end + post do + require_gitlab_workhorse! + bad_request!('File is too large') if max_file_size_exceeded? + + alert = find_project_alert(params[:alert_iid]) + + authorize!(:upload_alert_management_metric_image, alert) + + upload = ::AlertManagement::MetricImages::UploadService.new( + alert, + current_user, + params.slice(:file, :url, :url_text) + ).execute + + if upload.success? + present upload.payload[:metric], + with: Entities::MetricImage, + current_user: current_user, + project: user_project + else + render_api_error!(upload.message, upload.http_status) + end + end + + desc 'Metric Images for alert' + get do + alert = find_project_alert(params[:alert_iid]) + + if can?(current_user, :read_alert_management_metric_image, alert) + present alert.metric_images.order_created_at_asc, with: Entities::MetricImage + else + render_api_error!('Alert not found', 404) + end + end + + desc 'Update a metric image for an alert' do + success Entities::MetricImage + end + params do + requires :metric_image_id, type: Integer, desc: 'The ID of metric image' + optional :url, type: String, desc: 'The url to view more metric info' + optional :url_text, type: String, desc: 'A description of the image or URL' + end + put ':metric_image_id' do + alert = find_project_alert(params[:alert_iid]) + + authorize!(:update_alert_management_metric_image, alert) + + render_api_error!('Feature not available', 403) unless alert.metric_images_available? + + metric_image = alert.metric_images.find_by_id(params[:metric_image_id]) + + render_api_error!('Metric image not found', 404) unless metric_image + + if metric_image.update(params.slice(:url, :url_text)) + present metric_image, with: Entities::MetricImage, current_user: current_user, project: user_project + else + unprocessable_entity!('Metric image could not be updated') + end + end + + desc 'Remove a metric image for an alert' do + success Entities::MetricImage + end + params do + requires :metric_image_id, type: Integer, desc: 'The ID of metric image' + end + delete ':metric_image_id' do + alert = find_project_alert(params[:alert_iid]) + + authorize!(:destroy_alert_management_metric_image, alert) + + render_api_error!('Feature not available', 403) unless alert.metric_images_available? + + metric_image = alert.metric_images.find_by_id(params[:metric_image_id]) + + render_api_error!('Metric image not found', 404) unless metric_image + + if metric_image.destroy + no_content! + else + unprocessable_entity!('Metric image could not be deleted') + end + end + end + end + + helpers do + def find_project_alert(iid, project_id = nil) + project = project_id ? find_project!(project_id) : user_project + + ::AlertManagement::AlertsFinder.new(current_user, project, { iid: [iid] }).execute.first + end + + def max_file_size_exceeded? + params[:file].size > ::AlertManagement::MetricImage::MAX_FILE_SIZE + end + end + end +end diff --git a/lib/api/api.rb b/lib/api/api.rb index 5100ec9ec9d..4dca47efdf2 100644 --- a/lib/api/api.rb +++ b/lib/api/api.rb @@ -80,6 +80,10 @@ module API Gitlab::UsageDataCounters::JetBrainsPluginActivityUniqueCounter.track_api_request_when_trackable(user_agent: request&.user_agent, user: @current_user) end + after do + Gitlab::UsageDataCounters::GitLabCliActivityUniqueCounter.track_api_request_when_trackable(user_agent: request&.user_agent, user: @current_user) + end + # The locale is set to the current user's locale when `current_user` is loaded after { Gitlab::I18n.use_default_locale } @@ -159,6 +163,7 @@ module API mount ::API::Admin::InstanceClusters mount ::API::Admin::PlanLimits mount ::API::Admin::Sidekiq + mount ::API::AlertManagementAlerts mount ::API::Appearance mount ::API::Applications mount ::API::Avatar @@ -178,6 +183,7 @@ module API mount ::API::Ci::SecureFiles mount ::API::Ci::Triggers mount ::API::Ci::Variables + mount ::API::Clusters::Agents mount ::API::Commits mount ::API::CommitStatuses mount ::API::ContainerRegistryEvent @@ -317,7 +323,7 @@ module API end end - route :any, '*path', feature_category: :not_owned do + route :any, '*path', feature_category: :not_owned do # rubocop:todo Gitlab/AvoidFeatureCategoryNotOwned error!('404 Not Found', 404) end end diff --git a/lib/api/bulk_imports.rb b/lib/api/bulk_imports.rb index c732da17166..53967e0af5d 100644 --- a/lib/api/bulk_imports.rb +++ b/lib/api/bulk_imports.rb @@ -10,7 +10,7 @@ module API def bulk_imports @bulk_imports ||= ::BulkImports::ImportsFinder.new( user: current_user, - status: params[:status] + params: params ).execute end @@ -22,7 +22,7 @@ module API @bulk_import_entities ||= ::BulkImports::EntitiesFinder.new( user: current_user, bulk_import: bulk_import, - status: params[:status] + params: params ).execute end @@ -70,6 +70,8 @@ module API end params do use :pagination + optional :sort, type: String, values: %w[asc desc], default: 'desc', + desc: 'Return GitLab Migrations sorted in created by `asc` or `desc` order.' optional :status, type: String, values: BulkImport.all_human_statuses, desc: 'Return GitLab Migrations with specified status' end @@ -82,13 +84,15 @@ module API end params do use :pagination + optional :sort, type: String, values: %w[asc desc], default: 'desc', + desc: 'Return GitLab Migrations sorted in created by `asc` or `desc` order.' optional :status, type: String, values: ::BulkImports::Entity.all_human_statuses, desc: "Return all GitLab Migrations' entities with specified status" end get :entities do entities = ::BulkImports::EntitiesFinder.new( user: current_user, - status: params[:status] + params: params ).execute present paginate(entities), with: Entities::BulkImports::Entity diff --git a/lib/api/ci/helpers/runner.rb b/lib/api/ci/helpers/runner.rb index 43ed35b99fd..173cfc9a59a 100644 --- a/lib/api/ci/helpers/runner.rb +++ b/lib/api/ci/helpers/runner.rb @@ -104,10 +104,7 @@ module API def set_application_context return unless current_job - Gitlab::ApplicationContext.push( - user: -> { current_job.user }, - project: -> { current_job.project } - ) + Gitlab::ApplicationContext.push(job: current_job) end def track_ci_minutes_usage!(_build, _runner) diff --git a/lib/api/ci/job_artifacts.rb b/lib/api/ci/job_artifacts.rb index 9f59eea5013..0800993602b 100644 --- a/lib/api/ci/job_artifacts.rb +++ b/lib/api/ci/job_artifacts.rb @@ -28,7 +28,7 @@ module API requires :job, type: String, desc: 'The name for the job' end route_setting :authentication, job_token_allowed: true - get ':id/jobs/artifacts/:ref_name/download', + get ':id/jobs/artifacts/:ref_name/download', urgency: :low, requirements: { ref_name: /.+/ } do authorize_download_artifacts! @@ -87,7 +87,7 @@ module API requires :artifact_path, type: String, desc: 'Artifact path' end route_setting :authentication, job_token_allowed: true - get ':id/jobs/:job_id/artifacts/*artifact_path', format: false do + get ':id/jobs/:job_id/artifacts/*artifact_path', urgency: :low, format: false do authorize_download_artifacts! build = find_build!(params[:job_id]) @@ -100,7 +100,11 @@ module API bad_request! unless path.valid? - send_artifacts_entry(build.artifacts_file, path) + # This endpoint is being used for Artifact Browser feature that renders the content via pages. + # Since Content-Type is controlled by Rails and Workhorse, if a wrong + # content-type is sent, it could cause a regression on pages rendering. + # See https://gitlab.com/gitlab-org/gitlab/-/issues/357078 for more information. + legacy_send_artifacts_entry(build.artifacts_file, path) end desc 'Keep the artifacts to prevent them from being deleted' do @@ -140,8 +144,6 @@ module API desc 'Expire the artifacts files from a project' delete ':id/artifacts' do - not_found! unless Feature.enabled?(:bulk_expire_project_artifacts, default_enabled: :yaml) - authorize_destroy_artifacts! ::Ci::JobArtifacts::DeleteProjectArtifactsService.new(project: user_project).execute diff --git a/lib/api/ci/jobs.rb b/lib/api/ci/jobs.rb index d9d0da2e4d1..86897eb61ae 100644 --- a/lib/api/ci/jobs.rb +++ b/lib/api/ci/jobs.rb @@ -114,11 +114,14 @@ module API build = find_build!(params[:job_id]) authorize!(:update_build, build) - break forbidden!('Job is not retryable') unless build.retryable? - build = ::Ci::Build.retry(build, current_user) + response = ::Ci::RetryJobService.new(@project, current_user).execute(build) - present build, with: Entities::Ci::Job + if response.success? + present response[:job], with: Entities::Ci::Job + else + forbidden!('Job is not retryable') + end end desc 'Erase job (remove artifacts and the trace)' do @@ -194,7 +197,7 @@ module API pipeline = current_authenticated_job.pipeline project = current_authenticated_job.project - agent_authorizations = Clusters::AgentAuthorizationsFinder.new(project).execute + agent_authorizations = ::Clusters::AgentAuthorizationsFinder.new(project).execute project_groups = project.group&.self_and_ancestor_ids&.map { |id| { id: id } } || [] user_access_level = project.team.max_member_access(current_user.id) roles_in_project = Gitlab::Access.sym_options_with_owner diff --git a/lib/api/ci/pipelines.rb b/lib/api/ci/pipelines.rb index 2d7a437ca08..8d2c58dabdf 100644 --- a/lib/api/ci/pipelines.rb +++ b/lib/api/ci/pipelines.rb @@ -146,7 +146,7 @@ module API use :pagination end - get ':id/pipelines/:pipeline_id/bridges', feature_category: :pipeline_authoring do + get ':id/pipelines/:pipeline_id/bridges', urgency: :low, feature_category: :pipeline_authoring do authorize!(:read_build, user_project) pipeline = user_project.all_pipelines.find(params[:pipeline_id]) diff --git a/lib/api/ci/secure_files.rb b/lib/api/ci/secure_files.rb index d5b21e2ef29..ee39bdfd90c 100644 --- a/lib/api/ci/secure_files.rb +++ b/lib/api/ci/secure_files.rb @@ -54,6 +54,7 @@ module API resource do before do + read_only_feature_flag_enabled? authorize! :admin_secure_files, user_project end @@ -97,6 +98,10 @@ module API def feature_flag_enabled? service_unavailable! unless Feature.enabled?(:ci_secure_files, user_project, default_enabled: :yaml) end + + def read_only_feature_flag_enabled? + service_unavailable! if Feature.enabled?(:ci_secure_files_read_only, user_project, type: :ops, default_enabled: :yaml) + end end end end diff --git a/lib/api/ci/variables.rb b/lib/api/ci/variables.rb index 9c04d5e9923..ec9951aba0d 100644 --- a/lib/api/ci/variables.rb +++ b/lib/api/ci/variables.rb @@ -23,7 +23,7 @@ module API params do use :pagination end - get ':id/variables' do + get ':id/variables', urgency: :low do variables = user_project.variables present paginate(variables), with: Entities::Ci::Variable end diff --git a/lib/api/clusters/agents.rb b/lib/api/clusters/agents.rb new file mode 100644 index 00000000000..6c1bf21b952 --- /dev/null +++ b/lib/api/clusters/agents.rb @@ -0,0 +1,81 @@ +# frozen_string_literal: true + +module API + module Clusters + class Agents < ::API::Base + include PaginationParams + + before { authenticate! } + + feature_category :kubernetes_management + + params do + requires :id, type: String, desc: 'The ID of a project' + end + resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do + desc 'List agents' do + detail 'This feature was introduced in GitLab 14.10.' + success Entities::Clusters::Agent + end + params do + use :pagination + end + get ':id/cluster_agents' do + authorize! :read_cluster, user_project + + agents = ::Clusters::AgentsFinder.new(user_project, current_user).execute + + present paginate(agents), with: Entities::Clusters::Agent + end + + desc 'Get single agent' do + detail 'This feature was introduced in GitLab 14.10.' + success Entities::Clusters::Agent + end + params do + requires :agent_id, type: Integer, desc: 'The ID of an agent' + end + get ':id/cluster_agents/:agent_id' do + authorize! :read_cluster, user_project + + agent = user_project.cluster_agents.find(params[:agent_id]) + + present agent, with: Entities::Clusters::Agent + end + + desc 'Add an agent to a project' do + detail 'This feature was introduced in GitLab 14.10.' + success Entities::Clusters::Agent + end + params do + requires :name, type: String, desc: 'The name of the agent' + end + post ':id/cluster_agents' do + authorize! :create_cluster, user_project + + params = declared_params(include_missing: false) + + result = ::Clusters::Agents::CreateService.new(user_project, current_user).execute(name: params[:name]) + + bad_request!(result[:message]) if result[:status] == :error + + present result[:cluster_agent], with: Entities::Clusters::Agent + end + + desc 'Delete an agent' do + detail 'This feature was introduced in GitLab 14.10.' + end + params do + requires :agent_id, type: Integer, desc: 'The ID of an agent' + end + delete ':id/cluster_agents/:agent_id' do + authorize! :admin_cluster, user_project + + agent = user_project.cluster_agents.find(params.delete(:agent_id)) + + destroy_conditionally!(agent) + end + end + end + end +end diff --git a/lib/api/composer_packages.rb b/lib/api/composer_packages.rb index 0e6e04d2645..c311b34a697 100644 --- a/lib/api/composer_packages.rb +++ b/lib/api/composer_packages.rb @@ -113,10 +113,6 @@ module API end resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do - before do - unauthorized_user_project! - end - desc 'Composer packages endpoint for registering packages' namespace ':id/packages/composer' do route_setting :authentication, job_token_allowed: true, basic_auth_personal_access_token: true, deploy_token_allowed: true @@ -150,8 +146,11 @@ module API requires :sha, type: String, desc: 'Shasum of current json' requires :package_name, type: String, file_path: true, desc: 'The Composer package name' end + route_setting :authentication, job_token_allowed: true, basic_auth_personal_access_token: true, deploy_token_allowed: true get 'archives/*package_name' do - metadata = unauthorized_user_project + authorize_read_package!(authorized_user_project) + + metadata = authorized_user_project .packages .composer .with_name(params[:package_name]) @@ -161,9 +160,9 @@ module API not_found! unless metadata - track_package_event('pull_package', :composer, project: unauthorized_user_project, namespace: unauthorized_user_project.namespace) + track_package_event('pull_package', :composer, project: authorized_user_project, namespace: authorized_user_project.namespace) - send_git_archive unauthorized_user_project.repository, ref: metadata.target_sha, format: 'zip', append_sha: true + send_git_archive authorized_user_project.repository, ref: metadata.target_sha, format: 'zip', append_sha: true end end end diff --git a/lib/api/entities/application_setting.rb b/lib/api/entities/application_setting.rb index 465c5f4112b..db51d4380d0 100644 --- a/lib/api/entities/application_setting.rb +++ b/lib/api/entities/application_setting.rb @@ -40,6 +40,9 @@ module API expose :password_authentication_enabled_for_web, as: :signin_enabled expose :allow_local_requests_from_web_hooks_and_services, as: :allow_local_requests_from_hooks_and_services expose :asset_proxy_allowlist, as: :asset_proxy_whitelist + + # This field is deprecated and always returns true + expose(:housekeeping_bitmaps_enabled) { |_settings, _options| true } end end end diff --git a/lib/api/entities/award_emoji.rb b/lib/api/entities/award_emoji.rb index da9a183bf39..40dc38b1900 100644 --- a/lib/api/entities/award_emoji.rb +++ b/lib/api/entities/award_emoji.rb @@ -8,6 +8,7 @@ module API expose :user, using: Entities::UserBasic expose :created_at, :updated_at expose :awardable_id, :awardable_type + expose :url end end end diff --git a/lib/api/entities/basic_release_details.rb b/lib/api/entities/basic_release_details.rb new file mode 100644 index 00000000000..d13080f32f4 --- /dev/null +++ b/lib/api/entities/basic_release_details.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +module API + module Entities + class BasicReleaseDetails < Grape::Entity + include ::API::Helpers::Presentable + + expose :name + expose :tag, as: :tag_name + expose :description + expose :created_at + expose :released_at + expose :upcoming_release?, as: :upcoming_release + end + end +end diff --git a/lib/api/entities/ci/job_request/artifacts.rb b/lib/api/entities/ci/job_request/artifacts.rb index 4b09db40504..d1fb7d330b9 100644 --- a/lib/api/entities/ci/job_request/artifacts.rb +++ b/lib/api/entities/ci/job_request/artifacts.rb @@ -6,7 +6,7 @@ module API module JobRequest class Artifacts < Grape::Entity expose :name - expose :untracked + expose :untracked, expose_nil: false expose :paths expose :exclude, expose_nil: false expose :when diff --git a/lib/api/entities/clusters/agent.rb b/lib/api/entities/clusters/agent.rb index 3b4538b81c2..140b680f5e8 100644 --- a/lib/api/entities/clusters/agent.rb +++ b/lib/api/entities/clusters/agent.rb @@ -5,7 +5,10 @@ module API module Clusters class Agent < Grape::Entity expose :id + expose :name expose :project, with: Entities::ProjectIdentity, as: :config_project + expose :created_at + expose :created_by_user_id end end end diff --git a/lib/api/entities/commit_with_link.rb b/lib/api/entities/commit_with_link.rb index a135cc19480..23efaca34d5 100644 --- a/lib/api/entities/commit_with_link.rb +++ b/lib/api/entities/commit_with_link.rb @@ -29,7 +29,7 @@ module API end expose :signature_html, if: { type: :full } do |commit| - render('projects/commit/_signature', signature: commit.signature) if commit.has_signature? + ::CommitPresenter.new(commit).signature_html end expose :prev_commit_id, if: { type: :full } do |commit| @@ -50,12 +50,6 @@ module API pipelines_project_commit_path(pipeline_project, commit.id, ref: pipeline_ref) end - - def render(*args) - return unless request.respond_to?(:render) && request.render.respond_to?(:call) - - request.render.call(*args) - end end end end diff --git a/lib/api/entities/issue.rb b/lib/api/entities/issue.rb index e2506cc596e..f87ef093cd8 100644 --- a/lib/api/entities/issue.rb +++ b/lib/api/entities/issue.rb @@ -35,6 +35,10 @@ module API issue end + expose :severity, + format_with: :upcase, + documentation: { type: "String", desc: "One of #{::IssuableSeverity.severities.keys.map(&:upcase)}" } + # Calculating the value of subscribed field triggers Markdown # processing. We can't do that for multiple issues / merge # requests in a single API request. diff --git a/lib/api/entities/member.rb b/lib/api/entities/member.rb index 87f03adba31..7ce1e73a043 100644 --- a/lib/api/entities/member.rb +++ b/lib/api/entities/member.rb @@ -6,6 +6,7 @@ module API expose :user, merge: true, using: UserBasic expose :access_level expose :created_at + expose :created_by, with: UserBasic, expose_nil: false expose :expires_at end end diff --git a/lib/api/entities/merge_request_changes.rb b/lib/api/entities/merge_request_changes.rb index 488f33dfb93..a1e8b5ae00a 100644 --- a/lib/api/entities/merge_request_changes.rb +++ b/lib/api/entities/merge_request_changes.rb @@ -24,7 +24,7 @@ module API end def expose_raw_diffs? - options[:access_raw_diffs] || ::Feature.enabled?(:mrc_api_use_raw_diffs_from_gitaly, options[:project]) + options[:access_raw_diffs] end end end diff --git a/lib/api/entities/metric_image.rb b/lib/api/entities/metric_image.rb new file mode 100644 index 00000000000..fd5e3a62e40 --- /dev/null +++ b/lib/api/entities/metric_image.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +module API + module Entities + class MetricImage < Grape::Entity + expose :id, :created_at, :filename, :file_path, :url, :url_text + end + end +end diff --git a/lib/api/entities/project.rb b/lib/api/entities/project.rb index 8f9a8add938..60cc5167c41 100644 --- a/lib/api/entities/project.rb +++ b/lib/api/entities/project.rb @@ -85,8 +85,11 @@ module API end expose :mr_default_target_self, if: -> (project) { project.forked? } + expose :import_url, if: -> (project, options) { Ability.allowed?(options[:current_user], :admin_project, project) } do |project| + project[:import_url] + end + expose :import_type, if: -> (project, options) { Ability.allowed?(options[:current_user], :admin_project, project) } expose :import_status - expose :import_error, if: lambda { |_project, options| options[:user_can_admin_project] } do |project| project.import_state&.last_error end diff --git a/lib/api/entities/release.rb b/lib/api/entities/release.rb index 056b54674f1..2403c907f7f 100644 --- a/lib/api/entities/release.rb +++ b/lib/api/entities/release.rb @@ -2,20 +2,14 @@ module API module Entities - class Release < Grape::Entity + class Release < BasicReleaseDetails include ::API::Helpers::Presentable - expose :name - expose :tag, as: :tag_name, if: ->(_, _) { can_download_code? } - expose :description expose :description_html, if: -> (_, options) { options[:include_html_description] } do |entity| MarkupHelper.markdown_field(entity, :description, current_user: options[:current_user]) end - expose :created_at - expose :released_at expose :author, using: Entities::UserBasic, if: -> (release, _) { release.author.present? } expose :commit, using: Entities::Commit, if: ->(_, _) { can_download_code? } - expose :upcoming_release?, as: :upcoming_release expose :milestones, using: Entities::MilestoneWithStats, if: -> (release, _) { release.milestones.present? && can_read_milestone? } do |release, _| diff --git a/lib/api/entities/user_with_admin.rb b/lib/api/entities/user_with_admin.rb index e148a5c45b5..f9c1a646a4f 100644 --- a/lib/api/entities/user_with_admin.rb +++ b/lib/api/entities/user_with_admin.rb @@ -5,6 +5,7 @@ module API class UserWithAdmin < UserPublic expose :admin?, as: :is_admin expose :note + expose :namespace_id end end end diff --git a/lib/api/entities/wiki_attachment.rb b/lib/api/entities/wiki_attachment.rb index e622dea04dd..03a6cc8d644 100644 --- a/lib/api/entities/wiki_attachment.rb +++ b/lib/api/entities/wiki_attachment.rb @@ -16,11 +16,11 @@ module API end def filename - object.file_name + object[:file_name] end def secure_url - object.file_path + object[:file_path] end end end diff --git a/lib/api/environments.rb b/lib/api/environments.rb index c032b80e39b..19b48c1e3cf 100644 --- a/lib/api/environments.rb +++ b/lib/api/environments.rb @@ -131,7 +131,7 @@ module API environment = user_project.environments.find(params[:environment_id]) authorize! :stop_environment, environment - environment.stop_with_action!(current_user) + environment.stop_with_actions!(current_user) status 200 present environment, with: Entities::Environment, current_user: current_user diff --git a/lib/api/files.rb b/lib/api/files.rb index 39b3904ec90..41a8e899614 100644 --- a/lib/api/files.rb +++ b/lib/api/files.rb @@ -24,7 +24,8 @@ module API file_content_encoding: attrs[:encoding], author_email: attrs[:author_email], author_name: attrs[:author_name], - last_commit_sha: attrs[:last_commit_id] + last_commit_sha: attrs[:last_commit_id], + execute_filemode: attrs[:execute_filemode] } end @@ -65,7 +66,8 @@ module API ref: params[:ref], blob_id: @blob.id, commit_id: @commit.id, - last_commit_id: @repo.last_commit_id_for_path(@commit.sha, params[:file_path], literal_pathspec: true) + last_commit_id: @repo.last_commit_id_for_path(@commit.sha, params[:file_path], literal_pathspec: true), + execute_filemode: @blob.executable? } end @@ -83,6 +85,7 @@ module API requires :content, type: String, desc: 'File content' optional :encoding, type: String, values: %w[base64], desc: 'File encoding' optional :last_commit_id, type: String, desc: 'Last known commit id for this file' + optional :execute_filemode, type: Boolean, desc: 'Enable / Disable the executable flag on the file path' end end diff --git a/lib/api/group_export.rb b/lib/api/group_export.rb index f0c0182a02f..5754eceda97 100644 --- a/lib/api/group_export.rb +++ b/lib/api/group_export.rb @@ -3,8 +3,6 @@ module API class GroupExport < ::API::Base before do - not_found! unless Feature.enabled?(:group_import_export, user_group, default_enabled: true) - authorize! :admin_group, user_group end diff --git a/lib/api/groups.rb b/lib/api/groups.rb index 5fbf222be5d..0ed14476c61 100644 --- a/lib/api/groups.rb +++ b/lib/api/groups.rb @@ -7,10 +7,10 @@ module API before { authenticate_non_get! } - feature_category :subgroups - helpers Helpers::GroupsHelpers + feature_category :subgroups, ['/groups/:id/custom_attributes', '/groups/:id/custom_attributes/:key'] + helpers do params :statistics_params do optional :statistics, type: Boolean, default: false, desc: 'Include project statistics' @@ -181,7 +181,7 @@ module API use :group_list_params use :with_custom_attributes end - get do + get feature_category: :subgroups do groups = find_groups(declared_params(include_missing: false), params[:id]) present_groups_with_pagination_strategies params, groups end @@ -196,7 +196,7 @@ module API use :optional_params end - post do + post feature_category: :subgroups do parent_group = find_group!(params[:parent_id]) if params[:parent_id].present? if parent_group authorize! :create_subgroup, parent_group @@ -229,7 +229,7 @@ module API use :optional_update_params use :optional_update_params_ee end - put ':id' do + put ':id', feature_category: :subgroups do group = find_group!(params[:id]) group.preload_shared_group_links @@ -249,7 +249,8 @@ module API use :with_custom_attributes optional :with_projects, type: Boolean, default: true, desc: 'Omit project details' end - get ":id" do + # TODO: Set higher urgency after resolving https://gitlab.com/gitlab-org/gitlab/-/issues/357841 + get ":id", feature_category: :subgroups, urgency: :low do group = find_group!(params[:id]) group.preload_shared_group_links @@ -265,7 +266,7 @@ module API end desc 'Remove a group.' - delete ":id" do + delete ":id", feature_category: :subgroups do group = find_group!(params[:id]) authorize! :admin_group, group check_subscription! group @@ -300,7 +301,8 @@ module API use :with_custom_attributes use :optional_projects_params end - get ":id/projects" do + # TODO: Set higher urgency after resolving https://gitlab.com/gitlab-org/gitlab/-/issues/211498 + get ":id/projects", feature_category: :subgroups, urgency: :low do finder_options = { only_owned: !params[:with_shared], include_subgroups: params[:include_subgroups], @@ -334,7 +336,7 @@ module API use :pagination use :with_custom_attributes end - get ":id/projects/shared" do + get ":id/projects/shared", feature_category: :subgroups do projects = find_group_projects(params, { only_shared: true }) present_projects(params, projects) @@ -347,7 +349,7 @@ module API use :group_list_params use :with_custom_attributes end - get ":id/subgroups" do + get ":id/subgroups", feature_category: :subgroups, urgency: :low do groups = find_groups(declared_params(include_missing: false), params[:id]) present_groups params, groups end @@ -359,7 +361,7 @@ module API use :group_list_params use :with_custom_attributes end - get ":id/descendant_groups" do + get ":id/descendant_groups", feature_category: :subgroups do finder_params = declared_params(include_missing: false).merge(include_parent_descendants: true) groups = find_groups(finder_params, params[:id]) present_groups params, groups @@ -371,7 +373,7 @@ module API params do requires :project_id, type: String, desc: 'The ID or path of the project' end - post ":id/projects/:project_id", requirements: { project_id: /.+/ } do + post ":id/projects/:project_id", requirements: { project_id: /.+/ }, feature_category: :projects do authenticated_as_admin! group = find_group!(params[:id]) group.preload_shared_group_links @@ -391,7 +393,7 @@ module API desc: 'The ID of the target group to which the group needs to be transferred to.'\ 'If not provided, the source group will be promoted to a root group.' end - post ':id/transfer' do + post ':id/transfer', feature_category: :subgroups do group = find_group!(params[:id]) authorize! :admin_group, group @@ -415,7 +417,7 @@ module API requires :group_access, type: Integer, values: Gitlab::Access.all_values, desc: 'The group access level' optional :expires_at, type: Date, desc: 'Share expiration date' end - post ":id/share" do + post ":id/share", feature_category: :subgroups do shared_group = find_group!(params[:id]) shared_with_group = find_group!(params[:group_id]) @@ -438,7 +440,7 @@ module API requires :group_id, type: Integer, desc: 'The ID of the shared group' end # rubocop: disable CodeReuse/ActiveRecord - delete ":id/share/:group_id" do + delete ":id/share/:group_id", feature_category: :subgroups do shared_group = find_group!(params[:id]) link = shared_group.shared_with_group_links.find_by(shared_with_group_id: params[:group_id]) diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb index de9d42bdce7..e4a7f2213ae 100644 --- a/lib/api/helpers.rb +++ b/lib/api/helpers.rb @@ -705,8 +705,16 @@ module API body '' end + # Deprecated. Use `send_artifacts_entry` instead. + def legacy_send_artifacts_entry(file, entry) + header(*Gitlab::Workhorse.send_artifacts_entry(file, entry)) + + body '' + end + def send_artifacts_entry(file, entry) header(*Gitlab::Workhorse.send_artifacts_entry(file, entry)) + header(*Gitlab::Workhorse.detect_content_type) body '' end diff --git a/lib/api/integrations.rb b/lib/api/integrations.rb index ff1d88e35f0..71c55704ddf 100644 --- a/lib/api/integrations.rb +++ b/lib/api/integrations.rb @@ -6,7 +6,7 @@ module API integrations = Helpers::IntegrationsHelpers.integrations integration_classes = Helpers::IntegrationsHelpers.integration_classes - if Rails.env.development? + if Gitlab.dev_or_test_env? integrations['mock-ci'] = [ { required: true, diff --git a/lib/api/internal/base.rb b/lib/api/internal/base.rb index 9c527f28d44..2ab5d482295 100644 --- a/lib/api/internal/base.rb +++ b/lib/api/internal/base.rb @@ -189,7 +189,7 @@ module API present actor.user, with: Entities::UserSafe end - get '/check', feature_category: :not_owned do + get '/check', feature_category: :not_owned do # rubocop:todo Gitlab/AvoidFeatureCategoryNotOwned { api_version: API.version, gitlab_version: Gitlab::VERSION, diff --git a/lib/api/internal/kubernetes.rb b/lib/api/internal/kubernetes.rb index df887a83c4f..59bc917a602 100644 --- a/lib/api/internal/kubernetes.rb +++ b/lib/api/internal/kubernetes.rb @@ -54,7 +54,7 @@ module API def check_agent_token unauthorized! unless agent_token - Clusters::AgentTokens::TrackUsageService.new(agent_token).execute + ::Clusters::AgentTokens::TrackUsageService.new(agent_token).execute end end @@ -91,9 +91,9 @@ module API requires :agent_config, type: JSON, desc: 'Configuration for the Agent' end post '/' do - agent = Clusters::Agent.find(params[:agent_id]) + agent = ::Clusters::Agent.find(params[:agent_id]) - Clusters::Agents::RefreshAuthorizationService.new(agent, config: params[:agent_config]).execute + ::Clusters::Agents::RefreshAuthorizationService.new(agent, config: params[:agent_config]).execute no_content! end diff --git a/lib/api/invitations.rb b/lib/api/invitations.rb index d78576b5d5b..75f63a5d98f 100644 --- a/lib/api/invitations.rb +++ b/lib/api/invitations.rb @@ -20,19 +20,25 @@ module API success Entities::Invitation end params do - requires :email, types: [String, Array[String]], email_or_email_list: true, desc: 'The email address to invite, or multiple emails separated by comma' requires :access_level, type: Integer, values: Gitlab::Access.all_values, desc: 'A valid access level (defaults: `30`, developer access level)' + optional :email, types: [String, Array[String]], email_or_email_list: true, desc: 'The email address to invite, or multiple emails separated by comma' + optional :user_id, types: [Integer, String], desc: 'The user ID of the new member or multiple IDs separated by commas.' optional :expires_at, type: DateTime, desc: 'Date string in the format YEAR-MONTH-DAY' optional :invite_source, type: String, desc: 'Source that triggered the member creation process', default: 'invitations-api' optional :tasks_to_be_done, type: Array[String], coerce_with: Validations::Types::CommaSeparatedToArray.coerce, desc: 'Tasks the inviter wants the member to do' optional :tasks_project_id, type: Integer, desc: 'The project ID in which to create the task issues' end post ":id/invitations" do - params[:source] = find_source(source_type, params[:id]) + ::Gitlab::QueryLimiting.disable!('https://gitlab.com/gitlab-org/gitlab/-/issues/354016') - authorize_admin_source!(source_type, params[:source]) + bad_request!('Must provide either email or user_id as a parameter') if params[:email].blank? && params[:user_id].blank? - ::Members::InviteService.new(current_user, params).execute + source = find_source(source_type, params[:id]) + authorize_admin_source!(source_type, source) + + create_service_params = params.except(:user_id).merge({ user_ids: params[:user_id], source: source }) + + ::Members::InviteService.new(current_user, create_service_params).execute end desc 'Get a list of group or project invitations viewable by the authenticated user' do diff --git a/lib/api/issue_links.rb b/lib/api/issue_links.rb index 98451afb12d..0e93a4adb65 100644 --- a/lib/api/issue_links.rb +++ b/lib/api/issue_links.rb @@ -67,14 +67,16 @@ module API requires :issue_link_id, type: Integer, desc: 'The ID of an issue link' end delete ':id/issues/:issue_iid/links/:issue_link_id' do - issue_link = IssueLink.find(declared_params[:issue_link_id]) + issue = find_project_issue(params[:issue_iid]) + issue_link = IssueLink + .for_source_or_target(issue) + .find(declared_params[:issue_link_id]) - find_project_issue(params[:issue_iid]) find_project_issue(issue_link.target.iid.to_s, issue_link.target.project_id.to_s) result = ::IssueLinks::DestroyService - .new(issue_link, current_user) - .execute + .new(issue_link, current_user) + .execute if result[:status] == :success present issue_link, with: Entities::IssueLink diff --git a/lib/api/lint.rb b/lib/api/lint.rb index 6de78c81cac..f65ecf3b4a6 100644 --- a/lib/api/lint.rb +++ b/lib/api/lint.rb @@ -21,7 +21,7 @@ module API optional :include_merged_yaml, type: Boolean, desc: 'Whether or not to include merged CI config yaml in the response' optional :include_jobs, type: Boolean, desc: 'Whether or not to include CI jobs in the response' end - post '/lint' do + post '/lint', urgency: :low do unauthorized! unless can_lint_ci? result = Gitlab::Ci::Lint.new(project: nil, current_user: current_user) diff --git a/lib/api/markdown.rb b/lib/api/markdown.rb index de612ff8321..c465087c4a2 100644 --- a/lib/api/markdown.rb +++ b/lib/api/markdown.rb @@ -2,7 +2,7 @@ module API class Markdown < ::API::Base - feature_category :not_owned + feature_category :not_owned # rubocop:todo Gitlab/AvoidFeatureCategoryNotOwned params do requires :text, type: String, desc: "The markdown text to render" diff --git a/lib/api/members.rb b/lib/api/members.rb index 4798edc4ddf..01e859c94c4 100644 --- a/lib/api/members.rb +++ b/lib/api/members.rb @@ -7,6 +7,7 @@ module API before { authenticate! } feature_category :authentication_and_authorization + urgency :low helpers ::API::Helpers::MembersHelpers @@ -100,8 +101,6 @@ module API end 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) diff --git a/lib/api/metrics/dashboard/annotations.rb b/lib/api/metrics/dashboard/annotations.rb index 0989340b3ea..c6406bf61df 100644 --- a/lib/api/metrics/dashboard/annotations.rb +++ b/lib/api/metrics/dashboard/annotations.rb @@ -12,7 +12,7 @@ module API ANNOTATIONS_SOURCES = [ { class: ::Environment, resource: :environments, create_service_param_key: :environment }, - { class: Clusters::Cluster, resource: :clusters, create_service_param_key: :cluster } + { class: ::Clusters::Cluster, resource: :clusters, create_service_param_key: :cluster } ].freeze ANNOTATIONS_SOURCES.each do |annotations_source| diff --git a/lib/api/namespaces.rb b/lib/api/namespaces.rb index d2468fb1c2e..1f3516e0667 100644 --- a/lib/api/namespaces.rb +++ b/lib/api/namespaces.rb @@ -6,8 +6,6 @@ module API before { authenticate! } - feature_category :subgroups - helpers do params :optional_list_params_ee do # EE::API::Namespaces would override this helper @@ -32,7 +30,7 @@ module API use :pagination use :optional_list_params_ee end - get do + get feature_category: :subgroups do owned_only = params[:owned_only] == true namespaces = current_user.admin ? Namespace.all : current_user.namespaces(owned_only: owned_only) @@ -54,7 +52,7 @@ module API params do requires :id, type: String, desc: "Namespace's ID or path" end - get ':id', requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do + get ':id', requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS, feature_category: :subgroups do user_namespace = find_namespace!(params[:id]) present user_namespace, with: Entities::Namespace, current_user: current_user @@ -67,7 +65,7 @@ module API requires :namespace, type: String, desc: "Namespace's path" optional :parent_id, type: Integer, desc: "The ID of the parent namespace. If no ID is specified, only top-level namespaces are considered." end - get ':namespace/exists', requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do + get ':namespace/exists', requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS, feature_category: :subgroups do namespace_path = params[:namespace] exists = Namespace.without_project_namespaces.by_parent(params[:parent_id]).filter_by_path(namespace_path).exists? diff --git a/lib/api/notes.rb b/lib/api/notes.rb index b260f5289b3..c12b3bf5562 100644 --- a/lib/api/notes.rb +++ b/lib/api/notes.rb @@ -112,7 +112,7 @@ module API requires :noteable_id, type: Integer, desc: 'The ID of the noteable' requires :note_id, type: Integer, desc: 'The ID of a note' optional :body, type: String, allow_blank: false, desc: 'The content of a note' - optional :confidential, type: Boolean, desc: 'Confidentiality note flag' + optional :confidential, type: Boolean, desc: '[Deprecated in 14.10] No longer allowed to update confidentiality of notes' end put ":id/#{noteables_str}/:noteable_id/notes/:note_id", feature_category: feature_category do noteable = find_noteable(noteable_type, params[:noteable_id]) diff --git a/lib/api/notification_settings.rb b/lib/api/notification_settings.rb index 7d28394e034..420eabb41db 100644 --- a/lib/api/notification_settings.rb +++ b/lib/api/notification_settings.rb @@ -5,7 +5,7 @@ module API class NotificationSettings < ::API::Base before { authenticate! } - feature_category :users + feature_category :team_planning helpers ::API::Helpers::MembersHelpers diff --git a/lib/api/project_events.rb b/lib/api/project_events.rb index 69b47f9420d..e8829216336 100644 --- a/lib/api/project_events.rb +++ b/lib/api/project_events.rb @@ -8,6 +8,9 @@ module API feature_category :users + # TODO: Set higher urgency after resolving https://gitlab.com/gitlab-org/gitlab/-/issues/357839 + urgency :low + params do requires :id, type: String, desc: 'The ID of a project' end diff --git a/lib/api/project_export.rb b/lib/api/project_export.rb index 843f72c0e1d..8b27d8d2163 100644 --- a/lib/api/project_export.rb +++ b/lib/api/project_export.rb @@ -25,7 +25,7 @@ module API detail 'This feature was introduced in GitLab 10.6.' end get ':id/export/download' do - check_rate_limit! :project_download_export, scope: [current_user, user_project] + check_rate_limit! :project_download_export, scope: [current_user, user_project.namespace] if user_project.export_file_exists? if user_project.export_archive_exists? diff --git a/lib/api/project_import.rb b/lib/api/project_import.rb index fae170d638b..bd8faefa803 100644 --- a/lib/api/project_import.rb +++ b/lib/api/project_import.rb @@ -135,8 +135,6 @@ module API success Entities::ProjectImportStatus end post 'remote-import' do - not_found! unless ::Feature.enabled?(:import_project_from_remote_file, default_enabled: :yaml) - check_rate_limit! :project_import, scope: [current_user, :project_import] response = ::Import::GitlabProjects::CreateProjectService.new( diff --git a/lib/api/project_snippets.rb b/lib/api/project_snippets.rb index a80e45637dc..14792730eae 100644 --- a/lib/api/project_snippets.rb +++ b/lib/api/project_snippets.rb @@ -38,7 +38,7 @@ module API params do use :pagination end - get ":id/snippets" do + get ":id/snippets", urgency: :low do authenticate! present paginate(snippets_for_current_user), with: Entities::ProjectSnippet, current_user: current_user diff --git a/lib/api/projects.rb b/lib/api/projects.rb index d772079372c..9f7b3f9b088 100644 --- a/lib/api/projects.rb +++ b/lib/api/projects.rb @@ -20,6 +20,7 @@ module API projects = projects.with_merge_requests_enabled if params[:with_merge_requests_enabled] projects = projects.with_statistics if params[:statistics] projects = projects.joins(:statistics) if params[:order_by].include?('project_statistics') # rubocop: disable CodeReuse/ActiveRecord + projects = projects.created_by(current_user).imported.with_import_state if params[:imported] lang = params[:with_programming_language] projects = projects.with_programming_language(lang) if lang @@ -125,6 +126,7 @@ module API optional :search_namespaces, type: Boolean, desc: "Include ancestor namespaces when matching search criteria" optional :owned, type: Boolean, default: false, desc: 'Limit by owned by authenticated user' optional :starred, type: Boolean, default: false, desc: 'Limit by starred status' + optional :imported, type: Boolean, default: false, desc: 'Limit by imported by authenticated user' optional :membership, type: Boolean, default: false, desc: 'Limit by projects that the current user is a member of' optional :with_issues_enabled, type: Boolean, default: false, desc: 'Limit by enabled issues feature' optional :with_merge_requests_enabled, type: Boolean, default: false, desc: 'Limit by enabled merge requests feature' @@ -212,7 +214,7 @@ module API use :statistics_params use :with_custom_attributes end - get ":user_id/projects", feature_category: :projects do + get ":user_id/projects", feature_category: :projects, urgency: :default do user = find_user(params[:user_id]) not_found!('User') unless user @@ -249,7 +251,8 @@ module API use :statistics_params use :with_custom_attributes end - get feature_category: :projects do + # TODO: Set higher urgency https://gitlab.com/gitlab-org/gitlab/-/issues/211495 + get feature_category: :projects, urgency: :low do present_projects load_projects end @@ -338,7 +341,8 @@ module API optional :license, type: Boolean, default: false, desc: 'Include project license data' end - get ":id", feature_category: :projects do + # TODO: Set higher urgency https://gitlab.com/gitlab-org/gitlab/-/issues/357622 + get ":id", feature_category: :projects, urgency: :default do options = { with: current_user ? Entities::ProjectWithAccess : Entities::BasicProjectDetails, current_user: current_user, @@ -609,7 +613,8 @@ module API params do requires :project_id, type: Integer, desc: 'The ID of the source project to import the members from.' end - post ":id/import_project_members/:project_id", feature_category: :experimentation_expansion do + post ":id/import_project_members/:project_id", feature_category: :projects do + ::Gitlab::QueryLimiting.disable!('https://gitlab.com/gitlab-org/gitlab/-/issues/355916') authorize! :admin_project, user_project source_project = Project.find_by_id(params[:project_id]) @@ -628,7 +633,7 @@ module API desc 'Workhorse authorize the file upload' do detail 'This feature was introduced in GitLab 13.11' end - post ':id/uploads/authorize', feature_category: :not_owned do + post ':id/uploads/authorize', feature_category: :not_owned do # rubocop:todo Gitlab/AvoidFeatureCategoryNotOwned require_gitlab_workhorse! status 200 @@ -640,7 +645,7 @@ module API params do requires :file, types: [Rack::Multipart::UploadedFile, ::API::Validations::Types::WorkhorseFile], desc: 'The attachment file to be uploaded' end - post ":id/uploads", feature_category: :not_owned do + post ":id/uploads", feature_category: :not_owned do # rubocop:todo Gitlab/AvoidFeatureCategoryNotOwned log_if_upload_exceed_max_size(user_project, params[:file]) service = UploadService.new(user_project, params[:file]) diff --git a/lib/api/projects_relation_builder.rb b/lib/api/projects_relation_builder.rb index aabecb43653..35f555e16b5 100644 --- a/lib/api/projects_relation_builder.rb +++ b/lib/api/projects_relation_builder.rb @@ -14,6 +14,7 @@ module API Preloaders::UserMaxAccessLevelInProjectsPreloader.new(projects_relation, options[:current_user]).execute if options[:current_user] Preloaders::SingleHierarchyProjectGroupPlansPreloader.new(projects_relation).execute if options[:single_hierarchy] + preload_groups(projects_relation) if options[:with] == Entities::Project projects_relation end @@ -40,6 +41,25 @@ module API def repositories_for_preload(projects_relation) projects_relation.map(&:repository) end + + # For all projects except those in a user namespace, the `namespace` + # and `group` are identical. Preload the group when it's not a user namespace. + def preload_groups(projects_relation) + return unless Feature.enabled?(:group_projects_api_preload_groups) + + group_projects = projects_for_group_preload(projects_relation) + groups = group_projects.map(&:namespace) + + Preloaders::GroupRootAncestorPreloader.new(groups).execute + + group_projects.each do |project| + project.group = project.namespace + end + end + + def projects_for_group_preload(projects_relation) + projects_relation.select { |project| project.namespace.type == Group.sti_name } + end end end end diff --git a/lib/api/releases.rb b/lib/api/releases.rb index 7b89a177fd9..9e085a91a7c 100644 --- a/lib/api/releases.rb +++ b/lib/api/releases.rb @@ -8,16 +8,48 @@ module API .merge(tag_name: API::NO_SLASH_URL_PART_REGEX) RELEASE_CLI_USER_AGENT = 'GitLab-release-cli' - before { authorize_read_releases! } + feature_category :release_orchestration - after { track_release_event } + params do + requires :id, type: String, desc: 'The ID of a group' + end + resource :groups, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do + before { authorize_read_group_releases! } - feature_category :release_orchestration + desc 'Get a list of releases for projects in this group.' do + success Entities::Release + end + params do + requires :id, type: Integer, desc: 'The ID of the group to get releases for' + optional :sort, type: String, values: %w[asc desc], default: 'desc', + desc: 'Return projects sorted in ascending and descending order by released_at' + optional :simple, type: Boolean, default: false, + desc: 'Return only the ID, URL, name, and path of each project' + + use :pagination + end + get ":id/releases" do + not_found! unless Feature.enabled?(:group_releases_finder_inoperator) + + finder_options = { + sort: params[:sort] + } + + strict_params = declared_params(include_missing: false) + releases = find_group_releases(finder_options) + + present_group_releases(strict_params, releases) + 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 { authorize_read_releases! } + + after { track_release_event } + desc 'Get a project releases' do detail 'This feature was introduced in GitLab 11.7.' named 'get_releases' @@ -162,6 +194,10 @@ module API end helpers do + def authorize_read_group_releases! + authorize! :read_release, user_group + end + def authorize_create_release! authorize! :create_release, user_project end @@ -220,6 +256,22 @@ module API Gitlab::Tracking.event(options[:for].name, options[:route_options][:named], project: user_project, user: current_user, **event_context) end + + def find_group_releases(finder_options) + ::Releases::GroupReleasesFinder + .new(user_group, current_user, finder_options) + .execute(preload: true) + end + + def present_group_releases(params, releases) + options = { + with: params[:simple] ? Entities::BasicReleaseDetails : Entities::Release, + current_user: current_user + } + + # GroupReleasesFinder has already ordered the data for us + present paginate(releases, skip_default_order: true), options + end end end end diff --git a/lib/api/remote_mirrors.rb b/lib/api/remote_mirrors.rb index 83096772d32..8de155312fb 100644 --- a/lib/api/remote_mirrors.rb +++ b/lib/api/remote_mirrors.rb @@ -25,6 +25,18 @@ module API with: Entities::RemoteMirror end + desc 'Get a single remote mirror' do + success Entities::RemoteMirror + end + params do + requires :mirror_id, type: String, desc: 'The ID of a remote mirror' + end + get ':id/remote_mirrors/:mirror_id' do + mirror = user_project.remote_mirrors.find(params[:mirror_id]) + + present mirror, with: Entities::RemoteMirror + end + desc 'Create remote mirror for a project' do success Entities::RemoteMirror end @@ -73,6 +85,29 @@ module API render_api_error!(result[:message], result[:http_status]) end end + + desc 'Delete a single remote mirror' do + detail 'This feature was introduced in GitLab 14.10' + end + params do + requires :mirror_id, type: String, desc: 'The ID of a remote mirror' + end + delete ':id/remote_mirrors/:mirror_id' do + mirror = user_project.remote_mirrors.find(params[:mirror_id]) + + destroy_conditionally!(mirror) do + mirror_params = declared_params(include_missing: false).merge(_destroy: 1) + mirror_params[:id] = mirror_params.delete(:mirror_id) + update_params = { remote_mirrors_attributes: mirror_params } + + # Note: We are using the update service to be consistent with how the controller handles deletion + result = ::Projects::UpdateService.new(user_project, current_user, update_params).execute + + if result[:status] != :success + render_api_error!(result[:message], 400) + end + end + end end end end diff --git a/lib/api/resource_access_tokens.rb b/lib/api/resource_access_tokens.rb index e52f8fd9111..2ba109b7092 100644 --- a/lib/api/resource_access_tokens.rb +++ b/lib/api/resource_access_tokens.rb @@ -27,6 +27,28 @@ module API present paginate(tokens), with: Entities::ResourceAccessToken, resource: resource end + desc 'Get an access token for the specified resource by ID' do + detail 'This feature was introduced in GitLab 14.10.' + end + params do + requires :id, type: String, desc: "The #{source_type} ID" + requires :token_id, type: String, desc: "The ID of the token" + end + get ":id/access_tokens/:token_id" do + resource = find_source(source_type, params[:id]) + + next unauthorized! unless current_user.can?(:read_resource_access_tokens, resource) + + token = find_token(resource, params[:token_id]) + + if token.nil? + next not_found!("Could not find #{source_type} access token with token_id: #{params[:token_id]}") + end + + resource.members.load + present token, with: Entities::ResourceAccessToken, resource: resource + end + desc 'Revoke a resource access token' do detail 'This feature was introduced in GitLab 13.9.' end diff --git a/lib/api/settings.rb b/lib/api/settings.rb index b256432fbf1..774ab472f2d 100644 --- a/lib/api/settings.rb +++ b/lib/api/settings.rb @@ -4,7 +4,7 @@ module API class Settings < ::API::Base before { authenticated_as_admin! } - feature_category :not_owned + feature_category :not_owned # rubocop:todo Gitlab/AvoidFeatureCategoryNotOwned helpers Helpers::SettingsHelpers @@ -83,7 +83,6 @@ module API optional :home_page_url, type: String, desc: 'We will redirect non-logged in users to this page' optional :housekeeping_enabled, type: Boolean, desc: 'Enable automatic repository housekeeping (git repack, git gc)' given housekeeping_enabled: ->(val) { val } do - requires :housekeeping_bitmaps_enabled, type: Boolean, desc: "Creating pack file bitmaps makes housekeeping take a little longer but bitmaps should accelerate 'git clone' performance." requires :housekeeping_full_repack_period, type: Integer, desc: "Number of Git pushes after which a full 'git repack' is run." requires :housekeeping_gc_period, type: Integer, desc: "Number of Git pushes after which 'git gc' is run." requires :housekeeping_incremental_repack_period, type: Integer, desc: "Number of Git pushes after which an incremental 'git repack' is run." @@ -182,7 +181,7 @@ module API optional :group_runner_token_expiration_interval, type: Integer, desc: 'Token expiration interval for group runners, in seconds' optional :project_runner_token_expiration_interval, type: Integer, desc: 'Token expiration interval for project runners, in seconds' - ApplicationSetting::SUPPORTED_KEY_TYPES.each do |type| + Gitlab::SSHPublicKey.supported_types.each do |type| optional :"#{type}_key_restriction", type: Integer, values: KeyRestrictionValidator.supported_key_restrictions(type), diff --git a/lib/api/sidekiq_metrics.rb b/lib/api/sidekiq_metrics.rb index 680363d036e..c30b9d7583a 100644 --- a/lib/api/sidekiq_metrics.rb +++ b/lib/api/sidekiq_metrics.rb @@ -6,7 +6,7 @@ module API class SidekiqMetrics < ::API::Base before { authenticated_as_admin! } - feature_category :not_owned + feature_category :not_owned # rubocop:todo Gitlab/AvoidFeatureCategoryNotOwned helpers do def queue_metrics diff --git a/lib/api/snippets.rb b/lib/api/snippets.rb index 9a3c68bc854..496532a15b2 100644 --- a/lib/api/snippets.rb +++ b/lib/api/snippets.rb @@ -184,7 +184,7 @@ module API params do use :raw_file_params end - get ":id/files/:ref/:file_path/raw", requirements: { file_path: API::NO_SLASH_URL_PART_REGEX } do + get ":id/files/:ref/:file_path/raw", urgency: :low, requirements: { file_path: API::NO_SLASH_URL_PART_REGEX } do snippet = snippets.find_by_id(params.delete(:id)) not_found!('Snippet') unless snippet&.repo_exists? @@ -200,7 +200,7 @@ module API get ":id/user_agent_detail" do authenticated_as_admin! - snippet = Snippet.find_by_id!(params[:id]) + snippet = Snippet.find(params[:id]) break not_found!('UserAgentDetail') unless snippet.user_agent_detail diff --git a/lib/api/users.rb b/lib/api/users.rb index 0f710e0a307..b26611cfe03 100644 --- a/lib/api/users.rb +++ b/lib/api/users.rb @@ -89,6 +89,7 @@ module API optional :created_before, type: DateTime, desc: 'Return users created before the specified time' optional :without_projects, type: Boolean, default: false, desc: 'Filters only users without projects' optional :exclude_internal, as: :non_internal, type: Boolean, default: false, desc: 'Filters only non internal users' + optional :without_project_bots, type: Boolean, default: false, desc: 'Filters users without project bots' optional :admins, type: Boolean, default: false, desc: 'Filters only admin users' all_or_none_of :extern_uid, :provider @@ -98,7 +99,7 @@ module API use :optional_index_params_ee end # rubocop: disable CodeReuse/ActiveRecord - get feature_category: :users do + get feature_category: :users, urgency: :default do authenticated_as_admin! if params[:extern_uid].present? && params[:provider].present? unless current_user&.admin? @@ -120,8 +121,11 @@ module API users = reorder_users(users) entity = current_user&.admin? ? Entities::UserWithAdmin : Entities::UserBasic - users = users.preload(:identities, :u2f_registrations) if entity == Entities::UserWithAdmin - users = users.preload(:identities, :webauthn_registrations) if entity == Entities::UserWithAdmin + + if entity == Entities::UserWithAdmin + users = users.preload(:identities, :u2f_registrations, :webauthn_registrations, :namespace) + end + users, options = with_custom_attributes(users, { with: entity, current_user: current_user }) users = users.preload(:user_detail) @@ -139,7 +143,7 @@ module API use :with_custom_attributes end # rubocop: disable CodeReuse/ActiveRecord - get ":id", feature_category: :users do + get ":id", feature_category: :users, urgency: :medium do forbidden!('Not authorized!') unless current_user unless current_user.admin? @@ -164,7 +168,7 @@ module API params do requires :user_id, type: String, desc: 'The ID or username of the user' end - get ":user_id/status", requirements: API::USER_REQUIREMENTS, feature_category: :users do + get ":user_id/status", requirements: API::USER_REQUIREMENTS, feature_category: :users, urgency: :high do user = find_user(params[:user_id]) not_found!('User') unless user && can?(current_user, :read_user, user) @@ -915,7 +919,7 @@ module API desc 'Get the currently authenticated user' do success Entities::UserPublic end - get feature_category: :users do + get feature_category: :users, urgency: :medium do entity = if current_user.admin? Entities::UserWithAdmin @@ -1090,7 +1094,7 @@ module API requires :credit_card_mask_number, type: String, desc: 'The last 4 digits of credit card number' requires :credit_card_type, type: String, desc: 'The credit card network name' end - put ":user_id/credit_card_validation", feature_category: :users do + put ":user_id/credit_card_validation", feature_category: :purchase do authenticated_as_admin! user = find_user(params[:user_id]) diff --git a/lib/api/validations/validators/limit.rb b/lib/api/validations/validators/limit.rb index e8f894849a5..7e11f1d77cc 100644 --- a/lib/api/validations/validators/limit.rb +++ b/lib/api/validations/validators/limit.rb @@ -7,7 +7,7 @@ module API def validate_param!(attr_name, params) value = params[attr_name] - return if value.size <= @option + return if value.nil? || value.size <= @option raise Grape::Exceptions::Validation.new( params: [@scope.full_name(attr_name)], diff --git a/lib/api/version.rb b/lib/api/version.rb index 86eb34ca589..bdce88ab827 100644 --- a/lib/api/version.rb +++ b/lib/api/version.rb @@ -9,7 +9,7 @@ module API before { authenticate! } - feature_category :not_owned + feature_category :not_owned # rubocop:todo Gitlab/AvoidFeatureCategoryNotOwned METADATA_QUERY = <<~EOF { diff --git a/lib/api/wikis.rb b/lib/api/wikis.rb index e90d88940a5..12dbf4792d6 100644 --- a/lib/api/wikis.rb +++ b/lib/api/wikis.rb @@ -12,7 +12,7 @@ module API params :common_wiki_page_params do optional :format, type: String, - values: Wiki::MARKUPS.values.map(&:to_s), + values: Wiki::VALID_USER_MARKUPS.keys.map(&:to_s), default: 'markdown', desc: 'Format of a wiki page. Available formats are markdown, rdoc, asciidoc and org' end @@ -48,7 +48,7 @@ module API optional :version, type: String, desc: 'The version hash of a wiki page' optional :render_html, type: Boolean, default: false, desc: 'Render content to HTML' end - get ':id/wikis/:slug' do + get ':id/wikis/:slug', urgency: :low do authorize! :read_wiki, container present wiki_page(params[:version]), with: Entities::WikiPage, render_html: params[:render_html] @@ -136,7 +136,7 @@ module API if result[:status] == :success status(201) - present OpenStruct.new(result[:result]), with: Entities::WikiAttachment + present result[:result], with: Entities::WikiAttachment else render_api_error!(result[:message], 400) end |