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