Adding pepper and stretches configuration per model, and globaly setup through Devise.pepper and Devise.stretches

This commit is contained in:
Carlos A. da Silva
2009-10-20 11:08:40 -02:00
parent 4d45672298
commit 342f948bc8
9 changed files with 169 additions and 61 deletions

View File

@@ -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
View File

@@ -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

View File

@@ -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'

View File

@@ -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

View File

@@ -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

View File

@@ -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.

View File

@@ -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

View File

@@ -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

View File

@@ -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