summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorGo MAEDA <maeda@farend.jp>2020-04-30 09:00:06 +0000
committerGo MAEDA <maeda@farend.jp>2020-04-30 09:00:06 +0000
commit94c62816a52c13e1cdb91aab6f26c4798897b4a5 (patch)
tree6df427ce135df8554fc6ded0b95bf2860caaaa2b
parentd94226dd4ccc5757a94f3fe1542cdd736831fd48 (diff)
downloadredmine-94c62816a52c13e1cdb91aab6f26c4798897b4a5.tar.gz
redmine-94c62816a52c13e1cdb91aab6f26c4798897b4a5.zip
Allowed/Disallowed email domains settings to restrict users' email addresses (#3369).
Patch by Yuichi HARADA and Go MAEDA. git-svn-id: http://svn.redmine.org/redmine/trunk@19735 e93f8b46-1217-0410-a6f0-8f06a7374b81
-rw-r--r--app/models/email_address.rb27
-rw-r--r--app/views/settings/_users.html.erb6
-rw-r--r--config/locales/en.yml2
-rw-r--r--config/settings.yml4
-rw-r--r--test/functional/email_addresses_controller_test.rb30
-rw-r--r--test/unit/email_address_test.rb34
6 files changed, 103 insertions, 0 deletions
diff --git a/app/models/email_address.rb b/app/models/email_address.rb
index b27cf6e58..1005914de 100644
--- a/app/models/email_address.rb
+++ b/app/models/email_address.rb
@@ -36,6 +36,7 @@ class EmailAddress < ActiveRecord::Base
validates_length_of :address, :maximum => User::MAIL_LENGTH_LIMIT, :allow_nil => true
validates_uniqueness_of :address, :case_sensitive => false,
:if => Proc.new {|email| email.address_changed? && email.address.present?}
+ validate :validate_email_domain, :if => proc {|email| email.address.present?}
safe_attributes 'address'
@@ -51,6 +52,28 @@ class EmailAddress < ActiveRecord::Base
end
end
+ # Returns true if the email domain is allowed regarding allowed/denied
+ # domains defined in application settings, otherwise false
+ def self.valid_domain?(domain_or_email)
+ denied, allowed =
+ [:email_domains_denied, :email_domains_allowed].map do |setting|
+ Setting.__send__(setting)
+ end
+ domain = domain_or_email.split('@').last
+ return false if denied.present? && domain_in?(domain, denied)
+ return false if allowed.present? && !domain_in?(domain, allowed)
+ true
+ end
+
+ # Returns true if domain belongs to domains list.
+ def self.domain_in?(domain, domains)
+ domain = domain.downcase
+ domains = domains.to_s.split(/[\s,]+/) unless domains.is_a?(Array)
+ domains.reject(&:blank?).map(&:downcase).any? do |s|
+ s.start_with?('.') ? domain.end_with?(s) : domain == s
+ end
+ end
+
private
# send a security notification to user that a new email address was added
@@ -117,4 +140,8 @@ class EmailAddress < ActiveRecord::Base
Token.where(:user_id => user_id, :action => tokens).delete_all
end
end
+
+ def validate_email_domain
+ errors.add(:address, :invalid) unless self.class.valid_domain?(address)
+ end
end
diff --git a/app/views/settings/_users.html.erb b/app/views/settings/_users.html.erb
index ab61d7c21..11c2aadac 100644
--- a/app/views/settings/_users.html.erb
+++ b/app/views/settings/_users.html.erb
@@ -3,6 +3,12 @@
<div class="box tabular settings">
<p><%= setting_text_field :max_additional_emails, :size => 6 %></p>
+ <p><%= setting_text_area :email_domains_allowed %>
+ <em class="info"><%= l(:text_comma_separated) %> <%= l(:label_example) %>: example.com, example.org</em></p>
+
+ <p><%= setting_text_area :email_domains_denied %>
+ <em class="info"><%= l(:text_comma_separated) %> <%= l(:label_example) %>: .example.com, foo.example.org, example.net</em></p>
+
<p><%= setting_check_box :unsubscribe %></p>
</div>
diff --git a/config/locales/en.yml b/config/locales/en.yml
index cd9b9a766..f1abf88ed 100644
--- a/config/locales/en.yml
+++ b/config/locales/en.yml
@@ -479,6 +479,8 @@ en:
setting_force_default_language_for_loggedin: Force default language for logged-in users
setting_link_copied_issue: Link issues on copy
setting_max_additional_emails: Maximum number of additional email addresses
+ setting_email_domains_allowed: Allowed email domains
+ setting_email_domains_denied: Disallowed email domains
setting_search_results_per_page: Search results per page
setting_attachment_extensions_allowed: Allowed extensions
setting_attachment_extensions_denied: Disallowed extensions
diff --git a/config/settings.yml b/config/settings.yml
index 5aaaaac28..7c0912232 100644
--- a/config/settings.yml
+++ b/config/settings.yml
@@ -53,6 +53,10 @@ password_max_age:
max_additional_emails:
format: int
default: 5
+email_domains_allowed:
+ default:
+email_domains_denied:
+ default:
# Maximum lifetime of user sessions in minutes
session_lifetime:
format: int
diff --git a/test/functional/email_addresses_controller_test.rb b/test/functional/email_addresses_controller_test.rb
index 76ff0a561..deb996105 100644
--- a/test/functional/email_addresses_controller_test.rb
+++ b/test/functional/email_addresses_controller_test.rb
@@ -118,6 +118,36 @@ class EmailAddressesControllerTest < Redmine::ControllerTest
end
end
+ def test_create_with_disallowed_domain_should_fail
+ @request.session[:user_id] = 2
+
+ with_settings :email_domains_denied => 'black.example' do
+ assert_no_difference 'EmailAddress.count' do
+ post :create, :params => {
+ :user_id => 2,
+ :email_address => {
+ :address => 'another@black.example'
+ }
+ }
+ assert_response :success
+ assert_select_error 'Email is invalid'
+ end
+ end
+
+ with_settings :email_domains_allowed => 'white.example' do
+ assert_no_difference 'EmailAddress.count' do
+ post :create, :params => {
+ :user_id => 2,
+ :email_address => {
+ :address => 'something@example.fr'
+ }
+ }
+ assert_response :success
+ assert_select_error 'Email is invalid'
+ end
+ end
+ end
+
def test_create_should_send_security_notification
@request.session[:user_id] = 2
ActionMailer::Base.deliveries.clear
diff --git a/test/unit/email_address_test.rb b/test/unit/email_address_test.rb
index 3237d01b4..c3242e25b 100644
--- a/test/unit/email_address_test.rb
+++ b/test/unit/email_address_test.rb
@@ -30,4 +30,38 @@ class EmailAddressTest < ActiveSupport::TestCase
email = EmailAddress.new(address: 'jsmith@example.xn--80akhbyknj4f')
assert email.valid?
end
+
+ def test_address_should_be_validated_against_denied_domains
+ with_settings :email_domains_denied => "black.test\r\nBLACK.EXAMPLE, .subdomain.test" do
+ email = EmailAddress.new(address: 'user@black.test')
+ assert_not email.valid?
+ email = EmailAddress.new(address: 'user@notblack.test')
+ assert email.valid?
+ email = EmailAddress.new(address: 'user@BLACK.TEST')
+ assert_not email.valid?
+ email = EmailAddress.new(address: 'user@black.example')
+ assert_not email.valid?
+ email = EmailAddress.new(address: 'user@subdomain.test')
+ assert email.valid?
+ email = EmailAddress.new(address: 'user@foo.subdomain.test')
+ assert_not email.valid?
+ end
+ end
+
+ def test_address_should_be_validated_against_allowed_domains
+ with_settings :email_domains_allowed => "white.test\r\nWHITE.EXAMPLE, .subdomain.test" do
+ email = EmailAddress.new(address: 'user@white.test')
+ assert email.valid?
+ email = EmailAddress.new(address: 'user@notwhite.test')
+ assert_not email.valid?
+ email = EmailAddress.new(address: 'user@WHITE.TEST')
+ assert email.valid?
+ email = EmailAddress.new(address: 'user@white.example')
+ assert email.valid?
+ email = EmailAddress.new(address: 'user@subdomain.test')
+ assert_not email.valid?
+ email = EmailAddress.new(address: 'user@foo.subdomain.test')
+ assert email.valid?
+ end
+ end
end