diff options
Diffstat (limited to 'app/models/gpg_key.rb')
-rw-r--r-- | app/models/gpg_key.rb | 127 |
1 files changed, 127 insertions, 0 deletions
diff --git a/app/models/gpg_key.rb b/app/models/gpg_key.rb new file mode 100644 index 00000000000..44eda741679 --- /dev/null +++ b/app/models/gpg_key.rb @@ -0,0 +1,127 @@ +class GpgKey < ActiveRecord::Base + KEY_PREFIX = '-----BEGIN PGP PUBLIC KEY BLOCK-----'.freeze + KEY_SUFFIX = '-----END PGP PUBLIC KEY BLOCK-----'.freeze + + 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/m, + 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.map do |user_info| + [ + user_info[:email], + user.verified_email?(user_info[:email]) + ] + end.to_h + 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 + GpgSignature + .with_key_and_subkeys(self) + .where.not(verification_status: GpgSignature.verification_statuses[:unknown_key]) + .update_all( + gpg_key_id: nil, + gpg_key_subkey_id: nil, + verification_status: 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 |