From b48211cd4a98026b21715df039725507996d975d Mon Sep 17 00:00:00 2001 From: evazion Date: Fri, 15 Dec 2017 12:38:17 -0600 Subject: [PATCH 1/3] dmails: only spam check recipient's copy of the dmail. Each dmail creates two copies, one for the sender and one for the receiver. Only spam check the receiver's copy. Prevents senders from being able to tell when their messages are being spam filtered. --- app/models/dmail.rb | 6 +++--- test/unit/dmail_test.rb | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/app/models/dmail.rb b/app/models/dmail.rb index 4e861d5d1..ea2a29114 100644 --- a/app/models/dmail.rb +++ b/app/models/dmail.rb @@ -31,9 +31,9 @@ class Dmail < ApplicationRecord creator_ip_addr.to_s end - def spam?(sender = CurrentUser.user) + def spam? return false if Danbooru.config.rakismet_key.blank? - return false if sender.is_gold? + return false if from.is_gold? super() end end @@ -58,7 +58,6 @@ class Dmail < ApplicationRecord def initialize_attributes self.from_id ||= CurrentUser.id self.creator_ip_addr ||= CurrentUser.ip_addr - self.is_spam = spam?(CurrentUser.user) end end @@ -73,6 +72,7 @@ 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 diff --git a/test/unit/dmail_test.rb b/test/unit/dmail_test.rb index 0d4427cce..a2ab36fc2 100644 --- a/test/unit/dmail_test.rb +++ b/test/unit/dmail_test.rb @@ -23,8 +23,8 @@ class DmailTest < ActiveSupport::TestCase should "not validate" 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") - assert(dmail.is_spam?) + Dmail.create_split(:to_id => @recipient.id, :title => "My video", :body => "hey Noneeditsonlyme. My webcam see here http://bit.ly/2vTv9Ki") + assert(@recipient.dmails.last.is_spam?) end end end From e2eb45a5a3bb7bc5ac46846e8352b9af2cda7e7c Mon Sep 17 00:00:00 2001 From: evazion Date: Fri, 15 Dec 2017 14:51:24 -0600 Subject: [PATCH 2/3] Auto-promote DanbooruBot to Mod. Auto-create DanbooruBot if it doesn't exist. --- app/models/dmail.rb | 4 ++-- app/models/user.rb | 4 ++-- config/danbooru_default_config.rb | 4 +++- config/initializers/system_user.rb | 10 ++++++++++ test/unit/dmail_test.rb | 2 +- test/unit/post_disapproval_test.rb | 2 +- test/unit/post_replacement_test.rb | 2 +- test/unit/user_test.rb | 2 +- 8 files changed, 21 insertions(+), 9 deletions(-) create mode 100644 config/initializers/system_user.rb diff --git a/app/models/dmail.rb b/app/models/dmail.rb index ea2a29114..da4344a3e 100644 --- a/app/models/dmail.rb +++ b/app/models/dmail.rb @@ -86,7 +86,7 @@ class Dmail < ApplicationRecord end 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.save dmail @@ -234,7 +234,7 @@ class Dmail < ApplicationRecord end def is_automated? - from == Danbooru.config.system_user + from == User.system end def filtered? diff --git a/app/models/user.rb b/app/models/user.rb index ca582c23f..be950b7f8 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -316,7 +316,7 @@ class User < ApplicationRecord module ClassMethods def system - Danbooru.config.system_user + User.find_by!(name: Danbooru.config.system_user) end def level_hash @@ -366,7 +366,7 @@ class User < ApplicationRecord def promote_to_admin_if_first_user return if Rails.env.test? - if User.count == 0 + if User.admins.count == 0 self.level = Levels::ADMIN self.can_approve_posts = true self.can_upload_free = true diff --git a/config/danbooru_default_config.rb b/config/danbooru_default_config.rb index ec254f56a..874eef4a6 100644 --- a/config/danbooru_default_config.rb +++ b/config/danbooru_default_config.rb @@ -37,8 +37,10 @@ module Danbooru end # 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 - User.find_by_name("DanbooruBot") || User.admins.first + "DanbooruBot" end def upload_feedback_topic diff --git a/config/initializers/system_user.rb b/config/initializers/system_user.rb new file mode 100644 index 000000000..2c210facd --- /dev/null +++ b/config/initializers/system_user.rb @@ -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 diff --git a/test/unit/dmail_test.rb b/test/unit/dmail_test.rb index a2ab36fc2..875eaedb8 100644 --- a/test/unit/dmail_test.rb +++ b/test/unit/dmail_test.rb @@ -177,7 +177,7 @@ class DmailTest < ActiveSupport::TestCase context "that is automated" do setup do @bot = FactoryGirl.create(:user) - Danbooru.config.stubs(:system_user).returns(@bot) + User.stubs(:system).returns(@bot) end should "only create a copy for the recipient" do diff --git a/test/unit/post_disapproval_test.rb b/test/unit/post_disapproval_test.rb index 8b010d8f8..c4ef31399 100644 --- a/test/unit/post_disapproval_test.rb +++ b/test/unit/post_disapproval_test.rb @@ -85,7 +85,7 @@ class PostDisapprovalTest < ActiveSupport::TestCase should "dmail the uploaders" do 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 PostDisapproval.dmail_messages! diff --git a/test/unit/post_replacement_test.rb b/test/unit/post_replacement_test.rb index e33fdbf4a..3785bafea 100644 --- a/test/unit/post_replacement_test.rb +++ b/test/unit/post_replacement_test.rb @@ -20,7 +20,7 @@ class PostReplacementTest < ActiveSupport::TestCase Delayed::Worker.delay_jobs = true # don't delete the old images right away @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) @replacer = FactoryGirl.create(:user, created_at: 2.weeks.ago, can_approve_posts: true) diff --git a/test/unit/user_test.rb b/test/unit/user_test.rb index 8cd53246e..21efdb64c 100644 --- a/test/unit/user_test.rb +++ b/test/unit/user_test.rb @@ -28,7 +28,7 @@ class UserTest < ActiveSupport::TestCase should "send an automated dmail to the user" do bot = FactoryGirl.create(:user) - Danbooru.config.stubs(:system_user).returns(bot) + User.stubs(:system).returns(bot) assert_difference("Dmail.count", 1) do @user.promote_to!(User::Levels::GOLD) From 002b5e385a3543e18bb7b8d2b4d615444b8a0c4d Mon Sep 17 00:00:00 2001 From: evazion Date: Fri, 15 Dec 2017 15:55:27 -0600 Subject: [PATCH 3/3] Autoban dmail spambots (#3408). If a user sends spam to more than 10 users within a 24 hour window, automatically ban them for 3 days. --- app/models/dmail.rb | 28 ++++++++++++++++++++++++++++ test/unit/dmail_test.rb | 15 +++++++++++++++ 2 files changed, 43 insertions(+) diff --git a/app/models/dmail.rb b/app/models/dmail.rb index da4344a3e..0bfcb7eb0 100644 --- a/app/models/dmail.rb +++ b/app/models/dmail.rb @@ -1,6 +1,11 @@ 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 + include Rakismet::Model 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 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 "#{title}\n\n#{body}" end @@ -80,6 +102,8 @@ 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 @@ -119,6 +143,10 @@ class Dmail < ApplicationRecord end module SearchMethods + def sent_by(user) + where("dmails.from_id = ? AND dmails.owner_id != ?", user.id, user.id) + end + def active where("is_deleted = ?", false) end diff --git a/test/unit/dmail_test.rb b/test/unit/dmail_test.rb index 875eaedb8..7e40ffcd9 100644 --- a/test/unit/dmail_test.rb +++ b/test/unit/dmail_test.rb @@ -3,6 +3,7 @@ require 'test_helper' class DmailTest < ActiveSupport::TestCase context "A dmail" do setup do + User.any_instance.stubs(:validate_sock_puppets).returns(true) @user = FactoryGirl.create(:user) CurrentUser.user = @user CurrentUser.ip_addr = "1.2.3.4" @@ -27,6 +28,20 @@ class DmailTest < ActiveSupport::TestCase assert(@recipient.dmails.last.is_spam?) 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 context "filter" do