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

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

G
gitlabhq 已提交
5
class User < ActiveRecord::Base
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 IgnorableColumn
17
  include FeatureGate
18
  include CreatedAtFilterable
19
  include BulkMemberAccessLoad
20
  include BlocksJsonSerialization
J
Jan Provaznik 已提交
21
  include WithUploads
Y
Yorick Peterse 已提交
22
  include OptionallySearch
23
  include FromUnion
24

25 26
  DEFAULT_NOTIFICATION_LEVEL = :participating

27 28
  ignore_column :external_email
  ignore_column :email_provider
29
  ignore_column :authentication_token
30

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

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

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

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

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

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

  BLOCKED_MESSAGE = "Your account has been blocked. Please contact your GitLab " \
                    "administrator if you think this is an error.".freeze
G
gitlabhq 已提交
62

63 64
  # Override Devise::Models::Trackable#update_tracked_fields!
  # to limit database writes to at most once every hour
65
  # rubocop: disable CodeReuse/ServiceClass
66
  def update_tracked_fields!(request)
67 68
    return if Gitlab::Database.read_only?

69 70
    update_tracked_fields(request)

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

J
James Lopez 已提交
74
    Users::UpdateService.new(self, user: self).execute(validate: false)
75
  end
76
  # rubocop: enable CodeReuse/ServiceClass
77

78
  attr_accessor :force_random_password
G
gitlabhq 已提交
79

80 81 82
  # Virtual attribute for authenticating by either username or email
  attr_accessor :login

83 84 85 86
  #
  # Relations
  #

87
  # Namespace for personal projects
88
  has_one :namespace, -> { where(type: nil) }, dependent: :destroy, foreign_key: :owner_id, inverse_of: :owner, autosave: true # rubocop:disable Cop/ActiveRecordDependent
89 90

  # Profile
91 92
  has_many :keys, -> { where(type: ['Key', nil]) }, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
  has_many :deploy_keys, -> { where(type: 'DeployKey') }, dependent: :nullify # rubocop:disable Cop/ActiveRecordDependent
93
  has_many :gpg_keys
94

95 96 97 98 99
  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
100
  has_one :user_synced_attributes_metadata, autosave: true
101 102

  # Groups
103 104
  has_many :members
  has_many :group_members, -> { where(requested_at: nil) }, source: 'GroupMember'
105 106
  has_many :groups, through: :group_members
  has_many :owned_groups, -> { where(members: { access_level: Gitlab::Access::OWNER }) }, through: :group_members, source: :group
107
  has_many :maintainers_groups, -> { where(members: { access_level: Gitlab::Access::MAINTAINER }) }, through: :group_members, source: :group
108 109 110 111
  has_many :owned_or_maintainers_groups,
           -> { where(members: { access_level: [Gitlab::Access::MAINTAINER, Gitlab::Access::OWNER] }) },
           through: :group_members,
           source: :group
112
  alias_attribute :masters_groups, :maintainers_groups
113

114
  # Projects
115 116
  has_many :groups_projects,          through: :groups, source: :projects
  has_many :personal_projects,        through: :namespace, source: :projects
117
  has_many :project_members, -> { where(requested_at: nil) }
118 119
  has_many :projects,                 through: :project_members
  has_many :created_projects,         foreign_key: :creator_id, class_name: 'Project'
120
  has_many :users_star_projects, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
121
  has_many :starred_projects, through: :users_star_projects, source: :project
122
  has_many :project_authorizations, dependent: :delete_all # rubocop:disable Cop/ActiveRecordDependent
123
  has_many :authorized_projects, through: :project_authorizations, source: :project
124

125
  has_many :user_interacted_projects
126
  has_many :project_interactions, through: :user_interacted_projects, source: :project, class_name: 'Project'
127

128 129 130 131 132 133 134 135 136 137 138 139
  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
  has_many :events,                   dependent: :destroy, foreign_key: :author_id # rubocop:disable Cop/ActiveRecordDependent
  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
140
  has_many :todos
141
  has_many :notification_settings
142 143
  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
144

145
  has_many :issue_assignees
146
  has_many :assigned_issues, class_name: "Issue", through: :issue_assignees, source: :issue
147
  has_many :assigned_merge_requests,  dependent: :nullify, foreign_key: :assignee_id, class_name: "MergeRequest" # rubocop:disable Cop/ActiveRecordDependent
148

149
  has_many :custom_attributes, class_name: 'UserCustomAttribute'
M
Matija Čupić 已提交
150
  has_many :callouts, class_name: 'UserCallout'
151 152
  has_many :term_agreements
  belongs_to :accepted_term, class_name: 'ApplicationSetting::Term'
153

B
Bob Van Landuyt 已提交
154
  has_one :status, class_name: 'UserStatus'
155
  has_one :user_preference
B
Bob Van Landuyt 已提交
156

157 158 159
  #
  # Validations
  #
160
  # Note: devise :validatable above adds validations for :email and :password
C
Cyril 已提交
161
  validates :name, presence: true
D
Douwe Maan 已提交
162
  validates :email, confirmation: true
163 164
  validates :notification_email, presence: true
  validates :notification_email, email: true, if: ->(user) { user.notification_email != user.email }
165
  validates :public_email, presence: true, uniqueness: true, email: true, allow_blank: true
166
  validates :commit_email, email: true, allow_nil: true, if: ->(user) { user.commit_email != user.email }
167
  validates :bio, length: { maximum: 255 }, allow_blank: true
