1
0
Fork 0
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:
José Valim 2010-03-31 11:54:11 +02:00
parent 597a930c74
commit 6cc32db2dd
7 changed files with 61 additions and 22 deletions

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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