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:
parent
c1ba41195e
commit
3c2c6ab24b
|
@ -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, '')
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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})
|
||||
|
|
|
@ -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)"
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue