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.
-
#update_sessions_by_user_id(user_id, user) ⇒ Object
WARNING: Comparatively slow.
Constructor Details
#initialize ⇒ SessionFactory
Returns a new instance of SessionFactory.
455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 |
# File 'lib/hub_sso_lib.rb', line 455 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.
718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 |
# File 'lib/hub_sso_lib.rb', line 718 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.
641 642 643 644 645 646 647 648 649 650 651 652 653 |
# File 'lib/hub_sso_lib.rb', line 641 def destroy_session_by_key(key) unless @hub_be_quiet puts "Session factory: Deleting session with key #{key}" end 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.
664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 |
# File 'lib/hub_sso_lib.rb', line 664 def destroy_sessions_by_user_id(user_id) unless @hub_be_quiet puts "Session factory: Deleting all session records for user ID #{user_id.inspect}" end 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.
750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 |
# File 'lib/hub_sso_lib.rb', line 750 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.
593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 |
# File 'lib/hub_sso_lib.rb', line 593 def enumerate_hub_session_ids() count = @hub_sessions.size keys = if count > HUB_SESSION_ENUMERATION_KEY_MAX nil else HUB_MUTEX.synchronize do @hub_sessions.keys # (Hash#keys returns a new array) end 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.
561 562 563 564 565 566 567 |
# File 'lib/hub_sso_lib.rb', line 561 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.
525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 |
# File 'lib/hub_sso_lib.rb', line 525 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.
614 615 616 |
# File 'lib/hub_sso_lib.rb', line 614 def retrieve_session_by_key(key) HUB_MUTEX.synchronize { @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.
629 630 631 632 633 634 635 |
# File 'lib/hub_sso_lib.rb', line 629 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 |
#update_sessions_by_user_id(user_id, user) ⇒ Object
WARNING: Comparatively slow.
Call only if you MUST update details of a session user inside all Hub sessions. Pass the user ID and HubSsoLib::User details that are to be stored for all sessions owned by that user ID.
686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 |
# File 'lib/hub_sso_lib.rb', line 686 def update_sessions_by_user_id(user_id, user) unless @hub_be_quiet puts "Session factory: Updating all session records for user ID #{user_id.inspect}" end HUB_MUTEX.synchronize do @hub_sessions.each do | key, session | if session&.session_user&.user_id == user_id session.session_user = user end end end rescue => e Sentry.capture_exception(e) if defined?(Sentry) && Sentry.respond_to?(:capture_exception) raise end |