class SimpleWorkflow::Middleware

Rack middleware to detect and store detours and manage returns from detours.

Public Class Methods

new(app) click to toggle source
# File lib/simple_workflow/middleware.rb, line 10
def initialize(app)
  @app = app
  @simple_workflow_encryptor = nil
end

Public Instance Methods

call(env) click to toggle source
# File lib/simple_workflow/middleware.rb, line 15
def call(env)
  store_detour_from_params(env)
  status, headers, body = @app.call(env)
  remove_old_detours(env)
  [status, headers, body]
end

Private Instance Methods

cookies(env) click to toggle source
# File lib/simple_workflow/middleware.rb, line 36
def cookies(env)
  request(env).cookies
end
encryptor(env) click to toggle source
# File lib/simple_workflow/middleware.rb, line 95
def encryptor(env)
  return @simple_workflow_encryptor if @simple_workflow_encryptor

  @simple_workflow_encryptor = cookie_jar(env).instance_variable_get(:@encryptor)
  return @simple_workflow_encryptor if @simple_workflow_encryptor

  Rails.logger.warn 'simple_workflow: Could not get encryptor from the cookie jar'
  secret_key_base = Rails.application.config.secret_key_base || Rails.application.config.secret_token ||
      SecureRandom.hex(64)
  key_generator = ActiveSupport::KeyGenerator.new(secret_key_base, iterations: 1000)
  key_generator = ActiveSupport::CachingKeyGenerator.new(key_generator)
  secret = key_generator.generate_key('encrypted cookie')
  sign_secret = key_generator.generate_key('signed encrypted cookie')
  @simple_workflow_encryptor = ActiveSupport::MessageEncryptor.new(secret, sign_secret)
end
params(env) click to toggle source
# File lib/simple_workflow/middleware.rb, line 32
def params(env)
  request(env).params
end
remove_discarded_flashes(session) click to toggle source
# File lib/simple_workflow/middleware.rb, line 75
  def remove_discarded_flashes(session)
    return unless (old_flashes = session[:flash] && session[:flash]['discard'])

    Rails.logger.warn <<~MSG
      simple_workflow: found discarded flash entries: #{old_flashes}.  Deleting them.
    MSG
    session[:flash]['flashes'] = session[:flash]['flashes'].except(*old_flashes)
    Rails.logger.warn "simple_workflow: session: #{session.to_hash}"
  end
remove_old_detours(env) click to toggle source
# File lib/simple_workflow/middleware.rb, line 44
  def remove_old_detours(env)
    return unless session(env).instance_variable_get(:@by).is_a?(ActionDispatch::Session::CookieStore)

    session_size = workflow_size = nil
    session = session(env)
    cookie_jar = cookie_jar(env)
    encryptor = encryptor(env)
    loop do
      ser_val = serialize_session(cookie_jar, session.to_hash)
      session_size = encryptor.encrypt_and_sign(ser_val).size
      wf_ser_val = serialize_session(cookie_jar, session[:detours])
      workflow_size = encryptor.encrypt_and_sign(wf_ser_val).size
      break unless workflow_size >= 2048 ||
          (session_size >= 3072 && session[:detours] && !session[:detours].empty?)

      Rails.logger.warn("Workflow too large (#{workflow_size}/#{session_size}).  Dropping oldest detour.")
      session[:detours].shift
      reset_workflow(session) if session[:detours].empty?
    end
    Rails.logger.debug { <<~MSG }
      session: #{session_size} bytes, workflow(#{session[:detours].try(:size) || 0}): #{workflow_size} bytes
    MSG
    return unless session_size > 4096

    Rails.logger.warn <<~MSG
      simple_workflow: session exceeds cookie size limit: #{session_size} bytes.  Workflow empty!  Not My Fault!
    MSG
    Rails.logger.warn "simple_workflow: session: #{session.to_hash}"
    remove_discarded_flashes(session)
  end
request(env) click to toggle source
# File lib/simple_workflow/middleware.rb, line 24
def request(env)
  ActionDispatch::Request.new(env)
end
serialize_session(cookie_jar, session) click to toggle source
# File lib/simple_workflow/middleware.rb, line 86
def serialize_session(cookie_jar, session)
  cookie_jar.send(:serializer).send(:dump, session)
end
session(env) click to toggle source
# File lib/simple_workflow/middleware.rb, line 40
def session(env)
  env['rack.session']
end
store_detour_from_params(env) click to toggle source
# File lib/simple_workflow/middleware.rb, line 111
def store_detour_from_params(env)
  store_detour_in_session(session(env), params(env)[:detour]) if params(env)[:detour]
  return unless params(env)[:return_from_detour] && session(env)[:detours]

  params_hash = params(env).to_h.reject { |k, _v| %i[detour return_from_detour].include? k.to_sym }
  pop_detour(session(env), params_hash)
end