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