Merge pull request #3427 from evazion/feat-autoban-spammers
Fix #3408: More automated measures against spammers
This commit is contained in:
@@ -1,6 +1,11 @@
|
|||||||
require 'digest/sha1'
|
require 'digest/sha1'
|
||||||
|
|
||||||
class Dmail < ApplicationRecord
|
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
|
||||||
|
|
||||||
include Rakismet::Model
|
include Rakismet::Model
|
||||||
|
|
||||||
with_options on: :create do
|
with_options on: :create do
|
||||||
@@ -23,6 +28,23 @@ class Dmail < ApplicationRecord
|
|||||||
rakismet_attrs author: :from_name, author_email: :from_email, content: :title_and_body, user_ip: :creator_ip_addr_str
|
rakismet_attrs author: :from_name, author_email: :from_email, content: :title_and_body, user_ip: :creator_ip_addr_str
|
||||||
|
|
||||||
concerning :SpamMethods do
|
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 title_and_body
|
def title_and_body
|
||||||
"#{title}\n\n#{body}"
|
"#{title}\n\n#{body}"
|
||||||
end
|
end
|
||||||
@@ -31,9 +53,9 @@ class Dmail < ApplicationRecord
|
|||||||
creator_ip_addr.to_s
|
creator_ip_addr.to_s
|
||||||
end
|
end
|
||||||
|
|
||||||
def spam?(sender = CurrentUser.user)
|
def spam?
|
||||||
return false if Danbooru.config.rakismet_key.blank?
|
return false if Danbooru.config.rakismet_key.blank?
|
||||||
return false if sender.is_gold?
|
return false if from.is_gold?
|
||||||
super()
|
super()
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -58,7 +80,6 @@ class Dmail < ApplicationRecord
|
|||||||
def initialize_attributes
|
def initialize_attributes
|
||||||
self.from_id ||= CurrentUser.id
|
self.from_id ||= CurrentUser.id
|
||||||
self.creator_ip_addr ||= CurrentUser.ip_addr
|
self.creator_ip_addr ||= CurrentUser.ip_addr
|
||||||
self.is_spam = spam?(CurrentUser.user)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -73,6 +94,7 @@ class Dmail < ApplicationRecord
|
|||||||
# recipient's copy
|
# recipient's copy
|
||||||
copy = Dmail.new(params)
|
copy = Dmail.new(params)
|
||||||
copy.owner_id = copy.to_id
|
copy.owner_id = copy.to_id
|
||||||
|
copy.is_spam = copy.spam?
|
||||||
copy.save unless copy.to_id == copy.from_id
|
copy.save unless copy.to_id == copy.from_id
|
||||||
|
|
||||||
# sender's copy
|
# sender's copy
|
||||||
@@ -80,13 +102,15 @@ class Dmail < ApplicationRecord
|
|||||||
copy.owner_id = copy.from_id
|
copy.owner_id = copy.from_id
|
||||||
copy.is_read = true
|
copy.is_read = true
|
||||||
copy.save
|
copy.save
|
||||||
|
|
||||||
|
Dmail.ban_spammer(copy.from) if Dmail.is_spammer?(copy.from)
|
||||||
end
|
end
|
||||||
|
|
||||||
copy
|
copy
|
||||||
end
|
end
|
||||||
|
|
||||||
def create_automated(params)
|
def create_automated(params)
|
||||||
dmail = Dmail.new(from: Danbooru.config.system_user, **params)
|
dmail = Dmail.new(from: User.system, **params)
|
||||||
dmail.owner = dmail.to
|
dmail.owner = dmail.to
|
||||||
dmail.save
|
dmail.save
|
||||||
dmail
|
dmail
|
||||||
@@ -119,6 +143,10 @@ class Dmail < ApplicationRecord
|
|||||||
end
|
end
|
||||||
|
|
||||||
module SearchMethods
|
module SearchMethods
|
||||||
|
def sent_by(user)
|
||||||
|
where("dmails.from_id = ? AND dmails.owner_id != ?", user.id, user.id)
|
||||||
|
end
|
||||||
|
|
||||||
def active
|
def active
|
||||||
where("is_deleted = ?", false)
|
where("is_deleted = ?", false)
|
||||||
end
|
end
|
||||||
@@ -234,7 +262,7 @@ class Dmail < ApplicationRecord
|
|||||||
end
|
end
|
||||||
|
|
||||||
def is_automated?
|
def is_automated?
|
||||||
from == Danbooru.config.system_user
|
from == User.system
|
||||||
end
|
end
|
||||||
|
|
||||||
def filtered?
|
def filtered?
|
||||||
|
|||||||
@@ -316,7 +316,7 @@ class User < ApplicationRecord
|
|||||||
|
|
||||||
module ClassMethods
|
module ClassMethods
|
||||||
def system
|
def system
|
||||||
Danbooru.config.system_user
|
User.find_by!(name: Danbooru.config.system_user)
|
||||||
end
|
end
|
||||||
|
|
||||||
def level_hash
|
def level_hash
|
||||||
@@ -366,7 +366,7 @@ class User < ApplicationRecord
|
|||||||
def promote_to_admin_if_first_user
|
def promote_to_admin_if_first_user
|
||||||
return if Rails.env.test?
|
return if Rails.env.test?
|
||||||
|
|
||||||
if User.count == 0
|
if User.admins.count == 0
|
||||||
self.level = Levels::ADMIN
|
self.level = Levels::ADMIN
|
||||||
self.can_approve_posts = true
|
self.can_approve_posts = true
|
||||||
self.can_upload_free = true
|
self.can_upload_free = true
|
||||||
|
|||||||
@@ -37,8 +37,10 @@ module Danbooru
|
|||||||
end
|
end
|
||||||
|
|
||||||
# System actions, such as sending automated dmails, will be performed with this account.
|
# System actions, such as sending automated dmails, will be performed with this account.
|
||||||
|
# This account will be created automatically if it doesn't exist. It will
|
||||||
|
# be promoted to Moderator if it isn't already a Moderator.
|
||||||
def system_user
|
def system_user
|
||||||
User.find_by_name("DanbooruBot") || User.admins.first
|
"DanbooruBot"
|
||||||
end
|
end
|
||||||
|
|
||||||
def upload_feedback_topic
|
def upload_feedback_topic
|
||||||
|
|||||||
10
config/initializers/system_user.rb
Normal file
10
config/initializers/system_user.rb
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
require "securerandom"
|
||||||
|
|
||||||
|
system = User.find_or_create_by!(name: Danbooru.config.system_user) do |user|
|
||||||
|
user.password = SecureRandom.base64(32)
|
||||||
|
end
|
||||||
|
|
||||||
|
unless system.is_moderator?
|
||||||
|
system.level = User::Levels::MODERATOR
|
||||||
|
system.save
|
||||||
|
end
|
||||||
@@ -3,6 +3,7 @@ require 'test_helper'
|
|||||||
class DmailTest < ActiveSupport::TestCase
|
class DmailTest < ActiveSupport::TestCase
|
||||||
context "A dmail" do
|
context "A dmail" do
|
||||||
setup do
|
setup do
|
||||||
|
User.any_instance.stubs(:validate_sock_puppets).returns(true)
|
||||||
@user = FactoryGirl.create(:user)
|
@user = FactoryGirl.create(:user)
|
||||||
CurrentUser.user = @user
|
CurrentUser.user = @user
|
||||||
CurrentUser.ip_addr = "1.2.3.4"
|
CurrentUser.ip_addr = "1.2.3.4"
|
||||||
@@ -23,10 +24,24 @@ class DmailTest < ActiveSupport::TestCase
|
|||||||
|
|
||||||
should "not validate" do
|
should "not validate" do
|
||||||
assert_difference("Dmail.count", 2)do
|
assert_difference("Dmail.count", 2)do
|
||||||
dmail = Dmail.create_split(:to_id => @recipient.id, :title => "My video", :body => "hey Noneeditsonlyme. My webcam see here http://bit.ly/2vTv9Ki")
|
Dmail.create_split(:to_id => @recipient.id, :title => "My video", :body => "hey Noneeditsonlyme. My webcam see here http://bit.ly/2vTv9Ki")
|
||||||
assert(dmail.is_spam?)
|
assert(@recipient.dmails.last.is_spam?)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
should "autoban spammers after sending spam to N distinct users" do
|
||||||
|
Dmail.any_instance.expects(:spam?).returns(true)
|
||||||
|
|
||||||
|
users = FactoryGirl.create_list(:user, Dmail::AUTOBAN_THRESHOLD)
|
||||||
|
users.each do |user|
|
||||||
|
Dmail.create_split(from: @user, to: user, title: "spam", body: "wonderful spam")
|
||||||
|
end
|
||||||
|
|
||||||
|
assert_equal(true, Dmail.is_spammer?(@user))
|
||||||
|
assert_equal(true, @user.reload.is_banned)
|
||||||
|
assert_equal(1, @user.bans.count)
|
||||||
|
assert_match(/Spambot./, @user.bans.last.reason)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context "filter" do
|
context "filter" do
|
||||||
@@ -177,7 +192,7 @@ class DmailTest < ActiveSupport::TestCase
|
|||||||
context "that is automated" do
|
context "that is automated" do
|
||||||
setup do
|
setup do
|
||||||
@bot = FactoryGirl.create(:user)
|
@bot = FactoryGirl.create(:user)
|
||||||
Danbooru.config.stubs(:system_user).returns(@bot)
|
User.stubs(:system).returns(@bot)
|
||||||
end
|
end
|
||||||
|
|
||||||
should "only create a copy for the recipient" do
|
should "only create a copy for the recipient" do
|
||||||
|
|||||||
@@ -85,7 +85,7 @@ class PostDisapprovalTest < ActiveSupport::TestCase
|
|||||||
|
|
||||||
should "dmail the uploaders" do
|
should "dmail the uploaders" do
|
||||||
bot = FactoryGirl.create(:user)
|
bot = FactoryGirl.create(:user)
|
||||||
Danbooru.config.stubs(:system_user).returns(bot)
|
User.stubs(:system).returns(bot)
|
||||||
|
|
||||||
assert_difference(["@uploaders[0].dmails.count", "@uploaders[1].dmails.count"], 1) do
|
assert_difference(["@uploaders[0].dmails.count", "@uploaders[1].dmails.count"], 1) do
|
||||||
PostDisapproval.dmail_messages!
|
PostDisapproval.dmail_messages!
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ class PostReplacementTest < ActiveSupport::TestCase
|
|||||||
Delayed::Worker.delay_jobs = true # don't delete the old images right away
|
Delayed::Worker.delay_jobs = true # don't delete the old images right away
|
||||||
|
|
||||||
@system = FactoryGirl.create(:user, created_at: 2.weeks.ago)
|
@system = FactoryGirl.create(:user, created_at: 2.weeks.ago)
|
||||||
Danbooru.config.stubs(:system_user).returns(@system)
|
User.stubs(:system).returns(@system)
|
||||||
|
|
||||||
@uploader = FactoryGirl.create(:user, created_at: 2.weeks.ago, can_upload_free: true)
|
@uploader = FactoryGirl.create(:user, created_at: 2.weeks.ago, can_upload_free: true)
|
||||||
@replacer = FactoryGirl.create(:user, created_at: 2.weeks.ago, can_approve_posts: true)
|
@replacer = FactoryGirl.create(:user, created_at: 2.weeks.ago, can_approve_posts: true)
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ class UserTest < ActiveSupport::TestCase
|
|||||||
|
|
||||||
should "send an automated dmail to the user" do
|
should "send an automated dmail to the user" do
|
||||||
bot = FactoryGirl.create(:user)
|
bot = FactoryGirl.create(:user)
|
||||||
Danbooru.config.stubs(:system_user).returns(bot)
|
User.stubs(:system).returns(bot)
|
||||||
|
|
||||||
assert_difference("Dmail.count", 1) do
|
assert_difference("Dmail.count", 1) do
|
||||||
@user.promote_to!(User::Levels::GOLD)
|
@user.promote_to!(User::Levels::GOLD)
|
||||||
|
|||||||
Reference in New Issue
Block a user