2019-05-03 06:17:27 +08:00
|
|
|
# frozen_string_literal: true
|
|
|
|
|
2013-08-14 23:05:53 +08:00
|
|
|
# A ScreenedEmail record represents an email address that is being watched,
|
|
|
|
# typically when creating a new User account. If the email of the signup form
|
|
|
|
# (or some other form) matches a ScreenedEmail record, an action can be
|
|
|
|
# performed based on the action_type.
|
|
|
|
class ScreenedEmail < ActiveRecord::Base
|
|
|
|
|
|
|
|
include ScreeningModel
|
|
|
|
|
|
|
|
default_action :block
|
|
|
|
|
|
|
|
validates :email, presence: true, uniqueness: true
|
|
|
|
|
2014-07-14 22:16:24 +08:00
|
|
|
before_save :downcase_email
|
|
|
|
|
|
|
|
def downcase_email
|
|
|
|
self.email = email.downcase
|
|
|
|
end
|
|
|
|
|
2020-04-24 12:09:51 +08:00
|
|
|
def self.canonical(email)
|
|
|
|
name, domain = email.split('@', 2)
|
|
|
|
name = name.gsub(/\+.*/, '')
|
|
|
|
if ['gmail.com', 'googlemail.com'].include?(domain.downcase)
|
|
|
|
name = name.gsub('.', '')
|
|
|
|
end
|
|
|
|
"#{name}@#{domain}".downcase
|
|
|
|
end
|
|
|
|
|
2013-08-14 23:05:53 +08:00
|
|
|
def self.block(email, opts = {})
|
2020-04-24 12:09:51 +08:00
|
|
|
email = canonical(email)
|
|
|
|
find_by_email(email) || create!(opts.slice(:action_type, :ip_address).merge(email: email))
|
2013-08-14 23:05:53 +08:00
|
|
|
end
|
|
|
|
|
|
|
|
def self.should_block?(email)
|
2020-04-24 12:09:51 +08:00
|
|
|
|
|
|
|
email = canonical(email)
|
|
|
|
|
2014-07-12 07:59:43 +08:00
|
|
|
screened_emails = ScreenedEmail.order(created_at: :desc).limit(100)
|
|
|
|
|
|
|
|
distances = {}
|
2014-07-14 22:16:24 +08:00
|
|
|
screened_emails.each { |se| distances[se.email] = levenshtein(se.email.downcase, email.downcase) }
|
2014-07-12 07:59:43 +08:00
|
|
|
|
|
|
|
max_distance = SiteSetting.levenshtein_distance_spammer_emails
|
|
|
|
screened_email = screened_emails.select { |se| distances[se.email] <= max_distance }
|
|
|
|
.sort { |se| distances[se.email] }
|
|
|
|
.first
|
|
|
|
|
2013-08-14 23:05:53 +08:00
|
|
|
screened_email.record_match! if screened_email
|
2014-07-12 07:59:43 +08:00
|
|
|
|
2014-09-25 23:44:48 +08:00
|
|
|
screened_email.try(:action_type) == actions[:block]
|
2013-08-14 23:05:53 +08:00
|
|
|
end
|
|
|
|
|
2014-07-12 07:59:43 +08:00
|
|
|
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
|
2015-03-10 01:55:17 +08:00
|
|
|
matrix.last.last
|
2014-07-12 07:59:43 +08:00
|
|
|
end
|
|
|
|
|
2013-08-14 23:05:53 +08:00
|
|
|
end
|
2013-08-28 08:42:58 +08:00
|
|
|
|
|
|
|
# == Schema Information
|
|
|
|
#
|
|
|
|
# Table name: screened_emails
|
|
|
|
#
|
|
|
|
# id :integer not null, primary key
|
2019-01-12 03:29:56 +08:00
|
|
|
# email :string not null
|
2013-08-28 08:42:58 +08:00
|
|
|
# action_type :integer not null
|
|
|
|
# match_count :integer default(0), not null
|
|
|
|
# last_match_at :datetime
|
2014-08-27 13:19:25 +08:00
|
|
|
# created_at :datetime not null
|
|
|
|
# updated_at :datetime not null
|
2013-12-05 14:40:35 +08:00
|
|
|
# ip_address :inet
|
2013-08-28 08:42:58 +08:00
|
|
|
#
|
|
|
|
# Indexes
|
|
|
|
#
|
2019-01-12 03:29:56 +08:00
|
|
|
# index_screened_emails_on_email (email) UNIQUE
|
|
|
|
# index_screened_emails_on_last_match_at (last_match_at)
|
2013-08-28 08:42:58 +08:00
|
|
|
#
|