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