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