Read about our upcoming Code of Conduct on this issue

user.rb 43.1 KB
Newer Older
Steven Thonus's avatar
Steven Thonus committed
1
2
require 'carrierwave/orm/activerecord'

gitlabhq's avatar
gitlabhq committed
3
class User < ActiveRecord::Base
4
  extend Gitlab::ConfigHelper
5
6

  include Gitlab::ConfigHelper
Hiroyuki Sato's avatar
Hiroyuki Sato committed
7
  include Gitlab::SQL::Pattern
8
  include AfterCommitQueue
9
  include Avatarable
10
11
  include Referable
  include Sortable
12
  include CaseSensitivity
13
  include TokenAuthenticatable
14
  include IgnorableColumn
15
  include FeatureGate
16
  include CreatedAtFilterable
17
  include IgnorableColumn
18
  include BulkMemberAccessLoad
19
  include BlocksJsonSerialization
Jan Provaznik's avatar
Jan Provaznik committed
20
  include WithUploads
21

22
23
  DEFAULT_NOTIFICATION_LEVEL = :participating

24
25
  ignore_column :external_email
  ignore_column :email_provider
26
  ignore_column :authentication_token
27

28
  add_authentication_token_field :incoming_email_token
29
  add_authentication_token_field :feed_token
30

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

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

48
  devise :two_factor_authenticatable,
49
         otp_secret_encryption_key: Gitlab::Application.secrets.otp_key_base
Robert Speicher's avatar
Robert Speicher committed
50

51
  devise :two_factor_backupable, otp_number_of_backup_codes: 10
52
  serialize :otp_backup_codes, JSON # rubocop:disable Cop/ActiveRecordSerialize
Robert Speicher's avatar
Robert Speicher committed
53

54
  devise :lockable, :recoverable, :rememberable, :trackable,
55
56
57
58
         :validatable, :omniauthable, :confirmable, :registerable

  BLOCKED_MESSAGE = "Your account has been blocked. Please contact your GitLab " \
                    "administrator if you think this is an error.".freeze
gitlabhq's avatar
gitlabhq committed
59

60
61
  # Override Devise::Models::Trackable#update_tracked_fields!
  # to limit database writes to at most once every hour
62
  def update_tracked_fields!(request)
63
64
    return if Gitlab::Database.read_only?

65
66
    update_tracked_fields(request)

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

James Lopez's avatar
James Lopez committed
70
    Users::UpdateService.new(self, user: self).execute(validate: false)
71
72
  end

73
  attr_accessor :force_random_password
gitlabhq's avatar
gitlabhq committed
74

75
76
77
  # Virtual attribute for authenticating by either username or email
  attr_accessor :login

78
79
80
81
  #
  # Relations
  #

82
  # Namespace for personal projects
83
  has_one :namespace, -> { where(type: nil) }, dependent: :destroy, foreign_key: :owner_id, inverse_of: :owner, autosave: true # rubocop:disable Cop/ActiveRecordDependent
84
85

  # Profile
86
87
  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
88
  has_many :gpg_keys
89

90
91
92
93
94
  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
95
  has_one :user_synced_attributes_metadata, autosave: true
96
97

  # Groups
98
99
  has_many :members
  has_many :group_members, -> { where(requested_at: nil) }, source: 'GroupMember'
100
101
  has_many :groups, through: :group_members
  has_many :owned_groups, -> { where(members: { access_level: Gitlab::Access::OWNER }) }, through: :group_members, source: :group
102
103
  has_many :maintainers_groups, -> { where(members: { access_level: Gitlab::Access::MAINTAINER }) }, through: :group_members, source: :group
  alias_attribute :masters_groups, :maintainers_groups
104

105
  # Projects
106
107
  has_many :groups_projects,          through: :groups, source: :projects
  has_many :personal_projects,        through: :namespace, source: :projects
108
  has_many :project_members, -> { where(requested_at: nil) }
109
110
  has_many :projects,                 through: :project_members
  has_many :created_projects,         foreign_key: :creator_id, class_name: 'Project'
111
  has_many :users_star_projects, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
112
  has_many :starred_projects, through: :users_star_projects, source: :project
113
  has_many :project_authorizations, dependent: :delete_all # rubocop:disable Cop/ActiveRecordDependent
114
  has_many :authorized_projects, through: :project_authorizations, source: :project
