hubssolib 3.6.0 → 3.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: faacd8f740f867ee072f0f8e0f15324109941026b836cb3fcc8618f61ee68e02
4
- data.tar.gz: 6ef08dccd8bbe03a974c238353a27d7f249a40911a40d65dfcd481dd6fe7b48e
3
+ metadata.gz: 13d41f0609a0ebf34e61267f91db31652699915b531010ddbda76ecc63110239
4
+ data.tar.gz: aea0e7149740e0d25714bc1a0b9a2b5333910058497a0ec24906a65364475218
5
5
  SHA512:
6
- metadata.gz: 67b9f58743d8f9ed2983d3b9b970fd00d50ba335dcd761f8c9626866c9c91a1e65c378bb0139835c1e8eb48557e681ea510f6391373d06e1a6806aaf8b8fc2e2
7
- data.tar.gz: d4e6efe525fb55b25f40c888ab4d05a7041c90c1cddcba7bab74002d299b2a1f96e35f58ea211cb666da68fda3d129400a7bf874bc8b7ddf09e9fdd6a8d1fbd2
6
+ metadata.gz: 30c7c7f431d456cb286bd439be4e7ec3d80abde85d856e6f8914daf3d46e8f5ca0d36ac380a7240ddecde149f225fd8c80a5738070d9ed9404e87e77823e1e85
7
+ data.tar.gz: c48fefd6183cbfe3d5f8c9bd9e5f503d31f24524ff000121f73b0f2651d7d081e72fa5f4870d0ae6ec6d7599d900eea8b4b4b283380d36a3f2184e2da46a69dc
data/CHANGELOG.md CHANGED
@@ -1,9 +1,26 @@
1
+ ## 3.7.0, 28-Mar-2025
2
+
3
+ * Login indicator cookie wasn't updated on session timeout, so when the page loaded for pages that do *not* require authorisation, the warning flash about being timed out would show, but the indicator would still show a "logged in" state until the next page fetch.
4
+ * User trust mechanism introduced, including HubSsoLib::Core#hubssolib_trusted? convenience accessor for Hub-integrated applications to check on user trust and `HubSsoLib::Core#hubssolib_review_action` convenience alias for `HubSsoLib::Trust.get_trust_object().review_action`.
5
+
6
+ ## 3.6.1, 27-Mar-2025
7
+
8
+ Some fixes:
9
+
10
+ * Session reload could fail if a Hub flash happened to be persisted, because YAML won't load Symbol by default and it wasn't in the allow-list.
11
+ - Remove the flash message from the dump since some flash from "whenever" may well be confusing if reloaded and shown at some later time.
12
+ - Add Symbol so that if restarting under this gem version, a dump from an older version which _does_ contain flash data will still load OK.
13
+
14
+ * Secure services failure redirection _still_ wasn't quite in the right place - the earliest thing that runs is of course the 'before action' hook. Moved it there and added a Sentry warning too.
15
+
1
16
  ## 3.6.0, 26-Mar-2025
2
17
 
3
18
  Cleans up and offers new enumeration features. Ordering by last-recently-active first allows clients to be deterministic about enumerated sessions. Features created to support improvements in the Hub app v3.6.0.
4
19
 
5
- Note that the session generator in the factory - HubSsoLib::SessionFactory#get_hub_session_proxy - no longer pays attention to IP address parameter, which should now be omitted (it is now an ignored parameter that defaults to +nil+). See implementation comments for rationale, but basically, IP addresses can legitimately change for users due to DHCP (even if that's rare) and given v3.5.0's on-shutdown session store, it didn't seem wise to keep IP addresses around inside there for any length of time. It was cleanest to just drop them. PII in persisted data is once again limited to "real name" and e-mail address.
6
- s
20
+ * HubSsoLib::Core#enumerate_hub_sessions is deprecated. Use HubSsoLib::Core#enumerate_hub_session_keys instead.
21
+ * For any client code that might be "hitting the metal" and calling the DRb server directly, note that HubSsoLib::SessionFactory#get_hub_session_proxy no longer pays attention to IP address parameter and this should be removed (it is now an ignored parameter that defaults to +nil+). See implementation comments for rationale, but basically, IP addresses can legitimately change for users due to DHCP (even if that's rare) and given v3.5.0's on-shutdown session store, it didn't seem wise to keep IP addresses around inside there for any length of time. It was cleanest to just drop them. PII in persisted data is once again limited to "real name" and e-mail address.
22
+ * HubSsoLib::Core exception handling for the Hub app's "tasks" notification is more extensive. A few prior gem versions unwittingly restricted it to only one specific method call. Now it's done in the current user retrieval, which is an endpoint used by a majority of Core module method calls.
23
+
7
24
  ## 3.5.0, 25-Mar-2025
