Changesets can be listed by changeset number.
The Git repository is here.
- Revision:
- 330
- Log:
Updated Hub gem to version 0.2.7.
- Author:
- rool
- Date:
- Sat Mar 19 17:04:30 +0000 2011
- Size:
- 50028 Bytes
1 | ####################################################################### |
2 | # Module: HubSsoLib # |
3 | # (C) Hipposoft 2006 # |
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 | ####################################################################### |
16 | |
17 | module HubSsoLib |
18 | |
19 | require 'drb' |
20 | |
21 | # DRb connection |
22 | HUBSSOLIB_DRB_URI = ENV['HUB_CONNECTION_URI'] |
23 | |
24 | # Location of Hub application root. |
25 | HUB_PATH_PREFIX = ENV['HUB_PATH_PREFIX'] |
26 | |
27 | # Time limit, *in seconds*, for the account inactivity timeout. |
28 | # If a user performs no Hub actions during this time they will |
29 | # be automatically logged out upon their next action. |
30 | HUBSSOLIB_IDLE_TIME_LIMIT = 60 * 60 |
31 | |
32 | # Random file location. |
33 | HUBSSOLIB_RND_FILE_PATH = ENV['HUB_RANDOM_FILE'] |
34 | |
35 | # Shared cookie name and path. |
36 | HUBSSOLIB_COOKIE_NAME = 'hubapp_shared_id' |
37 | HUBSSOLIB_COOKIE_PATH = ENV['HUB_COOKIE_PATH'] |
38 | |
39 | # Cache the random data. Assuming FCGI or similar, this code gets |
40 | # executed only once per FCGI instance initialisation rather than |
41 | # once per request. |
42 | |
43 | attr_reader :HUBSSOLIB_RANDOM_DATA, :HUBSSOLIB_RANDOM_DATA_SIZE |
44 | |
45 | HUBSSOLIB_RANDOM_DATA_SIZE = File.size(HUBSSOLIB_RND_FILE_PATH) |
46 | HUBSSOLIB_RANDOM_DATA_SIZE = 16384 if (HUBSSOLIB_RANDOM_DATA_SIZE > 16384) |
47 | |
48 | if HUBSSOLIB_RANDOM_DATA_SIZE < 1024 |
49 | raise "HubSsoLib needs at least 1024 bytes of random data - file '#{rnd_file}' is too small" |
50 | else |
51 | HUBSSOLIB_RANDOM_DATA = File.read(HUBSSOLIB_RND_FILE_PATH) |
52 | end |
53 | |
54 | ####################################################################### |
55 | # Class: Crypto # |
56 | # (C) Hipposoft 2006 # |
57 | # # |
58 | # Purpose: Encryption and decryption utilities. # |
59 | # # |
60 | # Author: A.D.Hodgkinson # |
61 | # # |
62 | # History: 28-Aug-2006 (ADH): First version. # |
63 | # 20-Oct-2006 (ADH): Integrated into HubSsoLib, renamed to # |
64 | # 'Crypto' from 'HubSsoCrypto'. # |
65 | ####################################################################### |
66 | |
67 | # Encryption and decryption utility object. Once instantiated, a |
68 | # HubSsoLib::Crypto object is used to encrypt and decrypt data with the |
69 | # AES-256-CBC cipher. A single passphrase is used for both operations. |
70 | # A SHA-256 hash of that passphrase is used as the encryption key. |
71 | # |
72 | # CBC operation requires an initialization vector for the first block of |
73 | # data during encryption and decryption. A block of random data is used |
74 | # for this in conjunction with the passphrase used to generate the key. By |
75 | # so doing, the initialization vector is not revealed to third parties, |
76 | # even though the source code of the object is available. The weakness is |
77 | # that for a given passphrase and random data pool the same initialization |
78 | # vector will always be generated - indeed, this is relied upon, to allow |
79 | # callers themselves to only have to remember the passphrase. See private |
80 | # method obtain_iv() for more details. |
81 | # |
82 | # The block of random data is obtained from the DRb server. It is usually |
83 | # a RAM-cached near-random file. The important behaviour is that the |
84 | # contents are not know to the outside world and the contents, while they |
85 | # may change at any point, don't change during the duration of a user |
86 | # log-in session (at least, if it changes, all current sessions will be |
87 | # harmlessly invalidated). |
88 | # |
89 | class Crypto |
90 | |
91 | require 'openssl' |
92 | require 'digest/sha2' |
93 | require 'digest/md5' |
94 | |
95 | # # Initialize the HubSsoLib::Crypto object. |
96 | # # |
97 | # def initialize() |
98 | # DRb.start_service() |
99 | # |
100 | # factory = DRbObject.new_with_uri(HUBSSOLIB_DRB_URI) |
101 | # @rnd_data = factory.random_data() |
102 | # @rnd_size = factory.random_data_size() |
103 | # |
104 | # DRb.stop_service() |
105 | # end |
106 | |
107 | # Generate a series of pseudo-random bytes of the given length. |
108 | # |
109 | def self.random_data(size) |
110 | data = '' |
111 | size.times { data << rand(256) } |
112 | data |
113 | end |
114 | |
115 | def random_data(size) |
116 | HubSsoLib::Crypto.random_data(size) |
117 | end |
118 | |
119 | # Encode some given data in base-64 format with no line breaks. |
120 | # |
121 | def self.pack64(data) |
122 | [data].pack('m1000000') # Stupid long number to avoid "\n" in the output |
123 | end |
124 | |
125 | def pack64(data) |
126 | HubSsoLib::Crypto.pack64(data) |
127 | end |
128 | |
129 | # Decode some given data from base-64 format with no line breaks. |
130 | # |
131 | def self.unpack64(data) |
132 | data.unpack('m').first |
133 | end |
134 | |
135 | def unpack64(data) |
136 | HubSsoLib::Crypto.unpack64(data) |
137 | end |
138 | |
139 | # Encrypt the given data with the AES-256-CBC algorithm using the |
140 | # given passphrase. Returns the encrypted result in a string. |
141 | # Distantly based upon: |
142 | # |
143 | # http://www.bigbold.com/snippets/posts/show/576 |
144 | # |
145 | # In the context of Hub, the passphrase tends to be fixed per IP |
146 | # address (albeit unknown to the public) and the IV is derived from |
147 | # it. This means the same data will encode to the same result. With |
148 | # the source data having some parts which are invariant, security |
149 | # is compromised. To avoid this, data is prefixed by a quantity of |
150 | # random bytes, effectively supplementing the IV and ensuring that |
151 | # different size and content data is generated each time. |
152 | # |
153 | def encrypt(data, passphrase) |
154 | cipher = OpenSSL::Cipher::Cipher.new("aes-256-cbc") |
155 | cipher.encrypt |
156 | |
157 | cipher.key = Digest::SHA256.digest(passphrase) |
158 | cipher.iv = obtain_iv(passphrase) |
159 | |
160 | rsize = rand(32) |
161 | data = '' << rsize << random_data(rsize) << data |
162 | |
163 | encrypted = cipher.update(data) |
164 | encrypted << cipher.final |
165 | |
166 | return encrypted |
167 | end |
168 | |
169 | # Decrypt the given data with the AES-256-CBC algorithm using the |
170 | # given passphrase. Returns 'nil' if there is any kind of error in |
171 | # the decryption process. Distantly based upon: |
172 | # |
173 | # http://www.bigbold.com/snippets/posts/show/576 |
174 | # |
175 | def decrypt(data, passphrase) |
176 | cipher = OpenSSL::Cipher::Cipher.new("aes-256-cbc") |
177 | cipher.decrypt |
178 | |
179 | cipher.key = Digest::SHA256.digest(passphrase) |
180 | cipher.iv = obtain_iv(passphrase) |
181 | |
182 | decrypted = cipher.update(data) |
183 | decrypted << cipher.final |
184 | |
185 | rsize = decrypted[0] |
186 | return decrypted[rsize + 1..-1] |
187 | rescue |
188 | return nil |
189 | end |
190 | |
191 | # Encrypt and base-64 encode the given data with the given passphrase. |
192 | # Returns the encoded result. |
193 | # |
194 | def encode(data, passphrase) |
195 | pack64(encrypt(data, passphrase)) |
196 | end |
197 | |
198 | # Decrypt and base-64 decode the given data with the given passphrase. |
199 | # Returns the decoded result or 'nil' on error. |
200 | # |
201 | def decode(data, passphrase) |
202 | decrypt(unpack64(data), passphrase) |
203 | rescue |
204 | return nil |
205 | end |
206 | |
207 | # Class method that takes an object and passphrase and encrypts |
208 | # the result. The passphrase is scrambled internally using data |
209 | # not available to the public, the object serialised (so it must |
210 | # support serialisation), encrypted and base-64 encoded, and the |
211 | # 7-bit safe string result returned. On failure, exceptions will |
212 | # be raised (failure is not expected). |
213 | # |
214 | def self.encode_object(object, passphrase) |
215 | crypto = HubSsoLib::Crypto.new |
216 | passphrase = crypto.scramble_passphrase(passphrase) |
217 | |
218 | return crypto.encode(Marshal.dump(object), passphrase) |
219 | end |
220 | |
221 | def encode_object(object, passphrase) |
222 | HubSsoLib::Crypto.encode_object(object, passphrase) |
223 | end |
224 | |
225 | # Class method that takes output from Crypto.encode_object and |
226 | # decodes it, returning an object reference. Since failure may |
227 | # result from invalid data input and this can be a common case, |
228 | # rather than raise an exception as with Crypto.encode_object, |
229 | # this method returns 'nil' should there be any decode problems. |
230 | # |
231 | def self.decode_object(data, passphrase) |
232 | crypto = HubSsoLib::Crypto.new |
233 | passphrase = crypto.scramble_passphrase(passphrase) |
234 | object = nil |
235 | |
236 | if (data && !data.empty?) |
237 | object = Marshal.load(crypto.decode(data, passphrase)) |
238 | end |
239 | |
240 | return object |
241 | rescue |
242 | return nil |
243 | end |
244 | |
245 | def decode_object(data, passphrase) |
246 | HubSsoLib::Crypto.decode_object(data, passphrase) |
247 | end |
248 | |
249 | # "Scramble" a passphrase. Cookie data encryption is done purely so that |
250 | # some hypothetical malicious user cannot easily examine or modify the |
251 | # cookie contents for some nefarious purpose. Encryption is done at the |
252 | # head end. We need to be able to decrypt in the absence of any other |
253 | # information. A fixed passphrase thus needs to be used, but it cannot be |
254 | # included in the source code or anyone can read the cookie contents! To |
255 | # work around this, transform the passphrase into 32 bytes of data from |
256 | # the random pool if asked. The random pool is not known to the outside |
257 | # world so security is improved (albeit far from perfect, but this is all |
258 | # part of little more than an anti-spam measure - not Fort Knox!). |
259 | # |
260 | def scramble_passphrase(passphrase) |
261 | |
262 | # Generate a 16-byte hash of the passphrase using the MD5 algorithm. Get |
263 | # this as a string of hex digits and convert that into an integer. Strip |
264 | # off the top bits (since we've no more reason to believe that the top |
265 | # bits contain more randomly varying data than the bottom bits) so that |
266 | # the number is bound to between zero and the random pool size, minus |
267 | # 33, thus providing an offset into the file from which we can safely |
268 | # read 32 bytes of data. |
269 | |
270 | offset = Digest::MD5.hexdigest(passphrase).hex % (HubSsoLib::HUBSSOLIB_RANDOM_DATA_SIZE - 32) |
271 | |
272 | # Return 32 bytes of data from the random pool at the calculated offset. |
273 | |
274 | return HubSsoLib::HUBSSOLIB_RANDOM_DATA[offset..offset + 31] |
275 | end |
276 | |
277 | private |
278 | |
279 | # Obtain an initialization vector (IV) of 32 bytes (256 bits) length based |
280 | # on external data loaded when the object was created. Since the data |
281 | # content is unknown, the IV is unknown. This is important; see: |
282 | # |
283 | # http://www.ciphersbyritter.com/GLOSSARY.HTM#CipherBlockChaining |
284 | # |
285 | # Weakness: An offset into the supplied data is generated from the given |
286 | # passphrase. Since the data is cached internally, the same IV will be |
287 | # produced for any given passphrase (this is as much a feature as it is a |
288 | # weakness, since the encryption and decryption routines rely on it). |
289 | # |
290 | # The passphrase scrambler is used to do the back-end work. Since the |
291 | # caller may have already scrambled the passphrase once, scrambled data is |
292 | # used as input; we end up scrambling it twice. This is a desired result - |
293 | # we don't want the IV being the data that's actually also used for the |
294 | # encryption passphrase. |
295 | # |
296 | def obtain_iv(passphrase) |
297 | return scramble_passphrase(passphrase) |
298 | end |
299 | |
300 | end # Crypto class |
301 | |
302 | ####################################################################### |
303 | # Class: Roles # |
304 | # (C) Hipposoft 2006 # |
305 | # # |
306 | # Purpose: Shared methods for handling user account roles. # |
307 | # # |
308 | # Author: A.D.Hodgkinson # |
309 | # # |
310 | # History: 17-Oct-2006 (ADH): Adapted from Clubhouse. # |
311 | # 20-Oct-2006 (ADH): Integrated into HubSsoLib. # |
312 | ####################################################################### |
313 | |
314 | class Roles |
315 | |
316 | # Association of symbolic role names to display names, in no |
317 | # particular order. |
318 | # |
319 | ROLES = { |
320 | :admin => 'Administrator', |
321 | :webmaster => 'Webmaster', |
322 | :privileged => 'Advanced user', |
323 | :normal => 'Normal user' |
324 | } |
325 | |
326 | ADMIN = :admin |
327 | NORMAL = :normal |
328 | |
329 | # Return the display name of a given role symbol. Class method. |
330 | # |
331 | def self.get_display_name(symbol) |
332 | ROLES[symbol] |
333 | end |
334 | |
335 | # Return all display names in an array. Class method. |
336 | |
337 | def self.get_display_names |
338 | ROLES.values |
339 | end |
340 | |
341 | # Return an array of known role symbols. They can be used with |
342 | # methods like get_display_name. Class method. |
343 | |
344 | def self.get_role_symbols |
345 | ROLES.keys |
346 | end |
347 | |
348 | # Initialize a new Roles object. Pass 'true' if this is for |
349 | # an admin user account, else 'false'. Default is 'false'. Note |
350 | # that further down in this file, the String, Symbol and Array |
351 | # classes are extended with to_authenticated_roles methods, which |
352 | # provide other ways of creating Roles objects. |
353 | # |
354 | def initialize(admin = false) |
355 | if (admin) |
356 | @role_array = [ ADMIN ] |
357 | else |
358 | @role_array = [ NORMAL ] |
359 | end |
360 | end |
361 | |
362 | # Adds a role, supplied as a string or symbol, to the internal list. |
363 | # A non-nil return indicates that the role was already present. |
364 | # |
365 | def add(role) |
366 | @role_array.push(role.to_s.intern).uniq! |
367 | end |
368 | |
369 | # Deletes a role, supplied as a string or symbol, from the internal |
370 | # list. A nil return indicates that the role was not in the list. |
371 | # |
372 | def delete(role) |
373 | @role_array.delete(role.to_s.intern) |
374 | end |
375 | |
376 | # Delete all roles from the internal list. |
377 | # |
378 | def clear |
379 | @role_array.clear |
380 | end |
381 | |
382 | # Return a copy of the internal roles list as a string. |
383 | # |
384 | def to_s |
385 | return @role_array.join(',') |
386 | end |
387 | |
388 | # Return a copy of the internal roles list as an array. |
389 | # |
390 | def to_a |
391 | return @role_array.dup |
392 | end |
393 | |
394 | # Return a copy of the intenal roles list as a human readable string. |
395 | # |
396 | def to_human_s |
397 | human_names = [] |
398 | |
399 | @role_array.each do |role| |
400 | human_names.push(HubSsoLib::Roles.get_display_name(role)) |
401 | end |
402 | |
403 | if (human_names.length == 0) |
404 | return '' |
405 | elsif (human_names.length == 1) |
406 | return human_names[0] |
407 | else |
408 | return human_names[0..-2].join(', ') + ' and ' + human_names.last |
409 | end |
410 | end |
411 | |
412 | # Do nothing - this is just useful for polymorphic code, where a function |
413 | # can take a String, Array, Symbol or Roles object and make the |
414 | # same method call to return a Roles object in return. |
415 | # |
416 | def to_authenticated_roles |
417 | return self |
418 | end |
419 | |
420 | # Does the internal list of roles include the supplied role or roles? |
421 | # The roles can be given as an array of individual role symbols or |
422 | # equivalent strings, or as a single symbol or single equivalent |
423 | # symbol, or as a string containing equivalents of role symbols in a |
424 | # comma-separated list (no white space or other spurious characters). |
425 | # Returns 'true' if the internal list of roles includes at least one |
426 | # of the supplied roles, else 'false'. |
427 | # |
428 | def include?(roles) |
429 | return false if roles.nil? |
430 | |
431 | # Ensure we've an array of roles, one way or another |
432 | roles = roles.to_s if roles.class == Symbol |
433 | roles = roles.split(',') if roles.class == String |
434 | |
435 | roles.each do |role| |
436 | return true if @role_array.include?(role.to_s.intern) |
437 | end |
438 | |
439 | return false |
440 | end |
441 | |
442 | # Synonym for 'include?'. |
443 | # |
444 | alias includes? include? |
445 | |
446 | # Validate the list of roles. Validation means ensuring that all |
447 | # roles in this object are found in the internal ROLES hash. Returns |
448 | # true if the roles validate or false if unknown roles are found. |
449 | # |
450 | def validate |
451 | return false if @role_array.empty? |
452 | |
453 | @role_array.each do |role| |
454 | return false unless ROLES[role] |
455 | end |
456 | |
457 | return true |
458 | end |
459 | |
460 | end # Roles class |
461 | |
462 | ####################################################################### |
463 | # Class: Permissions # |
464 | # (C) Hipposoft 2006 # |
465 | # # |
466 | # Purpose: Methods to help, in conjunction with Roles, determine the # |
467 | # access permissions a particular user is granted. # |
468 | # # |
469 | # Author: A.D.Hodgkinson # |
470 | # # |
471 | # History: 17-Oct-2006 (ADH): Adapted from Clubhouse. # |
472 | # 20-Oct-2006 (ADH): Integrated into HubSsoLib. # |
473 | ####################################################################### |
474 | |
475 | class Permissions |
476 | |
477 | # Initialize a permissions object. The map is a hash which maps action |
478 | # names, expressed as symbols, to roles, expressed as individual symbols, |
479 | # equivalent strings, or arrays of multiple strings or symbols. Use 'nil' |
480 | # to indicate permission for the general public - no login required - or |
481 | # simply omit the action (unlisted actions are permitted). |
482 | # |
483 | # Example mapping for a generic controller: |
484 | # |
485 | # { |
486 | # :new => [ :admin, :webmaster, :privileged, :normal ], |
487 | # :create => [ :admin, :webmaster, :privileged, :normal ], |
488 | # :edit => [ :admin, :webmaster, :privileged, :normal ], |
489 | # :update => [ :admin, :webmaster, :privileged, :normal ], |
490 | # :delete => [ :admin, :webmaster, :privileged ], |
491 | # :list => nil, |
492 | # :show => nil |
493 | # } |
494 | # |
495 | def initialize(pmap) |
496 | @permissions = pmap |
497 | end |
498 | |
499 | # Does the given Roles object grant permission for the given action, |
500 | # expressed as a string or symbol? Returns 'true' if so, else 'false'. |
501 | # |
502 | # If a role is given as some other type, an attempt is made to convert |
503 | # it to a Roles object internally (so you could pass a role symbol, |
504 | # string, array of symbols or strings, or comma-separated string). |
505 | # |
506 | # Passing an empty roles string will tell you whether or not the |
507 | # action requires login. Only actions not in the permissions list or |
508 | # those with a 'nil' list of roles will generate a result 'true', |
509 | # since any other actions will require your empty roles string to |
510 | # include at least one role (which it obviously doesn't). |
511 | # |
512 | def permitted?(roles, action) |
513 | action = action.to_s.intern |
514 | roles = roles.to_authenticated_roles |
515 | |
516 | return true unless @permissions.include?(action) |
517 | return true if @permissions[action].nil? |
518 | return roles.include?(@permissions[action]) |
519 | end |
520 | end # Permissions class |
521 | |
522 | ####################################################################### |
523 | # Class: User # |
524 | # (C) Hipposoft 2006 # |
525 | # # |
526 | # Purpose: A representation of the Hub application's User Model in # |
527 | # terms of a simple set of properties, so that applications # |
528 | # don't need User access to understand user attributes. # |
529 | # # |
530 | # Author: A.D.Hodgkinson # |
531 | # # |
532 | # History: 21-Oct-2006 (ADH): Created. # |
533 | ####################################################################### |
534 | |
535 | class User |
536 | |
537 | # This *must not* be 'undumped', since it gets passed from clients |
538 | # back to the persistent DRb server process. A client thread may |
539 | # disappear and be recreated by the web server at any time; if the |
540 | # user object is undumpable, then the DRb server has to *call back |
541 | # to the client* (in DRb, clients are also servers...!) to find out |
542 | # about the object. Trouble is, if the client thread has been |
543 | # recreated, the server will be trying to access to stale objects |
544 | # that only exist if the garbage collector hasn't got to them yet. |
545 | |
546 | attr_accessor :user_salt |
547 | attr_accessor :user_roles |
548 | attr_accessor :user_updated_at |
549 | attr_accessor :user_activated_at |
550 | attr_accessor :user_real_name |
551 | attr_accessor :user_crypted_password |
552 | attr_accessor :user_remember_token_expires_at |
553 | attr_accessor :user_activation_code |
554 | attr_accessor :user_member_id |
555 | attr_accessor :user_id |
556 | attr_accessor :user_password_reset_code |
557 | attr_accessor :user_remember_token |
558 | attr_accessor :user_email |
559 | attr_accessor :user_created_at |
560 | attr_accessor :user_password_reset_code_expires_at |
561 | |
562 | def initialize |
563 | @user_salt = nil |
564 | @user_roles = nil |
565 | @user_updated_at = nil |
566 | @user_activated_at = nil |
567 | @user_real_name = nil |
568 | @user_crypted_password = nil |
569 | @user_remember_token_expires_at = nil |
570 | @user_activation_code = nil |
571 | @user_member_id = nil |
572 | @user_id = nil |
573 | @user_password_reset_code = nil |
574 | @user_remember_token = nil |
575 | @user_email = nil |
576 | @user_created_at = nil |
577 | @user_password_reset_code_expires_at = nil |
578 | end |
579 | end # User class |
580 | |
581 | ####################################################################### |
582 | # Class: Session # |
583 | # (C) Hipposoft 2006 # |
584 | # # |
585 | # Purpose: Session support object, used to store session metadata in # |
586 | # an insecure cross-application cookie. # |
587 | # # |
588 | # Author: A.D.Hodgkinson # |
589 | # # |
590 | # History: 22-Oct-2006 (ADH): Created. # |
591 | ####################################################################### |
592 | |
593 | class Session |
594 | |
595 | # Unlike a User, this *is* undumpable since it only gets passed from |
596 | # server to client. The server's always here to service requests |
597 | # from the client and used sessions are never garbage collected |
598 | # since the DRb server's front object, a SessionFactory, keeps them |
599 | # in a hash held within an instance variable. |
600 | |
601 | include DRb::DRbUndumped |
602 | |
603 | attr_accessor :session_last_used |
604 | attr_accessor :session_return_to |
605 | attr_accessor :session_flash |
606 | attr_accessor :session_user |
607 | |
608 | def initialize |
609 | @session_last_used = Time.now.utc |
610 | @session_return_to = nil |
611 | @session_flash = {} |
612 | @session_user = HubSsoLib::User.new |
613 | end |
614 | end # Session class |
615 | |
616 | ####################################################################### |
617 | # Class: SessionFactory # |
618 | # (C) Hipposoft 2006 # |
619 | # # |
620 | # Purpose: Build Session objects for DRb server clients. Maintains a # |
621 | # hash of Session objects. # |
622 | # # |
623 | # Author: A.D.Hodgkinson # |
624 | # # |
625 | # History: 26-Oct-2006 (ADH): Created. # |
626 | ####################################################################### |
627 | |
628 | class SessionFactory |
629 | def initialize |
630 | @sessions = {} |
631 | end |
632 | |
633 | def get_session(key) |
634 | unless (@sessions.has_key? key) |
635 | @sessions[key] = HubSsoLib::Session.new |
636 | end |
637 | |
638 | return @sessions[key] |
639 | end |
640 | |
641 | def enumerate_sessions |
642 | @sessions |
643 | end |
644 | end |
645 | |
646 | ####################################################################### |
647 | # Module: Server # |
648 | # (C) Hipposoft 2006 # |
649 | # # |
650 | # Purpose: DRb server to provide shared data across applications. # |
651 | # Thanks to RubyPanther, rubyonrails IRC, for suggesting # |
652 | # this after a cookie-based scheme failed. # |
653 | # # |
654 | # Include the module then call hubssolib_launch_server. The # |
655 | # call will not return as the server runs indefinitely. # |
656 | # # |
657 | # Author: A.D.Hodgkinson # |
658 | # # |
659 | # History: 26-Oct-2006 (ADH): Created. # |
660 | ####################################################################### |
661 | |
662 | module Server |
663 | def hubssolib_launch_server |
664 | @@session_factory = HubSsoLib::SessionFactory.new |
665 | DRb.start_service(HUBSSOLIB_DRB_URI, @@session_factory) |
666 | DRb.thread.join |
667 | end |
668 | end # Server module |
669 | |
670 | ####################################################################### |
671 | # Module: Core # |
672 | # Various authors # |
673 | # # |
674 | # Purpose: The barely recognisable core of acts_as_authenticated's # |
675 | # AuthenticatedSystem module, modified to work with the # |
676 | # other parts of HubSsoLib. You should include this module # |
677 | # to use its facilities. # |
678 | # # |
679 | # Author: Various; adaptation by A.D.Hodgkinson # |
680 | # # |
681 | # History: 20-Oct-2006 (ADH): Integrated into HubSsoLib. # |
682 | ####################################################################### |
683 | |
684 | module Core |
685 | |
686 | # Returns true or false if the user is logged in. |
687 | # |
688 | # Preloads @hubssolib_current_user with user data if logged in. |
689 | # |
690 | def hubssolib_logged_in? |
691 | !!self.hubssolib_current_user |
692 | end |
693 | |
694 | # Check if the user is authorized to perform the current action. If calling |
695 | # from a helper, pass the action name and class name; otherwise by default, |
696 | # the current action name and 'self.class' will be used. |
697 | # |
698 | # Override this method in your controllers if you want to restrict access |
699 | # to a different set of actions. Presently, the current user's roles are |
700 | # compared against the caller's permissions hash and the action name. |
701 | # |
702 | def hubssolib_authorized?(action = action_name, classname = self.class) |
703 | |
704 | # Classes with no permissions object always authorise everything. |
705 | # Otherwise, ask the permissions object for its opinion. |
706 | |
707 | if (classname.respond_to? :hubssolib_permissions) |
708 | return classname.hubssolib_permissions.permitted?(hubssolib_get_user_roles, action) |
709 | else |
710 | return true |
711 | end |
712 | end |
713 | |
714 | # Is the current user privileged? Anything other than normal user |
715 | # privileges will suffice. Can be called if not logged in. Returns |
716 | # 'false' for logged out or normal user privileges only, else 'true'. |
717 | # |
718 | def hubssolib_privileged? |
719 | return false unless hubssolib_logged_in? |
720 | |
721 | pnormal = HubSsoLib::Roles.new(false).to_s |
722 | puser = hubssolib_get_user_roles.to_s |
723 | |
724 | return (puser && !puser.empty? && puser != pnormal) |
725 | end |
726 | |
727 | # Log out the user. Very few applications should ever need to call this, |
728 | # though Hub certainly does and it gets used internally too. |
729 | # |
730 | def hubssolib_log_out |
731 | # Causes the "hubssolib_current_[foo]=" methods to run, which |
732 | # deal with everything else. |
733 | self.hubssolib_current_user = nil |
734 | self.hubssolib_current_session = nil |
735 | end |
736 | |
737 | # Accesses the current user, via the DRb server if necessary |
738 | # |
739 | def hubssolib_current_user |
740 | hubssolib_get_user_data |
741 | end |
742 | |
743 | # Store the given user data in the cookie |
744 | # |
745 | def hubssolib_current_user=(new_user) |
746 | hubssolib_set_user_data(new_user) |
747 | end |
748 | |
749 | # Accesses the current session from the cookie. Creates a new |
750 | # session object if need be. |
751 | # |
752 | def hubssolib_current_session |
753 | @hubssolib_current_session ||= hubssolib_get_session_data |
754 | end |
755 | |
756 | # Store the given session data. |
757 | # |
758 | def hubssolib_current_session=(new_session) |
759 | @hubssolib_current_session = new_session |
760 | end |
761 | |
762 | # Public read-only accessor methods for common user activities: |
763 | # return the current user's roles as a Roles object, or nil if |
764 | # there's no user. |
765 | # |
766 | def hubssolib_get_user_roles |
767 | user = self.hubssolib_current_user |
768 | user ? user.user_roles.to_authenticated_roles : nil |
769 | end |
770 | |
771 | # Public read-only accessor methods for common user activities: |
772 | # return the current user's name as a string, or nil if there's |
773 | # no user. See also hubssolib_unique_name. |
774 | # |
775 | def hubssolib_get_user_name |
776 | user = self.hubssolib_current_user |
777 | user ? user.user_real_name : nil |
778 | end |
779 | |
780 | # Public read-only accessor methods for common user activities: |
781 | # return the Hub database ID of the current user account, or |
782 | # nil if there's no user. See also hubssolib_unique_name. |
783 | # |
784 | def hubssolib_get_user_id |
785 | user = self.hubssolib_current_user |
786 | user ? user.user_id : nil |
787 | end |
788 | |
789 | # Public read-only accessor methods for common user activities: |
790 | # return the current user's e-mail address, or nil if there's |
791 | # no user. |
792 | # |
793 | def hubssolib_get_user_address |
794 | user = self.hubssolib_current_user |
795 | user ? user.user_email : nil |
796 | end |
797 | |
798 | # Return a human-readable unique ID for a user. We don't want to |
799 | # have e-mail addresses all over the place, but don't want to rely |
800 | # on real names as unique - they aren't. Instead, produce a |
801 | # composite of the user's account database ID (which must be |
802 | # unique by definition) and their real name. See also |
803 | # hubssolib_get_name. |
804 | # |
805 | def hubssolib_unique_name |
806 | user = hubssolib_current_user |
807 | user ? "#{user.user_real_name} (#{user.user_id})" : 'Anonymous' |
808 | end |
809 | |
810 | # Main filter method to implement HubSsoLib permissions management, |
811 | # session expiry and so-on. Call from controllers only, always as a |
812 | # before_fitler. |
813 | # |
814 | def hubssolib_beforehand |
815 | |
816 | # Does this action require a logged in user? |
817 | |
818 | if (self.class.respond_to? :hubssolib_permissions) |
819 | login_is_required = !self.class.hubssolib_permissions.permitted?('', action_name) |
820 | else |
821 | login_is_required = false |
822 | end |
823 | |
824 | # If we require login but we're logged out, redirect to Hub login. |
825 | |
826 | logged_in = hubssolib_logged_in? |
827 | |
828 | if (login_is_required and logged_in == false) |
829 | hubssolib_store_location |
830 | return hubssolib_must_login |
831 | end |
832 | |
833 | # If we reach here the user is either logged, or the method does |
834 | # not require them to be. In the latter case, if we're not logged |
835 | # in there is no more work to do - exit early. |
836 | |
837 | return true unless logged_in # true -> let action processing continue |
838 | |
839 | # So we reach here knowing we're logged in, but the action may or |
840 | # may not require authorisation. |
841 | |
842 | unless (login_is_required) |
843 | |
844 | # We have to update session expiry even for actions that don't |
845 | # need us to be logged in, since we *are* logged in and need to |
846 | # maintain that state. If, though, the session expires, we just |
847 | # quietly log out and let action processing carry on. |
848 | |
849 | if (hubssolib_session_expired?) |
850 | hubssolib_log_out |
851 | hubssolib_set_flash(:attention, 'Your session timed out, so you are no longer logged in.') |
852 | else |
853 | hubssolib_set_last_used(Time.now.utc) |
854 | end |
855 | |
856 | return true # true -> let action processing continue |
857 | |
858 | else |
859 | |
860 | # Login *is* required for this action. If the session expires, |
861 | # redirect to Hub's login page via its expiry action. Otherwise |
862 | # check authorisation and allow action processing to continue |
863 | # if OK, else indicate that access is denied. |
864 | |
865 | if (hubssolib_session_expired?) |
866 | hubssolib_store_location |
867 | hubssolib_log_out |
868 | hubssolib_set_flash(:attention, 'Sorry, your session timed out; you need to log in again to continue.') |
869 | |
870 | # We mean this: redirect_to :controller => 'account', :action => 'login' |
871 | # ...except for the Hub, rather than the current application (whatever |
872 | # it may be). |
873 | redirect_to HUB_PATH_PREFIX + '/account/login' |
874 | else |
875 | hubssolib_set_last_used(Time.now.utc) |
876 | return hubssolib_authorized? ? true : hubssolib_access_denied |
877 | end |
878 | |
879 | end |
880 | end |
881 | |
882 | # Main after_filter method to tidy up after running state changes. |
883 | # |
884 | def hubssolib_afterwards |
885 | begin |
886 | DRb.current_server |
887 | DRb.stop_service() |
888 | rescue DRb::DRbServerNotFound |
889 | # Nothing to do; no service is running. |
890 | end |
891 | end |
892 | |
893 | # Store the URI of the current request in the session, or store the |
894 | # optional supplied specific URI. |
895 | # |
896 | # We can return to this location by calling #redirect_back_or_default. |
897 | # |
898 | def hubssolib_store_location(uri_str = request.request_uri) |
899 | |
900 | if (uri_str && !uri_str.empty?) |
901 | uri_str = hubssolib_promote_uri_to_ssl(uri_str, request.host) unless request.ssl? |
902 | hubssolib_set_return_to(uri_str) |
903 | else |
904 | hubssolib_set_return_to(nil) |
905 | end |
906 | |
907 | end |
908 | |
909 | # Redirect to the URI stored by the most recent store_location call or |
910 | # to the passed default. |
911 | def hubssolib_redirect_back_or_default(default) |
912 | url = hubssolib_get_return_to |
913 | hubssolib_set_return_to(nil) |
914 | |
915 | redirect_to(url || default) |
916 | end |
917 | |
918 | # Take a URI and pass an optional host parameter. Decomposes the URI, |
919 | # sets the host you provide (or leaves it alone if you omit the |
920 | # parameter), then forces the scheme to 'https'. Returns the result |
921 | # as a flat string. |
922 | |
923 | def hubssolib_promote_uri_to_ssl(uri_str, host = nil) |
924 | uri = URI.parse(uri_str) |
925 | uri.host = host if host |
926 | uri.scheme = 'https' |
927 | return uri.to_s |
928 | end |
929 | |
930 | # Ensure the current request is carried out over HTTPS by redirecting |
931 | # back to the current URL with the HTTPS protocol if it isn't. Returns |
932 | # 'true' if not redirected (already HTTPS), else 'false'. |
933 | # |
934 | def hubssolib_ensure_https |
935 | unless request.ssl? |
936 | # This isn't reliable: redirect_to({ :protocol => 'https://' }) |
937 | redirect_to (hubssolib_promote_uri_to_ssl(request.request_uri, request.host)) |
938 | return false |
939 | else |
940 | return true |
941 | end |
942 | end |
943 | |
944 | # Public methods to set some data that would normally go in @session, |
945 | # but can't because it needs to be accessed across applications. It is |
946 | # put in an insecure support cookie instead. There are some related |
947 | # private methods for things like session expiry too. |
948 | # |
949 | def hubssolib_get_flash() |
950 | f = self.hubssolib_current_session ? self.hubssolib_current_session.session_flash : nil |
951 | return f || {} |
952 | end |
953 | |
954 | def hubssolib_set_flash(symbol, message) |
955 | return unless self.hubssolib_current_session |
956 | f = hubssolib_get_flash |
957 | f[symbol] = message |
958 | self.hubssolib_current_session.session_flash = f |
959 | end |
960 | |
961 | def hubssolib_clear_flash |
962 | return unless self.hubssolib_current_session |
963 | self.hubssolib_current_session.session_flash = {} |
964 | end |
965 | |
966 | # Helper methods to output flash data. It isn't merged into the standard |
967 | # application flash with a filter because the rather daft and difficult |
968 | # to manage lifecycle model of the standard flash gets in the way. |
969 | # |
970 | # First, return tags for a flash using the given key, clearing the |
971 | # result in the flash hash now it has been used. |
972 | # |
973 | def hubssolib_flash_tag(key) |
974 | value = hubssolib_get_flash[key] |
975 | |
976 | if (value) |
977 | hubssolib_set_flash(key, nil) |
978 | return "<h2 align=\"left\" class=\"#{key}\">#{value}</h2><p />" |
979 | else |
980 | return '' |
981 | end |
982 | end |
983 | |
984 | # Next, return tags for a standard application flash using the given key. |
985 | # |
986 | def hubssolib_standard_flash_tag(key) |
987 | value = flash[key] if defined?(flash) |
988 | |
989 | if (value) |
990 | flash.delete(key) |
991 | return "<h2 align=\"left\" class=\"#{key}\">#{value}</h2><p />" |
992 | else |
993 | return '' |
994 | end |
995 | end |
996 | |
997 | # Return flash tags for known keys, then all remaining keys, from both |
998 | # the cross-application and standard standard flash hashes. |
999 | # |
1000 | def hubssolib_flash_tags |
1001 | # These known key values are used to guarantee an order in the output |
1002 | # for cases where multiple messages are defined. |
1003 | |
1004 | tags = hubssolib_flash_tag(:notice) << |
1005 | hubssolib_flash_tag(:attention) << |
1006 | hubssolib_flash_tag(:alert) |
1007 | |
1008 | tags << hubssolib_standard_flash_tag(:notice) << |
1009 | hubssolib_standard_flash_tag(:attention) << |
1010 | hubssolib_standard_flash_tag(:alert) |
1011 | |
1012 | # Now pick up anything else. |
1013 | |
1014 | hubssolib_get_flash.each do |key, value| |
1015 | tags << hubssolib_flash_tag(key) if (value and !value.empty?) |
1016 | end |
1017 | |
1018 | flash.each do |key, value| |
1019 | tags << hubssolib_standard_flash_tag(key) if (value and !value.empty?) |
1020 | end if defined?(flash) |
1021 | |
1022 | return tags |
1023 | end |
1024 | |
1025 | # Retrieve the message of an exception stored as an object in the given |
1026 | # string. |
1027 | # |
1028 | def hubssolib_get_exception_message(id_data) |
1029 | hubssolib_get_exception_data(CGI::unescape(id_data)) |
1030 | end |
1031 | |
1032 | # Inclusion hook to make various methods available as ActionView |
1033 | # helper methods. |
1034 | # |
1035 | def self.included(base) |
1036 | base.send :helper_method, |
1037 | :hubssolib_current_user, |
1038 | :hubssolib_unique_name, |
1039 | :hubssolib_logged_in?, |
1040 | :hubssolib_authorized?, |
1041 | :hubssolib_privileged?, |
1042 | :hubssolib_flash_tags |
1043 | rescue |
1044 | # We're not always included in controllers... |
1045 | nil |
1046 | end |
1047 | |
1048 | private |
1049 | |
1050 | # Indicate that the user must log in to complete their request. |
1051 | # Returns false to enable a before_filter to return through this |
1052 | # method while ensuring that the previous action processing is |
1053 | # halted (since the overall return value is therefore 'false'). |
1054 | # |
1055 | def hubssolib_must_login |
1056 | # If HTTP, redirect to the same place, but HTTPS. Then we can store the |
1057 | # flash and return-to in the session data. We'll have the same set of |
1058 | # before-filter operations running and they'll find out we're either |
1059 | # authorised after all, or come back to this very function, which will |
1060 | # now be happily running from an HTTPS connection and will go on to set |
1061 | # the flash and redirect to the login page. |
1062 | |
1063 | if hubssolib_ensure_https |
1064 | hubssolib_set_flash(:alert, 'You must log in before you can continue.') |
1065 | redirect_to HUB_PATH_PREFIX + '/account/login' |
1066 | end |
1067 | |
1068 | return false |
1069 | end |
1070 | |
1071 | # Indicate access is denied for a given logged in user's request. |
1072 | # Returns false to enable a before_filter to return through this |
1073 | # method while ensuring that the previous action processing is |
1074 | # halted (since the overall return value is therefore 'false'). |
1075 | # |
1076 | def hubssolib_access_denied |
1077 | # See hubsso_must_login for the reason behind the following call. |
1078 | |
1079 | if hubssolib_ensure_https |
1080 | hubssolib_set_flash(:alert, 'You do not have permission to carry out that action on this site.') |
1081 | redirect_to HUB_PATH_PREFIX + '/' |
1082 | end |
1083 | |
1084 | return false |
1085 | end |
1086 | |
1087 | # Check conditions for session expiry. Returns 'true' if session's |
1088 | # last_used date indicates expiry, else 'false'. |
1089 | # |
1090 | def hubssolib_session_expired? |
1091 | |
1092 | # 23-Oct-2006 (ADH): |
1093 | # |
1094 | # An exception, which is also a security hole of sorts. POST requests |
1095 | # cannot be redirected because HTTP doesn't have that concept. If a user |
1096 | # is editing a Wiki page, say, then goes away, comes back later and now |
1097 | # finishes their edits, their session may have timed out. They submit |
1098 | # the page but it's by POST so their submission details are lost. If they |
1099 | # are lucky their browser might remember the form contents if they go |
1100 | # back but not all do and not all users would think of doing that. |
1101 | # |
1102 | # To work around this, don't enforce a timeout for POST requests. Should |
1103 | # a user on a public computer not log out, then a hacker arrive *after* |
1104 | # the session expiry time (if they arrive before it expires then the |
1105 | # except for POSTs is irrelevant), they could recover the session by |
1106 | # constructing a POST request. It's a convoluted path, requires a user to |
1107 | # have not logged out anyway, and the Hub isn't intended for Fort Knox. |
1108 | # At the time of writing the trade-off of usability vs security is |
1109 | # considered acceptable, though who knows, the view may change in future. |
1110 | |
1111 | last_used = hubssolib_get_last_used |
1112 | (request.method != :post && last_used && Time.now.utc - last_used > HUBSSOLIB_IDLE_TIME_LIMIT) |
1113 | end |
1114 | |
1115 | # Retrieve data from a given cookie with encrypted contents. |
1116 | # |
1117 | def hubssolib_get_secure_cookie_data(name) |
1118 | return HubSsoLib::Crypto.decode_object(cookies[name], request.remote_ip) |
1119 | end |
1120 | |
1121 | # Set the given cookie to a value of the given data, which |
1122 | # will be encrypted. |
1123 | # |
1124 | def hubssolib_set_secure_cookie_data(name, value) |
1125 | if (@hubssolib_have_written_cookie) |
1126 | raise "HubSsoLib: Attmept to set cookie '#{name}' more than once" |
1127 | end |
1128 | |
1129 | @hubssolib_have_written_cookie = true |
1130 | |
1131 | # Using cookies.delete *should* work but doesn't. Set the |
1132 | # cookie with nil data instead. |
1133 | |
1134 | data = value.nil? ? nil : HubSsoLib::Crypto.encode_object(value, request.remote_ip) |
1135 | |
1136 | # No expiry time; to aid security, use session cookies only. |
1137 | |
1138 | cookies[name] = { |
1139 | :value => data, |
1140 | :path => HUBSSOLIB_COOKIE_PATH, |
1141 | :secure => true |
1142 | } |
1143 | end |
1144 | |
1145 | # Establish a single DRb factory connection. |
1146 | # |
1147 | def hubssolib_factory |
1148 | |
1149 | # See: |
1150 | # http://stackoverflow.com/questions/299219/where-is-the-correct-place-to-initialize-the-drb-service-within-a-rails-applicati |
1151 | # |
1152 | begin |
1153 | DRb.current_server |
1154 | rescue DRb::DRbServerNotFound |
1155 | DRb.start_service |
1156 | # Move to different ThreadGroup to stop Mongrel hang on exit. |
1157 | ThreadGroup.new.add DRb.thread |
1158 | end |
1159 | |
1160 | return @factory ||= DRbObject.new_with_uri(HUBSSOLIB_DRB_URI) |
1161 | end |
1162 | |
1163 | # Retrieve user data from the DRb server. |
1164 | # |
1165 | def hubssolib_get_user_data |
1166 | user = self.hubssolib_current_session ? self.hubssolib_current_session.session_user : nil |
1167 | |
1168 | if (user && user.user_id) |
1169 | return user |
1170 | else |
1171 | return nil |
1172 | end |
1173 | end |
1174 | |
1175 | def hubssolib_set_user_data(user) |
1176 | self.hubssolib_current_session.session_user = user |
1177 | end |
1178 | |
1179 | def hubssolib_get_session_data |
1180 | |
1181 | # If we're not using SSL, forget it |
1182 | return nil unless request.ssl? |
1183 | |
1184 | # If we've no cookie, we need a new session ID |
1185 | key = hubssolib_get_secure_cookie_data(HUBSSOLIB_COOKIE_NAME) |
1186 | |
1187 | unless (key) |
1188 | key = HubSsoLib::Crypto.pack64(HubSsoLib::Crypto.random_data(48)) |
1189 | hubssolib_set_secure_cookie_data(HUBSSOLIB_COOKIE_NAME, key) |
1190 | end |
1191 | |
1192 | return hubssolib_factory().get_session(key) |
1193 | |
1194 | rescue Exception => e |
1195 | |
1196 | # At this point there tends to be no Session data, so we're |
1197 | # going to have to encode the exception data into the URI... |
1198 | # It must be escaped twice, as many servers treat "%2F" in a |
1199 | # URI as a "/" and Apache may flat refuse to serve the page, |
1200 | # raising a 404 error unless "AllowEncodedSlashes on" is |
1201 | # specified in its configuration. |
1202 | |
1203 | suffix = '/' + CGI::escape(CGI::escape(hubssolib_set_exception_data(e))) |
1204 | new_path = HUB_PATH_PREFIX + '/tasks/service' |
1205 | redirect_to new_path + suffix unless request.path.include?(new_path) |
1206 | return nil |
1207 | end |
1208 | |
1209 | def hubssolib_set_session_data(session) |
1210 | # Nothing to do presently - DRb handles everything |
1211 | end |
1212 | |
1213 | # Return an array of Hub User objects representing users based |
1214 | # on a list of known sessions returned by the DRb server. Note |
1215 | # that if an application exposes this method to a view, it is |
1216 | # up to the application to ensure sufficient access permission |
1217 | # protection for that view according to the webmaster's choice |
1218 | # of site security level. Generally, normal users should not |
1219 | # be allowed access. |
1220 | # |
1221 | def hubssolib_enumerate_users |
1222 | sessions = hubssolib_factory().enumerate_sessions() |
1223 | users = [] |
1224 | |
1225 | sessions.each do |key, value| |
1226 | user = value.session_user |
1227 | users.push(user) if (user && user.respond_to?(:user_id) && user.user_id) |
1228 | end |
1229 | |
1230 | return users |
1231 | |
1232 | rescue Exception => e |
1233 | |
1234 | # At this point there tends to be no Session data, so we're |
1235 | # going to have to encode the exception data into the URI... |
1236 | # See earlier for double-escaping rationale. |
1237 | |
1238 | suffix = '/' + CGI::escape(CGI::escape(hubssolib_set_exception_data(e))) |
1239 | new_path = HUB_PATH_PREFIX + '/tasks/service' |
1240 | redirect_to new_path + suffix unless request.path.include?(new_path) |
1241 | return nil |
1242 | end |
1243 | |
1244 | # Encode exception data into a string suitable for using in a URL |
1245 | # if CGI escaped first. Pass the exception object; stores only the |
1246 | # message. |
1247 | # |
1248 | def hubssolib_set_exception_data(e) |
1249 | HubSsoLib::Crypto.encode_object(e.message, request.remote_ip) |
1250 | end |
1251 | |
1252 | # Decode exception data encoded with hubssolib_set_exception_data. |
1253 | # Returns the originally stored message string or 'nil' if there |
1254 | # are any decoding problems. Pass the encoded data. |
1255 | # |
1256 | def hubssolib_get_exception_data(data) |
1257 | HubSsoLib::Crypto.decode_object(data, request.remote_ip) |
1258 | end |
1259 | |
1260 | # Various accessors that ultimately run through the DRb server if |
1261 | # the session data is available, else return default values. |
1262 | |
1263 | def hubssolib_get_last_used |
1264 | session = self.hubssolib_current_session |
1265 | session ? session.session_last_used : Time.now.utc |
1266 | end |
1267 | |
1268 | def hubssolib_set_last_used(time) |
1269 | return unless self.hubssolib_current_session |
1270 | self.hubssolib_current_session.session_last_used = time |
1271 | end |
1272 | |
1273 | def hubssolib_get_return_to |
1274 | session = self.hubssolib_current_session |
1275 | session ? session.session_return_to : nil |
1276 | end |
1277 | |
1278 | def hubssolib_set_return_to(uri) |
1279 | return unless self.hubssolib_current_session |
1280 | self.hubssolib_current_session.session_return_to = uri |
1281 | end |
1282 | |
1283 | end # Core module |
1284 | end # HubSsoLib module |
1285 | |
1286 | ####################################################################### |
1287 | # Classes: Standard class extensions for HubSsoLib Roles operations. # |
1288 | # (C) Hipposoft 2006 # |
1289 | # # |
1290 | # Purpose: Extensions to standard classes to support HubSsoLib. # |
1291 | # # |
1292 | # Author: A.D.Hodgkinson # |
1293 | # # |
1294 | # History: 20-Oct-2006 (ADH): Integrated into HubSsoLib. # |
1295 | ####################################################################### |
1296 | |
1297 | # Method to return a Roles object created from the |
1298 | # contents of the String the method is invoked upon. The |
1299 | # string may contain a single role or a comma-separated list |
1300 | # with no white space. |
1301 | # |
1302 | class String |
1303 | def to_authenticated_roles |
1304 | roles = HubSsoLib::Roles.new |
1305 | array = self.split(',') |
1306 | |
1307 | roles.clear |
1308 | array.each { |role| roles.add(role) } |
1309 | |
1310 | return roles |
1311 | end |
1312 | end # String class |
1313 | |
1314 | # Method to return a Roles object created from the |
1315 | # contents of the Symbol the method is invoked upon. |
1316 | # |
1317 | class Symbol |
1318 | def to_authenticated_roles |
1319 | return self.to_s.to_authenticated_roles |
1320 | end |
1321 | end # Symbol class |
1322 | |
1323 | # Method to return a Roles object created from the |
1324 | # contents of the Array the method is invoked upon. The array |
1325 | # contents will be flattened. After that, each entry must be |
1326 | # a single role symbol or string equivalent. Comma-separated |
1327 | # lists are not currently allowed (improvements to the roles |
1328 | # class could easily give this, but the bloat isn't needed). |
1329 | # |
1330 | class Array |
1331 | def to_authenticated_roles |
1332 | roles = HubSsoLib::Roles.new |
1333 | roles.clear |
1334 | |
1335 | self.flatten.each { |entry| roles.add(entry.to_s) } |
1336 | |
1337 | return roles |
1338 | end |
1339 | end # Array class |