hubssolib 0.2.9

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.
Files changed (3) hide show
  1. data/VERSION +1 -0
  2. data/lib/hub_sso_lib.rb +1345 -0
  3. metadata +68 -0
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.2.9 (04-May-2014)
@@ -0,0 +1,1345 @@
1
+ #######################################################################
2
+ # Module: HubSsoLib #
3
+ # (C) Hipposoft 2006-2014 #
4
+ # #
5
+ # Purpose: Cross-application same domain single sign-on support. #
6
+ # #
7
+ # Author: A.D.Hodgkinson #
8
+ # #
9
+ # History: 20-Oct-2006 (ADH): First version of stand-alone library, #
10
+ # split from Hub application. #
11
+ # 08-Dec-2006 (ADH): DRB URI, path prefix and random file #
12
+ # path come from environment variables. #
13
+ # 09-Mar-2011 (ADH): Updated for Hub on Rails 2.3.11 along #
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. #
18
+ #######################################################################
19
+
20
+ module HubSsoLib
21
+
22
+ require 'drb'
23
+
24
+ # DRb connection
25
+ HUBSSOLIB_DRB_URI = ENV['HUB_CONNECTION_URI']
26
+
27
+ # Location of Hub application root.
28
+ HUB_PATH_PREFIX = ENV['HUB_PATH_PREFIX']
29
+
30
+ # Time limit, *in seconds*, for the account inactivity timeout.
31
+ # If a user performs no Hub actions during this time they will
32
+ # 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']
37
+
38
+ # Shared cookie name and path.
39
+ HUBSSOLIB_COOKIE_NAME = 'hubapp_shared_id'
40
+ HUBSSOLIB_COOKIE_PATH = ENV['HUB_COOKIE_PATH']
41
+
42
+ # Bypass SSL, for testing purposes?
43
+ HUBSSOLIB_BYPASS_SSL = ( ENV['HUB_BYPASS_SSL'] == "true" )
44
+
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
59
+
60
+ #######################################################################
61
+ # Class: Crypto #
62
+ # (C) Hipposoft 2006 #
63
+ # #
64
+ # Purpose: Encryption and decryption utilities. #
65
+ # #
66
+ # Author: A.D.Hodgkinson #
67
+ # #
68
+ # History: 28-Aug-2006 (ADH): First version. #
69
+ # 20-Oct-2006 (ADH): Integrated into HubSsoLib, renamed to #
70
+ # 'Crypto' from 'HubSsoCrypto'. #
71
+ #######################################################################
72
+
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.
87
+ #
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
250
+
251
+ def decode_object(data, passphrase)
252
+ HubSsoLib::Crypto.decode_object(data, passphrase)
253
+ end
254
+
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)
304
+ end
305
+
306
+ end # Crypto class
307
+
308
+ #######################################################################
309
+ # Class: Roles #
310
+ # (C) Hipposoft 2006 #
311
+ # #
312
+ # Purpose: Shared methods for handling user account roles. #
313
+ # #
314
+ # Author: A.D.Hodgkinson #
315
+ # #
316
+ # History: 17-Oct-2006 (ADH): Adapted from Clubhouse. #
317
+ # 20-Oct-2006 (ADH): Integrated into HubSsoLib. #
318
+ #######################################################################
319
+
320
+ class Roles
321
+
322
+ # Association of symbolic role names to display names, in no
323
+ # particular order.
324
+ #
325
+ ROLES = {
326
+ :admin => 'Administrator',
327
+ :webmaster => 'Webmaster',
328
+ :privileged => 'Advanced user',
329
+ :normal => 'Normal user'
330
+ }
331
+
332
+ ADMIN = :admin
333
+ NORMAL = :normal
334
+
335
+ # Return the display name of a given role symbol. Class method.
336
+ #
337
+ def self.get_display_name(symbol)
338
+ ROLES[symbol]
339
+ end
340
+
341
+ # Return all display names in an array. Class method.
342
+
343
+ def self.get_display_names
344
+ ROLES.values
345
+ end
346
+
347
+ # Return an array of known role symbols. They can be used with
348
+ # methods like get_display_name. Class method.
349
+
350
+ def self.get_role_symbols
351
+ ROLES.keys
352
+ end
353
+
354
+ # Initialize a new Roles object. Pass 'true' if this is for
355
+ # an admin user account, else 'false'. Default is 'false'. Note
356
+ # that further down in this file, the String, Symbol and Array
357
+ # classes are extended with to_authenticated_roles methods, which
358
+ # provide other ways of creating Roles objects.
359
+ #
360
+ def initialize(admin = false)
361
+ if (admin)
362
+ @role_array = [ ADMIN ]
363
+ else
364
+ @role_array = [ NORMAL ]
365
+ end
366
+ end
367
+
368
+ # Adds a role, supplied as a string or symbol, to the internal list.
369
+ # A non-nil return indicates that the role was already present.
370
+ #
371
+ def add(role)
372
+ @role_array.push(role.to_s.intern).uniq!
373
+ end
374
+
375
+ # Deletes a role, supplied as a string or symbol, from the internal
376
+ # list. A nil return indicates that the role was not in the list.
377
+ #
378
+ def delete(role)
379
+ @role_array.delete(role.to_s.intern)
380
+ end
381
+
382
+ # Delete all roles from the internal list.
383
+ #
384
+ def clear
385
+ @role_array.clear
386
+ end
387
+
388
+ # Return a copy of the internal roles list as a string.
389
+ #
390
+ def to_s
391
+ return @role_array.join(',')
392
+ end
393
+
394
+ # Return a copy of the internal roles list as an array.
395
+ #
396
+ def to_a
397
+ return @role_array.dup
398
+ end
399
+
400
+ # Return a copy of the intenal roles list as a human readable string.
401
+ #
402
+ def to_human_s
403
+ human_names = []
404
+
405
+ @role_array.each do |role|
406
+ human_names.push(HubSsoLib::Roles.get_display_name(role))
407
+ end
408
+
409
+ if (human_names.length == 0)
410
+ return ''
411
+ elsif (human_names.length == 1)
412
+ return human_names[0]
413
+ else
414
+ return human_names[0..-2].join(', ') + ' and ' + human_names.last
415
+ end
416
+ end
417
+
418
+ # Do nothing - this is just useful for polymorphic code, where a function
419
+ # can take a String, Array, Symbol or Roles object and make the
420
+ # same method call to return a Roles object in return.
421
+ #
422
+ def to_authenticated_roles
423
+ return self
424
+ end
425
+
426
+ # Does the internal list of roles include the supplied role or roles?
427
+ # The roles can be given as an array of individual role symbols or
428
+ # equivalent strings, or as a single symbol or single equivalent
429
+ # symbol, or as a string containing equivalents of role symbols in a
430
+ # comma-separated list (no white space or other spurious characters).
431
+ # Returns 'true' if the internal list of roles includes at least one
432
+ # of the supplied roles, else 'false'.
433
+ #
434
+ def include?(roles)
435
+ return false if roles.nil?
436
+
437
+ # Ensure we've an array of roles, one way or another
438
+ roles = roles.to_s if roles.class == Symbol
439
+ roles = roles.split(',') if roles.class == String
440
+
441
+ roles.each do |role|
442
+ return true if @role_array.include?(role.to_s.intern)
443
+ end
444
+
445
+ return false
446
+ end
447
+
448
+ # Synonym for 'include?'.
449
+ #
450
+ alias includes? include?
451
+
452
+ # Validate the list of roles. Validation means ensuring that all
453
+ # roles in this object are found in the internal ROLES hash. Returns
454
+ # true if the roles validate or false if unknown roles are found.
455
+ #
456
+ def validate
457
+ return false if @role_array.empty?
458
+
459
+ @role_array.each do |role|
460
+ return false unless ROLES[role]
461
+ end
462
+
463
+ return true
464
+ end
465
+
466
+ end # Roles class
467
+
468
+ #######################################################################
469
+ # Class: Permissions #
470
+ # (C) Hipposoft 2006 #
471
+ # #
472
+ # Purpose: Methods to help, in conjunction with Roles, determine the #
473
+ # access permissions a particular user is granted. #
474
+ # #
475
+ # Author: A.D.Hodgkinson #
476
+ # #
477
+ # History: 17-Oct-2006 (ADH): Adapted from Clubhouse. #
478
+ # 20-Oct-2006 (ADH): Integrated into HubSsoLib. #
479
+ #######################################################################
480
+
481
+ class Permissions
482
+
483
+ # Initialize a permissions object. The map is a hash which maps action
484
+ # names, expressed as symbols, to roles, expressed as individual symbols,
485
+ # equivalent strings, or arrays of multiple strings or symbols. Use 'nil'
486
+ # to indicate permission for the general public - no login required - or
487
+ # simply omit the action (unlisted actions are permitted).
488
+ #
489
+ # Example mapping for a generic controller:
490
+ #
491
+ # {
492
+ # :new => [ :admin, :webmaster, :privileged, :normal ],
493
+ # :create => [ :admin, :webmaster, :privileged, :normal ],
494
+ # :edit => [ :admin, :webmaster, :privileged, :normal ],
495
+ # :update => [ :admin, :webmaster, :privileged, :normal ],
496
+ # :delete => [ :admin, :webmaster, :privileged ],
497
+ # :list => nil,
498
+ # :show => nil
499
+ # }
500
+ #
501
+ def initialize(pmap)
502
+ @permissions = pmap
503
+ end
504
+
505
+ # Does the given Roles object grant permission for the given action,
506
+ # expressed as a string or symbol? Returns 'true' if so, else 'false'.
507
+ #
508
+ # If a role is given as some other type, an attempt is made to convert
509
+ # it to a Roles object internally (so you could pass a role symbol,
510
+ # string, array of symbols or strings, or comma-separated string).
511
+ #
512
+ # Passing an empty roles string will tell you whether or not the
513
+ # action requires login. Only actions not in the permissions list or
514
+ # those with a 'nil' list of roles will generate a result 'true',
515
+ # since any other actions will require your empty roles string to
516
+ # include at least one role (which it obviously doesn't).
517
+ #
518
+ def permitted?(roles, action)
519
+ action = action.to_s.intern
520
+ roles = roles.to_authenticated_roles
521
+
522
+ return true unless @permissions.include?(action)
523
+ return true if @permissions[action].nil?
524
+ return roles.include?(@permissions[action])
525
+ end
526
+ end # Permissions class
527
+
528
+ #######################################################################
529
+ # Class: User #
530
+ # (C) Hipposoft 2006 #
531
+ # #
532
+ # Purpose: A representation of the Hub application's User Model in #
533
+ # terms of a simple set of properties, so that applications #
534
+ # don't need User access to understand user attributes. #
535
+ # #
536
+ # Author: A.D.Hodgkinson #
537
+ # #
538
+ # History: 21-Oct-2006 (ADH): Created. #
539
+ #######################################################################
540
+
541
+ class User
542
+
543
+ # This *must not* be 'undumped', since it gets passed from clients
544
+ # back to the persistent DRb server process. A client thread may
545
+ # disappear and be recreated by the web server at any time; if the
546
+ # user object is undumpable, then the DRb server has to *call back
547
+ # to the client* (in DRb, clients are also servers...!) to find out
548
+ # about the object. Trouble is, if the client thread has been
549
+ # recreated, the server will be trying to access to stale objects
550
+ # that only exist if the garbage collector hasn't got to them yet.
551
+
552
+ attr_accessor :user_salt
553
+ attr_accessor :user_roles
554
+ attr_accessor :user_updated_at
555
+ attr_accessor :user_activated_at
556
+ attr_accessor :user_real_name
557
+ attr_accessor :user_crypted_password
558
+ attr_accessor :user_remember_token_expires_at
559
+ attr_accessor :user_activation_code
560
+ attr_accessor :user_member_id
561
+ attr_accessor :user_id
562
+ attr_accessor :user_password_reset_code
563
+ attr_accessor :user_remember_token
564
+ attr_accessor :user_email
565
+ attr_accessor :user_created_at
566
+ attr_accessor :user_password_reset_code_expires_at
567
+
568
+ def initialize
569
+ @user_salt = nil
570
+ @user_roles = nil
571
+ @user_updated_at = nil
572
+ @user_activated_at = nil
573
+ @user_real_name = nil
574
+ @user_crypted_password = nil
575
+ @user_remember_token_expires_at = nil
576
+ @user_activation_code = nil
577
+ @user_member_id = nil
578
+ @user_id = nil
579
+ @user_password_reset_code = nil
580
+ @user_remember_token = nil
581
+ @user_email = nil
582
+ @user_created_at = nil
583
+ @user_password_reset_code_expires_at = nil
584
+ end
585
+ end # User class
586
+
587
+ #######################################################################
588
+ # Class: Session #
589
+ # (C) Hipposoft 2006 #
590
+ # #
591
+ # Purpose: Session support object, used to store session metadata in #
592
+ # an insecure cross-application cookie. #
593
+ # #
594
+ # Author: A.D.Hodgkinson #
595
+ # #
596
+ # History: 22-Oct-2006 (ADH): Created. #
597
+ #######################################################################
598
+
599
+ class Session
600
+
601
+ # Unlike a User, this *is* undumpable since it only gets passed from
602
+ # server to client. The server's always here to service requests
603
+ # from the client and used sessions are never garbage collected
604
+ # since the DRb server's front object, a SessionFactory, keeps them
605
+ # in a hash held within an instance variable.
606
+
607
+ include DRb::DRbUndumped
608
+
609
+ attr_accessor :session_last_used
610
+ attr_accessor :session_return_to
611
+ attr_accessor :session_flash
612
+ attr_accessor :session_user
613
+
614
+ def initialize
615
+ @session_last_used = Time.now.utc
616
+ @session_return_to = nil
617
+ @session_flash = {}
618
+ @session_user = HubSsoLib::User.new
619
+ end
620
+ end # Session class
621
+
622
+ #######################################################################
623
+ # Class: SessionFactory #
624
+ # (C) Hipposoft 2006 #
625
+ # #
626
+ # Purpose: Build Session objects for DRb server clients. Maintains a #
627
+ # hash of Session objects. #
628
+ # #
629
+ # Author: A.D.Hodgkinson #
630
+ # #
631
+ # History: 26-Oct-2006 (ADH): Created. #
632
+ #######################################################################
633
+
634
+ class SessionFactory
635
+ def initialize
636
+ @sessions = {}
637
+ end
638
+
639
+ def get_session(key)
640
+ unless (@sessions.has_key? key)
641
+ @sessions[key] = HubSsoLib::Session.new
642
+ end
643
+
644
+ return @sessions[key]
645
+ end
646
+
647
+ def enumerate_sessions
648
+ @sessions
649
+ end
650
+ end
651
+
652
+ #######################################################################
653
+ # Module: Server #
654
+ # (C) Hipposoft 2006 #
655
+ # #
656
+ # Purpose: DRb server to provide shared data across applications. #
657
+ # Thanks to RubyPanther, rubyonrails IRC, for suggesting #
658
+ # this after a cookie-based scheme failed. #
659
+ # #
660
+ # Include the module then call hubssolib_launch_server. The #
661
+ # call will not return as the server runs indefinitely. #
662
+ # #
663
+ # Author: A.D.Hodgkinson #
664
+ # #
665
+ # History: 26-Oct-2006 (ADH): Created. #
666
+ #######################################################################
667
+
668
+ module Server
669
+ def hubssolib_launch_server
670
+ @@session_factory = HubSsoLib::SessionFactory.new
671
+ DRb.start_service(HUBSSOLIB_DRB_URI, @@session_factory)
672
+ DRb.thread.join
673
+ end
674
+ end # Server module
675
+
676
+ #######################################################################
677
+ # Module: Core #
678
+ # Various authors #
679
+ # #
680
+ # Purpose: The barely recognisable core of acts_as_authenticated's #
681
+ # AuthenticatedSystem module, modified to work with the #
682
+ # other parts of HubSsoLib. You should include this module #
683
+ # to use its facilities. #
684
+ # #
685
+ # Author: Various; adaptation by A.D.Hodgkinson #
686
+ # #
687
+ # History: 20-Oct-2006 (ADH): Integrated into HubSsoLib. #
688
+ #######################################################################
689
+
690
+ module Core
691
+
692
+ # Returns true or false if the user is logged in.
693
+ #
694
+ # Preloads @hubssolib_current_user with user data if logged in.
695
+ #
696
+ def hubssolib_logged_in?
697
+ !!self.hubssolib_current_user
698
+ end
699
+
700
+ # Check if the user is authorized to perform the current action. If calling
701
+ # from a helper, pass the action name and class name; otherwise by default,
702
+ # the current action name and 'self.class' will be used.
703
+ #
704
+ # Override this method in your controllers if you want to restrict access
705
+ # to a different set of actions. Presently, the current user's roles are
706
+ # compared against the caller's permissions hash and the action name.
707
+ #
708
+ def hubssolib_authorized?(action = action_name, classname = self.class)
709
+
710
+ # Classes with no permissions object always authorise everything.
711
+ # Otherwise, ask the permissions object for its opinion.
712
+
713
+ if (classname.respond_to? :hubssolib_permissions)
714
+ return classname.hubssolib_permissions.permitted?(hubssolib_get_user_roles, action)
715
+ else
716
+ return true
717
+ end
718
+ end
719
+
720
+ # Is the current user privileged? Anything other than normal user
721
+ # privileges will suffice. Can be called if not logged in. Returns
722
+ # 'false' for logged out or normal user privileges only, else 'true'.
723
+ #
724
+ def hubssolib_privileged?
725
+ return false unless hubssolib_logged_in?
726
+
727
+ pnormal = HubSsoLib::Roles.new(false).to_s
728
+ puser = hubssolib_get_user_roles.to_s
729
+
730
+ return (puser && !puser.empty? && puser != pnormal)
731
+ end
732
+
733
+ # Log out the user. Very few applications should ever need to call this,
734
+ # though Hub certainly does and it gets used internally too.
735
+ #
736
+ def hubssolib_log_out
737
+ # Causes the "hubssolib_current_[foo]=" methods to run, which
738
+ # deal with everything else.
739
+ self.hubssolib_current_user = nil
740
+ self.hubssolib_current_session = nil
741
+ end
742
+
743
+ # Accesses the current user, via the DRb server if necessary
744
+ #
745
+ def hubssolib_current_user
746
+ hubssolib_get_user_data
747
+ end
748
+
749
+ # Store the given user data in the cookie
750
+ #
751
+ def hubssolib_current_user=(new_user)
752
+ hubssolib_set_user_data(new_user)
753
+ end
754
+
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
760
+ end
761
+
762
+ # Store the given session data.
763
+ #
764
+ def hubssolib_current_session=(new_session)
765
+ @hubssolib_current_session = new_session
766
+ end
767
+
768
+ # Public read-only accessor methods for common user activities:
769
+ # return the current user's roles as a Roles object, or nil if
770
+ # there's no user.
771
+ #
772
+ def hubssolib_get_user_roles
773
+ user = self.hubssolib_current_user
774
+ user ? user.user_roles.to_authenticated_roles : nil
775
+ end
776
+
777
+ # Public read-only accessor methods for common user activities:
778
+ # return the current user's name as a string, or nil if there's
779
+ # no user. See also hubssolib_unique_name.
780
+ #
781
+ def hubssolib_get_user_name
782
+ user = self.hubssolib_current_user
783
+ user ? user.user_real_name : nil
784
+ end
785
+
786
+ # Public read-only accessor methods for common user activities:
787
+ # return the Hub database ID of the current user account, or
788
+ # nil if there's no user. See also hubssolib_unique_name.
789
+ #
790
+ def hubssolib_get_user_id
791
+ user = self.hubssolib_current_user
792
+ user ? user.user_id : nil
793
+ end
794
+
795
+ # Public read-only accessor methods for common user activities:
796
+ # return the current user's e-mail address, or nil if there's
797
+ # no user.
798
+ #
799
+ def hubssolib_get_user_address
800
+ user = self.hubssolib_current_user
801
+ user ? user.user_email : nil
802
+ end
803
+
804
+ # Return a human-readable unique ID for a user. We don't want to
805
+ # have e-mail addresses all over the place, but don't want to rely
806
+ # on real names as unique - they aren't. Instead, produce a
807
+ # composite of the user's account database ID (which must be
808
+ # unique by definition) and their real name. See also
809
+ # hubssolib_get_name.
810
+ #
811
+ def hubssolib_unique_name
812
+ user = hubssolib_current_user
813
+ user ? "#{user.user_real_name} (#{user.user_id})" : 'Anonymous'
814
+ end
815
+
816
+ # Main filter method to implement HubSsoLib permissions management,
817
+ # session expiry and so-on. Call from controllers only, always as a
818
+ # before_fitler.
819
+ #
820
+ def hubssolib_beforehand
821
+
822
+ # Does this action require a logged in user?
823
+
824
+ if (self.class.respond_to? :hubssolib_permissions)
825
+ login_is_required = !self.class.hubssolib_permissions.permitted?('', action_name)
826
+ else
827
+ login_is_required = false
828
+ end
829
+
830
+ # If we require login but we're logged out, redirect to Hub login.
831
+
832
+ logged_in = hubssolib_logged_in?
833
+
834
+ if (login_is_required and logged_in == false)
835
+ hubssolib_store_location
836
+ return hubssolib_must_login
837
+ end
838
+
839
+ # If we reach here the user is either logged, or the method does
840
+ # not require them to be. In the latter case, if we're not logged
841
+ # in there is no more work to do - exit early.
842
+
843
+ return true unless logged_in # true -> let action processing continue
844
+
845
+ # So we reach here knowing we're logged in, but the action may or
846
+ # may not require authorisation.
847
+
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
865
+
866
+ # Login *is* required for this action. If the session expires,
867
+ # redirect to Hub's login page via its expiry action. Otherwise
868
+ # check authorisation and allow action processing to continue
869
+ # if OK, else indicate that access is denied.
870
+
871
+ if (hubssolib_session_expired?)
872
+ hubssolib_store_location
873
+ hubssolib_log_out
874
+ hubssolib_set_flash(:attention, 'Sorry, your session timed out; you need to log in again to continue.')
875
+
876
+ # We mean this: redirect_to :controller => 'account', :action => 'login'
877
+ # ...except for the Hub, rather than the current application (whatever
878
+ # it may be).
879
+ redirect_to HUB_PATH_PREFIX + '/account/login'
880
+ else
881
+ hubssolib_set_last_used(Time.now.utc)
882
+ return hubssolib_authorized? ? true : hubssolib_access_denied
883
+ end
884
+
885
+ end
886
+ end
887
+
888
+ # Main after_filter method to tidy up after running state changes.
889
+ #
890
+ def hubssolib_afterwards
891
+ begin
892
+ DRb.current_server
893
+ DRb.stop_service()
894
+ rescue DRb::DRbServerNotFound
895
+ # Nothing to do; no service is running.
896
+ end
897
+ end
898
+
899
+ # Store the URI of the current request in the session, or store the
900
+ # optional supplied specific URI.
901
+ #
902
+ # We can return to this location by calling #redirect_back_or_default.
903
+ #
904
+ def hubssolib_store_location(uri_str = request.request_uri)
905
+
906
+ if (uri_str && !uri_str.empty?)
907
+ uri_str = hubssolib_promote_uri_to_ssl(uri_str, request.host) unless request.ssl?
908
+ hubssolib_set_return_to(uri_str)
909
+ else
910
+ hubssolib_set_return_to(nil)
911
+ end
912
+
913
+ end
914
+
915
+ # Redirect to the URI stored by the most recent store_location call or
916
+ # to the passed default.
917
+ def hubssolib_redirect_back_or_default(default)
918
+ url = hubssolib_get_return_to
919
+ hubssolib_set_return_to(nil)
920
+
921
+ redirect_to(url || default)
922
+ end
923
+
924
+ # Take a URI and pass an optional host parameter. Decomposes the URI,
925
+ # sets the host you provide (or leaves it alone if you omit the
926
+ # parameter), then forces the scheme to 'https'. Returns the result
927
+ # as a flat string.
928
+
929
+ def hubssolib_promote_uri_to_ssl(uri_str, host = nil)
930
+ uri = URI.parse(uri_str)
931
+ uri.host = host if host
932
+ uri.scheme = HUBSSOLIB_BYPASS_SSL ? 'http' : 'https'
933
+ return uri.to_s
934
+ end
935
+
936
+ # Ensure the current request is carried out over HTTPS by redirecting
937
+ # back to the current URL with the HTTPS protocol if it isn't. Returns
938
+ # 'true' if not redirected (already HTTPS), else 'false'.
939
+ #
940
+ def hubssolib_ensure_https
941
+ if request.ssl? || HUBSSOLIB_BYPASS_SSL
942
+ return true
943
+ else
944
+ # This isn't reliable: redirect_to({ :protocol => 'https://' })
945
+ redirect_to( hubssolib_promote_uri_to_ssl( request.request_uri, request.host ) )
946
+ return false
947
+ end
948
+ end
949
+
950
+ # Public methods to set some data that would normally go in @session,
951
+ # but can't because it needs to be accessed across applications. It is
952
+ # put in an insecure support cookie instead. There are some related
953
+ # private methods for things like session expiry too.
954
+ #
955
+ def hubssolib_get_flash()
956
+ f = self.hubssolib_current_session ? self.hubssolib_current_session.session_flash : nil
957
+ return f || {}
958
+ end
959
+
960
+ def hubssolib_set_flash(symbol, message)
961
+ return unless self.hubssolib_current_session
962
+ f = hubssolib_get_flash
963
+ f[symbol] = message
964
+ self.hubssolib_current_session.session_flash = f
965
+ end
966
+
967
+ def hubssolib_clear_flash
968
+ return unless self.hubssolib_current_session
969
+ self.hubssolib_current_session.session_flash = {}
970
+ end
971
+
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.
975
+ #
976
+ # First, return tags for a flash using the given key, clearing the
977
+ # result in the flash hash now it has been used.
978
+ #
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.
991
+ #
992
+ def hubssolib_standard_flash_tag(key)
993
+ value = flash[key] if defined?(flash)
994
+
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
+ # These known key values are used to guarantee an order in the output
1008
+ # for cases where multiple messages are defined.
1009
+
1010
+ tags = hubssolib_flash_tag(:notice) <<
1011
+ hubssolib_flash_tag(:attention) <<
1012
+ hubssolib_flash_tag(:alert)
1013
+
1014
+ tags << hubssolib_standard_flash_tag(:notice) <<
1015
+ hubssolib_standard_flash_tag(:attention) <<
1016
+ hubssolib_standard_flash_tag(:alert)
1017
+
1018
+ # Now pick up anything else.
1019
+
1020
+ hubssolib_get_flash.each do |key, value|
1021
+ tags << hubssolib_flash_tag(key) if (value and !value.empty?)
1022
+ end
1023
+
1024
+ flash.each do |key, value|
1025
+ tags << hubssolib_standard_flash_tag(key) if (value and !value.empty?)
1026
+ end if defined?(flash)
1027
+
1028
+ return tags
1029
+ end
1030
+
1031
+ # Retrieve the message of an exception stored as an object in the given
1032
+ # string.
1033
+ #
1034
+ def hubssolib_get_exception_message(id_data)
1035
+ hubssolib_get_exception_data(CGI::unescape(id_data))
1036
+ end
1037
+
1038
+ # Inclusion hook to make various methods available as ActionView
1039
+ # helper methods.
1040
+ #
1041
+ def self.included(base)
1042
+ base.send :helper_method,
1043
+ :hubssolib_current_user,
1044
+ :hubssolib_unique_name,
1045
+ :hubssolib_logged_in?,
1046
+ :hubssolib_authorized?,
1047
+ :hubssolib_privileged?,
1048
+ :hubssolib_flash_tags
1049
+ rescue
1050
+ # We're not always included in controllers...
1051
+ nil
1052
+ end
1053
+
1054
+ private
1055
+
1056
+ # Indicate that the user must log in to complete their request.
1057
+ # Returns false to enable a before_filter to return through this
1058
+ # method while ensuring that the previous action processing is
1059
+ # halted (since the overall return value is therefore 'false').
1060
+ #
1061
+ def hubssolib_must_login
1062
+ # If HTTP, redirect to the same place, but HTTPS. Then we can store the
1063
+ # flash and return-to in the session data. We'll have the same set of
1064
+ # before-filter operations running and they'll find out we're either
1065
+ # authorised after all, or come back to this very function, which will
1066
+ # now be happily running from an HTTPS connection and will go on to set
1067
+ # the flash and redirect to the login page.
1068
+
1069
+ if hubssolib_ensure_https
1070
+ hubssolib_set_flash(:alert, 'You must log in before you can continue.')
1071
+ redirect_to HUB_PATH_PREFIX + '/account/login'
1072
+ end
1073
+
1074
+ return false
1075
+ end
1076
+
1077
+ # Indicate access is denied for a given logged in user's request.
1078
+ # Returns false to enable a before_filter to return through this
1079
+ # method while ensuring that the previous action processing is
1080
+ # halted (since the overall return value is therefore 'false').
1081
+ #
1082
+ def hubssolib_access_denied
1083
+ # See hubsso_must_login for the reason behind the following call.
1084
+
1085
+ if hubssolib_ensure_https
1086
+ hubssolib_set_flash(:alert, 'You do not have permission to carry out that action on this site.')
1087
+ redirect_to HUB_PATH_PREFIX + '/'
1088
+ end
1089
+
1090
+ return false
1091
+ end
1092
+
1093
+ # Check conditions for session expiry. Returns 'true' if session's
1094
+ # last_used date indicates expiry, else 'false'.
1095
+ #
1096
+ def hubssolib_session_expired?
1097
+
1098
+ # 23-Oct-2006 (ADH):
1099
+ #
1100
+ # An exception, which is also a security hole of sorts. POST requests
1101
+ # cannot be redirected because HTTP doesn't have that concept. If a user
1102
+ # is editing a Wiki page, say, then goes away, comes back later and now
1103
+ # finishes their edits, their session may have timed out. They submit
1104
+ # the page but it's by POST so their submission details are lost. If they
1105
+ # are lucky their browser might remember the form contents if they go
1106
+ # back but not all do and not all users would think of doing that.
1107
+ #
1108
+ # To work around this, don't enforce a timeout for POST requests. Should
1109
+ # a user on a public computer not log out, then a hacker arrive *after*
1110
+ # the session expiry time (if they arrive before it expires then the
1111
+ # except for POSTs is irrelevant), they could recover the session by
1112
+ # constructing a POST request. It's a convoluted path, requires a user to
1113
+ # have not logged out anyway, and the Hub isn't intended for Fort Knox.
1114
+ # At the time of writing the trade-off of usability vs security is
1115
+ # considered acceptable, though who knows, the view may change in future.
1116
+
1117
+ 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)
1125
+ end
1126
+
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
+
1187
+ # If we're not using SSL, forget it
1188
+ return nil unless request.ssl? || HUBSSOLIB_BYPASS_SSL
1189
+
1190
+ # If we've no cookie, we need a new session ID
1191
+ key = hubssolib_get_secure_cookie_data(HUBSSOLIB_COOKIE_NAME)
1192
+
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
1197
+
1198
+ return hubssolib_factory().get_session(key)
1199
+
1200
+ rescue Exception => e
1201
+
1202
+ # At this point there tends to be no Session data, so we're
1203
+ # going to have to encode the exception data into the URI...
1204
+ # It must be escaped twice, as many servers treat "%2F" in a
1205
+ # URI as a "/" and Apache may flat refuse to serve the page,
1206
+ # raising a 404 error unless "AllowEncodedSlashes on" is
1207
+ # specified in its configuration.
1208
+
1209
+ suffix = '/' + CGI::escape(CGI::escape(hubssolib_set_exception_data(e)))
1210
+ new_path = HUB_PATH_PREFIX + '/tasks/service'
1211
+ redirect_to new_path + suffix unless request.path.include?(new_path)
1212
+ return nil
1213
+ end
1214
+
1215
+ def hubssolib_set_session_data(session)
1216
+ # Nothing to do presently - DRb handles everything
1217
+ end
1218
+
1219
+ # Return an array of Hub User objects representing users based
1220
+ # on a list of known sessions returned by the DRb server. Note
1221
+ # that if an application exposes this method to a view, it is
1222
+ # up to the application to ensure sufficient access permission
1223
+ # protection for that view according to the webmaster's choice
1224
+ # of site security level. Generally, normal users should not
1225
+ # be allowed access.
1226
+ #
1227
+ def hubssolib_enumerate_users
1228
+ sessions = hubssolib_factory().enumerate_sessions()
1229
+ users = []
1230
+
1231
+ sessions.each do |key, value|
1232
+ user = value.session_user
1233
+ users.push(user) if (user && user.respond_to?(:user_id) && user.user_id)
1234
+ end
1235
+
1236
+ return users
1237
+
1238
+ rescue Exception => e
1239
+
1240
+ # At this point there tends to be no Session data, so we're
1241
+ # going to have to encode the exception data into the URI...
1242
+ # See earlier for double-escaping rationale.
1243
+
1244
+ suffix = '/' + CGI::escape(CGI::escape(hubssolib_set_exception_data(e)))
1245
+ new_path = HUB_PATH_PREFIX + '/tasks/service'
1246
+ redirect_to new_path + suffix unless request.path.include?(new_path)
1247
+ return nil
1248
+ end
1249
+
1250
+ # Encode exception data into a string suitable for using in a URL
1251
+ # if CGI escaped first. Pass the exception object; stores only the
1252
+ # message.
1253
+ #
1254
+ def hubssolib_set_exception_data(e)
1255
+ HubSsoLib::Crypto.encode_object(e.message, request.remote_ip)
1256
+ end
1257
+
1258
+ # Decode exception data encoded with hubssolib_set_exception_data.
1259
+ # Returns the originally stored message string or 'nil' if there
1260
+ # are any decoding problems. Pass the encoded data.
1261
+ #
1262
+ def hubssolib_get_exception_data(data)
1263
+ HubSsoLib::Crypto.decode_object(data, request.remote_ip)
1264
+ end
1265
+
1266
+ # Various accessors that ultimately run through the DRb server if
1267
+ # the session data is available, else return default values.
1268
+
1269
+ def hubssolib_get_last_used
1270
+ session = self.hubssolib_current_session
1271
+ session ? session.session_last_used : Time.now.utc
1272
+ end
1273
+
1274
+ def hubssolib_set_last_used(time)
1275
+ return unless self.hubssolib_current_session
1276
+ self.hubssolib_current_session.session_last_used = time
1277
+ end
1278
+
1279
+ def hubssolib_get_return_to
1280
+ session = self.hubssolib_current_session
1281
+ session ? session.session_return_to : nil
1282
+ end
1283
+
1284
+ def hubssolib_set_return_to(uri)
1285
+ return unless self.hubssolib_current_session
1286
+ self.hubssolib_current_session.session_return_to = uri
1287
+ end
1288
+
1289
+ end # Core module
1290
+ end # HubSsoLib module
1291
+
1292
+ #######################################################################
1293
+ # Classes: Standard class extensions for HubSsoLib Roles operations. #
1294
+ # (C) Hipposoft 2006 #
1295
+ # #
1296
+ # Purpose: Extensions to standard classes to support HubSsoLib. #
1297
+ # #
1298
+ # Author: A.D.Hodgkinson #
1299
+ # #
1300
+ # History: 20-Oct-2006 (ADH): Integrated into HubSsoLib. #
1301
+ #######################################################################
1302
+
1303
+ # Method to return a Roles object created from the
1304
+ # contents of the String the method is invoked upon. The
1305
+ # string may contain a single role or a comma-separated list
1306
+ # with no white space.
1307
+ #
1308
+ class String
1309
+ def to_authenticated_roles
1310
+ roles = HubSsoLib::Roles.new
1311
+ array = self.split(',')
1312
+
1313
+ roles.clear
1314
+ array.each { |role| roles.add(role) }
1315
+
1316
+ return roles
1317
+ end
1318
+ end # String class
1319
+
1320
+ # Method to return a Roles object created from the
1321
+ # contents of the Symbol the method is invoked upon.
1322
+ #
1323
+ class Symbol
1324
+ def to_authenticated_roles
1325
+ return self.to_s.to_authenticated_roles
1326
+ end
1327
+ end # Symbol class
1328
+
1329
+ # Method to return a Roles object created from the
1330
+ # contents of the Array the method is invoked upon. The array
1331
+ # contents will be flattened. After that, each entry must be
1332
+ # a single role symbol or string equivalent. Comma-separated
1333
+ # lists are not currently allowed (improvements to the roles
1334
+ # class could easily give this, but the bloat isn't needed).
1335
+ #
1336
+ class Array
1337
+ def to_authenticated_roles
1338
+ roles = HubSsoLib::Roles.new
1339
+ roles.clear
1340
+
1341
+ self.flatten.each { |entry| roles.add(entry.to_s) }
1342
+
1343
+ return roles
1344
+ end
1345
+ end # Array class