hubssolib 0.2.9 → 2.0.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.
data/lib/hub_sso_lib.rb CHANGED
@@ -1,6 +1,6 @@
1
1
  #######################################################################
2
2
  # Module: HubSsoLib #
3
- # (C) Hipposoft 2006-2014 #
3
+ # (C) Hipposoft 2006-2019 #
4
4
  # #
5
5
  # Purpose: Cross-application same domain single sign-on support. #
6
6
  # #
@@ -12,297 +12,69 @@
12
12
  # path come from environment variables. #
13
13
  # 09-Mar-2011 (ADH): Updated for Hub on Rails 2.3.11 along #
14
14
  # with several important bug fixes. #
15
- # 04-May-2014 (ADH): HUB_BYPASS_SSL environment variable #
16
- # aids testing with e.g. Mongrel by #
17
- # allowing non-SSL use. #
15
+ # 01-May-2019 (ADH): Updated for Ruby 2.5.x. #
18
16
  #######################################################################
19
17
 
20
18
  module HubSsoLib
21
19
 
22
20
  require 'drb'
21
+ require 'securerandom'
23
22
 
24
23
  # DRb connection
25
- HUBSSOLIB_DRB_URI = ENV['HUB_CONNECTION_URI']
24
+ HUB_CONNECTION_URI = ENV['HUB_CONNECTION_URI'] || 'drbunix:' + File.join( ENV['HOME'] || '/', '/.hub_drb')
25
+
26
+ unless HUB_CONNECTION_URI.downcase.start_with?('drbunix:')
27
+ puts
28
+ puts '*' * 80
29
+ puts "You *must* use a 'drbunix:' scheme for HUB_CONNECTION_URI (#{ HUB_CONNECTION_URI.inspect } is invalid)"
30
+ puts '*' * 80
31
+ puts
32
+
33
+ raise 'Exiting'
34
+ end
26
35
 
27
36
  # Location of Hub application root.
28
- HUB_PATH_PREFIX = ENV['HUB_PATH_PREFIX']
37
+ HUB_PATH_PREFIX = ENV['HUB_PATH_PREFIX'] || ''
29
38
 
30
39
  # Time limit, *in seconds*, for the account inactivity timeout.
31
40
  # If a user performs no Hub actions during this time they will
32
41
  # be automatically logged out upon their next action.
33
- HUBSSOLIB_IDLE_TIME_LIMIT = 60 * 60
34
-
35
- # Random file location.
36
- HUBSSOLIB_RND_FILE_PATH = ENV['HUB_RANDOM_FILE']
42
+ HUB_IDLE_TIME_LIMIT = 4 * 60 * 60
37
43
 
38
- # Shared cookie name and path.
39
- HUBSSOLIB_COOKIE_NAME = 'hubapp_shared_id'
40
- HUBSSOLIB_COOKIE_PATH = ENV['HUB_COOKIE_PATH']
44
+ # Shared cookie name.
45
+ HUB_COOKIE_NAME = 'hubapp_shared_id'
41
46
 
42
- # Bypass SSL, for testing purposes?
43
- HUBSSOLIB_BYPASS_SSL = ( ENV['HUB_BYPASS_SSL'] == "true" )
47
+ # Bypass SSL, for testing purposes? Rails 'production' mode will
48
+ # insist on SSL otherwise. Development & test environments do not,
49
+ # so do not need this variable setting.
50
+ HUB_BYPASS_SSL = ( ENV['HUB_BYPASS_SSL'] == "true" )
44
51
 
45
- # Cache the random data. Assuming FCGI or similar, this code gets
46
- # executed only once per FCGI instance initialisation rather than
47
- # once per request.
48
-
49
- attr_reader :HUBSSOLIB_RANDOM_DATA, :HUBSSOLIB_RANDOM_DATA_SIZE
50
-
51
- HUBSSOLIB_RANDOM_DATA_SIZE = File.size(HUBSSOLIB_RND_FILE_PATH)
52
- HUBSSOLIB_RANDOM_DATA_SIZE = 16384 if (HUBSSOLIB_RANDOM_DATA_SIZE > 16384)
53
-
54
- if HUBSSOLIB_RANDOM_DATA_SIZE < 1024
55
- raise "HubSsoLib needs at least 1024 bytes of random data - file '#{rnd_file}' is too small"
56
- else
57
- HUBSSOLIB_RANDOM_DATA = File.read(HUBSSOLIB_RND_FILE_PATH)
58
- end
52
+ # Thread safety.
53
+ HUB_MUTEX = Mutex.new
59
54
 
60
55
  #######################################################################
