From 104c41f7cc50abe0afbfb00c315b9701ac54cfba Mon Sep 17 00:00:00 2001 From: Juliya Smith Date: Mon, 29 Jun 2020 14:46:36 +0000 Subject: [PATCH 01/17] SavE --- Packs/Code42/Integrations/Code42/Code42.py | 102 ++++++++++++++---- .../Code42/Integrations/Code42/Code42_test.py | 4 +- 2 files changed, 81 insertions(+), 25 deletions(-) diff --git a/Packs/Code42/Integrations/Code42/Code42.py b/Packs/Code42/Integrations/Code42/Code42.py index cceef47e6e8a..261efd55842c 100644 --- a/Packs/Code42/Integrations/Code42/Code42.py +++ b/Packs/Code42/Integrations/Code42/Code42.py @@ -514,14 +514,20 @@ def alert_get_command(client, args): code42_securityalert_context, headers=SECURITY_ALERT_HEADERS, ) - return readable_outputs, {"Code42.SecurityAlert": code42_securityalert_context}, alert + return CommandResults( + outputs_prefix="Code42.SecurityAlert", + outputs_key_field="ID", + outputs=code42_securityalert_context, + readable_output=readable_outputs, + raw_response=alert, + ) except Exception as e: return_error(create_command_error_message(demisto.command(), e)) @logger def alert_resolve_command(client, args): - code42_security_alert_context = [] + code42_securityalert_context = [] try: alert_id = client.resolve_alert(args["id"]) @@ -535,16 +541,18 @@ def alert_resolve_command(client, args): return "Error retrieving updated alert", {}, {} code42_context = map_to_code42_alert_context(alert_details) - code42_security_alert_context.append(code42_context) + code42_securityalert_context.append(code42_context) readable_outputs = tableToMarkdown( "Code42 Security Alert Resolved", - code42_security_alert_context, + code42_securityalert_context, headers=SECURITY_ALERT_HEADERS, ) - return ( - readable_outputs, - {"Code42.SecurityAlert": code42_security_alert_context}, - alert_details, + return CommandResults( + outputs_prefix="Code42.SecurityAlert", + outputs_key_field="ID", + outputs=code42_securityalert_context, + readable_output=readable_outputs, + raw_response=alert_details, ) except Exception as e: return_error(create_command_error_message(demisto.command(), e)) @@ -564,7 +572,13 @@ def departingemployee_add_command(client, args): "Note": note, } readable_outputs = tableToMarkdown("Code42 Departing Employee List User Added", de_context) - return readable_outputs, {"Code42.DepartingEmployee": de_context}, user_id + return CommandResults( + outputs_prefix="Code42.DepartingEmployee", + outputs_key_field="UserID", + outputs=de_context, + readable_output=readable_outputs, + raw_response=user_id, + ) except Exception as e: return_error(create_command_error_message(demisto.command(), e)) @@ -578,7 +592,13 @@ def departingemployee_remove_command(client, args): readable_outputs = tableToMarkdown( "Code42 Departing Employee List User Removed", de_context ) - return readable_outputs, {"Code42.DepartingEmployee": de_context}, user_id + return CommandResults( + outputs_prefix="Code42.DepartingEmployee", + outputs_key_field="UserID", + outputs=de_context, + readable_output=readable_outputs, + raw_response=user_id, + ) except Exception as e: return_error(create_command_error_message(demisto.command(), e)) @@ -598,10 +618,12 @@ def departingemployee_get_all_command(client, args): for e in employees ] readable_outputs = tableToMarkdown("All Departing Employees", employees_context) - return ( - readable_outputs, - {"Code42.DepartingEmployee(val.UserID && val.UserID == obj.UserID)": employees_context}, - employees, + return CommandResults( + outputs_prefix="Code42.DepartingEmployee", + outputs_key_field="UserID", + outputs=employees_context, + readable_output=readable_outputs, + raw_response=employees, ) except Exception as e: return_error(create_command_error_message(demisto.command(), e)) @@ -615,7 +637,13 @@ def highriskemployee_add_command(client, args): user_id = client.add_user_to_high_risk_employee(username, note) hr_context = {"UserID": user_id, "Username": username} readable_outputs = tableToMarkdown("Code42 High Risk Employee List User Added", hr_context) - return readable_outputs, {"Code42.HighRiskEmployee": hr_context}, user_id + return CommandResults( + outputs_prefix="Code42HighRiskEmployee", + outputs_key_field="UserID", + outputs=hr_context, + readable_output=readable_outputs, + raw_response=user_id, + ) except Exception as e: return_error(create_command_error_message(demisto.command(), e)) @@ -629,7 +657,13 @@ def highriskemployee_remove_command(client, args): readable_outputs = tableToMarkdown( "Code42 High Risk Employee List User Removed", hr_context ) - return readable_outputs, {"Code42.HighRiskEmployee": hr_context}, user_id + return CommandResults( + outputs_prefix="Code42.HighRiskEmployee", + outputs_key_field="UserID", + outputs=hr_context, + readable_output=readable_outputs, + raw_response=user_id, + ) except Exception as e: return_error(create_command_error_message(demisto.command(), e)) @@ -645,11 +679,14 @@ def highriskemployee_get_all_command(client, args): for e in employees ] readable_outputs = tableToMarkdown("Retrieved All High Risk Employees", employees_context) - return ( - readable_outputs, - {"Code42.HighRiskEmployee(val.UserID && val.UserID == obj.UserID)": employees_context}, - employees, + return CommandResults( + outputs_prefix="Code42.HighRiskEmployee", + outputs_key_field="UserID", + outputs=employees_context, + readable_output=readable_outputs, + raw_response=employees, ) + except Exception as e: return_error(create_command_error_message(demisto.command(), e)) @@ -662,7 +699,13 @@ def highriskemployee_add_risk_tags_command(client, args): user_id = client.add_user_risk_tags(username, tags) rt_context = {"UserID": user_id, "Username": username, "RiskTags": tags} readable_outputs = tableToMarkdown("Code42 Risk Tags Added", rt_context) - return readable_outputs, {"Code42.HighRiskEmployee": rt_context}, user_id + return CommandResults( + outputs_prefix="Code42.HighRiskEmployee", + outputs_key_field="UserID", + outputs=rt_context, + readable_output=readable_outputs, + raw_response=user_id, + ) except Exception as e: return_error(create_command_error_message(demisto.command(), e)) @@ -675,7 +718,13 @@ def highriskemployee_remove_risk_tags_command(client, args): user_id = client.remove_user_risk_tags(username, tags) rt_context = {"UserID": user_id, "Username": username, "RiskTags": tags} readable_outputs = tableToMarkdown("Code42 Risk Tags Removed", rt_context) - return readable_outputs, {"Code42.HighRiskEmployee": rt_context}, user_id + return CommandResults( + outputs_prefix="Code42.HighRiskEmployee", + outputs_key_field="UserID", + outputs=rt_context, + readable_output=readable_outputs, + raw_response=user_id, + ) except Exception as e: return_error(create_command_error_message(demisto.command(), e)) @@ -706,10 +755,14 @@ def securitydata_search_command(client, args): security_data_context_key = "Code42.SecurityData(val.EventID && val.EventID == obj.EventID)" context = {security_data_context_key: code42_security_data_context, "File": file_context} return readable_outputs, context, file_events + else: return "No results found", {}, {} +"""Fetching""" + + def _create_incident_from_alert_details(details): return {"name": "Code42 - {}".format(details["name"]), "occurred": details["createdAt"]} @@ -832,6 +885,9 @@ def fetch_incidents( return fetcher.fetch() +"""Main and test""" + + def test_module(client): try: # Will fail if unauthorized @@ -900,7 +956,7 @@ def main(): integration_context["remaining_incidents"] = remaining_incidents demisto.setIntegrationContext(integration_context) elif command in commands: - return_outputs(*commands[command](client, demisto.args())) + return_results(*commands[command](client, demisto.args())) # Log exceptions except Exception as e: return_error(f"Failed to execute {demisto.command()} command. Error: {str(e)}") diff --git a/Packs/Code42/Integrations/Code42/Code42_test.py b/Packs/Code42/Integrations/Code42/Code42_test.py index 42a087861867..b75f59f6d0de 100644 --- a/Packs/Code42/Integrations/Code42/Code42_test.py +++ b/Packs/Code42/Integrations/Code42/Code42_test.py @@ -1148,8 +1148,8 @@ def test_map_to_file_context(): def test_alert_get_command(code42_alerts_mock): client = create_client(code42_alerts_mock) - _, _, res = alert_get_command(client, {"id": "36fb8ca5-0533-4d25-9763-e09d35d60610"}) - assert res["ruleId"] == "4576576e-13cb-4f88-be3a-ee77739de649" + res = alert_get_command(client, {"id": "36fb8ca5-0533-4d25-9763-e09d35d60610"}) + assert res.raw_response["ruleId"] == "4576576e-13cb-4f88-be3a-ee77739de649" def test_alert_resolve_command(code42_alerts_mock): From 940266cd86788ab7324b73834273c51e8b543db6 Mon Sep 17 00:00:00 2001 From: Juliya Smith Date: Mon, 29 Jun 2020 15:42:20 +0000 Subject: [PATCH 02/17] Save --- .../Code42/Integrations/Code42/Code42_test.py | 37 ++++++++++--------- 1 file changed, 19 insertions(+), 18 deletions(-) diff --git a/Packs/Code42/Integrations/Code42/Code42_test.py b/Packs/Code42/Integrations/Code42/Code42_test.py index e08e5361001e..a57f60828938 100644 --- a/Packs/Code42/Integrations/Code42/Code42_test.py +++ b/Packs/Code42/Integrations/Code42/Code42_test.py @@ -1296,24 +1296,25 @@ def test_departingemployee_get_all_command_when_no_employees( assert res == expected assert code42_departing_employee_mock.detectionlists.departing_employee.get_all.call_count == 1 assert_departingemployee_outputs_match_response(outputs_list, res) -# -# -# def test_highriskemployee_add_command(code42_high_risk_employee_mock): -# client = create_client(code42_high_risk_employee_mock) -# _, outputs, res = highriskemployee_add_command( -# client, {"username": _TEST_USERNAME, "note": "Dummy note"} -# ) -# assert res == _TEST_USER_ID -# assert outputs["Code42.HighRiskEmployee"]["UserID"] == _TEST_USER_ID -# assert outputs["Code42.HighRiskEmployee"]["Username"] == _TEST_USERNAME -# code42_high_risk_employee_mock.detectionlists.high_risk_employee.add.assert_called_once_with( -# _TEST_USER_ID -# ) -# code42_high_risk_employee_mock.detectionlists.update_user_notes.assert_called_once_with( -# _TEST_USER_ID, "Dummy note" -# ) -# -# + + +def test_highriskemployee_add_command(code42_high_risk_employee_mock): + client = create_client(code42_high_risk_employee_mock) + cmd_res = highriskemployee_add_command( + client, {"username": _TEST_USERNAME, "note": "Dummy note"} + ) + assert cmd_res.raw_response == _TEST_USER_ID + assert cmd_res.outputs_prefix == "Code42.HighRiskEmployee" + assert outputs["Code42.HighRiskEmployee"]["UserID"] == _TEST_USER_ID + assert outputs["Code42.HighRiskEmployee"]["Username"] == _TEST_USERNAME + code42_high_risk_employee_mock.detectionlists.high_risk_employee.add.assert_called_once_with( + _TEST_USER_ID + ) + code42_high_risk_employee_mock.detectionlists.update_user_notes.assert_called_once_with( + _TEST_USER_ID, "Dummy note" + ) + + # def test_highriskemployee_remove_command(code42_sdk_mock): # client = create_client(code42_sdk_mock) # _, outputs, res = highriskemployee_remove_command(client, {"username": _TEST_USERNAME}) From 3ec31a26a2978e2f3b4b9db82b7d34966eef2b39 Mon Sep 17 00:00:00 2001 From: Juliya Smith Date: Mon, 29 Jun 2020 16:14:23 +0000 Subject: [PATCH 03/17] New cmd outputs results --- Packs/Code42/Integrations/Code42/Code42.py | 2 +- .../Code42/Integrations/Code42/Code42_test.py | 564 +++++++++--------- .../Code42/integration-Code42.yml | 104 +++- 3 files changed, 366 insertions(+), 304 deletions(-) diff --git a/Packs/Code42/Integrations/Code42/Code42.py b/Packs/Code42/Integrations/Code42/Code42.py index 8c60aec171a8..ed7baacffd11 100644 --- a/Packs/Code42/Integrations/Code42/Code42.py +++ b/Packs/Code42/Integrations/Code42/Code42.py @@ -640,7 +640,7 @@ def highriskemployee_add_command(client, args): hr_context = {"UserID": user_id, "Username": username} readable_outputs = tableToMarkdown("Code42 High Risk Employee List User Added", hr_context) return CommandResults( - outputs_prefix="Code42HighRiskEmployee", + outputs_prefix="Code42.HighRiskEmployee", outputs_key_field="UserID", outputs=hr_context, readable_output=readable_outputs, diff --git a/Packs/Code42/Integrations/Code42/Code42_test.py b/Packs/Code42/Integrations/Code42/Code42_test.py index a57f60828938..bb58b476e9dc 100644 --- a/Packs/Code42/Integrations/Code42/Code42_test.py +++ b/Packs/Code42/Integrations/Code42/Code42_test.py @@ -1279,7 +1279,7 @@ def test_departingemployee_get_all_command_when_no_employees( no_employees_response ) client = create_client(code42_departing_employee_mock) - _, outputs, res = departingemployee_get_all_command( + cmd_res = departingemployee_get_all_command( client, { "risktags": [ @@ -1289,13 +1289,11 @@ def test_departingemployee_get_all_command_when_no_employees( ] }, ) - outputs_list = outputs["Code42.DepartingEmployee(val.UserID && val.UserID == obj.UserID)"] - - # Only first employee has the given risk tags - expected = [] - assert res == expected + assert cmd_res.outputs_prefix == "Code42.DepartingEmployee" + assert cmd_res.outputs_key_field == "UserID" + assert cmd_res.raw_response == [] + assert cmd_res.outputs == [] assert code42_departing_employee_mock.detectionlists.departing_employee.get_all.call_count == 1 - assert_departingemployee_outputs_match_response(outputs_list, res) def test_highriskemployee_add_command(code42_high_risk_employee_mock): @@ -1305,8 +1303,9 @@ def test_highriskemployee_add_command(code42_high_risk_employee_mock): ) assert cmd_res.raw_response == _TEST_USER_ID assert cmd_res.outputs_prefix == "Code42.HighRiskEmployee" - assert outputs["Code42.HighRiskEmployee"]["UserID"] == _TEST_USER_ID - assert outputs["Code42.HighRiskEmployee"]["Username"] == _TEST_USERNAME + assert cmd_res.outputs_key_field == "UserID" + assert cmd_res.outputs["UserID"] == _TEST_USER_ID + assert cmd_res.outputs["Username"] == _TEST_USERNAME code42_high_risk_employee_mock.detectionlists.high_risk_employee.add.assert_called_once_with( _TEST_USER_ID ) @@ -1315,274 +1314,279 @@ def test_highriskemployee_add_command(code42_high_risk_employee_mock): ) -# def test_highriskemployee_remove_command(code42_sdk_mock): -# client = create_client(code42_sdk_mock) -# _, outputs, res = highriskemployee_remove_command(client, {"username": _TEST_USERNAME}) -# assert res == _TEST_USER_ID -# assert outputs["Code42.HighRiskEmployee"]["UserID"] == _TEST_USER_ID -# assert outputs["Code42.HighRiskEmployee"]["Username"] == _TEST_USERNAME -# code42_sdk_mock.detectionlists.high_risk_employee.remove.assert_called_once_with(_TEST_USER_ID) -# -# -# def test_highriskemployee_get_all_command(code42_high_risk_employee_mock): -# client = create_client(code42_high_risk_employee_mock) -# _, outputs, res = highriskemployee_get_all_command(client, {}) -# outputs_list = outputs["Code42.HighRiskEmployee(val.UserID && val.UserID == obj.UserID)"] -# expected = json.loads(MOCK_GET_ALL_HIGH_RISK_EMPLOYEES_RESPONSE)["items"] -# assert res == expected -# assert code42_high_risk_employee_mock.detectionlists.high_risk_employee.get_all.call_count == 1 -# assert_detection_list_outputs_match_response_items(outputs_list, expected) -# -# -# def test_highriskemployee_get_all_command_gets_employees_from_multiple_pages( -# code42_high_risk_employee_mock, mocker -# ): -# # Setup get all high risk employees -# page = MOCK_GET_ALL_HIGH_RISK_EMPLOYEES_RESPONSE -# # Setup 3 pages of employees -# employee_page_generator = ( -# create_mock_code42_sdk_response(mocker, page) for page in [page, page, page] -# ) -# code42_high_risk_employee_mock.detectionlists.high_risk_employee.get_all.return_value = ( -# employee_page_generator -# ) -# client = create_client(code42_high_risk_employee_mock) -# -# _, outputs, res = highriskemployee_get_all_command(client, {"username": _TEST_USERNAME}) -# outputs_list = outputs["Code42.HighRiskEmployee(val.UserID && val.UserID == obj.UserID)"] -# -# # Expect to have employees from 3 pages in the result -# expected_page = json.loads(MOCK_GET_ALL_HIGH_RISK_EMPLOYEES_RESPONSE)["items"] -# expected = expected_page + expected_page + expected_page -# assert res == expected -# assert_detection_list_outputs_match_response_items(outputs_list, expected) -# -# -# def test_highriskemployee_get_all_command_when_given_risk_tags_only_gets_employees_with_tags( -# code42_high_risk_employee_mock -# ): -# client = create_client(code42_high_risk_employee_mock) -# _, outputs, res = highriskemployee_get_all_command( -# client, -# {"risktags": "PERFORMANCE_CONCERNS SUSPICIOUS_SYSTEM_ACTIVITY POOR_SECURITY_PRACTICES"}, -# ) -# outputs_list = outputs["Code42.HighRiskEmployee(val.UserID && val.UserID == obj.UserID)"] -# # Only first employee has the given risk tags -# expected = [json.loads(MOCK_GET_ALL_HIGH_RISK_EMPLOYEES_RESPONSE)["items"][0]] -# assert res == expected -# assert code42_high_risk_employee_mock.detectionlists.high_risk_employee.get_all.call_count == 1 -# assert_detection_list_outputs_match_response_items(outputs_list, expected) -# -# -# def test_highriskemployee_get_all_command_gets_number_of_employees_equal_to_results_param( -# code42_high_risk_employee_mock, mocker -# ): -# # Setup get all high risk employees -# page = MOCK_GET_ALL_HIGH_RISK_EMPLOYEES_RESPONSE -# # Setup 3 pages of employees -# employee_page_generator = ( -# create_mock_code42_sdk_response(mocker, page) for page in [page, page, page] -# ) -# code42_high_risk_employee_mock.detectionlists.high_risk_employee.get_all.return_value = ( -# employee_page_generator -# ) -# client = create_client(code42_high_risk_employee_mock) -# _, _, res = highriskemployee_get_all_command(client, {"results": 1}) -# assert len(res) == 1 -# -# -# def test_highriskemployee_get_all_command_when_no_employees(code42_high_risk_employee_mock, mocker): -# no_employees_response = get_empty_detectionlist_response( -# mocker, MOCK_GET_ALL_HIGH_RISK_EMPLOYEES_RESPONSE -# ) -# code42_high_risk_employee_mock.detectionlists.high_risk_employee.get_all.return_value = ( -# no_employees_response -# ) -# client = create_client(code42_high_risk_employee_mock) -# _, outputs, res = highriskemployee_get_all_command( -# client, -# { -# "risktags": "PERFORMANCE_CONCERNS SUSPICIOUS_SYSTEM_ACTIVITY POOR_SECURITY_PRACTICES" -# }, -# ) -# outputs_list = outputs["Code42.HighRiskEmployee(val.UserID && val.UserID == obj.UserID)"] -# # Only first employee has the given risk tags -# expected = [] -# assert res == expected -# assert code42_high_risk_employee_mock.detectionlists.high_risk_employee.get_all.call_count == 1 -# assert_detection_list_outputs_match_response_items(outputs_list, expected) -# -# -# def test_highriskemployee_add_risk_tags_command(code42_sdk_mock): -# tags = "FLIGHT_RISK" -# client = create_client(code42_sdk_mock) -# _, outputs, res = highriskemployee_add_risk_tags_command( -# client, {"username": _TEST_USERNAME, "risktags": "FLIGHT_RISK"} -# ) -# assert res == _TEST_USER_ID -# assert outputs["Code42.HighRiskEmployee"]["UserID"] == _TEST_USER_ID -# assert outputs["Code42.HighRiskEmployee"]["Username"] == _TEST_USERNAME -# assert outputs["Code42.HighRiskEmployee"]["RiskTags"] == tags -# code42_sdk_mock.detectionlists.add_user_risk_tags.assert_called_once_with( -# _TEST_USER_ID, ["FLIGHT_RISK"] -# ) -# -# -# def test_highriskemployee_remove_risk_tags_command(code42_sdk_mock): -# client = create_client(code42_sdk_mock) -# _, outputs, res = highriskemployee_remove_risk_tags_command( -# client, {"username": _TEST_USERNAME, "risktags": "FLIGHT_RISK CONTRACT_EMPLOYEE"} -# ) -# assert res == _TEST_USER_ID -# assert outputs["Code42.HighRiskEmployee"]["UserID"] == _TEST_USER_ID -# assert outputs["Code42.HighRiskEmployee"]["Username"] == _TEST_USERNAME -# assert outputs["Code42.HighRiskEmployee"]["RiskTags"] == "FLIGHT_RISK CONTRACT_EMPLOYEE" -# code42_sdk_mock.detectionlists.remove_user_risk_tags.assert_called_once_with( -# _TEST_USER_ID, ["FLIGHT_RISK", "CONTRACT_EMPLOYEE"] -# ) -# -# -# def test_security_data_search_command(code42_file_events_mock): -# client = create_client(code42_file_events_mock) -# _, outputs, res = securitydata_search_command(client, MOCK_SECURITY_DATA_SEARCH_QUERY) -# outputs_list = outputs["Code42.SecurityData(val.EventID && val.EventID == obj.EventID)"] -# actual_query = code42_file_events_mock.securitydata.search_file_events.call_args[0][0] -# filter_groups = json.loads(str(actual_query))["groups"] -# expected_query_items = [ -# ("md5Checksum", "d41d8cd98f00b204e9800998ecf8427e"), -# ("osHostName", "DESKTOP-0001"), -# ("deviceUserName", "user3@example.com"), -# ("exposure", "ApplicationRead") -# ] -# expected_file_events = json.loads(MOCK_SECURITY_EVENT_RESPONSE)["fileEvents"] -# -# # Assert that the correct query gets made -# assert len(filter_groups) == len(expected_query_items) -# for i in range(0, len(filter_groups)): -# _filter = filter_groups[i]["filters"][0] -# assert _filter["term"] == expected_query_items[i][0] -# assert _filter["value"] == expected_query_items[i][1] -# -# assert len(res) == len(outputs_list) == 3 -# assert res == expected_file_events -# -# # Assert that the Outputs are mapped from the file events. -# for i in range(0, len(expected_file_events)): -# mapped_event = map_to_code42_event_context(expected_file_events[i]) -# output_item = outputs_list[i] -# assert output_item == mapped_event -# -# -# def test_fetch_when_no_significant_file_categories_ignores_filter( -# code42_fetch_incidents_mock, mocker -# ): -# response_text = MOCK_ALERT_DETAILS_RESPONSE.replace( -# '"isSignificant": true', '"isSignificant": false' -# ) -# alert_details_response = create_mock_code42_sdk_response(mocker, response_text) -# code42_fetch_incidents_mock.alerts.get_details.return_value = alert_details_response -# client = create_client(code42_fetch_incidents_mock) -# _, _, _ = fetch_incidents( -# client=client, -# last_run={"last_fetch": None}, -# first_fetch_time=MOCK_FETCH_TIME, -# event_severity_filter=None, -# fetch_limit=10, -# include_files=True, -# integration_context=None, -# ) -# actual_query = str(code42_fetch_incidents_mock.securitydata.search_file_events.call_args[0][0]) -# assert "fileCategory" not in actual_query -# assert "IMAGE" not in actual_query -# -# -# def test_fetch_incidents_handles_single_severity(code42_sdk_mock): -# client = create_client(code42_sdk_mock) -# fetch_incidents( -# client=client, -# last_run={"last_fetch": None}, -# first_fetch_time=MOCK_FETCH_TIME, -# event_severity_filter="High", -# fetch_limit=10, -# include_files=True, -# integration_context=None, -# ) -# assert "HIGH" in str(code42_sdk_mock.alerts.search.call_args[0][0]) -# -# -# def test_fetch_incidents_handles_multi_severity(code42_fetch_incidents_mock): -# client = create_client(code42_fetch_incidents_mock) -# 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, -# ) -# 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): -# client = create_client(code42_fetch_incidents_mock) -# next_run, incidents, remaining_incidents = fetch_incidents( -# client=client, -# last_run={"last_fetch": None}, -# first_fetch_time=MOCK_FETCH_TIME, -# event_severity_filter=None, -# fetch_limit=10, -# include_files=True, -# integration_context=None, -# ) -# assert len(incidents) == 3 -# assert next_run["last_fetch"] -# -# -# def test_fetch_incidents_next_run(code42_fetch_incidents_mock): -# mock_date = "2020-01-01T00:00:00.000Z" -# mock_timestamp = int(time.mktime(time.strptime(mock_date, "%Y-%m-%dT%H:%M:%S.000Z"))) -# client = create_client(code42_fetch_incidents_mock) -# next_run, incidents, remaining_incidents = fetch_incidents( -# client=client, -# last_run={"last_fetch": mock_timestamp}, -# first_fetch_time=MOCK_FETCH_TIME, -# event_severity_filter=None, -# fetch_limit=10, -# include_files=True, -# integration_context=None, -# ) -# assert len(incidents) == 3 -# assert next_run["last_fetch"] -# -# -# def test_fetch_incidents_fetch_limit(code42_fetch_incidents_mock): -# mock_date = "2020-01-01T00:00:00.000Z" -# mock_timestamp = int(time.mktime(time.strptime(mock_date, "%Y-%m-%dT%H:%M:%S.000Z"))) -# client = create_client(code42_fetch_incidents_mock) -# next_run, incidents, remaining_incidents = fetch_incidents( -# client=client, -# last_run={"last_fetch": mock_timestamp}, -# first_fetch_time=MOCK_FETCH_TIME, -# event_severity_filter=None, -# fetch_limit=2, -# include_files=True, -# integration_context=None, -# ) -# assert len(incidents) == 2 -# assert next_run["last_fetch"] -# assert len(remaining_incidents) == 1 -# # Run again to get the last incident -# next_run, incidents, remaining_incidents = fetch_incidents( -# client=client, -# last_run={"last_fetch": mock_timestamp}, -# first_fetch_time=MOCK_FETCH_TIME, -# event_severity_filter=None, -# fetch_limit=2, -# include_files=True, -# integration_context={"remaining_incidents": remaining_incidents}, -# ) -# assert len(incidents) == 1 -# assert next_run["last_fetch"] -# assert not remaining_incidents +def test_highriskemployee_remove_command(code42_sdk_mock): + client = create_client(code42_sdk_mock) + cmd_res = highriskemployee_remove_command(client, {"username": _TEST_USERNAME}) + assert cmd_res.raw_response == _TEST_USER_ID + assert cmd_res.outputs_prefix == "Code42.HighRiskEmployee" + assert cmd_res.outputs_key_field == "UserID" + assert cmd_res.outputs["UserID"] == _TEST_USER_ID + assert cmd_res.outputs["Username"] == _TEST_USERNAME + code42_sdk_mock.detectionlists.high_risk_employee.remove.assert_called_once_with(_TEST_USER_ID) + + +def test_highriskemployee_get_all_command(code42_high_risk_employee_mock): + client = create_client(code42_high_risk_employee_mock) + cmd_res = highriskemployee_get_all_command(client, {}) + expected_response = json.loads(MOCK_GET_ALL_HIGH_RISK_EMPLOYEES_RESPONSE)["items"] + assert cmd_res.outputs_key_field == "UserID" + assert cmd_res.outputs_prefix == "Code42.HighRiskEmployee" + assert cmd_res.raw_response == expected_response + assert code42_high_risk_employee_mock.detectionlists.high_risk_employee.get_all.call_count == 1 + assert_detection_list_outputs_match_response_items(cmd_res.outputs, expected_response) + + +def test_highriskemployee_get_all_command_gets_employees_from_multiple_pages( + code42_high_risk_employee_mock, mocker +): + # Setup get all high risk employees + page = MOCK_GET_ALL_HIGH_RISK_EMPLOYEES_RESPONSE + # Setup 3 pages of employees + employee_page_generator = ( + create_mock_code42_sdk_response(mocker, page) for page in [page, page, page] + ) + code42_high_risk_employee_mock.detectionlists.high_risk_employee.get_all.return_value = ( + employee_page_generator + ) + client = create_client(code42_high_risk_employee_mock) + + cmd_res = highriskemployee_get_all_command(client, {"username": _TEST_USERNAME}) + expected_response = json.loads(MOCK_GET_ALL_HIGH_RISK_EMPLOYEES_RESPONSE)["items"] * 3 + assert cmd_res.outputs_prefix == "Code42.HighRiskEmployee" + assert cmd_res.outputs_key_field == "UserID" + assert cmd_res.raw_response == expected_response + assert_detection_list_outputs_match_response_items(cmd_res.outputs, expected_response) + + +def test_highriskemployee_get_all_command_when_given_risk_tags_only_gets_employees_with_tags( + code42_high_risk_employee_mock +): + client = create_client(code42_high_risk_employee_mock) + cmd_res = highriskemployee_get_all_command( + client, + {"risktags": "PERFORMANCE_CONCERNS SUSPICIOUS_SYSTEM_ACTIVITY POOR_SECURITY_PRACTICES"}, + ) + expected_response = [json.loads(MOCK_GET_ALL_HIGH_RISK_EMPLOYEES_RESPONSE)["items"][0]] + assert cmd_res.outputs_prefix == "Code42.HighRiskEmployee" + assert cmd_res.outputs_key_field == "UserID" + assert cmd_res.raw_response == expected_response + assert code42_high_risk_employee_mock.detectionlists.high_risk_employee.get_all.call_count == 1 + assert_detection_list_outputs_match_response_items(cmd_res.outputs, expected_response) + + +def test_highriskemployee_get_all_command_gets_number_of_employees_equal_to_results_param( + code42_high_risk_employee_mock, mocker +): + # Setup get all high risk employees + page = MOCK_GET_ALL_HIGH_RISK_EMPLOYEES_RESPONSE + # Setup 3 pages of employees + employee_page_generator = ( + create_mock_code42_sdk_response(mocker, page) for page in [page, page, page] + ) + code42_high_risk_employee_mock.detectionlists.high_risk_employee.get_all.return_value = ( + employee_page_generator + ) + client = create_client(code42_high_risk_employee_mock) + cmd_res = highriskemployee_get_all_command(client, {"results": 1}) + assert len(cmd_res.raw_response) == 1 + assert len(cmd_res.outputs) == 1 + + +def test_highriskemployee_get_all_command_when_no_employees(code42_high_risk_employee_mock, mocker): + no_employees_response = get_empty_detectionlist_response( + mocker, MOCK_GET_ALL_HIGH_RISK_EMPLOYEES_RESPONSE + ) + code42_high_risk_employee_mock.detectionlists.high_risk_employee.get_all.return_value = ( + no_employees_response + ) + client = create_client(code42_high_risk_employee_mock) + cmd_res = highriskemployee_get_all_command( + client, + { + "risktags": "PERFORMANCE_CONCERNS SUSPICIOUS_SYSTEM_ACTIVITY POOR_SECURITY_PRACTICES" + }, + ) + assert cmd_res.outputs_prefix == "Code42.HighRiskEmployee" + assert cmd_res.outputs_key_field == "UserID" + assert cmd_res.outputs == [] + assert cmd_res.raw_response == [] + assert code42_high_risk_employee_mock.detectionlists.high_risk_employee.get_all.call_count == 1 + + +def test_highriskemployee_add_risk_tags_command(code42_sdk_mock): + tags = "FLIGHT_RISK" + client = create_client(code42_sdk_mock) + cmd_res = highriskemployee_add_risk_tags_command( + client, {"username": _TEST_USERNAME, "risktags": "FLIGHT_RISK"} + ) + assert cmd_res.raw_response == _TEST_USER_ID + assert cmd_res.outputs_prefix == "Code42.HighRiskEmployee" + assert cmd_res.outputs_key_field == "UserID" + assert cmd_res.outputs["UserID"] == _TEST_USER_ID + assert cmd_res.outputs["Username"] == _TEST_USERNAME + assert cmd_res.outputs["RiskTags"] == tags + code42_sdk_mock.detectionlists.add_user_risk_tags.assert_called_once_with( + _TEST_USER_ID, ["FLIGHT_RISK"] + ) + + +def test_highriskemployee_remove_risk_tags_command(code42_sdk_mock): + client = create_client(code42_sdk_mock) + cmd_res = highriskemployee_remove_risk_tags_command( + client, {"username": _TEST_USERNAME, "risktags": "FLIGHT_RISK CONTRACT_EMPLOYEE"} + ) + assert cmd_res.raw_response == _TEST_USER_ID + assert cmd_res.outputs_prefix == "Code42.HighRiskEmployee" + assert cmd_res.outputs_key_field == "UserID" + assert cmd_res.outputs["UserID"] == _TEST_USER_ID + assert cmd_res.outputs["Username"] == _TEST_USERNAME + assert cmd_res.outputs["RiskTags"] == "FLIGHT_RISK CONTRACT_EMPLOYEE" + code42_sdk_mock.detectionlists.remove_user_risk_tags.assert_called_once_with( + _TEST_USER_ID, ["FLIGHT_RISK", "CONTRACT_EMPLOYEE"] + ) + + +def test_security_data_search_command(code42_file_events_mock): + client = create_client(code42_file_events_mock) + _, outputs, res = securitydata_search_command(client, MOCK_SECURITY_DATA_SEARCH_QUERY) + outputs_list = outputs["Code42.SecurityData(val.EventID && val.EventID == obj.EventID)"] + actual_query = code42_file_events_mock.securitydata.search_file_events.call_args[0][0] + filter_groups = json.loads(str(actual_query))["groups"] + expected_query_items = [ + ("md5Checksum", "d41d8cd98f00b204e9800998ecf8427e"), + ("osHostName", "DESKTOP-0001"), + ("deviceUserName", "user3@example.com"), + ("exposure", "ApplicationRead") + ] + expected_file_events = json.loads(MOCK_SECURITY_EVENT_RESPONSE)["fileEvents"] + + # Assert that the correct query gets made + assert len(filter_groups) == len(expected_query_items) + for i in range(0, len(filter_groups)): + _filter = filter_groups[i]["filters"][0] + assert _filter["term"] == expected_query_items[i][0] + assert _filter["value"] == expected_query_items[i][1] + + assert len(res) == len(outputs_list) == 3 + assert res == expected_file_events + + # Assert that the Outputs are mapped from the file events. + for i in range(0, len(expected_file_events)): + mapped_event = map_to_code42_event_context(expected_file_events[i]) + output_item = outputs_list[i] + assert output_item == mapped_event + + +def test_fetch_when_no_significant_file_categories_ignores_filter( + code42_fetch_incidents_mock, mocker +): + response_text = MOCK_ALERT_DETAILS_RESPONSE.replace( + '"isSignificant": true', '"isSignificant": false' + ) + alert_details_response = create_mock_code42_sdk_response(mocker, response_text) + code42_fetch_incidents_mock.alerts.get_details.return_value = alert_details_response + client = create_client(code42_fetch_incidents_mock) + _, _, _ = fetch_incidents( + client=client, + last_run={"last_fetch": None}, + first_fetch_time=MOCK_FETCH_TIME, + event_severity_filter=None, + fetch_limit=10, + include_files=True, + integration_context=None, + ) + actual_query = str(code42_fetch_incidents_mock.securitydata.search_file_events.call_args[0][0]) + assert "fileCategory" not in actual_query + assert "IMAGE" not in actual_query + + +def test_fetch_incidents_handles_single_severity(code42_sdk_mock): + client = create_client(code42_sdk_mock) + fetch_incidents( + client=client, + last_run={"last_fetch": None}, + first_fetch_time=MOCK_FETCH_TIME, + event_severity_filter="High", + fetch_limit=10, + include_files=True, + integration_context=None, + ) + assert "HIGH" in str(code42_sdk_mock.alerts.search.call_args[0][0]) + + +def test_fetch_incidents_handles_multi_severity(code42_fetch_incidents_mock): + client = create_client(code42_fetch_incidents_mock) + 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, + ) + 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): + client = create_client(code42_fetch_incidents_mock) + next_run, incidents, remaining_incidents = fetch_incidents( + client=client, + last_run={"last_fetch": None}, + first_fetch_time=MOCK_FETCH_TIME, + event_severity_filter=None, + fetch_limit=10, + include_files=True, + integration_context=None, + ) + assert len(incidents) == 3 + assert next_run["last_fetch"] + + +def test_fetch_incidents_next_run(code42_fetch_incidents_mock): + mock_date = "2020-01-01T00:00:00.000Z" + mock_timestamp = int(time.mktime(time.strptime(mock_date, "%Y-%m-%dT%H:%M:%S.000Z"))) + client = create_client(code42_fetch_incidents_mock) + next_run, incidents, remaining_incidents = fetch_incidents( + client=client, + last_run={"last_fetch": mock_timestamp}, + first_fetch_time=MOCK_FETCH_TIME, + event_severity_filter=None, + fetch_limit=10, + include_files=True, + integration_context=None, + ) + assert len(incidents) == 3 + assert next_run["last_fetch"] + + +def test_fetch_incidents_fetch_limit(code42_fetch_incidents_mock): + mock_date = "2020-01-01T00:00:00.000Z" + mock_timestamp = int(time.mktime(time.strptime(mock_date, "%Y-%m-%dT%H:%M:%S.000Z"))) + client = create_client(code42_fetch_incidents_mock) + next_run, incidents, remaining_incidents = fetch_incidents( + client=client, + last_run={"last_fetch": mock_timestamp}, + first_fetch_time=MOCK_FETCH_TIME, + event_severity_filter=None, + fetch_limit=2, + include_files=True, + integration_context=None, + ) + assert len(incidents) == 2 + assert next_run["last_fetch"] + assert len(remaining_incidents) == 1 + # Run again to get the last incident + next_run, incidents, remaining_incidents = fetch_incidents( + client=client, + last_run={"last_fetch": mock_timestamp}, + first_fetch_time=MOCK_FETCH_TIME, + event_severity_filter=None, + fetch_limit=2, + include_files=True, + integration_context={"remaining_incidents": remaining_incidents}, + ) + assert len(incidents) == 1 + assert next_run["last_fetch"] + assert not remaining_incidents diff --git a/Packs/Code42/Integrations/Code42/integration-Code42.yml b/Packs/Code42/Integrations/Code42/integration-Code42.yml index f36c37bfd63e..d5037dae35cb 100644 --- a/Packs/Code42/Integrations/Code42/integration-Code42.yml +++ b/Packs/Code42/Integrations/Code42/integration-Code42.yml @@ -594,7 +594,13 @@ script: code42_securityalert_context, headers=SECURITY_ALERT_HEADERS, ) - return readable_outputs, {"Code42.SecurityAlert": code42_securityalert_context}, alert + return CommandResults( + outputs_prefix="Code42.SecurityAlert", + outputs_key_field="ID", + outputs=code42_securityalert_context, + readable_output=readable_outputs, + raw_response=alert, + ) except Exception as e: return_error(create_command_error_message(demisto.command(), e)) @@ -602,7 +608,7 @@ script: @logger def alert_resolve_command(client, args): - code42_security_alert_context = [] + code42_securityalert_context = [] try: alert_id = client.resolve_alert(args["id"]) @@ -616,16 +622,18 @@ script: return "Error retrieving updated alert", {}, {} code42_context = map_to_code42_alert_context(alert_details) - code42_security_alert_context.append(code42_context) + code42_securityalert_context.append(code42_context) readable_outputs = tableToMarkdown( "Code42 Security Alert Resolved", - code42_security_alert_context, + code42_securityalert_context, headers=SECURITY_ALERT_HEADERS, ) - return ( - readable_outputs, - {"Code42.SecurityAlert": code42_security_alert_context}, - alert_details, + return CommandResults( + outputs_prefix="Code42.SecurityAlert", + outputs_key_field="ID", + outputs=code42_securityalert_context, + readable_output=readable_outputs, + raw_response=alert_details, ) except Exception as e: return_error(create_command_error_message(demisto.command(), e)) @@ -646,7 +654,13 @@ script: "Note": note, } readable_outputs = tableToMarkdown("Code42 Departing Employee List User Added", de_context) - return readable_outputs, {"Code42.DepartingEmployee": de_context}, user_id + return CommandResults( + outputs_prefix="Code42.DepartingEmployee", + outputs_key_field="UserID", + outputs=de_context, + readable_output=readable_outputs, + raw_response=user_id, + ) except Exception as e: return_error(create_command_error_message(demisto.command(), e)) @@ -661,7 +675,13 @@ script: readable_outputs = tableToMarkdown( "Code42 Departing Employee List User Removed", de_context ) - return readable_outputs, {"Code42.DepartingEmployee": de_context}, user_id + return CommandResults( + outputs_prefix="Code42.DepartingEmployee", + outputs_key_field="UserID", + outputs=de_context, + readable_output=readable_outputs, + raw_response=user_id, + ) except Exception as e: return_error(create_command_error_message(demisto.command(), e)) @@ -682,10 +702,12 @@ script: for e in employees ] readable_outputs = tableToMarkdown("All Departing Employees", employees_context) - return ( - readable_outputs, - {"Code42.DepartingEmployee(val.UserID && val.UserID == obj.UserID)": employees_context}, - employees, + return CommandResults( + outputs_prefix="Code42.DepartingEmployee", + outputs_key_field="UserID", + outputs=employees_context, + readable_output=readable_outputs, + raw_response=employees, ) except Exception as e: return_error(create_command_error_message(demisto.command(), e)) @@ -700,7 +722,13 @@ script: user_id = client.add_user_to_high_risk_employee(username, note) hr_context = {"UserID": user_id, "Username": username} readable_outputs = tableToMarkdown("Code42 High Risk Employee List User Added", hr_context) - return readable_outputs, {"Code42.HighRiskEmployee": hr_context}, user_id + return CommandResults( + outputs_prefix="Code42.HighRiskEmployee", + outputs_key_field="UserID", + outputs=hr_context, + readable_output=readable_outputs, + raw_response=user_id, + ) except Exception as e: return_error(create_command_error_message(demisto.command(), e)) @@ -715,7 +743,13 @@ script: readable_outputs = tableToMarkdown( "Code42 High Risk Employee List User Removed", hr_context ) - return readable_outputs, {"Code42.HighRiskEmployee": hr_context}, user_id + return CommandResults( + outputs_prefix="Code42.HighRiskEmployee", + outputs_key_field="UserID", + outputs=hr_context, + readable_output=readable_outputs, + raw_response=user_id, + ) except Exception as e: return_error(create_command_error_message(demisto.command(), e)) @@ -732,11 +766,14 @@ script: for e in employees ] readable_outputs = tableToMarkdown("Retrieved All High Risk Employees", employees_context) - return ( - readable_outputs, - {"Code42.HighRiskEmployee(val.UserID && val.UserID == obj.UserID)": employees_context}, - employees, + return CommandResults( + outputs_prefix="Code42.HighRiskEmployee", + outputs_key_field="UserID", + outputs=employees_context, + readable_output=readable_outputs, + raw_response=employees, ) + except Exception as e: return_error(create_command_error_message(demisto.command(), e)) @@ -750,7 +787,13 @@ script: user_id = client.add_user_risk_tags(username, tags) rt_context = {"UserID": user_id, "Username": username, "RiskTags": tags} readable_outputs = tableToMarkdown("Code42 Risk Tags Added", rt_context) - return readable_outputs, {"Code42.HighRiskEmployee": rt_context}, user_id + return CommandResults( + outputs_prefix="Code42.HighRiskEmployee", + outputs_key_field="UserID", + outputs=rt_context, + readable_output=readable_outputs, + raw_response=user_id, + ) except Exception as e: return_error(create_command_error_message(demisto.command(), e)) @@ -764,7 +807,13 @@ script: user_id = client.remove_user_risk_tags(username, tags) rt_context = {"UserID": user_id, "Username": username, "RiskTags": tags} readable_outputs = tableToMarkdown("Code42 Risk Tags Removed", rt_context) - return readable_outputs, {"Code42.HighRiskEmployee": rt_context}, user_id + return CommandResults( + outputs_prefix="Code42.HighRiskEmployee", + outputs_key_field="UserID", + outputs=rt_context, + readable_output=readable_outputs, + raw_response=user_id, + ) except Exception as e: return_error(create_command_error_message(demisto.command(), e)) @@ -796,10 +845,15 @@ script: security_data_context_key = "Code42.SecurityData(val.EventID && val.EventID == obj.EventID)" context = {security_data_context_key: code42_security_data_context, "File": file_context} return readable_outputs, context, file_events + else: return "No results found", {}, {} + """Fetching""" + + + def _create_incident_from_alert_details(details): return {"name": "Code42 - {}".format(details["name"]), "occurred": details["createdAt"]} @@ -922,6 +976,10 @@ script: return fetcher.fetch() + """Main and test""" + + + def test_module(client): try: # Will fail if unauthorized @@ -990,7 +1048,7 @@ script: integration_context["remaining_incidents"] = remaining_incidents demisto.setIntegrationContext(integration_context) elif command in commands: - return_outputs(*commands[command](client, demisto.args())) + return_results(*commands[command](client, demisto.args())) # Log exceptions except Exception as e: return_error(f"Failed to execute {demisto.command()} command. Error: {str(e)}") From 94821899419cbf48a4868f12c8e64817ba5fa6e1 Mon Sep 17 00:00:00 2001 From: Juliya Smith Date: Mon, 29 Jun 2020 17:01:48 +0000 Subject: [PATCH 04/17] Save --- Packs/Code42/Integrations/Code42/Code42.py | 21 ++++++++++++++++++- .../Code42/integration-Code42.yml | 7 ++++++- 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/Packs/Code42/Integrations/Code42/Code42.py b/Packs/Code42/Integrations/Code42/Code42.py index ed7baacffd11..43bf1fb64f3b 100644 --- a/Packs/Code42/Integrations/Code42/Code42.py +++ b/Packs/Code42/Integrations/Code42/Code42.py @@ -754,6 +754,20 @@ def securitydata_search_command(client, args): code42_security_data_context, headers=SECURITY_EVENT_HEADERS, ) + + code42_results = CommandResults( + outputs_prefix="Code42.SecurityData", + outputs_key_field="EventID", + outputs=code42_security_data_context, + raw_response=file_events + ) + file_results = CommandResults( + outputs_prefix="File", + outputs_key_field=None, + outputs=file_context, + ) + + security_data_context_key = "Code42.SecurityData(val.EventID && val.EventID == obj.EventID)" context = {security_data_context_key: code42_security_data_context, "File": file_context} return readable_outputs, context, file_events @@ -958,7 +972,12 @@ def main(): integration_context["remaining_incidents"] = remaining_incidents demisto.setIntegrationContext(integration_context) elif command in commands: - return_results(*commands[command](client, demisto.args())) + results = commands[command](client, demisto.args()) + if isinstance(results, CommandResults): + return_results(results) + else: + # Case for when command affects multiple outputs. + return_results(*results) # Log exceptions except Exception as e: return_error(f"Failed to execute {demisto.command()} command. Error: {str(e)}") diff --git a/Packs/Code42/Integrations/Code42/integration-Code42.yml b/Packs/Code42/Integrations/Code42/integration-Code42.yml index d5037dae35cb..988d7ff16e53 100644 --- a/Packs/Code42/Integrations/Code42/integration-Code42.yml +++ b/Packs/Code42/Integrations/Code42/integration-Code42.yml @@ -1048,7 +1048,12 @@ script: integration_context["remaining_incidents"] = remaining_incidents demisto.setIntegrationContext(integration_context) elif command in commands: - return_results(*commands[command](client, demisto.args())) + results = commands[command](client, demisto.args()) + if isinstance(results, CommandResults): + return_results(results) + else: + # Case for when command affects multiple outputs. + return_results(*results) # Log exceptions except Exception as e: return_error(f"Failed to execute {demisto.command()} command. Error: {str(e)}") From 78deed23810569c845029376c169a81d09e7d91a Mon Sep 17 00:00:00 2001 From: Juliya Smith Date: Mon, 29 Jun 2020 17:44:12 +0000 Subject: [PATCH 05/17] Command results --- Packs/Code42/Integrations/Code42/Code42.py | 17 +++++-------- .../Code42/integration-Code42.yml | 25 +++++++++++++------ 2 files changed, 23 insertions(+), 19 deletions(-) diff --git a/Packs/Code42/Integrations/Code42/Code42.py b/Packs/Code42/Integrations/Code42/Code42.py index 43bf1fb64f3b..806acf1a9d54 100644 --- a/Packs/Code42/Integrations/Code42/Code42.py +++ b/Packs/Code42/Integrations/Code42/Code42.py @@ -754,7 +754,6 @@ def securitydata_search_command(client, args): code42_security_data_context, headers=SECURITY_EVENT_HEADERS, ) - code42_results = CommandResults( outputs_prefix="Code42.SecurityData", outputs_key_field="EventID", @@ -766,11 +765,7 @@ def securitydata_search_command(client, args): outputs_key_field=None, outputs=file_context, ) - - - security_data_context_key = "Code42.SecurityData(val.EventID && val.EventID == obj.EventID)" - context = {security_data_context_key: code42_security_data_context, "File": file_context} - return readable_outputs, context, file_events + return code42_results, file_results else: return "No results found", {}, {} @@ -973,11 +968,11 @@ def main(): demisto.setIntegrationContext(integration_context) elif command in commands: results = commands[command](client, demisto.args()) - if isinstance(results, CommandResults): - return_results(results) - else: - # Case for when command affects multiple outputs. - return_results(*results) + if not isinstance(results, tuple) and not isinstance(results, list): + results = [results] + for result in results: + return_results(result) + # Log exceptions except Exception as e: return_error(f"Failed to execute {demisto.command()} command. Error: {str(e)}") diff --git a/Packs/Code42/Integrations/Code42/integration-Code42.yml b/Packs/Code42/Integrations/Code42/integration-Code42.yml index 988d7ff16e53..f605c76fe9dc 100644 --- a/Packs/Code42/Integrations/Code42/integration-Code42.yml +++ b/Packs/Code42/Integrations/Code42/integration-Code42.yml @@ -842,9 +842,18 @@ script: code42_security_data_context, headers=SECURITY_EVENT_HEADERS, ) - security_data_context_key = "Code42.SecurityData(val.EventID && val.EventID == obj.EventID)" - context = {security_data_context_key: code42_security_data_context, "File": file_context} - return readable_outputs, context, file_events + code42_results = CommandResults( + outputs_prefix="Code42.SecurityData", + outputs_key_field="EventID", + outputs=code42_security_data_context, + raw_response=file_events + ) + file_results = CommandResults( + outputs_prefix="File", + outputs_key_field=None, + outputs=file_context, + ) + return code42_results, file_results else: return "No results found", {}, {} @@ -1049,11 +1058,11 @@ script: demisto.setIntegrationContext(integration_context) elif command in commands: results = commands[command](client, demisto.args()) - if isinstance(results, CommandResults): - return_results(results) - else: - # Case for when command affects multiple outputs. - return_results(*results) + if not isinstance(results, tuple) and not isinstance(results, list): + results = [results] + for result in results: + return_results(result) + # Log exceptions except Exception as e: return_error(f"Failed to execute {demisto.command()} command. Error: {str(e)}") From 3d1d29006920173ff6017b816e58a15d87330de5 Mon Sep 17 00:00:00 2001 From: Juliya Smith Date: Mon, 29 Jun 2020 17:51:22 +0000 Subject: [PATCH 06/17] Add missing CL entries --- Packs/Code42/Integrations/Code42/CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Packs/Code42/Integrations/Code42/CHANGELOG.md b/Packs/Code42/Integrations/Code42/CHANGELOG.md index 244b5e76f237..6a2e966b02ff 100644 --- a/Packs/Code42/Integrations/Code42/CHANGELOG.md +++ b/Packs/Code42/Integrations/Code42/CHANGELOG.md @@ -9,6 +9,10 @@ Added new commands: - **code42-highriskemployee-remove-risk-tags** that takes a username and risk tags and disassociates the risk tags from the user. Improve error messages for all Commands to include exception detail. +`Code42.DepartingEmployee.UserID` output added. + +`Code42.DepartingEmployee.CaseID` output removed. Use `Code42.DepartingEmployee.UserID`. + ## [20.3.3] - 2020-03-18 #### New Integration 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. \ No newline at end of file From 76aedf784cfb698cb7dc62ec35d495e5864e5381 Mon Sep 17 00:00:00 2001 From: Juliya Smith Date: Mon, 29 Jun 2020 19:37:27 +0000 Subject: [PATCH 07/17] Fix final test --- Packs/Code42/Integrations/Code42/Code42_test.py | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/Packs/Code42/Integrations/Code42/Code42_test.py b/Packs/Code42/Integrations/Code42/Code42_test.py index bb58b476e9dc..a0ff9f4916d3 100644 --- a/Packs/Code42/Integrations/Code42/Code42_test.py +++ b/Packs/Code42/Integrations/Code42/Code42_test.py @@ -1448,8 +1448,14 @@ def test_highriskemployee_remove_risk_tags_command(code42_sdk_mock): def test_security_data_search_command(code42_file_events_mock): client = create_client(code42_file_events_mock) - _, outputs, res = securitydata_search_command(client, MOCK_SECURITY_DATA_SEARCH_QUERY) - outputs_list = outputs["Code42.SecurityData(val.EventID && val.EventID == obj.EventID)"] + cmd_res = securitydata_search_command(client, MOCK_SECURITY_DATA_SEARCH_QUERY) + code42_res = cmd_res[0] + file_res = cmd_res[1] + + assert code42_res.outputs_prefix == "Code42.SecurityData" + assert code42_res.outputs_key_field == "EventID" + assert file_res.outputs_prefix == "File" + actual_query = code42_file_events_mock.securitydata.search_file_events.call_args[0][0] filter_groups = json.loads(str(actual_query))["groups"] expected_query_items = [ @@ -1467,13 +1473,13 @@ def test_security_data_search_command(code42_file_events_mock): assert _filter["term"] == expected_query_items[i][0] assert _filter["value"] == expected_query_items[i][1] - assert len(res) == len(outputs_list) == 3 - assert res == expected_file_events + assert len(code42_res.raw_response) == len(code42_res.outputs) == 3 + assert code42_res.raw_response == expected_file_events # Assert that the Outputs are mapped from the file events. for i in range(0, len(expected_file_events)): mapped_event = map_to_code42_event_context(expected_file_events[i]) - output_item = outputs_list[i] + output_item = code42_res.outputs[i] assert output_item == mapped_event From 5cbf8df27a03655bd31fcedcb6792ec34910b0f1 Mon Sep 17 00:00:00 2001 From: Juliya Smith Date: Mon, 29 Jun 2020 20:09:19 +0000 Subject: [PATCH 08/17] Use readable outputs var --- Packs/Code42/Integrations/Code42/Code42.py | 1 + 1 file changed, 1 insertion(+) diff --git a/Packs/Code42/Integrations/Code42/Code42.py b/Packs/Code42/Integrations/Code42/Code42.py index e5da607dafc0..5a9780c14b18 100644 --- a/Packs/Code42/Integrations/Code42/Code42.py +++ b/Packs/Code42/Integrations/Code42/Code42.py @@ -758,6 +758,7 @@ def securitydata_search_command(client, args): outputs_prefix="Code42.SecurityData", outputs_key_field="EventID", outputs=code42_security_data_context, + readable_output=readable_outputs, raw_response=file_events ) file_results = CommandResults( From a115c351b738806b21777dbc3ad61a392f597480 Mon Sep 17 00:00:00 2001 From: Juliya Smith Date: Mon, 29 Jun 2020 20:09:30 +0000 Subject: [PATCH 09/17] Gen yml --- Packs/Code42/Integrations/Code42/integration-Code42.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Packs/Code42/Integrations/Code42/integration-Code42.yml b/Packs/Code42/Integrations/Code42/integration-Code42.yml index 5292f2a5054f..4655c55c222b 100644 --- a/Packs/Code42/Integrations/Code42/integration-Code42.yml +++ b/Packs/Code42/Integrations/Code42/integration-Code42.yml @@ -846,6 +846,7 @@ script: outputs_prefix="Code42.SecurityData", outputs_key_field="EventID", outputs=code42_security_data_context, + readable_output=readable_outputs, raw_response=file_events ) file_results = CommandResults( @@ -1013,7 +1014,7 @@ script: # Remove trailing slash to prevent wrong URL path to service verify_certificate = not demisto.params().get("insecure", False) proxy = demisto.params().get("proxy", False) - LOG(f"Command being called is {demisto.command()}") + LOG("Command being called is {0}.".format(demisto.command())) try: client = Code42Client( base_url=base_url, @@ -1066,7 +1067,7 @@ script: # Log exceptions except Exception as e: - return_error(f"Failed to execute {demisto.command()} command. Error: {str(e)}") + return_error("Failed to execute {0} command. Error: {1}".format(demisto.command(), str(e))) if __name__ in ("__main__", "__builtin__", "builtins"): From 0cedd05bf61fabf5bce3f148f50a4148a6915a8b Mon Sep 17 00:00:00 2001 From: Juliya Smith Date: Mon, 29 Jun 2020 20:18:31 +0000 Subject: [PATCH 10/17] Put back CaseID and alias UserID --- Packs/Code42/Integrations/Code42/Code42.py | 5 ++++- Packs/Code42/Integrations/Code42/Code42.yml | 4 ++++ Packs/Code42/Integrations/Code42/Code42_test.py | 2 ++ 3 files changed, 10 insertions(+), 1 deletion(-) diff --git a/Packs/Code42/Integrations/Code42/Code42.py b/Packs/Code42/Integrations/Code42/Code42.py index 5a9780c14b18..bbd0e9e76cbb 100644 --- a/Packs/Code42/Integrations/Code42/Code42.py +++ b/Packs/Code42/Integrations/Code42/Code42.py @@ -567,7 +567,9 @@ def departingemployee_add_command(client, args): note = args.get("note") try: user_id = client.add_user_to_departing_employee(username, departing_date, note) + # CaseID included but it deprecated. de_context = { + "CaseID": user_id, "UserID": user_id, "Username": username, "DepartureDate": departing_date, @@ -590,7 +592,8 @@ def departingemployee_remove_command(client, args): username = args["username"] try: user_id = client.remove_user_from_departing_employee(username) - de_context = {"UserID": user_id, "Username": username} + # CaseID included but is deprecated. + de_context = {"CaseID": user_id, "UserID": user_id, "Username": username} readable_outputs = tableToMarkdown( "Code42 Departing Employee List User Removed", de_context ) diff --git a/Packs/Code42/Integrations/Code42/Code42.yml b/Packs/Code42/Integrations/Code42/Code42.yml index 2dcf45befea1..7a0c05c5ebc8 100644 --- a/Packs/Code42/Integrations/Code42/Code42.yml +++ b/Packs/Code42/Integrations/Code42/Code42.yml @@ -262,6 +262,8 @@ script: - name: note description: Note to attach to the Departing Employee. outputs: + - contextPath: Code42.DepartingEmployee.CaseID + description: Internal Code42 Case ID for the Departing Employee. Deprecated. Use Code42.DepartingEmployee.UserID. - contextPath: Code42.DepartingEmployee.UserID description: Internal Code42 User ID for the Departing Employee. type: string @@ -280,6 +282,8 @@ script: required: true description: The username to remove from the Departing Employee List. outputs: + - contextPath: Code42.DepartingEmployee.CaseID + description: Internal Code42 Case ID for the Departing Employee. Deprecated. Use Code42.DepartingEmployee.UserID. - contextPath: Code42.DepartingEmployee.UserID description: Internal Code42 User ID for the Departing Employee. type: string diff --git a/Packs/Code42/Integrations/Code42/Code42_test.py b/Packs/Code42/Integrations/Code42/Code42_test.py index a0ff9f4916d3..5581d74a0921 100644 --- a/Packs/Code42/Integrations/Code42/Code42_test.py +++ b/Packs/Code42/Integrations/Code42/Code42_test.py @@ -1199,6 +1199,7 @@ def test_departingemployee_add_command(code42_sdk_mock): assert cmd_res.outputs["Note"] == note assert cmd_res.outputs["Username"] == _TEST_USERNAME assert cmd_res.outputs["UserID"] == _TEST_USER_ID + assert cmd_res.outputs["CaseID"] == _TEST_USER_ID add_func.assert_called_once_with(_TEST_USER_ID, departure_date=date) code42_sdk_mock.detectionlists.update_user_notes.assert_called_once_with(_TEST_USER_ID, note) @@ -1211,6 +1212,7 @@ def test_departingemployee_remove_command(code42_sdk_mock): assert cmd_res.outputs_key_field == "UserID" assert cmd_res.outputs["Username"] == _TEST_USERNAME assert cmd_res.outputs["UserID"] == _TEST_USER_ID + assert cmd_res.outputs["CaseID"] == _TEST_USER_ID code42_sdk_mock.detectionlists.departing_employee.remove.assert_called_once_with(_TEST_USER_ID) From 52cd1b669692d927527a84ad98a00ac747069429 Mon Sep 17 00:00:00 2001 From: Juliya Smith Date: Mon, 29 Jun 2020 20:18:46 +0000 Subject: [PATCH 11/17] Gen yml --- Packs/Code42/Integrations/Code42/integration-Code42.yml | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/Packs/Code42/Integrations/Code42/integration-Code42.yml b/Packs/Code42/Integrations/Code42/integration-Code42.yml index 4655c55c222b..9438b9b66774 100644 --- a/Packs/Code42/Integrations/Code42/integration-Code42.yml +++ b/Packs/Code42/Integrations/Code42/integration-Code42.yml @@ -647,7 +647,9 @@ script: note = args.get("note") try: user_id = client.add_user_to_departing_employee(username, departing_date, note) + # CaseID included but it deprecated. de_context = { + "CaseID": user_id, "UserID": user_id, "Username": username, "DepartureDate": departing_date, @@ -671,7 +673,8 @@ script: username = args["username"] try: user_id = client.remove_user_from_departing_employee(username) - de_context = {"UserID": user_id, "Username": username} + # CaseID included but is deprecated. + de_context = {"CaseID": user_id, "UserID": user_id, "Username": username} readable_outputs = tableToMarkdown( "Code42 Departing Employee List User Removed", de_context ) @@ -1277,6 +1280,8 @@ script: - name: note description: Note to attach to the Departing Employee. outputs: + - contextPath: Code42.DepartingEmployee.CaseID + description: Internal Code42 Case ID for the Departing Employee. Deprecated. Use Code42.DepartingEmployee.UserID. - contextPath: Code42.DepartingEmployee.UserID description: Internal Code42 User ID for the Departing Employee. type: string @@ -1295,6 +1300,8 @@ script: required: true description: The username to remove from the Departing Employee List. outputs: + - contextPath: Code42.DepartingEmployee.CaseID + description: Internal Code42 Case ID for the Departing Employee. Deprecated. Use Code42.DepartingEmployee.UserID. - contextPath: Code42.DepartingEmployee.UserID description: Internal Code42 User ID for the Departing Employee. type: string From edf2da1e5aba49b4233b70fcda9e77a5b0f62081 Mon Sep 17 00:00:00 2001 From: Juliya Smith Date: Mon, 29 Jun 2020 22:00:43 +0000 Subject: [PATCH 12/17] Put back CL --- Packs/Code42/Integrations/Code42/CHANGELOG.md | 4 ---- 1 file changed, 4 deletions(-) diff --git a/Packs/Code42/Integrations/Code42/CHANGELOG.md b/Packs/Code42/Integrations/Code42/CHANGELOG.md index 6a2e966b02ff..244b5e76f237 100644 --- a/Packs/Code42/Integrations/Code42/CHANGELOG.md +++ b/Packs/Code42/Integrations/Code42/CHANGELOG.md @@ -9,10 +9,6 @@ Added new commands: - **code42-highriskemployee-remove-risk-tags** that takes a username and risk tags and disassociates the risk tags from the user. Improve error messages for all Commands to include exception detail. -`Code42.DepartingEmployee.UserID` output added. - -`Code42.DepartingEmployee.CaseID` output removed. Use `Code42.DepartingEmployee.UserID`. - ## [20.3.3] - 2020-03-18 #### New Integration 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. \ No newline at end of file From cc874d165d2760fb958858ed6827bf5abf286c00 Mon Sep 17 00:00:00 2001 From: Juliya Smith Date: Mon, 29 Jun 2020 22:37:11 +0000 Subject: [PATCH 13/17] Add type --- Packs/Code42/Integrations/Code42/Code42.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Packs/Code42/Integrations/Code42/Code42.yml b/Packs/Code42/Integrations/Code42/Code42.yml index 7a0c05c5ebc8..78356ecedd32 100644 --- a/Packs/Code42/Integrations/Code42/Code42.yml +++ b/Packs/Code42/Integrations/Code42/Code42.yml @@ -264,6 +264,7 @@ script: outputs: - contextPath: Code42.DepartingEmployee.CaseID description: Internal Code42 Case ID for the Departing Employee. Deprecated. Use Code42.DepartingEmployee.UserID. + type: string - contextPath: Code42.DepartingEmployee.UserID description: Internal Code42 User ID for the Departing Employee. type: string @@ -284,6 +285,7 @@ script: outputs: - contextPath: Code42.DepartingEmployee.CaseID description: Internal Code42 Case ID for the Departing Employee. Deprecated. Use Code42.DepartingEmployee.UserID. + type: string - contextPath: Code42.DepartingEmployee.UserID description: Internal Code42 User ID for the Departing Employee. type: string From 661ca93d3c2414f0efd88b1b45d2342bcede2c63 Mon Sep 17 00:00:00 2001 From: Juliya Smith Date: Tue, 30 Jun 2020 13:01:07 +0000 Subject: [PATCH 14/17] PR feedback + refactor --- Packs/Code42/Integrations/Code42/Code42.py | 471 +++++++++-------- .../Code42/Integrations/Code42/Code42_test.py | 13 +- .../Code42/integration-Code42.yml | 485 +++++++++--------- 3 files changed, 484 insertions(+), 485 deletions(-) diff --git a/Packs/Code42/Integrations/Code42/Code42.py b/Packs/Code42/Integrations/Code42/Code42.py index bbd0e9e76cbb..461aee809534 100644 --- a/Packs/Code42/Integrations/Code42/Code42.py +++ b/Packs/Code42/Integrations/Code42/Code42.py @@ -165,7 +165,11 @@ def __init__(self, sdk, base_url, auth, verify=True, proxy=False): # Allow sdk parameter for unit testing. # Otherwise, lazily load the SDK so that the TEST Command can effectively check auth. self._sdk = sdk - self._sdk_factory = lambda: py42.sdk.from_local_account(base_url, auth[0], auth[1]) if not self._sdk else None + self._sdk_factory = ( + lambda: py42.sdk.from_local_account(base_url, auth[0], auth[1]) + if not self._sdk + else None + ) py42.settings.set_user_agent_suffix("Cortex XSOAR") def _get_sdk(self): @@ -504,60 +508,61 @@ def create_command_error_message(cmd, ex): @logger def alert_get_command(client, args): code42_securityalert_context = [] - try: - alert = client.get_alert_details(args["id"]) - if not alert: - return "No results found", {}, {} - - code42_context = map_to_code42_alert_context(alert) - code42_securityalert_context.append(code42_context) - readable_outputs = tableToMarkdown( - "Code42 Security Alert Results", - code42_securityalert_context, - headers=SECURITY_ALERT_HEADERS, - ) + alert = client.get_alert_details(args["id"]) + if not alert: return CommandResults( - outputs_prefix="Code42.SecurityAlert", + readable_output="No results found", + outputs={}, outputs_key_field="ID", - outputs=code42_securityalert_context, - readable_output=readable_outputs, - raw_response=alert, + outputs_prefix="Code42.SecurityAlert", + raw_response={}, ) - except Exception as e: - return_error(create_command_error_message(demisto.command(), e)) + + code42_context = map_to_code42_alert_context(alert) + code42_securityalert_context.append(code42_context) + readable_outputs = tableToMarkdown( + "Code42 Security Alert Results", + code42_securityalert_context, + headers=SECURITY_ALERT_HEADERS, + ) + return CommandResults( + outputs_prefix="Code42.SecurityAlert", + outputs_key_field="ID", + outputs=code42_securityalert_context, + readable_output=readable_outputs, + raw_response=alert, + ) @logger def alert_resolve_command(client, args): code42_securityalert_context = [] - - try: - alert_id = client.resolve_alert(args["id"]) - - if not alert_id: - return "No results found", {}, {} - - # Retrieve new alert details - alert_details = client.get_alert_details(alert_id) - if not alert_details: - return "Error retrieving updated alert", {}, {} - - code42_context = map_to_code42_alert_context(alert_details) - code42_securityalert_context.append(code42_context) - readable_outputs = tableToMarkdown( - "Code42 Security Alert Resolved", - code42_securityalert_context, - headers=SECURITY_ALERT_HEADERS, - ) + alert_id = client.resolve_alert(args["id"]) + if not alert_id: return CommandResults( - outputs_prefix="Code42.SecurityAlert", + readable_output="No results found", + outputs={}, outputs_key_field="ID", - outputs=code42_securityalert_context, - readable_output=readable_outputs, - raw_response=alert_details, + outputs_prefix="Code42.SecurityAlert", + raw_response={}, ) - except Exception as e: - return_error(create_command_error_message(demisto.command(), e)) + + # Retrieve new alert details + alert_details = client.get_alert_details(alert_id) + code42_context = map_to_code42_alert_context(alert_details) + code42_securityalert_context.append(code42_context) + readable_outputs = tableToMarkdown( + "Code42 Security Alert Resolved", + code42_securityalert_context, + headers=SECURITY_ALERT_HEADERS, + ) + return CommandResults( + outputs_prefix="Code42.SecurityAlert", + outputs_key_field="ID", + outputs=code42_securityalert_context, + readable_output=readable_outputs, + raw_response=alert_details, + ) @logger @@ -565,173 +570,144 @@ def departingemployee_add_command(client, args): departing_date = args.get("departuredate") username = args["username"] note = args.get("note") - try: - user_id = client.add_user_to_departing_employee(username, departing_date, note) - # CaseID included but it deprecated. - de_context = { - "CaseID": user_id, - "UserID": user_id, - "Username": username, - "DepartureDate": departing_date, - "Note": note, - } - readable_outputs = tableToMarkdown("Code42 Departing Employee List User Added", de_context) - return CommandResults( - outputs_prefix="Code42.DepartingEmployee", - outputs_key_field="UserID", - outputs=de_context, - readable_output=readable_outputs, - raw_response=user_id, - ) - except Exception as e: - return_error(create_command_error_message(demisto.command(), e)) + user_id = client.add_user_to_departing_employee(username, departing_date, note) + # CaseID included but is deprecated. + de_context = { + "CaseID": user_id, + "UserID": user_id, + "Username": username, + "DepartureDate": departing_date, + "Note": note, + } + readable_outputs = tableToMarkdown("Code42 Departing Employee List User Added", de_context) + return CommandResults( + outputs_prefix="Code42.DepartingEmployee", + outputs_key_field="UserID", + outputs=de_context, + readable_output=readable_outputs, + raw_response=user_id, + ) @logger def departingemployee_remove_command(client, args): username = args["username"] - try: - user_id = client.remove_user_from_departing_employee(username) - # CaseID included but is deprecated. - de_context = {"CaseID": user_id, "UserID": user_id, "Username": username} - readable_outputs = tableToMarkdown( - "Code42 Departing Employee List User Removed", de_context - ) - return CommandResults( - outputs_prefix="Code42.DepartingEmployee", - outputs_key_field="UserID", - outputs=de_context, - readable_output=readable_outputs, - raw_response=user_id, - ) - except Exception as e: - return_error(create_command_error_message(demisto.command(), e)) + user_id = client.remove_user_from_departing_employee(username) + # CaseID included but is deprecated. + de_context = {"CaseID": user_id, "UserID": user_id, "Username": username} + readable_outputs = tableToMarkdown("Code42 Departing Employee List User Removed", de_context) + return CommandResults( + outputs_prefix="Code42.DepartingEmployee", + outputs_key_field="UserID", + outputs=de_context, + readable_output=readable_outputs, + raw_response=user_id, + ) @logger def departingemployee_get_all_command(client, args): results = args.get("results") or 50 - try: - employees = client.get_all_departing_employees(results) - employees_context = [ - { - "UserID": e["userId"], - "Username": e["userName"], - "DepartureDate": e.get("departureDate"), - "Note": e["notes"], - } - for e in employees - ] - readable_outputs = tableToMarkdown("All Departing Employees", employees_context) - return CommandResults( - outputs_prefix="Code42.DepartingEmployee", - outputs_key_field="UserID", - outputs=employees_context, - readable_output=readable_outputs, - raw_response=employees, - ) - except Exception as e: - return_error(create_command_error_message(demisto.command(), e)) + employees = client.get_all_departing_employees(results) + employees_context = [ + { + "UserID": e["userId"], + "Username": e["userName"], + "DepartureDate": e.get("departureDate"), + "Note": e["notes"], + } + for e in employees + ] + readable_outputs = tableToMarkdown("All Departing Employees", employees_context) + return CommandResults( + outputs_prefix="Code42.DepartingEmployee", + outputs_key_field="UserID", + outputs=employees_context, + readable_output=readable_outputs, + raw_response=employees, + ) @logger def highriskemployee_add_command(client, args): username = args["username"] note = args.get("note") - try: - user_id = client.add_user_to_high_risk_employee(username, note) - hr_context = {"UserID": user_id, "Username": username} - readable_outputs = tableToMarkdown("Code42 High Risk Employee List User Added", hr_context) - return CommandResults( - outputs_prefix="Code42.HighRiskEmployee", - outputs_key_field="UserID", - outputs=hr_context, - readable_output=readable_outputs, - raw_response=user_id, - ) - except Exception as e: - return_error(create_command_error_message(demisto.command(), e)) + user_id = client.add_user_to_high_risk_employee(username, note) + hr_context = {"UserID": user_id, "Username": username} + readable_outputs = tableToMarkdown("Code42 High Risk Employee List User Added", hr_context) + return CommandResults( + outputs_prefix="Code42.HighRiskEmployee", + outputs_key_field="UserID", + outputs=hr_context, + readable_output=readable_outputs, + raw_response=user_id, + ) @logger def highriskemployee_remove_command(client, args): username = args["username"] - try: - user_id = client.remove_user_from_high_risk_employee(username) - hr_context = {"UserID": user_id, "Username": username} - readable_outputs = tableToMarkdown( - "Code42 High Risk Employee List User Removed", hr_context - ) - return CommandResults( - outputs_prefix="Code42.HighRiskEmployee", - outputs_key_field="UserID", - outputs=hr_context, - readable_output=readable_outputs, - raw_response=user_id, - ) - except Exception as e: - return_error(create_command_error_message(demisto.command(), e)) + user_id = client.remove_user_from_high_risk_employee(username) + hr_context = {"UserID": user_id, "Username": username} + readable_outputs = tableToMarkdown("Code42 High Risk Employee List User Removed", hr_context) + return CommandResults( + outputs_prefix="Code42.HighRiskEmployee", + outputs_key_field="UserID", + outputs=hr_context, + readable_output=readable_outputs, + raw_response=user_id, + ) @logger def highriskemployee_get_all_command(client, args): tags = args.get("risktags") results = args.get("results") or 50 - try: - employees = client.get_all_high_risk_employees(tags, results) - employees_context = [ - {"UserID": e.get("userId"), "Username": e.get("userName"), "Note": e.get("notes")} - for e in employees - ] - readable_outputs = tableToMarkdown("Retrieved All High Risk Employees", employees_context) - return CommandResults( - outputs_prefix="Code42.HighRiskEmployee", - outputs_key_field="UserID", - outputs=employees_context, - readable_output=readable_outputs, - raw_response=employees, - ) - - except Exception as e: - return_error(create_command_error_message(demisto.command(), e)) + employees = client.get_all_high_risk_employees(tags, results) + employees_context = [ + {"UserID": e.get("userId"), "Username": e.get("userName"), "Note": e.get("notes")} + for e in employees + ] + readable_outputs = tableToMarkdown("Retrieved All High Risk Employees", employees_context) + return CommandResults( + outputs_prefix="Code42.HighRiskEmployee", + outputs_key_field="UserID", + outputs=employees_context, + readable_output=readable_outputs, + raw_response=employees, + ) @logger def highriskemployee_add_risk_tags_command(client, args): username = args.get("username") tags = args.get("risktags") - try: - user_id = client.add_user_risk_tags(username, tags) - rt_context = {"UserID": user_id, "Username": username, "RiskTags": tags} - readable_outputs = tableToMarkdown("Code42 Risk Tags Added", rt_context) - return CommandResults( - outputs_prefix="Code42.HighRiskEmployee", - outputs_key_field="UserID", - outputs=rt_context, - readable_output=readable_outputs, - raw_response=user_id, - ) - except Exception as e: - return_error(create_command_error_message(demisto.command(), e)) + user_id = client.add_user_risk_tags(username, tags) + rt_context = {"UserID": user_id, "Username": username, "RiskTags": tags} + readable_outputs = tableToMarkdown("Code42 Risk Tags Added", rt_context) + return CommandResults( + outputs_prefix="Code42.HighRiskEmployee", + outputs_key_field="UserID", + outputs=rt_context, + readable_output=readable_outputs, + raw_response=user_id, + ) @logger def highriskemployee_remove_risk_tags_command(client, args): username = args.get("username") tags = args.get("risktags") - try: - user_id = client.remove_user_risk_tags(username, tags) - rt_context = {"UserID": user_id, "Username": username, "RiskTags": tags} - readable_outputs = tableToMarkdown("Code42 Risk Tags Removed", rt_context) - return CommandResults( - outputs_prefix="Code42.HighRiskEmployee", - outputs_key_field="UserID", - outputs=rt_context, - readable_output=readable_outputs, - raw_response=user_id, - ) - except Exception as e: - return_error(create_command_error_message(demisto.command(), e)) + user_id = client.remove_user_risk_tags(username, tags) + rt_context = {"UserID": user_id, "Username": username, "RiskTags": tags} + readable_outputs = tableToMarkdown("Code42 Risk Tags Removed", rt_context) + return CommandResults( + outputs_prefix="Code42.HighRiskEmployee", + outputs_key_field="UserID", + outputs=rt_context, + readable_output=readable_outputs, + raw_response=user_id, + ) @logger @@ -739,6 +715,7 @@ def securitydata_search_command(client, args): code42_security_data_context = [] _json = args.get("json") file_context = [] + # If JSON payload is passed as an argument, ignore all other args and search by JSON payload if _json is not None: file_events = client.search_file_events(_json) @@ -762,17 +739,21 @@ def securitydata_search_command(client, args): outputs_key_field="EventID", outputs=code42_security_data_context, readable_output=readable_outputs, - raw_response=file_events + raw_response=file_events, ) file_results = CommandResults( - outputs_prefix="File", - outputs_key_field=None, - outputs=file_context, + outputs_prefix="File", outputs_key_field=None, outputs=file_context ) return code42_results, file_results else: - return "No results found", {}, {} + return CommandResults( + readable_output="No results found", + outputs={}, + outputs_key_field="EventID", + outputs_prefix="Code42.SecurityData", + raw_response={}, + ) """Fetching""" @@ -827,7 +808,7 @@ def fetch(self): incidents = [self._create_incident_from_alert(a) for a in alerts] save_time = datetime.utcnow().timestamp() next_run = {"last_fetch": save_time} - return next_run, incidents[: self._fetch_limit], incidents[self._fetch_limit:] + return next_run, incidents[: self._fetch_limit], incidents[self._fetch_limit :] def _fetch_remaining_incidents_from_last_run(self): if self._integration_context: @@ -836,8 +817,8 @@ 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], + remaining_incidents[self._fetch_limit :], ) def _get_start_query_time(self): @@ -917,70 +898,84 @@ def test_module(client): ) -def main(): - """ - PARSE AND VALIDATE INTEGRATION PARAMS - """ +def get_command_map(): + return { + "code42-alert-get": alert_get_command, + "code42-alert-resolve": alert_resolve_command, + "code42-securitydata-search": securitydata_search_command, + "code42-departingemployee-add": departingemployee_add_command, + "code42-departingemployee-remove": departingemployee_remove_command, + "code42-departingemployee-get-all": departingemployee_get_all_command, + "code42-highriskemployee-add": highriskemployee_add_command, + "code42-highriskemployee-remove": highriskemployee_remove_command, + "code42-highriskemployee-get-all": highriskemployee_get_all_command, + "code42-highriskemployee-add-risk-tags": highriskemployee_add_risk_tags_command, + "code42-highriskemployee-remove-risk-tags": highriskemployee_remove_risk_tags_command, + } + + +def handle_test_command(client): + # This is the call made when pressing the integration Test button. + result = test_module(client) + demisto.results(result) + + +def handle_fetch_command(client): + integration_context = demisto.getIntegrationContext() + # Set and define the fetch incidents command to run after activated via integration settings. + next_run, incidents, remaining_incidents = fetch_incidents( + client=client, + last_run=demisto.getLastRun(), + first_fetch_time=demisto.params().get("fetch_time"), + event_severity_filter=demisto.params().get("alert_severity"), + fetch_limit=int(demisto.params().get("fetch_limit")), + include_files=demisto.params().get("include_files"), + integration_context=integration_context, + ) + demisto.setLastRun(next_run) + demisto.incidents(incidents) + # Store remaining incidents in integration context + integration_context["remaining_incidents"] = remaining_incidents + demisto.setIntegrationContext(integration_context) + + +def try_run_command(command): + try: + results = command() + if not isinstance(results, tuple) and not isinstance(results, list): + results = [results] + for result in results: + return_results(result) + except Exception as e: + return_error(create_command_error_message(demisto.command(), e)) + + +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") - # Remove trailing slash to prevent wrong URL path to service verify_certificate = not demisto.params().get("insecure", False) proxy = demisto.params().get("proxy", False) LOG("Command being called is {0}.".format(demisto.command())) - try: - client = Code42Client( - base_url=base_url, - sdk=None, - auth=(username, password), - verify=verify_certificate, - proxy=proxy, - ) - commands = { - "code42-alert-get": alert_get_command, - "code42-alert-resolve": alert_resolve_command, - "code42-securitydata-search": securitydata_search_command, - "code42-departingemployee-add": departingemployee_add_command, - "code42-departingemployee-remove": departingemployee_remove_command, - "code42-departingemployee-get-all": departingemployee_get_all_command, - "code42-highriskemployee-add": highriskemployee_add_command, - "code42-highriskemployee-remove": highriskemployee_remove_command, - "code42-highriskemployee-get-all": highriskemployee_get_all_command, - "code42-highriskemployee-add-risk-tags": highriskemployee_add_risk_tags_command, - "code42-highriskemployee-remove-risk-tags": highriskemployee_remove_risk_tags_command, - } - command = demisto.command() - if command == "test-module": - # This is the call made when pressing the integration Test button. - result = test_module(client) - demisto.results(result) - elif command == "fetch-incidents": - integration_context = demisto.getIntegrationContext() - # Set and define the fetch incidents command to run after activated via integration settings. - next_run, incidents, remaining_incidents = fetch_incidents( - client=client, - last_run=demisto.getLastRun(), - first_fetch_time=demisto.params().get("fetch_time"), - event_severity_filter=demisto.params().get("alert_severity"), - fetch_limit=int(demisto.params().get("fetch_limit")), - include_files=demisto.params().get("include_files"), - integration_context=integration_context, - ) - demisto.setLastRun(next_run) - demisto.incidents(incidents) - # Store remaining incidents in integration context - integration_context["remaining_incidents"] = remaining_incidents - demisto.setIntegrationContext(integration_context) - elif command in commands: - results = commands[command](client, demisto.args()) - if not isinstance(results, tuple) and not isinstance(results, list): - results = [results] - for result in results: - return_results(result) - - # Log exceptions - except Exception as e: - return_error("Failed to execute {0} command. Error: {1}".format(demisto.command(), str(e))) + client = Code42Client( + base_url=base_url, + sdk=None, + auth=(username, password), + verify=verify_certificate, + proxy=proxy, + ) + commands = get_command_map() + command = demisto.command() + if command == "test-module": + handle_test_command(client) + elif command == "fetch-incidents": + handle_fetch_command(client) + elif command in commands: + try_run_command(lambda: commands[command](client, demisto.args())) + + +def main(): + run_code42_integration() if __name__ in ("__main__", "__builtin__", "builtins"): diff --git a/Packs/Code42/Integrations/Code42/Code42_test.py b/Packs/Code42/Integrations/Code42/Code42_test.py index 5581d74a0921..2170f3e21647 100644 --- a/Packs/Code42/Integrations/Code42/Code42_test.py +++ b/Packs/Code42/Integrations/Code42/Code42_test.py @@ -1188,9 +1188,8 @@ def test_departingemployee_add_command(code42_sdk_mock): date = "2020-01-01" note = "Dummy note" cmd_res = departingemployee_add_command( - client, - {"username": _TEST_USERNAME, "departuredate": date, "note": note}, - ) + client, {"username": _TEST_USERNAME, "departuredate": date, "note": note} + ) add_func = code42_sdk_mock.detectionlists.departing_employee.add assert cmd_res.raw_response == _TEST_USER_ID assert cmd_res.outputs_prefix == "Code42.DepartingEmployee" @@ -1332,7 +1331,7 @@ def test_highriskemployee_get_all_command(code42_high_risk_employee_mock): cmd_res = highriskemployee_get_all_command(client, {}) expected_response = json.loads(MOCK_GET_ALL_HIGH_RISK_EMPLOYEES_RESPONSE)["items"] assert cmd_res.outputs_key_field == "UserID" - assert cmd_res.outputs_prefix == "Code42.HighRiskEmployee" + assert cmd_res.outputs_prefix == "Code42.HighRiskEmployee" assert cmd_res.raw_response == expected_response assert code42_high_risk_employee_mock.detectionlists.high_risk_employee.get_all.call_count == 1 assert_detection_list_outputs_match_response_items(cmd_res.outputs, expected_response) @@ -1404,9 +1403,7 @@ def test_highriskemployee_get_all_command_when_no_employees(code42_high_risk_emp client = create_client(code42_high_risk_employee_mock) cmd_res = highriskemployee_get_all_command( client, - { - "risktags": "PERFORMANCE_CONCERNS SUSPICIOUS_SYSTEM_ACTIVITY POOR_SECURITY_PRACTICES" - }, + {"risktags": "PERFORMANCE_CONCERNS SUSPICIOUS_SYSTEM_ACTIVITY POOR_SECURITY_PRACTICES"}, ) assert cmd_res.outputs_prefix == "Code42.HighRiskEmployee" assert cmd_res.outputs_key_field == "UserID" @@ -1464,7 +1461,7 @@ def test_security_data_search_command(code42_file_events_mock): ("md5Checksum", "d41d8cd98f00b204e9800998ecf8427e"), ("osHostName", "DESKTOP-0001"), ("deviceUserName", "user3@example.com"), - ("exposure", "ApplicationRead") + ("exposure", "ApplicationRead"), ] expected_file_events = json.loads(MOCK_SECURITY_EVENT_RESPONSE)["fileEvents"] diff --git a/Packs/Code42/Integrations/Code42/integration-Code42.yml b/Packs/Code42/Integrations/Code42/integration-Code42.yml index 9438b9b66774..50f631f4f34b 100644 --- a/Packs/Code42/Integrations/Code42/integration-Code42.yml +++ b/Packs/Code42/Integrations/Code42/integration-Code42.yml @@ -2,7 +2,7 @@ commonfields: id: Code42 version: -1 name: Code42 -display: Code42 +display: Code42 (Partner Contribution) category: Endpoint 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. configuration: @@ -236,7 +236,11 @@ script: # Allow sdk parameter for unit testing. # Otherwise, lazily load the SDK so that the TEST Command can effectively check auth. self._sdk = sdk - self._sdk_factory = lambda: py42.sdk.from_local_account(base_url, auth[0], auth[1]) if not self._sdk else None + self._sdk_factory = ( + lambda: py42.sdk.from_local_account(base_url, auth[0], auth[1]) + if not self._sdk + else None + ) py42.settings.set_user_agent_suffix("Cortex XSOAR") def _get_sdk(self): @@ -582,61 +586,62 @@ script: def alert_get_command(client, args): code42_securityalert_context = [] - try: - alert = client.get_alert_details(args["id"]) - if not alert: - return "No results found", {}, {} - - code42_context = map_to_code42_alert_context(alert) - code42_securityalert_context.append(code42_context) - readable_outputs = tableToMarkdown( - "Code42 Security Alert Results", - code42_securityalert_context, - headers=SECURITY_ALERT_HEADERS, - ) + alert = client.get_alert_details(args["id"]) + if not alert: return CommandResults( - outputs_prefix="Code42.SecurityAlert", + readable_output="No results found", + outputs={}, outputs_key_field="ID", - outputs=code42_securityalert_context, - readable_output=readable_outputs, - raw_response=alert, + outputs_prefix="Code42.SecurityAlert", + raw_response={}, ) - except Exception as e: - return_error(create_command_error_message(demisto.command(), e)) + + code42_context = map_to_code42_alert_context(alert) + code42_securityalert_context.append(code42_context) + readable_outputs = tableToMarkdown( + "Code42 Security Alert Results", + code42_securityalert_context, + headers=SECURITY_ALERT_HEADERS, + ) + return CommandResults( + outputs_prefix="Code42.SecurityAlert", + outputs_key_field="ID", + outputs=code42_securityalert_context, + readable_output=readable_outputs, + raw_response=alert, + ) @logger def alert_resolve_command(client, args): code42_securityalert_context = [] - - try: - alert_id = client.resolve_alert(args["id"]) - - if not alert_id: - return "No results found", {}, {} - - # Retrieve new alert details - alert_details = client.get_alert_details(alert_id) - if not alert_details: - return "Error retrieving updated alert", {}, {} - - code42_context = map_to_code42_alert_context(alert_details) - code42_securityalert_context.append(code42_context) - readable_outputs = tableToMarkdown( - "Code42 Security Alert Resolved", - code42_securityalert_context, - headers=SECURITY_ALERT_HEADERS, - ) + alert_id = client.resolve_alert(args["id"]) + if not alert_id: return CommandResults( - outputs_prefix="Code42.SecurityAlert", + readable_output="No results found", + outputs={}, outputs_key_field="ID", - outputs=code42_securityalert_context, - readable_output=readable_outputs, - raw_response=alert_details, + outputs_prefix="Code42.SecurityAlert", + raw_response={}, ) - except Exception as e: - return_error(create_command_error_message(demisto.command(), e)) + + # Retrieve new alert details + alert_details = client.get_alert_details(alert_id) + code42_context = map_to_code42_alert_context(alert_details) + code42_securityalert_context.append(code42_context) + readable_outputs = tableToMarkdown( + "Code42 Security Alert Resolved", + code42_securityalert_context, + headers=SECURITY_ALERT_HEADERS, + ) + return CommandResults( + outputs_prefix="Code42.SecurityAlert", + outputs_key_field="ID", + outputs=code42_securityalert_context, + readable_output=readable_outputs, + raw_response=alert_details, + ) @logger @@ -645,75 +650,64 @@ script: departing_date = args.get("departuredate") username = args["username"] note = args.get("note") - try: - user_id = client.add_user_to_departing_employee(username, departing_date, note) - # CaseID included but it deprecated. - de_context = { - "CaseID": user_id, - "UserID": user_id, - "Username": username, - "DepartureDate": departing_date, - "Note": note, - } - readable_outputs = tableToMarkdown("Code42 Departing Employee List User Added", de_context) - return CommandResults( - outputs_prefix="Code42.DepartingEmployee", - outputs_key_field="UserID", - outputs=de_context, - readable_output=readable_outputs, - raw_response=user_id, - ) - except Exception as e: - return_error(create_command_error_message(demisto.command(), e)) + user_id = client.add_user_to_departing_employee(username, departing_date, note) + # CaseID included but it deprecated. + de_context = { + "CaseID": user_id, + "UserID": user_id, + "Username": username, + "DepartureDate": departing_date, + "Note": note, + } + readable_outputs = tableToMarkdown("Code42 Departing Employee List User Added", de_context) + return CommandResults( + outputs_prefix="Code42.DepartingEmployee", + outputs_key_field="UserID", + outputs=de_context, + readable_output=readable_outputs, + raw_response=user_id, + ) @logger def departingemployee_remove_command(client, args): username = args["username"] - try: - user_id = client.remove_user_from_departing_employee(username) - # CaseID included but is deprecated. - de_context = {"CaseID": user_id, "UserID": user_id, "Username": username} - readable_outputs = tableToMarkdown( - "Code42 Departing Employee List User Removed", de_context - ) - return CommandResults( - outputs_prefix="Code42.DepartingEmployee", - outputs_key_field="UserID", - outputs=de_context, - readable_output=readable_outputs, - raw_response=user_id, - ) - except Exception as e: - return_error(create_command_error_message(demisto.command(), e)) + user_id = client.remove_user_from_departing_employee(username) + # CaseID included but is deprecated. + de_context = {"CaseID": user_id, "UserID": user_id, "Username": username} + readable_outputs = tableToMarkdown("Code42 Departing Employee List User Removed", de_context) + return CommandResults( + outputs_prefix="Code42.DepartingEmployee", + outputs_key_field="UserID", + outputs=de_context, + readable_output=readable_outputs, + raw_response=user_id, + ) @logger def departingemployee_get_all_command(client, args): results = args.get("results") or 50 - try: - employees = client.get_all_departing_employees(results) - employees_context = [ - { - "UserID": e["userId"], - "Username": e["userName"], - "DepartureDate": e.get("departureDate"), - "Note": e["notes"], - } - for e in employees - ] - readable_outputs = tableToMarkdown("All Departing Employees", employees_context) - return CommandResults( - outputs_prefix="Code42.DepartingEmployee", - outputs_key_field="UserID", - outputs=employees_context, - readable_output=readable_outputs, - raw_response=employees, - ) - except Exception as e: - return_error(create_command_error_message(demisto.command(), e)) + employees = client.get_all_departing_employees(results) + employees_context = [ + { + "UserID": e["userId"], + "Username": e["userName"], + "DepartureDate": e.get("departureDate"), + "Note": e["notes"], + } + for e in employees + ] + readable_outputs = tableToMarkdown("All Departing Employees", employees_context) + return CommandResults( + outputs_prefix="Code42.DepartingEmployee", + outputs_key_field="UserID", + outputs=employees_context, + readable_output=readable_outputs, + raw_response=employees, + ) @logger @@ -721,40 +715,32 @@ script: def highriskemployee_add_command(client, args): username = args["username"] note = args.get("note") - try: - user_id = client.add_user_to_high_risk_employee(username, note) - hr_context = {"UserID": user_id, "Username": username} - readable_outputs = tableToMarkdown("Code42 High Risk Employee List User Added", hr_context) - return CommandResults( - outputs_prefix="Code42.HighRiskEmployee", - outputs_key_field="UserID", - outputs=hr_context, - readable_output=readable_outputs, - raw_response=user_id, - ) - except Exception as e: - return_error(create_command_error_message(demisto.command(), e)) + user_id = client.add_user_to_high_risk_employee(username, note) + hr_context = {"UserID": user_id, "Username": username} + readable_outputs = tableToMarkdown("Code42 High Risk Employee List User Added", hr_context) + return CommandResults( + outputs_prefix="Code42.HighRiskEmployee", + outputs_key_field="UserID", + outputs=hr_context, + readable_output=readable_outputs, + raw_response=user_id, + ) @logger def highriskemployee_remove_command(client, args): username = args["username"] - try: - user_id = client.remove_user_from_high_risk_employee(username) - hr_context = {"UserID": user_id, "Username": username} - readable_outputs = tableToMarkdown( - "Code42 High Risk Employee List User Removed", hr_context - ) - return CommandResults( - outputs_prefix="Code42.HighRiskEmployee", - outputs_key_field="UserID", - outputs=hr_context, - readable_output=readable_outputs, - raw_response=user_id, - ) - except Exception as e: - return_error(create_command_error_message(demisto.command(), e)) + user_id = client.remove_user_from_high_risk_employee(username) + hr_context = {"UserID": user_id, "Username": username} + readable_outputs = tableToMarkdown("Code42 High Risk Employee List User Removed", hr_context) + return CommandResults( + outputs_prefix="Code42.HighRiskEmployee", + outputs_key_field="UserID", + outputs=hr_context, + readable_output=readable_outputs, + raw_response=user_id, + ) @logger @@ -762,23 +748,19 @@ script: def highriskemployee_get_all_command(client, args): tags = args.get("risktags") results = args.get("results") or 50 - try: - employees = client.get_all_high_risk_employees(tags, results) - employees_context = [ - {"UserID": e.get("userId"), "Username": e.get("userName"), "Note": e.get("notes")} - for e in employees - ] - readable_outputs = tableToMarkdown("Retrieved All High Risk Employees", employees_context) - return CommandResults( - outputs_prefix="Code42.HighRiskEmployee", - outputs_key_field="UserID", - outputs=employees_context, - readable_output=readable_outputs, - raw_response=employees, - ) - - except Exception as e: - return_error(create_command_error_message(demisto.command(), e)) + employees = client.get_all_high_risk_employees(tags, results) + employees_context = [ + {"UserID": e.get("userId"), "Username": e.get("userName"), "Note": e.get("notes")} + for e in employees + ] + readable_outputs = tableToMarkdown("Retrieved All High Risk Employees", employees_context) + return CommandResults( + outputs_prefix="Code42.HighRiskEmployee", + outputs_key_field="UserID", + outputs=employees_context, + readable_output=readable_outputs, + raw_response=employees, + ) @logger @@ -786,19 +768,16 @@ script: def highriskemployee_add_risk_tags_command(client, args): username = args.get("username") tags = args.get("risktags") - try: - user_id = client.add_user_risk_tags(username, tags) - rt_context = {"UserID": user_id, "Username": username, "RiskTags": tags} - readable_outputs = tableToMarkdown("Code42 Risk Tags Added", rt_context) - return CommandResults( - outputs_prefix="Code42.HighRiskEmployee", - outputs_key_field="UserID", - outputs=rt_context, - readable_output=readable_outputs, - raw_response=user_id, - ) - except Exception as e: - return_error(create_command_error_message(demisto.command(), e)) + user_id = client.add_user_risk_tags(username, tags) + rt_context = {"UserID": user_id, "Username": username, "RiskTags": tags} + readable_outputs = tableToMarkdown("Code42 Risk Tags Added", rt_context) + return CommandResults( + outputs_prefix="Code42.HighRiskEmployee", + outputs_key_field="UserID", + outputs=rt_context, + readable_output=readable_outputs, + raw_response=user_id, + ) @logger @@ -806,19 +785,16 @@ script: def highriskemployee_remove_risk_tags_command(client, args): username = args.get("username") tags = args.get("risktags") - try: - user_id = client.remove_user_risk_tags(username, tags) - rt_context = {"UserID": user_id, "Username": username, "RiskTags": tags} - readable_outputs = tableToMarkdown("Code42 Risk Tags Removed", rt_context) - return CommandResults( - outputs_prefix="Code42.HighRiskEmployee", - outputs_key_field="UserID", - outputs=rt_context, - readable_output=readable_outputs, - raw_response=user_id, - ) - except Exception as e: - return_error(create_command_error_message(demisto.command(), e)) + user_id = client.remove_user_risk_tags(username, tags) + rt_context = {"UserID": user_id, "Username": username, "RiskTags": tags} + readable_outputs = tableToMarkdown("Code42 Risk Tags Removed", rt_context) + return CommandResults( + outputs_prefix="Code42.HighRiskEmployee", + outputs_key_field="UserID", + outputs=rt_context, + readable_output=readable_outputs, + raw_response=user_id, + ) @logger @@ -827,6 +803,7 @@ script: code42_security_data_context = [] _json = args.get("json") file_context = [] + # If JSON payload is passed as an argument, ignore all other args and search by JSON payload if _json is not None: file_events = client.search_file_events(_json) @@ -850,17 +827,21 @@ script: outputs_key_field="EventID", outputs=code42_security_data_context, readable_output=readable_outputs, - raw_response=file_events + raw_response=file_events, ) file_results = CommandResults( - outputs_prefix="File", - outputs_key_field=None, - outputs=file_context, + outputs_prefix="File", outputs_key_field=None, outputs=file_context ) return code42_results, file_results else: - return "No results found", {}, {} + return CommandResults( + readable_output="No results found", + outputs={}, + outputs_key_field="EventID", + outputs_prefix="Code42.SecurityData", + raw_response={}, + ) """Fetching""" @@ -916,7 +897,7 @@ script: incidents = [self._create_incident_from_alert(a) for a in alerts] save_time = datetime.utcnow().timestamp() next_run = {"last_fetch": save_time} - return next_run, incidents[: self._fetch_limit], incidents[self._fetch_limit:] + return next_run, incidents[: self._fetch_limit], incidents[self._fetch_limit :] def _fetch_remaining_incidents_from_last_run(self): if self._integration_context: @@ -925,8 +906,8 @@ script: if remaining_incidents: return ( self._last_run, - remaining_incidents[:self._fetch_limit], - remaining_incidents[self._fetch_limit:], + remaining_incidents[: self._fetch_limit], + remaining_incidents[self._fetch_limit :], ) def _get_start_query_time(self): @@ -1007,70 +988,84 @@ script: ) - def main(): - """ - PARSE AND VALIDATE INTEGRATION PARAMS - """ + def get_command_map(): + return { + "code42-alert-get": alert_get_command, + "code42-alert-resolve": alert_resolve_command, + "code42-securitydata-search": securitydata_search_command, + "code42-departingemployee-add": departingemployee_add_command, + "code42-departingemployee-remove": departingemployee_remove_command, + "code42-departingemployee-get-all": departingemployee_get_all_command, + "code42-highriskemployee-add": highriskemployee_add_command, + "code42-highriskemployee-remove": highriskemployee_remove_command, + "code42-highriskemployee-get-all": highriskemployee_get_all_command, + "code42-highriskemployee-add-risk-tags": highriskemployee_add_risk_tags_command, + "code42-highriskemployee-remove-risk-tags": highriskemployee_remove_risk_tags_command, + } + + + def handle_test_command(client): + # This is the call made when pressing the integration Test button. + result = test_module(client) + demisto.results(result) + + + def handle_fetch_command(client): + integration_context = demisto.getIntegrationContext() + # Set and define the fetch incidents command to run after activated via integration settings. + next_run, incidents, remaining_incidents = fetch_incidents( + client=client, + last_run=demisto.getLastRun(), + first_fetch_time=demisto.params().get("fetch_time"), + event_severity_filter=demisto.params().get("alert_severity"), + fetch_limit=int(demisto.params().get("fetch_limit")), + include_files=demisto.params().get("include_files"), + integration_context=integration_context, + ) + demisto.setLastRun(next_run) + demisto.incidents(incidents) + # Store remaining incidents in integration context + integration_context["remaining_incidents"] = remaining_incidents + demisto.setIntegrationContext(integration_context) + + + def try_run_command(command): + try: + results = command() + if not isinstance(results, tuple) and not isinstance(results, list): + results = [results] + for result in results: + return_results(result) + except Exception as e: + return_error(create_command_error_message(demisto.command(), e)) + + + 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") - # Remove trailing slash to prevent wrong URL path to service verify_certificate = not demisto.params().get("insecure", False) proxy = demisto.params().get("proxy", False) LOG("Command being called is {0}.".format(demisto.command())) - try: - client = Code42Client( - base_url=base_url, - sdk=None, - auth=(username, password), - verify=verify_certificate, - proxy=proxy, - ) - commands = { - "code42-alert-get": alert_get_command, - "code42-alert-resolve": alert_resolve_command, - "code42-securitydata-search": securitydata_search_command, - "code42-departingemployee-add": departingemployee_add_command, - "code42-departingemployee-remove": departingemployee_remove_command, - "code42-departingemployee-get-all": departingemployee_get_all_command, - "code42-highriskemployee-add": highriskemployee_add_command, - "code42-highriskemployee-remove": highriskemployee_remove_command, - "code42-highriskemployee-get-all": highriskemployee_get_all_command, - "code42-highriskemployee-add-risk-tags": highriskemployee_add_risk_tags_command, - "code42-highriskemployee-remove-risk-tags": highriskemployee_remove_risk_tags_command, - } - command = demisto.command() - if command == "test-module": - # This is the call made when pressing the integration Test button. - result = test_module(client) - demisto.results(result) - elif command == "fetch-incidents": - integration_context = demisto.getIntegrationContext() - # Set and define the fetch incidents command to run after activated via integration settings. - next_run, incidents, remaining_incidents = fetch_incidents( - client=client, - last_run=demisto.getLastRun(), - first_fetch_time=demisto.params().get("fetch_time"), - event_severity_filter=demisto.params().get("alert_severity"), - fetch_limit=int(demisto.params().get("fetch_limit")), - include_files=demisto.params().get("include_files"), - integration_context=integration_context, - ) - demisto.setLastRun(next_run) - demisto.incidents(incidents) - # Store remaining incidents in integration context - integration_context["remaining_incidents"] = remaining_incidents - demisto.setIntegrationContext(integration_context) - elif command in commands: - results = commands[command](client, demisto.args()) - if not isinstance(results, tuple) and not isinstance(results, list): - results = [results] - for result in results: - return_results(result) - - # Log exceptions - except Exception as e: - return_error("Failed to execute {0} command. Error: {1}".format(demisto.command(), str(e))) + client = Code42Client( + base_url=base_url, + sdk=None, + auth=(username, password), + verify=verify_certificate, + proxy=proxy, + ) + commands = get_command_map() + command = demisto.command() + if command == "test-module": + handle_test_command(client) + elif command == "fetch-incidents": + handle_fetch_command(client) + elif command in commands: + try_run_command(lambda: commands[command](client, demisto.args())) + + + def main(): + run_code42_integration() if __name__ in ("__main__", "__builtin__", "builtins"): @@ -1282,6 +1277,7 @@ script: outputs: - contextPath: Code42.DepartingEmployee.CaseID description: Internal Code42 Case ID for the Departing Employee. Deprecated. Use Code42.DepartingEmployee.UserID. + type: string - contextPath: Code42.DepartingEmployee.UserID description: Internal Code42 User ID for the Departing Employee. type: string @@ -1302,6 +1298,7 @@ script: outputs: - contextPath: Code42.DepartingEmployee.CaseID description: Internal Code42 Case ID for the Departing Employee. Deprecated. Use Code42.DepartingEmployee.UserID. + type: string - contextPath: Code42.DepartingEmployee.UserID description: Internal Code42 User ID for the Departing Employee. type: string @@ -1416,6 +1413,16 @@ script: subtype: python3 image: data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAHgAAAAyCAYAAACXpx/YAAAHjElEQVR4nO2aeYxV1R3HP/e+h2wyoCxalhkQpFJpUcQ2LijRLsE9ikutUjCh44poWzUqalyitmqiQdM0rfsSlbjVuFsXlChVaaWKSnBBBnDsMAwyFpiZd815871wuNztDTP4B+eT3Hn3nv2e3zm/3+/87uBwOBwOh8PhcDgcDofD4XA4HA6Hw7EteKXjqAMGA9908kz2AVYDY4B6O8ML4GN/ANOKU/AJ6EGrE2IXUZQgsH47E9Omb7fn6fdef19Wen3YM2igtCnV0dn4XTyjga4tUhb7g3i6MIYhwVon3C6mqwW8BaEo7/b3pZEe9Kbl+3rvHYbidn3RAD7wB/Fs4YdUB020bbl7+wIHAbsBG4CFwOIKWt8f2AsoAMuA+cD6jDo1wE5Am549XQ1AY44+ewDDgNJWmmoz3YAmYFVGWz3VVpvaMu+xUe+S1LbZoPsBo3T/KfC2xlOmUgG3qrNuFdazbO941tCT3fmGtnYFYgZ3E3CMVSzkE2Am8HxK05cBFwD9I+lmcu4GLgbWxNQzE/gmMCSh3c+AF4AbgM8TyvwUeC1lbCHPAEdmlLkROC+SZhZFNWyl6nYBrgJO173NWuDPwLVUoKL/BkzQ7hoEjANuzVm3nQAW+bvxTGE0NUFjKNxTgCXAsRKuWb11lkc/GngOuDqmxSpgkV4kFG6DtVPMzvwdsBzYp6KxtjMCqJWgoxPf2RxUQR8n63QyM0a44bxcA7xIDgF/K8HOAN5Vw2Y3vA/Mkkr8X9aIwm35lD+GRnqFx6JfAg9ZxWYDuwJDNcjJUk9h3mmRZs3uGqv7f2gxDAB+oIU4R3m9tcsGpwxxLrCzjnSmnfGapGbl3wZcmPGaf5CaHRu5jAo9K6PuLRn5NuNi0pq0c21+DlyUJeBfSbBJfAxMSrERm2jxfBb4wxgYNIee8+1W9gHaifYgn5NdNbv0PuCfVt5FwM90f7PU+xIrv147Yrqeq1QuidUS5kdqx9j/K4CR0ihhP3untLFMNv+DyPWetVDj+L1UPaq3KGMqLzWWTvePaTH1kw9zQaTsMWkCNmr5jYzOwkHdmJDnl68Alnr9qaOKncv+EyfJ9houB95KqF+vFTsVWGGlT9XvJ9o5SRgb/KTyjGobnlAu6az2FTDNep6R0ldHqA5tpZgtJymL2TJrJ2j+Q+ZE5mlAmpP1YAUD/gvQXd5vGJYy6mpdaE+Xe1Ws83aiT1AW8C9UpmStxiSi2mGstZOeyDG2RywbPzHFYUriJe3C8aqfRLXeeWQk33jaX2qxRLle+UhjPQ6cmWNMyxK0woSIKVqVJOBWqd+8fJFkozz9ubMwgSIlCgRGYuFOWqyXr4Q9rLIf5qj3X+s+aQfnaWO8hNgj4fh1k644jIa7JJJ+NHCq9TxLvz07OEbjfzwcSZubpqIz7WoltFDA29xk2G9HgtAF6z5PffuIUUgpl0bYj99JwSE/4hPUWhuqI/P+Y9WvttJeNio7aQcX5U2uzNnBUA1yozUZZqU3B3C7F9A8ve0dZhWPKjtYHkF4lBku56Cpgpeps+7z7EhbZeZ9nyh76rleJ4s4ZsnbHhXJ6x4T5LjCahPVuUyevD3eKpkwc+S5M6HfM4C/R9IW6BSSGuj4dc5DPBLu5THprfKAm4cGa8uhSRO9KhLMl3rqK/uYZYdtFsqRGCwv/7qM8pOt+39V0E/IOMv2vpNSboV235KUMiHRc/kfE8r1UqxgYIKAH5ScbO4Azgmf09RNreW+pzFa0aI4SuXLg5pgDQOCZr5tD4I9IgcMeZFVCfV9BVROsdJa5IygiZ+aUNdgjlln636enKVKucMq/0AH6ncFP5KTFRXuGZZwy2HXLHvyfMbZz8RyX8kTuuwbrGefYCVfe73NuelrqSkUf/1PzKo2bT6qiM1D1pkWhelW6/4e4PyYLs3OfcV6np01Rgtfnr6xawcq+S55uklU8lnsbcUXnrSux7XwG6xyG7QoX7XSJupoNCzSpqk3RWOuU0zhffPBf22Ob8HGtvxVR4ySjPlvFXBIc1w2quxX5iP/y4WRzCgez/CgMXS45tjqRBGyfysceqjlUb6mgIrNgYolh6zSuX2DgiC2LazV+G0K8v6HKMhRr7RA/dverIl0nRjzfodYZqxBvkR0sQ/TIpwWUz+O1y2TEBeL/g1wf862mvJ+bJipa70E3Ctnvc14cHjbUg7xP+NNv6Ys5BLeuVrNtyka8xNdNn9KMAHzdWS6X8LeXSvY5nOp8HkZo+ut2HMUcwy7UgJOeKtN9I/54BEyKM8UxbS5zeTdwR1l0w5G/6rzUmEUtcXjqA7WtIe42jEq8QjgYE3SBqmauTk9X3NMOEq7paD+zDEhzUn0FOI08e//W+lFecrv5QiKDNRXoo32J7oIVQqBvp5zDg/Xwm1R3P+pSNs1Mh/NGUcq472v274Cpn1I07tNYZ5fw4j2XdxFXTvY3v/RES6300sLy+Ju3b7d75B09Qx7W9kUDw5rW8qk0qfUeVV2dMvRBRTl+fVJ+K+HbaGf2tzCNgWS+LS2d1ngDy2HMIuJ5svhcDgcDofD4XA4HA6Hw+FwOBwOh8Px/QF8B+pZzTnuPtCMAAAAAElFTkSuQmCC detaileddescription: > + ### Partner Contributed Integration + + #### Integration Author: Code42 + + Support and maintenance for this integration are provided by the author. Please use the following contact details: + + - **URL**: [https://support.code42.com/Administrator/Cloud/Monitoring_and_managing](https://support.code42.com/Administrator/Cloud/Monitoring_and_managing) + + *** + ## Code42 From 6f684cb83be47e40e23853e70fd2a59b27d8ca93 Mon Sep 17 00:00:00 2001 From: Juliya Smith Date: Tue, 30 Jun 2020 13:04:25 +0000 Subject: [PATCH 15/17] Format --- Packs/Code42/Integrations/Code42/Code42.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Packs/Code42/Integrations/Code42/Code42.py b/Packs/Code42/Integrations/Code42/Code42.py index 461aee809534..61f957cae6fd 100644 --- a/Packs/Code42/Integrations/Code42/Code42.py +++ b/Packs/Code42/Integrations/Code42/Code42.py @@ -808,7 +808,7 @@ def fetch(self): incidents = [self._create_incident_from_alert(a) for a in alerts] save_time = datetime.utcnow().timestamp() next_run = {"last_fetch": save_time} - return next_run, incidents[: self._fetch_limit], incidents[self._fetch_limit :] + return next_run, incidents[: self._fetch_limit], incidents[self._fetch_limit:] def _fetch_remaining_incidents_from_last_run(self): if self._integration_context: @@ -818,7 +818,7 @@ def _fetch_remaining_incidents_from_last_run(self): return ( self._last_run, remaining_incidents[: self._fetch_limit], - remaining_incidents[self._fetch_limit :], + remaining_incidents[self._fetch_limit:], ) def _get_start_query_time(self): From 96206ceb700c6e2049a5f257124949185ca51976 Mon Sep 17 00:00:00 2001 From: Juliya Smith Date: Tue, 30 Jun 2020 13:04:40 +0000 Subject: [PATCH 16/17] Gen yml --- Packs/Code42/Integrations/Code42/integration-Code42.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Packs/Code42/Integrations/Code42/integration-Code42.yml b/Packs/Code42/Integrations/Code42/integration-Code42.yml index 50f631f4f34b..e8a6c0e321db 100644 --- a/Packs/Code42/Integrations/Code42/integration-Code42.yml +++ b/Packs/Code42/Integrations/Code42/integration-Code42.yml @@ -651,7 +651,7 @@ script: username = args["username"] note = args.get("note") user_id = client.add_user_to_departing_employee(username, departing_date, note) - # CaseID included but it deprecated. + # CaseID included but is deprecated. de_context = { "CaseID": user_id, "UserID": user_id, @@ -897,7 +897,7 @@ script: incidents = [self._create_incident_from_alert(a) for a in alerts] save_time = datetime.utcnow().timestamp() next_run = {"last_fetch": save_time} - return next_run, incidents[: self._fetch_limit], incidents[self._fetch_limit :] + return next_run, incidents[: self._fetch_limit], incidents[self._fetch_limit:] def _fetch_remaining_incidents_from_last_run(self): if self._integration_context: @@ -907,7 +907,7 @@ script: return ( self._last_run, remaining_incidents[: self._fetch_limit], - remaining_incidents[self._fetch_limit :], + remaining_incidents[self._fetch_limit:], ) def _get_start_query_time(self): From d11b75cbb7b1a0f9318df37041e9afa353b7acda Mon Sep 17 00:00:00 2001 From: Juliya Smith Date: Tue, 30 Jun 2020 13:22:23 +0000 Subject: [PATCH 17/17] Handle no results for detection list get alls --- Packs/Code42/Integrations/Code42/Code42.py | 29 +++++++++++++++---- .../Code42/integration-Code42.yml | 29 +++++++++++++++---- 2 files changed, 46 insertions(+), 12 deletions(-) diff --git a/Packs/Code42/Integrations/Code42/Code42.py b/Packs/Code42/Integrations/Code42/Code42.py index 61f957cae6fd..a8b7c2d1300b 100644 --- a/Packs/Code42/Integrations/Code42/Code42.py +++ b/Packs/Code42/Integrations/Code42/Code42.py @@ -512,7 +512,7 @@ def alert_get_command(client, args): if not alert: return CommandResults( readable_output="No results found", - outputs={}, + outputs={"Results": []}, outputs_key_field="ID", outputs_prefix="Code42.SecurityAlert", raw_response={}, @@ -541,7 +541,7 @@ def alert_resolve_command(client, args): if not alert_id: return CommandResults( readable_output="No results found", - outputs={}, + outputs={"Results": []}, outputs_key_field="ID", outputs_prefix="Code42.SecurityAlert", raw_response={}, @@ -609,12 +609,21 @@ def departingemployee_remove_command(client, args): def departingemployee_get_all_command(client, args): results = args.get("results") or 50 employees = client.get_all_departing_employees(results) + if not employees: + return CommandResults( + readable_output="No results found", + outputs_prefix="Code42.DepartingEmployee", + outputs_key_field="UserID", + outputs={"Results": []}, + raw_response={} + ) + employees_context = [ { - "UserID": e["userId"], - "Username": e["userName"], + "UserID": e.get("userId"), + "Username": e.get("userName"), "DepartureDate": e.get("departureDate"), - "Note": e["notes"], + "Note": e.get("notes"), } for e in employees ] @@ -664,6 +673,14 @@ def highriskemployee_get_all_command(client, args): tags = args.get("risktags") results = args.get("results") or 50 employees = client.get_all_high_risk_employees(tags, results) + if not employees: + return CommandResults( + readable_output="No results found", + outputs_prefix="Code42.HighRiskEmployee", + outputs_key_field="UserID", + outputs={"Results": []}, + raw_response={} + ) employees_context = [ {"UserID": e.get("userId"), "Username": e.get("userName"), "Note": e.get("notes")} for e in employees @@ -749,7 +766,7 @@ def securitydata_search_command(client, args): else: return CommandResults( readable_output="No results found", - outputs={}, + outputs={"Results": []}, outputs_key_field="EventID", outputs_prefix="Code42.SecurityData", raw_response={}, diff --git a/Packs/Code42/Integrations/Code42/integration-Code42.yml b/Packs/Code42/Integrations/Code42/integration-Code42.yml index e8a6c0e321db..702ad3f5db7b 100644 --- a/Packs/Code42/Integrations/Code42/integration-Code42.yml +++ b/Packs/Code42/Integrations/Code42/integration-Code42.yml @@ -590,7 +590,7 @@ script: if not alert: return CommandResults( readable_output="No results found", - outputs={}, + outputs={"Results": []}, outputs_key_field="ID", outputs_prefix="Code42.SecurityAlert", raw_response={}, @@ -620,7 +620,7 @@ script: if not alert_id: return CommandResults( readable_output="No results found", - outputs={}, + outputs={"Results": []}, outputs_key_field="ID", outputs_prefix="Code42.SecurityAlert", raw_response={}, @@ -691,12 +691,21 @@ script: def departingemployee_get_all_command(client, args): results = args.get("results") or 50 employees = client.get_all_departing_employees(results) + if not employees: + return CommandResults( + readable_output="No results found", + outputs_prefix="Code42.DepartingEmployee", + outputs_key_field="UserID", + outputs={"Results": []}, + raw_response={} + ) + employees_context = [ { - "UserID": e["userId"], - "Username": e["userName"], + "UserID": e.get("userId"), + "Username": e.get("userName"), "DepartureDate": e.get("departureDate"), - "Note": e["notes"], + "Note": e.get("notes"), } for e in employees ] @@ -749,6 +758,14 @@ script: tags = args.get("risktags") results = args.get("results") or 50 employees = client.get_all_high_risk_employees(tags, results) + if not employees: + return CommandResults( + readable_output="No results found", + outputs_prefix="Code42.HighRiskEmployee", + outputs_key_field="UserID", + outputs={"Results": []}, + raw_response={} + ) employees_context = [ {"UserID": e.get("userId"), "Username": e.get("userName"), "Note": e.get("notes")} for e in employees @@ -837,7 +854,7 @@ script: else: return CommandResults( readable_output="No results found", - outputs={}, + outputs={"Results": []}, outputs_key_field="EventID", outputs_prefix="Code42.SecurityData", raw_response={},