168 169 170
  validates :projects_limit,
    presence: true,
    numericality: { greater_than_or_equal_to: 0, less_than_or_equal_to: Gitlab::Database::MAX_INT_VALUE }
171
  validates :username, presence: true
172

173
  validates :namespace, presence: true
174 175
  validate :namespace_move_dir_allowed, if: :username_changed?

T
Tiago Botelho 已提交
176 177 178
  validate :unique_email, if: :email_changed?
  validate :owns_notification_email, if: :notification_email_changed?
  validate :owns_public_email, if: :public_email_changed?
179
  validate :owns_commit_email, if: :commit_email_changed?
180
  validate :signup_domain_valid?, on: :create, if: ->(user) { !user.created_by_id }
181

182
  before_validation :sanitize_attrs
183
  before_validation :set_notification_email, if: :new_record?
T
Tiago Botelho 已提交
184
  before_validation :set_public_email, if: :public_email_changed?
185
  before_validation :set_commit_email, if: :commit_email_changed?
186
  before_save :set_public_email, if: :public_email_changed? # in case validation is skipped
187
  before_save :set_commit_email, if: :commit_email_changed? # in case validation is skipped
D
Douwe Maan 已提交
188
  before_save :ensure_incoming_email_token
189
  before_save :ensure_user_rights_and_limits, if: ->(user) { user.new_record? || user.external_changed? }
190
  before_save :skip_reconfirmation!, if: ->(user) { user.email_changed? && user.read_only_attribute?(:email) }
A
Alexandra 已提交
191
  before_save :check_for_verified_email, if: ->(user) { user.email_changed? && !user.new_record? }
192
  before_validation :ensure_namespace_correct
193
  before_save :ensure_namespace_correct # in case validation is skipped
194
  after_validation :set_username_errors
195
  after_update :username_changed_hook, if: :username_changed?
196
  after_destroy :post_destroy_hook
Y
Yorick Peterse 已提交
197
  after_destroy :remove_key_cache
198 199 200 201 202 203 204 205 206 207 208 209 210 211 212
  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
      previous_email = previous_changes[:email][0]

      update_emails_with_primary_email(previous_email)
      update_invalid_gpg_signatures

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

214
  after_initialize :set_projects_limit
D
Dmitriy Zaporozhets 已提交
215

216
  # User's Layout preference
217
  enum layout: [:fixed, :fluid]
218

219 220
  # User's Dashboard preference
  # Note: When adding an option, it MUST go on the end of the array.
221
  enum dashboard: [:projects, :stars, :project_activity, :starred_project_activity, :groups, :todos, :issues, :merge_requests, :operations]
222

223
  # User's Project preference
224 225 226
  # Note: When adding an option, it MUST go on the end of the array.
  enum project_view: [:readme, :activity, :files]

227
  delegate :path, to: :namespace, allow_nil: true, prefix: true
228 229
  delegate :notes_filter_for, to: :user_preference
  delegate :set_notes_filter, to: :user_preference
230

231 232 233
  state_machine :state, initial: :active do
    event :block do
      transition active: :blocked
234
      transition ldap_blocked: :blocked
235 236
    end

237 238 239 240
    event :ldap_block do
      transition active: :ldap_blocked
    end

241 242
    event :activate do
      transition blocked: :active
243
      transition ldap_blocked: :active
244
    end
245 246 247 248 249

    state :blocked, :ldap_blocked do
      def blocked?
        true
      end
250 251 252 253 254 255

      def active_for_authentication?
        false
      end

      def inactive_message
256
        BLOCKED_MESSAGE
257
      end
258
    end
259 260
  end

A
Andrey Kumanyaev 已提交
261
  # Scopes
262
  scope :admins, -> { where(admin: true) }
263
  scope :blocked, -> { with_states(:blocked, :ldap_blocked) }
264
  scope :external, -> { where(external: true) }
J
James Lopez 已提交
265
  scope :active, -> { with_state(:active).non_internal }
266
  scope :without_projects, -> { joins('LEFT JOIN project_authorizations ON users.id = project_authorizations.user_id').where(project_authorizations: { user_id: nil }) }
267 268
  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')) }
269
  scope :confirmed, -> { where.not(confirmed_at: nil) }
270
  scope :by_username, -> (usernames) { iwhere(username: Array(usernames).map(&:to_s)) }
271
  scope :for_todos, -> (todos) { where(id: todos.select(:user_id)) }
272

Y
Yorick Peterse 已提交
273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295
  # 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.
296
      User.unscoped.from_union([all, User.unscoped.where(id: user_id)])
Y
Yorick Peterse 已提交
297 298 299 300 301
    else
      all
    end
  end

302
  def self.with_two_factor
303 304 305 306 307 308 309 310 311
    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)
312 313 314
  end

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

319 320 321
  #
  # Class methods
  #
A
Andrey Kumanyaev 已提交
322
  class << self
323
    # Devise method overridden to allow sign in with email or username
324 325 326
    def find_for_database_authentication(warden_conditions)
      conditions = warden_conditions.dup
      if login = conditions.delete(:login)
327
        where(conditions).find_by("lower(username) = :value OR lower(email) = :value", value: login.downcase.strip)
328
      else
G
Gabriel Mazetto 已提交
329
        find_by(conditions)
330 331
      end
    end
332

333
    def sort_by_attribute(method)
334 335 336
      order_method = method || 'id_desc'

      case order_method.to_s
