user.rb 24.8 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
  state_machine :state, initial: :active do
    event :block do
      transition active: :blocked
199
      transition ldap_blocked: :blocked
200 201
    end

202 203 204 205
    event :ldap_block do
      transition active: :ldap_blocked
    end

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

    state :blocked, :ldap_blocked do
      def blocked?
        true
      end
    end
216 217
  end

218
  mount_uploader :avatar, AvatarUploader
219

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

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

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

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

      User.find_by_sql([sql, { email: email }]).first
264
    end
265

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

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

287
    def by_login(login)
288 289 290 291 292 293 294
      return nil unless login

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

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

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

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

    def reference_prefix
      '@'
    end
312 313 314 315 316 317 318 319

    # 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
320
  end
randx's avatar
randx committed
321

322 323 324
  #
  # Instance methods
  #
325 326 327 328 329

  def to_param
    username
  end

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

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

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

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

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

350
    @reset_token
351 352
  end

353 354 355 356
  def recently_sent_password_reset?
    reset_password_sent_at.present? && reset_password_sent_at >= 1.minute.ago
  end

357 358 359 360 361 362 363 364 365 366
  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

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

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

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

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

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

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

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

406 407 408 409
      self.update_secondary_emails!
    end
  end

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

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

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

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

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

  def is_admin?
    admin
  end

  def require_ssh_key?
    keys.count == 0
  end

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

446
  def can_change_username?
447
    gitlab_config.username_changing_enabled
448 449
  end

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

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

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

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

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

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

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

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

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

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

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

  def projects_sorted_by_activity
    authorized_projects.sorted_by_activity
  end

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

  def namespace_id
    namespace.try :id
  end
518

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    self
619
  end
620

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

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

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

    website_url
  end

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

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

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

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

666 667 668 669
  def all_emails
    [self.email, *self.emails.map(&:email)]
  end

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

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

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

  def system_hook_service
    SystemHooksService.new
  end
Ciro Santilli's avatar
Ciro Santilli committed
709 710

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

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

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

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

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

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

  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
784 785 786 787

  def can_be_removed?
    !solo_owned_groups.present?
  end
788 789

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

  private

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

  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
814 815 816 817 818

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