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