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
131
|
# frozen_string_literal: true
class GpgKey < ApplicationRecord
KEY_PREFIX = '-----BEGIN PGP PUBLIC KEY BLOCK-----'
KEY_SUFFIX = '-----END PGP PUBLIC KEY BLOCK-----'
include ShaAttribute
sha_attribute :primary_keyid
sha_attribute :fingerprint
belongs_to :user
has_many :gpg_signatures
has_many :subkeys, class_name: 'GpgKeySubkey'
scope :with_subkeys, -> { includes(:subkeys) }
validates :user, presence: true
validates :key,
presence: true,
uniqueness: true,
format: {
with: /\A#{KEY_PREFIX}((?!#{KEY_PREFIX})(?!#{KEY_SUFFIX}).)+#{KEY_SUFFIX}\Z/mo,
message: "is invalid. A valid public GPG key begins with '#{KEY_PREFIX}' and ends with '#{KEY_SUFFIX}'"
}
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) }
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
after_commit :update_invalid_gpg_signatures, on: :create
after_create :generate_subkeys
def primary_keyid
super&.upcase
end
alias_method :keyid, :primary_keyid
def fingerprint
super&.upcase
end
def key=(value)
super(value&.strip)
end
def keyids
[keyid].concat(subkeys.map(&:keyid))
end
def user_infos
@user_infos ||= Gitlab::Gpg.user_infos_from_key(key)
end
def verified_user_infos
user_infos.select do |user_info|
user.verified_email?(user_info[:email])
end
end
def emails_with_verified_status
user_infos.to_h do |user_info|
[
user_info[:email],
user.verified_email?(user_info[:email])
]
end
end
def verified?
emails_with_verified_status.values.any?
end
def verified_and_belongs_to_email?(email)
emails_with_verified_status.fetch(email.downcase, false)
end
def update_invalid_gpg_signatures
InvalidGpgSignatureUpdateWorker.perform_async(self.id)
end
def revoke
CommitSignatures::GpgSignature
.with_key_and_subkeys(self)
.where.not(verification_status: CommitSignatures::GpgSignature.verification_statuses[:unknown_key])
.update_all(
gpg_key_id: nil,
gpg_key_subkey_id: nil,
verification_status: CommitSignatures::GpgSignature.verification_statuses[:unknown_key],
updated_at: Time.zone.now
)
destroy
end
private
def extract_fingerprint
# we can assume that the result only contains one item as the validation
# only allows one key
self.fingerprint = Gitlab::Gpg.fingerprints_from_key(key).first
end
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
def generate_subkeys
gpg_subkeys = Gitlab::Gpg.subkeys_from_key(key)
gpg_subkeys[primary_keyid]&.each do |subkey_data|
subkeys.create!(keyid: subkey_data[:keyid], fingerprint: subkey_data[:fingerprint])
end
end
end
GpgKey.prepend_mod_with('GpgKey')
|