0
Fork 0
mirror of https://github.com/logto-io/logto.git synced 2024-12-16 20:26:19 -05:00

refactor(console,core,schemas): allow SAML application to use IdP-initiated SSO (#6849)

* refactor(console,core,schemas): allow SAML application to use  IdP-initiated SSO

allow SAML application to use IdP-initiated

* fix(core): fix ut

fix ut
This commit is contained in:
simeng-li 2024-12-04 17:33:01 +08:00 committed by GitHub
parent 239b81e31a
commit 709c320426
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 77 additions and 34 deletions

View file

@ -42,7 +42,7 @@ type FormProps = {
function ConfigForm({
ssoConnector,
applications: allApplications,
applications,
idpInitiatedAuthConfig,
mutateIdpInitiatedConfig,
}: FormProps) {
@ -50,21 +50,6 @@ function ConfigForm({
const { getTo } = useTenantPathname();
const api = useApi();
/**
* See definition of `applicationsSearchUrl`, there is only non-third party SPA/Traditional applications here, and SAML applications are always third party secured by DB schema, we need to manually exclude other application types here to make TypeScript happy.
*/
const applications = useMemo(
() =>
allApplications.filter(
(
application
): application is Omit<Application, 'type'> & {
type: Exclude<ApplicationType, ApplicationType.SAML>;
} => application.type !== ApplicationType.SAML
),
[allApplications]
);
const {
control,
register,

View file

@ -1,4 +1,8 @@
import { type Application, type SsoConnectorWithProviderConfig } from '@logto/schemas';
import {
ApplicationType,
type Application,
type SsoConnectorWithProviderConfig,
} from '@logto/schemas';
import { useMemo } from 'react';
import useSWR from 'swr';
@ -31,6 +35,15 @@ function IdpInitiatedAuth({ ssoConnector }: Props) {
[applicationError, applications, idpInitiatedAuthConfig, idpInitiatedAuthConfigError]
);
// Filter out non-SAML third-party applications
const filteredApplications = useMemo(
() =>
applications?.filter(
({ type, isThirdParty }) => !isThirdParty || type === ApplicationType.SAML
),
[applications]
);
if (isLoading) {
return (
<FormCard
@ -45,7 +58,7 @@ function IdpInitiatedAuth({ ssoConnector }: Props) {
return (
<ConfigForm
ssoConnector={ssoConnector}
applications={applications ?? []}
applications={filteredApplications ?? []}
idpInitiatedAuthConfig={idpInitiatedAuthConfig}
mutateIdpInitiatedConfig={mutate}
/>

View file

@ -12,7 +12,8 @@ import { toast } from 'react-hot-toast';
const applicationsSearchParams = new URLSearchParams([
['types', ApplicationType.Traditional],
['types', ApplicationType.SPA],
['isThirdParty', 'false'],
['types', ApplicationType.SAML],
// TODO: for now we allow all third-party applications here, including SAML and OIDC
]);
export const applicationsSearchUrl = `api/applications?${applicationsSearchParams.toString()}`;

View file

@ -13,7 +13,6 @@ const findAllSsoConnectors = jest.fn();
const getConnectorById = jest.fn();
const findApplicationById = jest.fn();
const insertIdpInitiatedAuthConfig = jest.fn();
const updateIdpInitiatedAuthConfig = jest.fn();
const queries = new MockQueries({
ssoConnectors: {
@ -132,8 +131,10 @@ describe('SsoConnectorLibrary', () => {
autoSendAuthorizationRequest: true,
})
).rejects.toMatchError(
new RequestError('single_sign_on.idp_initiated_authentication_invalid_application_type', {
type: ApplicationType.Traditional,
new RequestError({
code: 'single_sign_on.idp_initiated_authentication_invalid_application_type',
type: `${ApplicationType.Traditional}, ${ApplicationType.SAML}`,
statue: 400,
})
);
@ -154,8 +155,10 @@ describe('SsoConnectorLibrary', () => {
clientIdpInitiatedAuthCallbackUri: 'https://callback.com',
})
).rejects.toMatchError(
new RequestError('single_sign_on.idp_initiated_authentication_invalid_application_type', {
type: `${ApplicationType.Traditional}, ${ApplicationType.SPA}`,
new RequestError({
code: 'single_sign_on.idp_initiated_authentication_invalid_application_type',
type: `${ApplicationType.Traditional}, ${ApplicationType.SPA}, ${ApplicationType.SAML}`,
status: 400,
})
);
@ -176,8 +179,10 @@ describe('SsoConnectorLibrary', () => {
autoSendAuthorizationRequest: true,
})
).rejects.toMatchError(
new RequestError('single_sign_on.idp_initiated_authentication_invalid_application_type', {
type: ApplicationType.Traditional,
new RequestError({
code: 'single_sign_on.idp_initiated_authentication_invalid_application_type',
type: `${ApplicationType.Traditional}, ${ApplicationType.SAML}`,
status: 400,
})
);

View file

@ -85,11 +85,14 @@ export const createSsoConnectorLibrary = (queries: Queries) => {
// Authorization request initiated by Logto server
if (autoSendAuthorizationRequest) {
// Only first-party traditional web applications are allowed
// Only first-party traditional web applications or SAML applications are allowed
assertThat(
application.type === ApplicationType.Traditional && !application.isThirdParty,
new RequestError('single_sign_on.idp_initiated_authentication_invalid_application_type', {
type: ApplicationType.Traditional,
(application.type === ApplicationType.Traditional && !application.isThirdParty) ||
application.type === ApplicationType.SAML,
new RequestError({
code: 'single_sign_on.idp_initiated_authentication_invalid_application_type',
type: `${ApplicationType.Traditional}, ${ApplicationType.SAML}`,
status: 400,
})
);
@ -100,11 +103,16 @@ export const createSsoConnectorLibrary = (queries: Queries) => {
);
} else {
// Authorization request initiated by the client
// Only first-party traditional web applications, SPAs, or SAML applications are allowed
assertThat(
(application.type === ApplicationType.Traditional && !application.isThirdParty) ||
application.type === ApplicationType.SPA,
new RequestError('single_sign_on.idp_initiated_authentication_invalid_application_type', {
type: `${ApplicationType.Traditional}, ${ApplicationType.SPA}`,
application.type === ApplicationType.SPA ||
application.type === ApplicationType.SAML,
new RequestError({
code: 'single_sign_on.idp_initiated_authentication_invalid_application_type',
type: `${ApplicationType.Traditional}, ${ApplicationType.SPA}, ${ApplicationType.SAML}`,
status: 400,
})
);

View file

@ -9,6 +9,7 @@ const guide = {
MachineToMachine: 'Machine-to-machine',
Protected: 'Non-SDK Integration',
ThirdParty: 'Third-party app',
SAML: 'SAML',
},
filter: {
title: 'Filter framework',

View file

@ -0,0 +1,30 @@
import { sql } from '@silverhand/slonik';
import type { AlterationScript } from '../lib/types/alteration.js';
const alteration: AlterationScript = {
up: async (pool) => {
await pool.query(sql`
alter table sso_connector_idp_initiated_auth_configs
drop constraint application_type;`);
await pool.query(sql`
alter table sso_connector_idp_initiated_auth_configs
add constraint application_type
check (check_application_type(default_application_id, 'Traditional', 'SPA', 'SAML'));
`);
},
down: async (pool) => {
await pool.query(sql`
alter table sso_connector_idp_initiated_auth_configs
drop constraint application_type;`);
await pool.query(sql`
alter table sso_connector_idp_initiated_auth_configs
add constraint application_type
check (check_application_type(default_application_id, 'Traditional', 'SPA'));
`);
},
};
export default alteration;

View file

@ -20,5 +20,5 @@ create table sso_connector_idp_initiated_auth_configs (
primary key (tenant_id, connector_id),
/** Insure the application type is Traditional or SPA. */
constraint application_type
check (check_application_type(default_application_id, 'Traditional', 'SPA'))
check (check_application_type(default_application_id, 'Traditional', 'SPA', 'SAML'))
);