key.rb 3.14 KB
Newer Older
1 2
# frozen_string_literal: true

miks's avatar
miks committed
3 4
require 'digest/md5'

5
class Key < ApplicationRecord
6
  include AfterCommitQueue
7
  include Sortable
8

gitlabhq's avatar
gitlabhq committed
9 10
  belongs_to :user

11
  before_validation :generate_fingerprint
Andrey Kumanyaev's avatar
Andrey Kumanyaev committed
12

13 14 15
  validates :title,
    presence: true,
    length: { maximum: 255 }
16

17 18 19 20
  validates :key,
    presence: true,
    length: { maximum: 5000 },
    format: { with: /\A(ssh|ecdsa)-.*\Z/ }
21

22 23 24
  validates :fingerprint,
    uniqueness: true,
    presence: { message: 'cannot be generated' }
gitlabhq's avatar
gitlabhq committed
25

26 27
  validate :key_meets_restrictions

28
  delegate :name, :email, to: :user, prefix: true
miks's avatar
miks committed
29

30
  after_commit :add_to_shell, on: :create
31
  after_create :post_create_hook
32
  after_create :refresh_user_cache
33
  after_commit :remove_from_shell, on: :destroy
34
  after_destroy :post_destroy_hook
35
  after_destroy :refresh_user_cache
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
36

37 38 39 40
  def self.regular_keys
    where(type: ['Key', nil])
  end

41
  def key=(value)
Rubén Dávila's avatar
Rubén Dávila committed
42 43
    write_attribute(:key, value.present? ? Gitlab::SSHPublicKey.sanitize(value) : nil)

44
    @public_key = nil
miks's avatar
miks committed
45 46
  end

47
  def publishable_key
48 49 50
    # Strip out the keys comment so we don't leak email addresses
    # Replace with simple ident of user_name (hostname)
    self.key.split[0..1].push("#{self.user_name} (#{Gitlab.config.gitlab.host})").join(' ')
51 52
  end

53
  # projects that has this key
gitlabhq's avatar
gitlabhq committed
54
  def projects
55
    user.authorized_projects
gitlabhq's avatar
gitlabhq committed
56
  end
57

58
  def shell_id
59
    "key-#{id}"
60
  end
61

62 63 64 65 66
  # EE overrides this
  def can_delete?
    true
  end

67
  # rubocop: disable CodeReuse/ServiceClass
68
  def update_last_used_at
69
    Keys::LastUsedService.new(self).execute
70
  end
71
  # rubocop: enable CodeReuse/ServiceClass
72

Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
73 74 75 76 77 78 79 80
  def add_to_shell
    GitlabShellWorker.perform_async(
      :add_key,
      shell_id,
      key
    )
  end

81
  # rubocop: disable CodeReuse/ServiceClass
82 83 84
  def post_create_hook
    SystemHooksService.new.execute_hooks_for(self, :create)
  end
85
  # rubocop: enable CodeReuse/ServiceClass
86

Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
87 88 89 90
  def remove_from_shell
    GitlabShellWorker.perform_async(
      :remove_key,
      shell_id,
91
      key
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
92 93 94
    )
  end

95
  # rubocop: disable CodeReuse/ServiceClass
96 97 98 99 100
  def refresh_user_cache
    return unless user

    Users::KeysCountService.new(user).refresh_cache
  end
101
  # rubocop: enable CodeReuse/ServiceClass
102

103
  # rubocop: disable CodeReuse/ServiceClass
104 105 106
  def post_destroy_hook
    SystemHooksService.new.execute_hooks_for(self, :destroy)
  end
107
  # rubocop: enable CodeReuse/ServiceClass
108

109 110 111 112
  def public_key
    @public_key ||= Gitlab::SSHPublicKey.new(key)
  end

113 114
  private

Steven Burgart's avatar
Steven Burgart committed
115
  def generate_fingerprint
116
    self.fingerprint = nil
117

Rubén Dávila's avatar
Rubén Dávila committed
118
    return unless public_key.valid?
119

120 121 122 123
    self.fingerprint = public_key.fingerprint
  end

  def key_meets_restrictions
124
    restriction = Gitlab::CurrentSettings.key_restriction_for(public_key.type)
125 126 127 128 129 130 131 132 133 134

    if restriction == ApplicationSetting::FORBIDDEN_KEY_VALUE
      errors.add(:key, forbidden_key_type_message)
    elsif public_key.bits < restriction
      errors.add(:key, "must be at least #{restriction} bits")
    end
  end

  def forbidden_key_type_message
    allowed_types =
135
      Gitlab::CurrentSettings
136 137 138 139 140
        .allowed_key_types
        .map(&:upcase)
        .to_sentence(last_word_connector: ', or ', two_words_connector: ' or ')

    "type is forbidden. Must be #{allowed_types}"
141
  end
gitlabhq's avatar
gitlabhq committed
142
end
143 144

Key.prepend(EE::Key)