Skip to content

Commit 47ee357

Browse files
feat(core): Improve ldap/saml toggle and tests (#5771)
* improve ldap/saml toggle and tests * import cleanup * reject regular login users when saml is enabled * lint fix
1 parent 30aeeb7 commit 47ee357

File tree

9 files changed

+183
-40
lines changed

9 files changed

+183
-40
lines changed

packages/cli/src/Ldap/helpers.ts

Lines changed: 27 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,11 @@ import type { ConnectionSecurity, LdapConfig } from './types';
2626
import { jsonParse, LoggerProxy as Logger } from 'n8n-workflow';
2727
import { License } from '@/License';
2828
import { InternalHooks } from '@/InternalHooks';
29+
import {
30+
isEmailCurrentAuthenticationMethod,
31+
isLdapCurrentAuthenticationMethod,
32+
setCurrentAuthenticationMethod,
33+
} from '@/sso/ssoHelpers';
2934

3035
/**
3136
* Check whether the LDAP feature is disabled in the instance
@@ -50,8 +55,24 @@ export const setLdapLoginLabel = (value: string): void => {
5055
/**
5156
* Set the LDAP login enabled to the configuration object
5257
*/
53-
export const setLdapLoginEnabled = (value: boolean): void => {
54-
config.set(LDAP_LOGIN_ENABLED, value);
58+
export const setLdapLoginEnabled = async (value: boolean): Promise<void> => {
59+
if (config.get(LDAP_LOGIN_ENABLED) === value) {
60+
return;
61+
}
62+
// only one auth method can be active at a time, with email being the default
63+
if (value && isEmailCurrentAuthenticationMethod()) {
64+
// enable ldap login and disable email login, but only if email is the current auth method
65+
config.set(LDAP_LOGIN_ENABLED, true);
66+
await setCurrentAuthenticationMethod('ldap');
67+
} else if (!value && isLdapCurrentAuthenticationMethod()) {
68+
// disable ldap login, but only if ldap is the current auth method
69+
config.set(LDAP_LOGIN_ENABLED, false);
70+
await setCurrentAuthenticationMethod('email');
71+
} else {
72+
Logger.warn(
73+
'Cannot switch LDAP login enabled state when an authentication method other than email is active',
74+
);
75+
}
5576
};
5677

5778
/**
@@ -126,8 +147,8 @@ export const getLdapConfig = async (): Promise<LdapConfig> => {
126147
/**
127148
* Take the LDAP configuration and set login enabled and login label to the config object
128149
*/
129-
export const setGlobalLdapConfigVariables = (ldapConfig: LdapConfig): void => {
130-
setLdapLoginEnabled(ldapConfig.loginEnabled);
150+
export const setGlobalLdapConfigVariables = async (ldapConfig: LdapConfig): Promise<void> => {
151+
await setLdapLoginEnabled(ldapConfig.loginEnabled);
131152
setLdapLoginLabel(ldapConfig.loginLabel);
132153
};
133154

@@ -175,7 +196,7 @@ export const updateLdapConfig = async (ldapConfig: LdapConfig): Promise<void> =>
175196
{ key: LDAP_FEATURE_NAME },
176197
{ value: JSON.stringify(ldapConfig), loadOnStartup: true },
177198
);
178-
setGlobalLdapConfigVariables(ldapConfig);
199+
await setGlobalLdapConfigVariables(ldapConfig);
179200
};
180201

181202
/**
@@ -197,7 +218,7 @@ export const handleLdapInit = async (): Promise<void> => {
197218

198219
const ldapConfig = await getLdapConfig();
199220

200-
setGlobalLdapConfigVariables(ldapConfig);
221+
await setGlobalLdapConfigVariables(ldapConfig);
201222

202223
// init LDAP manager with the current
203224
// configuration

packages/cli/src/controllers/auth.controller.ts

Lines changed: 8 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,10 @@ import type {
1919
} from '@/Interfaces';
2020
import { handleEmailLogin, handleLdapLogin } from '@/auth';
2121
import type { PostHogClient } from '@/posthog';
22-
import { isSamlCurrentAuthenticationMethod } from '../sso/ssoHelpers';
23-
import { SamlUrls } from '../sso/saml/constants';
22+
import {
23+
isLdapCurrentAuthenticationMethod,
24+
isSamlCurrentAuthenticationMethod,
25+
} from '@/sso/ssoHelpers';
2426

2527
@RestController()
2628
export class AuthController {
@@ -73,19 +75,12 @@ export class AuthController {
7375
if (preliminaryUser?.globalRole?.name === 'owner') {
7476
user = preliminaryUser;
7577
} else {
76-
// TODO:SAML - uncomment this block when we have a way to redirect users to the SSO flow
77-
// if (doRedirectUsersFromLoginToSsoFlow()) {
78-
res.redirect(SamlUrls.restInitSSO);
79-
return;
80-
// return withFeatureFlags(this.postHog, sanitizeUser(preliminaryUser));
81-
// } else {
82-
// throw new AuthError(
83-
// 'Login with username and password is disabled due to SAML being the default authentication method. Please use SAML to log in.',
84-
// );
85-
// }
78+
throw new AuthError('SAML is enabled, please log in with SAML');
8679
}
80+
} else if (isLdapCurrentAuthenticationMethod()) {
81+
user = await handleLdapLogin(email, password);
8782
} else {
88-
user = (await handleLdapLogin(email, password)) ?? (await handleEmailLogin(email, password));
83+
user = await handleEmailLogin(email, password);
8984
}
9085
if (user) {
9186
await issueCookie(res, user);

packages/cli/src/sso/saml/samlHelpers.ts

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import {
1616
isSamlCurrentAuthenticationMethod,
1717
setCurrentAuthenticationMethod,
1818
} from '../ssoHelpers';
19+
import { LoggerProxy } from 'n8n-workflow';
1920
/**
2021
* Check whether the SAML feature is licensed and enabled in the instance
2122
*/
@@ -29,14 +30,19 @@ export function getSamlLoginLabel(): string {
2930

3031
// can only toggle between email and saml, not directly to e.g. ldap
3132
export async function setSamlLoginEnabled(enabled: boolean): Promise<void> {
32-
if (enabled) {
33-
if (isEmailCurrentAuthenticationMethod()) {
34-
config.set(SAML_LOGIN_ENABLED, true);
35-
await setCurrentAuthenticationMethod('saml');
36-
}
37-
} else {
33+
if (config.get(SAML_LOGIN_ENABLED) === enabled) {
34+
return;
35+
}
36+
if (enabled && isEmailCurrentAuthenticationMethod()) {
37+
config.set(SAML_LOGIN_ENABLED, true);
38+
await setCurrentAuthenticationMethod('saml');
39+
} else if (!enabled && isSamlCurrentAuthenticationMethod()) {
3840
config.set(SAML_LOGIN_ENABLED, false);
3941
await setCurrentAuthenticationMethod('email');
42+
} else {
43+
LoggerProxy.warn(
44+
'Cannot switch SAML login enabled state when an authentication method other than email is active',
45+
);
4046
}
4147
}
4248

packages/cli/src/sso/ssoHelpers.ts

Lines changed: 27 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,37 @@ import config from '@/config';
22
import * as Db from '@/Db';
33
import type { AuthProviderType } from '@/databases/entities/AuthIdentity';
44

5+
/**
6+
* Only one authentication method can be active at a time. This function sets the current authentication method
7+
* and saves it to the database.
8+
* SSO methods should only switch to email and then to another method. Email can switch to any method.
9+
* @param authenticationMethod
10+
*/
11+
export async function setCurrentAuthenticationMethod(
12+
authenticationMethod: AuthProviderType,
13+
): Promise<void> {
14+
config.set('userManagement.authenticationMethod', authenticationMethod);
15+
await Db.collections.Settings.save({
16+
key: 'userManagement.authenticationMethod',
17+
value: authenticationMethod,
18+
loadOnStartup: true,
19+
});
20+
}
21+
22+
export function getCurrentAuthenticationMethod(): AuthProviderType {
23+
return config.getEnv('userManagement.authenticationMethod');
24+
}
25+
526
export function isSamlCurrentAuthenticationMethod(): boolean {
6-
return config.getEnv('userManagement.authenticationMethod') === 'saml';
27+
return getCurrentAuthenticationMethod() === 'saml';
28+
}
29+
30+
export function isLdapCurrentAuthenticationMethod(): boolean {
31+
return getCurrentAuthenticationMethod() === 'ldap';
732
}
833

934
export function isEmailCurrentAuthenticationMethod(): boolean {
10-
return config.getEnv('userManagement.authenticationMethod') === 'email';
35+
return getCurrentAuthenticationMethod() === 'email';
1136
}
1237

1338
export function isSsoJustInTimeProvisioningEnabled(): boolean {
@@ -17,14 +42,3 @@ export function isSsoJustInTimeProvisioningEnabled(): boolean {
1742
export function doRedirectUsersFromLoginToSsoFlow(): boolean {
1843
return config.getEnv('sso.redirectLoginToSso');
1944
}
20-
21-
export async function setCurrentAuthenticationMethod(
22-
authenticationMethod: AuthProviderType,
23-
): Promise<void> {
24-
config.set('userManagement.authenticationMethod', authenticationMethod);
25-
await Db.collections.Settings.save({
26-
key: 'userManagement.authenticationMethod',
27-
value: authenticationMethod,
28-
loadOnStartup: true,
29-
});
30-
}

packages/cli/test/integration/ldap/ldap.api.test.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import { randomEmail, randomName, uniqueId } from './../shared/random';
1616
import * as testDb from './../shared/testDb';
1717
import type { AuthAgent } from '../shared/types';
1818
import * as utils from '../shared/utils';
19+
import { getCurrentAuthenticationMethod, setCurrentAuthenticationMethod } from '@/sso/ssoHelpers';
1920

2021
jest.mock('@/telemetry');
2122
jest.mock('@/UserManagement/email/NodeMailer');
@@ -55,6 +56,8 @@ beforeAll(async () => {
5556
);
5657

5758
utils.initConfigFile();
59+
60+
await setCurrentAuthenticationMethod('email');
5861
});
5962

6063
beforeEach(async () => {
@@ -174,6 +177,7 @@ describe('PUT /ldap/config', () => {
174177
const emailUser = await Db.collections.User.findOneByOrFail({ id: member.id });
175178
const localLdapIdentities = await testDb.getLdapIdentities();
176179

180+
expect(getCurrentAuthenticationMethod()).toBe('email');
177181
expect(emailUser.email).toBe(member.email);
178182
expect(emailUser.lastName).toBe(member.lastName);
179183
expect(emailUser.firstName).toBe(member.firstName);
@@ -190,6 +194,7 @@ test('GET /ldap/config route should retrieve current configuration', async () =>
190194

191195
let response = await authAgent(owner).put('/ldap/config').send(validPayload);
192196
expect(response.statusCode).toBe(200);
197+
expect(getCurrentAuthenticationMethod()).toBe('ldap');
193198

194199
response = await authAgent(owner).get('/ldap/config');
195200

packages/cli/test/integration/saml/saml.api.test.ts

Lines changed: 65 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,11 @@ import type { SuperAgentTest } from 'supertest';
22
import config from '@/config';
33
import type { User } from '@db/entities/User';
44
import { setSamlLoginEnabled } from '@/sso/saml/samlHelpers';
5-
import { setCurrentAuthenticationMethod } from '@/sso/ssoHelpers';
5+
import { getCurrentAuthenticationMethod, setCurrentAuthenticationMethod } from '@/sso/ssoHelpers';
66
import { randomEmail, randomName, randomValidPassword } from '../shared/random';
77
import * as testDb from '../shared/testDb';
88
import * as utils from '../shared/utils';
9+
import { sampleConfig } from './sampleMetadata';
910

1011
let owner: User;
1112
let authOwnerAgent: SuperAgentTest;
@@ -16,7 +17,7 @@ async function enableSaml(enable: boolean) {
1617
}
1718

1819
beforeAll(async () => {
19-
const app = await utils.initTestServer({ endpointGroups: ['me'] });
20+
const app = await utils.initTestServer({ endpointGroups: ['me', 'saml'] });
2021
owner = await testDb.createOwner();
2122
authOwnerAgent = utils.createAuthAgent(app)(owner);
2223
});
@@ -67,4 +68,66 @@ describe('Instance owner', () => {
6768
});
6869
});
6970
});
71+
72+
describe('POST /sso/saml/config', () => {
73+
test('should post saml config', async () => {
74+
await authOwnerAgent
75+
.post('/sso/saml/config')
76+
.send({
77+
...sampleConfig,
78+
loginEnabled: true,
79+
})
80+
.expect(200);
81+
expect(getCurrentAuthenticationMethod()).toBe('saml');
82+
});
83+
});
84+
85+
describe('POST /sso/saml/config/toggle', () => {
86+
test('should toggle saml as default authentication method', async () => {
87+
await enableSaml(true);
88+
expect(getCurrentAuthenticationMethod()).toBe('saml');
89+
90+
await authOwnerAgent
91+
.post('/sso/saml/config/toggle')
92+
.send({
93+
loginEnabled: false,
94+
})
95+
.expect(200);
96+
expect(getCurrentAuthenticationMethod()).toBe('email');
97+
98+
await authOwnerAgent
99+
.post('/sso/saml/config/toggle')
100+
.send({
101+
loginEnabled: true,
102+
})
103+
.expect(200);
104+
expect(getCurrentAuthenticationMethod()).toBe('saml');
105+
});
106+
});
107+
108+
describe('POST /sso/saml/config/toggle', () => {
109+
test('should fail enable saml if default authentication is not email', async () => {
110+
await enableSaml(true);
111+
112+
await authOwnerAgent
113+
.post('/sso/saml/config/toggle')
114+
.send({
115+
loginEnabled: false,
116+
})
117+
.expect(200);
118+
expect(getCurrentAuthenticationMethod()).toBe('email');
119+
120+
await setCurrentAuthenticationMethod('ldap');
121+
expect(getCurrentAuthenticationMethod()).toBe('ldap');
122+
123+
await authOwnerAgent
124+
.post('/sso/saml/config/toggle')
125+
.send({
126+
loginEnabled: true,
127+
})
128+
.expect(200);
129+
130+
expect(getCurrentAuthenticationMethod()).toBe('ldap');
131+
});
132+
});
70133
});
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
export const sampleMetadata =
2+
'<md:EntityDescriptor xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" xmlns:ds="http://www.w3.org/2000/09/xmldsig#" xmlns:md="urn:oasis:names:tc:SAML:2.0:metadata" ID="_2b2b09b520cf971059a06de918bff422ebb0aa943c52972e0309c533149721ef" entityID="authentik"><ds:Signature>\n<ds:SignedInfo>\n<ds:CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>\n<ds:SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>\n<ds:Reference URI="#_2b2b09b520cf971059a06de918bff422ebb0aa943c52972e0309c533149721ef">\n<ds:Transforms>\n<ds:Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"/>\n<ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>\n</ds:Transforms>\n<ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>\n<ds:DigestValue>d/0TlU9d7qi9oQxDwjsZi69RMCiheKmcjJ7W0fRCHlM=</ds:DigestValue>\n</ds:Reference>\n</ds:SignedInfo>\n<ds:SignatureValue>um+M46ZJmOhK1vGm6ZTIOY926ZN8pkMClyVprLs0NAWH3sEO11rZZZkcAnSuWrLR\n8BcrwpKRU6qE4zrZBWfh+/Fqp180OvUa7vUDpxuZFJZhv7dSldfLgAdFX2VHctBo\n77hdLmrmJuWv/u6Gzsie/J8/2D0U0OwDGwfsOLLW3rjrfea5opcaAxY+0Rh+2zzk\nzIxVBqtSnSKxAJtkOpCDzbtnQIO0meB0ZvO7ssxwSFjBbHs34TRj1S3GFgCZXzl5\naXDi7AoWEs1YPviRNb368OrD3aljFBK0gzjullFter0rzp2TzSzZilkxaZmhupJe\n388cIDBKJPUmkxumafWXxJIOMfktUTnciUl4kz0OfDQ0J5m5NaDrmvYU8g/2A0+P\nVRI88N9n0GcT9cDvzTCEDSBFefOVpvuQkue+ZYLpZ8bJJS0ykunkcNiXLbGlBlCS\nje3Od78eNjwzG/WYmHsf9ajmBezBrUmzvdJx+SmfGRZplu86z9NrOQMliKcU4/T6\nOGEwz0pRcvhMJLn+MNR2DPzX6YHnPZ0neyiUqnIkzt0fU4q1QNdcyqSTfRQlZjkx\ndbdLsEFALxcNRv8vFaAbsQpxPuFNlfZeyAWQ/MLoBG1rUiEl06I9REMN6KM7CTog\n5i926hP4LLsIki45Ob83glFOrIoj/3nAw2jbd2Crl+E=</ds:SignatureValue>\n<ds:KeyInfo>\n<ds:X509Data>\n<ds:X509Certificate>MIIFUzCCAzugAwIBAgIRAJ1peD6pO0pygujUcWb85QswDQYJKoZIhvcNAQELBQAw\nHTEbMBkGA1UEAwwSYXV0aGVudGlrIDIwMjMuMi4yMB4XDTIzMDIyNzEzMTQ0MFoX\nDTI0MDIyODEzMTQ0MFowVjEqMCgGA1UEAwwhYXV0aGVudGlrIFNlbGYtc2lnbmVk\nIENlcnRpZmljYXRlMRIwEAYDVQQKDAlhdXRoZW50aWsxFDASBgNVBAsMC1NlbGYt\nc2lnbmVkMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA3thve9UWPL09\nouGwUPlCxfrBDDKmDdvoMc3eahfuop2tSP38EvdBcnPCVYTtu2hhHNqN/QtoyAZc\nTvwD8oDjwiYxdO6VbNjMZAnMD4W84l2niGnG7ATy/niNcZoge4xy+OmCJKXsolbs\nXT+hQGQ2oiUDnbX8QwMQCMN8FBF+EvYoHXKvRjmjO75DHyHY9JP05HZTO3lycVLW\nGrIq4oJfp60PN/0z5tbpk/Tyst21o4lcESAM4fkmndonPmoKMr7q9g+CFYRT+As6\niB+L38J44YNWs0Qm42tHAlveinBRuLLMi+eMC2L0sckvyJKB1qHG+bKl7jVXNDJg\n5KWKEHdM4CBg3dJkign+12EO205ruLYSBydZErAb2NKd2htgYs/zGHSgb3LhQ3vE\nuHiTIcq828PWmVM7l3B8CJ+ZyPLixywT0pKgkb8lrDqzXIffRljCYMT2pIR4FNuy\n+CzXMYm+N30qVO8h9+cl3YRSHpFBk9KJ0/+HQp1k6ELnaYW+LryS8Jr1uPxhwyMq\nGu+4bxCF8JfZncojMhlQghXCQUvOaboNlBWv5jtsoZ9mN266V1EJpnF064UimQ1f\noN1O4l4292NvkChcmiQf2YDE5PrMWm10gQg401oulE9o91OsxLRmyw/qZTJvA06K\ngVamNLfhN/St/CVfl8q6ldgoHmWaxY8CAwEAAaNVMFMwUQYDVR0RAQH/BEcwRYJD\nT1BRVVpWNW1qdWFvQ01hdEVvenU5ajNoUnlhU0UyQThaTjd4WlZqUy5zZWxmLXNp\nZ25lZC5nb2F1dGhlbnRpay5pbzANBgkqhkiG9w0BAQsFAAOCAgEAwaQtK4s2DnJx\njg6i6BSo/rhNg7ClXgnOyF79T7JO3gexVjzboY2UTi1ut/DEII01PI0qgQ62+q9l\nTloWd1SpxPOrOVeu2uVgTK0LkGb63q355iJ2myfhFYYPPprNDzvUhnX8cVY979Ma\niqAOCJW7irlHAH2bLAujanRdlcgFtmoe5lZ+qnS5iOUmp5tehPsDJGlPZ3nCWJcR\nQHDLLSOp3TvR5no8nj0cWxUWnNeaGoJy1GsJlGapLXS5pUKpxVg9GeEcQxjBkFgM\nLWrkWBsQDvC5+GlmHgSkdRvuYBlB6CRK2eGY7G06v7ZRPhf82LvEFRBwzJvGdM0g\n491OTTJquTN2wyq45UlJK4anMYrUbpi8p8MOW7IUw6a+SvZyJab9gNoLTUzA6Mlz\nQP9bPrEALpwNhmHsmD09zNyYiNfpkpLJog96wPscx4b+gsg+5PcilET8qvth6VYD\nup8TdsonPvDPH0oyo66SAYoyOgAeB+BHTicjtVt+UnrhXYj92BHDXfmfdTzA8QcY\n7reLPIOQVk1zV24cwySiLh4F2Hr8z8V1wMRVNVHcezMsVBvCzxQ15XlMq9X2wBuj\nfED93dXJVs+WuzbpTIoXvHHT3zWnzykX8hVbrj9ddzF8TuJW4NYis0cH5SLzvtPj\n7EzvuRaQc7pNrduO1pTKoPAy+2SLgqo=</ds:X509Certificate>\n</ds:X509Data>\n</ds:KeyInfo>\n</ds:Signature><md:IDPSSODescriptor protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol"><md:KeyDescriptor use="signing"><ds:KeyInfo><ds:X509Data><ds:X509Certificate>MIIFUzCCAzugAwIBAgIRAJ1peD6pO0pygujUcWb85QswDQYJKoZIhvcNAQELBQAwHTEbMBkGA1UEAwwSYXV0aGVudGlrIDIwMjMuMi4yMB4XDTIzMDIyNzEzMTQ0MFoXDTI0MDIyODEzMTQ0MFowVjEqMCgGA1UEAwwhYXV0aGVudGlrIFNlbGYtc2lnbmVkIENlcnRpZmljYXRlMRIwEAYDVQQKDAlhdXRoZW50aWsxFDASBgNVBAsMC1NlbGYtc2lnbmVkMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA3thve9UWPL09ouGwUPlCxfrBDDKmDdvoMc3eahfuop2tSP38EvdBcnPCVYTtu2hhHNqN/QtoyAZcTvwD8oDjwiYxdO6VbNjMZAnMD4W84l2niGnG7ATy/niNcZoge4xy+OmCJKXsolbsXT+hQGQ2oiUDnbX8QwMQCMN8FBF+EvYoHXKvRjmjO75DHyHY9JP05HZTO3lycVLWGrIq4oJfp60PN/0z5tbpk/Tyst21o4lcESAM4fkmndonPmoKMr7q9g+CFYRT+As6iB+L38J44YNWs0Qm42tHAlveinBRuLLMi+eMC2L0sckvyJKB1qHG+bKl7jVXNDJg5KWKEHdM4CBg3dJkign+12EO205ruLYSBydZErAb2NKd2htgYs/zGHSgb3LhQ3vEuHiTIcq828PWmVM7l3B8CJ+ZyPLixywT0pKgkb8lrDqzXIffRljCYMT2pIR4FNuy+CzXMYm+N30qVO8h9+cl3YRSHpFBk9KJ0/+HQp1k6ELnaYW+LryS8Jr1uPxhwyMqGu+4bxCF8JfZncojMhlQghXCQUvOaboNlBWv5jtsoZ9mN266V1EJpnF064UimQ1foN1O4l4292NvkChcmiQf2YDE5PrMWm10gQg401oulE9o91OsxLRmyw/qZTJvA06KgVamNLfhN/St/CVfl8q6ldgoHmWaxY8CAwEAAaNVMFMwUQYDVR0RAQH/BEcwRYJDT1BRVVpWNW1qdWFvQ01hdEVvenU5ajNoUnlhU0UyQThaTjd4WlZqUy5zZWxmLXNpZ25lZC5nb2F1dGhlbnRpay5pbzANBgkqhkiG9w0BAQsFAAOCAgEAwaQtK4s2DnJxjg6i6BSo/rhNg7ClXgnOyF79T7JO3gexVjzboY2UTi1ut/DEII01PI0qgQ62+q9lTloWd1SpxPOrOVeu2uVgTK0LkGb63q355iJ2myfhFYYPPprNDzvUhnX8cVY979MaiqAOCJW7irlHAH2bLAujanRdlcgFtmoe5lZ+qnS5iOUmp5tehPsDJGlPZ3nCWJcRQHDLLSOp3TvR5no8nj0cWxUWnNeaGoJy1GsJlGapLXS5pUKpxVg9GeEcQxjBkFgMLWrkWBsQDvC5+GlmHgSkdRvuYBlB6CRK2eGY7G06v7ZRPhf82LvEFRBwzJvGdM0g491OTTJquTN2wyq45UlJK4anMYrUbpi8p8MOW7IUw6a+SvZyJab9gNoLTUzA6MlzQP9bPrEALpwNhmHsmD09zNyYiNfpkpLJog96wPscx4b+gsg+5PcilET8qvth6VYDup8TdsonPvDPH0oyo66SAYoyOgAeB+BHTicjtVt+UnrhXYj92BHDXfmfdTzA8QcY7reLPIOQVk1zV24cwySiLh4F2Hr8z8V1wMRVNVHcezMsVBvCzxQ15XlMq9X2wBujfED93dXJVs+WuzbpTIoXvHHT3zWnzykX8hVbrj9ddzF8TuJW4NYis0cH5SLzvtPj7EzvuRaQc7pNrduO1pTKoPAy+2SLgqo=</ds:X509Certificate></ds:X509Data></ds:KeyInfo></md:KeyDescriptor><md:SingleLogoutService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" Location="https://localhost:9943/application/saml/n8n-saml-implicit/slo/binding/redirect/"/><md:SingleLogoutService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="https://localhost:9943/application/saml/n8n-saml-implicit/slo/binding/post/"/><md:NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress</md:NameIDFormat><md:NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:persistent</md:NameIDFormat><md:NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:X509SubjectName</md:NameIDFormat><md:NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:transient</md:NameIDFormat><md:SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" Location="https://localhost:9943/application/saml/n8n-saml-implicit/sso/binding/redirect/"/><md:SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="https://localhost:9943/application/saml/n8n-saml-implicit/sso/binding/post/"/></md:IDPSSODescriptor></md:EntityDescriptor>';
3+
4+
export const sampleConfig = {
5+
mapping: {
6+
email: 'http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress',
7+
firstName: 'http://schemas.xmlsoap.org/ws/2005/05/identity/claims/firstname',
8+
lastName: 'http://schemas.xmlsoap.org/ws/2005/05/identity/claims/lastname',
9+
userPrincipalName: 'http://schemas.xmlsoap.org/ws/2005/05/identity/claims/upn',
10+
},
11+
metadata: sampleMetadata,
12+
metadataUrl: '',
13+
ignoreSSL: true,
14+
loginBinding: 'redirect',
15+
acsBinding: 'post',
16+
authnRequestsSigned: false,
17+
loginEnabled: false,
18+
loginLabel: 'SAML Login',
19+
wantAssertionsSigned: true,
20+
wantMessageSigned: true,
21+
signatureConfig: {
22+
prefix: 'ds',
23+
location: {
24+
reference: '/samlp:Response/saml:Issuer',
25+
action: 'after',
26+
},
27+
},
28+
entityID: 'https://n8n-tunnel.localhost.dev/rest/sso/saml/metadata',
29+
returnUrl: 'https://n8n-tunnel.localhost.dev/rest/sso/saml/acs',
30+
};

packages/cli/test/integration/shared/types.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ type EndpointGroup =
2222
| 'publicApi'
2323
| 'nodes'
2424
| 'ldap'
25+
| 'saml'
2526
| 'eventBus'
2627
| 'license';
2728

packages/cli/test/integration/shared/utils.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,9 @@ import { LdapManager } from '@/Ldap/LdapManager.ee';
7878
import { LDAP_ENABLED } from '@/Ldap/constants';
7979
import { handleLdapInit } from '@/Ldap/helpers';
8080
import { Push } from '@/push';
81+
import { setSamlLoginEnabled } from '@/sso/saml/samlHelpers';
82+
import { SamlService } from '@/sso/saml/saml.service.ee';
83+
import { SamlController } from '@/sso/saml/routes/saml.controller.ee';
8184

8285
export const mockInstance = <T>(
8386
ctor: new (...args: any[]) => T,
@@ -190,6 +193,11 @@ export async function initTestServer({
190193
new LdapController(service, sync, internalHooks),
191194
);
192195
break;
196+
case 'saml':
197+
await setSamlLoginEnabled(true);
198+
const samlService = Container.get(SamlService);
199+
registerController(testServer.app, config, new SamlController(samlService));
200+
break;
193201
case 'nodes':
194202
registerController(
195203
testServer.app,

0 commit comments

Comments
 (0)