Merge branch 'master' of github.com:discourse/discourse
This commit is contained in:
commit
038b2ceca7
|
@ -17,11 +17,43 @@ class ScreenedEmail < ActiveRecord::Base
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.should_block?(email)
|
def self.should_block?(email)
|
||||||
screened_email = ScreenedEmail.find_by(email: email)
|
screened_emails = ScreenedEmail.order(created_at: :desc).limit(100)
|
||||||
|
|
||||||
|
distances = {}
|
||||||
|
screened_emails.each { |se| distances[se.email] = levenshtein(se.email, email) }
|
||||||
|
|
||||||
|
max_distance = SiteSetting.levenshtein_distance_spammer_emails
|
||||||
|
screened_email = screened_emails.select { |se| distances[se.email] <= max_distance }
|
||||||
|
.sort { |se| distances[se.email] }
|
||||||
|
.first
|
||||||
|
|
||||||
screened_email.record_match! if screened_email
|
screened_email.record_match! if screened_email
|
||||||
|
|
||||||
screened_email && screened_email.action_type == actions[:block]
|
screened_email && screened_email.action_type == actions[:block]
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def self.levenshtein(first, second)
|
||||||
|
matrix = [(0..first.length).to_a]
|
||||||
|
(1..second.length).each do |j|
|
||||||
|
matrix << [j] + [0] * (first.length)
|
||||||
|
end
|
||||||
|
|
||||||
|
(1..second.length).each do |i|
|
||||||
|
(1..first.length).each do |j|
|
||||||
|
if first[j-1] == second[i-1]
|
||||||
|
matrix[i][j] = matrix[i-1][j-1]
|
||||||
|
else
|
||||||
|
matrix[i][j] = [
|
||||||
|
matrix[i-1][j],
|
||||||
|
matrix[i][j-1],
|
||||||
|
matrix[i-1][j-1],
|
||||||
|
].min + 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return matrix.last.last
|
||||||
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
# == Schema Information
|
# == Schema Information
|
||||||
|
|
|
@ -890,6 +890,8 @@ en:
|
||||||
white_listed_spam_host_domains: "A pipe-delimited list of domains excluded from spam host testing, new users will be able to create an unrestricted count of posts with links to this domain"
|
white_listed_spam_host_domains: "A pipe-delimited list of domains excluded from spam host testing, new users will be able to create an unrestricted count of posts with links to this domain"
|
||||||
staff_like_weight: "Extra weighting factor given to likes when performed by staff."
|
staff_like_weight: "Extra weighting factor given to likes when performed by staff."
|
||||||
|
|
||||||
|
levenshtein_distance_spammer_emails: "Number of characters different from a known spammer email."
|
||||||
|
|
||||||
reply_by_email_enabled: "Enable replying to topics via email"
|
reply_by_email_enabled: "Enable replying to topics via email"
|
||||||
reply_by_email_address: "Template for reply by email incoming email address, for example: %{reply_key}@reply.example.com or replies+%{reply_key}@example.com"
|
reply_by_email_address: "Template for reply by email incoming email address, for example: %{reply_key}@reply.example.com or replies+%{reply_key}@example.com"
|
||||||
|
|
||||||
|
|
|
@ -527,6 +527,10 @@ spam:
|
||||||
white_listed_spam_host_domains:
|
white_listed_spam_host_domains:
|
||||||
default: ''
|
default: ''
|
||||||
type: list
|
type: list
|
||||||
|
levenshtein_distance_spammer_emails:
|
||||||
|
default: 2
|
||||||
|
min: 0
|
||||||
|
max: 3
|
||||||
|
|
||||||
rate_limits:
|
rate_limits:
|
||||||
unique_posts_mins:
|
unique_posts_mins:
|
||||||
|
|
|
@ -3,33 +3,34 @@ require 'spec_helper'
|
||||||
describe ScreenedEmail do
|
describe ScreenedEmail do
|
||||||
|
|
||||||
let(:email) { 'block@spamfromhome.org' }
|
let(:email) { 'block@spamfromhome.org' }
|
||||||
|
let(:similar_email) { 'bl0ck@spamfromhome.org' }
|
||||||
|
|
||||||
describe "new record" do
|
describe "new record" do
|
||||||
it "sets a default action_type" do
|
it "sets a default action_type" do
|
||||||
described_class.create(email: email).action_type.should == described_class.actions[:block]
|
ScreenedEmail.create(email: email).action_type.should == ScreenedEmail.actions[:block]
|
||||||
end
|
end
|
||||||
|
|
||||||
it "last_match_at is null" do
|
it "last_match_at is null" do
|
||||||
# If we manually load the table with some emails, we can see whether those emails
|
# If we manually load the table with some emails, we can see whether those emails
|
||||||
# have ever been blocked by looking at last_match_at.
|
# have ever been blocked by looking at last_match_at.
|
||||||
described_class.create(email: email).last_match_at.should be_nil
|
ScreenedEmail.create(email: email).last_match_at.should be_nil
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe '#block' do
|
describe '#block' do
|
||||||
context 'email is not being blocked' do
|
context 'email is not being blocked' do
|
||||||
it 'creates a new record with default action of :block' do
|
it 'creates a new record with default action of :block' do
|
||||||
record = described_class.block(email)
|
record = ScreenedEmail.block(email)
|
||||||
record.should_not be_new_record
|
record.should_not be_new_record
|
||||||
record.email.should == email
|
record.email.should == email
|
||||||
record.action_type.should == described_class.actions[:block]
|
record.action_type.should == ScreenedEmail.actions[:block]
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'lets action_type be overriden' do
|
it 'lets action_type be overriden' do
|
||||||
record = described_class.block(email, action_type: described_class.actions[:do_nothing])
|
record = ScreenedEmail.block(email, action_type: ScreenedEmail.actions[:do_nothing])
|
||||||
record.should_not be_new_record
|
record.should_not be_new_record
|
||||||
record.email.should == email
|
record.email.should == email
|
||||||
record.action_type.should == described_class.actions[:do_nothing]
|
record.action_type.should == ScreenedEmail.actions[:do_nothing]
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -37,22 +38,34 @@ describe ScreenedEmail do
|
||||||
let!(:existing) { Fabricate(:screened_email, email: email) }
|
let!(:existing) { Fabricate(:screened_email, email: email) }
|
||||||
|
|
||||||
it "doesn't create a new record" do
|
it "doesn't create a new record" do
|
||||||
expect { described_class.block(email) }.to_not change { described_class.count }
|
expect { ScreenedEmail.block(email) }.to_not change { ScreenedEmail.count }
|
||||||
end
|
end
|
||||||
|
|
||||||
it "returns the existing record" do
|
it "returns the existing record" do
|
||||||
described_class.block(email).should == existing
|
ScreenedEmail.block(email).should == existing
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe '#should_block?' do
|
describe '#should_block?' do
|
||||||
subject { described_class.should_block?(email) }
|
subject { ScreenedEmail.should_block?(email) }
|
||||||
|
|
||||||
it "returns false if a record with the email doesn't exist" do
|
it "returns false if a record with the email doesn't exist" do
|
||||||
subject.should be_false
|
subject.should be_false
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it "returns true when there is a record with the email" do
|
||||||
|
ScreenedEmail.should_block?(email).should be_false
|
||||||
|
ScreenedEmail.create(email: email).save
|
||||||
|
ScreenedEmail.should_block?(email).should be_true
|
||||||
|
end
|
||||||
|
|
||||||
|
it "returns true when there is a record with a similar email" do
|
||||||
|
ScreenedEmail.should_block?(email).should be_false
|
||||||
|
ScreenedEmail.create(email: similar_email).save
|
||||||
|
ScreenedEmail.should_block?(email).should be_true
|
||||||
|
end
|
||||||
|
|
||||||
shared_examples "when a ScreenedEmail record matches" do
|
shared_examples "when a ScreenedEmail record matches" do
|
||||||
it "updates statistics" do
|
it "updates statistics" do
|
||||||
Timecop.freeze(Time.zone.now) do
|
Timecop.freeze(Time.zone.now) do
|
||||||
|
@ -63,13 +76,13 @@ describe ScreenedEmail do
|
||||||
end
|
end
|
||||||
|
|
||||||
context "action_type is :block" do
|
context "action_type is :block" do
|
||||||
let!(:screened_email) { Fabricate(:screened_email, email: email, action_type: described_class.actions[:block]) }
|
let!(:screened_email) { Fabricate(:screened_email, email: email, action_type: ScreenedEmail.actions[:block]) }
|
||||||
it { should be_true }
|
it { should be_true }
|
||||||
include_examples "when a ScreenedEmail record matches"
|
include_examples "when a ScreenedEmail record matches"
|
||||||
end
|
end
|
||||||
|
|
||||||
context "action_type is :do_nothing" do
|
context "action_type is :do_nothing" do
|
||||||
let!(:screened_email) { Fabricate(:screened_email, email: email, action_type: described_class.actions[:do_nothing]) }
|
let!(:screened_email) { Fabricate(:screened_email, email: email, action_type: ScreenedEmail.actions[:do_nothing]) }
|
||||||
it { should be_false }
|
it { should be_false }
|
||||||
include_examples "when a ScreenedEmail record matches"
|
include_examples "when a ScreenedEmail record matches"
|
||||||
end
|
end
|
||||||
|
|
Loading…
Reference in New Issue