summaryrefslogtreecommitdiff
path: root/lib/api
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2021-04-20 14:36:54 +0000
committerGitLab Bot <gitlab-bot@gitlab.com>2021-04-20 14:36:54 +0000
commitf61bb2a16a514b71bf33aabbbb999d6732016a24 (patch)
tree9548caa89e60b4f40b99bbd1dac030420b812aa8 /lib/api
parent35fc54e5d261f8898e390aea7c2f5ec5fdf0539d (diff)
downloadgitlab-ce-4a647b2732a66bd1012cef18484571aacad42b5a.tar.gz
Add latest changes from gitlab-org/gitlab@13-11-stable-eev13.11.0-rc42
Diffstat (limited to 'lib/api')
-rw-r--r--lib/api/admin/sidekiq.rb4
-rw-r--r--lib/api/api.rb4
-rw-r--r--lib/api/api_guard.rb4
-rw-r--r--lib/api/applications.rb2
-rw-r--r--lib/api/ci/pipelines.rb2
-rw-r--r--lib/api/ci/runner.rb4
-rw-r--r--lib/api/commits.rb6
-rw-r--r--lib/api/composer_packages.rb2
-rw-r--r--lib/api/concerns/packages/nuget_endpoints.rb2
-rw-r--r--lib/api/deploy_keys.rb2
-rw-r--r--lib/api/deployments.rb4
-rw-r--r--lib/api/entities/basic_project_details.rb9
-rw-r--r--lib/api/entities/clusters/agent.rb12
-rw-r--r--lib/api/entities/email.rb2
-rw-r--r--lib/api/entities/job_request/job_info.rb2
-rw-r--r--lib/api/entities/namespace_existence.rb9
-rw-r--r--lib/api/entities/project.rb5
-rw-r--r--lib/api/entities/project_import_failed_relation.rb6
-rw-r--r--lib/api/entities/user.rb4
-rw-r--r--lib/api/entities/user_preferences.rb9
-rw-r--r--lib/api/entities/user_public.rb1
-rw-r--r--lib/api/environments.rb2
-rw-r--r--lib/api/files.rb4
-rw-r--r--lib/api/generic_packages.rb4
-rw-r--r--lib/api/group_variables.rb36
-rw-r--r--lib/api/groups.rb10
-rw-r--r--lib/api/helpers.rb22
-rw-r--r--lib/api/helpers/authentication.rb5
-rw-r--r--lib/api/helpers/caching.rb137
-rw-r--r--lib/api/helpers/common_helpers.rb4
-rw-r--r--lib/api/helpers/graphql_helpers.rb4
-rw-r--r--lib/api/helpers/notes_helpers.rb6
-rw-r--r--lib/api/helpers/packages/conan/api_helpers.rb23
-rw-r--r--lib/api/helpers/packages/dependency_proxy_helpers.rb2
-rw-r--r--lib/api/helpers/packages_helpers.rb3
-rw-r--r--lib/api/helpers/runner.rb9
-rw-r--r--lib/api/helpers/services_helpers.rb10
-rw-r--r--lib/api/helpers/variables_helpers.rb27
-rw-r--r--lib/api/internal/base.rb61
-rw-r--r--lib/api/internal/kubernetes.rb6
-rw-r--r--lib/api/invitations.rb6
-rw-r--r--lib/api/issue_links.rb5
-rw-r--r--lib/api/issues.rb6
-rw-r--r--lib/api/jobs.rb30
-rw-r--r--lib/api/maven_packages.rb43
-rw-r--r--lib/api/members.rb8
-rw-r--r--lib/api/merge_request_diffs.rb2
-rw-r--r--lib/api/merge_requests.rb67
-rw-r--r--lib/api/milestone_responses.rb2
-rw-r--r--lib/api/namespaces.rb17
-rw-r--r--lib/api/project_import.rb2
-rw-r--r--lib/api/projects.rb53
-rw-r--r--lib/api/repositories.rb15
-rw-r--r--lib/api/resource_access_tokens.rb6
-rw-r--r--lib/api/rubygem_packages.rb12
-rw-r--r--lib/api/search.rb18
-rw-r--r--lib/api/settings.rb1
-rw-r--r--lib/api/tags.rb8
-rw-r--r--lib/api/triggers.rb2
-rw-r--r--lib/api/usage_data.rb17
-rw-r--r--lib/api/usage_data_non_sql_metrics.rb27
-rw-r--r--lib/api/usage_data_queries.rb27
-rw-r--r--lib/api/users.rb27
-rw-r--r--lib/api/v3/github.rb12
-rw-r--r--lib/api/variables.rb30
65 files changed, 678 insertions, 235 deletions
diff --git a/lib/api/admin/sidekiq.rb b/lib/api/admin/sidekiq.rb
index 7e561783685..d91d4a0d4d5 100644
--- a/lib/api/admin/sidekiq.rb
+++ b/lib/api/admin/sidekiq.rb
@@ -12,11 +12,11 @@ module API
namespace 'queues' do
desc 'Drop jobs matching the given metadata from the Sidekiq queue'
params do
- Labkit::Context::KNOWN_KEYS.each do |key|
+ Gitlab::ApplicationContext::KNOWN_KEYS.each do |key|
optional key, type: String, allow_blank: false
end
- at_least_one_of(*Labkit::Context::KNOWN_KEYS)
+ at_least_one_of(*Gitlab::ApplicationContext::KNOWN_KEYS)
end
delete ':queue_name' do
result =
diff --git a/lib/api/api.rb b/lib/api/api.rb
index f83a36068dd..a287ffbfcd8 100644
--- a/lib/api/api.rb
+++ b/lib/api/api.rb
@@ -59,7 +59,7 @@ module API
project: -> { @project },
namespace: -> { @group },
runner: -> { @current_runner || @runner },
- caller_id: route.origin,
+ caller_id: api_endpoint.endpoint_id,
remote_ip: request.ip,
feature_category: feature_category
)
@@ -293,6 +293,8 @@ module API
mount ::API::Triggers
mount ::API::Unleash
mount ::API::UsageData
+ mount ::API::UsageDataQueries
+ mount ::API::UsageDataNonSqlMetrics
mount ::API::UserCounts
mount ::API::Users
mount ::API::Variables
diff --git a/lib/api/api_guard.rb b/lib/api/api_guard.rb
index 8641271f2df..8822a30d4a1 100644
--- a/lib/api/api_guard.rb
+++ b/lib/api/api_guard.rb
@@ -55,7 +55,7 @@ module API
user = find_user_from_sources
return unless user
- if user.is_a?(User) && Feature.enabled?(:user_mode_in_session)
+ if user.is_a?(User) && Gitlab::CurrentSettings.admin_mode
# Sessions are enforced to be unavailable for API calls, so ignore them for admin mode
Gitlab::Auth::CurrentUserMode.bypass_session!(user.id)
end
@@ -236,7 +236,7 @@ module API
def after
# Use a Grape middleware since the Grape `after` blocks might run
# before we are finished rendering the `Grape::Entity` classes
- Gitlab::Auth::CurrentUserMode.reset_bypass_session! if Feature.enabled?(:user_mode_in_session)
+ Gitlab::Auth::CurrentUserMode.reset_bypass_session! if Gitlab::CurrentSettings.admin_mode
# Explicit nil is needed or the api call return value will be overwritten
nil
diff --git a/lib/api/applications.rb b/lib/api/applications.rb
index b883f83cc19..be482272b20 100644
--- a/lib/api/applications.rb
+++ b/lib/api/applications.rb
@@ -41,6 +41,8 @@ module API
desc 'Delete an application'
delete ':id' do
application = ApplicationsFinder.new(params).execute
+ break not_found!('Application') unless application
+
application.destroy
no_content!
diff --git a/lib/api/ci/pipelines.rb b/lib/api/ci/pipelines.rb
index fa75d012613..339c0e779f9 100644
--- a/lib/api/ci/pipelines.rb
+++ b/lib/api/ci/pipelines.rb
@@ -70,7 +70,7 @@ module API
optional :variables, Array, desc: 'Array of variables available in the pipeline'
end
post ':id/pipeline' do
- Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab-foss/issues/42124')
+ Gitlab::QueryLimiting.disable!('https://gitlab.com/gitlab-org/gitlab/-/issues/20711')
authorize! :create_pipeline, user_project
diff --git a/lib/api/ci/runner.rb b/lib/api/ci/runner.rb
index 80d5e80e21e..c5249f1377b 100644
--- a/lib/api/ci/runner.rb
+++ b/lib/api/ci/runner.rb
@@ -245,7 +245,7 @@ module API
job = authenticate_job!
- result = ::Ci::CreateJobArtifactsService.new(job).authorize(artifact_type: params[:artifact_type], filesize: params[:filesize])
+ result = ::Ci::JobArtifacts::CreateService.new(job).authorize(artifact_type: params[:artifact_type], filesize: params[:filesize])
if result[:status] == :success
content_type Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE
@@ -284,7 +284,7 @@ module API
artifacts = params[:file]
metadata = params[:metadata]
- result = ::Ci::CreateJobArtifactsService.new(job).execute(artifacts, params, metadata_file: metadata)
+ result = ::Ci::JobArtifacts::CreateService.new(job).execute(artifacts, params, metadata_file: metadata)
if result[:status] == :success
status :created
diff --git a/lib/api/commits.rb b/lib/api/commits.rb
index a24848082a9..bd9f83ac24c 100644
--- a/lib/api/commits.rb
+++ b/lib/api/commits.rb
@@ -186,16 +186,14 @@ module API
use :pagination
requires :sha, type: String, desc: 'A commit sha, or the name of a branch or tag'
end
- # rubocop: disable CodeReuse/ActiveRecord
get ':id/repository/commits/:sha/comments', requirements: API::COMMIT_ENDPOINT_REQUIREMENTS do
commit = user_project.commit(params[:sha])
not_found! 'Commit' unless commit
- notes = commit.notes.order(:created_at)
+ notes = commit.notes.with_api_entity_associations.fresh
present paginate(notes), with: Entities::CommitNote
end
- # rubocop: enable CodeReuse/ActiveRecord
desc 'Cherry pick commit into a branch' do
detail 'This feature was introduced in GitLab 8.15'
@@ -372,7 +370,7 @@ module API
current_user,
project_id: user_project.id,
commit_sha: commit.sha
- ).execute
+ ).execute.with_api_entity_associations
present paginate(commit_merge_requests), with: Entities::MergeRequestBasic
end
diff --git a/lib/api/composer_packages.rb b/lib/api/composer_packages.rb
index bd8d9b68858..115a6b8ac4f 100644
--- a/lib/api/composer_packages.rb
+++ b/lib/api/composer_packages.rb
@@ -161,6 +161,8 @@ module API
not_found! unless metadata
+ track_package_event('pull_package', :composer)
+
send_git_archive unauthorized_user_project.repository, ref: metadata.target_sha, format: 'zip', append_sha: true
end
end
diff --git a/lib/api/concerns/packages/nuget_endpoints.rb b/lib/api/concerns/packages/nuget_endpoints.rb
index 53b778875fc..5364eeb1880 100644
--- a/lib/api/concerns/packages/nuget_endpoints.rb
+++ b/lib/api/concerns/packages/nuget_endpoints.rb
@@ -95,7 +95,7 @@ module API
# https://docs.microsoft.com/en-us/nuget/api/search-query-service-resource
params do
- requires :q, type: String, desc: 'The search term'
+ optional :q, type: String, desc: 'The search term'
optional :skip, type: Integer, desc: 'The number of results to skip', default: 0, regexp: NON_NEGATIVE_INTEGER_REGEX
optional :take, type: Integer, desc: 'The number of results to return', default: Kaminari.config.default_per_page, regexp: POSITIVE_INTEGER_REGEX
optional :prerelease, type: ::Grape::API::Boolean, desc: 'Include prerelease versions', default: true
diff --git a/lib/api/deploy_keys.rb b/lib/api/deploy_keys.rb
index 0a541620c3a..9f0f569b711 100644
--- a/lib/api/deploy_keys.rb
+++ b/lib/api/deploy_keys.rb
@@ -44,7 +44,7 @@ module API
end
# rubocop: disable CodeReuse/ActiveRecord
get ":id/deploy_keys" do
- keys = user_project.deploy_keys_projects.preload(:deploy_key)
+ keys = user_project.deploy_keys_projects.preload(deploy_key: :user)
present paginate(keys), with: Entities::DeployKeysProject
end
diff --git a/lib/api/deployments.rb b/lib/api/deployments.rb
index d0c842bb19d..0a6ecf2919c 100644
--- a/lib/api/deployments.rb
+++ b/lib/api/deployments.rb
@@ -36,7 +36,9 @@ module API
get ':id/deployments' do
authorize! :read_deployment, user_project
- deployments = DeploymentsFinder.new(params.merge(project: user_project)).execute
+ deployments =
+ DeploymentsFinder.new(params.merge(project: user_project))
+ .execute.with_api_entity_associations
present paginate(deployments), with: Entities::Deployment
end
diff --git a/lib/api/entities/basic_project_details.rb b/lib/api/entities/basic_project_details.rb
index cf0b32bed26..2de49d6ed40 100644
--- a/lib/api/entities/basic_project_details.rb
+++ b/lib/api/entities/basic_project_details.rb
@@ -8,11 +8,10 @@ module API
expose :default_branch, if: -> (project, options) { Ability.allowed?(options[:current_user], :download_code, project) }
# Avoids an N+1 query: https://github.com/mbleigh/acts-as-taggable-on/issues/91#issuecomment-168273770
expose :tag_list do |project|
- # project.tags.order(:name).pluck(:name) is the most suitable option
- # to avoid loading all the ActiveRecord objects but, if we use it here
- # it override the preloaded associations and makes a query
- # (fixed in https://github.com/rails/rails/pull/25976).
- project.tags.map(&:name).sort
+ # Tags is a preloaded association. If we perform then sorting
+ # through the database, it will trigger a new query, ending up
+ # in an N+1 if we have several projects
+ project.tags.pluck(:name).sort # rubocop:disable CodeReuse/ActiveRecord
end
expose :ssh_url_to_repo, :http_url_to_repo, :web_url, :readme_url
diff --git a/lib/api/entities/clusters/agent.rb b/lib/api/entities/clusters/agent.rb
new file mode 100644
index 00000000000..3b4538b81c2
--- /dev/null
+++ b/lib/api/entities/clusters/agent.rb
@@ -0,0 +1,12 @@
+# frozen_string_literal: true
+
+module API
+ module Entities
+ module Clusters
+ class Agent < Grape::Entity
+ expose :id
+ expose :project, with: Entities::ProjectIdentity, as: :config_project
+ end
+ end
+ end
+end
diff --git a/lib/api/entities/email.rb b/lib/api/entities/email.rb
index 5ba425def3d..46ebc458bcd 100644
--- a/lib/api/entities/email.rb
+++ b/lib/api/entities/email.rb
@@ -3,7 +3,7 @@
module API
module Entities
class Email < Grape::Entity
- expose :id, :email
+ expose :id, :email, :confirmed_at
end
end
end
diff --git a/lib/api/entities/job_request/job_info.rb b/lib/api/entities/job_request/job_info.rb
index 09c13aa8471..a4bcc9726d0 100644
--- a/lib/api/entities/job_request/job_info.rb
+++ b/lib/api/entities/job_request/job_info.rb
@@ -4,7 +4,7 @@ module API
module Entities
module JobRequest
class JobInfo < Grape::Entity
- expose :name, :stage
+ expose :id, :name, :stage
expose :project_id, :project_name
end
end
diff --git a/lib/api/entities/namespace_existence.rb b/lib/api/entities/namespace_existence.rb
new file mode 100644
index 00000000000..d93078ecdac
--- /dev/null
+++ b/lib/api/entities/namespace_existence.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+module API
+ module Entities
+ class NamespaceExistence < Grape::Entity
+ expose :exists, :suggests
+ end
+ end
+end
diff --git a/lib/api/entities/project.rb b/lib/api/entities/project.rb
index e332e5e40fa..690bc5d419d 100644
--- a/lib/api/entities/project.rb
+++ b/lib/api/entities/project.rb
@@ -127,15 +127,16 @@ module API
# as `:tags` are defined as: `has_many :tags, through: :taggings`
# N+1 is solved then by using `subject.tags.map(&:name)`
# MR describing the solution: https://gitlab.com/gitlab-org/gitlab-foss/merge_requests/20555
- super(projects_relation).preload(:group)
+ super(projects_relation).preload(group: :namespace_settings)
.preload(:ci_cd_settings)
.preload(:project_setting)
.preload(:container_expiration_policy)
.preload(:auto_devops)
+ .preload(:service_desk_setting)
.preload(project_group_links: { group: :route },
fork_network: :root_project,
fork_network_member: :forked_from_project,
- forked_from_project: [:route, :forks, :tags, namespace: :route])
+ forked_from_project: [:route, :forks, :tags, :group, :project_feature, namespace: [:route, :owner]])
end
# rubocop: enable CodeReuse/ActiveRecord
diff --git a/lib/api/entities/project_import_failed_relation.rb b/lib/api/entities/project_import_failed_relation.rb
index 16b26ad0efa..b8f842c1646 100644
--- a/lib/api/entities/project_import_failed_relation.rb
+++ b/lib/api/entities/project_import_failed_relation.rb
@@ -3,7 +3,11 @@
module API
module Entities
class ProjectImportFailedRelation < Grape::Entity
- expose :id, :created_at, :exception_class, :exception_message, :source
+ expose :id, :created_at, :exception_class, :source
+
+ expose :exception_message do |_|
+ nil
+ end
expose :relation_key, as: :relation_name
end
diff --git a/lib/api/entities/user.rb b/lib/api/entities/user.rb
index 248a86751d2..3ce6d03e236 100644
--- a/lib/api/entities/user.rb
+++ b/lib/api/entities/user.rb
@@ -11,10 +11,10 @@ module API
work_information(user)
end
expose :followers, if: ->(user, opts) { Ability.allowed?(opts[:current_user], :read_user_profile, user) } do |user|
- user.followers.count
+ user.followers.size
end
expose :following, if: ->(user, opts) { Ability.allowed?(opts[:current_user], :read_user_profile, user) } do |user|
- user.followees.count
+ user.followees.size
end
end
end
diff --git a/lib/api/entities/user_preferences.rb b/lib/api/entities/user_preferences.rb
new file mode 100644
index 00000000000..7a6df9b6c59
--- /dev/null
+++ b/lib/api/entities/user_preferences.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+module API
+ module Entities
+ class UserPreferences < Grape::Entity
+ expose :id, :user_id, :view_diffs_file_by_file
+ end
+ end
+end
diff --git a/lib/api/entities/user_public.rb b/lib/api/entities/user_public.rb
index 15e9b905bef..685adb1dd10 100644
--- a/lib/api/entities/user_public.rb
+++ b/lib/api/entities/user_public.rb
@@ -14,6 +14,7 @@ module API
expose :two_factor_enabled?, as: :two_factor_enabled
expose :external
expose :private_profile
+ expose :commit_email
end
end
end
diff --git a/lib/api/environments.rb b/lib/api/environments.rb
index 3e1e430c2f9..b606b2e814d 100644
--- a/lib/api/environments.rb
+++ b/lib/api/environments.rb
@@ -26,7 +26,7 @@ module API
get ':id/environments' do
authorize! :read_environment, user_project
- environments = ::EnvironmentsFinder.new(user_project, current_user, params).find
+ environments = ::EnvironmentsFinder.new(user_project, current_user, params).execute
present paginate(environments), with: Entities::Environment, current_user: current_user
end
diff --git a/lib/api/files.rb b/lib/api/files.rb
index cb73bde73f5..f3de7fbe96b 100644
--- a/lib/api/files.rb
+++ b/lib/api/files.rb
@@ -113,7 +113,7 @@ module API
desc 'Get raw file metadata from repository'
params do
requires :file_path, type: String, file_path: true, desc: 'The url encoded path to the file. Ex. lib%2Fclass%2Erb'
- requires :ref, type: String, desc: 'The name of branch, tag or commit', allow_blank: false
+ optional :ref, type: String, desc: 'The name of branch, tag or commit', allow_blank: false
end
head ":id/repository/files/:file_path/raw", requirements: FILE_ENDPOINT_REQUIREMENTS do
assign_file_vars!
@@ -124,7 +124,7 @@ module API
desc 'Get raw file contents from the repository'
params do
requires :file_path, type: String, file_path: true, desc: 'The url encoded path to the file. Ex. lib%2Fclass%2Erb'
- requires :ref, type: String, desc: 'The name of branch, tag commit', allow_blank: false
+ optional :ref, type: String, desc: 'The name of branch, tag or commit', allow_blank: false
end
get ":id/repository/files/:file_path/raw", requirements: FILE_ENDPOINT_REQUIREMENTS do
assign_file_vars!
diff --git a/lib/api/generic_packages.rb b/lib/api/generic_packages.rb
index 3d0ba97b51a..cce55fa92d9 100644
--- a/lib/api/generic_packages.rb
+++ b/lib/api/generic_packages.rb
@@ -62,7 +62,7 @@ module API
authorize_upload!(project)
bad_request!('File is too large') if max_file_size_exceeded?
- track_event('push_package')
+ ::Gitlab::Tracking.event(self.options[:for].name, 'push_package')
create_package_file_params = declared_params.merge(build: current_authenticated_job)
::Packages::Generic::CreatePackageFileService
@@ -94,7 +94,7 @@ module API
package = ::Packages::Generic::PackageFinder.new(project).execute!(params[:package_name], params[:package_version])
package_file = ::Packages::PackageFileFinder.new(package, params[:file_name]).execute!
- track_event('pull_package')
+ ::Gitlab::Tracking.event(self.options[:for].name, 'pull_package')
present_carrierwave_file!(package_file.file)
end
diff --git a/lib/api/group_variables.rb b/lib/api/group_variables.rb
index 09744fbeda2..8d52a0a5b4e 100644
--- a/lib/api/group_variables.rb
+++ b/lib/api/group_variables.rb
@@ -8,6 +8,8 @@ module API
before { authorize! :admin_group, user_group }
feature_category :continuous_integration
+ helpers Helpers::VariablesHelpers
+
params do
requires :id, type: String, desc: 'The ID of a group'
end
@@ -30,16 +32,13 @@ module API
params do
requires :key, type: String, desc: 'The key of the variable'
end
- # rubocop: disable CodeReuse/ActiveRecord
get ':id/variables/:key' do
- key = params[:key]
- variable = user_group.variables.find_by(key: key)
+ variable = find_variable(user_group, params)
break not_found!('GroupVariable') unless variable
present variable, with: Entities::Ci::Variable
end
- # rubocop: enable CodeReuse/ActiveRecord
desc 'Create a new variable in a group' do
success Entities::Ci::Variable
@@ -50,12 +49,19 @@ module API
optional :protected, type: String, desc: 'Whether the variable is protected'
optional :masked, type: String, desc: 'Whether the variable is masked'
optional :variable_type, type: String, values: ::Ci::GroupVariable.variable_types.keys, desc: 'The type of variable, must be one of env_var or file. Defaults to env_var'
+
+ use :optional_group_variable_params_ee
end
post ':id/variables' do
+ filtered_params = filter_variable_parameters(
+ user_group,
+ declared_params(include_missing: false)
+ )
+
variable = ::Ci::ChangeVariableService.new(
container: user_group,
current_user: current_user,
- params: { action: :create, variable_params: declared_params(include_missing: false) }
+ params: { action: :create, variable_params: filtered_params }
).execute
if variable.valid?
@@ -74,13 +80,19 @@ module API
optional :protected, type: String, desc: 'Whether the variable is protected'
optional :masked, type: String, desc: 'Whether the variable is masked'
optional :variable_type, type: String, values: ::Ci::GroupVariable.variable_types.keys, desc: 'The type of variable, must be one of env_var or file'
+
+ use :optional_group_variable_params_ee
end
- # rubocop: disable CodeReuse/ActiveRecord
put ':id/variables/:key' do
+ filtered_params = filter_variable_parameters(
+ user_group,
+ declared_params(include_missing: false)
+ )
+
variable = ::Ci::ChangeVariableService.new(
container: user_group,
current_user: current_user,
- params: { action: :update, variable_params: declared_params(include_missing: false) }
+ params: { action: :update, variable_params: filtered_params }
).execute
if variable.valid?
@@ -91,7 +103,6 @@ module API
rescue ::ActiveRecord::RecordNotFound
not_found!('GroupVariable')
end
- # rubocop: enable CodeReuse/ActiveRecord
desc 'Delete an existing variable from a group' do
success Entities::Ci::Variable
@@ -99,21 +110,18 @@ module API
params do
requires :key, type: String, desc: 'The key of the variable'
end
- # rubocop: disable CodeReuse/ActiveRecord
delete ':id/variables/:key' do
- variable = user_group.variables.find_by!(key: params[:key])
+ variable = find_variable(user_group, params)
+ break not_found!('GroupVariable') unless variable
destroy_conditionally!(variable) do |target_variable|
::Ci::ChangeVariableService.new(
container: user_group,
current_user: current_user,
- params: { action: :destroy, variable_params: declared_params(include_missing: false) }
+ params: { action: :destroy, variable: variable }
).execute
end
- rescue ::ActiveRecord::RecordNotFound
- not_found!('GroupVariable')
end
- # rubocop: enable CodeReuse/ActiveRecord
end
end
end
diff --git a/lib/api/groups.rb b/lib/api/groups.rb
index 26fa00d6186..912813d5bb7 100644
--- a/lib/api/groups.rb
+++ b/lib/api/groups.rb
@@ -52,9 +52,7 @@ module API
groups = groups.where.not(id: params[:skip_groups]) if params[:skip_groups].present?
order_options = { params[:order_by] => params[:sort] }
order_options["id"] ||= "asc"
- groups = groups.reorder(order_options)
-
- groups
+ groups.reorder(order_options)
end
# rubocop: enable CodeReuse/ActiveRecord
@@ -112,7 +110,6 @@ module API
end
def delete_group(group)
- Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab-foss/issues/46285')
destroy_conditionally!(group) do |group|
::Groups::DestroyService.new(group, current_user).async_execute
end
@@ -141,6 +138,10 @@ module API
def authorize_group_creation!
authorize! :create_group
end
+
+ def check_subscription!(group)
+ render_api_error!("This group can't be removed because it is linked to a subscription.", :bad_request) if group.paid?
+ end
end
resource :groups do
@@ -239,6 +240,7 @@ module API
delete ":id" do
group = find_group!(params[:id])
authorize! :admin_group, group
+ check_subscription! group
delete_group(group)
end
diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb
index 9db4a03c5b9..2d8a4f60e2a 100644
--- a/lib/api/helpers.rb
+++ b/lib/api/helpers.rb
@@ -3,6 +3,7 @@
module API
module Helpers
include Gitlab::Utils
+ include Helpers::Caching
include Helpers::Pagination
include Helpers::PaginationStrategies
@@ -48,7 +49,11 @@ module API
# Returns the job associated with the token provided for
# authentication, if any
def current_authenticated_job
- @current_authenticated_job
+ if try(:namespace_inheritable, :authentication)
+ ci_build_from_namespace_inheritable
+ else
+ @current_authenticated_job # rubocop:disable Gitlab/ModuleWithInstanceVariables
+ end
end
# rubocop:disable Gitlab/ModuleWithInstanceVariables
@@ -539,17 +544,6 @@ module API
end
end
- def track_event(action = action_name, **args)
- category = args.delete(:category) || self.options[:for].name
- raise "invalid category" unless category
-
- ::Gitlab::Tracking.event(category, action.to_s, **args)
- rescue => error
- Gitlab::AppLogger.warn(
- "Tracking event failed for action: #{action}, category: #{category}, message: #{error.message}"
- )
- end
-
def increment_counter(event_name)
feature_name = "usage_data_#{event_name}"
return unless Feature.enabled?(feature_name)
@@ -564,10 +558,6 @@ module API
def increment_unique_values(event_name, values)
return unless values.present?
- feature_flag = "usage_data_#{event_name}"
-
- return unless Feature.enabled?(feature_flag, default_enabled: true)
-
Gitlab::UsageDataCounters::HLLRedisCounter.track_event(event_name, values: values)
rescue => error
Gitlab::AppLogger.warn("Redis tracking event failed for event: #{event_name}, message: #{error.message}")
diff --git a/lib/api/helpers/authentication.rb b/lib/api/helpers/authentication.rb
index a6cfe930190..da11f07485b 100644
--- a/lib/api/helpers/authentication.rb
+++ b/lib/api/helpers/authentication.rb
@@ -52,6 +52,11 @@ module API
token&.user
end
+ def ci_build_from_namespace_inheritable
+ token = token_from_namespace_inheritable
+ token if token.is_a?(::Ci::Build)
+ end
+
private
def find_token_from_raw_credentials(token_types, raw)
diff --git a/lib/api/helpers/caching.rb b/lib/api/helpers/caching.rb
new file mode 100644
index 00000000000..d0f22109879
--- /dev/null
+++ b/lib/api/helpers/caching.rb
@@ -0,0 +1,137 @@
+# frozen_string_literal: true
+
+# Grape helpers for caching.
+#
+# This module helps introduce standardised caching into the Grape API
+# in a similar manner to the standard Grape DSL.
+
+module API
+ module Helpers
+ module Caching
+ # @return [ActiveSupport::Duration]
+ DEFAULT_EXPIRY = 1.day
+
+ # @return [ActiveSupport::Cache::Store]
+ def cache
+ Rails.cache
+ end
+
+ # This is functionally equivalent to the standard `#present` used in
+ # Grape endpoints, but the JSON for the object, or for each object of
+ # a collection, will be cached.
+ #
+ # With a collection all the keys will be fetched in a single call and the
+ # Entity rendered for those missing from the cache, which are then written
+ # back into it.
+ #
+ # Both the single object, and all objects inside a collection, must respond
+ # to `#cache_key`.
+ #
+ # To override the Grape formatter we return a custom wrapper in
+ # `Gitlab::Json::PrecompiledJson` which tells the `Gitlab::Json::GrapeFormatter`
+ # to export the string without conversion.
+ #
+ # A cache context can be supplied to add more context to the cache key. This
+ # defaults to including the `current_user` in every key for safety, unless overridden.
+ #
+ # @param obj_or_collection [Object, Enumerable<Object>] the object or objects to render
+ # @param with [Grape::Entity] the entity to use for rendering
+ # @param cache_context [Proc] a proc to call for each object to provide more context to the cache key
+ # @param expires_in [ActiveSupport::Duration, Integer] an expiry time for the cache entry
+ # @param presenter_args [Hash] keyword arguments to be passed to the entity
+ # @return [Gitlab::Json::PrecompiledJson]
+ def present_cached(obj_or_collection, with:, cache_context: -> (_) { current_user.cache_key }, expires_in: DEFAULT_EXPIRY, **presenter_args)
+ json =
+ if obj_or_collection.is_a?(Enumerable)
+ cached_collection(
+ obj_or_collection,
+ presenter: with,
+ presenter_args: presenter_args,
+ context: cache_context,
+ expires_in: expires_in
+ )
+ else
+ cached_object(
+ obj_or_collection,
+ presenter: with,
+ presenter_args: presenter_args,
+ context: cache_context,
+ expires_in: expires_in
+ )
+ end
+
+ body Gitlab::Json::PrecompiledJson.new(json)
+ end
+
+ private
+
+ # Optionally uses a `Proc` to add context to a cache key
+ #
+ # @param object [Object] must respond to #cache_key
+ # @param context [Proc] a proc that will be called with the object as an argument, and which should return a
+ # string or array of strings to be combined into the cache key
+ # @return [String]
+ def contextual_cache_key(object, context)
+ return object.cache_key if context.nil?
+
+ [object.cache_key, context.call(object)].flatten.join(":")
+ end
+
+ # Used for fetching or rendering a single object
+ #
+ # @param object [Object] the object to render
+ # @param presenter [Grape::Entity]
+ # @param presenter_args [Hash] keyword arguments to be passed to the entity
+ # @param context [Proc]
+ # @param expires_in [ActiveSupport::Duration, Integer] an expiry time for the cache entry
+ # @return [String]
+ def cached_object(object, presenter:, presenter_args:, context:, expires_in:)
+ cache.fetch(contextual_cache_key(object, context), expires_in: expires_in) do
+ Gitlab::Json.dump(presenter.represent(object, **presenter_args).as_json)
+ end
+ end
+
+ # Used for fetching or rendering multiple objects
+ #
+ # @param objects [Enumerable<Object>] the objects to render
+ # @param presenter [Grape::Entity]
+ # @param presenter_args [Hash] keyword arguments to be passed to the entity
+ # @param context [Proc]
+ # @param expires_in [ActiveSupport::Duration, Integer] an expiry time for the cache entry
+ # @return [Array<String>]
+ def cached_collection(collection, presenter:, presenter_args:, context:, expires_in:)
+ json = fetch_multi(collection, context: context, expires_in: expires_in) do |obj|
+ Gitlab::Json.dump(presenter.represent(obj, **presenter_args).as_json)
+ end
+
+ json.values
+ end
+
+ # An adapted version of ActiveSupport::Cache::Store#fetch_multi.
+ #
+ # The original method only provides the missing key to the block,
+ # not the missing object, so we have to create a map of cache keys
+ # to the objects to allow us to pass the object to the missing value
+ # block.
+ #
+ # The result is that this is functionally identical to `#fetch`.
+ def fetch_multi(*objs, context:, **kwargs)
+ objs.flatten!
+ map = multi_key_map(objs, context: context)
+
+ cache.fetch_multi(*map.keys, **kwargs) do |key|
+ yield map[key]
+ end
+ end
+
+ # @param objects [Enumerable<Object>] objects which _must_ respond to `#cache_key`
+ # @param context [Proc] a proc that can be called to help generate each cache key
+ # @return [Hash]
+ def multi_key_map(objects, context:)
+ objects.index_by do |object|
+ contextual_cache_key(object, context)
+ end
+ end
+ end
+ end
+end
diff --git a/lib/api/helpers/common_helpers.rb b/lib/api/helpers/common_helpers.rb
index a44fd4b0a5b..8940cf87f82 100644
--- a/lib/api/helpers/common_helpers.rb
+++ b/lib/api/helpers/common_helpers.rb
@@ -32,6 +32,10 @@ module API
end
end.compact.to_set
end
+
+ def endpoint_id
+ "#{request.request_method} #{route.origin}"
+ end
end
end
end
diff --git a/lib/api/helpers/graphql_helpers.rb b/lib/api/helpers/graphql_helpers.rb
index 3ddef0c16b3..4f7f85bd69d 100644
--- a/lib/api/helpers/graphql_helpers.rb
+++ b/lib/api/helpers/graphql_helpers.rb
@@ -6,8 +6,8 @@ module API
# against the graphql API. Helper code for the graphql server implementation
# should be in app/graphql/ or lib/gitlab/graphql/
module GraphqlHelpers
- def run_graphql!(query:, context: {}, transform: nil)
- result = GitlabSchema.execute(query, context: context)
+ def run_graphql!(query:, context: {}, variables: nil, transform: nil)
+ result = GitlabSchema.execute(query, variables: variables, context: context)
if transform
transform.call(result)
diff --git a/lib/api/helpers/notes_helpers.rb b/lib/api/helpers/notes_helpers.rb
index 71a18524104..cb938bc8a14 100644
--- a/lib/api/helpers/notes_helpers.rb
+++ b/lib/api/helpers/notes_helpers.rb
@@ -116,7 +116,7 @@ module API
end
def create_note(noteable, opts)
- whitelist_query_limiting
+ disable_query_limiting
authorize!(:create_note, noteable)
parent = noteable_parent(noteable)
@@ -144,8 +144,8 @@ module API
present discussion, with: Entities::Discussion
end
- def whitelist_query_limiting
- Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab/-/issues/211538')
+ def disable_query_limiting
+ Gitlab::QueryLimiting.disable!('https://gitlab.com/gitlab-org/gitlab/-/issues/211538')
end
end
end
diff --git a/lib/api/helpers/packages/conan/api_helpers.rb b/lib/api/helpers/packages/conan/api_helpers.rb
index d5f5448fd42..b18f52b5be6 100644
--- a/lib/api/helpers/packages/conan/api_helpers.rb
+++ b/lib/api/helpers/packages/conan/api_helpers.rb
@@ -14,7 +14,8 @@ module API
package,
current_user,
project,
- conan_package_reference: params[:conan_package_reference]
+ conan_package_reference: params[:conan_package_reference],
+ id: params[:id]
)
render_api_error!("No recipe manifest found", 404) if yield(presenter).empty?
@@ -31,19 +32,15 @@ module API
end
def recipe_upload_urls
- { upload_urls: Hash[
- file_names.select(&method(:recipe_file?)).map do |file_name|
- [file_name, build_recipe_file_upload_url(file_name)]
- end
- ] }
+ { upload_urls: file_names.select(&method(:recipe_file?)).to_h do |file_name|
+ [file_name, build_recipe_file_upload_url(file_name)]
+ end }
end
def package_upload_urls
- { upload_urls: Hash[
- file_names.select(&method(:package_file?)).map do |file_name|
- [file_name, build_package_file_upload_url(file_name)]
- end
- ] }
+ { upload_urls: file_names.select(&method(:package_file?)).to_h do |file_name|
+ [file_name, build_package_file_upload_url(file_name)]
+ end }
end
def recipe_file?(file_name)
@@ -212,10 +209,8 @@ module API
end
def find_personal_access_token
- personal_access_token = find_personal_access_token_from_conan_jwt ||
+ find_personal_access_token_from_conan_jwt ||
find_personal_access_token_from_http_basic_auth
-
- personal_access_token
end
def find_user_from_job_token
diff --git a/lib/api/helpers/packages/dependency_proxy_helpers.rb b/lib/api/helpers/packages/dependency_proxy_helpers.rb
index 577ba97d68a..989c4e1761b 100644
--- a/lib/api/helpers/packages/dependency_proxy_helpers.rb
+++ b/lib/api/helpers/packages/dependency_proxy_helpers.rb
@@ -10,7 +10,7 @@ module API
def redirect_registry_request(forward_to_registry, package_type, options)
if forward_to_registry && redirect_registry_request_available?
- track_event("#{package_type}_request_forward")
+ ::Gitlab::Tracking.event(self.options[:for].name, "#{package_type}_request_forward")
redirect(registry_url(package_type, options))
else
yield
diff --git a/lib/api/helpers/packages_helpers.rb b/lib/api/helpers/packages_helpers.rb
index e1898d28ef7..2221eec0f82 100644
--- a/lib/api/helpers/packages_helpers.rb
+++ b/lib/api/helpers/packages_helpers.rb
@@ -50,7 +50,8 @@ module API
def track_package_event(event_name, scope, **args)
::Packages::CreateEventService.new(nil, current_user, event_name: event_name, scope: scope).execute
- track_event(event_name, **args)
+ category = args.delete(:category) || self.options[:for].name
+ ::Gitlab::Tracking.event(category, event_name.to_s, **args)
end
end
end
diff --git a/lib/api/helpers/runner.rb b/lib/api/helpers/runner.rb
index 39586483990..688cd2da994 100644
--- a/lib/api/helpers/runner.rb
+++ b/lib/api/helpers/runner.rb
@@ -38,10 +38,17 @@ module API
end
end
+ # HTTP status codes to terminate the job on GitLab Runner:
+ # - 403
def authenticate_job!(require_running: true)
job = current_job
- not_found! unless job
+ # 404 is not returned here because we want to terminate the job if it's
+ # running. A 404 can be returned from anywhere in the networking stack which is why
+ # we are explicit about a 403, we should improve this in
+ # https://gitlab.com/gitlab-org/gitlab/-/issues/327703
+ forbidden! unless job
+
forbidden! unless job_token_valid?(job)
forbidden!('Project has been deleted!') if job.project.nil? || job.project.pending_delete?
diff --git a/lib/api/helpers/services_helpers.rb b/lib/api/helpers/services_helpers.rb
index ed3d694f006..2f2ad88c942 100644
--- a/lib/api/helpers/services_helpers.rb
+++ b/lib/api/helpers/services_helpers.rb
@@ -394,7 +394,7 @@ module API
required: true,
name: :external_wiki_url,
type: String,
- desc: 'The URL of the external Wiki'
+ desc: 'The URL of the external wiki'
}
],
'flowdock' => [
@@ -543,9 +543,15 @@ module API
},
{
required: false,
+ name: :jira_issue_transition_automatic,
+ type: Boolean,
+ desc: 'Enable automatic issue transitions'
+ },
+ {
+ required: false,
name: :jira_issue_transition_id,
type: String,
- desc: 'The ID of a transition that moves issues to a closed state. You can find this number under the Jira workflow administration (**Administration > Issues > Workflows**) by selecting **View** under **Operations** of the desired workflow of your project. The ID of each state can be found inside the parenthesis of each transition name under the **Transitions (id)** column ([see screenshot][trans]). By default, this ID is set to `2`'
+ desc: 'The ID of one or more transitions for custom issue transitions'
},
{
required: false,
diff --git a/lib/api/helpers/variables_helpers.rb b/lib/api/helpers/variables_helpers.rb
new file mode 100644
index 00000000000..e2b3372fc33
--- /dev/null
+++ b/lib/api/helpers/variables_helpers.rb
@@ -0,0 +1,27 @@
+# frozen_string_literal: true
+
+module API
+ module Helpers
+ module VariablesHelpers
+ extend ActiveSupport::Concern
+ extend Grape::API::Helpers
+
+ params :optional_group_variable_params_ee do
+ end
+
+ def filter_variable_parameters(_, params)
+ params # Overridden in EE
+ end
+
+ def find_variable(owner, params)
+ variables = ::Ci::VariablesFinder.new(owner, params).execute.to_a
+
+ return variables.first unless variables.many? # rubocop: disable CodeReuse/ActiveRecord
+
+ conflict!("There are multiple variables with provided parameters. Please use 'filter[environment_scope]'")
+ end
+ end
+ end
+end
+
+API::Helpers::VariablesHelpers.prepend_if_ee('EE::API::Helpers::VariablesHelpers')
diff --git a/lib/api/internal/base.rb b/lib/api/internal/base.rb
index a3fee49cd8f..4dcfc0cf7eb 100644
--- a/lib/api/internal/base.rb
+++ b/lib/api/internal/base.rb
@@ -15,7 +15,7 @@ module API
Gitlab::ApplicationContext.push(
user: -> { actor&.user },
project: -> { project },
- caller_id: route.origin,
+ caller_id: api_endpoint.endpoint_id,
remote_ip: request.ip,
feature_category: feature_category
)
@@ -23,7 +23,7 @@ module API
helpers ::API::Helpers::InternalHelpers
- UNKNOWN_CHECK_RESULT_ERROR = 'Unknown check result'.freeze
+ UNKNOWN_CHECK_RESULT_ERROR = 'Unknown check result'
VALID_PAT_SCOPES = Set.new(
Gitlab::Auth::API_SCOPES + Gitlab::Auth::REPOSITORY_SCOPES + Gitlab::Auth::REGISTRY_SCOPES
@@ -52,20 +52,20 @@ module API
actor.update_last_used_at!
check_result = begin
- Gitlab::Auth::CurrentUserMode.bypass_session!(actor.user&.id) do
- access_check!(actor, params)
- end
- rescue Gitlab::GitAccess::ForbiddenError => e
- # The return code needs to be 401. If we return 403
- # the custom message we return won't be shown to the user
- # and, instead, the default message 'GitLab: API is not accessible'
- # will be displayed
- return response_with_status(code: 401, success: false, message: e.message)
- rescue Gitlab::GitAccess::TimeoutError => e
- return response_with_status(code: 503, success: false, message: e.message)
- rescue Gitlab::GitAccess::NotFoundError => e
- return response_with_status(code: 404, success: false, message: e.message)
- end
+ with_admin_mode_bypass!(actor.user&.id) do
+ access_check!(actor, params)
+ end
+ rescue Gitlab::GitAccess::ForbiddenError => e
+ # The return code needs to be 401. If we return 403
+ # the custom message we return won't be shown to the user
+ # and, instead, the default message 'GitLab: API is not accessible'
+ # will be displayed
+ return response_with_status(code: 401, success: false, message: e.message)
+ rescue Gitlab::GitAccess::TimeoutError => e
+ return response_with_status(code: 503, success: false, message: e.message)
+ rescue Gitlab::GitAccess::NotFoundError => e
+ return response_with_status(code: 404, success: false, message: e.message)
+ end
log_user_activity(actor.user)
@@ -109,9 +109,7 @@ module API
end
end
- def validate_actor_key(actor, key_id)
- return 'Could not find a user without a key' unless key_id
-
+ def validate_actor(actor)
return 'Could not find the given key' unless actor.key
'Could not find a user for the given key' unless actor.user
@@ -120,6 +118,19 @@ module API
def two_factor_otp_check
{ success: false, message: 'Feature is not available' }
end
+
+ def with_admin_mode_bypass!(actor_id)
+ return yield unless Gitlab::CurrentSettings.admin_mode
+
+ Gitlab::Auth::CurrentUserMode.bypass_session!(actor_id) do
+ yield
+ end
+ end
+
+ # Overridden in EE
+ def geo_proxy
+ {}
+ end
end
namespace 'internal' do
@@ -193,7 +204,7 @@ module API
actor.update_last_used_at!
user = actor.user
- error_message = validate_actor_key(actor, params[:key_id])
+ error_message = validate_actor(actor)
if params[:user_id] && user.nil?
break { success: false, message: 'Could not find the given user' }
@@ -222,7 +233,7 @@ module API
actor.update_last_used_at!
user = actor.user
- error_message = validate_actor_key(actor, params[:key_id])
+ error_message = validate_actor(actor)
break { success: false, message: 'Deploy keys cannot be used to create personal access tokens' } if actor.key.is_a?(DeployKey)
@@ -295,7 +306,7 @@ module API
actor.update_last_used_at!
user = actor.user
- error_message = validate_actor_key(actor, params[:key_id])
+ error_message = validate_actor(actor)
if error_message
{ success: false, message: error_message }
@@ -314,6 +325,12 @@ module API
two_factor_otp_check
end
+
+ # Workhorse calls this to determine if it is a Geo secondary site
+ # that should proxy requests. FOSS can quickly return empty data.
+ get '/geo_proxy', feature_category: :geo_replication do
+ geo_proxy
+ end
end
end
end
diff --git a/lib/api/internal/kubernetes.rb b/lib/api/internal/kubernetes.rb
index 87ad79d601f..af2c53dd778 100644
--- a/lib/api/internal/kubernetes.rb
+++ b/lib/api/internal/kubernetes.rb
@@ -13,7 +13,7 @@ module API
helpers do
def authenticate_gitlab_kas_request!
- unauthorized! unless Gitlab::Kas.verify_api_request(headers)
+ render_api_error!('KAS JWT authentication invalid', 401) unless Gitlab::Kas.verify_api_request(headers)
end
def agent_token
@@ -51,9 +51,11 @@ module API
end
def check_agent_token
- forbidden! unless agent_token
+ unauthorized! unless agent_token
forbidden! unless Gitlab::Kas.included_in_gitlab_com_rollout?(agent.project)
+
+ agent_token.track_usage
end
end
diff --git a/lib/api/invitations.rb b/lib/api/invitations.rb
index 52c32b4d1cf..0d562cc18f8 100644
--- a/lib/api/invitations.rb
+++ b/lib/api/invitations.rb
@@ -25,11 +25,11 @@ module API
optional :expires_at, type: DateTime, desc: 'Date string in the format YEAR-MONTH-DAY'
end
post ":id/invitations" do
- source = find_source(source_type, params[:id])
+ params[:source] = find_source(source_type, params[:id])
- authorize_admin_source!(source_type, source)
+ authorize_admin_source!(source_type, params[:source])
- ::Members::InviteService.new(current_user, params).execute(source)
+ ::Members::InviteService.new(current_user, params).execute
end
desc 'Get a list of group or project invitations viewable by the authenticated user' do
diff --git a/lib/api/issue_links.rb b/lib/api/issue_links.rb
index e938dbbae87..1cd5bde224b 100644
--- a/lib/api/issue_links.rb
+++ b/lib/api/issue_links.rb
@@ -18,7 +18,10 @@ module API
end
get ':id/issues/:issue_iid/links' do
source_issue = find_project_issue(params[:issue_iid])
- related_issues = source_issue.related_issues(current_user)
+ related_issues = source_issue.related_issues(current_user) do |issues|
+ issues.with_api_entity_associations.preload_awardable
+ end
+ related_issues.each { |issue| issue.lazy_subscription(current_user, user_project) } # preload subscriptions
present related_issues,
with: Entities::RelatedIssue,
diff --git a/lib/api/issues.rb b/lib/api/issues.rb
index 13dac1c174c..4f2ac73c0d3 100644
--- a/lib/api/issues.rb
+++ b/lib/api/issues.rb
@@ -242,7 +242,7 @@ module API
use :issue_params
end
post ':id/issues' do
- Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab-foss/issues/42320')
+ Gitlab::QueryLimiting.disable!('https://gitlab.com/gitlab-org/gitlab/-/issues/21140')
check_rate_limit! :issues_create, [current_user]
@@ -288,7 +288,7 @@ module API
end
# rubocop: disable CodeReuse/ActiveRecord
put ':id/issues/:issue_iid' do
- Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab-foss/issues/42322')
+ Gitlab::QueryLimiting.disable!('https://gitlab.com/gitlab-org/gitlab/-/issues/20775')
issue = user_project.issues.find_by!(iid: params.delete(:issue_iid))
authorize! :update_issue, issue
@@ -346,7 +346,7 @@ module API
end
# rubocop: disable CodeReuse/ActiveRecord
post ':id/issues/:issue_iid/move' do
- Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab-foss/issues/42323')
+ Gitlab::QueryLimiting.disable!('https://gitlab.com/gitlab-org/gitlab/-/issues/20776')
issue = user_project.issues.find_by(iid: params[:issue_iid])
not_found!('Issue') unless issue
diff --git a/lib/api/jobs.rb b/lib/api/jobs.rb
index 7390219b60e..54951f9bd01 100644
--- a/lib/api/jobs.rb
+++ b/lib/api/jobs.rb
@@ -6,8 +6,6 @@ module API
before { authenticate! }
- feature_category :continuous_integration
-
resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do
params do
requires :id, type: String, desc: 'The ID of a project'
@@ -40,7 +38,7 @@ module API
use :pagination
end
# rubocop: disable CodeReuse/ActiveRecord
- get ':id/jobs' do
+ get ':id/jobs', feature_category: :continuous_integration do
authorize_read_builds!
builds = user_project.builds.order('id DESC')
@@ -57,7 +55,7 @@ module API
params do
requires :job_id, type: Integer, desc: 'The ID of a job'
end
- get ':id/jobs/:job_id' do
+ get ':id/jobs/:job_id', feature_category: :continuous_integration do
authorize_read_builds!
build = find_build!(params[:job_id])
@@ -72,7 +70,7 @@ module API
params do
requires :job_id, type: Integer, desc: 'The ID of a job'
end
- get ':id/jobs/:job_id/trace' do
+ get ':id/jobs/:job_id/trace', feature_category: :continuous_integration do
authorize_read_builds!
build = find_build!(params[:job_id])
@@ -94,7 +92,7 @@ module API
params do
requires :job_id, type: Integer, desc: 'The ID of a job'
end
- post ':id/jobs/:job_id/cancel' do
+ post ':id/jobs/:job_id/cancel', feature_category: :continuous_integration do
authorize_update_builds!
build = find_build!(params[:job_id])
@@ -111,7 +109,7 @@ module API
params do
requires :job_id, type: Integer, desc: 'The ID of a build'
end
- post ':id/jobs/:job_id/retry' do
+ post ':id/jobs/:job_id/retry', feature_category: :continuous_integration do
authorize_update_builds!
build = find_build!(params[:job_id])
@@ -129,7 +127,7 @@ module API
params do
requires :job_id, type: Integer, desc: 'The ID of a build'
end
- post ':id/jobs/:job_id/erase' do
+ post ':id/jobs/:job_id/erase', feature_category: :continuous_integration do
authorize_update_builds!
build = find_build!(params[:job_id])
@@ -148,7 +146,7 @@ module API
requires :job_id, type: Integer, desc: 'The ID of a Job'
end
- post ":id/jobs/:job_id/play" do
+ post ":id/jobs/:job_id/play", feature_category: :continuous_integration do
authorize_read_builds!
job = find_job!(params[:job_id])
@@ -174,10 +172,8 @@ module API
success Entities::Ci::Job
end
route_setting :authentication, job_token_allowed: true
- get do
- # current_authenticated_job will be nil if user is using
- # a valid authentication that is not CI_JOB_TOKEN
- not_found!('Job') unless current_authenticated_job
+ get '', feature_category: :continuous_integration do
+ validate_current_authenticated_job
present current_authenticated_job, with: Entities::Ci::Job
end
@@ -196,6 +192,14 @@ module API
builds.where(status: available_statuses && scope)
end
# rubocop: enable CodeReuse/ActiveRecord
+
+ def validate_current_authenticated_job
+ # current_authenticated_job will be nil if user is using
+ # a valid authentication (like PRIVATE-TOKEN) that is not CI_JOB_TOKEN
+ not_found!('Job') unless current_authenticated_job
+ end
end
end
end
+
+API::Jobs.prepend_if_ee('EE::API::Jobs')
diff --git a/lib/api/maven_packages.rb b/lib/api/maven_packages.rb
index 4a5b2ead163..bd1d984719e 100644
--- a/lib/api/maven_packages.rb
+++ b/lib/api/maven_packages.rb
@@ -23,6 +23,15 @@ module API
helpers ::API::Helpers::PackagesHelpers
helpers do
+ def path_exists?(path)
+ # return true when FF disabled so that processing the request is not stopped
+ return true unless Feature.enabled?(:check_maven_path_first)
+ return false if path.blank?
+
+ Packages::Maven::Metadatum.with_path(path)
+ .exists?
+ end
+
def extract_format(file_name)
name, _, format = file_name.rpartition('.')
@@ -77,6 +86,22 @@ module API
request.head? &&
file.fog_credentials[:provider] == 'AWS'
end
+
+ def fetch_package(file_name:, project: nil, group: nil)
+ order_by_package_file = false
+ if Feature.enabled?(:maven_packages_group_level_improvements, default_enabled: :yaml)
+ order_by_package_file = file_name.include?(::Packages::Maven::Metadata.filename) &&
+ !params[:path].include?(::Packages::Maven::FindOrCreatePackageService::SNAPSHOT_TERM)
+ end
+
+ ::Packages::Maven::PackageFinder.new(
+ params[:path],
+ current_user,
+ project: project,
+ group: group,
+ order_by_package_file: order_by_package_file
+ ).execute!
+ end
end
desc 'Download the maven package file at instance level' do
@@ -88,6 +113,9 @@ module API
end
route_setting :authentication, job_token_allowed: true, deploy_token_allowed: true
get 'packages/maven/*path/:file_name', requirements: MAVEN_ENDPOINT_REQUIREMENTS do
+ # return a similar failure to authorize_read_package!(project)
+ forbidden! unless path_exists?(params[:path])
+
file_name, format = extract_format(params[:file_name])
# To avoid name collision we require project path and project package be the same.
@@ -97,8 +125,7 @@ module API
authorize_read_package!(project)
- package = ::Packages::Maven::PackageFinder
- .new(params[:path], current_user, project: project).execute!
+ package = fetch_package(file_name: file_name, project: project)
package_file = ::Packages::PackageFileFinder
.new(package, file_name).execute!
@@ -127,14 +154,16 @@ module API
end
route_setting :authentication, job_token_allowed: true, deploy_token_allowed: true
get ':id/-/packages/maven/*path/:file_name', requirements: MAVEN_ENDPOINT_REQUIREMENTS do
+ # return a similar failure to group = find_group(params[:id])
+ not_found!('Group') unless path_exists?(params[:path])
+
file_name, format = extract_format(params[:file_name])
group = find_group(params[:id])
not_found!('Group') unless can?(current_user, :read_group, group)
- package = ::Packages::Maven::PackageFinder
- .new(params[:path], current_user, group: group).execute!
+ package = fetch_package(file_name: file_name, group: group)
authorize_read_package!(package.project)
@@ -167,12 +196,14 @@ module API
end
route_setting :authentication, job_token_allowed: true, deploy_token_allowed: true
get ':id/packages/maven/*path/:file_name', requirements: MAVEN_ENDPOINT_REQUIREMENTS do
+ # return a similar failure to user_project
+ not_found!('Project') unless path_exists?(params[:path])
+
authorize_read_package!(user_project)
file_name, format = extract_format(params[:file_name])
- package = ::Packages::Maven::PackageFinder
- .new(params[:path], current_user, project: user_project).execute!
+ package = fetch_package(file_name: file_name, project: user_project)
package_file = ::Packages::PackageFileFinder
.new(package, file_name).execute!
diff --git a/lib/api/members.rb b/lib/api/members.rb
index 42f608102b3..aaf0e3e1927 100644
--- a/lib/api/members.rb
+++ b/lib/api/members.rb
@@ -100,9 +100,9 @@ module API
authorize_admin_source!(source_type, source)
if params[:user_id].to_s.include?(',')
- create_service_params = params.except(:user_id).merge({ user_ids: params[:user_id] })
+ create_service_params = params.except(:user_id).merge({ user_ids: params[:user_id], source: source })
- ::Members::CreateService.new(current_user, create_service_params).execute(source)
+ ::Members::CreateService.new(current_user, create_service_params).execute
elsif params[:user_id].present?
member = source.members.find_by(user_id: params[:user_id])
conflict!('Member already exists') if member
@@ -155,6 +155,8 @@ module API
desc 'Removes a user from a group or project.'
params do
requires :user_id, type: Integer, desc: 'The user ID of the member'
+ optional :skip_subresources, type: Boolean, default: false,
+ desc: 'Flag indicating if the deletion of direct memberships of the removed member in subgroups and projects should be skipped'
optional :unassign_issuables, type: Boolean, default: false,
desc: 'Flag indicating if the removed member should be unassigned from any issues or merge requests within given group or project'
end
@@ -164,7 +166,7 @@ module API
member = source_members(source).find_by!(user_id: params[:user_id])
destroy_conditionally!(member) do
- ::Members::DestroyService.new(current_user).execute(member, unassign_issuables: params[:unassign_issuables])
+ ::Members::DestroyService.new(current_user).execute(member, skip_subresources: params[:skip_subresources], unassign_issuables: params[:unassign_issuables])
end
end
# rubocop: enable CodeReuse/ActiveRecord
diff --git a/lib/api/merge_request_diffs.rb b/lib/api/merge_request_diffs.rb
index 97a6c7075b3..470f78a7dc2 100644
--- a/lib/api/merge_request_diffs.rb
+++ b/lib/api/merge_request_diffs.rb
@@ -45,7 +45,7 @@ module API
merge_request = find_merge_request_with_access(params[:merge_request_iid])
- present merge_request.merge_request_diffs.find(params[:version_id]), with: Entities::MergeRequestDiffFull
+ present_cached merge_request.merge_request_diffs.find(params[:version_id]), with: Entities::MergeRequestDiffFull, cache_context: nil
end
end
end
diff --git a/lib/api/merge_requests.rb b/lib/api/merge_requests.rb
index 5051c1a5529..613de514ffa 100644
--- a/lib/api/merge_requests.rb
+++ b/lib/api/merge_requests.rb
@@ -8,11 +8,20 @@ module API
before { authenticate_non_get! }
- feature_category :code_review
-
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
+ # endpoints, we need to define them at the top-level by route.
+ feature_category :code_review, [
+ '/projects/:id/merge_requests/:merge_request_iid/time_estimate',
+ '/projects/:id/merge_requests/:merge_request_iid/reset_time_estimate',
+ '/projects/:id/merge_requests/:merge_request_iid/add_spent_time',
+ '/projects/:id/merge_requests/:merge_request_iid/reset_spent_time',
+ '/projects/:id/merge_requests/:merge_request_iid/time_stats'
+ ]
+
# EE::API::MergeRequests would override the following helpers
helpers do
params :optional_params_ee do
@@ -125,7 +134,7 @@ module API
use :merge_requests_params
use :optional_scope_param
end
- get do
+ get feature_category: :code_review do
authenticate! unless params[:scope] == 'all'
merge_requests = find_merge_requests
@@ -145,7 +154,7 @@ module API
optional :non_archived, type: Boolean, desc: 'Return merge requests from non archived projects',
default: true
end
- get ":id/merge_requests" do
+ get ":id/merge_requests", feature_category: :code_review do
merge_requests = find_merge_requests(group_id: user_group.id, include_subgroups: true)
present merge_requests, serializer_options_for(merge_requests).merge(group: user_group)
@@ -184,7 +193,7 @@ module API
use :merge_requests_params
optional :iids, type: Array[Integer], coerce_with: ::API::Validations::Types::CommaSeparatedToIntegerArray.coerce, desc: 'The IID array of merge requests'
end
- get ":id/merge_requests" do
+ get ":id/merge_requests", feature_category: :code_review do
authorize! :read_merge_request, user_project
merge_requests = find_merge_requests(project_id: user_project.id)
@@ -206,8 +215,8 @@ module API
desc: 'The target project of the merge request defaults to the :id of the project'
use :optional_params
end
- post ":id/merge_requests" do
- Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab-foss/issues/42316')
+ post ":id/merge_requests", feature_category: :code_review do
+ Gitlab::QueryLimiting.disable!('https://gitlab.com/gitlab-org/gitlab/-/issues/20770')
authorize! :create_merge_request_from, user_project
@@ -228,7 +237,7 @@ module API
params do
requires :merge_request_iid, type: Integer, desc: 'The IID of a merge request'
end
- delete ":id/merge_requests/:merge_request_iid" do
+ delete ":id/merge_requests/:merge_request_iid", feature_category: :code_review do
merge_request = find_project_merge_request(params[:merge_request_iid])
authorize!(:destroy_merge_request, merge_request)
@@ -247,7 +256,7 @@ module API
desc 'Get a single merge request' do
success Entities::MergeRequest
end
- get ':id/merge_requests/:merge_request_iid' do
+ get ':id/merge_requests/:merge_request_iid', feature_category: :code_review do
not_found!("Merge Request") unless can?(current_user, :read_merge_request, user_project)
merge_request = find_merge_request_with_access(params[:merge_request_iid])
@@ -265,7 +274,7 @@ module API
desc 'Get the participants of a merge request' do
success Entities::UserBasic
end
- get ':id/merge_requests/:merge_request_iid/participants' do
+ get ':id/merge_requests/:merge_request_iid/participants', feature_category: :code_review do
not_found!("Merge Request") unless can?(current_user, :read_merge_request, user_project)
merge_request = find_merge_request_with_access(params[:merge_request_iid])
@@ -278,7 +287,7 @@ module API
desc 'Get the commits of a merge request' do
success Entities::Commit
end
- get ':id/merge_requests/:merge_request_iid/commits' do
+ get ':id/merge_requests/:merge_request_iid/commits', feature_category: :code_review do
not_found!("Merge Request") unless can?(current_user, :read_merge_request, user_project)
merge_request = find_merge_request_with_access(params[:merge_request_iid])
@@ -293,7 +302,7 @@ module API
desc 'Get the context commits of a merge request' do
success Entities::Commit
end
- get ':id/merge_requests/:merge_request_iid/context_commits' do
+ get ':id/merge_requests/:merge_request_iid/context_commits', feature_category: :code_review do
merge_request = find_merge_request_with_access(params[:merge_request_iid])
project = merge_request.project
@@ -311,7 +320,7 @@ module API
desc 'create context commits of merge request' do
success Entities::Commit
end
- post ':id/merge_requests/:merge_request_iid/context_commits' do
+ post ':id/merge_requests/:merge_request_iid/context_commits', feature_category: :code_review do
commit_ids = params[:commits]
if commit_ids.size > CONTEXT_COMMITS_POST_LIMIT
@@ -339,7 +348,7 @@ module API
requires :commits, type: Array[String], coerce_with: ::API::Validations::Types::CommaSeparatedToArray.coerce, allow_blank: false, desc: 'List of context commits sha'
end
desc 'remove context commits of merge request'
- delete ':id/merge_requests/:merge_request_iid/context_commits' do
+ delete ':id/merge_requests/:merge_request_iid/context_commits', feature_category: :code_review do
commit_ids = params[:commits]
merge_request = find_merge_request_with_access(params[:merge_request_iid])
project = merge_request.project
@@ -361,7 +370,7 @@ module API
desc 'Show the merge request changes' do
success Entities::MergeRequestChanges
end
- get ':id/merge_requests/:merge_request_iid/changes' do
+ get ':id/merge_requests/:merge_request_iid/changes', feature_category: :code_review do
not_found!("Merge Request") unless can?(current_user, :read_merge_request, user_project)
merge_request = find_merge_request_with_access(params[:merge_request_iid])
@@ -376,7 +385,7 @@ module API
desc 'Get the merge request pipelines' do
success Entities::Ci::PipelineBasic
end
- get ':id/merge_requests/:merge_request_iid/pipelines' do
+ get ':id/merge_requests/:merge_request_iid/pipelines', feature_category: :continuous_integration do
pipelines = merge_request_pipelines_with_access
not_found!("Merge Request") unless can?(current_user, :read_merge_request, user_project)
@@ -387,7 +396,7 @@ module API
desc 'Create a pipeline for merge request' do
success ::API::Entities::Ci::Pipeline
end
- post ':id/merge_requests/:merge_request_iid/pipelines' do
+ post ':id/merge_requests/:merge_request_iid/pipelines', feature_category: :continuous_integration do
pipeline = ::MergeRequests::CreatePipelineService
.new(user_project, current_user, allow_duplicate: true)
.execute(find_merge_request_with_access(params[:merge_request_iid]))
@@ -415,8 +424,8 @@ module API
use :optional_params
at_least_one_of(*::API::MergeRequests.update_params_at_least_one_of)
end
- put ':id/merge_requests/:merge_request_iid' do
- Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab-foss/issues/42318')
+ put ':id/merge_requests/:merge_request_iid', feature_category: :code_review do
+ Gitlab::QueryLimiting.disable!('https://gitlab.com/gitlab-org/gitlab/-/issues/20772')
merge_request = find_merge_request_with_access(params.delete(:merge_request_iid), :update_merge_request)
@@ -424,7 +433,13 @@ module API
mr_params[:force_remove_source_branch] = mr_params.delete(:remove_source_branch) if mr_params.has_key?(:remove_source_branch)
mr_params = convert_parameters_from_legacy_format(mr_params)
- merge_request = ::MergeRequests::UpdateService.new(user_project, current_user, mr_params).execute(merge_request)
+ service = if mr_params.one? && (mr_params.keys & %i[assignee_id assignee_ids]).one?
+ ::MergeRequests::UpdateAssigneesService
+ else
+ ::MergeRequests::UpdateService
+ end
+
+ merge_request = service.new(user_project, current_user, mr_params).execute(merge_request)
handle_merge_request_errors!(merge_request)
@@ -444,8 +459,8 @@ module API
optional :sha, type: String, desc: 'When present, must have the HEAD SHA of the source branch'
optional :squash, type: Grape::API::Boolean, desc: 'When true, the commits will be squashed into a single commit on merge'
end
- put ':id/merge_requests/:merge_request_iid/merge' do
- Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab-foss/issues/42317')
+ put ':id/merge_requests/:merge_request_iid/merge', feature_category: :code_review do
+ Gitlab::QueryLimiting.disable!('https://gitlab.com/gitlab-org/gitlab/-/issues/4796')
merge_request = find_project_merge_request(params[:merge_request_iid])
@@ -485,7 +500,7 @@ module API
end
desc 'Returns the up to date merge-ref HEAD commit'
- get ':id/merge_requests/:merge_request_iid/merge_ref' do
+ get ':id/merge_requests/:merge_request_iid/merge_ref', feature_category: :code_review do
merge_request = find_project_merge_request(params[:merge_request_iid])
result = ::MergeRequests::MergeabilityCheckService.new(merge_request).execute(recheck: true)
@@ -500,7 +515,7 @@ module API
desc 'Cancel merge if "Merge When Pipeline Succeeds" is enabled' do
success Entities::MergeRequest
end
- post ':id/merge_requests/:merge_request_iid/cancel_merge_when_pipeline_succeeds' do
+ post ':id/merge_requests/:merge_request_iid/cancel_merge_when_pipeline_succeeds', feature_category: :code_review do
merge_request = find_project_merge_request(params[:merge_request_iid])
unauthorized! unless merge_request.can_cancel_auto_merge?(current_user)
@@ -514,7 +529,7 @@ module API
params do
optional :skip_ci, type: Boolean, desc: 'Do not create CI pipeline'
end
- put ':id/merge_requests/:merge_request_iid/rebase' do
+ put ':id/merge_requests/:merge_request_iid/rebase', feature_category: :code_review do
merge_request = find_project_merge_request(params[:merge_request_iid])
authorize_push_to_merge_request!(merge_request)
@@ -533,7 +548,7 @@ module API
params do
use :pagination
end
- get ':id/merge_requests/:merge_request_iid/closes_issues' do
+ get ':id/merge_requests/:merge_request_iid/closes_issues', feature_category: :code_review do
merge_request = find_merge_request_with_access(params[:merge_request_iid])
issues = ::Kaminari.paginate_array(merge_request.visible_closing_issues_for(current_user))
issues = paginate(issues)
diff --git a/lib/api/milestone_responses.rb b/lib/api/milestone_responses.rb
index b337b992841..d75ed3a48d7 100644
--- a/lib/api/milestone_responses.rb
+++ b/lib/api/milestone_responses.rb
@@ -80,7 +80,7 @@ module API
params = build_finder_params(milestone, parent)
- issuables = finder_klass.new(current_user, params).execute
+ issuables = finder_klass.new(current_user, params).execute.with_api_entity_associations
present paginate(issuables), with: entity, current_user: current_user
end
diff --git a/lib/api/namespaces.rb b/lib/api/namespaces.rb
index 25a901c18b6..465d2f23e9d 100644
--- a/lib/api/namespaces.rb
+++ b/lib/api/namespaces.rb
@@ -56,6 +56,23 @@ module API
present user_namespace, with: Entities::Namespace, current_user: current_user
end
+
+ desc 'Get existence of a namespace including alternative suggestions' do
+ success Entities::NamespaceExistence
+ end
+ params do
+ requires :namespace, type: String, desc: "Namespace's path"
+ optional :parent_id, type: Integer, desc: "The ID of the parent namespace. If no ID is specified, only top-level namespaces are considered."
+ end
+ get ':namespace/exists', requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do
+ namespace_path = params[:namespace]
+
+ exists = Namespace.by_parent(params[:parent_id]).filter_by_path(namespace_path).exists?
+ suggestions = exists ? [Namespace.clean_path(namespace_path)] : []
+
+ present :exists, exists
+ present :suggests, suggestions
+ end
end
end
end
diff --git a/lib/api/project_import.rb b/lib/api/project_import.rb
index 15b06cea385..5f3a574eeee 100644
--- a/lib/api/project_import.rb
+++ b/lib/api/project_import.rb
@@ -67,7 +67,7 @@ module API
check_rate_limit! :project_import, [current_user, :project_import]
- Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab-foss/issues/42437')
+ Gitlab::QueryLimiting.disable!('https://gitlab.com/gitlab-org/gitlab/-/issues/20823')
validate_file!
diff --git a/lib/api/projects.rb b/lib/api/projects.rb
index 19b63c28f89..92f6970e6fc 100644
--- a/lib/api/projects.rb
+++ b/lib/api/projects.rb
@@ -13,6 +13,8 @@ module API
feature_category :projects, ['/projects/:id/custom_attributes', '/projects/:id/custom_attributes/:key']
+ PROJECT_ATTACHMENT_SIZE_EXEMPT = 1.gigabyte
+
helpers do
# EE::API::Projects would override this method
def apply_filters(projects)
@@ -52,6 +54,29 @@ module API
accepted!
end
+
+ def exempt_from_global_attachment_size?(user_project)
+ list = ::Gitlab::RackAttack::UserAllowlist.new(ENV['GITLAB_UPLOAD_API_ALLOWLIST'])
+ list.include?(user_project.id)
+ end
+
+ # Temporarily introduced for upload API: https://gitlab.com/gitlab-org/gitlab/-/issues/325788
+ def project_attachment_size(user_project)
+ return PROJECT_ATTACHMENT_SIZE_EXEMPT if exempt_from_global_attachment_size?(user_project)
+ return user_project.max_attachment_size if Feature.enabled?(:enforce_max_attachment_size_upload_api, user_project)
+
+ PROJECT_ATTACHMENT_SIZE_EXEMPT
+ end
+
+ # This is to help determine which projects to use in https://gitlab.com/gitlab-org/gitlab/-/issues/325788
+ def log_if_upload_exceed_max_size(user_project, file)
+ return if file.size <= user_project.max_attachment_size
+
+ if file.size > user_project.max_attachment_size
+ allowed = exempt_from_global_attachment_size?(user_project)
+ Gitlab::AppLogger.info({ message: "File exceeds maximum size", file_bytes: file.size, project_id: user_project.id, project_path: user_project.full_path, upload_allowed: allowed })
+ end
+ end
end
helpers do
@@ -215,7 +240,7 @@ module API
use :create_params
end
post do
- Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab/issues/21139')
+ Gitlab::QueryLimiting.disable!('https://gitlab.com/gitlab-org/gitlab/issues/21139')
attrs = declared_params(include_missing: false)
attrs = translate_params_for_compatibility(attrs)
filter_attributes_using_license!(attrs)
@@ -248,7 +273,7 @@ module API
end
# rubocop: disable CodeReuse/ActiveRecord
post "user/:user_id", feature_category: :projects do
- Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab/issues/21139')
+ Gitlab::QueryLimiting.disable!('https://gitlab.com/gitlab-org/gitlab/issues/21139')
authenticated_as_admin!
user = User.find_by(id: params.delete(:user_id))
not_found!('User') unless user
@@ -310,7 +335,7 @@ module API
optional :visibility, type: String, values: Gitlab::VisibilityLevel.string_values, desc: 'The visibility of the fork'
end
post ':id/fork', feature_category: :source_code_management do
- Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab-foss/issues/42284')
+ Gitlab::QueryLimiting.disable!('https://gitlab.com/gitlab-org/gitlab/-/issues/20759')
not_found! unless can?(current_user, :fork_project, user_project)
@@ -460,7 +485,7 @@ module API
get ':id/languages', feature_category: :source_code_management do
::Projects::RepositoryLanguagesService
.new(user_project, current_user)
- .execute.map { |lang| [lang.name, lang.share] }.to_h
+ .execute.to_h { |lang| [lang.name, lang.share] }
end
desc 'Delete a project'
@@ -545,13 +570,27 @@ module API
end
# rubocop: enable CodeReuse/ActiveRecord
+ desc 'Workhorse authorize the file upload' do
+ detail 'This feature was introduced in GitLab 13.11'
+ end
+ post ':id/uploads/authorize', feature_category: :not_owned do
+ require_gitlab_workhorse!
+
+ status 200
+ content_type Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE
+ FileUploader.workhorse_authorize(has_length: false, maximum_size: project_attachment_size(user_project))
+ end
+
desc 'Upload a file'
params do
- # TODO: remove rubocop disable - https://gitlab.com/gitlab-org/gitlab/issues/14960
- requires :file, type: File, desc: 'The file to be uploaded' # rubocop:disable Scalability/FileUploads
+ requires :file, types: [Rack::Multipart::UploadedFile, ::API::Validations::Types::WorkhorseFile], desc: 'The attachment file to be uploaded'
end
post ":id/uploads", feature_category: :not_owned do
- upload = UploadService.new(user_project, params[:file]).execute
+ log_if_upload_exceed_max_size(user_project, params[:file])
+
+ service = UploadService.new(user_project, params[:file])
+ service.override_max_attachment_size = project_attachment_size(user_project)
+ upload = service.execute
present upload, with: Entities::ProjectUpload
end
diff --git a/lib/api/repositories.rb b/lib/api/repositories.rb
index f6ffeeea829..033cc6744b0 100644
--- a/lib/api/repositories.rb
+++ b/lib/api/repositories.rb
@@ -116,10 +116,23 @@ module API
params do
requires :from, type: String, desc: 'The commit, branch name, or tag name to start comparison'
requires :to, type: String, desc: 'The commit, branch name, or tag name to stop comparison'
+ optional :from_project_id, type: String, desc: 'The project to compare from'
optional :straight, type: Boolean, desc: 'Comparison method, `true` for direct comparison between `from` and `to` (`from`..`to`), `false` to compare using merge base (`from`...`to`)', default: false
end
get ':id/repository/compare' do
- compare = CompareService.new(user_project, params[:to]).execute(user_project, params[:from], straight: params[:straight])
+ if params[:from_project_id].present?
+ target_project = MergeRequestTargetProjectFinder
+ .new(current_user: current_user, source_project: user_project, project_feature: :repository)
+ .execute(include_routes: true).find_by_id(params[:from_project_id])
+
+ if target_project.blank?
+ render_api_error!("Target project id:#{params[:from_project_id]} is not a fork of project id:#{params[:id]}", 400)
+ end
+ else
+ target_project = user_project
+ end
+
+ compare = CompareService.new(user_project, params[:to]).execute(target_project, params[:from], straight: params[:straight])
if compare
present compare, with: Entities::Compare
diff --git a/lib/api/resource_access_tokens.rb b/lib/api/resource_access_tokens.rb
index 99c278be8e7..705e4778c83 100644
--- a/lib/api/resource_access_tokens.rb
+++ b/lib/api/resource_access_tokens.rb
@@ -19,7 +19,7 @@ module API
get ":id/access_tokens" do
resource = find_source(source_type, params[:id])
- next unauthorized! unless has_permission_to_read?(resource)
+ next unauthorized! unless current_user.can?(:read_resource_access_tokens, resource)
tokens = PersonalAccessTokensFinder.new({ user: resource.bots, impersonation: false }).execute
@@ -85,10 +85,6 @@ module API
def find_token(resource, token_id)
PersonalAccessTokensFinder.new({ user: resource.bots, impersonation: false }).find_by_id(token_id)
end
-
- def has_permission_to_read?(resource)
- can?(current_user, :project_bot_access, resource) || can?(current_user, :admin_resource_access_tokens, resource)
- end
end
end
end
diff --git a/lib/api/rubygem_packages.rb b/lib/api/rubygem_packages.rb
index 8d2d4586d8d..1d17148e0df 100644
--- a/lib/api/rubygem_packages.rb
+++ b/lib/api/rubygem_packages.rb
@@ -99,6 +99,8 @@ module API
track_package_event('push_package', :rubygems)
+ package_file = nil
+
ActiveRecord::Base.transaction do
package = ::Packages::CreateTemporaryPackageService.new(
user_project, current_user, declared_params.merge(build: current_authenticated_job)
@@ -109,12 +111,18 @@ module API
file_name: PACKAGE_FILENAME
}
- ::Packages::CreatePackageFileService.new(
+ package_file = ::Packages::CreatePackageFileService.new(
package, file_params.merge(build: current_authenticated_job)
).execute
end
- created!
+ if package_file
+ ::Packages::Rubygems::ExtractionWorker.perform_async(package_file.id) # rubocop:disable CodeReuse/Worker
+
+ created!
+ else
+ bad_request!('Package creation failed')
+ end
rescue ObjectStorage::RemoteStoreError => e
Gitlab::ErrorTracking.track_exception(e, extra: { file_name: params[:file_name], project_id: user_project.id })
diff --git a/lib/api/search.rb b/lib/api/search.rb
index f0ffe6ba443..8fabf379d49 100644
--- a/lib/api/search.rb
+++ b/lib/api/search.rb
@@ -22,13 +22,15 @@ module API
users: Entities::UserBasic
}.freeze
- SCOPE_PRELOAD_METHOD = {
- merge_requests: :with_api_entity_associations,
- projects: :with_api_entity_associations,
- issues: :with_api_entity_associations,
- milestones: :with_api_entity_associations,
- commits: :with_api_commit_entity_associations
- }.freeze
+ def scope_preload_method
+ {
+ merge_requests: :with_api_entity_associations,
+ projects: :with_api_entity_associations,
+ issues: :with_api_entity_associations,
+ milestones: :with_api_entity_associations,
+ commits: :with_api_commit_entity_associations
+ }.freeze
+ end
def search(additional_params = {})
search_params = {
@@ -60,7 +62,7 @@ module API
end
def preload_method
- SCOPE_PRELOAD_METHOD[params[:scope].to_sym]
+ scope_preload_method[params[:scope].to_sym]
end
def verify_search_scope!(resource:)
diff --git a/lib/api/settings.rb b/lib/api/settings.rb
index 64a72b4cb7f..95d0c525ced 100644
--- a/lib/api/settings.rb
+++ b/lib/api/settings.rb
@@ -30,6 +30,7 @@ module API
success Entities::ApplicationSetting
end
params do
+ optional :admin_mode, type: Boolean, desc: 'Require admin users to re-authenticate for administrative (i.e. potentially dangerous) operations'
optional :admin_notification_email, type: String, desc: 'Deprecated: Use :abuse_notification_email instead. Abuse reports will be sent to this address if it is set. Abuse reports are always available in the admin area.'
optional :abuse_notification_email, type: String, desc: 'Abuse reports will be sent to this address if it is set. Abuse reports are always available in the admin area.'
optional :after_sign_up_text, type: String, desc: 'Text shown after sign up'
diff --git a/lib/api/tags.rb b/lib/api/tags.rb
index 7636c45bdac..e77d7e34de3 100644
--- a/lib/api/tags.rb
+++ b/lib/api/tags.rb
@@ -28,7 +28,13 @@ module API
sort: "#{params[:order_by]}_#{params[:sort]}",
search: params[:search]).execute
- present paginate(::Kaminari.paginate_array(tags)), with: Entities::Tag, project: user_project
+ paginated_tags = paginate(::Kaminari.paginate_array(tags))
+
+ if Feature.enabled?(:api_caching_tags, user_project, type: :development)
+ present_cached paginated_tags, with: Entities::Tag, project: user_project, cache_context: -> (_tag) { user_project.cache_key }
+ else
+ present paginated_tags, with: Entities::Tag, project: user_project
+ end
end
desc 'Get a single repository tag' do
diff --git a/lib/api/triggers.rb b/lib/api/triggers.rb
index aebbc95cbea..84c51e5aeac 100644
--- a/lib/api/triggers.rb
+++ b/lib/api/triggers.rb
@@ -21,7 +21,7 @@ module API
optional :variables, type: Hash, desc: 'The list of variables to be injected into build'
end
post ":id/(ref/:ref/)trigger/pipeline", requirements: { ref: /.+/ } do
- Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab-foss/issues/42283')
+ Gitlab::QueryLimiting.disable!('https://gitlab.com/gitlab-org/gitlab/-/issues/20758')
forbidden! if gitlab_pipeline_hook_request?
diff --git a/lib/api/usage_data.rb b/lib/api/usage_data.rb
index c7d63f8d6ac..7deec15dcac 100644
--- a/lib/api/usage_data.rb
+++ b/lib/api/usage_data.rb
@@ -2,24 +2,22 @@
module API
class UsageData < ::API::Base
- before { authenticate! }
+ before { authenticate_non_get! }
feature_category :usage_ping
namespace 'usage_data' do
before do
- not_found! unless Feature.enabled?(:usage_data_api, default_enabled: true)
+ not_found! unless Feature.enabled?(:usage_data_api, default_enabled: :yaml, type: :ops)
forbidden!('Invalid CSRF token is provided') unless verified_request?
end
desc 'Track usage data events' do
detail 'This feature was introduced in GitLab 13.4.'
end
-
params do
requires :event, type: String, desc: 'The event name that should be tracked'
end
-
post 'increment_counter' do
event_name = params[:event]
@@ -31,7 +29,6 @@ module API
params do
requires :event, type: String, desc: 'The event name that should be tracked'
end
-
post 'increment_unique_users' do
event_name = params[:event]
@@ -39,6 +36,16 @@ module API
status :ok
end
+
+ desc 'Get a list of all metric definitions' do
+ detail 'This feature was introduced in GitLab 13.11.'
+ end
+ get 'metric_definitions' do
+ content_type 'application/yaml'
+ env['api.format'] = :binary
+
+ Gitlab::Usage::MetricDefinition.dump_metrics_yaml
+ end
end
end
end
diff --git a/lib/api/usage_data_non_sql_metrics.rb b/lib/api/usage_data_non_sql_metrics.rb
new file mode 100644
index 00000000000..63a14a223f5
--- /dev/null
+++ b/lib/api/usage_data_non_sql_metrics.rb
@@ -0,0 +1,27 @@
+# frozen_string_literal: true
+
+module API
+ class UsageDataNonSqlMetrics < ::API::Base
+ before { authenticated_as_admin! }
+
+ feature_category :usage_ping
+
+ namespace 'usage_data' do
+ before do
+ not_found! unless Feature.enabled?(:usage_data_non_sql_metrics, default_enabled: :yaml, type: :ops)
+ end
+
+ desc 'Get Non SQL usage ping metrics' do
+ detail 'This feature was introduced in GitLab 13.11.'
+ end
+
+ get 'non_sql_metrics' do
+ Gitlab::QueryLimiting.disable!('https://gitlab.com/gitlab-org/gitlab/-/issues/325534')
+
+ data = Gitlab::UsageDataNonSqlMetrics.uncached_data
+
+ present data
+ end
+ end
+ end
+end
diff --git a/lib/api/usage_data_queries.rb b/lib/api/usage_data_queries.rb
new file mode 100644
index 00000000000..0ad9ad7650c
--- /dev/null
+++ b/lib/api/usage_data_queries.rb
@@ -0,0 +1,27 @@
+# frozen_string_literal: true
+
+module API
+ class UsageDataQueries < ::API::Base
+ before { authenticated_as_admin! }
+
+ feature_category :usage_ping
+
+ namespace 'usage_data' do
+ before do
+ not_found! unless Feature.enabled?(:usage_data_queries_api, default_enabled: :yaml, type: :ops)
+ end
+
+ desc 'Get raw SQL queries for usage data SQL metrics' do
+ detail 'This feature was introduced in GitLab 13.11.'
+ end
+
+ get 'queries' do
+ Gitlab::QueryLimiting.disable!('https://gitlab.com/gitlab-org/gitlab/-/issues/325534')
+
+ queries = Gitlab::UsageDataQueries.uncached_data
+
+ present queries
+ end
+ end
+ end
+end
diff --git a/lib/api/users.rb b/lib/api/users.rb
index b2f99bb18dc..078ba7542a3 100644
--- a/lib/api/users.rb
+++ b/lib/api/users.rb
@@ -231,7 +231,7 @@ module API
optional :password, type: String, desc: 'The password of the new user'
optional :reset_password, type: Boolean, desc: 'Flag indicating the user will be sent a password reset token'
optional :skip_confirmation, type: Boolean, desc: 'Flag indicating the account is confirmed'
- at_least_one_of :password, :reset_password
+ at_least_one_of :password, :reset_password, :force_random_password
requires :name, type: String, desc: 'The name of the user'
requires :username, type: String, desc: 'The username of the user'
optional :force_random_password, type: Boolean, desc: 'Flag indicating a random password will be set'
@@ -571,8 +571,6 @@ module API
end
# rubocop: disable CodeReuse/ActiveRecord
delete ":id", feature_category: :users do
- Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab/issues/20757')
-
authenticated_as_admin!
user = User.find_by(id: params[:id])
@@ -998,6 +996,29 @@ module API
present paginate(current_user.emails), with: Entities::Email
end
+ desc "Update the current user's preferences" do
+ success Entities::UserPreferences
+ detail 'This feature was introduced in GitLab 13.10.'
+ end
+ params do
+ requires :view_diffs_file_by_file, type: Boolean, desc: 'Flag indicating the user sees only one file diff per page'
+ end
+ put "preferences", feature_category: :users do
+ authenticate!
+
+ preferences = current_user.user_preference
+
+ attrs = declared_params(include_missing: false)
+
+ service = ::UserPreferences::UpdateService.new(current_user, attrs).execute
+
+ if service.success?
+ present preferences, with: Entities::UserPreferences
+ else
+ render_api_error!('400 Bad Request', 400)
+ end
+ end
+
desc 'Get a single email address owned by the currently authenticated user' do
success Entities::Email
end
diff --git a/lib/api/v3/github.rb b/lib/api/v3/github.rb
index 2d25e76626a..29e4a79110f 100644
--- a/lib/api/v3/github.rb
+++ b/lib/api/v3/github.rb
@@ -18,7 +18,7 @@ module API
# Used to differentiate Jira Cloud requests from Jira Server requests
# Jira Cloud user agent format: Jira DVCS Connector Vertigo/version
# Jira Server user agent format: Jira DVCS Connector/version
- JIRA_DVCS_CLOUD_USER_AGENT = 'Jira DVCS Connector Vertigo'.freeze
+ JIRA_DVCS_CLOUD_USER_AGENT = 'Jira DVCS Connector Vertigo'
include PaginationParams
@@ -75,11 +75,14 @@ module API
# rubocop: enable CodeReuse/ActiveRecord
def authorized_merge_requests
- MergeRequestsFinder.new(current_user, authorized_only: !current_user.admin?).execute
+ MergeRequestsFinder.new(current_user, authorized_only: !current_user.admin?)
+ .execute.with_jira_integration_associations
end
def authorized_merge_requests_for_project(project)
- MergeRequestsFinder.new(current_user, authorized_only: !current_user.admin?, project_id: project.id).execute
+ MergeRequestsFinder
+ .new(current_user, authorized_only: !current_user.admin?, project_id: project.id)
+ .execute.with_jira_integration_associations
end
# rubocop: disable CodeReuse/ActiveRecord
@@ -194,16 +197,13 @@ module API
# Self-hosted Jira (tested on 7.11.1) requests this endpoint right
# after fetching branches.
- # rubocop: disable CodeReuse/ActiveRecord
get ':namespace/:project/events' do
user_project = find_project_with_access(params)
merge_requests = authorized_merge_requests_for_project(user_project)
- merge_requests = merge_requests.preload(:author, :assignees, :metrics, source_project: :namespace, target_project: :namespace)
present paginate(merge_requests), with: ::API::Github::Entities::PullRequestEvent
end
- # rubocop: enable CodeReuse/ActiveRecord
params do
use :project_full_path
diff --git a/lib/api/variables.rb b/lib/api/variables.rb
index 94fa98b7a14..8b0745c6b5b 100644
--- a/lib/api/variables.rb
+++ b/lib/api/variables.rb
@@ -9,21 +9,7 @@ module API
feature_category :continuous_integration
- helpers do
- def filter_variable_parameters(params)
- # This method exists so that EE can more easily filter out certain
- # parameters, without having to modify the source code directly.
- params
- end
-
- def find_variable(params)
- variables = ::Ci::VariablesFinder.new(user_project, params).execute.to_a
-
- return variables.first unless variables.many? # rubocop: disable CodeReuse/ActiveRecord
-
- conflict!("There are multiple variables with provided parameters. Please use 'filter[environment_scope]'")
- end
- end
+ helpers Helpers::VariablesHelpers
params do
requires :id, type: String, desc: 'The ID of a project'
@@ -49,7 +35,7 @@ module API
end
# rubocop: disable CodeReuse/ActiveRecord
get ':id/variables/:key' do
- variable = find_variable(params)
+ variable = find_variable(user_project, params)
not_found!('Variable') unless variable
present variable, with: Entities::Ci::Variable
@@ -71,7 +57,7 @@ module API
variable = ::Ci::ChangeVariableService.new(
container: user_project,
current_user: current_user,
- params: { action: :create, variable_params: filter_variable_parameters(declared_params(include_missing: false)) }
+ params: { action: :create, variable_params: declared_params(include_missing: false) }
).execute
if variable.valid?
@@ -95,17 +81,13 @@ module API
end
# rubocop: disable CodeReuse/ActiveRecord
put ':id/variables/:key' do
- variable = find_variable(params)
+ variable = find_variable(user_project, params)
not_found!('Variable') unless variable
- variable_params = filter_variable_parameters(
- declared_params(include_missing: false)
- .except(:key, :filter)
- )
variable = ::Ci::ChangeVariableService.new(
container: user_project,
current_user: current_user,
- params: { action: :update, variable: variable, variable_params: variable_params }
+ params: { action: :update, variable: variable, variable_params: declared_params(include_missing: false).except(:key, :filter) }
).execute
if variable.valid?
@@ -125,7 +107,7 @@ module API
end
# rubocop: disable CodeReuse/ActiveRecord
delete ':id/variables/:key' do
- variable = find_variable(params)
+ variable = find_variable(user_project, params)
not_found!('Variable') unless variable
::Ci::ChangeVariableService.new(