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