337 338
      when 'recent_sign_in' then order_recent_sign_in
      when 'oldest_sign_in' then order_oldest_sign_in
339
      else
340
        order_by(order_method)
V
Valery Sizov 已提交
341 342 343
      end
    end

344
    def for_github_id(id)
345
      joins(:identities).merge(Identity.with_extern_uid(:github, id))
346 347
    end

348
    # Find a User by their primary email or any associated secondary email
349 350
    def find_by_any_email(email, confirmed: false)
      by_any_email(email, confirmed: confirmed).take
Y
Yorick Peterse 已提交
351 352 353
    end

    # Returns a relation containing all the users for the given Email address
354
    def by_any_email(email, confirmed: false)
Y
Yorick Peterse 已提交
355
      users = where(email: email)
356 357
      users = users.confirmed if confirmed

Y
Yorick Peterse 已提交
358
      emails = joins(:emails).where(emails: { email: email })
359
      emails = emails.confirmed if confirmed
Y
Yorick Peterse 已提交
360

361
      from_union([users, emails])
362
    end
363

364
    def filter(filter_name)
A
Andrey Kumanyaev 已提交
365
      case filter_name
366
      when 'admins'
367
        admins
368
      when 'blocked'
369
        blocked
370
      when 'two_factor_disabled'
371
        without_two_factor
372
      when 'two_factor_enabled'
373
        with_two_factor
374
      when 'wop'
375
        without_projects
376
      when 'external'
377
        external
A
Andrey Kumanyaev 已提交
378
      else
379
        active
A
Andrey Kumanyaev 已提交
380
      end
381 382
    end

383 384 385 386 387 388 389
    # Searches users matching the given query.
    #
    # This method uses ILIKE on PostgreSQL and LIKE on MySQL.
    #
    # query - The search query as a String
    #
    # Returns an ActiveRecord::Relation.
390
    def search(query)
391 392
      return none if query.blank?

393 394
      query = query.downcase

395 396 397 398 399 400 401 402 403
      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

404
      where(
405 406
        fuzzy_arel_match(:name, query, lower_exact_match: true)
          .or(fuzzy_arel_match(:username, query, lower_exact_match: true))
407
          .or(arel_table[:email].eq(query))
408
      ).reorder(order % { query: ActiveRecord::Base.connection.quote(query) }, :name)
A
Andrey Kumanyaev 已提交
409
    end
410

Y
Yorick Peterse 已提交
411 412 413 414 415 416 417 418 419 420 421 422
    # 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

423 424 425 426 427
    # 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 and LIKE on MySQL.

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

430 431
      query = query.downcase

432
      email_table = Email.arel_table
433 434
      matched_by_emails_user_ids = email_table
        .project(email_table[:user_id])
435
        .where(email_table[:email].eq(query))
436 437

      where(
438 439
        fuzzy_arel_match(:name, query)
          .or(fuzzy_arel_match(:username, query))
440
          .or(arel_table[:email].eq(query))
441
          .or(arel_table[:id].in(matched_by_emails_user_ids))
442 443 444
      )
    end

445
    def by_login(login)
446 447 448 449 450 451 452
      return nil unless login

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

455
    def find_by_username(username)
456
      by_username(username).take
457 458
    end

R
Robert Speicher 已提交
459
    def find_by_username!(username)
460
      by_username(username).take!
R
Robert Speicher 已提交
461 462
    end

T
Timothy Andrew 已提交
463
    def find_by_personal_access_token(token_string)
464 465
      return unless token_string

466
      PersonalAccessTokensFinder.new(state: 'active').find_by(token: token_string)&.user # rubocop: disable CodeReuse/Finder
T
Timothy Andrew 已提交
467 468
    end

Y
Yorick Peterse 已提交
469 470
    # Returns a user for the given SSH key.
    def find_by_ssh_key_id(key_id)
471
      Key.find_by(id: key_id)&.user
Y
Yorick Peterse 已提交
472 473
    end

474
    def find_by_full_path(path, follow_redirects: false)
475 476
      namespace = Namespace.for_user.find_by_full_path(path, follow_redirects: follow_redirects)
      namespace&.owner
477 478
    end

479 480 481
    def reference_prefix
      '@'
    end
