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