8
25
 
9
26
  Builds on the cleaner session interface with some changes and improvements:
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- hubssolib (3.5.0)
4
+ hubssolib (3.7.0)
5
5
  base64 (~> 0.2)
6
6
  drb (~> 2.2)
7
7
 
@@ -52,7 +52,7 @@ GEM
52
52
  simplecov_json_formatter (~> 0.1)
53
53
  simplecov-html (0.13.1)
54
54
  simplecov_json_formatter (0.1.4)
55
- stringio (3.1.5)
55
+ stringio (3.1.6)
56
56
 
57
57
  PLATFORMS
58
58
  ruby
data/hubssolib.gemspec CHANGED
@@ -4,7 +4,7 @@ spec = Gem::Specification.new do |s|
4
4
  s.platform = Gem::Platform::RUBY
5
5
  s.name = 'hubssolib'
6
6
 
7
- s.version = '3.6.0'
7
+ s.version = '3.7.0'
8
8
  s.author = 'Andrew Hodgkinson and others'
9
9
  s.email = 'ahodgkin@rowing.org.uk'
10
10
  s.homepage = 'http://pond.org.uk/'
data/lib/hub_sso_lib.rb CHANGED
@@ -23,14 +23,16 @@ module HubSsoLib
23
23
  require 'json'
24
24
  require 'yaml'
25
25
 
26
- # DRb connection.
26
+ # DRb connections.
27
27
  #
28
- HUB_CONNECTION_URI = ENV['HUB_CONNECTION_URI'] || 'drbunix:' + File.join( ENV['HOME'] || '/', '/.hub_drb')
28
+ HUB_CONNECTION_URI = ENV['HUB_CONNECTION_URI' ] || 'drbunix:' + File.join(ENV['HOME'] || '/', '/.hub_drb')
29
+ HUB_TRUST_CONNECTION_URI = ENV['HUB_TRUST_CONNECTION_URI'] || 'drbunix:' + File.join(ENV['HOME'] || '/', '/.hub_trust_drb')
29
30
 
30
- unless HUB_CONNECTION_URI.downcase.start_with?('drbunix:')
31
+ unless HUB_CONNECTION_URI.downcase.start_with?('drbunix:') && HUB_TRUST_CONNECTION_URI.downcase.start_with?('drbunix:')
31
32
  puts
32
33
  puts '*' * 80
33
- puts "You *must* use a 'drbunix:' scheme for HUB_CONNECTION_URI (#{ HUB_CONNECTION_URI.inspect } is invalid)"
34
+ puts 'You *must* "drbunix:" for HUB_CONNECTION_URI and HUB_TRUST_CONNECTION_URI.'
35
+ puts "Either or both of #{HUB_CONNECTION_URI.inspect} and #{HUB_TRUST_CONNECTION_URI.inspect} is invalid)"
34
36
  puts '*' * 80
35
37
  puts
36
38
 
@@ -39,7 +41,7 @@ module HubSsoLib
39
41
 
40
42
  # External application command registry for on-user-change events.
41
43
  #
42
- HUB_COMMAND_REGISTRY = ENV['HUB_COMMAND_REGISTRY'] || File.join( ENV['HOME'] || '/', '/.hub_cmd_reg')
44
+ HUB_COMMAND_REGISTRY = ENV['HUB_COMMAND_REGISTRY'] || File.join(ENV['HOME'] || '/', '/.hub_cmd_reg')
43
45
 
44
46
  unless Dir.exist?(File.dirname(HUB_COMMAND_REGISTRY))
45
47
  puts
@@ -221,7 +223,7 @@ module HubSsoLib
221
223
  return @role_array.dup
222
224
  end
223
225
 
