2019-03-30 03:23:56 -04:00
|
|
|
# frozen_string_literal: true
|
|
|
|
|
2016-02-12 10:05:17 -05:00
|
|
|
require 'spec_helper'
|
|
|
|
|
2017-07-10 10:24:02 -04:00
|
|
|
describe PagesDomain do
|
2018-02-06 08:25:46 -05:00
|
|
|
using RSpec::Parameterized::TableSyntax
|
|
|
|
|
|
|
|
subject(:pages_domain) { described_class.new }
|
|
|
|
|
2016-02-12 10:05:17 -05:00
|
|
|
describe 'associations' do
|
|
|
|
it { is_expected.to belong_to(:project) }
|
|
|
|
end
|
2016-12-20 06:24:44 -05:00
|
|
|
|
2017-03-24 19:04:03 -04:00
|
|
|
describe 'validate domain' do
|
2017-05-19 10:07:38 -04:00
|
|
|
subject(:pages_domain) { build(:pages_domain, domain: domain) }
|
2016-02-12 10:05:17 -05:00
|
|
|
|
|
|
|
context 'is unique' do
|
|
|
|
let(:domain) { 'my.domain.com' }
|
|
|
|
|
2017-07-24 08:33:14 -04:00
|
|
|
it { is_expected.to validate_uniqueness_of(:domain).case_insensitive }
|
2016-02-12 10:05:17 -05:00
|
|
|
end
|
|
|
|
|
2018-01-03 03:07:03 -05:00
|
|
|
describe "hostname" do
|
|
|
|
{
|
|
|
|
'my.domain.com' => true,
|
|
|
|
'123.456.789' => true,
|
|
|
|
'0x12345.com' => true,
|
|
|
|
'0123123' => true,
|
|
|
|
'_foo.com' => false,
|
|
|
|
'reserved.com' => false,
|
|
|
|
'a.reserved.com' => false,
|
|
|
|
nil => false
|
|
|
|
}.each do |value, validity|
|
|
|
|
context "domain #{value.inspect} validity" do
|
|
|
|
before do
|
|
|
|
allow(Settings.pages).to receive(:host).and_return('reserved.com')
|
|
|
|
end
|
|
|
|
|
|
|
|
let(:domain) { value }
|
|
|
|
|
|
|
|
it { expect(pages_domain.valid?).to eq(validity) }
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
describe "HTTPS-only" do
|
|
|
|
using RSpec::Parameterized::TableSyntax
|
|
|
|
|
|
|
|
let(:domain) { 'my.domain.com' }
|
|
|
|
|
|
|
|
let(:project) do
|
|
|
|
instance_double(Project, pages_https_only?: pages_https_only)
|
|
|
|
end
|
|
|
|
|
|
|
|
let(:pages_domain) do
|
|
|
|
build(:pages_domain, certificate: certificate, key: key).tap do |pd|
|
|
|
|
allow(pd).to receive(:project).and_return(project)
|
|
|
|
pd.valid?
|
2017-05-19 10:07:38 -04:00
|
|
|
end
|
2018-01-03 03:07:03 -05:00
|
|
|
end
|
2017-05-19 10:07:38 -04:00
|
|
|
|
2018-01-03 03:07:03 -05:00
|
|
|
where(:pages_https_only, :certificate, :key, :errors_on) do
|
|
|
|
attributes = attributes_for(:pages_domain)
|
|
|
|
cert, key = attributes.fetch_values(:certificate, :key)
|
|
|
|
|
|
|
|
true | nil | nil | %i(certificate key)
|
|
|
|
true | cert | nil | %i(key)
|
|
|
|
true | nil | key | %i(certificate key)
|
|
|
|
true | cert | key | []
|
|
|
|
false | nil | nil | []
|
|
|
|
false | cert | nil | %i(key)
|
|
|
|
false | nil | key | %i(key)
|
|
|
|
false | cert | key | []
|
|
|
|
end
|
2017-05-19 10:07:38 -04:00
|
|
|
|
2018-01-03 03:07:03 -05:00
|
|
|
with_them do
|
|
|
|
it "is adds the expected errors" do
|
|
|
|
expect(pages_domain.errors.keys).to eq errors_on
|
|
|
|
end
|
2017-05-19 10:07:38 -04:00
|
|
|
end
|
2016-02-12 10:05:17 -05:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
describe 'validate certificate' do
|
|
|
|
subject { domain }
|
|
|
|
|
2018-01-03 03:07:03 -05:00
|
|
|
context 'with matching key' do
|
|
|
|
let(:domain) { build(:pages_domain) }
|
2016-02-12 10:05:17 -05:00
|
|
|
|
2018-01-03 03:07:03 -05:00
|
|
|
it { is_expected.to be_valid }
|
2016-02-12 10:05:17 -05:00
|
|
|
end
|
|
|
|
|
2018-01-03 03:07:03 -05:00
|
|
|
context 'when no certificate is specified' do
|
|
|
|
let(:domain) { build(:pages_domain, :without_certificate) }
|
2016-02-12 10:05:17 -05:00
|
|
|
|
2016-05-30 04:11:46 -04:00
|
|
|
it { is_expected.not_to be_valid }
|
2016-02-12 10:05:17 -05:00
|
|
|
end
|
|
|
|
|
2018-01-03 03:07:03 -05:00
|
|
|
context 'when no key is specified' do
|
|
|
|
let(:domain) { build(:pages_domain, :without_key) }
|
2016-02-12 10:05:17 -05:00
|
|
|
|
2018-01-03 03:07:03 -05:00
|
|
|
it { is_expected.not_to be_valid }
|
2016-02-12 10:05:17 -05:00
|
|
|
end
|
|
|
|
|
|
|
|
context 'for not matching key' do
|
2018-01-03 03:07:03 -05:00
|
|
|
let(:domain) { build(:pages_domain, :with_missing_chain) }
|
2016-02-12 10:05:17 -05:00
|
|
|
|
2016-05-30 04:11:46 -04:00
|
|
|
it { is_expected.not_to be_valid }
|
2016-02-12 10:05:17 -05:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2018-02-06 08:25:46 -05:00
|
|
|
describe 'validations' do
|
|
|
|
it { is_expected.to validate_presence_of(:verification_code) }
|
|
|
|
end
|
|
|
|
|
|
|
|
describe '#verification_code' do
|
|
|
|
subject { pages_domain.verification_code }
|
|
|
|
|
|
|
|
it 'is set automatically with 128 bits of SecureRandom data' do
|
|
|
|
expect(SecureRandom).to receive(:hex).with(16) { 'verification code' }
|
|
|
|
|
|
|
|
is_expected.to eq('verification code')
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
describe '#keyed_verification_code' do
|
|
|
|
subject { pages_domain.keyed_verification_code }
|
|
|
|
|
|
|
|
it { is_expected.to eq("gitlab-pages-verification-code=#{pages_domain.verification_code}") }
|
|
|
|
end
|
|
|
|
|
|
|
|
describe '#verification_domain' do
|
|
|
|
subject { pages_domain.verification_domain }
|
|
|
|
|
|
|
|
it { is_expected.to be_nil }
|
|
|
|
|
|
|
|
it 'is a well-known subdomain if the domain is present' do
|
|
|
|
pages_domain.domain = 'example.com'
|
|
|
|
|
|
|
|
is_expected.to eq('_gitlab-pages-verification-code.example.com')
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2017-03-24 19:04:03 -04:00
|
|
|
describe '#url' do
|
2016-02-12 10:05:17 -05:00
|
|
|
subject { domain.url }
|
|
|
|
|
2018-01-03 03:07:03 -05:00
|
|
|
let(:domain) { build(:pages_domain) }
|
2016-02-12 10:05:17 -05:00
|
|
|
|
2018-01-03 03:07:03 -05:00
|
|
|
it { is_expected.to eq("https://#{domain.domain}") }
|
2016-02-12 10:05:17 -05:00
|
|
|
|
2018-01-03 03:07:03 -05:00
|
|
|
context 'without the certificate' do
|
|
|
|
let(:domain) { build(:pages_domain, :without_certificate) }
|
2016-02-12 10:05:17 -05:00
|
|
|
|
2018-01-03 03:07:03 -05:00
|
|
|
it { is_expected.to eq("http://#{domain.domain}") }
|
2016-02-12 10:05:17 -05:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2017-03-24 19:04:03 -04:00
|
|
|
describe '#has_matching_key?' do
|
2016-02-12 10:05:17 -05:00
|
|
|
subject { domain.has_matching_key? }
|
|
|
|
|
2018-01-03 03:07:03 -05:00
|
|
|
let(:domain) { build(:pages_domain) }
|
2016-02-12 10:05:17 -05:00
|
|
|
|
2018-01-03 03:07:03 -05:00
|
|
|
it { is_expected.to be_truthy }
|
2016-02-12 10:05:17 -05:00
|
|
|
|
|
|
|
context 'for invalid key' do
|
2018-01-03 03:07:03 -05:00
|
|
|
let(:domain) { build(:pages_domain, :with_missing_chain) }
|
2016-02-12 10:05:17 -05:00
|
|
|
|
|
|
|
it { is_expected.to be_falsey }
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2017-03-24 19:04:03 -04:00
|
|
|
describe '#has_intermediates?' do
|
2016-02-12 10:05:17 -05:00
|
|
|
subject { domain.has_intermediates? }
|
|
|
|
|
|
|
|
context 'for self signed' do
|
2018-01-03 03:07:03 -05:00
|
|
|
let(:domain) { build(:pages_domain) }
|
2016-02-12 10:05:17 -05:00
|
|
|
|
|
|
|
it { is_expected.to be_truthy }
|
|
|
|
end
|
|
|
|
|
2016-02-16 08:39:58 -05:00
|
|
|
context 'for missing certificate chain' do
|
|
|
|
let(:domain) { build(:pages_domain, :with_missing_chain) }
|
2016-02-12 10:05:17 -05:00
|
|
|
|
|
|
|
it { is_expected.to be_falsey }
|
|
|
|
end
|
2016-02-16 08:39:58 -05:00
|
|
|
|
|
|
|
context 'for trusted certificate chain' do
|
2016-02-17 04:05:26 -05:00
|
|
|
# We only validate that we can to rebuild the trust chain, for certificates
|
|
|
|
# We assume that 'AddTrustExternalCARoot' needed to validate the chain is in trusted store.
|
|
|
|
# It will be if ca-certificates is installed on Debian/Ubuntu/Alpine
|
|
|
|
|
2016-02-16 08:39:58 -05:00
|
|
|
let(:domain) { build(:pages_domain, :with_trusted_chain) }
|
|
|
|
|
|
|
|
it { is_expected.to be_truthy }
|
|
|
|
end
|
2016-02-12 10:05:17 -05:00
|
|
|
end
|
|
|
|
|
2017-03-24 19:04:03 -04:00
|
|
|
describe '#expired?' do
|
2016-02-12 10:05:17 -05:00
|
|
|
subject { domain.expired? }
|
|
|
|
|
|
|
|
context 'for valid' do
|
2018-01-03 03:07:03 -05:00
|
|
|
let(:domain) { build(:pages_domain) }
|
2016-02-12 10:05:17 -05:00
|
|
|
|
|
|
|
it { is_expected.to be_falsey }
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'for expired' do
|
|
|
|
let(:domain) { build(:pages_domain, :with_expired_certificate) }
|
|
|
|
|
|
|
|
it { is_expected.to be_truthy }
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2017-03-24 19:04:03 -04:00
|
|
|
describe '#subject' do
|
2018-01-03 03:07:03 -05:00
|
|
|
let(:domain) { build(:pages_domain) }
|
2016-02-12 10:05:17 -05:00
|
|
|
|
|
|
|
subject { domain.subject }
|
|
|
|
|
|
|
|
it { is_expected.to eq('/CN=test-certificate') }
|
|
|
|
end
|
|
|
|
|
2017-03-24 19:04:03 -04:00
|
|
|
describe '#certificate_text' do
|
2018-01-03 03:07:03 -05:00
|
|
|
let(:domain) { build(:pages_domain) }
|
2016-02-12 10:05:17 -05:00
|
|
|
|
|
|
|
subject { domain.certificate_text }
|
|
|
|
|
|
|
|
# We test only existence of output, since the output is long
|
2016-05-30 04:11:46 -04:00
|
|
|
it { is_expected.not_to be_empty }
|
2016-02-12 10:05:17 -05:00
|
|
|
end
|
2018-02-06 08:25:46 -05:00
|
|
|
|
2018-01-03 03:07:03 -05:00
|
|
|
describe "#https?" do
|
|
|
|
context "when a certificate is present" do
|
|
|
|
subject { build(:pages_domain) }
|
|
|
|
it { is_expected.to be_https }
|
|
|
|
end
|
|
|
|
|
|
|
|
context "when no certificate is present" do
|
|
|
|
subject { build(:pages_domain, :without_certificate) }
|
|
|
|
it { is_expected.not_to be_https }
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2018-02-06 08:25:46 -05:00
|
|
|
describe '#update_daemon' do
|
|
|
|
it 'runs when the domain is created' do
|
|
|
|
domain = build(:pages_domain)
|
|
|
|
|
|
|
|
expect(domain).to receive(:update_daemon)
|
|
|
|
|
|
|
|
domain.save!
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'runs when the domain is destroyed' do
|
|
|
|
domain = create(:pages_domain)
|
|
|
|
|
|
|
|
expect(domain).to receive(:update_daemon)
|
|
|
|
|
|
|
|
domain.destroy!
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'delegates to Projects::UpdatePagesConfigurationService' do
|
|
|
|
service = instance_double('Projects::UpdatePagesConfigurationService')
|
|
|
|
expect(Projects::UpdatePagesConfigurationService).to receive(:new) { service }
|
|
|
|
expect(service).to receive(:execute)
|
|
|
|
|
|
|
|
create(:pages_domain)
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'configuration updates when attributes change' do
|
|
|
|
set(:project1) { create(:project) }
|
|
|
|
set(:project2) { create(:project) }
|
|
|
|
set(:domain) { create(:pages_domain) }
|
|
|
|
|
|
|
|
where(:attribute, :old_value, :new_value, :update_expected) do
|
|
|
|
now = Time.now
|
|
|
|
future = now + 1.day
|
|
|
|
|
|
|
|
:project | nil | :project1 | true
|
|
|
|
:project | :project1 | :project1 | false
|
|
|
|
:project | :project1 | :project2 | true
|
|
|
|
:project | :project1 | nil | true
|
|
|
|
|
|
|
|
# domain can't be set to nil
|
|
|
|
:domain | 'a.com' | 'a.com' | false
|
|
|
|
:domain | 'a.com' | 'b.com' | true
|
|
|
|
|
|
|
|
# verification_code can't be set to nil
|
|
|
|
:verification_code | 'foo' | 'foo' | false
|
|
|
|
:verification_code | 'foo' | 'bar' | false
|
|
|
|
|
|
|
|
:verified_at | nil | now | false
|
|
|
|
:verified_at | now | now | false
|
|
|
|
:verified_at | now | future | false
|
|
|
|
:verified_at | now | nil | false
|
|
|
|
|
|
|
|
:enabled_until | nil | now | true
|
|
|
|
:enabled_until | now | now | false
|
|
|
|
:enabled_until | now | future | false
|
|
|
|
:enabled_until | now | nil | true
|
|
|
|
end
|
|
|
|
|
|
|
|
with_them do
|
|
|
|
it 'runs if a relevant attribute has changed' do
|
|
|
|
a = old_value.is_a?(Symbol) ? send(old_value) : old_value
|
|
|
|
b = new_value.is_a?(Symbol) ? send(new_value) : new_value
|
|
|
|
|
|
|
|
domain.update!(attribute => a)
|
|
|
|
|
|
|
|
if update_expected
|
|
|
|
expect(domain).to receive(:update_daemon)
|
|
|
|
else
|
|
|
|
expect(domain).not_to receive(:update_daemon)
|
|
|
|
end
|
|
|
|
|
|
|
|
domain.update!(attribute => b)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'TLS configuration' do
|
2018-01-03 03:07:03 -05:00
|
|
|
set(:domain_without_tls) { create(:pages_domain, :without_certificate, :without_key) }
|
|
|
|
set(:domain) { create(:pages_domain) }
|
2018-02-06 08:25:46 -05:00
|
|
|
|
2018-01-03 03:07:03 -05:00
|
|
|
let(:cert1) { domain.certificate }
|
2018-02-06 08:25:46 -05:00
|
|
|
let(:cert2) { cert1 + ' ' }
|
2018-01-03 03:07:03 -05:00
|
|
|
let(:key1) { domain.key }
|
2018-02-06 08:25:46 -05:00
|
|
|
let(:key2) { key1 + ' ' }
|
|
|
|
|
|
|
|
it 'updates when added' do
|
2018-01-03 03:07:03 -05:00
|
|
|
expect(domain_without_tls).to receive(:update_daemon)
|
2018-02-06 08:25:46 -05:00
|
|
|
|
2018-01-03 03:07:03 -05:00
|
|
|
domain_without_tls.update!(key: key1, certificate: cert1)
|
2018-02-06 08:25:46 -05:00
|
|
|
end
|
|
|
|
|
|
|
|
it 'updates when changed' do
|
2018-01-03 03:07:03 -05:00
|
|
|
expect(domain).to receive(:update_daemon)
|
2018-02-06 08:25:46 -05:00
|
|
|
|
2018-01-03 03:07:03 -05:00
|
|
|
domain.update!(key: key2, certificate: cert2)
|
2018-02-06 08:25:46 -05:00
|
|
|
end
|
|
|
|
|
|
|
|
it 'updates when removed' do
|
2018-01-03 03:07:03 -05:00
|
|
|
expect(domain).to receive(:update_daemon)
|
2018-02-06 08:25:46 -05:00
|
|
|
|
2018-01-03 03:07:03 -05:00
|
|
|
domain.update!(key: nil, certificate: nil)
|
2018-02-06 08:25:46 -05:00
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
2019-04-30 08:05:54 -04:00
|
|
|
|
|
|
|
describe '.for_removal' do
|
|
|
|
subject { described_class.for_removal }
|
|
|
|
|
|
|
|
context 'when domain is not schedule for removal' do
|
|
|
|
let!(:domain) { create :pages_domain }
|
|
|
|
|
|
|
|
it 'does not return domain' do
|
|
|
|
is_expected.to be_empty
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'when domain is scheduled for removal yesterday' do
|
|
|
|
let!(:domain) { create :pages_domain, remove_at: 1.day.ago }
|
|
|
|
|
|
|
|
it 'returns domain' do
|
|
|
|
is_expected.to eq([domain])
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'when domain is scheduled for removal tomorrow' do
|
|
|
|
let!(:domain) { create :pages_domain, remove_at: 1.day.from_now }
|
|
|
|
|
|
|
|
it 'does not return domain' do
|
|
|
|
is_expected.to be_empty
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
2016-02-12 10:05:17 -05:00
|
|
|
end
|