9e909d5be3
Update Bundler to 1.0.15. Update Rails to 2.3.12. Update rails_xss plugin. The latter two were the source of a considerable amount of grief, as rails_xss is now MUCH stricter about what string methods can be used. Also made it possible to use rake 0.9.x with Instiki. But you probably REALLY want to use ruby bundle exec rake ... instead of just saying rake ....
277 lines
7.6 KiB
Ruby
277 lines
7.6 KiB
Ruby
require 'rack/utils'
|
|
|
|
module ActionController
|
|
module Session
|
|
class AbstractStore
|
|
ENV_SESSION_KEY = 'rack.session'.freeze
|
|
ENV_SESSION_OPTIONS_KEY = 'rack.session.options'.freeze
|
|
|
|
HTTP_COOKIE = 'HTTP_COOKIE'.freeze
|
|
SET_COOKIE = 'Set-Cookie'.freeze
|
|
|
|
# thin wrapper around Hash that allows us to lazily
|
|
# load session id into session_options
|
|
class OptionsHash < Hash
|
|
def initialize(by, env, default_options)
|
|
@by = by
|
|
@env = env
|
|
@session_id_loaded = false
|
|
merge!(default_options)
|
|
end
|
|
|
|
def [](key)
|
|
if key == :id
|
|
load_session_id! unless super(:id) || has_session_id?
|
|
end
|
|
super(key)
|
|
end
|
|
|
|
private
|
|
|
|
def has_session_id?
|
|
@session_id_loaded
|
|
end
|
|
|
|
def load_session_id!
|
|
self[:id] = @by.send(:extract_session_id, @env)
|
|
@session_id_loaded = true
|
|
end
|
|
end
|
|
|
|
class SessionHash < Hash
|
|
def initialize(by, env)
|
|
super()
|
|
@by = by
|
|
@env = env
|
|
@loaded = false
|
|
end
|
|
|
|
def session_id
|
|
ActiveSupport::Deprecation.warn(
|
|
"ActionController::Session::AbstractStore::SessionHash#session_id " +
|
|
"has been deprecated. Please use request.session_options[:id] instead.", caller)
|
|
@env[ENV_SESSION_OPTIONS_KEY][:id]
|
|
end
|
|
|
|
def [](key)
|
|
load_for_read!
|
|
super
|
|
end
|
|
|
|
def has_key?(key)
|
|
load_for_read!
|
|
super
|
|
end
|
|
|
|
def []=(key, value)
|
|
load_for_write!
|
|
super
|
|
end
|
|
|
|
def clear
|
|
load_for_write!
|
|
super
|
|
end
|
|
|
|
def to_hash
|
|
load_for_read!
|
|
h = {}.replace(self)
|
|
h.delete_if { |k,v| v.nil? }
|
|
h
|
|
end
|
|
|
|
def update(hash)
|
|
load_for_write!
|
|
super
|
|
end
|
|
|
|
def delete(key)
|
|
load_for_write!
|
|
super
|
|
end
|
|
|
|
def data
|
|
ActiveSupport::Deprecation.warn(
|
|
"ActionController::Session::AbstractStore::SessionHash#data " +
|
|
"has been deprecated. Please use #to_hash instead.", caller)
|
|
to_hash
|
|
end
|
|
|
|
def inspect
|
|
load_for_read!
|
|
super
|
|
end
|
|
|
|
def exists?
|
|
return @exists if instance_variable_defined?(:@exists)
|
|
@exists = @by.send(:exists?, @env)
|
|
end
|
|
|
|
def loaded?
|
|
@loaded
|
|
end
|
|
|
|
def destroy
|
|
clear
|
|
@by.send(:destroy, @env) if @by
|
|
@env[ENV_SESSION_OPTIONS_KEY][:id] = nil if @env && @env[ENV_SESSION_OPTIONS_KEY]
|
|
@loaded = false
|
|
end
|
|
|
|
private
|
|
|
|
def load_for_read!
|
|
load! if !loaded? && exists?
|
|
end
|
|
|
|
def load_for_write!
|
|
load! unless loaded?
|
|
end
|
|
|
|
def load!
|
|
id, session = @by.send(:load_session, @env)
|
|
@env[ENV_SESSION_OPTIONS_KEY][:id] = id
|
|
replace(session)
|
|
@loaded = true
|
|
end
|
|
|
|
end
|
|
|
|
DEFAULT_OPTIONS = {
|
|
:key => '_session_id',
|
|
:path => '/',
|
|
:domain => nil,
|
|
:expire_after => nil,
|
|
:secure => false,
|
|
:httponly => true,
|
|
:cookie_only => true
|
|
}
|
|
|
|
def initialize(app, options = {})
|
|
# Process legacy CGI options
|
|
options = options.symbolize_keys
|
|
if options.has_key?(:session_path)
|
|
ActiveSupport::Deprecation.warn "Giving :session_path to SessionStore is deprecated, " <<
|
|
"please use :path instead", caller
|
|
options[:path] = options.delete(:session_path)
|
|
end
|
|
if options.has_key?(:session_key)
|
|
ActiveSupport::Deprecation.warn "Giving :session_key to SessionStore is deprecated, " <<
|
|
"please use :key instead", caller
|
|
options[:key] = options.delete(:session_key)
|
|
end
|
|
if options.has_key?(:session_http_only)
|
|
ActiveSupport::Deprecation.warn "Giving :session_http_only to SessionStore is deprecated, " <<
|
|
"please use :httponly instead", caller
|
|
options[:httponly] = options.delete(:session_http_only)
|
|
end
|
|
|
|
@app = app
|
|
@default_options = DEFAULT_OPTIONS.merge(options)
|
|
@key = @default_options[:key]
|
|
@cookie_only = @default_options[:cookie_only]
|
|
end
|
|
|
|
def call(env)
|
|
prepare!(env)
|
|
response = @app.call(env)
|
|
|
|
session_data = env[ENV_SESSION_KEY]
|
|
options = env[ENV_SESSION_OPTIONS_KEY]
|
|
|
|
if !session_data.is_a?(AbstractStore::SessionHash) || session_data.loaded? || options[:expire_after]
|
|
request = ActionController::Request.new(env)
|
|
|
|
return response if (options[:secure] && !request.ssl?)
|
|
|
|
session_data.send(:load!) if session_data.is_a?(AbstractStore::SessionHash) && !session_data.loaded?
|
|
|
|
sid = options[:id] || generate_sid
|
|
|
|
unless set_session(env, sid, session_data.to_hash)
|
|
return response
|
|
end
|
|
|
|
request_cookies = env["rack.request.cookie_hash"]
|
|
|
|
if (request_cookies.nil? || request_cookies[@key] != sid) || options[:expire_after]
|
|
cookie = {:value => sid}
|
|
cookie[:expires] = Time.now + options[:expire_after] if options[:expire_after]
|
|
Rack::Utils.set_cookie_header!(response[1], @key, cookie.merge(options))
|
|
end
|
|
end
|
|
|
|
response
|
|
end
|
|
|
|
private
|
|
|
|
def prepare!(env)
|
|
env[ENV_SESSION_KEY] = SessionHash.new(self, env)
|
|
env[ENV_SESSION_OPTIONS_KEY] = OptionsHash.new(self, env, @default_options)
|
|
end
|
|
|
|
def generate_sid
|
|
ActiveSupport::SecureRandom.hex(16)
|
|
end
|
|
|
|
def load_session(env)
|
|
stale_session_check! do
|
|
sid = current_session_id(env)
|
|
sid, session = get_session(env, sid)
|
|
[sid, session]
|
|
end
|
|
end
|
|
|
|
def extract_session_id(env)
|
|
stale_session_check! do
|
|
request = Rack::Request.new(env)
|
|
sid = request.cookies[@key]
|
|
sid ||= request.params[@key] unless @cookie_only
|
|
sid
|
|
end
|
|
end
|
|
|
|
def current_session_id(env)
|
|
env[ENV_SESSION_OPTIONS_KEY][:id]
|
|
end
|
|
|
|
def exists?(env)
|
|
current_session_id(env).present?
|
|
end
|
|
|
|
def get_session(env, sid)
|
|
raise '#get_session needs to be implemented.'
|
|
end
|
|
|
|
def set_session(env, sid, session_data)
|
|
raise '#set_session needs to be implemented.'
|
|
end
|
|
|
|
def destroy(env)
|
|
raise '#destroy needs to be implemented.'
|
|
end
|
|
|
|
module SessionUtils
|
|
private
|
|
def stale_session_check!
|
|
yield
|
|
rescue ArgumentError => argument_error
|
|
if argument_error.message =~ %r{undefined class/module ([\w:]*\w)}
|
|
begin
|
|
# Note that the regexp does not allow $1 to end with a ':'
|
|
$1.constantize
|
|
rescue LoadError, NameError => const_error
|
|
raise ActionController::SessionRestoreError, "Session contains objects whose class definition isn\\'t available.\nRemember to require the classes for all objects kept in the session.\n(Original exception: \#{const_error.message} [\#{const_error.class}])\n"
|
|
end
|
|
retry
|
|
else
|
|
raise
|
|
end
|
|
end
|
|
end
|
|
include SessionUtils
|
|
end
|
|
end
|
|
end
|