##// END OF EJS Templates
Fix LDAP on the fly creation. The User object doesn't have a :dn attribute....
Eric Davis -
r3371:19d4ddf2f215
parent child
Show More
@@ -1,360 +1,361
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2009 Jean-Philippe Lang
2 # Copyright (C) 2006-2009 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 require "digest/sha1"
18 require "digest/sha1"
19
19
20 class User < Principal
20 class User < Principal
21
21
22 # Account statuses
22 # Account statuses
23 STATUS_ANONYMOUS = 0
23 STATUS_ANONYMOUS = 0
24 STATUS_ACTIVE = 1
24 STATUS_ACTIVE = 1
25 STATUS_REGISTERED = 2
25 STATUS_REGISTERED = 2
26 STATUS_LOCKED = 3
26 STATUS_LOCKED = 3
27
27
28 USER_FORMATS = {
28 USER_FORMATS = {
29 :firstname_lastname => '#{firstname} #{lastname}',
29 :firstname_lastname => '#{firstname} #{lastname}',
30 :firstname => '#{firstname}',
30 :firstname => '#{firstname}',
31 :lastname_firstname => '#{lastname} #{firstname}',
31 :lastname_firstname => '#{lastname} #{firstname}',
32 :lastname_coma_firstname => '#{lastname}, #{firstname}',
32 :lastname_coma_firstname => '#{lastname}, #{firstname}',
33 :username => '#{login}'
33 :username => '#{login}'
34 }
34 }
35
35
36 has_and_belongs_to_many :groups, :after_add => Proc.new {|user, group| group.user_added(user)},
36 has_and_belongs_to_many :groups, :after_add => Proc.new {|user, group| group.user_added(user)},
37 :after_remove => Proc.new {|user, group| group.user_removed(user)}
37 :after_remove => Proc.new {|user, group| group.user_removed(user)}
38 has_many :issue_categories, :foreign_key => 'assigned_to_id', :dependent => :nullify
38 has_many :issue_categories, :foreign_key => 'assigned_to_id', :dependent => :nullify
39 has_many :changesets, :dependent => :nullify
39 has_many :changesets, :dependent => :nullify
40 has_one :preference, :dependent => :destroy, :class_name => 'UserPreference'
40 has_one :preference, :dependent => :destroy, :class_name => 'UserPreference'
41 has_one :rss_token, :dependent => :destroy, :class_name => 'Token', :conditions => "action='feeds'"
41 has_one :rss_token, :dependent => :destroy, :class_name => 'Token', :conditions => "action='feeds'"
42 has_one :api_token, :dependent => :destroy, :class_name => 'Token', :conditions => "action='api'"
42 has_one :api_token, :dependent => :destroy, :class_name => 'Token', :conditions => "action='api'"
43 belongs_to :auth_source
43 belongs_to :auth_source
44
44
45 # Active non-anonymous users scope
45 # Active non-anonymous users scope
46 named_scope :active, :conditions => "#{User.table_name}.status = #{STATUS_ACTIVE}"
46 named_scope :active, :conditions => "#{User.table_name}.status = #{STATUS_ACTIVE}"
47
47
48 acts_as_customizable
48 acts_as_customizable
49
49
50 attr_accessor :password, :password_confirmation
50 attr_accessor :password, :password_confirmation
51 attr_accessor :last_before_login_on
51 attr_accessor :last_before_login_on
52 # Prevents unauthorized assignments
52 # Prevents unauthorized assignments
53 attr_protected :login, :admin, :password, :password_confirmation, :hashed_password, :group_ids
53 attr_protected :login, :admin, :password, :password_confirmation, :hashed_password, :group_ids
54
54
55 validates_presence_of :login, :firstname, :lastname, :mail, :if => Proc.new { |user| !user.is_a?(AnonymousUser) }
55 validates_presence_of :login, :firstname, :lastname, :mail, :if => Proc.new { |user| !user.is_a?(AnonymousUser) }
56 validates_uniqueness_of :login, :if => Proc.new { |user| !user.login.blank? }
56 validates_uniqueness_of :login, :if => Proc.new { |user| !user.login.blank? }
57 validates_uniqueness_of :mail, :if => Proc.new { |user| !user.mail.blank? }, :case_sensitive => false
57 validates_uniqueness_of :mail, :if => Proc.new { |user| !user.mail.blank? }, :case_sensitive => false
58 # Login must contain lettres, numbers, underscores only
58 # Login must contain lettres, numbers, underscores only
59 validates_format_of :login, :with => /^[a-z0-9_\-@\.]*$/i
59 validates_format_of :login, :with => /^[a-z0-9_\-@\.]*$/i
60 validates_length_of :login, :maximum => 30
60 validates_length_of :login, :maximum => 30
61 validates_format_of :firstname, :lastname, :with => /^[\w\s\'\-\.]*$/i
61 validates_format_of :firstname, :lastname, :with => /^[\w\s\'\-\.]*$/i
62 validates_length_of :firstname, :lastname, :maximum => 30
62 validates_length_of :firstname, :lastname, :maximum => 30
63 validates_format_of :mail, :with => /^([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})$/i, :allow_nil => true
63 validates_format_of :mail, :with => /^([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})$/i, :allow_nil => true
64 validates_length_of :mail, :maximum => 60, :allow_nil => true
64 validates_length_of :mail, :maximum => 60, :allow_nil => true
65 validates_confirmation_of :password, :allow_nil => true
65 validates_confirmation_of :password, :allow_nil => true
66
66
67 def before_create
67 def before_create
68 self.mail_notification = false
68 self.mail_notification = false
69 true
69 true
70 end
70 end
71
71
72 def before_save
72 def before_save
73 # update hashed_password if password was set
73 # update hashed_password if password was set
74 self.hashed_password = User.hash_password(self.password) if self.password
74 self.hashed_password = User.hash_password(self.password) if self.password
75 end
75 end
76
76
77 def reload(*args)
77 def reload(*args)
78 @name = nil
78 @name = nil
79 super
79 super
80 end
80 end
81
81
82 def identity_url=(url)
82 def identity_url=(url)
83 if url.blank?
83 if url.blank?
84 write_attribute(:identity_url, '')
84 write_attribute(:identity_url, '')
85 else
85 else
86 begin
86 begin
87 write_attribute(:identity_url, OpenIdAuthentication.normalize_identifier(url))
87 write_attribute(:identity_url, OpenIdAuthentication.normalize_identifier(url))
88 rescue OpenIdAuthentication::InvalidOpenId
88 rescue OpenIdAuthentication::InvalidOpenId
89 # Invlaid url, don't save
89 # Invlaid url, don't save
90 end
90 end
91 end
91 end
92 self.read_attribute(:identity_url)
92 self.read_attribute(:identity_url)
93 end
93 end
94
94
95 # Returns the user that matches provided login and password, or nil
95 # Returns the user that matches provided login and password, or nil
96 def self.try_to_login(login, password)
96 def self.try_to_login(login, password)
97 # Make sure no one can sign in with an empty password
97 # Make sure no one can sign in with an empty password
98 return nil if password.to_s.empty?
98 return nil if password.to_s.empty?
99 user = find(:first, :conditions => ["login=?", login])
99 user = find(:first, :conditions => ["login=?", login])
100 if user
100 if user
101 # user is already in local database
101 # user is already in local database
102 return nil if !user.active?
102 return nil if !user.active?
103 if user.auth_source
103 if user.auth_source
104 # user has an external authentication method
104 # user has an external authentication method
105 return nil unless user.auth_source.authenticate(login, password)
105 return nil unless user.auth_source.authenticate(login, password)
106 else
106 else
107 # authentication with local password
107 # authentication with local password
108 return nil unless User.hash_password(password) == user.hashed_password
108 return nil unless User.hash_password(password) == user.hashed_password
109 end
109 end
110 else
110 else
111 # user is not yet registered, try to authenticate with available sources
111 # user is not yet registered, try to authenticate with available sources
112 attrs = AuthSource.authenticate(login, password)
112 attrs = AuthSource.authenticate(login, password)
113 if attrs
113 if attrs
114 user = new(*attrs)
114 attributes = *attrs
115 user = new(attributes.symbolize_keys.except(:dn))
115 user.login = login
116 user.login = login
116 user.language = Setting.default_language
117 user.language = Setting.default_language
117 if user.save
118 if user.save
118 user.reload
119 user.reload
119 logger.info("User '#{user.login}' created from the LDAP") if logger
120 logger.info("User '#{user.login}' created from the LDAP") if logger
120 end
121 end
121 end
122 end
122 end
123 end
123 user.update_attribute(:last_login_on, Time.now) if user && !user.new_record?
124 user.update_attribute(:last_login_on, Time.now) if user && !user.new_record?
124 user
125 user
125 rescue => text
126 rescue => text
126 raise text
127 raise text
127 end
128 end
128
129
129 # Returns the user who matches the given autologin +key+ or nil
130 # Returns the user who matches the given autologin +key+ or nil
130 def self.try_to_autologin(key)
131 def self.try_to_autologin(key)
131 tokens = Token.find_all_by_action_and_value('autologin', key)
132 tokens = Token.find_all_by_action_and_value('autologin', key)
132 # Make sure there's only 1 token that matches the key
133 # Make sure there's only 1 token that matches the key
133 if tokens.size == 1
134 if tokens.size == 1
134 token = tokens.first
135 token = tokens.first
135 if (token.created_on > Setting.autologin.to_i.day.ago) && token.user && token.user.active?
136 if (token.created_on > Setting.autologin.to_i.day.ago) && token.user && token.user.active?
136 token.user.update_attribute(:last_login_on, Time.now)
137 token.user.update_attribute(:last_login_on, Time.now)
137 token.user
138 token.user
138 end
139 end
139 end
140 end
140 end
141 end
141
142
142 # Return user's full name for display
143 # Return user's full name for display
143 def name(formatter = nil)
144 def name(formatter = nil)
144 if formatter
145 if formatter
145 eval('"' + (USER_FORMATS[formatter] || USER_FORMATS[:firstname_lastname]) + '"')
146 eval('"' + (USER_FORMATS[formatter] || USER_FORMATS[:firstname_lastname]) + '"')
146 else
147 else
147 @name ||= eval('"' + (USER_FORMATS[Setting.user_format] || USER_FORMATS[:firstname_lastname]) + '"')
148 @name ||= eval('"' + (USER_FORMATS[Setting.user_format] || USER_FORMATS[:firstname_lastname]) + '"')
148 end
149 end
149 end
150 end
150
151
151 def active?
152 def active?
152 self.status == STATUS_ACTIVE
153 self.status == STATUS_ACTIVE
153 end
154 end
154
155
155 def registered?
156 def registered?
156 self.status == STATUS_REGISTERED
157 self.status == STATUS_REGISTERED
157 end
158 end
158
159
159 def locked?
160 def locked?
160 self.status == STATUS_LOCKED
161 self.status == STATUS_LOCKED
161 end
162 end
162
163
163 def check_password?(clear_password)
164 def check_password?(clear_password)
164 User.hash_password(clear_password) == self.hashed_password
165 User.hash_password(clear_password) == self.hashed_password
165 end
166 end
166
167
167 # Generate and set a random password. Useful for automated user creation
168 # Generate and set a random password. Useful for automated user creation
168 # Based on Token#generate_token_value
169 # Based on Token#generate_token_value
169 #
170 #
170 def random_password
171 def random_password
171 chars = ("a".."z").to_a + ("A".."Z").to_a + ("0".."9").to_a
172 chars = ("a".."z").to_a + ("A".."Z").to_a + ("0".."9").to_a
172 password = ''
173 password = ''
173 40.times { |i| password << chars[rand(chars.size-1)] }
174 40.times { |i| password << chars[rand(chars.size-1)] }
174 self.password = password
175 self.password = password
175 self.password_confirmation = password
176 self.password_confirmation = password
176 self
177 self
177 end
178 end
178
179
179 def pref
180 def pref
180 self.preference ||= UserPreference.new(:user => self)
181 self.preference ||= UserPreference.new(:user => self)
181 end
182 end
182
183
183 def time_zone
184 def time_zone
184 @time_zone ||= (self.pref.time_zone.blank? ? nil : ActiveSupport::TimeZone[self.pref.time_zone])
185 @time_zone ||= (self.pref.time_zone.blank? ? nil : ActiveSupport::TimeZone[self.pref.time_zone])
185 end
186 end
186
187
187 def wants_comments_in_reverse_order?
188 def wants_comments_in_reverse_order?
188 self.pref[:comments_sorting] == 'desc'
189 self.pref[:comments_sorting] == 'desc'
189 end
190 end
190
191
191 # Return user's RSS key (a 40 chars long string), used to access feeds
192 # Return user's RSS key (a 40 chars long string), used to access feeds
192 def rss_key
193 def rss_key
193 token = self.rss_token || Token.create(:user => self, :action => 'feeds')
194 token = self.rss_token || Token.create(:user => self, :action => 'feeds')
194 token.value
195 token.value
195 end
196 end
196
197
197 # Return user's API key (a 40 chars long string), used to access the API
198 # Return user's API key (a 40 chars long string), used to access the API
198 def api_key
199 def api_key
199 token = self.api_token || self.create_api_token(:action => 'api')
200 token = self.api_token || self.create_api_token(:action => 'api')
200 token.value
201 token.value
201 end
202 end
202
203
203 # Return an array of project ids for which the user has explicitly turned mail notifications on
204 # Return an array of project ids for which the user has explicitly turned mail notifications on
204 def notified_projects_ids
205 def notified_projects_ids
205 @notified_projects_ids ||= memberships.select {|m| m.mail_notification?}.collect(&:project_id)
206 @notified_projects_ids ||= memberships.select {|m| m.mail_notification?}.collect(&:project_id)
206 end
207 end
207
208
208 def notified_project_ids=(ids)
209 def notified_project_ids=(ids)
209 Member.update_all("mail_notification = #{connection.quoted_false}", ['user_id = ?', id])
210 Member.update_all("mail_notification = #{connection.quoted_false}", ['user_id = ?', id])
210 Member.update_all("mail_notification = #{connection.quoted_true}", ['user_id = ? AND project_id IN (?)', id, ids]) if ids && !ids.empty?
211 Member.update_all("mail_notification = #{connection.quoted_true}", ['user_id = ? AND project_id IN (?)', id, ids]) if ids && !ids.empty?
211 @notified_projects_ids = nil
212 @notified_projects_ids = nil
212 notified_projects_ids
213 notified_projects_ids
213 end
214 end
214
215
215 def self.find_by_rss_key(key)
216 def self.find_by_rss_key(key)
216 token = Token.find_by_value(key)
217 token = Token.find_by_value(key)
217 token && token.user.active? ? token.user : nil
218 token && token.user.active? ? token.user : nil
218 end
219 end
219
220
220 def self.find_by_api_key(key)
221 def self.find_by_api_key(key)
221 token = Token.find_by_action_and_value('api', key)
222 token = Token.find_by_action_and_value('api', key)
222 token && token.user.active? ? token.user : nil
223 token && token.user.active? ? token.user : nil
223 end
224 end
224
225
225 # Makes find_by_mail case-insensitive
226 # Makes find_by_mail case-insensitive
226 def self.find_by_mail(mail)
227 def self.find_by_mail(mail)
227 find(:first, :conditions => ["LOWER(mail) = ?", mail.to_s.downcase])
228 find(:first, :conditions => ["LOWER(mail) = ?", mail.to_s.downcase])
228 end
229 end
229
230
230 def to_s
231 def to_s
231 name
232 name
232 end
233 end
233
234
234 # Returns the current day according to user's time zone
235 # Returns the current day according to user's time zone
235 def today
236 def today
236 if time_zone.nil?
237 if time_zone.nil?
237 Date.today
238 Date.today
238 else
239 else
239 Time.now.in_time_zone(time_zone).to_date
240 Time.now.in_time_zone(time_zone).to_date
240 end
241 end
241 end
242 end
242
243
243 def logged?
244 def logged?
244 true
245 true
245 end
246 end
246
247
247 def anonymous?
248 def anonymous?
248 !logged?
249 !logged?
249 end
250 end
250
251
251 # Return user's roles for project
252 # Return user's roles for project
252 def roles_for_project(project)
253 def roles_for_project(project)
253 roles = []
254 roles = []
254 # No role on archived projects
255 # No role on archived projects
255 return roles unless project && project.active?
256 return roles unless project && project.active?
256 if logged?
257 if logged?
257 # Find project membership
258 # Find project membership
258 membership = memberships.detect {|m| m.project_id == project.id}
259 membership = memberships.detect {|m| m.project_id == project.id}
259 if membership
260 if membership
260 roles = membership.roles
261 roles = membership.roles
261 else
262 else
262 @role_non_member ||= Role.non_member
263 @role_non_member ||= Role.non_member
263 roles << @role_non_member
264 roles << @role_non_member
264 end
265 end
265 else
266 else
266 @role_anonymous ||= Role.anonymous
267 @role_anonymous ||= Role.anonymous
267 roles << @role_anonymous
268 roles << @role_anonymous
268 end
269 end
269 roles
270 roles
270 end
271 end
271
272
272 # Return true if the user is a member of project
273 # Return true if the user is a member of project
273 def member_of?(project)
274 def member_of?(project)
274 !roles_for_project(project).detect {|role| role.member?}.nil?
275 !roles_for_project(project).detect {|role| role.member?}.nil?
275 end
276 end
276
277
277 # Return true if the user is allowed to do the specified action on project
278 # Return true if the user is allowed to do the specified action on project
278 # action can be:
279 # action can be:
279 # * a parameter-like Hash (eg. :controller => 'projects', :action => 'edit')
280 # * a parameter-like Hash (eg. :controller => 'projects', :action => 'edit')
280 # * a permission Symbol (eg. :edit_project)
281 # * a permission Symbol (eg. :edit_project)
281 def allowed_to?(action, project, options={})
282 def allowed_to?(action, project, options={})
282 if project
283 if project
283 # No action allowed on archived projects
284 # No action allowed on archived projects
284 return false unless project.active?
285 return false unless project.active?
285 # No action allowed on disabled modules
286 # No action allowed on disabled modules
286 return false unless project.allows_to?(action)
287 return false unless project.allows_to?(action)
287 # Admin users are authorized for anything else
288 # Admin users are authorized for anything else
288 return true if admin?
289 return true if admin?
289
290
290 roles = roles_for_project(project)
291 roles = roles_for_project(project)
291 return false unless roles
292 return false unless roles
292 roles.detect {|role| (project.is_public? || role.member?) && role.allowed_to?(action)}
293 roles.detect {|role| (project.is_public? || role.member?) && role.allowed_to?(action)}
293
294
294 elsif options[:global]
295 elsif options[:global]
295 # Admin users are always authorized
296 # Admin users are always authorized
296 return true if admin?
297 return true if admin?
297
298
298 # authorize if user has at least one role that has this permission
299 # authorize if user has at least one role that has this permission
299 roles = memberships.collect {|m| m.roles}.flatten.uniq
300 roles = memberships.collect {|m| m.roles}.flatten.uniq
300 roles.detect {|r| r.allowed_to?(action)} || (self.logged? ? Role.non_member.allowed_to?(action) : Role.anonymous.allowed_to?(action))
301 roles.detect {|r| r.allowed_to?(action)} || (self.logged? ? Role.non_member.allowed_to?(action) : Role.anonymous.allowed_to?(action))
301 else
302 else
302 false
303 false
303 end
304 end
304 end
305 end
305
306
306 def self.current=(user)
307 def self.current=(user)
307 @current_user = user
308 @current_user = user
308 end
309 end
309
310
310 def self.current
311 def self.current
311 @current_user ||= User.anonymous
312 @current_user ||= User.anonymous
312 end
313 end
313
314
314 # Returns the anonymous user. If the anonymous user does not exist, it is created. There can be only
315 # Returns the anonymous user. If the anonymous user does not exist, it is created. There can be only
315 # one anonymous user per database.
316 # one anonymous user per database.
316 def self.anonymous
317 def self.anonymous
317 anonymous_user = AnonymousUser.find(:first)
318 anonymous_user = AnonymousUser.find(:first)
318 if anonymous_user.nil?
319 if anonymous_user.nil?
319 anonymous_user = AnonymousUser.create(:lastname => 'Anonymous', :firstname => '', :mail => '', :login => '', :status => 0)
320 anonymous_user = AnonymousUser.create(:lastname => 'Anonymous', :firstname => '', :mail => '', :login => '', :status => 0)
320 raise 'Unable to create the anonymous user.' if anonymous_user.new_record?
321 raise 'Unable to create the anonymous user.' if anonymous_user.new_record?
321 end
322 end
322 anonymous_user
323 anonymous_user
323 end
324 end
324
325
325 protected
326 protected
326
327
327 def validate
328 def validate
328 # Password length validation based on setting
329 # Password length validation based on setting
329 if !password.nil? && password.size < Setting.password_min_length.to_i
330 if !password.nil? && password.size < Setting.password_min_length.to_i
330 errors.add(:password, :too_short, :count => Setting.password_min_length.to_i)
331 errors.add(:password, :too_short, :count => Setting.password_min_length.to_i)
331 end
332 end
332 end
333 end
333
334
334 private
335 private
335
336
336 # Return password digest
337 # Return password digest
337 def self.hash_password(clear_password)
338 def self.hash_password(clear_password)
338 Digest::SHA1.hexdigest(clear_password || "")
339 Digest::SHA1.hexdigest(clear_password || "")
339 end
340 end
340 end
341 end
341
342
342 class AnonymousUser < User
343 class AnonymousUser < User
343
344
344 def validate_on_create
345 def validate_on_create
345 # There should be only one AnonymousUser in the database
346 # There should be only one AnonymousUser in the database
346 errors.add_to_base 'An anonymous user already exists.' if AnonymousUser.find(:first)
347 errors.add_to_base 'An anonymous user already exists.' if AnonymousUser.find(:first)
347 end
348 end
348
349
349 def available_custom_fields
350 def available_custom_fields
350 []
351 []
351 end
352 end
352
353
353 # Overrides a few properties
354 # Overrides a few properties
354 def logged?; false end
355 def logged?; false end
355 def admin; false end
356 def admin; false end
356 def name(*args); I18n.t(:label_user_anonymous) end
357 def name(*args); I18n.t(:label_user_anonymous) end
357 def mail; nil end
358 def mail; nil end
358 def time_zone; nil end
359 def time_zone; nil end
359 def rss_key; nil end
360 def rss_key; nil end
360 end
361 end
@@ -1,279 +1,309
1 # redMine - project management software
1 # redMine - project management software
2 # Copyright (C) 2006 Jean-Philippe Lang
2 # Copyright (C) 2006 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 require File.dirname(__FILE__) + '/../test_helper'
18 require File.dirname(__FILE__) + '/../test_helper'
19
19
20 class UserTest < ActiveSupport::TestCase
20 class UserTest < ActiveSupport::TestCase
21 fixtures :users, :members, :projects, :roles, :member_roles
21 fixtures :users, :members, :projects, :roles, :member_roles
22
22
23 def setup
23 def setup
24 @admin = User.find(1)
24 @admin = User.find(1)
25 @jsmith = User.find(2)
25 @jsmith = User.find(2)
26 @dlopper = User.find(3)
26 @dlopper = User.find(3)
27 end
27 end
28
28
29 test 'object_daddy creation' do
29 test 'object_daddy creation' do
30 User.generate_with_protected!(:firstname => 'Testing connection')
30 User.generate_with_protected!(:firstname => 'Testing connection')
31 User.generate_with_protected!(:firstname => 'Testing connection')
31 User.generate_with_protected!(:firstname => 'Testing connection')
32 assert_equal 2, User.count(:all, :conditions => {:firstname => 'Testing connection'})
32 assert_equal 2, User.count(:all, :conditions => {:firstname => 'Testing connection'})
33 end
33 end
34
34
35 def test_truth
35 def test_truth
36 assert_kind_of User, @jsmith
36 assert_kind_of User, @jsmith
37 end
37 end
38
38
39 def test_create
39 def test_create
40 user = User.new(:firstname => "new", :lastname => "user", :mail => "newuser@somenet.foo")
40 user = User.new(:firstname => "new", :lastname => "user", :mail => "newuser@somenet.foo")
41
41
42 user.login = "jsmith"
42 user.login = "jsmith"
43 user.password, user.password_confirmation = "password", "password"
43 user.password, user.password_confirmation = "password", "password"
44 # login uniqueness
44 # login uniqueness
45 assert !user.save
45 assert !user.save
46 assert_equal 1, user.errors.count
46 assert_equal 1, user.errors.count
47
47
48 user.login = "newuser"
48 user.login = "newuser"
49 user.password, user.password_confirmation = "passwd", "password"
49 user.password, user.password_confirmation = "passwd", "password"
50 # password confirmation
50 # password confirmation
51 assert !user.save
51 assert !user.save
52 assert_equal 1, user.errors.count
52 assert_equal 1, user.errors.count
53
53
54 user.password, user.password_confirmation = "password", "password"
54 user.password, user.password_confirmation = "password", "password"
55 assert user.save
55 assert user.save
56 end
56 end
57
57
58 def test_mail_uniqueness_should_not_be_case_sensitive
58 def test_mail_uniqueness_should_not_be_case_sensitive
59 u = User.new(:firstname => "new", :lastname => "user", :mail => "newuser@somenet.foo")
59 u = User.new(:firstname => "new", :lastname => "user", :mail => "newuser@somenet.foo")
60 u.login = 'newuser1'
60 u.login = 'newuser1'
61 u.password, u.password_confirmation = "password", "password"
61 u.password, u.password_confirmation = "password", "password"
62 assert u.save
62 assert u.save
63
63
64 u = User.new(:firstname => "new", :lastname => "user", :mail => "newUser@Somenet.foo")
64 u = User.new(:firstname => "new", :lastname => "user", :mail => "newUser@Somenet.foo")
65 u.login = 'newuser2'
65 u.login = 'newuser2'
66 u.password, u.password_confirmation = "password", "password"
66 u.password, u.password_confirmation = "password", "password"
67 assert !u.save
67 assert !u.save
68 assert_equal I18n.translate('activerecord.errors.messages.taken'), u.errors.on(:mail)
68 assert_equal I18n.translate('activerecord.errors.messages.taken'), u.errors.on(:mail)
69 end
69 end
70
70
71 def test_update
71 def test_update
72 assert_equal "admin", @admin.login
72 assert_equal "admin", @admin.login
73 @admin.login = "john"
73 @admin.login = "john"
74 assert @admin.save, @admin.errors.full_messages.join("; ")
74 assert @admin.save, @admin.errors.full_messages.join("; ")
75 @admin.reload
75 @admin.reload
76 assert_equal "john", @admin.login
76 assert_equal "john", @admin.login
77 end
77 end
78
78
79 def test_destroy
79 def test_destroy
80 User.find(2).destroy
80 User.find(2).destroy
81 assert_nil User.find_by_id(2)
81 assert_nil User.find_by_id(2)
82 assert Member.find_all_by_user_id(2).empty?
82 assert Member.find_all_by_user_id(2).empty?
83 end
83 end
84
84
85 def test_validate
85 def test_validate
86 @admin.login = ""
86 @admin.login = ""
87 assert !@admin.save
87 assert !@admin.save
88 assert_equal 1, @admin.errors.count
88 assert_equal 1, @admin.errors.count
89 end
89 end
90
90
91 def test_password
91 def test_password
92 user = User.try_to_login("admin", "admin")
92 user = User.try_to_login("admin", "admin")
93 assert_kind_of User, user
93 assert_kind_of User, user
94 assert_equal "admin", user.login
94 assert_equal "admin", user.login
95 user.password = "hello"
95 user.password = "hello"
96 assert user.save
96 assert user.save
97
97
98 user = User.try_to_login("admin", "hello")
98 user = User.try_to_login("admin", "hello")
99 assert_kind_of User, user
99 assert_kind_of User, user
100 assert_equal "admin", user.login
100 assert_equal "admin", user.login
101 assert_equal User.hash_password("hello"), user.hashed_password
101 assert_equal User.hash_password("hello"), user.hashed_password
102 end
102 end
103
103
104 def test_name_format
104 def test_name_format
105 assert_equal 'Smith, John', @jsmith.name(:lastname_coma_firstname)
105 assert_equal 'Smith, John', @jsmith.name(:lastname_coma_firstname)
106 Setting.user_format = :firstname_lastname
106 Setting.user_format = :firstname_lastname
107 assert_equal 'John Smith', @jsmith.reload.name
107 assert_equal 'John Smith', @jsmith.reload.name
108 Setting.user_format = :username
108 Setting.user_format = :username
109 assert_equal 'jsmith', @jsmith.reload.name
109 assert_equal 'jsmith', @jsmith.reload.name
110 end
110 end
111
111
112 def test_lock
112 def test_lock
113 user = User.try_to_login("jsmith", "jsmith")
113 user = User.try_to_login("jsmith", "jsmith")
114 assert_equal @jsmith, user
114 assert_equal @jsmith, user
115
115
116 @jsmith.status = User::STATUS_LOCKED
116 @jsmith.status = User::STATUS_LOCKED
117 assert @jsmith.save
117 assert @jsmith.save
118
118
119 user = User.try_to_login("jsmith", "jsmith")
119 user = User.try_to_login("jsmith", "jsmith")
120 assert_equal nil, user
120 assert_equal nil, user
121 end
121 end
122
122
123 if ldap_configured?
124 context "#try_to_login using LDAP" do
125 context "on the fly registration" do
126 setup do
127 @auth_source = AuthSourceLdap.generate!(:name => 'localhost',
128 :host => '127.0.0.1',
129 :port => 389,
130 :base_dn => 'OU=Person,DC=redmine,DC=org',
131 :attr_login => 'uid',
132 :attr_firstname => 'givenName',
133 :attr_lastname => 'sn',
134 :attr_mail => 'mail',
135 :onthefly_register => true)
136
137 end
138
139 context "with a successful authentication" do
140 should "create a new user account" do
141 assert_difference('User.count') do
142 User.try_to_login('edavis', '123456')
143 end
144 end
145 end
146 end
147 end
148
149 else
150 puts "Skipping LDAP tests."
151 end
152
123 def test_create_anonymous
153 def test_create_anonymous
124 AnonymousUser.delete_all
154 AnonymousUser.delete_all
125 anon = User.anonymous
155 anon = User.anonymous
126 assert !anon.new_record?
156 assert !anon.new_record?
127 assert_kind_of AnonymousUser, anon
157 assert_kind_of AnonymousUser, anon
128 end
158 end
129
159
130 should_have_one :rss_token
160 should_have_one :rss_token
131
161
132 def test_rss_key
162 def test_rss_key
133 assert_nil @jsmith.rss_token
163 assert_nil @jsmith.rss_token
134 key = @jsmith.rss_key
164 key = @jsmith.rss_key
135 assert_equal 40, key.length
165 assert_equal 40, key.length
136
166
137 @jsmith.reload
167 @jsmith.reload
138 assert_equal key, @jsmith.rss_key
168 assert_equal key, @jsmith.rss_key
139 end
169 end
140
170
141
171
142 should_have_one :api_token
172 should_have_one :api_token
143
173
144 context "User#api_key" do
174 context "User#api_key" do
145 should "generate a new one if the user doesn't have one" do
175 should "generate a new one if the user doesn't have one" do
146 user = User.generate_with_protected!(:api_token => nil)
176 user = User.generate_with_protected!(:api_token => nil)
147 assert_nil user.api_token
177 assert_nil user.api_token
148
178
149 key = user.api_key
179 key = user.api_key
150 assert_equal 40, key.length
180 assert_equal 40, key.length
151 user.reload
181 user.reload
152 assert_equal key, user.api_key
182 assert_equal key, user.api_key
153 end
183 end
154
184
155 should "return the existing api token value" do
185 should "return the existing api token value" do
156 user = User.generate_with_protected!
186 user = User.generate_with_protected!
157 token = Token.generate!(:action => 'api')
187 token = Token.generate!(:action => 'api')
158 user.api_token = token
188 user.api_token = token
159 assert user.save
189 assert user.save
160
190
161 assert_equal token.value, user.api_key
191 assert_equal token.value, user.api_key
162 end
192 end
163 end
193 end
164
194
165 context "User#find_by_api_key" do
195 context "User#find_by_api_key" do
166 should "return nil if no matching key is found" do
196 should "return nil if no matching key is found" do
167 assert_nil User.find_by_api_key('zzzzzzzzz')
197 assert_nil User.find_by_api_key('zzzzzzzzz')
168 end
198 end
169
199
170 should "return nil if the key is found for an inactive user" do
200 should "return nil if the key is found for an inactive user" do
171 user = User.generate_with_protected!(:status => User::STATUS_LOCKED)
201 user = User.generate_with_protected!(:status => User::STATUS_LOCKED)
172 token = Token.generate!(:action => 'api')
202 token = Token.generate!(:action => 'api')
173 user.api_token = token
203 user.api_token = token
174 user.save
204 user.save
175
205
176 assert_nil User.find_by_api_key(token.value)
206 assert_nil User.find_by_api_key(token.value)
177 end
207 end
178
208
179 should "return the user if the key is found for an active user" do
209 should "return the user if the key is found for an active user" do
180 user = User.generate_with_protected!(:status => User::STATUS_ACTIVE)
210 user = User.generate_with_protected!(:status => User::STATUS_ACTIVE)
181 token = Token.generate!(:action => 'api')
211 token = Token.generate!(:action => 'api')
182 user.api_token = token
212 user.api_token = token
183 user.save
213 user.save
184
214
185 assert_equal user, User.find_by_api_key(token.value)
215 assert_equal user, User.find_by_api_key(token.value)
186 end
216 end
187 end
217 end
188
218
189 def test_roles_for_project
219 def test_roles_for_project
190 # user with a role
220 # user with a role
191 roles = @jsmith.roles_for_project(Project.find(1))
221 roles = @jsmith.roles_for_project(Project.find(1))
192 assert_kind_of Role, roles.first
222 assert_kind_of Role, roles.first
193 assert_equal "Manager", roles.first.name
223 assert_equal "Manager", roles.first.name
194
224
195 # user with no role
225 # user with no role
196 assert_nil @dlopper.roles_for_project(Project.find(2)).detect {|role| role.member?}
226 assert_nil @dlopper.roles_for_project(Project.find(2)).detect {|role| role.member?}
197 end
227 end
198
228
199 def test_mail_notification_all
229 def test_mail_notification_all
200 @jsmith.mail_notification = true
230 @jsmith.mail_notification = true
201 @jsmith.notified_project_ids = []
231 @jsmith.notified_project_ids = []
202 @jsmith.save
232 @jsmith.save
203 @jsmith.reload
233 @jsmith.reload
204 assert @jsmith.projects.first.recipients.include?(@jsmith.mail)
234 assert @jsmith.projects.first.recipients.include?(@jsmith.mail)
205 end
235 end
206
236
207 def test_mail_notification_selected
237 def test_mail_notification_selected
208 @jsmith.mail_notification = false
238 @jsmith.mail_notification = false
209 @jsmith.notified_project_ids = [1]
239 @jsmith.notified_project_ids = [1]
210 @jsmith.save
240 @jsmith.save
211 @jsmith.reload
241 @jsmith.reload
212 assert Project.find(1).recipients.include?(@jsmith.mail)
242 assert Project.find(1).recipients.include?(@jsmith.mail)
213 end
243 end
214
244
215 def test_mail_notification_none
245 def test_mail_notification_none
216 @jsmith.mail_notification = false
246 @jsmith.mail_notification = false
217 @jsmith.notified_project_ids = []
247 @jsmith.notified_project_ids = []
218 @jsmith.save
248 @jsmith.save
219 @jsmith.reload
249 @jsmith.reload
220 assert !@jsmith.projects.first.recipients.include?(@jsmith.mail)
250 assert !@jsmith.projects.first.recipients.include?(@jsmith.mail)
221 end
251 end
222
252
223 def test_comments_sorting_preference
253 def test_comments_sorting_preference
224 assert !@jsmith.wants_comments_in_reverse_order?
254 assert !@jsmith.wants_comments_in_reverse_order?
225 @jsmith.pref.comments_sorting = 'asc'
255 @jsmith.pref.comments_sorting = 'asc'
226 assert !@jsmith.wants_comments_in_reverse_order?
256 assert !@jsmith.wants_comments_in_reverse_order?
227 @jsmith.pref.comments_sorting = 'desc'
257 @jsmith.pref.comments_sorting = 'desc'
228 assert @jsmith.wants_comments_in_reverse_order?
258 assert @jsmith.wants_comments_in_reverse_order?
229 end
259 end
230
260
231 def test_find_by_mail_should_be_case_insensitive
261 def test_find_by_mail_should_be_case_insensitive
232 u = User.find_by_mail('JSmith@somenet.foo')
262 u = User.find_by_mail('JSmith@somenet.foo')
233 assert_not_nil u
263 assert_not_nil u
234 assert_equal 'jsmith@somenet.foo', u.mail
264 assert_equal 'jsmith@somenet.foo', u.mail
235 end
265 end
236
266
237 def test_random_password
267 def test_random_password
238 u = User.new
268 u = User.new
239 u.random_password
269 u.random_password
240 assert !u.password.blank?
270 assert !u.password.blank?
241 assert !u.password_confirmation.blank?
271 assert !u.password_confirmation.blank?
242 end
272 end
243
273
244 if Object.const_defined?(:OpenID)
274 if Object.const_defined?(:OpenID)
245
275
246 def test_setting_identity_url
276 def test_setting_identity_url
247 normalized_open_id_url = 'http://example.com/'
277 normalized_open_id_url = 'http://example.com/'
248 u = User.new( :identity_url => 'http://example.com/' )
278 u = User.new( :identity_url => 'http://example.com/' )
249 assert_equal normalized_open_id_url, u.identity_url
279 assert_equal normalized_open_id_url, u.identity_url
250 end
280 end
251
281
252 def test_setting_identity_url_without_trailing_slash
282 def test_setting_identity_url_without_trailing_slash
253 normalized_open_id_url = 'http://example.com/'
283 normalized_open_id_url = 'http://example.com/'
254 u = User.new( :identity_url => 'http://example.com' )
284 u = User.new( :identity_url => 'http://example.com' )
255 assert_equal normalized_open_id_url, u.identity_url
285 assert_equal normalized_open_id_url, u.identity_url
256 end
286 end
257
287
258 def test_setting_identity_url_without_protocol
288 def test_setting_identity_url_without_protocol
259 normalized_open_id_url = 'http://example.com/'
289 normalized_open_id_url = 'http://example.com/'
260 u = User.new( :identity_url => 'example.com' )
290 u = User.new( :identity_url => 'example.com' )
261 assert_equal normalized_open_id_url, u.identity_url
291 assert_equal normalized_open_id_url, u.identity_url
262 end
292 end
263
293
264 def test_setting_blank_identity_url
294 def test_setting_blank_identity_url
265 u = User.new( :identity_url => 'example.com' )
295 u = User.new( :identity_url => 'example.com' )
266 u.identity_url = ''
296 u.identity_url = ''
267 assert u.identity_url.blank?
297 assert u.identity_url.blank?
268 end
298 end
269
299
270 def test_setting_invalid_identity_url
300 def test_setting_invalid_identity_url
271 u = User.new( :identity_url => 'this is not an openid url' )
301 u = User.new( :identity_url => 'this is not an openid url' )
272 assert u.identity_url.blank?
302 assert u.identity_url.blank?
273 end
303 end
274
304
275 else
305 else
276 puts "Skipping openid tests."
306 puts "Skipping openid tests."
277 end
307 end
278
308
279 end
309 end
General Comments 0
You need to be logged in to leave comments. Login now