Add sockpuppet spammer detection. Automatically flag posts if they are from new users (registered less than 24 hours ago) at the same IP address and one of them started the topic.

This commit is contained in:
Neil Lalonde
2013-10-11 13:33:23 -04:00
parent c1ba41195e
commit 3c2c6ab24b
9 changed files with 472 additions and 295 deletions

View File

@ -73,6 +73,8 @@ class SiteSetting < ActiveRecord::Base
setting(:num_users_to_block_new_user, 3) setting(:num_users_to_block_new_user, 3)
setting(:notify_mods_when_user_blocked, false) setting(:notify_mods_when_user_blocked, false)
setting(:flag_sockpuppets, true)
# used mainly for dev, force hostname for Discourse.base_url # used mainly for dev, force hostname for Discourse.base_url
# You would usually use multisite for this # You would usually use multisite for this
setting(:force_hostname, '') setting(:force_hostname, '')

View File

@ -249,6 +249,10 @@ class User < ActiveRecord::Base
self.password_hash == hash_password(password, salt) self.password_hash == hash_password(password, salt)
end end
def new_user?
created_at >= 24.hours.ago || trust_level == TrustLevel.levels[:newuser]
end
def seen_before? def seen_before?
last_seen_at.present? last_seen_at.present?
end end

View File

@ -4,27 +4,55 @@ class SpamRulesEnforcer
include Rails.application.routes.url_helpers include Rails.application.routes.url_helpers
# The exclamation point means that this method may make big changes to posts and the user. # The exclamation point means that this method may make big changes to posts and users.
def self.enforce!(user) def self.enforce!(arg)
SpamRulesEnforcer.new(user).enforce! SpamRulesEnforcer.new(arg).enforce!
end end
def initialize(arg)
@user = arg if arg.is_a?(User)
@post = arg if arg.is_a?(Post)
end
def enforce!
# TODO: once rules are in their own classes, invoke them from here in priority order
if @user
block_user if block?
end
if @post
flag_sockpuppet_users if SiteSetting.flag_sockpuppets and reply_is_from_sockpuppet?
end
true
end
# TODO: move this sockpuppet code to its own class. We should be able to add more rules, like ActiveModel validators.
def reply_is_from_sockpuppet?
return false if @post.post_number and @post.post_number == 1
first_post = @post.topic.posts.by_post_number.first
return false if first_post.user.nil?
!first_post.user.staff? and !@post.user.staff? and
@post.user != first_post.user and
@post.user.ip_address == first_post.user.ip_address and
@post.user.new_user?
end
def flag_sockpuppet_users
system_user = Discourse.system_user
PostAction.act(system_user, @post, PostActionType.types[:spam]) rescue PostAction::AlreadyActed
if (first_post = @post.topic.posts.by_post_number.first).try(:user).try(:new_user?)
PostAction.act(system_user, first_post, PostActionType.types[:spam]) rescue PostAction::AlreadyActed
end
end
# TODO: move all this auto-block code to another class:
def self.block?(user) def self.block?(user)
SpamRulesEnforcer.new(user).block? SpamRulesEnforcer.new(user).block?
end end
def self.punish!(user) def self.punish!(user)
SpamRulesEnforcer.new(user).punish_user SpamRulesEnforcer.new(user).block_user
end
def initialize(user)
@user = user
end
def enforce!
punish_user if block?
true
end end
def block? def block?
@ -46,7 +74,7 @@ class SpamRulesEnforcer
PostAction.spam_flags.where(post_id: post_ids).uniq.pluck(:user_id).size PostAction.spam_flags.where(post_id: post_ids).uniq.pluck(:user_id).size
end end
def punish_user def block_user
Post.transaction do Post.transaction do
if UserBlocker.block(@user, nil, {message: :too_many_spam_flags}) and SiteSetting.notify_mods_when_user_blocked if UserBlocker.block(@user, nil, {message: :too_many_spam_flags}) and SiteSetting.notify_mods_when_user_blocked
GroupMessage.create(Group[:moderators].name, :user_automatically_blocked, {user: @user, limit_once_per: false}) GroupMessage.create(Group[:moderators].name, :user_automatically_blocked, {user: @user, limit_once_per: false})

View File

