gpg_key.rb 2.68 KB
Newer Older
Alexis Reigel's avatar
Alexis Reigel committed
1
2
class GpgKey < ActiveRecord::Base
  KEY_PREFIX = '-----BEGIN PGP PUBLIC KEY BLOCK-----'.freeze
Alexis Reigel's avatar
Alexis Reigel committed
3
  KEY_SUFFIX = '-----END PGP PUBLIC KEY BLOCK-----'.freeze
Alexis Reigel's avatar
Alexis Reigel committed
4

5
6
7
8
9
  include ShaAttribute

  sha_attribute :primary_keyid
  sha_attribute :fingerprint

Alexis Reigel's avatar
Alexis Reigel committed
10
  belongs_to :user
11
  has_many :gpg_signatures
Alexis Reigel's avatar
Alexis Reigel committed
12

13
14
  validates :user, presence: true

Alexis Reigel's avatar
Alexis Reigel committed
15
16
17
18
  validates :key,
    presence: true,
    uniqueness: true,
    format: {
Alexis Reigel's avatar
Alexis Reigel committed
19
20
      with: /\A#{KEY_PREFIX}((?!#{KEY_PREFIX})(?!#{KEY_SUFFIX}).)+#{KEY_SUFFIX}\Z/m,
      message: "is invalid. A valid public GPG key begins with '#{KEY_PREFIX}' and ends with '#{KEY_SUFFIX}'"
Alexis Reigel's avatar
Alexis Reigel committed
21
22
    }

23
24
25
26
27
28
29
  validates :fingerprint,
    presence: true,
    uniqueness: true,
    # only validate when the `key` is valid, as we don't want the user to show
    # the error about the fingerprint
    unless: -> { errors.has_key?(:key) }

30
31
32
33
34
35
36
37
  validates :primary_keyid,
    presence: true,
    uniqueness: true,
    # only validate when the `key` is valid, as we don't want the user to show
    # the error about the fingerprint
    unless: -> { errors.has_key?(:key) }

  before_validation :extract_fingerprint, :extract_primary_keyid
38
  after_commit :update_invalid_gpg_signatures, on: :create
Alexis Reigel's avatar
Alexis Reigel committed
39

40
41
42
43
44
45
46
47
  def primary_keyid
    super&.upcase
  end

  def fingerprint
    super&.upcase
  end

Alexis Reigel's avatar
Alexis Reigel committed
48
  def key=(value)
Alexis Reigel's avatar
Alexis Reigel committed
49
    super(value&.strip)
Alexis Reigel's avatar
Alexis Reigel committed
50
51
  end

52
53
54
55
56
57
  def user_infos
    @user_infos ||= Gitlab::Gpg.user_infos_from_key(key)
  end

  def verified_user_infos
    user_infos.select do |user_info|
58
      user.verified_email?(user_info[:email])
59
    end
60
61
  end

Alexis Reigel's avatar
Alexis Reigel committed
62
  def emails_with_verified_status
63
    user_infos.map do |user_info|
Alexis Reigel's avatar
Alexis Reigel committed
64
      [
65
        user_info[:email],
66
        user.verified_email?(user_info[:email])
Alexis Reigel's avatar
Alexis Reigel committed
67
      ]
Alexis Reigel's avatar
Alexis Reigel committed
68
    end.to_h
Alexis Reigel's avatar
Alexis Reigel committed
69
70
  end

71
  def verified?
72
    emails_with_verified_status.values.any?
73
74
  end

75
  def verified_and_belongs_to_email?(email)
76
    emails_with_verified_status.fetch(email.downcase, false)
77
78
  end

79
  def update_invalid_gpg_signatures
80
    InvalidGpgSignatureUpdateWorker.perform_async(self.id)
81
82
  end

Alexis Reigel's avatar
Alexis Reigel committed
83
  def revoke
Alexis Reigel's avatar
Alexis Reigel committed
84
85
86
87
88
89
90
91
    GpgSignature
      .where(gpg_key: self)
      .where.not(verification_status: GpgSignature.verification_statuses[:unknown_key])
      .update_all(
        gpg_key_id: nil,
        verification_status: GpgSignature.verification_statuses[:unknown_key],
        updated_at: Time.zone.now
      )
Alexis Reigel's avatar
Alexis Reigel committed
92
93
94
95

    destroy
  end

Alexis Reigel's avatar
Alexis Reigel committed
96
97
98
99
100
  private

  def extract_fingerprint
    # we can assume that the result only contains one item as the validation
    # only allows one key
101
    self.fingerprint = Gitlab::Gpg.fingerprints_from_key(key).first
Alexis Reigel's avatar
Alexis Reigel committed
102
  end
103

104
105
106
107
108
  def extract_primary_keyid
    # we can assume that the result only contains one item as the validation
    # only allows one key
    self.primary_keyid = Gitlab::Gpg.primary_keyids_from_key(key).first
  end
Alexis Reigel's avatar
Alexis Reigel committed
109
end