From de17640489493feac688d2e721f2c0f2d599536e Mon Sep 17 00:00:00 2001 From: Eric Davis Date: Sun, 20 Jun 2010 21:40:50 +0000 Subject: [PATCH] Change User#login to be case-insensitive. #2473 This change also overrides User#find_by_login to give priority to exact matches in the login. Contributed by Greg Mefford git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@3807 e93f8b46-1217-0410-a6f0-8f06a7374b81 --- app/models/user.rb | 13 +++++++++++-- test/unit/user_test.rb | 34 ++++++++++++++++++++++++++++++++++ 2 files changed, 45 insertions(+), 2 deletions(-) diff --git a/app/models/user.rb b/app/models/user.rb index a38a0917..5d0254ee 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -53,7 +53,7 @@ class User < Principal attr_protected :login, :admin, :password, :password_confirmation, :hashed_password, :group_ids validates_presence_of :login, :firstname, :lastname, :mail, :if => Proc.new { |user| !user.is_a?(AnonymousUser) } - validates_uniqueness_of :login, :if => Proc.new { |user| !user.login.blank? } + validates_uniqueness_of :login, :if => Proc.new { |user| !user.login.blank? }, :case_sensitive => false validates_uniqueness_of :mail, :if => Proc.new { |user| !user.mail.blank? }, :case_sensitive => false # Login must contain lettres, numbers, underscores only validates_format_of :login, :with => /^[a-z0-9_\-@\.]*$/i @@ -96,7 +96,7 @@ class User < Principal def self.try_to_login(login, password) # Make sure no one can sign in with an empty password return nil if password.to_s.empty? - user = find(:first, :conditions => ["login=?", login]) + user = find_by_login(login) if user # user is already in local database return nil if !user.active? @@ -222,6 +222,15 @@ class User < Principal notified_projects_ids end + # case-insensitive fall-over + def self.find_by_login(login) + # First look for an exact match + user = find(:first, :conditions => ["login = ?", login]) + # Fail over to case-insensitive if none was found + user = find(:first, :conditions => ["LOWER(login) = ?", login.to_s.downcase]) if user.nil? + return user + end + def self.find_by_rss_key(key) token = Token.find_by_value(key) token && token.user.active? ? token.user : nil diff --git a/test/unit/user_test.rb b/test/unit/user_test.rb index 77a9ee98..8d66e86d 100644 --- a/test/unit/user_test.rb +++ b/test/unit/user_test.rb @@ -55,6 +55,21 @@ class UserTest < ActiveSupport::TestCase assert user.save end + context "User.login" do + should "be case-insensitive." do + u = User.new(:firstname => "new", :lastname => "user", :mail => "newuser@somenet.foo") + u.login = 'newuser' + u.password, u.password_confirmation = "password", "password" + assert u.save + + u = User.new(:firstname => "Similar", :lastname => "User", :mail => "similaruser@somenet.foo") + u.login = 'NewUser' + u.password, u.password_confirmation = "password", "password" + assert !u.save + assert_equal I18n.translate('activerecord.errors.messages.taken'), u.errors.on(:login) + end + end + def test_mail_uniqueness_should_not_be_case_sensitive u = User.new(:firstname => "new", :lastname => "user", :mail => "newuser@somenet.foo") u.login = 'newuser1' @@ -88,6 +103,25 @@ class UserTest < ActiveSupport::TestCase assert_equal 1, @admin.errors.count end + context "User#try_to_login" do + should "fall-back to case-insensitive if user login is not found as-typed." do + user = User.try_to_login("AdMin", "admin") + assert_kind_of User, user + assert_equal "admin", user.login + end + + should "select the exact matching user first" do + case_sensitive_user = User.generate_with_protected!(:login => 'changed', :password => 'admin', :password_confirmation => 'admin') + # bypass validations to make it appear like existing data + case_sensitive_user.update_attribute(:login, 'ADMIN') + + user = User.try_to_login("ADMIN", "admin") + assert_kind_of User, user + assert_equal "ADMIN", user.login + + end + end + def test_password user = User.try_to_login("admin", "admin") assert_kind_of User, user