mirror of
https://github.com/heartcombo/devise.git
synced 2026-01-09 14:58:05 -05:00
Creating rememberable module.
This commit is contained in:
@@ -7,6 +7,10 @@
|
||||
<p><%= f.label :password %></p>
|
||||
<p><%= f.password_field :password %></p>
|
||||
|
||||
<% if devise_mapping.rememberable? -%>
|
||||
<p><%= f.check_box :remember_me %> <%= f.label :remember_me %></p>
|
||||
<% end -%>
|
||||
|
||||
<p><%= f.submit "Sign in" %></p>
|
||||
<% end -%>
|
||||
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
begin
|
||||
require 'warden'
|
||||
rescue
|
||||
gem 'hassox-warden'
|
||||
require 'warden'
|
||||
end
|
||||
#begin
|
||||
# require 'warden'
|
||||
#rescue
|
||||
# gem 'hassox-warden'
|
||||
# require 'warden'
|
||||
#end
|
||||
|
||||
require File.join(File.dirname(__FILE__), '..', 'warden', 'lib', 'warden')
|
||||
|
||||
module Devise
|
||||
ALL = [:authenticable, :confirmable, :recoverable, :validatable].freeze
|
||||
ALL = [:authenticable, :confirmable, :recoverable, :rememberable, :validatable].freeze
|
||||
|
||||
# Maps controller names to devise modules
|
||||
CONTROLLERS = {
|
||||
@@ -17,7 +19,6 @@ module Devise
|
||||
end
|
||||
|
||||
require 'devise/warden'
|
||||
require 'devise/mapping'
|
||||
require 'devise/routes'
|
||||
|
||||
# Ensure to include Devise modules only after Rails initialization.
|
||||
|
||||
@@ -40,6 +40,7 @@ module Devise
|
||||
|
||||
# Sign out based on scope.
|
||||
def sign_out(scope, *args)
|
||||
warden.user(scope) # Without loading user here, before_logout hook is not called
|
||||
warden.raw_session.inspect # Without this inspect here. The session does not clear.
|
||||
warden.logout(scope, *args)
|
||||
end
|
||||
|
||||
27
lib/devise/hooks/rememberable.rb
Normal file
27
lib/devise/hooks/rememberable.rb
Normal file
@@ -0,0 +1,27 @@
|
||||
# After authenticate hook to verify if the user in the given scope asked to be
|
||||
# remembered while he does not sign out. Generates a new remember token for
|
||||
# that specific user and adds a cookie with this user info to sign in this user
|
||||
# automatically without asking for credentials. Refer to rememberable strategy
|
||||
# for more info.
|
||||
Warden::Manager.after_authentication do |record, auth, options|
|
||||
scope = options[:scope]
|
||||
remember_me = auth.params[scope].try(:fetch, :remember_me, nil)
|
||||
remember_me = remember_me == '1' || remember_me == 'true'
|
||||
mapping = Devise.mappings[scope]
|
||||
if remember_me && mapping.present? && mapping.rememberable?
|
||||
record.remember_me!
|
||||
auth.cookies['remember_token'] = record.class.serialize_into_cookie(record)
|
||||
end
|
||||
end
|
||||
|
||||
# Before logout hook to forget the user in the given scope, only if rememberable
|
||||
# is activated for this scope. Also clear remember token to ensure the user
|
||||
# won't be remembered again.
|
||||
# TODO: verify warden to call before_logout when @users are not loaded yet.
|
||||
Warden::Manager.before_logout do |record, auth, scope|
|
||||
mapping = Devise.mappings[scope]
|
||||
if mapping.present? && mapping.rememberable?
|
||||
record.forget_me!
|
||||
auth.cookies['remember_token'] = nil
|
||||
end
|
||||
end
|
||||
@@ -9,7 +9,7 @@ module Devise
|
||||
# map.devise_for :users
|
||||
# mapping = Devise.mappings[:user]
|
||||
#
|
||||
# mapping.name #=> :user
|
||||
# mapping.name #=> :user
|
||||
# # is the scope used in controllers and warden, given in the route as :singular.
|
||||
#
|
||||
# mapping.as #=> "users"
|
||||
@@ -57,7 +57,7 @@ module Devise
|
||||
# self.for.include?(:confirmable)
|
||||
# end
|
||||
#
|
||||
CONTROLLERS.values.each do |m|
|
||||
ALL.each do |m|
|
||||
class_eval <<-METHOD, __FILE__, __LINE__
|
||||
def #{m}?
|
||||
self.for.include?(:#{m})
|
||||
|
||||
62
lib/devise/models/rememberable.rb
Normal file
62
lib/devise/models/rememberable.rb
Normal file
@@ -0,0 +1,62 @@
|
||||
require 'digest/sha1'
|
||||
|
||||
module Devise
|
||||
module Models
|
||||
|
||||
# Rememberable Module
|
||||
module Rememberable
|
||||
|
||||
def self.included(base)
|
||||
base.class_eval do
|
||||
extend ClassMethods
|
||||
|
||||
# Remember me option available in after_authentication hook.
|
||||
attr_accessor :remember_me
|
||||
attr_accessible :remember_me
|
||||
end
|
||||
end
|
||||
|
||||
# Generate a new remember token and save the record without validations.
|
||||
def remember_me!
|
||||
self.remember_token = friendly_token
|
||||
save(false)
|
||||
end
|
||||
|
||||
# Removes the remember token only if it exists, and save the record
|
||||
# without validations.
|
||||
def forget_me!
|
||||
if remember_token?
|
||||
self.remember_token = nil
|
||||
save(false)
|
||||
end
|
||||
end
|
||||
|
||||
# Checks whether the incoming token matches or not with the record token.
|
||||
def valid_remember_token?(token)
|
||||
remember_token.present? && remember_token == token
|
||||
end
|
||||
|
||||
module ClassMethods
|
||||
|
||||
# Attempts to remember the user through it's id and remember_token.
|
||||
# Returns the user if one is found and the token is valid, otherwise nil.
|
||||
# Attributes must contain :id and :remember_token
|
||||
def remember_me!(attributes={})
|
||||
rememberable = find_by_id(attributes[:id])
|
||||
rememberable if rememberable.try(:valid_remember_token?, attributes[:remember_token])
|
||||
end
|
||||
|
||||
# Create the cookie key using the record id and remember_token
|
||||
def serialize_into_cookie(record)
|
||||
"#{record.id}::#{record.remember_token}"
|
||||
end
|
||||
|
||||
# Recreate the user based on the stored cookie
|
||||
def serialize_from_cookie(cookie)
|
||||
record_id, remember_token = cookie.split('::')
|
||||
remember_me!(:id => record_id, :remember_token => remember_token)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -16,28 +16,30 @@ module Devise
|
||||
end
|
||||
end
|
||||
|
||||
# Find the attributes for the current mapping.
|
||||
def attributes
|
||||
@attributes ||= request.params[scope]
|
||||
end
|
||||
private
|
||||
|
||||
# Check for the right keys.
|
||||
def valid_attributes?
|
||||
attributes && attributes[:email].present? && attributes[:password].present?
|
||||
end
|
||||
# Find the attributes for the current mapping.
|
||||
def attributes
|
||||
@attributes ||= params[scope]
|
||||
end
|
||||
|
||||
# Stores requested uri to redirect the user after signing in. We cannot use
|
||||
# scoped session provided by warden here, since the user is not authenticated
|
||||
# yet, but we still need to store the uri based on scope, so different scopes
|
||||
# would never use the same uri to redirect.
|
||||
def store_location
|
||||
session[:"#{mapping.name}.return_to"] = request.request_uri if request.get?
|
||||
end
|
||||
# Check for the right keys.
|
||||
def valid_attributes?
|
||||
attributes && attributes[:email].present? && attributes[:password].present?
|
||||
end
|
||||
|
||||
# Create path to sign in the resource
|
||||
def sign_in_path
|
||||
"/#{mapping.as}/#{mapping.path_names[:sign_in]}"
|
||||
end
|
||||
# Stores requested uri to redirect the user after signing in. We cannot use
|
||||
# scoped session provided by warden here, since the user is not authenticated
|
||||
# yet, but we still need to store the uri based on scope, so different scopes
|
||||
# would never use the same uri to redirect.
|
||||
def store_location
|
||||
session[:"#{mapping.name}.return_to"] = request.request_uri if request.get?
|
||||
end
|
||||
|
||||
# Create path to sign in the resource
|
||||
def sign_in_path
|
||||
"/#{mapping.as}/#{mapping.path_names[:sign_in]}"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
33
lib/devise/strategies/rememberable.rb
Normal file
33
lib/devise/strategies/rememberable.rb
Normal file
@@ -0,0 +1,33 @@
|
||||
module Devise
|
||||
module Strategies
|
||||
# Remember the user through the remember token. This strategy is responsible
|
||||
# to verify whether there is a cookie with the remember token, and to
|
||||
# recreate the user from this cookie if it exists. Must be called *before*
|
||||
# authenticable.
|
||||
class Rememberable < Devise::Strategies::Base
|
||||
|
||||
# A valid strategy for rememberable needs a remember token in the cookies.
|
||||
def valid?
|
||||
super && remember_me_cookie.present?
|
||||
end
|
||||
|
||||
# To authenticate a user we deserialize the cookie and attempt finding
|
||||
# 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_me_cookie)
|
||||
success!(resource)
|
||||
else
|
||||
pass
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# Accessor for remember cookie
|
||||
def remember_me_cookie
|
||||
cookies['remember_token']
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -19,6 +19,11 @@ module Warden::Mixins::Common
|
||||
raw_session.inspect # why do I have to inspect it to get it to clear?
|
||||
raw_session.clear
|
||||
end
|
||||
|
||||
# Proxy to request cookies
|
||||
def cookies
|
||||
request.cookies
|
||||
end
|
||||
end
|
||||
|
||||
# Session Serialization in. This block determines how the user will be stored
|
||||
@@ -41,9 +46,13 @@ end
|
||||
# Adds Warden Manager to Rails middleware stack, configuring default devise
|
||||
# strategy and also the controller who will manage not authenticated users.
|
||||
Rails.configuration.middleware.use Warden::Manager do |manager|
|
||||
manager.default_strategies :authenticable
|
||||
manager.default_strategies :rememberable, :authenticable
|
||||
manager.failure_app = SessionsController
|
||||
end
|
||||
|
||||
# Setup devise strategies for Warden
|
||||
Warden::Strategies.add(:rememberable, Devise::Strategies::Rememberable)
|
||||
Warden::Strategies.add(:authenticable, Devise::Strategies::Authenticable)
|
||||
|
||||
# Require rememberable hooks
|
||||
require 'devise/hooks/rememberable'
|
||||
|
||||
@@ -48,6 +48,7 @@ class ControllerAuthenticableTest < ActionController::TestCase
|
||||
end
|
||||
|
||||
test 'proxy logout to warden' do
|
||||
@mock_warden.expects(:user).with(:user).returns(true)
|
||||
@mock_warden.expects(:logout).with(:user).returns(true)
|
||||
@controller.sign_out(:user)
|
||||
end
|
||||
|
||||
56
test/integration/rememberable_test.rb
Normal file
56
test/integration/rememberable_test.rb
Normal file
@@ -0,0 +1,56 @@
|
||||
require 'test/test_helper'
|
||||
|
||||
class RememberMeTest < ActionController::IntegrationTest
|
||||
|
||||
def create_user_and_remember(add_to_token='')
|
||||
user = create_user
|
||||
user.remember_me!
|
||||
cookies['remember_token'] = User.serialize_into_cookie(user) + add_to_token
|
||||
user
|
||||
end
|
||||
|
||||
test 'do not remember the user if he has not checked remember me option' do
|
||||
user = sign_in_as_user
|
||||
|
||||
assert_nil user.reload.remember_token
|
||||
end
|
||||
|
||||
test 'generate remember token after sign in' do
|
||||
user = sign_in_as_user :remember_me => true
|
||||
|
||||
assert_not_nil user.reload.remember_token
|
||||
end
|
||||
|
||||
test 'remember the user before sign in' do
|
||||
user = create_user_and_remember
|
||||
get users_path
|
||||
assert_response :success
|
||||
assert warden.authenticated?(:user)
|
||||
assert warden.user(:user) == user
|
||||
end
|
||||
|
||||
test 'do not remember with invalid token' do
|
||||
user = create_user_and_remember('add')
|
||||
get users_path
|
||||
assert_response :success
|
||||
assert_not warden.authenticated?(:user)
|
||||
end
|
||||
|
||||
test 'forget the user before sign out' do
|
||||
user = create_user_and_remember
|
||||
get users_path
|
||||
assert warden.authenticated?(:user)
|
||||
get destroy_user_session_path
|
||||
assert_not warden.authenticated?(:user)
|
||||
assert_nil user.reload.remember_token
|
||||
end
|
||||
|
||||
test 'do not remember the user anymore after forget' do
|
||||
user = create_user_and_remember
|
||||
get users_path
|
||||
assert warden.authenticated?(:user)
|
||||
get destroy_user_session_path
|
||||
get users_path
|
||||
assert_not warden.authenticated?(:user)
|
||||
end
|
||||
end
|
||||
@@ -60,10 +60,12 @@ class MapTest < ActiveSupport::TestCase
|
||||
assert mapping.authenticable?
|
||||
assert mapping.confirmable?
|
||||
assert mapping.recoverable?
|
||||
assert mapping.rememberable?
|
||||
|
||||
mapping = Devise.mappings[:admin]
|
||||
assert mapping.authenticable?
|
||||
assert_not mapping.confirmable?
|
||||
assert_not mapping.recoverable?
|
||||
assert_not mapping.rememberable?
|
||||
end
|
||||
end
|
||||
|
||||
76
test/models/rememberable_test.rb
Normal file
76
test/models/rememberable_test.rb
Normal file
@@ -0,0 +1,76 @@
|
||||
require 'test/test_helper'
|
||||
|
||||
class RememberableTest < ActiveSupport::TestCase
|
||||
|
||||
test 'should respond to remember_me attribute' do
|
||||
user = new_user
|
||||
assert user.respond_to?(:remember_me)
|
||||
end
|
||||
|
||||
test 'should have remember_me accessible' do
|
||||
assert field_accessible?(:remember_me)
|
||||
end
|
||||
|
||||
test 'remember_me should generate a new token and save the record without validating' do
|
||||
user = create_user
|
||||
user.expects(:valid?).never
|
||||
token = user.remember_token
|
||||
user.remember_me!
|
||||
assert_not_equal token, user.remember_token
|
||||
assert_not user.changed?
|
||||
end
|
||||
|
||||
test 'forget_me should clear remember token and save the record without validating' do
|
||||
user = create_user
|
||||
user.remember_me!
|
||||
assert_not_nil user.remember_token
|
||||
user.expects(:valid?).never
|
||||
user.forget_me!
|
||||
assert_nil user.remember_token
|
||||
assert_not user.changed?
|
||||
end
|
||||
|
||||
test 'forget should do nothing if no remember token exists' do
|
||||
user = create_user
|
||||
user.expects(:save).never
|
||||
user.forget_me!
|
||||
end
|
||||
|
||||
test 'valid remember token' do
|
||||
user = create_user
|
||||
assert_not user.valid_remember_token?(user.remember_token)
|
||||
user.remember_me!
|
||||
assert user.valid_remember_token?(user.remember_token)
|
||||
user.forget_me!
|
||||
assert_not user.valid_remember_token?(user.remember_token)
|
||||
end
|
||||
|
||||
test 'find a user by its id and remember it if the token is valid' do
|
||||
user = create_user
|
||||
user.remember_me!
|
||||
remembered_user = User.remember_me!(:id => user.id, :remember_token => user.remember_token)
|
||||
assert_not_nil remembered_user
|
||||
assert_equal remembered_user, user
|
||||
end
|
||||
|
||||
test 'remember me should return nil if no user is found' do
|
||||
assert_nil User.remember_me!(:id => 0)
|
||||
end
|
||||
|
||||
test 'remember me return nil if is a valid user with invalid token' do
|
||||
user = create_user
|
||||
assert_nil User.remember_me!(:id => user.id, :remember_token => 'invalid_token')
|
||||
end
|
||||
|
||||
test 'serialize into cookie' do
|
||||
user = create_user
|
||||
user.remember_me!
|
||||
assert_equal "#{user.id}::#{user.remember_token}", User.serialize_into_cookie(user)
|
||||
end
|
||||
|
||||
test 'serialize from cookie' do
|
||||
user = create_user
|
||||
user.remember_me!
|
||||
assert_equal user, User.serialize_from_cookie("#{user.id}::#{user.remember_token}")
|
||||
end
|
||||
end
|
||||
@@ -1,3 +1,3 @@
|
||||
class Admin < ActiveRecord::Base
|
||||
devise :all, :except => [:recoverable, :confirmable]
|
||||
devise :all, :except => [:recoverable, :confirmable, :rememberable]
|
||||
end
|
||||
|
||||
@@ -24,21 +24,24 @@ class ActionController::IntegrationTest
|
||||
end
|
||||
|
||||
def sign_in_as_user(options={}, &block)
|
||||
create_user(options)
|
||||
user = create_user(options)
|
||||
visit new_user_session_path unless options[:visit] == false
|
||||
fill_in 'email', :with => 'user@test.com'
|
||||
fill_in 'password', :with => '123456'
|
||||
check 'remember me' if options[:remember_me] == true
|
||||
yield if block_given?
|
||||
click_button 'Sign In'
|
||||
user
|
||||
end
|
||||
|
||||
def sign_in_as_admin(options={}, &block)
|
||||
create_admin(options)
|
||||
admin = create_admin(options)
|
||||
visit new_admin_session_path unless options[:visit] == false
|
||||
fill_in 'email', :with => 'admin@test.com'
|
||||
fill_in 'password', :with => '123456'
|
||||
yield if block_given?
|
||||
click_button 'Sign In'
|
||||
admin
|
||||
end
|
||||
|
||||
# Fix assert_redirect_to in integration sessions because they don't take into
|
||||
|
||||
@@ -19,10 +19,15 @@ ActiveRecord::Schema.define(:version => 1) do
|
||||
t.string :email, :null => false
|
||||
t.string :encrypted_password, :null => false
|
||||
t.string :password_salt, :null => false
|
||||
t.string :confirmation_token
|
||||
t.datetime :confirmation_sent_at
|
||||
t.datetime :confirmed_at
|
||||
t.string :reset_password_token
|
||||
if table == :users
|
||||
t.string :confirmation_token
|
||||
t.datetime :confirmation_sent_at
|
||||
t.datetime :confirmed_at
|
||||
t.string :reset_password_token
|
||||
t.string :remember_token
|
||||
end
|
||||
|
||||
t.timestamps
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user