diff --git a/packages/mapviewer/src/router/storeSync/BaseUrlOverrideParamConfig.class.js b/packages/mapviewer/src/router/storeSync/BaseUrlOverrideParamConfig.class.js index c937b11640..27c8a32350 100644 --- a/packages/mapviewer/src/router/storeSync/BaseUrlOverrideParamConfig.class.js +++ b/packages/mapviewer/src/router/storeSync/BaseUrlOverrideParamConfig.class.js @@ -6,9 +6,21 @@ import { import AbstractParamConfig from '@/router/storeSync/abstractParamConfig.class' import { isValidUrl } from '@/utils/utils' +// https://sys-{wms,wmts,api3}.{dev,int}.bgdi.ch/ or http://localhost{:port_number} +const ALLOWED_BGDI_URL = /^https:\/\/sys-(wms|wmts|api3)\.(dev|int)\.bgdi\.ch(\/[^?]*)?(\?.*)?$/ +const ALLOWED_LOCALHOST_URL = /^http:\/\/localhost(:\d{2,6})?(\/[^?]*)?(\?.*)?$/ + +/** + * To protect against XSS attacks, we only allow the override of the base URL if the URL is + * whitelisted. + */ +export function isBaseUrlOverrideAllowed(url) { + return ALLOWED_BGDI_URL.test(url) || ALLOWED_LOCALHOST_URL.test(url) +} + export default function createBaseUrlOverrideParamConfig({ urlParamName, baseUrlPropertyName }) { function dispatchBaseUrlOverride(to, store, urlParamValue) { - if (isValidUrl(urlParamValue)) { + if (isValidUrl(urlParamValue) && isBaseUrlOverrideAllowed(urlParamValue)) { setBaseUrlOverrides(baseUrlPropertyName, urlParamValue) } else { setBaseUrlOverrides(baseUrlPropertyName, null) diff --git a/packages/mapviewer/src/router/storeSync/__tests__/BaseUrlOverrideParamConfig.spec.js b/packages/mapviewer/src/router/storeSync/__tests__/BaseUrlOverrideParamConfig.spec.js new file mode 100644 index 0000000000..bbf28ea26f --- /dev/null +++ b/packages/mapviewer/src/router/storeSync/__tests__/BaseUrlOverrideParamConfig.spec.js @@ -0,0 +1,43 @@ +import { describe, expect, it } from 'vitest' + +import { isBaseUrlOverrideAllowed } from '../BaseUrlOverrideParamConfig.class' + +const services = ['wms', 'wmts', 'api3'] + +describe('isBaseUrlOverrideAllowed', () => { + services.forEach((service) => { + const devUrl = `https://sys-${service}.dev.bgdi.ch` + const intUrl = `https://sys-${service}.int.bgdi.ch` + + it(`allows a PR preview link as override for ${service}`, () => { + expect( + isBaseUrlOverrideAllowed(`${devUrl}/some-pr-path`), + 'DEV PR preview link was not allowed' + ).toBe(true) + expect( + isBaseUrlOverrideAllowed(`${intUrl}/some-pr-path`), + 'INT PR preview link was not allowed' + ).toBe(true) + }) + + it(`allows the use of the DEV or INT staging as override for ${service}`, () => { + expect(isBaseUrlOverrideAllowed(devUrl)).toBe(true) + expect(isBaseUrlOverrideAllowed(intUrl)).toBe(true) + }) + }) + + it('allows the use of localhost URLs as override', () => { + expect(isBaseUrlOverrideAllowed('http://localhost')).toBe(true) + expect(isBaseUrlOverrideAllowed('http://localhost:1234')).toBe(true) + expect(isBaseUrlOverrideAllowed('http://localhost:5678/some-path')).toBe(true) + }) + + it('should return false for a URL not matching either allowed pattern', () => { + expect(isBaseUrlOverrideAllowed('https://unallowed.example.com')).toBe(false) + expect(isBaseUrlOverrideAllowed('https://sys-wrongservice.dev.bgdi.ch')).toBe(false) + }) + + it('should return false for an invalid URL', () => { + expect(isBaseUrlOverrideAllowed('not-a-valid-url')).toBe(false) + }) +})