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