From 45e283055562b0bc5c3224d10187982662e26524 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Iv=C3=A1n=20Ovejero?= Date: Sat, 18 Sep 2021 22:45:57 +0200 Subject: [PATCH] :sparkles: Add MISP node (#2126) * :sparkles: Create MISP node * :zap: Improvements * :zap: Refactor tags type * :zap: Refactor tag into eventTag * :zap: Add required params to feed:create * :zap: Change endpoint for tag:getAll * :zap: Add description to role ID * :zap: Small improvements * :zap: Improvements * :fire: Remove empty file * :zap: Add sharing group ID param * :fire: Remove param with no effect * :hammer: Refactor sharing group to remove duplication * :fire: Remove param with no effect * :zap: Validate URL in feed resource * :pencil2: Rename Inviter param * :pencil2: Reword dynamic list param descriptions * :zap: Clean up error handling * :shirt: Nodelinter pass * :fire: Remove unused import * :hammer: Change param to color type * :zap: Improvements * :zap: Fix color Co-authored-by: ricardo Co-authored-by: Jan Oberhauser --- .../credentials/MispApi.credentials.ts | 30 + .../nodes-base/nodes/Misp/GenericFunctions.ts | 145 +++ packages/nodes-base/nodes/Misp/Misp.node.ts | 829 ++++++++++++++++++ .../Misp/descriptions/AttributeDescription.ts | 344 ++++++++ .../Misp/descriptions/EventDescription.ts | 466 ++++++++++ .../Misp/descriptions/EventTagDescription.ts | 118 +++ .../Misp/descriptions/FeedDescription.ts | 368 ++++++++ .../Misp/descriptions/GalaxyDescription.ts | 124 +++ .../descriptions/NoticelistDescription.ts | 94 ++ .../descriptions/OrganisationDescription.ts | 278 ++++++ .../nodes/Misp/descriptions/TagDescription.ts | 207 +++++ .../Misp/descriptions/UserDescription.ts | 285 ++++++ .../descriptions/WarninglistDescription.ts | 98 +++ .../nodes/Misp/descriptions/index.ts | 10 + packages/nodes-base/nodes/Misp/misp.svg | 41 + packages/nodes-base/nodes/Misp/types.d.ts | 23 + packages/nodes-base/package.json | 2 + 17 files changed, 3462 insertions(+) create mode 100644 packages/nodes-base/credentials/MispApi.credentials.ts create mode 100644 packages/nodes-base/nodes/Misp/GenericFunctions.ts create mode 100644 packages/nodes-base/nodes/Misp/Misp.node.ts create mode 100644 packages/nodes-base/nodes/Misp/descriptions/AttributeDescription.ts create mode 100644 packages/nodes-base/nodes/Misp/descriptions/EventDescription.ts create mode 100644 packages/nodes-base/nodes/Misp/descriptions/EventTagDescription.ts create mode 100644 packages/nodes-base/nodes/Misp/descriptions/FeedDescription.ts create mode 100644 packages/nodes-base/nodes/Misp/descriptions/GalaxyDescription.ts create mode 100644 packages/nodes-base/nodes/Misp/descriptions/NoticelistDescription.ts create mode 100644 packages/nodes-base/nodes/Misp/descriptions/OrganisationDescription.ts create mode 100644 packages/nodes-base/nodes/Misp/descriptions/TagDescription.ts create mode 100644 packages/nodes-base/nodes/Misp/descriptions/UserDescription.ts create mode 100644 packages/nodes-base/nodes/Misp/descriptions/WarninglistDescription.ts create mode 100644 packages/nodes-base/nodes/Misp/descriptions/index.ts create mode 100644 packages/nodes-base/nodes/Misp/misp.svg create mode 100644 packages/nodes-base/nodes/Misp/types.d.ts diff --git a/packages/nodes-base/credentials/MispApi.credentials.ts b/packages/nodes-base/credentials/MispApi.credentials.ts new file mode 100644 index 000000000..fc32e90c1 --- /dev/null +++ b/packages/nodes-base/credentials/MispApi.credentials.ts @@ -0,0 +1,30 @@ +import { + ICredentialType, + INodeProperties, +} from 'n8n-workflow'; + +export class MispApi implements ICredentialType { + name = 'mispApi'; + displayName = 'MISP API'; + documentationUrl = 'misp'; + properties: INodeProperties[] = [ + { + displayName: 'API Key', + name: 'apiKey', + type: 'string', + default: '', + }, + { + displayName: 'Base URL', + name: 'baseUrl', + type: 'string', + default: '', + }, + { + displayName: 'Allow Unauthorized Certificates', + name: 'allowUnauthorizedCerts', + type: 'boolean', + default: false, + }, + ]; +} diff --git a/packages/nodes-base/nodes/Misp/GenericFunctions.ts b/packages/nodes-base/nodes/Misp/GenericFunctions.ts new file mode 100644 index 000000000..83f6227a3 --- /dev/null +++ b/packages/nodes-base/nodes/Misp/GenericFunctions.ts @@ -0,0 +1,145 @@ +import { + IExecuteFunctions, +} from 'n8n-core'; + +import { + IDataObject, + ILoadOptionsFunctions, + NodeApiError, + NodeOperationError, +} from 'n8n-workflow'; + +import { + OptionsWithUri, +} from 'request'; + +import { + MispCredentials, +} from './types'; + +import { URL } from 'url'; + +export async function mispApiRequest( + this: IExecuteFunctions | ILoadOptionsFunctions, + method: string, + endpoint: string, + body: IDataObject = {}, + qs: IDataObject = {}, +) { + const { + baseUrl, + apiKey, + allowUnauthorizedCerts, + } = await this.getCredentials('mispApi') as MispCredentials; + + const options: OptionsWithUri = { + headers: { + Authorization: apiKey, + }, + method, + body, + qs, + uri: `${baseUrl}${endpoint}`, + json: true, + rejectUnauthorized: !allowUnauthorizedCerts, + }; + + if (!Object.keys(body).length) { + delete options.body; + } + + if (!Object.keys(qs).length) { + delete options.qs; + } + + try { + return await this.helpers.request!(options); + } catch (error) { + + // MISP API wrongly returns 403 for malformed requests + if (error.statusCode === 403) { + error.statusCode = 400; + } + + const errors = error?.error?.errors; + + if (errors) { + const key = Object.keys(errors)[0]; + + if (key !== undefined) { + let message = errors[key].join(); + + if (message.includes(' nter')) { + message = message.replace(' nter', ' enter'); + } + + error.error.message = `${error.error.message}: ${message}`; + } + } + + throw new NodeApiError(this.getNode(), error); + } +} + +export async function mispApiRequestAllItems( + this: IExecuteFunctions, + endpoint: string, +) { + const responseData = await mispApiRequest.call(this, 'GET', endpoint); + const returnAll = this.getNodeParameter('returnAll', 0) as boolean; + + if (!returnAll) { + const limit = this.getNodeParameter('limit', 0) as number; + return responseData.slice(0, limit); + } + + return responseData; +} + +export function throwOnEmptyUpdate( + this: IExecuteFunctions, + resource: string, + updateFields: IDataObject, +) { + if (!Object.keys(updateFields).length) { + throw new NodeOperationError( + this.getNode(), + `Please enter at least one field to update for the ${resource}.`, + ); + } +} + +const SHARING_GROUP_OPTION_ID = 4; + +export function throwOnMissingSharingGroup( + this: IExecuteFunctions, + fields: IDataObject, +) { + if ( + fields.distribution === SHARING_GROUP_OPTION_ID && + !fields.sharing_group_id + ) { + throw new NodeOperationError(this.getNode(), 'Please specify a sharing group'); + } +} + +const isValidUrl = (str: string) => { + try { + new URL(str); // tslint:disable-line: no-unused-expression + return true; + } catch (error) { + return false; + } +}; + +export function throwOnInvalidUrl( + this: IExecuteFunctions, + str: string, +) { + if (!isValidUrl(str)) { + throw new NodeOperationError( + this.getNode(), + 'Please specify a valid URL, protocol included. Example: https://site.com', + ); + } +} diff --git a/packages/nodes-base/nodes/Misp/Misp.node.ts b/packages/nodes-base/nodes/Misp/Misp.node.ts new file mode 100644 index 000000000..1b75b8804 --- /dev/null +++ b/packages/nodes-base/nodes/Misp/Misp.node.ts @@ -0,0 +1,829 @@ +import { + IExecuteFunctions, + ILoadOptionsFunctions, +} from 'n8n-core'; + +import { + IDataObject, + INodeExecutionData, + INodeType, + INodeTypeDescription, +} from 'n8n-workflow'; + +import { + mispApiRequest, + mispApiRequestAllItems, + throwOnEmptyUpdate, + throwOnInvalidUrl, + throwOnMissingSharingGroup, +} from './GenericFunctions'; + +import { + attributeFields, + attributeOperations, + eventFields, + eventOperations, + eventTagFields, + eventTagOperations, + feedFields, + feedOperations, + galaxyFields, + galaxyOperations, + noticelistFields, + noticelistOperations, + organisationFields, + organisationOperations, + tagFields, + tagOperations, + userFields, + userOperations, + warninglistFields, + warninglistOperations, +} from './descriptions'; + +import { + LoadedOrgs, + LoadedSharingGroups, + LoadedTags, + LoadedUsers, +} from './types'; + +export class Misp implements INodeType { + description: INodeTypeDescription = { + displayName: 'MISP', + name: 'misp', + icon: 'file:misp.svg', + group: ['transform'], + version: 1, + subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}', + description: 'Consume the MISP API', + defaults: { + name: 'MISP', + color: '#36bdf7', + }, + inputs: ['main'], + outputs: ['main'], + credentials: [ + { + name: 'mispApi', + required: true, + }, + ], + properties: [ + { + displayName: 'Resource', + name: 'resource', + type: 'options', + noDataExpression: true, + options: [ + { + name: 'Attribute', + value: 'attribute', + }, + { + name: 'Event', + value: 'event', + }, + { + name: 'Event Tag', + value: 'eventTag', + }, + { + name: 'Feed', + value: 'feed', + }, + { + name: 'Galaxy', + value: 'galaxy', + }, + { + name: 'Noticelist', + value: 'noticelist', + }, + { + name: 'Organisation', + value: 'organisation', + }, + { + name: 'Tag', + value: 'tag', + }, + { + name: 'User', + value: 'user', + }, + { + name: 'Warninglist', + value: 'warninglist', + }, + ], + default: 'attribute', + }, + ...attributeOperations, + ...attributeFields, + ...eventOperations, + ...eventFields, + ...eventTagOperations, + ...eventTagFields, + ...feedOperations, + ...feedFields, + ...galaxyOperations, + ...galaxyFields, + ...noticelistOperations, + ...noticelistFields, + ...organisationOperations, + ...organisationFields, + ...tagOperations, + ...tagFields, + ...userOperations, + ...userFields, + ...warninglistOperations, + ...warninglistFields, + ], + }; + + methods = { + loadOptions: { + async getOrgs(this: ILoadOptionsFunctions) { + const responseData = await mispApiRequest.call(this, 'GET', '/organisations') as LoadedOrgs; + return responseData.map((i) => ({ name: i.Organisation.name, value: i.Organisation.id })); + }, + + async getSharingGroups(this: ILoadOptionsFunctions) { + const responseData = await mispApiRequest.call(this, 'GET', '/sharing_groups') as LoadedSharingGroups; + return responseData.response.map((i) => ({ name: i.SharingGroup.name, value: i.SharingGroup.id })); + }, + + async getTags(this: ILoadOptionsFunctions) { + const responseData = await mispApiRequest.call(this, 'GET', '/tags') as LoadedTags; + return responseData.Tag.map((i) => ({ name: i.name, value: i.id })); + }, + + async getUsers(this: ILoadOptionsFunctions) { + const responseData = await mispApiRequest.call(this, 'GET', '/admin/users') as LoadedUsers; + return responseData.map((i) => ({ name: i.User.email, value: i.User.id })); + }, + }, + }; + + async execute(this: IExecuteFunctions): Promise { + const items = this.getInputData(); + const returnData: IDataObject[] = []; + + const resource = this.getNodeParameter('resource', 0) as string; + const operation = this.getNodeParameter('operation', 0) as string; + + let responseData; + + for (let i = 0; i < items.length; i++) { + + try { + + if (resource === 'attribute') { + + // ********************************************************************** + // attribute + // ********************************************************************** + + if (operation === 'create') { + + // ---------------------------------------- + // attribute: create + // ---------------------------------------- + + const body = { + type: this.getNodeParameter('type', i), + value: this.getNodeParameter('value', i), + }; + + const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; + + throwOnMissingSharingGroup.call(this, additionalFields); + + if (Object.keys(additionalFields)) { + Object.assign(body, additionalFields); + } + + const eventId = this.getNodeParameter('eventId', i); + const endpoint = `/attributes/add/${eventId}`; + responseData = await mispApiRequest.call(this, 'POST', endpoint, body); + responseData = responseData.Attribute; + + } else if (operation === 'delete') { + + // ---------------------------------------- + // attribute: delete + // ---------------------------------------- + + const attributeId = this.getNodeParameter('attributeId', i); + const endpoint = `/attributes/delete/${attributeId}`; + responseData = await mispApiRequest.call(this, 'DELETE', endpoint); + + } else if (operation === 'get') { + + // ---------------------------------------- + // attribute: get + // ---------------------------------------- + + const attributeId = this.getNodeParameter('attributeId', i); + const endpoint = `/attributes/view/${attributeId}`; + responseData = await mispApiRequest.call(this, 'GET', endpoint); + responseData = responseData.Attribute; + + } else if (operation === 'getAll') { + + // ---------------------------------------- + // attribute: getAll + // ---------------------------------------- + + responseData = await mispApiRequestAllItems.call(this, '/attributes'); + + } else if (operation === 'update') { + + // ---------------------------------------- + // attribute: update + // ---------------------------------------- + + const body = {}; + const updateFields = this.getNodeParameter('updateFields', i) as IDataObject; + + throwOnEmptyUpdate.call(this, resource, updateFields); + throwOnMissingSharingGroup.call(this, updateFields); + + Object.assign(body, updateFields); + + const attributeId = this.getNodeParameter('attributeId', i); + const endpoint = `/attributes/edit/${attributeId}`; + responseData = await mispApiRequest.call(this, 'PUT', endpoint, body); + responseData = responseData.Attribute; + } + + } else if (resource === 'event') { + + // ********************************************************************** + // event + // ********************************************************************** + + if (operation === 'create') { + + // ---------------------------------------- + // event: create + // ---------------------------------------- + + const body = { + org_id: this.getNodeParameter('org_id', i), + info: this.getNodeParameter('information', i), + }; + + const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; + + throwOnMissingSharingGroup.call(this, additionalFields); + + if (Object.keys(additionalFields)) { + Object.assign(body, additionalFields); + } + + responseData = await mispApiRequest.call(this, 'POST', '/events', body); + responseData = responseData.Event; + + } else if (operation === 'delete') { + + // ---------------------------------------- + // event: delete + // ---------------------------------------- + + const eventId = this.getNodeParameter('eventId', i); + const endpoint = `/events/delete/${eventId}`; + responseData = await mispApiRequest.call(this, 'DELETE', endpoint); + + } else if (operation === 'get') { + + // ---------------------------------------- + // event: get + // ---------------------------------------- + + const eventId = this.getNodeParameter('eventId', i); + const endpoint = `/events/view/${eventId}`; + responseData = await mispApiRequest.call(this, 'GET', endpoint); + responseData = responseData.Event; + delete responseData.Attribute; // prevent excessive payload size + + } else if (operation === 'getAll') { + + // ---------------------------------------- + // event: getAll + // ---------------------------------------- + + responseData = await mispApiRequestAllItems.call(this, '/events'); + + } else if (operation === 'publish') { + + // ---------------------------------------- + // event: publish + // ---------------------------------------- + + const eventId = this.getNodeParameter('eventId', i); + const endpoint = `/events/publish/${eventId}`; + responseData = await mispApiRequest.call(this, 'POST', endpoint); + + } else if (operation === 'unpublish') { + + // ---------------------------------------- + // event: unpublish + // ---------------------------------------- + + const eventId = this.getNodeParameter('eventId', i); + + const endpoint = `/events/unpublish/${eventId}`; + responseData = await mispApiRequest.call(this, 'POST', endpoint); + + } else if (operation === 'update') { + + // ---------------------------------------- + // event: update + // ---------------------------------------- + + const body = {}; + const updateFields = this.getNodeParameter('updateFields', i) as IDataObject; + + throwOnEmptyUpdate.call(this, resource, updateFields); + throwOnMissingSharingGroup.call(this, updateFields); + + Object.assign(body, updateFields); + + const eventId = this.getNodeParameter('eventId', i); + const endpoint = `/events/edit/${eventId}`; + responseData = await mispApiRequest.call(this, 'PUT', endpoint, body); + responseData = responseData.Event; + delete responseData.Attribute; // prevent excessive payload size + + } + + } else if (resource === 'eventTag') { + + if (operation === 'add') { + + // ---------------------------------------- + // eventTag: add + // ---------------------------------------- + + const body = { + event: this.getNodeParameter('eventId', i), + tag: this.getNodeParameter('tagId', i), + }; + + const endpoint = `/events/addTag`; + responseData = await mispApiRequest.call(this, 'POST', endpoint, body); + + } else if (operation === 'remove') { + + // ---------------------------------------- + // eventTag: remove + // ---------------------------------------- + + const eventId = this.getNodeParameter('eventId', i); + const tagId = this.getNodeParameter('tagId', i); + + const endpoint = `/events/removeTag/${eventId}/${tagId}`; + responseData = await mispApiRequest.call(this, 'POST', endpoint); + + } + + } else if (resource === 'feed') { + + // ********************************************************************** + // feed + // ********************************************************************** + + if (operation === 'create') { + + // ---------------------------------------- + // feed: create + // ---------------------------------------- + + const url = this.getNodeParameter('url', i) as string; + + throwOnInvalidUrl.call(this, url); + + const body = { + name: this.getNodeParameter('name', i), + provider: this.getNodeParameter('provider', i), + url, + }; + + const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; + + if (Object.keys(additionalFields)) { + Object.assign(body, additionalFields); + } + + responseData = await mispApiRequest.call(this, 'POST', '/feeds/add', body); + responseData = responseData.Feed; + + } else if (operation === 'disable') { + + // ---------------------------------------- + // feed: disable + // ---------------------------------------- + + const feedId = this.getNodeParameter('feedId', i); + + const endpoint = `/feeds/disable/${feedId}`; + responseData = await mispApiRequest.call(this, 'POST', endpoint); + + } else if (operation === 'enable') { + + // ---------------------------------------- + // feed: enable + // ---------------------------------------- + + const feedId = this.getNodeParameter('feedId', i); + const endpoint = `/feeds/enable/${feedId}`; + responseData = await mispApiRequest.call(this, 'POST', endpoint); + + } else if (operation === 'get') { + + // ---------------------------------------- + // feed: get + // ---------------------------------------- + + const feedId = this.getNodeParameter('feedId', i); + responseData = await mispApiRequest.call(this, 'GET', `/feeds/view/${feedId}`); + responseData = responseData.Feed; + + } else if (operation === 'getAll') { + + // ---------------------------------------- + // feed: getAll + // ---------------------------------------- + + responseData = await mispApiRequestAllItems.call(this, '/feeds') as Array<{ Feed: unknown }>; + responseData = responseData.map(i => i.Feed); + + } else if (operation === 'update') { + + // ---------------------------------------- + // feed: update + // ---------------------------------------- + + const body = {}; + const updateFields = this.getNodeParameter('updateFields', i) as IDataObject & { url: string }; + + throwOnEmptyUpdate.call(this, resource, updateFields); + + if (updateFields.url) { + throwOnInvalidUrl.call(this, updateFields.url); + } + + Object.assign(body, updateFields); + + const feedId = this.getNodeParameter('feedId', i); + responseData = await mispApiRequest.call(this, 'PUT', `/feeds/edit/${feedId}`, body); + responseData = responseData.Feed; + + } + + } else if (resource === 'galaxy') { + + // ********************************************************************** + // galaxy + // ********************************************************************** + + if (operation === 'delete') { + + // ---------------------------------------- + // galaxy: delete + // ---------------------------------------- + + const galaxyId = this.getNodeParameter('galaxyId', i); + const endpoint = `/galaxies/delete/${galaxyId}`; + responseData = await mispApiRequest.call(this, 'DELETE', endpoint); + + } else if (operation === 'get') { + + // ---------------------------------------- + // galaxy: get + // ---------------------------------------- + + const galaxyId = this.getNodeParameter('galaxyId', i); + const endpoint = `/galaxies/view/${galaxyId}`; + responseData = await mispApiRequest.call(this, 'GET', endpoint); + responseData = responseData.Galaxy; + + } else if (operation === 'getAll') { + + // ---------------------------------------- + // galaxy: getAll + // ---------------------------------------- + + responseData = await mispApiRequestAllItems.call(this, '/galaxies') as Array<{ Galaxy: unknown }>; + responseData = responseData.map(i => i.Galaxy); + + } + + } else if (resource === 'noticelist') { + + // ********************************************************************** + // noticelist + // ********************************************************************** + + if (operation === 'get') { + + // ---------------------------------------- + // noticelist: get + // ---------------------------------------- + + const noticelistId = this.getNodeParameter('noticelistId', i); + const endpoint = `/noticelists/view/${noticelistId}`; + responseData = await mispApiRequest.call(this, 'GET', endpoint); + responseData = responseData.Noticelist; + + } else if (operation === 'getAll') { + + // ---------------------------------------- + // noticelist: getAll + // ---------------------------------------- + + responseData = await mispApiRequestAllItems.call(this, '/noticelists') as Array<{ Noticelist: unknown }>; + responseData = responseData.map(i => i.Noticelist); + + } + + } else if (resource === 'organisation') { + + // ********************************************************************** + // organisation + // ********************************************************************** + + if (operation === 'create') { + + // ---------------------------------------- + // organisation: create + // ---------------------------------------- + + const body = { + name: this.getNodeParameter('name', i), + }; + + const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; + + if (Object.keys(additionalFields)) { + Object.assign(body, additionalFields); + } + + const endpoint = '/admin/organisations/add'; + responseData = await mispApiRequest.call(this, 'POST', endpoint, body); + responseData = responseData.Organisation; + + } else if (operation === 'delete') { + + // ---------------------------------------- + // organisation: delete + // ---------------------------------------- + + const organisationId = this.getNodeParameter('organisationId', i); + const endpoint = `/admin/organisations/delete/${organisationId}`; + responseData = await mispApiRequest.call(this, 'DELETE', endpoint); + + } else if (operation === 'get') { + + // ---------------------------------------- + // organisation: get + // ---------------------------------------- + + const organisationId = this.getNodeParameter('organisationId', i); + const endpoint = `/organisations/view/${organisationId}`; + responseData = await mispApiRequest.call(this, 'GET', endpoint); + responseData = responseData.Organisation; + + } else if (operation === 'getAll') { + + // ---------------------------------------- + // organisation: getAll + // ---------------------------------------- + + responseData = await mispApiRequestAllItems.call(this, '/organisations') as Array<{ Organisation: unknown }>; + responseData = responseData.map(i => i.Organisation); + + } else if (operation === 'update') { + + // ---------------------------------------- + // organisation: update + // ---------------------------------------- + + const body = {}; + const updateFields = this.getNodeParameter('updateFields', i) as IDataObject; + throwOnEmptyUpdate.call(this, resource, updateFields); + Object.assign(body, updateFields); + + const organisationId = this.getNodeParameter('organisationId', i); + const endpoint = `/admin/organisations/edit/${organisationId}`; + responseData = await mispApiRequest.call(this, 'PUT', endpoint, body); + responseData = responseData.Organisation; + + } + + } else if (resource === 'tag') { + + // ********************************************************************** + // tag + // ********************************************************************** + + if (operation === 'create') { + + // ---------------------------------------- + // tag: create + // ---------------------------------------- + + const body = { + name: this.getNodeParameter('name', i), + }; + + const { colour } = this.getNodeParameter('additionalFields', i) as { name?: string; colour?: string }; + + if (colour) { + Object.assign(body, { + colour: !colour.startsWith('#') ? `#${colour}` : colour, + }); + } + + responseData = await mispApiRequest.call(this, 'POST', '/tags/add', body); + responseData = responseData.Tag; + + } else if (operation === 'delete') { + + // ---------------------------------------- + // tag: delete + // ---------------------------------------- + + const tagId = this.getNodeParameter('tagId', i); + responseData = await mispApiRequest.call(this, 'POST', `/tags/delete/${tagId}`); + + } else if (operation === 'getAll') { + + // ---------------------------------------- + // tag: getAll + // ---------------------------------------- + + responseData = await mispApiRequest.call(this, 'GET', '/tags') as LoadedTags; + + const returnAll = this.getNodeParameter('returnAll', 0) as boolean; + + if (!returnAll) { + const limit = this.getNodeParameter('limit', 0) as number; + responseData = responseData.Tag.slice(0, limit); + } + + } else if (operation === 'update') { + + // ---------------------------------------- + // tag: update + // ---------------------------------------- + + const body = {}; + const updateFields = this.getNodeParameter('updateFields', i) as { colour?: string; name?: string; }; + throwOnEmptyUpdate.call(this, resource, updateFields); + Object.assign(body, updateFields); + + const { colour, name } = updateFields; + + Object.assign(body, { + ...name && { name }, + ...colour && { colour: !colour.startsWith('#') ? `#${colour}` : colour }, + }); + + const tagId = this.getNodeParameter('tagId', i); + responseData = await mispApiRequest.call(this, 'POST', `/tags/edit/${tagId}`, body); + responseData = responseData.Tag; + + } + + } else if (resource === 'user') { + + // ********************************************************************** + // user + // ********************************************************************** + + if (operation === 'create') { + + // ---------------------------------------- + // user: create + // ---------------------------------------- + + const body = { + email: this.getNodeParameter('email', i), + role_id: this.getNodeParameter('role_id', i), + }; + + const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; + + if (Object.keys(additionalFields)) { + Object.assign(body, additionalFields); + } + + responseData = await mispApiRequest.call(this, 'POST', '/admin/users/add', body); + responseData = responseData.User; + + } else if (operation === 'delete') { + + // ---------------------------------------- + // user: delete + // ---------------------------------------- + + const userId = this.getNodeParameter('userId', i); + const endpoint = `/admin/users/delete/${userId}`; + responseData = await mispApiRequest.call(this, 'DELETE', endpoint); + + } else if (operation === 'get') { + + // ---------------------------------------- + // user: get + // ---------------------------------------- + + const userId = this.getNodeParameter('userId', i); + const endpoint = `/admin/users/view/${userId}`; + responseData = await mispApiRequest.call(this, 'GET', endpoint); + responseData = responseData.User; + + } else if (operation === 'getAll') { + + // ---------------------------------------- + // user: getAll + // ---------------------------------------- + + responseData = await mispApiRequestAllItems.call(this, '/admin/users') as Array<{ User: unknown }>; + responseData = responseData.map(i => i.User); + + } else if (operation === 'update') { + + // ---------------------------------------- + // user: update + // ---------------------------------------- + + const body = {}; + const updateFields = this.getNodeParameter('updateFields', i) as IDataObject; + throwOnEmptyUpdate.call(this, resource, updateFields); + Object.assign(body, updateFields); + + const userId = this.getNodeParameter('userId', i); + const endpoint = `/admin/users/edit/${userId}`; + responseData = await mispApiRequest.call(this, 'PUT', endpoint, body); + responseData = responseData.User; + + } + + } else if (resource === 'warninglist') { + + // ********************************************************************** + // warninglist + // ********************************************************************** + + if (operation === 'get') { + + // ---------------------------------------- + // warninglist: get + // ---------------------------------------- + + const warninglistId = this.getNodeParameter('warninglistId', i); + const endpoint = `/warninglists/view/${warninglistId}`; + responseData = await mispApiRequest.call(this, 'GET', endpoint); + responseData = responseData.Warninglist; + + } else if (operation === 'getAll') { + + // ---------------------------------------- + // warninglist: getAll + // ---------------------------------------- + + responseData = await mispApiRequest.call(this, 'GET', '/warninglists') as { Warninglists: Array<{ Warninglist: unknown }> }; + + const returnAll = this.getNodeParameter('returnAll', 0) as boolean; + + if (!returnAll) { + const limit = this.getNodeParameter('limit', 0) as number; + responseData = responseData.Warninglists.slice(0, limit).map(i => i.Warninglist); + } else { + responseData = responseData.Warninglists.map(i => i.Warninglist); + } + + } + + } + + } catch (error) { + if (this.continueOnFail()) { + returnData.push({ error: error.message }); + continue; + } + throw error; + } + + Array.isArray(responseData) + ? returnData.push(...responseData) + : returnData.push(responseData); + + } + + return [this.helpers.returnJsonArray(returnData)]; + } +} diff --git a/packages/nodes-base/nodes/Misp/descriptions/AttributeDescription.ts b/packages/nodes-base/nodes/Misp/descriptions/AttributeDescription.ts new file mode 100644 index 000000000..b1a20f89e --- /dev/null +++ b/packages/nodes-base/nodes/Misp/descriptions/AttributeDescription.ts @@ -0,0 +1,344 @@ +import { + INodeProperties, +} from 'n8n-workflow'; + +export const attributeOperations: INodeProperties[] = [ + { + displayName: 'Operation', + name: 'operation', + type: 'options', + displayOptions: { + show: { + resource: [ + 'attribute', + ], + }, + }, + noDataExpression: true, + options: [ + { + name: 'Create', + value: 'create', + }, + { + name: 'Delete', + value: 'delete', + }, + { + name: 'Get', + value: 'get', + }, + { + name: 'Get All', + value: 'getAll', + }, + { + name: 'Update', + value: 'update', + }, + ], + default: 'create', + }, +]; + +export const attributeFields: INodeProperties[] = [ + // ---------------------------------------- + // attribute: create + // ---------------------------------------- + { + displayName: 'Event UUID', + name: 'eventId', + description: 'UUID of the event to attach the attribute to', + type: 'string', + required: true, + default: '', + displayOptions: { + show: { + resource: [ + 'attribute', + ], + operation: [ + 'create', + ], + }, + }, + }, + { + displayName: 'Type', + name: 'type', + type: 'options', + options: [ + { + name: 'Text', + value: 'text', + }, + { + name: 'URL', + value: 'url', + }, + { + name: 'Comment', + value: 'comment', + }, + ], + required: true, + default: 'text', + displayOptions: { + show: { + resource: [ + 'attribute', + ], + operation: [ + 'create', + ], + }, + }, + }, + { + displayName: 'Value', + name: 'value', + type: 'string', + required: true, + default: '', + displayOptions: { + show: { + resource: [ + 'attribute', + ], + operation: [ + 'create', + ], + }, + }, + }, + { + displayName: 'Additional Fields', + name: 'additionalFields', + type: 'collection', + placeholder: 'Add Field', + default: {}, + displayOptions: { + show: { + resource: [ + 'attribute', + ], + operation: [ + 'create', + ], + }, + }, + options: [ + { + displayName: 'Distribution', + name: 'distribution', + type: 'options', + default: 0, + description: 'Who will be able to see this event once published', + options: [ + { + name: 'Your Organization Only', + value: 0, + }, + { + name: 'This Community Only', + value: 1, + }, + { + name: 'Connected Communities', + value: 2, + }, + { + name: 'All Communities', + value: 3, + }, + { + name: 'Sharing Group', + value: 4, + }, + { + name: 'Inherit Event', + value: 5, + }, + ], + }, + { + displayName: 'Sharing Group Name/ID', + name: 'sharing_group_id', + type: 'options', + default: '', + description: 'Choose from the list, or specify an ID using an expression. Use only for when Sharing Group is selected in Distribution', + typeOptions: { + loadOptionsMethod: 'getSharingGroups', + }, + }, + ], + }, + + // ---------------------------------------- + // attribute: delete + // ---------------------------------------- + { + displayName: 'Attribute ID', + name: 'attributeId', + description: 'UUID or numeric ID of the attribute', + type: 'string', + required: true, + default: '', + displayOptions: { + show: { + resource: [ + 'attribute', + ], + operation: [ + 'delete', + ], + }, + }, + }, + + // ---------------------------------------- + // attribute: get + // ---------------------------------------- + { + displayName: 'Attribute ID', + name: 'attributeId', + description: 'UUID or numeric ID of the attribute', + type: 'string', + required: true, + default: '', + displayOptions: { + show: { + resource: [ + 'attribute', + ], + operation: [ + 'get', + ], + }, + }, + }, + { + displayName: 'Return All', + name: 'returnAll', + type: 'boolean', + default: false, + description: 'Whether to return all results or only up to a given limit', + displayOptions: { + show: { + resource: [ + 'attribute', + ], + operation: [ + 'getAll', + ], + }, + }, + }, + { + displayName: 'Limit', + name: 'limit', + type: 'number', + default: 50, + description: 'Max number of results to return', + typeOptions: { + minValue: 1, + }, + displayOptions: { + show: { + resource: [ + 'attribute', + ], + operation: [ + 'getAll', + ], + returnAll: [ + false, + ], + }, + }, + }, + + // ---------------------------------------- + // attribute: update + // ---------------------------------------- + { + displayName: 'Attribute ID', + name: 'attributeId', + description: 'ID of the attribute to update', + type: 'string', + required: true, + default: '', + displayOptions: { + show: { + resource: [ + 'attribute', + ], + operation: [ + 'update', + ], + }, + }, + }, + { + displayName: 'Update Fields', + name: 'updateFields', + type: 'collection', + placeholder: 'Add Field', + default: {}, + displayOptions: { + show: { + resource: [ + 'attribute', + ], + operation: [ + 'update', + ], + }, + }, + options: [ + { + displayName: 'Distribution', + name: 'distribution', + type: 'options', + default: 0, + description: 'Who will be able to see this event once published', + options: [ + { + name: 'Your Organization Only', + value: 0, + }, + { + name: 'This Community Only', + value: 1, + }, + { + name: 'Connected Communities', + value: 2, + }, + { + name: 'All Communities', + value: 3, + }, + { + name: 'Sharing Group', + value: 4, + }, + { + name: 'Inherit Event', + value: 5, + }, + ], + }, + { + displayName: 'Sharing Group Name/ID', + name: 'sharing_group_id', + type: 'options', + default: '', + description: 'Choose from the list, or specify an ID using an expression. Use only for when Sharing Group is selected in Distribution', + typeOptions: { + loadOptionsMethod: 'getSharingGroups', + }, + }, + ], + }, +]; diff --git a/packages/nodes-base/nodes/Misp/descriptions/EventDescription.ts b/packages/nodes-base/nodes/Misp/descriptions/EventDescription.ts new file mode 100644 index 000000000..eb7225c99 --- /dev/null +++ b/packages/nodes-base/nodes/Misp/descriptions/EventDescription.ts @@ -0,0 +1,466 @@ +import { + INodeProperties, +} from 'n8n-workflow'; + +export const eventOperations: INodeProperties[] = [ + { + displayName: 'Operation', + name: 'operation', + type: 'options', + displayOptions: { + show: { + resource: [ + 'event', + ], + }, + }, + noDataExpression: true, + options: [ + { + name: 'Create', + value: 'create', + }, + { + name: 'Delete', + value: 'delete', + }, + { + name: 'Get', + value: 'get', + }, + { + name: 'Get All', + value: 'getAll', + }, + { + name: 'Publish', + value: 'publish', + }, + { + name: 'Unpublish', + value: 'unpublish', + }, + { + name: 'Update', + value: 'update', + }, + ], + default: 'create', + }, +]; + +export const eventFields: INodeProperties[] = [ + // ---------------------------------------- + // event: create + // ---------------------------------------- + { + displayName: 'Organization Name/ID', + name: 'org_id', + type: 'options', + default: '', + required: true, + description: 'Choose from the list, or specify an ID using an expression', + typeOptions: { + loadOptionsMethod: 'getOrgs', + }, + displayOptions: { + show: { + resource: [ + 'event', + ], + operation: [ + 'create', + ], + }, + }, + }, + { + displayName: 'Information', + name: 'information', + type: 'string', + default: '', + required: true, + description: 'Information on the event - max 65535 characters', + displayOptions: { + show: { + resource: [ + 'event', + ], + operation: [ + 'create', + ], + }, + }, + }, + { + displayName: 'Additional Fields', + name: 'additionalFields', + type: 'collection', + placeholder: 'Add Field', + default: {}, + displayOptions: { + show: { + resource: [ + 'event', + ], + operation: [ + 'create', + ], + }, + }, + options: [ + { + displayName: 'Analysis', + name: 'analysis', + type: 'options', + default: 0, + description: 'Analysis maturity level of the event', + options: [ + { + name: 'Initial', + value: 0, + }, + { + name: 'Ongoing', + value: 1, + }, + { + name: 'Complete', + value: 2, + }, + ], + }, + { + displayName: 'Distribution', + name: 'distribution', + type: 'options', + default: 0, + description: 'Who will be able to see this event once published', + options: [ + { + name: 'Your Organization Only', + value: 0, + }, + { + name: 'This Community Only', + value: 1, + }, + { + name: 'Connected Communities', + value: 2, + }, + { + name: 'All Communities', + value: 3, + }, + { + name: 'Sharing Group', + value: 4, + }, + { + name: 'Inherit Event', + value: 5, + }, + ], + }, + { + displayName: 'Sharing Group Name/ID', + name: 'sharing_group_id', + type: 'options', + default: '', + description: 'Choose from the list, or specify an ID using an expression. Use only for when Sharing Group is selected in Distribution', + typeOptions: { + loadOptionsMethod: 'getSharingGroups', + }, + }, + { + displayName: 'Threat Level ID', + name: 'threat_level_id', + type: 'options', + default: 1, + options: [ + { + name: 'High', + value: 1, + }, + { + name: 'Medium', + value: 2, + }, + { + name: 'Low', + value: 3, + }, + { + name: 'Undefined', + value: 4, + }, + ], + }, + ], + }, + + // ---------------------------------------- + // event: delete + // ---------------------------------------- + { + displayName: 'Event ID', + name: 'eventId', + description: 'UUID or numeric ID of the event', + type: 'string', + required: true, + default: '', + displayOptions: { + show: { + resource: [ + 'event', + ], + operation: [ + 'delete', + ], + }, + }, + }, + + // ---------------------------------------- + // event: get + // ---------------------------------------- + { + displayName: 'Event ID', + name: 'eventId', + description: 'UUID or numeric ID of the event', + type: 'string', + required: true, + default: '', + displayOptions: { + show: { + resource: [ + 'event', + ], + operation: [ + 'get', + ], + }, + }, + }, + { + displayName: 'Return All', + name: 'returnAll', + type: 'boolean', + default: false, + description: 'Whether to return all results or only up to a given limit', + displayOptions: { + show: { + resource: [ + 'event', + ], + operation: [ + 'getAll', + ], + }, + }, + }, + { + displayName: 'Limit', + name: 'limit', + type: 'number', + default: 50, + description: 'Max number of results to return', + typeOptions: { + minValue: 1, + }, + displayOptions: { + show: { + resource: [ + 'event', + ], + operation: [ + 'getAll', + ], + returnAll: [ + false, + ], + }, + }, + }, + + // ---------------------------------------- + // event: publish + // ---------------------------------------- + { + displayName: 'Event ID', + name: 'eventId', + description: 'UUID or numeric ID of the event', + type: 'string', + required: true, + default: '', + displayOptions: { + show: { + resource: [ + 'event', + ], + operation: [ + 'publish', + ], + }, + }, + }, + + // ---------------------------------------- + // event: unpublish + // ---------------------------------------- + { + displayName: 'Event ID', + name: 'eventId', + description: 'UUID or numeric ID of the event', + type: 'string', + required: true, + default: '', + displayOptions: { + show: { + resource: [ + 'event', + ], + operation: [ + 'unpublish', + ], + }, + }, + }, + + // ---------------------------------------- + // event: update + // ---------------------------------------- + { + displayName: 'Event ID', + name: 'eventId', + description: 'UUID or numeric ID of the event', + type: 'string', + required: true, + default: '', + displayOptions: { + show: { + resource: [ + 'event', + ], + operation: [ + 'update', + ], + }, + }, + }, + { + displayName: 'Update Fields', + name: 'updateFields', + type: 'collection', + placeholder: 'Add Field', + default: {}, + displayOptions: { + show: { + resource: [ + 'event', + ], + operation: [ + 'update', + ], + }, + }, + options: [ + { + displayName: 'Analysis', + name: 'analysis', + type: 'options', + default: 0, + description: 'Analysis maturity level of the event', + options: [ + { + name: 'Initial', + value: 0, + }, + { + name: 'Ongoing', + value: 1, + }, + { + name: 'Complete', + value: 2, + }, + ], + }, + { + displayName: 'Distribution', + name: 'distribution', + type: 'options', + default: 0, + description: 'Who will be able to see this event once published', + options: [ + { + name: 'Your Organization Only', + value: 0, + }, + { + name: 'This Community Only', + value: 1, + }, + { + name: 'Connected Communities', + value: 2, + }, + { + name: 'All Communities', + value: 3, + }, + { + name: 'Sharing Group', + value: 4, + }, + { + name: 'Inherit Event', + value: 5, + }, + ], + }, + { + displayName: 'Information', + name: 'information', + type: 'string', + default: '', + description: 'Information on the event - max 65535 characters', + }, + { + displayName: 'Sharing Group Name/ID', + name: 'sharing_group_id', + type: 'options', + default: '', + description: 'Choose from the list, or specify an ID using an expression. Use only for when Sharing Group is selected in Distribution', + typeOptions: { + loadOptionsMethod: 'getSharingGroups', + }, + }, + { + displayName: 'Threat Level ID', + name: 'threat_level_id', + type: 'options', + default: 1, + options: [ + { + name: 'High', + value: 1, + }, + { + name: 'Medium', + value: 2, + }, + { + name: 'Low', + value: 3, + }, + { + name: 'Undefined', + value: 4, + }, + ], + }, + ], + }, +]; diff --git a/packages/nodes-base/nodes/Misp/descriptions/EventTagDescription.ts b/packages/nodes-base/nodes/Misp/descriptions/EventTagDescription.ts new file mode 100644 index 000000000..06cd07b21 --- /dev/null +++ b/packages/nodes-base/nodes/Misp/descriptions/EventTagDescription.ts @@ -0,0 +1,118 @@ +import { + INodeProperties, +} from 'n8n-workflow'; + +export const eventTagOperations: INodeProperties[] = [ + { + displayName: 'Operation', + name: 'operation', + type: 'options', + displayOptions: { + show: { + resource: [ + 'eventTag', + ], + }, + }, + noDataExpression: true, + options: [ + { + name: 'Add', + value: 'add', + }, + { + name: 'Remove', + value: 'remove', + }, + ], + default: 'add', + }, +]; + +export const eventTagFields: INodeProperties[] = [ + // ---------------------------------------- + // eventTag: add + // ---------------------------------------- + { + displayName: 'Event ID', + name: 'eventId', + description: 'UUID or numeric ID of the event', + type: 'string', + required: true, + default: '', + displayOptions: { + show: { + resource: [ + 'eventTag', + ], + operation: [ + 'add', + ], + }, + }, + }, + { + displayName: 'Tag Name/ID', + name: 'tagId', + description: 'Choose from the list, or specify an ID using an expression', + type: 'options', + required: true, + default: '', + typeOptions: { + loadOptionsMethod: 'getTags', + }, + displayOptions: { + show: { + resource: [ + 'eventTag', + ], + operation: [ + 'add', + ], + }, + }, + }, + + // ---------------------------------------- + // eventTag: remove + // ---------------------------------------- + { + displayName: 'Event ID', + name: 'eventId', + description: 'UUID or numeric ID of the event', + type: 'string', + required: true, + default: '', + displayOptions: { + show: { + resource: [ + 'eventTag', + ], + operation: [ + 'remove', + ], + }, + }, + }, + { + displayName: 'Tag Name/ID', + name: 'tagId', + description: 'Choose from the list, or specify an ID using an expression', + type: 'options', + required: true, + default: '', + typeOptions: { + loadOptionsMethod: 'getTags', + }, + displayOptions: { + show: { + resource: [ + 'eventTag', + ], + operation: [ + 'remove', + ], + }, + }, + }, +]; diff --git a/packages/nodes-base/nodes/Misp/descriptions/FeedDescription.ts b/packages/nodes-base/nodes/Misp/descriptions/FeedDescription.ts new file mode 100644 index 000000000..cc87d4dcc --- /dev/null +++ b/packages/nodes-base/nodes/Misp/descriptions/FeedDescription.ts @@ -0,0 +1,368 @@ +import { + INodeProperties, +} from 'n8n-workflow'; + +export const feedOperations: INodeProperties[] = [ + { + displayName: 'Operation', + name: 'operation', + type: 'options', + displayOptions: { + show: { + resource: [ + 'feed', + ], + }, + }, + noDataExpression: true, + options: [ + { + name: 'Create', + value: 'create', + }, + { + name: 'Disable', + value: 'disable', + }, + { + name: 'Enable', + value: 'enable', + }, + { + name: 'Get', + value: 'get', + }, + { + name: 'Get All', + value: 'getAll', + }, + { + name: 'Update', + value: 'update', + }, + ], + default: 'create', + }, +]; + +export const feedFields: INodeProperties[] = [ + // ---------------------------------------- + // feed: create + // ---------------------------------------- + { + displayName: 'Name', + name: 'name', + type: 'string', + required: true, + default: '', + displayOptions: { + show: { + resource: [ + 'feed', + ], + operation: [ + 'create', + ], + }, + }, + }, + { + displayName: 'Provider', + name: 'provider', + type: 'string', + default: '', + required: true, + displayOptions: { + show: { + resource: [ + 'feed', + ], + operation: [ + 'create', + ], + }, + }, + }, + { + displayName: 'URL', + name: 'url', + type: 'string', + default: '', + placeholder: 'https://example.com', + required: true, + displayOptions: { + show: { + resource: [ + 'feed', + ], + operation: [ + 'create', + ], + }, + }, + }, + { + displayName: 'Additional Fields', + name: 'additionalFields', + type: 'collection', + placeholder: 'Add Field', + default: {}, + displayOptions: { + show: { + resource: [ + 'feed', + ], + operation: [ + 'create', + ], + }, + }, + options: [ + { + displayName: 'Distribution', + name: 'distribution', + type: 'options', + default: 0, + description: 'Who will be able to see this event once published', + options: [ + { + name: 'Your Organization Only', + value: 0, + }, + { + name: 'This Community Only', + value: 1, + }, + { + name: 'Connected Communities', + value: 2, + }, + { + name: 'All Communities', + value: 3, + }, + { + name: 'Sharing Group', + value: 4, + }, + { + name: 'Inherit Event', + value: 5, + }, + ], + }, + { + displayName: 'Rules', + name: 'json', + type: 'string', + default: '', + description: 'Filter rules for the feed', + }, + ], + }, + + // ---------------------------------------- + // feed: disable + // ---------------------------------------- + { + displayName: 'Feed ID', + name: 'feedId', + description: 'UUID or numeric ID of the feed', + type: 'string', + required: true, + default: '', + displayOptions: { + show: { + resource: [ + 'feed', + ], + operation: [ + 'disable', + ], + }, + }, + }, + + // ---------------------------------------- + // feed: enable + // ---------------------------------------- + { + displayName: 'Feed ID', + name: 'feedId', + description: 'UUID or numeric ID of the feed', + type: 'string', + required: true, + default: '', + displayOptions: { + show: { + resource: [ + 'feed', + ], + operation: [ + 'enable', + ], + }, + }, + }, + + // ---------------------------------------- + // feed: get + // ---------------------------------------- + { + displayName: 'Feed ID', + name: 'feedId', + description: 'UUID or numeric ID of the feed', + type: 'string', + required: true, + default: '', + displayOptions: { + show: { + resource: [ + 'feed', + ], + operation: [ + 'get', + ], + }, + }, + }, + { + displayName: 'Return All', + name: 'returnAll', + type: 'boolean', + default: false, + description: 'Whether to return all results or only up to a given limit', + displayOptions: { + show: { + resource: [ + 'feed', + ], + operation: [ + 'getAll', + ], + }, + }, + }, + { + displayName: 'Limit', + name: 'limit', + type: 'number', + default: 50, + description: 'Max number of results to return', + typeOptions: { + minValue: 1, + }, + displayOptions: { + show: { + resource: [ + 'feed', + ], + operation: [ + 'getAll', + ], + returnAll: [ + false, + ], + }, + }, + }, + + // ---------------------------------------- + // feed: update + // ---------------------------------------- + { + displayName: 'Feed ID', + name: 'feedId', + description: 'ID of the feed to update', + type: 'string', + required: true, + default: '', + displayOptions: { + show: { + resource: [ + 'feed', + ], + operation: [ + 'update', + ], + }, + }, + }, + { + displayName: 'Update Fields', + name: 'updateFields', + type: 'collection', + placeholder: 'Add Field', + default: {}, + displayOptions: { + show: { + resource: [ + 'feed', + ], + operation: [ + 'update', + ], + }, + }, + options: [ + { + displayName: 'Distribution', + name: 'distribution', + type: 'options', + default: 0, + description: 'Who will be able to see this event once published', + options: [ + { + name: 'Your Organization Only', + value: 0, + }, + { + name: 'This Community Only', + value: 1, + }, + { + name: 'Connected Communities', + value: 2, + }, + { + name: 'All Communities', + value: 3, + }, + { + name: 'Sharing Group', + value: 4, + }, + { + name: 'Inherit Event', + value: 5, + }, + ], + }, + { + displayName: 'Name', + name: 'name', + type: 'string', + default: '', + }, + { + displayName: 'Provider', + name: 'provider', + type: 'string', + default: '', + }, + { + displayName: 'Rules', + name: 'rules', + type: 'json', + default: '', + description: 'Filter rules for the feed', + }, + { + displayName: 'URL', + name: 'url', + type: 'string', + default: '', + }, + ], + }, +]; diff --git a/packages/nodes-base/nodes/Misp/descriptions/GalaxyDescription.ts b/packages/nodes-base/nodes/Misp/descriptions/GalaxyDescription.ts new file mode 100644 index 000000000..d05761510 --- /dev/null +++ b/packages/nodes-base/nodes/Misp/descriptions/GalaxyDescription.ts @@ -0,0 +1,124 @@ +import { + INodeProperties, +} from 'n8n-workflow'; + +export const galaxyOperations: INodeProperties[] = [ + { + displayName: 'Operation', + name: 'operation', + type: 'options', + displayOptions: { + show: { + resource: [ + 'galaxy', + ], + }, + }, + noDataExpression: true, + options: [ + { + name: 'Delete', + value: 'delete', + }, + { + name: 'Get', + value: 'get', + }, + { + name: 'Get All', + value: 'getAll', + }, + ], + default: 'get', + }, +]; + +export const galaxyFields: INodeProperties[] = [ + // ---------------------------------------- + // galaxy: delete + // ---------------------------------------- + { + displayName: 'Galaxy ID', + name: 'galaxyId', + description: 'UUID or numeric ID of the galaxy', + type: 'string', + required: true, + default: '', + displayOptions: { + show: { + resource: [ + 'galaxy', + ], + operation: [ + 'delete', + ], + }, + }, + }, + + // ---------------------------------------- + // galaxy: get + // ---------------------------------------- + { + displayName: 'Galaxy ID', + name: 'galaxyId', + description: 'UUID or numeric ID of the galaxy', + type: 'string', + required: true, + default: '', + displayOptions: { + show: { + resource: [ + 'galaxy', + ], + operation: [ + 'get', + ], + }, + }, + }, + + // ---------------------------------------- + // galaxy: getAll + // ---------------------------------------- + { + displayName: 'Return All', + name: 'returnAll', + type: 'boolean', + default: false, + description: 'Whether to return all results or only up to a given limit', + displayOptions: { + show: { + resource: [ + 'galaxy', + ], + operation: [ + 'getAll', + ], + }, + }, + }, + { + displayName: 'Limit', + name: 'limit', + type: 'number', + default: 50, + description: 'Max number of results to return', + typeOptions: { + minValue: 1, + }, + displayOptions: { + show: { + resource: [ + 'galaxy', + ], + operation: [ + 'getAll', + ], + returnAll: [ + false, + ], + }, + }, + }, +]; diff --git a/packages/nodes-base/nodes/Misp/descriptions/NoticelistDescription.ts b/packages/nodes-base/nodes/Misp/descriptions/NoticelistDescription.ts new file mode 100644 index 000000000..00f176ba0 --- /dev/null +++ b/packages/nodes-base/nodes/Misp/descriptions/NoticelistDescription.ts @@ -0,0 +1,94 @@ +import { + INodeProperties, +} from 'n8n-workflow'; + +export const noticelistOperations: INodeProperties[] = [ + { + displayName: 'Operation', + name: 'operation', + type: 'options', + displayOptions: { + show: { + resource: [ + 'noticelist', + ], + }, + }, + noDataExpression: true, + options: [ + { + name: 'Get', + value: 'get', + }, + { + name: 'Get All', + value: 'getAll', + }, + ], + default: 'get', + }, +]; + +export const noticelistFields: INodeProperties[] = [ + // ---------------------------------------- + // noticelist: get + // ---------------------------------------- + { + displayName: 'Noticelist ID', + name: 'noticelistId', + description: 'Numeric ID of the noticelist', + type: 'string', + required: true, + default: '', + displayOptions: { + show: { + resource: [ + 'noticelist', + ], + operation: [ + 'get', + ], + }, + }, + }, + { + displayName: 'Return All', + name: 'returnAll', + type: 'boolean', + default: false, + description: 'Whether to return all results or only up to a given limit', + displayOptions: { + show: { + resource: [ + 'noticelist', + ], + operation: [ + 'getAll', + ], + }, + }, + }, + { + displayName: 'Limit', + name: 'limit', + type: 'number', + default: 50, + description: 'Max number of results to return', + typeOptions: { + minValue: 1, + }, + displayOptions: { + show: { + resource: [ + 'noticelist', + ], + operation: [ + 'getAll', + ], + returnAll: [ + false, + ], + }, + }, + }, +]; diff --git a/packages/nodes-base/nodes/Misp/descriptions/OrganisationDescription.ts b/packages/nodes-base/nodes/Misp/descriptions/OrganisationDescription.ts new file mode 100644 index 000000000..37ee008ee --- /dev/null +++ b/packages/nodes-base/nodes/Misp/descriptions/OrganisationDescription.ts @@ -0,0 +1,278 @@ +import { + INodeProperties, +} from 'n8n-workflow'; + +export const organisationOperations: INodeProperties[] = [ + { + displayName: 'Operation', + name: 'operation', + type: 'options', + displayOptions: { + show: { + resource: [ + 'organisation', + ], + }, + }, + noDataExpression: true, + options: [ + { + name: 'Create', + value: 'create', + }, + { + name: 'Delete', + value: 'delete', + }, + { + name: 'Get', + value: 'get', + }, + { + name: 'Get All', + value: 'getAll', + }, + { + name: 'Update', + value: 'update', + }, + ], + default: 'create', + }, +]; + +export const organisationFields: INodeProperties[] = [ + // ---------------------------------------- + // organisation: create + // ---------------------------------------- + { + displayName: 'Name', + name: 'name', + type: 'string', + required: true, + default: '', + displayOptions: { + show: { + resource: [ + 'organisation', + ], + operation: [ + 'create', + ], + }, + }, + }, + { + displayName: 'Additional Fields', + name: 'additionalFields', + type: 'collection', + placeholder: 'Add Field', + default: {}, + displayOptions: { + show: { + resource: [ + 'organisation', + ], + operation: [ + 'create', + ], + }, + }, + options: [ + { + displayName: 'Created by Email', + name: 'created_by_email', + type: 'string', + default: '', + }, + { + displayName: 'Description', + name: 'description', + type: 'string', + default: '', + }, + { + displayName: 'Nationality', + name: 'nationality', + type: 'string', + default: '', + }, + { + displayName: 'Sector', + name: 'sector', + type: 'string', + default: '', + }, + { + displayName: 'Type', + name: 'type', + type: 'string', + default: '', + }, + { + displayName: 'User Count', + name: 'usercount', + type: 'number', + typeOptions: { + minValue: 0, + }, + default: 0, + }, + ], + }, + + // ---------------------------------------- + // organisation: delete + // ---------------------------------------- + { + displayName: 'Organisation ID', + name: 'organisationId', + description: 'UUID or numeric ID of the organisation', + type: 'string', + required: true, + default: '', + displayOptions: { + show: { + resource: [ + 'organisation', + ], + operation: [ + 'delete', + ], + }, + }, + }, + + // ---------------------------------------- + // organisation: get + // ---------------------------------------- + { + displayName: 'Organisation ID', + name: 'organisationId', + description: 'UUID or numeric ID of the organisation', + type: 'string', + required: true, + default: '', + displayOptions: { + show: { + resource: [ + 'organisation', + ], + operation: [ + 'get', + ], + }, + }, + }, + { + displayName: 'Return All', + name: 'returnAll', + type: 'boolean', + default: false, + description: 'Whether to return all results or only up to a given limit', + displayOptions: { + show: { + resource: [ + 'organisation', + ], + operation: [ + 'getAll', + ], + }, + }, + }, + { + displayName: 'Limit', + name: 'limit', + type: 'number', + default: 50, + description: 'Max number of results to return', + typeOptions: { + minValue: 1, + }, + displayOptions: { + show: { + resource: [ + 'organisation', + ], + operation: [ + 'getAll', + ], + returnAll: [ + false, + ], + }, + }, + }, + + // ---------------------------------------- + // organisation: update + // ---------------------------------------- + { + displayName: 'Organisation ID', + name: 'organisationId', + description: 'ID of the organisation to update', + type: 'string', + required: true, + default: '', + displayOptions: { + show: { + resource: [ + 'organisation', + ], + operation: [ + 'update', + ], + }, + }, + }, + { + displayName: 'Update Fields', + name: 'updateFields', + type: 'collection', + placeholder: 'Add Field', + default: {}, + displayOptions: { + show: { + resource: [ + 'organisation', + ], + operation: [ + 'update', + ], + }, + }, + options: [ + { + displayName: 'Description', + name: 'description', + type: 'string', + default: '', + }, + { + displayName: 'Name', + name: 'name', + type: 'string', + default: '', + }, + { + displayName: 'Nationality', + name: 'nationality', + type: 'string', + default: '', + }, + { + displayName: 'Sector', + name: 'sector', + type: 'string', + default: '', + }, + { + displayName: 'Type', + name: 'type', + type: 'string', + default: '', + }, + ], + }, +]; diff --git a/packages/nodes-base/nodes/Misp/descriptions/TagDescription.ts b/packages/nodes-base/nodes/Misp/descriptions/TagDescription.ts new file mode 100644 index 000000000..fb2486642 --- /dev/null +++ b/packages/nodes-base/nodes/Misp/descriptions/TagDescription.ts @@ -0,0 +1,207 @@ +import { + INodeProperties, +} from 'n8n-workflow'; + +export const tagOperations: INodeProperties[] = [ + { + displayName: 'Operation', + name: 'operation', + type: 'options', + displayOptions: { + show: { + resource: [ + 'tag', + ], + }, + }, + noDataExpression: true, + options: [ + { + name: 'Create', + value: 'create', + }, + { + name: 'Delete', + value: 'delete', + }, + { + name: 'Get All', + value: 'getAll', + }, + { + name: 'Update', + value: 'update', + }, + ], + default: 'create', + }, +]; + +export const tagFields: INodeProperties[] = [ + // ---------------------------------------- + // tag: create + // ---------------------------------------- + { + displayName: 'Name', + name: 'name', + type: 'string', + required: true, + default: '', + displayOptions: { + show: { + resource: [ + 'tag', + ], + operation: [ + 'create', + ], + }, + }, + }, + { + displayName: 'Additional Fields', + name: 'additionalFields', + type: 'collection', + placeholder: 'Add Field', + default: {}, + displayOptions: { + show: { + resource: [ + 'tag', + ], + operation: [ + 'create', + ], + }, + }, + options: [ + { + displayName: 'Color', + description: 'Hex color code for the tag', + name: 'colour', + type: 'color', + default: '', + }, + ], + }, + + // ---------------------------------------- + // tag: delete + // ---------------------------------------- + { + displayName: 'Tag ID', + name: 'tagId', + description: 'Numeric ID of the attribute', + type: 'string', + required: true, + default: '', + displayOptions: { + show: { + resource: [ + 'tag', + ], + operation: [ + 'delete', + ], + }, + }, + }, + + // ---------------------------------------- + // tag: getAll + // ---------------------------------------- + { + displayName: 'Return All', + name: 'returnAll', + type: 'boolean', + default: false, + description: 'Whether to return all results or only up to a given limit', + displayOptions: { + show: { + resource: [ + 'tag', + ], + operation: [ + 'getAll', + ], + }, + }, + }, + { + displayName: 'Limit', + name: 'limit', + type: 'number', + default: 50, + description: 'Max number of results to return', + typeOptions: { + minValue: 1, + }, + displayOptions: { + show: { + resource: [ + 'tag', + ], + operation: [ + 'getAll', + ], + returnAll: [ + false, + ], + }, + }, + }, + + // ---------------------------------------- + // tag: update + // ---------------------------------------- + { + displayName: 'Tag ID', + name: 'tagId', + description: 'ID of the tag to update', + type: 'string', + required: true, + default: '', + displayOptions: { + show: { + resource: [ + 'tag', + ], + operation: [ + 'update', + ], + }, + }, + }, + { + displayName: 'Update Fields', + name: 'updateFields', + type: 'collection', + placeholder: 'Add Field', + default: {}, + displayOptions: { + show: { + resource: [ + 'tag', + ], + operation: [ + 'update', + ], + }, + }, + options: [ + { + displayName: 'Color', + description: 'Hex color code for the tag', + name: 'colour', + type: 'color', + default: '', + }, + { + displayName: 'Name', + name: 'name', + type: 'string', + default: '', + }, + ], + }, +]; diff --git a/packages/nodes-base/nodes/Misp/descriptions/UserDescription.ts b/packages/nodes-base/nodes/Misp/descriptions/UserDescription.ts new file mode 100644 index 000000000..7ffd2c468 --- /dev/null +++ b/packages/nodes-base/nodes/Misp/descriptions/UserDescription.ts @@ -0,0 +1,285 @@ +import { + INodeProperties, +} from 'n8n-workflow'; + +export const userOperations: INodeProperties[] = [ + { + displayName: 'Operation', + name: 'operation', + type: 'options', + displayOptions: { + show: { + resource: [ + 'user', + ], + }, + }, + noDataExpression: true, + options: [ + { + name: 'Create', + value: 'create', + }, + { + name: 'Delete', + value: 'delete', + }, + { + name: 'Get', + value: 'get', + }, + { + name: 'Get All', + value: 'getAll', + }, + { + name: 'Update', + value: 'update', + }, + ], + default: 'create', + }, +]; + +export const userFields: INodeProperties[] = [ + // ---------------------------------------- + // user: create + // ---------------------------------------- + { + displayName: 'Email', + name: 'email', + type: 'string', + required: true, + default: '', + displayOptions: { + show: { + resource: [ + 'user', + ], + operation: [ + 'create', + ], + }, + }, + }, + { + displayName: 'Role ID', + name: 'role_id', + type: 'string', + description: 'Role IDs are available in the MISP dashboard at /roles/index', + required: true, + default: '', + displayOptions: { + show: { + resource: [ + 'user', + ], + operation: [ + 'create', + ], + }, + }, + }, + { + displayName: 'Additional Fields', + name: 'additionalFields', + type: 'collection', + placeholder: 'Add Field', + default: {}, + displayOptions: { + show: { + resource: [ + 'user', + ], + operation: [ + 'create', + ], + }, + }, + options: [ + { + displayName: 'GPG Key', + name: 'gpgkey', + type: 'string', + default: '', + }, + { + displayName: 'Inviter Email/ID', + name: 'invited_by', + type: 'options', + default: '', + description: 'Choose from the list, or specify an ID using an expression', + typeOptions: { + loadOptionsMethod: 'getUsers', + }, + }, + { + displayName: 'Organization Name/ID', + name: 'org_id', + type: 'options', + description: 'Choose from the list, or specify an ID using an expression', + default: '', + typeOptions: { + loadOptionsMethod: 'getOrgs', + }, + }, + ], + }, + + // ---------------------------------------- + // user: delete + // ---------------------------------------- + { + displayName: 'User ID', + name: 'userId', + description: 'Numeric ID of the user', + type: 'string', + required: true, + default: '', + displayOptions: { + show: { + resource: [ + 'user', + ], + operation: [ + 'delete', + ], + }, + }, + }, + + // ---------------------------------------- + // user: get + // ---------------------------------------- + { + displayName: 'User ID', + name: 'userId', + description: 'Numeric ID of the user', + type: 'string', + required: true, + default: '', + displayOptions: { + show: { + resource: [ + 'user', + ], + operation: [ + 'get', + ], + }, + }, + }, + { + displayName: 'Return All', + name: 'returnAll', + type: 'boolean', + default: false, + description: 'Whether to return all results or only up to a given limit', + displayOptions: { + show: { + resource: [ + 'user', + ], + operation: [ + 'getAll', + ], + }, + }, + }, + { + displayName: 'Limit', + name: 'limit', + type: 'number', + default: 50, + description: 'Max number of results to return', + typeOptions: { + minValue: 1, + }, + displayOptions: { + show: { + resource: [ + 'user', + ], + operation: [ + 'getAll', + ], + returnAll: [ + false, + ], + }, + }, + }, + + // ---------------------------------------- + // user: update + // ---------------------------------------- + { + displayName: 'User ID', + name: 'userId', + description: 'ID of the user to update', + type: 'string', + required: true, + default: '', + displayOptions: { + show: { + resource: [ + 'user', + ], + operation: [ + 'update', + ], + }, + }, + }, + { + displayName: 'Update Fields', + name: 'updateFields', + type: 'collection', + placeholder: 'Add Field', + default: {}, + displayOptions: { + show: { + resource: [ + 'user', + ], + operation: [ + 'update', + ], + }, + }, + options: [ + { + displayName: 'Email', + name: 'email', + type: 'string', + default: '', + }, + { + displayName: 'GPG Key', + name: 'gpgkey', + type: 'string', + default: '', + }, + { + displayName: 'Inviter Name/ID', + name: 'invited_by', + type: 'options', + description: 'Choose from the list, or specify an ID using an expression', + default: '', + typeOptions: { + loadOptionsMethod: 'getUsers', + }, + }, + { + displayName: 'Organization Name/ID', + name: 'org_id', + type: 'options', + description: 'Choose from the list, or specify an ID using an expression', + default: '', + typeOptions: { + loadOptionsMethod: 'getOrgs', + }, + }, + ], + }, +]; diff --git a/packages/nodes-base/nodes/Misp/descriptions/WarninglistDescription.ts b/packages/nodes-base/nodes/Misp/descriptions/WarninglistDescription.ts new file mode 100644 index 000000000..9d9d44ecf --- /dev/null +++ b/packages/nodes-base/nodes/Misp/descriptions/WarninglistDescription.ts @@ -0,0 +1,98 @@ +import { + INodeProperties, +} from 'n8n-workflow'; + +export const warninglistOperations: INodeProperties[] = [ + { + displayName: 'Operation', + name: 'operation', + type: 'options', + displayOptions: { + show: { + resource: [ + 'warninglist', + ], + }, + }, + noDataExpression: true, + options: [ + { + name: 'Get', + value: 'get', + }, + { + name: 'Get All', + value: 'getAll', + }, + ], + default: 'get', + }, +]; + +export const warninglistFields: INodeProperties[] = [ + // ---------------------------------------- + // warninglist: get + // ---------------------------------------- + { + displayName: 'Warninglist ID', + name: 'warninglistId', + description: 'Numeric ID of the warninglist', + type: 'string', + required: true, + default: '', + displayOptions: { + show: { + resource: [ + 'warninglist', + ], + operation: [ + 'get', + ], + }, + }, + }, + + // ---------------------------------------- + // warninglist: getAll + // ---------------------------------------- + { + displayName: 'Return All', + name: 'returnAll', + type: 'boolean', + default: false, + description: 'Whether to return all results or only up to a given limit', + displayOptions: { + show: { + resource: [ + 'warninglist', + ], + operation: [ + 'getAll', + ], + }, + }, + }, + { + displayName: 'Limit', + name: 'limit', + type: 'number', + default: 50, + description: 'Max number of results to return', + typeOptions: { + minValue: 1, + }, + displayOptions: { + show: { + resource: [ + 'warninglist', + ], + operation: [ + 'getAll', + ], + returnAll: [ + false, + ], + }, + }, + }, +]; diff --git a/packages/nodes-base/nodes/Misp/descriptions/index.ts b/packages/nodes-base/nodes/Misp/descriptions/index.ts new file mode 100644 index 000000000..9bd7d6ae3 --- /dev/null +++ b/packages/nodes-base/nodes/Misp/descriptions/index.ts @@ -0,0 +1,10 @@ +export * from './AttributeDescription'; +export * from './EventDescription'; +export * from './EventTagDescription'; +export * from './FeedDescription'; +export * from './GalaxyDescription'; +export * from './NoticelistDescription'; +export * from './OrganisationDescription'; +export * from './TagDescription'; +export * from './UserDescription'; +export * from './WarninglistDescription'; diff --git a/packages/nodes-base/nodes/Misp/misp.svg b/packages/nodes-base/nodes/Misp/misp.svg new file mode 100644 index 000000000..f7c43f772 --- /dev/null +++ b/packages/nodes-base/nodes/Misp/misp.svg @@ -0,0 +1,41 @@ + + + + + + + + + + + + diff --git a/packages/nodes-base/nodes/Misp/types.d.ts b/packages/nodes-base/nodes/Misp/types.d.ts new file mode 100644 index 000000000..3a4fb35ab --- /dev/null +++ b/packages/nodes-base/nodes/Misp/types.d.ts @@ -0,0 +1,23 @@ +export type MispCredentials = { + baseUrl: string; + apiKey: string; + allowUnauthorizedCerts: boolean; +}; + +export type LoadedOrgs = Array<{ + Organisation: { id: string; name: string }; +}>; + +export type LoadedTags = { + Tag: Array<{ id: string; name: string }>; +}; + +export type LoadedUsers = Array<{ + User: { id: string; email: string }; +}>; + +export type LoadedSharingGroups = { + response: Array<{ + SharingGroup: { id: string; name: string }; + }>; +} diff --git a/packages/nodes-base/package.json b/packages/nodes-base/package.json index eb95afa8d..ff7fcaff3 100644 --- a/packages/nodes-base/package.json +++ b/packages/nodes-base/package.json @@ -179,6 +179,7 @@ "dist/credentials/MicrosoftToDoOAuth2Api.credentials.js", "dist/credentials/MindeeReceiptApi.credentials.js", "dist/credentials/MindeeInvoiceApi.credentials.js", + "dist/credentials/MispApi.credentials.js", "dist/credentials/MonicaCrmApi.credentials.js", "dist/credentials/MoceanApi.credentials.js", "dist/credentials/MondayComApi.credentials.js", @@ -483,6 +484,7 @@ "dist/nodes/Microsoft/Teams/MicrosoftTeams.node.js", "dist/nodes/Microsoft/ToDo/MicrosoftToDo.node.js", "dist/nodes/Mindee/Mindee.node.js", + "dist/nodes/Misp/Misp.node.js", "dist/nodes/MonicaCrm/MonicaCrm.node.js", "dist/nodes/MoveBinaryData.node.js", "dist/nodes/Mocean/Mocean.node.js",