diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2021-03-16 18:18:33 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2021-03-16 18:18:33 +0000 |
commit | f64a639bcfa1fc2bc89ca7db268f594306edfd7c (patch) | |
tree | a2c3c2ebcc3b45e596949db485d6ed18ffaacfa1 /lib/api | |
parent | bfbc3e0d6583ea1a91f627528bedc3d65ba4b10f (diff) | |
download | gitlab-ce-f64a639bcfa1fc2bc89ca7db268f594306edfd7c.tar.gz |
Add latest changes from gitlab-org/gitlab@13-10-stable-eev13.10.0-rc40
Diffstat (limited to 'lib/api')
44 files changed, 446 insertions, 126 deletions
diff --git a/lib/api/admin/plan_limits.rb b/lib/api/admin/plan_limits.rb new file mode 100644 index 00000000000..92f7d3dce0d --- /dev/null +++ b/lib/api/admin/plan_limits.rb @@ -0,0 +1,57 @@ +# frozen_string_literal: true + +module API + module Admin + class PlanLimits < ::API::Base + before { authenticated_as_admin! } + + feature_category :not_owned + + helpers do + def current_plan(name) + plan = ::Admin::PlansFinder.new({ name: name }).execute + + not_found!('Plan') unless plan + plan + end + end + + desc 'Get current plan limits' do + success Entities::PlanLimit + end + params do + optional :plan_name, type: String, values: Plan.all_plans, default: Plan::DEFAULT, desc: 'Name of the plan' + end + get "application/plan_limits" do + params = declared_params(include_missing: false) + plan = current_plan(params.delete(:plan_name)) + + present plan.actual_limits, with: Entities::PlanLimit + end + + desc 'Modify plan limits' do + success Entities::PlanLimit + end + params do + requires :plan_name, type: String, values: Plan.all_plans, desc: 'Name of the plan' + + optional :conan_max_file_size, type: Integer, desc: 'Maximum Conan package file size in bytes' + optional :generic_packages_max_file_size, type: Integer, desc: 'Maximum generic package file size in bytes' + optional :maven_max_file_size, type: Integer, desc: 'Maximum Maven package file size in bytes' + optional :npm_max_file_size, type: Integer, desc: 'Maximum NPM package file size in bytes' + optional :nuget_max_file_size, type: Integer, desc: 'Maximum NuGet package file size in bytes' + optional :pypi_max_file_size, type: Integer, desc: 'Maximum PyPI package file size in bytes' + end + put "application/plan_limits" do + params = declared_params(include_missing: false) + plan = current_plan(params.delete(:plan_name)) + + if plan.actual_limits.update(params) + present plan.actual_limits, with: Entities::PlanLimit + else + render_validation_error!(plan.actual_limits) + end + end + end + end +end diff --git a/lib/api/api.rb b/lib/api/api.rb index 0598f03c7ab..f83a36068dd 100644 --- a/lib/api/api.rb +++ b/lib/api/api.rb @@ -58,6 +58,7 @@ module API user: -> { @current_user }, project: -> { @project }, namespace: -> { @group }, + runner: -> { @current_runner || @runner }, caller_id: route.origin, remote_ip: request.ip, feature_category: feature_category @@ -147,7 +148,7 @@ module API # Only overwrite `text/plain+deprecated` if content_types[api_format] == 'text/plain+deprecated' - if Feature.enabled?(:api_always_use_application_json) + if Feature.enabled?(:api_always_use_application_json, default_enabled: :yaml) content_type 'application/json' else content_type 'text/plain' @@ -169,6 +170,7 @@ module API mount ::API::AccessRequests mount ::API::Admin::Ci::Variables mount ::API::Admin::InstanceClusters + mount ::API::Admin::PlanLimits mount ::API::Admin::Sidekiq mount ::API::Appearance mount ::API::Applications diff --git a/lib/api/boards.rb b/lib/api/boards.rb index 5fd4ca3546c..79f4b02f26a 100644 --- a/lib/api/boards.rb +++ b/lib/api/boards.rb @@ -30,7 +30,7 @@ module API use :pagination end get '/' do - authorize!(:read_board, user_project) + authorize!(:read_issue_board, user_project) present paginate(board_parent.boards.with_associations), with: Entities::Board end @@ -39,7 +39,7 @@ module API success Entities::Board end get '/:board_id' do - authorize!(:read_board, user_project) + authorize!(:read_issue_board, user_project) present board, with: Entities::Board end @@ -51,7 +51,7 @@ module API requires :name, type: String, desc: 'The board name' end post '/' do - authorize!(:admin_board, board_parent) + authorize!(:admin_issue_board, board_parent) create_board end @@ -64,7 +64,7 @@ module API use :update_params end put '/:board_id' do - authorize!(:admin_board, board_parent) + authorize!(:admin_issue_board, board_parent) update_board end @@ -75,7 +75,7 @@ module API end delete '/:board_id' do - authorize!(:admin_board, board_parent) + authorize!(:admin_issue_board, board_parent) delete_board end @@ -93,7 +93,7 @@ module API use :pagination end get '/lists' do - authorize!(:read_board, user_project) + authorize!(:read_issue_board, user_project) present paginate(board_lists), with: Entities::List end @@ -105,7 +105,7 @@ module API requires :list_id, type: Integer, desc: 'The ID of a list' end get '/lists/:list_id' do - authorize!(:read_board, user_project) + authorize!(:read_issue_board, user_project) present board_lists.find(params[:list_id]), with: Entities::List end @@ -117,7 +117,7 @@ module API use :list_creation_params end post '/lists' do - authorize!(:admin_list, user_project) + authorize!(:admin_issue_board_list, user_project) create_list end @@ -133,7 +133,7 @@ module API put '/lists/:list_id' do list = board_lists.find(params[:list_id]) - authorize!(:admin_list, user_project) + authorize!(:admin_issue_board_list, user_project) move_list(list) end @@ -146,7 +146,7 @@ module API requires :list_id, type: Integer, desc: 'The ID of a board list' end delete "/lists/:list_id" do - authorize!(:admin_list, user_project) + authorize!(:admin_issue_board_list, user_project) list = board_lists.find(params[:list_id]) destroy_list(list) diff --git a/lib/api/ci/runner.rb b/lib/api/ci/runner.rb index 5cfb65e1fbb..80d5e80e21e 100644 --- a/lib/api/ci/runner.rb +++ b/lib/api/ci/runner.rb @@ -34,22 +34,22 @@ module API if runner_registration_token_valid? # Create shared runner. Requires admin access attributes.merge(runner_type: :instance_type) - elsif project = Project.find_by_runners_token(params[:token]) + elsif @project = Project.find_by_runners_token(params[:token]) # Create a specific runner for the project - attributes.merge(runner_type: :project_type, projects: [project]) - elsif group = Group.find_by_runners_token(params[:token]) + attributes.merge(runner_type: :project_type, projects: [@project]) + elsif @group = Group.find_by_runners_token(params[:token]) # Create a specific runner for the group - attributes.merge(runner_type: :group_type, groups: [group]) + attributes.merge(runner_type: :group_type, groups: [@group]) else forbidden! end - runner = ::Ci::Runner.create(attributes) + @runner = ::Ci::Runner.create(attributes) - if runner.persisted? - present runner, with: Entities::RunnerRegistrationDetails + if @runner.persisted? + present @runner, with: Entities::RunnerRegistrationDetails else - render_validation_error!(runner) + render_validation_error!(@runner) end end @@ -62,9 +62,7 @@ module API delete '/' do authenticate_runner! - runner = ::Ci::Runner.find_by_token(params[:token]) - - destroy_conditionally!(runner) + destroy_conditionally!(current_runner) end desc 'Validates authentication credentials' do @@ -81,12 +79,7 @@ module API end resource :jobs do - before do - Gitlab::ApplicationContext.push( - user: -> { current_job&.user }, - project: -> { current_job&.project } - ) - end + before { set_application_context } desc 'Request a job' do success Entities::JobRequest::Response diff --git a/lib/api/commit_statuses.rb b/lib/api/commit_statuses.rb index 26af921432c..e199111c975 100644 --- a/lib/api/commit_statuses.rb +++ b/lib/api/commit_statuses.rb @@ -100,7 +100,12 @@ module API attributes_for_keys(%w[target_url description coverage]) status.update(optional_attributes) if optional_attributes.any? - render_validation_error!(status) if status.invalid? + + if status.valid? + status.update_older_statuses_retried! if Feature.enabled?(:ci_fix_commit_status_retried, user_project, default_enabled: :yaml) + else + render_validation_error!(status) + end begin case params[:state] diff --git a/lib/api/composer_packages.rb b/lib/api/composer_packages.rb index 1181650fe96..ec7585363e2 100644 --- a/lib/api/composer_packages.rb +++ b/lib/api/composer_packages.rb @@ -93,6 +93,20 @@ module API route_setting :authentication, job_token_allowed: true, basic_auth_personal_access_token: true + get ':id/-/packages/composer/p2/*package_name', requirements: COMPOSER_ENDPOINT_REQUIREMENTS, file_path: true do + not_found! if packages.empty? + + presenter.package_versions + end + + desc 'Composer packages endpoint at group level for package versions metadata' + + params do + requires :package_name, type: String, file_path: true, desc: 'The Composer package name' + end + + route_setting :authentication, job_token_allowed: true, basic_auth_personal_access_token: true + get ':id/-/packages/composer/*package_name', requirements: COMPOSER_ENDPOINT_REQUIREMENTS, file_path: true do not_found! if packages.empty? not_found! if params[:sha].blank? diff --git a/lib/api/concerns/packages/conan_endpoints.rb b/lib/api/concerns/packages/conan_endpoints.rb index 1796d51324f..eb762be8285 100644 --- a/lib/api/concerns/packages/conan_endpoints.rb +++ b/lib/api/concerns/packages/conan_endpoints.rb @@ -42,7 +42,7 @@ module API # Personal access token will be extracted from Bearer or Basic authorization # in the overridden find_personal_access_token or find_user_from_job_token helpers - authenticate! + authenticate_non_get! end desc 'Ping the Conan API' do @@ -71,6 +71,10 @@ module API end namespace 'users' do + before do + authenticate! + end + format :txt content_type :txt, 'text/plain' diff --git a/lib/api/concerns/packages/npm_endpoints.rb b/lib/api/concerns/packages/npm_endpoints.rb index 833288c6013..d6e006df976 100644 --- a/lib/api/concerns/packages/npm_endpoints.rb +++ b/lib/api/concerns/packages/npm_endpoints.rb @@ -41,8 +41,8 @@ module API authorize_read_package!(project) - packages = ::Packages::Npm::PackageFinder.new(project, package_name) - .execute + packages = ::Packages::Npm::PackageFinder.new(package_name, project: project) + .execute not_found! if packages.empty? @@ -68,9 +68,8 @@ module API authorize_create_package!(project) - package = ::Packages::Npm::PackageFinder - .new(project, package_name) - .find_by_version(version) + package = ::Packages::Npm::PackageFinder.new(package_name, project: project) + .find_by_version(version) not_found!('Package') unless package ::Packages::Npm::CreateTagService.new(package, tag).execute @@ -112,9 +111,8 @@ module API route_setting :authentication, job_token_allowed: true, deploy_token_allowed: true get '*package_name', format: false, requirements: ::API::Helpers::Packages::Npm::NPM_ENDPOINT_REQUIREMENTS do package_name = params[:package_name] - - packages = ::Packages::Npm::PackageFinder.new(project_or_nil, package_name) - .execute + packages = ::Packages::Npm::PackageFinder.new(package_name, project: project_or_nil) + .execute redirect_request = project_or_nil.blank? || packages.empty? diff --git a/lib/api/entities/ci/pipeline_basic.rb b/lib/api/entities/ci/pipeline_basic.rb index dbb9b828757..f4f2356c812 100644 --- a/lib/api/entities/ci/pipeline_basic.rb +++ b/lib/api/entities/ci/pipeline_basic.rb @@ -4,7 +4,7 @@ module API module Entities module Ci class PipelineBasic < Grape::Entity - expose :id, :sha, :ref, :status + expose :id, :project_id, :sha, :ref, :status expose :created_at, :updated_at expose :web_url do |pipeline, _options| diff --git a/lib/api/entities/job_request/response.rb b/lib/api/entities/job_request/response.rb index 8db9aff3dc9..bf22ea1e6e2 100644 --- a/lib/api/entities/job_request/response.rb +++ b/lib/api/entities/job_request/response.rb @@ -20,7 +20,7 @@ module API model end - expose :variables + expose :runner_variables, as: :variables expose :steps, using: Entities::JobRequest::Step expose :image, using: Entities::JobRequest::Image expose :services, using: Entities::JobRequest::Service diff --git a/lib/api/entities/plan_limit.rb b/lib/api/entities/plan_limit.rb new file mode 100644 index 00000000000..40e8b348c18 --- /dev/null +++ b/lib/api/entities/plan_limit.rb @@ -0,0 +1,14 @@ +# frozen_string_literal: true + +module API + module Entities + class PlanLimit < Grape::Entity + expose :conan_max_file_size + expose :generic_packages_max_file_size + expose :maven_max_file_size + expose :npm_max_file_size + expose :nuget_max_file_size + expose :pypi_max_file_size + end + end +end diff --git a/lib/api/entities/project.rb b/lib/api/entities/project.rb index 6ad6123a20e..e332e5e40fa 100644 --- a/lib/api/entities/project.rb +++ b/lib/api/entities/project.rb @@ -5,6 +5,8 @@ module API class Project < BasicProjectDetails include ::API::Helpers::RelatedResourcesHelpers + expose :container_registry_url, as: :container_registry_image_prefix, if: -> (_, _) { Gitlab.config.registry.enabled } + expose :_links do expose :self do |project| expose_url(api_v4_projects_path(id: project.id)) diff --git a/lib/api/entities/project_repository_storage_move.rb b/lib/api/entities/project_repository_storage_move.rb deleted file mode 100644 index 191bbaf19d7..00000000000 --- a/lib/api/entities/project_repository_storage_move.rb +++ /dev/null @@ -1,9 +0,0 @@ -# frozen_string_literal: true - -module API - module Entities - class ProjectRepositoryStorageMove < BasicRepositoryStorageMove - expose :project, using: Entities::ProjectIdentity - end - end -end diff --git a/lib/api/entities/projects/repository_storage_move.rb b/lib/api/entities/projects/repository_storage_move.rb new file mode 100644 index 00000000000..7844cd36e02 --- /dev/null +++ b/lib/api/entities/projects/repository_storage_move.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +module API + module Entities + module Projects + class RepositoryStorageMove < BasicRepositoryStorageMove + expose :project, using: Entities::ProjectIdentity + end + end + end +end diff --git a/lib/api/entities/protected_branch.rb b/lib/api/entities/protected_branch.rb index 80c8a791053..e5dbaffb591 100644 --- a/lib/api/entities/protected_branch.rb +++ b/lib/api/entities/protected_branch.rb @@ -7,6 +7,7 @@ module API expose :name expose :push_access_levels, using: Entities::ProtectedRefAccess expose :merge_access_levels, using: Entities::ProtectedRefAccess + expose :allow_force_push end end end diff --git a/lib/api/entities/public_group_details.rb b/lib/api/entities/public_group_details.rb new file mode 100644 index 00000000000..0dfe28d251a --- /dev/null +++ b/lib/api/entities/public_group_details.rb @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +module API + module Entities + class PublicGroupDetails < BasicGroupDetails + expose :avatar_url do |group, options| + group.avatar_url(only_path: false) + end + expose :full_name, :full_path + end + end +end diff --git a/lib/api/entities/snippets/repository_storage_move.rb b/lib/api/entities/snippets/repository_storage_move.rb new file mode 100644 index 00000000000..4e14d1dfba2 --- /dev/null +++ b/lib/api/entities/snippets/repository_storage_move.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +module API + module Entities + module Snippets + class RepositoryStorageMove < BasicRepositoryStorageMove + expose :snippet, using: Entities::BasicSnippet + end + end + end +end diff --git a/lib/api/environments.rb b/lib/api/environments.rb index 5dd2fa22690..3e1e430c2f9 100644 --- a/lib/api/environments.rb +++ b/lib/api/environments.rb @@ -75,6 +75,33 @@ module API end end + desc "Delete multiple stopped review apps" do + detail "Remove multiple stopped review environments older than a specific age" + success Entities::Environment + end + params do + optional :before, type: Time, desc: "The timestamp before which environments can be deleted. Defaults to 30 days ago.", default: -> { 30.days.ago } + optional :limit, type: Integer, desc: "Maximum number of environments to delete. Defaults to 100.", default: 100, values: 1..1000 + optional :dry_run, type: Boolean, desc: "If set, perform a dry run where no actual deletions will be performed. Defaults to true.", default: true + end + delete ":id/environments/review_apps" do + authorize! :read_environment, user_project + + result = ::Environments::ScheduleToDeleteReviewAppsService.new(user_project, current_user, params).execute + + response = { + scheduled_entries: Entities::Environment.represent(result.scheduled_entries), + unprocessable_entries: Entities::Environment.represent(result.unprocessable_entries) + } + + if result.success? + status result.status + present response, current_user: current_user + else + render_api_error!(response.merge!(message: result.error_message), result.status) + end + end + desc 'Deletes an existing environment' do detail 'This feature was introduced in GitLab 8.11.' success Entities::Environment diff --git a/lib/api/generic_packages.rb b/lib/api/generic_packages.rb index 24d726f4a41..3d0ba97b51a 100644 --- a/lib/api/generic_packages.rb +++ b/lib/api/generic_packages.rb @@ -13,7 +13,7 @@ module API before do require_packages_enabled! - authenticate! + authenticate_non_get! require_generic_packages_available! end diff --git a/lib/api/group_boards.rb b/lib/api/group_boards.rb index 7425e1bd145..90632048354 100644 --- a/lib/api/group_boards.rb +++ b/lib/api/group_boards.rb @@ -30,7 +30,7 @@ module API use :pagination end get '/' do - authorize!(:read_board, user_group) + authorize!(:read_issue_board, user_group) present paginate(board_parent.boards.with_associations), with: Entities::Board end @@ -39,7 +39,7 @@ module API success Entities::Board end get '/:board_id' do - authorize!(:read_board, user_group) + authorize!(:read_issue_board, user_group) present board, with: Entities::Board end @@ -51,7 +51,7 @@ module API use :update_params end put '/:board_id' do - authorize!(:admin_board, board_parent) + authorize!(:admin_issue_board, board_parent) update_board end @@ -69,7 +69,7 @@ module API use :pagination end get '/lists' do - authorize!(:read_board, user_group) + authorize!(:read_issue_board, user_group) present paginate(board_lists), with: Entities::List end @@ -81,7 +81,7 @@ module API requires :list_id, type: Integer, desc: 'The ID of a list' end get '/lists/:list_id' do - authorize!(:read_board, user_group) + authorize!(:read_issue_board, user_group) present board_lists.find(params[:list_id]), with: Entities::List end @@ -93,7 +93,7 @@ module API use :list_creation_params end post '/lists' do - authorize!(:admin_list, user_group) + authorize!(:admin_issue_board_list, user_group) create_list end @@ -109,7 +109,7 @@ module API put '/lists/:list_id' do list = board_lists.find(params[:list_id]) - authorize!(:admin_list, user_group) + authorize!(:admin_issue_board_list, user_group) move_list(list) end @@ -122,7 +122,7 @@ module API requires :list_id, type: Integer, desc: 'The ID of a board list' end delete "/lists/:list_id" do - authorize!(:admin_list, user_group) + authorize!(:admin_issue_board_list, user_group) list = board_lists.find(params[:list_id]) destroy_list(list) diff --git a/lib/api/groups.rb b/lib/api/groups.rb index a8b1cdab021..26fa00d6186 100644 --- a/lib/api/groups.rb +++ b/lib/api/groups.rb @@ -137,6 +137,10 @@ module API end end # rubocop: enable CodeReuse/ActiveRecord + + def authorize_group_creation! + authorize! :create_group + end end resource :groups do @@ -169,7 +173,7 @@ module API if parent_group authorize! :create_subgroup, parent_group else - authorize! :create_group + authorize_group_creation! end group = create_group diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb index 0abb21c9831..9db4a03c5b9 100644 --- a/lib/api/helpers.rb +++ b/lib/api/helpers.rb @@ -467,7 +467,7 @@ module API def handle_api_exception(exception) if report_exception?(exception) define_params_for_grape_middleware - Gitlab::ErrorTracking.with_context(current_user) do + Gitlab::ApplicationContext.with_context(user: current_user) do Gitlab::ErrorTracking.track_exception(exception) end end diff --git a/lib/api/helpers/issues_helpers.rb b/lib/api/helpers/issues_helpers.rb index b303f1f845d..2b1ed479692 100644 --- a/lib/api/helpers/issues_helpers.rb +++ b/lib/api/helpers/issues_helpers.rb @@ -11,10 +11,7 @@ module API params :optional_issue_params_ee do end - params :optional_issues_params_ee do - end - - params :optional_issue_not_params_ee do + params :issues_stats_params_ee do end def self.update_params_at_least_one_of diff --git a/lib/api/helpers/packages/conan/api_helpers.rb b/lib/api/helpers/packages/conan/api_helpers.rb index 39ecfc171a9..d5f5448fd42 100644 --- a/lib/api/helpers/packages/conan/api_helpers.rb +++ b/lib/api/helpers/packages/conan/api_helpers.rb @@ -221,7 +221,7 @@ module API def find_user_from_job_token return unless route_authentication_setting[:job_token_allowed] - job = find_job_from_token || raise(::Gitlab::Auth::UnauthorizedError) + job = find_job_from_token || return @current_authenticated_job = job # rubocop:disable Gitlab/ModuleWithInstanceVariables job.user diff --git a/lib/api/helpers/packages/npm.rb b/lib/api/helpers/packages/npm.rb index c1f6a001201..2d556f889bf 100644 --- a/lib/api/helpers/packages/npm.rb +++ b/lib/api/helpers/packages/npm.rb @@ -49,13 +49,28 @@ module API when :project params[:id] when :instance - ::Packages::Package.npm - .with_name(params[:package_name]) - .first - &.project_id + namespace_path = namespace_path_from_package_name + next unless namespace_path + + namespace = Namespace.top_most + .by_path(namespace_path) + next unless namespace + + finder = ::Packages::Npm::PackageFinder.new(params[:package_name], namespace: namespace) + + finder.last&.project_id end end end + + # from "@scope/package-name" return "scope" or nil + def namespace_path_from_package_name + package_name = params[:package_name] + return unless package_name.starts_with?('@') + return unless package_name.include?('/') + + package_name.match(Gitlab::Regex.npm_package_name_regex)&.captures&.first + end end end end diff --git a/lib/api/helpers/runner.rb b/lib/api/helpers/runner.rb index 1c85669a626..39586483990 100644 --- a/lib/api/helpers/runner.rb +++ b/lib/api/helpers/runner.rb @@ -71,6 +71,15 @@ module API header 'Job-Status', job.status forbidden!(reason) end + + def set_application_context + return unless current_job + + Gitlab::ApplicationContext.push( + user: -> { current_job.user }, + project: -> { current_job.project } + ) + end end end end diff --git a/lib/api/helpers/services_helpers.rb b/lib/api/helpers/services_helpers.rb index 6101a8d307e..ed3d694f006 100644 --- a/lib/api/helpers/services_helpers.rb +++ b/lib/api/helpers/services_helpers.rb @@ -635,7 +635,7 @@ module API required: true, name: :google_iap_audience_client_id, type: String, - desc: 'Client ID of the IAP secured resource (looks like IAP_CLIENT_ID.apps.googleusercontent.com)' + desc: 'Client ID of the IAP-secured resource (looks like IAP_CLIENT_ID.apps.googleusercontent.com)' }, { required: true, diff --git a/lib/api/invitations.rb b/lib/api/invitations.rb index 2ab1f97afe6..52c32b4d1cf 100644 --- a/lib/api/invitations.rb +++ b/lib/api/invitations.rb @@ -49,6 +49,39 @@ module API present_member_invitations invitations end + desc 'Updates a group or project invitation.' do + success Entities::Member + end + params do + requires :email, type: String, desc: 'The email address of the invitation.' + optional :access_level, type: Integer, values: Gitlab::Access.all_values, desc: 'A valid access level (defaults: `30`, developer access level).' + optional :expires_at, type: DateTime, desc: 'Date string in ISO 8601 format (`YYYY-MM-DDTHH:MM:SSZ`).' + end + put ":id/invitations/:email", requirements: { email: /[^\/]+/ } do + source = find_source(source_type, params.delete(:id)) + invite_email = params[:email] + authorize_admin_source!(source_type, source) + + invite = retrieve_member_invitations(source, invite_email).first + not_found! unless invite + + update_params = declared_params(include_missing: false) + update_params.delete(:email) + bad_request! unless update_params.any? + + result = ::Members::UpdateService + .new(current_user, update_params) + .execute(invite) + + updated_member = result[:member] + + if result[:status] == :success + present_members updated_member + else + render_validation_error!(updated_member) + end + end + desc 'Removes an invitation from a group or project.' params do requires :email, type: String, desc: 'The email address of the invitation' diff --git a/lib/api/issues.rb b/lib/api/issues.rb index ea09174f03a..13dac1c174c 100644 --- a/lib/api/issues.rb +++ b/lib/api/issues.rb @@ -15,6 +15,24 @@ module API optional :labels, type: Array[String], coerce_with: ::API::Validations::Types::CommaSeparatedToArray.coerce, desc: 'Comma-separated list of label names' optional :milestone, type: String, desc: 'Milestone title' optional :iids, type: Array[Integer], coerce_with: ::API::Validations::Types::CommaSeparatedToIntegerArray.coerce, desc: 'The IID array of issues' + + optional :author_id, type: Integer, desc: 'Return issues which are not authored by the user with the given ID' + optional :author_username, type: String, desc: 'Return issues which are not authored by the user with the given username' + mutually_exclusive :author_id, :author_username + + optional :assignee_id, type: Integer, desc: 'Return issues which are not assigned to the user with the given ID' + optional :assignee_username, type: Array[String], check_assignees_count: true, + coerce_with: Validations::Validators::CheckAssigneesCount.coerce, + desc: 'Return issues which are not assigned to the user with the given username' + mutually_exclusive :assignee_id, :assignee_username + + use :negatable_issue_filter_params_ee + end + + params :issues_stats_params do + optional :labels, type: Array[String], coerce_with: ::API::Validations::Types::CommaSeparatedToArray.coerce, desc: 'Comma-separated list of label names' + optional :milestone, type: String, desc: 'Milestone title' + optional :iids, type: Array[Integer], coerce_with: ::API::Validations::Types::CommaSeparatedToIntegerArray.coerce, desc: 'The IID array of issues' optional :search, type: String, desc: 'Search issues for text present in the title, description, or any combination of these' optional :in, type: String, desc: '`title`, `description`, or a string joining them with comma' @@ -29,11 +47,6 @@ module API desc: 'Return issues which are assigned to the user with the given username' mutually_exclusive :assignee_id, :assignee_username - use :negatable_issue_filter_params_ee - end - - params :issues_stats_params do - use :negatable_issue_filter_params optional :created_after, type: DateTime, desc: 'Return issues created after the specified time' optional :created_before, type: DateTime, desc: 'Return issues created before the specified time' optional :updated_after, type: DateTime, desc: 'Return issues updated after the specified time' @@ -48,7 +61,7 @@ module API optional :my_reaction_emoji, type: String, desc: 'Return issues reacted by the authenticated user by the given emoji' optional :confidential, type: Boolean, desc: 'Filter confidential or public issues' - use :optional_issues_params_ee + use :issues_stats_params_ee end params :issues_params do diff --git a/lib/api/job_artifacts.rb b/lib/api/job_artifacts.rb index 28737f61f61..3dec0a29181 100644 --- a/lib/api/job_artifacts.rb +++ b/lib/api/job_artifacts.rb @@ -45,6 +45,7 @@ module API requires :job, type: String, desc: 'The name for the job' requires :artifact_path, type: String, desc: 'Artifact path' end + route_setting :authentication, job_token_allowed: true get ':id/jobs/artifacts/:ref_name/raw/*artifact_path', format: false, requirements: { ref_name: /.+/ } do @@ -84,13 +85,14 @@ module API requires :job_id, type: Integer, desc: 'The ID of a job' requires :artifact_path, type: String, desc: 'Artifact path' end + route_setting :authentication, job_token_allowed: true get ':id/jobs/:job_id/artifacts/*artifact_path', format: false do authorize_download_artifacts! build = find_build!(params[:job_id]) authorize_read_job_artifacts!(build) - not_found! unless build.artifacts? + not_found! unless build.available_artifacts? path = Gitlab::Ci::Build::Artifacts::Path .new(params[:artifact_path]) diff --git a/lib/api/jobs.rb b/lib/api/jobs.rb index 390dbc892e2..7390219b60e 100644 --- a/lib/api/jobs.rb +++ b/lib/api/jobs.rb @@ -8,10 +8,11 @@ module API feature_category :continuous_integration - params do - requires :id, type: String, desc: 'The ID of a project' - end resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do + params do + requires :id, type: String, desc: 'The ID of a project' + end + helpers do params :optional_scope do optional :scope, types: [String, Array[String]], desc: 'The scope of builds to show', @@ -168,6 +169,20 @@ module API end end + resource :job do + desc 'Get current project using job token' do + 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 + + present current_authenticated_job, with: Entities::Ci::Job + end + end + helpers do # rubocop: disable CodeReuse/ActiveRecord def filter_builds(builds, scope) diff --git a/lib/api/nuget_project_packages.rb b/lib/api/nuget_project_packages.rb index e071b6bd68f..73ecc140959 100644 --- a/lib/api/nuget_project_packages.rb +++ b/lib/api/nuget_project_packages.rb @@ -62,8 +62,9 @@ module API file_name: PACKAGE_FILENAME ) - package = ::Packages::Nuget::CreatePackageService.new(project_or_group, current_user, declared_params.merge(build: current_authenticated_job)) - .execute + package = ::Packages::CreateTemporaryPackageService.new( + project_or_group, current_user, declared_params.merge(build: current_authenticated_job) + ).execute(:nuget, name: ::Packages::Nuget::TEMPORARY_PACKAGE_NAME) package_file = ::Packages::CreatePackageFileService.new(package, file_params.merge(build: current_authenticated_job)) .execute diff --git a/lib/api/project_packages.rb b/lib/api/project_packages.rb index 0fdaa4b2656..babc7b9dd58 100644 --- a/lib/api/project_packages.rb +++ b/lib/api/project_packages.rb @@ -70,7 +70,11 @@ module API package = ::Packages::PackageFinder .new(user_project, params[:package_id]).execute - destroy_conditionally!(package) + destroy_conditionally!(package) do |package| + if package.destroy + package.sync_maven_metadata(current_user) + end + end end end end diff --git a/lib/api/project_repository_storage_moves.rb b/lib/api/project_repository_storage_moves.rb index 196b7d88500..ab5d8b3a888 100644 --- a/lib/api/project_repository_storage_moves.rb +++ b/lib/api/project_repository_storage_moves.rb @@ -11,28 +11,28 @@ module API resource :project_repository_storage_moves do desc 'Get a list of all project repository storage moves' do detail 'This feature was introduced in GitLab 13.0.' - success Entities::ProjectRepositoryStorageMove + success Entities::Projects::RepositoryStorageMove end params do use :pagination end get do - storage_moves = ProjectRepositoryStorageMove.with_projects.order_created_at_desc + storage_moves = ::Projects::RepositoryStorageMove.with_projects.order_created_at_desc - present paginate(storage_moves), with: Entities::ProjectRepositoryStorageMove, current_user: current_user + present paginate(storage_moves), with: Entities::Projects::RepositoryStorageMove, current_user: current_user end desc 'Get a project repository storage move' do detail 'This feature was introduced in GitLab 13.0.' - success Entities::ProjectRepositoryStorageMove + success Entities::Projects::RepositoryStorageMove end params do requires :repository_storage_move_id, type: Integer, desc: 'The ID of a project repository storage move' end get ':repository_storage_move_id' do - storage_move = ProjectRepositoryStorageMove.find(params[:repository_storage_move_id]) + storage_move = ::Projects::RepositoryStorageMove.find(params[:repository_storage_move_id]) - present storage_move, with: Entities::ProjectRepositoryStorageMove, current_user: current_user + present storage_move, with: Entities::Projects::RepositoryStorageMove, current_user: current_user end desc 'Schedule bulk project repository storage moves' do @@ -58,7 +58,7 @@ module API resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do desc 'Get a list of all project repository storage moves' do detail 'This feature was introduced in GitLab 13.1.' - success Entities::ProjectRepositoryStorageMove + success Entities::Projects::RepositoryStorageMove end params do use :pagination @@ -66,12 +66,12 @@ module API get ':id/repository_storage_moves' do storage_moves = user_project.repository_storage_moves.with_projects.order_created_at_desc - present paginate(storage_moves), with: Entities::ProjectRepositoryStorageMove, current_user: current_user + present paginate(storage_moves), with: Entities::Projects::RepositoryStorageMove, current_user: current_user end desc 'Get a project repository storage move' do detail 'This feature was introduced in GitLab 13.1.' - success Entities::ProjectRepositoryStorageMove + success Entities::Projects::RepositoryStorageMove end params do requires :repository_storage_move_id, type: Integer, desc: 'The ID of a project repository storage move' @@ -79,12 +79,12 @@ module API get ':id/repository_storage_moves/:repository_storage_move_id' do storage_move = user_project.repository_storage_moves.find(params[:repository_storage_move_id]) - present storage_move, with: Entities::ProjectRepositoryStorageMove, current_user: current_user + present storage_move, with: Entities::Projects::RepositoryStorageMove, current_user: current_user end desc 'Schedule a project repository storage move' do detail 'This feature was introduced in GitLab 13.1.' - success Entities::ProjectRepositoryStorageMove + success Entities::Projects::RepositoryStorageMove end params do optional :destination_storage_name, type: String, desc: 'The destination storage shard' @@ -95,7 +95,7 @@ module API ) if storage_move.schedule - present storage_move, with: Entities::ProjectRepositoryStorageMove, current_user: current_user + present storage_move, with: Entities::Projects::RepositoryStorageMove, current_user: current_user else render_validation_error!(storage_move) end diff --git a/lib/api/projects.rb b/lib/api/projects.rb index fca68c3606b..19b63c28f89 100644 --- a/lib/api/projects.rb +++ b/lib/api/projects.rb @@ -136,6 +136,17 @@ module API present records, options end + def present_groups(groups) + options = { + with: Entities::PublicGroupDetails, + current_user: current_user + } + + groups, options = with_custom_attributes(groups, options) + + present paginate(groups), options + end + def translate_params_for_compatibility(params) params[:builds_enabled] = params.delete(:jobs_enabled) if params.key?(:jobs_enabled) params @@ -561,6 +572,25 @@ module API present paginate(users), with: Entities::UserBasic end + desc 'Get ancestor and shared groups for a project' do + success Entities::PublicGroupDetails + end + params do + optional :search, type: String, desc: 'Return list of groups matching the search criteria' + optional :skip_groups, type: Array[Integer], coerce_with: ::API::Validations::Types::CommaSeparatedToIntegerArray.coerce, desc: 'Array of group ids to exclude from list' + optional :with_shared, type: Boolean, default: false, + desc: 'Include shared groups' + optional :shared_min_access_level, type: Integer, values: Gitlab::Access.all_values, + desc: 'Limit returned shared groups by minimum access level to the project' + use :pagination + end + get ':id/groups', feature_category: :source_code_management do + groups = ::Projects::GroupsFinder.new(project: user_project, current_user: current_user, params: declared_params(include_missing: false)).execute + groups = groups.search(params[:search]) if params[:search].present? + + present_groups groups + end + desc 'Start the housekeeping task for a project' do detail 'This feature was introduced in GitLab 9.0.' end diff --git a/lib/api/protected_branches.rb b/lib/api/protected_branches.rb index 17574739a7c..802dfdec511 100644 --- a/lib/api/protected_branches.rb +++ b/lib/api/protected_branches.rb @@ -60,6 +60,9 @@ module API optional :merge_access_level, type: Integer, values: ProtectedBranch::MergeAccessLevel.allowed_access_levels, desc: 'Access levels allowed to merge (defaults: `40`, maintainer access level)' + optional :allow_force_push, type: Boolean, + default: false, + desc: 'Allow force push for all users with push access.' use :optional_params_ee end diff --git a/lib/api/repositories.rb b/lib/api/repositories.rb index eaedd53aedb..f6ffeeea829 100644 --- a/lib/api/repositories.rb +++ b/lib/api/repositories.rb @@ -184,7 +184,7 @@ module API type: String, desc: 'The first commit in the range of commits to use for the changelog' - requires :to, + optional :to, type: String, desc: 'The last commit in the range of commits to use for the changelog' diff --git a/lib/api/resource_access_tokens.rb b/lib/api/resource_access_tokens.rb index 66948f9eaf3..99c278be8e7 100644 --- a/lib/api/resource_access_tokens.rb +++ b/lib/api/resource_access_tokens.rb @@ -69,7 +69,7 @@ module API ).execute if token_response.success? - present token_response.payload[:access_token], with: Entities::PersonalAccessToken + present token_response.payload[:access_token], with: Entities::PersonalAccessTokenWithToken else bad_request!(token_response.message) end diff --git a/lib/api/rubygem_packages.rb b/lib/api/rubygem_packages.rb index 7819aab879c..8d2d4586d8d 100644 --- a/lib/api/rubygem_packages.rb +++ b/lib/api/rubygem_packages.rb @@ -12,7 +12,7 @@ module API # The Marshal version can be found by "#{Marshal::MAJOR_VERSION}.#{Marshal::MINOR_VERSION}" # Updating the version should require a GitLab API version change. MARSHAL_VERSION = '4.8' - + PACKAGE_FILENAME = 'package.gem' FILE_NAME_REQUIREMENTS = { file_name: API::NO_SLASH_URL_PART_REGEX }.freeze @@ -26,7 +26,7 @@ module API before do require_packages_enabled! - authenticate! + authenticate_non_get! not_found! unless Feature.enabled?(:rubygem_packages, user_project) end @@ -64,8 +64,15 @@ module API requires :file_name, type: String, desc: 'Package file name' end get "gems/:file_name", requirements: FILE_NAME_REQUIREMENTS do - # To be implemented in https://gitlab.com/gitlab-org/gitlab/-/issues/299283 - not_found! + authorize!(:read_package, user_project) + + package_file = ::Packages::PackageFile.for_rubygem_with_file_name( + user_project, params[:file_name] + ).last! + + track_package_event('pull_package', :rubygems) + + present_carrierwave_file!(package_file.file) end namespace 'api/v1' do @@ -73,27 +80,69 @@ module API detail 'This feature was introduced in GitLab 13.9' end post 'gems/authorize' do - # To be implemented in https://gitlab.com/gitlab-org/gitlab/-/issues/299263 - not_found! + authorize_workhorse!( + subject: user_project, + has_length: false, + maximum_size: user_project.actual_limits.rubygems_max_file_size + ) end desc 'Upload a gem' do detail 'This feature was introduced in GitLab 13.9' end + params do + requires :file, type: ::API::Validations::Types::WorkhorseFile, desc: 'The package file to be published (generated by Multipart middleware)' + end post 'gems' do - # To be implemented in https://gitlab.com/gitlab-org/gitlab/-/issues/299263 - not_found! + authorize_upload!(user_project) + bad_request!('File is too large') if user_project.actual_limits.exceeded?(:rubygems_max_file_size, params[:file].size) + + track_package_event('push_package', :rubygems) + + ActiveRecord::Base.transaction do + package = ::Packages::CreateTemporaryPackageService.new( + user_project, current_user, declared_params.merge(build: current_authenticated_job) + ).execute(:rubygems, name: ::Packages::Rubygems::TEMPORARY_PACKAGE_NAME) + + file_params = { + file: params[:file], + file_name: PACKAGE_FILENAME + } + + ::Packages::CreatePackageFileService.new( + package, file_params.merge(build: current_authenticated_job) + ).execute + end + + created! + rescue ObjectStorage::RemoteStoreError => e + Gitlab::ErrorTracking.track_exception(e, extra: { file_name: params[:file_name], project_id: user_project.id }) + + forbidden! end desc 'Fetch a list of dependencies' do detail 'This feature was introduced in GitLab 13.9' end params do - optional :gems, type: String, desc: 'Comma delimited gem names' + optional :gems, type: Array[String], coerce_with: ::API::Validations::Types::CommaSeparatedToArray.coerce, desc: 'Comma delimited gem names' end get 'dependencies' do - # To be implemented in https://gitlab.com/gitlab-org/gitlab/-/issues/299282 - not_found! + authorize_read_package! + + if params[:gems].blank? + status :ok + else + results = params[:gems].map do |gem_name| + service_result = Packages::Rubygems::DependencyResolverService.new(user_project, current_user, gem_name: gem_name).execute + render_api_error!(service_result.message, service_result.http_status) if service_result.error? + + service_result.payload + end + + content_type 'application/octet-stream' + Marshal.dump(results.flatten) + end end end end diff --git a/lib/api/settings.rb b/lib/api/settings.rb index e7ee8b08d87..64a72b4cb7f 100644 --- a/lib/api/settings.rb +++ b/lib/api/settings.rb @@ -91,6 +91,7 @@ module API optional :import_sources, type: Array[String], coerce_with: Validations::Types::CommaSeparatedToArray.coerce, values: %w[github bitbucket bitbucket_server gitlab google_code fogbugz git gitlab_project gitea manifest phabricator], desc: 'Enabled sources for code import during project creation. OmniAuth must be configured for GitHub, Bitbucket, and GitLab.com' + optional :in_product_marketing_emails_enabled, type: Boolean, desc: 'By default, in-product marketing emails are enabled. To disable these emails, disable this option.' optional :invisible_captcha_enabled, type: Boolean, desc: 'Enable Invisible Captcha spam detection during signup.' optional :max_artifacts_size, type: Integer, desc: "Set the maximum file size for each job's artifacts" optional :max_attachment_size, type: Integer, desc: 'Maximum attachment size in MB' diff --git a/lib/api/snippet_repository_storage_moves.rb b/lib/api/snippet_repository_storage_moves.rb index 84dbc03ba33..e3034191641 100644 --- a/lib/api/snippet_repository_storage_moves.rb +++ b/lib/api/snippet_repository_storage_moves.rb @@ -11,28 +11,28 @@ module API resource :snippet_repository_storage_moves do desc 'Get a list of all snippet repository storage moves' do detail 'This feature was introduced in GitLab 13.8.' - success Entities::SnippetRepositoryStorageMove + success Entities::Snippets::RepositoryStorageMove end params do use :pagination end get do - storage_moves = SnippetRepositoryStorageMove.order_created_at_desc + storage_moves = ::Snippets::RepositoryStorageMove.order_created_at_desc - present paginate(storage_moves), with: Entities::SnippetRepositoryStorageMove, current_user: current_user + present paginate(storage_moves), with: Entities::Snippets::RepositoryStorageMove, current_user: current_user end desc 'Get a snippet repository storage move' do detail 'This feature was introduced in GitLab 13.8.' - success Entities::SnippetRepositoryStorageMove + success Entities::Snippets::RepositoryStorageMove end params do requires :repository_storage_move_id, type: Integer, desc: 'The ID of a snippet repository storage move' end get ':repository_storage_move_id' do - storage_move = SnippetRepositoryStorageMove.find(params[:repository_storage_move_id]) + storage_move = ::Snippets::RepositoryStorageMove.find(params[:repository_storage_move_id]) - present storage_move, with: Entities::SnippetRepositoryStorageMove, current_user: current_user + present storage_move, with: Entities::Snippets::RepositoryStorageMove, current_user: current_user end desc 'Schedule bulk snippet repository storage moves' do @@ -68,7 +68,7 @@ module API desc 'Get a list of all snippets repository storage moves' do detail 'This feature was introduced in GitLab 13.8.' - success Entities::SnippetRepositoryStorageMove + success Entities::Snippets::RepositoryStorageMove end params do use :pagination @@ -76,12 +76,12 @@ module API get ':id/repository_storage_moves' do storage_moves = user_snippet.repository_storage_moves.order_created_at_desc - present paginate(storage_moves), with: Entities::SnippetRepositoryStorageMove, current_user: current_user + present paginate(storage_moves), with: Entities::Snippets::RepositoryStorageMove, current_user: current_user end desc 'Get a snippet repository storage move' do detail 'This feature was introduced in GitLab 13.8.' - success Entities::SnippetRepositoryStorageMove + success Entities::Snippets::RepositoryStorageMove end params do requires :repository_storage_move_id, type: Integer, desc: 'The ID of a snippet repository storage move' @@ -89,12 +89,12 @@ module API get ':id/repository_storage_moves/:repository_storage_move_id' do storage_move = user_snippet.repository_storage_moves.find(params[:repository_storage_move_id]) - present storage_move, with: Entities::SnippetRepositoryStorageMove, current_user: current_user + present storage_move, with: Entities::Snippets::RepositoryStorageMove, current_user: current_user end desc 'Schedule a snippet repository storage move' do detail 'This feature was introduced in GitLab 13.8.' - success Entities::SnippetRepositoryStorageMove + success Entities::Snippets::RepositoryStorageMove end params do optional :destination_storage_name, type: String, desc: 'The destination storage shard' @@ -105,7 +105,7 @@ module API ) if storage_move.schedule - present storage_move, with: Entities::SnippetRepositoryStorageMove, current_user: current_user + present storage_move, with: Entities::Snippets::RepositoryStorageMove, current_user: current_user else render_validation_error!(storage_move) end diff --git a/lib/api/users.rb b/lib/api/users.rb index f91e3c34ef2..b2f99bb18dc 100644 --- a/lib/api/users.rb +++ b/lib/api/users.rb @@ -58,6 +58,7 @@ module API optional :color_scheme_id, type: Integer, desc: 'The color scheme for the file viewer' optional :private_profile, type: Boolean, desc: 'Flag indicating the user has a private profile' optional :note, type: String, desc: 'Admin note for this user' + optional :view_diffs_file_by_file, type: Boolean, desc: 'Flag indicating the user sees only one file diff per page' all_or_none_of :extern_uid, :provider use :optional_params_ee @@ -82,6 +83,7 @@ module API optional :search, type: String, desc: 'Search for a username' optional :active, type: Boolean, default: false, desc: 'Filters only active users' optional :external, type: Boolean, default: false, desc: 'Filters only external users' + optional :exclude_external, as: :non_external, type: Boolean, default: false, desc: 'Filters only non external users' optional :blocked, type: Boolean, default: false, desc: 'Filters only blocked users' optional :created_after, type: DateTime, desc: 'Return users created after the specified time' optional :created_before, type: DateTime, desc: 'Return users created before the specified time' @@ -97,7 +99,7 @@ module API end # rubocop: disable CodeReuse/ActiveRecord get feature_category: :users do - authenticated_as_admin! if params[:external].present? || (params[:extern_uid].present? && params[:provider].present?) + authenticated_as_admin! if params[:extern_uid].present? && params[:provider].present? unless current_user&.admin? params.except!(:created_after, :created_before, :order_by, :sort, :two_factor, :without_projects) @@ -1071,10 +1073,7 @@ module API put "status", feature_category: :users do forbidden! unless can?(current_user, :update_user_status, current_user) - update_params = declared_params - update_params.delete(:clear_status_after) if Feature.disabled?(:clear_status_with_quick_options, current_user, default_enabled: :yaml) - - if ::Users::SetStatusService.new(current_user, update_params).execute + if ::Users::SetStatusService.new(current_user, declared_params).execute present current_user.status, with: Entities::UserStatus else render_validation_error!(current_user.status) diff --git a/lib/api/v3/github.rb b/lib/api/v3/github.rb index 327335aec2d..2d25e76626a 100644 --- a/lib/api/v3/github.rb +++ b/lib/api/v3/github.rb @@ -194,13 +194,16 @@ 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/wikis.rb b/lib/api/wikis.rb index 3fa42be47a9..8441aeb10ab 100644 --- a/lib/api/wikis.rb +++ b/lib/api/wikis.rb @@ -111,7 +111,7 @@ module API if response.success? no_content! else - render_api_error!(reponse.message) + unprocessable_entity!(response.message) end end |