diff --git a/Packs/Code42/Integrations/Code42/Code42.py b/Packs/Code42/Integrations/Code42/Code42.py index 0347abbc3263..f069d0675ac5 100644 --- a/Packs/Code42/Integrations/Code42/Code42.py +++ b/Packs/Code42/Integrations/Code42/Code42.py @@ -8,6 +8,8 @@ import py42.sdk import py42.settings from datetime import datetime +from py42.services.detectionlists.departing_employee import DepartingEmployeeFilters +from py42.services.detectionlists.high_risk_employee import HighRiskEmployeeFilters from py42.sdk.queries.fileevents.file_event_query import FileEventQuery from py42.sdk.queries.fileevents.filters import ( MD5, @@ -86,21 +88,6 @@ "osHostName": "Hostname", } -CODE42_FILE_CATEGORY_MAPPER = { - "SourceCode": "SOURCE_CODE", - "Audio": "AUDIO", - "Executable": "EXECUTABLE", - "Document": "DOCUMENT", - "Image": "IMAGE", - "PDF": "PDF", - "Presentation": "PRESENTATION", - "Script": "SCRIPT", - "Spreadsheet": "SPREADSHEET", - "Video": "VIDEO", - "VirtualDiskImage": "VIRTUAL_DISK_IMAGE", - "Archive": "ARCHIVE", -} - SECURITY_EVENT_HEADERS = [ "EventType", "FileName", @@ -198,7 +185,7 @@ def remove_user_from_departing_employee(self, username): def get_all_departing_employees(self, results, filter_type): res = [] results = int(results) if results else 50 - filter_type = filter_type if filter_type else "OPEN" + filter_type = filter_type if filter_type else DepartingEmployeeFilters.OPEN pages = self._get_sdk().detectionlists.departing_employee.get_all(filter_type=filter_type) for page in pages: page_json = json.loads(page.text) @@ -236,7 +223,7 @@ def remove_user_risk_tags(self, username, risk_tags): def get_all_high_risk_employees(self, risk_tags, results, filter_type): risk_tags = argToList(risk_tags) results = int(results) if results else 50 - filter_type = filter_type if filter_type else "OPEN" + filter_type = filter_type if filter_type else HighRiskEmployeeFilters.OPEN res = [] pages = self._get_sdk().detectionlists.high_risk_employee.get_all(filter_type=filter_type) for page in pages: @@ -538,8 +525,24 @@ def _create_exposure_filter(exposure_arg): return ExposureType.is_in(exposure_arg) -def _get_file_category_value(key): - return CODE42_FILE_CATEGORY_MAPPER.get(key, "UNCATEGORIZED") +def get_file_category_value(key): + # Meant to handle all possible cases + key = key.lower().replace("-", "").replace("_", "") + category_map = { + "sourcecode": FileCategory.SOURCE_CODE, + "audio": FileCategory.AUDIO, + "executable": FileCategory.EXECUTABLE, + "document": FileCategory.DOCUMENT, + "image": FileCategory.IMAGE, + "pdf": FileCategory.PDF, + "presentation": FileCategory.PRESENTATION, + "script": FileCategory.SCRIPT, + "spreadsheet": FileCategory.SPREADSHEET, + "video": FileCategory.VIDEO, + "virtualdiskimage": FileCategory.VIRTUAL_DISK_IMAGE, + "archive": FileCategory.ZIP, + } + return category_map.get(key, "UNCATEGORIZED") class ObservationToSecurityQueryMapper(object): @@ -640,7 +643,7 @@ def _create_file_category_filters(self): observed_file_categories = self._observation_data.get("fileCategories") if observed_file_categories: categories = [ - _get_file_category_value(c.get("category")) + get_file_category_value(c.get("category")) for c in observed_file_categories if c.get("isSignificant") and c.get("category") ] @@ -801,7 +804,7 @@ def departingemployee_remove_command(client, args): @logger def departingemployee_get_all_command(client, args): results = args.get("results", 50) - filter_type = args.get("filtertype", "OPEN") + filter_type = args.get("filtertype", DepartingEmployeeFilters.OPEN) employees = client.get_all_departing_employees(results, filter_type) if not employees: return CommandResults( @@ -905,7 +908,7 @@ def highriskemployee_remove_command(client, args): def highriskemployee_get_all_command(client, args): tags = args.get("risktags") results = args.get("results", 50) - filter_type = args.get("filtertype", "OPEN") + filter_type = args.get("filtertype", HighRiskEmployeeFilters.OPEN) employees = client.get_all_high_risk_employees(tags, results, filter_type) if not employees: return CommandResults( diff --git a/Packs/Code42/Integrations/Code42/Code42.yml b/Packs/Code42/Integrations/Code42/Code42.yml index c1efed0529d0..91f1dde2e469 100644 --- a/Packs/Code42/Integrations/Code42/Code42.yml +++ b/Packs/Code42/Integrations/Code42/Code42.yml @@ -805,7 +805,7 @@ script: - contextPath: Code42.DepartingEmployee.DepartureDate description: The departure date for the Departing Employee. type: Unknown - dockerimage: demisto/py42:1.0.0.10664 + dockerimage: demisto/py42:1.0.0.11140 feed: false isfetch: true longRunning: false diff --git a/Packs/Code42/Integrations/Code42/Code42_test.py b/Packs/Code42/Integrations/Code42/Code42_test.py index 2b820b5c6aa1..e6fa3333addc 100644 --- a/Packs/Code42/Integrations/Code42/Code42_test.py +++ b/Packs/Code42/Integrations/Code42/Code42_test.py @@ -1,12 +1,15 @@ import json import pytest +from py42.sdk.queries.fileevents.filters import FileCategory from requests import Response from py42.sdk import SDKClient from py42.response import Py42Response +from py42.sdk.queries.alerts.filters import Severity from Code42 import ( Code42Client, Code42LegalHoldMatterNotFoundError, Code42InvalidLegalHoldMembershipError, + get_file_category_value, build_query_payload, map_observation_to_security_query, map_to_code42_event_context, @@ -476,6 +479,13 @@ "fileCount": 3, "totalFileSize": 533846, "isSignificant": true + }, + { + "type$": "OBSERVED_FILE_CATEGORY", + "category": "Pdf", + "fileCount": 3, + "totalFileSize": 533846, + "isSignificant": true } ], "files": [ @@ -723,9 +733,12 @@ "filters": [{"operator": "IS", "term": "exposure", "value": "ApplicationRead"}], }, { - "filterClause": "AND", - "filters": [{"operator": "IS", "term": "fileCategory", "value": "SOURCE_CODE"}], - }, + "filterClause": "OR", + "filters": [ + {"operator": "IS", "term": "fileCategory", "value": "PDF"}, + {"operator": "IS", "term": "fileCategory", "value": "SOURCE_CODE"} + ] + } ], "pgNum": 1, "pgSize": 10000, @@ -1347,6 +1360,48 @@ def assert_detection_list_outputs_match_response_items(outputs_list, response_it """TESTS""" +def test_get_file_category_value_handles_screaming_snake_case(): + actual = get_file_category_value("SOURCE_CODE") + expected = FileCategory.SOURCE_CODE + assert actual == expected + + +def test_get_file_category_value_handles_capitalized_case(): + actual = get_file_category_value("Pdf") + expected = FileCategory.PDF + assert actual == expected + + +def test_get_file_category_value_handles_lower_case(): + actual = get_file_category_value("pdf") + expected = FileCategory.PDF + assert actual == expected + + +def test_get_file_category_value_handles_upper_case(): + actual = get_file_category_value("PDF") + expected = FileCategory.PDF + assert actual == expected + + +def test_get_file_category_value_handles_pascal_case(): + actual = get_file_category_value("SourceCode") + expected = FileCategory.SOURCE_CODE + assert actual == expected + + +def test_get_file_category_value_handles_hungarian_case(): + actual = get_file_category_value("sourceCode") + expected = FileCategory.SOURCE_CODE + assert actual == expected + + +def test_get_file_category_value_handles_hyphenated_case(): + actual = get_file_category_value("source-code") + expected = FileCategory.SOURCE_CODE + assert actual == expected + + def test_client_lazily_inits_sdk(mocker, code42_sdk_mock): sdk_factory_mock = mocker.patch("py42.sdk.from_local_account") response_json_mock = """{"total": 1, "users": [{"username": "Test"}]}""" @@ -1992,8 +2047,8 @@ def test_fetch_incidents_handles_multi_severity(code42_fetch_incidents_mock): integration_context=None, ) call_args = str(code42_fetch_incidents_mock.alerts.search.call_args[0][0]) - assert "HIGH" in call_args - assert "LOW" in call_args + assert Severity.HIGH in call_args + assert Severity.LOW in call_args def test_fetch_when_include_files_includes_files(code42_fetch_incidents_mock): diff --git a/Packs/Code42/Playbooks/playbook-Code42_File_Download.yml b/Packs/Code42/Playbooks/playbook-Code42_File_Download.yml index bfc58cd65030..ebbbc61291a3 100644 --- a/Packs/Code42/Playbooks/playbook-Code42_File_Download.yml +++ b/Packs/Code42/Playbooks/playbook-Code42_File_Download.yml @@ -1,75 +1,24 @@ -description: This playbook downloads a file via Code42 by either MD5 or SHA256 hash. id: Code42 File Download -inputs: -- description: MD5 hash to search for - key: MD5 - playbookInputQuery: - required: false - value: - complex: - accessor: MD5 - root: File - transformers: - - operator: uniq -- description: SHA256 hash to search for - key: SHA256 - playbookInputQuery: - required: false - value: - complex: - accessor: SHA256 - root: File - transformers: - - operator: uniq -- description: The name of the file to save as. - key: Filename - playbookInputQuery: - required: false - value: {} +version: -1 name: Code42 File Download -outputs: -- contextPath: File.Size - description: The size of the file. -- contextPath: File.SHA1 - description: The SHA1 hash of the file. -- contextPath: File.SHA256 - description: The SHA256 hash of the file. -- contextPath: File.Name - description: The name of the file. -- contextPath: File.SSDeep - description: The SSDeep hash of the file. -- contextPath: File.EntryID - description: The entry ID of the file. -- contextPath: File.Info - description: File information. -- contextPath: File.Type - description: The file type. -- contextPath: File.MD5 - description: The MD5 hash of the file. -- contextPath: File.Extension - description: The file extension. +description: This playbook downloads a file via Code42 by either MD5 or SHA256 hash. starttaskid: "0" tasks: "0": id: "0" - ignoreworker: false + taskid: f45944a7-0362-48e3-8adb-7022ef46ef0e + type: start + task: + id: f45944a7-0362-48e3-8adb-7022ef46ef0e + version: -1 + name: "" + iscommand: false + brand: "" + description: '' nexttasks: '#none#': - "1" - note: false - quietmode: 0 separatecontext: false - skipunavailable: false - task: - brand: "" - description: "" - id: f45944a7-0362-48e3-8adb-7022ef46ef0e - iscommand: false - name: "" - version: -1 - taskid: f45944a7-0362-48e3-8adb-7022ef46ef0e - timertriggers: [] - type: start view: |- { "position": { @@ -77,53 +26,55 @@ tasks: "y": 50 } } + note: false + timertriggers: [] + ignoreworker: false + skipunavailable: false + quietmode: 0 "1": + id: "1" + taskid: 22138dd2-186a-4001-83b5-006026235ffc + type: condition + task: + id: 22138dd2-186a-4001-83b5-006026235ffc + version: -1 + name: Is Code42 Integration Active? + description: Checks to see if a Code42 Integration is active. + type: condition + iscommand: false + brand: "" + nexttasks: + '#default#': + - "7" + "yes": + - "3" + separatecontext: false conditions: - - condition: - - - left: - iscontext: true + - label: "yes" + condition: + - - operator: isExists + left: value: complex: + root: modules filters: - - - left: - iscontext: true + - - operator: isEqualString + left: value: simple: brand - operator: isEqualString + iscontext: true right: value: simple: Code42 - - - left: - iscontext: true + - - operator: isEqualString + left: value: simple: state - operator: isEqualString + iscontext: true right: value: simple: active - root: modules - operator: isExists - label: "yes" - id: "1" - ignoreworker: false - nexttasks: - "yes": - - "3" - note: false - quietmode: 0 - separatecontext: false - skipunavailable: false - task: - brand: "" - description: Checks to see if a Code42 Integration is active. - id: 22138dd2-186a-4001-83b5-006026235ffc - iscommand: false - name: Is Code42 Integration Active? - type: condition - version: -1 - taskid: 22138dd2-186a-4001-83b5-006026235ffc - timertriggers: [] - type: condition + iscontext: true view: |- { "position": { @@ -131,24 +82,24 @@ tasks: "y": 195 } } + note: false + timertriggers: [] + ignoreworker: false + skipunavailable: false + quietmode: 0 "3": - conditions: - - condition: - - - left: - iscontext: true - value: - simple: inputs.MD5 - operator: isNotEmpty - label: MD5 - - condition: - - - left: - iscontext: true - value: - simple: inputs.SHA256 - operator: isNotEmpty - label: SHA256 id: "3" - ignoreworker: false + taskid: 3d40417b-2a78-4c8d-877c-10fa9b4d9d84 + type: condition + task: + id: 3d40417b-2a78-4c8d-877c-10fa9b4d9d84 + version: -1 + name: What type of hash was supplied? + description: Check whether the values provided in arguments are equal. If either + of the arguments are missing, no is returned. + type: condition + iscommand: false + brand: "" nexttasks: '#default#': - "7" @@ -156,145 +107,196 @@ tasks: - "6" SHA256: - "5" - note: false - quietmode: 0 separatecontext: false - skipunavailable: false - task: - brand: "" - description: Check whether the values provided in arguments are equal. If either - of the arguments are missing, no is returned. - id: 3d40417b-2a78-4c8d-877c-10fa9b4d9d84 - iscommand: false - name: What type of hash was supplied? - type: condition - version: -1 - taskid: 3d40417b-2a78-4c8d-877c-10fa9b4d9d84 - timertriggers: [] - type: condition + conditions: + - label: MD5 + condition: + - - operator: isNotEmpty + left: + value: + simple: inputs.MD5 + iscontext: true + - label: SHA256 + condition: + - - operator: isNotEmpty + left: + value: + simple: inputs.SHA256 + iscontext: true view: |- { "position": { - "x": 377.5, + "x": 50, "y": 370 } } + note: false + timertriggers: [] + ignoreworker: false + skipunavailable: false + quietmode: 0 "5": - continueonerror: true - evidencedata: - customfields: {} - description: - simple: The file that caused the alert. id: "5" - ignoreworker: false + taskid: 3b2c3188-d267-4e9d-8f27-54993aa266ee + type: regular + task: + id: 3b2c3188-d267-4e9d-8f27-54993aa266ee + version: -1 + name: Code42 Download by SHA256 + description: Downloads a file from Code42 servers. + script: Code42|||code42-download-file + type: regular + iscommand: true + brand: Code42 nexttasks: '#none#': - "7" - note: false - quietmode: 0 scriptarguments: filename: simple: ${inputs.Filename} hash: simple: ${inputs.SHA256} + continueonerror: true separatecontext: false - skipunavailable: false - task: - brand: Code42 - description: Downloads a file from Code42 servers. - id: 3b2c3188-d267-4e9d-8f27-54993aa266ee - iscommand: true - name: Code42 Download by SHA256 - script: Code42|||code42-download-file - type: regular - version: -1 - taskid: 3b2c3188-d267-4e9d-8f27-54993aa266ee - timertriggers: [] - type: regular view: |- { "position": { - "x": 630, - "y": 600 + "x": 50, + "y": 545 } } - "6": - continueonerror: true + note: false evidencedata: - customfields: {} description: simple: The file that caused the alert. - id: "6" + customfields: {} + timertriggers: [] ignoreworker: false + skipunavailable: false + quietmode: 0 + "6": + id: "6" + taskid: 6b09d948-56b3-4236-87d5-06469c6a67b2 + type: regular + task: + id: 6b09d948-56b3-4236-87d5-06469c6a67b2 + version: -1 + name: Code42 Download by MD5 + description: Downloads a file from Code42 servers. + script: Code42|||code42-download-file + type: regular + iscommand: true + brand: Code42 nexttasks: '#none#': - "7" - note: false - quietmode: 0 scriptarguments: filename: simple: ${inputs.Filename} hash: simple: ${inputs.MD5} + continueonerror: true separatecontext: false - skipunavailable: false - task: - brand: Code42 - description: Downloads a file from Code42 servers. - id: 6b09d948-56b3-4236-87d5-06469c6a67b2 - iscommand: true - name: Code42 Download by MD5 - script: Code42|||code42-download-file - type: regular - version: -1 - taskid: 6b09d948-56b3-4236-87d5-06469c6a67b2 - timertriggers: [] - type: regular view: |- { "position": { - "x": 100, - "y": 590 + "x": 480, + "y": 545 } } - "7": - id: "7" - ignoreworker: false note: false - quietmode: 0 - separatecontext: false + evidencedata: + description: + simple: The file that caused the alert. + customfields: {} + timertriggers: [] + ignoreworker: false skipunavailable: false + quietmode: 0 + "7": + id: "7" + taskid: a31058a7-f7d7-4c3b-8d52-633b15b8b385 + type: title task: - brand: "" - description: "" id: a31058a7-f7d7-4c3b-8d52-633b15b8b385 - iscommand: false + version: -1 name: Complete type: title - version: -1 - taskid: a31058a7-f7d7-4c3b-8d52-633b15b8b385 - timertriggers: [] - type: title + iscommand: false + brand: "" + description: '' + separatecontext: false view: |- { "position": { - "x": 377.5, - "y": 775 + "x": 265, + "y": 720 } } -version: -1 + note: false + timertriggers: [] + ignoreworker: false + skipunavailable: false + quietmode: 0 view: |- { "linkLabelsPosition": {}, "paper": { "dimensions": { - "height": 790, - "width": 910, - "x": 100, + "height": 735, + "width": 810, + "x": 50, "y": 50 } } } -fromversion: 5.0.0 +inputs: +- key: MD5 + value: + complex: + root: File + accessor: MD5 + transformers: + - operator: uniq + required: false + description: MD5 hash to search for + playbookInputQuery: +- key: SHA256 + value: + complex: + root: File + accessor: SHA256 + transformers: + - operator: uniq + required: false + description: SHA256 hash to search for + playbookInputQuery: +- key: Filename + value: {} + required: false + description: The name of the file to save as. + playbookInputQuery: +outputs: +- contextPath: File.Size + description: The size of the file. +- contextPath: File.SHA1 + description: The SHA1 hash of the file. +- contextPath: File.SHA256 + description: The SHA256 hash of the file. +- contextPath: File.Name + description: The name of the file. +- contextPath: File.SSDeep + description: The SSDeep hash of the file. +- contextPath: File.EntryID + description: The entry ID of the file. +- contextPath: File.Info + description: File information. +- contextPath: File.Type + description: The file type. +- contextPath: File.MD5 + description: The MD5 hash of the file. +- contextPath: File.Extension + description: The file extension. tests: - No Test +fromversion: 5.0.0 diff --git a/Packs/Code42/ReleaseNotes/2_0_4.md b/Packs/Code42/ReleaseNotes/2_0_4.md new file mode 100644 index 000000000000..162127ac56d9 --- /dev/null +++ b/Packs/Code42/ReleaseNotes/2_0_4.md @@ -0,0 +1,9 @@ + +#### Integrations +##### Code42 +- Fix bug where capitalized Alert file-observation file categories would not map to file event query values. +- Upgrade py42 dependency and internal code improvements. + +#### Playbooks +##### Code42 File Download +- Add missing Else case to the Code42 Download File playbook. diff --git a/Packs/Code42/pack_metadata.json b/Packs/Code42/pack_metadata.json index 95e6a37217a1..2be3799f53c8 100644 --- a/Packs/Code42/pack_metadata.json +++ b/Packs/Code42/pack_metadata.json @@ -2,7 +2,7 @@ "name": "Code42", "description": "Use the Code42 integration to identify potential data exfiltration from insider threats while speeding investigation and response by providing fast access to file events and metadata across physical and cloud environments.", "support": "partner", - "currentVersion": "2.0.3", + "currentVersion": "2.0.4", "author": "Code42", "url": "https://support.code42.com/Administrator/Cloud/Monitoring_and_managing", "email": "",