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