user.rb 59.7 KB
Newer Older
1 2
# frozen_string_literal: true

S
Steven Thonus 已提交
3 4
require 'carrierwave/orm/activerecord'

M
Mark Chao 已提交
5
class User < ApplicationRecord
6
  extend Gitlab::ConfigHelper
7 8

  include Gitlab::ConfigHelper
H
Hiroyuki Sato 已提交
9
  include Gitlab::SQL::Pattern
D
Douwe Maan 已提交
10
  include AfterCommitQueue
11
  include Avatarable
12 13
  include Referable
  include Sortable
14
  include CaseSensitivity
15
  include TokenAuthenticatable
16
  include FeatureGate
17
  include CreatedAtFilterable
18
  include BulkMemberAccessLoad
19
  include BlocksJsonSerialization
J
Jan Provaznik 已提交
20
  include WithUploads
Y
Yorick Peterse 已提交
21
  include OptionallySearch
22
  include FromUnion
23
  include BatchDestroyDependentAssociations
24
  include HasUniqueInternalUsers
25
  include IgnorableColumns
26
  include UpdateHighestRole
27
  include HasUserType
28

29 30
  DEFAULT_NOTIFICATION_LEVEL = :participating

31
  add_authentication_token_field :incoming_email_token, token_generator: -> { SecureRandom.hex.to_i(16).to_s(36) }
32
  add_authentication_token_field :feed_token
33
  add_authentication_token_field :static_object_token
34

35
  default_value_for :admin, false
36
  default_value_for(:external) { Gitlab::CurrentSettings.user_default_external }
37
  default_value_for :can_create_group, gitlab_config.default_can_create_group
38 39
  default_value_for :can_create_team, false
  default_value_for :hide_no_ssh_key, false
40
  default_value_for :hide_no_password, false
41
  default_value_for :project_view, :files
42
  default_value_for :notified_of_own_activity, false
43
  default_value_for :preferred_language, I18n.default_locale
44
  default_value_for :theme_id, gitlab_config.default_theme
45

46
  attr_encrypted :otp_secret,
47
    key:       Gitlab::Application.secrets.otp_key_base,
48
    mode:      :per_attribute_iv_and_salt,
49
    insecure_mode: true,
50 51
    algorithm: 'aes-256-cbc'

52
  devise :two_factor_authenticatable,
53
         otp_secret_encryption_key: Gitlab::Application.secrets.otp_key_base
R
Robert Speicher 已提交
54

55
  devise :two_factor_backupable, otp_number_of_backup_codes: 10
56
  serialize :otp_backup_codes, JSON # rubocop:disable Cop/ActiveRecordSerialize
R
Robert Speicher 已提交
57

58
  devise :lockable, :recoverable, :rememberable, :trackable,
59 60
         :validatable, :omniauthable, :confirmable, :registerable

61 62 63 64
  # This module adds async behaviour to Devise emails
  # and should be added after Devise modules are initialized.
  include AsyncDeviseEmail

65
  BLOCKED_MESSAGE = "Your account has been blocked. Please contact your GitLab " \
66
                    "administrator if you think this is an error."
67 68
  LOGIN_FORBIDDEN = "Your account does not have the required permission to login. Please contact your GitLab " \
                    "administrator if you think this is an error."
G
gitlabhq 已提交
69

70
  MINIMUM_INACTIVE_DAYS = 180
71

72 73
  # Override Devise::Models::Trackable#update_tracked_fields!
  # to limit database writes to at most once every hour
74
  # rubocop: disable CodeReuse/ServiceClass
75
  def update_tracked_fields!(request)
76 77
    return if Gitlab::Database.read_only?

78 79
    update_tracked_fields(request)

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

J
James Lopez 已提交
83
    Users::UpdateService.new(self, user: self).execute(validate: false)
84
  end
85
  # rubocop: enable CodeReuse/ServiceClass
86

87
  attr_accessor :force_random_password
G
gitlabhq 已提交
88

89 90 91
  # Virtual attribute for authenticating by either username or email
  attr_accessor :login

92 93 94
  # Virtual attribute for impersonator
  attr_accessor :impersonator

95 96 97 98
  #
  # Relations
  #

99
  # Namespace for personal projects
100
  has_one :namespace, -> { where(type: nil) }, dependent: :destroy, foreign_key: :owner_id, inverse_of: :owner, autosave: true # rubocop:disable Cop/ActiveRecordDependent
101 102

  # Profile
103
  has_many :keys, -> { regular_keys }, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
104
  has_many :deploy_keys, -> { where(type: 'DeployKey') }, dependent: :nullify # rubocop:disable Cop/ActiveRecordDependent
105
  has_many :gpg_keys
106

107 108 109 110 111
  has_many :emails, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
  has_many :personal_access_tokens, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
  has_many :identities, dependent: :destroy, autosave: true # rubocop:disable Cop/ActiveRecordDependent
  has_many :u2f_registrations, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
  has_many :chat_names, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
112
  has_one :user_synced_attributes_metadata, autosave: true
113
  has_one :aws_role, class_name: 'Aws::Role'
114 115

  # Groups
116 117
  has_many :members
  has_many :group_members, -> { where(requested_at: nil) }, source: 'GroupMember'
118 119
  has_many :groups, through: :group_members
  has_many :owned_groups, -> { where(members: { access_level: Gitlab::Access::OWNER }) }, through: :group_members, source: :group
120
  has_many :maintainers_groups, -> { where(members: { access_level: Gitlab::Access::MAINTAINER }) }, through: :group_members, source: :group
G
Gosia Ksionek 已提交
121
  has_many :developer_groups, -> { where(members: { access_level: ::Gitlab::Access::DEVELOPER }) }, through: :group_members, source: :group
122 123 124 125
  has_many :owned_or_maintainers_groups,
           -> { where(members: { access_level: [Gitlab::Access::MAINTAINER, Gitlab::Access::OWNER] }) },
           through: :group_members,
           source: :group
126
  alias_attribute :masters_groups, :maintainers_groups
127 128 129 130
  has_many :reporter_developer_maintainer_owned_groups,
           -> { where(members: { access_level: [Gitlab::Access::REPORTER, Gitlab::Access::DEVELOPER, Gitlab::Access::MAINTAINER, Gitlab::Access::OWNER] }) },
           through: :group_members,
           source: :group
131

132
  # Projects
133 134
  has_many :groups_projects,          through: :groups, source: :projects
  has_many :personal_projects,        through: :namespace, source: :projects
135
  has_many :project_members, -> { where(requested_at: nil) }
136 137
  has_many :projects,                 through: :project_members
  has_many :created_projects,         foreign_key: :creator_id, class_name: 'Project'
138
  has_many :users_star_projects, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
139
  has_many :starred_projects, through: :users_star_projects, source: :project
140
  has_many :project_authorizations, dependent: :delete_all # rubocop:disable Cop/ActiveRecordDependent
141
  has_many :authorized_projects, through: :project_authorizations, source: :project
142

143
  has_many :user_interacted_projects
144
  has_many :project_interactions, through: :user_interacted_projects, source: :project, class_name: 'Project'
145

146 147 148 149
  has_many :snippets,                 dependent: :destroy, foreign_key: :author_id # rubocop:disable Cop/ActiveRecordDependent
  has_many :notes,                    dependent: :destroy, foreign_key: :author_id # rubocop:disable Cop/ActiveRecordDependent
  has_many :issues,                   dependent: :destroy, foreign_key: :author_id # rubocop:disable Cop/ActiveRecordDependent
  has_many :merge_requests,           dependent: :destroy, foreign_key: :author_id # rubocop:disable Cop/ActiveRecordDependent
