##// END OF EJS Templates
Merged r3873 from trunk....
Jean-Philippe Lang -
r3760:ec526c1261ac
parent child
Show More
@@ -1,382 +1,386
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 has_and_belongs_to_many :groups, :after_add => Proc.new {|user, group| group.user_added(user)},
37 37 :after_remove => Proc.new {|user, group| group.user_removed(user)}
38 38 has_many :issue_categories, :foreign_key => 'assigned_to_id', :dependent => :nullify
39 39 has_many :changesets, :dependent => :nullify
40 40 has_one :preference, :dependent => :destroy, :class_name => 'UserPreference'
41 41 has_one :rss_token, :dependent => :destroy, :class_name => 'Token', :conditions => "action='feeds'"
42 42 has_one :api_token, :dependent => :destroy, :class_name => 'Token', :conditions => "action='api'"
43 43 belongs_to :auth_source
44 44
45 45 # Active non-anonymous users scope
46 46 named_scope :active, :conditions => "#{User.table_name}.status = #{STATUS_ACTIVE}"
47 47
48 48 acts_as_customizable
49 49
50 50 attr_accessor :password, :password_confirmation
51 51 attr_accessor :last_before_login_on
52 52 # Prevents unauthorized assignments
53 53 attr_protected :login, :admin, :password, :password_confirmation, :hashed_password, :group_ids
54 54
55 55 validates_presence_of :login, :firstname, :lastname, :mail, :if => Proc.new { |user| !user.is_a?(AnonymousUser) }
56 56 validates_uniqueness_of :login, :if => Proc.new { |user| !user.login.blank? }, :case_sensitive => false
57 57 validates_uniqueness_of :mail, :if => Proc.new { |user| !user.mail.blank? }, :case_sensitive => false
58 58 # Login must contain lettres, numbers, underscores only
59 59 validates_format_of :login, :with => /^[a-z0-9_\-@\.]*$/i
60 60 validates_length_of :login, :maximum => 30
61 61 validates_format_of :firstname, :lastname, :with => /^[\w\s\'\-\.]*$/i
62 62 validates_length_of :firstname, :lastname, :maximum => 30
63 63 validates_format_of :mail, :with => /^([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})$/i, :allow_nil => true
64 64 validates_length_of :mail, :maximum => 60, :allow_nil => true
65 65 validates_confirmation_of :password, :allow_nil => true
66 66
67 67 def before_create
68 68 self.mail_notification = false
69 69 true
70 70 end
71 71
72 72 def before_save
73 73 # update hashed_password if password was set
74 74 self.hashed_password = User.hash_password(self.password) if self.password && self.auth_source_id.blank?
75 75 end
76 76
77 77 def reload(*args)
78 78 @name = nil
79 79 super
80 80 end
81 81
82 def mail=(arg)
83 write_attribute(:mail, arg.to_s.strip)
84 end
85
82 86 def identity_url=(url)
83 87 if url.blank?
84 88 write_attribute(:identity_url, '')
85 89 else
86 90 begin
87 91 write_attribute(:identity_url, OpenIdAuthentication.normalize_identifier(url))
88 92 rescue OpenIdAuthentication::InvalidOpenId
89 93 # Invlaid url, don't save
90 94 end
91 95 end
92 96 self.read_attribute(:identity_url)
93 97 end
94 98
95 99 # Returns the user that matches provided login and password, or nil
96 100 def self.try_to_login(login, password)
97 101 # Make sure no one can sign in with an empty password
98 102 return nil if password.to_s.empty?
99 103 user = find_by_login(login)
100 104 if user
101 105 # user is already in local database
102 106 return nil if !user.active?
103 107 if user.auth_source
104 108 # user has an external authentication method
105 109 return nil unless user.auth_source.authenticate(login, password)
106 110 else
107 111 # authentication with local password
108 112 return nil unless User.hash_password(password) == user.hashed_password
109 113 end
110 114 else
111 115 # user is not yet registered, try to authenticate with available sources
112 116 attrs = AuthSource.authenticate(login, password)
113 117 if attrs
114 118 user = new(attrs)
115 119 user.login = login
116 120 user.language = Setting.default_language
117 121 if user.save
118 122 user.reload
119 123 logger.info("User '#{user.login}' created from external auth source: #{user.auth_source.type} - #{user.auth_source.name}") if logger && user.auth_source
120 124 end
121 125 end
122 126 end
123 127 user.update_attribute(:last_login_on, Time.now) if user && !user.new_record?
124 128 user
125 129 rescue => text
126 130 raise text
127 131 end
128 132
129 133 # Returns the user who matches the given autologin +key+ or nil
130 134 def self.try_to_autologin(key)
131 135 tokens = Token.find_all_by_action_and_value('autologin', key)
132 136 # Make sure there's only 1 token that matches the key
133 137 if tokens.size == 1
134 138 token = tokens.first
135 139 if (token.created_on > Setting.autologin.to_i.day.ago) && token.user && token.user.active?
136 140 token.user.update_attribute(:last_login_on, Time.now)
137 141 token.user
138 142 end
139 143 end
140 144 end
141 145
142 146 # Return user's full name for display
143 147 def name(formatter = nil)
144 148 if formatter
145 149 eval('"' + (USER_FORMATS[formatter] || USER_FORMATS[:firstname_lastname]) + '"')
146 150 else
147 151 @name ||= eval('"' + (USER_FORMATS[Setting.user_format] || USER_FORMATS[:firstname_lastname]) + '"')
148 152 end
149 153 end
150 154
151 155 def active?
152 156 self.status == STATUS_ACTIVE
153 157 end
154 158
155 159 def registered?
156 160 self.status == STATUS_REGISTERED
157 161 end
158 162
159 163 def locked?
160 164 self.status == STATUS_LOCKED
161 165 end
162 166
163 167 def check_password?(clear_password)
164 168 if auth_source_id.present?
165 169 auth_source.authenticate(self.login, clear_password)
166 170 else
167 171 User.hash_password(clear_password) == self.hashed_password
168 172 end
169 173 end
170 174
171 175 # Does the backend storage allow this user to change their password?
172 176 def change_password_allowed?
173 177 return true if auth_source_id.blank?
174 178 return auth_source.allow_password_changes?
175 179 end
176 180
177 181 # Generate and set a random password. Useful for automated user creation
178 182 # Based on Token#generate_token_value
179 183 #
180 184 def random_password
181 185 chars = ("a".."z").to_a + ("A".."Z").to_a + ("0".."9").to_a
182 186 password = ''
183 187 40.times { |i| password << chars[rand(chars.size-1)] }
184 188 self.password = password
185 189 self.password_confirmation = password
186 190 self
187 191 end
188 192
189 193 def pref
190 194 self.preference ||= UserPreference.new(:user => self)
191 195 end
192 196
193 197 def time_zone
194 198 @time_zone ||= (self.pref.time_zone.blank? ? nil : ActiveSupport::TimeZone[self.pref.time_zone])
195 199 end
196 200
197 201 def wants_comments_in_reverse_order?
198 202 self.pref[:comments_sorting] == 'desc'
199 203 end
200 204
201 205 # Return user's RSS key (a 40 chars long string), used to access feeds
202 206 def rss_key
203 207 token = self.rss_token || Token.create(:user => self, :action => 'feeds')
204 208 token.value
205 209 end
206 210
207 211 # Return user's API key (a 40 chars long string), used to access the API
208 212 def api_key
209 213 token = self.api_token || self.create_api_token(:action => 'api')
210 214 token.value
211 215 end
212 216
213 217 # Return an array of project ids for which the user has explicitly turned mail notifications on
214 218 def notified_projects_ids
215 219 @notified_projects_ids ||= memberships.select {|m| m.mail_notification?}.collect(&:project_id)
216 220 end
217 221
218 222 def notified_project_ids=(ids)
219 223 Member.update_all("mail_notification = #{connection.quoted_false}", ['user_id = ?', id])
220 224 Member.update_all("mail_notification = #{connection.quoted_true}", ['user_id = ? AND project_id IN (?)', id, ids]) if ids && !ids.empty?
221 225 @notified_projects_ids = nil
222 226 notified_projects_ids
223 227 end
224 228
225 229 # Find a user account by matching the exact login and then a case-insensitive
226 230 # version. Exact matches will be given priority.
227 231 def self.find_by_login(login)
228 232 # force string comparison to be case sensitive on MySQL
229 233 type_cast = (ActiveRecord::Base.connection.adapter_name == 'MySQL') ? 'BINARY' : ''
230 234
231 235 # First look for an exact match
232 236 user = first(:conditions => ["#{type_cast} login = ?", login])
233 237 # Fail over to case-insensitive if none was found
234 238 user ||= first(:conditions => ["#{type_cast} LOWER(login) = ?", login.to_s.downcase])
235 239 end
236 240
237 241 def self.find_by_rss_key(key)
238 242 token = Token.find_by_value(key)
239 243 token && token.user.active? ? token.user : nil
240 244 end
241 245
242 246 def self.find_by_api_key(key)
243 247 token = Token.find_by_action_and_value('api', key)
244 248 token && token.user.active? ? token.user : nil
245 249 end
246 250
247 251 # Makes find_by_mail case-insensitive
248 252 def self.find_by_mail(mail)
249 253 find(:first, :conditions => ["LOWER(mail) = ?", mail.to_s.downcase])
250 254 end
251 255
252 256 def to_s
253 257 name
254 258 end
255 259
256 260 # Returns the current day according to user's time zone
257 261 def today
258 262 if time_zone.nil?
259 263 Date.today
260 264 else
261 265 Time.now.in_time_zone(time_zone).to_date
262 266 end
263 267 end
264 268
265 269 def logged?
266 270 true
267 271 end
268 272
269 273 def anonymous?
270 274 !logged?
271 275 end
272 276
273 277 # Return user's roles for project
274 278 def roles_for_project(project)
275 279 roles = []
276 280 # No role on archived projects
277 281 return roles unless project && project.active?
278 282 if logged?
279 283 # Find project membership
280 284 membership = memberships.detect {|m| m.project_id == project.id}
281 285 if membership
282 286 roles = membership.roles
283 287 else
284 288 @role_non_member ||= Role.non_member
285 289 roles << @role_non_member
286 290 end
287 291 else
288 292 @role_anonymous ||= Role.anonymous
289 293 roles << @role_anonymous
290 294 end
291 295 roles
292 296 end
293 297
294 298 # Return true if the user is a member of project
295 299 def member_of?(project)
296 300 !roles_for_project(project).detect {|role| role.member?}.nil?
297 301 end
298 302
299 303 # Return true if the user is allowed to do the specified action on project
300 304 # action can be:
301 305 # * a parameter-like Hash (eg. :controller => 'projects', :action => 'edit')
302 306 # * a permission Symbol (eg. :edit_project)
303 307 def allowed_to?(action, project, options={})
304 308 if project
305 309 # No action allowed on archived projects
306 310 return false unless project.active?
307 311 # No action allowed on disabled modules
308 312 return false unless project.allows_to?(action)
309 313 # Admin users are authorized for anything else
310 314 return true if admin?
311 315
312 316 roles = roles_for_project(project)
313 317 return false unless roles
314 318 roles.detect {|role| (project.is_public? || role.member?) && role.allowed_to?(action)}
315 319
316 320 elsif options[:global]
317 321 # Admin users are always authorized
318 322 return true if admin?
319 323
320 324 # authorize if user has at least one role that has this permission
321 325 roles = memberships.collect {|m| m.roles}.flatten.uniq
322 326 roles.detect {|r| r.allowed_to?(action)} || (self.logged? ? Role.non_member.allowed_to?(action) : Role.anonymous.allowed_to?(action))
323 327 else
324 328 false
325 329 end
326 330 end
327 331
328 332 def self.current=(user)
329 333 @current_user = user
330 334 end
331 335
332 336 def self.current
333 337 @current_user ||= User.anonymous
334 338 end
335 339
336 340 # Returns the anonymous user. If the anonymous user does not exist, it is created. There can be only
337 341 # one anonymous user per database.
338 342 def self.anonymous
339 343 anonymous_user = AnonymousUser.find(:first)
340 344 if anonymous_user.nil?
341 345 anonymous_user = AnonymousUser.create(:lastname => 'Anonymous', :firstname => '', :mail => '', :login => '', :status => 0)
342 346 raise 'Unable to create the anonymous user.' if anonymous_user.new_record?
343 347 end
344 348 anonymous_user
345 349 end
346 350
347 351 protected
348 352
349 353 def validate
350 354 # Password length validation based on setting
351 355 if !password.nil? && password.size < Setting.password_min_length.to_i
352 356 errors.add(:password, :too_short, :count => Setting.password_min_length.to_i)
353 357 end
354 358 end
355 359
356 360 private
357 361
358 362 # Return password digest
359 363 def self.hash_password(clear_password)
360 364 Digest::SHA1.hexdigest(clear_password || "")
361 365 end
362 366 end
363 367
364 368 class AnonymousUser < User
365 369
366 370 def validate_on_create
367 371 # There should be only one AnonymousUser in the database
368 372 errors.add_to_base 'An anonymous user already exists.' if AnonymousUser.find(:first)
369 373 end
370 374
371 375 def available_custom_fields
372 376 []
373 377 end
374 378
375 379 # Overrides a few properties
376 380 def logged?; false end
377 381 def admin; false end
378 382 def name(*args); I18n.t(:label_user_anonymous) end
379 383 def mail; nil end
380 384 def time_zone; nil end
381 385 def rss_key; nil end
382 386 end
@@ -1,387 +1,393
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
39 def test_mail_should_be_stripped
40 u = User.new
41 u.mail = " foo@bar.com "
42 assert_equal "foo@bar.com", u.mail
43 end
38 44
39 45 def test_create
40 46 user = User.new(:firstname => "new", :lastname => "user", :mail => "newuser@somenet.foo")
41 47
42 48 user.login = "jsmith"
43 49 user.password, user.password_confirmation = "password", "password"
44 50 # login uniqueness
45 51 assert !user.save
46 52 assert_equal 1, user.errors.count
47 53
48 54 user.login = "newuser"
49 55 user.password, user.password_confirmation = "passwd", "password"
50 56 # password confirmation
51 57 assert !user.save
52 58 assert_equal 1, user.errors.count
53 59
54 60 user.password, user.password_confirmation = "password", "password"
55 61 assert user.save
56 62 end
57 63
58 64 context "User.login" do
59 65 should "be case-insensitive." do
60 66 u = User.new(:firstname => "new", :lastname => "user", :mail => "newuser@somenet.foo")
61 67 u.login = 'newuser'
62 68 u.password, u.password_confirmation = "password", "password"
63 69 assert u.save
64 70
65 71 u = User.new(:firstname => "Similar", :lastname => "User", :mail => "similaruser@somenet.foo")
66 72 u.login = 'NewUser'
67 73 u.password, u.password_confirmation = "password", "password"
68 74 assert !u.save
69 75 assert_equal I18n.translate('activerecord.errors.messages.taken'), u.errors.on(:login)
70 76 end
71 77 end
72 78
73 79 def test_mail_uniqueness_should_not_be_case_sensitive
74 80 u = User.new(:firstname => "new", :lastname => "user", :mail => "newuser@somenet.foo")
75 81 u.login = 'newuser1'
76 82 u.password, u.password_confirmation = "password", "password"
77 83 assert u.save
78 84
79 85 u = User.new(:firstname => "new", :lastname => "user", :mail => "newUser@Somenet.foo")
80 86 u.login = 'newuser2'
81 87 u.password, u.password_confirmation = "password", "password"
82 88 assert !u.save
83 89 assert_equal I18n.translate('activerecord.errors.messages.taken'), u.errors.on(:mail)
84 90 end
85 91
86 92 def test_update
87 93 assert_equal "admin", @admin.login
88 94 @admin.login = "john"
89 95 assert @admin.save, @admin.errors.full_messages.join("; ")
90 96 @admin.reload
91 97 assert_equal "john", @admin.login
92 98 end
93 99
94 100 def test_destroy
95 101 User.find(2).destroy
96 102 assert_nil User.find_by_id(2)
97 103 assert Member.find_all_by_user_id(2).empty?
98 104 end
99 105
100 106 def test_validate
101 107 @admin.login = ""
102 108 assert !@admin.save
103 109 assert_equal 1, @admin.errors.count
104 110 end
105 111
106 112 context "User#try_to_login" do
107 113 should "fall-back to case-insensitive if user login is not found as-typed." do
108 114 user = User.try_to_login("AdMin", "admin")
109 115 assert_kind_of User, user
110 116 assert_equal "admin", user.login
111 117 end
112 118
113 119 should "select the exact matching user first" do
114 120 case_sensitive_user = User.generate_with_protected!(:login => 'changed', :password => 'admin', :password_confirmation => 'admin')
115 121 # bypass validations to make it appear like existing data
116 122 case_sensitive_user.update_attribute(:login, 'ADMIN')
117 123
118 124 user = User.try_to_login("ADMIN", "admin")
119 125 assert_kind_of User, user
120 126 assert_equal "ADMIN", user.login
121 127
122 128 end
123 129 end
124 130
125 131 def test_password
126 132 user = User.try_to_login("admin", "admin")
127 133 assert_kind_of User, user
128 134 assert_equal "admin", user.login
129 135 user.password = "hello"
130 136 assert user.save
131 137
132 138 user = User.try_to_login("admin", "hello")
133 139 assert_kind_of User, user
134 140 assert_equal "admin", user.login
135 141 assert_equal User.hash_password("hello"), user.hashed_password
136 142 end
137 143
138 144 def test_name_format
139 145 assert_equal 'Smith, John', @jsmith.name(:lastname_coma_firstname)
140 146 Setting.user_format = :firstname_lastname
141 147 assert_equal 'John Smith', @jsmith.reload.name
142 148 Setting.user_format = :username
143 149 assert_equal 'jsmith', @jsmith.reload.name
144 150 end
145 151
146 152 def test_lock
147 153 user = User.try_to_login("jsmith", "jsmith")
148 154 assert_equal @jsmith, user
149 155
150 156 @jsmith.status = User::STATUS_LOCKED
151 157 assert @jsmith.save
152 158
153 159 user = User.try_to_login("jsmith", "jsmith")
154 160 assert_equal nil, user
155 161 end
156 162
157 163 if ldap_configured?
158 164 context "#try_to_login using LDAP" do
159 165 context "with failed connection to the LDAP server" do
160 166 should "return nil" do
161 167 @auth_source = AuthSourceLdap.find(1)
162 168 AuthSource.any_instance.stubs(:initialize_ldap_con).raises(Net::LDAP::LdapError, 'Cannot connect')
163 169
164 170 assert_equal nil, User.try_to_login('edavis', 'wrong')
165 171 end
166 172 end
167 173
168 174 context "with an unsuccessful authentication" do
169 175 should "return nil" do
170 176 assert_equal nil, User.try_to_login('edavis', 'wrong')
171 177 end
172 178 end
173 179
174 180 context "on the fly registration" do
175 181 setup do
176 182 @auth_source = AuthSourceLdap.find(1)
177 183 end
178 184
179 185 context "with a successful authentication" do
180 186 should "create a new user account if it doesn't exist" do
181 187 assert_difference('User.count') do
182 188 user = User.try_to_login('edavis', '123456')
183 189 assert !user.admin?
184 190 end
185 191 end
186 192
187 193 should "retrieve existing user" do
188 194 user = User.try_to_login('edavis', '123456')
189 195 user.admin = true
190 196 user.save!
191 197
192 198 assert_no_difference('User.count') do
193 199 user = User.try_to_login('edavis', '123456')
194 200 assert user.admin?
195 201 end
196 202 end
197 203 end
198 204 end
199 205 end
200 206
201 207 else
202 208 puts "Skipping LDAP tests."
203 209 end
204 210
205 211 def test_create_anonymous
206 212 AnonymousUser.delete_all
207 213 anon = User.anonymous
208 214 assert !anon.new_record?
209 215 assert_kind_of AnonymousUser, anon
210 216 end
211 217
212 218 should_have_one :rss_token
213 219
214 220 def test_rss_key
215 221 assert_nil @jsmith.rss_token
216 222 key = @jsmith.rss_key
217 223 assert_equal 40, key.length
218 224
219 225 @jsmith.reload
220 226 assert_equal key, @jsmith.rss_key
221 227 end
222 228
223 229
224 230 should_have_one :api_token
225 231
226 232 context "User#api_key" do
227 233 should "generate a new one if the user doesn't have one" do
228 234 user = User.generate_with_protected!(:api_token => nil)
229 235 assert_nil user.api_token
230 236
231 237 key = user.api_key
232 238 assert_equal 40, key.length
233 239 user.reload
234 240 assert_equal key, user.api_key
235 241 end
236 242
237 243 should "return the existing api token value" do
238 244 user = User.generate_with_protected!
239 245 token = Token.generate!(:action => 'api')
240 246 user.api_token = token
241 247 assert user.save
242 248
243 249 assert_equal token.value, user.api_key
244 250 end
245 251 end
246 252
247 253 context "User#find_by_api_key" do
248 254 should "return nil if no matching key is found" do
249 255 assert_nil User.find_by_api_key('zzzzzzzzz')
250 256 end
251 257
252 258 should "return nil if the key is found for an inactive user" do
253 259 user = User.generate_with_protected!(:status => User::STATUS_LOCKED)
254 260 token = Token.generate!(:action => 'api')
255 261 user.api_token = token
256 262 user.save
257 263
258 264 assert_nil User.find_by_api_key(token.value)
259 265 end
260 266
261 267 should "return the user if the key is found for an active user" do
262 268 user = User.generate_with_protected!(:status => User::STATUS_ACTIVE)
263 269 token = Token.generate!(:action => 'api')
264 270 user.api_token = token
265 271 user.save
266 272
267 273 assert_equal user, User.find_by_api_key(token.value)
268 274 end
269 275 end
270 276
271 277 def test_roles_for_project
272 278 # user with a role
273 279 roles = @jsmith.roles_for_project(Project.find(1))
274 280 assert_kind_of Role, roles.first
275 281 assert_equal "Manager", roles.first.name
276 282
277 283 # user with no role
278 284 assert_nil @dlopper.roles_for_project(Project.find(2)).detect {|role| role.member?}
279 285 end
280 286
281 287 def test_mail_notification_all
282 288 @jsmith.mail_notification = true
283 289 @jsmith.notified_project_ids = []
284 290 @jsmith.save
285 291 @jsmith.reload
286 292 assert @jsmith.projects.first.recipients.include?(@jsmith.mail)
287 293 end
288 294
289 295 def test_mail_notification_selected
290 296 @jsmith.mail_notification = false
291 297 @jsmith.notified_project_ids = [1]
292 298 @jsmith.save
293 299 @jsmith.reload
294 300 assert Project.find(1).recipients.include?(@jsmith.mail)
295 301 end
296 302
297 303 def test_mail_notification_none
298 304 @jsmith.mail_notification = false
299 305 @jsmith.notified_project_ids = []
300 306 @jsmith.save
301 307 @jsmith.reload
302 308 assert !@jsmith.projects.first.recipients.include?(@jsmith.mail)
303 309 end
304 310
305 311 def test_comments_sorting_preference
306 312 assert !@jsmith.wants_comments_in_reverse_order?
307 313 @jsmith.pref.comments_sorting = 'asc'
308 314 assert !@jsmith.wants_comments_in_reverse_order?
309 315 @jsmith.pref.comments_sorting = 'desc'
310 316 assert @jsmith.wants_comments_in_reverse_order?
311 317 end
312 318
313 319 def test_find_by_mail_should_be_case_insensitive
314 320 u = User.find_by_mail('JSmith@somenet.foo')
315 321 assert_not_nil u
316 322 assert_equal 'jsmith@somenet.foo', u.mail
317 323 end
318 324
319 325 def test_random_password
320 326 u = User.new
321 327 u.random_password
322 328 assert !u.password.blank?
323 329 assert !u.password_confirmation.blank?
324 330 end
325 331
326 332 context "#change_password_allowed?" do
327 333 should "be allowed if no auth source is set" do
328 334 user = User.generate_with_protected!
329 335 assert user.change_password_allowed?
330 336 end
331 337
332 338 should "delegate to the auth source" do
333 339 user = User.generate_with_protected!
334 340
335 341 allowed_auth_source = AuthSource.generate!
336 342 def allowed_auth_source.allow_password_changes?; true; end
337 343
338 344 denied_auth_source = AuthSource.generate!
339 345 def denied_auth_source.allow_password_changes?; false; end
340 346
341 347 assert user.change_password_allowed?
342 348
343 349 user.auth_source = allowed_auth_source
344 350 assert user.change_password_allowed?, "User not allowed to change password, though auth source does"
345 351
346 352 user.auth_source = denied_auth_source
347 353 assert !user.change_password_allowed?, "User allowed to change password, though auth source does not"
348 354 end
349 355
350 356 end
351 357
352 358 if Object.const_defined?(:OpenID)
353 359
354 360 def test_setting_identity_url
355 361 normalized_open_id_url = 'http://example.com/'
356 362 u = User.new( :identity_url => 'http://example.com/' )
357 363 assert_equal normalized_open_id_url, u.identity_url
358 364 end
359 365
360 366 def test_setting_identity_url_without_trailing_slash
361 367 normalized_open_id_url = 'http://example.com/'
362 368 u = User.new( :identity_url => 'http://example.com' )
363 369 assert_equal normalized_open_id_url, u.identity_url
364 370 end
365 371
366 372 def test_setting_identity_url_without_protocol
367 373 normalized_open_id_url = 'http://example.com/'
368 374 u = User.new( :identity_url => 'example.com' )
369 375 assert_equal normalized_open_id_url, u.identity_url
370 376 end
371 377
372 378 def test_setting_blank_identity_url
373 379 u = User.new( :identity_url => 'example.com' )
374 380 u.identity_url = ''
375 381 assert u.identity_url.blank?
376 382 end
377 383
378 384 def test_setting_invalid_identity_url
379 385 u = User.new( :identity_url => 'this is not an openid url' )
380 386 assert u.identity_url.blank?
381 387 end
382 388
383 389 else
384 390 puts "Skipping openid tests."
385 391 end
386 392
387 393 end
General Comments 0
You need to be logged in to leave comments. Login now