user.rb 46.9 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

24 25
  DEFAULT_NOTIFICATION_LEVEL = :participating

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

30
  add_authentication_token_field :incoming_email_token
31
  add_authentication_token_field :feed_token
32

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

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

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

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

56
  devise :lockable, :recoverable, :rememberable, :trackable,
57 58 59 60
         :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 已提交
61

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

68 69
    update_tracked_fields(request)

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

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

77
  attr_accessor :force_random_password
G
gitlabhq 已提交
78

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

82 83 84 85
  #
  # Relations
  #

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

  # Profile
90 91
  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
92
  has_many :gpg_keys
93

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

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

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

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

127 128 129 130 131 132 133 134 135 136 137 138
  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
139
  has_many :todos
140
  has_many :notification_settings
141 142
  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
143

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

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

B
Bob Van Landuyt 已提交
153 154
  has_one :status, class_name: 'UserStatus'

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

171
  validates :namespace, presence: true
172 173
  validate :namespace_move_dir_allowed, if: :username_changed?

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

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

212
  after_initialize :set_projects_limit
D
Dmitriy Zaporozhets 已提交
213

214
  # User's Layout preference
215
  enum layout: [:fixed, :fluid]
216

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

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

225
  delegate :path, to: :namespace, allow_nil: true, prefix: true
226

227 228 229
  state_machine :state, initial: :active do
    event :block do
      transition active: :blocked
230
      transition ldap_blocked: :blocked
231 232
    end

233 234 235 236
    event :ldap_block do
      transition active: :ldap_blocked
    end

237 238
    event :activate do
      transition blocked: :active
239
      transition ldap_blocked: :active
240
    end
241 242 243 244 245

    state :blocked, :ldap_blocked do
      def blocked?
        true
      end
246 247 248 249 250 251

      def active_for_authentication?
        false
      end

      def inactive_message
252
        BLOCKED_MESSAGE
253
      end
254
    end
255 256
  end

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

Y
Yorick Peterse 已提交
268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298
  # 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?
      union = Gitlab::SQL::Union.new([all, User.unscoped.where(id: user_id)])

      # We use "unscoped" here so that any inner conditions are not repeated for
      # the outer query, which would be redundant.
      User.unscoped.from("(#{union.to_sql}) #{User.table_name}")
    else
      all
    end
  end

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

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

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

330
    def sort_by_attribute(method)
331 332 333
      order_method = method || 'id_desc'

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

341
    def for_github_id(id)
342
      joins(:identities).merge(Identity.with_extern_uid(:github, id))
343 344
    end

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

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

Y
Yorick Peterse 已提交
355
      emails = joins(:emails).where(emails: { email: email })
356
      emails = emails.confirmed if confirmed
Y
Yorick Peterse 已提交
357 358 359
      union = Gitlab::SQL::Union.new([users, emails])

      from("(#{union.to_sql}) #{table_name}")
360
    end
361

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

381 382 383 384 385 386 387
    # 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.
388
    def search(query)
389 390
      return none if query.blank?

391 392
      query = query.downcase

393 394 395 396 397 398 399 400 401
      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

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

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

421 422 423 424 425
    # 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)
426 427
      return none if query.blank?

428 429
      query = query.downcase

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

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

443
    def by_login(login)
444 445 446 447 448 449 450
      return nil unless login

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

453
    def find_by_username(username)
454
      by_username(username).take
455 456
    end

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

T
Timothy Andrew 已提交
461
    def find_by_personal_access_token(token_string)
462 463
      return unless token_string

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

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

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

477 478 479
    def reference_prefix
      '@'
    end
480 481 482 483

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

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

    # 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 已提交
509
  end
R
randx 已提交
510

M
Michael Kozono 已提交
511 512 513 514
  def full_path
    username
  end

515 516 517 518
  def self.internal_attributes
    [:ghost]
  end

519
  def internal?
