diff --git a/packages/nodes-base/credentials/TheHiveApi.credentials.ts b/packages/nodes-base/credentials/TheHiveApi.credentials.ts index 667e9c11cb4..48ee0f023a3 100644 --- a/packages/nodes-base/credentials/TheHiveApi.credentials.ts +++ b/packages/nodes-base/credentials/TheHiveApi.credentials.ts @@ -1,4 +1,5 @@ import { + IAuthenticate, IAuthenticateGeneric, ICredentialTestRequest, ICredentialType, @@ -55,14 +56,14 @@ export class TheHiveApi implements ICredentialType { type: 'generic', properties: { headers: { - Authorization: '=Bearer {{$credentials.ApiKey}}', + Authorization: '=Bearer {{$credentials?.ApiKey}}', }, }, }; test: ICredentialTestRequest = { request: { - baseURL: '={{$credentials.url}}', - url: '/api/alert', + baseURL: `={{$credentials?.url}}`, + url: '/api/case', }, }; } diff --git a/packages/nodes-base/nodes/TheHive/GenericFunctions.ts b/packages/nodes-base/nodes/TheHive/GenericFunctions.ts index 68254638e7e..406206d9a87 100644 --- a/packages/nodes-base/nodes/TheHive/GenericFunctions.ts +++ b/packages/nodes-base/nodes/TheHive/GenericFunctions.ts @@ -78,7 +78,11 @@ export function prepareOptional(optionals: IDataObject): IDataObject { } else if (moment(optionals[key] as string, moment.ISO_8601).isValid()) { response[key] = Date.parse(optionals[key] as string); } else if (key === 'artifacts') { - response[key] = JSON.parse(optionals[key] as string); + try { + response[key] = JSON.parse(optionals[key] as string); + } catch (error) { + throw new Error('Invalid JSON for artifacts'); + } } else if (key === 'tags') { response[key] = splitTags(optionals[key] as string); } else { @@ -96,15 +100,26 @@ export async function prepareCustomFields( ): Promise { // Check if the additionalFields object contains customFields if (jsonParameters === true) { - const customFieldsJson = additionalFields.customFieldsJson; + 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') { - return JSON.parse(customFieldsJson); - } else if (typeof customFieldsJson === 'object') { - return customFieldsJson as IDataObject; + try { + customFieldsJson = JSON.parse(customFieldsJson); + } catch (error) { + throw new Error('Invalid JSON for customFields'); + } + } + + 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'); } @@ -136,9 +151,8 @@ export async function prepareCustomFields( // Might be able to do some type conversions here if needed, TODO - acc[fieldName] = { - [referenceTypeMapping[fieldName]]: curr.value, - }; + const updatedField = `customFields.${fieldName}.${[referenceTypeMapping[fieldName]]}`; + acc[updatedField] = curr.value; return acc; }, {} as IDataObject, @@ -151,18 +165,10 @@ export async function prepareCustomFields( } export function buildCustomFieldSearch(customFields: IDataObject): IDataObject[] { - const customFieldTypes = ['boolean', 'date', 'float', 'integer', 'number', 'string']; const searchQueries: IDataObject[] = []; + Object.keys(customFields).forEach((customFieldName) => { - const customField = customFields[customFieldName] as IDataObject; - - // Figure out the field type from the object's keys - const fieldType = Object.keys(customField).filter( - (key) => customFieldTypes.indexOf(key) > -1, - )[0]; - const fieldValue = customField[fieldType]; - - searchQueries.push(Eq(`customFields.${customFieldName}.${fieldType}`, fieldValue)); + searchQueries.push(Eq(customFieldName, customFields[customFieldName])); }); return searchQueries; } diff --git a/packages/nodes-base/nodes/TheHive/TheHive.node.ts b/packages/nodes-base/nodes/TheHive/TheHive.node.ts index e8ae9168092..fa36381eac9 100644 --- a/packages/nodes-base/nodes/TheHive/TheHive.node.ts +++ b/packages/nodes-base/nodes/TheHive/TheHive.node.ts @@ -22,8 +22,6 @@ import { taskFields, taskOperations } from './descriptions/TaskDescription'; import { logFields, logOperations } from './descriptions/LogDescription'; -import { Buffer } from 'buffer'; - import { And, Between, ContainsString, Eq, Id, In, IQueryObject, Parent } from './QueryFunctions'; import { @@ -37,6 +35,8 @@ import { theHiveApiRequest, } from './GenericFunctions'; +import { set } from 'lodash'; + export class TheHive implements INodeType { description: INodeTypeDescription = { displayName: 'TheHive', @@ -396,10 +396,15 @@ export class TheHive implements INodeType { source: this.getNodeParameter('source', i), sourceRef: this.getNodeParameter('sourceRef', i), follow: this.getNodeParameter('follow', i, true), - customFields, ...prepareOptional(additionalFields), }; + if (customFields) { + Object.keys(customFields).forEach((key) => { + set(body, key, customFields[key]); + }); + } + const artifactUi = this.getNodeParameter('artifactUi', i) as IDataObject; if (artifactUi) { @@ -510,8 +515,18 @@ export class TheHive implements INodeType { if (operation === 'get') { const alertId = this.getNodeParameter('id', i) as string; + const includeSimilar = this.getNodeParameter( + 'options.includeSimilar', + i, + false, + ) as boolean; + const qs: IDataObject = {}; - responseData = await theHiveApiRequest.call(this, 'GET', `/alert/${alertId}`, {}); + if (includeSimilar) { + qs.similarity = true; + } + + responseData = await theHiveApiRequest.call(this, 'GET', `/alert/${alertId}`, {}, qs); } if (operation === 'getAll') { const credentials = await this.getCredentials('theHiveApi'); @@ -665,7 +680,7 @@ export class TheHive implements INodeType { delete updateFields.artifactUi; const body: IDataObject = { - customFields, + ...customFields, }; Object.assign(body, updateFields); @@ -1272,10 +1287,15 @@ export class TheHive implements INodeType { flag: this.getNodeParameter('flag', i), tlp: this.getNodeParameter('tlp', i), tags: splitTags(this.getNodeParameter('tags', i) as string), - customFields, ...prepareOptional(options), }; + if (customFields) { + Object.keys(customFields).forEach((key) => { + set(body, key, customFields[key]); + }); + } + responseData = await theHiveApiRequest.call(this, 'POST', '/case' as string, body); } @@ -1415,7 +1435,7 @@ export class TheHive implements INodeType { const customFields = await prepareCustomFields.call(this, updateFields, jsonParameters); const body: IDataObject = { - customFields, + ...customFields, ...prepareOptional(updateFields), }; diff --git a/packages/nodes-base/nodes/TheHive/TheHiveTrigger.node.ts b/packages/nodes-base/nodes/TheHive/TheHiveTrigger.node.ts index c6bc31c26d0..7100cf5fcfa 100644 --- a/packages/nodes-base/nodes/TheHive/TheHiveTrigger.node.ts +++ b/packages/nodes-base/nodes/TheHive/TheHiveTrigger.node.ts @@ -7,6 +7,7 @@ import { INodeTypeDescription, IWebhookResponseData, } from 'n8n-workflow'; +import { eventsDescription } from './descriptions/EventsDescription'; export class TheHiveTrigger implements INodeType { description: INodeTypeDescription = { @@ -14,7 +15,7 @@ export class TheHiveTrigger implements INodeType { name: 'theHiveTrigger', icon: 'file:thehive.svg', group: ['trigger'], - version: 1, + version: [1, 2], description: 'Starts the workflow when TheHive events occur', defaults: { name: 'TheHive Trigger', @@ -29,98 +30,7 @@ export class TheHiveTrigger implements INodeType { path: 'webhook', }, ], - properties: [ - { - displayName: 'Events', - name: 'events', - type: 'multiOptions', - default: [], - required: true, - description: 'Events types', - options: [ - { - name: '*', - value: '*', - description: 'Any time any event is triggered (Wildcard Event)', - }, - { - name: 'Alert Created', - value: 'alert_create', - description: 'Triggered when an alert is created', - }, - { - name: 'Alert Deleted', - value: 'alert_delete', - description: 'Triggered when an alert is deleted', - }, - { - name: 'Alert Updated', - value: 'alert_update', - description: 'Triggered when an alert is updated', - }, - { - name: 'Case Created', - value: 'case_create', - description: 'Triggered when a case is created', - }, - { - name: 'Case Deleted', - value: 'case_delete', - description: 'Triggered when a case is deleted', - }, - { - name: 'Case Updated', - value: 'case_update', - description: 'Triggered when a case is updated', - }, - { - name: 'Log Created', - value: 'case_task_log_create', - description: 'Triggered when a task log is created', - }, - { - name: 'Log Deleted', - value: 'case_task_log_delete', - description: 'Triggered when a task log is deleted', - }, - { - name: 'Log Updated', - value: 'case_task_log_update', - description: 'Triggered when a task log is updated', - }, - { - name: 'Observable Created', - value: 'case_artifact_create', - description: 'Triggered when an observable is created', - }, - { - name: 'Observable Deleted', - value: 'case_artifact_delete', - description: 'Triggered when an observable is deleted', - }, - { - name: 'Observable Updated', - value: 'case_artifact_update', - description: 'Triggered when an observable is updated', - }, - { - name: 'Task Created', - value: 'case_task_create', - description: 'Triggered when a task is created', - }, - { - name: 'Task Deleted', - value: 'case_task_delete', - description: 'Triggered when a task is deleted', - }, - { - name: 'Task Updated', - value: 'case_task_update', - description: 'Triggered when a task is updated', - }, - ], - }, - ], + properties: [...eventsDescription], }; // @ts-ignore (because of request) webhookMethods = { @@ -150,6 +60,7 @@ export class TheHiveTrigger implements INodeType { // Replace Creation with Create for TheHive 3 support const operation = (bodyData.operation as string).replace('Creation', 'Create'); const event = `${(bodyData.objectType as string).toLowerCase()}_${operation.toLowerCase()}`; + if (events.indexOf('*') === -1 && events.indexOf(event) === -1) { return {}; } diff --git a/packages/nodes-base/nodes/TheHive/descriptions/AlertDescription.ts b/packages/nodes-base/nodes/TheHive/descriptions/AlertDescription.ts index f2499af8de7..7d1b5b751c7 100644 --- a/packages/nodes-base/nodes/TheHive/descriptions/AlertDescription.ts +++ b/packages/nodes-base/nodes/TheHive/descriptions/AlertDescription.ts @@ -762,6 +762,32 @@ export const alertFields: INodeProperties[] = [ }, ], }, + { + displayName: 'Options', + name: 'options', + displayOptions: { + show: { + operation: [ + 'get', + ], + resource: [ + 'alert', + ], + }, + }, + type: 'collection', + placeholder: 'Add Option', + default: {}, + options: [ + { + displayName: 'Include Similar Cases', + name: 'includeSimilar', + type: 'boolean', + description: 'Whether to include similar cases', + default: false, + }, + ], + }, { displayName: 'Filters', name: 'filters', diff --git a/packages/nodes-base/nodes/TheHive/descriptions/EventsDescription.ts b/packages/nodes-base/nodes/TheHive/descriptions/EventsDescription.ts new file mode 100644 index 00000000000..9894b0a5f2e --- /dev/null +++ b/packages/nodes-base/nodes/TheHive/descriptions/EventsDescription.ts @@ -0,0 +1,189 @@ +import { INodeProperties } from 'n8n-workflow'; + +export const eventsDescription: INodeProperties[] = [ + { + displayName: 'Events', + name: 'events', + type: 'multiOptions', + default: [], + required: true, + description: 'Events types', + displayOptions: { + show: { + '@version': [1], + }, + }, + options: [ + { + name: '*', + value: '*', + description: 'Any time any event is triggered (Wildcard Event)', + }, + { + name: 'Alert Created', + value: 'alert_create', + description: 'Triggered when an alert is created', + }, + { + name: 'Alert Deleted', + value: 'alert_delete', + description: 'Triggered when an alert is deleted', + }, + { + name: 'Alert Updated', + value: 'alert_update', + description: 'Triggered when an alert is updated', + }, + { + name: 'Case Created', + value: 'case_create', + description: 'Triggered when a case is created', + }, + { + name: 'Case Deleted', + value: 'case_delete', + description: 'Triggered when a case is deleted', + }, + { + name: 'Case Updated', + value: 'case_update', + description: 'Triggered when a case is updated', + }, + { + name: 'Log Created', + value: 'case_task_log_create', + description: 'Triggered when a task log is created', + }, + { + name: 'Log Deleted', + value: 'case_task_log_delete', + description: 'Triggered when a task log is deleted', + }, + { + name: 'Log Updated', + value: 'case_task_log_update', + description: 'Triggered when a task log is updated', + }, + { + name: 'Observable Created', + value: 'case_artifact_create', + description: 'Triggered when an observable is created', + }, + { + name: 'Observable Deleted', + value: 'case_artifact_delete', + description: 'Triggered when an observable is deleted', + }, + { + name: 'Observable Updated', + value: 'case_artifact_update', + description: 'Triggered when an observable is updated', + }, + { + name: 'Task Created', + value: 'case_task_create', + description: 'Triggered when a task is created', + }, + { + name: 'Task Deleted', + value: 'case_task_delete', + description: 'Triggered when a task is deleted', + }, + { + name: 'Task Updated', + value: 'case_task_update', + description: 'Triggered when a task is updated', + }, + ], + }, + { + displayName: 'Events', + name: 'events', + type: 'multiOptions', + default: [], + required: true, + description: 'Events types', + displayOptions: { + show: { + '@version': [2], + }, + }, + options: [ + { + name: '*', + value: '*', + description: 'Any time any event is triggered (Wildcard Event)', + }, + { + name: 'Alert Created', + value: 'alert_create', + description: 'Triggered when an alert is created', + }, + { + name: 'Alert Deleted', + value: 'alert_delete', + description: 'Triggered when an alert is deleted', + }, + { + name: 'Alert Updated', + value: 'alert_update', + description: 'Triggered when an alert is updated', + }, + { + name: 'Case Created', + value: 'case_create', + description: 'Triggered when a case is created', + }, + { + name: 'Case Deleted', + value: 'case_delete', + description: 'Triggered when a case is deleted', + }, + { + name: 'Case Updated', + value: 'case_update', + description: 'Triggered when a case is updated', + }, + { + name: 'Log Created', + value: 'case_task_log_create', + description: 'Triggered when a task log is created', + }, + { + name: 'Log Deleted', + value: 'case_task_log_delete', + description: 'Triggered when a task log is deleted', + }, + { + name: 'Log Updated', + value: 'case_task_log_update', + description: 'Triggered when a task log is updated', + }, + { + name: 'Observable Created', + value: 'case_artifact_create', + description: 'Triggered when an observable is created', + }, + { + name: 'Observable Deleted', + value: 'case_artifact_delete', + description: 'Triggered when an observable is deleted', + }, + { + name: 'Observable Updated', + value: 'case_artifact_update', + description: 'Triggered when an observable is updated', + }, + { + name: 'Task Created', + value: 'case_task_create', + description: 'Triggered when a task is created', + }, + { + name: 'Task Updated', + value: 'case_task_update', + description: 'Triggered when a task is updated', + }, + ], + }, +]; diff --git a/packages/nodes-base/nodes/TheHive/descriptions/LogDescription.ts b/packages/nodes-base/nodes/TheHive/descriptions/LogDescription.ts index fe0af9b0c3e..892f2d66821 100644 --- a/packages/nodes-base/nodes/TheHive/descriptions/LogDescription.ts +++ b/packages/nodes-base/nodes/TheHive/descriptions/LogDescription.ts @@ -4,8 +4,8 @@ export const logOperations: INodeProperties[] = [ { displayName: 'Operation', name: 'operation', - type: 'options', noDataExpression: true, + type: 'options', required: true, default: 'getAll', displayOptions: { diff --git a/packages/nodes-base/nodes/TheHive/descriptions/ObservableDescription.ts b/packages/nodes-base/nodes/TheHive/descriptions/ObservableDescription.ts index 486617fb6ec..9ed5e9d3236 100644 --- a/packages/nodes-base/nodes/TheHive/descriptions/ObservableDescription.ts +++ b/packages/nodes-base/nodes/TheHive/descriptions/ObservableDescription.ts @@ -101,7 +101,7 @@ export const observableFields: INodeProperties[] = [ }, }, description: - 'Type of the observable. Choose from the list, or specify an ID using an expression.', + 'Choose from the list, or specify an ID using an expression', }, { displayName: 'Data', @@ -448,7 +448,7 @@ export const observableFields: INodeProperties[] = [ loadOptionsMethod: 'loadObservableTypes', }, description: - 'Type of the observable. Choose from the list, or specify IDs using an expression.', + 'Choose from the list, or specify IDs using an expression', }, { displayName: 'Date Range',