##// END OF EJS Templates
Validates user's mail_notification and turn options into strings (the attribute type)....
Jean-Philippe Lang -
r4380:e4f319fe6122
parent child
Show More
@@ -1,498 +1,499
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 [:all, :label_user_mail_option_all],
39 [:selected, :label_user_mail_option_selected],
40 [:none, :label_user_mail_option_none],
41 [:only_my_events, :label_user_mail_option_only_my_events],
42 [:only_assigned, :label_user_mail_option_only_assigned],
43 [:only_owner, :label_user_mail_option_only_owner]
44 ]
38 ['all', :label_user_mail_option_all],
39 ['selected', :label_user_mail_option_selected],
40 ['only_my_events', :label_user_mail_option_only_my_events],
41 ['only_assigned', :label_user_mail_option_only_assigned],
42 ['only_owner', :label_user_mail_option_only_owner],
43 ['none', :label_user_mail_option_none]
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 51 has_one :rss_token, :dependent => :destroy, :class_name => 'Token', :conditions => "action='feeds'"
52 52 has_one :api_token, :dependent => :destroy, :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, :group_ids
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_format_of :firstname, :lastname, :with => /^[\w\s\'\-\.]*$/i
72 72 validates_length_of :firstname, :lastname, :maximum => 30
73 73 validates_format_of :mail, :with => /^([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})$/i, :allow_nil => true
74 74 validates_length_of :mail, :maximum => 60, :allow_nil => true
75 75 validates_confirmation_of :password, :allow_nil => true
76 validates_inclusion_of :mail_notification, :in => MAIL_NOTIFICATION_OPTIONS.collect(&:first), :allow_blank => true
76 77
77 78 def before_create
78 79 self.mail_notification = Setting.default_notification_option if self.mail_notification.blank?
79 80 true
80 81 end
81 82
82 83 def before_save
83 84 # update hashed_password if password was set
84 85 self.hashed_password = User.hash_password(self.password) if self.password && self.auth_source_id.blank?
85 86 end
86 87
87 88 def reload(*args)
88 89 @name = nil
89 90 super
90 91 end
91 92
92 93 def mail=(arg)
93 94 write_attribute(:mail, arg.to_s.strip)
94 95 end
95 96
96 97 def identity_url=(url)
97 98 if url.blank?
98 99 write_attribute(:identity_url, '')
99 100 else
100 101 begin
101 102 write_attribute(:identity_url, OpenIdAuthentication.normalize_identifier(url))
102 103 rescue OpenIdAuthentication::InvalidOpenId
103 104 # Invlaid url, don't save
104 105 end
105 106 end
106 107 self.read_attribute(:identity_url)
107 108 end
108 109
109 110 # Returns the user that matches provided login and password, or nil
110 111 def self.try_to_login(login, password)
111 112 # Make sure no one can sign in with an empty password
112 113 return nil if password.to_s.empty?
113 114 user = find_by_login(login)
114 115 if user
115 116 # user is already in local database
116 117 return nil if !user.active?
117 118 if user.auth_source
118 119 # user has an external authentication method
119 120 return nil unless user.auth_source.authenticate(login, password)
120 121 else
121 122 # authentication with local password
122 123 return nil unless User.hash_password(password) == user.hashed_password
123 124 end
124 125 else
125 126 # user is not yet registered, try to authenticate with available sources
126 127 attrs = AuthSource.authenticate(login, password)
127 128 if attrs
128 129 user = new(attrs)
129 130 user.login = login
130 131 user.language = Setting.default_language
131 132 if user.save
132 133 user.reload
133 134 logger.info("User '#{user.login}' created from external auth source: #{user.auth_source.type} - #{user.auth_source.name}") if logger && user.auth_source
134 135 end
135 136 end
136 137 end
137 138 user.update_attribute(:last_login_on, Time.now) if user && !user.new_record?
138 139 user
139 140 rescue => text
140 141 raise text
141 142 end
142 143
143 144 # Returns the user who matches the given autologin +key+ or nil
144 145 def self.try_to_autologin(key)
145 146 tokens = Token.find_all_by_action_and_value('autologin', key)
146 147 # Make sure there's only 1 token that matches the key
147 148 if tokens.size == 1
148 149 token = tokens.first
149 150 if (token.created_on > Setting.autologin.to_i.day.ago) && token.user && token.user.active?
150 151 token.user.update_attribute(:last_login_on, Time.now)
151 152 token.user
152 153 end
153 154 end
154 155 end
155 156
156 157 # Return user's full name for display
157 158 def name(formatter = nil)
158 159 if formatter
159 160 eval('"' + (USER_FORMATS[formatter] || USER_FORMATS[:firstname_lastname]) + '"')
160 161 else
161 162 @name ||= eval('"' + (USER_FORMATS[Setting.user_format] || USER_FORMATS[:firstname_lastname]) + '"')
162 163 end
163 164 end
164 165
165 166 def active?
166 167 self.status == STATUS_ACTIVE
167 168 end
168 169
169 170 def registered?
170 171 self.status == STATUS_REGISTERED
171 172 end
172 173
173 174 def locked?
174 175 self.status == STATUS_LOCKED
175 176 end
176 177
177 178 def activate
178 179 self.status = STATUS_ACTIVE
179 180 end
180 181
181 182 def register
182 183 self.status = STATUS_REGISTERED
183 184 end
184 185
185 186 def lock
186 187 self.status = STATUS_LOCKED
187 188 end
188 189
189 190 def activate!
190 191 update_attribute(:status, STATUS_ACTIVE)
191 192 end
192 193
193 194 def register!
194 195 update_attribute(:status, STATUS_REGISTERED)
195 196 end
196 197
197 198 def lock!
198 199 update_attribute(:status, STATUS_LOCKED)
199 200 end
200 201
201 202 def check_password?(clear_password)
202 203 if auth_source_id.present?
203 204 auth_source.authenticate(self.login, clear_password)
204 205 else
205 206 User.hash_password(clear_password) == self.hashed_password
206 207 end
207 208 end
208 209
209 210 # Does the backend storage allow this user to change their password?
210 211 def change_password_allowed?
211 212 return true if auth_source_id.blank?
212 213 return auth_source.allow_password_changes?
213 214 end
214 215
215 216 # Generate and set a random password. Useful for automated user creation
216 217 # Based on Token#generate_token_value
217 218 #
218 219 def random_password
219 220 chars = ("a".."z").to_a + ("A".."Z").to_a + ("0".."9").to_a
220 221 password = ''
221 222 40.times { |i| password << chars[rand(chars.size-1)] }
222 223 self.password = password
223 224 self.password_confirmation = password
224 225 self
225 226 end
226 227
227 228 def pref
228 229 self.preference ||= UserPreference.new(:user => self)
229 230 end
230 231
231 232 def time_zone
232 233 @time_zone ||= (self.pref.time_zone.blank? ? nil : ActiveSupport::TimeZone[self.pref.time_zone])
233 234 end
234 235
235 236 def wants_comments_in_reverse_order?
236 237 self.pref[:comments_sorting] == 'desc'
237 238 end
238 239
239 240 # Return user's RSS key (a 40 chars long string), used to access feeds
240 241 def rss_key
241 242 token = self.rss_token || Token.create(:user => self, :action => 'feeds')
242 243 token.value
243 244 end
244 245
245 246 # Return user's API key (a 40 chars long string), used to access the API
246 247 def api_key
247 248 token = self.api_token || self.create_api_token(:action => 'api')
248 249 token.value
249 250 end
250 251
251 252 # Return an array of project ids for which the user has explicitly turned mail notifications on
252 253 def notified_projects_ids
253 254 @notified_projects_ids ||= memberships.select {|m| m.mail_notification?}.collect(&:project_id)
254 255 end
255 256
256 257 def notified_project_ids=(ids)
257 258 Member.update_all("mail_notification = #{connection.quoted_false}", ['user_id = ?', id])
258 259 Member.update_all("mail_notification = #{connection.quoted_true}", ['user_id = ? AND project_id IN (?)', id, ids]) if ids && !ids.empty?
259 260 @notified_projects_ids = nil
260 261 notified_projects_ids
261 262 end
262 263
263 264 # Only users that belong to more than 1 project can select projects for which they are notified
264 265 def valid_notification_options
265 266 # Note that @user.membership.size would fail since AR ignores
266 267 # :include association option when doing a count
267 268 if memberships.length < 1
268 MAIL_NOTIFICATION_OPTIONS.delete_if {|option| option.first == :selected}
269 MAIL_NOTIFICATION_OPTIONS.delete_if {|option| option.first == 'selected'}
269 270 else
270 271 MAIL_NOTIFICATION_OPTIONS
271 272 end
272 273 end
273 274
274 275 # Find a user account by matching the exact login and then a case-insensitive
275 276 # version. Exact matches will be given priority.
276 277 def self.find_by_login(login)
277 278 # force string comparison to be case sensitive on MySQL
278 279 type_cast = (ActiveRecord::Base.connection.adapter_name == 'MySQL') ? 'BINARY' : ''
279 280
280 281 # First look for an exact match
281 282 user = first(:conditions => ["#{type_cast} login = ?", login])
282 283 # Fail over to case-insensitive if none was found
283 284 user ||= first(:conditions => ["#{type_cast} LOWER(login) = ?", login.to_s.downcase])
284 285 end
285 286
286 287 def self.find_by_rss_key(key)
287 288 token = Token.find_by_value(key)
288 289 token && token.user.active? ? token.user : nil
289 290 end
290 291
291 292 def self.find_by_api_key(key)
292 293 token = Token.find_by_action_and_value('api', key)
293 294 token && token.user.active? ? token.user : nil
294 295 end
295 296
296 297 # Makes find_by_mail case-insensitive
297 298 def self.find_by_mail(mail)
298 299 find(:first, :conditions => ["LOWER(mail) = ?", mail.to_s.downcase])
299 300 end
300 301
301 302 def to_s
302 303 name
303 304 end
304 305
305 306 # Returns the current day according to user's time zone
306 307 def today
307 308 if time_zone.nil?
308 309 Date.today
309 310 else
310 311 Time.now.in_time_zone(time_zone).to_date
311 312 end
312 313 end
313 314
314 315 def logged?
315 316 true
316 317 end
317 318
318 319 def anonymous?
319 320 !logged?
320 321 end
321 322
322 323 # Return user's roles for project
323 324 def roles_for_project(project)
324 325 roles = []
325 326 # No role on archived projects
326 327 return roles unless project && project.active?
327 328 if logged?
328 329 # Find project membership
329 330 membership = memberships.detect {|m| m.project_id == project.id}
330 331 if membership
331 332 roles = membership.roles
332 333 else
333 334 @role_non_member ||= Role.non_member
334 335 roles << @role_non_member
335 336 end
336 337 else
337 338 @role_anonymous ||= Role.anonymous
338 339 roles << @role_anonymous
339 340 end
340 341 roles
341 342 end
342 343
343 344 # Return true if the user is a member of project
344 345 def member_of?(project)
345 346 !roles_for_project(project).detect {|role| role.member?}.nil?
346 347 end
347 348
348 349 # Return true if the user is allowed to do the specified action on a specific context
349 350 # Action can be:
350 351 # * a parameter-like Hash (eg. :controller => 'projects', :action => 'edit')
351 352 # * a permission Symbol (eg. :edit_project)
352 353 # Context can be:
353 354 # * a project : returns true if user is allowed to do the specified action on this project
354 355 # * a group of projects : returns true if user is allowed on every project
355 356 # * nil with options[:global] set : check if user has at least one role allowed for this action,
356 357 # or falls back to Non Member / Anonymous permissions depending if the user is logged
357 358 def allowed_to?(action, context, options={})
358 359 if context && context.is_a?(Project)
359 360 # No action allowed on archived projects
360 361 return false unless context.active?
361 362 # No action allowed on disabled modules
362 363 return false unless context.allows_to?(action)
363 364 # Admin users are authorized for anything else
364 365 return true if admin?
365 366
366 367 roles = roles_for_project(context)
367 368 return false unless roles
368 369 roles.detect {|role| (context.is_public? || role.member?) && role.allowed_to?(action)}
369 370
370 371 elsif context && context.is_a?(Array)
371 372 # Authorize if user is authorized on every element of the array
372 373 context.map do |project|
373 374 allowed_to?(action,project,options)
374 375 end.inject do |memo,allowed|
375 376 memo && allowed
376 377 end
377 378 elsif options[:global]
378 379 # Admin users are always authorized
379 380 return true if admin?
380 381
381 382 # authorize if user has at least one role that has this permission
382 383 roles = memberships.collect {|m| m.roles}.flatten.uniq
383 384 roles.detect {|r| r.allowed_to?(action)} || (self.logged? ? Role.non_member.allowed_to?(action) : Role.anonymous.allowed_to?(action))
384 385 else
385 386 false
386 387 end
387 388 end
388 389
389 390 # Is the user allowed to do the specified action on any project?
390 391 # See allowed_to? for the actions and valid options.
391 392 def allowed_to_globally?(action, options)
392 393 allowed_to?(action, nil, options.reverse_merge(:global => true))
393 394 end
394 395
395 396 safe_attributes 'login',
396 397 'firstname',
397 398 'lastname',
398 399 'mail',
399 400 'mail_notification',
400 401 'language',
401 402 'custom_field_values',
402 403 'custom_fields',
403 404 'identity_url'
404 405
405 406 safe_attributes 'status',
406 407 'auth_source_id',
407 408 :if => lambda {|user, current_user| current_user.admin?}
408 409
409 410 # Utility method to help check if a user should be notified about an
410 411 # event.
411 412 #
412 413 # TODO: only supports Issue events currently
413 414 def notify_about?(object)
414 case mail_notification.to_sym
415 when :all
415 case mail_notification
416 when 'all'
416 417 true
417 when :selected
418 when 'selected'
418 419 # Handled by the Project
419 when :none
420 when 'none'
420 421 false
421 when :only_my_events
422 when 'only_my_events'
422 423 if object.is_a?(Issue) && (object.author == self || object.assigned_to == self)
423 424 true
424 425 else
425 426 false
426 427 end
427 when :only_assigned
428 when 'only_assigned'
428 429 if object.is_a?(Issue) && object.assigned_to == self
429 430 true
430 431 else
431 432 false
432 433 end
433 when :only_owner
434 when 'only_owner'
434 435 if object.is_a?(Issue) && object.author == self
435 436 true
436 437 else
437 438 false
438 439 end
439 440 else
440 441 false
441 442 end
442 443 end
443 444
444 445 def self.current=(user)
445 446 @current_user = user
446 447 end
447 448
448 449 def self.current
449 450 @current_user ||= User.anonymous
450 451 end
451 452
452 453 # Returns the anonymous user. If the anonymous user does not exist, it is created. There can be only
453 454 # one anonymous user per database.
454 455 def self.anonymous
455 456 anonymous_user = AnonymousUser.find(:first)
456 457 if anonymous_user.nil?
457 458 anonymous_user = AnonymousUser.create(:lastname => 'Anonymous', :firstname => '', :mail => '', :login => '', :status => 0)
458 459 raise 'Unable to create the anonymous user.' if anonymous_user.new_record?
459 460 end
460 461 anonymous_user
461 462 end
462 463
463 464 protected
464 465
465 466 def validate
466 467 # Password length validation based on setting
467 468 if !password.nil? && password.size < Setting.password_min_length.to_i
468 469 errors.add(:password, :too_short, :count => Setting.password_min_length.to_i)
469 470 end
470 471 end
471 472
472 473 private
473 474
474 475 # Return password digest
475 476 def self.hash_password(clear_password)
476 477 Digest::SHA1.hexdigest(clear_password || "")
477 478 end
478 479 end
479 480
480 481 class AnonymousUser < User
481 482
482 483 def validate_on_create
483 484 # There should be only one AnonymousUser in the database
484 485 errors.add_to_base 'An anonymous user already exists.' if AnonymousUser.find(:first)
485 486 end
486 487
487 488 def available_custom_fields
488 489 []
489 490 end
490 491
491 492 # Overrides a few properties
492 493 def logged?; false end
493 494 def admin; false end
494 495 def name(*args); I18n.t(:label_user_anonymous) end
495 496 def mail; nil end
496 497 def time_zone; nil end
497 498 def rss_key; nil end
498 499 end
@@ -1,531 +1,532
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.dirname(__FILE__) + '/../test_helper'
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 112 def test_destroy
113 113 User.find(2).destroy
114 114 assert_nil User.find_by_id(2)
115 115 assert Member.find_all_by_user_id(2).empty?
116 116 end
117 117
118 def test_validate
118 def test_validate_login_presence
119 119 @admin.login = ""
120 120 assert !@admin.save
121 121 assert_equal 1, @admin.errors.count
122 122 end
123 123
124 def test_validate_mail_notification_inclusion
125 u = User.new
126 u.mail_notification = 'foo'
127 u.save
128 assert_not_nil u.errors.on(:mail_notification)
129 end
130
124 131 context "User#try_to_login" do
125 132 should "fall-back to case-insensitive if user login is not found as-typed." do
126 133 user = User.try_to_login("AdMin", "admin")
127 134 assert_kind_of User, user
128 135 assert_equal "admin", user.login
129 136 end
130 137
131 138 should "select the exact matching user first" do
132 139 case_sensitive_user = User.generate_with_protected!(:login => 'changed', :password => 'admin', :password_confirmation => 'admin')
133 140 # bypass validations to make it appear like existing data
134 141 case_sensitive_user.update_attribute(:login, 'ADMIN')
135 142
136 143 user = User.try_to_login("ADMIN", "admin")
137 144 assert_kind_of User, user
138 145 assert_equal "ADMIN", user.login
139 146
140 147 end
141 148 end
142 149
143 150 def test_password
144 151 user = User.try_to_login("admin", "admin")
145 152 assert_kind_of User, user
146 153 assert_equal "admin", user.login
147 154 user.password = "hello"
148 155 assert user.save
149 156
150 157 user = User.try_to_login("admin", "hello")
151 158 assert_kind_of User, user
152 159 assert_equal "admin", user.login
153 160 assert_equal User.hash_password("hello"), user.hashed_password
154 161 end
155 162
156 163 def test_name_format
157 164 assert_equal 'Smith, John', @jsmith.name(:lastname_coma_firstname)
158 165 Setting.user_format = :firstname_lastname
159 166 assert_equal 'John Smith', @jsmith.reload.name
160 167 Setting.user_format = :username
161 168 assert_equal 'jsmith', @jsmith.reload.name
162 169 end
163 170
164 171 def test_lock
165 172 user = User.try_to_login("jsmith", "jsmith")
166 173 assert_equal @jsmith, user
167 174
168 175 @jsmith.status = User::STATUS_LOCKED
169 176 assert @jsmith.save
170 177
171 178 user = User.try_to_login("jsmith", "jsmith")
172 179 assert_equal nil, user
173 180 end
174 181
175 182 if ldap_configured?
176 183 context "#try_to_login using LDAP" do
177 184 context "with failed connection to the LDAP server" do
178 185 should "return nil" do
179 186 @auth_source = AuthSourceLdap.find(1)
180 187 AuthSource.any_instance.stubs(:initialize_ldap_con).raises(Net::LDAP::LdapError, 'Cannot connect')
181 188
182 189 assert_equal nil, User.try_to_login('edavis', 'wrong')
183 190 end
184 191 end
185 192
186 193 context "with an unsuccessful authentication" do
187 194 should "return nil" do
188 195 assert_equal nil, User.try_to_login('edavis', 'wrong')
189 196 end
190 197 end
191 198
192 199 context "on the fly registration" do
193 200 setup do
194 201 @auth_source = AuthSourceLdap.find(1)
195 202 end
196 203
197 204 context "with a successful authentication" do
198 205 should "create a new user account if it doesn't exist" do
199 206 assert_difference('User.count') do
200 207 user = User.try_to_login('edavis', '123456')
201 208 assert !user.admin?
202 209 end
203 210 end
204 211
205 212 should "retrieve existing user" do
206 213 user = User.try_to_login('edavis', '123456')
207 214 user.admin = true
208 215 user.save!
209 216
210 217 assert_no_difference('User.count') do
211 218 user = User.try_to_login('edavis', '123456')
212 219 assert user.admin?
213 220 end
214 221 end
215 222 end
216 223 end
217 224 end
218 225
219 226 else
220 227 puts "Skipping LDAP tests."
221 228 end
222 229
223 230 def test_create_anonymous
224 231 AnonymousUser.delete_all
225 232 anon = User.anonymous
226 233 assert !anon.new_record?
227 234 assert_kind_of AnonymousUser, anon
228 235 end
229 236
230 237 should_have_one :rss_token
231 238
232 239 def test_rss_key
233 240 assert_nil @jsmith.rss_token
234 241 key = @jsmith.rss_key
235 242 assert_equal 40, key.length
236 243
237 244 @jsmith.reload
238 245 assert_equal key, @jsmith.rss_key
239 246 end
240 247
241 248
242 249 should_have_one :api_token
243 250
244 251 context "User#api_key" do
245 252 should "generate a new one if the user doesn't have one" do
246 253 user = User.generate_with_protected!(:api_token => nil)
247 254 assert_nil user.api_token
248 255
249 256 key = user.api_key
250 257 assert_equal 40, key.length
251 258 user.reload
252 259 assert_equal key, user.api_key
253 260 end
254 261
255 262 should "return the existing api token value" do
256 263 user = User.generate_with_protected!
257 264 token = Token.generate!(:action => 'api')
258 265 user.api_token = token
259 266 assert user.save
260 267
261 268 assert_equal token.value, user.api_key
262 269 end
263 270 end
264 271
265 272 context "User#find_by_api_key" do
266 273 should "return nil if no matching key is found" do
267 274 assert_nil User.find_by_api_key('zzzzzzzzz')
268 275 end
269 276
270 277 should "return nil if the key is found for an inactive user" do
271 278 user = User.generate_with_protected!(:status => User::STATUS_LOCKED)
272 279 token = Token.generate!(:action => 'api')
273 280 user.api_token = token
274 281 user.save
275 282
276 283 assert_nil User.find_by_api_key(token.value)
277 284 end
278 285
279 286 should "return the user if the key is found for an active user" do
280 287 user = User.generate_with_protected!(:status => User::STATUS_ACTIVE)
281 288 token = Token.generate!(:action => 'api')
282 289 user.api_token = token
283 290 user.save
284 291
285 292 assert_equal user, User.find_by_api_key(token.value)
286 293 end
287 294 end
288 295
289 296 def test_roles_for_project
290 297 # user with a role
291 298 roles = @jsmith.roles_for_project(Project.find(1))
292 299 assert_kind_of Role, roles.first
293 300 assert_equal "Manager", roles.first.name
294 301
295 302 # user with no role
296 303 assert_nil @dlopper.roles_for_project(Project.find(2)).detect {|role| role.member?}
297 304 end
298 305
299 306 def test_mail_notification_all
300 307 @jsmith.mail_notification = 'all'
301 308 @jsmith.notified_project_ids = []
302 309 @jsmith.save
303 310 @jsmith.reload
304 311 assert @jsmith.projects.first.recipients.include?(@jsmith.mail)
305 312 end
306 313
307 314 def test_mail_notification_selected
308 315 @jsmith.mail_notification = 'selected'
309 316 @jsmith.notified_project_ids = [1]
310 317 @jsmith.save
311 318 @jsmith.reload
312 319 assert Project.find(1).recipients.include?(@jsmith.mail)
313 320 end
314 321
315 322 def test_mail_notification_only_my_events
316 323 @jsmith.mail_notification = 'only_my_events'
317 324 @jsmith.notified_project_ids = []
318 325 @jsmith.save
319 326 @jsmith.reload
320 327 assert !@jsmith.projects.first.recipients.include?(@jsmith.mail)
321 328 end
322 329
323 330 def test_comments_sorting_preference
324 331 assert !@jsmith.wants_comments_in_reverse_order?
325 332 @jsmith.pref.comments_sorting = 'asc'
326 333 assert !@jsmith.wants_comments_in_reverse_order?
327 334 @jsmith.pref.comments_sorting = 'desc'
328 335 assert @jsmith.wants_comments_in_reverse_order?
329 336 end
330 337
331 338 def test_find_by_mail_should_be_case_insensitive
332 339 u = User.find_by_mail('JSmith@somenet.foo')
333 340 assert_not_nil u
334 341 assert_equal 'jsmith@somenet.foo', u.mail
335 342 end
336 343
337 344 def test_random_password
338 345 u = User.new
339 346 u.random_password
340 347 assert !u.password.blank?
341 348 assert !u.password_confirmation.blank?
342 349 end
343 350
344 351 context "#change_password_allowed?" do
345 352 should "be allowed if no auth source is set" do
346 353 user = User.generate_with_protected!
347 354 assert user.change_password_allowed?
348 355 end
349 356
350 357 should "delegate to the auth source" do
351 358 user = User.generate_with_protected!
352 359
353 360 allowed_auth_source = AuthSource.generate!
354 361 def allowed_auth_source.allow_password_changes?; true; end
355 362
356 363 denied_auth_source = AuthSource.generate!
357 364 def denied_auth_source.allow_password_changes?; false; end
358 365
359 366 assert user.change_password_allowed?
360 367
361 368 user.auth_source = allowed_auth_source
362 369 assert user.change_password_allowed?, "User not allowed to change password, though auth source does"
363 370
364 371 user.auth_source = denied_auth_source
365 372 assert !user.change_password_allowed?, "User allowed to change password, though auth source does not"
366 373 end
367 374
368 375 end
369 376
370 377 context "#allowed_to?" do
371 378 context "with a unique project" do
372 379 should "return false if project is archived" do
373 380 project = Project.find(1)
374 381 Project.any_instance.stubs(:status).returns(Project::STATUS_ARCHIVED)
375 382 assert ! @admin.allowed_to?(:view_issues, Project.find(1))
376 383 end
377 384
378 385 should "return false if related module is disabled" do
379 386 project = Project.find(1)
380 387 project.enabled_module_names = ["issue_tracking"]
381 388 assert @admin.allowed_to?(:add_issues, project)
382 389 assert ! @admin.allowed_to?(:view_wiki_pages, project)
383 390 end
384 391
385 392 should "authorize nearly everything for admin users" do
386 393 project = Project.find(1)
387 394 assert ! @admin.member_of?(project)
388 395 %w(edit_issues delete_issues manage_news manage_documents manage_wiki).each do |p|
389 396 assert @admin.allowed_to?(p.to_sym, project)
390 397 end
391 398 end
392 399
393 400 should "authorize normal users depending on their roles" do
394 401 project = Project.find(1)
395 402 assert @jsmith.allowed_to?(:delete_messages, project) #Manager
396 403 assert ! @dlopper.allowed_to?(:delete_messages, project) #Developper
397 404 end
398 405 end
399 406
400 407 context "with multiple projects" do
401 408 should "return false if array is empty" do
402 409 assert ! @admin.allowed_to?(:view_project, [])
403 410 end
404 411
405 412 should "return true only if user has permission on all these projects" do
406 413 assert @admin.allowed_to?(:view_project, Project.all)
407 414 assert ! @dlopper.allowed_to?(:view_project, Project.all) #cannot see Project(2)
408 415 assert @jsmith.allowed_to?(:edit_issues, @jsmith.projects) #Manager or Developer everywhere
409 416 assert ! @jsmith.allowed_to?(:delete_issue_watchers, @jsmith.projects) #Dev cannot delete_issue_watchers
410 417 end
411 418
412 419 should "behave correctly with arrays of 1 project" do
413 420 assert ! User.anonymous.allowed_to?(:delete_issues, [Project.first])
414 421 end
415 422 end
416 423
417 424 context "with options[:global]" do
418 425 should "authorize if user has at least one role that has this permission" do
419 426 @dlopper2 = User.find(5) #only Developper on a project, not Manager anywhere
420 427 @anonymous = User.find(6)
421 428 assert @jsmith.allowed_to?(:delete_issue_watchers, nil, :global => true)
422 429 assert ! @dlopper2.allowed_to?(:delete_issue_watchers, nil, :global => true)
423 430 assert @dlopper2.allowed_to?(:add_issues, nil, :global => true)
424 431 assert ! @anonymous.allowed_to?(:add_issues, nil, :global => true)
425 432 assert @anonymous.allowed_to?(:view_issues, nil, :global => true)
426 433 end
427 434 end
428 435 end
429 436
430 437 context "User#notify_about?" do
431 438 context "Issues" do
432 439 setup do
433 440 @project = Project.find(1)
434 441 @author = User.generate_with_protected!
435 442 @assignee = User.generate_with_protected!
436 443 @issue = Issue.generate_for_project!(@project, :assigned_to => @assignee, :author => @author)
437 444 end
438 445
439 446 should "be true for a user with :all" do
440 @author.update_attribute(:mail_notification, :all)
447 @author.update_attribute(:mail_notification, 'all')
441 448 assert @author.notify_about?(@issue)
442 449 end
443 450
444 451 should "be false for a user with :none" do
445 @author.update_attribute(:mail_notification, :none)
452 @author.update_attribute(:mail_notification, 'none')
446 453 assert ! @author.notify_about?(@issue)
447 454 end
448 455
449 456 should "be false for a user with :only_my_events and isn't an author, creator, or assignee" do
450 @user = User.generate_with_protected!(:mail_notification => :only_my_events)
457 @user = User.generate_with_protected!(:mail_notification => 'only_my_events')
451 458 assert ! @user.notify_about?(@issue)
452 459 end
453 460
454 461 should "be true for a user with :only_my_events and is the author" do
455 @author.update_attribute(:mail_notification, :only_my_events)
462 @author.update_attribute(:mail_notification, 'only_my_events')
456 463 assert @author.notify_about?(@issue)
457 464 end
458 465
459 466 should "be true for a user with :only_my_events and is the assignee" do
460 @assignee.update_attribute(:mail_notification, :only_my_events)
467 @assignee.update_attribute(:mail_notification, 'only_my_events')
461 468 assert @assignee.notify_about?(@issue)
462 469 end
463 470
464 471 should "be true for a user with :only_assigned and is the assignee" do
465 @assignee.update_attribute(:mail_notification, :only_assigned)
472 @assignee.update_attribute(:mail_notification, 'only_assigned')
466 473 assert @assignee.notify_about?(@issue)
467 474 end
468 475
469 476 should "be false for a user with :only_assigned and is not the assignee" do
470 @author.update_attribute(:mail_notification, :only_assigned)
477 @author.update_attribute(:mail_notification, 'only_assigned')
471 478 assert ! @author.notify_about?(@issue)
472 479 end
473 480
474 481 should "be true for a user with :only_owner and is the author" do
475 @author.update_attribute(:mail_notification, :only_owner)
482 @author.update_attribute(:mail_notification, 'only_owner')
476 483 assert @author.notify_about?(@issue)
477 484 end
478 485
479 486 should "be false for a user with :only_owner and is not the author" do
480 @assignee.update_attribute(:mail_notification, :only_owner)
487 @assignee.update_attribute(:mail_notification, 'only_owner')
481 488 assert ! @assignee.notify_about?(@issue)
482 489 end
483
484 should "be false if the mail_notification is anything else" do
485 @assignee.update_attribute(:mail_notification, :somthing_else)
486 assert ! @assignee.notify_about?(@issue)
487 end
488
489 490 end
490 491
491 492 context "other events" do
492 493 should 'be added and tested'
493 494 end
494 495 end
495 496
496 497 if Object.const_defined?(:OpenID)
497 498
498 499 def test_setting_identity_url
499 500 normalized_open_id_url = 'http://example.com/'
500 501 u = User.new( :identity_url => 'http://example.com/' )
501 502 assert_equal normalized_open_id_url, u.identity_url
502 503 end
503 504
504 505 def test_setting_identity_url_without_trailing_slash
505 506 normalized_open_id_url = 'http://example.com/'
506 507 u = User.new( :identity_url => 'http://example.com' )
507 508 assert_equal normalized_open_id_url, u.identity_url
508 509 end
509 510
510 511 def test_setting_identity_url_without_protocol
511 512 normalized_open_id_url = 'http://example.com/'
512 513 u = User.new( :identity_url => 'example.com' )
513 514 assert_equal normalized_open_id_url, u.identity_url
514 515 end
515 516
516 517 def test_setting_blank_identity_url
517 518 u = User.new( :identity_url => 'example.com' )
518 519 u.identity_url = ''
519 520 assert u.identity_url.blank?
520 521 end
521 522
522 523 def test_setting_invalid_identity_url
523 524 u = User.new( :identity_url => 'this is not an openid url' )
524 525 assert u.identity_url.blank?
525 526 end
526 527
527 528 else
528 529 puts "Skipping openid tests."
529 530 end
530 531
531 532 end
General Comments 0
You need to be logged in to leave comments. Login now