61
- # Class: Crypto #
62
- # (C) Hipposoft 2006 #
56
+ # Class: Serialiser #
57
+ # (C) Hipposoft 2020 #
63
58
  # #
64
- # Purpose: Encryption and decryption utilities. #
59
+ # Purpose: Simple object serialiser/deserialiser. #
65
60
  # #
66
61
  # Author: A.D.Hodgkinson #
67
62
  # #
68
- # History: 28-Aug-2006 (ADH): First version. #
69
- # 20-Oct-2006 (ADH): Integrated into HubSsoLib, renamed to #
70
- # 'Crypto' from 'HubSsoCrypto'. #
63
+ # History: 18-Apr-2002 (ADH): First version. #
71
64
  #######################################################################
72
65
 
73
- # Encryption and decryption utility object. Once instantiated, a
74
- # HubSsoLib::Crypto object is used to encrypt and decrypt data with the
75
- # AES-256-CBC cipher. A single passphrase is used for both operations.
76
- # A SHA-256 hash of that passphrase is used as the encryption key.
77
- #
78
- # CBC operation requires an initialization vector for the first block of
79
- # data during encryption and decryption. A block of random data is used
80
- # for this in conjunction with the passphrase used to generate the key. By
81
- # so doing, the initialization vector is not revealed to third parties,
82
- # even though the source code of the object is available. The weakness is
83
- # that for a given passphrase and random data pool the same initialization
84
- # vector will always be generated - indeed, this is relied upon, to allow
85
- # callers themselves to only have to remember the passphrase. See private
86
- # method obtain_iv() for more details.
66
+ # Simple object serialiser and deserialiser using Marshal and Base64.
87
67
  #
88
- # The block of random data is obtained from the DRb server. It is usually
89
- # a RAM-cached near-random file. The important behaviour is that the
90
- # contents are not know to the outside world and the contents, while they
91
- # may change at any point, don't change during the duration of a user
92
- # log-in session (at least, if it changes, all current sessions will be
93
- # harmlessly invalidated).
94
- #
95
- class Crypto
96
-
97
- require 'openssl'
98
- require 'digest/sha2'
99
- require 'digest/md5'
100
-
101
- # # Initialize the HubSsoLib::Crypto object.
102
- # #
103
- # def initialize()
104
- # DRb.start_service()
105
- #
106
- # factory = DRbObject.new_with_uri(HUBSSOLIB_DRB_URI)
107
- # @rnd_data = factory.random_data()
108
- # @rnd_size = factory.random_data_size()
109
- #
110
- # DRb.stop_service()
111
- # end
112
-
113
- # Generate a series of pseudo-random bytes of the given length.
114
- #
115
- def self.random_data(size)
116
- data = ''
117
- size.times { data << rand(256) }
118
- data
119
- end
120
-
121
- def random_data(size)
122
- HubSsoLib::Crypto.random_data(size)
123
- end
124
-
125
- # Encode some given data in base-64 format with no line breaks.
126
- #
127
- def self.pack64(data)
128
- [data].pack('m1000000') # Stupid long number to avoid "\n" in the output
129
- end
130
-
131
- def pack64(data)
132
- HubSsoLib::Crypto.pack64(data)
133
- end
134
-
135
- # Decode some given data from base-64 format with no line breaks.
136
- #
137
- def self.unpack64(data)
138
- data.unpack('m').first
139
- end
140
-
141
- def unpack64(data)
142
- HubSsoLib::Crypto.unpack64(data)
143
- end
144
-
145
- # Encrypt the given data with the AES-256-CBC algorithm using the
146
- # given passphrase. Returns the encrypted result in a string.
147
- # Distantly based upon:
148
- #
149
- # http://www.bigbold.com/snippets/posts/show/576
150
- #
151
- # In the context of Hub, the passphrase tends to be fixed per IP
152
- # address (albeit unknown to the public) and the IV is derived from
153
- # it. This means the same data will encode to the same result. With
154
- # the source data having some parts which are invariant, security
155
- # is compromised. To avoid this, data is prefixed by a quantity of
156
- # random bytes, effectively supplementing the IV and ensuring that
157
- # different size and content data is generated each time.
158
- #
159
- def encrypt(data, passphrase)
160
- cipher = OpenSSL::Cipher::Cipher.new("aes-256-cbc")
161
- cipher.encrypt
162
-
163
- cipher.key = Digest::SHA256.digest(passphrase)
164
- cipher.iv = obtain_iv(passphrase)
165
-
166
- rsize = rand(32)
167
- data = '' << rsize << random_data(rsize) << data
168
-
169
- encrypted = cipher.update(data)
170
- encrypted << cipher.final
171
-
172
- return encrypted
173
- end
174
-
175
- # Decrypt the given data with the AES-256-CBC algorithm using the
176
- # given passphrase. Returns 'nil' if there is any kind of error in
177
- # the decryption process. Distantly based upon:
178
- #
179
- # http://www.bigbold.com/snippets/posts/show/576
180
- #
181
- def decrypt(data, passphrase)
182
- cipher = OpenSSL::Cipher::Cipher.new("aes-256-cbc")
183
- cipher.decrypt
184
-
185
- cipher.key = Digest::SHA256.digest(passphrase)
186
- cipher.iv = obtain_iv(passphrase)
187
-
188
- decrypted = cipher.update(data)
189
- decrypted << cipher.final
190
-
191
- rsize = decrypted[0]
192
- return decrypted[rsize + 1..-1]
193
- rescue
194
- return nil
195
- end
196
-
197
- # Encrypt and base-64 encode the given data with the given passphrase.
198
- # Returns the encoded result.
199
- #
200
- def encode(data, passphrase)
201
- pack64(encrypt(data, passphrase))
202
- end
203
-
204
- # Decrypt and base-64 decode the given data with the given passphrase.
205
- # Returns the decoded result or 'nil' on error.
206
- #
207
- def decode(data, passphrase)
208
- decrypt(unpack64(data), passphrase)
209
- rescue
210
- return nil
211
- end
212
-
213
- # Class method that takes an object and passphrase and encrypts
214
- # the result. The passphrase is scrambled internally using data
215
- # not available to the public, the object serialised (so it must
216
- # support serialisation), encrypted and base-64 encoded, and the
217
- # 7-bit safe string result returned. On failure, exceptions will
218
- # be raised (failure is not expected).
219
- #
220
- def self.encode_object(object, passphrase)
221
- crypto = HubSsoLib::Crypto.new
222
- passphrase = crypto.scramble_passphrase(passphrase)
223
-
224
- return crypto.encode(Marshal.dump(object), passphrase)
225
- end
226
-
227
- def encode_object(object, passphrase)
228
- HubSsoLib::Crypto.encode_object(object, passphrase)
229
- end
230
-
231
- # Class method that takes output from Crypto.encode_object and
232
- # decodes it, returning an object reference. Since failure may
233
- # result from invalid data input and this can be a common case,
234
- # rather than raise an exception as with Crypto.encode_object,
235
- # this method returns 'nil' should there be any decode problems.
236
- #
237
- def self.decode_object(data, passphrase)
238
- crypto = HubSsoLib::Crypto.new
239
- passphrase = crypto.scramble_passphrase(passphrase)
240
- object = nil
241
-
242
- if (data && !data.empty?)
243
- object = Marshal.load(crypto.decode(data, passphrase))
244
- end
245
-
246
- return object
247
- rescue
248
- return nil
249
- end
68
+ class Serialiser
69
+ require 'base64'
250
70
 
