diff --git a/app/helpers/projects/pages_helper.rb b/app/helpers/projects/pages_helper.rb index eef43eb87d1b6087dc6ac8fdf42ad0e697cf3c88..0171f8a5ab91a52e944271abcf7b6020a627cadf 100644 --- a/app/helpers/projects/pages_helper.rb +++ b/app/helpers/projects/pages_helper.rb @@ -4,10 +4,19 @@ module Projects module PagesHelper def can_create_pages_custom_domains?(current_user, project) current_user.can?(:update_pages, project) && - (Gitlab.config.pages.external_http || Gitlab.config.pages.external_https) && + (Gitlab.config.pages.external_http || + Gitlab.config.pages.external_https || + pages_custom_domain_enabled?) && project.can_create_custom_domains? end + def pages_custom_domain_enabled? + custom_domain_mode = Gitlab.config.pages.custom_domain_mode + allowed_values = %w[http https] + + allowed_values.include?(custom_domain_mode.to_s.downcase) + end + def pages_subdomain(project) Gitlab::Pages::UrlBuilder .new(project) diff --git a/app/models/project.rb b/app/models/project.rb index 86e4a6506f92fa2332789576c8f34682a76ba21b..b0481f3954e7d2521df99a02cb6adc8159a3ca73 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -1780,13 +1780,13 @@ def visibility_level_allowed_as_fork end def pages_https_only - return false unless Gitlab.config.pages.external_https + return false unless Gitlab.config.pages.external_https || Gitlab.config.pages.custom_domain_mode == 'https' super end def pages_https_only? - return false unless Gitlab.config.pages.external_https + return false unless Gitlab.config.pages.external_https || Gitlab.config.pages.custom_domain_mode == 'https' super end diff --git a/app/views/projects/pages/_pages_settings.html.haml b/app/views/projects/pages/_pages_settings.html.haml index 00a60376a15f815512e395cb45d3b2d2f68a5179..0d3a4224e6f8d187e712a1fe0c9ff8bb0a6ebd5b 100644 --- a/app/views/projects/pages/_pages_settings.html.haml +++ b/app/views/projects/pages/_pages_settings.html.haml @@ -1,7 +1,7 @@ = gitlab_ui_form_for @project, url: project_pages_path(@project, anchor: 'domains-settings'), html: { class: 'gl-inline-block', title: pages_https_only_title } do |f| = render_if_exists 'shared/pages/max_pages_size_input', form: f - - if Gitlab.config.pages.external_http || Gitlab.config.pages.external_https + - if Gitlab.config.pages.custom_domain_mode || Gitlab.config.pages.external_http || Gitlab.config.pages.external_https .form-group = f.gitlab_ui_checkbox_component :pages_https_only, s_('GitLabPages|Force HTTPS (requires valid certificates)'), @@ -20,7 +20,7 @@ %p.gl-pl-6 = s_("GitLabPages|When enabled, a unique domain is generated to access pages.").html_safe - - if Gitlab.config.pages.external_http || Gitlab.config.pages.external_https + - if Gitlab.config.pages.custom_domain_mode || Gitlab.config.pages.external_http || Gitlab.config.pages.external_https - if @project.pages_domains.present? .form-group = f.fields_for :project_setting do |settings| diff --git a/app/views/projects/pages/show.html.haml b/app/views/projects/pages/show.html.haml index 45ec16bec994537f61b08480200b6dea460a835e..9750edef259c8e3812c48c9dcb3a932f76700e4d 100644 --- a/app/views/projects/pages/show.html.haml +++ b/app/views/projects/pages/show.html.haml @@ -18,7 +18,7 @@ .tab-pane#domains-settings .gl-flex.gl-flex-col.gl-gap-5 = render 'ssl_limitations_warning' if pages_subdomain(@project).include?(".") - - if Gitlab.config.pages.external_http || Gitlab.config.pages.external_https + - if Gitlab.config.pages.custom_domain_mode || Gitlab.config.pages.external_http || Gitlab.config.pages.external_https = render 'list' - else = render 'no_domains' diff --git a/app/views/projects/pages_domains/_form.html.haml b/app/views/projects/pages_domains/_form.html.haml index f2a343b1a91812d2c2115d54098fae7971064703..18f02df2a87cd99885cc14ddf776a6cbd5719737 100644 --- a/app/views/projects/pages_domains/_form.html.haml +++ b/app/views/projects/pages_domains/_form.html.haml @@ -19,7 +19,7 @@ - if domain_presenter.persisted? = render 'dns' -- if Gitlab.config.pages.external_https +- if Gitlab.config.pages.external_https || Gitlab.config.pages.custom_domain_mode == 'https' = render 'certificate', f: f - else .gl-text-subtle.gl-my-5 diff --git a/config/gitlab.yml.example b/config/gitlab.yml.example index 0f0cd22d7839d73175a0cd7b59114d8ab2a1d739..2eb11ab17be9d3117a221d714533d9c5bea0e194 100644 --- a/config/gitlab.yml.example +++ b/config/gitlab.yml.example @@ -502,6 +502,7 @@ production: &base artifacts_server: true # Set to false if you want to disable online view of HTML artifacts # external_http: ["1.1.1.1:80", "[2001::1]:80"] # If defined, enables custom domain support in GitLab Pages # external_https: ["1.1.1.1:443", "[2001::1]:443"] # If defined, enables custom domain and certificate support in GitLab Pages + # custom_domain_mode: http # Configure Pages to enable custom domain: `http` or `https` # File that contains the shared secret key for verifying access for gitlab-pages. # Default is '.gitlab_pages_secret' relative to Rails.root (i.e. root of the GitLab app). diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb index 9def8e5e1bdf35e492f101ff2c9dd602399ac523..d23d184286bf7573f72e27b722b2398955c6ea63 100644 --- a/config/initializers/1_settings.rb +++ b/config/initializers/1_settings.rb @@ -357,6 +357,9 @@ Settings.pages['url'] ||= Settings.__send__(:build_pages_url) Settings.pages['external_http'] ||= false unless Settings.pages['external_http'].present? Settings.pages['external_https'] ||= false unless Settings.pages['external_https'].present? +Settings.pages['custom_domain_mode'] = 'http' if Settings.pages['external_http'].present? +Settings.pages['custom_domain_mode'] = 'https' if Settings.pages['external_https'].present? +Settings.pages['custom_domain_mode'] = nil unless Settings.pages['custom_domain_mode'].present? Settings.pages['artifacts_server'] ||= Settings.pages['enabled'] if Settings.pages['artifacts_server'].nil? Settings.pages['secret_file'] ||= Rails.root.join('.gitlab_pages_secret') # We want pages zip archives to be stored on the same directory as old pages hierarchical structure diff --git a/spec/helpers/projects/pages_helper_spec.rb b/spec/helpers/projects/pages_helper_spec.rb index 0972b4595b569b5abc7cc8d7319677e59131d5a6..7611bf6beea07a256ece66803f3e12aeb4c4f66c 100644 --- a/spec/helpers/projects/pages_helper_spec.rb +++ b/spec/helpers/projects/pages_helper_spec.rb @@ -11,6 +11,7 @@ access_control: true, external_http: true, external_https: true, + custom_domain_mode: 'http', host: "new.domain.com" }) end @@ -23,16 +24,23 @@ context 'on custom domain' do using RSpec::Parameterized::TableSyntax - where(:external_http, :external_https, :can_create) do - false | false | false - false | true | true - true | false | true - true | true | true + where(:external_http, :external_https, :custom_domain_mode, :can_create) do + false | false | nil | false + false | true | nil | true + true | false | nil | true + true | true | nil | true + false | false | 'http' | true + false | false | 'https' | true + false | false | false | false end with_them do it do - stub_config(pages: { external_http: external_http, external_https: external_https }) + stub_config(pages: { + external_http: external_http, + external_https: external_https, + custom_domain_mode: custom_domain_mode + }) expect(can_create_pages_custom_domains?(user, project)).to be can_create end diff --git a/spec/initializers/1_settings_spec.rb b/spec/initializers/1_settings_spec.rb index 503667067dd69fd8457c509b8e40073f0eec9000..004de417aa41ebdbe9e625e47e4fc04a85c84106 100644 --- a/spec/initializers/1_settings_spec.rb +++ b/spec/initializers/1_settings_spec.rb @@ -97,4 +97,37 @@ it { expect(Settings.cell.topology_service_client.private_key_file).to eq(config[:private_key_file]) } end end + + describe 'Pages custom domains settings' do + using RSpec::Parameterized::TableSyntax + + where(:external_http, :external_https, :initial_custom_domain_mode, :expected_custom_domain_mode) do + nil | true | nil | 'https' + true | nil | nil | 'http' + true | true | nil | 'https' + nil | nil | 'https' | 'https' + false | false | 'http' | 'http' + nil | true | 'http' | 'https' + nil | nil | nil | nil + end + + with_them do + before do + stub_config(pages: { + enabled: true, + external_http: external_http, + external_https: external_https, + custom_domain_mode: initial_custom_domain_mode + }) + + allow(Settings.pages).to receive(:__getobj__).and_return(Settings.pages) + end + + it 'sets the expected custom_domain_mode value' do + load_settings + + expect(Settings.pages['custom_domain_mode']).to eq(expected_custom_domain_mode) + end + end + end end diff --git a/spec/support/pages.rb b/spec/support/pages.rb index ad73d5b9ef523347708add4c8f6dceee957a197f..c2909376746e4160462b36e19bdbe2c55d4ed423 100644 --- a/spec/support/pages.rb +++ b/spec/support/pages.rb @@ -2,18 +2,18 @@ RSpec.configure do |config| config.before(:each, :http_pages_enabled) do |_| - allow(Gitlab.config.pages).to receive(:external_http).and_return(['1.1.1.1:80']) + allow(Gitlab.config.pages).to receive_messages(external_http: ['1.1.1.1:80'], custom_domain_mode: "http") end config.before(:each, :https_pages_enabled) do |_| - allow(Gitlab.config.pages).to receive(:external_https).and_return(['1.1.1.1:443']) + allow(Gitlab.config.pages).to receive_messages(external_https: ['1.1.1.1:443'], custom_domain_mode: "https") end config.before(:each, :http_pages_disabled) do |_| - allow(Gitlab.config.pages).to receive(:external_http).and_return(false) + allow(Gitlab.config.pages).to receive_messages(external_http: false, custom_domain_mode: nil) end config.before(:each, :https_pages_disabled) do |_| - allow(Gitlab.config.pages).to receive(:external_https).and_return(false) + allow(Gitlab.config.pages).to receive_messages(external_https: false, custom_domain_mode: nil) end end diff --git a/spec/views/projects/pages/_pages_settings.html.haml_spec.rb b/spec/views/projects/pages/_pages_settings.html.haml_spec.rb index 2be20dc07d48a7fb223fe57034be2ad42c5ae9e3..85f6bef69729182605fa4d2e3303a5f13b9dec76 100644 --- a/spec/views/projects/pages/_pages_settings.html.haml_spec.rb +++ b/spec/views/projects/pages/_pages_settings.html.haml_spec.rb @@ -6,49 +6,72 @@ let_it_be(:project) { build_stubbed(:project) } let_it_be(:user) { build_stubbed(:user) } - before do - stub_config(pages: { - enabled: true, - external_http: true, - external_https: true, - access_control: false - }) - assign(:project, project) - allow(view).to receive(:current_user).and_return(user) - end - - context 'for pages unique domain' do - it 'shows the unique domain toggle' do - render + shared_examples 'page settings tests' do + context 'for pages unique domain' do + it 'shows the unique domain toggle' do + render - expect(rendered).to have_content('Use unique domain') + expect(rendered).to have_content('Use unique domain') + end end - end - context 'when pages_domains is empty' do - before do - allow(project).to receive(:pages_domains).and_return([]) + context 'when pages_domains is empty' do + before do + allow(project).to receive(:pages_domains).and_return([]) + end + + it 'does not render the redirect domains section' do + render + + expect(rendered).not_to have_selector('.form-group', text: 'Primary domain') + end end - it 'does not render the redirect domains section' do - render + context 'when pages_domains is not empty' do + before do + allow(project).to receive(:pages_domains).and_return([build_stubbed(:pages_domain)]) + allow(view).to receive(:project_pages_domain_choices).and_return( + options_for_select([['new.domain.com', 'new.domain.com']]) + ) + end + + it 'renders the redirect domains section' do + render - expect(rendered).not_to have_selector('.form-group', text: 'Primary domain') + expect(rendered).to have_content('Primary domain') + end end end - context 'when pages_domains is not empty' do + context 'when external_http and external_https are both true and custom_domain_mode is https' do before do - allow(project).to receive(:pages_domains).and_return([build_stubbed(:pages_domain)]) - allow(view).to receive(:project_pages_domain_choices).and_return( - options_for_select([['new.domain.com', 'new.domain.com']]) - ) + stub_config(pages: { + enabled: true, + external_http: true, + external_https: true, + custom_domain_mode: "https", + access_control: false + }) + assign(:project, project) + allow(view).to receive(:current_user).and_return(user) end - it 'renders the redirect domains section' do - render + include_examples 'page settings tests' + end - expect(rendered).to have_content('Primary domain') + context 'when external_http and external_https are both false and custom_domain_mode is https' do + before do + stub_config(pages: { + enabled: true, + external_http: false, + external_https: false, + custom_domain_mode: "https", + access_control: false + }) + assign(:project, project) + allow(view).to receive(:current_user).and_return(user) end + + include_examples 'page settings tests' end end diff --git a/spec/views/projects/pages_domains/_form.html.haml_spec.rb b/spec/views/projects/pages_domains/_form.html.haml_spec.rb new file mode 100644 index 0000000000000000000000000000000000000000..9ff664e8edac8811c0398c454bcafb46aca0c6df --- /dev/null +++ b/spec/views/projects/pages_domains/_form.html.haml_spec.rb @@ -0,0 +1,47 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'projects/pages_domains/_form', feature_category: :pages do + let(:project) { build(:project, :repository) } + let(:domain) { build(:pages_domain, project: project) } + let(:form) { instance_double(Gitlab::FormBuilders::GitlabUiFormBuilder, text_field: nil, label: nil) } + let(:domain_presenter) { domain.present } + + before do + assign(:project, project) + allow(view).to receive_messages(domain_presenter: domain_presenter, f: form) + allow(view).to receive(:render).and_call_original + allow(view).to receive(:render).with('certificate', f: form).and_return('') + end + + describe 'certificate section rendering' do + where(:external_https, :custom_domain_mode, :renders_certificate) do + [ + [true, 'https', true], + [true, 'http', true], + [false, 'https', true], + [false, 'http', false] + ] + end + + with_them do + it "when external_https=#{params[:external_https]} and custom_domain_mode=#{params[:custom_domain_mode]}" do + stub_pages_setting(external_https: external_https, custom_domain_mode: custom_domain_mode) + + if renders_certificate + expect(view).to receive(:render).with('certificate', f: form) + else + expect(view).not_to receive(:render).with('certificate', f: form) + end + + render partial: 'projects/pages_domains/form' + + unless renders_certificate + expect(rendered).to have_content( + "Support for custom certificates is disabled. Ask your system's administrator to enable it.") + end + end + end + end +end diff --git a/spec/views/projects/pages_domains/show.html.haml_spec.rb b/spec/views/projects/pages_domains/show.html.haml_spec.rb index d2abe3dfa56cfccfbcf40ac9d66fe54710424273..d356596460b97950c0719373eb3519833f58d637 100644 --- a/spec/views/projects/pages_domains/show.html.haml_spec.rb +++ b/spec/views/projects/pages_domains/show.html.haml_spec.rb @@ -5,31 +5,47 @@ RSpec.describe 'projects/pages_domains/show' do let(:project) { create(:project, :repository) } - before do - assign(:project, project) - allow(view).to receive(:domain_presenter).and_return(domain.present) - stub_pages_setting(external_https: true) - end + shared_examples 'pages domain tests' do + context 'when auto_ssl is enabled' do + context 'when domain is disabled' do + let(:domain) { create(:pages_domain, :disabled, project: project, auto_ssl_enabled: true) } + + it 'shows verification warning' do + render - context 'when auto_ssl is enabled' do - context 'when domain is disabled' do - let(:domain) { create(:pages_domain, :disabled, project: project, auto_ssl_enabled: true) } + expect(rendered).to have_content("A Let's Encrypt SSL certificate can not be obtained until your domain is verified.") + end + end - it 'shows verification warning' do - render + context 'when certificate is absent' do + let(:domain) { create(:pages_domain, :without_key, :without_certificate, project: project, auto_ssl_enabled: true) } - expect(rendered).to have_content("A Let's Encrypt SSL certificate can not be obtained until your domain is verified.") + it 'shows alert about time of obtaining certificate' do + render + + expect(rendered).to have_content("GitLab is obtaining a Let's Encrypt SSL certificate for this domain. This process can take some time. Please try again later.") + end end end + end - context 'when certificate is absent' do - let(:domain) { create(:pages_domain, :without_key, :without_certificate, project: project, auto_ssl_enabled: true) } + context 'when external_https is true' do + before do + assign(:project, project) + allow(view).to receive(:domain_presenter).and_return(domain.present) + stub_pages_setting(external_https: true, custom_domain_mode: 'https') + end - it 'shows alert about time of obtaining certificate' do - render + include_examples 'pages domain tests' + end - expect(rendered).to have_content("GitLab is obtaining a Let's Encrypt SSL certificate for this domain. This process can take some time. Please try again later.") - end + context 'when external_https is false' do + before do + assign(:project, project) + allow(view).to receive(:domain_presenter).and_return(domain.present) + stub_pages_setting(external_https: false, custom_domain_mode: 'https') end + + include_examples 'pages domain tests' end end