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