Compare commits

...

3 Commits

Author SHA1 Message Date
Matt Jankowski
b1009dd74f
Merge a8dfd353b2 into e9170e2de1 2025-07-09 17:06:32 +00:00
Matt Jankowski
a8dfd353b2 Use Regexp.escape on domain values 2025-07-03 18:29:27 -04:00
Matt Jankowski
a80763abbe Move allow/deny list config to email_domains area 2025-07-03 18:29:27 -04:00
9 changed files with 69 additions and 29 deletions

View File

@ -34,9 +34,13 @@ class EmailMxValidator < ActiveModel::Validator
end end
def on_allowlist?(domain) def on_allowlist?(domain)
return false if Rails.configuration.x.email_domains_allowlist.blank? return false if allowed_email_domains.blank?
Rails.configuration.x.email_domains_allowlist.include?(domain) allowed_email_domains.include?(domain)
end
def allowed_email_domains
Rails.configuration.x.email_domains.allowlist
end end
def resolve_mx(domain) def resolve_mx(domain)

View File

@ -1,6 +1,8 @@
# frozen_string_literal: true # frozen_string_literal: true
class UserEmailValidator < ActiveModel::Validator class UserEmailValidator < ActiveModel::Validator
SEPARATOR = '|'
def validate(user) def validate(user)
return if user.valid_invitation? || user.email.blank? return if user.valid_invitation? || user.email.blank?
@ -23,20 +25,42 @@ class UserEmailValidator < ActiveModel::Validator
end end
def not_allowed_through_configuration?(email) def not_allowed_through_configuration?(email)
return false if Rails.configuration.x.email_domains_allowlist.blank? return false if allowed_email_domains.blank?
domains = Rails.configuration.x.email_domains_allowlist.gsub('.', '\.') domains = escaped_domains(allowed_email_domains)
regexp = Regexp.new("@(.+\\.)?(#{domains})$", true)
email !~ regexp email !~ allowed_domain_pattern(domains)
end end
def disallowed_through_configuration?(email) def disallowed_through_configuration?(email)
return false if Rails.configuration.x.email_domains_denylist.blank? return false if denied_email_domains.blank?
domains = Rails.configuration.x.email_domains_denylist.gsub('.', '\.') domains = escaped_domains(denied_email_domains)
regexp = Regexp.new("@(.+\\.)?(#{domains})", true)
regexp.match?(email) denied_domain_pattern(domains).match?(email)
end
def allowed_domain_pattern(domains)
Regexp.new("@(.+\\.)?(#{domains})$", true)
end
def denied_domain_pattern(domains)
Regexp.new("@(.+\\.)?(#{domains})", true)
end
def escaped_domains(domains)
domains
.split(SEPARATOR)
.map { |domain| Regexp.escape(domain) }
.join(SEPARATOR)
.to_s
end
def allowed_email_domains
Rails.configuration.x.email_domains.allowlist
end
def denied_email_domains
Rails.configuration.x.email_domains.denylist
end end
end end

View File

@ -11,7 +11,7 @@ class Scheduler::AutoCloseRegistrationsScheduler
OPEN_REGISTRATIONS_MODERATOR_THRESHOLD = 1.week + UserTrackingConcern::SIGN_IN_UPDATE_FREQUENCY OPEN_REGISTRATIONS_MODERATOR_THRESHOLD = 1.week + UserTrackingConcern::SIGN_IN_UPDATE_FREQUENCY
def perform def perform
return if Rails.configuration.x.email_domains_allowlist.present? || ENV['DISABLE_AUTOMATIC_SWITCHING_TO_APPROVED_REGISTRATIONS'] == 'true' return if Rails.configuration.x.email_domains.allowlist.present? || ENV['DISABLE_AUTOMATIC_SWITCHING_TO_APPROVED_REGISTRATIONS'] == 'true'
return unless Setting.registrations_mode == 'open' return unless Setting.registrations_mode == 'open'
switch_to_approval_mode! unless active_moderators? switch_to_approval_mode! unless active_moderators?

View File

@ -106,6 +106,7 @@ module Mastodon
config.x.cache_buster = config_for(:cache_buster) config.x.cache_buster = config_for(:cache_buster)
config.x.captcha = config_for(:captcha) config.x.captcha = config_for(:captcha)
config.x.email = config_for(:email) config.x.email = config_for(:email)
config.x.email_domains = config_for(:email_domains)
config.x.mastodon = config_for(:mastodon) config.x.mastodon = config_for(:mastodon)
config.x.omniauth = config_for(:omniauth) config.x.omniauth = config_for(:omniauth)
config.x.translation = config_for(:translation) config.x.translation = config_for(:translation)

3
config/email_domains.yml Normal file
View File

