From 3ccc1f65dd7fa6a980ca282b0f1f595990425481 Mon Sep 17 00:00:00 2001 From: Ricardo Espinoza Date: Tue, 11 Oct 2022 10:07:36 -0400 Subject: [PATCH] feat(Venafi TLS Protect Cloud Trigger Node): add Venafi TLS Protect Cloud Trigger (#4288) * :sparkles: Add Venafi webhook trigger * Fix typo --- .../Venafi/ProtectCloud/GenericFunctions.ts | 5 +- .../VenafiTlsProtectCloudTrigger.node.ts | 208 +++++++++++++----- packages/nodes-base/package.json | 1 + 3 files changed, 152 insertions(+), 62 deletions(-) diff --git a/packages/nodes-base/nodes/Venafi/ProtectCloud/GenericFunctions.ts b/packages/nodes-base/nodes/Venafi/ProtectCloud/GenericFunctions.ts index 678dd9d0a94e5..2da5c7e38792f 100644 --- a/packages/nodes-base/nodes/Venafi/ProtectCloud/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Venafi/ProtectCloud/GenericFunctions.ts @@ -4,17 +4,16 @@ import { IExecuteFunctions, IExecuteSingleFunctions, ILoadOptionsFunctions, - IPollFunctions, } from 'n8n-core'; -import { IDataObject, JsonObject, NodeApiError } from 'n8n-workflow'; +import { IDataObject, IHookFunctions, JsonObject, NodeApiError } from 'n8n-workflow'; import { get } from 'lodash'; import * as nacl_factory from 'js-nacl'; export async function venafiApiRequest( - this: IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions | IPollFunctions, + this: IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions | IHookFunctions, method: string, resource: string, body = {}, diff --git a/packages/nodes-base/nodes/Venafi/ProtectCloud/VenafiTlsProtectCloudTrigger.node.ts b/packages/nodes-base/nodes/Venafi/ProtectCloud/VenafiTlsProtectCloudTrigger.node.ts index 8db5ba08ca2a1..92fa8a2c1e3fc 100644 --- a/packages/nodes-base/nodes/Venafi/ProtectCloud/VenafiTlsProtectCloudTrigger.node.ts +++ b/packages/nodes-base/nodes/Venafi/ProtectCloud/VenafiTlsProtectCloudTrigger.node.ts @@ -1,8 +1,12 @@ -import { IPollFunctions } from 'n8n-core'; - -import { INodeExecutionData, INodeType, INodeTypeDescription } from 'n8n-workflow'; - -import moment from 'moment'; +import { + IHookFunctions, + ILoadOptionsFunctions, + INodePropertyOptions, + INodeType, + INodeTypeDescription, + IWebhookFunctions, + IWebhookResponseData, +} from 'n8n-workflow'; import { venafiApiRequest } from './GenericFunctions'; @@ -13,10 +17,9 @@ export class VenafiTlsProtectCloudTrigger implements INodeType { icon: 'file:../venafi.svg', group: ['trigger'], version: 1, - subtitle: '={{$parameter["triggerOn"]}}', description: 'Starts the workflow when Venafi events occure', defaults: { - name: 'Venafi TLS Protect Cloud​', + name: 'Venafi TLS Protect Cloud​ Trigger', color: '#000000', }, credentials: [ @@ -25,77 +28,164 @@ export class VenafiTlsProtectCloudTrigger implements INodeType { required: true, }, ], - polling: true, + webhooks: [ + { + name: 'default', + httpMethod: 'POST', + responseMode: 'onReceived', + path: 'webhook', + }, + ], inputs: [], outputs: ['main'], properties: [ { - displayName: 'Trigger On', - name: 'trigger On', + // eslint-disable-next-line n8n-nodes-base/node-param-display-name-wrong-for-dynamic-options + displayName: 'Resource', + name: 'resource', type: 'options', - options: [ - { - name: 'Certificate Expired', - value: 'certificateExpired', - }, - ], + noDataExpression: true, + typeOptions: { + loadOptionsMethod: 'getActivityTypes', + }, + required: true, + default: [], + description: + 'Choose from the list, or specify an ID using an expression. Choose from the list, or specify IDs using an expression. Choose from the list, or specify an ID using an expression.', + }, + { + // eslint-disable-next-line n8n-nodes-base/node-param-display-name-wrong-for-dynamic-multi-options + displayName: 'Trigger On', + name: 'triggerOn', + type: 'multiOptions', + typeOptions: { + loadOptionsMethod: 'getActivitySubTypes', + loadOptionsDependsOn: ['resource'], + }, required: true, - default: 'certificateExpired', + default: [], + description: + 'Choose from the list, or specify an ID using an expression. Choose from the list, or specify IDs using an expression. Choose from the list, or specify an ID using an expression. Choose from the list, or specify IDs using an expression.', }, ], }; - async poll(this: IPollFunctions): Promise { - const webhookData = this.getWorkflowStaticData('node'); - const event = this.getNodeParameter('event') as string; + methods = { + loadOptions: { + async getActivityTypes(this: ILoadOptionsFunctions): Promise { + const activitytypes = await venafiApiRequest.call(this, 'GET', '/v1/activitytypes'); + return activitytypes.map( + ({ key, readableName }: { key: string; readableName: string }) => ({ + name: readableName, + value: key, + }), + ); + }, + async getActivitySubTypes(this: ILoadOptionsFunctions): Promise { + const resource = this.getCurrentNodeParameter('resource') as string; + const activitytypes = await venafiApiRequest.call(this, 'GET', '/v1/activitytypes'); + const activity = activitytypes.find(({ key }: { key: string }) => key === resource) as { + values: [{ key: string; readableName: string }]; + }; + const subActivities = activity.values.map(({ key, readableName }) => ({ name: readableName, value: key })); + subActivities.unshift({ name: '[All]', value: '*' }); + return subActivities; + }, + }, + }; - const now = moment().format(); + webhookMethods = { + default: { + async checkExists(this: IHookFunctions): Promise { + const webhookUrl = this.getNodeWebhookUrl('default'); - const startDate = webhookData.lastTimeChecked || now; - const endDate = now; + const { connectors } = await venafiApiRequest.call(this, 'GET', '/v1/connectors'); - const { certificates: certificates } = await venafiApiRequest.call( - this, - 'POST', - `/outagedetection/v1/certificatesearch`, - { - expression: { - operands: [ - { - operator: 'AND', - operands: [ - { - field: 'validityEnd', - operator: 'LTE', - values: [endDate], - }, - { - field: 'validityEnd', - operator: 'GTE', - values: [startDate], - }, - ], + for (const connector of connectors) { + const { + id, + properties: { + target: { + connection: { url }, + }, }, - ], - }, - ordering: { - orders: [ - { - field: 'certificatInstanceModificationDate', - direction: 'DESC', + } = connector; + + if (url === webhookUrl) { + await venafiApiRequest.call(this, 'DELETE', `/v1/connectors/${id}`); + return false; + } + } + return false; + }, + async create(this: IHookFunctions): Promise { + const webhookUrl = this.getNodeWebhookUrl('default'); + const resource = this.getNodeParameter('resource') as string; + const body = { + name: `n8n-webhook (${webhookUrl})`, + properties: { + connectorKind: 'WEBHOOK', + target: { + type: 'generic', + connection: { + url: webhookUrl, + }, }, - ], - }, + filter: { + activityTypes: [resource], + }, + }, + }; + + const responseData = await venafiApiRequest.call(this, 'POST', `/v1/connectors`, body); + + if (responseData.id === undefined) { + // Required data is missing so was not successful + return false; + } + + const webhookData = this.getWorkflowStaticData('node'); + webhookData.webhookId = responseData.id as string; + + return true; + }, + async delete(this: IHookFunctions): Promise { + const webhookData = this.getWorkflowStaticData('node'); + + if (webhookData.webhookId !== undefined) { + try { + await venafiApiRequest.call(this, 'DELETE', `/v1/connectors/${webhookData.webhookId}`); + } catch (error) { + return false; + } + + // Remove from the static workflow data so that it is clear + // that no webhooks are registred anymore + delete webhookData.webhookId; + } + + return true; }, - {}, - ); + }, + }; - webhookData.lastTimeChecked = endDate; + async webhook(this: IWebhookFunctions): Promise { + const bodyData = this.getBodyData() as { message: string; eventName: string }; + const triggerOn = this.getNodeParameter('triggerOn') as string; - if (Array.isArray(certificates) && certificates.length) { - return [this.helpers.returnJsonArray(certificates)]; + if (Object.keys(bodyData).length === 1 && bodyData.message) { + // Is a create webhook confirmation request + const res = this.getResponseObject(); + res.status(200).end(); + return { + noWebhookResponse: true, + }; } - return null; + if (!triggerOn.includes('*') && !triggerOn.includes(bodyData.eventName)) return {}; + + return { + workflowData: [this.helpers.returnJsonArray(bodyData)], + }; } } diff --git a/packages/nodes-base/package.json b/packages/nodes-base/package.json index 51aee202af8dd..f6564eb06575d 100644 --- a/packages/nodes-base/package.json +++ b/packages/nodes-base/package.json @@ -688,6 +688,7 @@ "dist/nodes/UrlScanIo/UrlScanIo.node.js", "dist/nodes/Vero/Vero.node.js", "dist/nodes/Venafi/ProtectCloud/VenafiTlsProtectCloud.node.js", + "dist/nodes/Venafi/ProtectCloud/VenafiTlsProtectCloudTrigger.node.js", "dist/nodes/Venafi/Datacenter/VenafiTlsProtectDatacenter.node.js", "dist/nodes/Vonage/Vonage.node.js", "dist/nodes/Wait/Wait.node.js",