Class: User

Inherits:
ActiveRecord::Base
  • Object
show all
Extended by:
Gitlab::ConfigHelper
Includes:
CaseSensitivity, Gitlab::ConfigHelper, Gitlab::CurrentSettings, Referable, Sortable, TokenAuthenticatable
Defined in:
app/models/user.rb

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Gitlab::ConfigHelper

gitlab_config, gitlab_config_features

Methods included from Referable

#reference_link_text

Methods included from Gitlab::CurrentSettings

#current_application_settings, #fake_application_settings

Instance Attribute Details

#force_random_passwordObject

Returns the value of attribute force_random_password


98
99
100
# File 'app/models/user.rb', line 98

def force_random_password
  @force_random_password
end

#loginObject

Virtual attribute for authenticating by either username or email


101
102
103
# File 'app/models/user.rb', line 101

def 
  @login
end

Class Method Details

.build_user(attrs = {}) ⇒ Object


331
332
333
# File 'app/models/user.rb', line 331

def build_user(attrs = {})
  User.new(attrs)
end

.by_login(login) ⇒ Object


313
314
315
316
317
318
319
320
321
# File 'app/models/user.rb', line 313

def ()
  return nil unless 

  if .include?('@'.freeze)
    unscoped.iwhere(email: ).take
  else
    unscoped.iwhere(username: ).take
  end
end

.by_username_or_id(name_or_id) ⇒ Object


327
328
329
# File 'app/models/user.rb', line 327

def by_username_or_id(name_or_id)
  find_by('users.username = ? OR users.id = ?', name_or_id.to_s, name_or_id.to_i)
end

.filter(filter_name) ⇒ Object


276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
# File 'app/models/user.rb', line 276

def filter(filter_name)
  case filter_name
  when 'admins'
    self.admins
  when 'blocked'
    self.blocked
  when 'two_factor_disabled'
    self.without_two_factor
  when 'two_factor_enabled'
    self.with_two_factor
  when 'wop'
    self.without_projects
  when 'external'
    self.external
  else
    self.active
  end
end

.find_by_any_email(email) ⇒ Object

Find a User by their primary email or any associated secondary email


263
264
265
266
267
268
269
270
271
272
273
274
# File 'app/models/user.rb', line 263

def find_by_any_email(email)
  sql = 'SELECT *
  FROM users
  WHERE id IN (
    SELECT id FROM users WHERE email = :email
    UNION
    SELECT emails.user_id FROM emails WHERE email = :email
  )
  LIMIT 1;'

  User.find_by_sql([sql, { email: email }]).first
end

.find_by_username!(username) ⇒ Object


323
324
325
# File 'app/models/user.rb', line 323

def find_by_username!(username)
  find_by!('lower(username) = ?', username.downcase)
end

.find_for_database_authentication(warden_conditions) ⇒ Object

Devise method overridden to allow sign in with email or username


244
245
246
247
248
249
250
251
# File 'app/models/user.rb', line 244

def find_for_database_authentication(warden_conditions)
  conditions = warden_conditions.dup
  if  = conditions.delete(:login)
    where(conditions).find_by("lower(username) = :value OR lower(email) = :value", value: .downcase)
  else
    find_by(conditions)
  end
end

.reference_patternObject

Pattern used to extract `@user` user references from text


340
341
342
343
344
345
# File 'app/models/user.rb', line 340

