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(:notify_mods_when_user_blocked, false)
setting(:flag_sockpuppets, true)
# used mainly for dev, force hostname for Discourse.base_url
# You would usually use multisite for this
setting(:force_hostname, '')

View File

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

View File

@ -4,27 +4,55 @@ class SpamRulesEnforcer
include Rails.application.routes.url_helpers
# The exclamation point means that this method may make big changes to posts and the user.
def self.enforce!(user)
SpamRulesEnforcer.new(user).enforce!
# The exclamation point means that this method may make big changes to posts and users.
def self.enforce!(arg)
SpamRulesEnforcer.new(arg).enforce!
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)
SpamRulesEnforcer.new(user).block?
end
def self.punish!(user)
SpamRulesEnforcer.new(user).punish_user
end
def initialize(user)
@user = user
end
def enforce!
punish_user if block?
true
SpamRulesEnforcer.new(user).block_user
end
def block?
@ -46,7 +74,7 @@ class SpamRulesEnforcer
PostAction.spam_flags.where(post_id: post_ids).uniq.pluck(:user_id).size
end
def punish_user
def block_user
Post.transaction do
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})

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_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."
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"
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,
limit_once_per: 24.hours,
message_params: {domains: @post.linked_hosts.keys.join(', ')} } )
else
SpamRulesEnforcer.enforce!(@post)
end
enqueue_jobs

View File