251
- def decode_object(data, passphrase)
252
- HubSsoLib::Crypto.decode_object(data, passphrase)
71
+ def self.serialise_object(object)
72
+ Base64.strict_encode64(Marshal.dump(object))
253
73
  end
254
74
 
255
- # "Scramble" a passphrase. Cookie data encryption is done purely so that
256
- # some hypothetical malicious user cannot easily examine or modify the
257
- # cookie contents for some nefarious purpose. Encryption is done at the
258
- # head end. We need to be able to decrypt in the absence of any other
259
- # information. A fixed passphrase thus needs to be used, but it cannot be
260
- # included in the source code or anyone can read the cookie contents! To
261
- # work around this, transform the passphrase into 32 bytes of data from
262
- # the random pool if asked. The random pool is not known to the outside
263
- # world so security is improved (albeit far from perfect, but this is all
264
- # part of little more than an anti-spam measure - not Fort Knox!).
265
- #
266
- def scramble_passphrase(passphrase)
267
-
268
- # Generate a 16-byte hash of the passphrase using the MD5 algorithm. Get
269
- # this as a string of hex digits and convert that into an integer. Strip
270
- # off the top bits (since we've no more reason to believe that the top
271
- # bits contain more randomly varying data than the bottom bits) so that
272
- # the number is bound to between zero and the random pool size, minus
273
- # 33, thus providing an offset into the file from which we can safely
274
- # read 32 bytes of data.
275
-
276
- offset = Digest::MD5.hexdigest(passphrase).hex % (HubSsoLib::HUBSSOLIB_RANDOM_DATA_SIZE - 32)
277
-
278
- # Return 32 bytes of data from the random pool at the calculated offset.
279
-
280
- return HubSsoLib::HUBSSOLIB_RANDOM_DATA[offset..offset + 31]
281
- end
282
-
283
- private
284
-
285
- # Obtain an initialization vector (IV) of 32 bytes (256 bits) length based
286
- # on external data loaded when the object was created. Since the data
287
- # content is unknown, the IV is unknown. This is important; see:
288
- #
289
- # http://www.ciphersbyritter.com/GLOSSARY.HTM#CipherBlockChaining
290
- #
291
- # Weakness: An offset into the supplied data is generated from the given
292
- # passphrase. Since the data is cached internally, the same IV will be
293
- # produced for any given passphrase (this is as much a feature as it is a
294
- # weakness, since the encryption and decryption routines rely on it).
295
- #
296
- # The passphrase scrambler is used to do the back-end work. Since the
297
- # caller may have already scrambled the passphrase once, scrambled data is
298
- # used as input; we end up scrambling it twice. This is a desired result -
299
- # we don't want the IV being the data that's actually also used for the
300
- # encryption passphrase.
301
- #
302
- def obtain_iv(passphrase)
303
- return scramble_passphrase(passphrase)
75
+ def self.deserialise_object(data)
76
+ Marshal.load(Base64.strict_decode64(data)) rescue nil
304
77
  end
