mirror of
https://github.com/n8n-io/n8n.git
synced 2024-12-25 04:34:06 -08:00
fix(editor): Allow disabling SSO when config request fails (#10635)
This commit is contained in:
parent
7fd0c71bdc
commit
ce39933766
203
packages/editor-ui/src/views/SettingsSso.test.ts
Normal file
203
packages/editor-ui/src/views/SettingsSso.test.ts
Normal file
|
@ -0,0 +1,203 @@
|
|||
import { createTestingPinia } from '@pinia/testing';
|
||||
import { createComponentRenderer } from '@/__tests__/render';
|
||||
import SettingsSso from './SettingsSso.vue';
|
||||
import { useSSOStore } from '@/stores/sso.store';
|
||||
import { useUIStore } from '@/stores/ui.store';
|
||||
import { within, waitFor } from '@testing-library/vue';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { mockedStore } from '@/__tests__/utils';
|
||||
|
||||
const renderView = createComponentRenderer(SettingsSso);
|
||||
|
||||
const samlConfig = {
|
||||
metadata: 'metadata dummy',
|
||||
metadataUrl:
|
||||
'https://dev-qqkrykgkoo0p63d5.eu.auth0.com/samlp/metadata/KR1cSrRrxaZT2gV8ZhPAUIUHtEY4duhN',
|
||||
entityID: 'https://n8n-tunnel.myhost.com/rest/sso/saml/metadata',
|
||||
returnUrl: 'https://n8n-tunnel.myhost.com/rest/sso/saml/acs',
|
||||
};
|
||||
|
||||
const telemetryTrack = vi.fn();
|
||||
vi.mock('@/composables/useTelemetry', () => ({
|
||||
useTelemetry: () => ({
|
||||
track: telemetryTrack,
|
||||
}),
|
||||
}));
|
||||
|
||||
const showError = vi.fn();
|
||||
vi.mock('@/composables/useToast', () => ({
|
||||
useToast: () => ({
|
||||
showError,
|
||||
}),
|
||||
}));
|
||||
|
||||
const confirmMessage = vi.fn();
|
||||
vi.mock('@/composables/useMessage', () => ({
|
||||
useMessage: () => ({
|
||||
confirm: confirmMessage,
|
||||
}),
|
||||
}));
|
||||
|
||||
describe('SettingsSso View', () => {
|
||||
beforeEach(() => {
|
||||
telemetryTrack.mockReset();
|
||||
confirmMessage.mockReset();
|
||||
showError.mockReset();
|
||||
});
|
||||
|
||||
it('should show upgrade banner when enterprise SAML is disabled', async () => {
|
||||
const pinia = createTestingPinia();
|
||||
const ssoStore = mockedStore(useSSOStore);
|
||||
ssoStore.isEnterpriseSamlEnabled = false;
|
||||
|
||||
const uiStore = useUIStore();
|
||||
|
||||
const { getByTestId } = renderView({ pinia });
|
||||
|
||||
const actionBox = getByTestId('sso-content-unlicensed');
|
||||
expect(actionBox).toBeInTheDocument();
|
||||
|
||||
await userEvent.click(await within(actionBox).findByText('See plans'));
|
||||
expect(uiStore.goToUpgrade).toHaveBeenCalledWith('sso', 'upgrade-sso');
|
||||
});
|
||||
|
||||
it('should show user SSO config', async () => {
|
||||
const pinia = createTestingPinia();
|
||||
|
||||
const ssoStore = mockedStore(useSSOStore);
|
||||
ssoStore.isEnterpriseSamlEnabled = true;
|
||||
|
||||
ssoStore.getSamlConfig.mockResolvedValue(samlConfig);
|
||||
|
||||
const { getAllByTestId } = renderView({ pinia });
|
||||
|
||||
expect(ssoStore.getSamlConfig).toHaveBeenCalledTimes(1);
|
||||
|
||||
await waitFor(async () => {
|
||||
const copyInputs = getAllByTestId('copy-input');
|
||||
expect(copyInputs[0].textContent).toContain(samlConfig.returnUrl);
|
||||
expect(copyInputs[1].textContent).toContain(samlConfig.entityID);
|
||||
});
|
||||
});
|
||||
|
||||
it('allows user to toggle SSO', async () => {
|
||||
const pinia = createTestingPinia();
|
||||
|
||||
const ssoStore = mockedStore(useSSOStore);
|
||||
ssoStore.isEnterpriseSamlEnabled = true;
|
||||
ssoStore.isSamlLoginEnabled = false;
|
||||
|
||||
ssoStore.getSamlConfig.mockResolvedValue(samlConfig);
|
||||
|
||||
const { getByTestId } = renderView({ pinia });
|
||||
|
||||
const toggle = getByTestId('sso-toggle');
|
||||
|
||||
expect(toggle.textContent).toContain('Deactivated');
|
||||
|
||||
await userEvent.click(toggle);
|
||||
expect(toggle.textContent).toContain('Activated');
|
||||
|
||||
await userEvent.click(toggle);
|
||||
expect(toggle.textContent).toContain('Deactivated');
|
||||
});
|
||||
|
||||
it("allows user to fill Identity Provider's URL", async () => {
|
||||
confirmMessage.mockResolvedValueOnce('confirm');
|
||||
|
||||
const pinia = createTestingPinia();
|
||||
const windowOpenSpy = vi.spyOn(window, 'open');
|
||||
|
||||
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.metadataUrl);
|
||||
|
||||
expect(saveButton).not.toBeDisabled();
|
||||
await userEvent.click(saveButton);
|
||||
|
||||
expect(ssoStore.saveSamlConfig).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ metadataUrl: samlConfig.metadataUrl }),
|
||||
);
|
||||
|
||||
expect(ssoStore.testSamlConfig).toHaveBeenCalled();
|
||||
expect(windowOpenSpy).toHaveBeenCalled();
|
||||
|
||||
expect(telemetryTrack).toHaveBeenCalledWith(
|
||||
expect.any(String),
|
||||
expect.objectContaining({ identity_provider: 'metadata' }),
|
||||
);
|
||||
|
||||
expect(ssoStore.getSamlConfig).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
it("allows user to fill Identity Provider's XML", async () => {
|
||||
confirmMessage.mockResolvedValueOnce('confirm');
|
||||
|
||||
const pinia = createTestingPinia();
|
||||
const windowOpenSpy = vi.spyOn(window, 'open');
|
||||
|
||||
const ssoStore = mockedStore(useSSOStore);
|
||||
ssoStore.isEnterpriseSamlEnabled = true;
|
||||
|
||||
const { getByTestId } = renderView({ pinia });
|
||||
|
||||
const saveButton = getByTestId('sso-save');
|
||||
expect(saveButton).toBeDisabled();
|
||||
|
||||
await userEvent.click(getByTestId('radio-button-xml'));
|
||||
|
||||
const xmlInput = getByTestId('sso-provider-xml');
|
||||
|
||||
expect(xmlInput).toBeVisible();
|
||||
await userEvent.type(xmlInput, samlConfig.metadata);
|
||||
|
||||
expect(saveButton).not.toBeDisabled();
|
||||
await userEvent.click(saveButton);
|
||||
|
||||
expect(ssoStore.saveSamlConfig).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ metadata: samlConfig.metadata }),
|
||||
);
|
||||
|
||||
expect(ssoStore.testSamlConfig).toHaveBeenCalled();
|
||||
expect(windowOpenSpy).toHaveBeenCalled();
|
||||
|
||||
expect(telemetryTrack).toHaveBeenCalledWith(
|
||||
expect.any(String),
|
||||
expect.objectContaining({ identity_provider: 'xml' }),
|
||||
);
|
||||
|
||||
expect(ssoStore.getSamlConfig).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
it('PAY-1812: allows user to disable SSO even if config request failed', async () => {
|
||||
const pinia = createTestingPinia();
|
||||
|
||||
const ssoStore = mockedStore(useSSOStore);
|
||||
ssoStore.isEnterpriseSamlEnabled = true;
|
||||
ssoStore.isSamlLoginEnabled = true;
|
||||
|
||||
const error = new Error('Request failed with status code 404');
|
||||
ssoStore.getSamlConfig.mockRejectedValue(error);
|
||||
|
||||
const { getByTestId } = renderView({ pinia });
|
||||
|
||||
expect(ssoStore.getSamlConfig).toHaveBeenCalledTimes(1);
|
||||
|
||||
await waitFor(async () => {
|
||||
expect(showError).toHaveBeenCalledWith(error, 'error');
|
||||
const toggle = getByTestId('sso-toggle');
|
||||
expect(toggle.textContent).toContain('Activated');
|
||||
await userEvent.click(toggle);
|
||||
expect(toggle.textContent).toContain('Deactivated');
|
||||
});
|
||||
});
|
||||
});
|
|
@ -134,6 +134,15 @@ const goToUpgrade = () => {
|
|||
void uiStore.goToUpgrade('sso', 'upgrade-sso');
|
||||
};
|
||||
|
||||
const isToggleSsoDisabled = computed(() => {
|
||||
/** Allow users to disable SSO even if config request fails */
|
||||
if (ssoStore.isSamlLoginEnabled) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return !ssoSettingsSaved.value;
|
||||
});
|
||||
|
||||
onMounted(async () => {
|
||||
if (!ssoStore.isEnterpriseSamlEnabled) {
|
||||
return;
|
||||
|
@ -162,7 +171,8 @@ onMounted(async () => {
|
|||
</template>
|
||||
<el-switch
|
||||
v-model="ssoStore.isSamlLoginEnabled"
|
||||
:disabled="!ssoSettingsSaved"
|
||||
data-test-id="sso-toggle"
|
||||
:disabled="isToggleSsoDisabled"
|
||||
:class="$style.switch"
|
||||
:inactive-text="ssoActivatedLabel"
|
||||
/>
|
||||
|
@ -205,11 +215,18 @@ onMounted(async () => {
|
|||
name="metadataUrl"
|
||||
size="large"
|
||||
:placeholder="i18n.baseText('settings.sso.settings.ips.url.placeholder')"
|
||||
data-test-id="sso-provider-url"
|
||||
/>
|
||||
<small>{{ i18n.baseText('settings.sso.settings.ips.url.help') }}</small>
|
||||
</div>
|
||||
<div v-show="ipsType === IdentityProviderSettingsType.XML">
|
||||
<n8n-input v-model="metadata" type="textarea" name="metadata" :rows="4" />
|
||||
<n8n-input
|
||||
v-model="metadata"
|
||||
type="textarea"
|
||||
name="metadata"
|
||||
:rows="4"
|
||||
data-test-id="sso-provider-xml"
|
||||
/>
|
||||
<small>{{ i18n.baseText('settings.sso.settings.ips.xml.help') }}</small>
|
||||
</div>
|
||||
</div>
|
||||
|
|
Loading…
Reference in a new issue