482 483 484 485

    # Pattern used to extract `@user` user references from text
    def reference_pattern
      %r{
486
        (?<!\w)
487
        #{Regexp.escape(reference_prefix)}
488
        (?<user>#{Gitlab::PathRegex::FULL_NAMESPACE_FORMAT_REGEX})
489 490
      }x
    end
491 492 493 494

    # Return (create if necessary) the ghost user. The ghost user
    # owns records previously belonging to deleted users.
    def ghost
495 496
      email = 'ghost%s@example.com'
      unique_internal(where(ghost: true), 'ghost', email) do |u|
497 498 499
        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
500
    end
501 502 503 504 505 506 507 508 509 510

    # 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 已提交
511
  end
R
randx 已提交
512

M
Michael Kozono 已提交
513 514 515 516
  def full_path
    username
  end

517 518 519 520
  def self.internal_attributes
    [:ghost]
  end

521
  def internal?
522 523 524 525 526 527 528 529
    self.class.internal_attributes.any? { |a| self[a] }
  end

  def self.internal
    where(Hash[internal_attributes.zip([true] * internal_attributes.size)])
  end

  def self.non_internal
530
    where(internal_attributes.map { |attr| "#{attr} IS NOT TRUE" }.join(" AND "))
531 532
  end

533 534 535
  #
  # Instance methods
  #
536 537 538 539 540

  def to_param
    username
  end

J
Jarka Kadlecova 已提交
541
  def to_reference(_from = nil, target_project: nil, full: nil)
542 543 544
    "#{self.class.reference_prefix}#{username}"
  end

545 546
  def skip_confirmation=(bool)
    skip_confirmation! if bool
D
Daniel Juarez 已提交
547 548 549 550
  end

  def skip_reconfirmation=(bool)
    skip_reconfirmation! if bool
R
randx 已提交
551
  end
552

553
  def generate_reset_token
M
Marin Jankovski 已提交
554
    @reset_token, enc = Devise.token_generator.generate(self.class, :reset_password_token)
555 556 557 558

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

M
Marin Jankovski 已提交
559
    @reset_token
560 561
  end

562 563 564 565
  def recently_sent_password_reset?
    reset_password_sent_at.present? && reset_password_sent_at >= 1.minute.ago
  end

T
Toon Claes 已提交
566 567 568 569 570 571 572 573
  def remember_me!
    super if ::Gitlab::Database.read_write?
  end

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

R
Robert Speicher 已提交
574
  def disable_two_factor!
575
    transaction do
L
Lin Jen-Shin 已提交
576
      update(
577 578 579 580 581 582 583
        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
      )
584
      self.u2f_registrations.destroy_all # rubocop: disable DestroyAll
585 586 587 588 589 590 591 592
    end
  end

  def two_factor_enabled?
    two_factor_otp_enabled? || two_factor_u2f_enabled?
  end

  def two_factor_otp_enabled?
593
    otp_required_for_login?
594 595 596
  end

  def two_factor_u2f_enabled?
597 598 599 600 601
    if u2f_registrations.loaded?
      u2f_registrations.any?
    else
      u2f_registrations.exists?
    end
R
Robert Speicher 已提交
602 603
  end

604 605 606 607 608 609
  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

610
  def unique_email
611 612
    if !emails.exists?(email: email) && Email.exists?(email: email)
      errors.add(:email, 'has already been taken')
613
    end
614 615
  end

616
  def owns_notification_email
617
    return if temp_oauth_email?
618

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

622
  def owns_public_email
623
    return if public_email.blank?
624

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

628 629 630 631 632 633 634 635 636 637 638 639
  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

  # 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
640
    return self.email unless has_attribute?(:commit_email)
641 642 643 644 645 646 647 648 649 650 651 652 653

    # 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

A
Alexandra 已提交
654 655 656 657 658
  # 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

659
  # Note: the use of the Emails services will cause `saves` on the user object, running
660
  # through the callbacks again and can have side effects, such as the `previous_changes`
661 662 663
  # 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
664
  # rubocop: disable CodeReuse/ServiceClass
665
  def update_emails_with_primary_email(previous_email)
666
    primary_email_record = emails.find_by(email: email)
667
    Emails::DestroyService.new(self, user: self).execute(primary_email_record) if primary_email_record
668

669 670
    # 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
671
    Emails::CreateService.new(self, user: self, email: previous_email).execute(confirmed_at: confirmed_at)
672
  end
673
  # rubocop: enable CodeReuse/ServiceClass
674

675
  def update_invalid_gpg_signatures
676
    gpg_keys.each(&:update_invalid_gpg_signatures)
677 678
  end

679
  # Returns the groups a user has access to, either through a membership or a project authorization
680
  def authorized_groups
681 682 683 684 685 686
    Group.unscoped do
      Group.from_union([
        groups,
        authorized_projects.joins(:namespace).select('namespaces.*')
      ])
    end
687 688
  end

689 690 691 692 693
  # Returns the groups a user is a member of, either directly or through a parent group
  def membership_groups
    Gitlab::GroupHierarchy.new(groups).base_and_descendants
  end

694 695
  # Returns a relation of groups the user has access to, including their parent
  # and child groups (recursively).
696
  def all_expanded_groups
697
    Gitlab::GroupHierarchy.new(groups).all_groups
698 699 700 701 702 703
  end

  def expanded_groups_requiring_two_factor_authentication
    all_expanded_groups.where(require_two_factor_authentication: true)
  end

704
  # rubocop: disable CodeReuse/ServiceClass
705
  def refresh_authorized_projects
706 707
    Users::RefreshAuthorizedProjectsService.new(self).execute
  end
708
  # rubocop: enable CodeReuse/ServiceClass
709 710

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

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

    if min_access_level
720 721
      projects = projects
        .where('project_authorizations.access_level >= ?', min_access_level)
722
    end
723 724 725 726 727 728

    projects
  end

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

731 732 733 734 735 736 737 738 739
  # Typically used in conjunction with projects table to get projects
  # a user has been given access to.
  #
  # Example use:
  # `Project.where('EXISTS(?)', user.authorizations_for_projects)`
  def authorizations_for_projects
    project_authorizations.select(1).where('project_authorizations.project_id = projects.id')
  end

740 741 742 743 744 745 746 747 748 749
  # 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

750
  def owned_projects
751 752 753 754 755 756 757 758 759
    @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
    )
760 761
  end

762 763 764 765
  # 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 已提交
766
    authorized_projects(Gitlab::Access::REPORTER).non_archived.with_issues_enabled
767 768
  end

769
  # rubocop: disable CodeReuse/ServiceClass
