class Rack::Session::Abstract::Persisted
ID
sets up a basic framework for implementing an id based sessioning service. Cookies sent to the client for maintaining sessions will only contain an id reference. Only find_session
, write_session
and delete_session
are required to be overwritten.
All parameters are optional.
-
:key determines the name of the cookie, by default it is 'rack.session'
-
:path, :domain, :expire_after, :secure, and :httponly set the related cookie options as by
Rack::Response#set_cookie
-
:skip will not a set a cookie in the response nor update the session state
-
:defer will not set a cookie in the response but still update the session state if it is used with a backend
-
:renew (implementation dependent) will prompt the generation of a new session id, and migration of data to be referenced at the new id. If :defer is set, it will be overridden and the cookie will be set.
-
:sidbits sets the number of bits in length that a generated session id will be.
These options can be set on a per request basis, at the location of env['rack.session.options']
. Additionally the id of the session can be found within the options hash at the key :id. It is highly not recommended to change its value.
Is Rack::Utils::Context
compatible.
Not included by default; you must require 'rack/session/abstract/id' to use.
Constants
- DEFAULT_OPTIONS
Attributes
Public Class Methods
# File lib/rack/session/abstract/id.rb, line 250 def initialize(app, options = {}) @app = app @default_options = self.class::DEFAULT_OPTIONS.merge(options) @key = @default_options.delete(:key) @cookie_only = @default_options.delete(:cookie_only) @same_site = @default_options.delete(:same_site) initialize_sid end
Public Instance Methods
# File lib/rack/session/abstract/id.rb, line 259 def call(env) context(env) end
Acquires the session from the environment and the session id from the session options and passes them to write_session
. If successful and the :defer option is not true, a cookie will be added to the response with the session's id.
# File lib/rack/session/abstract/id.rb, line 373 def commit_session(req, res) session = req.get_header RACK_SESSION options = session.options if options[:drop] || options[:renew] session_id = delete_session(req, session.id || generate_sid, options) return unless session_id end return unless commit_session?(req, session, options) session.send(:load!) unless loaded_session?(session) session_id ||= session.id session_data = session.to_hash.delete_if { |k, v| v.nil? } if not data = write_session(req, session_id, session_data, options) req.get_header(RACK_ERRORS).puts("Warning! #{self.class.name} failed to save session. Content dropped.") elsif options[:defer] and not options[:renew] req.get_header(RACK_ERRORS).puts("Deferring cookie for #{session_id}") if $VERBOSE else cookie = Hash.new cookie[:value] = cookie_value(data) cookie[:expires] = Time.now + options[:expire_after] if options[:expire_after] cookie[:expires] = Time.now + options[:max_age] if options[:max_age] if @same_site.respond_to? :call cookie[:same_site] = @same_site.call(req, res) else cookie[:same_site] = @same_site end set_cookie(req, res, cookie.merge!(options)) end end
# File lib/rack/session/abstract/id.rb, line 263 def context(env, app = @app) req = make_request env prepare_session(req) status, headers, body = app.call(req.env) res = Rack::Response::Raw.new status, headers commit_session(req, res) [status, headers, body] end
Private Instance Methods
Session
should be committed if it was loaded, any of specific options like :renew, :drop or :expire_after was given and the security permissions match. Skips if skip is given.
# File lib/rack/session/abstract/id.rb, line 342 def commit_session?(req, session, options) if options[:skip] false else has_session = loaded_session?(session) || forced_session_update?(session, options) has_session && security_matches?(req, options) end end
Returns the current session id from the SessionHash
.
# File lib/rack/session/abstract/id.rb, line 328 def current_session_id(req) req.get_header(RACK_SESSION).id end
All thread safety and session destroy procedures should occur here. Should return a new session id or nil if options
# File lib/rack/session/abstract/id.rb, line 448 def delete_session(req, sid, options) raise '#delete_session not implemented' end
Extract session id from request object.
# File lib/rack/session/abstract/id.rb, line 320 def extract_session_id(request) sid = request.cookies[@key] sid ||= request.params[@key] unless @cookie_only sid end
All thread safety and session retrieval procedures should occur here. Should return [session_id, session]. If nil is provided as the session id, generation of a new valid id should occur within.
# File lib/rack/session/abstract/id.rb, line 433 def find_session(env, sid) raise '#find_session not implemented.' end
# File lib/rack/session/abstract/id.rb, line 359 def force_options?(options) options.values_at(:max_age, :renew, :drop, :defer, :expire_after).any? end
# File lib/rack/session/abstract/id.rb, line 355 def forced_session_update?(session, options) force_options?(options) && session && !session.empty? end
Generate a new session id using Ruby rand. The size of the session id is controlled by the :sidbits option. Monkey patch this to use custom methods for session id generation.
# File lib/rack/session/abstract/id.rb, line 288 def generate_sid(secure = @sid_secure) if secure secure.hex(@sid_length) else "%0#{@sid_length}x" % Kernel.rand(2**@sidbits - 1) end rescue NotImplementedError generate_sid(false) end
# File lib/rack/session/abstract/id.rb, line 278 def initialize_sid @sidbits = @default_options[:sidbits] @sid_secure = @default_options[:secure_random] @sid_length = @sidbits / 4 end
Extracts the session id from provided cookies and passes it and the environment to find_session
.
# File lib/rack/session/abstract/id.rb, line 312 def load_session(req) sid = current_session_id(req) sid, session = find_session(req, sid) [sid, session || {}] end
# File lib/rack/session/abstract/id.rb, line 351 def loaded_session?(session) !session.is_a?(session_class) || session.loaded? end
# File lib/rack/session/abstract/id.rb, line 274 def make_request(env) Rack::Request.new env end
Sets the lazy session at 'rack.session' and places options and session metadata into 'rack.session.options'.
# File lib/rack/session/abstract/id.rb, line 301 def prepare_session(req) session_was = req.get_header RACK_SESSION session = session_class.new(self, req) req.set_header RACK_SESSION, session req.set_header RACK_SESSION_OPTIONS, @default_options.dup session.merge! session_was if session_was end
# File lib/rack/session/abstract/id.rb, line 363 def security_matches?(request, options) return true unless options[:secure] request.ssl? end
Allow subclasses to prepare_session
for different Session
classes
# File lib/rack/session/abstract/id.rb, line 424 def session_class SessionHash end
Check if the session exists or not.
# File lib/rack/session/abstract/id.rb, line 334 def session_exists?(req) value = current_session_id(req) value && !value.empty? end
All thread safety and session storage procedures should occur here. Must return the session id if the session was saved successfully, or false if the session could not be saved.
# File lib/rack/session/abstract/id.rb, line 441 def write_session(req, sid, session, options) raise '#write_session not implemented.' end