115

116
  has_many :user_interacted_projects
117
  has_many :project_interactions, through: :user_interacted_projects, source: :project, class_name: 'Project'
118

119
120
121
122
123
124
125
126
127
128
129
130
  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
131
  has_many :todos
132
133
134
  has_many :notification_settings,    dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
  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
135

136
  has_many :issue_assignees
137
  has_many :assigned_issues, class_name: "Issue", through: :issue_assignees, source: :issue
138
  has_many :assigned_merge_requests,  dependent: :nullify, foreign_key: :assignee_id, class_name: "MergeRequest" # rubocop:disable Cop/ActiveRecordDependent
139

140
  has_many :custom_attributes, class_name: 'UserCustomAttribute'
Matija Čupić's avatar
Matija Čupić committed
141
  has_many :callouts, class_name: 'UserCallout'
142
143
  has_many :term_agreements
  belongs_to :accepted_term, class_name: 'ApplicationSetting::Term'
144

145
146
147
  #
  # Validations
  #
148
  # Note: devise :validatable above adds validations for :email and :password
Cyril Jouve's avatar
Cyril Jouve committed
149
  validates :name, presence: true
Douwe Maan's avatar
Douwe Maan committed
150
  validates :email, confirmation: true
151
152
  validates :notification_email, presence: true
  validates :notification_email, email: true, if: ->(user) { user.notification_email != user.email }
153
  validates :public_email, presence: true, uniqueness: true, email: true, allow_blank: true
154
  validates :bio, length: { maximum: 255 }, allow_blank: true
155
156
157
  validates :projects_limit,
    presence: true,
    numericality: { greater_than_or_equal_to: 0, less_than_or_equal_to: Gitlab::Database::MAX_INT_VALUE }
158
  validates :username, presence: true
159

160
  validates :namespace, presence: true
161
162
  validate :namespace_move_dir_allowed, if: :username_changed?

163
164
165
  validate :unique_email, if: :email_changed?
  validate :owns_notification_email, if: :notification_email_changed?
  validate :owns_public_email, if: :public_email_changed?
166
  validate :signup_domain_valid?, on: :create, if: ->(user) { !user.created_by_id }
167

168
  before_validation :sanitize_attrs
169
  before_validation :set_notification_email, if: :new_record?
170
  before_validation :set_public_email, if: :public_email_changed?
171
  before_save :set_public_email, if: :public_email_changed? # in case validation is skipped
Douwe Maan's avatar
Douwe Maan committed
172
  before_save :ensure_incoming_email_token
173
  before_save :ensure_user_rights_and_limits, if: ->(user) { user.new_record? || user.external_changed? }
174
  before_save :skip_reconfirmation!, if: ->(user) { user.email_changed? && user.read_only_attribute?(:email) }
Alexandra's avatar
Alexandra committed
175
  before_save :check_for_verified_email, if: ->(user) { user.email_changed? && !user.new_record? }
176
  before_validation :ensure_namespace_correct
177
  before_save :ensure_namespace_correct # in case validation is skipped
178
  after_validation :set_username_errors
179
  after_update :username_changed_hook, if: :username_changed?
180
  after_destroy :post_destroy_hook
181
  after_destroy :remove_key_cache
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
  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
197

198
  after_initialize :set_projects_limit
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
199

200
  # User's Layout preference
201
  enum layout: [:fixed, :fluid]
202

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

207
  # User's Project preference
208
209
210
  # Note: When adding an option, it MUST go on the end of the array.
  enum project_view: [:readme, :activity, :files]

211
  delegate :path, to: :namespace, allow_nil: true, prefix: true
212

213
214
215
  state_machine :state, initial: :active do
    event :block do
      transition active: :blocked
216
      transition ldap_blocked: :blocked
217
218
    end

219
220
221
222
    event :ldap_block do
      transition active: :ldap_blocked
    end

223
224
    event :activate do
      transition blocked: :active
225
      transition ldap_blocked: :active
226
    end
227
228
229
230
231

    state :blocked, :ldap_blocked do
      def blocked?
        true
      end
232
233
234
235
236
237

      def active_for_authentication?
        false
      end

      def inactive_message
238
        BLOCKED_MESSAGE
239
      end
240
    end
241
242
  end

Andrey Kumanyaev's avatar
Andrey Kumanyaev committed
243
  # Scopes
