user.rb 24.5 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 57 58 59 60 61
#  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)
#  consumed_timestep           :integer
#  layout                      :integer          default(0)
#  hide_project_limit          :boolean          default(FALSE)
#  unlock_token                :string
#  otp_grace_period_started_at :datetime
62 63
#

64 65 66
require 'carrierwave/orm/activerecord'
require 'file_size_validator'

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

  include Gitlab::ConfigHelper
71
  include Gitlab::CurrentSettings
72 73
  include Referable
  include Sortable
74
  include CaseSensitivity
75 76 77
  include TokenAuthenticatable

  add_authentication_token_field :authentication_token
78

79
  default_value_for :admin, false
80
  default_value_for :can_create_group, gitlab_config.default_can_create_group
81 82
  default_value_for :can_create_team, false
  default_value_for :hide_no_ssh_key, false
83
  default_value_for :hide_no_password, false
84
  default_value_for :theme_id, gitlab_config.default_theme
85

86 87
  devise :two_factor_authenticatable,
         otp_secret_encryption_key: File.read(Rails.root.join('.secret')).chomp
88
  alias_attribute :two_factor_enabled, :otp_required_for_login
89

90
  devise :two_factor_backupable, otp_number_of_backup_codes: 10
91 92
  serialize :otp_backup_codes, JSON

93 94
  devise :lockable, :async, :recoverable, :rememberable, :trackable,
    :validatable, :omniauthable, :confirmable, :registerable
gitlabhq's avatar
gitlabhq committed
95

96
  attr_accessor :force_random_password
gitlabhq's avatar
gitlabhq committed
97

98 99 100
  # Virtual attribute for authenticating by either username or email
  attr_accessor :login

101 102 103 104
  #
  # Relations
  #

105
  # Namespace for personal projects
106
  has_one :namespace, -> { where type: nil }, dependent: :destroy, foreign_key: :owner_id, class_name: "Namespace"
107 108 109

  # Profile
  has_many :keys, dependent: :destroy
110
  has_many :emails, dependent: :destroy
111
  has_many :identities, dependent: :destroy, autosave: true
112 113

  # Groups
114 115 116 117
  has_many :members, dependent: :destroy
  has_many :project_members, source: 'ProjectMember'
  has_many :group_members, source: 'GroupMember'
  has_many :groups, through: :group_members
118 119
  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
120

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

129
  has_many :snippets,                 dependent: :destroy, foreign_key: :author_id, class_name: "Snippet"
130
  has_many :project_members,          dependent: :destroy, class_name: 'ProjectMember'
131 132 133 134
  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"
135
  has_many :subscriptions,            dependent: :destroy
136
  has_many :recent_events, -> { order "id DESC" }, foreign_key: :author_id,   class_name: "Event"
137 138
  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
139
  has_many :oauth_applications, class_name: 'Doorkeeper::Application', as: :owner, dependent: :destroy
140
  has_one  :abuse_report,             dependent: :destroy
141
  has_many :builds,                   dependent: :nullify, class_name: 'Ci::Build'
142

143

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

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

168
  before_validation :generate_password, on: :create
169
  before_validation :restricted_signup_domains, on: :create
170
  before_validation :sanitize_attrs
171
  before_validation :set_notification_email, if: ->(user) { user.email_changed? }
172
  before_validation :set_public_email, if: ->(user) { user.public_email_changed? }
173

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

181
  # User's Layout preference
182
  enum layout: [:fixed, :fluid]
183

184 185
  # User's Dashboard preference
  # Note: When adding an option, it MUST go on the end of the array.
186
  enum dashboard: [:projects, :stars, :project_activity, :starred_project_activity]
187

188 189
  # User's Project preference
  # Note: When adding an option, it MUST go on the end of the array.
190
  enum project_view: [:readme, :activity, :files]
191

Nihad Abbasov's avatar
Nihad Abbasov committed
192
  alias_attribute :private_token, :authentication_token
193

194
  delegate :path, to: :namespace, allow_nil: true, prefix: true
195

196 197 198 199 200 201 202 203 204 205
  state_machine :state, initial: :active do
    event :block do
      transition active: :blocked
    end

    event :activate do
      transition blocked: :active
    end
  end

206
  mount_uploader :avatar, AvatarUploader
207

