From 859a6fb938bb9ee2a317c46dfa4fcc1af49608f0 Mon Sep 17 00:00:00 2001 From: GitLab Bot Date: Thu, 18 Feb 2021 10:34:06 +0000 Subject: Add latest changes from gitlab-org/gitlab@13-9-stable-ee --- app/graphql/mutations/alert_management/base.rb | 2 +- .../alert_management/http_integration/create.rb | 19 +---- .../http_integration/http_integration_base.rb | 7 ++ .../alert_management/http_integration/update.rb | 4 +- .../prometheus_integration/create.rb | 8 +-- app/graphql/mutations/boards/lists/base.rb | 28 -------- app/graphql/mutations/boards/lists/base_create.rb | 55 ++++++++++++++ app/graphql/mutations/boards/lists/create.rb | 51 ++++--------- app/graphql/mutations/branches/create.rb | 10 +-- app/graphql/mutations/commits/create.rb | 10 +-- .../concerns/mutations/can_mutate_spammable.rb | 83 ++++++++++++++++++++++ .../concerns/mutations/resolves_resource_parent.rb | 4 +- .../mutations/spammable_mutation_fields.rb | 24 ------- .../container_expiration_policies/update.rb | 10 +-- .../mutations/discussions/toggle_resolve.rb | 2 +- app/graphql/mutations/issues/create.rb | 8 +-- app/graphql/mutations/jira_import/import_users.rb | 16 ++--- app/graphql/mutations/jira_import/start.rb | 16 ++--- app/graphql/mutations/merge_requests/create.rb | 10 +-- .../mutations/merge_requests/reviewer_rereview.rb | 27 +++++++ app/graphql/mutations/merge_requests/update.rb | 11 ++- app/graphql/mutations/notes/create/base.rb | 15 ++++ app/graphql/mutations/releases/base.rb | 8 +-- app/graphql/mutations/releases/create.rb | 2 +- app/graphql/mutations/releases/delete.rb | 2 +- app/graphql/mutations/releases/update.rb | 2 +- .../security/ci_configuration/configure_sast.rb | 46 ++++++++++++ app/graphql/mutations/snippets/create.rb | 41 ++++++----- .../mutations/snippets/service_compatibility.rb | 23 ++++++ app/graphql/mutations/snippets/update.rb | 48 ++++++++----- app/graphql/mutations/todos/create.rb | 2 +- app/graphql/mutations/todos/mark_all_done.rb | 6 +- app/graphql/mutations/todos/mark_done.rb | 4 +- app/graphql/mutations/todos/restore.rb | 4 +- app/graphql/mutations/todos/restore_many.rb | 10 +-- 35 files changed, 377 insertions(+), 241 deletions(-) delete mode 100644 app/graphql/mutations/boards/lists/base.rb create mode 100644 app/graphql/mutations/boards/lists/base_create.rb create mode 100644 app/graphql/mutations/concerns/mutations/can_mutate_spammable.rb delete mode 100644 app/graphql/mutations/concerns/mutations/spammable_mutation_fields.rb create mode 100644 app/graphql/mutations/merge_requests/reviewer_rereview.rb create mode 100644 app/graphql/mutations/security/ci_configuration/configure_sast.rb create mode 100644 app/graphql/mutations/snippets/service_compatibility.rb (limited to 'app/graphql/mutations') diff --git a/app/graphql/mutations/alert_management/base.rb b/app/graphql/mutations/alert_management/base.rb index 3a57cb9670d..86908c1449c 100644 --- a/app/graphql/mutations/alert_management/base.rb +++ b/app/graphql/mutations/alert_management/base.rb @@ -21,7 +21,7 @@ module Mutations field :todo, Types::TodoType, null: true, - description: "The todo after mutation." + description: "The to-do item after mutation." field :issue, Types::IssueType, diff --git a/app/graphql/mutations/alert_management/http_integration/create.rb b/app/graphql/mutations/alert_management/http_integration/create.rb index ff165d7f302..2d7bffb4333 100644 --- a/app/graphql/mutations/alert_management/http_integration/create.rb +++ b/app/graphql/mutations/alert_management/http_integration/create.rb @@ -4,7 +4,7 @@ module Mutations module AlertManagement module HttpIntegration class Create < HttpIntegrationBase - include ResolvesProject + include FindsProject graphql_name 'HttpIntegrationCreate' @@ -21,27 +21,14 @@ module Mutations description: 'Whether the integration is receiving alerts.' def resolve(args) - @project = authorized_find!(full_path: args[:project_path]) + project = authorized_find!(args[:project_path]) response ::AlertManagement::HttpIntegrations::CreateService.new( project, current_user, - http_integration_params(args) + http_integration_params(project, args) ).execute end - - private - - attr_reader :project - - def find_object(full_path:) - resolve_project(full_path: full_path) - end - - # overriden in EE - def http_integration_params(args) - args.slice(:name, :active) - end end end end diff --git a/app/graphql/mutations/alert_management/http_integration/http_integration_base.rb b/app/graphql/mutations/alert_management/http_integration/http_integration_base.rb index 147df982bec..e33b7bb399a 100644 --- a/app/graphql/mutations/alert_management/http_integration/http_integration_base.rb +++ b/app/graphql/mutations/alert_management/http_integration/http_integration_base.rb @@ -23,7 +23,14 @@ module Mutations errors: result.errors } end + + # overriden in EE + def http_integration_params(_project, args) + args.slice(:name, :active) + end end end end end + +Mutations::AlertManagement::HttpIntegration::HttpIntegrationBase.prepend_if_ee('::EE::Mutations::AlertManagement::HttpIntegration::HttpIntegrationBase') diff --git a/app/graphql/mutations/alert_management/http_integration/update.rb b/app/graphql/mutations/alert_management/http_integration/update.rb index 431fccaa5e5..b1e4ce841ee 100644 --- a/app/graphql/mutations/alert_management/http_integration/update.rb +++ b/app/graphql/mutations/alert_management/http_integration/update.rb @@ -24,10 +24,12 @@ module Mutations response ::AlertManagement::HttpIntegrations::UpdateService.new( integration, current_user, - args.slice(:name, :active) + http_integration_params(integration.project, args) ).execute end end end end end + +Mutations::AlertManagement::HttpIntegration::Update.prepend_if_ee('::EE::Mutations::AlertManagement::HttpIntegration::Update') diff --git a/app/graphql/mutations/alert_management/prometheus_integration/create.rb b/app/graphql/mutations/alert_management/prometheus_integration/create.rb index c676cde90b4..87e6bc46937 100644 --- a/app/graphql/mutations/alert_management/prometheus_integration/create.rb +++ b/app/graphql/mutations/alert_management/prometheus_integration/create.rb @@ -4,7 +4,7 @@ module Mutations module AlertManagement module PrometheusIntegration class Create < PrometheusIntegrationBase - include ResolvesProject + include FindsProject graphql_name 'PrometheusIntegrationCreate' @@ -21,7 +21,7 @@ module Mutations description: 'Endpoint at which prometheus can be queried.' def resolve(args) - project = authorized_find!(full_path: args[:project_path]) + project = authorized_find!(args[:project_path]) return integration_exists if project.prometheus_service @@ -37,10 +37,6 @@ module Mutations private - def find_object(full_path:) - resolve_project(full_path: full_path) - end - def integration_exists response(nil, message: _('Multiple Prometheus integrations are not supported')) end diff --git a/app/graphql/mutations/boards/lists/base.rb b/app/graphql/mutations/boards/lists/base.rb deleted file mode 100644 index 34c138bddc9..00000000000 --- a/app/graphql/mutations/boards/lists/base.rb +++ /dev/null @@ -1,28 +0,0 @@ -# frozen_string_literal: true - -module Mutations - module Boards - module Lists - class Base < BaseMutation - include Mutations::ResolvesIssuable - - argument :board_id, ::Types::GlobalIDType[::Board], - required: true, - description: 'Global ID of the issue board to mutate.' - - field :list, - Types::BoardListType, - null: true, - description: 'List of the issue board.' - - authorize :admin_list - - private - - def find_object(id:) - GitlabSchema.object_from_id(id, expected_type: ::Board) - end - end - end - end -end diff --git a/app/graphql/mutations/boards/lists/base_create.rb b/app/graphql/mutations/boards/lists/base_create.rb new file mode 100644 index 00000000000..a21c7feece3 --- /dev/null +++ b/app/graphql/mutations/boards/lists/base_create.rb @@ -0,0 +1,55 @@ +# frozen_string_literal: true + +module Mutations + module Boards + module Lists + class BaseCreate < BaseMutation + argument :backlog, GraphQL::BOOLEAN_TYPE, + required: false, + description: 'Create the backlog list.' + + argument :label_id, ::Types::GlobalIDType[::Label], + required: false, + description: 'Global ID of an existing label.' + + def ready?(**args) + if args.slice(*mutually_exclusive_args).size != 1 + arg_str = mutually_exclusive_args.map { |x| x.to_s.camelize(:lower) }.join(' or ') + raise Gitlab::Graphql::Errors::ArgumentError, "one and only one of #{arg_str} is required" + end + + super + end + + def resolve(**args) + board = authorized_find!(id: args[:board_id]) + params = create_list_params(args) + + response = create_list(board, params) + + { + list: response.success? ? response.payload[:list] : nil, + errors: response.errors + } + end + + private + + def create_list(board, params) + raise NotImplementedError + end + + def create_list_params(args) + params = args.slice(*mutually_exclusive_args).with_indifferent_access + params[:label_id] &&= ::GitlabSchema.parse_gid(params[:label_id], expected_type: ::Label).model_id + + params + end + + def mutually_exclusive_args + [:backlog, :label_id] + end + end + end + end +end diff --git a/app/graphql/mutations/boards/lists/create.rb b/app/graphql/mutations/boards/lists/create.rb index 9eb9a4d4b87..f3aae9ac9c8 100644 --- a/app/graphql/mutations/boards/lists/create.rb +++ b/app/graphql/mutations/boards/lists/create.rb @@ -3,59 +3,32 @@ module Mutations module Boards module Lists - class Create < Base + class Create < BaseCreate graphql_name 'BoardListCreate' - argument :backlog, GraphQL::BOOLEAN_TYPE, - required: false, - description: 'Create the backlog list.' + argument :board_id, ::Types::GlobalIDType[::Board], + required: true, + description: 'Global ID of the issue board to mutate.' - argument :label_id, ::Types::GlobalIDType[::Label], - required: false, - description: 'Global ID of an existing label.' + field :list, + Types::BoardListType, + null: true, + description: 'Issue list in the issue board.' - def ready?(**args) - if args.slice(*mutually_exclusive_args).size != 1 - arg_str = mutually_exclusive_args.map { |x| x.to_s.camelize(:lower) }.join(' or ') - raise Gitlab::Graphql::Errors::ArgumentError, "one and only one of #{arg_str} is required" - end + authorize :admin_list - super - end - - def resolve(**args) - board = authorized_find!(id: args[:board_id]) - params = create_list_params(args) - - response = create_list(board, params) + private - { - list: response.success? ? response.payload[:list] : nil, - errors: response.errors - } + def find_object(id:) + GitlabSchema.object_from_id(id, expected_type: ::Board) end - private - def create_list(board, params) create_list_service = ::Boards::Lists::CreateService.new(board.resource_parent, current_user, params) create_list_service.execute(board) end - - # Overridden in EE - def create_list_params(args) - params = args.slice(*mutually_exclusive_args).with_indifferent_access - params[:label_id] &&= ::GitlabSchema.parse_gid(params[:label_id], expected_type: ::Label).model_id - - params - end - - # Overridden in EE - def mutually_exclusive_args - [:backlog, :label_id] - end end end end diff --git a/app/graphql/mutations/branches/create.rb b/app/graphql/mutations/branches/create.rb index 9fe9bef5403..6354976f1ea 100644 --- a/app/graphql/mutations/branches/create.rb +++ b/app/graphql/mutations/branches/create.rb @@ -3,7 +3,7 @@ module Mutations module Branches class Create < BaseMutation - include ResolvesProject + include FindsProject graphql_name 'CreateBranch' @@ -28,7 +28,7 @@ module Mutations authorize :push_code def resolve(project_path:, name:, ref:) - project = authorized_find!(full_path: project_path) + project = authorized_find!(project_path) context.scoped_set!(:branch_project, project) @@ -40,12 +40,6 @@ module Mutations errors: Array.wrap(result[:message]) } end - - private - - def find_object(full_path:) - resolve_project(full_path: full_path) - end end end end diff --git a/app/graphql/mutations/commits/create.rb b/app/graphql/mutations/commits/create.rb index ae14401558b..84933fee5d2 100644 --- a/app/graphql/mutations/commits/create.rb +++ b/app/graphql/mutations/commits/create.rb @@ -3,7 +3,7 @@ module Mutations module Commits class Create < BaseMutation - include ResolvesProject + include FindsProject graphql_name 'CommitCreate' @@ -37,7 +37,7 @@ module Mutations authorize :push_code def resolve(project_path:, branch:, message:, actions:, **args) - project = authorized_find!(full_path: project_path) + project = authorized_find!(project_path) attributes = { commit_message: message, @@ -53,12 +53,6 @@ module Mutations errors: Array.wrap(result[:message]) } end - - private - - def find_object(full_path:) - resolve_project(full_path: full_path) - 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 new file mode 100644 index 00000000000..2d4983f0d6e --- /dev/null +++ b/app/graphql/mutations/concerns/mutations/can_mutate_spammable.rb @@ -0,0 +1,83 @@ +# 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 CanMutateSpammable + extend ActiveSupport::Concern + + # 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. + included do + argument :captcha_response, GraphQL::STRING_TYPE, + required: false, + 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, + 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, + 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, + 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, + 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, + 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 + + # with_spam_action_fields(spammable) { {other_fields: true} } -> hash + # + # Takes a Spammable and a block as arguments. + # + # The block passed should be a hash, which the spam action fields will be merged into. + def with_spam_action_fields(spammable) + spam_action_fields = { + spam: spammable.spam?, + # NOTE: These fields are intentionally named with 'captcha' instead of 'recaptcha', so + # that they can be applied to future alternative CAPTCHA implementations other than + # reCAPTCHA (such as FriendlyCaptcha) without having to change the response field name + # in the API. + needs_captcha_response: spammable.render_recaptcha?, + spam_log_id: spammable.spam_log&.id, + captcha_site_key: Gitlab::CurrentSettings.recaptcha_site_key + } + + yield.merge(spam_action_fields) + end + end +end diff --git a/app/graphql/mutations/concerns/mutations/resolves_resource_parent.rb b/app/graphql/mutations/concerns/mutations/resolves_resource_parent.rb index e2b3f4b046f..b8ef675c3d4 100644 --- a/app/graphql/mutations/concerns/mutations/resolves_resource_parent.rb +++ b/app/graphql/mutations/concerns/mutations/resolves_resource_parent.rb @@ -9,11 +9,11 @@ module Mutations included do argument :project_path, GraphQL::ID_TYPE, required: false, - description: 'The project full path the resource is associated with.' + description: 'Full path of the project with which the resource is associated.' argument :group_path, GraphQL::ID_TYPE, required: false, - description: 'The group full path the resource is associated with.' + description: 'Full path of the group with which the resource is associated.' end def ready?(**args) diff --git a/app/graphql/mutations/concerns/mutations/spammable_mutation_fields.rb b/app/graphql/mutations/concerns/mutations/spammable_mutation_fields.rb deleted file mode 100644 index e5df8565618..00000000000 --- a/app/graphql/mutations/concerns/mutations/spammable_mutation_fields.rb +++ /dev/null @@ -1,24 +0,0 @@ -# frozen_string_literal: true - -module Mutations - module SpammableMutationFields - extend ActiveSupport::Concern - - included do - field :spam, - GraphQL::BOOLEAN_TYPE, - null: true, - description: 'Indicates whether the operation returns a record detected as spam.' - end - - def with_spam_params(&block) - request = Feature.enabled?(:snippet_spam) ? context[:request] : nil - - yield.merge({ api: true, request: request }) - end - - def with_spam_fields(spammable, &block) - { spam: spammable.spam? }.merge!(yield) - end - end -end diff --git a/app/graphql/mutations/container_expiration_policies/update.rb b/app/graphql/mutations/container_expiration_policies/update.rb index 37cf2fa6bf3..f61d852bb6c 100644 --- a/app/graphql/mutations/container_expiration_policies/update.rb +++ b/app/graphql/mutations/container_expiration_policies/update.rb @@ -3,7 +3,7 @@ module Mutations module ContainerExpirationPolicies class Update < Mutations::BaseMutation - include ResolvesProject + include FindsProject graphql_name 'UpdateContainerExpirationPolicy' @@ -50,7 +50,7 @@ module Mutations description: 'The container expiration policy after mutation.' def resolve(project_path:, **args) - project = authorized_find!(full_path: project_path) + project = authorized_find!(project_path) result = ::ContainerExpirationPolicies::UpdateService .new(container: project, current_user: current_user, params: args) @@ -61,12 +61,6 @@ module Mutations errors: result.errors } end - - private - - def find_object(full_path:) - resolve_project(full_path: full_path) - end end end end diff --git a/app/graphql/mutations/discussions/toggle_resolve.rb b/app/graphql/mutations/discussions/toggle_resolve.rb index c9834c946b2..6639252ec67 100644 --- a/app/graphql/mutations/discussions/toggle_resolve.rb +++ b/app/graphql/mutations/discussions/toggle_resolve.rb @@ -69,7 +69,7 @@ module Mutations end def unresolve!(discussion) - discussion.unresolve! + ::Discussions::UnresolveService.new(discussion, current_user).execute end end end diff --git a/app/graphql/mutations/issues/create.rb b/app/graphql/mutations/issues/create.rb index 18b80ff1736..37fddd92832 100644 --- a/app/graphql/mutations/issues/create.rb +++ b/app/graphql/mutations/issues/create.rb @@ -3,7 +3,7 @@ module Mutations module Issues class Create < BaseMutation - include ResolvesProject + include FindsProject graphql_name 'CreateIssue' authorize :create_issue @@ -70,7 +70,7 @@ module Mutations end def resolve(project_path:, **attributes) - project = authorized_find!(full_path: project_path) + project = authorized_find!(project_path) params = build_create_issue_params(attributes.merge(author_id: current_user.id)) issue = ::Issues::CreateService.new(project, current_user, params).execute @@ -98,10 +98,6 @@ module Mutations def mutually_exclusive_label_args [:labels, :label_ids] end - - def find_object(full_path:) - resolve_project(full_path: full_path) - end end end end diff --git a/app/graphql/mutations/jira_import/import_users.rb b/app/graphql/mutations/jira_import/import_users.rb index 616ef390657..af2bb18161f 100644 --- a/app/graphql/mutations/jira_import/import_users.rb +++ b/app/graphql/mutations/jira_import/import_users.rb @@ -3,10 +3,12 @@ module Mutations module JiraImport class ImportUsers < BaseMutation - include ResolvesProject + include FindsProject graphql_name 'JiraImportUsers' + authorize :admin_project + field :jira_users, [Types::JiraUserType], null: true, @@ -20,7 +22,7 @@ module Mutations description: 'The index of the record the import should started at, default 0 (50 records returned).' def resolve(project_path:, start_at: 0) - project = authorized_find!(full_path: project_path) + project = authorized_find!(project_path) service_response = ::JiraImport::UsersImporter.new(context[:current_user], project, start_at.to_i).execute @@ -29,16 +31,6 @@ module Mutations errors: service_response.errors } end - - private - - def find_object(full_path:) - resolve_project(full_path: full_path) - end - - def authorized_resource?(project) - Ability.allowed?(context[:current_user], :admin_project, project) - end end end end diff --git a/app/graphql/mutations/jira_import/start.rb b/app/graphql/mutations/jira_import/start.rb index 3d50ebde13a..e31aaf53a09 100644 --- a/app/graphql/mutations/jira_import/start.rb +++ b/app/graphql/mutations/jira_import/start.rb @@ -3,10 +3,12 @@ module Mutations module JiraImport class Start < BaseMutation - include ResolvesProject + include FindsProject graphql_name 'JiraImportStart' + authorize :admin_project + field :jira_import, Types::JiraImportType, null: true, @@ -27,7 +29,7 @@ module Mutations description: 'The mapping of Jira to GitLab users.' def resolve(project_path:, jira_project_key:, users_mapping:) - project = authorized_find!(full_path: project_path) + project = authorized_find!(project_path) mapping = users_mapping.to_ary.map { |map| map.to_hash } service_response = ::JiraImport::StartImportService @@ -40,16 +42,6 @@ module Mutations errors: service_response.errors } end - - private - - def find_object(full_path:) - resolve_project(full_path: full_path) - end - - def authorized_resource?(project) - Ability.allowed?(context[:current_user], :admin_project, project) - end end end end diff --git a/app/graphql/mutations/merge_requests/create.rb b/app/graphql/mutations/merge_requests/create.rb index 64fa8417e50..9ac8f70be95 100644 --- a/app/graphql/mutations/merge_requests/create.rb +++ b/app/graphql/mutations/merge_requests/create.rb @@ -3,7 +3,7 @@ module Mutations module MergeRequests class Create < BaseMutation - include ResolvesProject + include FindsProject graphql_name 'MergeRequestCreate' @@ -39,7 +39,7 @@ module Mutations authorize :create_merge_request_from def resolve(project_path:, **attributes) - project = authorized_find!(full_path: project_path) + project = authorized_find!(project_path) params = attributes.merge(author_id: current_user.id) merge_request = ::MergeRequests::CreateService.new(project, current_user, params).execute @@ -49,12 +49,6 @@ module Mutations errors: errors_on_object(merge_request) } end - - private - - def find_object(full_path:) - resolve_project(full_path: full_path) - end end end end diff --git a/app/graphql/mutations/merge_requests/reviewer_rereview.rb b/app/graphql/mutations/merge_requests/reviewer_rereview.rb new file mode 100644 index 00000000000..f6f4881654e --- /dev/null +++ b/app/graphql/mutations/merge_requests/reviewer_rereview.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: true + +module Mutations + module MergeRequests + class ReviewerRereview < Base + graphql_name 'MergeRequestReviewerRereview' + + argument :user_id, ::Types::GlobalIDType[::User], + loads: Types::UserType, + required: true, + description: <<~DESC + The user ID for the user that has been requested for a new review. + DESC + + def resolve(project_path:, iid:, user:) + merge_request = authorized_find!(project_path: project_path, iid: iid) + + result = ::MergeRequests::RequestReviewService.new(merge_request.project, current_user).execute(merge_request, user) + + { + merge_request: merge_request, + errors: Array(result[:message]) + } + end + end + end +end diff --git a/app/graphql/mutations/merge_requests/update.rb b/app/graphql/mutations/merge_requests/update.rb index 4721ebab41b..6a94d2f37b2 100644 --- a/app/graphql/mutations/merge_requests/update.rb +++ b/app/graphql/mutations/merge_requests/update.rb @@ -19,9 +19,14 @@ module Mutations required: false, description: copy_field_description(Types::MergeRequestType, :description) - def resolve(args) - merge_request = authorized_find!(**args.slice(:project_path, :iid)) - attributes = args.slice(:title, :description, :target_branch).compact + argument :state, ::Types::MergeRequestStateEventEnum, + required: false, + as: :state_event, + description: 'The action to perform to change the state.' + + def resolve(project_path:, iid:, **args) + merge_request = authorized_find!(project_path: project_path, iid: iid) + attributes = args.compact ::MergeRequests::UpdateService .new(merge_request.project, current_user, attributes) diff --git a/app/graphql/mutations/notes/create/base.rb b/app/graphql/mutations/notes/create/base.rb index 2351af01813..a157a5abdf2 100644 --- a/app/graphql/mutations/notes/create/base.rb +++ b/app/graphql/mutations/notes/create/base.rb @@ -25,6 +25,7 @@ module Mutations def resolve(args) noteable = authorized_find!(id: args[:noteable_id]) + verify_rate_limit!(current_user) note = ::Notes::CreateService.new( noteable.project, @@ -54,6 +55,20 @@ module Mutations confidential: args[:confidential] } end + + def verify_rate_limit!(current_user) + return unless rate_limit_throttled? + + raise Gitlab::Graphql::Errors::ResourceNotAvailable, + 'This endpoint has been requested too many times. Try again later.' + end + + def rate_limit_throttled? + rate_limiter = ::Gitlab::ApplicationRateLimiter + allowlist = Gitlab::CurrentSettings.current_application_settings.notes_create_limit_allowlist + + rate_limiter.throttled?(:notes_create, scope: [current_user], users_allowlist: allowlist) + end end end end diff --git a/app/graphql/mutations/releases/base.rb b/app/graphql/mutations/releases/base.rb index dd1724fe320..610e9cd9cde 100644 --- a/app/graphql/mutations/releases/base.rb +++ b/app/graphql/mutations/releases/base.rb @@ -3,17 +3,11 @@ module Mutations module Releases class Base < BaseMutation - include ResolvesProject + include FindsProject argument :project_path, GraphQL::ID_TYPE, required: true, description: 'Full path of the project the release is associated with.' - - private - - def find_object(full_path:) - resolve_project(full_path: full_path) - end end end end diff --git a/app/graphql/mutations/releases/create.rb b/app/graphql/mutations/releases/create.rb index 91ac256033e..914c1302094 100644 --- a/app/graphql/mutations/releases/create.rb +++ b/app/graphql/mutations/releases/create.rb @@ -41,7 +41,7 @@ module Mutations authorize :create_release def resolve(project_path:, assets: nil, **scalars) - project = authorized_find!(full_path: project_path) + project = authorized_find!(project_path) params = { **scalars, diff --git a/app/graphql/mutations/releases/delete.rb b/app/graphql/mutations/releases/delete.rb index e887b702cce..020c9133b58 100644 --- a/app/graphql/mutations/releases/delete.rb +++ b/app/graphql/mutations/releases/delete.rb @@ -17,7 +17,7 @@ module Mutations authorize :destroy_release def resolve(project_path:, tag:) - project = authorized_find!(full_path: project_path) + project = authorized_find!(project_path) params = { tag: tag }.with_indifferent_access diff --git a/app/graphql/mutations/releases/update.rb b/app/graphql/mutations/releases/update.rb index dff743254bd..35f2a7b3d4b 100644 --- a/app/graphql/mutations/releases/update.rb +++ b/app/graphql/mutations/releases/update.rb @@ -47,7 +47,7 @@ module Mutations end def resolve(project_path:, **scalars) - project = authorized_find!(full_path: project_path) + project = authorized_find!(project_path) params = scalars.with_indifferent_access diff --git a/app/graphql/mutations/security/ci_configuration/configure_sast.rb b/app/graphql/mutations/security/ci_configuration/configure_sast.rb new file mode 100644 index 00000000000..e4a3f815396 --- /dev/null +++ b/app/graphql/mutations/security/ci_configuration/configure_sast.rb @@ -0,0 +1,46 @@ +# frozen_string_literal: true + +module Mutations + module Security + module CiConfiguration + class ConfigureSast < BaseMutation + include FindsProject + + graphql_name 'ConfigureSast' + + argument :project_path, GraphQL::ID_TYPE, + required: true, + description: 'Full path of the project.' + + argument :configuration, ::Types::CiConfiguration::Sast::InputType, + required: true, + description: 'SAST CI configuration for the project.' + + field :status, GraphQL::STRING_TYPE, null: false, + description: 'Status of creating the commit for the supplied SAST CI configuration.' + + field :success_path, GraphQL::STRING_TYPE, null: true, + description: 'Redirect path to use when the response is successful.' + + authorize :push_code + + def resolve(project_path:, configuration:) + project = authorized_find!(project_path) + + result = ::Security::CiConfiguration::SastCreateService.new(project, current_user, configuration).execute + prepare_response(result) + end + + private + + def prepare_response(result) + { + status: result[:status], + success_path: result[:success_path], + errors: Array(result[:errors]) + } + end + end + end + end +end diff --git a/app/graphql/mutations/snippets/create.rb b/app/graphql/mutations/snippets/create.rb index b4485e28c5a..73eac9f0f3b 100644 --- a/app/graphql/mutations/snippets/create.rb +++ b/app/graphql/mutations/snippets/create.rb @@ -3,7 +3,8 @@ module Mutations module Snippets class Create < BaseMutation - include SpammableMutationFields + include ServiceCompatibility + include CanMutateSpammable authorize :create_snippet @@ -45,18 +46,17 @@ module Mutations authorize!(:global) end - service_response = ::Snippets::CreateService.new(project, - current_user, - create_params(args)).execute + process_args_for_params!(args) - snippet = service_response.payload[:snippet] + service_response = ::Snippets::CreateService.new(project, current_user, args).execute # Only when the user is not an api user and the operation was successful if !api_user? && service_response.success? ::Gitlab::UsageDataCounters::EditorUniqueCounter.track_snippet_editor_edit_action(author: current_user) end - with_spam_fields(snippet) do + snippet = service_response.payload[:snippet] + with_spam_action_fields(snippet) do { snippet: service_response.success? ? snippet : nil, errors: errors_on_object(snippet) @@ -70,18 +70,25 @@ module Mutations Project.find_by_full_path(full_path) end - def create_params(args) - with_spam_params do - args.tap do |create_args| - # We need to rename `blob_actions` into `snippet_actions` because - # it's the expected key param - create_args[:snippet_actions] = create_args.delete(:blob_actions)&.map(&:to_h) - - # We need to rename `uploaded_files` into `files` because - # it's the expected key param - create_args[:files] = create_args.delete(:uploaded_files) - end + # process_args_for_params!(args) -> nil + # + # Modifies/adds/deletes mutation resolve args as necessary to be passed as params to service layer. + def process_args_for_params!(args) + convert_blob_actions_to_snippet_actions!(args) + + # We need to rename `uploaded_files` into `files` because + # it's the expected key param + args[:files] = args.delete(:uploaded_files) + + if Feature.enabled?(:snippet_spam) + args.merge!(additional_spam_params) + else + args[:disable_spam_action_service] = true end + + # Return nil to make it explicit that this method is mutating the args parameter, and that + # the return value is not relevant and is not to be used. + nil end end end diff --git a/app/graphql/mutations/snippets/service_compatibility.rb b/app/graphql/mutations/snippets/service_compatibility.rb new file mode 100644 index 00000000000..0e7ee5d78bf --- /dev/null +++ b/app/graphql/mutations/snippets/service_compatibility.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +module Mutations + module Snippets + # Translates graphql mutation field params to be compatible with those expected by the service layer + module ServiceCompatibility + extend ActiveSupport::Concern + + # convert_blob_actions_to_snippet_actions!(args) -> nil + # + # Converts the blob_actions mutation argument into the + # snippet_actions hash which the service layer expects + def convert_blob_actions_to_snippet_actions!(args) + # We need to rename `blob_actions` into `snippet_actions` because + # it's the expected key param + args[:snippet_actions] = args.delete(:blob_actions)&.map(&:to_h) + + # Return nil to make it explicit that this method is mutating the args parameter + nil + end + end + end +end diff --git a/app/graphql/mutations/snippets/update.rb b/app/graphql/mutations/snippets/update.rb index 930440fbd35..af8e6f384b7 100644 --- a/app/graphql/mutations/snippets/update.rb +++ b/app/graphql/mutations/snippets/update.rb @@ -3,7 +3,8 @@ module Mutations module Snippets class Update < Base - include SpammableMutationFields + include ServiceCompatibility + include CanMutateSpammable graphql_name 'UpdateSnippet' @@ -30,19 +31,23 @@ module Mutations def resolve(id:, **args) snippet = authorized_find!(id: id) - result = ::Snippets::UpdateService.new(snippet.project, - current_user, - update_params(args)).execute(snippet) - snippet = result.payload[:snippet] + process_args_for_params!(args) + + service_response = ::Snippets::UpdateService.new(snippet.project, current_user, args).execute(snippet) + + # TODO: DRY this up - From here down, this is all duplicated with Mutations::Snippets::Create#resolve, except for + # `snippet.reset`, which is required in order to return the object in its non-dirty, unmodified, database state + # See issue here: https://gitlab.com/gitlab-org/gitlab/-/issues/300250 # Only when the user is not an api user and the operation was successful - if !api_user? && result.success? + if !api_user? && service_response.success? ::Gitlab::UsageDataCounters::EditorUniqueCounter.track_snippet_editor_edit_action(author: current_user) end - with_spam_fields(snippet) do + snippet = service_response.payload[:snippet] + with_spam_action_fields(snippet) do { - snippet: result.success? ? snippet : snippet.reset, + snippet: service_response.success? ? snippet : snippet.reset, errors: errors_on_object(snippet) } end @@ -50,18 +55,25 @@ module Mutations private - def ability_name - 'update' - end + # process_args_for_params!(args) -> nil + # + # Modifies/adds/deletes mutation resolve args as necessary to be passed as params to service layer. + def process_args_for_params!(args) + convert_blob_actions_to_snippet_actions!(args) - def update_params(args) - with_spam_params do - args.tap do |update_args| - # We need to rename `blob_actions` into `snippet_actions` because - # it's the expected key param - update_args[:snippet_actions] = update_args.delete(:blob_actions)&.map(&:to_h) - end + if Feature.enabled?(:snippet_spam) + args.merge!(additional_spam_params) + else + args[:disable_spam_action_service] = true end + + # Return nil to make it explicit that this method is mutating the args parameter, and that + # the return value is not relevant and is not to be used. + nil + end + + def ability_name + 'update' end end end diff --git a/app/graphql/mutations/todos/create.rb b/app/graphql/mutations/todos/create.rb index 814f7ec4fc4..b6250b0228c 100644 --- a/app/graphql/mutations/todos/create.rb +++ b/app/graphql/mutations/todos/create.rb @@ -14,7 +14,7 @@ module Mutations field :todo, Types::TodoType, null: true, - description: 'The to-do created.' + description: 'The to-do item created.' def resolve(target_id:) id = ::Types::GlobalIDType[Todoable].coerce_isolated_input(target_id) diff --git a/app/graphql/mutations/todos/mark_all_done.rb b/app/graphql/mutations/todos/mark_all_done.rb index c8359953567..22a5893d4ec 100644 --- a/app/graphql/mutations/todos/mark_all_done.rb +++ b/app/graphql/mutations/todos/mark_all_done.rb @@ -10,12 +10,12 @@ module Mutations field :updated_ids, [::Types::GlobalIDType[::Todo]], null: false, - deprecated: { reason: 'Use todos', milestone: '13.2' }, - description: 'Ids of the updated todos.' + deprecated: { reason: 'Use to-do items', milestone: '13.2' }, + description: 'IDs of the updated to-do items.' field :todos, [::Types::TodoType], null: false, - description: 'Updated todos.' + description: 'Updated to-do items.' def resolve authorize!(current_user) diff --git a/app/graphql/mutations/todos/mark_done.rb b/app/graphql/mutations/todos/mark_done.rb index 95144abb040..a78cc91da68 100644 --- a/app/graphql/mutations/todos/mark_done.rb +++ b/app/graphql/mutations/todos/mark_done.rb @@ -10,11 +10,11 @@ module Mutations argument :id, ::Types::GlobalIDType[::Todo], required: true, - description: 'The global ID of the todo to mark as done.' + description: 'The global ID of the to-do item to mark as done.' field :todo, Types::TodoType, null: false, - description: 'The requested todo.' + description: 'The requested to-do item.' def resolve(id:) todo = authorized_find!(id: id) diff --git a/app/graphql/mutations/todos/restore.rb b/app/graphql/mutations/todos/restore.rb index e496627aec2..70c33c439c4 100644 --- a/app/graphql/mutations/todos/restore.rb +++ b/app/graphql/mutations/todos/restore.rb @@ -10,11 +10,11 @@ module Mutations argument :id, ::Types::GlobalIDType[::Todo], required: true, - description: 'The global ID of the todo to restore.' + description: 'The global ID of the to-do item to restore.' field :todo, Types::TodoType, null: false, - description: 'The requested todo.' + description: 'The requested to-do item.' def resolve(id:) todo = authorized_find!(id: id) diff --git a/app/graphql/mutations/todos/restore_many.rb b/app/graphql/mutations/todos/restore_many.rb index 9263c1d9afe..dc02ffadada 100644 --- a/app/graphql/mutations/todos/restore_many.rb +++ b/app/graphql/mutations/todos/restore_many.rb @@ -10,16 +10,16 @@ module Mutations argument :ids, [::Types::GlobalIDType[::Todo]], required: true, - description: 'The global IDs of the todos to restore (a maximum of 50 is supported at once).' + description: 'The global IDs of the to-do items to restore (a maximum of 50 is supported at once).' field :updated_ids, [::Types::GlobalIDType[Todo]], null: false, - description: 'The IDs of the updated todo items.', - deprecated: { reason: 'Use todos', milestone: '13.2' } + description: 'The IDs of the updated to-do items.', + deprecated: { reason: 'Use to-do items', milestone: '13.2' } field :todos, [::Types::TodoType], null: false, - description: 'Updated todos.' + description: 'Updated to-do items.' def resolve(ids:) check_update_amount_limit!(ids) @@ -46,7 +46,7 @@ module Mutations end def raise_too_many_todos_requested_error - raise Gitlab::Graphql::Errors::ArgumentError, 'Too many todos requested.' + raise Gitlab::Graphql::Errors::ArgumentError, 'Too many to-do items requested.' end def check_update_amount_limit!(ids) -- cgit v1.2.3