##// END OF EJS Templates
Adds random salt to user passwords (#7410)....
Jean-Philippe Lang -
r4816:ce84bb1a0194
parent child
Show More
@@ -0,0 +1,9
1 class AddUsersSalt < ActiveRecord::Migration
2 def self.up
3 add_column :users, :salt, :string, :limit => 64
4 end
5
6 def self.down
7 remove_column :users, :salt
8 end
9 end
@@ -0,0 +1,13
1 class SaltUserPasswords < ActiveRecord::Migration
2
3 def self.up
4 say_with_time "Salting user passwords, this may take some time..." do
5 User.salt_unsalted_passwords!
6 end
7 end
8
9 def self.down
10 # Unsalted passwords can not be restored
11 raise ActiveRecord::IrreversibleMigration, "Can't decypher salted passwords. This migration can not be rollback'ed."
12 end
13 end
@@ -1,542 +1,572
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, :class_name => 'Token', :conditions => "action='feeds'"
51 has_one :rss_token, :class_name => 'Token', :conditions => "action='feeds'"
52 has_one :api_token, :class_name => 'Token', :conditions => "action='api'"
52 has_one :api_token, :class_name => 'Token', :conditions => "action='api'"
53 belongs_to :auth_source
53 belongs_to :auth_source
54
54
55 # Active non-anonymous users scope
55 # Active non-anonymous users scope
56 named_scope :active, :conditions => "#{User.table_name}.status = #{STATUS_ACTIVE}"
56 named_scope :active, :conditions => "#{User.table_name}.status = #{STATUS_ACTIVE}"
57
57
58 acts_as_customizable
58 acts_as_customizable
59
59
60 attr_accessor :password, :password_confirmation
60 attr_accessor :password, :password_confirmation
61 attr_accessor :last_before_login_on
61 attr_accessor :last_before_login_on
62 # Prevents unauthorized assignments
62 # Prevents unauthorized assignments
63 attr_protected :login, :admin, :password, :password_confirmation, :hashed_password
63 attr_protected :login, :admin, :password, :password_confirmation, :hashed_password
64
64
65 validates_presence_of :login, :firstname, :lastname, :mail, :if => Proc.new { |user| !user.is_a?(AnonymousUser) }
65 validates_presence_of :login, :firstname, :lastname, :mail, :if => Proc.new { |user| !user.is_a?(AnonymousUser) }
66 validates_uniqueness_of :login, :if => Proc.new { |user| !user.login.blank? }, :case_sensitive => false
66 validates_uniqueness_of :login, :if => Proc.new { |user| !user.login.blank? }, :case_sensitive => false
67 validates_uniqueness_of :mail, :if => Proc.new { |user| !user.mail.blank? }, :case_sensitive => false
67 validates_uniqueness_of :mail, :if => Proc.new { |user| !user.mail.blank? }, :case_sensitive => false
68 # Login must contain lettres, numbers, underscores only
68 # Login must contain lettres, numbers, underscores only
69 validates_format_of :login, :with => /^[a-z0-9_\-@\.]*$/i
69 validates_format_of :login, :with => /^[a-z0-9_\-@\.]*$/i
70 validates_length_of :login, :maximum => 30
70 validates_length_of :login, :maximum => 30
71 validates_length_of :firstname, :lastname, :maximum => 30
71 validates_length_of :firstname, :lastname, :maximum => 30
72 validates_format_of :mail, :with => /^([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})$/i, :allow_nil => true
72 validates_format_of :mail, :with => /^([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})$/i, :allow_nil => true
73 validates_length_of :mail, :maximum => 60, :allow_nil => true
73 validates_length_of :mail, :maximum => 60, :allow_nil => true
74 validates_confirmation_of :password, :allow_nil => true
74 validates_confirmation_of :password, :allow_nil => true
75 validates_inclusion_of :mail_notification, :in => MAIL_NOTIFICATION_OPTIONS.collect(&:first), :allow_blank => true
75 validates_inclusion_of :mail_notification, :in => MAIL_NOTIFICATION_OPTIONS.collect(&:first), :allow_blank => true
76
76
77 before_destroy :remove_references_before_destroy
77 before_destroy :remove_references_before_destroy
78
78
79 def before_create
79 def before_create
80 self.mail_notification = Setting.default_notification_option if self.mail_notification.blank?
80 self.mail_notification = Setting.default_notification_option if self.mail_notification.blank?
81 true
81 true
82 end
82 end
83
83
84 def before_save
84 def before_save
85 # update hashed_password if password was set
85 # update hashed_password if password was set
86 self.hashed_password = User.hash_password(self.password) if self.password && self.auth_source_id.blank?
86 if self.password && self.auth_source_id.blank?
87 salt_password(password)
88 end
87 end
89 end
88
90
89 def reload(*args)
91 def reload(*args)
90 @name = nil
92 @name = nil
91 super
93 super
92 end
94 end
93
95
94 def mail=(arg)
96 def mail=(arg)
95 write_attribute(:mail, arg.to_s.strip)
97 write_attribute(:mail, arg.to_s.strip)
96 end
98 end
97
99
98 def identity_url=(url)
100 def identity_url=(url)
99 if url.blank?
101 if url.blank?
100 write_attribute(:identity_url, '')
102 write_attribute(:identity_url, '')
101 else
103 else
102 begin
104 begin
103 write_attribute(:identity_url, OpenIdAuthentication.normalize_identifier(url))
105 write_attribute(:identity_url, OpenIdAuthentication.normalize_identifier(url))
104 rescue OpenIdAuthentication::InvalidOpenId
106 rescue OpenIdAuthentication::InvalidOpenId
105 # Invlaid url, don't save
107 # Invlaid url, don't save
106 end
108 end
107 end
109 end
108 self.read_attribute(:identity_url)
110 self.read_attribute(:identity_url)
109 end
111 end
110
112
111 # Returns the user that matches provided login and password, or nil
113 # Returns the user that matches provided login and password, or nil
112 def self.try_to_login(login, password)
114 def self.try_to_login(login, password)
113 # Make sure no one can sign in with an empty password
115 # Make sure no one can sign in with an empty password
114 return nil if password.to_s.empty?
116 return nil if password.to_s.empty?
115 user = find_by_login(login)
117 user = find_by_login(login)
116 if user
118 if user
117 # user is already in local database
119 # user is already in local database
118 return nil if !user.active?
120 return nil if !user.active?
119 if user.auth_source
121 if user.auth_source
120 # user has an external authentication method
122 # user has an external authentication method
121 return nil unless user.auth_source.authenticate(login, password)
123 return nil unless user.auth_source.authenticate(login, password)
122 else
124 else
123 # authentication with local password
125 # authentication with local password
124 return nil unless User.hash_password(password) == user.hashed_password
126 return nil unless user.check_password?(password)
125 end
127 end
126 else
128 else
127 # user is not yet registered, try to authenticate with available sources
129 # user is not yet registered, try to authenticate with available sources
128 attrs = AuthSource.authenticate(login, password)
130 attrs = AuthSource.authenticate(login, password)
129 if attrs
131 if attrs
130 user = new(attrs)
132 user = new(attrs)
131 user.login = login
133 user.login = login
132 user.language = Setting.default_language
134 user.language = Setting.default_language
133 if user.save
135 if user.save
134 user.reload
136 user.reload
135 logger.info("User '#{user.login}' created from external auth source: #{user.auth_source.type} - #{user.auth_source.name}") if logger && user.auth_source
137 logger.info("User '#{user.login}' created from external auth source: #{user.auth_source.type} - #{user.auth_source.name}") if logger && user.auth_source
136 end
138 end
137 end
139 end
138 end
140 end
139 user.update_attribute(:last_login_on, Time.now) if user && !user.new_record?
141 user.update_attribute(:last_login_on, Time.now) if user && !user.new_record?
140 user
142 user
141 rescue => text
143 rescue => text
142 raise text
144 raise text
143 end
145 end
144
146
145 # Returns the user who matches the given autologin +key+ or nil
147 # Returns the user who matches the given autologin +key+ or nil
146 def self.try_to_autologin(key)
148 def self.try_to_autologin(key)
147 tokens = Token.find_all_by_action_and_value('autologin', key)
149 tokens = Token.find_all_by_action_and_value('autologin', key)
148 # Make sure there's only 1 token that matches the key
150 # Make sure there's only 1 token that matches the key
149 if tokens.size == 1
151 if tokens.size == 1
150 token = tokens.first
152 token = tokens.first
151 if (token.created_on > Setting.autologin.to_i.day.ago) && token.user && token.user.active?
153 if (token.created_on > Setting.autologin.to_i.day.ago) && token.user && token.user.active?
152 token.user.update_attribute(:last_login_on, Time.now)
154 token.user.update_attribute(:last_login_on, Time.now)
153 token.user
155 token.user
154 end
156 end
155 end
157 end
156 end
158 end
157
159
158 # Return user's full name for display
160 # Return user's full name for display
159 def name(formatter = nil)
161 def name(formatter = nil)
160 if formatter
162 if formatter
161 eval('"' + (USER_FORMATS[formatter] || USER_FORMATS[:firstname_lastname]) + '"')
163 eval('"' + (USER_FORMATS[formatter] || USER_FORMATS[:firstname_lastname]) + '"')
162 else
164 else
163 @name ||= eval('"' + (USER_FORMATS[Setting.user_format] || USER_FORMATS[:firstname_lastname]) + '"')
165 @name ||= eval('"' + (USER_FORMATS[Setting.user_format] || USER_FORMATS[:firstname_lastname]) + '"')
164 end
166 end
165 end
167 end
166
168
167 def active?
169 def active?
168 self.status == STATUS_ACTIVE
170 self.status == STATUS_ACTIVE
169 end
171 end
170
172
171 def registered?
173 def registered?
172 self.status == STATUS_REGISTERED
174 self.status == STATUS_REGISTERED
173 end
175 end
174
176
175 def locked?
177 def locked?
176 self.status == STATUS_LOCKED
178 self.status == STATUS_LOCKED
177 end
179 end
178
180
179 def activate
181 def activate
180 self.status = STATUS_ACTIVE
182 self.status = STATUS_ACTIVE
181 end
183 end
182
184
183 def register
185 def register
184 self.status = STATUS_REGISTERED
186 self.status = STATUS_REGISTERED
185 end
187 end
186
188
187 def lock
189 def lock
188 self.status = STATUS_LOCKED
190 self.status = STATUS_LOCKED
189 end
191 end
190
192
191 def activate!
193 def activate!
192 update_attribute(:status, STATUS_ACTIVE)
194 update_attribute(:status, STATUS_ACTIVE)
193 end
195 end
194
196
195 def register!
197 def register!
196 update_attribute(:status, STATUS_REGISTERED)
198 update_attribute(:status, STATUS_REGISTERED)
197 end
199 end
198
200
199 def lock!
201 def lock!
200 update_attribute(:status, STATUS_LOCKED)
202 update_attribute(:status, STATUS_LOCKED)
201 end
203 end
202
204
205 # Returns true if +clear_password+ is the correct user's password, otherwise false
203 def check_password?(clear_password)
206 def check_password?(clear_password)
204 if auth_source_id.present?
207 if auth_source_id.present?
205 auth_source.authenticate(self.login, clear_password)
208 auth_source.authenticate(self.login, clear_password)
206 else
209 else
207 User.hash_password(clear_password) == self.hashed_password
210 User.hash_password("#{salt}#{User.hash_password clear_password}") == hashed_password
211 end
208 end
212 end
213
214 # Generates a random salt and computes hashed_password for +clear_password+
215 # The hashed password is stored in the following form: SHA1(salt + SHA1(password))
216 def salt_password(clear_password)
217 self.salt = User.generate_salt
218 self.hashed_password = User.hash_password("#{salt}#{User.hash_password clear_password}")
209 end
219 end
210
220
211 # Does the backend storage allow this user to change their password?
221 # Does the backend storage allow this user to change their password?
212 def change_password_allowed?
222 def change_password_allowed?
213 return true if auth_source_id.blank?
223 return true if auth_source_id.blank?
214 return auth_source.allow_password_changes?
224 return auth_source.allow_password_changes?
215 end
225 end
216
226
217 # Generate and set a random password. Useful for automated user creation
227 # Generate and set a random password. Useful for automated user creation
218 # Based on Token#generate_token_value
228 # Based on Token#generate_token_value
219 #
229 #
220 def random_password
230 def random_password
221 chars = ("a".."z").to_a + ("A".."Z").to_a + ("0".."9").to_a
231 chars = ("a".."z").to_a + ("A".."Z").to_a + ("0".."9").to_a
222 password = ''
232 password = ''
223 40.times { |i| password << chars[rand(chars.size-1)] }
233 40.times { |i| password << chars[rand(chars.size-1)] }
224 self.password = password
234 self.password = password
225 self.password_confirmation = password
235 self.password_confirmation = password
226 self
236 self
227 end
237 end
228
238
229 def pref
239 def pref
230 self.preference ||= UserPreference.new(:user => self)
240 self.preference ||= UserPreference.new(:user => self)
231 end
241 end
232
242
233 def time_zone
243 def time_zone
234 @time_zone ||= (self.pref.time_zone.blank? ? nil : ActiveSupport::TimeZone[self.pref.time_zone])
244 @time_zone ||= (self.pref.time_zone.blank? ? nil : ActiveSupport::TimeZone[self.pref.time_zone])
235 end
245 end
236
246
237 def wants_comments_in_reverse_order?
247 def wants_comments_in_reverse_order?
238 self.pref[:comments_sorting] == 'desc'
248 self.pref[:comments_sorting] == 'desc'
239 end
249 end
240
250
241 # Return user's RSS key (a 40 chars long string), used to access feeds
251 # Return user's RSS key (a 40 chars long string), used to access feeds
242 def rss_key
252 def rss_key
243 token = self.rss_token || Token.create(:user => self, :action => 'feeds')
253 token = self.rss_token || Token.create(:user => self, :action => 'feeds')
244 token.value
254 token.value
245 end
255 end
246
256
247 # Return user's API key (a 40 chars long string), used to access the API
257 # Return user's API key (a 40 chars long string), used to access the API
248 def api_key
258 def api_key
249 token = self.api_token || self.create_api_token(:action => 'api')
259 token = self.api_token || self.create_api_token(:action => 'api')
250 token.value
260 token.value
251 end
261 end
252
262
253 # Return an array of project ids for which the user has explicitly turned mail notifications on
263 # Return an array of project ids for which the user has explicitly turned mail notifications on
254 def notified_projects_ids
264 def notified_projects_ids
255 @notified_projects_ids ||= memberships.select {|m| m.mail_notification?}.collect(&:project_id)
265 @notified_projects_ids ||= memberships.select {|m| m.mail_notification?}.collect(&:project_id)
256 end
266 end
257
267
258 def notified_project_ids=(ids)
268 def notified_project_ids=(ids)
259 Member.update_all("mail_notification = #{connection.quoted_false}", ['user_id = ?', id])
269 Member.update_all("mail_notification = #{connection.quoted_false}", ['user_id = ?', id])
260 Member.update_all("mail_notification = #{connection.quoted_true}", ['user_id = ? AND project_id IN (?)', id, ids]) if ids && !ids.empty?
270 Member.update_all("mail_notification = #{connection.quoted_true}", ['user_id = ? AND project_id IN (?)', id, ids]) if ids && !ids.empty?
261 @notified_projects_ids = nil
271 @notified_projects_ids = nil
262 notified_projects_ids
272 notified_projects_ids
263 end
273 end
264
274
265 def valid_notification_options
275 def valid_notification_options
266 self.class.valid_notification_options(self)
276 self.class.valid_notification_options(self)
267 end
277 end
268
278
269 # Only users that belong to more than 1 project can select projects for which they are notified
279 # Only users that belong to more than 1 project can select projects for which they are notified
270 def self.valid_notification_options(user=nil)
280 def self.valid_notification_options(user=nil)
271 # Note that @user.membership.size would fail since AR ignores
281 # Note that @user.membership.size would fail since AR ignores
272 # :include association option when doing a count
282 # :include association option when doing a count
273 if user.nil? || user.memberships.length < 1
283 if user.nil? || user.memberships.length < 1
274 MAIL_NOTIFICATION_OPTIONS.reject {|option| option.first == 'selected'}
284 MAIL_NOTIFICATION_OPTIONS.reject {|option| option.first == 'selected'}
275 else
285 else
276 MAIL_NOTIFICATION_OPTIONS
286 MAIL_NOTIFICATION_OPTIONS
277 end
287 end
278 end
288 end
279
289
280 # Find a user account by matching the exact login and then a case-insensitive
290 # Find a user account by matching the exact login and then a case-insensitive
281 # version. Exact matches will be given priority.
291 # version. Exact matches will be given priority.
282 def self.find_by_login(login)
292 def self.find_by_login(login)
283 # force string comparison to be case sensitive on MySQL
293 # force string comparison to be case sensitive on MySQL
284 type_cast = (ActiveRecord::Base.connection.adapter_name == 'MySQL') ? 'BINARY' : ''
294 type_cast = (ActiveRecord::Base.connection.adapter_name == 'MySQL') ? 'BINARY' : ''
285
295
286 # First look for an exact match
296 # First look for an exact match
287 user = first(:conditions => ["#{type_cast} login = ?", login])
297 user = first(:conditions => ["#{type_cast} login = ?", login])
288 # Fail over to case-insensitive if none was found
298 # Fail over to case-insensitive if none was found
289 user ||= first(:conditions => ["#{type_cast} LOWER(login) = ?", login.to_s.downcase])
299 user ||= first(:conditions => ["#{type_cast} LOWER(login) = ?", login.to_s.downcase])
290 end
300 end
291
301
292 def self.find_by_rss_key(key)
302 def self.find_by_rss_key(key)
293 token = Token.find_by_value(key)
303 token = Token.find_by_value(key)
294 token && token.user.active? ? token.user : nil
304 token && token.user.active? ? token.user : nil
295 end
305 end
296
306
297 def self.find_by_api_key(key)
307 def self.find_by_api_key(key)
298 token = Token.find_by_action_and_value('api', key)
308 token = Token.find_by_action_and_value('api', key)
299 token && token.user.active? ? token.user : nil
309 token && token.user.active? ? token.user : nil
300 end
310 end
301
311
302 # Makes find_by_mail case-insensitive
312 # Makes find_by_mail case-insensitive
303 def self.find_by_mail(mail)
313 def self.find_by_mail(mail)
304 find(:first, :conditions => ["LOWER(mail) = ?", mail.to_s.downcase])
314 find(:first, :conditions => ["LOWER(mail) = ?", mail.to_s.downcase])
305 end
315 end
306
316
307 def to_s
317 def to_s
308 name
318 name
309 end
319 end
310
320
311 # Returns the current day according to user's time zone
321 # Returns the current day according to user's time zone
312 def today
322 def today
313 if time_zone.nil?
323 if time_zone.nil?
314 Date.today
324 Date.today
315 else
325 else
316 Time.now.in_time_zone(time_zone).to_date
326 Time.now.in_time_zone(time_zone).to_date
317 end
327 end
318 end
328 end
319
329
320 def logged?
330 def logged?
321 true
331 true
322 end
332 end
323
333
324 def anonymous?
334 def anonymous?
325 !logged?
335 !logged?
326 end
336 end
327
337
328 # Return user's roles for project
338 # Return user's roles for project
329 def roles_for_project(project)
339 def roles_for_project(project)
330 roles = []
340 roles = []
331 # No role on archived projects
341 # No role on archived projects
332 return roles unless project && project.active?
342 return roles unless project && project.active?
333 if logged?
343 if logged?
334 # Find project membership
344 # Find project membership
335 membership = memberships.detect {|m| m.project_id == project.id}
345 membership = memberships.detect {|m| m.project_id == project.id}
336 if membership
346 if membership
337 roles = membership.roles
347 roles = membership.roles
338 else
348 else
339 @role_non_member ||= Role.non_member
349 @role_non_member ||= Role.non_member
340 roles << @role_non_member
350 roles << @role_non_member
341 end
351 end
342 else
352 else
343 @role_anonymous ||= Role.anonymous
353 @role_anonymous ||= Role.anonymous
344 roles << @role_anonymous
354 roles << @role_anonymous
345 end
355 end
346 roles
356 roles
347 end
357 end
348
358
349 # Return true if the user is a member of project
359 # Return true if the user is a member of project
350 def member_of?(project)
360 def member_of?(project)
351 !roles_for_project(project).detect {|role| role.member?}.nil?
361 !roles_for_project(project).detect {|role| role.member?}.nil?
352 end
362 end
353
363
354 # Return true if the user is allowed to do the specified action on a specific context
364 # Return true if the user is allowed to do the specified action on a specific context
355 # Action can be:
365 # Action can be:
356 # * a parameter-like Hash (eg. :controller => 'projects', :action => 'edit')
366 # * a parameter-like Hash (eg. :controller => 'projects', :action => 'edit')
357 # * a permission Symbol (eg. :edit_project)
367 # * a permission Symbol (eg. :edit_project)
358 # Context can be:
368 # Context can be:
359 # * a project : returns true if user is allowed to do the specified action on this project
369 # * a project : returns true if user is allowed to do the specified action on this project
360 # * a group of projects : returns true if user is allowed on every project
370 # * a group of projects : returns true if user is allowed on every project
361 # * nil with options[:global] set : check if user has at least one role allowed for this action,
371 # * nil with options[:global] set : check if user has at least one role allowed for this action,
362 # or falls back to Non Member / Anonymous permissions depending if the user is logged
372 # or falls back to Non Member / Anonymous permissions depending if the user is logged
363 def allowed_to?(action, context, options={})
373 def allowed_to?(action, context, options={})
364 if context && context.is_a?(Project)
374 if context && context.is_a?(Project)
365 # No action allowed on archived projects
375 # No action allowed on archived projects
366 return false unless context.active?
376 return false unless context.active?
367 # No action allowed on disabled modules
377 # No action allowed on disabled modules
368 return false unless context.allows_to?(action)
378 return false unless context.allows_to?(action)
369 # Admin users are authorized for anything else
379 # Admin users are authorized for anything else
370 return true if admin?
380 return true if admin?
371
381
372 roles = roles_for_project(context)
382 roles = roles_for_project(context)
373 return false unless roles
383 return false unless roles
374 roles.detect {|role| (context.is_public? || role.member?) && role.allowed_to?(action)}
384 roles.detect {|role| (context.is_public? || role.member?) && role.allowed_to?(action)}
375
385
376 elsif context && context.is_a?(Array)
386 elsif context && context.is_a?(Array)
377 # Authorize if user is authorized on every element of the array
387 # Authorize if user is authorized on every element of the array
378 context.map do |project|
388 context.map do |project|
379 allowed_to?(action,project,options)
389 allowed_to?(action,project,options)
380 end.inject do |memo,allowed|
390 end.inject do |memo,allowed|
381 memo && allowed
391 memo && allowed
382 end
392 end
383 elsif options[:global]
393 elsif options[:global]
384 # Admin users are always authorized
394 # Admin users are always authorized
385 return true if admin?
395 return true if admin?
386
396
387 # authorize if user has at least one role that has this permission
397 # authorize if user has at least one role that has this permission
388 roles = memberships.collect {|m| m.roles}.flatten.uniq
398 roles = memberships.collect {|m| m.roles}.flatten.uniq
389 roles.detect {|r| r.allowed_to?(action)} || (self.logged? ? Role.non_member.allowed_to?(action) : Role.anonymous.allowed_to?(action))
399 roles.detect {|r| r.allowed_to?(action)} || (self.logged? ? Role.non_member.allowed_to?(action) : Role.anonymous.allowed_to?(action))
390 else
400 else
391 false
401 false
392 end
402 end
393 end
403 end
394
404
395 # Is the user allowed to do the specified action on any project?
405 # Is the user allowed to do the specified action on any project?
396 # See allowed_to? for the actions and valid options.
406 # See allowed_to? for the actions and valid options.
397 def allowed_to_globally?(action, options)
407 def allowed_to_globally?(action, options)
398 allowed_to?(action, nil, options.reverse_merge(:global => true))
408 allowed_to?(action, nil, options.reverse_merge(:global => true))
399 end
409 end
400
410
401 safe_attributes 'login',
411 safe_attributes 'login',
402 'firstname',
412 'firstname',
403 'lastname',
413 'lastname',
404 'mail',
414 'mail',
405 'mail_notification',
415 'mail_notification',
406 'language',
416 'language',
407 'custom_field_values',
417 'custom_field_values',
408 'custom_fields',
418 'custom_fields',
409 'identity_url'
419 'identity_url'
410
420
411 safe_attributes 'status',
421 safe_attributes 'status',
412 'auth_source_id',
422 'auth_source_id',
413 :if => lambda {|user, current_user| current_user.admin?}
423 :if => lambda {|user, current_user| current_user.admin?}
414
424
415 safe_attributes 'group_ids',
425 safe_attributes 'group_ids',
416 :if => lambda {|user, current_user| current_user.admin? && !user.new_record?}
426 :if => lambda {|user, current_user| current_user.admin? && !user.new_record?}
417
427
418 # Utility method to help check if a user should be notified about an
428 # Utility method to help check if a user should be notified about an
419 # event.
429 # event.
420 #
430 #
421 # TODO: only supports Issue events currently
431 # TODO: only supports Issue events currently
422 def notify_about?(object)
432 def notify_about?(object)
423 case mail_notification
433 case mail_notification
424 when 'all'
434 when 'all'
425 true
435 true
426 when 'selected'
436 when 'selected'
427 # user receives notifications for created/assigned issues on unselected projects
437 # user receives notifications for created/assigned issues on unselected projects
428 if object.is_a?(Issue) && (object.author == self || object.assigned_to == self)
438 if object.is_a?(Issue) && (object.author == self || object.assigned_to == self)
429 true
439 true
430 else
440 else
431 false
441 false
432 end
442 end
433 when 'none'
443 when 'none'
434 false
444 false
435 when 'only_my_events'
445 when 'only_my_events'
436 if object.is_a?(Issue) && (object.author == self || object.assigned_to == self)
446 if object.is_a?(Issue) && (object.author == self || object.assigned_to == self)
437 true
447 true
438 else
448 else
439 false
449 false
440 end
450 end
441 when 'only_assigned'
451 when 'only_assigned'
442 if object.is_a?(Issue) && object.assigned_to == self
452 if object.is_a?(Issue) && object.assigned_to == self
443 true
453 true
444 else
454 else
445 false
455 false
446 end
456 end
447 when 'only_owner'
457 when 'only_owner'
448 if object.is_a?(Issue) && object.author == self
458 if object.is_a?(Issue) && object.author == self
449 true
459 true
450 else
460 else
451 false
461 false
452 end
462 end
453 else
463 else
454 false
464 false
455 end
465 end
456 end
466 end
457
467
458 def self.current=(user)
468 def self.current=(user)
459 @current_user = user
469 @current_user = user
460 end
470 end
461
471
462 def self.current
472 def self.current
463 @current_user ||= User.anonymous
473 @current_user ||= User.anonymous
464 end
474 end
465
475
466 # Returns the anonymous user. If the anonymous user does not exist, it is created. There can be only
476 # Returns the anonymous user. If the anonymous user does not exist, it is created. There can be only
467 # one anonymous user per database.
477 # one anonymous user per database.
468 def self.anonymous
478 def self.anonymous
469 anonymous_user = AnonymousUser.find(:first)
479 anonymous_user = AnonymousUser.find(:first)
470 if anonymous_user.nil?
480 if anonymous_user.nil?
471 anonymous_user = AnonymousUser.create(:lastname => 'Anonymous', :firstname => '', :mail => '', :login => '', :status => 0)
481 anonymous_user = AnonymousUser.create(:lastname => 'Anonymous', :firstname => '', :mail => '', :login => '', :status => 0)
472 raise 'Unable to create the anonymous user.' if anonymous_user.new_record?
482 raise 'Unable to create the anonymous user.' if anonymous_user.new_record?
473 end
483 end
474 anonymous_user
484 anonymous_user
475 end
485 end
476
486
487 # Salts all existing unsalted passwords
488 # It changes password storage scheme from SHA1(password) to SHA1(salt + SHA1(password))
489 # This method is used in the SaltPasswords migration and is to be kept as is
490 def self.salt_unsalted_passwords!
491 transaction do
492 User.find_each(:conditions => "salt IS NULL OR salt = ''") do |user|
493 next if user.hashed_password.blank?
494 salt = User.generate_salt
495 hashed_password = User.hash_password("#{salt}#{user.hashed_password}")
496 User.update_all("salt = '#{salt}', hashed_password = '#{hashed_password}'", ["id = ?", user.id] )
497 end
498 end
499 end
500
477 protected
501 protected
478
502
479 def validate
503 def validate
480 # Password length validation based on setting
504 # Password length validation based on setting
481 if !password.nil? && password.size < Setting.password_min_length.to_i
505 if !password.nil? && password.size < Setting.password_min_length.to_i
482 errors.add(:password, :too_short, :count => Setting.password_min_length.to_i)
506 errors.add(:password, :too_short, :count => Setting.password_min_length.to_i)
483 end
507 end
484 end
508 end
485
509
486 private
510 private
487
511
488 # Removes references that are not handled by associations
512 # Removes references that are not handled by associations
489 # Things that are not deleted are reassociated with the anonymous user
513 # Things that are not deleted are reassociated with the anonymous user
490 def remove_references_before_destroy
514 def remove_references_before_destroy
491 return if self.id.nil?
515 return if self.id.nil?
492
516
493 substitute = User.anonymous
517 substitute = User.anonymous
494 Attachment.update_all ['author_id = ?', substitute.id], ['author_id = ?', id]
518 Attachment.update_all ['author_id = ?', substitute.id], ['author_id = ?', id]
495 Comment.update_all ['author_id = ?', substitute.id], ['author_id = ?', id]
519 Comment.update_all ['author_id = ?', substitute.id], ['author_id = ?', id]
496 Issue.update_all ['author_id = ?', substitute.id], ['author_id = ?', id]
520 Issue.update_all ['author_id = ?', substitute.id], ['author_id = ?', id]
497 Issue.update_all 'assigned_to_id = NULL', ['assigned_to_id = ?', id]
521 Issue.update_all 'assigned_to_id = NULL', ['assigned_to_id = ?', id]
498 Journal.update_all ['user_id = ?', substitute.id], ['user_id = ?', id]
522 Journal.update_all ['user_id = ?', substitute.id], ['user_id = ?', id]
499 JournalDetail.update_all ['old_value = ?', substitute.id.to_s], ["property = 'attr' AND prop_key = 'assigned_to_id' AND old_value = ?", id.to_s]
523 JournalDetail.update_all ['old_value = ?', substitute.id.to_s], ["property = 'attr' AND prop_key = 'assigned_to_id' AND old_value = ?", id.to_s]
500 JournalDetail.update_all ['value = ?', substitute.id.to_s], ["property = 'attr' AND prop_key = 'assigned_to_id' AND value = ?", id.to_s]
524 JournalDetail.update_all ['value = ?', substitute.id.to_s], ["property = 'attr' AND prop_key = 'assigned_to_id' AND value = ?", id.to_s]
501 Message.update_all ['author_id = ?', substitute.id], ['author_id = ?', id]
525 Message.update_all ['author_id = ?', substitute.id], ['author_id = ?', id]
502 News.update_all ['author_id = ?', substitute.id], ['author_id = ?', id]
526 News.update_all ['author_id = ?', substitute.id], ['author_id = ?', id]
503 # Remove private queries and keep public ones
527 # Remove private queries and keep public ones
504 Query.delete_all ['user_id = ? AND is_public = ?', id, false]
528 Query.delete_all ['user_id = ? AND is_public = ?', id, false]
505 Query.update_all ['user_id = ?', substitute.id], ['user_id = ?', id]
529 Query.update_all ['user_id = ?', substitute.id], ['user_id = ?', id]
506 TimeEntry.update_all ['user_id = ?', substitute.id], ['user_id = ?', id]
530 TimeEntry.update_all ['user_id = ?', substitute.id], ['user_id = ?', id]
507 Token.delete_all ['user_id = ?', id]
531 Token.delete_all ['user_id = ?', id]
508 Watcher.delete_all ['user_id = ?', id]
532 Watcher.delete_all ['user_id = ?', id]
509 WikiContent.update_all ['author_id = ?', substitute.id], ['author_id = ?', id]
533 WikiContent.update_all ['author_id = ?', substitute.id], ['author_id = ?', id]
510 WikiContent::Version.update_all ['author_id = ?', substitute.id], ['author_id = ?', id]
534 WikiContent::Version.update_all ['author_id = ?', substitute.id], ['author_id = ?', id]
511 end
535 end
512
536
513 # Return password digest
537 # Return password digest
514 def self.hash_password(clear_password)
538 def self.hash_password(clear_password)
515 Digest::SHA1.hexdigest(clear_password || "")
539 Digest::SHA1.hexdigest(clear_password || "")
516 end
540 end
541
542 # Returns a 128bits random salt as a hex string (32 chars long)
543 def self.generate_salt
544 ActiveSupport::SecureRandom.hex(16)
545 end
546
517 end
547 end
518
548
519 class AnonymousUser < User
549 class AnonymousUser < User
520
550
521 def validate_on_create
551 def validate_on_create
522 # There should be only one AnonymousUser in the database
552 # There should be only one AnonymousUser in the database
523 errors.add_to_base 'An anonymous user already exists.' if AnonymousUser.find(:first)
553 errors.add_to_base 'An anonymous user already exists.' if AnonymousUser.find(:first)
524 end
554 end
525
555
526 def available_custom_fields
556 def available_custom_fields
527 []
557 []
528 end
558 end
529
559
530 # Overrides a few properties
560 # Overrides a few properties
531 def logged?; false end
561 def logged?; false end
532 def admin; false end
562 def admin; false end
533 def name(*args); I18n.t(:label_user_anonymous) end
563 def name(*args); I18n.t(:label_user_anonymous) end
534 def mail; nil end
564 def mail; nil end
535 def time_zone; nil end
565 def time_zone; nil end
536 def rss_key; nil end
566 def rss_key; nil end
537
567
538 # Anonymous user can not be destroyed
568 # Anonymous user can not be destroyed
539 def destroy
569 def destroy
540 false
570 false
541 end
571 end
542 end
572 end
@@ -1,386 +1,387
1 package Apache::Authn::Redmine;
1 package Apache::Authn::Redmine;
2
2
3 =head1 Apache::Authn::Redmine
3 =head1 Apache::Authn::Redmine
4
4
5 Redmine - a mod_perl module to authenticate webdav subversion users
5 Redmine - a mod_perl module to authenticate webdav subversion users
6 against redmine database
6 against redmine database
7
7
8 =head1 SYNOPSIS
8 =head1 SYNOPSIS
9
9
10 This module allow anonymous users to browse public project and
10 This module allow anonymous users to browse public project and
11 registred users to browse and commit their project. Authentication is
11 registred users to browse and commit their project. Authentication is
12 done against the redmine database or the LDAP configured in redmine.
12 done against the redmine database or the LDAP configured in redmine.
13
13
14 This method is far simpler than the one with pam_* and works with all
14 This method is far simpler than the one with pam_* and works with all
15 database without an hassle but you need to have apache/mod_perl on the
15 database without an hassle but you need to have apache/mod_perl on the
16 svn server.
16 svn server.
17
17
18 =head1 INSTALLATION
18 =head1 INSTALLATION
19
19
20 For this to automagically work, you need to have a recent reposman.rb
20 For this to automagically work, you need to have a recent reposman.rb
21 (after r860) and if you already use reposman, read the last section to
21 (after r860) and if you already use reposman, read the last section to
22 migrate.
22 migrate.
23
23
24 Sorry ruby users but you need some perl modules, at least mod_perl2,
24 Sorry ruby users but you need some perl modules, at least mod_perl2,
25 DBI and DBD::mysql (or the DBD driver for you database as it should
25 DBI and DBD::mysql (or the DBD driver for you database as it should
26 work on allmost all databases).
26 work on allmost all databases).
27
27
28 On debian/ubuntu you must do :
28 On debian/ubuntu you must do :
29
29
30 aptitude install libapache-dbi-perl libapache2-mod-perl2 libdbd-mysql-perl
30 aptitude install libapache-dbi-perl libapache2-mod-perl2 libdbd-mysql-perl
31
31
32 If your Redmine users use LDAP authentication, you will also need
32 If your Redmine users use LDAP authentication, you will also need
33 Authen::Simple::LDAP (and IO::Socket::SSL if LDAPS is used):
33 Authen::Simple::LDAP (and IO::Socket::SSL if LDAPS is used):
34
34
35 aptitude install libauthen-simple-ldap-perl libio-socket-ssl-perl
35 aptitude install libauthen-simple-ldap-perl libio-socket-ssl-perl
36
36
37 =head1 CONFIGURATION
37 =head1 CONFIGURATION
38
38
39 ## This module has to be in your perl path
39 ## This module has to be in your perl path
40 ## eg: /usr/lib/perl5/Apache/Authn/Redmine.pm
40 ## eg: /usr/lib/perl5/Apache/Authn/Redmine.pm
41 PerlLoadModule Apache::Authn::Redmine
41 PerlLoadModule Apache::Authn::Redmine
42 <Location /svn>
42 <Location /svn>
43 DAV svn
43 DAV svn
44 SVNParentPath "/var/svn"
44 SVNParentPath "/var/svn"
45
45
46 AuthType Basic
46 AuthType Basic
47 AuthName redmine
47 AuthName redmine
48 Require valid-user
48 Require valid-user
49
49
50 PerlAccessHandler Apache::Authn::Redmine::access_handler
50 PerlAccessHandler Apache::Authn::Redmine::access_handler
51 PerlAuthenHandler Apache::Authn::Redmine::authen_handler
51 PerlAuthenHandler Apache::Authn::Redmine::authen_handler
52
52
53 ## for mysql
53 ## for mysql
54 RedmineDSN "DBI:mysql:database=databasename;host=my.db.server"
54 RedmineDSN "DBI:mysql:database=databasename;host=my.db.server"
55 ## for postgres
55 ## for postgres
56 # RedmineDSN "DBI:Pg:dbname=databasename;host=my.db.server"
56 # RedmineDSN "DBI:Pg:dbname=databasename;host=my.db.server"
57
57
58 RedmineDbUser "redmine"
58 RedmineDbUser "redmine"
59 RedmineDbPass "password"
59 RedmineDbPass "password"
60 ## Optional where clause (fulltext search would be slow and
60 ## Optional where clause (fulltext search would be slow and
61 ## database dependant).
61 ## database dependant).
62 # RedmineDbWhereClause "and members.role_id IN (1,2)"
62 # RedmineDbWhereClause "and members.role_id IN (1,2)"
63 ## Optional credentials cache size
63 ## Optional credentials cache size
64 # RedmineCacheCredsMax 50
64 # RedmineCacheCredsMax 50
65 </Location>
65 </Location>
66
66
67 To be able to browse repository inside redmine, you must add something
67 To be able to browse repository inside redmine, you must add something
68 like that :
68 like that :
69
69
70 <Location /svn-private>
70 <Location /svn-private>
71 DAV svn
71 DAV svn
72 SVNParentPath "/var/svn"
72 SVNParentPath "/var/svn"
73 Order deny,allow
73 Order deny,allow
74 Deny from all
74 Deny from all
75 # only allow reading orders
75 # only allow reading orders
76 <Limit GET PROPFIND OPTIONS REPORT>
76 <Limit GET PROPFIND OPTIONS REPORT>
77 Allow from redmine.server.ip
77 Allow from redmine.server.ip
78 </Limit>
78 </Limit>
79 </Location>
79 </Location>
80
80
81 and you will have to use this reposman.rb command line to create repository :
81 and you will have to use this reposman.rb command line to create repository :
82
82
83 reposman.rb --redmine my.redmine.server --svn-dir /var/svn --owner www-data -u http://svn.server/svn-private/
83 reposman.rb --redmine my.redmine.server --svn-dir /var/svn --owner www-data -u http://svn.server/svn-private/
84
84
85 =head1 MIGRATION FROM OLDER RELEASES
85 =head1 MIGRATION FROM OLDER RELEASES
86
86
87 If you use an older reposman.rb (r860 or before), you need to change
87 If you use an older reposman.rb (r860 or before), you need to change
88 rights on repositories to allow the apache user to read and write
88 rights on repositories to allow the apache user to read and write
89 S<them :>
89 S<them :>
90
90
91 sudo chown -R www-data /var/svn/*
91 sudo chown -R www-data /var/svn/*
92 sudo chmod -R u+w /var/svn/*
92 sudo chmod -R u+w /var/svn/*
93
93
94 And you need to upgrade at least reposman.rb (after r860).
94 And you need to upgrade at least reposman.rb (after r860).
95
95
96 =cut
96 =cut
97
97
98 use strict;
98 use strict;
99 use warnings FATAL => 'all', NONFATAL => 'redefine';
99 use warnings FATAL => 'all', NONFATAL => 'redefine';
100
100
101 use DBI;
101 use DBI;
102 use Digest::SHA1;
102 use Digest::SHA1;
103 # optional module for LDAP authentication
103 # optional module for LDAP authentication
104 my $CanUseLDAPAuth = eval("use Authen::Simple::LDAP; 1");
104 my $CanUseLDAPAuth = eval("use Authen::Simple::LDAP; 1");
105
105
106 use Apache2::Module;
106 use Apache2::Module;
107 use Apache2::Access;
107 use Apache2::Access;
108 use Apache2::ServerRec qw();
108 use Apache2::ServerRec qw();
109 use Apache2::RequestRec qw();
109 use Apache2::RequestRec qw();
110 use Apache2::RequestUtil qw();
110 use Apache2::RequestUtil qw();
111 use Apache2::Const qw(:common :override :cmd_how);
111 use Apache2::Const qw(:common :override :cmd_how);
112 use APR::Pool ();
112 use APR::Pool ();
113 use APR::Table ();
113 use APR::Table ();
114
114
115 # use Apache2::Directive qw();
115 # use Apache2::Directive qw();
116
116
117 my @directives = (
117 my @directives = (
118 {
118 {
119 name => 'RedmineDSN',
119 name => 'RedmineDSN',
120 req_override => OR_AUTHCFG,
120 req_override => OR_AUTHCFG,
121 args_how => TAKE1,
121 args_how => TAKE1,
122 errmsg => 'Dsn in format used by Perl DBI. eg: "DBI:Pg:dbname=databasename;host=my.db.server"',
122 errmsg => 'Dsn in format used by Perl DBI. eg: "DBI:Pg:dbname=databasename;host=my.db.server"',
123 },
123 },
124 {
124 {
125 name => 'RedmineDbUser',
125 name => 'RedmineDbUser',
126 req_override => OR_AUTHCFG,
126 req_override => OR_AUTHCFG,
127 args_how => TAKE1,
127 args_how => TAKE1,
128 },
128 },
129 {
129 {
130 name => 'RedmineDbPass',
130 name => 'RedmineDbPass',
131 req_override => OR_AUTHCFG,
131 req_override => OR_AUTHCFG,
132 args_how => TAKE1,
132 args_how => TAKE1,
133 },
133 },
134 {
134 {
135 name => 'RedmineDbWhereClause',
135 name => 'RedmineDbWhereClause',
136 req_override => OR_AUTHCFG,
136 req_override => OR_AUTHCFG,
137 args_how => TAKE1,
137 args_how => TAKE1,
138 },
138 },
139 {
139 {
140 name => 'RedmineCacheCredsMax',
140 name => 'RedmineCacheCredsMax',
141 req_override => OR_AUTHCFG,
141 req_override => OR_AUTHCFG,
142 args_how => TAKE1,
142 args_how => TAKE1,
143 errmsg => 'RedmineCacheCredsMax must be decimal number',
143 errmsg => 'RedmineCacheCredsMax must be decimal number',
144 },
144 },
145 );
145 );
146
146
147 sub RedmineDSN {
147 sub RedmineDSN {
148 my ($self, $parms, $arg) = @_;
148 my ($self, $parms, $arg) = @_;
149 $self->{RedmineDSN} = $arg;
149 $self->{RedmineDSN} = $arg;
150 my $query = "SELECT
150 my $query = "SELECT
151 hashed_password, auth_source_id, permissions
151 hashed_password, salt, auth_source_id, permissions
152 FROM members, projects, users, roles, member_roles
152 FROM members, projects, users, roles, member_roles
153 WHERE
153 WHERE
154 projects.id=members.project_id
154 projects.id=members.project_id
155 AND member_roles.member_id=members.id
155 AND member_roles.member_id=members.id
156 AND users.id=members.user_id
156 AND users.id=members.user_id
157 AND roles.id=member_roles.role_id
157 AND roles.id=member_roles.role_id
158 AND users.status=1
158 AND users.status=1
159 AND login=?
159 AND login=?
160 AND identifier=? ";
160 AND identifier=? ";
161 $self->{RedmineQuery} = trim($query);
161 $self->{RedmineQuery} = trim($query);
162 }
162 }
163
163
164 sub RedmineDbUser { set_val('RedmineDbUser', @_); }
164 sub RedmineDbUser { set_val('RedmineDbUser', @_); }
165 sub RedmineDbPass { set_val('RedmineDbPass', @_); }
165 sub RedmineDbPass { set_val('RedmineDbPass', @_); }
166 sub RedmineDbWhereClause {
166 sub RedmineDbWhereClause {
167 my ($self, $parms, $arg) = @_;
167 my ($self, $parms, $arg) = @_;
168 $self->{RedmineQuery} = trim($self->{RedmineQuery}.($arg ? $arg : "")." ");
168 $self->{RedmineQuery} = trim($self->{RedmineQuery}.($arg ? $arg : "")." ");
169 }
169 }
170
170
171 sub RedmineCacheCredsMax {
171 sub RedmineCacheCredsMax {
172 my ($self, $parms, $arg) = @_;
172 my ($self, $parms, $arg) = @_;
173 if ($arg) {
173 if ($arg) {
174 $self->{RedmineCachePool} = APR::Pool->new;
174 $self->{RedmineCachePool} = APR::Pool->new;
175 $self->{RedmineCacheCreds} = APR::Table::make($self->{RedmineCachePool}, $arg);
175 $self->{RedmineCacheCreds} = APR::Table::make($self->{RedmineCachePool}, $arg);
176 $self->{RedmineCacheCredsCount} = 0;
176 $self->{RedmineCacheCredsCount} = 0;
177 $self->{RedmineCacheCredsMax} = $arg;
177 $self->{RedmineCacheCredsMax} = $arg;
178 }
178 }
179 }
179 }
180
180
181 sub trim {
181 sub trim {
182 my $string = shift;
182 my $string = shift;
183 $string =~ s/\s{2,}/ /g;
183 $string =~ s/\s{2,}/ /g;
184 return $string;
184 return $string;
185 }
185 }
186
186
187 sub set_val {
187 sub set_val {
188 my ($key, $self, $parms, $arg) = @_;
188 my ($key, $self, $parms, $arg) = @_;
189 $self->{$key} = $arg;
189 $self->{$key} = $arg;
190 }
190 }
191
191
192 Apache2::Module::add(__PACKAGE__, \@directives);
192 Apache2::Module::add(__PACKAGE__, \@directives);
193
193
194
194
195 my %read_only_methods = map { $_ => 1 } qw/GET PROPFIND REPORT OPTIONS/;
195 my %read_only_methods = map { $_ => 1 } qw/GET PROPFIND REPORT OPTIONS/;
196
196
197 sub access_handler {
197 sub access_handler {
198 my $r = shift;
198 my $r = shift;
199
199
200 unless ($r->some_auth_required) {
200 unless ($r->some_auth_required) {
201 $r->log_reason("No authentication has been configured");
201 $r->log_reason("No authentication has been configured");
202 return FORBIDDEN;
202 return FORBIDDEN;
203 }
203 }
204
204
205 my $method = $r->method;
205 my $method = $r->method;
206 return OK unless defined $read_only_methods{$method};
206 return OK unless defined $read_only_methods{$method};
207
207
208 my $project_id = get_project_identifier($r);
208 my $project_id = get_project_identifier($r);
209
209
210 $r->set_handlers(PerlAuthenHandler => [\&OK])
210 $r->set_handlers(PerlAuthenHandler => [\&OK])
211 if is_public_project($project_id, $r);
211 if is_public_project($project_id, $r);
212
212
213 return OK
213 return OK
214 }
214 }
215
215
216 sub authen_handler {
216 sub authen_handler {
217 my $r = shift;
217 my $r = shift;
218
218
219 my ($res, $redmine_pass) = $r->get_basic_auth_pw();
219 my ($res, $redmine_pass) = $r->get_basic_auth_pw();
220 return $res unless $res == OK;
220 return $res unless $res == OK;
221
221
222 if (is_member($r->user, $redmine_pass, $r)) {
222 if (is_member($r->user, $redmine_pass, $r)) {
223 return OK;
223 return OK;
224 } else {
224 } else {
225 $r->note_auth_failure();
225 $r->note_auth_failure();
226 return AUTH_REQUIRED;
226 return AUTH_REQUIRED;
227 }
227 }
228 }
228 }
229
229
230 # check if authentication is forced
230 # check if authentication is forced
231 sub is_authentication_forced {
231 sub is_authentication_forced {
232 my $r = shift;
232 my $r = shift;
233
233
234 my $dbh = connect_database($r);
234 my $dbh = connect_database($r);
235 my $sth = $dbh->prepare(
235 my $sth = $dbh->prepare(
236 "SELECT value FROM settings where settings.name = 'login_required';"
236 "SELECT value FROM settings where settings.name = 'login_required';"
237 );
237 );
238
238
239 $sth->execute();
239 $sth->execute();
240 my $ret = 0;
240 my $ret = 0;
241 if (my @row = $sth->fetchrow_array) {
241 if (my @row = $sth->fetchrow_array) {
242 if ($row[0] eq "1" || $row[0] eq "t") {
242 if ($row[0] eq "1" || $row[0] eq "t") {
243 $ret = 1;
243 $ret = 1;
244 }
244 }
245 }
245 }
246 $sth->finish();
246 $sth->finish();
247 undef $sth;
247 undef $sth;
248
248
249 $dbh->disconnect();
249 $dbh->disconnect();
250 undef $dbh;
250 undef $dbh;
251
251
252 $ret;
252 $ret;
253 }
253 }
254
254
255 sub is_public_project {
255 sub is_public_project {
256 my $project_id = shift;
256 my $project_id = shift;
257 my $r = shift;
257 my $r = shift;
258
258
259 if (is_authentication_forced($r)) {
259 if (is_authentication_forced($r)) {
260 return 0;
260 return 0;
261 }
261 }
262
262
263 my $dbh = connect_database($r);
263 my $dbh = connect_database($r);
264 my $sth = $dbh->prepare(
264 my $sth = $dbh->prepare(
265 "SELECT is_public FROM projects WHERE projects.identifier = ?;"
265 "SELECT is_public FROM projects WHERE projects.identifier = ?;"
266 );
266 );
267
267
268 $sth->execute($project_id);
268 $sth->execute($project_id);
269 my $ret = 0;
269 my $ret = 0;
270 if (my @row = $sth->fetchrow_array) {
270 if (my @row = $sth->fetchrow_array) {
271 if ($row[0] eq "1" || $row[0] eq "t") {
271 if ($row[0] eq "1" || $row[0] eq "t") {
272 $ret = 1;
272 $ret = 1;
273 }
273 }
274 }
274 }
275 $sth->finish();
275 $sth->finish();
276 undef $sth;
276 undef $sth;
277 $dbh->disconnect();
277 $dbh->disconnect();
278 undef $dbh;
278 undef $dbh;
279
279
280 $ret;
280 $ret;
281 }
281 }
282
282
283 # perhaps we should use repository right (other read right) to check public access.
283 # perhaps we should use repository right (other read right) to check public access.
284 # it could be faster BUT it doesn't work for the moment.
284 # it could be faster BUT it doesn't work for the moment.
285 # sub is_public_project_by_file {
285 # sub is_public_project_by_file {
286 # my $project_id = shift;
286 # my $project_id = shift;
287 # my $r = shift;
287 # my $r = shift;
288
288
289 # my $tree = Apache2::Directive::conftree();
289 # my $tree = Apache2::Directive::conftree();
290 # my $node = $tree->lookup('Location', $r->location);
290 # my $node = $tree->lookup('Location', $r->location);
291 # my $hash = $node->as_hash;
291 # my $hash = $node->as_hash;
292
292
293 # my $svnparentpath = $hash->{SVNParentPath};
293 # my $svnparentpath = $hash->{SVNParentPath};
294 # my $repos_path = $svnparentpath . "/" . $project_id;
294 # my $repos_path = $svnparentpath . "/" . $project_id;
295 # return 1 if (stat($repos_path))[2] & 00007;
295 # return 1 if (stat($repos_path))[2] & 00007;
296 # }
296 # }
297
297
298 sub is_member {
298 sub is_member {
299 my $redmine_user = shift;
299 my $redmine_user = shift;
300 my $redmine_pass = shift;
300 my $redmine_pass = shift;
301 my $r = shift;
301 my $r = shift;
302
302
303 my $dbh = connect_database($r);
303 my $dbh = connect_database($r);
304 my $project_id = get_project_identifier($r);
304 my $project_id = get_project_identifier($r);
305
305
306 my $pass_digest = Digest::SHA1::sha1_hex($redmine_pass);
306 my $pass_digest = Digest::SHA1::sha1_hex($redmine_pass);
307
307
308 my $cfg = Apache2::Module::get_config(__PACKAGE__, $r->server, $r->per_dir_config);
308 my $cfg = Apache2::Module::get_config(__PACKAGE__, $r->server, $r->per_dir_config);
309 my $usrprojpass;
309 my $usrprojpass;
310 if ($cfg->{RedmineCacheCredsMax}) {
310 if ($cfg->{RedmineCacheCredsMax}) {
311 $usrprojpass = $cfg->{RedmineCacheCreds}->get($redmine_user.":".$project_id);
311 $usrprojpass = $cfg->{RedmineCacheCreds}->get($redmine_user.":".$project_id);
312 return 1 if (defined $usrprojpass and ($usrprojpass eq $pass_digest));
312 return 1 if (defined $usrprojpass and ($usrprojpass eq $pass_digest));
313 }
313 }
314 my $query = $cfg->{RedmineQuery};
314 my $query = $cfg->{RedmineQuery};
315 my $sth = $dbh->prepare($query);
315 my $sth = $dbh->prepare($query);
316 $sth->execute($redmine_user, $project_id);
316 $sth->execute($redmine_user, $project_id);
317
317
318 my $ret;
318 my $ret;
319 while (my ($hashed_password, $auth_source_id, $permissions) = $sth->fetchrow_array) {
319 while (my ($hashed_password, $salt, $auth_source_id, $permissions) = $sth->fetchrow_array) {
320
320
321 unless ($auth_source_id) {
321 unless ($auth_source_id) {
322 my $method = $r->method;
322 my $method = $r->method;
323 if ($hashed_password eq $pass_digest && ((defined $read_only_methods{$method} && $permissions =~ /:browse_repository/) || $permissions =~ /:commit_access/) ) {
323 my $salted_password = Digest::SHA1::sha1_hex($salt.$pass_digest);
324 if ($hashed_password eq $salted_password && ((defined $read_only_methods{$method} && $permissions =~ /:browse_repository/) || $permissions =~ /:commit_access/) ) {
324 $ret = 1;
325 $ret = 1;
325 last;
326 last;
326 }
327 }
327 } elsif ($CanUseLDAPAuth) {
328 } elsif ($CanUseLDAPAuth) {
328 my $sthldap = $dbh->prepare(
329 my $sthldap = $dbh->prepare(
329 "SELECT host,port,tls,account,account_password,base_dn,attr_login from auth_sources WHERE id = ?;"
330 "SELECT host,port,tls,account,account_password,base_dn,attr_login from auth_sources WHERE id = ?;"
330 );
331 );
331 $sthldap->execute($auth_source_id);
332 $sthldap->execute($auth_source_id);
332 while (my @rowldap = $sthldap->fetchrow_array) {
333 while (my @rowldap = $sthldap->fetchrow_array) {
333 my $ldap = Authen::Simple::LDAP->new(
334 my $ldap = Authen::Simple::LDAP->new(
334 host => ($rowldap[2] eq "1" || $rowldap[2] eq "t") ? "ldaps://$rowldap[0]:$rowldap[1]" : $rowldap[0],
335 host => ($rowldap[2] eq "1" || $rowldap[2] eq "t") ? "ldaps://$rowldap[0]:$rowldap[1]" : $rowldap[0],
335 port => $rowldap[1],
336 port => $rowldap[1],
336 basedn => $rowldap[5],
337 basedn => $rowldap[5],
337 binddn => $rowldap[3] ? $rowldap[3] : "",
338 binddn => $rowldap[3] ? $rowldap[3] : "",
338 bindpw => $rowldap[4] ? $rowldap[4] : "",
339 bindpw => $rowldap[4] ? $rowldap[4] : "",
339 filter => "(".$rowldap[6]."=%s)"
340 filter => "(".$rowldap[6]."=%s)"
340 );
341 );
341 my $method = $r->method;
342 my $method = $r->method;
342 $ret = 1 if ($ldap->authenticate($redmine_user, $redmine_pass) && ((defined $read_only_methods{$method} && $permissions =~ /:browse_repository/) || $permissions =~ /:commit_access/));
343 $ret = 1 if ($ldap->authenticate($redmine_user, $redmine_pass) && ((defined $read_only_methods{$method} && $permissions =~ /:browse_repository/) || $permissions =~ /:commit_access/));
343
344
344 }
345 }
345 $sthldap->finish();
346 $sthldap->finish();
346 undef $sthldap;
347 undef $sthldap;
347 }
348 }
348 }
349 }
349 $sth->finish();
350 $sth->finish();
350 undef $sth;
351 undef $sth;
351 $dbh->disconnect();
352 $dbh->disconnect();
352 undef $dbh;
353 undef $dbh;
353
354
354 if ($cfg->{RedmineCacheCredsMax} and $ret) {
355 if ($cfg->{RedmineCacheCredsMax} and $ret) {
355 if (defined $usrprojpass) {
356 if (defined $usrprojpass) {
356 $cfg->{RedmineCacheCreds}->set($redmine_user.":".$project_id, $pass_digest);
357 $cfg->{RedmineCacheCreds}->set($redmine_user.":".$project_id, $pass_digest);
357 } else {
358 } else {
358 if ($cfg->{RedmineCacheCredsCount} < $cfg->{RedmineCacheCredsMax}) {
359 if ($cfg->{RedmineCacheCredsCount} < $cfg->{RedmineCacheCredsMax}) {
359 $cfg->{RedmineCacheCreds}->set($redmine_user.":".$project_id, $pass_digest);
360 $cfg->{RedmineCacheCreds}->set($redmine_user.":".$project_id, $pass_digest);
360 $cfg->{RedmineCacheCredsCount}++;
361 $cfg->{RedmineCacheCredsCount}++;
361 } else {
362 } else {
362 $cfg->{RedmineCacheCreds}->clear();
363 $cfg->{RedmineCacheCreds}->clear();
363 $cfg->{RedmineCacheCredsCount} = 0;
364 $cfg->{RedmineCacheCredsCount} = 0;
364 }
365 }
365 }
366 }
366 }
367 }
367
368
368 $ret;
369 $ret;
369 }
370 }
370
371
371 sub get_project_identifier {
372 sub get_project_identifier {
372 my $r = shift;
373 my $r = shift;
373
374
374 my $location = $r->location;
375 my $location = $r->location;
375 my ($identifier) = $r->uri =~ m{$location/*([^/]+)};
376 my ($identifier) = $r->uri =~ m{$location/*([^/]+)};
376 $identifier;
377 $identifier;
377 }
378 }
378
379
379 sub connect_database {
380 sub connect_database {
380 my $r = shift;
381 my $r = shift;
381
382
382 my $cfg = Apache2::Module::get_config(__PACKAGE__, $r->server, $r->per_dir_config);
383 my $cfg = Apache2::Module::get_config(__PACKAGE__, $r->server, $r->per_dir_config);
383 return DBI->connect($cfg->{RedmineDSN}, $cfg->{RedmineDbUser}, $cfg->{RedmineDbPass});
384 return DBI->connect($cfg->{RedmineDSN}, $cfg->{RedmineDbUser}, $cfg->{RedmineDbPass});
384 }
385 }
385
386
386 1;
387 1;
@@ -1,156 +1,164
1 ---
1 ---
2 users_004:
2 users_004:
3 created_on: 2006-07-19 19:34:07 +02:00
3 created_on: 2006-07-19 19:34:07 +02:00
4 status: 1
4 status: 1
5 last_login_on:
5 last_login_on:
6 language: en
6 language: en
7 hashed_password: 4e4aeb7baaf0706bd670263fef42dad15763b608
7 # password = foo
8 salt: 3126f764c3c5ac61cbfc103f25f934cf
9 hashed_password: 9e4dd7eeb172c12a0691a6d9d3a269f7e9fe671b
8 updated_on: 2006-07-19 19:34:07 +02:00
10 updated_on: 2006-07-19 19:34:07 +02:00
9 admin: false
11 admin: false
10 mail: rhill@somenet.foo
12 mail: rhill@somenet.foo
11 lastname: Hill
13 lastname: Hill
12 firstname: Robert
14 firstname: Robert
13 id: 4
15 id: 4
14 auth_source_id:
16 auth_source_id:
15 mail_notification: all
17 mail_notification: all
16 login: rhill
18 login: rhill
17 type: User
19 type: User
18 users_001:
20 users_001:
19 created_on: 2006-07-19 19:12:21 +02:00
21 created_on: 2006-07-19 19:12:21 +02:00
20 status: 1
22 status: 1
21 last_login_on: 2006-07-19 22:57:52 +02:00
23 last_login_on: 2006-07-19 22:57:52 +02:00
22 language: en
24 language: en
23 hashed_password: d033e22ae348aeb5660fc2140aec35850c4da997
25 # password = admin
26 salt: 82090c953c4a0000a7db253b0691a6b4
27 hashed_password: b5b6ff9543bf1387374cdfa27a54c96d236a7150
24 updated_on: 2006-07-19 22:57:52 +02:00
28 updated_on: 2006-07-19 22:57:52 +02:00
25 admin: true
29 admin: true
26 mail: admin@somenet.foo
30 mail: admin@somenet.foo
27 lastname: Admin
31 lastname: Admin
28 firstname: redMine
32 firstname: redMine
29 id: 1
33 id: 1
30 auth_source_id:
34 auth_source_id:
31 mail_notification: all
35 mail_notification: all
32 login: admin
36 login: admin
33 type: User
37 type: User
34 users_002:
38 users_002:
35 created_on: 2006-07-19 19:32:09 +02:00
39 created_on: 2006-07-19 19:32:09 +02:00
36 status: 1
40 status: 1
37 last_login_on: 2006-07-19 22:42:15 +02:00
41 last_login_on: 2006-07-19 22:42:15 +02:00
38 language: en
42 language: en
39 hashed_password: a9a653d4151fa2c081ba1ffc2c2726f3b80b7d7d
43 # password = jsmith
44 salt: 67eb4732624d5a7753dcea7ce0bb7d7d
45 hashed_password: bfbe06043353a677d0215b26a5800d128d5413bc
40 updated_on: 2006-07-19 22:42:15 +02:00
46 updated_on: 2006-07-19 22:42:15 +02:00
41 admin: false
47 admin: false
42 mail: jsmith@somenet.foo
48 mail: jsmith@somenet.foo
43 lastname: Smith
49 lastname: Smith
44 firstname: John
50 firstname: John
45 id: 2
51 id: 2
46 auth_source_id:
52 auth_source_id:
47 mail_notification: all
53 mail_notification: all
48 login: jsmith
54 login: jsmith
49 type: User
55 type: User
50 users_003:
56 users_003:
51 created_on: 2006-07-19 19:33:19 +02:00
57 created_on: 2006-07-19 19:33:19 +02:00
52 status: 1
58 status: 1
53 last_login_on:
59 last_login_on:
54 language: en
60 language: en
55 hashed_password: 7feb7657aa7a7bf5aef3414a5084875f27192415
61 # password = foo
62 salt: 7599f9963ec07b5a3b55b354407120c0
63 hashed_password: 8f659c8d7c072f189374edacfa90d6abbc26d8ed
56 updated_on: 2006-07-19 19:33:19 +02:00
64 updated_on: 2006-07-19 19:33:19 +02:00
57 admin: false
65 admin: false
58 mail: dlopper@somenet.foo
66 mail: dlopper@somenet.foo
59 lastname: Lopper
67 lastname: Lopper
60 firstname: Dave
68 firstname: Dave
61 id: 3
69 id: 3
62 auth_source_id:
70 auth_source_id:
63 mail_notification: all
71 mail_notification: all
64 login: dlopper
72 login: dlopper
65 type: User
73 type: User
66 users_005:
74 users_005:
67 id: 5
75 id: 5
68 created_on: 2006-07-19 19:33:19 +02:00
76 created_on: 2006-07-19 19:33:19 +02:00
69 # Locked
77 # Locked
70 status: 3
78 status: 3
71 last_login_on:
79 last_login_on:
72 language: en
80 language: en
73 hashed_password: 7feb7657aa7a7bf5aef3414a5084875f27192415
81 hashed_password: 1
74 updated_on: 2006-07-19 19:33:19 +02:00
82 updated_on: 2006-07-19 19:33:19 +02:00
75 admin: false
83 admin: false
76 mail: dlopper2@somenet.foo
84 mail: dlopper2@somenet.foo
77 lastname: Lopper2
85 lastname: Lopper2
78 firstname: Dave2
86 firstname: Dave2
79 auth_source_id:
87 auth_source_id:
80 mail_notification: all
88 mail_notification: all
81 login: dlopper2
89 login: dlopper2
82 type: User
90 type: User
83 users_006:
91 users_006:
84 id: 6
92 id: 6
85 created_on: 2006-07-19 19:33:19 +02:00
93 created_on: 2006-07-19 19:33:19 +02:00
86 status: 0
94 status: 0
87 last_login_on:
95 last_login_on:
88 language: ''
96 language: ''
89 hashed_password: 1
97 hashed_password: 1
90 updated_on: 2006-07-19 19:33:19 +02:00
98 updated_on: 2006-07-19 19:33:19 +02:00
91 admin: false
99 admin: false
92 mail: ''
100 mail: ''
93 lastname: Anonymous
101 lastname: Anonymous
94 firstname: ''
102 firstname: ''
95 auth_source_id:
103 auth_source_id:
96 mail_notification: only_my_events
104 mail_notification: only_my_events
97 login: ''
105 login: ''
98 type: AnonymousUser
106 type: AnonymousUser
99 users_007:
107 users_007:
100 id: 7
108 id: 7
101 created_on: 2006-07-19 19:33:19 +02:00
109 created_on: 2006-07-19 19:33:19 +02:00
102 status: 1
110 status: 1
103 last_login_on:
111 last_login_on:
104 language: ''
112 language: ''
105 hashed_password: 1
113 hashed_password: 1
106 updated_on: 2006-07-19 19:33:19 +02:00
114 updated_on: 2006-07-19 19:33:19 +02:00
107 admin: false
115 admin: false
108 mail: someone@foo.bar
116 mail: someone@foo.bar
109 lastname: One
117 lastname: One
110 firstname: Some
118 firstname: Some
111 auth_source_id:
119 auth_source_id:
112 mail_notification: only_my_events
120 mail_notification: only_my_events
113 login: someone
121 login: someone
114 type: User
122 type: User
115 users_008:
123 users_008:
116 id: 8
124 id: 8
117 created_on: 2006-07-19 19:33:19 +02:00
125 created_on: 2006-07-19 19:33:19 +02:00
118 status: 1
126 status: 1
119 last_login_on:
127 last_login_on:
120 language: 'it'
128 language: 'it'
121 hashed_password: 1
129 hashed_password: 1
122 updated_on: 2006-07-19 19:33:19 +02:00
130 updated_on: 2006-07-19 19:33:19 +02:00
123 admin: false
131 admin: false
124 mail: miscuser8@foo.bar
132 mail: miscuser8@foo.bar
125 lastname: Misc
133 lastname: Misc
126 firstname: User
134 firstname: User
127 auth_source_id:
135 auth_source_id:
128 mail_notification: only_my_events
136 mail_notification: only_my_events
129 login: miscuser8
137 login: miscuser8
130 type: User
138 type: User
131 users_009:
139 users_009:
132 id: 9
140 id: 9
133 created_on: 2006-07-19 19:33:19 +02:00
141 created_on: 2006-07-19 19:33:19 +02:00
134 status: 1
142 status: 1
135 last_login_on:
143 last_login_on:
136 language: 'it'
144 language: 'it'
137 hashed_password: 1
145 hashed_password: 1
138 updated_on: 2006-07-19 19:33:19 +02:00
146 updated_on: 2006-07-19 19:33:19 +02:00
139 admin: false
147 admin: false
140 mail: miscuser9@foo.bar
148 mail: miscuser9@foo.bar
141 lastname: Misc
149 lastname: Misc
142 firstname: User
150 firstname: User
143 auth_source_id:
151 auth_source_id:
144 mail_notification: only_my_events
152 mail_notification: only_my_events
145 login: miscuser9
153 login: miscuser9
146 type: User
154 type: User
147 groups_010:
155 groups_010:
148 id: 10
156 id: 10
149 lastname: A Team
157 lastname: A Team
150 type: Group
158 type: Group
151 groups_011:
159 groups_011:
152 id: 11
160 id: 11
153 lastname: B Team
161 lastname: B Team
154 type: Group
162 type: Group
155
163
156
164
@@ -1,766 +1,798
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2011 Jean-Philippe Lang
2 # Copyright (C) 2006-2011 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 require File.expand_path('../../test_helper', __FILE__)
18 require File.expand_path('../../test_helper', __FILE__)
19
19
20 class UserTest < ActiveSupport::TestCase
20 class UserTest < ActiveSupport::TestCase
21 fixtures :users, :members, :projects, :roles, :member_roles, :auth_sources
21 fixtures :users, :members, :projects, :roles, :member_roles, :auth_sources
22
22
23 def setup
23 def setup
24 @admin = User.find(1)
24 @admin = User.find(1)
25 @jsmith = User.find(2)
25 @jsmith = User.find(2)
26 @dlopper = User.find(3)
26 @dlopper = User.find(3)
27 end
27 end
28
28
29 test 'object_daddy creation' do
29 test 'object_daddy creation' do
30 User.generate_with_protected!(:firstname => 'Testing connection')
30 User.generate_with_protected!(:firstname => 'Testing connection')
31 User.generate_with_protected!(:firstname => 'Testing connection')
31 User.generate_with_protected!(:firstname => 'Testing connection')
32 assert_equal 2, User.count(:all, :conditions => {:firstname => 'Testing connection'})
32 assert_equal 2, User.count(:all, :conditions => {:firstname => 'Testing connection'})
33 end
33 end
34
34
35 def test_truth
35 def test_truth
36 assert_kind_of User, @jsmith
36 assert_kind_of User, @jsmith
37 end
37 end
38
38
39 def test_mail_should_be_stripped
39 def test_mail_should_be_stripped
40 u = User.new
40 u = User.new
41 u.mail = " foo@bar.com "
41 u.mail = " foo@bar.com "
42 assert_equal "foo@bar.com", u.mail
42 assert_equal "foo@bar.com", u.mail
43 end
43 end
44
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_should_delete_members_and_roles
112 def test_destroy_should_delete_members_and_roles
113 members = Member.find_all_by_user_id(2)
113 members = Member.find_all_by_user_id(2)
114 ms = members.size
114 ms = members.size
115 rs = members.collect(&:roles).flatten.size
115 rs = members.collect(&:roles).flatten.size
116
116
117 assert_difference 'Member.count', - ms do
117 assert_difference 'Member.count', - ms do
118 assert_difference 'MemberRole.count', - rs do
118 assert_difference 'MemberRole.count', - rs do
119 User.find(2).destroy
119 User.find(2).destroy
120 end
120 end
121 end
121 end
122
122
123 assert_nil User.find_by_id(2)
123 assert_nil User.find_by_id(2)
124 assert Member.find_all_by_user_id(2).empty?
124 assert Member.find_all_by_user_id(2).empty?
125 end
125 end
126
126
127 def test_destroy_should_update_attachments
127 def test_destroy_should_update_attachments
128 attachment = Attachment.create!(:container => Project.find(1),
128 attachment = Attachment.create!(:container => Project.find(1),
129 :file => uploaded_test_file("testfile.txt", "text/plain"),
129 :file => uploaded_test_file("testfile.txt", "text/plain"),
130 :author_id => 2)
130 :author_id => 2)
131
131
132 User.find(2).destroy
132 User.find(2).destroy
133 assert_nil User.find_by_id(2)
133 assert_nil User.find_by_id(2)
134 assert_equal User.anonymous, attachment.reload.author
134 assert_equal User.anonymous, attachment.reload.author
135 end
135 end
136
136
137 def test_destroy_should_update_comments
137 def test_destroy_should_update_comments
138 comment = Comment.create!(
138 comment = Comment.create!(
139 :commented => News.create!(:project_id => 1, :author_id => 1, :title => 'foo', :description => 'foo'),
139 :commented => News.create!(:project_id => 1, :author_id => 1, :title => 'foo', :description => 'foo'),
140 :author => User.find(2),
140 :author => User.find(2),
141 :comments => 'foo'
141 :comments => 'foo'
142 )
142 )
143
143
144 User.find(2).destroy
144 User.find(2).destroy
145 assert_nil User.find_by_id(2)
145 assert_nil User.find_by_id(2)
146 assert_equal User.anonymous, comment.reload.author
146 assert_equal User.anonymous, comment.reload.author
147 end
147 end
148
148
149 def test_destroy_should_update_issues
149 def test_destroy_should_update_issues
150 issue = Issue.create!(:project_id => 1, :author_id => 2, :tracker_id => 1, :subject => 'foo')
150 issue = Issue.create!(:project_id => 1, :author_id => 2, :tracker_id => 1, :subject => 'foo')
151
151
152 User.find(2).destroy
152 User.find(2).destroy
153 assert_nil User.find_by_id(2)
153 assert_nil User.find_by_id(2)
154 assert_equal User.anonymous, issue.reload.author
154 assert_equal User.anonymous, issue.reload.author
155 end
155 end
156
156
157 def test_destroy_should_unassign_issues
157 def test_destroy_should_unassign_issues
158 issue = Issue.create!(:project_id => 1, :author_id => 1, :tracker_id => 1, :subject => 'foo', :assigned_to_id => 2)
158 issue = Issue.create!(:project_id => 1, :author_id => 1, :tracker_id => 1, :subject => 'foo', :assigned_to_id => 2)
159
159
160 User.find(2).destroy
160 User.find(2).destroy
161 assert_nil User.find_by_id(2)
161 assert_nil User.find_by_id(2)
162 assert_nil issue.reload.assigned_to
162 assert_nil issue.reload.assigned_to
163 end
163 end
164
164
165 def test_destroy_should_update_journals
165 def test_destroy_should_update_journals
166 issue = Issue.create!(:project_id => 1, :author_id => 2, :tracker_id => 1, :subject => 'foo')
166 issue = Issue.create!(:project_id => 1, :author_id => 2, :tracker_id => 1, :subject => 'foo')
167 issue.init_journal(User.find(2), "update")
167 issue.init_journal(User.find(2), "update")
168 issue.save!
168 issue.save!
169
169
170 User.find(2).destroy
170 User.find(2).destroy
171 assert_nil User.find_by_id(2)
171 assert_nil User.find_by_id(2)
172 assert_equal User.anonymous, issue.journals.first.reload.user
172 assert_equal User.anonymous, issue.journals.first.reload.user
173 end
173 end
174
174
175 def test_destroy_should_update_journal_details_old_value
175 def test_destroy_should_update_journal_details_old_value
176 issue = Issue.create!(:project_id => 1, :author_id => 1, :tracker_id => 1, :subject => 'foo', :assigned_to_id => 2)
176 issue = Issue.create!(:project_id => 1, :author_id => 1, :tracker_id => 1, :subject => 'foo', :assigned_to_id => 2)
177 issue.init_journal(User.find(1), "update")
177 issue.init_journal(User.find(1), "update")
178 issue.assigned_to_id = nil
178 issue.assigned_to_id = nil
179 assert_difference 'JournalDetail.count' do
179 assert_difference 'JournalDetail.count' do
180 issue.save!
180 issue.save!
181 end
181 end
182 journal_detail = JournalDetail.first(:order => 'id DESC')
182 journal_detail = JournalDetail.first(:order => 'id DESC')
183 assert_equal '2', journal_detail.old_value
183 assert_equal '2', journal_detail.old_value
184
184
185 User.find(2).destroy
185 User.find(2).destroy
186 assert_nil User.find_by_id(2)
186 assert_nil User.find_by_id(2)
187 assert_equal User.anonymous.id.to_s, journal_detail.reload.old_value
187 assert_equal User.anonymous.id.to_s, journal_detail.reload.old_value
188 end
188 end
189
189
190 def test_destroy_should_update_journal_details_value
190 def test_destroy_should_update_journal_details_value
191 issue = Issue.create!(:project_id => 1, :author_id => 1, :tracker_id => 1, :subject => 'foo')
191 issue = Issue.create!(:project_id => 1, :author_id => 1, :tracker_id => 1, :subject => 'foo')
192 issue.init_journal(User.find(1), "update")
192 issue.init_journal(User.find(1), "update")
193 issue.assigned_to_id = 2
193 issue.assigned_to_id = 2
194 assert_difference 'JournalDetail.count' do
194 assert_difference 'JournalDetail.count' do
195 issue.save!
195 issue.save!
196 end
196 end
197 journal_detail = JournalDetail.first(:order => 'id DESC')
197 journal_detail = JournalDetail.first(:order => 'id DESC')
198 assert_equal '2', journal_detail.value
198 assert_equal '2', journal_detail.value
199
199
200 User.find(2).destroy
200 User.find(2).destroy
201 assert_nil User.find_by_id(2)
201 assert_nil User.find_by_id(2)
202 assert_equal User.anonymous.id.to_s, journal_detail.reload.value
202 assert_equal User.anonymous.id.to_s, journal_detail.reload.value
203 end
203 end
204
204
205 def test_destroy_should_update_messages
205 def test_destroy_should_update_messages
206 board = Board.create!(:project_id => 1, :name => 'Board', :description => 'Board')
206 board = Board.create!(:project_id => 1, :name => 'Board', :description => 'Board')
207 message = Message.create!(:board_id => board.id, :author_id => 2, :subject => 'foo', :content => 'foo')
207 message = Message.create!(:board_id => board.id, :author_id => 2, :subject => 'foo', :content => 'foo')
208
208
209 User.find(2).destroy
209 User.find(2).destroy
210 assert_nil User.find_by_id(2)
210 assert_nil User.find_by_id(2)
211 assert_equal User.anonymous, message.reload.author
211 assert_equal User.anonymous, message.reload.author
212 end
212 end
213
213
214 def test_destroy_should_update_news
214 def test_destroy_should_update_news
215 news = News.create!(:project_id => 1, :author_id => 2, :title => 'foo', :description => 'foo')
215 news = News.create!(:project_id => 1, :author_id => 2, :title => 'foo', :description => 'foo')
216
216
217 User.find(2).destroy
217 User.find(2).destroy
218 assert_nil User.find_by_id(2)
218 assert_nil User.find_by_id(2)
219 assert_equal User.anonymous, news.reload.author
219 assert_equal User.anonymous, news.reload.author
220 end
220 end
221
221
222 def test_destroy_should_delete_private_queries
222 def test_destroy_should_delete_private_queries
223 query = Query.new(:name => 'foo', :is_public => false)
223 query = Query.new(:name => 'foo', :is_public => false)
224 query.project_id = 1
224 query.project_id = 1
225 query.user_id = 2
225 query.user_id = 2
226 query.save!
226 query.save!
227
227
228 User.find(2).destroy
228 User.find(2).destroy
229 assert_nil User.find_by_id(2)
229 assert_nil User.find_by_id(2)
230 assert_nil Query.find_by_id(query.id)
230 assert_nil Query.find_by_id(query.id)
231 end
231 end
232
232
233 def test_destroy_should_update_public_queries
233 def test_destroy_should_update_public_queries
234 query = Query.new(:name => 'foo', :is_public => true)
234 query = Query.new(:name => 'foo', :is_public => true)
235 query.project_id = 1
235 query.project_id = 1
236 query.user_id = 2
236 query.user_id = 2
237 query.save!
237 query.save!
238
238
239 User.find(2).destroy
239 User.find(2).destroy
240 assert_nil User.find_by_id(2)
240 assert_nil User.find_by_id(2)
241 assert_equal User.anonymous, query.reload.user
241 assert_equal User.anonymous, query.reload.user
242 end
242 end
243
243
244 def test_destroy_should_update_time_entries
244 def test_destroy_should_update_time_entries
245 entry = TimeEntry.new(:hours => '2', :spent_on => Date.today, :activity => TimeEntryActivity.create!(:name => 'foo'))
245 entry = TimeEntry.new(:hours => '2', :spent_on => Date.today, :activity => TimeEntryActivity.create!(:name => 'foo'))
246 entry.project_id = 1
246 entry.project_id = 1
247 entry.user_id = 2
247 entry.user_id = 2
248 entry.save!
248 entry.save!
249
249
250 User.find(2).destroy
250 User.find(2).destroy
251 assert_nil User.find_by_id(2)
251 assert_nil User.find_by_id(2)
252 assert_equal User.anonymous, entry.reload.user
252 assert_equal User.anonymous, entry.reload.user
253 end
253 end
254
254
255 def test_destroy_should_delete_tokens
255 def test_destroy_should_delete_tokens
256 token = Token.create!(:user_id => 2, :value => 'foo')
256 token = Token.create!(:user_id => 2, :value => 'foo')
257
257
258 User.find(2).destroy
258 User.find(2).destroy
259 assert_nil User.find_by_id(2)
259 assert_nil User.find_by_id(2)
260 assert_nil Token.find_by_id(token.id)
260 assert_nil Token.find_by_id(token.id)
261 end
261 end
262
262
263 def test_destroy_should_delete_watchers
263 def test_destroy_should_delete_watchers
264 issue = Issue.create!(:project_id => 1, :author_id => 1, :tracker_id => 1, :subject => 'foo')
264 issue = Issue.create!(:project_id => 1, :author_id => 1, :tracker_id => 1, :subject => 'foo')
265 watcher = Watcher.create!(:user_id => 2, :watchable => issue)
265 watcher = Watcher.create!(:user_id => 2, :watchable => issue)
266
266
267 User.find(2).destroy
267 User.find(2).destroy
268 assert_nil User.find_by_id(2)
268 assert_nil User.find_by_id(2)
269 assert_nil Watcher.find_by_id(watcher.id)
269 assert_nil Watcher.find_by_id(watcher.id)
270 end
270 end
271
271
272 def test_destroy_should_update_wiki_contents
272 def test_destroy_should_update_wiki_contents
273 wiki_content = WikiContent.create!(
273 wiki_content = WikiContent.create!(
274 :text => 'foo',
274 :text => 'foo',
275 :author_id => 2,
275 :author_id => 2,
276 :page => WikiPage.create!(:title => 'Foo', :wiki => Wiki.create!(:project_id => 1, :start_page => 'Start'))
276 :page => WikiPage.create!(:title => 'Foo', :wiki => Wiki.create!(:project_id => 1, :start_page => 'Start'))
277 )
277 )
278 wiki_content.text = 'bar'
278 wiki_content.text = 'bar'
279 assert_difference 'WikiContent::Version.count' do
279 assert_difference 'WikiContent::Version.count' do
280 wiki_content.save!
280 wiki_content.save!
281 end
281 end
282
282
283 User.find(2).destroy
283 User.find(2).destroy
284 assert_nil User.find_by_id(2)
284 assert_nil User.find_by_id(2)
285 assert_equal User.anonymous, wiki_content.reload.author
285 assert_equal User.anonymous, wiki_content.reload.author
286 wiki_content.versions.each do |version|
286 wiki_content.versions.each do |version|
287 assert_equal User.anonymous, version.reload.author
287 assert_equal User.anonymous, version.reload.author
288 end
288 end
289 end
289 end
290
290
291 def test_destroy_should_nullify_issue_categories
291 def test_destroy_should_nullify_issue_categories
292 category = IssueCategory.create!(:project_id => 1, :assigned_to_id => 2, :name => 'foo')
292 category = IssueCategory.create!(:project_id => 1, :assigned_to_id => 2, :name => 'foo')
293
293
294 User.find(2).destroy
294 User.find(2).destroy
295 assert_nil User.find_by_id(2)
295 assert_nil User.find_by_id(2)
296 assert_nil category.reload.assigned_to_id
296 assert_nil category.reload.assigned_to_id
297 end
297 end
298
298
299 def test_destroy_should_nullify_changesets
299 def test_destroy_should_nullify_changesets
300 changeset = Changeset.create!(
300 changeset = Changeset.create!(
301 :repository => Repository::Subversion.create!(
301 :repository => Repository::Subversion.create!(
302 :project_id => 1,
302 :project_id => 1,
303 :url => 'file:///var/svn'
303 :url => 'file:///var/svn'
304 ),
304 ),
305 :revision => '12',
305 :revision => '12',
306 :committed_on => Time.now,
306 :committed_on => Time.now,
307 :committer => 'jsmith'
307 :committer => 'jsmith'
308 )
308 )
309 assert_equal 2, changeset.user_id
309 assert_equal 2, changeset.user_id
310
310
311 User.find(2).destroy
311 User.find(2).destroy
312 assert_nil User.find_by_id(2)
312 assert_nil User.find_by_id(2)
313 assert_nil changeset.reload.user_id
313 assert_nil changeset.reload.user_id
314 end
314 end
315
315
316 def test_anonymous_user_should_not_be_destroyable
316 def test_anonymous_user_should_not_be_destroyable
317 assert_no_difference 'User.count' do
317 assert_no_difference 'User.count' do
318 assert_equal false, User.anonymous.destroy
318 assert_equal false, User.anonymous.destroy
319 end
319 end
320 end
320 end
321
321
322 def test_validate_login_presence
322 def test_validate_login_presence
323 @admin.login = ""
323 @admin.login = ""
324 assert !@admin.save
324 assert !@admin.save
325 assert_equal 1, @admin.errors.count
325 assert_equal 1, @admin.errors.count
326 end
326 end
327
327
328 def test_validate_mail_notification_inclusion
328 def test_validate_mail_notification_inclusion
329 u = User.new
329 u = User.new
330 u.mail_notification = 'foo'
330 u.mail_notification = 'foo'
331 u.save
331 u.save
332 assert_not_nil u.errors.on(:mail_notification)
332 assert_not_nil u.errors.on(:mail_notification)
333 end
333 end
334
334
335 context "User#try_to_login" do
335 context "User#try_to_login" do
336 should "fall-back to case-insensitive if user login is not found as-typed." do
336 should "fall-back to case-insensitive if user login is not found as-typed." do
337 user = User.try_to_login("AdMin", "admin")
337 user = User.try_to_login("AdMin", "admin")
338 assert_kind_of User, user
338 assert_kind_of User, user
339 assert_equal "admin", user.login
339 assert_equal "admin", user.login
340 end
340 end
341
341
342 should "select the exact matching user first" do
342 should "select the exact matching user first" do
343 case_sensitive_user = User.generate_with_protected!(:login => 'changed', :password => 'admin', :password_confirmation => 'admin')
343 case_sensitive_user = User.generate_with_protected!(:login => 'changed', :password => 'admin', :password_confirmation => 'admin')
344 # bypass validations to make it appear like existing data
344 # bypass validations to make it appear like existing data
345 case_sensitive_user.update_attribute(:login, 'ADMIN')
345 case_sensitive_user.update_attribute(:login, 'ADMIN')
346
346
347 user = User.try_to_login("ADMIN", "admin")
347 user = User.try_to_login("ADMIN", "admin")
348 assert_kind_of User, user
348 assert_kind_of User, user
349 assert_equal "ADMIN", user.login
349 assert_equal "ADMIN", user.login
350
350
351 end
351 end
352 end
352 end
353
353
354 def test_password
354 def test_password
355 user = User.try_to_login("admin", "admin")
355 user = User.try_to_login("admin", "admin")
356 assert_kind_of User, user
356 assert_kind_of User, user
357 assert_equal "admin", user.login
357 assert_equal "admin", user.login
358 user.password = "hello"
358 user.password = "hello"
359 assert user.save
359 assert user.save
360
360
361 user = User.try_to_login("admin", "hello")
361 user = User.try_to_login("admin", "hello")
362 assert_kind_of User, user
362 assert_kind_of User, user
363 assert_equal "admin", user.login
363 assert_equal "admin", user.login
364 assert_equal User.hash_password("hello"), user.hashed_password
365 end
364 end
366
365
367 def test_name_format
366 def test_name_format
368 assert_equal 'Smith, John', @jsmith.name(:lastname_coma_firstname)
367 assert_equal 'Smith, John', @jsmith.name(:lastname_coma_firstname)
369 Setting.user_format = :firstname_lastname
368 Setting.user_format = :firstname_lastname
370 assert_equal 'John Smith', @jsmith.reload.name
369 assert_equal 'John Smith', @jsmith.reload.name
371 Setting.user_format = :username
370 Setting.user_format = :username
372 assert_equal 'jsmith', @jsmith.reload.name
371 assert_equal 'jsmith', @jsmith.reload.name
373 end
372 end
374
373
375 def test_lock
374 def test_lock
376 user = User.try_to_login("jsmith", "jsmith")
375 user = User.try_to_login("jsmith", "jsmith")
377 assert_equal @jsmith, user
376 assert_equal @jsmith, user
378
377
379 @jsmith.status = User::STATUS_LOCKED
378 @jsmith.status = User::STATUS_LOCKED
380 assert @jsmith.save
379 assert @jsmith.save
381
380
382 user = User.try_to_login("jsmith", "jsmith")
381 user = User.try_to_login("jsmith", "jsmith")
383 assert_equal nil, user
382 assert_equal nil, user
384 end
383 end
385
384
385 context ".try_to_login" do
386 context "with good credentials" do
387 should "return the user" do
388 user = User.try_to_login("admin", "admin")
389 assert_kind_of User, user
390 assert_equal "admin", user.login
391 end
392 end
393
394 context "with wrong credentials" do
395 should "return nil" do
396 assert_nil User.try_to_login("admin", "foo")
397 end
398 end
399 end
400
386 if ldap_configured?
401 if ldap_configured?
387 context "#try_to_login using LDAP" do
402 context "#try_to_login using LDAP" do
388 context "with failed connection to the LDAP server" do
403 context "with failed connection to the LDAP server" do
389 should "return nil" do
404 should "return nil" do
390 @auth_source = AuthSourceLdap.find(1)
405 @auth_source = AuthSourceLdap.find(1)
391 AuthSource.any_instance.stubs(:initialize_ldap_con).raises(Net::LDAP::LdapError, 'Cannot connect')
406 AuthSource.any_instance.stubs(:initialize_ldap_con).raises(Net::LDAP::LdapError, 'Cannot connect')
392
407
393 assert_equal nil, User.try_to_login('edavis', 'wrong')
408 assert_equal nil, User.try_to_login('edavis', 'wrong')
394 end
409 end
395 end
410 end
396
411
397 context "with an unsuccessful authentication" do
412 context "with an unsuccessful authentication" do
398 should "return nil" do
413 should "return nil" do
399 assert_equal nil, User.try_to_login('edavis', 'wrong')
414 assert_equal nil, User.try_to_login('edavis', 'wrong')
400 end
415 end
401 end
416 end
402
417
403 context "on the fly registration" do
418 context "on the fly registration" do
404 setup do
419 setup do
405 @auth_source = AuthSourceLdap.find(1)
420 @auth_source = AuthSourceLdap.find(1)
406 end
421 end
407
422
408 context "with a successful authentication" do
423 context "with a successful authentication" do
409 should "create a new user account if it doesn't exist" do
424 should "create a new user account if it doesn't exist" do
410 assert_difference('User.count') do
425 assert_difference('User.count') do
411 user = User.try_to_login('edavis', '123456')
426 user = User.try_to_login('edavis', '123456')
412 assert !user.admin?
427 assert !user.admin?
413 end
428 end
414 end
429 end
415
430
416 should "retrieve existing user" do
431 should "retrieve existing user" do
417 user = User.try_to_login('edavis', '123456')
432 user = User.try_to_login('edavis', '123456')
418 user.admin = true
433 user.admin = true
419 user.save!
434 user.save!
420
435
421 assert_no_difference('User.count') do
436 assert_no_difference('User.count') do
422 user = User.try_to_login('edavis', '123456')
437 user = User.try_to_login('edavis', '123456')
423 assert user.admin?
438 assert user.admin?
424 end
439 end
425 end
440 end
426 end
441 end
427 end
442 end
428 end
443 end
429
444
430 else
445 else
431 puts "Skipping LDAP tests."
446 puts "Skipping LDAP tests."
432 end
447 end
433
448
434 def test_create_anonymous
449 def test_create_anonymous
435 AnonymousUser.delete_all
450 AnonymousUser.delete_all
436 anon = User.anonymous
451 anon = User.anonymous
437 assert !anon.new_record?
452 assert !anon.new_record?
438 assert_kind_of AnonymousUser, anon
453 assert_kind_of AnonymousUser, anon
439 end
454 end
440
455
441 should_have_one :rss_token
456 should_have_one :rss_token
442
457
443 def test_rss_key
458 def test_rss_key
444 assert_nil @jsmith.rss_token
459 assert_nil @jsmith.rss_token
445 key = @jsmith.rss_key
460 key = @jsmith.rss_key
446 assert_equal 40, key.length
461 assert_equal 40, key.length
447
462
448 @jsmith.reload
463 @jsmith.reload
449 assert_equal key, @jsmith.rss_key
464 assert_equal key, @jsmith.rss_key
450 end
465 end
451
466
452
467
453 should_have_one :api_token
468 should_have_one :api_token
454
469
455 context "User#api_key" do
470 context "User#api_key" do
456 should "generate a new one if the user doesn't have one" do
471 should "generate a new one if the user doesn't have one" do
457 user = User.generate_with_protected!(:api_token => nil)
472 user = User.generate_with_protected!(:api_token => nil)
458 assert_nil user.api_token
473 assert_nil user.api_token
459
474
460 key = user.api_key
475 key = user.api_key
461 assert_equal 40, key.length
476 assert_equal 40, key.length
462 user.reload
477 user.reload
463 assert_equal key, user.api_key
478 assert_equal key, user.api_key
464 end
479 end
465
480
466 should "return the existing api token value" do
481 should "return the existing api token value" do
467 user = User.generate_with_protected!
482 user = User.generate_with_protected!
468 token = Token.generate!(:action => 'api')
483 token = Token.generate!(:action => 'api')
469 user.api_token = token
484 user.api_token = token
470 assert user.save
485 assert user.save
471
486
472 assert_equal token.value, user.api_key
487 assert_equal token.value, user.api_key
473 end
488 end
474 end
489 end
475
490
476 context "User#find_by_api_key" do
491 context "User#find_by_api_key" do
477 should "return nil if no matching key is found" do
492 should "return nil if no matching key is found" do
478 assert_nil User.find_by_api_key('zzzzzzzzz')
493 assert_nil User.find_by_api_key('zzzzzzzzz')
479 end
494 end
480
495
481 should "return nil if the key is found for an inactive user" do
496 should "return nil if the key is found for an inactive user" do
482 user = User.generate_with_protected!(:status => User::STATUS_LOCKED)
497 user = User.generate_with_protected!(:status => User::STATUS_LOCKED)
483 token = Token.generate!(:action => 'api')
498 token = Token.generate!(:action => 'api')
484 user.api_token = token
499 user.api_token = token
485 user.save
500 user.save
486
501
487 assert_nil User.find_by_api_key(token.value)
502 assert_nil User.find_by_api_key(token.value)
488 end
503 end
489
504
490 should "return the user if the key is found for an active user" do
505 should "return the user if the key is found for an active user" do
491 user = User.generate_with_protected!(:status => User::STATUS_ACTIVE)
506 user = User.generate_with_protected!(:status => User::STATUS_ACTIVE)
492 token = Token.generate!(:action => 'api')
507 token = Token.generate!(:action => 'api')
493 user.api_token = token
508 user.api_token = token
494 user.save
509 user.save
495
510
496 assert_equal user, User.find_by_api_key(token.value)
511 assert_equal user, User.find_by_api_key(token.value)
497 end
512 end
498 end
513 end
499
514
500 def test_roles_for_project
515 def test_roles_for_project
501 # user with a role
516 # user with a role
502 roles = @jsmith.roles_for_project(Project.find(1))
517 roles = @jsmith.roles_for_project(Project.find(1))
503 assert_kind_of Role, roles.first
518 assert_kind_of Role, roles.first
504 assert_equal "Manager", roles.first.name
519 assert_equal "Manager", roles.first.name
505
520
506 # user with no role
521 # user with no role
507 assert_nil @dlopper.roles_for_project(Project.find(2)).detect {|role| role.member?}
522 assert_nil @dlopper.roles_for_project(Project.find(2)).detect {|role| role.member?}
508 end
523 end
509
524
510 def test_valid_notification_options
525 def test_valid_notification_options
511 # without memberships
526 # without memberships
512 assert_equal 5, User.find(7).valid_notification_options.size
527 assert_equal 5, User.find(7).valid_notification_options.size
513 # with memberships
528 # with memberships
514 assert_equal 6, User.find(2).valid_notification_options.size
529 assert_equal 6, User.find(2).valid_notification_options.size
515 end
530 end
516
531
517 def test_valid_notification_options_class_method
532 def test_valid_notification_options_class_method
518 assert_equal 5, User.valid_notification_options.size
533 assert_equal 5, User.valid_notification_options.size
519 assert_equal 5, User.valid_notification_options(User.find(7)).size
534 assert_equal 5, User.valid_notification_options(User.find(7)).size
520 assert_equal 6, User.valid_notification_options(User.find(2)).size
535 assert_equal 6, User.valid_notification_options(User.find(2)).size
521 end
536 end
522
537
523 def test_mail_notification_all
538 def test_mail_notification_all
524 @jsmith.mail_notification = 'all'
539 @jsmith.mail_notification = 'all'
525 @jsmith.notified_project_ids = []
540 @jsmith.notified_project_ids = []
526 @jsmith.save
541 @jsmith.save
527 @jsmith.reload
542 @jsmith.reload
528 assert @jsmith.projects.first.recipients.include?(@jsmith.mail)
543 assert @jsmith.projects.first.recipients.include?(@jsmith.mail)
529 end
544 end
530
545
531 def test_mail_notification_selected
546 def test_mail_notification_selected
532 @jsmith.mail_notification = 'selected'
547 @jsmith.mail_notification = 'selected'
533 @jsmith.notified_project_ids = [1]
548 @jsmith.notified_project_ids = [1]
534 @jsmith.save
549 @jsmith.save
535 @jsmith.reload
550 @jsmith.reload
536 assert Project.find(1).recipients.include?(@jsmith.mail)
551 assert Project.find(1).recipients.include?(@jsmith.mail)
537 end
552 end
538
553
539 def test_mail_notification_only_my_events
554 def test_mail_notification_only_my_events
540 @jsmith.mail_notification = 'only_my_events'
555 @jsmith.mail_notification = 'only_my_events'
541 @jsmith.notified_project_ids = []
556 @jsmith.notified_project_ids = []
542 @jsmith.save
557 @jsmith.save
543 @jsmith.reload
558 @jsmith.reload
544 assert !@jsmith.projects.first.recipients.include?(@jsmith.mail)
559 assert !@jsmith.projects.first.recipients.include?(@jsmith.mail)
545 end
560 end
546
561
547 def test_comments_sorting_preference
562 def test_comments_sorting_preference
548 assert !@jsmith.wants_comments_in_reverse_order?
563 assert !@jsmith.wants_comments_in_reverse_order?
549 @jsmith.pref.comments_sorting = 'asc'
564 @jsmith.pref.comments_sorting = 'asc'
550 assert !@jsmith.wants_comments_in_reverse_order?
565 assert !@jsmith.wants_comments_in_reverse_order?
551 @jsmith.pref.comments_sorting = 'desc'
566 @jsmith.pref.comments_sorting = 'desc'
552 assert @jsmith.wants_comments_in_reverse_order?
567 assert @jsmith.wants_comments_in_reverse_order?
553 end
568 end
554
569
555 def test_find_by_mail_should_be_case_insensitive
570 def test_find_by_mail_should_be_case_insensitive
556 u = User.find_by_mail('JSmith@somenet.foo')
571 u = User.find_by_mail('JSmith@somenet.foo')
557 assert_not_nil u
572 assert_not_nil u
558 assert_equal 'jsmith@somenet.foo', u.mail
573 assert_equal 'jsmith@somenet.foo', u.mail
559 end
574 end
560
575
561 def test_random_password
576 def test_random_password
562 u = User.new
577 u = User.new
563 u.random_password
578 u.random_password
564 assert !u.password.blank?
579 assert !u.password.blank?
565 assert !u.password_confirmation.blank?
580 assert !u.password_confirmation.blank?
566 end
581 end
567
582
568 context "#change_password_allowed?" do
583 context "#change_password_allowed?" do
569 should "be allowed if no auth source is set" do
584 should "be allowed if no auth source is set" do
570 user = User.generate_with_protected!
585 user = User.generate_with_protected!
571 assert user.change_password_allowed?
586 assert user.change_password_allowed?
572 end
587 end
573
588
574 should "delegate to the auth source" do
589 should "delegate to the auth source" do
575 user = User.generate_with_protected!
590 user = User.generate_with_protected!
576
591
577 allowed_auth_source = AuthSource.generate!
592 allowed_auth_source = AuthSource.generate!
578 def allowed_auth_source.allow_password_changes?; true; end
593 def allowed_auth_source.allow_password_changes?; true; end
579
594
580 denied_auth_source = AuthSource.generate!
595 denied_auth_source = AuthSource.generate!
581 def denied_auth_source.allow_password_changes?; false; end
596 def denied_auth_source.allow_password_changes?; false; end
582
597
583 assert user.change_password_allowed?
598 assert user.change_password_allowed?
584
599
585 user.auth_source = allowed_auth_source
600 user.auth_source = allowed_auth_source
586 assert user.change_password_allowed?, "User not allowed to change password, though auth source does"
601 assert user.change_password_allowed?, "User not allowed to change password, though auth source does"
587
602
588 user.auth_source = denied_auth_source
603 user.auth_source = denied_auth_source
589 assert !user.change_password_allowed?, "User allowed to change password, though auth source does not"
604 assert !user.change_password_allowed?, "User allowed to change password, though auth source does not"
590 end
605 end
591
606
592 end
607 end
593
608
594 context "#allowed_to?" do
609 context "#allowed_to?" do
595 context "with a unique project" do
610 context "with a unique project" do
596 should "return false if project is archived" do
611 should "return false if project is archived" do
597 project = Project.find(1)
612 project = Project.find(1)
598 Project.any_instance.stubs(:status).returns(Project::STATUS_ARCHIVED)
613 Project.any_instance.stubs(:status).returns(Project::STATUS_ARCHIVED)
599 assert ! @admin.allowed_to?(:view_issues, Project.find(1))
614 assert ! @admin.allowed_to?(:view_issues, Project.find(1))
600 end
615 end
601
616
602 should "return false if related module is disabled" do
617 should "return false if related module is disabled" do
603 project = Project.find(1)
618 project = Project.find(1)
604 project.enabled_module_names = ["issue_tracking"]
619 project.enabled_module_names = ["issue_tracking"]
605 assert @admin.allowed_to?(:add_issues, project)
620 assert @admin.allowed_to?(:add_issues, project)
606 assert ! @admin.allowed_to?(:view_wiki_pages, project)
621 assert ! @admin.allowed_to?(:view_wiki_pages, project)
607 end
622 end
608
623
609 should "authorize nearly everything for admin users" do
624 should "authorize nearly everything for admin users" do
610 project = Project.find(1)
625 project = Project.find(1)
611 assert ! @admin.member_of?(project)
626 assert ! @admin.member_of?(project)
612 %w(edit_issues delete_issues manage_news manage_documents manage_wiki).each do |p|
627 %w(edit_issues delete_issues manage_news manage_documents manage_wiki).each do |p|
613 assert @admin.allowed_to?(p.to_sym, project)
628 assert @admin.allowed_to?(p.to_sym, project)
614 end
629 end
615 end
630 end
616
631
617 should "authorize normal users depending on their roles" do
632 should "authorize normal users depending on their roles" do
618 project = Project.find(1)
633 project = Project.find(1)
619 assert @jsmith.allowed_to?(:delete_messages, project) #Manager
634 assert @jsmith.allowed_to?(:delete_messages, project) #Manager
620 assert ! @dlopper.allowed_to?(:delete_messages, project) #Developper
635 assert ! @dlopper.allowed_to?(:delete_messages, project) #Developper
621 end
636 end
622 end
637 end
623
638
624 context "with multiple projects" do
639 context "with multiple projects" do
625 should "return false if array is empty" do
640 should "return false if array is empty" do
626 assert ! @admin.allowed_to?(:view_project, [])
641 assert ! @admin.allowed_to?(:view_project, [])
627 end
642 end
628
643
629 should "return true only if user has permission on all these projects" do
644 should "return true only if user has permission on all these projects" do
630 assert @admin.allowed_to?(:view_project, Project.all)
645 assert @admin.allowed_to?(:view_project, Project.all)
631 assert ! @dlopper.allowed_to?(:view_project, Project.all) #cannot see Project(2)
646 assert ! @dlopper.allowed_to?(:view_project, Project.all) #cannot see Project(2)
632 assert @jsmith.allowed_to?(:edit_issues, @jsmith.projects) #Manager or Developer everywhere
647 assert @jsmith.allowed_to?(:edit_issues, @jsmith.projects) #Manager or Developer everywhere
633 assert ! @jsmith.allowed_to?(:delete_issue_watchers, @jsmith.projects) #Dev cannot delete_issue_watchers
648 assert ! @jsmith.allowed_to?(:delete_issue_watchers, @jsmith.projects) #Dev cannot delete_issue_watchers
634 end
649 end
635
650
636 should "behave correctly with arrays of 1 project" do
651 should "behave correctly with arrays of 1 project" do
637 assert ! User.anonymous.allowed_to?(:delete_issues, [Project.first])
652 assert ! User.anonymous.allowed_to?(:delete_issues, [Project.first])
638 end
653 end
639 end
654 end
640
655
641 context "with options[:global]" do
656 context "with options[:global]" do
642 should "authorize if user has at least one role that has this permission" do
657 should "authorize if user has at least one role that has this permission" do
643 @dlopper2 = User.find(5) #only Developper on a project, not Manager anywhere
658 @dlopper2 = User.find(5) #only Developper on a project, not Manager anywhere
644 @anonymous = User.find(6)
659 @anonymous = User.find(6)
645 assert @jsmith.allowed_to?(:delete_issue_watchers, nil, :global => true)
660 assert @jsmith.allowed_to?(:delete_issue_watchers, nil, :global => true)
646 assert ! @dlopper2.allowed_to?(:delete_issue_watchers, nil, :global => true)
661 assert ! @dlopper2.allowed_to?(:delete_issue_watchers, nil, :global => true)
647 assert @dlopper2.allowed_to?(:add_issues, nil, :global => true)
662 assert @dlopper2.allowed_to?(:add_issues, nil, :global => true)
648 assert ! @anonymous.allowed_to?(:add_issues, nil, :global => true)
663 assert ! @anonymous.allowed_to?(:add_issues, nil, :global => true)
649 assert @anonymous.allowed_to?(:view_issues, nil, :global => true)
664 assert @anonymous.allowed_to?(:view_issues, nil, :global => true)
650 end
665 end
651 end
666 end
652 end
667 end
653
668
654 context "User#notify_about?" do
669 context "User#notify_about?" do
655 context "Issues" do
670 context "Issues" do
656 setup do
671 setup do
657 @project = Project.find(1)
672 @project = Project.find(1)
658 @author = User.generate_with_protected!
673 @author = User.generate_with_protected!
659 @assignee = User.generate_with_protected!
674 @assignee = User.generate_with_protected!
660 @issue = Issue.generate_for_project!(@project, :assigned_to => @assignee, :author => @author)
675 @issue = Issue.generate_for_project!(@project, :assigned_to => @assignee, :author => @author)
661 end
676 end
662
677
663 should "be true for a user with :all" do
678 should "be true for a user with :all" do
664 @author.update_attribute(:mail_notification, 'all')
679 @author.update_attribute(:mail_notification, 'all')
665 assert @author.notify_about?(@issue)
680 assert @author.notify_about?(@issue)
666 end
681 end
667
682
668 should "be false for a user with :none" do
683 should "be false for a user with :none" do
669 @author.update_attribute(:mail_notification, 'none')
684 @author.update_attribute(:mail_notification, 'none')
670 assert ! @author.notify_about?(@issue)
685 assert ! @author.notify_about?(@issue)
671 end
686 end
672
687
673 should "be false for a user with :only_my_events and isn't an author, creator, or assignee" do
688 should "be false for a user with :only_my_events and isn't an author, creator, or assignee" do
674 @user = User.generate_with_protected!(:mail_notification => 'only_my_events')
689 @user = User.generate_with_protected!(:mail_notification => 'only_my_events')
675 Member.create!(:user => @user, :project => @project, :role_ids => [1])
690 Member.create!(:user => @user, :project => @project, :role_ids => [1])
676 assert ! @user.notify_about?(@issue)
691 assert ! @user.notify_about?(@issue)
677 end
692 end
678
693
679 should "be true for a user with :only_my_events and is the author" do
694 should "be true for a user with :only_my_events and is the author" do
680 @author.update_attribute(:mail_notification, 'only_my_events')
695 @author.update_attribute(:mail_notification, 'only_my_events')
681 assert @author.notify_about?(@issue)
696 assert @author.notify_about?(@issue)
682 end
697 end
683
698
684 should "be true for a user with :only_my_events and is the assignee" do
699 should "be true for a user with :only_my_events and is the assignee" do
685 @assignee.update_attribute(:mail_notification, 'only_my_events')
700 @assignee.update_attribute(:mail_notification, 'only_my_events')
686 assert @assignee.notify_about?(@issue)
701 assert @assignee.notify_about?(@issue)
687 end
702 end
688
703
689 should "be true for a user with :only_assigned and is the assignee" do
704 should "be true for a user with :only_assigned and is the assignee" do
690 @assignee.update_attribute(:mail_notification, 'only_assigned')
705 @assignee.update_attribute(:mail_notification, 'only_assigned')
691 assert @assignee.notify_about?(@issue)
706 assert @assignee.notify_about?(@issue)
692 end
707 end
693
708
694 should "be false for a user with :only_assigned and is not the assignee" do
709 should "be false for a user with :only_assigned and is not the assignee" do
695 @author.update_attribute(:mail_notification, 'only_assigned')
710 @author.update_attribute(:mail_notification, 'only_assigned')
696 assert ! @author.notify_about?(@issue)
711 assert ! @author.notify_about?(@issue)
697 end
712 end
698
713
699 should "be true for a user with :only_owner and is the author" do
714 should "be true for a user with :only_owner and is the author" do
700 @author.update_attribute(:mail_notification, 'only_owner')
715 @author.update_attribute(:mail_notification, 'only_owner')
701 assert @author.notify_about?(@issue)
716 assert @author.notify_about?(@issue)
702 end
717 end
703
718
704 should "be false for a user with :only_owner and is not the author" do
719 should "be false for a user with :only_owner and is not the author" do
705 @assignee.update_attribute(:mail_notification, 'only_owner')
720 @assignee.update_attribute(:mail_notification, 'only_owner')
706 assert ! @assignee.notify_about?(@issue)
721 assert ! @assignee.notify_about?(@issue)
707 end
722 end
708
723
709 should "be true for a user with :selected and is the author" do
724 should "be true for a user with :selected and is the author" do
710 @author.update_attribute(:mail_notification, 'selected')
725 @author.update_attribute(:mail_notification, 'selected')
711 assert @author.notify_about?(@issue)
726 assert @author.notify_about?(@issue)
712 end
727 end
713
728
714 should "be true for a user with :selected and is the assignee" do
729 should "be true for a user with :selected and is the assignee" do
715 @assignee.update_attribute(:mail_notification, 'selected')
730 @assignee.update_attribute(:mail_notification, 'selected')
716 assert @assignee.notify_about?(@issue)
731 assert @assignee.notify_about?(@issue)
717 end
732 end
718
733
719 should "be false for a user with :selected and is not the author or assignee" do
734 should "be false for a user with :selected and is not the author or assignee" do
720 @user = User.generate_with_protected!(:mail_notification => 'selected')
735 @user = User.generate_with_protected!(:mail_notification => 'selected')
721 Member.create!(:user => @user, :project => @project, :role_ids => [1])
736 Member.create!(:user => @user, :project => @project, :role_ids => [1])
722 assert ! @user.notify_about?(@issue)
737 assert ! @user.notify_about?(@issue)
723 end
738 end
724 end
739 end
725
740
726 context "other events" do
741 context "other events" do
727 should 'be added and tested'
742 should 'be added and tested'
728 end
743 end
729 end
744 end
730
745
746 def test_salt_unsalted_passwords
747 # Restore a user with an unsalted password
748 user = User.find(1)
749 user.salt = nil
750 user.hashed_password = User.hash_password("unsalted")
751 user.save!
752
753 User.salt_unsalted_passwords!
754
755 user.reload
756 # Salt added
757 assert !user.salt.blank?
758 # Password still valid
759 assert user.check_password?("unsalted")
760 assert_equal user, User.try_to_login(user.login, "unsalted")
761 end
762
731 if Object.const_defined?(:OpenID)
763 if Object.const_defined?(:OpenID)
732
764
733 def test_setting_identity_url
765 def test_setting_identity_url
734 normalized_open_id_url = 'http://example.com/'
766 normalized_open_id_url = 'http://example.com/'
735 u = User.new( :identity_url => 'http://example.com/' )
767 u = User.new( :identity_url => 'http://example.com/' )
736 assert_equal normalized_open_id_url, u.identity_url
768 assert_equal normalized_open_id_url, u.identity_url
737 end
769 end
738
770
739 def test_setting_identity_url_without_trailing_slash
771 def test_setting_identity_url_without_trailing_slash
740 normalized_open_id_url = 'http://example.com/'
772 normalized_open_id_url = 'http://example.com/'
741 u = User.new( :identity_url => 'http://example.com' )
773 u = User.new( :identity_url => 'http://example.com' )
742 assert_equal normalized_open_id_url, u.identity_url
774 assert_equal normalized_open_id_url, u.identity_url
743 end
775 end
744
776
745 def test_setting_identity_url_without_protocol
777 def test_setting_identity_url_without_protocol
746 normalized_open_id_url = 'http://example.com/'
778 normalized_open_id_url = 'http://example.com/'
747 u = User.new( :identity_url => 'example.com' )
779 u = User.new( :identity_url => 'example.com' )
748 assert_equal normalized_open_id_url, u.identity_url
780 assert_equal normalized_open_id_url, u.identity_url
749 end
781 end
750
782
751 def test_setting_blank_identity_url
783 def test_setting_blank_identity_url
752 u = User.new( :identity_url => 'example.com' )
784 u = User.new( :identity_url => 'example.com' )
753 u.identity_url = ''
785 u.identity_url = ''
754 assert u.identity_url.blank?
786 assert u.identity_url.blank?
755 end
787 end
756
788
757 def test_setting_invalid_identity_url
789 def test_setting_invalid_identity_url
758 u = User.new( :identity_url => 'this is not an openid url' )
790 u = User.new( :identity_url => 'this is not an openid url' )
759 assert u.identity_url.blank?
791 assert u.identity_url.blank?
760 end
792 end
761
793
762 else
794 else
763 puts "Skipping openid tests."
795 puts "Skipping openid tests."
764 end
796 end
765
797
766 end
798 end
General Comments 0
You need to be logged in to leave comments. Login now