Andrey Kumanyaev's avatar
Andrey Kumanyaev committed
208
  # Scopes
209
  scope :admins, -> { where(admin: true) }
210 211
  scope :blocked, -> { with_state(:blocked) }
  scope :active, -> { with_state(:active) }
skv's avatar
skv committed
212
  scope :not_in_project, ->(project) { project.users.present? ? where("id not in (:ids)", ids: project.users.map(&:id) ) : all }
213
  scope :without_projects, -> { where('id NOT IN (SELECT DISTINCT(user_id) FROM members)') }
214 215
  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
216

217 218 219
  #
  # Class methods
  #
Andrey Kumanyaev's avatar
Andrey Kumanyaev committed
220
  class << self
221
    # Devise method overridden to allow sign in with email or username
222 223 224
    def find_for_database_authentication(warden_conditions)
      conditions = warden_conditions.dup
      if login = conditions.delete(:login)
Gabriel Mazetto's avatar
Gabriel Mazetto committed
225
        where(conditions).find_by("lower(username) = :value OR lower(email) = :value", value: login.downcase)
226
      else
Gabriel Mazetto's avatar
Gabriel Mazetto committed
227
        find_by(conditions)
228 229
      end
    end
230

Valery Sizov's avatar
Valery Sizov committed
231 232
    def sort(method)
      case method.to_s
233 234 235 236
      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
237 238 239
      end
    end

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

      User.find_by_sql([sql, { email: email }]).first
252
    end
253

254
    def filter(filter_name)
Andrey Kumanyaev's avatar
Andrey Kumanyaev committed
255
      case filter_name
256 257 258 259 260 261 262 263 264 265
      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
266 267 268
      else
        self.active
      end
269 270
    end

271
    def search(query)
272
      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
273
    end
274

275
    def by_login(login)
276 277 278 279 280 281 282
      return nil unless login

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

285 286 287 288
    def find_by_username!(username)
      find_by!('lower(username) = ?', username.downcase)
    end

289
    def by_username_or_id(name_or_id)
Gabriel Mazetto's avatar
Gabriel Mazetto committed
290
      find_by('users.username = ? OR users.id = ?', name_or_id.to_s, name_or_id.to_i)
291
    end
292

293 294
    def build_user(attrs = {})
      User.new(attrs)
295
    end
296 297 298 299

    def reference_prefix
      '@'
    end
300 301 302 303 304 305 306 307

    # 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
308
  end
randx's avatar
randx committed
309

310 311 312
  #
  # Instance methods
  #
313 314 315 316 317

  def to_param
    username
  end

318 319 320 321
  def to_reference(_from_project = nil)
    "#{self.class.reference_prefix}#{username}"
  end

Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
322 323 324 325
  def notification
    @notification ||= Notification.new(self)
  end

Andrey Kumanyaev's avatar
Andrey Kumanyaev committed
326 327 328 329
  def generate_password
    if self.force_random_password
      self.password = self.password_confirmation = Devise.friendly_token.first(8)
    end
randx's avatar
randx committed
330
  end
331

332
  def generate_reset_token
333
    @reset_token, enc = Devise.token_generator.generate(self.class, :reset_password_token)
334 335 336 337

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

338
    @reset_token
339 340
  end

341 342 343 344
  def recently_sent_password_reset?
    reset_password_sent_at.present? && reset_password_sent_at >= 1.minute.ago
  end

345 346 347 348 349 350 351 352 353 354
  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

355
  def namespace_uniq
356 357 358
    # Return early if username already failed the first uniqueness validation
    return if self.errors[:username].include?('has already been taken')

359
    namespace_name = self.username
360 361
    existing_namespace = Namespace.by_path(namespace_name)
    if existing_namespace && existing_namespace != self.namespace
362
      self.errors.add(:username, 'has already been taken')
363 364
    end
  end
365

366 367 368 369 370 371
  def avatar_type
    unless self.avatar.image?
      self.errors.add :avatar, "only images allowed"
    end
  end

372
  def unique_email
373 374 375
    if !self.emails.exists?(email: self.email) && Email.exists?(email: self.email)
      self.errors.add(:email, 'has already been taken')
    end
376 377
  end

378 379 380 381
  def owns_notification_email
    self.errors.add(:notification_email, "is not an email you own") unless self.all_emails.include?(self.notification_email)
  end

382
  def owns_public_email
