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
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
|
# frozen_string_literal: true
module Spammable
extend ActiveSupport::Concern
include Gitlab::Utils::StrongMemoize
class_methods do
def attr_spammable(attr, options = {})
spammable_attrs << [attr.to_s, options]
end
end
included do
has_one :user_agent_detail, as: :subject, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
attr_writer :spam
attr_accessor :needs_recaptcha
attr_accessor :spam_log
alias_method :spam?, :spam
alias_method :needs_recaptcha?, :needs_recaptcha
# if spam errors are added before validation, they will be wiped
after_validation :invalidate_if_spam, on: [:create, :update]
cattr_accessor :spammable_attrs, instance_accessor: false do
[]
end
delegate :ip_address, :user_agent, to: :user_agent_detail, allow_nil: true
end
def spam
!!@spam # rubocop:disable Gitlab/ModuleWithInstanceVariables
end
def submittable_as_spam_by?(current_user)
current_user && current_user.admin? && submittable_as_spam?
end
def submittable_as_spam?
if user_agent_detail
user_agent_detail.submittable? && Gitlab::CurrentSettings.current_application_settings.akismet_enabled
else
false
end
end
def needs_recaptcha!
if self.supports_recaptcha?
self.needs_recaptcha = true
else
self.spam!
end
end
# Override in Spammable if recaptcha is supported
def supports_recaptcha?
false
end
##
# Indicates if a recaptcha should be rendered before allowing this model to be saved.
#
def render_recaptcha?
return false unless Gitlab::Recaptcha.enabled? && supports_recaptcha?
return false if self.errors.count > 1 # captcha should not be rendered if are still other errors
self.needs_recaptcha?
end
def spam!
self.spam = true
end
def clear_spam_flags!
self.spam = false
self.needs_recaptcha = false
end
def invalidate_if_spam
if needs_recaptcha? && Gitlab::Recaptcha.enabled? && supports_recaptcha?
recaptcha_error!
elsif needs_recaptcha? || spam?
unrecoverable_spam_error!
end
end
def recaptcha_error!
self.errors.add(:base, _("Your %{spammable_entity_type} has been recognized as spam. "\
"Please, change the content or solve the reCAPTCHA to proceed.") \
% { spammable_entity_type: spammable_entity_type })
end
def unrecoverable_spam_error!
self.errors.add(:base, _("Your %{spammable_entity_type} has been recognized as spam. "\
"Please, change the content to proceed.") \
% { spammable_entity_type: spammable_entity_type })
end
def spammable_entity_type
case self
when Issue
_('issue')
when MergeRequest
_('merge request')
when Note
_('comment')
when Snippet
_('snippet')
else
self.class.model_name.human.downcase
end
end
def spam_title
attr = self.class.spammable_attrs.find do |_, options|
options.fetch(:spam_title, false)
end
public_send(attr.first) if attr && respond_to?(attr.first.to_sym) # rubocop:disable GitlabSecurity/PublicSend
end
def spam_description
attr = self.class.spammable_attrs.find do |_, options|
options.fetch(:spam_description, false)
end
public_send(attr.first) if attr && respond_to?(attr.first.to_sym) # rubocop:disable GitlabSecurity/PublicSend
end
def spammable_text
result = self.class.spammable_attrs.map do |attr|
public_send(attr.first) # rubocop:disable GitlabSecurity/PublicSend
end
result.reject(&:blank?).join("\n")
end
# Override in included class if further checks are necessary
def check_for_spam?(*)
spammable_attribute_changed?
end
def spammable_attribute_changed?
(changed & self.class.spammable_attrs.to_h.keys).any?
end
def check_for_spam(user:, action:, extra_features: {})
strong_memoize_with(:check_for_spam, user, action, extra_features) do
Spam::SpamActionService.new(spammable: self, user: user, action: action, extra_features: extra_features).execute
end
end
# Override in included class if you want to allow possible spam under specific circumstances
def allow_possible_spam?(*)
Gitlab::CurrentSettings.allow_possible_spam
end
end
|