summaryrefslogtreecommitdiff
path: root/app/models/key.rb
blob: 295680d1ae455fe67314c4d4cfd2f3bf73721d12 (plain)
1
2
3
4
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
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
require 'digest/md5'

class Key < ActiveRecord::Base
  include AfterCommitQueue
  include Gitlab::CurrentSettings
  include Sortable

  LAST_USED_AT_REFRESH_TIME = 1.day.to_i

  belongs_to :user

  before_validation :generate_fingerprint

  validates :title,
    presence: true,
    length: { maximum: 255 }
  validates :key,
    presence: true,
    length: { maximum: 5000 },
    format: { with: /\A(ssh|ecdsa)-.*\Z/ }
  validates :key,
    format: { without: /\n|\r/, message: 'should be a single line' }
  validates :fingerprint,
    uniqueness: true,
    presence: { message: 'cannot be generated' }
  validate :key_meets_minimum_bit_length, :key_type_is_allowed

  delegate :name, :email, to: :user, prefix: true

  after_create :add_to_shell
  after_create :notify_user
  after_create :post_create_hook
  after_destroy :remove_from_shell
  after_destroy :post_destroy_hook

  def key=(value)
    value.strip! unless value.blank?
    write_attribute(:key, value)
  end

  def publishable_key
    # 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(' ')
  end

  # projects that has this key
  def projects
    user.authorized_projects
  end

  def shell_id
    "key-#{id}"
  end

  def update_last_used_at
    lease = Gitlab::ExclusiveLease.new("key_update_last_used_at:#{id}", timeout: LAST_USED_AT_REFRESH_TIME)
    return unless lease.try_obtain

    UseKeyWorker.perform_async(id)
  end

  def add_to_shell
    GitlabShellWorker.perform_async(
      :add_key,
      shell_id,
      key
    )
  end

  def post_create_hook
    SystemHooksService.new.execute_hooks_for(self, :create)
  end

  def remove_from_shell
    GitlabShellWorker.perform_async(
      :remove_key,
      shell_id,
      key
    )
  end

  def post_destroy_hook
    SystemHooksService.new.execute_hooks_for(self, :destroy)
  end

  private

  def generate_fingerprint
    self.fingerprint = nil

    return unless self.key.present?

    self.fingerprint = public_key.fingerprint
  end

  def key_meets_minimum_bit_length
    case public_key.type
    when :ecdsa
      if public_key.size < current_application_settings.minimum_ecdsa_bits
        errors.add(:key, "elliptic curve size must be at least #{current_application_settings.minimum_ecdsa_bits} bits")
      end
    when :rsa
      if public_key.size < current_application_settings.minimum_rsa_bits
        errors.add(:key, "length must be at least #{current_application_settings.minimum_rsa_bits} bits")
      end
    when :dsa
      if public_key.size < current_application_settings.minimum_dsa_bits
        errors.add(:key, "length must be at least #{current_application_settings.minimum_dsa_bits} bits")
      end
    end
  end

  def key_type_is_allowed
    unless current_application_settings.allowed_key_types.include?(public_key.type.to_s)
      allowed_types = current_application_settings.allowed_key_types.
        map(&:upcase).
        to_sentence(last_word_connector: ', or ', two_words_connector: ' or ')
      errors.add(:key, "type is not allowed. Must be #{allowed_types}")
    end
  end

  def public_key
    @public_key ||= Gitlab::SSHPublicKey.new(key)
  end

  def notify_user
    run_after_commit { NotificationService.new.new_key(self) }
  end
end