diff --git a/app/assets/javascripts/ide/init_gitlab_web_ide.js b/app/assets/javascripts/ide/init_gitlab_web_ide.js index 7cef7ff9ea6e7b1df5602632b87006b505f7d653..83d6a8dc86179c0c1f0c37ab6e485b96c2893ea7 100644 --- a/app/assets/javascripts/ide/init_gitlab_web_ide.js +++ b/app/assets/javascripts/ide/init_gitlab_web_ide.js @@ -1,5 +1,5 @@ import { start } from '@gitlab/web-ide'; -import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils'; +import { convertObjectPropsToCamelCase, parseBoolean } from '~/lib/utils/common_utils'; import csrf from '~/lib/utils/csrf'; import Tracking from '~/tracking'; import { getLineRangeFromHash } from '~/lib/utils/url_utility'; @@ -34,30 +34,35 @@ export const initGitlabWebIDE = async (el) => { extensionMarketplaceSettings: extensionMarketplaceSettingsJSON, settingsContextHash, signOutPath, + extensionHostDomain, + extensionHostDomainChanged, } = el.dataset; - const webIdeWorkbenchConfig = await getWebIDEWorkbenchConfig(); - const container = setupIdeContainer(el); - const editorFont = editorFontJSON - ? convertObjectPropsToCamelCase(JSON.parse(editorFontJSON), { deep: true }) - : null; - const forkInfo = forkInfoJSON ? JSON.parse(forkInfoJSON) : null; - const extensionMarketplaceSettings = extensionMarketplaceSettingsJSON - ? convertObjectPropsToCamelCase(JSON.parse(extensionMarketplaceSettingsJSON), { deep: true }) - : undefined; + try { + const webIdeWorkbenchConfig = await getWebIDEWorkbenchConfig({ + extensionHostDomain, + extensionHostDomainChanged: parseBoolean(extensionHostDomainChanged), + }); + const container = setupIdeContainer(el); + const editorFont = editorFontJSON + ? convertObjectPropsToCamelCase(JSON.parse(editorFontJSON), { deep: true }) + : null; + const forkInfo = forkInfoJSON ? JSON.parse(forkInfoJSON) : null; + const extensionMarketplaceSettings = extensionMarketplaceSettingsJSON + ? convertObjectPropsToCamelCase(JSON.parse(extensionMarketplaceSettingsJSON), { deep: true }) + : undefined; - const oauthConfig = getOAuthConfig(el.dataset); - const httpHeaders = oauthConfig - ? undefined - : // Use same headers as defined in axios_utils (not needed in oauth) - { - [csrf.headerKey]: csrf.token, - 'X-Requested-With': 'XMLHttpRequest', - }; + const oauthConfig = getOAuthConfig(el.dataset); + const httpHeaders = oauthConfig + ? undefined + : // Use same headers as defined in axios_utils (not needed in oauth) + { + [csrf.headerKey]: csrf.token, + 'X-Requested-With': 'XMLHttpRequest', + }; - const lineRange = getLineRangeFromHash(); + const lineRange = getLineRangeFromHash(); - try { // See ClientOnlyConfig https://gitlab.com/gitlab-org/gitlab-web-ide/-/blob/main/packages/web-ide-types/src/config.ts#L17 const { ready } = await start(container.element, { ...getBaseConfig(), diff --git a/app/assets/javascripts/ide/lib/gitlab_web_ide/get_web_ide_workbench_config.js b/app/assets/javascripts/ide/lib/gitlab_web_ide/get_web_ide_workbench_config.js index c5b8cc92d703deccc510425bd854887732423b07..a55534a9e1ed037036e4ed7cbe428db5203193d5 100644 --- a/app/assets/javascripts/ide/lib/gitlab_web_ide/get_web_ide_workbench_config.js +++ b/app/assets/javascripts/ide/lib/gitlab_web_ide/get_web_ide_workbench_config.js @@ -1,12 +1,48 @@ import * as packageJSON from '@gitlab/web-ide/package.json'; import { pingWorkbench } from '@gitlab/web-ide'; +import { s__ } from '~/locale'; import { sha256 } from '~/lib/utils/text_utility'; +import { joinPaths } from '~/lib/utils/url_utility'; import { getGitLabUrl } from './get_gitlab_url'; -const buildExtensionHostUrl = () => { - const workbenchVersion = packageJSON.version; +/** + * Builds the URL path that points to the Web IDE extension host + * assets. If the extension host domain changed in application settings, + * the Web IDE assumes that the new domain points to the Gitlab instance + * itself therefore the base path starts with the Gitlab instance root assets + * path "assets/webpack". + * @param {Boolean} extensionHostDomainChanged Whether the base extension host domain + * is not the default value built into the GitLab instance. + * @returns + */ +const buildBaseAssetsPath = (extensionHostDomainChanged) => { + const assetsRoot = extensionHostDomainChanged ? '/assets/webpack' : '/'; + + return joinPaths(assetsRoot, `gitlab-web-ide-vscode-workbench-${packageJSON.version}`); +}; + +/** + * Builds the URL that points to the VSCode extension host service. If instance admin + * provides a custom the extension host domain, this function prepends `/assets/webpack` + * to the URL path because it assumes the custom extension host domains points to the + * GitLab instance. + * + * VSCode expects that the extension host domain is a wildcard therefore we insert a placeholder + * {{uuid}} at the beginning of the domain. + * + * @param {String} options.extensionHostDomain Base extension host domain coming from + * application settings + * @param {Boolean} options.extensionHostDomainChanged Whether the base extension host domain + * is not the default value built into the GitLab instance. + * @returns + */ +const buildExtensionHostUrl = ({ extensionHostDomain, extensionHostDomainChanged }) => { + const baseAssetsPath = buildBaseAssetsPath(extensionHostDomainChanged); + const fullAssetsPath = joinPaths(baseAssetsPath, 'vscode'); + + const extensionHostUrl = new URL(fullAssetsPath, `https://{{uuid}}.${extensionHostDomain}`); - return `https://{{uuid}}.cdn.web-ide.gitlab-static.net/gitlab-web-ide-vscode-workbench-${workbenchVersion}/vscode`; + return extensionHostUrl.href; }; const rejectHTTPEmbedderOrigin = () => { @@ -16,35 +52,68 @@ const rejectHTTPEmbedderOrigin = () => { }; /** - * Generates the workbench URL for Web IDE + * Builds the URL that points to the VSCode workbench assets. If instance admin + * provides a custom the extension host domain, this function prepends `/assets/webpack` + * to the URL path because it assumes the custom extension host domains points to the + * GitLab instance. + * + * The client generates a workbench subdomain based on the GitLab instance domain and the + * current username. + * + * @param {String} options.extensionHostDomain Base extension host domain coming from + * application settings + * @param {Boolean} options.extensionHostDomainChanged Whether the base extension host domain + * is not the default value built into the GitLab instance. * - * Uses the current user's username and the origin to generate a digest - * to ensure that the URL is unique for each user. - * @returns {string} */ -export const buildWorkbenchUrl = async () => { +export const buildWorkbenchUrl = async ({ extensionHostDomain, extensionHostDomainChanged }) => { const digest = await sha256(`${window.location.origin}-${window.gon.current_username}`); const digestShort = digest.slice(0, 30); - const workbenchVersion = packageJSON.version; + const workbenchUrl = new URL( + buildBaseAssetsPath(extensionHostDomainChanged), + `https://workbench-${digestShort}.${extensionHostDomain}`, + ); - return `https://workbench-${digestShort}.cdn.web-ide.gitlab-static.net/gitlab-web-ide-vscode-workbench-${workbenchVersion}`; + return workbenchUrl.href; }; /** * Retrieves configuration for Web IDE workbench. * + * @param {String} options.extensionHostDomain Base extension host domain coming from + * application settings + * @param {Boolean} options.extensionHostDomainChanged Whether the base extension host domain + * is not the default value built into the GitLab instance. + * * @returns An object containing the following properties * - workbenchBaseUrl URL pointing to the origin and base path where the Web IDE's workbench assets are hosted. * - extensionsHostBaseUrl URL pointing to the origin and the base path where the Web IDE's extensions host assets are hosted. * - crossOriginExtensionHost Boolean specifying whether the extensions host will use cross-origin isolation. */ -export const getWebIDEWorkbenchConfig = async () => { - const extensionsHostBaseUrl = buildExtensionHostUrl(); +export const getWebIDEWorkbenchConfig = async ({ + extensionHostDomain, + extensionHostDomainChanged = false, +} = {}) => { + if (typeof extensionHostDomain !== 'string' || !extensionHostDomain) { + throw new Error( + s__( + 'WebIDE|The Web IDE does not have a valid extension host domain and it could not be initialized.', + ), + ); + } + + const extensionsHostBaseUrl = buildExtensionHostUrl({ + extensionHostDomain, + extensionHostDomainChanged, + }); try { rejectHTTPEmbedderOrigin(); - const workbenchBaseUrl = await buildWorkbenchUrl(); + const workbenchBaseUrl = await buildWorkbenchUrl({ + extensionHostDomain, + extensionHostDomainChanged, + }); await pingWorkbench({ el: document.body, diff --git a/app/controllers/admin/application_settings_controller.rb b/app/controllers/admin/application_settings_controller.rb index d3d625ff8bd7221575800d809ceddc8f36741482..3ff466b6ea33104391fff1b47e35c12e68fa7ac7 100644 --- a/app/controllers/admin/application_settings_controller.rb +++ b/app/controllers/admin/application_settings_controller.rb @@ -38,6 +38,7 @@ class ApplicationSettingsController < Admin::ApplicationController feature_category :observability, [:reset_error_tracking_access_token] feature_category :global_search, [:search] feature_category :environment_management, [:usage_quotas] + feature_category :editor_extensions, [:reset_vscode_extension_marketplace_extension_host_domain] VALID_SETTING_PANELS = %w[general repository ci_cd reporting metrics_and_profiling @@ -104,6 +105,13 @@ def reset_error_tracking_access_token notice: _('New error tracking access token has been generated!') end + def reset_vscode_extension_marketplace_extension_host_domain + ::WebIde::ExtensionMarketplace.reset_extension_host_domain! + + redirect_to general_admin_application_settings_path(anchor: 'js-web-ide-settings'), + notice: _('The Web IDE extension host domain was restored to its default value.') + end + def clear_repository_check_states RepositoryCheck::ClearWorker.perform_async # rubocop:disable CodeReuse/Worker diff --git a/app/controllers/concerns/web_ide_csp.rb b/app/controllers/concerns/web_ide_csp.rb index 0d67393e3bd57430e9bc45128f2a92871c44a7b4..072eebbddb515a6ea603490c57e416fef70b59fc 100644 --- a/app/controllers/concerns/web_ide_csp.rb +++ b/app/controllers/concerns/web_ide_csp.rb @@ -23,7 +23,7 @@ def include_web_ide_csp default_src = Array(request.content_security_policy.directives['default-src'] || []) request.content_security_policy.directives['frame-src'] ||= default_src - request.content_security_policy.directives['frame-src'].concat([webpack_url, 'https://*.web-ide.gitlab-static.net/', + request.content_security_policy.directives['frame-src'].concat([webpack_url, "https://*.#{WebIde::ExtensionMarketplace.extension_host_domain}/", ide_oauth_redirect_url, oauth_authorization_url]) request.content_security_policy.directives['worker-src'] ||= default_src diff --git a/app/helpers/application_settings_helper.rb b/app/helpers/application_settings_helper.rb index fe89034fb16e533a704cde09ab1723d7ce5f8c43..bb3b0a94d944b6ee325edbc435056258ffed2320 100644 --- a/app/helpers/application_settings_helper.rb +++ b/app/helpers/application_settings_helper.rb @@ -636,6 +636,7 @@ def visible_attributes :minimum_language_server_version, :vscode_extension_marketplace, :vscode_extension_marketplace_enabled, + :vscode_extension_marketplace_extension_host_domain, :reindexing_minimum_index_size, :reindexing_minimum_relative_bloat_size, :anonymous_searches_allowed, diff --git a/app/helpers/ide_helper.rb b/app/helpers/ide_helper.rb index 476bc24a35b9b2db2060e2b7c3bae0ed9331fe6d..d97943be77f9d9460c27067c1760dc228a9d35df 100644 --- a/app/helpers/ide_helper.rb +++ b/app/helpers/ide_helper.rb @@ -83,7 +83,9 @@ def extend_ide_data(project:) 'csp-nonce' => content_security_policy_nonce, 'editor-font' => ide_fonts.to_json, 'extension-marketplace-settings' => extension_marketplace_settings.to_json, - 'settings-context-hash' => settings_context_hash + 'settings-context-hash' => settings_context_hash, + 'extension-host-domain' => WebIde::ExtensionMarketplace.extension_host_domain, + 'extension-host-domain-changed' => WebIde::ExtensionMarketplace.extension_host_domain_changed?.to_s }.merge(ide_code_suggestions_data).merge(ide_oauth_data) end diff --git a/app/models/application_setting.rb b/app/models/application_setting.rb index ba82b76bd5c6e4354591a24090cfbde60daae6bc..9316182d1b1b22fc020ab2f4d31e906cb5b4690d 100644 --- a/app/models/application_setting.rb +++ b/app/models/application_setting.rb @@ -1025,7 +1025,11 @@ def self.kroki_formats_attributes json_schema: { filename: "application_setting_vscode_extension_marketplace", detail_errors: true } jsonb_accessor :vscode_extension_marketplace, - vscode_extension_marketplace_enabled: [:boolean, { default: false, store_key: :enabled }] + vscode_extension_marketplace_enabled: [:boolean, { default: false, store_key: :enabled }], + vscode_extension_marketplace_extension_host_domain: [ + :string, + { default: ::WebIde::ExtensionMarketplace::DEFAULT_EXTENSION_HOST_DOMAIN, store_key: :extension_host_domain } + ] jsonb_accessor :editor_extensions, enable_language_server_restrictions: [:boolean, { default: false }], diff --git a/app/models/application_setting_implementation.rb b/app/models/application_setting_implementation.rb index d2dd3fc3eec7e21090d9848bc75b879799b0613c..66f4654af62a5b1c45447442d00ef584408dd250 100644 --- a/app/models/application_setting_implementation.rb +++ b/app/models/application_setting_implementation.rb @@ -351,6 +351,7 @@ def defaults # rubocop:disable Metrics/AbcSize top_level_group_creation_enabled: true, ropc_without_client_credentials: true, vscode_extension_marketplace_enabled: false, + vscode_extension_marketplace_extension_host_domain: ::WebIde::ExtensionMarketplace::DEFAULT_EXTENSION_HOST_DOMAIN, reindexing_minimum_index_size: 1.gigabyte, reindexing_minimum_relative_bloat_size: 0.2, git_push_pipeline_limit: 4, diff --git a/app/validators/json_schemas/application_setting_vscode_extension_marketplace.json b/app/validators/json_schemas/application_setting_vscode_extension_marketplace.json index 54c0c0ae07cb6ee4bb5e1a1df74df5d844157cfc..2e34937e7153fe33204564ea02c2b684ece97c38 100644 --- a/app/validators/json_schemas/application_setting_vscode_extension_marketplace.json +++ b/app/validators/json_schemas/application_setting_vscode_extension_marketplace.json @@ -9,6 +9,17 @@ "default": false, "description": "Should the VSCode Extension Marketplace be enabled for Web IDE and Workspaces" }, + "extension_host_domain": { + "type": "string", + "pattern": "^(?=.{1,253}$)(?!-)(?:[a-zA-Z0-9-]{1,63}(? { el.dataset.userPreferencesPath = TEST_USER_PREFERENCES_PATH; el.dataset.mergeRequest = TEST_MR_ID; el.dataset.filePath = TEST_FILE_PATH; + el.dataset.extensionHostDomain = 'web-ide.example.net'; + el.dataset.extensionHostDomainChanged = true; el.dataset.editorFont = JSON.stringify({ fallback_font_family: 'monospace', font_faces: [ @@ -180,6 +182,17 @@ describe('ide/init_gitlab_web_ide', () => { }); }); + it('provides extensionHostDomain and extensionHostDomainChanged external parameters to workbench URL builder', () => { + const rootEl = findRootElement(); + + expect(rootEl.dataset.extensionHostDomain).toBe('web-ide.example.net'); + expect(rootEl.dataset.extensionHostDomainChanged).toBe('true'); + expect(getWebIDEWorkbenchConfig).toHaveBeenCalledWith({ + extensionHostDomain: 'web-ide.example.net', + extensionHostDomainChanged: true, + }); + }); + describe('when web-ide is ready', () => { beforeEach(() => { start.mockResolvedValue({ ready: Promise.resolve() }); diff --git a/spec/frontend/ide/lib/gitlab_web_ide/get_web_ide_workbench_config_spec.js b/spec/frontend/ide/lib/gitlab_web_ide/get_web_ide_workbench_config_spec.js index 26e9b818c76b36ee512cb7b26b4ba4b6bb0f6036..d4c760c31813d450f0be116899818e027694bf63 100644 --- a/spec/frontend/ide/lib/gitlab_web_ide/get_web_ide_workbench_config_spec.js +++ b/spec/frontend/ide/lib/gitlab_web_ide/get_web_ide_workbench_config_spec.js @@ -14,6 +14,10 @@ jest.mock('~/ide/lib/gitlab_web_ide/get_gitlab_url'); describe('~/ide/lib/gitlab_web_ide/get_base_config', () => { const TEST_GITLAB_WEB_IDE_PUBLIC_PATH = 'test/gitlab-web-ide/public/path'; const GITLAB_URL = 'https://gitlab.example.com'; + const DEFAULT_PARAMETERS = { + extensionHostDomain: 'web-ide-example.net', + extensionHostDomainChanged: false, + }; useMockLocationHelper(); stubCrypto(); @@ -33,7 +37,7 @@ describe('~/ide/lib/gitlab_web_ide/get_base_config', () => { beforeEach(async () => { window.location.protocol = 'http:'; - config = await getWebIDEWorkbenchConfig(); + config = await getWebIDEWorkbenchConfig(DEFAULT_PARAMETERS); }); it('does not call pingWorkbench', () => { @@ -49,25 +53,44 @@ describe('~/ide/lib/gitlab_web_ide/get_base_config', () => { }); }); + describe('when the extensionHostDomain changed', () => { + let config; + + beforeEach(async () => { + config = await getWebIDEWorkbenchConfig({ + ...DEFAULT_PARAMETERS, + extensionHostDomainChanged: true, + }); + }); + + it('appends /assets/webpack to the URL paths', () => { + expect(config).toEqual({ + crossOriginExtensionHost: true, + workbenchBaseUrl: `https://workbench-82f9aaae2ef4f6ffb993ca55c2a2eb.${DEFAULT_PARAMETERS.extensionHostDomain}/assets/webpack/gitlab-web-ide-vscode-workbench-${packageJSON.version}`, + extensionsHostBaseUrl: `https://{{uuid}}.${DEFAULT_PARAMETERS.extensionHostDomain}/assets/webpack/gitlab-web-ide-vscode-workbench-${packageJSON.version}/vscode`, + }); + }); + }); + describe('when pingWorkbench is successful', () => { beforeEach(() => { pingWorkbench.mockResolvedValueOnce(); }); it('returns workbench configuration based on cdn.web-ide.gitlab-static.net', async () => { - const config = await getWebIDEWorkbenchConfig(); + const config = await getWebIDEWorkbenchConfig(DEFAULT_PARAMETERS); expect(pingWorkbench).toHaveBeenCalledWith({ el: document.body, config: { - workbenchBaseUrl: `https://workbench-82f9aaae2ef4f6ffb993ca55c2a2eb.cdn.web-ide.gitlab-static.net/gitlab-web-ide-vscode-workbench-${packageJSON.version}`, + workbenchBaseUrl: `https://workbench-82f9aaae2ef4f6ffb993ca55c2a2eb.${DEFAULT_PARAMETERS.extensionHostDomain}/gitlab-web-ide-vscode-workbench-${packageJSON.version}`, gitlabUrl: 'https://gitlab.example.com', }, }); expect(config).toEqual({ crossOriginExtensionHost: true, - workbenchBaseUrl: `https://workbench-82f9aaae2ef4f6ffb993ca55c2a2eb.cdn.web-ide.gitlab-static.net/gitlab-web-ide-vscode-workbench-${packageJSON.version}`, - extensionsHostBaseUrl: `https://{{uuid}}.cdn.web-ide.gitlab-static.net/gitlab-web-ide-vscode-workbench-${packageJSON.version}/vscode`, + workbenchBaseUrl: `https://workbench-82f9aaae2ef4f6ffb993ca55c2a2eb.${DEFAULT_PARAMETERS.extensionHostDomain}/gitlab-web-ide-vscode-workbench-${packageJSON.version}`, + extensionsHostBaseUrl: `https://{{uuid}}.${DEFAULT_PARAMETERS.extensionHostDomain}/gitlab-web-ide-vscode-workbench-${packageJSON.version}/vscode`, }); }); }); @@ -78,12 +101,12 @@ describe('~/ide/lib/gitlab_web_ide/get_base_config', () => { }); it('return workbenchConfiguration based on gitlabUrl', async () => { - const result = await getWebIDEWorkbenchConfig(); + const result = await getWebIDEWorkbenchConfig(DEFAULT_PARAMETERS); expect(pingWorkbench).toHaveBeenCalledWith({ el: document.body, config: { - workbenchBaseUrl: `https://workbench-82f9aaae2ef4f6ffb993ca55c2a2eb.cdn.web-ide.gitlab-static.net/gitlab-web-ide-vscode-workbench-${packageJSON.version}`, + workbenchBaseUrl: `https://workbench-82f9aaae2ef4f6ffb993ca55c2a2eb.${DEFAULT_PARAMETERS.extensionHostDomain}/gitlab-web-ide-vscode-workbench-${packageJSON.version}`, gitlabUrl: 'https://gitlab.example.com', }, }); @@ -110,8 +133,8 @@ describe('~/ide/lib/gitlab_web_ide/get_base_config', () => { window.location.origin = origin; window.gon.current_username = currentUsername; - expect(await buildWorkbenchUrl()).toBe( - `https://workbench-${result}.cdn.web-ide.gitlab-static.net/gitlab-web-ide-vscode-workbench-${packageJSON.version}`, + expect(await buildWorkbenchUrl(DEFAULT_PARAMETERS)).toBe( + `https://workbench-${result}.${DEFAULT_PARAMETERS.extensionHostDomain}/gitlab-web-ide-vscode-workbench-${packageJSON.version}`, ); }, ); diff --git a/spec/helpers/ide_helper_spec.rb b/spec/helpers/ide_helper_spec.rb index b1401a3f81b39fda57bb90fcc5c17f140f802110..ddbcb2e5722370e592cf313dbb207fbdf6780fa1 100644 --- a/spec/helpers/ide_helper_spec.rb +++ b/spec/helpers/ide_helper_spec.rb @@ -105,12 +105,18 @@ it 'includes extension marketplace settings and settings context hash' do expect(WebIde::ExtensionMarketplace).to receive(:webide_extension_marketplace_settings) .with(user: user).and_return(settings) + expect(WebIde::ExtensionMarketplace).to receive(:extension_host_domain) + .and_return('web-ide.net') + expect(WebIde::ExtensionMarketplace).to receive(:extension_host_domain_changed?) + .and_return(true) actual = helper.ide_data(project: nil, fork_info: fork_info, params: params) expect(actual).to include({ 'extension-marketplace-settings' => settings.to_json, - 'settings-context-hash' => expected_settings_hash + 'settings-context-hash' => expected_settings_hash, + 'extension-host-domain' => 'web-ide.net', + 'extension-host-domain-changed' => "true" }) end end diff --git a/spec/lib/web_ide/extension_marketplace_spec.rb b/spec/lib/web_ide/extension_marketplace_spec.rb index e7ca71cad80dd8f96eb1a5077f80a03ebde481a4..d1d12d5992de1adea6783d22ae8be8611ffc95bf 100644 --- a/spec/lib/web_ide/extension_marketplace_spec.rb +++ b/spec/lib/web_ide/extension_marketplace_spec.rb @@ -97,4 +97,52 @@ it { is_expected.to match(expectation) } end end + + describe '#extension_host_domain' do + subject(:extension_host_domain) { described_class.extension_host_domain } + + context 'when vscode_extension_marketplace_extension_host_domain is set to default' do + before do + Gitlab::CurrentSettings.update!( + vscode_extension_marketplace_extension_host_domain: 'cdn.web-ide.gitlab-static.net' + ) + end + + it { is_expected.to eq('cdn.web-ide.gitlab-static.net') } + end + + context 'when vscode_extension_marketplace_extension_host_domain is set to custom domain' do + before do + Gitlab::CurrentSettings.update!( + vscode_extension_marketplace_extension_host_domain: 'custom-cdn.example.com' + ) + end + + it { is_expected.to eq('custom-cdn.example.com') } + end + end + + describe '#extension_host_domain_changed?' do + subject(:extension_host_domain_changed) { described_class.extension_host_domain_changed? } + + context 'when extension_host_domain is set to default value' do + before do + Gitlab::CurrentSettings.update!( + vscode_extension_marketplace_extension_host_domain: 'cdn.web-ide.gitlab-static.net' + ) + end + + it { is_expected.to be(false) } + end + + context 'when extension_host_domain is set to custom domain' do + before do + Gitlab::CurrentSettings.update!( + vscode_extension_marketplace_extension_host_domain: 'custom-cdn.example.com' + ) + end + + it { is_expected.to be(true) } + end + end end diff --git a/spec/models/application_setting_spec.rb b/spec/models/application_setting_spec.rb index 9b9c6b443856f89d2afcd56049a5f6677ffe3fdc..45b8854f893a7690c30432c0e78a29a8fa61fdc7 100644 --- a/spec/models/application_setting_spec.rb +++ b/spec/models/application_setting_spec.rb @@ -299,8 +299,13 @@ users_get_by_id_limit: 300, users_get_by_id_limit_allowlist: [], valid_runner_registrars: ApplicationSettingImplementation::VALID_RUNNER_REGISTRAR_TYPES, - vscode_extension_marketplace: { 'enabled' => false }, + vscode_extension_marketplace: { + 'enabled' => false, + 'extension_host_domain' => ::WebIde::ExtensionMarketplace::DEFAULT_EXTENSION_HOST_DOMAIN + }, vscode_extension_marketplace_enabled?: false, + vscode_extension_marketplace_extension_host_domain: + ::WebIde::ExtensionMarketplace::DEFAULT_EXTENSION_HOST_DOMAIN, whats_new_variant: 'all_tiers', # changed from 0 to "all_tiers" due to enum conversion wiki_asciidoc_allow_uri_includes: false, wiki_page_max_content_bytes: 5.megabytes @@ -2473,6 +2478,34 @@ def expect_invalid end end + describe '#vscode_extension_marketplace_extension_host_domain' do + context 'with valid domain' do + it { is_expected.to allow_value({ extension_host_domain: 'foo.net' }).for(:vscode_extension_marketplace) } + it { is_expected.to allow_value({ extension_host_domain: 'cdn.foo.net' }).for(:vscode_extension_marketplace) } + end + + context "with invalid domain" do + using RSpec::Parameterized::TableSyntax + + where(:domain) do + %w[ + foo + invalid domain + http://foo.com + example..com + -example.com + example.com- + .example.com + example.com. + ] + end + + with_them do + it { is_expected.not_to allow_value({ extension_host_domain: domain }).for(:vscode_extension_marketplace) } + end + end + end + describe '#static_objects_external_storage_auth_token=', :aggregate_failures do subject(:set_auth_token) { setting.static_objects_external_storage_auth_token = token } diff --git a/spec/requests/api/settings_spec.rb b/spec/requests/api/settings_spec.rb index ee5f7637b8e47a4eefacffc1ae004a2ec2064d4c..57971d29256be5f7d9a1526b152d9b601a99c2d0 100644 --- a/spec/requests/api/settings_spec.rb +++ b/spec/requests/api/settings_spec.rb @@ -1244,7 +1244,8 @@ expect(response).to have_gitlab_http_status(:ok) expect(json_response['vscode_extension_marketplace_enabled']).to eq(true) - expect(json_response['vscode_extension_marketplace']).to eq({ "enabled" => true }) + expect(json_response['vscode_extension_marketplace']) + .to eq({ "enabled" => true, "extension_host_domain" => "cdn.web-ide.gitlab-static.net" }) end end end diff --git a/spec/requests/ide_controller_spec.rb b/spec/requests/ide_controller_spec.rb index 5571d4f5debdd0a6c2b2e72b54ac8c9b27159570..b29e00c1878b8ec8c88d48e9209381e91c1e83d1 100644 --- a/spec/requests/ide_controller_spec.rb +++ b/spec/requests/ide_controller_spec.rb @@ -194,7 +194,8 @@ it 'updates the content security policy with the correct frame sources' do subject - expect(find_csp_directive('frame-src')).to include("http://www.example.com/assets/webpack/", "https://*.web-ide.gitlab-static.net/", + expect(find_csp_directive('frame-src')).to include("http://www.example.com/assets/webpack/", + "https://*.cdn.web-ide.gitlab-static.net/", ide_oauth_redirect_url, oauth_authorization_url) expect(find_csp_directive('worker-src')).to include("http://www.example.com/assets/webpack/") end diff --git a/spec/views/admin/application_settings/_extension_marketplace.html.haml_spec.rb b/spec/views/admin/application_settings/_extension_marketplace.html.haml_spec.rb index 08615494a0855939d315119b3cc90de556eb1033..222f9b111254f10cd30c72ffdd26693a0111eaf6 100644 --- a/spec/views/admin/application_settings/_extension_marketplace.html.haml_spec.rb +++ b/spec/views/admin/application_settings/_extension_marketplace.html.haml_spec.rb @@ -38,7 +38,7 @@ expected_json = { presets: expected_presets, - initialSettings: { enabled: false } + initialSettings: { enabled: false, extension_host_domain: "cdn.web-ide.gitlab-static.net" } }.to_json expect(vue_app).not_to be_nil