diff --git a/packages/nodes-base/credentials/SecurityScorecardApi.credentials.ts b/packages/nodes-base/credentials/SecurityScorecardApi.credentials.ts new file mode 100644 index 0000000000..e1eff983dc --- /dev/null +++ b/packages/nodes-base/credentials/SecurityScorecardApi.credentials.ts @@ -0,0 +1,18 @@ +import { + ICredentialType, + NodePropertyTypes, +} from 'n8n-workflow'; + +export class SecurityScorecardApi implements ICredentialType { + name = 'securityScorecardApi'; + displayName = 'SecurityScorecard API'; + properties = [ + { + displayName: 'API Key', + name: 'apiKey', + type: 'string' as NodePropertyTypes, + default: '', + required: true, + }, + ]; +} diff --git a/packages/nodes-base/nodes/SecurityScorecard/GenericFunctions.ts b/packages/nodes-base/nodes/SecurityScorecard/GenericFunctions.ts new file mode 100644 index 0000000000..d8d5c0fa7c --- /dev/null +++ b/packages/nodes-base/nodes/SecurityScorecard/GenericFunctions.ts @@ -0,0 +1,64 @@ +import { + OptionsWithUri, +} from 'request'; + +import { + IExecuteFunctions, + IHookFunctions, + ILoadOptionsFunctions, +} from 'n8n-core'; + +import { + IDataObject, +} from 'n8n-workflow'; + +export async function scorecardApiRequest(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('securityScorecardApi'); + + if (credentials === undefined) { + throw new Error('No credentials got returned!'); + } + + const headerWithAuthentication = { Authorization: `Token ${credentials.apiKey}` }; + + let options: OptionsWithUri = { + headers: headerWithAuthentication, + method, + qs: query, + uri: uri || `https://api.securityscorecard.io/${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 = `SecurityScorecard error response [${error.statusCode}]: ${error.error.error ? error.error.error.message : error.error}`; + throw new Error(errorMessage); + } else throw error; + } +} + +export function simplify(data: IDataObject[]) { + const results = []; + for (const record of data) { + const newRecord: IDataObject = { date: record.date }; + for (const factor of record.factors as IDataObject[]) { + newRecord[factor.name as string] = factor.score; + } + results.push(newRecord); + } + return results; +} diff --git a/packages/nodes-base/nodes/SecurityScorecard/SecurityScorecard.node.ts b/packages/nodes-base/nodes/SecurityScorecard/SecurityScorecard.node.ts new file mode 100644 index 0000000000..08c770626f --- /dev/null +++ b/packages/nodes-base/nodes/SecurityScorecard/SecurityScorecard.node.ts @@ -0,0 +1,568 @@ +import { + IExecuteFunctions +} from 'n8n-core'; + +import { + IDataObject, + INodeExecutionData, + INodeType, + INodeTypeDescription, +} from 'n8n-workflow'; + +import { + companyFields, + companyOperations, +} from './descriptions/CompanyDescription'; + +import { + industryFields, + industryOperations, +} from './descriptions/IndustryDescription'; + +import { + inviteFields, + inviteOperations, +} from './descriptions/InviteDescription'; + +import { + portfolioFields, + portfolioOperations, +} from './descriptions/PortfolioDescription'; + +import { + portfolioCompanyFields, + portfolioCompanyOperations, +} from './descriptions/PortfolioCompanyDescription'; + +import { + reportFields, + reportOperations, +} from './descriptions/ReportDescription'; + +import { + scorecardApiRequest, + simplify, +} from './GenericFunctions'; + +import * as moment from 'moment'; + +export class SecurityScorecard implements INodeType { + description: INodeTypeDescription = { + displayName: 'SecurityScorecard', + name: 'securityScorecard', + icon: 'file:securityScorecard.svg', + group: ['transform'], + subtitle: '={{$parameter["operation"]}} : {{$parameter["resource"]}}', + version: 1, + description: 'Consume SecurityScorecard API', + defaults: { + name: 'SecurityScorecard', + color: '#619e73', + }, + inputs: ['main'], + outputs: ['main'], + credentials: [ + { + name: 'securityScorecardApi', + required: true, + }, + ], + properties: [ + { + displayName: 'Resource', + name: 'resource', + type: 'options', + required: true, + options: [ + { + name: 'Company', + value: 'company', + }, + { + name: 'Industry', + value: 'industry', + }, + { + name: 'Invite', + value: 'invite', + }, + { + name: 'Portfolio', + value: 'portfolio', + }, + { + name: 'Portfolio Company', + value: 'portfolioCompany', + }, + { + name: 'Report', + value: 'report', + }, + ], + default: 'company', + }, + // Company + ...companyOperations, + ...companyFields, + // Industry + ...industryOperations, + ...industryFields, + // Invite + ...inviteOperations, + ...inviteFields, + // Portfolio + ...portfolioOperations, + ...portfolioFields, + // Portfolio Company + ...portfolioCompanyOperations, + ...portfolioCompanyFields, + // Report + ...reportOperations, + ...reportFields, + ], + }; + + async execute(this: IExecuteFunctions): Promise { + const items = this.getInputData(); + const returnData: IDataObject[] = []; + let responseData; + const length = (items.length as unknown) as number; + + 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 === 'portfolio') { + + if (operation === 'create') { + const name = this.getNodeParameter('name', i) as string; + const description = this.getNodeParameter('description', i) as string; + const privacy = this.getNodeParameter('privacy', i) as string; + + const body: IDataObject = { + name, + description, + privacy, + }; + + responseData = await scorecardApiRequest.call( + this, + 'POST', + 'portfolios', + body, + ); + returnData.push(responseData as IDataObject); + } + + if (operation === 'delete') { + const portfolioId = this.getNodeParameter('portfolioId', i) as string; + responseData = await scorecardApiRequest.call( + this, + 'DELETE', + `portfolios/${portfolioId}`, + ); + returnData.push({ success: true }); + } + + if (operation === 'update') { + const portfolioId = this.getNodeParameter('portfolioId', i) as string; + const name = this.getNodeParameter('name', i) as string; + const description = this.getNodeParameter('description', i) as string; + const privacy = this.getNodeParameter('privacy', i) as string; + + const body: IDataObject = { + name, + description, + privacy, + }; + + responseData = await scorecardApiRequest.call( + this, + 'PUT', + `portfolios/${portfolioId}`, + body, + ); + returnData.push(responseData as IDataObject); + } + + if (operation === 'getAll') { + const returnAll = this.getNodeParameter('returnAll', 0) as boolean; + responseData = await scorecardApiRequest.call( + this, + 'GET', + 'portfolios', + ); + responseData = responseData.entries; + + if (returnAll === false) { + const limit = this.getNodeParameter('limit', 0) as number; + responseData = responseData.splice(0, limit); + } + + returnData.push.apply(returnData, responseData as IDataObject[]); + } + } + + if (resource === 'portfolioCompany') { + if (operation === 'add') { + const portfolioId = this.getNodeParameter('portfolioId', i) as string; + const domain = this.getNodeParameter('domain', i); + responseData = await scorecardApiRequest.call( + this, + 'PUT', + `portfolios/${portfolioId}/companies/${domain}`, + ); + returnData.push(responseData as IDataObject); + } + + if (operation === 'remove') { + const portfolioId = this.getNodeParameter('portfolioId', i) as string; + const domain = this.getNodeParameter('domain', i); + responseData = await scorecardApiRequest.call( + this, + 'DELETE', + `portfolios/${portfolioId}/companies/${domain}`, + ); + returnData.push({ success: true }); + } + + if (operation === 'getAll') { + const returnAll = this.getNodeParameter('returnAll', 0) as boolean; + const portfolioId = this.getNodeParameter('portfolioId', i) as string; + const filterParams = this.getNodeParameter('filters', i) as IDataObject; + responseData = await scorecardApiRequest.call( + this, + 'GET', + `portfolios/${portfolioId}/companies`, + {}, + filterParams, + ); + + responseData = responseData.entries; + + if (returnAll === false) { + const limit = this.getNodeParameter('limit', 0) as boolean; + responseData = responseData.splice(0, limit); + } + + returnData.push.apply(returnData, responseData as IDataObject[]); + } + } + + if (resource === 'report') { + if (operation === 'download') { + const reportUrl = this.getNodeParameter('url', i) as string; + + const response = await scorecardApiRequest.call( + this, + 'GET', + '', + {}, + {}, + reportUrl, + { encoding: null, resolveWithFullResponse: true }); + + let mimeType: string | undefined; + if (response.headers['content-type']) { + mimeType = response.headers['content-type']; + } + + const newItem: INodeExecutionData = { + json: items[i].json, + binary: {}, + }; + + if (items[i].binary !== undefined) { + // Create a shallow copy of the binary data so that the old + // data references which do not get changed still stay behind + // but the incoming data does not get changed. + Object.assign(newItem.binary, items[i].binary); + } + + items[i] = newItem; + + const dataPropertyNameDownload = this.getNodeParameter('binaryPropertyName', i) as string; + + const fileName = reportUrl.split('/').pop(); + + const data = Buffer.from(response.body as string, 'utf8'); + items[i].binary![dataPropertyNameDownload] = await this.helpers.prepareBinaryData(data as unknown as Buffer, fileName, mimeType); + } + + if (operation === 'generate') { + const reportType = this.getNodeParameter('report', i) as string; + let body: IDataObject = {}; + + if (reportType !== 'portfolio') { + body['scorecard_identifier'] = this.getNodeParameter('scorecardIdentifier', i); + } else { + body['portfolio_id'] = this.getNodeParameter('portfolioId', i); + } + if (reportType === 'events-json') { + body['date'] = this.getNodeParameter('date', i); + } + if (['issues', 'portfolio'].indexOf(reportType) > -1) { + body['format'] = this.getNodeParameter('options.format', i) || 'pdf'; + } + if (['detailed', 'summary'].indexOf(reportType) > -1) { + body['branding'] = this.getNodeParameter('branding', i); + } + // json reports want the params differently + if (['events-json', 'full-scorecard-json'].indexOf(reportType) > -1) { + body = { params: body }; + } + if (reportType === 'scorecard-footprint') { + const options = this.getNodeParameter('options', i) as IDataObject; + Object.assign(body, options); + } + + responseData = await scorecardApiRequest.call( + this, + 'POST', + `reports/${reportType}`, + body, + ); + + returnData.push(responseData as IDataObject); + } + + if (operation === 'getAll') { + const returnAll = this.getNodeParameter('returnAll', 0) as boolean; + responseData = await scorecardApiRequest.call( + this, + 'GET', + 'reports/recent', + ); + responseData = responseData.entries; + + if (returnAll === false) { + const limit = this.getNodeParameter('limit', i) as number; + responseData = responseData.splice(0, limit); + } + returnData.push.apply(returnData, responseData as IDataObject[]); + } + } + + if (resource === 'invite') { + if (operation === 'create') { + const body: IDataObject = { + email: this.getNodeParameter('email', i), + first_name: this.getNodeParameter('firstName', i), + last_name: this.getNodeParameter('lastName', i), + message: this.getNodeParameter('message', i), + }; + const additionalFields = this.getNodeParameter('additionalFields', i); + Object.assign(body, additionalFields); + + responseData = await scorecardApiRequest.call( + this, + 'POST', + `invitations`, + body, + ); + returnData.push(responseData as IDataObject); + } + } + + if (resource === 'industry') { + if (operation === 'getScore') { + const industry = this.getNodeParameter('industry', i); + responseData = await scorecardApiRequest.call( + this, + 'GET', + `industries/${industry}/score`, + ); + returnData.push(responseData as IDataObject); + } + + if (operation === 'getFactor') { + const simple = this.getNodeParameter('simple', 0) as boolean; + const returnAll = this.getNodeParameter('returnAll', 0) as boolean; + const industry = this.getNodeParameter('industry', i); + responseData = await scorecardApiRequest.call( + this, + 'GET', + `industries/${industry}/history/factors`, + ); + responseData = responseData.entries; + + if (returnAll === false) { + const limit = this.getNodeParameter('limit', i) as number; + responseData = responseData.splice(0, limit); + } + + if (simple === true) { + responseData = simplify(responseData); + } + + returnData.push.apply(returnData, responseData as IDataObject[]); + } + + if (operation === 'getFactorHistorical') { + const simple = this.getNodeParameter('simple', 0) as boolean; + const returnAll = this.getNodeParameter('returnAll', i) as boolean; + const industry = this.getNodeParameter('industry', i); + const options = this.getNodeParameter('options', i) as IDataObject; + // Convert to YYYY-MM-DD + if (options['from']) { + options['from'] = moment(options['from'] as Date).format('YYYY-MM-DD'); + } + + if (options['to']) { + options['to'] = moment(options['to'] as Date).format('YYYY-MM-DD'); + } + responseData = await scorecardApiRequest.call( + this, + 'GET', + `industries/${industry}/history/factors`, + {}, + options, + ); + responseData = responseData.entries; + + if (returnAll === false) { + const limit = this.getNodeParameter('limit', i) as number; + responseData = responseData.splice(0, limit); + } + + if (simple === true) { + responseData = simplify(responseData); + } + + returnData.push.apply(returnData, responseData as IDataObject[]); + } + } + + if (resource === 'company') { + if (operation === 'getScorecard') { + const scorecardIdentifier = this.getNodeParameter('scorecardIdentifier', i) as string; + responseData = await scorecardApiRequest.call( + this, + 'GET', + `companies/${scorecardIdentifier}`, + ); + returnData.push(responseData as IDataObject); + } + + if (operation === 'getFactor') { + const returnAll = this.getNodeParameter('returnAll', i) as boolean; + const scorecardIdentifier = this.getNodeParameter('scorecardIdentifier', i); + const filterParams = this.getNodeParameter('filters', i) as IDataObject; + responseData = await scorecardApiRequest.call( + this, + 'GET', + `companies/${scorecardIdentifier}/factors`, + {}, + filterParams, + ); + + responseData = responseData.entries; + + if (returnAll === false) { + const limit = this.getNodeParameter('limit', i) as number; + responseData = responseData.splice(0, limit); + } + + returnData.push.apply(returnData, responseData as IDataObject[]); + } + + if (operation === 'getFactorHistorical') { + const simple = this.getNodeParameter('simple', 0) as boolean; + const returnAll = this.getNodeParameter('returnAll', i) as boolean; + const scorecardIdentifier = this.getNodeParameter('scorecardIdentifier', i) as string; + const options = this.getNodeParameter('options', i) as IDataObject; + // Convert to YYYY-MM-DD + if (options['date_from']) { + options['date_from'] = moment(options['date_from'] as Date).format('YYYY-MM-DD'); + } + + if (options['date_to']) { + options['date_to'] = moment(options['date_to'] as Date).format('YYYY-MM-DD'); + } + responseData = await scorecardApiRequest.call( + this, + 'GET', + `companies/${scorecardIdentifier}/history/factors/score`, + {}, + options, + ); + + responseData = responseData.entries; + + if (returnAll === false) { + const limit = this.getNodeParameter('limit', i) as number; + responseData = responseData.splice(0, limit); + } + + if (simple === true) { + responseData = simplify(responseData); + } + + returnData.push.apply(returnData, responseData as IDataObject[]); + } + + if (operation === 'getHistoricalScore') { + const simple = this.getNodeParameter('simple', 0) as boolean; + const returnAll = this.getNodeParameter('returnAll', i) as boolean; + const scorecardIdentifier = this.getNodeParameter('scorecardIdentifier', i); + const options = this.getNodeParameter('options', i) as IDataObject; + + // for some reason the params are different between these two APis :/ + if (options['date_from']) { + options['from'] = moment(options['date_from'] as Date).format('YYYY-MM-DD'); + delete options['date_from']; + } + if (options['date_to']) { + options['to'] = moment(options['date_to'] as Date).format('YYYY-MM-DD'); + delete options['date_to']; + } + responseData = await scorecardApiRequest.call( + this, + 'GET', + `companies/${scorecardIdentifier}/history/factors/score`, + {}, + options, + ); + responseData = responseData.entries; + + if (returnAll === false) { + const limit = this.getNodeParameter('limit', i) as number; + responseData = responseData.splice(0, limit); + } + + if (simple === true) { + responseData = simplify(responseData); + } + + returnData.push.apply(returnData, responseData as IDataObject[]); + } + + if (operation === 'getScorePlan') { + const returnAll = this.getNodeParameter('returnAll', i) as boolean; + const scorecardIdentifier = this.getNodeParameter('scorecardIdentifier', i) as string; + const targetScore = this.getNodeParameter('score', i); + responseData = await scorecardApiRequest.call( + this, + 'GET', + `companies/${scorecardIdentifier}/score-plans/by-target/${targetScore}`, + ); + + responseData = responseData.entries; + + if (returnAll === false) { + const limit = this.getNodeParameter('limit', i) as number; + responseData = responseData.splice(0, limit); + } + + returnData.push.apply(returnData, responseData as IDataObject[]); + } + } + } + // Handle file download output data differently + if (resource === 'report' && operation === 'download') { + return this.prepareOutputData(items); + } + return [this.helpers.returnJsonArray(returnData)]; + } +} diff --git a/packages/nodes-base/nodes/SecurityScorecard/descriptions/CompanyDescription.ts b/packages/nodes-base/nodes/SecurityScorecard/descriptions/CompanyDescription.ts new file mode 100644 index 0000000000..a2509495b6 --- /dev/null +++ b/packages/nodes-base/nodes/SecurityScorecard/descriptions/CompanyDescription.ts @@ -0,0 +1,253 @@ +import { + INodeProperties, +} from 'n8n-workflow'; + +export const companyOperations = [ + { + displayName: 'Operation', + name: 'operation', + type: 'options', + required: true, + displayOptions: { + show: { + resource: [ + 'company', + ], + }, + }, + options: [ + { + name: 'Get Factor Scores', + value: 'getFactor', + description: 'Get company factor scores and issue counts', + }, + { + name: 'Get Historical Factor Scores', + value: 'getFactorHistorical', + description: 'Get company\'s historical factor scores', + }, + { + name: 'Get Historical Scores', + value: 'getHistoricalScore', + description: 'Get company\'s historical scores', + }, + { + name: 'Get Information and Scorecard', + value: 'getScorecard', + description: 'Get company information and summary of their scorecard', + }, + { + name: 'Get Score Plan', + value: 'getScorePlan', + description: 'Get company\'s score improvement plan', + }, + ], + default: 'getFactor', + }, +] as INodeProperties[]; + +export const companyFields = [ + { + displayName: 'Scorecard Identifier', + name: 'scorecardIdentifier', + description: 'Primary identifier of a company or scorecard, i.e. domain', + type: 'string', + default: '', + required: true, + displayOptions: { + show: { + resource: [ + 'company', + ], + operation: [ + 'getScorecard', + 'getFactor', + 'getFactorHistorical', + 'getHistoricalScore', + 'getScorePlan', + ], + }, + }, + }, + { + displayName: 'Score', + name: 'score', + description: 'Score target', + type: 'number', + displayOptions: { + show: { + resource: [ + 'company', + ], + operation: [ + 'getScorePlan', + ], + }, + }, + required: true, + default: 0, + }, + { + displayName: 'Return All', + name: 'returnAll', + type: 'boolean', + displayOptions: { + show: { + resource: [ + 'company', + ], + operation: [ + 'getFactor', + 'getFactorHistorical', + 'getHistoricalScore', + 'getScorePlan', + ], + }, + }, + default: false, + description: 'If all results should be returned or only up to a given limit.', + }, + { + displayName: 'Limit', + name: 'limit', + type: 'number', + displayOptions: { + show: { + resource: [ + 'company', + ], + operation: [ + 'getFactor', + 'getFactorHistorical', + 'getHistoricalScore', + 'getScorePlan', + ], + returnAll: [ + false, + ], + }, + }, + typeOptions: { + minValue: 1, + maxValue: 100, + }, + default: 100, + description: 'Number of results to return.', + }, + { + displayName: 'Simple', + name: 'simple', + type: 'boolean', + displayOptions: { + show: { + resource: [ + 'company', + ], + operation: [ + 'getFactorHistorical', + 'getHistoricalScore', + ], + }, + }, + default: true, + description: 'Simplify the response.', + }, + + // company:getFactor + { + displayName: 'Filters', + name: 'filters', + displayOptions: { + show: { + resource: [ + 'company', + ], + operation: [ + 'getFactor', + ], + }, + }, + type: 'collection', + placeholder: 'Add Filter', + default: {}, + options: [ + { + displayName: 'Severity', + name: 'severity', + type: 'string', + default: '', + placeholder: '', + }, + { + displayName: 'Severity In', + description: 'Filter issues by comma separated severity list', + name: 'severity_in', + type: 'string', + default: '', + placeholder: '', + }, + ], + }, + + // company:getFactorHistorical + // company:getHistoricalScore + { + displayName: 'Options', + name: 'options', + displayOptions: { + show: { + resource: [ + 'company', + ], + operation: [ + 'getFactorHistorical', + 'getHistoricalScore', + ], + }, + }, + type: 'collection', + placeholder: 'Add Option', + default: {}, + options: [ + { + displayName: 'Date From', + description: 'History start date', + name: 'date_from', + type: 'dateTime', + default: '', + required: false, + + }, + { + displayName: 'Date To', + description: 'History end date', + name: 'date_to', + type: 'dateTime', + default: '', + required: false, + }, + { + displayName: 'Timing', + description: 'Date granularity', + name: 'timing', + type: 'options', + options: [ + { + name: 'Daily', + value: 'daily', + }, + { + name: 'Weekly', + value: 'weekly', + }, + { + name: 'Monthly', + value: 'monthly', + }, + ], + default: 'daily', + required: false, + }, + ], + }, +] as INodeProperties[]; diff --git a/packages/nodes-base/nodes/SecurityScorecard/descriptions/IndustryDescription.ts b/packages/nodes-base/nodes/SecurityScorecard/descriptions/IndustryDescription.ts new file mode 100644 index 0000000000..665c4bdd28 --- /dev/null +++ b/packages/nodes-base/nodes/SecurityScorecard/descriptions/IndustryDescription.ts @@ -0,0 +1,173 @@ +import { + INodeProperties, +} from 'n8n-workflow'; + +export const industryOperations = [ + { + displayName: 'Operation', + name: 'operation', + type: 'options', + required: true, + displayOptions: { + show: { + resource: [ + 'industry', + ], + }, + }, + options: [ + { + name: 'Get Factor Scores', + value: 'getFactor', + }, + { + name: 'Get Historical Factor Scores', + value: 'getFactorHistorical', + }, + { + name: 'Get Score', + value: 'getScore', + }, + ], + default: 'getFactor', + }, +] as INodeProperties[]; + +export const industryFields = [ + { + displayName: 'Industry', + name: 'industry', + type: 'options', + options: [ + { + name: 'Food', + value: 'food', + }, + { + name: 'Healthcare', + value: 'healthcare', + }, + { + name: 'Manofacturing', + value: 'manofacturing', + }, + { + name: 'Retail', + value: 'retail', + }, + { + name: 'Technology', + value: 'technology', + }, + ], + required: true, + displayOptions: { + show: { + resource: [ + 'industry', + ], + operation: [ + 'getScore', + 'getFactor', + 'getFactorHistorical', + ], + }, + }, + }, + { + displayName: 'Return All', + name: 'returnAll', + type: 'boolean', + displayOptions: { + show: { + resource: [ + 'industry', + ], + operation: [ + 'getFactor', + 'getFactorHistorical', + ], + }, + }, + default: false, + description: 'If all results should be returned or only up to a given limit.', + }, + { + displayName: 'Limit', + name: 'limit', + type: 'number', + displayOptions: { + show: { + resource: [ + 'industry', + ], + operation: [ + 'getFactor', + 'getFactorHistorical', + ], + returnAll: [ + false, + ], + }, + }, + typeOptions: { + minValue: 1, + maxValue: 100, + }, + default: 100, + description: 'Number of results to return.', + }, + { + displayName: 'Simple', + name: 'simple', + type: 'boolean', + displayOptions: { + show: { + resource: [ + 'industry', + ], + operation: [ + 'getFactor', + 'getFactorHistorical', + ], + }, + }, + default: true, + description: 'Simplify the response.', + }, + { + displayName: 'Options', + name: 'options', + displayOptions: { + show: { + resource: [ + 'industry', + ], + operation: [ + 'getFactorHistorical', + ], + }, + }, + type: 'collection', + placeholder: 'Add Option', + default: {}, + options: [ + { + displayName: 'Date From', + description: 'History start date', + name: 'from', + type: 'dateTime', + default: '', + required: false, + }, + { + displayName: 'Date To', + description: 'History end date', + name: 'to', + type: 'dateTime', + default: '', + required: false, + }, + ], + }, +] as INodeProperties[]; diff --git a/packages/nodes-base/nodes/SecurityScorecard/descriptions/InviteDescription.ts b/packages/nodes-base/nodes/SecurityScorecard/descriptions/InviteDescription.ts new file mode 100644 index 0000000000..2710d42ea8 --- /dev/null +++ b/packages/nodes-base/nodes/SecurityScorecard/descriptions/InviteDescription.ts @@ -0,0 +1,178 @@ +import { + INodeProperties, +} from 'n8n-workflow'; + +export const inviteOperations = [ + { + displayName: 'Operation', + name: 'operation', + type: 'options', + required: true, + displayOptions: { + show: { + resource: [ + 'invite', + ], + }, + }, + options: [ + { + name: 'Create', + value: 'create', + description: 'Create an invite for a company/user', + }, + ], + default: 'create', + }, +] as INodeProperties[]; + +export const inviteFields = [ + { + displayName: 'Email', + name: 'email', + type: 'string', + default: '', + required: true, + displayOptions: { + show: { + resource: [ + 'invite', + ], + operation: [ + 'create', + ], + }, + }, + }, + { + displayName: 'First Name', + name: 'firstName', + type: 'string', + default: '', + required: true, + displayOptions: { + show: { + resource: [ + 'invite', + ], + operation: [ + 'create', + ], + }, + }, + }, + { + displayName: 'Last Name', + name: 'lastName', + type: 'string', + default: '', + required: true, + displayOptions: { + show: { + resource: [ + 'invite', + ], + operation: [ + 'create', + ], + }, + }, + }, + { + displayName: 'Message', + name: 'message', + description: 'Message for the invitee', + type: 'string', + default: '', + required: true, + displayOptions: { + show: { + resource: [ + 'invite', + ], + operation: [ + 'create', + ], + }, + }, + }, + { + displayName: 'Additional Fields', + name: 'additionalFields', + type: 'collection', + placeholder: 'Add Field', + displayOptions: { + show: { + resource: [ + 'invite', + ], + operation: [ + 'create', + ], + }, + }, + default: {}, + options: [ + { + displayName: 'Days to Resolve Issue', + description: 'Minimum days to resolve a scorecard issue', + name: 'days_to_resolve_issue', + type: 'number', + default: 0, + }, + { + displayName: 'Domain', + description: 'Invitee company domain', + name: 'domain', + type: 'string', + default: '', + }, + { + displayName: 'Grade to Maintain', + description: 'Request the invitee\'s organisation to maintain a minimum grade', + name: 'grade_to_maintain', + type: 'string', + default: '', + }, + { + displayName: 'Is Organisation Point of Contact', + description: 'Is the invitee organisation\'s point of contact', + name: 'is_organization_point_of_contact', + type: 'boolean', + default: false, + }, + { + displayName: 'Issue Description', + name: 'issue_desc', + type: 'string', + default: '', + }, + { + displayName: 'Issue Title', + name: 'issue_title', + type: 'string', + default: '', + }, + { + displayName: 'Issue Type', + name: 'issue_type', + type: 'string', + default: '', + }, + { + displayName: 'Send Me a Copy', + name: 'sendme_copy', + description: 'Send a copy of the invite to the requesting user', + type: 'boolean', + default: false, + }, + { + displayName: 'Target URL', + name: 'target_url', + type: 'string', + description: 'Optional URL to take the invitee to when arriving to the platform', + default: '', + }, + ], + }, +] as INodeProperties[]; diff --git a/packages/nodes-base/nodes/SecurityScorecard/descriptions/PortfolioCompanyDescription.ts b/packages/nodes-base/nodes/SecurityScorecard/descriptions/PortfolioCompanyDescription.ts new file mode 100644 index 0000000000..6842e531fa --- /dev/null +++ b/packages/nodes-base/nodes/SecurityScorecard/descriptions/PortfolioCompanyDescription.ts @@ -0,0 +1,188 @@ +import { + INodeProperties, +} from 'n8n-workflow'; + +export const portfolioCompanyOperations = [ + { + displayName: 'Operation', + name: 'operation', + type: 'options', + required: true, + displayOptions: { + show: { + resource: [ + 'portfolioCompany', + ], + }, + }, + options: [ + { + name: 'Add', + value: 'add', + description: 'Add a company to portfolio', + }, + { + name: 'Get All', + value: 'getAll', + description: 'Get all companies in a portfolio', + }, + { + name: 'Remove', + value: 'remove', + description: 'Remove a company from portfolio', + }, + ], + default: 'add', + }, +] as INodeProperties[]; + +export const portfolioCompanyFields = [ + { + displayName: 'Portfolio ID', + name: 'portfolioId', + type: 'string', + required: true, + default: '', + displayOptions: { + show: { + resource: [ + 'portfolioCompany', + ], + operation: [ + 'getAll', + 'add', + 'remove', + ], + }, + }, + }, + { + displayName: 'Return All', + name: 'returnAll', + type: 'boolean', + displayOptions: { + show: { + resource: [ + 'portfolioCompany', + ], + operation: [ + 'getAll', + ], + }, + }, + default: false, + description: 'If all results should be returned or only up to a given limit.', + }, + { + displayName: 'Limit', + name: 'limit', + type: 'number', + displayOptions: { + show: { + resource: [ + 'portfolioCompany', + ], + operation: [ + 'getAll', + ], + returnAll: [ + false, + ], + }, + }, + typeOptions: { + minValue: 1, + maxValue: 100, + }, + default: 100, + description: 'Number of results to return.', + }, + { + displayName: 'Filters', + name: 'filters', + displayOptions: { + show: { + resource: [ + 'portfolioCompany', + ], + operation: [ + 'getAll', + ], + }, + }, + type: 'collection', + placeholder: 'Add Filter', + default: {}, + options: [ + { + displayName: 'Grade', + name: 'grade', + type: 'string', + placeholder: '', + default: '', + description: 'Company score grade filter', + }, + { + displayName: 'Industry', + name: 'industry', + type: 'string', + placeholder: '', + default: '', + description: 'Industry filter', + }, + { + displayName: 'Issue Type', + name: 'issueType', + type: 'string', + placeholder: '', + description: 'Issue type filter', + default: '', + }, + { + displayName: 'Status', + name: 'status', + type: 'options', + options: [ + { + name: 'Active', + value: 'active', + }, + { + name: 'Inactive', + value: 'inactive', + }, + ], + default: '', + }, + { + displayName: 'Vulnerability', + name: 'vulnerability', + type: 'string', + placeholder: '', + description: 'CVE vulnerability filter', + default: '', + }, + ], + }, + { + displayName: 'Domain', + name: 'domain', + type: 'string', + required: true, + default: '', + displayOptions: { + show: { + resource: [ + 'portfolioCompany', + ], + operation: [ + 'add', + 'remove', + ], + }, + }, + description: 'Company\'s domain name', + }, + +] as INodeProperties[]; + diff --git a/packages/nodes-base/nodes/SecurityScorecard/descriptions/PortfolioDescription.ts b/packages/nodes-base/nodes/SecurityScorecard/descriptions/PortfolioDescription.ts new file mode 100644 index 0000000000..f6cbe5d6a6 --- /dev/null +++ b/packages/nodes-base/nodes/SecurityScorecard/descriptions/PortfolioDescription.ts @@ -0,0 +1,178 @@ +import { + INodeProperties, +} from 'n8n-workflow'; + +export const portfolioOperations = [ + { + displayName: 'Operation', + name: 'operation', + type: 'options', + required: true, + displayOptions: { + show: { + resource: [ + 'portfolio', + ], + }, + }, + options: [ + { + name: 'Create', + value: 'create', + description: 'Create a portfolio', + }, + { + name: 'Delete', + value: 'delete', + description: 'Delete a portfolio', + }, + { + name: 'Get All', + value: 'getAll', + description: 'Get all portfolios', + }, + { + name: 'Update', + value: 'update', + description: 'Update a portfolio', + }, + ], + default: 'create', + }, +] as INodeProperties[]; + +export const portfolioFields = [ + { + displayName: 'Return All', + name: 'returnAll', + type: 'boolean', + displayOptions: { + show: { + resource: [ + 'portfolio', + ], + operation: [ + 'getAll', + ], + }, + }, + default: false, + description: 'If all results should be returned or only up to a given limit.', + }, + { + displayName: 'Limit', + name: 'limit', + type: 'number', + displayOptions: { + show: { + resource: [ + 'portfolio', + ], + operation: [ + 'getAll', + ], + returnAll: [ + false, + ], + }, + }, + typeOptions: { + minValue: 1, + maxValue: 100, + }, + default: 100, + description: 'Number of results to return.', + }, + { + displayName: 'Portfolio ID', + name: 'portfolioId', + type: 'string', + required: true, + default: '', + displayOptions: { + show: { + resource: [ + 'portfolio', + ], + operation: [ + 'update', + 'delete', + ], + }, + }, + }, + { + displayName: 'Portfolio Name', + name: 'name', + type: 'string', + required: true, + default: '', + displayOptions: { + show: { + resource: [ + 'portfolio', + ], + operation: [ + 'create', + 'update', + ], + }, + }, + description: 'Name of the portfolio', + }, + { + displayName: 'Description', + name: 'description', + type: 'string', + required: false, + default: '', + displayOptions: { + show: { + resource: [ + 'portfolio', + ], + operation: [ + 'create', + 'update', + ], + }, + }, + description: 'Description', + }, + { + displayName: 'Privacy', + name: 'privacy', + type: 'options', + required: false, + displayOptions: { + show: { + resource: [ + 'portfolio', + ], + operation: [ + 'create', + 'update', + ], + }, + }, + options: [ + { + name: 'Private', + value: 'private', + description: 'Only visible to you', + }, + { + name: 'Shared', + value: 'shared', + description: 'Visible to everyone in your company', + }, + { + name: 'Team', + value: 'team', + description: 'Visible to the people on your team', + }, + ], + default: 'shared', + }, +] as INodeProperties[]; + diff --git a/packages/nodes-base/nodes/SecurityScorecard/descriptions/ReportDescription.ts b/packages/nodes-base/nodes/SecurityScorecard/descriptions/ReportDescription.ts new file mode 100644 index 0000000000..332137bff5 --- /dev/null +++ b/packages/nodes-base/nodes/SecurityScorecard/descriptions/ReportDescription.ts @@ -0,0 +1,383 @@ +import { + INodeProperties, +} from 'n8n-workflow'; + +export const reportOperations = [ + { + displayName: 'Operation', + name: 'operation', + type: 'options', + required: true, + displayOptions: { + show: { + resource: [ + 'report', + ], + }, + }, + options: [ + { + name: 'Download', + value: 'download', + description: 'Download a generated report', + }, + { + name: 'Generate', + value: 'generate', + description: 'Generate a report', + }, + { + name: 'Get All', + value: 'getAll', + description: 'Get list of recently generated report', + }, + ], + default: 'getAll', + }, +] as INodeProperties[]; + +export const reportFields = [ + { + displayName: 'Return All', + name: 'returnAll', + type: 'boolean', + displayOptions: { + show: { + resource: [ + 'report', + ], + operation: [ + 'getAll', + ], + }, + }, + default: false, + description: 'If all results should be returned or only up to a given limit.', + }, + { + displayName: 'Limit', + name: 'limit', + type: 'number', + displayOptions: { + show: { + resource: [ + 'report', + ], + operation: [ + 'getAll', + ], + returnAll: [ + false, + ], + }, + }, + typeOptions: { + minValue: 1, + maxValue: 100, + }, + default: 100, + description: 'Number of results to return.', + }, + { + displayName: 'Report', + name: 'report', + type: 'options', + required: true, + displayOptions: { + show: { + resource: [ + 'report', + ], + operation: [ + 'generate', + ], + }, + }, + options: [ + { + name: 'Company Detailed', + value: 'detailed', + }, + { + name: 'Company Events', + value: 'events-json', + }, + { + name: 'Company Issues', + value: 'issues', + }, + { + name: 'Company Partnership', + value: 'partnership', + }, + { + name: 'Company Summary', + value: 'summary', + }, + { + name: 'Full Scorecard', + value: 'full-scorecard-json', + }, + { + name: 'Portfolio', + value: 'portfolio', + }, + { + name: 'Scorecard Footprint', + value: 'scorecard-footprint', + }, + + ], + default: 'detailed', + }, + { + displayName: 'Scorecard Identifier', + name: 'scorecardIdentifier', + description: 'Primary identifier of a company or scorecard, i.e. domain', + type: 'string', + required: true, + default: '', + displayOptions: { + show: { + resource: [ + 'report', + ], + operation: [ + 'generate', + ], + report: [ + 'detailed', + 'events-json', + 'full-scorecard-json', + 'issues', + 'partnership', + 'scorecard-footprint', + 'summary', + ], + }, + }, + }, + { + displayName: 'Portfolio ID', + name: 'portfolioId', + type: 'string', + required: true, + default: '', + displayOptions: { + show: { + resource: [ + 'report', + ], + operation: [ + 'generate', + ], + report: [ + 'portfolio', + ], + }, + }, + description: 'Portfolio ID', + }, + { + displayName: 'Branding', + name: 'branding', + type: 'options', + required: false, + displayOptions: { + show: { + resource: [ + 'report', + ], + operation: [ + 'generate', + ], + report: [ + 'detailed', + 'summary', + ], + }, + }, + options: [ + { + name: 'SecurityScorecard', + value: 'securityscorecard', + }, + { + name: 'Company and SecurityScorecard', + value: 'company_and_securityscorecard', + }, + { + name: 'Company', + value: 'company', + }, + ], + default: 'securityscorecard', + }, + { + displayName: 'Date', + name: 'date', + type: 'dateTime', + required: true, + displayOptions: { + show: { + resource: [ + 'report', + ], + operation: [ + 'generate', + ], + report: [ + 'events-json', + ], + }, + }, + }, + { + displayName: 'Options', + name: 'options', + type: 'collection', + required: false, + default: {}, + placeholder: 'Add Field', + displayOptions: { + show: { + resource: [ + 'report', + ], + operation: [ + 'generate', + ], + report: [ + 'issues', + 'portfolio', + ], + }, + }, + options: [ + { + displayName: 'Format', + name: 'format', + type: 'options', + default: 'pdf', + options: [ + { + name: 'CSV', + value: 'csv', + }, + { + name: 'PDF', + value: 'pdf', + }, + ], + required: false, + }, + ], + }, + { + displayName: 'Options', + name: 'options', + type: 'collection', + required: false, + default: {}, + placeholder: 'Add Field', + displayOptions: { + show: { + resource: [ + 'report', + ], + operation: [ + 'generate', + ], + report: [ + 'scorecard-footprint', + ], + }, + }, + options: [ + { + displayName: 'Countries', + name: 'countries', + type: 'string', + typeOptions: { + multipleValues: true, + }, + default: [], + required: false, + }, + { + displayName: 'Format', + name: 'format', + type: 'options', + default: 'pdf', + options: [ + { + name: 'CSV', + value: 'csv', + }, + { + name: 'PDF', + value: 'pdf', + }, + ], + required: false, + }, + { + displayName: 'IPs', + name: 'ips', + type: 'string', + typeOptions: { + multipleValues: true, + }, + default: [], + required: false, + }, + { + displayName: 'Subdomains', + name: 'subdomains', + type: 'string', + typeOptions: { + multipleValues: true, + }, + default: [], + required: false, + }, + + ], + }, + { + displayName: 'Report URL', + name: 'url', + type: 'string', + default: '', + required: true, + description: 'URL to a generated report', + displayOptions: { + show: { + resource: [ + 'report', + ], + operation: [ + 'download', + ], + }, + }, + }, + { + displayName: 'Binary Property', + name: 'binaryPropertyName', + type: 'string', + required: true, + default: 'data', + displayOptions: { + show: { + resource: [ + 'report', + ], + operation: [ + 'download', + ], + }, + }, + description: 'Name of the binary property to which to
write the data of the read file.', + }, +] as INodeProperties[]; diff --git a/packages/nodes-base/nodes/SecurityScorecard/securityScorecard.svg b/packages/nodes-base/nodes/SecurityScorecard/securityScorecard.svg new file mode 100644 index 0000000000..aec08d785f --- /dev/null +++ b/packages/nodes-base/nodes/SecurityScorecard/securityScorecard.svg @@ -0,0 +1,19 @@ + + + + background + + + + Layer 1 + + + + + + + + + + + \ No newline at end of file diff --git a/packages/nodes-base/package.json b/packages/nodes-base/package.json index 4a3d278620..8c774a701e 100644 --- a/packages/nodes-base/package.json +++ b/packages/nodes-base/package.json @@ -182,6 +182,7 @@ "dist/credentials/SalesforceJwtApi.credentials.js", "dist/credentials/SalesforceOAuth2Api.credentials.js", "dist/credentials/SalesmateApi.credentials.js", + "dist/credentials/SecurityScorecardApi.credentials.js", "dist/credentials/SegmentApi.credentials.js", "dist/credentials/SendGridApi.credentials.js", "dist/credentials/SendyApi.credentials.js", @@ -428,6 +429,7 @@ "dist/nodes/Rundeck/Rundeck.node.js", "dist/nodes/S3/S3.node.js", "dist/nodes/Salesforce/Salesforce.node.js", + "dist/nodes/SecurityScorecard/SecurityScorecard.node.js", "dist/nodes/Set.node.js", "dist/nodes/SentryIo/SentryIo.node.js", "dist/nodes/SendGrid/SendGrid.node.js",