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:
parent
239b81e31a
commit
709c320426
8 changed files with 77 additions and 34 deletions
|
@ -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,
|
||||
|
|
|
@ -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}
|
||||
/>
|
||||
|
|
|
@ -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()}`;
|
||||
|
|
|
@ -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,
|
||||
})
|
||||
);
|
||||
|
||||
|
|
|
@ -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,
|
||||
})
|
||||
);
|
||||
|
||||
|
|
|
@ -9,6 +9,7 @@ const guide = {
|
|||
MachineToMachine: 'Machine-to-machine',
|
||||
Protected: 'Non-SDK Integration',
|
||||
ThirdParty: 'Third-party app',
|
||||
SAML: 'SAML',
|
||||
},
|
||||
filter: {
|
||||
title: 'Filter framework',
|
||||
|
|
|
@ -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;
|
|
@ -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'))
|
||||
);
|
||||
|
|
Loading…
Reference in a new issue