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