diff --git a/packages/cli/src/sso.ee/saml/__tests__/saml.service.ee.test.ts b/packages/cli/src/sso.ee/saml/__tests__/saml.service.ee.test.ts
index 01741c3725..05ad0d5600 100644
--- a/packages/cli/src/sso.ee/saml/__tests__/saml.service.ee.test.ts
+++ b/packages/cli/src/sso.ee/saml/__tests__/saml.service.ee.test.ts
@@ -48,6 +48,9 @@ const InvalidSamlSetting: Settings = {
}),
};
+const SamlMetadataWithoutRedirectBinding =
+ '\n\n \n \n \n \n MIIC4jCCAcoCCQC33wnybT5QZDANBgkqhkiG9w0BAQsFADAyMQswCQYDVQQGEwJV\nSzEPMA0GA1UECgwGQm94eUhRMRIwEAYDVQQDDAlNb2NrIFNBTUwwIBcNMjIwMjI4\nMjE0NjM4WhgPMzAyMTA3MDEyMTQ2MzhaMDIxCzAJBgNVBAYTAlVLMQ8wDQYDVQQK\nDAZCb3h5SFExEjAQBgNVBAMMCU1vY2sgU0FNTDCCASIwDQYJKoZIhvcNAQEBBQAD\nggEPADCCAQoCggEBALGfYettMsct1T6tVUwTudNJH5Pnb9GGnkXi9Zw/e6x45DD0\nRuRONbFlJ2T4RjAE/uG+AjXxXQ8o2SZfb9+GgmCHuTJFNgHoZ1nFVXCmb/Hg8Hpd\n4vOAGXndixaReOiq3EH5XvpMjMkJ3+8+9VYMzMZOjkgQtAqO36eAFFfNKX7dTj3V\npwLkvz6/KFCq8OAwY+AUi4eZm5J57D31GzjHwfjH9WTeX0MyndmnNB1qV75qQR3b\n2/W5sGHRv+9AarggJkF+ptUkXoLtVA51wcfYm6hILptpde5FQC8RWY1YrswBWAEZ\nNfyrR4JeSweElNHg4NVOs4TwGjOPwWGqzTfgTlECAwEAATANBgkqhkiG9w0BAQsF\nAAOCAQEAAYRlYflSXAWoZpFfwNiCQVE5d9zZ0DPzNdWhAybXcTyMf0z5mDf6FWBW\n5Gyoi9u3EMEDnzLcJNkwJAAc39Apa4I2/tml+Jy29dk8bTyX6m93ngmCgdLh5Za4\nkhuU3AM3L63g7VexCuO7kwkjh/+LqdcIXsVGO6XDfu2QOs1Xpe9zIzLpwm/RNYeX\nUjbSj5ce/jekpAw7qyVVL4xOyh8AtUW1ek3wIw1MJvEgEPt0d16oshWJpoS1OT8L\nr/22SvYEo3EmSGdTVGgk3x3s+A0qWAqTcyjr7Q4s/GKYRFfomGwz0TZ4Iw1ZN99M\nm0eo2USlSRTVl7QHRTuiuSThHpLKQQ==\n \n \n \n urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress\n \n \n';
+
const SamlSettingWithInvalidUrl: Settings = {
loadOnStartup: true,
key: SAML_PREFERENCES_DB_KEY,
@@ -342,6 +345,14 @@ describe('SamlService', () => {
expect(samlService.samlPreferences.metadataUrl).toBe(metadataUrlTestData);
});
+ test('does throw `InvalidSamlMetadataError` when a metadata does not contain redirect binding', async () => {
+ await expect(
+ samlService.setSamlPreferences({
+ metadata: SamlMetadataWithoutRedirectBinding,
+ }),
+ ).rejects.toThrowError(InvalidSamlMetadataError);
+ });
+
test('does throw `InvalidSamlMetadataError` when a metadata url is not a valid url', async () => {
await expect(
samlService.setSamlPreferences({
@@ -396,6 +407,17 @@ describe('SamlService', () => {
});
});
+ describe('getIdentityProviderInstance', () => {
+ test('does throw `InvalidSamlMetadataError` when a metadata does not contain redirect binding', async () => {
+ await samlService.loadPreferencesWithoutValidation({
+ metadata: SamlMetadataWithoutRedirectBinding,
+ });
+ expect(() => samlService.getIdentityProviderInstance(true)).toThrowError(
+ InvalidSamlMetadataError,
+ );
+ });
+ });
+
describe('reset', () => {
test('disables saml login and deletes the saml `features.saml` key in the db', async () => {
// ARRANGE
diff --git a/packages/cli/src/sso.ee/saml/errors/invalid-saml-metadata.error.ts b/packages/cli/src/sso.ee/saml/errors/invalid-saml-metadata.error.ts
index 7c228a71c6..033fd0162a 100644
--- a/packages/cli/src/sso.ee/saml/errors/invalid-saml-metadata.error.ts
+++ b/packages/cli/src/sso.ee/saml/errors/invalid-saml-metadata.error.ts
@@ -1,7 +1,7 @@
import { UserError } from 'n8n-workflow';
export class InvalidSamlMetadataError extends UserError {
- constructor() {
- super('Invalid SAML metadata');
+ constructor(detail: string = '') {
+ super(`Invalid SAML metadata${detail ? ': ' + detail : ''}`);
}
}
diff --git a/packages/cli/src/sso.ee/saml/saml-validator.ts b/packages/cli/src/sso.ee/saml/saml-validator.ts
index e2deaa6b6d..a34209ed50 100644
--- a/packages/cli/src/sso.ee/saml/saml-validator.ts
+++ b/packages/cli/src/sso.ee/saml/saml-validator.ts
@@ -1,7 +1,11 @@
import { Service } from '@n8n/di';
import { Logger } from 'n8n-core';
+import { Constants, IdentityProvider } from 'samlify';
+import type { IdentityProviderInstance } from 'samlify';
import type { XMLFileInfo, XMLLintOptions, XMLValidationResult } from 'xmllint-wasm';
+import { InvalidSamlMetadataError } from './errors/invalid-saml-metadata.error';
+
@Service()
export class SamlValidator {
private xmlMetadata: XMLFileInfo;
@@ -21,8 +25,24 @@ export class SamlValidator {
this.xmllint = await import('xmllint-wasm');
}
+ validateIdentiyProvider(idp: IdentityProviderInstance) {
+ const binding = idp.entityMeta.getSingleSignOnService(Constants.wording.binding.redirect);
+ if (typeof binding !== 'string') {
+ throw new InvalidSamlMetadataError('only SAML redirect binding is supported.');
+ }
+ }
+
async validateMetadata(metadata: string): Promise {
- return await this.validateXml('metadata', metadata);
+ const validXML = await this.validateXml('metadata', metadata);
+
+ if (validXML) {
+ const idp = IdentityProvider({
+ metadata,
+ });
+ this.validateIdentiyProvider(idp);
+ }
+
+ return validXML;
}
async validateResponse(response: string): Promise {
diff --git a/packages/cli/src/sso.ee/saml/saml.service.ee.ts b/packages/cli/src/sso.ee/saml/saml.service.ee.ts
index 3db1d23b23..161981ae35 100644
--- a/packages/cli/src/sso.ee/saml/saml.service.ee.ts
+++ b/packages/cli/src/sso.ee/saml/saml.service.ee.ts
@@ -7,7 +7,7 @@ import type express from 'express';
import https from 'https';
import { Logger } from 'n8n-core';
import { jsonParse, UnexpectedError } from 'n8n-workflow';
-import type { IdentityProviderInstance, ServiceProviderInstance } from 'samlify';
+import { type IdentityProviderInstance, type ServiceProviderInstance } from 'samlify';
import type { BindingContext, PostBindingContext } from 'samlify/types/src/entity';
import { AuthError } from '@/errors/response-errors/auth.error';
@@ -135,6 +135,8 @@ export class SamlService {
});
}
+ this.validator.validateIdentiyProvider(this.identityProviderInstance);
+
return this.identityProviderInstance;
}