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