summaryrefslogtreecommitdiff
path: root/app/graphql/mutations
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2021-04-20 23:50:22 +0000
committerGitLab Bot <gitlab-bot@gitlab.com>2021-04-20 23:50:22 +0000
commit9dc93a4519d9d5d7be48ff274127136236a3adb3 (patch)
tree70467ae3692a0e35e5ea56bcb803eb512a10bedb /app/graphql/mutations
parent4b0f34b6d759d6299322b3a54453e930c6121ff0 (diff)
downloadgitlab-ce-9dc93a4519d9d5d7be48ff274127136236a3adb3.tar.gz
Add latest changes from gitlab-org/gitlab@13-11-stable-eev13.11.0-rc43
Diffstat (limited to 'app/graphql/mutations')
-rw-r--r--app/graphql/mutations/admin/sidekiq_queues/delete_jobs.rb2
-rw-r--r--app/graphql/mutations/alert_management/prometheus_integration/create.rb2
-rw-r--r--app/graphql/mutations/alert_management/prometheus_integration/update.rb2
-rw-r--r--app/graphql/mutations/base_mutation.rb29
-rw-r--r--app/graphql/mutations/boards/issues/issue_move_list.rb15
-rw-r--r--app/graphql/mutations/ci/ci_cd_settings_update.rb12
-rw-r--r--app/graphql/mutations/concerns/mutations/assignable.rb46
-rw-r--r--app/graphql/mutations/concerns/mutations/can_mutate_spammable.rb41
-rw-r--r--app/graphql/mutations/concerns/mutations/spam_protection.rb67
-rw-r--r--app/graphql/mutations/container_repositories/destroy_tags.rb2
-rw-r--r--app/graphql/mutations/issues/move.rb2
-rw-r--r--app/graphql/mutations/issues/set_assignees.rb13
-rw-r--r--app/graphql/mutations/merge_requests/accept.rb3
-rw-r--r--app/graphql/mutations/merge_requests/set_assignees.rb2
-rw-r--r--app/graphql/mutations/release_asset_links/delete.rb40
-rw-r--r--app/graphql/mutations/snippets/create.rb13
-rw-r--r--app/graphql/mutations/snippets/update.rb13
17 files changed, 222 insertions, 82 deletions
diff --git a/app/graphql/mutations/admin/sidekiq_queues/delete_jobs.rb b/app/graphql/mutations/admin/sidekiq_queues/delete_jobs.rb
index 32ca6de9b96..ea1502d4b62 100644
--- a/app/graphql/mutations/admin/sidekiq_queues/delete_jobs.rb
+++ b/app/graphql/mutations/admin/sidekiq_queues/delete_jobs.rb
@@ -8,7 +8,7 @@ module Mutations
ADMIN_MESSAGE = 'You must be an admin to use this mutation'
- Labkit::Context::KNOWN_KEYS.each do |key|
+ Gitlab::ApplicationContext::KNOWN_KEYS.each do |key|
argument key,
GraphQL::STRING_TYPE,
required: false,
diff --git a/app/graphql/mutations/alert_management/prometheus_integration/create.rb b/app/graphql/mutations/alert_management/prometheus_integration/create.rb
index 87e6bc46937..c6dc85dc07c 100644
--- a/app/graphql/mutations/alert_management/prometheus_integration/create.rb
+++ b/app/graphql/mutations/alert_management/prometheus_integration/create.rb
@@ -18,7 +18,7 @@ module Mutations
argument :api_url, GraphQL::STRING_TYPE,
required: true,
- description: 'Endpoint at which prometheus can be queried.'
+ description: 'Endpoint at which Prometheus can be queried.'
def resolve(args)
project = authorized_find!(args[:project_path])
diff --git a/app/graphql/mutations/alert_management/prometheus_integration/update.rb b/app/graphql/mutations/alert_management/prometheus_integration/update.rb
index 62fb81bca5a..7594766176f 100644
--- a/app/graphql/mutations/alert_management/prometheus_integration/update.rb
+++ b/app/graphql/mutations/alert_management/prometheus_integration/update.rb
@@ -16,7 +16,7 @@ module Mutations
argument :api_url, GraphQL::STRING_TYPE,
required: false,
- description: "Endpoint at which prometheus can be queried."
+ description: "Endpoint at which Prometheus can be queried."
def resolve(args)
integration = authorized_find!(id: args[:id])
diff --git a/app/graphql/mutations/base_mutation.rb b/app/graphql/mutations/base_mutation.rb
index ac5ddc5bd4c..1f18a37fcb9 100644
--- a/app/graphql/mutations/base_mutation.rb
+++ b/app/graphql/mutations/base_mutation.rb
@@ -2,13 +2,14 @@
module Mutations
class BaseMutation < GraphQL::Schema::RelayClassicMutation
- prepend Gitlab::Graphql::Authorize::AuthorizeResource
+ include Gitlab::Graphql::Authorize::AuthorizeResource
prepend Gitlab::Graphql::CopyFieldDescription
prepend ::Gitlab::Graphql::GlobalIDCompatibility
ERROR_MESSAGE = 'You cannot perform write operations on a read-only instance'
field_class ::Types::BaseField
+ argument_class ::Types::BaseArgument
field :errors, [GraphQL::STRING_TYPE],
null: false,
@@ -28,11 +29,29 @@ module Mutations
end
def ready?(**args)
- if Gitlab::Database.read_only?
- raise Gitlab::Graphql::Errors::ResourceNotAvailable, ERROR_MESSAGE
- else
- true
+ raise_resource_not_available_error! ERROR_MESSAGE if Gitlab::Database.read_only?
+
+ true
+ end
+
+ def load_application_object(argument, lookup_as_type, id, context)
+ ::Gitlab::Graphql::Lazy.new { super }.catch(::GraphQL::UnauthorizedError) do |e|
+ Gitlab::ErrorTracking.track_exception(e)
+ # The default behaviour is to abort processing and return nil for the
+ # entire mutation field, but not set any top-level errors. We prefer to
+ # at least say that something went wrong.
+ raise_resource_not_available_error!
end
end
+
+ def self.authorized?(object, context)
+ # we never provide an object to mutations, but we do need to have a user.
+ context[:current_user].present? && !context[:current_user].blocked?
+ end
+
+ # See: AuthorizeResource#authorized_resource?
+ def self.authorization
+ @authorization ||= ::Gitlab::Graphql::Authorize::ObjectAuthorization.new(authorize)
+ end
end
end
diff --git a/app/graphql/mutations/boards/issues/issue_move_list.rb b/app/graphql/mutations/boards/issues/issue_move_list.rb
index 096ac89db1c..f32205643da 100644
--- a/app/graphql/mutations/boards/issues/issue_move_list.rb
+++ b/app/graphql/mutations/boards/issues/issue_move_list.rb
@@ -52,13 +52,10 @@ module Mutations
super
end
- def resolve(board:, **args)
- Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab/-/issues/247861')
+ def resolve(board:, project_path:, iid:, **args)
+ Gitlab::QueryLimiting.disable!('https://gitlab.com/gitlab-org/gitlab/-/issues/247861')
- raise_resource_not_available_error! unless board
- authorize_board!(board)
-
- issue = authorized_find!(project_path: args[:project_path], iid: args[:iid])
+ issue = authorized_find!(project_path: project_path, iid: iid)
move_params = { id: issue.id, board_id: board.id }.merge(move_arguments(args))
move_issue(board, issue, move_params)
@@ -84,12 +81,6 @@ module Mutations
def move_arguments(args)
args.slice(:from_list_id, :to_list_id, :move_after_id, :move_before_id)
end
-
- def authorize_board!(board)
- return if Ability.allowed?(current_user, :read_issue_board, board.resource_parent)
-
- raise_resource_not_available_error!
- end
end
end
end
diff --git a/app/graphql/mutations/ci/ci_cd_settings_update.rb b/app/graphql/mutations/ci/ci_cd_settings_update.rb
index 6b7750ee860..d7451babaea 100644
--- a/app/graphql/mutations/ci/ci_cd_settings_update.rb
+++ b/app/graphql/mutations/ci/ci_cd_settings_update.rb
@@ -17,13 +17,23 @@ module Mutations
required: false,
description: 'Indicates if the latest artifact should be kept for this project.'
+ field :ci_cd_settings,
+ Types::Ci::CiCdSettingType,
+ null: false,
+ description: 'The CI/CD settings after mutation.'
+
def resolve(full_path:, **args)
project = authorized_find!(full_path)
settings = project.ci_cd_settings
settings.update(args)
- { errors: errors_on_object(settings) }
+ {
+ ci_cd_settings: settings,
+ errors: errors_on_object(settings)
+ }
end
end
end
end
+
+Mutations::Ci::CiCdSettingsUpdate.prepend_if_ee('::EE::Mutations::Ci::CiCdSettingsUpdate')
diff --git a/app/graphql/mutations/concerns/mutations/assignable.rb b/app/graphql/mutations/concerns/mutations/assignable.rb
index f6f4b744f4e..d3ab0a1779a 100644
--- a/app/graphql/mutations/concerns/mutations/assignable.rb
+++ b/app/graphql/mutations/concerns/mutations/assignable.rb
@@ -13,19 +13,15 @@ module Mutations
argument :operation_mode,
Types::MutationOperationModeEnum,
required: false,
+ default_value: Types::MutationOperationModeEnum.default_mode,
description: 'The operation to perform. Defaults to REPLACE.'
end
- def resolve(project_path:, iid:, assignee_usernames:, operation_mode: Types::MutationOperationModeEnum.enum[:replace])
+ def resolve(project_path:, iid:, assignee_usernames:, operation_mode:)
resource = authorized_find!(project_path: project_path, iid: iid)
+ users = new_assignees(resource, assignee_usernames)
- Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab/issues/36098') if resource.is_a?(MergeRequest)
-
- update_service_class.new(
- resource.project,
- current_user,
- assignee_ids: assignee_ids(resource, assignee_usernames, operation_mode)
- ).execute(resource)
+ assign!(resource, users, operation_mode)
{
resource.class.name.underscore.to_sym => resource,
@@ -35,18 +31,32 @@ module Mutations
private
- def assignee_ids(resource, usernames, operation_mode)
- assignee_ids = []
- assignee_ids += resource.assignees.map(&:id) if Types::MutationOperationModeEnum.enum.values_at(:remove, :append).include?(operation_mode)
- user_ids = UsersFinder.new(current_user, username: usernames).execute.map(&:id)
+ def assign!(resource, users, operation_mode)
+ update_service_class.new(
+ resource.project,
+ current_user,
+ assignee_ids: assignee_ids(resource, users, operation_mode)
+ ).execute(resource)
+ end
- if operation_mode == Types::MutationOperationModeEnum.enum[:remove]
- assignee_ids -= user_ids
- else
- assignee_ids |= user_ids
- end
+ def new_assignees(resource, usernames)
+ UsersFinder.new(current_user, username: usernames).execute.to_a
+ end
+
+ def assignee_ids(resource, users, mode)
+ transform_list(mode, resource, users.map(&:id))
+ end
+
+ def current_assignee_ids(resource)
+ resource.assignees.map(&:id)
+ end
- assignee_ids
+ def transform_list(mode, resource, new_values)
+ case mode
+ when 'REPLACE' then new_values
+ when 'APPEND' then current_assignee_ids(resource) | new_values
+ when 'REMOVE' then current_assignee_ids(resource) - new_values
+ end
end
end
end
diff --git a/app/graphql/mutations/concerns/mutations/can_mutate_spammable.rb b/app/graphql/mutations/concerns/mutations/can_mutate_spammable.rb
index ba644eff36c..3c5f077110c 100644
--- a/app/graphql/mutations/concerns/mutations/can_mutate_spammable.rb
+++ b/app/graphql/mutations/concerns/mutations/can_mutate_spammable.rb
@@ -1,64 +1,51 @@
# frozen_string_literal: true
module Mutations
- # This concern can be mixed into a mutation to provide support for spam checking,
- # and optionally support the workflow to allow clients to display and solve CAPTCHAs.
+ # This concern is deprecated and will be deleted in 14.6
+ #
+ # Use the SpamProtection concern instead.
module CanMutateSpammable
extend ActiveSupport::Concern
- include Spam::Concerns::HasSpamActionResponseFields
- # NOTE: The arguments and fields are intentionally named with 'captcha' instead of 'recaptcha',
- # so that they can be applied to future alternative CAPTCHA implementations other than
- # reCAPTCHA (e.g. FriendlyCaptcha) without having to change the names and descriptions in the API.
+ DEPRECATION_NOTICE = {
+ reason: 'Use spam protection with HTTP headers instead',
+ milestone: '13.11'
+ }.freeze
+
included do
argument :captcha_response, GraphQL::STRING_TYPE,
required: false,
+ deprecated: DEPRECATION_NOTICE,
description: 'A valid CAPTCHA response value obtained by using the provided captchaSiteKey with a CAPTCHA API to present a challenge to be solved on the client. Required to resubmit if the previous operation returned "NeedsCaptchaResponse: true".'
argument :spam_log_id, GraphQL::INT_TYPE,
required: false,
+ deprecated: DEPRECATION_NOTICE,
description: 'The spam log ID which must be passed along with a valid CAPTCHA response for the operation to be completed. Required to resubmit if the previous operation returned "NeedsCaptchaResponse: true".'
field :spam,
GraphQL::BOOLEAN_TYPE,
null: true,
+ deprecated: DEPRECATION_NOTICE,
description: 'Indicates whether the operation was detected as definite spam. There is no option to resubmit the request with a CAPTCHA response.'
field :needs_captcha_response,
GraphQL::BOOLEAN_TYPE,
null: true,
+ deprecated: DEPRECATION_NOTICE,
description: 'Indicates whether the operation was detected as possible spam and not completed. If CAPTCHA is enabled, the request must be resubmitted with a valid CAPTCHA response and spam_log_id included for the operation to be completed. Included only when an operation was not completed because "NeedsCaptchaResponse" is true.'
field :spam_log_id,
GraphQL::INT_TYPE,
null: true,
+ deprecated: DEPRECATION_NOTICE,
description: 'The spam log ID which must be passed along with a valid CAPTCHA response for an operation to be completed. Included only when an operation was not completed because "NeedsCaptchaResponse" is true.'
field :captcha_site_key,
GraphQL::STRING_TYPE,
null: true,
+ deprecated: DEPRECATION_NOTICE,
description: 'The CAPTCHA site key which must be used to render a challenge for the user to solve to obtain a valid captchaResponse value. Included only when an operation was not completed because "NeedsCaptchaResponse" is true.'
end
-
- private
-
- # additional_spam_params -> hash
- #
- # Used from a spammable mutation's #resolve method to generate
- # the required additional spam/recaptcha params which must be merged into the params
- # passed to the constructor of a service, where they can then be used in the service
- # to perform spam checking via SpamActionService.
- #
- # Also accesses the #context of the mutation's Resolver superclass to obtain the request.
- #
- # Example:
- #
- # existing_args.merge!(additional_spam_params)
- def additional_spam_params
- {
- api: true,
- request: context[:request]
- }
- end
end
end
diff --git a/app/graphql/mutations/concerns/mutations/spam_protection.rb b/app/graphql/mutations/concerns/mutations/spam_protection.rb
new file mode 100644
index 00000000000..d765da23a4b
--- /dev/null
+++ b/app/graphql/mutations/concerns/mutations/spam_protection.rb
@@ -0,0 +1,67 @@
+# frozen_string_literal: true
+
+module Mutations
+ # This concern can be mixed into a mutation to provide support for spam checking,
+ # and optionally support the workflow to allow clients to display and solve CAPTCHAs.
+ module SpamProtection
+ extend ActiveSupport::Concern
+ include Spam::Concerns::HasSpamActionResponseFields
+
+ SpamActionError = Class.new(GraphQL::ExecutionError)
+ NeedsCaptchaResponseError = Class.new(SpamActionError)
+ SpamDisallowedError = Class.new(SpamActionError)
+
+ NEEDS_CAPTCHA_RESPONSE_MESSAGE = "Request denied. Solve CAPTCHA challenge and retry"
+ SPAM_DISALLOWED_MESSAGE = "Request denied. Spam detected"
+
+ private
+
+ # additional_spam_params -> hash
+ #
+ # Used from a spammable mutation's #resolve method to generate
+ # the required additional spam/CAPTCHA params which must be merged into the params
+ # passed to the constructor of a service, where they can then be used in the service
+ # to perform spam checking via SpamActionService.
+ #
+ # Also accesses the #context of the mutation's Resolver superclass to obtain the request.
+ #
+ # Example:
+ #
+ # existing_args.merge!(additional_spam_params)
+ def additional_spam_params
+ {
+ api: true,
+ request: context[:request]
+ }
+ end
+
+ def spam_action_response(object)
+ fields = spam_action_response_fields(object)
+
+ # If the SpamActionService detected something as spam,
+ # this is non-recoverable and the needs_captcha_response
+ # should not be considered
+ kind = if fields[:spam]
+ :spam
+ elsif fields[:needs_captcha_response]
+ :needs_captcha_response
+ end
+
+ [kind, fields]
+ end
+
+ def check_spam_action_response!(object)
+ kind, fields = spam_action_response(object)
+
+ case kind
+ when :needs_captcha_response
+ fields.delete :spam
+ raise NeedsCaptchaResponseError.new(NEEDS_CAPTCHA_RESPONSE_MESSAGE, extensions: fields)
+ when :spam
+ raise SpamDisallowedError.new(SPAM_DISALLOWED_MESSAGE, extensions: { spam: true })
+ else
+ nil
+ end
+ end
+ end
+end
diff --git a/app/graphql/mutations/container_repositories/destroy_tags.rb b/app/graphql/mutations/container_repositories/destroy_tags.rb
index 636ceccee04..12d65f604b8 100644
--- a/app/graphql/mutations/container_repositories/destroy_tags.rb
+++ b/app/graphql/mutations/container_repositories/destroy_tags.rb
@@ -3,7 +3,7 @@
module Mutations
module ContainerRepositories
class DestroyTags < ::Mutations::ContainerRepositories::DestroyBase
- LIMIT = 20.freeze
+ LIMIT = 20
TOO_MANY_TAGS_ERROR_MESSAGE = "Number of tags is greater than #{LIMIT}"
diff --git a/app/graphql/mutations/issues/move.rb b/app/graphql/mutations/issues/move.rb
index 3f97325c921..0f2af99bf61 100644
--- a/app/graphql/mutations/issues/move.rb
+++ b/app/graphql/mutations/issues/move.rb
@@ -11,7 +11,7 @@ module Mutations
description: 'The project to move the issue to.'
def resolve(project_path:, iid:, target_project_path:)
- Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab/-/issues/267762')
+ Gitlab::QueryLimiting.disable!('https://gitlab.com/gitlab-org/gitlab/-/issues/20816')
issue = authorized_find!(project_path: project_path, iid: iid)
source_project = issue.project
diff --git a/app/graphql/mutations/issues/set_assignees.rb b/app/graphql/mutations/issues/set_assignees.rb
index a4d1c755b53..8413c89b010 100644
--- a/app/graphql/mutations/issues/set_assignees.rb
+++ b/app/graphql/mutations/issues/set_assignees.rb
@@ -7,6 +7,19 @@ module Mutations
include Assignable
+ def assign!(issue, users, mode)
+ permitted, forbidden = users.partition { |u| u.can?(:read_issue, issue) }
+
+ super(issue, permitted, mode)
+
+ forbidden.each do |user|
+ issue.errors.add(
+ :assignees,
+ "Cannot assign #{user.to_reference} to #{issue.to_reference}"
+ )
+ end
+ end
+
def update_service_class
::Issues::UpdateService
end
diff --git a/app/graphql/mutations/merge_requests/accept.rb b/app/graphql/mutations/merge_requests/accept.rb
index 540be7098ac..da94dcd8890 100644
--- a/app/graphql/mutations/merge_requests/accept.rb
+++ b/app/graphql/mutations/merge_requests/accept.rb
@@ -42,7 +42,8 @@ module Mutations
description: 'Squash commits on the source branch before merge.'
def resolve(project_path:, iid:, **args)
- Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab-foss/issues/42317')
+ Gitlab::QueryLimiting.disable!('https://gitlab.com/gitlab-org/gitlab/-/issues/4796')
+
merge_request = authorized_find!(project_path: project_path, iid: iid)
project = merge_request.target_project
merge_params = args.compact.with_indifferent_access
diff --git a/app/graphql/mutations/merge_requests/set_assignees.rb b/app/graphql/mutations/merge_requests/set_assignees.rb
index 548c6b55a85..dc96523685e 100644
--- a/app/graphql/mutations/merge_requests/set_assignees.rb
+++ b/app/graphql/mutations/merge_requests/set_assignees.rb
@@ -8,7 +8,7 @@ module Mutations
include Assignable
def update_service_class
- ::MergeRequests::UpdateService
+ ::MergeRequests::UpdateAssigneesService
end
end
end
diff --git a/app/graphql/mutations/release_asset_links/delete.rb b/app/graphql/mutations/release_asset_links/delete.rb
new file mode 100644
index 00000000000..dd450f36cdd
--- /dev/null
+++ b/app/graphql/mutations/release_asset_links/delete.rb
@@ -0,0 +1,40 @@
+# frozen_string_literal: true
+
+module Mutations
+ module ReleaseAssetLinks
+ class Delete < BaseMutation
+ graphql_name 'ReleaseAssetLinkDelete'
+
+ authorize :destroy_release
+
+ ReleaseAssetLinkID = ::Types::GlobalIDType[::Releases::Link]
+
+ argument :id, ReleaseAssetLinkID,
+ required: true,
+ description: 'ID of the release asset link to delete.'
+
+ field :link,
+ Types::ReleaseAssetLinkType,
+ null: true,
+ description: 'The deleted release asset link.'
+
+ def resolve(id:)
+ link = authorized_find!(id)
+
+ unless link.destroy
+ return { link: nil, errors: link.errors.full_messages }
+ end
+
+ { link: link, errors: [] }
+ end
+
+ def find_object(id)
+ # TODO: remove this line when the compatibility layer is removed
+ # See: https://gitlab.com/gitlab-org/gitlab/-/issues/257883
+ id = ReleaseAssetLinkID.coerce_isolated_input(id)
+
+ GitlabSchema.find_by_gid(id)
+ end
+ end
+ end
+end
diff --git a/app/graphql/mutations/snippets/create.rb b/app/graphql/mutations/snippets/create.rb
index 7f2dd448b8b..e9b45294659 100644
--- a/app/graphql/mutations/snippets/create.rb
+++ b/app/graphql/mutations/snippets/create.rb
@@ -5,6 +5,7 @@ module Mutations
class Create < BaseMutation
include ServiceCompatibility
include CanMutateSpammable
+ include Mutations::SpamProtection
authorize :create_snippet
@@ -56,12 +57,12 @@ module Mutations
end
snippet = service_response.payload[:snippet]
- with_spam_action_response_fields(snippet) do
- {
- snippet: service_response.success? ? snippet : nil,
- errors: errors_on_object(snippet)
- }
- end
+ check_spam_action_response!(snippet)
+
+ {
+ snippet: service_response.success? ? snippet : nil,
+ errors: errors_on_object(snippet)
+ }
end
private
diff --git a/app/graphql/mutations/snippets/update.rb b/app/graphql/mutations/snippets/update.rb
index 9f9f8bca848..b9b9b13eebb 100644
--- a/app/graphql/mutations/snippets/update.rb
+++ b/app/graphql/mutations/snippets/update.rb
@@ -5,6 +5,7 @@ module Mutations
class Update < Base
include ServiceCompatibility
include CanMutateSpammable
+ include Mutations::SpamProtection
graphql_name 'UpdateSnippet'
@@ -45,12 +46,12 @@ module Mutations
end
snippet = service_response.payload[:snippet]
- with_spam_action_response_fields(snippet) do
- {
- snippet: service_response.success? ? snippet : snippet.reset,
- errors: errors_on_object(snippet)
- }
- end
+ check_spam_action_response!(snippet)
+
+ {
+ snippet: service_response.success? ? snippet : snippet.reset,
+ errors: errors_on_object(snippet)
+ }
end
private