1
0
Fork 0
mirror of https://github.com/heartcombo/devise.git synced 2022-11-09 12:18:31 -05:00

Merge branch 'session_timeout'

This commit is contained in:
Carlos Antonio da Silva 2009-11-23 23:34:38 -02:00
commit 7933d203e3
15 changed files with 225 additions and 37 deletions

View file

@ -34,6 +34,10 @@ Devise.setup do |config|
# The time the user will be remembered without asking for credentials again.
# config.remember_for = 2.weeks
# The time you want to timeout the user session without activity. After this
# time the user will be asked for credentials again.
# config.timeout = 10.minutes
# Configure the e-mail address which will be shown in DeviseMailer.
# config.mailer_sender = "foo.bar@yourapp.com"

View file

@ -1,5 +1,5 @@
module Devise
ALL = [:authenticatable, :confirmable, :recoverable, :rememberable, :validatable].freeze
ALL = [:authenticatable, :confirmable, :recoverable, :rememberable, :timeoutable, :validatable].freeze
# Maps controller names to devise modules
CONTROLLERS = {
@ -14,7 +14,7 @@ module Devise
# Maps the messages types that are used in flash message. This array is not
# frozen, so you can add messages from your own strategies.
FLASH_MESSAGES = [ :unauthenticated, :unconfirmed, :invalid ]
FLASH_MESSAGES = [ :unauthenticated, :unconfirmed, :invalid, :timeout ]
# Declare encryptors length which are used in migrations.
ENCRYPTORS_LENGTH = {
@ -45,6 +45,10 @@ module Devise
mattr_accessor :confirm_within
@@confirm_within = 0.days
# Time interval to timeout the user session without activity.
mattr_accessor :timeout
@@timeout = 30.minutes
# Used to define the password encryption algorithm.
mattr_accessor :encryptor
@@encryptor = :sha1
@ -141,5 +145,4 @@ Warden::Manager.default_scope = nil
require 'devise/strategies/base'
require 'devise/serializers/base'
require 'devise/rails'

View file

@ -6,7 +6,6 @@ Warden::Manager.after_set_user do |record, warden, options|
if record && record.respond_to?(:active?) && !record.active?
scope = options[:scope]
warden.logout(scope)
if warden.winning_strategy
# If winning strategy was set, this is being called after authenticate and
# there is no need to force a redirect.

View file

@ -0,0 +1,19 @@
# Each time a record is set we check whether it's session has already timed out
# or not, based on last request time. If so, the record is logged out and
# redirected to the sign in page. Also, each time the request comes and the
# record is set, we set the last request time inside it's scoped session to
# verify timeout in the following request.
Warden::Manager.after_set_user do |record, warden, options|
if record && record.respond_to?(:timeout?)
scope = options[:scope]
# Record may have already been logged out by another hook (ie confirmable).
if warden.authenticated?(scope)
last_request_at = warden.session(scope)['last_request_at']
if record.timeout?(last_request_at)
warden.logout(scope)
throw :warden, :scope => scope, :message => :timeout
end
warden.session(scope)['last_request_at'] = Time.now.utc
end
end
end

View file

@ -6,6 +6,7 @@ en:
unauthenticated: 'You need to sign in or sign up before continuing.'
unconfirmed: 'You have to confirm your account before continuing.'
invalid: 'Invalid email or password.'
timeout: 'Your session expired, please sign in again to continue.'
passwords:
send_instructions: 'You will receive an email with instructions about how to reset your password in a few minutes.'
updated: 'Your password was changed successfully. You are now signed in.'

View file

@ -0,0 +1,30 @@
require 'devise/hooks/timeoutable'
module Devise
module Models
# Timeoutable takes care of veryfing whether a user session has already
# expired or not. When a session expires after the configured time, the user
# will be asked for credentials again, it means, he/she will be redirected
# to the sign in page.
#
# Configuration:
#
# timeout: the time you want to timeout the user session without activity.
module Timeoutable
def self.included(base)
base.extend ClassMethods
end
# Checks whether the user session has expired based on configured time.
def timeout?(last_access)
last_access && last_access <= self.class.timeout.ago.utc
end
module ClassMethods
Devise::Models.config(self, :timeout)
end
end
end
end

View file

@ -1,5 +1,5 @@
require 'test/test_helper'
require 'ostruct'
require 'ostruct'
class FailureTest < ActiveSupport::TestCase
@ -22,6 +22,18 @@ class FailureTest < ActiveSupport::TestCase
assert_equal '/users/sign_in?test=true', location
end
test 'uses the given message' do
warden = OpenStruct.new(:message => 'Hello world')
location = call_failure('warden' => warden).second['Location']
assert_equal '/users/sign_in?message=Hello+world', location
end
test 'setup default url' do
Devise::FailureApp.default_url = 'test/sign_in'
location = call_failure('warden.options' => { :scope => nil }).second['Location']
assert_equal '/test/sign_in?unauthenticated=true', location
end
test 'set content type to default text/plain' do
assert_equal 'text/plain', call_failure.second['Content-Type']
end

View file

@ -102,17 +102,14 @@ class AuthenticationTest < ActionController::IntegrationTest
end
test 'error message is configurable by resource name' do
begin
I18n.backend.store_translations(:en, :devise => { :sessions =>
{ :admin => { :invalid => "Invalid credentials" } } })
store_translations :en, :devise => {
:sessions => { :admin => { :invalid => "Invalid credentials" } }
} do
sign_in_as_admin do
fill_in 'password', :with => 'abcdef'
end
assert_contain 'Invalid credentials'
ensure
I18n.reload!
end
end

View file

@ -58,32 +58,30 @@ class ConfirmationTest < ActionController::IntegrationTest
assert warden.authenticated?(:user)
end
test 'not confirmed user and setup to block without confirmation should not be able to sign in' do
Devise.confirm_within = 0
user = sign_in_as_user(:confirm => false)
test 'not confirmed user with setup to block without confirmation should not be able to sign in' do
swap Devise, :confirm_within => 0.days do
sign_in_as_user(:confirm => false)
assert_contain 'You have to confirm your account before continuing'
assert_not warden.authenticated?(:user)
assert_contain 'You have to confirm your account before continuing'
assert_not warden.authenticated?(:user)
end
end
test 'not confirmed user but configured with some days to confirm should be able to sign in' do
Devise.confirm_within = 1
user = sign_in_as_user(:confirm => false)
swap Devise, :confirm_within => 1.day do
sign_in_as_user(:confirm => false)
assert_response :success
assert warden.authenticated?(:user)
assert_response :success
assert warden.authenticated?(:user)
end
end
test 'error message is configurable by resource name' do
begin
I18n.backend.store_translations(:en, :devise => { :sessions =>
{ :admin => { :unconfirmed => "Not confirmed user" } } })
store_translations :en, :devise => {
:sessions => { :admin => { :unconfirmed => "Not confirmed user" } }
} do
get new_admin_session_path(:unconfirmed => true)
assert_contain 'Not confirmed user'
ensure
I18n.reload!
end
end
end

View file

@ -0,0 +1,73 @@
require 'test/test_helper'
class SessionTimeoutTest < ActionController::IntegrationTest
def last_request_at
@controller.user_session['last_request_at']
end
test 'set last request at in user session after each request' do
sign_in_as_user
old_last_request = last_request_at
assert_not_nil last_request_at
get users_path
assert_not_nil last_request_at
assert_not_equal old_last_request, last_request_at
end
test 'not time out user session before default limit time' do
user = sign_in_as_user
# Setup last_request_at to timeout
get edit_user_path(user)
assert_not_nil last_request_at
get users_path
assert_response :success
assert warden.authenticated?(:user)
end
test 'time out user session after default limit time' do
sign_in_as_user
assert_response :success
assert warden.authenticated?(:user)
# Setup last_request_at to timeout
get new_user_path
assert_not_nil last_request_at
get users_path
assert_redirected_to new_user_session_path(:timeout => true)
assert_not warden.authenticated?(:user)
end
test 'user configured timeout limit' do
swap Devise, :timeout => 8.minutes do
user = sign_in_as_user
# Setup last_request_at to timeout
get edit_user_path(user)
assert_not_nil last_request_at
assert_response :success
assert warden.authenticated?(:user)
get users_path
assert_redirected_to new_user_session_path(:timeout => true)
assert_not warden.authenticated?(:user)
end
end
test 'error message with i18n' do
store_translations :en, :devise => {
:sessions => { :user => { :timeout => 'Session expired!' } }
} do
sign_in_as_user
# Setup last_request_at to timeout
get new_user_path
get users_path
follow_redirect!
assert_contain 'Session expired!'
end
end
end

View file

@ -0,0 +1,27 @@
require 'test/test_helper'
class TimeoutableTest < ActiveSupport::TestCase
test 'should be expired' do
assert new_user.timeout?(11.minutes.ago)
end
test 'should not be expired' do
assert_not new_user.timeout?(9.minutes.ago)
end
test 'should not be expired when params is nil' do
assert_not new_user.timeout?(nil)
end
test 'fallback to Devise config option' do
swap Devise, :timeout => 1.minute do
user = new_user
assert user.timeout?(2.minutes.ago)
assert_not user.timeout?(30.seconds.ago)
Devise.timeout = 5.minutes
assert_not user.timeout?(2.minutes.ago)
assert user.timeout?(6.minutes.ago)
end
end
end

View file

@ -1,6 +1,6 @@
require 'test/test_helper'
class Authenticable < User
class Authenticatable < User
devise :authenticatable
end
@ -16,6 +16,10 @@ class Rememberable < User
devise :authenticatable, :rememberable
end
class Timeoutable < User
devise :authenticatable, :timeoutable
end
class Validatable < User
devise :authenticatable, :validatable
end
@ -32,7 +36,8 @@ class Configurable < User
devise :all, :stretches => 15,
:pepper => 'abcdef',
:confirm_within => 5.days,
:remember_for => 7.days
:remember_for => 7.days,
:timeout => 15.minutes
end
class ActiveRecordTest < ActiveSupport::TestCase
@ -54,33 +59,38 @@ class ActiveRecordTest < ActiveSupport::TestCase
end
test 'include by default authenticatable only' do
assert_include_modules Authenticable, :authenticatable
assert_not_include_modules Authenticable, :confirmable, :recoverable, :rememberable, :validatable
assert_include_modules Authenticatable, :authenticatable
assert_not_include_modules Authenticatable, :confirmable, :recoverable, :rememberable, :timeoutable, :validatable
end
test 'add confirmable module only' do
assert_include_modules Confirmable, :authenticatable, :confirmable
assert_not_include_modules Confirmable, :recoverable, :rememberable, :validatable
assert_not_include_modules Confirmable, :recoverable, :rememberable, :timeoutable, :validatable
end
test 'add recoverable module only' do
assert_include_modules Recoverable, :authenticatable, :recoverable
assert_not_include_modules Recoverable, :confirmable, :rememberable, :validatable
assert_not_include_modules Recoverable, :confirmable, :rememberable, :timeoutable, :validatable
end
test 'add rememberable module only' do
assert_include_modules Rememberable, :authenticatable, :rememberable
assert_not_include_modules Rememberable, :confirmable, :recoverable, :validatable
assert_not_include_modules Rememberable, :confirmable, :recoverable, :timeoutable, :validatable
end
test 'add timeoutable module only' do
assert_include_modules Timeoutable, :authenticatable, :timeoutable
assert_not_include_modules Timeoutable, :confirmable, :recoverable, :rememberable, :validatable
end
test 'add validatable module only' do
assert_include_modules Validatable, :authenticatable, :validatable
assert_not_include_modules Validatable, :confirmable, :recoverable, :rememberable
assert_not_include_modules Validatable, :confirmable, :recoverable, :timeoutable, :rememberable
end
test 'add all modules' do
assert_include_modules Devisable,
:authenticatable, :confirmable, :recoverable, :rememberable, :validatable
:authenticatable, :confirmable, :recoverable, :rememberable, :timeoutable, :validatable
end
test 'configure modules with except option' do
@ -104,6 +114,10 @@ class ActiveRecordTest < ActiveSupport::TestCase
assert_equal 7.days, Configurable.remember_for
end
test 'set a default value for timeout' do
assert_equal 15.minutes, Configurable.timeout
end
test 'set null fields on migrations' do
Admin.create!
end

View file

@ -4,4 +4,14 @@ class UsersController < ApplicationController
def index
user_session[:cart] = "Cart"
end
def new
user_session['last_request_at'] = 11.minutes.ago.utc
render :text => 'New user!'
end
def edit
user_session['last_request_at'] = 9.minutes.ago.utc
render :text => 'Edit user!'
end
end

View file

@ -8,7 +8,7 @@ ActionController::Routing::Routes.draw do |map|
:path_prefix => '/:locale',
:requirements => { :extra => 'value' }
map.resources :users, :only => :index
map.resources :users, :only => [:index, :new, :edit]
map.resources :admins, :only => :index
map.root :controller => :home

View file

@ -33,6 +33,7 @@ end
Webrat.configure do |config|
config.mode = :rails
config.open_error_files = false
end
class ActiveSupport::TestCase