mirror of
https://github.com/Abdulazizzn/n8n-enterprise-unlocked.git
synced 2025-12-17 18:12:04 +00:00
fix(Microsoft SQL Node): Prevent SQL injection (#7467)
Github issue / Community forum post (link here to close automatically): --------- Co-authored-by: कारतोफ्फेलस्क्रिप्ट™ <aditya@netroy.in>
This commit is contained in:
@@ -11,21 +11,17 @@ import type {
|
||||
} from 'n8n-workflow';
|
||||
import { NodeOperationError } from 'n8n-workflow';
|
||||
|
||||
import mssql from 'mssql';
|
||||
|
||||
import type { ITables } from './TableInterface';
|
||||
import type { ITables } from './interfaces';
|
||||
|
||||
import {
|
||||
copyInputItem,
|
||||
configurePool,
|
||||
createTableStruct,
|
||||
executeQueryQueue,
|
||||
extractDeleteValues,
|
||||
extractUpdateCondition,
|
||||
extractUpdateSet,
|
||||
extractValues,
|
||||
formatColumns,
|
||||
deleteOperation,
|
||||
insertOperation,
|
||||
updateOperation,
|
||||
} from './GenericFunctions';
|
||||
import { chunk, flatten, generatePairedItemData, getResolvables } from '@utils/utilities';
|
||||
|
||||
import { flatten, generatePairedItemData, getResolvables } from '@utils/utilities';
|
||||
|
||||
export class MicrosoftSql implements INodeType {
|
||||
description: INodeTypeDescription = {
|
||||
@@ -127,13 +123,13 @@ export class MicrosoftSql implements INodeType {
|
||||
displayName: 'Columns',
|
||||
name: 'columns',
|
||||
type: 'string',
|
||||
requiresDataPath: 'multiple',
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: ['insert'],
|
||||
},
|
||||
},
|
||||
default: '',
|
||||
|
||||
placeholder: 'id,name,description',
|
||||
description:
|
||||
'Comma-separated list of the properties which should used as columns for the new rows',
|
||||
@@ -159,6 +155,7 @@ export class MicrosoftSql implements INodeType {
|
||||
displayName: 'Update Key',
|
||||
name: 'updateKey',
|
||||
type: 'string',
|
||||
requiresDataPath: 'single',
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: ['update'],
|
||||
@@ -174,6 +171,7 @@ export class MicrosoftSql implements INodeType {
|
||||
displayName: 'Columns',
|
||||
name: 'columns',
|
||||
type: 'string',
|
||||
requiresDataPath: 'multiple',
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: ['update'],
|
||||
@@ -205,6 +203,7 @@ export class MicrosoftSql implements INodeType {
|
||||
displayName: 'Delete Key',
|
||||
name: 'deleteKey',
|
||||
type: 'string',
|
||||
requiresDataPath: 'single',
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: ['delete'],
|
||||
@@ -227,23 +226,7 @@ export class MicrosoftSql implements INodeType {
|
||||
): Promise<INodeCredentialTestResult> {
|
||||
const credentials = credential.data as ICredentialDataDecryptedObject;
|
||||
try {
|
||||
const config = {
|
||||
server: credentials.server as string,
|
||||
port: credentials.port as number,
|
||||
database: credentials.database as string,
|
||||
user: credentials.user as string,
|
||||
password: credentials.password as string,
|
||||
domain: credentials.domain ? (credentials.domain as string) : undefined,
|
||||
connectionTimeout: credentials.connectTimeout as number,
|
||||
requestTimeout: credentials.requestTimeout as number,
|
||||
options: {
|
||||
encrypt: credentials.tls as boolean,
|
||||
enableArithAbort: false,
|
||||
tdsVersion: credentials.tdsVersion as string,
|
||||
trustServerCertificate: credentials.allowUnauthorizedCerts as boolean,
|
||||
},
|
||||
};
|
||||
const pool = new mssql.ConnectionPool(config);
|
||||
const pool = configurePool(credentials);
|
||||
await pool.connect();
|
||||
} catch (error) {
|
||||
return {
|
||||
@@ -262,24 +245,7 @@ export class MicrosoftSql implements INodeType {
|
||||
async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
|
||||
const credentials = await this.getCredentials('microsoftSql');
|
||||
|
||||
const config = {
|
||||
server: credentials.server as string,
|
||||
port: credentials.port as number,
|
||||
database: credentials.database as string,
|
||||
user: credentials.user as string,
|
||||
password: credentials.password as string,
|
||||
domain: credentials.domain ? (credentials.domain as string) : undefined,
|
||||
connectionTimeout: credentials.connectTimeout as number,
|
||||
requestTimeout: credentials.requestTimeout as number,
|
||||
options: {
|
||||
encrypt: credentials.tls as boolean,
|
||||
enableArithAbort: false,
|
||||
tdsVersion: credentials.tdsVersion as string,
|
||||
trustServerCertificate: credentials.allowUnauthorizedCerts as boolean,
|
||||
},
|
||||
};
|
||||
|
||||
const pool = new mssql.ConnectionPool(config);
|
||||
const pool = configurePool(credentials);
|
||||
await pool.connect();
|
||||
|
||||
const returnItems: INodeExecutionData[] = [];
|
||||
@@ -290,10 +256,6 @@ export class MicrosoftSql implements INodeType {
|
||||
|
||||
try {
|
||||
if (operation === 'executeQuery') {
|
||||
// ----------------------------------
|
||||
// executeQuery
|
||||
// ----------------------------------
|
||||
|
||||
let rawQuery = this.getNodeParameter('query', 0) as string;
|
||||
|
||||
for (const resolvable of getResolvables(rawQuery)) {
|
||||
@@ -309,76 +271,27 @@ export class MicrosoftSql implements INodeType {
|
||||
|
||||
responseData = result;
|
||||
} else if (operation === 'insert') {
|
||||
// ----------------------------------
|
||||
// insert
|
||||
// ----------------------------------
|
||||
|
||||
const tables = createTableStruct(this.getNodeParameter, items);
|
||||
await executeQueryQueue(
|
||||
tables,
|
||||
({
|
||||
table,
|
||||
columnString,
|
||||
// eslint-disable-next-line @typescript-eslint/no-shadow
|
||||
items,
|
||||
}: {
|
||||
table: string;
|
||||
columnString: string;
|
||||
items: IDataObject[];
|
||||
}): Array<Promise<object>> => {
|
||||
return chunk(items, 1000).map(async (insertValues) => {
|
||||
const values = insertValues.map((item: IDataObject) => extractValues(item)).join(',');
|
||||
return pool
|
||||
.request()
|
||||
.query(`INSERT INTO ${table}(${formatColumns(columnString)}) VALUES ${values};`);
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
await insertOperation(tables, pool);
|
||||
|
||||
responseData = items;
|
||||
} else if (operation === 'update') {
|
||||
// ----------------------------------
|
||||
// update
|
||||
// ----------------------------------
|
||||
|
||||
const updateKeys = items.map(
|
||||
(item, index) => this.getNodeParameter('updateKey', index) as string,
|
||||
);
|
||||
|
||||
const tables = createTableStruct(
|
||||
this.getNodeParameter,
|
||||
items,
|
||||
['updateKey'].concat(updateKeys),
|
||||
'updateKey',
|
||||
);
|
||||
await executeQueryQueue(
|
||||
tables,
|
||||
({
|
||||
table,
|
||||
columnString,
|
||||
// eslint-disable-next-line @typescript-eslint/no-shadow
|
||||
items,
|
||||
}: {
|
||||
table: string;
|
||||
columnString: string;
|
||||
items: IDataObject[];
|
||||
}): Array<Promise<object>> => {
|
||||
return items.map(async (item) => {
|
||||
const columns = columnString.split(',').map((column) => column.trim());
|
||||
|
||||
const setValues = extractUpdateSet(item, columns);
|
||||
const condition = extractUpdateCondition(item, item.updateKey as string);
|
||||
|
||||
return pool.request().query(`UPDATE ${table} SET ${setValues} WHERE ${condition};`);
|
||||
});
|
||||
},
|
||||
);
|
||||
await updateOperation(tables, pool);
|
||||
|
||||
responseData = items;
|
||||
} else if (operation === 'delete') {
|
||||
// ----------------------------------
|
||||
// delete
|
||||
// ----------------------------------
|
||||
|
||||
const tables = items.reduce((acc, item, index) => {
|
||||
const table = this.getNodeParameter('table', index) as string;
|
||||
const deleteKey = this.getNodeParameter('deleteKey', index) as string;
|
||||
@@ -392,38 +305,7 @@ export class MicrosoftSql implements INodeType {
|
||||
return acc;
|
||||
}, {} as ITables);
|
||||
|
||||
const queriesResults = await Promise.all(
|
||||
Object.keys(tables).map(async (table) => {
|
||||
const deleteKeyResults = Object.keys(tables[table]).map(async (deleteKey) => {
|
||||
const deleteItemsList = chunk(
|
||||
tables[table][deleteKey].map((item) =>
|
||||
copyInputItem(item as INodeExecutionData, [deleteKey]),
|
||||
),
|
||||
1000,
|
||||
);
|
||||
const queryQueue = deleteItemsList.map(async (deleteValues) => {
|
||||
return pool
|
||||
.request()
|
||||
.query(
|
||||
`DELETE FROM ${table} WHERE "${deleteKey}" IN ${extractDeleteValues(
|
||||
deleteValues,
|
||||
deleteKey,
|
||||
)};`,
|
||||
);
|
||||
});
|
||||
return Promise.all(queryQueue);
|
||||
});
|
||||
return Promise.all(deleteKeyResults);
|
||||
}),
|
||||
);
|
||||
|
||||
const rowsDeleted = flatten(queriesResults).reduce(
|
||||
(acc: number, resp: mssql.IResult<object>): number =>
|
||||
(acc += resp.rowsAffected.reduce((sum, val) => (sum += val))),
|
||||
0,
|
||||
);
|
||||
|
||||
responseData = rowsDeleted;
|
||||
responseData = await deleteOperation(tables, pool);
|
||||
} else {
|
||||
await pool.close();
|
||||
throw new NodeOperationError(
|
||||
@@ -444,6 +326,7 @@ export class MicrosoftSql implements INodeType {
|
||||
await pool.close();
|
||||
|
||||
const itemData = generatePairedItemData(items.length);
|
||||
|
||||
const executionData = this.helpers.constructExecutionMetaData(
|
||||
this.helpers.returnJsonArray(responseData),
|
||||
{ itemData },
|
||||
|
||||
Reference in New Issue
Block a user