aboutsummaryrefslogtreecommitdiffstats
path: root/actionpack/lib/action_dispatch/middleware/session/abstract_store.rb
diff options
context:
space:
mode:
Diffstat (limited to 'actionpack/lib/action_dispatch/middleware/session/abstract_store.rb')
-rw-r--r--actionpack/lib/action_dispatch/middleware/session/abstract_store.rb149
1 files changed, 113 insertions, 36 deletions
diff --git a/actionpack/lib/action_dispatch/middleware/session/abstract_store.rb b/actionpack/lib/action_dispatch/middleware/session/abstract_store.rb
index 3e8d64b0c6..08bc80dbc2 100644
--- a/actionpack/lib/action_dispatch/middleware/session/abstract_store.rb
+++ b/actionpack/lib/action_dispatch/middleware/session/abstract_store.rb
@@ -12,6 +12,35 @@ module ActionDispatch
ENV_SESSION_KEY = 'rack.session'.freeze
ENV_SESSION_OPTIONS_KEY = 'rack.session.options'.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()
@@ -21,66 +50,75 @@ module ActionDispatch
end
def [](key)
- load! unless @loaded
+ load_for_read!
+ super(key.to_s)
+ end
+
+ def has_key?(key)
+ load_for_read!
super(key.to_s)
end
def []=(key, value)
- load! unless @loaded
+ load_for_write!
super(key.to_s, value)
end
def to_hash
+ load_for_read!
h = {}.replace(self)
h.delete_if { |k,v| v.nil? }
h
end
def update(hash)
- load! unless @loaded
+ load_for_write!
super(hash.stringify_keys)
end
def delete(key)
- load! unless @loaded
+ load_for_write!
super(key.to_s)
end
def inspect
- load! unless @loaded
+ 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!
- stale_session_check! do
- id, session = @by.send(:load_session, @env)
- (@env[ENV_SESSION_OPTIONS_KEY] ||= {})[:id] = id
- replace(session.stringify_keys)
- @loaded = true
- end
+
+ def load_for_read!
+ load! if !loaded? && exists?
end
- 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 ActionDispatch::Session::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
+ 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.stringify_keys)
+ @loaded = true
end
+
end
DEFAULT_OPTIONS = {
@@ -108,8 +146,8 @@ module ActionDispatch
session_data = env[ENV_SESSION_KEY]
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?)
+ if !session_data.is_a?(AbstractStore::SessionHash) || session_data.loaded? || options[:expire_after]
+ session_data.send(:load!) if session_data.is_a?(AbstractStore::SessionHash) && !session_data.loaded?
sid = options[:id] || generate_sid
session_data = session_data.to_hash
@@ -133,7 +171,7 @@ module ActionDispatch
def prepare!(env)
env[ENV_SESSION_KEY] = SessionHash.new(self, env)
- env[ENV_SESSION_OPTIONS_KEY] = @default_options.dup
+ env[ENV_SESSION_OPTIONS_KEY] = OptionsHash.new(self, env, @default_options)
end
def generate_sid
@@ -141,15 +179,30 @@ module ActionDispatch
end
def set_cookie(request, options)
- request.cookie_jar[@key] = options
+ if request.cookie_jar[@key] != options[:value] || !options[:expires].nil?
+ request.cookie_jar[@key] = options
+ end
end
def load_session(env)
- request = Rack::Request.new(env)
- sid = request.cookies[@key]
- sid ||= request.params[@key] unless @cookie_only
- sid, session = get_session(env, sid)
- [sid, session]
+ 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 = ActionDispatch::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 ensure_session_key!
@@ -161,6 +214,26 @@ module ActionDispatch
end
end
+ 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 ActionDispatch::Session::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
+
+ def exists?(env)
+ current_session_id(env).present?
+ end
+
def get_session(env, sid)
raise '#get_session needs to be implemented.'
end
@@ -169,6 +242,10 @@ module ActionDispatch
raise '#set_session needs to be implemented and should return ' <<
'the value to be stored in the cookie (usually the sid)'
end
+
+ def destroy(env)
+ raise '#destroy needs to be implemented.'
+ end
end
end
end