##// END OF EJS Templates
Column 'address' in where clause may be ambiguous (#19485)....
Jean-Philippe Lang -
r13902:b688214ce038
parent child
Show More
@@ -1,852 +1,852
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(#{EmailAddress.table_name}.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 period = Setting.password_max_age.to_i
328 period = Setting.password_max_age.to_i
329 if period.zero?
329 if period.zero?
330 false
330 false
331 else
331 else
332 changed_on = self.passwd_changed_on || Time.at(0)
332 changed_on = self.passwd_changed_on || Time.at(0)
333 changed_on < period.days.ago
333 changed_on < period.days.ago
334 end
334 end
335 end
335 end
336
336
337 def must_change_password?
337 def must_change_password?
338 (must_change_passwd? || password_expired?) && change_password_allowed?
338 (must_change_passwd? || password_expired?) && change_password_allowed?
339 end
339 end
340
340
341 def generate_password?
341 def generate_password?
342 generate_password == '1' || generate_password == true
342 generate_password == '1' || generate_password == true
343 end
343 end
344
344
345 # Generate and set a random password on given length
345 # Generate and set a random password on given length
346 def random_password(length=40)
346 def random_password(length=40)
347 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
348 chars -= %w(0 O 1 l)
348 chars -= %w(0 O 1 l)
349 password = ''
349 password = ''
350 length.times {|i| password << chars[SecureRandom.random_number(chars.size)] }
350 length.times {|i| password << chars[SecureRandom.random_number(chars.size)] }
351 self.password = password
351 self.password = password
352 self.password_confirmation = password
352 self.password_confirmation = password
353 self
353 self
354 end
354 end
355
355
356 def pref
356 def pref
357 self.preference ||= UserPreference.new(:user => self)
357 self.preference ||= UserPreference.new(:user => self)
358 end
358 end
359
359
360 def time_zone
360 def time_zone
361 @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])
362 end
362 end
363
363
364 def force_default_language?
364 def force_default_language?
365 Setting.force_default_language_for_loggedin?
365 Setting.force_default_language_for_loggedin?
366 end
366 end
367
367
368 def language
368 def language
369 if force_default_language?
369 if force_default_language?
370 Setting.default_language
370 Setting.default_language
371 else
371 else
372 super
372 super
373 end
373 end
374 end
374 end
375
375
376 def wants_comments_in_reverse_order?
376 def wants_comments_in_reverse_order?
377 self.pref[:comments_sorting] == 'desc'
377 self.pref[:comments_sorting] == 'desc'
378 end
378 end
379
379
380 # 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
381 def rss_key
381 def rss_key
382 if rss_token.nil?
382 if rss_token.nil?
383 create_rss_token(:action => 'feeds')
383 create_rss_token(:action => 'feeds')
384 end
384 end
385 rss_token.value
385 rss_token.value
386 end
386 end
387
387
388 # 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
389 def api_key
389 def api_key
390 if api_token.nil?
390 if api_token.nil?
391 create_api_token(:action => 'api')
391 create_api_token(:action => 'api')
392 end
392 end
393 api_token.value
393 api_token.value
394 end
394 end
395
395
396 # 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
397 def notified_projects_ids
397 def notified_projects_ids
398 @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)
399 end
399 end
400
400
401 def notified_project_ids=(ids)
401 def notified_project_ids=(ids)
402 @notified_projects_ids_changed = true
402 @notified_projects_ids_changed = true
403 @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}
404 end
404 end
405
405
406 # Updates per project notifications (after_save callback)
406 # Updates per project notifications (after_save callback)
407 def update_notified_project_ids
407 def update_notified_project_ids
408 if @notified_projects_ids_changed
408 if @notified_projects_ids_changed
409 ids = (mail_notification == 'selected' ? Array.wrap(notified_projects_ids).reject(&:blank?) : [])
409 ids = (mail_notification == 'selected' ? Array.wrap(notified_projects_ids).reject(&:blank?) : [])
410 members.update_all(:mail_notification => false)
410 members.update_all(:mail_notification => false)
411 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?
412 end
412 end
413 end
413 end
414 private :update_notified_project_ids
414 private :update_notified_project_ids
415
415
416 def valid_notification_options
416 def valid_notification_options
417 self.class.valid_notification_options(self)
417 self.class.valid_notification_options(self)
418 end
418 end
419
419
420 # 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
421 def self.valid_notification_options(user=nil)
421 def self.valid_notification_options(user=nil)
422 # Note that @user.membership.size would fail since AR ignores
422 # Note that @user.membership.size would fail since AR ignores
423 # :include association option when doing a count
423 # :include association option when doing a count
424 if user.nil? || user.memberships.length < 1
424 if user.nil? || user.memberships.length < 1
425 MAIL_NOTIFICATION_OPTIONS.reject {|option| option.first == 'selected'}
425 MAIL_NOTIFICATION_OPTIONS.reject {|option| option.first == 'selected'}
426 else
426 else
427 MAIL_NOTIFICATION_OPTIONS
427 MAIL_NOTIFICATION_OPTIONS
428 end
428 end
429 end
429 end
430
430
431 # 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
432 # version. Exact matches will be given priority.
432 # version. Exact matches will be given priority.
433 def self.find_by_login(login)
433 def self.find_by_login(login)
434 login = Redmine::CodesetUtil.replace_invalid_utf8(login.to_s)
434 login = Redmine::CodesetUtil.replace_invalid_utf8(login.to_s)
435 if login.present?
435 if login.present?
436 # First look for an exact match
436 # First look for an exact match
437 user = where(:login => login).detect {|u| u.login == login}
437 user = where(:login => login).detect {|u| u.login == login}
438 unless user
438 unless user
439 # Fail over to case-insensitive if none was found
439 # Fail over to case-insensitive if none was found
440 user = where("LOWER(login) = ?", login.downcase).first
440 user = where("LOWER(login) = ?", login.downcase).first
441 end
441 end
442 user
442 user
443 end
443 end
444 end
444 end
445
445
446 def self.find_by_rss_key(key)
446 def self.find_by_rss_key(key)
447 Token.find_active_user('feeds', key)
447 Token.find_active_user('feeds', key)
448 end
448 end
449
449
450 def self.find_by_api_key(key)
450 def self.find_by_api_key(key)
451 Token.find_active_user('api', key)
451 Token.find_active_user('api', key)
452 end
452 end
453
453
454 # Makes find_by_mail case-insensitive
454 # Makes find_by_mail case-insensitive
455 def self.find_by_mail(mail)
455 def self.find_by_mail(mail)
456 having_mail(mail).first
456 having_mail(mail).first
457 end
457 end
458
458
459 # 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
460 def self.default_admin_account_changed?
460 def self.default_admin_account_changed?
461 !User.active.find_by_login("admin").try(:check_password?, "admin")
461 !User.active.find_by_login("admin").try(:check_password?, "admin")
462 end
462 end
463
463
464 def to_s
464 def to_s
465 name
465 name
466 end
466 end
467
467
468 CSS_CLASS_BY_STATUS = {
468 CSS_CLASS_BY_STATUS = {
469 STATUS_ANONYMOUS => 'anon',
469 STATUS_ANONYMOUS => 'anon',
470 STATUS_ACTIVE => 'active',
470 STATUS_ACTIVE => 'active',
471 STATUS_REGISTERED => 'registered',
471 STATUS_REGISTERED => 'registered',
472 STATUS_LOCKED => 'locked'
472 STATUS_LOCKED => 'locked'
473 }
473 }
474
474
475 def css_classes
475 def css_classes
476 "user #{CSS_CLASS_BY_STATUS[status]}"
476 "user #{CSS_CLASS_BY_STATUS[status]}"
477 end
477 end
478
478
479 # Returns the current day according to user's time zone
479 # Returns the current day according to user's time zone
480 def today
480 def today
481 if time_zone.nil?
481 if time_zone.nil?
482 Date.today
482 Date.today
483 else
483 else
484 Time.now.in_time_zone(time_zone).to_date
484 Time.now.in_time_zone(time_zone).to_date
485 end
485 end
486 end
486 end
487
487
488 # Returns the day of +time+ according to user's time zone
488 # Returns the day of +time+ according to user's time zone
489 def time_to_date(time)
489 def time_to_date(time)
490 if time_zone.nil?
490 if time_zone.nil?
491 time.to_date
491 time.to_date
492 else
492 else
493 time.in_time_zone(time_zone).to_date
493 time.in_time_zone(time_zone).to_date
494 end
494 end
495 end
495 end
496
496
497 def logged?
497 def logged?
498 true
498 true
499 end
499 end
500
500
501 def anonymous?
501 def anonymous?
502 !logged?
502 !logged?
503 end
503 end
504
504
505 # Returns user's membership for the given project
505 # Returns user's membership for the given project
506 # or nil if the user is not a member of project
506 # or nil if the user is not a member of project
507 def membership(project)
507 def membership(project)
508 project_id = project.is_a?(Project) ? project.id : project
508 project_id = project.is_a?(Project) ? project.id : project
509
509
510 @membership_by_project_id ||= Hash.new {|h, project_id|
510 @membership_by_project_id ||= Hash.new {|h, project_id|
511 h[project_id] = memberships.where(:project_id => project_id).first
511 h[project_id] = memberships.where(:project_id => project_id).first
512 }
512 }
513 @membership_by_project_id[project_id]
513 @membership_by_project_id[project_id]
514 end
514 end
515
515
516 # Returns the user's bult-in role
516 # Returns the user's bult-in role
517 def builtin_role
517 def builtin_role
518 @builtin_role ||= Role.non_member
518 @builtin_role ||= Role.non_member
519 end
519 end
520
520
521 # Return user's roles for project
521 # Return user's roles for project
522 def roles_for_project(project)
522 def roles_for_project(project)
523 # No role on archived projects
523 # No role on archived projects
524 return [] if project.nil? || project.archived?
524 return [] if project.nil? || project.archived?
525 if membership = membership(project)
525 if membership = membership(project)
526 membership.roles.dup
526 membership.roles.dup
527 elsif project.is_public?
527 elsif project.is_public?
528 project.override_roles(builtin_role)
528 project.override_roles(builtin_role)
529 else
529 else
530 []
530 []
531 end
531 end
532 end
532 end
533
533
534 # Returns a hash of user's projects grouped by roles
534 # Returns a hash of user's projects grouped by roles
535 def projects_by_role
535 def projects_by_role
536 return @projects_by_role if @projects_by_role
536 return @projects_by_role if @projects_by_role
537
537
538 hash = Hash.new([])
538 hash = Hash.new([])
539
539
540 group_class = anonymous? ? GroupAnonymous : GroupNonMember
540 group_class = anonymous? ? GroupAnonymous : GroupNonMember
541 members = Member.joins(:project, :principal).
541 members = Member.joins(:project, :principal).
542 where("#{Project.table_name}.status <> 9").
542 where("#{Project.table_name}.status <> 9").
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 where("#{Member.table_name}.user_id = ? OR (#{Project.table_name}.is_public = ? AND #{Principal.table_name}.type = ?)", self.id, true, group_class.name).
544 preload(:project, :roles).
544 preload(:project, :roles).
545 to_a
545 to_a
546
546
547 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)}
548 members.each do |member|
548 members.each do |member|
549 if member.project
549 if member.project
550 member.roles.each do |role|
550 member.roles.each do |role|
551 hash[role] = [] unless hash.key?(role)
551 hash[role] = [] unless hash.key?(role)
552 hash[role] << member.project
552 hash[role] << member.project
553 end
553 end
554 end
554 end
555 end
555 end
556
556
557 hash.each do |role, projects|
557 hash.each do |role, projects|
558 projects.uniq!
558 projects.uniq!
559 end
559 end
560
560
561 @projects_by_role = hash
561 @projects_by_role = hash
562 end
562 end
563
563
564 # Returns the ids of visible projects
564 # Returns the ids of visible projects
565 def visible_project_ids
565 def visible_project_ids
566 @visible_project_ids ||= Project.visible(self).pluck(:id)
566 @visible_project_ids ||= Project.visible(self).pluck(:id)
567 end
567 end
568
568
569 # Returns true if user is arg or belongs to arg
569 # Returns true if user is arg or belongs to arg
570 def is_or_belongs_to?(arg)
570 def is_or_belongs_to?(arg)
571 if arg.is_a?(User)
571 if arg.is_a?(User)
572 self == arg
572 self == arg
573 elsif arg.is_a?(Group)
573 elsif arg.is_a?(Group)
574 arg.users.include?(self)
574 arg.users.include?(self)
575 else
575 else
576 false
576 false
577 end
577 end
578 end
578 end
579
579
580 # 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
581 # Action can be:
581 # Action can be:
582 # * a parameter-like Hash (eg. :controller => 'projects', :action => 'edit')
582 # * a parameter-like Hash (eg. :controller => 'projects', :action => 'edit')
583 # * a permission Symbol (eg. :edit_project)
583 # * a permission Symbol (eg. :edit_project)
584 # Context can be:
584 # Context can be:
585 # * 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
586 # * 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
587 # * 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,
588 # 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
589 def allowed_to?(action, context, options={}, &block)
589 def allowed_to?(action, context, options={}, &block)
590 if context && context.is_a?(Project)
590 if context && context.is_a?(Project)
591 return false unless context.allows_to?(action)
591 return false unless context.allows_to?(action)
592 # Admin users are authorized for anything else
592 # Admin users are authorized for anything else
593 return true if admin?
593 return true if admin?
594
594
595 roles = roles_for_project(context)
595 roles = roles_for_project(context)
596 return false unless roles
596 return false unless roles
597 roles.any? {|role|
597 roles.any? {|role|
598 (context.is_public? || role.member?) &&
598 (context.is_public? || role.member?) &&
599 role.allowed_to?(action) &&
599 role.allowed_to?(action) &&
600 (block_given? ? yield(role, self) : true)
600 (block_given? ? yield(role, self) : true)
601 }
601 }
602 elsif context && context.is_a?(Array)
602 elsif context && context.is_a?(Array)
603 if context.empty?
603 if context.empty?
604 false
604 false
605 else
605 else
606 # Authorize if user is authorized on every element of the array
606 # Authorize if user is authorized on every element of the array
607 context.map {|project| allowed_to?(action, project, options, &block)}.reduce(:&)
607 context.map {|project| allowed_to?(action, project, options, &block)}.reduce(:&)
608 end
608 end
609 elsif context
609 elsif context
610 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")
611 elsif options[:global]
611 elsif options[:global]
612 # Admin users are always authorized
612 # Admin users are always authorized
613 return true if admin?
613 return true if admin?
614
614
615 # 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
616 roles = memberships.collect {|m| m.roles}.flatten.uniq
616 roles = memberships.collect {|m| m.roles}.flatten.uniq
617 roles << (self.logged? ? Role.non_member : Role.anonymous)
617 roles << (self.logged? ? Role.non_member : Role.anonymous)
618 roles.any? {|role|
618 roles.any? {|role|
619 role.allowed_to?(action) &&
619 role.allowed_to?(action) &&
620 (block_given? ? yield(role, self) : true)
620 (block_given? ? yield(role, self) : true)
621 }
621 }
622 else
622 else
623 false
623 false
624 end
624 end
625 end
625 end
626
626
627 # 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?
628 # See allowed_to? for the actions and valid options.
628 # See allowed_to? for the actions and valid options.
629 #
629 #
630 # 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
631 # 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
632 # 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.
633 def allowed_to_globally?(action, options={}, &block)
633 def allowed_to_globally?(action, options={}, &block)
634 allowed_to?(action, nil, options.reverse_merge(:global => true), &block)
634 allowed_to?(action, nil, options.reverse_merge(:global => true), &block)
635 end
635 end
636
636
637 def allowed_to_view_all_time_entries?(context)
637 def allowed_to_view_all_time_entries?(context)
638 allowed_to?(:view_time_entries, context) do |role, user|
638 allowed_to?(:view_time_entries, context) do |role, user|
639 role.time_entries_visibility == 'all'
639 role.time_entries_visibility == 'all'
640 end
640 end
641 end
641 end
642
642
643 # Returns true if the user is allowed to delete the user's own account
643 # Returns true if the user is allowed to delete the user's own account
644 def own_account_deletable?
644 def own_account_deletable?
645 Setting.unsubscribe? &&
645 Setting.unsubscribe? &&
646 (!admin? || User.active.where("admin = ? AND id <> ?", true, id).exists?)
646 (!admin? || User.active.where("admin = ? AND id <> ?", true, id).exists?)
647 end
647 end
648
648
649 safe_attributes 'login',
649 safe_attributes 'login',
650 'firstname',
650 'firstname',
651 'lastname',
651 'lastname',
652 'mail',
652 'mail',
653 'mail_notification',
653 'mail_notification',
654 'notified_project_ids',
654 'notified_project_ids',
655 'language',
655 'language',
656 'custom_field_values',
656 'custom_field_values',
657 'custom_fields',
657 'custom_fields',
658 'identity_url'
658 'identity_url'
659
659
660 safe_attributes 'status',
660 safe_attributes 'status',
661 'auth_source_id',
661 'auth_source_id',
662 'generate_password',
662 'generate_password',
663 'must_change_passwd',
663 'must_change_passwd',
664 :if => lambda {|user, current_user| current_user.admin?}
664 :if => lambda {|user, current_user| current_user.admin?}
665
665
666 safe_attributes 'group_ids',
666 safe_attributes 'group_ids',
667 :if => lambda {|user, current_user| current_user.admin? && !user.new_record?}
667 :if => lambda {|user, current_user| current_user.admin? && !user.new_record?}
668
668
669 # Utility method to help check if a user should be notified about an
669 # Utility method to help check if a user should be notified about an
670 # event.
670 # event.
671 #
671 #
672 # TODO: only supports Issue events currently
672 # TODO: only supports Issue events currently
673 def notify_about?(object)
673 def notify_about?(object)
674 if mail_notification == 'all'
674 if mail_notification == 'all'
675 true
675 true
676 elsif mail_notification.blank? || mail_notification == 'none'
676 elsif mail_notification.blank? || mail_notification == 'none'
677 false
677 false
678 else
678 else
679 case object
679 case object
680 when Issue
680 when Issue
681 case mail_notification
681 case mail_notification
682 when 'selected', 'only_my_events'
682 when 'selected', 'only_my_events'
683 # user receives notifications for created/assigned issues on unselected projects
683 # user receives notifications for created/assigned issues on unselected projects
684 object.author == self || is_or_belongs_to?(object.assigned_to) || is_or_belongs_to?(object.assigned_to_was)
684 object.author == self || is_or_belongs_to?(object.assigned_to) || is_or_belongs_to?(object.assigned_to_was)
685 when 'only_assigned'
685 when 'only_assigned'
686 is_or_belongs_to?(object.assigned_to) || is_or_belongs_to?(object.assigned_to_was)
686 is_or_belongs_to?(object.assigned_to) || is_or_belongs_to?(object.assigned_to_was)
687 when 'only_owner'
687 when 'only_owner'
688 object.author == self
688 object.author == self
689 end
689 end
690 when News
690 when News
691 # always send to project members except when mail_notification is set to 'none'
691 # always send to project members except when mail_notification is set to 'none'
692 true
692 true
693 end
693 end
694 end
694 end
695 end
695 end
696
696
697 def self.current=(user)
697 def self.current=(user)
698 RequestStore.store[:current_user] = user
698 RequestStore.store[:current_user] = user
699 end
699 end
700
700
701 def self.current
701 def self.current
702 RequestStore.store[:current_user] ||= User.anonymous
702 RequestStore.store[:current_user] ||= User.anonymous
703 end
703 end
704
704
705 # Returns the anonymous user. If the anonymous user does not exist, it is created. There can be only
705 # Returns the anonymous user. If the anonymous user does not exist, it is created. There can be only
706 # one anonymous user per database.
706 # one anonymous user per database.
707 def self.anonymous
707 def self.anonymous
708 anonymous_user = AnonymousUser.first
708 anonymous_user = AnonymousUser.first
709 if anonymous_user.nil?
709 if anonymous_user.nil?
710 anonymous_user = AnonymousUser.create(:lastname => 'Anonymous', :firstname => '', :login => '', :status => 0)
710 anonymous_user = AnonymousUser.create(:lastname => 'Anonymous', :firstname => '', :login => '', :status => 0)
711 raise 'Unable to create the anonymous user.' if anonymous_user.new_record?
711 raise 'Unable to create the anonymous user.' if anonymous_user.new_record?
712 end
712 end
713 anonymous_user
713 anonymous_user
714 end
714 end
715
715
716 # Salts all existing unsalted passwords
716 # Salts all existing unsalted passwords
717 # It changes password storage scheme from SHA1(password) to SHA1(salt + SHA1(password))
717 # It changes password storage scheme from SHA1(password) to SHA1(salt + SHA1(password))
718 # This method is used in the SaltPasswords migration and is to be kept as is
718 # This method is used in the SaltPasswords migration and is to be kept as is
719 def self.salt_unsalted_passwords!
719 def self.salt_unsalted_passwords!
720 transaction do
720 transaction do
721 User.where("salt IS NULL OR salt = ''").find_each do |user|
721 User.where("salt IS NULL OR salt = ''").find_each do |user|
722 next if user.hashed_password.blank?
722 next if user.hashed_password.blank?
723 salt = User.generate_salt
723 salt = User.generate_salt
724 hashed_password = User.hash_password("#{salt}#{user.hashed_password}")
724 hashed_password = User.hash_password("#{salt}#{user.hashed_password}")
725 User.where(:id => user.id).update_all(:salt => salt, :hashed_password => hashed_password)
725 User.where(:id => user.id).update_all(:salt => salt, :hashed_password => hashed_password)
726 end
726 end
727 end
727 end
728 end
728 end
729
729
730 protected
730 protected
731
731
732 def validate_password_length
732 def validate_password_length
733 return if password.blank? && generate_password?
733 return if password.blank? && generate_password?
734 # Password length validation based on setting
734 # Password length validation based on setting
735 if !password.nil? && password.size < Setting.password_min_length.to_i
735 if !password.nil? && password.size < Setting.password_min_length.to_i
736 errors.add(:password, :too_short, :count => Setting.password_min_length.to_i)
736 errors.add(:password, :too_short, :count => Setting.password_min_length.to_i)
737 end
737 end
738 end
738 end
739
739
740 def instantiate_email_address
740 def instantiate_email_address
741 email_address || build_email_address
741 email_address || build_email_address
742 end
742 end
743
743
744 private
744 private
745
745
746 def generate_password_if_needed
746 def generate_password_if_needed
747 if generate_password? && auth_source.nil?
747 if generate_password? && auth_source.nil?
748 length = [Setting.password_min_length.to_i + 2, 10].max
748 length = [Setting.password_min_length.to_i + 2, 10].max
749 random_password(length)
749 random_password(length)
750 end
750 end
751 end
751 end
752
752
753 # Delete all outstanding password reset tokens on password change.
753 # Delete all outstanding password reset tokens on password change.
754 # Delete the autologin tokens on password change to prohibit session leakage.
754 # Delete the autologin tokens on password change to prohibit session leakage.
755 # This helps to keep the account secure in case the associated email account
755 # This helps to keep the account secure in case the associated email account
756 # was compromised.
756 # was compromised.
757 def destroy_tokens
757 def destroy_tokens
758 if hashed_password_changed?
758 if hashed_password_changed?
759 tokens = ['recovery', 'autologin']
759 tokens = ['recovery', 'autologin']
760 Token.where(:user_id => id, :action => tokens).delete_all
760 Token.where(:user_id => id, :action => tokens).delete_all
761 end
761 end
762 end
762 end
763
763
764 # Removes references that are not handled by associations
764 # Removes references that are not handled by associations
765 # Things that are not deleted are reassociated with the anonymous user
765 # Things that are not deleted are reassociated with the anonymous user
766 def remove_references_before_destroy
766 def remove_references_before_destroy
767 return if self.id.nil?
767 return if self.id.nil?
768
768
769 substitute = User.anonymous
769 substitute = User.anonymous
770 Attachment.where(['author_id = ?', id]).update_all(['author_id = ?', substitute.id])
770 Attachment.where(['author_id = ?', id]).update_all(['author_id = ?', substitute.id])
771 Comment.where(['author_id = ?', id]).update_all(['author_id = ?', substitute.id])
771 Comment.where(['author_id = ?', id]).update_all(['author_id = ?', substitute.id])
772 Issue.where(['author_id = ?', id]).update_all(['author_id = ?', substitute.id])
772 Issue.where(['author_id = ?', id]).update_all(['author_id = ?', substitute.id])
773 Issue.where(['assigned_to_id = ?', id]).update_all('assigned_to_id = NULL')
773 Issue.where(['assigned_to_id = ?', id]).update_all('assigned_to_id = NULL')
774 Journal.where(['user_id = ?', id]).update_all(['user_id = ?', substitute.id])
774 Journal.where(['user_id = ?', id]).update_all(['user_id = ?', substitute.id])
775 JournalDetail.
775 JournalDetail.
776 where(["property = 'attr' AND prop_key = 'assigned_to_id' AND old_value = ?", id.to_s]).
776 where(["property = 'attr' AND prop_key = 'assigned_to_id' AND old_value = ?", id.to_s]).
777 update_all(['old_value = ?', substitute.id.to_s])
777 update_all(['old_value = ?', substitute.id.to_s])
778 JournalDetail.
778 JournalDetail.
779 where(["property = 'attr' AND prop_key = 'assigned_to_id' AND value = ?", id.to_s]).
779 where(["property = 'attr' AND prop_key = 'assigned_to_id' AND value = ?", id.to_s]).
780 update_all(['value = ?', substitute.id.to_s])
780 update_all(['value = ?', substitute.id.to_s])
781 Message.where(['author_id = ?', id]).update_all(['author_id = ?', substitute.id])
781 Message.where(['author_id = ?', id]).update_all(['author_id = ?', substitute.id])
782 News.where(['author_id = ?', id]).update_all(['author_id = ?', substitute.id])
782 News.where(['author_id = ?', id]).update_all(['author_id = ?', substitute.id])
783 # Remove private queries and keep public ones
783 # Remove private queries and keep public ones
784 ::Query.delete_all ['user_id = ? AND visibility = ?', id, ::Query::VISIBILITY_PRIVATE]
784 ::Query.delete_all ['user_id = ? AND visibility = ?', id, ::Query::VISIBILITY_PRIVATE]
785 ::Query.where(['user_id = ?', id]).update_all(['user_id = ?', substitute.id])
785 ::Query.where(['user_id = ?', id]).update_all(['user_id = ?', substitute.id])
786 TimeEntry.where(['user_id = ?', id]).update_all(['user_id = ?', substitute.id])
786 TimeEntry.where(['user_id = ?', id]).update_all(['user_id = ?', substitute.id])
787 Token.delete_all ['user_id = ?', id]
787 Token.delete_all ['user_id = ?', id]
788 Watcher.delete_all ['user_id = ?', id]
788 Watcher.delete_all ['user_id = ?', id]
789 WikiContent.where(['author_id = ?', id]).update_all(['author_id = ?', substitute.id])
789 WikiContent.where(['author_id = ?', id]).update_all(['author_id = ?', substitute.id])
790 WikiContent::Version.where(['author_id = ?', id]).update_all(['author_id = ?', substitute.id])
790 WikiContent::Version.where(['author_id = ?', id]).update_all(['author_id = ?', substitute.id])
791 end
791 end
792
792
793 # Return password digest
793 # Return password digest
794 def self.hash_password(clear_password)
794 def self.hash_password(clear_password)
795 Digest::SHA1.hexdigest(clear_password || "")
795 Digest::SHA1.hexdigest(clear_password || "")
796 end
796 end
797
797
798 # Returns a 128bits random salt as a hex string (32 chars long)
798 # Returns a 128bits random salt as a hex string (32 chars long)
799 def self.generate_salt
799 def self.generate_salt
800 Redmine::Utils.random_hex(16)
800 Redmine::Utils.random_hex(16)
801 end
801 end
802
802
803 end
803 end
804
804
805 class AnonymousUser < User
805 class AnonymousUser < User
806 validate :validate_anonymous_uniqueness, :on => :create
806 validate :validate_anonymous_uniqueness, :on => :create
807
807
808 def validate_anonymous_uniqueness
808 def validate_anonymous_uniqueness
809 # There should be only one AnonymousUser in the database
809 # There should be only one AnonymousUser in the database
810 errors.add :base, 'An anonymous user already exists.' if AnonymousUser.exists?
810 errors.add :base, 'An anonymous user already exists.' if AnonymousUser.exists?
811 end
811 end
812
812
813 def available_custom_fields
813 def available_custom_fields
814 []
814 []
815 end
815 end
816
816
817 # Overrides a few properties
817 # Overrides a few properties
818 def logged?; false end
818 def logged?; false end
819 def admin; false end
819 def admin; false end
820 def name(*args); I18n.t(:label_user_anonymous) end
820 def name(*args); I18n.t(:label_user_anonymous) end
821 def mail=(*args); nil end
821 def mail=(*args); nil end
822 def mail; nil end
822 def mail; nil end
823 def time_zone; nil end
823 def time_zone; nil end
824 def rss_key; nil end
824 def rss_key; nil end
825
825
826 def pref
826 def pref
827 UserPreference.new(:user => self)
827 UserPreference.new(:user => self)
828 end
828 end
829
829
830 # Returns the user's bult-in role
830 # Returns the user's bult-in role
831 def builtin_role
831 def builtin_role
832 @builtin_role ||= Role.anonymous
832 @builtin_role ||= Role.anonymous
833 end
833 end
834
834
835 def membership(*args)
835 def membership(*args)
836 nil
836 nil
837 end
837 end
838
838
839 def member_of?(*args)
839 def member_of?(*args)
840 false
840 false
841 end
841 end
842
842
843 # Anonymous user can not be destroyed
843 # Anonymous user can not be destroyed
844 def destroy
844 def destroy
845 false
845 false
846 end
846 end
847
847
848 protected
848 protected
849
849
850 def instantiate_email_address
850 def instantiate_email_address
851 end
851 end
852 end
852 end
General Comments 0
You need to be logged in to leave comments. Login now