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