@ -0,0 +1,3 @@
shared:
denylist: <%= ENV.fetch('EMAIL_DOMAIN_DENYLIST', nil) || ENV.fetch('EMAIL_DOMAIN_BLACKLIST', '') %>
allowlist: <%= ENV.fetch('EMAIL_DOMAIN_ALLOWLIST', nil) || ENV.fetch('EMAIL_DOMAIN_WHITELIST', '') %>

View File

@ -29,3 +29,17 @@ if ENV.key?('WHITELIST_MODE')
LIMITED_FEDERATION_MODE. Please update your configuration. LIMITED_FEDERATION_MODE. Please update your configuration.
MESSAGE MESSAGE
end end
if ENV.key?('EMAIL_DOMAIN_BLACKLIST')
warn(<<~MESSAGE.squish)
WARNING: The environment variable EMAIL_DOMAIN_BLACKLIST has been replaced
with EMAIL_DOMAIN_DENYLIST. Please update your configuration.
MESSAGE
end
if ENV.key?('EMAIL_DOMAIN_WHITELIST')
warn(<<~MESSAGE.squish)
WARNING: The environment variable EMAIL_DOMAIN_WHITELIST has been replaced
with EMAIL_DOMAIN_ALLOWLIST. Please update your configuration.
MESSAGE
end

View File

@ -1,6 +0,0 @@
# frozen_string_literal: true
Rails.application.configure do
config.x.email_domains_denylist = ENV.fetch('EMAIL_DOMAIN_DENYLIST', nil) || ENV.fetch('EMAIL_DOMAIN_BLACKLIST', '')
config.x.email_domains_allowlist = ENV.fetch('EMAIL_DOMAIN_ALLOWLIST', nil) || ENV.fetch('EMAIL_DOMAIN_WHITELIST', '')
end

View File

@ -138,13 +138,13 @@ RSpec.describe User do
describe 'email domains denylist integration' do describe 'email domains denylist integration' do
around do |example| around do |example|
original = Rails.configuration.x.email_domains_denylist original = Rails.configuration.x.email_domains.denylist
Rails.configuration.x.email_domains_denylist = 'mvrht.com' Rails.configuration.x.email_domains.denylist = 'mvrht.com'
example.run example.run
Rails.configuration.x.email_domains_denylist = original Rails.configuration.x.email_domains.denylist = original
end end
it 'allows a user with an email domain that is not on the denylist to be created' do it 'allows a user with an email domain that is not on the denylist to be created' do
@ -391,13 +391,13 @@ RSpec.describe User do
describe 'allowlist integration' do describe 'allowlist integration' do
around do |example| around do |example|
original = Rails.configuration.x.email_domains_allowlist original = Rails.configuration.x.email_domains.allowlist
Rails.configuration.x.email_domains_allowlist = 'mastodon.space' Rails.configuration.x.email_domains.allowlist = 'mastodon.space'
example.run example.run
Rails.configuration.x.email_domains_allowlist = original Rails.configuration.x.email_domains.allowlist = original
end end
it 'does not allow a user to be created when their email is not on the allowlist' do it 'does not allow a user to be created when their email is not on the allowlist' do
@ -417,13 +417,13 @@ RSpec.describe User do
context 'with a subdomain on the denylist' do context 'with a subdomain on the denylist' do
around do |example| around do |example|
original = Rails.configuration.x.email_domains_denylist original = Rails.configuration.x.email_domains.denylist
example.run example.run
Rails.configuration.x.email_domains_denylist = original Rails.configuration.x.email_domains.denylist = original
end end
it 'does not allow a user to be created with an email subdomain on the denylist even if the top domain is on the allowlist' do it 'does not allow a user to be created with an email subdomain on the denylist even if the top domain is on the allowlist' do
Rails.configuration.x.email_domains_denylist = 'denylisted.mastodon.space' Rails.configuration.x.email_domains.denylist = 'denylisted.mastodon.space'
user = described_class.new(email: 'foo@denylisted.mastodon.space', account: account, password: password) user = described_class.new(email: 'foo@denylisted.mastodon.space', account: account, password: password)
expect(user).to_not be_valid expect(user).to_not be_valid

View File

@ -9,10 +9,10 @@ RSpec.describe EmailMxValidator do
context 'with an e-mail domain that is explicitly allowed' do context 'with an e-mail domain that is explicitly allowed' do
around do |block| around do |block|
tmp = Rails.configuration.x.email_domains_allowlist tmp = Rails.configuration.x.email_domains.allowlist
Rails.configuration.x.email_domains_allowlist = 'example.com' Rails.configuration.x.email_domains.allowlist = 'example.com'
block.call block.call
Rails.configuration.x.email_domains_allowlist = tmp Rails.configuration.x.email_domains.allowlist = tmp
end end
it 'does not add errors if there are no DNS records' do it 'does not add errors if there are no DNS records' do