##// END OF EJS Templates
Send a security notification when users gain or loose admin (#21421)....
Jean-Philippe Lang -
r14883:4aef2735c878
parent child
Show More
@@ -1,888 +1,917
1 1 # Redmine - project management software
2 2 # Copyright (C) 2006-2016 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 :lastnamefirstname => {
51 51 :string => '#{lastname}#{firstname}',
52 52 :order => %w(lastname firstname id),
53 53 :setting_order => 5
54 54 },
55 55 :lastname_comma_firstname => {
56 56 :string => '#{lastname}, #{firstname}',
57 57 :order => %w(lastname firstname id),
58 58 :setting_order => 6
59 59 },
60 60 :lastname => {
61 61 :string => '#{lastname}',
62 62 :order => %w(lastname id),
63 63 :setting_order => 7
64 64 },
65 65 :username => {
66 66 :string => '#{login}',
67 67 :order => %w(login id),
68 68 :setting_order => 8
69 69 },
70 70 }
71 71
72 72 MAIL_NOTIFICATION_OPTIONS = [
73 73 ['all', :label_user_mail_option_all],
74 74 ['selected', :label_user_mail_option_selected],
75 75 ['only_my_events', :label_user_mail_option_only_my_events],
76 76 ['only_assigned', :label_user_mail_option_only_assigned],
77 77 ['only_owner', :label_user_mail_option_only_owner],
78 78 ['none', :label_user_mail_option_none]
79 79 ]
80 80
81 81 has_and_belongs_to_many :groups,
82 82 :join_table => "#{table_name_prefix}groups_users#{table_name_suffix}",
83 83 :after_add => Proc.new {|user, group| group.user_added(user)},
84 84 :after_remove => Proc.new {|user, group| group.user_removed(user)}
85 85 has_many :changesets, :dependent => :nullify
86 86 has_one :preference, :dependent => :destroy, :class_name => 'UserPreference'
87 87 has_one :rss_token, lambda {where "action='feeds'"}, :class_name => 'Token'
88 88 has_one :api_token, lambda {where "action='api'"}, :class_name => 'Token'
89 89 has_one :email_address, lambda {where :is_default => true}, :autosave => true
90 90 has_many :email_addresses, :dependent => :delete_all
91 91 belongs_to :auth_source
92 92
93 93 scope :logged, lambda { where("#{User.table_name}.status <> #{STATUS_ANONYMOUS}") }
94 94 scope :status, lambda {|arg| where(arg.blank? ? nil : {:status => arg.to_i}) }
95 95
96 96 acts_as_customizable
97 97
98 98 attr_accessor :password, :password_confirmation, :generate_password
99 99 attr_accessor :last_before_login_on
100 100 attr_accessor :remote_ip
101 101
102 102 # Prevents unauthorized assignments
103 103 attr_protected :login, :admin, :password, :password_confirmation, :hashed_password
104 104
105 105 LOGIN_LENGTH_LIMIT = 60
106 106 MAIL_LENGTH_LIMIT = 60
107 107
108 108 validates_presence_of :login, :firstname, :lastname, :if => Proc.new { |user| !user.is_a?(AnonymousUser) }
109 109 validates_uniqueness_of :login, :if => Proc.new { |user| user.login_changed? && user.login.present? }, :case_sensitive => false
110 110 # Login must contain letters, numbers, underscores only
111 111 validates_format_of :login, :with => /\A[a-z0-9_\-@\.]*\z/i
112 112 validates_length_of :login, :maximum => LOGIN_LENGTH_LIMIT
113 113 validates_length_of :firstname, :lastname, :maximum => 30
114 114 validates_inclusion_of :mail_notification, :in => MAIL_NOTIFICATION_OPTIONS.collect(&:first), :allow_blank => true
115 115 validate :validate_password_length
116 116 validate do
117 117 if password_confirmation && password != password_confirmation
118 118 errors.add(:password, :confirmation)
119 119 end
120 120 end
121 121
122 122 before_validation :instantiate_email_address
123 123 before_create :set_mail_notification
124 124 before_save :generate_password_if_needed, :update_hashed_password
125 125 before_destroy :remove_references_before_destroy
126 after_save :update_notified_project_ids, :destroy_tokens
126 after_save :update_notified_project_ids, :destroy_tokens, :deliver_security_notification
127 after_destroy :deliver_security_notification
127 128
128 129 scope :in_group, lambda {|group|
129 130 group_id = group.is_a?(Group) ? group.id : group.to_i
130 131 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)
131 132 }
132 133 scope :not_in_group, lambda {|group|
133 134 group_id = group.is_a?(Group) ? group.id : group.to_i
134 135 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)
135 136 }
136 137 scope :sorted, lambda { order(*User.fields_for_order_statement)}
137 138 scope :having_mail, lambda {|arg|
138 139 addresses = Array.wrap(arg).map {|a| a.to_s.downcase}
139 140 if addresses.any?
140 141 joins(:email_addresses).where("LOWER(#{EmailAddress.table_name}.address) IN (?)", addresses).uniq
141 142 else
142 143 none
143 144 end
144 145 }
145 146
146 147 def set_mail_notification
147 148 self.mail_notification = Setting.default_notification_option if self.mail_notification.blank?
148 149 true
149 150 end
150 151
151 152 def update_hashed_password
152 153 # update hashed_password if password was set
153 154 if self.password && self.auth_source_id.blank?
154 155 salt_password(password)
155 156 end
156 157 end
157 158
158 159 alias :base_reload :reload
159 160 def reload(*args)
160 161 @name = nil
161 162 @projects_by_role = nil
162 163 @membership_by_project_id = nil
163 164 @notified_projects_ids = nil
164 165 @notified_projects_ids_changed = false
165 166 @builtin_role = nil
166 167 @visible_project_ids = nil
167 168 @managed_roles = nil
168 169 base_reload(*args)
169 170 end
170 171
171 172 def mail
172 173 email_address.try(:address)
173 174 end
174 175
175 176 def mail=(arg)
176 177 email = email_address || build_email_address
177 178 email.address = arg
178 179 end
179 180
180 181 def mail_changed?
181 182 email_address.try(:address_changed?)
182 183 end
183 184
184 185 def mails
185 186 email_addresses.pluck(:address)
186 187 end
187 188
188 189 def self.find_or_initialize_by_identity_url(url)
189 190 user = where(:identity_url => url).first
190 191 unless user
191 192 user = User.new
192 193 user.identity_url = url
193 194 end
194 195 user
195 196 end
196 197
197 198 def identity_url=(url)
198 199 if url.blank?
199 200 write_attribute(:identity_url, '')
200 201 else
201 202 begin
202 203 write_attribute(:identity_url, OpenIdAuthentication.normalize_identifier(url))
203 204 rescue OpenIdAuthentication::InvalidOpenId
204 205 # Invalid url, don't save
205 206 end
206 207 end
207 208 self.read_attribute(:identity_url)
208 209 end
209 210
210 211 # Returns the user that matches provided login and password, or nil
211 212 def self.try_to_login(login, password, active_only=true)
212 213 login = login.to_s
213 214 password = password.to_s
214 215
215 216 # Make sure no one can sign in with an empty login or password
216 217 return nil if login.empty? || password.empty?
217 218 user = find_by_login(login)
218 219 if user
219 220 # user is already in local database
220 221 return nil unless user.check_password?(password)
221 222 return nil if !user.active? && active_only
222 223 else
223 224 # user is not yet registered, try to authenticate with available sources
224 225 attrs = AuthSource.authenticate(login, password)
225 226 if attrs
226 227 user = new(attrs)
227 228 user.login = login
228 229 user.language = Setting.default_language
229 230 if user.save
230 231 user.reload
231 232 logger.info("User '#{user.login}' created from external auth source: #{user.auth_source.type} - #{user.auth_source.name}") if logger && user.auth_source
232 233 end
233 234 end
234 235 end
235 236 user.update_column(:last_login_on, Time.now) if user && !user.new_record? && user.active?
236 237 user
237 238 rescue => text
238 239 raise text
239 240 end
240 241
241 242 # Returns the user who matches the given autologin +key+ or nil
242 243 def self.try_to_autologin(key)
243 244 user = Token.find_active_user('autologin', key, Setting.autologin.to_i)
244 245 if user
245 246 user.update_column(:last_login_on, Time.now)
246 247 user
247 248 end
248 249 end
249 250
250 251 def self.name_formatter(formatter = nil)
251 252 USER_FORMATS[formatter || Setting.user_format] || USER_FORMATS[:firstname_lastname]
252 253 end
253 254
254 255 # Returns an array of fields names than can be used to make an order statement for users
255 256 # according to how user names are displayed
256 257 # Examples:
257 258 #
258 259 # User.fields_for_order_statement => ['users.login', 'users.id']
259 260 # User.fields_for_order_statement('authors') => ['authors.login', 'authors.id']
260 261 def self.fields_for_order_statement(table=nil)
261 262 table ||= table_name
262 263 name_formatter[:order].map {|field| "#{table}.#{field}"}
263 264 end
264 265
265 266 # Return user's full name for display
266 267 def name(formatter = nil)
267 268 f = self.class.name_formatter(formatter)
268 269 if formatter
269 270 eval('"' + f[:string] + '"')
270 271 else
271 272 @name ||= eval('"' + f[:string] + '"')
272 273 end
273 274 end
274 275
275 276 def active?
276 277 self.status == STATUS_ACTIVE
277 278 end
278 279
279 280 def registered?
280 281 self.status == STATUS_REGISTERED
281 282 end
282 283
283 284 def locked?
284 285 self.status == STATUS_LOCKED
285 286 end
286 287
287 288 def activate
288 289 self.status = STATUS_ACTIVE
289 290 end
290 291
291 292 def register
292 293 self.status = STATUS_REGISTERED
293 294 end
294 295
295 296 def lock
296 297 self.status = STATUS_LOCKED
297 298 end
298 299
299 300 def activate!
300 301 update_attribute(:status, STATUS_ACTIVE)
301 302 end
302 303
303 304 def register!
304 305 update_attribute(:status, STATUS_REGISTERED)
305 306 end
306 307
307 308 def lock!
308 309 update_attribute(:status, STATUS_LOCKED)
309 310 end
310 311
311 312 # Returns true if +clear_password+ is the correct user's password, otherwise false
312 313 def check_password?(clear_password)
313 314 if auth_source_id.present?
314 315 auth_source.authenticate(self.login, clear_password)
315 316 else
316 317 User.hash_password("#{salt}#{User.hash_password clear_password}") == hashed_password
317 318 end
318 319 end
319 320
320 321 # Generates a random salt and computes hashed_password for +clear_password+
321 322 # The hashed password is stored in the following form: SHA1(salt + SHA1(password))
322 323 def salt_password(clear_password)
323 324 self.salt = User.generate_salt
324 325 self.hashed_password = User.hash_password("#{salt}#{User.hash_password clear_password}")
325 326 self.passwd_changed_on = Time.now.change(:usec => 0)
326 327 end
327 328
328 329 # Does the backend storage allow this user to change their password?
329 330 def change_password_allowed?
330 331 return true if auth_source.nil?
331 332 return auth_source.allow_password_changes?
332 333 end
333 334
334 335 # Returns true if the user password has expired
335 336 def password_expired?
336 337 period = Setting.password_max_age.to_i
337 338 if period.zero?
338 339 false
339 340 else
340 341 changed_on = self.passwd_changed_on || Time.at(0)
341 342 changed_on < period.days.ago
342 343 end
343 344 end
344 345
345 346 def must_change_password?
346 347 (must_change_passwd? || password_expired?) && change_password_allowed?
347 348 end
348 349
349 350 def generate_password?
350 351 generate_password == '1' || generate_password == true
351 352 end
352 353
353 354 # Generate and set a random password on given length
354 355 def random_password(length=40)
355 356 chars = ("a".."z").to_a + ("A".."Z").to_a + ("0".."9").to_a
356 357 chars -= %w(0 O 1 l)
357 358 password = ''
358 359 length.times {|i| password << chars[SecureRandom.random_number(chars.size)] }
359 360 self.password = password
360 361 self.password_confirmation = password
361 362 self
362 363 end
363 364
364 365 def pref
365 366 self.preference ||= UserPreference.new(:user => self)
366 367 end
367 368
368 369 def time_zone
369 370 @time_zone ||= (self.pref.time_zone.blank? ? nil : ActiveSupport::TimeZone[self.pref.time_zone])
370 371 end
371 372
372 373 def force_default_language?
373 374 Setting.force_default_language_for_loggedin?
374 375 end
375 376
376 377 def language
377 378 if force_default_language?
378 379 Setting.default_language
379 380 else
380 381 super
381 382 end
382 383 end
383 384
384 385 def wants_comments_in_reverse_order?
385 386 self.pref[:comments_sorting] == 'desc'
386 387 end
387 388
388 389 # Return user's RSS key (a 40 chars long string), used to access feeds
389 390 def rss_key
390 391 if rss_token.nil?
391 392 create_rss_token(:action => 'feeds')
392 393 end
393 394 rss_token.value
394 395 end
395 396
396 397 # Return user's API key (a 40 chars long string), used to access the API
397 398 def api_key
398 399 if api_token.nil?
399 400 create_api_token(:action => 'api')
400 401 end
401 402 api_token.value
402 403 end
403 404
404 405 # Generates a new session token and returns its value
405 406 def generate_session_token
406 407 token = Token.create!(:user_id => id, :action => 'session')
407 408 token.value
408 409 end
409 410
410 411 # Returns true if token is a valid session token for the user whose id is user_id
411 412 def self.verify_session_token(user_id, token)
412 413 return false if user_id.blank? || token.blank?
413 414
414 415 scope = Token.where(:user_id => user_id, :value => token.to_s, :action => 'session')
415 416 if Setting.session_lifetime?
416 417 scope = scope.where("created_on > ?", Setting.session_lifetime.to_i.minutes.ago)
417 418 end
418 419 if Setting.session_timeout?
419 420 scope = scope.where("updated_on > ?", Setting.session_timeout.to_i.minutes.ago)
420 421 end
421 422 scope.update_all(:updated_on => Time.now) == 1
422 423 end
423 424
424 425 # Return an array of project ids for which the user has explicitly turned mail notifications on
425 426 def notified_projects_ids
426 427 @notified_projects_ids ||= memberships.select {|m| m.mail_notification?}.collect(&:project_id)
427 428 end
428 429
429 430 def notified_project_ids=(ids)
430 431 @notified_projects_ids_changed = true
431 432 @notified_projects_ids = ids.map(&:to_i).uniq.select {|n| n > 0}
432 433 end
433 434
434 435 # Updates per project notifications (after_save callback)
435 436 def update_notified_project_ids
436 437 if @notified_projects_ids_changed
437 438 ids = (mail_notification == 'selected' ? Array.wrap(notified_projects_ids).reject(&:blank?) : [])
438 439 members.update_all(:mail_notification => false)
439 440 members.where(:project_id => ids).update_all(:mail_notification => true) if ids.any?
440 441 end
441 442 end
442 443 private :update_notified_project_ids
443 444
444 445 def valid_notification_options
445 446 self.class.valid_notification_options(self)
446 447 end
447 448
448 449 # Only users that belong to more than 1 project can select projects for which they are notified
449 450 def self.valid_notification_options(user=nil)
450 451 # Note that @user.membership.size would fail since AR ignores
451 452 # :include association option when doing a count
452 453 if user.nil? || user.memberships.length < 1
453 454 MAIL_NOTIFICATION_OPTIONS.reject {|option| option.first == 'selected'}
454 455 else
455 456 MAIL_NOTIFICATION_OPTIONS
456 457 end
457 458 end
458 459
459 460 # Find a user account by matching the exact login and then a case-insensitive
460 461 # version. Exact matches will be given priority.
461 462 def self.find_by_login(login)
462 463 login = Redmine::CodesetUtil.replace_invalid_utf8(login.to_s)
463 464 if login.present?
464 465 # First look for an exact match
465 466 user = where(:login => login).detect {|u| u.login == login}
466 467 unless user
467 468 # Fail over to case-insensitive if none was found
468 469 user = where("LOWER(login) = ?", login.downcase).first
469 470 end
470 471 user
471 472 end
472 473 end
473 474
474 475 def self.find_by_rss_key(key)
475 476 Token.find_active_user('feeds', key)
476 477 end
477 478
478 479 def self.find_by_api_key(key)
479 480 Token.find_active_user('api', key)
480 481 end
481 482
482 483 # Makes find_by_mail case-insensitive
483 484 def self.find_by_mail(mail)
484 485 having_mail(mail).first
485 486 end
486 487
487 488 # Returns true if the default admin account can no longer be used
488 489 def self.default_admin_account_changed?
489 490 !User.active.find_by_login("admin").try(:check_password?, "admin")
490 491 end
491 492
492 493 def to_s
493 494 name
494 495 end
495 496
496 497 CSS_CLASS_BY_STATUS = {
497 498 STATUS_ANONYMOUS => 'anon',
498 499 STATUS_ACTIVE => 'active',
499 500 STATUS_REGISTERED => 'registered',
500 501 STATUS_LOCKED => 'locked'
501 502 }
502 503
503 504 def css_classes
504 505 "user #{CSS_CLASS_BY_STATUS[status]}"
505 506 end
506 507
507 508 # Returns the current day according to user's time zone
508 509 def today
509 510 if time_zone.nil?
510 511 Date.today
511 512 else
512 513 Time.now.in_time_zone(time_zone).to_date
513 514 end
514 515 end
515 516
516 517 # Returns the day of +time+ according to user's time zone
517 518 def time_to_date(time)
518 519 if time_zone.nil?
519 520 time.to_date
520 521 else
521 522 time.in_time_zone(time_zone).to_date
522 523 end
523 524 end
524 525
525 526 def logged?
526 527 true
527 528 end
528 529
529 530 def anonymous?
530 531 !logged?
531 532 end
532 533
533 534 # Returns user's membership for the given project
534 535 # or nil if the user is not a member of project
535 536 def membership(project)
536 537 project_id = project.is_a?(Project) ? project.id : project
537 538
538 539 @membership_by_project_id ||= Hash.new {|h, project_id|
539 540 h[project_id] = memberships.where(:project_id => project_id).first
540 541 }
541 542 @membership_by_project_id[project_id]
542 543 end
543 544
544 545 # Returns the user's bult-in role
545 546 def builtin_role
546 547 @builtin_role ||= Role.non_member
547 548 end
548 549
549 550 # Return user's roles for project
550 551 def roles_for_project(project)
551 552 # No role on archived projects
552 553 return [] if project.nil? || project.archived?
553 554 if membership = membership(project)
554 555 membership.roles.to_a
555 556 elsif project.is_public?
556 557 project.override_roles(builtin_role)
557 558 else
558 559 []
559 560 end
560 561 end
561 562
562 563 # Returns a hash of user's projects grouped by roles
563 564 def projects_by_role
564 565 return @projects_by_role if @projects_by_role
565 566
566 567 hash = Hash.new([])
567 568
568 569 group_class = anonymous? ? GroupAnonymous : GroupNonMember
569 570 members = Member.joins(:project, :principal).
570 571 where("#{Project.table_name}.status <> 9").
571 572 where("#{Member.table_name}.user_id = ? OR (#{Project.table_name}.is_public = ? AND #{Principal.table_name}.type = ?)", self.id, true, group_class.name).
572 573 preload(:project, :roles).
573 574 to_a
574 575
575 576 members.reject! {|member| member.user_id != id && project_ids.include?(member.project_id)}
576 577 members.each do |member|
577 578 if member.project
578 579 member.roles.each do |role|
579 580 hash[role] = [] unless hash.key?(role)
580 581 hash[role] << member.project
581 582 end
582 583 end
583 584 end
584 585
585 586 hash.each do |role, projects|
586 587 projects.uniq!
587 588 end
588 589
589 590 @projects_by_role = hash
590 591 end
591 592
592 593 # Returns the ids of visible projects
593 594 def visible_project_ids
594 595 @visible_project_ids ||= Project.visible(self).pluck(:id)
595 596 end
596 597
597 598 # Returns the roles that the user is allowed to manage for the given project
598 599 def managed_roles(project)
599 600 if admin?
600 601 @managed_roles ||= Role.givable.to_a
601 602 else
602 603 membership(project).try(:managed_roles) || []
603 604 end
604 605 end
605 606
606 607 # Returns true if user is arg or belongs to arg
607 608 def is_or_belongs_to?(arg)
608 609 if arg.is_a?(User)
609 610 self == arg
610 611 elsif arg.is_a?(Group)
611 612 arg.users.include?(self)
612 613 else
613 614 false
614 615 end
615 616 end
616 617
617 618 # Return true if the user is allowed to do the specified action on a specific context
618 619 # Action can be:
619 620 # * a parameter-like Hash (eg. :controller => 'projects', :action => 'edit')
620 621 # * a permission Symbol (eg. :edit_project)
621 622 # Context can be:
622 623 # * a project : returns true if user is allowed to do the specified action on this project
623 624 # * an array of projects : returns true if user is allowed on every project
624 625 # * nil with options[:global] set : check if user has at least one role allowed for this action,
625 626 # or falls back to Non Member / Anonymous permissions depending if the user is logged
626 627 def allowed_to?(action, context, options={}, &block)
627 628 if context && context.is_a?(Project)
628 629 return false unless context.allows_to?(action)
629 630 # Admin users are authorized for anything else
630 631 return true if admin?
631 632
632 633 roles = roles_for_project(context)
633 634 return false unless roles
634 635 roles.any? {|role|
635 636 (context.is_public? || role.member?) &&
636 637 role.allowed_to?(action) &&
637 638 (block_given? ? yield(role, self) : true)
638 639 }
639 640 elsif context && context.is_a?(Array)
640 641 if context.empty?
641 642 false
642 643 else
643 644 # Authorize if user is authorized on every element of the array
644 645 context.map {|project| allowed_to?(action, project, options, &block)}.reduce(:&)
645 646 end
646 647 elsif context
647 648 raise ArgumentError.new("#allowed_to? context argument must be a Project, an Array of projects or nil")
648 649 elsif options[:global]
649 650 # Admin users are always authorized
650 651 return true if admin?
651 652
652 653 # authorize if user has at least one role that has this permission
653 654 roles = memberships.collect {|m| m.roles}.flatten.uniq
654 655 roles << (self.logged? ? Role.non_member : Role.anonymous)
655 656 roles.any? {|role|
656 657 role.allowed_to?(action) &&
657 658 (block_given? ? yield(role, self) : true)
658 659 }
659 660 else
660 661 false
661 662 end
662 663 end
663 664
664 665 # Is the user allowed to do the specified action on any project?
665 666 # See allowed_to? for the actions and valid options.
666 667 #
667 668 # NB: this method is not used anywhere in the core codebase as of
668 669 # 2.5.2, but it's used by many plugins so if we ever want to remove
669 670 # it it has to be carefully deprecated for a version or two.
670 671 def allowed_to_globally?(action, options={}, &block)
671 672 allowed_to?(action, nil, options.reverse_merge(:global => true), &block)
672 673 end
673 674
674 675 def allowed_to_view_all_time_entries?(context)
675 676 allowed_to?(:view_time_entries, context) do |role, user|
676 677 role.time_entries_visibility == 'all'
677 678 end
678 679 end
679 680
680 681 # Returns true if the user is allowed to delete the user's own account
681 682 def own_account_deletable?
682 683 Setting.unsubscribe? &&
683 684 (!admin? || User.active.where("admin = ? AND id <> ?", true, id).exists?)
684 685 end
685 686
686 687 safe_attributes 'firstname',
687 688 'lastname',
688 689 'mail',
689 690 'mail_notification',
690 691 'notified_project_ids',
691 692 'language',
692 693 'custom_field_values',
693 694 'custom_fields',
694 695 'identity_url'
695 696
696 697 safe_attributes 'status',
697 698 'auth_source_id',
698 699 'generate_password',
699 700 'must_change_passwd',
700 701 :if => lambda {|user, current_user| current_user.admin?}
701 702
702 703 safe_attributes 'group_ids',
703 704 :if => lambda {|user, current_user| current_user.admin? && !user.new_record?}
704 705
705 706 # Utility method to help check if a user should be notified about an
706 707 # event.
707 708 #
708 709 # TODO: only supports Issue events currently
709 710 def notify_about?(object)
710 711 if mail_notification == 'all'
711 712 true
712 713 elsif mail_notification.blank? || mail_notification == 'none'
713 714 false
714 715 else
715 716 case object
716 717 when Issue
717 718 case mail_notification
718 719 when 'selected', 'only_my_events'
719 720 # user receives notifications for created/assigned issues on unselected projects
720 721 object.author == self || is_or_belongs_to?(object.assigned_to) || is_or_belongs_to?(object.assigned_to_was)
721 722 when 'only_assigned'
722 723 is_or_belongs_to?(object.assigned_to) || is_or_belongs_to?(object.assigned_to_was)
723 724 when 'only_owner'
724 725 object.author == self
725 726 end
726 727 when News
727 728 # always send to project members except when mail_notification is set to 'none'
728 729 true
729 730 end
730 731 end
731 732 end
732 733
733 734 def self.current=(user)
734 735 RequestStore.store[:current_user] = user
735 736 end
736 737
737 738 def self.current
738 739 RequestStore.store[:current_user] ||= User.anonymous
739 740 end
740 741
741 742 # Returns the anonymous user. If the anonymous user does not exist, it is created. There can be only
742 743 # one anonymous user per database.
743 744 def self.anonymous
744 745 anonymous_user = AnonymousUser.first
745 746 if anonymous_user.nil?
746 747 anonymous_user = AnonymousUser.create(:lastname => 'Anonymous', :firstname => '', :login => '', :status => 0)
747 748 raise 'Unable to create the anonymous user.' if anonymous_user.new_record?
748 749 end
749 750 anonymous_user
750 751 end
751 752
752 753 # Salts all existing unsalted passwords
753 754 # It changes password storage scheme from SHA1(password) to SHA1(salt + SHA1(password))
754 755 # This method is used in the SaltPasswords migration and is to be kept as is
755 756 def self.salt_unsalted_passwords!
756 757 transaction do
757 758 User.where("salt IS NULL OR salt = ''").find_each do |user|
758 759 next if user.hashed_password.blank?
759 760 salt = User.generate_salt
760 761 hashed_password = User.hash_password("#{salt}#{user.hashed_password}")
761 762 User.where(:id => user.id).update_all(:salt => salt, :hashed_password => hashed_password)
762 763 end
763 764 end
764 765 end
765 766
766 767 protected
767 768
768 769 def validate_password_length
769 770 return if password.blank? && generate_password?
770 771 # Password length validation based on setting
771 772 if !password.nil? && password.size < Setting.password_min_length.to_i
772 773 errors.add(:password, :too_short, :count => Setting.password_min_length.to_i)
773 774 end
774 775 end
775 776
776 777 def instantiate_email_address
777 778 email_address || build_email_address
778 779 end
779 780
780 781 private
781 782
782 783 def generate_password_if_needed
783 784 if generate_password? && auth_source.nil?
784 785 length = [Setting.password_min_length.to_i + 2, 10].max
785 786 random_password(length)
786 787 end
787 788 end
788 789
789 790 # Delete all outstanding password reset tokens on password change.
790 791 # Delete the autologin tokens on password change to prohibit session leakage.
791 792 # This helps to keep the account secure in case the associated email account
792 793 # was compromised.
793 794 def destroy_tokens
794 795 if hashed_password_changed? || (status_changed? && !active?)
795 796 tokens = ['recovery', 'autologin', 'session']
796 797 Token.where(:user_id => id, :action => tokens).delete_all
797 798 end
798 799 end
799 800
800 801 # Removes references that are not handled by associations
801 802 # Things that are not deleted are reassociated with the anonymous user
802 803 def remove_references_before_destroy
803 804 return if self.id.nil?
804 805
805 806 substitute = User.anonymous
806 807 Attachment.where(['author_id = ?', id]).update_all(['author_id = ?', substitute.id])
807 808 Comment.where(['author_id = ?', id]).update_all(['author_id = ?', substitute.id])
808 809 Issue.where(['author_id = ?', id]).update_all(['author_id = ?', substitute.id])
809 810 Issue.where(['assigned_to_id = ?', id]).update_all('assigned_to_id = NULL')
810 811 Journal.where(['user_id = ?', id]).update_all(['user_id = ?', substitute.id])
811 812 JournalDetail.
812 813 where(["property = 'attr' AND prop_key = 'assigned_to_id' AND old_value = ?", id.to_s]).
813 814 update_all(['old_value = ?', substitute.id.to_s])
814 815 JournalDetail.
815 816 where(["property = 'attr' AND prop_key = 'assigned_to_id' AND value = ?", id.to_s]).
816 817 update_all(['value = ?', substitute.id.to_s])
817 818 Message.where(['author_id = ?', id]).update_all(['author_id = ?', substitute.id])
818 819 News.where(['author_id = ?', id]).update_all(['author_id = ?', substitute.id])
819 820 # Remove private queries and keep public ones
820 821 ::Query.delete_all ['user_id = ? AND visibility = ?', id, ::Query::VISIBILITY_PRIVATE]
821 822 ::Query.where(['user_id = ?', id]).update_all(['user_id = ?', substitute.id])
822 823 TimeEntry.where(['user_id = ?', id]).update_all(['user_id = ?', substitute.id])
823 824 Token.delete_all ['user_id = ?', id]
824 825 Watcher.delete_all ['user_id = ?', id]
825 826 WikiContent.where(['author_id = ?', id]).update_all(['author_id = ?', substitute.id])
826 827 WikiContent::Version.where(['author_id = ?', id]).update_all(['author_id = ?', substitute.id])
827 828 end
828 829
829 830 # Return password digest
830 831 def self.hash_password(clear_password)
831 832 Digest::SHA1.hexdigest(clear_password || "")
832 833 end
833 834
834 835 # Returns a 128bits random salt as a hex string (32 chars long)
835 836 def self.generate_salt
836 837 Redmine::Utils.random_hex(16)
837 838 end
839 # Send a security notification to all admins if the user has gained/lost admin privileges
840 def deliver_security_notification
841 options = {
842 field: :field_admin,
843 value: login,
844 title: :label_user_plural,
845 url: {controller: 'users', action: 'index'}
846 }
847 deliver = false
848 if (admin? && id_changed? && active?) || # newly created admin
849 (admin? && admin_changed? && active?) || # regular user became admin
850 (admin? && status_changed? && active?) # locked admin became active again
851
852 deliver = true
853 options[:message] = :mail_body_security_notification_add
854
855 elsif (admin? && destroyed? && active?) || # active admin user was deleted
856 (!admin? && admin_changed? && active?) || # admin is no longer admin
857 (admin? && status_changed? && !active?) # admin was locked
858
859 deliver = true
860 options[:message] = :mail_body_security_notification_remove
861 end
862
863 User.where(admin: true, status: Principal::STATUS_ACTIVE).each{|u| Mailer.security_notification(u, options).deliver} if deliver
864 end
865
866
838 867
839 868 end
840 869
841 870 class AnonymousUser < User
842 871 validate :validate_anonymous_uniqueness, :on => :create
843 872
844 873 def validate_anonymous_uniqueness
845 874 # There should be only one AnonymousUser in the database
846 875 errors.add :base, 'An anonymous user already exists.' if AnonymousUser.exists?
847 876 end
848 877
849 878 def available_custom_fields
850 879 []
851 880 end
852 881
853 882 # Overrides a few properties
854 883 def logged?; false end
855 884 def admin; false end
856 885 def name(*args); I18n.t(:label_user_anonymous) end
857 886 def mail=(*args); nil end
858 887 def mail; nil end
859 888 def time_zone; nil end
860 889 def rss_key; nil end
861 890
862 891 def pref
863 892 UserPreference.new(:user => self)
864 893 end
865 894
866 895 # Returns the user's bult-in role
867 896 def builtin_role
868 897 @builtin_role ||= Role.anonymous
869 898 end
870 899
871 900 def membership(*args)
872 901 nil
873 902 end
874 903
875 904 def member_of?(*args)
876 905 false
877 906 end
878 907
879 908 # Anonymous user can not be destroyed
880 909 def destroy
881 910 false
882 911 end
883 912
884 913 protected
885 914
886 915 def instantiate_email_address
887 916 end
888 917 end
@@ -1,452 +1,596
1 1 # Redmine - project management software
2 2 # Copyright (C) 2006-2016 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 File.expand_path('../../test_helper', __FILE__)
19 19
20 20 class UsersControllerTest < ActionController::TestCase
21 21 include Redmine::I18n
22 22
23 23 fixtures :users, :email_addresses, :projects, :members, :member_roles, :roles,
24 24 :custom_fields, :custom_values, :groups_users,
25 25 :auth_sources,
26 26 :enabled_modules,
27 27 :issues, :issue_statuses,
28 28 :trackers
29 29
30 30 def setup
31 31 User.current = nil
32 32 @request.session[:user_id] = 1 # admin
33 33 end
34 34
35 35 def test_index
36 36 get :index
37 37 assert_response :success
38 38 assert_template 'index'
39 39 assert_not_nil assigns(:users)
40 40 # active users only
41 41 assert_nil assigns(:users).detect {|u| !u.active?}
42 42 end
43 43
44 44 def test_index_with_status_filter
45 45 get :index, :status => 3
46 46 assert_response :success
47 47 assert_template 'index'
48 48 assert_not_nil assigns(:users)
49 49 assert_equal [3], assigns(:users).map(&:status).uniq
50 50 end
51 51
52 52 def test_index_with_name_filter
53 53 get :index, :name => 'john'
54 54 assert_response :success
55 55 assert_template 'index'
56 56 users = assigns(:users)
57 57 assert_not_nil users
58 58 assert_equal 1, users.size
59 59 assert_equal 'John', users.first.firstname
60 60 end
61 61
62 62 def test_index_with_group_filter
63 63 get :index, :group_id => '10'
64 64 assert_response :success
65 65 assert_template 'index'
66 66 users = assigns(:users)
67 67 assert users.any?
68 68 assert_equal([], (users - Group.find(10).users))
69 69 assert_select 'select[name=group_id]' do
70 70 assert_select 'option[value="10"][selected=selected]'
71 71 end
72 72 end
73 73
74 74 def test_show
75 75 @request.session[:user_id] = nil
76 76 get :show, :id => 2
77 77 assert_response :success
78 78 assert_template 'show'
79 79 assert_not_nil assigns(:user)
80 80
81 81 assert_select 'li', :text => /Phone number/
82 82 end
83 83
84 84 def test_show_should_not_display_hidden_custom_fields
85 85 @request.session[:user_id] = nil
86 86 UserCustomField.find_by_name('Phone number').update_attribute :visible, false
87 87 get :show, :id => 2
88 88 assert_response :success
89 89 assert_template 'show'
90 90 assert_not_nil assigns(:user)
91 91
92 92 assert_select 'li', :text => /Phone number/, :count => 0
93 93 end
94 94
95 95 def test_show_should_not_fail_when_custom_values_are_nil
96 96 user = User.find(2)
97 97
98 98 # Create a custom field to illustrate the issue
99 99 custom_field = CustomField.create!(:name => 'Testing', :field_format => 'text')
100 100 custom_value = user.custom_values.build(:custom_field => custom_field).save!
101 101
102 102 get :show, :id => 2
103 103 assert_response :success
104 104 end
105 105
106 106 def test_show_inactive
107 107 @request.session[:user_id] = nil
108 108 get :show, :id => 5
109 109 assert_response 404
110 110 end
111 111
112 112 def test_show_inactive_by_admin
113 113 @request.session[:user_id] = 1
114 114 get :show, :id => 5
115 115 assert_response 200
116 116 assert_not_nil assigns(:user)
117 117 end
118 118
119 119 def test_show_user_who_is_not_visible_should_return_404
120 120 Role.anonymous.update! :users_visibility => 'members_of_visible_projects'
121 121 user = User.generate!
122 122
123 123 @request.session[:user_id] = nil
124 124 get :show, :id => user.id
125 125 assert_response 404
126 126 end
127 127
128 128 def test_show_displays_memberships_based_on_project_visibility
129 129 @request.session[:user_id] = 1
130 130 get :show, :id => 2
131 131 assert_response :success
132 132 memberships = assigns(:memberships)
133 133 assert_not_nil memberships
134 134 project_ids = memberships.map(&:project_id)
135 135 assert project_ids.include?(2) #private project admin can see
136 136 end
137 137
138 138 def test_show_current_should_require_authentication
139 139 @request.session[:user_id] = nil
140 140 get :show, :id => 'current'
141 141 assert_response 302
142 142 end
143 143
144 144 def test_show_current
145 145 @request.session[:user_id] = 2
146 146 get :show, :id => 'current'
147 147 assert_response :success
148 148 assert_template 'show'
149 149 assert_equal User.find(2), assigns(:user)
150 150 end
151 151
152 152 def test_new
153 153 get :new
154 154 assert_response :success
155 155 assert_template :new
156 156 assert assigns(:user)
157 157 end
158 158
159 159 def test_create
160 160 Setting.bcc_recipients = '1'
161 161
162 162 assert_difference 'User.count' do
163 163 assert_difference 'ActionMailer::Base.deliveries.size' do
164 164 post :create,
165 165 :user => {
166 166 :firstname => 'John',
167 167 :lastname => 'Doe',
168 168 :login => 'jdoe',
169 169 :password => 'secret123',
170 170 :password_confirmation => 'secret123',
171 171 :mail => 'jdoe@gmail.com',
172 172 :mail_notification => 'none'
173 173 },
174 174 :send_information => '1'
175 175 end
176 176 end
177 177
178 178 user = User.order('id DESC').first
179 179 assert_redirected_to :controller => 'users', :action => 'edit', :id => user.id
180 180
181 181 assert_equal 'John', user.firstname
182 182 assert_equal 'Doe', user.lastname
183 183 assert_equal 'jdoe', user.login
184 184 assert_equal 'jdoe@gmail.com', user.mail
185 185 assert_equal 'none', user.mail_notification
186 186 assert user.check_password?('secret123')
187 187
188 188 mail = ActionMailer::Base.deliveries.last
189 189 assert_not_nil mail
190 190 assert_equal [user.mail], mail.bcc
191 191 assert_mail_body_match 'secret', mail
192 192 end
193 193
194 194 def test_create_with_preferences
195 195 assert_difference 'User.count' do
196 196 post :create,
197 197 :user => {
198 198 :firstname => 'John',
199 199 :lastname => 'Doe',
200 200 :login => 'jdoe',
201 201 :password => 'secret123',
202 202 :password_confirmation => 'secret123',
203 203 :mail => 'jdoe@gmail.com',
204 204 :mail_notification => 'none'
205 205 },
206 206 :pref => {
207 207 'hide_mail' => '1',
208 208 'time_zone' => 'Paris',
209 209 'comments_sorting' => 'desc',
210 210 'warn_on_leaving_unsaved' => '0'
211 211 }
212 212 end
213 213 user = User.order('id DESC').first
214 214 assert_equal 'jdoe', user.login
215 215 assert_equal true, user.pref.hide_mail
216 216 assert_equal 'Paris', user.pref.time_zone
217 217 assert_equal 'desc', user.pref[:comments_sorting]
218 218 assert_equal '0', user.pref[:warn_on_leaving_unsaved]
219 219 end
220 220
221 221 def test_create_with_generate_password_should_email_the_password
222 222 assert_difference 'User.count' do
223 223 post :create, :user => {
224 224 :login => 'randompass',
225 225 :firstname => 'Random',
226 226 :lastname => 'Pass',
227 227 :mail => 'randompass@example.net',
228 228 :language => 'en',
229 229 :generate_password => '1',
230 230 :password => '',
231 231 :password_confirmation => ''
232 232 }, :send_information => 1
233 233 end
234 234 user = User.order('id DESC').first
235 235 assert_equal 'randompass', user.login
236 236
237 237 mail = ActionMailer::Base.deliveries.last
238 238 assert_not_nil mail
239 239 m = mail_body(mail).match(/Password: ([a-zA-Z0-9]+)/)
240 240 assert m
241 241 password = m[1]
242 242 assert user.check_password?(password)
243 243 end
244 244
245 245 def test_create_and_continue
246 246 post :create, :user => {
247 247 :login => 'randompass',
248 248 :firstname => 'Random',
249 249 :lastname => 'Pass',
250 250 :mail => 'randompass@example.net',
251 251 :generate_password => '1'
252 252 }, :continue => '1'
253 253 assert_redirected_to '/users/new?user%5Bgenerate_password%5D=1'
254 254 end
255 255
256 256 def test_create_with_failure
257 257 assert_no_difference 'User.count' do
258 258 post :create, :user => {}
259 259 end
260 260 assert_response :success
261 261 assert_template 'new'
262 262 end
263 263
264 264 def test_create_with_failure_sould_preserve_preference
265 265 assert_no_difference 'User.count' do
266 266 post :create,
267 267 :user => {},
268 268 :pref => {
269 269 'no_self_notified' => '1',
270 270 'hide_mail' => '1',
271 271 'time_zone' => 'Paris',
272 272 'comments_sorting' => 'desc',
273 273 'warn_on_leaving_unsaved' => '0'
274 274 }
275 275 end
276 276 assert_response :success
277 277 assert_template 'new'
278 278
279 279 assert_select 'select#pref_time_zone option[selected=selected]', :text => /Paris/
280 280 assert_select 'input#pref_no_self_notified[value="1"][checked=checked]'
281 281 end
282 282
283 def test_create_admin_should_send_security_notification
284 ActionMailer::Base.deliveries.clear
285 post :create,
286 :user => {
287 :firstname => 'Edgar',
288 :lastname => 'Schmoe',
289 :login => 'eschmoe',
290 :password => 'secret123',
291 :password_confirmation => 'secret123',
292 :mail => 'eschmoe@example.foo',
293 :admin => '1'
294 }
295
296 assert_not_nil (mail = ActionMailer::Base.deliveries.last)
297 assert_mail_body_match '0.0.0.0', mail
298 assert_mail_body_match I18n.t(:mail_body_security_notification_add, field: I18n.t(:field_admin), value: 'eschmoe'), mail
299 assert_select_email do
300 assert_select 'a[href^=?]', 'http://localhost:3000/users', :text => 'Users'
301 end
302
303 # All admins should receive this
304 User.where(admin: true, status: Principal::STATUS_ACTIVE).each do |admin|
305 assert_not_nil ActionMailer::Base.deliveries.detect{|mail| [mail.bcc, mail.cc].flatten.include?(admin.mail) }
306 end
307 end
308
309 def test_create_non_admin_should_not_send_security_notification
310 ActionMailer::Base.deliveries.clear
311 post :create,
312 :user => {
313 :firstname => 'Edgar',
314 :lastname => 'Schmoe',
315 :login => 'eschmoe',
316 :password => 'secret123',
317 :password_confirmation => 'secret123',
318 :mail => 'eschmoe@example.foo',
319 :admin => '0'
320 }
321 assert_nil ActionMailer::Base.deliveries.last
322 end
323
324
283 325 def test_edit
284 326 get :edit, :id => 2
285 327 assert_response :success
286 328 assert_template 'edit'
287 329 assert_equal User.find(2), assigns(:user)
288 330 end
289 331
290 332 def test_edit_registered_user
291 333 assert User.find(2).register!
292 334
293 335 get :edit, :id => 2
294 336 assert_response :success
295 337 assert_select 'a', :text => 'Activate'
296 338 end
297 339
298 340 def test_update
299 341 ActionMailer::Base.deliveries.clear
300 342 put :update, :id => 2,
301 343 :user => {:firstname => 'Changed', :mail_notification => 'only_assigned'},
302 344 :pref => {:hide_mail => '1', :comments_sorting => 'desc'}
303 345 user = User.find(2)
304 346 assert_equal 'Changed', user.firstname
305 347 assert_equal 'only_assigned', user.mail_notification
306 348 assert_equal true, user.pref[:hide_mail]
307 349 assert_equal 'desc', user.pref[:comments_sorting]
308 350 assert ActionMailer::Base.deliveries.empty?
309 351 end
310 352
311 353 def test_update_with_failure
312 354 assert_no_difference 'User.count' do
313 355 put :update, :id => 2, :user => {:firstname => ''}
314 356 end
315 357 assert_response :success
316 358 assert_template 'edit'
317 359 end
318 360
319 361 def test_update_with_group_ids_should_assign_groups
320 362 put :update, :id => 2, :user => {:group_ids => ['10']}
321 363 user = User.find(2)
322 364 assert_equal [10], user.group_ids
323 365 end
324 366
325 367 def test_update_with_activation_should_send_a_notification
326 368 u = User.new(:firstname => 'Foo', :lastname => 'Bar', :mail => 'foo.bar@somenet.foo', :language => 'fr')
327 369 u.login = 'foo'
328 370 u.status = User::STATUS_REGISTERED
329 371 u.save!
330 372 ActionMailer::Base.deliveries.clear
331 373 Setting.bcc_recipients = '1'
332 374
333 375 put :update, :id => u.id, :user => {:status => User::STATUS_ACTIVE}
334 376 assert u.reload.active?
335 377 mail = ActionMailer::Base.deliveries.last
336 378 assert_not_nil mail
337 379 assert_equal ['foo.bar@somenet.foo'], mail.bcc
338 380 assert_mail_body_match ll('fr', :notice_account_activated), mail
339 381 end
340 382
341 383 def test_update_with_password_change_should_send_a_notification
342 384 ActionMailer::Base.deliveries.clear
343 385 Setting.bcc_recipients = '1'
344 386
345 387 put :update, :id => 2, :user => {:password => 'newpass123', :password_confirmation => 'newpass123'}, :send_information => '1'
346 388 u = User.find(2)
347 389 assert u.check_password?('newpass123')
348 390
349 391 mail = ActionMailer::Base.deliveries.last
350 392 assert_not_nil mail
351 393 assert_equal [u.mail], mail.bcc
352 394 assert_mail_body_match 'newpass123', mail
353 395 end
354 396
355 397 def test_update_with_generate_password_should_email_the_password
356 398 ActionMailer::Base.deliveries.clear
357 399 Setting.bcc_recipients = '1'
358 400
359 401 put :update, :id => 2, :user => {
360 402 :generate_password => '1',
361 403 :password => '',
362 404 :password_confirmation => ''
363 405 }, :send_information => '1'
364 406
365 407 mail = ActionMailer::Base.deliveries.last
366 408 assert_not_nil mail
367 409 m = mail_body(mail).match(/Password: ([a-zA-Z0-9]+)/)
368 410 assert m
369 411 password = m[1]
370 412 assert User.find(2).check_password?(password)
371 413 end
372 414
373 415 def test_update_without_generate_password_should_not_change_password
374 416 put :update, :id => 2, :user => {
375 417 :firstname => 'changed',
376 418 :generate_password => '0',
377 419 :password => '',
378 420 :password_confirmation => ''
379 421 }, :send_information => '1'
380 422
381 423 user = User.find(2)
382 424 assert_equal 'changed', user.firstname
383 425 assert user.check_password?('jsmith')
384 426 end
385 427
386 428 def test_update_user_switchin_from_auth_source_to_password_authentication
387 429 # Configure as auth source
388 430 u = User.find(2)
389 431 u.auth_source = AuthSource.find(1)
390 432 u.save!
391 433
392 434 put :update, :id => u.id, :user => {:auth_source_id => '', :password => 'newpass123', :password_confirmation => 'newpass123'}
393 435
394 436 assert_equal nil, u.reload.auth_source
395 437 assert u.check_password?('newpass123')
396 438 end
397 439
398 440 def test_update_notified_project
399 441 get :edit, :id => 2
400 442 assert_response :success
401 443 assert_template 'edit'
402 444 u = User.find(2)
403 445 assert_equal [1, 2, 5], u.projects.collect{|p| p.id}.sort
404 446 assert_equal [1, 2, 5], u.notified_projects_ids.sort
405 447 assert_select 'input[name=?][value=?]', 'user[notified_project_ids][]', '1'
406 448 assert_equal 'all', u.mail_notification
407 449 put :update, :id => 2,
408 450 :user => {
409 451 :mail_notification => 'selected',
410 452 :notified_project_ids => [1, 2]
411 453 }
412 454 u = User.find(2)
413 455 assert_equal 'selected', u.mail_notification
414 456 assert_equal [1, 2], u.notified_projects_ids.sort
415 457 end
416 458
417 459 def test_update_status_should_not_update_attributes
418 460 user = User.find(2)
419 461 user.pref[:no_self_notified] = '1'
420 462 user.pref.save
421 463
422 464 put :update, :id => 2, :user => {:status => 3}
423 465 assert_response 302
424 466 user = User.find(2)
425 467 assert_equal 3, user.status
426 468 assert_equal '1', user.pref[:no_self_notified]
427 469 end
428 470
471 def test_update_assign_admin_should_send_security_notification
472 ActionMailer::Base.deliveries.clear
473 put :update, :id => 2, :user => {
474 :admin => 1
475 }
476
477 assert_not_nil (mail = ActionMailer::Base.deliveries.last)
478 assert_mail_body_match I18n.t(:mail_body_security_notification_add, field: I18n.t(:field_admin), value: User.find(2).login), mail
479
480 # All admins should receive this
481 User.where(admin: true, status: Principal::STATUS_ACTIVE).each do |admin|
482 assert_not_nil ActionMailer::Base.deliveries.detect{|mail| [mail.bcc, mail.cc].flatten.include?(admin.mail) }
483 end
484 end
485
486 def test_update_unassign_admin_should_send_security_notification
487 user = User.find(2)
488 user.admin = true
489 user.save!
490
491 ActionMailer::Base.deliveries.clear
492 put :update, :id => user.id, :user => {
493 :admin => 0
494 }
495
496 assert_not_nil (mail = ActionMailer::Base.deliveries.last)
497 assert_mail_body_match I18n.t(:mail_body_security_notification_remove, field: I18n.t(:field_admin), value: user.login), mail
498
499 # All admins should receive this
500 User.where(admin: true, status: Principal::STATUS_ACTIVE).each do |admin|
501 assert_not_nil ActionMailer::Base.deliveries.detect{|mail| [mail.bcc, mail.cc].flatten.include?(admin.mail) }
502 end
503 end
504
505 def test_update_lock_admin_should_send_security_notification
506 user = User.find(2)
507 user.admin = true
508 user.save!
509
510 ActionMailer::Base.deliveries.clear
511 put :update, :id => 2, :user => {
512 :status => Principal::STATUS_LOCKED
513 }
514
515 assert_not_nil (mail = ActionMailer::Base.deliveries.last)
516 assert_mail_body_match I18n.t(:mail_body_security_notification_remove, field: I18n.t(:field_admin), value: User.find(2).login), mail
517
518 # All admins should receive this
519 User.where(admin: true, status: Principal::STATUS_ACTIVE).each do |admin|
520 assert_not_nil ActionMailer::Base.deliveries.detect{|mail| [mail.bcc, mail.cc].flatten.include?(admin.mail) }
521 end
522
523 # if user is already locked, destroying should not send a second mail
524 # (for active admins see furtherbelow)
525 ActionMailer::Base.deliveries.clear
526 delete :destroy, :id => 1
527 assert_nil ActionMailer::Base.deliveries.last
528
529 end
530
531 def test_update_unlock_admin_should_send_security_notification
532 user = User.find(5) # already locked
533 user.admin = true
534 user.save!
535 ActionMailer::Base.deliveries.clear
536 put :update, :id => user.id, :user => {
537 :status => Principal::STATUS_ACTIVE
538 }
539
540 assert_not_nil (mail = ActionMailer::Base.deliveries.last)
541 assert_mail_body_match I18n.t(:mail_body_security_notification_add, field: I18n.t(:field_admin), value: user.login), mail
542
543 # All admins should receive this
544 User.where(admin: true, status: Principal::STATUS_ACTIVE).each do |admin|
545 assert_not_nil ActionMailer::Base.deliveries.detect{|mail| [mail.bcc, mail.cc].flatten.include?(admin.mail) }
546 end
547 end
548
549 def test_update_admin_unrelated_property_should_not_send_security_notification
550 ActionMailer::Base.deliveries.clear
551 put :update, :id => 1, :user => {
552 :firstname => 'Jimmy'
553 }
554 assert_nil ActionMailer::Base.deliveries.last
555 end
556
429 557 def test_destroy
430 558 assert_difference 'User.count', -1 do
431 559 delete :destroy, :id => 2
432 560 end
433 561 assert_redirected_to '/users'
434 562 assert_nil User.find_by_id(2)
435 563 end
436 564
437 565 def test_destroy_should_be_denied_for_non_admin_users
438 566 @request.session[:user_id] = 3
439 567
440 568 assert_no_difference 'User.count' do
441 569 get :destroy, :id => 2
442 570 end
443 571 assert_response 403
444 572 end
445 573
446 574 def test_destroy_should_redirect_to_back_url_param
447 575 assert_difference 'User.count', -1 do
448 576 delete :destroy, :id => 2, :back_url => '/users?name=foo'
449 577 end
450 578 assert_redirected_to '/users?name=foo'
451 579 end
580
581 def test_destroy_active_admin_should_send_security_notification
582 user = User.find(2)
583 user.admin = true
584 user.save!
585 ActionMailer::Base.deliveries.clear
586 delete :destroy, :id => user.id
587
588 assert_not_nil (mail = ActionMailer::Base.deliveries.last)
589 assert_mail_body_match I18n.t(:mail_body_security_notification_remove, field: I18n.t(:field_admin), value: user.login), mail
590
591 # All admins should receive this
592 User.where(admin: true, status: Principal::STATUS_ACTIVE).each do |admin|
593 assert_not_nil ActionMailer::Base.deliveries.detect{|mail| [mail.bcc, mail.cc].flatten.include?(admin.mail) }
594 end
595 end
452 596 end
General Comments 0
You need to be logged in to leave comments. Login now