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