Read about our upcoming Code of Conduct on this issue

user.rb 24.2 KB
Newer Older
1
2
3
4
# == Schema Information
#
# Table name: users
#
Stan Hu's avatar
Stan Hu committed
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
#  id                         :integer          not null, primary key
#  email                      :string(255)      default(""), not null
#  encrypted_password         :string(255)      default(""), not null
#  reset_password_token       :string(255)
#  reset_password_sent_at     :datetime
#  remember_created_at        :datetime
#  sign_in_count              :integer          default(0)
#  current_sign_in_at         :datetime
#  last_sign_in_at            :datetime
#  current_sign_in_ip         :string(255)
#  last_sign_in_ip            :string(255)
#  created_at                 :datetime
#  updated_at                 :datetime
#  name                       :string(255)
#  admin                      :boolean          default(FALSE), not null
#  projects_limit             :integer          default(10)
#  skype                      :string(255)      default(""), not null
#  linkedin                   :string(255)      default(""), not null
#  twitter                    :string(255)      default(""), not null
#  authentication_token       :string(255)
#  theme_id                   :integer          default(1), not null
#  bio                        :string(255)
#  failed_attempts            :integer          default(0)
#  locked_at                  :datetime
#  username                   :string(255)
#  can_create_group           :boolean          default(TRUE), not null
#  can_create_team            :boolean          default(TRUE), not null
#  state                      :string(255)
#  color_scheme_id            :integer          default(1), not null
#  notification_level         :integer          default(1), not null
#  password_expires_at        :datetime
#  created_by_id              :integer
#  last_credential_check_at   :datetime
#  avatar                     :string(255)
#  confirmation_token         :string(255)
#  confirmed_at               :datetime
#  confirmation_sent_at       :datetime
#  unconfirmed_email          :string(255)
#  hide_no_ssh_key            :boolean          default(FALSE)
#  website_url                :string(255)      default(""), not null
#  notification_email         :string(255)
#  hide_no_password           :boolean          default(FALSE)
#  password_automatically_set :boolean          default(FALSE)
#  location                   :string(255)
#  encrypted_otp_secret       :string(255)
#  encrypted_otp_secret_iv    :string(255)
#  encrypted_otp_secret_salt  :string(255)
#  otp_required_for_login     :boolean          default(FALSE), not null
#  otp_backup_codes           :text
#  public_email               :string(255)      default(""), not null
#  dashboard                  :integer          default(0)
#  project_view               :integer          default(0)
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
57
#  consumed_timestep          :integer
58
#  layout                     :integer          default(0)
Stan Hu's avatar
Stan Hu committed
59
#  hide_project_limit         :boolean          default(FALSE)
60
61
#

Steven Thonus's avatar
Steven Thonus committed
62
63
64
require 'carrierwave/orm/activerecord'
require 'file_size_validator'

gitlabhq's avatar
gitlabhq committed
65
class User < ActiveRecord::Base
66
  extend Gitlab::ConfigHelper
67
68

  include Gitlab::ConfigHelper
69
  include Gitlab::CurrentSettings
70
71
72
  include Referable
  include Sortable
  include TokenAuthenticatable
73
  include CaseSensitivity
74

75
  default_value_for :admin, false
76
  default_value_for :can_create_group, gitlab_config.default_can_create_group
77
78
  default_value_for :can_create_team, false
  default_value_for :hide_no_ssh_key, false
79
  default_value_for :hide_no_password, false
80
  default_value_for :theme_id, gitlab_config.default_theme
81

82
83
  devise :two_factor_authenticatable,
         otp_secret_encryption_key: File.read(Rails.root.join('.secret')).chomp
84
  alias_attribute :two_factor_enabled, :otp_required_for_login
Robert Speicher's avatar
Robert Speicher committed
85

86
  devise :two_factor_backupable, otp_number_of_backup_codes: 10
Robert Speicher's avatar
Robert Speicher committed
87
88
  serialize :otp_backup_codes, JSON

89
90
  devise :lockable, :async, :recoverable, :rememberable, :trackable,
    :validatable, :omniauthable, :confirmable, :registerable
gitlabhq's avatar
gitlabhq committed
91