383 384
    return if self.public_email.blank?

385 386 387 388 389 390 391 392
    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)
393

394 395 396 397
      self.update_secondary_emails!
    end
  end

398 399
  # Returns the groups a user has access to
  def authorized_groups
400
    union = Gitlab::SQL::Union.
401
      new([groups.select(:id), authorized_projects.select(:namespace_id)])
402

403
    Group.where("namespaces.id IN (#{union.to_sql})")
404 405
  end

406
  # Returns the groups a user is authorized to access.
407 408
  def authorized_projects
    Project.where("projects.id IN (#{projects_union.to_sql})")
409 410
  end

411
  def owned_projects
412
    @owned_projects ||=
413 414
      Project.where('namespace_id IN (?) OR namespace_id = ?',
                    owned_groups.select(:id), namespace.id).joins(:namespace)
415 416
  end

417 418
  # Team membership in authorized projects
  def tm_in_authorized_projects
419
    ProjectMember.where(source_id: authorized_projects.map(&:id), user_id: self.id)
420
  end
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
421 422 423 424 425 426 427 428 429

  def is_admin?
    admin
  end

  def require_ssh_key?
    keys.count == 0
  end

430 431 432 433
  def require_password?
    password_automatically_set? && !ldap_user?
  end

434
  def can_change_username?
435
    gitlab_config.username_changing_enabled
436 437
  end

Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
438
  def can_create_project?
439
    projects_limit_left > 0
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
440 441 442
  end

  def can_create_group?
443
    can?(:create_group, nil)
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
444 445 446
  end

  def abilities
Ciro Santilli's avatar
Ciro Santilli committed
447
    Ability.abilities
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
448 449
  end

450 451 452 453
  def can_select_namespace?
    several_namespaces? || admin
  end

454
  def can?(action, subject)
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
455 456 457 458 459 460 461 462
    abilities.allowed?(self, action, subject)
  end

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

  def cared_merge_requests
463
    MergeRequest.cared(self)
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
464 465
  end

466
  def projects_limit_left
467
    projects_limit - personal_projects.count
468 469
  end

Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
470 471
  def projects_limit_percent
    return 100 if projects_limit.zero?
472
    (personal_projects.count.to_f / projects_limit) * 100
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
473 474
  end

475
  def recent_push(project_id = nil)
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
476 477 478 479
    # 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

480 481 482 483 484 485 486 487 488 489 490 491 492
    # 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
493 494 495 496 497 498 499
  end

  def projects_sorted_by_activity
    authorized_projects.sorted_by_activity
  end

  def several_namespaces?
500
    owned_groups.any? || masters_groups.any?
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
501 502 503 504 505
  end

  def namespace_id
    namespace.try :id
  end
506

507 508 509
  def name_with_username
    "#{name} (#{username})"
  end
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
510 511

  def tm_of(project)
512
    project.project_member_by_id(self.id)
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
513
  end
514

515
  def already_forked?(project)
516 517 518
    !!fork_of(project)
  end

519
  def fork_of(project)
520 521 522 523 524 525 526 527
    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
528 529

  def ldap_user?
530 531 532 533 534
    identities.exists?(["provider LIKE ? AND extern_uid IS NOT NULL", "ldap%"])
  end

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

537
  def project_deploy_keys
538
    DeployKey.unscoped.in_projects(self.authorized_projects.pluck(:id)).distinct(:id)
539 540
  end

541
  def accessible_deploy_keys
542 543 544 545 546
    @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
547
  end
548 549

  def created_by
skv's avatar
skv committed
550
    User.find_by(id: created_by_id) if created_by_id
551
  end
552 553

  def sanitize_attrs
554
    %w(name username skype linkedin twitter).each do |attr|
555 556 557 558
      value = self.send(attr)
      self.send("#{attr}=", Sanitize.clean(value)) if value.present?
    end
  end
559

560 561
  def set_notification_email
    if self.notification_email.blank? || !self.all_emails.include?(self.notification_email)
562
      self.notification_email = self.email
563 564 565
    end
  end

566 567
  def set_public_email
    if self.public_email.blank? || !self.all_emails.include?(self.public_email)
568
      self.public_email = ''
569 570 571
    end
  end

572 573 574 575 576 577
  def update_secondary_emails!
    self.set_notification_email
    self.set_public_email
    self.save if self.notification_email_changed? || self.public_email_changed?
  end

