Skip to content

Commit 709c320

Browse files
authored
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
1 parent 239b81e commit 709c320

File tree

8 files changed

+77
-34
lines changed

8 files changed

+77
-34
lines changed

packages/console/src/pages/EnterpriseSsoDetails/IdpInitiatedAuth/ConfigForm.tsx

Lines changed: 1 addition & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -42,29 +42,14 @@ type FormProps = {
4242

4343
function ConfigForm({
4444
ssoConnector,
45-
applications: allApplications,
45+
applications,
4646
idpInitiatedAuthConfig,
4747
mutateIdpInitiatedConfig,
4848
}: FormProps) {
4949
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
5050
const { getTo } = useTenantPathname();
5151
const api = useApi();
5252

53-
/**
54-
* 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.
55-
*/
56-
const applications = useMemo(
57-
() =>
58-
allApplications.filter(
59-
(
60-
application
61-
): application is Omit<Application, 'type'> & {
62-
type: Exclude<ApplicationType, ApplicationType.SAML>;
63-
} => application.type !== ApplicationType.SAML
64-
),
65-
[allApplications]
66-
);
67-
6853
const {
6954
control,
7055
register,

packages/console/src/pages/EnterpriseSsoDetails/IdpInitiatedAuth/index.tsx

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
1-
import { type Application, type SsoConnectorWithProviderConfig } from '@logto/schemas';
1+
import {
2+
ApplicationType,
3+
type Application,
4+
type SsoConnectorWithProviderConfig,
5+
} from '@logto/schemas';
26
import { useMemo } from 'react';
37
import useSWR from 'swr';
48

@@ -31,6 +35,15 @@ function IdpInitiatedAuth({ ssoConnector }: Props) {
3135
[applicationError, applications, idpInitiatedAuthConfig, idpInitiatedAuthConfigError]
3236
);
3337

38+
// Filter out non-SAML third-party applications
39+
const filteredApplications = useMemo(
40+
() =>
41+
applications?.filter(
42+
({ type, isThirdParty }) => !isThirdParty || type === ApplicationType.SAML
43+
),
44+
[applications]
45+
);
46+
3447
if (isLoading) {
3548
return (
3649
<FormCard
@@ -45,7 +58,7 @@ function IdpInitiatedAuth({ ssoConnector }: Props) {
4558
return (
4659
<ConfigForm
4760
ssoConnector={ssoConnector}
48-
applications={applications ?? []}
61+
applications={filteredApplications ?? []}
4962
idpInitiatedAuthConfig={idpInitiatedAuthConfig}
5063
mutateIdpInitiatedConfig={mutate}
5164
/>

packages/console/src/pages/EnterpriseSsoDetails/IdpInitiatedAuth/utils.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@ import { toast } from 'react-hot-toast';
1212
const applicationsSearchParams = new URLSearchParams([
1313
['types', ApplicationType.Traditional],
1414
['types', ApplicationType.SPA],
15-
['isThirdParty', 'false'],
15+
['types', ApplicationType.SAML],
16+
// TODO: for now we allow all third-party applications here, including SAML and OIDC
1617
]);
1718

1819
export const applicationsSearchUrl = `api/applications?${applicationsSearchParams.toString()}`;

packages/core/src/libraries/sso-connector.test.ts

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ const findAllSsoConnectors = jest.fn();
1313
const getConnectorById = jest.fn();
1414
const findApplicationById = jest.fn();
1515
const insertIdpInitiatedAuthConfig = jest.fn();
16-
const updateIdpInitiatedAuthConfig = jest.fn();
1716

1817
const queries = new MockQueries({
1918
ssoConnectors: {
@@ -132,8 +131,10 @@ describe('SsoConnectorLibrary', () => {
132131
autoSendAuthorizationRequest: true,
133132
})
134133
).rejects.toMatchError(
135-
new RequestError('single_sign_on.idp_initiated_authentication_invalid_application_type', {
136-
type: ApplicationType.Traditional,
134+
new RequestError({
135+
code: 'single_sign_on.idp_initiated_authentication_invalid_application_type',
136+
type: `${ApplicationType.Traditional}, ${ApplicationType.SAML}`,
137+
statue: 400,
137138
})
138139
);
139140

@@ -154,8 +155,10 @@ describe('SsoConnectorLibrary', () => {
154155
clientIdpInitiatedAuthCallbackUri: 'https://callback.com',
155156
})
156157
).rejects.toMatchError(
157-
new RequestError('single_sign_on.idp_initiated_authentication_invalid_application_type', {
158-
type: `${ApplicationType.Traditional}, ${ApplicationType.SPA}`,
158+
new RequestError({
159+
code: 'single_sign_on.idp_initiated_authentication_invalid_application_type',
160+
type: `${ApplicationType.Traditional}, ${ApplicationType.SPA}, ${ApplicationType.SAML}`,
161+
status: 400,
159162
})
160163
);
161164

@@ -176,8 +179,10 @@ describe('SsoConnectorLibrary', () => {
176179
autoSendAuthorizationRequest: true,
177180
})
178181
).rejects.toMatchError(
179-
new RequestError('single_sign_on.idp_initiated_authentication_invalid_application_type', {
180-
type: ApplicationType.Traditional,
182+
new RequestError({
183+
code: 'single_sign_on.idp_initiated_authentication_invalid_application_type',
184+
type: `${ApplicationType.Traditional}, ${ApplicationType.SAML}`,
185+
status: 400,
181186
})
182187
);
183188

packages/core/src/libraries/sso-connector.ts

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -85,11 +85,14 @@ export const createSsoConnectorLibrary = (queries: Queries) => {
8585

8686
// Authorization request initiated by Logto server
8787
if (autoSendAuthorizationRequest) {
88-
// Only first-party traditional web applications are allowed
88+
// Only first-party traditional web applications or SAML applications are allowed
8989
assertThat(
90-
application.type === ApplicationType.Traditional && !application.isThirdParty,
91-
new RequestError('single_sign_on.idp_initiated_authentication_invalid_application_type', {
92-
type: ApplicationType.Traditional,
90+
(application.type === ApplicationType.Traditional && !application.isThirdParty) ||
91+
application.type === ApplicationType.SAML,
92+
new RequestError({
93+
code: 'single_sign_on.idp_initiated_authentication_invalid_application_type',
94+
type: `${ApplicationType.Traditional}, ${ApplicationType.SAML}`,
95+
status: 400,
9396
})
9497
);
9598

@@ -100,11 +103,16 @@ export const createSsoConnectorLibrary = (queries: Queries) => {
100103
);
101104
} else {
102105
// Authorization request initiated by the client
106+
107+
// Only first-party traditional web applications, SPAs, or SAML applications are allowed
103108
assertThat(
104109
(application.type === ApplicationType.Traditional && !application.isThirdParty) ||
105-
application.type === ApplicationType.SPA,
106-
new RequestError('single_sign_on.idp_initiated_authentication_invalid_application_type', {
107-
type: `${ApplicationType.Traditional}, ${ApplicationType.SPA}`,
110+
application.type === ApplicationType.SPA ||
111+
application.type === ApplicationType.SAML,
112+
new RequestError({
113+
code: 'single_sign_on.idp_initiated_authentication_invalid_application_type',
114+
type: `${ApplicationType.Traditional}, ${ApplicationType.SPA}, ${ApplicationType.SAML}`,
115+
status: 400,
108116
})
109117
);
110118

packages/phrases/src/locales/en/translation/admin-console/guide.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ const guide = {
99
MachineToMachine: 'Machine-to-machine',
1010
Protected: 'Non-SDK Integration',
1111
ThirdParty: 'Third-party app',
12+
SAML: 'SAML',
1213
},
1314
filter: {
1415
title: 'Filter framework',
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import { sql } from '@silverhand/slonik';
2+
3+
import type { AlterationScript } from '../lib/types/alteration.js';
4+
5+
const alteration: AlterationScript = {
6+
up: async (pool) => {
7+
await pool.query(sql`
8+
alter table sso_connector_idp_initiated_auth_configs
9+
drop constraint application_type;`);
10+
11+
await pool.query(sql`
12+
alter table sso_connector_idp_initiated_auth_configs
13+
add constraint application_type
14+
check (check_application_type(default_application_id, 'Traditional', 'SPA', 'SAML'));
15+
`);
16+
},
17+
down: async (pool) => {
18+
await pool.query(sql`
19+
alter table sso_connector_idp_initiated_auth_configs
20+
drop constraint application_type;`);
21+
22+
await pool.query(sql`
23+
alter table sso_connector_idp_initiated_auth_configs
24+
add constraint application_type
25+
check (check_application_type(default_application_id, 'Traditional', 'SPA'));
26+
`);
27+
},
28+
};
29+
30+
export default alteration;

packages/schemas/tables/sso_connector_idp_initiated_auth_configs.sql

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,5 +20,5 @@ create table sso_connector_idp_initiated_auth_configs (
2020
primary key (tenant_id, connector_id),
2121
/** Insure the application type is Traditional or SPA. */
2222
constraint application_type
23-
check (check_application_type(default_application_id, 'Traditional', 'SPA'))
23+
check (check_application_type(default_application_id, 'Traditional', 'SPA', 'SAML'))
2424
);

0 commit comments

Comments
 (0)