redis-session-store 0.11.5 → 0.11.6

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
  SHA256:
3
- metadata.gz: 90331bd500f283f691772c63fe0fd9800433c50aca9a380ff5bde853bf70fd19
4
- data.tar.gz: 6d5b6f2f7017261c21f6265de114ac78d7febcfacfa50e308a56dcb15a4ae216
3
+ metadata.gz: d18e9f3e69775d747faf496a6dcd718aaae738759306fc85fb158e0670de9588
4
+ data.tar.gz: bd5457371142d59eb6b2009b6b3add32efcc57bb886ec6e05b415298031ad9d8
5
5
  SHA512:
6
- metadata.gz: a7bb1ac24ac0cbc84f7d923047c8d05fdab9936c53bb1faa6b571617fc0b59706ca386527094aa5d2533cb9867dc00990404836b578077c4121805ee9eb4fce6
7
- data.tar.gz: 50fa31bdfe84b874f97ee55e86eb8a575ad49eccd60f22b8315e23e1cf87e01940470b46ddf089ee9d5fc720112abf8010e1ae6983c85993baa20237c3380946
6
+ metadata.gz: 69db8ffaf3ba8bfa9002f2355474b213ab9e7091338eeb63e3049ea31e575a461ed770e6024be89a925dccbaf8a1b4f62b261e5b927b30b1ef041f3e92d9f275
7
+ data.tar.gz: 3f4af5b4cd6b806c44407d2ad8fd536873c03fc72db4e20a386afaffc3a681a98ab7c7755b478c56d656c71439d1a44b651413d92ec01d3c6a60b4824d971c61
data/.rubocop.yml CHANGED
@@ -24,7 +24,10 @@ Layout/LineLength:
24
24
  Max: 100
25
25
 
26
26
  Metrics/ClassLength:
27
- Max: 120
27
+ Max: 140
28
+
29
+ RSpec/MultipleExpectations:
30
+ Max: 3
28
31
 
29
32
  Security/MarshalLoad:
30
33
  Enabled: false
data/AUTHORS.md CHANGED
@@ -2,6 +2,7 @@ Redis Session Store authors
2
2
  ===========================
3
3
 
4
4
  - Ben Marini
5
+ - Bill Ruddock
5
6
  - Dan Buch
6
7
  - Donald Plummer
7
8
  - Edwin Cruz
data/CHANGELOG.md CHANGED
@@ -4,6 +4,10 @@
4
4
 
5
5
  ## [Unreleased]
6
6
 
7
+ # [0.11.6] - 2024-11-12
8
+ - Fix secure session using private_id
9
+ - Support Rails 8
10
+
7
11
  # [0.11.5] - 2022-11-27
8
12
 
9
13
  ### Changed
@@ -3,7 +3,7 @@ require 'redis'
3
3
  # Redis session storage for Rails, and for Rails only. Derived from
4
4
  # the MemCacheStore code, simply dropping in Redis instead.
5
5
  class RedisSessionStore < ActionDispatch::Session::AbstractSecureStore
6
- VERSION = '0.11.5'.freeze
6
+ VERSION = '0.11.6'.freeze
7
7
  # Rails 3.1 and beyond defines the constant elsewhere
8
8
  unless defined?(ENV_SESSION_OPTIONS_KEY)
9
9
  ENV_SESSION_OPTIONS_KEY = if Rack.release.split('.').first.to_i > 1
@@ -65,7 +65,7 @@ class RedisSessionStore < ActionDispatch::Session::AbstractSecureStore
65
65
 
66
66
  !!(
67
67
  value && !value.empty? &&
68
- key_exists?(value)
68
+ key_exists_with_fallback?(value)
69
69
  )
70
70
  rescue Errno::ECONNREFUSED, Redis::CannotConnectError => e
71
71
  on_redis_down.call(e, env, value) if on_redis_down
@@ -73,6 +73,12 @@ class RedisSessionStore < ActionDispatch::Session::AbstractSecureStore
73
73
  true
74
74
  end
75
75
 
76
+ def key_exists_with_fallback?(value)
77
+ return false if private_session_id?(value.public_id)
78
+
79
+ key_exists?(value.private_id) || key_exists?(value.public_id)
80
+ end
81
+
76
82
  def key_exists?(value)
77
83
  if redis.respond_to?(:exists?)
