diff options
Diffstat (limited to 'spec/models')
-rw-r--r-- | spec/models/commit_spec.rb | 21 | ||||
-rw-r--r-- | spec/models/concerns/x509_serial_number_attribute_spec.rb | 91 | ||||
-rw-r--r-- | spec/models/x509_certificate_spec.rb | 107 | ||||
-rw-r--r-- | spec/models/x509_commit_signature_spec.rb | 53 | ||||
-rw-r--r-- | spec/models/x509_issuer_spec.rb | 71 |
5 files changed, 343 insertions, 0 deletions
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 |