520 521 522 523 524 525 526 527
    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
528
    where(internal_attributes.map { |attr| "#{attr} IS NOT TRUE" }.join(" AND "))
529 530
  end

531 532 533
  #
  # Instance methods
  #
534 535 536 537 538

  def to_param
    username
  end

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

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

  def skip_reconfirmation=(bool)
    skip_reconfirmation! if bool
R
randx 已提交
549
  end
550

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

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

M
Marin Jankovski 已提交
557
    @reset_token
558 559
  end

560 561 562 563
  def recently_sent_password_reset?
    reset_password_sent_at.present? && reset_password_sent_at >= 1.minute.ago
  end

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

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

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

  def two_factor_enabled?
    two_factor_otp_enabled? || two_factor_u2f_enabled?
  end

  def two_factor_otp_enabled?
591
    otp_required_for_login?
592 593 594
  end

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

602 603 604 605 606 607
  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

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

614
  def owns_notification_email
615
    return if temp_oauth_email?
616

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

620
  def owns_public_email
621
    return if public_email.blank?
622

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

626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651
  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
    return unless has_attribute?(:commit_email)

    # 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 已提交
652 653 654 655 656
  # 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

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

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

673
  def update_invalid_gpg_signatures
674
    gpg_keys.each(&:update_invalid_gpg_signatures)
675 676
  end

677
  # Returns the groups a user has access to, either through a membership or a project authorization
678
  def authorized_groups
679 680
    union = Gitlab::SQL::Union
      .new([groups.select(:id), authorized_projects.select(:namespace_id)])
681

682
    Group.where("namespaces.id IN (#{union.to_sql})") # rubocop:disable GitlabSecurity/SqlInjection
683 684
  end

685 686 687 688 689
  # 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

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

  def expanded_groups_requiring_two_factor_authentication
    all_expanded_groups.where(require_two_factor_authentication: true)
  end

700
  # rubocop: disable CodeReuse/ServiceClass
701
  def refresh_authorized_projects
702 703
    Users::RefreshAuthorizedProjectsService.new(self).execute
  end
704
  # rubocop: enable CodeReuse/ServiceClass
705 706

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

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

    if min_access_level
716 717
      projects = projects
        .where('project_authorizations.access_level >= ?', min_access_level)
718
    end
719 720 721 722 723 724

    projects
  end

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

727 728 729 730 731 732 733 734 735
  # 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

736 737 738 739 740 741 742 743 744 745
  # 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

746
  def owned_projects
747
    @owned_projects ||= Project.from("(#{owned_projects_union.to_sql}) AS projects")
748 749
  end

750 751 752 753
  # 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 已提交
754
    authorized_projects(Gitlab::Access::REPORTER).non_archived.with_issues_enabled
755 756
  end

757
  # rubocop: disable CodeReuse/ServiceClass
D
Dmitriy Zaporozhets 已提交
758
  def require_ssh_key?
Y
Yorick Peterse 已提交
759 760 761
    count = Users::KeysCountService.new(self).count

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

765 766 767 768 769 770
  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?
771 772
  end

773
  def require_personal_access_token_creation_for_git_auth?
774
    return false if allow_password_authentication_for_git? || ldap_user?
775 776

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

779 780 781 782
  def require_extra_setup_for_git_auth?
    require_password_creation_for_git? || require_personal_access_token_creation_for_git_auth?
  end

783
  def allow_password_authentication?
784 785 786 787
    allow_password_authentication_for_web? || allow_password_authentication_for_git?
  end

  def allow_password_authentication_for_web?
788
    Gitlab::CurrentSettings.password_authentication_enabled_for_web? && !ldap_user?
789 790 791
  end

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

795
  def can_change_username?
796
    gitlab_config.username_changing_enabled
797 798
  end

D
Dmitriy Zaporozhets 已提交
799
  def can_create_project?
800
    projects_limit_left > 0