92
  attr_accessor :force_random_password
gitlabhq's avatar
gitlabhq committed
93

94
95
96
  # Virtual attribute for authenticating by either username or email
  attr_accessor :login

97
98
99
100
  #
  # Relations
  #

101
  # Namespace for personal projects
102
  has_one :namespace, -> { where type: nil }, dependent: :destroy, foreign_key: :owner_id, class_name: "Namespace"
103
104
105

  # Profile
  has_many :keys, dependent: :destroy
106
  has_many :emails, dependent: :destroy
107
  has_many :identities, dependent: :destroy, autosave: true
108
109

  # Groups
110
111
112
113
  has_many :members, dependent: :destroy
  has_many :project_members, source: 'ProjectMember'
  has_many :group_members, source: 'GroupMember'
  has_many :groups, through: :group_members
114
115
  has_many :owned_groups, -> { where members: { access_level: Gitlab::Access::OWNER } }, through: :group_members, source: :group
  has_many :masters_groups, -> { where members: { access_level: Gitlab::Access::MASTER } }, through: :group_members, source: :group
116

117
  # Projects
118
119
  has_many :groups_projects,          through: :groups, source: :projects
  has_many :personal_projects,        through: :namespace, source: :projects
120
  has_many :projects,                 through: :project_members
121
  has_many :created_projects,         foreign_key: :creator_id, class_name: 'Project'
Ciro Santilli's avatar
Ciro Santilli committed
122
123
  has_many :users_star_projects, dependent: :destroy
  has_many :starred_projects, through: :users_star_projects, source: :project
124

125
  has_many :snippets,                 dependent: :destroy, foreign_key: :author_id, class_name: "Snippet"
126
  has_many :project_members,          dependent: :destroy, class_name: 'ProjectMember'
127
128
129
130
  has_many :issues,                   dependent: :destroy, foreign_key: :author_id
  has_many :notes,                    dependent: :destroy, foreign_key: :author_id
  has_many :merge_requests,           dependent: :destroy, foreign_key: :author_id
  has_many :events,                   dependent: :destroy, foreign_key: :author_id,   class_name: "Event"
131
  has_many :subscriptions,            dependent: :destroy
132
  has_many :recent_events, -> { order "id DESC" }, foreign_key: :author_id,   class_name: "Event"
133
134
  has_many :assigned_issues,          dependent: :destroy, foreign_key: :assignee_id, class_name: "Issue"
  has_many :assigned_merge_requests,  dependent: :destroy, foreign_key: :assignee_id, class_name: "MergeRequest"
Valery Sizov's avatar
Valery Sizov committed
135
  has_many :oauth_applications, class_name: 'Doorkeeper::Application', as: :owner, dependent: :destroy
136
  has_one  :abuse_report,             dependent: :destroy
137
  has_many :builds,                   dependent: :nullify, class_name: 'Ci::Build'
138

139

140
141
142
  #
  # Validations
  #
Cyril Jouve's avatar
Cyril Jouve committed
143
  validates :name, presence: true
144
  # Note that a 'uniqueness' and presence check is provided by devise :validatable for email. We do not need to
145
  # duplicate that here as the validation framework will have duplicate errors in the event of a failure.
146
  validates :email, presence: true, email: { strict_mode: true }
147
  validates :notification_email, presence: true, email: { strict_mode: true }
148
  validates :public_email, presence: true, email: { strict_mode: true }, allow_blank: true, uniqueness: true
149
  validates :bio, length: { maximum: 255 }, allow_blank: true
150
  validates :projects_limit, presence: true, numericality: { greater_than_or_equal_to: 0 }
151
  validates :username,
152
    namespace: true,
153
    presence: true,
154
    uniqueness: { case_sensitive: false }
155

Andrey Kumanyaev's avatar
Andrey Kumanyaev committed
156
  validates :notification_level, inclusion: { in: Notification.notification_levels }, presence: true
157
  validate :namespace_uniq, if: ->(user) { user.username_changed? }
158
  validate :avatar_type, if: ->(user) { user.avatar.present? && user.avatar_changed? }
159
  validate :unique_email, if: ->(user) { user.email_changed? }
