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