Class: User

Constant Summary collapse

DEFAULT_NOTIFICATION_LEVEL =
:participating
BLOCKED_MESSAGE =
"Your account has been blocked. Please contact your GitLab " \
"administrator if you think this is an error."
LOGIN_FORBIDDEN =
"Your account does not have the required permission to login. Please contact your GitLab " \
"administrator if you think this is an error."
MINIMUM_INACTIVE_DAYS =
180
REQUIRES_ROLE_VALUE =
99

Constants included from HasUserType

HasUserType::BOT_USER_TYPES, HasUserType::INTERNAL_USER_TYPES, HasUserType::NON_INTERNAL_USER_TYPES, HasUserType::USER_TYPES

Constants included from UpdateHighestRole

UpdateHighestRole::HIGHEST_ROLE_JOB_DELAY, UpdateHighestRole::HIGHEST_ROLE_LEASE_TIMEOUT

Constants included from BatchDestroyDependentAssociations

BatchDestroyDependentAssociations::DEPENDENT_ASSOCIATIONS_BATCH_SIZE

Constants included from WithUploads

WithUploads::FILE_UPLOADERS

Constants included from BlocksJsonSerialization

BlocksJsonSerialization::JsonSerializationError

Constants included from Avatarable

Avatarable::ALLOWED_IMAGE_SCALER_WIDTHS

Constants included from Gitlab::SQL::Pattern

Gitlab::SQL::Pattern::MIN_CHARS_FOR_PARTIAL_MATCHING, Gitlab::SQL::Pattern::REGEX_QUOTED_WORD

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 AdminChangedPasswordNotifier

#initialize, #send_only_admin_changed_your_password_notification!

Methods included from HasUserType

#bot?, #internal?

Methods included from BatchDestroyDependentAssociations

#dependent_associations_to_destroy, #destroy_dependent_associations_in_batches

Methods included from WithUploads

#retrieve_upload

Methods included from FastDestroyAll::Helpers

#perform_fast_destroy

Methods included from AfterCommitQueue

#run_after_commit, #run_after_commit_or_now

Methods included from BlocksJsonSerialization

#to_json

Methods included from FeatureGate

#flipper_id

Methods included from Referable

#referable_inspect, #reference_link_text, #to_reference_base

Methods included from Avatarable

#avatar_path, #avatar_type, #uncached_avatar_path, #upload_paths

Methods inherited from ApplicationRecord

at_most, id_in, id_not_in, iid_in, pluck_primary_key, primary_key_in, safe_ensure_unique, safe_find_or_create_by, safe_find_or_create_by!, underscore, without_order

Instance Attribute Details

#force_random_passwordObject

rubocop: enable CodeReuse/ServiceClass


91
92
93
# File 'app/models/user.rb', line 91

def force_random_password
  @force_random_password
end

#impersonatorObject

Virtual attribute for impersonator


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

def impersonator
  @impersonator
end

#loginObject

Virtual attribute for authenticating by either username or email


94
95
96
# File 'app/models/user.rb', line 94

def 
  @login
end

Class Method Details

.alert_botObject


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

def alert_bot
  email_pattern = "alert%[email protected]#{Settings.gitlab.host}"

  unique_internal(where(user_type: :alert_bot), 'alert-bot', email_pattern) do |u|
    u.bio = 'The GitLab alert bot'
    u.name = 'GitLab Alert Bot'
    u.avatar = bot_avatar(image: 'alert-bot.png')
  end
end

.by_any_email(emails, confirmed: false) ⇒ Object

Returns a relation containing all the users for the given email addresses

Parameters:

  • emails (String, Array<String>)

    email addresses to check

  • confirmed (Boolean) (defaults to: false)

    Only return users where the email is confirmed


508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
# File 'app/models/user.rb', line 508

def by_any_email(emails, confirmed: false)
  emails = Array(emails).map(&:downcase)

  from_users = where(email: emails)
  from_users = from_users.confirmed if confirmed

  from_emails = joins(:emails).where(emails: { email: emails })
  from_emails = from_emails.confirmed.merge(Email.confirmed) if confirmed

  items = [from_users, from_emails]

  user_ids = Gitlab::PrivateCommitEmail.user_ids_for_emails(emails)
  items << where(id: user_ids) if user_ids.present?

  from_union(items)
end

.by_login(login) ⇒ Object


617
618
619
620
621
622
623
624
625
# File 'app/models/user.rb', line 617

def ()
  return unless 

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

.filter_items(filter_name) ⇒ Object


531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
# File 'app/models/user.rb', line 531

def filter_items(filter_name)
  case filter_name
  when 'admins'
    admins
  when 'blocked'
    blocked
  when 'two_factor_disabled'
    without_two_factor
  when 'two_factor_enabled'
    with_two_factor
  when 'wop'
    without_projects
  when 'external'
    external
  when 'deactivated'
    deactivated
  else
    active_without_ghosts
  end
end

.find_by_any_email(email, confirmed: false) ⇒ Object

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


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

def find_by_any_email(email, confirmed: false)
  return unless email

  by_any_email(email, confirmed: confirmed).take
end

.find_by_full_path(path, follow_redirects: false) ⇒ Object


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

def find_by_full_path(path, follow_redirects: false)
  namespace = Namespace.for_user.find_by_full_path(path, follow_redirects: follow_redirects)
  namespace&.owner
end

.find_by_private_commit_email(email) ⇒ Object


525
526
527
528
529
# File 'app/models/user.rb', line 525

def find_by_private_commit_email(email)
  user_id = Gitlab::PrivateCommitEmail.user_id_for_email(email)

  find_by(id: user_id)
end

.find_by_ssh_key_id(key_id) ⇒ Object

Returns a user for the given SSH key.


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

def find_by_ssh_key_id(key_id)
  find_by('EXISTS (?)', Key.select(1).where('keys.user_id = users.id').where(id: key_id))
end

.find_by_username(username) ⇒ Object


627
628
629
# File 'app/models/user.rb', line 627

def find_by_username(username)
  by_username(username).take
end

.find_by_username!(username) ⇒ Object


631
632
633
# File 'app/models/user.rb', line 631

def find_by_username!(username)
  by_username(username).take!
end

.find_for_database_authentication(warden_conditions) ⇒ Object

Devise method overridden to allow sign in with email or username


471
472
473
474
475
476
477
478
# File 'app/models/user.rb', line 471

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.strip)
  else
    find_by(conditions)
  end
end

.for_github_id(id) ⇒ Object


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

def for_github_id(id)
  joins(:identities).merge(Identity.with_extern_uid(:github, id))
end

.ghostObject

Return (create if necessary) the ghost user. The ghost user owns records previously belonging to deleted users.


661
662
663
664
665
666
667
# File 'app/models/user.rb', line 661

def ghost
  email = 'ghost%[email protected]'
  unique_internal(where(user_type: :ghost), 'ghost', email) do |u|
    u.bio = _('This is a "Ghost User", created to hold all issues authored by users that have since been deleted. This user cannot be removed.')
    u.name = 'Ghost User'
  end
end

.limit_to_todo_authors(user: nil, with_todos: false, todo_state: nil) ⇒ Object

Limits the users to those that have TODOs, optionally in the given state.

user - The user to get the todos for.

with_todos - If we should limit the result set to users that are the

authors of todos.

todo_state - An optional state to require the todos to be in.


411
412
413
414
415
416
417
# File 'app/models/user.rb', line 411

