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