224
- # Return a copy of the intenal roles list as a human readable string.
226
+ # Return a copy of the internal roles list as a human readable string.
225
227
  #
226
228
  def to_human_s
227
229
  human_names = []
@@ -259,6 +261,7 @@ module HubSsoLib
259
261
  return false if roles.nil?
260
262
 
261
263
  # Ensure we've an array of roles, one way or another
264
+ #
262
265
  roles = roles.to_s if roles.class == Symbol
263
266
  roles = roles.split(',') if roles.class == String
264
267
 
@@ -360,6 +363,7 @@ module HubSsoLib
360
363
  # Author: A.D.Hodgkinson #
361
364
  # #
362
365
  # History: 21-Oct-2006 (ADH): Created. #
366
+ # 26-Feb-2025 (ADH): Add 'trusted' concept. #
363
367
  #######################################################################
364
368
 
365
369
  class User
@@ -388,6 +392,7 @@ module HubSsoLib
388
392
  attr_accessor :user_email
389
393
  attr_accessor :user_created_at
390
394
  attr_accessor :user_password_reset_code_expires_at
395
+ attr_accessor :user_trusted
391
396
 
392
397
  def initialize
393
398
  @user_salt = nil
@@ -405,6 +410,7 @@ module HubSsoLib
405
410
  @user_email = nil
406
411
  @user_created_at = nil
407
412
  @user_password_reset_code_expires_at = nil
413
+ @user_trusted = nil
408
414
  end
409
415
  end # User class
410
416
 
@@ -465,7 +471,7 @@ module HubSsoLib
465
471
 
466
472
  class SessionFactory
467
473
  def initialize
468
- @hub_be_quiet = ! ENV['HUB_QUIET_SERVER'].nil?
474
+ @hub_be_quiet = (ENV['HUB_QUIET_SERVER'] == 'yes')
469
475
  @hub_sessions = {}
470
476
 
471
477
  puts "Session factory: Awakening..." unless @hub_be_quiet
@@ -477,7 +483,8 @@ module HubSsoLib
477
483
  permitted_classes: [
478
484
  ::HubSsoLib::Session,
479
485
  ::HubSsoLib::User,
480
- Time
486
+ Time,
487
+ Symbol
481
488
  ]
482
489
  )
483
490
 
@@ -741,6 +748,8 @@ module HubSsoLib
741
748
  @hub_sessions.each do | key, session |
742
749
  next if session&.session_user&.user_id.nil? # NOTE EARLY LOOP RESTART
743
750
 
751
+ session.session_flash = nil
752
+
744
753
  dump = ::YAML.dump({key => session})
745
754
  dump.sub!(/^---\n/, '') # (avoid multiple document markers)
746
755
 
@@ -782,7 +791,7 @@ module HubSsoLib
782
791
  QUEUE = ::Queue.new
783
792
 
784
793
  def self.run
785
- puts "Server: Starting at #{ HUB_CONNECTION_URI }" if ENV['HUB_QUIET_SERVER'].nil?
794
+ puts "Server: Starting at #{ HUB_CONNECTION_URI }" if ENV['HUB_QUIET_SERVER'] != 'yes'
786
795
 
787
796
  @@hub_session_factory = HubSsoLib::SessionFactory.new
788
797
 
@@ -812,6 +821,79 @@ module HubSsoLib
812
821
  end # Runner class
813
822
  end # Server module
814
823
 
