Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
path: root/spec
diff options
context:
space:
mode:
Diffstat (limited to 'spec')
-rw-r--r--spec/controllers/projects/compare_controller_spec.rb2
-rw-r--r--spec/factories/x509_certificate.rb12
-rw-r--r--spec/factories/x509_commit_signature.rb10
-rw-r--r--spec/factories/x509_issuer.rb10
-rw-r--r--spec/frontend/blob/components/__snapshots__/blob_header_filepath_spec.js.snap35
-rw-r--r--spec/frontend/blob/components/blob_header_filepath_spec.js90
-rw-r--r--spec/frontend/blob/components/mock_data.js29
-rw-r--r--spec/lib/gitlab/x509/commit_spec.rb208
-rw-r--r--spec/migrations/migrate_create_commit_signature_worker_sidekiq_queue_spec.rb44
-rw-r--r--spec/models/commit_spec.rb21
-rw-r--r--spec/models/concerns/x509_serial_number_attribute_spec.rb91
-rw-r--r--spec/models/x509_certificate_spec.rb107
-rw-r--r--spec/models/x509_commit_signature_spec.rb53
-rw-r--r--spec/models/x509_issuer_spec.rb71
-rw-r--r--spec/requests/api/users_spec.rb39
-rw-r--r--spec/services/git/branch_hooks_service_spec.rb16
-rw-r--r--spec/support/helpers/x509_helpers.rb208
-rw-r--r--spec/workers/create_commit_signature_worker_spec.rb (renamed from spec/workers/create_gpg_signature_worker_spec.rb)45
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