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