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