diff options
Diffstat (limited to 'app/services/spam')
-rw-r--r-- | app/services/spam/akismet_service.rb | 4 | ||||
-rw-r--r-- | app/services/spam/spam_action_service.rb | 21 | ||||
-rw-r--r-- | app/services/spam/spam_constants.rb | 22 | ||||
-rw-r--r-- | app/services/spam/spam_verdict_service.rb | 76 |
4 files changed, 109 insertions, 14 deletions
diff --git a/app/services/spam/akismet_service.rb b/app/services/spam/akismet_service.rb index ab35fb8700f..e11a1dbdd96 100644 --- a/app/services/spam/akismet_service.rb +++ b/app/services/spam/akismet_service.rb @@ -27,7 +27,7 @@ module Spam is_spam, is_blatant = akismet_client.check(options[:ip_address], options[:user_agent], params) is_spam || is_blatant rescue => e - Rails.logger.error("Unable to connect to Akismet: #{e}, skipping check") # rubocop:disable Gitlab/RailsLogger + Gitlab::AppLogger.error("Unable to connect to Akismet: #{e}, skipping check") false end end @@ -67,7 +67,7 @@ module Spam akismet_client.public_send(type, options[:ip_address], options[:user_agent], params) # rubocop:disable GitlabSecurity/PublicSend true rescue => e - Rails.logger.error("Unable to connect to Akismet: #{e}, skipping!") # rubocop:disable Gitlab/RailsLogger + Gitlab::AppLogger.error("Unable to connect to Akismet: #{e}, skipping!") false end end diff --git a/app/services/spam/spam_action_service.rb b/app/services/spam/spam_action_service.rb index f0a4aff4443..b745b67f566 100644 --- a/app/services/spam/spam_action_service.rb +++ b/app/services/spam/spam_action_service.rb @@ -7,9 +7,11 @@ module Spam attr_accessor :target, :request, :options attr_reader :spam_log - def initialize(spammable:, request:) + def initialize(spammable:, request:, user:, context: {}) @target = spammable @request = request + @user = user + @context = context @options = {} if @request @@ -22,7 +24,7 @@ module Spam end end - def execute(api: false, recaptcha_verified:, spam_log_id:, user:) + def execute(api: false, recaptcha_verified:, spam_log_id:) if recaptcha_verified # If it's a request which is already verified through reCAPTCHA, # update the spam log accordingly. @@ -40,6 +42,8 @@ module Spam private + attr_reader :user, :context + def allowlisted?(user) user.respond_to?(:gitlab_employee) && user.gitlab_employee? end @@ -49,7 +53,8 @@ module Spam # ask the SpamVerdictService what to do with the target. spam_verdict_service.execute.tap do |result| case result - when REQUIRE_RECAPTCHA + when CONDITIONAL_ALLOW + # at the moment, this means "ask for reCAPTCHA" create_spam_log(api) break if target.allow_possible_spam? @@ -74,7 +79,7 @@ module Spam description: target.spam_description, source_ip: options[:ip_address], user_agent: options[:user_agent], - noteable_type: target.class.to_s, + noteable_type: notable_type, via_api: api } ) @@ -84,8 +89,14 @@ module Spam def spam_verdict_service SpamVerdictService.new(target: target, + user: user, request: @request, - options: options) + options: options, + context: context.merge(target_type: notable_type)) + end + + def notable_type + @notable_type ||= target.class.to_s end end end diff --git a/app/services/spam/spam_constants.rb b/app/services/spam/spam_constants.rb index 085bac684c4..2a16cfae78b 100644 --- a/app/services/spam/spam_constants.rb +++ b/app/services/spam/spam_constants.rb @@ -2,8 +2,24 @@ module Spam module SpamConstants - REQUIRE_RECAPTCHA = :recaptcha - DISALLOW = :disallow - ALLOW = :allow + CONDITIONAL_ALLOW = "conditional_allow" + DISALLOW = "disallow" + ALLOW = "allow" + BLOCK_USER = "block" + + SUPPORTED_VERDICTS = { + BLOCK_USER => { + priority: 1 + }, + DISALLOW => { + priority: 2 + }, + CONDITIONAL_ALLOW => { + priority: 3 + }, + ALLOW => { + priority: 4 + } + }.freeze end end diff --git a/app/services/spam/spam_verdict_service.rb b/app/services/spam/spam_verdict_service.rb index 2b4d5f4a984..68f1135ae28 100644 --- a/app/services/spam/spam_verdict_service.rb +++ b/app/services/spam/spam_verdict_service.rb @@ -5,22 +5,90 @@ module Spam include AkismetMethods include SpamConstants - def initialize(target:, request:, options:) + def initialize(user:, target:, request:, options:, context: {}) @target = target @request = request + @user = user @options = options + @verdict_params = assemble_verdict_params(context) end def execute + external_spam_check_result = spam_verdict + akismet_result = akismet_verdict + + # filter out anything we don't recognise, including nils. + valid_results = [external_spam_check_result, akismet_result].compact.select { |r| SUPPORTED_VERDICTS.key?(r) } + # Treat nils - such as service unavailable - as ALLOW + return ALLOW unless valid_results.any? + + # Favour the most restrictive result. + valid_results.min_by { |v| SUPPORTED_VERDICTS[v][:priority] } + end + + private + + attr_reader :user, :target, :request, :options, :verdict_params + + def akismet_verdict if akismet.spam? - Gitlab::Recaptcha.enabled? ? REQUIRE_RECAPTCHA : DISALLOW + Gitlab::Recaptcha.enabled? ? CONDITIONAL_ALLOW : DISALLOW else ALLOW end end - private + def spam_verdict + return unless Gitlab::CurrentSettings.spam_check_endpoint_enabled + return if endpoint_url.blank? + + begin + result = Gitlab::HTTP.post(endpoint_url, body: verdict_params.to_json, headers: { 'Content-Type' => 'application/json' }) + return unless result + + json_result = Gitlab::Json.parse(result).with_indifferent_access + # @TODO metrics/logging + # Expecting: + # error: (string or nil) + # result: (string or nil) + verdict = json_result[:verdict] + return unless SUPPORTED_VERDICTS.include?(verdict) - attr_reader :target, :request, :options + # @TODO log if json_result[:error] + + json_result[:verdict] + rescue *Gitlab::HTTP::HTTP_ERRORS => e + # @TODO: log error via try_post https://gitlab.com/gitlab-org/gitlab/-/issues/219223 + Gitlab::ErrorTracking.log_exception(e) + return + rescue + # @TODO log + ALLOW + end + end + + def assemble_verdict_params(context) + return {} unless endpoint_url.present? + + project = target.try(:project) + + context.merge({ + target: { + title: target.spam_title, + description: target.spam_description, + type: target.class.to_s + }, + user: { + created_at: user.created_at, + email: user.email, + username: user.username + }, + user_in_project: user.authorized_project?(project) + }) + end + + def endpoint_url + @endpoint_url ||= Gitlab::CurrentSettings.current_application_settings.spam_check_endpoint_url + end end end |