305
-
306
78
  end # Crypto class
307
79
 
308
80
  #######################################################################
@@ -610,12 +382,16 @@ module HubSsoLib
610
382
  attr_accessor :session_return_to
611
383
  attr_accessor :session_flash
612
384
  attr_accessor :session_user
385
+ attr_accessor :session_key_rotation
386
+ attr_accessor :session_ip
613
387
 
614
388
  def initialize
615
- @session_last_used = Time.now.utc
616
- @session_return_to = nil
617
- @session_flash = {}
618
- @session_user = HubSsoLib::User.new
389
+ @session_last_used = Time.now.utc
390
+ @session_return_to = nil
391
+ @session_flash = {}
392
+ @session_user = HubSsoLib::User.new
393
+ @session_key_rotation = nil
394
+ @session_ip = nil
619
395
  end
620
396
  end # Session class
621
397
 
@@ -633,19 +409,59 @@ module HubSsoLib
633
409
 
634
410
  class SessionFactory
635
411
  def initialize
636
- @sessions = {}
412
+ puts "Session factory: Awaken"
413
+
414
+ @hub_sessions = {}
637
415
  end
638
416
 
639
- def get_session(key)
640
- unless (@sessions.has_key? key)
641
- @sessions[key] = HubSsoLib::Session.new
417
+ # Get a session using a given key (a UUID). Generates a new session if
418
+ # the key is unrecognised or if the IP address given mismatches the one
419
+ # recorded in existing session data.
420
+ #
421
+ # Whether new or pre-existing, the returned session will have changed key
422
+ # as a result of being read; check the #session_key_rotation property to
423
+ # find out the new key. If you fail to do this, you'll lose access to the
424
+ # session data as you won't know which key it lies under.
425
+ #
426
+ # The returned object is proxied via DRb - it is shared between processes.
427
+ #
428
+ # +key+:: Session key; lazy-initialises a new session under this key
429
+ # if none is found, then immeediately rotates it.
430
+ #
431
+ # +remote_ip+:: Request's remote IP address. If there is an existing
432
+ # session which matches this, it's returned. If there is an
433
+ # existing session but the IP mismatches, it's treated as
434
+ # invalid and discarded.
435
+ #
436
+ def get_hub_session_proxy(key, remote_ip)
437
+ retrieve_existing = @hub_sessions.has_key?(key)
438
+ message = retrieve_existing ? 'Retrieving' : 'Created'
439
+ new_key = SecureRandom.uuid
440
+
441
+ puts "#{message} session for key #{key} and rotating to #{new_key}"
442
+
443
+ if retrieve_existing
444
+ hub_session = @hub_sessions[key]
445
+ if remote_ip != hub_session.session_ip
446
+ puts "WARNING: IP address changed from #{hub_session.session_ip} to #{remote_ip} -> discarding session"
447
+ hub_session = @hub_sessions[key] = HubSsoLib::Session.new
448
+ end
449
+
450
+ else
451
+ hub_session = @hub_sessions[key] = HubSsoLib::Session.new
452
+ hub_session.session_ip = remote_ip
453
+
642
454
  end
643
455
 
644
- return @sessions[key]
456
+ @hub_sessions.delete(key)
457
+ @hub_sessions[new_key] = hub_session
458
+
459
+ hub_session.session_key_rotation = new_key
460
+ return hub_session
645
461
  end
646
462
 
647
- def enumerate_sessions
648
- @sessions
463
+ def enumerate_hub_sessions()
464
+ @hub_sessions
649
465
  end
650
466
  end
651
467
 
