From b86b41704e3947163aecfb53de2bf32fae2e2055 Mon Sep 17 00:00:00 2001 From: Mai Morag <81917647+maimorag@users.noreply.github.com> Date: Sun, 18 Jun 2023 13:29:59 +0300 Subject: [PATCH] Xsoar file management (#26455) * commonserver.js * working * ok * fileDeleteAttachmentCommand * read me * removing examples file * removing changes * rl update * small fixes * removing extra space * RL * remove RL * adding version * RL base * commit * temp * coreApiFileCheckCommand fix * fixing fileDeleteCommand * fileUploadCommand fix * rl * Bump pack from version Base to 1.32.5. * after conflicts * Rl * xsoar concate bug fix * docstring * undo changes in unrelevant files * removing _mm * Bump pack from version Base to 1.32.6. * Bump pack from version Base to 1.32.7. * val changes * removing notes * small fixes * cr fixes * fileUploadCommand fix * small update * Update Packs/DemistoRESTAPI/Integrations/CoreRESTAPI/CoreRESTAPI.yml Co-authored-by: ShirleyDenkberg <62508050+ShirleyDenkberg@users.noreply.github.com> * Update Packs/DemistoRESTAPI/ReleaseNotes/1_3_26.md Co-authored-by: ShirleyDenkberg <62508050+ShirleyDenkberg@users.noreply.github.com> * Update Packs/DemistoRESTAPI/ReleaseNotes/1_3_26.md Co-authored-by: ShirleyDenkberg <62508050+ShirleyDenkberg@users.noreply.github.com> * Update Packs/DemistoRESTAPI/ReleaseNotes/1_3_26.md Co-authored-by: ShirleyDenkberg <62508050+ShirleyDenkberg@users.noreply.github.com> * Update Packs/DemistoRESTAPI/Integrations/CoreRESTAPI/CoreRESTAPI.yml Co-authored-by: ShirleyDenkberg <62508050+ShirleyDenkberg@users.noreply.github.com> * Update Packs/DemistoRESTAPI/Integrations/CoreRESTAPI/CoreRESTAPI.yml Co-authored-by: ShirleyDenkberg <62508050+ShirleyDenkberg@users.noreply.github.com> * Update Packs/DemistoRESTAPI/Integrations/CoreRESTAPI/CoreRESTAPI.yml Co-authored-by: ShirleyDenkberg <62508050+ShirleyDenkberg@users.noreply.github.com> * Update Packs/DemistoRESTAPI/Integrations/CoreRESTAPI/CoreRESTAPI.yml Co-authored-by: ShirleyDenkberg <62508050+ShirleyDenkberg@users.noreply.github.com> * Update Packs/DemistoRESTAPI/ReleaseNotes/1_3_26.md Co-authored-by: ShirleyDenkberg <62508050+ShirleyDenkberg@users.noreply.github.com> * Update Packs/DemistoRESTAPI/ReleaseNotes/1_3_26.md Co-authored-by: ShirleyDenkberg <62508050+ShirleyDenkberg@users.noreply.github.com> * Update Packs/DemistoRESTAPI/Integrations/CoreRESTAPI/CoreRESTAPI.yml Co-authored-by: ShirleyDenkberg <62508050+ShirleyDenkberg@users.noreply.github.com> * Update Packs/DemistoRESTAPI/Integrations/CoreRESTAPI/CoreRESTAPI.yml Co-authored-by: ShirleyDenkberg <62508050+ShirleyDenkberg@users.noreply.github.com> * Update Packs/DemistoRESTAPI/Integrations/CoreRESTAPI/CoreRESTAPI.yml Co-authored-by: ShirleyDenkberg <62508050+ShirleyDenkberg@users.noreply.github.com> * Update Packs/DemistoRESTAPI/Integrations/CoreRESTAPI/README.md Co-authored-by: ShirleyDenkberg <62508050+ShirleyDenkberg@users.noreply.github.com> * Update Packs/DemistoRESTAPI/Integrations/CoreRESTAPI/CoreRESTAPI.yml Co-authored-by: ShirleyDenkberg <62508050+ShirleyDenkberg@users.noreply.github.com> * Update Packs/DemistoRESTAPI/Integrations/CoreRESTAPI/README.md Co-authored-by: ShirleyDenkberg <62508050+ShirleyDenkberg@users.noreply.github.com> * Update Packs/DemistoRESTAPI/Integrations/CoreRESTAPI/README.md Co-authored-by: ShirleyDenkberg <62508050+ShirleyDenkberg@users.noreply.github.com> * Update Packs/DemistoRESTAPI/Integrations/CoreRESTAPI/README.md Co-authored-by: ShirleyDenkberg <62508050+ShirleyDenkberg@users.noreply.github.com> * Update Packs/DemistoRESTAPI/Integrations/CoreRESTAPI/README.md Co-authored-by: ShirleyDenkberg <62508050+ShirleyDenkberg@users.noreply.github.com> * Update Packs/DemistoRESTAPI/Integrations/CoreRESTAPI/README.md Co-authored-by: ShirleyDenkberg <62508050+ShirleyDenkberg@users.noreply.github.com> * Update Packs/DemistoRESTAPI/Integrations/CoreRESTAPI/README.md Co-authored-by: ShirleyDenkberg <62508050+ShirleyDenkberg@users.noreply.github.com> * Update Packs/DemistoRESTAPI/Integrations/CoreRESTAPI/README.md Co-authored-by: ShirleyDenkberg <62508050+ShirleyDenkberg@users.noreply.github.com> * specifying what the FileResult function does * RL * known word * ignore word * adding to read me * Update Packs/Base/ReleaseNotes/1_32_7.md Co-authored-by: Shelly Tzohar <45915502+Shellyber@users.noreply.github.com> * removed from read me old demisto command * adding to ignore * ignore * Bump pack from version Base to 1.32.8. * m * Bump pack from version Base to 1.32.9. * Bump pack from version Base to 1.32.10. * Bump pack from version Base to 1.32.11. * Bump pack from version Base to 1.32.12. * demo fixes * Bump pack from version Base to 1.32.15. --------- Co-authored-by: Content Bot Co-authored-by: ShirleyDenkberg <62508050+ShirleyDenkberg@users.noreply.github.com> Co-authored-by: Shelly Tzohar <45915502+Shellyber@users.noreply.github.com> --- Packs/Base/.pack-ignore | 2 + Packs/Base/ReleaseNotes/1_32_15.md | 5 + .../Base/Scripts/CommonServer/CommonServer.js | 21 ++ Packs/Base/pack_metadata.json | 2 +- Packs/DemistoRESTAPI/.pack-ignore | 2 +- .../Integrations/CoreRESTAPI/CoreRESTAPI.js | 260 +++++++++++++++++- .../Integrations/CoreRESTAPI/CoreRESTAPI.yml | 47 +++- .../Integrations/CoreRESTAPI/README.md | 82 ++++++ Packs/DemistoRESTAPI/ReleaseNotes/1_3_28.md | 9 + Packs/DemistoRESTAPI/pack_metadata.json | 2 +- 10 files changed, 427 insertions(+), 5 deletions(-) create mode 100644 Packs/Base/ReleaseNotes/1_32_15.md create mode 100644 Packs/DemistoRESTAPI/ReleaseNotes/1_3_28.md diff --git a/Packs/Base/.pack-ignore b/Packs/Base/.pack-ignore index d8945f5eecca..05cc6ed5a614 100644 --- a/Packs/Base/.pack-ignore +++ b/Packs/Base/.pack-ignore @@ -76,6 +76,8 @@ refang indicatorssearcher FeedIndicatorType str +FileResult +JavaScript [tests_require_network] CommonServerPython diff --git a/Packs/Base/ReleaseNotes/1_32_15.md b/Packs/Base/ReleaseNotes/1_32_15.md new file mode 100644 index 000000000000..3fda549b2ec5 --- /dev/null +++ b/Packs/Base/ReleaseNotes/1_32_15.md @@ -0,0 +1,5 @@ + +#### Scripts + +##### CommonServer +Added a **FileResult** function to create a file from the given data in *JavaScript*. diff --git a/Packs/Base/Scripts/CommonServer/CommonServer.js b/Packs/Base/Scripts/CommonServer/CommonServer.js index e6f412f60741..eebff2617f8f 100644 --- a/Packs/Base/Scripts/CommonServer/CommonServer.js +++ b/Packs/Base/Scripts/CommonServer/CommonServer.js @@ -2103,3 +2103,24 @@ function mergeContextLists(newItems, oldItems, objectKey) { var newItemsByKey = newItems.reduce(toMapByKey, {}); return Object.values(Object.assign(oldItemsByKey, newItemsByKey)).filter(function() {return !e['remove']}); } + +/** + * Creates a file from the given data. + * @param {Object} entryType - The entry type from entryTypes. + * @param {string} file_name - The name of the file. + * @param {string} file_content - The content of the file. + * @param {boolean} human_readable - (Optional) if human_readable isn't passed the human readable would be the name of the file. + * @returns {dict} A Demisto war room entry. + */ +function fileResult(file_name, file_content,entryType=null, human_readable_optional=null){ + + fileEntryId = saveFile(file_content); + HumanReadable = (!(human_readable_optional)) ? `${file_name} uploaded with entryID: ${fileEntryId}.` : human_readable_optional; + return { + Type: entryType, + FileID: fileEntryId, + File: file_name, + Contents: file_content, + HumanReadable: HumanReadable + }; +}; diff --git a/Packs/Base/pack_metadata.json b/Packs/Base/pack_metadata.json index 9d10ed1ec696..dd108ae833f3 100644 --- a/Packs/Base/pack_metadata.json +++ b/Packs/Base/pack_metadata.json @@ -2,7 +2,7 @@ "name": "Base", "description": "The base pack for Cortex XSOAR.", "support": "xsoar", - "currentVersion": "1.32.14", + "currentVersion": "1.32.15", "author": "Cortex XSOAR", "serverMinVersion": "6.0.0", "url": "https://www.paloaltonetworks.com/cortex", diff --git a/Packs/DemistoRESTAPI/.pack-ignore b/Packs/DemistoRESTAPI/.pack-ignore index 2d48acc4eb7e..f13af5a829bc 100644 --- a/Packs/DemistoRESTAPI/.pack-ignore +++ b/Packs/DemistoRESTAPI/.pack-ignore @@ -20,4 +20,4 @@ ignore=RM106 ignore=SC106 [file:CoreRESTAPI.yml] -ignore=IN139 \ No newline at end of file +ignore=IN139,RM110 \ No newline at end of file diff --git a/Packs/DemistoRESTAPI/Integrations/CoreRESTAPI/CoreRESTAPI.js b/Packs/DemistoRESTAPI/Integrations/CoreRESTAPI/CoreRESTAPI.js index 287484b04f56..b07d5044fa63 100644 --- a/Packs/DemistoRESTAPI/Integrations/CoreRESTAPI/CoreRESTAPI.js +++ b/Packs/DemistoRESTAPI/Integrations/CoreRESTAPI/CoreRESTAPI.js @@ -132,7 +132,7 @@ sendMultipart = function (uri, entryID, body) { }; var sendRequest = function(method, uri, body, raw) { - var requestUrl = getRequestURL(uri) + var requestUrl = getRequestURL(uri); var key = params.apikey? params.apikey : (params.creds_apikey? params.creds_apikey.password : ''); if (key == ''){ throw 'API Key must be provided.'; @@ -313,6 +313,256 @@ var installPacks = function(packs_to_install, file_url, entry_id, skip_verify, s } }; + +/* helper functions */ + +/** + * deletes an entry by entryID by the key_to_delete +Arguments: + @param {String} file_content -- content of the file to upload + @param {String} file_name -- name of the file in the dest incident + @param {String} key_to_delete -- the name of the key to delete + @param {String} incident_id -- the incident id +Returns: + CommandResults +""" + */ +var uploadFile= function(incident_id, file_content, file_name) { + var body = { + file: + { + value: [file_content], + options: { + filename: [file_name], + contentType: 'multipart/form-data' + } + }, + }; + var res = httpMultipart(`/entry/upload/${incident_id}`,file_content ,body); + if (isError(res[0])) { + throw res[0].Contents; + } + return res; +}; + +/** + * deletes an entry by entryID by the key_to_delete +Arguments: + @param {String} key_to_delete -- the name of the key to delete + @param {String} incident_id -- the incident id +Returns: + CommandResults +""" + */ +var deleteContextRequest = function (incident_id, key_to_delete) { + var body = JSON.stringify({ + "args": null, + "id": "", + "investigationId": `${incident_id}`, + "data": `!DeleteContext key=${key_to_delete}\n`, + "markdown": false, + "version": 0 + }); + return sendRequest('POST', '/entry', body); +}; + + +/** + * deletes a file by entryID +Arguments: + @param {String} delete_artifact -- in order to delete the artifact + @param {String} entry_id -- entry ID of the file +Returns: + CommandResults +""" + */ +var deleteFileRequest = function (entry_id, delete_artifact = true) { + const body_content = JSON.stringify({ + id: entry_id, + deleteArtifact: delete_artifact}); + + return sendRequest( 'POST', '/entry/delete/v2', body_content); +}; + + +/** + * Sends http request to delete attachment +Arguments: + @param {String} incident_id -- incident id to upload the file to + @param {String} file_path -- the file path to delete + @param {String} field_name -- Name of the field (type attachment) you want to remove the attachment +Returns: + Results +""" + */ +var deleteAttachmentRequest=function(incident_id, attachment_path, field_name = 'attachment') { + body = JSON.stringify({ + fieldName: field_name, + files: { + [attachment_path]: { + path: attachment_path + } + }, + originalAttachments: [ + { + path: attachment_path + } + ] + }); + try{ + return sendRequest('POST', `/incident/remove/${incident_id}`, body); + } + catch (e) { + throw new Error(`File already deleted or not found.\n${e}`); + } +}; + +/** + * Upload a new file +Arguments: + @param {String} incident_id -- incident id to upload the file to + @param {String} file_content -- content of the file to upload + @param {String} file_name -- name of the file in the dest incident + @param {String} entryID -- entry ID of the file +Returns: + CommandResults -- Readable output +Note: + You can give either the entryID or file_name. +""" + */ +var fileUploadCommand = function(incident_id, file_content, file_name, entryID ) { + incident_id = (incident_id === 'undefined')? investigation.id: incident_id; + if (incident_id!=investigation.id){ + log(`Note that the file would be uploaded to ${incident_id} from incident ${investigation.id}`); + } + if ((!file_name) && (!entryID)) { + throw 'Either file_name or entry_id argument must be provided.'; + } + var fileId = ''; + if ((!entryID)) { + response = uploadFile(incident_id, file_content, file_name); + fileId = saveFile(file_content); + } else { + if (file_name === undefined) { + file_name = dq(invContext, `File(val.EntryID == ${entryID}).Name`); + } + if (Array.isArray(file_name)) { + if (file_name.length > 0) { + file_name = file_name[0]; + } else { + file_name = undefined; + } + } + response_multi= sendMultipart(`/incident/upload/${incident_id}`,entryID,'{}'); + return `The file ${entryID} uploaded successfully to incident ${incident_id}. `; + } + var md = `File ${file_name} uploaded successfully to incident ${incident_id}.`; + fileId = file_name ? fileId : entryID; + return { + Type: entryTypes.file, + FileID: fileId, + File: file_name, + Contents: file_content, + HumanReadable: md + }; +}; + + + +/** + * Deletes a specific file. +Arguments: + @param {String} entryId -- entry ID of the file +Returns: + Message that the file was deleted successfully + entry_id +""" + */ +// getting the context data +var fileDeleteCommand = function(EntryID) { + files = invContext['File']; + if (!files){ + throw new Error(`Files not found.`); + } + files = (invContext['File'] instanceof Array)? invContext['File']:[invContext['File']]; + if (files[0]=='undefined'){ + throw new Error(`Files not found.`); + + } + var edit_content_data_files = [] + var not_found = true + for (var i = 0 ;i <=Object.keys(files).length - 1; i++) { + if (files[i]['EntryID'] != EntryID) { + edit_content_data_files.push(files[i]); + } + else{ + not_found= false + } + + } + if(not_found){ + throw new Error(`File already deleted or not found.`); + } + deleteContextRequest(investigation.id, 'File'); + deleteFileRequest(EntryID); + let context = { + 'File(val.MD5==obj.MD5)': createContext(edit_content_data_files) + }; + return {Type: entryTypes.note, + Contents: '', + ContentsType: formats.json, + EntryContext: context, + HumanReadable: `File ${EntryID} was deleted successfully.`}; + + +} + + +/** + This command checks if the file is existing. + Arguments: + @param {String} EntryID -- entry ID of the file + Returns: + Dictionary with EntryID as key and boolean if the file exists as value. +*/ +function coreApiFileCheckCommand(EntryID) { + files = invContext['File']instanceof Array? invContext['File']:[invContext['File']]; + var file_found = false; + var human_readable = `File ${EntryID} isn't exists`; + if (typeof files['0'] !== 'undefined') { + for (var i = 0 ;i <=Object.keys(files).length - 1; i++) { + if (files[i]['EntryID'] == EntryID) { + file_found= true ; + human_readable = `File ${EntryID} exists`; + } + } + } + return { + Type: entryTypes.note, + Contents: {[EntryID]:file_found}, + HumanReadable: human_readable, + EntryContext: {[`IsFileExists(val.${EntryID}==${EntryID})`]:{[EntryID]:file_found}} + }; + + +}; + +/** + This command deletes attachment from an incident. + Arguments: + @param {String} incident_id -- incident id to delete the file from + @param {String} attachment_path -- the file path + @param {String} field_name -- Name of the field (type attachment) you want to remove the attachment + Returns: + Show a message that the file was deleted successfully +*/ +var fileDeleteAttachmentCommand = function (attachment_path, incident_id, field_name){ + incident_id = (incident_id=='undefined')? investigation.id: incident_id; + deleteAttachmentRequest(incident_id, attachment_path, field_name); + return `Attachment ${attachment_path} deleted `; +}; + + + switch (command) { case 'test-module': sendRequest('GET','user'); @@ -360,6 +610,14 @@ switch (command) { case 'demisto-api-install-packs': case 'core-api-install-packs': return installPacks(args.packs_to_install, args.file_url, args.entry_id, args.skip_verify, args.skip_validation); + case 'core-api-file-upload': + return fileUploadCommand(args.incident_id, args.file_content, args.file_name, args.entry_id) + case 'core-api-file-delete': + return fileDeleteCommand(args.entry_id); + case 'core-api-file-attachment-delete': + return fileDeleteAttachmentCommand(args.file_path, args.incident_id, args.field_name); + case 'core-api-file-check': + return coreApiFileCheckCommand(args.entry_id); default: throw 'Core REST APIs - unknown command'; } diff --git a/Packs/DemistoRESTAPI/Integrations/CoreRESTAPI/CoreRESTAPI.yml b/Packs/DemistoRESTAPI/Integrations/CoreRESTAPI/CoreRESTAPI.yml index 7c875e28e9c8..cfb35c0c4b5a 100644 --- a/Packs/DemistoRESTAPI/Integrations/CoreRESTAPI/CoreRESTAPI.yml +++ b/Packs/DemistoRESTAPI/Integrations/CoreRESTAPI/CoreRESTAPI.yml @@ -253,7 +253,52 @@ script: predefined: - 'true' - 'false' - description: Upload packs to Core server from url or the marketplace. + description: Upload packs to the core server from the URL or marketplace. + - name: core-api-file-upload + arguments: + - name: incident_id + required: false + description: The incident's ID. + - name: file_name + required: false + description: "The new file's name." + - name: file_content + description: "The new file's content." + - name: entry_id + description: "The War Room entry ID of the pack zip file." + required: false + isArray: false + description: Upload to the incident a file that the user provided according to the entry_id or the content of the file. + - name: core-api-file-delete + arguments: + - name: entry_id + description: "The War Room entry ID of the file." + required: true + isArray: false + description: Delete a file from Cortex XSOAR by entry_id. + - name: core-api-file-attachment-delete + arguments: + - name: incident_id + required: true + description: The incident's ID. + - name: file_path + required: true + description: "The file's path." + - name: field_name + defaultValue: 'attachment' + required: false + description: "Name of the field (type attachment) from which to remove the attachment." + description: Delete the attachment from the incident and from the Cortex XSOAR server. + - name: core-api-file-check + arguments: + - name: entry_id + description: "The War Room entry ID of the file." + required: true + isArray: false + description: Check if the file exists in Cortex XSOAR by entry_id. + outputs: + - description: Dictionary with EntryID as the key and boolean if the file exists as a value. + contextPath: IsFileExists runonce: false tests: - No tests diff --git a/Packs/DemistoRESTAPI/Integrations/CoreRESTAPI/README.md b/Packs/DemistoRESTAPI/Integrations/CoreRESTAPI/README.md index 486b1c898f4e..5b126c09d703 100644 --- a/Packs/DemistoRESTAPI/Integrations/CoreRESTAPI/README.md +++ b/Packs/DemistoRESTAPI/Integrations/CoreRESTAPI/README.md @@ -250,3 +250,85 @@ There is no context output for this command. #### Human Readable Output >The following packs installed successfully: AutoFocus + +### core-api-file-upload + +*** +Upload to the incident a file that the user provided according to the entry_id or the content of the file. + +#### Base Command + +`core-api-file-upload` + +#### Input + +| **Argument Name** | **Description** | **Required** | +| --- | --- | --- | +| incident_id | The incident's ID. | Required | +| file_name | The new file's name. | Optional | +| file_content | The new file's content. | Optional | +| entry_id | The War Room entry ID of the pack zip file. | Optional | + +#### Context Output + +There is no context output for this command. +### core-api-file-delete + +*** +Delete a file from Cortex XSOAR by entry_id. + +#### Base Command + +`core-api-file-delete` + +#### Input + +| **Argument Name** | **Description** | **Required** | +| --- | --- | --- | +| entry_id | The War Room entry ID of the file. | Required | + +#### Context Output + +There is no context output for this command. +### core-api-file-attachment-delete + +*** +Delete the attachment from the incident and from the Cortex XSOAR server. + +#### Base Command + +`core-api-file-attachment-delete` + +#### Input + +| **Argument Name** | **Description** | **Required** | +| --- | --- | --- | +| incident_id | The incident's ID. | Required | +| file_path | The file's path. | Required | +| field_name | Name of the field (type attachment) from which to remove the attachment. Default is attachment. | Optional | + +#### Command example +```!core-api-file-attachment-delete file_path=1@1 incident_id=1``` +#### Human Readable Output + +>Attachment 1@1 deleted. +### core-api-file-check + +*** +Check if the file exists in Cortex XSOAR by entry_id. + +#### Base Command + +`core-api-file-check` + +#### Input + +| **Argument Name** | **Description** | **Required** | +| --- | --- | --- | +| entry_id | The War Room entry ID of the file. | Required | + +#### Context Output + +| **Path** | **Type** | **Description** | +| --- | --- | --- | +| IsFileExists | unknown | Dictionary with EntryID as the key and boolean if the file exists as a value. | diff --git a/Packs/DemistoRESTAPI/ReleaseNotes/1_3_28.md b/Packs/DemistoRESTAPI/ReleaseNotes/1_3_28.md new file mode 100644 index 000000000000..e32c7e0e4f29 --- /dev/null +++ b/Packs/DemistoRESTAPI/ReleaseNotes/1_3_28.md @@ -0,0 +1,9 @@ + +#### Integrations + +##### Core REST API +Added the following commands: +- ***core-api-file-upload*** +- ***core-api-file-delete*** +- ***core-api-file-attachment-delete*** +- ***core-api-file-check*** diff --git a/Packs/DemistoRESTAPI/pack_metadata.json b/Packs/DemistoRESTAPI/pack_metadata.json index c4ab0accc257..d711efd03f7e 100644 --- a/Packs/DemistoRESTAPI/pack_metadata.json +++ b/Packs/DemistoRESTAPI/pack_metadata.json @@ -2,7 +2,7 @@ "name": "Cortex REST API", "description": "Use Demisto REST APIs", "support": "xsoar", - "currentVersion": "1.3.27", + "currentVersion": "1.3.28", "author": "Cortex XSOAR", "url": "https://www.paloaltonetworks.com/cortex", "email": "",