2018-07-25 05:30:33 -04:00
|
|
|
# frozen_string_literal: true
|
|
|
|
|
2019-03-28 09:17:42 -04:00
|
|
|
class GpgKey < ApplicationRecord
|
2019-08-31 15:57:00 -04:00
|
|
|
KEY_PREFIX = '-----BEGIN PGP PUBLIC KEY BLOCK-----'
|
|
|
|
KEY_SUFFIX = '-----END PGP PUBLIC KEY BLOCK-----'
|
2017-02-22 06:49:17 -05:00
|
|
|
|
2017-07-20 09:44:15 -04:00
|
|
|
include ShaAttribute
|
|
|
|
|
|
|
|
sha_attribute :primary_keyid
|
|
|
|
sha_attribute :fingerprint
|
|
|
|
|
2017-02-22 06:49:17 -05:00
|
|
|
belongs_to :user
|
2017-07-20 10:33:44 -04:00
|
|
|
has_many :gpg_signatures
|
2017-09-27 13:27:39 -04:00
|
|
|
has_many :subkeys, class_name: 'GpgKeySubkey'
|
2017-02-22 06:49:17 -05:00
|
|
|
|
2017-09-27 21:45:08 -04:00
|
|
|
scope :with_subkeys, -> { includes(:subkeys) }
|
|
|
|
|
2017-07-05 08:03:36 -04:00
|
|
|
validates :user, presence: true
|
|
|
|
|
2017-02-22 06:49:17 -05:00
|
|
|
validates :key,
|
|
|
|
presence: true,
|
|
|
|
uniqueness: true,
|
|
|
|
format: {
|
2022-08-01 02:12:25 -04:00
|
|
|
with: /\A#{KEY_PREFIX}((?!#{KEY_PREFIX})(?!#{KEY_SUFFIX}).)+#{KEY_SUFFIX}\Z/mo,
|
2017-07-26 09:47:00 -04:00
|
|
|
message: "is invalid. A valid public GPG key begins with '#{KEY_PREFIX}' and ends with '#{KEY_SUFFIX}'"
|
2017-09-27 13:28:50 -04:00
|
|
|
}
|
2017-02-22 06:49:17 -05:00
|
|
|
|
2017-02-22 10:22:39 -05:00
|
|
|
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) }
|
|
|
|
|
2017-06-13 07:46:43 -04:00
|
|
|
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) }
|
|
|
|
|
2017-09-27 13:28:50 -04:00
|
|
|
before_validation :extract_fingerprint, :extract_primary_keyid
|
2017-07-05 08:23:23 -04:00
|
|
|
after_commit :update_invalid_gpg_signatures, on: :create
|
2017-09-27 13:28:50 -04:00
|
|
|
after_create :generate_subkeys
|
2017-02-22 06:49:17 -05:00
|
|
|
|
2017-07-25 10:23:52 -04:00
|
|
|
def primary_keyid
|
|
|
|
super&.upcase
|
|
|
|
end
|
2017-09-29 18:55:36 -04:00
|
|
|
alias_method :keyid, :primary_keyid
|
2017-07-25 10:23:52 -04:00
|
|
|
|
|
|
|
def fingerprint
|
|
|
|
super&.upcase
|
|
|
|
end
|
|
|
|
|
2017-02-22 06:49:17 -05:00
|
|
|
def key=(value)
|
2017-07-25 14:35:44 -04:00
|
|
|
super(value&.strip)
|
2017-02-22 06:49:17 -05:00
|
|
|
end
|
|
|
|
|
2017-09-29 18:55:36 -04:00
|
|
|
def keyids
|
|
|
|
[keyid].concat(subkeys.map(&:keyid))
|
|
|
|
end
|
|
|
|
|
2017-07-13 09:22:15 -04:00
|
|
|
def user_infos
|
|
|
|
@user_infos ||= Gitlab::Gpg.user_infos_from_key(key)
|
|
|
|
end
|
|
|
|
|
|
|
|
def verified_user_infos
|
|
|
|
user_infos.select do |user_info|
|
2017-08-28 06:31:14 -04:00
|
|
|
user.verified_email?(user_info[:email])
|
2017-07-13 09:22:15 -04:00
|
|
|
end
|
2017-02-22 09:37:49 -05:00
|
|
|
end
|
|
|
|
|
2017-02-28 09:25:12 -05:00
|
|
|
def emails_with_verified_status
|
2021-03-28 11:09:30 -04:00
|
|
|
user_infos.to_h do |user_info|
|
2017-02-24 15:28:26 -05:00
|
|
|
[
|
2017-07-13 09:22:15 -04:00
|
|
|
user_info[:email],
|
2017-08-28 06:31:14 -04:00
|
|
|
user.verified_email?(user_info[:email])
|
2017-02-24 15:28:26 -05:00
|
|
|
]
|
2021-03-28 11:09:30 -04:00
|
|
|
end
|
2017-02-24 15:28:26 -05:00
|
|
|
end
|
|
|
|
|
2017-06-15 03:57:50 -04:00
|
|
|
def verified?
|
2017-08-24 10:52:06 -04:00
|
|
|
emails_with_verified_status.values.any?
|
2017-06-15 03:57:50 -04:00
|
|
|
end
|
|
|
|
|
2017-08-24 08:21:30 -04:00
|
|
|
def verified_and_belongs_to_email?(email)
|
2017-09-19 13:57:01 -04:00
|
|
|
emails_with_verified_status.fetch(email.downcase, false)
|
2017-08-24 08:21:30 -04:00
|
|
|
end
|
|
|
|
|
2017-06-15 09:07:44 -04:00
|
|
|
def update_invalid_gpg_signatures
|
2017-06-22 08:18:01 -04:00
|
|
|
InvalidGpgSignatureUpdateWorker.perform_async(self.id)
|
2017-06-15 09:07:44 -04:00
|
|
|
end
|
|
|
|
|
2017-07-12 01:59:28 -04:00
|
|
|
def revoke
|
2021-12-13 07:12:59 -05:00
|
|
|
CommitSignatures::GpgSignature
|
2017-10-04 19:44:49 -04:00
|
|
|
.with_key_and_subkeys(self)
|
2021-12-13 07:12:59 -05:00
|
|
|
.where.not(verification_status: CommitSignatures::GpgSignature.verification_statuses[:unknown_key])
|
2017-08-30 07:27:40 -04:00
|
|
|
.update_all(
|
|
|
|
gpg_key_id: nil,
|
2017-10-04 19:44:49 -04:00
|
|
|
gpg_key_subkey_id: nil,
|
2021-12-13 07:12:59 -05:00
|
|
|
verification_status: CommitSignatures::GpgSignature.verification_statuses[:unknown_key],
|
2017-08-30 07:27:40 -04:00
|
|
|
updated_at: Time.zone.now
|
|
|
|
)
|
2017-07-12 01:59:28 -04:00
|
|
|
|
|
|
|
destroy
|
|
|
|
end
|
|
|
|
|
2017-02-22 06:49:17 -05:00
|
|
|
private
|
|
|
|
|
|
|
|
def extract_fingerprint
|
|
|
|
# we can assume that the result only contains one item as the validation
|
|
|
|
# only allows one key
|
2017-02-22 11:20:42 -05:00
|
|
|
self.fingerprint = Gitlab::Gpg.fingerprints_from_key(key).first
|
2017-02-22 06:49:17 -05:00
|
|
|
end
|
2017-02-22 12:36:25 -05:00
|
|
|
|
2017-06-13 07:46:43 -04:00
|
|
|
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
|
2017-09-26 20:42:23 -04:00
|
|
|
|
|
|
|
def generate_subkeys
|
|
|
|
gpg_subkeys = Gitlab::Gpg.subkeys_from_key(key)
|
|
|
|
|
2017-10-01 22:50:38 -04:00
|
|
|
gpg_subkeys[primary_keyid]&.each do |subkey_data|
|
2017-09-27 13:28:50 -04:00
|
|
|
subkeys.create!(keyid: subkey_data[:keyid], fingerprint: subkey_data[:fingerprint])
|
2017-09-26 20:42:23 -04:00
|
|
|
end
|
|
|
|
end
|
2017-02-22 06:49:17 -05:00
|
|
|
end
|
2020-12-24 10:09:57 -05:00
|
|
|
|
2021-05-11 17:10:21 -04:00
|
|
|
GpgKey.prepend_mod_with('GpgKey')
|