def self.limit_to_todo_authors(user: nil, with_todos: false, todo_state: nil)
  if user && with_todos
    where(id: Todo.where(user: user, state: todo_state).select(:author_id))
  else
    all
  end
end

.migration_botObject


679
680
681
682
683
684
685
686
687
# File 'app/models/user.rb', line 679

def migration_bot
  email_pattern = "noreply+gitlab-migration-bot%[email protected]#{Settings.gitlab.host}"

  unique_internal(where(user_type: :migration_bot), 'migration-bot', email_pattern) do |u|
    u.bio = 'The GitLab migration bot'
    u.name = 'GitLab Migration Bot'
    u.confirmed_at = Time.zone.now
  end
end

.password_lengthObject

Devise method overridden to allow support for dynamic password lengths


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

def password_length
  Gitlab::CurrentSettings.minimum_password_length..Devise.password_length.max
end

.random_passwordObject

Generate a random password that conforms to the current password length settings


466
467
468
# File 'app/models/user.rb', line 466

def random_password
  Devise.friendly_token(password_length.max)
end

.reference_patternObject

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


650
651
652
653
654
655
656
657
# File 'app/models/user.rb', line 650

def reference_pattern
  @reference_pattern ||=
    %r{
      (?<!\w)
      #{Regexp.escape(reference_prefix)}
      (?<user>#{Gitlab::PathRegex::FULL_NAMESPACE_FORMAT_REGEX})
    }x
end

.reference_prefixObject


645
646
647
# File 'app/models/user.rb', line 645

def reference_prefix
  '@'
end

.reorder_by_nameObject


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

def reorder_by_name
  reorder(:name)
end

.search(query, **options) ⇒ Object

Searches users matching the given query.

This method uses ILIKE on PostgreSQL.

query - The search query as a String

Returns an ActiveRecord::Relation.


559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
# File 'app/models/user.rb', line 559

def search(query, **options)
  query = query&.delete_prefix('@')
  return none if query.blank?

  query = query.downcase

  order = <<~SQL
    CASE
      WHEN users.name = :query THEN 0
      WHEN users.username = :query THEN 1
      WHEN users.email = :query THEN 2
      ELSE 3
    END
  SQL

  sanitized_order_sql = Arel.sql(sanitize_sql_array([order, query: query]))

  where(
    fuzzy_arel_match(:name, query, lower_exact_match: true)
      .or(fuzzy_arel_match(:username, query, lower_exact_match: true))
      .or(arel_table[:email].eq(query))
  ).reorder(sanitized_order_sql, :name)
end

.search_with_secondary_emails(query) ⇒ Object

searches user by given pattern it compares name, email, username fields and user's secondary emails with given pattern This method uses ILIKE on PostgreSQL.


599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
# File 'app/models/user.rb', line 599

def search_with_secondary_emails(query)
  return none if query.blank?

  query = query.downcase

  email_table = Email.arel_table
  matched_by_emails_user_ids = email_table
    .project(email_table[:user_id])
    .where(email_table[:email].eq(query))

  where(
    fuzzy_arel_match(:name, query)
      .or(fuzzy_arel_match(:username, query))
      .or(arel_table[:email].eq(query))
      .or(arel_table[:id].in(matched_by_emails_user_ids))
  )
end

.single_userObject


705
706
707
# File 'app/models/user.rb', line 705

def single_user
  User.non_internal.first if single_user?
end

.single_user?Boolean

Return true if there is only single non-internal user in the deployment, ghost user is ignored.

Returns:

  • (Boolean)

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

def single_user?
  User.non_internal.limit(2).count == 1
end

.sort_by_attribute(method) ⇒ Object


480
481
482
483
484
485
486
487
488
489
490
491
# File 'app/models/user.rb', line 480

def sort_by_attribute(method)
  order_method = method || 'id_desc'

  case order_method.to_s
  when 'recent_sign_in' then 
  when 'oldest_sign_in' then 
  when 'last_activity_on_desc' then order_recent_last_activity
  when 'last_activity_on_asc' then order_oldest_last_activity
  else
    order_by(order_method)
  end
end

.support_botObject


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

def support_bot
  email_pattern = "support%[email protected]#{Settings.gitlab.host}"

  unique_internal(where(user_type: :support_bot), 'support-bot', email_pattern) do |u|
    u.bio = 'The GitLab support bot used for Service Desk'
    u.name = 'GitLab Support Bot'
    u.avatar = bot_avatar(image: 'support-bot.png')
  end
end

.union_with_user(user_id = nil) ⇒ Object

Returns a relation that optionally includes the given user.

user_id - The ID of the user to include.


422
423
424
425
426
427
428
429
430
# File 'app/models/user.rb', line 422

def self.union_with_user(user_id = nil)
  if user_id.present?
    # We use "unscoped" here so that any inner conditions are not repeated for
    # the outer query, which would be redundant.
    User.unscoped.from_union([all, User.unscoped.where(id: user_id)])
  else
    all
  end
end

.where_not_in(users = nil) ⇒ Object

Limits the result set to users not in the given query/list of IDs.

users - The list of users to ignore. This can be an

`ActiveRecord::Relation`, or an Array.

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

def where_not_in(users = nil)
  users ? where.not(id: users) : all
end

.with_two_factorObject


432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
# File 'app/models/user.rb', line 432

def self.with_two_factor
  with_u2f_registrations = <<-SQL
    EXISTS (
      SELECT *
      FROM u2f_registrations AS u2f
      WHERE u2f.user_id = users.id
    ) OR users.otp_required_for_login = ?
    OR
    EXISTS (
      SELECT *
      FROM webauthn_registrations AS webauthn
      WHERE webauthn.user_id = users.id
    )
  SQL

  where(with_u2f_registrations, true)
end

.with_visible_profile(user) ⇒ Object


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

def self.with_visible_profile(user)
  return with_public_profile if user.nil?

  if user.admin?
    all
  else
    with_public_profile.or(where(id: user.id))
  end
end

.without_two_factorObject


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

def self.without_two_factor
  joins("LEFT OUTER JOIN u2f_registrations AS u2f ON u2f.user_id = users.id
         LEFT OUTER JOIN webauthn_registrations AS webauthn ON webauthn.user_id = users.id")
    .where("u2f.id IS NULL AND webauthn.id IS NULL AND users.otp_required_for_login = ?", false)
end

Instance Method Details

#accept_pending_invitations!Object


1252
1253
1254
1255
1256
# File 'app/models/user.rb', line 1252

def accept_pending_invitations!
  pending_invitations.select do |member|
    member.accept_invite!(self)
  end
end

#access_levelObject

rubocop: enable CodeReuse/ServiceClass


1585
1586
1587
1588
1589
1590
1591
# File 'app/models/user.rb', line 1585

def access_level
  if admin?
    :admin
  else
    :regular
  end
end

#access_level=(new_level) ⇒ Object


1593
1594
1595
1596
1597
1598
# File 'app/models/user.rb', line 1593

def access_level=(new_level)
  new_level = new_level.to_s
  return unless %w(admin regular).include?(new_level)

  self.admin = (new_level == 'admin')
end

#accessible_deploy_keysObject


1131
1132
1133
1134
1135
1136
# File 'app/models/user.rb', line 1131

def accessible_deploy_keys
  DeployKey.from_union([
    DeployKey.where(id: project_deploy_keys.select(:deploy_key_id)),
    DeployKey.are_public
  ])
end

#active_for_authentication?Boolean

Returns:

  • (Boolean)

379
380
381
# File 'app/models/user.rb', line 379

def active_for_authentication?
  super && can?(:log_in)
end

#all_emails(include_private_email: true) ⇒ Object


1262
1263
1264
1265
1266
1267
1268
# File 'app/models/user.rb', line 1262

def all_emails(include_private_email: true)
  all_emails = []
  all_emails << email unless temp_oauth_email?
  all_emails << private_commit_email if include_private_email
  all_emails.concat(emails.map(&:email))
  all_emails
end

#all_expanded_groupsObject

Returns a relation of groups the user has access to, including their parent and child groups (recursively).


905
906
907
# File 'app/models/user.rb', line 905

def all_expanded_groups
  Gitlab::ObjectHierarchy.new(groups).all_objects
end

#all_ssh_keysObject


1234
1235
1236
# File 'app/models/user.rb', line 1234

def all_ssh_keys
  keys.map(&:publishable_key)
end

#allow_password_authentication?Boolean

Returns:

  • (Boolean)

1018
1019
1020
# File 'app/models/user.rb', line 1018

def allow_password_authentication?
  allow_password_authentication_for_web? || allow_password_authentication_for_git?
end

#allow_password_authentication_for_git?Boolean

Returns:

  • (Boolean)

1026
1027
1028
# File 'app/models/user.rb', line 1026

def allow_password_authentication_for_git?
  Gitlab::CurrentSettings.password_authentication_enabled_for_git? && !ldap_user?
end

#allow_password_authentication_for_web?Boolean

Returns:

  • (Boolean)

1022
1023
1024
# File 'app/models/user.rb', line 1022

def allow_password_authentication_for_web?
  Gitlab::CurrentSettings.password_authentication_enabled_for_web? && !ldap_user?
end

#already_forked?(project) ⇒ Boolean

Returns:

  • (Boolean)

1099
1100
1101
# File 'app/models/user.rb', line 1099

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

#any_email?(check_email) ⇒ Boolean

Returns:

  • (Boolean)

1284
1285
1286
1287
1288
1289
1290
1291
1292
# File 'app/models/user.rb', line 1284

def any_email?(check_email)
  downcased = check_email.downcase

  # handle the outdated private commit email case
  return true if persisted? &&
      id == Gitlab::PrivateCommitEmail.user_id_for_email(downcased)

  all_emails.include?(check_email.downcase)
end

#assigned_open_issues_count(force: false) ⇒ Object


1508
1509
1510
1511
1512
# File 'app/models/user.rb', line 1508

def assigned_open_issues_count(force: false)
  Rails.cache.fetch(['users', id, 'assigned_open_issues_count'], force: force, expires_in: 20.minutes) do
    IssuesFinder.new(self, assignee_id: self.id, state: 'opened', non_archived: true).execute.count
  end
end

#assigned_open_merge_requests_count(force: false) ⇒ Object


1502
1503
1504
1505
1506
# File 'app/models/user.rb', line 1502

def assigned_open_merge_requests_count(force: false)
  Rails.cache.fetch(['users', id, 'assigned_open_merge_requests_count'], force: force, expires_in: 20.minutes) do
    MergeRequestsFinder.new(self, assignee_id: self.id, state: 'opened', non_archived: true).execute.count
  end
end

#authorizations_for_projects(min_access_level: nil, related_project_column: 'projects.id') ⇒ Object

Typically used in conjunction with projects table to get projects a user has been given access to. The param `related_project_column` is the column to compare to the project_authorizations. By default is projects.id

Example use: `Project.where('EXISTS(?)', user.authorizations_for_projects)`


953
954
955
956
957
958
959
960
961
# File 'app/models/user.rb', line 953

def authorizations_for_projects(min_access_level: nil, related_project_column: 'projects.id')
  authorizations = project_authorizations
                    .select(1)
                    .where("project_authorizations.project_id = #{related_project_column}")

  return authorizations unless min_access_level.present?

  authorizations.where('project_authorizations.access_level >= ?', min_access_level)
end

#authorized_groupsObject

Returns the groups a user has access to, either through a membership or a project authorization


889
890
891
892
893
894
895
896
# File 'app/models/user.rb', line 889

def authorized_groups
  Group.unscoped do
    Group.from_union([
      groups,
      authorized_projects.joins(:namespace).select('namespaces.*')
    ])
  end
end

#authorized_project?(project, min_access_level = nil) ⇒ Boolean

Returns:

  • (Boolean)

942
943
944
# File 'app/models/user.rb', line 942

def authorized_project?(project, min_access_level = nil)
  authorized_projects(min_access_level).exists?({ id: project.id })
end

#authorized_projects(min_access_level = nil) ⇒ Object


929
930
931
932
933
934
935
936
937
938
939
940
# File 'app/models/user.rb', line 929

def authorized_projects(min_access_level = nil)
  # We're overriding an association, so explicitly call super with no
  # arguments or it would be passed as `force_reload` to the association
  projects = super()

  if min_access_level
    projects = projects
      .where('project_authorizations.access_level >= ?', min_access_level)
  end

  projects
end

#avatar_url(size: nil, scale: 2, **args) ⇒ Object

rubocop: disable CodeReuse/ServiceClass


1243
1244
1245
# File 'app/models/user.rb', line 1243

def avatar_url(size: nil, scale: 2, **args)
  GravatarService.new.execute(email, size, scale, username: username)
end

#can?(action, subject = :global) ⇒ Boolean

Returns:

  • (Boolean)

1046
1047
1048
# File 'app/models/user.rb', line 1046

def can?(action, subject = :global)
  Ability.allowed?(self, action, subject)
end

#can_be_deactivated?Boolean

Returns:

  • (Boolean)

1707
1708
1709
# File 'app/models/user.rb', line 1707

def can_be_deactivated?
  active? && no_recent_activity?
end

#can_be_removed?Boolean

Returns:

  • (Boolean)

1437
1438
1439
# File 'app/models/user.rb', line 1437

def can_be_removed?
  !solo_owned_groups.present?
end

#can_change_username?Boolean

Returns:

  • (Boolean)

1030
1031
1032
# File 'app/models/user.rb', line 1030

def can_change_username?
  gitlab_config.username_changing_enabled
end

#can_create_group?Boolean

Returns:

  • (Boolean)

1038
1039
1040
# File 'app/models/user.rb', line 1038

def can_create_group?
  can?(:create_group)
end

#can_create_project?Boolean

Returns:

  • (Boolean)

1034
1035
1036
# File 'app/models/user.rb', line 1034

def can_create_project?
  projects_limit_left > 0
end

#can_leave_project?(project) ⇒ Boolean

Returns:

  • (Boolean)

1219
1220
1221
1222
# File 'app/models/user.rb', line 1219

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

#can_read_all_resources?Boolean

Returns:

  • (Boolean)

1600
1601
1602
# File 'app/models/user.rb', line 1600

def can_read_all_resources?
  can?(:read_all_resources)
end

#can_select_namespace?Boolean

Returns:

  • (Boolean)

1042
1043
1044
# File 'app/models/user.rb', line 1042

def can_select_namespace?
  several_namespaces? || admin
end

#check_for_verified_emailObject

see if the new email is already a verified secondary email


862
863
864
# File 'app/models/user.rb', line 862

def check_for_verified_email
  skip_reconfirmation! if emails.confirmed.where(email: self.email).any?
end

#ci_owned_runnersObject


1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
# File 'app/models/user.rb', line 1441

def ci_owned_runners
  @ci_owned_runners ||= begin
    project_runners = Ci::RunnerProject
      .where(project: authorized_projects(Gitlab::Access::MAINTAINER))
      .joins(:runner)
      .select('ci_runners.*')

    group_runners = Ci::RunnerNamespace
      .where(namespace_id: Gitlab::ObjectHierarchy.new(owned_groups).base_and_descendants.select(:id))
      .joins(:runner)
      .select('ci_runners.*')

    Ci::Runner.from_union([project_runners, group_runners])
  end
end

#commit_emailObject

Define commit_email-related attribute methods explicitly instead of relying on ActiveRecord to provide them. Some of the specs use the current state of the model code but an older database schema, so we need to guard against the possibility of the commit_email column not existing.


838
839
840
841
842
843
844
845
846
847
# File 'app/models/user.rb', line 838

def commit_email
  return self.email unless has_attribute?(:commit_email)

  if super == Gitlab::PrivateCommitEmail::TOKEN
    return private_commit_email
  end

  # The commit email is the same as the primary email if undefined
  super.presence || self.email
end

#commit_email=(email) ⇒ Object


849
850
851
# File 'app/models/user.rb', line 849

def commit_email=(email)
  super if has_attribute?(:commit_email)
end

#commit_email_changed?Boolean

Returns:

  • (Boolean)

853
854
855
# File 'app/models/user.rb', line 853

def commit_email_changed?
  has_attribute?(:commit_email) && super
end

#confirm_deletion_with_password?Boolean

Returns:

  • (Boolean)

1050
1051
1052
# File 'app/models/user.rb', line 1050

def confirm_deletion_with_password?
  !password_automatically_set? && allow_password_authentication?
end

#confirmation_required_on_sign_in?Boolean

Returns:

  • (Boolean)

1740
1741
1742
# File 'app/models/user.rb', line 1740

def confirmation_required_on_sign_in?
  !confirmed? && !confirmation_period_valid?
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.


1427
1428
1429
1430
1431
1432
1433
1434
1435
# File 'app/models/user.rb', line 1427

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

  Project.where(id: events)
end

#created_byObject


1138
1139
1140
# File 'app/models/user.rb', line 1138

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

#created_recently?Boolean

Returns:

  • (Boolean)

1748
1749
1750
# File 'app/models/user.rb', line 1748

def created_recently?
  created_at > Devise.confirm_within.ago
end

#current_highest_access_levelObject

Load the current highest access by looking directly at the user's memberships


1736
1737
1738
# File 'app/models/user.rb', line 1736

def current_highest_access_level
  members.non_request.maximum(:access_level)
end

#delete_async(deleted_by:, params: {}) ⇒ Object

rubocop: enable CodeReuse/ServiceClass


1344
1345
1346
1347
# File 'app/models/user.rb', line 1344

def delete_async(deleted_by:, params: {})
  block if params[:hard_delete]
  DeleteUserWorker.perform_async(deleted_by.id, id, params.to_h)
end

#disable_two_factor!Object


755
756
757
758
759
760
761
762
763
764
765
766
767
768
# File 'app/models/user.rb', line 755

def disable_two_factor!
  transaction do
    update(
      otp_required_for_login:      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
    )
    self.u2f_registrations.destroy_all # rubocop: disable Cop/DestroyAll
    self.webauthn_registrations.destroy_all # rubocop: disable Cop/DestroyAll
  end
end

#dismissed_callout?(feature_name:, ignore_dismissal_earlier_than: nil) ⇒ Boolean

Returns:

  • (Boolean)

1728
1729
1730
1731
1732
1733
# File 'app/models/user.rb', line 1728

def dismissed_callout?(feature_name:, ignore_dismissal_earlier_than: nil)
  callouts = self.callouts.with_feature_name(feature_name)
  callouts = callouts.with_dismissed_after(ignore_dismissal_earlier_than) if ignore_dismissal_earlier_than

  callouts.any?
end

#ensure_namespace_correctObject


1313
1314
1315
1316
1317
1318
1319
1320
1321
# File 'app/models/user.rb', line 1313

def ensure_namespace_correct
  if namespace
    namespace.path = username if username_changed?
    namespace.name = name if name_changed?
  else
    namespace = build_namespace(path: username, name: name)
    namespace.build_namespace_settings
  end
end

#expanded_groups_requiring_two_factor_authenticationObject


909
910
911
# File 'app/models/user.rb', line 909

def expanded_groups_requiring_two_factor_authentication
  all_expanded_groups.where(require_two_factor_authentication: true)
end

#feed_tokenObject

each existing user needs to have an `feed_token`. we do this on read since migrating all existing users is not a feasible solution.


1616
1617
1618
# File 'app/models/user.rb', line 1616

def feed_token
  ensure_feed_token!
end

#first_nameObject


1054
1055
1056
1057
1058
# File 'app/models/user.rb', line 1054

def first_name
  read_attribute(:first_name) || begin
    name.split(' ').first unless name.blank?
  end
end

#forget_me!Object


751
752
753
# File 'app/models/user.rb', line 751

def forget_me!
  super if ::Gitlab::Database.read_write?
end

#fork_of(project) ⇒ Object


1103
1104
1105
# File 'app/models/user.rb', line 1103

def fork_of(project)
  namespace.find_fork_of(project)
end

#full_pathObject

Instance methods


714
715
716
# File 'app/models/user.rb', line 714

def full_path
  username
end

#full_website_urlObject


1224
1225
1226
1227
1228
# File 'app/models/user.rb', line 1224

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

  website_url
end

#generate_reset_tokenObject


734
735
736
737
738
739
740
741
# File 'app/models/user.rb', line 734

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.current.utc

  @reset_token
end

#global_notification_settingObject

Lazy load global notification setting Initializes User setting with Participating level if setting not persisted


1493
1494
1495
1496
1497
1498
1499
1500
# File 'app/models/user.rb', line 1493

def global_notification_setting
  return @global_notification_setting if defined?(@global_notification_setting)

  @global_notification_setting = notification_settings.find_or_initialize_by(source: nil)
  @global_notification_setting.update(level: NotificationSetting.levels[DEFAULT_NOTIFICATION_LEVEL]) unless @global_notification_setting.persisted?

  @global_notification_setting
end

#highest_roleObject


1127
1128
1129
# File 'app/models/user.rb', line 1127

def highest_role
  user_highest_role&.highest_access_level || Gitlab::Access::NO_ACCESS
end

#hook_attrsObject


1304
1305
1306
1307
1308
1309
1310
1311
# File 'app/models/user.rb', line 1304

def hook_attrs
  {
    name: name,
    username: username,
    avatar_url: avatar_url(only_path: false),
    email: email
  }
end

#impersonated?Boolean

Returns:

  • (Boolean)

1744
1745
1746
# File 'app/models/user.rb', line 1744

def impersonated?
  impersonator.present?
end

#inactive_messageObject


383
384
385
386
387
388
389
390
391
# File 'app/models/user.rb', line 383

def inactive_message
  if blocked?
    BLOCKED_MESSAGE
  elsif internal?
    LOGIN_FORBIDDEN
  else
    super
  end
end

#increment_failed_attempts!Object

This is copied from Devise::Models::Lockable#valid_for_authentication?, as our auth flow means we don't call that automatically (and can't conveniently do so).

See:

<https://github.com/plataformatec/devise/blob/v4.7.1/lib/devise/models/lockable.rb#L104>

rubocop: disable CodeReuse/ServiceClass


1572
1573
1574
1575
1576
1577
1578
1579
1580
1581
1582
# File 'app/models/user.rb', line 1572

def increment_failed_attempts!
  return if ::Gitlab::Database.read_only?

  increment_failed_attempts

  if attempts_exceeded?
    lock_access! unless access_locked?
  else
    Users::UpdateService.new(self, user: self).execute(validate: false)
  end
end

#invalidate_cache_countsObject


1537
1538
1539
1540
1541
1542
1543
# File 'app/models/user.rb', line 1537

def invalidate_cache_counts
  invalidate_issue_cache_counts
  invalidate_merge_request_cache_counts
  invalidate_todos_done_count
  invalidate_todos_pending_count
  invalidate_personal_projects_count
end

#invalidate_issue_cache_countsObject


1545
1546
1547
# File 'app/models/user.rb', line 1545

def invalidate_issue_cache_counts
  Rails.cache.delete(['users', id, 'assigned_open_issues_count'])
end

#invalidate_merge_request_cache_countsObject


1549
1550
1551
# File 'app/models/user.rb', line 1549

def invalidate_merge_request_cache_counts
  Rails.cache.delete(['users', id, 'assigned_open_merge_requests_count'])
end

#invalidate_personal_projects_countObject


1561
1562
1563
# File 'app/models/user.rb', line 1561

def invalidate_personal_projects_count
  Rails.cache.delete(['users', id, 'personal_projects_count'])
end

#invalidate_todos_done_countObject


1553
1554
1555
# File 'app/models/user.rb', line 1553

def invalidate_todos_done_count
  Rails.cache.delete(['users', id, 'todos_done_count'])
end

#invalidate_todos_pending_countObject


1557
1558
1559
# File 'app/models/user.rb', line 1557

def invalidate_todos_pending_count
  Rails.cache.delete(['users', id, 'todos_pending_count'])
end

#last_active_atObject


1711
1712
1713
1714
1715
1716
# File 'app/models/user.rb', line 1711

def last_active_at
  last_activity = last_activity_on&.to_time&.in_time_zone
   = 

  [last_activity, ].compact.max
end

#last_nameObject


1060
1061
1062
1063
1064
# File 'app/models/user.rb', line 1060

def last_name
  read_attribute(:last_name) || begin
    name.split(' ').drop(1).join(' ') unless name.blank?
  end
end

#ldap_identityObject


1115
1116
1117
# File 'app/models/user.rb', line 1115

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

#ldap_sync_timeObject


1193
1194
1195
1196
# File 'app/models/user.rb', line 1193

def ldap_sync_time
  # This number resides in this method so it can be redefined in EE.
  1.hour
end

#ldap_user?Boolean

Returns:

  • (Boolean)

1107
1108
1109
1110
1111
1112
1113
# File 'app/models/user.rb', line 1107

def ldap_user?
  if identities.loaded?
    identities.find { |identity| Gitlab::Auth::OAuth::Provider.ldap_provider?(identity.provider) && !identity.extern_uid.nil? }
  else
    identities.exists?(["provider LIKE ? AND extern_uid IS NOT NULL", "ldap%"])
  end
end

#lock_access!Object

override, from Devise


1644
1645
1646
1647
# File 'app/models/user.rb', line 1644

def lock_access!
  Gitlab::AppLogger.info("Account Locked: username=#{username}")
  super
end

#log_info(message) ⇒ Object

rubocop: enable CodeReuse/ServiceClass


1355
1356
1357
# File 'app/models/user.rb', line 1355

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

#manageable_groups(include_groups_with_developer_maintainer_access: false) ⇒ Object


1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
# File 'app/models/user.rb', line 1386

def manageable_groups(include_groups_with_developer_maintainer_access: false)
  owned_and_maintainer_group_hierarchy = Gitlab::ObjectHierarchy.new(owned_or_maintainers_groups).base_and_descendants

  if include_groups_with_developer_maintainer_access
    union_sql = ::Gitlab::SQL::Union.new(
      [owned_and_maintainer_group_hierarchy,
       groups_with_developer_maintainer_project_access]).to_sql

    ::Group.from("(#{union_sql}) #{::Group.table_name}")
  else
    owned_and_maintainer_group_hierarchy
  end
end

#manageable_groups_with_routes(include_groups_with_developer_maintainer_access: false) ⇒ Object


1400
1401
1402
1403
1404
# File 'app/models/user.rb', line 1400

def manageable_groups_with_routes(include_groups_with_developer_maintainer_access: false)
  manageable_groups(include_groups_with_developer_maintainer_access: include_groups_with_developer_maintainer_access)
    .eager_load(:route)
    .order('routes.path')
end

#manageable_namespacesObject


1382
1383
1384
# File 'app/models/user.rb', line 1382

def manageable_namespaces
  @manageable_namespaces ||= [namespace] + manageable_groups
end

#matches_identity?(provider, extern_uid) ⇒ Boolean

Returns:

  • (Boolean)

1119
1120
1121
# File 'app/models/user.rb', line 1119

def matches_identity?(provider, extern_uid)
  identities.where(provider: provider, extern_uid: extern_uid).exists?
end

#max_member_access_for_group(group_id) ⇒ Object


1673
1674
1675
# File 'app/models/user.rb', line 1673

def max_member_access_for_group(group_id)
  max_member_access_for_group_ids([group_id])[group_id]
end

#max_member_access_for_group_ids(group_ids) ⇒ Object

Determine the maximum access level for a group of groups in bulk.

Returns a Hash mapping project ID -> maximum access level.


1667
1668
1669
1670
1671
# File 'app/models/user.rb', line 1667

def max_member_access_for_group_ids(group_ids)
  max_member_access_for_resource_ids(Group, group_ids) do |group_ids|
    group_members.where(source: group_ids).group(:source_id).maximum(:access_level)
  end
end

#max_member_access_for_project(project_id) ⇒ Object


1660
1661
1662
# File 'app/models/user.rb', line 1660

def max_member_access_for_project(project_id)
  max_member_access_for_project_ids([project_id])[project_id]
end

#max_member_access_for_project_ids(project_ids) ⇒ Object

Determine the maximum access level for a group of projects in bulk.

Returns a Hash mapping project ID -> maximum access level.


1652
1653
1654
1655
1656
1657
1658
# File 'app/models/user.rb', line 1652

def max_member_access_for_project_ids(project_ids)
  max_member_access_for_resource_ids(Project, project_ids) do |project_ids|
    project_authorizations.where(project: project_ids)
                          .group(:project_id)
                          .maximum(:access_level)
  end
end

#membership_groupsObject

Returns the groups a user is a member of, either directly or through a parent group


899
900
901
# File 'app/models/user.rb', line 899

def membership_groups
  Gitlab::ObjectHierarchy.new(groups).base_and_descendants
end

#name_with_usernameObject


1095
1096
1097
# File 'app/models/user.rb', line 1095

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

#namespace_idObject


1091
1092
1093
# File 'app/models/user.rb', line 1091

def namespace_id
  namespace.try :id
end

#namespace_move_dir_allowedObject


796
797
798
799
800
# File 'app/models/user.rb', line 796

def namespace_move_dir_allowed
  if namespace&.any_project_has_container_registry_tags?
    errors.add(:username, _('cannot be changed if a personal project has container registry tags.'))
  end
end

#namespacesObject


1406
1407
1408
1409
1410
# File 'app/models/user.rb', line 1406

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

#notification_email_for(notification_group) ⇒ Object


1457
1458
1459
1460
# File 'app/models/user.rb', line 1457

def notification_email_for(notification_group)
  # Return group-specific email address if present, otherwise return global notification email address
  notification_group&.notification_email_for(self) || notification_email
end

#notification_serviceObject

rubocop: disable CodeReuse/ServiceClass


1350
1351
1352
# File 'app/models/user.rb', line 1350

def notification_service
  NotificationService.new
end

#notification_settings_for(source, inherit: false) ⇒ Object


1462
1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484
# File 'app/models/user.rb', line 1462

def notification_settings_for(source, inherit: false)
  if notification_settings.loaded?
    notification_settings.find do |notification|
      notification.source_type == source.class.base_class.name &&
        notification.source_id == source.id
    end
  else
    notification_settings.find_or_initialize_by(source: source) do |ns|
      next unless source.is_a?(Group) && inherit

      # If we're here it means we're trying to create a NotificationSetting for a group that doesn't have one.
      # Find the closest parent with a notification_setting that's not Global level, or that has an email set.
      ancestor_ns = source
                      .notification_settings(hierarchy_order: :asc)
                      .where(user: self)
                      .find_by('level != ? OR notification_email IS NOT NULL', NotificationSetting.levels[:global])
      # Use it to seed the settings
      ns.assign_attributes(ancestor_ns&.slice(*NotificationSetting.allowed_fields))
      ns.source = source
      ns.user = self
    end
  end
end

#notification_settings_for_groups(groups) ⇒ Object


1486
1487
1488
1489
# File 'app/models/user.rb', line 1486

def notification_settings_for_groups(groups)
  ids = groups.is_a?(ActiveRecord::Relation) ? groups.select(:id) : groups.map(&:id)
  notification_settings.for_groups.where(source_id: ids)
end

#oauth_authorized_tokensObject


1412
1413
1414
# File 'app/models/user.rb', line 1412

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

#owned_projectsObject


973
974
975
976
977
978
979
980
981
982
983
# File 'app/models/user.rb', line 973

def owned_projects
  @owned_projects ||= Project.from_union(
    [
      Project.where(namespace: namespace),
      Project.joins(:project_authorizations)
        .where("projects.namespace_id <> ?", namespace.id)
        .where(project_authorizations: { user_id: id, access_level: Gitlab::Access::OWNER })
    ],
    remove_duplicates: false
  )
end

#owns_commit_emailObject


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

def owns_commit_email
  return if read_attribute(:commit_email).blank?

  errors.add(:commit_email, _("is not an email you own")) unless verified_emails.include?(commit_email)
end

#owns_notification_emailObject


815
816
817
818
819
# File 'app/models/user.rb', line 815

def owns_notification_email
  return if new_record? || temp_oauth_email?

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

#owns_public_emailObject


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

def owns_public_email
  return if public_email.blank?

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

#password_expired?Boolean

Returns:

  • (Boolean)

1703
1704
1705
# File 'app/models/user.rb', line 1703

def password_expired?
  !!(password_expires_at && password_expires_at < Time.current)
end

#pending_invitationsObject


1258
1259
1260
# File 'app/models/user.rb', line 1258

def pending_invitations
  Member.where(invite_email: verified_emails).invite
end

#pending_todo_for(target) ⇒ Object


1699
1700
1701
# File 'app/models/user.rb', line 1699

def pending_todo_for(target)
  todos.find_by(target: target, state: :pending)
end

#personal_projects_count(force: false) ⇒ Object


1526
1527
1528
1529
1530
# File 'app/models/user.rb', line 1526

def personal_projects_count(force: false)
  Rails.cache.fetch(['users', id, 'personal_projects_count'], force: force, expires_in: 24.hours, raw: true) do
    personal_projects.count
  end.to_i
end

#post_destroy_hookObject


1332
1333
1334
1335
1336
# File 'app/models/user.rb', line 1332

def post_destroy_hook
  log_info("User \"#{name}\" (#{email})  was removed")

  system_hook_service.execute_hooks_for(self, :destroy)
end

#preferred_languageObject


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

def preferred_language
  read_attribute('preferred_language') ||
    I18n.default_locale.to_s.presence_in(Gitlab::I18n::AVAILABLE_LANGUAGES.keys) ||
    'en'
end

#primary_email_verified?Boolean

rubocop: enable CodeReuse/ServiceClass

Returns:

  • (Boolean)

1248
1249
1250
# File 'app/models/user.rb', line 1248

def primary_email_verified?
  confirmed? && !temp_oauth_email?
end

#private_commit_emailObject


857
858
859
# File 'app/models/user.rb', line 857

def private_commit_email
  Gitlab::PrivateCommitEmail.for_user(self)
end

#project_deploy_keysObject


1123
1124
1125
# File 'app/models/user.rb', line 1123

def project_deploy_keys
  @project_deploy_keys ||= DeployKey.in_projects(authorized_projects.select(:id)).distinct(:id)
end

#projects_limit_leftObject


1066
1067
1068
# File 'app/models/user.rb', line 1066

def projects_limit_left
  projects_limit - personal_projects_count
end

#projects_where_can_admin_issuesObject

Returns projects which user can admin issues on (for example to move an issue to that project).

This logic is duplicated from `Ability#project_abilities` into a SQL form.


988
989
990
# File 'app/models/user.rb', line 988

def projects_where_can_admin_issues
  authorized_projects(Gitlab::Access::REPORTER).non_archived.with_issues_enabled
end

#projects_with_reporter_access_limited_to(projects) ⇒ Object

Returns the projects this user has reporter (or greater) access to, limited to at most the given projects.

This method is useful when you have a list of projects and want to efficiently check to which of these projects the user has at least reporter access.


969
970
971
# File 'app/models/user.rb', line 969

def projects_with_reporter_access_limited_to(projects)
  authorized_projects(Gitlab::Access::REPORTER).where(id: projects)
end

#public_verified_emailsObject


1278
1279
1280
1281
1282
# File 'app/models/user.rb', line 1278

def public_verified_emails
  emails = verified_emails(include_private_email: false)
  emails << email unless temp_oauth_email?
  emails.uniq
end

#read_only_attribute?(attribute) ⇒ Boolean

Returns:

  • (Boolean)

1639
1640
1641
# File 'app/models/user.rb', line 1639

def read_only_attribute?(attribute)
  &.read_only?(attribute)
end

#recent_push(project = nil) ⇒ Object

rubocop: disable CodeReuse/ServiceClass


1071
1072
1073
1074
1075
1076
1077
1078
1079
# File 'app/models/user.rb', line 1071

def recent_push(project = nil)
  service = Users::LastPushEventService.new(self)

  if project
    service.last_event_for_project(project)
  else
    service.last_event_for_user
  end
end

#recently_sent_password_reset?Boolean

Returns:

  • (Boolean)

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

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

#refresh_authorized_projectsObject

rubocop: disable CodeReuse/ServiceClass


920
921
922
# File 'app/models/user.rb', line 920

def refresh_authorized_projects
  Users::RefreshAuthorizedProjectsService.new(self).execute
end

#remember_me!Object


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

def remember_me!
  super if ::Gitlab::Database.read_write?
end

#remove_key_cacheObject

rubocop: disable CodeReuse/ServiceClass


1339
1340
1341
# File 'app/models/user.rb', line 1339

def remove_key_cache
  Users::KeysCountService.new(self).delete_cache
end

#remove_project_authorizations(project_ids) ⇒ Object

rubocop: enable CodeReuse/ServiceClass


925
926
927
# File 'app/models/user.rb', line 925

def remove_project_authorizations(project_ids)
  project_authorizations.where(project_id: project_ids).delete_all
end

#require_extra_setup_for_git_auth?Boolean

Returns:

  • (Boolean)

1014
1015
1016
# File 'app/models/user.rb', line 1014

def require_extra_setup_for_git_auth?
  require_password_creation_for_git? || require_personal_access_token_creation_for_git_auth?
end

#require_password_creation_for_git?Boolean

Returns:

  • (Boolean)

1004
1005
1006
# File 'app/models/user.rb', line 1004

def require_password_creation_for_git?
  allow_password_authentication_for_git? && password_automatically_set?
end

#require_password_creation_for_web?Boolean

rubocop: enable CodeReuse/ServiceClass

Returns:

  • (Boolean)

1000
1001
1002
# File 'app/models/user.rb', line 1000

def require_password_creation_for_web?
  allow_password_authentication_for_web? && password_automatically_set?
end

#require_personal_access_token_creation_for_git_auth?Boolean

Returns:

  • (Boolean)

1008
1009
1010
1011
1012
# File 'app/models/user.rb', line 1008

def require_personal_access_token_creation_for_git_auth?
  return false if allow_password_authentication_for_git? || ldap_user?

  PersonalAccessTokensFinder.new(user: self, impersonation: false, state: 'active').execute.none?
end

#require_ssh_key?Boolean

rubocop: disable CodeReuse/ServiceClass

Returns:

  • (Boolean)

993
994
995
996
997
# File 'app/models/user.rb', line 993

def require_ssh_key?
  count = Users::KeysCountService.new(self).count

  count == 0 && Gitlab::ProtocolAccess.allowed?('ssh')
end

#required_terms_not_accepted?Boolean

Returns:

  • (Boolean)

1681
1682
1683
1684
# File 'app/models/user.rb', line 1681

def required_terms_not_accepted?
  Gitlab::CurrentSettings.current_application_settings.enforce_terms? &&
    !terms_accepted?
end

#requires_ldap_check?Boolean

Returns:

  • (Boolean)

1183
1184
1185
1186
1187
1188
1189
1190
1191
# File 'app/models/user.rb', line 1183

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

#requires_usage_stats_consent?Boolean

Returns:

  • (Boolean)

1686
1687
1688
# File 'app/models/user.rb', line 1686

def requires_usage_stats_consent?
  self.admin? && 7.days.ago > self.created_at && !has_current_license? && User.single_user? && !consented_usage_stats?
end

#role_required?Boolean

Returns:

  • (Boolean)

1720
1721
1722
# File 'app/models/user.rb', line 1720

def role_required?
  role_before_type_cast == REQUIRES_ROLE_VALUE
end

#sanitize_attrsObject


1142
1143
1144
1145
1146
1147
# File 'app/models/user.rb', line 1142

def sanitize_attrs
  %i[skype linkedin twitter].each do |attr|
    value = self[attr]
    self[attr] = Sanitize.clean(value) if value.present?
  end
end

#set_commit_emailObject


1161
1162
1163
1164
1165
# File 'app/models/user.rb', line 1161

def set_commit_email
  if commit_email.blank? || verified_emails.exclude?(commit_email)
    self.commit_email = nil
  end
end

#set_notification_emailObject


1149
1150
1151
1152
1153
# File 'app/models/user.rb', line 1149

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

#set_projects_limitObject


1174
1175
1176
1177
1178
1179
1180
1181
# File 'app/models/user.rb', line 1174

def set_projects_limit
  # `User.select(:id)` raises
  # `ActiveModel::MissingAttributeError: missing attribute: projects_limit`
  # without this safeguard!
  return unless has_attribute?(:projects_limit) && projects_limit.nil?

  self.projects_limit = Gitlab::CurrentSettings.default_projects_limit
end

#set_public_emailObject


1155
1156
1157
1158
1159
# File 'app/models/user.rb', line 1155

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

#set_role_required!Object


1724
1725
1726
# File 'app/models/user.rb', line 1724

def set_role_required!
  update_column(:role, REQUIRES_ROLE_VALUE)
end

#set_username_errorsObject


1323
1324
1325
1326
# File 'app/models/user.rb', line 1323

def set_username_errors
  namespace_path_errors = self.errors.delete(:"namespace.path")
  self.errors[:username].concat(namespace_path_errors) if namespace_path_errors
end

#several_namespaces?Boolean

rubocop: enable CodeReuse/ServiceClass

Returns:

  • (Boolean)

1082
1083
1084
1085
1086
1087
1088
1089
# File 'app/models/user.rb', line 1082

def several_namespaces?
  union_sql = ::Gitlab::SQL::Union.new(
    [owned_groups,
     maintainers_groups,
     groups_with_developer_maintainer_project_access]).to_sql

  ::Group.from("(#{union_sql}) #{::Group.table_name}").any?
end

#short_website_urlObject


1230
1231
1232
# File 'app/models/user.rb', line 1230

def short_website_url
  website_url.sub(%r{\Ahttps?://}, '')
end

#skip_confirmation=(bool) ⇒ Object


726
727
728
# File 'app/models/user.rb', line 726

def skip_confirmation=(bool)
  skip_confirmation! if bool
end

#skip_reconfirmation=(bool) ⇒ Object


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

def skip_reconfirmation=(bool)
  skip_reconfirmation! if bool
end

#solo_owned_groupsObject


1205
1206
1207
1208
1209
# File 'app/models/user.rb', line 1205

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

#source_groups_of_two_factor_authentication_requirementObject


913
914
915
916
917
# File 'app/models/user.rb', line 913

def source_groups_of_two_factor_authentication_requirement
  Gitlab::ObjectHierarchy.new(expanded_groups_requiring_two_factor_authentication)
    .all_objects
    .where(id: groups)
end

#starred?(project) ⇒ Boolean

rubocop: enable CodeReuse/ServiceClass

Returns:

  • (Boolean)

1365
1366
1367
# File 'app/models/user.rb', line 1365

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

#static_object_tokenObject

Each existing user needs to have a `static_object_token`. We do this on read since migrating all existing users is not a feasible solution.


1623
1624
1625
# File 'app/models/user.rb', line 1623

def static_object_token
  ensure_static_object_token!
end

#sync_attribute?(attribute) ⇒ Boolean

Returns:

  • (Boolean)

1627
1628
1629
1630
1631
1632
1633
1634
1635
1636
1637
# File 'app/models/user.rb', line 1627

def sync_attribute?(attribute)
  return true if ldap_user? && attribute == :email

  attributes = Gitlab.config.omniauth.sync_profile_attributes

  if attributes.is_a?(Array)
    attributes.include?(attribute.to_s)
  else
    attributes
  end
end

#system_hook_serviceObject

rubocop: disable CodeReuse/ServiceClass


1360
1361
1362
# File 'app/models/user.rb', line 1360

def system_hook_service
  SystemHooksService.new
end

#temp_oauth_email?Boolean

Returns:

  • (Boolean)

1238
1239
1240
# File 'app/models/user.rb', line 1238

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

#terms_accepted?Boolean

Returns:

  • (Boolean)

1677
1678
1679
# File 'app/models/user.rb', line 1677

def terms_accepted?
  accepted_term_id.present?
end

#to_paramObject


718
719
720
# File 'app/models/user.rb', line 718

def to_param
  username
end

#to_reference(_from = nil, target_project: nil, full: nil) ⇒ Object


722
723
724
# File 'app/models/user.rb', line 722

def to_reference(_from = nil, target_project: nil, full: nil)
  "#{self.class.reference_prefix}#{username}"
end

#todos_done_count(force: false) ⇒ Object


1514
1515
1516
1517
1518
# File 'app/models/user.rb', line 1514

def todos_done_count(force: false)
  Rails.cache.fetch(['users', id, 'todos_done_count'], force: force, expires_in: 20.minutes) do
    TodosFinder.new(self, state: :done).execute.count
  end
end

#todos_pending_count(force: false) ⇒ Object


1520
1521
1522
1523
1524
# File 'app/models/user.rb', line 1520

def todos_pending_count(force: false)
  Rails.cache.fetch(['users', id, 'todos_pending_count'], force: force, expires_in: 20.minutes) do
    TodosFinder.new(self, state: :pending).execute.count
  end
end

#toggle_star(project) ⇒ Object


1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
# File 'app/models/user.rb', line 1369

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


1198
1199
1200
1201
1202
1203
# File 'app/models/user.rb', line 1198

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

#two_factor_enabled?Boolean

Returns:

  • (Boolean)

770
771
772
# File 'app/models/user.rb', line 770

def two_factor_enabled?
  two_factor_otp_enabled? || two_factor_webauthn_u2f_enabled?
end

#two_factor_otp_enabled?Boolean

Returns:

  • (Boolean)

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

def two_factor_otp_enabled?
  otp_required_for_login?
end

#two_factor_u2f_enabled?Boolean

Returns:

  • (Boolean)

778
779
780
781
782
783
784
# File 'app/models/user.rb', line 778

def two_factor_u2f_enabled?
  if u2f_registrations.loaded?
    u2f_registrations.any?
  else
    u2f_registrations.exists?
  end
end

#two_factor_webauthn_enabled?Boolean

Returns:

  • (Boolean)

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

def two_factor_webauthn_enabled?
  return false unless Feature.enabled?(:webauthn)

  (webauthn_registrations.loaded? && webauthn_registrations.any?) || (!webauthn_registrations.loaded? && webauthn_registrations.exists?)
end

#two_factor_webauthn_u2f_enabled?Boolean

Returns:

  • (Boolean)

786
787
788
# File 'app/models/user.rb', line 786

def two_factor_webauthn_u2f_enabled?
  two_factor_u2f_enabled? || two_factor_webauthn_enabled?
end

#unique_emailObject


809
810
811
812
813
# File 'app/models/user.rb', line 809

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

#update_emails_with_primary_email(previous_confirmed_at, previous_email) ⇒ Object

Note: the use of the Emails services will cause `saves` on the user object, running through the callbacks again and can have side effects, such as the `previous_changes` hash and `_was` variables getting munged. By using an `after_commit` instead of `after_update`, we avoid the recursive callback scenario, though it then requires us to use the `previous_changes` hash rubocop: disable CodeReuse/ServiceClass


872
873
874
875
876
877
878
879
880
881
# File 'app/models/user.rb', line 872

def update_emails_with_primary_email(previous_confirmed_at, previous_email)
  primary_email_record = emails.find_by(email: email)
  Emails::DestroyService.new(self, user: self).execute(primary_email_record) if primary_email_record

  # the original primary email was confirmed, and we want that to carry over.  We don't
  # have access to the original confirmation values at this point, so just set confirmed_at
  Emails::CreateService.new(self, user: self, email: previous_email).execute(confirmed_at: previous_confirmed_at)

  update_columns(confirmed_at: primary_email_record.confirmed_at) if primary_email_record&.confirmed_at
end

#update_invalid_gpg_signaturesObject

rubocop: enable CodeReuse/ServiceClass


884
885
886
# File 'app/models/user.rb', line 884

def update_invalid_gpg_signatures
  gpg_keys.each(&:update_invalid_gpg_signatures)
end

#update_secondary_emails!Object


1167
1168
1169
1170
1171
1172
# File 'app/models/user.rb', line 1167

def update_secondary_emails!
  set_notification_email
  set_public_email
  set_commit_email
  save if notification_email_changed? || public_email_changed? || commit_email_changed?
end

#update_todos_count_cacheObject


1532
1533
1534
1535
# File 'app/models/user.rb', line 1532

def update_todos_count_cache
  todos_done_count(force: true)
  todos_pending_count(force: true)
end

#update_tracked_fields!(request) ⇒ Object

Override Devise::Models::Trackable#update_tracked_fields! to limit database writes to at most once every hour rubocop: disable CodeReuse/ServiceClass


79
80
81
82
83
84
85
86
87
88
# File 'app/models/user.rb', line 79

def update_tracked_fields!(request)
  return if Gitlab::Database.read_only?

  update_tracked_fields(request)

  lease = Gitlab::ExclusiveLease.new("user_update_tracked_fields:#{id}", timeout: 1.hour.to_i)
  return unless lease.try_obtain

  Users::UpdateService.new(self, user: self).execute(validate: false)
end

#update_two_factor_requirementObject


1604
1605
1606
1607
1608
1609
1610
1611
# File 'app/models/user.rb', line 1604

def update_two_factor_requirement
  periods = expanded_groups_requiring_two_factor_authentication.pluck(:two_factor_grace_period)

  self.require_two_factor_authentication_from_group = periods.any?
  self.two_factor_grace_period = periods.min || User.column_defaults['two_factor_grace_period']

  save
end

#user_detailObject


1695
1696
1697
# File 'app/models/user.rb', line 1695

def user_detail
  super.presence || build_user_detail
end

#user_preferenceObject

Avoid migrations only building user preference object when needed.


1691
1692
1693
# File 'app/models/user.rb', line 1691

def user_preference
  super.presence || build_user_preference
end

#username_changed_hookObject


1328
1329
1330
# File 'app/models/user.rb', line 1328

def username_changed_hook
  system_hook_service.execute_hooks_for(self, :rename)
end

#verified_email?(check_email) ⇒ Boolean

Returns:

  • (Boolean)

1294
1295
1296
1297
1298
1299
1300
1301
1302
# File 'app/models/user.rb', line 1294

def verified_email?(check_email)
  downcased = check_email.downcase

  # handle the outdated private commit email case
  return true if persisted? &&
      id == Gitlab::PrivateCommitEmail.user_id_for_email(downcased)

  verified_emails.include?(check_email.downcase)
end

#verified_emails(include_private_email: true) ⇒ Object


1270
1271
1272
1273
1274
1275
1276
# File 'app/models/user.rb', line 1270

def verified_emails(include_private_email: true)
  verified_emails = []
  verified_emails << email if primary_email_verified?
  verified_emails << private_commit_email if include_private_email
  verified_emails.concat(emails.confirmed.pluck(:email))
  verified_emails
end

#will_save_change_to_login?Boolean

will_save_change_to_attribute? is used by Devise to check if it is necessary to clear any existing reset_password_tokens before updating an authentication_key and login in our case is a virtual attribute to allow login by username or email.

Returns:

  • (Boolean)

805
806
807
# File 'app/models/user.rb', line 805

def will_save_change_to_login?
  will_save_change_to_username? || will_save_change_to_email?
end

#with_defaultsObject


1211
1212
1213
1214
1215
1216
1217
# File 'app/models/user.rb', line 1211

def with_defaults
  User.defaults.each do |k, v|
    public_send("#{k}=", v) # rubocop:disable GitlabSecurity/PublicSend
  end

  self
end