diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2022-06-20 11:10:13 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2022-06-20 11:10:13 +0000 |
commit | 0ea3fcec397b69815975647f5e2aa5fe944a8486 (patch) | |
tree | 7979381b89d26011bcf9bdc989a40fcc2f1ed4ff /lib/api | |
parent | 72123183a20411a36d607d70b12d57c484394c8e (diff) | |
download | gitlab-ce-0ea3fcec397b69815975647f5e2aa5fe944a8486.tar.gz |
Add latest changes from gitlab-org/gitlab@15-1-stable-eev15.1.0-rc42
Diffstat (limited to 'lib/api')
52 files changed, 470 insertions, 139 deletions
diff --git a/lib/api/api.rb b/lib/api/api.rb index 0d74bc841b1..8cafde4fedb 100644 --- a/lib/api/api.rb +++ b/lib/api/api.rb @@ -131,7 +131,7 @@ module API # This is a specific exception raised by `rack-timeout` gem when Puma # requests surpass its timeout. Given it inherits from Exception, we # should rescue it separately. For more info, see: - # - https://github.com/sharpstone/rack-timeout/blob/master/doc/exceptions.md + # - https://github.com/zombocom/rack-timeout/blob/master/doc/exceptions.md # - https://github.com/ruby-grape/grape#exception-handling rescue_from Rack::Timeout::RequestTimeoutException do |exception| handle_api_exception(exception) @@ -229,6 +229,7 @@ module API mount ::API::ImportGithub mount ::API::Integrations mount ::API::Integrations::JiraConnect::Subscriptions + mount ::API::Integrations::Slack::Events mount ::API::Invitations mount ::API::IssueLinks mount ::API::Issues @@ -314,6 +315,7 @@ module API mount ::API::Internal::Kubernetes mount ::API::Internal::MailRoom mount ::API::Internal::ContainerRegistry::Migration + mount ::API::Internal::Workhorse version 'v3', using: :path do # Although the following endpoints are kept behind V3 namespace, diff --git a/lib/api/appearance.rb b/lib/api/appearance.rb index 1eaa4167a7d..e599abf4aaf 100644 --- a/lib/api/appearance.rb +++ b/lib/api/appearance.rb @@ -5,6 +5,7 @@ module API before { authenticated_as_admin! } feature_category :navigation + urgency :low helpers do def current_appearance diff --git a/lib/api/avatar.rb b/lib/api/avatar.rb index bd9fb37e18b..0fb7a4cd435 100644 --- a/lib/api/avatar.rb +++ b/lib/api/avatar.rb @@ -3,7 +3,7 @@ module API class Avatar < ::API::Base feature_category :users - urgency :high + urgency :medium resource :avatar do desc 'Return avatar url for a user' do diff --git a/lib/api/badges.rb b/lib/api/badges.rb index 68095fb2975..f969eec8431 100644 --- a/lib/api/badges.rb +++ b/lib/api/badges.rb @@ -32,7 +32,7 @@ module API params do use :pagination end - get ":id/badges", urgency: :default do + get ":id/badges", urgency: :low do source = find_source(source_type, params[:id]) badges = source.badges @@ -91,7 +91,7 @@ module API requires :image_url, type: String, desc: 'URL of the badge image' optional :name, type: String, desc: 'Name for the badge' end - post ":id/badges", urgency: :default do + post ":id/badges" do source = find_source_if_admin(source_type) badge = ::Badges::CreateService.new(declared_params(include_missing: false)).execute(source) diff --git a/lib/api/broadcast_messages.rb b/lib/api/broadcast_messages.rb index e081265b418..b5d68ca5de2 100644 --- a/lib/api/broadcast_messages.rb +++ b/lib/api/broadcast_messages.rb @@ -5,6 +5,7 @@ module API include PaginationParams feature_category :navigation + urgency :low resource :broadcast_messages do helpers do diff --git a/lib/api/bulk_imports.rb b/lib/api/bulk_imports.rb index 766e05eca23..b1cb84c97cb 100644 --- a/lib/api/bulk_imports.rb +++ b/lib/api/bulk_imports.rb @@ -47,7 +47,7 @@ module API requires :source_type, type: String, desc: 'Source entity type (only `group_entity` is supported)', values: %w[group_entity] requires :source_full_path, type: String, desc: 'Source full path of the entity to import' - requires :destination_name, type: String, desc: 'Destination name for the entity' + requires :destination_name, type: String, desc: 'Destination slug for the entity' requires :destination_namespace, type: String, desc: 'Destination namespace for the entity' end end diff --git a/lib/api/ci/job_artifacts.rb b/lib/api/ci/job_artifacts.rb index 0800993602b..8b332f96be0 100644 --- a/lib/api/ci/job_artifacts.rb +++ b/lib/api/ci/job_artifacts.rb @@ -3,6 +3,8 @@ module API module Ci class JobArtifacts < ::API::Base + helpers ::API::Helpers::ProjectStatsRefreshConflictsHelpers + before { authenticate_non_get! } feature_category :build_artifacts @@ -35,7 +37,7 @@ module API latest_build = user_project.latest_successful_build_for_ref!(params[:job], params[:ref_name]) authorize_read_job_artifacts!(latest_build) - present_carrierwave_file!(latest_build.artifacts_file) + present_artifacts_file!(latest_build.artifacts_file) end desc 'Download a specific file from artifacts archive from a ref' do @@ -76,7 +78,7 @@ module API build = find_build!(params[:job_id]) authorize_read_job_artifacts!(build) - present_carrierwave_file!(build.artifacts_file) + present_artifacts_file!(build.artifacts_file) end desc 'Download a specific file from artifacts archive' do @@ -137,6 +139,8 @@ module API build = find_build!(params[:job_id]) authorize!(:destroy_artifacts, build) + reject_if_build_artifacts_size_refreshing!(build.project) + build.erase_erasable_artifacts! status :no_content @@ -146,6 +150,8 @@ module API delete ':id/artifacts' do authorize_destroy_artifacts! + reject_if_build_artifacts_size_refreshing!(user_project) + ::Ci::JobArtifacts::DeleteProjectArtifactsService.new(project: user_project).execute accepted! diff --git a/lib/api/ci/jobs.rb b/lib/api/ci/jobs.rb index 04999b5fb44..97471d3c96e 100644 --- a/lib/api/ci/jobs.rb +++ b/lib/api/ci/jobs.rb @@ -4,6 +4,9 @@ module API module Ci class Jobs < ::API::Base include PaginationParams + + helpers ::API::Helpers::ProjectStatsRefreshConflictsHelpers + before { authenticate! } resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do @@ -137,6 +140,8 @@ module API authorize!(:erase_build, build) break forbidden!('Job is not erasable!') unless build.erasable? + reject_if_build_artifacts_size_refreshing!(build.project) + build.erase(erased_by: current_user) present build, with: Entities::Ci::Job end @@ -204,7 +209,7 @@ module API .select { |_role, role_access_level| role_access_level <= user_access_level } .map(&:first) - environment = if environment_slug = current_authenticated_job.deployment&.environment&.slug + environment = if environment_slug = current_authenticated_job.persisted_environment&.slug { slug: environment_slug } end diff --git a/lib/api/ci/pipelines.rb b/lib/api/ci/pipelines.rb index 4253a9eb4d7..cd686a28dd2 100644 --- a/lib/api/ci/pipelines.rb +++ b/lib/api/ci/pipelines.rb @@ -5,6 +5,8 @@ module API class Pipelines < ::API::Base include PaginationParams + helpers ::API::Helpers::ProjectStatsRefreshConflictsHelpers + before { authenticate_non_get! } params do @@ -208,6 +210,8 @@ module API delete ':id/pipelines/:pipeline_id', urgency: :low, feature_category: :continuous_integration do authorize! :destroy_pipeline, pipeline + reject_if_build_artifacts_size_refreshing!(pipeline.project) + destroy_conditionally!(pipeline) do ::Ci::DestroyPipelineService.new(user_project, current_user).execute(pipeline) end diff --git a/lib/api/ci/runner.rb b/lib/api/ci/runner.rb index 4381309fb9e..65dc002e67d 100644 --- a/lib/api/ci/runner.rb +++ b/lib/api/ci/runner.rb @@ -330,7 +330,7 @@ module API authenticate_job!(require_running: false) end - present_carrierwave_file!(current_job.artifacts_file, supports_direct_download: params[:direct_download]) + present_artifacts_file!(current_job.artifacts_file, supports_direct_download: params[:direct_download]) end end end diff --git a/lib/api/ci/secure_files.rb b/lib/api/ci/secure_files.rb index 6c7f502b428..c1f47dd67ce 100644 --- a/lib/api/ci/secure_files.rb +++ b/lib/api/ci/secure_files.rb @@ -26,7 +26,7 @@ module API end route_setting :authentication, basic_auth_personal_access_token: true, job_token_allowed: true get ':id/secure_files' do - secure_files = user_project.secure_files + secure_files = user_project.secure_files.order_by_created_at present paginate(secure_files), with: Entities::Ci::SecureFile end diff --git a/lib/api/clusters/agent_tokens.rb b/lib/api/clusters/agent_tokens.rb index 1e52790f26b..1f9c8700d7a 100644 --- a/lib/api/clusters/agent_tokens.rb +++ b/lib/api/clusters/agent_tokens.rb @@ -26,9 +26,7 @@ module API use :pagination end get do - authorize! :read_cluster, user_project - - agent = user_project.cluster_agents.find(params[:agent_id]) + agent = ::Clusters::AgentsFinder.new(user_project, current_user).find(params[:agent_id]) present paginate(agent.agent_tokens), with: Entities::Clusters::AgentTokenBasic end @@ -41,9 +39,8 @@ module API requires :token_id, type: Integer, desc: 'The ID of the agent token' end get ':token_id' do - authorize! :read_cluster, user_project + agent = ::Clusters::AgentsFinder.new(user_project, current_user).find(params[:agent_id]) - agent = user_project.cluster_agents.find(params[:agent_id]) token = agent.agent_tokens.find(params[:token_id]) present token, with: Entities::Clusters::AgentToken @@ -62,7 +59,7 @@ module API token_params = declared_params(include_missing: false) - agent = user_project.cluster_agents.find(params[:agent_id]) + agent = ::Clusters::AgentsFinder.new(user_project, current_user).find(params[:agent_id]) result = ::Clusters::AgentTokens::CreateService.new( container: agent.project, current_user: current_user, params: token_params.merge(agent_id: agent.id) @@ -82,7 +79,8 @@ module API delete ':token_id' do authorize! :admin_cluster, user_project - agent = user_project.cluster_agents.find(params[:agent_id]) + agent = ::Clusters::AgentsFinder.new(user_project, current_user).find(params[:agent_id]) + token = agent.agent_tokens.find(params[:token_id]) # Skipping explicit error handling and relying on exceptions diff --git a/lib/api/clusters/agents.rb b/lib/api/clusters/agents.rb index 0fa556d2da9..2affd9680b6 100644 --- a/lib/api/clusters/agents.rb +++ b/lib/api/clusters/agents.rb @@ -22,7 +22,7 @@ module API use :pagination end get ':id/cluster_agents' do - authorize! :read_cluster, user_project + not_found!('ClusterAgents') unless can?(current_user, :read_cluster, user_project) agents = ::Clusters::AgentsFinder.new(user_project, current_user).execute @@ -37,9 +37,7 @@ module API 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]) + agent = ::Clusters::AgentsFinder.new(user_project, current_user).find(params[:agent_id]) present agent, with: Entities::Clusters::Agent end @@ -72,7 +70,7 @@ module API delete ':id/cluster_agents/:agent_id' do authorize! :admin_cluster, user_project - agent = user_project.cluster_agents.find(params.delete(:agent_id)) + agent = ::Clusters::AgentsFinder.new(user_project, current_user).find(params[:agent_id]) destroy_conditionally!(agent) end diff --git a/lib/api/entities/ci/job_request/image.rb b/lib/api/entities/ci/job_request/image.rb index 8e404a8fa02..83f64da6050 100644 --- a/lib/api/entities/ci/job_request/image.rb +++ b/lib/api/entities/ci/job_request/image.rb @@ -7,6 +7,8 @@ module API class Image < Grape::Entity expose :name, :entrypoint expose :ports, using: Entities::Ci::JobRequest::Port + + expose :pull_policy, if: ->(_) { ::Feature.enabled?(:ci_docker_image_pull_policy) } end end end diff --git a/lib/api/entities/ci/job_request/service.rb b/lib/api/entities/ci/job_request/service.rb index 0dae5d5a933..d9da2c92ec7 100644 --- a/lib/api/entities/ci/job_request/service.rb +++ b/lib/api/entities/ci/job_request/service.rb @@ -4,7 +4,10 @@ module API module Entities module Ci module JobRequest - class Service < Entities::Ci::JobRequest::Image + class Service < Grape::Entity + expose :name, :entrypoint + expose :ports, using: Entities::Ci::JobRequest::Port + expose :alias, :command expose :variables end diff --git a/lib/api/entities/hook.rb b/lib/api/entities/hook.rb index ac813bcac3f..d176e76b321 100644 --- a/lib/api/entities/hook.rb +++ b/lib/api/entities/hook.rb @@ -5,6 +5,9 @@ module API class Hook < Grape::Entity expose :id, :url, :created_at, :push_events, :tag_push_events, :merge_requests_events, :repository_update_events expose :enable_ssl_verification + + expose :alert_status + expose :disabled_until end end end diff --git a/lib/api/entities/issue.rb b/lib/api/entities/issue.rb index f87ef093cd8..1060b2c517a 100644 --- a/lib/api/entities/issue.rb +++ b/lib/api/entities/issue.rb @@ -29,6 +29,16 @@ module API expose :project do |issue| expose_url(api_v4_projects_path(id: issue.project_id)) end + + expose :closed_as_duplicate_of do |issue| + if ::Feature.enabled?(:closed_as_duplicate_of_issues_api, issue.project) && + issue.duplicated? && + options[:current_user]&.can?(:read_issue, issue.duplicated_to) + expose_url( + api_v4_project_issue_path(id: issue.duplicated_to.project_id, issue_iid: issue.duplicated_to.iid) + ) + end + end end expose :references, with: IssuableReferences do |issue| diff --git a/lib/api/entities/personal_access_token_with_details.rb b/lib/api/entities/personal_access_token_with_details.rb new file mode 100644 index 00000000000..5654bd4a1e1 --- /dev/null +++ b/lib/api/entities/personal_access_token_with_details.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +module API + module Entities + class PersonalAccessTokenWithDetails < Entities::PersonalAccessToken + expose :expired?, as: :expired + expose :expires_soon?, as: :expires_soon + expose :revoke_path do |token| + Gitlab::Routing.url_helpers.revoke_profile_personal_access_token_path(token) + end + end + end +end diff --git a/lib/api/entities/releases/link.rb b/lib/api/entities/releases/link.rb index c1d83a8924f..5157645af69 100644 --- a/lib/api/entities/releases/link.rb +++ b/lib/api/entities/releases/link.rb @@ -7,16 +7,11 @@ module API expose :id expose :name expose :url - expose :direct_asset_url + expose :direct_asset_url do |link| + ::Releases::LinkPresenter.new(link).direct_asset_url + end expose :external?, as: :external expose :link_type - - def direct_asset_url - return object.url unless object.filepath - - release = object.release.present - release.download_url(object.filepath) - end end end end diff --git a/lib/api/entities/wiki_page.rb b/lib/api/entities/wiki_page.rb index 43af6a336d2..5bba4271396 100644 --- a/lib/api/entities/wiki_page.rb +++ b/lib/api/entities/wiki_page.rb @@ -6,7 +6,15 @@ module API include ::MarkupHelper expose :content do |wiki_page, options| - options[:render_html] ? render_wiki_content(wiki_page, ref: wiki_page.version.id) : wiki_page.content + if options[:render_html] + render_wiki_content( + wiki_page, + ref: wiki_page.version.id, + current_user: options[:current_user] + ) + else + wiki_page.content + end end expose :encoding do |wiki_page| diff --git a/lib/api/environments.rb b/lib/api/environments.rb index 11f1cab0c72..c4b67f83941 100644 --- a/lib/api/environments.rb +++ b/lib/api/environments.rb @@ -22,16 +22,15 @@ module API use :pagination optional :name, type: String, desc: 'Returns the environment with this name' optional :search, type: String, desc: 'Returns list of environments matching the search criteria' + optional :states, type: String, values: Environment.valid_states.map(&:to_s), desc: 'List all environments that match a specific state' mutually_exclusive :name, :search, message: 'cannot be used together' end get ':id/environments' do authorize! :read_environment, user_project - environments = ::Environments::EnvironmentsFinder.new(user_project, current_user, params).execute + environments = ::Environments::EnvironmentsFinder.new(user_project, current_user, declared_params(include_missing: false)).execute present paginate(environments), with: Entities::Environment, current_user: current_user - rescue ::Environments::EnvironmentsFinder::InvalidStatesError => exception - bad_request!(exception.message) end desc 'Creates a new environment' do @@ -129,14 +128,14 @@ module API end params do requires :environment_id, type: Integer, desc: 'The environment ID' + optional :force, type: Boolean, default: false end post ':id/environments/:environment_id/stop' do authorize! :read_environment, user_project environment = user_project.environments.find(params[:environment_id]) - authorize! :stop_environment, environment - - environment.stop_with_actions!(current_user) + ::Environments::StopService.new(user_project, current_user, declared_params(include_missing: false)) + .execute(environment) status 200 present environment, with: Entities::Environment, current_user: current_user diff --git a/lib/api/error_tracking/client_keys.rb b/lib/api/error_tracking/client_keys.rb index d92cf220433..c1c378111a7 100644 --- a/lib/api/error_tracking/client_keys.rb +++ b/lib/api/error_tracking/client_keys.rb @@ -5,6 +5,7 @@ module API before { authenticate! } feature_category :error_tracking + urgency :low params do requires :id, type: String, desc: 'The ID of a project' diff --git a/lib/api/error_tracking/collector.rb b/lib/api/error_tracking/collector.rb index 29b213eaffb..eea0fd2bce9 100644 --- a/lib/api/error_tracking/collector.rb +++ b/lib/api/error_tracking/collector.rb @@ -6,6 +6,7 @@ module API # sentry backend. For more details see https://gitlab.com/gitlab-org/gitlab/-/issues/329596. class ErrorTracking::Collector < ::API::Base feature_category :error_tracking + urgency :low content_type :envelope, 'application/x-sentry-envelope' content_type :json, 'application/json' diff --git a/lib/api/error_tracking/project_settings.rb b/lib/api/error_tracking/project_settings.rb index 74432d1eaec..fefc2098137 100644 --- a/lib/api/error_tracking/project_settings.rb +++ b/lib/api/error_tracking/project_settings.rb @@ -5,6 +5,7 @@ module API before { authenticate! } feature_category :error_tracking + urgency :low helpers do def project_setting diff --git a/lib/api/features.rb b/lib/api/features.rb index bff2817a2ec..13a6aedc2df 100644 --- a/lib/api/features.rb +++ b/lib/api/features.rb @@ -68,10 +68,13 @@ module API requires :value, type: String, desc: '`true` or `false` to enable/disable, a float for percentage of time' optional :key, type: String, desc: '`percentage_of_actors` or the default `percentage_of_time`' optional :feature_group, type: String, desc: 'A Feature group name' - optional :user, type: String, desc: 'A GitLab username' - optional :group, type: String, desc: "A GitLab group's path, such as 'gitlab-org'" - optional :namespace, type: String, desc: "A GitLab group or user namespace path, such as 'gitlab-org'" - optional :project, type: String, desc: 'A projects path, like gitlab-org/gitlab-ce' + optional :user, type: String, desc: 'A GitLab username or comma-separated multiple usernames' + optional :group, type: String, + desc: "A GitLab group's path, such as 'gitlab-org', or comma-separated multiple group paths" + optional :namespace, type: String, + desc: "A GitLab group or user namespace path, such as 'john-doe', or comma-separated multiple namespace paths" + optional :project, type: String, + desc: "A projects path, such as `gitlab-org/gitlab-ce`, or comma-separated multiple project paths" optional :force, type: Boolean, desc: 'Skip feature flag validation checks, ie. YAML definition' mutually_exclusive :key, :feature_group @@ -110,6 +113,8 @@ module API present Feature.get(params[:name]), # rubocop:disable Gitlab/AvoidFeatureGet with: Entities::Feature, current_user: current_user + rescue Feature::Target::UnknowTargetError => e + bad_request!(e.message) end desc 'Remove the gate value for the given feature' diff --git a/lib/api/groups.rb b/lib/api/groups.rb index 60bb51bf48f..c17bc432404 100644 --- a/lib/api/groups.rb +++ b/lib/api/groups.rb @@ -417,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", feature_category: :subgroups do + post ":id/share", feature_category: :subgroups, urgency: :low do shared_with_group = find_group!(params[:group_id]) group_link_create_params = { diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb index a079c591519..fc1037131d8 100644 --- a/lib/api/helpers.rb +++ b/lib/api/helpers.rb @@ -394,8 +394,7 @@ module API end def order_options_with_tie_breaker - order_by = if Feature.enabled?(:replace_order_by_created_at_with_id) && - params[:order_by] == 'created_at' + order_by = if params[:order_by] == 'created_at' 'id' else params[:order_by] @@ -409,15 +408,11 @@ module API # error helpers def forbidden!(reason = nil) - message = ['403 Forbidden'] - message << "- #{reason}" if reason - render_api_error!(message.join(' '), 403) + render_api_error_with_reason!(403, '403 Forbidden', reason) end def bad_request!(reason = nil) - message = ['400 Bad request'] - message << "- #{reason}" if reason - render_api_error!(message.join(' '), 400) + render_api_error_with_reason!(400, '400 Bad request', reason) end def bad_request_missing_attribute!(attribute) @@ -437,8 +432,8 @@ module API end end - def unauthorized! - render_api_error!('401 Unauthorized', 401) + def unauthorized!(reason = nil) + render_api_error_with_reason!(401, '401 Unauthorized', reason) end def not_allowed!(message = nil) @@ -491,6 +486,12 @@ module API model.errors.messages end + def render_api_error_with_reason!(status, message, reason) + message = [message] + message << "- #{reason}" if reason + render_api_error!(message.join(' '), status) + end + def render_api_error!(message, status) render_structured_api_error!({ 'message' => message }, status) end @@ -569,11 +570,19 @@ module API end end + def log_artifact_size(file) + Gitlab::ApplicationContext.push(artifact: file.model) + end + + def present_artifacts_file!(file, **args) + log_artifact_size(file) if file + + present_carrierwave_file!(file, **args) + end + def present_carrierwave_file!(file, supports_direct_download: true) return not_found! unless file&.exists? - log_artifact_size(file) if file.is_a?(JobArtifactUploader) - if file.file_storage? present_disk_file!(file.path, file.filename) elsif supports_direct_download && file.class.direct_download_enabled? @@ -724,7 +733,6 @@ module API # Deprecated. Use `send_artifacts_entry` instead. def legacy_send_artifacts_entry(file, entry) header(*Gitlab::Workhorse.send_artifacts_entry(file, entry)) - log_artifact_size(file) body '' end @@ -732,15 +740,10 @@ module API def send_artifacts_entry(file, entry) header(*Gitlab::Workhorse.send_artifacts_entry(file, entry)) header(*Gitlab::Workhorse.detect_content_type) - log_artifact_size(file) body '' end - def log_artifact_size(file) - Gitlab::ApplicationContext.push(artifact: file.model) - end - # The Grape Error Middleware only has access to `env` but not `params` nor # `request`. We workaround this by defining methods that returns the right # values. diff --git a/lib/api/helpers/project_stats_refresh_conflicts_helpers.rb b/lib/api/helpers/project_stats_refresh_conflicts_helpers.rb new file mode 100644 index 00000000000..db464521033 --- /dev/null +++ b/lib/api/helpers/project_stats_refresh_conflicts_helpers.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +module API + module Helpers + module ProjectStatsRefreshConflictsHelpers + def reject_if_build_artifacts_size_refreshing!(project) + return unless project.refreshing_build_artifacts_size? + + Gitlab::ProjectStatsRefreshConflictsLogger.warn_request_rejected_during_stats_refresh(project.id) + + conflict!('Action temporarily disabled. The project this pipeline belongs to is undergoing stats refresh.') + end + end + end +end diff --git a/lib/api/helpers/projects_helpers.rb b/lib/api/helpers/projects_helpers.rb index 7a9dd78e4ed..52cb398d6bf 100644 --- a/lib/api/helpers/projects_helpers.rb +++ b/lib/api/helpers/projects_helpers.rb @@ -169,7 +169,6 @@ module API :merge_commit_template, :squash_commit_template, :repository_storage, - :compliance_framework_setting, :packages_enabled, :service_desk_enabled, :keep_latest_artifact, diff --git a/lib/api/helpers/sse_helpers.rb b/lib/api/helpers/sse_helpers.rb deleted file mode 100644 index c354694f508..00000000000 --- a/lib/api/helpers/sse_helpers.rb +++ /dev/null @@ -1,16 +0,0 @@ -# frozen_string_literal: true - -module API - module Helpers - module SSEHelpers - def request_from_sse?(project) - return false if request.referer.blank? - - uri = URI.parse(request.referer) - uri.path.starts_with?(::Gitlab::Routing.url_helpers.project_root_sse_path(project)) - rescue URI::InvalidURIError - false - end - end - end -end diff --git a/lib/api/integrations/jira_connect/subscriptions.rb b/lib/api/integrations/jira_connect/subscriptions.rb index fa19dc2be3f..a6e931ba7bb 100644 --- a/lib/api/integrations/jira_connect/subscriptions.rb +++ b/lib/api/integrations/jira_connect/subscriptions.rb @@ -23,7 +23,7 @@ module API installation = JiraConnectInstallation.find_by_client_key(jwt.iss_claim) if !installation || !jwt.valid?(installation.shared_secret) || !jwt.verify_context_qsh_claim - unauthorized! + unauthorized!('JWT authentication failed') end jira_user = installation.client.user_info(jwt.sub_claim) diff --git a/lib/api/integrations/slack/events.rb b/lib/api/integrations/slack/events.rb new file mode 100644 index 00000000000..6227b75a9d7 --- /dev/null +++ b/lib/api/integrations/slack/events.rb @@ -0,0 +1,40 @@ +# frozen_string_literal: true + +# This API endpoint handles all events sent from Slack once a Slack +# workspace has installed the GitLab Slack app. +# +# See https://api.slack.com/apis/connections/events-api. +module API + class Integrations + module Slack + class Events < ::API::Base + feature_category :integrations + + before { verify_slack_request! } + + helpers do + def verify_slack_request! + unauthorized! unless Request.verify!(request) + end + end + + namespace 'integrations/slack' do + post :events do + type = params['type'] + raise ArgumentError, "Unable to handle event type: '#{type}'" unless type == 'url_verification' + + status :ok + UrlVerification.call(params) + rescue ArgumentError => e + # Track the error, but respond with a `2xx` because we don't want to risk + # Slack rate-limiting, or disabling our app, due to error responses. + # See https://api.slack.com/apis/connections/events-api. + Gitlab::ErrorTracking.track_exception(e) + + no_content! + end + end + end + end + end +end diff --git a/lib/api/integrations/slack/events/url_verification.rb b/lib/api/integrations/slack/events/url_verification.rb new file mode 100644 index 00000000000..4628b93665d --- /dev/null +++ b/lib/api/integrations/slack/events/url_verification.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +module API + class Integrations + module Slack + class Events + class UrlVerification + # When the GitLab Slack app is first configured to receive Slack events, + # Slack will issue a special request to the endpoint and expect it to respond + # with the `challenge` param. + # + # This must be done in-request, rather than on a queue. + # + # See https://api.slack.com/apis/connections/events-api. + def self.call(params) + { challenge: params[:challenge] } + end + end + end + end + end +end diff --git a/lib/api/integrations/slack/request.rb b/lib/api/integrations/slack/request.rb new file mode 100644 index 00000000000..df0109b07aa --- /dev/null +++ b/lib/api/integrations/slack/request.rb @@ -0,0 +1,51 @@ +# frozen_string_literal: true + +module API + class Integrations + module Slack + module Request + VERIFICATION_VERSION = 'v0' + VERIFICATION_TIMESTAMP_HEADER = 'X-Slack-Request-Timestamp' + VERIFICATION_SIGNATURE_HEADER = 'X-Slack-Signature' + VERIFICATION_DELIMITER = ':' + VERIFICATION_HMAC_ALGORITHM = 'sha256' + VERIFICATION_TIMESTAMP_EXPIRY = 1.minute.to_i + + # Verify the request by comparing the given request signature in the header + # with a signature value that we compute according to the steps in: + # https://api.slack.com/authentication/verifying-requests-from-slack. + def self.verify!(request) + return false unless Gitlab::CurrentSettings.slack_app_signing_secret + + timestamp, signature = request.headers.values_at( + VERIFICATION_TIMESTAMP_HEADER, + VERIFICATION_SIGNATURE_HEADER + ) + + return false if timestamp.nil? || signature.nil? + return false if Time.current.to_i - timestamp.to_i >= VERIFICATION_TIMESTAMP_EXPIRY + + request.body.rewind + + basestring = [ + VERIFICATION_VERSION, + timestamp, + request.body.read + ].join(VERIFICATION_DELIMITER) + + hmac_digest = OpenSSL::HMAC.hexdigest( + VERIFICATION_HMAC_ALGORITHM, + Gitlab::CurrentSettings.slack_app_signing_secret, + basestring + ) + + # Signature will look like: 'v0=a2114d57b48eac39b9ad189dd8316235a7b4a8d21a10bd27519666489c69b503' + ActiveSupport::SecurityUtils.secure_compare( + signature, + "#{VERIFICATION_VERSION}=#{hmac_digest}" + ) + end + end + end + end +end diff --git a/lib/api/internal/base.rb b/lib/api/internal/base.rb index b53f855c3a2..3edd38a0108 100644 --- a/lib/api/internal/base.rb +++ b/lib/api/internal/base.rb @@ -164,6 +164,18 @@ module API check_allowed(params) end + post '/error_tracking_allowed', feature_category: :error_tracking do + public_key = params[:public_key] + project_id = params[:project_id] + + unprocessable_entity! if public_key.blank? || project_id.blank? + + enabled = ::ErrorTracking::ClientKey.enabled_key_for(project_id, public_key).exists? + + status 200 + { enabled: enabled } + end + post "/lfs_authenticate", feature_category: :source_code_management, urgency: :high do not_found! unless container&.lfs_enabled? diff --git a/lib/api/internal/mail_room.rb b/lib/api/internal/mail_room.rb index 6e24cf6e7c5..1e5e8c4c4e2 100644 --- a/lib/api/internal/mail_room.rb +++ b/lib/api/internal/mail_room.rb @@ -12,6 +12,10 @@ module API class MailRoom < ::API::Base feature_category :service_desk + format :json + content_type :txt, 'text/plain' + default_format :txt + before do authenticate_gitlab_mailroom_request! end @@ -30,7 +34,7 @@ module API end post "/*mailbox_type" do worker = Gitlab::MailRoom.worker_for(params[:mailbox_type]) - raw = request.body.read + raw = Gitlab::EncodingHelper.encode_utf8(request.body.read) begin worker.perform_async(raw) rescue Gitlab::SidekiqMiddleware::SizeLimiter::ExceedLimitError diff --git a/lib/api/internal/workhorse.rb b/lib/api/internal/workhorse.rb new file mode 100644 index 00000000000..910cf52bc3b --- /dev/null +++ b/lib/api/internal/workhorse.rb @@ -0,0 +1,37 @@ +# frozen_string_literal: true + +module API + module Internal + class Workhorse < ::API::Base + feature_category :not_owned # rubocop:todo Gitlab/AvoidFeatureCategoryNotOwned + + before do + verify_workhorse_api! + content_type Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE + end + + helpers do + def request_authenticated? + authenticator = Gitlab::Auth::RequestAuthenticator.new(request) + return true if authenticator.find_authenticated_requester([:api]) + + # Look up user from warden, ignoring the absence of a CSRF token. For + # web users the CSRF token can be in the POST form data but Workhorse + # does not propagate the form data to us. + !!request.env['warden']&.authenticate + end + end + + namespace 'internal' do + namespace 'workhorse' do + post 'authorize_upload' do + unauthorized! unless request_authenticated? + + status 200 + { TempPath: File.join(::Gitlab.config.uploads.storage_path, 'uploads/tmp') } + end + end + end + end + end +end diff --git a/lib/api/issue_links.rb b/lib/api/issue_links.rb index cf075af8373..c07c2c1994e 100644 --- a/lib/api/issue_links.rb +++ b/lib/api/issue_links.rb @@ -61,6 +61,22 @@ module API end # rubocop: enable CodeReuse/ActiveRecord + desc 'Get issues relation' do + detail 'This feature was introduced in GitLab 15.1.' + success Entities::IssueLink + end + params do + requires :issue_link_id, type: Integer, desc: 'The ID of an issue link' + end + get ':id/issues/:issue_iid/links/:issue_link_id' do + issue = find_project_issue(params[:issue_iid]) + issue_link = IssueLink.for_source_or_target(issue).find(declared_params[:issue_link_id]) + + find_project_issue(issue_link.target.iid.to_s, issue_link.target.project_id.to_s) + + present issue_link, with: Entities::IssueLink + end + desc 'Remove issues relation' do success Entities::IssueLink end diff --git a/lib/api/merge_requests.rb b/lib/api/merge_requests.rb index 730baae63a2..156a92802b0 100644 --- a/lib/api/merge_requests.rb +++ b/lib/api/merge_requests.rb @@ -9,7 +9,6 @@ module API before { authenticate_non_get! } helpers Helpers::MergeRequestsHelpers - helpers Helpers::SSEHelpers # These endpoints are defined in `TimeTrackingEndpoints` and is shared by # API::Issues. In order to be able to define the feature category of these @@ -234,8 +233,6 @@ module API handle_merge_request_errors!(merge_request) - Gitlab::UsageDataCounters::EditorUniqueCounter.track_sse_edit_action(author: current_user) if request_from_sse?(user_project) - present merge_request, with: Entities::MergeRequest, current_user: current_user, project: user_project end @@ -458,7 +455,11 @@ module API not_allowed! if !immediately_mergeable && !automatically_mergeable - render_api_error!('Branch cannot be merged', 406) unless merge_request.mergeable?(skip_ci_check: automatically_mergeable) + if Feature.enabled?(:change_response_code_merge_status, user_project) + render_api_error!('Branch cannot be merged', 422) unless merge_request.mergeable?(skip_ci_check: automatically_mergeable) + else + render_api_error!('Branch cannot be merged', 406) unless merge_request.mergeable?(skip_ci_check: automatically_mergeable) + end check_sha_param!(params, merge_request) diff --git a/lib/api/metrics/dashboard/annotations.rb b/lib/api/metrics/dashboard/annotations.rb index c6406bf61df..6fc90da87d4 100644 --- a/lib/api/metrics/dashboard/annotations.rb +++ b/lib/api/metrics/dashboard/annotations.rb @@ -5,6 +5,7 @@ module API module Dashboard class Annotations < ::API::Base feature_category :metrics + urgency :low desc 'Create a new monitoring dashboard annotation' do success Entities::Metrics::Dashboard::Annotation diff --git a/lib/api/metrics/user_starred_dashboards.rb b/lib/api/metrics/user_starred_dashboards.rb index 909f7f0405d..83d95f8b062 100644 --- a/lib/api/metrics/user_starred_dashboards.rb +++ b/lib/api/metrics/user_starred_dashboards.rb @@ -4,6 +4,7 @@ module API module Metrics class UserStarredDashboards < ::API::Base feature_category :metrics + urgency :low resource :projects do desc 'Marks selected metrics dashboard as starred' do diff --git a/lib/api/namespaces.rb b/lib/api/namespaces.rb index 4ff7096b5d9..a12fbbb9bb6 100644 --- a/lib/api/namespaces.rb +++ b/lib/api/namespaces.rb @@ -67,9 +67,10 @@ module API end get ':namespace/exists', requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS, feature_category: :subgroups, urgency: :low do namespace_path = params[:namespace] + existing_namespaces_within_the_parent = Namespace.without_project_namespaces.by_parent(params[:parent_id]) - exists = Namespace.without_project_namespaces.by_parent(params[:parent_id]).filter_by_path(namespace_path).exists? - suggestions = exists ? [Namespace.clean_path(namespace_path)] : [] + exists = existing_namespaces_within_the_parent.filter_by_path(namespace_path).exists? + suggestions = exists ? [Namespace.clean_path(namespace_path, limited_to: existing_namespaces_within_the_parent)] : [] present :exists, exists present :suggests, suggestions diff --git a/lib/api/personal_access_tokens.rb b/lib/api/personal_access_tokens.rb index 40e6486dae9..f8b744bb14b 100644 --- a/lib/api/personal_access_tokens.rb +++ b/lib/api/personal_access_tokens.rb @@ -54,6 +54,14 @@ module API present paginate(tokens), with: Entities::PersonalAccessToken end + get ':id' do + token = PersonalAccessToken.find_by_id(params[:id]) + + unauthorized! unless token && Ability.allowed?(current_user, :read_user_personal_access_tokens, token.user) + + present token, with: Entities::PersonalAccessToken + end + delete 'self' do revoke_token(access_token) end diff --git a/lib/api/projects_relation_builder.rb b/lib/api/projects_relation_builder.rb index 35f555e16b5..fb782b49f02 100644 --- a/lib/api/projects_relation_builder.rb +++ b/lib/api/projects_relation_builder.rb @@ -45,8 +45,6 @@ module API # 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) diff --git a/lib/api/pypi_packages.rb b/lib/api/pypi_packages.rb index f11270457c9..5bf3c3b8aac 100644 --- a/lib/api/pypi_packages.rb +++ b/lib/api/pypi_packages.rb @@ -39,6 +39,51 @@ module API params :package_name do requires :package_name, type: String, file_path: true, desc: 'The PyPi package name' end + + def present_simple_index(group_or_project) + authorize_read_package!(group_or_project) + + packages = Packages::Pypi::PackagesFinder.new(current_user, group_or_project).execute + presenter = ::Packages::Pypi::SimpleIndexPresenter.new(packages, group_or_project) + + present_html(presenter.body) + end + + def present_simple_package(group_or_project) + authorize_read_package!(group_or_project) + track_simple_event(group_or_project, 'list_package') + + packages = Packages::Pypi::PackagesFinder.new(current_user, group_or_project, { package_name: params[:package_name] }).execute + empty_packages = packages.empty? + + redirect_registry_request(empty_packages, :pypi, package_name: params[:package_name]) do + not_found!('Package') if empty_packages + presenter = ::Packages::Pypi::SimplePackageVersionsPresenter.new(packages, group_or_project) + + present_html(presenter.body) + end + end + + def track_simple_event(group_or_project, event_name) + if group_or_project.is_a?(Project) + project = group_or_project + namespace = group_or_project.namespace + else + project = nil + namespace = group_or_project + end + + track_package_event(event_name, :pypi, project: project, namespace: namespace) + end + + def present_html(content) + # Adjusts grape output format + # to be HTML + content_type "text/html; charset=utf-8" + env['api.format'] = :binary + + body content + end end params do @@ -67,7 +112,18 @@ module API present_carrierwave_file!(package_file.file, supports_direct_download: true) end - desc 'The PyPi Simple Endpoint' do + desc 'The PyPi Simple Group Index Endpoint' do + detail 'This feature was introduced in GitLab 15.1' + end + + # An API entry point but returns an HTML file instead of JSON. + # PyPi simple API returns a list of packages as a simple HTML file. + route_setting :authentication, deploy_token_allowed: true, basic_auth_personal_access_token: true, job_token_allowed: :basic_auth + get 'simple', format: :txt do + present_simple_index(find_authorized_group!) + end + + desc 'The PyPi Simple Group Package Endpoint' do detail 'This feature was introduced in GitLab 12.10' end @@ -75,29 +131,11 @@ module API use :package_name end - # An Api entry point but returns an HTML file instead of JSON. + # An API entry point but returns an HTML file instead of JSON. # PyPi simple API returns the package descriptor as a simple HTML file. route_setting :authentication, deploy_token_allowed: true, basic_auth_personal_access_token: true, job_token_allowed: :basic_auth get 'simple/*package_name', format: :txt do - group = find_authorized_group! - authorize_read_package!(group) - - track_package_event('list_package', :pypi) - - packages = Packages::Pypi::PackagesFinder.new(current_user, group, { package_name: params[:package_name] }).execute - empty_packages = packages.empty? - - redirect_registry_request(empty_packages, :pypi, package_name: params[:package_name]) do - not_found!('Package') if empty_packages - presenter = ::Packages::Pypi::PackagePresenter.new(packages, group) - - # Adjusts grape output format - # to be HTML - content_type "text/html; charset=utf-8" - env['api.format'] = :binary - - body presenter.body - end + present_simple_package(find_authorized_group!) end end end @@ -133,7 +171,18 @@ module API present_carrierwave_file!(package_file.file, supports_direct_download: true) end - desc 'The PyPi Simple Endpoint' do + desc 'The PyPi Simple Project Index Endpoint' do + detail 'This feature was introduced in GitLab 15.1' + end + + # An API entry point but returns an HTML file instead of JSON. + # PyPi simple API returns a list of packages as a simple HTML file. + route_setting :authentication, deploy_token_allowed: true, basic_auth_personal_access_token: true, job_token_allowed: :basic_auth + get 'simple', format: :txt do + present_simple_index(authorized_user_project) + end + + desc 'The PyPi Simple Project Package Endpoint' do detail 'This feature was introduced in GitLab 12.10' end @@ -141,28 +190,11 @@ module API use :package_name end - # An Api entry point but returns an HTML file instead of JSON. + # An API entry point but returns an HTML file instead of JSON. # PyPi simple API returns the package descriptor as a simple HTML file. route_setting :authentication, deploy_token_allowed: true, basic_auth_personal_access_token: true, job_token_allowed: :basic_auth get 'simple/*package_name', format: :txt do - authorize_read_package!(authorized_user_project) - - track_package_event('list_package', :pypi, project: authorized_user_project, namespace: authorized_user_project.namespace) - - packages = Packages::Pypi::PackagesFinder.new(current_user, authorized_user_project, { package_name: params[:package_name] }).execute - empty_packages = packages.empty? - - redirect_registry_request(empty_packages, :pypi, package_name: params[:package_name]) do - not_found!('Package') if empty_packages - presenter = ::Packages::Pypi::PackagePresenter.new(packages, authorized_user_project) - - # Adjusts grape output format - # to be HTML - content_type "text/html; charset=utf-8" - env['api.format'] = :binary - - body presenter.body - end + present_simple_package(authorized_user_project) end desc 'The PyPi Package upload endpoint' do diff --git a/lib/api/release/links.rb b/lib/api/release/links.rb index bc5ffe5b21f..8b9380b332e 100644 --- a/lib/api/release/links.rb +++ b/lib/api/release/links.rb @@ -29,6 +29,7 @@ module API params do use :pagination end + route_setting :authentication, job_token_allowed: true get 'links' do authorize! :read_release, release @@ -45,6 +46,7 @@ module API optional :filepath, type: String, desc: 'The filepath of the link' optional :link_type, type: String, desc: 'The link type, one of: "runbook", "image", "package" or "other"' end + route_setting :authentication, job_token_allowed: true post 'links' do authorize! :create_release, release @@ -65,6 +67,7 @@ module API detail 'This feature was introduced in GitLab 11.7.' success Entities::Releases::Link end + route_setting :authentication, job_token_allowed: true get do authorize! :read_release, release @@ -82,6 +85,7 @@ module API optional :link_type, type: String, desc: 'The link type' at_least_one_of :name, :url end + route_setting :authentication, job_token_allowed: true put do authorize! :update_release, release @@ -96,6 +100,7 @@ module API detail 'This feature was introduced in GitLab 11.7.' success Entities::Releases::Link end + route_setting :authentication, job_token_allowed: true delete do authorize! :destroy_release, release diff --git a/lib/api/releases.rb b/lib/api/releases.rb index c69f45f1f38..aecd6f9eef8 100644 --- a/lib/api/releases.rb +++ b/lib/api/releases.rb @@ -107,9 +107,10 @@ module API end params do requires :tag_name, type: String, desc: 'The name of the tag', as: :tag + optional :tag_message, type: String, desc: 'Message to use if creating a new annotated tag' optional :name, type: String, desc: 'The name of the release' optional :description, type: String, desc: 'The release notes' - optional :ref, type: String, desc: 'The commit sha or branch name' + optional :ref, type: String, desc: 'Commit SHA or branch name to use if creating a new tag' optional :assets, type: Hash do optional :links, type: Array do requires :name, type: String, desc: 'The name of the link' diff --git a/lib/api/terraform/modules/v1/packages.rb b/lib/api/terraform/modules/v1/packages.rb index 8da77ba18ae..f96cffb008c 100644 --- a/lib/api/terraform/modules/v1/packages.rb +++ b/lib/api/terraform/modules/v1/packages.rb @@ -114,7 +114,9 @@ module API module_version: params[:module_version] ) - jwt_token = Gitlab::TerraformRegistryToken.from_token(token_from_namespace_inheritable).encoded + if token_from_namespace_inheritable + jwt_token = Gitlab::TerraformRegistryToken.from_token(token_from_namespace_inheritable).encoded + end header 'X-Terraform-Get', module_file_path.sub(%r{module_version/file$}, "#{params[:module_version]}/file?token=#{jwt_token}&archive=tgz") status :no_content diff --git a/lib/api/terraform/state.rb b/lib/api/terraform/state.rb index 7b111451b9f..b727fbd9f65 100644 --- a/lib/api/terraform/state.rb +++ b/lib/api/terraform/state.rb @@ -13,6 +13,7 @@ module API default_format :json rescue_from( + ::Terraform::RemoteStateHandler::StateDeletedError, ::ActiveRecord::RecordNotUnique, ::PG::UniqueViolation ) do |e| @@ -24,6 +25,11 @@ module API authorize! :read_terraform_state, user_project increment_unique_values('p_terraform_state_api_unique_users', current_user.id) + + if Feature.enabled?(:route_hll_to_snowplow_phase2, user_project&.namespace) + Gitlab::Tracking.event('API::Terraform::State', 'p_terraform_state_api_unique_users', + namespace: user_project&.namespace, user: current_user) + end end params do @@ -76,7 +82,7 @@ module API authorize! :admin_terraform_state, user_project remote_state_handler.handle_with_lock do |state| - state.destroy! + ::Terraform::States::TriggerDestroyService.new(state, current_user: current_user).execute end body false diff --git a/lib/api/user_counts.rb b/lib/api/user_counts.rb index 756901c5717..d0b1e458a27 100644 --- a/lib/api/user_counts.rb +++ b/lib/api/user_counts.rb @@ -3,6 +3,7 @@ module API class UserCounts < ::API::Base feature_category :navigation + urgency :low resource :user_counts do desc 'Return the user specific counts' do diff --git a/lib/api/users.rb b/lib/api/users.rb index b10458c4358..93df9413119 100644 --- a/lib/api/users.rb +++ b/lib/api/users.rb @@ -10,7 +10,7 @@ module API feature_category :users, ['/users/:id/custom_attributes', '/users/:id/custom_attributes/:key'] - urgency :high, ['/users/:id/custom_attributes', '/users/:id/custom_attributes/:key'] + urgency :medium, ['/users/:id/custom_attributes', '/users/:id/custom_attributes/:key'] resource :users, requirements: { uid: /[0-9]*/, id: /[0-9]*/ } do include CustomAttributesEndpoints @@ -145,7 +145,7 @@ module API use :with_custom_attributes end # rubocop: disable CodeReuse/ActiveRecord - get ":id", feature_category: :users, urgency: :medium do + get ":id", feature_category: :users, urgency: :low do forbidden!('Not authorized!') unless current_user unless current_user.admin? @@ -170,7 +170,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, urgency: :high do + get ":user_id/status", requirements: API::USER_REQUIREMENTS, feature_category: :users, urgency: :default do user = find_user(params[:user_id]) not_found!('User') unless user && can?(current_user, :read_user, user) @@ -346,6 +346,30 @@ module API end # rubocop: enable CodeReuse/ActiveRecord + desc 'Get the project-level Deploy keys that a specified user can access to.' do + success Entities::DeployKey + end + params do + requires :user_id, type: String, desc: 'The ID or username of the user' + use :pagination + end + get ':user_id/project_deploy_keys', requirements: API::USER_REQUIREMENTS, feature_category: :continuous_delivery do + user = find_user(params[:user_id]) + not_found!('User') unless user && can?(current_user, :read_user, user) + + project_ids = Project.visible_to_user_and_access_level(current_user, Gitlab::Access::MAINTAINER) + + unless current_user == user + # Restrict to only common projects of both current_user and user. + project_ids = project_ids.visible_to_user_and_access_level(user, Gitlab::Access::MAINTAINER) + end + + forbidden!('No common authorized project found') unless project_ids.present? + + keys = DeployKey.in_projects(project_ids) + present paginate(keys), with: Entities::DeployKey + end + desc 'Add an SSH key to a specified user. Available only for admins.' do success Entities::SSHKey end @@ -921,7 +945,7 @@ module API desc 'Get the currently authenticated user' do success Entities::UserPublic end - get feature_category: :users, urgency: :medium do + get feature_category: :users, urgency: :low do entity = if current_user.admin? Entities::UserWithAdmin @@ -1096,7 +1120,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: :purchase do + put ":user_id/credit_card_validation", urgency: :low, feature_category: :purchase do authenticated_as_admin! user = find_user(params[:user_id]) diff --git a/lib/api/wikis.rb b/lib/api/wikis.rb index 12dbf4792d6..082be1f7e11 100644 --- a/lib/api/wikis.rb +++ b/lib/api/wikis.rb @@ -37,7 +37,12 @@ module API entity = params[:with_content] ? Entities::WikiPage : Entities::WikiPageBasic - present container.wiki.list_pages(load_content: params[:with_content]), with: entity + options = { + with: entity, + current_user: current_user + } + + present container.wiki.list_pages(load_content: params[:with_content]), options end desc 'Get a wiki page' do @@ -51,7 +56,13 @@ module API get ':id/wikis/:slug', urgency: :low do authorize! :read_wiki, container - present wiki_page(params[:version]), with: Entities::WikiPage, render_html: params[:render_html] + options = { + with: Entities::WikiPage, + render_html: params[:render_html], + current_user: current_user + } + + present wiki_page(params[:version]), options end desc 'Create a wiki page' do |