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