n8n/packages/nodes-base/nodes/TheHive/GenericFunctions.ts
Iván Ovejero e77fd5d286
refactor: Switch plain errors in nodes-base to ApplicationError (no-changelog) (#7914)
Ensure all errors in `nodes-base` are `ApplicationError` or children of
it and contain no variables in the message, to continue normalizing all
the backend errors we report to Sentry. Also, skip reporting to Sentry
errors from user input and from external APIs. In future we should
refine `ApplicationError` to more specific errors.

Follow-up to: [#7877](https://github.com/n8n-io/n8n/pull/7877)

- [x] Test workflows:
https://github.com/n8n-io/n8n/actions/runs/7084627970
- [x] e2e: https://github.com/n8n-io/n8n/actions/runs/7084936861

---------

Co-authored-by: Michael Kret <michael.k@radency.com>
2023-12-05 11:17:08 +01:00

196 lines
5.7 KiB
TypeScript

import type { OptionsWithUri } from 'request';
import type {
IExecuteFunctions,
IHookFunctions,
ILoadOptionsFunctions,
IDataObject,
} from 'n8n-workflow';
import { ApplicationError, jsonParse } from 'n8n-workflow';
import moment from 'moment';
import { Eq } from './QueryFunctions';
export async function theHiveApiRequest(
this: IHookFunctions | IExecuteFunctions | ILoadOptionsFunctions,
method: string,
resource: string,
body: IDataObject = {},
query: IDataObject = {},
uri?: string,
option: IDataObject = {},
) {
const credentials = await this.getCredentials('theHiveApi');
let options: OptionsWithUri = {
method,
qs: query,
uri: uri || `${credentials.url}/api${resource}`,
body,
rejectUnauthorized: !credentials.allowUnauthorizedCerts,
json: true,
};
if (Object.keys(option).length !== 0) {
options = Object.assign({}, options, option);
}
if (Object.keys(body).length === 0) {
delete options.body;
}
if (Object.keys(query).length === 0) {
delete options.qs;
}
return this.helpers.requestWithAuthentication.call(this, 'theHiveApi', options);
}
// Helpers functions
export function mapResource(resource: string): string {
switch (resource) {
case 'alert':
return 'alert';
case 'case':
return 'case';
case 'observable':
return 'case_artifact';
case 'task':
return 'case_task';
case 'log':
return 'case_task_log';
default:
return '';
}
}
export function splitTags(tags: string): string[] {
return tags.split(',').filter((tag) => tag !== ' ' && tag);
}
export function prepareOptional(optionals: IDataObject): IDataObject {
const response: IDataObject = {};
for (const key in optionals) {
if (optionals[key] !== undefined && optionals[key] !== null && optionals[key] !== '') {
if (['customFieldsJson', 'customFieldsUi'].indexOf(key) > -1) {
continue; // Ignore customFields, they need special treatment
} else if (moment(optionals[key] as string, moment.ISO_8601).isValid()) {
response[key] = Date.parse(optionals[key] as string);
} else if (key === 'artifacts') {
try {
response[key] = jsonParse(optionals[key] as string);
} catch (error) {
throw new ApplicationError('Invalid JSON for artifacts', { level: 'warning' });
}
} else if (key === 'tags') {
response[key] = splitTags(optionals[key] as string);
} else {
response[key] = optionals[key];
}
}
}
return response;
}
export async function prepareCustomFields(
this: IHookFunctions | IExecuteFunctions | ILoadOptionsFunctions,
additionalFields: IDataObject,
jsonParameters = false,
): Promise<IDataObject | undefined> {
// Check if the additionalFields object contains customFields
if (jsonParameters) {
let customFieldsJson = additionalFields.customFieldsJson;
// Delete from additionalFields as some operations (e.g. alert:update) do not run prepareOptional
// which would remove the extra fields
delete additionalFields.customFieldsJson;
if (typeof customFieldsJson === 'string') {
try {
customFieldsJson = jsonParse(customFieldsJson);
} catch (error) {
throw new ApplicationError('Invalid JSON for customFields', { level: 'warning' });
}
}
if (typeof customFieldsJson === 'object') {
const customFields = Object.keys(customFieldsJson as IDataObject).reduce((acc, curr) => {
acc[`customFields.${curr}`] = (customFieldsJson as IDataObject)[curr];
return acc;
}, {} as IDataObject);
return customFields;
} else if (customFieldsJson) {
throw Error('customFieldsJson value is invalid');
}
} else if (additionalFields.customFieldsUi) {
// Get Custom Field Types from TheHive
const credentials = await this.getCredentials('theHiveApi');
const version = credentials.apiVersion;
const endpoint = version === 'v1' ? '/customField' : '/list/custom_fields';
const requestResult = await theHiveApiRequest.call(this, 'GET', endpoint as string);
// Convert TheHive3 response to the same format as TheHive 4
// [{name, reference, type}]
const hiveCustomFields =
version === 'v1'
? requestResult
: Object.keys(requestResult as IDataObject).map((key) => requestResult[key]);
// Build reference to type mapping object
const referenceTypeMapping = hiveCustomFields.reduce(
(acc: IDataObject, curr: IDataObject) => ((acc[curr.reference as string] = curr.type), acc),
{},
);
// Build "fieldName": {"type": "value"} objects
const customFieldsUi = additionalFields.customFieldsUi as IDataObject;
const customFields: IDataObject = (customFieldsUi?.customFields as IDataObject[]).reduce(
(acc: IDataObject, curr: IDataObject) => {
const fieldName = curr.field as string;
// Might be able to do some type conversions here if needed, TODO
const updatedField = `customFields.${fieldName}.${[referenceTypeMapping[fieldName]]}`;
acc[updatedField] = curr.value;
return acc;
},
{} as IDataObject,
);
delete additionalFields.customFieldsUi;
return customFields;
}
return undefined;
}
export function buildCustomFieldSearch(customFields: IDataObject): IDataObject[] {
const searchQueries: IDataObject[] = [];
Object.keys(customFields).forEach((customFieldName) => {
searchQueries.push(Eq(customFieldName, customFields[customFieldName]));
});
return searchQueries;
}
export function prepareSortQuery(sort: string, body: { query: [IDataObject] }) {
if (sort) {
const field = sort.substring(1);
const value = sort.charAt(0) === '+' ? 'asc' : 'desc';
const sortOption: IDataObject = {};
sortOption[field] = value;
body.query.push({
_name: 'sort',
_fields: [sortOption],
});
}
}
export function prepareRangeQuery(range: string, body: { query: IDataObject[] }) {
if (range && range !== 'all') {
body.query.push({
_name: 'page',
from: parseInt(range.split('-')[0], 10),
to: parseInt(range.split('-')[1], 10),
});
}
}