diff --git a/Packs/Code42/Integrations/Code42/CHANGELOG.md b/Packs/Code42/Integrations/Code42/CHANGELOG.md index 64cdf65c7f14..d30648e07019 100644 --- a/Packs/Code42/Integrations/Code42/CHANGELOG.md +++ b/Packs/Code42/Integrations/Code42/CHANGELOG.md @@ -1,22 +1,18 @@ ## [Unreleased] -- Internal code improvements. -- Added new commands: - - **code42-departingemployee-get-all** - - **code42-highriskemployee-add** - - **code42-highriskemployee-remove** - - **code42-highriskemployee-get-all** - - **code42-highriskemployee-add-risk-tags** - - **code42-highriskemployee-remove-risk-tags** - - **code42-user-deactivate** - - **code42-user-reactivate** - - **code42-user-block** - - **code42-user-unblock** - - **code42-user-create** - - **code42-file-download** -- Improve error messages for all Commands to include exception detail. -- Fixed bug in Fetch where errors occurred when `FileCategory` was set to include only one category. -- Fixed bug in Fetch to handle new Code42 exposure type **Outside trusted domains**. -- Improved Fetch to handle unsupported exposure types better. +Added new commands: + - **code42-departingemployee-get-all** that gets all the employees on the Departing Employee List. + - **code42-highriskemployee-add** that takes a username and adds the employee to the High Risk Employee List. + - **code42-highriskemployee-remove** that takes a username and remove the employee from the High Risk Employee List. + - **code42-highriskemployee-get-all** that gets all the employees on the High Risk Employee List. + Optionally takes a list of risk tags and only gets employees who have those risk tags. + - **code42-highriskemployee-add-risk-tags** that takes a username and risk tags and associates the risk tags with the user. + - **code42-highriskemployee-remove-risk-tags** that takes a username and risk tags and disassociates the risk tags from the user. + - **code42-user-deactivate** that deactivates a user in Code42. + - **code42-user-reactivate** that reactivates a user in Code42. + - **code42-user-block** that blocks a user in Code42. + - **code42-user-unblock** that unblocks a user in Code42. + - **code42-user-create** that creates a user in Code42. +Improve error messages for all Commands to include exception detail. ## [20.3.3] - 2020-03-18 #### New Integration diff --git a/Packs/Code42/Integrations/Code42/Code42.py b/Packs/Code42/Integrations/Code42/Code42.py index 7a38ccdd163e..23dfcbe25f12 100644 --- a/Packs/Code42/Integrations/Code42/Code42.py +++ b/Packs/Code42/Integrations/Code42/Code42.py @@ -306,15 +306,6 @@ def search_file_events(self, payload): res = self._get_sdk().securitydata.search_file_events(payload) return res["fileEvents"] - def download_file(self, hash_arg): - security_module = self._get_sdk().securitydata - if _hash_is_md5(hash_arg): - return security_module.stream_file_by_md5(hash_arg) - elif _hash_is_sha256(hash_arg): - return security_module.stream_file_by_sha256(hash_arg) - else: - raise Exception("Unsupported hash. Must be SHA256 or MD5.") - def _get_user_id(self, username): user_id = self.get_user(username).get("userUid") if user_id: @@ -428,18 +419,12 @@ def build_query_payload(args): return query -def _hash_is_sha256(hash_arg): - return hash_arg and len(hash_arg) == 64 - - -def _hash_is_md5(hash_arg): - return hash_arg and len(hash_arg) == 32 - - def _create_hash_filter(hash_arg): - if _hash_is_md5(hash_arg): + if not hash_arg: + return None + elif len(hash_arg) == 32: return MD5.eq(hash_arg) - elif _hash_is_sha256(hash_arg): + elif len(hash_arg) == 64: return SHA256.eq(hash_arg) @@ -470,7 +455,7 @@ class ObservationToSecurityQueryMapper(object): exposure_type_map = { "PublicSearchableShare": ExposureType.IS_PUBLIC, "PublicLinkShare": ExposureType.SHARED_VIA_LINK, - "SharedOutsideTrustedDomain": ExposureType.OUTSIDE_TRUSTED_DOMAINS, + "SharedOutsideTrustedDomain": "OutsideTrustedDomains", } def __init__(self, observation, actor): @@ -950,13 +935,6 @@ def user_reactivate_command(client, args): ) -def download_file_command(client, args): - file_hash = args.get("hash") - response = client.download_file(file_hash) - file_chunks = [c for c in response.iter_content(chunk_size=128) if c] - return fileResult(file_hash, data=b"".join(file_chunks)) - - """Fetching""" @@ -996,7 +974,7 @@ def __init__( self._first_fetch_time = first_fetch_time self._event_severity_filter = event_severity_filter self._fetch_limit = fetch_limit - self._include_files = include_files + self._include_files = (include_files,) self._integration_context = integration_context @logger @@ -1018,7 +996,7 @@ def _fetch_remaining_incidents_from_last_run(self): if remaining_incidents: return ( self._last_run, - remaining_incidents[:self._fetch_limit], + remaining_incidents[: self._fetch_limit], remaining_incidents[self._fetch_limit:], ) @@ -1043,8 +1021,7 @@ def _fetch_alerts(self, start_query_time): def _create_incident_from_alert(self, alert): details = self._client.get_alert_details(alert["id"]) incident = _create_incident_from_alert_details(details) - if self._include_files: - details = self._relate_files_to_alert(details) + details = self._relate_files_to_alert(details) incident["rawJSON"] = json.dumps(details) return incident @@ -1118,7 +1095,6 @@ def get_command_map(): "code42-user-unblock": user_unblock_command, "code42-user-deactivate": user_deactivate_command, "code42_user-reactivate": user_reactivate_command, - "code42-download-file": download_file_command, } @@ -1147,10 +1123,10 @@ def handle_fetch_command(client): demisto.setIntegrationContext(integration_context) -def run_command(command): +def try_run_command(command): try: results = command() - if not isinstance(results, (tuple, list)): + if not isinstance(results, tuple) and not isinstance(results, list): results = [results] for result in results: return_results(result) @@ -1158,32 +1134,28 @@ def run_command(command): return_error(create_command_error_message(demisto.command(), e)) -def create_client(): +def run_code42_integration(): username = demisto.params().get("credentials").get("identifier") password = demisto.params().get("credentials").get("password") base_url = demisto.params().get("console_url") verify_certificate = not demisto.params().get("insecure", False) proxy = demisto.params().get("proxy", False) - return Code42Client( + LOG("Command being called is {0}.".format(demisto.command())) + client = Code42Client( base_url=base_url, sdk=None, auth=(username, password), verify=verify_certificate, proxy=proxy, ) - - -def run_code42_integration(): - client = create_client() commands = get_command_map() - command_key = demisto.command() - LOG("Command being called is {0}.".format(command_key)) - if command_key == "test-module": + command = demisto.command() + if command == "test-module": handle_test_command(client) - elif command_key == "fetch-incidents": + elif command == "fetch-incidents": handle_fetch_command(client) - elif command_key in commands: - run_command(lambda: commands[command_key](client, demisto.args())) + elif command in commands: + try_run_command(lambda: commands[command](client, demisto.args())) def main(): diff --git a/Packs/Code42/Integrations/Code42/Code42.yml b/Packs/Code42/Integrations/Code42/Code42.yml index b16f1aa93348..1315f1bff0f0 100644 --- a/Packs/Code42/Integrations/Code42/Code42.yml +++ b/Packs/Code42/Integrations/Code42/Code42.yml @@ -79,7 +79,7 @@ script: - auto: PREDEFINED default: false description: Exposure types to search for. Can be "RemovableMedia", "ApplicationRead", - "CloudStorage", "IsPublic", "SharedViaLink", "SharedViaDomain", or "OutsideTrustedDomains". + "CloudStorage", "IsPublic", "SharedViaLink", or "SharedViaDomain". isArray: true name: exposure predefined: @@ -89,7 +89,6 @@ script: - IsPublic - SharedViaLink - SharedViaDomain - - OutsideTrustedDomains required: false secret: false - default: false @@ -332,7 +331,7 @@ script: description: The username to remove from the Departing Employee List. isArray: false name: username - required: false + required: true secret: false deprecated: false description: Removes a user from the Departing Employee List. @@ -597,18 +596,7 @@ script: - contextPath: Code42.User.UserID description: The ID of a Code42 User. type: String - - arguments: - - default: false - description: Either the SHA256 or MD5 hash of the file. - isArray: false - name: hash - required: true - secret: false - deprecated: false - description: Downloads a file from Code42 servers. - execution: false - name: code42-download-file - dockerimage: demisto/py42:1.0.0.9653 + dockerimage: demisto/py42:1.0.0.9323 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 43ea9043687e..a3698087000d 100644 --- a/Packs/Code42/Integrations/Code42/Code42_test.py +++ b/Packs/Code42/Integrations/Code42/Code42_test.py @@ -26,7 +26,6 @@ user_unblock_command, user_deactivate_command, user_reactivate_command, - download_file_command, fetch_incidents, ) import time @@ -1380,7 +1379,16 @@ def test_departingemployee_get_all_command_when_no_employees( no_employees_response ) client = create_client(code42_departing_employee_mock) - cmd_res = departingemployee_get_all_command(client,{}) + cmd_res = departingemployee_get_all_command( + client, + { + "risktags": [ + "PERFORMANCE_CONCERNS", + "SUSPICIOUS_SYSTEM_ACTIVITY", + "POOR_SECURITY_PRACTICES", + ] + }, + ) assert cmd_res.outputs_prefix == "Code42.DepartingEmployee" assert cmd_res.outputs_key_field == "UserID" assert cmd_res.raw_response == {} @@ -1627,25 +1635,6 @@ def test_security_data_search_command(code42_file_events_mock): assert output_item == mapped_event -def test_download_file_command_when_given_md5(code42_sdk_mock, mocker): - fr = mocker.patch("Code42.fileResult") - client = create_client(code42_sdk_mock) - _ = download_file_command(client, {"hash": "b6312dbe4aa4212da94523ccb28c5c16"}) - code42_sdk_mock.securitydata.stream_file_by_md5.assert_called_once_with( - "b6312dbe4aa4212da94523ccb28c5c16" - ) - assert fr.call_count == 1 - - -def test_download_file_command_when_given_sha256(code42_sdk_mock, mocker): - fr = mocker.patch("Code42.fileResult") - _hash = "41966f10cc59ab466444add08974fde4cd37f88d79321d42da8e4c79b51c2149" - client = create_client(code42_sdk_mock) - _ = download_file_command(client, {"hash": _hash}) - code42_sdk_mock.securitydata.stream_file_by_sha256.assert_called_once_with(_hash) - assert fr.call_count == 1 - - def test_fetch_when_no_significant_file_categories_ignores_filter( code42_fetch_incidents_mock, mocker ): @@ -1694,41 +1683,8 @@ def test_fetch_incidents_handles_multi_severity(code42_fetch_incidents_mock): include_files=True, 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 - - -def test_fetch_when_include_files_includes_files(code42_fetch_incidents_mock): - client = create_client(code42_fetch_incidents_mock) - _, incidents, _ = fetch_incidents( - client=client, - last_run={"last_fetch": None}, - first_fetch_time=MOCK_FETCH_TIME, - event_severity_filter=["High", "Low"], - fetch_limit=10, - include_files=True, - integration_context=None, - ) - for i in incidents: - _json = json.loads(i["rawJSON"]) - assert len(_json["fileevents"]) - - -def test_fetch_when_not_include_files_excludes_files(code42_fetch_incidents_mock): - client = create_client(code42_fetch_incidents_mock) - _, incidents, _ = fetch_incidents( - client=client, - last_run={"last_fetch": None}, - first_fetch_time=MOCK_FETCH_TIME, - event_severity_filter=["High", "Low"], - fetch_limit=10, - include_files=False, - integration_context=None, - ) - for i in incidents: - _json = json.loads(i["rawJSON"]) - assert not _json.get("fileevents") + assert "HIGH" in str(code42_fetch_incidents_mock.alerts.search.call_args[0][0]) + assert "LOW" in str(code42_fetch_incidents_mock.alerts.search.call_args[0][0]) def test_fetch_incidents_first_run(code42_fetch_incidents_mock): diff --git a/Packs/Code42/Integrations/Code42/README.md b/Packs/Code42/Integrations/Code42/README.md index 02c75bdbd7de..5ad1a1e2e4ca 100644 --- a/Packs/Code42/Integrations/Code42/README.md +++ b/Packs/Code42/Integrations/Code42/README.md @@ -717,27 +717,3 @@ Reactivates the user with the given username. | UserID | | ------ | | 123456790 | - - -### code42-download-file -*** -Downloads a file from Code42 servers. - -#### Base Command - -`code42-download-file` -#### Input - -| **Argument Name** | **Description** | **Required** | -| --- | --- | --- | -| hash | Either the SHA256 or MD5 hash of the file. | Required | - - -#### Command Example -```!code42-download-file hash="bf6b326107d4d85eb485eed84b28133a"``` - -#### Human Readable Output -### Code42 User Deactivated -| Type | Size | Info | MD5 | SHA1 | SHA256 | SHA512 | SSDeep | -| ------ | ---- | ---- | --- | ---- | ------ | ------ | ------ | -| application/vnd.ms-excel | 41,472 bytes | Composite Document File V2 Document, Little Endian, Os: MacOS, Version 14.10, Code page: 10000, Last Saved By: John Doe, Name of Creating Application: Microsoft Macintosh Excel, Create Time/Date: Fri Feb 21 17:35:19 2020, Last Saved Time/Date: Mon Apr 13 11:54:08 2020, Security: 0 | 2e45562437ec4f41387f2e14c3850dd6 | 59e552e637bfe5254b163bb4e426a2322d10f50d | d3f8566d04df5dc34bf2607ac803a585ac81e06f28afe81f35cc2e5fe63d2ab5 | 776bd9626761cd567a4b498bafe4f5f896c3f4bc9f3c60513ccacd14251a2568fa3ba44060000affa8b57fb768c417cf271500086e4e49272f26b26a90627abb | 768:pudkQzl3ZpWh+QO3uMdS9dSttRJwyE/KtxA1almvy6mhk+GlESOwWoqSY7bTKCUv:siQzl3ZpWh+QO3uMdS9dSttRJwyE/KtF | \ No newline at end of file diff --git a/Packs/Code42/Integrations/Code42/integration-Code42.yml b/Packs/Code42/Integrations/Code42/integration-Code42.yml new file mode 100644 index 000000000000..80c39d1cad62 --- /dev/null +++ b/Packs/Code42/Integrations/Code42/integration-Code42.yml @@ -0,0 +1,1822 @@ +category: Endpoint +commonfields: + id: Code42 + version: -1 +configuration: +- defaultvalue: console.us.code42.com + display: Code42 Console URL for the pod your Code42 instance is running in + name: console_url + required: true + type: 0 +- display: Username + name: credentials + required: true + type: 9 +- display: Fetch incidents + name: isFetch + required: false + type: 8 +- display: Incident type + name: incidentType + required: false + type: 13 +- display: Alert severities to fetch when fetching incidents + name: alert_severity + options: + - High + - Medium + - Low + required: false + type: 16 +- defaultvalue: 24 hours + display: First fetch time range (