diff --git a/CHANGELOG.rdoc b/CHANGELOG.rdoc index 4b5a89d862..59ea9f0a95 100644 --- a/CHANGELOG.rdoc +++ b/CHANGELOG.rdoc @@ -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. diff --git a/README.rdoc b/README.rdoc index a2e37af213..20cc42573a 100644 --- a/README.rdoc +++ b/README.rdoc @@ -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 diff --git a/TODO b/TODO index 6019b88bb3..2b8ebc9c8c 100644 --- a/TODO +++ b/TODO @@ -1,3 +1,2 @@ -* Extract Activatable tests from Confirmable * Move integration tests to Capybara * Better ORM integration diff --git a/lib/devise/hooks/activatable.rb b/lib/devise/hooks/activatable.rb index 5e3205232e..1d4f2378c5 100644 --- a/lib/devise/hooks/activatable.rb +++ b/lib/devise/hooks/activatable.rb @@ -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 \ No newline at end of file +end \ No newline at end of file diff --git a/lib/devise/hooks/forgetable.rb b/lib/devise/hooks/forgetable.rb new file mode 100644 index 0000000000..f51b53e83b --- /dev/null +++ b/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 \ No newline at end of file diff --git a/lib/devise/hooks/rememberable.rb b/lib/devise/hooks/rememberable.rb index c44103c874..47f59772f1 100644 --- a/lib/devise/hooks/rememberable.rb +++ b/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 @@ -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]) diff --git a/lib/devise/models.rb b/lib/devise/models.rb index 7ace598211..3b4d3ca2c2 100644 --- a/lib/devise/models.rb +++ b/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. @@ -45,7 +47,7 @@ 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) @@ -53,6 +55,10 @@ def devise(*modules) 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 diff --git a/lib/devise/models/activatable.rb b/lib/devise/models/activatable.rb deleted file mode 100644 index efc5040e48..0000000000 --- a/lib/devise/models/activatable.rb +++ /dev/null @@ -1,16 +0,0 @@ -require 'devise/hooks/activatable' - -module Devise - module Models - # This module implements the default API required in activatable hook. - module Activatable - def active? - true - end - - def inactive_message - :inactive - end - end - end -end \ No newline at end of file diff --git a/lib/devise/models/authenticatable.rb b/lib/devise/models/authenticatable.rb index 40985defe3..3c9a18e24a 100644 --- a/lib/devise/models/authenticatable.rb +++ b/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. @@ -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 diff --git a/lib/devise/models/confirmable.rb b/lib/devise/models/confirmable.rb index c4ad95efc7..4a1ebc0d28 100644 --- a/lib/devise/models/confirmable.rb +++ b/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 @@ -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? diff --git a/lib/devise/models/database_authenticatable.rb b/lib/devise/models/database_authenticatable.rb index 522c5717da..1fce973e23 100644 --- a/lib/devise/models/database_authenticatable.rb +++ b/lib/devise/models/database_authenticatable.rb @@ -1,4 +1,3 @@ -require 'devise/models/authenticatable' require 'devise/strategies/database_authenticatable' module Devise @@ -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 diff --git a/lib/devise/models/lockable.rb b/lib/devise/models/lockable.rb index e9e4462ec3..eac1a5a81e 100644 --- a/lib/devise/models/lockable.rb +++ b/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 @@ -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" @@ -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 diff --git a/lib/devise/models/rememberable.rb b/lib/devise/models/rememberable.rb index 737ab30629..1dd402ba13 100644 --- a/lib/devise/models/rememberable.rb +++ b/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 diff --git a/lib/devise/models/token_authenticatable.rb b/lib/devise/models/token_authenticatable.rb index c6e6824d8a..97f9c1b6df 100644 --- a/lib/devise/models/token_authenticatable.rb +++ b/lib/devise/models/token_authenticatable.rb @@ -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 diff --git a/lib/devise/modules.rb b/lib/devise/modules.rb index 25c9eb073d..9935ebd6d9 100644 --- a/lib/devise/modules.rb +++ b/lib/devise/modules.rb @@ -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 diff --git a/lib/devise/strategies/authenticatable.rb b/lib/devise/strategies/authenticatable.rb index 26a35d4a37..7b9a985bf8 100644 --- a/lib/devise/strategies/authenticatable.rb +++ b/lib/devise/strategies/authenticatable.rb @@ -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 && diff --git a/lib/devise/strategies/base.rb b/lib/devise/strategies/base.rb index d6cd9015a2..4b0be0d6c6 100644 --- a/lib/devise/strategies/base.rb +++ b/lib/devise/strategies/base.rb @@ -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 \ No newline at end of file diff --git a/lib/devise/strategies/rememberable.rb b/lib/devise/strategies/rememberable.rb index 9465f430d0..40db521db5 100644 --- a/lib/devise/strategies/rememberable.rb +++ b/lib/devise/strategies/rememberable.rb @@ -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) diff --git a/lib/devise/strategies/token_authenticatable.rb b/lib/devise/strategies/token_authenticatable.rb index 197c460c06..8041209881 100644 --- a/lib/devise/strategies/token_authenticatable.rb +++ b/lib/devise/strategies/token_authenticatable.rb @@ -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 diff --git a/test/models/lockable_test.rb b/test/models/lockable_test.rb index 26cbca1de5..17e239dad8 100644 --- a/test/models/lockable_test.rb +++ b/test/models/lockable_test.rb @@ -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? @@ -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 } @@ -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?