user.rb 24.7 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 :spam_logs,                dependent: :destroy
142
  has_many :builds,                   dependent: :nullify, class_name: 'Ci::Build'
143
  has_many :todos,                    dependent: :destroy
144

145 146 147
  #
  # Validations
  #
Cyril's avatar
Cyril committed
148
  validates :name, presence: true
149 150
  validates :notification_email, presence: true, email: true
  validates :public_email, presence: true, uniqueness: true, email: true, allow_blank: true
151
  validates :bio, length: { maximum: 255 }, allow_blank: true
152
  validates :projects_limit, presence: true, numericality: { greater_than_or_equal_to: 0 }
153
  validates :username,
154
    namespace: true,
155
    presence: true,
156
    uniqueness: { case_sensitive: false }
157

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

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

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

179
  # User's Layout preference
180
  enum layout: [:fixed, :fluid]
181

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

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

Nihad Abbasov's avatar
Nihad Abbasov committed
190
  alias_attribute :private_token, :authentication_token
191

192
  delegate :path, to: :namespace, allow_nil: true, prefix: true
193

194 195 196
  state_machine :state, initial: :active do
    event :block do
      transition active: :blocked
197
      transition ldap_blocked: :blocked
198 199
    end

200 201 202 203
    event :ldap_block do
      transition active: :ldap_blocked
    end

204 205
    event :activate do
      transition blocked: :active
206
      transition ldap_blocked: :active
207
    end
208 209 210 211 212 213

    state :blocked, :ldap_blocked do
      def blocked?
        true
      end
    end
214 215
  end

216
  mount_uploader :avatar, AvatarUploader
217

Andrey Kumanyaev's avatar
Andrey Kumanyaev committed
218
  # Scopes
219
  scope :admins, -> { where(admin: true) }
220
  scope :blocked, -> { with_states(:blocked, :ldap_blocked) }
221
  scope :active, -> { with_state(:active) }
skv's avatar
skv committed
222
  scope :not_in_project, ->(project) { project.users.present? ? where("id not in (:ids)", ids: project.users.map(&:id) ) : all }
223
  scope :without_projects, -> { where('id NOT IN (SELECT DISTINCT(user_id) FROM members)') }
224 225
  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
226

227 228 229
  #
  # Class methods
  #
Andrey Kumanyaev's avatar
Andrey Kumanyaev committed
230
  class << self
231
    # Devise method overridden to allow sign in with email or username
232 233 234
    def find_for_database_authentication(warden_conditions)
      conditions = warden_conditions.dup
      if login = conditions.delete(:login)
Gabriel Mazetto's avatar
Gabriel Mazetto committed
235
        where(conditions).find_by("lower(username) = :value OR lower(email) = :value", value: login.downcase)
236
      else
Gabriel Mazetto's avatar
Gabriel Mazetto committed
237
        find_by(conditions)
238 239
      end
    end
240

Valery Sizov's avatar
Valery Sizov committed
241 242
    def sort(method)
      case method.to_s
243 244 245 246
      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
247 248 249
      end
    end

250 251
    # Find a User by their primary email or any associated secondary email
    def find_by_any_email(email)
252 253 254 255 256 257 258
      sql = 'SELECT *
      FROM users
      WHERE id IN (
        SELECT id FROM users WHERE email = :email
        UNION
        SELECT emails.user_id FROM emails WHERE email = :email
      )
259 260 261
      LIMIT 1;'

      User.find_by_sql([sql, { email: email }]).first
262
    end
263

264
    def filter(filter_name)
Andrey Kumanyaev's avatar
Andrey Kumanyaev committed
265
      case filter_name
266 267 268 269 270 271 272 273 274 275
      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
276 277 278
      else
        self.active
      end
279 280
    end

281
    def search(query)
282
      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
283
    end
284

285
    def by_login(login)
286 287 288 289 290 291 292
      return nil unless login

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

295 296 297 298
    def find_by_username!(username)
      find_by!('lower(username) = ?', username.downcase)
    end

299
    def by_username_or_id(name_or_id)
Gabriel Mazetto's avatar
Gabriel Mazetto committed
300
      find_by('users.username = ? OR users.id = ?', name_or_id.to_s, name_or_id.to_i)
301
    end
302

303 304
    def build_user(attrs = {})
      User.new(attrs)
305
    end
306 307 308 309

    def reference_prefix
      '@'
    end
310 311 312 313 314 315 316 317

    # 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
318
  end
randx's avatar
randx committed
319

320 321 322
  #
  # Instance methods
  #
323 324 325 326 327

  def to_param
    username
  end

328 329 330 331
  def to_reference(_from_project = nil)
    "#{self.class.reference_prefix}#{username}"
  end

Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
332 333 334 335
  def notification
    @notification ||= Notification.new(self)
  end

Andrey Kumanyaev's avatar
Andrey Kumanyaev committed
336 337 338 339
  def generate_password
    if self.force_random_password
      self.password = self.password_confirmation = Devise.friendly_token.first(8)
    end
randx's avatar
randx committed
340
  end