824
+ #######################################################################
825
+ # Class: Trust #
826
+ # #
827
+ # Purpose: Allow other applications to call into the Hub Rails #
828
+ # application to tell it about untrusted user operations. #
829
+ # The application can then store the details, e-mail #
830
+ # moderators and in due course call back to the originating #
831
+ # other application to move the user action forwards. #
832
+ # #
833
+ # The external API is HubSsoLib::Trust::Server, implemented #
834
+ # by the Hub Rails application, not this gem. #
835
+ # #
836
+ # Author: A.D.Hodgkinson #
837
+ # #
838
+ # History: 19-Mar-2025 (ADH): Created #
839
+ #######################################################################
840
+
841
+ class Trust
842
+
843
+ # Return the DRb endpoint URI for the trust server.
844
+ #
845
+ def self.get_trust_server_connection_uri
846
+ HUB_TRUST_CONNECTION_URI
847
+ end
848
+
849
+ # Start the trust server. This should only ever be called by the Hub Rails
850
+ # application, which implements HubSsoLib::Trust::Server.
851
+ #
852
+ def self.launch_server
853
+ uri = self.get_trust_server_connection_uri()
854
+ path = URI.parse(uri).path
855
+ already_running = File.exist?(path)
856
+
857
+ unless ENV['HUB_QUIET_SERVER'] == 'yes'
858
+ message = unless already_running
859
+ "Trust server: Starting at #{ uri }"
860
+ else
861
+ "Trust server: Already running at at #{ uri }"
862
+ end
863
+
864
+ puts message
865
+ end
866
+
867
+ unless already_running
868
+ loop do
869
+ DRb.start_service(uri, ::HubSsoLib::Trust::Server.new)
870
+ DRb.thread.join # Keep the thread alive...
871
+ end # ...but auto-restart if e.g. DRb.stop_service() is invoked
872
+ end
873
+ end
874
+
875
+ # Obtain a connection to the trust server. This is called by any client
876
+ # code that needs to talk to the server which must, at the time called, be
877
+ # running via startup within the Hub Rails application.
878
+ #
879
+ # The returned object is an instance of ::HubSsoLib::Trust::Server, which
880
+ # is defined inside the Hub Rails application. See there for details.
881
+ #
882
+ def self.get_trust_object
883
+ HUB_MUTEX.synchronize do
884
+ begin
885
+ DRb.current_server
886
+ rescue DRb::DRbServerNotFound
887
+ DRb.start_service()
888
+ end
889
+
890
+ @@trust_object ||= DRbObject.new_with_uri(self.get_trust_server_connection_uri())
891
+ end
892
+
893
+ return @@trust_object
894
+ end
895
+ end
896
+
815
897
  #######################################################################
816
898
  # Module: Core #
817
899
  # Various authors #
@@ -845,6 +927,8 @@ module HubSsoLib
845
927
  #
846
928
  def hubssolib_log_out
847
929
  self.hubssolib_current_user = nil # (which deals with all related session and cookie consequences)
930
+ @hubssolib_session = nil
931
+ cookies.delete(HUB_LOGIN_INDICATOR_COOKIE, domain: :all, path: '/')
848
932
  end
849
933
 
850
934
  # Returns true or false if a user is logged in or not, respectively.
@@ -863,18 +947,6 @@ module HubSsoLib
863
947
  user = hub_session&.session_user
864
948
 
865
949
  return (user&.user_id.nil? ? nil : user)
866
-
867
- rescue Exception => e
868
-
869
- # At this point there tends to be no Session data, so we're going to have
870
- # to encode the exception data into the URI... It must be escaped twice,
871
- # as many servers treat "%2F" in a URI as a "/". Apache can then fail to
872
- # serve the page, raising a 404 error unless "AllowEncodedSlashes on" is
873
- # specified in its configuration.
874
- #
875
- suffix = '/' + CGI::escape(CGI::escape(hubssolib_set_exception_data(e)))
876
- new_path = HUB_PATH_PREFIX + '/tasks/service'
877
- redirect_to(new_path + suffix) unless request.path.include?(new_path)
878
950
  end
879
951
 
880
952
  # Sets the currently signed in user. Note that although this works and is
@@ -1000,6 +1072,26 @@ module HubSsoLib
1000
1072
  return (puser && !puser.empty? && puser != pnormal)
1001
1073
  end
1002
1074
 
1075
+ # Convenience method that returns +true+ if there's a currently logged-in
1076
+ # Hub user that has been flagged as trusted, else - whether there is no
1077
+ # current user, or they haven't been flagged as trusted - returns +false+.
1078
+ #
1079
+ def hubssolib_trusted?
1080
+ self.hubssolib_current_user&.user_trusted == 'true'
1081
+ end
1082
+
1083
+ # Convenience accessor to HubSsoLib::Trust.get_trust_object().review_action
1084
+ # - see that method for details. Note that the Trust object is implemented
1085
+ # in the Hub application, not here; see:
1086
+ #
1087
+ # HubSsoLib::Trust::Server::review_action
1088
+ #
1089
+ # ...in "app/hub/lib/hub_sso_lib/trust/server.rb".
1090
+ #
1091
+ def hubssolib_review_action(**args)
1092
+ HubSsoLib::Trust.get_trust_object().review_action(**args)
1093
+ end
1094
+
1003
1095
  # Public read-only accessor methods for common user activities:
