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