@ -537,6 +537,7 @@ en:
num_flags_to_block_new_user: "If a new user's posts get this many spam flags from (n) different users, hide all their posts and prevent future posting. 0 disables this feature." num_flags_to_block_new_user: "If a new user's posts get this many spam flags from (n) different users, hide all their posts and prevent future posting. 0 disables this feature."
num_users_to_block_new_user: "If a new user's posts get (x) spam flags from this many different users, hide all their posts and prevent future posting. 0 disables this feature." num_users_to_block_new_user: "If a new user's posts get (x) spam flags from this many different users, hide all their posts and prevent future posting. 0 disables this feature."
notify_mods_when_user_blocked: "If a user is automatically blocked, send a message to all moderators." notify_mods_when_user_blocked: "If a user is automatically blocked, send a message to all moderators."
flag_sockpuppets: "If a new user (i.e., registered in the last 24 hours) who started a topic and a new user who replies in that topic are at the same IP address, both their posts will automatically be flagged as spam."
traditional_markdown_linebreaks: "Use traditional linebreaks in Markdown, which require two trailing spaces for a linebreak" traditional_markdown_linebreaks: "Use traditional linebreaks in Markdown, which require two trailing spaces for a linebreak"
post_undo_action_window_mins: "Number of seconds users are allowed to reverse actions on a post (like, flag, etc)" post_undo_action_window_mins: "Number of seconds users are allowed to reverse actions on a post (like, flag, etc)"

View File

@ -76,6 +76,8 @@ class PostCreator
{ user: @user, { user: @user,
limit_once_per: 24.hours, limit_once_per: 24.hours,
message_params: {domains: @post.linked_hosts.keys.join(', ')} } ) message_params: {domains: @post.linked_hosts.keys.join(', ')} } )
else
SpamRulesEnforcer.enforce!(@post)
end end
enqueue_jobs enqueue_jobs

View File

@ -8,6 +8,7 @@ Fabricator(:user) do
password 'myawesomepassword' password 'myawesomepassword'
trust_level TrustLevel.levels[:basic] trust_level TrustLevel.levels[:basic]
bio_raw "I'm batman!" bio_raw "I'm batman!"
ip_address { sequence(:ip_address) { |i| "99.232.23.#{i%254}"} }
end end
Fabricator(:coding_horror, from: :user) do Fabricator(:coding_horror, from: :user) do

View File

@ -0,0 +1,59 @@
# encoding: UTF-8
require 'spec_helper'
describe SpamRulesEnforcer do
Given(:ip_address) { '182.189.119.174' }
Given!(:spammer1) { Fabricate(:user, ip_address: ip_address) }
Given!(:spammer2) { Fabricate(:user, ip_address: ip_address) }
Given(:spammer3) { Fabricate(:user, ip_address: ip_address) }
context 'flag_sockpuppets is disabled' do
Given { SiteSetting.stubs(:flag_sockpuppets).returns(false) }
Given!(:first_post) { create_post(user: spammer1) }
Given!(:second_post) { create_post(user: spammer2, topic: first_post.topic) }
Then { first_post.reload.spam_count.should == 0 }
And { second_post.reload.spam_count.should == 0 }
end
context 'flag_sockpuppets is enabled' do
Given { SiteSetting.stubs(:flag_sockpuppets).returns(true) }
context 'first spammer starts a topic' do
Given!(:first_post) { create_post(user: spammer1) }
context 'second spammer replies' do
Given!(:second_post) { create_post(user: spammer2, topic: first_post.topic) }
Then { first_post.reload.spam_count.should == 1 }
And { second_post.reload.spam_count.should == 1 }
context 'third spam post' do
Given!(:third_post) { create_post(user: spammer3, topic: first_post.topic) }
Then { first_post.reload.spam_count.should == 1 }
And { second_post.reload.spam_count.should == 1 }
And { third_post.reload.spam_count.should == 1 }
end
end
end
context 'first user is not new' do
Given!(:old_user) { Fabricate(:user, ip_address: ip_address, created_at: 2.days.ago, trust_level: TrustLevel.levels[:basic]) }
context 'first user starts a topic' do
Given!(:first_post) { create_post(user: old_user) }
context 'a reply by a new user at the same IP address' do
Given!(:second_post) { create_post(user: spammer2, topic: first_post.topic) }
Then { first_post.reload.spam_count.should == 0 }
And { second_post.reload.spam_count.should == 1 }
end
end
end
end
end

View File

