Skip to content

Commit

Permalink
:activatable is included by default in your models. If you are buildi…
Browse files Browse the repository at this point in the history
…ng a strategy for devise, you now need to call validate(resource), since Devise has now a default API to validate resources before and after signing them in. You can still use other Warden::Strategies with Devise, but they won't work with a few modules like unlockable (they never did, but now we have a single point to make it work).
  • Loading branch information
josevalim committed Apr 6, 2010
1 parent dbe116c commit c07b5ae
Show file tree
Hide file tree
Showing 20 changed files with 95 additions and 91 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.rdoc
Expand Up @@ -17,6 +17,7 @@
* TokenAuthenticatable now works with HTTP Basic Auth.
* 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.
* No need to append ?unauthenticated=true in URLs anymore since Flash was moved to a middleware in Rails 3.
* :activatable is included by default in your models.

* deprecations
* Rails 3 compatible only.
Expand Down
1 change: 0 additions & 1 deletion README.rdoc
Expand Up @@ -19,7 +19,6 @@ Right now it's composed of 11 modules:
* Timeoutable: expires sessions that have no activity in a specified period of time.
* Validatable: provides validations of email and password. It's optional and can be customized, so you're able to define your own validations.
* 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.

== Examples

Expand Down
1 change: 0 additions & 1 deletion TODO
@@ -1,3 +1,2 @@
* Extract Activatable tests from Confirmable
* Move integration tests to Capybara
* Better ORM integration
21 changes: 1 addition & 20 deletions lib/devise/hooks/activatable.rb
Expand Up @@ -5,23 +5,4 @@
warden.logout(scope)
throw :warden, :scope => scope, :message => record.inactive_message
end
end

module Devise
module Hooks
# Overwrite Devise base strategy to only authenticate an user if it's active.
# If you have an strategy that does not use Devise::Strategy::Base, don't worry
# because the hook above will still avoid it to authenticate.
module Activatable
def success!(resource)
if resource.respond_to?(:active?) && !resource.active?
fail!(resource.inactive_message)
else
super
end
end
end
end
end

Devise::Strategies::Base.send :include, Devise::Hooks::Activatable
end
10 changes: 10 additions & 0 deletions lib/devise/hooks/forgetable.rb
@@ -0,0 +1,10 @@
# Before logout hook to forget the user in the given scope, if it responds
# to forget_me! Also clear remember token to ensure the user won't be
# remembered again. Notice that we forget the user unless the record is frozen.
# This avoids forgetting deleted users.
Warden::Manager.before_logout do |record, warden, scope|
if record.respond_to?(:forget_me!)
record.forget_me! unless record.frozen?
warden.cookies.delete "remember_#{scope}_token"
end
end
18 changes: 4 additions & 14 deletions lib/devise/hooks/rememberable.rb
@@ -1,19 +1,9 @@
# Before logout hook to forget the user in the given scope, if it responds
# to forget_me! Also clear remember token to ensure the user won't be
# remembered again. Notice that we forget the user unless the record is frozen.
# This avoids forgetting deleted users.
Warden::Manager.before_logout do |record, warden, scope|
if record.respond_to?(:forget_me!)
record.forget_me! unless record.frozen?
warden.cookies.delete "remember_#{scope}_token"
end
end

module Devise
module Hooks
# Overwrite success! in authentication strategies allowing users to be remembered.
# We choose to implement this as an strategy hook instead of a Devise hook to avoid users
# giving a remember_me access in strategies that should not create remember me tokens.
# We choose to implement this as an strategy hook instead of a warden hook to allow a specific
# strategy (like token authenticatable or facebook authenticatable) to turn off remember_me?
# cookies.
module Rememberable #:nodoc:
def success!(resource)
super
Expand All @@ -29,7 +19,7 @@ def success!(resource)
end
end

protected
protected

def remember_me?
valid_params? && Devise::TRUE_VALUES.include?(params_auth_hash[:remember_me])
Expand Down
8 changes: 7 additions & 1 deletion lib/devise/models.rb
@@ -1,3 +1,5 @@
require 'devise/models/authenticatable'

module Devise
module Models
# Creates configuration values for Devise and for the given module.
Expand Down Expand Up @@ -45,14 +47,18 @@ def #{accessor}=(value)
# for a complete description on those values.
#
def devise(*modules)
raise "You need to give at least one Devise module" if modules.empty?
include Devise::Models::Authenticatable
options = modules.extract_options!