150
  has_many :events,                   dependent: :delete_all, foreign_key: :author_id # rubocop:disable Cop/ActiveRecordDependent
151
  has_many :releases,                 dependent: :nullify, foreign_key: :author_id # rubocop:disable Cop/ActiveRecordDependent
152 153 154 155 156 157 158
  has_many :subscriptions,            dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
  has_many :oauth_applications, class_name: 'Doorkeeper::Application', as: :owner, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
  has_one  :abuse_report,             dependent: :destroy, foreign_key: :user_id # rubocop:disable Cop/ActiveRecordDependent
  has_many :reported_abuse_reports,   dependent: :destroy, foreign_key: :reporter_id, class_name: "AbuseReport" # rubocop:disable Cop/ActiveRecordDependent
  has_many :spam_logs,                dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
  has_many :builds,                   dependent: :nullify, class_name: 'Ci::Build' # rubocop:disable Cop/ActiveRecordDependent
  has_many :pipelines,                dependent: :nullify, class_name: 'Ci::Pipeline' # rubocop:disable Cop/ActiveRecordDependent
159
  has_many :todos
160
  has_many :notification_settings
161 162
  has_many :award_emoji,              dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
  has_many :triggers,                 dependent: :destroy, class_name: 'Ci::Trigger', foreign_key: :owner_id # rubocop:disable Cop/ActiveRecordDependent
163

164 165
  has_many :issue_assignees, inverse_of: :assignee
  has_many :merge_request_assignees, inverse_of: :assignee
166
  has_many :assigned_issues, class_name: "Issue", through: :issue_assignees, source: :issue
167
  has_many :assigned_merge_requests, class_name: "MergeRequest", through: :merge_request_assignees, source: :merge_request
168

169
  has_many :custom_attributes, class_name: 'UserCustomAttribute'
M
Matija Čupić 已提交
170
  has_many :callouts, class_name: 'UserCallout'
171 172
  has_many :term_agreements
  belongs_to :accepted_term, class_name: 'ApplicationSetting::Term'
173

174 175
  has_many :metrics_users_starred_dashboards, class_name: 'Metrics::UsersStarredDashboard', inverse_of: :user

B
Bob Van Landuyt 已提交
176
  has_one :status, class_name: 'UserStatus'
177
  has_one :user_preference
178
  has_one :user_detail
179
  has_one :user_highest_role
180
  has_one :user_canonical_email
B
Bob Van Landuyt 已提交
181

182 183
  has_many :reviews, foreign_key: :author_id, inverse_of: :author

184 185 186
  #
  # Validations
  #
187
  # Note: devise :validatable above adds validations for :email and :password
188 189 190
  validates :name, presence: true, length: { maximum: 255 }
  validates :first_name, length: { maximum: 127 }
  validates :last_name, length: { maximum: 127 }
D
Douwe Maan 已提交
191
  validates :email, confirmation: true
192
  validates :notification_email, presence: true
193 194 195
  validates :notification_email, devise_email: true, if: ->(user) { user.notification_email != user.email }
  validates :public_email, presence: true, uniqueness: true, devise_email: true, allow_blank: true
  validates :commit_email, devise_email: true, allow_nil: true, if: ->(user) { user.commit_email != user.email }
196
  validates :bio, length: { maximum: 255 }, allow_blank: true
197 198 199
  validates :projects_limit,
    presence: true,
    numericality: { greater_than_or_equal_to: 0, less_than_or_equal_to: Gitlab::Database::MAX_INT_VALUE }
200
  validates :username, presence: true
201

202
  validates :namespace, presence: true
203 204
  validate :namespace_move_dir_allowed, if: :username_changed?

T
Tiago Botelho 已提交
205 206 207
  validate :unique_email, if: :email_changed?
  validate :owns_notification_email, if: :notification_email_changed?
  validate :owns_public_email, if: :public_email_changed?
208
  validate :owns_commit_email, if: :commit_email_changed?
209
  validate :signup_domain_valid?, on: :create, if: ->(user) { !user.created_by_id }
210
  validate :check_email_restrictions, on: :create, if: ->(user) { !user.created_by_id }
211

212 213 214 215 216 217
  validates :theme_id, allow_nil: true, inclusion: { in: Gitlab::Themes.valid_ids,
    message: _("%{placeholder} is not a valid theme") % { placeholder: '%{value}' } }

  validates :color_scheme_id, allow_nil: true, inclusion: { in: Gitlab::ColorSchemes.valid_ids,
    message: _("%{placeholder} is not a valid color scheme") % { placeholder: '%{value}' } }

218
  before_validation :sanitize_attrs
219
  before_validation :set_notification_email, if: :new_record?
T
Tiago Botelho 已提交
220
  before_validation :set_public_email, if: :public_email_changed?
221
  before_validation :set_commit_email, if: :commit_email_changed?
222
  before_save :default_private_profile_to_false
223
  before_save :set_public_email, if: :public_email_changed? # in case validation is skipped
224
  before_save :set_commit_email, if: :commit_email_changed? # in case validation is skipped
D
Douwe Maan 已提交
225
  before_save :ensure_incoming_email_token
226
  before_save :ensure_user_rights_and_limits, if: ->(user) { user.new_record? || user.external_changed? }
227
  before_save :skip_reconfirmation!, if: ->(user) { user.email_changed? && user.read_only_attribute?(:email) }
A
Alexandra 已提交
228
  before_save :check_for_verified_email, if: ->(user) { user.email_changed? && !user.new_record? }
229
  before_validation :ensure_namespace_correct
230
  before_save :ensure_namespace_correct # in case validation is skipped
231
  before_save :ensure_bio_is_assigned_to_user_details, if: :bio_changed?
232
  after_validation :set_username_errors
J
Jasper Maes 已提交
233
  after_update :username_changed_hook, if: :saved_change_to_username?
234
  after_destroy :post_destroy_hook
Y
Yorick Peterse 已提交
235
  after_destroy :remove_key_cache
236 237 238 239
  after_commit(on: :update) do
    if previous_changes.key?('email')
      # Grab previous_email here since previous_changes changes after
      # #update_emails_with_primary_email and #update_notification_email are called
240
      previous_confirmed_at = previous_changes.key?('confirmed_at') ? previous_changes['confirmed_at'][0] : confirmed_at
241 242
      previous_email = previous_changes[:email][0]

243
      update_emails_with_primary_email(previous_confirmed_at, previous_email)
244 245 246 247 248 249 250 251
      update_invalid_gpg_signatures

      if previous_email == notification_email
        self.notification_email = email
        save
      end
    end
  end
252

253
  after_initialize :set_projects_limit
D
Dmitriy Zaporozhets 已提交
254

255
  # User's Layout preference
256
  enum layout: { fixed: 0, fluid: 1 }
257

258
  # User's Dashboard preference
259
  enum dashboard: { projects: 0, stars: 1, project_activity: 2, starred_project_activity: 3, groups: 4, todos: 5, issues: 6, merge_requests: 7, operations: 8 }
260

261
  # User's Project preference
262
  enum project_view: { readme: 0, activity: 1, files: 2 }
263

264
  # User's role
265
  enum role: { software_developer: 0, development_team_lead: 1, devops_engineer: 2, systems_administrator: 3, security_analyst: 4, data_analyst: 5, product_manager: 6, product_designer: 7, other: 8 }, _suffix: true
266

