chore(editor): Validate SSO metadata url (#15206)

This commit is contained in:
Andreas Fitzek
2025-05-08 15:46:26 +02:00
committed by GitHub
parent be72f736ac
commit c1229e007e
3 changed files with 80 additions and 1 deletions

View File

@@ -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",

View File

@@ -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);

View File

@@ -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');
};