@ -2,104 +2,106 @@
require 'spec_helper' require 'spec_helper'
describe PostAction do describe SpamRulesEnforcer do
before do describe 'auto-blocking users based on flagging' do
SiteSetting.stubs(:flags_required_to_hide_post).returns(0) # never before do
SiteSetting.stubs(:num_flags_to_block_new_user).returns(2) SiteSetting.stubs(:flags_required_to_hide_post).returns(0) # never
SiteSetting.stubs(:num_users_to_block_new_user).returns(2) SiteSetting.stubs(:num_flags_to_block_new_user).returns(2)
end SiteSetting.stubs(:num_users_to_block_new_user).returns(2)
end
Given!(:admin) { Fabricate(:admin) } # needed to send a system message Given!(:admin) { Fabricate(:admin) } # needed to send a system message
Given!(:moderator) { Fabricate(:moderator) } Given!(:moderator) { Fabricate(:moderator) }
Given(:user1) { Fabricate(:user) } Given(:user1) { Fabricate(:user) }
Given(:user2) { Fabricate(:user) } Given(:user2) { Fabricate(:user) }
context 'spammer is a new user' do context 'spammer is a new user' do
Given(:spammer) { Fabricate(:user, trust_level: TrustLevel.levels[:newuser]) } Given(:spammer) { Fabricate(:user, trust_level: TrustLevel.levels[:newuser]) }
context 'spammer post is not flagged enough times' do context 'spammer post is not flagged enough times' do
Given!(:spam_post) { create_post(user: spammer) } Given!(:spam_post) { create_post(user: spammer) }
Given!(:spam_post2) { create_post(user: spammer) } Given!(:spam_post2) { create_post(user: spammer) }
When { PostAction.act(user1, spam_post, PostActionType.types[:spam]) } When { PostAction.act(user1, spam_post, PostActionType.types[:spam]) }
Then { expect(spam_post.reload).to_not be_hidden } Then { expect(spam_post.reload).to_not be_hidden }
context 'spam posts are flagged enough times, but not by enough users' do context 'spam posts are flagged enough times, but not by enough users' do
When { PostAction.act(user1, spam_post2, PostActionType.types[:spam]) } When { PostAction.act(user1, spam_post2, PostActionType.types[:spam]) }
Then { expect(spam_post.reload).to_not be_hidden } Then { expect(spam_post.reload).to_not be_hidden }
And { expect(spam_post2.reload).to_not be_hidden } And { expect(spam_post2.reload).to_not be_hidden }
And { expect(spammer.reload).to_not be_blocked } And { expect(spammer.reload).to_not be_blocked }
end
context 'one spam post is flagged enough times by enough users' do
Given!(:another_topic) { Fabricate(:topic) }
Given!(:private_messages_count) { spammer.private_topics_count }
Given!(:mod_pm_count) { moderator.private_topics_count }
When { PostAction.act(user2, spam_post, PostActionType.types[:spam]) }
Invariant { expect(Guardian.new(spammer).can_create_topic?(nil)).to be_false }
Invariant { expect{PostCreator.create(spammer, {title: 'limited time offer for you', raw: 'better buy this stuff ok', archetype_id: 1})}.to raise_error(Discourse::InvalidAccess) }
Invariant { expect{PostCreator.create(spammer, {topic_id: another_topic.id, raw: 'my reply is spam in your topic', archetype_id: 1})}.to raise_error(Discourse::InvalidAccess) }
Then { expect(spammer.reload).to be_blocked }
And { expect(spam_post.reload).to be_hidden }
And { expect(spam_post2.reload).to be_hidden }
And { expect(spammer.reload.private_topics_count).to eq(private_messages_count + 1) }
# The following cases describe when a staff user takes some action, but the user
# still won't be able to make posts.
# A staff user needs to clear the blocked flag from the user record.
context "a post's flags are cleared" do
When { PostAction.clear_flags!(spam_post, admin); spammer.reload }
Then { expect(spammer.reload).to be_blocked }
end
context "a post is deleted" do
When { spam_post.trash!(moderator); spammer.reload }
Then { expect(spammer.reload).to be_blocked }
end
context "spammer becomes a basic user" do
When { spammer.change_trust_level!(:basic); spammer.reload }
Then { expect(spammer.reload).to be_blocked }
end
end
context 'flags_required_to_hide_post takes effect too' do
Given { SiteSetting.stubs(:flags_required_to_hide_post).returns(2) }
When { PostAction.act(user2, spam_post, PostActionType.types[:spam]) }
Then { expect(spammer.reload).to be_blocked }
And { expect(Guardian.new(spammer).can_create_topic?(nil)).to be_false }
end
end end
end
context "spammer has trust level basic" do
Given(:spammer) { Fabricate(:user, trust_level: TrustLevel.levels[:basic]) }
context 'one spam post is flagged enough times by enough users' do context 'one spam post is flagged enough times by enough users' do
Given!(:another_topic) { Fabricate(:topic) } Given!(:spam_post) { Fabricate(:post, user: spammer) }
Given!(:private_messages_count) { spammer.private_topics_count } Given!(:private_messages_count) { spammer.private_topics_count }
Given!(:mod_pm_count) { moderator.private_topics_count } When { PostAction.act(user1, spam_post, PostActionType.types[:spam]) }
When { PostAction.act(user2, spam_post, PostActionType.types[:spam]) } When { PostAction.act(user2, spam_post, PostActionType.types[:spam]) }
Then { expect(spam_post.reload).to_not be_hidden }
Invariant { expect(Guardian.new(spammer).can_create_topic?(nil)).to be_false } And { expect(Guardian.new(spammer).can_create_topic?(nil)).to be_true }
Invariant { expect{PostCreator.create(spammer, {title: 'limited time offer for you', raw: 'better buy this stuff ok', archetype_id: 1})}.to raise_error(Discourse::InvalidAccess) } And { expect{PostCreator.create(spammer, {title: 'limited time offer for you', raw: 'better buy this stuff ok', archetype_id: 1})}.to_not raise_error }
Invariant { expect{PostCreator.create(spammer, {topic_id: another_topic.id, raw: 'my reply is spam in your topic', archetype_id: 1})}.to raise_error(Discourse::InvalidAccess) } And { expect(spammer.reload.private_topics_count).to eq(private_messages_count) }
Then { expect(spammer.reload).to be_blocked }
And { expect(spam_post.reload).to be_hidden }
And { expect(spam_post2.reload).to be_hidden }
And { expect(spammer.reload.private_topics_count).to eq(private_messages_count + 1) }
# The following cases describe when a staff user takes some action, but the user
# still won't be able to make posts.
# A staff user needs to clear the blocked flag from the user record.
context "a post's flags are cleared" do
When { PostAction.clear_flags!(spam_post, admin); spammer.reload }
Then { expect(spammer.reload).to be_blocked }
end
context "a post is deleted" do
When { spam_post.trash!(moderator); spammer.reload }
Then { expect(spammer.reload).to be_blocked }
end
context "spammer becomes a basic user" do
When { spammer.change_trust_level!(:basic); spammer.reload }
Then { expect(spammer.reload).to be_blocked }
end
end
context 'flags_required_to_hide_post takes effect too' do
Given { SiteSetting.stubs(:flags_required_to_hide_post).returns(2) }
When { PostAction.act(user2, spam_post, PostActionType.types[:spam]) }
Then { expect(spammer.reload).to be_blocked }
And { expect(Guardian.new(spammer).can_create_topic?(nil)).to be_false }
end end
end end
end
context "spammer has trust level basic" do [[:user, trust_level: TrustLevel.levels[:regular]], [:admin], [:moderator]].each do |spammer_args|
Given(:spammer) { Fabricate(:user, trust_level: TrustLevel.levels[:basic]) } context "spammer is trusted #{spammer_args[0]}" do
Given!(:spammer) { Fabricate(*spammer_args) }
context 'one spam post is flagged enough times by enough users' do Given!(:spam_post) { Fabricate(:post, user: spammer) }
Given!(:spam_post) { Fabricate(:post, user: spammer) } Given!(:private_messages_count) { spammer.private_topics_count }
Given!(:private_messages_count) { spammer.private_topics_count } When { PostAction.act(user1, spam_post, PostActionType.types[:spam]) }
When { PostAction.act(user1, spam_post, PostActionType.types[:spam]) } When { PostAction.act(user2, spam_post, PostActionType.types[:spam]) }
When { PostAction.act(user2, spam_post, PostActionType.types[:spam]) } Then { expect(spam_post.reload).to_not be_hidden }
Then { expect(spam_post.reload).to_not be_hidden } end
And { expect(Guardian.new(spammer).can_create_topic?(nil)).to be_true }
And { expect{PostCreator.create(spammer, {title: 'limited time offer for you', raw: 'better buy this stuff ok', archetype_id: 1})}.to_not raise_error }
And { expect(spammer.reload.private_topics_count).to eq(private_messages_count) }
end
end
[[:user, trust_level: TrustLevel.levels[:regular]], [:admin], [:moderator]].each do |spammer_args|
context "spammer is trusted #{spammer_args[0]}" do
Given!(:spammer) { Fabricate(*spammer_args) }
Given!(:spam_post) { Fabricate(:post, user: spammer) }
Given!(:private_messages_count) { spammer.private_topics_count }
When { PostAction.act(user1, spam_post, PostActionType.types[:spam]) }
When { PostAction.act(user2, spam_post, PostActionType.types[:spam]) }
Then { expect(spam_post.reload).to_not be_hidden }
end end
end end
end end

