mod reports: autoreport spam and autoban spammers.

* Automatically generate a mod report when a comment, forum post, or
  dmail is detected as spam.
* Automatically ban users that receive too many automatic spam reports
  within a short window of time.
* Automatically mark spam dmails as deleted.
* Change ban threshold from 10 spam reports in 24 hours to 10 reports in 1 hour.
* Change ban length from 3 days to forever.
This commit is contained in:
evazion
2020-02-03 04:12:03 -06:00
parent 170a0e8a48
commit bb2022abed
7 changed files with 89 additions and 78 deletions

View File

@@ -4,6 +4,12 @@
class SpamDetector
include Rakismet::Model
# if a person receives more than 10 automatic spam reports within a 1 hour
# window, automatically ban them forever.
AUTOBAN_THRESHOLD = 10
AUTOBAN_WINDOW = 1.hours
AUTOBAN_DURATION = 999999
attr_accessor :record, :user, :user_ip, :content, :comment_type
rakismet_attrs author: proc { user.name },
author_email: proc { user.email },
@@ -24,6 +30,23 @@ class SpamDetector
false
end
def self.is_spammer?(user)
return false if user.is_gold?
automatic_reports = ModerationReport.where("created_at > ?", AUTOBAN_WINDOW.ago).where(creator: User.system)
dmail_reports = automatic_reports.where(model: Dmail.sent_by(user))
comment_reports = automatic_reports.where(model: user.comments)
forum_post_reports = automatic_reports.where(model: user.forum_posts)
report_count = dmail_reports.or(comment_reports).or(forum_post_reports).count
report_count >= AUTOBAN_THRESHOLD
end
def self.ban_spammer!(spammer)
spammer.bans.create!(banner: User.system, reason: "Spambot.", duration: AUTOBAN_DURATION)
end
def initialize(record, user_ip: nil)
case record
when Dmail

View File

@@ -2,13 +2,14 @@ class Comment < ApplicationRecord
include Mentionable
validate :validate_creator_is_not_limited, :on => :create
validate :validate_comment_is_not_spam, on: :create
validates_presence_of :body, :message => "has no content"
belongs_to :post
belongs_to :creator, class_name: "User"
belongs_to_updater
has_many :moderation_reports, as: :model
has_many :votes, :class_name => "CommentVote", :dependent => :destroy
before_create :autoreport_spam
after_create :update_last_commented_at_on_create
after_update(:if => ->(rec) {(!rec.is_deleted? || !rec.saved_change_to_is_deleted?) && CurrentUser.id != rec.creator_id}) do |rec|
ModAction.log("comment ##{rec.id} updated by #{CurrentUser.name}", :comment_update)
@@ -97,8 +98,10 @@ class Comment < ApplicationRecord
end
end
def validate_comment_is_not_spam
errors[:base] << "Failed to create comment" if SpamDetector.new(self).spam?
def autoreport_spam
if SpamDetector.new(self).spam?
moderation_reports << ModerationReport.new(creator: User.system, reason: "Spam.")
end
end
def update_last_commented_at_on_create

View File