267 268 269 270 271 272 273 274 275 276 277
  delegate  :notes_filter_for,
            :set_notes_filter,
            :first_day_of_week, :first_day_of_week=,
            :timezone, :timezone=,
            :time_display_relative, :time_display_relative=,
            :time_format_in_24h, :time_format_in_24h=,
            :show_whitespace_in_diffs, :show_whitespace_in_diffs=,
            :tab_width, :tab_width=,
            :sourcegraph_enabled, :sourcegraph_enabled=,
            :setup_for_company, :setup_for_company=,
            :render_whitespace_in_code, :render_whitespace_in_code=,
278
            :experience_level, :experience_level=,
279 280
            to: :user_preference

281
  delegate :path, to: :namespace, allow_nil: true, prefix: true
282
  delegate :job_title, :job_title=, to: :user_detail, allow_nil: true
283 284

  accepts_nested_attributes_for :user_preference, update_only: true
285
  accepts_nested_attributes_for :user_detail, update_only: true
286

287 288 289
  state_machine :state, initial: :active do
    event :block do
      transition active: :blocked
290
      transition deactivated: :blocked
291
      transition ldap_blocked: :blocked
292 293
    end

294 295
    event :ldap_block do
      transition active: :ldap_blocked
296
      transition deactivated: :ldap_blocked
297 298
    end

299
    event :activate do
300
      transition deactivated: :active
301
      transition blocked: :active
302
      transition ldap_blocked: :active
303
    end
304

305 306 307 308
    event :deactivate do
      transition active: :deactivated
    end

309 310 311 312 313
    state :blocked, :ldap_blocked do
      def blocked?
        true
      end
    end
314

315 316 317 318
    before_transition do
      !Gitlab::Database.read_only?
    end

319 320 321 322 323 324 325 326 327
    # rubocop: disable CodeReuse/ServiceClass
    # Ideally we should not call a service object here but user.block
    # is also bcalled by Users::MigrateToGhostUserService which references
    # this state transition object in order to do a rollback.
    # For this reason the tradeoff is to disable this cop.
    after_transition any => :blocked do |user|
      Ci::CancelUserPipelinesService.new.execute(user)
    end
    # rubocop: enable CodeReuse/ServiceClass
328 329
  end

A
Andrey Kumanyaev 已提交
330
  # Scopes
331
  scope :admins, -> { where(admin: true) }
332
  scope :blocked, -> { with_states(:blocked, :ldap_blocked) }
333
  scope :external, -> { where(external: true) }
334
  scope :confirmed, -> { where.not(confirmed_at: nil) }
J
James Lopez 已提交
335
  scope :active, -> { with_state(:active).non_internal }
336
  scope :active_without_ghosts, -> { with_state(:active).without_ghosts }
337
  scope :deactivated, -> { with_state(:deactivated).non_internal }
338
  scope :without_projects, -> { joins('LEFT JOIN project_authorizations ON users.id = project_authorizations.user_id').where(project_authorizations: { user_id: nil }) }
339
  scope :by_username, -> (usernames) { iwhere(username: Array(usernames).map(&:to_s)) }
340
  scope :for_todos, -> (todos) { where(id: todos.select(:user_id)) }
341
  scope :with_emails, -> { preload(:emails) }
L
Logan King 已提交
342
  scope :with_dashboard, -> (dashboard) { where(dashboard: dashboard) }
C
Camil Staps 已提交
343
  scope :with_public_profile, -> { where(private_profile: false) }
344 345 346 347
  scope :with_expiring_and_not_notified_personal_access_tokens, ->(at) do
    where('EXISTS (?)',
          ::PersonalAccessToken
            .where('personal_access_tokens.user_id = users.id')
348
            .without_impersonation
349 350
            .expiring_and_not_notified(at).select(1))
  end
351 352 353 354
  scope :order_recent_sign_in, -> { reorder(Gitlab::Database.nulls_last_order('current_sign_in_at', 'DESC')) }
  scope :order_oldest_sign_in, -> { reorder(Gitlab::Database.nulls_last_order('current_sign_in_at', 'ASC')) }
  scope :order_recent_last_activity, -> { reorder(Gitlab::Database.nulls_last_order('last_activity_on', 'DESC')) }
  scope :order_oldest_last_activity, -> { reorder(Gitlab::Database.nulls_first_order('last_activity_on', 'ASC')) }
355

356 357 358 359 360 361 362 363 364 365 366 367 368 369
  def active_for_authentication?
    super && can?(:log_in)
  end

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

C
Camil Staps 已提交
370 371 372 373
  def self.with_visible_profile(user)
    return with_public_profile if user.nil?

    if user.admin?
374 375
      all
    else
C
Camil Staps 已提交
376
      with_public_profile.or(where(id: user.id))
377
    end
C
Camil Staps 已提交
378
  end
379

Y
Yorick Peterse 已提交
380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402
  # 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.
  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

  # Returns a relation that optionally includes the given user.
  #
  # user_id - The ID of the user to include.
  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.
403
      User.unscoped.from_union([all, User.unscoped.where(id: user_id)])
Y
Yorick Peterse 已提交
404 405 406 407 408
    else
      all
    end
  end

409
  def self.with_two_factor
410 411 412 413 414 415 416 417 418
    with_u2f_registrations = <<-SQL
      EXISTS (
        SELECT *
        FROM u2f_registrations AS u2f
        WHERE u2f.user_id = users.id
      ) OR users.otp_required_for_login = ?
    SQL

    where(with_u2f_registrations, true)
419 420 421
  end

  def self.without_two_factor
422
    joins("LEFT OUTER JOIN u2f_registrations AS u2f ON u2f.user_id = users.id")
423
      .where("u2f.id IS NULL AND users.otp_required_for_login = ?", false)
424
  end
A
Andrey Kumanyaev 已提交
425

426 427 428
  #
  # Class methods
  #
A
Andrey Kumanyaev 已提交
429
  class << self
430 431 432 433 434
    # Devise method overridden to allow support for dynamic password lengths
    def password_length
      Gitlab::CurrentSettings.minimum_password_length..Devise.password_length.max
    end

435 436 437 438 439
    # Generate a random password that conforms to the current password length settings
    def random_password
      Devise.friendly_token(password_length.max)
    end

440
    # Devise method overridden to allow sign in with email or username
441 442 443
    def find_for_database_authentication(warden_conditions)
      conditions = warden_conditions.dup
      if login = conditions.delete(:login)
444
        where(conditions).find_by("lower(username) = :value OR lower(email) = :value", value: login.downcase.strip)
445
      else
G
Gabriel Mazetto 已提交
446
        find_by(conditions)
447 448
      end
    end
449

450
    def sort_by_attribute(method)
451 452 453
      order_method = method || 'id_desc'

      case order_method.to_s
454 455
      when 'recent_sign_in' then order_recent_sign_in
      when 'oldest_sign_in' then order_oldest_sign_in
456 457
      when 'last_activity_on_desc' then order_recent_last_activity
      when 'last_activity_on_asc' then order_oldest_last_activity
458
      else
459
        order_by(order_method)
V
Valery Sizov 已提交
460 461 462
      end
    end

463
    def for_github_id(id)
464
      joins(:identities).merge(Identity.with_extern_uid(:github, id))
465 466
    end

467
    # Find a User by their primary email or any associated secondary email
468
    def find_by_any_email(email, confirmed: false)
469 470
      return unless email

471
      by_any_email(email, confirmed: confirmed).take
Y
Yorick Peterse 已提交
472 473
    end

474 475 476 477 478 479 480 481 482
    # Returns a relation containing all the users for the given email addresses
    #
    # @param emails [String, Array<String>] email addresses to check
    # @param confirmed [Boolean] Only return users where the email is confirmed
    def by_any_email(emails, confirmed: false)
      emails = Array(emails).map(&:downcase)

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

484
      from_emails = joins(:emails).where(emails: { email: emails })