if modules.delete(:authenticatable)
ActiveSupport::Deprecation.warn ":authenticatable as module is deprecated. Please give :database_authenticatable instead.", caller
modules << :database_authenticatable
end

if modules.delete(:activatable)
ActiveSupport::Deprecation.warn ":activatable as module is deprecated. It's included in your model by default.", caller
end

if modules.delete(:http_authenticatable)
ActiveSupport::Deprecation.warn ":http_authenticatable as module is deprecated and is on by default. Revert by setting :http_authenticatable => false.", caller
end
Expand Down
16 changes: 0 additions & 16 deletions lib/devise/models/activatable.rb

This file was deleted.

44 changes: 40 additions & 4 deletions lib/devise/models/authenticatable.rb
@@ -1,8 +1,10 @@
require 'devise/hooks/activatable'

module Devise
module Models
# Authenticable module. Holds common settings for authentication.
#
# Configuration:
# == Configuration:
#
# You can overwrite configuration values by setting in globally in Devise,
# using devise method or overwriting the respective instance method.
Expand All @@ -15,13 +17,47 @@ module Models
# params_authenticatable: if this model allows authentication through request params. By default true.
# It also accepts an array specifying the strategies that should allow params authentication.
#
# == Active?
#
# Before authenticating an user and in each request, Devise checks if your model is active by
# calling model.active?. This method is overwriten by other devise modules. For instance,
# :confirmable overwrites .active? to only return true if your model was confirmed.
#
# You overwrite this method yourself, but if you do, don't forget to call super:
#
# def active?
# super && special_condition_is_valid?
# end
#
# Whenever active? returns false, Devise asks the reason why your model is inactive using
# the inactive_message method. You can overwrite it as well:
#
# def inactive_message
# special_condition_is_valid? ? super : :special_condition_is_not_valid
# end
#
module Authenticatable
extend ActiveSupport::Concern

# Yields the given block. This method is overwritten by other modules to provide
# hooks around authentication.
# Check if the current object is valid for authentication. This method and find_for_authentication
# are the methods used in a Warden::Strategy to check if a model should be signed in or not.
#
# However, you should not need to overwrite this method, you should overwrite active? and
# inactive_message instead.
def valid_for_authentication?
yield
if active?
block_given? ? yield : true
else
inactive_message
end
end

def active?
true
end

def inactive_message
:inactive
end

module ClassMethods
Expand Down
4 changes: 0 additions & 4 deletions lib/devise/models/confirmable.rb
@@ -1,8 +1,5 @@
require 'devise/models/activatable'

module Devise
module Models

# Confirmable is responsible to verify if an account is already confirmed to
# sign in, and to send emails with confirmation instructions.
# Confirmation instructions are sent to the user email after creating a
Expand Down Expand Up @@ -30,7 +27,6 @@ module Models
# User.find(1).resend_confirmation! # generates a new token and resent it
module Confirmable
extend ActiveSupport::Concern
include Devise::Models::Activatable

included do
before_create :generate_confirmation_token, :if => :confirmation_required?
Expand Down
4 changes: 1 addition & 3 deletions lib/devise/models/database_authenticatable.rb
@@ -1,4 +1,3 @@
require 'devise/models/authenticatable'
require 'devise/strategies/database_authenticatable'

module Devise
Expand All @@ -25,8 +24,7 @@ module Models
# User.find(1).valid_password?('password123') # returns true/false
#
module DatabaseAuthenticatable
extend ActiveSupport::Concern
include Devise::Models::Authenticatable
extend ActiveSupport::Concern

included do
attr_reader :password, :current_password
Expand Down
16 changes: 6 additions & 10 deletions lib/devise/models/lockable.rb
@@ -1,8 +1,5 @@
require 'devise/models/activatable'

module Devise
module Models

# Handles blocking a user access after a certain number of attempts.
# Lockable accepts two different strategies to unlock a user after it's
# blocked: email and time. The former will send an email to the user when
Expand All @@ -20,7 +17,6 @@ module Models
#
module Lockable
extend ActiveSupport::Concern
include Devise::Models::Activatable

delegate :lock_strategy_enabled?, :unlock_strategy_enabled?, :to => "self.class"

Expand Down Expand Up @@ -77,15 +73,15 @@ def inactive_message
# for verifying whether an user is allowed to sign in or not. If the user
# is locked, it should never be allowed.
def valid_for_authentication?
return :locked if access_locked?
return super unless persisted?
return super unless lock_strategy_enabled?(:failed_attempts)
return super unless persisted? && lock_strategy_enabled?(:failed_attempts)

