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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 75627410dd935b6194426983f63b4f20c8510166
4
- data.tar.gz: 0d1870b25983cbad435ceb10ab4cf0d0de02435b
3
+ metadata.gz: 2ca9a6f28fcec98323ae67b683f903e44fb68eea
4
+ data.tar.gz: 1b86bb5bb7a36448df329b2aeda819feb2c66fa2
5
5
  SHA512:
6
- metadata.gz: 1dd33618145c3e88849b2761e9b8f33c17a78d7ea05d9a431124e16f57764cdcde9b13cb3d64a926f6f97e36393042f1c53f53d6f0e522dab3bad2b357bf478c
7
- data.tar.gz: 8f02447b3531c5bef12f79e1fae54d70a909cf8dfc6111dd05f25160439e1209708dd83d549d352f246b314897c61f26b6ff7add885f27da068e3414245f4297
6
+ metadata.gz: 7279e0472f9c4e1760368ee5c138f1338e2426dd2c194e5014a318b5faf2e0227c04bd09f556c32bc3abeca899270bbbd92208c7aaf454cac2100d4116a2b4c8
7
+ data.tar.gz: 5be21037ad98e014ece09d98ceef3812256aaa9239eb7ea770f68df6aa636f245fe9a5168ef545944e0206954a11ed0d7388c0083f3ff7237e35f62a4cbb80d9
data/.travis.yml CHANGED
@@ -8,9 +8,9 @@ rvm:
8
8
  - 2.2.4
9
9
  - 2.3.0
10
10
  - ruby-head
11
- - rbx
11
+ - rbx-3.65
12
12
  env: COVERAGE=true BENCHMARK=true
13
13
  matrix:
14
14
  allow_failures:
15
- - rvm: rbx
15
+ - rvm: rbx-3.65
16
16
  - rvm: ruby-head
@@ -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
@@ -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
- def initialize(app, secret:, **options)
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
- salt = OpenSSL::Random.random_bytes(16)
38
- @key = OpenSSL::PKCS5.pbkdf2_hmac_sha1(secret, salt, 1, KEY_LENGTH)
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
- @options = {
41
- :domain => nil,
42
- :path => "/",
43
- :expires_after => nil
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
- if session_hash.changed?
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 commit(values, headers)
104
- data = encrypt(values)
105
-
106
- cookie = {:value => data}
107
-
108
- cookie[:expires] = Time.now + @options[:expires_after] unless @options[:expires_after].nil?
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.merge(@options))
158
+ Rack::Utils.set_cookie_header!(headers, @cookie_name, cookie)
111
159
  end
112
160
 
113
161
  def encrypt(hash)
@@ -19,5 +19,5 @@
19
19
  # THE SOFTWARE.
20
20
 
21
21
  module Utopia
22
- VERSION = "1.8.1"
22
+ VERSION = "1.8.2"
23
23
  end
@@ -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?(:a)).to be true
168
+ expect(hash).to include(:a, :b)
169
+
109
170
  expect(hash.delete(:a)).to be 10
110
- expect(hash.include?(:a)).to be false
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
@@ -1,5 +1,8 @@
1
1
 
2
- use Utopia::Session, secret: "97111cabf4c1a5e85b8029cf7c61aa44424fc24a"
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.1
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-10-31 00:00:00.000000000 Z
11
+ date: 2016-11-03 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: trenni