From 64a8bb6d00202bb85c394acc8cd54f38cf57a311 Mon Sep 17 00:00:00 2001 From: Gabriel Sobrinho Date: Thu, 30 Apr 2020 20:00:50 -0300 Subject: [PATCH] Add rolling option --- Changelog.md | 1 + lib/devise/encryptable/encryptable.rb | 4 ++ lib/devise/encryptable/model.rb | 54 +++++++++++++++++---- test/devise/encryptable/encryptable_test.rb | 15 ++++++ test/support/swappers.rb | 1 + 5 files changed, 65 insertions(+), 10 deletions(-) diff --git a/Changelog.md b/Changelog.md index 2b8f903..3699e81 100644 --- a/Changelog.md +++ b/Changelog.md @@ -2,6 +2,7 @@ * Support Rails 4.1 - 7.0, dropped support for Rails <= 4.0. (same support as Devise as of v4.8) * Support Ruby 2.1 - 3.1, dropped support for Ruby <= 2.0. (same support as Devise as of v4.8) +* Add support to roll legacy encryptors ### 0.2.0 diff --git a/lib/devise/encryptable/encryptable.rb b/lib/devise/encryptable/encryptable.rb index cfd5af3..e5d6afd 100644 --- a/lib/devise/encryptable/encryptable.rb +++ b/lib/devise/encryptable/encryptable.rb @@ -13,6 +13,10 @@ module Devise mattr_accessor :encryptor @@encryptor = nil + # Used to define a legacy algorithm that needs to be rolled to a new one. + mattr_accessor :transition_from_encryptor + @@transition_from_encryptor = nil + module Encryptable module Encryptors autoload :AuthlogicSha512, 'devise/encryptable/encryptors/authlogic_sha512' diff --git a/lib/devise/encryptable/model.rb b/lib/devise/encryptable/model.rb index c84beca..b23f552 100644 --- a/lib/devise/encryptable/model.rb +++ b/lib/devise/encryptable/model.rb @@ -13,9 +13,13 @@ module Models # # * +encryptor+: the encryptor going to be used. By default is nil. # + # * +transition_from_encryptor+: the legacy encryptor that needs to rolled to the +encryptor+. + # # == Examples # - # User.find(1).valid_password?('password123') # returns true/false + # User.find(1).valid_password?('password123') + # #=> returns true/false + # #=> if true and using a legacy encryptor, it will update the user to the new encryptor. # module Encryptable extend ActiveSupport::Concern @@ -38,7 +42,21 @@ def password=(new_password) # Validates the password considering the salt. def valid_password?(password) return false if encrypted_password.blank? - encryptor_class.compare(encrypted_password, password, self.class.stretches, authenticatable_salt, self.class.pepper) + + encryptor_arguments = [ + encrypted_password, + password, + self.class.stretches, + authenticatable_salt, + self.class.pepper + ] + + if transition_from_encryptor_class.try(:compare, *encryptor_arguments) + update_attribute(:password, password) + return true + end + + encryptor_class.compare(*encryptor_arguments) end # Overrides authenticatable salt to use the new password_salt @@ -62,24 +80,40 @@ def encryptor_class self.class.encryptor_class end + def transition_from_encryptor_class + self.class.transition_from_encryptor_class + end + module ClassMethods Devise::Models.config(self, :encryptor) + Devise::Models.config(self, :transition_from_encryptor) # Returns the class for the configured encryptor. def encryptor_class - @encryptor_class ||= case encryptor - when :bcrypt - raise "In order to use bcrypt as encryptor, simply remove :encryptable from your devise model" - when nil - raise "You need to give an :encryptor as option in order to use :encryptable" - else - Devise::Encryptable::Encryptors.const_get(encryptor.to_s.classify) - end + @encryptor_class ||= compute_encryptor_class(encryptor) + end + + # Returns the class for the configured transition from encryptor. + def transition_from_encryptor_class + @transition_from_encryptor_class ||= compute_encryptor_class(transition_from_encryptor) if transition_from_encryptor end def password_salt self.encryptor_class.salt(self.stretches) end + + private + + def compute_encryptor_class(encryptor) + case encryptor + when :bcrypt + raise "In order to use bcrypt as encryptor, simply remove :encryptable from your devise model" + when nil + raise "You need to give an :encryptor as option in order to use :encryptable" + else + Devise::Encryptable::Encryptors.const_get(encryptor.to_s.classify) + end + end end end end diff --git a/test/devise/encryptable/encryptable_test.rb b/test/devise/encryptable/encryptable_test.rb index 68bb125..3e474d6 100644 --- a/test/devise/encryptable/encryptable_test.rb +++ b/test/devise/encryptable/encryptable_test.rb @@ -48,6 +48,21 @@ def encrypt_password(admin, pepper=Admin.pepper, stretches=Admin.stretches, encr assert_not_equal encrypted_password, admin.encrypted_password end + test 'should roll from legacy encryptor to the new one' do + admin = nil + + swap_with_encryptor Admin, :sha1 do + admin = create_admin + assert admin.valid_password?('123456') + assert_equal admin.encrypted_password, encrypt_password(admin, Admin.pepper, Admin.stretches, Devise::Encryptable::Encryptors::Sha1) + end + + swap_with_encryptor Admin, :sha512, transition_from_encryptor: :sha1 do + assert admin.valid_password?('123456') + assert_equal admin.encrypted_password, encrypt_password(admin, Admin.pepper, Admin.stretches, Devise::Encryptable::Encryptors::Sha512) + end + end + test 'should respect encryptor configuration' do swap_with_encryptor Admin, :sha512 do admin = create_admin diff --git a/test/support/swappers.rb b/test/support/swappers.rb index a2d8715..acabca6 100644 --- a/test/support/swappers.rb +++ b/test/support/swappers.rb @@ -2,6 +2,7 @@ module Support module Swappers def swap_with_encryptor(klass, encryptor, options={}) klass.instance_variable_set(:@encryptor_class, nil) + klass.instance_variable_set(:@transition_from_encryptor, nil) swap klass, options.merge(:encryptor => encryptor) do begin