1004
1096
  # return the current user's roles as a Roles object, or nil if
1005
1097
  # there's no user.
@@ -1040,7 +1132,7 @@ module HubSsoLib
1040
1132
  # hubssolib_get_name.
1041
1133
  #
1042
1134
  def hubssolib_unique_name
1043
- user = hubssolib_current_user
1135
+ user = self.hubssolib_current_user
1044
1136
  user ? "#{user.user_real_name} (#{user.user_id})" : 'Anonymous'
1045
1137
  end
1046
1138
 
@@ -1276,7 +1368,7 @@ module HubSsoLib
1276
1368
  # quietly log out and let action processing carry on.
1277
1369
 
1278
1370
  if (hubssolib_session_expired?)
1279
- hubssolib_log_out
1371
+ hubssolib_log_out()
1280
1372
  hubssolib_set_flash(:attention, 'Your session timed out, so you are no longer logged in.')
1281
1373
  else
1282
1374
  hubssolib_set_last_used(Time.now.utc)
@@ -1285,6 +1377,19 @@ module HubSsoLib
1285
1377
  return true # true -> let action processing continue
1286
1378
 
1287
1379
  end
1380
+
1381
+ rescue Exception => e
1382
+ Sentry.capture_exception(e) if defined?(Sentry) && Sentry.respond_to?(:capture_exception)
1383
+
1384
+ # At this point there tends to be no Session data, so we're going to have
1385
+ # to encode the exception data into the URI... It must be escaped twice,
1386
+ # as many servers treat "%2F" in a URI as a "/". Apache can then fail to
1387
+ # serve the page, raising a 404 error unless "AllowEncodedSlashes on" is
1388
+ # specified in its configuration.
1389
+ #
1390
+ suffix = '/' + CGI::escape(CGI::escape(hubssolib_set_exception_data(e)))
1391
+ new_path = HUB_PATH_PREFIX + '/tasks/service'
1392
+ redirect_to(new_path + suffix) unless request.path.include?(new_path)
1288
1393
  end
1289
1394
 
1290
1395
  # Mandatory controller "after_action" callback method to tidy up after Hub
@@ -1293,7 +1398,6 @@ module HubSsoLib
1293
1398
  def hubssolib_afterwards
1294
1399
  begin
1295
1400
  DRb.current_server
1296
- DRb.stop_service()
1297
1401
  rescue DRb::DRbServerNotFound
1298
1402
  # Nothing to do; no service is running.
1299
1403
  end
@@ -1437,11 +1541,13 @@ module HubSsoLib
1437
1541
 
1438
1542
  :hubssolib_current_user,
1439
1543
  :hubssolib_unique_name,
1544
+ :hubssolib_account_link,
1545
+ :hubssolib_flash_data,
1546
+
1440
1547
  :hubssolib_logged_in?,
1441
1548
  :hubssolib_authorized?,
1442
1549
  :hubssolib_privileged?,
1443
- :hubssolib_account_link,
1444
- :hubssolib_flash_data
1550
+ :hubssolib_trusted?
1445
1551
  )
1446
1552
  end
1447
1553
  end
@@ -1602,8 +1708,9 @@ module HubSsoLib
1602
1708
  # halted (since the overall return value is therefore 'false').
1603
1709
  #
1604
1710
  def hubssolib_access_denied
1605
- # See hubsso_must_login for the reason behind the following call.
1606
1711
 
1712
+ # See hubsso_must_login for the reason behind the following call.
1713
+ #
1607
1714
  if hubssolib_ensure_https
1608
1715
  hubssolib_set_flash(:alert, 'You do not have permission to carry out that action on this site.')
1609
1716
  redirect_to HUB_PATH_PREFIX + '/'
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: hubssolib
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.6.0
4
+ version: 3.7.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andrew Hodgkinson and others
8
8
  bindir: bin
9
9
  cert_chain: []
10
- date: 2025-03-26 00:00:00.000000000 Z
10
+ date: 2025-03-28 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: drb