78
84
  # added in redis gem v4.2
@@ -83,6 +89,10 @@ class RedisSessionStore < ActionDispatch::Session::AbstractSecureStore
83
89
  end
84
90
  end
85
91
 
92
+ def private_session_id?(value)
93
+ value.match?(/\A\d+::/)
94
+ end
95
+
86
96
  def verify_handlers!
87
97
  %w(on_redis_down on_session_load_error).each do |h|
88
98
  next unless (handler = public_send(h)) && !handler.respond_to?(:call)
@@ -100,13 +110,21 @@ class RedisSessionStore < ActionDispatch::Session::AbstractSecureStore
100
110
  end
101
111
 
102
112
  def get_session(env, sid)
103
- sid && (session = load_session_from_redis(sid)) ? [sid, session] : session_default_values
113
+ sid && (session = load_session_with_fallback(sid)) ? [sid, session] : session_default_values
104
114
  rescue Errno::ECONNREFUSED, Redis::CannotConnectError => e
105
115
  on_redis_down.call(e, env, sid) if on_redis_down
106
116
  session_default_values
107
117
  end
108
118
  alias find_session get_session
109
119
 
120
+ def load_session_with_fallback(sid)
121
+ return nil if private_session_id?(sid.public_id)
122
+
123
+ load_session_from_redis(
124
+ key_exists?(sid.private_id) ? sid.private_id : sid.public_id
125
+ )
126
+ end
127
+
110
128
  def load_session_from_redis(sid)
111
129
  data = redis.get(prefixed(sid))
112
130
  begin
@@ -126,9 +144,9 @@ class RedisSessionStore < ActionDispatch::Session::AbstractSecureStore
126
144
  def set_session(env, sid, session_data, options = nil)
127
145
  expiry = get_expiry(env, options)
128
146
  if expiry
129
- redis.setex(prefixed(sid), expiry, encode(session_data))
147
+ redis.setex(prefixed(sid.private_id), expiry, encode(session_data))
130
148
  else
131
- redis.set(prefixed(sid), encode(session_data))
149
+ redis.set(prefixed(sid.private_id), encode(session_data))
132
150
  end
133
151
  sid
134
152
  rescue Errno::ECONNREFUSED, Redis::CannotConnectError => e
@@ -147,14 +165,17 @@ class RedisSessionStore < ActionDispatch::Session::AbstractSecureStore
147
165
  end
148
166
 
149
167
  def destroy_session(env, sid, options)
150
- destroy_session_from_sid(sid, (options || {}).to_hash.merge(env: env))
168
+ destroy_session_from_sid(sid.public_id, (options || {}).to_hash.merge(env: env, drop: true))
169
+ destroy_session_from_sid(sid.private_id, (options || {}).to_hash.merge(env: env))
151
170
  end
152
171
  alias delete_session destroy_session
153
172
 
154
173
  def destroy(env)
155
174
  if env['rack.request.cookie_hash'] &&
156
175
  (sid = env['rack.request.cookie_hash'][key])
157
- destroy_session_from_sid(sid, drop: true, env: env)
176
+ sid = Rack::Session::SessionId.new(sid)
177
+ destroy_session_from_sid(sid.private_id, drop: true, env: env)
178
+ destroy_session_from_sid(sid.public_id, drop: true, env: env)
158
179
  end
159
180
  false
160
181
  end
@@ -15,7 +15,7 @@ Gem::Specification.new do |gem|
15
15
  gem.version = File.read('lib/redis-session-store.rb')
16
16
  .match(/^ VERSION = '(.*)'/)[1]
17
17
 
18
- gem.add_runtime_dependency 'actionpack', '>= 6', '< 8'
18
+ gem.add_runtime_dependency 'actionpack', '>= 5.2.4.1', '< 9'
19
19
  gem.add_runtime_dependency 'redis', '>= 3', '< 6'
20
20
 
21
21
  gem.add_development_dependency 'fakeredis', '~> 0.8'
@@ -157,7 +157,7 @@ describe RedisSessionStore do
157
157
  # https://github.com/rack/rack/blob/1.4.5/lib/rack/session/abstract/id.rb
158
158
 
159
159
  let(:env) { double('env') }
160
- let(:session_id) { 12_345 }
160
+ let(:session_id) { Rack::Session::SessionId.new('12 345') }
161
161
  let(:session_data) { double('session_data') }
