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.
* Allow to specify haml in devise_views.
* Compatibility with Datamapper and Mongoid.
* Allow :unlock_strategy to be :none.
* 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
* 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:
* 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").
* 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.
* Recoverable: resets the user password and sends reset instructions.
* 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.
* 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
* 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.
== Related Applications
== Devise extensions
* 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
@@scoped_views = false
# Number of authentication tries before locking an account
mattr_accessor :maximum_attempts
@@maximum_attempts = 20
# Defines which strategy can be used to lock an account.
# Values: :failed_attempts, :none
mattr_accessor :lock_strategy
@@lock_strategy = :failed_attempts
# Defines which strategy can be used to unlock an account.
# Values: :email, :time, :both
mattr_accessor :unlock_strategy
@@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.
mattr_accessor :unlock_in
@@unlock_in = 1.hour
@ -131,7 +136,7 @@ module Devise
@@token_authentication_key = :auth_token
# Private methods to interface with Warden.
mattr_reader :warden_config
mattr_accessor :warden_config
@@warden_config = nil
@@warden_config_block = nil

View file

@ -13,6 +13,7 @@ module Devise
# Configuration:
#
# 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_in: the time you want to lock the user after to lock happens. Only
# available when unlock_strategy is :time or :both.
@ -21,11 +22,13 @@ module Devise
extend ActiveSupport::Concern
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.
def lock_access!
self.locked_at = Time.now
if self.class.unlock_strategy_enabled?(:email)
if unlock_strategy_enabled?(:email)
generate_unlock_token
send_unlock_instructions
end
@ -37,8 +40,8 @@ module Devise
def unlock_access!
if_access_locked do
self.locked_at = nil
self.failed_attempts = 0 if self.respond_to?(:failed_attempts=)
self.unlock_token = nil if self.respond_to?(:unlock_token=)
self.failed_attempts = 0 if respond_to?(:failed_attempts=)
self.unlock_token = nil if respond_to?(:unlock_token=)
save(:validate => false)
end
end
@ -75,7 +78,8 @@ module Devise
# is locked, it should never be allowed.
def valid_for_authentication?
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
self.failed_attempts = 0
@ -105,7 +109,7 @@ module Devise
# Tells if the lock is expired if :time unlock strategy is active
def lock_expired?
if self.class.unlock_strategy_enabled?(:time)
if unlock_strategy_enabled?(:time)
locked_at && locked_at < self.class.unlock_in.ago
else
false
@ -149,11 +153,16 @@ module Devise
[:both, strategy].include?(self.unlock_strategy)
end
# Is the lock enabled for the given lock strategy?
def lock_strategy_enabled?(strategy)
self.lock_strategy == strategy
end
def unlock_token
Devise.friendly_token
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

View file

@ -59,16 +59,22 @@ module Devise
apply_schema :last_sign_in_ip, String
end
# Creates failed_attempts, unlock_token and locked_at.
# Creates failed_attempts, unlock_token and locked_at depending on the options given.
#
# == 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.
# * :lock_strategy - The strategy used for locking. Can be :failed_attempts (default) or :none.
def lockable(options={})
unlock_strategy = options[:unlock_strategy] ||
(respond_to?(:unlock_strategy) ? self.unlock_strategy : :both)
unlock_strategy = options[:unlock_strategy]
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
end

View file

@ -55,15 +55,22 @@ Devise.setup do |config|
# config.timeout_in = 10.minutes
# ==> Configuration for :lockable
# Number of authentication tries before locking an account.
# config.maximum_attempts = 20
# Defines which strategy will be used to lock an account.
# :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.
# :email = Sends an unlock link to the user email
# :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
# 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.
# config.unlock_in = 1.hour

View file

@ -21,6 +21,15 @@ class LockableTest < ActiveSupport::TestCase
assert_equal 0, user.reload.failed_attempts
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
user = create_user
user.lock_access!