Skip to content

Commit

Permalink
Introduce a new base class to avoid breaking when upgrading
Browse files Browse the repository at this point in the history
Third-party session store would still need to be chaged to be more
secure but only upgrading rack will not break any application.
  • Loading branch information
rafaelfranca authored and tenderlove committed Dec 17, 2019
1 parent 5b1cab6 commit f1a79b2
Show file tree
Hide file tree
Showing 5 changed files with 54 additions and 22 deletions.
52 changes: 42 additions & 10 deletions lib/rack/session/abstract/id.rb
Original file line number Diff line number Diff line change
Expand Up @@ -81,11 +81,7 @@ def each(&block)

def [](key)
load_for_read!
if key == "session_id"
id.public_id
else
@data[key.to_s]
end
@data[key.to_s]
end

def fetch(key, default=Unspecified, &block)
Expand Down Expand Up @@ -283,13 +279,11 @@ def initialize_sid
# Monkey patch this to use custom methods for session id generation.

def generate_sid(secure = @sid_secure)
public_id = if secure
if secure
secure.hex(@sid_length)
else
"%0#{@sid_length}x" % Kernel.rand(2**@sidbits - 1)
end

SessionId.new(public_id)
rescue NotImplementedError
generate_sid(false)
end
Expand Down Expand Up @@ -319,7 +313,7 @@ def load_session(req)
def extract_session_id(request)
sid = request.cookies[@key]
sid ||= request.params[@key] unless @cookie_only
sid && SessionId.new(sid)
sid
end

# Returns the current session id from the SessionHash.
Expand Down Expand Up @@ -390,14 +384,18 @@ def commit_session(req, res)
req.get_header(RACK_ERRORS).puts("Deferring cookie for #{session_id}") if $VERBOSE
else
cookie = Hash.new
cookie[:value] = data.cookie_value
cookie[:value] = cookie_value(data)
cookie[:expires] = Time.now + options[:expire_after] if options[:expire_after]
cookie[:expires] = Time.now + options[:max_age] if options[:max_age]
set_cookie(req, res, cookie.merge!(options))
end
end
public :commit_session

def cookie_value(data)
data
end

# Sets the cookie back to the client with session id. We skip the cookie
# setting if the value didn't change (sid is the same) or expires was given.

Expand Down Expand Up @@ -439,6 +437,40 @@ def delete_session(req, sid, options)
end
end

class PersistedSecure < Persisted
class SecureSessionHash < SessionHash
def [](key)
if key == "session_id"
load_for_read!
id.public_id
else
super
end
end
end

def generate_sid(*)
public_id = super

SessionId.new(public_id)
end

def extract_session_id(*)
public_id = super
public_id && SessionId.new(public_id)
end

private

def session_class
SecureSessionHash
end

def cookie_value(data)
data.cookie_value
end
end

class ID < Persisted
def self.inherited(klass)
k = klass.ancestors.find { |kl| kl.respond_to?(:superclass) && kl.superclass == ID }
Expand Down
2 changes: 1 addition & 1 deletion lib/rack/session/cookie.rb
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ module Session
# })
#

class Cookie < Abstract::Persisted
class Cookie < Abstract::PersistedSecure
# Encode session cookies as Base64
class Base64
def encode(str)
Expand Down
18 changes: 9 additions & 9 deletions lib/rack/session/memcache.rb
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ module Session
# Note that memcache does drop data before it may be listed to expire. For
# a full description of behaviour, please see memcache's documentation.

class Memcache < Abstract::ID
class Memcache < Abstract::PersistedSecure
attr_reader :mutex, :pool

DEFAULT_OPTIONS = Abstract::ID::DEFAULT_OPTIONS.merge \
Expand All @@ -46,8 +46,8 @@ def generate_sid
end
end

def get_session(env, sid)
with_lock(env) do
def find_session(req, sid)
with_lock(req) do
unless sid and session = get_session_with_fallback(sid)
sid, session = generate_sid, {}
unless /^STORED/ =~ @pool.add(sid.private_id, session)
Expand All @@ -58,26 +58,26 @@ def get_session(env, sid)
end
end

def set_session(env, session_id, new_session, options)
def write_session(req, session_id, new_session, options)
expiry = options[:expire_after]
expiry = expiry.nil? ? 0 : expiry + 1

with_lock(env) do
with_lock(req) do
@pool.set session_id.private_id, new_session, expiry
session_id
end
end

def destroy_session(env, session_id, options)
with_lock(env) do
def delete_session(req, session_id, options)
with_lock(req) do
@pool.delete(session_id.public_id)
@pool.delete(session_id.private_id)
generate_sid unless options[:drop]
end
end

def with_lock(env)
@mutex.lock if env[RACK_MULTITHREAD]
def with_lock(req)
@mutex.lock if req.multithread?
yield
rescue MemCache::MemCacheError, Errno::ECONNREFUSED
if $VERBOSE
Expand Down
2 changes: 1 addition & 1 deletion lib/rack/session/pool.rb
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ module Session
# )
# Rack::Handler::WEBrick.run sessioned

class Pool < Abstract::Persisted
class Pool < Abstract::PersistedSecure
attr_reader :mutex, :pool
DEFAULT_OPTIONS = Abstract::ID::DEFAULT_OPTIONS.merge :drop => false

Expand Down
2 changes: 1 addition & 1 deletion test/spec_session_abstract_id.rb
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ def hex(*args)
end
end
id = Rack::Session::Abstract::ID.new nil, :secure_random => secure_random.new
id.send(:generate_sid).public_id.must_equal 'fake_hex'
id.send(:generate_sid).must_equal 'fake_hex'
end

end

0 comments on commit f1a79b2

Please sign in to comment.