D
Dmitriy Zaporozhets 已提交
770
  def require_ssh_key?
Y
Yorick Peterse 已提交
771 772 773
    count = Users::KeysCountService.new(self).count

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

777 778 779 780 781 782
  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?
783 784
  end

785
  def require_personal_access_token_creation_for_git_auth?
786
    return false if allow_password_authentication_for_git? || ldap_user?
787 788

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

791 792 793 794
  def require_extra_setup_for_git_auth?
    require_password_creation_for_git? || require_personal_access_token_creation_for_git_auth?
  end

795
  def allow_password_authentication?
796 797 798 799
    allow_password_authentication_for_web? || allow_password_authentication_for_git?
  end

  def allow_password_authentication_for_web?
800
    Gitlab::CurrentSettings.password_authentication_enabled_for_web? && !ldap_user?
801 802 803
  end

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

807
  def can_change_username?
808
    gitlab_config.username_changing_enabled
809 810
  end

D
Dmitriy Zaporozhets 已提交
811
  def can_create_project?
812
    projects_limit_left > 0
D
Dmitriy Zaporozhets 已提交
813 814 815
  end

  def can_create_group?
816
    can?(:create_group)
D
Dmitriy Zaporozhets 已提交
817 818
  end

819 820 821 822
  def can_select_namespace?
    several_namespaces? || admin
  end

823
  def can?(action, subject = :global)
H
http://jneen.net/ 已提交
824
    Ability.allowed?(self, action, subject)
D
Dmitriy Zaporozhets 已提交
825 826
  end

827 828 829 830
  def confirm_deletion_with_password?
    !password_automatically_set? && allow_password_authentication?
  end

D
Dmitriy Zaporozhets 已提交
831 832 833 834
  def first_name
    name.split.first unless name.blank?
  end

835
  def projects_limit_left
836 837 838
    projects_limit - personal_projects_count
  end

839
  # rubocop: disable CodeReuse/ServiceClass
840 841
  def recent_push(project = nil)
    service = Users::LastPushEventService.new(self)
D
Dmitriy Zaporozhets 已提交
842

843 844 845 846
    if project
      service.last_event_for_project(project)
    else
      service.last_event_for_user
847
    end
D
Dmitriy Zaporozhets 已提交
848
  end
849
  # rubocop: enable CodeReuse/ServiceClass
D
Dmitriy Zaporozhets 已提交
850 851

  def several_namespaces?
852
    owned_groups.any? || maintainers_groups.any?
D
Dmitriy Zaporozhets 已提交
853 854 855 856 857
  end

  def namespace_id
    namespace.try :id
  end
858

859 860 861
  def name_with_username
    "#{name} (#{username})"
  end
D
Dmitriy Zaporozhets 已提交
862

863
  def already_forked?(project)
864 865 866
    !!fork_of(project)
  end

867
  def fork_of(project)
868
    namespace.find_fork_of(project)
869
  end
870 871

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

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

883
  def project_deploy_keys
884
    DeployKey.unscoped.in_projects(authorized_projects.pluck(:id)).distinct(:id)
885 886
  end

887
  def accessible_deploy_keys
888 889 890 891 892
    @accessible_deploy_keys ||= begin
      key_ids = project_deploy_keys.pluck(:id)
      key_ids.push(*DeployKey.are_public.pluck(:id))
      DeployKey.where(id: key_ids)
    end
893
  end
894 895

  def created_by
S
skv 已提交
896
    User.find_by(id: created_by_id) if created_by_id
897
  end
898 899

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

906
  def set_notification_email
907
    if notification_email.blank? || all_emails.exclude?(notification_email)
908
      self.notification_email = email
909 910 911
    end
  end

912
  def set_public_email
913
    if public_email.blank? || all_emails.exclude?(public_email)
914
      self.public_email = ''
915 916 917
    end
  end

918 919 920 921 922 923
  def set_commit_email
    if commit_email.blank? || verified_emails.exclude?(commit_email)
      self.commit_email = nil
    end
  end

924
  def update_secondary_emails!
925 926
    set_notification_email
    set_public_email
927 928
    set_commit_email
    save if notification_email_changed? || public_email_changed? || commit_email_changed?
929 930
  end

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

937
    self.projects_limit = Gitlab::CurrentSettings.default_projects_limit
938 939
  end

940
  def requires_ldap_check?
941 942 943
    if !Gitlab.config.ldap.enabled
      false
    elsif ldap_user?
944 945 946 947 948 949
      !last_credential_check_at || (last_credential_check_at + 1.hour) < Time.now
    else
      false
    end
  end

J
Jacob Vosmaer 已提交
950 951 952 953 954 955 956
  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

957 958 959 960 961
  def solo_owned_groups
    @solo_owned_groups ||= owned_groups.select do |group|
      group.owners == [self]
    end
  end
962 963

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

    self
969
  end
970

971 972 973 974
  def can_leave_project?(project)
    project.namespace != namespace &&
      project.project_member(self)
  end
975

J
Jerome Dalbert 已提交
976
  def full_website_url
