# 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

cookie_value(data) click to toggle source

    # File lib/rack/session/abstract/id.rb, line 408
    def cookie_value(data)
      data
    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

set_cookie(request, res, cookie) click to toggle source

Sets the cookie back to the client with session id. We skip the cookie setting if the value didn't change (sid is the same) or expires was given.

    # File lib/rack/session/abstract/id.rb, line 415
    def set_cookie(request, res, cookie)
      if request.cookies[@key] != cookie[:value] || cookie[:expires]
        res.set_cookie_header =
          Utils.add_cookie_to_header(res.set_cookie_header, @key, cookie)
      end
    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