1
1
mirror of https://github.com/n8n-io/n8n.git synced 2024-11-10 12:35:46 +03:00

Do not save credential default values to DB #PROD-52

This commit is contained in:
Jan Oberhauser 2020-05-14 14:27:19 +02:00
parent 09a4b44875
commit cfe6e72440
8 changed files with 126 additions and 32 deletions

View File

@ -5,16 +5,15 @@ import {
import {
CredentialsOverwrites,
ICredentialsTypeData,
} from './';
class CredentialTypesClass implements ICredentialTypesInterface {
credentialTypes: {
[key: string]: ICredentialType
} = {};
credentialTypes: ICredentialsTypeData = {};
async init(credentialTypes: { [key: string]: ICredentialType }): Promise<void> {
async init(credentialTypes: ICredentialsTypeData): Promise<void> {
this.credentialTypes = credentialTypes;
// Load the credentials overwrites if any exist

View File

@ -5,10 +5,13 @@ import {
import {
ICredentialDataDecryptedObject,
ICredentialsHelper,
INodeParameters,
NodeHelpers,
} from 'n8n-workflow';
import {
CredentialsOverwrites,
CredentialTypes,
Db,
ICredentialsDb,
} from './';
@ -42,15 +45,51 @@ export class CredentialsHelper extends ICredentialsHelper {
*
* @param {string} name Name of the credentials to return data of
* @param {string} type Type of the credentials to return data of
* @param {boolean} [raw] Return the data as supplied without defaults or overwrites
* @returns {ICredentialDataDecryptedObject}
* @memberof CredentialsHelper
*/
getDecrypted(name: string, type: string): ICredentialDataDecryptedObject {
getDecrypted(name: string, type: string, raw?: boolean): ICredentialDataDecryptedObject {
const credentials = this.getCredentials(name, type);
const decryptedDataOriginal = credentials.getData(this.encryptionKey);
if (raw === true) {
return decryptedDataOriginal;
}
return this.applyDefaultsAndOverwrites(decryptedDataOriginal, type);
}
/**
* Applies credential default data and overwrites
*
* @param {ICredentialDataDecryptedObject} decryptedDataOriginal The credential data to overwrite data on
* @param {string} type Type of the credentials to overwrite data of
* @returns {ICredentialDataDecryptedObject}
* @memberof CredentialsHelper
*/
applyDefaultsAndOverwrites(decryptedDataOriginal: ICredentialDataDecryptedObject, type: string): ICredentialDataDecryptedObject {
const credentialTypes = CredentialTypes();
const credentialType = credentialTypes.getByName(type);
if (credentialType === undefined) {
throw new Error(`The credential type "${type}" is not known and can so not be decrypted.`);
}
// Add the default credential values
const decryptedData = NodeHelpers.getNodeParameters(credentialType.properties, decryptedDataOriginal as INodeParameters, true, false) as ICredentialDataDecryptedObject;
if (decryptedDataOriginal.oauthTokenData !== undefined) {
// The OAuth data gets removed as it is not defined specifically as a parameter
// on the credentials so add it back in case it was set
decryptedData.oauthTokenData = decryptedDataOriginal.oauthTokenData;
}
// Load and apply the credentials overwrites if any exist
const credentialsOverwrites = CredentialsOverwrites();
return credentialsOverwrites.applyOverwrite(credentials.type, credentials.getData(this.encryptionKey));
return credentialsOverwrites.applyOverwrite(type, decryptedData);
}

View File

@ -12,7 +12,14 @@ class CredentialsOverwritesClass {
private overwriteData: ICredentialsOverwrite = {};
async init() {
async init(overwriteData?: ICredentialsOverwrite) {
if (overwriteData !== undefined) {
// If data is already given it can directly be set instead of
// loaded from environment
this.overwriteData = overwriteData;
return;
}
const data = await GenericHelpers.getConfigValue('credentials.overwrite') as string;
try {

View File

@ -2,6 +2,7 @@ import {
ICredentialDataDecryptedObject,
ICredentialsDecrypted,
ICredentialsEncrypted,
ICredentialType,
IDataObject,
IExecutionError,
IRun,
@ -36,6 +37,10 @@ export interface ICustomRequest extends Request {
parsedUrl: Url | undefined;
}
export interface ICredentialsTypeData {
[key: string]: ICredentialType;
}
export interface ICredentialsOverwrite {
[key: string]: ICredentialDataDecryptedObject;
}
@ -350,7 +355,10 @@ export interface IWorkflowExecutionDataProcess {
workflowData: IWorkflowBase;
}
export interface IWorkflowExecutionDataProcessWithExecution extends IWorkflowExecutionDataProcess {
credentialsOverwrite: ICredentialsOverwrite;
credentialsTypeData: ICredentialsTypeData;
executionId: string;
nodeTypeData: ITransferNodeTypes;
}

View File

@ -21,7 +21,7 @@ import * as csrf from 'csrf';
import {
ActiveExecutions,
ActiveWorkflowRunner,
CredentialsOverwrites,
CredentialsHelper,
CredentialTypes,
Db,
IActivationError,
@ -51,7 +51,6 @@ import {
WorkflowCredentials,
WebhookHelpers,
WorkflowExecuteAdditionalData,
WorkflowHelpers,
WorkflowRunner,
GenericHelpers,
} from './';
@ -63,6 +62,7 @@ import {
} from 'n8n-core';
import {
ICredentialsEncrypted,
ICredentialType,
IDataObject,
INodeCredentials,
@ -70,6 +70,7 @@ import {
INodeParameters,
INodePropertyOptions,
IRunData,
IWorkflowCredentials,
Workflow,
} from 'n8n-workflow';
@ -913,12 +914,15 @@ class App {
throw new Error('No encryption key got found to decrypt the credentials!');
}
const credentials = new Credentials(result.name, result.type, result.nodesAccess, result.data);
const savedCredentialsData = credentials.getData(encryptionKey);
// Load the credentials overwrites if any exist
const credentialsOverwrites = CredentialsOverwrites();
const oauthCredentials = credentialsOverwrites.applyOverwrite(credentials.type, savedCredentialsData);
// Decrypt the currently saved credentials
const workflowCredentials: IWorkflowCredentials = {
[result.type as string]: {
[result.name as string]: result as ICredentialsEncrypted,
},
};
const credentialsHelper = new CredentialsHelper(workflowCredentials, encryptionKey);
const decryptedDataOriginal = credentialsHelper.getDecrypted(result.name, result.type, true);
const oauthCredentials = credentialsHelper.applyDefaultsAndOverwrites(decryptedDataOriginal, result.type);
const token = new csrf();
// Generate a CSRF prevention token and send it as a OAuth2 state stringma/ERR
@ -939,8 +943,11 @@ class App {
state: stateEncodedStr,
});
savedCredentialsData.csrfSecret = csrfSecret;
credentials.setData(savedCredentialsData, encryptionKey);
// Encrypt the data
const credentials = new Credentials(result.name, result.type, result.nodesAccess);
decryptedDataOriginal.csrfSecret = csrfSecret;
credentials.setData(decryptedDataOriginal, encryptionKey);
const newCredentialsData = credentials.getDataToSave() as unknown as ICredentialsDb;
// Add special database related data
@ -992,15 +999,18 @@ class App {
return ResponseHelper.sendErrorResponse(res, errorResponse);
}
const credentials = new Credentials(result.name, result.type, result.nodesAccess, result.data);
const savedCredentialsData = credentials.getData(encryptionKey!);
// Load the credentials overwrites if any exist
const credentialsOverwrites = CredentialsOverwrites();
const oauthCredentials = credentialsOverwrites.applyOverwrite(credentials.type, savedCredentialsData);
// Decrypt the currently saved credentials
const workflowCredentials: IWorkflowCredentials = {
[result.type as string]: {
[result.name as string]: result as ICredentialsEncrypted,
},
};
const credentialsHelper = new CredentialsHelper(workflowCredentials, encryptionKey);
const decryptedDataOriginal = credentialsHelper.getDecrypted(result.name, result.type, true);
const oauthCredentials = credentialsHelper.applyDefaultsAndOverwrites(decryptedDataOriginal, result.type);
const token = new csrf();
if (oauthCredentials.csrfSecret === undefined || !token.verify(oauthCredentials.csrfSecret as string, state.token)) {
if (decryptedDataOriginal.csrfSecret === undefined || !token.verify(decryptedDataOriginal.csrfSecret as string, state.token)) {
const errorResponse = new ResponseHelper.ResponseError('The OAuth2 callback state is invalid!', undefined, 404);
return ResponseHelper.sendErrorResponse(res, errorResponse);
}
@ -1032,18 +1042,19 @@ class App {
return ResponseHelper.sendErrorResponse(res, errorResponse);
}
if (savedCredentialsData.oauthTokenData) {
if (decryptedDataOriginal.oauthTokenData) {
// Only overwrite supplied data as some providers do for example just return the
// refresh_token on the very first request and not on subsequent ones.
Object.assign(savedCredentialsData.oauthTokenData, oauthToken.data);
Object.assign(decryptedDataOriginal.oauthTokenData, oauthToken.data);
} else {
// No data exists so simply set
savedCredentialsData.oauthTokenData = oauthToken.data;
decryptedDataOriginal.oauthTokenData = oauthToken.data;
}
_.unset(savedCredentialsData, 'csrfSecret');
_.unset(decryptedDataOriginal, 'csrfSecret');
credentials.setData(savedCredentialsData, encryptionKey);
const credentials = new Credentials(result.name, result.type, result.nodesAccess);
credentials.setData(decryptedDataOriginal, encryptionKey);
const newCredentialsData = credentials.getDataToSave() as unknown as ICredentialsDb;
// Add special database related data
newCredentialsData.updatedAt = this.getCurrentDate();

View File

@ -1,5 +1,9 @@
import {
ActiveExecutions,
CredentialsOverwrites,
CredentialTypes,
ICredentialsOverwrite,
ICredentialsTypeData,
IProcessMessageDataHook,
ITransferNodeTypes,
IWorkflowExecutionDataProcess,
@ -31,12 +35,14 @@ import { fork } from 'child_process';
export class WorkflowRunner {
activeExecutions: ActiveExecutions.ActiveExecutions;
credentialsOverwrites: ICredentialsOverwrite;
push: Push.Push;
constructor() {
this.push = Push.getInstance();
this.activeExecutions = ActiveExecutions.getInstance();
this.credentialsOverwrites = CredentialsOverwrites().getAll();
}
@ -192,8 +198,16 @@ export class WorkflowRunner {
nodeTypeData = WorkflowHelpers.getNodeTypeData(data.workflowData.nodes);
}
const credentialTypes = CredentialTypes();
const credentialTypeData: ICredentialsTypeData = {};
for (const credentialType of Object.keys(data.credentials)) {
credentialTypeData[credentialType] = credentialTypes.getByName(credentialType);
}
(data as unknown as IWorkflowExecutionDataProcessWithExecution).executionId = executionId;
(data as unknown as IWorkflowExecutionDataProcessWithExecution).nodeTypeData = nodeTypeData;
(data as unknown as IWorkflowExecutionDataProcessWithExecution).credentialsOverwrite = this.credentialsOverwrites;
(data as unknown as IWorkflowExecutionDataProcessWithExecution).credentialsTypeData = credentialTypeData; // TODO: Still needs correct value
const workflowHooks = WorkflowExecuteAdditionalData.getWorkflowHooksMain(data, executionId);

View File

@ -1,5 +1,7 @@
import {
CredentialsOverwrites,
CredentialTypes,
IWorkflowExecutionDataProcessWithExecution,
NodeTypes,
WorkflowExecuteAdditionalData,
@ -58,6 +60,14 @@ export class WorkflowRunnerProcess {
const nodeTypes = NodeTypes();
await nodeTypes.init(nodeTypesData);
// Init credential types the workflow uses (is needed to apply default values to credentials)
const credentialTypes = CredentialTypes();
await credentialTypes.init(inputData.credentialsTypeData);
// Load the credentials overwrites if any exist
const credentialsOverwrites = CredentialsOverwrites();
await credentialsOverwrites.init();
this.workflow = new Workflow({ id: this.data.workflowData.id as string | undefined, name: this.data.workflowData.name, nodes: this.data.workflowData!.nodes, connections: this.data.workflowData!.connections, active: this.data.workflowData!.active, nodeTypes, staticData: this.data.workflowData!.staticData, settings: this.data.workflowData!.settings});
const additionalData = await WorkflowExecuteAdditionalData.getBase(this.data.credentials);
additionalData.hooks = this.getProcessForwardHooks();

View File

@ -55,7 +55,7 @@
</el-tooltip>
</div>
<div v-for="parameter in credentialProperties" :key="parameter.name">
<el-row v-if="displayCredentialParameter(parameter)" class="parameter-wrapper">
<el-row class="parameter-wrapper">
<el-col :span="6" class="parameter-name">
{{parameter.displayName}}:
<el-tooltip placement="top" class="parameter-info" v-if="parameter.description" effect="light">
@ -128,6 +128,7 @@ import {
INodeParameters,
INodeProperties,
INodeTypeDescription,
NodeHelpers,
} from 'n8n-workflow';
import ParameterInput from '@/components/ParameterInput.vue';
@ -199,6 +200,9 @@ export default mixins(
},
credentialProperties (): INodeProperties[] {
return this.credentialTypeData.properties.filter((propertyData: INodeProperties) => {
if (!this.displayCredentialParameter(propertyData)) {
return false;
}
return !this.credentialTypeData.__overwrittenProperties || !this.credentialTypeData.__overwrittenProperties.includes(propertyData.name);
});
},
@ -283,7 +287,8 @@ export default mixins(
name: this.name,
type: (this.credentialTypeData as ICredentialType).name,
nodesAccess,
data: this.propertyValue,
// Save only the none default data
data: NodeHelpers.getNodeParameters(this.credentialTypeData.properties as INodeProperties[], this.propertyValue as INodeParameters, false, false),
} as ICredentialsDecrypted;
let result;
@ -402,7 +407,8 @@ export default mixins(
name: this.name,
type: (this.credentialTypeData as ICredentialType).name,
nodesAccess,
data: this.propertyValue,
// Save only the none default data
data: NodeHelpers.getNodeParameters(this.credentialTypeData.properties as INodeProperties[], this.propertyValue as INodeParameters, false, false),
} as ICredentialsDecrypted;
let result;