diff options
Diffstat (limited to 'spec')
18 files changed, 1079 insertions, 12 deletions
diff --git a/spec/controllers/projects/compare_controller_spec.rb b/spec/controllers/projects/compare_controller_spec.rb index bd50811726a..d1a4a9a0058 100644 --- a/spec/controllers/projects/compare_controller_spec.rb +++ b/spec/controllers/projects/compare_controller_spec.rb @@ -281,7 +281,7 @@ describe Projects::CompareController do context 'when the user has access to the project' do render_views - let(:signature_commit) { build(:commit, project: project, safe_message: "message", sha: 'signature_commit') } + let(:signature_commit) { project.commit_by(oid: '0b4bc9a49b562e85de7cc9e834518ea6828729b9') } let(:non_signature_commit) { build(:commit, project: project, safe_message: "message", sha: 'non_signature_commit') } before do diff --git a/spec/factories/x509_certificate.rb b/spec/factories/x509_certificate.rb new file mode 100644 index 00000000000..819ad0704dc --- /dev/null +++ b/spec/factories/x509_certificate.rb @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +FactoryBot.define do + factory :x509_certificate do + subject_key_identifier { 'BC:BC:BC:BC:BC:BC:BC:BC:BC:BC:BC:BC:BC:BC:BC:BC:BC:BC:BC:BC' } + subject { 'CN=gitlab@example.org,OU=Example,O=World' } + + email { 'gitlab@example.org' } + serial_number { 278969561018901340486471282831158785578 } + x509_issuer + end +end diff --git a/spec/factories/x509_commit_signature.rb b/spec/factories/x509_commit_signature.rb new file mode 100644 index 00000000000..a342b240690 --- /dev/null +++ b/spec/factories/x509_commit_signature.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +FactoryBot.define do + factory :x509_commit_signature do + commit_sha { Digest::SHA1.hexdigest(SecureRandom.hex) } + project + x509_certificate + verification_status { :verified } + end +end diff --git a/spec/factories/x509_issuer.rb b/spec/factories/x509_issuer.rb new file mode 100644 index 00000000000..e003b16ad86 --- /dev/null +++ b/spec/factories/x509_issuer.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +FactoryBot.define do + factory :x509_issuer do + subject_key_identifier { 'AB:AB:AB:AB:AB:AB:AB:AB:AB:AB:AB:AB:AB:AB:AB:AB:AB:AB:AB:AB' } + subject { 'CN=PKI,OU=Example,O=World' } + + crl_url { 'http://example.com/pki.crl' } + end +end diff --git a/spec/frontend/blob/components/__snapshots__/blob_header_filepath_spec.js.snap b/spec/frontend/blob/components/__snapshots__/blob_header_filepath_spec.js.snap new file mode 100644 index 00000000000..7382a3a4cf7 --- /dev/null +++ b/spec/frontend/blob/components/__snapshots__/blob_header_filepath_spec.js.snap @@ -0,0 +1,35 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Blob Header Filepath rendering matches the snapshot 1`] = ` +<div + class="file-header-content d-flex align-items-center lh-100" +> + + <file-icon-stub + aria-hidden="true" + cssclasses="mr-2" + filename="dummy.md" + size="18" + /> + + <strong + class="file-title-name qa-file-title-name mr-1 js-blob-header-filepath" + > + dummy.md + </strong> + + <small + class="mr-2" + > + a lot + </small> + + <clipboard-button-stub + cssclass="btn-clipboard btn-transparent lh-100 position-static" + gfm="\`dummy.md\`" + text="dummy.md" + title="Copy file path" + tooltipplacement="top" + /> +</div> +`; diff --git a/spec/frontend/blob/components/blob_header_filepath_spec.js b/spec/frontend/blob/components/blob_header_filepath_spec.js new file mode 100644 index 00000000000..d029ba2a7a4 --- /dev/null +++ b/spec/frontend/blob/components/blob_header_filepath_spec.js @@ -0,0 +1,90 @@ +import { shallowMount } from '@vue/test-utils'; +import BlobHeaderFilepath from '~/blob/components/blob_header_filepath.vue'; +import ClipboardButton from '~/vue_shared/components/clipboard_button.vue'; +import { Blob as MockBlob } from './mock_data'; +import { numberToHumanSize } from '~/lib/utils/number_utils'; + +const mockHumanReadableSize = 'a lot'; +jest.mock('~/lib/utils/number_utils', () => ({ + numberToHumanSize: jest.fn(() => mockHumanReadableSize), +})); + +describe('Blob Header Filepath', () => { + let wrapper; + + function createComponent(blobProps = {}, options = {}) { + wrapper = shallowMount(BlobHeaderFilepath, { + propsData: { + blob: Object.assign({}, MockBlob, blobProps), + }, + ...options, + }); + } + + afterEach(() => { + wrapper.destroy(); + }); + + describe('rendering', () => { + it('matches the snapshot', () => { + createComponent(); + expect(wrapper.element).toMatchSnapshot(); + }); + + it('renders regular name', () => { + createComponent(); + expect( + wrapper + .find('.js-blob-header-filepath') + .text() + .trim(), + ).toBe(MockBlob.name); + }); + + it('does not fail if the name is empty', () => { + const emptyName = ''; + createComponent({ name: emptyName }); + expect(wrapper.find('.js-blob-header-filepath').exists()).toBe(false); + }); + + it('renders copy-to-clipboard icon that copies path of the Blob', () => { + createComponent(); + const btn = wrapper.find(ClipboardButton); + expect(btn.exists()).toBe(true); + expect(btn.vm.text).toBe(MockBlob.path); + }); + + it('renders filesize in a human-friendly format', () => { + createComponent(); + expect(numberToHumanSize).toHaveBeenCalled(); + expect(wrapper.vm.blobSize).toBe(mockHumanReadableSize); + }); + + it('renders a slot and prepends its contents to the existing one', () => { + const slotContent = 'Foo Bar'; + createComponent( + {}, + { + scopedSlots: { + filepathPrepend: `<span>${slotContent}</span>`, + }, + }, + ); + + expect(wrapper.text()).toContain(slotContent); + expect( + wrapper + .text() + .trim() + .substring(0, slotContent.length), + ).toBe(slotContent); + }); + }); + + describe('functionality', () => { + it('sets gfm value correctly on the clipboard-button', () => { + createComponent(); + expect(wrapper.vm.gfmCopyText).toBe('`dummy.md`'); + }); + }); +}); diff --git a/spec/frontend/blob/components/mock_data.js b/spec/frontend/blob/components/mock_data.js new file mode 100644 index 00000000000..4f7b297aba0 --- /dev/null +++ b/spec/frontend/blob/components/mock_data.js @@ -0,0 +1,29 @@ +export const Blob = { + binary: false, + highlightedData: + '<h1 data-sourcepos="1:1-1:19" dir="auto">\n<a id="user-content-this-one-is-dummy" class="anchor" href="#this-one-is-dummy" aria-hidden="true"></a>This one is dummy</h1>\n<h2 data-sourcepos="3:1-3:21" dir="auto">\n<a id="user-content-and-has-sub-header" class="anchor" href="#and-has-sub-header" aria-hidden="true"></a>And has sub-header</h2>\n<p data-sourcepos="5:1-5:27" dir="auto">Even some stupid text here</p>', + name: 'dummy.md', + path: 'dummy.md', + rawPath: '/flightjs/flight/snippets/51/raw', + size: 75, + simpleViewer: { + collapsed: false, + fileType: 'text', + loadAsync: true, + loadingPartialName: 'loading', + renderError: null, + tooLarge: false, + type: 'simple', + }, + richViewer: { + collapsed: false, + fileType: 'markup', + loadAsync: true, + loadingPartialName: 'loading', + renderError: null, + tooLarge: false, + type: 'rich', + }, +}; + +export default {}; diff --git a/spec/lib/gitlab/x509/commit_spec.rb b/spec/lib/gitlab/x509/commit_spec.rb new file mode 100644 index 00000000000..9cddf27ddce --- /dev/null +++ b/spec/lib/gitlab/x509/commit_spec.rb @@ -0,0 +1,208 @@ +# frozen_string_literal: true +require 'spec_helper' + +describe Gitlab::X509::Commit do + describe '#signature' do + let(:signature) { described_class.new(commit).signature } + + let(:user1_certificate_attributes) do + { + subject_key_identifier: X509Helpers::User1.certificate_subject_key_identifier, + subject: X509Helpers::User1.certificate_subject, + email: X509Helpers::User1.certificate_email, + serial_number: X509Helpers::User1.certificate_serial + } + end + + let(:user1_issuer_attributes) do + { + subject_key_identifier: X509Helpers::User1.issuer_subject_key_identifier, + subject: X509Helpers::User1.certificate_issuer, + crl_url: X509Helpers::User1.certificate_crl + } + end + + shared_examples 'returns the cached signature on second call' do + it 'returns the cached signature on second call' do + x509_commit = described_class.new(commit) + + expect(x509_commit).to receive(:create_cached_signature).and_call_original + signature + + # consecutive call + expect(x509_commit).not_to receive(:create_cached_signature).and_call_original + signature + end + end + + let!(:project) { create :project, :repository, path: X509Helpers::User1.path } + let!(:commit_sha) { X509Helpers::User1.commit } + + context 'unsigned commit' do + let!(:commit) { create :commit, project: project, sha: commit_sha } + + it 'returns nil' do + expect(described_class.new(commit).signature).to be_nil + end + end + + context 'valid signature from known user' do + let!(:commit) { create :commit, project: project, sha: commit_sha, created_at: Time.utc(2019, 1, 1, 20, 15, 0), committer_email: X509Helpers::User1.emails.first } + + let!(:user) { create(:user, email: X509Helpers::User1.emails.first) } + + before do + allow(Gitlab::Git::Commit).to receive(:extract_signature_lazily) + .with(Gitlab::Git::Repository, commit_sha) + .and_return( + [ + X509Helpers::User1.signed_commit_signature, + X509Helpers::User1.signed_commit_base_data + ] + ) + end + + it 'returns an unverified signature' do + expect(signature).to have_attributes( + commit_sha: commit_sha, + project: project, + verification_status: 'unverified' + ) + expect(signature.x509_certificate).to have_attributes(user1_certificate_attributes) + expect(signature.x509_certificate.x509_issuer).to have_attributes(user1_issuer_attributes) + expect(signature.persisted?).to be_truthy + end + end + + context 'verified signature from known user' do + let!(:commit) { create :commit, project: project, sha: commit_sha, created_at: Time.utc(2019, 1, 1, 20, 15, 0), committer_email: X509Helpers::User1.emails.first } + + let!(:user) { create(:user, email: X509Helpers::User1.emails.first) } + + before do + allow(Gitlab::Git::Commit).to receive(:extract_signature_lazily) + .with(Gitlab::Git::Repository, commit_sha) + .and_return( + [ + X509Helpers::User1.signed_commit_signature, + X509Helpers::User1.signed_commit_base_data + ] + ) + end + + context 'with trusted certificate store' do + before do + store = OpenSSL::X509::Store.new + certificate = OpenSSL::X509::Certificate.new X509Helpers::User1.trust_cert + store.add_cert(certificate) + allow(OpenSSL::X509::Store).to receive(:new) + .and_return( + store + ) + end + + it 'returns a verified signature' do + expect(signature).to have_attributes( + commit_sha: commit_sha, + project: project, + verification_status: 'verified' + ) + expect(signature.x509_certificate).to have_attributes(user1_certificate_attributes) + expect(signature.x509_certificate.x509_issuer).to have_attributes(user1_issuer_attributes) + expect(signature.persisted?).to be_truthy + end + end + + context 'without trusted certificate within store' do + before do + store = OpenSSL::X509::Store.new + allow(OpenSSL::X509::Store).to receive(:new) + .and_return( + store + ) + end + + it 'returns an unverified signature' do + expect(signature).to have_attributes( + commit_sha: commit_sha, + project: project, + verification_status: 'unverified' + ) + expect(signature.x509_certificate).to have_attributes(user1_certificate_attributes) + expect(signature.x509_certificate.x509_issuer).to have_attributes(user1_issuer_attributes) + expect(signature.persisted?).to be_truthy + end + end + end + + context 'unverified signature from unknown user' do + let!(:commit) { create :commit, project: project, sha: commit_sha, created_at: Time.utc(2019, 1, 1, 20, 15, 0), committer_email: X509Helpers::User1.emails.first } + + before do + allow(Gitlab::Git::Commit).to receive(:extract_signature_lazily) + .with(Gitlab::Git::Repository, commit_sha) + .and_return( + [ + X509Helpers::User1.signed_commit_signature, + X509Helpers::User1.signed_commit_base_data + ] + ) + end + + it 'returns an unverified signature' do + expect(signature).to have_attributes( + commit_sha: commit_sha, + project: project, + verification_status: 'unverified' + ) + expect(signature.x509_certificate).to have_attributes(user1_certificate_attributes) + expect(signature.x509_certificate.x509_issuer).to have_attributes(user1_issuer_attributes) + expect(signature.persisted?).to be_truthy + end + end + + context 'invalid signature' do + let!(:commit) { create :commit, project: project, sha: commit_sha, committer_email: X509Helpers::User1.emails.first } + + let!(:user) { create(:user, email: X509Helpers::User1.emails.first) } + + before do + allow(Gitlab::Git::Commit).to receive(:extract_signature_lazily) + .with(Gitlab::Git::Repository, commit_sha) + .and_return( + [ + # Corrupt the key + X509Helpers::User1.signed_commit_signature.tr('A', 'B'), + X509Helpers::User1.signed_commit_base_data + ] + ) + end + + it 'returns nil' do + expect(described_class.new(commit).signature).to be_nil + end + end + + context 'invalid commit message' do + let!(:commit) { create :commit, project: project, sha: commit_sha, committer_email: X509Helpers::User1.emails.first } + + let!(:user) { create(:user, email: X509Helpers::User1.emails.first) } + + before do + allow(Gitlab::Git::Commit).to receive(:extract_signature_lazily) + .with(Gitlab::Git::Repository, commit_sha) + .and_return( + [ + X509Helpers::User1.signed_commit_signature, + # Corrupt the commit message + 'x' + ] + ) + end + + it 'returns nil' do + expect(described_class.new(commit).signature).to be_nil + end + end + end +end diff --git a/spec/migrations/migrate_create_commit_signature_worker_sidekiq_queue_spec.rb b/spec/migrations/migrate_create_commit_signature_worker_sidekiq_queue_spec.rb new file mode 100644 index 00000000000..3d7803b7563 --- /dev/null +++ b/spec/migrations/migrate_create_commit_signature_worker_sidekiq_queue_spec.rb @@ -0,0 +1,44 @@ +# frozen_string_literal: true + +require 'spec_helper' +require Rails.root.join('db', 'post_migrate', '20200206091544_migrate_create_commit_signature_worker_sidekiq_queue.rb') + +describe MigrateCreateCommitSignatureWorkerSidekiqQueue, :sidekiq, :redis do + include Gitlab::Database::MigrationHelpers + include StubWorker + + context 'when there are jobs in the queue' do + it 'correctly migrates queue when migrating up' do + Sidekiq::Testing.disable! do + stub_worker(queue: 'create_commit_signature').perform_async('Something', [1]) + stub_worker(queue: 'create_gpg_signature').perform_async('Something', [1]) + + described_class.new.up + + expect(sidekiq_queue_length('create_gpg_signature')).to eq 0 + expect(sidekiq_queue_length('create_commit_signature')).to eq 2 + end + end + + it 'correctly migrates queue when migrating down' do + Sidekiq::Testing.disable! do + stub_worker(queue: 'create_gpg_signature').perform_async('Something', [1]) + + described_class.new.down + + expect(sidekiq_queue_length('create_gpg_signature')).to eq 1 + expect(sidekiq_queue_length('create_commit_signature')).to eq 0 + end + end + end + + context 'when there are no jobs in the queues' do + it 'does not raise error when migrating up' do + expect { described_class.new.up }.not_to raise_error + end + + it 'does not raise error when migrating down' do + expect { described_class.new.down }.not_to raise_error + end + end +end diff --git a/spec/models/commit_spec.rb b/spec/models/commit_spec.rb index c09f5bc4f4d..ada25005064 100644 --- a/spec/models/commit_spec.rb +++ b/spec/models/commit_spec.rb @@ -671,4 +671,25 @@ eos expect(commit2.merge_requests).to contain_exactly(merge_request1) end end + + describe 'signed commits' do + let(:gpg_signed_commit) { project.commit_by(oid: '0b4bc9a49b562e85de7cc9e834518ea6828729b9') } + let(:x509_signed_commit) { project.commit_by(oid: '189a6c924013fc3fe40d6f1ec1dc20214183bc97') } + let(:unsigned_commit) { project.commit_by(oid: '54fcc214b94e78d7a41a9a8fe6d87a5e59500e51') } + let!(:commit) { create(:commit, project: project) } + + it 'returns signature_type properly' do + expect(gpg_signed_commit.signature_type).to eq(:PGP) + expect(x509_signed_commit.signature_type).to eq(:X509) + expect(unsigned_commit.signature_type).to eq(:NONE) + expect(commit.signature_type).to eq(:NONE) + end + + it 'returns has_signature? properly' do + expect(gpg_signed_commit.has_signature?).to be_truthy + expect(x509_signed_commit.has_signature?).to be_truthy + expect(unsigned_commit.has_signature?).to be_falsey + expect(commit.has_signature?).to be_falsey + end + end end diff --git a/spec/models/concerns/x509_serial_number_attribute_spec.rb b/spec/models/concerns/x509_serial_number_attribute_spec.rb new file mode 100644 index 00000000000..18a1d85204c --- /dev/null +++ b/spec/models/concerns/x509_serial_number_attribute_spec.rb @@ -0,0 +1,91 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe X509SerialNumberAttribute do + let(:model) { Class.new { include X509SerialNumberAttribute } } + + before do + columns = [ + double(:column, name: 'name', type: :text), + double(:column, name: 'serial_number', type: :binary) + ] + + allow(model).to receive(:columns).and_return(columns) + end + + describe '#x509_serial_number_attribute' do + context 'when in non-production' do + before do + stub_rails_env('development') + end + + context 'when the table exists' do + before do + allow(model).to receive(:table_exists?).and_return(true) + end + + it 'defines a x509 serial number attribute for a binary column' do + expect(model).to receive(:attribute) + .with(:serial_number, an_instance_of(Gitlab::Database::X509SerialNumberAttribute)) + + model.x509_serial_number_attribute(:serial_number) + end + + it 'raises ArgumentError when the column type is not :binary' do + expect { model.x509_serial_number_attribute(:name) }.to raise_error(ArgumentError) + end + end + + context 'when the table does not exist' do + it 'allows the attribute to be added and issues a warning' do + allow(model).to receive(:table_exists?).and_return(false) + + expect(model).not_to receive(:columns) + expect(model).to receive(:attribute) + expect(model).to receive(:warn) + + model.x509_serial_number_attribute(:name) + end + end + + context 'when the column does not exist' do + it 'allows the attribute to be added and issues a warning' do + allow(model).to receive(:table_exists?).and_return(true) + + expect(model).to receive(:columns) + expect(model).to receive(:attribute) + expect(model).to receive(:warn) + + model.x509_serial_number_attribute(:no_name) + end + end + + context 'when other execeptions are raised' do + it 'logs and re-rasises the error' do + allow(model).to receive(:table_exists?).and_raise(ActiveRecord::NoDatabaseError.new('does not exist')) + + expect(model).not_to receive(:columns) + expect(model).not_to receive(:attribute) + expect(Gitlab::AppLogger).to receive(:error) + + expect { model.x509_serial_number_attribute(:name) }.to raise_error(ActiveRecord::NoDatabaseError) + end + end + end + + context 'when in production' do + before do + stub_rails_env('production') + end + + it 'defines a x509 serial number attribute' do + expect(model).not_to receive(:table_exists?) + expect(model).not_to receive(:columns) + expect(model).to receive(:attribute).with(:serial_number, an_instance_of(Gitlab::Database::X509SerialNumberAttribute)) + + model.x509_serial_number_attribute(:serial_number) + end + end + end +end diff --git a/spec/models/x509_certificate_spec.rb b/spec/models/x509_certificate_spec.rb new file mode 100644 index 00000000000..187d37334a1 --- /dev/null +++ b/spec/models/x509_certificate_spec.rb @@ -0,0 +1,107 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe X509Certificate do + describe 'validation' do + it { is_expected.to validate_presence_of(:subject_key_identifier) } + it { is_expected.to validate_presence_of(:subject) } + it { is_expected.to validate_presence_of(:email) } + it { is_expected.to validate_presence_of(:serial_number) } + it { is_expected.to validate_presence_of(:x509_issuer_id) } + end + + describe 'associations' do + it { is_expected.to belong_to(:x509_issuer).required } + end + + describe '.safe_create!' do + let(:subject_key_identifier) { 'CD:CD:CD:CD:CD:CD:CD:CD:CD:CD:CD:CD:CD:CD:CD:CD:CD:CD:CD:CD' } + let(:subject) { 'CN=gitlab@example.com,OU=Example,O=World' } + let(:email) { 'gitlab@example.com' } + let(:serial_number) { '123456789' } + let(:issuer) { create(:x509_issuer) } + + let(:attributes) do + { + subject_key_identifier: subject_key_identifier, + subject: subject, + email: email, + serial_number: serial_number, + x509_issuer_id: issuer.id + } + end + + it 'creates a new certificate if it was not found' do + expect { described_class.safe_create!(attributes) }.to change { described_class.count }.by(1) + end + + it 'assigns the correct attributes when creating' do + certificate = described_class.safe_create!(attributes) + + expect(certificate.subject_key_identifier).to eq(subject_key_identifier) + expect(certificate.subject).to eq(subject) + expect(certificate.email).to eq(email) + end + end + + describe 'validators' do + it 'accepts correct subject_key_identifier' do + subject_key_identifiers = [ + 'AB:AB:AB:AB:AB:AB:AB:AB:AB:AB:AB:AB:AB:AB:AB:AB:AB:AB:AB:AB', + 'CD:CD:CD:CD:CD:CD:CD:CD:CD:CD:CD:CD:CD:CD:CD:CD:CD:CD:CD:CD' + ] + + subject_key_identifiers.each do |identifier| + expect(build(:x509_certificate, subject_key_identifier: identifier)).to be_valid + end + end + + it 'rejects invalid subject_key_identifier' do + subject_key_identifiers = [ + 'AB:AB:AB:AB:AB:AB:AB:AB:AB:AB:AB:AB:AB:AB:AB:AB:AB:AB:AB', + 'CD:CD:CD:CD:CD:CD:CD:CD:CD:CD:CD:CD:CD:CD:CD:CD:CD:CD:CD:GG', + 'random string', + '12321342545356434523412341245452345623453542345234523453245' + ] + + subject_key_identifiers.each do |identifier| + expect(build(:x509_certificate, subject_key_identifier: identifier)).to be_invalid + end + end + + it 'accepts correct email address' do + emails = [ + 'smime@example.org', + 'smime@example.com' + ] + + emails.each do |email| + expect(build(:x509_certificate, email: email)).to be_valid + end + end + + it 'rejects invalid email' do + emails = [ + 'this is not an email', + '@example.org' + ] + + emails.each do |email| + expect(build(:x509_certificate, email: email)).to be_invalid + end + end + + it 'accepts valid serial_number' do + expect(build(:x509_certificate, serial_number: 123412341234)).to be_valid + + # rfc 5280 - 4.1.2.2 Serial number (20 octets is the maximum) + expect(build(:x509_certificate, serial_number: 1461501637330902918203684832716283019655932542975)).to be_valid + expect(build(:x509_certificate, serial_number: 'ffffffffffffffffffffffffffffffffffffffff'.to_i(16))).to be_valid + end + + it 'rejects invalid serial_number' do + expect(build(:x509_certificate, serial_number: "sgsgfsdgdsfg")).to be_invalid + end + end +end diff --git a/spec/models/x509_commit_signature_spec.rb b/spec/models/x509_commit_signature_spec.rb new file mode 100644 index 00000000000..a2f72228a86 --- /dev/null +++ b/spec/models/x509_commit_signature_spec.rb @@ -0,0 +1,53 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe X509CommitSignature do + let(:commit_sha) { '189a6c924013fc3fe40d6f1ec1dc20214183bc97' } + let(:project) { create(:project, :public, :repository) } + let!(:commit) { create(:commit, project: project, sha: commit_sha) } + let(:x509_certificate) { create(:x509_certificate) } + let(:x509_signature) { create(:x509_commit_signature, commit_sha: commit_sha) } + + it_behaves_like 'having unique enum values' + + describe 'validation' do + it { is_expected.to validate_presence_of(:commit_sha) } + it { is_expected.to validate_presence_of(:project_id) } + it { is_expected.to validate_presence_of(:x509_certificate_id) } + end + + describe 'associations' do + it { is_expected.to belong_to(:project).required } + it { is_expected.to belong_to(:x509_certificate).required } + end + + describe '.safe_create!' do + let(:attributes) do + { + commit_sha: commit_sha, + project: project, + x509_certificate_id: x509_certificate.id, + verification_status: "verified" + } + end + + it 'finds a signature by commit sha if it existed' do + x509_signature + + expect(described_class.safe_create!(commit_sha: commit_sha)).to eq(x509_signature) + end + + it 'creates a new signature if it was not found' do + expect { described_class.safe_create!(attributes) }.to change { described_class.count }.by(1) + end + + it 'assigns the correct attributes when creating' do + signature = described_class.safe_create!(attributes) + + expect(signature.project).to eq(project) + expect(signature.commit_sha).to eq(commit_sha) + expect(signature.x509_certificate_id).to eq(x509_certificate.id) + end + end +end diff --git a/spec/models/x509_issuer_spec.rb b/spec/models/x509_issuer_spec.rb new file mode 100644 index 00000000000..f1067cad655 --- /dev/null +++ b/spec/models/x509_issuer_spec.rb @@ -0,0 +1,71 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe X509Issuer do + describe 'validation' do + it { is_expected.to validate_presence_of(:subject_key_identifier) } + it { is_expected.to validate_presence_of(:subject) } + it { is_expected.to validate_presence_of(:crl_url) } + end + + describe '.safe_create!' do + let(:issuer_subject_key_identifier) { 'AB:AB:AB:AB:AB:AB:AB:AB:AB:AB:AB:AB:AB:AB:AB:AB:AB:AB:AB:AB' } + let(:issuer_subject) { 'CN=PKI,OU=Example,O=World' } + let(:issuer_crl_url) { 'http://example.com/pki.crl' } + + let(:attributes) do + { + subject_key_identifier: issuer_subject_key_identifier, + subject: issuer_subject, + crl_url: issuer_crl_url + } + end + + it 'creates a new issuer if it was not found' do + expect { described_class.safe_create!(attributes) }.to change { described_class.count }.by(1) + end + + it 'assigns the correct attributes when creating' do + issuer = described_class.safe_create!(attributes) + + expect(issuer.subject_key_identifier).to eq(issuer_subject_key_identifier) + expect(issuer.subject).to eq(issuer_subject) + expect(issuer.crl_url).to eq(issuer_crl_url) + end + end + + describe 'validators' do + it 'accepts correct subject_key_identifier' do + subject_key_identifiers = [ + 'AB:AB:AB:AB:AB:AB:AB:AB:AB:AB:AB:AB:AB:AB:AB:AB:AB:AB:AB:AB', + 'CD:CD:CD:CD:CD:CD:CD:CD:CD:CD:CD:CD:CD:CD:CD:CD:CD:CD:CD:CD' + ] + + subject_key_identifiers.each do |identifier| + expect(build(:x509_issuer, subject_key_identifier: identifier)).to be_valid + end + end + + it 'rejects invalid subject_key_identifier' do + subject_key_identifiers = [ + 'AB:AB:AB:AB:AB:AB:AB:AB:AB:AB:AB:AB:AB:AB:AB:AB:AB:AB:AB', + 'CD:CD:CD:CD:CD:CD:CD:CD:CD:CD:CD:CD:CD:CD:CD:CD:CD:CD:CD:GG', + 'random string', + '12321342545356434523412341245452345623453542345234523453245' + ] + + subject_key_identifiers.each do |identifier| + expect(build(:x509_issuer, subject_key_identifier: identifier)).to be_invalid + end + end + + it 'accepts valid crl_url' do + expect(build(:x509_issuer, crl_url: "https://pki.example.org")).to be_valid + end + + it 'rejects invalid crl_url' do + expect(build(:x509_issuer, crl_url: "ht://pki.example.org")).to be_invalid + end + end +end diff --git a/spec/requests/api/users_spec.rb b/spec/requests/api/users_spec.rb index f6ff2020c79..aa8dd021707 100644 --- a/spec/requests/api/users_spec.rb +++ b/spec/requests/api/users_spec.rb @@ -461,7 +461,7 @@ describe API::Users do end it "creates user with optional attributes" do - optional_attributes = { confirm: true } + optional_attributes = { confirm: true, theme_id: 2, color_scheme_id: 4 } attributes = attributes_for(:user).merge(optional_attributes) post api('/users', admin), params: attributes @@ -576,6 +576,15 @@ describe API::Users do expect(response).to have_gitlab_http_status(400) end + it "doesn't create user with invalid optional attributes" do + optional_attributes = { theme_id: 50, color_scheme_id: 50 } + attributes = attributes_for(:user).merge(optional_attributes) + + post api('/users', admin), params: attributes + + expect(response).to have_gitlab_http_status(400) + end + it 'returns 400 error if user does not validate' do post api('/users', admin), params: { @@ -824,6 +833,34 @@ describe API::Users do expect(user.reload.email).not_to eq('invalid email') end + it "updates theme id" do + put api("/users/#{user.id}", admin), params: { theme_id: 5 } + + expect(response).to have_gitlab_http_status(200) + expect(user.reload.theme_id).to eq(5) + end + + it "does not update invalid theme id" do + put api("/users/#{user.id}", admin), params: { theme_id: 50 } + + expect(response).to have_gitlab_http_status(400) + expect(user.reload.theme_id).not_to eq(50) + end + + it "updates color scheme id" do + put api("/users/#{user.id}", admin), params: { color_scheme_id: 5 } + + expect(response).to have_gitlab_http_status(200) + expect(user.reload.color_scheme_id).to eq(5) + end + + it "does not update invalid color scheme id" do + put api("/users/#{user.id}", admin), params: { color_scheme_id: 50 } + + expect(response).to have_gitlab_http_status(400) + expect(user.reload.color_scheme_id).not_to eq(50) + end + context 'when the current user is not an admin' do it "is not available" do expect do diff --git a/spec/services/git/branch_hooks_service_spec.rb b/spec/services/git/branch_hooks_service_spec.rb index 8dc8c804ea5..ae0506ad442 100644 --- a/spec/services/git/branch_hooks_service_spec.rb +++ b/spec/services/git/branch_hooks_service_spec.rb @@ -214,23 +214,23 @@ describe Git::BranchHooksService do end end - describe 'GPG signatures' do + describe 'signatures' do context 'when the commit has a signature' do context 'when the signature is already cached' do before do create(:gpg_signature, commit_sha: commit.id) end - it 'does not queue a CreateGpgSignatureWorker' do - expect(CreateGpgSignatureWorker).not_to receive(:perform_async) + it 'does not queue a CreateCommitSignatureWorker' do + expect(CreateCommitSignatureWorker).not_to receive(:perform_async) service.execute end end context 'when the signature is not yet cached' do - it 'queues a CreateGpgSignatureWorker' do - expect(CreateGpgSignatureWorker).to receive(:perform_async).with([commit.id], project.id) + it 'queues a CreateCommitSignatureWorker' do + expect(CreateCommitSignatureWorker).to receive(:perform_async).with([commit.id], project.id) service.execute end @@ -240,7 +240,7 @@ describe Git::BranchHooksService do .to receive(:shas_with_signatures) .and_return([sample_commit.id, another_sample_commit.id]) - expect(CreateGpgSignatureWorker) + expect(CreateCommitSignatureWorker) .to receive(:perform_async) .with([sample_commit.id, another_sample_commit.id], project.id) @@ -257,8 +257,8 @@ describe Git::BranchHooksService do .and_return([]) end - it 'does not queue a CreateGpgSignatureWorker' do - expect(CreateGpgSignatureWorker) + it 'does not queue a CreateCommitSignatureWorker' do + expect(CreateCommitSignatureWorker) .not_to receive(:perform_async) .with(sample_commit.id, project.id) diff --git a/spec/support/helpers/x509_helpers.rb b/spec/support/helpers/x509_helpers.rb new file mode 100644 index 00000000000..f72b518134c --- /dev/null +++ b/spec/support/helpers/x509_helpers.rb @@ -0,0 +1,208 @@ +# frozen_string_literal: true + +module X509Helpers + module User1 + extend self + + def commit + 'a4df3c87f040f5fa693d4d55a89b6af74e22cb56' + end + + def path + 'gitlab-test' + end + + def trust_cert + <<~TRUSTCERTIFICATE + -----BEGIN CERTIFICATE----- + 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 + -----END CERTIFICATE----- + TRUSTCERTIFICATE + end + + def signed_commit_signature + <<~SIGNATURE + -----BEGIN SIGNED MESSAGE----- + MIISUgYJKoZIhvcNAQcCoIISQzCCEj8CAQExDTALBglghkgBZQMEAgEwCwYJKoZI + hvcNAQcBoIIP3TCCB2kwggVRoAMCAQICBGvn1/4wDQYJKoZIhvcNAQELBQAwgZ8x + CzAJBgNVBAYTAkRFMQ8wDQYDVQQIDAZCYXllcm4xETAPBgNVBAcMCE11ZW5jaGVu + MRAwDgYDVQQKDAdTaWVtZW5zMREwDwYDVQQFEwhaWlpaWlpBMjEdMBsGA1UECwwU + U2llbWVucyBUcnVzdCBDZW50ZXIxKDAmBgNVBAMMH1NpZW1lbnMgSXNzdWluZyBD + QSBFRSBBdXRoIDIwMTYwHhcNMTcwMjAzMDY1MzUyWhcNMjAwMjAzMDY1MzUyWjBb + MREwDwYDVQQFEwhaMDAwTldESDEOMAwGA1UEKgwFUm9nZXIxDjAMBgNVBAQMBU1l + aWVyMRAwDgYDVQQKDAdTaWVtZW5zMRQwEgYDVQQDDAtNZWllciBSb2dlcjCCASIw + DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAIpqbpRAtn+vetgVb+APuoVOytZx + TmfWovp22nsmJQwE8ZgrJihRjIez0wjD3cvSREvWUXsvbiyxrSHmmwycRCV9YGi1 + Y9vaYRKOrWhT64Xv6wq6oq8VoA5J3z6V5P6Tkj7g9Q3OskRuSbhFQY89VUdsea+N + mcv/XrwtQR0SekfSZw9k0LhbauE69SWRV26O03raengjecbbkS+GTlP30/CqPzzQ + 4Ac2TmmVF7RlkGRB05mJqHS+nDK7Lmcr7jD0e92YW+v8Lft4Qu3MpFTYVa7zk712 + 5xWAgedyOaJb6TpJEz8KRX8v3i0PilQnuKAqZFkLjNcydOox0AtYRW1P2iMCAwEA + AaOCAu4wggLqMB0GA1UdDgQWBBTsALUoAlzTpaGrwqE0gYSqv5vP+DBDBgNVHREE + PDA6oCMGCisGAQQBgjcUAgOgFQwTci5tZWllckBzaWVtZW5zLmNvbYETci5tZWll + ckBzaWVtZW5zLmNvbTAOBgNVHQ8BAf8EBAMCB4AwKQYDVR0lBCIwIAYIKwYBBQUH + AwIGCCsGAQUFBwMEBgorBgEEAYI3FAICMIHKBgNVHR8EgcIwgb8wgbyggbmggbaG + Jmh0dHA6Ly9jaC5zaWVtZW5zLmNvbS9wa2k/WlpaWlpaQTIuY3JshkFsZGFwOi8v + Y2wuc2llbWVucy5uZXQvQ049WlpaWlpaQTIsTD1QS0k/Y2VydGlmaWNhdGVSZXZv + Y2F0aW9uTGlzdIZJbGRhcDovL2NsLnNpZW1lbnMuY29tL0NOPVpaWlpaWkEyLG89 + VHJ1c3RjZW50ZXI/Y2VydGlmaWNhdGVSZXZvY2F0aW9uTGlzdDBFBgNVHSAEPjA8 + MDoGDSsGAQQBoWkHAgIDAQEwKTAnBggrBgEFBQcCARYbaHR0cDovL3d3dy5zaWVt + ZW5zLmNvbS9wa2kvMAwGA1UdEwEB/wQCMAAwHwYDVR0jBBgwFoAUvb0qQyI9SEpX + fpgxF6lwne6fqJkwggEEBggrBgEFBQcBAQSB9zCB9DAyBggrBgEFBQcwAoYmaHR0 + cDovL2FoLnNpZW1lbnMuY29tL3BraT9aWlpaWlpBMi5jcnQwQQYIKwYBBQUHMAKG + NWxkYXA6Ly9hbC5zaWVtZW5zLm5ldC9DTj1aWlpaWlpBMixMPVBLST9jQUNlcnRp + ZmljYXRlMEkGCCsGAQUFBzAChj1sZGFwOi8vYWwuc2llbWVucy5jb20vQ049Wlpa + WlpaQTIsbz1UcnVzdGNlbnRlcj9jQUNlcnRpZmljYXRlMDAGCCsGAQUFBzABhiRo + dHRwOi8vb2NzcC5wa2ktc2VydmljZXMuc2llbWVucy5jb20wDQYJKoZIhvcNAQEL + BQADggIBAFY2sbX8DKjKlp0OdH+7Ak21ZdRr6p6JIXzQShWpuFr3wYTpM47+WYVe + arBekf8eS08feM+TWw6FHt/VNMpn5fLr20jHn7h+j3ClerAxQbx8J6BxhwJ/4DMy + 0cCdbe/fpfJyD/8TGdjnxwAgoq9iPuy1ueVnevygnLcuq1+se6EWJm9v1zrwB0LH + rE4/NaSCi06+KGg0D9yiigma9yErRZCiaFvqYXUEl7iGpu2OM9o38gZfGzkKaPtQ + e9BzRs6ndmvNpQQGLXvOlHn6DIsOuBHJp66A+wumRO2AC8rs1rc4NAIjCFRrz8k1 + kzb+ibFiTklWG69+At5/nb06BO/0ER4U18sSpmvOsFKNKPXzLkAn8O8ZzB+8afxy + egiIJFxYaqoJcQq3CCv8Xp7tp6I+ojr1ui0jK0yqJq6QfgS8FCXIJ+EErNYuoerx + ba6amD83e524sdMhCfD5dw6IeEY7LUl465ifUm+v5W3jStfa+0cQXnLZNGsC85nP + Lw5cXVIE3LfoSO3kWH45MfcX32fuqmyP2N3k+/+IOfUpSdT1iR1pEu0g/mow7lGj + CZngjmMpoto/Qi3l/n1KPWfmB09FZlUhHcGsHbK8+mrkqpv6HW3tKDSorah98aLM + Wvu1IXTrU9fOyBqt92i0e5buH+/9NHia0i6k79kwQy5wu6Q21GgUMIIIbDCCBlSg + AwIBAgIEL4jNizANBgkqhkiG9w0BAQsFADCBmTELMAkGA1UEBhMCREUxDzANBgNV + BAgMBkJheWVybjERMA8GA1UEBwwITXVlbmNoZW4xEDAOBgNVBAoMB1NpZW1lbnMx + ETAPBgNVBAUTCFpaWlpaWkExMR0wGwYDVQQLDBRTaWVtZW5zIFRydXN0IENlbnRl + cjEiMCAGA1UEAwwZU2llbWVucyBSb290IENBIFYzLjAgMjAxNjAeFw0xNjA3MjAx + MzA5MDhaFw0yMjA3MjAxMzA5MDhaMIGfMQswCQYDVQQGEwJERTEPMA0GA1UECAwG + QmF5ZXJuMREwDwYDVQQHDAhNdWVuY2hlbjEQMA4GA1UECgwHU2llbWVuczERMA8G + A1UEBRMIWlpaWlpaQTIxHTAbBgNVBAsMFFNpZW1lbnMgVHJ1c3QgQ2VudGVyMSgw + JgYDVQQDDB9TaWVtZW5zIElzc3VpbmcgQ0EgRUUgQXV0aCAyMDE2MIICIjANBgkq + hkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAy1aUq88DjZYPge0vZnAr3KJHmMi0o5mp + hy54Xr592Vtf8u/B3TCyD+iGCYANPYUq4sG18qXcVxGadz7zeEm6RI7jKKl3URAv + zFGiYForZE0JKxwo956T/diLLpH1vHEQDbp8AjNK7aGoltZnm/Jn6IVQy9iBY0SE + lRIBhUlppS4/J2PHtKEvQVYJfkAwTtHuGpvPaesoJ8bHA0KhEZ4+/kIYQebaNDf0 + ltTmXd4Z8zeUhE25d9MzoFnQUg+F01ewMfc0OsEFheKWP6dmo0MSLWARXxjI3K2R + THtJU5hxjb/+SA2wlfpqwNIAkTECDBfqYxHReAT8PeezvzEkNZ9RrXl9qj0Cm2iZ + AjY1SL+asuxrGvFwEW/ZKJ2ARY/ot1cHh/I79srzh/jFieShVHbT6s6fyKXmkUjB + OEnybUKUqcvNuOXnwEiJ/9jKT5UVBWTDxbEQucAarVNFBEf557o9ievbT+VAZKZ8 + F4tJge6jl2y19eppflresr7Xui9wekK2LYcLOF3X/MOCFq/9VyQDyE7X9KNGtEx7 + 4V6J2QpbbRJryvavh3b0eQEtqDc65eiEaP8awqOErN8EEYh7Gdx4Um3QFcm1TBhk + ZTdQdLlWv4LvIBnXiBEWRczQYEIm5wv5ZkyPwdL39Xwc72esPPBu8FtQFVcQlRdG + I2t5Ywefq48CAwEAAaOCArIwggKuMIIBBQYIKwYBBQUHAQEEgfgwgfUwQQYIKwYB + BQUHMAKGNWxkYXA6Ly9hbC5zaWVtZW5zLm5ldC9DTj1aWlpaWlpBMSxMPVBLST9j + QUNlcnRpZmljYXRlMDIGCCsGAQUFBzAChiZodHRwOi8vYWguc2llbWVucy5jb20v + cGtpP1paWlpaWkExLmNydDBKBggrBgEFBQcwAoY+bGRhcDovL2FsLnNpZW1lbnMu + Y29tL3VpZD1aWlpaWlpBMSxvPVRydXN0Y2VudGVyP2NBQ2VydGlmaWNhdGUwMAYI + KwYBBQUHMAGGJGh0dHA6Ly9vY3NwLnBraS1zZXJ2aWNlcy5zaWVtZW5zLmNvbTAf + BgNVHSMEGDAWgBRwbaBQ7KnQLGedGRX+/QRzNcPi1DASBgNVHRMBAf8ECDAGAQH/ + AgEAMEAGA1UdIAQ5MDcwNQYIKwYBBAGhaQcwKTAnBggrBgEFBQcCARYbaHR0cDov + L3d3dy5zaWVtZW5zLmNvbS9wa2kvMIHHBgNVHR8Egb8wgbwwgbmggbaggbOGP2xk + YXA6Ly9jbC5zaWVtZW5zLm5ldC9DTj1aWlpaWlpBMSxMPVBLST9hdXRob3JpdHlS + ZXZvY2F0aW9uTGlzdIYmaHR0cDovL2NoLnNpZW1lbnMuY29tL3BraT9aWlpaWlpB + MS5jcmyGSGxkYXA6Ly9jbC5zaWVtZW5zLmNvbS91aWQ9WlpaWlpaQTEsbz1UcnVz + dGNlbnRlcj9hdXRob3JpdHlSZXZvY2F0aW9uTGlzdDAzBgNVHSUELDAqBggrBgEF + BQcDAgYIKwYBBQUHAwQGCisGAQQBgjcUAgIGCCsGAQUFBwMJMA4GA1UdDwEB/wQE + AwIBBjAdBgNVHQ4EFgQUvb0qQyI9SEpXfpgxF6lwne6fqJkwDQYJKoZIhvcNAQEL + BQADggIBAEQB0qDUmU8rX9KVJA/0zxJUmIeE9zeldih8TKrf4UNzS1+2Cqn4agO7 + MxRG1d52/pL4uKenffwwYy2dP912PwLjCDOL7jvojjQKx/qpVUXF7XWsg8hAQec3 + 7Ras/jGPcPQ3OehbkcKcmXI4MrF0Haqo3q1n29gjlJ0fGn2fF1/CBnybPuODAjWG + o9mZodXfz0woGSxkftC6nTmAV2GCvIU+j5hNKpzEzo8c1KwLVeXtB4PAqioRW1BX + Ngjc7HQbvX/C39RnpOM3RdITw2KKXFxeKBMXdiDuFz/2CzO8HxKH9EVWEcSRbTnd + E5iEB4CZzcvfzl9X5AwrKkiIziOiEoiv21ooWeFWfR9V2dgYIE7G1TFwsQ4p0/w5 + xBHSzqP8TCJp1MQTw42/t8uUXoFEGqk5FKQWoIaFf7N//FLAn8r+7vxNhF5s+tMl + VsdKnXn3q8THB3JSnbb/AWGL9rjPK3vh2d3c0I5cWuKXexPLp74ynl2XUbiOXKE7 + XPUZ9qgK0G9JrrFMm4x1aID9Y9jqYeEz6krYjdFHo5BOVGso6SqWVJE48TxJ5KVv + FUb4OxhOAw118Tco0XA7H1G3c2/AKJvIku3cRuj8eLe/cpKqUqQl8uikIZs7POaO + +9eJsOnNPmUiwumJgwAo3Ka4ALteKZLbGmKvuo/2ueKCQ29F5rnOMYICOzCCAjcC + AQEwgagwgZ8xCzAJBgNVBAYTAkRFMQ8wDQYDVQQIDAZCYXllcm4xETAPBgNVBAcM + CE11ZW5jaGVuMRAwDgYDVQQKDAdTaWVtZW5zMREwDwYDVQQFEwhaWlpaWlpBMjEd + MBsGA1UECwwUU2llbWVucyBUcnVzdCBDZW50ZXIxKDAmBgNVBAMMH1NpZW1lbnMg + SXNzdWluZyBDQSBFRSBBdXRoIDIwMTYCBGvn1/4wCwYJYIZIAWUDBAIBoGkwHAYJ + KoZIhvcNAQkFMQ8XDTE5MDYyMDEwNDIwNlowLwYJKoZIhvcNAQkEMSIEIHPHp00z + IZ93dAl/uwOnixzuAtf1fUTyxFFaq/5yzc+0MBgGCSqGSIb3DQEJAzELBgkqhkiG + 9w0BBwEwCwYJKoZIhvcNAQEBBIIBAD8Or5F/A/vpeNPv1YOrGzTrMU5pbn6o8t2+ + Hqn+hAdjbD26HqjYQN/nyXNBpgXiV4P5vEVNVpmViAAXGsWKM3BJx7GdH/uUwDnj + upvoViXYtzQ92UC2Xzqo7uOg2ryMbDIFNfLosvy4a7NfDLYoMsVYrgOKpDrfOLsS + 1VNUjlyftm7vKigkJLrPIEmXrZSVEqsdKvFhcSxS55lm0lVd/fTCAi7TXR2FZWbc + TrsTrZx2YdIJDwN04szzBjnQ7yJ4jBLYz1GMBe22xDD10UA4XdBYK07rkcabrv/t + kUMI7uN/KeiKPeSvWCn3AUqH6TIFa9WU+tI4U2A2BsUMn6Bq9TY= + -----END SIGNED MESSAGE----- + SIGNATURE + end + + def signed_commit_base_data + <<~SIGNEDDATA + tree 84c167013d2ee86e8a88ac6011df0b178d261a23 + parent e63f41fe459e62e1228fcef60d7189127aeba95a + author Roger Meier <r.meier@siemens.com> 1561027326 +0200 + committer Roger Meier <r.meier@siemens.com> 1561027326 +0200 + + feat: add a smime signed commit + SIGNEDDATA + end + + def certificate_crl + 'http://ch.siemens.com/pki?ZZZZZZA2.crl' + end + + def certificate_serial + 1810356222 + end + + def certificate_subject_key_identifier + 'EC:00:B5:28:02:5C:D3:A5:A1:AB:C2:A1:34:81:84:AA:BF:9B:CF:F8' + end + + def issuer_subject_key_identifier + 'BD:BD:2A:43:22:3D:48:4A:57:7E:98:31:17:A9:70:9D:EE:9F:A8:99' + end + + def certificate_email + 'r.meier@siemens.com' + end + + def certificate_issuer + 'CN=Siemens Issuing CA EE Auth 2016,OU=Siemens Trust Center,serialNumber=ZZZZZZA2,O=Siemens,L=Muenchen,ST=Bayern,C=DE' + end + + def certificate_subject + 'CN=Meier Roger,O=Siemens,SN=Meier,GN=Roger,serialNumber=Z000NWDH' + end + + def names + ['Roger Meier'] + end + + def emails + ['r.meier@siemens.com'] + end + end +end diff --git a/spec/workers/create_gpg_signature_worker_spec.rb b/spec/workers/create_commit_signature_worker_spec.rb index 2504a6474db..d7235fcd907 100644 --- a/spec/workers/create_gpg_signature_worker_spec.rb +++ b/spec/workers/create_commit_signature_worker_spec.rb @@ -2,13 +2,14 @@ require 'spec_helper' -describe CreateGpgSignatureWorker do +describe CreateCommitSignatureWorker do let(:project) { create(:project, :repository) } let(:commits) { project.repository.commits('HEAD', limit: 3).commits } let(:commit_shas) { commits.map(&:id) } let(:gpg_commit) { instance_double(Gitlab::Gpg::Commit) } + let(:x509_commit) { instance_double(Gitlab::X509::Commit) } - context 'when GpgKey is found' do + context 'when a signature is found' do before do allow(Project).to receive(:find_by).with(id: project.id).and_return(project) allow(project).to receive(:commits_by).with(oids: commit_shas).and_return(commits) @@ -18,6 +19,7 @@ describe CreateGpgSignatureWorker do it 'calls Gitlab::Gpg::Commit#signature' do commits.each do |commit| + allow(commit).to receive(:signature_type).and_return(:PGP) expect(Gitlab::Gpg::Commit).to receive(:new).with(commit).and_return(gpg_commit).once end @@ -31,13 +33,46 @@ describe CreateGpgSignatureWorker do allow(Gitlab::Gpg::Commit).to receive(:new).and_return(gpg_commit) allow(Gitlab::Gpg::Commit).to receive(:new).with(commits.first).and_raise(StandardError) + allow(commits[1]).to receive(:signature_type).and_return(:PGP) + allow(commits[2]).to receive(:signature_type).and_return(:PGP) + expect(gpg_commit).to receive(:signature).twice subject end + + it 'calls Gitlab::X509::Commit#signature' do + commits.each do |commit| + allow(commit).to receive(:signature_type).and_return(:X509) + expect(Gitlab::X509::Commit).to receive(:new).with(commit).and_return(x509_commit).once + end + + expect(x509_commit).to receive(:signature).exactly(commits.size).times + + subject + end + + it 'can recover from exception and continue the X509 signature process' do + allow(x509_commit).to receive(:signature) + allow(Gitlab::X509::Commit).to receive(:new).and_return(x509_commit) + allow(Gitlab::X509::Commit).to receive(:new).with(commits.first).and_raise(StandardError) + + allow(commits[1]).to receive(:signature_type).and_return(:X509) + allow(commits[2]).to receive(:signature_type).and_return(:X509) + + expect(x509_commit).to receive(:signature).twice + + subject + end end context 'handles when a string is passed in for the commit SHA' do + before do + allow(Project).to receive(:find_by).with(id: project.id).and_return(project) + allow(project).to receive(:commits_by).with(oids: Array(commit_shas.first)).and_return(commits) + allow(commits.first).to receive(:signature_type).and_return(:PGP) + end + it 'creates a signature once' do allow(Gitlab::Gpg::Commit).to receive(:new).with(commits.first).and_return(gpg_commit) @@ -67,5 +102,11 @@ describe CreateGpgSignatureWorker do described_class.new.perform(commit_shas, nonexisting_project_id) end + + it 'does not call Gitlab::X509::Commit#signature' do + expect_any_instance_of(Gitlab::X509::Commit).not_to receive(:signature) + + described_class.new.perform(commit_shas, nonexisting_project_id) + end end end |