utopia 1.8.1 → 1.8.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|