Skip to content

Commit

Permalink
Stable session identifier support for CGI::Session::CookieStore
Browse files Browse the repository at this point in the history
  • Loading branch information
methodmissing committed Jul 23, 2008
1 parent 183cc2f commit 7f83b13
Show file tree
Hide file tree
Showing 2 changed files with 36 additions and 4 deletions.
26 changes: 24 additions & 2 deletions actionpack/lib/action_controller/session/cookie_store.rb
Expand Up @@ -33,6 +33,11 @@
# integrity defaults to 'SHA1' but may be any digest provided by OpenSSL,
# such as 'MD5', 'RIPEMD160', 'SHA256', etc.
#
# * <tt>:stable_session_id</tt>: The Cookie Session Store doesn't maintain any server side
# state by default.A unique session identifier is spawned per request, which may not be
# desireable for some applications.Set :stable_session_id to true to maintain a stable
# session identifier within the cookie.
#
# To generate a secret key for an existing application, run
# "rake secret" and set the key in config/environment.rb.
#
Expand All @@ -50,6 +55,7 @@ class TamperedWithCookie < StandardError; end

# Called from CGI::Session only.
def initialize(session, options = {})
options.reverse_merge!( 'stable_session_id' => false )
# The session_key option is required.
if options['session_key'].blank?
raise ArgumentError, 'A session_key is required to write a cookie containing the session data. Use config.action_controller.session = { :session_key => "_myapp_session", :secret => "some secret phrase" } in config/environment.rb'
Expand All @@ -59,7 +65,7 @@ def initialize(session, options = {})
ensure_secret_secure(options['secret'])

# Keep the session and its secret on hand so we can read and write cookies.
@session, @secret = session, options['secret']
@session, @secret, @stable_session_id = session, options['secret'], options['stable_session_id']

# Message digest defaults to SHA1.
@digest = options['digest'] || 'SHA1'
Expand Down Expand Up @@ -129,6 +135,7 @@ def generate_digest(data)
private
# Marshal a session hash into safe cookie data. Include an integrity hash.
def marshal(session)
session = stable_session_id!( session )
data = ActiveSupport::Base64.encode64(Marshal.dump(session)).chop
"#{data}--#{generate_digest(data)}"
end
Expand All @@ -144,10 +151,25 @@ def unmarshal(cookie)
raise TamperedWithCookie
end

Marshal.load(ActiveSupport::Base64.decode64(data))
returning( stable_session_id!( Marshal.load(ActiveSupport::Base64.decode64(data)) ) ) do |data|
@session.instance_variable_set(:@session_id, data[:session_id]) if @stable_session_id
end
end
end

def stable_session_id!( data )
return data unless @stable_session_id
( data ||= {} ).merge( inject_stable_session_id!( data ) )
end

def inject_stable_session_id!( data )
if data.respond_to?(:key?) && !data.key?( :session_id )
{ :session_id => CGI::Session.generate_unique_id }
else
{}
end
end

# Read the session data cookie.
def read_cookie
@session.cgi.cookies[@cookie_options['name']].first
Expand Down
14 changes: 12 additions & 2 deletions actionpack/test/controller/session/cookie_store_test.rb
Expand Up @@ -178,6 +178,15 @@ def test_new_session_doesnt_reuse_deleted_cookie_data
end
end

def test_should_inject_a_radom_session_id_within_the_cookie
set_cookie! cookie_value(:typical)
new_session( 'stable_session_id' => true ) do |session|
assert_not_nil session['user_id']
assert_not_nil session[:session_id]
assert_not_equal session.session_id, cookie_value(:typical)
end
end

private
def assert_no_cookies(session)
assert_nil session.cgi.output_cookies, session.cgi.output_cookies.inspect
Expand Down Expand Up @@ -209,16 +218,17 @@ def set_cookie!(value)
end

def new_session(options = {})
#options.reverse_merge!( 'stable_session_id' => true )
with_cgi do |cgi|
assert_nil cgi.output_hidden, "Output hidden params should be empty: #{cgi.output_hidden.inspect}"
assert_nil cgi.output_cookies, "Output cookies should be empty: #{cgi.output_cookies.inspect}"

@options = self.class.default_session_options.merge(options)
session = CGI::Session.new(cgi, @options)

session = CGI::Session.new(cgi, @options)
assert_nil cgi.output_hidden, "Output hidden params should be empty: #{cgi.output_hidden.inspect}"
assert_nil cgi.output_cookies, "Output cookies should be empty: #{cgi.output_cookies.inspect}"

yield session if block_given?
session
end
Expand Down

0 comments on commit 7f83b13

Please sign in to comment.