244
  scope :admins, -> { where(admin: true) }
245
  scope :blocked, -> { with_states(:blocked, :ldap_blocked) }
246
  scope :external, -> { where(external: true) }
James Lopez's avatar
James Lopez committed
247
  scope :active, -> { with_state(:active).non_internal }
248
  scope :without_projects, -> { joins('LEFT JOIN project_authorizations ON users.id = project_authorizations.user_id').where(project_authorizations: { user_id: nil }) }
249
  scope :todo_authors, ->(user_id, state) { where(id: Todo.where(user_id: user_id, state: state).select(:author_id)) }
250
251
  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')) }
252

253
  def self.with_two_factor_indistinct
254
    joins("LEFT OUTER JOIN u2f_registrations AS u2f ON u2f.user_id = users.id")
255
256
257
258
259
      .where("u2f.id IS NOT NULL OR users.otp_required_for_login = ?", true)
  end

  def self.with_two_factor
    with_two_factor_indistinct.distinct(arel_table[:id])
260
261
262
  end

  def self.without_two_factor
263
    joins("LEFT OUTER JOIN u2f_registrations AS u2f ON u2f.user_id = users.id")
264
      .where("u2f.id IS NULL AND users.otp_required_for_login = ?", false)
265
  end
Andrey Kumanyaev's avatar
Andrey Kumanyaev committed
266

267
268
269
  #
  # Class methods
  #
Andrey Kumanyaev's avatar
Andrey Kumanyaev committed
270
  class << self
271
    # Devise method overridden to allow sign in with email or username
272
273
274
    def find_for_database_authentication(warden_conditions)
      conditions = warden_conditions.dup
      if login = conditions.delete(:login)
275
        where(conditions).find_by("lower(username) = :value OR lower(email) = :value", value: login.downcase.strip)
276
      else
Gabriel Mazetto's avatar
Gabriel Mazetto committed
277
        find_by(conditions)
278
279
      end
    end
280

281
    def sort_by_attribute(method)
282
283
284
      order_method = method || 'id_desc'

      case order_method.to_s
285
286
      when 'recent_sign_in' then order_recent_sign_in
      when 'oldest_sign_in' then order_oldest_sign_in
287
      else
288
        order_by(order_method)
Valery Sizov's avatar
Valery Sizov committed
289
290
291
      end
    end

292
    def for_github_id(id)
293
      joins(:identities).merge(Identity.with_extern_uid(:github, id))
294
295
    end

296
297
    # Find a User by their primary email or any associated secondary email
    def find_by_any_email(email)
298
299
300
301
302
303
304
305
306
307
      by_any_email(email).take
    end

    # Returns a relation containing all the users for the given Email address
    def by_any_email(email)
      users = where(email: email)
      emails = joins(:emails).where(emails: { email: email })
      union = Gitlab::SQL::Union.new([users, emails])

      from("(#{union.to_sql}) #{table_name}")
308
    end
309

310
    def filter(filter_name)
Andrey Kumanyaev's avatar
Andrey Kumanyaev committed
311
      case filter_name
312
      when 'admins'
313
        admins
314
      when 'blocked'
315
        blocked
316
      when 'two_factor_disabled'
317
        without_two_factor
318
      when 'two_factor_enabled'
319
        with_two_factor
320
      when 'wop'
321
        without_projects
322
      when 'external'
323
        external
Andrey Kumanyaev's avatar
Andrey Kumanyaev committed
324
      else
325
        active
Andrey Kumanyaev's avatar
Andrey Kumanyaev committed
326
      end
327
328
    end

329
330
331
332
333
334
335
    # 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.
336
    def search(query)
337
338
      return none if query.blank?

339
340
      query = query.downcase

341
342
343
344
345
346
347
348
349
      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

350
      where(
351
352
        fuzzy_arel_match(:name, query, lower_exact_match: true)
          .or(fuzzy_arel_match(:username, query, lower_exact_match: true))
353
          .or(arel_table[:email].eq(query))
354
      ).reorder(order % { query: ActiveRecord::Base.connection.quote(query) }, :name)
Andrey Kumanyaev's avatar
Andrey Kumanyaev committed
355
    end
356

357
358
359
360
361
    # 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)
362
363
      return none if query.blank?

364
365
      query = query.downcase

