Class: Rack::Middleware::SessionInjector

Inherits:
Object
  • Object
show all
Defined in:
lib/session_injector.rb

Defined Under Namespace

Classes: InvalidHandshake

Constant Summary collapse

'rack.request.cookie_string'.freeze
'rack.request.cookie_hash'.freeze
'HTTP_COOKIE'.freeze
DEFAULT_OPTIONS =
{
  # use the AbstractStore default key as our session id key
  # if you have configured a custom session store key, you must
  # specify that as the value for this middleware
  :key => ActionDispatch::Session::AbstractStore::DEFAULT_OPTIONS[:key],
  :token_lifetime => 5000, # five seconds should be enough
  :die_on_handshake_failure => true
}
SESSION_INJECTOR_KEY =

the env key we will use to stash ourselves for downstream access

'_session_injector'
SESSION_PROPAGATE_KEY =

the env key upstream uses to stash a flag to tell us to propagate a session this is a convenience for manually adding a request parameter to a redirect response location

'_session_propagate'
HANDSHAKE_PARAM =

the internal parameter we will use to convey the session handshake token

'_hs_'

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(app, options = {}) ⇒ SessionInjector

Returns a new instance of SessionInjector.



32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
# File 'lib/session_injector.rb', line 32

def initialize(app, options = {})
  @app = app
  options = DEFAULT_OPTIONS.merge(options)
  @session_id_key = options[:key]
  # statically generated token key in case we
  # need to fall back (no cookie token key has been set)
  # handshakes are by definition transient, so the only
  # important requirement is that the middleware that generates
  # the token can decrypt the token. when not under a clustered/balanced
  # architecture, that most likely means the same process/middelware
  # so the key value is not important
  # in fact, non-durability of the token is a security feature
  generated_token_key = ActiveSupport::SecureRandom.hex(16)
  @token_key = options[:token_key] || generated_token_key
  @enforced_lifetime = options[:token_lifetime]
  @die_on_handshake_failure = options[:die_on_handshake_failure]
end

Class Method Details

.extract_session_id(request, session_id_key) ⇒ Object

find the current session id



104
105
106
107
# File 'lib/session_injector.rb', line 104

def self.extract_session_id(request, session_id_key)
  #request.session_options[:id]
  request.cookies[session_id_key]
end

.generate_handshake_parameter(request, target_domain, lifetime = nil) ⇒ Object

generates the handshake parameter key=value string



94
95
96
# File 'lib/session_injector.rb', line 94

def self.generate_handshake_parameter(request, target_domain, lifetime = nil)
  "#{HANDSHAKE_PARAM}=#{generate_handshake_token(request, target_domain, lifetime)}"
end

.generate_handshake_token(request, target_domain, lifetime = nil) ⇒ Object

generates the handshake token we can send to the target domain



74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
# File 'lib/session_injector.rb', line 74

def self.generate_handshake_token(request, target_domain, lifetime = nil)
  # retrieve the configured middleware instance
  session_injector = request.env[SESSION_INJECTOR_KEY]
  # note: scheme is not included in handshake
  # a session initiated on https may be established on http
  handshake = {
    :request_ip => request.ip,
    :request_path => request.fullpath, # more for accounting/stats than anything else
    :src_domain => request.host,
    :tgt_domain => target_domain,
    :token_create_time => Time.now.to_i,
    # the most important thing
    :session_id => extract_session_id(request, session_injector.session_id_key)
  }
  handshake[:requested_lifetime] = lifetime if lifetime
  # we could reuse ActionDispatch::Cookies.TOKEN_KEY if it is present but let's not!
  ActiveSupport::MessageEncryptor.new(session_injector.token_key).encrypt_and_sign(handshake);
end

.propagate_session(request, target_domain, lifetime = nil) ⇒ Object

helper that sets a flag to rewrite the location header with session propagation handshake



99
100
101
# File 'lib/session_injector.rb', line 99

def self.propagate_session(request, target_domain, lifetime = nil)
  request.env[SESSION_PROPAGATE_KEY] = [ target_domain, lifetime ]
end

Instance Method Details

#call(env) ⇒ Object



50
51
52
53
54
55
56
# File 'lib/session_injector.rb', line 50

def call(env)
  env[SESSION_INJECTOR_KEY] = self; # store ourselves for downstream access
  reconstitute_session(env)
  response = @app.call(env)
  response = propagate_session(env, *response)
  response
end

#propagate_session(env, status, headers, response) ⇒ Object

rewrites location header if requested



59
60
61
62
63
64
65
66
67
68
69
70
71
# File 'lib/session_injector.rb', line 59

def propagate_session(env, status, headers, response)
  propagate_flag = env.delete(SESSION_PROPAGATE_KEY)
  location = headers["Location"]
  if propagate_flag and location
    # we've been told to rewrite the location header and it is present
    uri = URI::parse(location)
    prefix = uri.query ? "&" : ""
    # append handshake param to query
    uri.query = [uri.query, prefix, SessionInjector.generate_handshake_parameter(Rack::Request.new(env), propagate_flag[0], propagate_flag[1])].join
    headers["Location"] = uri.to_s
  end
  [ status, headers, response]
end

#session_id_keyObject

return the env key containing the session id



110
111
112
# File 'lib/session_injector.rb', line 110

def session_id_key
  @session_id_key
end

#token_keyObject

return the key we use for encryption and hashing



115
116
117
# File 'lib/session_injector.rb', line 115

def token_key
  @token_key
end