@@ -667,8 +483,10 @@ module HubSsoLib
667
483
 
668
484
  module Server
669
485
  def hubssolib_launch_server
670
- @@session_factory = HubSsoLib::SessionFactory.new
671
- DRb.start_service(HUBSSOLIB_DRB_URI, @@session_factory)
486
+ puts "Server: Starting at #{ HUB_CONNECTION_URI }"
487
+
488
+ @@hub_session_factory = HubSsoLib::SessionFactory.new
489
+ DRb.start_service(HUB_CONNECTION_URI, @@hub_session_factory, { :safe_level => 1 })
672
490
  DRb.thread.join
673
491
  end
674
492
  end # Server module
@@ -725,7 +543,7 @@ module HubSsoLib
725
543
  return false unless hubssolib_logged_in?
726
544
 
727
545
  pnormal = HubSsoLib::Roles.new(false).to_s
728
- puser = hubssolib_get_user_roles.to_s
546
+ puser = hubssolib_get_user_roles().to_s
729
547
 
730
548
  return (puser && !puser.empty? && puser != pnormal)
731
549
  end
@@ -736,33 +554,36 @@ module HubSsoLib
736
554
  def hubssolib_log_out
737
555
  # Causes the "hubssolib_current_[foo]=" methods to run, which
738
556
  # deal with everything else.
739
- self.hubssolib_current_user = nil
740
- self.hubssolib_current_session = nil
557
+ self.hubssolib_current_user = nil
558
+ @hubssolib_current_session_proxy = nil
741
559
  end
742
560
 
743
- # Accesses the current user, via the DRb server if necessary
561
+ # Accesses the current session from the cookie. Creates a new session
562
+ # object if need be, but can return +nil+ if e.g. attempting to access
563
+ # session cookie data without SSL.
744
564
  #
745
- def hubssolib_current_user
746
- hubssolib_get_user_data
565
+ def hubssolib_current_session
566
+ @hubssolib_current_session_proxy ||= hubssolib_get_session_proxy()
747
567
  end
748
568
 
749
- # Store the given user data in the cookie
569
+ # Accesses the current user, via the DRb server if necessary.
750
570
  #
751
- def hubssolib_current_user=(new_user)
752
- hubssolib_set_user_data(new_user)
753
- end
571
+ def hubssolib_current_user
572
+ hub_session = self.hubssolib_current_session
573
+ user = hub_session.nil? ? nil : hub_session.session_user
754
574
 
755
- # Accesses the current session from the cookie. Creates a new
756
- # session object if need be.
757
- #
758
- def hubssolib_current_session
759
- @hubssolib_current_session ||= hubssolib_get_session_data
575
+ if (user && user.user_id)
576
+ return user
577
+ else
578
+ return nil
579
+ end
760
580
  end
761
581
 
762
- # Store the given session data.
582
+ # Store the given user data in the cookie
763
583
  #
764
- def hubssolib_current_session=(new_session)
765
- @hubssolib_current_session = new_session
584
+ def hubssolib_current_user=(user)
585
+ hub_session = self.hubssolib_current_session
586
+ hub_session.session_user = user unless hub_session.nil?
766
587
  end
767
588
 
768
589
  # Public read-only accessor methods for common user activities:
@@ -845,23 +666,7 @@ module HubSsoLib
845
666
  # So we reach here knowing we're logged in, but the action may or
846
667
  # may not require authorisation.
847
668
 
848
- unless (login_is_required)
849
-
850
- # We have to update session expiry even for actions that don't
851
- # need us to be logged in, since we *are* logged in and need to
852
- # maintain that state. If, though, the session expires, we just
853
- # quietly log out and let action processing carry on.
854
-
855
- if (hubssolib_session_expired?)
856
- hubssolib_log_out
857
- hubssolib_set_flash(:attention, 'Your session timed out, so you are no longer logged in.')
858
- else
859
- hubssolib_set_last_used(Time.now.utc)
860
- end
861
-
862
- return true # true -> let action processing continue
863
-
864
- else
669
+ if (login_is_required)
865
670
 
866
671
  # Login *is* required for this action. If the session expires,
867
672
  # redirect to Hub's login page via its expiry action. Otherwise
@@ -882,6 +687,22 @@ module HubSsoLib
882
687
  return hubssolib_authorized? ? true : hubssolib_access_denied
883
688
  end
884
689
 