160
  validate :owns_notification_email, if: ->(user) { user.notification_email_changed? }
161
  validate :owns_public_email, if: ->(user) { user.public_email_changed? }
162
  validates :avatar, file_size: { maximum: 200.kilobytes.to_i }
163

164
  before_validation :generate_password, on: :create
165
  before_validation :restricted_signup_domains, on: :create
166
  before_validation :sanitize_attrs
167
  before_validation :set_notification_email, if: ->(user) { user.email_changed? }
168
  before_validation :set_public_email, if: ->(user) { user.public_email_changed? }
169

170
  after_update :update_emails_with_primary_email, if: ->(user) { user.email_changed? }
Nihad Abbasov's avatar
Nihad Abbasov committed
171
  before_save :ensure_authentication_token
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
172
  after_save :ensure_namespace_correct
173
  after_initialize :set_projects_limit
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
174
175
176
  after_create :post_create_hook
  after_destroy :post_destroy_hook

177
  # User's Layout preference
178
  enum layout: [:fixed, :fluid]
179

180
181
  # User's Dashboard preference
  # Note: When adding an option, it MUST go on the end of the array.
182
  enum dashboard: [:projects, :stars, :project_activity, :starred_project_activity]
183

184
185
  # User's Project preference
  # Note: When adding an option, it MUST go on the end of the array.
186
  enum project_view: [:readme, :activity, :files]
187

Nihad Abbasov's avatar
Nihad Abbasov committed
188
  alias_attribute :private_token, :authentication_token
189

190
  delegate :path, to: :namespace, allow_nil: true, prefix: true
191

192
193
194
195
196
197
198
199
200
201
  state_machine :state, initial: :active do
    event :block do
      transition active: :blocked
    end

    event :activate do
      transition blocked: :active
    end
  end

202
  mount_uploader :avatar, AvatarUploader
Steven Thonus's avatar
Steven Thonus committed
203

Andrey Kumanyaev's avatar
Andrey Kumanyaev committed
204
  # Scopes
205
  scope :admins, -> { where(admin: true) }
206
207
  scope :blocked, -> { with_state(:blocked) }
  scope :active, -> { with_state(:active) }
skv's avatar
skv committed
208
  scope :not_in_project, ->(project) { project.users.present? ? where("id not in (:ids)", ids: project.users.map(&:id) ) : all }
209
  scope :without_projects, -> { where('id NOT IN (SELECT DISTINCT(user_id) FROM members)') }
210
211
  scope :with_two_factor,    -> { where(two_factor_enabled: true) }
  scope :without_two_factor, -> { where(two_factor_enabled: false) }
Andrey Kumanyaev's avatar
Andrey Kumanyaev committed
212

213
214
215
  #
  # Class methods
  #
Andrey Kumanyaev's avatar
Andrey Kumanyaev committed
216
  class << self
217
    # Devise method overridden to allow sign in with email or username
218
219
220
221
222
223
224
225
    def find_for_database_authentication(warden_conditions)
      conditions = warden_conditions.dup
      if login = conditions.delete(:login)
        where(conditions).where(["lower(username) = :value OR lower(email) = :value", { value: login.downcase }]).first
      else
        where(conditions).first
      end
    end
226

Valery Sizov's avatar
Valery Sizov committed
227
228
    def sort(method)
      case method.to_s
229
230
231
232
      when 'recent_sign_in' then reorder(last_sign_in_at: :desc)
      when 'oldest_sign_in' then reorder(last_sign_in_at: :asc)
      else
        order_by(method)
Valery Sizov's avatar
Valery Sizov committed
233
234
235
      end
    end

236
237
    # Find a User by their primary email or any associated secondary email
    def find_by_any_email(email)
238
239
240
241
242
243
244
      sql = 'SELECT *
      FROM users
      WHERE id IN (
        SELECT id FROM users WHERE email = :email
        UNION
        SELECT emails.user_id FROM emails WHERE email = :email
      )
245
246
247
      LIMIT 1;'

      User.find_by_sql([sql, { email: email }]).first
248
    end
249

250
    def filter(filter_name)
Andrey Kumanyaev's avatar
Andrey Kumanyaev committed
251
      case filter_name
