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