690
+ else
691
+
692
+ # We have to update session expiry even for actions that don't
693
+ # need us to be logged in, since we *are* logged in and need to
694
+ # maintain that state. If, though, the session expires, we just
695
+ # quietly log out and let action processing carry on.
696
+
697
+ if (hubssolib_session_expired?)
698
+ hubssolib_log_out
699
+ hubssolib_set_flash(:attention, 'Your session timed out, so you are no longer logged in.')
700
+ else
701
+ hubssolib_set_last_used(Time.now.utc)
702
+ end
703
+
704
+ return true # true -> let action processing continue
705
+
885
706
  end
886
707
  end
887
708
 
@@ -901,7 +722,7 @@ module HubSsoLib
901
722
  #
902
723
  # We can return to this location by calling #redirect_back_or_default.
903
724
  #
904
- def hubssolib_store_location(uri_str = request.request_uri)
725
+ def hubssolib_store_location(uri_str = request.url)
905
726
 
906
727
  if (uri_str && !uri_str.empty?)
907
728
  uri_str = hubssolib_promote_uri_to_ssl(uri_str, request.host) unless request.ssl?
@@ -929,7 +750,7 @@ module HubSsoLib
929
750
  def hubssolib_promote_uri_to_ssl(uri_str, host = nil)
930
751
  uri = URI.parse(uri_str)
931
752
  uri.host = host if host
932
- uri.scheme = HUBSSOLIB_BYPASS_SSL ? 'http' : 'https'
753
+ uri.scheme = hub_bypass_ssl? ? 'http' : 'https'
933
754
  return uri.to_s
934
755
  end
935
756
 
@@ -938,7 +759,7 @@ module HubSsoLib
938
759
  # 'true' if not redirected (already HTTPS), else 'false'.
939
760
  #
940
761
  def hubssolib_ensure_https
941
- if request.ssl? || HUBSSOLIB_BYPASS_SSL
762
+ if request.ssl? || hub_bypass_ssl?
942
763
  return true
943
764
  else
944
765
  # This isn't reliable: redirect_to({ :protocol => 'https://' })
@@ -960,7 +781,7 @@ module HubSsoLib
960
781
  def hubssolib_set_flash(symbol, message)
961
782
  return unless self.hubssolib_current_session
962
783
  f = hubssolib_get_flash
963
- f[symbol] = message
784
+ f[symbol.to_s] = message
964
785
  self.hubssolib_current_session.session_flash = f
965
786
  end
966
787
 
@@ -969,63 +790,51 @@ module HubSsoLib
969
790
  self.hubssolib_current_session.session_flash = {}
970
791
  end
971
792
 
972
- # Helper methods to output flash data. It isn't merged into the standard
973
- # application flash with a filter because the rather daft and difficult
974
- # to manage lifecycle model of the standard flash gets in the way.
793
+ # Return flash data for known keys, then all remaining keys, from both
794
+ # the cross-application and standard standard flash hashes. The returned
795
+ # Hash is of the form:
975
796
  #
976
- # First, return tags for a flash using the given key, clearing the
977
- # result in the flash hash now it has been used.
797
+ # { 'hub' => ...data..., 'standard' => ...data... }
978
798
  #
979
- def hubssolib_flash_tag(key)
980
- value = hubssolib_get_flash[key]
981
-
982
- if (value)
983
- hubssolib_set_flash(key, nil)
984
- return "<h2 align=\"left\" class=\"#{key}\">#{value}</h2><p />"
985
- else
986
- return ''
987
- end
988
- end
989
-
990
- # Next, return tags for a standard application flash using the given key.
799
+ # ...where "...data..." is itself a Hash of flash keys yielding flash
800
+ # values. This allows both the Hub and standard flashes to have values
801
+ # inside them under the same key. All keys are strings.
991
802
  #
992
- def hubssolib_standard_flash_tag(key)
993
- value = flash[key] if defined?(flash)
803
+ def hubssolib_flash_data
994
804
 
995
- if (value)
996
- flash.delete(key)
997
- return "<h2 align=\"left\" class=\"#{key}\">#{value}</h2><p />"
998
- else
999
- return ''
1000
- end
1001
- end
1002
-
1003
- # Return flash tags for known keys, then all remaining keys, from both
1004
- # the cross-application and standard standard flash hashes.
1005
- #
1006
- def hubssolib_flash_tags
1007
805
  # These known key values are used to guarantee an order in the output
1008
806
  # for cases where multiple messages are defined.
807
+ #
808
+ compiled_data = { 'hub' => {}, 'standard' => {} }
809
+ ordered_keys = [
810
+ 'notice',
811
+ 'attention',
812
+ 'alert'
813
+ ]
814
+
815
+ # Get an array of keys for the Hub flash with the ordered key items
816
+ # first and store data from that flash; same again for standard.
1009
817
 