485
      from_emails = from_emails.confirmed.merge(Email.confirmed) if confirmed
Y
Yorick Peterse 已提交
486

487 488 489 490 491 492
      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)
493
    end
494

495 496 497 498 499 500
    def find_by_private_commit_email(email)
      user_id = Gitlab::PrivateCommitEmail.user_id_for_email(email)

      find_by(id: user_id)
    end

501
    def filter_items(filter_name)
A
Andrey Kumanyaev 已提交
502
      case filter_name
503
      when 'admins'
504
        admins
505
      when 'blocked'
506
        blocked
507
      when 'two_factor_disabled'
508
        without_two_factor
509
      when 'two_factor_enabled'
510
        with_two_factor
511
      when 'wop'
512
        without_projects
513
      when 'external'
514
        external
515 516
      when 'deactivated'
        deactivated
A
Andrey Kumanyaev 已提交
517
      else
518
        active_without_ghosts
A
Andrey Kumanyaev 已提交
519
      end
520 521
    end

522 523
    # Searches users matching the given query.
    #
524
    # This method uses ILIKE on PostgreSQL.
525 526 527 528
    #
    # query - The search query as a String
    #
    # Returns an ActiveRecord::Relation.
529
    def search(query, **options)
530
      query = query&.delete_prefix('@')
531 532
      return none if query.blank?

533 534
      query = query.downcase

535 536
      order = <<~SQL
        CASE
537 538 539
          WHEN users.name = :query THEN 0
          WHEN users.username = :query THEN 1
          WHEN users.email = :query THEN 2
540 541 542 543
          ELSE 3
        END
      SQL

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

546
      where(
547 548
        fuzzy_arel_match(:name, query, lower_exact_match: true)
          .or(fuzzy_arel_match(:username, query, lower_exact_match: true))
549
          .or(arel_table[:email].eq(query))
550
      ).reorder(sanitized_order_sql, :name)
A
Andrey Kumanyaev 已提交
551
    end
552

Y
Yorick Peterse 已提交
553 554 555 556 557 558 559 560 561 562 563 564
    # 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.
    def where_not_in(users = nil)
      users ? where.not(id: users) : all
    end

    def reorder_by_name
      reorder(:name)
    end

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

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

572 573
      query = query.downcase

574
      email_table = Email.arel_table
575 576
      matched_by_emails_user_ids = email_table
        .project(email_table[:user_id])
577
        .where(email_table[:email].eq(query))
578 579

      where(
580 581
        fuzzy_arel_match(:name, query)
          .or(fuzzy_arel_match(:username, query))
582
          .or(arel_table[:email].eq(query))
583
          .or(arel_table[:id].in(matched_by_emails_user_ids))
584 585 586
      )
    end

587
    def by_login(login)
588
      return unless login
589

590
      if login.include?('@')
591 592 593 594
        unscoped.iwhere(email: login).take
      else
        unscoped.iwhere(username: login).take
      end
595 596
    end

597
    def find_by_username(username)
598
      by_username(username).take
599 600
    end

R
Robert Speicher 已提交
601
    def find_by_username!(username)
602
      by_username(username).take!
R
Robert Speicher 已提交
603 604
    end

Y
Yorick Peterse 已提交
605 606
    # Returns a user for the given SSH key.
    def find_by_ssh_key_id(key_id)
607
      find_by('EXISTS (?)', Key.select(1).where('keys.user_id = users.id').where(id: key_id))
Y
Yorick Peterse 已提交
608 609
    end

610
    def find_by_full_path(path, follow_redirects: false)
611 612
      namespace = Namespace.for_user.find_by_full_path(path, follow_redirects: follow_redirects)
      namespace&.owner
613 614
    end

615 616 617
    def reference_prefix
      '@'
    end
618 619 620

    # Pattern used to extract `@user` user references from text
    def reference_pattern
621 622 623 624 625 626
      @reference_pattern ||=
        %r{
          (?<!\w)
          #{Regexp.escape(reference_prefix)}
          (?<user>#{Gitlab::PathRegex::FULL_NAMESPACE_FORMAT_REGEX})
        }x
627
    end
628 629 630 631

    # Return (create if necessary) the ghost user. The ghost user
    # owns records previously belonging to deleted users.
    def ghost
632
      email = 'ghost%s@example.com'
633
      unique_internal(where(user_type: :ghost), 'ghost', email) do |u|
634
        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.')
635 636
        u.name = 'Ghost User'
      end
637
    end
638

639 640 641
    def alert_bot
      email_pattern = "alert%s@#{Settings.gitlab.host}"

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

649 650 651 652 653 654 655 656 657 658
    def migration_bot
      email_pattern = "noreply+gitlab-migration-bot%s@#{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

659 660 661 662 663 664
    def support_bot
      email_pattern = "support%s@#{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'
665
        u.avatar = bot_avatar(image: 'support-bot.png')
666 667 668
      end
    end

669 670 671 672 673 674 675 676 677
    # Return true if there is only single non-internal user in the deployment,
    # ghost user is ignored.
    def single_user?
      User.non_internal.limit(2).count == 1
    end

    def single_user
      User.non_internal.first if single_user?
    end
V
vsizov 已提交
678
  end
R
randx 已提交
679

680 681 682
  #
  # Instance methods
  #
683

684 685 686 687
  def full_path
    username
  end

688 689 690 691
  def to_param
    username
  end

J
Jarka Kadlecova 已提交
692
  def to_reference(_from = nil, target_project: nil, full: nil)
693 694 695
    "#{self.class.reference_prefix}#{username}"
  end

696 697
  def skip_confirmation=(bool)
    skip_confirmation! if bool
D
Daniel Juarez 已提交
698 699 700 701
  end

  def skip_reconfirmation=(bool)
    skip_reconfirmation! if bool
R
randx 已提交
702
  end
703

704
  def generate_reset_token
M
Marin Jankovski 已提交
705
    @reset_token, enc = Devise.token_generator.generate(self.class, :reset_password_token)
706 707

    self.reset_password_token   = enc
708
    self.reset_password_sent_at = Time.current.utc
709

M
Marin Jankovski 已提交
710
    @reset_token
711 712
  end

713 714 715 716
  def recently_sent_password_reset?
    reset_password_sent_at.present? && reset_password_sent_at >= 1.minute.ago
  end

T
Toon Claes 已提交
717 718 719 720 721 722 723 724
  def remember_me!
    super if ::Gitlab::Database.read_write?
  end

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

R
Robert Speicher 已提交
725
  def disable_two_factor!
726
    transaction do
L
Lin Jen-Shin 已提交
727
      update(
728 729 730 731 732 733 734
        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
      )
735
      self.u2f_registrations.destroy_all # rubocop: disable Cop/DestroyAll
736 737 738 739 740 741 742 743
    end
  end

  def two_factor_enabled?
    two_factor_otp_enabled? || two_factor_u2f_enabled?
  end

  def two_factor_otp_enabled?
744
    otp_required_for_login?
745 746 747
  end

  def two_factor_u2f_enabled?
748 749 750 751 752
    if u2f_registrations.loaded?
      u2f_registrations.any?
    else
      u2f_registrations.exists?
    end
R
Robert Speicher 已提交
753 754
  end

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

761 762 763 764 765 766 767
  # 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.
  def will_save_change_to_login?
    will_save_change_to_username? || will_save_change_to_email?
  end

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

774
  def owns_notification_email
775
    return if new_record? || temp_oauth_email?
776

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

780
  def owns_public_email
781
    return if public_email.blank?
782

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

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

789
    errors.add(:commit_email, _("is not an email you own")) unless verified_emails.include?(commit_email)
790 791 792 793 794 795 796 797
  end

  # 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.

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

800 801 802 803
    if super == Gitlab::PrivateCommitEmail::TOKEN
      return private_commit_email
    end

804 805 806 807 808 809 810 811 812 813 814 815
    # The commit email is the same as the primary email if undefined
    super.presence || self.email
  end

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

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

816 817 818 819
  def private_commit_email
    Gitlab::PrivateCommitEmail.for_user(self)
  end

A
Alexandra 已提交
820 821 822 823 824
  # see if the new email is already a verified secondary email
  def check_for_verified_email
    skip_reconfirmation! if emails.confirmed.where(email: self.email).any?
  end

825
  # Note: the use of the Emails services will cause `saves` on the user object, running
826
  # through the callbacks again and can have side effects, such as the `previous_changes`
827 828 829
  # 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
830
  # rubocop: disable CodeReuse/ServiceClass
831
  def update_emails_with_primary_email(previous_confirmed_at, previous_email)
832
    primary_email_record = emails.find_by(email: email)
833
    Emails::DestroyService.new(self, user: self).execute(primary_email_record) if primary_email_record
834

835 836
    # 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
837 838 839
    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
840
  end
841
  # rubocop: enable CodeReuse/ServiceClass
842

843
  def update_invalid_gpg_signatures
844
    gpg_keys.each(&:update_invalid_gpg_signatures)
845 846
  end

847
  # Returns the groups a user has access to, either through a membership or a project authorization
848
  def authorized_groups
849 850 851 852 853 854
    Group.unscoped do
      Group.from_union([
        groups,
        authorized_projects.joins(:namespace).select('namespaces.*')
      ])
    end
855 856
  end

857 858
  # Returns the groups a user is a member of, either directly or through a parent group
  def membership_groups
859
    Gitlab::ObjectHierarchy.new(groups).base_and_descendants
860 861
  end

862 863
  # Returns a relation of groups the user has access to, including their parent
  # and child groups (recursively).
864
  def all_expanded_groups
865
    Gitlab::ObjectHierarchy.new(groups).all_objects
866 867 868 869 870 871
  end

  def expanded_groups_requiring_two_factor_authentication
    all_expanded_groups.where(require_two_factor_authentication: true)
  end

872
  # rubocop: disable CodeReuse/ServiceClass
873
  def refresh_authorized_projects
874 875
    Users::RefreshAuthorizedProjectsService.new(self).execute
  end
876
  # rubocop: enable CodeReuse/ServiceClass
877 878

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

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

    if min_access_level
888 889
      projects = projects
        .where('project_authorizations.access_level >= ?', min_access_level)
890
    end
891 892 893 894 895 896

    projects
  end

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

899 900
  # Typically used in conjunction with projects table to get projects
  # a user has been given access to.
901 902
  # The param `related_project_column` is the column to compare to the
  # project_authorizations. By default is projects.id
903 904 905
  #
  # Example use:
  # `Project.where('EXISTS(?)', user.authorizations_for_projects)`
906 907 908 909
  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}")
