Class: HubSsoLib::SessionFactory
- Inherits:
-
Object
- Object
- HubSsoLib::SessionFactory
- Defined in:
- lib/hub_sso_lib.rb
Overview
Class: SessionFactory #
(C) Hipposoft 2006 #
#
Purpose: Manage Session objects for DRb server clients. This class #
implements the API exposed by the HubSsoLib::Server DRb #
endpoint, so this is the remote object that clients will #
be calling into. #
#
Author: A.D.Hodgkinson #
#
History: 26-Oct-2006 (ADH): Created. #
Instance Method Summary collapse
-
#destroy_ancient_sessions ⇒ Object
WARNING: Slow.
-
#destroy_session_by_key(key) ⇒ Object
Given a session key (which, if a session has been looked up and the key thus rotated, ought to be that new, rotated key), destroy the associated session data.
-
#destroy_sessions_by_user_id(user_id) ⇒ Object
WARNING: Comparatively slow.
-
#dump_sessions! ⇒ Object
Lock the session store and dump all sessions with a non-nil session user ID to a YAML file at HUB_SESSION_ARCHIVE.
-
#enumerate_hub_session_ids ⇒ Object
Returns all currently known session keys.
-
#enumerate_hub_sessions ⇒ Object
THIS INTERFACE IS DEPRECATED and will be removed in Hub 4.
-
#get_hub_session_proxy(key, _ignored = nil, create: true) ⇒ Object
Get a session using a given key (a UUID).
-
#initialize ⇒ SessionFactory
constructor
A new instance of SessionFactory.
-
#retrieve_session_by_key(key) ⇒ Object
Retrieve session data (as a HubSsoLib::Session instance) based on the given session key.
-
#retrieve_sessions_by_user_id(user_id) ⇒ Object
WARNING: Comparatively slow.
Constructor Details
#initialize ⇒ SessionFactory
Returns a new instance of SessionFactory.
473 474 475 476 477 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 506 507 508 509 |
# File 'lib/hub_sso_lib.rb', line 473 def initialize @hub_be_quiet = (ENV['HUB_QUIET_SERVER'] == 'yes') @hub_sessions = {} puts "Session factory: Awakening..." unless @hub_be_quiet if File.exist?(HUB_SESSION_ARCHIVE) begin restored_sessions = ::YAML.load_file( HUB_SESSION_ARCHIVE, permitted_classes: [ ::HubSsoLib::Session, ::HubSsoLib::User, Time, Symbol ] ) @hub_sessions = restored_sessions || {} self.destroy_ancient_sessions() puts "Session factory: Reloaded #{@hub_sessions.size} from archive" unless @hub_be_quiet rescue => e puts "Session factory: Ignored archive due to error #{e..inspect}" unless @hub_be_quiet ensure File.unlink(HUB_SESSION_ARCHIVE) end end puts "Session factory: ...Awakened" unless @hub_be_quiet rescue => e Sentry.capture_exception(e) if defined?(Sentry) && Sentry.respond_to?(:capture_exception) raise end |
Instance Method Details
#destroy_ancient_sessions ⇒ Object
WARNING: Slow.
This is a housekeeping task which checks sessions against Hub expiry and, if the session keys look to be substantially older than the value set in HUB_IDLE_TIME_LIMIT, the session simply deleted. If a user does return later, they’ll see themselves in logged out state without the Flash warning them of an expired session, but we can’t allow session keys to just hang around forever so some kind of sweep is needed.
This method clearly needs to iterate over all sessions under a mutex and makes relatively complex checks for each, so it’s fairly slow compared to most methods. Call it infrequently; any and all other attempts to read session data while the method runs will block until method finishes.
702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 |
# File 'lib/hub_sso_lib.rb', line 702 def destroy_ancient_sessions unless @hub_be_quiet puts "Session factory: Sweeping sessions inactive for more than #{ HUB_ARCHIVE_TIME_LIMIT } seconds..." end destroyed = 0 HUB_MUTEX.synchronize do count_before = @hub_sessions.size @hub_sessions.reject! do | key, session | last_used = session&.session_last_used last_used.nil? || Time.now.utc - last_used > HUB_ARCHIVE_TIME_LIMIT end count_after = @hub_sessions.size destroyed = count_before - count_after end unless @hub_be_quiet puts "Session factory: ...Destroyed #{destroyed} session(s)" end rescue => e Sentry.capture_exception(e) if defined?(Sentry) && Sentry.respond_to?(:capture_exception) raise end |
#destroy_session_by_key(key) ⇒ Object
Given a session key (which, if a session has been looked up and the key thus rotated, ought to be that new, rotated key), destroy the associated session data. Does nothing if the key is not found.
657 658 659 660 661 662 663 664 665 |
# File 'lib/hub_sso_lib.rb', line 657 def destroy_session_by_key(key) HUB_MUTEX.synchronize do @hub_sessions.delete(key) end rescue => e Sentry.capture_exception(e) if defined?(Sentry) && Sentry.respond_to?(:capture_exception) raise end |
#destroy_sessions_by_user_id(user_id) ⇒ Object
WARNING: Comparatively slow.
This is called in rare cases such as user deletion or being asked for a session under an old key, indicating loss of key rotation sequence. Removes all sessions found for a given user ID.
IN THE CURRENT IMPLEMENTATION THIS JUST SEQUENTIALLY SCANS ALL ACTIVE SESSIONS IN THE HASH and must therefore lock on mutex for the duration.
676 677 678 679 680 681 682 683 684 685 686 |
# File 'lib/hub_sso_lib.rb', line 676 def destroy_sessions_by_user_id(user_id) HUB_MUTEX.synchronize do @hub_sessions.reject! do | key, session | session&.session_user&.user_id == user_id end end rescue => e Sentry.capture_exception(e) if defined?(Sentry) && Sentry.respond_to?(:capture_exception) raise end |
#dump_sessions! ⇒ Object
Lock the session store and dump all sessions with a non-nil session user ID to a YAML file at HUB_SESSION_ARCHIVE. This is expected to only be called by the graceful shutdown code in HubSsoLib::Server.
734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 |
# File 'lib/hub_sso_lib.rb', line 734 def dump_sessions! written_record_count = 0 # Why not just do ::YAML.dump(@hub_sessions)? Well, it'd be faster, but # it builds the YAML data all in RAM which would cause a huge RAM spike # of unknown size (depends on live session count) and that's Bad. # # If any no-user sessions have crept in for any reason, this also gives # us a chance to skip them. # HUB_MUTEX.synchronize do File.open(HUB_SESSION_ARCHIVE, 'w') do | f | f.write("---\n") # (document marker) @hub_sessions.each do | key, session | next if session&.session_user&.user_id.nil? # NOTE EARLY LOOP RESTART session.session_flash = nil dump = ::YAML.dump({key => session}) dump.sub!(/^---\n/, '') # (avoid multiple document markers) f.write(dump) written_record_count += 1 end end end # Simple if slightly inefficient way to deal with zero actual useful # session records being present - an unusual real-world edge case. # File.unlink(HUB_SESSION_ARCHIVE) if written_record_count == 0 end |
#enumerate_hub_session_ids ⇒ Object
Returns all currently known session keys. This is a COPY of the internal keyset, since otherwise it would be necessary to try and enumerate keys on a Hash which is subject to change at any moment if a user logs in or out. Since Ruby raises an exception should an under-iteration Hash be changed, we can’t do that, which is why the keys are returned as a copied array instead.
Keys are ordered by least-recently-active first to most-recent last.
Call #retrieve_session_by_key(…) to get session details for that key. Bear in mind that nil returns are possible, since the session data may be changing rapidly and a user might’ve logged out or had their session expired in the time between you retrieving the list of current keys here, then requesting details of the session for that key later.
To avoid unbounded RAM requirements arising, the maximum number of keys returned herein is limited to HUB_SESSION_ENUMERATION_KEY_MAX.
Returns a Hash with Symbol keys that have values as follows:
count-
The number of known sessions - just a key count.
keys-
If
countexceeds HUB_SESSION_ENUMERATION_KEY_MAX, this isnil, else an array of zero or more session keys.
611 612 613 614 615 616 617 618 619 620 621 622 623 624 |
# File 'lib/hub_sso_lib.rb', line 611 def enumerate_hub_session_ids() count = @hub_sessions.size keys = if count > HUB_SESSION_ENUMERATION_KEY_MAX nil else @hub_sessions.keys # (Hash#keys returns a new array) end return { count: count, keys: keys } rescue => e Sentry.capture_exception(e) if defined?(Sentry) && Sentry.respond_to?(:capture_exception) raise end |
#enumerate_hub_sessions ⇒ Object
THIS INTERFACE IS DEPRECATED and will be removed in Hub 4. Change to #enumerate_hub_session_keys instead.
Enumerate all currently known sessions. The format is a Hash, with the session key UUIDs as keys and the related HubSsoLib::Session instances as values. If you attempt it iterate over this data YOU MUST USE A COPY of the keys to do so, since Hub users may log in or out at any time and Ruby will raise an exception if the session data changes during enumeration - end users will see errors.
579 580 581 582 583 584 585 |
# File 'lib/hub_sso_lib.rb', line 579 def enumerate_hub_sessions() @hub_sessions rescue => e Sentry.capture_exception(e) if defined?(Sentry) && Sentry.respond_to?(:capture_exception) raise end |
#get_hub_session_proxy(key, _ignored = nil, create: true) ⇒ Object
Get a session using a given key (a UUID). Generates a new session if the key is unrecognised or if the IP address given mismatches the one recorded in existing session data.
Whether new or pre-existing, the returned session will have changed key as a result of being read; check the #session_rotated_key property to find out the new key. If you fail to do this, you’ll lose access to the session data as you won’t know which key it lies under.
The returned object is proxied via DRb - it is shared between processes.
key-
Session key; lazy-initialises a new session under this key if none is found, then immediately rotates it by default, but may return no session for unrecognised keys depending on the
createparameter, described below.
The “_ignored” parameter is for backwards compatibility for older clients calling into a newer gem. This used to take an IP address of the request and would discard a session if the current IP address had changed, but since DHCP is a Thing then - even though in practice most IP addresses from ISPs are very stable - this wasn’t really a valid security measure. It required us to process and store IP data which is now often considered PII and we’d rather not (especially given their arising storage in HUB_SESSION_ARCHIVE on shutdown). This is, of course, quite ironic given the reason for removal is IP address unreliability when used as PII!
In addition, the following optional named parameters can be given:
create-
Default
true- an unknown key causes creation of an empty, new session under that key. Iffalse, attempts to read with an unrecognised key yieldnil.
543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 |
# File 'lib/hub_sso_lib.rb', line 543 def get_hub_session_proxy(key, _ignored = nil, create: true) hub_session = HUB_MUTEX.synchronize { @hub_sessions[key] } return nil if create == false && hub_session.nil? # NOTE EARLY EXIT = hub_session.nil? ? 'Created' : 'Retrieving' new_key = SecureRandom.uuid unless @hub_be_quiet puts "Session factory: #{ } session for key #{ key } and rotating to #{ new_key }" end hub_session = HubSsoLib::Session.new if hub_session.nil? HUB_MUTEX.synchronize do @hub_sessions.delete(key) @hub_sessions[new_key] = hub_session end hub_session.session_rotated_key = new_key return hub_session rescue => e Sentry.capture_exception(e) if defined?(Sentry) && Sentry.respond_to?(:capture_exception) raise end |
#retrieve_session_by_key(key) ⇒ Object
Retrieve session data (as a HubSsoLib::Session instance) based on the given session key. No key rotation occurs. Returns nil if no entry is found for that key.
630 631 632 |
# File 'lib/hub_sso_lib.rb', line 630 def retrieve_session_by_key(key) @hub_sessions[key] end |
#retrieve_sessions_by_user_id(user_id) ⇒ Object
WARNING: Comparatively slow.
This is usually only called in administrative interfaces to look at the known sessions for a specific user of interest. An Hash of session key values yielding HubSsoLib::Session instances as values is returned.
The array is ordered by least-recently-active first to most-recent last.
IN THE CURRENT IMPLEMENTATION THIS JUST SEQUENTIALLY SCANS ALL ACTIVE SESSIONS IN THE HASH and must therefore lock on mutex for the duration.
645 646 647 648 649 650 651 |
# File 'lib/hub_sso_lib.rb', line 645 def retrieve_sessions_by_user_id(user_id) HUB_MUTEX.synchronize do @hub_sessions.select do | key, session | session&.session_user&.user_id == user_id end end end |