Class: Hoodoo::Services::Session
- Inherits:
-
Object
- Object
- Hoodoo::Services::Session
- Defined in:
- lib/hoodoo/services/services/session.rb
Overview
A container for functionality related to a context session.
Constant Summary collapse
- TTL =
Time To Live: Number of seconds for which a session remains valid after being saved.
172800
Instance Attribute Summary collapse
-
#caller_fingerprint ⇒ Object
An optional property of a session is the Caller’s fingerprint, a UUID assigned to some Callers which can be persisted by resource instances when created and rendered in the
created_by
field via e.g. -
#caller_id ⇒ Object
A Session must always refer to a Caller instance by UUID.
-
#caller_identity_name ⇒ Object
An optional property of a session is the Caller’s “identity name”, a generic way to refer to this Caller which will appear in logs.
-
#caller_version ⇒ Object
Callers can change; if so, related sessions must be invalidated.
-
#created_at ⇒ Object
readonly
The creation date of this session instance as a Time instance in UTC.
-
#expires_at ⇒ Object
readonly
The expiry date for this session - the session should be considered expired at or after this date.
-
#identity ⇒ Object
An OpenStruct instance with session-creator defined key/value pairs that define the identity of the session holder.
-
#permissions ⇒ Object
A Hoodoo::Services::Permissions instance.
-
#scoping ⇒ Object
An OpenStruct instance with session-creator defined values that describe the scoping, that is, visbility of data, for the session.
-
#session_id ⇒ Object
A Session must have its own UUID.
-
#storage_engine ⇒ Object
Declares the transient storage engine this session will write to.
-
#storage_host_uri ⇒ Object
Connection IP address/port String for the selected storage engine.
Instance Method Summary collapse
-
#augment_with_permissions_for(interaction) ⇒ Object
Speciality interface usually only called by the middleware, or components closely related to the middleware.
-
#delete_from_store ⇒ Object
(also: #delete_from_memcached)
Delete this session from the transient store.
-
#expired? ⇒ Boolean
Has this session expired? Only valid if an expiry date is set; see #expires_at.
-
#from_h!(hash) ⇒ Object
Load session parameters from a given Hash, of the form set by #to_h.
-
#initialize(options = {}) ⇒ Session
constructor
Create a new instance.
-
#load_from_store!(sid) ⇒ Object
(also: #load_from_memcached!)
Load session data into this instance, overwriting instance values if the session is found.
-
#memcached_host ⇒ Object
Deprecated interface (use #storage_host_uri instead), dating back to when the Session engine was hard-coded to Memcached.
-
#memcached_host=(uri) ⇒ Object
Deprecated interface (use #storage_host_uri= instead), dating back to when the Session engine was hard-coded to Memcached.
-
#save_to_store ⇒ Object
(also: #save_to_memcached)
Save this session to the transient store, in a manner that will allow it to be loaded by #load_from_store! later.
-
#to_h ⇒ Object
Represent this session’s data as a Hash, for uses such as persistence or loading into another session instance.
-
#update_caller_version_in_memcached(cid, cv, store = nil) ⇒ Object
Deprecated interface (use #update_caller_version_in_store instead), dating back to when the Session engine was hard-coded to Memcached.
-
#update_caller_version_in_store(cid, cv, store = nil) ⇒ Object
Update the version of a given Caller in the transient store.
Constructor Details
#initialize(options = {}) ⇒ Session
Create a new instance.
options
-
Optional Hash of options, described below.
Options are:
session_id
-
UUID of this session. If unset, a new UUID is generated for you. You can read the UUID with the #session_id accessor method.
caller_id
-
UUID of the Caller instance associated with this session. This can be set either now or later, but the session cannot be saved without it.
caller_version
-
Version of the Caller instance. Defaults to zero.
caller_fingerprint
-
Optional Caller fingerprint UUID. Defaults to
nil
. storage_engine
-
An entry (Symbol) from Hoodoo::TransientStore::supported_storage_engines. Defaults to
:memcached
. storage_host_uri
-
URI for Hoodoo::TransientStore engine connections.
memcached_host
-
Host for Memcached connections (deprecated).
163 164 165 166 167 168 169 170 171 172 |
# File 'lib/hoodoo/services/services/session.rb', line 163 def initialize( = {} ) @created_at = Time.now.utc self.session_id = [ :session_id ] || Hoodoo::UUID.generate() self.storage_engine = [ :storage_engine ] || :memcached self.storage_host_uri = [ :storage_host_uri ] || [ :memcached_host ] self.caller_id = [ :caller_id ] self.caller_version = [ :caller_version ] || 0 self.caller_fingerprint = [ :caller_fingerprint ] end |
Instance Attribute Details
#caller_fingerprint ⇒ Object
An optional property of a session is the Caller’s fingerprint, a UUID assigned to some Callers which can be persisted by resource instances when created and rendered in the created_by
field via e.g. Hoodoo::Presenters::Base.#render_in.
47 48 49 |
# File 'lib/hoodoo/services/services/session.rb', line 47 def caller_fingerprint @caller_fingerprint end |
#caller_id ⇒ Object
A Session must always refer to a Caller instance by UUID.
32 33 34 |
# File 'lib/hoodoo/services/services/session.rb', line 32 def caller_id @caller_id end |
#caller_identity_name ⇒ Object
An optional property of a session is the Caller’s “identity name”, a generic way to refer to this Caller which will appear in logs. The use is up to the session creator, in combination with whatever logging engine is in use; if it ascribes meaning to the identity name, then the session creator must ensure it comforms.
40 41 42 |
# File 'lib/hoodoo/services/services/session.rb', line 40 def caller_identity_name @caller_identity_name end |
#caller_version ⇒ Object
Callers can change; if so, related sessions must be invalidated. This must be achieved by keeping a version count on the Caller. A session is associated with a particular Caller version and if the version changes, associated sessions are flushed.
If you change a Caller version in a Session, you really should call #save_to_store as soon as possible afterwards so that the change gets recognised in the Hoodoo::TransientStore.
58 59 60 |
# File 'lib/hoodoo/services/services/session.rb', line 58 def caller_version @caller_version end |
#created_at ⇒ Object (readonly)
The creation date of this session instance as a Time instance in UTC.
104 105 106 |
# File 'lib/hoodoo/services/services/session.rb', line 104 def created_at @created_at end |
#expires_at ⇒ Object (readonly)
The expiry date for this session - the session should be considered expired at or after this date. Some session stores may support automatic expiry of session data, but there may be a small window between the expiry date passing and the store expiring the data; so always check the expiry.
Only set when the session is saved (or loaded from a representation that includes an existing expiry date). See e.g.:
-
#save_to_store
The value is a Time instance in UTC. If nil
, the session has not yet been saved.
120 121 122 |
# File 'lib/hoodoo/services/services/session.rb', line 120 def expires_at @expires_at end |
#identity ⇒ Object
An OpenStruct instance with session-creator defined key/value pairs that define the identity of the session holder. This is usually related to a Caller resource instance - see also Hoodoo::Data::Resources::Caller - and will often contain a Caller resource instance’s UUID, amongst other data.
The object describes “who the session’s owner is”.
68 69 70 |
# File 'lib/hoodoo/services/services/session.rb', line 68 def identity @identity end |
#permissions ⇒ Object
A Hoodoo::Services::Permissions instance.
The instance describes “what the session is allowed to do”.
81 82 83 |
# File 'lib/hoodoo/services/services/session.rb', line 81 def @permissions end |
#scoping ⇒ Object
An OpenStruct instance with session-creator defined values that describe the scoping, that is, visbility of data, for the session. Its contents relate to service resource interface descriptions (see the DSL for Hoodoo::Services::Interface) and may be partially or entirely supported by the ActiveRecord finder extensions in Hoodoo::ActiveRecord::Finder.
The object describes the “data that the session can ‘see’”.
92 93 94 |
# File 'lib/hoodoo/services/services/session.rb', line 92 def scoping @scoping end |
#session_id ⇒ Object
A Session must have its own UUID.
28 29 30 |
# File 'lib/hoodoo/services/services/session.rb', line 28 def session_id @session_id end |
#storage_engine ⇒ Object
Declares the transient storage engine this session will write to. Both this and storage_host_uri
are used within Hoodoo::TransientStore::new.
126 127 128 |
# File 'lib/hoodoo/services/services/session.rb', line 126 def storage_engine @storage_engine end |
#storage_host_uri ⇒ Object
Connection IP address/port String for the selected storage engine. The connection host can be set either through this accessor, or via the object’s constructor.
132 133 134 |
# File 'lib/hoodoo/services/services/session.rb', line 132 def storage_host_uri @storage_host_uri end |
Instance Method Details
#augment_with_permissions_for(interaction) ⇒ Object
Speciality interface usually only called by the middleware, or components closely related to the middleware.
Takes this session and creates a copy for an inter-resource call which adds any additional parameters that the calling interface says it needs in order to complete the currently handled action.
Through calling this method, the middleware implements the access permission functionality described by Hoodoo::Services::Interface#additional_permissions_for.
interaction
-
Hoodoo::Services::Middleware::Interaction instance describing the current interaction. This is for the request that a resource implementation *is handling* at the point it wants to make an inter-resource call
-
it is not data related to the target of that
call.
-
Returns:
-
Hoodoo::Services::Session instance if everything works OK; this may be the same as, or different from, the input session depending on whether or not there were any permissions that needed adding.
-
false
if the session can’t be saved due to a mismatched caller version - the session must have become invalid during handling.
If the augmented session cannot be saved due to a storage problem, an exception is raised and the generic handler will turn this into a 500 response for the caller. At this time, we really can’t do much better than that since failure to save the augmented session means the inter-resource call cannot proceed; it’s an internal fault.
652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 |
# File 'lib/hoodoo/services/services/session.rb', line 652 def ( interaction ) # Set up some convenience variables interface = interaction.target_interface action = interaction.requested_action # If there are no additional permissions for this action, just return # the current session back again. action = action.to_s() = ( interface.() || {} )[ action ] return self if .nil? # Otherwise, duplicate the session and its permissions (or generate # defaults) and merge the additional permissions. local_session = self.dup() = self. ? self..dup() : Hoodoo::Services::Permissions.new .merge!( .to_h() ) # Make sure the new session has its own ID and set the updated # permissions. Then try to save it and return the result. local_session.session_id = Hoodoo::UUID.generate() local_session. = case local_session.save_to_store() when :ok return local_session when :outdated # Caller version mismatch; original session is now outdated and invalid return false else # Couldn't save it raise "Unable to create interim session for inter-resource call from #{ interface.resource } / #{ action }" end end |
#delete_from_store ⇒ Object Also known as: delete_from_memcached
Delete this session from the transient store. The Session object is not modified.
Returns a symbol:
-
:ok
: The Session was deleted from the transient store successfully. -
:fail
: The session data could not be deleted (unexpected storage engine failure).
478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 |
# File 'lib/hoodoo/services/services/session.rb', line 478 def delete_from_store begin store = get_store() result = store.delete( key: self.session_id ) case result when true return :ok when false raise 'Unknown storage engine failure' else raise result end rescue => exception # Log error and return nil if the session can't be parsed # Hoodoo::Services::Middleware.logger.warn( 'Hoodoo::Services::Session\#delete_from_store: Session delete - connection fault', exception.to_s ) return :fail end end |
#expired? ⇒ Boolean
Has this session expired? Only valid if an expiry date is set; see #expires_at.
Returns true
if the session has expired, or false
if it has either not expired, or has no expiry date set yet.
518 519 520 521 522 523 |
# File 'lib/hoodoo/services/services/session.rb', line 518 def expired? exp = self.expires_at() now = Time.now.utc return ! ( exp.nil? || now < exp ) end |
#from_h!(hash) ⇒ Object
Load session parameters from a given Hash, of the form set by #to_h.
If appropriate Hash keys are present, will set any or all of #session_id, #identity, #scoping and #permissions.
574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 |
# File 'lib/hoodoo/services/services/session.rb', line 574 def from_h!( hash ) hash = Hoodoo::Utilities.stringify( hash ) %w( session_id caller_id caller_version caller_identity_name caller_fingerprint ).each do | property | value = hash[ property ] self.send( "#{ property }=", value ) unless value.nil? end %w( created_at expires_at ).each do | property | if hash.has_key?( property ) begin instance_variable_set( "@#{ property }", Time.parse( hash[ property ] ).utc() ) rescue => e # Invalid time given; keep existing date end end end %w( identity scoping ).each do | property | value = hash[ property ] self.send( "#{ property }=", OpenStruct.new( value ) ) unless value.nil? end value = hash[ 'permissions' ] self. = Hoodoo::Services::Permissions.new( value ) unless value.nil? end |
#load_from_store!(sid) ⇒ Object Also known as: load_from_memcached!
Load session data into this instance, overwriting instance values if the session is found. Raises an exception if there is a problem connecting to the transient store.
sid
-
The Session UUID to look up.
Returns a symbol:
-
:ok
: The session data was loaded OK and is valid. -
:outdated
: The session data was loaded, but is outdated; either the session has expired, or its Caller version mismatches the associated stored Caller version in the transient store. -
:not_found
: The session was not found. -
:fail
: The session data could not be loaded (unexpected storage engine failure).
295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 |
# File 'lib/hoodoo/services/services/session.rb', line 295 def load_from_store!( sid ) begin store = get_store() session_hash = store.get( key: sid, allow_throw: true ) if session_hash.nil? return :not_found else self.from_h!( session_hash ) return :outdated if self.expired? cv = load_caller_version_from_store( store, self.caller_id ) if cv == nil || cv > self.caller_version return :outdated else return :ok end end rescue => exception # Log error and return nil if the session can't be parsed # Hoodoo::Services::Middleware.logger.warn( 'Hoodoo::Services::Session\#load_from_store!: Session loading failed - connection fault or session corrupt', exception.to_s ) end return :fail end |
#memcached_host ⇒ Object
Deprecated interface (use #storage_host_uri instead), dating back to when the Session engine was hard-coded to Memcached.
Supports backwards compatibility of options key memcached_host
, aliases storage_host_uri
.
Provides same functionality as alias_method
, however includes a deprecation warning.
Similar to:
alias_method( :memcached_host, :storage_host_uri )
403 404 405 406 407 408 409 |
# File 'lib/hoodoo/services/services/session.rb', line 403 def memcached_host Hoodoo::Services::Middleware.logger.warn( 'Hoodoo::Services::Session#memcached_host is deprecated - use #storage_host_uri' ) storage_host_uri() end |
#memcached_host=(uri) ⇒ Object
Deprecated interface (use #storage_host_uri= instead), dating back to when the Session engine was hard-coded to Memcached.
Provides same functionality as alias_method
, however includes a deprecation warning.
Similar to:
alias_method( :memcached_host=, :storage_host_uri= )
421 422 423 424 425 426 427 |
# File 'lib/hoodoo/services/services/session.rb', line 421 def memcached_host=( uri ) Hoodoo::Services::Middleware.logger.warn( 'Hoodoo::Services::Session#memcached_host= is deprecated - use #storage_host_uri=' ) self.storage_host_uri = uri end |
#save_to_store ⇒ Object Also known as: save_to_memcached
Save this session to the transient store, in a manner that will allow it to be loaded by #load_from_store! later.
A session can only be saved if it has a Caller ID - see #caller_id= or the options hash passed to the constructor.
The Hoodoo::Services::Session::TTL constant determines the maximum length of time for which the data persists inside the transient store.
Returns a symbol:
-
:ok
: The session data was saved OK and is valid. There was either a Caller record with an earlier or matching value in the transient store, no preexisting record of the Caller. -
:outdated
: The session data could not be saved because an existing Caller record was found in the transient store with a newer version than ‘this’ session, implying that the session is already outdated. -
:fail
: The session data could not be saved.
195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 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/hoodoo/services/services/session.rb', line 195 def save_to_store if self.caller_id.nil? raise 'Hoodoo::Services::Session\#save_to_store: Cannot save this session as it has no assigned Caller UUID' end begin store = get_store() # Try to update the Caller version in the store using this # Session's data. If this fails, the Caller version is out of # date or we couldn't talk to the store. Either way, bail out. # # TL;DR: The 'update' call here is critical. # # This process refreshes the caller version information in the # transient store back-end with each new Session. If eventually the # Caller version is expired or evicted, Sessions would be immediately # invalidated and a recreation would result in the Caller version # being rewritten. # # What if the Caller goes out of date? An external service must gate # access to Caller resource changes and update the Caller version # itself if the Caller alters in a way that should invalidate # Sessions. This refreshes the lifetime on that item which normally # expires at a much greater TTL than sessions anyway and, if anyone # tries to use a Stale session after, the Caller version mismatch # will invalidate it so they'll need a new one. # # If a Caller version is created but somehow evicted before any of # the older existing Sessions (perhaps because Caller version data is # small but Session data is large) then attempts to read the Session # will fail; *loading* a Session requires the Caller version. The # Caller would have to create a new Session and this would by virtue # of the handling resource endpoint's service code acquire the new # Caller version data immediately then cause it to be re-asserted / # re-written by the code below. # result = update_caller_version_in_store( self.caller_id, self.caller_version, store ) return result unless result.equal?( :ok ) # Must set this before saving, even though the delay between # setting this value and the store actually saving the value # with a TTL will mean that the store expires the key slightly # *after* the time we record. @expires_at = ( ::Time.now + TTL ).utc() result = store.set( key: self.session_id, payload: self.to_h(), maximum_lifespan: TTL ) case result when true return :ok when false raise 'Unknown storage engine failure' else raise result end rescue => exception # Log error and return nil if the session can't be parsed # Hoodoo::Services::Middleware.logger.warn( 'Hoodoo::Services::Session\#save_to_store: Session saving failed - connection fault or session corrupt', exception.to_s ) end return :fail end |
#to_h ⇒ Object
Represent this session’s data as a Hash, for uses such as persistence or loading into another session instance. See also #from_h!.
528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 |
# File 'lib/hoodoo/services/services/session.rb', line 528 def to_h hash = {} %w( session_id caller_id caller_version caller_identity_name caller_fingerprint ).each do | property | value = self.send( property ) hash[ property ] = value unless value.nil? end %w( created_at expires_at ).each do | property | value = self.send( property ) hash[ property ] = Hoodoo::Utilities.standard_datetime( value ) unless value.nil? end %w( identity scoping permissions ).each do | property | value = self.send( property ) hash[ property ] = Hoodoo::Utilities.stringify( value.to_h() ) unless value.nil? end return hash end |
#update_caller_version_in_memcached(cid, cv, store = nil) ⇒ Object
Deprecated interface (use #update_caller_version_in_store instead), dating back to when the Session engine was hard-coded to Memcached.
Parameters as for #update_caller_version_in_store, except store
is must be a fully configured Dalli::Client instance. Use of this interface is inefficient and discouraged; it will result in logged warnings.
437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 |
# File 'lib/hoodoo/services/services/session.rb', line 437 def update_caller_version_in_memcached( cid, cv, store = nil ) unless store.nil? Hoodoo::Services::Middleware.logger.warn( 'Hoodoo::Services::Session#update_caller_version_in_memcached is deprecated - use #update_caller_version_in_store' ) # Inefficient - create a TransientStore configured for the normal # Memcached connection data, but get hold of its storage engine and # change that engine's client to the provided Dalli::Client instance. temp_store = Hoodoo::TransientStore.new( storage_engine: :memcached, storage_host_uri: self.memcached_host(), default_namespace: 'nz_co_loyalty_hoodoo_session_' ) memcached_engine = temp_store.storage_engine_instance() memcached_engine.client = store begin update_caller_version_in_store( cid, cv, temp_store ) ensure temp_store.close() end else update_caller_version_in_store( cid, cv ) end end |
#update_caller_version_in_store(cid, cv, store = nil) ⇒ Object
Update the version of a given Caller in the transient store. This is done automatically when Sessions are saved to that store, but if external code alters any Callers independently, it MUST call here to keep stored records up to date.
If no cached version is in the transient store for the Caller, the method assumes it is being called for the first time for that Caller and writes the version it has to hand, rather than considering it an error condition.
cid
-
Caller UUID of the Caller record to update.
cv
-
New version to store (an Integer).
store
-
Optional Hoodoo::TransientStore instance to use for data storage. If omitted, a connection is established for you. This is mostly an optimisation parameter, used by code which already has established a connection and wants to avoid creating another unnecessarily.
Returns a Symbol:
-
:ok
: The Caller record was updated successfully. -
:outdated
: The Caller was already present in the transient store with a _higher version_ than the one you wanted to save. Your own local Caller data must therefore already be out of date. -
:fail
: The Caller could not be updated (unexpected storage engine failure).
365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 |
# File 'lib/hoodoo/services/services/session.rb', line 365 def update_caller_version_in_store( cid, cv, store = nil ) begin store ||= get_store() cached_version = load_caller_version_from_store( store, cid ) if cached_version != nil && cached_version > cv return :outdated elsif save_caller_version_to_store( store, cid, cv ) == true return :ok end rescue => exception # Log error and return nil if the session can't be parsed # Hoodoo::Services::Middleware.logger.warn( 'Hoodoo::Services::Session\#update_caller_version_in_store: Client version update - connection fault or corrupt record', exception.to_s ) end return :fail end |