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