366
      email_table = Email.arel_table
367
368
      matched_by_emails_user_ids = email_table
        .project(email_table[:user_id])
369
        .where(email_table[:email].eq(query))
370
371

      where(
372
373
        fuzzy_arel_match(:name, query)
          .or(fuzzy_arel_match(:username, query))
374
          .or(arel_table[:email].eq(query))
375
          .or(arel_table[:id].in(matched_by_emails_user_ids))
376
377
378
      )
    end

379
    def by_login(login)
380
381
382
383
384
385
386
      return nil unless login

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

389
390
391
392
    def find_by_username(username)
      iwhere(username: username).take
    end

Robert Speicher's avatar
Robert Speicher committed
393
    def find_by_username!(username)
394
      iwhere(username: username).take!
Robert Speicher's avatar
Robert Speicher committed
395
396
    end

Timothy Andrew's avatar
Timothy Andrew committed
397
    def find_by_personal_access_token(token_string)
398
399
      return unless token_string

400
      PersonalAccessTokensFinder.new(state: 'active').find_by(token: token_string)&.user
Timothy Andrew's avatar
Timothy Andrew committed
401
402
    end

Yorick Peterse's avatar
Yorick Peterse committed
403
404
    # Returns a user for the given SSH key.
    def find_by_ssh_key_id(key_id)
405
      Key.find_by(id: key_id)&.user
Yorick Peterse's avatar
Yorick Peterse committed
406
407
    end

408
    def find_by_full_path(path, follow_redirects: false)
409
410
      namespace = Namespace.for_user.find_by_full_path(path, follow_redirects: follow_redirects)
      namespace&.owner
411
412
    end

413
414
415
    def reference_prefix
      '@'
    end
