##// END OF EJS Templates
Fixed: User#allowed_to? returning true in any case if array of projects had only one item (#5332)...
Jean-Baptiste Barth -
r4119:e59c927ee52d
parent child
Show More
@@ -1,481 +1,483
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2009 Jean-Philippe Lang
2 # Copyright (C) 2006-2009 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 require "digest/sha1"
18 require "digest/sha1"
19
19
20 class User < Principal
20 class User < Principal
21
21
22 # Account statuses
22 # Account statuses
23 STATUS_ANONYMOUS = 0
23 STATUS_ANONYMOUS = 0
24 STATUS_ACTIVE = 1
24 STATUS_ACTIVE = 1
25 STATUS_REGISTERED = 2
25 STATUS_REGISTERED = 2
26 STATUS_LOCKED = 3
26 STATUS_LOCKED = 3
27
27
28 USER_FORMATS = {
28 USER_FORMATS = {
29 :firstname_lastname => '#{firstname} #{lastname}',
29 :firstname_lastname => '#{firstname} #{lastname}',
30 :firstname => '#{firstname}',
30 :firstname => '#{firstname}',
31 :lastname_firstname => '#{lastname} #{firstname}',
31 :lastname_firstname => '#{lastname} #{firstname}',
32 :lastname_coma_firstname => '#{lastname}, #{firstname}',
32 :lastname_coma_firstname => '#{lastname}, #{firstname}',
33 :username => '#{login}'
33 :username => '#{login}'
34 }
34 }
35
35
36 MAIL_NOTIFICATION_OPTIONS = [
36 MAIL_NOTIFICATION_OPTIONS = [
37 [:all, :label_user_mail_option_all],
37 [:all, :label_user_mail_option_all],
38 [:selected, :label_user_mail_option_selected],
38 [:selected, :label_user_mail_option_selected],
39 [:none, :label_user_mail_option_none],
39 [:none, :label_user_mail_option_none],
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 ]
43 ]
44
44
45 has_and_belongs_to_many :groups, :after_add => Proc.new {|user, group| group.user_added(user)},
45 has_and_belongs_to_many :groups, :after_add => Proc.new {|user, group| group.user_added(user)},
46 :after_remove => Proc.new {|user, group| group.user_removed(user)}
46 :after_remove => Proc.new {|user, group| group.user_removed(user)}
47 has_many :issue_categories, :foreign_key => 'assigned_to_id', :dependent => :nullify
47 has_many :issue_categories, :foreign_key => 'assigned_to_id', :dependent => :nullify
48 has_many :changesets, :dependent => :nullify
48 has_many :changesets, :dependent => :nullify
49 has_one :preference, :dependent => :destroy, :class_name => 'UserPreference'
49 has_one :preference, :dependent => :destroy, :class_name => 'UserPreference'
50 has_one :rss_token, :dependent => :destroy, :class_name => 'Token', :conditions => "action='feeds'"
50 has_one :rss_token, :dependent => :destroy, :class_name => 'Token', :conditions => "action='feeds'"
51 has_one :api_token, :dependent => :destroy, :class_name => 'Token', :conditions => "action='api'"
51 has_one :api_token, :dependent => :destroy, :class_name => 'Token', :conditions => "action='api'"
52 belongs_to :auth_source
52 belongs_to :auth_source
53
53
54 # Active non-anonymous users scope
54 # Active non-anonymous users scope
55 named_scope :active, :conditions => "#{User.table_name}.status = #{STATUS_ACTIVE}"
55 named_scope :active, :conditions => "#{User.table_name}.status = #{STATUS_ACTIVE}"
56
56
57 acts_as_customizable
57 acts_as_customizable
58
58
59 attr_accessor :password, :password_confirmation
59 attr_accessor :password, :password_confirmation
60 attr_accessor :last_before_login_on
60 attr_accessor :last_before_login_on
61 # Prevents unauthorized assignments
61 # Prevents unauthorized assignments
62 attr_protected :login, :admin, :password, :password_confirmation, :hashed_password, :group_ids
62 attr_protected :login, :admin, :password, :password_confirmation, :hashed_password, :group_ids
63
63
64 validates_presence_of :login, :firstname, :lastname, :mail, :if => Proc.new { |user| !user.is_a?(AnonymousUser) }
64 validates_presence_of :login, :firstname, :lastname, :mail, :if => Proc.new { |user| !user.is_a?(AnonymousUser) }
65 validates_uniqueness_of :login, :if => Proc.new { |user| !user.login.blank? }, :case_sensitive => false
65 validates_uniqueness_of :login, :if => Proc.new { |user| !user.login.blank? }, :case_sensitive => false
66 validates_uniqueness_of :mail, :if => Proc.new { |user| !user.mail.blank? }, :case_sensitive => false
66 validates_uniqueness_of :mail, :if => Proc.new { |user| !user.mail.blank? }, :case_sensitive => false
67 # Login must contain lettres, numbers, underscores only
67 # Login must contain lettres, numbers, underscores only
68 validates_format_of :login, :with => /^[a-z0-9_\-@\.]*$/i
68 validates_format_of :login, :with => /^[a-z0-9_\-@\.]*$/i
69 validates_length_of :login, :maximum => 30
69 validates_length_of :login, :maximum => 30
70 validates_format_of :firstname, :lastname, :with => /^[\w\s\'\-\.]*$/i
70 validates_format_of :firstname, :lastname, :with => /^[\w\s\'\-\.]*$/i
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
75
76 def before_create
76 def before_create
77 self.mail_notification = Setting.default_notification_option if self.mail_notification.blank?
77 self.mail_notification = Setting.default_notification_option if self.mail_notification.blank?
78 true
78 true
79 end
79 end
80
80
81 def before_save
81 def before_save
82 # update hashed_password if password was set
82 # update hashed_password if password was set
83 self.hashed_password = User.hash_password(self.password) if self.password && self.auth_source_id.blank?
83 self.hashed_password = User.hash_password(self.password) if self.password && self.auth_source_id.blank?
84 end
84 end
85
85
86 def reload(*args)
86 def reload(*args)
87 @name = nil
87 @name = nil
88 super
88 super
89 end
89 end
90
90
91 def mail=(arg)
91 def mail=(arg)
92 write_attribute(:mail, arg.to_s.strip)
92 write_attribute(:mail, arg.to_s.strip)
93 end
93 end
94
94
95 def identity_url=(url)
95 def identity_url=(url)
96 if url.blank?
96 if url.blank?
97 write_attribute(:identity_url, '')
97 write_attribute(:identity_url, '')
98 else
98 else
99 begin
99 begin
100 write_attribute(:identity_url, OpenIdAuthentication.normalize_identifier(url))
100 write_attribute(:identity_url, OpenIdAuthentication.normalize_identifier(url))
101 rescue OpenIdAuthentication::InvalidOpenId
101 rescue OpenIdAuthentication::InvalidOpenId
102 # Invlaid url, don't save
102 # Invlaid url, don't save
103 end
103 end
104 end
104 end
105 self.read_attribute(:identity_url)
105 self.read_attribute(:identity_url)
106 end
106 end
107
107
108 # Returns the user that matches provided login and password, or nil
108 # Returns the user that matches provided login and password, or nil
109 def self.try_to_login(login, password)
109 def self.try_to_login(login, password)
110 # Make sure no one can sign in with an empty password
110 # Make sure no one can sign in with an empty password
111 return nil if password.to_s.empty?
111 return nil if password.to_s.empty?
112 user = find_by_login(login)
112 user = find_by_login(login)
113 if user
113 if user
114 # user is already in local database
114 # user is already in local database
115 return nil if !user.active?
115 return nil if !user.active?
116 if user.auth_source
116 if user.auth_source
117 # user has an external authentication method
117 # user has an external authentication method
118 return nil unless user.auth_source.authenticate(login, password)
118 return nil unless user.auth_source.authenticate(login, password)
119 else
119 else
120 # authentication with local password
120 # authentication with local password
121 return nil unless User.hash_password(password) == user.hashed_password
121 return nil unless User.hash_password(password) == user.hashed_password
122 end
122 end
123 else
123 else
124 # user is not yet registered, try to authenticate with available sources
124 # user is not yet registered, try to authenticate with available sources
125 attrs = AuthSource.authenticate(login, password)
125 attrs = AuthSource.authenticate(login, password)
126 if attrs
126 if attrs
127 user = new(attrs)
127 user = new(attrs)
128 user.login = login
128 user.login = login
129 user.language = Setting.default_language
129 user.language = Setting.default_language
130 if user.save
130 if user.save
131 user.reload
131 user.reload
132 logger.info("User '#{user.login}' created from external auth source: #{user.auth_source.type} - #{user.auth_source.name}") if logger && user.auth_source
132 logger.info("User '#{user.login}' created from external auth source: #{user.auth_source.type} - #{user.auth_source.name}") if logger && user.auth_source
133 end
133 end
134 end
134 end
135 end
135 end
136 user.update_attribute(:last_login_on, Time.now) if user && !user.new_record?
136 user.update_attribute(:last_login_on, Time.now) if user && !user.new_record?
137 user
137 user
138 rescue => text
138 rescue => text
139 raise text
139 raise text
140 end
140 end
141
141
142 # Returns the user who matches the given autologin +key+ or nil
142 # Returns the user who matches the given autologin +key+ or nil
143 def self.try_to_autologin(key)
143 def self.try_to_autologin(key)
144 tokens = Token.find_all_by_action_and_value('autologin', key)
144 tokens = Token.find_all_by_action_and_value('autologin', key)
145 # Make sure there's only 1 token that matches the key
145 # Make sure there's only 1 token that matches the key
146 if tokens.size == 1
146 if tokens.size == 1
147 token = tokens.first
147 token = tokens.first
148 if (token.created_on > Setting.autologin.to_i.day.ago) && token.user && token.user.active?
148 if (token.created_on > Setting.autologin.to_i.day.ago) && token.user && token.user.active?
149 token.user.update_attribute(:last_login_on, Time.now)
149 token.user.update_attribute(:last_login_on, Time.now)
150 token.user
150 token.user
151 end
151 end
152 end
152 end
153 end
153 end
154
154
155 # Return user's full name for display
155 # Return user's full name for display
156 def name(formatter = nil)
156 def name(formatter = nil)
157 if formatter
157 if formatter
158 eval('"' + (USER_FORMATS[formatter] || USER_FORMATS[:firstname_lastname]) + '"')
158 eval('"' + (USER_FORMATS[formatter] || USER_FORMATS[:firstname_lastname]) + '"')
159 else
159 else
160 @name ||= eval('"' + (USER_FORMATS[Setting.user_format] || USER_FORMATS[:firstname_lastname]) + '"')
160 @name ||= eval('"' + (USER_FORMATS[Setting.user_format] || USER_FORMATS[:firstname_lastname]) + '"')
161 end
161 end
162 end
162 end
163
163
164 def active?
164 def active?
165 self.status == STATUS_ACTIVE
165 self.status == STATUS_ACTIVE
166 end
166 end
167
167
168 def registered?
168 def registered?
169 self.status == STATUS_REGISTERED
169 self.status == STATUS_REGISTERED
170 end
170 end
171
171
172 def locked?
172 def locked?
173 self.status == STATUS_LOCKED
173 self.status == STATUS_LOCKED
174 end
174 end
175
175
176 def activate
176 def activate
177 self.status = STATUS_ACTIVE
177 self.status = STATUS_ACTIVE
178 end
178 end
179
179
180 def register
180 def register
181 self.status = STATUS_REGISTERED
181 self.status = STATUS_REGISTERED
182 end
182 end
183
183
184 def lock
184 def lock
185 self.status = STATUS_LOCKED
185 self.status = STATUS_LOCKED
186 end
186 end
187
187
188 def activate!
188 def activate!
189 update_attribute(:status, STATUS_ACTIVE)
189 update_attribute(:status, STATUS_ACTIVE)
190 end
190 end
191
191
192 def register!
192 def register!
193 update_attribute(:status, STATUS_REGISTERED)
193 update_attribute(:status, STATUS_REGISTERED)
194 end
194 end
195
195
196 def lock!
196 def lock!
197 update_attribute(:status, STATUS_LOCKED)
197 update_attribute(:status, STATUS_LOCKED)
198 end
198 end
199
199
200 def check_password?(clear_password)
200 def check_password?(clear_password)
201 if auth_source_id.present?
201 if auth_source_id.present?
202 auth_source.authenticate(self.login, clear_password)
202 auth_source.authenticate(self.login, clear_password)
203 else
203 else
204 User.hash_password(clear_password) == self.hashed_password
204 User.hash_password(clear_password) == self.hashed_password
205 end
205 end
206 end
206 end
207
207
208 # Does the backend storage allow this user to change their password?
208 # Does the backend storage allow this user to change their password?
209 def change_password_allowed?
209 def change_password_allowed?
210 return true if auth_source_id.blank?
210 return true if auth_source_id.blank?
211 return auth_source.allow_password_changes?
211 return auth_source.allow_password_changes?
212 end
212 end
213
213
214 # Generate and set a random password. Useful for automated user creation
214 # Generate and set a random password. Useful for automated user creation
215 # Based on Token#generate_token_value
215 # Based on Token#generate_token_value
216 #
216 #
217 def random_password
217 def random_password
218 chars = ("a".."z").to_a + ("A".."Z").to_a + ("0".."9").to_a
218 chars = ("a".."z").to_a + ("A".."Z").to_a + ("0".."9").to_a
219 password = ''
219 password = ''
220 40.times { |i| password << chars[rand(chars.size-1)] }
220 40.times { |i| password << chars[rand(chars.size-1)] }
221 self.password = password
221 self.password = password
222 self.password_confirmation = password
222 self.password_confirmation = password
223 self
223 self
224 end
224 end
225
225
226 def pref
226 def pref
227 self.preference ||= UserPreference.new(:user => self)
227 self.preference ||= UserPreference.new(:user => self)
228 end
228 end
229
229
230 def time_zone
230 def time_zone
231 @time_zone ||= (self.pref.time_zone.blank? ? nil : ActiveSupport::TimeZone[self.pref.time_zone])
231 @time_zone ||= (self.pref.time_zone.blank? ? nil : ActiveSupport::TimeZone[self.pref.time_zone])
232 end
232 end
233
233
234 def wants_comments_in_reverse_order?
234 def wants_comments_in_reverse_order?
235 self.pref[:comments_sorting] == 'desc'
235 self.pref[:comments_sorting] == 'desc'
236 end
236 end
237
237
238 # Return user's RSS key (a 40 chars long string), used to access feeds
238 # Return user's RSS key (a 40 chars long string), used to access feeds
239 def rss_key
239 def rss_key
240 token = self.rss_token || Token.create(:user => self, :action => 'feeds')
240 token = self.rss_token || Token.create(:user => self, :action => 'feeds')
241 token.value
241 token.value
242 end
242 end
243
243
244 # Return user's API key (a 40 chars long string), used to access the API
244 # Return user's API key (a 40 chars long string), used to access the API
245 def api_key
245 def api_key
246 token = self.api_token || self.create_api_token(:action => 'api')
246 token = self.api_token || self.create_api_token(:action => 'api')
247 token.value
247 token.value
248 end
248 end
249
249
250 # Return an array of project ids for which the user has explicitly turned mail notifications on
250 # Return an array of project ids for which the user has explicitly turned mail notifications on
251 def notified_projects_ids
251 def notified_projects_ids
252 @notified_projects_ids ||= memberships.select {|m| m.mail_notification?}.collect(&:project_id)
252 @notified_projects_ids ||= memberships.select {|m| m.mail_notification?}.collect(&:project_id)
253 end
253 end
254
254
255 def notified_project_ids=(ids)
255 def notified_project_ids=(ids)
256 Member.update_all("mail_notification = #{connection.quoted_false}", ['user_id = ?', id])
256 Member.update_all("mail_notification = #{connection.quoted_false}", ['user_id = ?', id])
257 Member.update_all("mail_notification = #{connection.quoted_true}", ['user_id = ? AND project_id IN (?)', id, ids]) if ids && !ids.empty?
257 Member.update_all("mail_notification = #{connection.quoted_true}", ['user_id = ? AND project_id IN (?)', id, ids]) if ids && !ids.empty?
258 @notified_projects_ids = nil
258 @notified_projects_ids = nil
259 notified_projects_ids
259 notified_projects_ids
260 end
260 end
261
261
262 # Only users that belong to more than 1 project can select projects for which they are notified
262 # Only users that belong to more than 1 project can select projects for which they are notified
263 def valid_notification_options
263 def valid_notification_options
264 # Note that @user.membership.size would fail since AR ignores
264 # Note that @user.membership.size would fail since AR ignores
265 # :include association option when doing a count
265 # :include association option when doing a count
266 if memberships.length < 1
266 if memberships.length < 1
267 MAIL_NOTIFICATION_OPTIONS.delete_if {|option| option.first == :selected}
267 MAIL_NOTIFICATION_OPTIONS.delete_if {|option| option.first == :selected}
268 else
268 else
269 MAIL_NOTIFICATION_OPTIONS
269 MAIL_NOTIFICATION_OPTIONS
270 end
270 end
271 end
271 end
272
272
273 # Find a user account by matching the exact login and then a case-insensitive
273 # Find a user account by matching the exact login and then a case-insensitive
274 # version. Exact matches will be given priority.
274 # version. Exact matches will be given priority.
275 def self.find_by_login(login)
275 def self.find_by_login(login)
276 # force string comparison to be case sensitive on MySQL
276 # force string comparison to be case sensitive on MySQL
277 type_cast = (ActiveRecord::Base.connection.adapter_name == 'MySQL') ? 'BINARY' : ''
277 type_cast = (ActiveRecord::Base.connection.adapter_name == 'MySQL') ? 'BINARY' : ''
278
278
279 # First look for an exact match
279 # First look for an exact match
280 user = first(:conditions => ["#{type_cast} login = ?", login])
280 user = first(:conditions => ["#{type_cast} login = ?", login])
281 # Fail over to case-insensitive if none was found
281 # Fail over to case-insensitive if none was found
282 user ||= first(:conditions => ["#{type_cast} LOWER(login) = ?", login.to_s.downcase])
282 user ||= first(:conditions => ["#{type_cast} LOWER(login) = ?", login.to_s.downcase])
283 end
283 end
284
284
285 def self.find_by_rss_key(key)
285 def self.find_by_rss_key(key)
286 token = Token.find_by_value(key)
286 token = Token.find_by_value(key)
287 token && token.user.active? ? token.user : nil
287 token && token.user.active? ? token.user : nil
288 end
288 end
289
289
290 def self.find_by_api_key(key)
290 def self.find_by_api_key(key)
291 token = Token.find_by_action_and_value('api', key)
291 token = Token.find_by_action_and_value('api', key)
292 token && token.user.active? ? token.user : nil
292 token && token.user.active? ? token.user : nil
293 end
293 end
294
294
295 # Makes find_by_mail case-insensitive
295 # Makes find_by_mail case-insensitive
296 def self.find_by_mail(mail)
296 def self.find_by_mail(mail)
297 find(:first, :conditions => ["LOWER(mail) = ?", mail.to_s.downcase])
297 find(:first, :conditions => ["LOWER(mail) = ?", mail.to_s.downcase])
298 end
298 end
299
299
300 def to_s
300 def to_s
301 name
301 name
302 end
302 end
303
303
304 # Returns the current day according to user's time zone
304 # Returns the current day according to user's time zone
305 def today
305 def today
306 if time_zone.nil?
306 if time_zone.nil?
307 Date.today
307 Date.today
308 else
308 else
309 Time.now.in_time_zone(time_zone).to_date
309 Time.now.in_time_zone(time_zone).to_date
310 end
310 end
311 end
311 end
312
312
313 def logged?
313 def logged?
314 true
314 true
315 end
315 end
316
316
317 def anonymous?
317 def anonymous?
318 !logged?
318 !logged?
319 end
319 end
320
320
321 # Return user's roles for project
321 # Return user's roles for project
322 def roles_for_project(project)
322 def roles_for_project(project)
323 roles = []
323 roles = []
324 # No role on archived projects
324 # No role on archived projects
325 return roles unless project && project.active?
325 return roles unless project && project.active?
326 if logged?
326 if logged?
327 # Find project membership
327 # Find project membership
328 membership = memberships.detect {|m| m.project_id == project.id}
328 membership = memberships.detect {|m| m.project_id == project.id}
329 if membership
329 if membership
330 roles = membership.roles
330 roles = membership.roles
331 else
331 else
332 @role_non_member ||= Role.non_member
332 @role_non_member ||= Role.non_member
333 roles << @role_non_member
333 roles << @role_non_member
334 end
334 end
335 else
335 else
336 @role_anonymous ||= Role.anonymous
336 @role_anonymous ||= Role.anonymous
337 roles << @role_anonymous
337 roles << @role_anonymous
338 end
338 end
339 roles
339 roles
340 end
340 end
341
341
342 # Return true if the user is a member of project
342 # Return true if the user is a member of project
343 def member_of?(project)
343 def member_of?(project)
344 !roles_for_project(project).detect {|role| role.member?}.nil?
344 !roles_for_project(project).detect {|role| role.member?}.nil?
345 end
345 end
346
346
347 # Return true if the user is allowed to do the specified action on a specific context
347 # Return true if the user is allowed to do the specified action on a specific context
348 # Action can be:
348 # Action can be:
349 # * a parameter-like Hash (eg. :controller => 'projects', :action => 'edit')
349 # * a parameter-like Hash (eg. :controller => 'projects', :action => 'edit')
350 # * a permission Symbol (eg. :edit_project)
350 # * a permission Symbol (eg. :edit_project)
351 # Context can be:
351 # Context can be:
352 # * a project : returns true if user is allowed to do the specified action on this project
352 # * a project : returns true if user is allowed to do the specified action on this project
353 # * a group of projects : returns true if user is allowed on every project
353 # * a group of projects : returns true if user is allowed on every project
354 # * nil with options[:global] set : check if user has at least one role allowed for this action,
354 # * nil with options[:global] set : check if user has at least one role allowed for this action,
355 # or falls back to Non Member / Anonymous permissions depending if the user is logged
355 # or falls back to Non Member / Anonymous permissions depending if the user is logged
356 def allowed_to?(action, project, options={})
356 def allowed_to?(action, project, options={})
357 if project && project.is_a?(Project)
357 if project && project.is_a?(Project)
358 # No action allowed on archived projects
358 # No action allowed on archived projects
359 return false unless project.active?
359 return false unless project.active?
360 # No action allowed on disabled modules
360 # No action allowed on disabled modules
361 return false unless project.allows_to?(action)
361 return false unless project.allows_to?(action)
362 # Admin users are authorized for anything else
362 # Admin users are authorized for anything else
363 return true if admin?
363 return true if admin?
364
364
365 roles = roles_for_project(project)
365 roles = roles_for_project(project)
366 return false unless roles
366 return false unless roles
367 roles.detect {|role| (project.is_public? || role.member?) && role.allowed_to?(action)}
367 roles.detect {|role| (project.is_public? || role.member?) && role.allowed_to?(action)}
368
368
369 elsif project && project.is_a?(Array)
369 elsif project && project.is_a?(Array)
370 # Authorize if user is authorized on every element of the array
370 # Authorize if user is authorized on every element of the array
371 project.inject do |memo,p|
371 project.map do |p|
372 memo && allowed_to?(action,p,options)
372 allowed_to?(action,p,options)
373 end.inject do |memo,p|
374 memo && p
373 end
375 end
374 elsif options[:global]
376 elsif options[:global]
375 # Admin users are always authorized
377 # Admin users are always authorized
376 return true if admin?
378 return true if admin?
377
379
378 # authorize if user has at least one role that has this permission
380 # authorize if user has at least one role that has this permission
379 roles = memberships.collect {|m| m.roles}.flatten.uniq
381 roles = memberships.collect {|m| m.roles}.flatten.uniq
380 roles.detect {|r| r.allowed_to?(action)} || (self.logged? ? Role.non_member.allowed_to?(action) : Role.anonymous.allowed_to?(action))
382 roles.detect {|r| r.allowed_to?(action)} || (self.logged? ? Role.non_member.allowed_to?(action) : Role.anonymous.allowed_to?(action))
381 else
383 else
382 false
384 false
383 end
385 end
384 end
386 end
385
387
386 # Is the user allowed to do the specified action on any project?
388 # Is the user allowed to do the specified action on any project?
387 # See allowed_to? for the actions and valid options.
389 # See allowed_to? for the actions and valid options.
388 def allowed_to_globally?(action, options)
390 def allowed_to_globally?(action, options)
389 allowed_to?(action, nil, options.reverse_merge(:global => true))
391 allowed_to?(action, nil, options.reverse_merge(:global => true))
390 end
392 end
391
393
392 # Utility method to help check if a user should be notified about an
394 # Utility method to help check if a user should be notified about an
393 # event.
395 # event.
394 #
396 #
395 # TODO: only supports Issue events currently
397 # TODO: only supports Issue events currently
396 def notify_about?(object)
398 def notify_about?(object)
397 case mail_notification.to_sym
399 case mail_notification.to_sym
398 when :all
400 when :all
399 true
401 true
400 when :selected
402 when :selected
401 # Handled by the Project
403 # Handled by the Project
402 when :none
404 when :none
403 false
405 false
404 when :only_my_events
406 when :only_my_events
405 if object.is_a?(Issue) && (object.author == self || object.assigned_to == self)
407 if object.is_a?(Issue) && (object.author == self || object.assigned_to == self)
406 true
408 true
407 else
409 else
408 false
410 false
409 end
411 end
410 when :only_assigned
412 when :only_assigned
411 if object.is_a?(Issue) && object.assigned_to == self
413 if object.is_a?(Issue) && object.assigned_to == self
412 true
414 true
413 else
415 else
414 false
416 false
415 end
417 end
416 when :only_owner
418 when :only_owner
417 if object.is_a?(Issue) && object.author == self
419 if object.is_a?(Issue) && object.author == self
418 true
420 true
419 else
421 else
420 false
422 false
421 end
423 end
422 else
424 else
423 false
425 false
424 end
426 end
425 end
427 end
426
428
427 def self.current=(user)
429 def self.current=(user)
428 @current_user = user
430 @current_user = user
429 end
431 end
430
432
431 def self.current
433 def self.current
432 @current_user ||= User.anonymous
434 @current_user ||= User.anonymous
433 end
435 end
434
436
435 # Returns the anonymous user. If the anonymous user does not exist, it is created. There can be only
437 # Returns the anonymous user. If the anonymous user does not exist, it is created. There can be only
436 # one anonymous user per database.
438 # one anonymous user per database.
437 def self.anonymous
439 def self.anonymous
438 anonymous_user = AnonymousUser.find(:first)
440 anonymous_user = AnonymousUser.find(:first)
439 if anonymous_user.nil?
441 if anonymous_user.nil?
440 anonymous_user = AnonymousUser.create(:lastname => 'Anonymous', :firstname => '', :mail => '', :login => '', :status => 0)
442 anonymous_user = AnonymousUser.create(:lastname => 'Anonymous', :firstname => '', :mail => '', :login => '', :status => 0)
441 raise 'Unable to create the anonymous user.' if anonymous_user.new_record?
443 raise 'Unable to create the anonymous user.' if anonymous_user.new_record?
442 end
444 end
443 anonymous_user
445 anonymous_user
444 end
446 end
445
447
446 protected
448 protected
447
449
448 def validate
450 def validate
449 # Password length validation based on setting
451 # Password length validation based on setting
450 if !password.nil? && password.size < Setting.password_min_length.to_i
452 if !password.nil? && password.size < Setting.password_min_length.to_i
451 errors.add(:password, :too_short, :count => Setting.password_min_length.to_i)
453 errors.add(:password, :too_short, :count => Setting.password_min_length.to_i)
452 end
454 end
453 end
455 end
454
456
455 private
457 private
456
458
457 # Return password digest
459 # Return password digest
458 def self.hash_password(clear_password)
460 def self.hash_password(clear_password)
459 Digest::SHA1.hexdigest(clear_password || "")
461 Digest::SHA1.hexdigest(clear_password || "")
460 end
462 end
461 end
463 end
462
464
463 class AnonymousUser < User
465 class AnonymousUser < User
464
466
465 def validate_on_create
467 def validate_on_create
466 # There should be only one AnonymousUser in the database
468 # There should be only one AnonymousUser in the database
467 errors.add_to_base 'An anonymous user already exists.' if AnonymousUser.find(:first)
469 errors.add_to_base 'An anonymous user already exists.' if AnonymousUser.find(:first)
468 end
470 end
469
471
470 def available_custom_fields
472 def available_custom_fields
471 []
473 []
472 end
474 end
473
475
474 # Overrides a few properties
476 # Overrides a few properties
475 def logged?; false end
477 def logged?; false end
476 def admin; false end
478 def admin; false end
477 def name(*args); I18n.t(:label_user_anonymous) end
479 def name(*args); I18n.t(:label_user_anonymous) end
478 def mail; nil end
480 def mail; nil end
479 def time_zone; nil end
481 def time_zone; nil end
480 def rss_key; nil end
482 def rss_key; nil end
481 end
483 end
@@ -1,527 +1,531
1 # redMine - project management software
1 # redMine - project management software
2 # Copyright (C) 2006 Jean-Philippe Lang
2 # Copyright (C) 2006 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 require File.dirname(__FILE__) + '/../test_helper'
18 require File.dirname(__FILE__) + '/../test_helper'
19
19
20 class UserTest < ActiveSupport::TestCase
20 class UserTest < ActiveSupport::TestCase
21 fixtures :users, :members, :projects, :roles, :member_roles, :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
113 User.find(2).destroy
113 User.find(2).destroy
114 assert_nil User.find_by_id(2)
114 assert_nil User.find_by_id(2)
115 assert Member.find_all_by_user_id(2).empty?
115 assert Member.find_all_by_user_id(2).empty?
116 end
116 end
117
117
118 def test_validate
118 def test_validate
119 @admin.login = ""
119 @admin.login = ""
120 assert !@admin.save
120 assert !@admin.save
121 assert_equal 1, @admin.errors.count
121 assert_equal 1, @admin.errors.count
122 end
122 end
123
123
124 context "User#try_to_login" do
124 context "User#try_to_login" do
125 should "fall-back to case-insensitive if user login is not found as-typed." do
125 should "fall-back to case-insensitive if user login is not found as-typed." do
126 user = User.try_to_login("AdMin", "admin")
126 user = User.try_to_login("AdMin", "admin")
127 assert_kind_of User, user
127 assert_kind_of User, user
128 assert_equal "admin", user.login
128 assert_equal "admin", user.login
129 end
129 end
130
130
131 should "select the exact matching user first" do
131 should "select the exact matching user first" do
132 case_sensitive_user = User.generate_with_protected!(:login => 'changed', :password => 'admin', :password_confirmation => 'admin')
132 case_sensitive_user = User.generate_with_protected!(:login => 'changed', :password => 'admin', :password_confirmation => 'admin')
133 # bypass validations to make it appear like existing data
133 # bypass validations to make it appear like existing data
134 case_sensitive_user.update_attribute(:login, 'ADMIN')
134 case_sensitive_user.update_attribute(:login, 'ADMIN')
135
135
136 user = User.try_to_login("ADMIN", "admin")
136 user = User.try_to_login("ADMIN", "admin")
137 assert_kind_of User, user
137 assert_kind_of User, user
138 assert_equal "ADMIN", user.login
138 assert_equal "ADMIN", user.login
139
139
140 end
140 end
141 end
141 end
142
142
143 def test_password
143 def test_password
144 user = User.try_to_login("admin", "admin")
144 user = User.try_to_login("admin", "admin")
145 assert_kind_of User, user
145 assert_kind_of User, user
146 assert_equal "admin", user.login
146 assert_equal "admin", user.login
147 user.password = "hello"
147 user.password = "hello"
148 assert user.save
148 assert user.save
149
149
150 user = User.try_to_login("admin", "hello")
150 user = User.try_to_login("admin", "hello")
151 assert_kind_of User, user
151 assert_kind_of User, user
152 assert_equal "admin", user.login
152 assert_equal "admin", user.login
153 assert_equal User.hash_password("hello"), user.hashed_password
153 assert_equal User.hash_password("hello"), user.hashed_password
154 end
154 end
155
155
156 def test_name_format
156 def test_name_format
157 assert_equal 'Smith, John', @jsmith.name(:lastname_coma_firstname)
157 assert_equal 'Smith, John', @jsmith.name(:lastname_coma_firstname)
158 Setting.user_format = :firstname_lastname
158 Setting.user_format = :firstname_lastname
159 assert_equal 'John Smith', @jsmith.reload.name
159 assert_equal 'John Smith', @jsmith.reload.name
160 Setting.user_format = :username
160 Setting.user_format = :username
161 assert_equal 'jsmith', @jsmith.reload.name
161 assert_equal 'jsmith', @jsmith.reload.name
162 end
162 end
163
163
164 def test_lock
164 def test_lock
165 user = User.try_to_login("jsmith", "jsmith")
165 user = User.try_to_login("jsmith", "jsmith")
166 assert_equal @jsmith, user
166 assert_equal @jsmith, user
167
167
168 @jsmith.status = User::STATUS_LOCKED
168 @jsmith.status = User::STATUS_LOCKED
169 assert @jsmith.save
169 assert @jsmith.save
170
170
171 user = User.try_to_login("jsmith", "jsmith")
171 user = User.try_to_login("jsmith", "jsmith")
172 assert_equal nil, user
172 assert_equal nil, user
173 end
173 end
174
174
175 if ldap_configured?
175 if ldap_configured?
176 context "#try_to_login using LDAP" do
176 context "#try_to_login using LDAP" do
177 context "with failed connection to the LDAP server" do
177 context "with failed connection to the LDAP server" do
178 should "return nil" do
178 should "return nil" do
179 @auth_source = AuthSourceLdap.find(1)
179 @auth_source = AuthSourceLdap.find(1)
180 AuthSource.any_instance.stubs(:initialize_ldap_con).raises(Net::LDAP::LdapError, 'Cannot connect')
180 AuthSource.any_instance.stubs(:initialize_ldap_con).raises(Net::LDAP::LdapError, 'Cannot connect')
181
181
182 assert_equal nil, User.try_to_login('edavis', 'wrong')
182 assert_equal nil, User.try_to_login('edavis', 'wrong')
183 end
183 end
184 end
184 end
185
185
186 context "with an unsuccessful authentication" do
186 context "with an unsuccessful authentication" do
187 should "return nil" do
187 should "return nil" do
188 assert_equal nil, User.try_to_login('edavis', 'wrong')
188 assert_equal nil, User.try_to_login('edavis', 'wrong')
189 end
189 end
190 end
190 end
191
191
192 context "on the fly registration" do
192 context "on the fly registration" do
193 setup do
193 setup do
194 @auth_source = AuthSourceLdap.find(1)
194 @auth_source = AuthSourceLdap.find(1)
195 end
195 end
196
196
197 context "with a successful authentication" do
197 context "with a successful authentication" do
198 should "create a new user account if it doesn't exist" do
198 should "create a new user account if it doesn't exist" do
199 assert_difference('User.count') do
199 assert_difference('User.count') do
200 user = User.try_to_login('edavis', '123456')
200 user = User.try_to_login('edavis', '123456')
201 assert !user.admin?
201 assert !user.admin?
202 end
202 end
203 end
203 end
204
204
205 should "retrieve existing user" do
205 should "retrieve existing user" do
206 user = User.try_to_login('edavis', '123456')
206 user = User.try_to_login('edavis', '123456')
207 user.admin = true
207 user.admin = true
208 user.save!
208 user.save!
209
209
210 assert_no_difference('User.count') do
210 assert_no_difference('User.count') do
211 user = User.try_to_login('edavis', '123456')
211 user = User.try_to_login('edavis', '123456')
212 assert user.admin?
212 assert user.admin?
213 end
213 end
214 end
214 end
215 end
215 end
216 end
216 end
217 end
217 end
218
218
219 else
219 else
220 puts "Skipping LDAP tests."
220 puts "Skipping LDAP tests."
221 end
221 end
222
222
223 def test_create_anonymous
223 def test_create_anonymous
224 AnonymousUser.delete_all
224 AnonymousUser.delete_all
225 anon = User.anonymous
225 anon = User.anonymous
226 assert !anon.new_record?
226 assert !anon.new_record?
227 assert_kind_of AnonymousUser, anon
227 assert_kind_of AnonymousUser, anon
228 end
228 end
229
229
230 should_have_one :rss_token
230 should_have_one :rss_token
231
231
232 def test_rss_key
232 def test_rss_key
233 assert_nil @jsmith.rss_token
233 assert_nil @jsmith.rss_token
234 key = @jsmith.rss_key
234 key = @jsmith.rss_key
235 assert_equal 40, key.length
235 assert_equal 40, key.length
236
236
237 @jsmith.reload
237 @jsmith.reload
238 assert_equal key, @jsmith.rss_key
238 assert_equal key, @jsmith.rss_key
239 end
239 end
240
240
241
241
242 should_have_one :api_token
242 should_have_one :api_token
243
243
244 context "User#api_key" do
244 context "User#api_key" do
245 should "generate a new one if the user doesn't have one" do
245 should "generate a new one if the user doesn't have one" do
246 user = User.generate_with_protected!(:api_token => nil)
246 user = User.generate_with_protected!(:api_token => nil)
247 assert_nil user.api_token
247 assert_nil user.api_token
248
248
249 key = user.api_key
249 key = user.api_key
250 assert_equal 40, key.length
250 assert_equal 40, key.length
251 user.reload
251 user.reload
252 assert_equal key, user.api_key
252 assert_equal key, user.api_key
253 end
253 end
254
254
255 should "return the existing api token value" do
255 should "return the existing api token value" do
256 user = User.generate_with_protected!
256 user = User.generate_with_protected!
257 token = Token.generate!(:action => 'api')
257 token = Token.generate!(:action => 'api')
258 user.api_token = token
258 user.api_token = token
259 assert user.save
259 assert user.save
260
260
261 assert_equal token.value, user.api_key
261 assert_equal token.value, user.api_key
262 end
262 end
263 end
263 end
264
264
265 context "User#find_by_api_key" do
265 context "User#find_by_api_key" do
266 should "return nil if no matching key is found" do
266 should "return nil if no matching key is found" do
267 assert_nil User.find_by_api_key('zzzzzzzzz')
267 assert_nil User.find_by_api_key('zzzzzzzzz')
268 end
268 end
269
269
270 should "return nil if the key is found for an inactive user" do
270 should "return nil if the key is found for an inactive user" do
271 user = User.generate_with_protected!(:status => User::STATUS_LOCKED)
271 user = User.generate_with_protected!(:status => User::STATUS_LOCKED)
272 token = Token.generate!(:action => 'api')
272 token = Token.generate!(:action => 'api')
273 user.api_token = token
273 user.api_token = token
274 user.save
274 user.save
275
275
276 assert_nil User.find_by_api_key(token.value)
276 assert_nil User.find_by_api_key(token.value)
277 end
277 end
278
278
279 should "return the user if the key is found for an active user" do
279 should "return the user if the key is found for an active user" do
280 user = User.generate_with_protected!(:status => User::STATUS_ACTIVE)
280 user = User.generate_with_protected!(:status => User::STATUS_ACTIVE)
281 token = Token.generate!(:action => 'api')
281 token = Token.generate!(:action => 'api')
282 user.api_token = token
282 user.api_token = token
283 user.save
283 user.save
284
284
285 assert_equal user, User.find_by_api_key(token.value)
285 assert_equal user, User.find_by_api_key(token.value)
286 end
286 end
287 end
287 end
288
288
289 def test_roles_for_project
289 def test_roles_for_project
290 # user with a role
290 # user with a role
291 roles = @jsmith.roles_for_project(Project.find(1))
291 roles = @jsmith.roles_for_project(Project.find(1))
292 assert_kind_of Role, roles.first
292 assert_kind_of Role, roles.first
293 assert_equal "Manager", roles.first.name
293 assert_equal "Manager", roles.first.name
294
294
295 # user with no role
295 # user with no role
296 assert_nil @dlopper.roles_for_project(Project.find(2)).detect {|role| role.member?}
296 assert_nil @dlopper.roles_for_project(Project.find(2)).detect {|role| role.member?}
297 end
297 end
298
298
299 def test_mail_notification_all
299 def test_mail_notification_all
300 @jsmith.mail_notification = 'all'
300 @jsmith.mail_notification = 'all'
301 @jsmith.notified_project_ids = []
301 @jsmith.notified_project_ids = []
302 @jsmith.save
302 @jsmith.save
303 @jsmith.reload
303 @jsmith.reload
304 assert @jsmith.projects.first.recipients.include?(@jsmith.mail)
304 assert @jsmith.projects.first.recipients.include?(@jsmith.mail)
305 end
305 end
306
306
307 def test_mail_notification_selected
307 def test_mail_notification_selected
308 @jsmith.mail_notification = 'selected'
308 @jsmith.mail_notification = 'selected'
309 @jsmith.notified_project_ids = [1]
309 @jsmith.notified_project_ids = [1]
310 @jsmith.save
310 @jsmith.save
311 @jsmith.reload
311 @jsmith.reload
312 assert Project.find(1).recipients.include?(@jsmith.mail)
312 assert Project.find(1).recipients.include?(@jsmith.mail)
313 end
313 end
314
314
315 def test_mail_notification_only_my_events
315 def test_mail_notification_only_my_events
316 @jsmith.mail_notification = 'only_my_events'
316 @jsmith.mail_notification = 'only_my_events'
317 @jsmith.notified_project_ids = []
317 @jsmith.notified_project_ids = []
318 @jsmith.save
318 @jsmith.save
319 @jsmith.reload
319 @jsmith.reload
320 assert !@jsmith.projects.first.recipients.include?(@jsmith.mail)
320 assert !@jsmith.projects.first.recipients.include?(@jsmith.mail)
321 end
321 end
322
322
323 def test_comments_sorting_preference
323 def test_comments_sorting_preference
324 assert !@jsmith.wants_comments_in_reverse_order?
324 assert !@jsmith.wants_comments_in_reverse_order?
325 @jsmith.pref.comments_sorting = 'asc'
325 @jsmith.pref.comments_sorting = 'asc'
326 assert !@jsmith.wants_comments_in_reverse_order?
326 assert !@jsmith.wants_comments_in_reverse_order?
327 @jsmith.pref.comments_sorting = 'desc'
327 @jsmith.pref.comments_sorting = 'desc'
328 assert @jsmith.wants_comments_in_reverse_order?
328 assert @jsmith.wants_comments_in_reverse_order?
329 end
329 end
330
330
331 def test_find_by_mail_should_be_case_insensitive
331 def test_find_by_mail_should_be_case_insensitive
332 u = User.find_by_mail('JSmith@somenet.foo')
332 u = User.find_by_mail('JSmith@somenet.foo')
333 assert_not_nil u
333 assert_not_nil u
334 assert_equal 'jsmith@somenet.foo', u.mail
334 assert_equal 'jsmith@somenet.foo', u.mail
335 end
335 end
336
336
337 def test_random_password
337 def test_random_password
338 u = User.new
338 u = User.new
339 u.random_password
339 u.random_password
340 assert !u.password.blank?
340 assert !u.password.blank?
341 assert !u.password_confirmation.blank?
341 assert !u.password_confirmation.blank?
342 end
342 end
343
343
344 context "#change_password_allowed?" do
344 context "#change_password_allowed?" do
345 should "be allowed if no auth source is set" do
345 should "be allowed if no auth source is set" do
346 user = User.generate_with_protected!
346 user = User.generate_with_protected!
347 assert user.change_password_allowed?
347 assert user.change_password_allowed?
348 end
348 end
349
349
350 should "delegate to the auth source" do
350 should "delegate to the auth source" do
351 user = User.generate_with_protected!
351 user = User.generate_with_protected!
352
352
353 allowed_auth_source = AuthSource.generate!
353 allowed_auth_source = AuthSource.generate!
354 def allowed_auth_source.allow_password_changes?; true; end
354 def allowed_auth_source.allow_password_changes?; true; end
355
355
356 denied_auth_source = AuthSource.generate!
356 denied_auth_source = AuthSource.generate!
357 def denied_auth_source.allow_password_changes?; false; end
357 def denied_auth_source.allow_password_changes?; false; end
358
358
359 assert user.change_password_allowed?
359 assert user.change_password_allowed?
360
360
361 user.auth_source = allowed_auth_source
361 user.auth_source = allowed_auth_source
362 assert user.change_password_allowed?, "User not allowed to change password, though auth source does"
362 assert user.change_password_allowed?, "User not allowed to change password, though auth source does"
363
363
364 user.auth_source = denied_auth_source
364 user.auth_source = denied_auth_source
365 assert !user.change_password_allowed?, "User allowed to change password, though auth source does not"
365 assert !user.change_password_allowed?, "User allowed to change password, though auth source does not"
366 end
366 end
367
367
368 end
368 end
369
369
370 context "#allowed_to?" do
370 context "#allowed_to?" do
371 context "with a unique project" do
371 context "with a unique project" do
372 should "return false if project is archived" do
372 should "return false if project is archived" do
373 project = Project.find(1)
373 project = Project.find(1)
374 Project.any_instance.stubs(:status).returns(Project::STATUS_ARCHIVED)
374 Project.any_instance.stubs(:status).returns(Project::STATUS_ARCHIVED)
375 assert ! @admin.allowed_to?(:view_issues, Project.find(1))
375 assert ! @admin.allowed_to?(:view_issues, Project.find(1))
376 end
376 end
377
377
378 should "return false if related module is disabled" do
378 should "return false if related module is disabled" do
379 project = Project.find(1)
379 project = Project.find(1)
380 project.enabled_module_names = ["issue_tracking"]
380 project.enabled_module_names = ["issue_tracking"]
381 assert @admin.allowed_to?(:add_issues, project)
381 assert @admin.allowed_to?(:add_issues, project)
382 assert ! @admin.allowed_to?(:view_wiki_pages, project)
382 assert ! @admin.allowed_to?(:view_wiki_pages, project)
383 end
383 end
384
384
385 should "authorize nearly everything for admin users" do
385 should "authorize nearly everything for admin users" do
386 project = Project.find(1)
386 project = Project.find(1)
387 assert ! @admin.member_of?(project)
387 assert ! @admin.member_of?(project)
388 %w(edit_issues delete_issues manage_news manage_documents manage_wiki).each do |p|
388 %w(edit_issues delete_issues manage_news manage_documents manage_wiki).each do |p|
389 assert @admin.allowed_to?(p.to_sym, project)
389 assert @admin.allowed_to?(p.to_sym, project)
390 end
390 end
391 end
391 end
392
392
393 should "authorize normal users depending on their roles" do
393 should "authorize normal users depending on their roles" do
394 project = Project.find(1)
394 project = Project.find(1)
395 assert @jsmith.allowed_to?(:delete_messages, project) #Manager
395 assert @jsmith.allowed_to?(:delete_messages, project) #Manager
396 assert ! @dlopper.allowed_to?(:delete_messages, project) #Developper
396 assert ! @dlopper.allowed_to?(:delete_messages, project) #Developper
397 end
397 end
398 end
398 end
399
399
400 context "with multiple projects" do
400 context "with multiple projects" do
401 should "return false if array is empty" do
401 should "return false if array is empty" do
402 assert ! @admin.allowed_to?(:view_project, [])
402 assert ! @admin.allowed_to?(:view_project, [])
403 end
403 end
404
404
405 should "return true only if user has permission on all these projects" do
405 should "return true only if user has permission on all these projects" do
406 assert @admin.allowed_to?(:view_project, Project.all)
406 assert @admin.allowed_to?(:view_project, Project.all)
407 assert ! @dlopper.allowed_to?(:view_project, Project.all) #cannot see Project(2)
407 assert ! @dlopper.allowed_to?(:view_project, Project.all) #cannot see Project(2)
408 assert @jsmith.allowed_to?(:edit_issues, @jsmith.projects) #Manager or Developer everywhere
408 assert @jsmith.allowed_to?(:edit_issues, @jsmith.projects) #Manager or Developer everywhere
409 assert ! @jsmith.allowed_to?(:delete_issue_watchers, @jsmith.projects) #Dev cannot delete_issue_watchers
409 assert ! @jsmith.allowed_to?(:delete_issue_watchers, @jsmith.projects) #Dev cannot delete_issue_watchers
410 end
410 end
411
412 should "behave correctly with arrays of 1 project" do
413 assert ! User.anonymous.allowed_to?(:delete_issues, [Project.first])
414 end
411 end
415 end
412
416
413 context "with options[:global]" do
417 context "with options[:global]" do
414 should "authorize if user has at least one role that has this permission" do
418 should "authorize if user has at least one role that has this permission" do
415 @dlopper2 = User.find(5) #only Developper on a project, not Manager anywhere
419 @dlopper2 = User.find(5) #only Developper on a project, not Manager anywhere
416 @anonymous = User.find(6)
420 @anonymous = User.find(6)
417 assert @jsmith.allowed_to?(:delete_issue_watchers, nil, :global => true)
421 assert @jsmith.allowed_to?(:delete_issue_watchers, nil, :global => true)
418 assert ! @dlopper2.allowed_to?(:delete_issue_watchers, nil, :global => true)
422 assert ! @dlopper2.allowed_to?(:delete_issue_watchers, nil, :global => true)
419 assert @dlopper2.allowed_to?(:add_issues, nil, :global => true)
423 assert @dlopper2.allowed_to?(:add_issues, nil, :global => true)
420 assert ! @anonymous.allowed_to?(:add_issues, nil, :global => true)
424 assert ! @anonymous.allowed_to?(:add_issues, nil, :global => true)
421 assert @anonymous.allowed_to?(:view_issues, nil, :global => true)
425 assert @anonymous.allowed_to?(:view_issues, nil, :global => true)
422 end
426 end
423 end
427 end
424 end
428 end
425
429
426 context "User#notify_about?" do
430 context "User#notify_about?" do
427 context "Issues" do
431 context "Issues" do
428 setup do
432 setup do
429 @project = Project.find(1)
433 @project = Project.find(1)
430 @author = User.generate_with_protected!
434 @author = User.generate_with_protected!
431 @assignee = User.generate_with_protected!
435 @assignee = User.generate_with_protected!
432 @issue = Issue.generate_for_project!(@project, :assigned_to => @assignee, :author => @author)
436 @issue = Issue.generate_for_project!(@project, :assigned_to => @assignee, :author => @author)
433 end
437 end
434
438
435 should "be true for a user with :all" do
439 should "be true for a user with :all" do
436 @author.update_attribute(:mail_notification, :all)
440 @author.update_attribute(:mail_notification, :all)
437 assert @author.notify_about?(@issue)
441 assert @author.notify_about?(@issue)
438 end
442 end
439
443
440 should "be false for a user with :none" do
444 should "be false for a user with :none" do
441 @author.update_attribute(:mail_notification, :none)
445 @author.update_attribute(:mail_notification, :none)
442 assert ! @author.notify_about?(@issue)
446 assert ! @author.notify_about?(@issue)
443 end
447 end
444
448
445 should "be false for a user with :only_my_events and isn't an author, creator, or assignee" do
449 should "be false for a user with :only_my_events and isn't an author, creator, or assignee" do
446 @user = User.generate_with_protected!(:mail_notification => :only_my_events)
450 @user = User.generate_with_protected!(:mail_notification => :only_my_events)
447 assert ! @user.notify_about?(@issue)
451 assert ! @user.notify_about?(@issue)
448 end
452 end
449
453
450 should "be true for a user with :only_my_events and is the author" do
454 should "be true for a user with :only_my_events and is the author" do
451 @author.update_attribute(:mail_notification, :only_my_events)
455 @author.update_attribute(:mail_notification, :only_my_events)
452 assert @author.notify_about?(@issue)
456 assert @author.notify_about?(@issue)
453 end
457 end
454
458
455 should "be true for a user with :only_my_events and is the assignee" do
459 should "be true for a user with :only_my_events and is the assignee" do
456 @assignee.update_attribute(:mail_notification, :only_my_events)
460 @assignee.update_attribute(:mail_notification, :only_my_events)
457 assert @assignee.notify_about?(@issue)
461 assert @assignee.notify_about?(@issue)
458 end
462 end
459
463
460 should "be true for a user with :only_assigned and is the assignee" do
464 should "be true for a user with :only_assigned and is the assignee" do
461 @assignee.update_attribute(:mail_notification, :only_assigned)
465 @assignee.update_attribute(:mail_notification, :only_assigned)
462 assert @assignee.notify_about?(@issue)
466 assert @assignee.notify_about?(@issue)
463 end
467 end
464
468
465 should "be false for a user with :only_assigned and is not the assignee" do
469 should "be false for a user with :only_assigned and is not the assignee" do
466 @author.update_attribute(:mail_notification, :only_assigned)
470 @author.update_attribute(:mail_notification, :only_assigned)
467 assert ! @author.notify_about?(@issue)
471 assert ! @author.notify_about?(@issue)
468 end
472 end
469
473
470 should "be true for a user with :only_owner and is the author" do
474 should "be true for a user with :only_owner and is the author" do
471 @author.update_attribute(:mail_notification, :only_owner)
475 @author.update_attribute(:mail_notification, :only_owner)
472 assert @author.notify_about?(@issue)
476 assert @author.notify_about?(@issue)
473 end
477 end
474
478
475 should "be false for a user with :only_owner and is not the author" do
479 should "be false for a user with :only_owner and is not the author" do
476 @assignee.update_attribute(:mail_notification, :only_owner)
480 @assignee.update_attribute(:mail_notification, :only_owner)
477 assert ! @assignee.notify_about?(@issue)
481 assert ! @assignee.notify_about?(@issue)
478 end
482 end
479
483
480 should "be false if the mail_notification is anything else" do
484 should "be false if the mail_notification is anything else" do
481 @assignee.update_attribute(:mail_notification, :somthing_else)
485 @assignee.update_attribute(:mail_notification, :somthing_else)
482 assert ! @assignee.notify_about?(@issue)
486 assert ! @assignee.notify_about?(@issue)
483 end
487 end
484
488
485 end
489 end
486
490
487 context "other events" do
491 context "other events" do
488 should 'be added and tested'
492 should 'be added and tested'
489 end
493 end
490 end
494 end
491
495
492 if Object.const_defined?(:OpenID)
496 if Object.const_defined?(:OpenID)
493
497
494 def test_setting_identity_url
498 def test_setting_identity_url
495 normalized_open_id_url = 'http://example.com/'
499 normalized_open_id_url = 'http://example.com/'
496 u = User.new( :identity_url => 'http://example.com/' )
500 u = User.new( :identity_url => 'http://example.com/' )
497 assert_equal normalized_open_id_url, u.identity_url
501 assert_equal normalized_open_id_url, u.identity_url
498 end
502 end
499
503
500 def test_setting_identity_url_without_trailing_slash
504 def test_setting_identity_url_without_trailing_slash
501 normalized_open_id_url = 'http://example.com/'
505 normalized_open_id_url = 'http://example.com/'
502 u = User.new( :identity_url => 'http://example.com' )
506 u = User.new( :identity_url => 'http://example.com' )
503 assert_equal normalized_open_id_url, u.identity_url
507 assert_equal normalized_open_id_url, u.identity_url
504 end
508 end
505
509
506 def test_setting_identity_url_without_protocol
510 def test_setting_identity_url_without_protocol
507 normalized_open_id_url = 'http://example.com/'
511 normalized_open_id_url = 'http://example.com/'
508 u = User.new( :identity_url => 'example.com' )
512 u = User.new( :identity_url => 'example.com' )
509 assert_equal normalized_open_id_url, u.identity_url
513 assert_equal normalized_open_id_url, u.identity_url
510 end
514 end
511
515
512 def test_setting_blank_identity_url
516 def test_setting_blank_identity_url
513 u = User.new( :identity_url => 'example.com' )
517 u = User.new( :identity_url => 'example.com' )
514 u.identity_url = ''
518 u.identity_url = ''
515 assert u.identity_url.blank?
519 assert u.identity_url.blank?
516 end
520 end
517
521
518 def test_setting_invalid_identity_url
522 def test_setting_invalid_identity_url
519 u = User.new( :identity_url => 'this is not an openid url' )
523 u = User.new( :identity_url => 'this is not an openid url' )
520 assert u.identity_url.blank?
524 assert u.identity_url.blank?
521 end
525 end
522
526
523 else
527 else
524 puts "Skipping openid tests."
528 puts "Skipping openid tests."
525 end
529 end
526
530
527 end
531 end
General Comments 0
You need to be logged in to leave comments. Login now