252
253
254
255
256
257
258
259
260
261
      when 'admins'
        self.admins
      when 'blocked'
        self.blocked
      when 'two_factor_disabled'
        self.without_two_factor
      when 'two_factor_enabled'
        self.with_two_factor
      when 'wop'
        self.without_projects
Andrey Kumanyaev's avatar
Andrey Kumanyaev committed
262
263
264
      else
        self.active
      end
265
266
    end

267
    def search(query)
268
      where("lower(name) LIKE :query OR lower(email) LIKE :query OR lower(username) LIKE :query", query: "%#{query.downcase}%")
Andrey Kumanyaev's avatar
Andrey Kumanyaev committed
269
    end
270

271
    def by_login(login)
272
273
274
275
276
277
278
      return nil unless login

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

Robert Speicher's avatar
Robert Speicher committed
281
282
283
284
    def find_by_username!(username)
      find_by!('lower(username) = ?', username.downcase)
    end

285
    def by_username_or_id(name_or_id)
286
      where('users.username = ? OR users.id = ?', name_or_id.to_s, name_or_id.to_i).first
287
    end
288

289
290
    def build_user(attrs = {})
      User.new(attrs)
291
    end
292
293
294
295

    def reference_prefix
      '@'
    end
296
297
298
299
300
301
302
303

    # Pattern used to extract `@user` user references from text
    def reference_pattern
      %r{
        #{Regexp.escape(reference_prefix)}
        (?<user>#{Gitlab::Regex::NAMESPACE_REGEX_STR})
      }x
    end
vsizov's avatar
vsizov committed
304
  end
randx's avatar
randx committed
305

306
307
308
  #
  # Instance methods
  #
309
310
311
312
313

  def to_param
    username
  end

314
315
316
317
  def to_reference(_from_project = nil)
    "#{self.class.reference_prefix}#{username}"
  end

Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
318
319
320
321
  def notification
    @notification ||= Notification.new(self)
  end

Andrey Kumanyaev's avatar
Andrey Kumanyaev committed
322
323
324
325
  def generate_password
    if self.force_random_password
      self.password = self.password_confirmation = Devise.friendly_token.first(8)
    end
randx's avatar
randx committed
326
  end
327

328
  def generate_reset_token
Marin Jankovski's avatar
Marin Jankovski committed
329
    @reset_token, enc = Devise.token_generator.generate(self.class, :reset_password_token)
330
331
332
333

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

Marin Jankovski's avatar
Marin Jankovski committed
334
    @reset_token
335
336
  end

337
338
339
340
  def recently_sent_password_reset?
    reset_password_sent_at.present? && reset_password_sent_at >= 1.minute.ago
  end

Robert Speicher's avatar
Robert Speicher committed
341
342
343
344
345
346
347
348
349
350
  def disable_two_factor!
    update_attributes(
      two_factor_enabled:        false,
      encrypted_otp_secret:      nil,
      encrypted_otp_secret_iv:   nil,
      encrypted_otp_secret_salt: nil,
      otp_backup_codes:          nil
    )
  end

351
352
  def namespace_uniq
    namespace_name = self.username
353
354
    existing_namespace = Namespace.by_path(namespace_name)
    if existing_namespace && existing_namespace != self.namespace
lol768's avatar
lol768 committed
355
      self.errors.add :username, "already exists"
356
357
    end
  end
358

359
360
361
362
363
364
  def avatar_type
    unless self.avatar.image?
      self.errors.add :avatar, "only images allowed"
    end
  end

365
  def unique_email
366
367
368
    if !self.emails.exists?(email: self.email) && Email.exists?(email: self.email)
      self.errors.add(:email, 'has already been taken')
    end
369
370
  end

371
372
373
374
  def owns_notification_email
    self.errors.add(:notification_email, "is not an email you own") unless self.all_emails.include?(self.notification_email)
  end

375
  def owns_public_email
376
377
    return if self.public_email.blank?

378
379
380
381
382
383
384
385
    self.errors.add(:public_email, "is not an email you own") unless self.all_emails.include?(self.public_email)
  end

  def update_emails_with_primary_email
    primary_email_record = self.emails.find_by(email: self.email)
    if primary_email_record
      primary_email_record.destroy
      self.emails.create(email: self.email_was)
386

387
388
389
390
      self.update_secondary_emails!
    end
  end

391
392
  # Returns the groups a user has access to
  def authorized_groups
393
    union = Gitlab::SQL::Union.
394
      new([groups.select(:id), authorized_projects.select(:namespace_id)])
395

396
    Group.where("namespaces.id IN (#{union.to_sql})")
397
398
  end

399
  # Returns the groups a user is authorized to access.
400
401
  def authorized_projects
    Project.where("projects.id IN (#{projects_union.to_sql})")
402
403
  end

404
  def owned_projects
405
    @owned_projects ||=
406
407
      Project.where('namespace_id IN (?) OR namespace_id = ?',
                    owned_groups.select(:id), namespace.id).joins(:namespace)
408
409
  end

410
411
  # Team membership in authorized projects
  def tm_in_authorized_projects
412
    ProjectMember.where(source_id: authorized_projects.map(&:id), user_id: self.id)
413
  end
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
414
415
416
417
418
419
420
421
422

  def is_admin?
    admin
  end

  def require_ssh_key?
    keys.count == 0
  end

423
424
425
426
  def require_password?
    password_automatically_set? && !ldap_user?
  end

427
  def can_change_username?
428
    gitlab_config.username_changing_enabled
429
430
  end

Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
431
  def can_create_project?
432
    projects_limit_left > 0
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
433
434
435
  end

  def can_create_group?
436
    can?(:create_group, nil)
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
437
438
439
  end

  def abilities
Ciro Santilli's avatar
Ciro Santilli committed
440
    Ability.abilities
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
441
442
  end

443
444
445
446
  def can_select_namespace?
    several_namespaces? || admin
  end

447
  def can?(action, subject)
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
448
449
450
451
452
453
454
455
    abilities.allowed?(self, action, subject)
  end

  def first_name
    name.split.first unless name.blank?
  end

  def cared_merge_requests
456
    MergeRequest.cared(self)
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
457
458
  end

459
  def projects_limit_left
460
    projects_limit - personal_projects.count
461
462
  end

Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
463
464
  def projects_limit_percent
    return 100 if projects_limit.zero?
465
    (personal_projects.count.to_f / projects_limit) * 100
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
466
467
  end

468
  def recent_push(project_id = nil)
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
469
470
471
472
    # Get push events not earlier than 2 hours ago
    events = recent_events.code_push.where("created_at > ?", Time.now - 2.hours)
    events = events.where(project_id: project_id) if project_id

473
474
475
476
477
478
479
480
481
482
483
484
485
    # Use the latest event that has not been pushed or merged recently
    events.recent.find do |event|
      project = Project.find_by_id(event.project_id)
      next unless project
      repo = project.repository

      if repo.branch_names.include?(event.branch_name)
        merge_requests = MergeRequest.where("created_at >= ?", event.created_at).
            where(source_project_id: project.id,
                  source_branch: event.branch_name)
        merge_requests.empty?
      end
    end
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
486
487
488
489
490
491
492
  end

  def projects_sorted_by_activity
    authorized_projects.sorted_by_activity
  end

  def several_namespaces?
493
    owned_groups.any? || masters_groups.any?
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
494
495
496
497
498
  end

  def namespace_id
    namespace.try :id
  end
499

500
501
502
  def name_with_username
    "#{name} (#{username})"
  end
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
503
504

  def tm_of(project)
505
    project.project_member_by_id(self.id)
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
506
  end
507

508
  def already_forked?(project)
509
510
511
    !!fork_of(project)
  end

512
  def fork_of(project)
513
514
515
516
517
518
519
520
    links = ForkedProjectLink.where(forked_from_project_id: project, forked_to_project_id: personal_projects)

    if links.any?
      links.first.forked_to_project
    else
      nil
    end
  end
521
522

  def ldap_user?
523
524
525
526
527
    identities.exists?(["provider LIKE ? AND extern_uid IS NOT NULL", "ldap%"])
  end

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

530
  def project_deploy_keys
531
    DeployKey.unscoped.in_projects(self.authorized_projects.pluck(:id)).distinct(:id)
532
533
  end

534
  def accessible_deploy_keys
535
536
537
538
539
    @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
540
  end
541
542

  def created_by
skv's avatar
skv committed
543
    User.find_by(id: created_by_id) if created_by_id
544
  end
545
546

  def sanitize_attrs
547
    %w(name username skype linkedin twitter).each do |attr|
548
549
550
551
      value = self.send(attr)
      self.send("#{attr}=", Sanitize.clean(value)) if value.present?
    end
  end
552

553
554
  def set_notification_email
    if self.notification_email.blank? || !self.all_emails.include?(self.notification_email)
555
      self.notification_email = self.email
556
557
558
    end
  end

559
560
  def set_public_email
    if self.public_email.blank? || !self.all_emails.include?(self.public_email)
561
      self.public_email = ''
562
563
564
    end
  end

565
566
567
568
569
570
  def update_secondary_emails!
    self.set_notification_email
    self.set_public_email
    self.save if self.notification_email_changed? || self.public_email_changed?
  end

571
572
573
574
575
576
577
  def set_projects_limit
    connection_default_value_defined = new_record? && !projects_limit_changed?
    return unless self.projects_limit.nil? || connection_default_value_defined

    self.projects_limit = current_application_settings.default_projects_limit
  end

578
  def requires_ldap_check?
579
580
581
    if !Gitlab.config.ldap.enabled
      false
    elsif ldap_user?
582
583
584
585
586
587
      !last_credential_check_at || (last_credential_check_at + 1.hour) < Time.now
    else
      false
    end
  end

588
589
590
591
592
  def solo_owned_groups
    @solo_owned_groups ||= owned_groups.select do |group|
      group.owners == [self]
    end
  end
593
594

  def with_defaults
595
596
    User.defaults.each do |k, v|
      self.send("#{k}=", v)
597
    end
598
599

    self
600
  end
601

602
603
604
605
  def can_leave_project?(project)
    project.namespace != namespace &&
      project.project_member(self)
  end
606
607
608
609
610
611
612
613
614
615
616
617
618
619

  # Reset project events cache related to this user
  #
  # Since we do cache @event we need to reset cache in special cases:
  # * when the user changes their avatar
  # Events cache stored like  events/23-20130109142513.
  # The cache key includes updated_at timestamp.
  # Thus it will automatically generate a new fragment
  # when the event is updated because the key changes.
  def reset_events_cache
    Event.where(author_id: self.id).
      order('id DESC').limit(1000).
      update_all(updated_at: Time.now)
  end
Jerome Dalbert's avatar
Jerome Dalbert committed
620
621

  def full_website_url
622
    return "http://#{website_url}" if website_url !~ /\Ahttps?:\/\//
Jerome Dalbert's avatar
Jerome Dalbert committed
623
624
625
626
627

    website_url
  end

  def short_website_url
628
    website_url.sub(/\Ahttps?:\/\//, '')
Jerome Dalbert's avatar
Jerome Dalbert committed
629
  end
GitLab's avatar
GitLab committed
630

631
  def all_ssh_keys
632
    keys.map(&:publishable_key)
633
  end
634
635

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

639
  def avatar_url(size = nil, scale = 2)
640
    if avatar.present?
641
      [gitlab_config.url, avatar.url].join
642
    else
643
      GravatarService.new.execute(email, size, scale)
644
645
    end
  end
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
646

647
648
649
650
  def all_emails
    [self.email, *self.emails.map(&:email)]
  end

Kirill Zaitsev's avatar
Kirill Zaitsev committed
651
652
653
654
655
656
657
658
  def hook_attrs
    {
      name: name,
      username: username,
      avatar_url: avatar_url
    }
  end

Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
659
660
661
662
663
664
665
666
667
668
669
  def ensure_namespace_correct
    # Ensure user has namespace
    self.create_namespace!(path: self.username, name: self.username) unless self.namespace

    if self.username_changed?
      self.namespace.update_attributes(path: self.username, name: self.username)
    end
  end

  def post_create_hook
    log_info("User \"#{self.name}\" (#{self.email}) was created")
670
    notification_service.new_user(self, @reset_token) if self.created_by_id
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
671
672
673
674
675
676
677
678
    system_hook_service.execute_hooks_for(self, :create)
  end

  def post_destroy_hook
    log_info("User \"#{self.name}\" (#{self.email})  was removed")
    system_hook_service.execute_hooks_for(self, :destroy)
  end

Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
679
  def notification_service
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
680
681
682
    NotificationService.new
  end

683
  def log_info(message)
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
684
685
686
687
688
689
    Gitlab::AppLogger.info message
  end

  def system_hook_service
    SystemHooksService.new
  end
Ciro Santilli's avatar
Ciro Santilli committed
690
691

  def starred?(project)
692
    starred_projects.exists?(project.id)
Ciro Santilli's avatar
Ciro Santilli committed
693
694
695
  end

  def toggle_star(project)
696
697
698
699
700
701
702
703
704
    UsersStarProject.transaction do
      user_star_project = users_star_projects.
          where(project: project, user: self).lock(true).first

      if user_star_project
        user_star_project.destroy
      else
        UsersStarProject.create!(project: project, user: self)
      end
Ciro Santilli's avatar
Ciro Santilli committed
705
706
    end
  end
707
708

  def manageable_namespaces
709
    @manageable_namespaces ||= [namespace] + owned_groups + masters_groups
710
  end
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
711

712
713
714
715
716
717
  def namespaces
    namespace_ids = groups.pluck(:id)
    namespace_ids.push(namespace.id)
    Namespace.where(id: namespace_ids)
  end

Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
718
719
720
  def oauth_authorized_tokens
    Doorkeeper::AccessToken.where(resource_owner_id: self.id, revoked_at: nil)
  end
721

722
723
724
725
726
727
728
729
730
  # 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
731
  # ms on a database with a similar size to GitLab.com's database. On the other
732
733
734
735
  # hand, using a subquery means we can get the exact same data in about 40 ms.
  def contributed_projects
    events = Event.select(:project_id).
      contributions.where(author_id: self).
736
      where("created_at > ?", Time.now - 1.year).
737
      uniq.
738
739
740
      reorder(nil)

    Project.where(id: events)
741
  end
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764

  def restricted_signup_domains
    email_domains = current_application_settings.restricted_signup_domains

    unless email_domains.blank?
      match_found = email_domains.any? do |domain|
        escaped = Regexp.escape(domain).gsub('\*','.*?')
        regexp = Regexp.new "^#{escaped}$", Regexp::IGNORECASE
        email_domain = Mail::Address.new(self.email).domain
        email_domain =~ regexp
      end

      unless match_found
        self.errors.add :email,
                        'is not whitelisted. ' +
                        'Email domains valid for registration are: ' +
                        email_domains.join(', ')
        return false
      end
    end

    true
  end
765
766
767
768

  def can_be_removed?
    !solo_owned_groups.present?
  end
769
770

  def ci_authorized_runners
771
    @ci_authorized_runners ||= begin
772
773
      runner_ids = Ci::RunnerProject.
        where("ci_runner_projects.gl_project_id IN (#{ci_projects_union.to_sql})").
774
        select(:runner_id)
775
776
      Ci::Runner.specific.where(id: runner_ids)
    end
777
  end
778
779
780
781
782
783
784
785

  private

  def projects_union
    Gitlab::SQL::Union.new([personal_projects.select(:id),
                            groups_projects.select(:id),
                            projects.select(:id)])
  end
786
787
788
789
790
791
792
793
794

  def ci_projects_union
    scope  = { access_level: [Gitlab::Access::MASTER, Gitlab::Access::OWNER] }
    groups = groups_projects.where(members: scope)
    other  = projects.where(members: scope)

    Gitlab::SQL::Union.new([personal_projects.select(:id), groups.select(:id),
                            other.select(:id)])
  end
Valery Sizov's avatar
Valery Sizov committed
795
796
797
798
799

  # Added according to https://github.com/plataformatec/devise/blob/7df57d5081f9884849ca15e4fde179ef164a575f/README.md#activejob-integration
  def send_devise_notification(notification, *args)
    devise_mailer.send(notification, self, *args).deliver_later
  end
gitlabhq's avatar
gitlabhq committed
800
end