977
    return "http://#{website_url}" if website_url !~ %r{\Ahttps?://}
J
Jerome Dalbert 已提交
978 979 980 981 982

    website_url
  end

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

986
  def all_ssh_keys
987
    keys.map(&:publishable_key)
988
  end
989 990

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

994
  # rubocop: disable CodeReuse/ServiceClass
995
  def avatar_url(size: nil, scale: 2, **args)
996
    GravatarService.new.execute(email, size, scale, username: username)
997
  end
998
  # rubocop: enable CodeReuse/ServiceClass
D
Dmitriy Zaporozhets 已提交
999

1000 1001 1002 1003
  def primary_email_verified?
    confirmed? && !temp_oauth_email?
  end

1004 1005 1006 1007 1008 1009 1010 1011 1012 1013
  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

1014
  def all_emails
1015
    all_emails = []
1016 1017
    all_emails << email unless temp_oauth_email?
    all_emails.concat(emails.map(&:email))
1018
    all_emails
1019 1020
  end

1021
  def verified_emails
1022
    verified_emails = []
1023
    verified_emails << email if primary_email_verified?
1024
    verified_emails.concat(emails.confirmed.pluck(:email))
1025 1026 1027
    verified_emails
  end

1028
  def verified_email?(check_email)
1029
    downcased = check_email.downcase
A
Alexandra 已提交
1030
    email == downcased ? primary_email_verified? : emails.confirmed.where(email: downcased).exists?
1031 1032
  end

K
Kirill Zaitsev 已提交
1033 1034 1035 1036
  def hook_attrs
    {
      name: name,
      username: username,
1037
      avatar_url: avatar_url(only_path: false)
K
Kirill Zaitsev 已提交
1038 1039 1040
    }
  end

D
Dmitriy Zaporozhets 已提交
1041
  def ensure_namespace_correct
1042 1043 1044 1045
    if namespace
      namespace.path = namespace.name = username if username_changed?
    else
      build_namespace(path: username, name: username)
D
Dmitriy Zaporozhets 已提交
1046 1047 1048
    end
  end

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

1054 1055 1056 1057
  def username_changed_hook
    system_hook_service.execute_hooks_for(self, :rename)
  end

D
Dmitriy Zaporozhets 已提交
1058
  def post_destroy_hook
1059
    log_info("User \"#{name}\" (#{email})  was removed")
D
Douwe Maan 已提交
1060

D
Dmitriy Zaporozhets 已提交
1061 1062 1063
    system_hook_service.execute_hooks_for(self, :destroy)
  end

1064
  # rubocop: disable CodeReuse/ServiceClass
Y
Yorick Peterse 已提交
1065 1066 1067
  def remove_key_cache
    Users::KeysCountService.new(self).delete_cache
  end
1068
  # rubocop: enable CodeReuse/ServiceClass
Y
Yorick Peterse 已提交
1069

N
Nick Thomas 已提交
1070 1071
  def delete_async(deleted_by:, params: {})
    block if params[:hard_delete]
1072
    DeleteUserWorker.perform_async(deleted_by.id, id, params.to_h)
N
Nick Thomas 已提交
1073 1074
  end

1075
  # rubocop: disable CodeReuse/ServiceClass
D
Dmitriy Zaporozhets 已提交
1076
  def notification_service
D
Dmitriy Zaporozhets 已提交
1077 1078
    NotificationService.new
  end
1079
  # rubocop: enable CodeReuse/ServiceClass
D
Dmitriy Zaporozhets 已提交
1080

1081
  def log_info(message)
D
Dmitriy Zaporozhets 已提交
1082 1083 1084
    Gitlab::AppLogger.info message
  end

1085
  # rubocop: disable CodeReuse/ServiceClass
D
Dmitriy Zaporozhets 已提交
1086 1087 1088
  def system_hook_service
    SystemHooksService.new
  end
1089
  # rubocop: enable CodeReuse/ServiceClass
C
Ciro Santilli 已提交
1090 1091

  def starred?(project)
V
Valery Sizov 已提交
1092
    starred_projects.exists?(project.id)
C
Ciro Santilli 已提交
1093 1094 1095
  end

  def toggle_star(project)
1096
    UsersStarProject.transaction do
1097 1098
      user_star_project = users_star_projects
          .where(project: project, user: self).lock(true).first
1099 1100 1101 1102 1103 1104

      if user_star_project
        user_star_project.destroy
      else
        UsersStarProject.create!(project: project, user: self)
      end
C
Ciro Santilli 已提交
1105 1106
    end
  end
1107 1108

  def manageable_namespaces
1109 1110 1111 1112
    @manageable_namespaces ||= [namespace] + manageable_groups
  end

  def manageable_groups
1113
    Gitlab::GroupHierarchy.new(owned_or_maintainers_groups).base_and_descendants
1114
  end
D
Dmitriy Zaporozhets 已提交
1115

1116 1117 1118 1119 1120 1121
  def namespaces
    namespace_ids = groups.pluck(:id)
    namespace_ids.push(namespace.id)
    Namespace.where(id: namespace_ids)
  end

D
Dmitriy Zaporozhets 已提交
1122
  def oauth_authorized_tokens
1123
    Doorkeeper::AccessToken.where(resource_owner_id: id, revoked_at: nil)
D
Dmitriy Zaporozhets 已提交
1124
  end
1125

1126 1127 1128 1129 1130 1131 1132 1133 1134
  # 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
1135
  # ms on a database with a similar size to GitLab.com's database. On the other
1136 1137
  # hand, using a subquery means we can get the exact same data in about 40 ms.
  def contributed_projects
1138 1139 1140 1141 1142
    events = Event.select(:project_id)
      .contributions.where(author_id: self)
      .where("created_at > ?", Time.now - 1.year)
      .uniq
      .reorder(nil)
1143 1144

    Project.where(id: events)
1145
  end
1146

1147 1148 1149
  def can_be_removed?
    !solo_owned_groups.present?
  end
1150

1151 1152
  def ci_owned_runners
    @ci_owned_runners ||= begin
1153
      project_runners = Ci::RunnerProject
1154
        .where(project: authorized_projects(Gitlab::Access::MAINTAINER))
1155 1156
        .joins(:runner)
        .select('ci_runners.*')
1157

1158
      group_runners = Ci::RunnerNamespace
1159
        .where(namespace_id: owned_or_maintainers_groups.select(:id))
1160 1161
        .joins(:runner)
        .select('ci_runners.*')
1162

1163
      Ci::Runner.from_union([project_runners, group_runners])
K
Kamil Trzcinski 已提交
1164
    end
1165
  end
1166

1167
  def notification_settings_for(source)
1168
    if notification_settings.loaded?
1169 1170 1171 1172
      notification_settings.find do |notification|
        notification.source_type == source.class.base_class.name &&
          notification.source_id == source.id
      end
1173 1174 1175
    else
      notification_settings.find_or_initialize_by(source: source)
    end
1176 1177
  end

1178 1179 1180
  # Lazy load global notification setting
  # Initializes User setting with Participating level if setting not persisted
  def global_notification_setting
1181 1182 1183
    return @global_notification_setting if defined?(@global_notification_setting)

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

    @global_notification_setting
1187 1188
  end

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

J
Josh Frye 已提交
1195
  def assigned_open_issues_count(force: false)
1196
    Rails.cache.fetch(['users', id, 'assigned_open_issues_count'], force: force, expires_in: 20.minutes) do
1197
      IssuesFinder.new(self, assignee_id: self.id, state: 'opened', non_archived: true).execute.count
1198
    end
1199 1200
  end

1201 1202 1203 1204 1205 1206 1207 1208 1209 1210 1211 1212
  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 已提交
1213 1214 1215 1216 1217 1218
  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

1219 1220 1221 1222 1223
  def update_todos_count_cache
    todos_done_count(force: true)
    todos_pending_count(force: true)
  end

1224
  def invalidate_cache_counts
1225 1226
    invalidate_issue_cache_counts
    invalidate_merge_request_cache_counts
1227 1228
    invalidate_todos_done_count
    invalidate_todos_pending_count
A
Andreas Brandl 已提交
1229
    invalidate_personal_projects_count
1230 1231 1232
  end

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

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

1240 1241
  def invalidate_todos_done_count
    Rails.cache.delete(['users', id, 'todos_done_count'])
P
Paco Guzman 已提交
1242 1243
  end

1244 1245
  def invalidate_todos_pending_count
    Rails.cache.delete(['users', id, 'todos_pending_count'])
P
Paco Guzman 已提交
1246 1247
  end

A
Andreas Brandl 已提交
1248 1249 1250 1251
  def invalidate_personal_projects_count
    Rails.cache.delete(['users', id, 'personal_projects_count'])
  end

1252 1253 1254 1255 1256 1257
  # 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.0.0/lib/devise/models/lockable.rb#L92>
  #
1258
  # rubocop: disable CodeReuse/ServiceClass
1259
  def increment_failed_attempts!
1260 1261
    return if ::Gitlab::Database.read_only?

1262 1263
    self.failed_attempts ||= 0
    self.failed_attempts += 1
1264

1265 1266 1267
    if attempts_exceeded?
      lock_access! unless access_locked?
    else
J
James Lopez 已提交
1268
      Users::UpdateService.new(self, user: self).execute(validate: false)
1269 1270
    end
  end
1271
  # rubocop: enable CodeReuse/ServiceClass
1272

1273 1274 1275 1276 1277 1278 1279 1280 1281
  def access_level
    if admin?
      :admin
    else
      :regular
    end
  end

  def access_level=(new_level)
D
Douwe Maan 已提交
1282 1283
    new_level = new_level.to_s
    return unless %w(admin regular).include?(new_level)
1284

D
Douwe Maan 已提交
1285 1286
    self.admin = (new_level == 'admin')
  end
1287

1288 1289 1290 1291 1292 1293
  # Does the user have access to all private groups & projects?
  # Overridden in EE to also check auditor?
  def full_private_access?
    admin?
  end

1294
  def update_two_factor_requirement
1295
    periods = expanded_groups_requiring_two_factor_authentication.pluck(:two_factor_grace_period)
1296

1297
    self.require_two_factor_authentication_from_group = periods.any?
1298 1299 1300 1301 1302
    self.two_factor_grace_period = periods.min || User.column_defaults['two_factor_grace_period']

    save
  end

1303
  # each existing user needs to have an `feed_token`.
A
Alexis Reigel 已提交
1304 1305
  # we do this on read since migrating all existing users is not a feasible
  # solution.
1306 1307
  def feed_token
    ensure_feed_token!
A
Alexis Reigel 已提交
1308 1309
  end

1310 1311 1312 1313 1314 1315 1316 1317 1318 1319 1320 1321 1322 1323 1324 1325
  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 已提交
1326 1327
  # override, from Devise
  def lock_access!
1328
    Gitlab::AppLogger.info("Account Locked: username=#{username}")
B
Brian Neel 已提交
1329 1330 1331
    super
  end

1332 1333 1334 1335 1336 1337 1338 1339 1340 1341 1342 1343 1344 1345 1346 1347 1348 1349 1350 1351 1352 1353 1354 1355 1356 1357 1358 1359
  # 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

1360 1361 1362 1363
  def terms_accepted?
    accepted_term_id.present?
  end

1364 1365 1366 1367 1368
  def required_terms_not_accepted?
    Gitlab::CurrentSettings.current_application_settings.enforce_terms? &&
      !terms_accepted?
  end

1369 1370 1371 1372
  def requires_usage_stats_consent?
    !consented_usage_stats? && 7.days.ago > self.created_at && !has_current_license? && User.single_user?
  end

1373 1374 1375 1376 1377
  # Avoid migrations only building user preference object when needed.
  def user_preference
    super.presence || build_user_preference
  end

1378 1379 1380 1381
  def todos_limited_to(ids)
    todos.where(id: ids)
  end

1382 1383 1384
  # @deprecated
  alias_method :owned_or_masters_groups, :owned_or_maintainers_groups

1385 1386 1387 1388 1389
  protected

  # override, from Devise::Validatable
  def password_required?
    return false if internal?
1390

1391 1392 1393
    super
  end

1394 1395
  private

1396 1397 1398 1399 1400 1401 1402 1403
  def has_current_license?
    false
  end

  def consented_usage_stats?
    Gitlab::CurrentSettings.usage_stats_set_by_user_id == self.id
  end

V
Valery Sizov 已提交
1404 1405
  # Added according to https://github.com/plataformatec/devise/blob/7df57d5081f9884849ca15e4fde179ef164a575f/README.md#activejob-integration
  def send_devise_notification(notification, *args)
1406
    return true unless can?(:receive_notifications)
1407

1408
    devise_mailer.__send__(notification, self, *args).deliver_later # rubocop:disable GitlabSecurity/PublicSend
V
Valery Sizov 已提交
1409
  end
Z
Zeger-Jan van de Weg 已提交
1410

1411 1412 1413 1414 1415 1416 1417 1418 1419
  # This works around a bug in Devise 4.2.0 that erroneously causes a user to
  # be considered active in MySQL specs due to a sub-second comparison
  # issue. For more details, see: https://gitlab.com/gitlab-org/gitlab-ee/issues/2362#note_29004709
  def confirmation_period_valid?
    return false if self.class.allow_unconfirmed_access_for == 0.days

    super
  end

1420 1421 1422 1423 1424
  def ensure_user_rights_and_limits
    if external?
      self.can_create_group = false
      self.projects_limit   = 0
    else
1425 1426
      # 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?
1427
      self.projects_limit = Gitlab::CurrentSettings.default_projects_limit unless projects_limit_changed?
1428
    end
Z
Zeger-Jan van de Weg 已提交
1429
  end
1430

1431 1432 1433 1434
  def signup_domain_valid?
    valid = true
    error = nil

1435 1436
    if Gitlab::CurrentSettings.domain_blacklist_enabled?
      blocked_domains = Gitlab::CurrentSettings.domain_blacklist
1437
      if domain_matches?(blocked_domains, email)
1438 1439 1440 1441 1442
        error = 'is not from an allowed domain.'
        valid = false
      end
    end

1443
    allowed_domains = Gitlab::CurrentSettings.domain_whitelist
1444
    unless allowed_domains.blank?
1445
      if domain_matches?(allowed_domains, email)
1446 1447
        valid = true
      else
D
dev-chris 已提交
1448
        error = "domain is not authorized for sign-up"
1449 1450 1451 1452
        valid = false
      end
    end

1453
    errors.add(:email, error) unless valid
1454 1455 1456

    valid
  end
1457

1458
  def domain_matches?(email_domains, email)
1459 1460 1461 1462 1463 1464 1465
    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
1466

1467 1468
  def self.unique_internal(scope, username, email_pattern, &block)
    scope.first || create_unique_internal(scope, username, email_pattern, &block)
1469 1470 1471 1472
  end

  def self.create_unique_internal(scope, username, email_pattern, &creation_block)
    # Since we only want a single one of these in an instance, we use an
1473
    # exclusive lease to ensure than this block is never run concurrently.
1474
    lease_key = "user:unique_internal:#{username}"
1475 1476 1477 1478 1479 1480 1481 1482
    lease = Gitlab::ExclusiveLease.new(lease_key, timeout: 1.minute.to_i)

    until uuid = lease.try_obtain
      # Keep trying until we obtain the lease. To prevent hammering Redis too
      # much we'll wait for a bit between retries.
      sleep(1)
    end

1483
    # Recheck if the user is already present. One might have been
1484 1485
    # added between the time we last checked (first line of this method)
    # and the time we acquired the lock.
1486 1487
    existing_user = uncached { scope.first }
    return existing_user if existing_user.present?
1488 1489 1490

    uniquify = Uniquify.new

1491
    username = uniquify.string(username) { |s| User.find_by_username(s) }
1492

1493
    email = uniquify.string(-> (n) { Kernel.sprintf(email_pattern, n) }) do |s|
1494 1495 1496
      User.find_by_email(s)
    end

1497
    user = scope.build(
1498 1499 1500
      username: username,
      email: email,
      &creation_block
1501
    )
J
James Lopez 已提交
1502

1503
    Users::UpdateService.new(user, user: user).execute(validate: false) # rubocop: disable CodeReuse/ServiceClass
1504
    user
1505 1506 1507
  ensure
    Gitlab::ExclusiveLease.cancel(lease_key, uuid)
  end
G
gitlabhq 已提交
1508
end