578 579 580 581 582 583 584
  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

585
  def requires_ldap_check?
586 587 588
    if !Gitlab.config.ldap.enabled
      false
    elsif ldap_user?
589 590 591 592 593 594
      !last_credential_check_at || (last_credential_check_at + 1.hour) < Time.now
    else
      false
    end
  end

595 596 597 598 599
  def solo_owned_groups
    @solo_owned_groups ||= owned_groups.select do |group|
      group.owners == [self]
    end
  end
600 601

  def with_defaults
602 603
    User.defaults.each do |k, v|
      self.send("#{k}=", v)
604
    end
605 606

    self
607
  end
608

609 610 611 612
  def can_leave_project?(project)
    project.namespace != namespace &&
      project.project_member(self)
  end
613 614 615 616 617 618 619 620 621 622 623 624 625 626

  # 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
627 628

  def full_website_url
629
    return "http://#{website_url}" if website_url !~ /\Ahttps?:\/\//
Jerome Dalbert's avatar
Jerome Dalbert committed
630 631 632 633 634

    website_url
  end

  def short_website_url
635
    website_url.sub(/\Ahttps?:\/\//, '')
Jerome Dalbert's avatar
Jerome Dalbert committed
636
  end
GitLab's avatar
GitLab committed
637

638
  def all_ssh_keys
639
    keys.map(&:publishable_key)
640
  end
641 642

  def temp_oauth_email?
643
    email.start_with?('temp-email-for-oauth')
644 645
  end

646
  def avatar_url(size = nil, scale = 2)
647
    if avatar.present?
648
      [gitlab_config.url, avatar.url].join
649
    else
650
      GravatarService.new.execute(email, size, scale)
651 652
    end
  end
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
653

654 655 656 657
  def all_emails
    [self.email, *self.emails.map(&:email)]
  end

Kirill Zaitsev's avatar
Kirill Zaitsev committed
658 659 660 661 662 663 664 665
  def hook_attrs
    {
      name: name,
      username: username,
      avatar_url: avatar_url
    }
  end

Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
666 667 668 669 670 671 672 673 674 675 676
  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")
677
    notification_service.new_user(self, @reset_token) if self.created_by_id
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
678 679 680 681 682 683 684 685
    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
686
  def notification_service
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
687 688 689
    NotificationService.new
  end

690
  def log_info(message)
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
691 692 693 694 695 696
    Gitlab::AppLogger.info message
  end

  def system_hook_service
    SystemHooksService.new
  end
Ciro Santilli's avatar
Ciro Santilli committed
697 698

  def starred?(project)
699
    starred_projects.exists?(project.id)
Ciro Santilli's avatar
Ciro Santilli committed
700 701 702
  end

  def toggle_star(project)
703 704 705 706 707 708 709 710 711
    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
712 713
    end
  end
714 715

  def manageable_namespaces
716
    @manageable_namespaces ||= [namespace] + owned_groups + masters_groups
717
  end
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
718

719 720 721 722 723 724
  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
725 726 727
  def oauth_authorized_tokens
    Doorkeeper::AccessToken.where(resource_owner_id: self.id, revoked_at: nil)
  end
728

729 730 731 732 733 734 735 736 737
  # 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
738
  # ms on a database with a similar size to GitLab.com's database. On the other
739 740 741 742
  # 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).
743
      where("created_at > ?", Time.now - 1.year).
744
      uniq.
745 746 747
      reorder(nil)

    Project.where(id: events)
748
  end
749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771

  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
772 773 774 775

  def can_be_removed?
    !solo_owned_groups.present?
  end
776 777

  def ci_authorized_runners
778
    @ci_authorized_runners ||= begin
779 780
      runner_ids = Ci::RunnerProject.
        where("ci_runner_projects.gl_project_id IN (#{ci_projects_union.to_sql})").
781
        select(:runner_id)
782 783
      Ci::Runner.specific.where(id: runner_ids)
    end
784
  end
785 786 787 788 789 790 791 792

  private

  def projects_union
    Gitlab::SQL::Union.new([personal_projects.select(:id),
                            groups_projects.select(:id),
                            projects.select(:id)])
  end
793 794 795 796 797 798 799 800 801

  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
802 803 804 805 806

  # 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
807
end