1010
- tags = hubssolib_flash_tag(:notice) <<
1011
- hubssolib_flash_tag(:attention) <<
1012
- hubssolib_flash_tag(:alert)
818
+ hash = hubssolib_get_flash()
819
+ keys = ordered_keys | hash.keys
1013
820
 
1014
- tags << hubssolib_standard_flash_tag(:notice) <<
1015
- hubssolib_standard_flash_tag(:attention) <<
1016
- hubssolib_standard_flash_tag(:alert)
821
+ keys.each do | key |
822
+ compiled_data[ 'hub' ][ key ] = hash[ key ] if hash.has_key?( key )
823
+ end
1017
824
 
1018
- # Now pick up anything else.
825
+ if defined?( flash )
826
+ hash = flash.to_h()
827
+ keys = ordered_keys | hash.keys
1019
828
 
1020
- hubssolib_get_flash.each do |key, value|
1021
- tags << hubssolib_flash_tag(key) if (value and !value.empty?)
829
+ keys.each do | key |
830
+ compiled_data[ 'standard' ][ key ] = hash[ key ] if hash.has_key?( key )
831
+ end
1022
832
  end
1023
833
 
1024
- flash.each do |key, value|
1025
- tags << hubssolib_standard_flash_tag(key) if (value and !value.empty?)
1026
- end if defined?(flash)
834
+ hubssolib_clear_flash()
835
+ flash.discard()
1027
836
 
1028
- return tags
837
+ return compiled_data
1029
838
  end
1030
839
 
1031
840
  # Retrieve the message of an exception stored as an object in the given
@@ -1045,7 +854,7 @@ module HubSsoLib
1045
854
  :hubssolib_logged_in?,
1046
855
  :hubssolib_authorized?,
1047
856
  :hubssolib_privileged?,
1048
- :hubssolib_flash_tags
857
+ :hubssolib_flash_data
1049
858
  rescue
1050
859
  # We're not always included in controllers...
1051
860
  nil
@@ -1053,6 +862,28 @@ module HubSsoLib
1053
862
 
1054
863
  private
1055
864
 
865
+ # Establish a single DRb factory connection.
866
+ #
867
+ def hubssolib_factory
868
+ HUB_MUTEX.synchronize do
869
+ begin
870
+ DRb.current_server
871
+ rescue DRb::DRbServerNotFound
872
+ DRb.start_service()
873
+ end
874
+
875
+ @factory ||= DRbObject.new_with_uri(HUB_CONNECTION_URI)
876
+ end
877
+
878
+ return @factory
879
+ end
880
+
881
+ # Helper that decides if we should insist on SSL (or not).
882
+ #
883
+ def hub_bypass_ssl?
884
+ HUB_BYPASS_SSL || ! Rails.env.production?
885
+ end
886
+
1056
887
  # Indicate that the user must log in to complete their request.
1057
888
  # Returns false to enable a before_filter to return through this
1058
889
  # method while ensuring that the previous action processing is
@@ -1115,87 +946,26 @@ module HubSsoLib
1115
946
  # considered acceptable, though who knows, the view may change in future.
1116
947
 
1117
948
  last_used = hubssolib_get_last_used
1118
- (request.method != :post && last_used && Time.now.utc - last_used > HUBSSOLIB_IDLE_TIME_LIMIT)
1119
- end
1120
-
1121
- # Retrieve data from a given cookie with encrypted contents.
1122
- #
1123
- def hubssolib_get_secure_cookie_data(name)
1124
- return HubSsoLib::Crypto.decode_object(cookies[name], request.remote_ip)
949
+ (request.method != :post && last_used && Time.now.utc - last_used > HUB_IDLE_TIME_LIMIT)
1125
950
  end
1126
951
 
