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.

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 249
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 258
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 372
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 262
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 341
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 327
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 447
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 319
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 432
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 358
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 354
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 287
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 277
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 311
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 350
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 273
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 300
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 362
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 423
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 333
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 440
def write_session(req, sid, session, options)
  raise '#write_session not implemented.'
end