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 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