diff --git a/packages/frontend/editor-ui/src/plugins/i18n/locales/en.json b/packages/frontend/editor-ui/src/plugins/i18n/locales/en.json index cc9601f6b8..38b759e0f4 100644 --- a/packages/frontend/editor-ui/src/plugins/i18n/locales/en.json +++ b/packages/frontend/editor-ui/src/plugins/i18n/locales/en.json @@ -2732,6 +2732,7 @@ "settings.sso.settings.ips.xml.help": "Paste here the raw Metadata XML provided by your Identity Provider", "settings.sso.settings.ips.url.help": "Paste here the Internet Provider Metadata URL", "settings.sso.settings.ips.url.placeholder": "e.g. https://samltest.id/saml/idp", + "settings.sso.settings.ips.url.invalid": "The Internet Provider Metadata URL is not valid", "settings.sso.settings.ips.options.url": "Metadata URL", "settings.sso.settings.ips.options.xml": "XML", "settings.sso.settings.test": "Test settings", diff --git a/packages/frontend/editor-ui/src/views/SettingsSso.test.ts b/packages/frontend/editor-ui/src/views/SettingsSso.test.ts index bbd927a1e3..d9050133aa 100644 --- a/packages/frontend/editor-ui/src/views/SettingsSso.test.ts +++ b/packages/frontend/editor-ui/src/views/SettingsSso.test.ts @@ -194,7 +194,65 @@ describe('SettingsSso View', () => { expect(ssoStore.getSamlConfig).toHaveBeenCalledTimes(2); }); - it('PAY-1812: allows user to disable SSO even if config request failed', async () => { + it('should validate the url before setting the saml config', async () => { + const pinia = createTestingPinia(); + + const ssoStore = mockedStore(useSSOStore); + ssoStore.isEnterpriseSamlEnabled = true; + + const { getByTestId } = renderView({ pinia }); + + const saveButton = getByTestId('sso-save'); + expect(saveButton).toBeDisabled(); + + const urlinput = getByTestId('sso-provider-url'); + + expect(urlinput).toBeVisible(); + await userEvent.type(urlinput, samlConfig.metadata!); + + expect(saveButton).not.toBeDisabled(); + await userEvent.click(saveButton); + + expect(showError).toHaveBeenCalled(); + expect(ssoStore.saveSamlConfig).not.toHaveBeenCalled(); + + expect(ssoStore.testSamlConfig).not.toHaveBeenCalled(); + + expect(telemetryTrack).not.toHaveBeenCalled(); + + expect(ssoStore.getSamlConfig).toHaveBeenCalledTimes(2); + }); + + it('should ensure the url does not support invalid protocols like mailto', async () => { + const pinia = createTestingPinia(); + + const ssoStore = mockedStore(useSSOStore); + ssoStore.isEnterpriseSamlEnabled = true; + + const { getByTestId } = renderView({ pinia }); + + const saveButton = getByTestId('sso-save'); + expect(saveButton).toBeDisabled(); + + const urlinput = getByTestId('sso-provider-url'); + + expect(urlinput).toBeVisible(); + await userEvent.type(urlinput, 'mailto://test@example.com'); + + expect(saveButton).not.toBeDisabled(); + await userEvent.click(saveButton); + + expect(showError).toHaveBeenCalled(); + expect(ssoStore.saveSamlConfig).not.toHaveBeenCalled(); + + expect(ssoStore.testSamlConfig).not.toHaveBeenCalled(); + + expect(telemetryTrack).not.toHaveBeenCalled(); + + expect(ssoStore.getSamlConfig).toHaveBeenCalledTimes(2); + }); + + it('allows user to disable SSO even if config request failed', async () => { const pinia = createTestingPinia(); const ssoStore = mockedStore(useSSOStore); diff --git a/packages/frontend/editor-ui/src/views/SettingsSso.vue b/packages/frontend/editor-ui/src/views/SettingsSso.vue index 23321f0639..35c81fa4d6 100644 --- a/packages/frontend/editor-ui/src/views/SettingsSso.vue +++ b/packages/frontend/editor-ui/src/views/SettingsSso.vue @@ -86,6 +86,7 @@ const getSamlConfig = async () => { const onSave = async () => { try { + validateInput(); const config = ipsType.value === IdentityProviderSettingsType.URL ? { metadataUrl: metadataUrl.value } @@ -132,6 +133,25 @@ const onTest = async () => { } }; +const validateInput = () => { + if (ipsType.value === IdentityProviderSettingsType.URL) { + // In case the user wants to set the metadata url we want to be sure that + // the provided url is at least a valid http, https url. + try { + const parsedUrl = new URL(metadataUrl.value); + // We allow http and https URLs for now, because we want to avoid a theoretical breaking + // change, this should be restricted to only allow https when switching to V2. + if (parsedUrl.protocol !== 'https:' && parsedUrl.protocol !== 'http:') { + // The content of this error is never seen by the user, because the catch clause + // below catches it and translates it to a more general error message. + throw new Error('The provided protocol is not supported'); + } + } catch (error) { + throw new Error(i18n.baseText('settings.sso.settings.ips.url.invalid')); + } + } +}; + const goToUpgrade = () => { void pageRedirectionHelper.goToUpgrade('sso', 'upgrade-sso'); };