910 911 912 913

    return authorizations unless min_access_level.present?

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

916 917 918 919 920 921 922 923 924 925
  # 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.
  def projects_with_reporter_access_limited_to(projects)
    authorized_projects(Gitlab::Access::REPORTER).where(id: projects)
  end

926
  def owned_projects
927 928 929 930 931 932 933 934 935
    @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
    )
936 937
  end

938 939 940 941
  # 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.
  def projects_where_can_admin_issues
F
Felipe Artur 已提交
942
    authorized_projects(Gitlab::Access::REPORTER).non_archived.with_issues_enabled
943 944
  end

945
  # rubocop: disable CodeReuse/ServiceClass
D
Dmitriy Zaporozhets 已提交
946
  def require_ssh_key?
Y
Yorick Peterse 已提交
947 948 949
    count = Users::KeysCountService.new(self).count

    count.zero? && Gitlab::ProtocolAccess.allowed?('ssh')
D
Dmitriy Zaporozhets 已提交
950
  end
951
  # rubocop: enable CodeReuse/ServiceClass
D
Dmitriy Zaporozhets 已提交
952

953 954 955 956 957 958
  def require_password_creation_for_web?
    allow_password_authentication_for_web? && password_automatically_set?
  end

  def require_password_creation_for_git?
    allow_password_authentication_for_git? && password_automatically_set?
959 960
  end

961
  def require_personal_access_token_creation_for_git_auth?
962
    return false if allow_password_authentication_for_git? || ldap_user?
963 964

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

967 968 969 970
  def require_extra_setup_for_git_auth?
    require_password_creation_for_git? || require_personal_access_token_creation_for_git_auth?
  end

971
  def allow_password_authentication?
972 973 974 975
    allow_password_authentication_for_web? || allow_password_authentication_for_git?
  end

  def allow_password_authentication_for_web?
976
    Gitlab::CurrentSettings.password_authentication_enabled_for_web? && !ldap_user?
977 978 979
  end

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

983
  def can_change_username?
984
    gitlab_config.username_changing_enabled
985 986
  end

D
Dmitriy Zaporozhets 已提交
987
  def can_create_project?
988
    projects_limit_left > 0
D
Dmitriy Zaporozhets 已提交
989 990 991
  end

  def can_create_group?
992
    can?(:create_group)
D
Dmitriy Zaporozhets 已提交
993 994
  end

995 996 997 998
  def can_select_namespace?
    several_namespaces? || admin
  end

999
  def can?(action, subject = :global)
H
http://jneen.net/ 已提交
1000
    Ability.allowed?(self, action, subject)
D
Dmitriy Zaporozhets 已提交
1001 1002
  end

1003 1004 1005 1006
  def confirm_deletion_with_password?
    !password_automatically_set? && allow_password_authentication?
  end

D
Dmitriy Zaporozhets 已提交
1007
  def first_name
1008 1009 1010 1011 1012 1013 1014 1015 1016
    read_attribute(:first_name) || begin
      name.split(' ').first unless name.blank?
    end
  end

  def last_name
    read_attribute(:last_name) || begin
      name.split(' ').drop(1).join(' ') unless name.blank?
    end
D
Dmitriy Zaporozhets 已提交
1017 1018
  end

1019
  def projects_limit_left
1020 1021 1022
    projects_limit - personal_projects_count
  end

1023
  # rubocop: disable CodeReuse/ServiceClass
1024 1025
  def recent_push(project = nil)
    service = Users::LastPushEventService.new(self)
D
Dmitriy Zaporozhets 已提交
1026

1027 1028 1029 1030
    if project
      service.last_event_for_project(project)
    else
      service.last_event_for_user
1031
    end
D
Dmitriy Zaporozhets 已提交
1032
  end
1033
  # rubocop: enable CodeReuse/ServiceClass
D
Dmitriy Zaporozhets 已提交
1034 1035

  def several_namespaces?
G
Gosia Ksionek 已提交
1036 1037 1038 1039 1040 1041
    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?
D
Dmitriy Zaporozhets 已提交
1042 1043 1044 1045 1046
  end

  def namespace_id
    namespace.try :id
  end
1047

1048 1049 1050
  def name_with_username
    "#{name} (#{username})"
  end
D
Dmitriy Zaporozhets 已提交
1051

1052
  def already_forked?(project)
1053 1054 1055
    !!fork_of(project)
  end

1056
  def fork_of(project)
1057
    namespace.find_fork_of(project)
1058
  end
1059 1060

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

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

1072 1073 1074 1075
  def matches_identity?(provider, extern_uid)
    identities.where(provider: provider, extern_uid: extern_uid).exists?
  end

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

