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