mirror of
https://github.com/n8n-io/n8n.git
synced 2025-03-05 20:50:17 -08:00
move templates into templates directory
This commit is contained in:
parent
b4cf627cd3
commit
f6e20402fe
|
@ -0,0 +1,77 @@
|
||||||
|
import { type Response } from 'express';
|
||||||
|
import { mock } from 'jest-mock-extended';
|
||||||
|
|
||||||
|
import type { User } from '@/databases/entities/user';
|
||||||
|
import { UrlService } from '@/services/url.service';
|
||||||
|
import { mockInstance } from '@test/mocking';
|
||||||
|
|
||||||
|
import { SamlService } from '../../saml.service.ee';
|
||||||
|
import { getServiceProviderConfigTestReturnUrl } from '../../service-provider.ee';
|
||||||
|
import type { SamlConfiguration } from '../../types/requests';
|
||||||
|
import type { SamlUserAttributes } from '../../types/saml-user-attributes';
|
||||||
|
import { SamlController } from '../saml.controller.ee';
|
||||||
|
|
||||||
|
const urlService = mockInstance(UrlService);
|
||||||
|
urlService.getInstanceBaseUrl.mockReturnValue('');
|
||||||
|
const samlService = mockInstance(SamlService);
|
||||||
|
const controller = new SamlController(mock(), samlService, mock(), mock());
|
||||||
|
|
||||||
|
const user = mock<User>({
|
||||||
|
id: '123',
|
||||||
|
password: 'password',
|
||||||
|
authIdentities: [],
|
||||||
|
role: 'global:owner',
|
||||||
|
});
|
||||||
|
|
||||||
|
const attributes: SamlUserAttributes = {
|
||||||
|
email: 'test@example.com',
|
||||||
|
firstName: 'Test',
|
||||||
|
lastName: 'User',
|
||||||
|
userPrincipalName: 'upn:test@example.com',
|
||||||
|
};
|
||||||
|
|
||||||
|
describe('Test views', () => {
|
||||||
|
test('Should render success with template', async () => {
|
||||||
|
const req = mock<SamlConfiguration.AcsRequest>();
|
||||||
|
const res = mock<Response>();
|
||||||
|
|
||||||
|
req.body.RelayState = getServiceProviderConfigTestReturnUrl();
|
||||||
|
samlService.handleSamlLogin.mockResolvedValueOnce({
|
||||||
|
authenticatedUser: user,
|
||||||
|
attributes,
|
||||||
|
onboardingRequired: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
await controller.acsPost(req, res);
|
||||||
|
|
||||||
|
expect(res.render).toBeCalledWith('saml-connection-test-success', attributes);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Should render failure with template', async () => {
|
||||||
|
const req = mock<SamlConfiguration.AcsRequest>();
|
||||||
|
const res = mock<Response>();
|
||||||
|
|
||||||
|
req.body.RelayState = getServiceProviderConfigTestReturnUrl();
|
||||||
|
samlService.handleSamlLogin.mockResolvedValueOnce({
|
||||||
|
authenticatedUser: undefined,
|
||||||
|
attributes,
|
||||||
|
onboardingRequired: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
await controller.acsPost(req, res);
|
||||||
|
|
||||||
|
expect(res.render).toBeCalledWith('saml-connection-test-failed', { message: '', attributes });
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Should render error with template', async () => {
|
||||||
|
const req = mock<SamlConfiguration.AcsRequest>();
|
||||||
|
const res = mock<Response>();
|
||||||
|
|
||||||
|
req.body.RelayState = getServiceProviderConfigTestReturnUrl();
|
||||||
|
samlService.handleSamlLogin.mockRejectedValueOnce(new Error('Test Error'));
|
||||||
|
|
||||||
|
await controller.acsPost(req, res);
|
||||||
|
|
||||||
|
expect(res.render).toBeCalledWith('saml-connection-test-failed', { message: 'Test Error' });
|
||||||
|
});
|
||||||
|
});
|
|
@ -26,8 +26,6 @@ import {
|
||||||
import type { SamlLoginBinding } from '../types';
|
import type { SamlLoginBinding } from '../types';
|
||||||
import { SamlConfiguration } from '../types/requests';
|
import { SamlConfiguration } from '../types/requests';
|
||||||
import { getInitSSOFormView } from '../views/init-sso-post';
|
import { getInitSSOFormView } from '../views/init-sso-post';
|
||||||
import { getSamlConnectionTestFailedView } from '../views/saml-connection-test-failed';
|
|
||||||
import { getSamlConnectionTestSuccessView } from '../views/saml-connection-test-success';
|
|
||||||
|
|
||||||
@RestController('/sso/saml')
|
@RestController('/sso/saml')
|
||||||
export class SamlController {
|
export class SamlController {
|
||||||
|
@ -92,7 +90,7 @@ export class SamlController {
|
||||||
/**
|
/**
|
||||||
* Assertion Consumer Service endpoint
|
* Assertion Consumer Service endpoint
|
||||||
*/
|
*/
|
||||||
@Get('/acs', { middlewares: [samlLicensedMiddleware], skipAuth: true })
|
@Get('/acs', { middlewares: [samlLicensedMiddleware], skipAuth: true, usesTemplates: true })
|
||||||
async acsGet(req: SamlConfiguration.AcsRequest, res: express.Response) {
|
async acsGet(req: SamlConfiguration.AcsRequest, res: express.Response) {
|
||||||
return await this.acsHandler(req, res, 'redirect');
|
return await this.acsHandler(req, res, 'redirect');
|
||||||
}
|
}
|
||||||
|
@ -100,7 +98,7 @@ export class SamlController {
|
||||||
/**
|
/**
|
||||||
* Assertion Consumer Service endpoint
|
* Assertion Consumer Service endpoint
|
||||||
*/
|
*/
|
||||||
@Post('/acs', { middlewares: [samlLicensedMiddleware], skipAuth: true })
|
@Post('/acs', { middlewares: [samlLicensedMiddleware], skipAuth: true, usesTemplates: true })
|
||||||
async acsPost(req: SamlConfiguration.AcsRequest, res: express.Response) {
|
async acsPost(req: SamlConfiguration.AcsRequest, res: express.Response) {
|
||||||
return await this.acsHandler(req, res, 'post');
|
return await this.acsHandler(req, res, 'post');
|
||||||
}
|
}
|
||||||
|
@ -120,9 +118,12 @@ export class SamlController {
|
||||||
// if RelayState is set to the test connection Url, this is a test connection
|
// if RelayState is set to the test connection Url, this is a test connection
|
||||||
if (isConnectionTestRequest(req)) {
|
if (isConnectionTestRequest(req)) {
|
||||||
if (loginResult.authenticatedUser) {
|
if (loginResult.authenticatedUser) {
|
||||||
return res.send(getSamlConnectionTestSuccessView(loginResult.attributes));
|
return res.render('saml-connection-test-success', loginResult.attributes);
|
||||||
} else {
|
} else {
|
||||||
return res.send(getSamlConnectionTestFailedView('', loginResult.attributes));
|
return res.render('saml-connection-test-failed', {
|
||||||
|
message: '',
|
||||||
|
attributes: loginResult.attributes,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (loginResult.authenticatedUser) {
|
if (loginResult.authenticatedUser) {
|
||||||
|
@ -151,7 +152,7 @@ export class SamlController {
|
||||||
throw new AuthError('SAML Authentication failed');
|
throw new AuthError('SAML Authentication failed');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (isConnectionTestRequest(req)) {
|
if (isConnectionTestRequest(req)) {
|
||||||
return res.send(getSamlConnectionTestFailedView((error as Error).message));
|
return res.render('saml-connection-test-failed', { message: (error as Error).message });
|
||||||
}
|
}
|
||||||
this.eventService.emit('user-login-failed', {
|
this.eventService.emit('user-login-failed', {
|
||||||
userEmail: 'unknown',
|
userEmail: 'unknown',
|
||||||
|
|
|
@ -1,33 +0,0 @@
|
||||||
import { getSamlConnectionTestFailedView } from '../saml-connection-test-failed';
|
|
||||||
|
|
||||||
const basicXssPayload = '<script>alert("1");</script>';
|
|
||||||
const basicXssMitigated = '<script>alert("1");</script>';
|
|
||||||
|
|
||||||
describe('SAML Connection Test Failed', () => {
|
|
||||||
test('should not allow XSS via error message', () => {
|
|
||||||
const result = getSamlConnectionTestFailedView('Test ' + basicXssPayload);
|
|
||||||
expect(result).not.toMatch(basicXssPayload);
|
|
||||||
expect(result).toMatch(basicXssMitigated);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should not allow XSS via attributes', () => {
|
|
||||||
const result = getSamlConnectionTestFailedView('', {
|
|
||||||
email: 'test@example.com',
|
|
||||||
firstName: 'Test',
|
|
||||||
lastName: 'McXss' + basicXssPayload,
|
|
||||||
userPrincipalName: 'test@example.com',
|
|
||||||
});
|
|
||||||
expect(result).not.toMatch(basicXssPayload);
|
|
||||||
expect(result).toMatch(basicXssMitigated);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should replace undefined with (n/a)', () => {
|
|
||||||
expect(
|
|
||||||
getSamlConnectionTestFailedView('', {
|
|
||||||
firstName: 'No',
|
|
||||||
lastName: 'Email',
|
|
||||||
userPrincipalName: 'test@example.com',
|
|
||||||
}),
|
|
||||||
).toMatch('<strong>Email:</strong> (n/a)');
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -1,27 +0,0 @@
|
||||||
import { getSamlConnectionTestSuccessView } from '../saml-connection-test-success';
|
|
||||||
|
|
||||||
const basicXssPayload = '<script>alert("1");</script>';
|
|
||||||
const basicXssMitigated = '<script>alert("1");</script>';
|
|
||||||
|
|
||||||
describe('SAML Connection Test Succeeded', () => {
|
|
||||||
test('should not allow XSS via attributes', () => {
|
|
||||||
const result = getSamlConnectionTestSuccessView({
|
|
||||||
email: 'test@example.com',
|
|
||||||
firstName: 'Test',
|
|
||||||
lastName: 'McXss' + basicXssPayload,
|
|
||||||
userPrincipalName: 'test@example.com',
|
|
||||||
});
|
|
||||||
expect(result).not.toMatch(basicXssPayload);
|
|
||||||
expect(result).toMatch(basicXssMitigated);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should replace undefined with (n/a)', () => {
|
|
||||||
expect(
|
|
||||||
getSamlConnectionTestSuccessView({
|
|
||||||
firstName: 'No',
|
|
||||||
lastName: 'Email',
|
|
||||||
userPrincipalName: 'test@example.com',
|
|
||||||
}),
|
|
||||||
).toMatch('<strong>Email:</strong> (n/a)');
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -1,51 +0,0 @@
|
||||||
import { compile } from 'handlebars';
|
|
||||||
|
|
||||||
import type { SamlUserAttributes } from '../types/saml-user-attributes';
|
|
||||||
|
|
||||||
const failedTemplate = compile<{ message?: string; attributes?: SamlUserAttributes }>(`
|
|
||||||
<http>
|
|
||||||
<head>
|
|
||||||
<title>n8n - SAML Connection Test Result</title>
|
|
||||||
<style>
|
|
||||||
body { background: rgb(251,252,254); font-family: 'Open Sans', sans-serif; padding: 10px; margin: auto; width: 500px; top: 40%; position: relative; }
|
|
||||||
h1 { color: rgb(240, 60, 60); font-size: 16px; font-weight: 400; margin: 0 0 10px 0; }
|
|
||||||
h2 { color: rgb(0, 0, 0); font-size: 12px; font-weight: 400; margin: 0 0 10px 0; }
|
|
||||||
button { border: 1px solid rgb(219, 223, 231); background: rgb(255, 255, 255); border-radius: 4px; padding: 10px; }
|
|
||||||
ul { border: 1px solid rgb(219, 223, 231); border-radius: 4px; padding: 10px; }
|
|
||||||
li { decoration: none; list-style: none; margin: 0 0 0px 0; color: rgb(125, 125, 125); font-size: 12px;}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div style="text-align:center">
|
|
||||||
<h1>SAML Connection Test failed</h1>
|
|
||||||
<h2>{{message}}</h2>
|
|
||||||
<button onclick="window.close()">You can close this window now</button>
|
|
||||||
<p></p>
|
|
||||||
{{#with attributes}}
|
|
||||||
<h2>Here are the attributes returned by your SAML IdP:</h2>
|
|
||||||
<ul>
|
|
||||||
<li><strong>Email:</strong> {{email}}</li>
|
|
||||||
<li><strong>First Name:</strong> {{firstName}}</li>
|
|
||||||
<li><strong>Last Name:</strong> {{lastName}}</li>
|
|
||||||
<li><strong>UPN:</strong> {{userPrincipalName}}</li>
|
|
||||||
{{/with}}
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</body>
|
|
||||||
</http>
|
|
||||||
`);
|
|
||||||
|
|
||||||
export function getSamlConnectionTestFailedView(
|
|
||||||
message?: string,
|
|
||||||
attributes?: Partial<SamlUserAttributes>,
|
|
||||||
): string {
|
|
||||||
return failedTemplate({
|
|
||||||
message: message ?? 'A common issue could be that no email attribute is set',
|
|
||||||
attributes: attributes && {
|
|
||||||
email: attributes.email ?? '(n/a)',
|
|
||||||
firstName: attributes.firstName ?? '(n/a)',
|
|
||||||
lastName: attributes.lastName ?? '(n/a)',
|
|
||||||
userPrincipalName: attributes.userPrincipalName ?? '(n/a)',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
|
@ -1,42 +0,0 @@
|
||||||
import { compile } from 'handlebars';
|
|
||||||
|
|
||||||
import type { SamlUserAttributes } from '../types/saml-user-attributes';
|
|
||||||
|
|
||||||
const successTemplate = compile<SamlUserAttributes>(`
|
|
||||||
<http>
|
|
||||||
<head>
|
|
||||||
<title>n8n - SAML Connection Test Result</title>
|
|
||||||
<style>
|
|
||||||
body { background: rgb(251,252,254); font-family: 'Open Sans', sans-serif; padding: 10px; margin: auto; width: 500px; top: 40%; position: relative; }
|
|
||||||
h1 { color: rgb(0, 0, 0); font-size: 16px; font-weight: 400; margin: 0 0 10px 0; }
|
|
||||||
h2 { color: rgb(0, 0, 0); font-size: 12px; font-weight: 400; margin: 0 0 10px 0; }
|
|
||||||
button { border: 1px solid rgb(219, 223, 231); background: rgb(255, 255, 255); border-radius: 4px; padding: 10px; }
|
|
||||||
ul { border: 1px solid rgb(219, 223, 231); border-radius: 4px; padding: 10px; }
|
|
||||||
li { decoration: none; list-style: none; margin: 0 0 0px 0; color: rgb(125, 125, 125); font-size: 12px;}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div style="text-align:center">
|
|
||||||
<h1>SAML Connection Test was successful</h1>
|
|
||||||
<button onclick="window.close()">You can close this window now</button>
|
|
||||||
<p></p>
|
|
||||||
<h2>Here are the attributes returned by your SAML IdP:</h2>
|
|
||||||
<ul>
|
|
||||||
<li><strong>Email:</strong> {{email}}</li>
|
|
||||||
<li><strong>First Name:</strong> {{firstName}}</li>
|
|
||||||
<li><strong>Last Name:</strong> {{lastName}}</li>
|
|
||||||
<li><strong>UPN:</strong> {{userPrincipalName}}</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</body>
|
|
||||||
</http>
|
|
||||||
`);
|
|
||||||
|
|
||||||
export function getSamlConnectionTestSuccessView(attributes: Partial<SamlUserAttributes>): string {
|
|
||||||
return successTemplate({
|
|
||||||
email: attributes.email ?? '(n/a)',
|
|
||||||
firstName: attributes.firstName ?? '(n/a)',
|
|
||||||
lastName: attributes.lastName ?? '(n/a)',
|
|
||||||
userPrincipalName: attributes.userPrincipalName ?? '(n/a)',
|
|
||||||
});
|
|
||||||
}
|
|
|
@ -0,0 +1,30 @@
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>n8n - SAML Connection Test Result</title>
|
||||||
|
<style>
|
||||||
|
body { background: rgb(251,252,254); font-family: 'Open Sans', sans-serif; padding: 10px; margin: auto; width: 500px; top: 40%; position: relative; }
|
||||||
|
h1 { color: rgb(240, 60, 60); font-size: 16px; font-weight: 400; margin: 0 0 10px 0; }
|
||||||
|
h2 { color: rgb(0, 0, 0); font-size: 12px; font-weight: 400; margin: 0 0 10px 0; }
|
||||||
|
button { border: 1px solid rgb(219, 223, 231); background: rgb(255, 255, 255); border-radius: 4px; padding: 10px; }
|
||||||
|
ul { border: 1px solid rgb(219, 223, 231); border-radius: 4px; padding: 10px; }
|
||||||
|
li { decoration: none; list-style: none; margin: 0 0 0px 0; color: rgb(125, 125, 125); font-size: 12px;}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div style="text-align:center">
|
||||||
|
<h1>SAML Connection Test failed</h1>
|
||||||
|
<h2>{{#if message}}{{message}}{{else}}A common issue could be that no email attribute is set{{/if}}</h2>
|
||||||
|
<button onclick="window.close()">You can close this window now</button>
|
||||||
|
<p></p>
|
||||||
|
{{#with attributes}}
|
||||||
|
<h2>Here are the attributes returned by your SAML IdP:</h2>
|
||||||
|
<ul>
|
||||||
|
<li><strong>Email:</strong> {{#if email}}{{email}}{{else}}(n/a){{/if}}</li>
|
||||||
|
<li><strong>First Name:</strong> {{#if firstName}}{{firstName}}{{else}}(n/a){{/if}}</li>
|
||||||
|
<li><strong>Last Name:</strong> {{#if lastName}}{{lastName}}{{else}}(n/a){{/if}}</li>
|
||||||
|
<li><strong>UPN:</strong> {{#if userPrincipalName}}{{userPrincipalName}}{{else}}(n/a){{/if}}</li>
|
||||||
|
{{/with}}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -0,0 +1,27 @@
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>n8n - SAML Connection Test Result</title>
|
||||||
|
<style>
|
||||||
|
body { background: rgb(251,252,254); font-family: 'Open Sans', sans-serif; padding: 10px; margin: auto; width: 500px; top: 40%; position: relative; }
|
||||||
|
h1 { color: rgb(0, 0, 0); font-size: 16px; font-weight: 400; margin: 0 0 10px 0; }
|
||||||
|
h2 { color: rgb(0, 0, 0); font-size: 12px; font-weight: 400; margin: 0 0 10px 0; }
|
||||||
|
button { border: 1px solid rgb(219, 223, 231); background: rgb(255, 255, 255); border-radius: 4px; padding: 10px; }
|
||||||
|
ul { border: 1px solid rgb(219, 223, 231); border-radius: 4px; padding: 10px; }
|
||||||
|
li { decoration: none; list-style: none; margin: 0 0 0px 0; color: rgb(125, 125, 125); font-size: 12px;}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div style="text-align:center">
|
||||||
|
<h1>SAML Connection Test was successful</h1>
|
||||||
|
<button onclick="window.close()">You can close this window now</button>
|
||||||
|
<p></p>
|
||||||
|
<h2>Here are the attributes returned by your SAML IdP:</h2>
|
||||||
|
<ul>
|
||||||
|
<li><strong>Email:</strong> {{#if email}}{{email}}{{else}}(n/a){{/if}}</li>
|
||||||
|
<li><strong>First Name:</strong> {{#if firstName}}{{firstName}}{{else}}(n/a){{/if}}</li>
|
||||||
|
<li><strong>Last Name:</strong> {{#if lastName}}{{lastName}}{{else}}(n/a){{/if}}</li>
|
||||||
|
<li><strong>UPN:</strong> {{#if userPrincipalName}}{{userPrincipalName}}{{else}}(n/a){{/if}}</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
Loading…
Reference in a new issue