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