mirror of
https://github.com/heartcombo/devise.git
synced 2022-11-09 12:18:31 -05:00
Add lock_strategy.
This commit is contained in:
parent
597a930c74
commit
6cc32db2dd
7 changed files with 61 additions and 22 deletions
|
@ -14,8 +14,10 @@
|
||||||
* E-mails asks headers_for in the model to set the proper headers.
|
* E-mails asks headers_for in the model to set the proper headers.
|
||||||
* Allow to specify haml in devise_views.
|
* Allow to specify haml in devise_views.
|
||||||
* Compatibility with Datamapper and Mongoid.
|
* Compatibility with Datamapper and Mongoid.
|
||||||
* Allow :unlock_strategy to be :none.
|
|
||||||
* Make config.devise available on config/application.rb.
|
* Make config.devise available on config/application.rb.
|
||||||
|
* Allow :unlock_strategy to be :none and add :lock_strategy which can be :failed_attempts or
|
||||||
|
none. Setting those values to :none means that you want to handle lock and unlocking by
|
||||||
|
yourself.
|
||||||
|
|
||||||
* bug fix
|
* bug fix
|
||||||
* Do not allow unlockable strategies based on time to access a controller.
|
* Do not allow unlockable strategies based on time to access a controller.
|
||||||
|
|
|
@ -9,9 +9,8 @@ Devise is a flexible authentication solution for Rails based on Warden. It:
|
||||||
|
|
||||||
Right now it's composed of 12 modules:
|
Right now it's composed of 12 modules:
|
||||||
|
|
||||||
* Authenticatable: encrypts a password and validates the authenticity of a user while signing in.
|
* Database Authenticatable: encrypts and stores a password in the database to validate the authenticity of an user while signing in.
|
||||||
* Token Authenticatable: validates the authenticity of a user while signing in using an authentication token (also known as "single access token").
|
* Token Authenticatable: validates the authenticity of a user while signing in using an authentication token (also known as "single access token").
|
||||||
* HttpAuthenticatable: sign in users using basic HTTP authentication.
|
|
||||||
* Confirmable: sends emails with confirmation instructions and verifies whether an account is already confirmed during sign in.
|
* Confirmable: sends emails with confirmation instructions and verifies whether an account is already confirmed during sign in.
|
||||||
* Recoverable: resets the user password and sends reset instructions.
|
* Recoverable: resets the user password and sends reset instructions.
|
||||||
* Registerable: handles signing up users through a registration process.
|
* Registerable: handles signing up users through a registration process.
|
||||||
|
@ -22,6 +21,8 @@ Right now it's composed of 12 modules:
|
||||||
* Lockable: locks an account after a specified number of failed sign-in attempts. Can unlock via email or after a specified time period.
|
* Lockable: locks an account after a specified number of failed sign-in attempts. Can unlock via email or after a specified time period.
|
||||||
* Activatable: use this module if you need to activate accounts by means other than confirmation.
|
* Activatable: use this module if you need to activate accounts by means other than confirmation.
|
||||||
|
|
||||||
|
Additionaly, Devise has several extensions (listed at the end of this README) and has http authentication built in.
|
||||||
|
|
||||||
== Examples
|
== Examples
|
||||||
|
|
||||||
* Example application using Devise at http://github.com/plataformatec/devise_example
|
* Example application using Devise at http://github.com/plataformatec/devise_example
|
||||||
|
@ -266,7 +267,7 @@ Please refer to TODO file.
|
||||||
|
|
||||||
We have a long list of valued contributors. See the CHANGELOG or do `git shortlog -s -n` in the cloned repository.
|
We have a long list of valued contributors. See the CHANGELOG or do `git shortlog -s -n` in the cloned repository.
|
||||||
|
|
||||||
== Related Applications
|
== Devise extensions
|
||||||
|
|
||||||
* http://github.com/scambra/devise_invitable adds support to Devise for sending invitations by email.
|
* http://github.com/scambra/devise_invitable adds support to Devise for sending invitations by email.
|
||||||
|
|
||||||
|
|
|
@ -101,15 +101,20 @@ module Devise
|
||||||
mattr_accessor :scoped_views
|
mattr_accessor :scoped_views
|
||||||
@@scoped_views = false
|
@@scoped_views = false
|
||||||
|
|
||||||
# Number of authentication tries before locking an account
|
# Defines which strategy can be used to lock an account.
|
||||||
mattr_accessor :maximum_attempts
|
# Values: :failed_attempts, :none
|
||||||
@@maximum_attempts = 20
|
mattr_accessor :lock_strategy
|
||||||
|
@@lock_strategy = :failed_attempts
|
||||||
|
|
||||||
# Defines which strategy can be used to unlock an account.
|
# Defines which strategy can be used to unlock an account.
|
||||||
# Values: :email, :time, :both
|
# Values: :email, :time, :both
|
||||||
mattr_accessor :unlock_strategy
|
mattr_accessor :unlock_strategy
|
||||||
@@unlock_strategy = :both
|
@@unlock_strategy = :both
|
||||||
|
|
||||||
|
# Number of authentication tries before locking an account
|
||||||
|
mattr_accessor :maximum_attempts
|
||||||
|
@@maximum_attempts = 20
|
||||||
|
|
||||||
# Time interval to unlock the account if :time is defined as unlock_strategy.
|
# Time interval to unlock the account if :time is defined as unlock_strategy.
|
||||||
mattr_accessor :unlock_in
|
mattr_accessor :unlock_in
|
||||||
@@unlock_in = 1.hour
|
@@unlock_in = 1.hour
|
||||||
|
@ -131,7 +136,7 @@ module Devise
|
||||||
@@token_authentication_key = :auth_token
|
@@token_authentication_key = :auth_token
|
||||||
|
|
||||||
# Private methods to interface with Warden.
|
# Private methods to interface with Warden.
|
||||||
mattr_reader :warden_config
|
mattr_accessor :warden_config
|
||||||
@@warden_config = nil
|
@@warden_config = nil
|
||||||
@@warden_config_block = nil
|
@@warden_config_block = nil
|
||||||
|
|
||||||
|
|
|
@ -13,6 +13,7 @@ module Devise
|
||||||
# Configuration:
|
# Configuration:
|
||||||
#
|
#
|
||||||
# maximum_attempts: how many attempts should be accepted before blocking the user.
|
# maximum_attempts: how many attempts should be accepted before blocking the user.
|
||||||
|
# lock_strategy: lock the user account by :failed_attempts or :none.
|
||||||
# unlock_strategy: unlock the user account by :time, :email, :both or :none.
|
# unlock_strategy: unlock the user account by :time, :email, :both or :none.
|
||||||
# unlock_in: the time you want to lock the user after to lock happens. Only
|
# unlock_in: the time you want to lock the user after to lock happens. Only
|
||||||
# available when unlock_strategy is :time or :both.
|
# available when unlock_strategy is :time or :both.
|
||||||
|
@ -21,11 +22,13 @@ module Devise
|
||||||
extend ActiveSupport::Concern
|
extend ActiveSupport::Concern
|
||||||
include Devise::Models::Activatable
|
include Devise::Models::Activatable
|
||||||
|
|
||||||
|
delegate :lock_strategy_enabled?, :unlock_strategy_enabled?, :to => "self.class"
|
||||||
|
|
||||||
# Lock an user setting it's locked_at to actual time.
|
# Lock an user setting it's locked_at to actual time.
|
||||||
def lock_access!
|
def lock_access!
|
||||||
self.locked_at = Time.now
|
self.locked_at = Time.now
|
||||||
|
|
||||||
if self.class.unlock_strategy_enabled?(:email)
|
if unlock_strategy_enabled?(:email)
|
||||||
generate_unlock_token
|
generate_unlock_token
|
||||||
send_unlock_instructions
|
send_unlock_instructions
|
||||||
end
|
end
|
||||||
|
@ -37,8 +40,8 @@ module Devise
|
||||||
def unlock_access!
|
def unlock_access!
|
||||||
if_access_locked do
|
if_access_locked do
|
||||||
self.locked_at = nil
|
self.locked_at = nil
|
||||||
self.failed_attempts = 0 if self.respond_to?(:failed_attempts=)
|
self.failed_attempts = 0 if respond_to?(:failed_attempts=)
|
||||||
self.unlock_token = nil if self.respond_to?(:unlock_token=)
|
self.unlock_token = nil if respond_to?(:unlock_token=)
|
||||||
save(:validate => false)
|
save(:validate => false)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -75,7 +78,8 @@ module Devise
|
||||||
# is locked, it should never be allowed.
|
# is locked, it should never be allowed.
|
||||||
def valid_for_authentication?
|
def valid_for_authentication?
|
||||||
return :locked if access_locked?
|
return :locked if access_locked?
|
||||||
return super if !persisted? || self.class.unlock_strategy == :none
|
return super unless persisted?
|
||||||
|
return super unless lock_strategy_enabled?(:failed_attempts)
|
||||||
|
|
||||||
if result = super
|
if result = super
|
||||||
self.failed_attempts = 0
|
self.failed_attempts = 0
|
||||||
|
@ -105,7 +109,7 @@ module Devise
|
||||||
|
|
||||||
# Tells if the lock is expired if :time unlock strategy is active
|
# Tells if the lock is expired if :time unlock strategy is active
|
||||||
def lock_expired?
|
def lock_expired?
|
||||||
if self.class.unlock_strategy_enabled?(:time)
|
if unlock_strategy_enabled?(:time)
|
||||||
locked_at && locked_at < self.class.unlock_in.ago
|
locked_at && locked_at < self.class.unlock_in.ago
|
||||||
else
|
else
|
||||||
false
|
false
|
||||||
|
@ -149,11 +153,16 @@ module Devise
|
||||||
[:both, strategy].include?(self.unlock_strategy)
|
[:both, strategy].include?(self.unlock_strategy)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Is the lock enabled for the given lock strategy?
|
||||||
|
def lock_strategy_enabled?(strategy)
|
||||||
|
self.lock_strategy == strategy
|
||||||
|
end
|
||||||
|
|
||||||
def unlock_token
|
def unlock_token
|
||||||
Devise.friendly_token
|
Devise.friendly_token
|
||||||
end
|
end
|
||||||
|
|
||||||
Devise::Models.config(self, :maximum_attempts, :unlock_strategy, :unlock_in)
|
Devise::Models.config(self, :maximum_attempts, :lock_strategy, :unlock_strategy, :unlock_in)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -59,16 +59,22 @@ module Devise
|
||||||
apply_schema :last_sign_in_ip, String
|
apply_schema :last_sign_in_ip, String
|
||||||
end
|
end
|
||||||
|
|
||||||
# Creates failed_attempts, unlock_token and locked_at.
|
# Creates failed_attempts, unlock_token and locked_at depending on the options given.
|
||||||
#
|
#
|
||||||
# == Options
|
# == Options
|
||||||
# * :unlock_strategy - The strategy used for unlock. Can be :time, :email, :both, :none.
|
# * :unlock_strategy - The strategy used for unlock. Can be :time, :email, :both (default), :none.
|
||||||
# If :email or :both, creates a unlock_token field.
|
# If :email or :both, creates a unlock_token field.
|
||||||
|
# * :lock_strategy - The strategy used for locking. Can be :failed_attempts (default) or :none.
|
||||||
def lockable(options={})
|
def lockable(options={})
|
||||||
unlock_strategy = options[:unlock_strategy] ||
|
unlock_strategy = options[:unlock_strategy]
|
||||||
(respond_to?(:unlock_strategy) ? self.unlock_strategy : :both)
|
unlock_strategy ||= self.unlock_strategy if respond_to?(:unlock_strategy)
|
||||||
|
unlock_strategy ||= :both
|
||||||
|
|
||||||
unless unlock_strategy == :none
|
lock_strategy = options[:lock_strategy]
|
||||||
|
lock_strategy ||= self.lock_strategy if respond_to?(:lock_strategy)
|
||||||
|
lock_strategy ||= :failed_attempts
|
||||||
|
|
||||||
|
if lock_strategy == :failed_attempts
|
||||||
apply_schema :failed_attempts, Integer, :default => 0
|
apply_schema :failed_attempts, Integer, :default => 0
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -55,15 +55,22 @@ Devise.setup do |config|
|
||||||
# config.timeout_in = 10.minutes
|
# config.timeout_in = 10.minutes
|
||||||
|
|
||||||
# ==> Configuration for :lockable
|
# ==> Configuration for :lockable
|
||||||
# Number of authentication tries before locking an account.
|
# Defines which strategy will be used to lock an account.
|
||||||
# config.maximum_attempts = 20
|
# :failed_attempts = Locks an account after a number of failed attempts to sign in.
|
||||||
|
# :none = No lock strategy. You should handle locking by yourself.
|
||||||
|
# config.lock_strategy = :failed_attempts
|
||||||
|
|
||||||
# Defines which strategy will be used to unlock an account.
|
# Defines which strategy will be used to unlock an account.
|
||||||
# :email = Sends an unlock link to the user email
|
# :email = Sends an unlock link to the user email
|
||||||
# :time = Reanables login after a certain ammount of time (see :unlock_in below)
|
# :time = Reanables login after a certain ammount of time (see :unlock_in below)
|
||||||
# :both = enables both strategies
|
# :both = Enables both strategies
|
||||||
|
# :none = No unlock strategy. You should handle unlocking by yourself.
|
||||||
# config.unlock_strategy = :both
|
# config.unlock_strategy = :both
|
||||||
|
|
||||||
|
# Number of authentication tries before locking an account if lock_strategy
|
||||||
|
# is failed attempts.
|
||||||
|
# config.maximum_attempts = 20
|
||||||
|
|
||||||
# Time interval to unlock the account if :time is enabled as unlock_strategy.
|
# Time interval to unlock the account if :time is enabled as unlock_strategy.
|
||||||
# config.unlock_in = 1.hour
|
# config.unlock_in = 1.hour
|
||||||
|
|
||||||
|
|
|
@ -21,6 +21,15 @@ class LockableTest < ActiveSupport::TestCase
|
||||||
assert_equal 0, user.reload.failed_attempts
|
assert_equal 0, user.reload.failed_attempts
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "should not touch failed_attempts if lock_strategy is none" do
|
||||||
|
user = create_user
|
||||||
|
swap Devise, :lock_strategy => :none, :maximum_attempts => 2 do
|
||||||
|
3.times { user.valid_for_authentication?{ false } }
|
||||||
|
assert !user.access_locked?
|
||||||
|
assert_equal 0, user.failed_attempts
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
test 'should be valid for authentication with a unlocked user' do
|
test 'should be valid for authentication with a unlocked user' do
|
||||||
user = create_user
|
user = create_user
|
||||||
user.lock_access!
|
user.lock_access!
|
||||||
|
|
Loading…
Reference in a new issue