From 096863a5c7ee4da3d52818b04a8fe81677d7dbe6 Mon Sep 17 00:00:00 2001 From: Ed Date: Fri, 2 Aug 2024 11:28:35 -0400 Subject: [PATCH 01/14] Asset search --- config/config.js | 12 ++++--- config/config.json | 33 ++++++++++++++++--- package.json | 2 +- src/displayStructures/assets.js | 1 + .../customFunctionalityByType.js | 2 +- .../defaultFunctionalityByType.js | 1 - 6 files changed, 39 insertions(+), 12 deletions(-) diff --git a/config/config.js b/config/config.js index fd117da..a3065c7 100644 --- a/config/config.js +++ b/config/config.js @@ -46,6 +46,10 @@ module.exports = { { key: 'requestedItem', regex: /RITM[0-9]{7,}/ + }, + { + key: 'assetAttribute', + regex: /^(? `number=${value}`; - const { getTableQueryDataSummaryTags } = require('./createSummaryTagsFunctions'); const { tableQueryDisplayStructure } = require('../displayStructures/index'); From 441cfd58a0c922e8e07fc87bdb07ef3b3e049409 Mon Sep 17 00:00:00 2001 From: Ed Date: Tue, 6 Aug 2024 18:03:04 -0400 Subject: [PATCH 02/14] Enable/disable incident search option, days back search option --- config/config.js | 105 ++++++------------ config/config.json | 38 ++++--- package.json | 2 +- src/dataTransformations.js | 36 +++++- .../assetsAndIncidentCustomFunctionality.js | 16 ++- .../customFunctionalityByType.js | 2 - .../defaultFunctionalityByType.js | 12 +- src/querying/queryAssets.js | 2 +- 8 files changed, 114 insertions(+), 99 deletions(-) diff --git a/config/config.js b/config/config.js index a3065c7..5c78d28 100644 --- a/config/config.js +++ b/config/config.js @@ -1,73 +1,33 @@ module.exports = { - /** - * Name of the integration which is displayed in the Polarity integrations user interface - * - * @type String - * @required - */ name: 'ServiceNow', - /** - * The acronym that appears in the notification window when information from this integration - * is displayed. Note that the acronym is included as part of each "tag" in the summary information - * for the integration. As a result, it is best to keep it to 4 or less characters. The casing used - * here will be carried forward into the notification window. - * - * @type String - * @required - */ acronym: 'SN', defaultColor: 'light-purple', - /** - * Description for this integration which is displayed in the Polarity integrations user interface - * - * @type String - * @optional - */ description: 'ServiceNow automates and streamlines work and helps create great employee and customer experiences.', entityTypes: ['IPv4', 'email', 'domain', 'string', 'cve'], customTypes: [ { key: 'incident', - regex: /INC[0-9]{7,}/ + regex: 'INC[0-9]{7,}' }, { key: 'knowledgeBase', - regex: /KB[0-9]{7,}/ + regex: 'KB[0-9]{7,}' }, { key: 'change', - regex: /CHG[0-9]{7,}/ + regex: 'CHG[0-9]{7,}' }, { key: 'request', - regex: /REQ[0-9]{7,}/ + regex: 'REQ[0-9]{7,}' }, { key: 'requestedItem', - regex: /RITM[0-9]{7,}/ - }, - { - key: 'assetAttribute', - regex: /^(? { }; }; -const objectPromiseAll = async (obj = { fn1: async () => {} }) => { +const objectPromiseAll = async ( + obj = { + fn1: async () => {} + } +) => { const labels = keys(obj); const functions = values(obj); const executedFunctions = await Promise.all(map((func) => func(), functions)); @@ -126,6 +130,33 @@ const mapObjectAsync = async (func, obj) => { const parseErrorToReadableJSON = (error) => JSON.parse(JSON.stringify(error, Object.getOwnPropertyNames(error))); +/** + * Merges two arrays of objects and removes duplicates based on the equality of the + * given `mergeKey` + */ +const mergeAndRemoveDuplicates = (arr1, arr2, mergeKey) => { + // Merge the two arrays + const mergedArray = [...arr1, ...arr2]; + + // Sort the merged array based on the 'key' property + mergedArray.sort((a, b) => { + if (a[mergeKey] < b[mergeKey]) return -1; + if (a[mergeKey] > b[mergeKey]) return 1; + return 0; + }); + + // Filter out duplicates + const uniqueArray = []; + for (let i = 0; i < mergedArray.length; i++) { + // If it's the first element or different from the previous one, add it to the uniqueArray + if (i === 0 || mergedArray[i][mergeKey] !== mergedArray[i - 1][mergeKey]) { + uniqueArray.push(mergedArray[i]); + } + } + + return uniqueArray; +}; + module.exports = { getKeys, groupEntities, @@ -135,5 +166,6 @@ module.exports = { mapObject, mapObjectAsync, transpose2DArray, - parseErrorToReadableJSON + parseErrorToReadableJSON, + mergeAndRemoveDuplicates }; diff --git a/src/functionalityByEntityType/assetsAndIncidentCustomFunctionality.js b/src/functionalityByEntityType/assetsAndIncidentCustomFunctionality.js index f895286..8cb7d4b 100644 --- a/src/functionalityByEntityType/assetsAndIncidentCustomFunctionality.js +++ b/src/functionalityByEntityType/assetsAndIncidentCustomFunctionality.js @@ -15,10 +15,15 @@ const { const assetsAndIncidentCustomFunctionality = { queryFunction: async (entity, options, requestWithDefaults, Logger) => ({ - ...(options.enableAssetSearch ? await queryAssets(entity, options, requestWithDefaults, Logger) : {}), - ...(await queryTableData(entity, options, requestWithDefaults, Logger)) + ...(options.enableAssetSearch + ? await queryAssets(entity, options, requestWithDefaults, Logger) + : {}), + ...(options.enableIncidentSearch + ? await queryTableData(entity, options, requestWithDefaults, Logger) + : {}) }), - tableQueryQueryString: ({ value }, { incidentQueryFields }) => + tableQueryTableName: 'incident', + tableQueryQueryString: ({ value }, { incidentQueryFields, incidentDaysAgoToSearch }) => incidentQueryFields && typeof incidentQueryFields === 'string' && incidentQueryFields.length !== 0 && @@ -26,7 +31,10 @@ const assetsAndIncidentCustomFunctionality = { split(','), compact, map((field) => `${trim(field)}CONTAINS${value}`), - join('^NQ') + join('^OR'), + (query) => { + return `${query}^opened_at>javascript:gs.daysAgoStart(${incidentDaysAgoToSearch})`; + } )(incidentQueryFields), createSummaryTags: (results, entity, Logger) => getTableQueryDataSummaryTags(results, entity, Logger).concat( diff --git a/src/functionalityByEntityType/customFunctionalityByType.js b/src/functionalityByEntityType/customFunctionalityByType.js index 3e7790d..b56c814 100644 --- a/src/functionalityByEntityType/customFunctionalityByType.js +++ b/src/functionalityByEntityType/customFunctionalityByType.js @@ -98,7 +98,6 @@ const CUSTOM_FUNCTIONALITY_FOR_CUSTOM_ENTITY_TYPES = { defaults: { tableQuerySummaryTagPaths: ['category', 'phase'] }, - assetAttribute: assetsAndIncidentCustomFunctionality, // Specific Custom Types knowledgeBase: { queryFunction: queryKnowledgeBase, @@ -106,7 +105,6 @@ const CUSTOM_FUNCTIONALITY_FOR_CUSTOM_ENTITY_TYPES = { displayTabNames: { knowledgeBaseData: 'Knowledge Base' }, displayStructure: { knowledgeBaseData: knowledgeBaseDisplayStructure } }, - change: { tableQueryTableName: 'change_request', displayTabNames: { tableQueryData: 'Changes' } diff --git a/src/functionalityByEntityType/defaultFunctionalityByType.js b/src/functionalityByEntityType/defaultFunctionalityByType.js index 9492997..b4ec6ae 100644 --- a/src/functionalityByEntityType/defaultFunctionalityByType.js +++ b/src/functionalityByEntityType/defaultFunctionalityByType.js @@ -1,6 +1,9 @@ const { map, flow, get, reduce } = require('lodash/fp'); -const { entityTypes, customTypes } = require('../../config/config'); +const { mergeAndRemoveDuplicates } = require('../dataTransformations'); +const { entityTypes, customTypes: customTypesJs } = require('../../config/config'); +const { customTypes: customTypesJson } = require('../../config/config.json'); +const customTypes = mergeAndRemoveDuplicates(customTypesJs, customTypesJson, 'key'); const queryTableData = require('../querying/queryTableData'); @@ -8,8 +11,11 @@ const numberTableQueryString = ({ value }) => `number=${value}`; const { getTableQueryDataSummaryTags } = require('./createSummaryTagsFunctions'); const { tableQueryDisplayStructure } = require('../displayStructures/index'); +const assetsAndIncidentCustomFunctionality = require('./assetsAndIncidentCustomFunctionality'); -const DEFAULT_FUNCTIONALITY_OBJECT = { +// Not currently used but contains default values if you only want to search the Incident +// table and not search the assets table. +const DEFAULT_INCIDENT_ONLY_SEARCH_FUNCTIONALITY_OBJECT = { queryFunction: queryTableData, tableQueryTableName: 'incident', tableQueryQueryString: numberTableQueryString, @@ -20,6 +26,8 @@ const DEFAULT_FUNCTIONALITY_OBJECT = { tableQuerySummaryTagPaths: false }; +const DEFAULT_FUNCTIONALITY_OBJECT = assetsAndIncidentCustomFunctionality; + const defaultFunctionalityForStandardEntityTypes = reduce( (agg, entityType) => ({ ...agg, [entityType]: DEFAULT_FUNCTIONALITY_OBJECT }), {}, diff --git a/src/querying/queryAssets.js b/src/querying/queryAssets.js index fb02a1a..092e48e 100644 --- a/src/querying/queryAssets.js +++ b/src/querying/queryAssets.js @@ -7,7 +7,7 @@ const queryAssets = async (entity, options, requestWithDefaults, Logger) => { get('assetTableFields'), split(','), map((field) => `${trim(field)}CONTAINS${entity.value}`), - join('^NQ') + join('^OR') )(options); const requestOptions = { From eee57d09e88fdee8e91e61164d89f1e82f9277a7 Mon Sep 17 00:00:00 2001 From: Ed Date: Tue, 6 Aug 2024 18:05:07 -0400 Subject: [PATCH 03/14] Bump dependencies --- package-lock.json | 44 ++++++++++++++++++++++---------------------- package.json | 2 +- 2 files changed, 23 insertions(+), 23 deletions(-) diff --git a/package-lock.json b/package-lock.json index e460030..156c5bd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "service-now", - "version": "3.4.2", + "version": "3.5.0", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -15,9 +15,9 @@ } }, "@postman/tough-cookie": { - "version": "4.1.2-postman.1", - "resolved": "https://registry.npmjs.org/@postman/tough-cookie/-/tough-cookie-4.1.2-postman.1.tgz", - "integrity": "sha512-keOKL3RQohnH5K4GNGSV7JE8+SU/ktWJ3h9ulqttOGUWCZWcbUOtMNXkC3PQ/R1hIa+2qEfh0/5NCcAhWqeMTQ==", + "version": "4.1.3-postman.1", + "resolved": "https://registry.npmjs.org/@postman/tough-cookie/-/tough-cookie-4.1.3-postman.1.tgz", + "integrity": "sha512-txpgUqZOnWYnUHZpHjkfb0IwVH4qJmyq77pPnJLlfhMtdCLMFTEeQHlzQiK906aaNCe4NEB5fGJHo9uzGbFMeA==", "requires": { "psl": "^1.1.33", "punycode": "^2.1.1", @@ -26,9 +26,9 @@ } }, "@postman/tunnel-agent": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/@postman/tunnel-agent/-/tunnel-agent-0.6.3.tgz", - "integrity": "sha512-k57fzmAZ2PJGxfOA4SGR05ejorHbVAa/84Hxh/2nAztjNXc4ZjOm9NUIk6/Z6LCrBvJZqjRZbN8e/nROVUPVdg==", + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/@postman/tunnel-agent/-/tunnel-agent-0.6.4.tgz", + "integrity": "sha512-CJJlq8V7rNKhAw4sBfjixKpJW00SHqebqNUQKxMoepgeWZIbdPcD+rguRcivGhS4N12PymDcKgUgSD4rVC+RjQ==", "requires": { "safe-buffer": "^5.0.1" } @@ -68,9 +68,9 @@ "integrity": "sha512-08kcGqnYf/YmjoRhfxyu+CLxBjUtHLXLXX/vUfx9l2LYzG3c1m61nrpyFUZI6zeS+Li/wWMMidD9KgrqtGq3mA==" }, "aws4": { - "version": "1.12.0", - "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.12.0.tgz", - "integrity": "sha512-NmWvPnx0F1SfrQbYwOi7OeaNGokp9XhzNioJ/CSBs8Qa4vxug81mhJEAVZwxXuBmYB5KDRfMq/F3RR0BIU7sWg==" + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.13.1.tgz", + "integrity": "sha512-u5w79Rd7SU4JaIlA/zFqG+gOiuq25q5VLyZ8E+ijJeILuTxVzZgp2CaGw/UTw6pXYN9XMO9yiqj/nEHmhTG5CA==" }, "base64-js": { "version": "1.5.1", @@ -265,13 +265,13 @@ "integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==" }, "postman-request": { - "version": "2.88.1-postman.32", - "resolved": "https://registry.npmjs.org/postman-request/-/postman-request-2.88.1-postman.32.tgz", - "integrity": "sha512-Zf5D0b2G/UmnmjRwQKhYy4TBkuahwD0AMNyWwFK3atxU1u5GS38gdd7aw3vyR6E7Ii+gD//hREpflj2dmpbE7w==", + "version": "2.88.1-postman.38", + "resolved": "https://registry.npmjs.org/postman-request/-/postman-request-2.88.1-postman.38.tgz", + "integrity": "sha512-gQpK/jjTreGGv7VKeCzwlPa8dulOBANpXfLFuYUEu7sTWQTC6Kv60kfW0z047p7ujMmVwsZkrre/QP/u3DkdnQ==", "requires": { "@postman/form-data": "~3.1.1", - "@postman/tough-cookie": "~4.1.2-postman.1", - "@postman/tunnel-agent": "^0.6.3", + "@postman/tough-cookie": "~4.1.3-postman.1", + "@postman/tunnel-agent": "^0.6.4", "aws-sign2": "~0.7.0", "aws4": "^1.12.0", "brotli": "^1.3.3", @@ -299,9 +299,9 @@ "integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==" }, "punycode": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz", - "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==" + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==" }, "qs": { "version": "6.5.3", @@ -329,9 +329,9 @@ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, "sshpk": { - "version": "1.17.0", - "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.17.0.tgz", - "integrity": "sha512-/9HIEs1ZXGhSPE8X6Ccm7Nam1z8KcoCqPdI7ecm1N33EzAetWahvQWVqLZtaZQ+IDKX4IyA2o0gBzqIMkAagHQ==", + "version": "1.18.0", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.18.0.tgz", + "integrity": "sha512-2p2KJZTSqQ/I3+HX42EpYOa2l3f8Erv8MWKsy2I9uf4wA7yFIkXRffYdsx86y6z4vHtV8u7g+pPlr8/4ouAxsQ==", "requires": { "asn1": "~0.2.3", "assert-plus": "^1.0.0", @@ -395,4 +395,4 @@ } } } -} \ No newline at end of file +} diff --git a/package.json b/package.json index 723f638..b740613 100644 --- a/package.json +++ b/package.json @@ -7,6 +7,6 @@ "author": "Polarity", "dependencies": { "lodash": "^4.17.21", - "postman-request": "^2.88.1-postman.32" + "postman-request": "^2.88.1-postman.38" } } \ No newline at end of file From 632aac0feccffdca984d5c1678fa0d5c78f12064 Mon Sep 17 00:00:00 2001 From: Ed Date: Tue, 6 Aug 2024 18:07:30 -0400 Subject: [PATCH 04/14] Fix option name --- config/config.js | 2 +- config/config.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/config/config.js b/config/config.js index 5c78d28..0ac0122 100644 --- a/config/config.js +++ b/config/config.js @@ -107,7 +107,7 @@ module.exports = { }, { key: 'incidentDaysAgoToSearch', - name: 'Days ', + name: 'Incident Search Window in Days', description: 'Number of days back to search when searching incidents. Filters based on the date that the Incident was opened. Defaults to 360.', default: 360, diff --git a/config/config.json b/config/config.json index 5f87ce8..b7d78fb 100644 --- a/config/config.json +++ b/config/config.json @@ -111,7 +111,7 @@ }, { "key": "incidentDaysAgoToSearch", - "name": "Days ", + "name": "Incident Search Window in Days ", "description": "Number of days back to search when searching incidents. Filters based on the date that the Incident was opened. Defaults to 360.", "default": 360, "type": "number", From e348b6ceb40caa8b7c5cd3dd0213276a1fb13087 Mon Sep 17 00:00:00 2001 From: Ed Date: Tue, 6 Aug 2024 18:10:31 -0400 Subject: [PATCH 05/14] Change checklist container to use rockylinux:8 --- .github/workflows/run-int-dev-checklist.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/run-int-dev-checklist.yml b/.github/workflows/run-int-dev-checklist.yml index ffa86d7..80e76bd 100644 --- a/.github/workflows/run-int-dev-checklist.yml +++ b/.github/workflows/run-int-dev-checklist.yml @@ -7,7 +7,7 @@ on: jobs: run-integration-development-checklist: runs-on: ubuntu-latest - container: 'centos:7' + container: 'rockylinux:8' steps: - uses: actions/checkout@v2 From 8ef1c85e306dd3e15d71dfd50b9c1fa13279aa10 Mon Sep 17 00:00:00 2001 From: Ed Date: Tue, 6 Aug 2024 18:21:24 -0400 Subject: [PATCH 06/14] Fix incident id lookups --- .../customFunctionalityByType.js | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/functionalityByEntityType/customFunctionalityByType.js b/src/functionalityByEntityType/customFunctionalityByType.js index b56c814..7983342 100644 --- a/src/functionalityByEntityType/customFunctionalityByType.js +++ b/src/functionalityByEntityType/customFunctionalityByType.js @@ -4,7 +4,7 @@ const { mapObject } = require('../dataTransformations'); const queryKnowledgeBase = require('../querying/queryKnowledgeBase'); -const { getTotalKbDocsSummaryTag } = require('./createSummaryTagsFunctions'); +const { getTotalKbDocsSummaryTag, getTableQueryDataSummaryTags} = require('./createSummaryTagsFunctions'); const assetsAndIncidentCustomFunctionality = require('./assetsAndIncidentCustomFunctionality'); @@ -12,6 +12,8 @@ const { knowledgeBaseDisplayStructure, usersDisplayStructure } = require('../displayStructures/index'); +const queryTableData = require("../querying/queryTableData"); +const {tableQueryDisplayStructure} = require("../displayStructures"); /** CUSTOM_FUNCTIONALITY_FOR_STANDARD_ENTITY_TYPES @@ -116,6 +118,16 @@ const CUSTOM_FUNCTIONALITY_FOR_CUSTOM_ENTITY_TYPES = { requestedItem: { tableQueryTableName: 'sc_req_item', displayTabNames: { tableQueryData: 'Requested Items' } + }, + incident: { + queryFunction: queryTableData, + tableQueryTableName: 'incident', + tableQueryQueryString: ({ value }) => `number=${value}`, + createSummaryTags: getTableQueryDataSummaryTags, + displayTabNames: { tableQueryData: 'Incidents' }, + displayStructure: { tableQueryData: tableQueryDisplayStructure }, + // Empty Defaults + tableQuerySummaryTagPaths: false } }; From 5366b94fca9093810955b832c4745d2f20600091 Mon Sep 17 00:00:00 2001 From: Ed Date: Tue, 6 Aug 2024 18:28:56 -0400 Subject: [PATCH 07/14] Update README and option descriptions --- README.md | 22 +++++++++++++--------- config/config.js | 4 ++-- config/config.json | 4 ++-- 3 files changed, 17 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 9aac006..2376706 100644 --- a/README.md +++ b/README.md @@ -22,21 +22,25 @@ The username of the Service Now user you want the integration to authenticate as ### Password The password for the provided username you want the integration to authenticate as. -### Search By String -This will toggle whether or not to search the ServiceNow's Asset Table with strings found in your channels. +### Search By Annotated Entities +This will toggle whether to search ServiceNow for annotated entities found in your channels. The "string" Data Type must also be enabled for this option to have an effect. + +### Enable Incident Search +If checked, the integration will search ServiceNow's Incident Table (incident) for IP Addresses, Domains, CVEs, annotated entities, and any added custom types ### Incident Query Fields -A comma separated list of Fields to query against Incidents. -> NOTE: If a field is not in this list, it will not be searched on on in ServiceNow's Incident Table. -> (This applies to IP Addresses, Domains, and String searches) +A comma separated list of fields to search when querying for Incidents. Incident searches are done for IPs, domains, CVEs, annotated entities and any added custom types. NOTE: If a field is not in this list, it will not be searched for Incident Queries. + +### Incident Search Window in Days +Number of days back to search when searching incidents. Filters based on the date that the Incident was opened. Defaults to 360. ### Enable Asset Search -If checked, the integration will search ServiceNow's Asset Table (alm_asset) for IP Addresses, Domains, CVEs and annotated entities. +If checked, the integration will search ServiceNow's Asset Table (alm_asset) for IP Addresses, Domains, CVEs, annotated entities, and any added custom types. ### Asset Query Fields -A comma separated list of fields to search domains and IPs by in ServiceNow's Asset Table. -> NOTE: If a field is not in this list, it will not be searched on in ServiceNow's Asset Table. -> (This applies to IP Addresses, Domains, and String searches) +A comma separated list of fields to search when querying for Assets. Asset searches are done for IPs, domains, CVEs, annotated entities and any added custom types. NOTE: If a field is not in this list, the field will not be searched in ServiceNow's Asset Table. + +This option defaults to searching the `ci.name` and `ci.asset_tag` fields. The correct fields to search are dependent on your ServiceNow implementation. A common additional field to add is `comments`. ## IP Lookups and Finding Query Fields Because ServiceNow is often customized to fit specific needs, Polarity's ServiceNow Integration offers the ability to look up IPv4 matches on custom Incident and Asset fields. Simply add a comma separated list of custom fields to the `Custom Fields` integration option, and when Polarity recognizes an IP address, it will look up the address in the custom fields you listed and display the results. To determine what value you should put in this field your can reference our guide [**Here**](./HowToFindCustomFields.md) using the dashboard. diff --git a/config/config.js b/config/config.js index 0ac0122..65642db 100644 --- a/config/config.js +++ b/config/config.js @@ -79,7 +79,7 @@ module.exports = { key: 'shouldSearchString', name: 'Search By Annotated Entities', description: - 'This will toggle whether or not to search ServiceNow for annotated entities found in your channels. The "string" Data Type must also be enabled for this option to have an effect.', + 'This will toggle whether to search ServiceNow for annotated entities found in your channels. The "string" Data Type must also be enabled for this option to have an effect.', default: false, type: 'boolean', userCanEdit: false, @@ -119,7 +119,7 @@ module.exports = { key: 'enableAssetSearch', name: 'Enable Asset Search', description: - "If checked, the integration will search ServiceNow's Asset Table (alm_asset) for IP Addresses, Domains, CVEs, annotated entities, and any added custom types", + "If checked, the integration will search ServiceNow's Asset Table (alm_asset) for IP Addresses, Domains, CVEs, annotated entities, and any added custom types.", default: true, type: 'boolean', userCanEdit: false, diff --git a/config/config.json b/config/config.json index b7d78fb..3af910d 100644 --- a/config/config.json +++ b/config/config.json @@ -85,7 +85,7 @@ { "key": "shouldSearchString", "name": "Search By Annotated Entities", - "description": "This will toggle whether or not to search ServiceNow for annotated entities found in your channels. The \"string\" Data Type must also be enabled for this option to have an effect.", + "description": "This will toggle whether to search ServiceNow for annotated entities found in your channels. The \"string\" Data Type must also be enabled for this option to have an effect.", "default": false, "type": "boolean", "userCanEdit": false, @@ -121,7 +121,7 @@ { "key": "enableAssetSearch", "name": "Enable Asset Search", - "description": "If checked, the integration will search ServiceNow's Asset Table (alm_asset) for IP Addresses, Domains, CVEs, annotated entities, and any added custom types", + "description": "If checked, the integration will search ServiceNow's Asset Table (alm_asset) for IP Addresses, Domains, CVEs, annotated entities, and any added custom types.", "default": true, "type": "boolean", "userCanEdit": false, From ab0351ffe030000453b60f06f2f662418e905c48 Mon Sep 17 00:00:00 2001 From: Ed Date: Tue, 6 Aug 2024 19:06:17 -0400 Subject: [PATCH 08/14] Additional fixes for various custom lookups --- .../customFunctionalityByType.js | 87 ++++++++++--------- .../defaultFunctionalityByType.js | 7 +- 2 files changed, 49 insertions(+), 45 deletions(-) diff --git a/src/functionalityByEntityType/customFunctionalityByType.js b/src/functionalityByEntityType/customFunctionalityByType.js index 7983342..a78ce38 100644 --- a/src/functionalityByEntityType/customFunctionalityByType.js +++ b/src/functionalityByEntityType/customFunctionalityByType.js @@ -4,7 +4,10 @@ const { mapObject } = require('../dataTransformations'); const queryKnowledgeBase = require('../querying/queryKnowledgeBase'); -const { getTotalKbDocsSummaryTag, getTableQueryDataSummaryTags} = require('./createSummaryTagsFunctions'); +const { + getTotalKbDocsSummaryTag, + getTableQueryDataSummaryTags +} = require('./createSummaryTagsFunctions'); const assetsAndIncidentCustomFunctionality = require('./assetsAndIncidentCustomFunctionality'); @@ -12,21 +15,31 @@ const { knowledgeBaseDisplayStructure, usersDisplayStructure } = require('../displayStructures/index'); -const queryTableData = require("../querying/queryTableData"); -const {tableQueryDisplayStructure} = require("../displayStructures"); +const queryTableData = require('../querying/queryTableData'); +const { tableQueryDisplayStructure } = require('../displayStructures'); +const numberTableQueryString = ({ value }) => `number=${value}`; +const BUILT_IN_SERVICENOW_TYPE_DEFAULTS = { + queryFunction: queryTableData, + tableQueryTableName: 'incident', + tableQueryQueryString: numberTableQueryString, + createSummaryTags: getTableQueryDataSummaryTags, + displayTabNames: { tableQueryData: 'Incidents' }, + displayStructure: { tableQueryData: tableQueryDisplayStructure }, + tableQuerySummaryTagPaths: ['category', 'phase'] +}; /** CUSTOM_FUNCTIONALITY_FOR_STANDARD_ENTITY_TYPES - * This is where all the magic stems from. This object is where can you specify custom + * This is where all the magic stems from. This object is where can you specify custom * functionality for any standard (non-custom) entity type without really having to - * understand all the gotcha's of the rest of the code and allows you to quickly and - * easily add new type to the integration. In this object the keys are the entity type - * names, and the values are where you specify you custom functionality for that entity + * understand all the gotcha's of the rest of the code and allows you to quickly and + * easily add new type to the integration. In this object the keys are the entity type + * names, and the values are where you specify you custom functionality for that entity * type. * { - * [result of entity.type check]: { + * [result of entity.type check]: { * This object that describes querying and formatting functionality for this entity type - * + * * queryFunction: [default = queryTableData] * async (entity, options, requestWithDefaults, Logger) => { * ...Query code @@ -36,37 +49,37 @@ const {tableQueryDisplayStructure} = require("../displayStructures"); * } * }, check out use in getLookupResults.js for more info * NOTE: It is recommended you create this function in the ./querying folder and import it here. - * + * * tableQueryTableName: [default = "incidents"] * "String used to specify the table name for the Table Query" * NOTE: Omit this key if you use a queryFunction that does not execute queryTableData. - * Can be found one an individual record for the table on the dashboard under the + * Can be found one an individual record for the table on the dashboard under the * "uri" query parameter in the dashboard url excluding the / or %2F, and the ".do" * tableQueryQueryString: [default = numberTableQueryString] * (entity, options) => "String used to specify the query string for the Table Query" * NOTE: Omit this key if you use a queryFunction that does not execute queryTableData. * tableQuerySummaryTagPaths: [default = ["category"]] * ["List of string paths of properties you would like to display from Table Query results"] - * NOTE: The defaults will always be added to your result. Also, omit this key if - * you use a queryFunction that does not execute queryTableData Or if only the + * NOTE: The defaults will always be added to your result. Also, omit this key if + * you use a queryFunction that does not execute queryTableData Or if only the * default paths are needed. - * + * * createSummaryTags: [default = getTableQueryDataSummaryTags] * (entity, results) => { * return ["Summary Tags generated from results for this entity type"]; * } * results is the result object generated in the query function * NOTE: It is recommended you add your custom functionality to createSummaryTagsFunctions.js - * + * * displayTabNames: [default = { tableQueryData: "Incidents" }] * { [key returned from queryFunction]: "Tab name for this query result"} * displayStructure: [default = { tableQueryData: tableQueryDisplayStructure }] * { [key returned from queryFunction]: [{displayStructure}]} * NOTE: A Display Structure shows how we format the data for the front end to display. * Look to ./displayStructures/tableQuery for reference. -* } - * NOTE: If you would like to just use the defaults for an entity type, it can just be - * omitted from this object. If you would like to understand the defaults better you + * } + * NOTE: If you would like to just use the defaults for an entity type, it can just be + * omitted from this object. If you would like to understand the defaults better you * check out the DEFAULT_FUNCTIONALITY_OBJECT in defaultFunctionalityByType.js */ @@ -86,49 +99,44 @@ const CUSTOM_FUNCTIONALITY_FOR_STANDARD_ENTITY_TYPES = { /** CUSTOM_FUNCTIONALITY_FOR_CUSTOM_ENTITY_TYPES * Custom Entity Types work the same as standard entity types with two differences: - * 1.) The Keys on the top level are not the result of the entity.type check, but + * 1.) The Keys on the top level are not the result of the entity.type check, but * instead are the "key" properties specified in the config.js file. - * 2.) Properties in "defaults" get passed along to to all specific custom types, but + * 2.) Properties in "defaults" get passed along to to all specific custom types, but * can be overridden if specified on the individual custom type level. * - * NOTE: If you would like to just use the defaults for an entity type, it can just be - * omitted from this object. If you would like to understand the defaults better you + * NOTE: If you would like to just use the defaults for an entity type, it can just be + * omitted from this object. If you would like to understand the defaults better you * check out the DEFAULT_FUNCTIONALITY_OBJECT in defaultFunctionalityByType.js -*/ + */ const CUSTOM_FUNCTIONALITY_FOR_CUSTOM_ENTITY_TYPES = { - // All Custom Types - defaults: { - tableQuerySummaryTagPaths: ['category', 'phase'] - }, - // Specific Custom Types + // All Custom Type Defaults + defaults: assetsAndIncidentCustomFunctionality, + + // Specific Custom Types which override default above which is to search both incident and asset + // tables. knowledgeBase: { + ...BUILT_IN_SERVICENOW_TYPE_DEFAULTS, queryFunction: queryKnowledgeBase, createSummaryTags: getTotalKbDocsSummaryTag, displayTabNames: { knowledgeBaseData: 'Knowledge Base' }, displayStructure: { knowledgeBaseData: knowledgeBaseDisplayStructure } }, change: { + ...BUILT_IN_SERVICENOW_TYPE_DEFAULTS, tableQueryTableName: 'change_request', displayTabNames: { tableQueryData: 'Changes' } }, request: { + ...BUILT_IN_SERVICENOW_TYPE_DEFAULTS, tableQueryTableName: 'sc_request', displayTabNames: { tableQueryData: 'Requests' } }, requestedItem: { + ...BUILT_IN_SERVICENOW_TYPE_DEFAULTS, tableQueryTableName: 'sc_req_item', displayTabNames: { tableQueryData: 'Requested Items' } }, - incident: { - queryFunction: queryTableData, - tableQueryTableName: 'incident', - tableQueryQueryString: ({ value }) => `number=${value}`, - createSummaryTags: getTableQueryDataSummaryTags, - displayTabNames: { tableQueryData: 'Incidents' }, - displayStructure: { tableQueryData: tableQueryDisplayStructure }, - // Empty Defaults - tableQuerySummaryTagPaths: false - } + incident: BUILT_IN_SERVICENOW_TYPE_DEFAULTS }; const specificCustomTypesWithDefaults = flow( @@ -147,5 +155,4 @@ const customFunctionalityByType = { ...specificCustomTypesWithDefaults }; - -module.exports = customFunctionalityByType; \ No newline at end of file +module.exports = customFunctionalityByType; diff --git a/src/functionalityByEntityType/defaultFunctionalityByType.js b/src/functionalityByEntityType/defaultFunctionalityByType.js index b4ec6ae..5bc8a3a 100644 --- a/src/functionalityByEntityType/defaultFunctionalityByType.js +++ b/src/functionalityByEntityType/defaultFunctionalityByType.js @@ -15,19 +15,16 @@ const assetsAndIncidentCustomFunctionality = require('./assetsAndIncidentCustomF // Not currently used but contains default values if you only want to search the Incident // table and not search the assets table. -const DEFAULT_INCIDENT_ONLY_SEARCH_FUNCTIONALITY_OBJECT = { +const DEFAULT_FUNCTIONALITY_OBJECT = { queryFunction: queryTableData, tableQueryTableName: 'incident', tableQueryQueryString: numberTableQueryString, createSummaryTags: getTableQueryDataSummaryTags, displayTabNames: { tableQueryData: 'Incidents' }, displayStructure: { tableQueryData: tableQueryDisplayStructure }, - // Empty Defaults - tableQuerySummaryTagPaths: false + tableQuerySummaryTagPaths: ['category', 'phase'] }; -const DEFAULT_FUNCTIONALITY_OBJECT = assetsAndIncidentCustomFunctionality; - const defaultFunctionalityForStandardEntityTypes = reduce( (agg, entityType) => ({ ...agg, [entityType]: DEFAULT_FUNCTIONALITY_OBJECT }), {}, From 75e1416938e971d48a13d617bcb7106fcb296b50 Mon Sep 17 00:00:00 2001 From: Ed Date: Tue, 6 Aug 2024 22:54:31 -0400 Subject: [PATCH 09/14] Add additional info about Asset searches --- README.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/README.md b/README.md index 2376706..b4d38d0 100644 --- a/README.md +++ b/README.md @@ -42,6 +42,20 @@ A comma separated list of fields to search when querying for Assets. Asset searc This option defaults to searching the `ci.name` and `ci.asset_tag` fields. The correct fields to search are dependent on your ServiceNow implementation. A common additional field to add is `comments`. +## Searching Assets + +Assets in ServiceNow are commonly found in the Assets table `alm_assets` or the Configuration Item table `cmdb_ci`. The Polarity ServiceNow integration searches the `alm_assets` table as part of its asset search capability but fields within the `cmdb_ci` table can be referenced for searching by prepending the table's column name with `ci.`. As an example, if you'd like to search the `asset_tag` field within the `cmdb_ci` table, you should set the "Asset Query Fields" option to `ci.asset_tag`. + +Common "Asset Query Fields" include: + +* display_name +* name +* asset_tag +* comments +* ci.name +* ci.display_name +* ci.asset_tag + ## IP Lookups and Finding Query Fields Because ServiceNow is often customized to fit specific needs, Polarity's ServiceNow Integration offers the ability to look up IPv4 matches on custom Incident and Asset fields. Simply add a comma separated list of custom fields to the `Custom Fields` integration option, and when Polarity recognizes an IP address, it will look up the address in the custom fields you listed and display the results. To determine what value you should put in this field your can reference our guide [**Here**](./HowToFindCustomFields.md) using the dashboard. From 02975335aea0c49baad33a00d583f04c9361f047 Mon Sep 17 00:00:00 2001 From: Ed Date: Tue, 6 Aug 2024 22:54:56 -0400 Subject: [PATCH 10/14] Ensure default search for new custom types is asset and incident search --- src/functionalityByEntityType/defaultFunctionalityByType.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/functionalityByEntityType/defaultFunctionalityByType.js b/src/functionalityByEntityType/defaultFunctionalityByType.js index 5bc8a3a..44b0fce 100644 --- a/src/functionalityByEntityType/defaultFunctionalityByType.js +++ b/src/functionalityByEntityType/defaultFunctionalityByType.js @@ -4,14 +4,13 @@ const { mergeAndRemoveDuplicates } = require('../dataTransformations'); const { entityTypes, customTypes: customTypesJs } = require('../../config/config'); const { customTypes: customTypesJson } = require('../../config/config.json'); const customTypes = mergeAndRemoveDuplicates(customTypesJs, customTypesJson, 'key'); - +const assetsAndIncidentCustomFunctionality = require('./assetsAndIncidentCustomFunctionality'); const queryTableData = require('../querying/queryTableData'); const numberTableQueryString = ({ value }) => `number=${value}`; const { getTableQueryDataSummaryTags } = require('./createSummaryTagsFunctions'); const { tableQueryDisplayStructure } = require('../displayStructures/index'); -const assetsAndIncidentCustomFunctionality = require('./assetsAndIncidentCustomFunctionality'); // Not currently used but contains default values if you only want to search the Incident // table and not search the assets table. @@ -34,7 +33,7 @@ const defaultFunctionalityForStandardEntityTypes = reduce( const defaultFunctionalityForCustomEntityTypes = flow( map(get('key')), reduce( - (agg, entityType) => ({ ...agg, [entityType]: DEFAULT_FUNCTIONALITY_OBJECT }), + (agg, entityType) => ({ ...agg, [entityType]: assetsAndIncidentCustomFunctionality }), {} ) )(customTypes); From 5ad0c048e79fffcea32889885bd4c0433bb0382c Mon Sep 17 00:00:00 2001 From: Ed Date: Thu, 8 Aug 2024 09:51:39 -0400 Subject: [PATCH 11/14] Update default asset search fields --- config/config.js | 2 +- config/config.json | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/config/config.js b/config/config.js index 65642db..b6402ab 100644 --- a/config/config.js +++ b/config/config.js @@ -130,7 +130,7 @@ module.exports = { name: 'Asset Query Fields', description: "A comma separated list of fields to search when querying for Assets. Asset searches are done for IPs, domains, CVEs, annotated entities and any added custom types. NOTE: If a field is not in this list, the field will not be searched in ServiceNow's Asset Table.", - default: 'ci.name, ci.asset_tag, short_description', + default: 'name, display_name, asset_tag, ci.name, ci.asset_tag', type: 'text', userCanEdit: false, adminOnly: true diff --git a/config/config.json b/config/config.json index 3af910d..d85b6ec 100644 --- a/config/config.json +++ b/config/config.json @@ -130,8 +130,8 @@ { "key": "assetTableFields", "name": "Asset Query Fields", - "description": "A comma separated list of fields to search when querying for Assets. Asset searches are done for IPs, domains, CVEs, annotated entities and any added custom types. NOTE: If a field is not in this list, the field will not be searched in ServiceNow's Asset Table.", - "default": "ci.name, ci.asset_tag, short_description", + "description": "A comma separated list of fields to search when querying for Assets. Asset searches are done for IPs, domains, CVEs, annotated entities and any added custom types. NOTE: If a field is not in this list, the field will not be searched in ServiceNow's Asset (alm_asset) Table.", + "default": "name, display_name, asset_tag, ci.name, ci.asset_tag", "type": "text", "userCanEdit": false, "adminOnly": true From 96f011c7f45db7cedc9cdacad190152c78f26bb2 Mon Sep 17 00:00:00 2001 From: Ed Date: Thu, 8 Aug 2024 09:51:57 -0400 Subject: [PATCH 12/14] Simplify default custom functionality --- .../customFunctionalityByType.js | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/functionalityByEntityType/customFunctionalityByType.js b/src/functionalityByEntityType/customFunctionalityByType.js index a78ce38..020f384 100644 --- a/src/functionalityByEntityType/customFunctionalityByType.js +++ b/src/functionalityByEntityType/customFunctionalityByType.js @@ -110,33 +110,29 @@ const CUSTOM_FUNCTIONALITY_FOR_STANDARD_ENTITY_TYPES = { */ const CUSTOM_FUNCTIONALITY_FOR_CUSTOM_ENTITY_TYPES = { // All Custom Type Defaults - defaults: assetsAndIncidentCustomFunctionality, + defaults: BUILT_IN_SERVICENOW_TYPE_DEFAULTS, // Specific Custom Types which override default above which is to search both incident and asset // tables. knowledgeBase: { - ...BUILT_IN_SERVICENOW_TYPE_DEFAULTS, queryFunction: queryKnowledgeBase, createSummaryTags: getTotalKbDocsSummaryTag, displayTabNames: { knowledgeBaseData: 'Knowledge Base' }, displayStructure: { knowledgeBaseData: knowledgeBaseDisplayStructure } }, change: { - ...BUILT_IN_SERVICENOW_TYPE_DEFAULTS, tableQueryTableName: 'change_request', displayTabNames: { tableQueryData: 'Changes' } }, request: { - ...BUILT_IN_SERVICENOW_TYPE_DEFAULTS, tableQueryTableName: 'sc_request', displayTabNames: { tableQueryData: 'Requests' } }, requestedItem: { - ...BUILT_IN_SERVICENOW_TYPE_DEFAULTS, tableQueryTableName: 'sc_req_item', displayTabNames: { tableQueryData: 'Requested Items' } }, - incident: BUILT_IN_SERVICENOW_TYPE_DEFAULTS + incident: {} }; const specificCustomTypesWithDefaults = flow( From 0966a1e9b5197c78441cb5f7f5f820d7188cd122 Mon Sep 17 00:00:00 2001 From: Ed Date: Thu, 8 Aug 2024 10:19:57 -0400 Subject: [PATCH 13/14] Add additional custom type information --- README.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/README.md b/README.md index b4d38d0..16bd554 100644 --- a/README.md +++ b/README.md @@ -67,6 +67,14 @@ They are usually prepended with a `u_` and then the name of the custom field, in |---| |*Custom Query Field Example*| +## Known Issues + +If adding additional custom types to the ServiceNow integration, please ensure the added custom types do not also match on the built-in custom types for Incidents, Change Requests, Knowledge Base, Request, and Request Item ids. + +As an example, if you add a new custom type that matches on the string `INC0001234`, this will conflict with the integration's built-in custom type for looking up incidents by ID. + +Ensure that newly added custom types (e.g., for hostnames), does not overlap with these custom types. + ## Polarity Polarity is a memory-augmentation platform that improves and accelerates analyst decision making. For more information about the Polarity platform please see: From c43ef5b7854e1b23b018b39c75db1c0cc2102b96 Mon Sep 17 00:00:00 2001 From: Ed Date: Thu, 8 Aug 2024 10:25:28 -0400 Subject: [PATCH 14/14] Add known issues section --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 16bd554..b1897f8 100644 --- a/README.md +++ b/README.md @@ -69,11 +69,11 @@ They are usually prepended with a `u_` and then the name of the custom field, in ## Known Issues -If adding additional custom types to the ServiceNow integration, please ensure the added custom types do not also match on the built-in custom types for Incidents, Change Requests, Knowledge Base, Request, and Request Item ids. +If adding additional custom types to the ServiceNow integration, ensure the added custom types do not also match on the built-in custom types for Incidents, Change Requests, Knowledge Base, Request, and Request Item ids. As an example, if you add a new custom type that matches on the string `INC0001234`, this will conflict with the integration's built-in custom type for looking up incidents by ID. -Ensure that newly added custom types (e.g., for hostnames), does not overlap with these custom types. +Ensure that newly added custom types (e.g., for hostnames), do not overlap with these custom types. ## Polarity