341

342
  def generate_reset_token
343
    @reset_token, enc = Devise.token_generator.generate(self.class, :reset_password_token)
344 345 346 347

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

348
    @reset_token
349 350
  end

351 352 353 354
  def recently_sent_password_reset?
    reset_password_sent_at.present? && reset_password_sent_at >= 1.minute.ago
  end

355 356
  def disable_two_factor!
    update_attributes(
357 358 359 360 361 362
      two_factor_enabled:          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
363 364 365
    )
  end

366
  def namespace_uniq
367 368 369
    # Return early if username already failed the first uniqueness validation
    return if self.errors[:username].include?('has already been taken')

370
    namespace_name = self.username
371 372
    existing_namespace = Namespace.by_path(namespace_name)
    if existing_namespace && existing_namespace != self.namespace
373
      self.errors.add(:username, 'has already been taken')
374 375
    end
  end
376

377 378 379 380 381 382
  def avatar_type
    unless self.avatar.image?
      self.errors.add :avatar, "only images allowed"
    end
  end

383
  def unique_email
384 385 386
    if !self.emails.exists?(email: self.email) && Email.exists?(email: self.email)
      self.errors.add(:email, 'has already been taken')
    end
387 388
  end

389 390 391 392
  def owns_notification_email
    self.errors.add(:notification_email, "is not an email you own") unless self.all_emails.include?(self.notification_email)
  end

393
  def owns_public_email
394 395
    return if self.public_email.blank?

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

405 406 407 408
      self.update_secondary_emails!
    end
  end

409 410
  # Returns the groups a user has access to
  def authorized_groups
411
    union = Gitlab::SQL::Union.
412
      new([groups.select(:id), authorized_projects.select(:namespace_id)])
413

414
    Group.where("namespaces.id IN (#{union.to_sql})")
415 416
  end

417
  # Returns the groups a user is authorized to access.
418 419
  def authorized_projects
    Project.where("projects.id IN (#{projects_union.to_sql})")
420 421
  end

422
  def owned_projects
423
    @owned_projects ||=
424 425
      Project.where('namespace_id IN (?) OR namespace_id = ?',
                    owned_groups.select(:id), namespace.id).joins(:namespace)
426 427
  end

428 429
  # Team membership in authorized projects
  def tm_in_authorized_projects
430
    ProjectMember.where(source_id: authorized_projects.map(&:id), user_id: self.id)
431
  end
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
432 433 434 435 436 437 438 439 440

  def is_admin?
    admin
  end

  def require_ssh_key?
    keys.count == 0
  end

441 442 443 444
  def require_password?
    password_automatically_set? && !ldap_user?
  end

445
  def can_change_username?
446
    gitlab_config.username_changing_enabled
447 448
  end

Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
449
  def can_create_project?
450
    projects_limit_left > 0
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
451 452 453
  end

  def can_create_group?
454
    can?(:create_group, nil)
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
455 456 457
  end

  def abilities
Ciro Santilli's avatar
Ciro Santilli committed
458
    Ability.abilities
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
459 460
  end

461 462 463 464
  def can_select_namespace?
    several_namespaces? || admin
  end

465
  def can?(action, subject)
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
466 467 468 469 470 471 472 473
    abilities.allowed?(self, action, subject)
  end

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

  def cared_merge_requests
474
    MergeRequest.cared(self)
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
475 476
  end

477
  def projects_limit_left
478
    projects_limit - personal_projects.count
479 480
  end

Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
481 482
  def projects_limit_percent
    return 100 if projects_limit.zero?
483
    (personal_projects.count.to_f / projects_limit) * 100
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
484 485
  end

486
  def recent_push(project_id = nil)
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
487 488 489 490
    # 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

491 492 493 494 495 496 497 498 499 500 501 502 503
    # 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
504 505 506 507 508 509 510
  end

  def projects_sorted_by_activity
    authorized_projects.sorted_by_activity
  end

  def several_namespaces?
511
    owned_groups.any? || masters_groups.any?
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
512 513 514 515 516
  end

  def namespace_id
    namespace.try :id
  end
517

518 519 520
  def name_with_username
    "#{name} (#{username})"
  end
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
521 522

  def tm_of(project)
523
    project.project_member_by_id(self.id)
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
524
  end
525

526
  def already_forked?(project)
527 528 529
    !!fork_of(project)
  end

530
  def fork_of(project)
531 532 533 534 535 536 537 538
    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
539 540

  def ldap_user?
541 542 543 544 545
    identities.exists?(["provider LIKE ? AND extern_uid IS NOT NULL", "ldap%"])
  end

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

548
  def project_deploy_keys
549
    DeployKey.unscoped.in_projects(self.authorized_projects.pluck(:id)).distinct(:id)
550 551
  end

552
  def accessible_deploy_keys
553 554 555 556 557
    @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
558
  end
559 560

  def created_by
skv's avatar
skv committed
561
    User.find_by(id: created_by_id) if created_by_id
562
  end
563 564

  def sanitize_attrs
