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