require 'active_support/core_ext/hash/keys'
require 'action_dispatch/middleware/session/abstract_store'
require 'rack/session/cookie'
module ActionDispatch
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:
#
# * :secret: An application-wide key string. 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.
#
# secret: '449fe2e7daee471bffae2fd8dc02313d'
#
# * :digest: 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/initializers/secret_token.rb.
#
# Note that changing digest or secret invalidates all existing sessions!
class CookieStore < Rack::Session::Abstract::ID
include Compatibility
include StaleSessionCheck
include SessionObject
def initialize(app, options={})
super(app, options.merge!(:cookie_only => true))
end
def destroy_session(env, session_id, options)
new_sid = generate_sid unless options[:drop]
# Reset hash and Assign the new session id
env["action_dispatch.request.unsigned_session_cookie"] = new_sid ? { "session_id" => new_sid } : {}
new_sid
end
def load_session(env)
stale_session_check! do
data = unpacked_cookie_data(env)
data = persistent_session_id!(data)
[data["session_id"], data]
end
end
private
def extract_session_id(env)
stale_session_check! do
unpacked_cookie_data(env)["session_id"]
end
end
def unpacked_cookie_data(env)
env["action_dispatch.request.unsigned_session_cookie"] ||= begin
stale_session_check! do
if data = get_cookie(env)
data.stringify_keys!
end
data || {}
end
end
end
def persistent_session_id!(data, sid=nil)
data ||= {}
data["session_id"] ||= sid || generate_sid
data
end
def set_session(env, sid, session_data, options)
session_data["session_id"] = sid
session_data
end
def set_cookie(env, session_id, cookie)
cookie_jar(env)[@key] = cookie
end
def get_cookie(env)
cookie_jar(env)[@key]
end
def cookie_jar(env)
request = ActionDispatch::Request.new(env)
request.cookie_jar.signed
end
end
class EncryptedCookieStore < CookieStore
private
def cookie_jar(env)
request = ActionDispatch::Request.new(env)
request.cookie_jar.encrypted
end
end
# This cookie store helps you upgrading apps that use +CookieStore+ to the new default +EncryptedCookieStore+
# To use this CookieStore set
#
# Myapp::Application.config.session_store :upgrade_signature_to_encryption_cookie_store, key: '_myapp_session'
#
# in your config/initializers/session_store.rb
#
# You will also need to add
#
# Myapp::Application.config.secret_key_base = 'some secret'
#
# in your config/initializers/secret_token.rb, but do not remove +Myapp::Application.config.secret_token = 'some secret'+
class UpgradeSignatureToEncryptionCookieStore < EncryptedCookieStore
private
def get_cookie(env)
signed_using_old_secret_cookie_jar(env)[@key] || cookie_jar(env)[@key]
end
def signed_using_old_secret_cookie_jar(env)
request = ActionDispatch::Request.new(env)
request.cookie_jar.signed_using_old_secret
end
end
end
end