162
162
  let(:options) { { expire_after: 123 } }
163
163
 
@@ -217,7 +217,8 @@ describe RedisSessionStore do
217
217
  end
218
218
 
219
219
  describe 'checking for session existence' do
220
- let(:session_id) { 'foo' }
220
+ let(:public_id) { 'foo' }
221
+ let(:session_id) { Rack::Session::SessionId.new(public_id) }
221
222
 
222
223
  before do
223
224
  allow(store).to receive(:current_session_id)
@@ -234,10 +235,9 @@ describe RedisSessionStore do
234
235
  end
235
236
 
236
237
  context 'when session id is empty string' do
237
- let(:session_id) { '' }
238
+ let(:public_id) { '' }
238
239
 
239
240
  it 'returns false' do
240
- allow(store).to receive(:current_session_id).with(:env).and_return('')
241
241
  expect(store.send(:session_exists?, :env)).to eq(false)
242
242
  end
243
243
  end
@@ -250,20 +250,46 @@ describe RedisSessionStore do
250
250
  end
251
251
  end
252
252
 
253
- context 'when session id does not exist in redis' do
254
- it 'returns false' do
255
- expect(redis).to receive(:exists).with('foo').and_return(false)
256
- expect(store.send(:session_exists?, :env)).to eq(false)
253
+ context 'when session private id does not exist in redis' do
254
+ context 'when session public id does not exist in redis' do
255
+ it 'returns false' do
256
+ expect(redis).to receive(:exists)
257
+ .with(session_id.private_id)
258
+ .and_return(false)
259
+ expect(redis).to receive(:exists).with('foo').and_return(false)
260
+ expect(store.send(:session_exists?, :env)).to eq(false)
261
+ end
262
+ end
263
+
264
+ context 'when session public id exists in redis' do
265
+ it 'returns true' do
266
+ expect(redis).to receive(:exists)
267
+ .with(session_id.private_id)
268
+ .and_return(false)
269
+ expect(redis).to receive(:exists).with('foo').and_return(true)
270
+ expect(store.send(:session_exists?, :env)).to eq(true)
271
+ end
257
272
  end
258
273
  end
259
274
 
260
- context 'when session id exists in redis' do
275
+ context 'when session private id exists in redis' do
261
276
  it 'returns true' do
262
- expect(redis).to receive(:exists).with('foo').and_return(true)
277
+ expect(redis).to receive(:exists)
278
+ .with(session_id.private_id)
279
+ .and_return(true)
263
280
  expect(store.send(:session_exists?, :env)).to eq(true)
264
281
  end
265
282
  end
266
283
 
284
+ context 'when session public id is formatted like a private id' do
285
+ let(:public_id) { Rack::Session::SessionId.new('foo').private_id }
286
+
287
+ it 'returns false' do
288
+ expect(redis).not_to receive(:exists)
289
+ expect(store.send(:session_exists?, :env)).to eq(false)
290
+ end
291
+ end
292
+
267
293
  context 'when redis is down' do
268
294
  it 'returns true (fallback to old behavior)' do
269
295
  allow(store).to receive(:redis).and_raise(Redis::CannotConnectError)
@@ -281,6 +307,7 @@ describe RedisSessionStore do
281
307
  end
282
308
 
283
309
  let(:fake_key) { 'thisisarediskey' }
310
+ let(:session_id) { Rack::Session::SessionId.new(fake_key) }
284
311
 
285
312
  describe 'generate_sid' do
286
313
  it 'generates a secure ID' do
@@ -289,13 +316,50 @@ describe RedisSessionStore do
289
316
  end
290
317
  end
291
318
 
292
- it 'retrieves the prefixed key from redis' do
293
- redis = double('redis')
294
- allow(store).to receive(:redis).and_return(redis)
295
- allow(store).to receive(:generate_sid).and_return(fake_key)
296
- expect(redis).to receive(:get).with("#{options[:key_prefix]}#{fake_key}")
319
+ context 'when redis is up' do
320
+ let(:redis) { double('redis') }
321
+ let(:private_exists) { true }
322
+
323
+ before do
324
+ allow(store).to receive(:redis).and_return(redis)
325
+ allow(redis).to receive(:exists)
326
+ .with("#{options[:key_prefix]}#{session_id.private_id}")
327
+ .and_return(private_exists)
328
+ end
297
329
 
