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