mirror of
https://github.com/heartcombo/devise.git
synced 2026-01-10 15:28:18 -05:00
Adding pepper and stretches configuration per model, and globaly setup through Devise.pepper and Devise.stretches
This commit is contained in:
10
README.rdoc
10
README.rdoc
@@ -90,6 +90,16 @@ This line adds devise authenticable automatically for you inside your User class
|
||||
|
||||
Note that validations aren't added by default, so you're able to customize it. In order to have automatic validations working just include :validatable.
|
||||
|
||||
In addition to :except, you can provide some options to devise call:
|
||||
|
||||
* pepper: setup a pepper to generate de encrypted password. By default no pepper is used:
|
||||
|
||||
devise :all, :pepper => 'my_pepper'
|
||||
|
||||
* stretches: configure how many times you want the password is reencrypted.
|
||||
|
||||
devise :all, :stretches => 20
|
||||
|
||||
The next step after setting up your model is to configure your routes for devise. You do this by opening up your config/routes.rb and adding:
|
||||
|
||||
map.devise_for :users
|
||||
|
||||
2
TODO
2
TODO
@@ -2,7 +2,6 @@
|
||||
* Add customizable time frame for confirmation and filters
|
||||
|
||||
* Create generators
|
||||
* Allow stretches and pepper per model
|
||||
* Use request_ip in session cookies
|
||||
* Session timeout
|
||||
|
||||
@@ -35,3 +34,4 @@
|
||||
|
||||
* Add remember me
|
||||
* Mailer subjects namespaced by model
|
||||
* Allow stretches and pepper per model
|
||||
|
||||
@@ -16,6 +16,16 @@ module Devise
|
||||
}.freeze
|
||||
|
||||
TRUE_VALUES = [true, 1, '1', 't', 'T', 'true', 'TRUE'].freeze
|
||||
|
||||
# Default pepper and stretches used in authenticable to create password hash.
|
||||
# You can setup it inside each model or globaly using this attributes.
|
||||
# Example:
|
||||
# Devise.pepper = 'my_pepper_123'
|
||||
# Devise.stretches = 20
|
||||
mattr_accessor :pepper, :stretches
|
||||
|
||||
# Default stretches configuration
|
||||
self.stretches = 10
|
||||
end
|
||||
|
||||
require 'devise/warden'
|
||||
|
||||
@@ -1,6 +1,23 @@
|
||||
module Devise
|
||||
module ActiveRecord
|
||||
# Shortcut method for including all devise modules inside your model
|
||||
# Shortcut method for including all devise modules inside your model.
|
||||
# You can give some extra options while declaring devise in your model:
|
||||
#
|
||||
# * except: let's you add all devise modules, except the ones you setup here:
|
||||
#
|
||||
# devise :all, :except => :rememberable
|
||||
#
|
||||
# * pepper: setup a pepper to generate de encrypted password. By default no
|
||||
# pepper is used:
|
||||
#
|
||||
# devise :all, :pepper => 'my_pepper'
|
||||
#
|
||||
# * stretches: configure how many times you want the password is reencrypted.
|
||||
#
|
||||
# devise :all, :stretches => 20
|
||||
#
|
||||
# You can refer to Authenticable for more information about writing your own
|
||||
# method to setup pepper and stretches
|
||||
#
|
||||
# Examples:
|
||||
#
|
||||
@@ -30,6 +47,7 @@ module Devise
|
||||
#
|
||||
def devise(*modules)
|
||||
options = modules.extract_options!
|
||||
options.assert_valid_keys(:except, :stretches, :pepper)
|
||||
|
||||
modules = Devise::ALL if modules.include?(:all)
|
||||
modules -= Array(options[:except]) if options.key?(:except)
|
||||
@@ -39,6 +57,13 @@ module Devise
|
||||
devise_modules << m.to_sym
|
||||
include Devise::Models.const_get(m.to_s.classify)
|
||||
end
|
||||
|
||||
if options.key?(:stretches) || options.key?(:pepper)
|
||||
class_eval <<-END_EVAL, __FILE__, __LINE__
|
||||
def stretches; #{options[:stretches]}; end if options.key?(:stretches)
|
||||
def pepper; '#{options[:pepper]}'; end if options.key?(:pepper)
|
||||
END_EVAL
|
||||
end
|
||||
end
|
||||
|
||||
# Stores all modules included inside the model, so we are able to verify
|
||||
|
||||
@@ -10,8 +10,10 @@ module Devise
|
||||
# pepper: encryption key used for creating encrypted password. Each time
|
||||
# password changes, it's gonna be encrypted again, and this key
|
||||
# is added to the password and salt to create a secure hash.
|
||||
# def pepper; '1234567890987654321'; end
|
||||
#
|
||||
# stretches: defines how many times the password will be encrypted.
|
||||
# def stretches; 20; end
|
||||
#
|
||||
# Examples:
|
||||
#
|
||||
@@ -20,12 +22,6 @@ module Devise
|
||||
module Authenticable
|
||||
mattr_accessor :pepper, :stretches
|
||||
|
||||
# Pepper for encrypting password
|
||||
self.pepper = '23c64df433d9b08e464db5c05d1e6202dd2823f0'
|
||||
|
||||
# Encrypt password as many times as possible
|
||||
self.stretches = 10
|
||||
|
||||
def self.included(base)
|
||||
base.class_eval do
|
||||
extend ClassMethods
|
||||
@@ -36,21 +32,41 @@ module Devise
|
||||
end
|
||||
end
|
||||
|
||||
# Regenerates password salt and encrypted password each time password is
|
||||
# setted.
|
||||
def password=(new_password)
|
||||
@password = new_password
|
||||
self.password_salt = friendly_token
|
||||
self.encrypted_password = password_digest(@password)
|
||||
end
|
||||
|
||||
# Verifies whether an incoming_password (ie from login) is the user password
|
||||
# Verifies whether an incoming_password (ie from login) is the user
|
||||
# password.
|
||||
def valid_password?(incoming_password)
|
||||
password_digest(incoming_password) == encrypted_password
|
||||
end
|
||||
|
||||
private
|
||||
protected
|
||||
|
||||
# Pepper for encrypting password. Fallback to default configuration if
|
||||
# no one exists for this specific model. Overwrite inside your model
|
||||
# to provide specific pepper configuration:
|
||||
#
|
||||
# def pepper; 'my_pepper_123'; end
|
||||
def pepper
|
||||
@pepper ||= Devise.pepper
|
||||
end
|
||||
|
||||
# Encrypt password as many times as possible. Fallback to default
|
||||
# configuration if no one exists for this specific model.
|
||||
#
|
||||
# def stretches; 20; end
|
||||
def stretches
|
||||
@stretches ||= Devise.stretches
|
||||
end
|
||||
|
||||
# Gererates a default password digest based on salt, pepper and the
|
||||
# incoming password
|
||||
# incoming password.
|
||||
def password_digest(password_to_digest)
|
||||
digest = pepper
|
||||
stretches.times { digest = secure_digest(password_salt, digest, password_to_digest, pepper) }
|
||||
@@ -64,7 +80,7 @@ module Devise
|
||||
::Digest::SHA1.hexdigest('--' << tokens.flatten.join('--') << '--')
|
||||
end
|
||||
|
||||
# Generate a friendly string randomically to be used as token
|
||||
# Generate a friendly string randomically to be used as token.
|
||||
def friendly_token
|
||||
ActiveSupport::SecureRandom.base64(15).tr('+/=', '-_ ').strip.delete("\n")
|
||||
end
|
||||
|
||||
@@ -56,7 +56,7 @@ module Devise
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
protected
|
||||
|
||||
# Remove confirmation date from the user, ensuring after a user update
|
||||
# it's email, it won't be able to sign in without confirming it.
|
||||
|
||||
@@ -23,7 +23,7 @@ module Devise
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
protected
|
||||
|
||||
# Checks whether a password is needed or not. For validations only.
|
||||
# Passwords are always required if it's a new record, or if the password
|
||||
|
||||
@@ -1,79 +1,96 @@
|
||||
require 'test/test_helper'
|
||||
|
||||
class Authenticable < ActiveRecord::Base
|
||||
class Authenticable < User
|
||||
devise
|
||||
end
|
||||
|
||||
class Confirmable < ActiveRecord::Base
|
||||
class Confirmable < User
|
||||
devise :confirmable
|
||||
end
|
||||
|
||||
class Recoverable < ActiveRecord::Base
|
||||
class Recoverable < User
|
||||
devise :recoverable
|
||||
end
|
||||
|
||||
class Validatable < ActiveRecord::Base
|
||||
class Rememberable < User
|
||||
devise :rememberable
|
||||
end
|
||||
|
||||
class Validatable < User
|
||||
devise :validatable
|
||||
end
|
||||
|
||||
class Devisable < ActiveRecord::Base
|
||||
class Devisable < User
|
||||
devise :all
|
||||
end
|
||||
|
||||
class Exceptable < User
|
||||
devise :all, :except => [:recoverable, :rememberable, :validatable]
|
||||
end
|
||||
|
||||
class Configurable < User
|
||||
devise :all, :stretches => 15, :pepper => 'abcdef'
|
||||
end
|
||||
|
||||
class ActiveRecordTest < ActiveSupport::TestCase
|
||||
|
||||
def include_authenticable_module?(mod)
|
||||
mod.devise_modules.include?(:authenticable) &&
|
||||
mod.included_modules.include?(Devise::Models::Authenticable)
|
||||
def include_module?(klass, mod)
|
||||
klass.devise_modules.include?(mod) &&
|
||||
klass.included_modules.include?(Devise::Models::const_get(mod.to_s.classify))
|
||||
end
|
||||
|
||||
def include_confirmable_module?(mod)
|
||||
mod.devise_modules.include?(:confirmable) &&
|
||||
mod.included_modules.include?(Devise::Models::Confirmable)
|
||||
def assert_include_modules(klass, *modules)
|
||||
modules.each do |mod|
|
||||
assert include_module?(klass, mod)
|
||||
end
|
||||
end
|
||||
|
||||
def include_recoverable_module?(mod)
|
||||
mod.devise_modules.include?(:recoverable) &&
|
||||
mod.included_modules.include?(Devise::Models::Recoverable)
|
||||
def assert_not_include_modules(klass, *modules)
|
||||
modules.each do |mod|
|
||||
assert_not include_module?(klass, mod)
|
||||
end
|
||||
end
|
||||
|
||||
def include_validatable_module?(mod)
|
||||
mod.devise_modules.include?(:validatable) &&
|
||||
mod.included_modules.include?(Devise::Models::Validatable)
|
||||
test 'include by default authenticable only' do
|
||||
assert_include_modules Authenticable, :authenticable
|
||||
assert_not_include_modules Authenticable, :confirmable, :recoverable, :rememberable, :validatable
|
||||
end
|
||||
|
||||
test 'acts as devisable should include by default authenticable only' do
|
||||
assert include_authenticable_module?(Authenticable)
|
||||
assert_not include_confirmable_module?(Authenticable)
|
||||
assert_not include_recoverable_module?(Authenticable)
|
||||
assert_not include_validatable_module?(Authenticable)
|
||||
test 'add confirmable module only' do
|
||||
assert_include_modules Confirmable, :authenticable, :confirmable
|
||||
assert_not_include_modules Confirmable, :recoverable, :rememberable, :validatable
|
||||
end
|
||||
|
||||
test 'acts as devisable should be able to add confirmable module only' do
|
||||
assert include_authenticable_module?(Confirmable)
|
||||
assert include_confirmable_module?(Confirmable)
|
||||
assert_not include_recoverable_module?(Confirmable)
|
||||
assert_not include_validatable_module?(Confirmable)
|
||||
test 'add recoverable module only' do
|
||||
assert_include_modules Recoverable, :authenticable, :recoverable
|
||||
assert_not_include_modules Recoverable, :confirmable, :rememberable, :validatable
|
||||
end
|
||||
|
||||
test 'acts as devisable should be able to add recoverable module only' do
|
||||
assert include_authenticable_module?(Recoverable)
|
||||
assert_not include_confirmable_module?(Recoverable)
|
||||
assert include_recoverable_module?(Recoverable)
|
||||
assert_not include_validatable_module?(Recoverable)
|
||||
test 'add rememberable module only' do
|
||||
assert_include_modules Rememberable, :authenticable, :rememberable
|
||||
assert_not_include_modules Rememberable, :confirmable, :recoverable, :validatable
|
||||
end
|
||||
|
||||
test 'acts as devisable should be able to add validatable module only' do
|
||||
assert include_authenticable_module?(Validatable)
|
||||
assert_not include_confirmable_module?(Validatable)
|
||||
assert_not include_recoverable_module?(Validatable)
|
||||
assert include_validatable_module?(Validatable)
|
||||
test 'add validatable module only' do
|
||||
assert_include_modules Validatable, :authenticable, :validatable
|
||||
assert_not_include_modules Validatable, :confirmable, :recoverable, :rememberable
|
||||
end
|
||||
|
||||
test 'acts as devisable should be able to add all modules' do
|
||||
assert include_authenticable_module?(Devisable)
|
||||
assert include_confirmable_module?(Devisable)
|
||||
assert include_recoverable_module?(Devisable)
|
||||
assert include_validatable_module?(Devisable)
|
||||
test 'add all modules' do
|
||||
assert_include_modules Devisable,
|
||||
:authenticable, :confirmable, :recoverable, :rememberable, :validatable
|
||||
end
|
||||
|
||||
test 'configure modules with except option' do
|
||||
assert_include_modules Exceptable, :authenticable, :confirmable
|
||||
assert_not_include_modules Exceptable, :recoverable, :rememberable, :validatable
|
||||
end
|
||||
|
||||
test 'set a default value for stretches' do
|
||||
assert_equal 15, Configurable.new.send(:stretches)
|
||||
end
|
||||
|
||||
test 'set a default value for pepper' do
|
||||
assert_equal 'abcdef', Configurable.new.send(:pepper)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -74,11 +74,41 @@ class AuthenticableTest < ActiveSupport::TestCase
|
||||
end
|
||||
|
||||
test 'should encrypt password using a sha1 hash' do
|
||||
Devise::Models::Authenticable.pepper = 'pepper'
|
||||
Devise::Models::Authenticable.stretches = 1
|
||||
user = create_user
|
||||
expected_password = ::Digest::SHA1.hexdigest("--#{user.password_salt}--pepper--123456--pepper--")
|
||||
assert_equal expected_password, user.encrypted_password
|
||||
user = new_user
|
||||
assert_equal encrypt_password(user), user.encrypted_password
|
||||
end
|
||||
|
||||
def encrypt_password(user, pepper=nil, stretches=1)
|
||||
user.instance_variable_set(:@stretches, stretches) if stretches
|
||||
user.password = '123456'
|
||||
::Digest::SHA1.hexdigest("--#{user.password_salt}--#{pepper}--123456--#{pepper}--")
|
||||
end
|
||||
|
||||
test 'should fallback to devise pepper default configuring' do
|
||||
begin
|
||||
Devise.pepper = ''
|
||||
user = new_user
|
||||
assert_equal encrypt_password(user), user.encrypted_password
|
||||
Devise.pepper = 'new_pepper'
|
||||
user = new_user
|
||||
assert_equal encrypt_password(user, 'new_pepper'), user.encrypted_password
|
||||
Devise.pepper = '123456'
|
||||
user = new_user
|
||||
assert_equal encrypt_password(user, '123456'), user.encrypted_password
|
||||
ensure
|
||||
Devise.pepper = nil
|
||||
end
|
||||
end
|
||||
|
||||
test 'should fallback to devise stretches default configuring' do
|
||||
begin
|
||||
default_stretches = Devise.stretches
|
||||
Devise.stretches = 1
|
||||
user = new_user
|
||||
assert_equal encrypt_password(user, nil, nil), user.encrypted_password
|
||||
ensure
|
||||
Devise.stretches = default_stretches
|
||||
end
|
||||
end
|
||||
|
||||
test 'should test for a valid password' do
|
||||
|
||||
Reference in New Issue
Block a user