hubssolib 0.2.9 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
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