2020-01-01 13:55:28 +05:30
# frozen_string_literal: true
2019-12-04 20:38:33 +05:30
require 'spec_helper'
2017-09-10 17:25:29 +05:30
2020-07-28 23:09:34 +05:30
RSpec . describe Gitlab :: Gpg :: Commit do
2022-08-13 15:12:31 +05:30
let_it_be ( :project ) { create ( :project , :repository , path : 'sample-project' ) }
let ( :commit_sha ) { '0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33' }
let ( :committer_email ) { GpgHelpers :: User1 . emails . first }
let ( :user_email ) { committer_email }
let ( :public_key ) { GpgHelpers :: User1 . public_key }
let ( :user ) { create ( :user , email : user_email ) }
let ( :commit ) { create ( :commit , project : project , sha : commit_sha , committer_email : committer_email ) }
let ( :crypto ) { instance_double ( GPGME :: Crypto ) }
let ( :mock_signature_data? ) { true }
# gpg_keys must be pre-loaded so that they can be found during signature verification.
let! ( :gpg_key ) { create ( :gpg_key , key : public_key , user : user ) }
let ( :signature_data ) do
[
GpgHelpers :: User1 . signed_commit_signature ,
GpgHelpers :: User1 . signed_commit_base_data
]
end
before do
if mock_signature_data?
allow ( Gitlab :: Git :: Commit ) . to receive ( :extract_signature_lazily )
. with ( Gitlab :: Git :: Repository , commit_sha )
. and_return ( signature_data )
end
end
2017-09-10 17:25:29 +05:30
describe '#signature' do
2018-03-17 18:26:18 +05:30
shared_examples 'returns the cached signature on second call' do
2017-09-10 17:25:29 +05:30
it 'returns the cached signature on second call' do
2018-03-17 18:26:18 +05:30
gpg_commit = described_class . new ( commit )
2017-09-10 17:25:29 +05:30
expect ( gpg_commit ) . to receive ( :using_keychain ) . and_call_original
gpg_commit . signature
# consecutive call
expect ( gpg_commit ) . not_to receive ( :using_keychain ) . and_call_original
gpg_commit . signature
end
end
2018-03-17 18:26:18 +05:30
context 'unsigned commit' do
2022-08-13 15:12:31 +05:30
let ( :signature_data ) { nil }
2018-03-17 18:26:18 +05:30
it 'returns nil' do
expect ( described_class . new ( commit ) . signature ) . to be_nil
end
end
2019-02-15 15:39:39 +05:30
context 'invalid signature' do
2022-08-13 15:12:31 +05:30
let ( :signature_data ) do
[
# Corrupt the key
GpgHelpers :: User1 . signed_commit_signature . tr ( '=' , 'a' ) ,
GpgHelpers :: User1 . signed_commit_base_data
]
2019-02-15 15:39:39 +05:30
end
it 'returns nil' do
expect ( described_class . new ( commit ) . signature ) . to be_nil
end
end
2018-03-17 18:26:18 +05:30
context 'known key' do
context 'user matches the key uid' do
context 'user email matches the email committer' do
it 'returns a valid signature' do
2018-03-27 19:54:05 +05:30
signature = described_class . new ( commit ) . signature
expect ( signature ) . to have_attributes (
2018-03-17 18:26:18 +05:30
commit_sha : commit_sha ,
project : project ,
gpg_key : gpg_key ,
gpg_key_primary_keyid : GpgHelpers :: User1 . primary_keyid ,
gpg_key_user_name : GpgHelpers :: User1 . names . first ,
gpg_key_user_email : GpgHelpers :: User1 . emails . first ,
verification_status : 'verified'
)
2018-03-27 19:54:05 +05:30
expect ( signature . persisted? ) . to be_truthy
2018-03-17 18:26:18 +05:30
end
it_behaves_like 'returns the cached signature on second call'
2018-03-27 19:54:05 +05:30
context 'read-only mode' do
before do
allow ( Gitlab :: Database ) . to receive ( :read_only? ) . and_return ( true )
end
it 'does not create a cached signature' do
signature = described_class . new ( commit ) . signature
expect ( signature ) . to have_attributes (
commit_sha : commit_sha ,
project : project ,
gpg_key : gpg_key ,
gpg_key_primary_keyid : GpgHelpers :: User1 . primary_keyid ,
gpg_key_user_name : GpgHelpers :: User1 . names . first ,
gpg_key_user_email : GpgHelpers :: User1 . emails . first ,
verification_status : 'verified'
)
expect ( signature . persisted? ) . to be_falsey
end
end
2018-03-17 18:26:18 +05:30
end
2019-09-04 21:01:54 +05:30
context 'valid key signed using recent version of Gnupg' do
before do
verified_signature = double ( 'verified-signature' , fingerprint : GpgHelpers :: User1 . fingerprint , valid? : true )
allow ( GPGME :: Crypto ) . to receive ( :new ) . and_return ( crypto )
2021-12-11 22:18:48 +05:30
allow ( crypto ) . to receive ( :verify ) . and_yield ( verified_signature )
2022-08-13 15:12:31 +05:30
end
2019-09-04 21:01:54 +05:30
2022-08-13 15:12:31 +05:30
it 'returns a valid signature' do
2019-09-04 21:01:54 +05:30
signature = described_class . new ( commit ) . signature
expect ( signature ) . to have_attributes (
commit_sha : commit_sha ,
project : project ,
gpg_key : gpg_key ,
gpg_key_primary_keyid : GpgHelpers :: User1 . primary_keyid ,
gpg_key_user_name : GpgHelpers :: User1 . names . first ,
gpg_key_user_email : GpgHelpers :: User1 . emails . first ,
verification_status : 'verified'
)
end
end
context 'valid key signed using older version of Gnupg' do
before do
keyid = GpgHelpers :: User1 . fingerprint . last ( 16 )
verified_signature = double ( 'verified-signature' , fingerprint : keyid , valid? : true )
allow ( GPGME :: Crypto ) . to receive ( :new ) . and_return ( crypto )
2021-12-11 22:18:48 +05:30
allow ( crypto ) . to receive ( :verify ) . and_yield ( verified_signature )
2022-08-13 15:12:31 +05:30
end
2019-09-04 21:01:54 +05:30
2022-08-13 15:12:31 +05:30
it 'returns a valid signature' do
2019-09-04 21:01:54 +05:30
signature = described_class . new ( commit ) . signature
expect ( signature ) . to have_attributes (
commit_sha : commit_sha ,
project : project ,
gpg_key : gpg_key ,
gpg_key_primary_keyid : GpgHelpers :: User1 . primary_keyid ,
gpg_key_user_name : GpgHelpers :: User1 . names . first ,
gpg_key_user_email : GpgHelpers :: User1 . emails . first ,
verification_status : 'verified'
)
end
end
2021-12-11 22:18:48 +05:30
context 'commit with multiple signatures' do
before do
verified_signature = double ( 'verified-signature' , fingerprint : GpgHelpers :: User1 . fingerprint , valid? : true )
allow ( GPGME :: Crypto ) . to receive ( :new ) . and_return ( crypto )
allow ( crypto ) . to receive ( :verify ) . and_yield ( verified_signature ) . and_yield ( verified_signature )
2022-08-13 15:12:31 +05:30
end
2021-12-11 22:18:48 +05:30
2022-08-13 15:12:31 +05:30
it 'returns an invalid signatures error' do
2021-12-11 22:18:48 +05:30
signature = described_class . new ( commit ) . signature
expect ( signature ) . to have_attributes (
commit_sha : commit_sha ,
project : project ,
gpg_key : gpg_key ,
gpg_key_primary_keyid : GpgHelpers :: User1 . primary_keyid ,
gpg_key_user_name : GpgHelpers :: User1 . names . first ,
gpg_key_user_email : GpgHelpers :: User1 . emails . first ,
verification_status : 'multiple_signatures'
)
end
end
2018-03-17 18:26:18 +05:30
context 'commit signed with a subkey' do
2022-08-13 15:12:31 +05:30
let ( :committer_email ) { GpgHelpers :: User3 . emails . first }
let ( :public_key ) { GpgHelpers :: User3 . public_key }
2018-03-17 18:26:18 +05:30
let ( :gpg_key_subkey ) do
2019-10-03 12:08:05 +05:30
gpg_key . subkeys . find_by ( fingerprint : GpgHelpers :: User3 . subkey_fingerprints . last )
2018-03-17 18:26:18 +05:30
end
2022-08-13 15:12:31 +05:30
let ( :signature_data ) do
[
GpgHelpers :: User3 . signed_commit_signature ,
GpgHelpers :: User3 . signed_commit_base_data
]
2018-03-17 18:26:18 +05:30
end
it 'returns a valid signature' do
expect ( described_class . new ( commit ) . signature ) . to have_attributes (
commit_sha : commit_sha ,
project : project ,
gpg_key : gpg_key_subkey ,
gpg_key_primary_keyid : gpg_key_subkey . keyid ,
gpg_key_user_name : GpgHelpers :: User3 . names . first ,
gpg_key_user_email : GpgHelpers :: User3 . emails . first ,
verification_status : 'verified'
)
end
it_behaves_like 'returns the cached signature on second call'
end
2022-07-29 17:44:30 +05:30
context 'gpg key email does not match the committer_email but is the same user when the committer_email belongs to the user as a confirmed secondary email' do
2022-08-13 15:12:31 +05:30
let ( :committer_email ) { GpgHelpers :: User2 . emails . first }
2018-03-17 18:26:18 +05:30
let ( :user ) do
create ( :user , email : GpgHelpers :: User1 . emails . first ) . tap do | user |
2022-08-13 15:12:31 +05:30
create :email , :confirmed , user : user , email : committer_email
2018-03-17 18:26:18 +05:30
end
end
it 'returns an invalid signature' do
expect ( described_class . new ( commit ) . signature ) . to have_attributes (
commit_sha : commit_sha ,
project : project ,
gpg_key : gpg_key ,
gpg_key_primary_keyid : GpgHelpers :: User1 . primary_keyid ,
gpg_key_user_name : GpgHelpers :: User1 . names . first ,
gpg_key_user_email : GpgHelpers :: User1 . emails . first ,
verification_status : 'same_user_different_email'
)
end
it_behaves_like 'returns the cached signature on second call'
end
2022-07-29 17:44:30 +05:30
context 'gpg key email does not match the committer_email when the committer_email belongs to the user as a unconfirmed secondary email' do
2022-08-13 15:12:31 +05:30
let ( :committer_email ) { GpgHelpers :: User2 . emails . first }
2022-07-29 17:44:30 +05:30
let ( :user ) do
create ( :user , email : GpgHelpers :: User1 . emails . first ) . tap do | user |
2022-08-13 15:12:31 +05:30
create :email , user : user , email : committer_email
2022-07-29 17:44:30 +05:30
end
end
it 'returns an invalid signature' do
expect ( described_class . new ( commit ) . signature ) . to have_attributes (
commit_sha : commit_sha ,
project : project ,
gpg_key : gpg_key ,
gpg_key_primary_keyid : GpgHelpers :: User1 . primary_keyid ,
gpg_key_user_name : GpgHelpers :: User1 . names . first ,
gpg_key_user_email : GpgHelpers :: User1 . emails . first ,
verification_status : 'other_user'
)
end
it_behaves_like 'returns the cached signature on second call'
end
2018-03-17 18:26:18 +05:30
context 'user email does not match the committer email' do
2022-08-13 15:12:31 +05:30
let ( :committer_email ) { GpgHelpers :: User2 . emails . first }
let ( :user_email ) { GpgHelpers :: User1 . emails . first }
2018-03-17 18:26:18 +05:30
it 'returns an invalid signature' do
expect ( described_class . new ( commit ) . signature ) . to have_attributes (
commit_sha : commit_sha ,
project : project ,
gpg_key : gpg_key ,
gpg_key_primary_keyid : GpgHelpers :: User1 . primary_keyid ,
gpg_key_user_name : GpgHelpers :: User1 . names . first ,
gpg_key_user_email : GpgHelpers :: User1 . emails . first ,
verification_status : 'other_user'
)
end
it_behaves_like 'returns the cached signature on second call'
end
end
context 'user does not match the key uid' do
2022-08-13 15:12:31 +05:30
let ( :user_email ) { GpgHelpers :: User2 . emails . first }
let ( :public_key ) { GpgHelpers :: User1 . public_key }
2018-03-17 18:26:18 +05:30
it 'returns an invalid signature' do
expect ( described_class . new ( commit ) . signature ) . to have_attributes (
commit_sha : commit_sha ,
project : project ,
gpg_key : gpg_key ,
gpg_key_primary_keyid : GpgHelpers :: User1 . primary_keyid ,
gpg_key_user_name : GpgHelpers :: User1 . names . first ,
gpg_key_user_email : GpgHelpers :: User1 . emails . first ,
verification_status : 'unverified_key'
)
end
2017-09-10 17:25:29 +05:30
2018-03-17 18:26:18 +05:30
it_behaves_like 'returns the cached signature on second call'
2017-09-10 17:25:29 +05:30
end
end
2018-03-17 18:26:18 +05:30
context 'unknown key' do
2022-08-13 15:12:31 +05:30
let ( :gpg_key ) { nil }
2017-09-10 17:25:29 +05:30
it 'returns an invalid signature' do
2018-03-17 18:26:18 +05:30
expect ( described_class . new ( commit ) . signature ) . to have_attributes (
2017-09-10 17:25:29 +05:30
commit_sha : commit_sha ,
project : project ,
gpg_key : nil ,
gpg_key_primary_keyid : GpgHelpers :: User1 . primary_keyid ,
gpg_key_user_name : nil ,
gpg_key_user_email : nil ,
2018-03-17 18:26:18 +05:30
verification_status : 'unknown_key'
2017-09-10 17:25:29 +05:30
)
end
2018-03-17 18:26:18 +05:30
it_behaves_like 'returns the cached signature on second call'
2017-09-10 17:25:29 +05:30
end
2019-12-21 20:55:43 +05:30
context 'multiple commits with signatures' do
2022-08-13 15:12:31 +05:30
let ( :mock_signature_data? ) { false }
2019-12-21 20:55:43 +05:30
2022-08-13 15:12:31 +05:30
let! ( :first_signature ) { create ( :gpg_signature ) }
let! ( :gpg_key ) { create ( :gpg_key , key : GpgHelpers :: User2 . public_key ) }
let! ( :second_signature ) { create ( :gpg_signature , gpg_key : gpg_key ) }
2019-12-21 20:55:43 +05:30
let! ( :first_commit ) { create ( :commit , project : project , sha : first_signature . commit_sha ) }
let! ( :second_commit ) { create ( :commit , project : project , sha : second_signature . commit_sha ) }
2022-08-13 15:12:31 +05:30
let! ( :commits ) do
2019-12-21 20:55:43 +05:30
[ first_commit , second_commit ] . map do | commit |
gpg_commit = described_class . new ( commit )
allow ( gpg_commit ) . to receive ( :has_signature? ) . and_return ( true )
gpg_commit
end
end
it 'does an aggregated sql request instead of 2 separate ones' do
recorder = ActiveRecord :: QueryRecorder . new do
commits . each ( & :signature )
end
expect ( recorder . count ) . to eq ( 1 )
end
end
2017-09-10 17:25:29 +05:30
end
2022-08-13 15:12:31 +05:30
describe '#update_signature!' do
let! ( :gpg_key ) { nil }
let ( :signature ) { described_class . new ( commit ) . signature }
it 'updates signature record' do
signature
create ( :gpg_key , key : public_key , user : user )
stored_signature = CommitSignatures :: GpgSignature . find_by_commit_sha ( commit_sha )
expect { described_class . new ( commit ) . update_signature! ( stored_signature ) } . to (
change { signature . reload . verification_status } . from ( 'unknown_key' ) . to ( 'verified' )
)
end
end
2017-09-10 17:25:29 +05:30
end