T
Thiago Presa 已提交
1080
  def highest_role
1081
    user_highest_role&.highest_access_level || Gitlab::Access::NO_ACCESS
T
Thiago Presa 已提交
1082 1083
  end

1084
  def accessible_deploy_keys
1085 1086 1087 1088
    DeployKey.from_union([
      DeployKey.where(id: project_deploy_keys.select(:deploy_key_id)),
      DeployKey.are_public
    ])
1089
  end
1090 1091

  def created_by
S
skv 已提交
1092
    User.find_by(id: created_by_id) if created_by_id
1093
  end
1094 1095

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

1102
  def set_notification_email
1103
    if notification_email.blank? || all_emails.exclude?(notification_email)
1104
      self.notification_email = email
1105 1106 1107
    end
  end

1108
  def set_public_email
1109
    if public_email.blank? || all_emails.exclude?(public_email)
1110
      self.public_email = ''
1111 1112 1113
    end
  end

1114 1115 1116 1117 1118 1119
  def set_commit_email
    if commit_email.blank? || verified_emails.exclude?(commit_email)
      self.commit_email = nil
    end
  end

1120
  def update_secondary_emails!
1121 1122
    set_notification_email
    set_public_email
1123 1124
    set_commit_email
    save if notification_email_changed? || public_email_changed? || commit_email_changed?
1125 1126
  end

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

1133
    self.projects_limit = Gitlab::CurrentSettings.default_projects_limit
1134 1135
  end

1136
  def requires_ldap_check?
1137 1138 1139
    if !Gitlab.config.ldap.enabled
      false
    elsif ldap_user?
1140
      !last_credential_check_at || (last_credential_check_at + ldap_sync_time) < Time.current
1141 1142 1143 1144 1145
    else
      false
    end
  end

1146 1147 1148 1149 1150
  def ldap_sync_time
    # This number resides in this method so it can be redefined in EE.
    1.hour
  end

J
Jacob Vosmaer 已提交
1151 1152 1153 1154 1155 1156 1157
  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

1158 1159 1160 1161 1162
  def solo_owned_groups
    @solo_owned_groups ||= owned_groups.select do |group|
      group.owners == [self]
    end
  end
1163 1164

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

    self
1170
  end
1171

1172 1173 1174 1175
  def can_leave_project?(project)
    project.namespace != namespace &&
      project.project_member(self)
  end
1176

J
Jerome Dalbert 已提交
1177
  def full_website_url
