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
|