redis-session-store 0.11.5 → 0.11.6

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