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

default_options[R]
key[R]
sid_secure[R]

Public Class Methods

new(app, options = {}) click to toggle source
# 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

call(env) click to toggle source
# File lib/rack/session/abstract/id.rb, line 259
def call(env)
  context(env)
end
commit_session(req, res) click to toggle source

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
context(env, app = @app) click to toggle source
# 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

commit_session?(req, session, options) click to toggle source

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
current_session_id(req) click to toggle source

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
delete_session(req, sid, options) click to toggle source

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(request) click to toggle source

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
find_session(env, sid) click to toggle source

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
force_options?(options) click to toggle source
# 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
forced_session_update?(session, options) click to toggle source
# File lib/rack/session/abstract/id.rb, line 355
def forced_session_update?(session, options)
  force_options?(options) && session && !session.empty?
end
generate_sid(secure = @sid_secure) click to toggle source

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
initialize_sid() click to toggle source
# 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
load_session(req) click to toggle source

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
loaded_session?(session) click to toggle source
# File lib/rack/session/abstract/id.rb, line 351
def loaded_session?(session)
  !session.is_a?(session_class) || session.loaded?
end
make_request(env) click to toggle source
# File lib/rack/session/abstract/id.rb, line 274
def make_request(env)
  Rack::Request.new env
end
prepare_session(req) click to toggle source

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
security_matches?(request, options) click to toggle source
# File lib/rack/session/abstract/id.rb, line 363
def security_matches?(request, options)
  return true unless options[:secure]
  request.ssl?
end
session_class() click to toggle source

Allow subclasses to prepare_session for different Session classes

# File lib/rack/session/abstract/id.rb, line 424
def session_class
  SessionHash
end
session_exists?(req) click to toggle source

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
write_session(req, sid, session, options) click to toggle source

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