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