def reference_pattern
  %r{
    #{Regexp.escape(reference_prefix)}
    (?<user>#{Gitlab::Regex::NAMESPACE_REGEX_STR})
  }x
end

.reference_prefixObject


335
336
337
# File 'app/models/user.rb', line 335

def reference_prefix
  '@'
end

.search(query) ⇒ Object

Searches users matching the given query.

This method uses ILIKE on PostgreSQL and LIKE on MySQL.

query - The search query as a String

Returns an ActiveRecord::Relation.


302
303
304
305
306
307
308
309
310
311
# File 'app/models/user.rb', line 302

def search(query)
  table   = arel_table
  pattern = "%#{query}%"

  where(
    table[:name].matches(pattern).
      or(table[:email].matches(pattern)).
      or(table[:username].matches(pattern))
  )
end

.sort(method) ⇒ Object


253
254
255
256
257
258
259
260
# File 'app/models/user.rb', line 253

def sort(method)
  case method.to_s
  when 'recent_sign_in' then reorder(last_sign_in_at: :desc)
  when 'oldest_sign_in' then reorder(last_sign_in_at: :asc)
  else
    order_by(method)
  end
end

Instance Method Details

#abilitiesObject


484
485
486
# File 'app/models/user.rb', line 484

def abilities
  Ability.abilities
end

#accessible_deploy_keysObject


579
580
581
582
583
584
585
# File 'app/models/user.rb', line 579

def accessible_deploy_keys
  @accessible_deploy_keys ||= begin
    key_ids = project_deploy_keys.pluck(:id)
    key_ids.push(*DeployKey.are_public.pluck(:id))
    DeployKey.where(id: key_ids)
  end
end

#all_emailsObject


699
700
701
702
703
704
# File 'app/models/user.rb', line 699

def all_emails
  all_emails = []
  all_emails << self.email unless self.temp_oauth_email?
  all_emails.concat(self.emails.map(&:email))
  all_emails
end

#all_ssh_keysObject


683
684
685
# File 'app/models/user.rb', line 683

def all_ssh_keys
  keys.map(&:publishable_key)
end

#already_forked?(project) ⇒ Boolean

Returns:

  • (Boolean)

553
554
555
# File 'app/models/user.rb', line 553

def already_forked?(project)
  !!fork_of(project)
end

#authorized_groupsObject

Returns the groups a user has access to


437
438
439
440
441
442
# File 'app/models/user.rb', line 437

def authorized_groups
  union = Gitlab::SQL::Union.
    new([groups.select(:id), authorized_projects.select(:namespace_id)])

  Group.where("namespaces.id IN (#{union.to_sql})")
end

#authorized_projectsObject

Returns projects user is authorized to access.


445
446
447
# File 'app/models/user.rb', line 445

def authorized_projects
  Project.where("projects.id IN (#{projects_union.to_sql})")
end

#avatar_typeObject


402
403
404
405
406
# File 'app/models/user.rb', line 402

def avatar_type
  unless self.avatar.image?
    self.errors.add :avatar, "only images allowed"
  end
end

#avatar_url(size = nil, scale = 2) ⇒ Object


691
692
693
694
695
696
697
# File 'app/models/user.rb', line 691

def avatar_url(size = nil, scale = 2)
  if avatar.present?
    [gitlab_config.url, avatar.url].join
  else
    GravatarService.new.execute(email, size, scale)
  end
end

#can?(action, subject) ⇒ Boolean

Returns:

  • (Boolean)

492
493
494
# File 'app/models/user.rb', line 492

def can?(action, subject)
  abilities.allowed?(self, action, subject)
end

#can_be_removed?Boolean

Returns:

  • (Boolean)

821
822
823
# File 'app/models/user.rb', line 821

def can_be_removed?
  !solo_owned_groups.present?
end

#can_change_username?Boolean

Returns:

  • (Boolean)

472
473
474
# File 'app/models/user.rb', line 472

def can_change_username?
  gitlab_config.username_changing_enabled
end

#can_create_group?Boolean

Returns:

  • (Boolean)

480
481
482
# File 'app/models/user.rb', line 480

def can_create_group?
  can?(:create_group, nil)
end

#can_create_project?Boolean

Returns:

  • (Boolean)

476
477
478
# File 'app/models/user.rb', line 476

def can_create_project?
  projects_limit_left > 0
end

#can_leave_project?(project) ⇒ Boolean

Returns:

  • (Boolean)

654
655
656
657
# File 'app/models/user.rb', line 654

def can_leave_project?(project)
  project.namespace != namespace &&
    project.project_member(self)
end

#can_select_namespace?Boolean

Returns:

  • (Boolean)

488
489
490
# File 'app/models/user.rb', line 488

def can_select_namespace?
  several_namespaces? || admin
end

#cared_merge_requestsObject


500
501
502
# File 'app/models/user.rb', line 500

def cared_merge_requests
  MergeRequest.cared(self)
end

#ci_authorized_runnersObject


825
826
827
828
829
830
831
832
# File 'app/models/user.rb', line 825

def ci_authorized_runners
  @ci_authorized_runners ||= begin
    runner_ids = Ci::RunnerProject.
      where("ci_runner_projects.gl_project_id IN (#{ci_projects_union.to_sql})").
      select(:runner_id)
    Ci::Runner.specific.where(id: runner_ids)
  end
end

#contributed_projectsObject

Returns the projects a user contributed to in the last year.

This method relies on a subquery as this performs significantly better compared to a JOIN when coupled with, for example, `Project.visible_to_user`. That is, consider the following code:

some_user.contributed_projects.visible_to_user(other_user)

If this method were to use a JOIN the resulting query would take roughly 200 ms on a database with a similar size to GitLab.com's database. On the other hand, using a subquery means we can get the exact same data in about 40 ms.


788
789
790
791
792
793
794
795
796
# File 'app/models/user.rb', line 788

def contributed_projects
  events = Event.select(:project_id).
    contributions.where(author_id: self).
    where("created_at > ?", Time.now - 1.year).
    uniq.
    reorder(nil)

  Project.where(id: events)
end

#created_byObject


587
588
589
# File 'app/models/user.rb', line 587

def created_by
  User.find_by(id: created_by_id) if created_by_id
end

#disable_two_factor!Object


379
380
381
382
383
384
385
386
387
388
# File 'app/models/user.rb', line 379

def disable_two_factor!
  update_attributes(
    two_factor_enabled:          false,
    encrypted_otp_secret:        nil,
    encrypted_otp_secret_iv:     nil,
    encrypted_otp_secret_salt:   nil,
    otp_grace_period_started_at: nil,
    otp_backup_codes:            nil
  )
end

#ensure_namespace_correctObject


714
715
716
717
718
719
720
721
# File 'app/models/user.rb', line 714

def ensure_namespace_correct
  # Ensure user has namespace
  self.create_namespace!(path: self.username, name: self.username) unless self.namespace

  if self.username_changed?
    self.namespace.update_attributes(path: self.username, name: self.username)
  end
end

#first_nameObject


496
497
498
# File 'app/models/user.rb', line 496

def first_name
  name.split.first unless name.blank?
end

#fork_of(project) ⇒ Object


557
558
559
560
561
562
563
564
565
# File 'app/models/user.rb', line 557

def fork_of(project)
  links = ForkedProjectLink.where(forked_from_project_id: project, forked_to_project_id: personal_projects)

  if links.any?
    links.first.forked_to_project
  else
    nil
  end
end

#full_website_urlObject


673
674
675
676
677
# File 'app/models/user.rb', line 673

def full_website_url
  return "http://#{website_url}" if website_url !~ /\Ahttps?:\/\//

  website_url
end

#generate_passwordObject


360
361
362
363
364
# File 'app/models/user.rb', line 360

def generate_password
  if self.force_random_password
    self.password = self.password_confirmation = Devise.friendly_token.first(8)
  end
end

#generate_reset_tokenObject


366
367
368
369
370
371
372
373
# File 'app/models/user.rb', line 366

def generate_reset_token
  @reset_token, enc = Devise.token_generator.generate(self.class, :reset_password_token)

  self.reset_password_token   = enc
  self.reset_password_sent_at = Time.now.utc

  @reset_token
end

#hook_attrsObject


706
707
708
709
710
711
712
# File 'app/models/user.rb', line 706

def hook_attrs
  {
    name: name,
    username: username,
    avatar_url: avatar_url
  }
end

#is_admin?Boolean

Returns:

  • (Boolean)

460
461
462
# File 'app/models/user.rb', line 460

def is_admin?
  admin
end

#ldap_identityObject


571
572
573
# File 'app/models/user.rb', line 571

def ldap_identity
  @ldap_identity ||= identities.find_by(["provider LIKE ?", "ldap%"])
end

#ldap_user?Boolean

Returns:

  • (Boolean)

567
568
569
# File 'app/models/user.rb', line 567

def ldap_user?
  identities.exists?(["provider LIKE ? AND extern_uid IS NOT NULL", "ldap%"])
end

#log_info(message) ⇒ Object


738
739
740
# File 'app/models/user.rb', line 738

def log_info(message)
  Gitlab::AppLogger.info message
end

#manageable_namespacesObject


763
764
765
# File 'app/models/user.rb', line 763

def manageable_namespaces
  @manageable_namespaces ||= [namespace] + owned_groups + masters_groups
end

#name_with_usernameObject


545
546
547
# File 'app/models/user.rb', line 545

def name_with_username
  "#{name} (#{username})"
end

#namespace_idObject


541
542
543
# File 'app/models/user.rb', line 541

def namespace_id
  namespace.try :id
end

#namespace_uniqObject


390
391
392
393
394
395
396
397
398
399
400
# File 'app/models/user.rb', line 390

def namespace_uniq
  # Return early if username already failed the first uniqueness validation
  return if self.errors.key?(:username) &&
    self.errors[:username].include?('has already been taken')

  namespace_name = self.username
  existing_namespace = Namespace.by_path(namespace_name)
  if existing_namespace && existing_namespace != self.namespace
    self.errors.add(:username, 'has already been taken')
  end
end

#namespacesObject


767
768
769
770
771
# File 'app/models/user.rb', line 767

def namespaces
  namespace_ids = groups.pluck(:id)
  namespace_ids.push(namespace.id)
  Namespace.where(id: namespace_ids)
end

#notification_serviceObject


734
735
736
# File 'app/models/user.rb', line 734

def notification_service
  NotificationService.new
end

#notification_settings_for(source) ⇒ Object


834
835
836
# File 'app/models/user.rb', line 834

def notification_settings_for(source)
  notification_settings.find_or_initialize_by(source: source)
end

#oauth_authorized_tokensObject


773
774
775
# File 'app/models/user.rb', line 773

def oauth_authorized_tokens
  Doorkeeper::AccessToken.where(resource_owner_id: self.id, revoked_at: nil)
end

#owned_projectsObject


449
450
451
452
453
# File 'app/models/user.rb', line 449

def owned_projects
  @owned_projects ||=
    Project.where('namespace_id IN (?) OR namespace_id = ?',
                  owned_groups.select(:id), namespace.id).joins(:namespace)
end

#owns_notification_emailObject


414
415
416
417
418
# File 'app/models/user.rb', line 414

def owns_notification_email
  return if self.temp_oauth_email?

  self.errors.add(:notification_email, "is not an email you own") unless self.all_emails.include?(self.notification_email)
end

#owns_public_emailObject


420
421
422
423
424
# File 'app/models/user.rb', line 420

def owns_public_email
  return if self.public_email.blank?

  self.errors.add(:public_email, "is not an email you own") unless self.all_emails.include?(self.public_email)
end

#post_create_hookObject


723
724
725
726
727
# File 'app/models/user.rb', line 723

def post_create_hook
  log_info("User \"#{self.name}\" (#{self.email}) was created")
  notification_service.new_user(self, @reset_token) if self.created_by_id
  system_hook_service.execute_hooks_for(self, :create)
end

#post_destroy_hookObject


729
730
731
732
# File 'app/models/user.rb', line 729

def post_destroy_hook
  log_info("User \"#{self.name}\" (#{self.email})  was removed")
  system_hook_service.execute_hooks_for(self, :destroy)
end

#project_deploy_keysObject


575
576
577
# File 'app/models/user.rb', line 575

def project_deploy_keys
  DeployKey.unscoped.in_projects(self.authorized_projects.pluck(:id)).distinct(:id)
end

#projects_limit_leftObject


504
505
506
# File 'app/models/user.rb', line 504

def projects_limit_left
  projects_limit - personal_projects.count
end

#projects_limit_percentObject


508
509
510
511
# File 'app/models/user.rb', line 508

def projects_limit_percent
  return 100 if projects_limit.zero?
  (personal_projects.count.to_f / projects_limit) * 100
end

#projects_sorted_by_activityObject


533
534
535
# File 'app/models/user.rb', line 533

def projects_sorted_by_activity
  authorized_projects.sorted_by_activity
end

#recent_push(project_id = nil) ⇒ Object


513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
# File 'app/models/user.rb', line 513

def recent_push(project_id = nil)
  # Get push events not earlier than 2 hours ago
  events = recent_events.code_push.where("created_at > ?", Time.now - 2.hours)
  events = events.where(project_id: project_id) if project_id

  # Use the latest event that has not been pushed or merged recently
  events.recent.find do |event|
    project = Project.find_by_id(event.project_id)
    next unless project
    repo = project.repository

    if repo.branch_names.include?(event.branch_name)
      merge_requests = MergeRequest.where("created_at >= ?", event.created_at).
          where(source_project_id: project.id,
                source_branch: event.branch_name)
      merge_requests.empty?
    end
  end
end

#recently_sent_password_reset?Boolean

Returns:

  • (Boolean)

375
376
377
# File 'app/models/user.rb', line 375

def recently_sent_password_reset?
  reset_password_sent_at.present? && reset_password_sent_at >= 1.minute.ago
end

#require_password?Boolean

Returns:

  • (Boolean)

468
469
470
# File 'app/models/user.rb', line 468

def require_password?
  password_automatically_set? && !ldap_user?
end

#require_ssh_key?Boolean

Returns:

  • (Boolean)

464
465
466
# File 'app/models/user.rb', line 464

def require_ssh_key?
  keys.count == 0
end

#requires_ldap_check?Boolean

Returns:

  • (Boolean)

623
624
625
626
627
628
629
630
631
# File 'app/models/user.rb', line 623

def requires_ldap_check?
  if !Gitlab.config.ldap.enabled
    false
  elsif ldap_user?
    !last_credential_check_at || (last_credential_check_at + 1.hour) < Time.now
  else
    false
  end
end

#reset_events_cacheObject

Reset project events cache related to this user

Since we do cache @event we need to reset cache in special cases:

  • when the user changes their avatar

Events cache stored like events/23-20130109142513. The cache key includes updated_at timestamp. Thus it will automatically generate a new fragment when the event is updated because the key changes.


667
668
669
670
671
# File 'app/models/user.rb', line 667

def reset_events_cache
  Event.where(author_id: self.id).
    order('id DESC').limit(1000).
    update_all(updated_at: Time.now)
end

#restricted_signup_domainsObject


798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
# File 'app/models/user.rb', line 798

def 
  email_domains = current_application_settings.

  unless email_domains.blank?
    match_found = email_domains.any? do |domain|
      escaped = Regexp.escape(domain).gsub('\*','.*?')
      regexp = Regexp.new "^#{escaped}$", Regexp::IGNORECASE
      email_domain = Mail::Address.new(self.email).domain
      email_domain =~ regexp
    end

    unless match_found
      self.errors.add :email,
                      'is not whitelisted. ' +
                      'Email domains valid for registration are: ' +
                      email_domains.join(', ')
      return false
    end
  end

  true
end

#sanitize_attrsObject


591
592
593
594
595
596
# File 'app/models/user.rb', line 591

def sanitize_attrs
  %w(name username skype linkedin twitter).each do |attr|
    value = self.send(attr)
    self.send("#{attr}=", Sanitize.clean(value)) if value.present?
  end
end

#set_notification_emailObject


598
599
600
601
602
# File 'app/models/user.rb', line 598

def set_notification_email
  if self.notification_email.blank? || !self.all_emails.include?(self.notification_email)
    self.notification_email = self.email
  end
end

#set_projects_limitObject


616
617
618
619
620
621
# File 'app/models/user.rb', line 616

def set_projects_limit
  connection_default_value_defined = new_record? && !projects_limit_changed?
  return unless self.projects_limit.nil? || connection_default_value_defined

  self.projects_limit = current_application_settings.default_projects_limit
end

#set_public_emailObject


604
605
606
607
608
# File 'app/models/user.rb', line 604

def set_public_email
  if self.public_email.blank? || !self.all_emails.include?(self.public_email)
    self.public_email = ''
  end
end

#several_namespaces?Boolean

Returns:

  • (Boolean)

537
538
539
# File 'app/models/user.rb', line 537

def several_namespaces?
  owned_groups.any? || masters_groups.any?
end

#short_website_urlObject


679
680
681
# File 'app/models/user.rb', line 679

def short_website_url
  website_url.sub(/\Ahttps?:\/\//, '')
end

#solo_owned_groupsObject


640
641
642
643
644
# File 'app/models/user.rb', line 640

def solo_owned_groups
  @solo_owned_groups ||= owned_groups.select do |group|
    group.owners == [self]
  end
end

#starred?(project) ⇒ Boolean

Returns:

  • (Boolean)

746
747
748
# File 'app/models/user.rb', line 746

def starred?(project)
  starred_projects.exists?(project.id)
end

#system_hook_serviceObject


742
743
744
# File 'app/models/user.rb', line 742

def system_hook_service
  SystemHooksService.new
end

#temp_oauth_email?Boolean

Returns:

  • (Boolean)

687
688
689
# File 'app/models/user.rb', line 687

def temp_oauth_email?
  email.start_with?('temp-email-for-oauth')
end

#tm_in_authorized_projectsObject

Team membership in authorized projects


456
457
458
# File 'app/models/user.rb', line 456

def tm_in_authorized_projects
  ProjectMember.where(source_id: authorized_projects.map(&:id), user_id: self.id)
end

#tm_of(project) ⇒ Object


549
550
551
# File 'app/models/user.rb', line 549

def tm_of(project)
  project.project_member_by_id(self.id)
end

#to_paramObject

Instance methods


352
353
354
# File 'app/models/user.rb', line 352

def to_param
  username
end

#to_reference(_from_project = nil) ⇒ Object


356
357
358
# File 'app/models/user.rb', line 356

def to_reference(_from_project = nil)
  "#{self.class.reference_prefix}#{username}"
end

#toggle_star(project) ⇒ Object


750
751
752
753
754
755
756
757
758
759
760
761
# File 'app/models/user.rb', line 750

def toggle_star(project)
  UsersStarProject.transaction do
    user_star_project = users_star_projects.
        where(project: project, user: self).lock(true).first

    if user_star_project
      user_star_project.destroy
    else
      UsersStarProject.create!(project: project, user: self)
    end
  end
end

#try_obtain_ldap_leaseObject


633
634
635
636
637
638
# File 'app/models/user.rb', line 633

def try_obtain_ldap_lease
  # After obtaining this lease LDAP checks will be blocked for 600 seconds
  # (10 minutes) for this user.
  lease = Gitlab::ExclusiveLease.new("user_ldap_check:#{id}", timeout: 600)
  lease.try_obtain
end

#unique_emailObject


408
409
410
411
412
# File 'app/models/user.rb', line 408

def unique_email
  if !self.emails.exists?(email: self.email) && Email.exists?(email: self.email)
    self.errors.add(:email, 'has already been taken')
  end
end

#update_emails_with_primary_emailObject


426
427
428
429
430
431
432
433
434
# File 'app/models/user.rb', line 426

def update_emails_with_primary_email
  primary_email_record = self.emails.find_by(email: self.email)
  if primary_email_record
    primary_email_record.destroy
    self.emails.create(email: self.email_was)

    self.update_secondary_emails!
  end
end

#update_secondary_emails!Object


610
611
612
613
614
# File 'app/models/user.rb', line 610

def update_secondary_emails!
  self.set_notification_email
  self.set_public_email
  self.save if self.notification_email_changed? || self.public_email_changed?
end

#with_defaultsObject


646
647
648
649
650
651
652
# File 'app/models/user.rb', line 646

def with_defaults
  User.defaults.each do |k, v|
    self.send("#{k}=", v)
  end

  self
end