summaryrefslogtreecommitdiff
path: root/app/services/spam/spam_verdict_service.rb
blob: 68f1135ae28279014c4f49204eb3515580a81488 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
# frozen_string_literal: true

module Spam
  class SpamVerdictService
    include AkismetMethods
    include SpamConstants

    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? ? CONDITIONAL_ALLOW : DISALLOW
      else
        ALLOW
      end
    end

    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)

        # @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