diff --git a/lib/devise.rb b/lib/devise.rb index 028eb9a8..290fd78a 100644 --- a/lib/devise.rb +++ b/lib/devise.rb @@ -14,6 +14,7 @@ module Devise module Controllers autoload :Helpers, 'devise/controllers/helpers' autoload :InternalHelpers, 'devise/controllers/internal_helpers' + autoload :Rememberable, 'devise/controllers/rememberable' autoload :ScopedViews, 'devise/controllers/scoped_views' autoload :UrlHelpers, 'devise/controllers/url_helpers' end diff --git a/lib/devise/controllers/rememberable.rb b/lib/devise/controllers/rememberable.rb new file mode 100644 index 00000000..44415a3d --- /dev/null +++ b/lib/devise/controllers/rememberable.rb @@ -0,0 +1,52 @@ +module Devise + module Controllers + # A module that may be optionally included in a controller in order + # to provide remember me behavior. + module Rememberable + # Return default cookie values retrieved from session options. + def self.cookie_values + Rails.configuration.session_options.slice(:path, :domain, :secure) + end + + # A small warden proxy so we can remember and forget uses from hooks. + class Proxy #:nodoc: + include Devise::Controllers::Rememberable + + delegate :cookies, :env, :to => :@warden + + def initialize(warden) + @warden = warden + end + end + + # Remembers the given resource by setting up a cookie + def remember_me(resource) + scope = Devise::Mapping.find_scope!(resource) + resource.remember_me!(resource.extend_remember_period) + cookies.signed["remember_#{scope}_token"] = remember_cookie_values(resource) + end + + # Forgets the given resource by deleting a cookie + def forget_me(resource) + scope = Devise::Mapping.find_scope!(resource) + resource.forget_me! unless resource.frozen? + cookies.delete("remember_#{scope}_token", forget_cookie_values(resource)) + end + + protected + + def forget_cookie_values(resource) + Devise::Controllers::Rememberable.cookie_values.merge!(resource.cookie_options) + end + + def remember_cookie_values(resource) + options = { :httponly => true } + options.merge!(forget_cookie_values(resource)) + options.merge!( + :value => resource.class.serialize_into_cookie(resource), + :expires => resource.remember_expires_at + ) + end + end + end +end \ No newline at end of file diff --git a/lib/devise/hooks/forgetable.rb b/lib/devise/hooks/forgetable.rb index 9d9a2699..3678bcb7 100644 --- a/lib/devise/hooks/forgetable.rb +++ b/lib/devise/hooks/forgetable.rb @@ -4,9 +4,6 @@ # This avoids forgetting deleted users. Warden::Manager.before_logout do |record, warden, options| if record.respond_to?(:forget_me!) - record.forget_me! unless record.frozen? - cookie_options = Rails.configuration.session_options.slice(:path, :domain, :secure) - cookie_options.merge!(record.cookie_options) - warden.cookies.delete("remember_#{options[:scope]}_token", cookie_options) + Devise::Controllers::Rememberable::Proxy.new(warden).forget_me(record) end end diff --git a/lib/devise/hooks/rememberable.rb b/lib/devise/hooks/rememberable.rb index 322e1359..4d718264 100644 --- a/lib/devise/hooks/rememberable.rb +++ b/lib/devise/hooks/rememberable.rb @@ -1,48 +1,6 @@ -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 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 - - if succeeded? && resource.respond_to?(:remember_me!) && remember_me? - resource.remember_me!(extend_remember_period?) - cookies.signed["remember_#{scope}_token"] = cookie_values(resource) - end - end - - protected - - def cookie_values(resource) - options = Rails.configuration.session_options.slice(:path, :domain, :secure) - options[:httponly] = true - - options.merge!(resource.cookie_options) - options.merge!( - :value => resource.class.serialize_into_cookie(resource), - :expires => resource.remember_expires_at - ) - - options - end - - def succeeded? - @result == :success - end - - def extend_remember_period? - false - end - - def remember_me? - valid_params? && Devise::TRUE_VALUES.include?(params_auth_hash[:remember_me]) - end - end +Warden::Manager.after_set_user :except => :fetch do |record, warden, options| + scope = options[:scope] + if record.respond_to?(:remember_me) && record.remember_me && warden.authenticated?(scope) + Devise::Controllers::Rememberable::Proxy.new(warden).remember_me(record) end -end - -Devise::Strategies::Authenticatable.send :include, Devise::Hooks::Rememberable - +end \ No newline at end of file diff --git a/lib/devise/models/rememberable.rb b/lib/devise/models/rememberable.rb index bd7f1a23..0cd8f27f 100644 --- a/lib/devise/models/rememberable.rb +++ b/lib/devise/models/rememberable.rb @@ -44,10 +44,7 @@ module Devise module Rememberable extend ActiveSupport::Concern - included do - # Remember me option available in after_authentication hook. - attr_accessor :remember_me - end + attr_accessor :remember_me, :extend_remember_period # Generate a new remember token and save the record without validations # unless remember_across_browsers is true and the user already has a valid token. @@ -60,7 +57,7 @@ module Devise # Removes the remember token only if it exists, and save the record # without validations. def forget_me! - self.remember_token = nil if respond_to?(:remember_token) + self.remember_token = nil if respond_to?(:remember_token=) self.remember_created_at = nil save(:validate => false) end diff --git a/lib/devise/strategies/authenticatable.rb b/lib/devise/strategies/authenticatable.rb index 02573786..0ec708f0 100644 --- a/lib/devise/strategies/authenticatable.rb +++ b/lib/devise/strategies/authenticatable.rb @@ -19,13 +19,26 @@ module Devise result = resource && resource.valid_for_authentication?(&block) case result - when Symbol, String + when String, Symbol fail!(result) + when TrueClass + decorate(resource) + true else result end end + # Get values from params and set in the resource. + def decorate(resource) + resource.remember_me = remember_me? if resource.respond_to?(:remember_me=) + end + + # Should this resource be marked to be remembered? + def remember_me? + valid_params? && Devise::TRUE_VALUES.include?(params_auth_hash[:remember_me]) + end + # Check if this is strategy is valid for http authentication by: # # * Validating if the model allows params authentication; diff --git a/lib/devise/strategies/rememberable.rb b/lib/devise/strategies/rememberable.rb index 59184bee..d9e87da9 100644 --- a/lib/devise/strategies/rememberable.rb +++ b/lib/devise/strategies/rememberable.rb @@ -28,6 +28,11 @@ module Devise private + def decorate(resource) + super + resource.extend_remember_period = mapping.to.extend_remember_period if resource.respond_to?(:extend_remember_period=) + end + def remember_me? true end @@ -36,10 +41,6 @@ module Devise "remember_#{scope}_token" end - def extend_remember_period? - mapping.to.extend_remember_period - end - # Accessor for remember cookie def remember_cookie @remember_cookie ||= cookies.signed[remember_key] diff --git a/test/integration/rememberable_test.rb b/test/integration/rememberable_test.rb index 0fac2a59..b8039ef9 100644 --- a/test/integration/rememberable_test.rb +++ b/test/integration/rememberable_test.rb @@ -161,7 +161,6 @@ class RememberMeTest < ActionController::IntegrationTest get users_path assert_not warden.authenticated?(:user) - assert_nil warden.cookies['remember_user_token'] end test 'do not remember the admin anymore after forget' do @@ -171,11 +170,11 @@ class RememberMeTest < ActionController::IntegrationTest get destroy_admin_session_path assert_not warden.authenticated?(:admin) + assert_nil admin.reload.remember_token assert_nil warden.cookies['remember_admin_token'] get root_path assert_not warden.authenticated?(:admin) - assert_nil warden.cookies['remember_admin_token'] end test 'changing user password expires remember me token' do