D
Dmitriy Zaporozhets 已提交
801 802 803
  end

  def can_create_group?
804
    can?(:create_group)
D
Dmitriy Zaporozhets 已提交
805 806
  end

807 808 809 810
  def can_select_namespace?
    several_namespaces? || admin
  end

811
  def can?(action, subject = :global)
H
http://jneen.net/ 已提交
812
    Ability.allowed?(self, action, subject)
D
Dmitriy Zaporozhets 已提交
813 814
  end

815 816 817 818
  def confirm_deletion_with_password?
    !password_automatically_set? && allow_password_authentication?
  end

D
Dmitriy Zaporozhets 已提交
819 820 821 822
  def first_name
    name.split.first unless name.blank?
  end

823
  def projects_limit_left
824 825 826
    projects_limit - personal_projects_count
  end

827
  # rubocop: disable CodeReuse/ServiceClass
828 829
  def recent_push(project = nil)
    service = Users::LastPushEventService.new(self)
D
Dmitriy Zaporozhets 已提交
830

831 832 833 834
    if project
      service.last_event_for_project(project)
    else
      service.last_event_for_user
835
    end
D
Dmitriy Zaporozhets 已提交
836
  end
837
  # rubocop: enable CodeReuse/ServiceClass
D
Dmitriy Zaporozhets 已提交
838 839

  def several_namespaces?
840
    owned_groups.any? || maintainers_groups.any?
D
Dmitriy Zaporozhets 已提交
841 842 843 844 845
  end

  def namespace_id
    namespace.try :id
  end
846

847 848 849
  def name_with_username
    "#{name} (#{username})"
  end
D
Dmitriy Zaporozhets 已提交
850

851
  def already_forked?(project)
852 853 854
    !!fork_of(project)
  end

855
  def fork_of(project)
856
    namespace.find_fork_of(project)
857
  end
858 859

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

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

871
  def project_deploy_keys
872
    DeployKey.unscoped.in_projects(authorized_projects.pluck(:id)).distinct(:id)
873 874
  end

875
  def accessible_deploy_keys
876 877 878 879 880
    @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
881
  end
882 883

  def created_by
S
skv 已提交
884
    User.find_by(id: created_by_id) if created_by_id
885
  end
886 887

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

894
  def set_notification_email
895
    if notification_email.blank? || all_emails.exclude?(notification_email)
896
      self.notification_email = email
897 898 899
    end
  end

900
  def set_public_email
901
    if public_email.blank? || all_emails.exclude?(public_email)
902
      self.public_email = ''
903 904 905
    end
  end

906 907 908 909 910 911
  def set_commit_email
    if commit_email.blank? || verified_emails.exclude?(commit_email)
      self.commit_email = nil
    end
  end

912
  def update_secondary_emails!
913 914
    set_notification_email
    set_public_email
915 916
    set_commit_email
    save if notification_email_changed? || public_email_changed? || commit_email_changed?
917 918
  end

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

925
    self.projects_limit = Gitlab::CurrentSettings.default_projects_limit
926 927
  end

928
  def requires_ldap_check?
929 930 931
    if !Gitlab.config.ldap.enabled
      false
    elsif ldap_user?
932 933 934 935 936 937
      !last_credential_check_at || (last_credential_check_at + 1.hour) < Time.now
    else
      false
    end
  end

J
Jacob Vosmaer 已提交
938 939 940 941 942 943 944
  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

945 946 947 948 949
  def solo_owned_groups
    @solo_owned_groups ||= owned_groups.select do |group|
      group.owners == [self]
    end
  end
950 951

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

    self
957
  end
958

959 960 961 962
  def can_leave_project?(project)
    project.namespace != namespace &&
      project.project_member(self)
  end
963

J
Jerome Dalbert 已提交
964
  def full_website_url
