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