298
- store.send(:get_session, double('env'), fake_key)
330
+ context 'when session private id exists in redis' do
331
+ it 'retrieves the prefixed private id from redis' do
332
+ expect(redis).to receive(:get).with("#{options[:key_prefix]}#{session_id.private_id}")
333
+
334
+ store.send(:get_session, double('env'), session_id)
335
+ end
336
+ end
337
+
338
+ context 'when session private id not found in redis' do
339
+ let(:private_exists) { false }
340
+
341
+ it 'retrieves the prefixed public id from redis' do
342
+ expect(redis).to receive(:get).with("#{options[:key_prefix]}#{fake_key}")
343
+
344
+ store.send(:get_session, double('env'), session_id)
345
+ end
346
+ end
347
+
348
+ context 'when session id is formatted like a private id' do
349
+ let(:fake_key) { Rack::Session::SessionId.new('anykey').private_id }
350
+ let(:new_sid) { Rack::Session::SessionId.new('newid') }
351
+
352
+ before do
353
+ allow(store).to receive(:generate_sid).and_return(new_sid)
354
+ end
355
+
356
+ it 'returns a default new session' do
357
+ expect(redis).not_to receive(:exists)
358
+ expect(redis).not_to receive(:get)
359
+ expect(store.send(:get_session, double('env'), session_id))
360
+ .to eq([new_sid, {}])
361
+ end
362
+ end
299
363
  end
300
364
 
301
365
  context 'when redis is down' do
@@ -305,12 +369,12 @@ describe RedisSessionStore do
305
369
  end
306
370
 
307
371
  it 'returns an empty session hash' do
308
- expect(store.send(:get_session, double('env'), fake_key).last)
372
+ expect(store.send(:get_session, double('env'), session_id).last)
309
373
  .to eq({})
310
374
  end
311
375
 
312
376
  it 'returns a newly generated sid' do
313
- expect(store.send(:get_session, double('env'), fake_key).first)
377
+ expect(store.send(:get_session, double('env'), session_id).first)
314
378
  .to eq('foop')
315
379
  end
316
380
 
@@ -319,7 +383,7 @@ describe RedisSessionStore do
319
383
 
320
384
  it 'explodes' do
321
385
  expect do
322
- store.send(:get_session, double('env'), fake_key)
386
+ store.send(:get_session, double('env'), session_id)
323
387
  end.to raise_error(Redis::CannotConnectError)
324
388
  end
325
389
  end
@@ -331,6 +395,7 @@ describe RedisSessionStore do
331
395
  let(:env) { { 'rack.request.cookie_hash' => cookie_hash } }
332
396
  let(:cookie_hash) { double('cookie hash') }
333
397
  let(:fake_key) { 'thisisarediskey' }
398
+ let(:session_id) { Rack::Session::SessionId.new(fake_key) }
334
399
 
335
400
  before do
336
401
  allow(cookie_hash).to receive(:[]).and_return(fake_key)
@@ -341,6 +406,8 @@ describe RedisSessionStore do
341
406
  allow(store).to receive(:redis).and_return(redis)
342
407
  expect(redis).to receive(:del)
343
408
  .with("#{options[:key_prefix]}#{fake_key}")
409
+ expect(redis).to receive(:del)
410
+ .with("#{options[:key_prefix]}#{session_id.private_id}")
344
411
 
345
412
  store.send(:destroy, env)
346
413
  end
@@ -371,7 +438,8 @@ describe RedisSessionStore do
371
438
  redis = double('redis', setnx: true)
372
439
  allow(store).to receive(:redis).and_return(redis)
373
440
  sid = store.send(:generate_sid)
374
- expect(redis).to receive(:del).with("#{options[:key_prefix]}#{sid}")
441
+ expect(redis).to receive(:del).with("#{options[:key_prefix]}#{sid.public_id}")
442
+ expect(redis).to receive(:del).with("#{options[:key_prefix]}#{sid.private_id}")
375
443
 
376
444
  store.send(:destroy_session, {}, sid, nil)
377
445
  end
@@ -380,7 +448,7 @@ describe RedisSessionStore do
380
448
 
381
449
  describe 'session encoding' do
382
450
  let(:env) { double('env') }
