##// END OF EJS Templates
When destroying a user, remove all references to that user (#7296)....
Jean-Philippe Lang -
r4606:e809d40f4ed9
parent child
Show More
@@ -1,501 +1,533
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2009 Jean-Philippe Lang
2 # Copyright (C) 2006-2009 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 require "digest/sha1"
18 require "digest/sha1"
19
19
20 class User < Principal
20 class User < Principal
21 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, :dependent => :destroy, :class_name => 'Token', :conditions => "action='feeds'"
51 has_one :rss_token, :class_name => 'Token', :conditions => "action='feeds'"
52 has_one :api_token, :dependent => :destroy, :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_nil => 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
78
77 def before_create
79 def before_create
78 self.mail_notification = Setting.default_notification_option if self.mail_notification.blank?
80 self.mail_notification = Setting.default_notification_option if self.mail_notification.blank?
79 true
81 true
80 end
82 end
81
83
82 def before_save
84 def before_save
83 # update hashed_password if password was set
85 # update hashed_password if password was set
84 self.hashed_password = User.hash_password(self.password) if self.password && self.auth_source_id.blank?
86 self.hashed_password = User.hash_password(self.password) if self.password && self.auth_source_id.blank?
85 end
87 end
86
88
87 def reload(*args)
89 def reload(*args)
88 @name = nil
90 @name = nil
89 super
91 super
90 end
92 end
91
93
92 def mail=(arg)
94 def mail=(arg)
93 write_attribute(:mail, arg.to_s.strip)
95 write_attribute(:mail, arg.to_s.strip)
94 end
96 end
95
97
96 def identity_url=(url)
98 def identity_url=(url)
97 if url.blank?
99 if url.blank?
98 write_attribute(:identity_url, '')
100 write_attribute(:identity_url, '')
99 else
101 else
100 begin
102 begin
101 write_attribute(:identity_url, OpenIdAuthentication.normalize_identifier(url))
103 write_attribute(:identity_url, OpenIdAuthentication.normalize_identifier(url))
102 rescue OpenIdAuthentication::InvalidOpenId
104 rescue OpenIdAuthentication::InvalidOpenId
103 # Invlaid url, don't save
105 # Invlaid url, don't save
104 end
106 end
105 end
107 end
106 self.read_attribute(:identity_url)
108 self.read_attribute(:identity_url)
107 end
109 end
108
110
109 # Returns the user that matches provided login and password, or nil
111 # Returns the user that matches provided login and password, or nil
110 def self.try_to_login(login, password)
112 def self.try_to_login(login, password)
111 # Make sure no one can sign in with an empty password
113 # Make sure no one can sign in with an empty password
112 return nil if password.to_s.empty?
114 return nil if password.to_s.empty?
113 user = find_by_login(login)
115 user = find_by_login(login)
114 if user
116 if user
115 # user is already in local database
117 # user is already in local database
116 return nil if !user.active?
118 return nil if !user.active?
117 if user.auth_source
119 if user.auth_source
118 # user has an external authentication method
120 # user has an external authentication method
119 return nil unless user.auth_source.authenticate(login, password)
121 return nil unless user.auth_source.authenticate(login, password)
120 else
122 else
121 # authentication with local password
123 # authentication with local password
122 return nil unless User.hash_password(password) == user.hashed_password
124 return nil unless User.hash_password(password) == user.hashed_password
123 end
125 end
124 else
126 else
125 # user is not yet registered, try to authenticate with available sources
127 # user is not yet registered, try to authenticate with available sources
126 attrs = AuthSource.authenticate(login, password)
128 attrs = AuthSource.authenticate(login, password)
127 if attrs
129 if attrs
128 user = new(attrs)
130 user = new(attrs)
129 user.login = login
131 user.login = login
130 user.language = Setting.default_language
132 user.language = Setting.default_language
131 if user.save
133 if user.save
132 user.reload
134 user.reload
133 logger.info("User '#{user.login}' created from external auth source: #{user.auth_source.type} - #{user.auth_source.name}") if logger && user.auth_source
135 logger.info("User '#{user.login}' created from external auth source: #{user.auth_source.type} - #{user.auth_source.name}") if logger && user.auth_source
134 end
136 end
135 end
137 end
136 end
138 end
137 user.update_attribute(:last_login_on, Time.now) if user && !user.new_record?
139 user.update_attribute(:last_login_on, Time.now) if user && !user.new_record?
138 user
140 user
139 rescue => text
141 rescue => text
140 raise text
142 raise text
141 end
143 end
142
144
143 # Returns the user who matches the given autologin +key+ or nil
145 # Returns the user who matches the given autologin +key+ or nil
144 def self.try_to_autologin(key)
146 def self.try_to_autologin(key)
145 tokens = Token.find_all_by_action_and_value('autologin', key)
147 tokens = Token.find_all_by_action_and_value('autologin', key)
146 # Make sure there's only 1 token that matches the key
148 # Make sure there's only 1 token that matches the key
147 if tokens.size == 1
149 if tokens.size == 1
148 token = tokens.first
150 token = tokens.first
149 if (token.created_on > Setting.autologin.to_i.day.ago) && token.user && token.user.active?
151 if (token.created_on > Setting.autologin.to_i.day.ago) && token.user && token.user.active?
150 token.user.update_attribute(:last_login_on, Time.now)
152 token.user.update_attribute(:last_login_on, Time.now)
151 token.user
153 token.user
152 end
154 end
153 end
155 end
154 end
156 end
155
157
156 # Return user's full name for display
158 # Return user's full name for display
157 def name(formatter = nil)
159 def name(formatter = nil)
158 if formatter
160 if formatter
159 eval('"' + (USER_FORMATS[formatter] || USER_FORMATS[:firstname_lastname]) + '"')
161 eval('"' + (USER_FORMATS[formatter] || USER_FORMATS[:firstname_lastname]) + '"')
160 else
162 else
161 @name ||= eval('"' + (USER_FORMATS[Setting.user_format] || USER_FORMATS[:firstname_lastname]) + '"')
163 @name ||= eval('"' + (USER_FORMATS[Setting.user_format] || USER_FORMATS[:firstname_lastname]) + '"')
162 end
164 end
163 end
165 end
164
166
165 def active?
167 def active?
166 self.status == STATUS_ACTIVE
168 self.status == STATUS_ACTIVE
167 end
169 end
168
170
169 def registered?
171 def registered?
170 self.status == STATUS_REGISTERED
172 self.status == STATUS_REGISTERED
171 end
173 end
172
174
173 def locked?
175 def locked?
174 self.status == STATUS_LOCKED
176 self.status == STATUS_LOCKED
175 end
177 end
176
178
177 def activate
179 def activate
178 self.status = STATUS_ACTIVE
180 self.status = STATUS_ACTIVE
179 end
181 end
180
182
181 def register
183 def register
182 self.status = STATUS_REGISTERED
184 self.status = STATUS_REGISTERED
183 end
185 end
184
186
185 def lock
187 def lock
186 self.status = STATUS_LOCKED
188 self.status = STATUS_LOCKED
187 end
189 end
188
190
189 def activate!
191 def activate!
190 update_attribute(:status, STATUS_ACTIVE)
192 update_attribute(:status, STATUS_ACTIVE)
191 end
193 end
192
194
193 def register!
195 def register!
194 update_attribute(:status, STATUS_REGISTERED)
196 update_attribute(:status, STATUS_REGISTERED)
195 end
197 end
196
198
197 def lock!
199 def lock!
198 update_attribute(:status, STATUS_LOCKED)
200 update_attribute(:status, STATUS_LOCKED)
199 end
201 end
200
202
201 def check_password?(clear_password)
203 def check_password?(clear_password)
202 if auth_source_id.present?
204 if auth_source_id.present?
203 auth_source.authenticate(self.login, clear_password)
205 auth_source.authenticate(self.login, clear_password)
204 else
206 else
205 User.hash_password(clear_password) == self.hashed_password
207 User.hash_password(clear_password) == self.hashed_password
206 end
208 end
207 end
209 end
208
210
209 # Does the backend storage allow this user to change their password?
211 # Does the backend storage allow this user to change their password?
210 def change_password_allowed?
212 def change_password_allowed?
211 return true if auth_source_id.blank?
213 return true if auth_source_id.blank?
212 return auth_source.allow_password_changes?
214 return auth_source.allow_password_changes?
213 end
215 end
214
216
215 # Generate and set a random password. Useful for automated user creation
217 # Generate and set a random password. Useful for automated user creation
216 # Based on Token#generate_token_value
218 # Based on Token#generate_token_value
217 #
219 #
218 def random_password
220 def random_password
219 chars = ("a".."z").to_a + ("A".."Z").to_a + ("0".."9").to_a
221 chars = ("a".."z").to_a + ("A".."Z").to_a + ("0".."9").to_a
220 password = ''
222 password = ''
221 40.times { |i| password << chars[rand(chars.size-1)] }
223 40.times { |i| password << chars[rand(chars.size-1)] }
222 self.password = password
224 self.password = password
223 self.password_confirmation = password
225 self.password_confirmation = password
224 self
226 self
225 end
227 end
226
228
227 def pref
229 def pref
228 self.preference ||= UserPreference.new(:user => self)
230 self.preference ||= UserPreference.new(:user => self)
229 end
231 end
230
232
231 def time_zone
233 def time_zone
232 @time_zone ||= (self.pref.time_zone.blank? ? nil : ActiveSupport::TimeZone[self.pref.time_zone])
234 @time_zone ||= (self.pref.time_zone.blank? ? nil : ActiveSupport::TimeZone[self.pref.time_zone])
233 end
235 end
234
236
235 def wants_comments_in_reverse_order?
237 def wants_comments_in_reverse_order?
236 self.pref[:comments_sorting] == 'desc'
238 self.pref[:comments_sorting] == 'desc'
237 end
239 end
238
240
239 # Return user's RSS key (a 40 chars long string), used to access feeds
241 # Return user's RSS key (a 40 chars long string), used to access feeds
240 def rss_key
242 def rss_key
241 token = self.rss_token || Token.create(:user => self, :action => 'feeds')
243 token = self.rss_token || Token.create(:user => self, :action => 'feeds')
242 token.value
244 token.value
243 end
245 end
244
246
245 # Return user's API key (a 40 chars long string), used to access the API
247 # Return user's API key (a 40 chars long string), used to access the API
246 def api_key
248 def api_key
247 token = self.api_token || self.create_api_token(:action => 'api')
249 token = self.api_token || self.create_api_token(:action => 'api')
248 token.value
250 token.value
249 end
251 end
250
252
251 # Return an array of project ids for which the user has explicitly turned mail notifications on
253 # Return an array of project ids for which the user has explicitly turned mail notifications on
252 def notified_projects_ids
254 def notified_projects_ids
253 @notified_projects_ids ||= memberships.select {|m| m.mail_notification?}.collect(&:project_id)
255 @notified_projects_ids ||= memberships.select {|m| m.mail_notification?}.collect(&:project_id)
254 end
256 end
255
257
256 def notified_project_ids=(ids)
258 def notified_project_ids=(ids)
257 Member.update_all("mail_notification = #{connection.quoted_false}", ['user_id = ?', id])
259 Member.update_all("mail_notification = #{connection.quoted_false}", ['user_id = ?', id])
258 Member.update_all("mail_notification = #{connection.quoted_true}", ['user_id = ? AND project_id IN (?)', id, ids]) if ids && !ids.empty?
260 Member.update_all("mail_notification = #{connection.quoted_true}", ['user_id = ? AND project_id IN (?)', id, ids]) if ids && !ids.empty?
259 @notified_projects_ids = nil
261 @notified_projects_ids = nil
260 notified_projects_ids
262 notified_projects_ids
261 end
263 end
262
264
263 # Only users that belong to more than 1 project can select projects for which they are notified
265 # Only users that belong to more than 1 project can select projects for which they are notified
264 def valid_notification_options
266 def valid_notification_options
265 # Note that @user.membership.size would fail since AR ignores
267 # Note that @user.membership.size would fail since AR ignores
266 # :include association option when doing a count
268 # :include association option when doing a count
267 if memberships.length < 1
269 if memberships.length < 1
268 MAIL_NOTIFICATION_OPTIONS.delete_if {|option| option.first == 'selected'}
270 MAIL_NOTIFICATION_OPTIONS.delete_if {|option| option.first == 'selected'}
269 else
271 else
270 MAIL_NOTIFICATION_OPTIONS
272 MAIL_NOTIFICATION_OPTIONS
271 end
273 end
272 end
274 end
273
275
274 # Find a user account by matching the exact login and then a case-insensitive
276 # Find a user account by matching the exact login and then a case-insensitive
275 # version. Exact matches will be given priority.
277 # version. Exact matches will be given priority.
276 def self.find_by_login(login)
278 def self.find_by_login(login)
277 # force string comparison to be case sensitive on MySQL
279 # force string comparison to be case sensitive on MySQL
278 type_cast = (ActiveRecord::Base.connection.adapter_name == 'MySQL') ? 'BINARY' : ''
280 type_cast = (ActiveRecord::Base.connection.adapter_name == 'MySQL') ? 'BINARY' : ''
279
281
280 # First look for an exact match
282 # First look for an exact match
281 user = first(:conditions => ["#{type_cast} login = ?", login])
283 user = first(:conditions => ["#{type_cast} login = ?", login])
282 # Fail over to case-insensitive if none was found
284 # Fail over to case-insensitive if none was found
283 user ||= first(:conditions => ["#{type_cast} LOWER(login) = ?", login.to_s.downcase])
285 user ||= first(:conditions => ["#{type_cast} LOWER(login) = ?", login.to_s.downcase])
284 end
286 end
285
287
286 def self.find_by_rss_key(key)
288 def self.find_by_rss_key(key)
287 token = Token.find_by_value(key)
289 token = Token.find_by_value(key)
288 token && token.user.active? ? token.user : nil
290 token && token.user.active? ? token.user : nil
289 end
291 end
290
292
291 def self.find_by_api_key(key)
293 def self.find_by_api_key(key)
292 token = Token.find_by_action_and_value('api', key)
294 token = Token.find_by_action_and_value('api', key)
293 token && token.user.active? ? token.user : nil
295 token && token.user.active? ? token.user : nil
294 end
296 end
295
297
296 # Makes find_by_mail case-insensitive
298 # Makes find_by_mail case-insensitive
297 def self.find_by_mail(mail)
299 def self.find_by_mail(mail)
298 find(:first, :conditions => ["LOWER(mail) = ?", mail.to_s.downcase])
300 find(:first, :conditions => ["LOWER(mail) = ?", mail.to_s.downcase])
299 end
301 end
300
302
301 def to_s
303 def to_s
302 name
304 name
303 end
305 end
304
306
305 # Returns the current day according to user's time zone
307 # Returns the current day according to user's time zone
306 def today
308 def today
307 if time_zone.nil?
309 if time_zone.nil?
308 Date.today
310 Date.today
309 else
311 else
310 Time.now.in_time_zone(time_zone).to_date
312 Time.now.in_time_zone(time_zone).to_date
311 end
313 end
312 end
314 end
313
315
314 def logged?
316 def logged?
315 true
317 true
316 end
318 end
317
319
318 def anonymous?
320 def anonymous?
319 !logged?
321 !logged?
320 end
322 end
321
323
322 # Return user's roles for project
324 # Return user's roles for project
323 def roles_for_project(project)
325 def roles_for_project(project)
324 roles = []
326 roles = []
325 # No role on archived projects
327 # No role on archived projects
326 return roles unless project && project.active?
328 return roles unless project && project.active?
327 if logged?
329 if logged?
328 # Find project membership
330 # Find project membership
329 membership = memberships.detect {|m| m.project_id == project.id}
331 membership = memberships.detect {|m| m.project_id == project.id}
330 if membership
332 if membership
331 roles = membership.roles
333 roles = membership.roles
332 else
334 else
333 @role_non_member ||= Role.non_member
335 @role_non_member ||= Role.non_member
334 roles << @role_non_member
336 roles << @role_non_member
335 end
337 end
336 else
338 else
337 @role_anonymous ||= Role.anonymous
339 @role_anonymous ||= Role.anonymous
338 roles << @role_anonymous
340 roles << @role_anonymous
339 end
341 end
340 roles
342 roles
341 end
343 end
342
344
343 # Return true if the user is a member of project
345 # Return true if the user is a member of project
344 def member_of?(project)
346 def member_of?(project)
345 !roles_for_project(project).detect {|role| role.member?}.nil?
347 !roles_for_project(project).detect {|role| role.member?}.nil?
346 end
348 end
347
349
348 # Return true if the user is allowed to do the specified action on a specific context
350 # Return true if the user is allowed to do the specified action on a specific context
349 # Action can be:
351 # Action can be:
350 # * a parameter-like Hash (eg. :controller => 'projects', :action => 'edit')
352 # * a parameter-like Hash (eg. :controller => 'projects', :action => 'edit')
351 # * a permission Symbol (eg. :edit_project)
353 # * a permission Symbol (eg. :edit_project)
352 # Context can be:
354 # Context can be:
353 # * a project : returns true if user is allowed to do the specified action on this project
355 # * a project : returns true if user is allowed to do the specified action on this project
354 # * a group of projects : returns true if user is allowed on every project
356 # * a group of projects : returns true if user is allowed on every project
355 # * nil with options[:global] set : check if user has at least one role allowed for this action,
357 # * nil with options[:global] set : check if user has at least one role allowed for this action,
356 # or falls back to Non Member / Anonymous permissions depending if the user is logged
358 # or falls back to Non Member / Anonymous permissions depending if the user is logged
357 def allowed_to?(action, context, options={})
359 def allowed_to?(action, context, options={})
358 if context && context.is_a?(Project)
360 if context && context.is_a?(Project)
359 # No action allowed on archived projects
361 # No action allowed on archived projects
360 return false unless context.active?
362 return false unless context.active?
361 # No action allowed on disabled modules
363 # No action allowed on disabled modules
362 return false unless context.allows_to?(action)
364 return false unless context.allows_to?(action)
363 # Admin users are authorized for anything else
365 # Admin users are authorized for anything else
364 return true if admin?
366 return true if admin?
365
367
366 roles = roles_for_project(context)
368 roles = roles_for_project(context)
367 return false unless roles
369 return false unless roles
368 roles.detect {|role| (context.is_public? || role.member?) && role.allowed_to?(action)}
370 roles.detect {|role| (context.is_public? || role.member?) && role.allowed_to?(action)}
369
371
370 elsif context && context.is_a?(Array)
372 elsif context && context.is_a?(Array)
371 # Authorize if user is authorized on every element of the array
373 # Authorize if user is authorized on every element of the array
372 context.map do |project|
374 context.map do |project|
373 allowed_to?(action,project,options)
375 allowed_to?(action,project,options)
374 end.inject do |memo,allowed|
376 end.inject do |memo,allowed|
375 memo && allowed
377 memo && allowed
376 end
378 end
377 elsif options[:global]
379 elsif options[:global]
378 # Admin users are always authorized
380 # Admin users are always authorized
379 return true if admin?
381 return true if admin?
380
382
381 # authorize if user has at least one role that has this permission
383 # authorize if user has at least one role that has this permission
382 roles = memberships.collect {|m| m.roles}.flatten.uniq
384 roles = memberships.collect {|m| m.roles}.flatten.uniq
383 roles.detect {|r| r.allowed_to?(action)} || (self.logged? ? Role.non_member.allowed_to?(action) : Role.anonymous.allowed_to?(action))
385 roles.detect {|r| r.allowed_to?(action)} || (self.logged? ? Role.non_member.allowed_to?(action) : Role.anonymous.allowed_to?(action))
384 else
386 else
385 false
387 false
386 end
388 end
387 end
389 end
388
390
389 # Is the user allowed to do the specified action on any project?
391 # Is the user allowed to do the specified action on any project?
390 # See allowed_to? for the actions and valid options.
392 # See allowed_to? for the actions and valid options.
391 def allowed_to_globally?(action, options)
393 def allowed_to_globally?(action, options)
392 allowed_to?(action, nil, options.reverse_merge(:global => true))
394 allowed_to?(action, nil, options.reverse_merge(:global => true))
393 end
395 end
394
396
395 safe_attributes 'login',
397 safe_attributes 'login',
396 'firstname',
398 'firstname',
397 'lastname',
399 'lastname',
398 'mail',
400 'mail',
399 'mail_notification',
401 'mail_notification',
400 'language',
402 'language',
401 'custom_field_values',
403 'custom_field_values',
402 'custom_fields',
404 'custom_fields',
403 'identity_url'
405 'identity_url'
404
406
405 safe_attributes 'status',
407 safe_attributes 'status',
406 'auth_source_id',
408 'auth_source_id',
407 :if => lambda {|user, current_user| current_user.admin?}
409 :if => lambda {|user, current_user| current_user.admin?}
408
410
409 safe_attributes 'group_ids',
411 safe_attributes 'group_ids',
410 :if => lambda {|user, current_user| current_user.admin? && !user.new_record?}
412 :if => lambda {|user, current_user| current_user.admin? && !user.new_record?}
411
413
412 # Utility method to help check if a user should be notified about an
414 # Utility method to help check if a user should be notified about an
413 # event.
415 # event.
414 #
416 #
415 # TODO: only supports Issue events currently
417 # TODO: only supports Issue events currently
416 def notify_about?(object)
418 def notify_about?(object)
417 case mail_notification
419 case mail_notification
418 when 'all'
420 when 'all'
419 true
421 true
420 when 'selected'
422 when 'selected'
421 # Handled by the Project
423 # Handled by the Project
422 when 'none'
424 when 'none'
423 false
425 false
424 when 'only_my_events'
426 when 'only_my_events'
425 if object.is_a?(Issue) && (object.author == self || object.assigned_to == self)
427 if object.is_a?(Issue) && (object.author == self || object.assigned_to == self)
426 true
428 true
427 else
429 else
428 false
430 false
429 end
431 end
430 when 'only_assigned'
432 when 'only_assigned'
431 if object.is_a?(Issue) && object.assigned_to == self
433 if object.is_a?(Issue) && object.assigned_to == self
432 true
434 true
433 else
435 else
434 false
436 false
435 end
437 end
436 when 'only_owner'
438 when 'only_owner'
437 if object.is_a?(Issue) && object.author == self
439 if object.is_a?(Issue) && object.author == self
438 true
440 true
439 else
441 else
440 false
442 false
441 end
443 end
442 else
444 else
443 false
445 false
444 end
446 end
445 end
447 end
446
448
447 def self.current=(user)
449 def self.current=(user)
448 @current_user = user
450 @current_user = user
449 end
451 end
450
452
451 def self.current
453 def self.current
452 @current_user ||= User.anonymous
454 @current_user ||= User.anonymous
453 end
455 end
454
456
455 # Returns the anonymous user. If the anonymous user does not exist, it is created. There can be only
457 # Returns the anonymous user. If the anonymous user does not exist, it is created. There can be only
456 # one anonymous user per database.
458 # one anonymous user per database.
457 def self.anonymous
459 def self.anonymous
458 anonymous_user = AnonymousUser.find(:first)
460 anonymous_user = AnonymousUser.find(:first)
459 if anonymous_user.nil?
461 if anonymous_user.nil?
460 anonymous_user = AnonymousUser.create(:lastname => 'Anonymous', :firstname => '', :mail => '', :login => '', :status => 0)
462 anonymous_user = AnonymousUser.create(:lastname => 'Anonymous', :firstname => '', :mail => '', :login => '', :status => 0)
461 raise 'Unable to create the anonymous user.' if anonymous_user.new_record?
463 raise 'Unable to create the anonymous user.' if anonymous_user.new_record?
462 end
464 end
463 anonymous_user
465 anonymous_user
464 end
466 end
465
467
466 protected
468 protected
467
469
468 def validate
470 def validate
469 # Password length validation based on setting
471 # Password length validation based on setting
470 if !password.nil? && password.size < Setting.password_min_length.to_i
472 if !password.nil? && password.size < Setting.password_min_length.to_i
471 errors.add(:password, :too_short, :count => Setting.password_min_length.to_i)
473 errors.add(:password, :too_short, :count => Setting.password_min_length.to_i)
472 end
474 end
473 end
475 end
474
476
475 private
477 private
476
478
479 # Removes references that are not handled by associations
480 # Things that are not deleted are reassociated with the anonymous user
481 def remove_references_before_destroy
482 return if self.id.nil?
483
484 substitute = User.anonymous
485 Attachment.update_all ['author_id = ?', substitute.id], ['author_id = ?', id]
486 Comment.update_all ['author_id = ?', substitute.id], ['author_id = ?', id]
487 Issue.update_all ['author_id = ?', substitute.id], ['author_id = ?', id]
488 Issue.update_all 'assigned_to_id = NULL', ['assigned_to_id = ?', id]
489 Journal.update_all ['user_id = ?', substitute.id], ['user_id = ?', id]
490 JournalDetail.update_all ['old_value = ?', substitute.id.to_s], ["property = 'attr' AND prop_key = 'assigned_to_id' AND old_value = ?", id.to_s]
491 JournalDetail.update_all ['value = ?', substitute.id.to_s], ["property = 'attr' AND prop_key = 'assigned_to_id' AND value = ?", id.to_s]
492 Message.update_all ['author_id = ?', substitute.id], ['author_id = ?', id]
493 News.update_all ['author_id = ?', substitute.id], ['author_id = ?', id]
494 # Remove private queries and keep public ones
495 Query.delete_all ['user_id = ? AND is_public = ?', id, false]
496 Query.update_all ['user_id = ?', substitute.id], ['user_id = ?', id]
497 TimeEntry.update_all ['user_id = ?', substitute.id], ['user_id = ?', id]
498 Token.delete_all ['user_id = ?', id]
499 Watcher.delete_all ['user_id = ?', id]
500 WikiContent.update_all ['author_id = ?', substitute.id], ['author_id = ?', id]
501 WikiContent::Version.update_all ['author_id = ?', substitute.id], ['author_id = ?', id]
502 end
503
477 # Return password digest
504 # Return password digest
478 def self.hash_password(clear_password)
505 def self.hash_password(clear_password)
479 Digest::SHA1.hexdigest(clear_password || "")
506 Digest::SHA1.hexdigest(clear_password || "")
480 end
507 end
481 end
508 end
482
509
483 class AnonymousUser < User
510 class AnonymousUser < User
484
511
485 def validate_on_create
512 def validate_on_create
486 # There should be only one AnonymousUser in the database
513 # There should be only one AnonymousUser in the database
487 errors.add_to_base 'An anonymous user already exists.' if AnonymousUser.find(:first)
514 errors.add_to_base 'An anonymous user already exists.' if AnonymousUser.find(:first)
488 end
515 end
489
516
490 def available_custom_fields
517 def available_custom_fields
491 []
518 []
492 end
519 end
493
520
494 # Overrides a few properties
521 # Overrides a few properties
495 def logged?; false end
522 def logged?; false end
496 def admin; false end
523 def admin; false end
497 def name(*args); I18n.t(:label_user_anonymous) end
524 def name(*args); I18n.t(:label_user_anonymous) end
498 def mail; nil end
525 def mail; nil end
499 def time_zone; nil end
526 def time_zone; nil end
500 def rss_key; nil end
527 def rss_key; nil end
528
529 # Anonymous user can not be destroyed
530 def destroy
531 false
532 end
501 end
533 end
@@ -1,160 +1,159
1 # redMine - project management software
1 # redMine - project management software
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
2 # Copyright (C) 2006-2007 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 RepositoryTest < ActiveSupport::TestCase
20 class RepositoryTest < ActiveSupport::TestCase
21 fixtures :projects,
21 fixtures :projects,
22 :trackers,
22 :trackers,
23 :projects_trackers,
23 :projects_trackers,
24 :enabled_modules,
24 :enabled_modules,
25 :repositories,
25 :repositories,
26 :issues,
26 :issues,
27 :issue_statuses,
27 :issue_statuses,
28 :issue_categories,
28 :issue_categories,
29 :changesets,
29 :changesets,
30 :changes,
30 :changes,
31 :users,
31 :users,
32 :members,
32 :members,
33 :member_roles,
33 :member_roles,
34 :roles,
34 :roles,
35 :enumerations
35 :enumerations
36
36
37 def setup
37 def setup
38 @repository = Project.find(1).repository
38 @repository = Project.find(1).repository
39 end
39 end
40
40
41 def test_create
41 def test_create
42 repository = Repository::Subversion.new(:project => Project.find(3))
42 repository = Repository::Subversion.new(:project => Project.find(3))
43 assert !repository.save
43 assert !repository.save
44
44
45 repository.url = "svn://localhost"
45 repository.url = "svn://localhost"
46 assert repository.save
46 assert repository.save
47 repository.reload
47 repository.reload
48
48
49 project = Project.find(3)
49 project = Project.find(3)
50 assert_equal repository, project.repository
50 assert_equal repository, project.repository
51 end
51 end
52
52
53 def test_destroy
53 def test_destroy
54 changesets = Changeset.count(:all, :conditions => "repository_id = 10")
54 changesets = Changeset.count(:all, :conditions => "repository_id = 10")
55 changes = Change.count(:all, :conditions => "repository_id = 10", :include => :changeset)
55 changes = Change.count(:all, :conditions => "repository_id = 10", :include => :changeset)
56 assert_difference 'Changeset.count', -changesets do
56 assert_difference 'Changeset.count', -changesets do
57 assert_difference 'Change.count', -changes do
57 assert_difference 'Change.count', -changes do
58 Repository.find(10).destroy
58 Repository.find(10).destroy
59 end
59 end
60 end
60 end
61 end
61 end
62
62
63 def test_should_not_create_with_disabled_scm
63 def test_should_not_create_with_disabled_scm
64 # disable Subversion
64 # disable Subversion
65 Setting.enabled_scm = ['Darcs', 'Git']
65 with_settings :enabled_scm => ['Darcs', 'Git'] do
66 repository = Repository::Subversion.new(:project => Project.find(3), :url => "svn://localhost")
66 repository = Repository::Subversion.new(:project => Project.find(3), :url => "svn://localhost")
67 assert !repository.save
67 assert !repository.save
68 assert_equal I18n.translate('activerecord.errors.messages.invalid'), repository.errors.on(:type)
68 assert_equal I18n.translate('activerecord.errors.messages.invalid'), repository.errors.on(:type)
69 # re-enable Subversion for following tests
69 end
70 Setting.delete_all
71 end
70 end
72
71
73 def test_scan_changesets_for_issue_ids
72 def test_scan_changesets_for_issue_ids
74 Setting.default_language = 'en'
73 Setting.default_language = 'en'
75 Setting.notified_events = ['issue_added','issue_updated']
74 Setting.notified_events = ['issue_added','issue_updated']
76
75
77 # choosing a status to apply to fix issues
76 # choosing a status to apply to fix issues
78 Setting.commit_fix_status_id = IssueStatus.find(:first, :conditions => ["is_closed = ?", true]).id
77 Setting.commit_fix_status_id = IssueStatus.find(:first, :conditions => ["is_closed = ?", true]).id
79 Setting.commit_fix_done_ratio = "90"
78 Setting.commit_fix_done_ratio = "90"
80 Setting.commit_ref_keywords = 'refs , references, IssueID'
79 Setting.commit_ref_keywords = 'refs , references, IssueID'
81 Setting.commit_fix_keywords = 'fixes , closes'
80 Setting.commit_fix_keywords = 'fixes , closes'
82 Setting.default_language = 'en'
81 Setting.default_language = 'en'
83 ActionMailer::Base.deliveries.clear
82 ActionMailer::Base.deliveries.clear
84
83
85 # make sure issue 1 is not already closed
84 # make sure issue 1 is not already closed
86 fixed_issue = Issue.find(1)
85 fixed_issue = Issue.find(1)
87 assert !fixed_issue.status.is_closed?
86 assert !fixed_issue.status.is_closed?
88 old_status = fixed_issue.status
87 old_status = fixed_issue.status
89
88
90 Repository.scan_changesets_for_issue_ids
89 Repository.scan_changesets_for_issue_ids
91 assert_equal [101, 102], Issue.find(3).changeset_ids
90 assert_equal [101, 102], Issue.find(3).changeset_ids
92
91
93 # fixed issues
92 # fixed issues
94 fixed_issue.reload
93 fixed_issue.reload
95 assert fixed_issue.status.is_closed?
94 assert fixed_issue.status.is_closed?
96 assert_equal 90, fixed_issue.done_ratio
95 assert_equal 90, fixed_issue.done_ratio
97 assert_equal [101], fixed_issue.changeset_ids
96 assert_equal [101], fixed_issue.changeset_ids
98
97
99 # issue change
98 # issue change
100 journal = fixed_issue.journals.find(:first, :order => 'created_on desc')
99 journal = fixed_issue.journals.find(:first, :order => 'created_on desc')
101 assert_equal User.find_by_login('dlopper'), journal.user
100 assert_equal User.find_by_login('dlopper'), journal.user
102 assert_equal 'Applied in changeset r2.', journal.notes
101 assert_equal 'Applied in changeset r2.', journal.notes
103
102
104 # 2 email notifications
103 # 2 email notifications
105 assert_equal 2, ActionMailer::Base.deliveries.size
104 assert_equal 2, ActionMailer::Base.deliveries.size
106 mail = ActionMailer::Base.deliveries.first
105 mail = ActionMailer::Base.deliveries.first
107 assert_kind_of TMail::Mail, mail
106 assert_kind_of TMail::Mail, mail
108 assert mail.subject.starts_with?("[#{fixed_issue.project.name} - #{fixed_issue.tracker.name} ##{fixed_issue.id}]")
107 assert mail.subject.starts_with?("[#{fixed_issue.project.name} - #{fixed_issue.tracker.name} ##{fixed_issue.id}]")
109 assert mail.body.include?("Status changed from #{old_status} to #{fixed_issue.status}")
108 assert mail.body.include?("Status changed from #{old_status} to #{fixed_issue.status}")
110
109
111 # ignoring commits referencing an issue of another project
110 # ignoring commits referencing an issue of another project
112 assert_equal [], Issue.find(4).changesets
111 assert_equal [], Issue.find(4).changesets
113 end
112 end
114
113
115 def test_for_changeset_comments_strip
114 def test_for_changeset_comments_strip
116 repository = Repository::Mercurial.create( :project => Project.find( 4 ), :url => '/foo/bar/baz' )
115 repository = Repository::Mercurial.create( :project => Project.find( 4 ), :url => '/foo/bar/baz' )
117 comment = <<-COMMENT
116 comment = <<-COMMENT
118 This is a loooooooooooooooooooooooooooong comment
117 This is a loooooooooooooooooooooooooooong comment
119
118
120
119
121 COMMENT
120 COMMENT
122 changeset = Changeset.new(
121 changeset = Changeset.new(
123 :comments => comment, :commit_date => Time.now, :revision => 0, :scmid => 'f39b7922fb3c',
122 :comments => comment, :commit_date => Time.now, :revision => 0, :scmid => 'f39b7922fb3c',
124 :committer => 'foo <foo@example.com>', :committed_on => Time.now, :repository => repository )
123 :committer => 'foo <foo@example.com>', :committed_on => Time.now, :repository => repository )
125 assert( changeset.save )
124 assert( changeset.save )
126 assert_not_equal( comment, changeset.comments )
125 assert_not_equal( comment, changeset.comments )
127 assert_equal( 'This is a loooooooooooooooooooooooooooong comment', changeset.comments )
126 assert_equal( 'This is a loooooooooooooooooooooooooooong comment', changeset.comments )
128 end
127 end
129
128
130 def test_for_urls_strip
129 def test_for_urls_strip
131 repository = Repository::Cvs.create(:project => Project.find(4), :url => ' :pserver:login:password@host:/path/to/the/repository',
130 repository = Repository::Cvs.create(:project => Project.find(4), :url => ' :pserver:login:password@host:/path/to/the/repository',
132 :root_url => 'foo ')
131 :root_url => 'foo ')
133 assert repository.save
132 assert repository.save
134 repository.reload
133 repository.reload
135 assert_equal ':pserver:login:password@host:/path/to/the/repository', repository.url
134 assert_equal ':pserver:login:password@host:/path/to/the/repository', repository.url
136 assert_equal 'foo', repository.root_url
135 assert_equal 'foo', repository.root_url
137 end
136 end
138
137
139 def test_manual_user_mapping
138 def test_manual_user_mapping
140 assert_no_difference "Changeset.count(:conditions => 'user_id <> 2')" do
139 assert_no_difference "Changeset.count(:conditions => 'user_id <> 2')" do
141 c = Changeset.create!(:repository => @repository, :committer => 'foo', :committed_on => Time.now, :revision => 100, :comments => 'Committed by foo.')
140 c = Changeset.create!(:repository => @repository, :committer => 'foo', :committed_on => Time.now, :revision => 100, :comments => 'Committed by foo.')
142 assert_nil c.user
141 assert_nil c.user
143 @repository.committer_ids = {'foo' => '2'}
142 @repository.committer_ids = {'foo' => '2'}
144 assert_equal User.find(2), c.reload.user
143 assert_equal User.find(2), c.reload.user
145 # committer is now mapped
144 # committer is now mapped
146 c = Changeset.create!(:repository => @repository, :committer => 'foo', :committed_on => Time.now, :revision => 101, :comments => 'Another commit by foo.')
145 c = Changeset.create!(:repository => @repository, :committer => 'foo', :committed_on => Time.now, :revision => 101, :comments => 'Another commit by foo.')
147 assert_equal User.find(2), c.user
146 assert_equal User.find(2), c.user
148 end
147 end
149 end
148 end
150
149
151 def test_auto_user_mapping_by_username
150 def test_auto_user_mapping_by_username
152 c = Changeset.create!(:repository => @repository, :committer => 'jsmith', :committed_on => Time.now, :revision => 100, :comments => 'Committed by john.')
151 c = Changeset.create!(:repository => @repository, :committer => 'jsmith', :committed_on => Time.now, :revision => 100, :comments => 'Committed by john.')
153 assert_equal User.find(2), c.user
152 assert_equal User.find(2), c.user
154 end
153 end
155
154
156 def test_auto_user_mapping_by_email
155 def test_auto_user_mapping_by_email
157 c = Changeset.create!(:repository => @repository, :committer => 'john <jsmith@somenet.foo>', :committed_on => Time.now, :revision => 100, :comments => 'Committed by john.')
156 c = Changeset.create!(:repository => @repository, :committer => 'john <jsmith@somenet.foo>', :committed_on => Time.now, :revision => 100, :comments => 'Committed by john.')
158 assert_equal User.find(2), c.user
157 assert_equal User.find(2), c.user
159 end
158 end
160 end
159 end
@@ -1,532 +1,736
1 # redMine - project management software
1 # redMine - project management software
2 # Copyright (C) 2006 Jean-Philippe Lang
2 # Copyright (C) 2006 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 require File.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
44
45 def test_create
45 def test_create
46 user = User.new(:firstname => "new", :lastname => "user", :mail => "newuser@somenet.foo")
46 user = User.new(:firstname => "new", :lastname => "user", :mail => "newuser@somenet.foo")
47
47
48 user.login = "jsmith"
48 user.login = "jsmith"
49 user.password, user.password_confirmation = "password", "password"
49 user.password, user.password_confirmation = "password", "password"
50 # login uniqueness
50 # login uniqueness
51 assert !user.save
51 assert !user.save
52 assert_equal 1, user.errors.count
52 assert_equal 1, user.errors.count
53
53
54 user.login = "newuser"
54 user.login = "newuser"
55 user.password, user.password_confirmation = "passwd", "password"
55 user.password, user.password_confirmation = "passwd", "password"
56 # password confirmation
56 # password confirmation
57 assert !user.save
57 assert !user.save
58 assert_equal 1, user.errors.count
58 assert_equal 1, user.errors.count
59
59
60 user.password, user.password_confirmation = "password", "password"
60 user.password, user.password_confirmation = "password", "password"
61 assert user.save
61 assert user.save
62 end
62 end
63
63
64 context "User#before_create" do
64 context "User#before_create" do
65 should "set the mail_notification to the default Setting" do
65 should "set the mail_notification to the default Setting" do
66 @user1 = User.generate_with_protected!
66 @user1 = User.generate_with_protected!
67 assert_equal 'only_my_events', @user1.mail_notification
67 assert_equal 'only_my_events', @user1.mail_notification
68
68
69 with_settings :default_notification_option => 'all' do
69 with_settings :default_notification_option => 'all' do
70 @user2 = User.generate_with_protected!
70 @user2 = User.generate_with_protected!
71 assert_equal 'all', @user2.mail_notification
71 assert_equal 'all', @user2.mail_notification
72 end
72 end
73 end
73 end
74 end
74 end
75
75
76 context "User.login" do
76 context "User.login" do
77 should "be case-insensitive." do
77 should "be case-insensitive." do
78 u = User.new(:firstname => "new", :lastname => "user", :mail => "newuser@somenet.foo")
78 u = User.new(:firstname => "new", :lastname => "user", :mail => "newuser@somenet.foo")
79 u.login = 'newuser'
79 u.login = 'newuser'
80 u.password, u.password_confirmation = "password", "password"
80 u.password, u.password_confirmation = "password", "password"
81 assert u.save
81 assert u.save
82
82
83 u = User.new(:firstname => "Similar", :lastname => "User", :mail => "similaruser@somenet.foo")
83 u = User.new(:firstname => "Similar", :lastname => "User", :mail => "similaruser@somenet.foo")
84 u.login = 'NewUser'
84 u.login = 'NewUser'
85 u.password, u.password_confirmation = "password", "password"
85 u.password, u.password_confirmation = "password", "password"
86 assert !u.save
86 assert !u.save
87 assert_equal I18n.translate('activerecord.errors.messages.taken'), u.errors.on(:login)
87 assert_equal I18n.translate('activerecord.errors.messages.taken'), u.errors.on(:login)
88 end
88 end
89 end
89 end
90
90
91 def test_mail_uniqueness_should_not_be_case_sensitive
91 def test_mail_uniqueness_should_not_be_case_sensitive
92 u = User.new(:firstname => "new", :lastname => "user", :mail => "newuser@somenet.foo")
92 u = User.new(:firstname => "new", :lastname => "user", :mail => "newuser@somenet.foo")
93 u.login = 'newuser1'
93 u.login = 'newuser1'
94 u.password, u.password_confirmation = "password", "password"
94 u.password, u.password_confirmation = "password", "password"
95 assert u.save
95 assert u.save
96
96
97 u = User.new(:firstname => "new", :lastname => "user", :mail => "newUser@Somenet.foo")
97 u = User.new(:firstname => "new", :lastname => "user", :mail => "newUser@Somenet.foo")
98 u.login = 'newuser2'
98 u.login = 'newuser2'
99 u.password, u.password_confirmation = "password", "password"
99 u.password, u.password_confirmation = "password", "password"
100 assert !u.save
100 assert !u.save
101 assert_equal I18n.translate('activerecord.errors.messages.taken'), u.errors.on(:mail)
101 assert_equal I18n.translate('activerecord.errors.messages.taken'), u.errors.on(:mail)
102 end
102 end
103
103
104 def test_update
104 def test_update
105 assert_equal "admin", @admin.login
105 assert_equal "admin", @admin.login
106 @admin.login = "john"
106 @admin.login = "john"
107 assert @admin.save, @admin.errors.full_messages.join("; ")
107 assert @admin.save, @admin.errors.full_messages.join("; ")
108 @admin.reload
108 @admin.reload
109 assert_equal "john", @admin.login
109 assert_equal "john", @admin.login
110 end
110 end
111
111
112 def test_destroy
112 def test_destroy_should_delete_members_and_roles
113 members = Member.find_all_by_user_id(2)
114 ms = members.size
115 rs = members.collect(&:roles).flatten.size
116
117 assert_difference 'Member.count', - ms do
118 assert_difference 'MemberRole.count', - rs do
113 User.find(2).destroy
119 User.find(2).destroy
120 end
121 end
122
114 assert_nil User.find_by_id(2)
123 assert_nil User.find_by_id(2)
115 assert Member.find_all_by_user_id(2).empty?
124 assert Member.find_all_by_user_id(2).empty?
116 end
125 end
117
126
127 def test_destroy_should_update_attachments
128 attachment = Attachment.create!(:container => Project.find(1),
129 :file => uploaded_test_file("testfile.txt", "text/plain"),
130 :author_id => 2)
131
132 User.find(2).destroy
133 assert_nil User.find_by_id(2)
134 assert_equal User.anonymous, attachment.reload.author
135 end
136
137 def test_destroy_should_update_comments
138 comment = Comment.create!(
139 :commented => News.create!(:project_id => 1, :author_id => 1, :title => 'foo', :description => 'foo'),
140 :author => User.find(2),
141 :comments => 'foo'
142 )
143
144 User.find(2).destroy
145 assert_nil User.find_by_id(2)
146 assert_equal User.anonymous, comment.reload.author
147 end
148
149 def test_destroy_should_update_issues
150 issue = Issue.create!(:project_id => 1, :author_id => 2, :tracker_id => 1, :subject => 'foo')
151
152 User.find(2).destroy
153 assert_nil User.find_by_id(2)
154 assert_equal User.anonymous, issue.reload.author
155 end
156
157 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)
159
160 User.find(2).destroy
161 assert_nil User.find_by_id(2)
162 assert_nil issue.reload.assigned_to
163 end
164
165 def test_destroy_should_update_journals
166 issue = Issue.create!(:project_id => 1, :author_id => 2, :tracker_id => 1, :subject => 'foo')
167 issue.init_journal(User.find(2), "update")
168 issue.save!
169
170 User.find(2).destroy
171 assert_nil User.find_by_id(2)
172 assert_equal User.anonymous, issue.journals.first.reload.user
173 end
174
175 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)
177 issue.init_journal(User.find(1), "update")
178 issue.assigned_to_id = nil
179 assert_difference 'JournalDetail.count' do
180 issue.save!
181 end
182 journal_detail = JournalDetail.first(:order => 'id DESC')
183 assert_equal '2', journal_detail.old_value
184
185 User.find(2).destroy
186 assert_nil User.find_by_id(2)
187 assert_equal User.anonymous.id.to_s, journal_detail.reload.old_value
188 end
189
190 def test_destroy_should_update_journal_details_value
191 issue = Issue.create!(:project_id => 1, :author_id => 1, :tracker_id => 1, :subject => 'foo')
192 issue.init_journal(User.find(1), "update")
193 issue.assigned_to_id = 2
194 assert_difference 'JournalDetail.count' do
195 issue.save!
196 end
197 journal_detail = JournalDetail.first(:order => 'id DESC')
198 assert_equal '2', journal_detail.value
199
200 User.find(2).destroy
201 assert_nil User.find_by_id(2)
202 assert_equal User.anonymous.id.to_s, journal_detail.reload.value
203 end
204
205 def test_destroy_should_update_messages
206 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')
208
209 User.find(2).destroy
210 assert_nil User.find_by_id(2)
211 assert_equal User.anonymous, message.reload.author
212 end
213
214 def test_destroy_should_update_news
215 news = News.create!(:project_id => 1, :author_id => 2, :title => 'foo', :description => 'foo')
216
217 User.find(2).destroy
218 assert_nil User.find_by_id(2)
219 assert_equal User.anonymous, news.reload.author
220 end
221
222 def test_destroy_should_delete_private_queries
223 query = Query.new(:name => 'foo', :is_public => false)
224 query.project_id = 1
225 query.user_id = 2
226 query.save!
227
228 User.find(2).destroy
229 assert_nil User.find_by_id(2)
230 assert_nil Query.find_by_id(query.id)
231 end
232
233 def test_destroy_should_update_public_queries
234 query = Query.new(:name => 'foo', :is_public => true)
235 query.project_id = 1
236 query.user_id = 2
237 query.save!
238
239 User.find(2).destroy
240 assert_nil User.find_by_id(2)
241 assert_equal User.anonymous, query.reload.user
242 end
243
244 def test_destroy_should_update_time_entries
245 entry = TimeEntry.new(:hours => '2', :spent_on => Date.today, :activity => TimeEntryActivity.create!(:name => 'foo'))
246 entry.project_id = 1
247 entry.user_id = 2
248 entry.save!
249
250 User.find(2).destroy
251 assert_nil User.find_by_id(2)
252 assert_equal User.anonymous, entry.reload.user
253 end
254
255 def test_destroy_should_delete_tokens
256 token = Token.create!(:user_id => 2, :value => 'foo')
257
258 User.find(2).destroy
259 assert_nil User.find_by_id(2)
260 assert_nil Token.find_by_id(token.id)
261 end
262
263 def test_destroy_should_delete_watchers
264 issue = Issue.create!(:project_id => 1, :author_id => 1, :tracker_id => 1, :subject => 'foo')
265 watcher = Watcher.create!(:user_id => 2, :watchable => issue)
266
267 User.find(2).destroy
268 assert_nil User.find_by_id(2)
269 assert_nil Watcher.find_by_id(watcher.id)
270 end
271
272 def test_destroy_should_update_wiki_contents
273 wiki_content = WikiContent.create!(
274 :text => 'foo',
275 :author_id => 2,
276 :page => WikiPage.create!(:title => 'Foo', :wiki => Wiki.create!(:project_id => 1, :start_page => 'Start'))
277 )
278 wiki_content.text = 'bar'
279 assert_difference 'WikiContent::Version.count' do
280 wiki_content.save!
281 end
282
283 User.find(2).destroy
284 assert_nil User.find_by_id(2)
285 assert_equal User.anonymous, wiki_content.reload.author
286 wiki_content.versions.each do |version|
287 assert_equal User.anonymous, version.reload.author
288 end
289 end
290
291 def test_destroy_should_nullify_issue_categories
292 category = IssueCategory.create!(:project_id => 1, :assigned_to_id => 2, :name => 'foo')
293
294 User.find(2).destroy
295 assert_nil User.find_by_id(2)
296 assert_nil category.reload.assigned_to_id
297 end
298
299 def test_destroy_should_nullify_changesets
300 changeset = Changeset.create!(
301 :repository => Repository::Subversion.create!(
302 :project_id => 1,
303 :url => 'file:///var/svn'
304 ),
305 :revision => '12',
306 :committed_on => Time.now,
307 :committer => 'jsmith'
308 )
309 assert_equal 2, changeset.user_id
310
311 User.find(2).destroy
312 assert_nil User.find_by_id(2)
313 assert_nil changeset.reload.user_id
314 end
315
316 def test_anonymous_user_should_not_be_destroyable
317 assert_no_difference 'User.count' do
318 assert_equal false, User.anonymous.destroy
319 end
320 end
321
118 def test_validate_login_presence
322 def test_validate_login_presence
119 @admin.login = ""
323 @admin.login = ""
120 assert !@admin.save
324 assert !@admin.save
121 assert_equal 1, @admin.errors.count
325 assert_equal 1, @admin.errors.count
122 end
326 end
123
327
124 def test_validate_mail_notification_inclusion
328 def test_validate_mail_notification_inclusion
125 u = User.new
329 u = User.new
126 u.mail_notification = 'foo'
330 u.mail_notification = 'foo'
127 u.save
331 u.save
128 assert_not_nil u.errors.on(:mail_notification)
332 assert_not_nil u.errors.on(:mail_notification)
129 end
333 end
130
334
131 context "User#try_to_login" do
335 context "User#try_to_login" do
132 should "fall-back to case-insensitive if user login is not found as-typed." do
336 should "fall-back to case-insensitive if user login is not found as-typed." do
133 user = User.try_to_login("AdMin", "admin")
337 user = User.try_to_login("AdMin", "admin")
134 assert_kind_of User, user
338 assert_kind_of User, user
135 assert_equal "admin", user.login
339 assert_equal "admin", user.login
136 end
340 end
137
341
138 should "select the exact matching user first" do
342 should "select the exact matching user first" do
139 case_sensitive_user = User.generate_with_protected!(:login => 'changed', :password => 'admin', :password_confirmation => 'admin')
343 case_sensitive_user = User.generate_with_protected!(:login => 'changed', :password => 'admin', :password_confirmation => 'admin')
140 # bypass validations to make it appear like existing data
344 # bypass validations to make it appear like existing data
141 case_sensitive_user.update_attribute(:login, 'ADMIN')
345 case_sensitive_user.update_attribute(:login, 'ADMIN')
142
346
143 user = User.try_to_login("ADMIN", "admin")
347 user = User.try_to_login("ADMIN", "admin")
144 assert_kind_of User, user
348 assert_kind_of User, user
145 assert_equal "ADMIN", user.login
349 assert_equal "ADMIN", user.login
146
350
147 end
351 end
148 end
352 end
149
353
150 def test_password
354 def test_password
151 user = User.try_to_login("admin", "admin")
355 user = User.try_to_login("admin", "admin")
152 assert_kind_of User, user
356 assert_kind_of User, user
153 assert_equal "admin", user.login
357 assert_equal "admin", user.login
154 user.password = "hello"
358 user.password = "hello"
155 assert user.save
359 assert user.save
156
360
157 user = User.try_to_login("admin", "hello")
361 user = User.try_to_login("admin", "hello")
158 assert_kind_of User, user
362 assert_kind_of User, user
159 assert_equal "admin", user.login
363 assert_equal "admin", user.login
160 assert_equal User.hash_password("hello"), user.hashed_password
364 assert_equal User.hash_password("hello"), user.hashed_password
161 end
365 end
162
366
163 def test_name_format
367 def test_name_format
164 assert_equal 'Smith, John', @jsmith.name(:lastname_coma_firstname)
368 assert_equal 'Smith, John', @jsmith.name(:lastname_coma_firstname)
165 Setting.user_format = :firstname_lastname
369 Setting.user_format = :firstname_lastname
166 assert_equal 'John Smith', @jsmith.reload.name
370 assert_equal 'John Smith', @jsmith.reload.name
167 Setting.user_format = :username
371 Setting.user_format = :username
168 assert_equal 'jsmith', @jsmith.reload.name
372 assert_equal 'jsmith', @jsmith.reload.name
169 end
373 end
170
374
171 def test_lock
375 def test_lock
172 user = User.try_to_login("jsmith", "jsmith")
376 user = User.try_to_login("jsmith", "jsmith")
173 assert_equal @jsmith, user
377 assert_equal @jsmith, user
174
378
175 @jsmith.status = User::STATUS_LOCKED
379 @jsmith.status = User::STATUS_LOCKED
176 assert @jsmith.save
380 assert @jsmith.save
177
381
178 user = User.try_to_login("jsmith", "jsmith")
382 user = User.try_to_login("jsmith", "jsmith")
179 assert_equal nil, user
383 assert_equal nil, user
180 end
384 end
181
385
182 if ldap_configured?
386 if ldap_configured?
183 context "#try_to_login using LDAP" do
387 context "#try_to_login using LDAP" do
184 context "with failed connection to the LDAP server" do
388 context "with failed connection to the LDAP server" do
185 should "return nil" do
389 should "return nil" do
186 @auth_source = AuthSourceLdap.find(1)
390 @auth_source = AuthSourceLdap.find(1)
187 AuthSource.any_instance.stubs(:initialize_ldap_con).raises(Net::LDAP::LdapError, 'Cannot connect')
391 AuthSource.any_instance.stubs(:initialize_ldap_con).raises(Net::LDAP::LdapError, 'Cannot connect')
188
392
189 assert_equal nil, User.try_to_login('edavis', 'wrong')
393 assert_equal nil, User.try_to_login('edavis', 'wrong')
190 end
394 end
191 end
395 end
192
396
193 context "with an unsuccessful authentication" do
397 context "with an unsuccessful authentication" do
194 should "return nil" do
398 should "return nil" do
195 assert_equal nil, User.try_to_login('edavis', 'wrong')
399 assert_equal nil, User.try_to_login('edavis', 'wrong')
196 end
400 end
197 end
401 end
198
402
199 context "on the fly registration" do
403 context "on the fly registration" do
200 setup do
404 setup do
201 @auth_source = AuthSourceLdap.find(1)
405 @auth_source = AuthSourceLdap.find(1)
202 end
406 end
203
407
204 context "with a successful authentication" do
408 context "with a successful authentication" do
205 should "create a new user account if it doesn't exist" do
409 should "create a new user account if it doesn't exist" do
206 assert_difference('User.count') do
410 assert_difference('User.count') do
207 user = User.try_to_login('edavis', '123456')
411 user = User.try_to_login('edavis', '123456')
208 assert !user.admin?
412 assert !user.admin?
209 end
413 end
210 end
414 end
211
415
212 should "retrieve existing user" do
416 should "retrieve existing user" do
213 user = User.try_to_login('edavis', '123456')
417 user = User.try_to_login('edavis', '123456')
214 user.admin = true
418 user.admin = true
215 user.save!
419 user.save!
216
420
217 assert_no_difference('User.count') do
421 assert_no_difference('User.count') do
218 user = User.try_to_login('edavis', '123456')
422 user = User.try_to_login('edavis', '123456')
219 assert user.admin?
423 assert user.admin?
220 end
424 end
221 end
425 end
222 end
426 end
223 end
427 end
224 end
428 end
225
429
226 else
430 else
227 puts "Skipping LDAP tests."
431 puts "Skipping LDAP tests."
228 end
432 end
229
433
230 def test_create_anonymous
434 def test_create_anonymous
231 AnonymousUser.delete_all
435 AnonymousUser.delete_all
232 anon = User.anonymous
436 anon = User.anonymous
233 assert !anon.new_record?
437 assert !anon.new_record?
234 assert_kind_of AnonymousUser, anon
438 assert_kind_of AnonymousUser, anon
235 end
439 end
236
440
237 should_have_one :rss_token
441 should_have_one :rss_token
238
442
239 def test_rss_key
443 def test_rss_key
240 assert_nil @jsmith.rss_token
444 assert_nil @jsmith.rss_token
241 key = @jsmith.rss_key
445 key = @jsmith.rss_key
242 assert_equal 40, key.length
446 assert_equal 40, key.length
243
447
244 @jsmith.reload
448 @jsmith.reload
245 assert_equal key, @jsmith.rss_key
449 assert_equal key, @jsmith.rss_key
246 end
450 end
247
451
248
452
249 should_have_one :api_token
453 should_have_one :api_token
250
454
251 context "User#api_key" do
455 context "User#api_key" do
252 should "generate a new one if the user doesn't have one" do
456 should "generate a new one if the user doesn't have one" do
253 user = User.generate_with_protected!(:api_token => nil)
457 user = User.generate_with_protected!(:api_token => nil)
254 assert_nil user.api_token
458 assert_nil user.api_token
255
459
256 key = user.api_key
460 key = user.api_key
257 assert_equal 40, key.length
461 assert_equal 40, key.length
258 user.reload
462 user.reload
259 assert_equal key, user.api_key
463 assert_equal key, user.api_key
260 end
464 end
261
465
262 should "return the existing api token value" do
466 should "return the existing api token value" do
263 user = User.generate_with_protected!
467 user = User.generate_with_protected!
264 token = Token.generate!(:action => 'api')
468 token = Token.generate!(:action => 'api')
265 user.api_token = token
469 user.api_token = token
266 assert user.save
470 assert user.save
267
471
268 assert_equal token.value, user.api_key
472 assert_equal token.value, user.api_key
269 end
473 end
270 end
474 end
271
475
272 context "User#find_by_api_key" do
476 context "User#find_by_api_key" do
273 should "return nil if no matching key is found" do
477 should "return nil if no matching key is found" do
274 assert_nil User.find_by_api_key('zzzzzzzzz')
478 assert_nil User.find_by_api_key('zzzzzzzzz')
275 end
479 end
276
480
277 should "return nil if the key is found for an inactive user" do
481 should "return nil if the key is found for an inactive user" do
278 user = User.generate_with_protected!(:status => User::STATUS_LOCKED)
482 user = User.generate_with_protected!(:status => User::STATUS_LOCKED)
279 token = Token.generate!(:action => 'api')
483 token = Token.generate!(:action => 'api')
280 user.api_token = token
484 user.api_token = token
281 user.save
485 user.save
282
486
283 assert_nil User.find_by_api_key(token.value)
487 assert_nil User.find_by_api_key(token.value)
284 end
488 end
285
489
286 should "return the user if the key is found for an active user" do
490 should "return the user if the key is found for an active user" do
287 user = User.generate_with_protected!(:status => User::STATUS_ACTIVE)
491 user = User.generate_with_protected!(:status => User::STATUS_ACTIVE)
288 token = Token.generate!(:action => 'api')
492 token = Token.generate!(:action => 'api')
289 user.api_token = token
493 user.api_token = token
290 user.save
494 user.save
291
495
292 assert_equal user, User.find_by_api_key(token.value)
496 assert_equal user, User.find_by_api_key(token.value)
293 end
497 end
294 end
498 end
295
499
296 def test_roles_for_project
500 def test_roles_for_project
297 # user with a role
501 # user with a role
298 roles = @jsmith.roles_for_project(Project.find(1))
502 roles = @jsmith.roles_for_project(Project.find(1))
299 assert_kind_of Role, roles.first
503 assert_kind_of Role, roles.first
300 assert_equal "Manager", roles.first.name
504 assert_equal "Manager", roles.first.name
301
505
302 # user with no role
506 # user with no role
303 assert_nil @dlopper.roles_for_project(Project.find(2)).detect {|role| role.member?}
507 assert_nil @dlopper.roles_for_project(Project.find(2)).detect {|role| role.member?}
304 end
508 end
305
509
306 def test_mail_notification_all
510 def test_mail_notification_all
307 @jsmith.mail_notification = 'all'
511 @jsmith.mail_notification = 'all'
308 @jsmith.notified_project_ids = []
512 @jsmith.notified_project_ids = []
309 @jsmith.save
513 @jsmith.save
310 @jsmith.reload
514 @jsmith.reload
311 assert @jsmith.projects.first.recipients.include?(@jsmith.mail)
515 assert @jsmith.projects.first.recipients.include?(@jsmith.mail)
312 end
516 end
313
517
314 def test_mail_notification_selected
518 def test_mail_notification_selected
315 @jsmith.mail_notification = 'selected'
519 @jsmith.mail_notification = 'selected'
316 @jsmith.notified_project_ids = [1]
520 @jsmith.notified_project_ids = [1]
317 @jsmith.save
521 @jsmith.save
318 @jsmith.reload
522 @jsmith.reload
319 assert Project.find(1).recipients.include?(@jsmith.mail)
523 assert Project.find(1).recipients.include?(@jsmith.mail)
320 end
524 end
321
525
322 def test_mail_notification_only_my_events
526 def test_mail_notification_only_my_events
323 @jsmith.mail_notification = 'only_my_events'
527 @jsmith.mail_notification = 'only_my_events'
324 @jsmith.notified_project_ids = []
528 @jsmith.notified_project_ids = []
325 @jsmith.save
529 @jsmith.save
326 @jsmith.reload
530 @jsmith.reload
327 assert !@jsmith.projects.first.recipients.include?(@jsmith.mail)
531 assert !@jsmith.projects.first.recipients.include?(@jsmith.mail)
328 end
532 end
329
533
330 def test_comments_sorting_preference
534 def test_comments_sorting_preference
331 assert !@jsmith.wants_comments_in_reverse_order?
535 assert !@jsmith.wants_comments_in_reverse_order?
332 @jsmith.pref.comments_sorting = 'asc'
536 @jsmith.pref.comments_sorting = 'asc'
333 assert !@jsmith.wants_comments_in_reverse_order?
537 assert !@jsmith.wants_comments_in_reverse_order?
334 @jsmith.pref.comments_sorting = 'desc'
538 @jsmith.pref.comments_sorting = 'desc'
335 assert @jsmith.wants_comments_in_reverse_order?
539 assert @jsmith.wants_comments_in_reverse_order?
336 end
540 end
337
541
338 def test_find_by_mail_should_be_case_insensitive
542 def test_find_by_mail_should_be_case_insensitive
339 u = User.find_by_mail('JSmith@somenet.foo')
543 u = User.find_by_mail('JSmith@somenet.foo')
340 assert_not_nil u
544 assert_not_nil u
341 assert_equal 'jsmith@somenet.foo', u.mail
545 assert_equal 'jsmith@somenet.foo', u.mail
342 end
546 end
343
547
344 def test_random_password
548 def test_random_password
345 u = User.new
549 u = User.new
346 u.random_password
550 u.random_password
347 assert !u.password.blank?
551 assert !u.password.blank?
348 assert !u.password_confirmation.blank?
552 assert !u.password_confirmation.blank?
349 end
553 end
350
554
351 context "#change_password_allowed?" do
555 context "#change_password_allowed?" do
352 should "be allowed if no auth source is set" do
556 should "be allowed if no auth source is set" do
353 user = User.generate_with_protected!
557 user = User.generate_with_protected!
354 assert user.change_password_allowed?
558 assert user.change_password_allowed?
355 end
559 end
356
560
357 should "delegate to the auth source" do
561 should "delegate to the auth source" do
358 user = User.generate_with_protected!
562 user = User.generate_with_protected!
359
563
360 allowed_auth_source = AuthSource.generate!
564 allowed_auth_source = AuthSource.generate!
361 def allowed_auth_source.allow_password_changes?; true; end
565 def allowed_auth_source.allow_password_changes?; true; end
362
566
363 denied_auth_source = AuthSource.generate!
567 denied_auth_source = AuthSource.generate!
364 def denied_auth_source.allow_password_changes?; false; end
568 def denied_auth_source.allow_password_changes?; false; end
365
569
366 assert user.change_password_allowed?
570 assert user.change_password_allowed?
367
571
368 user.auth_source = allowed_auth_source
572 user.auth_source = allowed_auth_source
369 assert user.change_password_allowed?, "User not allowed to change password, though auth source does"
573 assert user.change_password_allowed?, "User not allowed to change password, though auth source does"
370
574
371 user.auth_source = denied_auth_source
575 user.auth_source = denied_auth_source
372 assert !user.change_password_allowed?, "User allowed to change password, though auth source does not"
576 assert !user.change_password_allowed?, "User allowed to change password, though auth source does not"
373 end
577 end
374
578
375 end
579 end
376
580
377 context "#allowed_to?" do
581 context "#allowed_to?" do
378 context "with a unique project" do
582 context "with a unique project" do
379 should "return false if project is archived" do
583 should "return false if project is archived" do
380 project = Project.find(1)
584 project = Project.find(1)
381 Project.any_instance.stubs(:status).returns(Project::STATUS_ARCHIVED)
585 Project.any_instance.stubs(:status).returns(Project::STATUS_ARCHIVED)
382 assert ! @admin.allowed_to?(:view_issues, Project.find(1))
586 assert ! @admin.allowed_to?(:view_issues, Project.find(1))
383 end
587 end
384
588
385 should "return false if related module is disabled" do
589 should "return false if related module is disabled" do
386 project = Project.find(1)
590 project = Project.find(1)
387 project.enabled_module_names = ["issue_tracking"]
591 project.enabled_module_names = ["issue_tracking"]
388 assert @admin.allowed_to?(:add_issues, project)
592 assert @admin.allowed_to?(:add_issues, project)
389 assert ! @admin.allowed_to?(:view_wiki_pages, project)
593 assert ! @admin.allowed_to?(:view_wiki_pages, project)
390 end
594 end
391
595
392 should "authorize nearly everything for admin users" do
596 should "authorize nearly everything for admin users" do
393 project = Project.find(1)
597 project = Project.find(1)
394 assert ! @admin.member_of?(project)
598 assert ! @admin.member_of?(project)
395 %w(edit_issues delete_issues manage_news manage_documents manage_wiki).each do |p|
599 %w(edit_issues delete_issues manage_news manage_documents manage_wiki).each do |p|
396 assert @admin.allowed_to?(p.to_sym, project)
600 assert @admin.allowed_to?(p.to_sym, project)
397 end
601 end
398 end
602 end
399
603
400 should "authorize normal users depending on their roles" do
604 should "authorize normal users depending on their roles" do
401 project = Project.find(1)
605 project = Project.find(1)
402 assert @jsmith.allowed_to?(:delete_messages, project) #Manager
606 assert @jsmith.allowed_to?(:delete_messages, project) #Manager
403 assert ! @dlopper.allowed_to?(:delete_messages, project) #Developper
607 assert ! @dlopper.allowed_to?(:delete_messages, project) #Developper
404 end
608 end
405 end
609 end
406
610
407 context "with multiple projects" do
611 context "with multiple projects" do
408 should "return false if array is empty" do
612 should "return false if array is empty" do
409 assert ! @admin.allowed_to?(:view_project, [])
613 assert ! @admin.allowed_to?(:view_project, [])
410 end
614 end
411
615
412 should "return true only if user has permission on all these projects" do
616 should "return true only if user has permission on all these projects" do
413 assert @admin.allowed_to?(:view_project, Project.all)
617 assert @admin.allowed_to?(:view_project, Project.all)
414 assert ! @dlopper.allowed_to?(:view_project, Project.all) #cannot see Project(2)
618 assert ! @dlopper.allowed_to?(:view_project, Project.all) #cannot see Project(2)
415 assert @jsmith.allowed_to?(:edit_issues, @jsmith.projects) #Manager or Developer everywhere
619 assert @jsmith.allowed_to?(:edit_issues, @jsmith.projects) #Manager or Developer everywhere
416 assert ! @jsmith.allowed_to?(:delete_issue_watchers, @jsmith.projects) #Dev cannot delete_issue_watchers
620 assert ! @jsmith.allowed_to?(:delete_issue_watchers, @jsmith.projects) #Dev cannot delete_issue_watchers
417 end
621 end
418
622
419 should "behave correctly with arrays of 1 project" do
623 should "behave correctly with arrays of 1 project" do
420 assert ! User.anonymous.allowed_to?(:delete_issues, [Project.first])
624 assert ! User.anonymous.allowed_to?(:delete_issues, [Project.first])
421 end
625 end
422 end
626 end
423
627
424 context "with options[:global]" do
628 context "with options[:global]" do
425 should "authorize if user has at least one role that has this permission" do
629 should "authorize if user has at least one role that has this permission" do
426 @dlopper2 = User.find(5) #only Developper on a project, not Manager anywhere
630 @dlopper2 = User.find(5) #only Developper on a project, not Manager anywhere
427 @anonymous = User.find(6)
631 @anonymous = User.find(6)
428 assert @jsmith.allowed_to?(:delete_issue_watchers, nil, :global => true)
632 assert @jsmith.allowed_to?(:delete_issue_watchers, nil, :global => true)
429 assert ! @dlopper2.allowed_to?(:delete_issue_watchers, nil, :global => true)
633 assert ! @dlopper2.allowed_to?(:delete_issue_watchers, nil, :global => true)
430 assert @dlopper2.allowed_to?(:add_issues, nil, :global => true)
634 assert @dlopper2.allowed_to?(:add_issues, nil, :global => true)
431 assert ! @anonymous.allowed_to?(:add_issues, nil, :global => true)
635 assert ! @anonymous.allowed_to?(:add_issues, nil, :global => true)
432 assert @anonymous.allowed_to?(:view_issues, nil, :global => true)
636 assert @anonymous.allowed_to?(:view_issues, nil, :global => true)
433 end
637 end
434 end
638 end
435 end
639 end
436
640
437 context "User#notify_about?" do
641 context "User#notify_about?" do
438 context "Issues" do
642 context "Issues" do
439 setup do
643 setup do
440 @project = Project.find(1)
644 @project = Project.find(1)
441 @author = User.generate_with_protected!
645 @author = User.generate_with_protected!
442 @assignee = User.generate_with_protected!
646 @assignee = User.generate_with_protected!
443 @issue = Issue.generate_for_project!(@project, :assigned_to => @assignee, :author => @author)
647 @issue = Issue.generate_for_project!(@project, :assigned_to => @assignee, :author => @author)
444 end
648 end
445
649
446 should "be true for a user with :all" do
650 should "be true for a user with :all" do
447 @author.update_attribute(:mail_notification, 'all')
651 @author.update_attribute(:mail_notification, 'all')
448 assert @author.notify_about?(@issue)
652 assert @author.notify_about?(@issue)
449 end
653 end
450
654
451 should "be false for a user with :none" do
655 should "be false for a user with :none" do
452 @author.update_attribute(:mail_notification, 'none')
656 @author.update_attribute(:mail_notification, 'none')
453 assert ! @author.notify_about?(@issue)
657 assert ! @author.notify_about?(@issue)
454 end
658 end
455
659
456 should "be false for a user with :only_my_events and isn't an author, creator, or assignee" do
660 should "be false for a user with :only_my_events and isn't an author, creator, or assignee" do
457 @user = User.generate_with_protected!(:mail_notification => 'only_my_events')
661 @user = User.generate_with_protected!(:mail_notification => 'only_my_events')
458 assert ! @user.notify_about?(@issue)
662 assert ! @user.notify_about?(@issue)
459 end
663 end
460
664
461 should "be true for a user with :only_my_events and is the author" do
665 should "be true for a user with :only_my_events and is the author" do
462 @author.update_attribute(:mail_notification, 'only_my_events')
666 @author.update_attribute(:mail_notification, 'only_my_events')
463 assert @author.notify_about?(@issue)
667 assert @author.notify_about?(@issue)
464 end
668 end
465
669
466 should "be true for a user with :only_my_events and is the assignee" do
670 should "be true for a user with :only_my_events and is the assignee" do
467 @assignee.update_attribute(:mail_notification, 'only_my_events')
671 @assignee.update_attribute(:mail_notification, 'only_my_events')
468 assert @assignee.notify_about?(@issue)
672 assert @assignee.notify_about?(@issue)
469 end
673 end
470
674
471 should "be true for a user with :only_assigned and is the assignee" do
675 should "be true for a user with :only_assigned and is the assignee" do
472 @assignee.update_attribute(:mail_notification, 'only_assigned')
676 @assignee.update_attribute(:mail_notification, 'only_assigned')
473 assert @assignee.notify_about?(@issue)
677 assert @assignee.notify_about?(@issue)
474 end
678 end
475
679
476 should "be false for a user with :only_assigned and is not the assignee" do
680 should "be false for a user with :only_assigned and is not the assignee" do
477 @author.update_attribute(:mail_notification, 'only_assigned')
681 @author.update_attribute(:mail_notification, 'only_assigned')
478 assert ! @author.notify_about?(@issue)
682 assert ! @author.notify_about?(@issue)
479 end
683 end
480
684
481 should "be true for a user with :only_owner and is the author" do
685 should "be true for a user with :only_owner and is the author" do
482 @author.update_attribute(:mail_notification, 'only_owner')
686 @author.update_attribute(:mail_notification, 'only_owner')
483 assert @author.notify_about?(@issue)
687 assert @author.notify_about?(@issue)
484 end
688 end
485
689
486 should "be false for a user with :only_owner and is not the author" do
690 should "be false for a user with :only_owner and is not the author" do
487 @assignee.update_attribute(:mail_notification, 'only_owner')
691 @assignee.update_attribute(:mail_notification, 'only_owner')
488 assert ! @assignee.notify_about?(@issue)
692 assert ! @assignee.notify_about?(@issue)
489 end
693 end
490 end
694 end
491
695
492 context "other events" do
696 context "other events" do
493 should 'be added and tested'
697 should 'be added and tested'
494 end
698 end
495 end
699 end
496
700
497 if Object.const_defined?(:OpenID)
701 if Object.const_defined?(:OpenID)
498
702
499 def test_setting_identity_url
703 def test_setting_identity_url
500 normalized_open_id_url = 'http://example.com/'
704 normalized_open_id_url = 'http://example.com/'
501 u = User.new( :identity_url => 'http://example.com/' )
705 u = User.new( :identity_url => 'http://example.com/' )
502 assert_equal normalized_open_id_url, u.identity_url
706 assert_equal normalized_open_id_url, u.identity_url
503 end
707 end
504
708
505 def test_setting_identity_url_without_trailing_slash
709 def test_setting_identity_url_without_trailing_slash
506 normalized_open_id_url = 'http://example.com/'
710 normalized_open_id_url = 'http://example.com/'
507 u = User.new( :identity_url => 'http://example.com' )
711 u = User.new( :identity_url => 'http://example.com' )
508 assert_equal normalized_open_id_url, u.identity_url
712 assert_equal normalized_open_id_url, u.identity_url
509 end
713 end
510
714
511 def test_setting_identity_url_without_protocol
715 def test_setting_identity_url_without_protocol
512 normalized_open_id_url = 'http://example.com/'
716 normalized_open_id_url = 'http://example.com/'
513 u = User.new( :identity_url => 'example.com' )
717 u = User.new( :identity_url => 'example.com' )
514 assert_equal normalized_open_id_url, u.identity_url
718 assert_equal normalized_open_id_url, u.identity_url
515 end
719 end
516
720
517 def test_setting_blank_identity_url
721 def test_setting_blank_identity_url
518 u = User.new( :identity_url => 'example.com' )
722 u = User.new( :identity_url => 'example.com' )
519 u.identity_url = ''
723 u.identity_url = ''
520 assert u.identity_url.blank?
724 assert u.identity_url.blank?
521 end
725 end
522
726
523 def test_setting_invalid_identity_url
727 def test_setting_invalid_identity_url
524 u = User.new( :identity_url => 'this is not an openid url' )
728 u = User.new( :identity_url => 'this is not an openid url' )
525 assert u.identity_url.blank?
729 assert u.identity_url.blank?
526 end
730 end
527
731
528 else
732 else
529 puts "Skipping openid tests."
733 puts "Skipping openid tests."
530 end
734 end
531
735
532 end
736 end
General Comments 0
You need to be logged in to leave comments. Login now