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