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