summaryrefslogtreecommitdiff
path: root/lib/api
diff options
context:
space:
mode:
Diffstat (limited to 'lib/api')
-rw-r--r--lib/api/api.rb4
-rw-r--r--lib/api/appearance.rb1
-rw-r--r--lib/api/avatar.rb2
-rw-r--r--lib/api/badges.rb4
-rw-r--r--lib/api/broadcast_messages.rb1
-rw-r--r--lib/api/bulk_imports.rb2
-rw-r--r--lib/api/ci/job_artifacts.rb10
-rw-r--r--lib/api/ci/jobs.rb7
-rw-r--r--lib/api/ci/pipelines.rb4
-rw-r--r--lib/api/ci/runner.rb2
-rw-r--r--lib/api/ci/secure_files.rb2
-rw-r--r--lib/api/clusters/agent_tokens.rb12
-rw-r--r--lib/api/clusters/agents.rb8
-rw-r--r--lib/api/entities/ci/job_request/image.rb2
-rw-r--r--lib/api/entities/ci/job_request/service.rb5
-rw-r--r--lib/api/entities/hook.rb3
-rw-r--r--lib/api/entities/issue.rb10
-rw-r--r--lib/api/entities/personal_access_token_with_details.rb13
-rw-r--r--lib/api/entities/releases/link.rb11
-rw-r--r--lib/api/entities/wiki_page.rb10
-rw-r--r--lib/api/environments.rb11
-rw-r--r--lib/api/error_tracking/client_keys.rb1
-rw-r--r--lib/api/error_tracking/collector.rb1
-rw-r--r--lib/api/error_tracking/project_settings.rb1
-rw-r--r--lib/api/features.rb13
-rw-r--r--lib/api/groups.rb2
-rw-r--r--lib/api/helpers.rb39
-rw-r--r--lib/api/helpers/project_stats_refresh_conflicts_helpers.rb15
-rw-r--r--lib/api/helpers/projects_helpers.rb1
-rw-r--r--lib/api/helpers/sse_helpers.rb16
-rw-r--r--lib/api/integrations/jira_connect/subscriptions.rb2
-rw-r--r--lib/api/integrations/slack/events.rb40
-rw-r--r--lib/api/integrations/slack/events/url_verification.rb22
-rw-r--r--lib/api/integrations/slack/request.rb51
-rw-r--r--lib/api/internal/base.rb12
-rw-r--r--lib/api/internal/mail_room.rb6
-rw-r--r--lib/api/internal/workhorse.rb37
-rw-r--r--lib/api/issue_links.rb16
-rw-r--r--lib/api/merge_requests.rb9
-rw-r--r--lib/api/metrics/dashboard/annotations.rb1
-rw-r--r--lib/api/metrics/user_starred_dashboards.rb1
-rw-r--r--lib/api/namespaces.rb5
-rw-r--r--lib/api/personal_access_tokens.rb8
-rw-r--r--lib/api/projects_relation_builder.rb2
-rw-r--r--lib/api/pypi_packages.rb114
-rw-r--r--lib/api/release/links.rb5
-rw-r--r--lib/api/releases.rb3
-rw-r--r--lib/api/terraform/modules/v1/packages.rb4
-rw-r--r--lib/api/terraform/state.rb8
-rw-r--r--lib/api/user_counts.rb1
-rw-r--r--lib/api/users.rb34
-rw-r--r--lib/api/wikis.rb15
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