416
417
418
419

    # Pattern used to extract `@user` user references from text
    def reference_pattern
      %r{
420
        (?<!\w)
421
        #{Regexp.escape(reference_prefix)}
422
        (?<user>#{Gitlab::PathRegex::FULL_NAMESPACE_FORMAT_REGEX})
423
424
      }x
    end
425
426
427
428

    # Return (create if necessary) the ghost user. The ghost user
    # owns records previously belonging to deleted users.
    def ghost
429
430
      email = 'ghost%s@example.com'
      unique_internal(where(ghost: true), 'ghost', email) do |u|
431
432
433
        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
434
    end
vsizov's avatar
vsizov committed
435
  end
randx's avatar
randx committed
436

Michael Kozono's avatar
Michael Kozono committed
437
438
439
440
  def full_path
    username
  end

441
442
443
444
  def self.internal_attributes
    [:ghost]
  end

445
  def internal?
446
447
448
449
450
451
452
453
    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
454
    where(internal_attributes.map { |attr| "#{attr} IS NOT TRUE" }.join(" AND "))
455
456
  end

457
458
459
  #
  # Instance methods
  #
460
461
462
463
464

  def to_param
    username
  end

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

469
470
  def skip_confirmation=(bool)
    skip_confirmation! if bool
Daniel Juarez's avatar
Daniel Juarez committed
471
472
473
474
  end

  def skip_reconfirmation=(bool)
    skip_reconfirmation! if bool
randx's avatar
randx committed
475
  end
476

477
  def generate_reset_token
Marin Jankovski's avatar
Marin Jankovski committed
478
    @reset_token, enc = Devise.token_generator.generate(self.class, :reset_password_token)
479
480
481
482

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

Marin Jankovski's avatar
Marin Jankovski committed
483
    @reset_token
484
485
  end

486
487
488
489
  def recently_sent_password_reset?
    reset_password_sent_at.present? && reset_password_sent_at >= 1.minute.ago
  end

Toon Claes's avatar
Toon Claes committed
490
491
492
493
494
495
496
497
  def remember_me!
    super if ::Gitlab::Database.read_write?
  end

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

Robert Speicher's avatar
Robert Speicher committed
498
  def disable_two_factor!
499
    transaction do
Lin Jen-Shin's avatar
Lin Jen-Shin committed
500
      update(
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
        otp_required_for_login:      false,
        encrypted_otp_secret:        nil,
        encrypted_otp_secret_iv:     nil,
        encrypted_otp_secret_salt:   nil,
        otp_grace_period_started_at: nil,
        otp_backup_codes:            nil
      )
      self.u2f_registrations.destroy_all
    end
  end

  def two_factor_enabled?
    two_factor_otp_enabled? || two_factor_u2f_enabled?
  end

  def two_factor_otp_enabled?
517
    otp_required_for_login?
518
519
520
  end

  def two_factor_u2f_enabled?
521
522
523
524
525
    if u2f_registrations.loaded?
      u2f_registrations.any?
    else
      u2f_registrations.exists?
    end
Robert Speicher's avatar
Robert Speicher committed
526
527
  end

528
529
530
531
532
533
  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

534
  def unique_email
535
536
    if !emails.exists?(email: email) && Email.exists?(email: email)
      errors.add(:email, 'has already been taken')
537
    end
538
539
  end

540
  def owns_notification_email
541
    return if temp_oauth_email?
542

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

546
  def owns_public_email
547
    return if public_email.blank?
548

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

Alexandra's avatar
Alexandra committed
552
553
554
555
556
  # 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

557
  # Note: the use of the Emails services will cause `saves` on the user object, running
558
  # through the callbacks again and can have side effects, such as the `previous_changes`
559
560
561
  # 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
562
  def update_emails_with_primary_email(previous_email)
563
    primary_email_record = emails.find_by(email: email)
564
    Emails::DestroyService.new(self, user: self).execute(primary_email_record) if primary_email_record
565

566
567
    # 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
568
    Emails::CreateService.new(self, user: self, email: previous_email).execute(confirmed_at: confirmed_at)
569
570
  end

571
  def update_invalid_gpg_signatures
572
    gpg_keys.each(&:update_invalid_gpg_signatures)
573
574
  end

575
  # Returns the groups a user has access to, either through a membership or a project authorization
576
  def authorized_groups
577
578
    union = Gitlab::SQL::Union
      .new([groups.select(:id), authorized_projects.select(:namespace_id)])
579

580
    Group.where("namespaces.id IN (#{union.to_sql})") # rubocop:disable GitlabSecurity/SqlInjection
581
582
  end

583
584
585
586
587
  # 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

588
589
  # Returns a relation of groups the user has access to, including their parent
  # and child groups (recursively).
590
  def all_expanded_groups
591
    Gitlab::GroupHierarchy.new(groups).all_groups
592
593
594
595
596
597
  end

  def expanded_groups_requiring_two_factor_authentication
    all_expanded_groups.where(require_two_factor_authentication: true)
  end

598
  def refresh_authorized_projects
599
600
601
602
    Users::RefreshAuthorizedProjectsService.new(self).execute
  end

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

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

    if min_access_level
612
613
      projects = projects
        .where('project_authorizations.access_level >= ?', min_access_level)
614
    end
615
616
617
618
619
620

    projects
  end

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

623
624
625
626
627
628
629
630
631
  # 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

632
633
634
635
636
637
638
639
640
641
  # 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

642
  def owned_projects
643
    @owned_projects ||= Project.from("(#{owned_projects_union.to_sql}) AS projects")
644
645
  end

646
647
648
649
  # 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
Felipe Artur's avatar
Felipe Artur committed
650
    authorized_projects(Gitlab::Access::REPORTER).non_archived.with_issues_enabled
651
652
  end

Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
653
  def require_ssh_key?
654
655
656
    count = Users::KeysCountService.new(self).count

    count.zero? && Gitlab::ProtocolAccess.allowed?('ssh')
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
657
658
  end

659
660
661
662
663
664
  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?
665
666
  end

667
  def require_personal_access_token_creation_for_git_auth?
668
    return false if allow_password_authentication_for_git? || ldap_user?
669
670

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

673
674
675
676
  def require_extra_setup_for_git_auth?
    require_password_creation_for_git? || require_personal_access_token_creation_for_git_auth?
  end

677
  def allow_password_authentication?
678
679
680
681
    allow_password_authentication_for_web? || allow_password_authentication_for_git?
  end

  def allow_password_authentication_for_web?
682
    Gitlab::CurrentSettings.password_authentication_enabled_for_web? && !ldap_user?
683
684
685
  end

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

689
  def can_change_username?
690
    gitlab_config.username_changing_enabled
691
692
  end

Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
693
  def can_create_project?
694
    projects_limit_left > 0
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
695
696
697
  end

  def can_create_group?
698
    can?(:create_group)
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
699
700
  end

701
702
703
704
  def can_select_namespace?
    several_namespaces? || admin
  end

705
  def can?(action, subject = :global)
http://jneen.net/'s avatar
http://jneen.net/ committed
706
    Ability.allowed?(self, action, subject)
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
707
708
  end

709
710
711
712
  def confirm_deletion_with_password?
    !password_automatically_set? && allow_password_authentication?
  end

Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
713
714
715
716
  def first_name
    name.split.first unless name.blank?
  end

717
  def projects_limit_left
718
719
720
    projects_limit - personal_projects_count
  end

721
722
  def recent_push(project = nil)
    service = Users::LastPushEventService.new(self)
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
723

724
725
726
727
    if project
      service.last_event_for_project(project)
    else
      service.last_event_for_user
728
    end
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
729
730
731
  end

  def several_namespaces?
732
    owned_groups.any? || maintainers_groups.any?
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
733
734
735
736
737
  end

  def namespace_id
    namespace.try :id
  end
738

739
740
741
  def name_with_username
    "#{name} (#{username})"
  end
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
742

743
  def already_forked?(project)
744
745
746
    !!fork_of(project)
  end

747
  def fork_of(project)
748
    namespace.find_fork_of(project)
749
  end
750
751

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

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

763
  def project_deploy_keys
764
    DeployKey.unscoped.in_projects(authorized_projects.pluck(:id)).distinct(:id)
765
766
  end

767
  def accessible_deploy_keys
768
769
770
771
772
    @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
773
  end
774
775

  def created_by
skv's avatar
skv committed
776
    User.find_by(id: created_by_id) if created_by_id
777
  end
778
779

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

786
  def set_notification_email
787
    if notification_email.blank? || all_emails.exclude?(notification_email)
788
      self.notification_email = email
789
790
791
    end
  end

792
  def set_public_email
793
    if public_email.blank? || all_emails.exclude?(public_email)
794
      self.public_email = ''
795
796
797
    end
  end

798
  def update_secondary_emails!
799
800
801
    set_notification_email
    set_public_email
    save if notification_email_changed? || public_email_changed?
802
803
  end

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

810
    self.projects_limit = Gitlab::CurrentSettings.default_projects_limit
811
812
  end

813
  def requires_ldap_check?
814
815
816
    if !Gitlab.config.ldap.enabled
      false
    elsif ldap_user?
817
818
819
820
821
822
      !last_credential_check_at || (last_credential_check_at + 1.hour) < Time.now
    else
      false
    end
  end

Jacob Vosmaer's avatar
Jacob Vosmaer committed
823
824
825
826
827
828
829
  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

830
831
832
833
834
  def solo_owned_groups
    @solo_owned_groups ||= owned_groups.select do |group|
      group.owners == [self]
    end
  end
835
836

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

    self
842
  end
843

844
845
846
847
  def can_leave_project?(project)
    project.namespace != namespace &&
      project.project_member(self)
  end
848

Jerome Dalbert's avatar
Jerome Dalbert committed
849
  def full_website_url
850
    return "http://#{website_url}" if website_url !~ %r{\Ahttps?://}
Jerome Dalbert's avatar
Jerome Dalbert committed
851
852
853
854
855

    website_url
  end

  def short_website_url
856
    website_url.sub(%r{\Ahttps?://}, '')
Jerome Dalbert's avatar
Jerome Dalbert committed
857
  end
GitLab's avatar
GitLab committed
858

859
  def all_ssh_keys
860
    keys.map(&:publishable_key)
861
  end
862
863

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

867
  def avatar_url(size: nil, scale: 2, **args)
868
    GravatarService.new.execute(email, size, scale, username: username)
869
  end
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
870

871
872
873
874
  def primary_email_verified?
    confirmed? && !temp_oauth_email?
  end

875
876
877
878
879
880
881
882
883
884
  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

885
  def all_emails
886
    all_emails = []
887
888
    all_emails << email unless temp_oauth_email?
    all_emails.concat(emails.map(&:email))
889
    all_emails
890
891
  end

892
  def verified_emails
893
    verified_emails = []
894
    verified_emails << email if primary_email_verified?
895
    verified_emails.concat(emails.confirmed.pluck(:email))
896
897
898
    verified_emails
  end

899
  def verified_email?(check_email)