diff --git a/lib/casino_core/authenticator/activerecord.rb b/lib/casino_core/authenticator/activerecord.rb index 046ea45..970f874 100644 --- a/lib/casino_core/authenticator/activerecord.rb +++ b/lib/casino_core/authenticator/activerecord.rb @@ -1,6 +1,7 @@ require 'active_record' require 'unix_crypt' require 'bcrypt' +require 'digest/sha1' class CASinoCore::Authenticator::ActiveRecord @@ -23,10 +24,13 @@ class #{self.class.to_s}::#{@options[:table].classify} < AuthDatabase def validate(username, password) @model.verify_active_connections! - user = @model.send("find_by_#{@options[:username_column]}!", username) + user = @model.send("find_by_#{@options[:username_column]}", username) + unless user + user = @model.send("find_by_#{@options[:email_column]}!", username) + end password_from_database = user.send(@options[:password_column]) - if valid_password?(password, password_from_database) + if valid_password?(password, password_from_database, (user.salt if user.respond_to?(:salt))) { username: user.send(@options[:username_column]), extra_attributes: extra_attributes(user) } else false @@ -37,12 +41,18 @@ def validate(username, password) end private - def valid_password?(password, password_from_database) + def secure_digest(*args) + Digest::SHA1.hexdigest(args.flatten.join('--')) + end + + def valid_password?(password, password_from_database, salt=nil) return false if password_from_database.blank? magic = password_from_database.split('$')[1] case magic when /\A2a?\z/ valid_password_with_bcrypt?(password, password_from_database) + when /\Asha?\z/ + valid_password_with_sha1_crypt?(password, password_from_database, salt) else valid_password_with_unix_crypt?(password, password_from_database) end @@ -57,6 +67,14 @@ def valid_password_with_unix_crypt?(password, password_from_database) UnixCrypt.valid?(password, password_from_database) end + def valid_password_with_sha1_crypt?(password, password_from_database, salt) + site_auth_key = digest = @options[:site_auth_key].to_s + 10.times do + digest = secure_digest(digest, salt, password, site_auth_key) + end + digest == password_from_database.split('$')[2] + end + def extra_attributes(user) attributes = {} extra_attributes_option.each do |attribute_name, database_column| diff --git a/spec/casino_core/authenticator/activerecord_spec.rb b/spec/casino_core/authenticator/activerecord_spec.rb index a4abfac..1039099 100644 --- a/spec/casino_core/authenticator/activerecord_spec.rb +++ b/spec/casino_core/authenticator/activerecord_spec.rb @@ -5,6 +5,7 @@ let(:pepper) { nil } let(:extra_attributes) {{ email: 'mail_address' }} + let(:site_auth_key) { '9df92c193273ae9adf804195641b50828dee0088' } let(:options) do { connection: { @@ -14,7 +15,9 @@ table: 'users', username_column: 'username', password_column: 'password', + email_column: 'email', pepper: pepper, + site_auth_key: site_auth_key, extra_attributes: extra_attributes } end @@ -30,6 +33,8 @@ t.string :username t.string :password t.string :mail_address + t.string :salt + t.string :email end end end @@ -130,6 +135,33 @@ end end + context 'support for sha1 restful-authentication' do + before do + CASinoCore::Authenticator::ActiveRecord::User.create!( + username: 'test4', + password: '$sha$2986d3bb945c0d03901fb7ec7e1405e5d9846271', + mail_address: 'mail@example.org', + salt: 'b1676d830c1558b584491089239f3ff448e5277e') + end + + it 'is able to handle sha1 restful-authentication password hashes' do + @authenticator.validate('test4', 'password').should be_instance_of(Hash) + end + end + + context 'support for login by username and/or email' do + before do + CASinoCore::Authenticator::ActiveRecord::User.create!( + username: 'test5', + password: '$2a$10$q9nu9AfeUCkwMeDaFlhOQ.2L0UK0tpYIq8JjJDzphO7qy1vdU9.Se', + email: 'test5@copper.io' + ) + end + it 'allows login using email' do + @authenticator.validate('test5@copper.io', 'testpassword4').should be_instance_of(Hash) + end + end + end end