mirror of
https://github.com/logto-io/logto.git
synced 2024-12-16 20:26:19 -05:00
feat(core,test): update sso connector util functions, APIs and integration tests (#4807)
This commit is contained in:
parent
40a5a18d90
commit
7fe41a0037
13 changed files with 214 additions and 183 deletions
|
@ -25,7 +25,7 @@
|
|||
"test:report": "codecov -F core"
|
||||
},
|
||||
"dependencies": {
|
||||
"@authenio/samlify-xsd-schema-validator": "^1.0.5",
|
||||
"@authenio/samlify-node-xmllint": "^2.0.0",
|
||||
"@aws-sdk/client-s3": "^3.315.0",
|
||||
"@azure/storage-blob": "^12.13.0",
|
||||
"@google-cloud/storage": "^7.3.0",
|
||||
|
|
|
@ -1,3 +0,0 @@
|
|||
declare module '@authenio/samlify-xsd-schema-validator' {
|
||||
export declare const validate: (xml: string) => Promise<void>;
|
||||
}
|
|
@ -8,6 +8,7 @@ import { z } from 'zod';
|
|||
import RequestError from '#src/errors/RequestError/index.js';
|
||||
import { type WithLogContext } from '#src/middleware/koa-audit-log.js';
|
||||
import koaGuard from '#src/middleware/koa-guard.js';
|
||||
import { OidcSsoConnector } from '#src/sso/OidcSsoConnector/index.js';
|
||||
import { ssoConnectorFactories } from '#src/sso/index.js';
|
||||
import type TenantContext from '#src/tenants/TenantContext.js';
|
||||
import assertThat from '#src/utils/assert-that.js';
|
||||
|
@ -22,6 +23,7 @@ export default function singleSignOnRoutes<T extends IRouterParamContext>(
|
|||
tenant: TenantContext
|
||||
) {
|
||||
const {
|
||||
id: tenantId,
|
||||
provider,
|
||||
libraries: { ssoConnector },
|
||||
} = tenant;
|
||||
|
@ -74,20 +76,20 @@ export default function singleSignOnRoutes<T extends IRouterParamContext>(
|
|||
|
||||
try {
|
||||
// Will throw ConnectorError if the config is invalid
|
||||
const connectorInstance = new ssoConnectorFactories[connectorData.providerName].constructor(
|
||||
connectorData
|
||||
);
|
||||
const factory = ssoConnectorFactories[connectorData.providerName];
|
||||
const connectorInstance = new factory.constructor(connectorData, tenantId);
|
||||
|
||||
// Will throw ConnectorError if failed to fetch the provider's config
|
||||
const redirectTo = await connectorInstance.getAuthorizationUrl(
|
||||
{ state, redirectUri },
|
||||
async (connectorSession: ConnectorSession) =>
|
||||
assignConnectorSessionResult(ctx, provider, connectorSession)
|
||||
);
|
||||
if (connectorInstance instanceof OidcSsoConnector) {
|
||||
const redirectTo = await connectorInstance.getAuthorizationUrl(
|
||||
{ state, redirectUri },
|
||||
async (connectorSession: ConnectorSession) =>
|
||||
assignConnectorSessionResult(ctx, provider, connectorSession)
|
||||
);
|
||||
|
||||
// TODO: Add SAML connector support later
|
||||
ctx.body = { redirectTo };
|
||||
}
|
||||
|
||||
ctx.body = { redirectTo };
|
||||
// TODO: Add SAML `getSingleSignOnUrl` here
|
||||
} catch (error: unknown) {
|
||||
// Catch ConnectorError and re-throw as 500 RequestError
|
||||
if (error instanceof ConnectorError) {
|
||||
|
|
|
@ -5,19 +5,19 @@ import { z } from 'zod';
|
|||
|
||||
import RequestError from '#src/errors/RequestError/index.js';
|
||||
import koaGuard from '#src/middleware/koa-guard.js';
|
||||
import { ssoConnectorFactories, standardSsoConnectorProviders } from '#src/sso/index.js';
|
||||
import { isSupportedSsoProvider, isSupportedSsoConnector } from '#src/sso/utils.js';
|
||||
import { tableToPathname } from '#src/utils/SchemaRouter.js';
|
||||
|
||||
import { type AuthedRouter, type RouterInitArgs } from '../types.js';
|
||||
|
||||
import {
|
||||
connectorFactoriesResponseGuard,
|
||||
type ConnectorFactoryDetail,
|
||||
ssoConnectorCreateGuard,
|
||||
ssoConnectorWithProviderConfigGuard,
|
||||
ssoConnectorPatchGuard,
|
||||
} from './type.js';
|
||||
} from '#src/routes/sso-connector/type.js';
|
||||
import { ssoConnectorFactories, standardSsoConnectorProviders } from '#src/sso/index.js';
|
||||
import { isSupportedSsoProvider, isSupportedSsoConnector } from '#src/sso/utils.js';
|
||||
import { tableToPathname } from '#src/utils/SchemaRouter.js';
|
||||
|
||||
import { type AuthedRouter, type RouterInitArgs } from '../types.js';
|
||||
|
||||
import {
|
||||
parseFactoryDetail,
|
||||
parseConnectorConfig,
|
||||
|
@ -28,13 +28,15 @@ export default function singleSignOnRoutes<T extends AuthedRouter>(...args: Rout
|
|||
const [
|
||||
router,
|
||||
{
|
||||
libraries: { ssoConnector: ssoConnectorLibrary },
|
||||
id: tenantId,
|
||||
queries: { ssoConnectors },
|
||||
libraries: {
|
||||
ssoConnector: { getSsoConnectorById, getSsoConnectors },
|
||||
},
|
||||
},
|
||||
] = args;
|
||||
|
||||
const pathname = `/${tableToPathname(SsoConnectors.table)}`;
|
||||
const { getSsoConnectorById, getSsoConnectors } = ssoConnectorLibrary;
|
||||
|
||||
/*
|
||||
Get all supported single sign on connector factory details
|
||||
|
@ -124,7 +126,7 @@ export default function singleSignOnRoutes<T extends AuthedRouter>(...args: Rout
|
|||
|
||||
// Fetch provider details for each connector
|
||||
const connectorsWithProviderDetails = await Promise.all(
|
||||
connectors.map(async (connector) => fetchConnectorProviderDetails(connector))
|
||||
connectors.map(async (connector) => fetchConnectorProviderDetails(connector, tenantId))
|
||||
);
|
||||
|
||||
ctx.body = connectorsWithProviderDetails;
|
||||
|
@ -147,7 +149,7 @@ export default function singleSignOnRoutes<T extends AuthedRouter>(...args: Rout
|
|||
const connector = await getSsoConnectorById(id);
|
||||
|
||||
// Fetch provider details for the connector
|
||||
const connectorWithProviderDetails = await fetchConnectorProviderDetails(connector);
|
||||
const connectorWithProviderDetails = await fetchConnectorProviderDetails(connector, tenantId);
|
||||
|
||||
ctx.body = connectorWithProviderDetails;
|
||||
|
||||
|
@ -208,7 +210,7 @@ export default function singleSignOnRoutes<T extends AuthedRouter>(...args: Rout
|
|||
new RequestError({ code: 'connector.not_found', status: 404 })
|
||||
);
|
||||
|
||||
const connectorWithProviderDetails = await fetchConnectorProviderDetails(connector);
|
||||
const connectorWithProviderDetails = await fetchConnectorProviderDetails(connector, tenantId);
|
||||
|
||||
ctx.body = connectorWithProviderDetails;
|
||||
|
||||
|
@ -248,7 +250,7 @@ export default function singleSignOnRoutes<T extends AuthedRouter>(...args: Rout
|
|||
);
|
||||
|
||||
// Fetch provider details for the connector
|
||||
const connectorWithProviderDetails = await fetchConnectorProviderDetails(connector);
|
||||
const connectorWithProviderDetails = await fetchConnectorProviderDetails(connector, tenantId);
|
||||
|
||||
ctx.body = connectorWithProviderDetails;
|
||||
|
||||
|
|
|
@ -14,6 +14,8 @@ await mockEsmWithActual('#src/sso/OidcConnector/utils.js', () => ({
|
|||
const { ssoConnectorFactories } = await import('#src/sso/index.js');
|
||||
const { parseFactoryDetail, fetchConnectorProviderDetails } = await import('./utils.js');
|
||||
|
||||
const mockTenantId = 'mock_tenant_id';
|
||||
|
||||
describe('parseFactoryDetail', () => {
|
||||
it.each(Object.values(SsoProviderName))('should return correct detail for %s', (providerName) => {
|
||||
const { logo, description } = ssoConnectorFactories[providerName];
|
||||
|
@ -43,16 +45,15 @@ describe('parseFactoryDetail', () => {
|
|||
|
||||
describe('fetchConnectorProviderDetails', () => {
|
||||
it('providerConfig should be undefined if connector config is invalid', async () => {
|
||||
const connector = {
|
||||
...mockSsoConnector,
|
||||
config: { clientId: 'foo' },
|
||||
};
|
||||
const result = await fetchConnectorProviderDetails(connector);
|
||||
const connector = { ...mockSsoConnector, config: { clientId: 'foo' } };
|
||||
const result = await fetchConnectorProviderDetails(connector, mockTenantId);
|
||||
|
||||
expect(result).toEqual({
|
||||
...connector,
|
||||
providerLogo: ssoConnectorFactories[connector.providerName].logo,
|
||||
});
|
||||
expect(result).toMatchObject(
|
||||
expect.objectContaining({
|
||||
...connector,
|
||||
providerLogo: ssoConnectorFactories[connector.providerName].logo,
|
||||
})
|
||||
);
|
||||
|
||||
expect(fetchOidcConfig).not.toBeCalled();
|
||||
});
|
||||
|
@ -64,12 +65,14 @@ describe('fetchConnectorProviderDetails', () => {
|
|||
};
|
||||
|
||||
fetchOidcConfig.mockRejectedValueOnce(new Error('mock-error'));
|
||||
const result = await fetchConnectorProviderDetails(connector);
|
||||
const result = await fetchConnectorProviderDetails(connector, mockTenantId);
|
||||
|
||||
expect(result).toEqual({
|
||||
...connector,
|
||||
providerLogo: ssoConnectorFactories[connector.providerName].logo,
|
||||
});
|
||||
expect(result).toMatchObject(
|
||||
expect.objectContaining({
|
||||
...connector,
|
||||
providerLogo: ssoConnectorFactories[connector.providerName].logo,
|
||||
})
|
||||
);
|
||||
|
||||
expect(fetchOidcConfig).toBeCalledWith(connector.config.issuer);
|
||||
});
|
||||
|
@ -81,16 +84,18 @@ describe('fetchConnectorProviderDetails', () => {
|
|||
};
|
||||
|
||||
fetchOidcConfig.mockResolvedValueOnce({ tokenEndpoint: 'http://example.com/token' });
|
||||
const result = await fetchConnectorProviderDetails(connector);
|
||||
const result = await fetchConnectorProviderDetails(connector, mockTenantId);
|
||||
|
||||
expect(result).toEqual({
|
||||
...connector,
|
||||
providerLogo: ssoConnectorFactories[connector.providerName].logo,
|
||||
providerConfig: {
|
||||
...connector.config,
|
||||
scope: 'openid', // Default scope
|
||||
tokenEndpoint: 'http://example.com/token',
|
||||
},
|
||||
});
|
||||
expect(result).toMatchObject(
|
||||
expect.objectContaining({
|
||||
...connector,
|
||||
providerLogo: ssoConnectorFactories[connector.providerName].logo,
|
||||
providerConfig: {
|
||||
...connector.config,
|
||||
scope: 'openid', // Default scope
|
||||
tokenEndpoint: 'http://example.com/token',
|
||||
},
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -4,7 +4,7 @@ import { conditional, trySafe } from '@silverhand/essentials';
|
|||
|
||||
import RequestError from '#src/errors/RequestError/index.js';
|
||||
import { type SingleSignOnFactory, ssoConnectorFactories } from '#src/sso/index.js';
|
||||
import { type SsoProviderName, type SupportedSsoConnector } from '#src/sso/types/index.js';
|
||||
import { type SupportedSsoConnector, type SsoProviderName } from '#src/sso/types/index.js';
|
||||
|
||||
import { type SsoConnectorWithProviderConfig } from './type.js';
|
||||
|
||||
|
@ -57,14 +57,15 @@ export const parseConnectorConfig = (
|
|||
Return undefined if failed to fetch or parse the config.
|
||||
*/
|
||||
export const fetchConnectorProviderDetails = async (
|
||||
connector: SupportedSsoConnector
|
||||
connector: SupportedSsoConnector,
|
||||
tenantId: string
|
||||
): Promise<SsoConnectorWithProviderConfig> => {
|
||||
const { providerName } = connector;
|
||||
|
||||
const { logo, constructor } = ssoConnectorFactories[providerName];
|
||||
|
||||
const providerConfig = await trySafe(async () => {
|
||||
const instance = new constructor(connector);
|
||||
const instance = new constructor(connector, tenantId);
|
||||
return instance.getConfig();
|
||||
});
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import * as validator from '@authenio/samlify-xsd-schema-validator';
|
||||
import * as validator from '@authenio/samlify-node-xmllint';
|
||||
import { ConnectorError, ConnectorErrorCodes, socialUserInfoGuard } from '@logto/connector-kit';
|
||||
import { type Optional, conditional } from '@silverhand/essentials';
|
||||
import { got } from 'got';
|
||||
|
|
|
@ -14,15 +14,24 @@ describe('SamlSsoConnector', () => {
|
|||
expect(samlSsoConnectorFactory.configGuard).toBeDefined();
|
||||
});
|
||||
|
||||
it('constructor should work properly', () => {
|
||||
// eslint-disable-next-line unicorn/consistent-function-scoping
|
||||
const createSamlSsoConnector = () =>
|
||||
new samlSsoConnectorFactory.constructor(mockSsoConnector, 'default_tenant');
|
||||
|
||||
expect(createSamlSsoConnector).not.toThrow();
|
||||
});
|
||||
|
||||
it('constructor should throw error if config is invalid', () => {
|
||||
const result = samlSsoConnectorFactory.configGuard.safeParse(mockSsoConnector.config);
|
||||
const temporaryMockSsoConnector = { ...mockSsoConnector, config: { metadata: 123 } };
|
||||
const result = samlSsoConnectorFactory.configGuard.safeParse(temporaryMockSsoConnector.config);
|
||||
|
||||
if (result.success) {
|
||||
throw new Error('Invalid config');
|
||||
}
|
||||
|
||||
const createSamlSsoConnector = () => {
|
||||
return new samlSsoConnectorFactory.constructor(mockSsoConnector, 'http://localhost:3001/api');
|
||||
return new samlSsoConnectorFactory.constructor(temporaryMockSsoConnector, 'default_tenant');
|
||||
};
|
||||
|
||||
expect(createSamlSsoConnector).toThrow(
|
||||
|
|
|
@ -14,7 +14,6 @@ import { samlConnectorConfigGuard } from '../types/saml.js';
|
|||
* @property data The SAML connector data from the database
|
||||
*
|
||||
* @method getConfig Get parsed SAML config along with it's metadata. Throws error if config is invalid.
|
||||
* @method getAuthorizationUrl Get SAML auth URL.
|
||||
* @method getUserInfo Get social user info.
|
||||
*/
|
||||
export class SamlSsoConnector extends SamlConnector implements SingleSignOn {
|
||||
|
@ -50,17 +49,6 @@ export class SamlSsoConnector extends SamlConnector implements SingleSignOn {
|
|||
async getUserInfo(assertion: Record<string, unknown>) {
|
||||
return this.parseSamlAssertion(assertion);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get SAML auth URL.
|
||||
*
|
||||
* @param jti The current session id.
|
||||
*
|
||||
* @returns The SAML auth URL.
|
||||
*/
|
||||
async getAuthorizationUrl(jti: string) {
|
||||
return this.getSingleSignOnUrl(jti);
|
||||
}
|
||||
}
|
||||
|
||||
export const samlSsoConnectorFactory: SingleSignOnFactory<SsoProviderName.SAML> = {
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -4,6 +4,7 @@ import { logtoConsoleUrl, logtoUrl, logtoCloudUrl } from '#src/constants.js';
|
|||
|
||||
const api = got.extend({
|
||||
prefixUrl: new URL('/api', logtoUrl),
|
||||
timeout: { response: 5000 }, // The default is 60s which is way too long for tests.
|
||||
});
|
||||
|
||||
export default api;
|
||||
|
|
|
@ -1,5 +1,9 @@
|
|||
import { HTTPError } from 'got';
|
||||
|
||||
import {
|
||||
providerNames,
|
||||
partialConfigAndProviderNames,
|
||||
} from '#src/__mocks__/sso-connectors-mock.js';
|
||||
import {
|
||||
getSsoConnectorFactories,
|
||||
createSsoConnector,
|
||||
|
@ -9,9 +13,6 @@ import {
|
|||
patchSsoConnectorById,
|
||||
patchSsoConnectorConfigById,
|
||||
} from '#src/api/sso-connector.js';
|
||||
import { logtoUrl } from '#src/constants.js';
|
||||
|
||||
const logtoIssuer = `${logtoUrl}/oidc`;
|
||||
|
||||
describe('sso-connector library', () => {
|
||||
it('should return sso-connector-factories', async () => {
|
||||
|
@ -20,7 +21,13 @@ describe('sso-connector library', () => {
|
|||
expect(response).toHaveProperty('standardConnectors');
|
||||
expect(response).toHaveProperty('providerConnectors');
|
||||
|
||||
expect(response.standardConnectors.length).toBeGreaterThan(0);
|
||||
expect(response.standardConnectors.length).toBe(2);
|
||||
expect(
|
||||
response.standardConnectors.find(({ providerName }) => providerName === 'OIDC')
|
||||
).toBeDefined();
|
||||
expect(
|
||||
response.standardConnectors.find(({ providerName }) => providerName === 'SAML')
|
||||
).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -50,14 +57,14 @@ describe('post sso-connectors', () => {
|
|||
).rejects.toThrow(HTTPError);
|
||||
});
|
||||
|
||||
it('should create a new sso connector', async () => {
|
||||
it.each(providerNames)('should create a new sso connector', async (providerName) => {
|
||||
const response = await createSsoConnector({
|
||||
providerName: 'OIDC',
|
||||
providerName,
|
||||
connectorName: 'test',
|
||||
});
|
||||
|
||||
expect(response).toHaveProperty('id');
|
||||
expect(response).toHaveProperty('providerName', 'OIDC');
|
||||
expect(response).toHaveProperty('providerName', providerName);
|
||||
expect(response).toHaveProperty('connectorName', 'test');
|
||||
expect(response).toHaveProperty('config', {});
|
||||
expect(response).toHaveProperty('domains', []);
|
||||
|
@ -67,48 +74,49 @@ describe('post sso-connectors', () => {
|
|||
await deleteSsoConnectorById(response.id);
|
||||
});
|
||||
|
||||
it('should throw if invalid config is provided', async () => {
|
||||
it.each(providerNames)('should throw if invalid config is provided', async (providerName) => {
|
||||
await expect(
|
||||
createSsoConnector({
|
||||
providerName: 'OIDC',
|
||||
providerName,
|
||||
connectorName: 'test',
|
||||
config: {
|
||||
issuer: 23,
|
||||
entityId: 123,
|
||||
},
|
||||
})
|
||||
).rejects.toThrow(HTTPError);
|
||||
});
|
||||
|
||||
it('should create a new sso connector with partial configs', async () => {
|
||||
const data = {
|
||||
providerName: 'OIDC',
|
||||
connectorName: 'test',
|
||||
config: {
|
||||
clientId: 'foo',
|
||||
issuer: 'https://test.com',
|
||||
},
|
||||
domains: ['test.com'],
|
||||
ssoOnly: true,
|
||||
};
|
||||
it.each(partialConfigAndProviderNames)(
|
||||
'should create a new sso connector with partial configs',
|
||||
async ({ providerName, config }) => {
|
||||
const data = {
|
||||
providerName,
|
||||
connectorName: 'test',
|
||||
config,
|
||||
domains: ['test.com'],
|
||||
ssoOnly: true,
|
||||
};
|
||||
|
||||
const response = await createSsoConnector(data);
|
||||
const response = await createSsoConnector(data);
|
||||
|
||||
expect(response).toHaveProperty('id');
|
||||
expect(response).toHaveProperty('providerName', 'OIDC');
|
||||
expect(response).toHaveProperty('connectorName', 'test');
|
||||
expect(response).toHaveProperty('config', data.config);
|
||||
expect(response).toHaveProperty('domains', data.domains);
|
||||
expect(response).toHaveProperty('ssoOnly', data.ssoOnly);
|
||||
expect(response).toHaveProperty('syncProfile', false);
|
||||
expect(response).toHaveProperty('id');
|
||||
expect(response).toHaveProperty('providerName', providerName);
|
||||
expect(response).toHaveProperty('connectorName', 'test');
|
||||
expect(response).toHaveProperty('config', data.config);
|
||||
expect(response).toHaveProperty('domains', data.domains);
|
||||
expect(response).toHaveProperty('ssoOnly', data.ssoOnly);
|
||||
expect(response).toHaveProperty('syncProfile', false);
|
||||
|
||||
await deleteSsoConnectorById(response.id);
|
||||
});
|
||||
await deleteSsoConnectorById(response.id);
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
describe('get sso-connectors', () => {
|
||||
it('should return sso connectors', async () => {
|
||||
it.each(providerNames)('should return sso connectors', async (providerName) => {
|
||||
const { id } = await createSsoConnector({
|
||||
providerName: 'OIDC',
|
||||
providerName,
|
||||
connectorName: 'test',
|
||||
});
|
||||
|
||||
|
@ -120,8 +128,11 @@ describe('get sso-connectors', () => {
|
|||
expect(connector).toBeDefined();
|
||||
expect(connector?.providerLogo).toBeDefined();
|
||||
|
||||
// Invalid config
|
||||
expect(connector?.providerConfig).toBeUndefined();
|
||||
// Empty config object is a valid SAML config.
|
||||
if (providerName === 'OIDC') {
|
||||
// Invalid config
|
||||
expect(connector?.providerConfig).toBeUndefined();
|
||||
}
|
||||
|
||||
await deleteSsoConnectorById(id);
|
||||
});
|
||||
|
@ -132,16 +143,16 @@ describe('get sso-connector by id', () => {
|
|||
await expect(getSsoConnectorById('invalid-id')).rejects.toThrow(HTTPError);
|
||||
});
|
||||
|
||||
it('should return sso connector', async () => {
|
||||
it.each(providerNames)('should return sso connector', async (providerName) => {
|
||||
const { id } = await createSsoConnector({
|
||||
providerName: 'OIDC',
|
||||
providerName,
|
||||
connectorName: 'integration_test connector',
|
||||
});
|
||||
|
||||
const connector = await getSsoConnectorById(id);
|
||||
|
||||
expect(connector).toHaveProperty('id', id);
|
||||
expect(connector).toHaveProperty('providerName', 'OIDC');
|
||||
expect(connector).toHaveProperty('providerName', providerName);
|
||||
expect(connector).toHaveProperty('connectorName', 'integration_test connector');
|
||||
expect(connector).toHaveProperty('config', {});
|
||||
expect(connector).toHaveProperty('domains', []);
|
||||
|
@ -157,9 +168,9 @@ describe('delete sso-connector by id', () => {
|
|||
await expect(getSsoConnectorById('invalid-id')).rejects.toThrow(HTTPError);
|
||||
});
|
||||
|
||||
it('should delete sso connector', async () => {
|
||||
it.each(providerNames)('should delete sso connector', async (providerName) => {
|
||||
const { id } = await createSsoConnector({
|
||||
providerName: 'OIDC',
|
||||
providerName,
|
||||
connectorName: 'integration_test connector',
|
||||
});
|
||||
|
||||
|
@ -178,9 +189,9 @@ describe('patch sso-connector by id', () => {
|
|||
);
|
||||
});
|
||||
|
||||
it('should patch sso connector without config', async () => {
|
||||
it.each(providerNames)('should patch sso connector without config', async (providerName) => {
|
||||
const { id } = await createSsoConnector({
|
||||
providerName: 'OIDC',
|
||||
providerName,
|
||||
connectorName: 'integration_test connector',
|
||||
});
|
||||
|
||||
|
@ -191,7 +202,7 @@ describe('patch sso-connector by id', () => {
|
|||
});
|
||||
|
||||
expect(connector).toHaveProperty('id', id);
|
||||
expect(connector).toHaveProperty('providerName', 'OIDC');
|
||||
expect(connector).toHaveProperty('providerName', providerName);
|
||||
expect(connector).toHaveProperty('connectorName', 'integration_test connector updated');
|
||||
expect(connector).toHaveProperty('config', {});
|
||||
expect(connector).toHaveProperty('domains', ['test.com']);
|
||||
|
@ -201,9 +212,9 @@ describe('patch sso-connector by id', () => {
|
|||
await deleteSsoConnectorById(id);
|
||||
});
|
||||
|
||||
it('should directly return if no changes are made', async () => {
|
||||
it.each(providerNames)('should directly return if no changes are made', async (providerName) => {
|
||||
const { id } = await createSsoConnector({
|
||||
providerName: 'OIDC',
|
||||
providerName,
|
||||
connectorName: 'integration_test connector',
|
||||
});
|
||||
|
||||
|
@ -212,7 +223,7 @@ describe('patch sso-connector by id', () => {
|
|||
});
|
||||
|
||||
expect(connector).toHaveProperty('id', id);
|
||||
expect(connector).toHaveProperty('providerName', 'OIDC');
|
||||
expect(connector).toHaveProperty('providerName', providerName);
|
||||
expect(connector).toHaveProperty('connectorName', 'integration_test connector');
|
||||
expect(connector).toHaveProperty('config', {});
|
||||
expect(connector).toHaveProperty('domains', []);
|
||||
|
@ -222,17 +233,17 @@ describe('patch sso-connector by id', () => {
|
|||
await deleteSsoConnectorById(id);
|
||||
});
|
||||
|
||||
it('should throw if invalid config is provided', async () => {
|
||||
it.each(providerNames)('should throw if invalid config is provided', async (providerName) => {
|
||||
const { id } = await createSsoConnector({
|
||||
providerName: 'OIDC',
|
||||
providerName,
|
||||
connectorName: 'integration_test connector',
|
||||
});
|
||||
|
||||
await expect(
|
||||
patchSsoConnectorById(id, {
|
||||
config: {
|
||||
clientId: 'foo',
|
||||
issuer: logtoIssuer,
|
||||
issuer: 23,
|
||||
entityId: 123,
|
||||
},
|
||||
})
|
||||
).rejects.toThrow(HTTPError);
|
||||
|
@ -240,36 +251,29 @@ describe('patch sso-connector by id', () => {
|
|||
await deleteSsoConnectorById(id);
|
||||
});
|
||||
|
||||
it('should patch sso connector with config', async () => {
|
||||
const { id } = await createSsoConnector({
|
||||
providerName: 'OIDC',
|
||||
connectorName: 'integration_test connector',
|
||||
});
|
||||
it.each(partialConfigAndProviderNames)(
|
||||
'should patch sso connector with config',
|
||||
async ({ providerName, config }) => {
|
||||
const { id } = await createSsoConnector({
|
||||
providerName,
|
||||
connectorName: 'integration_test connector',
|
||||
});
|
||||
|
||||
const connector = await patchSsoConnectorById(id, {
|
||||
connectorName: 'integration_test connector updated',
|
||||
config: {
|
||||
clientId: 'foo',
|
||||
clientSecret: 'bar',
|
||||
issuer: logtoIssuer,
|
||||
scope: 'profile email',
|
||||
},
|
||||
syncProfile: true,
|
||||
});
|
||||
const connector = await patchSsoConnectorById(id, {
|
||||
connectorName: 'integration_test connector updated',
|
||||
config,
|
||||
syncProfile: true,
|
||||
});
|
||||
|
||||
expect(connector).toHaveProperty('id', id);
|
||||
expect(connector).toHaveProperty('providerName', 'OIDC');
|
||||
expect(connector).toHaveProperty('connectorName', 'integration_test connector updated');
|
||||
expect(connector).toHaveProperty('config', {
|
||||
clientId: 'foo',
|
||||
clientSecret: 'bar',
|
||||
issuer: logtoIssuer,
|
||||
scope: 'profile email openid', // Should merged with default scope openid
|
||||
});
|
||||
expect(connector).toHaveProperty('syncProfile', true);
|
||||
expect(connector).toHaveProperty('id', id);
|
||||
expect(connector).toHaveProperty('providerName', providerName);
|
||||
expect(connector).toHaveProperty('connectorName', 'integration_test connector updated');
|
||||
expect(connector).toHaveProperty('config', config);
|
||||
expect(connector).toHaveProperty('syncProfile', true);
|
||||
|
||||
await deleteSsoConnectorById(id);
|
||||
});
|
||||
await deleteSsoConnectorById(id);
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
describe('patch sso-connector config by id', () => {
|
||||
|
@ -277,48 +281,42 @@ describe('patch sso-connector config by id', () => {
|
|||
await expect(patchSsoConnectorConfigById('invalid-id', {})).rejects.toThrow(HTTPError);
|
||||
});
|
||||
|
||||
it('should throw if invalid config is provided', async () => {
|
||||
it.each(providerNames)('should throw if invalid config is provided', async (providerName) => {
|
||||
const { id } = await createSsoConnector({
|
||||
providerName: 'OIDC',
|
||||
providerName,
|
||||
connectorName: 'integration_test connector',
|
||||
config: {
|
||||
clientSecret: 'bar',
|
||||
metadataType: 'URL',
|
||||
},
|
||||
});
|
||||
|
||||
await expect(
|
||||
patchSsoConnectorConfigById(id, {
|
||||
clientId: 'foo',
|
||||
issuer: 23,
|
||||
entityId: 123,
|
||||
})
|
||||
).rejects.toThrow(HTTPError);
|
||||
|
||||
await deleteSsoConnectorById(id);
|
||||
});
|
||||
|
||||
it('should patch sso connector config', async () => {
|
||||
const { id } = await createSsoConnector({
|
||||
providerName: 'OIDC',
|
||||
connectorName: 'integration_test connector',
|
||||
config: {
|
||||
clientId: 'foo',
|
||||
},
|
||||
});
|
||||
it.each(partialConfigAndProviderNames)(
|
||||
'should patch sso connector config',
|
||||
async ({ providerName, config }) => {
|
||||
const { id } = await createSsoConnector({
|
||||
providerName,
|
||||
connectorName: 'integration_test connector',
|
||||
});
|
||||
|
||||
const connector = await patchSsoConnectorConfigById(id, {
|
||||
clientSecret: 'bar',
|
||||
issuer: logtoIssuer,
|
||||
});
|
||||
const connector = await patchSsoConnectorConfigById(id, config);
|
||||
|
||||
expect(connector).toHaveProperty('id', id);
|
||||
expect(connector).toHaveProperty('providerName', 'OIDC');
|
||||
expect(connector).toHaveProperty('connectorName', 'integration_test connector');
|
||||
expect(connector).toHaveProperty('config', {
|
||||
clientId: 'foo',
|
||||
clientSecret: 'bar',
|
||||
issuer: logtoIssuer,
|
||||
scope: 'openid',
|
||||
});
|
||||
expect(connector).toHaveProperty('id', id);
|
||||
expect(connector).toHaveProperty('providerName', providerName);
|
||||
expect(connector).toHaveProperty('connectorName', 'integration_test connector');
|
||||
expect(connector).toHaveProperty('config', config);
|
||||
|
||||
await deleteSsoConnectorById(id);
|
||||
});
|
||||
await deleteSsoConnectorById(id);
|
||||
}
|
||||
);
|
||||
});
|
||||
|
|
|
@ -3121,9 +3121,9 @@ importers:
|
|||
|
||||
packages/core:
|
||||
dependencies:
|
||||
'@authenio/samlify-xsd-schema-validator':
|
||||
specifier: ^1.0.5
|
||||
version: 1.0.5(samlify@2.8.10)
|
||||
'@authenio/samlify-node-xmllint':
|
||||
specifier: ^2.0.0
|
||||
version: 2.0.0(samlify@2.8.10)
|
||||
'@aws-sdk/client-s3':
|
||||
specifier: ^3.315.0
|
||||
version: 3.315.0
|
||||
|
@ -4171,12 +4171,12 @@ packages:
|
|||
'@jridgewell/trace-mapping': 0.3.18
|
||||
dev: true
|
||||
|
||||
/@authenio/samlify-xsd-schema-validator@1.0.5(samlify@2.8.10):
|
||||
resolution: {integrity: sha512-HJjmjM1WbeB/z4nVbYEcmtIWTLPKqjrqRGEpC9lu7s03Usc4nxxfrJGjHgh3M8MvBJy4neVUoeM9rP4ym3GLgg==}
|
||||
/@authenio/samlify-node-xmllint@2.0.0(samlify@2.8.10):
|
||||
resolution: {integrity: sha512-V9cQ0CHqu3JwOmbSecGPUnzIES5kHxD00FEZKnWh90ksQUJG5/TscV2r9XLbKp7MlRMOSUfWxecM35xPSLFdSg==}
|
||||
peerDependencies:
|
||||
samlify: '>= 2.6.0'
|
||||
dependencies:
|
||||
'@authenio/xsd-schema-validator': 0.7.3
|
||||
node-xmllint: 1.0.0
|
||||
samlify: 2.8.10
|
||||
dev: false
|
||||
|
||||
|
@ -4189,11 +4189,6 @@ packages:
|
|||
xpath: 0.0.32
|
||||
dev: false
|
||||
|
||||
/@authenio/xsd-schema-validator@0.7.3:
|
||||
resolution: {integrity: sha512-Jhc/Hxv90bacZr0Fv+u+PEb440zPh4mO6rw+bzEAIBiFLKCtRa/BvKGRxPdCAwsGRPuwl2hFqQGF+Lfz6Q8kFg==}
|
||||
requiresBuild: true
|
||||
dev: false
|
||||
|
||||
/@aws-crypto/crc32@3.0.0:
|
||||
resolution: {integrity: sha512-IzSgsrxUcsrejQbPVilIKy16kAT52EwB6zSaI+M3xxIhKh5+aldEyvI+z6erM7TCLB2BJsFrtHjp6/4/sr+3dA==}
|
||||
dependencies:
|
||||
|
@ -16768,6 +16763,10 @@ packages:
|
|||
asn1: 0.2.6
|
||||
dev: false
|
||||
|
||||
/node-xmllint@1.0.0:
|
||||
resolution: {integrity: sha512-71UV2HRUP+djvHpdyatiuv+Y1o8hI4ZI7bMfuuoACMLR1JJCErM4WXAclNeHd6BgHXkqeqnnAk3wpDkSQWmFXw==}
|
||||
dev: false
|
||||
|
||||
/nodemailer@6.9.1:
|
||||
resolution: {integrity: sha512-qHw7dOiU5UKNnQpXktdgQ1d3OFgRAekuvbJLcdG5dnEo/GtcTHRYM7+UfJARdOFU9WUQO8OiIamgWPmiSFHYAA==}
|
||||
engines: {node: '>=6.0.0'}
|
||||
|
|
Loading…
Reference in a new issue