From fdea519df86d999201ed8cc0d5b7230c3f47e43f Mon Sep 17 00:00:00 2001 From: Marcelo Silveira Date: Mon, 9 Nov 2009 18:43:21 -0200 Subject: [PATCH] moved password encryption out of Authenticatable to allow custom encryptions for people coming by with an existent users table --- generators/devise_install/templates/devise.rb | 6 +++ lib/devise.rb | 2 +- lib/devise/migrations.rb | 2 +- lib/devise/models/authenticatable.rb | 24 ++++------- lib/devise/models/encryptors/sha1.rb | 40 +++++++++++++++++++ lib/devise/models/encryptors/sha512.rb | 40 +++++++++++++++++++ test/models/authenticatable_test.rb | 15 ++++++- 7 files changed, 109 insertions(+), 20 deletions(-) create mode 100644 lib/devise/models/encryptors/sha1.rb create mode 100644 lib/devise/models/encryptors/sha512.rb diff --git a/generators/devise_install/templates/devise.rb b/generators/devise_install/templates/devise.rb index 8843e7b5..f234cb80 100644 --- a/generators/devise_install/templates/devise.rb +++ b/generators/devise_install/templates/devise.rb @@ -8,6 +8,12 @@ Devise.setup do |config| # Configure how many times you want the password is reencrypted. Default is 10. # config.stretches = 10 + # Define what will be the encryption algorithm. Sha1 is the default. + # Supported encryptions: + # => ::Devise::Models::Encryptors::Sha1 + # => ::Devise::Models::Encryptors::Sha512 + # config.encryptor = ::Devise::Models::Encryptors::Sha1 + # The time you want give to your user to confirm his account. During this time # he will be able to access your application without confirming. Default is nil. # config.confirm_within = 2.days diff --git a/lib/devise.rb b/lib/devise.rb index 717619ce..7a63ca54 100644 --- a/lib/devise.rb +++ b/lib/devise.rb @@ -18,7 +18,7 @@ module Devise } # Models configuration - mattr_accessor :pepper, :stretches, :remember_for, :confirm_within + mattr_accessor :pepper, :encryptor, :stretches, :remember_for, :confirm_within # Mappings mattr_accessor :mappings diff --git a/lib/devise/migrations.rb b/lib/devise/migrations.rb index 822dec26..94e5a490 100644 --- a/lib/devise/migrations.rb +++ b/lib/devise/migrations.rb @@ -22,7 +22,7 @@ module Devise def authenticatable(options={}) null = options[:null] || false string :email, :limit => 100, :null => null - string :encrypted_password, :limit => 40, :null => null + string :encrypted_password, :limit => 128, :null => null string :password_salt, :limit => 20, :null => null end diff --git a/lib/devise/models/authenticatable.rb b/lib/devise/models/authenticatable.rb index 1dcaa94f..3d4a70e4 100644 --- a/lib/devise/models/authenticatable.rb +++ b/lib/devise/models/authenticatable.rb @@ -1,4 +1,3 @@ -require 'digest/sha1' require 'devise/strategies/authenticatable' module Devise @@ -39,30 +38,20 @@ module Devise def password=(new_password) @password = new_password self.password_salt = friendly_token - self.encrypted_password = password_digest(@password) + self.encrypted_password = encryptor.digest(@password, encryptor_params) end # Verifies whether an incoming_password (ie from login) is the user # password. def valid_password?(incoming_password) - password_digest(incoming_password) == encrypted_password + encryptor.digest(incoming_password, encryptor_params) == encrypted_password end protected - - # Gererates a default password digest based on salt, pepper and the - # incoming password. - def password_digest(password_to_digest) - digest = pepper - stretches.times { digest = secure_digest(password_salt, digest, password_to_digest, pepper) } - digest - end - - # Generate a SHA1 digest joining args. Generated token is something like - # - # --arg1--arg2--arg3--argN-- - def secure_digest(*tokens) - ::Digest::SHA1.hexdigest('--' << tokens.flatten.join('--') << '--') + + # Puts the encryptor default params together + def encryptor_params + { :salt => password_salt, :pepper => pepper } end # Generate a friendly string randomically to be used as token. @@ -92,6 +81,7 @@ module Devise Devise::Models.config(self, :pepper) Devise::Models.config(self, :stretches, 10) + Devise::Models.config(self, :encryptor, ::Devise::Models::Encryptors::Sha1) end end end diff --git a/lib/devise/models/encryptors/sha1.rb b/lib/devise/models/encryptors/sha1.rb new file mode 100644 index 00000000..17c69734 --- /dev/null +++ b/lib/devise/models/encryptors/sha1.rb @@ -0,0 +1,40 @@ +require 'digest/sha1' + +module Devise + module Models + + # Implements a way of adding different encryptions. + # The class should implement a self.digest method that taks the following params: + # - password to be digest + # - params (#hash) + # - salt: the password salt as defined by devise + # - pepper: Devise config option + # - stretches: Devise config option + # + module Encryptors + # = Sha1 + # + # Uses the Sha1 hash algorithm to encrypt passwords. + class Sha1 + + # Gererates a default password digest based on salt, pepper and the + # incoming password. + def self.digest(password_to_digest, params) + digest = params[:pepper] + Devise.stretches.times { digest = self.secure_digest(params[:salt], digest, password_to_digest, params[:pepper]) } + digest + end + + private + + # Generate a SHA1 digest joining args. Generated token is something like + # + # --arg1--arg2--arg3--argN-- + def self.secure_digest(*tokens) + ::Digest::SHA1.hexdigest('--' << tokens.flatten.join('--') << '--') + end + + end + end + end +end \ No newline at end of file diff --git a/lib/devise/models/encryptors/sha512.rb b/lib/devise/models/encryptors/sha512.rb new file mode 100644 index 00000000..0943c920 --- /dev/null +++ b/lib/devise/models/encryptors/sha512.rb @@ -0,0 +1,40 @@ +require "digest/sha2" + +module Devise + module Models + + # Implements a way of adding different encryptions. + # The class should implement a self.digest method that taks the following params: + # - password to be digest + # - params (#hash) + # - salt: the password salt as defined by devise + # - pepper: Devise config option + # - stretches: Devise config option + # + module Encryptors + # = Sha512 + # + # Uses the Sha512 hash algorithm to encrypt passwords. + class Sha512 + + # Gererates a default password digest based on salt, pepper and the + # incoming password. + def self.digest(password_to_digest, params) + digest = params[:pepper] + Devise.stretches.times { digest = self.secure_digest(params[:salt], digest, password_to_digest, params[:pepper]) } + digest + end + + private + + # Generate a Sha512 digest joining args. Generated token is something like + # + # --arg1--arg2--arg3--argN-- + def self.secure_digest(*tokens) + ::Digest::SHA512.hexdigest('--' << tokens.flatten.join('--') << '--') + end + + end + end + end +end \ No newline at end of file diff --git a/test/models/authenticatable_test.rb b/test/models/authenticatable_test.rb index 8219c17e..0116748f 100644 --- a/test/models/authenticatable_test.rb +++ b/test/models/authenticatable_test.rb @@ -6,7 +6,7 @@ class AuthenticatableTest < ActiveSupport::TestCase def encrypt_password(user, pepper=nil, stretches=1) user.class_eval { define_method(:stretches) { stretches } } if stretches user.password = '123456' - ::Digest::SHA1.hexdigest("--#{user.password_salt}--#{pepper}--123456--#{pepper}--") + user.encryptor.digest('123456', { :salt => user.password_salt, :pepper => pepper }) end test 'should respond to password and password confirmation' do @@ -90,6 +90,19 @@ class AuthenticatableTest < ActiveSupport::TestCase Devise.stretches = default_stretches end end + + test 'should fallback to Sha1 as default encryption' do + user = create_user + puts user.encrypted_password + assert_equal user.encrypted_password, ::Devise::Models::Encryptors::Sha1.digest('123456', { :pepper => Devise.pepper, :salt => user.password_salt }) + end + + test 'should act according to encryptor configuration' do + Devise.encryptor = ::Devise::Models::Encryptors::Sha512 + user = create_user + puts user.encrypted_password + assert_equal user.encrypted_password, ::Devise::Models::Encryptors::Sha512.digest('123456', { :pepper => Devise.pepper, :salt => user.password_salt }) + end test 'should test for a valid password' do user = create_user