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.
- data/README.rdoc +14 -5
- data/VERSION +1 -1
- data/devise_session_expirable.gemspec +2 -2
- data/lib/devise_session_expirable.rb +4 -9
- data/lib/devise_session_expirable/delegator.rb +13 -0
- data/lib/devise_session_expirable/devise_extensions.rb +38 -0
- data/lib/devise_session_expirable/failure_app.rb +21 -0
- data/lib/devise_session_expirable/hook.rb +0 -41
- data/lib/devise_session_expirable/model.rb +0 -5
- data/lib/devise_session_expirable/warden_extensions.rb +46 -0
- data/test/integration/session_expirable_test.rb +20 -22
- metadata +9 -2
data/README.rdoc
CHANGED
@@ -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
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
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
|
+
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.
|
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-
|
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
|
-
|
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
|
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
|
56
|
+
assert_redirected_to new_user_session_path
|
57
57
|
assert_not warden.authenticated?(:user)
|
58
58
|
end
|
59
59
|
|
60
|
-
test '
|
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
|
-
|
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",
|
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
|
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
|
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.
|
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-
|
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:
|