utopia 1.8.1 → 1.8.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +2 -2
- data/lib/utopia/session/lazy_hash.rb +17 -0
- data/lib/utopia/session.rb +67 -19
- data/lib/utopia/version.rb +1 -1
- data/spec/utopia/session_spec.rb +68 -3
- data/spec/utopia/session_spec.ru +6 -3
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 2ca9a6f28fcec98323ae67b683f903e44fb68eea
|
4
|
+
data.tar.gz: 1b86bb5bb7a36448df329b2aeda819feb2c66fa2
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7279e0472f9c4e1760368ee5c138f1338e2426dd2c194e5014a318b5faf2e0227c04bd09f556c32bc3abeca899270bbbd92208c7aaf454cac2100d4116a2b4c8
|
7
|
+
data.tar.gz: 5be21037ad98e014ece09d98ceef3812256aaa9239eb7ea770f68df6aa636f245fe9a5168ef545944e0206954a11ed0d7388c0083f3ff7237e35f62a4cbb80d9
|
data/.travis.yml
CHANGED
@@ -65,6 +65,23 @@ module Utopia
|
|
65
65
|
def load!
|
66
66
|
@values ||= @loader.call
|
67
67
|
end
|
68
|
+
|
69
|
+
def loaded?
|
70
|
+
!@values.nil?
|
71
|
+
end
|
72
|
+
|
73
|
+
def needs_update?(timeout = nil)
|
74
|
+
# If data has changed, we need update:
|
75
|
+
return true if @changed
|
76
|
+
|
77
|
+
# We want to be careful here and not call load! which isn't cheap operation.
|
78
|
+
if timeout and @values and updated_at = @values[:updated_at]
|
79
|
+
# If the last update was too long ago, we need update:
|
80
|
+
return true if updated_at < (Time.now - timeout)
|
81
|
+
end
|
82
|
+
|
83
|
+
return false
|
84
|
+
end
|
68
85
|
end
|
69
86
|
end
|
70
87
|
end
|
data/lib/utopia/session.rb
CHANGED
@@ -28,30 +28,59 @@ module Utopia
|
|
28
28
|
class Session
|
29
29
|
RACK_SESSION = "rack.session".freeze
|
30
30
|
CIPHER_ALGORITHM = "aes-256-cbc"
|
31
|
-
KEY_LENGTH = 32
|
32
31
|
|
33
|
-
|
32
|
+
# The session will expire if no requests were made within 24 hours:
|
33
|
+
DEFAULT_EXPIRES_AFTER = 3600*24
|
34
|
+
|
35
|
+
# At least, the session will be updated every 1 hour:
|
36
|
+
DEFAULT_UPDATE_TIMEOUT = 3600
|
37
|
+
|
38
|
+
def initialize(app, session_name: nil, secret:, expires_after: nil, update_timeout: nil, **options)
|
34
39
|
@app = app
|
35
|
-
@cookie_name = options.delete(:cookie_name) || (RACK_SESSION + ".encrypted")
|
36
40
|
|
37
|
-
|
38
|
-
@
|
41
|
+
@session_name = session_name || RACK_SESSION
|
42
|
+
@cookie_name = @session_name + ".encrypted"
|
43
|
+
|
44
|
+
# This generates a 32-byte key suitable for aes.
|
45
|
+
@key = Digest::SHA2.digest(secret)
|
39
46
|
|
40
|
-
@
|
41
|
-
|
42
|
-
|
43
|
-
|
47
|
+
@expires_after = expires_after || DEFAULT_EXPIRES_AFTER
|
48
|
+
@update_timeout = update_timeout || DEFAULT_UPDATE_TIMEOUT
|
49
|
+
|
50
|
+
@cookie_defaults = {
|
51
|
+
domain: nil,
|
52
|
+
path: "/",
|
53
|
+
# The Secure attribute is meant to keep cookie communication limited to encrypted transmission, directing browsers to use cookies only via secure/encrypted connections. However, if a web server sets a cookie with a secure attribute from a non-secure connection, the cookie can still be intercepted when it is sent to the user by man-in-the-middle attacks. Therefore, for maximum security, cookies with the Secure attribute should only be set over a secure connection.
|
54
|
+
secure: false,
|
55
|
+
# The HttpOnly attribute directs browsers not to expose cookies through channels other than HTTP (and HTTPS) requests. This means that the cookie cannot be accessed via client-side scripting languages (notably JavaScript), and therefore cannot be stolen easily via cross-site scripting (a pervasive attack technique).
|
56
|
+
http_only: true,
|
44
57
|
}.merge(options)
|
45
58
|
end
|
59
|
+
|
60
|
+
attr :cookie_name
|
61
|
+
attr :key
|
62
|
+
|
63
|
+
attr :expires_after
|
64
|
+
attr :update_timeout
|
65
|
+
|
66
|
+
attr :cookie_defaults
|
67
|
+
|
68
|
+
def freeze
|
69
|
+
@cookie_name.freeze
|
70
|
+
@key.freeze
|
71
|
+
@expires_after.freeze
|
72
|
+
@update_timeout.freeze
|
73
|
+
@cookie_defaults.freeze
|
74
|
+
|
75
|
+
super
|
76
|
+
end
|
46
77
|
|
47
78
|
def call(env)
|
48
79
|
session_hash = prepare_session(env)
|
49
80
|
|
50
81
|
status, headers, body = @app.call(env)
|
51
82
|
|
52
|
-
|
53
|
-
commit(session_hash.values, headers)
|
54
|
-
end
|
83
|
+
update_session(env, session_hash, headers)
|
55
84
|
|
56
85
|
return [status, headers, body]
|
57
86
|
end
|
@@ -64,11 +93,25 @@ module Utopia
|
|
64
93
|
end
|
65
94
|
end
|
66
95
|
|
96
|
+
def update_session(env, session_hash, headers)
|
97
|
+
if session_hash.needs_update?(@update_timeout)
|
98
|
+
values = session_hash.values
|
99
|
+
|
100
|
+
values[:updated_at] = Time.now
|
101
|
+
|
102
|
+
data = encrypt(session_hash.values)
|
103
|
+
|
104
|
+
commit(data, headers)
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
67
108
|
# Constructs a valid session for the given request. These fields must match as per the checks performed in `valid_session?`:
|
68
109
|
def build_initial_session(request)
|
69
110
|
{
|
70
111
|
request_ip: request.ip,
|
71
112
|
request_user_agent: request.user_agent,
|
113
|
+
created_at: Time.now,
|
114
|
+
updated_at: Time.now,
|
72
115
|
}
|
73
116
|
end
|
74
117
|
|
@@ -100,14 +143,19 @@ module Utopia
|
|
100
143
|
return true
|
101
144
|
end
|
102
145
|
|
103
|
-
def
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
146
|
+
def expires
|
147
|
+
if @expires_after
|
148
|
+
return Time.now + @expires_after
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
def commit(value, headers)
|
153
|
+
cookie = {
|
154
|
+
value: value,
|
155
|
+
expires: expires
|
156
|
+
}.merge(@cookie_defaults)
|
109
157
|
|
110
|
-
Rack::Utils.set_cookie_header!(headers, @cookie_name, cookie
|
158
|
+
Rack::Utils.set_cookie_header!(headers, @cookie_name, cookie)
|
111
159
|
end
|
112
160
|
|
113
161
|
def encrypt(hash)
|
data/lib/utopia/version.rb
CHANGED
data/spec/utopia/session_spec.rb
CHANGED
@@ -46,8 +46,36 @@ module Utopia::SessionSpec
|
|
46
46
|
expect(last_response.header).to be_include 'Set-Cookie'
|
47
47
|
|
48
48
|
get "/session-get?key=foo"
|
49
|
+
expect(last_request.cookies).to include('rack.session.encrypted')
|
49
50
|
expect(last_response.body).to be == "bar"
|
50
51
|
end
|
52
|
+
|
53
|
+
it "should ignore session if cookie value is invalid" do
|
54
|
+
set_cookie 'rack.session.encrypted=junk'
|
55
|
+
|
56
|
+
get "/session-get?key=foo"
|
57
|
+
|
58
|
+
expect(last_response.body).to be == ""
|
59
|
+
end
|
60
|
+
|
61
|
+
it "shouldn't update the session if there are no changes" do
|
62
|
+
get "/session-set?key=foo&value=bar"
|
63
|
+
expect(last_response.header).to be_include 'Set-Cookie'
|
64
|
+
|
65
|
+
get "/session-set?key=foo&value=bar"
|
66
|
+
expect(last_response.header).to_not be_include 'Set-Cookie'
|
67
|
+
end
|
68
|
+
|
69
|
+
it "should update the session if time has passed" do
|
70
|
+
get "/session-set?key=foo&value=bar"
|
71
|
+
expect(last_response.header).to be_include 'Set-Cookie'
|
72
|
+
|
73
|
+
# Sleep more than update_timeout
|
74
|
+
sleep 2
|
75
|
+
|
76
|
+
get "/session-set?key=foo&value=bar"
|
77
|
+
expect(last_response.header).to be_include 'Set-Cookie'
|
78
|
+
end
|
51
79
|
end
|
52
80
|
|
53
81
|
describe Utopia::Session do
|
@@ -85,7 +113,7 @@ module Utopia::SessionSpec
|
|
85
113
|
end
|
86
114
|
|
87
115
|
describe Utopia::Session::LazyHash do
|
88
|
-
it "should load hash when required" do
|
116
|
+
it "should load hash only when required" do
|
89
117
|
loaded = false
|
90
118
|
|
91
119
|
hash = Utopia::Session::LazyHash.new do
|
@@ -100,14 +128,51 @@ module Utopia::SessionSpec
|
|
100
128
|
expect(loaded).to be true
|
101
129
|
end
|
102
130
|
|
131
|
+
it "should need to be reloaded if changed" do
|
132
|
+
hash = Utopia::Session::LazyHash.new do
|
133
|
+
{a: 10}
|
134
|
+
end
|
135
|
+
|
136
|
+
expect(hash.needs_update?).to be false
|
137
|
+
|
138
|
+
hash[:a] = 10
|
139
|
+
|
140
|
+
expect(hash.needs_update?).to be false
|
141
|
+
|
142
|
+
hash[:a] = 20
|
143
|
+
|
144
|
+
expect(hash.needs_update?).to be true
|
145
|
+
end
|
146
|
+
|
147
|
+
it "should need to be reloaded if old" do
|
148
|
+
hash = Utopia::Session::LazyHash.new do
|
149
|
+
{updated_at: Time.now - 3700}
|
150
|
+
end
|
151
|
+
|
152
|
+
expect(hash.needs_update?(3600)).to be false
|
153
|
+
|
154
|
+
expect(hash).to include(:updated_at)
|
155
|
+
|
156
|
+
# If the timeout is 2 hours, it shouldn't require any update:
|
157
|
+
expect(hash.needs_update?(3600*2)).to be false
|
158
|
+
|
159
|
+
# However if the timeout is 1 hour ago, it WILL require an update:
|
160
|
+
expect(hash.needs_update?(3600)).to be true
|
161
|
+
end
|
162
|
+
|
103
163
|
it "should delete the specified item" do
|
104
164
|
hash = Utopia::Session::LazyHash.new do
|
105
165
|
{a: 10, b: 20}
|
106
166
|
end
|
107
167
|
|
108
|
-
expect(hash.include
|
168
|
+
expect(hash).to include(:a, :b)
|
169
|
+
|
109
170
|
expect(hash.delete(:a)).to be 10
|
110
|
-
|
171
|
+
|
172
|
+
expect(hash).to include(:b)
|
173
|
+
expect(hash).to_not include(:a)
|
174
|
+
|
175
|
+
expect(hash.needs_update?).to be true
|
111
176
|
end
|
112
177
|
end
|
113
178
|
end
|
data/spec/utopia/session_spec.ru
CHANGED
@@ -1,5 +1,8 @@
|
|
1
1
|
|
2
|
-
use Utopia::Session,
|
2
|
+
use Utopia::Session,
|
3
|
+
secret: "97111cabf4c1a5e85b8029cf7c61aa44424fc24a",
|
4
|
+
expires_after: 3600 * 48,
|
5
|
+
update_timeout: 1
|
3
6
|
|
4
7
|
run lambda { |env|
|
5
8
|
request = Rack::Request.new(env)
|
@@ -9,11 +12,11 @@ run lambda { |env|
|
|
9
12
|
|
10
13
|
[200, {}, []]
|
11
14
|
elsif env[Rack::PATH_INFO] =~ /session-set/
|
12
|
-
env['rack.session'][request.params['key']] = request.params['value']
|
15
|
+
env['rack.session'][request.params['key'].to_sym] = request.params['value']
|
13
16
|
|
14
17
|
[200, {}, []]
|
15
18
|
elsif env[Rack::PATH_INFO] =~ /session-get/
|
16
|
-
[200, {}, [env['rack.session'][request.params['key']]]]
|
19
|
+
[200, {}, [env['rack.session'][request.params['key'].to_sym]]]
|
17
20
|
else
|
18
21
|
[404, {}, []]
|
19
22
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: utopia
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.8.
|
4
|
+
version: 1.8.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Samuel Williams
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-
|
11
|
+
date: 2016-11-03 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: trenni
|