View File

@ -6,228 +6,306 @@ describe SpamRulesEnforcer do
SystemMessage.stubs(:create) SystemMessage.stubs(:create)
end end
before do context 'flagging posts based on IP address of users' do
SiteSetting.stubs(:flags_required_to_hide_post).returns(0) # never describe 'reply_is_from_sockpuppet?' do
SiteSetting.stubs(:num_flags_to_block_new_user).returns(2) let(:user1) { Fabricate(:user, ip_address: '182.189.119.174') }
SiteSetting.stubs(:num_users_to_block_new_user).returns(2) let(:post1) { Fabricate(:post, user: user1, topic: Fabricate(:topic, user: user1)) }
end
describe 'enforce!' do it 'is false for the first post in a topic' do
let(:post) { Fabricate.build(:post, user: Fabricate.build(:user, trust_level: TrustLevel.levels[:newuser])) } SpamRulesEnforcer.new(post1).reply_is_from_sockpuppet?.should eq(false)
subject { SpamRulesEnforcer.new(post.user) } end
it "does nothing if the user's trust level is higher than 'new user'" do it 'is false if users have different IP addresses' do
basic_user = Fabricate.build(:user, trust_level: TrustLevel.levels[:basic]) post2 = Fabricate(:post, user: Fabricate(:user, ip_address: '182.189.199.199'), topic: post1.topic)
enforcer = SpamRulesEnforcer.new(basic_user) SpamRulesEnforcer.new(post2).reply_is_from_sockpuppet?.should eq(false)
enforcer.expects(:num_spam_flags_against_user).never end
enforcer.expects(:num_users_who_flagged_spam_against_user).never
enforcer.expects(:punish_user).never
enforcer.enforce!
end
it 'takes no action if not enough flags by enough users have been submitted' do it 'is true if users have the same IP address' do
subject.stubs(:block?).returns(false) post2 = Fabricate(:post, user: Fabricate(:user, ip_address: '182.189.119.174'), topic: post1.topic)
subject.expects(:punish_user).never SpamRulesEnforcer.new(post2).reply_is_from_sockpuppet?.should eq(true)
subject.enforce! end
end
it 'delivers punishment when there are enough flags from enough users' do it 'is false if reply and first post are from the same user' do
subject.stubs(:block?).returns(true) post2 = Fabricate(:post, user: user1, topic: post1.topic)
subject.expects(:punish_user) SpamRulesEnforcer.new(post2).reply_is_from_sockpuppet?.should eq(false)
subject.enforce! end
it 'is false if first post user is staff' do
staff1 = Fabricate(:admin, ip_address: '182.189.119.174')
staff_post1 = Fabricate(:post, user: staff1, topic: Fabricate(:topic, user: staff1))
post2 = Fabricate(:post, user: Fabricate(:user, ip_address: staff1.ip_address), topic: staff_post1.topic)
SpamRulesEnforcer.new(post2).reply_is_from_sockpuppet?.should eq(false)
end
it 'is false if second post user is staff' do
post2 = Fabricate(:post, user: Fabricate(:moderator, ip_address: user1.ip_address), topic: post1.topic)
SpamRulesEnforcer.new(post2).reply_is_from_sockpuppet?.should eq(false)
end
it 'is false if both users are staff' do
staff1 = Fabricate(:moderator, ip_address: '182.189.119.174')
staff_post1 = Fabricate(:post, user: staff1, topic: Fabricate(:topic, user: staff1))
post2 = Fabricate(:post, user: Fabricate(:admin, ip_address: staff1.ip_address), topic: staff_post1.topic)
SpamRulesEnforcer.new(post2).reply_is_from_sockpuppet?.should eq(false)
end
it 'is true if first post user was created over 24 hours ago and has trust level higher than 0' do
old_user = Fabricate(:user, ip_address: '182.189.119.174', created_at: 25.hours.ago, trust_level: TrustLevel.levels[:basic])
first_post = Fabricate(:post, user: old_user, topic: Fabricate(:topic, user: old_user))
post2 = Fabricate(:post, user: Fabricate(:user, ip_address: old_user.ip_address), topic: first_post.topic)
SpamRulesEnforcer.new(post2).reply_is_from_sockpuppet?.should eq(true)
end
it 'is false if second post user was created over 24 hours ago and has trust level higher than 0' do
post2 = Fabricate(:post, user: Fabricate(:user, ip_address: user1.ip_address, created_at: 25.hours.ago, trust_level: TrustLevel.levels[:basic]), topic: post1.topic)
SpamRulesEnforcer.new(post2).reply_is_from_sockpuppet?.should eq(false)
end
it 'is true if first post user was created less that 24 hours ago and has trust level higher than 0' do
new_user = Fabricate(:user, ip_address: '182.189.119.174', created_at: 1.hour.ago, trust_level: TrustLevel.levels[:basic])
first_post = Fabricate(:post, user: new_user, topic: Fabricate(:topic, user: new_user))
post2 = Fabricate(:post, user: Fabricate(:user, ip_address: new_user.ip_address), topic: first_post.topic)
SpamRulesEnforcer.new(post2).reply_is_from_sockpuppet?.should eq(true)
end
it 'is true if second user was created less that 24 hours ago and has trust level higher than 0' do
post2 = Fabricate(:post, user: Fabricate(:user, ip_address: user1.ip_address, created_at: 23.hours.ago, trust_level: TrustLevel.levels[:basic]), topic: post1.topic)
SpamRulesEnforcer.new(post2).reply_is_from_sockpuppet?.should eq(true)
end
# A weird case
it 'is false when user is nil on first post' do
post1.user = nil; post1.save!
post2 = Fabricate(:post, user: Fabricate(:user), topic: post1.topic)
SpamRulesEnforcer.new(post2).reply_is_from_sockpuppet?.should eq(false)
end
end end
end end
describe 'num_spam_flags_against_user' do context 'auto-blocking users based on flags' do
before { SpamRulesEnforcer.any_instance.stubs(:punish_user) }
let(:post) { Fabricate(:post) }
let(:enforcer) { SpamRulesEnforcer.new(post.user) }
subject { enforcer.num_spam_flags_against_user }
it 'returns 0 when there are no flags' do
expect(subject).to eq(0)
end
it 'returns 0 when there is one flag that has a reason other than spam' do
Fabricate(:flag, post: post, post_action_type_id: PostActionType.types[:off_topic])
expect(subject).to eq(0)
end
it 'returns 2 when there are two flags with spam as the reason' do
2.times { Fabricate(:flag, post: post, post_action_type_id: PostActionType.types[:spam]) }
expect(subject).to eq(2)
end
it 'returns 2 when there are two spam flags, each on a different post' do
Fabricate(:flag, post: post, post_action_type_id: PostActionType.types[:spam])
Fabricate(:flag, post: Fabricate(:post, user: post.user), post_action_type_id: PostActionType.types[:spam])
expect(subject).to eq(2)
end
end
describe 'num_users_who_flagged_spam_against_user' do
before { SpamRulesEnforcer.any_instance.stubs(:punish_user) }
let(:post) { Fabricate(:post) }
let(:enforcer) { SpamRulesEnforcer.new(post.user) }
subject { enforcer.num_users_who_flagged_spam_against_user }
it 'returns 0 when there are no flags' do
expect(subject).to eq(0)
end
it 'returns 0 when there is one flag that has a reason other than spam' do
Fabricate(:flag, post: post, post_action_type_id: PostActionType.types[:off_topic])
expect(subject).to eq(0)
end
it 'returns 1 when there is one spam flag' do
Fabricate(:flag, post: post, post_action_type_id: PostActionType.types[:spam])
expect(subject).to eq(1)
end
it 'returns 2 when there are two spam flags from 2 users' do
Fabricate(:flag, post: post, post_action_type_id: PostActionType.types[:spam])
Fabricate(:flag, post: post, post_action_type_id: PostActionType.types[:spam])
expect(subject).to eq(2)
end
it 'returns 1 when there are two spam flags on two different posts from 1 user' do
flagger = Fabricate(:user)
Fabricate(:flag, post: post, user: flagger, post_action_type_id: PostActionType.types[:spam])
Fabricate(:flag, post: Fabricate(:post, user: post.user), user: flagger, post_action_type_id: PostActionType.types[:spam])
expect(subject).to eq(1)
end
end
describe 'punish_user' do
let!(:admin) { Fabricate(:admin) } # needed for SystemMessage
let(:user) { Fabricate(:user) }
let!(:post) { Fabricate(:post, user: user) }
subject { SpamRulesEnforcer.new(user) }
before do before do
SpamRulesEnforcer.stubs(:block?).with {|u| u.id != user.id }.returns(false) SiteSetting.stubs(:flags_required_to_hide_post).returns(0) # never
SpamRulesEnforcer.stubs(:block?).with {|u| u.id == user.id }.returns(true) SiteSetting.stubs(:num_flags_to_block_new_user).returns(2)
subject.stubs(:block?).returns(true) SiteSetting.stubs(:num_users_to_block_new_user).returns(2)
end end
context 'user is not blocked' do describe 'enforce!' do
let(:post) { Fabricate.build(:post, user: Fabricate.build(:user, trust_level: TrustLevel.levels[:newuser])) }
subject { SpamRulesEnforcer.new(post.user) }
it "does nothing if the user's trust level is higher than 'new user'" do
basic_user = Fabricate.build(:user, trust_level: TrustLevel.levels[:basic])
enforcer = SpamRulesEnforcer.new(basic_user)
enforcer.expects(:num_spam_flags_against_user).never
enforcer.expects(:num_users_who_flagged_spam_against_user).never
enforcer.expects(:block_user).never
enforcer.enforce!
end
it 'takes no action if not enough flags by enough users have been submitted' do
subject.stubs(:block?).returns(false)
subject.expects(:block_user).never
subject.enforce!
end
it 'delivers punishment when there are enough flags from enough users' do
subject.stubs(:block?).returns(true)
subject.expects(:block_user)
subject.enforce!
end
end
describe 'num_spam_flags_against_user' do
before { SpamRulesEnforcer.any_instance.stubs(:block_user) }
let(:post) { Fabricate(:post) }
let(:enforcer) { SpamRulesEnforcer.new(post.user) }
subject { enforcer.num_spam_flags_against_user }
it 'returns 0 when there are no flags' do
expect(subject).to eq(0)
end
it 'returns 0 when there is one flag that has a reason other than spam' do
Fabricate(:flag, post: post, post_action_type_id: PostActionType.types[:off_topic])
expect(subject).to eq(0)
end
it 'returns 2 when there are two flags with spam as the reason' do
2.times { Fabricate(:flag, post: post, post_action_type_id: PostActionType.types[:spam]) }
expect(subject).to eq(2)
end
it 'returns 2 when there are two spam flags, each on a different post' do
Fabricate(:flag, post: post, post_action_type_id: PostActionType.types[:spam])
Fabricate(:flag, post: Fabricate(:post, user: post.user), post_action_type_id: PostActionType.types[:spam])
expect(subject).to eq(2)
end
end
describe 'num_users_who_flagged_spam_against_user' do
before { SpamRulesEnforcer.any_instance.stubs(:block_user) }
let(:post) { Fabricate(:post) }
let(:enforcer) { SpamRulesEnforcer.new(post.user) }
subject { enforcer.num_users_who_flagged_spam_against_user }
it 'returns 0 when there are no flags' do
expect(subject).to eq(0)
end
it 'returns 0 when there is one flag that has a reason other than spam' do
Fabricate(:flag, post: post, post_action_type_id: PostActionType.types[:off_topic])
expect(subject).to eq(0)
end
it 'returns 1 when there is one spam flag' do
Fabricate(:flag, post: post, post_action_type_id: PostActionType.types[:spam])
expect(subject).to eq(1)
end
it 'returns 2 when there are two spam flags from 2 users' do
Fabricate(:flag, post: post, post_action_type_id: PostActionType.types[:spam])
Fabricate(:flag, post: post, post_action_type_id: PostActionType.types[:spam])
expect(subject).to eq(2)
end
it 'returns 1 when there are two spam flags on two different posts from 1 user' do
flagger = Fabricate(:user)
Fabricate(:flag, post: post, user: flagger, post_action_type_id: PostActionType.types[:spam])
Fabricate(:flag, post: Fabricate(:post, user: post.user), user: flagger, post_action_type_id: PostActionType.types[:spam])
expect(subject).to eq(1)
end
end
describe 'block_user' do
let!(:admin) { Fabricate(:admin) } # needed for SystemMessage
let(:user) { Fabricate(:user) }
let!(:post) { Fabricate(:post, user: user) }
subject { SpamRulesEnforcer.new(user) }
before do before do
UserBlocker.expects(:block).with(user, nil, has_entries(message: :too_many_spam_flags)).returns(true) SpamRulesEnforcer.stubs(:block?).with {|u| u.id != user.id }.returns(false)
SpamRulesEnforcer.stubs(:block?).with {|u| u.id == user.id }.returns(true)
subject.stubs(:block?).returns(true)
end end
it 'prevents the user from making new posts' do context 'user is not blocked' do
subject.punish_user before do
expect(Guardian.new(user).can_create_post?(nil)).to be_false UserBlocker.expects(:block).with(user, nil, has_entries(message: :too_many_spam_flags)).returns(true)
end
it 'sends private message to moderators' do
SiteSetting.stubs(:notify_mods_when_user_blocked).returns(true)
moderator = Fabricate(:moderator)
GroupMessage.expects(:create).with do |group, msg_type, params|
group == Group[:moderators].name and msg_type == :user_automatically_blocked and params[:user].id == user.id
end end
subject.punish_user
end
it "doesn't send a pm to moderators if notify_mods_when_user_blocked is false" do it 'prevents the user from making new posts' do
SiteSetting.stubs(:notify_mods_when_user_blocked).returns(false) subject.block_user
GroupMessage.expects(:create).never expect(Guardian.new(user).can_create_post?(nil)).to be_false
subject.punish_user end
end
end
context 'user is already blocked' do it 'sends private message to moderators' do
before do SiteSetting.stubs(:notify_mods_when_user_blocked).returns(true)
UserBlocker.expects(:block).with(user, nil, has_entries(message: :too_many_spam_flags)).returns(false) moderator = Fabricate(:moderator)
end GroupMessage.expects(:create).with do |group, msg_type, params|
group == Group[:moderators].name and msg_type == :user_automatically_blocked and params[:user].id == user.id
end
subject.block_user
end
it "doesn't send a pm to moderators if the user is already blocked" do it "doesn't send a pm to moderators if notify_mods_when_user_blocked is false" do
GroupMessage.expects(:create).never SiteSetting.stubs(:notify_mods_when_user_blocked).returns(false)
subject.punish_user GroupMessage.expects(:create).never
end subject.block_user
end
end
describe 'block?' do
context 'never been blocked' do
shared_examples "can't be blocked" do
it "returns false" do
enforcer = SpamRulesEnforcer.new(user)
enforcer.expects(:num_spam_flags_against_user).never
enforcer.expects(:num_users_who_flagged_spam_against_user).never
expect(enforcer.block?).to be_false
end end
end end
[:basic, :regular, :leader, :elder].each do |trust_level| context 'user is already blocked' do
context "user has trust level #{trust_level}" do before do
let(:user) { Fabricate(:user, trust_level: TrustLevel.levels[trust_level]) } UserBlocker.expects(:block).with(user, nil, has_entries(message: :too_many_spam_flags)).returns(false)
end
it "doesn't send a pm to moderators if the user is already blocked" do
GroupMessage.expects(:create).never
subject.block_user
end
end
end
describe 'block?' do
context 'never been blocked' do
shared_examples "can't be blocked" do
it "returns false" do
enforcer = SpamRulesEnforcer.new(user)
enforcer.expects(:num_spam_flags_against_user).never
enforcer.expects(:num_users_who_flagged_spam_against_user).never
expect(enforcer.block?).to be_false
end
end
[:basic, :regular, :leader, :elder].each do |trust_level|
context "user has trust level #{trust_level}" do
let(:user) { Fabricate(:user, trust_level: TrustLevel.levels[trust_level]) }
include_examples "can't be blocked"
end
end
context "user is an admin" do
let(:user) { Fabricate(:admin) }
include_examples "can't be blocked"
end
context "user is a moderator" do
let(:user) { Fabricate(:moderator) }
include_examples "can't be blocked" include_examples "can't be blocked"
end end
end end
context "user is an admin" do context 'new user' do
let(:user) { Fabricate(:admin) } let(:user) { Fabricate(:user, trust_level: TrustLevel.levels[:newuser]) }
include_examples "can't be blocked" subject { SpamRulesEnforcer.new(user) }
it 'returns false if there are no spam flags' do
subject.stubs(:num_spam_flags_against_user).returns(0)
subject.stubs(:num_users_who_flagged_spam_against_user).returns(0)
expect(subject.block?).to be_false
end
it 'returns false if there are not received enough flags' do
subject.stubs(:num_spam_flags_against_user).returns(1)
subject.stubs(:num_users_who_flagged_spam_against_user).returns(2)
expect(subject.block?).to be_false
end
it 'returns false if there have not been enough users' do
subject.stubs(:num_spam_flags_against_user).returns(2)
subject.stubs(:num_users_who_flagged_spam_against_user).returns(1)
expect(subject.block?).to be_false
end
it 'returns false if num_flags_to_block_new_user is 0' do
SiteSetting.stubs(:num_flags_to_block_new_user).returns(0)
subject.stubs(:num_spam_flags_against_user).returns(100)
subject.stubs(:num_users_who_flagged_spam_against_user).returns(100)
expect(subject.block?).to be_false
end
it 'returns false if num_users_to_block_new_user is 0' do
SiteSetting.stubs(:num_users_to_block_new_user).returns(0)
subject.stubs(:num_spam_flags_against_user).returns(100)
subject.stubs(:num_users_who_flagged_spam_against_user).returns(100)
expect(subject.block?).to be_false
end
it 'returns true when there are enough flags from enough users' do
subject.stubs(:num_spam_flags_against_user).returns(2)
subject.stubs(:num_users_who_flagged_spam_against_user).returns(2)
expect(subject.block?).to be_true
end
end end
context "user is a moderator" do context "blocked, but has higher trust level now" do
let(:user) { Fabricate(:moderator) } let(:user) { Fabricate(:user, blocked: true, trust_level: TrustLevel.levels[:basic]) }
include_examples "can't be blocked" subject { SpamRulesEnforcer.new(user) }
end
end
context 'new user' do it 'returns false' do
let(:user) { Fabricate(:user, trust_level: TrustLevel.levels[:newuser]) } expect(subject.block?).to be_true
subject { SpamRulesEnforcer.new(user) } end
it 'returns false if there are no spam flags' do
subject.stubs(:num_spam_flags_against_user).returns(0)
subject.stubs(:num_users_who_flagged_spam_against_user).returns(0)
expect(subject.block?).to be_false
end
it 'returns false if there are not received enough flags' do
subject.stubs(:num_spam_flags_against_user).returns(1)
subject.stubs(:num_users_who_flagged_spam_against_user).returns(2)
expect(subject.block?).to be_false
end
it 'returns false if there have not been enough users' do
subject.stubs(:num_spam_flags_against_user).returns(2)
subject.stubs(:num_users_who_flagged_spam_against_user).returns(1)
expect(subject.block?).to be_false
end
it 'returns false if num_flags_to_block_new_user is 0' do
SiteSetting.stubs(:num_flags_to_block_new_user).returns(0)
subject.stubs(:num_spam_flags_against_user).returns(100)
subject.stubs(:num_users_who_flagged_spam_against_user).returns(100)
expect(subject.block?).to be_false
end
it 'returns false if num_users_to_block_new_user is 0' do
SiteSetting.stubs(:num_users_to_block_new_user).returns(0)
subject.stubs(:num_spam_flags_against_user).returns(100)
subject.stubs(:num_users_who_flagged_spam_against_user).returns(100)
expect(subject.block?).to be_false
end
it 'returns true when there are enough flags from enough users' do
subject.stubs(:num_spam_flags_against_user).returns(2)
subject.stubs(:num_users_who_flagged_spam_against_user).returns(2)
expect(subject.block?).to be_true
end
end
context "blocked, but has higher trust level now" do
let(:user) { Fabricate(:user, blocked: true, trust_level: TrustLevel.levels[:basic]) }
subject { SpamRulesEnforcer.new(user) }
it 'returns false' do
expect(subject.block?).to be_true
end end
end end
end end