Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add rolling option #19

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
4 changes: 4 additions & 0 deletions lib/devise/encryptable/encryptable.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down
54 changes: 44 additions & 10 deletions lib/devise/encryptable/model.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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
Expand Down
15 changes: 15 additions & 0 deletions test/devise/encryptable/encryptable_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions test/support/swappers.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down