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