##// END OF EJS Templates
Implements the behaviour expected in user_test....
Jean-Philippe Lang -
r8909:df3c2466f8ca
parent child
Show More
@@ -1,642 +1,643
1 1 # Redmine - project management software
2 2 # Copyright (C) 2006-2011 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 require "digest/sha1"
19 19
20 20 class User < Principal
21 21 include Redmine::SafeAttributes
22 22
23 23 # Account statuses
24 24 STATUS_ANONYMOUS = 0
25 25 STATUS_ACTIVE = 1
26 26 STATUS_REGISTERED = 2
27 27 STATUS_LOCKED = 3
28 28
29 29 # Different ways of displaying/sorting users
30 30 USER_FORMATS = {
31 31 :firstname_lastname => {:string => '#{firstname} #{lastname}', :order => %w(firstname lastname id)},
32 32 :firstname => {:string => '#{firstname}', :order => %w(firstname id)},
33 33 :lastname_firstname => {:string => '#{lastname} #{firstname}', :order => %w(lastname firstname id)},
34 34 :lastname_coma_firstname => {:string => '#{lastname}, #{firstname}', :order => %w(lastname firstname id)},
35 35 :username => {:string => '#{login}', :order => %w(login id)},
36 36 }
37 37
38 38 MAIL_NOTIFICATION_OPTIONS = [
39 39 ['all', :label_user_mail_option_all],
40 40 ['selected', :label_user_mail_option_selected],
41 41 ['only_my_events', :label_user_mail_option_only_my_events],
42 42 ['only_assigned', :label_user_mail_option_only_assigned],
43 43 ['only_owner', :label_user_mail_option_only_owner],
44 44 ['none', :label_user_mail_option_none]
45 45 ]
46 46
47 47 has_and_belongs_to_many :groups, :after_add => Proc.new {|user, group| group.user_added(user)},
48 48 :after_remove => Proc.new {|user, group| group.user_removed(user)}
49 49 has_many :changesets, :dependent => :nullify
50 50 has_one :preference, :dependent => :destroy, :class_name => 'UserPreference'
51 51 has_one :rss_token, :class_name => 'Token', :conditions => "action='feeds'"
52 52 has_one :api_token, :class_name => 'Token', :conditions => "action='api'"
53 53 belongs_to :auth_source
54 54
55 55 # Active non-anonymous users scope
56 56 named_scope :active, :conditions => "#{User.table_name}.status = #{STATUS_ACTIVE}"
57 57 named_scope :logged, :conditions => "#{User.table_name}.status <> #{STATUS_ANONYMOUS}"
58 58 named_scope :status, lambda {|arg| arg.blank? ? {} : {:conditions => {:status => arg.to_i}} }
59 59
60 60 acts_as_customizable
61 61
62 62 attr_accessor :password, :password_confirmation
63 63 attr_accessor :last_before_login_on
64 64 # Prevents unauthorized assignments
65 65 attr_protected :login, :admin, :password, :password_confirmation, :hashed_password
66 66
67 67 LOGIN_LENGTH_LIMIT = 60
68 68 MAIL_LENGTH_LIMIT = 60
69 69
70 70 validates_presence_of :login, :firstname, :lastname, :mail, :if => Proc.new { |user| !user.is_a?(AnonymousUser) }
71 71 validates_uniqueness_of :login, :if => Proc.new { |user| !user.login.blank? }, :case_sensitive => false
72 72 validates_uniqueness_of :mail, :if => Proc.new { |user| !user.mail.blank? }, :case_sensitive => false
73 73 # Login must contain lettres, numbers, underscores only
74 74 validates_format_of :login, :with => /^[a-z0-9_\-@\.]*$/i
75 75 validates_length_of :login, :maximum => LOGIN_LENGTH_LIMIT
76 76 validates_length_of :firstname, :lastname, :maximum => 30
77 77 validates_format_of :mail, :with => /^([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})$/i, :allow_blank => true
78 78 validates_length_of :mail, :maximum => MAIL_LENGTH_LIMIT, :allow_nil => true
79 79 validates_confirmation_of :password, :allow_nil => true
80 80 validates_inclusion_of :mail_notification, :in => MAIL_NOTIFICATION_OPTIONS.collect(&:first), :allow_blank => true
81 81 validate :validate_password_length
82 82
83 83 before_create :set_mail_notification
84 84 before_save :update_hashed_password
85 85 before_destroy :remove_references_before_destroy
86 86
87 87 named_scope :in_group, lambda {|group|
88 88 group_id = group.is_a?(Group) ? group.id : group.to_i
89 89 { :conditions => ["#{User.table_name}.id IN (SELECT gu.user_id FROM #{table_name_prefix}groups_users#{table_name_suffix} gu WHERE gu.group_id = ?)", group_id] }
90 90 }
91 91 named_scope :not_in_group, lambda {|group|
92 92 group_id = group.is_a?(Group) ? group.id : group.to_i
93 93 { :conditions => ["#{User.table_name}.id NOT IN (SELECT gu.user_id FROM #{table_name_prefix}groups_users#{table_name_suffix} gu WHERE gu.group_id = ?)", group_id] }
94 94 }
95 95
96 96 def set_mail_notification
97 97 self.mail_notification = Setting.default_notification_option if self.mail_notification.blank?
98 98 true
99 99 end
100 100
101 101 def update_hashed_password
102 102 # update hashed_password if password was set
103 103 if self.password && self.auth_source_id.blank?
104 104 salt_password(password)
105 105 end
106 106 end
107 107
108 108 def reload(*args)
109 109 @name = nil
110 110 @projects_by_role = nil
111 111 super
112 112 end
113 113
114 114 def mail=(arg)
115 115 write_attribute(:mail, arg.to_s.strip)
116 116 end
117 117
118 118 def identity_url=(url)
119 119 if url.blank?
120 120 write_attribute(:identity_url, '')
121 121 else
122 122 begin
123 123 write_attribute(:identity_url, OpenIdAuthentication.normalize_identifier(url))
124 124 rescue OpenIdAuthentication::InvalidOpenId
125 125 # Invlaid url, don't save
126 126 end
127 127 end
128 128 self.read_attribute(:identity_url)
129 129 end
130 130
131 131 # Returns the user that matches provided login and password, or nil
132 132 def self.try_to_login(login, password)
133 133 # Make sure no one can sign in with an empty password
134 134 return nil if password.to_s.empty?
135 user = find_by_login(login)
135 matches = find_all_by_login(login)
136 user = (matches.size < 2 ? matches.first : matches.detect {|u| u.login == login})
136 137 if user
137 138 # user is already in local database
138 139 return nil if !user.active?
139 140 if user.auth_source
140 141 # user has an external authentication method
141 142 return nil unless user.auth_source.authenticate(login, password)
142 143 else
143 144 # authentication with local password
144 145 return nil unless user.check_password?(password)
145 146 end
146 147 else
147 148 # user is not yet registered, try to authenticate with available sources
148 149 attrs = AuthSource.authenticate(login, password)
149 150 if attrs
150 151 user = new(attrs)
151 152 user.login = login
152 153 user.language = Setting.default_language
153 154 if user.save
154 155 user.reload
155 156 logger.info("User '#{user.login}' created from external auth source: #{user.auth_source.type} - #{user.auth_source.name}") if logger && user.auth_source
156 157 end
157 158 end
158 159 end
159 160 user.update_attribute(:last_login_on, Time.now) if user && !user.new_record?
160 161 user
161 162 rescue => text
162 163 raise text
163 164 end
164 165
165 166 # Returns the user who matches the given autologin +key+ or nil
166 167 def self.try_to_autologin(key)
167 168 tokens = Token.find_all_by_action_and_value('autologin', key)
168 169 # Make sure there's only 1 token that matches the key
169 170 if tokens.size == 1
170 171 token = tokens.first
171 172 if (token.created_on > Setting.autologin.to_i.day.ago) && token.user && token.user.active?
172 173 token.user.update_attribute(:last_login_on, Time.now)
173 174 token.user
174 175 end
175 176 end
176 177 end
177 178
178 179 def self.name_formatter(formatter = nil)
179 180 USER_FORMATS[formatter || Setting.user_format] || USER_FORMATS[:firstname_lastname]
180 181 end
181 182
182 183 # Returns an array of fields names than can be used to make an order statement for users
183 184 # according to how user names are displayed
184 185 # Examples:
185 186 #
186 187 # User.fields_for_order_statement => ['users.login', 'users.id']
187 188 # User.fields_for_order_statement('authors') => ['authors.login', 'authors.id']
188 189 def self.fields_for_order_statement(table=nil)
189 190 table ||= table_name
190 191 name_formatter[:order].map {|field| "#{table}.#{field}"}
191 192 end
192 193
193 194 # Return user's full name for display
194 195 def name(formatter = nil)
195 196 f = self.class.name_formatter(formatter)
196 197 if formatter
197 198 eval('"' + f[:string] + '"')
198 199 else
199 200 @name ||= eval('"' + f[:string] + '"')
200 201 end
201 202 end
202 203
203 204 def active?
204 205 self.status == STATUS_ACTIVE
205 206 end
206 207
207 208 def registered?
208 209 self.status == STATUS_REGISTERED
209 210 end
210 211
211 212 def locked?
212 213 self.status == STATUS_LOCKED
213 214 end
214 215
215 216 def activate
216 217 self.status = STATUS_ACTIVE
217 218 end
218 219
219 220 def register
220 221 self.status = STATUS_REGISTERED
221 222 end
222 223
223 224 def lock
224 225 self.status = STATUS_LOCKED
225 226 end
226 227
227 228 def activate!
228 229 update_attribute(:status, STATUS_ACTIVE)
229 230 end
230 231
231 232 def register!
232 233 update_attribute(:status, STATUS_REGISTERED)
233 234 end
234 235
235 236 def lock!
236 237 update_attribute(:status, STATUS_LOCKED)
237 238 end
238 239
239 240 # Returns true if +clear_password+ is the correct user's password, otherwise false
240 241 def check_password?(clear_password)
241 242 if auth_source_id.present?
242 243 auth_source.authenticate(self.login, clear_password)
243 244 else
244 245 User.hash_password("#{salt}#{User.hash_password clear_password}") == hashed_password
245 246 end
246 247 end
247 248
248 249 # Generates a random salt and computes hashed_password for +clear_password+
249 250 # The hashed password is stored in the following form: SHA1(salt + SHA1(password))
250 251 def salt_password(clear_password)
251 252 self.salt = User.generate_salt
252 253 self.hashed_password = User.hash_password("#{salt}#{User.hash_password clear_password}")
253 254 end
254 255
255 256 # Does the backend storage allow this user to change their password?
256 257 def change_password_allowed?
257 258 return true if auth_source.nil?
258 259 return auth_source.allow_password_changes?
259 260 end
260 261
261 262 # Generate and set a random password. Useful for automated user creation
262 263 # Based on Token#generate_token_value
263 264 #
264 265 def random_password
265 266 chars = ("a".."z").to_a + ("A".."Z").to_a + ("0".."9").to_a
266 267 password = ''
267 268 40.times { |i| password << chars[rand(chars.size-1)] }
268 269 self.password = password
269 270 self.password_confirmation = password
270 271 self
271 272 end
272 273
273 274 def pref
274 275 self.preference ||= UserPreference.new(:user => self)
275 276 end
276 277
277 278 def time_zone
278 279 @time_zone ||= (self.pref.time_zone.blank? ? nil : ActiveSupport::TimeZone[self.pref.time_zone])
279 280 end
280 281
281 282 def wants_comments_in_reverse_order?
282 283 self.pref[:comments_sorting] == 'desc'
283 284 end
284 285
285 286 # Return user's RSS key (a 40 chars long string), used to access feeds
286 287 def rss_key
287 288 token = self.rss_token || Token.create(:user => self, :action => 'feeds')
288 289 token.value
289 290 end
290 291
291 292 # Return user's API key (a 40 chars long string), used to access the API
292 293 def api_key
293 294 token = self.api_token || self.create_api_token(:action => 'api')
294 295 token.value
295 296 end
296 297
297 298 # Return an array of project ids for which the user has explicitly turned mail notifications on
298 299 def notified_projects_ids
299 300 @notified_projects_ids ||= memberships.select {|m| m.mail_notification?}.collect(&:project_id)
300 301 end
301 302
302 303 def notified_project_ids=(ids)
303 304 Member.update_all("mail_notification = #{connection.quoted_false}", ['user_id = ?', id])
304 305 Member.update_all("mail_notification = #{connection.quoted_true}", ['user_id = ? AND project_id IN (?)', id, ids]) if ids && !ids.empty?
305 306 @notified_projects_ids = nil
306 307 notified_projects_ids
307 308 end
308 309
309 310 def valid_notification_options
310 311 self.class.valid_notification_options(self)
311 312 end
312 313
313 314 # Only users that belong to more than 1 project can select projects for which they are notified
314 315 def self.valid_notification_options(user=nil)
315 316 # Note that @user.membership.size would fail since AR ignores
316 317 # :include association option when doing a count
317 318 if user.nil? || user.memberships.length < 1
318 319 MAIL_NOTIFICATION_OPTIONS.reject {|option| option.first == 'selected'}
319 320 else
320 321 MAIL_NOTIFICATION_OPTIONS
321 322 end
322 323 end
323 324
324 325 # Find a user account by matching the exact login and then a case-insensitive
325 326 # version. Exact matches will be given priority.
326 327 def self.find_by_login(login)
327 328 # force string comparison to be case sensitive on MySQL
328 329 type_cast = (ActiveRecord::Base.connection.adapter_name == 'MySQL') ? 'BINARY' : ''
329 330
330 331 # First look for an exact match
331 332 user = first(:conditions => ["#{type_cast} login = ?", login])
332 333 # Fail over to case-insensitive if none was found
333 334 user ||= first(:conditions => ["#{type_cast} LOWER(login) = ?", login.to_s.downcase])
334 335 end
335 336
336 337 def self.find_by_rss_key(key)
337 338 token = Token.find_by_value(key)
338 339 token && token.user.active? ? token.user : nil
339 340 end
340 341
341 342 def self.find_by_api_key(key)
342 343 token = Token.find_by_action_and_value('api', key)
343 344 token && token.user.active? ? token.user : nil
344 345 end
345 346
346 347 # Makes find_by_mail case-insensitive
347 348 def self.find_by_mail(mail)
348 349 find(:first, :conditions => ["LOWER(mail) = ?", mail.to_s.downcase])
349 350 end
350 351
351 352 def to_s
352 353 name
353 354 end
354 355
355 356 # Returns the current day according to user's time zone
356 357 def today
357 358 if time_zone.nil?
358 359 Date.today
359 360 else
360 361 Time.now.in_time_zone(time_zone).to_date
361 362 end
362 363 end
363 364
364 365 def logged?
365 366 true
366 367 end
367 368
368 369 def anonymous?
369 370 !logged?
370 371 end
371 372
372 373 # Return user's roles for project
373 374 def roles_for_project(project)
374 375 roles = []
375 376 # No role on archived projects
376 377 return roles unless project && project.active?
377 378 if logged?
378 379 # Find project membership
379 380 membership = memberships.detect {|m| m.project_id == project.id}
380 381 if membership
381 382 roles = membership.roles
382 383 else
383 384 @role_non_member ||= Role.non_member
384 385 roles << @role_non_member
385 386 end
386 387 else
387 388 @role_anonymous ||= Role.anonymous
388 389 roles << @role_anonymous
389 390 end
390 391 roles
391 392 end
392 393
393 394 # Return true if the user is a member of project
394 395 def member_of?(project)
395 396 !roles_for_project(project).detect {|role| role.member?}.nil?
396 397 end
397 398
398 399 # Returns a hash of user's projects grouped by roles
399 400 def projects_by_role
400 401 return @projects_by_role if @projects_by_role
401 402
402 403 @projects_by_role = Hash.new {|h,k| h[k]=[]}
403 404 memberships.each do |membership|
404 405 membership.roles.each do |role|
405 406 @projects_by_role[role] << membership.project if membership.project
406 407 end
407 408 end
408 409 @projects_by_role.each do |role, projects|
409 410 projects.uniq!
410 411 end
411 412
412 413 @projects_by_role
413 414 end
414 415
415 416 # Returns true if user is arg or belongs to arg
416 417 def is_or_belongs_to?(arg)
417 418 if arg.is_a?(User)
418 419 self == arg
419 420 elsif arg.is_a?(Group)
420 421 arg.users.include?(self)
421 422 else
422 423 false
423 424 end
424 425 end
425 426
426 427 # Return true if the user is allowed to do the specified action on a specific context
427 428 # Action can be:
428 429 # * a parameter-like Hash (eg. :controller => 'projects', :action => 'edit')
429 430 # * a permission Symbol (eg. :edit_project)
430 431 # Context can be:
431 432 # * a project : returns true if user is allowed to do the specified action on this project
432 433 # * an array of projects : returns true if user is allowed on every project
433 434 # * nil with options[:global] set : check if user has at least one role allowed for this action,
434 435 # or falls back to Non Member / Anonymous permissions depending if the user is logged
435 436 def allowed_to?(action, context, options={}, &block)
436 437 if context && context.is_a?(Project)
437 438 # No action allowed on archived projects
438 439 return false unless context.active?
439 440 # No action allowed on disabled modules
440 441 return false unless context.allows_to?(action)
441 442 # Admin users are authorized for anything else
442 443 return true if admin?
443 444
444 445 roles = roles_for_project(context)
445 446 return false unless roles
446 447 roles.detect {|role|
447 448 (context.is_public? || role.member?) &&
448 449 role.allowed_to?(action) &&
449 450 (block_given? ? yield(role, self) : true)
450 451 }
451 452 elsif context && context.is_a?(Array)
452 453 # Authorize if user is authorized on every element of the array
453 454 context.map do |project|
454 455 allowed_to?(action, project, options, &block)
455 456 end.inject do |memo,allowed|
456 457 memo && allowed
457 458 end
458 459 elsif options[:global]
459 460 # Admin users are always authorized
460 461 return true if admin?
461 462
462 463 # authorize if user has at least one role that has this permission
463 464 roles = memberships.collect {|m| m.roles}.flatten.uniq
464 465 roles << (self.logged? ? Role.non_member : Role.anonymous)
465 466 roles.detect {|role|
466 467 role.allowed_to?(action) &&
467 468 (block_given? ? yield(role, self) : true)
468 469 }
469 470 else
470 471 false
471 472 end
472 473 end
473 474
474 475 # Is the user allowed to do the specified action on any project?
475 476 # See allowed_to? for the actions and valid options.
476 477 def allowed_to_globally?(action, options, &block)
477 478 allowed_to?(action, nil, options.reverse_merge(:global => true), &block)
478 479 end
479 480
480 481 safe_attributes 'login',
481 482 'firstname',
482 483 'lastname',
483 484 'mail',
484 485 'mail_notification',
485 486 'language',
486 487 'custom_field_values',
487 488 'custom_fields',
488 489 'identity_url'
489 490
490 491 safe_attributes 'status',
491 492 'auth_source_id',
492 493 :if => lambda {|user, current_user| current_user.admin?}
493 494
494 495 safe_attributes 'group_ids',
495 496 :if => lambda {|user, current_user| current_user.admin? && !user.new_record?}
496 497
497 498 # Utility method to help check if a user should be notified about an
498 499 # event.
499 500 #
500 501 # TODO: only supports Issue events currently
501 502 def notify_about?(object)
502 503 case mail_notification
503 504 when 'all'
504 505 true
505 506 when 'selected'
506 507 # user receives notifications for created/assigned issues on unselected projects
507 508 if object.is_a?(Issue) && (object.author == self || is_or_belongs_to?(object.assigned_to) || is_or_belongs_to?(object.assigned_to_was))
508 509 true
509 510 else
510 511 false
511 512 end
512 513 when 'none'
513 514 false
514 515 when 'only_my_events'
515 516 if object.is_a?(Issue) && (object.author == self || is_or_belongs_to?(object.assigned_to) || is_or_belongs_to?(object.assigned_to_was))
516 517 true
517 518 else
518 519 false
519 520 end
520 521 when 'only_assigned'
521 522 if object.is_a?(Issue) && (is_or_belongs_to?(object.assigned_to) || is_or_belongs_to?(object.assigned_to_was))
522 523 true
523 524 else
524 525 false
525 526 end
526 527 when 'only_owner'
527 528 if object.is_a?(Issue) && object.author == self
528 529 true
529 530 else
530 531 false
531 532 end
532 533 else
533 534 false
534 535 end
535 536 end
536 537
537 538 def self.current=(user)
538 539 @current_user = user
539 540 end
540 541
541 542 def self.current
542 543 @current_user ||= User.anonymous
543 544 end
544 545
545 546 # Returns the anonymous user. If the anonymous user does not exist, it is created. There can be only
546 547 # one anonymous user per database.
547 548 def self.anonymous
548 549 anonymous_user = AnonymousUser.find(:first)
549 550 if anonymous_user.nil?
550 551 anonymous_user = AnonymousUser.create(:lastname => 'Anonymous', :firstname => '', :mail => '', :login => '', :status => 0)
551 552 raise 'Unable to create the anonymous user.' if anonymous_user.new_record?
552 553 end
553 554 anonymous_user
554 555 end
555 556
556 557 # Salts all existing unsalted passwords
557 558 # It changes password storage scheme from SHA1(password) to SHA1(salt + SHA1(password))
558 559 # This method is used in the SaltPasswords migration and is to be kept as is
559 560 def self.salt_unsalted_passwords!
560 561 transaction do
561 562 User.find_each(:conditions => "salt IS NULL OR salt = ''") do |user|
562 563 next if user.hashed_password.blank?
563 564 salt = User.generate_salt
564 565 hashed_password = User.hash_password("#{salt}#{user.hashed_password}")
565 566 User.update_all("salt = '#{salt}', hashed_password = '#{hashed_password}'", ["id = ?", user.id] )
566 567 end
567 568 end
568 569 end
569 570
570 571 protected
571 572
572 573 def validate_password_length
573 574 # Password length validation based on setting
574 575 if !password.nil? && password.size < Setting.password_min_length.to_i
575 576 errors.add(:password, :too_short, :count => Setting.password_min_length.to_i)
576 577 end
577 578 end
578 579
579 580 private
580 581
581 582 # Removes references that are not handled by associations
582 583 # Things that are not deleted are reassociated with the anonymous user
583 584 def remove_references_before_destroy
584 585 return if self.id.nil?
585 586
586 587 substitute = User.anonymous
587 588 Attachment.update_all ['author_id = ?', substitute.id], ['author_id = ?', id]
588 589 Comment.update_all ['author_id = ?', substitute.id], ['author_id = ?', id]
589 590 Issue.update_all ['author_id = ?', substitute.id], ['author_id = ?', id]
590 591 Issue.update_all 'assigned_to_id = NULL', ['assigned_to_id = ?', id]
591 592 Journal.update_all ['user_id = ?', substitute.id], ['user_id = ?', id]
592 593 JournalDetail.update_all ['old_value = ?', substitute.id.to_s], ["property = 'attr' AND prop_key = 'assigned_to_id' AND old_value = ?", id.to_s]
593 594 JournalDetail.update_all ['value = ?', substitute.id.to_s], ["property = 'attr' AND prop_key = 'assigned_to_id' AND value = ?", id.to_s]
594 595 Message.update_all ['author_id = ?', substitute.id], ['author_id = ?', id]
595 596 News.update_all ['author_id = ?', substitute.id], ['author_id = ?', id]
596 597 # Remove private queries and keep public ones
597 598 ::Query.delete_all ['user_id = ? AND is_public = ?', id, false]
598 599 ::Query.update_all ['user_id = ?', substitute.id], ['user_id = ?', id]
599 600 TimeEntry.update_all ['user_id = ?', substitute.id], ['user_id = ?', id]
600 601 Token.delete_all ['user_id = ?', id]
601 602 Watcher.delete_all ['user_id = ?', id]
602 603 WikiContent.update_all ['author_id = ?', substitute.id], ['author_id = ?', id]
603 604 WikiContent::Version.update_all ['author_id = ?', substitute.id], ['author_id = ?', id]
604 605 end
605 606
606 607 # Return password digest
607 608 def self.hash_password(clear_password)
608 609 Digest::SHA1.hexdigest(clear_password || "")
609 610 end
610 611
611 612 # Returns a 128bits random salt as a hex string (32 chars long)
612 613 def self.generate_salt
613 614 ActiveSupport::SecureRandom.hex(16)
614 615 end
615 616
616 617 end
617 618
618 619 class AnonymousUser < User
619 620 validate :validate_anonymous_uniqueness, :on => :create
620 621
621 622 def validate_anonymous_uniqueness
622 623 # There should be only one AnonymousUser in the database
623 624 errors.add :base, 'An anonymous user already exists.' if AnonymousUser.find(:first)
624 625 end
625 626
626 627 def available_custom_fields
627 628 []
628 629 end
629 630
630 631 # Overrides a few properties
631 632 def logged?; false end
632 633 def admin; false end
633 634 def name(*args); I18n.t(:label_user_anonymous) end
634 635 def mail; nil end
635 636 def time_zone; nil end
636 637 def rss_key; nil end
637 638
638 639 # Anonymous user can not be destroyed
639 640 def destroy
640 641 false
641 642 end
642 643 end
General Comments 0
You need to be logged in to leave comments. Login now