@ -8,6 +8,7 @@ Fabricator(:user) do
password 'myawesomepassword'
trust_level TrustLevel.levels[:basic]
bio_raw "I'm batman!"
ip_address { sequence(:ip_address) { |i| "99.232.23.#{i%254}"} }
end
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'
describe PostAction do
describe SpamRulesEnforcer do
before do
SiteSetting.stubs(:flags_required_to_hide_post).returns(0) # never
SiteSetting.stubs(:num_flags_to_block_new_user).returns(2)
SiteSetting.stubs(:num_users_to_block_new_user).returns(2)
end
describe 'auto-blocking users based on flagging' do
before do
SiteSetting.stubs(:flags_required_to_hide_post).returns(0) # never
SiteSetting.stubs(:num_flags_to_block_new_user).returns(2)
SiteSetting.stubs(:num_users_to_block_new_user).returns(2)
end
Given!(:admin) { Fabricate(:admin) } # needed to send a system message
Given!(:moderator) { Fabricate(:moderator) }
Given(:user1) { Fabricate(:user) }
Given(:user2) { Fabricate(:user) }
Given!(:admin) { Fabricate(:admin) } # needed to send a system message
Given!(:moderator) { Fabricate(:moderator) }
Given(:user1) { Fabricate(:user) }
Given(:user2) { Fabricate(:user) }
context 'spammer is a new user' do
Given(:spammer) { Fabricate(:user, trust_level: TrustLevel.levels[:newuser]) }
context 'spammer is a new user' do
Given(:spammer) { Fabricate(:user, trust_level: TrustLevel.levels[:newuser]) }
context 'spammer post is not flagged enough times' do
Given!(:spam_post) { create_post(user: spammer) }
Given!(:spam_post2) { create_post(user: spammer) }
When { PostAction.act(user1, spam_post, PostActionType.types[:spam]) }
Then { expect(spam_post.reload).to_not be_hidden }
context 'spammer post is not flagged enough times' do
Given!(:spam_post) { create_post(user: spammer) }
Given!(:spam_post2) { create_post(user: spammer) }
When { PostAction.act(user1, spam_post, PostActionType.types[:spam]) }
Then { expect(spam_post.reload).to_not be_hidden }
context 'spam posts are flagged enough times, but not by enough users' do
When { PostAction.act(user1, spam_post2, PostActionType.types[:spam]) }
Then { expect(spam_post.reload).to_not be_hidden }
And { expect(spam_post2.reload).to_not be_hidden }
And { expect(spammer.reload).to_not be_blocked }
context 'spam posts are flagged enough times, but not by enough users' do
When { PostAction.act(user1, spam_post2, PostActionType.types[:spam]) }
Then { expect(spam_post.reload).to_not be_hidden }
And { expect(spam_post2.reload).to_not be_hidden }
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
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
Given!(:another_topic) { Fabricate(:topic) }
Given!(:spam_post) { Fabricate(:post, user: spammer) }
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]) }
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 }
Then { expect(spam_post.reload).to_not be_hidden }
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
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
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 }
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 }
[[: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

View File

@ -6,228 +6,306 @@ describe SpamRulesEnforcer do
SystemMessage.stubs(:create)
end
before do
SiteSetting.stubs(:flags_required_to_hide_post).returns(0) # never
SiteSetting.stubs(:num_flags_to_block_new_user).returns(2)
SiteSetting.stubs(:num_users_to_block_new_user).returns(2)
end
context 'flagging posts based on IP address of users' do
describe 'reply_is_from_sockpuppet?' do
let(:user1) { Fabricate(:user, ip_address: '182.189.119.174') }
let(:post1) { Fabricate(:post, user: user1, topic: Fabricate(:topic, user: user1)) }
describe 'enforce!' do
let(:post) { Fabricate.build(:post, user: Fabricate.build(:user, trust_level: TrustLevel.levels[:newuser])) }
subject { SpamRulesEnforcer.new(post.user) }
it 'is false for the first post in a topic' do
SpamRulesEnforcer.new(post1).reply_is_from_sockpuppet?.should eq(false)
end
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(:punish_user).never
enforcer.enforce!
end
it 'is false if users have different IP addresses' do
post2 = Fabricate(:post, user: Fabricate(:user, ip_address: '182.189.199.199'), topic: post1.topic)
SpamRulesEnforcer.new(post2).reply_is_from_sockpuppet?.should eq(false)
end
it 'takes no action if not enough flags by enough users have been submitted' do
subject.stubs(:block?).returns(false)
subject.expects(:punish_user).never
subject.enforce!
end
it 'is true if users have the same IP address' do
post2 = Fabricate(:post, user: Fabricate(:user, ip_address: '182.189.119.174'), topic: post1.topic)
SpamRulesEnforcer.new(post2).reply_is_from_sockpuppet?.should eq(true)
end
it 'delivers punishment when there are enough flags from enough users' do
subject.stubs(:block?).returns(true)
subject.expects(:punish_user)
subject.enforce!
it 'is false if reply and first post are from the same user' do
post2 = Fabricate(:post, user: user1, topic: post1.topic)
SpamRulesEnforcer.new(post2).reply_is_from_sockpuppet?.should eq(false)
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
describe 'num_spam_flags_against_user' 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) }
context 'auto-blocking users based on flags' do
before do
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)
SiteSetting.stubs(:flags_required_to_hide_post).returns(0) # never
SiteSetting.stubs(:num_flags_to_block_new_user).returns(2)
SiteSetting.stubs(:num_users_to_block_new_user).returns(2)
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
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
it 'prevents the user from making new posts' do
subject.punish_user
expect(Guardian.new(user).can_create_post?(nil)).to be_false
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
context 'user is not blocked' do
before do
UserBlocker.expects(:block).with(user, nil, has_entries(message: :too_many_spam_flags)).returns(true)
end
subject.punish_user
end
it "doesn't send a pm to moderators if notify_mods_when_user_blocked is false" do
SiteSetting.stubs(:notify_mods_when_user_blocked).returns(false)
GroupMessage.expects(:create).never
subject.punish_user
end
end
it 'prevents the user from making new posts' do
subject.block_user
expect(Guardian.new(user).can_create_post?(nil)).to be_false
end
context 'user is already blocked' do
before do
UserBlocker.expects(:block).with(user, nil, has_entries(message: :too_many_spam_flags)).returns(false)
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
subject.block_user
end
it "doesn't send a pm to moderators if the user is already blocked" do
GroupMessage.expects(:create).never
subject.punish_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
it "doesn't send a pm to moderators if notify_mods_when_user_blocked is false" do
SiteSetting.stubs(:notify_mods_when_user_blocked).returns(false)
GroupMessage.expects(:create).never
subject.block_user
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]) }
context 'user is already blocked' do
before do
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"
end
end
context "user is an admin" do
let(:user) { Fabricate(:admin) }
include_examples "can't be blocked"
context 'new user' do
let(:user) { Fabricate(:user, trust_level: TrustLevel.levels[:newuser]) }
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
context "user is a moderator" do
let(:user) { Fabricate(:moderator) }
include_examples "can't be blocked"
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) }
context 'new user' do
let(:user) { Fabricate(:user, trust_level: TrustLevel.levels[:newuser]) }
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
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
it 'returns false' do
expect(subject.block?).to be_true
end
end
end
end