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