FEATURE: enforce_canonical_emails site setting

The new `enforce_canonical_emails` site setting ensures that emails in the
canonical form are unique.

This mean that if `s.a.m+1@gmail.com` is registered `sam@gmail.com` will
not be allowed.

The commit contains a blanket "tag strip" (stripping everything after +)
it also contains special handling of a "dot strip" for googlemail and gmail.

The setting only impacts new registrations after `enforce_canonical_emails`

The setting is default false so it will not impact any existing installs.
This commit is contained in:
Sam Saffron
2020-04-14 14:16:30 +10:00
parent e2284cf739
commit 6f9177e2ed
5 changed files with 70 additions and 8 deletions

View File

@@ -15,10 +15,27 @@ class UserEmail < ActiveRecord::Base
validate :user_id_not_changed, if: :primary
validate :unique_email
before_save :save_canonical
scope :secondary, -> { where(primary: false) }
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
private
def save_canonical
if SiteSetting.enforce_canonical_emails && self.will_save_change_to_email?
self.canonical_email = UserEmail.canonical(self.email)
end
end
def strip_downcase_email
if self.email
self.email = self.email.strip
@@ -32,8 +49,14 @@ class UserEmail < ActiveRecord::Base
end
def unique_email
if self.will_save_change_to_email? && self.class.where("lower(email) = ?", email).exists?
self.errors.add(:email, :taken)
if self.will_save_change_to_email?
exists = self.class.where("lower(email) = ?", email).exists?
exists ||= SiteSetting.enforce_canonical_emails &&
self.class.where("canonical_email = ?", UserEmail.canonical(email)).exists?
if exists
self.errors.add(:email, :taken)
end
end
end
@@ -50,15 +73,17 @@ end
#
# Table name: user_emails
#
# id :integer not null, primary key
# user_id :integer not null
# email :string(513) not null
# primary :boolean default(FALSE), not null
# created_at :datetime not null
# updated_at :datetime not null
# id :integer not null, primary key
# user_id :integer not null
# email :string(513) not null
# primary :boolean default(FALSE), not null
# created_at :datetime not null
# updated_at :datetime not null
# canonical_email :string
#
# Indexes
#
# index_user_emails_on_canonical_email (canonical_email) WHERE (canonical_email IS NOT NULL)
# index_user_emails_on_email (lower((email)::text)) UNIQUE
# index_user_emails_on_user_id (user_id)
# index_user_emails_on_user_id_and_primary (user_id,primary) UNIQUE WHERE "primary"