565
    %w(name username skype linkedin twitter).each do |attr|
566 567 568 569
      value = self.send(attr)
      self.send("#{attr}=", Sanitize.clean(value)) if value.present?
    end
  end
570

571 572
  def set_notification_email
    if self.notification_email.blank? || !self.all_emails.include?(self.notification_email)
573
      self.notification_email = self.email
574 575 576
    end
  end

577 578
  def set_public_email
    if self.public_email.blank? || !self.all_emails.include?(self.public_email)
579
      self.public_email = ''
580 581 582
    end
  end

583 584 585 586 587 588
  def update_secondary_emails!
    self.set_notification_email
    self.set_public_email
    self.save if self.notification_email_changed? || self.public_email_changed?
  end

589 590 591 592 593 594 595
  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

596
  def requires_ldap_check?
597 598 599
    if !Gitlab.config.ldap.enabled
      false
    elsif ldap_user?
600 601 602 603 604 605
      !last_credential_check_at || (last_credential_check_at + 1.hour) < Time.now
    else
      false
    end
  end

606 607 608 609 610
  def solo_owned_groups
    @solo_owned_groups ||= owned_groups.select do |group|
      group.owners == [self]
    end
  end
611 612

  def with_defaults
613 614
    User.defaults.each do |k, v|
      self.send("#{k}=", v)
615
    end
616 617

    self
618
  end
619

620 621 622 623
  def can_leave_project?(project)
    project.namespace != namespace &&
      project.project_member(self)
  end
624 625 626 627 628 629 630 631 632 633 634 635 636 637

  # 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
638 639

  def full_website_url
640
    return "http://#{website_url}" if website_url !~ /\Ahttps?:\/\//
Jerome Dalbert's avatar
Jerome Dalbert committed
641 642 643 644 645

    website_url
  end

  def short_website_url
646
    website_url.sub(/\Ahttps?:\/\//, '')
Jerome Dalbert's avatar
Jerome Dalbert committed
647
  end
GitLab's avatar
GitLab committed
648

649
  def all_ssh_keys
650
    keys.map(&:publishable_key)
651
  end
652 653

  def temp_oauth_email?
654
    email.start_with?('temp-email-for-oauth')
655 656
  end

657
  def avatar_url(size = nil, scale = 2)
658
    if avatar.present?
659
      [gitlab_config.url, avatar.url].join
660
    else
661
      GravatarService.new.execute(email, size, scale)
662 663
    end
  end
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
664

665
  def all_emails
666 667 668 669
    all_emails = []
    all_emails << self.email unless self.temp_oauth_email?
    all_emails.concat(self.emails.map(&:email))
    all_emails
670 671
  end

Kirill Zaitsev's avatar
Kirill Zaitsev committed
672 673 674 675 676 677 678 679
  def hook_attrs
    {
      name: name,
      username: username,
      avatar_url: avatar_url
    }
  end

Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
680 681 682 683 684 685 686 687 688 689 690
  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")
691
    notification_service.new_user(self, @reset_token) if self.created_by_id
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
692 693 694 695 696 697 698 699
    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
700
  def notification_service
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
701 702 703
    NotificationService.new
  end

704
  def log_info(message)
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
705 706 707 708 709 710
    Gitlab::AppLogger.info message
  end

  def system_hook_service
    SystemHooksService.new
  end
Ciro Santilli's avatar
Ciro Santilli committed
711 712

  def starred?(project)
713
    starred_projects.exists?(project.id)
Ciro Santilli's avatar
Ciro Santilli committed
714 715 716
  end

  def toggle_star(project)
717 718 719 720 721 722 723 724 725
    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
726 727
    end
  end
728 729

  def manageable_namespaces
730
    @manageable_namespaces ||= [namespace] + owned_groups + masters_groups
731
  end
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
732

733 734 735 736 737 738
  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
739 740 741
  def oauth_authorized_tokens
    Doorkeeper::AccessToken.where(resource_owner_id: self.id, revoked_at: nil)
  end
742

743 744 745 746 747 748 749 750 751
  # 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
752
  # ms on a database with a similar size to GitLab.com's database. On the other
753 754 755 756
  # 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).
757
      where("created_at > ?", Time.now - 1.year).
758
      uniq.
759 760 761
      reorder(nil)

    Project.where(id: events)
762
  end
763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785

  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
786 787 788 789

  def can_be_removed?
    !solo_owned_groups.present?
  end
790 791

  def ci_authorized_runners
792
    @ci_authorized_runners ||= begin
793 794
      runner_ids = Ci::RunnerProject.
        where("ci_runner_projects.gl_project_id IN (#{ci_projects_union.to_sql})").
795
        select(:runner_id)
796 797
      Ci::Runner.specific.where(id: runner_ids)
    end
798
  end
799 800 801 802 803 804 805 806

  private

  def projects_union
    Gitlab::SQL::Union.new([personal_projects.select(:id),
                            groups_projects.select(:id),
                            projects.select(:id)])
  end
807 808 809 810 811 812 813 814 815

  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
816 817 818 819 820

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