Skip to content

Commit de902e4

Browse files
committed
Merge branch '1-6-sec' into 1-6-stable
* 1-6-sec: making diff smaller fix memcache tests on 1.6 fix tests on 1.6 Introduce a new base class to avoid breaking when upgrading Add a version prefix to the private id to make easier to migrate old values Fallback to the public id when reading the session in the pool adapter Also drop the session with the public id when destroying sessions Fallback to the legacy id when the new id is not found Add the private id revert conditionals to master remove NullSession remove || raise and get closer to master store hashed id, send public id use session id objects remove more nils try to ensure we always have some kind of object
2 parents b7d6546 + d3e2f88 commit de902e4

File tree

7 files changed

+196
-24
lines changed

7 files changed

+196
-24
lines changed

lib/rack/session/abstract/id.rb

Lines changed: 79 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,38 @@
99
rescue LoadError
1010
# We just won't get securerandom
1111
end
12+
require "digest/sha2"
1213

1314
module Rack
1415

1516
module Session
1617

18+
class SessionId
19+
ID_VERSION = 2
20+
21+
attr_reader :public_id
22+
23+
def initialize(public_id)
24+
@public_id = public_id
25+
end
26+
27+
def private_id
28+
"#{ID_VERSION}::#{hash_sid(public_id)}"
29+
end
30+
31+
alias :cookie_value :public_id
32+
33+
def empty?; false; end
34+
def to_s; raise; end
35+
def inspect; public_id.inspect; end
36+
37+
private
38+
39+
def hash_sid(sid)
40+
Digest::SHA256.hexdigest(sid)
41+
end
42+
end
43+
1744
module Abstract
1845
ENV_SESSION_KEY = 'rack.session'.freeze
1946
ENV_SESSION_OPTIONS_KEY = 'rack.session.options'.freeze
@@ -191,7 +218,7 @@ def stringify_keys(other)
191218
# Not included by default; you must require 'rack/session/abstract/id'
192219
# to use.
193220

