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