1127
- # Set the given cookie to a value of the given data, which
1128
- # will be encrypted.
1129
- #
1130
- def hubssolib_set_secure_cookie_data(name, value)
1131
- if (@hubssolib_have_written_cookie)
1132
- raise "HubSsoLib: Attmept to set cookie '#{name}' more than once"
1133
- end
1134
-
1135
- @hubssolib_have_written_cookie = true
1136
-
1137
- # Using cookies.delete *should* work but doesn't. Set the
1138
- # cookie with nil data instead.
1139
-
1140
- data = value.nil? ? nil : HubSsoLib::Crypto.encode_object(value, request.remote_ip)
1141
-
1142
- # No expiry time; to aid security, use session cookies only.
1143
-
1144
- cookies[name] = {
1145
- :value => data,
1146
- :path => HUBSSOLIB_COOKIE_PATH,
1147
- :secure => ! HUBSSOLIB_BYPASS_SSL
1148
- }
1149
- end
1150
-
1151
- # Establish a single DRb factory connection.
1152
- #
1153
- def hubssolib_factory
1154
-
1155
- # See:
1156
- # http://stackoverflow.com/questions/299219/where-is-the-correct-place-to-initialize-the-drb-service-within-a-rails-applicati
1157
- #
1158
- begin
1159
- DRb.current_server
1160
- rescue DRb::DRbServerNotFound
1161
- DRb.start_service
1162
- # Move to different ThreadGroup to stop Mongrel hang on exit.
1163
- ThreadGroup.new.add DRb.thread
1164
- end
1165
-
1166
- return @factory ||= DRbObject.new_with_uri(HUBSSOLIB_DRB_URI)
1167
- end
1168
-
1169
- # Retrieve user data from the DRb server.
1170
- #
1171
- def hubssolib_get_user_data
1172
- user = self.hubssolib_current_session ? self.hubssolib_current_session.session_user : nil
1173
-
1174
- if (user && user.user_id)
1175
- return user
1176
- else
1177
- return nil
1178
- end
1179
- end
1180
-
1181
- def hubssolib_set_user_data(user)
1182
- self.hubssolib_current_session.session_user = user
1183
- end
1184
-
1185
- def hubssolib_get_session_data
1186
-
952
+ def hubssolib_get_session_proxy
1187
953
  # If we're not using SSL, forget it
1188
- return nil unless request.ssl? || HUBSSOLIB_BYPASS_SSL
954
+ return nil unless request.ssl? || hub_bypass_ssl?
1189
955
 
1190
- # If we've no cookie, we need a new session ID
1191
- key = hubssolib_get_secure_cookie_data(HUBSSOLIB_COOKIE_NAME)
956
+ key = cookies[HUB_COOKIE_NAME] || SecureRandom.uuid
957
+ hub_session = hubssolib_factory().get_hub_session_proxy(key, request.remote_ip)
958
+ key = hub_session.session_key_rotation unless hub_session.nil?
1192
959
 
1193
- unless (key)
1194
- key = HubSsoLib::Crypto.pack64(HubSsoLib::Crypto.random_data(48))
1195
- hubssolib_set_secure_cookie_data(HUBSSOLIB_COOKIE_NAME, key)
1196
- end
960
+ cookies[HUB_COOKIE_NAME] = {
961
+ :value => key,
962
+ :domain => :all,
963
+ :path => '/',
964
+ :secure => ! hub_bypass_ssl?,
965
+ :httponly => true
966
+ }
1197
967
 
1198
- return hubssolib_factory().get_session(key)
968
+ return hub_session
1199
969
 
1200
970
  rescue Exception => e
1201
971
 
@@ -1208,7 +978,8 @@ module HubSsoLib
1208
978
 
1209
979
  suffix = '/' + CGI::escape(CGI::escape(hubssolib_set_exception_data(e)))
1210
980
  new_path = HUB_PATH_PREFIX + '/tasks/service'
1211
- redirect_to new_path + suffix unless request.path.include?(new_path)
981
+ redirect_to(new_path + suffix) unless request.path.include?(new_path)
982
+
1212
983
  return nil
1213
984
  end
1214
985
 
@@ -1225,7 +996,7 @@ module HubSsoLib
1225
996
  # be allowed access.
1226
997
  #
1227
998
  def hubssolib_enumerate_users
1228
- sessions = hubssolib_factory().enumerate_sessions()
999
+ sessions = hubssolib_factory().enumerate_hub_sessions()
1229
1000
  users = []
1230
1001
 
1231
1002
  sessions.each do |key, value|
@@ -1252,7 +1023,12 @@ module HubSsoLib
1252
1023
  # message.
1253
1024
  #
1254
1025
  def hubssolib_set_exception_data(e)
1255
- HubSsoLib::Crypto.encode_object(e.message, request.remote_ip)
1026
+ if Rails.env.development?
1027
+ backtrace = e.backtrace.join( ", " )
1028
+ HubSsoLib::Serialiser.serialise_object("#{ e.message }: #{ backtrace }"[0..511])
1029
+ else
1030
+ HubSsoLib::Serialiser.serialise_object(e.message[0..511])
1031
+ end
1256
1032
  end
1257
1033
 
1258
1034
  # Decode exception data encoded with hubssolib_set_exception_data.
@@ -1260,7 +1036,7 @@ module HubSsoLib
1260
1036
  # are any decoding problems. Pass the encoded data.
1261
1037
  #
1262
1038
  def hubssolib_get_exception_data(data)
1263
- HubSsoLib::Crypto.decode_object(data, request.remote_ip)
1039
+ HubSsoLib::Serialiser.deserialise_object(data)
1264
1040
  end
1265
1041
 
1266
1042
  # Various accessors that ultimately run through the DRb server if