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 132 133
|
# 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, class_name: 'CommitSignatures::GpgSignature'
has_many :subkeys, class_name: 'GpgKeySubkey'
scope :with_subkeys, -> { includes(:subkeys) }
scope :externally_invalid, -> { where(externally_verified: false) }
scope :externally_valid, -> { where(externally_verified: true) }
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_create :generate_subkeys
after_commit :update_invalid_gpg_signatures, on: :create
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')
|