From 676675dc0b95194be72dfa13829b5ba06e0d1844 Mon Sep 17 00:00:00 2001 From: Krasimir Angelov Date: Fri, 6 Sep 2019 17:20:05 +1200 Subject: [PATCH] Add support for custom domains to the internal Pages API Update the `/internal/pages` endpoint to return virtual domain configuration for custom domains. --- app/models/pages/lookup_path.rb | 38 +++++++++++ app/models/pages/virtual_domain.rb | 28 ++++++++ app/models/pages_domain.rb | 4 ++ app/models/project.rb | 14 ++-- app/models/project_feature.rb | 4 ++ lib/api/entities/internal.rb | 19 ++++++ lib/api/internal/pages.rb | 7 +- .../schemas/internal/pages/lookup_path.json | 25 ++++++++ .../internal/pages/virtual_domain.json | 16 +++++ spec/models/pages/lookup_path_spec.rb | 64 +++++++++++++++++++ spec/requests/api/internal/pages_spec.rb | 28 +++++++- 11 files changed, 238 insertions(+), 9 deletions(-) create mode 100644 app/models/pages/lookup_path.rb create mode 100644 app/models/pages/virtual_domain.rb create mode 100644 lib/api/entities/internal.rb create mode 100644 spec/fixtures/api/schemas/internal/pages/lookup_path.json create mode 100644 spec/fixtures/api/schemas/internal/pages/virtual_domain.json create mode 100644 spec/models/pages/lookup_path_spec.rb diff --git a/app/models/pages/lookup_path.rb b/app/models/pages/lookup_path.rb new file mode 100644 index 00000000000..1b3183a2a43 --- /dev/null +++ b/app/models/pages/lookup_path.rb @@ -0,0 +1,38 @@ +# frozen_string_literal: true + +module Pages + class LookupPath + def initialize(project, domain: nil) + @project = project + @domain = domain + end + + def project_id + project.id + end + + def access_control + project.private_pages? + end + + def https_only + domain_https = domain ? domain.https? : true + project.pages_https_only? && domain_https + end + + def source + { + type: 'file', + path: File.join(project.full_path, 'public/') + } + end + + def prefix + '/' + end + + private + + attr_reader :project, :domain + end +end diff --git a/app/models/pages/virtual_domain.rb b/app/models/pages/virtual_domain.rb new file mode 100644 index 00000000000..3a876dc06a2 --- /dev/null +++ b/app/models/pages/virtual_domain.rb @@ -0,0 +1,28 @@ +# frozen_string_literal: true + +module Pages + class VirtualDomain + def initialize(projects, domain: nil) + @projects = projects + @domain = domain + end + + def certificate + domain&.certificate + end + + def key + domain&.key + end + + def lookup_paths + projects.map do |project| + project.pages_lookup_path(domain: domain) + end.sort_by(&:prefix).reverse + end + + private + + attr_reader :projects, :domain + end +end diff --git a/app/models/pages_domain.rb b/app/models/pages_domain.rb index a2a471074a9..22a6bae7cf7 100644 --- a/app/models/pages_domain.rb +++ b/app/models/pages_domain.rb @@ -185,6 +185,10 @@ class PagesDomain < ApplicationRecord self.certificate_source = 'gitlab_provided' if key_changed? end + def pages_virtual_domain + Pages::VirtualDomain.new([project], domain: self) + end + private def set_verification_code diff --git a/app/models/project.rb b/app/models/project.rb index d948410e397..12f5da05efa 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -61,11 +61,11 @@ class Project < ApplicationRecord cache_markdown_field :description, pipeline: :description - delegate :feature_available?, :builds_enabled?, :wiki_enabled?, - :merge_requests_enabled?, :issues_enabled?, :pages_enabled?, :public_pages?, - :merge_requests_access_level, :issues_access_level, :wiki_access_level, - :snippets_access_level, :builds_access_level, :repository_access_level, - to: :project_feature, allow_nil: true + delegate :feature_available?, :builds_enabled?, :wiki_enabled?, :merge_requests_enabled?, + :issues_enabled?, :pages_enabled?, :public_pages?, :private_pages?, + :merge_requests_access_level, :issues_access_level, :wiki_access_level, + :snippets_access_level, :builds_access_level, :repository_access_level, + to: :project_feature, allow_nil: true delegate :base_dir, :disk_path, :ensure_storage_path_exists, to: :storage @@ -2201,6 +2201,10 @@ class Project < ApplicationRecord members.maintainers.order_recent_sign_in.limit(ACCESS_REQUEST_APPROVERS_TO_BE_NOTIFIED_LIMIT) end + def pages_lookup_path(domain: nil) + Pages::LookupPath.new(self, domain: domain) + end + private def merge_requests_allowing_collaboration(source_branch = nil) diff --git a/app/models/project_feature.rb b/app/models/project_feature.rb index 78e82955342..efa3fbcf015 100644 --- a/app/models/project_feature.rb +++ b/app/models/project_feature.rb @@ -129,6 +129,10 @@ class ProjectFeature < ApplicationRecord pages_access_level == PUBLIC || pages_access_level == ENABLED && project.public? end + def private_pages? + !public_pages? + end + private # Validates builds and merge requests access level diff --git a/lib/api/entities/internal.rb b/lib/api/entities/internal.rb new file mode 100644 index 00000000000..8f79bd14833 --- /dev/null +++ b/lib/api/entities/internal.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +module API + module Entities + module Internal + module Pages + class LookupPath < Grape::Entity + expose :project_id, :access_control, + :source, :https_only, :prefix + end + + class VirtualDomain < Grape::Entity + expose :certificate, :key + expose :lookup_paths, using: LookupPath + end + end + end + end +end diff --git a/lib/api/internal/pages.rb b/lib/api/internal/pages.rb index 6ea048bde03..eaa434cff51 100644 --- a/lib/api/internal/pages.rb +++ b/lib/api/internal/pages.rb @@ -18,7 +18,12 @@ module API namespace 'internal' do namespace 'pages' do get "/" do - status :ok + host = PagesDomain.find_by_domain(params[:host]) + not_found! unless host + + virtual_domain = host.pages_virtual_domain + + present virtual_domain, with: Entities::Internal::Pages::VirtualDomain end end end diff --git a/spec/fixtures/api/schemas/internal/pages/lookup_path.json b/spec/fixtures/api/schemas/internal/pages/lookup_path.json new file mode 100644 index 00000000000..b2b3d3f9d0a --- /dev/null +++ b/spec/fixtures/api/schemas/internal/pages/lookup_path.json @@ -0,0 +1,25 @@ +{ + "type": "object", + "required": [ + "project_id", + "https_only", + "access_control", + "source", + "prefix" + ], + "properties": { + "project_id": { "type": "integer" }, + "https_only": { "type": "boolean" }, + "access_control": { "type": "boolean" }, + "source": { "type": "object", + "required": ["type", "path"], + "properties" : { + "type": { "type": "string", "enum": ["file"] }, + "path": { "type": "string" } + }, + "additionalProperties": false + }, + "prefix": { "type": "string" } + }, + "additionalProperties": false +} diff --git a/spec/fixtures/api/schemas/internal/pages/virtual_domain.json b/spec/fixtures/api/schemas/internal/pages/virtual_domain.json new file mode 100644 index 00000000000..02df69026b0 --- /dev/null +++ b/spec/fixtures/api/schemas/internal/pages/virtual_domain.json @@ -0,0 +1,16 @@ +{ + "type": "object", + "required": [ + "lookup_paths" + ], + "optional": [ + "certificate", + "key" + ], + "properties": { + "certificate": { "type": ["string", "null"] }, + "key": { "type": ["string", "null"] }, + "lookup_paths": { "type": "array", "items": { "$ref": "lookup_path.json" } } + }, + "additionalProperties": false +} diff --git a/spec/models/pages/lookup_path_spec.rb b/spec/models/pages/lookup_path_spec.rb new file mode 100644 index 00000000000..2146b0c9abd --- /dev/null +++ b/spec/models/pages/lookup_path_spec.rb @@ -0,0 +1,64 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Pages::LookupPath do + let(:project) do + instance_double(Project, + id: 12345, + private_pages?: true, + pages_https_only?: true, + full_path: 'the/full/path' + ) + end + + subject(:lookup_path) { described_class.new(project) } + + describe '#project_id' do + it 'delegates to Project#id' do + expect(lookup_path.project_id).to eq(12345) + end + end + + describe '#access_control' do + it 'delegates to Project#private_pages?' do + expect(lookup_path.access_control).to eq(true) + end + end + + describe '#https_only' do + subject(:lookup_path) { described_class.new(project, domain: domain) } + + context 'when no domain provided' do + let(:domain) { nil } + + it 'delegates to Project#pages_https_only?' do + expect(lookup_path.https_only).to eq(true) + end + end + + context 'when there is domain provided' do + let(:domain) { instance_double(PagesDomain, https?: false) } + + it 'takes into account the https setting of the domain' do + expect(lookup_path.https_only).to eq(false) + end + end + end + + describe '#source' do + it 'sets the source type to "file"' do + expect(lookup_path.source[:type]).to eq('file') + end + + it 'sets the source path to the project full path suffixed with "public/' do + expect(lookup_path.source[:path]).to eq('the/full/path/public/') + end + end + + describe '#prefix' do + it 'returns "/"' do + expect(lookup_path.prefix).to eq('/') + end + end +end diff --git a/spec/requests/api/internal/pages_spec.rb b/spec/requests/api/internal/pages_spec.rb index 0b3c5be9c45..e1b563b92f4 100644 --- a/spec/requests/api/internal/pages_spec.rb +++ b/spec/requests/api/internal/pages_spec.rb @@ -43,10 +43,32 @@ describe API::Internal::Pages do super(host, headers) end - it 'responds with 200 OK' do - query_host('pages.gitlab.io') + context 'not existing host' do + it 'responds with 404 Not Found' do + query_host('pages.gitlab.io') + + expect(response).to have_gitlab_http_status(404) + end + end + + context 'custom domain' do + let(:namespace) { create(:namespace, name: 'gitlab-org') } + let(:project) { create(:project, namespace: namespace, name: 'gitlab-ce') } + let!(:pages_domain) { create(:pages_domain, domain: 'pages.gitlab.io', project: project) } + + it 'responds with the correct domain configuration' do + query_host('pages.gitlab.io') + + expect(response).to have_gitlab_http_status(200) + expect(response).to match_response_schema('internal/pages/virtual_domain') + + expect(json_response['certificate']).to eq(pages_domain.certificate) + expect(json_response['key']).to eq(pages_domain.key) - expect(response).to have_gitlab_http_status(200) + lookup_path = json_response['lookup_paths'][0] + expect(lookup_path['prefix']).to eq('/') + expect(lookup_path['source']['path']).to eq('gitlab-org/gitlab-ce/public/') + end end end end -- GitLab