@@ -1,10 +1,6 @@
require 'digest/sha1'
class Dmail < ApplicationRecord
# if a person sends spam to more than 10 users within a 24 hour window, automatically ban them for 3 days.
AUTOBAN_THRESHOLD = 10
AUTOBAN_WINDOW = 24.hours
AUTOBAN_DURATION = 3
validates_presence_of :title, :body, on: :create
validate :validate_sender_is_not_banned, on: :create
@@ -12,8 +8,10 @@ class Dmail < ApplicationRecord
belongs_to :owner, :class_name => "User"
belongs_to :to, :class_name => "User"
belongs_to :from, :class_name => "User"
has_many :moderation_reports, as: :model
after_initialize :initialize_attributes, if: :new_record?
before_create :autoreport_spam
after_save :update_unread_dmail_count
after_commit :send_email, on: :create
@@ -27,29 +25,6 @@ class Dmail < ApplicationRecord
scope :sent, -> { where("dmails.owner_id = dmails.from_id") }
scope :received, -> { where("dmails.owner_id = dmails.to_id") }
concerning :SpamMethods do
class_methods do
def is_spammer?(user)
return false if user.is_gold?
spammed_users = sent_by(user).where(is_spam: true).where("created_at > ?", AUTOBAN_WINDOW.ago).distinct.count(:to_id)
spammed_users >= AUTOBAN_THRESHOLD
end
def ban_spammer(spammer)
spammer.bans.create! do |ban|
ban.banner = User.system
ban.reason = "Spambot."
ban.duration = AUTOBAN_DURATION
end
end
end
def spam?
SpamDetector.new(self).spam?
end
end
module AddressMethods
def to_name=(name)
self.to = User.find_by_name(name)
@@ -72,7 +47,6 @@ class Dmail < ApplicationRecord
# recipient's copy
copy = Dmail.new(params)
copy.owner_id = copy.to_id
copy.is_spam = copy.spam?
copy.save unless copy.to_id == copy.from_id
# sender's copy
@@ -80,8 +54,6 @@ class Dmail < ApplicationRecord
copy.owner_id = copy.from_id
copy.is_read = true
copy.save
Dmail.ban_spammer(copy.from) if Dmail.is_spammer?(copy.from)
end
copy
@@ -200,6 +172,13 @@ class Dmail < ApplicationRecord
owner == to
end
def autoreport_spam
if is_recipient? && SpamDetector.new(self).spam?
self.is_deleted = true
moderation_reports << ModerationReport.new(creator: User.system, reason: "Spam.")
end
end
def update_unread_dmail_count
return unless saved_change_to_id? || saved_change_to_is_read? || saved_change_to_is_deleted?

View File

@@ -14,12 +14,12 @@ class ForumPost < ApplicationRecord
before_validation :initialize_is_deleted, :on => :create
before_save :update_dtext_links, if: :dtext_links_changed?
before_create :autoreport_spam
after_create :update_topic_updated_at_on_create
after_update :update_topic_updated_at_on_update_for_original_posts
after_destroy :update_topic_updated_at_on_destroy
validates_presence_of :body
validate :validate_topic_is_unlocked
validate :validate_post_is_not_spam, on: :create
validate :topic_is_not_restricted, :on => :create
before_destroy :validate_topic_is_unlocked
after_save :delete_topic_if_original_post
@@ -108,8 +108,10 @@ class ForumPost < ApplicationRecord
votes.where(creator_id: user.id, score: score).exists?
end
def validate_post_is_not_spam
errors[:base] << "Failed to create forum post" if SpamDetector.new(self, user_ip: CurrentUser.ip_addr).spam?
def autoreport_spam
if SpamDetector.new(self, user_ip: CurrentUser.ip_addr).spam?
moderation_reports << ModerationReport.new(creator: User.system, reason: "Spam.")
end
end
def validate_topic_is_unlocked

View File

@@ -7,6 +7,7 @@ class ModerationReport < ApplicationRecord
validates :creator, uniqueness: { scope: [:model_type, :model_id], message: "have already reported this message." }
after_create :create_forum_post!
after_create :autoban_reported_user
scope :user, -> { where(model_type: "User") }
scope :dmail, -> { where(model_type: "Dmail") }
@@ -54,6 +55,23 @@ class ModerationReport < ApplicationRecord
updater.update(forum_post_message)
end
def autoban_reported_user
if SpamDetector.is_spammer?(reported_user)
SpamDetector.ban_spammer!(reported_user)
end
end
def reported_user
case model
when Comment, ForumPost
model.creator
when Dmail
model.from
else
raise NotImplementedError
end
end
def self.visible(user = CurrentUser.user)
user.is_moderator? ? all : none
end