about summary refs log tree commit diff
path: root/app
diff options
context:
space:
mode:
authorEugen Rochko <eugen@zeonfederated.com>2021-04-17 03:14:25 +0200
committerGitHub <noreply@github.com>2021-04-17 03:14:25 +0200
commitb3ceb3dcc4df62803aa967d7aecee686973a8996 (patch)
treedc34486813237852b90cc81b26c4e361323c7757 /app
parent170e05db127c9f357183239a5543bdfc9525680d (diff)
Add canonical e-mail blocks for suspended accounts (#16049)
Prevent new accounts from being created using the same underlying
e-mail as a suspended account using extensions and period
permutations. Stores e-mails as a SHA256 hash
Diffstat (limited to 'app')
-rw-r--r--app/helpers/email_helper.rb18
-rw-r--r--app/models/account.rb14
-rw-r--r--app/models/canonical_email_block.rb27
-rw-r--r--app/validators/blacklisted_email_validator.rb30
4 files changed, 78 insertions, 11 deletions
diff --git a/app/helpers/email_helper.rb b/app/helpers/email_helper.rb
new file mode 100644
index 000000000..360783c62
--- /dev/null
+++ b/app/helpers/email_helper.rb
@@ -0,0 +1,18 @@
+# frozen_string_literal: true
+
+module EmailHelper
+  def self.included(base)
+    base.extend(self)
+  end
+
+  def email_to_canonical_email(str)
+    username, domain = str.downcase.split('@', 2)
+    username, = username.gsub('.', '').split('+', 2)
+
+    "#{username}@#{domain}"
+  end
+
+  def email_to_canonical_email_hash(str)
+    Digest::SHA2.new(256).hexdigest(email_to_canonical_email(str))
+  end
+end
diff --git a/app/models/account.rb b/app/models/account.rb
index 80689d4aa..a573365de 100644
--- a/app/models/account.rb
+++ b/app/models/account.rb
@@ -235,6 +235,7 @@ class Account < ApplicationRecord
     transaction do
       create_deletion_request!
       update!(suspended_at: date, suspension_origin: origin)
+      create_canonical_email_block!
     end
   end
 
@@ -242,6 +243,7 @@ class Account < ApplicationRecord
     transaction do
       deletion_request&.destroy!
       update!(suspended_at: nil, suspension_origin: nil)
+      destroy_canonical_email_block!
     end
   end
 
@@ -569,4 +571,16 @@ class Account < ApplicationRecord
   def clean_feed_manager
     FeedManager.instance.clean_feeds!(:home, [id])
   end
+
+  def create_canonical_email_block!
+    return unless local? && user_email.present?
+
+    CanonicalEmailBlock.create(reference_account: self, email: user_email)
+  end
+
+  def destroy_canonical_email_block!
+    return unless local?
+
+    CanonicalEmailBlock.where(reference_account: self).delete_all
+  end
 end
diff --git a/app/models/canonical_email_block.rb b/app/models/canonical_email_block.rb
new file mode 100644
index 000000000..a8546d65a
--- /dev/null
+++ b/app/models/canonical_email_block.rb
@@ -0,0 +1,27 @@
+# frozen_string_literal: true
+# == Schema Information
+#
+# Table name: canonical_email_blocks
+#
+#  id                   :bigint(8)        not null, primary key
+#  canonical_email_hash :string           default(""), not null
+#  reference_account_id :bigint(8)        not null
+#  created_at           :datetime         not null
+#  updated_at           :datetime         not null
+#
+
+class CanonicalEmailBlock < ApplicationRecord
+  include EmailHelper
+
+  belongs_to :reference_account, class_name: 'Account'
+
+  validates :canonical_email_hash, presence: true
+
+  def email=(email)
+    self.canonical_email_hash = email_to_canonical_email_hash(email)
+  end
+
+  def self.block?(email)
+    where(canonical_email_hash: email_to_canonical_email_hash(email)).exists?
+  end
+end
diff --git a/app/validators/blacklisted_email_validator.rb b/app/validators/blacklisted_email_validator.rb
index 1ca73fdcc..eb66ad93d 100644
--- a/app/validators/blacklisted_email_validator.rb
+++ b/app/validators/blacklisted_email_validator.rb
@@ -6,26 +6,25 @@ class BlacklistedEmailValidator < ActiveModel::Validator
 
     @email = user.email
 
-    user.errors.add(:email, :blocked) if blocked_email?
+    user.errors.add(:email, :blocked) if blocked_email_provider?
+    user.errors.add(:email, :taken) if blocked_canonical_email?
   end
 
   private
 
-  def blocked_email?
-    on_blacklist? || not_on_whitelist?
+  def blocked_email_provider?
+    disallowed_through_email_domain_block? || disallowed_through_configuration? || not_allowed_through_configuration?
   end
 
-  def on_blacklist?
-    return true  if EmailDomainBlock.block?(@email)
-    return false if Rails.configuration.x.email_domains_blacklist.blank?
-
-    domains = Rails.configuration.x.email_domains_blacklist.gsub('.', '\.')
-    regexp  = Regexp.new("@(.+\\.)?(#{domains})", true)
+  def blocked_canonical_email?
+    CanonicalEmailBlock.block?(@email)
+  end
 
-    regexp.match?(@email)
+  def disallowed_through_email_domain_block?
+    EmailDomainBlock.block?(@email)
   end
 
-  def not_on_whitelist?
+  def not_allowed_through_configuration?
     return false if Rails.configuration.x.email_domains_whitelist.blank?
 
     domains = Rails.configuration.x.email_domains_whitelist.gsub('.', '\.')
@@ -33,4 +32,13 @@ class BlacklistedEmailValidator < ActiveModel::Validator
 
     @email !~ regexp
   end
+
+  def disallowed_through_configuration?
+    return false if Rails.configuration.x.email_domains_blacklist.blank?
+
+    domains = Rails.configuration.x.email_domains_blacklist.gsub('.', '\.')
+    regexp  = Regexp.new("@(.+\\.)?(#{domains})", true)
+
+    regexp.match?(@email)
+  end
 end