if result = super
case (result = super)
when Symbol
return result
when TrueClass
self.failed_attempts = 0
else
when FalseClass
self.failed_attempts += 1

if attempts_exceeded?
lock_access!
return :locked
Expand Down
1 change: 1 addition & 0 deletions lib/devise/models/rememberable.rb
@@ -1,5 +1,6 @@
require 'devise/strategies/rememberable'
require 'devise/hooks/rememberable'
require 'devise/hooks/forgetable'

module Devise
module Models
Expand Down
3 changes: 1 addition & 2 deletions lib/devise/models/token_authenticatable.rb
Expand Up @@ -16,8 +16,7 @@ module Models
# +token_authentication_key+ - Defines name of the authentication token params key. E.g. /users/sign_in?some_key=...
#
module TokenAuthenticatable
extend ActiveSupport::Concern
include Devise::Models::Authenticatable
extend ActiveSupport::Concern

# Generate new authentication token (a.k.a. "single access token").
def reset_authentication_token
Expand Down
1 change: 0 additions & 1 deletion lib/devise/modules.rb
Expand Up @@ -14,7 +14,6 @@
d.add_module :validatable

# The ones which can sign out after
d.add_module :activatable
d.add_module :confirmable, :controller => :confirmations, :route => :confirmation
d.add_module :lockable, :controller => :unlocks, :route => :unlock
d.add_module :timeoutable
Expand Down
12 changes: 0 additions & 12 deletions lib/devise/strategies/authenticatable.rb
Expand Up @@ -14,18 +14,6 @@ def valid?

private

# Simply invokes valid_for_authentication? with the given block and deal with the result.
def validate(resource, &block)
result = resource && resource.valid_for_authentication?(&block)

case result
when Symbol, String
fail!(result)
else
result
end
end

# Check if this is strategy is valid for http authentication.
def valid_for_http_auth?
http_authenticatable? && request.authorization &&
Expand Down
14 changes: 14 additions & 0 deletions lib/devise/strategies/base.rb
Expand Up @@ -11,9 +11,23 @@ def mapping
end
end

protected

def succeeded?
@result == :success
end

# Simply invokes valid_for_authentication? with the given block and deal with the result.
def validate(resource, &block)
result = resource && resource.valid_for_authentication?(&block)

case result
when Symbol, String
fail!(result)
else
result
end
end
end
end
end
4 changes: 3 additions & 1 deletion lib/devise/strategies/rememberable.rb
Expand Up @@ -16,7 +16,9 @@ def valid?
# the record in the database. If the attempt fails, we pass to another
# strategy handle the authentication.
def authenticate!
if resource = mapping.to.serialize_from_cookie(*remember_cookie)
resource = mapping.to.serialize_from_cookie(*remember_cookie)

if validate(resource)
success!(resource)
else
cookies.delete(remember_key)
Expand Down
4 changes: 3 additions & 1 deletion lib/devise/strategies/token_authenticatable.rb
Expand Up @@ -11,7 +11,9 @@ module Strategies
# you can pass anything and it will simply be ignored.
class TokenAuthenticatable < Authenticatable
def authenticate!
if resource = mapping.to.find_for_token_authentication(authentication_hash)
resource = mapping.to.find_for_token_authentication(authentication_hash)

if validate(resource)
resource.after_token_authentication
success!(resource)
else
Expand Down
3 changes: 3 additions & 0 deletions test/models/lockable_test.rb
Expand Up @@ -7,6 +7,7 @@ def setup

test "should respect maximum attempts configuration" do
user = create_user
user.confirm!
swap Devise, :maximum_attempts => 2 do
3.times { user.valid_for_authentication?{ false } }
assert user.reload.access_locked?
Expand All @@ -15,6 +16,7 @@ def setup

test "should clear failed_attempts on successfull validation" do
user = create_user
user.confirm!
user.valid_for_authentication?{ false }
assert_equal 1, user.reload.failed_attempts
user.valid_for_authentication?{ true }
Expand All @@ -23,6 +25,7 @@ def setup

test "should not touch failed_attempts if lock_strategy is none" do
user = create_user
user.confirm!
swap Devise, :lock_strategy => :none, :maximum_attempts => 2 do
3.times { user.valid_for_authentication?{ false } }
assert !user.access_locked?
Expand Down

0 comments on commit c07b5ae

Please sign in to comment.