2010-05-17 19:43:06 -04:00
|
|
|
require 'action_dispatch/middleware/cookies'
|
2009-12-22 18:11:21 -05:00
|
|
|
require 'active_support/core_ext/hash/keys'
|
2010-03-28 08:15:02 -04:00
|
|
|
require 'active_support/core_ext/object/blank'
|
2009-08-27 02:07:55 -04:00
|
|
|
|
2009-01-27 19:54:01 -05:00
|
|
|
module ActionDispatch
|
2008-12-15 17:33:31 -05:00
|
|
|
module Session
|
|
|
|
# This cookie-based session store is the Rails default. Sessions typically
|
|
|
|
# contain at most a user_id and flash message; both fit within the 4K cookie
|
|
|
|
# size limit. Cookie-based sessions are dramatically faster than the
|
|
|
|
# alternatives.
|
|
|
|
#
|
|
|
|
# If you have more than 4K of session data or don't want your data to be
|
|
|
|
# visible to the user, pick another session store.
|
|
|
|
#
|
|
|
|
# CookieOverflow is raised if you attempt to store more than 4K of data.
|
|
|
|
#
|
|
|
|
# A message digest is included with the cookie to ensure data integrity:
|
|
|
|
# a user cannot alter his +user_id+ without knowing the secret key
|
|
|
|
# included in the hash. New apps are generated with a pregenerated secret
|
|
|
|
# in config/environment.rb. Set your own for old apps you're upgrading.
|
|
|
|
#
|
|
|
|
# Session options:
|
|
|
|
#
|
|
|
|
# * <tt>:secret</tt>: An application-wide key string or block returning a
|
|
|
|
# string called per generated digest. The block is called with the
|
|
|
|
# CGI::Session instance as an argument. It's important that the secret
|
|
|
|
# is not vulnerable to a dictionary attack. Therefore, you should choose
|
|
|
|
# a secret consisting of random numbers and letters and more than 30
|
|
|
|
# characters. Examples:
|
|
|
|
#
|
|
|
|
# :secret => '449fe2e7daee471bffae2fd8dc02313d'
|
|
|
|
# :secret => Proc.new { User.current_user.secret_key }
|
|
|
|
#
|
|
|
|
# * <tt>:digest</tt>: The message digest algorithm used to verify session
|
|
|
|
# integrity defaults to 'SHA1' but may be any digest provided by OpenSSL,
|
|
|
|
# such as 'MD5', 'RIPEMD160', 'SHA256', etc.
|
|
|
|
#
|
|
|
|
# To generate a secret key for an existing application, run
|
|
|
|
# "rake secret" and set the key in config/environment.rb.
|
|
|
|
#
|
|
|
|
# Note that changing digest or secret invalidates all existing sessions!
|
2009-09-03 16:14:49 -04:00
|
|
|
class CookieStore
|
2008-12-15 17:33:31 -05:00
|
|
|
DEFAULT_OPTIONS = {
|
2008-12-18 13:56:18 -05:00
|
|
|
:key => '_session_id',
|
|
|
|
:domain => nil,
|
|
|
|
:path => "/",
|
|
|
|
:expire_after => nil,
|
2009-01-20 12:50:43 -05:00
|
|
|
:httponly => true
|
2008-12-15 17:33:31 -05:00
|
|
|
}.freeze
|
2009-12-22 18:11:21 -05:00
|
|
|
|
2009-09-03 12:50:01 -04:00
|
|
|
class OptionsHash < Hash
|
|
|
|
def initialize(by, env, default_options)
|
|
|
|
@session_data = env[CookieStore::ENV_SESSION_KEY]
|
2010-05-17 19:43:06 -04:00
|
|
|
merge!(default_options)
|
2009-09-03 12:50:01 -04:00
|
|
|
end
|
2008-12-15 17:33:31 -05:00
|
|
|
|
2009-09-03 12:50:01 -04:00
|
|
|
def [](key)
|
|
|
|
key == :id ? @session_data[:session_id] : super(key)
|
|
|
|
end
|
|
|
|
end
|
2009-12-22 18:11:21 -05:00
|
|
|
|
2008-12-15 17:33:31 -05:00
|
|
|
ENV_SESSION_KEY = "rack.session".freeze
|
|
|
|
ENV_SESSION_OPTIONS_KEY = "rack.session.options".freeze
|
|
|
|
|
|
|
|
def initialize(app, options = {})
|
2008-12-18 13:56:18 -05:00
|
|
|
# Process legacy CGI options
|
2010-05-17 19:43:06 -04:00
|
|
|
# TODO Refactor and deprecate me
|
2008-12-18 13:56:18 -05:00
|
|
|
options = options.symbolize_keys
|
2010-05-17 19:43:06 -04:00
|
|
|
|
2008-12-18 13:56:18 -05:00
|
|
|
if options.has_key?(:session_path)
|
|
|
|
options[:path] = options.delete(:session_path)
|
|
|
|
end
|
|
|
|
if options.has_key?(:session_key)
|
|
|
|
options[:key] = options.delete(:session_key)
|
|
|
|
end
|
|
|
|
if options.has_key?(:session_http_only)
|
|
|
|
options[:httponly] = options.delete(:session_http_only)
|
|
|
|
end
|
|
|
|
|
2008-12-15 17:33:31 -05:00
|
|
|
@app = app
|
|
|
|
|
|
|
|
# The session_key option is required.
|
|
|
|
ensure_session_key(options[:key])
|
|
|
|
@key = options.delete(:key).freeze
|
|
|
|
|
|
|
|
@default_options = DEFAULT_OPTIONS.merge(options).freeze
|
|
|
|
freeze
|
|
|
|
end
|
2007-03-13 16:44:16 -04:00
|
|
|
|
2008-12-15 17:33:31 -05:00
|
|
|
def call(env)
|
2008-12-20 15:37:51 -05:00
|
|
|
env[ENV_SESSION_KEY] = AbstractStore::SessionHash.new(self, env)
|
2009-09-03 12:50:01 -04:00
|
|
|
env[ENV_SESSION_OPTIONS_KEY] = OptionsHash.new(self, env, @default_options)
|
2009-12-22 18:11:21 -05:00
|
|
|
|
2008-12-15 17:33:31 -05:00
|
|
|
status, headers, body = @app.call(env)
|
2007-11-21 16:31:45 -05:00
|
|
|
|
2008-12-20 15:37:51 -05:00
|
|
|
session_data = env[ENV_SESSION_KEY]
|
2009-01-28 00:05:07 -05:00
|
|
|
options = env[ENV_SESSION_OPTIONS_KEY]
|
|
|
|
|
|
|
|
if !session_data.is_a?(AbstractStore::SessionHash) || session_data.send(:loaded?) || options[:expire_after]
|
|
|
|
session_data.send(:load!) if session_data.is_a?(AbstractStore::SessionHash) && !session_data.send(:loaded?)
|
2010-05-17 19:43:06 -04:00
|
|
|
session_data = persistent_session_id!(session_data.to_hash)
|
2007-02-21 04:17:38 -05:00
|
|
|
|
2010-05-17 19:43:06 -04:00
|
|
|
cookie = { :value => session_data }
|
2008-12-15 17:33:31 -05:00
|
|
|
unless options[:expire_after].nil?
|
2010-05-17 19:43:06 -04:00
|
|
|
cookie[:expires] = Time.now + options.delete(:expire_after)
|
2008-12-15 17:33:31 -05:00
|
|
|
end
|
2007-02-21 04:17:38 -05:00
|
|
|
|
2010-05-17 19:43:06 -04:00
|
|
|
request = ActionDispatch::Request.new(env)
|
|
|
|
request.cookie_jar.signed[@key] = cookie.merge!(options)
|
2008-12-15 17:33:31 -05:00
|
|
|
end
|
2007-02-21 04:17:38 -05:00
|
|
|
|
2008-12-15 17:33:31 -05:00
|
|
|
[status, headers, body]
|
2007-02-21 04:17:38 -05:00
|
|
|
end
|
|
|
|
|
2008-12-15 17:33:31 -05:00
|
|
|
private
|
|
|
|
|
|
|
|
def load_session(env)
|
2010-05-17 19:43:06 -04:00
|
|
|
request = ActionDispatch::Request.new(env)
|
|
|
|
data = request.cookie_jar.signed[@key]
|
|
|
|
data = persistent_session_id!(data || {})
|
2009-04-26 15:33:57 -04:00
|
|
|
data.stringify_keys!
|
|
|
|
[data["session_id"], data]
|
2008-12-15 17:33:31 -05:00
|
|
|
end
|
|
|
|
|
2010-05-17 19:43:06 -04:00
|
|
|
def generate_sid
|
|
|
|
ActiveSupport::SecureRandom.hex(16)
|
2008-12-15 17:33:31 -05:00
|
|
|
end
|
|
|
|
|
|
|
|
def ensure_session_key(key)
|
|
|
|
if key.blank?
|
2009-01-13 15:27:23 -05:00
|
|
|
raise ArgumentError, 'A key is required to write a ' +
|
2008-12-15 17:33:31 -05:00
|
|
|
'cookie containing the session data. Use ' +
|
2010-05-09 10:14:48 -04:00
|
|
|
'config.session_store :cookie_store, { :key => ' +
|
2010-03-04 18:06:25 -05:00
|
|
|
'"_myapp_session" } in config/application.rb'
|
2008-12-15 17:33:31 -05:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2008-12-18 12:33:53 -05:00
|
|
|
def persistent_session_id!(data)
|
|
|
|
(data ||= {}).merge!(inject_persistent_session_id(data))
|
|
|
|
end
|
|
|
|
|
|
|
|
def inject_persistent_session_id(data)
|
2009-04-26 15:33:57 -04:00
|
|
|
requires_session_id?(data) ? { "session_id" => generate_sid } : {}
|
2008-12-18 12:33:53 -05:00
|
|
|
end
|
|
|
|
|
|
|
|
def requires_session_id?(data)
|
|
|
|
if data
|
2009-04-26 15:33:57 -04:00
|
|
|
data.respond_to?(:key?) && !data.key?("session_id")
|
2008-12-18 12:33:53 -05:00
|
|
|
else
|
|
|
|
true
|
|
|
|
end
|
|
|
|
end
|
2008-11-23 10:42:15 -05:00
|
|
|
end
|
2008-12-15 17:33:31 -05:00
|
|
|
end
|
2007-02-21 04:17:38 -05:00
|
|
|
end
|