383
- let(:session_id) { 12_345 }
451
+ let(:session_id) { Rack::Session::SessionId.new('12 345') }
384
452
  let(:session_data) { { 'some' => 'data' } }
385
453
  let(:options) { {} }
386
454
  let(:encoded_data) { Marshal.dump(session_data) }
@@ -393,11 +461,12 @@ describe RedisSessionStore do
393
461
 
394
462
  shared_examples_for 'serializer' do
395
463
  it 'encodes correctly' do
396
- expect(redis).to receive(:set).with('12345', expected_encoding)
464
+ expect(redis).to receive(:set).with(session_id.private_id, expected_encoding)
397
465
  store.send(:set_session, env, session_id, session_data, options)
398
466
  end
399
467
 
400
468
  it 'decodes correctly' do
469
+ allow(redis).to receive(:exists).with(session_id.private_id).and_return(true)
401
470
  expect(store.send(:get_session, env, session_id))
402
471
  .to eq([session_id, session_data])
403
472
  end
@@ -548,7 +617,7 @@ describe RedisSessionStore do
548
617
  describe 'setting the session' do
549
618
  it 'allows changing the session' do
550
619
  env = { 'rack.session.options' => {} }
551
- sid = 1234
620
+ sid = Rack::Session::SessionId.new('1234')
552
621
  allow(store).to receive(:redis).and_return(Redis.new)
553
622
  data1 = { 'foo' => 'bar' }
554
623
  store.send(:set_session, env, sid, data1)
@@ -560,7 +629,7 @@ describe RedisSessionStore do
560
629
 
561
630
  it 'allows changing the session when the session has an expiry' do
562
631
  env = { 'rack.session.options' => { expire_after: 60 } }
563
- sid = 1234
632
+ sid = Rack::Session::SessionId.new('1234')
564
633
  allow(store).to receive(:redis).and_return(Redis.new)
565
634
  data1 = { 'foo' => 'bar' }
566
635
  store.send(:set_session, env, sid, data1)
data/spec/support.rb CHANGED
@@ -11,10 +11,32 @@ unless defined?(Rack::Session::SessionId)
11
11
  module Rack
12
12
  module Session
13
13
  class SessionId
14
+ ID_VERSION = 2
15
+
14
16
  attr_reader :public_id
15
17
 
16
- def initialize(_public_id)
17
- @public_id
18
+ def initialize(public_id)
19
+ @public_id = public_id
20
+ end
21
+
22
+ alias to_s public_id
23
+
24
+ def empty?
25
+ false
26
+ end
27
+
28
+ def inspect
29
+ public_id.inspect
30
+ end
31
+
32
+ def private_id
33
+ "#{ID_VERSION}::#{hash_sid(public_id)}"
34
+ end
35
+
36
+ private
37
+
38
+ def hash_sid(value)
39
+ "test_hash_from:#{value}"
18
40
  end
19
41
  end
20
42
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: redis-session-store
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.11.5
4
+ version: 0.11.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mathias Meyer
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-11-27 00:00:00.000000000 Z
11
+ date: 2024-11-17 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: actionpack
@@ -16,20 +16,20 @@ dependencies:
16
16
  requirements:
17
17
  - - ">="
18
18
  - !ruby/object:Gem::Version
19
- version: '6'
19
+ version: 5.2.4.1
20
20
  - - "<"
21
21
  - !ruby/object:Gem::Version
22
- version: '8'
22
+ version: '9'
23
23
  type: :runtime
24
24
  prerelease: false
25
25
  version_requirements: !ruby/object:Gem::Requirement
26
26
  requirements:
27
27
  - - ">="
28
28
  - !ruby/object:Gem::Version
29
- version: '6'
29
+ version: 5.2.4.1
30
30
  - - "<"
31
31
  - !ruby/object:Gem::Version
32
- version: '8'
32
+ version: '9'
33
33
  - !ruby/object:Gem::Dependency
34
34
  name: redis
35
35
  requirement: !ruby/object:Gem::Requirement
@@ -197,7 +197,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
197
197
  - !ruby/object:Gem::Version
198
198
  version: '0'
199
199
  requirements: []
200
- rubygems_version: 3.1.6
200
+ rubygems_version: 3.5.23
201
201
  signing_key:
202
202
  specification_version: 4
203
203
  summary: A drop-in replacement for e.g. MemCacheStore to store Rails sessions (and