diff --git a/app/models/email_address.rb b/app/models/email_address.rb index 4cd9c5dfd..85b77fc3a 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_domain, :if => Proc.new{|email| email.errors[:address].blank? && email.address.present?} safe_attributes 'address' @@ -117,4 +118,29 @@ class EmailAddress < ActiveRecord::Base Token.where(:user_id => user_id, :action => tokens).delete_all end end + + def validate_domain + denied, allowed = + [:email_domains_denied, :email_domains_allowed].map do |setting| + Setting.__send__(setting) + end + invalid = false + domain = address.sub(/\A.*@/, '') + if denied.present? && domain_in?(domain, denied) + invalid = true + end + if allowed.present? && !domain_in?(domain, allowed) + invalid = true + end + errors.add(:address, l(:error_domain_not_allowed, :domain => domain)) if invalid + end + + def domain_in?(addr, domains) + domain = addr.downcase.sub(/\A.*@/, '') + unless domains.is_a?(Array) + domains = domains.to_s.split(/[\s,]+/) + end + domains = domains.map{|s| s.downcase.sub(/\A.*@/, '')}.reject(&:blank?) + domains.include?(domain) + end end diff --git a/app/views/settings/_users.html.erb b/app/views/settings/_users.html.erb index ab61d7c21..8d446317a 100644 --- a/app/views/settings/_users.html.erb +++ b/app/views/settings/_users.html.erb @@ -4,6 +4,12 @@

<%= setting_text_field :max_additional_emails, :size => 6 %>

<%= setting_check_box :unsubscribe %>

+ +

<%= setting_text_area :email_domains_allowed %> + <%= l(:text_comma_separated) %> <%= l(:label_example) %>: foo.example.com, bar.example.org

+ +

<%= setting_text_area :email_domains_denied %> + <%= l(:text_comma_separated) %> <%= l(:label_example) %>: baz.example.net, qux.example.biz

diff --git a/config/locales/en.yml b/config/locales/en.yml index 7bb506c57..7d1c793e5 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -232,6 +232,7 @@ en: error_can_not_delete_auth_source: "This authentication mode is in use and cannot be deleted." error_spent_on_future_date: "Cannot log time on a future date" error_not_allowed_to_log_time_for_other_users: "You are not allowed to log time for other users" + error_domain_not_allowed: "Domain %{domain} is not allowed" mail_subject_lost_password: "Your %{value} password" mail_body_lost_password: 'To change your password, click on the following link:' @@ -476,6 +477,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 d33523aeb..4dee88b26 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..f19e67a08 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_failure + @request.session[:user_id] = 2 + + with_settings :email_domains_denied => "example.com, Somenet.Foo\r\nlocalhost.localdomain" do + assert_no_difference 'EmailAddress.count' do + post :create, :params => { + :user_id => 2, + :email_address => { + :address => 'another@somenet.foo' + } + } + assert_response :success + assert_select_error /Email Domain somenet.foo is not allowed/ + end + end + + with_settings :email_domains_allowed => "example.com, Somenet.Foo\r\nlocalhost.localdomain" 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 Domain example.fr is not allowed/ + end + end + end + def test_create_should_send_security_notification @request.session[:user_id] = 2 ActionMailer::Base.deliveries.clear diff --git a/test/unit/user_test.rb b/test/unit/user_test.rb index 56e4c5ecf..dbd7e28a4 100644 --- a/test/unit/user_test.rb +++ b/test/unit/user_test.rb @@ -225,6 +225,56 @@ class UserTest < ActiveSupport::TestCase assert_include "Email #{I18n.translate('activerecord.errors.messages.taken')}", u.errors.full_messages end + def test_mail_with_disallowed_domain + with_settings :email_domains_denied => "example.com, Somenet.Foo\r\nlocalhost.localdomain" do + assert_difference 'User.count' do + assert_difference 'EmailAddress.count' do + u = User.new( + :login => 'newuser1', :firstname => 'new', :lastname => 'user', + :mail => 'newuser@example.net' + ) + assert u.save + end + end + + assert_no_difference 'User.count' do + assert_no_difference 'EmailAddress.count' do + u = User.new( + :login => 'newuser2', :firstname => 'new', :lastname => 'user', + :mail => 'newuser@somenet.foo' + ) + assert !u.save + assert_include 'Email Domain somenet.foo is not allowed', u.errors.full_messages + end + end + end + end + + def test_mail_with_allowed_domain + with_settings :email_domains_allowed => "example.com, Somenet.Foo\r\nlocalhost.localdomain" do + assert_difference 'User.count' do + assert_difference 'EmailAddress.count' do + u = User.new( + :login => 'newuser1', :firstname => 'new', :lastname => 'user', + :mail => 'newuser@somenet.foo' + ) + assert u.save + end + end + + assert_no_difference 'User.count' do + assert_no_difference 'EmailAddress.count' do + u = User.new( + :login => 'newuser2', :firstname => 'new', :lastname => 'user', + :mail => 'newuser@example.net' + ) + assert !u.save + assert_include 'Email Domain example.net is not allowed', u.errors.full_messages + end + end + end + end + def test_update assert_equal "admin", @admin.login @admin.login = "john"