diff --git a/app/graphql/mutations/concerns/mutations/can_mutate_spammable.rb b/app/graphql/mutations/concerns/mutations/can_mutate_spammable.rb index 2d4983f0d6e4f43e85d52dec9eee56dc058d65e0..ba644eff36cdc03ff5523fa35d9f422a7fc0e625 100644 --- a/app/graphql/mutations/concerns/mutations/can_mutate_spammable.rb +++ b/app/graphql/mutations/concerns/mutations/can_mutate_spammable.rb @@ -5,6 +5,7 @@ module Mutations # and optionally support the workflow to allow clients to display and solve CAPTCHAs. 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 @@ -59,25 +60,5 @@ def additional_spam_params 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/snippets/create.rb b/app/graphql/mutations/snippets/create.rb index 73eac9f0f3baf1fbacf5486a3348bc32c72ad7a8..7f2dd448b8b14c45fd94d97503cb0a213f91ce5c 100644 --- a/app/graphql/mutations/snippets/create.rb +++ b/app/graphql/mutations/snippets/create.rb @@ -56,7 +56,7 @@ def resolve(project_path: nil, **args) end snippet = service_response.payload[:snippet] - with_spam_action_fields(snippet) do + with_spam_action_response_fields(snippet) do { snippet: service_response.success? ? snippet : nil, errors: errors_on_object(snippet) diff --git a/app/graphql/mutations/snippets/update.rb b/app/graphql/mutations/snippets/update.rb index af8e6f384b70b5acd5a3c596217d162451d2c689..9f9f8bca8485b965bd41ab34216b54532da778c5 100644 --- a/app/graphql/mutations/snippets/update.rb +++ b/app/graphql/mutations/snippets/update.rb @@ -45,7 +45,7 @@ def resolve(id:, **args) end snippet = service_response.payload[:snippet] - with_spam_action_fields(snippet) do + with_spam_action_response_fields(snippet) do { snippet: service_response.success? ? snippet : snippet.reset, errors: errors_on_object(snippet) diff --git a/app/services/spam/spam_action_service.rb b/app/services/spam/spam_action_service.rb index ff32bc32d939f589841adb49fedcc0e03edf8b5a..185b9e39070a448a4ed3b28b77c6accd63c2c9a4 100644 --- a/app/services/spam/spam_action_service.rb +++ b/app/services/spam/spam_action_service.rb @@ -9,7 +9,9 @@ class SpamActionService # after the spammable is created/updated based on the remaining parameters. # # Takes a hash of parameters from an incoming request to modify a model (via a controller, - # service, or GraphQL mutation). + # service, or GraphQL mutation). The parameters will either be camelCase (if they are + # received directly via controller params) or underscore_case (if they have come from + # a GraphQL mutation which has converted them to underscore) # # Deletes the parameters which are related to spam and captcha processing, and returns # them in a SpamParams parameters object. See: @@ -18,12 +20,12 @@ def self.filter_spam_params!(params) # NOTE: The 'captcha_response' field can be expanded to multiple fields when we move to future # alternative captcha implementations such as FriendlyCaptcha. See # https://gitlab.com/gitlab-org/gitlab/-/issues/273480 - captcha_response = params.delete(:captcha_response) + captcha_response = params.delete(:captcha_response) || params.delete(:captchaResponse) SpamParams.new( api: params.delete(:api), captcha_response: captcha_response, - spam_log_id: params.delete(:spam_log_id) + spam_log_id: params.delete(:spam_log_id) || params.delete(:spamLogId) ) end diff --git a/lib/quality/test_level.rb b/lib/quality/test_level.rb index 45cfa9b373db249ceeabfbe0162db63d18e90af7..ad9de067375654aa11bfaf3411fdb4c976c60b31 100644 --- a/lib/quality/test_level.rb +++ b/lib/quality/test_level.rb @@ -43,6 +43,7 @@ class TestLevel serializers services sidekiq + spam support_specs tasks uploaders diff --git a/lib/spam/concerns/has_spam_action_response_fields.rb b/lib/spam/concerns/has_spam_action_response_fields.rb new file mode 100644 index 0000000000000000000000000000000000000000..d49f5cd6454c11ae40d9057198ee94e13e344c9f --- /dev/null +++ b/lib/spam/concerns/has_spam_action_response_fields.rb @@ -0,0 +1,37 @@ +# frozen_string_literal: true + +module Spam + module Concerns + # This concern is shared by the controller and GraphQL layer to handle + # addition of spam/CAPTCHA related fields in the response. + module HasSpamActionResponseFields + extend ActiveSupport::Concern + + # spam_action_response_fields(spammable) -> hash + # + # Takes a Spammable as an argument and returns response fields necessary to display a CAPTCHA on + # the client. + def spam_action_response_fields(spammable) + { + 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 + } + end + + # with_spam_action_response_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_response_fields(spammable) + yield.merge(spam_action_response_fields(spammable)) + end + end + end +end diff --git a/spec/graphql/mutations/concerns/mutations/can_mutate_spammable_spec.rb b/spec/graphql/mutations/concerns/mutations/can_mutate_spammable_spec.rb index ee8db7a1f31c7e0bc05f22ad2c5402c88e939ce9..8d1fce406fa6664d02a740d7a0d4440678cce5ea 100644 --- a/spec/graphql/mutations/concerns/mutations/can_mutate_spammable_spec.rb +++ b/spec/graphql/mutations/concerns/mutations/can_mutate_spammable_spec.rb @@ -30,7 +30,7 @@ end it 'merges in spam action fields from spammable' do - result = subject.send(:with_spam_action_fields, spammable) do + result = subject.send(:with_spam_action_response_fields, spammable) do { other_field: true } end expect(result) diff --git a/spec/lib/quality/test_level_spec.rb b/spec/lib/quality/test_level_spec.rb index 2232d47234f49f8d91305164c96924d11962b36a..32960cd571b0d5c0c5a13788983f8d5505dcc99d 100644 --- a/spec/lib/quality/test_level_spec.rb +++ b/spec/lib/quality/test_level_spec.rb @@ -28,7 +28,7 @@ context 'when level is unit' do it 'returns a pattern' do expect(subject.pattern(:unit)) - .to eq("spec/{bin,channels,config,db,dependencies,elastic,elastic_integration,experiments,factories,finders,frontend,graphql,haml_lint,helpers,initializers,javascripts,lib,models,policies,presenters,rack_servers,replicators,routing,rubocop,serializers,services,sidekiq,support_specs,tasks,uploaders,validators,views,workers,tooling}{,/**/}*_spec.rb") + .to eq("spec/{bin,channels,config,db,dependencies,elastic,elastic_integration,experiments,factories,finders,frontend,graphql,haml_lint,helpers,initializers,javascripts,lib,models,policies,presenters,rack_servers,replicators,routing,rubocop,serializers,services,sidekiq,spam,support_specs,tasks,uploaders,validators,views,workers,tooling}{,/**/}*_spec.rb") end end @@ -103,7 +103,7 @@ context 'when level is unit' do it 'returns a regexp' do expect(subject.regexp(:unit)) - .to eq(%r{spec/(bin|channels|config|db|dependencies|elastic|elastic_integration|experiments|factories|finders|frontend|graphql|haml_lint|helpers|initializers|javascripts|lib|models|policies|presenters|rack_servers|replicators|routing|rubocop|serializers|services|sidekiq|support_specs|tasks|uploaders|validators|views|workers|tooling)}) + .to eq(%r{spec/(bin|channels|config|db|dependencies|elastic|elastic_integration|experiments|factories|finders|frontend|graphql|haml_lint|helpers|initializers|javascripts|lib|models|policies|presenters|rack_servers|replicators|routing|rubocop|serializers|services|sidekiq|spam|support_specs|tasks|uploaders|validators|views|workers|tooling)}) end end diff --git a/spec/spam/concerns/has_spam_action_response_fields_spec.rb b/spec/spam/concerns/has_spam_action_response_fields_spec.rb new file mode 100644 index 0000000000000000000000000000000000000000..4d5f8d9d4311d7a7c07d65027d72713c946bee8a --- /dev/null +++ b/spec/spam/concerns/has_spam_action_response_fields_spec.rb @@ -0,0 +1,35 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Spam::Concerns::HasSpamActionResponseFields do + subject do + klazz = Class.new + klazz.include described_class + klazz.new + end + + describe '#with_spam_action_response_fields' do + let(:spam_log) { double(:spam_log, id: 1) } + let(:spammable) { double(:spammable, spam?: true, render_recaptcha?: true, spam_log: spam_log) } + let(:recaptcha_site_key) { 'abc123' } + + before do + allow(Gitlab::CurrentSettings).to receive(:recaptcha_site_key) { recaptcha_site_key } + end + + it 'merges in spam action fields from spammable' do + result = subject.send(:with_spam_action_response_fields, spammable) do + { other_field: true } + end + expect(result) + .to eq({ + spam: true, + needs_captcha_response: true, + spam_log_id: 1, + captcha_site_key: recaptcha_site_key, + other_field: true + }) + end + end +end diff --git a/spec/support/shared_examples/graphql/mutations/can_mutate_spammable_examples.rb b/spec/support/shared_examples/graphql/mutations/can_mutate_spammable_examples.rb index d294f034d2eb6a09a760a52722526dc919ecfe2f..bb4270d7db6559722500582e0d867fbc7815041e 100644 --- a/spec/support/shared_examples/graphql/mutations/can_mutate_spammable_examples.rb +++ b/spec/support/shared_examples/graphql/mutations/can_mutate_spammable_examples.rb @@ -21,14 +21,14 @@ end end - describe "#with_spam_action_fields" do + describe "#with_spam_action_response_fields" do it 'resolves with spam action fields' do subject # NOTE: We do not need to assert on the specific values of spam action fields here, we only need - # to verify that #with_spam_action_fields was invoked and that the fields are present in the - # response. The specific behavior of #with_spam_action_fields is covered in the - # CanMutateSpammable unit tests. + # to verify that #with_spam_action_response_fields was invoked and that the fields are present in the + # response. The specific behavior of #with_spam_action_response_fields is covered in the + # HasSpamActionResponseFields unit tests. expect(mutation_response.keys) .to include('spam', 'spamLogId', 'needsCaptchaResponse', 'captchaSiteKey') end