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