##// END OF EJS Templates
Test broken by r14265 (#19458)....
Jean-Philippe Lang -
r13884:738ecf13654c
parent child
Show More
@@ -1,845 +1,846
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 133 joins(:email_addresses).where("LOWER(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 if Setting.password_max_age.to_i.zero?
328 period = Setting.password_max_age.to_i
329 if period.zero?
329 330 false
330 331 else
331 332 changed_on = self.passwd_changed_on || Time.at(0)
332 333 changed_on < period.days.ago
333 334 end
334 335 end
335 336
336 337 def must_change_password?
337 338 (must_change_passwd? || password_expired?) && change_password_allowed?
338 339 end
339 340
340 341 def generate_password?
341 342 generate_password == '1' || generate_password == true
342 343 end
343 344
344 345 # Generate and set a random password on given length
345 346 def random_password(length=40)
346 347 chars = ("a".."z").to_a + ("A".."Z").to_a + ("0".."9").to_a
347 348 chars -= %w(0 O 1 l)
348 349 password = ''
349 350 length.times {|i| password << chars[SecureRandom.random_number(chars.size)] }
350 351 self.password = password
351 352 self.password_confirmation = password
352 353 self
353 354 end
354 355
355 356 def pref
356 357 self.preference ||= UserPreference.new(:user => self)
357 358 end
358 359
359 360 def time_zone
360 361 @time_zone ||= (self.pref.time_zone.blank? ? nil : ActiveSupport::TimeZone[self.pref.time_zone])
361 362 end
362 363
363 364 def force_default_language?
364 365 Setting.force_default_language_for_loggedin?
365 366 end
366 367
367 368 def language
368 369 if force_default_language?
369 370 Setting.default_language
370 371 else
371 372 super
372 373 end
373 374 end
374 375
375 376 def wants_comments_in_reverse_order?
376 377 self.pref[:comments_sorting] == 'desc'
377 378 end
378 379
379 380 # Return user's RSS key (a 40 chars long string), used to access feeds
380 381 def rss_key
381 382 if rss_token.nil?
382 383 create_rss_token(:action => 'feeds')
383 384 end
384 385 rss_token.value
385 386 end
386 387
387 388 # Return user's API key (a 40 chars long string), used to access the API
388 389 def api_key
389 390 if api_token.nil?
390 391 create_api_token(:action => 'api')
391 392 end
392 393 api_token.value
393 394 end
394 395
395 396 # Return an array of project ids for which the user has explicitly turned mail notifications on
396 397 def notified_projects_ids
397 398 @notified_projects_ids ||= memberships.select {|m| m.mail_notification?}.collect(&:project_id)
398 399 end
399 400
400 401 def notified_project_ids=(ids)
401 402 @notified_projects_ids_changed = true
402 403 @notified_projects_ids = ids.map(&:to_i).uniq.select {|n| n > 0}
403 404 end
404 405
405 406 # Updates per project notifications (after_save callback)
406 407 def update_notified_project_ids
407 408 if @notified_projects_ids_changed
408 409 ids = (mail_notification == 'selected' ? Array.wrap(notified_projects_ids).reject(&:blank?) : [])
409 410 members.update_all(:mail_notification => false)
410 411 members.where(:project_id => ids).update_all(:mail_notification => true) if ids.any?
411 412 end
412 413 end
413 414 private :update_notified_project_ids
414 415
415 416 def valid_notification_options
416 417 self.class.valid_notification_options(self)
417 418 end
418 419
419 420 # Only users that belong to more than 1 project can select projects for which they are notified
420 421 def self.valid_notification_options(user=nil)
421 422 # Note that @user.membership.size would fail since AR ignores
422 423 # :include association option when doing a count
423 424 if user.nil? || user.memberships.length < 1
424 425 MAIL_NOTIFICATION_OPTIONS.reject {|option| option.first == 'selected'}
425 426 else
426 427 MAIL_NOTIFICATION_OPTIONS
427 428 end
428 429 end
429 430
430 431 # Find a user account by matching the exact login and then a case-insensitive
431 432 # version. Exact matches will be given priority.
432 433 def self.find_by_login(login)
433 434 login = Redmine::CodesetUtil.replace_invalid_utf8(login.to_s)
434 435 if login.present?
435 436 # First look for an exact match
436 437 user = where(:login => login).detect {|u| u.login == login}
437 438 unless user
438 439 # Fail over to case-insensitive if none was found
439 440 user = where("LOWER(login) = ?", login.downcase).first
440 441 end
441 442 user
442 443 end
443 444 end
444 445
445 446 def self.find_by_rss_key(key)
446 447 Token.find_active_user('feeds', key)
447 448 end
448 449
449 450 def self.find_by_api_key(key)
450 451 Token.find_active_user('api', key)
451 452 end
452 453
453 454 # Makes find_by_mail case-insensitive
454 455 def self.find_by_mail(mail)
455 456 having_mail(mail).first
456 457 end
457 458
458 459 # Returns true if the default admin account can no longer be used
459 460 def self.default_admin_account_changed?
460 461 !User.active.find_by_login("admin").try(:check_password?, "admin")
461 462 end
462 463
463 464 def to_s
464 465 name
465 466 end
466 467
467 468 CSS_CLASS_BY_STATUS = {
468 469 STATUS_ANONYMOUS => 'anon',
469 470 STATUS_ACTIVE => 'active',
470 471 STATUS_REGISTERED => 'registered',
471 472 STATUS_LOCKED => 'locked'
472 473 }
473 474
474 475 def css_classes
475 476 "user #{CSS_CLASS_BY_STATUS[status]}"
476 477 end
477 478
478 479 # Returns the current day according to user's time zone
479 480 def today
480 481 if time_zone.nil?
481 482 Date.today
482 483 else
483 484 Time.now.in_time_zone(time_zone).to_date
484 485 end
485 486 end
486 487
487 488 # Returns the day of +time+ according to user's time zone
488 489 def time_to_date(time)
489 490 if time_zone.nil?
490 491 time.to_date
491 492 else
492 493 time.in_time_zone(time_zone).to_date
493 494 end
494 495 end
495 496
496 497 def logged?
497 498 true
498 499 end
499 500
500 501 def anonymous?
501 502 !logged?
502 503 end
503 504
504 505 # Returns user's membership for the given project
505 506 # or nil if the user is not a member of project
506 507 def membership(project)
507 508 project_id = project.is_a?(Project) ? project.id : project
508 509
509 510 @membership_by_project_id ||= Hash.new {|h, project_id|
510 511 h[project_id] = memberships.where(:project_id => project_id).first
511 512 }
512 513 @membership_by_project_id[project_id]
513 514 end
514 515
515 516 # Returns the user's bult-in role
516 517 def builtin_role
517 518 @builtin_role ||= Role.non_member
518 519 end
519 520
520 521 # Return user's roles for project
521 522 def roles_for_project(project)
522 523 # No role on archived projects
523 524 return [] if project.nil? || project.archived?
524 525 if membership = membership(project)
525 526 membership.roles.dup
526 527 elsif project.is_public?
527 528 project.override_roles(builtin_role)
528 529 else
529 530 []
530 531 end
531 532 end
532 533
533 534 # Returns a hash of user's projects grouped by roles
534 535 def projects_by_role
535 536 return @projects_by_role if @projects_by_role
536 537
537 538 hash = Hash.new([])
538 539
539 540 group_class = anonymous? ? GroupAnonymous : GroupNonMember
540 541 members = Member.joins(:project, :principal).
541 542 where("#{Project.table_name}.status <> 9").
542 543 where("#{Member.table_name}.user_id = ? OR (#{Project.table_name}.is_public = ? AND #{Principal.table_name}.type = ?)", self.id, true, group_class.name).
543 544 preload(:project, :roles).
544 545 to_a
545 546
546 547 members.reject! {|member| member.user_id != id && project_ids.include?(member.project_id)}
547 548 members.each do |member|
548 549 if member.project
549 550 member.roles.each do |role|
550 551 hash[role] = [] unless hash.key?(role)
551 552 hash[role] << member.project
552 553 end
553 554 end
554 555 end
555 556
556 557 hash.each do |role, projects|
557 558 projects.uniq!
558 559 end
559 560
560 561 @projects_by_role = hash
561 562 end
562 563
563 564 # Returns the ids of visible projects
564 565 def visible_project_ids
565 566 @visible_project_ids ||= Project.visible(self).pluck(:id)
566 567 end
567 568
568 569 # Returns true if user is arg or belongs to arg
569 570 def is_or_belongs_to?(arg)
570 571 if arg.is_a?(User)
571 572 self == arg
572 573 elsif arg.is_a?(Group)
573 574 arg.users.include?(self)
574 575 else
575 576 false
576 577 end
577 578 end
578 579
579 580 # Return true if the user is allowed to do the specified action on a specific context
580 581 # Action can be:
581 582 # * a parameter-like Hash (eg. :controller => 'projects', :action => 'edit')
582 583 # * a permission Symbol (eg. :edit_project)
583 584 # Context can be:
584 585 # * a project : returns true if user is allowed to do the specified action on this project
585 586 # * an array of projects : returns true if user is allowed on every project
586 587 # * nil with options[:global] set : check if user has at least one role allowed for this action,
587 588 # or falls back to Non Member / Anonymous permissions depending if the user is logged
588 589 def allowed_to?(action, context, options={}, &block)
589 590 if context && context.is_a?(Project)
590 591 return false unless context.allows_to?(action)
591 592 # Admin users are authorized for anything else
592 593 return true if admin?
593 594
594 595 roles = roles_for_project(context)
595 596 return false unless roles
596 597 roles.any? {|role|
597 598 (context.is_public? || role.member?) &&
598 599 role.allowed_to?(action) &&
599 600 (block_given? ? yield(role, self) : true)
600 601 }
601 602 elsif context && context.is_a?(Array)
602 603 if context.empty?
603 604 false
604 605 else
605 606 # Authorize if user is authorized on every element of the array
606 607 context.map {|project| allowed_to?(action, project, options, &block)}.reduce(:&)
607 608 end
608 609 elsif context
609 610 raise ArgumentError.new("#allowed_to? context argument must be a Project, an Array of projects or nil")
610 611 elsif options[:global]
611 612 # Admin users are always authorized
612 613 return true if admin?
613 614
614 615 # authorize if user has at least one role that has this permission
615 616 roles = memberships.collect {|m| m.roles}.flatten.uniq
616 617 roles << (self.logged? ? Role.non_member : Role.anonymous)
617 618 roles.any? {|role|
618 619 role.allowed_to?(action) &&
619 620 (block_given? ? yield(role, self) : true)
620 621 }
621 622 else
622 623 false
623 624 end
624 625 end
625 626
626 627 # Is the user allowed to do the specified action on any project?
627 628 # See allowed_to? for the actions and valid options.
628 629 #
629 630 # NB: this method is not used anywhere in the core codebase as of
630 631 # 2.5.2, but it's used by many plugins so if we ever want to remove
631 632 # it it has to be carefully deprecated for a version or two.
632 633 def allowed_to_globally?(action, options={}, &block)
633 634 allowed_to?(action, nil, options.reverse_merge(:global => true), &block)
634 635 end
635 636
636 637 # Returns true if the user is allowed to delete the user's own account
637 638 def own_account_deletable?
638 639 Setting.unsubscribe? &&
639 640 (!admin? || User.active.where("admin = ? AND id <> ?", true, id).exists?)
640 641 end
641 642
642 643 safe_attributes 'login',
643 644 'firstname',
644 645 'lastname',
645 646 'mail',
646 647 'mail_notification',
647 648 'notified_project_ids',
648 649 'language',
649 650 'custom_field_values',
650 651 'custom_fields',
651 652 'identity_url'
652 653
653 654 safe_attributes 'status',
654 655 'auth_source_id',
655 656 'generate_password',
656 657 'must_change_passwd',
657 658 :if => lambda {|user, current_user| current_user.admin?}
658 659
659 660 safe_attributes 'group_ids',
660 661 :if => lambda {|user, current_user| current_user.admin? && !user.new_record?}
661 662
662 663 # Utility method to help check if a user should be notified about an
663 664 # event.
664 665 #
665 666 # TODO: only supports Issue events currently
666 667 def notify_about?(object)
667 668 if mail_notification == 'all'
668 669 true
669 670 elsif mail_notification.blank? || mail_notification == 'none'
670 671 false
671 672 else
672 673 case object
673 674 when Issue
674 675 case mail_notification
675 676 when 'selected', 'only_my_events'
676 677 # user receives notifications for created/assigned issues on unselected projects
677 678 object.author == self || is_or_belongs_to?(object.assigned_to) || is_or_belongs_to?(object.assigned_to_was)
678 679 when 'only_assigned'
679 680 is_or_belongs_to?(object.assigned_to) || is_or_belongs_to?(object.assigned_to_was)
680 681 when 'only_owner'
681 682 object.author == self
682 683 end
683 684 when News
684 685 # always send to project members except when mail_notification is set to 'none'
685 686 true
686 687 end
687 688 end
688 689 end
689 690
690 691 def self.current=(user)
691 692 RequestStore.store[:current_user] = user
692 693 end
693 694
694 695 def self.current
695 696 RequestStore.store[:current_user] ||= User.anonymous
696 697 end
697 698
698 699 # Returns the anonymous user. If the anonymous user does not exist, it is created. There can be only
699 700 # one anonymous user per database.
700 701 def self.anonymous
701 702 anonymous_user = AnonymousUser.first
702 703 if anonymous_user.nil?
703 704 anonymous_user = AnonymousUser.create(:lastname => 'Anonymous', :firstname => '', :login => '', :status => 0)
704 705 raise 'Unable to create the anonymous user.' if anonymous_user.new_record?
705 706 end
706 707 anonymous_user
707 708 end
708 709
709 710 # Salts all existing unsalted passwords
710 711 # It changes password storage scheme from SHA1(password) to SHA1(salt + SHA1(password))
711 712 # This method is used in the SaltPasswords migration and is to be kept as is
712 713 def self.salt_unsalted_passwords!
713 714 transaction do
714 715 User.where("salt IS NULL OR salt = ''").find_each do |user|
715 716 next if user.hashed_password.blank?
716 717 salt = User.generate_salt
717 718 hashed_password = User.hash_password("#{salt}#{user.hashed_password}")
718 719 User.where(:id => user.id).update_all(:salt => salt, :hashed_password => hashed_password)
719 720 end
720 721 end
721 722 end
722 723
723 724 protected
724 725
725 726 def validate_password_length
726 727 return if password.blank? && generate_password?
727 728 # Password length validation based on setting
728 729 if !password.nil? && password.size < Setting.password_min_length.to_i
729 730 errors.add(:password, :too_short, :count => Setting.password_min_length.to_i)
730 731 end
731 732 end
732 733
733 734 def instantiate_email_address
734 735 email_address || build_email_address
735 736 end
736 737
737 738 private
738 739
739 740 def generate_password_if_needed
740 741 if generate_password? && auth_source.nil?
741 742 length = [Setting.password_min_length.to_i + 2, 10].max
742 743 random_password(length)
743 744 end
744 745 end
745 746
746 747 # Delete all outstanding password reset tokens on password change.
747 748 # Delete the autologin tokens on password change to prohibit session leakage.
748 749 # This helps to keep the account secure in case the associated email account
749 750 # was compromised.
750 751 def destroy_tokens
751 752 if hashed_password_changed?
752 753 tokens = ['recovery', 'autologin']
753 754 Token.where(:user_id => id, :action => tokens).delete_all
754 755 end
755 756 end
756 757
757 758 # Removes references that are not handled by associations
758 759 # Things that are not deleted are reassociated with the anonymous user
759 760 def remove_references_before_destroy
760 761 return if self.id.nil?
761 762
762 763 substitute = User.anonymous
763 764 Attachment.where(['author_id = ?', id]).update_all(['author_id = ?', substitute.id])
764 765 Comment.where(['author_id = ?', id]).update_all(['author_id = ?', substitute.id])
765 766 Issue.where(['author_id = ?', id]).update_all(['author_id = ?', substitute.id])
766 767 Issue.where(['assigned_to_id = ?', id]).update_all('assigned_to_id = NULL')
767 768 Journal.where(['user_id = ?', id]).update_all(['user_id = ?', substitute.id])
768 769 JournalDetail.
769 770 where(["property = 'attr' AND prop_key = 'assigned_to_id' AND old_value = ?", id.to_s]).
770 771 update_all(['old_value = ?', substitute.id.to_s])
771 772 JournalDetail.
772 773 where(["property = 'attr' AND prop_key = 'assigned_to_id' AND value = ?", id.to_s]).
773 774 update_all(['value = ?', substitute.id.to_s])
774 775 Message.where(['author_id = ?', id]).update_all(['author_id = ?', substitute.id])
775 776 News.where(['author_id = ?', id]).update_all(['author_id = ?', substitute.id])
776 777 # Remove private queries and keep public ones
777 778 ::Query.delete_all ['user_id = ? AND visibility = ?', id, ::Query::VISIBILITY_PRIVATE]
778 779 ::Query.where(['user_id = ?', id]).update_all(['user_id = ?', substitute.id])
779 780 TimeEntry.where(['user_id = ?', id]).update_all(['user_id = ?', substitute.id])
780 781 Token.delete_all ['user_id = ?', id]
781 782 Watcher.delete_all ['user_id = ?', id]
782 783 WikiContent.where(['author_id = ?', id]).update_all(['author_id = ?', substitute.id])
783 784 WikiContent::Version.where(['author_id = ?', id]).update_all(['author_id = ?', substitute.id])
784 785 end
785 786
786 787 # Return password digest
787 788 def self.hash_password(clear_password)
788 789 Digest::SHA1.hexdigest(clear_password || "")
789 790 end
790 791
791 792 # Returns a 128bits random salt as a hex string (32 chars long)
792 793 def self.generate_salt
793 794 Redmine::Utils.random_hex(16)
794 795 end
795 796
796 797 end
797 798
798 799 class AnonymousUser < User
799 800 validate :validate_anonymous_uniqueness, :on => :create
800 801
801 802 def validate_anonymous_uniqueness
802 803 # There should be only one AnonymousUser in the database
803 804 errors.add :base, 'An anonymous user already exists.' if AnonymousUser.exists?
804 805 end
805 806
806 807 def available_custom_fields
807 808 []
808 809 end
809 810
810 811 # Overrides a few properties
811 812 def logged?; false end
812 813 def admin; false end
813 814 def name(*args); I18n.t(:label_user_anonymous) end
814 815 def mail=(*args); nil end
815 816 def mail; nil end
816 817 def time_zone; nil end
817 818 def rss_key; nil end
818 819
819 820 def pref
820 821 UserPreference.new(:user => self)
821 822 end
822 823
823 824 # Returns the user's bult-in role
824 825 def builtin_role
825 826 @builtin_role ||= Role.anonymous
826 827 end
827 828
828 829 def membership(*args)
829 830 nil
830 831 end
831 832
832 833 def member_of?(*args)
833 834 false
834 835 end
835 836
836 837 # Anonymous user can not be destroyed
837 838 def destroy
838 839 false
839 840 end
840 841
841 842 protected
842 843
843 844 def instantiate_email_address
844 845 end
845 846 end
General Comments 0
You need to be logged in to leave comments. Login now