##// END OF EJS Templates
Do not show 2 validation errors when user mail is blank....
Jean-Philippe Lang -
r6048:f2af44b6feb9
parent child
Show More
@@ -1,606 +1,606
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2011 Jean-Philippe Lang
2 # Copyright (C) 2006-2011 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 USER_FORMATS = {
29 USER_FORMATS = {
30 :firstname_lastname => '#{firstname} #{lastname}',
30 :firstname_lastname => '#{firstname} #{lastname}',
31 :firstname => '#{firstname}',
31 :firstname => '#{firstname}',
32 :lastname_firstname => '#{lastname} #{firstname}',
32 :lastname_firstname => '#{lastname} #{firstname}',
33 :lastname_coma_firstname => '#{lastname}, #{firstname}',
33 :lastname_coma_firstname => '#{lastname}, #{firstname}',
34 :username => '#{login}'
34 :username => '#{login}'
35 }
35 }
36
36
37 MAIL_NOTIFICATION_OPTIONS = [
37 MAIL_NOTIFICATION_OPTIONS = [
38 ['all', :label_user_mail_option_all],
38 ['all', :label_user_mail_option_all],
39 ['selected', :label_user_mail_option_selected],
39 ['selected', :label_user_mail_option_selected],
40 ['only_my_events', :label_user_mail_option_only_my_events],
40 ['only_my_events', :label_user_mail_option_only_my_events],
41 ['only_assigned', :label_user_mail_option_only_assigned],
41 ['only_assigned', :label_user_mail_option_only_assigned],
42 ['only_owner', :label_user_mail_option_only_owner],
42 ['only_owner', :label_user_mail_option_only_owner],
43 ['none', :label_user_mail_option_none]
43 ['none', :label_user_mail_option_none]
44 ]
44 ]
45
45
46 has_and_belongs_to_many :groups, :after_add => Proc.new {|user, group| group.user_added(user)},
46 has_and_belongs_to_many :groups, :after_add => Proc.new {|user, group| group.user_added(user)},
47 :after_remove => Proc.new {|user, group| group.user_removed(user)}
47 :after_remove => Proc.new {|user, group| group.user_removed(user)}
48 has_many :issue_categories, :foreign_key => 'assigned_to_id', :dependent => :nullify
48 has_many :issue_categories, :foreign_key => 'assigned_to_id', :dependent => :nullify
49 has_many :changesets, :dependent => :nullify
49 has_many :changesets, :dependent => :nullify
50 has_one :preference, :dependent => :destroy, :class_name => 'UserPreference'
50 has_one :preference, :dependent => :destroy, :class_name => 'UserPreference'
51 has_one :rss_token, :class_name => 'Token', :conditions => "action='feeds'"
51 has_one :rss_token, :class_name => 'Token', :conditions => "action='feeds'"
52 has_one :api_token, :class_name => 'Token', :conditions => "action='api'"
52 has_one :api_token, :class_name => 'Token', :conditions => "action='api'"
53 belongs_to :auth_source
53 belongs_to :auth_source
54
54
55 # Active non-anonymous users scope
55 # Active non-anonymous users scope
56 named_scope :active, :conditions => "#{User.table_name}.status = #{STATUS_ACTIVE}"
56 named_scope :active, :conditions => "#{User.table_name}.status = #{STATUS_ACTIVE}"
57
57
58 acts_as_customizable
58 acts_as_customizable
59
59
60 attr_accessor :password, :password_confirmation
60 attr_accessor :password, :password_confirmation
61 attr_accessor :last_before_login_on
61 attr_accessor :last_before_login_on
62 # Prevents unauthorized assignments
62 # Prevents unauthorized assignments
63 attr_protected :login, :admin, :password, :password_confirmation, :hashed_password
63 attr_protected :login, :admin, :password, :password_confirmation, :hashed_password
64
64
65 validates_presence_of :login, :firstname, :lastname, :mail, :if => Proc.new { |user| !user.is_a?(AnonymousUser) }
65 validates_presence_of :login, :firstname, :lastname, :mail, :if => Proc.new { |user| !user.is_a?(AnonymousUser) }
66 validates_uniqueness_of :login, :if => Proc.new { |user| !user.login.blank? }, :case_sensitive => false
66 validates_uniqueness_of :login, :if => Proc.new { |user| !user.login.blank? }, :case_sensitive => false
67 validates_uniqueness_of :mail, :if => Proc.new { |user| !user.mail.blank? }, :case_sensitive => false
67 validates_uniqueness_of :mail, :if => Proc.new { |user| !user.mail.blank? }, :case_sensitive => false
68 # Login must contain lettres, numbers, underscores only
68 # Login must contain lettres, numbers, underscores only
69 validates_format_of :login, :with => /^[a-z0-9_\-@\.]*$/i
69 validates_format_of :login, :with => /^[a-z0-9_\-@\.]*$/i
70 validates_length_of :login, :maximum => 30
70 validates_length_of :login, :maximum => 30
71 validates_length_of :firstname, :lastname, :maximum => 30
71 validates_length_of :firstname, :lastname, :maximum => 30
72 validates_format_of :mail, :with => /^([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})$/i, :allow_nil => true
72 validates_format_of :mail, :with => /^([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})$/i, :allow_blank => true
73 validates_length_of :mail, :maximum => 60, :allow_nil => true
73 validates_length_of :mail, :maximum => 60, :allow_nil => true
74 validates_confirmation_of :password, :allow_nil => true
74 validates_confirmation_of :password, :allow_nil => true
75 validates_inclusion_of :mail_notification, :in => MAIL_NOTIFICATION_OPTIONS.collect(&:first), :allow_blank => true
75 validates_inclusion_of :mail_notification, :in => MAIL_NOTIFICATION_OPTIONS.collect(&:first), :allow_blank => true
76
76
77 before_destroy :remove_references_before_destroy
77 before_destroy :remove_references_before_destroy
78
78
79 named_scope :in_group, lambda {|group|
79 named_scope :in_group, lambda {|group|
80 group_id = group.is_a?(Group) ? group.id : group.to_i
80 group_id = group.is_a?(Group) ? group.id : group.to_i
81 { :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] }
81 { :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] }
82 }
82 }
83 named_scope :not_in_group, lambda {|group|
83 named_scope :not_in_group, lambda {|group|
84 group_id = group.is_a?(Group) ? group.id : group.to_i
84 group_id = group.is_a?(Group) ? group.id : group.to_i
85 { :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] }
85 { :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] }
86 }
86 }
87
87
88 def before_create
88 def before_create
89 self.mail_notification = Setting.default_notification_option if self.mail_notification.blank?
89 self.mail_notification = Setting.default_notification_option if self.mail_notification.blank?
90 true
90 true
91 end
91 end
92
92
93 def before_save
93 def before_save
94 # update hashed_password if password was set
94 # update hashed_password if password was set
95 if self.password && self.auth_source_id.blank?
95 if self.password && self.auth_source_id.blank?
96 salt_password(password)
96 salt_password(password)
97 end
97 end
98 end
98 end
99
99
100 def reload(*args)
100 def reload(*args)
101 @name = nil
101 @name = nil
102 @projects_by_role = nil
102 @projects_by_role = nil
103 super
103 super
104 end
104 end
105
105
106 def mail=(arg)
106 def mail=(arg)
107 write_attribute(:mail, arg.to_s.strip)
107 write_attribute(:mail, arg.to_s.strip)
108 end
108 end
109
109
110 def identity_url=(url)
110 def identity_url=(url)
111 if url.blank?
111 if url.blank?
112 write_attribute(:identity_url, '')
112 write_attribute(:identity_url, '')
113 else
113 else
114 begin
114 begin
115 write_attribute(:identity_url, OpenIdAuthentication.normalize_identifier(url))
115 write_attribute(:identity_url, OpenIdAuthentication.normalize_identifier(url))
116 rescue OpenIdAuthentication::InvalidOpenId
116 rescue OpenIdAuthentication::InvalidOpenId
117 # Invlaid url, don't save
117 # Invlaid url, don't save
118 end
118 end
119 end
119 end
120 self.read_attribute(:identity_url)
120 self.read_attribute(:identity_url)
121 end
121 end
122
122
123 # Returns the user that matches provided login and password, or nil
123 # Returns the user that matches provided login and password, or nil
124 def self.try_to_login(login, password)
124 def self.try_to_login(login, password)
125 # Make sure no one can sign in with an empty password
125 # Make sure no one can sign in with an empty password
126 return nil if password.to_s.empty?
126 return nil if password.to_s.empty?
127 user = find_by_login(login)
127 user = find_by_login(login)
128 if user
128 if user
129 # user is already in local database
129 # user is already in local database
130 return nil if !user.active?
130 return nil if !user.active?
131 if user.auth_source
131 if user.auth_source
132 # user has an external authentication method
132 # user has an external authentication method
133 return nil unless user.auth_source.authenticate(login, password)
133 return nil unless user.auth_source.authenticate(login, password)
134 else
134 else
135 # authentication with local password
135 # authentication with local password
136 return nil unless user.check_password?(password)
136 return nil unless user.check_password?(password)
137 end
137 end
138 else
138 else
139 # user is not yet registered, try to authenticate with available sources
139 # user is not yet registered, try to authenticate with available sources
140 attrs = AuthSource.authenticate(login, password)
140 attrs = AuthSource.authenticate(login, password)
141 if attrs
141 if attrs
142 user = new(attrs)
142 user = new(attrs)
143 user.login = login
143 user.login = login
144 user.language = Setting.default_language
144 user.language = Setting.default_language
145 if user.save
145 if user.save
146 user.reload
146 user.reload
147 logger.info("User '#{user.login}' created from external auth source: #{user.auth_source.type} - #{user.auth_source.name}") if logger && user.auth_source
147 logger.info("User '#{user.login}' created from external auth source: #{user.auth_source.type} - #{user.auth_source.name}") if logger && user.auth_source
148 end
148 end
149 end
149 end
150 end
150 end
151 user.update_attribute(:last_login_on, Time.now) if user && !user.new_record?
151 user.update_attribute(:last_login_on, Time.now) if user && !user.new_record?
152 user
152 user
153 rescue => text
153 rescue => text
154 raise text
154 raise text
155 end
155 end
156
156
157 # Returns the user who matches the given autologin +key+ or nil
157 # Returns the user who matches the given autologin +key+ or nil
158 def self.try_to_autologin(key)
158 def self.try_to_autologin(key)
159 tokens = Token.find_all_by_action_and_value('autologin', key)
159 tokens = Token.find_all_by_action_and_value('autologin', key)
160 # Make sure there's only 1 token that matches the key
160 # Make sure there's only 1 token that matches the key
161 if tokens.size == 1
161 if tokens.size == 1
162 token = tokens.first
162 token = tokens.first
163 if (token.created_on > Setting.autologin.to_i.day.ago) && token.user && token.user.active?
163 if (token.created_on > Setting.autologin.to_i.day.ago) && token.user && token.user.active?
164 token.user.update_attribute(:last_login_on, Time.now)
164 token.user.update_attribute(:last_login_on, Time.now)
165 token.user
165 token.user
166 end
166 end
167 end
167 end
168 end
168 end
169
169
170 # Return user's full name for display
170 # Return user's full name for display
171 def name(formatter = nil)
171 def name(formatter = nil)
172 if formatter
172 if formatter
173 eval('"' + (USER_FORMATS[formatter] || USER_FORMATS[:firstname_lastname]) + '"')
173 eval('"' + (USER_FORMATS[formatter] || USER_FORMATS[:firstname_lastname]) + '"')
174 else
174 else
175 @name ||= eval('"' + (USER_FORMATS[Setting.user_format] || USER_FORMATS[:firstname_lastname]) + '"')
175 @name ||= eval('"' + (USER_FORMATS[Setting.user_format] || USER_FORMATS[:firstname_lastname]) + '"')
176 end
176 end
177 end
177 end
178
178
179 def active?
179 def active?
180 self.status == STATUS_ACTIVE
180 self.status == STATUS_ACTIVE
181 end
181 end
182
182
183 def registered?
183 def registered?
184 self.status == STATUS_REGISTERED
184 self.status == STATUS_REGISTERED
185 end
185 end
186
186
187 def locked?
187 def locked?
188 self.status == STATUS_LOCKED
188 self.status == STATUS_LOCKED
189 end
189 end
190
190
191 def activate
191 def activate
192 self.status = STATUS_ACTIVE
192 self.status = STATUS_ACTIVE
193 end
193 end
194
194
195 def register
195 def register
196 self.status = STATUS_REGISTERED
196 self.status = STATUS_REGISTERED
197 end
197 end
198
198
199 def lock
199 def lock
200 self.status = STATUS_LOCKED
200 self.status = STATUS_LOCKED
201 end
201 end
202
202
203 def activate!
203 def activate!
204 update_attribute(:status, STATUS_ACTIVE)
204 update_attribute(:status, STATUS_ACTIVE)
205 end
205 end
206
206
207 def register!
207 def register!
208 update_attribute(:status, STATUS_REGISTERED)
208 update_attribute(:status, STATUS_REGISTERED)
209 end
209 end
210
210
211 def lock!
211 def lock!
212 update_attribute(:status, STATUS_LOCKED)
212 update_attribute(:status, STATUS_LOCKED)
213 end
213 end
214
214
215 # Returns true if +clear_password+ is the correct user's password, otherwise false
215 # Returns true if +clear_password+ is the correct user's password, otherwise false
216 def check_password?(clear_password)
216 def check_password?(clear_password)
217 if auth_source_id.present?
217 if auth_source_id.present?
218 auth_source.authenticate(self.login, clear_password)
218 auth_source.authenticate(self.login, clear_password)
219 else
219 else
220 User.hash_password("#{salt}#{User.hash_password clear_password}") == hashed_password
220 User.hash_password("#{salt}#{User.hash_password clear_password}") == hashed_password
221 end
221 end
222 end
222 end
223
223
224 # Generates a random salt and computes hashed_password for +clear_password+
224 # Generates a random salt and computes hashed_password for +clear_password+
225 # The hashed password is stored in the following form: SHA1(salt + SHA1(password))
225 # The hashed password is stored in the following form: SHA1(salt + SHA1(password))
226 def salt_password(clear_password)
226 def salt_password(clear_password)
227 self.salt = User.generate_salt
227 self.salt = User.generate_salt
228 self.hashed_password = User.hash_password("#{salt}#{User.hash_password clear_password}")
228 self.hashed_password = User.hash_password("#{salt}#{User.hash_password clear_password}")
229 end
229 end
230
230
231 # Does the backend storage allow this user to change their password?
231 # Does the backend storage allow this user to change their password?
232 def change_password_allowed?
232 def change_password_allowed?
233 return true if auth_source_id.blank?
233 return true if auth_source_id.blank?
234 return auth_source.allow_password_changes?
234 return auth_source.allow_password_changes?
235 end
235 end
236
236
237 # Generate and set a random password. Useful for automated user creation
237 # Generate and set a random password. Useful for automated user creation
238 # Based on Token#generate_token_value
238 # Based on Token#generate_token_value
239 #
239 #
240 def random_password
240 def random_password
241 chars = ("a".."z").to_a + ("A".."Z").to_a + ("0".."9").to_a
241 chars = ("a".."z").to_a + ("A".."Z").to_a + ("0".."9").to_a
242 password = ''
242 password = ''
243 40.times { |i| password << chars[rand(chars.size-1)] }
243 40.times { |i| password << chars[rand(chars.size-1)] }
244 self.password = password
244 self.password = password
245 self.password_confirmation = password
245 self.password_confirmation = password
246 self
246 self
247 end
247 end
248
248
249 def pref
249 def pref
250 self.preference ||= UserPreference.new(:user => self)
250 self.preference ||= UserPreference.new(:user => self)
251 end
251 end
252
252
253 def time_zone
253 def time_zone
254 @time_zone ||= (self.pref.time_zone.blank? ? nil : ActiveSupport::TimeZone[self.pref.time_zone])
254 @time_zone ||= (self.pref.time_zone.blank? ? nil : ActiveSupport::TimeZone[self.pref.time_zone])
255 end
255 end
256
256
257 def wants_comments_in_reverse_order?
257 def wants_comments_in_reverse_order?
258 self.pref[:comments_sorting] == 'desc'
258 self.pref[:comments_sorting] == 'desc'
259 end
259 end
260
260
261 # Return user's RSS key (a 40 chars long string), used to access feeds
261 # Return user's RSS key (a 40 chars long string), used to access feeds
262 def rss_key
262 def rss_key
263 token = self.rss_token || Token.create(:user => self, :action => 'feeds')
263 token = self.rss_token || Token.create(:user => self, :action => 'feeds')
264 token.value
264 token.value
265 end
265 end
266
266
267 # Return user's API key (a 40 chars long string), used to access the API
267 # Return user's API key (a 40 chars long string), used to access the API
268 def api_key
268 def api_key
269 token = self.api_token || self.create_api_token(:action => 'api')
269 token = self.api_token || self.create_api_token(:action => 'api')
270 token.value
270 token.value
271 end
271 end
272
272
273 # Return an array of project ids for which the user has explicitly turned mail notifications on
273 # Return an array of project ids for which the user has explicitly turned mail notifications on
274 def notified_projects_ids
274 def notified_projects_ids
275 @notified_projects_ids ||= memberships.select {|m| m.mail_notification?}.collect(&:project_id)
275 @notified_projects_ids ||= memberships.select {|m| m.mail_notification?}.collect(&:project_id)
276 end
276 end
277
277
278 def notified_project_ids=(ids)
278 def notified_project_ids=(ids)
279 Member.update_all("mail_notification = #{connection.quoted_false}", ['user_id = ?', id])
279 Member.update_all("mail_notification = #{connection.quoted_false}", ['user_id = ?', id])
280 Member.update_all("mail_notification = #{connection.quoted_true}", ['user_id = ? AND project_id IN (?)', id, ids]) if ids && !ids.empty?
280 Member.update_all("mail_notification = #{connection.quoted_true}", ['user_id = ? AND project_id IN (?)', id, ids]) if ids && !ids.empty?
281 @notified_projects_ids = nil
281 @notified_projects_ids = nil
282 notified_projects_ids
282 notified_projects_ids
283 end
283 end
284
284
285 def valid_notification_options
285 def valid_notification_options
286 self.class.valid_notification_options(self)
286 self.class.valid_notification_options(self)
287 end
287 end
288
288
289 # Only users that belong to more than 1 project can select projects for which they are notified
289 # Only users that belong to more than 1 project can select projects for which they are notified
290 def self.valid_notification_options(user=nil)
290 def self.valid_notification_options(user=nil)
291 # Note that @user.membership.size would fail since AR ignores
291 # Note that @user.membership.size would fail since AR ignores
292 # :include association option when doing a count
292 # :include association option when doing a count
293 if user.nil? || user.memberships.length < 1
293 if user.nil? || user.memberships.length < 1
294 MAIL_NOTIFICATION_OPTIONS.reject {|option| option.first == 'selected'}
294 MAIL_NOTIFICATION_OPTIONS.reject {|option| option.first == 'selected'}
295 else
295 else
296 MAIL_NOTIFICATION_OPTIONS
296 MAIL_NOTIFICATION_OPTIONS
297 end
297 end
298 end
298 end
299
299
300 # Find a user account by matching the exact login and then a case-insensitive
300 # Find a user account by matching the exact login and then a case-insensitive
301 # version. Exact matches will be given priority.
301 # version. Exact matches will be given priority.
302 def self.find_by_login(login)
302 def self.find_by_login(login)
303 # force string comparison to be case sensitive on MySQL
303 # force string comparison to be case sensitive on MySQL
304 type_cast = (ActiveRecord::Base.connection.adapter_name == 'MySQL') ? 'BINARY' : ''
304 type_cast = (ActiveRecord::Base.connection.adapter_name == 'MySQL') ? 'BINARY' : ''
305
305
306 # First look for an exact match
306 # First look for an exact match
307 user = first(:conditions => ["#{type_cast} login = ?", login])
307 user = first(:conditions => ["#{type_cast} login = ?", login])
308 # Fail over to case-insensitive if none was found
308 # Fail over to case-insensitive if none was found
309 user ||= first(:conditions => ["#{type_cast} LOWER(login) = ?", login.to_s.downcase])
309 user ||= first(:conditions => ["#{type_cast} LOWER(login) = ?", login.to_s.downcase])
310 end
310 end
311
311
312 def self.find_by_rss_key(key)
312 def self.find_by_rss_key(key)
313 token = Token.find_by_value(key)
313 token = Token.find_by_value(key)
314 token && token.user.active? ? token.user : nil
314 token && token.user.active? ? token.user : nil
315 end
315 end
316
316
317 def self.find_by_api_key(key)
317 def self.find_by_api_key(key)
318 token = Token.find_by_action_and_value('api', key)
318 token = Token.find_by_action_and_value('api', key)
319 token && token.user.active? ? token.user : nil
319 token && token.user.active? ? token.user : nil
320 end
320 end
321
321
322 # Makes find_by_mail case-insensitive
322 # Makes find_by_mail case-insensitive
323 def self.find_by_mail(mail)
323 def self.find_by_mail(mail)
324 find(:first, :conditions => ["LOWER(mail) = ?", mail.to_s.downcase])
324 find(:first, :conditions => ["LOWER(mail) = ?", mail.to_s.downcase])
325 end
325 end
326
326
327 def to_s
327 def to_s
328 name
328 name
329 end
329 end
330
330
331 # Returns the current day according to user's time zone
331 # Returns the current day according to user's time zone
332 def today
332 def today
333 if time_zone.nil?
333 if time_zone.nil?
334 Date.today
334 Date.today
335 else
335 else
336 Time.now.in_time_zone(time_zone).to_date
336 Time.now.in_time_zone(time_zone).to_date
337 end
337 end
338 end
338 end
339
339
340 def logged?
340 def logged?
341 true
341 true
342 end
342 end
343
343
344 def anonymous?
344 def anonymous?
345 !logged?
345 !logged?
346 end
346 end
347
347
348 # Return user's roles for project
348 # Return user's roles for project
349 def roles_for_project(project)
349 def roles_for_project(project)
350 roles = []
350 roles = []
351 # No role on archived projects
351 # No role on archived projects
352 return roles unless project && project.active?
352 return roles unless project && project.active?
353 if logged?
353 if logged?
354 # Find project membership
354 # Find project membership
355 membership = memberships.detect {|m| m.project_id == project.id}
355 membership = memberships.detect {|m| m.project_id == project.id}
356 if membership
356 if membership
357 roles = membership.roles
357 roles = membership.roles
358 else
358 else
359 @role_non_member ||= Role.non_member
359 @role_non_member ||= Role.non_member
360 roles << @role_non_member
360 roles << @role_non_member
361 end
361 end
362 else
362 else
363 @role_anonymous ||= Role.anonymous
363 @role_anonymous ||= Role.anonymous
364 roles << @role_anonymous
364 roles << @role_anonymous
365 end
365 end
366 roles
366 roles
367 end
367 end
368
368
369 # Return true if the user is a member of project
369 # Return true if the user is a member of project
370 def member_of?(project)
370 def member_of?(project)
371 !roles_for_project(project).detect {|role| role.member?}.nil?
371 !roles_for_project(project).detect {|role| role.member?}.nil?
372 end
372 end
373
373
374 # Returns a hash of user's projects grouped by roles
374 # Returns a hash of user's projects grouped by roles
375 def projects_by_role
375 def projects_by_role
376 return @projects_by_role if @projects_by_role
376 return @projects_by_role if @projects_by_role
377
377
378 @projects_by_role = Hash.new {|h,k| h[k]=[]}
378 @projects_by_role = Hash.new {|h,k| h[k]=[]}
379 memberships.each do |membership|
379 memberships.each do |membership|
380 membership.roles.each do |role|
380 membership.roles.each do |role|
381 @projects_by_role[role] << membership.project if membership.project
381 @projects_by_role[role] << membership.project if membership.project
382 end
382 end
383 end
383 end
384 @projects_by_role.each do |role, projects|
384 @projects_by_role.each do |role, projects|
385 projects.uniq!
385 projects.uniq!
386 end
386 end
387
387
388 @projects_by_role
388 @projects_by_role
389 end
389 end
390
390
391 # Return true if the user is allowed to do the specified action on a specific context
391 # Return true if the user is allowed to do the specified action on a specific context
392 # Action can be:
392 # Action can be:
393 # * a parameter-like Hash (eg. :controller => 'projects', :action => 'edit')
393 # * a parameter-like Hash (eg. :controller => 'projects', :action => 'edit')
394 # * a permission Symbol (eg. :edit_project)
394 # * a permission Symbol (eg. :edit_project)
395 # Context can be:
395 # Context can be:
396 # * a project : returns true if user is allowed to do the specified action on this project
396 # * a project : returns true if user is allowed to do the specified action on this project
397 # * an array of projects : returns true if user is allowed on every project
397 # * an array of projects : returns true if user is allowed on every project
398 # * nil with options[:global] set : check if user has at least one role allowed for this action,
398 # * nil with options[:global] set : check if user has at least one role allowed for this action,
399 # or falls back to Non Member / Anonymous permissions depending if the user is logged
399 # or falls back to Non Member / Anonymous permissions depending if the user is logged
400 def allowed_to?(action, context, options={}, &block)
400 def allowed_to?(action, context, options={}, &block)
401 if context && context.is_a?(Project)
401 if context && context.is_a?(Project)
402 # No action allowed on archived projects
402 # No action allowed on archived projects
403 return false unless context.active?
403 return false unless context.active?
404 # No action allowed on disabled modules
404 # No action allowed on disabled modules
405 return false unless context.allows_to?(action)
405 return false unless context.allows_to?(action)
406 # Admin users are authorized for anything else
406 # Admin users are authorized for anything else
407 return true if admin?
407 return true if admin?
408
408
409 roles = roles_for_project(context)
409 roles = roles_for_project(context)
410 return false unless roles
410 return false unless roles
411 roles.detect {|role|
411 roles.detect {|role|
412 (context.is_public? || role.member?) &&
412 (context.is_public? || role.member?) &&
413 role.allowed_to?(action) &&
413 role.allowed_to?(action) &&
414 (block_given? ? yield(role, self) : true)
414 (block_given? ? yield(role, self) : true)
415 }
415 }
416 elsif context && context.is_a?(Array)
416 elsif context && context.is_a?(Array)
417 # Authorize if user is authorized on every element of the array
417 # Authorize if user is authorized on every element of the array
418 context.map do |project|
418 context.map do |project|
419 allowed_to?(action, project, options, &block)
419 allowed_to?(action, project, options, &block)
420 end.inject do |memo,allowed|
420 end.inject do |memo,allowed|
421 memo && allowed
421 memo && allowed
422 end
422 end
423 elsif options[:global]
423 elsif options[:global]
424 # Admin users are always authorized
424 # Admin users are always authorized
425 return true if admin?
425 return true if admin?
426
426
427 # authorize if user has at least one role that has this permission
427 # authorize if user has at least one role that has this permission
428 roles = memberships.collect {|m| m.roles}.flatten.uniq
428 roles = memberships.collect {|m| m.roles}.flatten.uniq
429 roles << (self.logged? ? Role.non_member : Role.anonymous)
429 roles << (self.logged? ? Role.non_member : Role.anonymous)
430 roles.detect {|role|
430 roles.detect {|role|
431 role.allowed_to?(action) &&
431 role.allowed_to?(action) &&
432 (block_given? ? yield(role, self) : true)
432 (block_given? ? yield(role, self) : true)
433 }
433 }
434 else
434 else
435 false
435 false
436 end
436 end
437 end
437 end
438
438
439 # Is the user allowed to do the specified action on any project?
439 # Is the user allowed to do the specified action on any project?
440 # See allowed_to? for the actions and valid options.
440 # See allowed_to? for the actions and valid options.
441 def allowed_to_globally?(action, options, &block)
441 def allowed_to_globally?(action, options, &block)
442 allowed_to?(action, nil, options.reverse_merge(:global => true), &block)
442 allowed_to?(action, nil, options.reverse_merge(:global => true), &block)
443 end
443 end
444
444
445 safe_attributes 'login',
445 safe_attributes 'login',
446 'firstname',
446 'firstname',
447 'lastname',
447 'lastname',
448 'mail',
448 'mail',
449 'mail_notification',
449 'mail_notification',
450 'language',
450 'language',
451 'custom_field_values',
451 'custom_field_values',
452 'custom_fields',
452 'custom_fields',
453 'identity_url'
453 'identity_url'
454
454
455 safe_attributes 'status',
455 safe_attributes 'status',
456 'auth_source_id',
456 'auth_source_id',
457 :if => lambda {|user, current_user| current_user.admin?}
457 :if => lambda {|user, current_user| current_user.admin?}
458
458
459 safe_attributes 'group_ids',
459 safe_attributes 'group_ids',
460 :if => lambda {|user, current_user| current_user.admin? && !user.new_record?}
460 :if => lambda {|user, current_user| current_user.admin? && !user.new_record?}
461
461
462 # Utility method to help check if a user should be notified about an
462 # Utility method to help check if a user should be notified about an
463 # event.
463 # event.
464 #
464 #
465 # TODO: only supports Issue events currently
465 # TODO: only supports Issue events currently
466 def notify_about?(object)
466 def notify_about?(object)
467 case mail_notification
467 case mail_notification
468 when 'all'
468 when 'all'
469 true
469 true
470 when 'selected'
470 when 'selected'
471 # user receives notifications for created/assigned issues on unselected projects
471 # user receives notifications for created/assigned issues on unselected projects
472 if object.is_a?(Issue) && (object.author == self || object.assigned_to == self)
472 if object.is_a?(Issue) && (object.author == self || object.assigned_to == self)
473 true
473 true
474 else
474 else
475 false
475 false
476 end
476 end
477 when 'none'
477 when 'none'
478 false
478 false
479 when 'only_my_events'
479 when 'only_my_events'
480 if object.is_a?(Issue) && (object.author == self || object.assigned_to == self)
480 if object.is_a?(Issue) && (object.author == self || object.assigned_to == self)
481 true
481 true
482 else
482 else
483 false
483 false
484 end
484 end
485 when 'only_assigned'
485 when 'only_assigned'
486 if object.is_a?(Issue) && object.assigned_to == self
486 if object.is_a?(Issue) && object.assigned_to == self
487 true
487 true
488 else
488 else
489 false
489 false
490 end
490 end
491 when 'only_owner'
491 when 'only_owner'
492 if object.is_a?(Issue) && object.author == self
492 if object.is_a?(Issue) && object.author == self
493 true
493 true
494 else
494 else
495 false
495 false
496 end
496 end
497 else
497 else
498 false
498 false
499 end
499 end
500 end
500 end
501
501
502 def self.current=(user)
502 def self.current=(user)
503 @current_user = user
503 @current_user = user
504 end
504 end
505
505
506 def self.current
506 def self.current
507 @current_user ||= User.anonymous
507 @current_user ||= User.anonymous
508 end
508 end
509
509
510 # Returns the anonymous user. If the anonymous user does not exist, it is created. There can be only
510 # Returns the anonymous user. If the anonymous user does not exist, it is created. There can be only
511 # one anonymous user per database.
511 # one anonymous user per database.
512 def self.anonymous
512 def self.anonymous
513 anonymous_user = AnonymousUser.find(:first)
513 anonymous_user = AnonymousUser.find(:first)
514 if anonymous_user.nil?
514 if anonymous_user.nil?
515 anonymous_user = AnonymousUser.create(:lastname => 'Anonymous', :firstname => '', :mail => '', :login => '', :status => 0)
515 anonymous_user = AnonymousUser.create(:lastname => 'Anonymous', :firstname => '', :mail => '', :login => '', :status => 0)
516 raise 'Unable to create the anonymous user.' if anonymous_user.new_record?
516 raise 'Unable to create the anonymous user.' if anonymous_user.new_record?
517 end
517 end
518 anonymous_user
518 anonymous_user
519 end
519 end
520
520
521 # Salts all existing unsalted passwords
521 # Salts all existing unsalted passwords
522 # It changes password storage scheme from SHA1(password) to SHA1(salt + SHA1(password))
522 # It changes password storage scheme from SHA1(password) to SHA1(salt + SHA1(password))
523 # This method is used in the SaltPasswords migration and is to be kept as is
523 # This method is used in the SaltPasswords migration and is to be kept as is
524 def self.salt_unsalted_passwords!
524 def self.salt_unsalted_passwords!
525 transaction do
525 transaction do
526 User.find_each(:conditions => "salt IS NULL OR salt = ''") do |user|
526 User.find_each(:conditions => "salt IS NULL OR salt = ''") do |user|
527 next if user.hashed_password.blank?
527 next if user.hashed_password.blank?
528 salt = User.generate_salt
528 salt = User.generate_salt
529 hashed_password = User.hash_password("#{salt}#{user.hashed_password}")
529 hashed_password = User.hash_password("#{salt}#{user.hashed_password}")
530 User.update_all("salt = '#{salt}', hashed_password = '#{hashed_password}'", ["id = ?", user.id] )
530 User.update_all("salt = '#{salt}', hashed_password = '#{hashed_password}'", ["id = ?", user.id] )
531 end
531 end
532 end
532 end
533 end
533 end
534
534
535 protected
535 protected
536
536
537 def validate
537 def validate
538 # Password length validation based on setting
538 # Password length validation based on setting
539 if !password.nil? && password.size < Setting.password_min_length.to_i
539 if !password.nil? && password.size < Setting.password_min_length.to_i
540 errors.add(:password, :too_short, :count => Setting.password_min_length.to_i)
540 errors.add(:password, :too_short, :count => Setting.password_min_length.to_i)
541 end
541 end
542 end
542 end
543
543
544 private
544 private
545
545
546 # Removes references that are not handled by associations
546 # Removes references that are not handled by associations
547 # Things that are not deleted are reassociated with the anonymous user
547 # Things that are not deleted are reassociated with the anonymous user
548 def remove_references_before_destroy
548 def remove_references_before_destroy
549 return if self.id.nil?
549 return if self.id.nil?
550
550
551 substitute = User.anonymous
551 substitute = User.anonymous
552 Attachment.update_all ['author_id = ?', substitute.id], ['author_id = ?', id]
552 Attachment.update_all ['author_id = ?', substitute.id], ['author_id = ?', id]
553 Comment.update_all ['author_id = ?', substitute.id], ['author_id = ?', id]
553 Comment.update_all ['author_id = ?', substitute.id], ['author_id = ?', id]
554 Issue.update_all ['author_id = ?', substitute.id], ['author_id = ?', id]
554 Issue.update_all ['author_id = ?', substitute.id], ['author_id = ?', id]
555 Issue.update_all 'assigned_to_id = NULL', ['assigned_to_id = ?', id]
555 Issue.update_all 'assigned_to_id = NULL', ['assigned_to_id = ?', id]
556 Journal.update_all ['user_id = ?', substitute.id], ['user_id = ?', id]
556 Journal.update_all ['user_id = ?', substitute.id], ['user_id = ?', id]
557 JournalDetail.update_all ['old_value = ?', substitute.id.to_s], ["property = 'attr' AND prop_key = 'assigned_to_id' AND old_value = ?", id.to_s]
557 JournalDetail.update_all ['old_value = ?', substitute.id.to_s], ["property = 'attr' AND prop_key = 'assigned_to_id' AND old_value = ?", id.to_s]
558 JournalDetail.update_all ['value = ?', substitute.id.to_s], ["property = 'attr' AND prop_key = 'assigned_to_id' AND value = ?", id.to_s]
558 JournalDetail.update_all ['value = ?', substitute.id.to_s], ["property = 'attr' AND prop_key = 'assigned_to_id' AND value = ?", id.to_s]
559 Message.update_all ['author_id = ?', substitute.id], ['author_id = ?', id]
559 Message.update_all ['author_id = ?', substitute.id], ['author_id = ?', id]
560 News.update_all ['author_id = ?', substitute.id], ['author_id = ?', id]
560 News.update_all ['author_id = ?', substitute.id], ['author_id = ?', id]
561 # Remove private queries and keep public ones
561 # Remove private queries and keep public ones
562 Query.delete_all ['user_id = ? AND is_public = ?', id, false]
562 Query.delete_all ['user_id = ? AND is_public = ?', id, false]
563 Query.update_all ['user_id = ?', substitute.id], ['user_id = ?', id]
563 Query.update_all ['user_id = ?', substitute.id], ['user_id = ?', id]
564 TimeEntry.update_all ['user_id = ?', substitute.id], ['user_id = ?', id]
564 TimeEntry.update_all ['user_id = ?', substitute.id], ['user_id = ?', id]
565 Token.delete_all ['user_id = ?', id]
565 Token.delete_all ['user_id = ?', id]
566 Watcher.delete_all ['user_id = ?', id]
566 Watcher.delete_all ['user_id = ?', id]
567 WikiContent.update_all ['author_id = ?', substitute.id], ['author_id = ?', id]
567 WikiContent.update_all ['author_id = ?', substitute.id], ['author_id = ?', id]
568 WikiContent::Version.update_all ['author_id = ?', substitute.id], ['author_id = ?', id]
568 WikiContent::Version.update_all ['author_id = ?', substitute.id], ['author_id = ?', id]
569 end
569 end
570
570
571 # Return password digest
571 # Return password digest
572 def self.hash_password(clear_password)
572 def self.hash_password(clear_password)
573 Digest::SHA1.hexdigest(clear_password || "")
573 Digest::SHA1.hexdigest(clear_password || "")
574 end
574 end
575
575
576 # Returns a 128bits random salt as a hex string (32 chars long)
576 # Returns a 128bits random salt as a hex string (32 chars long)
577 def self.generate_salt
577 def self.generate_salt
578 ActiveSupport::SecureRandom.hex(16)
578 ActiveSupport::SecureRandom.hex(16)
579 end
579 end
580
580
581 end
581 end
582
582
583 class AnonymousUser < User
583 class AnonymousUser < User
584
584
585 def validate_on_create
585 def validate_on_create
586 # There should be only one AnonymousUser in the database
586 # There should be only one AnonymousUser in the database
587 errors.add_to_base 'An anonymous user already exists.' if AnonymousUser.find(:first)
587 errors.add_to_base 'An anonymous user already exists.' if AnonymousUser.find(:first)
588 end
588 end
589
589
590 def available_custom_fields
590 def available_custom_fields
591 []
591 []
592 end
592 end
593
593
594 # Overrides a few properties
594 # Overrides a few properties
595 def logged?; false end
595 def logged?; false end
596 def admin; false end
596 def admin; false end
597 def name(*args); I18n.t(:label_user_anonymous) end
597 def name(*args); I18n.t(:label_user_anonymous) end
598 def mail; nil end
598 def mail; nil end
599 def time_zone; nil end
599 def time_zone; nil end
600 def rss_key; nil end
600 def rss_key; nil end
601
601
602 # Anonymous user can not be destroyed
602 # Anonymous user can not be destroyed
603 def destroy
603 def destroy
604 false
604 false
605 end
605 end
606 end
606 end
@@ -1,815 +1,822
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2011 Jean-Philippe Lang
2 # Copyright (C) 2006-2011 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
22
23 def setup
23 def setup
24 @admin = User.find(1)
24 @admin = User.find(1)
25 @jsmith = User.find(2)
25 @jsmith = User.find(2)
26 @dlopper = User.find(3)
26 @dlopper = User.find(3)
27 end
27 end
28
28
29 test 'object_daddy creation' do
29 test 'object_daddy creation' do
30 User.generate_with_protected!(:firstname => 'Testing connection')
30 User.generate_with_protected!(:firstname => 'Testing connection')
31 User.generate_with_protected!(:firstname => 'Testing connection')
31 User.generate_with_protected!(:firstname => 'Testing connection')
32 assert_equal 2, User.count(:all, :conditions => {:firstname => 'Testing connection'})
32 assert_equal 2, User.count(:all, :conditions => {:firstname => 'Testing connection'})
33 end
33 end
34
34
35 def test_truth
35 def test_truth
36 assert_kind_of User, @jsmith
36 assert_kind_of User, @jsmith
37 end
37 end
38
38
39 def test_mail_should_be_stripped
39 def test_mail_should_be_stripped
40 u = User.new
40 u = User.new
41 u.mail = " foo@bar.com "
41 u.mail = " foo@bar.com "
42 assert_equal "foo@bar.com", u.mail
42 assert_equal "foo@bar.com", u.mail
43 end
43 end
44
45 def test_mail_validation
46 u = User.new
47 u.mail = ''
48 assert !u.valid?
49 assert_equal I18n.translate('activerecord.errors.messages.blank'), u.errors.on(:mail)
50 end
44
51
45 def test_create
52 def test_create
46 user = User.new(:firstname => "new", :lastname => "user", :mail => "newuser@somenet.foo")
53 user = User.new(:firstname => "new", :lastname => "user", :mail => "newuser@somenet.foo")
47
54
48 user.login = "jsmith"
55 user.login = "jsmith"
49 user.password, user.password_confirmation = "password", "password"
56 user.password, user.password_confirmation = "password", "password"
50 # login uniqueness
57 # login uniqueness
51 assert !user.save
58 assert !user.save
52 assert_equal 1, user.errors.count
59 assert_equal 1, user.errors.count
53
60
54 user.login = "newuser"
61 user.login = "newuser"
55 user.password, user.password_confirmation = "passwd", "password"
62 user.password, user.password_confirmation = "passwd", "password"
56 # password confirmation
63 # password confirmation
57 assert !user.save
64 assert !user.save
58 assert_equal 1, user.errors.count
65 assert_equal 1, user.errors.count
59
66
60 user.password, user.password_confirmation = "password", "password"
67 user.password, user.password_confirmation = "password", "password"
61 assert user.save
68 assert user.save
62 end
69 end
63
70
64 context "User#before_create" do
71 context "User#before_create" do
65 should "set the mail_notification to the default Setting" do
72 should "set the mail_notification to the default Setting" do
66 @user1 = User.generate_with_protected!
73 @user1 = User.generate_with_protected!
67 assert_equal 'only_my_events', @user1.mail_notification
74 assert_equal 'only_my_events', @user1.mail_notification
68
75
69 with_settings :default_notification_option => 'all' do
76 with_settings :default_notification_option => 'all' do
70 @user2 = User.generate_with_protected!
77 @user2 = User.generate_with_protected!
71 assert_equal 'all', @user2.mail_notification
78 assert_equal 'all', @user2.mail_notification
72 end
79 end
73 end
80 end
74 end
81 end
75
82
76 context "User.login" do
83 context "User.login" do
77 should "be case-insensitive." do
84 should "be case-insensitive." do
78 u = User.new(:firstname => "new", :lastname => "user", :mail => "newuser@somenet.foo")
85 u = User.new(:firstname => "new", :lastname => "user", :mail => "newuser@somenet.foo")
79 u.login = 'newuser'
86 u.login = 'newuser'
80 u.password, u.password_confirmation = "password", "password"
87 u.password, u.password_confirmation = "password", "password"
81 assert u.save
88 assert u.save
82
89
83 u = User.new(:firstname => "Similar", :lastname => "User", :mail => "similaruser@somenet.foo")
90 u = User.new(:firstname => "Similar", :lastname => "User", :mail => "similaruser@somenet.foo")
84 u.login = 'NewUser'
91 u.login = 'NewUser'
85 u.password, u.password_confirmation = "password", "password"
92 u.password, u.password_confirmation = "password", "password"
86 assert !u.save
93 assert !u.save
87 assert_equal I18n.translate('activerecord.errors.messages.taken'), u.errors.on(:login)
94 assert_equal I18n.translate('activerecord.errors.messages.taken'), u.errors.on(:login)
88 end
95 end
89 end
96 end
90
97
91 def test_mail_uniqueness_should_not_be_case_sensitive
98 def test_mail_uniqueness_should_not_be_case_sensitive
92 u = User.new(:firstname => "new", :lastname => "user", :mail => "newuser@somenet.foo")
99 u = User.new(:firstname => "new", :lastname => "user", :mail => "newuser@somenet.foo")
93 u.login = 'newuser1'
100 u.login = 'newuser1'
94 u.password, u.password_confirmation = "password", "password"
101 u.password, u.password_confirmation = "password", "password"
95 assert u.save
102 assert u.save
96
103
97 u = User.new(:firstname => "new", :lastname => "user", :mail => "newUser@Somenet.foo")
104 u = User.new(:firstname => "new", :lastname => "user", :mail => "newUser@Somenet.foo")
98 u.login = 'newuser2'
105 u.login = 'newuser2'
99 u.password, u.password_confirmation = "password", "password"
106 u.password, u.password_confirmation = "password", "password"
100 assert !u.save
107 assert !u.save
101 assert_equal I18n.translate('activerecord.errors.messages.taken'), u.errors.on(:mail)
108 assert_equal I18n.translate('activerecord.errors.messages.taken'), u.errors.on(:mail)
102 end
109 end
103
110
104 def test_update
111 def test_update
105 assert_equal "admin", @admin.login
112 assert_equal "admin", @admin.login
106 @admin.login = "john"
113 @admin.login = "john"
107 assert @admin.save, @admin.errors.full_messages.join("; ")
114 assert @admin.save, @admin.errors.full_messages.join("; ")
108 @admin.reload
115 @admin.reload
109 assert_equal "john", @admin.login
116 assert_equal "john", @admin.login
110 end
117 end
111
118
112 def test_destroy_should_delete_members_and_roles
119 def test_destroy_should_delete_members_and_roles
113 members = Member.find_all_by_user_id(2)
120 members = Member.find_all_by_user_id(2)
114 ms = members.size
121 ms = members.size
115 rs = members.collect(&:roles).flatten.size
122 rs = members.collect(&:roles).flatten.size
116
123
117 assert_difference 'Member.count', - ms do
124 assert_difference 'Member.count', - ms do
118 assert_difference 'MemberRole.count', - rs do
125 assert_difference 'MemberRole.count', - rs do
119 User.find(2).destroy
126 User.find(2).destroy
120 end
127 end
121 end
128 end
122
129
123 assert_nil User.find_by_id(2)
130 assert_nil User.find_by_id(2)
124 assert Member.find_all_by_user_id(2).empty?
131 assert Member.find_all_by_user_id(2).empty?
125 end
132 end
126
133
127 def test_destroy_should_update_attachments
134 def test_destroy_should_update_attachments
128 attachment = Attachment.create!(:container => Project.find(1),
135 attachment = Attachment.create!(:container => Project.find(1),
129 :file => uploaded_test_file("testfile.txt", "text/plain"),
136 :file => uploaded_test_file("testfile.txt", "text/plain"),
130 :author_id => 2)
137 :author_id => 2)
131
138
132 User.find(2).destroy
139 User.find(2).destroy
133 assert_nil User.find_by_id(2)
140 assert_nil User.find_by_id(2)
134 assert_equal User.anonymous, attachment.reload.author
141 assert_equal User.anonymous, attachment.reload.author
135 end
142 end
136
143
137 def test_destroy_should_update_comments
144 def test_destroy_should_update_comments
138 comment = Comment.create!(
145 comment = Comment.create!(
139 :commented => News.create!(:project_id => 1, :author_id => 1, :title => 'foo', :description => 'foo'),
146 :commented => News.create!(:project_id => 1, :author_id => 1, :title => 'foo', :description => 'foo'),
140 :author => User.find(2),
147 :author => User.find(2),
141 :comments => 'foo'
148 :comments => 'foo'
142 )
149 )
143
150
144 User.find(2).destroy
151 User.find(2).destroy
145 assert_nil User.find_by_id(2)
152 assert_nil User.find_by_id(2)
146 assert_equal User.anonymous, comment.reload.author
153 assert_equal User.anonymous, comment.reload.author
147 end
154 end
148
155
149 def test_destroy_should_update_issues
156 def test_destroy_should_update_issues
150 issue = Issue.create!(:project_id => 1, :author_id => 2, :tracker_id => 1, :subject => 'foo')
157 issue = Issue.create!(:project_id => 1, :author_id => 2, :tracker_id => 1, :subject => 'foo')
151
158
152 User.find(2).destroy
159 User.find(2).destroy
153 assert_nil User.find_by_id(2)
160 assert_nil User.find_by_id(2)
154 assert_equal User.anonymous, issue.reload.author
161 assert_equal User.anonymous, issue.reload.author
155 end
162 end
156
163
157 def test_destroy_should_unassign_issues
164 def test_destroy_should_unassign_issues
158 issue = Issue.create!(:project_id => 1, :author_id => 1, :tracker_id => 1, :subject => 'foo', :assigned_to_id => 2)
165 issue = Issue.create!(:project_id => 1, :author_id => 1, :tracker_id => 1, :subject => 'foo', :assigned_to_id => 2)
159
166
160 User.find(2).destroy
167 User.find(2).destroy
161 assert_nil User.find_by_id(2)
168 assert_nil User.find_by_id(2)
162 assert_nil issue.reload.assigned_to
169 assert_nil issue.reload.assigned_to
163 end
170 end
164
171
165 def test_destroy_should_update_journals
172 def test_destroy_should_update_journals
166 issue = Issue.create!(:project_id => 1, :author_id => 2, :tracker_id => 1, :subject => 'foo')
173 issue = Issue.create!(:project_id => 1, :author_id => 2, :tracker_id => 1, :subject => 'foo')
167 issue.init_journal(User.find(2), "update")
174 issue.init_journal(User.find(2), "update")
168 issue.save!
175 issue.save!
169
176
170 User.find(2).destroy
177 User.find(2).destroy
171 assert_nil User.find_by_id(2)
178 assert_nil User.find_by_id(2)
172 assert_equal User.anonymous, issue.journals.first.reload.user
179 assert_equal User.anonymous, issue.journals.first.reload.user
173 end
180 end
174
181
175 def test_destroy_should_update_journal_details_old_value
182 def test_destroy_should_update_journal_details_old_value
176 issue = Issue.create!(:project_id => 1, :author_id => 1, :tracker_id => 1, :subject => 'foo', :assigned_to_id => 2)
183 issue = Issue.create!(:project_id => 1, :author_id => 1, :tracker_id => 1, :subject => 'foo', :assigned_to_id => 2)
177 issue.init_journal(User.find(1), "update")
184 issue.init_journal(User.find(1), "update")
178 issue.assigned_to_id = nil
185 issue.assigned_to_id = nil
179 assert_difference 'JournalDetail.count' do
186 assert_difference 'JournalDetail.count' do
180 issue.save!
187 issue.save!
181 end
188 end
182 journal_detail = JournalDetail.first(:order => 'id DESC')
189 journal_detail = JournalDetail.first(:order => 'id DESC')
183 assert_equal '2', journal_detail.old_value
190 assert_equal '2', journal_detail.old_value
184
191
185 User.find(2).destroy
192 User.find(2).destroy
186 assert_nil User.find_by_id(2)
193 assert_nil User.find_by_id(2)
187 assert_equal User.anonymous.id.to_s, journal_detail.reload.old_value
194 assert_equal User.anonymous.id.to_s, journal_detail.reload.old_value
188 end
195 end
189
196
190 def test_destroy_should_update_journal_details_value
197 def test_destroy_should_update_journal_details_value
191 issue = Issue.create!(:project_id => 1, :author_id => 1, :tracker_id => 1, :subject => 'foo')
198 issue = Issue.create!(:project_id => 1, :author_id => 1, :tracker_id => 1, :subject => 'foo')
192 issue.init_journal(User.find(1), "update")
199 issue.init_journal(User.find(1), "update")
193 issue.assigned_to_id = 2
200 issue.assigned_to_id = 2
194 assert_difference 'JournalDetail.count' do
201 assert_difference 'JournalDetail.count' do
195 issue.save!
202 issue.save!
196 end
203 end
197 journal_detail = JournalDetail.first(:order => 'id DESC')
204 journal_detail = JournalDetail.first(:order => 'id DESC')
198 assert_equal '2', journal_detail.value
205 assert_equal '2', journal_detail.value
199
206
200 User.find(2).destroy
207 User.find(2).destroy
201 assert_nil User.find_by_id(2)
208 assert_nil User.find_by_id(2)
202 assert_equal User.anonymous.id.to_s, journal_detail.reload.value
209 assert_equal User.anonymous.id.to_s, journal_detail.reload.value
203 end
210 end
204
211
205 def test_destroy_should_update_messages
212 def test_destroy_should_update_messages
206 board = Board.create!(:project_id => 1, :name => 'Board', :description => 'Board')
213 board = Board.create!(:project_id => 1, :name => 'Board', :description => 'Board')
207 message = Message.create!(:board_id => board.id, :author_id => 2, :subject => 'foo', :content => 'foo')
214 message = Message.create!(:board_id => board.id, :author_id => 2, :subject => 'foo', :content => 'foo')
208
215
209 User.find(2).destroy
216 User.find(2).destroy
210 assert_nil User.find_by_id(2)
217 assert_nil User.find_by_id(2)
211 assert_equal User.anonymous, message.reload.author
218 assert_equal User.anonymous, message.reload.author
212 end
219 end
213
220
214 def test_destroy_should_update_news
221 def test_destroy_should_update_news
215 news = News.create!(:project_id => 1, :author_id => 2, :title => 'foo', :description => 'foo')
222 news = News.create!(:project_id => 1, :author_id => 2, :title => 'foo', :description => 'foo')
216
223
217 User.find(2).destroy
224 User.find(2).destroy
218 assert_nil User.find_by_id(2)
225 assert_nil User.find_by_id(2)
219 assert_equal User.anonymous, news.reload.author
226 assert_equal User.anonymous, news.reload.author
220 end
227 end
221
228
222 def test_destroy_should_delete_private_queries
229 def test_destroy_should_delete_private_queries
223 query = Query.new(:name => 'foo', :is_public => false)
230 query = Query.new(:name => 'foo', :is_public => false)
224 query.project_id = 1
231 query.project_id = 1
225 query.user_id = 2
232 query.user_id = 2
226 query.save!
233 query.save!
227
234
228 User.find(2).destroy
235 User.find(2).destroy
229 assert_nil User.find_by_id(2)
236 assert_nil User.find_by_id(2)
230 assert_nil Query.find_by_id(query.id)
237 assert_nil Query.find_by_id(query.id)
231 end
238 end
232
239
233 def test_destroy_should_update_public_queries
240 def test_destroy_should_update_public_queries
234 query = Query.new(:name => 'foo', :is_public => true)
241 query = Query.new(:name => 'foo', :is_public => true)
235 query.project_id = 1
242 query.project_id = 1
236 query.user_id = 2
243 query.user_id = 2
237 query.save!
244 query.save!
238
245
239 User.find(2).destroy
246 User.find(2).destroy
240 assert_nil User.find_by_id(2)
247 assert_nil User.find_by_id(2)
241 assert_equal User.anonymous, query.reload.user
248 assert_equal User.anonymous, query.reload.user
242 end
249 end
243
250
244 def test_destroy_should_update_time_entries
251 def test_destroy_should_update_time_entries
245 entry = TimeEntry.new(:hours => '2', :spent_on => Date.today, :activity => TimeEntryActivity.create!(:name => 'foo'))
252 entry = TimeEntry.new(:hours => '2', :spent_on => Date.today, :activity => TimeEntryActivity.create!(:name => 'foo'))
246 entry.project_id = 1
253 entry.project_id = 1
247 entry.user_id = 2
254 entry.user_id = 2
248 entry.save!
255 entry.save!
249
256
250 User.find(2).destroy
257 User.find(2).destroy
251 assert_nil User.find_by_id(2)
258 assert_nil User.find_by_id(2)
252 assert_equal User.anonymous, entry.reload.user
259 assert_equal User.anonymous, entry.reload.user
253 end
260 end
254
261
255 def test_destroy_should_delete_tokens
262 def test_destroy_should_delete_tokens
256 token = Token.create!(:user_id => 2, :value => 'foo')
263 token = Token.create!(:user_id => 2, :value => 'foo')
257
264
258 User.find(2).destroy
265 User.find(2).destroy
259 assert_nil User.find_by_id(2)
266 assert_nil User.find_by_id(2)
260 assert_nil Token.find_by_id(token.id)
267 assert_nil Token.find_by_id(token.id)
261 end
268 end
262
269
263 def test_destroy_should_delete_watchers
270 def test_destroy_should_delete_watchers
264 issue = Issue.create!(:project_id => 1, :author_id => 1, :tracker_id => 1, :subject => 'foo')
271 issue = Issue.create!(:project_id => 1, :author_id => 1, :tracker_id => 1, :subject => 'foo')
265 watcher = Watcher.create!(:user_id => 2, :watchable => issue)
272 watcher = Watcher.create!(:user_id => 2, :watchable => issue)
266
273
267 User.find(2).destroy
274 User.find(2).destroy
268 assert_nil User.find_by_id(2)
275 assert_nil User.find_by_id(2)
269 assert_nil Watcher.find_by_id(watcher.id)
276 assert_nil Watcher.find_by_id(watcher.id)
270 end
277 end
271
278
272 def test_destroy_should_update_wiki_contents
279 def test_destroy_should_update_wiki_contents
273 wiki_content = WikiContent.create!(
280 wiki_content = WikiContent.create!(
274 :text => 'foo',
281 :text => 'foo',
275 :author_id => 2,
282 :author_id => 2,
276 :page => WikiPage.create!(:title => 'Foo', :wiki => Wiki.create!(:project_id => 1, :start_page => 'Start'))
283 :page => WikiPage.create!(:title => 'Foo', :wiki => Wiki.create!(:project_id => 1, :start_page => 'Start'))
277 )
284 )
278 wiki_content.text = 'bar'
285 wiki_content.text = 'bar'
279 assert_difference 'WikiContent::Version.count' do
286 assert_difference 'WikiContent::Version.count' do
280 wiki_content.save!
287 wiki_content.save!
281 end
288 end
282
289
283 User.find(2).destroy
290 User.find(2).destroy
284 assert_nil User.find_by_id(2)
291 assert_nil User.find_by_id(2)
285 assert_equal User.anonymous, wiki_content.reload.author
292 assert_equal User.anonymous, wiki_content.reload.author
286 wiki_content.versions.each do |version|
293 wiki_content.versions.each do |version|
287 assert_equal User.anonymous, version.reload.author
294 assert_equal User.anonymous, version.reload.author
288 end
295 end
289 end
296 end
290
297
291 def test_destroy_should_nullify_issue_categories
298 def test_destroy_should_nullify_issue_categories
292 category = IssueCategory.create!(:project_id => 1, :assigned_to_id => 2, :name => 'foo')
299 category = IssueCategory.create!(:project_id => 1, :assigned_to_id => 2, :name => 'foo')
293
300
294 User.find(2).destroy
301 User.find(2).destroy
295 assert_nil User.find_by_id(2)
302 assert_nil User.find_by_id(2)
296 assert_nil category.reload.assigned_to_id
303 assert_nil category.reload.assigned_to_id
297 end
304 end
298
305
299 def test_destroy_should_nullify_changesets
306 def test_destroy_should_nullify_changesets
300 changeset = Changeset.create!(
307 changeset = Changeset.create!(
301 :repository => Repository::Subversion.create!(
308 :repository => Repository::Subversion.create!(
302 :project_id => 1,
309 :project_id => 1,
303 :url => 'file:///var/svn'
310 :url => 'file:///var/svn'
304 ),
311 ),
305 :revision => '12',
312 :revision => '12',
306 :committed_on => Time.now,
313 :committed_on => Time.now,
307 :committer => 'jsmith'
314 :committer => 'jsmith'
308 )
315 )
309 assert_equal 2, changeset.user_id
316 assert_equal 2, changeset.user_id
310
317
311 User.find(2).destroy
318 User.find(2).destroy
312 assert_nil User.find_by_id(2)
319 assert_nil User.find_by_id(2)
313 assert_nil changeset.reload.user_id
320 assert_nil changeset.reload.user_id
314 end
321 end
315
322
316 def test_anonymous_user_should_not_be_destroyable
323 def test_anonymous_user_should_not_be_destroyable
317 assert_no_difference 'User.count' do
324 assert_no_difference 'User.count' do
318 assert_equal false, User.anonymous.destroy
325 assert_equal false, User.anonymous.destroy
319 end
326 end
320 end
327 end
321
328
322 def test_validate_login_presence
329 def test_validate_login_presence
323 @admin.login = ""
330 @admin.login = ""
324 assert !@admin.save
331 assert !@admin.save
325 assert_equal 1, @admin.errors.count
332 assert_equal 1, @admin.errors.count
326 end
333 end
327
334
328 def test_validate_mail_notification_inclusion
335 def test_validate_mail_notification_inclusion
329 u = User.new
336 u = User.new
330 u.mail_notification = 'foo'
337 u.mail_notification = 'foo'
331 u.save
338 u.save
332 assert_not_nil u.errors.on(:mail_notification)
339 assert_not_nil u.errors.on(:mail_notification)
333 end
340 end
334
341
335 context "User#try_to_login" do
342 context "User#try_to_login" do
336 should "fall-back to case-insensitive if user login is not found as-typed." do
343 should "fall-back to case-insensitive if user login is not found as-typed." do
337 user = User.try_to_login("AdMin", "admin")
344 user = User.try_to_login("AdMin", "admin")
338 assert_kind_of User, user
345 assert_kind_of User, user
339 assert_equal "admin", user.login
346 assert_equal "admin", user.login
340 end
347 end
341
348
342 should "select the exact matching user first" do
349 should "select the exact matching user first" do
343 case_sensitive_user = User.generate_with_protected!(:login => 'changed', :password => 'admin', :password_confirmation => 'admin')
350 case_sensitive_user = User.generate_with_protected!(:login => 'changed', :password => 'admin', :password_confirmation => 'admin')
344 # bypass validations to make it appear like existing data
351 # bypass validations to make it appear like existing data
345 case_sensitive_user.update_attribute(:login, 'ADMIN')
352 case_sensitive_user.update_attribute(:login, 'ADMIN')
346
353
347 user = User.try_to_login("ADMIN", "admin")
354 user = User.try_to_login("ADMIN", "admin")
348 assert_kind_of User, user
355 assert_kind_of User, user
349 assert_equal "ADMIN", user.login
356 assert_equal "ADMIN", user.login
350
357
351 end
358 end
352 end
359 end
353
360
354 def test_password
361 def test_password
355 user = User.try_to_login("admin", "admin")
362 user = User.try_to_login("admin", "admin")
356 assert_kind_of User, user
363 assert_kind_of User, user
357 assert_equal "admin", user.login
364 assert_equal "admin", user.login
358 user.password = "hello"
365 user.password = "hello"
359 assert user.save
366 assert user.save
360
367
361 user = User.try_to_login("admin", "hello")
368 user = User.try_to_login("admin", "hello")
362 assert_kind_of User, user
369 assert_kind_of User, user
363 assert_equal "admin", user.login
370 assert_equal "admin", user.login
364 end
371 end
365
372
366 def test_name_format
373 def test_name_format
367 assert_equal 'Smith, John', @jsmith.name(:lastname_coma_firstname)
374 assert_equal 'Smith, John', @jsmith.name(:lastname_coma_firstname)
368 Setting.user_format = :firstname_lastname
375 Setting.user_format = :firstname_lastname
369 assert_equal 'John Smith', @jsmith.reload.name
376 assert_equal 'John Smith', @jsmith.reload.name
370 Setting.user_format = :username
377 Setting.user_format = :username
371 assert_equal 'jsmith', @jsmith.reload.name
378 assert_equal 'jsmith', @jsmith.reload.name
372 end
379 end
373
380
374 def test_lock
381 def test_lock
375 user = User.try_to_login("jsmith", "jsmith")
382 user = User.try_to_login("jsmith", "jsmith")
376 assert_equal @jsmith, user
383 assert_equal @jsmith, user
377
384
378 @jsmith.status = User::STATUS_LOCKED
385 @jsmith.status = User::STATUS_LOCKED
379 assert @jsmith.save
386 assert @jsmith.save
380
387
381 user = User.try_to_login("jsmith", "jsmith")
388 user = User.try_to_login("jsmith", "jsmith")
382 assert_equal nil, user
389 assert_equal nil, user
383 end
390 end
384
391
385 context ".try_to_login" do
392 context ".try_to_login" do
386 context "with good credentials" do
393 context "with good credentials" do
387 should "return the user" do
394 should "return the user" do
388 user = User.try_to_login("admin", "admin")
395 user = User.try_to_login("admin", "admin")
389 assert_kind_of User, user
396 assert_kind_of User, user
390 assert_equal "admin", user.login
397 assert_equal "admin", user.login
391 end
398 end
392 end
399 end
393
400
394 context "with wrong credentials" do
401 context "with wrong credentials" do
395 should "return nil" do
402 should "return nil" do
396 assert_nil User.try_to_login("admin", "foo")
403 assert_nil User.try_to_login("admin", "foo")
397 end
404 end
398 end
405 end
399 end
406 end
400
407
401 if ldap_configured?
408 if ldap_configured?
402 context "#try_to_login using LDAP" do
409 context "#try_to_login using LDAP" do
403 context "with failed connection to the LDAP server" do
410 context "with failed connection to the LDAP server" do
404 should "return nil" do
411 should "return nil" do
405 @auth_source = AuthSourceLdap.find(1)
412 @auth_source = AuthSourceLdap.find(1)
406 AuthSource.any_instance.stubs(:initialize_ldap_con).raises(Net::LDAP::LdapError, 'Cannot connect')
413 AuthSource.any_instance.stubs(:initialize_ldap_con).raises(Net::LDAP::LdapError, 'Cannot connect')
407
414
408 assert_equal nil, User.try_to_login('edavis', 'wrong')
415 assert_equal nil, User.try_to_login('edavis', 'wrong')
409 end
416 end
410 end
417 end
411
418
412 context "with an unsuccessful authentication" do
419 context "with an unsuccessful authentication" do
413 should "return nil" do
420 should "return nil" do
414 assert_equal nil, User.try_to_login('edavis', 'wrong')
421 assert_equal nil, User.try_to_login('edavis', 'wrong')
415 end
422 end
416 end
423 end
417
424
418 context "on the fly registration" do
425 context "on the fly registration" do
419 setup do
426 setup do
420 @auth_source = AuthSourceLdap.find(1)
427 @auth_source = AuthSourceLdap.find(1)
421 end
428 end
422
429
423 context "with a successful authentication" do
430 context "with a successful authentication" do
424 should "create a new user account if it doesn't exist" do
431 should "create a new user account if it doesn't exist" do
425 assert_difference('User.count') do
432 assert_difference('User.count') do
426 user = User.try_to_login('edavis', '123456')
433 user = User.try_to_login('edavis', '123456')
427 assert !user.admin?
434 assert !user.admin?
428 end
435 end
429 end
436 end
430
437
431 should "retrieve existing user" do
438 should "retrieve existing user" do
432 user = User.try_to_login('edavis', '123456')
439 user = User.try_to_login('edavis', '123456')
433 user.admin = true
440 user.admin = true
434 user.save!
441 user.save!
435
442
436 assert_no_difference('User.count') do
443 assert_no_difference('User.count') do
437 user = User.try_to_login('edavis', '123456')
444 user = User.try_to_login('edavis', '123456')
438 assert user.admin?
445 assert user.admin?
439 end
446 end
440 end
447 end
441 end
448 end
442 end
449 end
443 end
450 end
444
451
445 else
452 else
446 puts "Skipping LDAP tests."
453 puts "Skipping LDAP tests."
447 end
454 end
448
455
449 def test_create_anonymous
456 def test_create_anonymous
450 AnonymousUser.delete_all
457 AnonymousUser.delete_all
451 anon = User.anonymous
458 anon = User.anonymous
452 assert !anon.new_record?
459 assert !anon.new_record?
453 assert_kind_of AnonymousUser, anon
460 assert_kind_of AnonymousUser, anon
454 end
461 end
455
462
456 should_have_one :rss_token
463 should_have_one :rss_token
457
464
458 def test_rss_key
465 def test_rss_key
459 assert_nil @jsmith.rss_token
466 assert_nil @jsmith.rss_token
460 key = @jsmith.rss_key
467 key = @jsmith.rss_key
461 assert_equal 40, key.length
468 assert_equal 40, key.length
462
469
463 @jsmith.reload
470 @jsmith.reload
464 assert_equal key, @jsmith.rss_key
471 assert_equal key, @jsmith.rss_key
465 end
472 end
466
473
467
474
468 should_have_one :api_token
475 should_have_one :api_token
469
476
470 context "User#api_key" do
477 context "User#api_key" do
471 should "generate a new one if the user doesn't have one" do
478 should "generate a new one if the user doesn't have one" do
472 user = User.generate_with_protected!(:api_token => nil)
479 user = User.generate_with_protected!(:api_token => nil)
473 assert_nil user.api_token
480 assert_nil user.api_token
474
481
475 key = user.api_key
482 key = user.api_key
476 assert_equal 40, key.length
483 assert_equal 40, key.length
477 user.reload
484 user.reload
478 assert_equal key, user.api_key
485 assert_equal key, user.api_key
479 end
486 end
480
487
481 should "return the existing api token value" do
488 should "return the existing api token value" do
482 user = User.generate_with_protected!
489 user = User.generate_with_protected!
483 token = Token.generate!(:action => 'api')
490 token = Token.generate!(:action => 'api')
484 user.api_token = token
491 user.api_token = token
485 assert user.save
492 assert user.save
486
493
487 assert_equal token.value, user.api_key
494 assert_equal token.value, user.api_key
488 end
495 end
489 end
496 end
490
497
491 context "User#find_by_api_key" do
498 context "User#find_by_api_key" do
492 should "return nil if no matching key is found" do
499 should "return nil if no matching key is found" do
493 assert_nil User.find_by_api_key('zzzzzzzzz')
500 assert_nil User.find_by_api_key('zzzzzzzzz')
494 end
501 end
495
502
496 should "return nil if the key is found for an inactive user" do
503 should "return nil if the key is found for an inactive user" do
497 user = User.generate_with_protected!(:status => User::STATUS_LOCKED)
504 user = User.generate_with_protected!(:status => User::STATUS_LOCKED)
498 token = Token.generate!(:action => 'api')
505 token = Token.generate!(:action => 'api')
499 user.api_token = token
506 user.api_token = token
500 user.save
507 user.save
501
508
502 assert_nil User.find_by_api_key(token.value)
509 assert_nil User.find_by_api_key(token.value)
503 end
510 end
504
511
505 should "return the user if the key is found for an active user" do
512 should "return the user if the key is found for an active user" do
506 user = User.generate_with_protected!(:status => User::STATUS_ACTIVE)
513 user = User.generate_with_protected!(:status => User::STATUS_ACTIVE)
507 token = Token.generate!(:action => 'api')
514 token = Token.generate!(:action => 'api')
508 user.api_token = token
515 user.api_token = token
509 user.save
516 user.save
510
517
511 assert_equal user, User.find_by_api_key(token.value)
518 assert_equal user, User.find_by_api_key(token.value)
512 end
519 end
513 end
520 end
514
521
515 def test_roles_for_project
522 def test_roles_for_project
516 # user with a role
523 # user with a role
517 roles = @jsmith.roles_for_project(Project.find(1))
524 roles = @jsmith.roles_for_project(Project.find(1))
518 assert_kind_of Role, roles.first
525 assert_kind_of Role, roles.first
519 assert_equal "Manager", roles.first.name
526 assert_equal "Manager", roles.first.name
520
527
521 # user with no role
528 # user with no role
522 assert_nil @dlopper.roles_for_project(Project.find(2)).detect {|role| role.member?}
529 assert_nil @dlopper.roles_for_project(Project.find(2)).detect {|role| role.member?}
523 end
530 end
524
531
525 def test_projects_by_role_for_user_with_role
532 def test_projects_by_role_for_user_with_role
526 user = User.find(2)
533 user = User.find(2)
527 assert_kind_of Hash, user.projects_by_role
534 assert_kind_of Hash, user.projects_by_role
528 assert_equal 2, user.projects_by_role.size
535 assert_equal 2, user.projects_by_role.size
529 assert_equal [1,5], user.projects_by_role[Role.find(1)].collect(&:id).sort
536 assert_equal [1,5], user.projects_by_role[Role.find(1)].collect(&:id).sort
530 assert_equal [2], user.projects_by_role[Role.find(2)].collect(&:id).sort
537 assert_equal [2], user.projects_by_role[Role.find(2)].collect(&:id).sort
531 end
538 end
532
539
533 def test_projects_by_role_for_user_with_no_role
540 def test_projects_by_role_for_user_with_no_role
534 user = User.generate!
541 user = User.generate!
535 assert_equal({}, user.projects_by_role)
542 assert_equal({}, user.projects_by_role)
536 end
543 end
537
544
538 def test_projects_by_role_for_anonymous
545 def test_projects_by_role_for_anonymous
539 assert_equal({}, User.anonymous.projects_by_role)
546 assert_equal({}, User.anonymous.projects_by_role)
540 end
547 end
541
548
542 def test_valid_notification_options
549 def test_valid_notification_options
543 # without memberships
550 # without memberships
544 assert_equal 5, User.find(7).valid_notification_options.size
551 assert_equal 5, User.find(7).valid_notification_options.size
545 # with memberships
552 # with memberships
546 assert_equal 6, User.find(2).valid_notification_options.size
553 assert_equal 6, User.find(2).valid_notification_options.size
547 end
554 end
548
555
549 def test_valid_notification_options_class_method
556 def test_valid_notification_options_class_method
550 assert_equal 5, User.valid_notification_options.size
557 assert_equal 5, User.valid_notification_options.size
551 assert_equal 5, User.valid_notification_options(User.find(7)).size
558 assert_equal 5, User.valid_notification_options(User.find(7)).size
552 assert_equal 6, User.valid_notification_options(User.find(2)).size
559 assert_equal 6, User.valid_notification_options(User.find(2)).size
553 end
560 end
554
561
555 def test_mail_notification_all
562 def test_mail_notification_all
556 @jsmith.mail_notification = 'all'
563 @jsmith.mail_notification = 'all'
557 @jsmith.notified_project_ids = []
564 @jsmith.notified_project_ids = []
558 @jsmith.save
565 @jsmith.save
559 @jsmith.reload
566 @jsmith.reload
560 assert @jsmith.projects.first.recipients.include?(@jsmith.mail)
567 assert @jsmith.projects.first.recipients.include?(@jsmith.mail)
561 end
568 end
562
569
563 def test_mail_notification_selected
570 def test_mail_notification_selected
564 @jsmith.mail_notification = 'selected'
571 @jsmith.mail_notification = 'selected'
565 @jsmith.notified_project_ids = [1]
572 @jsmith.notified_project_ids = [1]
566 @jsmith.save
573 @jsmith.save
567 @jsmith.reload
574 @jsmith.reload
568 assert Project.find(1).recipients.include?(@jsmith.mail)
575 assert Project.find(1).recipients.include?(@jsmith.mail)
569 end
576 end
570
577
571 def test_mail_notification_only_my_events
578 def test_mail_notification_only_my_events
572 @jsmith.mail_notification = 'only_my_events'
579 @jsmith.mail_notification = 'only_my_events'
573 @jsmith.notified_project_ids = []
580 @jsmith.notified_project_ids = []
574 @jsmith.save
581 @jsmith.save
575 @jsmith.reload
582 @jsmith.reload
576 assert !@jsmith.projects.first.recipients.include?(@jsmith.mail)
583 assert !@jsmith.projects.first.recipients.include?(@jsmith.mail)
577 end
584 end
578
585
579 def test_comments_sorting_preference
586 def test_comments_sorting_preference
580 assert !@jsmith.wants_comments_in_reverse_order?
587 assert !@jsmith.wants_comments_in_reverse_order?
581 @jsmith.pref.comments_sorting = 'asc'
588 @jsmith.pref.comments_sorting = 'asc'
582 assert !@jsmith.wants_comments_in_reverse_order?
589 assert !@jsmith.wants_comments_in_reverse_order?
583 @jsmith.pref.comments_sorting = 'desc'
590 @jsmith.pref.comments_sorting = 'desc'
584 assert @jsmith.wants_comments_in_reverse_order?
591 assert @jsmith.wants_comments_in_reverse_order?
585 end
592 end
586
593
587 def test_find_by_mail_should_be_case_insensitive
594 def test_find_by_mail_should_be_case_insensitive
588 u = User.find_by_mail('JSmith@somenet.foo')
595 u = User.find_by_mail('JSmith@somenet.foo')
589 assert_not_nil u
596 assert_not_nil u
590 assert_equal 'jsmith@somenet.foo', u.mail
597 assert_equal 'jsmith@somenet.foo', u.mail
591 end
598 end
592
599
593 def test_random_password
600 def test_random_password
594 u = User.new
601 u = User.new
595 u.random_password
602 u.random_password
596 assert !u.password.blank?
603 assert !u.password.blank?
597 assert !u.password_confirmation.blank?
604 assert !u.password_confirmation.blank?
598 end
605 end
599
606
600 context "#change_password_allowed?" do
607 context "#change_password_allowed?" do
601 should "be allowed if no auth source is set" do
608 should "be allowed if no auth source is set" do
602 user = User.generate_with_protected!
609 user = User.generate_with_protected!
603 assert user.change_password_allowed?
610 assert user.change_password_allowed?
604 end
611 end
605
612
606 should "delegate to the auth source" do
613 should "delegate to the auth source" do
607 user = User.generate_with_protected!
614 user = User.generate_with_protected!
608
615
609 allowed_auth_source = AuthSource.generate!
616 allowed_auth_source = AuthSource.generate!
610 def allowed_auth_source.allow_password_changes?; true; end
617 def allowed_auth_source.allow_password_changes?; true; end
611
618
612 denied_auth_source = AuthSource.generate!
619 denied_auth_source = AuthSource.generate!
613 def denied_auth_source.allow_password_changes?; false; end
620 def denied_auth_source.allow_password_changes?; false; end
614
621
615 assert user.change_password_allowed?
622 assert user.change_password_allowed?
616
623
617 user.auth_source = allowed_auth_source
624 user.auth_source = allowed_auth_source
618 assert user.change_password_allowed?, "User not allowed to change password, though auth source does"
625 assert user.change_password_allowed?, "User not allowed to change password, though auth source does"
619
626
620 user.auth_source = denied_auth_source
627 user.auth_source = denied_auth_source
621 assert !user.change_password_allowed?, "User allowed to change password, though auth source does not"
628 assert !user.change_password_allowed?, "User allowed to change password, though auth source does not"
622 end
629 end
623
630
624 end
631 end
625
632
626 context "#allowed_to?" do
633 context "#allowed_to?" do
627 context "with a unique project" do
634 context "with a unique project" do
628 should "return false if project is archived" do
635 should "return false if project is archived" do
629 project = Project.find(1)
636 project = Project.find(1)
630 Project.any_instance.stubs(:status).returns(Project::STATUS_ARCHIVED)
637 Project.any_instance.stubs(:status).returns(Project::STATUS_ARCHIVED)
631 assert ! @admin.allowed_to?(:view_issues, Project.find(1))
638 assert ! @admin.allowed_to?(:view_issues, Project.find(1))
632 end
639 end
633
640
634 should "return false if related module is disabled" do
641 should "return false if related module is disabled" do
635 project = Project.find(1)
642 project = Project.find(1)
636 project.enabled_module_names = ["issue_tracking"]
643 project.enabled_module_names = ["issue_tracking"]
637 assert @admin.allowed_to?(:add_issues, project)
644 assert @admin.allowed_to?(:add_issues, project)
638 assert ! @admin.allowed_to?(:view_wiki_pages, project)
645 assert ! @admin.allowed_to?(:view_wiki_pages, project)
639 end
646 end
640
647
641 should "authorize nearly everything for admin users" do
648 should "authorize nearly everything for admin users" do
642 project = Project.find(1)
649 project = Project.find(1)
643 assert ! @admin.member_of?(project)
650 assert ! @admin.member_of?(project)
644 %w(edit_issues delete_issues manage_news manage_documents manage_wiki).each do |p|
651 %w(edit_issues delete_issues manage_news manage_documents manage_wiki).each do |p|
645 assert @admin.allowed_to?(p.to_sym, project)
652 assert @admin.allowed_to?(p.to_sym, project)
646 end
653 end
647 end
654 end
648
655
649 should "authorize normal users depending on their roles" do
656 should "authorize normal users depending on their roles" do
650 project = Project.find(1)
657 project = Project.find(1)
651 assert @jsmith.allowed_to?(:delete_messages, project) #Manager
658 assert @jsmith.allowed_to?(:delete_messages, project) #Manager
652 assert ! @dlopper.allowed_to?(:delete_messages, project) #Developper
659 assert ! @dlopper.allowed_to?(:delete_messages, project) #Developper
653 end
660 end
654 end
661 end
655
662
656 context "with multiple projects" do
663 context "with multiple projects" do
657 should "return false if array is empty" do
664 should "return false if array is empty" do
658 assert ! @admin.allowed_to?(:view_project, [])
665 assert ! @admin.allowed_to?(:view_project, [])
659 end
666 end
660
667
661 should "return true only if user has permission on all these projects" do
668 should "return true only if user has permission on all these projects" do
662 assert @admin.allowed_to?(:view_project, Project.all)
669 assert @admin.allowed_to?(:view_project, Project.all)
663 assert ! @dlopper.allowed_to?(:view_project, Project.all) #cannot see Project(2)
670 assert ! @dlopper.allowed_to?(:view_project, Project.all) #cannot see Project(2)
664 assert @jsmith.allowed_to?(:edit_issues, @jsmith.projects) #Manager or Developer everywhere
671 assert @jsmith.allowed_to?(:edit_issues, @jsmith.projects) #Manager or Developer everywhere
665 assert ! @jsmith.allowed_to?(:delete_issue_watchers, @jsmith.projects) #Dev cannot delete_issue_watchers
672 assert ! @jsmith.allowed_to?(:delete_issue_watchers, @jsmith.projects) #Dev cannot delete_issue_watchers
666 end
673 end
667
674
668 should "behave correctly with arrays of 1 project" do
675 should "behave correctly with arrays of 1 project" do
669 assert ! User.anonymous.allowed_to?(:delete_issues, [Project.first])
676 assert ! User.anonymous.allowed_to?(:delete_issues, [Project.first])
670 end
677 end
671 end
678 end
672
679
673 context "with options[:global]" do
680 context "with options[:global]" do
674 should "authorize if user has at least one role that has this permission" do
681 should "authorize if user has at least one role that has this permission" do
675 @dlopper2 = User.find(5) #only Developper on a project, not Manager anywhere
682 @dlopper2 = User.find(5) #only Developper on a project, not Manager anywhere
676 @anonymous = User.find(6)
683 @anonymous = User.find(6)
677 assert @jsmith.allowed_to?(:delete_issue_watchers, nil, :global => true)
684 assert @jsmith.allowed_to?(:delete_issue_watchers, nil, :global => true)
678 assert ! @dlopper2.allowed_to?(:delete_issue_watchers, nil, :global => true)
685 assert ! @dlopper2.allowed_to?(:delete_issue_watchers, nil, :global => true)
679 assert @dlopper2.allowed_to?(:add_issues, nil, :global => true)
686 assert @dlopper2.allowed_to?(:add_issues, nil, :global => true)
680 assert ! @anonymous.allowed_to?(:add_issues, nil, :global => true)
687 assert ! @anonymous.allowed_to?(:add_issues, nil, :global => true)
681 assert @anonymous.allowed_to?(:view_issues, nil, :global => true)
688 assert @anonymous.allowed_to?(:view_issues, nil, :global => true)
682 end
689 end
683 end
690 end
684 end
691 end
685
692
686 context "User#notify_about?" do
693 context "User#notify_about?" do
687 context "Issues" do
694 context "Issues" do
688 setup do
695 setup do
689 @project = Project.find(1)
696 @project = Project.find(1)
690 @author = User.generate_with_protected!
697 @author = User.generate_with_protected!
691 @assignee = User.generate_with_protected!
698 @assignee = User.generate_with_protected!
692 @issue = Issue.generate_for_project!(@project, :assigned_to => @assignee, :author => @author)
699 @issue = Issue.generate_for_project!(@project, :assigned_to => @assignee, :author => @author)
693 end
700 end
694
701
695 should "be true for a user with :all" do
702 should "be true for a user with :all" do
696 @author.update_attribute(:mail_notification, 'all')
703 @author.update_attribute(:mail_notification, 'all')
697 assert @author.notify_about?(@issue)
704 assert @author.notify_about?(@issue)
698 end
705 end
699
706
700 should "be false for a user with :none" do
707 should "be false for a user with :none" do
701 @author.update_attribute(:mail_notification, 'none')
708 @author.update_attribute(:mail_notification, 'none')
702 assert ! @author.notify_about?(@issue)
709 assert ! @author.notify_about?(@issue)
703 end
710 end
704
711
705 should "be false for a user with :only_my_events and isn't an author, creator, or assignee" do
712 should "be false for a user with :only_my_events and isn't an author, creator, or assignee" do
706 @user = User.generate_with_protected!(:mail_notification => 'only_my_events')
713 @user = User.generate_with_protected!(:mail_notification => 'only_my_events')
707 Member.create!(:user => @user, :project => @project, :role_ids => [1])
714 Member.create!(:user => @user, :project => @project, :role_ids => [1])
708 assert ! @user.notify_about?(@issue)
715 assert ! @user.notify_about?(@issue)
709 end
716 end
710
717
711 should "be true for a user with :only_my_events and is the author" do
718 should "be true for a user with :only_my_events and is the author" do
712 @author.update_attribute(:mail_notification, 'only_my_events')
719 @author.update_attribute(:mail_notification, 'only_my_events')
713 assert @author.notify_about?(@issue)
720 assert @author.notify_about?(@issue)
714 end
721 end
715
722
716 should "be true for a user with :only_my_events and is the assignee" do
723 should "be true for a user with :only_my_events and is the assignee" do
717 @assignee.update_attribute(:mail_notification, 'only_my_events')
724 @assignee.update_attribute(:mail_notification, 'only_my_events')
718 assert @assignee.notify_about?(@issue)
725 assert @assignee.notify_about?(@issue)
719 end
726 end
720
727
721 should "be true for a user with :only_assigned and is the assignee" do
728 should "be true for a user with :only_assigned and is the assignee" do
722 @assignee.update_attribute(:mail_notification, 'only_assigned')
729 @assignee.update_attribute(:mail_notification, 'only_assigned')
723 assert @assignee.notify_about?(@issue)
730 assert @assignee.notify_about?(@issue)
724 end
731 end
725
732
726 should "be false for a user with :only_assigned and is not the assignee" do
733 should "be false for a user with :only_assigned and is not the assignee" do
727 @author.update_attribute(:mail_notification, 'only_assigned')
734 @author.update_attribute(:mail_notification, 'only_assigned')
728 assert ! @author.notify_about?(@issue)
735 assert ! @author.notify_about?(@issue)
729 end
736 end
730
737
731 should "be true for a user with :only_owner and is the author" do
738 should "be true for a user with :only_owner and is the author" do
732 @author.update_attribute(:mail_notification, 'only_owner')
739 @author.update_attribute(:mail_notification, 'only_owner')
733 assert @author.notify_about?(@issue)
740 assert @author.notify_about?(@issue)
734 end
741 end
735
742
736 should "be false for a user with :only_owner and is not the author" do
743 should "be false for a user with :only_owner and is not the author" do
737 @assignee.update_attribute(:mail_notification, 'only_owner')
744 @assignee.update_attribute(:mail_notification, 'only_owner')
738 assert ! @assignee.notify_about?(@issue)
745 assert ! @assignee.notify_about?(@issue)
739 end
746 end
740
747
741 should "be true for a user with :selected and is the author" do
748 should "be true for a user with :selected and is the author" do
742 @author.update_attribute(:mail_notification, 'selected')
749 @author.update_attribute(:mail_notification, 'selected')
743 assert @author.notify_about?(@issue)
750 assert @author.notify_about?(@issue)
744 end
751 end
745
752
746 should "be true for a user with :selected and is the assignee" do
753 should "be true for a user with :selected and is the assignee" do
747 @assignee.update_attribute(:mail_notification, 'selected')
754 @assignee.update_attribute(:mail_notification, 'selected')
748 assert @assignee.notify_about?(@issue)
755 assert @assignee.notify_about?(@issue)
749 end
756 end
750
757
751 should "be false for a user with :selected and is not the author or assignee" do
758 should "be false for a user with :selected and is not the author or assignee" do
752 @user = User.generate_with_protected!(:mail_notification => 'selected')
759 @user = User.generate_with_protected!(:mail_notification => 'selected')
753 Member.create!(:user => @user, :project => @project, :role_ids => [1])
760 Member.create!(:user => @user, :project => @project, :role_ids => [1])
754 assert ! @user.notify_about?(@issue)
761 assert ! @user.notify_about?(@issue)
755 end
762 end
756 end
763 end
757
764
758 context "other events" do
765 context "other events" do
759 should 'be added and tested'
766 should 'be added and tested'
760 end
767 end
761 end
768 end
762
769
763 def test_salt_unsalted_passwords
770 def test_salt_unsalted_passwords
764 # Restore a user with an unsalted password
771 # Restore a user with an unsalted password
765 user = User.find(1)
772 user = User.find(1)
766 user.salt = nil
773 user.salt = nil
767 user.hashed_password = User.hash_password("unsalted")
774 user.hashed_password = User.hash_password("unsalted")
768 user.save!
775 user.save!
769
776
770 User.salt_unsalted_passwords!
777 User.salt_unsalted_passwords!
771
778
772 user.reload
779 user.reload
773 # Salt added
780 # Salt added
774 assert !user.salt.blank?
781 assert !user.salt.blank?
775 # Password still valid
782 # Password still valid
776 assert user.check_password?("unsalted")
783 assert user.check_password?("unsalted")
777 assert_equal user, User.try_to_login(user.login, "unsalted")
784 assert_equal user, User.try_to_login(user.login, "unsalted")
778 end
785 end
779
786
780 if Object.const_defined?(:OpenID)
787 if Object.const_defined?(:OpenID)
781
788
782 def test_setting_identity_url
789 def test_setting_identity_url
783 normalized_open_id_url = 'http://example.com/'
790 normalized_open_id_url = 'http://example.com/'
784 u = User.new( :identity_url => 'http://example.com/' )
791 u = User.new( :identity_url => 'http://example.com/' )
785 assert_equal normalized_open_id_url, u.identity_url
792 assert_equal normalized_open_id_url, u.identity_url
786 end
793 end
787
794
788 def test_setting_identity_url_without_trailing_slash
795 def test_setting_identity_url_without_trailing_slash
789 normalized_open_id_url = 'http://example.com/'
796 normalized_open_id_url = 'http://example.com/'
790 u = User.new( :identity_url => 'http://example.com' )
797 u = User.new( :identity_url => 'http://example.com' )
791 assert_equal normalized_open_id_url, u.identity_url
798 assert_equal normalized_open_id_url, u.identity_url
792 end
799 end
793
800
794 def test_setting_identity_url_without_protocol
801 def test_setting_identity_url_without_protocol
795 normalized_open_id_url = 'http://example.com/'
802 normalized_open_id_url = 'http://example.com/'
796 u = User.new( :identity_url => 'example.com' )
803 u = User.new( :identity_url => 'example.com' )
797 assert_equal normalized_open_id_url, u.identity_url
804 assert_equal normalized_open_id_url, u.identity_url
798 end
805 end
799
806
800 def test_setting_blank_identity_url
807 def test_setting_blank_identity_url
801 u = User.new( :identity_url => 'example.com' )
808 u = User.new( :identity_url => 'example.com' )
802 u.identity_url = ''
809 u.identity_url = ''
803 assert u.identity_url.blank?
810 assert u.identity_url.blank?
804 end
811 end
805
812
806 def test_setting_invalid_identity_url
813 def test_setting_invalid_identity_url
807 u = User.new( :identity_url => 'this is not an openid url' )
814 u = User.new( :identity_url => 'this is not an openid url' )
808 assert u.identity_url.blank?
815 assert u.identity_url.blank?
809 end
816 end
810
817
811 else
818 else
812 puts "Skipping openid tests."
819 puts "Skipping openid tests."
813 end
820 end
814
821
815 end
822 end
General Comments 0
You need to be logged in to leave comments. Login now