Skip to content

Commit

Permalink
Refactor has_secure_password to extract dedicated attribute module
Browse files Browse the repository at this point in the history
Follow up of #26764 and #35700.

And add test case for #35700.
  • Loading branch information
kamipo committed Apr 4, 2019
1 parent dc45130 commit 50fba82
Show file tree
Hide file tree
Showing 3 changed files with 59 additions and 36 deletions.
74 changes: 38 additions & 36 deletions activemodel/lib/active_model/secure_password.rb
Expand Up @@ -69,42 +69,7 @@ def has_secure_password(attribute = :password, validations: true)
raise
end

mod = Module.new do
attr_reader attribute

define_method("#{attribute}=") do |unencrypted_password|
if unencrypted_password.nil?
self.send("#{attribute}_digest=", nil)
elsif !unencrypted_password.empty?
instance_variable_set("@#{attribute}", unencrypted_password)
cost = ActiveModel::SecurePassword.min_cost ? BCrypt::Engine::MIN_COST : BCrypt::Engine.cost
self.send("#{attribute}_digest=", BCrypt::Password.create(unencrypted_password, cost: cost))
end
end

define_method("#{attribute}_confirmation=") do |unencrypted_password|
instance_variable_set("@#{attribute}_confirmation", unencrypted_password)
end

# Returns +self+ if the password is correct, otherwise +false+.
#
# class User < ActiveRecord::Base
# has_secure_password validations: false
# end
#
# user = User.new(name: 'david', password: 'mUc3m00RsqyRe')
# user.save
# user.authenticate_password('notright') # => false
# user.authenticate_password('mUc3m00RsqyRe') # => user
define_method("authenticate_#{attribute}") do |unencrypted_password|
attribute_digest = send("#{attribute}_digest")
BCrypt::Password.new(attribute_digest).is_password?(unencrypted_password) && self
end

alias_method :authenticate, :authenticate_password if attribute == :password
end

include mod
include InstanceMethodsOnActivation.new(attribute)

if validations
include ActiveModel::Validations
Expand All @@ -122,5 +87,42 @@ def has_secure_password(attribute = :password, validations: true)
end
end
end

class InstanceMethodsOnActivation < Module
def initialize(attribute)
attr_reader attribute

define_method("#{attribute}=") do |unencrypted_password|
if unencrypted_password.nil?
self.send("#{attribute}_digest=", nil)
elsif !unencrypted_password.empty?
instance_variable_set("@#{attribute}", unencrypted_password)
cost = ActiveModel::SecurePassword.min_cost ? BCrypt::Engine::MIN_COST : BCrypt::Engine.cost
self.send("#{attribute}_digest=", BCrypt::Password.create(unencrypted_password, cost: cost))
end
end

define_method("#{attribute}_confirmation=") do |unencrypted_password|
instance_variable_set("@#{attribute}_confirmation", unencrypted_password)
end

# Returns +self+ if the password is correct, otherwise +false+.
#
# class User < ActiveRecord::Base
# has_secure_password validations: false
# end
#
# user = User.new(name: 'david', password: 'mUc3m00RsqyRe')
# user.save
# user.authenticate_password('notright') # => false
# user.authenticate_password('mUc3m00RsqyRe') # => user
define_method("authenticate_#{attribute}") do |unencrypted_password|
attribute_digest = send("#{attribute}_digest")
BCrypt::Password.new(attribute_digest).is_password?(unencrypted_password) && self
end

alias_method :authenticate, :authenticate_password if attribute == :password
end
end
end
end
14 changes: 14 additions & 0 deletions activemodel/test/cases/secure_password_test.rb
Expand Up @@ -184,6 +184,20 @@ class SecurePasswordTest < ActiveModel::TestCase
assert_nil @existing_user.password_digest
end

test "override secure password attribute" do
assert_nil @user.password_called

@user.password = "secret"

assert_equal "secret", @user.password
assert_equal 1, @user.password_called

@user.password = "terces"

assert_equal "terces", @user.password
assert_equal 2, @user.password_called
end

test "authenticate" do
@user.password = "secret"
@user.recovery_password = "42password"
Expand Down
7 changes: 7 additions & 0 deletions activemodel/test/models/user.rb
Expand Up @@ -10,4 +10,11 @@ class User
has_secure_password :recovery_password, validations: false

attr_accessor :password_digest, :recovery_password_digest
attr_accessor :password_called

def password=(unencrypted_password)
self.password_called ||= 0
self.password_called += 1
super
end
end

0 comments on commit 50fba82

Please sign in to comment.