devise_session_expirable 0.1.1 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -2,7 +2,7 @@
2
2
 
3
3
  All good things come to an end: At least that's how it's supposed to work.
4
4
 
5
- SessionExpirable is an enhanced version of Devise's +timeoutable+ module
5
+ SessionExpirable is an enhanced version of Devise[https://github.com/plataformatec/devise]'s +timeoutable+ module
6
6
  that ensures that no session is allowed to last forever.
7
7
 
8
8
  Like Devise +timeoutable+, SessionExpirable adds a timestamp to sessions
@@ -20,10 +20,19 @@ never be considered as having timed out.
20
20
  In order to prevent the abuse of non-expiring sessions, SessionExpirable
21
21
  treats sessions without timestamps as having already expired.
22
22
 
23
- One other difference with +timeoutable+ is that SessionExpirable
24
- does not support invalidation of authentication tokens from the
25
- devise +token_authenticatable+ module when a request with a valid
26
- authentication token is accompanied by an expired session.
23
+ Another difference is that in the case of a timeout, +timeoutable+
24
+ prevents other authentication strategies from being tried, although
25
+ it has custom logic to allow Devise +rememberable+ to work in spite of a
26
+ timed-out session.
27
+
28
+ SessionExpirable does not allow a timed-out session to interfere with any
29
+ authentication strategies that are configured. One consequence is that
30
+ SessionExpirable does not support invalidation of authentication tokens
31
+ from the Devise +token_authenticatable+ module when a request with a
32
+ valid authentication token is accompanied by an expired session.
33
+
34
+ One final difference is that SessionExpirable does not invoke the Devise
35
+ FailureApp for requests that do not require authentication.
27
36
 
28
37
  == Configuration
29
38
 
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.1.1
1
+ 0.2.0
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = "devise_session_expirable"
8
- s.version = "0.1.1"
8
+ s.version = "0.2.0"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["Riley Lynch"]
12
- s.date = "2013-02-16"
12
+ s.date = "2013-03-11"
13
13
  s.description = "devise_session_expirable is an enhanced version of devise's timeoutable module that ensures that no session is allowed to last forever."
14
14
  s.email = "oss+expirable@teleological.net"
15
15
  s.extra_rdoc_files = [
@@ -3,16 +3,11 @@ require 'active_support/core_ext/module/attribute_accessors'
3
3
  require 'active_support/concern'
4
4
  require 'devise'
5
5
 
6
- module Devise #:nodoc:
7
-
8
- mattr_accessor :default_last_request_at
9
- @default_last_request_at = nil
10
-
11
- add_module :session_expirable,
12
- :model => 'devise_session_expirable/model'
13
-
14
- end
6
+ require 'devise_session_expirable/warden_extensions'
7
+ require 'devise_session_expirable/devise_extensions'
15
8
 
16
9
  module DeviseSessionExpirable #:nodoc:
10
+ autoload :Delegator, 'devise_session_expirable/delegator'
11
+ autoload :FailureApp, 'devise_session_expirable/failure_app'
17
12
  end
18
13
 
@@ -0,0 +1,13 @@
1
+
2
+ class DeviseSessionExpirable::Delegator < Devise::Delegator
3
+
4
+ def failure_app(env)
5
+ app = env["warden.options"] &&
6
+ (scope = env["warden.options"][:scope]) &&
7
+ Devise.mappings[scope.to_sym].failure_app
8
+
9
+ app || DeviseSessionExpirable::FailureApp
10
+ end
11
+
12
+ end
13
+
@@ -0,0 +1,38 @@
1
+
2
+ module Devise #:nodoc:
3
+
4
+ mattr_accessor :default_last_request_at
5
+ @default_last_request_at = nil
6
+
7
+ add_module :session_expirable,
8
+ :model => 'devise_session_expirable/model'
9
+
10
+ def self.configure_warden! #:nodoc:
11
+ warden_config.failure_app = DeviseSessionExpirable::Delegator.new
12
+ warden_config.default_scope = Devise.default_scope
13
+ warden_config.intercept_401 = false
14
+
15
+ Devise.mappings.each_value do |mapping|
16
+ warden_config.scope_defaults mapping.name, :strategies => mapping.strategies
17
+ end
18
+
19
+ @@warden_config_block.try :call, Devise.warden_config
20
+ true
21
+ end
22
+
23
+ class Mapping #:nodoc:
24
+
25
+ private
26
+
27
+ def default_failure_app(options)
28
+ @failure_app = options[:failure_app] || DeviseSessionExpirable::FailureApp
29
+ if @failure_app.is_a?(String)
30
+ ref = Devise.ref(@failure_app)
31
+ @failure_app = lambda { |env| ref.get.call(env) }
32
+ end
33
+ end
34
+
35
+ end
36
+
37
+ end
38
+
@@ -0,0 +1,21 @@
1
+
2
+ class DeviseSessionExpirable::FailureApp < Devise::FailureApp
3
+
4
+ def redirect_url
5
+ flash[:timedout] = true if timeout?
6
+ scope_path
7
+ end
8
+
9
+ protected
10
+
11
+ def warden_message
12
+ @message ||=
13
+ warden.message || warden_options[:message] || (timeout? ? :timeout : nil)
14
+ end
15
+
16
+ def timeout?
17
+ !! env['devise.timeout']
18
+ end
19
+
20
+ end
21
+
@@ -1,40 +1,3 @@
1
-
2
- # Each time the user record is fetched from a session, the record is
3
- # consulted (via +#session_expired?+) to determine if the
4
- # +last_request_at+ time in the session is valid, or if the session
5
- # should be considered as having timed out. This validation is not
6
- # performed if +devise.skip_timeout+ is set in the rack environment.
7
- #
8
- # If the session is deemed to have timed out, the record is logged out
9
- # of the session and a +:timeout+ message is thrown, invoking the
10
- # +FailureApp+.
11
- #
12
- # Unlike the Devise +timeoutable+ module, devise_session_expirable does
13
- # not support invalidation of authentication tokens from the devise
14
- # +token_authenticatable+ module when a request with a valid
15
- # authentication token is accompanied by an expired session.
16
-
17
- Warden::Manager.after_fetch do |record, warden, options|
18
- scope = options[:scope]
19
- env = warden.request.env
20
-
21
- puts "after fetch"
22
-
23
- if record &&
24
- record.respond_to?(:session_expired?) &&
25
- warden.authenticated?(scope) &&
26
- options[:store] != false &&
27
- !env['devise.skip_timeout']
28
-
29
- last_request_at = warden.session(scope)['last_request_at']
30
- if record.session_expired?(last_request_at)
31
- puts "expired!"
32
- warden.logout(scope)
33
- throw :warden, :scope => scope, :message => :timeout
34
- end
35
- end
36
- end
37
-
38
1
  # Each time the user record is set, the +last_request_at+ time
39
2
  # is updated in the scoped session. This update is not performed if
40
3
  # devise.skip_trackable is set in the rack environment.
@@ -43,16 +6,12 @@ Warden::Manager.after_set_user do |record, warden, options|
43
6
  scope = options[:scope]
44
7
  env = warden.request.env
45
8
 
46
- puts "after set_user"
47
-
48
9
  if record &&
49
10
  record.respond_to?(:session_expired?) &&
50
11
  warden.authenticated?(scope) &&
51
12
  options[:store] != false &&
52
13
  !env['devise.skip_trackable']
53
14
 
54
- puts "reset"
55
-
56
15
  warden.session(scope)['last_request_at'] = Time.now.utc
57
16
  end
58
17
  end
@@ -34,11 +34,6 @@ module Devise #:nodoc:
34
34
  # is set.
35
35
 
36
36
  def session_expired?(last_access)
37
- puts "last_access #{last_access}"
38
- puts "timeout_in #{timeout_in}"
39
- puts "deadline #{timeout_in.ago}"
40
- puts !timeout_in.nil? && (!last_access || last_access <= timeout_in.ago)
41
-
42
37
  last_access ||= default_last_request_at
43
38
  return false if remember_exists_and_not_expired?
44
39
  !timeout_in.nil? && (!last_access || last_access <= timeout_in.ago)
@@ -0,0 +1,46 @@
1
+
2
+ # Each time the user record is fetched from a session, the record is
3
+ # consulted (via +#session_expired?+) to determine if the
4
+ # +last_request_at+ time in the session is valid, or if the session
5
+ # should be considered as having timed out. If the session is deemed
6
+ # to have timed out, the record is disregarded.
7
+ #
8
+ # Unlike the Devise +timeoutable+ module, devise_session_expirable does
9
+ # not support invalidation of authentication tokens from the devise
10
+ # +token_authenticatable+ module when a request with a valid
11
+ # authentication token is accompanied by an expired session.
12
+
13
+ class Warden::SessionSerializer
14
+
15
+ def fetch(scope)
16
+ key = session[key_for(scope)]
17
+ return nil unless key
18
+
19
+ method_name = "#{scope}_deserialize"
20
+ user = respond_to?(method_name) ? send(method_name, key) : deserialize(key)
21
+ user = nil unless valid_for_deserialization?(scope, user)
22
+ delete(scope) unless user
23
+ user
24
+ end
25
+
26
+ private
27
+
28
+ def valid_for_deserialization?(scope, user)
29
+ ! validate_session_expiration(scope, user)
30
+ end
31
+
32
+ def validate_session_expiration(scope, user)
33
+ is_expired = false
34
+ if user && user.respond_to?(:session_expired?)
35
+ last_request_at = session_for_scope(scope)['last_request_at']
36
+ is_expired = user.session_expired?(last_request_at)
37
+ end
38
+ env['devise.timeout'] = is_expired
39
+ end
40
+
41
+ def session_for_scope(scope)
42
+ session["warden.user.#{scope}.session"] ||= {}
43
+ end
44
+
45
+ end
46
+
@@ -43,7 +43,7 @@ class SessionExpirableIntegrationTest < ActionDispatch::IntegrationTest
43
43
  get clear_timeout_user_path(user)
44
44
 
45
45
  get users_path
46
- assert_redirected_to users_path
46
+ assert_redirected_to new_user_session_path
47
47
  assert_not warden.authenticated?(:user)
48
48
  end
49
49
 
@@ -53,27 +53,39 @@ class SessionExpirableIntegrationTest < ActionDispatch::IntegrationTest
53
53
  assert_not_nil last_request_at
54
54
 
55
55
  get users_path
56
- assert_redirected_to users_path
56
+ assert_redirected_to new_user_session_path
57
57
  assert_not warden.authenticated?(:user)
58
58
  end
59
59
 
60
- test 'time out is not triggered on sign out' do
60
+ test 'sign out when timed out is ignored' do
61
61
  user = sign_in_as_user
62
62
  get expire_user_path(user)
63
63
 
64
64
  get destroy_user_session_path
65
-
66
65
  assert_response :redirect
67
66
  assert_redirected_to root_path
67
+ assert_not warden.authenticated?(:user)
68
68
  follow_redirect!
69
- assert_contain 'Signed out successfully'
69
+ assert_not_contain 'Signed out successfully'
70
+ end
71
+
72
+ test 'expired session is not extended by sign in page' do
73
+ user = sign_in_as_user
74
+ get expire_user_path(user)
75
+ assert warden.authenticated?(:user)
76
+
77
+ get "/users/sign_in"
78
+ assert_response :success
79
+ assert_contain 'Sign in'
80
+ assert_not warden.authenticated?(:user)
70
81
  end
71
82
 
72
83
  test 'time out is not triggered on sign in' do
73
84
  user = sign_in_as_user
74
85
  get expire_user_path(user)
75
86
 
76
- post "/users/sign_in", :email => user.email, :password => "123456"
87
+ post "/users/sign_in",
88
+ :user => { :email => user.email, :password => "12345678" }
77
89
 
78
90
  assert_response :redirect
79
91
  follow_redirect!
@@ -90,7 +102,7 @@ class SessionExpirableIntegrationTest < ActionDispatch::IntegrationTest
90
102
 
91
103
  begin
92
104
  get admins_path
93
- assert_redirected_to admins_path
105
+ assert_redirected_to new_admin_session_path
94
106
  assert_not warden.authenticated?(:admin)
95
107
  ensure
96
108
  Admin.send(:remove_method, :reset_authentication_token!)
@@ -108,25 +120,12 @@ class SessionExpirableIntegrationTest < ActionDispatch::IntegrationTest
108
120
 
109
121
  get expire_user_path(user)
110
122
  get users_path
111
- assert_redirected_to users_path
123
+ assert_redirected_to new_user_session_path
112
124
  assert_not warden.authenticated?(:user)
113
125
  end
114
126
  end
115
127
 
116
128
  test 'error message with i18n' do
117
- store_translations :en, :devise => {
118
- :failure => { :user => { :timeout => 'Session expired!' } }
119
- } do
120
- user = sign_in_as_user
121
-
122
- get expire_user_path(user)
123
- get root_path
124
- follow_redirect!
125
- assert_contain 'Session expired!'
126
- end
127
- end
128
-
129
- test 'error message with i18n with double redirect' do
130
129
  store_translations :en, :devise => {
131
130
  :failure => { :user => { :timeout => 'Session expired!' } }
132
131
  } do
@@ -135,7 +134,6 @@ class SessionExpirableIntegrationTest < ActionDispatch::IntegrationTest
135
134
  get expire_user_path(user)
136
135
  get users_path
137
136
  follow_redirect!
138
- follow_redirect!
139
137
  assert_contain 'Session expired!'
140
138
  end
141
139
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: devise_session_expirable
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.2.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2013-02-16 00:00:00.000000000 Z
12
+ date: 2013-03-11 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: activesupport
@@ -108,8 +108,12 @@ files:
108
108
  - VERSION
109
109
  - devise_session_expirable.gemspec
110
110
  - lib/devise_session_expirable.rb
111
+ - lib/devise_session_expirable/delegator.rb
112
+ - lib/devise_session_expirable/devise_extensions.rb
113
+ - lib/devise_session_expirable/failure_app.rb
111
114
  - lib/devise_session_expirable/hook.rb
112
115
  - lib/devise_session_expirable/model.rb
116
+ - lib/devise_session_expirable/warden_extensions.rb
113
117
  - test/integration/session_expirable_test.rb
114
118
  - test/models/session_expirable_test.rb
115
119
  - test/orm/active_record.rb
@@ -168,6 +172,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
168
172
  - - ! '>='
169
173
  - !ruby/object:Gem::Version
170
174
  version: '0'
175
+ segments:
176
+ - 0
177
+ hash: 1721273659600446097
171
178
  required_rubygems_version: !ruby/object:Gem::Requirement
172
179
  none: false
173
180
  requirements: