Class: GlobalSession::Rack::Middleware
- Inherits:
-
Object
- Object
- GlobalSession::Rack::Middleware
- Defined in:
- lib/global_session/rack.rb
Overview
Global session middleware. Note: this class relies on Rack::Cookies being used higher up in the chain.
Constant Summary collapse
- NUMERIC_HOST =
/^[0-9.]+$/.freeze
- LOCAL_SESSION_KEY =
"rack.session".freeze
Instance Attribute Summary collapse
- #configuration ⇒ GlobalSession::Configuration
- #directory ⇒ GlobalSession::Directory
- #keystore ⇒ GlobalSession::Keystore
Instance Method Summary collapse
-
#call(env) ⇒ Array
Rack request chain.
-
#cookie_domain(env) ⇒ Object
Determine the domain name for which we should set the cookie.
-
#create_session(env) ⇒ true
Ensure that the Rack environment contains a global session object; create a session if necessary.
-
#handle_error(activity, env, e) ⇒ true
Handle exceptions that occur during app invocation.
-
#initialize(app, configuration, directory = nil) {|env| ... } ⇒ Middleware
constructor
Make a new global session middleware.
-
#perform_invalidation_callbacks(env, old_session, new_session) ⇒ true
Perform callbacks to directory and/or local session informing them that this session has been invalidated.
-
#read_authorization_header(env) ⇒ Boolean
Read a global session from the HTTP Authorization header, if present.
-
#read_cookie(env) ⇒ Boolean
Read a global session from HTTP cookies, if present.
-
#renew_cookie(env) ⇒ true
Renew the session ticket.
-
#update_cookie(env) ⇒ true
Update the cookie jar with the revised ticket.
-
#wipe_cookie(env) ⇒ true
Delete the global session cookie from the cookie jar.
Constructor Details
#initialize(app, configuration, directory = nil) {|env| ... } ⇒ Middleware
Make a new global session middleware.
The optional block here controls an alternate ticket retrieval method. If no ticket is stored in the cookie jar, this function is called. If it returns a non-nil value, that value is the ticket.
56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 |
# File 'lib/global_session/rack.rb', line 56 def initialize(app, configuration, directory=nil, &block) @app = app # Initialize shared configuration # @deprecated require Configuration object in v4 if configuration.instance_of?(String) @configuration = Configuration.new(configuration, ENV['RACK_ENV'] || 'development') else @configuration = configuration end klass = nil begin # v0.9.0 - v3.0.4: class name is the value of the 'directory' key klass_name = @configuration['directory'] case klass_name when Hash # v3.0.5 and beyond: class name is in 'class' subkey klass_name = klass_name['class'] when NilClass # the eternal default, if the class name is not provided klass_name = 'GlobalSession::Directory' end if klass_name.is_a?(String) # for apps klass = klass_name.to_const else # for specs that need to directly inject a class/object klass = klass_name end rescue Exception => e raise GlobalSession::ConfigurationError, "Invalid/unknown directory class name: #{klass_name.inspect}" end # Initialize the directory object if directory.is_a?(Directory) # In v4-style initialization, the directory is always passed in @directory = directory elsif klass.is_a?(Class) # @deprecated v3-style initialization where the config file names the directory class @directory = klass.new(@configuration, directory) else raise GlobalSession::ConfigurationError, "Cannot determine directory class/instance; method parameter is a #{directory.class.name} and configuration parameter is #{klass.class.name}" end @cookie_retrieval = block @cookie_name = @configuration['cookie']['name'] end |
Instance Attribute Details
#configuration ⇒ GlobalSession::Configuration
36 37 38 |
# File 'lib/global_session/rack.rb', line 36 def configuration @configuration end |
#directory ⇒ GlobalSession::Directory
39 40 41 |
# File 'lib/global_session/rack.rb', line 39 def directory @directory end |
#keystore ⇒ GlobalSession::Keystore
42 43 44 |
# File 'lib/global_session/rack.rb', line 42 def keystore @keystore end |
Instance Method Details
#call(env) ⇒ Array
Rack request chain. Parses a global session from the request if present; makes a new session if absent; populates env with the session object and calls through to the next middleware.
On return, auto-renews the session if appropriate and writes a new session cookie if anything in the session has changed.
When reading session cookies or authorization headers, this middleware URL-decodes cookie/token values before passing them into the gem’s other logic. Some user agents and proxies “helpfully” URL-encode cookies which we need to undo in order to prevent subtle signature failures due to Base64 decoding issues resulting from “=” being URL-encoded.
124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 |
# File 'lib/global_session/rack.rb', line 124 def call(env) env['rack.cookies'] = {} unless env['rack.cookies'] begin err = nil (env) || (env) || create_session(env) rescue Exception => read_err err = read_err # Catch "double whammy" errors begin env['global_session'] = @directory.create_session rescue Exception => create_err err = create_err end handle_error('reading session cookie', env, err) end tuple = nil begin tuple = @app.call(env) rescue Exception => read_err handle_error('processing request', env, read_err) return tuple else (env) (env) return tuple end end |
#cookie_domain(env) ⇒ Object
Determine the domain name for which we should set the cookie. Uses the domain specified in the configuration if one is found; otherwise, uses the SERVER_NAME from the request but strips off the first component if the domain name contains more than two components.
334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 |
# File 'lib/global_session/rack.rb', line 334 def (env) name = env['HTTP_X_FORWARDED_HOST'] || env['SERVER_NAME'] if @configuration['cookie'].has_key?('domain') # Use the explicitly provided domain name domain = @configuration['cookie']['domain'] elsif name =~ NUMERIC_HOST # Don't set a domain if the browser requested an IP-based host domain = nil else # Guess an appropriate domain for the cookie. Strip one level of # subdomain; leave SLDs unmolested; omit domain entirely for # one-component domains (e.g. localhost). parts = name.split('.') case parts.length when 0..1 domain = nil when 2 domain = parts.join('.') else domain = parts[1..-1].join('.') end end domain end |
#create_session(env) ⇒ true
Ensure that the Rack environment contains a global session object; create a session if necessary.
206 207 208 209 210 |
# File 'lib/global_session/rack.rb', line 206 def create_session(env) env['global_session'] ||= @directory.create_session true end |
#handle_error(activity, env, e) ⇒ true
Handle exceptions that occur during app invocation. This will either save the error in the Rack environment or raise it, depending on the type of error. The error may also be logged.
294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 |
# File 'lib/global_session/rack.rb', line 294 def handle_error(activity, env, e) if env['rack.logger'] msg = "#{e.class} while #{activity}: #{e}" msg += " #{e.backtrace}" unless e.is_a?(ExpiredSession) env['rack.logger'].error(msg) end if e.is_a?(ClientError) || e.is_a?(SecurityError) env['global_session.error'] = e (env) elsif e.is_a? ConfigurationError env['global_session.error'] = e else # Don't intercept errors unless they're GlobalSession-related raise e end true end |
#perform_invalidation_callbacks(env, old_session, new_session) ⇒ true
Perform callbacks to directory and/or local session informing them that this session has been invalidated.
321 322 323 324 325 326 327 |
# File 'lib/global_session/rack.rb', line 321 def perform_invalidation_callbacks(env, old_session, new_session) if (local_session = env[LOCAL_SESSION_KEY]) && local_session.respond_to?(:rename!) local_session.rename!(old_session, new_session) end true end |
#read_authorization_header(env) ⇒ Boolean
Read a global session from the HTTP Authorization header, if present. If an authorization header was found, also disable global session cookie update and renewal by setting the corresponding keys of the Rack environment.
163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 |
# File 'lib/global_session/rack.rb', line 163 def (env) if env.has_key? 'X-HTTP_AUTHORIZATION' # RFC2617 style (preferred by OAuth 2.0 spec) header_data = env['X-HTTP_AUTHORIZATION'].to_s.split elsif env.has_key? 'HTTP_AUTHORIZATION' # Fallback style (generally when no load balancer is present, e.g. dev/test) header_data = env['HTTP_AUTHORIZATION'].to_s.split else header_data = nil end if header_data && header_data.size == 2 && header_data.first.downcase == 'bearer' env['global_session.req.renew'] = false env['global_session.req.update'] = false env['global_session'] = @directory.load_session(CGI.unescape(header_data.last)) true else false end end |
#read_cookie(env) ⇒ Boolean
Read a global session from HTTP cookies, if present.
188 189 190 191 192 193 194 195 196 197 198 199 |
# File 'lib/global_session/rack.rb', line 188 def (env) if @cookie_retrieval && ( = @cookie_retrieval.call(env)) env['global_session'] = @directory.load_session(CGI.unescape()) true elsif env['rack.cookies'].has_key?(@cookie_name) = env['rack.cookies'][@cookie_name] env['global_session'] = @directory.load_session(CGI.unescape()) true else false end end |
#renew_cookie(env) ⇒ true
Renew the session ticket.
216 217 218 219 220 221 222 223 224 225 226 |
# File 'lib/global_session/rack.rb', line 216 def (env) return true unless @directory. return true if env['global_session.req.renew'] == false if (renew = @configuration['renew']) && env['global_session'] && env['global_session'].expired_at < Time.at(Time.now.utc + 60 * renew.to_i) env['global_session'].renew! end true end |
#update_cookie(env) ⇒ true
Update the cookie jar with the revised ticket.
232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 |
# File 'lib/global_session/rack.rb', line 232 def (env) return true unless @directory. return true if env['global_session.req.update'] == false session = env['global_session'] if session unless session.valid? old_session = session session = @directory.create_session perform_invalidation_callbacks(env, old_session, session) env['global_session'] = session end value = session.to_s expires = @configuration['ephemeral'] ? nil : session.expired_at unless env['rack.cookies'][@cookie_name] == value secure = (env['HTTP_X_FORWARDED_PROTO'] == 'https') || (env['rack.url_scheme'] == 'https') env['rack.cookies'][@cookie_name] = { :value => value, :domain => (env), :expires => expires, :httponly => true, :secure => secure, } end else # write an empty cookie (env) end true rescue Exception => e (env) raise e end |
#wipe_cookie(env) ⇒ true
Delete the global session cookie from the cookie jar.
275 276 277 278 279 280 281 282 283 284 |
# File 'lib/global_session/rack.rb', line 275 def (env) return true unless @directory. return true if env['global_session.req.update'] == false env['rack.cookies'][@cookie_name] = {:value => nil, :domain => (env), :expires => Time.at(0)} true end |