| Class | User |
| In: |
app/models/user.rb
|
| Parent: | ActiveRecord::Base |
| BAD_USER_PW_MSG | = | "Bad username or password" | There are a lot of situtations where something went wrong on login but we don‘t want to say precisely what. Here‘s a message that covers them all in a suitably non-specific way… |
| authentication_status | [R] | Reported status for login attempts ("Account expired", "Login succeeded but there were ‘n’ prior failures, etc.) This is set by authenticate_by_password, q.v. The value is a list of strings, or nil. |
| current_password_check | [RW] | Also support UIs that want to verify that a user changing their password knows the current one… |
| password | [R] | Validation of passwords — we maintain shadow attributes for the UI, (re)set the hash when the UI sets the password, and allow saving only when the validations on the shadow attrs pass, as per the usual. |
| reset_bad_logins_flag | [R] | Pseudo-attribute for resetting bad login attempts |
Runs the body with User.current temporarily set to the user of record. Useful for managing settings related to authorization (as opposed to permissioning), e.g., the "change my password" action in the password controller, which should always change the password of the current user, never mind who they‘re aliased to.
# File app/models/user.rb, line 142
142: def User.acting_as_user_of_record
143: old_current = User.current
144: User.current = User.of_record
145: yield
146: ensure
147: User.current = old_current
148: end
Runs the body with User.of_record set to some_user, and User.current set to other_user. If the :acting_as keyword argument is omitted, both User.current and User.acting_as are set to some_user.
# File app/models/user.rb, line 111
111: def User.as( user, options = {} )
112:
113: raise ArgumentError, "incorrect keyword to User.as" if
114: options.size > 1 || (options.size == 1 &&
115: options.keys.first != :acting_as)
116:
117: old_current = User.current
118: old_of_record = User.of_record
119:
120: User.of_record = user
121: User.current = options[:acting_as] || user
122:
123: yield
124:
125: ensure
126:
127: User.current = old_current
128: User.of_record = old_of_record
129:
130: end
Returns all permissions for privilege ‘privilege’ on class ‘klass’, including direct permissions and applicable wildcards, but excluding grants.
# File app/models/user.rb, line 435
435: def all_permissions( privilege, klass )
436:
437: class_perms = self.perms_sorted[ klass.name ] || {}
438: all_perms = class_perms[ privilege ] || []
439:
440: if !class_perms[ :any ].nil?
441: all_perms = all_perms + class_perms[ :any ]
442: end
443:
444: return all_perms
445:
446: end
Wrap attributes= so that pasword attr is left unset if both it and password_confirmation are blank. XXX test
# File app/models/user.rb, line 189
189: def attributes=( attrs_hash )
190:
191: if %w(password password_confirmation).all? { |x| attrs_hash[x].blank?}
192: attrs_hash = attrs_hash.dup
193: %w(password password_confirmation).each { |x| attrs_hash[x] = nil }
194: end
195:
196: super( attrs_hash )
197:
198: end
Attempt to authenticate with the given password. Also checks that the password is not expired, and that the user isn‘t locked out explicitly, or due to multiple failed login attempts.
Returns a boolean true for success (user logged in), and a boolean false for failure.
Either way, the "authentication_status" attribute may be updated to contain a list of strings with information of interest ("Account expired", "Login succeeded but there were ‘n’ prior failures", etc.) Also, either way, various status fields are updated in the database, including the "last_login_at" date on success, and the various counts of bad login attempts on failure.
# File app/models/user.rb, line 259
259: def authenticate_by_password( password )
260:
261: fail_with = Proc.new {|msg| @authentication_status = [msg]; return false }
262:
263: # If the user is already banned, we don't even bother.
264:
265: fail_with.call( BAD_USER_PW_MSG ) if locked_out?
266: fail_with.call("Account expired") if !password_expires_at.nil? &&
267: password_expires_at.to_date < Date.today
268: fail_with.call("Too many failed attempts. Try again later.") if
269: !no_login_until.nil? && no_login_until.to_time > Time.now
270:
271: # User is not banned. Do a check...
272:
273: if self.has_password?( password )
274:
275: # Success...
276:
277: @authentication_status = []
278:
279: if !bad_login_attempts.nil?
280: @authentication_status <<
281: "Login succeeded. Note that there were #{bad_login_attempts} " +
282: "failed login attempts"
283: self.bad_login_attempts = nil
284: self.bad_logins_since_lockout = nil
285: self.no_login_until = nil
286: end
287:
288: pw_lifetime = current_password_remaining_days
289:
290: if !pw_lifetime.nil? && pw_lifetime < 5
291: @authentication_status <<
292: "Login succeeded. Your password expires in " +
293: "#{current_password_remaining_days} days. Please change it now!"
294: end
295:
296: @authentication_status = nil if @authentication_status == []
297:
298: self.last_login_at = Time.now
299: self.save!
300:
301: return true
302:
303: else
304:
305: # Bad password
306:
307: @authentication_status = [BAD_USER_PW_MSG]
308:
309: self.bad_login_attempts ||= 0
310: self.bad_login_attempts += 1
311:
312: self.bad_logins_since_lockout ||= 0
313: self.bad_logins_since_lockout += 1
314:
315: if !firm.max_bad_logins.nil? && !firm.bad_login_dead_minutes.nil?
316: if self.bad_logins_since_lockout >= firm.max_bad_logins
317: @authentication_status=["Too many failed attempts. Try again later"]
318: self.bad_logins_since_lockout = nil
319: self.no_login_until = Time.now + 60 * firm.bad_login_dead_minutes
320: end
321: end
322:
323: save!
324:
325: return false
326:
327: end
328:
329: end
Returns true iff this user has privilege ‘privilege’ on object ‘obj‘
# File app/models/user.rb, line 412
412: def can?( privilege, obj )
413:
414: return false if privilege == :forbidden_operation # lest wildcards allow it
415:
416: class_name = obj.class.name
417: class_perms = perms_sorted[class_name] || {}
418:
419: (class_perms[privilege] || []).each do |perm|
420: return true if perm.allows_internal?( obj, self )
421: end
422:
423: (class_perms[:any] || []).each do |perm|
424: return true if perm.allows_internal?( obj, self )
425: end
426:
427: return false
428:
429: end
Returns true iff this user has a permission which grants ‘operation’ on some set of objects of class ‘klass’ (which may be passed in as a class object, or as a string naming a class). This method just checks the permission structure; it does not guarantee that any such object currently exists. For that, try
klass.count_permitting( operation, :user => user ) > 0
which tells you if the user could perform the operation on some object *right now*.
# File app/models/user.rb, line 459
459: def could_ever?( operation, klass )
460: klass = klass.name if klass.is_a?( Class )
461: klass_perms = perms_sorted[klass]
462: return !klass_perms.nil? &&
463: (!klass_perms[operation].nil? || !klass_perms[:any].nil?)
464: end
Remaining life of the current password. Returns nil if no expiration date is set.
# File app/models/user.rb, line 364
364: def current_password_remaining_days
365: return nil if password_expires_at.nil?
366: return password_expires_at.to_date - Date.today
367: end
Only the grant permissions of this user, as an array.
# File app/models/user.rb, line 405
405: def grant_permissions( force_reload = false )
406: permissions( force_reload ).select( &:is_grant? )
407: end
Check to see whether the user‘s current password is the same as the argument. A new password that we‘re trying to set becomes ‘current’ when it is actually saved, but not before.
# File app/models/user.rb, line 343
343: def has_current_password?( password )
344: !@current_pw_hash.nil? &&
345: pw_hash(password, @current_pw_salt) == @current_pw_hash
346: end
Just a password check, with no accompanying foofaraw…
# File app/models/user.rb, line 333
333: def has_password?( password )
334: !self.password_hash.nil? &&
335: pw_hash(password, self.password_salt) == self.password_hash
336: end
# File app/models/user.rb, line 157
157: def password=( new_password )
158: @password = new_password
159: if !new_password.blank?
160: set_pw_hash_for( new_password )
161: pw_lifetime = firm.password_lifetime_days
162: if pw_lifetime.nil?
163: self.password_expires_at = nil
164: else
165: self.password_expires_at = pw_lifetime.days.from_now
166: end
167: end
168: end
All permissions of this user, as an array. This isn‘t actually an association proxy, but calling it with a non-nil first argument will force cached data to be reloaded anyway.
# File app/models/user.rb, line 384
384: def permissions( force_reload = false )
385:
386: # Two levels of joins here, so can't use has_many :through
387:
388: @permissions = nil if force_reload
389:
390: @permissions ||=
391: Permission.find :all, :conditions =>
392: ["role_id in (select role_id from role_assignments
393: where user_id = ?
394: and #{RoleAssignment::CURRENT_SQL_CONDITION})",
395: self.id]
396:
397: @permissions_by_class_and_op = sort_permissions( @permissions )
398:
399: return @permissions
400:
401: end
Reset the "bad login attempts" counter and associated state. Note — does not reset the permanent lockout bit; that‘s a separate flag, managed by the "locked_out" boolean attribute. NB as well — does not do a save.
# File app/models/user.rb, line 374
374: def reset_bad_logins
375: self.bad_login_attempts = nil
376: self.no_login_until = nil
377: end
# File app/models/user.rb, line 231
231: def reset_bad_logins_flag=( val )
232: @reset_bad_logins_flag = val
233: if @reset_bad_logins_flag
234: reset_bad_logins
235: end
236: end
# File app/models/user.rb, line 473
473: def sort_permissions( perms )
474:
475: perms_sorted = {}
476:
477: perms.each do |perm|
478: if !perm.is_grant
479: perms_sorted[perm.class_name] ||= {}
480: perms_sorted[perm.class_name][perm.privilege] ||= []
481: perms_sorted[perm.class_name][perm.privilege] << perm
482: end
483: end
484:
485: perms_sorted
486:
487: end