UNPKG

n8n

Version:

n8n Workflow Automation Tool

338 lines 16.2 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; return c > 3 && r && Object.defineProperty(target, key, r), r; }; var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; var __metadata = (this && this.__metadata) || function (k, v) { if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.SamlService = void 0; const typedi_1 = __importStar(require("typedi")); const n8n_workflow_1 = require("n8n-workflow"); const serviceProvider_ee_1 = require("./serviceProvider.ee"); const ssoHelpers_1 = require("../ssoHelpers"); const constants_1 = require("./constants"); const samlHelpers_1 = require("./samlHelpers"); const axios_1 = __importDefault(require("axios")); const https_1 = __importDefault(require("https")); const samlValidator_1 = require("./samlValidator"); const Logger_1 = require("../../Logger"); const user_repository_1 = require("../../databases/repositories/user.repository"); const settings_repository_1 = require("../../databases/repositories/settings.repository"); const bad_request_error_1 = require("../../errors/response-errors/bad-request.error"); const auth_error_1 = require("../../errors/response-errors/auth.error"); const url_service_1 = require("../../services/url.service"); let SamlService = class SamlService { get samlPreferences() { return { ...this._samlPreferences, loginEnabled: (0, samlHelpers_1.isSamlLoginEnabled)(), loginLabel: (0, samlHelpers_1.getSamlLoginLabel)(), }; } constructor(logger, urlService) { this.logger = logger; this.urlService = urlService; this._samlPreferences = { mapping: { email: 'http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress', firstName: 'http://schemas.xmlsoap.org/ws/2005/05/identity/claims/firstname', lastName: 'http://schemas.xmlsoap.org/ws/2005/05/identity/claims/lastname', userPrincipalName: 'http://schemas.xmlsoap.org/ws/2005/05/identity/claims/upn', }, metadata: '', metadataUrl: '', ignoreSSL: false, loginBinding: 'redirect', acsBinding: 'post', authnRequestsSigned: false, loginEnabled: false, loginLabel: 'SAML', wantAssertionsSigned: true, wantMessageSigned: true, relayState: this.urlService.getInstanceBaseUrl(), signatureConfig: { prefix: 'ds', location: { reference: '/samlp:Response/saml:Issuer', action: 'after', }, }, }; } async init() { await this.loadFromDbAndApplySamlPreferences(false); if ((0, samlHelpers_1.isSamlLicensedAndEnabled)()) { await this.loadSamlify(); await this.loadFromDbAndApplySamlPreferences(true); } } async loadSamlify() { if (this.samlify === undefined) { this.logger.debug('Loading samlify library into memory'); this.samlify = await Promise.resolve().then(() => __importStar(require('samlify'))); } this.samlify.setSchemaValidator({ validate: async (response) => { const valid = await (0, samlValidator_1.validateResponse)(response); if (!valid) { throw new n8n_workflow_1.ApplicationError('Invalid SAML response'); } }, }); } getIdentityProviderInstance(forceRecreate = false) { if (this.samlify === undefined) { throw new n8n_workflow_1.ApplicationError('Samlify is not initialized'); } if (this.identityProviderInstance === undefined || forceRecreate) { this.identityProviderInstance = this.samlify.IdentityProvider({ metadata: this._samlPreferences.metadata, }); } return this.identityProviderInstance; } getServiceProviderInstance() { if (this.samlify === undefined) { throw new n8n_workflow_1.ApplicationError('Samlify is not initialized'); } return (0, serviceProvider_ee_1.getServiceProviderInstance)(this._samlPreferences, this.samlify); } async getLoginRequestUrl(relayState, binding) { var _a; await this.loadSamlify(); if (binding === undefined) binding = (_a = this._samlPreferences.loginBinding) !== null && _a !== void 0 ? _a : 'redirect'; if (binding === 'post') { return { binding, context: this.getPostLoginRequestUrl(relayState), }; } else { return { binding, context: this.getRedirectLoginRequestUrl(relayState), }; } } getRedirectLoginRequestUrl(relayState) { const sp = this.getServiceProviderInstance(); sp.entitySetting.relayState = relayState !== null && relayState !== void 0 ? relayState : this.urlService.getInstanceBaseUrl(); const loginRequest = sp.createLoginRequest(this.getIdentityProviderInstance(), 'redirect'); return loginRequest; } getPostLoginRequestUrl(relayState) { const sp = this.getServiceProviderInstance(); sp.entitySetting.relayState = relayState !== null && relayState !== void 0 ? relayState : this.urlService.getInstanceBaseUrl(); const loginRequest = sp.createLoginRequest(this.getIdentityProviderInstance(), 'post'); return loginRequest; } async handleSamlLogin(req, binding) { const attributes = await this.getAttributesFromLoginResponse(req, binding); if (attributes.email) { const lowerCasedEmail = attributes.email.toLowerCase(); const user = await typedi_1.default.get(user_repository_1.UserRepository).findOne({ where: { email: lowerCasedEmail }, relations: ['authIdentities'], }); if (user) { if (user.authIdentities.find((e) => e.providerType === 'saml' && e.providerId === attributes.userPrincipalName)) { return { authenticatedUser: user, attributes, onboardingRequired: false, }; } else { const updatedUser = await (0, samlHelpers_1.updateUserFromSamlAttributes)(user, attributes); const onboardingRequired = !updatedUser.firstName || !updatedUser.lastName; return { authenticatedUser: updatedUser, attributes, onboardingRequired, }; } } else { if ((0, ssoHelpers_1.isSsoJustInTimeProvisioningEnabled)()) { const newUser = await (0, samlHelpers_1.createUserFromSamlAttributes)(attributes); return { authenticatedUser: newUser, attributes, onboardingRequired: true, }; } } } return { authenticatedUser: undefined, attributes, onboardingRequired: false, }; } async setSamlPreferences(prefs) { await this.loadSamlify(); await this.loadPreferencesWithoutValidation(prefs); if (prefs.metadataUrl) { const fetchedMetadata = await this.fetchMetadataFromUrl(); if (fetchedMetadata) { this._samlPreferences.metadata = fetchedMetadata; } } else if (prefs.metadata) { const validationResult = await (0, samlValidator_1.validateMetadata)(prefs.metadata); if (!validationResult) { throw new n8n_workflow_1.ApplicationError('Invalid SAML metadata'); } } this.getIdentityProviderInstance(true); const result = await this.saveSamlPreferencesToDb(); return result; } async loadPreferencesWithoutValidation(prefs) { var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l; this._samlPreferences.loginBinding = (_a = prefs.loginBinding) !== null && _a !== void 0 ? _a : this._samlPreferences.loginBinding; this._samlPreferences.metadata = (_b = prefs.metadata) !== null && _b !== void 0 ? _b : this._samlPreferences.metadata; this._samlPreferences.mapping = (_c = prefs.mapping) !== null && _c !== void 0 ? _c : this._samlPreferences.mapping; this._samlPreferences.ignoreSSL = (_d = prefs.ignoreSSL) !== null && _d !== void 0 ? _d : this._samlPreferences.ignoreSSL; this._samlPreferences.acsBinding = (_e = prefs.acsBinding) !== null && _e !== void 0 ? _e : this._samlPreferences.acsBinding; this._samlPreferences.signatureConfig = (_f = prefs.signatureConfig) !== null && _f !== void 0 ? _f : this._samlPreferences.signatureConfig; this._samlPreferences.authnRequestsSigned = (_g = prefs.authnRequestsSigned) !== null && _g !== void 0 ? _g : this._samlPreferences.authnRequestsSigned; this._samlPreferences.wantAssertionsSigned = (_h = prefs.wantAssertionsSigned) !== null && _h !== void 0 ? _h : this._samlPreferences.wantAssertionsSigned; this._samlPreferences.wantMessageSigned = (_j = prefs.wantMessageSigned) !== null && _j !== void 0 ? _j : this._samlPreferences.wantMessageSigned; if (prefs.metadataUrl) { this._samlPreferences.metadataUrl = prefs.metadataUrl; } else if (prefs.metadata) { this._samlPreferences.metadataUrl = undefined; this._samlPreferences.metadata = prefs.metadata; } await (0, samlHelpers_1.setSamlLoginEnabled)((_k = prefs.loginEnabled) !== null && _k !== void 0 ? _k : (0, samlHelpers_1.isSamlLoginEnabled)()); (0, samlHelpers_1.setSamlLoginLabel)((_l = prefs.loginLabel) !== null && _l !== void 0 ? _l : (0, samlHelpers_1.getSamlLoginLabel)()); } async loadFromDbAndApplySamlPreferences(apply = true) { const samlPreferences = await typedi_1.default.get(settings_repository_1.SettingsRepository).findOne({ where: { key: constants_1.SAML_PREFERENCES_DB_KEY }, }); if (samlPreferences) { const prefs = (0, n8n_workflow_1.jsonParse)(samlPreferences.value); if (prefs) { if (apply) { await this.setSamlPreferences(prefs); } else { await this.loadPreferencesWithoutValidation(prefs); } return prefs; } } return; } async saveSamlPreferencesToDb() { const samlPreferences = await typedi_1.default.get(settings_repository_1.SettingsRepository).findOne({ where: { key: constants_1.SAML_PREFERENCES_DB_KEY }, }); const settingsValue = JSON.stringify(this.samlPreferences); let result; if (samlPreferences) { samlPreferences.value = settingsValue; result = await typedi_1.default.get(settings_repository_1.SettingsRepository).save(samlPreferences, { transaction: false, }); } else { result = await typedi_1.default.get(settings_repository_1.SettingsRepository).save({ key: constants_1.SAML_PREFERENCES_DB_KEY, value: settingsValue, loadOnStartup: true, }, { transaction: false }); } if (result) return (0, n8n_workflow_1.jsonParse)(result.value); return; } async fetchMetadataFromUrl() { await this.loadSamlify(); if (!this._samlPreferences.metadataUrl) throw new bad_request_error_1.BadRequestError('Error fetching SAML Metadata, no Metadata URL set'); try { const agent = new https_1.default.Agent({ rejectUnauthorized: !this._samlPreferences.ignoreSSL, }); const response = await axios_1.default.get(this._samlPreferences.metadataUrl, { httpsAgent: agent }); if (response.status === 200 && response.data) { const xml = (await response.data); const validationResult = await (0, samlValidator_1.validateMetadata)(xml); if (!validationResult) { throw new bad_request_error_1.BadRequestError(`Data received from ${this._samlPreferences.metadataUrl} is not valid SAML metadata.`); } return xml; } } catch (error) { throw new bad_request_error_1.BadRequestError(`Error fetching SAML Metadata from ${this._samlPreferences.metadataUrl}: ${error}`); } return; } async getAttributesFromLoginResponse(req, binding) { let parsedSamlResponse; if (!this._samlPreferences.mapping) throw new bad_request_error_1.BadRequestError('Error fetching SAML Attributes, no Attribute mapping set'); try { await this.loadSamlify(); parsedSamlResponse = await this.getServiceProviderInstance().parseLoginResponse(this.getIdentityProviderInstance(), binding, req); } catch (error) { throw new auth_error_1.AuthError(`SAML Authentication failed. Could not parse SAML response. ${error instanceof Error ? error.message : error}`); } const { attributes, missingAttributes } = (0, samlHelpers_1.getMappedSamlAttributesFromFlowResult)(parsedSamlResponse, this._samlPreferences.mapping); if (!attributes) { throw new auth_error_1.AuthError('SAML Authentication failed. Invalid SAML response.'); } if (missingAttributes.length > 0) { throw new auth_error_1.AuthError(`SAML Authentication failed. Invalid SAML response (missing attributes: ${missingAttributes.join(', ')}).`); } return attributes; } }; exports.SamlService = SamlService; exports.SamlService = SamlService = __decorate([ (0, typedi_1.Service)(), __metadata("design:paramtypes", [Logger_1.Logger, url_service_1.UrlService]) ], SamlService); //# sourceMappingURL=saml.service.ee.js.map