fix: Apply credential overwrites recursively (#5072)

This ensures that overwrites defined for a parent credential type also applies to all credentials extending it.
This commit is contained in:
कारतोफ्फेलस्क्रिप्ट™ 2023-01-04 18:16:48 +01:00 committed by GitHub
parent f1184ccab5
commit 5d746c4a83
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 73 additions and 18 deletions

View file

@ -84,6 +84,7 @@
"@types/lodash.set": "^4.3.6", "@types/lodash.set": "^4.3.6",
"@types/lodash.split": "^4.4.7", "@types/lodash.split": "^4.4.7",
"@types/lodash.unionby": "^4.8.7", "@types/lodash.unionby": "^4.8.7",
"@types/lodash.uniq": "^4.5.7",
"@types/lodash.uniqby": "^4.7.7", "@types/lodash.uniqby": "^4.7.7",
"@types/lodash.unset": "^4.5.7", "@types/lodash.unset": "^4.5.7",
"@types/parseurl": "^1.3.1", "@types/parseurl": "^1.3.1",
@ -156,6 +157,7 @@
"lodash.set": "^4.3.2", "lodash.set": "^4.3.2",
"lodash.split": "^4.4.2", "lodash.split": "^4.4.2",
"lodash.unionby": "^4.8.0", "lodash.unionby": "^4.8.0",
"lodash.uniq": "^4.5.0",
"lodash.uniqby": "^4.7.0", "lodash.uniqby": "^4.7.0",
"lodash.unset": "^4.5.2", "lodash.unset": "^4.5.2",
"luxon": "^3.1.0", "luxon": "^3.1.0",

View file

@ -8,7 +8,9 @@ import type {
import { RESPONSE_ERROR_MESSAGES } from './constants'; import { RESPONSE_ERROR_MESSAGES } from './constants';
class CredentialTypesClass implements ICredentialTypes { class CredentialTypesClass implements ICredentialTypes {
constructor(private nodesAndCredentials: INodesAndCredentials) {} constructor(private nodesAndCredentials: INodesAndCredentials) {
nodesAndCredentials.credentialTypes = this;
}
recognizes(type: string) { recognizes(type: string) {
return type in this.knownCredentials || type in this.loadedCredentials; return type in this.knownCredentials || type in this.loadedCredentials;
@ -22,6 +24,22 @@ class CredentialTypesClass implements ICredentialTypes {
return this.knownCredentials[type]?.nodesToTestWith ?? []; return this.knownCredentials[type]?.nodesToTestWith ?? [];
} }
/**
* Returns all parent types of the given credential type
*/
getParentTypes(typeName: string): string[] {
const credentialType = this.getByName(typeName);
if (credentialType?.extends === undefined) return [];
const types: string[] = [];
credentialType.extends.forEach((type: string) => {
types.push(type);
types.push(...this.getParentTypes(type));
});
return types;
}
private getCredential(type: string): LoadedClass<ICredentialType> { private getCredential(type: string): LoadedClass<ICredentialType> {
const loadedCredentials = this.loadedCredentials; const loadedCredentials = this.loadedCredentials;
if (type in loadedCredentials) { if (type in loadedCredentials) {

View file

@ -247,18 +247,7 @@ export class CredentialsHelper extends ICredentialsHelper {
* Returns all parent types of the given credential type * Returns all parent types of the given credential type
*/ */
getParentTypes(typeName: string): string[] { getParentTypes(typeName: string): string[] {
const credentialType = this.credentialTypes.getByName(typeName); return this.credentialTypes.getParentTypes(typeName);
if (credentialType === undefined || credentialType.extends === undefined) {
return [];
}
let types: string[] = [];
credentialType.extends.forEach((type: string) => {
types = [...types, type, ...this.getParentTypes(type)];
});
return types;
} }
/** /**

View file

@ -86,8 +86,13 @@ class CredentialsOverwritesClass {
return overwrites; return overwrites;
} }
private get(type: string): ICredentialDataDecryptedObject | undefined { private get(name: string): ICredentialDataDecryptedObject | undefined {
return this.overwriteData[type]; const parentTypes = this.credentialTypes.getParentTypes(name);
return [name, ...parentTypes]
.reverse()
.map((type) => this.overwriteData[type])
.filter((type) => !!type)
.reduce((acc, current) => Object.assign(acc, current), {});
} }
getAll(): ICredentialsOverwrite { getAll(): ICredentialsOverwrite {

View file

@ -1,3 +1,4 @@
import uniq from 'lodash.uniq';
import { import {
CUSTOM_EXTENSION_ENV, CUSTOM_EXTENSION_ENV,
UserSettings, UserSettings,
@ -8,6 +9,7 @@ import {
Types, Types,
} from 'n8n-core'; } from 'n8n-core';
import type { import type {
ICredentialTypes,
ILogger, ILogger,
INodesAndCredentials, INodesAndCredentials,
KnownNodesAndCredentials, KnownNodesAndCredentials,
@ -46,6 +48,8 @@ export class LoadNodesAndCredentialsClass implements INodesAndCredentials {
includeNodes = config.getEnv('nodes.include'); includeNodes = config.getEnv('nodes.include');
credentialTypes: ICredentialTypes;
logger: ILogger; logger: ILogger;
async init() { async init() {
@ -68,8 +72,21 @@ export class LoadNodesAndCredentialsClass implements INodesAndCredentials {
async generateTypesForFrontend() { async generateTypesForFrontend() {
const credentialsOverwrites = CredentialsOverwrites().getAll(); const credentialsOverwrites = CredentialsOverwrites().getAll();
for (const credential of this.types.credentials) { for (const credential of this.types.credentials) {
const overwrittenProperties = [];
this.credentialTypes
.getParentTypes(credential.name)
.reverse()
.map((name) => credentialsOverwrites[name])
.forEach((overwrite) => {
if (overwrite) overwrittenProperties.push(...Object.keys(overwrite));
});
if (credential.name in credentialsOverwrites) { if (credential.name in credentialsOverwrites) {
credential.__overwrittenProperties = Object.keys(credentialsOverwrites[credential.name]); overwrittenProperties.push(...Object.keys(credentialsOverwrites[credential.name]));
}
if (overwrittenProperties.length) {
credential.__overwrittenProperties = uniq(overwrittenProperties);
} }
} }

View file

@ -8,6 +8,7 @@ import set from 'lodash.set';
import { BinaryDataManager, UserSettings } from 'n8n-core'; import { BinaryDataManager, UserSettings } from 'n8n-core';
import { import {
ICredentialType, ICredentialType,
ICredentialTypes,
IDataObject, IDataObject,
IExecuteFunctions, IExecuteFunctions,
INode, INode,
@ -71,6 +72,7 @@ import { eventBusRouter } from '@/eventbus/eventBusRoutes';
const loadNodesAndCredentials: INodesAndCredentials = { const loadNodesAndCredentials: INodesAndCredentials = {
loaded: { nodes: {}, credentials: {} }, loaded: { nodes: {}, credentials: {} },
known: { nodes: {}, credentials: {} }, known: { nodes: {}, credentials: {} },
credentialTypes: {} as ICredentialTypes,
}; };
const mockNodeTypes = NodeTypes(loadNodesAndCredentials); const mockNodeTypes = NodeTypes(loadNodesAndCredentials);
@ -160,7 +162,7 @@ export async function initTestServer({
* Pre-requisite: Mock the telemetry module before calling. * Pre-requisite: Mock the telemetry module before calling.
*/ */
export function initTestTelemetry() { export function initTestTelemetry() {
void InternalHooksManager.init('test-instance-id', 'test-version', mockNodeTypes); void InternalHooksManager.init('test-instance-id', mockNodeTypes);
} }
/** /**

View file

@ -43,4 +43,5 @@ const mockNodesAndCredentials = (): INodesAndCredentials => ({
}, },
}, },
known: { nodes: {}, credentials: {} }, known: { nodes: {}, credentials: {} },
credentialTypes: {} as ICredentialTypes,
}); });

View file

@ -2,6 +2,7 @@ import {
IAuthenticateGeneric, IAuthenticateGeneric,
ICredentialDataDecryptedObject, ICredentialDataDecryptedObject,
ICredentialType, ICredentialType,
ICredentialTypes,
IHttpRequestOptions, IHttpRequestOptions,
INode, INode,
INodeProperties, INodeProperties,
@ -16,6 +17,7 @@ const TEST_ENCRYPTION_KEY = 'test';
const mockNodesAndCredentials: INodesAndCredentials = { const mockNodesAndCredentials: INodesAndCredentials = {
loaded: { nodes: {}, credentials: {} }, loaded: { nodes: {}, credentials: {} },
known: { nodes: {}, credentials: {} }, known: { nodes: {}, credentials: {} },
credentialTypes: {} as ICredentialTypes,
}; };
describe('CredentialsHelper', () => { describe('CredentialsHelper', () => {

View file

@ -1,5 +1,11 @@
import { v4 as uuid } from 'uuid'; import { v4 as uuid } from 'uuid';
import { INodeTypeData, INodeTypes, SubworkflowOperationError, Workflow } from 'n8n-workflow'; import {
ICredentialTypes,
INodeTypeData,
INodeTypes,
SubworkflowOperationError,
Workflow,
} from 'n8n-workflow';
import config from '@/config'; import config from '@/config';
import * as Db from '@/Db'; import * as Db from '@/Db';
@ -35,6 +41,7 @@ beforeAll(async () => {
credentials: {}, credentials: {},
}, },
known: { nodes: {}, credentials: {} }, known: { nodes: {}, credentials: {} },
credentialTypes: {} as ICredentialTypes,
}); });
credentialOwnerRole = await testDb.getCredentialOwnerRole(); credentialOwnerRole = await testDb.getCredentialOwnerRole();

View file

@ -336,6 +336,7 @@ export interface ICredentialTypes {
recognizes(credentialType: string): boolean; recognizes(credentialType: string): boolean;
getByName(credentialType: string): ICredentialType; getByName(credentialType: string): ICredentialType;
getNodeTypesToTestWith(type: string): string[]; getNodeTypesToTestWith(type: string): string[];
getParentTypes(typeName: string): string[];
} }
// The way the credentials get saved in the database (data encrypted) // The way the credentials get saved in the database (data encrypted)
@ -1492,6 +1493,7 @@ export type LoadedNodesAndCredentials = {
export interface INodesAndCredentials { export interface INodesAndCredentials {
known: KnownNodesAndCredentials; known: KnownNodesAndCredentials;
loaded: LoadedNodesAndCredentials; loaded: LoadedNodesAndCredentials;
credentialTypes: ICredentialTypes;
} }
export interface IRun { export interface IRun {

View file

@ -122,6 +122,7 @@ importers:
'@types/lodash.set': ^4.3.6 '@types/lodash.set': ^4.3.6
'@types/lodash.split': ^4.4.7 '@types/lodash.split': ^4.4.7
'@types/lodash.unionby': ^4.8.7 '@types/lodash.unionby': ^4.8.7
'@types/lodash.uniq': ^4.5.7
'@types/lodash.uniqby': ^4.7.7 '@types/lodash.uniqby': ^4.7.7
'@types/lodash.unset': ^4.5.7 '@types/lodash.unset': ^4.5.7
'@types/parseurl': ^1.3.1 '@types/parseurl': ^1.3.1
@ -179,6 +180,7 @@ importers:
lodash.set: ^4.3.2 lodash.set: ^4.3.2
lodash.split: ^4.4.2 lodash.split: ^4.4.2
lodash.unionby: ^4.8.0 lodash.unionby: ^4.8.0
lodash.uniq: ^4.5.0
lodash.uniqby: ^4.7.0 lodash.uniqby: ^4.7.0
lodash.unset: ^4.5.2 lodash.unset: ^4.5.2
luxon: ^3.1.0 luxon: ^3.1.0
@ -271,6 +273,7 @@ importers:
lodash.set: 4.3.2 lodash.set: 4.3.2
lodash.split: 4.4.2 lodash.split: 4.4.2
lodash.unionby: 4.8.0 lodash.unionby: 4.8.0
lodash.uniq: 4.5.0
lodash.uniqby: 4.7.0 lodash.uniqby: 4.7.0
lodash.unset: 4.5.2 lodash.unset: 4.5.2
luxon: 3.1.1 luxon: 3.1.1
@ -332,6 +335,7 @@ importers:
'@types/lodash.set': 4.3.7 '@types/lodash.set': 4.3.7
'@types/lodash.split': 4.4.7 '@types/lodash.split': 4.4.7
'@types/lodash.unionby': 4.8.7 '@types/lodash.unionby': 4.8.7
'@types/lodash.uniq': 4.5.7
'@types/lodash.uniqby': 4.7.7 '@types/lodash.uniqby': 4.7.7
'@types/lodash.unset': 4.5.7 '@types/lodash.unset': 4.5.7
'@types/parseurl': 1.3.1 '@types/parseurl': 1.3.1
@ -5929,6 +5933,12 @@ packages:
'@types/lodash': 4.14.186 '@types/lodash': 4.14.186
dev: true dev: true
/@types/lodash.uniq/4.5.7:
resolution: {integrity: sha512-qg7DeAbdZMi6DGvCxThlJycykLLhETrJrQZ6F2KaZ+o0sNK1qRHz46lgNA+nHHjwrmA2a91DyiZTp3ey3m1rEw==}
dependencies:
'@types/lodash': 4.14.186
dev: true
/@types/lodash.uniqby/4.7.7: /@types/lodash.uniqby/4.7.7:
resolution: {integrity: sha512-sv2g6vkCIvEUsK5/Vq17haoZaisfj2EWW8mP7QWlnKi6dByoNmeuHDDXHR7sabuDqwO4gvU7ModIL22MmnOocg==} resolution: {integrity: sha512-sv2g6vkCIvEUsK5/Vq17haoZaisfj2EWW8mP7QWlnKi6dByoNmeuHDDXHR7sabuDqwO4gvU7ModIL22MmnOocg==}
dependencies: dependencies: