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