965
    return "http://#{website_url}" if website_url !~ %r{\Ahttps?://}
J
Jerome Dalbert 已提交
966 967 968 969 970

    website_url
  end

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

974
  def all_ssh_keys
975
    keys.map(&:publishable_key)
976
  end
977 978

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

982
  # rubocop: disable CodeReuse/ServiceClass
983
  def avatar_url(size: nil, scale: 2, **args)
984
    GravatarService.new.execute(email, size, scale, username: username)
985
  end
986
  # rubocop: enable CodeReuse/ServiceClass
D
Dmitriy Zaporozhets 已提交
987

988 989 990 991
  def primary_email_verified?
    confirmed? && !temp_oauth_email?
  end

992 993 994 995 996 997 998 999 1000 1001
  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

1002
  def all_emails
1003
    all_emails = []
1004 1005
    all_emails << email unless temp_oauth_email?
    all_emails.concat(emails.map(&:email))
1006
    all_emails
1007 1008
  end

1009
  def verified_emails
1010
    verified_emails = []
1011
    verified_emails << email if primary_email_verified?
1012
    verified_emails.concat(emails.confirmed.pluck(:email))
1013 1014 1015
    verified_emails
  end

1016
  def verified_email?(check_email)
1017
    downcased = check_email.downcase
A
Alexandra 已提交
1018
    email == downcased ? primary_email_verified? : emails.confirmed.where(email: downcased).exists?
1019 1020
  end

K
Kirill Zaitsev 已提交
1021 1022 1023 1024
  def hook_attrs
    {
      name: name,
      username: username,
1025
      avatar_url: avatar_url(only_path: false)
K
Kirill Zaitsev 已提交
1026 1027 1028
    }
  end

D
Dmitriy Zaporozhets 已提交
1029
  def ensure_namespace_correct
1030 1031 1032 1033
    if namespace
      namespace.path = namespace.name = username if username_changed?
    else
      build_namespace(path: username, name: username)
D
Dmitriy Zaporozhets 已提交
1034 1035 1036
    end
  end

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

1042 1043 1044 1045
  def username_changed_hook
    system_hook_service.execute_hooks_for(self, :rename)
  end

D
Dmitriy Zaporozhets 已提交
1046
  def post_destroy_hook
1047
    log_info("User \"#{name}\" (#{email})  was removed")
D
Douwe Maan 已提交
1048

D
Dmitriy Zaporozhets 已提交
1049 1050 1051
    system_hook_service.execute_hooks_for(self, :destroy)
  end

1052
  # rubocop: disable CodeReuse/ServiceClass
Y
Yorick Peterse 已提交
1053 1054 1055
  def remove_key_cache
    Users::KeysCountService.new(self).delete_cache
  end
1056
  # rubocop: enable CodeReuse/ServiceClass
Y
Yorick Peterse 已提交
1057

N
Nick Thomas 已提交
1058 1059
  def delete_async(deleted_by:, params: {})
    block if params[:hard_delete]
1060
    DeleteUserWorker.perform_async(deleted_by.id, id, params.to_h)
N
Nick Thomas 已提交
1061 1062
  end

1063
  # rubocop: disable CodeReuse/ServiceClass
D
Dmitriy Zaporozhets 已提交
1064
  def notification_service
D
Dmitriy Zaporozhets 已提交
1065 1066
    NotificationService.new
  end
1067
  # rubocop: enable CodeReuse/ServiceClass
D
Dmitriy Zaporozhets 已提交
1068

1069
  def log_info(message)
D
Dmitriy Zaporozhets 已提交
1070 1071 1072
    Gitlab::AppLogger.info message
  end

1073
  # rubocop: disable CodeReuse/ServiceClass
D
Dmitriy Zaporozhets 已提交
1074 1075 1076
  def system_hook_service
    SystemHooksService.new
  end
1077
  # rubocop: enable CodeReuse/ServiceClass
C
Ciro Santilli 已提交
1078 1079

  def starred?(project)
V
Valery Sizov 已提交
1080
    starred_projects.exists?(project.id)
C
Ciro Santilli 已提交
1081 1082 1083
  end

  def toggle_star(project)
1084
    UsersStarProject.transaction do
1085 1086
      user_star_project = users_star_projects
          .where(project: project, user: self).lock(true).first
1087 1088 1089 1090 1091 1092

      if user_star_project
        user_star_project.destroy
      else
        UsersStarProject.create!(project: project, user: self)
      end
C
Ciro Santilli 已提交
1093 1094
    end
  end
1095 1096

  def manageable_namespaces
1097 1098 1099 1100
    @manageable_namespaces ||= [namespace] + manageable_groups
  end

  def manageable_groups
1101
    Gitlab::GroupHierarchy.new(owned_or_maintainers_groups).base_and_descendants
1102
  end
D
Dmitriy Zaporozhets 已提交
1103

1104 1105 1106 1107 1108 1109
  def namespaces
    namespace_ids = groups.pluck(:id)
    namespace_ids.push(namespace.id)
    Namespace.where(id: namespace_ids)
  end

D
Dmitriy Zaporozhets 已提交
1110
  def oauth_authorized_tokens
1111
    Doorkeeper::AccessToken.where(resource_owner_id: id, revoked_at: nil)
D
Dmitriy Zaporozhets 已提交
1112
  end
1113

1114 1115 1116 1117 1118 1119 1120 1121 1122
  # 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
1123
  # ms on a database with a similar size to GitLab.com's database. On the other
1124 1125
  # hand, using a subquery means we can get the exact same data in about 40 ms.
  def contributed_projects
1126 1127 1128 1129 1130
    events = Event.select(:project_id)
      .contributions.where(author_id: self)
      .where("created_at > ?", Time.now - 1.year)
      .uniq
      .reorder(nil)
1131 1132

    Project.where(id: events)
1133
  end
1134

1135 1136 1137
  def can_be_removed?
    !solo_owned_groups.present?
  end
1138

1139 1140
  def ci_owned_runners
    @ci_owned_runners ||= begin
1141
      project_runner_ids = Ci::RunnerProject
1142
        .where(project: authorized_projects(Gitlab::Access::MAINTAINER))
1143
        .select(:runner_id)
1144 1145

      group_runner_ids = Ci::RunnerNamespace
1146
        .where(namespace_id: owned_or_maintainers_groups.select(:id))
1147 1148 1149 1150
        .select(:runner_id)

      union = Gitlab::SQL::Union.new([project_runner_ids, group_runner_ids])

1151
      Ci::Runner.where("ci_runners.id IN (#{union.to_sql})") # rubocop:disable GitlabSecurity/SqlInjection
K
Kamil Trzcinski 已提交
1152
    end
1153
  end
1154

1155
  def notification_settings_for(source)
1156
    if notification_settings.loaded?
1157 1158 1159 1160
      notification_settings.find do |notification|
        notification.source_type == source.class.base_class.name &&
          notification.source_id == source.id
      end
1161 1162 1163
    else
      notification_settings.find_or_initialize_by(source: source)
    end
1164 1165
  end

1166 1167 1168
  # Lazy load global notification setting
  # Initializes User setting with Participating level if setting not persisted
  def global_notification_setting
1169 1170 1171
    return @global_notification_setting if defined?(@global_notification_setting)

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

    @global_notification_setting
1175 1176
  end

1177
  def assigned_open_merge_requests_count(force: false)
1178
    Rails.cache.fetch(['users', id, 'assigned_open_merge_requests_count'], force: force, expires_in: 20.minutes) do
1179
      MergeRequestsFinder.new(self, assignee_id: self.id, state: 'opened').execute.count
1180 1181 1182
    end
  end

J
Josh Frye 已提交
1183
  def assigned_open_issues_count(force: false)
1184
    Rails.cache.fetch(['users', id, 'assigned_open_issues_count'], force: force, expires_in: 20.minutes) do
1185
      IssuesFinder.new(self, assignee_id: self.id, state: 'opened').execute.count
1186
    end
1187 1188
  end

1189 1190 1191 1192 1193 1194 1195 1196 1197 1198 1199 1200
  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 已提交
1201 1202 1203 1204 1205 1206
  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

1207 1208 1209 1210 1211
  def update_todos_count_cache
    todos_done_count(force: true)
    todos_pending_count(force: true)
  end

1212
  def invalidate_cache_counts
1213 1214
    invalidate_issue_cache_counts
    invalidate_merge_request_cache_counts
1215 1216
    invalidate_todos_done_count
    invalidate_todos_pending_count
A
Andreas Brandl 已提交
1217
    invalidate_personal_projects_count
1218 1219 1220
  end

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

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

1228 1229
  def invalidate_todos_done_count
    Rails.cache.delete(['users', id, 'todos_done_count'])
P
Paco Guzman 已提交
1230 1231
  end

1232 1233
  def invalidate_todos_pending_count
    Rails.cache.delete(['users', id, 'todos_pending_count'])
P
Paco Guzman 已提交
1234 1235
  end

A
Andreas Brandl 已提交
1236 1237 1238 1239
  def invalidate_personal_projects_count
    Rails.cache.delete(['users', id, 'personal_projects_count'])
  end

1240 1241 1242 1243 1244 1245
  # 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>
  #
1246
  # rubocop: disable CodeReuse/ServiceClass
1247
  def increment_failed_attempts!
1248 1249
    return if ::Gitlab::Database.read_only?

1250 1251
    self.failed_attempts ||= 0
    self.failed_attempts += 1
1252

1253 1254 1255
    if attempts_exceeded?
      lock_access! unless access_locked?
    else
J
James Lopez 已提交
1256
      Users::UpdateService.new(self, user: self).execute(validate: false)
1257 1258
    end
  end
1259
  # rubocop: enable CodeReuse/ServiceClass
1260

1261 1262 1263 1264 1265 1266 1267 1268 1269
  def access_level
    if admin?
      :admin
    else
      :regular
    end
  end

  def access_level=(new_level)
D
Douwe Maan 已提交
1270 1271
    new_level = new_level.to_s
    return unless %w(admin regular).include?(new_level)
1272

D
Douwe Maan 已提交
1273 1274
    self.admin = (new_level == 'admin')
  end
1275

1276 1277 1278 1279 1280 1281
  # Does the user have access to all private groups & projects?
  # Overridden in EE to also check auditor?
  def full_private_access?
    admin?
  end

1282
  def update_two_factor_requirement
1283
    periods = expanded_groups_requiring_two_factor_authentication.pluck(:two_factor_grace_period)
1284

1285
    self.require_two_factor_authentication_from_group = periods.any?
1286 1287 1288 1289 1290
    self.two_factor_grace_period = periods.min || User.column_defaults['two_factor_grace_period']

    save
  end

1291
  # each existing user needs to have an `feed_token`.
A
Alexis Reigel 已提交
1292 1293
  # we do this on read since migrating all existing users is not a feasible
  # solution.
1294 1295
  def feed_token
    ensure_feed_token!
A
Alexis Reigel 已提交
1296 1297
  end

1298 1299 1300 1301 1302 1303 1304 1305 1306 1307 1308 1309 1310 1311 1312 1313
  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 已提交
1314 1315
  # override, from Devise
  def lock_access!
1316
    Gitlab::AppLogger.info("Account Locked: username=#{username}")
B
Brian Neel 已提交
1317 1318 1319
    super
  end

1320 1321 1322 1323 1324 1325 1326 1327 1328 1329 1330 1331 1332 1333 1334 1335 1336 1337 1338 1339 1340 1341 1342 1343 1344 1345 1346 1347
  # 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

1348 1349 1350 1351
  def terms_accepted?
    accepted_term_id.present?
  end

1352 1353 1354 1355 1356
  def required_terms_not_accepted?
    Gitlab::CurrentSettings.current_application_settings.enforce_terms? &&
      !terms_accepted?
  end

1357 1358 1359 1360
  def requires_usage_stats_consent?
    !consented_usage_stats? && 7.days.ago > self.created_at && !has_current_license? && User.single_user?
  end

1361 1362 1363
  # @deprecated
  alias_method :owned_or_masters_groups, :owned_or_maintainers_groups

1364 1365 1366 1367 1368
  protected

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

1370 1371 1372
    super
  end

1373 1374
  private

1375 1376 1377 1378 1379 1380 1381 1382
  def has_current_license?
    false
  end

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

1383 1384 1385 1386 1387 1388 1389 1390 1391
  def owned_projects_union
    Gitlab::SQL::Union.new([
      Project.where(namespace: namespace),
      Project.joins(:project_authorizations)
        .where("projects.namespace_id <> ?", namespace.id)
        .where(project_authorizations: { user_id: id, access_level: Gitlab::Access::OWNER })
    ], remove_duplicates: false)
  end

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

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

1399 1400 1401 1402 1403 1404 1405 1406 1407
  # 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

1408 1409 1410 1411 1412
  def ensure_user_rights_and_limits
    if external?
      self.can_create_group = false
      self.projects_limit   = 0
    else
1413 1414
      # 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?
1415
      self.projects_limit = Gitlab::CurrentSettings.default_projects_limit unless projects_limit_changed?
1416
    end
Z
Zeger-Jan van de Weg 已提交
1417
  end
1418

1419 1420 1421 1422
  def signup_domain_valid?
    valid = true
    error = nil

1423 1424
    if Gitlab::CurrentSettings.domain_blacklist_enabled?
      blocked_domains = Gitlab::CurrentSettings.domain_blacklist
1425
      if domain_matches?(blocked_domains, email)
1426 1427 1428 1429 1430
        error = 'is not from an allowed domain.'
        valid = false
      end
    end

1431
    allowed_domains = Gitlab::CurrentSettings.domain_whitelist
1432
    unless allowed_domains.blank?
1433
      if domain_matches?(allowed_domains, email)
1434 1435
        valid = true
      else
D
dev-chris 已提交
1436
        error = "domain is not authorized for sign-up"
1437 1438 1439 1440
        valid = false
      end
    end

1441
    errors.add(:email, error) unless valid
1442 1443 1444

    valid
  end
1445

1446
  def domain_matches?(email_domains, email)
1447 1448 1449 1450 1451 1452 1453
    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
1454 1455 1456 1457

  def generate_token(token_field)
    if token_field == :incoming_email_token
      # Needs to be all lowercase and alphanumeric because it's gonna be used in an email address.
1458
      SecureRandom.hex.to_i(16).to_s(36)
1459 1460 1461 1462
    else
      super
    end
  end
1463

1464 1465
  def self.unique_internal(scope, username, email_pattern, &block)
    scope.first || create_unique_internal(scope, username, email_pattern, &block)
1466 1467 1468 1469
  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
1470
    # exclusive lease to ensure than this block is never run concurrently.
1471
    lease_key = "user:unique_internal:#{username}"
1472 1473 1474 1475 1476 1477 1478 1479
    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

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

    uniquify = Uniquify.new

1488
    username = uniquify.string(username) { |s| User.find_by_username(s) }
1489

1490
    email = uniquify.string(-> (n) { Kernel.sprintf(email_pattern, n) }) do |s|
1491 1492 1493
      User.find_by_email(s)
    end

1494
    user = scope.build(
1495 1496 1497
      username: username,
      email: email,
      &creation_block
1498
    )
J
James Lopez 已提交
1499

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