import { type IExecuteFunctions, type ILoadOptionsFunctions, type IDataObject, type INodeExecutionData, type INodeType, type INodeTypeDescription, NodeConnectionType, } from 'n8n-workflow'; import { mispApiRequest, mispApiRequestAllItems, mispApiRestSearch, throwOnEmptyUpdate, throwOnInvalidUrl, throwOnMissingSharingGroup, } from './GenericFunctions'; import { attributeFields, attributeOperations, eventFields, eventOperations, eventTagFields, eventTagOperations, feedFields, feedOperations, galaxyFields, galaxyOperations, noticelistFields, noticelistOperations, objectOperations, objectFields, organisationFields, organisationOperations, tagFields, tagOperations, userFields, userOperations, warninglistFields, warninglistOperations, } from './descriptions'; import type { 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', }, inputs: [NodeConnectionType.Main], outputs: [NodeConnectionType.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: 'Object', value: 'object', }, { 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, ...objectOperations, ...objectFields, ...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: INodeExecutionData[] = []; const resource = this.getNodeParameter('resource', 0); const operation = this.getNodeParameter('operation', 0); 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); 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 === 'search') { // ---------------------------------------- // attribute: search // ---------------------------------------- responseData = await mispApiRestSearch.call(this, 'attributes', i); } else if (operation === 'update') { // ---------------------------------------- // attribute: update // ---------------------------------------- const body = {}; const updateFields = this.getNodeParameter('updateFields', i); 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); 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 === 'search') { // ---------------------------------------- // event: search // ---------------------------------------- responseData = await mispApiRestSearch.call(this, 'events', i); } 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); 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); 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((entry) => entry.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((entry) => entry.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((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 // ********************************************************************** if (operation === 'create') { // ---------------------------------------- // organisation: create // ---------------------------------------- const body = { name: this.getNodeParameter('name', i), }; const additionalFields = this.getNodeParameter('additionalFields', i); 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((entry) => entry.Organisation); } else if (operation === 'update') { // ---------------------------------------- // organisation: update // ---------------------------------------- const body = {}; const updateFields = this.getNodeParameter('updateFields', i); 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); if (!returnAll) { const limit = this.getNodeParameter('limit', 0); 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); 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((entry) => entry.User); } else if (operation === 'update') { // ---------------------------------------- // user: update // ---------------------------------------- const body = {}; const updateFields = this.getNodeParameter('updateFields', i); 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); if (!returnAll) { const limit = this.getNodeParameter('limit', 0); responseData = responseData.Warninglists.slice(0, limit).map( (entry) => entry.Warninglist, ); } else { responseData = responseData.Warninglists.map((entry) => entry.Warninglist); } } } } catch (error) { if (this.continueOnFail(error)) { const executionErrorData = this.helpers.constructExecutionMetaData( this.helpers.returnJsonArray({ error: error.message }), { itemData: { item: i } }, ); returnData.push(...executionErrorData); continue; } throw error; } const executionData = this.helpers.constructExecutionMetaData( this.helpers.returnJsonArray(responseData as IDataObject[]), { itemData: { item: i } }, ); returnData.push(...executionData); } return [returnData]; } }