##// END OF EJS Templates
Fixes r9029....
Jean-Philippe Lang -
r8910:e1d1a3a0c2e5
parent child
Show More
@@ -1,643 +1,642
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 matches = find_all_by_login(login)
136 user = (matches.size < 2 ? matches.first : matches.detect {|u| u.login == login})
135 user = find_by_login(login)
137 136 if user
138 137 # user is already in local database
139 138 return nil if !user.active?
140 139 if user.auth_source
141 140 # user has an external authentication method
142 141 return nil unless user.auth_source.authenticate(login, password)
143 142 else
144 143 # authentication with local password
145 144 return nil unless user.check_password?(password)
146 145 end
147 146 else
148 147 # user is not yet registered, try to authenticate with available sources
149 148 attrs = AuthSource.authenticate(login, password)
150 149 if attrs
151 150 user = new(attrs)
152 151 user.login = login
153 152 user.language = Setting.default_language
154 153 if user.save
155 154 user.reload
156 155 logger.info("User '#{user.login}' created from external auth source: #{user.auth_source.type} - #{user.auth_source.name}") if logger && user.auth_source
157 156 end
158 157 end
159 158 end
160 159 user.update_attribute(:last_login_on, Time.now) if user && !user.new_record?
161 160 user
162 161 rescue => text
163 162 raise text
164 163 end
165 164
166 165 # Returns the user who matches the given autologin +key+ or nil
167 166 def self.try_to_autologin(key)
168 167 tokens = Token.find_all_by_action_and_value('autologin', key)
169 168 # Make sure there's only 1 token that matches the key
170 169 if tokens.size == 1
171 170 token = tokens.first
172 171 if (token.created_on > Setting.autologin.to_i.day.ago) && token.user && token.user.active?
173 172 token.user.update_attribute(:last_login_on, Time.now)
174 173 token.user
175 174 end
176 175 end
177 176 end
178 177
179 178 def self.name_formatter(formatter = nil)
180 179 USER_FORMATS[formatter || Setting.user_format] || USER_FORMATS[:firstname_lastname]
181 180 end
182 181
183 182 # Returns an array of fields names than can be used to make an order statement for users
184 183 # according to how user names are displayed
185 184 # Examples:
186 185 #
187 186 # User.fields_for_order_statement => ['users.login', 'users.id']
188 187 # User.fields_for_order_statement('authors') => ['authors.login', 'authors.id']
189 188 def self.fields_for_order_statement(table=nil)
190 189 table ||= table_name
191 190 name_formatter[:order].map {|field| "#{table}.#{field}"}
192 191 end
193 192
194 193 # Return user's full name for display
195 194 def name(formatter = nil)
196 195 f = self.class.name_formatter(formatter)
197 196 if formatter
198 197 eval('"' + f[:string] + '"')
199 198 else
200 199 @name ||= eval('"' + f[:string] + '"')
201 200 end
202 201 end
203 202
204 203 def active?
205 204 self.status == STATUS_ACTIVE
206 205 end
207 206
208 207 def registered?
209 208 self.status == STATUS_REGISTERED
210 209 end
211 210
212 211 def locked?
213 212 self.status == STATUS_LOCKED
214 213 end
215 214
216 215 def activate
217 216 self.status = STATUS_ACTIVE
218 217 end
219 218
220 219 def register
221 220 self.status = STATUS_REGISTERED
222 221 end
223 222
224 223 def lock
225 224 self.status = STATUS_LOCKED
226 225 end
227 226
228 227 def activate!
229 228 update_attribute(:status, STATUS_ACTIVE)
230 229 end
231 230
232 231 def register!
233 232 update_attribute(:status, STATUS_REGISTERED)
234 233 end
235 234
236 235 def lock!
237 236 update_attribute(:status, STATUS_LOCKED)
238 237 end
239 238
240 239 # Returns true if +clear_password+ is the correct user's password, otherwise false
241 240 def check_password?(clear_password)
242 241 if auth_source_id.present?
243 242 auth_source.authenticate(self.login, clear_password)
244 243 else
245 244 User.hash_password("#{salt}#{User.hash_password clear_password}") == hashed_password
246 245 end
247 246 end
248 247
249 248 # Generates a random salt and computes hashed_password for +clear_password+
250 249 # The hashed password is stored in the following form: SHA1(salt + SHA1(password))
251 250 def salt_password(clear_password)
252 251 self.salt = User.generate_salt
253 252 self.hashed_password = User.hash_password("#{salt}#{User.hash_password clear_password}")
254 253 end
255 254
256 255 # Does the backend storage allow this user to change their password?
257 256 def change_password_allowed?
258 257 return true if auth_source.nil?
259 258 return auth_source.allow_password_changes?
260 259 end
261 260
262 261 # Generate and set a random password. Useful for automated user creation
263 262 # Based on Token#generate_token_value
264 263 #
265 264 def random_password
266 265 chars = ("a".."z").to_a + ("A".."Z").to_a + ("0".."9").to_a
267 266 password = ''
268 267 40.times { |i| password << chars[rand(chars.size-1)] }
269 268 self.password = password
270 269 self.password_confirmation = password
271 270 self
272 271 end
273 272
274 273 def pref
275 274 self.preference ||= UserPreference.new(:user => self)
276 275 end
277 276
278 277 def time_zone
279 278 @time_zone ||= (self.pref.time_zone.blank? ? nil : ActiveSupport::TimeZone[self.pref.time_zone])
280 279 end
281 280
282 281 def wants_comments_in_reverse_order?
283 282 self.pref[:comments_sorting] == 'desc'
284 283 end
285 284
286 285 # Return user's RSS key (a 40 chars long string), used to access feeds
287 286 def rss_key
288 287 token = self.rss_token || Token.create(:user => self, :action => 'feeds')
289 288 token.value
290 289 end
291 290
292 291 # Return user's API key (a 40 chars long string), used to access the API
293 292 def api_key
294 293 token = self.api_token || self.create_api_token(:action => 'api')
295 294 token.value
296 295 end
297 296
298 297 # Return an array of project ids for which the user has explicitly turned mail notifications on
299 298 def notified_projects_ids
300 299 @notified_projects_ids ||= memberships.select {|m| m.mail_notification?}.collect(&:project_id)
301 300 end
302 301
303 302 def notified_project_ids=(ids)
304 303 Member.update_all("mail_notification = #{connection.quoted_false}", ['user_id = ?', id])
305 304 Member.update_all("mail_notification = #{connection.quoted_true}", ['user_id = ? AND project_id IN (?)', id, ids]) if ids && !ids.empty?
306 305 @notified_projects_ids = nil
307 306 notified_projects_ids
308 307 end
309 308
310 309 def valid_notification_options
311 310 self.class.valid_notification_options(self)
312 311 end
313 312
314 313 # Only users that belong to more than 1 project can select projects for which they are notified
315 314 def self.valid_notification_options(user=nil)
316 315 # Note that @user.membership.size would fail since AR ignores
317 316 # :include association option when doing a count
318 317 if user.nil? || user.memberships.length < 1
319 318 MAIL_NOTIFICATION_OPTIONS.reject {|option| option.first == 'selected'}
320 319 else
321 320 MAIL_NOTIFICATION_OPTIONS
322 321 end
323 322 end
324 323
325 324 # Find a user account by matching the exact login and then a case-insensitive
326 325 # version. Exact matches will be given priority.
327 326 def self.find_by_login(login)
328 # force string comparison to be case sensitive on MySQL
329 type_cast = (ActiveRecord::Base.connection.adapter_name == 'MySQL') ? 'BINARY' : ''
330
331 327 # First look for an exact match
332 user = first(:conditions => ["#{type_cast} login = ?", login])
333 # Fail over to case-insensitive if none was found
334 user ||= first(:conditions => ["#{type_cast} LOWER(login) = ?", login.to_s.downcase])
328 user = all(:conditions => {:login => login}).detect {|u| u.login == login}
329 unless user
330 # Fail over to case-insensitive if none was found
331 user = first(:conditions => ["LOWER(login) = ?", login.to_s.downcase])
332 end
333 user
335 334 end
336 335
337 336 def self.find_by_rss_key(key)
338 337 token = Token.find_by_value(key)
339 338 token && token.user.active? ? token.user : nil
340 339 end
341 340
342 341 def self.find_by_api_key(key)
343 342 token = Token.find_by_action_and_value('api', key)
344 343 token && token.user.active? ? token.user : nil
345 344 end
346 345
347 346 # Makes find_by_mail case-insensitive
348 347 def self.find_by_mail(mail)
349 348 find(:first, :conditions => ["LOWER(mail) = ?", mail.to_s.downcase])
350 349 end
351 350
352 351 def to_s
353 352 name
354 353 end
355 354
356 355 # Returns the current day according to user's time zone
357 356 def today
358 357 if time_zone.nil?
359 358 Date.today
360 359 else
361 360 Time.now.in_time_zone(time_zone).to_date
362 361 end
363 362 end
364 363
365 364 def logged?
366 365 true
367 366 end
368 367
369 368 def anonymous?
370 369 !logged?
371 370 end
372 371
373 372 # Return user's roles for project
374 373 def roles_for_project(project)
375 374 roles = []
376 375 # No role on archived projects
377 376 return roles unless project && project.active?
378 377 if logged?
379 378 # Find project membership
380 379 membership = memberships.detect {|m| m.project_id == project.id}
381 380 if membership
382 381 roles = membership.roles
383 382 else
384 383 @role_non_member ||= Role.non_member
385 384 roles << @role_non_member
386 385 end
387 386 else
388 387 @role_anonymous ||= Role.anonymous
389 388 roles << @role_anonymous
390 389 end
391 390 roles
392 391 end
393 392
394 393 # Return true if the user is a member of project
395 394 def member_of?(project)
396 395 !roles_for_project(project).detect {|role| role.member?}.nil?
397 396 end
398 397
399 398 # Returns a hash of user's projects grouped by roles
400 399 def projects_by_role
401 400 return @projects_by_role if @projects_by_role
402 401
403 402 @projects_by_role = Hash.new {|h,k| h[k]=[]}
404 403 memberships.each do |membership|
405 404 membership.roles.each do |role|
406 405 @projects_by_role[role] << membership.project if membership.project
407 406 end
408 407 end
409 408 @projects_by_role.each do |role, projects|
410 409 projects.uniq!
411 410 end
412 411
413 412 @projects_by_role
414 413 end
415 414
416 415 # Returns true if user is arg or belongs to arg
417 416 def is_or_belongs_to?(arg)
418 417 if arg.is_a?(User)
419 418 self == arg
420 419 elsif arg.is_a?(Group)
421 420 arg.users.include?(self)
422 421 else
423 422 false
424 423 end
425 424 end
426 425
427 426 # Return true if the user is allowed to do the specified action on a specific context
428 427 # Action can be:
429 428 # * a parameter-like Hash (eg. :controller => 'projects', :action => 'edit')
430 429 # * a permission Symbol (eg. :edit_project)
431 430 # Context can be:
432 431 # * a project : returns true if user is allowed to do the specified action on this project
433 432 # * an array of projects : returns true if user is allowed on every project
434 433 # * nil with options[:global] set : check if user has at least one role allowed for this action,
435 434 # or falls back to Non Member / Anonymous permissions depending if the user is logged
436 435 def allowed_to?(action, context, options={}, &block)
437 436 if context && context.is_a?(Project)
438 437 # No action allowed on archived projects
439 438 return false unless context.active?
440 439 # No action allowed on disabled modules
441 440 return false unless context.allows_to?(action)
442 441 # Admin users are authorized for anything else
443 442 return true if admin?
444 443
445 444 roles = roles_for_project(context)
446 445 return false unless roles
447 446 roles.detect {|role|
448 447 (context.is_public? || role.member?) &&
449 448 role.allowed_to?(action) &&
450 449 (block_given? ? yield(role, self) : true)
451 450 }
452 451 elsif context && context.is_a?(Array)
453 452 # Authorize if user is authorized on every element of the array
454 453 context.map do |project|
455 454 allowed_to?(action, project, options, &block)
456 455 end.inject do |memo,allowed|
457 456 memo && allowed
458 457 end
459 458 elsif options[:global]
460 459 # Admin users are always authorized
461 460 return true if admin?
462 461
463 462 # authorize if user has at least one role that has this permission
464 463 roles = memberships.collect {|m| m.roles}.flatten.uniq
465 464 roles << (self.logged? ? Role.non_member : Role.anonymous)
466 465 roles.detect {|role|
467 466 role.allowed_to?(action) &&
468 467 (block_given? ? yield(role, self) : true)
469 468 }
470 469 else
471 470 false
472 471 end
473 472 end
474 473
475 474 # Is the user allowed to do the specified action on any project?
476 475 # See allowed_to? for the actions and valid options.
477 476 def allowed_to_globally?(action, options, &block)
478 477 allowed_to?(action, nil, options.reverse_merge(:global => true), &block)
479 478 end
480 479
481 480 safe_attributes 'login',
482 481 'firstname',
483 482 'lastname',
484 483 'mail',
485 484 'mail_notification',
486 485 'language',
487 486 'custom_field_values',
488 487 'custom_fields',
489 488 'identity_url'
490 489
491 490 safe_attributes 'status',
492 491 'auth_source_id',
493 492 :if => lambda {|user, current_user| current_user.admin?}
494 493
495 494 safe_attributes 'group_ids',
496 495 :if => lambda {|user, current_user| current_user.admin? && !user.new_record?}
497 496
498 497 # Utility method to help check if a user should be notified about an
499 498 # event.
500 499 #
501 500 # TODO: only supports Issue events currently
502 501 def notify_about?(object)
503 502 case mail_notification
504 503 when 'all'
505 504 true
506 505 when 'selected'
507 506 # user receives notifications for created/assigned issues on unselected projects
508 507 if object.is_a?(Issue) && (object.author == self || is_or_belongs_to?(object.assigned_to) || is_or_belongs_to?(object.assigned_to_was))
509 508 true
510 509 else
511 510 false
512 511 end
513 512 when 'none'
514 513 false
515 514 when 'only_my_events'
516 515 if object.is_a?(Issue) && (object.author == self || is_or_belongs_to?(object.assigned_to) || is_or_belongs_to?(object.assigned_to_was))
517 516 true
518 517 else
519 518 false
520 519 end
521 520 when 'only_assigned'
522 521 if object.is_a?(Issue) && (is_or_belongs_to?(object.assigned_to) || is_or_belongs_to?(object.assigned_to_was))
523 522 true
524 523 else
525 524 false
526 525 end
527 526 when 'only_owner'
528 527 if object.is_a?(Issue) && object.author == self
529 528 true
530 529 else
531 530 false
532 531 end
533 532 else
534 533 false
535 534 end
536 535 end
537 536
538 537 def self.current=(user)
539 538 @current_user = user
540 539 end
541 540
542 541 def self.current
543 542 @current_user ||= User.anonymous
544 543 end
545 544
546 545 # Returns the anonymous user. If the anonymous user does not exist, it is created. There can be only
547 546 # one anonymous user per database.
548 547 def self.anonymous
549 548 anonymous_user = AnonymousUser.find(:first)
550 549 if anonymous_user.nil?
551 550 anonymous_user = AnonymousUser.create(:lastname => 'Anonymous', :firstname => '', :mail => '', :login => '', :status => 0)
552 551 raise 'Unable to create the anonymous user.' if anonymous_user.new_record?
553 552 end
554 553 anonymous_user
555 554 end
556 555
557 556 # Salts all existing unsalted passwords
558 557 # It changes password storage scheme from SHA1(password) to SHA1(salt + SHA1(password))
559 558 # This method is used in the SaltPasswords migration and is to be kept as is
560 559 def self.salt_unsalted_passwords!
561 560 transaction do
562 561 User.find_each(:conditions => "salt IS NULL OR salt = ''") do |user|
563 562 next if user.hashed_password.blank?
564 563 salt = User.generate_salt
565 564 hashed_password = User.hash_password("#{salt}#{user.hashed_password}")
566 565 User.update_all("salt = '#{salt}', hashed_password = '#{hashed_password}'", ["id = ?", user.id] )
567 566 end
568 567 end
569 568 end
570 569
571 570 protected
572 571
573 572 def validate_password_length
574 573 # Password length validation based on setting
575 574 if !password.nil? && password.size < Setting.password_min_length.to_i
576 575 errors.add(:password, :too_short, :count => Setting.password_min_length.to_i)
577 576 end
578 577 end
579 578
580 579 private
581 580
582 581 # Removes references that are not handled by associations
583 582 # Things that are not deleted are reassociated with the anonymous user
584 583 def remove_references_before_destroy
585 584 return if self.id.nil?
586 585
587 586 substitute = User.anonymous
588 587 Attachment.update_all ['author_id = ?', substitute.id], ['author_id = ?', id]
589 588 Comment.update_all ['author_id = ?', substitute.id], ['author_id = ?', id]
590 589 Issue.update_all ['author_id = ?', substitute.id], ['author_id = ?', id]
591 590 Issue.update_all 'assigned_to_id = NULL', ['assigned_to_id = ?', id]
592 591 Journal.update_all ['user_id = ?', substitute.id], ['user_id = ?', id]
593 592 JournalDetail.update_all ['old_value = ?', substitute.id.to_s], ["property = 'attr' AND prop_key = 'assigned_to_id' AND old_value = ?", id.to_s]
594 593 JournalDetail.update_all ['value = ?', substitute.id.to_s], ["property = 'attr' AND prop_key = 'assigned_to_id' AND value = ?", id.to_s]
595 594 Message.update_all ['author_id = ?', substitute.id], ['author_id = ?', id]
596 595 News.update_all ['author_id = ?', substitute.id], ['author_id = ?', id]
597 596 # Remove private queries and keep public ones
598 597 ::Query.delete_all ['user_id = ? AND is_public = ?', id, false]
599 598 ::Query.update_all ['user_id = ?', substitute.id], ['user_id = ?', id]
600 599 TimeEntry.update_all ['user_id = ?', substitute.id], ['user_id = ?', id]
601 600 Token.delete_all ['user_id = ?', id]
602 601 Watcher.delete_all ['user_id = ?', id]
603 602 WikiContent.update_all ['author_id = ?', substitute.id], ['author_id = ?', id]
604 603 WikiContent::Version.update_all ['author_id = ?', substitute.id], ['author_id = ?', id]
605 604 end
606 605
607 606 # Return password digest
608 607 def self.hash_password(clear_password)
609 608 Digest::SHA1.hexdigest(clear_password || "")
610 609 end
611 610
612 611 # Returns a 128bits random salt as a hex string (32 chars long)
613 612 def self.generate_salt
614 613 ActiveSupport::SecureRandom.hex(16)
615 614 end
616 615
617 616 end
618 617
619 618 class AnonymousUser < User
620 619 validate :validate_anonymous_uniqueness, :on => :create
621 620
622 621 def validate_anonymous_uniqueness
623 622 # There should be only one AnonymousUser in the database
624 623 errors.add :base, 'An anonymous user already exists.' if AnonymousUser.find(:first)
625 624 end
626 625
627 626 def available_custom_fields
628 627 []
629 628 end
630 629
631 630 # Overrides a few properties
632 631 def logged?; false end
633 632 def admin; false end
634 633 def name(*args); I18n.t(:label_user_anonymous) end
635 634 def mail; nil end
636 635 def time_zone; nil end
637 636 def rss_key; nil end
638 637
639 638 # Anonymous user can not be destroyed
640 639 def destroy
641 640 false
642 641 end
643 642 end
General Comments 0
You need to be logged in to leave comments. Login now