##// END OF EJS Templates
Rails3: replace deprecated 'before_create' method at User model....
Toshi MARUYAMA -
r6723:17ec2a289f93
parent child
Show More
@@ -1,616 +1,617
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 "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 :changesets, :dependent => :nullify
48 has_many :changesets, :dependent => :nullify
49 has_one :preference, :dependent => :destroy, :class_name => 'UserPreference'
49 has_one :preference, :dependent => :destroy, :class_name => 'UserPreference'
50 has_one :rss_token, :class_name => 'Token', :conditions => "action='feeds'"
50 has_one :rss_token, :class_name => 'Token', :conditions => "action='feeds'"
51 has_one :api_token, :class_name => 'Token', :conditions => "action='api'"
51 has_one :api_token, :class_name => 'Token', :conditions => "action='api'"
52 belongs_to :auth_source
52 belongs_to :auth_source
53
53
54 # Active non-anonymous users scope
54 # Active non-anonymous users scope
55 named_scope :active, :conditions => "#{User.table_name}.status = #{STATUS_ACTIVE}"
55 named_scope :active, :conditions => "#{User.table_name}.status = #{STATUS_ACTIVE}"
56
56
57 acts_as_customizable
57 acts_as_customizable
58
58
59 attr_accessor :password, :password_confirmation
59 attr_accessor :password, :password_confirmation
60 attr_accessor :last_before_login_on
60 attr_accessor :last_before_login_on
61 # Prevents unauthorized assignments
61 # Prevents unauthorized assignments
62 attr_protected :login, :admin, :password, :password_confirmation, :hashed_password
62 attr_protected :login, :admin, :password, :password_confirmation, :hashed_password
63
63
64 validates_presence_of :login, :firstname, :lastname, :mail, :if => Proc.new { |user| !user.is_a?(AnonymousUser) }
64 validates_presence_of :login, :firstname, :lastname, :mail, :if => Proc.new { |user| !user.is_a?(AnonymousUser) }
65 validates_uniqueness_of :login, :if => Proc.new { |user| !user.login.blank? }, :case_sensitive => false
65 validates_uniqueness_of :login, :if => Proc.new { |user| !user.login.blank? }, :case_sensitive => false
66 validates_uniqueness_of :mail, :if => Proc.new { |user| !user.mail.blank? }, :case_sensitive => false
66 validates_uniqueness_of :mail, :if => Proc.new { |user| !user.mail.blank? }, :case_sensitive => false
67 # Login must contain lettres, numbers, underscores only
67 # Login must contain lettres, numbers, underscores only
68 validates_format_of :login, :with => /^[a-z0-9_\-@\.]*$/i
68 validates_format_of :login, :with => /^[a-z0-9_\-@\.]*$/i
69 validates_length_of :login, :maximum => 30
69 validates_length_of :login, :maximum => 30
70 validates_length_of :firstname, :lastname, :maximum => 30
70 validates_length_of :firstname, :lastname, :maximum => 30
71 validates_format_of :mail, :with => /^([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})$/i, :allow_blank => true
71 validates_format_of :mail, :with => /^([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})$/i, :allow_blank => true
72 validates_length_of :mail, :maximum => 60, :allow_nil => true
72 validates_length_of :mail, :maximum => 60, :allow_nil => true
73 validates_confirmation_of :password, :allow_nil => true
73 validates_confirmation_of :password, :allow_nil => true
74 validates_inclusion_of :mail_notification, :in => MAIL_NOTIFICATION_OPTIONS.collect(&:first), :allow_blank => true
74 validates_inclusion_of :mail_notification, :in => MAIL_NOTIFICATION_OPTIONS.collect(&:first), :allow_blank => true
75
75
76 before_create :set_mail_notification
76 before_destroy :remove_references_before_destroy
77 before_destroy :remove_references_before_destroy
77
78
78 named_scope :in_group, lambda {|group|
79 named_scope :in_group, lambda {|group|
79 group_id = group.is_a?(Group) ? group.id : group.to_i
80 group_id = group.is_a?(Group) ? group.id : group.to_i
80 { :conditions => ["#{User.table_name}.id IN (SELECT gu.user_id FROM #{table_name_prefix}groups_users#{table_name_suffix} gu WHERE gu.group_id = ?)", group_id] }
81 { :conditions => ["#{User.table_name}.id IN (SELECT gu.user_id FROM #{table_name_prefix}groups_users#{table_name_suffix} gu WHERE gu.group_id = ?)", group_id] }
81 }
82 }
82 named_scope :not_in_group, lambda {|group|
83 named_scope :not_in_group, lambda {|group|
83 group_id = group.is_a?(Group) ? group.id : group.to_i
84 group_id = group.is_a?(Group) ? group.id : group.to_i
84 { :conditions => ["#{User.table_name}.id NOT IN (SELECT gu.user_id FROM #{table_name_prefix}groups_users#{table_name_suffix} gu WHERE gu.group_id = ?)", group_id] }
85 { :conditions => ["#{User.table_name}.id NOT IN (SELECT gu.user_id FROM #{table_name_prefix}groups_users#{table_name_suffix} gu WHERE gu.group_id = ?)", group_id] }
85 }
86 }
86
87
87 def before_create
88 def set_mail_notification
88 self.mail_notification = Setting.default_notification_option if self.mail_notification.blank?
89 self.mail_notification = Setting.default_notification_option if self.mail_notification.blank?
89 true
90 true
90 end
91 end
91
92
92 def before_save
93 def before_save
93 # update hashed_password if password was set
94 # update hashed_password if password was set
94 if self.password && self.auth_source_id.blank?
95 if self.password && self.auth_source_id.blank?
95 salt_password(password)
96 salt_password(password)
96 end
97 end
97 end
98 end
98
99
99 def reload(*args)
100 def reload(*args)
100 @name = nil
101 @name = nil
101 @projects_by_role = nil
102 @projects_by_role = nil
102 super
103 super
103 end
104 end
104
105
105 def mail=(arg)
106 def mail=(arg)
106 write_attribute(:mail, arg.to_s.strip)
107 write_attribute(:mail, arg.to_s.strip)
107 end
108 end
108
109
109 def identity_url=(url)
110 def identity_url=(url)
110 if url.blank?
111 if url.blank?
111 write_attribute(:identity_url, '')
112 write_attribute(:identity_url, '')
112 else
113 else
113 begin
114 begin
114 write_attribute(:identity_url, OpenIdAuthentication.normalize_identifier(url))
115 write_attribute(:identity_url, OpenIdAuthentication.normalize_identifier(url))
115 rescue OpenIdAuthentication::InvalidOpenId
116 rescue OpenIdAuthentication::InvalidOpenId
116 # Invlaid url, don't save
117 # Invlaid url, don't save
117 end
118 end
118 end
119 end
119 self.read_attribute(:identity_url)
120 self.read_attribute(:identity_url)
120 end
121 end
121
122
122 # Returns the user that matches provided login and password, or nil
123 # Returns the user that matches provided login and password, or nil
123 def self.try_to_login(login, password)
124 def self.try_to_login(login, password)
124 # Make sure no one can sign in with an empty password
125 # Make sure no one can sign in with an empty password
125 return nil if password.to_s.empty?
126 return nil if password.to_s.empty?
126 user = find_by_login(login)
127 user = find_by_login(login)
127 if user
128 if user
128 # user is already in local database
129 # user is already in local database
129 return nil if !user.active?
130 return nil if !user.active?
130 if user.auth_source
131 if user.auth_source
131 # user has an external authentication method
132 # user has an external authentication method
132 return nil unless user.auth_source.authenticate(login, password)
133 return nil unless user.auth_source.authenticate(login, password)
133 else
134 else
134 # authentication with local password
135 # authentication with local password
135 return nil unless user.check_password?(password)
136 return nil unless user.check_password?(password)
136 end
137 end
137 else
138 else
138 # user is not yet registered, try to authenticate with available sources
139 # user is not yet registered, try to authenticate with available sources
139 attrs = AuthSource.authenticate(login, password)
140 attrs = AuthSource.authenticate(login, password)
140 if attrs
141 if attrs
141 user = new(attrs)
142 user = new(attrs)
142 user.login = login
143 user.login = login
143 user.language = Setting.default_language
144 user.language = Setting.default_language
144 if user.save
145 if user.save
145 user.reload
146 user.reload
146 logger.info("User '#{user.login}' created from external auth source: #{user.auth_source.type} - #{user.auth_source.name}") if logger && user.auth_source
147 logger.info("User '#{user.login}' created from external auth source: #{user.auth_source.type} - #{user.auth_source.name}") if logger && user.auth_source
147 end
148 end
148 end
149 end
149 end
150 end
150 user.update_attribute(:last_login_on, Time.now) if user && !user.new_record?
151 user.update_attribute(:last_login_on, Time.now) if user && !user.new_record?
151 user
152 user
152 rescue => text
153 rescue => text
153 raise text
154 raise text
154 end
155 end
155
156
156 # Returns the user who matches the given autologin +key+ or nil
157 # Returns the user who matches the given autologin +key+ or nil
157 def self.try_to_autologin(key)
158 def self.try_to_autologin(key)
158 tokens = Token.find_all_by_action_and_value('autologin', key)
159 tokens = Token.find_all_by_action_and_value('autologin', key)
159 # Make sure there's only 1 token that matches the key
160 # Make sure there's only 1 token that matches the key
160 if tokens.size == 1
161 if tokens.size == 1
161 token = tokens.first
162 token = tokens.first
162 if (token.created_on > Setting.autologin.to_i.day.ago) && token.user && token.user.active?
163 if (token.created_on > Setting.autologin.to_i.day.ago) && token.user && token.user.active?
163 token.user.update_attribute(:last_login_on, Time.now)
164 token.user.update_attribute(:last_login_on, Time.now)
164 token.user
165 token.user
165 end
166 end
166 end
167 end
167 end
168 end
168
169
169 # Return user's full name for display
170 # Return user's full name for display
170 def name(formatter = nil)
171 def name(formatter = nil)
171 if formatter
172 if formatter
172 eval('"' + (USER_FORMATS[formatter] || USER_FORMATS[:firstname_lastname]) + '"')
173 eval('"' + (USER_FORMATS[formatter] || USER_FORMATS[:firstname_lastname]) + '"')
173 else
174 else
174 @name ||= eval('"' + (USER_FORMATS[Setting.user_format] || USER_FORMATS[:firstname_lastname]) + '"')
175 @name ||= eval('"' + (USER_FORMATS[Setting.user_format] || USER_FORMATS[:firstname_lastname]) + '"')
175 end
176 end
176 end
177 end
177
178
178 def active?
179 def active?
179 self.status == STATUS_ACTIVE
180 self.status == STATUS_ACTIVE
180 end
181 end
181
182
182 def registered?
183 def registered?
183 self.status == STATUS_REGISTERED
184 self.status == STATUS_REGISTERED
184 end
185 end
185
186
186 def locked?
187 def locked?
187 self.status == STATUS_LOCKED
188 self.status == STATUS_LOCKED
188 end
189 end
189
190
190 def activate
191 def activate
191 self.status = STATUS_ACTIVE
192 self.status = STATUS_ACTIVE
192 end
193 end
193
194
194 def register
195 def register
195 self.status = STATUS_REGISTERED
196 self.status = STATUS_REGISTERED
196 end
197 end
197
198
198 def lock
199 def lock
199 self.status = STATUS_LOCKED
200 self.status = STATUS_LOCKED
200 end
201 end
201
202
202 def activate!
203 def activate!
203 update_attribute(:status, STATUS_ACTIVE)
204 update_attribute(:status, STATUS_ACTIVE)
204 end
205 end
205
206
206 def register!
207 def register!
207 update_attribute(:status, STATUS_REGISTERED)
208 update_attribute(:status, STATUS_REGISTERED)
208 end
209 end
209
210
210 def lock!
211 def lock!
211 update_attribute(:status, STATUS_LOCKED)
212 update_attribute(:status, STATUS_LOCKED)
212 end
213 end
213
214
214 # Returns true if +clear_password+ is the correct user's password, otherwise false
215 # Returns true if +clear_password+ is the correct user's password, otherwise false
215 def check_password?(clear_password)
216 def check_password?(clear_password)
216 if auth_source_id.present?
217 if auth_source_id.present?
217 auth_source.authenticate(self.login, clear_password)
218 auth_source.authenticate(self.login, clear_password)
218 else
219 else
219 User.hash_password("#{salt}#{User.hash_password clear_password}") == hashed_password
220 User.hash_password("#{salt}#{User.hash_password clear_password}") == hashed_password
220 end
221 end
221 end
222 end
222
223
223 # Generates a random salt and computes hashed_password for +clear_password+
224 # Generates a random salt and computes hashed_password for +clear_password+
224 # The hashed password is stored in the following form: SHA1(salt + SHA1(password))
225 # The hashed password is stored in the following form: SHA1(salt + SHA1(password))
225 def salt_password(clear_password)
226 def salt_password(clear_password)
226 self.salt = User.generate_salt
227 self.salt = User.generate_salt
227 self.hashed_password = User.hash_password("#{salt}#{User.hash_password clear_password}")
228 self.hashed_password = User.hash_password("#{salt}#{User.hash_password clear_password}")
228 end
229 end
229
230
230 # Does the backend storage allow this user to change their password?
231 # Does the backend storage allow this user to change their password?
231 def change_password_allowed?
232 def change_password_allowed?
232 return true if auth_source_id.blank?
233 return true if auth_source_id.blank?
233 return auth_source.allow_password_changes?
234 return auth_source.allow_password_changes?
234 end
235 end
235
236
236 # Generate and set a random password. Useful for automated user creation
237 # Generate and set a random password. Useful for automated user creation
237 # Based on Token#generate_token_value
238 # Based on Token#generate_token_value
238 #
239 #
239 def random_password
240 def random_password
240 chars = ("a".."z").to_a + ("A".."Z").to_a + ("0".."9").to_a
241 chars = ("a".."z").to_a + ("A".."Z").to_a + ("0".."9").to_a
241 password = ''
242 password = ''
242 40.times { |i| password << chars[rand(chars.size-1)] }
243 40.times { |i| password << chars[rand(chars.size-1)] }
243 self.password = password
244 self.password = password
244 self.password_confirmation = password
245 self.password_confirmation = password
245 self
246 self
246 end
247 end
247
248
248 def pref
249 def pref
249 self.preference ||= UserPreference.new(:user => self)
250 self.preference ||= UserPreference.new(:user => self)
250 end
251 end
251
252
252 def time_zone
253 def time_zone
253 @time_zone ||= (self.pref.time_zone.blank? ? nil : ActiveSupport::TimeZone[self.pref.time_zone])
254 @time_zone ||= (self.pref.time_zone.blank? ? nil : ActiveSupport::TimeZone[self.pref.time_zone])
254 end
255 end
255
256
256 def wants_comments_in_reverse_order?
257 def wants_comments_in_reverse_order?
257 self.pref[:comments_sorting] == 'desc'
258 self.pref[:comments_sorting] == 'desc'
258 end
259 end
259
260
260 # Return user's RSS key (a 40 chars long string), used to access feeds
261 # Return user's RSS key (a 40 chars long string), used to access feeds
261 def rss_key
262 def rss_key
262 token = self.rss_token || Token.create(:user => self, :action => 'feeds')
263 token = self.rss_token || Token.create(:user => self, :action => 'feeds')
263 token.value
264 token.value
264 end
265 end
265
266
266 # Return user's API key (a 40 chars long string), used to access the API
267 # Return user's API key (a 40 chars long string), used to access the API
267 def api_key
268 def api_key
268 token = self.api_token || self.create_api_token(:action => 'api')
269 token = self.api_token || self.create_api_token(:action => 'api')
269 token.value
270 token.value
270 end
271 end
271
272
272 # Return an array of project ids for which the user has explicitly turned mail notifications on
273 # Return an array of project ids for which the user has explicitly turned mail notifications on
273 def notified_projects_ids
274 def notified_projects_ids
274 @notified_projects_ids ||= memberships.select {|m| m.mail_notification?}.collect(&:project_id)
275 @notified_projects_ids ||= memberships.select {|m| m.mail_notification?}.collect(&:project_id)
275 end
276 end
276
277
277 def notified_project_ids=(ids)
278 def notified_project_ids=(ids)
278 Member.update_all("mail_notification = #{connection.quoted_false}", ['user_id = ?', id])
279 Member.update_all("mail_notification = #{connection.quoted_false}", ['user_id = ?', id])
279 Member.update_all("mail_notification = #{connection.quoted_true}", ['user_id = ? AND project_id IN (?)', id, ids]) if ids && !ids.empty?
280 Member.update_all("mail_notification = #{connection.quoted_true}", ['user_id = ? AND project_id IN (?)', id, ids]) if ids && !ids.empty?
280 @notified_projects_ids = nil
281 @notified_projects_ids = nil
281 notified_projects_ids
282 notified_projects_ids
282 end
283 end
283
284
284 def valid_notification_options
285 def valid_notification_options
285 self.class.valid_notification_options(self)
286 self.class.valid_notification_options(self)
286 end
287 end
287
288
288 # Only users that belong to more than 1 project can select projects for which they are notified
289 # Only users that belong to more than 1 project can select projects for which they are notified
289 def self.valid_notification_options(user=nil)
290 def self.valid_notification_options(user=nil)
290 # Note that @user.membership.size would fail since AR ignores
291 # Note that @user.membership.size would fail since AR ignores
291 # :include association option when doing a count
292 # :include association option when doing a count
292 if user.nil? || user.memberships.length < 1
293 if user.nil? || user.memberships.length < 1
293 MAIL_NOTIFICATION_OPTIONS.reject {|option| option.first == 'selected'}
294 MAIL_NOTIFICATION_OPTIONS.reject {|option| option.first == 'selected'}
294 else
295 else
295 MAIL_NOTIFICATION_OPTIONS
296 MAIL_NOTIFICATION_OPTIONS
296 end
297 end
297 end
298 end
298
299
299 # Find a user account by matching the exact login and then a case-insensitive
300 # Find a user account by matching the exact login and then a case-insensitive
300 # version. Exact matches will be given priority.
301 # version. Exact matches will be given priority.
301 def self.find_by_login(login)
302 def self.find_by_login(login)
302 # force string comparison to be case sensitive on MySQL
303 # force string comparison to be case sensitive on MySQL
303 type_cast = (ActiveRecord::Base.connection.adapter_name == 'MySQL') ? 'BINARY' : ''
304 type_cast = (ActiveRecord::Base.connection.adapter_name == 'MySQL') ? 'BINARY' : ''
304
305
305 # First look for an exact match
306 # First look for an exact match
306 user = first(:conditions => ["#{type_cast} login = ?", login])
307 user = first(:conditions => ["#{type_cast} login = ?", login])
307 # Fail over to case-insensitive if none was found
308 # Fail over to case-insensitive if none was found
308 user ||= first(:conditions => ["#{type_cast} LOWER(login) = ?", login.to_s.downcase])
309 user ||= first(:conditions => ["#{type_cast} LOWER(login) = ?", login.to_s.downcase])
309 end
310 end
310
311
311 def self.find_by_rss_key(key)
312 def self.find_by_rss_key(key)
312 token = Token.find_by_value(key)
313 token = Token.find_by_value(key)
313 token && token.user.active? ? token.user : nil
314 token && token.user.active? ? token.user : nil
314 end
315 end
315
316
316 def self.find_by_api_key(key)
317 def self.find_by_api_key(key)
317 token = Token.find_by_action_and_value('api', key)
318 token = Token.find_by_action_and_value('api', key)
318 token && token.user.active? ? token.user : nil
319 token && token.user.active? ? token.user : nil
319 end
320 end
320
321
321 # Makes find_by_mail case-insensitive
322 # Makes find_by_mail case-insensitive
322 def self.find_by_mail(mail)
323 def self.find_by_mail(mail)
323 find(:first, :conditions => ["LOWER(mail) = ?", mail.to_s.downcase])
324 find(:first, :conditions => ["LOWER(mail) = ?", mail.to_s.downcase])
324 end
325 end
325
326
326 def to_s
327 def to_s
327 name
328 name
328 end
329 end
329
330
330 # Returns the current day according to user's time zone
331 # Returns the current day according to user's time zone
331 def today
332 def today
332 if time_zone.nil?
333 if time_zone.nil?
333 Date.today
334 Date.today
334 else
335 else
335 Time.now.in_time_zone(time_zone).to_date
336 Time.now.in_time_zone(time_zone).to_date
336 end
337 end
337 end
338 end
338
339
339 def logged?
340 def logged?
340 true
341 true
341 end
342 end
342
343
343 def anonymous?
344 def anonymous?
344 !logged?
345 !logged?
345 end
346 end
346
347
347 # Return user's roles for project
348 # Return user's roles for project
348 def roles_for_project(project)
349 def roles_for_project(project)
349 roles = []
350 roles = []
350 # No role on archived projects
351 # No role on archived projects
351 return roles unless project && project.active?
352 return roles unless project && project.active?
352 if logged?
353 if logged?
353 # Find project membership
354 # Find project membership
354 membership = memberships.detect {|m| m.project_id == project.id}
355 membership = memberships.detect {|m| m.project_id == project.id}
355 if membership
356 if membership
356 roles = membership.roles
357 roles = membership.roles
357 else
358 else
358 @role_non_member ||= Role.non_member
359 @role_non_member ||= Role.non_member
359 roles << @role_non_member
360 roles << @role_non_member
360 end
361 end
361 else
362 else
362 @role_anonymous ||= Role.anonymous
363 @role_anonymous ||= Role.anonymous
363 roles << @role_anonymous
364 roles << @role_anonymous
364 end
365 end
365 roles
366 roles
366 end
367 end
367
368
368 # Return true if the user is a member of project
369 # Return true if the user is a member of project
369 def member_of?(project)
370 def member_of?(project)
370 !roles_for_project(project).detect {|role| role.member?}.nil?
371 !roles_for_project(project).detect {|role| role.member?}.nil?
371 end
372 end
372
373
373 # Returns a hash of user's projects grouped by roles
374 # Returns a hash of user's projects grouped by roles
374 def projects_by_role
375 def projects_by_role
375 return @projects_by_role if @projects_by_role
376 return @projects_by_role if @projects_by_role
376
377
377 @projects_by_role = Hash.new {|h,k| h[k]=[]}
378 @projects_by_role = Hash.new {|h,k| h[k]=[]}
378 memberships.each do |membership|
379 memberships.each do |membership|
379 membership.roles.each do |role|
380 membership.roles.each do |role|
380 @projects_by_role[role] << membership.project if membership.project
381 @projects_by_role[role] << membership.project if membership.project
381 end
382 end
382 end
383 end
383 @projects_by_role.each do |role, projects|
384 @projects_by_role.each do |role, projects|
384 projects.uniq!
385 projects.uniq!
385 end
386 end
386
387
387 @projects_by_role
388 @projects_by_role
388 end
389 end
389
390
390 # Returns true if user is arg or belongs to arg
391 # Returns true if user is arg or belongs to arg
391 def is_or_belongs_to?(arg)
392 def is_or_belongs_to?(arg)
392 if arg.is_a?(User)
393 if arg.is_a?(User)
393 self == arg
394 self == arg
394 elsif arg.is_a?(Group)
395 elsif arg.is_a?(Group)
395 arg.users.include?(self)
396 arg.users.include?(self)
396 else
397 else
397 false
398 false
398 end
399 end
399 end
400 end
400
401
401 # Return true if the user is allowed to do the specified action on a specific context
402 # Return true if the user is allowed to do the specified action on a specific context
402 # Action can be:
403 # Action can be:
403 # * a parameter-like Hash (eg. :controller => 'projects', :action => 'edit')
404 # * a parameter-like Hash (eg. :controller => 'projects', :action => 'edit')
404 # * a permission Symbol (eg. :edit_project)
405 # * a permission Symbol (eg. :edit_project)
405 # Context can be:
406 # Context can be:
406 # * a project : returns true if user is allowed to do the specified action on this project
407 # * a project : returns true if user is allowed to do the specified action on this project
407 # * an array of projects : returns true if user is allowed on every project
408 # * an array of projects : returns true if user is allowed on every project
408 # * nil with options[:global] set : check if user has at least one role allowed for this action,
409 # * nil with options[:global] set : check if user has at least one role allowed for this action,
409 # or falls back to Non Member / Anonymous permissions depending if the user is logged
410 # or falls back to Non Member / Anonymous permissions depending if the user is logged
410 def allowed_to?(action, context, options={}, &block)
411 def allowed_to?(action, context, options={}, &block)
411 if context && context.is_a?(Project)
412 if context && context.is_a?(Project)
412 # No action allowed on archived projects
413 # No action allowed on archived projects
413 return false unless context.active?
414 return false unless context.active?
414 # No action allowed on disabled modules
415 # No action allowed on disabled modules
415 return false unless context.allows_to?(action)
416 return false unless context.allows_to?(action)
416 # Admin users are authorized for anything else
417 # Admin users are authorized for anything else
417 return true if admin?
418 return true if admin?
418
419
419 roles = roles_for_project(context)
420 roles = roles_for_project(context)
420 return false unless roles
421 return false unless roles
421 roles.detect {|role|
422 roles.detect {|role|
422 (context.is_public? || role.member?) &&
423 (context.is_public? || role.member?) &&
423 role.allowed_to?(action) &&
424 role.allowed_to?(action) &&
424 (block_given? ? yield(role, self) : true)
425 (block_given? ? yield(role, self) : true)
425 }
426 }
426 elsif context && context.is_a?(Array)
427 elsif context && context.is_a?(Array)
427 # Authorize if user is authorized on every element of the array
428 # Authorize if user is authorized on every element of the array
428 context.map do |project|
429 context.map do |project|
429 allowed_to?(action, project, options, &block)
430 allowed_to?(action, project, options, &block)
430 end.inject do |memo,allowed|
431 end.inject do |memo,allowed|
431 memo && allowed
432 memo && allowed
432 end
433 end
433 elsif options[:global]
434 elsif options[:global]
434 # Admin users are always authorized
435 # Admin users are always authorized
435 return true if admin?
436 return true if admin?
436
437
437 # authorize if user has at least one role that has this permission
438 # authorize if user has at least one role that has this permission
438 roles = memberships.collect {|m| m.roles}.flatten.uniq
439 roles = memberships.collect {|m| m.roles}.flatten.uniq
439 roles << (self.logged? ? Role.non_member : Role.anonymous)
440 roles << (self.logged? ? Role.non_member : Role.anonymous)
440 roles.detect {|role|
441 roles.detect {|role|
441 role.allowed_to?(action) &&
442 role.allowed_to?(action) &&
442 (block_given? ? yield(role, self) : true)
443 (block_given? ? yield(role, self) : true)
443 }
444 }
444 else
445 else
445 false
446 false
446 end
447 end
447 end
448 end
448
449
449 # Is the user allowed to do the specified action on any project?
450 # Is the user allowed to do the specified action on any project?
450 # See allowed_to? for the actions and valid options.
451 # See allowed_to? for the actions and valid options.
451 def allowed_to_globally?(action, options, &block)
452 def allowed_to_globally?(action, options, &block)
452 allowed_to?(action, nil, options.reverse_merge(:global => true), &block)
453 allowed_to?(action, nil, options.reverse_merge(:global => true), &block)
453 end
454 end
454
455
455 safe_attributes 'login',
456 safe_attributes 'login',
456 'firstname',
457 'firstname',
457 'lastname',
458 'lastname',
458 'mail',
459 'mail',
459 'mail_notification',
460 'mail_notification',
460 'language',
461 'language',
461 'custom_field_values',
462 'custom_field_values',
462 'custom_fields',
463 'custom_fields',
463 'identity_url'
464 'identity_url'
464
465
465 safe_attributes 'status',
466 safe_attributes 'status',
466 'auth_source_id',
467 'auth_source_id',
467 :if => lambda {|user, current_user| current_user.admin?}
468 :if => lambda {|user, current_user| current_user.admin?}
468
469
469 safe_attributes 'group_ids',
470 safe_attributes 'group_ids',
470 :if => lambda {|user, current_user| current_user.admin? && !user.new_record?}
471 :if => lambda {|user, current_user| current_user.admin? && !user.new_record?}
471
472
472 # Utility method to help check if a user should be notified about an
473 # Utility method to help check if a user should be notified about an
473 # event.
474 # event.
474 #
475 #
475 # TODO: only supports Issue events currently
476 # TODO: only supports Issue events currently
476 def notify_about?(object)
477 def notify_about?(object)
477 case mail_notification
478 case mail_notification
478 when 'all'
479 when 'all'
479 true
480 true
480 when 'selected'
481 when 'selected'
481 # user receives notifications for created/assigned issues on unselected projects
482 # user receives notifications for created/assigned issues on unselected projects
482 if object.is_a?(Issue) && (object.author == self || is_or_belongs_to?(object.assigned_to))
483 if object.is_a?(Issue) && (object.author == self || is_or_belongs_to?(object.assigned_to))
483 true
484 true
484 else
485 else
485 false
486 false
486 end
487 end
487 when 'none'
488 when 'none'
488 false
489 false
489 when 'only_my_events'
490 when 'only_my_events'
490 if object.is_a?(Issue) && (object.author == self || is_or_belongs_to?(object.assigned_to))
491 if object.is_a?(Issue) && (object.author == self || is_or_belongs_to?(object.assigned_to))
491 true
492 true
492 else
493 else
493 false
494 false
494 end
495 end
495 when 'only_assigned'
496 when 'only_assigned'
496 if object.is_a?(Issue) && is_or_belongs_to?(object.assigned_to)
497 if object.is_a?(Issue) && is_or_belongs_to?(object.assigned_to)
497 true
498 true
498 else
499 else
499 false
500 false
500 end
501 end
501 when 'only_owner'
502 when 'only_owner'
502 if object.is_a?(Issue) && object.author == self
503 if object.is_a?(Issue) && object.author == self
503 true
504 true
504 else
505 else
505 false
506 false
506 end
507 end
507 else
508 else
508 false
509 false
509 end
510 end
510 end
511 end
511
512
512 def self.current=(user)
513 def self.current=(user)
513 @current_user = user
514 @current_user = user
514 end
515 end
515
516
516 def self.current
517 def self.current
517 @current_user ||= User.anonymous
518 @current_user ||= User.anonymous
518 end
519 end
519
520
520 # Returns the anonymous user. If the anonymous user does not exist, it is created. There can be only
521 # Returns the anonymous user. If the anonymous user does not exist, it is created. There can be only
521 # one anonymous user per database.
522 # one anonymous user per database.
522 def self.anonymous
523 def self.anonymous
523 anonymous_user = AnonymousUser.find(:first)
524 anonymous_user = AnonymousUser.find(:first)
524 if anonymous_user.nil?
525 if anonymous_user.nil?
525 anonymous_user = AnonymousUser.create(:lastname => 'Anonymous', :firstname => '', :mail => '', :login => '', :status => 0)
526 anonymous_user = AnonymousUser.create(:lastname => 'Anonymous', :firstname => '', :mail => '', :login => '', :status => 0)
526 raise 'Unable to create the anonymous user.' if anonymous_user.new_record?
527 raise 'Unable to create the anonymous user.' if anonymous_user.new_record?
527 end
528 end
528 anonymous_user
529 anonymous_user
529 end
530 end
530
531
531 # Salts all existing unsalted passwords
532 # Salts all existing unsalted passwords
532 # It changes password storage scheme from SHA1(password) to SHA1(salt + SHA1(password))
533 # It changes password storage scheme from SHA1(password) to SHA1(salt + SHA1(password))
533 # This method is used in the SaltPasswords migration and is to be kept as is
534 # This method is used in the SaltPasswords migration and is to be kept as is
534 def self.salt_unsalted_passwords!
535 def self.salt_unsalted_passwords!
535 transaction do
536 transaction do
536 User.find_each(:conditions => "salt IS NULL OR salt = ''") do |user|
537 User.find_each(:conditions => "salt IS NULL OR salt = ''") do |user|
537 next if user.hashed_password.blank?
538 next if user.hashed_password.blank?
538 salt = User.generate_salt
539 salt = User.generate_salt
539 hashed_password = User.hash_password("#{salt}#{user.hashed_password}")
540 hashed_password = User.hash_password("#{salt}#{user.hashed_password}")
540 User.update_all("salt = '#{salt}', hashed_password = '#{hashed_password}'", ["id = ?", user.id] )
541 User.update_all("salt = '#{salt}', hashed_password = '#{hashed_password}'", ["id = ?", user.id] )
541 end
542 end
542 end
543 end
543 end
544 end
544
545
545 protected
546 protected
546
547
547 def validate
548 def validate
548 # Password length validation based on setting
549 # Password length validation based on setting
549 if !password.nil? && password.size < Setting.password_min_length.to_i
550 if !password.nil? && password.size < Setting.password_min_length.to_i
550 errors.add(:password, :too_short, :count => Setting.password_min_length.to_i)
551 errors.add(:password, :too_short, :count => Setting.password_min_length.to_i)
551 end
552 end
552 end
553 end
553
554
554 private
555 private
555
556
556 # Removes references that are not handled by associations
557 # Removes references that are not handled by associations
557 # Things that are not deleted are reassociated with the anonymous user
558 # Things that are not deleted are reassociated with the anonymous user
558 def remove_references_before_destroy
559 def remove_references_before_destroy
559 return if self.id.nil?
560 return if self.id.nil?
560
561
561 substitute = User.anonymous
562 substitute = User.anonymous
562 Attachment.update_all ['author_id = ?', substitute.id], ['author_id = ?', id]
563 Attachment.update_all ['author_id = ?', substitute.id], ['author_id = ?', id]
563 Comment.update_all ['author_id = ?', substitute.id], ['author_id = ?', id]
564 Comment.update_all ['author_id = ?', substitute.id], ['author_id = ?', id]
564 Issue.update_all ['author_id = ?', substitute.id], ['author_id = ?', id]
565 Issue.update_all ['author_id = ?', substitute.id], ['author_id = ?', id]
565 Issue.update_all 'assigned_to_id = NULL', ['assigned_to_id = ?', id]
566 Issue.update_all 'assigned_to_id = NULL', ['assigned_to_id = ?', id]
566 Journal.update_all ['user_id = ?', substitute.id], ['user_id = ?', id]
567 Journal.update_all ['user_id = ?', substitute.id], ['user_id = ?', id]
567 JournalDetail.update_all ['old_value = ?', substitute.id.to_s], ["property = 'attr' AND prop_key = 'assigned_to_id' AND old_value = ?", id.to_s]
568 JournalDetail.update_all ['old_value = ?', substitute.id.to_s], ["property = 'attr' AND prop_key = 'assigned_to_id' AND old_value = ?", id.to_s]
568 JournalDetail.update_all ['value = ?', substitute.id.to_s], ["property = 'attr' AND prop_key = 'assigned_to_id' AND value = ?", id.to_s]
569 JournalDetail.update_all ['value = ?', substitute.id.to_s], ["property = 'attr' AND prop_key = 'assigned_to_id' AND value = ?", id.to_s]
569 Message.update_all ['author_id = ?', substitute.id], ['author_id = ?', id]
570 Message.update_all ['author_id = ?', substitute.id], ['author_id = ?', id]
570 News.update_all ['author_id = ?', substitute.id], ['author_id = ?', id]
571 News.update_all ['author_id = ?', substitute.id], ['author_id = ?', id]
571 # Remove private queries and keep public ones
572 # Remove private queries and keep public ones
572 Query.delete_all ['user_id = ? AND is_public = ?', id, false]
573 Query.delete_all ['user_id = ? AND is_public = ?', id, false]
573 Query.update_all ['user_id = ?', substitute.id], ['user_id = ?', id]
574 Query.update_all ['user_id = ?', substitute.id], ['user_id = ?', id]
574 TimeEntry.update_all ['user_id = ?', substitute.id], ['user_id = ?', id]
575 TimeEntry.update_all ['user_id = ?', substitute.id], ['user_id = ?', id]
575 Token.delete_all ['user_id = ?', id]
576 Token.delete_all ['user_id = ?', id]
576 Watcher.delete_all ['user_id = ?', id]
577 Watcher.delete_all ['user_id = ?', id]
577 WikiContent.update_all ['author_id = ?', substitute.id], ['author_id = ?', id]
578 WikiContent.update_all ['author_id = ?', substitute.id], ['author_id = ?', id]
578 WikiContent::Version.update_all ['author_id = ?', substitute.id], ['author_id = ?', id]
579 WikiContent::Version.update_all ['author_id = ?', substitute.id], ['author_id = ?', id]
579 end
580 end
580
581
581 # Return password digest
582 # Return password digest
582 def self.hash_password(clear_password)
583 def self.hash_password(clear_password)
583 Digest::SHA1.hexdigest(clear_password || "")
584 Digest::SHA1.hexdigest(clear_password || "")
584 end
585 end
585
586
586 # Returns a 128bits random salt as a hex string (32 chars long)
587 # Returns a 128bits random salt as a hex string (32 chars long)
587 def self.generate_salt
588 def self.generate_salt
588 ActiveSupport::SecureRandom.hex(16)
589 ActiveSupport::SecureRandom.hex(16)
589 end
590 end
590
591
591 end
592 end
592
593
593 class AnonymousUser < User
594 class AnonymousUser < User
594
595
595 def validate_on_create
596 def validate_on_create
596 # There should be only one AnonymousUser in the database
597 # There should be only one AnonymousUser in the database
597 errors.add_to_base 'An anonymous user already exists.' if AnonymousUser.find(:first)
598 errors.add_to_base 'An anonymous user already exists.' if AnonymousUser.find(:first)
598 end
599 end
599
600
600 def available_custom_fields
601 def available_custom_fields
601 []
602 []
602 end
603 end
603
604
604 # Overrides a few properties
605 # Overrides a few properties
605 def logged?; false end
606 def logged?; false end
606 def admin; false end
607 def admin; false end
607 def name(*args); I18n.t(:label_user_anonymous) end
608 def name(*args); I18n.t(:label_user_anonymous) end
608 def mail; nil end
609 def mail; nil end
609 def time_zone; nil end
610 def time_zone; nil end
610 def rss_key; nil end
611 def rss_key; nil end
611
612
612 # Anonymous user can not be destroyed
613 # Anonymous user can not be destroyed
613 def destroy
614 def destroy
614 false
615 false
615 end
616 end
616 end
617 end
General Comments 0
You need to be logged in to leave comments. Login now