From b694e7743e17507b901706c5023a9aac83b903dd Mon Sep 17 00:00:00 2001 From: Michael Kret <88898367+michael-radency@users.noreply.github.com> Date: Fri, 26 Apr 2024 11:12:22 +0300 Subject: [PATCH] feat(MISP Node): Rest search operations (#9196) --- .../nodes-base/nodes/Misp/GenericFunctions.ts | 53 ++++++++- packages/nodes-base/nodes/Misp/Misp.node.ts | 32 ++++++ .../Misp/descriptions/AttributeDescription.ts | 21 ++++ .../Misp/descriptions/EventDescription.ts | 21 ++++ .../Misp/descriptions/ObjectDescription.ts | 41 +++++++ .../Misp/descriptions/common.descriptions.ts | 102 ++++++++++++++++++ .../nodes/Misp/descriptions/index.ts | 1 + 7 files changed, 270 insertions(+), 1 deletion(-) create mode 100644 packages/nodes-base/nodes/Misp/descriptions/ObjectDescription.ts create mode 100644 packages/nodes-base/nodes/Misp/descriptions/common.descriptions.ts diff --git a/packages/nodes-base/nodes/Misp/GenericFunctions.ts b/packages/nodes-base/nodes/Misp/GenericFunctions.ts index a0e3e03597..bdf59bda34 100644 --- a/packages/nodes-base/nodes/Misp/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Misp/GenericFunctions.ts @@ -7,7 +7,7 @@ import type { IHttpRequestMethods, IRequestOptions, } from 'n8n-workflow'; -import { NodeApiError, NodeOperationError } from 'n8n-workflow'; +import { NodeApiError, NodeOperationError, jsonParse } from 'n8n-workflow'; import type { MispCredentials } from './types'; @@ -79,6 +79,57 @@ export async function mispApiRequestAllItems(this: IExecuteFunctions, endpoint: return responseData; } +export async function mispApiRestSearch( + this: IExecuteFunctions, + resource: 'attributes' | 'events' | 'objects', + itemIndex: number, +) { + let body: IDataObject = {}; + const useJson = this.getNodeParameter('useJson', itemIndex) as boolean; + + if (useJson) { + const json = this.getNodeParameter('jsonOutput', itemIndex); + if (typeof json === 'string') { + body = jsonParse(json); + } else { + body = json as IDataObject; + } + } else { + const value = this.getNodeParameter('value', itemIndex) as string; + const additionalFields = this.getNodeParameter('additionalFields', itemIndex); + + body.value = value; + + if (Object.keys(additionalFields).length) { + if (additionalFields.tags) { + additionalFields.tags = (additionalFields.tags as string) + .split(',') + .map((tag) => tag.trim()); + } + Object.assign(body, additionalFields); + } + } + + const endpoint = `/${resource}/restSearch`; + const { response } = await mispApiRequest.call(this, 'POST', endpoint, body); + + if (response) { + if (resource === 'attributes') { + return response.Attribute; + } + + if (resource === 'events') { + return (response as IDataObject[]).map((event) => event.Event); + } + + if (resource === 'objects') { + return (response as IDataObject[]).map((obj) => obj.Object); + } + } else { + return []; + } +} + export function throwOnEmptyUpdate( this: IExecuteFunctions, resource: string, diff --git a/packages/nodes-base/nodes/Misp/Misp.node.ts b/packages/nodes-base/nodes/Misp/Misp.node.ts index a32b3d9dcf..55ef467b57 100644 --- a/packages/nodes-base/nodes/Misp/Misp.node.ts +++ b/packages/nodes-base/nodes/Misp/Misp.node.ts @@ -10,6 +10,7 @@ import type { import { mispApiRequest, mispApiRequestAllItems, + mispApiRestSearch, throwOnEmptyUpdate, throwOnInvalidUrl, throwOnMissingSharingGroup, @@ -28,6 +29,8 @@ import { galaxyOperations, noticelistFields, noticelistOperations, + objectOperations, + objectFields, organisationFields, organisationOperations, tagFields, @@ -91,6 +94,10 @@ export class Misp implements INodeType { name: 'Noticelist', value: 'noticelist', }, + { + name: 'Object', + value: 'object', + }, { name: 'Organisation', value: 'organisation', @@ -122,6 +129,8 @@ export class Misp implements INodeType { ...galaxyFields, ...noticelistOperations, ...noticelistFields, + ...objectOperations, + ...objectFields, ...organisationOperations, ...organisationFields, ...tagOperations, @@ -233,6 +242,12 @@ export class Misp implements INodeType { // ---------------------------------------- responseData = await mispApiRequestAllItems.call(this, '/attributes'); + } else if (operation === 'search') { + // ---------------------------------------- + // attribute: search + // ---------------------------------------- + + responseData = await mispApiRestSearch.call(this, 'attributes', i); } else if (operation === 'update') { // ---------------------------------------- // attribute: update @@ -300,6 +315,12 @@ export class Misp implements INodeType { // ---------------------------------------- responseData = await mispApiRequestAllItems.call(this, '/events'); + } else if (operation === 'search') { + // ---------------------------------------- + // event: search + // ---------------------------------------- + + responseData = await mispApiRestSearch.call(this, 'events', i); } else if (operation === 'publish') { // ---------------------------------------- // event: publish @@ -500,6 +521,17 @@ export class Misp implements INodeType { }>; responseData = responseData.map((entry) => entry.Noticelist); } + } else if (resource === 'object') { + // ********************************************************************** + // object + // ********************************************************************** + if (operation === 'search') { + // ---------------------------------------- + // attribute: search + // ---------------------------------------- + + responseData = await mispApiRestSearch.call(this, 'objects', i); + } } else if (resource === 'organisation') { // ********************************************************************** // organisation diff --git a/packages/nodes-base/nodes/Misp/descriptions/AttributeDescription.ts b/packages/nodes-base/nodes/Misp/descriptions/AttributeDescription.ts index 7462013d34..09e54912b4 100644 --- a/packages/nodes-base/nodes/Misp/descriptions/AttributeDescription.ts +++ b/packages/nodes-base/nodes/Misp/descriptions/AttributeDescription.ts @@ -1,4 +1,15 @@ import type { INodeProperties } from 'n8n-workflow'; +import { updateDisplayOptions } from '../../../utils/utilities'; +import { searchProperties } from './common.descriptions'; + +const searchDisplayOptions = { + show: { + resource: ['attribute'], + operation: ['search'], + }, +}; + +const searchDescription = updateDisplayOptions(searchDisplayOptions, searchProperties); export const attributeOperations: INodeProperties[] = [ { @@ -32,6 +43,11 @@ export const attributeOperations: INodeProperties[] = [ value: 'getAll', action: 'Get many attributes', }, + { + name: 'Search', + value: 'search', + action: 'Get a filtered list of attributes', + }, { name: 'Update', value: 'update', @@ -226,6 +242,11 @@ export const attributeFields: INodeProperties[] = [ }, }, + // ---------------------------------------- + // attribute: search + // ---------------------------------------- + ...searchDescription, + // ---------------------------------------- // attribute: update // ---------------------------------------- diff --git a/packages/nodes-base/nodes/Misp/descriptions/EventDescription.ts b/packages/nodes-base/nodes/Misp/descriptions/EventDescription.ts index 098d6c5553..ca46869f7f 100644 --- a/packages/nodes-base/nodes/Misp/descriptions/EventDescription.ts +++ b/packages/nodes-base/nodes/Misp/descriptions/EventDescription.ts @@ -1,4 +1,15 @@ import type { INodeProperties } from 'n8n-workflow'; +import { updateDisplayOptions } from '../../../utils/utilities'; +import { searchProperties } from './common.descriptions'; + +const searchDisplayOptions = { + show: { + resource: ['event'], + operation: ['search'], + }, +}; + +const searchDescription = updateDisplayOptions(searchDisplayOptions, searchProperties); export const eventOperations: INodeProperties[] = [ { @@ -37,6 +48,11 @@ export const eventOperations: INodeProperties[] = [ value: 'publish', action: 'Publish an event', }, + { + name: 'Search', + value: 'search', + action: 'Get a filtered list of events', + }, { name: 'Unpublish', value: 'unpublish', @@ -295,6 +311,11 @@ export const eventFields: INodeProperties[] = [ }, }, + // ---------------------------------------- + // event: search + // ---------------------------------------- + ...searchDescription, + // ---------------------------------------- // event: update // ---------------------------------------- diff --git a/packages/nodes-base/nodes/Misp/descriptions/ObjectDescription.ts b/packages/nodes-base/nodes/Misp/descriptions/ObjectDescription.ts new file mode 100644 index 0000000000..ad38ede1c8 --- /dev/null +++ b/packages/nodes-base/nodes/Misp/descriptions/ObjectDescription.ts @@ -0,0 +1,41 @@ +import type { INodeProperties } from 'n8n-workflow'; +import { updateDisplayOptions } from '../../../utils/utilities'; +import { searchProperties } from './common.descriptions'; + +const searchDisplayOptions = { + show: { + resource: ['object'], + operation: ['search'], + }, +}; + +const searchDescription = updateDisplayOptions(searchDisplayOptions, searchProperties); + +export const objectOperations: INodeProperties[] = [ + { + displayName: 'Operation', + name: 'operation', + type: 'options', + displayOptions: { + show: { + resource: ['object'], + }, + }, + noDataExpression: true, + options: [ + { + name: 'Search', + value: 'search', + action: 'Get a filtered list of objects', + }, + ], + default: 'search', + }, +]; + +export const objectFields: INodeProperties[] = [ + // ---------------------------------------- + // event: search + // ---------------------------------------- + ...searchDescription, +]; diff --git a/packages/nodes-base/nodes/Misp/descriptions/common.descriptions.ts b/packages/nodes-base/nodes/Misp/descriptions/common.descriptions.ts new file mode 100644 index 0000000000..ffa040e481 --- /dev/null +++ b/packages/nodes-base/nodes/Misp/descriptions/common.descriptions.ts @@ -0,0 +1,102 @@ +import type { INodeProperties } from 'n8n-workflow'; + +export const searchProperties: INodeProperties[] = [ + { + displayName: 'Use JSON to Specify Fields', + name: 'useJson', + type: 'boolean', + default: false, + description: 'Whether to use JSON to specify the fields for the search request', + }, + { + displayName: 'JSON', + name: 'jsonOutput', + type: 'json', + description: + 'Get more info at {YOUR_BASE_URL_SPECIFIED_IN_CREDENTIALS}/api/openapi#operation/restSearchAttributes', + typeOptions: { + rows: 5, + }, + default: '{\n "value": "search value",\n "type": "text"\n}\n', + validateType: 'object', + displayOptions: { + show: { + useJson: [true], + }, + }, + }, + { + displayName: 'Value', + name: 'value', + type: 'string', + required: true, + placeholder: 'e.g. 127.0.0.1', + default: '', + displayOptions: { + show: { + useJson: [false], + }, + }, + }, + { + displayName: 'Additional Fields', + name: 'additionalFields', + type: 'collection', + placeholder: 'Add Field', + default: {}, + displayOptions: { + show: { + useJson: [false], + }, + }, + options: [ + { + displayName: 'Category', + name: 'category', + type: 'string', + placeholder: 'e.g. Internal reference', + default: '', + }, + { + displayName: 'Deleted', + name: 'deleted', + type: 'boolean', + default: false, + }, + { + displayName: 'Search All', + name: 'searchall', + type: 'string', + description: + 'Search by matching any tag names, event descriptions, attribute values or attribute comments', + default: '', + displayOptions: { + hide: { + '/resource': ['attribute'], + }, + }, + }, + { + displayName: 'Tags', + name: 'tags', + type: 'string', + placeholder: 'e.g. tag1,tag2', + hint: 'Comma-separated list of tags', + default: '', + }, + { + displayName: 'Type', + name: 'type', + type: 'string', + placeholder: 'e.g. text', + default: '', + }, + { + displayName: 'Published', + name: 'published', + type: 'boolean', + default: false, + }, + ], + }, +]; diff --git a/packages/nodes-base/nodes/Misp/descriptions/index.ts b/packages/nodes-base/nodes/Misp/descriptions/index.ts index 9bd7d6ae36..a7800c3b30 100644 --- a/packages/nodes-base/nodes/Misp/descriptions/index.ts +++ b/packages/nodes-base/nodes/Misp/descriptions/index.ts @@ -4,6 +4,7 @@ export * from './EventTagDescription'; export * from './FeedDescription'; export * from './GalaxyDescription'; export * from './NoticelistDescription'; +export * from './ObjectDescription'; export * from './OrganisationDescription'; export * from './TagDescription'; export * from './UserDescription';