##// END OF EJS Templates
Invalidate security tokens on password or email changes (#17717)....
Jean-Baptiste Barth -
r13033:2eb95f41b41d
parent child
Show More
@@ -1,766 +1,778
1 1 # Redmine - project management software
2 2 # Copyright (C) 2006-2014 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, :class_name => 'Token', :conditions => "action='feeds'"
83 83 has_one :api_token, :class_name => 'Token', :conditions => "action='api'"
84 84 belongs_to :auth_source
85 85
86 86 scope :logged, lambda { where("#{User.table_name}.status <> #{STATUS_ANONYMOUS}") }
87 87 scope :status, lambda {|arg| where(arg.blank? ? nil : {:status => arg.to_i}) }
88 88
89 89 acts_as_customizable
90 90
91 91 attr_accessor :password, :password_confirmation, :generate_password
92 92 attr_accessor :last_before_login_on
93 93 # Prevents unauthorized assignments
94 94 attr_protected :login, :admin, :password, :password_confirmation, :hashed_password
95 95
96 96 LOGIN_LENGTH_LIMIT = 60
97 97 MAIL_LENGTH_LIMIT = 60
98 98
99 99 validates_presence_of :login, :firstname, :lastname, :mail, :if => Proc.new { |user| !user.is_a?(AnonymousUser) }
100 100 validates_uniqueness_of :login, :if => Proc.new { |user| user.login_changed? && user.login.present? }, :case_sensitive => false
101 101 validates_uniqueness_of :mail, :if => Proc.new { |user| user.mail_changed? && user.mail.present? }, :case_sensitive => false
102 102 # Login must contain letters, numbers, underscores only
103 103 validates_format_of :login, :with => /\A[a-z0-9_\-@\.]*\z/i
104 104 validates_length_of :login, :maximum => LOGIN_LENGTH_LIMIT
105 105 validates_length_of :firstname, :lastname, :maximum => 30
106 106 validates_format_of :mail, :with => /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\z/i, :allow_blank => true
107 107 validates_length_of :mail, :maximum => MAIL_LENGTH_LIMIT, :allow_nil => true
108 108 validates_confirmation_of :password, :allow_nil => true
109 109 validates_inclusion_of :mail_notification, :in => MAIL_NOTIFICATION_OPTIONS.collect(&:first), :allow_blank => true
110 110 validate :validate_password_length
111 111
112 112 before_create :set_mail_notification
113 113 before_save :generate_password_if_needed, :update_hashed_password
114 114 before_destroy :remove_references_before_destroy
115 after_save :update_notified_project_ids
115 after_save :update_notified_project_ids, :destroy_tokens
116 116
117 117 scope :in_group, lambda {|group|
118 118 group_id = group.is_a?(Group) ? group.id : group.to_i
119 119 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)
120 120 }
121 121 scope :not_in_group, lambda {|group|
122 122 group_id = group.is_a?(Group) ? group.id : group.to_i
123 123 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)
124 124 }
125 125 scope :sorted, lambda { order(*User.fields_for_order_statement)}
126 126
127 127 def set_mail_notification
128 128 self.mail_notification = Setting.default_notification_option if self.mail_notification.blank?
129 129 true
130 130 end
131 131
132 132 def update_hashed_password
133 133 # update hashed_password if password was set
134 134 if self.password && self.auth_source_id.blank?
135 135 salt_password(password)
136 136 end
137 137 end
138 138
139 139 alias :base_reload :reload
140 140 def reload(*args)
141 141 @name = nil
142 142 @projects_by_role = nil
143 143 @membership_by_project_id = nil
144 144 @notified_projects_ids = nil
145 145 @notified_projects_ids_changed = false
146 146 @builtin_role = nil
147 147 base_reload(*args)
148 148 end
149 149
150 150 def mail=(arg)
151 151 write_attribute(:mail, arg.to_s.strip)
152 152 end
153 153
154 154 def identity_url=(url)
155 155 if url.blank?
156 156 write_attribute(:identity_url, '')
157 157 else
158 158 begin
159 159 write_attribute(:identity_url, OpenIdAuthentication.normalize_identifier(url))
160 160 rescue OpenIdAuthentication::InvalidOpenId
161 161 # Invalid url, don't save
162 162 end
163 163 end
164 164 self.read_attribute(:identity_url)
165 165 end
166 166
167 167 # Returns the user that matches provided login and password, or nil
168 168 def self.try_to_login(login, password, active_only=true)
169 169 login = login.to_s
170 170 password = password.to_s
171 171
172 172 # Make sure no one can sign in with an empty login or password
173 173 return nil if login.empty? || password.empty?
174 174 user = find_by_login(login)
175 175 if user
176 176 # user is already in local database
177 177 return nil unless user.check_password?(password)
178 178 return nil if !user.active? && active_only
179 179 else
180 180 # user is not yet registered, try to authenticate with available sources
181 181 attrs = AuthSource.authenticate(login, password)
182 182 if attrs
183 183 user = new(attrs)
184 184 user.login = login
185 185 user.language = Setting.default_language
186 186 if user.save
187 187 user.reload
188 188 logger.info("User '#{user.login}' created from external auth source: #{user.auth_source.type} - #{user.auth_source.name}") if logger && user.auth_source
189 189 end
190 190 end
191 191 end
192 192 user.update_column(:last_login_on, Time.now) if user && !user.new_record? && user.active?
193 193 user
194 194 rescue => text
195 195 raise text
196 196 end
197 197
198 198 # Returns the user who matches the given autologin +key+ or nil
199 199 def self.try_to_autologin(key)
200 200 user = Token.find_active_user('autologin', key, Setting.autologin.to_i)
201 201 if user
202 202 user.update_column(:last_login_on, Time.now)
203 203 user
204 204 end
205 205 end
206 206
207 207 def self.name_formatter(formatter = nil)
208 208 USER_FORMATS[formatter || Setting.user_format] || USER_FORMATS[:firstname_lastname]
209 209 end
210 210
211 211 # Returns an array of fields names than can be used to make an order statement for users
212 212 # according to how user names are displayed
213 213 # Examples:
214 214 #
215 215 # User.fields_for_order_statement => ['users.login', 'users.id']
216 216 # User.fields_for_order_statement('authors') => ['authors.login', 'authors.id']
217 217 def self.fields_for_order_statement(table=nil)
218 218 table ||= table_name
219 219 name_formatter[:order].map {|field| "#{table}.#{field}"}
220 220 end
221 221
222 222 # Return user's full name for display
223 223 def name(formatter = nil)
224 224 f = self.class.name_formatter(formatter)
225 225 if formatter
226 226 eval('"' + f[:string] + '"')
227 227 else
228 228 @name ||= eval('"' + f[:string] + '"')
229 229 end
230 230 end
231 231
232 232 def active?
233 233 self.status == STATUS_ACTIVE
234 234 end
235 235
236 236 def registered?
237 237 self.status == STATUS_REGISTERED
238 238 end
239 239
240 240 def locked?
241 241 self.status == STATUS_LOCKED
242 242 end
243 243
244 244 def activate
245 245 self.status = STATUS_ACTIVE
246 246 end
247 247
248 248 def register
249 249 self.status = STATUS_REGISTERED
250 250 end
251 251
252 252 def lock
253 253 self.status = STATUS_LOCKED
254 254 end
255 255
256 256 def activate!
257 257 update_attribute(:status, STATUS_ACTIVE)
258 258 end
259 259
260 260 def register!
261 261 update_attribute(:status, STATUS_REGISTERED)
262 262 end
263 263
264 264 def lock!
265 265 update_attribute(:status, STATUS_LOCKED)
266 266 end
267 267
268 268 # Returns true if +clear_password+ is the correct user's password, otherwise false
269 269 def check_password?(clear_password)
270 270 if auth_source_id.present?
271 271 auth_source.authenticate(self.login, clear_password)
272 272 else
273 273 User.hash_password("#{salt}#{User.hash_password clear_password}") == hashed_password
274 274 end
275 275 end
276 276
277 277 # Generates a random salt and computes hashed_password for +clear_password+
278 278 # The hashed password is stored in the following form: SHA1(salt + SHA1(password))
279 279 def salt_password(clear_password)
280 280 self.salt = User.generate_salt
281 281 self.hashed_password = User.hash_password("#{salt}#{User.hash_password clear_password}")
282 282 end
283 283
284 284 # Does the backend storage allow this user to change their password?
285 285 def change_password_allowed?
286 286 return true if auth_source.nil?
287 287 return auth_source.allow_password_changes?
288 288 end
289 289
290 290 def must_change_password?
291 291 must_change_passwd? && change_password_allowed?
292 292 end
293 293
294 294 def generate_password?
295 295 generate_password == '1' || generate_password == true
296 296 end
297 297
298 298 # Generate and set a random password on given length
299 299 def random_password(length=40)
300 300 chars = ("a".."z").to_a + ("A".."Z").to_a + ("0".."9").to_a
301 301 chars -= %w(0 O 1 l)
302 302 password = ''
303 303 length.times {|i| password << chars[SecureRandom.random_number(chars.size)] }
304 304 self.password = password
305 305 self.password_confirmation = password
306 306 self
307 307 end
308 308
309 309 def pref
310 310 self.preference ||= UserPreference.new(:user => self)
311 311 end
312 312
313 313 def time_zone
314 314 @time_zone ||= (self.pref.time_zone.blank? ? nil : ActiveSupport::TimeZone[self.pref.time_zone])
315 315 end
316 316
317 317 def force_default_language?
318 318 Setting.force_default_language_for_loggedin?
319 319 end
320 320
321 321 def language
322 322 if force_default_language?
323 323 Setting.default_language
324 324 else
325 325 super
326 326 end
327 327 end
328 328
329 329 def wants_comments_in_reverse_order?
330 330 self.pref[:comments_sorting] == 'desc'
331 331 end
332 332
333 333 # Return user's RSS key (a 40 chars long string), used to access feeds
334 334 def rss_key
335 335 if rss_token.nil?
336 336 create_rss_token(:action => 'feeds')
337 337 end
338 338 rss_token.value
339 339 end
340 340
341 341 # Return user's API key (a 40 chars long string), used to access the API
342 342 def api_key
343 343 if api_token.nil?
344 344 create_api_token(:action => 'api')
345 345 end
346 346 api_token.value
347 347 end
348 348
349 349 # Return an array of project ids for which the user has explicitly turned mail notifications on
350 350 def notified_projects_ids
351 351 @notified_projects_ids ||= memberships.select {|m| m.mail_notification?}.collect(&:project_id)
352 352 end
353 353
354 354 def notified_project_ids=(ids)
355 355 @notified_projects_ids_changed = true
356 356 @notified_projects_ids = ids
357 357 end
358 358
359 359 # Updates per project notifications (after_save callback)
360 360 def update_notified_project_ids
361 361 if @notified_projects_ids_changed
362 362 ids = (mail_notification == 'selected' ? Array.wrap(notified_projects_ids).reject(&:blank?) : [])
363 363 members.update_all(:mail_notification => false)
364 364 members.where(:project_id => ids).update_all(:mail_notification => true) if ids.any?
365 365 end
366 366 end
367 367 private :update_notified_project_ids
368 368
369 369 def valid_notification_options
370 370 self.class.valid_notification_options(self)
371 371 end
372 372
373 373 # Only users that belong to more than 1 project can select projects for which they are notified
374 374 def self.valid_notification_options(user=nil)
375 375 # Note that @user.membership.size would fail since AR ignores
376 376 # :include association option when doing a count
377 377 if user.nil? || user.memberships.length < 1
378 378 MAIL_NOTIFICATION_OPTIONS.reject {|option| option.first == 'selected'}
379 379 else
380 380 MAIL_NOTIFICATION_OPTIONS
381 381 end
382 382 end
383 383
384 384 # Find a user account by matching the exact login and then a case-insensitive
385 385 # version. Exact matches will be given priority.
386 386 def self.find_by_login(login)
387 387 login = Redmine::CodesetUtil.replace_invalid_utf8(login.to_s)
388 388 if login.present?
389 389 # First look for an exact match
390 390 user = where(:login => login).detect {|u| u.login == login}
391 391 unless user
392 392 # Fail over to case-insensitive if none was found
393 393 user = where("LOWER(login) = ?", login.downcase).first
394 394 end
395 395 user
396 396 end
397 397 end
398 398
399 399 def self.find_by_rss_key(key)
400 400 Token.find_active_user('feeds', key)
401 401 end
402 402
403 403 def self.find_by_api_key(key)
404 404 Token.find_active_user('api', key)
405 405 end
406 406
407 407 # Makes find_by_mail case-insensitive
408 408 def self.find_by_mail(mail)
409 409 where("LOWER(mail) = ?", mail.to_s.downcase).first
410 410 end
411 411
412 412 # Returns true if the default admin account can no longer be used
413 413 def self.default_admin_account_changed?
414 414 !User.active.find_by_login("admin").try(:check_password?, "admin")
415 415 end
416 416
417 417 def to_s
418 418 name
419 419 end
420 420
421 421 CSS_CLASS_BY_STATUS = {
422 422 STATUS_ANONYMOUS => 'anon',
423 423 STATUS_ACTIVE => 'active',
424 424 STATUS_REGISTERED => 'registered',
425 425 STATUS_LOCKED => 'locked'
426 426 }
427 427
428 428 def css_classes
429 429 "user #{CSS_CLASS_BY_STATUS[status]}"
430 430 end
431 431
432 432 # Returns the current day according to user's time zone
433 433 def today
434 434 if time_zone.nil?
435 435 Date.today
436 436 else
437 437 Time.now.in_time_zone(time_zone).to_date
438 438 end
439 439 end
440 440
441 441 # Returns the day of +time+ according to user's time zone
442 442 def time_to_date(time)
443 443 if time_zone.nil?
444 444 time.to_date
445 445 else
446 446 time.in_time_zone(time_zone).to_date
447 447 end
448 448 end
449 449
450 450 def logged?
451 451 true
452 452 end
453 453
454 454 def anonymous?
455 455 !logged?
456 456 end
457 457
458 458 # Returns user's membership for the given project
459 459 # or nil if the user is not a member of project
460 460 def membership(project)
461 461 project_id = project.is_a?(Project) ? project.id : project
462 462
463 463 @membership_by_project_id ||= Hash.new {|h, project_id|
464 464 h[project_id] = memberships.where(:project_id => project_id).first
465 465 }
466 466 @membership_by_project_id[project_id]
467 467 end
468 468
469 469 # Returns the user's bult-in role
470 470 def builtin_role
471 471 @builtin_role ||= Role.non_member
472 472 end
473 473
474 474 # Return user's roles for project
475 475 def roles_for_project(project)
476 476 roles = []
477 477 # No role on archived projects
478 478 return roles if project.nil? || project.archived?
479 479 if membership = membership(project)
480 480 roles = membership.roles
481 481 else
482 482 roles << builtin_role
483 483 end
484 484 roles
485 485 end
486 486
487 487 # Return true if the user is a member of project
488 488 def member_of?(project)
489 489 projects.to_a.include?(project)
490 490 end
491 491
492 492 # Returns a hash of user's projects grouped by roles
493 493 def projects_by_role
494 494 return @projects_by_role if @projects_by_role
495 495
496 496 @projects_by_role = Hash.new([])
497 497 memberships.each do |membership|
498 498 if membership.project
499 499 membership.roles.each do |role|
500 500 @projects_by_role[role] = [] unless @projects_by_role.key?(role)
501 501 @projects_by_role[role] << membership.project
502 502 end
503 503 end
504 504 end
505 505 @projects_by_role.each do |role, projects|
506 506 projects.uniq!
507 507 end
508 508
509 509 @projects_by_role
510 510 end
511 511
512 512 # Returns true if user is arg or belongs to arg
513 513 def is_or_belongs_to?(arg)
514 514 if arg.is_a?(User)
515 515 self == arg
516 516 elsif arg.is_a?(Group)
517 517 arg.users.include?(self)
518 518 else
519 519 false
520 520 end
521 521 end
522 522
523 523 # Return true if the user is allowed to do the specified action on a specific context
524 524 # Action can be:
525 525 # * a parameter-like Hash (eg. :controller => 'projects', :action => 'edit')
526 526 # * a permission Symbol (eg. :edit_project)
527 527 # Context can be:
528 528 # * a project : returns true if user is allowed to do the specified action on this project
529 529 # * an array of projects : returns true if user is allowed on every project
530 530 # * nil with options[:global] set : check if user has at least one role allowed for this action,
531 531 # or falls back to Non Member / Anonymous permissions depending if the user is logged
532 532 def allowed_to?(action, context, options={}, &block)
533 533 if context && context.is_a?(Project)
534 534 return false unless context.allows_to?(action)
535 535 # Admin users are authorized for anything else
536 536 return true if admin?
537 537
538 538 roles = roles_for_project(context)
539 539 return false unless roles
540 540 roles.any? {|role|
541 541 (context.is_public? || role.member?) &&
542 542 role.allowed_to?(action) &&
543 543 (block_given? ? yield(role, self) : true)
544 544 }
545 545 elsif context && context.is_a?(Array)
546 546 if context.empty?
547 547 false
548 548 else
549 549 # Authorize if user is authorized on every element of the array
550 550 context.map {|project| allowed_to?(action, project, options, &block)}.reduce(:&)
551 551 end
552 552 elsif options[:global]
553 553 # Admin users are always authorized
554 554 return true if admin?
555 555
556 556 # authorize if user has at least one role that has this permission
557 557 roles = memberships.collect {|m| m.roles}.flatten.uniq
558 558 roles << (self.logged? ? Role.non_member : Role.anonymous)
559 559 roles.any? {|role|
560 560 role.allowed_to?(action) &&
561 561 (block_given? ? yield(role, self) : true)
562 562 }
563 563 else
564 564 false
565 565 end
566 566 end
567 567
568 568 # Is the user allowed to do the specified action on any project?
569 569 # See allowed_to? for the actions and valid options.
570 570 #
571 571 # NB: this method is not used anywhere in the core codebase as of
572 572 # 2.5.2, but it's used by many plugins so if we ever want to remove
573 573 # it it has to be carefully deprecated for a version or two.
574 574 def allowed_to_globally?(action, options={}, &block)
575 575 allowed_to?(action, nil, options.reverse_merge(:global => true), &block)
576 576 end
577 577
578 578 # Returns true if the user is allowed to delete the user's own account
579 579 def own_account_deletable?
580 580 Setting.unsubscribe? &&
581 581 (!admin? || User.active.where("admin = ? AND id <> ?", true, id).exists?)
582 582 end
583 583
584 584 safe_attributes 'login',
585 585 'firstname',
586 586 'lastname',
587 587 'mail',
588 588 'mail_notification',
589 589 'notified_project_ids',
590 590 'language',
591 591 'custom_field_values',
592 592 'custom_fields',
593 593 'identity_url'
594 594
595 595 safe_attributes 'status',
596 596 'auth_source_id',
597 597 'generate_password',
598 598 'must_change_passwd',
599 599 :if => lambda {|user, current_user| current_user.admin?}
600 600
601 601 safe_attributes 'group_ids',
602 602 :if => lambda {|user, current_user| current_user.admin? && !user.new_record?}
603 603
604 604 # Utility method to help check if a user should be notified about an
605 605 # event.
606 606 #
607 607 # TODO: only supports Issue events currently
608 608 def notify_about?(object)
609 609 if mail_notification == 'all'
610 610 true
611 611 elsif mail_notification.blank? || mail_notification == 'none'
612 612 false
613 613 else
614 614 case object
615 615 when Issue
616 616 case mail_notification
617 617 when 'selected', 'only_my_events'
618 618 # user receives notifications for created/assigned issues on unselected projects
619 619 object.author == self || is_or_belongs_to?(object.assigned_to) || is_or_belongs_to?(object.assigned_to_was)
620 620 when 'only_assigned'
621 621 is_or_belongs_to?(object.assigned_to) || is_or_belongs_to?(object.assigned_to_was)
622 622 when 'only_owner'
623 623 object.author == self
624 624 end
625 625 when News
626 626 # always send to project members except when mail_notification is set to 'none'
627 627 true
628 628 end
629 629 end
630 630 end
631 631
632 632 def self.current=(user)
633 633 RequestStore.store[:current_user] = user
634 634 end
635 635
636 636 def self.current
637 637 RequestStore.store[:current_user] ||= User.anonymous
638 638 end
639 639
640 640 # Returns the anonymous user. If the anonymous user does not exist, it is created. There can be only
641 641 # one anonymous user per database.
642 642 def self.anonymous
643 643 anonymous_user = AnonymousUser.first
644 644 if anonymous_user.nil?
645 645 anonymous_user = AnonymousUser.create(:lastname => 'Anonymous', :firstname => '', :mail => '', :login => '', :status => 0)
646 646 raise 'Unable to create the anonymous user.' if anonymous_user.new_record?
647 647 end
648 648 anonymous_user
649 649 end
650 650
651 651 # Salts all existing unsalted passwords
652 652 # It changes password storage scheme from SHA1(password) to SHA1(salt + SHA1(password))
653 653 # This method is used in the SaltPasswords migration and is to be kept as is
654 654 def self.salt_unsalted_passwords!
655 655 transaction do
656 656 User.where("salt IS NULL OR salt = ''").find_each do |user|
657 657 next if user.hashed_password.blank?
658 658 salt = User.generate_salt
659 659 hashed_password = User.hash_password("#{salt}#{user.hashed_password}")
660 660 User.where(:id => user.id).update_all(:salt => salt, :hashed_password => hashed_password)
661 661 end
662 662 end
663 663 end
664 664
665 665 protected
666 666
667 667 def validate_password_length
668 668 return if password.blank? && generate_password?
669 669 # Password length validation based on setting
670 670 if !password.nil? && password.size < Setting.password_min_length.to_i
671 671 errors.add(:password, :too_short, :count => Setting.password_min_length.to_i)
672 672 end
673 673 end
674 674
675 675 private
676 676
677 677 def generate_password_if_needed
678 678 if generate_password? && auth_source.nil?
679 679 length = [Setting.password_min_length.to_i + 2, 10].max
680 680 random_password(length)
681 681 end
682 682 end
683 683
684 # Delete all outstanding password reset tokens on password or email change.
685 # Delete the autologin tokens on password change to prohibit session leakage.
686 # This helps to keep the account secure in case the associated email account
687 # was compromised.
688 def destroy_tokens
689 tokens = []
690 tokens |= ['recovery', 'autologin'] if changes.has_key?('hashed_password')
691 tokens |= ['recovery'] if changes.has_key?('mail')
692
693 Token.delete_all(['user_id = ? AND action IN (?)', self.id, tokens]) if tokens.any?
694 end
695
684 696 # Removes references that are not handled by associations
685 697 # Things that are not deleted are reassociated with the anonymous user
686 698 def remove_references_before_destroy
687 699 return if self.id.nil?
688 700
689 701 substitute = User.anonymous
690 702 Attachment.where(['author_id = ?', id]).update_all(['author_id = ?', substitute.id])
691 703 Comment.where(['author_id = ?', id]).update_all(['author_id = ?', substitute.id])
692 704 Issue.where(['author_id = ?', id]).update_all(['author_id = ?', substitute.id])
693 705 Issue.where(['assigned_to_id = ?', id]).update_all('assigned_to_id = NULL')
694 706 Journal.where(['user_id = ?', id]).update_all(['user_id = ?', substitute.id])
695 707 JournalDetail.
696 708 where(["property = 'attr' AND prop_key = 'assigned_to_id' AND old_value = ?", id.to_s]).
697 709 update_all(['old_value = ?', substitute.id.to_s])
698 710 JournalDetail.
699 711 where(["property = 'attr' AND prop_key = 'assigned_to_id' AND value = ?", id.to_s]).
700 712 update_all(['value = ?', substitute.id.to_s])
701 713 Message.where(['author_id = ?', id]).update_all(['author_id = ?', substitute.id])
702 714 News.where(['author_id = ?', id]).update_all(['author_id = ?', substitute.id])
703 715 # Remove private queries and keep public ones
704 716 ::Query.delete_all ['user_id = ? AND visibility = ?', id, ::Query::VISIBILITY_PRIVATE]
705 717 ::Query.where(['user_id = ?', id]).update_all(['user_id = ?', substitute.id])
706 718 TimeEntry.where(['user_id = ?', id]).update_all(['user_id = ?', substitute.id])
707 719 Token.delete_all ['user_id = ?', id]
708 720 Watcher.delete_all ['user_id = ?', id]
709 721 WikiContent.where(['author_id = ?', id]).update_all(['author_id = ?', substitute.id])
710 722 WikiContent::Version.where(['author_id = ?', id]).update_all(['author_id = ?', substitute.id])
711 723 end
712 724
713 725 # Return password digest
714 726 def self.hash_password(clear_password)
715 727 Digest::SHA1.hexdigest(clear_password || "")
716 728 end
717 729
718 730 # Returns a 128bits random salt as a hex string (32 chars long)
719 731 def self.generate_salt
720 732 Redmine::Utils.random_hex(16)
721 733 end
722 734
723 735 end
724 736
725 737 class AnonymousUser < User
726 738 validate :validate_anonymous_uniqueness, :on => :create
727 739
728 740 def validate_anonymous_uniqueness
729 741 # There should be only one AnonymousUser in the database
730 742 errors.add :base, 'An anonymous user already exists.' if AnonymousUser.exists?
731 743 end
732 744
733 745 def available_custom_fields
734 746 []
735 747 end
736 748
737 749 # Overrides a few properties
738 750 def logged?; false end
739 751 def admin; false end
740 752 def name(*args); I18n.t(:label_user_anonymous) end
741 753 def mail; nil end
742 754 def time_zone; nil end
743 755 def rss_key; nil end
744 756
745 757 def pref
746 758 UserPreference.new(:user => self)
747 759 end
748 760
749 761 # Returns the user's bult-in role
750 762 def builtin_role
751 763 @builtin_role ||= Role.anonymous
752 764 end
753 765
754 766 def membership(*args)
755 767 nil
756 768 end
757 769
758 770 def member_of?(*args)
759 771 false
760 772 end
761 773
762 774 # Anonymous user can not be destroyed
763 775 def destroy
764 776 false
765 777 end
766 778 end
@@ -1,1167 +1,1203
1 1 # Redmine - project management software
2 2 # Copyright (C) 2006-2014 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 UserTest < ActiveSupport::TestCase
21 21 fixtures :users, :members, :projects, :roles, :member_roles, :auth_sources,
22 22 :trackers, :issue_statuses,
23 23 :projects_trackers,
24 24 :watchers,
25 25 :issue_categories, :enumerations, :issues,
26 26 :journals, :journal_details,
27 27 :groups_users,
28 28 :enabled_modules
29 29
30 30 def setup
31 31 @admin = User.find(1)
32 32 @jsmith = User.find(2)
33 33 @dlopper = User.find(3)
34 34 end
35 35
36 36 def test_sorted_scope_should_sort_user_by_display_name
37 37 assert_equal User.all.map(&:name).map(&:downcase).sort,
38 38 User.sorted.map(&:name).map(&:downcase)
39 39 end
40 40
41 41 def test_generate
42 42 User.generate!(:firstname => 'Testing connection')
43 43 User.generate!(:firstname => 'Testing connection')
44 44 assert_equal 2, User.where(:firstname => 'Testing connection').count
45 45 end
46 46
47 47 def test_truth
48 48 assert_kind_of User, @jsmith
49 49 end
50 50
51 51 def test_mail_should_be_stripped
52 52 u = User.new
53 53 u.mail = " foo@bar.com "
54 54 assert_equal "foo@bar.com", u.mail
55 55 end
56 56
57 57 def test_mail_validation
58 58 u = User.new
59 59 u.mail = ''
60 60 assert !u.valid?
61 61 assert_include I18n.translate('activerecord.errors.messages.blank'), u.errors[:mail]
62 62 end
63 63
64 64 def test_login_length_validation
65 65 user = User.new(:firstname => "new", :lastname => "user", :mail => "newuser@somenet.foo")
66 66 user.login = "x" * (User::LOGIN_LENGTH_LIMIT+1)
67 67 assert !user.valid?
68 68
69 69 user.login = "x" * (User::LOGIN_LENGTH_LIMIT)
70 70 assert user.valid?
71 71 assert user.save
72 72 end
73 73
74 74 def test_generate_password_should_respect_minimum_password_length
75 75 with_settings :password_min_length => 15 do
76 76 user = User.generate!(:generate_password => true)
77 77 assert user.password.length >= 15
78 78 end
79 79 end
80 80
81 81 def test_generate_password_should_not_generate_password_with_less_than_10_characters
82 82 with_settings :password_min_length => 4 do
83 83 user = User.generate!(:generate_password => true)
84 84 assert user.password.length >= 10
85 85 end
86 86 end
87 87
88 88 def test_generate_password_on_create_should_set_password
89 89 user = User.new(:firstname => "new", :lastname => "user", :mail => "newuser@somenet.foo")
90 90 user.login = "newuser"
91 91 user.generate_password = true
92 92 assert user.save
93 93
94 94 password = user.password
95 95 assert user.check_password?(password)
96 96 end
97 97
98 98 def test_generate_password_on_update_should_update_password
99 99 user = User.find(2)
100 100 hash = user.hashed_password
101 101 user.generate_password = true
102 102 assert user.save
103 103
104 104 password = user.password
105 105 assert user.check_password?(password)
106 106 assert_not_equal hash, user.reload.hashed_password
107 107 end
108 108
109 109 def test_create
110 110 user = User.new(:firstname => "new", :lastname => "user", :mail => "newuser@somenet.foo")
111 111
112 112 user.login = "jsmith"
113 113 user.password, user.password_confirmation = "password", "password"
114 114 # login uniqueness
115 115 assert !user.save
116 116 assert_equal 1, user.errors.count
117 117
118 118 user.login = "newuser"
119 119 user.password, user.password_confirmation = "password", "pass"
120 120 # password confirmation
121 121 assert !user.save
122 122 assert_equal 1, user.errors.count
123 123
124 124 user.password, user.password_confirmation = "password", "password"
125 125 assert user.save
126 126 end
127 127
128 128 def test_user_before_create_should_set_the_mail_notification_to_the_default_setting
129 129 @user1 = User.generate!
130 130 assert_equal 'only_my_events', @user1.mail_notification
131 131 with_settings :default_notification_option => 'all' do
132 132 @user2 = User.generate!
133 133 assert_equal 'all', @user2.mail_notification
134 134 end
135 135 end
136 136
137 137 def test_user_login_should_be_case_insensitive
138 138 u = User.new(:firstname => "new", :lastname => "user", :mail => "newuser@somenet.foo")
139 139 u.login = 'newuser'
140 140 u.password, u.password_confirmation = "password", "password"
141 141 assert u.save
142 142 u = User.new(:firstname => "Similar", :lastname => "User",
143 143 :mail => "similaruser@somenet.foo")
144 144 u.login = 'NewUser'
145 145 u.password, u.password_confirmation = "password", "password"
146 146 assert !u.save
147 147 assert_include I18n.translate('activerecord.errors.messages.taken'), u.errors[:login]
148 148 end
149 149
150 150 def test_mail_uniqueness_should_not_be_case_sensitive
151 151 u = User.new(:firstname => "new", :lastname => "user", :mail => "newuser@somenet.foo")
152 152 u.login = 'newuser1'
153 153 u.password, u.password_confirmation = "password", "password"
154 154 assert u.save
155 155
156 156 u = User.new(:firstname => "new", :lastname => "user", :mail => "newUser@Somenet.foo")
157 157 u.login = 'newuser2'
158 158 u.password, u.password_confirmation = "password", "password"
159 159 assert !u.save
160 160 assert_include I18n.translate('activerecord.errors.messages.taken'), u.errors[:mail]
161 161 end
162 162
163 163 def test_update
164 164 assert_equal "admin", @admin.login
165 165 @admin.login = "john"
166 166 assert @admin.save, @admin.errors.full_messages.join("; ")
167 167 @admin.reload
168 168 assert_equal "john", @admin.login
169 169 end
170 170
171 171 def test_update_should_not_fail_for_legacy_user_with_different_case_logins
172 172 u1 = User.new(:firstname => "new", :lastname => "user", :mail => "newuser1@somenet.foo")
173 173 u1.login = 'newuser1'
174 174 assert u1.save
175 175
176 176 u2 = User.new(:firstname => "new", :lastname => "user", :mail => "newuser2@somenet.foo")
177 177 u2.login = 'newuser1'
178 178 assert u2.save(:validate => false)
179 179
180 180 user = User.find(u2.id)
181 181 user.firstname = "firstname"
182 182 assert user.save, "Save failed"
183 183 end
184 184
185 185 def test_destroy_should_delete_members_and_roles
186 186 members = Member.where(:user_id => 2)
187 187 ms = members.count
188 188 rs = members.collect(&:roles).flatten.size
189 189 assert ms > 0
190 190 assert rs > 0
191 191 assert_difference 'Member.count', - ms do
192 192 assert_difference 'MemberRole.count', - rs do
193 193 User.find(2).destroy
194 194 end
195 195 end
196 196 assert_nil User.find_by_id(2)
197 197 assert_equal 0, Member.where(:user_id => 2).count
198 198 end
199 199
200 200 def test_destroy_should_update_attachments
201 201 attachment = Attachment.create!(:container => Project.find(1),
202 202 :file => uploaded_test_file("testfile.txt", "text/plain"),
203 203 :author_id => 2)
204 204
205 205 User.find(2).destroy
206 206 assert_nil User.find_by_id(2)
207 207 assert_equal User.anonymous, attachment.reload.author
208 208 end
209 209
210 210 def test_destroy_should_update_comments
211 211 comment = Comment.create!(
212 212 :commented => News.create!(:project_id => 1,
213 213 :author_id => 1, :title => 'foo', :description => 'foo'),
214 214 :author => User.find(2),
215 215 :comments => 'foo'
216 216 )
217 217
218 218 User.find(2).destroy
219 219 assert_nil User.find_by_id(2)
220 220 assert_equal User.anonymous, comment.reload.author
221 221 end
222 222
223 223 def test_destroy_should_update_issues
224 224 issue = Issue.create!(:project_id => 1, :author_id => 2,
225 225 :tracker_id => 1, :subject => 'foo')
226 226
227 227 User.find(2).destroy
228 228 assert_nil User.find_by_id(2)
229 229 assert_equal User.anonymous, issue.reload.author
230 230 end
231 231
232 232 def test_destroy_should_unassign_issues
233 233 issue = Issue.create!(:project_id => 1, :author_id => 1,
234 234 :tracker_id => 1, :subject => 'foo', :assigned_to_id => 2)
235 235
236 236 User.find(2).destroy
237 237 assert_nil User.find_by_id(2)
238 238 assert_nil issue.reload.assigned_to
239 239 end
240 240
241 241 def test_destroy_should_update_journals
242 242 issue = Issue.create!(:project_id => 1, :author_id => 2,
243 243 :tracker_id => 1, :subject => 'foo')
244 244 issue.init_journal(User.find(2), "update")
245 245 issue.save!
246 246
247 247 User.find(2).destroy
248 248 assert_nil User.find_by_id(2)
249 249 assert_equal User.anonymous, issue.journals.first.reload.user
250 250 end
251 251
252 252 def test_destroy_should_update_journal_details_old_value
253 253 issue = Issue.create!(:project_id => 1, :author_id => 1,
254 254 :tracker_id => 1, :subject => 'foo', :assigned_to_id => 2)
255 255 issue.init_journal(User.find(1), "update")
256 256 issue.assigned_to_id = nil
257 257 assert_difference 'JournalDetail.count' do
258 258 issue.save!
259 259 end
260 260 journal_detail = JournalDetail.order('id DESC').first
261 261 assert_equal '2', journal_detail.old_value
262 262
263 263 User.find(2).destroy
264 264 assert_nil User.find_by_id(2)
265 265 assert_equal User.anonymous.id.to_s, journal_detail.reload.old_value
266 266 end
267 267
268 268 def test_destroy_should_update_journal_details_value
269 269 issue = Issue.create!(:project_id => 1, :author_id => 1,
270 270 :tracker_id => 1, :subject => 'foo')
271 271 issue.init_journal(User.find(1), "update")
272 272 issue.assigned_to_id = 2
273 273 assert_difference 'JournalDetail.count' do
274 274 issue.save!
275 275 end
276 276 journal_detail = JournalDetail.order('id DESC').first
277 277 assert_equal '2', journal_detail.value
278 278
279 279 User.find(2).destroy
280 280 assert_nil User.find_by_id(2)
281 281 assert_equal User.anonymous.id.to_s, journal_detail.reload.value
282 282 end
283 283
284 284 def test_destroy_should_update_messages
285 285 board = Board.create!(:project_id => 1, :name => 'Board', :description => 'Board')
286 286 message = Message.create!(:board_id => board.id, :author_id => 2,
287 287 :subject => 'foo', :content => 'foo')
288 288 User.find(2).destroy
289 289 assert_nil User.find_by_id(2)
290 290 assert_equal User.anonymous, message.reload.author
291 291 end
292 292
293 293 def test_destroy_should_update_news
294 294 news = News.create!(:project_id => 1, :author_id => 2,
295 295 :title => 'foo', :description => 'foo')
296 296 User.find(2).destroy
297 297 assert_nil User.find_by_id(2)
298 298 assert_equal User.anonymous, news.reload.author
299 299 end
300 300
301 301 def test_destroy_should_delete_private_queries
302 302 query = Query.new(:name => 'foo', :visibility => Query::VISIBILITY_PRIVATE)
303 303 query.project_id = 1
304 304 query.user_id = 2
305 305 query.save!
306 306
307 307 User.find(2).destroy
308 308 assert_nil User.find_by_id(2)
309 309 assert_nil Query.find_by_id(query.id)
310 310 end
311 311
312 312 def test_destroy_should_update_public_queries
313 313 query = Query.new(:name => 'foo', :visibility => Query::VISIBILITY_PUBLIC)
314 314 query.project_id = 1
315 315 query.user_id = 2
316 316 query.save!
317 317
318 318 User.find(2).destroy
319 319 assert_nil User.find_by_id(2)
320 320 assert_equal User.anonymous, query.reload.user
321 321 end
322 322
323 323 def test_destroy_should_update_time_entries
324 324 entry = TimeEntry.new(:hours => '2', :spent_on => Date.today,
325 325 :activity => TimeEntryActivity.create!(:name => 'foo'))
326 326 entry.project_id = 1
327 327 entry.user_id = 2
328 328 entry.save!
329 329
330 330 User.find(2).destroy
331 331 assert_nil User.find_by_id(2)
332 332 assert_equal User.anonymous, entry.reload.user
333 333 end
334 334
335 335 def test_destroy_should_delete_tokens
336 336 token = Token.create!(:user_id => 2, :value => 'foo')
337 337
338 338 User.find(2).destroy
339 339 assert_nil User.find_by_id(2)
340 340 assert_nil Token.find_by_id(token.id)
341 341 end
342 342
343 343 def test_destroy_should_delete_watchers
344 344 issue = Issue.create!(:project_id => 1, :author_id => 1,
345 345 :tracker_id => 1, :subject => 'foo')
346 346 watcher = Watcher.create!(:user_id => 2, :watchable => issue)
347 347
348 348 User.find(2).destroy
349 349 assert_nil User.find_by_id(2)
350 350 assert_nil Watcher.find_by_id(watcher.id)
351 351 end
352 352
353 353 def test_destroy_should_update_wiki_contents
354 354 wiki_content = WikiContent.create!(
355 355 :text => 'foo',
356 356 :author_id => 2,
357 357 :page => WikiPage.create!(:title => 'Foo',
358 358 :wiki => Wiki.create!(:project_id => 3,
359 359 :start_page => 'Start'))
360 360 )
361 361 wiki_content.text = 'bar'
362 362 assert_difference 'WikiContent::Version.count' do
363 363 wiki_content.save!
364 364 end
365 365
366 366 User.find(2).destroy
367 367 assert_nil User.find_by_id(2)
368 368 assert_equal User.anonymous, wiki_content.reload.author
369 369 wiki_content.versions.each do |version|
370 370 assert_equal User.anonymous, version.reload.author
371 371 end
372 372 end
373 373
374 374 def test_destroy_should_nullify_issue_categories
375 375 category = IssueCategory.create!(:project_id => 1, :assigned_to_id => 2, :name => 'foo')
376 376
377 377 User.find(2).destroy
378 378 assert_nil User.find_by_id(2)
379 379 assert_nil category.reload.assigned_to_id
380 380 end
381 381
382 382 def test_destroy_should_nullify_changesets
383 383 changeset = Changeset.create!(
384 384 :repository => Repository::Subversion.create!(
385 385 :project_id => 1,
386 386 :url => 'file:///tmp',
387 387 :identifier => 'tmp'
388 388 ),
389 389 :revision => '12',
390 390 :committed_on => Time.now,
391 391 :committer => 'jsmith'
392 392 )
393 393 assert_equal 2, changeset.user_id
394 394
395 395 User.find(2).destroy
396 396 assert_nil User.find_by_id(2)
397 397 assert_nil changeset.reload.user_id
398 398 end
399 399
400 400 def test_anonymous_user_should_not_be_destroyable
401 401 assert_no_difference 'User.count' do
402 402 assert_equal false, User.anonymous.destroy
403 403 end
404 404 end
405 405
406 def test_password_change_should_destroy_tokens
407 recovery_token = Token.create!(:user_id => 2, :action => 'recovery')
408 autologin_token = Token.create!(:user_id => 2, :action => 'autologin')
409
410 user = User.find(2)
411 user.password, user.password_confirmation = "a new password", "a new password"
412 assert user.save
413
414 assert_nil Token.find_by_id(recovery_token.id)
415 assert_nil Token.find_by_id(autologin_token.id)
416 end
417
418 def test_mail_change_should_destroy_tokens
419 recovery_token = Token.create!(:user_id => 2, :action => 'recovery')
420 autologin_token = Token.create!(:user_id => 2, :action => 'autologin')
421
422 user = User.find(2)
423 user.mail = "user@somwehere.com"
424 assert user.save
425
426 assert_nil Token.find_by_id(recovery_token.id)
427 assert_equal autologin_token, Token.find_by_id(autologin_token.id)
428 end
429
430 def test_change_on_other_fields_should_not_destroy_tokens
431 recovery_token = Token.create!(:user_id => 2, :action => 'recovery')
432 autologin_token = Token.create!(:user_id => 2, :action => 'autologin')
433
434 user = User.find(2)
435 user.firstname = "Bobby"
436 assert user.save
437
438 assert_equal recovery_token, Token.find_by_id(recovery_token.id)
439 assert_equal autologin_token, Token.find_by_id(autologin_token.id)
440 end
441
406 442 def test_validate_login_presence
407 443 @admin.login = ""
408 444 assert !@admin.save
409 445 assert_equal 1, @admin.errors.count
410 446 end
411 447
412 448 def test_validate_mail_notification_inclusion
413 449 u = User.new
414 450 u.mail_notification = 'foo'
415 451 u.save
416 452 assert_not_equal [], u.errors[:mail_notification]
417 453 end
418 454
419 455 def test_password
420 456 user = User.try_to_login("admin", "admin")
421 457 assert_kind_of User, user
422 458 assert_equal "admin", user.login
423 459 user.password = "hello123"
424 460 assert user.save
425 461
426 462 user = User.try_to_login("admin", "hello123")
427 463 assert_kind_of User, user
428 464 assert_equal "admin", user.login
429 465 end
430 466
431 467 def test_validate_password_length
432 468 with_settings :password_min_length => '100' do
433 469 user = User.new(:firstname => "new100",
434 470 :lastname => "user100", :mail => "newuser100@somenet.foo")
435 471 user.login = "newuser100"
436 472 user.password, user.password_confirmation = "password100", "password100"
437 473 assert !user.save
438 474 assert_equal 1, user.errors.count
439 475 end
440 476 end
441 477
442 478 def test_name_format
443 479 assert_equal 'John S.', @jsmith.name(:firstname_lastinitial)
444 480 assert_equal 'Smith, John', @jsmith.name(:lastname_coma_firstname)
445 481 assert_equal 'J. Smith', @jsmith.name(:firstinitial_lastname)
446 482 assert_equal 'J.-P. Lang', User.new(:firstname => 'Jean-Philippe', :lastname => 'Lang').name(:firstinitial_lastname)
447 483 end
448 484
449 485 def test_name_should_use_setting_as_default_format
450 486 with_settings :user_format => :firstname_lastname do
451 487 assert_equal 'John Smith', @jsmith.reload.name
452 488 end
453 489 with_settings :user_format => :username do
454 490 assert_equal 'jsmith', @jsmith.reload.name
455 491 end
456 492 with_settings :user_format => :lastname do
457 493 assert_equal 'Smith', @jsmith.reload.name
458 494 end
459 495 end
460 496
461 497 def test_today_should_return_the_day_according_to_user_time_zone
462 498 preference = User.find(1).pref
463 499 date = Date.new(2012, 05, 15)
464 500 time = Time.gm(2012, 05, 15, 23, 30).utc # 2012-05-15 23:30 UTC
465 501 Date.stubs(:today).returns(date)
466 502 Time.stubs(:now).returns(time)
467 503
468 504 preference.update_attribute :time_zone, 'Baku' # UTC+4
469 505 assert_equal '2012-05-16', User.find(1).today.to_s
470 506
471 507 preference.update_attribute :time_zone, 'La Paz' # UTC-4
472 508 assert_equal '2012-05-15', User.find(1).today.to_s
473 509
474 510 preference.update_attribute :time_zone, ''
475 511 assert_equal '2012-05-15', User.find(1).today.to_s
476 512 end
477 513
478 514 def test_time_to_date_should_return_the_date_according_to_user_time_zone
479 515 preference = User.find(1).pref
480 516 time = Time.gm(2012, 05, 15, 23, 30).utc # 2012-05-15 23:30 UTC
481 517
482 518 preference.update_attribute :time_zone, 'Baku' # UTC+4
483 519 assert_equal '2012-05-16', User.find(1).time_to_date(time).to_s
484 520
485 521 preference.update_attribute :time_zone, 'La Paz' # UTC-4
486 522 assert_equal '2012-05-15', User.find(1).time_to_date(time).to_s
487 523
488 524 preference.update_attribute :time_zone, ''
489 525 assert_equal '2012-05-15', User.find(1).time_to_date(time).to_s
490 526 end
491 527
492 528 def test_fields_for_order_statement_should_return_fields_according_user_format_setting
493 529 with_settings :user_format => 'lastname_coma_firstname' do
494 530 assert_equal ['users.lastname', 'users.firstname', 'users.id'],
495 531 User.fields_for_order_statement
496 532 end
497 533 end
498 534
499 535 def test_fields_for_order_statement_width_table_name_should_prepend_table_name
500 536 with_settings :user_format => 'lastname_firstname' do
501 537 assert_equal ['authors.lastname', 'authors.firstname', 'authors.id'],
502 538 User.fields_for_order_statement('authors')
503 539 end
504 540 end
505 541
506 542 def test_fields_for_order_statement_with_blank_format_should_return_default
507 543 with_settings :user_format => '' do
508 544 assert_equal ['users.firstname', 'users.lastname', 'users.id'],
509 545 User.fields_for_order_statement
510 546 end
511 547 end
512 548
513 549 def test_fields_for_order_statement_with_invalid_format_should_return_default
514 550 with_settings :user_format => 'foo' do
515 551 assert_equal ['users.firstname', 'users.lastname', 'users.id'],
516 552 User.fields_for_order_statement
517 553 end
518 554 end
519 555
520 556 test ".try_to_login with good credentials should return the user" do
521 557 user = User.try_to_login("admin", "admin")
522 558 assert_kind_of User, user
523 559 assert_equal "admin", user.login
524 560 end
525 561
526 562 test ".try_to_login with wrong credentials should return nil" do
527 563 assert_nil User.try_to_login("admin", "foo")
528 564 end
529 565
530 566 def test_try_to_login_with_locked_user_should_return_nil
531 567 @jsmith.status = User::STATUS_LOCKED
532 568 @jsmith.save!
533 569
534 570 user = User.try_to_login("jsmith", "jsmith")
535 571 assert_equal nil, user
536 572 end
537 573
538 574 def test_try_to_login_with_locked_user_and_not_active_only_should_return_user
539 575 @jsmith.status = User::STATUS_LOCKED
540 576 @jsmith.save!
541 577
542 578 user = User.try_to_login("jsmith", "jsmith", false)
543 579 assert_equal @jsmith, user
544 580 end
545 581
546 582 test ".try_to_login should fall-back to case-insensitive if user login is not found as-typed" do
547 583 user = User.try_to_login("AdMin", "admin")
548 584 assert_kind_of User, user
549 585 assert_equal "admin", user.login
550 586 end
551 587
552 588 test ".try_to_login should select the exact matching user first" do
553 589 case_sensitive_user = User.generate! do |user|
554 590 user.password = "admin123"
555 591 end
556 592 # bypass validations to make it appear like existing data
557 593 case_sensitive_user.update_attribute(:login, 'ADMIN')
558 594
559 595 user = User.try_to_login("ADMIN", "admin123")
560 596 assert_kind_of User, user
561 597 assert_equal "ADMIN", user.login
562 598 end
563 599
564 600 if ldap_configured?
565 601 context "#try_to_login using LDAP" do
566 602 context "with failed connection to the LDAP server" do
567 603 should "return nil" do
568 604 @auth_source = AuthSourceLdap.find(1)
569 605 AuthSource.any_instance.stubs(:initialize_ldap_con).raises(Net::LDAP::LdapError, 'Cannot connect')
570 606
571 607 assert_equal nil, User.try_to_login('edavis', 'wrong')
572 608 end
573 609 end
574 610
575 611 context "with an unsuccessful authentication" do
576 612 should "return nil" do
577 613 assert_equal nil, User.try_to_login('edavis', 'wrong')
578 614 end
579 615 end
580 616
581 617 context "binding with user's account" do
582 618 setup do
583 619 @auth_source = AuthSourceLdap.find(1)
584 620 @auth_source.account = "uid=$login,ou=Person,dc=redmine,dc=org"
585 621 @auth_source.account_password = ''
586 622 @auth_source.save!
587 623
588 624 @ldap_user = User.new(:mail => 'example1@redmine.org', :firstname => 'LDAP', :lastname => 'user', :auth_source_id => 1)
589 625 @ldap_user.login = 'example1'
590 626 @ldap_user.save!
591 627 end
592 628
593 629 context "with a successful authentication" do
594 630 should "return the user" do
595 631 assert_equal @ldap_user, User.try_to_login('example1', '123456')
596 632 end
597 633 end
598 634
599 635 context "with an unsuccessful authentication" do
600 636 should "return nil" do
601 637 assert_nil User.try_to_login('example1', '11111')
602 638 end
603 639 end
604 640 end
605 641
606 642 context "on the fly registration" do
607 643 setup do
608 644 @auth_source = AuthSourceLdap.find(1)
609 645 @auth_source.update_attribute :onthefly_register, true
610 646 end
611 647
612 648 context "with a successful authentication" do
613 649 should "create a new user account if it doesn't exist" do
614 650 assert_difference('User.count') do
615 651 user = User.try_to_login('edavis', '123456')
616 652 assert !user.admin?
617 653 end
618 654 end
619 655
620 656 should "retrieve existing user" do
621 657 user = User.try_to_login('edavis', '123456')
622 658 user.admin = true
623 659 user.save!
624 660
625 661 assert_no_difference('User.count') do
626 662 user = User.try_to_login('edavis', '123456')
627 663 assert user.admin?
628 664 end
629 665 end
630 666 end
631 667
632 668 context "binding with user's account" do
633 669 setup do
634 670 @auth_source = AuthSourceLdap.find(1)
635 671 @auth_source.account = "uid=$login,ou=Person,dc=redmine,dc=org"
636 672 @auth_source.account_password = ''
637 673 @auth_source.save!
638 674 end
639 675
640 676 context "with a successful authentication" do
641 677 should "create a new user account if it doesn't exist" do
642 678 assert_difference('User.count') do
643 679 user = User.try_to_login('example1', '123456')
644 680 assert_kind_of User, user
645 681 end
646 682 end
647 683 end
648 684
649 685 context "with an unsuccessful authentication" do
650 686 should "return nil" do
651 687 assert_nil User.try_to_login('example1', '11111')
652 688 end
653 689 end
654 690 end
655 691 end
656 692 end
657 693
658 694 else
659 695 puts "Skipping LDAP tests."
660 696 end
661 697
662 698 def test_create_anonymous
663 699 AnonymousUser.delete_all
664 700 anon = User.anonymous
665 701 assert !anon.new_record?
666 702 assert_kind_of AnonymousUser, anon
667 703 end
668 704
669 705 def test_ensure_single_anonymous_user
670 706 AnonymousUser.delete_all
671 707 anon1 = User.anonymous
672 708 assert !anon1.new_record?
673 709 assert_kind_of AnonymousUser, anon1
674 710 anon2 = AnonymousUser.create(
675 711 :lastname => 'Anonymous', :firstname => '',
676 712 :mail => '', :login => '', :status => 0)
677 713 assert_equal 1, anon2.errors.count
678 714 end
679 715
680 716 def test_rss_key
681 717 assert_nil @jsmith.rss_token
682 718 key = @jsmith.rss_key
683 719 assert_equal 40, key.length
684 720
685 721 @jsmith.reload
686 722 assert_equal key, @jsmith.rss_key
687 723 end
688 724
689 725 def test_rss_key_should_not_be_generated_twice
690 726 assert_difference 'Token.count', 1 do
691 727 key1 = @jsmith.rss_key
692 728 key2 = @jsmith.rss_key
693 729 assert_equal key1, key2
694 730 end
695 731 end
696 732
697 733 def test_api_key_should_not_be_generated_twice
698 734 assert_difference 'Token.count', 1 do
699 735 key1 = @jsmith.api_key
700 736 key2 = @jsmith.api_key
701 737 assert_equal key1, key2
702 738 end
703 739 end
704 740
705 741 test "#api_key should generate a new one if the user doesn't have one" do
706 742 user = User.generate!(:api_token => nil)
707 743 assert_nil user.api_token
708 744
709 745 key = user.api_key
710 746 assert_equal 40, key.length
711 747 user.reload
712 748 assert_equal key, user.api_key
713 749 end
714 750
715 751 test "#api_key should return the existing api token value" do
716 752 user = User.generate!
717 753 token = Token.create!(:action => 'api')
718 754 user.api_token = token
719 755 assert user.save
720 756
721 757 assert_equal token.value, user.api_key
722 758 end
723 759
724 760 test "#find_by_api_key should return nil if no matching key is found" do
725 761 assert_nil User.find_by_api_key('zzzzzzzzz')
726 762 end
727 763
728 764 test "#find_by_api_key should return nil if the key is found for an inactive user" do
729 765 user = User.generate!
730 766 user.status = User::STATUS_LOCKED
731 767 token = Token.create!(:action => 'api')
732 768 user.api_token = token
733 769 user.save
734 770
735 771 assert_nil User.find_by_api_key(token.value)
736 772 end
737 773
738 774 test "#find_by_api_key should return the user if the key is found for an active user" do
739 775 user = User.generate!
740 776 token = Token.create!(:action => 'api')
741 777 user.api_token = token
742 778 user.save
743 779
744 780 assert_equal user, User.find_by_api_key(token.value)
745 781 end
746 782
747 783 def test_default_admin_account_changed_should_return_false_if_account_was_not_changed
748 784 user = User.find_by_login("admin")
749 785 user.password = "admin"
750 786 assert user.save(:validate => false)
751 787
752 788 assert_equal false, User.default_admin_account_changed?
753 789 end
754 790
755 791 def test_default_admin_account_changed_should_return_true_if_password_was_changed
756 792 user = User.find_by_login("admin")
757 793 user.password = "newpassword"
758 794 user.save!
759 795
760 796 assert_equal true, User.default_admin_account_changed?
761 797 end
762 798
763 799 def test_default_admin_account_changed_should_return_true_if_account_is_disabled
764 800 user = User.find_by_login("admin")
765 801 user.password = "admin"
766 802 user.status = User::STATUS_LOCKED
767 803 assert user.save(:validate => false)
768 804
769 805 assert_equal true, User.default_admin_account_changed?
770 806 end
771 807
772 808 def test_default_admin_account_changed_should_return_true_if_account_does_not_exist
773 809 user = User.find_by_login("admin")
774 810 user.destroy
775 811
776 812 assert_equal true, User.default_admin_account_changed?
777 813 end
778 814
779 815 def test_membership_with_project_should_return_membership
780 816 project = Project.find(1)
781 817
782 818 membership = @jsmith.membership(project)
783 819 assert_kind_of Member, membership
784 820 assert_equal @jsmith, membership.user
785 821 assert_equal project, membership.project
786 822 end
787 823
788 824 def test_membership_with_project_id_should_return_membership
789 825 project = Project.find(1)
790 826
791 827 membership = @jsmith.membership(1)
792 828 assert_kind_of Member, membership
793 829 assert_equal @jsmith, membership.user
794 830 assert_equal project, membership.project
795 831 end
796 832
797 833 def test_membership_for_non_member_should_return_nil
798 834 project = Project.find(1)
799 835
800 836 user = User.generate!
801 837 membership = user.membership(1)
802 838 assert_nil membership
803 839 end
804 840
805 841 def test_roles_for_project
806 842 # user with a role
807 843 roles = @jsmith.roles_for_project(Project.find(1))
808 844 assert_kind_of Role, roles.first
809 845 assert_equal "Manager", roles.first.name
810 846
811 847 # user with no role
812 848 assert_nil @dlopper.roles_for_project(Project.find(2)).detect {|role| role.member?}
813 849 end
814 850
815 851 def test_projects_by_role_for_user_with_role
816 852 user = User.find(2)
817 853 assert_kind_of Hash, user.projects_by_role
818 854 assert_equal 2, user.projects_by_role.size
819 855 assert_equal [1,5], user.projects_by_role[Role.find(1)].collect(&:id).sort
820 856 assert_equal [2], user.projects_by_role[Role.find(2)].collect(&:id).sort
821 857 end
822 858
823 859 def test_accessing_projects_by_role_with_no_projects_should_return_an_empty_array
824 860 user = User.find(2)
825 861 assert_equal [], user.projects_by_role[Role.find(3)]
826 862 # should not update the hash
827 863 assert_nil user.projects_by_role.values.detect(&:blank?)
828 864 end
829 865
830 866 def test_projects_by_role_for_user_with_no_role
831 867 user = User.generate!
832 868 assert_equal({}, user.projects_by_role)
833 869 end
834 870
835 871 def test_projects_by_role_for_anonymous
836 872 assert_equal({}, User.anonymous.projects_by_role)
837 873 end
838 874
839 875 def test_valid_notification_options
840 876 # without memberships
841 877 assert_equal 5, User.find(7).valid_notification_options.size
842 878 # with memberships
843 879 assert_equal 6, User.find(2).valid_notification_options.size
844 880 end
845 881
846 882 def test_valid_notification_options_class_method
847 883 assert_equal 5, User.valid_notification_options.size
848 884 assert_equal 5, User.valid_notification_options(User.find(7)).size
849 885 assert_equal 6, User.valid_notification_options(User.find(2)).size
850 886 end
851 887
852 888 def test_mail_notification_all
853 889 @jsmith.mail_notification = 'all'
854 890 @jsmith.notified_project_ids = []
855 891 @jsmith.save
856 892 @jsmith.reload
857 893 assert @jsmith.projects.first.recipients.include?(@jsmith.mail)
858 894 end
859 895
860 896 def test_mail_notification_selected
861 897 @jsmith.mail_notification = 'selected'
862 898 @jsmith.notified_project_ids = [1]
863 899 @jsmith.save
864 900 @jsmith.reload
865 901 assert Project.find(1).recipients.include?(@jsmith.mail)
866 902 end
867 903
868 904 def test_mail_notification_only_my_events
869 905 @jsmith.mail_notification = 'only_my_events'
870 906 @jsmith.notified_project_ids = []
871 907 @jsmith.save
872 908 @jsmith.reload
873 909 assert !@jsmith.projects.first.recipients.include?(@jsmith.mail)
874 910 end
875 911
876 912 def test_comments_sorting_preference
877 913 assert !@jsmith.wants_comments_in_reverse_order?
878 914 @jsmith.pref.comments_sorting = 'asc'
879 915 assert !@jsmith.wants_comments_in_reverse_order?
880 916 @jsmith.pref.comments_sorting = 'desc'
881 917 assert @jsmith.wants_comments_in_reverse_order?
882 918 end
883 919
884 920 def test_find_by_mail_should_be_case_insensitive
885 921 u = User.find_by_mail('JSmith@somenet.foo')
886 922 assert_not_nil u
887 923 assert_equal 'jsmith@somenet.foo', u.mail
888 924 end
889 925
890 926 def test_random_password
891 927 u = User.new
892 928 u.random_password
893 929 assert !u.password.blank?
894 930 assert !u.password_confirmation.blank?
895 931 end
896 932
897 933 test "#change_password_allowed? should be allowed if no auth source is set" do
898 934 user = User.generate!
899 935 assert user.change_password_allowed?
900 936 end
901 937
902 938 test "#change_password_allowed? should delegate to the auth source" do
903 939 user = User.generate!
904 940
905 941 allowed_auth_source = AuthSource.generate!
906 942 def allowed_auth_source.allow_password_changes?; true; end
907 943
908 944 denied_auth_source = AuthSource.generate!
909 945 def denied_auth_source.allow_password_changes?; false; end
910 946
911 947 assert user.change_password_allowed?
912 948
913 949 user.auth_source = allowed_auth_source
914 950 assert user.change_password_allowed?, "User not allowed to change password, though auth source does"
915 951
916 952 user.auth_source = denied_auth_source
917 953 assert !user.change_password_allowed?, "User allowed to change password, though auth source does not"
918 954 end
919 955
920 956 def test_own_account_deletable_should_be_true_with_unsubscrive_enabled
921 957 with_settings :unsubscribe => '1' do
922 958 assert_equal true, User.find(2).own_account_deletable?
923 959 end
924 960 end
925 961
926 962 def test_own_account_deletable_should_be_false_with_unsubscrive_disabled
927 963 with_settings :unsubscribe => '0' do
928 964 assert_equal false, User.find(2).own_account_deletable?
929 965 end
930 966 end
931 967
932 968 def test_own_account_deletable_should_be_false_for_a_single_admin
933 969 User.delete_all(["admin = ? AND id <> ?", true, 1])
934 970
935 971 with_settings :unsubscribe => '1' do
936 972 assert_equal false, User.find(1).own_account_deletable?
937 973 end
938 974 end
939 975
940 976 def test_own_account_deletable_should_be_true_for_an_admin_if_other_admin_exists
941 977 User.generate! do |user|
942 978 user.admin = true
943 979 end
944 980
945 981 with_settings :unsubscribe => '1' do
946 982 assert_equal true, User.find(1).own_account_deletable?
947 983 end
948 984 end
949 985
950 986 context "#allowed_to?" do
951 987 context "with a unique project" do
952 988 should "return false if project is archived" do
953 989 project = Project.find(1)
954 990 Project.any_instance.stubs(:status).returns(Project::STATUS_ARCHIVED)
955 991 assert_equal false, @admin.allowed_to?(:view_issues, Project.find(1))
956 992 end
957 993
958 994 should "return false for write action if project is closed" do
959 995 project = Project.find(1)
960 996 Project.any_instance.stubs(:status).returns(Project::STATUS_CLOSED)
961 997 assert_equal false, @admin.allowed_to?(:edit_project, Project.find(1))
962 998 end
963 999
964 1000 should "return true for read action if project is closed" do
965 1001 project = Project.find(1)
966 1002 Project.any_instance.stubs(:status).returns(Project::STATUS_CLOSED)
967 1003 assert_equal true, @admin.allowed_to?(:view_project, Project.find(1))
968 1004 end
969 1005
970 1006 should "return false if related module is disabled" do
971 1007 project = Project.find(1)
972 1008 project.enabled_module_names = ["issue_tracking"]
973 1009 assert_equal true, @admin.allowed_to?(:add_issues, project)
974 1010 assert_equal false, @admin.allowed_to?(:view_wiki_pages, project)
975 1011 end
976 1012
977 1013 should "authorize nearly everything for admin users" do
978 1014 project = Project.find(1)
979 1015 assert ! @admin.member_of?(project)
980 1016 %w(edit_issues delete_issues manage_news add_documents manage_wiki).each do |p|
981 1017 assert_equal true, @admin.allowed_to?(p.to_sym, project)
982 1018 end
983 1019 end
984 1020
985 1021 should "authorize normal users depending on their roles" do
986 1022 project = Project.find(1)
987 1023 assert_equal true, @jsmith.allowed_to?(:delete_messages, project) #Manager
988 1024 assert_equal false, @dlopper.allowed_to?(:delete_messages, project) #Developper
989 1025 end
990 1026 end
991 1027
992 1028 context "with multiple projects" do
993 1029 should "return false if array is empty" do
994 1030 assert_equal false, @admin.allowed_to?(:view_project, [])
995 1031 end
996 1032
997 1033 should "return true only if user has permission on all these projects" do
998 1034 assert_equal true, @admin.allowed_to?(:view_project, Project.all)
999 1035 assert_equal false, @dlopper.allowed_to?(:view_project, Project.all) #cannot see Project(2)
1000 1036 assert_equal true, @jsmith.allowed_to?(:edit_issues, @jsmith.projects) #Manager or Developer everywhere
1001 1037 assert_equal false, @jsmith.allowed_to?(:delete_issue_watchers, @jsmith.projects) #Dev cannot delete_issue_watchers
1002 1038 end
1003 1039
1004 1040 should "behave correctly with arrays of 1 project" do
1005 1041 assert_equal false, User.anonymous.allowed_to?(:delete_issues, [Project.first])
1006 1042 end
1007 1043 end
1008 1044
1009 1045 context "with options[:global]" do
1010 1046 should "authorize if user has at least one role that has this permission" do
1011 1047 @dlopper2 = User.find(5) #only Developper on a project, not Manager anywhere
1012 1048 @anonymous = User.find(6)
1013 1049 assert_equal true, @jsmith.allowed_to?(:delete_issue_watchers, nil, :global => true)
1014 1050 assert_equal false, @dlopper2.allowed_to?(:delete_issue_watchers, nil, :global => true)
1015 1051 assert_equal true, @dlopper2.allowed_to?(:add_issues, nil, :global => true)
1016 1052 assert_equal false, @anonymous.allowed_to?(:add_issues, nil, :global => true)
1017 1053 assert_equal true, @anonymous.allowed_to?(:view_issues, nil, :global => true)
1018 1054 end
1019 1055 end
1020 1056 end
1021 1057
1022 1058 # this is just a proxy method, the test only calls it to ensure it doesn't break trivially
1023 1059 context "#allowed_to_globally?" do
1024 1060 should "proxy to #allowed_to? and reflect global permissions" do
1025 1061 @dlopper2 = User.find(5) #only Developper on a project, not Manager anywhere
1026 1062 @anonymous = User.find(6)
1027 1063 assert_equal true, @jsmith.allowed_to_globally?(:delete_issue_watchers)
1028 1064 assert_equal false, @dlopper2.allowed_to_globally?(:delete_issue_watchers)
1029 1065 assert_equal true, @dlopper2.allowed_to_globally?(:add_issues)
1030 1066 assert_equal false, @anonymous.allowed_to_globally?(:add_issues)
1031 1067 assert_equal true, @anonymous.allowed_to_globally?(:view_issues)
1032 1068 end
1033 1069 end
1034 1070
1035 1071 context "User#notify_about?" do
1036 1072 context "Issues" do
1037 1073 setup do
1038 1074 @project = Project.find(1)
1039 1075 @author = User.generate!
1040 1076 @assignee = User.generate!
1041 1077 @issue = Issue.generate!(:project => @project, :assigned_to => @assignee, :author => @author)
1042 1078 end
1043 1079
1044 1080 should "be true for a user with :all" do
1045 1081 @author.update_attribute(:mail_notification, 'all')
1046 1082 assert @author.notify_about?(@issue)
1047 1083 end
1048 1084
1049 1085 should "be false for a user with :none" do
1050 1086 @author.update_attribute(:mail_notification, 'none')
1051 1087 assert ! @author.notify_about?(@issue)
1052 1088 end
1053 1089
1054 1090 should "be false for a user with :only_my_events and isn't an author, creator, or assignee" do
1055 1091 @user = User.generate!(:mail_notification => 'only_my_events')
1056 1092 Member.create!(:user => @user, :project => @project, :role_ids => [1])
1057 1093 assert ! @user.notify_about?(@issue)
1058 1094 end
1059 1095
1060 1096 should "be true for a user with :only_my_events and is the author" do
1061 1097 @author.update_attribute(:mail_notification, 'only_my_events')
1062 1098 assert @author.notify_about?(@issue)
1063 1099 end
1064 1100
1065 1101 should "be true for a user with :only_my_events and is the assignee" do
1066 1102 @assignee.update_attribute(:mail_notification, 'only_my_events')
1067 1103 assert @assignee.notify_about?(@issue)
1068 1104 end
1069 1105
1070 1106 should "be true for a user with :only_assigned and is the assignee" do
1071 1107 @assignee.update_attribute(:mail_notification, 'only_assigned')
1072 1108 assert @assignee.notify_about?(@issue)
1073 1109 end
1074 1110
1075 1111 should "be false for a user with :only_assigned and is not the assignee" do
1076 1112 @author.update_attribute(:mail_notification, 'only_assigned')
1077 1113 assert ! @author.notify_about?(@issue)
1078 1114 end
1079 1115
1080 1116 should "be true for a user with :only_owner and is the author" do
1081 1117 @author.update_attribute(:mail_notification, 'only_owner')
1082 1118 assert @author.notify_about?(@issue)
1083 1119 end
1084 1120
1085 1121 should "be false for a user with :only_owner and is not the author" do
1086 1122 @assignee.update_attribute(:mail_notification, 'only_owner')
1087 1123 assert ! @assignee.notify_about?(@issue)
1088 1124 end
1089 1125
1090 1126 should "be true for a user with :selected and is the author" do
1091 1127 @author.update_attribute(:mail_notification, 'selected')
1092 1128 assert @author.notify_about?(@issue)
1093 1129 end
1094 1130
1095 1131 should "be true for a user with :selected and is the assignee" do
1096 1132 @assignee.update_attribute(:mail_notification, 'selected')
1097 1133 assert @assignee.notify_about?(@issue)
1098 1134 end
1099 1135
1100 1136 should "be false for a user with :selected and is not the author or assignee" do
1101 1137 @user = User.generate!(:mail_notification => 'selected')
1102 1138 Member.create!(:user => @user, :project => @project, :role_ids => [1])
1103 1139 assert ! @user.notify_about?(@issue)
1104 1140 end
1105 1141 end
1106 1142 end
1107 1143
1108 1144 def test_notify_about_news
1109 1145 user = User.generate!
1110 1146 news = News.new
1111 1147
1112 1148 User::MAIL_NOTIFICATION_OPTIONS.map(&:first).each do |option|
1113 1149 user.mail_notification = option
1114 1150 assert_equal (option != 'none'), user.notify_about?(news)
1115 1151 end
1116 1152 end
1117 1153
1118 1154 def test_salt_unsalted_passwords
1119 1155 # Restore a user with an unsalted password
1120 1156 user = User.find(1)
1121 1157 user.salt = nil
1122 1158 user.hashed_password = User.hash_password("unsalted")
1123 1159 user.save!
1124 1160
1125 1161 User.salt_unsalted_passwords!
1126 1162
1127 1163 user.reload
1128 1164 # Salt added
1129 1165 assert !user.salt.blank?
1130 1166 # Password still valid
1131 1167 assert user.check_password?("unsalted")
1132 1168 assert_equal user, User.try_to_login(user.login, "unsalted")
1133 1169 end
1134 1170
1135 1171 if Object.const_defined?(:OpenID)
1136 1172 def test_setting_identity_url
1137 1173 normalized_open_id_url = 'http://example.com/'
1138 1174 u = User.new( :identity_url => 'http://example.com/' )
1139 1175 assert_equal normalized_open_id_url, u.identity_url
1140 1176 end
1141 1177
1142 1178 def test_setting_identity_url_without_trailing_slash
1143 1179 normalized_open_id_url = 'http://example.com/'
1144 1180 u = User.new( :identity_url => 'http://example.com' )
1145 1181 assert_equal normalized_open_id_url, u.identity_url
1146 1182 end
1147 1183
1148 1184 def test_setting_identity_url_without_protocol
1149 1185 normalized_open_id_url = 'http://example.com/'
1150 1186 u = User.new( :identity_url => 'example.com' )
1151 1187 assert_equal normalized_open_id_url, u.identity_url
1152 1188 end
1153 1189
1154 1190 def test_setting_blank_identity_url
1155 1191 u = User.new( :identity_url => 'example.com' )
1156 1192 u.identity_url = ''
1157 1193 assert u.identity_url.blank?
1158 1194 end
1159 1195
1160 1196 def test_setting_invalid_identity_url
1161 1197 u = User.new( :identity_url => 'this is not an openid url' )
1162 1198 assert u.identity_url.blank?
1163 1199 end
1164 1200 else
1165 1201 puts "Skipping openid tests."
1166 1202 end
1167 1203 end
General Comments 0
You need to be logged in to leave comments. Login now