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