##// END OF EJS Templates
When destroying a user, remove all references to that user (#7296)....
Jean-Philippe Lang -
r4606:e809d40f4ed9
parent child
Show More
@@ -1,501 +1,533
1 1 # Redmine - project management software
2 2 # Copyright (C) 2006-2009 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 USER_FORMATS = {
30 30 :firstname_lastname => '#{firstname} #{lastname}',
31 31 :firstname => '#{firstname}',
32 32 :lastname_firstname => '#{lastname} #{firstname}',
33 33 :lastname_coma_firstname => '#{lastname}, #{firstname}',
34 34 :username => '#{login}'
35 35 }
36 36
37 37 MAIL_NOTIFICATION_OPTIONS = [
38 38 ['all', :label_user_mail_option_all],
39 39 ['selected', :label_user_mail_option_selected],
40 40 ['only_my_events', :label_user_mail_option_only_my_events],
41 41 ['only_assigned', :label_user_mail_option_only_assigned],
42 42 ['only_owner', :label_user_mail_option_only_owner],
43 43 ['none', :label_user_mail_option_none]
44 44 ]
45 45
46 46 has_and_belongs_to_many :groups, :after_add => Proc.new {|user, group| group.user_added(user)},
47 47 :after_remove => Proc.new {|user, group| group.user_removed(user)}
48 48 has_many :issue_categories, :foreign_key => 'assigned_to_id', :dependent => :nullify
49 49 has_many :changesets, :dependent => :nullify
50 50 has_one :preference, :dependent => :destroy, :class_name => 'UserPreference'
51 has_one :rss_token, :dependent => :destroy, :class_name => 'Token', :conditions => "action='feeds'"
52 has_one :api_token, :dependent => :destroy, :class_name => 'Token', :conditions => "action='api'"
51 has_one :rss_token, :class_name => 'Token', :conditions => "action='feeds'"
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
58 58 acts_as_customizable
59 59
60 60 attr_accessor :password, :password_confirmation
61 61 attr_accessor :last_before_login_on
62 62 # Prevents unauthorized assignments
63 63 attr_protected :login, :admin, :password, :password_confirmation, :hashed_password
64 64
65 65 validates_presence_of :login, :firstname, :lastname, :mail, :if => Proc.new { |user| !user.is_a?(AnonymousUser) }
66 66 validates_uniqueness_of :login, :if => Proc.new { |user| !user.login.blank? }, :case_sensitive => false
67 67 validates_uniqueness_of :mail, :if => Proc.new { |user| !user.mail.blank? }, :case_sensitive => false
68 68 # Login must contain lettres, numbers, underscores only
69 69 validates_format_of :login, :with => /^[a-z0-9_\-@\.]*$/i
70 70 validates_length_of :login, :maximum => 30
71 71 validates_length_of :firstname, :lastname, :maximum => 30
72 72 validates_format_of :mail, :with => /^([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})$/i, :allow_nil => true
73 73 validates_length_of :mail, :maximum => 60, :allow_nil => true
74 74 validates_confirmation_of :password, :allow_nil => true
75 75 validates_inclusion_of :mail_notification, :in => MAIL_NOTIFICATION_OPTIONS.collect(&:first), :allow_blank => true
76 76
77 before_destroy :remove_references_before_destroy
78
77 79 def before_create
78 80 self.mail_notification = Setting.default_notification_option if self.mail_notification.blank?
79 81 true
80 82 end
81 83
82 84 def before_save
83 85 # update hashed_password if password was set
84 86 self.hashed_password = User.hash_password(self.password) if self.password && self.auth_source_id.blank?
85 87 end
86 88
87 89 def reload(*args)
88 90 @name = nil
89 91 super
90 92 end
91 93
92 94 def mail=(arg)
93 95 write_attribute(:mail, arg.to_s.strip)
94 96 end
95 97
96 98 def identity_url=(url)
97 99 if url.blank?
98 100 write_attribute(:identity_url, '')
99 101 else
100 102 begin
101 103 write_attribute(:identity_url, OpenIdAuthentication.normalize_identifier(url))
102 104 rescue OpenIdAuthentication::InvalidOpenId
103 105 # Invlaid url, don't save
104 106 end
105 107 end
106 108 self.read_attribute(:identity_url)
107 109 end
108 110
109 111 # Returns the user that matches provided login and password, or nil
110 112 def self.try_to_login(login, password)
111 113 # Make sure no one can sign in with an empty password
112 114 return nil if password.to_s.empty?
113 115 user = find_by_login(login)
114 116 if user
115 117 # user is already in local database
116 118 return nil if !user.active?
117 119 if user.auth_source
118 120 # user has an external authentication method
119 121 return nil unless user.auth_source.authenticate(login, password)
120 122 else
121 123 # authentication with local password
122 124 return nil unless User.hash_password(password) == user.hashed_password
123 125 end
124 126 else
125 127 # user is not yet registered, try to authenticate with available sources
126 128 attrs = AuthSource.authenticate(login, password)
127 129 if attrs
128 130 user = new(attrs)
129 131 user.login = login
130 132 user.language = Setting.default_language
131 133 if user.save
132 134 user.reload
133 135 logger.info("User '#{user.login}' created from external auth source: #{user.auth_source.type} - #{user.auth_source.name}") if logger && user.auth_source
134 136 end
135 137 end
136 138 end
137 139 user.update_attribute(:last_login_on, Time.now) if user && !user.new_record?
138 140 user
139 141 rescue => text
140 142 raise text
141 143 end
142 144
143 145 # Returns the user who matches the given autologin +key+ or nil
144 146 def self.try_to_autologin(key)
145 147 tokens = Token.find_all_by_action_and_value('autologin', key)
146 148 # Make sure there's only 1 token that matches the key
147 149 if tokens.size == 1
148 150 token = tokens.first
149 151 if (token.created_on > Setting.autologin.to_i.day.ago) && token.user && token.user.active?
150 152 token.user.update_attribute(:last_login_on, Time.now)
151 153 token.user
152 154 end
153 155 end
154 156 end
155 157
156 158 # Return user's full name for display
157 159 def name(formatter = nil)
158 160 if formatter
159 161 eval('"' + (USER_FORMATS[formatter] || USER_FORMATS[:firstname_lastname]) + '"')
160 162 else
161 163 @name ||= eval('"' + (USER_FORMATS[Setting.user_format] || USER_FORMATS[:firstname_lastname]) + '"')
162 164 end
163 165 end
164 166
165 167 def active?
166 168 self.status == STATUS_ACTIVE
167 169 end
168 170
169 171 def registered?
170 172 self.status == STATUS_REGISTERED
171 173 end
172 174
173 175 def locked?
174 176 self.status == STATUS_LOCKED
175 177 end
176 178
177 179 def activate
178 180 self.status = STATUS_ACTIVE
179 181 end
180 182
181 183 def register
182 184 self.status = STATUS_REGISTERED
183 185 end
184 186
185 187 def lock
186 188 self.status = STATUS_LOCKED
187 189 end
188 190
189 191 def activate!
190 192 update_attribute(:status, STATUS_ACTIVE)
191 193 end
192 194
193 195 def register!
194 196 update_attribute(:status, STATUS_REGISTERED)
195 197 end
196 198
197 199 def lock!
198 200 update_attribute(:status, STATUS_LOCKED)
199 201 end
200 202
201 203 def check_password?(clear_password)
202 204 if auth_source_id.present?
203 205 auth_source.authenticate(self.login, clear_password)
204 206 else
205 207 User.hash_password(clear_password) == self.hashed_password
206 208 end
207 209 end
208 210
209 211 # Does the backend storage allow this user to change their password?
210 212 def change_password_allowed?
211 213 return true if auth_source_id.blank?
212 214 return auth_source.allow_password_changes?
213 215 end
214 216
215 217 # Generate and set a random password. Useful for automated user creation
216 218 # Based on Token#generate_token_value
217 219 #
218 220 def random_password
219 221 chars = ("a".."z").to_a + ("A".."Z").to_a + ("0".."9").to_a
220 222 password = ''
221 223 40.times { |i| password << chars[rand(chars.size-1)] }
222 224 self.password = password
223 225 self.password_confirmation = password
224 226 self
225 227 end
226 228
227 229 def pref
228 230 self.preference ||= UserPreference.new(:user => self)
229 231 end
230 232
231 233 def time_zone
232 234 @time_zone ||= (self.pref.time_zone.blank? ? nil : ActiveSupport::TimeZone[self.pref.time_zone])
233 235 end
234 236
235 237 def wants_comments_in_reverse_order?
236 238 self.pref[:comments_sorting] == 'desc'
237 239 end
238 240
239 241 # Return user's RSS key (a 40 chars long string), used to access feeds
240 242 def rss_key
241 243 token = self.rss_token || Token.create(:user => self, :action => 'feeds')
242 244 token.value
243 245 end
244 246
245 247 # Return user's API key (a 40 chars long string), used to access the API
246 248 def api_key
247 249 token = self.api_token || self.create_api_token(:action => 'api')
248 250 token.value
249 251 end
250 252
251 253 # Return an array of project ids for which the user has explicitly turned mail notifications on
252 254 def notified_projects_ids
253 255 @notified_projects_ids ||= memberships.select {|m| m.mail_notification?}.collect(&:project_id)
254 256 end
255 257
256 258 def notified_project_ids=(ids)
257 259 Member.update_all("mail_notification = #{connection.quoted_false}", ['user_id = ?', id])
258 260 Member.update_all("mail_notification = #{connection.quoted_true}", ['user_id = ? AND project_id IN (?)', id, ids]) if ids && !ids.empty?
259 261 @notified_projects_ids = nil
260 262 notified_projects_ids
261 263 end
262 264
263 265 # Only users that belong to more than 1 project can select projects for which they are notified
264 266 def valid_notification_options
265 267 # Note that @user.membership.size would fail since AR ignores
266 268 # :include association option when doing a count
267 269 if memberships.length < 1
268 270 MAIL_NOTIFICATION_OPTIONS.delete_if {|option| option.first == 'selected'}
269 271 else
270 272 MAIL_NOTIFICATION_OPTIONS
271 273 end
272 274 end
273 275
274 276 # Find a user account by matching the exact login and then a case-insensitive
275 277 # version. Exact matches will be given priority.
276 278 def self.find_by_login(login)
277 279 # force string comparison to be case sensitive on MySQL
278 280 type_cast = (ActiveRecord::Base.connection.adapter_name == 'MySQL') ? 'BINARY' : ''
279 281
280 282 # First look for an exact match
281 283 user = first(:conditions => ["#{type_cast} login = ?", login])
282 284 # Fail over to case-insensitive if none was found
283 285 user ||= first(:conditions => ["#{type_cast} LOWER(login) = ?", login.to_s.downcase])
284 286 end
285 287
286 288 def self.find_by_rss_key(key)
287 289 token = Token.find_by_value(key)
288 290 token && token.user.active? ? token.user : nil
289 291 end
290 292
291 293 def self.find_by_api_key(key)
292 294 token = Token.find_by_action_and_value('api', key)
293 295 token && token.user.active? ? token.user : nil
294 296 end
295 297
296 298 # Makes find_by_mail case-insensitive
297 299 def self.find_by_mail(mail)
298 300 find(:first, :conditions => ["LOWER(mail) = ?", mail.to_s.downcase])
299 301 end
300 302
301 303 def to_s
302 304 name
303 305 end
304 306
305 307 # Returns the current day according to user's time zone
306 308 def today
307 309 if time_zone.nil?
308 310 Date.today
309 311 else
310 312 Time.now.in_time_zone(time_zone).to_date
311 313 end
312 314 end
313 315
314 316 def logged?
315 317 true
316 318 end
317 319
318 320 def anonymous?
319 321 !logged?
320 322 end
321 323
322 324 # Return user's roles for project
323 325 def roles_for_project(project)
324 326 roles = []
325 327 # No role on archived projects
326 328 return roles unless project && project.active?
327 329 if logged?
328 330 # Find project membership
329 331 membership = memberships.detect {|m| m.project_id == project.id}
330 332 if membership
331 333 roles = membership.roles
332 334 else
333 335 @role_non_member ||= Role.non_member
334 336 roles << @role_non_member
335 337 end
336 338 else
337 339 @role_anonymous ||= Role.anonymous
338 340 roles << @role_anonymous
339 341 end
340 342 roles
341 343 end
342 344
343 345 # Return true if the user is a member of project
344 346 def member_of?(project)
345 347 !roles_for_project(project).detect {|role| role.member?}.nil?
346 348 end
347 349
348 350 # Return true if the user is allowed to do the specified action on a specific context
349 351 # Action can be:
350 352 # * a parameter-like Hash (eg. :controller => 'projects', :action => 'edit')
351 353 # * a permission Symbol (eg. :edit_project)
352 354 # Context can be:
353 355 # * a project : returns true if user is allowed to do the specified action on this project
354 356 # * a group of projects : returns true if user is allowed on every project
355 357 # * nil with options[:global] set : check if user has at least one role allowed for this action,
356 358 # or falls back to Non Member / Anonymous permissions depending if the user is logged
357 359 def allowed_to?(action, context, options={})
358 360 if context && context.is_a?(Project)
359 361 # No action allowed on archived projects
360 362 return false unless context.active?
361 363 # No action allowed on disabled modules
362 364 return false unless context.allows_to?(action)
363 365 # Admin users are authorized for anything else
364 366 return true if admin?
365 367
366 368 roles = roles_for_project(context)
367 369 return false unless roles
368 370 roles.detect {|role| (context.is_public? || role.member?) && role.allowed_to?(action)}
369 371
370 372 elsif context && context.is_a?(Array)
371 373 # Authorize if user is authorized on every element of the array
372 374 context.map do |project|
373 375 allowed_to?(action,project,options)
374 376 end.inject do |memo,allowed|
375 377 memo && allowed
376 378 end
377 379 elsif options[:global]
378 380 # Admin users are always authorized
379 381 return true if admin?
380 382
381 383 # authorize if user has at least one role that has this permission
382 384 roles = memberships.collect {|m| m.roles}.flatten.uniq
383 385 roles.detect {|r| r.allowed_to?(action)} || (self.logged? ? Role.non_member.allowed_to?(action) : Role.anonymous.allowed_to?(action))
384 386 else
385 387 false
386 388 end
387 389 end
388 390
389 391 # Is the user allowed to do the specified action on any project?
390 392 # See allowed_to? for the actions and valid options.
391 393 def allowed_to_globally?(action, options)
392 394 allowed_to?(action, nil, options.reverse_merge(:global => true))
393 395 end
394 396
395 397 safe_attributes 'login',
396 398 'firstname',
397 399 'lastname',
398 400 'mail',
399 401 'mail_notification',
400 402 'language',
401 403 'custom_field_values',
402 404 'custom_fields',
403 405 'identity_url'
404 406
405 407 safe_attributes 'status',
406 408 'auth_source_id',
407 409 :if => lambda {|user, current_user| current_user.admin?}
408 410
409 411 safe_attributes 'group_ids',
410 412 :if => lambda {|user, current_user| current_user.admin? && !user.new_record?}
411 413
412 414 # Utility method to help check if a user should be notified about an
413 415 # event.
414 416 #
415 417 # TODO: only supports Issue events currently
416 418 def notify_about?(object)
417 419 case mail_notification
418 420 when 'all'
419 421 true
420 422 when 'selected'
421 423 # Handled by the Project
422 424 when 'none'
423 425 false
424 426 when 'only_my_events'
425 427 if object.is_a?(Issue) && (object.author == self || object.assigned_to == self)
426 428 true
427 429 else
428 430 false
429 431 end
430 432 when 'only_assigned'
431 433 if object.is_a?(Issue) && object.assigned_to == self
432 434 true
433 435 else
434 436 false
435 437 end
436 438 when 'only_owner'
437 439 if object.is_a?(Issue) && object.author == self
438 440 true
439 441 else
440 442 false
441 443 end
442 444 else
443 445 false
444 446 end
445 447 end
446 448
447 449 def self.current=(user)
448 450 @current_user = user
449 451 end
450 452
451 453 def self.current
452 454 @current_user ||= User.anonymous
453 455 end
454 456
455 457 # Returns the anonymous user. If the anonymous user does not exist, it is created. There can be only
456 458 # one anonymous user per database.
457 459 def self.anonymous
458 460 anonymous_user = AnonymousUser.find(:first)
459 461 if anonymous_user.nil?
460 462 anonymous_user = AnonymousUser.create(:lastname => 'Anonymous', :firstname => '', :mail => '', :login => '', :status => 0)
461 463 raise 'Unable to create the anonymous user.' if anonymous_user.new_record?
462 464 end
463 465 anonymous_user
464 466 end
465 467
466 468 protected
467 469
468 470 def validate
469 471 # Password length validation based on setting
470 472 if !password.nil? && password.size < Setting.password_min_length.to_i
471 473 errors.add(:password, :too_short, :count => Setting.password_min_length.to_i)
472 474 end
473 475 end
474 476
475 477 private
478
479 # Removes references that are not handled by associations
480 # Things that are not deleted are reassociated with the anonymous user
481 def remove_references_before_destroy
482 return if self.id.nil?
483
484 substitute = User.anonymous
485 Attachment.update_all ['author_id = ?', substitute.id], ['author_id = ?', id]
486 Comment.update_all ['author_id = ?', substitute.id], ['author_id = ?', id]
487 Issue.update_all ['author_id = ?', substitute.id], ['author_id = ?', id]
488 Issue.update_all 'assigned_to_id = NULL', ['assigned_to_id = ?', id]
489 Journal.update_all ['user_id = ?', substitute.id], ['user_id = ?', id]
490 JournalDetail.update_all ['old_value = ?', substitute.id.to_s], ["property = 'attr' AND prop_key = 'assigned_to_id' AND old_value = ?", id.to_s]
491 JournalDetail.update_all ['value = ?', substitute.id.to_s], ["property = 'attr' AND prop_key = 'assigned_to_id' AND value = ?", id.to_s]
492 Message.update_all ['author_id = ?', substitute.id], ['author_id = ?', id]
493 News.update_all ['author_id = ?', substitute.id], ['author_id = ?', id]
494 # Remove private queries and keep public ones
495 Query.delete_all ['user_id = ? AND is_public = ?', id, false]
496 Query.update_all ['user_id = ?', substitute.id], ['user_id = ?', id]
497 TimeEntry.update_all ['user_id = ?', substitute.id], ['user_id = ?', id]
498 Token.delete_all ['user_id = ?', id]
499 Watcher.delete_all ['user_id = ?', id]
500 WikiContent.update_all ['author_id = ?', substitute.id], ['author_id = ?', id]
501 WikiContent::Version.update_all ['author_id = ?', substitute.id], ['author_id = ?', id]
502 end
476 503
477 504 # Return password digest
478 505 def self.hash_password(clear_password)
479 506 Digest::SHA1.hexdigest(clear_password || "")
480 507 end
481 508 end
482 509
483 510 class AnonymousUser < User
484 511
485 512 def validate_on_create
486 513 # There should be only one AnonymousUser in the database
487 514 errors.add_to_base 'An anonymous user already exists.' if AnonymousUser.find(:first)
488 515 end
489 516
490 517 def available_custom_fields
491 518 []
492 519 end
493 520
494 521 # Overrides a few properties
495 522 def logged?; false end
496 523 def admin; false end
497 524 def name(*args); I18n.t(:label_user_anonymous) end
498 525 def mail; nil end
499 526 def time_zone; nil end
500 527 def rss_key; nil end
528
529 # Anonymous user can not be destroyed
530 def destroy
531 false
532 end
501 533 end
@@ -1,160 +1,159
1 1 # redMine - project management software
2 2 # Copyright (C) 2006-2007 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 RepositoryTest < ActiveSupport::TestCase
21 21 fixtures :projects,
22 22 :trackers,
23 23 :projects_trackers,
24 24 :enabled_modules,
25 25 :repositories,
26 26 :issues,
27 27 :issue_statuses,
28 28 :issue_categories,
29 29 :changesets,
30 30 :changes,
31 31 :users,
32 32 :members,
33 33 :member_roles,
34 34 :roles,
35 35 :enumerations
36 36
37 37 def setup
38 38 @repository = Project.find(1).repository
39 39 end
40 40
41 41 def test_create
42 42 repository = Repository::Subversion.new(:project => Project.find(3))
43 43 assert !repository.save
44 44
45 45 repository.url = "svn://localhost"
46 46 assert repository.save
47 47 repository.reload
48 48
49 49 project = Project.find(3)
50 50 assert_equal repository, project.repository
51 51 end
52 52
53 53 def test_destroy
54 54 changesets = Changeset.count(:all, :conditions => "repository_id = 10")
55 55 changes = Change.count(:all, :conditions => "repository_id = 10", :include => :changeset)
56 56 assert_difference 'Changeset.count', -changesets do
57 57 assert_difference 'Change.count', -changes do
58 58 Repository.find(10).destroy
59 59 end
60 60 end
61 61 end
62 62
63 63 def test_should_not_create_with_disabled_scm
64 64 # disable Subversion
65 Setting.enabled_scm = ['Darcs', 'Git']
66 repository = Repository::Subversion.new(:project => Project.find(3), :url => "svn://localhost")
67 assert !repository.save
68 assert_equal I18n.translate('activerecord.errors.messages.invalid'), repository.errors.on(:type)
69 # re-enable Subversion for following tests
70 Setting.delete_all
65 with_settings :enabled_scm => ['Darcs', 'Git'] do
66 repository = Repository::Subversion.new(:project => Project.find(3), :url => "svn://localhost")
67 assert !repository.save
68 assert_equal I18n.translate('activerecord.errors.messages.invalid'), repository.errors.on(:type)
69 end
71 70 end
72 71
73 72 def test_scan_changesets_for_issue_ids
74 73 Setting.default_language = 'en'
75 74 Setting.notified_events = ['issue_added','issue_updated']
76 75
77 76 # choosing a status to apply to fix issues
78 77 Setting.commit_fix_status_id = IssueStatus.find(:first, :conditions => ["is_closed = ?", true]).id
79 78 Setting.commit_fix_done_ratio = "90"
80 79 Setting.commit_ref_keywords = 'refs , references, IssueID'
81 80 Setting.commit_fix_keywords = 'fixes , closes'
82 81 Setting.default_language = 'en'
83 82 ActionMailer::Base.deliveries.clear
84 83
85 84 # make sure issue 1 is not already closed
86 85 fixed_issue = Issue.find(1)
87 86 assert !fixed_issue.status.is_closed?
88 87 old_status = fixed_issue.status
89 88
90 89 Repository.scan_changesets_for_issue_ids
91 90 assert_equal [101, 102], Issue.find(3).changeset_ids
92 91
93 92 # fixed issues
94 93 fixed_issue.reload
95 94 assert fixed_issue.status.is_closed?
96 95 assert_equal 90, fixed_issue.done_ratio
97 96 assert_equal [101], fixed_issue.changeset_ids
98 97
99 98 # issue change
100 99 journal = fixed_issue.journals.find(:first, :order => 'created_on desc')
101 100 assert_equal User.find_by_login('dlopper'), journal.user
102 101 assert_equal 'Applied in changeset r2.', journal.notes
103 102
104 103 # 2 email notifications
105 104 assert_equal 2, ActionMailer::Base.deliveries.size
106 105 mail = ActionMailer::Base.deliveries.first
107 106 assert_kind_of TMail::Mail, mail
108 107 assert mail.subject.starts_with?("[#{fixed_issue.project.name} - #{fixed_issue.tracker.name} ##{fixed_issue.id}]")
109 108 assert mail.body.include?("Status changed from #{old_status} to #{fixed_issue.status}")
110 109
111 110 # ignoring commits referencing an issue of another project
112 111 assert_equal [], Issue.find(4).changesets
113 112 end
114 113
115 114 def test_for_changeset_comments_strip
116 115 repository = Repository::Mercurial.create( :project => Project.find( 4 ), :url => '/foo/bar/baz' )
117 116 comment = <<-COMMENT
118 117 This is a loooooooooooooooooooooooooooong comment
119 118
120 119
121 120 COMMENT
122 121 changeset = Changeset.new(
123 122 :comments => comment, :commit_date => Time.now, :revision => 0, :scmid => 'f39b7922fb3c',
124 123 :committer => 'foo <foo@example.com>', :committed_on => Time.now, :repository => repository )
125 124 assert( changeset.save )
126 125 assert_not_equal( comment, changeset.comments )
127 126 assert_equal( 'This is a loooooooooooooooooooooooooooong comment', changeset.comments )
128 127 end
129 128
130 129 def test_for_urls_strip
131 130 repository = Repository::Cvs.create(:project => Project.find(4), :url => ' :pserver:login:password@host:/path/to/the/repository',
132 131 :root_url => 'foo ')
133 132 assert repository.save
134 133 repository.reload
135 134 assert_equal ':pserver:login:password@host:/path/to/the/repository', repository.url
136 135 assert_equal 'foo', repository.root_url
137 136 end
138 137
139 138 def test_manual_user_mapping
140 139 assert_no_difference "Changeset.count(:conditions => 'user_id <> 2')" do
141 140 c = Changeset.create!(:repository => @repository, :committer => 'foo', :committed_on => Time.now, :revision => 100, :comments => 'Committed by foo.')
142 141 assert_nil c.user
143 142 @repository.committer_ids = {'foo' => '2'}
144 143 assert_equal User.find(2), c.reload.user
145 144 # committer is now mapped
146 145 c = Changeset.create!(:repository => @repository, :committer => 'foo', :committed_on => Time.now, :revision => 101, :comments => 'Another commit by foo.')
147 146 assert_equal User.find(2), c.user
148 147 end
149 148 end
150 149
151 150 def test_auto_user_mapping_by_username
152 151 c = Changeset.create!(:repository => @repository, :committer => 'jsmith', :committed_on => Time.now, :revision => 100, :comments => 'Committed by john.')
153 152 assert_equal User.find(2), c.user
154 153 end
155 154
156 155 def test_auto_user_mapping_by_email
157 156 c = Changeset.create!(:repository => @repository, :committer => 'john <jsmith@somenet.foo>', :committed_on => Time.now, :revision => 100, :comments => 'Committed by john.')
158 157 assert_equal User.find(2), c.user
159 158 end
160 159 end
@@ -1,532 +1,736
1 1 # redMine - project management software
2 2 # Copyright (C) 2006 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
23 23 def setup
24 24 @admin = User.find(1)
25 25 @jsmith = User.find(2)
26 26 @dlopper = User.find(3)
27 27 end
28 28
29 29 test 'object_daddy creation' do
30 30 User.generate_with_protected!(:firstname => 'Testing connection')
31 31 User.generate_with_protected!(:firstname => 'Testing connection')
32 32 assert_equal 2, User.count(:all, :conditions => {:firstname => 'Testing connection'})
33 33 end
34 34
35 35 def test_truth
36 36 assert_kind_of User, @jsmith
37 37 end
38 38
39 39 def test_mail_should_be_stripped
40 40 u = User.new
41 41 u.mail = " foo@bar.com "
42 42 assert_equal "foo@bar.com", u.mail
43 43 end
44 44
45 45 def test_create
46 46 user = User.new(:firstname => "new", :lastname => "user", :mail => "newuser@somenet.foo")
47 47
48 48 user.login = "jsmith"
49 49 user.password, user.password_confirmation = "password", "password"
50 50 # login uniqueness
51 51 assert !user.save
52 52 assert_equal 1, user.errors.count
53 53
54 54 user.login = "newuser"
55 55 user.password, user.password_confirmation = "passwd", "password"
56 56 # password confirmation
57 57 assert !user.save
58 58 assert_equal 1, user.errors.count
59 59
60 60 user.password, user.password_confirmation = "password", "password"
61 61 assert user.save
62 62 end
63 63
64 64 context "User#before_create" do
65 65 should "set the mail_notification to the default Setting" do
66 66 @user1 = User.generate_with_protected!
67 67 assert_equal 'only_my_events', @user1.mail_notification
68 68
69 69 with_settings :default_notification_option => 'all' do
70 70 @user2 = User.generate_with_protected!
71 71 assert_equal 'all', @user2.mail_notification
72 72 end
73 73 end
74 74 end
75 75
76 76 context "User.login" do
77 77 should "be case-insensitive." do
78 78 u = User.new(:firstname => "new", :lastname => "user", :mail => "newuser@somenet.foo")
79 79 u.login = 'newuser'
80 80 u.password, u.password_confirmation = "password", "password"
81 81 assert u.save
82 82
83 83 u = User.new(:firstname => "Similar", :lastname => "User", :mail => "similaruser@somenet.foo")
84 84 u.login = 'NewUser'
85 85 u.password, u.password_confirmation = "password", "password"
86 86 assert !u.save
87 87 assert_equal I18n.translate('activerecord.errors.messages.taken'), u.errors.on(:login)
88 88 end
89 89 end
90 90
91 91 def test_mail_uniqueness_should_not_be_case_sensitive
92 92 u = User.new(:firstname => "new", :lastname => "user", :mail => "newuser@somenet.foo")
93 93 u.login = 'newuser1'
94 94 u.password, u.password_confirmation = "password", "password"
95 95 assert u.save
96 96
97 97 u = User.new(:firstname => "new", :lastname => "user", :mail => "newUser@Somenet.foo")
98 98 u.login = 'newuser2'
99 99 u.password, u.password_confirmation = "password", "password"
100 100 assert !u.save
101 101 assert_equal I18n.translate('activerecord.errors.messages.taken'), u.errors.on(:mail)
102 102 end
103 103
104 104 def test_update
105 105 assert_equal "admin", @admin.login
106 106 @admin.login = "john"
107 107 assert @admin.save, @admin.errors.full_messages.join("; ")
108 108 @admin.reload
109 109 assert_equal "john", @admin.login
110 110 end
111 111
112 def test_destroy
113 User.find(2).destroy
112 def test_destroy_should_delete_members_and_roles
113 members = Member.find_all_by_user_id(2)
114 ms = members.size
115 rs = members.collect(&:roles).flatten.size
116
117 assert_difference 'Member.count', - ms do
118 assert_difference 'MemberRole.count', - rs do
119 User.find(2).destroy
120 end
121 end
122
114 123 assert_nil User.find_by_id(2)
115 124 assert Member.find_all_by_user_id(2).empty?
116 125 end
117 126
127 def test_destroy_should_update_attachments
128 attachment = Attachment.create!(:container => Project.find(1),
129 :file => uploaded_test_file("testfile.txt", "text/plain"),
130 :author_id => 2)
131
132 User.find(2).destroy
133 assert_nil User.find_by_id(2)
134 assert_equal User.anonymous, attachment.reload.author
135 end
136
137 def test_destroy_should_update_comments
138 comment = Comment.create!(
139 :commented => News.create!(:project_id => 1, :author_id => 1, :title => 'foo', :description => 'foo'),
140 :author => User.find(2),
141 :comments => 'foo'
142 )
143
144 User.find(2).destroy
145 assert_nil User.find_by_id(2)
146 assert_equal User.anonymous, comment.reload.author
147 end
148
149 def test_destroy_should_update_issues
150 issue = Issue.create!(:project_id => 1, :author_id => 2, :tracker_id => 1, :subject => 'foo')
151
152 User.find(2).destroy
153 assert_nil User.find_by_id(2)
154 assert_equal User.anonymous, issue.reload.author
155 end
156
157 def test_destroy_should_unassign_issues
158 issue = Issue.create!(:project_id => 1, :author_id => 1, :tracker_id => 1, :subject => 'foo', :assigned_to_id => 2)
159
160 User.find(2).destroy
161 assert_nil User.find_by_id(2)
162 assert_nil issue.reload.assigned_to
163 end
164
165 def test_destroy_should_update_journals
166 issue = Issue.create!(:project_id => 1, :author_id => 2, :tracker_id => 1, :subject => 'foo')
167 issue.init_journal(User.find(2), "update")
168 issue.save!
169
170 User.find(2).destroy
171 assert_nil User.find_by_id(2)
172 assert_equal User.anonymous, issue.journals.first.reload.user
173 end
174
175 def test_destroy_should_update_journal_details_old_value
176 issue = Issue.create!(:project_id => 1, :author_id => 1, :tracker_id => 1, :subject => 'foo', :assigned_to_id => 2)
177 issue.init_journal(User.find(1), "update")
178 issue.assigned_to_id = nil
179 assert_difference 'JournalDetail.count' do
180 issue.save!
181 end
182 journal_detail = JournalDetail.first(:order => 'id DESC')
183 assert_equal '2', journal_detail.old_value
184
185 User.find(2).destroy
186 assert_nil User.find_by_id(2)
187 assert_equal User.anonymous.id.to_s, journal_detail.reload.old_value
188 end
189
190 def test_destroy_should_update_journal_details_value
191 issue = Issue.create!(:project_id => 1, :author_id => 1, :tracker_id => 1, :subject => 'foo')
192 issue.init_journal(User.find(1), "update")
193 issue.assigned_to_id = 2
194 assert_difference 'JournalDetail.count' do
195 issue.save!
196 end
197 journal_detail = JournalDetail.first(:order => 'id DESC')
198 assert_equal '2', journal_detail.value
199
200 User.find(2).destroy
201 assert_nil User.find_by_id(2)
202 assert_equal User.anonymous.id.to_s, journal_detail.reload.value
203 end
204
205 def test_destroy_should_update_messages
206 board = Board.create!(:project_id => 1, :name => 'Board', :description => 'Board')
207 message = Message.create!(:board_id => board.id, :author_id => 2, :subject => 'foo', :content => 'foo')
208
209 User.find(2).destroy
210 assert_nil User.find_by_id(2)
211 assert_equal User.anonymous, message.reload.author
212 end
213
214 def test_destroy_should_update_news
215 news = News.create!(:project_id => 1, :author_id => 2, :title => 'foo', :description => 'foo')
216
217 User.find(2).destroy
218 assert_nil User.find_by_id(2)
219 assert_equal User.anonymous, news.reload.author
220 end
221
222 def test_destroy_should_delete_private_queries
223 query = Query.new(:name => 'foo', :is_public => false)
224 query.project_id = 1
225 query.user_id = 2
226 query.save!
227
228 User.find(2).destroy
229 assert_nil User.find_by_id(2)
230 assert_nil Query.find_by_id(query.id)
231 end
232
233 def test_destroy_should_update_public_queries
234 query = Query.new(:name => 'foo', :is_public => true)
235 query.project_id = 1
236 query.user_id = 2
237 query.save!
238
239 User.find(2).destroy
240 assert_nil User.find_by_id(2)
241 assert_equal User.anonymous, query.reload.user
242 end
243
244 def test_destroy_should_update_time_entries
245 entry = TimeEntry.new(:hours => '2', :spent_on => Date.today, :activity => TimeEntryActivity.create!(:name => 'foo'))
246 entry.project_id = 1
247 entry.user_id = 2
248 entry.save!
249
250 User.find(2).destroy
251 assert_nil User.find_by_id(2)
252 assert_equal User.anonymous, entry.reload.user
253 end
254
255 def test_destroy_should_delete_tokens
256 token = Token.create!(:user_id => 2, :value => 'foo')
257
258 User.find(2).destroy
259 assert_nil User.find_by_id(2)
260 assert_nil Token.find_by_id(token.id)
261 end
262
263 def test_destroy_should_delete_watchers
264 issue = Issue.create!(:project_id => 1, :author_id => 1, :tracker_id => 1, :subject => 'foo')
265 watcher = Watcher.create!(:user_id => 2, :watchable => issue)
266
267 User.find(2).destroy
268 assert_nil User.find_by_id(2)
269 assert_nil Watcher.find_by_id(watcher.id)
270 end
271
272 def test_destroy_should_update_wiki_contents
273 wiki_content = WikiContent.create!(
274 :text => 'foo',
275 :author_id => 2,
276 :page => WikiPage.create!(:title => 'Foo', :wiki => Wiki.create!(:project_id => 1, :start_page => 'Start'))
277 )
278 wiki_content.text = 'bar'
279 assert_difference 'WikiContent::Version.count' do
280 wiki_content.save!
281 end
282
283 User.find(2).destroy
284 assert_nil User.find_by_id(2)
285 assert_equal User.anonymous, wiki_content.reload.author
286 wiki_content.versions.each do |version|
287 assert_equal User.anonymous, version.reload.author
288 end
289 end
290
291 def test_destroy_should_nullify_issue_categories
292 category = IssueCategory.create!(:project_id => 1, :assigned_to_id => 2, :name => 'foo')
293
294 User.find(2).destroy
295 assert_nil User.find_by_id(2)
296 assert_nil category.reload.assigned_to_id
297 end
298
299 def test_destroy_should_nullify_changesets
300 changeset = Changeset.create!(
301 :repository => Repository::Subversion.create!(
302 :project_id => 1,
303 :url => 'file:///var/svn'
304 ),
305 :revision => '12',
306 :committed_on => Time.now,
307 :committer => 'jsmith'
308 )
309 assert_equal 2, changeset.user_id
310
311 User.find(2).destroy
312 assert_nil User.find_by_id(2)
313 assert_nil changeset.reload.user_id
314 end
315
316 def test_anonymous_user_should_not_be_destroyable
317 assert_no_difference 'User.count' do
318 assert_equal false, User.anonymous.destroy
319 end
320 end
321
118 322 def test_validate_login_presence
119 323 @admin.login = ""
120 324 assert !@admin.save
121 325 assert_equal 1, @admin.errors.count
122 326 end
123 327
124 328 def test_validate_mail_notification_inclusion
125 329 u = User.new
126 330 u.mail_notification = 'foo'
127 331 u.save
128 332 assert_not_nil u.errors.on(:mail_notification)
129 333 end
130 334
131 335 context "User#try_to_login" do
132 336 should "fall-back to case-insensitive if user login is not found as-typed." do
133 337 user = User.try_to_login("AdMin", "admin")
134 338 assert_kind_of User, user
135 339 assert_equal "admin", user.login
136 340 end
137 341
138 342 should "select the exact matching user first" do
139 343 case_sensitive_user = User.generate_with_protected!(:login => 'changed', :password => 'admin', :password_confirmation => 'admin')
140 344 # bypass validations to make it appear like existing data
141 345 case_sensitive_user.update_attribute(:login, 'ADMIN')
142 346
143 347 user = User.try_to_login("ADMIN", "admin")
144 348 assert_kind_of User, user
145 349 assert_equal "ADMIN", user.login
146 350
147 351 end
148 352 end
149 353
150 354 def test_password
151 355 user = User.try_to_login("admin", "admin")
152 356 assert_kind_of User, user
153 357 assert_equal "admin", user.login
154 358 user.password = "hello"
155 359 assert user.save
156 360
157 361 user = User.try_to_login("admin", "hello")
158 362 assert_kind_of User, user
159 363 assert_equal "admin", user.login
160 364 assert_equal User.hash_password("hello"), user.hashed_password
161 365 end
162 366
163 367 def test_name_format
164 368 assert_equal 'Smith, John', @jsmith.name(:lastname_coma_firstname)
165 369 Setting.user_format = :firstname_lastname
166 370 assert_equal 'John Smith', @jsmith.reload.name
167 371 Setting.user_format = :username
168 372 assert_equal 'jsmith', @jsmith.reload.name
169 373 end
170 374
171 375 def test_lock
172 376 user = User.try_to_login("jsmith", "jsmith")
173 377 assert_equal @jsmith, user
174 378
175 379 @jsmith.status = User::STATUS_LOCKED
176 380 assert @jsmith.save
177 381
178 382 user = User.try_to_login("jsmith", "jsmith")
179 383 assert_equal nil, user
180 384 end
181 385
182 386 if ldap_configured?
183 387 context "#try_to_login using LDAP" do
184 388 context "with failed connection to the LDAP server" do
185 389 should "return nil" do
186 390 @auth_source = AuthSourceLdap.find(1)
187 391 AuthSource.any_instance.stubs(:initialize_ldap_con).raises(Net::LDAP::LdapError, 'Cannot connect')
188 392
189 393 assert_equal nil, User.try_to_login('edavis', 'wrong')
190 394 end
191 395 end
192 396
193 397 context "with an unsuccessful authentication" do
194 398 should "return nil" do
195 399 assert_equal nil, User.try_to_login('edavis', 'wrong')
196 400 end
197 401 end
198 402
199 403 context "on the fly registration" do
200 404 setup do
201 405 @auth_source = AuthSourceLdap.find(1)
202 406 end
203 407
204 408 context "with a successful authentication" do
205 409 should "create a new user account if it doesn't exist" do
206 410 assert_difference('User.count') do
207 411 user = User.try_to_login('edavis', '123456')
208 412 assert !user.admin?
209 413 end
210 414 end
211 415
212 416 should "retrieve existing user" do
213 417 user = User.try_to_login('edavis', '123456')
214 418 user.admin = true
215 419 user.save!
216 420
217 421 assert_no_difference('User.count') do
218 422 user = User.try_to_login('edavis', '123456')
219 423 assert user.admin?
220 424 end
221 425 end
222 426 end
223 427 end
224 428 end
225 429
226 430 else
227 431 puts "Skipping LDAP tests."
228 432 end
229 433
230 434 def test_create_anonymous
231 435 AnonymousUser.delete_all
232 436 anon = User.anonymous
233 437 assert !anon.new_record?
234 438 assert_kind_of AnonymousUser, anon
235 439 end
236 440
237 441 should_have_one :rss_token
238 442
239 443 def test_rss_key
240 444 assert_nil @jsmith.rss_token
241 445 key = @jsmith.rss_key
242 446 assert_equal 40, key.length
243 447
244 448 @jsmith.reload
245 449 assert_equal key, @jsmith.rss_key
246 450 end
247 451
248 452
249 453 should_have_one :api_token
250 454
251 455 context "User#api_key" do
252 456 should "generate a new one if the user doesn't have one" do
253 457 user = User.generate_with_protected!(:api_token => nil)
254 458 assert_nil user.api_token
255 459
256 460 key = user.api_key
257 461 assert_equal 40, key.length
258 462 user.reload
259 463 assert_equal key, user.api_key
260 464 end
261 465
262 466 should "return the existing api token value" do
263 467 user = User.generate_with_protected!
264 468 token = Token.generate!(:action => 'api')
265 469 user.api_token = token
266 470 assert user.save
267 471
268 472 assert_equal token.value, user.api_key
269 473 end
270 474 end
271 475
272 476 context "User#find_by_api_key" do
273 477 should "return nil if no matching key is found" do
274 478 assert_nil User.find_by_api_key('zzzzzzzzz')
275 479 end
276 480
277 481 should "return nil if the key is found for an inactive user" do
278 482 user = User.generate_with_protected!(:status => User::STATUS_LOCKED)
279 483 token = Token.generate!(:action => 'api')
280 484 user.api_token = token
281 485 user.save
282 486
283 487 assert_nil User.find_by_api_key(token.value)
284 488 end
285 489
286 490 should "return the user if the key is found for an active user" do
287 491 user = User.generate_with_protected!(:status => User::STATUS_ACTIVE)
288 492 token = Token.generate!(:action => 'api')
289 493 user.api_token = token
290 494 user.save
291 495
292 496 assert_equal user, User.find_by_api_key(token.value)
293 497 end
294 498 end
295 499
296 500 def test_roles_for_project
297 501 # user with a role
298 502 roles = @jsmith.roles_for_project(Project.find(1))
299 503 assert_kind_of Role, roles.first
300 504 assert_equal "Manager", roles.first.name
301 505
302 506 # user with no role
303 507 assert_nil @dlopper.roles_for_project(Project.find(2)).detect {|role| role.member?}
304 508 end
305 509
306 510 def test_mail_notification_all
307 511 @jsmith.mail_notification = 'all'
308 512 @jsmith.notified_project_ids = []
309 513 @jsmith.save
310 514 @jsmith.reload
311 515 assert @jsmith.projects.first.recipients.include?(@jsmith.mail)
312 516 end
313 517
314 518 def test_mail_notification_selected
315 519 @jsmith.mail_notification = 'selected'
316 520 @jsmith.notified_project_ids = [1]
317 521 @jsmith.save
318 522 @jsmith.reload
319 523 assert Project.find(1).recipients.include?(@jsmith.mail)
320 524 end
321 525
322 526 def test_mail_notification_only_my_events
323 527 @jsmith.mail_notification = 'only_my_events'
324 528 @jsmith.notified_project_ids = []
325 529 @jsmith.save
326 530 @jsmith.reload
327 531 assert !@jsmith.projects.first.recipients.include?(@jsmith.mail)
328 532 end
329 533
330 534 def test_comments_sorting_preference
331 535 assert !@jsmith.wants_comments_in_reverse_order?
332 536 @jsmith.pref.comments_sorting = 'asc'
333 537 assert !@jsmith.wants_comments_in_reverse_order?
334 538 @jsmith.pref.comments_sorting = 'desc'
335 539 assert @jsmith.wants_comments_in_reverse_order?
336 540 end
337 541
338 542 def test_find_by_mail_should_be_case_insensitive
339 543 u = User.find_by_mail('JSmith@somenet.foo')
340 544 assert_not_nil u
341 545 assert_equal 'jsmith@somenet.foo', u.mail
342 546 end
343 547
344 548 def test_random_password
345 549 u = User.new
346 550 u.random_password
347 551 assert !u.password.blank?
348 552 assert !u.password_confirmation.blank?
349 553 end
350 554
351 555 context "#change_password_allowed?" do
352 556 should "be allowed if no auth source is set" do
353 557 user = User.generate_with_protected!
354 558 assert user.change_password_allowed?
355 559 end
356 560
357 561 should "delegate to the auth source" do
358 562 user = User.generate_with_protected!
359 563
360 564 allowed_auth_source = AuthSource.generate!
361 565 def allowed_auth_source.allow_password_changes?; true; end
362 566
363 567 denied_auth_source = AuthSource.generate!
364 568 def denied_auth_source.allow_password_changes?; false; end
365 569
366 570 assert user.change_password_allowed?
367 571
368 572 user.auth_source = allowed_auth_source
369 573 assert user.change_password_allowed?, "User not allowed to change password, though auth source does"
370 574
371 575 user.auth_source = denied_auth_source
372 576 assert !user.change_password_allowed?, "User allowed to change password, though auth source does not"
373 577 end
374 578
375 579 end
376 580
377 581 context "#allowed_to?" do
378 582 context "with a unique project" do
379 583 should "return false if project is archived" do
380 584 project = Project.find(1)
381 585 Project.any_instance.stubs(:status).returns(Project::STATUS_ARCHIVED)
382 586 assert ! @admin.allowed_to?(:view_issues, Project.find(1))
383 587 end
384 588
385 589 should "return false if related module is disabled" do
386 590 project = Project.find(1)
387 591 project.enabled_module_names = ["issue_tracking"]
388 592 assert @admin.allowed_to?(:add_issues, project)
389 593 assert ! @admin.allowed_to?(:view_wiki_pages, project)
390 594 end
391 595
392 596 should "authorize nearly everything for admin users" do
393 597 project = Project.find(1)
394 598 assert ! @admin.member_of?(project)
395 599 %w(edit_issues delete_issues manage_news manage_documents manage_wiki).each do |p|
396 600 assert @admin.allowed_to?(p.to_sym, project)
397 601 end
398 602 end
399 603
400 604 should "authorize normal users depending on their roles" do
401 605 project = Project.find(1)
402 606 assert @jsmith.allowed_to?(:delete_messages, project) #Manager
403 607 assert ! @dlopper.allowed_to?(:delete_messages, project) #Developper
404 608 end
405 609 end
406 610
407 611 context "with multiple projects" do
408 612 should "return false if array is empty" do
409 613 assert ! @admin.allowed_to?(:view_project, [])
410 614 end
411 615
412 616 should "return true only if user has permission on all these projects" do
413 617 assert @admin.allowed_to?(:view_project, Project.all)
414 618 assert ! @dlopper.allowed_to?(:view_project, Project.all) #cannot see Project(2)
415 619 assert @jsmith.allowed_to?(:edit_issues, @jsmith.projects) #Manager or Developer everywhere
416 620 assert ! @jsmith.allowed_to?(:delete_issue_watchers, @jsmith.projects) #Dev cannot delete_issue_watchers
417 621 end
418 622
419 623 should "behave correctly with arrays of 1 project" do
420 624 assert ! User.anonymous.allowed_to?(:delete_issues, [Project.first])
421 625 end
422 626 end
423 627
424 628 context "with options[:global]" do
425 629 should "authorize if user has at least one role that has this permission" do
426 630 @dlopper2 = User.find(5) #only Developper on a project, not Manager anywhere
427 631 @anonymous = User.find(6)
428 632 assert @jsmith.allowed_to?(:delete_issue_watchers, nil, :global => true)
429 633 assert ! @dlopper2.allowed_to?(:delete_issue_watchers, nil, :global => true)
430 634 assert @dlopper2.allowed_to?(:add_issues, nil, :global => true)
431 635 assert ! @anonymous.allowed_to?(:add_issues, nil, :global => true)
432 636 assert @anonymous.allowed_to?(:view_issues, nil, :global => true)
433 637 end
434 638 end
435 639 end
436 640
437 641 context "User#notify_about?" do
438 642 context "Issues" do
439 643 setup do
440 644 @project = Project.find(1)
441 645 @author = User.generate_with_protected!
442 646 @assignee = User.generate_with_protected!
443 647 @issue = Issue.generate_for_project!(@project, :assigned_to => @assignee, :author => @author)
444 648 end
445 649
446 650 should "be true for a user with :all" do
447 651 @author.update_attribute(:mail_notification, 'all')
448 652 assert @author.notify_about?(@issue)
449 653 end
450 654
451 655 should "be false for a user with :none" do
452 656 @author.update_attribute(:mail_notification, 'none')
453 657 assert ! @author.notify_about?(@issue)
454 658 end
455 659
456 660 should "be false for a user with :only_my_events and isn't an author, creator, or assignee" do
457 661 @user = User.generate_with_protected!(:mail_notification => 'only_my_events')
458 662 assert ! @user.notify_about?(@issue)
459 663 end
460 664
461 665 should "be true for a user with :only_my_events and is the author" do
462 666 @author.update_attribute(:mail_notification, 'only_my_events')
463 667 assert @author.notify_about?(@issue)
464 668 end
465 669
466 670 should "be true for a user with :only_my_events and is the assignee" do
467 671 @assignee.update_attribute(:mail_notification, 'only_my_events')
468 672 assert @assignee.notify_about?(@issue)
469 673 end
470 674
471 675 should "be true for a user with :only_assigned and is the assignee" do
472 676 @assignee.update_attribute(:mail_notification, 'only_assigned')
473 677 assert @assignee.notify_about?(@issue)
474 678 end
475 679
476 680 should "be false for a user with :only_assigned and is not the assignee" do
477 681 @author.update_attribute(:mail_notification, 'only_assigned')
478 682 assert ! @author.notify_about?(@issue)
479 683 end
480 684
481 685 should "be true for a user with :only_owner and is the author" do
482 686 @author.update_attribute(:mail_notification, 'only_owner')
483 687 assert @author.notify_about?(@issue)
484 688 end
485 689
486 690 should "be false for a user with :only_owner and is not the author" do
487 691 @assignee.update_attribute(:mail_notification, 'only_owner')
488 692 assert ! @assignee.notify_about?(@issue)
489 693 end
490 694 end
491 695
492 696 context "other events" do
493 697 should 'be added and tested'
494 698 end
495 699 end
496 700
497 701 if Object.const_defined?(:OpenID)
498 702
499 703 def test_setting_identity_url
500 704 normalized_open_id_url = 'http://example.com/'
501 705 u = User.new( :identity_url => 'http://example.com/' )
502 706 assert_equal normalized_open_id_url, u.identity_url
503 707 end
504 708
505 709 def test_setting_identity_url_without_trailing_slash
506 710 normalized_open_id_url = 'http://example.com/'
507 711 u = User.new( :identity_url => 'http://example.com' )
508 712 assert_equal normalized_open_id_url, u.identity_url
509 713 end
510 714
511 715 def test_setting_identity_url_without_protocol
512 716 normalized_open_id_url = 'http://example.com/'
513 717 u = User.new( :identity_url => 'example.com' )
514 718 assert_equal normalized_open_id_url, u.identity_url
515 719 end
516 720
517 721 def test_setting_blank_identity_url
518 722 u = User.new( :identity_url => 'example.com' )
519 723 u.identity_url = ''
520 724 assert u.identity_url.blank?
521 725 end
522 726
523 727 def test_setting_invalid_identity_url
524 728 u = User.new( :identity_url => 'this is not an openid url' )
525 729 assert u.identity_url.blank?
526 730 end
527 731
528 732 else
529 733 puts "Skipping openid tests."
530 734 end
531 735
532 736 end
General Comments 0
You need to be logged in to leave comments. Login now