1178
    return "http://#{website_url}" if website_url !~ %r{\Ahttps?://}
J
Jerome Dalbert 已提交
1179 1180 1181 1182 1183

    website_url
  end

  def short_website_url
1184
    website_url.sub(%r{\Ahttps?://}, '')
J
Jerome Dalbert 已提交
1185
  end
G
GitLab 已提交
1186

1187
  def all_ssh_keys
1188
    keys.map(&:publishable_key)
1189
  end
1190 1191

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

1195
  # rubocop: disable CodeReuse/ServiceClass
1196
  def avatar_url(size: nil, scale: 2, **args)
1197
    GravatarService.new.execute(email, size, scale, username: username)
1198
  end
1199
  # rubocop: enable CodeReuse/ServiceClass
D
Dmitriy Zaporozhets 已提交
1200

1201 1202 1203 1204
  def primary_email_verified?
    confirmed? && !temp_oauth_email?
  end

1205 1206 1207 1208 1209 1210 1211 1212 1213 1214
  def accept_pending_invitations!
    pending_invitations.select do |member|
      member.accept_invite!(self)
    end
  end

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

1215
  def all_emails(include_private_email: true)
1216
    all_emails = []
1217
    all_emails << email unless temp_oauth_email?
1218
    all_emails << private_commit_email if include_private_email
1219
    all_emails.concat(emails.map(&:email))
1220
    all_emails
1221 1222
  end

1223
  def verified_emails(include_private_email: true)
1224
    verified_emails = []
1225
    verified_emails << email if primary_email_verified?
1226
    verified_emails << private_commit_email if include_private_email
1227
    verified_emails.concat(emails.confirmed.pluck(:email))
1228 1229 1230
    verified_emails
  end

1231 1232 1233 1234 1235 1236
  def public_verified_emails
    emails = verified_emails(include_private_email: false)
    emails << email unless temp_oauth_email?
    emails.uniq
  end

1237 1238 1239 1240 1241 1242 1243 1244 1245 1246
  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

1247
  def verified_email?(check_email)
1248
    downcased = check_email.downcase
1249

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

1254
    verified_emails.include?(check_email.downcase)
1255 1256
  end

K
Kirill Zaitsev 已提交
1257 1258 1259 1260
  def hook_attrs
    {
      name: name,
      username: username,
1261 1262
      avatar_url: avatar_url(only_path: false),
      email: email
K
Kirill Zaitsev 已提交
1263 1264 1265
    }
  end

D
Dmitriy Zaporozhets 已提交
1266
  def ensure_namespace_correct
1267
    if namespace
1268 1269
      namespace.path = username if username_changed?
      namespace.name = name if name_changed?
1270
    else
1271
      build_namespace(path: username, name: name)
D
Dmitriy Zaporozhets 已提交
1272 1273 1274
    end
  end

1275 1276 1277 1278 1279 1280 1281
  # Temporary, will be removed when bio is fully migrated
  def ensure_bio_is_assigned_to_user_details
    return if Feature.disabled?(:migrate_bio_to_user_details, default_enabled: true)

    user_detail.bio = bio.to_s[0...255] # bio can be NULL in users, but cannot be NULL in user_details
  end

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

1287 1288 1289 1290
  def username_changed_hook
    system_hook_service.execute_hooks_for(self, :rename)
  end

D
Dmitriy Zaporozhets 已提交
1291
  def post_destroy_hook
1292
    log_info("User \"#{name}\" (#{email})  was removed")
D
Douwe Maan 已提交
1293

D
Dmitriy Zaporozhets 已提交
1294 1295 1296
    system_hook_service.execute_hooks_for(self, :destroy)
  end

1297
  # rubocop: disable CodeReuse/ServiceClass
Y
Yorick Peterse 已提交
1298 1299 1300
  def remove_key_cache
    Users::KeysCountService.new(self).delete_cache
  end
1301
  # rubocop: enable CodeReuse/ServiceClass
Y
Yorick Peterse 已提交
1302

N
Nick Thomas 已提交
1303 1304
  def delete_async(deleted_by:, params: {})
    block if params[:hard_delete]
1305
    DeleteUserWorker.perform_async(deleted_by.id, id, params.to_h)
N
Nick Thomas 已提交
1306 1307
  end

1308
  # rubocop: disable CodeReuse/ServiceClass
D
Dmitriy Zaporozhets 已提交
1309
  def notification_service
D
Dmitriy Zaporozhets 已提交
1310 1311
    NotificationService.new
  end
1312
  # rubocop: enable CodeReuse/ServiceClass
D
Dmitriy Zaporozhets 已提交
1313

1314
  def log_info(message)
D
Dmitriy Zaporozhets 已提交
1315 1316 1317
    Gitlab::AppLogger.info message
  end

1318
  # rubocop: disable CodeReuse/ServiceClass
D
Dmitriy Zaporozhets 已提交
1319 1320 1321
  def system_hook_service
    SystemHooksService.new
  end
1322
  # rubocop: enable CodeReuse/ServiceClass
C
Ciro Santilli 已提交
1323 1324

  def starred?(project)
V
Valery Sizov 已提交
1325
    starred_projects.exists?(project.id)
C
Ciro Santilli 已提交
1326 1327 1328
  end

  def toggle_star(project)
1329
    UsersStarProject.transaction do
1330 1331
      user_star_project = users_star_projects
          .where(project: project, user: self).lock(true).first
1332 1333 1334 1335 1336 1337

      if user_star_project
        user_star_project.destroy
      else
        UsersStarProject.create!(project: project, user: self)
      end
C
Ciro Santilli 已提交
1338 1339
    end
  end
1340 1341

  def manageable_namespaces
1342 1343 1344
    @manageable_namespaces ||= [namespace] + manageable_groups
  end

G
Gosia Ksionek 已提交
1345 1346 1347 1348 1349 1350 1351 1352 1353 1354 1355 1356
  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
1357
  end
D
Dmitriy Zaporozhets 已提交
1358

G
Gosia Ksionek 已提交
1359 1360 1361 1362
  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')
1363 1364
  end

1365 1366 1367 1368 1369 1370
  def namespaces
    namespace_ids = groups.pluck(:id)
    namespace_ids.push(namespace.id)
    Namespace.where(id: namespace_ids)
  end

D
Dmitriy Zaporozhets 已提交
1371
  def oauth_authorized_tokens
1372
    Doorkeeper::AccessToken.where(resource_owner_id: id, revoked_at: nil)
D
Dmitriy Zaporozhets 已提交
1373
  end
1374

1375 1376 1377 1378 1379 1380 1381 1382 1383
  # 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
1384
  # ms on a database with a similar size to GitLab.com's database. On the other
1385 1386
  # hand, using a subquery means we can get the exact same data in about 40 ms.
  def contributed_projects
1387 1388
    events = Event.select(:project_id)
      .contributions.where(author_id: self)
1389
      .where("created_at > ?", Time.current - 1.year)
1390
      .distinct
1391
      .reorder(nil)
1392 1393

    Project.where(id: events)
1394
  end
1395

1396 1397 1398
  def can_be_removed?
    !solo_owned_groups.present?
  end
1399

1400 1401
  def ci_owned_runners
    @ci_owned_runners ||= begin
1402
      project_runners = Ci::RunnerProject
1403
        .where(project: authorized_projects(Gitlab::Access::MAINTAINER))
1404 1405
        .joins(:runner)
        .select('ci_runners.*')
1406

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

1412
      Ci::Runner.from_union([project_runners, group_runners])
K
Kamil Trzcinski 已提交
1413
    end
1414
  end
1415

1416 1417 1418 1419 1420
  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

1421
  def notification_settings_for(source, inherit: false)
1422
    if notification_settings.loaded?
1423 1424 1425 1426
      notification_settings.find do |notification|
        notification.source_type == source.class.base_class.name &&
          notification.source_id == source.id
      end
1427
    else
1428 1429 1430 1431 1432 1433 1434 1435 1436 1437 1438 1439 1440 1441
      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
1442
    end
1443 1444
  end

1445 1446 1447
  # Lazy load global notification setting
  # Initializes User setting with Participating level if setting not persisted
  def global_notification_setting
1448 1449 1450
    return @global_notification_setting if defined?(@global_notification_setting)

    @global_notification_setting = notification_settings.find_or_initialize_by(source: nil)
L
Lin Jen-Shin 已提交
1451
    @global_notification_setting.update(level: NotificationSetting.levels[DEFAULT_NOTIFICATION_LEVEL]) unless @global_notification_setting.persisted?
1452 1453

    @global_notification_setting
1454 1455
  end

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

J
Josh Frye 已提交
1462
  def assigned_open_issues_count(force: false)
1463
    Rails.cache.fetch(['users', id, 'assigned_open_issues_count'], force: force, expires_in: 20.minutes) do
1464
      IssuesFinder.new(self, assignee_id: self.id, state: 'opened', non_archived: true).execute.count
1465
    end
1466 1467
  end

1468 1469 1470 1471 1472 1473 1474 1475 1476 1477 1478 1479
  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

  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

A
Andreas Brandl 已提交
1480 1481 1482 1483 1484 1485
  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

1486 1487 1488 1489 1490
  def update_todos_count_cache
    todos_done_count(force: true)
    todos_pending_count(force: true)
  end

1491
  def invalidate_cache_counts
1492 1493
    invalidate_issue_cache_counts
    invalidate_merge_request_cache_counts
1494 1495
    invalidate_todos_done_count
    invalidate_todos_pending_count
A
Andreas Brandl 已提交
1496
    invalidate_personal_projects_count
1497 1498 1499
  end

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

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

1507 1508
  def invalidate_todos_done_count
    Rails.cache.delete(['users', id, 'todos_done_count'])
P
Paco Guzman 已提交
1509 1510
  end

1511 1512
  def invalidate_todos_pending_count
    Rails.cache.delete(['users', id, 'todos_pending_count'])
P
Paco Guzman 已提交
1513 1514
  end

A
Andreas Brandl 已提交
1515 1516 1517 1518
  def invalidate_personal_projects_count
    Rails.cache.delete(['users', id, 'personal_projects_count'])
  end

1519 1520 1521 1522
  # 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:
1523
  #   <https://github.com/plataformatec/devise/blob/v4.7.1/lib/devise/models/lockable.rb#L104>
1524
  #
1525
  # rubocop: disable CodeReuse/ServiceClass
1526
  def increment_failed_attempts!
1527 1528
    return if ::Gitlab::Database.read_only?

1529
    increment_failed_attempts
1530

1531 1532 1533
    if attempts_exceeded?
      lock_access! unless access_locked?
    else
J
James Lopez 已提交
1534
      Users::UpdateService.new(self, user: self).execute(validate: false)
1535 1536
    end
  end
1537
  # rubocop: enable CodeReuse/ServiceClass
1538

1539 1540 1541 1542 1543 1544 1545 1546 1547
  def access_level
    if admin?
      :admin
    else
      :regular
    end
  end

  def access_level=(new_level)
D
Douwe Maan 已提交
1548 1549
    new_level = new_level.to_s
    return unless %w(admin regular).include?(new_level)
1550

D
Douwe Maan 已提交
1551 1552
    self.admin = (new_level == 'admin')
  end
1553

1554
  def can_read_all_resources?
1555
    can?(:read_all_resources)
1556 1557
  end

1558
  def update_two_factor_requirement
1559
    periods = expanded_groups_requiring_two_factor_authentication.pluck(:two_factor_grace_period)
1560

1561
    self.require_two_factor_authentication_from_group = periods.any?
1562 1563 1564 1565 1566
    self.two_factor_grace_period = periods.min || User.column_defaults['two_factor_grace_period']

    save
  end

1567
  # each existing user needs to have an `feed_token`.
A
Alexis Reigel 已提交
1568 1569
  # we do this on read since migrating all existing users is not a feasible
  # solution.
1570 1571
  def feed_token
    ensure_feed_token!
A
Alexis Reigel 已提交
1572 1573
  end

1574 1575 1576 1577 1578 1579 1580
  # 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.
  def static_object_token
    ensure_static_object_token!
  end

1581 1582 1583 1584 1585 1586 1587 1588 1589 1590 1591 1592 1593 1594 1595 1596
  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

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

B
Brian Neel 已提交
1597 1598
  # override, from Devise
  def lock_access!
1599
    Gitlab::AppLogger.info("Account Locked: username=#{username}")
B
Brian Neel 已提交
1600 1601 1602
    super
  end

1603 1604 1605 1606 1607 1608 1609 1610 1611 1612 1613 1614 1615 1616 1617 1618 1619 1620 1621 1622 1623 1624 1625 1626 1627 1628 1629 1630
  # Determine the maximum access level for a group of projects in bulk.
  #
  # Returns a Hash mapping project ID -> maximum access level.
  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

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

  # Determine the maximum access level for a group of groups in bulk.
  #
  # Returns a Hash mapping project ID -> maximum access level.
  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

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

1631 1632 1633 1634
  def terms_accepted?
    accepted_term_id.present?
  end

1635 1636 1637 1638 1639
  def required_terms_not_accepted?
    Gitlab::CurrentSettings.current_application_settings.enforce_terms? &&
      !terms_accepted?
  end

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

1644 1645 1646 1647 1648
  # Avoid migrations only building user preference object when needed.
  def user_preference
    super.presence || build_user_preference
  end

1649 1650 1651 1652
  def user_detail
    super.presence || build_user_detail
  end

1653 1654 1655 1656
  def pending_todo_for(target)
    todos.find_by(target: target, state: :pending)
  end

1657
  def password_expired?
1658
    !!(password_expires_at && password_expires_at < Time.current)
1659 1660
  end

1661 1662 1663 1664 1665 1666 1667 1668 1669 1670 1671
  def can_be_deactivated?
    active? && no_recent_activity?
  end

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

    [last_activity, last_sign_in].compact.max
  end

1672 1673 1674 1675 1676 1677 1678 1679 1680 1681 1682 1683 1684 1685
  # Below is used for the signup_flow experiment. Should be removed
  # when experiment finishes.
  # See https://gitlab.com/gitlab-org/growth/engineering/issues/64
  REQUIRES_ROLE_VALUE = 99

  def role_required?
    role_before_type_cast == REQUIRES_ROLE_VALUE
  end

  def set_role_required!
    update_column(:role, REQUIRES_ROLE_VALUE)
  end
  # End of signup_flow experiment methods

1686 1687 1688 1689 1690 1691 1692
  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

1693 1694 1695 1696 1697
  # Load the current highest access by looking directly at the user's memberships
  def current_highest_access_level
    members.non_request.maximum(:access_level)
  end

1698 1699 1700 1701
  def confirmation_required_on_sign_in?
    !confirmed? && !confirmation_period_valid?
  end

1702 1703 1704 1705
  def impersonated?
    impersonator.present?
  end

1706 1707 1708 1709
  protected

  # override, from Devise::Validatable
  def password_required?
1710
    return false if internal? || project_bot?
1711

1712 1713 1714 1715 1716 1717 1718
    super
  end

  # override from Devise::Confirmable
  def confirmation_period_valid?
    return false if Feature.disabled?(:soft_email_confirmation)

1719 1720 1721
    super
  end

1722 1723 1724 1725 1726 1727 1728 1729 1730 1731 1732 1733 1734 1735 1736 1737 1738
  # This is copied from Devise::Models::TwoFactorAuthenticatable#consume_otp!
  #
  # An OTP cannot be used more than once in a given timestep
  # Storing timestep of last valid OTP is sufficient to satisfy this requirement
  #
  # See:
  #   <https://github.com/tinfoil/devise-two-factor/blob/master/lib/devise_two_factor/models/two_factor_authenticatable.rb#L66>
  #
  def consume_otp!
    if self.consumed_timestep != current_otp_timestep
      self.consumed_timestep = current_otp_timestep
      return Gitlab::Database.read_only? ? true : save(validate: false)
    end

    false
  end

1739 1740
  private

1741 1742 1743 1744 1745 1746
  def default_private_profile_to_false
    return unless private_profile_changed? && private_profile.nil?

    self.private_profile = false
  end

1747 1748 1749 1750 1751
  def has_current_license?
    false
  end

  def consented_usage_stats?
1752 1753 1754 1755 1756 1757 1758 1759
    # Bypass the cache here because it's possible the admin enabled the
    # usage ping, and we don't want to annoy the user again if they
    # already set the value. This is a bit of hack, but the alternative
    # would be to put in a more complex cache invalidation step. Since
    # this call only gets called in the uncommon situation where the
    # user is an admin and the only user in the instance, this shouldn't
    # cause too much load on the system.
    ApplicationSetting.current_without_cache&.usage_stats_set_by_user_id == self.id
1760 1761
  end

1762 1763 1764 1765 1766
  def ensure_user_rights_and_limits
    if external?
      self.can_create_group = false
      self.projects_limit   = 0
    else
1767 1768
      # Only revert these back to the default if they weren't specifically changed in this update.
      self.can_create_group = gitlab_config.default_can_create_group unless can_create_group_changed?
1769
      self.projects_limit = Gitlab::CurrentSettings.default_projects_limit unless projects_limit_changed?
1770
    end
Z
Zeger-Jan van de Weg 已提交
1771
  end
1772

1773 1774 1775 1776
  def signup_domain_valid?
    valid = true
    error = nil

1777 1778
    if Gitlab::CurrentSettings.domain_blacklist_enabled?
      blocked_domains = Gitlab::CurrentSettings.domain_blacklist
1779
      if domain_matches?(blocked_domains, email)
1780 1781 1782 1783 1784
        error = 'is not from an allowed domain.'
        valid = false
      end
    end

1785
    allowed_domains = Gitlab::CurrentSettings.domain_whitelist
1786
    unless allowed_domains.blank?
1787
      if domain_matches?(allowed_domains, email)
1788 1789
        valid = true
      else
D
dev-chris 已提交
1790
        error = "domain is not authorized for sign-up"
1791 1792 1793 1794
        valid = false
      end
    end

1795
    errors.add(:email, error) unless valid
1796 1797 1798

    valid
  end
1799

1800
  def domain_matches?(email_domains, email)
1801 1802 1803 1804 1805 1806 1807
    signup_domain = Mail::Address.new(email).domain
    email_domains.any? do |domain|
      escaped = Regexp.escape(domain).gsub('\*', '.*?')
      regexp = Regexp.new "^#{escaped}$", Regexp::IGNORECASE
      signup_domain =~ regexp
    end
  end
1808

1809 1810 1811 1812 1813 1814 1815
  def check_email_restrictions
    return unless Gitlab::CurrentSettings.email_restrictions_enabled?

    restrictions = Gitlab::CurrentSettings.email_restrictions
    return if restrictions.blank?

    if Gitlab::UntrustedRegexp.new(restrictions).match?(email)
1816
      errors.add(:email, _('is not allowed. Try again with a different email address, or contact your GitLab admin.'))
1817 1818 1819
    end
  end

G
Gosia Ksionek 已提交
1820 1821 1822 1823 1824 1825 1826 1827 1828 1829 1830
  def groups_with_developer_maintainer_project_access
    project_creation_levels = [::Gitlab::Access::DEVELOPER_MAINTAINER_PROJECT_ACCESS]

    if ::Gitlab::CurrentSettings.default_project_creation == ::Gitlab::Access::DEVELOPER_MAINTAINER_PROJECT_ACCESS
      project_creation_levels << nil
    end

    developer_groups_hierarchy = ::Gitlab::ObjectHierarchy.new(developer_groups).base_and_descendants
    ::Group.where(id: developer_groups_hierarchy.select(:id),
                  project_creation_level: project_creation_levels)
  end
1831 1832 1833 1834

  def no_recent_activity?
    last_active_at.to_i <= MINIMUM_INACTIVE_DAYS.days.ago.to_i
  end
1835

1836 1837
  def update_highest_role?
    return false unless persisted?
1838

1839
    (previous_changes.keys & %w(state user_type)).any?
1840 1841 1842 1843
  end

  def update_highest_role_attribute
    id
1844
  end
G
gitlabhq 已提交
1845
end
1846 1847

User.prepend_if_ee('EE::User')