diff --git a/packages/nodes-base/credentials/CortexApi.credentials.ts b/packages/nodes-base/credentials/CortexApi.credentials.ts
new file mode 100644
index 0000000000..eb07506bbf
--- /dev/null
+++ b/packages/nodes-base/credentials/CortexApi.credentials.ts
@@ -0,0 +1,26 @@
+import {
+ ICredentialType,
+ NodePropertyTypes,
+} from 'n8n-workflow';
+
+
+export class CortexApi implements ICredentialType {
+ name = 'cortexApi';
+ displayName = 'Cortex API';
+ properties = [
+ {
+ displayName: 'API Key',
+ name: 'cortexApiKey',
+ type: 'string' as NodePropertyTypes,
+ default: '',
+ },
+ {
+ displayName: 'Cortex Instance',
+ name: 'host',
+ type: 'string' as NodePropertyTypes,
+ description: 'The URL of the Cortex instance',
+ default: '',
+ placeholder:'https://localhost:9001'
+ },
+ ];
+}
diff --git a/packages/nodes-base/credentials/TheHiveApi.credentials.ts b/packages/nodes-base/credentials/TheHiveApi.credentials.ts
new file mode 100644
index 0000000000..3476ea38bc
--- /dev/null
+++ b/packages/nodes-base/credentials/TheHiveApi.credentials.ts
@@ -0,0 +1,44 @@
+import {
+ ICredentialType,
+ NodePropertyTypes,
+} from 'n8n-workflow';
+
+export class TheHiveApi implements ICredentialType {
+ name = 'theHiveApi';
+ displayName = 'The Hive API';
+ properties = [
+ {
+ displayName: 'API Key',
+ name: 'ApiKey',
+ type: 'string' as NodePropertyTypes,
+ default: '',
+ },
+ {
+ displayName: 'URL',
+ name: 'url',
+ default: '',
+ type: 'string' as NodePropertyTypes,
+ description: 'The URL of TheHive instance',
+ placeholder: 'https://localhost:9000',
+ },
+ {
+ displayName: 'API Version',
+ name: 'apiVersion',
+ default: '',
+ type: 'options' as NodePropertyTypes,
+ description: 'The version of api to be used',
+ options:[
+ {
+ name:'Version 1',
+ value:'v1',
+ description:'API version supported by TheHive 4'
+ },
+ {
+ name:'Version 0',
+ value:'',
+ description:'API version supported by TheHive 3'
+ },
+ ],
+ },
+ ];
+}
diff --git a/packages/nodes-base/nodes/Cortex/AnalyzerDescriptions.ts b/packages/nodes-base/nodes/Cortex/AnalyzerDescriptions.ts
new file mode 100644
index 0000000000..318e5b7fa0
--- /dev/null
+++ b/packages/nodes-base/nodes/Cortex/AnalyzerDescriptions.ts
@@ -0,0 +1,210 @@
+import {
+ INodeProperties,
+} from 'n8n-workflow';
+
+import {
+ TLP,
+}from './AnalyzerInterface';
+
+export const analyzersOperations = [
+ {
+ displayName: 'Operation',
+ name: 'operation',
+ type: 'options',
+ required: true,
+ description: 'Choose an operation',
+ displayOptions: {
+ show: {
+ resource: [
+ 'analyzer',
+ ],
+ },
+ },
+ default: 'execute',
+ options: [
+ {
+ name: 'Execute',
+ value: 'execute',
+ description: 'Execute Analyzer',
+ },
+ ],
+ },
+] as INodeProperties[];
+
+export const analyzerFields: INodeProperties[] =[
+ {
+ displayName: 'Analyzer Type',
+ name: 'analyzer',
+ type: 'options',
+ required: true,
+ typeOptions: {
+ loadOptionsMethod: 'loadActiveAnalyzers',
+ },
+ displayOptions:{
+ show: {
+ resource: [
+ 'analyzer',
+ ],
+ operation:[
+ 'execute',
+ ],
+ },
+ },
+ description: 'Choose the analyzer',
+ default: '',
+ },
+ {
+ displayName: 'Observable Type',
+ name: 'observableType',
+ type: 'options',
+ required: true,
+ displayOptions:{
+ show: {
+ resource: [
+ 'analyzer',
+ ],
+ operation:[
+ 'execute',
+ ],
+ },
+ hide:{
+ analyzer:[
+ '',
+ ],
+ },
+ },
+ typeOptions:{
+ loadOptionsMethod: 'loadObservableOptions',
+ loadOptionsDependsOn: [
+ 'analyzer',
+ ],
+ },
+ default: '',
+ description: 'Choose the observable type',
+ },
+
+ // Observable type != file
+ {
+ displayName: 'Observable Value',
+ name: 'observableValue',
+ type: 'string',
+ required: true,
+ displayOptions: {
+ show: {
+ resource: [
+ 'analyzer',
+ ],
+ operation:[
+ 'execute',
+ ],
+ },
+ hide:{
+ observableType: [
+ 'file',
+ ],
+ analyzer:[
+ '',
+ ],
+ },
+ },
+ default: '',
+ description: 'Enter the observable value',
+ },
+ {
+ displayName: 'Binary Property',
+ name: 'binaryPropertyName',
+ type: 'string',
+ default: 'data',
+ required: true,
+ displayOptions: {
+ show: {
+ observableType: [
+ 'file',
+ ],
+ resource: [
+ 'analyzer',
+ ],
+ operation: [
+ 'execute',
+ ],
+ },
+ },
+ description: 'Name of the binary property to which to
write the data of the read file.',
+ },
+ {
+ displayName: 'TLP',
+ name: 'tlp',
+ type: 'options',
+ required: false,
+ displayOptions:{
+ show: {
+ resource: [
+ 'analyzer',
+ ],
+ operation: [
+ 'execute',
+ ],
+ },
+ hide:{
+ observableType: [
+ '',
+ ],
+ analyzer: [
+ '',
+ ],
+ },
+ },
+ options: [
+ {
+ name: 'White',
+ value: TLP.white,
+ },
+ {
+ name: 'Green',
+ value: TLP.green,
+ },
+ {
+ name: 'Amber',
+ value: TLP.amber,
+ },{
+ name: 'Red',
+ value: TLP.red,
+ }
+ ],
+ default: 2,
+ description: 'The TLP of the analyzed observable',
+ },
+ {
+ displayName: 'Additional Fields',
+ name: 'additionalFields',
+ type: 'collection',
+ placeholder: 'Add Field',
+ default: {},
+ displayOptions: {
+ show: {
+ resource: [
+ 'analyzer',
+ ],
+ operation: [
+ 'execute',
+ ],
+ },
+ },
+ options: [
+ {
+ displayName: 'Force',
+ name: 'force',
+ type: 'boolean',
+ default: false,
+ description: 'To force bypassing the cache, set this parameter to true',
+ },
+ {
+ displayName: 'Timeout (seconds)',
+ name: 'timeout',
+ type: 'number',
+ default: 3,
+ description: 'Timeout to wait for the report in case it is not available at the time the query was made',
+ },
+ ],
+ },
+];
diff --git a/packages/nodes-base/nodes/Cortex/AnalyzerInterface.ts b/packages/nodes-base/nodes/Cortex/AnalyzerInterface.ts
new file mode 100644
index 0000000000..608fef6ed6
--- /dev/null
+++ b/packages/nodes-base/nodes/Cortex/AnalyzerInterface.ts
@@ -0,0 +1,86 @@
+import {
+ IDataObject,
+}from 'n8n-workflow';
+
+export enum JobStatus {
+ WAITING = 'Waiting',
+ INPROGRESS = 'InProgress',
+ SUCCESS = 'Success',
+ FAILURE = 'Failure',
+ DELETED = 'Deleted'
+}
+
+export enum TLP {
+ white,
+ green,
+ amber,
+ red
+}
+
+export enum ObservableDataType {
+ 'domain'= 'domain',
+ 'file'= 'file',
+ 'filename'= 'filename',
+ 'fqdn'= 'fqdn',
+ 'hash'= 'hash',
+ 'ip'= 'ip',
+ 'mail'= 'mail',
+ 'mail_subject'= 'mail_subject',
+ 'other'= 'other',
+ 'regexp'= 'regexp',
+ 'registry'= 'registry',
+ 'uri_path'= 'uri_path',
+ 'url'= 'url',
+ 'user-agent'= 'user-agent'
+}
+export interface IJob{
+ id?: string;
+ organization?: string;
+ analyzerDefinitionId?: string;
+ analyzerId?: string;
+ analyzerName?: string;
+ dataType?: ObservableDataType;
+ status?: JobStatus;
+ data?: string;
+ attachment?: IDataObject;
+ parameters?: IDataObject;
+ message? :string;
+ tlp?: TLP;
+ startDate?: Date;
+ endDate?: Date;
+ createdAt?: Date;
+ createdBy?: string;
+ updatedAt?: Date;
+ updatedBy?: Date;
+ report?: IDataObject | string;
+}
+export interface IAnalyzer{
+ id?: string;
+ analyzerDefinitionId?: string;
+ name? :string;
+ version?: string;
+ description?: string;
+ author?: string;
+ url?: string;
+ license?: string;
+ dataTypeList?: ObservableDataType[];
+ baseConfig?: string;
+ jobCache?: number;
+ rate?: number;
+ rateUnit?: string;
+ configuration?: IDataObject;
+ createdBy?: string;
+ updatedAt?: Date;
+ updatedBy?: Date;
+}
+
+export interface IResponder{
+ id?: string;
+ name?: string;
+ version?: string;
+ description?: string;
+ dataTypeList?: string[];
+ maxTlp?: number;
+ maxPap?: number;
+ cortexIds?: string[] | undefined;
+}
diff --git a/packages/nodes-base/nodes/Cortex/Cortex.node.ts b/packages/nodes-base/nodes/Cortex/Cortex.node.ts
new file mode 100644
index 0000000000..97eb108ab2
--- /dev/null
+++ b/packages/nodes-base/nodes/Cortex/Cortex.node.ts
@@ -0,0 +1,469 @@
+import {
+ IExecuteFunctions,
+ BINARY_ENCODING,
+} from 'n8n-core';
+
+import {
+ cortexApiRequest,
+ getEntityLabel,
+ prepareParameters,
+ splitTags,
+} from './GenericFunctions';
+
+import {
+ analyzersOperations,
+ analyzerFields,
+} from './AnalyzerDescriptions';
+
+import {
+ INodeExecutionData,
+ INodeType,
+ INodeTypeDescription,
+ INodePropertyOptions,
+ ILoadOptionsFunctions,
+ IDataObject,
+ IBinaryData,
+} from 'n8n-workflow';
+
+import {
+ respondersOperations,
+ responderFields,
+} from './ResponderDescription';
+
+import {
+ jobFields,
+ jobOperations,
+} from './JobDescription';
+
+import {
+ upperFirst,
+} from 'lodash';
+
+import {
+ IJob,
+} from './AnalyzerInterface';
+
+import {
+ createHash,
+} from 'crypto';
+
+import * as changeCase from 'change-case';
+
+export class Cortex implements INodeType {
+ description: INodeTypeDescription = {
+ displayName: 'Cortex',
+ name: 'cortex',
+ icon: 'file:cortex.png',
+ group: ['transform'],
+ subtitle: '={{$parameter["resource"]+ ": " + $parameter["operation"]}}',
+ version: 1,
+ description: 'Apply the Cortex analyzer/responder on the given entity',
+ defaults: {
+ name: 'Cortex',
+ color: '#54c4c3',
+ },
+ inputs: ['main'],
+ outputs: ['main'],
+ credentials: [
+ {
+ name: 'cortexApi',
+ required: true,
+ },
+ ],
+ properties: [
+ // Node properties which the user gets displayed and
+ // can change on the node.
+ {
+ displayName:'Resource',
+ name:'resource',
+ type:'options',
+ options:[
+ {
+ name: 'Analyzer',
+ value:'analyzer',
+ },
+ {
+ name: 'Responder',
+ value:'responder',
+ },
+ {
+ name: 'Job',
+ value:'job',
+ },
+ ],
+ default: 'analyzer',
+ description: 'Choose a resource',
+ required: true,
+ },
+ ...analyzersOperations,
+ ...analyzerFields,
+ ...respondersOperations,
+ ...responderFields,
+ ...jobOperations,
+ ...jobFields
+ ],
+ };
+
+ methods = {
+ loadOptions: {
+
+ async loadActiveAnalyzers(this: ILoadOptionsFunctions): Promise {
+ // request the enabled analyzers from instance
+ const requestResult = await cortexApiRequest.call(
+ this,
+ 'POST',
+ `/analyzer/_search`,
+ );
+
+ const returnData: INodePropertyOptions[] = [];
+
+ for (const analyzer of requestResult) {
+ returnData.push({
+ name: analyzer.name as string,
+ value: `${analyzer.id as string}::${analyzer.name as string}`,
+ description: analyzer.description as string,
+ });
+ }
+
+ return returnData;
+ },
+
+ async loadActiveResponders(this: ILoadOptionsFunctions): Promise {
+ // request the enabled responders from instance
+ const requestResult = await cortexApiRequest.call(
+ this,
+ 'GET',
+ `/responder`,
+ );
+
+ const returnData: INodePropertyOptions[] = [];
+ for (const responder of requestResult) {
+ returnData.push({
+ name: responder.name as string,
+ value: `${responder.id as string}::${responder.name as string}`,
+ description: responder.description as string,
+ });
+ }
+ return returnData;
+ },
+
+ async loadObservableOptions(this: ILoadOptionsFunctions): Promise {
+ const selectedAnalyzerId = (this.getNodeParameter('analyzer') as string).split('::')[0];
+ // request the analyzers from instance
+ const requestResult = await cortexApiRequest.call(
+ this,
+ 'GET',
+ `/analyzer/${selectedAnalyzerId}`,
+ );
+
+ // parse supported observable types into options
+ const returnData: INodePropertyOptions[] = [];
+ for (const dataType of requestResult.dataTypeList) {
+ returnData.push(
+ {
+ name: upperFirst(dataType as string),
+ value: dataType as string,
+ },
+ );
+ }
+ return returnData;
+ },
+
+ async loadDataTypeOptions(this: ILoadOptionsFunctions): Promise {
+ const selectedResponderId = (this.getNodeParameter('responder') as string).split('::')[0];
+ // request the responder from instance
+ const requestResult = await cortexApiRequest.call(
+ this,
+ 'GET',
+ `/responder/${selectedResponderId}`,
+ );
+ // parse the accepted dataType into options
+ const returnData: INodePropertyOptions[] = [];
+ for (const dataType of requestResult.dataTypeList) {
+ returnData.push(
+ {
+ value: (dataType as string).split(':')[1],
+ name: changeCase.capitalCase((dataType as string).split(':')[1])
+ },
+ );
+ }
+ return returnData;
+ },
+
+ },
+ };
+
+ async execute(this: IExecuteFunctions): Promise {
+ const items = this.getInputData();
+ const returnData: IDataObject[] = [];
+ const length = (items.length as unknown) as number;
+ const qs: IDataObject = {};
+ let responseData;
+ const resource = this.getNodeParameter('resource', 0) as string;
+ const operation = this.getNodeParameter('operation', 0) as string;
+
+ for (let i = 0; i < length; i++) {
+ if (resource === 'analyzer') {
+ //https://github.com/TheHive-Project/CortexDocs/blob/master/api/api-guide.md#run
+ if (operation === 'execute') {
+
+ let force = false;
+
+ const analyzer = this.getNodeParameter('analyzer', i) as string;
+
+ const observableType = this.getNodeParameter('observableType', i) as string;
+
+ const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject;
+
+ const tlp = this.getNodeParameter('tlp', i) as string;
+
+ const body: IDataObject = {
+ dataType: observableType,
+ tlp,
+ };
+
+ if (additionalFields.force === true) {
+ force = true;
+ }
+
+ if (observableType === 'file') {
+
+ const item = items[i];
+
+ if (item.binary === undefined) {
+ throw new Error('No binary data exists on item!');
+ }
+
+ const binaryPropertyName = this.getNodeParameter('binaryPropertyName', i) as string;
+
+ if (item.binary[binaryPropertyName] === undefined) {
+ throw new Error(`No binary data property "${binaryPropertyName}" does not exists on item!`);
+ }
+
+ const fileBufferData = Buffer.from(item.binary[binaryPropertyName].data, BINARY_ENCODING);
+
+ const options = {
+ formData: {
+ data: {
+ value: fileBufferData,
+ options: {
+ contentType: item.binary[binaryPropertyName].mimeType,
+ filename: item.binary[binaryPropertyName].fileName,
+ }
+ },
+ _json: JSON.stringify({
+ dataType: observableType,
+ tlp,
+ })
+ }
+ };
+
+ responseData = await cortexApiRequest.call(
+ this,
+ 'POST',
+ `/analyzer/${analyzer.split('::')[0]}/run`,
+ {},
+ { force },
+ '',
+ options,
+ ) as IJob;
+
+ continue;
+
+ } else {
+ const observableValue = this.getNodeParameter('observableValue', i) as string;
+
+ body.data = observableValue;
+
+ responseData = await cortexApiRequest.call(
+ this,
+ 'POST',
+ `/analyzer/${analyzer.split('::')[0]}/run`,
+ body,
+ { force },
+ ) as IJob;
+ }
+
+ if (additionalFields.timeout) {
+ responseData = await cortexApiRequest.call(
+ this,
+ 'GET',
+ `/job/${responseData.id}/waitreport`,
+ {},
+ { atMost: `${additionalFields.timeout}second` },
+ );
+ }
+ }
+ }
+
+ if (resource === 'job') {
+ //https://github.com/TheHive-Project/CortexDocs/blob/master/api/api-guide.md#get-details-1
+ if (operation === 'get') {
+
+ const jobId = this.getNodeParameter('jobId', i) as string;
+
+ responseData = await cortexApiRequest.call(
+ this,
+ 'GET',
+ `/job/${jobId}`,
+ );
+ }
+ //https://github.com/TheHive-Project/CortexDocs/blob/master/api/api-guide.md#get-details-and-report
+ if (operation === 'report') {
+
+ const jobId = this.getNodeParameter('jobId', i) as string;
+
+ responseData = await cortexApiRequest.call(
+ this,
+ 'GET',
+ `/job/${jobId}/report`,
+ );
+ }
+ }
+
+ if (resource === 'responder') {
+ if (operation === 'execute') {
+ const responderId = (this.getNodeParameter('responder', i) as string).split('::')[0];
+
+ const entityType = this.getNodeParameter('entityType', i) as string;
+
+ const isJSON = this.getNodeParameter('jsonObject',i) as boolean;
+ let body:IDataObject;
+
+
+ if(isJSON){
+
+
+ const entityJson = JSON.parse(this.getNodeParameter('objectData', i) as string);
+
+ body = {
+ responderId,
+ label: getEntityLabel(entityJson),
+ dataType: `thehive:${entityType}`,
+ data: entityJson,
+ tlp: entityJson.tlp || 2,
+ pap: entityJson.pap || 2,
+ message: entityJson.message || '',
+ parameters:[],
+ };
+
+ }else{
+
+ const values = (this.getNodeParameter('parameters',i) as IDataObject).values as IDataObject;
+
+ body= {
+ responderId,
+ dataType: `thehive:${entityType}`,
+ data: {
+ _type: entityType,
+ ...prepareParameters(values)
+ }
+ };
+ if( entityType === 'alert'){
+ // deal with alert artifacts
+ const artifacts = (body.data as IDataObject).artifacts as IDataObject;
+
+ if (artifacts) {
+
+ const artifactValues = (artifacts as IDataObject).artifactValues as IDataObject[];
+
+ if (artifactValues) {
+
+ const artifactData = [];
+
+ for (const artifactvalue of artifactValues) {
+
+ const element: IDataObject = {};
+
+ element.message = artifactvalue.message as string;
+
+ element.tags = splitTags(artifactvalue.tags as string) as string[];
+
+ element.dataType = artifactvalue.dataType as string;
+
+ element.data = artifactvalue.data as string;
+
+ if (artifactvalue.dataType === 'file') {
+
+ const item = items[i];
+
+ if (item.binary === undefined) {
+ throw new Error('No binary data exists on item!');
+ }
+
+ const binaryPropertyName = artifactvalue.binaryProperty as string;
+
+ if (item.binary[binaryPropertyName] === undefined) {
+ throw new Error(`No binary data property '${binaryPropertyName}' does not exists on item!`);
+ }
+
+ const binaryData = item.binary[binaryPropertyName] as IBinaryData;
+
+ element.data = `${binaryData.fileName};${binaryData.mimeType};${binaryData.data}`;
+ }
+
+ artifactData.push(element);
+ }
+
+ (body.data as IDataObject).artifacts = artifactData;
+ }
+ }
+ }
+ if(entityType ==='case_artifact'){
+ // deal with file observable
+
+ if ((body.data as IDataObject).dataType === 'file') {
+
+ const item = items[i];
+
+ if (item.binary === undefined) {
+ throw new Error('No binary data exists on item!');
+ }
+
+ const binaryPropertyName = (body.data as IDataObject).binaryPropertyName as string;
+ if (item.binary[binaryPropertyName] === undefined) {
+ throw new Error(`No binary data property "${binaryPropertyName}" does not exists on item!`);
+ }
+
+ const fileBufferData = Buffer.from(item.binary[binaryPropertyName].data, BINARY_ENCODING);
+ const sha256 = createHash('sha256').update(fileBufferData).digest('hex');
+
+ (body.data as IDataObject).attachment = {
+ name: item.binary[binaryPropertyName].fileName,
+ hashes: [
+ sha256,
+ createHash('sha1').update(fileBufferData).digest('hex'),
+ createHash('md5').update(fileBufferData).digest('hex')
+ ],
+ size:fileBufferData.byteLength,
+ contentType: item.binary[binaryPropertyName].mimeType,
+ id:sha256,
+ };
+
+ delete (body.data as IDataObject).binaryPropertyName;
+ }
+ }
+ // add the job label after getting all entity attributes
+ body = {
+ label: getEntityLabel(body.data as IDataObject),
+ ...body
+ };
+
+ }
+ responseData = await cortexApiRequest.call(
+ this,
+ 'POST',
+ `/responder/${responderId}/run`,
+ body,
+ ) as IJob;
+ }
+ }
+ }
+ if (Array.isArray(responseData)) {
+ returnData.push.apply(returnData, responseData as IDataObject[]);
+ } else if (responseData !== undefined) {
+ returnData.push(responseData as IDataObject);
+ }
+ return [this.helpers.returnJsonArray(returnData)];
+ }
+}
diff --git a/packages/nodes-base/nodes/Cortex/GenericFunctions.ts b/packages/nodes-base/nodes/Cortex/GenericFunctions.ts
new file mode 100644
index 0000000000..0c301ff3dd
--- /dev/null
+++ b/packages/nodes-base/nodes/Cortex/GenericFunctions.ts
@@ -0,0 +1,109 @@
+import {
+ OptionsWithUri,
+} from 'request';
+
+import {
+ IAnalyzer,
+ IJob,
+ IResponder,
+} from './AnalyzerInterface';
+
+import {
+ IExecuteFunctions,
+ IHookFunctions,
+ ILoadOptionsFunctions,
+ IExecuteSingleFunctions,
+} from 'n8n-core';
+
+import {
+ IDataObject,
+} from 'n8n-workflow';
+
+import * as moment from 'moment';
+
+export async function cortexApiRequest(this: IHookFunctions | IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions, method: string, resource: string, body: any = {}, query: IDataObject = {}, uri?: string, option: IDataObject = {}): Promise { // tslint:disable-line:no-any
+
+ const credentials = this.getCredentials('cortexApi');
+
+ if (credentials === undefined) {
+ throw new Error('No credentials got returned!');
+ }
+
+ const headerWithAuthentication = Object.assign({}, { Authorization: ` Bearer ${credentials.cortexApiKey}`});
+
+ let options: OptionsWithUri = {
+ headers: headerWithAuthentication,
+ method,
+ qs: query,
+ uri: uri || `${credentials.host}/api${resource}`,
+ body,
+ json: true,
+
+ };
+ if (Object.keys(option).length !== 0) {
+ options = Object.assign({},options, option);
+ }
+ if (Object.keys(body).length === 0) {
+ delete options.body;
+ }
+ if (Object.keys(query).length === 0) {
+ delete options.qs;
+ }
+
+ try {
+ return await this.helpers.request!(options);
+ } catch (error) {
+ if (error.error ) {
+ const errorMessage = `Cortex error response [${error.statusCode}]: ${error.error.message}`;
+ throw new Error(errorMessage);
+ } else throw error;
+ }
+}
+
+export function getEntityLabel(entity: IDataObject): string{
+ let label = '';
+ switch (entity._type) {
+ case 'case':
+ label = `#${entity.caseId} ${entity.title}`;
+ break;
+ case 'case_artifact':
+ //@ts-ignore
+ label = `[${entity.dataType}] ${entity.data?entity.data:(entity.attachment.name)}`;
+ break;
+ case 'alert':
+ label = `[${entity.source}:${entity.sourceRef}] ${entity.title}`;
+ break;
+ case 'case_task_log':
+ label = `${entity.message} from ${entity.createdBy}`;
+ break;
+ case 'case_task':
+ label = `${entity.title} (${entity.status})`;
+ break;
+ case 'job':
+ label = `${entity.analyzerName} (${entity.status})`;
+ break;
+ default:
+ break;
+ }
+ return label;
+}
+
+export function splitTags(tags: string): string[] {
+ return tags.split(',').filter(tag => tag !== ' ' && tag);
+}
+
+export function prepareParameters(values: IDataObject): IDataObject {
+ const response: IDataObject = {};
+ for (const key in values) {
+ if (values[key]!== undefined && values[key]!==null && values[key]!=='') {
+ if (moment(values[key] as string, moment.ISO_8601).isValid()) {
+ response[key] = Date.parse(values[key] as string);
+ } else if (key === 'tags') {
+ response[key] = splitTags(values[key] as string);
+ } else {
+ response[key] = values[key];
+ }
+ }
+ }
+ return response;
+}
diff --git a/packages/nodes-base/nodes/Cortex/JobDescription.ts b/packages/nodes-base/nodes/Cortex/JobDescription.ts
new file mode 100644
index 0000000000..1db9f41f69
--- /dev/null
+++ b/packages/nodes-base/nodes/Cortex/JobDescription.ts
@@ -0,0 +1,55 @@
+import {
+ INodeProperties,
+} from 'n8n-workflow';
+
+export const jobOperations = [
+ {
+ displayName: 'Operation',
+ name: 'operation',
+ type: 'options',
+ description:'Choose an operation',
+ required: true,
+ displayOptions: {
+ show: {
+ resource: [
+ 'job',
+ ],
+ },
+ },
+ options: [
+ {
+ name: 'Get',
+ value: 'get',
+ description: 'Get job details',
+ },
+ {
+ name: 'Report',
+ value: 'report',
+ description: 'Get job report',
+ },
+ ],
+ default: 'get',
+ },
+] as INodeProperties[];
+
+export const jobFields: INodeProperties[] =[
+ {
+ displayName: 'Job ID',
+ name: 'jobId',
+ type: 'string',
+ required: true,
+ displayOptions:{
+ show: {
+ resource: [
+ 'job',
+ ],
+ operation: [
+ 'get',
+ 'report',
+ ],
+ },
+ },
+ default:'',
+ description: 'ID of the job',
+ },
+];
diff --git a/packages/nodes-base/nodes/Cortex/ResponderDescription.ts b/packages/nodes-base/nodes/Cortex/ResponderDescription.ts
new file mode 100644
index 0000000000..14362a61e4
--- /dev/null
+++ b/packages/nodes-base/nodes/Cortex/ResponderDescription.ts
@@ -0,0 +1,892 @@
+import {
+ INodeProperties,
+} from 'n8n-workflow';
+
+import {
+ TLP,
+} from './AnalyzerInterface';
+
+export const respondersOperations = [
+ {
+ displayName: 'Operation',
+ name: 'operation',
+ type: 'options',
+ required: true,
+ description: 'Choose an operation',
+ displayOptions: {
+ show: {
+ resource: [
+ 'responder',
+ ],
+ },
+ },
+ options: [
+ {
+ name: 'Execute',
+ value: 'execute',
+ description: 'Execute Responder'
+ }
+ ],
+ default: 'execute'
+ }
+] as INodeProperties[];
+
+export const responderFields: INodeProperties[] = [
+ {
+ displayName: 'Responder Type',
+ name: 'responder',
+ type: 'options',
+ required: true,
+ typeOptions: {
+ loadOptionsMethod: 'loadActiveResponders'
+ },
+ default: '',
+ displayOptions: {
+ show: {
+ resource: [
+ 'responder',
+ ],
+ },
+ },
+ description: 'Choose the responder'
+ },
+ {
+ displayName: 'Entity Type',
+ name: 'entityType',
+ type: 'options',
+ required: true,
+ displayOptions: {
+ show: {
+ resource: [
+ 'responder',
+ ]
+ },
+ },
+ typeOptions: {
+ loadOptionsMethod: 'loadDataTypeOptions',
+ loadOptionsDependsOn: [
+ 'responder',
+ ],
+ },
+ default: '',
+ description: 'Choose the Data type',
+ },
+ {
+ displayName: 'JSON Parameters',
+ name: 'jsonObject',
+ type: 'boolean',
+ default: false,
+ description: 'Choose between providing JSON object or seperated attributes',
+ displayOptions: {
+ show: {
+ resource: [
+ 'responder',
+ ],
+ },
+ },
+ },
+ {
+ displayName: 'Entity Object (JSON)',
+ name: 'objectData',
+ type: 'string',
+ required: true,
+ displayOptions: {
+ show: {
+ resource: [
+ 'responder',
+ ],
+ jsonObject: [
+ true,
+ ],
+ },
+ },
+ default: ''
+ },
+ {
+ displayName: 'Parameters',
+ name: 'parameters',
+ type: 'fixedCollection',
+ placeholder: 'Add Parameter',
+ required: false,
+ options: [
+ {
+ displayName: 'Case Attributes',
+ name: 'values',
+ values: [
+ {
+ displayName: 'Title',
+ name: 'title',
+ type: 'string',
+ default: '',
+ description: 'Title of the case',
+ },
+ {
+ displayName: 'Description',
+ name: 'description',
+ type: 'string',
+ default: '',
+ description: 'Description of the case',
+ },
+ {
+ displayName: 'Severity',
+ name: 'severity',
+ type: 'options',
+ default: 2,
+ options: [
+ {
+ name: 'Low',
+ value: 1,
+ },
+ {
+ name: 'Medium',
+ value: 2,
+ },
+ {
+ name: 'High',
+ value: 3,
+ },
+ ],
+ description: 'Severity of the case. Default=Medium',
+ },
+ {
+ displayName: 'Start Date',
+ name: 'startDate',
+ type: 'dateTime',
+ default: '',
+ description: 'Date and time of the begin of the case default=now',
+ },
+ {
+ displayName: 'Owner',
+ name: 'owner',
+ type: 'string',
+ default: '',
+ description: `User who owns the case. This is automatically set to current user when status is set to InProgress`,
+ },
+ {
+ displayName: 'Flag',
+ name: 'flag',
+ type: 'boolean',
+ default: false,
+ description: 'Flag of the case default=false',
+ },
+ {
+ displayName: 'TLP',
+ name: 'tlp',
+ type: 'options',
+ default: 2,
+ options: [
+ {
+ name: 'White',
+ value: TLP.white,
+ },
+ {
+ name: 'Green',
+ value: TLP.green,
+ },
+ {
+ name: 'Amber',
+ value: TLP.amber,
+ },
+ {
+ name: 'Red',
+ value: TLP.red,
+ },
+ ],
+ description: 'Traffict Light Protocol (TLP). Default=Amber',
+ },
+ {
+ displayName: 'Tags',
+ name: 'tags',
+ type: 'string',
+ default: '',
+ placeholder:'tag1,tag2,...',
+ },
+ ],
+ },
+ ],
+ typeOptions:{
+ loadOptionsDependsOn:[
+ 'entityType',
+ ],
+ },
+ displayOptions: {
+ show: {
+ resource: [
+ 'responder',
+ ],
+ jsonObject: [
+ false,
+ ],
+ entityType: [
+ 'case',
+ ],
+ },
+ hide: {
+ entityType: [
+ '',
+ 'alert',
+ 'case_artifact',
+ 'case_task',
+ 'case_task_log',
+ ],
+ },
+ },
+ default: {}
+ },
+ {
+ displayName: 'Parameters',
+ name: 'parameters',
+ type: 'fixedCollection',
+ placeholder: 'Add Parameter',
+ required: false,
+ options: [
+ {
+ displayName: 'Alert Attributes',
+ name: 'values',
+ values: [
+ {
+ displayName: 'Title',
+ name: 'title',
+ type: 'string',
+ default: '',
+ description: 'Title of the alert',
+ },
+ {
+ displayName: 'Description',
+ name: 'description',
+ type: 'string',
+ default: '',
+ description: 'Description of the alert',
+ },
+ {
+ displayName: 'Severity',
+ name: 'severity',
+ type: 'options',
+ default: 2,
+ options:[
+ {
+ name: 'Low',
+ value: 1
+ },
+ {
+ name: 'Medium',
+ value: 2
+ },
+ {
+ name: 'High',
+ value: 3,
+ },
+ ],
+ description: 'Severity of the case. Default=Medium',
+ },
+ {
+ displayName: 'Date',
+ name: 'date',
+ type: 'dateTime',
+ default: '',
+ description: 'Date and time when the alert was raised default=now',
+ },
+ {
+ displayName: 'Tags',
+ name: 'tags',
+ type: 'string',
+ placeholder:'tag1,tag2,...',
+ default: ''
+ },
+ {
+ displayName: 'TLP',
+ name: 'tlp',
+ type: 'options',
+ default: 2,
+ options: [
+ {
+ name:'White',
+ value:TLP.white,
+ },
+ {
+ name:'Green',
+ value:TLP.green,
+ },
+ {
+ name:'Amber',
+ value:TLP.amber,
+ },{
+ name:'Red',
+ value:TLP.red,
+ }
+ ],
+ description: 'Traffict Light Protocol (TLP). Default=Amber',
+ },
+ {
+ displayName: 'Status',
+ name: 'status',
+ type: 'options',
+ default: 'New',
+ options:[
+ {
+ name: 'New',
+ value: 'New',
+ },
+ {
+ name: 'Updated',
+ value: 'Updated',
+ },
+ {
+ name: 'Ignored',
+ value: 'Ignored'
+ },
+ {
+ name: 'Imported',
+ value: 'Imported',
+ },
+ ],
+ description: 'Status of the alert. Default=New'
+ },
+ {
+ displayName: 'Type',
+ name: 'type',
+ type: 'string',
+ default: '',
+ description: 'Type of the alert',
+ },
+ {
+ displayName: 'Source',
+ name: 'source',
+ type: 'string',
+ default: '',
+ description: 'Source of the alert',
+ },
+ {
+ displayName: 'SourceRef',
+ name: 'sourceRef',
+ type: 'string',
+ default: '',
+ description: 'Source reference of the alert',
+ },
+ {
+ displayName: 'Follow',
+ name: 'follow',
+ type: 'boolean',
+ default: false
+ },
+ {
+ displayName: 'Artifacts',
+ name: 'artifacts',
+ type: 'fixedCollection',
+ placeholder:'Add an artifact',
+ required: false,
+ typeOptions: {
+ multipleValues: true,
+ multipleValueButtonText: 'Add an Artifact',
+ },
+ default: [],
+ options: [
+ {
+ displayName: 'Artifact',
+ name: 'artifactValues',
+ values: [
+ {
+ displayName: 'Data Type',
+ name: 'dataType',
+ type: 'options',
+ default: '',
+ options: [
+ {
+ name: 'Domain',
+ value: 'domain',
+ },
+ {
+ name: 'File',
+ value: 'file'
+ },
+ {
+ name: 'Filename',
+ value: 'filename'
+ },
+ {
+ name: 'Fqdn',
+ value: 'fqdn'
+ },
+ {
+ name: 'Hash',
+ value: 'hash'
+ },
+ {
+ name: 'IP',
+ value: 'ip'
+ },
+ {
+ name: 'Mail',
+ value: 'mail'
+ },
+ {
+ name: 'Mail Subject',
+ value: 'mail_subject'
+ },
+ {
+ name: 'Other',
+ value: 'other'
+ },
+ {
+ name: 'Regexp',
+ value: 'regexp'
+ },
+ {
+ name: 'Registry',
+ value: 'registry'
+ },
+ {
+ name: 'Uri Path',
+ value: 'uri_path'
+ },
+ {
+ name: 'URL',
+ value: 'url'
+ },
+ {
+ name: 'User Agent',
+ value: 'user-agent'
+ },
+ ],
+ description: '',
+ },
+ {
+ displayName: 'Data',
+ name: 'data',
+ type: 'string',
+ displayOptions: {
+ hide: {
+ dataType: [
+ 'file',
+ ],
+ },
+ },
+ default: '',
+ description: '',
+ },
+ {
+ displayName: 'Binary Property',
+ name: 'binaryProperty',
+ type: 'string',
+ displayOptions: {
+ show: {
+ dataType: [
+ 'file',
+ ],
+ },
+ },
+ default: 'data',
+ description: '',
+ },
+ {
+ displayName: 'Message',
+ name: 'message',
+ type: 'string',
+ default: '',
+ description: '',
+ },
+ {
+ displayName: 'Tags',
+ name: 'tags',
+ type: 'string',
+ default: '',
+ description: '',
+ },
+ ],
+ }
+ ]
+ },
+ ]
+ }
+ ],
+ typeOptions:{
+ loadOptionsDependsOn:[
+ 'entityType',
+ ],
+ },
+ displayOptions: {
+ show: {
+ resource: [
+ 'responder',
+ ],
+ jsonObject: [
+ false,
+ ],
+ entityType: [
+ 'alert',
+ ],
+ },
+ hide: {
+ responder: [
+ '',
+ ],
+ entityType: [
+ '',
+ 'case',
+ 'case_artifact',
+ 'case_task',
+ 'case_task_log',
+ ],
+ },
+ },
+ default: {},
+ },
+ {
+ displayName: 'Parameters',
+ name: 'parameters',
+ type: 'fixedCollection',
+ placeholder: 'Add Parameter',
+ required: false,
+ options: [
+ {
+ displayName: 'Observable Attributes',
+ name: 'values',
+ values: [
+ {
+ displayName: 'DataType',
+ name: 'dataType',
+ type: 'options',
+ default: '',
+ options: [
+ {
+ name: 'Domain',
+ value: 'domain',
+ },
+ {
+ name: 'File',
+ value: 'file'
+ },
+ {
+ name: 'Filename',
+ value: 'filename'
+ },
+ {
+ name: 'Fqdn',
+ value: 'fqdn'
+ },
+ {
+ name: 'Hash',
+ value: 'hash'
+ },
+ {
+ name: 'IP',
+ value: 'ip'
+ },
+ {
+ name: 'Mail',
+ value: 'mail'
+ },
+ {
+ name: 'Mail Subject',
+ value: 'mail_subject'
+ },
+ {
+ name: 'Other',
+ value: 'other'
+ },
+ {
+ name: 'Regexp',
+ value: 'regexp'
+ },
+ {
+ name: 'Registry',
+ value: 'registry'
+ },
+ {
+ name: 'Uri Path',
+ value: 'uri_path'
+ },
+ {
+ name: 'URL',
+ value: 'url'
+ },
+ {
+ name: 'User Agent',
+ value: 'user-agent'
+ },
+ ],
+ },
+ {
+ displayName: 'Data',
+ name: 'data',
+ type: 'string',
+ default: '',
+ displayOptions:{
+ hide:{
+ dataType:[
+ 'file',
+ ],
+ },
+ },
+ },
+ {
+ displayName: 'Binary Property',
+ name: 'binaryPropertyName',
+ type: 'string',
+ default: 'data',
+ displayOptions: {
+ show: {
+ dataType:[
+ 'file',
+ ],
+ },
+ },
+ description: 'Name of the binary property which contains the attachement data',
+ },
+ {
+ displayName: 'Message',
+ name: 'message',
+ type: 'string',
+ default: ''
+ },
+ {
+ displayName: 'Start Date',
+ name: 'startDate',
+ type: 'dateTime',
+ default: '',
+ description: 'Date and time of the begin of the case default=now',
+ },
+ {
+ displayName: 'TLP',
+ name: 'tlp',
+ type: 'options',
+ default: 2,
+ options: [
+ {
+ name:'White',
+ value:TLP.white,
+ },
+ {
+ name:'Green',
+ value:TLP.green,
+ },
+ {
+ name:'Amber',
+ value:TLP.amber,
+ },{
+ name:'Red',
+ value:TLP.red,
+ }
+ ],
+ description: 'Traffict Light Protocol (TLP). Default=Amber',
+ },
+ {
+ displayName: 'IOC',
+ name: 'ioc',
+ type: 'boolean',
+ default: false,
+ description: 'Indicates if the observable is an IOC (Indicator of compromise)',
+ },
+ {
+ displayName: 'Status',
+ name: 'status',
+ type: 'options',
+ default: '',
+ options: [
+ {
+ name: 'Ok',
+ value: 'Ok',
+ },
+ {
+ name: 'Deleted',
+ value: 'Deleted',
+ },
+ ],
+ description: 'Status of the observable (Ok or Deleted) default=Ok',
+ }
+ ],
+ },
+ ],
+ typeOptions:{
+ loadOptionsDependsOn:[
+ 'entityType',
+ ],
+ },
+ displayOptions: {
+ show: {
+ resource: [
+ 'responder',
+ ],
+ jsonObject: [
+ false,
+ ],
+ entityType: [
+ 'case_artifact',
+ ],
+ },
+ hide: {
+ responder: [
+ '',
+ ],
+ entityType: [
+ '',
+ 'case',
+ 'alert',
+ 'case_task',
+ 'case_task_log',
+ ],
+ },
+ },
+ default: {},
+ },
+ {
+ displayName: 'Parameters',
+ name: 'parameters',
+ type: 'fixedCollection',
+ placeholder: 'Add Parameter',
+ required: false,
+ options: [
+ {
+ displayName: 'Task Attributes',
+ name: 'values',
+ values: [
+ {
+ displayName: 'Title',
+ name: 'title',
+ type: 'string',
+ required: false,
+ default: '',
+ description: 'Title of the task',
+ },
+ {
+ displayName: 'Status',
+ name: 'status',
+ type: 'options',
+ default: 'Waiting',
+ options: [
+ {
+ name: 'Waiting',
+ value: 'Waiting',
+ },
+ {
+ name: 'InProgress',
+ value: 'InProgress',
+ },
+ {
+ name: 'Completed',
+ value: 'Completed',
+ },
+ {
+ name: 'Cancel',
+ value: 'Cancel',
+ },
+ ],
+ },
+ {
+ displayName: 'Flag',
+ name: 'flag',
+ type: 'boolean',
+ default: false
+ }
+ ]
+ }
+ ],
+ typeOptions:{
+ loadOptionsDependsOn:[
+ 'entityType',
+ ],
+ },
+ displayOptions: {
+ show: {
+ resource: [
+ 'responder',
+ ],
+ jsonObject: [
+ false,
+ ],
+ entityType: [
+ 'case_task',
+ ],
+ },
+ hide: {
+ responder: [
+ '',
+ ],
+ entityType: [
+ '',
+ 'case',
+ 'alert',
+ 'case_artifact',
+ 'case_task_log',
+ ],
+ },
+ },
+ default: {},
+ },
+ {
+ displayName: 'Parameters',
+ name: 'parameters',
+ type: 'fixedCollection',
+ placeholder: 'Add Parameter',
+ required: false,
+ options: [
+ {
+ displayName: 'Log Attributes',
+ name: 'values',
+ values: [
+ {
+ displayName: 'Message',
+ name: 'message',
+ type: 'string',
+ default: ''
+ },
+ {
+ displayName: 'Start Date',
+ name: 'startDate',
+ type: 'dateTime',
+ default: '',
+ description: 'Date and time of the begin of the case default=now',
+ },
+ {
+ displayName: 'Status',
+ name: 'status',
+ type: 'options',
+ required: true,
+ default: '',
+ options: [
+ {
+ name: 'Ok',
+ value: 'Ok',
+ },
+ {
+ name: 'Deleted',
+ value: 'Deleted',
+ },
+ ],
+ },
+ ],
+ },
+ ],
+ typeOptions:{
+ loadOptionsDependsOn:[
+ 'entityType',
+ ],
+ },
+ displayOptions: {
+ show: {
+ resource: [
+ 'responder',
+ ],
+ jsonObject: [
+ false,
+ ],
+ entityType: [
+ 'case_task_log',
+ ],
+ },
+ hide: {
+ responder: [
+ '',
+ ],
+ entityType: [
+ '',
+ 'case',
+ 'alert',
+ 'case_artifact',
+ 'case_task',
+ ],
+ },
+ },
+ default: {},
+ },
+];
\ No newline at end of file
diff --git a/packages/nodes-base/nodes/Cortex/cortex.png b/packages/nodes-base/nodes/Cortex/cortex.png
new file mode 100644
index 0000000000..b750755749
Binary files /dev/null and b/packages/nodes-base/nodes/Cortex/cortex.png differ
diff --git a/packages/nodes-base/nodes/TheHive/GenericFunctions.ts b/packages/nodes-base/nodes/TheHive/GenericFunctions.ts
new file mode 100644
index 0000000000..8c260b0587
--- /dev/null
+++ b/packages/nodes-base/nodes/TheHive/GenericFunctions.ts
@@ -0,0 +1,123 @@
+import {
+ OptionsWithUri,
+} from 'request';
+
+import {
+ IExecuteFunctions,
+ IHookFunctions,
+ ILoadOptionsFunctions,
+} from 'n8n-core';
+
+import {
+ IDataObject,
+} from 'n8n-workflow';
+
+import * as moment from 'moment';
+
+export async function theHiveApiRequest(this: IHookFunctions | IExecuteFunctions | ILoadOptionsFunctions, method: string, resource: string, body: any = {}, query: IDataObject = {}, uri?: string, option: IDataObject = {}): Promise { // tslint:disable-line:no-any
+ const credentials = this.getCredentials('theHiveApi');
+
+ if (credentials === undefined) {
+ throw new Error('No credentials got returned!');
+ }
+
+ const headerWithAuthentication = Object.assign({}, { Authorization: `Bearer ${credentials.ApiKey}` });
+
+ let options: OptionsWithUri = {
+ headers: headerWithAuthentication,
+ method,
+ qs: query,
+ uri: uri || `${credentials.url}/api${resource}`,
+ body,
+ json: true,
+ };
+
+ if (Object.keys(option).length !== 0) {
+ options = Object.assign({},options, option);
+ }
+
+ if (Object.keys(body).length === 0) {
+ delete options.body;
+ }
+
+ if (Object.keys(query).length === 0) {
+ delete options.qs;
+ }
+ try {
+ return await this.helpers.request!(options);
+ } catch (error) {
+ if (error.error ) {
+ const errorMessage = `TheHive error response [${error.statusCode}]: ${error.error.message || error.error.type}`;
+ throw new Error(errorMessage);
+ } else throw error;
+ }
+}
+
+// Helpers functions
+export function mapResource(resource: string): string {
+ switch (resource) {
+ case 'alert':
+ return 'alert';
+ case 'case':
+ return 'case';
+ case 'observable':
+ return 'case_artifact';
+ case 'task':
+ return 'case_task';
+ case 'log':
+ return 'case_task_log';
+ default:
+ return '';
+ }
+}
+
+export function splitTags(tags: string): string[] {
+ return tags.split(',').filter(tag => tag !== ' ' && tag);
+}
+
+export function prepareOptional(optionals: IDataObject): IDataObject {
+ const response: IDataObject = {};
+ for (const key in optionals) {
+ if (optionals[key]!== undefined && optionals[key]!==null && optionals[key]!=='') {
+ if (moment(optionals[key] as string, moment.ISO_8601).isValid()) {
+ response[key] = Date.parse(optionals[key] as string);
+ } else if (key === 'artifacts') {
+ response[key] = JSON.parse(optionals[key] as string);
+ } else if (key === 'tags') {
+ response[key] = splitTags(optionals[key] as string);
+ } else {
+ response[key] = optionals[key];
+ }
+ }
+ }
+ return response;
+}
+
+export function prepareSortQuery(sort: string, body: { query: [IDataObject] }) {
+ if (sort) {
+ const field = sort.substring(1);
+ const value = sort.charAt(0) === '+' ? 'asc' : 'desc';
+ const sortOption: IDataObject = {};
+ sortOption[field] = value;
+ body.query.push(
+ {
+ '_name': 'sort',
+ '_fields': [
+ sortOption,
+ ],
+ },
+ );
+ }
+}
+
+export function prepareRangeQuery(range: string, body: { 'query': Array<{}> }) {
+ if (range && range !== 'all') {
+ body['query'].push(
+ {
+ '_name': 'page',
+ 'from': parseInt(range.split('-')[0], 10),
+ 'to': parseInt(range.split('-')[1], 10)
+ }
+ );
+ }
+}
diff --git a/packages/nodes-base/nodes/TheHive/QueryFunctions.ts b/packages/nodes-base/nodes/TheHive/QueryFunctions.ts
new file mode 100644
index 0000000000..cd5b984b8a
--- /dev/null
+++ b/packages/nodes-base/nodes/TheHive/QueryFunctions.ts
@@ -0,0 +1,82 @@
+// Query types
+export declare type queryIndexSignature = '_field'|'_gt'|'_value'|'_gte'|'_lt'|'_lte'|'_and'|'_or'|'_not'|'_in'|'_contains'|'_id'|'_between'|'_parent'|'_parent'|'_child'|'_type'|'_string'|'_like'|'_wildcard';
+export type IQueryObject = {
+ [key in queryIndexSignature]?: IQueryObject|IQueryObject[]|string|number|object
+};
+
+// Query Functions
+export function Eq(field: string, value: any):IQueryObject{
+ return { '_field': field, '_value': value };
+}
+export function Gt(field: string, value: any):IQueryObject{
+ return { '_gt': { field: value } };
+}
+export function Gte(field: string, value: any):IQueryObject{
+ return { '_gte': { field: value } };
+}
+export function Lt(field: string, value: any):IQueryObject{
+ return { '_lt': { field: value } };
+}
+export function Lte(field: string, value: any):IQueryObject{
+ return { '_lte': { field: value } };
+}
+export function And(...criteria: IQueryObject[]): IQueryObject{
+ return { '_and': criteria };
+}
+export function Or(...criteria: IQueryObject[]): IQueryObject{
+ return { '_or': criteria };
+}
+export function Not(criteria: IQueryObject[]): IQueryObject{
+ return { '_not': criteria };
+}
+export function In(field: string, values: any[]): IQueryObject{
+ return { '_in': { '_field': field, '_values': values } };
+}
+export function Contains(field: string): IQueryObject{
+ return { '_contains': field };
+}
+export function Id(id: string|number): IQueryObject{
+ return {'_id': id };
+}
+export function Between(field:string, from_value: any, to_value: any): IQueryObject{
+ return {'_between': {'_field': field, '_from': from_value, '_to': to_value } };
+}
+export function ParentId(tpe:string, id:string):IQueryObject{
+ return { '_parent': {'_type': tpe, '_id': id } };
+}
+export function Parent(tpe:string, criterion:IQueryObject):IQueryObject{
+ return { '_parent': {'_type': tpe, '_query': criterion } };
+}
+export function Child(tpe:string, criterion:IQueryObject):IQueryObject{
+ return { '_child': {'_type': tpe, '_query': criterion } };
+}
+export function Type(tpe:string):IQueryObject{
+ return { '_type': tpe };
+}
+export function queryString(query_string:string):IQueryObject{
+ return { '_string': query_string };
+}
+export function Like(field:string, value:string):IQueryObject{
+ return { '_like': { '_field': field, '_value': value } };
+}
+export function StartsWith(field:string, value:string){
+ if (!value.startsWith('*')){
+ value = value + '*';
+ }
+ return { '_wildcard': { '_field': field, '_value': value } };
+}
+export function EndsWith(field:string, value:string){
+ if (!value.endsWith('*')){
+ value = '*' + value;
+ }
+ return { '_wildcard': { '_field': field, '_value': value } };
+}
+export function ContainsString(field:string, value:string){
+ if (!value.endsWith('*')){
+ value = value + '*';
+ }
+ if (!value.startsWith('*')){
+ value = '*' + value;
+ }
+ return { '_wildcard': { '_field': field, '_value': value } };
+}
diff --git a/packages/nodes-base/nodes/TheHive/TheHive.node.ts b/packages/nodes-base/nodes/TheHive/TheHive.node.ts
new file mode 100644
index 0000000000..2e25e23811
--- /dev/null
+++ b/packages/nodes-base/nodes/TheHive/TheHive.node.ts
@@ -0,0 +1,1971 @@
+import {
+ IExecuteFunctions,
+ BINARY_ENCODING
+} from 'n8n-core';
+
+import {
+ INodeExecutionData,
+ INodeType,
+ INodeTypeDescription,
+ IDataObject,
+ INodeParameters,
+ ILoadOptionsFunctions,
+ INodePropertyOptions,
+ IBinaryData
+} from 'n8n-workflow';
+
+import {
+ alertOperations,
+ alertFields,
+} from './descriptions/AlertDescription';
+
+import {
+ observableOperations,
+ observableFields,
+} from './descriptions/ObservableDescription';
+
+import {
+ caseOperations,
+ caseFields,
+} from './descriptions/CaseDescription';
+
+import {
+ taskOperations,
+ taskFields,
+} from './descriptions/TaskDescription';
+
+import {
+ logOperations,
+ logFields,
+} from './descriptions/LogDescription';
+
+import {
+ Buffer,
+} from 'buffer';
+
+import {
+ IQueryObject,
+ Parent,
+ Id,
+ Eq,
+ And,
+ Between,
+ In,
+ ContainsString,
+} from './QueryFunctions';
+
+import {
+ theHiveApiRequest,
+ mapResource,
+ splitTags,
+ prepareOptional,
+ prepareSortQuery,
+ prepareRangeQuery,
+} from './GenericFunctions';
+
+export class TheHive implements INodeType {
+ description: INodeTypeDescription = {
+ displayName: 'TheHive',
+ name: 'theHive',
+ icon: 'file:thehive.png',
+ group: ['transform'],
+ subtitle: '={{$parameter["operation"]}} : {{$parameter["resource"]}}',
+ version: 1,
+ description: 'Consume TheHive APIs',
+ defaults: {
+ name: 'TheHive',
+ color: '#f3d02f',
+ },
+ inputs: ['main'],
+ outputs: ['main'],
+ credentials: [
+ {
+ name: 'theHiveApi',
+ required: true,
+ },
+ ],
+ properties: [
+ {
+ displayName: 'Resource',
+ name: 'resource',
+ type: 'options',
+ required: true,
+ options: [
+ {
+ name: 'Alert',
+ value: 'alert',
+ },
+ {
+ name: 'Case',
+ value: 'case',
+ },
+ {
+ name: 'Log',
+ value: 'log',
+ },
+ {
+ name: 'Observable',
+ value: 'observable',
+ },
+ {
+ name: 'Task',
+ value: 'task',
+ },
+ ],
+ default: 'alert',
+ },
+ // Alert
+ ...alertOperations,
+ ...alertFields,
+ // Observable
+ ...observableOperations,
+ ...observableFields,
+ // Case
+ ...caseOperations,
+ ...caseFields,
+ // Task
+ ...taskOperations,
+ ...taskFields,
+ // Log
+ ...logOperations,
+ ...logFields,
+ ],
+ };
+ methods = {
+ loadOptions: {
+ async loadResponders(this: ILoadOptionsFunctions): Promise {
+ // request the analyzers from instance
+ const resource = mapResource(this.getNodeParameter('resource') as string);
+ const resourceId = this.getNodeParameter('id');
+ const endpoint = `/connector/cortex/responder/${resource}/${resourceId}`;
+
+ const responders = await theHiveApiRequest.call(
+ this,
+ 'GET',
+ endpoint as string,
+ );
+
+ const returnData: INodePropertyOptions[] = [];
+
+ for (const responder of responders) {
+ returnData.push({
+ name: responder.name as string,
+ value: responder.id,
+ description: responder.description as string,
+ });
+ }
+ return returnData;
+ },
+
+ async loadAnalyzers(this: ILoadOptionsFunctions): Promise {
+ // request the analyzers from instance
+ const dataType = this.getNodeParameter('dataType') as string;
+ const endpoint = `/connector/cortex/analyzer/type/${dataType}`;
+ const requestResult = await theHiveApiRequest.call(
+ this,
+ 'GET',
+ endpoint as string
+ );
+ const returnData: INodePropertyOptions[] = [];
+
+ for (const analyzer of requestResult) {
+ for (const cortexId of analyzer.cortexIds) {
+ returnData.push({
+ name: `[${cortexId}] ${analyzer.name}`,
+ value: `${analyzer.id as string}::${cortexId as string}`,
+ description: analyzer.description as string,
+ });
+ }
+ }
+ return returnData;
+ },
+ async loadObservableOptions(this: ILoadOptionsFunctions): Promise {
+ // if v1 is not used we remove 'count' option
+ const version = this.getCredentials('theHiveApi')?.apiVersion;
+
+ const options= [
+ ...(version==='v1')?[{name:'Count',value:'count',description:'Count observables'}]:[],
+ {name:'Create',value:'create',description:'Create observable'},
+ {name:'Execute Analyzer',value:'executeAnalyzer',description:'Execute an responder on selected observable'},
+ {name:'Execute Responder',value:'executeResponder',description:'Execute a responder on selected observable'},
+ {name:'Get All',value:'getAll',description:'Get all observables of a specific case'},
+ {name:'Get', value: 'get', description: 'Get a single observable' },
+ {name:'Search',value:'search',description:'Search observables'},
+ {name:'Update',value:'update',description:'Update observable'},
+ ];
+ return options;
+ },
+ async loadTaskOptions(this:ILoadOptionsFunctions): Promise{
+ const version = this.getCredentials('theHiveApi')?.apiVersion;
+ const options =[
+ ...(version==='v1')?[{name:'Count',value:'count',description:'Count tasks'}]:[],
+ {name:'Create',value:'create',description:'Create a task'},
+ {name:'Execute Responder', value: 'executeResponder', description: 'Execute a responder on the specified task' },
+ {name:'Get All',value:'getAll',description:'Get all asks of a specific case'},
+ {name:'Get', value: 'get', description: 'Get a single task' },
+ {name:'Search',value:'search',description:'Search tasks'},
+ {name:'Update',value:'update',description:'Update a task'},
+ ];
+ return options;
+ },
+ async loadAlertOptions(this:ILoadOptionsFunctions):Promise{
+ const version = this.getCredentials('theHiveApi')?.apiVersion;
+ const options = [
+ ...(version ==='v1')?[{ name: 'Count', value: 'count', description: 'Count alerts' }]:[],
+ { name: 'Create', value: 'create', description: 'Create alert' },
+ { name: 'Execute Responder', value: 'executeResponder', description: 'Execute a responder on the specified alert' },
+ { name: 'Get', value: 'get', description: 'Get an alert' },
+ { name: 'Get All', value: 'getAll', description: 'Get all alerts' },
+ { name: 'Merge', value: 'merge', description: 'Merge alert into an existing case' },
+ { name: 'Promote', value: 'promote', description: 'Promote an alert into a case' },
+ { name: 'Update', value: 'update', description: 'Update alert' },
+ ];
+ return options;
+ },
+ async loadCaseOptions(this:ILoadOptionsFunctions):Promise{
+ const version = this.getCredentials('theHiveApi')?.apiVersion;
+ const options=[
+ ...(version ==='v1')?[{ name: 'Count', value: 'count', description: 'Count a case' }]:[],
+ { name: 'Create', value: 'create', description: 'Create a case' },
+ { name: 'Execute Responder', value: 'executeResponder', description: 'Execute a responder on the specified case' },
+ { name: 'Get All', value: 'getAll', description: 'Get all cases' },
+ { name: 'Get', value: 'get', description: 'Get a single case' },
+ { name: 'Update', value: 'update', description: 'Update a case' },
+ ];
+ return options;
+ }
+ }
+ };
+
+ async execute(this: IExecuteFunctions): Promise {
+ const items = this.getInputData();
+ const returnData: IDataObject[] = [];
+ const length = (items.length as unknown) as number;
+ const qs: IDataObject = {};
+ let responseData;
+ const resource = this.getNodeParameter('resource', 0) as string;
+ const operation = this.getNodeParameter('operation', 0) as string;
+
+ for (let i = 0; i < length; i++) {
+ if (resource === 'alert') {
+ if (operation === 'count') {
+ const countQueryAttributs: any = prepareOptional(this.getNodeParameter('filters', i, {}) as INodeParameters);
+
+ const _countSearchQuery: IQueryObject = And();
+
+ for (const key of Object.keys(countQueryAttributs)) {
+ if ( key === 'tags') {
+ (_countSearchQuery['_and'] as IQueryObject[]).push(
+ In(key, countQueryAttributs[key] as string[])
+ );
+ } else if (key === 'description' || key === 'title' ) {
+ (_countSearchQuery['_and'] as IQueryObject[]).push(
+ ContainsString(key, countQueryAttributs[key] as string)
+ );
+ } else {
+ (_countSearchQuery['_and'] as IQueryObject[]).push(
+ Eq(key, countQueryAttributs[key] as string)
+ );
+ }
+ }
+
+ const body = {
+ 'query': [
+ {
+ '_name': 'listAlert',
+ },
+ {
+ '_name': 'filter',
+ '_and': _countSearchQuery['_and']
+ },
+ ]
+ };
+
+ body['query'].push(
+ {
+ '_name': 'count',
+ }
+ );
+
+ qs.name = 'count-Alert';
+
+
+ responseData = await theHiveApiRequest.call(
+ this,
+ 'POST',
+ '/v1/query',
+ body,
+ qs,
+ );
+
+ responseData = { count: responseData };
+
+ }
+
+ if (operation === 'create') {
+ const body: IDataObject = {
+ title: this.getNodeParameter('title', i),
+ description: this.getNodeParameter('description', i),
+ severity: this.getNodeParameter('severity', i),
+ date: Date.parse(this.getNodeParameter('date', i) as string),
+ tags: splitTags(this.getNodeParameter('tags', i) as string),
+ tlp: this.getNodeParameter('tlp', i),
+ status: this.getNodeParameter('status', i),
+ type: this.getNodeParameter('type', i),
+ source: this.getNodeParameter('source', i),
+ sourceRef: this.getNodeParameter('sourceRef', i),
+ follow: this.getNodeParameter('follow', i, true),
+ ...prepareOptional(this.getNodeParameter('optionals', i, {}) as INodeParameters)
+ };
+
+ const artifactUi = this.getNodeParameter('artifactUi', i)as IDataObject;
+
+ if (artifactUi) {
+
+ const artifactValues = (artifactUi as IDataObject).artifactValues as IDataObject[];
+
+ if (artifactValues) {
+
+ const artifactData = [];
+
+ for (const artifactvalue of artifactValues) {
+
+ const element: IDataObject = {};
+
+ element.message = artifactvalue.message as string;
+
+ element.tags = (artifactvalue.tags as string).split(',') as string[];
+
+ element.dataType = artifactvalue.dataType as string;
+
+ element.data = artifactvalue.data as string;
+
+ if (artifactvalue.dataType === 'file') {
+
+ const item = items[i];
+
+ if (item.binary === undefined) {
+ throw new Error('No binary data exists on item!');
+ }
+
+ const binaryPropertyName = artifactvalue.binaryProperty as string;
+
+ if (item.binary[binaryPropertyName] === undefined) {
+ throw new Error(`No binary data property '${binaryPropertyName}' does not exists on item!`);
+ }
+
+ const binaryData = item.binary[binaryPropertyName] as IBinaryData;
+
+ element.data = `${binaryData.fileName};${binaryData.mimeType};${binaryData.data}`;
+ }
+
+ artifactData.push(element);
+ }
+ body.artifacts = artifactData;
+ }
+ }
+
+ responseData = await theHiveApiRequest.call(
+ this,
+ 'POST',
+ '/alert' as string,
+ body,
+ );
+ }
+
+ /*
+ Execute responder feature differs from Cortex execute responder
+ if it doesn't interfere with n8n standards then we should keep it
+ */
+
+ if (operation === 'executeResponder'){
+ const alertId = this.getNodeParameter('id', i);
+ const responderId = this.getNodeParameter('responder', i) as string;
+ let body:IDataObject;
+ let response;
+ responseData = [];
+ body = {
+ responderId,
+ objectId:alertId,
+ objectType: 'alert'
+ };
+ response = await theHiveApiRequest.call(
+ this,
+ 'POST',
+ '/connector/cortex/action' as string,
+ body,
+ );
+ body = {
+ query: [
+ {
+ '_name': 'listAction'
+ },
+ {
+ '_name': 'filter',
+ '_and': [
+ {
+ '_field': 'cortexId',
+ '_value': response.cortexId
+ },
+ {
+ '_field': 'objectId',
+ '_value': response.objectId
+ },
+ {
+ '_field': 'startDate',
+ '_value': response.startDate
+ }
+
+ ]
+ }
+ ],
+ };
+ qs.name = 'log-actions';
+ do {
+ response = await theHiveApiRequest.call(
+ this,
+ 'POST',
+ `/v1/query`,
+ body,
+ qs
+ );
+ } while (response.status === 'Waiting' || response.status === 'InProgress' );
+
+ responseData = response;
+ }
+
+ if (operation === 'get') {
+ const alertId = this.getNodeParameter('id', i) as string;
+
+ responseData = await theHiveApiRequest.call(
+ this,
+ 'GET',
+ `/alert/${alertId}`,
+ {},
+ );
+ }
+
+ if (operation === 'getAll') {
+ const credentials = this.getCredentials('theHiveApi') as IDataObject;
+
+ const returnAll = this.getNodeParameter('returnAll', i) as boolean;
+
+ const version = credentials.apiVersion;
+
+ const queryAttributs: any = prepareOptional(this.getNodeParameter('filters', i, {}) as INodeParameters);
+
+ const options = this.getNodeParameter('options', i) as IDataObject;
+
+ const _searchQuery: IQueryObject = And();
+
+ for (const key of Object.keys(queryAttributs)) {
+ if ( key === 'tags') {
+ (_searchQuery['_and'] as IQueryObject[]).push(
+ In(key, queryAttributs[key] as string[])
+ );
+ } else if (key === 'description' || key === 'title' ) {
+ (_searchQuery['_and'] as IQueryObject[]).push(
+ ContainsString(key, queryAttributs[key] as string)
+ );
+ } else {
+ (_searchQuery['_and'] as IQueryObject[]).push(
+ Eq(key, queryAttributs[key] as string)
+ );
+ }
+ }
+
+ let endpoint;
+
+ let method;
+
+ let body: IDataObject = {};
+
+ let limit = undefined;
+
+ if (returnAll === false) {
+ limit = this.getNodeParameter('limit', i) as number;
+ }
+
+ if (version === 'v1') {
+ endpoint = '/v1/query';
+
+ method = 'POST';
+
+ body = {
+ 'query': [
+ {
+ '_name': 'listAlert',
+ },
+ {
+ '_name': 'filter',
+ '_and': _searchQuery['_and']
+ },
+ ],
+ };
+
+ //@ts-ignore
+ prepareSortQuery(options.sort, body);
+
+ if (limit !== undefined) {
+ //@ts-ignore
+ prepareRangeQuery(`0-${limit}`, body);
+ }
+
+ qs.name = 'alerts';
+
+ } else {
+ method = 'POST';
+
+ endpoint = '/alert/_search';
+
+ if (limit !== undefined) {
+ qs.range = `0-${limit}`;
+ }
+
+ body.query = _searchQuery;
+
+ Object.assign(qs, prepareOptional(options));
+
+ }
+
+ responseData = await theHiveApiRequest.call(
+ this,
+ method,
+ endpoint as string,
+ body,
+ qs,
+ );
+ }
+
+ if (operation === 'merge') {
+ const alertId = this.getNodeParameter('id', i) as string;
+
+ const caseId = this.getNodeParameter('caseId', i) as string;
+
+ responseData = await theHiveApiRequest.call(
+ this,
+ 'POST',
+ `/alert/${alertId}/merge/${caseId}`,
+ {},
+ );
+ }
+
+ if (operation === 'promote') {
+ const alertId = this.getNodeParameter('id', i) as string;
+
+ const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject;
+
+ const body: IDataObject = {};
+
+ Object.assign(body, additionalFields);
+
+ responseData = await theHiveApiRequest.call(
+ this,
+ 'POST',
+ `/alert/${alertId}/createCase`,
+ body,
+ );
+ }
+
+ if (operation === 'update') {
+ const alertId = this.getNodeParameter('id', i) as string;
+
+ const updateFields = this.getNodeParameter('updateFields', i) as IDataObject;
+
+ const artifactUi = updateFields.artifactUi as IDataObject;
+
+ delete updateFields.artifactUi;
+
+ const body: IDataObject = {};
+
+ Object.assign(body, updateFields);
+
+ if (artifactUi) {
+ const artifactValues = (artifactUi as IDataObject).artifactValues as IDataObject[];
+
+ if (artifactValues) {
+ const artifactData = [];
+
+ for (const artifactvalue of artifactValues) {
+
+ const element: IDataObject = {};
+
+ element.message = artifactvalue.message as string;
+
+ element.tags = (artifactvalue.tags as string).split(',') as string[];
+
+ element.dataType = artifactvalue.dataType as string;
+
+ element.data = artifactvalue.data as string;
+
+ if (artifactvalue.dataType === 'file') {
+ const item = items[i];
+
+ if (item.binary === undefined) {
+ throw new Error('No binary data exists on item!');
+ }
+
+ const binaryPropertyName = artifactvalue.binaryProperty as string;
+
+ if (item.binary[binaryPropertyName] === undefined) {
+ throw new Error(`No binary data property '${binaryPropertyName}' does not exists on item!`);
+ }
+
+ const binaryData = item.binary[binaryPropertyName] as IBinaryData;
+
+ element.data = `${binaryData.fileName};${binaryData.mimeType};${binaryData.data}`;
+ }
+
+ artifactData.push(element);
+ }
+ body.artifacts = artifactData;
+ }
+ }
+
+ responseData = await theHiveApiRequest.call(
+ this,
+ 'PATCH',
+ `/alert/${alertId}` as string,
+ body,
+ );
+ }
+ }
+
+ if(resource === 'observable'){
+ if(operation === 'count'){
+ const countQueryAttributs: any = prepareOptional(this.getNodeParameter('filters', i, {}) as INodeParameters);
+
+ const _countSearchQuery: IQueryObject = And();
+
+ for (const key of Object.keys(countQueryAttributs)) {
+ if (key === 'dataType' || key === 'tags') {
+ (_countSearchQuery['_and'] as IQueryObject[]).push(
+ In(key, countQueryAttributs[key] as string[])
+ );
+ } else if (key === 'description' || key === 'keywork' || key === 'message') {
+ (_countSearchQuery['_and'] as IQueryObject[]).push(
+ ContainsString(key, countQueryAttributs[key] as string)
+ );
+ } else if (key === 'range') {
+ (_countSearchQuery['_and'] as IQueryObject[]).push(
+ Between(
+ 'startDate',
+ countQueryAttributs['range']['dateRange']['fromDate'],
+ countQueryAttributs['range']['dateRange']['toDate']
+ )
+ );
+ } else {
+ (_countSearchQuery['_and'] as IQueryObject[]).push(
+ Eq(key, countQueryAttributs[key] as string)
+ );
+ }
+ }
+
+ const body = {
+ 'query': [
+ {
+ '_name': 'listObservable'
+ },
+ {
+ '_name': 'filter',
+ '_and': _countSearchQuery['_and']
+ },
+ ]
+ };
+
+ body['query'].push(
+ {
+ '_name': 'count'
+ }
+ );
+
+ qs.name = 'count-observables';
+
+ responseData = await theHiveApiRequest.call(
+ this,
+ 'POST',
+ '/v1/query',
+ body,
+ qs,
+ );
+
+ responseData = { count: responseData };
+ }
+
+ if (operation === 'executeAnalyzer'){
+ const observableId = this.getNodeParameter('id', i);
+ const analyzers = (this.getNodeParameter('analyzers', i) as string[])
+ .map(analyzer => {
+ const parts = analyzer.split('::');
+ return {
+ analyzerId: parts[0],
+ cortexId: parts[1]
+ };
+ });
+ let response: any;
+ let body: IDataObject;
+ responseData = [];
+ for (const analyzer of analyzers) {
+ body = {
+ ...analyzer,
+ artifactId:observableId,
+ };
+ // execute the analyzer
+ response = await theHiveApiRequest.call(
+ this,
+ 'POST',
+ '/connector/cortex/job' as string,
+ body,
+ qs
+ );
+ const jobId = response.id;
+ qs.name = 'observable-jobs';
+ // query the job result (including the report)
+ do {
+ responseData = await theHiveApiRequest.call(this,'GET',`/connector/cortex/job/${jobId}`,body,qs);
+ } while (responseData.status === 'Waiting' || responseData.status === 'InProgress' );
+ }
+
+ }
+
+ if (operation === 'executeResponder'){
+ const observableId = this.getNodeParameter('id', i);
+ const responderId = this.getNodeParameter('responder', i) as string;
+ let body: IDataObject;
+ let response;
+ responseData = [];
+ body = {
+ responderId,
+ objectId:observableId,
+ objectType: 'case_artifact'
+ };
+ response = await theHiveApiRequest.call(
+ this,
+ 'POST',
+ '/connector/cortex/action' as string,
+ body,
+ );
+ body = {
+ query: [
+ {
+ '_name': 'listAction'
+ },
+ {
+ '_name': 'filter',
+ '_and': [
+ {
+ '_field': 'cortexId',
+ '_value': response.cortexId
+ },
+ {
+ '_field': 'objectId',
+ '_value': response.objectId
+ },
+ {
+ '_field': 'startDate',
+ '_value': response.startDate
+ }
+
+ ]
+ }
+ ]
+ };
+ qs.name = 'log-actions';
+ do {
+ response = await theHiveApiRequest.call(
+ this,
+ 'POST',
+ `/v1/query`,
+ body,
+ qs
+ );
+ } while (response.status === 'Waiting' || response.status === 'InProgress' );
+
+ responseData = response;
+ }
+
+ if(operation === 'create'){
+ const caseId = this.getNodeParameter('caseId', i);
+
+ let body: IDataObject = {
+ dataType: this.getNodeParameter('dataType', i) as string,
+ message: this.getNodeParameter('message', i) as string,
+ startDate: Date.parse(this.getNodeParameter('startDate', i) as string),
+ tlp: this.getNodeParameter('tlp', i) as number,
+ ioc: this.getNodeParameter('ioc', i) as boolean,
+ sighted: this.getNodeParameter('sighted', i) as boolean,
+ status: this.getNodeParameter('status', i) as string,
+ ...prepareOptional(this.getNodeParameter('options', i, {}) as INodeParameters)
+ };
+
+ let options: IDataObject = {};
+
+ if (body.dataType === 'file') {
+ const item = items[i];
+
+ if (item.binary === undefined) {
+ throw new Error('No binary data exists on item!');
+ }
+
+ const binaryPropertyName = this.getNodeParameter('binaryProperty', i) as string;
+
+ if (item.binary[binaryPropertyName] === undefined) {
+ throw new Error(`No binary data property '${binaryPropertyName}' does not exists on item!`);
+ }
+
+ const binaryData = item.binary[binaryPropertyName] as IBinaryData;
+
+ options = {
+ formData: {
+ attachment: {
+ value: Buffer.from(binaryData.data, BINARY_ENCODING),
+ options: {
+ contentType: binaryData.mimeType,
+ filename: binaryData.fileName,
+ }
+ },
+ _json: JSON.stringify(body)
+ }
+ };
+ body = {};
+ }else{
+ body.data = this.getNodeParameter('data', i) as string;
+ }
+
+ responseData = await theHiveApiRequest.call(
+ this,
+ 'POST',
+ `/case/${caseId}/artifact` as string,
+ body,
+ qs,
+ '',
+ options
+ );
+ }
+
+ if(operation === 'get'){
+ const observableId = this.getNodeParameter('id', i) as string;
+
+ const credentials = this.getCredentials('theHiveApi') as IDataObject;
+
+ const version = credentials.apiVersion;
+
+ let endpoint;
+
+ let method;
+
+ let body: IDataObject = {};
+
+ if (version === 'v1') {
+
+ endpoint = '/v1/query';
+
+ method = 'POST';
+
+ body = {
+ 'query': [
+ {
+ '_name': 'getObservable',
+ 'idOrName': observableId
+ }
+ ]
+ };
+
+ qs.name = `get-observable-${observableId}`;
+
+ } else {
+
+ method = 'GET';
+
+ endpoint = `/case/artifact/${observableId}`;
+
+ }
+
+ responseData = await theHiveApiRequest.call(
+ this,
+ method,
+ endpoint as string,
+ body,
+ qs,
+ );
+ }
+
+ if(operation === 'getAll'){
+ const credentials = this.getCredentials('theHiveApi') as IDataObject;
+
+ const returnAll = this.getNodeParameter('returnAll', i) as boolean;
+
+ const version = credentials.apiVersion;
+
+ const options = this.getNodeParameter('options', i) as IDataObject;
+
+ const caseId = this.getNodeParameter('caseId', i);
+
+ let endpoint;
+
+ let method;
+
+ let body: IDataObject = {};
+
+ let limit = undefined;
+
+ if (returnAll === false) {
+ limit = this.getNodeParameter('limit', i) as number;
+ }
+
+ if (version === 'v1') {
+
+
+ endpoint = '/v1/query';
+
+ method = 'POST';
+
+ body = {
+ 'query': [
+ {
+ '_name': 'getCase',
+ 'idOrName': caseId
+ },
+ {
+ '_name': 'observables'
+ },
+ ]
+ };
+
+ //@ts-ignore
+ prepareSortQuery(options.sort, body);
+
+ if (limit !== undefined) {
+ //@ts-ignore
+ prepareRangeQuery(`0-${limit}`, body);
+ }
+
+ qs.name = 'observables';
+
+ } else {
+
+ method = 'POST';
+
+ endpoint = '/case/artifact/_search';
+
+ if (limit !== undefined) {
+ qs.range = `0-${limit}`;
+ }
+
+ body.query = Parent('case', Id(caseId as string));
+
+ Object.assign(qs, prepareOptional(options));
+ }
+
+ responseData = await theHiveApiRequest.call(
+ this,
+ method,
+ endpoint as string,
+ body,
+ qs,
+ );
+ }
+
+ if(operation === 'search'){
+ const credentials = this.getCredentials('theHiveApi') as IDataObject;
+
+ const returnAll = this.getNodeParameter('returnAll', i) as boolean;
+
+ const version = credentials.apiVersion;
+
+ const queryAttributs: any = prepareOptional(this.getNodeParameter('filters', i, {}) as INodeParameters);
+
+ const _searchQuery: IQueryObject = And();
+
+ const options = this.getNodeParameter('options', i) as IDataObject;
+
+ for (const key of Object.keys(queryAttributs)) {
+ if (key === 'dataType' || key === 'tags') {
+ (_searchQuery['_and'] as IQueryObject[]).push(
+ In(key, queryAttributs[key] as string[])
+ );
+ } else if (key === 'description' || key === 'keywork' || key === 'message') {
+ (_searchQuery['_and'] as IQueryObject[]).push(
+ ContainsString(key, queryAttributs[key] as string)
+ );
+ } else if (key === 'range') {
+ (_searchQuery['_and'] as IQueryObject[]).push(
+ Between(
+ 'startDate',
+ queryAttributs['range']['dateRange']['fromDate'],
+ queryAttributs['range']['dateRange']['toDate']
+ )
+ );
+ } else {
+ (_searchQuery['_and'] as IQueryObject[]).push(
+ Eq(key, queryAttributs[key] as string)
+ );
+ }
+ }
+
+ let endpoint;
+
+ let method;
+
+ let body: IDataObject = {};
+
+ let limit = undefined;
+
+ if (returnAll === false) {
+ limit = this.getNodeParameter('limit', i) as number;
+ }
+
+ if (version === 'v1') {
+
+ endpoint = '/v1/query';
+
+ method = 'POST';
+
+ body = {
+ 'query': [
+ {
+ '_name': 'listObservable'
+ },
+ {
+ '_name': 'filter',
+ '_and': _searchQuery['_and']
+ },
+ ]
+ };
+
+ //@ts-ignore
+ prepareSortQuery(options.sort, body);
+
+ if (limit !== undefined) {
+ //@ts-ignore
+ prepareRangeQuery(`0-${limit}`, body);
+ }
+
+ qs.name = 'observables';
+
+ } else {
+
+ method = 'POST';
+
+ endpoint = '/case/artifact/_search';
+
+ if (limit !== undefined) {
+ qs.range = `0-${limit}`;
+ }
+
+ body.query = _searchQuery;
+
+ Object.assign(qs, prepareOptional(options));
+
+ }
+
+ responseData = await theHiveApiRequest.call(
+ this,
+ method,
+ endpoint as string,
+ body,
+ qs,
+ );
+ }
+
+ if(operation === 'update'){
+ const id = this.getNodeParameter('id', i) as string;
+
+ const body: IDataObject = {
+ ...prepareOptional(this.getNodeParameter('updateFields', i, {}) as INodeParameters)
+ };
+
+ responseData = await theHiveApiRequest.call(
+ this,
+ 'PATCH',
+ `/case/artifact/${id}` as string,
+ body,
+ qs,
+ );
+
+ responseData = { success: true };
+ }
+ }
+
+ if (resource === 'case'){
+ if(operation === 'count'){
+ const countQueryAttributs: any = prepareOptional(this.getNodeParameter('filters', i, {}) as INodeParameters);
+
+ const _countSearchQuery: IQueryObject = And();
+
+ for (const key of Object.keys(countQueryAttributs)) {
+ if ( key === 'tags') {
+ (_countSearchQuery['_and'] as IQueryObject[]).push(
+ In(key, countQueryAttributs[key] as string[])
+ );
+ } else if (key === 'description' || key === 'summary' || key === 'title') {
+ (_countSearchQuery['_and'] as IQueryObject[]).push(
+ ContainsString(key, countQueryAttributs[key] as string)
+ );
+ } else {
+ (_countSearchQuery['_and'] as IQueryObject[]).push(
+ Eq(key, countQueryAttributs[key] as string)
+ );
+ }
+ }
+
+ const body = {
+ 'query': [
+ {
+ '_name': 'listCase',
+ },
+ {
+ '_name': 'filter',
+ '_and': _countSearchQuery['_and']
+ },
+ ]
+ };
+
+ body['query'].push(
+ {
+ '_name': 'count',
+ }
+ );
+
+ qs.name = 'count-cases';
+
+
+ responseData = await theHiveApiRequest.call(
+ this,
+ 'POST',
+ '/v1/query',
+ body,
+ qs,
+ );
+
+ responseData = { count: responseData };
+ }
+
+ if (operation === 'executeResponder'){
+ const caseId = this.getNodeParameter('id', i);
+ const responderId = this.getNodeParameter('responder', i) as string;
+ let body: IDataObject;
+ let response;
+ responseData = [];
+ body = {
+ responderId,
+ objectId:caseId,
+ objectType: 'case'
+ };
+ response = await theHiveApiRequest.call(
+ this,
+ 'POST',
+ '/connector/cortex/action' as string,
+ body,
+ );
+ body = {
+ query: [
+ {
+ '_name': 'listAction'
+ },
+ {
+ '_name': 'filter',
+ '_and': [
+ {
+ '_field': 'cortexId',
+ '_value': response.cortexId
+ },
+ {
+ '_field': 'objectId',
+ '_value': response.objectId
+ },
+ {
+ '_field': 'startDate',
+ '_value': response.startDate
+ }
+
+ ]
+ }
+ ]
+ };
+ qs.name = 'log-actions';
+ do {
+ response = await theHiveApiRequest.call(
+ this,
+ 'POST',
+ `/v1/query`,
+ body,
+ qs
+ );
+ } while (response.status === 'Waiting' || response.status === 'InProgress' );
+
+ responseData = response;
+ }
+
+ if(operation === 'create'){
+
+ const body: IDataObject = {
+ title: this.getNodeParameter('title', i),
+ description: this.getNodeParameter('description', i),
+ severity: this.getNodeParameter('severity', i),
+ startDate: Date.parse(this.getNodeParameter('startDate', i) as string),
+ owner: this.getNodeParameter('owner', i),
+ flag: this.getNodeParameter('flag', i),
+ tlp: this.getNodeParameter('tlp', i),
+ tags: splitTags(this.getNodeParameter('tags', i) as string),
+ ...prepareOptional(this.getNodeParameter('options', i, {}) as INodeParameters)
+ };
+
+ responseData = await theHiveApiRequest.call(
+ this,
+ 'POST',
+ '/case' as string,
+ body,
+ );
+ }
+
+ if(operation === 'get'){
+ const caseId = this.getNodeParameter('id', i) as string;
+
+ const credentials = this.getCredentials('theHiveApi') as IDataObject;
+
+ const version = credentials.apiVersion;
+
+ let endpoint;
+
+ let method;
+
+ let body: IDataObject = {};
+
+ if (version === 'v1') {
+
+ endpoint = '/v1/query';
+
+ method = 'POST';
+
+ body = {
+ 'query': [
+ {
+ '_name': 'getCase',
+ 'idOrName': caseId
+ }
+ ]
+ };
+
+ qs.name = `get-case-${caseId}`;
+
+ } else {
+
+ method = 'GET';
+
+ endpoint = `/case/${caseId}`;
+
+ }
+
+ responseData = await theHiveApiRequest.call(
+ this,
+ method,
+ endpoint as string,
+ body,
+ qs,
+ );
+ }
+
+ if(operation === 'getAll'){
+ const credentials = this.getCredentials('theHiveApi') as IDataObject;
+
+ const returnAll = this.getNodeParameter('returnAll', i) as boolean;
+
+ const version = credentials.apiVersion;
+
+ const queryAttributs: any = prepareOptional(this.getNodeParameter('filters', i, {}) as INodeParameters);
+
+ const _searchQuery: IQueryObject = And();
+
+ const options = this.getNodeParameter('options', i) as IDataObject;
+
+ for (const key of Object.keys(queryAttributs)) {
+ if ( key === 'tags') {
+ (_searchQuery['_and'] as IQueryObject[]).push(
+ In(key, queryAttributs[key] as string[])
+ );
+ } else if (key === 'description' || key === 'summary' || key === 'title') {
+ (_searchQuery['_and'] as IQueryObject[]).push(
+ ContainsString(key, queryAttributs[key] as string)
+ );
+ } else {
+ (_searchQuery['_and'] as IQueryObject[]).push(
+ Eq(key, queryAttributs[key] as string)
+ );
+ }
+ }
+
+ let endpoint;
+
+ let method;
+
+ let body: IDataObject = {};
+
+ let limit = undefined;
+
+ if (returnAll === false) {
+ limit = this.getNodeParameter('limit', i) as number;
+ }
+
+ if (version === 'v1') {
+ endpoint = '/v1/query';
+
+ method = 'POST';
+
+ body = {
+ 'query': [
+ {
+ '_name': 'listCase'
+ },
+ {
+ '_name': 'filter',
+ '_and': _searchQuery['_and']
+ },
+ ]
+ };
+
+ //@ts-ignore
+ prepareSortQuery(options.sort, body);
+
+ if (limit !== undefined) {
+ //@ts-ignore
+ prepareRangeQuery(`0-${limit}`, body);
+ }
+
+ qs.name = 'cases';
+
+ } else {
+ method = 'POST';
+
+ endpoint = '/case/_search';
+
+ if (limit !== undefined) {
+ qs.range = `0-${limit}`;
+ }
+
+ body.query = _searchQuery;
+
+ Object.assign(qs, prepareOptional(options));
+ }
+
+ responseData = await theHiveApiRequest.call(
+ this,
+ method,
+ endpoint as string,
+ body,
+ qs,
+ );
+ }
+
+ if(operation === 'update'){
+ const id = this.getNodeParameter('id', i) as string;
+
+ const body: IDataObject = {
+ ...prepareOptional(this.getNodeParameter('updateFields', i, {}) as INodeParameters)
+ };
+
+ responseData = await theHiveApiRequest.call(
+ this,
+ 'PATCH',
+ `/case/${id}` as string,
+ body,
+ );
+ }
+ }
+
+ if (resource === 'task'){
+ if (operation === 'count'){
+ const countQueryAttributs: any = prepareOptional(this.getNodeParameter('filters', i, {}) as INodeParameters);
+
+ const _countSearchQuery: IQueryObject = And();
+
+ for (const key of Object.keys(countQueryAttributs)) {
+ if (key === 'title' || key === 'description') {
+ (_countSearchQuery['_and'] as IQueryObject[]).push(
+ ContainsString(key, countQueryAttributs[key] as string)
+ );
+ } else {
+ (_countSearchQuery['_and'] as IQueryObject[]).push(
+ Eq(key, countQueryAttributs[key] as string)
+ );
+ }
+ }
+
+ const body = {
+ 'query': [
+ {
+ '_name': 'listTask'
+ },
+ {
+ '_name': 'filter',
+ '_and': _countSearchQuery['_and']
+ },
+ ]
+ };
+
+ body['query'].push(
+ {
+ '_name': 'count',
+ }
+ );
+
+ qs.name = 'count-tasks';
+
+ responseData = await theHiveApiRequest.call(
+ this,
+ 'POST',
+ '/v1/query',
+ body,
+ qs,
+ );
+
+ responseData = { count: responseData };
+ }
+
+ if (operation === 'create'){
+ const caseId = this.getNodeParameter('caseId', i) as string;
+
+ const body: IDataObject = {
+ title: this.getNodeParameter('title', i) as string,
+ status: this.getNodeParameter('status', i) as string,
+ flag: this.getNodeParameter('flag', i),
+ ...prepareOptional(this.getNodeParameter('options', i, {}) as INodeParameters)
+ };
+
+ responseData = await theHiveApiRequest.call(
+ this,
+ 'POST',
+ `/case/${caseId}/task` as string,
+ body,
+ );
+ }
+
+ if (operation === 'executeResponder'){
+ const taskId = this.getNodeParameter('id', i);
+ const responderId = this.getNodeParameter('responder', i) as string;
+ let body:IDataObject;
+ let response;
+ responseData = [];
+ body = {
+ responderId,
+ objectId: taskId,
+ objectType: 'case_task'
+ };
+ response = await theHiveApiRequest.call(
+ this,
+ 'POST',
+ '/connector/cortex/action' as string,
+ body,
+ );
+ body = {
+ query: [
+ {
+ '_name': 'listAction'
+ },
+ {
+ '_name': 'filter',
+ '_and': [
+ {
+ '_field': 'cortexId',
+ '_value': response.cortexId
+ },
+ {
+ '_field': 'objectId',
+ '_value': response.objectId
+ },
+ {
+ '_field': 'startDate',
+ '_value': response.startDate
+ }
+
+ ]
+ }
+ ],
+ };
+ qs.name = 'task-actions';
+ do {
+ response = await theHiveApiRequest.call(
+ this,
+ 'POST',
+ `/v1/query`,
+ body,
+ qs
+ );
+ } while (response.status === 'Waiting' || response.status === 'InProgress' );
+
+ responseData = response;
+ }
+
+ if (operation === 'get'){
+ const taskId = this.getNodeParameter('id', i) as string;
+
+ const credentials = this.getCredentials('theHiveApi') as IDataObject;
+
+ const version = credentials.apiVersion;
+
+ let endpoint;
+
+ let method;
+
+ let body: IDataObject = {};
+
+ if (version === 'v1') {
+ endpoint = '/v1/query';
+
+ method = 'POST';
+
+ body = {
+ 'query': [
+ {
+ '_name': 'getTask',
+ 'idOrName': taskId
+ }
+ ]
+ };
+
+ qs.name = `get-task-${taskId}`;
+
+ } else {
+ method = 'GET';
+
+ endpoint = `/case/task/${taskId}`;
+
+ }
+
+ responseData = await theHiveApiRequest.call(
+ this,
+ method,
+ endpoint as string,
+ body,
+ qs,
+ );
+ }
+
+ if(operation === 'getAll'){
+ // get all require a case id (it retursn all tasks for a specific case)
+ const credentials = this.getCredentials('theHiveApi') as IDataObject;
+
+ const returnAll = this.getNodeParameter('returnAll', i) as boolean;
+
+ const version = credentials.apiVersion;
+
+ const caseId = this.getNodeParameter('caseId', i) as string;
+
+ const options = this.getNodeParameter('options', i) as IDataObject;
+
+ let endpoint;
+
+ let method;
+
+ let body: IDataObject = {};
+
+ let limit = undefined;
+
+ if (returnAll === false) {
+ limit = this.getNodeParameter('limit', i) as number;
+ }
+
+ if (version === 'v1') {
+ endpoint = '/v1/query';
+
+ method = 'POST';
+
+ body = {
+ 'query': [
+ {
+ '_name': 'getCase',
+ 'idOrName': caseId
+ },
+ {
+ '_name': 'tasks'
+ },
+ ]
+ };
+
+ //@ts-ignore
+ prepareSortQuery(options.sort, body);
+
+ if (limit !== undefined) {
+ //@ts-ignore
+ prepareRangeQuery(`0-${limit}`, body);
+ }
+
+ qs.name = 'case-tasks';
+
+ } else {
+ method = 'POST';
+
+ endpoint = '/case/task/_search';
+
+
+ if (limit !== undefined) {
+ qs.range = `0-${limit}`;
+ }
+
+ body.query = And(Parent('case', Id(caseId)));
+
+ Object.assign(qs, prepareOptional(options));
+
+ }
+
+ responseData = await theHiveApiRequest.call(
+ this,
+ method,
+ endpoint as string,
+ body,
+ qs,
+ );
+ }
+
+ if(operation === 'search'){
+ const credentials = this.getCredentials('theHiveApi') as IDataObject;
+
+ const returnAll = this.getNodeParameter('returnAll', i) as boolean;
+
+ const version = credentials.apiVersion;
+
+ const queryAttributs: any = prepareOptional(this.getNodeParameter('filters', i, {}) as INodeParameters);
+
+ const _searchQuery: IQueryObject = And();
+
+ const options = this.getNodeParameter('options', i) as IDataObject;
+
+ for (const key of Object.keys(queryAttributs)) {
+ if (key === 'title' || key === 'description') {
+ (_searchQuery['_and'] as IQueryObject[]).push(
+ ContainsString(key, queryAttributs[key] as string)
+ );
+ } else {
+ (_searchQuery['_and'] as IQueryObject[]).push(
+ Eq(key, queryAttributs[key] as string)
+ );
+ }
+ }
+
+ let endpoint;
+
+ let method;
+
+ let body: IDataObject = {};
+
+ let limit = undefined;
+
+ if (returnAll === false) {
+ limit = this.getNodeParameter('limit', i) as number;
+ }
+
+ if (version === 'v1') {
+ endpoint = '/v1/query';
+
+ method = 'POST';
+
+ body = {
+ 'query': [
+ {
+ '_name': 'listTask'
+ },
+ {
+ '_name': 'filter',
+ '_and': _searchQuery['_and']
+ },
+ ]
+ };
+
+ //@ts-ignore
+ prepareSortQuery(options.sort, body);
+
+ if (limit !== undefined) {
+ //@ts-ignore
+ prepareRangeQuery(`0-${limit}`, body);
+ }
+
+ qs.name = 'tasks';
+
+ } else {
+ method = 'POST';
+
+ endpoint = '/case/task/_search';
+
+ if (limit !== undefined) {
+ qs.range = `0-${limit}`;
+ }
+
+ body.query = _searchQuery;
+
+ Object.assign(qs, prepareOptional(options));
+
+ }
+
+ responseData = await theHiveApiRequest.call(
+ this,
+ method,
+ endpoint as string,
+ body,
+ qs,
+ );
+ }
+
+ if(operation === 'update'){
+ const id = this.getNodeParameter('id', i) as string;
+
+ const body: IDataObject = {
+ ...prepareOptional(this.getNodeParameter('updateFields', i, {}) as INodeParameters)
+ };
+
+ responseData = await theHiveApiRequest.call(
+ this,
+ 'PATCH',
+ `/case/task/${id}` as string,
+ body,
+ );
+
+ }
+
+ }
+
+ if (resource === 'log'){
+ if (operation === 'create') {
+
+ const taskId = this.getNodeParameter('taskId', i) as string;
+
+ let body: IDataObject = {
+ message: this.getNodeParameter('message', i),
+ startDate: Date.parse(this.getNodeParameter('startDate', i) as string),
+ status: this.getNodeParameter('status', i),
+ };
+ const optionals = this.getNodeParameter('options', i) as IDataObject;
+
+ let options: IDataObject ={};
+
+ if (optionals.attachementUi) {
+ const attachmentValues = (optionals.attachementUi as IDataObject).attachmentValues as IDataObject;
+
+ if (attachmentValues) {
+ const item = items[i];
+
+ if (item.binary === undefined) {
+ throw new Error('No binary data exists on item!');
+ }
+
+ const binaryPropertyName = attachmentValues.binaryProperty as string;
+
+ if (item.binary[binaryPropertyName] === undefined) {
+ throw new Error(`No binary data property '${binaryPropertyName}' does not exists on item!`);
+ }
+
+ const binaryData = item.binary[binaryPropertyName] as IBinaryData;
+
+ options = {
+ formData: {
+ attachment: {
+ value: Buffer.from(binaryData.data, BINARY_ENCODING),
+ options: {
+ contentType: binaryData.mimeType,
+ filename: binaryData.fileName,
+ }
+ },
+ _json: JSON.stringify(body)
+ }
+ };
+
+ body = {};
+ }
+ }
+
+ responseData = await theHiveApiRequest.call(
+ this,
+ 'POST',
+ `/case/task/${taskId}/log` as string,
+ body,
+ qs,
+ '',
+ options
+ );
+ }
+
+ if (operation === 'executeResponder'){
+ const logId = this.getNodeParameter('id', i);
+ const responderId = this.getNodeParameter('responder', i) as string;
+ let body:IDataObject;
+ let response;
+ responseData = [];
+ body = {
+ responderId,
+ objectId:logId,
+ objectType: 'case_task_log'
+ };
+ response = await theHiveApiRequest.call(
+ this,
+ 'POST',
+ '/connector/cortex/action' as string,
+ body,
+ );
+ body = {
+ query: [
+ {
+ '_name': 'listAction'
+ },
+ {
+ '_name': 'filter',
+ '_and': [
+ {
+ '_field': 'cortexId',
+ '_value': response.cortexId
+ },
+ {
+ '_field': 'objectId',
+ '_value': response.objectId
+ },
+ {
+ '_field': 'startDate',
+ '_value': response.startDate
+ }
+
+ ]
+ }
+ ]
+ };
+ qs.name = 'log-actions';
+ do {
+ response = await theHiveApiRequest.call(
+ this,
+ 'POST',
+ `/v1/query`,
+ body,
+ qs
+ );
+ } while (response.status ==='Waiting' || response.status === 'InProgress' );
+
+ responseData = response;
+ }
+
+ if (operation === 'get') {
+ const logId = this.getNodeParameter('id', i) as string;
+
+ const credentials = this.getCredentials('theHiveApi') as IDataObject;
+
+ const version = credentials.apiVersion;
+
+ let endpoint;
+
+ let method;
+
+ let body: IDataObject = {};
+
+ if (version === 'v1') {
+
+ endpoint = '/v1/query';
+
+ method = 'POST';
+
+ body = {
+ query: [
+ {
+ _name: 'getLog',
+ idOrName: logId
+ }
+ ]
+ };
+
+ qs.name = `get-log-${logId}`;
+
+ } else {
+
+ method = 'POST';
+
+ endpoint = '/case/task/log/_search';
+
+ body.query = { _id: logId };
+
+ }
+
+ responseData = await theHiveApiRequest.call(
+ this,
+ method,
+ endpoint as string,
+ body,
+ qs,
+ );
+ }
+
+ if (operation === 'getAll'){
+
+ const credentials = this.getCredentials('theHiveApi') as IDataObject;
+
+ const returnAll = this.getNodeParameter('returnAll', i) as boolean;
+
+ const version = credentials.apiVersion;
+
+ const taskId = this.getNodeParameter('taskId', i) as string;
+
+ let endpoint;
+
+ let method;
+
+ let body: IDataObject = {};
+
+ let limit = undefined;
+
+ if (returnAll === false) {
+ limit = this.getNodeParameter('limit', i) as number;
+ }
+
+ if (version === 'v1') {
+ endpoint = '/v1/query';
+
+ method = 'POST';
+
+ body = {
+ 'query': [
+ {
+ '_name': 'getTask',
+ 'idOrName': taskId
+ },
+ {
+ '_name': 'logs'
+ },
+ ]
+ };
+
+ if (limit !== undefined) {
+ //@ts-ignore
+ prepareRangeQuery(`0-${limit}`, body);
+ }
+
+ qs.name = 'case-task-logs';
+
+ } else {
+ method = 'POST';
+
+ endpoint = '/case/task/log/_search';
+
+ if (limit !== undefined) {
+ qs.range = `0-${limit}`;
+ }
+
+ body.query = And(Parent(
+ 'task',
+ Id(taskId)
+ ));
+ }
+
+ responseData = await theHiveApiRequest.call(
+ this,
+ method,
+ endpoint as string,
+ body,
+ qs,
+ );
+ }
+ }
+
+ if (Array.isArray(responseData)) {
+ returnData.push.apply(returnData, responseData as IDataObject[]);
+
+ } else if (responseData !== undefined) {
+ returnData.push(responseData as IDataObject);
+ }
+ }
+ return [this.helpers.returnJsonArray(returnData)];
+ }
+}
diff --git a/packages/nodes-base/nodes/TheHive/TheHiveTrigger.node.ts b/packages/nodes-base/nodes/TheHive/TheHiveTrigger.node.ts
new file mode 100644
index 0000000000..968e9ef2b8
--- /dev/null
+++ b/packages/nodes-base/nodes/TheHive/TheHiveTrigger.node.ts
@@ -0,0 +1,165 @@
+import {
+ IWebhookFunctions,
+} from 'n8n-core';
+
+import {
+ IDataObject,
+ INodeTypeDescription,
+ INodeType,
+ IWebhookResponseData,
+ IHookFunctions,
+} from 'n8n-workflow';
+
+export class TheHiveTrigger implements INodeType {
+ description: INodeTypeDescription = {
+ displayName: 'TheHive Trigger',
+ name: 'theHiveTrigger',
+ icon: 'file:thehive.png',
+ group: ['trigger'],
+ version: 1,
+ description: 'Starts the workflow when a TheHive event occurs.',
+ defaults: {
+ name: 'TheHive Trigger',
+ color: '#f3d02f',
+ },
+ inputs: [],
+ outputs: ['main'],
+ webhooks: [
+ {
+ name: 'default',
+ httpMethod: 'POST',
+ reponseMode: 'onReceived',
+ path: 'webhook',
+ },
+ ],
+ properties: [
+ {
+ displayName: 'Events',
+ name: 'events',
+ type: 'multiOptions',
+ default: [],
+ required: true,
+ description: 'Events types',
+ options: [
+ {
+ name: '*',
+ value: '*',
+ description: 'Any time any event is triggered (Wildcard Event).',
+ },
+ {
+ name: 'Alert Created',
+ value: 'alert_create',
+ description: 'Triggered when an alert is created',
+ },
+ {
+ name: 'Alert Updated',
+ value: 'alert_update',
+ description: 'Triggered when an alert is updated',
+ },
+ {
+ name: 'Alert Deleted',
+ value: 'alert_delete',
+ description: 'Triggered when an alert is deleted',
+ },
+ {
+ name: 'Observable Created',
+ value: 'case_artifact_create',
+ description: 'Triggered when an observable is created',
+ },
+ {
+ name: 'Observable Updated',
+ value: 'case_artifact_update',
+ description: 'Triggered when an observable is updated',
+ },
+ {
+ name: 'Observable Deleted',
+ value: 'case_artifact_delete',
+ description: 'Triggered when an observable is deleted',
+ },
+ {
+ name: 'Case Created',
+ value: 'case_create',
+ description: 'Triggered when a case is created',
+ },
+ {
+ name: 'Case Updated',
+ value: 'case_update',
+ description: 'Triggered when a case is updated',
+ },
+ {
+ name: 'Case Deleted',
+ value: 'case_delete',
+ description: 'Triggered when a case is deleted',
+ },
+ {
+ name: 'Task Created',
+ value: 'case_task_create',
+ description: 'Triggered when a task is created',
+ },
+ {
+ name: 'Task Updated',
+ value: 'case_task_update',
+ description: 'Triggered when a task is updated',
+ },
+ {
+ name: 'Task Deleted',
+ value: 'case_task_delete',
+ description: 'Triggered when a task is deleted',
+ },
+ {
+ name: 'Log Created',
+ value: 'case_task_log_create',
+ description: 'Triggered when a task log is created',
+ },
+ ]
+ }
+ ]
+ };
+ // @ts-ignore (because of request)
+ webhookMethods = {
+ default: {
+ async checkExists(this: IHookFunctions): Promise {
+ return true;
+ },
+ async create(this: IHookFunctions): Promise {
+ return true;
+ },
+ async delete(this: IHookFunctions): Promise {
+ return true;
+ },
+ },
+ };
+
+ async webhook(this: IWebhookFunctions): Promise {
+ // Get the request body
+ const bodyData = this.getBodyData();
+ const events = this.getNodeParameter('events', []) as string[];
+ if(!bodyData.operation || !bodyData.objectType) {
+ // Don't start the workflow if mandatory fields are not specified
+ return {};
+ }
+
+ // Don't start the workflow if the event is not fired
+ const event = `${(bodyData.objectType as string).toLowerCase()}_${(bodyData.operation as string).toLowerCase()}`;
+ if(events.indexOf('*') === -1 && events.indexOf(event) === -1) {
+ return {};
+ }
+
+ // The data to return and so start the workflow with
+ const returnData: IDataObject[] = [];
+ returnData.push(
+ {
+ event,
+ body: this.getBodyData(),
+ headers: this.getHeaderData(),
+ query: this.getQueryData(),
+ },
+ );
+
+ return {
+ workflowData: [
+ this.helpers.returnJsonArray(returnData)
+ ],
+ };
+ }
+}
diff --git a/packages/nodes-base/nodes/TheHive/descriptions/AlertDescription.ts b/packages/nodes-base/nodes/TheHive/descriptions/AlertDescription.ts
new file mode 100644
index 0000000000..21c50f893d
--- /dev/null
+++ b/packages/nodes-base/nodes/TheHive/descriptions/AlertDescription.ts
@@ -0,0 +1,839 @@
+import {
+ INodeProperties,
+} from 'n8n-workflow';
+
+import {
+ TLP,
+} from '../interfaces/AlertInterface';
+
+export const alertOperations = [
+ {
+ displayName: 'Operation',
+ name: 'operation',
+ type: 'options',
+ required: true,
+ typeOptions: {
+ loadOptionsMethod: 'loadAlertOptions',
+ },
+ displayOptions: {
+ show: {
+ resource: [
+ 'alert',
+ ],
+ },
+ },
+ default: 'create',
+ },
+] as INodeProperties[];
+
+export const alertFields = [
+ {
+ displayName: 'Return All',
+ name: 'returnAll',
+ type: 'boolean',
+ displayOptions: {
+ show: {
+ operation: [
+ 'getAll',
+ ],
+ resource: [
+ 'alert',
+ ],
+ },
+ },
+ default: false,
+ description: 'If all results should be returned or only up to a given limit.',
+ },
+ {
+ displayName: 'Limit',
+ name: 'limit',
+ type: 'number',
+ displayOptions: {
+ show: {
+ operation: [
+ 'getAll',
+ ],
+ resource: [
+ 'alert',
+ ],
+ returnAll: [
+ false,
+ ],
+ },
+ },
+ typeOptions: {
+ minValue: 1,
+ maxValue: 500,
+ },
+ default: 100,
+ description: 'How many results to return.',
+ },
+ // required attributs
+ {
+ displayName: 'Alert ID',
+ name: 'id',
+ type: 'string',
+ required: true,
+ default: '',
+ displayOptions: {
+ show: {
+ resource: [
+ 'alert'
+ ],
+ operation: [
+ 'promote',
+ 'merge',
+ 'update',
+ 'executeResponder',
+ 'get',
+ ],
+ },
+ },
+ description: 'Title of the alert'
+ },
+ {
+ displayName: 'Case ID',
+ name: 'caseId',
+ type: 'string',
+ required: true,
+ default: '',
+ displayOptions: {
+ show: {
+ resource: [
+ 'alert',
+ ],
+ operation: [
+ 'merge',
+ ],
+ },
+ },
+ },
+ {
+ displayName: 'Title',
+ name: 'title',
+ type: 'string',
+ required: true,
+ default: '',
+ displayOptions: {
+ show: {
+ resource: [
+ 'alert',
+ ],
+ operation: [
+ 'create',
+ ],
+ },
+ },
+ description: 'Title of the alert',
+ },
+ {
+ displayName: 'Description',
+ name: 'description',
+ type: 'string',
+ required: true,
+ default: '',
+ displayOptions: {
+ show: {
+ resource: [
+ 'alert',
+ ],
+ operation: [
+ 'create',
+ ],
+ },
+ },
+ description: 'Description of the alert',
+ },
+ {
+ displayName: 'Severity',
+ name: 'severity',
+ type: 'options',
+ options:[
+ {
+ name: 'Low',
+ value: 1
+ },
+ {
+ name: 'Medium',
+ value: 2
+ },
+ {
+ name: 'High',
+ value: 3,
+ },
+ ],
+ required: true,
+ default: 2,
+ displayOptions: {
+ show: {
+ resource: [
+ 'alert',
+ ],
+ operation: [
+ 'create',
+ ],
+ },
+ },
+ description: 'Severity of the alert. Default=Medium',
+ },
+ {
+ displayName: 'Date',
+ name: 'date',
+ type: 'dateTime',
+ required: true,
+ default: '',
+ displayOptions: {
+ show: {
+ resource: [
+ 'alert',
+ ],
+ operation: [
+ 'create',
+ ],
+ },
+ },
+ description: 'Date and time when the alert was raised default=now'
+ },
+ {
+ displayName: 'Tags',
+ name: 'tags',
+ type: 'string',
+ required: true,
+ default: '',
+ placeholder:'tag,tag2,tag3...',
+ displayOptions: {
+ show: {
+ resource: [
+ 'alert',
+ ],
+ operation: [
+ 'create',
+ ],
+ },
+ },
+ description: 'Case Tags'
+ },
+ {
+ displayName: 'TLP',
+ name: 'tlp',
+ type: 'options',
+ required: true,
+ default: 2,
+ options: [
+ {
+ name:'White',
+ value:TLP.white,
+ },
+ {
+ name:'Green',
+ value:TLP.green,
+ },
+ {
+ name:'Amber',
+ value:TLP.amber,
+ },{
+ name:'Red',
+ value:TLP.red,
+ }
+ ],
+ displayOptions: {
+ show: {
+ resource: [
+ 'alert',
+ ],
+ operation: [
+ 'create',
+ ],
+ },
+ },
+ description: 'Traffict Light Protocol (TLP). Default=Amber'
+ },
+ {
+ displayName: 'Status',
+ name: 'status',
+ type: 'options',
+ required: true,
+ options:[
+ {
+ name: 'New',
+ value: 'New',
+ },
+ {
+ name: 'Updated',
+ value: 'Updated',
+ },
+ {
+ name: 'Ignored',
+ value: 'Ignored'
+ },
+ {
+ name: 'Imported',
+ value: 'Imported',
+ },
+ ],
+ default: 'New',
+ displayOptions: {
+ show: {
+ resource: [
+ 'alert',
+ ],
+ operation: [
+ 'create',
+ ],
+ },
+ },
+ description: 'Status of the alert',
+ },
+ {
+ displayName: 'Type',
+ name: 'type',
+ type: 'string',
+ required: true,
+ default: '',
+ displayOptions: {
+ show: {
+ resource: [
+ 'alert',
+ ],
+ operation: [
+ 'create',
+ ],
+ },
+ },
+ description: 'Type of the alert'
+ },
+ {
+ displayName: 'Source',
+ name: 'source',
+ type: 'string',
+ required: true,
+ default: '',
+ displayOptions: {
+ show: {
+ resource: [
+ 'alert',
+ ],
+ operation: [
+ 'create',
+ ],
+ },
+ },
+ description: 'Source of the alert'
+ },
+ {
+ displayName: 'SourceRef',
+ name: 'sourceRef',
+ type: 'string',
+ required: true,
+ default: '',
+ displayOptions: {
+ show: {
+ resource: [
+ 'alert',
+ ],
+ operation: [
+ 'create',
+ ],
+ },
+ },
+ description: 'Source reference of the alert'
+ },
+ {
+ displayName: 'Follow',
+ name: 'follow',
+ type: 'boolean',
+ required: true,
+ default: true,
+ displayOptions: {
+ show: {
+ resource: [
+ 'alert',
+ ],
+ operation: [
+ 'create',
+ ],
+ },
+ },
+ description: 'if true, the alert becomes active when updated default=true',
+ },
+ {
+ displayName: 'Artifacts',
+ name: 'artifactUi',
+ type: 'fixedCollection',
+ placeholder: 'Add Artifact',
+ default: '',
+ displayOptions: {
+ show: {
+ resource: [
+ 'alert',
+ ],
+ operation: [
+ 'create',
+ ],
+ },
+ },
+ typeOptions: {
+ multipleValues: true,
+ },
+ options: [
+ {
+ displayName: 'Artifact',
+ name: 'artifactValues',
+ values: [
+ {
+ displayName: 'Data Type',
+ name: 'dataType',
+ type: 'options',
+ default: '',
+ options: [
+ {
+ name: 'IP',
+ value: 'ip',
+ },
+ {
+ name: 'Domain',
+ value: 'domain',
+ },
+ {
+ name: 'File',
+ value: 'file',
+ },
+ ],
+ description: '',
+ },
+ {
+ displayName: 'Data',
+ name: 'data',
+ type: 'string',
+ displayOptions: {
+ hide: {
+ dataType: [
+ 'file',
+ ],
+ },
+ },
+ default: '',
+ description: '',
+ },
+ {
+ displayName: 'Binary Property',
+ name: 'binaryProperty',
+ type: 'string',
+ displayOptions: {
+ show: {
+ dataType: [
+ 'file',
+ ],
+ },
+ },
+ default: 'data',
+ description: '',
+ },
+ {
+ displayName: 'Message',
+ name: 'message',
+ type: 'string',
+ default: '',
+ description: '',
+ },
+ {
+ displayName: 'Case Tags',
+ name: 'tags',
+ type: 'string',
+ default: '',
+ description: '',
+ },
+ ],
+ },
+ ],
+ description: 'Artifact attributes'
+ },
+ // required for responder execution
+ {
+ displayName: 'Responder ID',
+ name: 'responder',
+ type: 'options',
+ required: true,
+ default: '',
+ typeOptions: {
+ loadOptionsDependsOn: [
+ 'id',
+ ],
+ loadOptionsMethod: 'loadResponders',
+ },
+ displayOptions:{
+ show: {
+ resource: [
+ 'alert',
+ ],
+ operation: [
+ 'executeResponder',
+ ],
+ },
+ hide: {
+ id: [
+ '',
+ ],
+ },
+ },
+ },
+ // optional attributs (Create, Promote operations)
+ {
+ displayName: 'Additional Fields',
+ name: 'additionalFields',
+ placeholder: 'Add Field',
+ type: 'collection',
+ required: false,
+ default: '',
+ displayOptions: {
+ show: {
+ resource: [
+ 'alert',
+ ],
+ operation: [
+ 'create',
+ 'promote',
+ ],
+ },
+ },
+ options:[
+ {
+ displayName: 'Case Template',
+ name: 'caseTemplate',
+ type:'string',
+ default: '',
+ description: `Case template to use when a case is created from this alert`,
+ },
+ ],
+ },
+ // optional attributs (Update operation)
+ {
+ displayName: 'Update Fields',
+ name: 'updateFields',
+ type: 'collection',
+ placeholder: 'Add Field',
+ default: '',
+ displayOptions: {
+ show: {
+ resource: [
+ 'alert',
+ ],
+ operation: [
+ 'update',
+ ],
+ },
+ },
+ options: [
+ {
+ displayName: 'Artifacts',
+ name: 'artifactUi',
+ type: 'fixedCollection',
+ placeholder: 'Add Artifact',
+ default: '',
+ typeOptions: {
+ multipleValues: true,
+ },
+ options: [
+ {
+ displayName: 'Artifact',
+ name: 'artifactValues',
+ values: [
+ {
+ displayName: 'Data Type',
+ name: 'dataType',
+ type: 'options',
+ default: '',
+ options: [
+ {
+ name: 'IP',
+ value: 'ip',
+ },
+ {
+ name: 'Domain',
+ value: 'domain',
+ },
+ {
+ name: 'File',
+ value: 'file',
+ },
+ ],
+ description: '',
+ },
+ {
+ displayName: 'Data',
+ name: 'data',
+ type: 'string',
+ displayOptions: {
+ hide: {
+ dataType: [
+ 'file',
+ ],
+ },
+ },
+ default: '',
+ description: '',
+ },
+ {
+ displayName: 'Binary Property',
+ name: 'binaryProperty',
+ type: 'string',
+ displayOptions: {
+ show: {
+ dataType: [
+ 'file',
+ ],
+ },
+ },
+ default: 'data',
+ description: '',
+ },
+ {
+ displayName: 'Message',
+ name: 'message',
+ type: 'string',
+ default: '',
+ description: '',
+ },
+ {
+ displayName: 'Case Tags',
+ name: 'tags',
+ type: 'string',
+ default: '',
+ description: '',
+ },
+ ],
+ },
+ ],
+ },
+ {
+ displayName: 'Case Template',
+ name: 'caseTemplate',
+ type: 'string',
+ required: false,
+ default: '',
+ description: `Case template to use when a case is created from this alert`,
+ },
+ {
+ displayName: 'Description',
+ name: 'description',
+ type: 'string',
+ required: false,
+ default: '',
+ description: 'Description of the alert',
+ },
+ {
+ displayName: 'Follow',
+ name: 'follow',
+ type: 'boolean',
+ default: true,
+ description: 'if true, the alert becomes active when updated default=true',
+ },
+ {
+ displayName: 'Severity',
+ name: ' severity',
+ type: 'options',
+ options:[
+ {
+ name: 'Low',
+ value: 1,
+ },
+ {
+ name: 'Medium',
+ value: 2,
+ },
+ {
+ name: 'High',
+ value: 3,
+ },
+ ],
+ default: 2,
+ description: 'Severity of the alert. Default=Medium',
+ },
+ {
+ displayName: 'Status',
+ name: 'status',
+ type: 'options',
+ options:[
+ {
+ name: 'New',
+ value: 'New',
+ },
+ {
+ name:'Updated',
+ value:'Updated',
+ },
+ {
+ name: 'Ignored',
+ value:'Ignored',
+ },
+ {
+ name:'Imported',
+ value:'Imported',
+ },
+ ],
+ default: 'New',
+ },
+ {
+ displayName: 'Case Tags',
+ name: 'tags',
+ type: 'string',
+ default: '',
+ placeholder:'tag,tag2,tag3...',
+ },
+ {
+ displayName: 'Title',
+ name: 'title',
+ type: 'string',
+ required: false,
+ default: '',
+ description: 'Title of the alert'
+ },
+ {
+ displayName: 'TLP',
+ name: 'tlp',
+ type: 'options',
+ required: false,
+ default: 2,
+ options: [
+ {
+ name: 'White',
+ value: TLP.white,
+ },
+ {
+ name: 'Green',
+ value: TLP.green,
+ },
+ {
+ name: 'Amber',
+ value: TLP.amber,
+ },
+ {
+ name: 'Red',
+ value: TLP.red,
+ },
+ ],
+ description: 'Traffict Light Protocol (TLP). Default=Amber'
+ },
+ ],
+ },
+ //Query attributs (Search operation)
+ {
+ displayName: 'Options',
+ name: 'options',
+ displayOptions: {
+ show: {
+ operation: [
+ 'getAll',
+ ],
+ resource: [
+ 'alert',
+ ],
+ },
+ },
+ type: 'collection',
+ placeholder: 'Add Option',
+ default: {},
+ options: [
+ {
+ displayName: 'Sort',
+ name: 'sort',
+ type: 'string',
+ placeholder: '±Attribut, exp +status',
+ default: '',
+ },
+ ],
+ },
+ {
+ displayName: 'Filters',
+ name: 'filters',
+ placeholder: 'Add Filter',
+ default: {},
+ type: 'collection',
+ displayOptions: {
+ show: {
+ resource: [
+ 'alert'
+ ],
+ operation: [
+ 'getAll',
+ 'count',
+ ],
+ },
+ },
+ options:[
+ {
+ displayName: 'Description',
+ name: 'description',
+ type: 'string',
+ default: '',
+ description: 'Description of the alert',
+ },
+ {
+ displayName: 'Follow',
+ name: 'follow',
+ type: 'boolean',
+ default: false,
+ description: 'if true, the alert becomes active when updated default=true',
+ },
+ {
+ displayName: 'Severity',
+ name: 'severity',
+ type: 'options',
+ options: [
+ {
+ name: 'Low',
+ value: 1
+ },
+ {
+ name: 'Medium',
+ value: 2
+ },
+ {
+ name: 'High',
+ value: 3
+ },
+ ],
+ default: 2,
+ description: 'Severity of the alert. Default=Medium',
+ },
+ {
+ displayName: 'Tags',
+ name: 'tags',
+ type: 'string',
+ default: '',
+ placeholder: 'tag,tag2,tag3...',
+ },
+ {
+ displayName: 'Title',
+ name: 'title',
+ type: 'string',
+ default: '',
+ },
+ {
+ displayName: 'TLP',
+ name: 'tlp',
+ type: 'options',
+ default: 2,
+ options: [
+ {
+ name:'White',
+ value:TLP.white,
+ },
+ {
+ name:'Green',
+ value:TLP.green,
+ },
+ {
+ name:'Amber',
+ value:TLP.amber,
+ },
+ {
+ name:'Red',
+ value:TLP.red,
+ }
+ ],
+ description: 'Traffict Light Protocol (TLP). Default=Amber'
+ },
+ ],
+ }
+] as INodeProperties[];
diff --git a/packages/nodes-base/nodes/TheHive/descriptions/CaseDescription.ts b/packages/nodes-base/nodes/TheHive/descriptions/CaseDescription.ts
new file mode 100644
index 0000000000..6e78d07ef6
--- /dev/null
+++ b/packages/nodes-base/nodes/TheHive/descriptions/CaseDescription.ts
@@ -0,0 +1,757 @@
+import {
+ INodeProperties,
+} from 'n8n-workflow';
+
+import {
+ TLP,
+} from '../interfaces/AlertInterface';
+
+export const caseOperations = [
+ {
+ displayName: 'Operation',
+ name: 'operation',
+ default: 'getAll',
+ type: 'options',
+ required: true,
+ displayOptions: {
+ show: {
+ resource: [
+ 'case',
+ ],
+ },
+ },
+ typeOptions: {
+ loadOptionsDependsOn: [
+ 'resource',
+ ],
+ loadOptionsMethod: 'loadCaseOptions',
+ },
+ },
+] as INodeProperties[];
+
+export const caseFields = [
+ {
+ displayName: 'Return All',
+ name: 'returnAll',
+ type: 'boolean',
+ displayOptions: {
+ show: {
+ operation: [
+ 'getAll',
+ ],
+ resource: [
+ 'case',
+ ],
+ },
+ },
+ default: false,
+ description: 'If all results should be returned or only up to a given limit.',
+ },
+ {
+ displayName: 'Limit',
+ name: 'limit',
+ type: 'number',
+ displayOptions: {
+ show: {
+ operation: [
+ 'getAll',
+ ],
+ resource: [
+ 'case',
+ ],
+ returnAll: [
+ false,
+ ],
+ },
+ },
+ typeOptions: {
+ minValue: 1,
+ maxValue: 500,
+ },
+ default: 100,
+ description: 'How many results to return.',
+ },
+ // Required fields
+ {
+ displayName: 'Case ID',
+ name: 'id',
+ type: 'string',
+ default: '',
+ required: true,
+ displayOptions: {
+ show: {
+ resource: [
+ 'case',
+ ],
+ operation: [
+ 'update',
+ 'executeResponder',
+ 'get',
+ ],
+ },
+ },
+ description: 'ID of the case',
+ },
+ {
+ displayName: 'Title',
+ name: 'title',
+ type: 'string',
+ default: '',
+ required: true,
+ displayOptions: {
+ show: {
+ resource: [
+ 'case',
+ ],
+ operation: [
+ 'create',
+ ],
+ },
+ },
+ description: 'Title of the case',
+ },
+ {
+ displayName: 'Description',
+ name: 'description',
+ type: 'string',
+ required: true,
+ default: '',
+ displayOptions: {
+ show: {
+ resource: [
+ 'case',
+ ],
+ operation: [
+ 'create',
+ ],
+ },
+ },
+ description: 'Description of the case',
+ },
+ {
+ displayName: 'Severity',
+ name: 'severity',
+ type: 'options',
+ options: [
+ {
+ name: 'Low',
+ value: 1,
+ },
+ {
+ name: 'Medium',
+ value: 2,
+ },
+ {
+ name: 'High',
+ value: 3,
+ },
+ ],
+ required: true,
+ default: 2,
+ displayOptions: {
+ show: {
+ resource: [
+ 'case',
+ ],
+ operation: [
+ 'create',
+ ],
+ },
+ },
+ description: 'Severity of the alert. Default=Medium',
+ },
+ {
+ displayName: 'Start Date',
+ name: 'startDate',
+ type: 'dateTime',
+ required: true,
+ default: '',
+ displayOptions: {
+ show: {
+ resource: [
+ 'case',
+ ],
+ operation: [
+ 'create',
+ ],
+ },
+ },
+ description: 'Date and time of the begin of the case default=now',
+ },
+ {
+ displayName: 'Owner',
+ name: 'owner',
+ type: 'string',
+ default: '',
+ required: true,
+ displayOptions: {
+ show: {
+ resource: [
+ 'case',
+ ],
+ operation: [
+ 'create',
+ ],
+ },
+ },
+ },
+ {
+ displayName: 'Flag',
+ name: 'flag',
+ type: 'boolean',
+ required: true,
+ default: false,
+ displayOptions: {
+ show: {
+ resource: [
+ 'case',
+ ],
+ operation: [
+ 'create',
+ ],
+ },
+ },
+ description: 'Flag of the case default=false',
+ },
+ {
+ displayName: 'TLP',
+ name: 'tlp',
+ type: 'options',
+ required: true,
+ default: 2,
+ options: [
+ {
+ name: 'White',
+ value: TLP.white,
+ },
+ {
+ name: 'Green',
+ value: TLP.green,
+ },
+ {
+ name: 'Amber',
+ value: TLP.amber,
+ },
+ {
+ name: 'Red',
+ value: TLP.red,
+ },
+ ],
+ displayOptions: {
+ show: {
+ resource: [
+ 'case',
+ ],
+ operation: [
+ 'create',
+ ],
+ },
+ },
+ description: 'Traffict Light Protocol (TLP). Default=Amber'
+ },
+ {
+ displayName: 'Tags',
+ name: 'tags',
+ type: 'string',
+ required: true,
+ default: '',
+ displayOptions: {
+ show: {
+ resource: [
+ 'case',
+ ],
+ operation: [
+ 'create',
+ ],
+ },
+ },
+ },
+ // required for responder execution
+ {
+ displayName: 'Responder ID',
+ name: 'responder',
+ type: 'options',
+ default: '',
+ required: true,
+ typeOptions: {
+ loadOptionsDependsOn: [
+ 'id',
+ ],
+ loadOptionsMethod: 'loadResponders',
+ },
+ displayOptions: {
+ show: {
+ resource: [
+ 'case',
+ ],
+ operation: [
+ 'executeResponder',
+ ],
+ },
+ hide: {
+ id: [
+ '',
+ ],
+ },
+ },
+ },
+ // Optional fields (Create operation)
+ {
+ displayName: 'Options',
+ type: 'collection',
+ name: 'options',
+ placeholder: 'Add options',
+ displayOptions: {
+ show: {
+ resource: [
+ 'case',
+ ],
+ operation: [
+ 'create',
+ ],
+ },
+ },
+ required: false,
+ default: '',
+ options: [
+ {
+ displayName: 'End Date',
+ name: 'endDate',
+ default: '',
+ type: 'dateTime',
+ description: 'Resolution date',
+ },
+ {
+ displayName: 'Summary',
+ name: 'summary',
+ type: 'string',
+ default: '',
+ description: 'Summary of the case, to be provided when closing a case',
+ },
+ {
+ displayName: 'Metrics (JSON)',
+ name: 'metrics',
+ default: '[]',
+ type: 'json',
+ description: 'List of metrics',
+ },
+ ],
+ },
+ // Optional fields (Update operations)
+ {
+ displayName: 'Update Fields',
+ type: 'collection',
+ name: 'updateFields',
+ placeholder: 'Add Field',
+ displayOptions: {
+ show: {
+ resource: [
+ 'case',
+ ],
+ operation: [
+ 'update',
+ ],
+ },
+ },
+ required: false,
+ default: '',
+ options: [
+ {
+ displayName: 'Description',
+ name: 'description',
+ type: 'string',
+ default: '',
+ description: 'Description of the case',
+ },
+ {
+ displayName: 'End Date',
+ name: 'endDate',
+ type: 'dateTime',
+ default: '',
+ description: 'Resolution date',
+ },
+ {
+ displayName: 'Flag',
+ name: 'flag',
+ type: 'boolean',
+ default: false,
+ description: 'Flag of the case default=false',
+ },
+ {
+ displayName: 'Impact Status',
+ name: 'impactStatus',
+ type: 'options',
+ default: '',
+ options: [
+ {
+ name: 'No Impact',
+ value: 'NoImpact'
+ },
+ {
+ name: 'With Impact',
+ value: 'WithImpact'
+ },
+ {
+ name: 'Not Applicable',
+ value: 'NotApplicable'
+ },
+ ],
+ description: 'Impact status of the case',
+ },
+ {
+ displayName: 'Metrics (JSON)',
+ name: 'metrics',
+ type: 'json',
+ default: '[]',
+ description: 'List of metrics',
+ },
+ {
+ displayName: 'Owner',
+ name: 'owner',
+ type: 'string',
+ default: '',
+ },
+ {
+ displayName: 'Resolution Status',
+ name: 'resolutionStatus',
+ type: 'options',
+ default: '',
+ options: [
+ {
+ value: 'Indeterminate',
+ name: 'Indeterminate'
+ },
+ {
+ value: 'False Positive',
+ name: 'FalsePositive'
+ },
+ {
+ value: 'True Positive',
+ name: 'TruePositive'
+ },
+ {
+ value: 'Other',
+ name: 'Other'
+ },
+ {
+ value: 'Duplicated',
+ name: 'Duplicated'
+ },
+ ],
+ description: 'Resolution status of the case',
+ },
+ {
+ displayName: 'Severity',
+ name: 'severity',
+ type: 'options',
+ options: [
+ {
+ name: 'Low',
+ value: 1
+ },
+ {
+ name: 'Medium',
+ value: 2
+ },
+ {
+ name: 'High',
+ value: 3
+ },
+ ],
+ default: 2,
+ description: 'Severity of the alert. Default=Medium',
+ },
+ {
+ displayName: 'Start Date',
+ name: 'startDate',
+ type: 'dateTime',
+ default: '',
+ description: 'Date and time of the begin of the case default=now',
+ },
+ {
+ displayName: 'Status',
+ name: 'status',
+ type: 'options',
+ options: [
+ {
+ name: 'Open',
+ value: 'Open',
+ },
+ {
+ name: 'Resolved',
+ value: 'Resolved',
+ },
+ {
+ name: 'Deleted',
+ value: 'Deleted',
+ },
+ ],
+ default: 'Open',
+ },
+ {
+ displayName: 'Summary',
+ name: 'summary',
+ type: 'string',
+ default: '',
+ description: 'Summary of the case, to be provided when closing a case'
+ },
+ {
+ displayName: 'Tags',
+ name: 'tags',
+ type: 'string',
+ default: '',
+ },
+ {
+ displayName: 'Title',
+ name: 'title',
+ type: 'string',
+ default: '',
+ description: 'Title of the case',
+ },
+ {
+ displayName: 'TLP',
+ name: 'tlp',
+ type: 'options',
+ default: 2,
+ options: [
+ {
+ name: 'White',
+ value: TLP.white,
+ },
+ {
+ name: 'Green',
+ value: TLP.green,
+ },
+ {
+ name: 'Amber',
+ value: TLP.amber,
+ },
+ {
+ name: 'Red',
+ value: TLP.red,
+ },
+ ],
+ description: 'Traffict Light Protocol (TLP). Default=Amber'
+ },
+ ],
+ },
+ // query options
+ {
+ displayName: 'Options',
+ name: 'options',
+ displayOptions: {
+ show: {
+ operation: [
+ 'getAll',
+ ],
+ resource: [
+ 'case',
+ ],
+ },
+ },
+ type: 'collection',
+ placeholder: 'Add Option',
+ default: {},
+ options: [
+ {
+ displayName: 'Sort',
+ name: 'sort',
+ type: 'string',
+ placeholder: '±Attribut, exp +status',
+ description: 'Specify the sorting attribut, + for asc, - for desc',
+ default: '',
+ },
+ ],
+ },
+ // Query filters
+ {
+ displayName: 'Filters',
+ name: 'filters',
+ type: 'collection',
+ required: false,
+ default: {},
+ placeholder: 'Add a Filter',
+ displayOptions: {
+ show: {
+ resource: [
+ 'case'
+ ],
+ operation: [
+ 'getAll',
+ 'count',
+ ],
+ },
+ },
+ options: [
+ {
+ displayName: 'Description',
+ name: 'description',
+ type: 'string',
+ default: '',
+ description: 'Description of the case',
+ },
+ {
+ displayName: 'End Date',
+ name: 'endDate',
+ type: 'dateTime',
+ default: '',
+ description: 'Resolution date',
+ },
+ {
+ displayName: 'Flag',
+ name: 'flag',
+ type: 'boolean',
+ default: false,
+ description: 'Flag of the case default=false',
+ },
+ {
+ displayName: 'Impact Status',
+ name: 'impactStatus',
+ type: 'options',
+ default: '',
+ options: [
+ {
+ name: 'No Impact',
+ value: 'NoImpact',
+ },
+ {
+ name: 'With Impact',
+ value: 'WithImpact',
+ },
+ {
+ name: 'Not Applicable',
+ value: 'NotApplicable',
+ },
+ ],
+ },
+ {
+ displayName: 'Owner',
+ name: 'owner',
+ type: 'string',
+ default: '',
+ },
+ {
+ displayName: 'Resolution Status',
+ name: 'resolutionStatus',
+ type: 'options',
+ default: '',
+ options: [
+ {
+ value: 'Indeterminate',
+ name: 'Indeterminate',
+ },
+ {
+ value: 'False Positive',
+ name: 'FalsePositive',
+ },
+ {
+ value: 'True Positive',
+ name: 'TruePositive',
+ },
+ {
+ value: 'Other',
+ name: 'Other',
+ },
+ {
+ value: 'Duplicated',
+ name: 'Duplicated',
+ },
+ ],
+ },
+ {
+ displayName: 'Severity',
+ name: 'severity',
+ type: 'options',
+ options: [
+ {
+ name: 'Low',
+ value: 1
+ },
+ {
+ name: 'Medium',
+ value: 2
+ },
+ {
+ name: 'High',
+ value: 3
+ },
+ ],
+ default: 2,
+ description: 'Severity of the alert. Default=Medium',
+ },
+ {
+ displayName: 'Start Date',
+ name: 'startDate',
+ type: 'dateTime',
+ default: '',
+ description: 'Date and time of the begin of the case default=now',
+ },
+ {
+ displayName: 'Status',
+ name: 'status',
+ type: 'options',
+ options: [
+ {
+ name: 'Open',
+ value: 'Open',
+ },
+ {
+ name: 'Resolved',
+ value: 'Resolved',
+ },
+ {
+ name: 'Deleted',
+ value: 'Deleted',
+ },
+ ],
+ default: 'Open',
+ },
+ {
+ displayName: 'Summary',
+ name: 'summary',
+ type: 'string',
+ default: '',
+ description: 'Summary of the case, to be provided when closing a case',
+ },
+ {
+ displayName: 'Tags',
+ name: 'tags',
+ type: 'string',
+ default: '',
+ },
+ {
+ displayName: 'Title',
+ name: 'title',
+ type: 'string',
+ default: '',
+ description: 'Title of the case',
+ },
+ {
+ displayName: 'TLP',
+ name: 'tlp',
+ type: 'options',
+ required: false,
+ default: 2,
+ options: [
+ {
+ name: 'White',
+ value: TLP.white,
+ },
+ {
+ name: 'Green',
+ value: TLP.green,
+ },
+ {
+ name: 'Amber',
+ value: TLP.amber,
+ },
+ {
+ name: 'Red',
+ value: TLP.red,
+ },
+ ],
+ description: 'Traffict Light Protocol (TLP). Default=Amber',
+ },
+ ],
+ },
+] as INodeProperties[];
diff --git a/packages/nodes-base/nodes/TheHive/descriptions/LogDescription.ts b/packages/nodes-base/nodes/TheHive/descriptions/LogDescription.ts
new file mode 100644
index 0000000000..73691719b8
--- /dev/null
+++ b/packages/nodes-base/nodes/TheHive/descriptions/LogDescription.ts
@@ -0,0 +1,262 @@
+import {
+ INodeProperties,
+} from 'n8n-workflow';
+
+export const logOperations = [
+ {
+ displayName: 'Operation',
+ name: 'operation',
+ type: 'options',
+ required: true,
+ default: 'getAll',
+ displayOptions: {
+ show: {
+ resource: [
+ 'log',
+ ],
+ },
+ },
+ options: [
+ {
+ name: 'Create',
+ value: 'create',
+ description: 'Create task log',
+ },
+ {
+ name: 'Execute Responder',
+ value: 'executeResponder',
+ description: 'Execute a responder on a selected log'
+ },
+ {
+ name: 'Get All',
+ value: 'getAll',
+ description: 'Get all task logs'
+ },
+ {
+ name: 'Get',
+ value: 'get',
+ description: 'Get a single log',
+ },
+ ],
+ }
+] as INodeProperties[];
+
+export const logFields = [
+ {
+ displayName: 'Task ID',
+ name: 'taskId',
+ type: 'string',
+ required: true,
+ default: '',
+ displayOptions: {
+ show: {
+ resource: [
+ 'log',
+ ],
+ operation: [
+ 'create',
+ 'getAll',
+ ],
+ },
+ },
+ description: 'ID of the task',
+ },
+ {
+ displayName: 'Return All',
+ name: 'returnAll',
+ type: 'boolean',
+ displayOptions: {
+ show: {
+ operation: [
+ 'getAll',
+ ],
+ resource: [
+ 'log',
+ ],
+ },
+ },
+ default: false,
+ description: 'If all results should be returned or only up to a given limit.',
+ },
+ {
+ displayName: 'Limit',
+ name: 'limit',
+ type: 'number',
+ displayOptions: {
+ show: {
+ operation: [
+ 'getAll',
+ ],
+ resource: [
+ 'log',
+ ],
+ returnAll: [
+ false,
+ ],
+ },
+ },
+ typeOptions: {
+ minValue: 1,
+ maxValue: 500,
+ },
+ default: 100,
+ description: 'How many results to return.',
+ },
+ // required attributs
+ {
+ displayName: 'Log ID',
+ name: 'id',
+ type: 'string',
+ required: true,
+ displayOptions: {
+ show: {
+ resource: [
+ 'log',
+ ],
+ operation: [
+ 'executeResponder',
+ 'get',
+ ],
+ },
+ },
+ },
+ {
+ displayName: 'Message',
+ name: 'message',
+ type: 'string',
+ required: true,
+ default: '',
+ displayOptions: {
+ show: {
+ resource: [
+ 'log',
+ ],
+ operation: [
+ 'create',
+ ],
+ },
+ },
+ description: 'Content of the Log',
+ },
+ {
+ displayName: 'Start Date',
+ name: 'startDate',
+ type: 'dateTime',
+ required: true,
+ default: '',
+ displayOptions: {
+ show: {
+ resource: [
+ 'log',
+ ],
+ operation: [
+ 'create',
+ ],
+ },
+ },
+ description: 'Date of the log submission default=now',
+ },
+ {
+ displayName: 'Status',
+ name: 'status',
+ type: 'options',
+ options: [
+ {
+ name: 'Ok',
+ value: 'Ok',
+ },
+ {
+ name: 'Deleted',
+ value: 'Deleted',
+ },
+ ],
+ default: '',
+ required: true,
+ displayOptions: {
+ show: {
+ resource: [
+ 'log',
+ ],
+ operation: [
+ 'create',
+ ],
+ },
+ },
+ description: 'Status of the log (Ok or Deleted) default=Ok',
+ },
+ // required for responder execution
+ {
+ displayName: 'Responder ID',
+ name: 'responder',
+ type: 'options',
+ required: true,
+ default: '',
+ typeOptions: {
+ loadOptionsDependsOn: [
+ 'id',
+ ],
+ loadOptionsMethod: 'loadResponders'
+ },
+ displayOptions: {
+ show: {
+ resource: [
+ 'log',
+ ],
+ operation: [
+ 'executeResponder',
+ ],
+ },
+ hide: {
+ id: [
+ '',
+ ],
+ },
+ },
+ },
+ // Optional attributs
+ {
+ displayName: 'Options',
+ name: 'options',
+ type: 'collection',
+ default: {},
+ displayOptions: {
+ show: {
+ resource: [
+ 'log',
+ ],
+ operation: [
+ 'create',
+ ],
+ },
+ },
+ placeholder: 'Add Option',
+ options: [
+ {
+ displayName: 'Attachment',
+ name: 'attachmentValues',
+ placeholder: 'Add Attachment',
+ type: 'fixedCollection',
+ typeOptions: {
+ multipleValues: false,
+ },
+ default: {},
+ options: [
+ {
+ displayName: 'Attachment',
+ name: 'attachmentValues',
+ values: [
+ {
+ displayName: 'Binary Property',
+ name: 'binaryProperty',
+ type: 'string',
+ default: 'data',
+ description: 'Object property name which holds binary data.',
+ },
+ ],
+ },
+ ],
+ description: 'File attached to the log',
+ },
+ ],
+ }
+] as INodeProperties[];
diff --git a/packages/nodes-base/nodes/TheHive/descriptions/ObservableDescription.ts b/packages/nodes-base/nodes/TheHive/descriptions/ObservableDescription.ts
new file mode 100644
index 0000000000..36e814db7b
--- /dev/null
+++ b/packages/nodes-base/nodes/TheHive/descriptions/ObservableDescription.ts
@@ -0,0 +1,787 @@
+import {
+ INodeProperties,
+} from 'n8n-workflow';
+
+import {
+ TLP,
+} from '../interfaces/AlertInterface';
+
+export const observableOperations = [
+ {
+ displayName: 'Operation',
+ name: 'operation',
+ type: 'options',
+ required: true,
+ default: 'getAll',
+ displayOptions: {
+ show: {
+ resource: [
+ 'observable',
+ ],
+ },
+ },
+ typeOptions: {
+ loadOptionsDependsOn: [
+ 'resource',
+ ],
+ loadOptionsMethod: 'loadObservableOptions',
+ },
+ },
+] as INodeProperties[];
+
+export const observableFields = [
+ {
+ displayName: 'Case ID',
+ name: 'caseId',
+ type: 'string',
+ required: true,
+ default: '',
+ displayOptions: {
+ show: {
+ resource: [
+ 'observable',
+ ],
+ operation: [
+ 'create',
+ 'getAll',
+ ],
+ },
+ },
+ description: 'ID of the case',
+ },
+ {
+ displayName: 'Return All',
+ name: 'returnAll',
+ type: 'boolean',
+ displayOptions: {
+ show: {
+ operation: [
+ 'getAll',
+ 'search',
+ ],
+ resource: [
+ 'observable',
+ ],
+ },
+ },
+ default: false,
+ description: 'If all results should be returned or only up to a given limit.',
+ },
+ {
+ displayName: 'Limit',
+ name: 'limit',
+ type: 'number',
+ displayOptions: {
+ show: {
+ operation: [
+ 'getAll',
+ 'search',
+ ],
+ resource: [
+ 'observable',
+ ],
+ returnAll: [
+ false,
+ ],
+ },
+ },
+ typeOptions: {
+ minValue: 1,
+ maxValue: 500,
+ },
+ default: 100,
+ description: 'How many results to return.',
+ },
+ // required attributs
+ {
+ displayName: 'Observable ID',
+ name: 'id',
+ type: 'string',
+ required: true,
+ default: '',
+ displayOptions: {
+ show: {
+ resource: [
+ 'observable',
+ ],
+ operation: [
+ 'update',
+ 'executeResponder',
+ 'executeAnalyzer',
+ 'get',
+ ],
+ },
+ },
+ description: 'ID of the observable',
+ },
+ {
+ displayName: 'Data Type',
+ name: 'dataType',
+ type: 'options',
+ required: true,
+ default: '',
+ options: [
+ {
+ name: 'domain',
+ value: 'domain',
+ },
+ {
+ name: 'file',
+ value: 'file'
+ },
+ {
+ name: 'filename',
+ value: 'filename'
+ },
+ {
+ name: 'fqdn',
+ value: 'fqdn'
+ },
+ {
+ name: 'hash',
+ value: 'hash'
+ },
+ {
+ name: 'ip',
+ value: 'ip'
+ },
+ {
+ name: 'mail',
+ value: 'mail'
+ },
+ {
+ name: 'mail_subject',
+ value: 'mail_subject'
+ },
+ {
+ name: 'other',
+ value: 'other'
+ },
+ {
+ name: 'regexp',
+ value: 'regexp'
+ },
+ {
+ name: 'registry',
+ value: 'registry'
+ },
+ {
+ name: 'uri_path',
+ value: 'uri_path'
+ },
+ {
+ name: 'url',
+ value: 'url'
+ },
+ {
+ name: 'user-agent',
+ value: 'user-agent'
+ },
+ ],
+ displayOptions: {
+ show: {
+ resource: [
+ 'observable',
+ ],
+ operation: [
+ 'create',
+ 'executeAnalyzer',
+ ],
+ },
+ },
+ description: 'Type of the observable',
+ },
+ {
+ displayName: 'Data',
+ name: 'data',
+ type: 'string',
+ required: true,
+ default: '',
+ displayOptions: {
+ show: {
+ resource: [
+ 'observable',
+ ],
+ operation: [
+ 'create',
+ ],
+ },
+ hide: {
+ dataType: [
+ 'file',
+ ],
+ },
+ },
+ },
+ {
+ displayName: 'Binary Property',
+ name: 'binaryProperty',
+ type: 'string',
+ required: true,
+ default: 'data',
+ description: 'Binary Property that represent the attachment file',
+ displayOptions: {
+ show: {
+ resource: [
+ 'observable',
+ ],
+ operation: [
+ 'create',
+ ],
+ dataType: [
+ 'file',
+ ],
+ },
+ },
+ },
+ {
+ displayName: 'Message',
+ name: 'message',
+ type: 'string',
+ required: true,
+ default: '',
+ displayOptions: {
+ show: {
+ resource: [
+ 'observable'
+ ],
+ operation: [
+ 'create',
+ ],
+ },
+ },
+ description: 'Description of the observable in the context of the case',
+ },
+ {
+ displayName: 'Start Date',
+ name: 'startDate',
+ type: 'dateTime',
+ required: true,
+ default: '',
+ displayOptions: {
+ show: {
+ resource: [
+ 'observable',
+ ],
+ operation: [
+ 'create',
+ ],
+ },
+ },
+ description: 'Date and time of the begin of the case default=now',
+ },
+ {
+ displayName: 'TLP',
+ name: 'tlp',
+ type: 'options',
+ required: true,
+ default: 2,
+ displayOptions: {
+ show: {
+ resource: [
+ 'observable',
+ ],
+ operation: [
+ 'create',
+ ],
+ },
+ },
+ options: [
+ {
+ name: 'White',
+ value: TLP.white,
+ },
+ {
+ name: 'Green',
+ value: TLP.green,
+ },
+ {
+ name: 'Amber',
+ value: TLP.amber,
+ },
+ {
+ name: 'Red',
+ value: TLP.red,
+ },
+ ],
+ description: 'Traffict Light Protocol (TLP). Default=Amber',
+ },
+ {
+ displayName: 'IOC',
+ name: 'ioc',
+ type: 'boolean',
+ required: true,
+ default: false,
+ displayOptions: {
+ show: {
+ resource: [
+ 'observable',
+ ],
+ operation: [
+ 'create',
+ ],
+ },
+ },
+ description: 'Indicates if the observable is an IOC (Indicator of compromise)',
+ },
+ {
+ displayName: 'Sighted',
+ name: 'sighted',
+ type: 'boolean',
+ required: true,
+ default: false,
+ displayOptions: {
+ show: {
+ resource: [
+ 'observable',
+ ],
+ operation: [
+ 'create',
+ ],
+ },
+ },
+ description: 'Sighted previously',
+ },
+ {
+ displayName: 'Status',
+ name: 'status',
+ type: 'options',
+ required: true,
+ default: '',
+ options: [
+ {
+ name: 'Ok',
+ value: 'Ok',
+ },
+ {
+ name: 'Deleted',
+ value: 'Deleted',
+ },
+ ],
+ displayOptions: {
+ show: {
+ resource: [
+ 'observable',
+ ],
+ operation: [
+ 'create',
+ ],
+ },
+ },
+ description: 'Status of the observable. Default=Ok',
+ },
+ // required for analyzer execution
+ {
+ displayName: 'Analyzer',
+ name: 'analyzers',
+ type: 'multiOptions',
+ required: true,
+ default: [],
+ typeOptions: {
+ loadOptionsDependsOn: [
+ 'id',
+ 'dataType',
+ ],
+ loadOptionsMethod: 'loadAnalyzers',
+ },
+ displayOptions: {
+ show: {
+ resource: [
+ 'observable',
+ ],
+ operation: [
+ 'executeAnalyzer',
+ ],
+ },
+ hide: {
+ id: [
+ '',
+ ],
+ },
+ },
+ },
+
+ // required for responder execution
+ {
+ displayName: 'Responder ID',
+ name: 'responder',
+ type: 'options',
+ required: true,
+ default: '',
+ typeOptions: {
+ loadOptionsDependsOn: [
+ 'id',
+ ],
+ loadOptionsMethod: 'loadResponders',
+ },
+ displayOptions: {
+ show: {
+ resource: [
+ 'observable',
+ ],
+ operation: [
+ 'executeResponder',
+ ],
+ },
+ hide: {
+ id: [
+ '',
+ ],
+ },
+ },
+ },
+ // Optional attributes (Create operation)
+ {
+ displayName: 'Options',
+ name: 'options',
+ type: 'collection',
+ placeholder: 'Add Option',
+ required: false,
+ default: '',
+ displayOptions: {
+ show: {
+ resource: [
+ 'observable',
+ ],
+ operation: [
+ 'create',
+ ],
+ },
+ },
+ options: [
+ {
+ displayName: 'Observable Tags',
+ name: 'tags',
+ type: 'string',
+ required: false,
+ default: '',
+ placeholder: 'tag1,tag2',
+ },
+ ],
+ },
+ // Optional attributes (Update operation)
+ {
+ displayName: 'Update Fields',
+ name: 'updateFields',
+ type: 'collection',
+ required: false,
+ default: '',
+ displayOptions: {
+ show: {
+ resource: [
+ 'observable',
+ ],
+ operation: [
+ 'update',
+ ],
+ },
+ },
+ options: [
+ {
+ displayName: 'Message',
+ name: 'message',
+ type: 'string',
+ default: '',
+ description: 'Description of the observable in the context of the case',
+
+ },
+ {
+ displayName: 'Observable Tags',
+ name: 'tags',
+ type: 'string',
+ default: '',
+ placeholder: 'tag1,tag2',
+ },
+ {
+ displayName: 'TLP',
+ name: 'tlp',
+ type: 'options',
+ default: 2,
+ options: [
+ {
+ name: 'White',
+ value: TLP.white,
+ },
+ {
+ name: 'Green',
+ value: TLP.green,
+ },
+ {
+ name: 'Amber',
+ value: TLP.amber,
+ },
+ {
+ name: 'Red',
+ value: TLP.red,
+ },
+ ],
+ description: 'Traffict Light Protocol (TLP). Default=Amber',
+ },
+ {
+ displayName: 'IOC',
+ name: 'ioc',
+ type: 'boolean',
+ default: false,
+ description: 'Indicates if the observable is an IOC (Indicator of compromise)',
+ },
+ {
+ displayName: 'Sighted',
+ name: 'sighted',
+ description: 'sighted previously',
+ type: 'boolean',
+ default: false,
+ },
+ {
+ displayName: 'Status',
+ name: 'status',
+ type: 'options',
+ default: '',
+ options: [
+ {
+ name: 'Ok',
+ value: 'Ok',
+ },
+ {
+ name: 'Deleted',
+ value: 'Deleted',
+ },
+ ],
+ description: 'Status of the observable. Default=Ok',
+ },
+ ],
+ },
+ // query options
+ {
+ displayName: 'Options',
+ name: 'options',
+ displayOptions: {
+ show: {
+ operation: [
+ 'getAll',
+ 'search',
+ ],
+ resource: [
+ 'observable',
+ ],
+ },
+ },
+ type: 'collection',
+ placeholder: 'Add Option',
+ default: {},
+ options: [
+ {
+ displayName: 'Sort',
+ name: 'sort',
+ type: 'string',
+ placeholder: '±Attribut, exp +status',
+ description: 'Specify the sorting attribut, + for asc, - for desc',
+ default: '',
+ },
+ ],
+ },
+ // query attributes
+ {
+ displayName: 'Filters',
+ name: 'filters',
+ type: 'collection',
+ required: false,
+ default: '',
+ placeholder: 'Add Filter',
+ displayOptions: {
+ show: {
+ resource: [
+ 'observable',
+ ],
+ operation: [
+ 'search',
+ 'count',
+ ],
+ },
+ },
+ options: [
+ {
+ displayName: 'Data Type',
+ name: 'dataType',
+ type: 'multiOptions',
+ default: [],
+ options: [
+ {
+ name: 'domain',
+ value: 'domain'
+ },
+ {
+ name: 'file',
+ value: 'file'
+ },
+ {
+ name: 'filename',
+ value: 'filename'
+ },
+ {
+ name: 'fqdn',
+ value: 'fqdn'
+ },
+ {
+ name: 'hash',
+ value: 'hash'
+ },
+ {
+ name: 'ip',
+ value: 'ip'
+ },
+ {
+ name: 'mail',
+ value: 'mail'
+ },
+ {
+ name: 'mail_subject',
+ value: 'mail_subject'
+ },
+ {
+ name: 'other',
+ value: 'other'
+ },
+ {
+ name: 'regexp',
+ value: 'regexp'
+ },
+ {
+ name: 'registry',
+ value: 'registry'
+ },
+ {
+ name: 'uri_path',
+ value: 'uri_path'
+ },
+ {
+ name: 'url',
+ value: 'url'
+ },
+ {
+ name: 'user-agent',
+ value: 'user-agent'
+ },
+ ],
+ description: 'Type of the observable',
+ },
+ {
+ displayName: 'Date range',
+ type: 'fixedCollection',
+ name: 'range',
+ default: {},
+ options: [
+ {
+ displayName: 'Add date range inputs',
+ name: 'dateRange',
+ values: [
+ {
+ displayName: 'From date',
+ name: 'fromDate',
+ type: 'dateTime',
+ required: false,
+ default: '',
+ },
+ {
+ displayName: 'To date',
+ name: 'toDate',
+ type: 'dateTime',
+ required: false,
+ default: '',
+ },
+ ],
+ },
+ ],
+ },
+ {
+ displayName: 'Description',
+ name: 'description',
+ type: 'string',
+ default: '',
+ placeholder: 'exp,freetext',
+ },
+ {
+ displayName: 'IOC',
+ name: 'ioc',
+ type: 'boolean',
+ default: false,
+ description: 'Indicates if the observable is an IOC (Indicator of compromise)',
+ },
+ {
+ displayName: 'Keyword',
+ name: 'keyword',
+ type: 'string',
+ default: '',
+ placeholder: 'exp,freetext',
+ },
+ {
+ displayName: 'Message',
+ name: 'message',
+ type: 'string',
+ default: '',
+ description: 'Description of the observable in the context of the case',
+ },
+ {
+ displayName: 'Observable Tags',
+ name: 'tags',
+ type: 'string',
+ default: '',
+ placeholder: 'tag1,tag2',
+ },
+ {
+ displayName: 'Sighted',
+ name: 'sighted',
+ type: 'boolean',
+ default: false,
+ },
+ {
+ name: 'Status',
+ displayName: 'Status',
+ type: 'options',
+ default: '',
+ options: [
+ {
+ name: 'Ok',
+ value: 'Ok',
+ },
+ {
+ name: 'Deleted',
+ value: 'Deleted',
+ },
+ ],
+ description: 'Status of the observable. Default=Ok',
+ },
+ {
+ displayName: 'TLP',
+ name: 'tlp',
+ type: 'options',
+ default: 2,
+ options: [
+ {
+ name: 'White',
+ value: TLP.white,
+ },
+ {
+ name: 'Green',
+ value: TLP.green,
+ },
+ {
+ name: 'Amber',
+ value: TLP.amber,
+ },
+ {
+ name: 'Red',
+ value: TLP.red,
+ },
+ ],
+ description: 'Traffict Light Protocol (TLP). Default=Amber',
+ },
+ {
+ displayName: 'Value',
+ name: 'data',
+ type: 'string',
+ default: '',
+ placeholder: 'example.com; 8.8.8.8',
+ },
+ ],
+ },
+] as INodeProperties[];
diff --git a/packages/nodes-base/nodes/TheHive/descriptions/TaskDescription.ts b/packages/nodes-base/nodes/TheHive/descriptions/TaskDescription.ts
new file mode 100644
index 0000000000..fad2f04e13
--- /dev/null
+++ b/packages/nodes-base/nodes/TheHive/descriptions/TaskDescription.ts
@@ -0,0 +1,468 @@
+import {
+ INodeProperties,
+} from 'n8n-workflow';
+
+export const taskOperations = [
+ {
+ displayName: 'Operation',
+ name: 'operation',
+ default: 'getAll',
+ type: 'options',
+ required: true,
+ displayOptions: {
+ show: {
+ resource: [
+ 'task',
+ ],
+ },
+ },
+ typeOptions: {
+ loadOptionsDependsOn: [
+ 'operation',
+ ],
+ loadOptionsMethod: 'loadTaskOptions',
+ },
+ },
+] as INodeProperties[];
+
+export const taskFields = [
+ {
+ displayName: 'Task ID',
+ name: 'id',
+ type: 'string',
+ required: true,
+ default: '',
+ displayOptions: {
+ show: {
+ resource: [
+ 'task',
+ ],
+ operation: [
+ 'update',
+ 'executeResponder',
+ 'get',
+ ],
+ },
+ },
+ description: 'ID of the taks',
+ },
+ {
+ displayName: 'Case ID',
+ name: 'caseId',
+ type: 'string',
+ required: true,
+ default: '',
+ displayOptions: {
+ show: {
+ resource: [
+ 'task',
+ ],
+ operation: [
+ 'create',
+ 'getAll',
+ ],
+ },
+ },
+ },
+ {
+ displayName: 'Return All',
+ name: 'returnAll',
+ type: 'boolean',
+ displayOptions: {
+ show: {
+ operation: [
+ 'search',
+ 'getAll',
+ ],
+ resource: [
+ 'task',
+ ],
+ },
+ },
+ default: false,
+ description: 'If all results should be returned or only up to a given limit.',
+ },
+ {
+ displayName: 'Limit',
+ name: 'limit',
+ type: 'number',
+ displayOptions: {
+ show: {
+ operation: [
+ 'search',
+ 'getAll',
+ ],
+ resource: [
+ 'task',
+ ],
+ returnAll: [
+ false,
+ ],
+ },
+ },
+ typeOptions: {
+ minValue: 1,
+ maxValue: 500,
+ },
+ default: 100,
+ description: 'How many results to return.',
+ },
+ {
+ displayName: 'Title',
+ name: 'title',
+ type: 'string',
+ required: true,
+ default: '',
+ displayOptions: {
+ show: {
+ resource: [
+ 'task',
+ ],
+ operation: [
+ 'create',
+ ],
+ },
+ },
+ description: 'Task details',
+ },
+ {
+ displayName: 'Status',
+ name: 'status',
+ type: 'options',
+ default: 'Waiting',
+ options: [
+ {
+ name: 'Waiting',
+ value: 'Waiting',
+ },
+ {
+ name: 'InProgress',
+ value: 'InProgress',
+ },
+ {
+ name: 'Completed',
+ value: 'Completed',
+ },
+ {
+ name: 'Cancel',
+ value: 'Cancel',
+ },
+ ],
+ required: true,
+ displayOptions: {
+ show: {
+ resource: [
+ 'task',
+ ],
+ operation: [
+ 'create',
+ ],
+ },
+ },
+ description: 'Status of the task. Default=Waiting',
+ },
+ {
+ displayName: 'Flag',
+ name: 'flag',
+ type: 'boolean',
+ required: true,
+ default: false,
+ displayOptions: {
+ show: {
+ resource: [
+ 'task',
+ ],
+ operation: [
+ 'create',
+ ],
+ },
+ },
+ description: 'Flag of the task. Default=false',
+ },
+ // required for responder execution
+ {
+ displayName: 'Responder ID',
+ name: 'responder',
+ type: 'options',
+ required: true,
+ default: '',
+ typeOptions: {
+ loadOptionsDependsOn: [
+ 'id',
+ ],
+ loadOptionsMethod: 'loadResponders',
+ },
+ displayOptions: {
+ show: {
+ resource: [
+ 'task',
+ ],
+ operation: [
+ 'executeResponder',
+ ],
+ },
+ hide: {
+ id: [
+ '',
+ ],
+ },
+ },
+ },
+ // optional attributes (Create operations)
+ {
+ displayName: 'Options',
+ type: 'collection',
+ name: 'options',
+ placeholder: 'Add Option',
+ required: false,
+ default: '',
+ displayOptions: {
+ show: {
+ resource: [
+ 'task',
+ ],
+ operation: [
+ 'create',
+ ],
+ },
+ },
+ options: [
+ {
+ displayName: 'Description',
+ name: 'description',
+ type: 'string',
+ default: '',
+ description: 'Task details',
+ },
+ {
+ displayName: 'End Date',
+ name: 'endDate',
+ type: 'dateTime',
+ default: '',
+ description: 'Date of the end of the task. This is automatically set when status is set to Completed',
+ },
+ {
+ displayName: 'Owner',
+ name: 'owner',
+ type: 'string',
+ default: '',
+ description: `User who owns the task. This is automatically set to current user when status is set to InProgress`,
+ },
+ {
+ displayName: 'Start Date',
+ name: 'startDate',
+ type: 'dateTime',
+ default: '',
+ description: 'Date of the beginning of the task. This is automatically set when status is set to Open',
+ },
+ ],
+ },
+ // optional attributes (Update operation)
+
+ {
+ displayName: 'Update Fields',
+ type: 'collection',
+ name: 'updateFields',
+ placeholder: 'Add Field',
+ default: '',
+ required: false,
+ displayOptions: {
+ show: {
+ resource: [
+ 'task',
+ ],
+ operation: [
+ 'update',
+ ],
+ },
+ },
+ options: [
+ {
+ displayName: 'Description',
+ name: 'description',
+ type: 'string',
+ default: '',
+ description: 'Task details',
+ },
+ {
+ displayName: 'End Date',
+ name: 'endDate',
+ type: 'dateTime',
+ default: '',
+ description: 'Date of the end of the task. This is automatically set when status is set to Completed',
+ },
+ {
+ displayName: 'Flag',
+ name: 'flag',
+ type: 'boolean',
+ default: false,
+ description: 'Flag of the task. Default=false',
+ },
+ {
+ displayName: 'Owner',
+ name: 'owner',
+ type: 'string',
+ default: '',
+ description: `User who owns the task. This is automatically set to current user when status is set to InProgress`,
+ },
+ {
+ displayName: 'Start Date',
+ name: 'startDate',
+ type: 'dateTime',
+ default: '',
+ description: 'Date of the beginning of the task. This is automatically set when status is set to Open',
+ },
+ {
+ displayName: 'status',
+ name: 'status',
+ type: 'options',
+ default: 'Waiting',
+ options: [
+ {
+ name: 'Waiting',
+ value: 'Waiting',
+ },
+ {
+ name: 'In Progress',
+ value: 'InProgress',
+ },
+ {
+ name: 'Completed',
+ value: 'Completed',
+ },
+ {
+ name: 'Cancel',
+ value: 'Cancel',
+ },
+ ],
+ description: 'Status of the task. Default=Waiting',
+ },
+ {
+ displayName: 'Title',
+ name: 'title',
+ type: 'string',
+ default: '',
+ description: 'Task details',
+ },
+ ],
+ },
+
+ // query options
+ {
+ displayName: 'Options',
+ name: 'options',
+ placeholder: 'Add Option',
+ type: 'collection',
+ default: {},
+ displayOptions: {
+ show: {
+ operation: [
+ 'getAll',
+ 'search',
+ ],
+ resource: [
+ 'task',
+ ],
+ },
+ },
+ options: [
+ {
+ displayName: 'Sort',
+ name: 'sort',
+ type: 'string',
+ placeholder: '±Attribut, exp +status',
+ description: 'Specify the sorting attribut, + for asc, - for desc',
+ default: '',
+ },
+ ],
+ },
+ // query attributes
+ {
+ displayName: 'Filters',
+ name: 'filters',
+ type: 'collection',
+ required: false,
+ default: {},
+ placeholder: 'Add Filter',
+ displayOptions: {
+ show: {
+ resource: [
+ 'task',
+ ],
+ operation: [
+ 'search',
+ 'count',
+ ],
+ },
+ },
+ options: [
+ {
+ displayName: 'Description',
+ name: 'description',
+ type: 'string',
+ default: '',
+ description: 'Task details',
+ },
+ {
+ displayName: 'End Date',
+ name: 'endDate',
+ type: 'dateTime',
+ default: '',
+ description: 'Date of the end of the task. This is automatically set when status is set to Completed',
+ },
+ {
+ displayName: 'Flag',
+ name: 'flag',
+ type: 'boolean',
+ default: false,
+ description: 'Flag of the task. Default=false',
+ },
+ {
+ displayName: 'Owner',
+ name: 'owner',
+ type: 'string',
+ default: '',
+ description: `User who owns the task. This is automatically set to current user when status is set to InProgress`,
+ },
+ {
+ displayName: 'Start Date',
+ name: 'startDate',
+ type: 'dateTime',
+ default: '',
+ description: 'Date of the beginning of the task. This is automatically set when status is set to Open',
+ },
+ {
+ displayName: 'Status',
+ name: 'status',
+ type: 'options',
+ default: 'Waiting',
+ options: [
+ {
+ name: 'Waiting',
+ value: 'Waiting',
+ },
+ {
+ name: 'In Progress',
+ value: 'InProgress'
+ },
+ {
+ name: 'Completed',
+ value: 'Completed'
+ },
+ {
+ name: 'Cancel',
+ value: 'Cancel'
+ },
+ ],
+ description: 'Status of the task. Default=Waiting',
+ },
+ {
+ displayName: 'Title',
+ name: 'title',
+ type: 'string',
+ default: '',
+ description: 'Task details',
+ },
+ ],
+ },
+] as INodeProperties[];
diff --git a/packages/nodes-base/nodes/TheHive/interfaces/AlertInterface.ts b/packages/nodes-base/nodes/TheHive/interfaces/AlertInterface.ts
new file mode 100644
index 0000000000..873d5bf927
--- /dev/null
+++ b/packages/nodes-base/nodes/TheHive/interfaces/AlertInterface.ts
@@ -0,0 +1,44 @@
+import {
+ IDataObject
+}from 'n8n-workflow'
+export enum AlertStatus{
+ NEW="New",
+ UPDATED="Updated",
+ IGNORED="Ignored",
+ IMPORTED="Imported",
+}
+export enum TLP{
+ white,
+ green,
+ amber,
+ red
+}
+
+export interface IAlert{
+ // Required attributes
+ id?:string;
+ title?:string;
+ description?:string;
+ severity?:number;
+ date?:Date;
+ tags?:string[];
+ tlp?:TLP;
+ status?:AlertStatus;
+ type?:string;
+ source?:string;
+ sourceRef?:string;
+ artifacts?:IDataObject[];
+ follow?:boolean;
+
+ // Optional attributes
+ caseTemplate?:string;
+
+ // Backend generated attributes
+ lastSyncDate?:Date;
+ case?:string;
+
+ createdBy?:string;
+ createdAt?:Date;
+ updatedBy?:string;
+ upadtedAt?:Date;
+}
diff --git a/packages/nodes-base/nodes/TheHive/interfaces/CaseInterface.ts b/packages/nodes-base/nodes/TheHive/interfaces/CaseInterface.ts
new file mode 100644
index 0000000000..88781526ff
--- /dev/null
+++ b/packages/nodes-base/nodes/TheHive/interfaces/CaseInterface.ts
@@ -0,0 +1,53 @@
+import { IDataObject } from "n8n-workflow";
+import { TLP } from './AlertInterface';
+export interface ICase{
+ // Required attributes
+ id?:string;
+ title?:string;
+ description?:string;
+ severity?:number;
+ startDate?:Date;
+ owner?:string;
+ flag?:boolean;
+ tlp?:TLP;
+ tags?:string[];
+
+ // Optional attributes
+ resolutionStatus?:CaseResolutionStatus;
+ impactStatus?:CaseImpactStatus;
+ summary?:string;
+ endDate?:Date;
+ metrics?:IDataObject;
+
+ // Backend generated attributes
+ status?:CaseStatus;
+ caseId?:number; // auto-generated attribute
+ mergeInto?:string;
+ mergeFrom?:string[];
+
+ createdBy?:string;
+ createdAt?:Date;
+ updatedBy?:string;
+ upadtedAt?:Date;
+}
+
+
+export enum CaseStatus{
+ OPEN="Open",
+ RESOLVED="Resolved",
+ DELETED="Deleted",
+}
+
+export enum CaseResolutionStatus{
+ INDETERMINATE="Indeterminate",
+ FALSEPOSITIVE="FalsePositive",
+ TRUEPOSITIVE="TruePositive",
+ OTHER="Other",
+ DUPLICATED="Duplicated",
+}
+
+export enum CaseImpactStatus{
+ NOIMPACT="NoImpact",
+ WITHIMPACT="WithImpact",
+ NOTAPPLICABLE="NotApplicable",
+}
\ No newline at end of file
diff --git a/packages/nodes-base/nodes/TheHive/interfaces/LogInterface.ts b/packages/nodes-base/nodes/TheHive/interfaces/LogInterface.ts
new file mode 100644
index 0000000000..a2e957313f
--- /dev/null
+++ b/packages/nodes-base/nodes/TheHive/interfaces/LogInterface.ts
@@ -0,0 +1,23 @@
+import { IDataObject } from "n8n-workflow";
+import {IAttachment} from "./ObservableInterface";
+export enum LogStatus{
+ OK="Ok",
+ DELETED="Deleted"
+}
+export interface ILog{
+ // Required attributes
+ id?:string;
+ message?:string;
+ startDate?:Date;
+ status?:LogStatus;
+
+ // Optional attributes
+ attachment?:IAttachment;
+
+ // Backend generated attributes
+
+ createdBy?:string;
+ createdAt?:Date;
+ updatedBy?:string;
+ upadtedAt?:Date;
+}
\ No newline at end of file
diff --git a/packages/nodes-base/nodes/TheHive/interfaces/ObservableInterface.ts b/packages/nodes-base/nodes/TheHive/interfaces/ObservableInterface.ts
new file mode 100644
index 0000000000..ef4ca93101
--- /dev/null
+++ b/packages/nodes-base/nodes/TheHive/interfaces/ObservableInterface.ts
@@ -0,0 +1,54 @@
+import {
+ TLP
+}from './AlertInterface'
+import { IDataObject } from 'n8n-workflow';
+
+export enum ObservableStatus{
+ OK="Ok",
+ DELETED="Deleted",
+}
+export enum ObservableDataType{
+ "domain"= "domain",
+ "file"= "file",
+ "filename"= "filename",
+ "fqdn"= "fqdn",
+ "hash"= "hash",
+ "ip"= "ip",
+ "mail"= "mail",
+ "mail_subject"= "mail_subject",
+ "other"= "other",
+ "regexp"= "regexp",
+ "registry"= "registry",
+ "uri_path"= "uri_path",
+ "url"= "url",
+ "user-agent"= "user-agent"
+}
+
+export interface IAttachment{
+ name?:string;
+ size?:number;
+ id?:string;
+ contentType?:string;
+ hashes:string[];
+}
+export interface IObservable{
+ // Required attributes
+ id?:string;
+ data?:string;
+ attachment?:IAttachment;
+ dataType?:ObservableDataType;
+ message?:string;
+ startDate?:Date;
+ tlp?:TLP;
+ ioc?:boolean;
+ status?:ObservableStatus;
+ // Optional attributes
+ tags:string[];
+ // Backend generated attributes
+
+ createdBy?:string;
+ createdAt?:Date;
+ updatedBy?:string;
+ upadtedAt?:Date;
+
+}
diff --git a/packages/nodes-base/nodes/TheHive/interfaces/TaskInterface.ts b/packages/nodes-base/nodes/TheHive/interfaces/TaskInterface.ts
new file mode 100644
index 0000000000..4008f214c0
--- /dev/null
+++ b/packages/nodes-base/nodes/TheHive/interfaces/TaskInterface.ts
@@ -0,0 +1,25 @@
+export interface ITask{
+ // Required attributes
+ id?:string;
+ title?:string;
+ status?:TaskStatus;
+ flag?:boolean;
+ // Optional attributes
+ owner?:string;
+ description?:string;
+ startDate?:Date;
+ endDate?:Date;
+ // Backend generated attributes
+
+ createdBy?:string;
+ createdAt?:Date;
+ updatedBy?:string;
+ upadtedAt?:Date;
+}
+
+export enum TaskStatus{
+ WAITING="Waiting",
+ INPROGRESS="InProgress",
+ COMPLETED="Completed",
+ CANCEL="Cancel",
+}
diff --git a/packages/nodes-base/nodes/TheHive/thehive.png b/packages/nodes-base/nodes/TheHive/thehive.png
new file mode 100644
index 0000000000..324e26df93
Binary files /dev/null and b/packages/nodes-base/nodes/TheHive/thehive.png differ
diff --git a/packages/nodes-base/package.json b/packages/nodes-base/package.json
index d6a107c45c..c935133f0f 100644
--- a/packages/nodes-base/package.json
+++ b/packages/nodes-base/package.json
@@ -54,6 +54,7 @@
"dist/credentials/ContentfulApi.credentials.js",
"dist/credentials/ConvertKitApi.credentials.js",
"dist/credentials/CopperApi.credentials.js",
+ "dist/credentials/CortexApi.credentials.js",
"dist/credentials/CalendlyApi.credentials.js",
"dist/credentials/CustomerIoApi.credentials.js",
"dist/credentials/S3.credentials.js",
@@ -195,6 +196,7 @@
"dist/credentials/TaigaCloudApi.credentials.js",
"dist/credentials/TaigaServerApi.credentials.js",
"dist/credentials/TelegramApi.credentials.js",
+ "dist/credentials/TheHiveApi.credentials.js",
"dist/credentials/TodoistApi.credentials.js",
"dist/credentials/TodoistOAuth2Api.credentials.js",
"dist/credentials/TravisCiApi.credentials.js",
@@ -265,6 +267,7 @@
"dist/nodes/ConvertKit/ConvertKit.node.js",
"dist/nodes/ConvertKit/ConvertKitTrigger.node.js",
"dist/nodes/Copper/CopperTrigger.node.js",
+ "dist/nodes/Cortex/Cortex.node.js",
"dist/nodes/CrateDb/CrateDb.node.js",
"dist/nodes/Cron.node.js",
"dist/nodes/Crypto.node.js",
@@ -420,6 +423,8 @@
"dist/nodes/Taiga/TaigaTrigger.node.js",
"dist/nodes/Telegram/Telegram.node.js",
"dist/nodes/Telegram/TelegramTrigger.node.js",
+ "dist/nodes/TheHive/TheHive.node.js",
+ "dist/nodes/TheHive/TheHiveTrigger.node.js",
"dist/nodes/Todoist/Todoist.node.js",
"dist/nodes/Toggl/TogglTrigger.node.js",
"dist/nodes/TravisCi/TravisCi.node.js",