194-
class ID
221+
class Persisted
195222
DEFAULT_OPTIONS = {
196223
:key => 'rack.session',
197224
:path => '/',
@@ -342,10 +369,10 @@ def commit_session(env, status, headers, body)
342369
if not data = set_session(env, session_id, session_data, options)
343370
env["rack.errors"].puts("Warning! #{self.class.name} failed to save session. Content dropped.")
344371
elsif options[:defer] and not options[:renew]
345-
env["rack.errors"].puts("Deferring cookie for #{session_id}") if $VERBOSE
372+
env["rack.errors"].puts("Deferring cookie for #{session_id.public_id}") if $VERBOSE
346373
else
347374
cookie = Hash.new
348-
cookie[:value] = data
375+
cookie[:value] = cookie_value(data)
349376
cookie[:expires] = Time.now + options[:expire_after] if options[:expire_after]
350377
cookie[:expires] = Time.now + options[:max_age] if options[:max_age]
351378
set_cookie(env, headers, cookie.merge!(options))
@@ -354,6 +381,10 @@ def commit_session(env, status, headers, body)
354381
[status, headers, body]
355382
end
356383

384+
def cookie_value(data)
385+
data
386+
end
387+
357388
# Sets the cookie back to the client with session id. We skip the cookie
358389
# setting if the value didn't change (sid is the same) or expires was given.
359390

@@ -394,6 +425,51 @@ def destroy_session(env, sid, options)
394425
raise '#destroy_session not implemented'
395426
end
396427
end
428+
429+
class PersistedSecure < Persisted
430+
class SecureSessionHash < SessionHash
431+
def [](key)
432+
if key == "session_id"
433+
load_for_read!
434+
id.public_id
435+
else
436+
super
437+
end
438+
end
439+
end
440+
441+
def generate_sid(*)
442+
public_id = super
443+
444+
SessionId.new(public_id)
445+
end
446+
447+
def extract_session_id(*)
448+
public_id = super
449+
public_id && SessionId.new(public_id)
450+
end
451+
452+
private
453+
454+
def session_class
455+
SecureSessionHash
456+
end
457+
458+
def cookie_value(data)
459+
data.cookie_value
460+
end
461+
end
462+
463+
class ID < Persisted
464+
def self.inherited(klass)
465+
k = klass.ancestors.find { |kl| kl.respond_to?(:superclass) && kl.superclass == ID }
466+
unless k.instance_variable_defined?(:"@_rack_warned")
467+
warn "#{klass} is inheriting from #{ID}. Inheriting from #{ID} is deprecated, please inherit from #{Persisted} instead" if $VERBOSE
468+
k.instance_variable_set(:"@_rack_warned", true)
469+
end
470+
super
471+
end
472+
end
397473
end
398474
end
399475
end

lib/rack/session/cookie.rb

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ module Session
4444
# })
4545
#
4646

47-
class Cookie < Abstract::ID
47+
class Cookie < Abstract::PersistedSecure
4848
# Encode session cookies as Base64
4949
class Base64
5050
def encode(str)
@@ -151,6 +151,15 @@ def persistent_session_id!(data, sid=nil)
151151
data
152152
end
153153

154+
class SessionId < DelegateClass(Session::SessionId)
155+
attr_reader :cookie_value
156+
157+
def initialize(session_id, cookie_value)
158+
super(session_id)
159+
@cookie_value = cookie_value
160+
end
161+
end
162+
154163
def set_session(env, session_id, session, options)
155164
session = session.merge("session_id" => session_id)
156165
session_data = coder.encode(session)
@@ -163,7 +172,7 @@ def set_session(env, session_id, session, options)
163172
env["rack.errors"].puts("Warning! Rack::Session::Cookie data size exceeds 4K.")
164173
nil
165174
else
166-
session_data
175+
SessionId.new(session_id, session_data)
167176
end
168177
end
169178

lib/rack/session/memcache.rb

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ module Session
1919
# Note that memcache does drop data before it may be listed to expire. For
2020
# a full description of behaviour, please see memcache's documentation.
2121

22-
class Memcache < Abstract::ID
22+
class Memcache < Abstract::PersistedSecure
2323
attr_reader :mutex, :pool
2424

2525
DEFAULT_OPTIONS = Abstract::ID::DEFAULT_OPTIONS.merge \
@@ -42,15 +42,15 @@ def initialize(app, options={})
4242
def generate_sid
4343
loop do
4444
sid = super
45-
break sid unless @pool.get(sid, true)
45+
break sid unless @pool.get(sid.private_id, true)
4646
end
4747
end
4848

4949
def get_session(env, sid)
5050
with_lock(env) do
51-
unless sid and session = @pool.get(sid)
51+
unless sid and session = get_session_with_fallback(sid)
5252
sid, session = generate_sid, {}
53-
unless /^STORED/ =~ @pool.add(sid, session)
53+
unless /^STORED/ =~ @pool.add(sid.private_id, session)
5454
raise "Session collision on '#{sid.inspect}'"
5555
end
5656
end
@@ -63,14 +63,15 @@ def set_session(env, session_id, new_session, options)
6363
expiry = expiry.nil? ? 0 : expiry + 1
6464

6565
with_lock(env) do
66-
@pool.set session_id, new_session, expiry
66+
@pool.set session_id.private_id, new_session, expiry
6767
session_id
6868
end
6969
end
7070

7171
def destroy_session(env, session_id, options)
7272
with_lock(env) do
73-
@pool.delete(session_id)
73+
@pool.delete(session_id.public_id)
74+
@pool.delete(session_id.private_id)
7475
generate_sid unless options[:drop]
7576
end
7677
end
@@ -88,6 +89,11 @@ def with_lock(env)
8889
@mutex.unlock if @mutex.locked?
8990
end
9091

92+
private
93+
94+
def get_session_with_fallback(sid)
95+
@pool.get(sid.private_id) || @pool.get(sid.public_id)
96+
end
9197
end
9298
end
9399
end

lib/rack/session/pool.rb

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ module Session
2424
# )
2525
# Rack::Handler::WEBrick.run sessioned
2626

27-
class Pool < Abstract::ID
27+
class Pool < Abstract::PersistedSecure
2828
attr_reader :mutex, :pool
2929
DEFAULT_OPTIONS = Abstract::ID::DEFAULT_OPTIONS.merge :drop => false
3030

@@ -37,30 +37,31 @@ def initialize(app, options={})
3737
def generate_sid
3838
loop do
3939
sid = super
40-
break sid unless @pool.key? sid
40+
break sid unless @pool.key? sid.private_id
4141
end
4242
end
4343

4444
def get_session(env, sid)
4545
with_lock(env) do
46-
unless sid and session = @pool[sid]
46+
unless sid and session = get_session_with_fallback(sid)
4747
sid, session = generate_sid, {}
48-
@pool.store sid, session
48+
@pool.store sid.private_id, session
4949
end
5050
[sid, session]
5151
end
5252
end
5353

5454
def set_session(env, session_id, new_session, options)
5555
with_lock(env) do
56-
@pool.store session_id, new_session
56+
@pool.store session_id.private_id, new_session
5757
session_id
5858
end
5959
end
6060

6161
def destroy_session(env, session_id, options)
6262
with_lock(env) do
63-
@pool.delete(session_id)
63+
@pool.delete(session_id.public_id)
64+
@pool.delete(session_id.private_id)
6465
generate_sid unless options[:drop]
6566
end
6667
end
@@ -71,6 +72,12 @@ def with_lock(env)
7172
ensure
7273
@mutex.unlock if @mutex.locked?
7374
end
75+
76+
private
77+
78+
def get_session_with_fallback(sid)
79+
@pool[sid.private_id] || @pool[sid.public_id]
80+
end
7481
end
7582
end
7683
end

test/spec_session_abstract_id.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ def hex(*args)
4747
end
4848
end
4949
id = Rack::Session::Abstract::ID.new nil, :secure_random => secure_random.new
50-
id.send(:generate_sid).should.eql 'fake_hex'
50+
id.send(:generate_sid).should.equal 'fake_hex'
5151
end
5252

5353
end

test/spec_session_memcache.rb

Lines changed: 40 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -225,15 +225,52 @@
225225
req = Rack::MockRequest.new(pool)
226226

227227
res0 = req.get("/")
228-
session_id = (cookie = res0["Set-Cookie"])[session_match, 1]
229-
ses0 = pool.pool.get(session_id, true)
228+
session_id = Rack::Session::SessionId.new (cookie = res0["Set-Cookie"])[session_match, 1]
229+
ses0 = pool.pool.get(session_id.private_id, true)
230230

231231
req.get("/", "HTTP_COOKIE" => cookie)
232-
ses1 = pool.pool.get(session_id, true)
232+
ses1 = pool.pool.get(session_id.private_id, true)
233233

234234
ses1.should.not.equal ses0
235235
end
236236

237+
it "can read the session with the legacy id" do
238+
pool = Rack::Session::Memcache.new(incrementor)
239+
req = Rack::MockRequest.new(pool)
240+
241+
res0 = req.get("/")
242+
cookie = res0["Set-Cookie"]
243+
session_id = Rack::Session::SessionId.new cookie[session_match, 1]
244+
ses0 = pool.pool.get(session_id.private_id, true)
245+
pool.pool.set(session_id.public_id, ses0, 0, true)
246+
pool.pool.delete(session_id.private_id)
247+
248+
res1 = req.get("/", "HTTP_COOKIE" => cookie)
249+
res1["Set-Cookie"].should.be.nil
250+
res1.body.should.equal '{"counter"=>2}'
251+
pool.pool.get(session_id.private_id, true).should.not.be.nil
252+
end
253+
254+
it "drops the session in the legacy id as well" do
255+
pool = Rack::Session::Memcache.new(incrementor)
256+
req = Rack::MockRequest.new(pool)
257+
drop = Rack::Utils::Context.new(pool, drop_session)
258+
dreq = Rack::MockRequest.new(drop)
259+
260+
res0 = req.get("/")
261+
cookie = res0["Set-Cookie"]
262+
session_id = Rack::Session::SessionId.new cookie[session_match, 1]
263+
ses0 = pool.pool.get(session_id.private_id, true)
264+
pool.pool.set(session_id.public_id, ses0, 0, true)
265+
pool.pool.delete(session_id.private_id)
266+
267+
res2 = dreq.get("/", "HTTP_COOKIE" => cookie)
268+
res2["Set-Cookie"].should.be.nil
269+
res2.body.should.equal '{"counter"=>2}'
270+
pool.pool.get(session_id.private_id, true).should.be.nil
271+
pool.pool.get(session_id.public_id, true).should.be.nil
272+
end
273+
237274
# anyone know how to do this better?
238275
it "cleanly merges sessions when multithreaded" do
239276
unless $DEBUG

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy