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