From 00fa6f5313203c69bb1e4605fb7f0a7c11afe96e Mon Sep 17 00:00:00 2001 From: Juliya Smith Date: Tue, 16 Jun 2020 17:02:37 +0000 Subject: [PATCH 01/17] Rename search json to search file events --- Packs/Code42/Integrations/Code42/Code42.py | 36 +++++++++++----------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/Packs/Code42/Integrations/Code42/Code42.py b/Packs/Code42/Integrations/Code42/Code42.py index aa0260bf4a82..1ac188885746 100644 --- a/Packs/Code42/Integrations/Code42/Code42.py +++ b/Packs/Code42/Integrations/Code42/Code42.py @@ -155,6 +155,14 @@ def add_user_to_departing_employee(self, username, departure_date=None, note=Non return None return user_id + def remove_user_from_departing_employee(self, username): + try: + user_id = self.get_user_id(username) + self._sdk.detectionlists.departing_employee.remove(user_id) + except Exception: + return None + return user_id + def fetch_alerts(self, start_time, event_severity_filter): try: query = _create_alert_query(event_severity_filter, start_time) @@ -170,27 +178,19 @@ def get_alert_details(self, alert_id): return None return res["alerts"][0] - def get_current_user(self): - try: - res = self._sdk.users.get_current() - except Exception: - return None - return res - - def remove_user_from_departing_employee(self, username): + def resolve_alert(self, id): try: - user_id = self.get_user_id(username) - self._sdk.detectionlists.departing_employee.remove(user_id) + self._sdk.alerts.resolve(id) except Exception: return None - return user_id + return id - def resolve_alert(self, id): + def get_current_user(self): try: - self._sdk.alerts.resolve(id) + res = self._sdk.users.get_current() except Exception: return None - return id + return res def get_user_id(self, username): try: @@ -199,7 +199,7 @@ def get_user_id(self, username): return None return res["users"][0]["userUid"] - def search_json(self, payload): + def search_file_events(self, payload): try: res = self._sdk.securitydata.search_file_events(payload) except Exception: @@ -587,7 +587,7 @@ def _relate_files_to_alert(self, alert_details): def _get_file_events_from_alert_details(self, observation, alert_details): security_data_query = map_observation_to_security_query(observation, alert_details["actor"]) - return self._client.search_json(security_data_query) + return self._client.search_file_events(security_data_query) def fetch_incidents( @@ -618,11 +618,11 @@ def securitydata_search_command(client, args): 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_json(_json) + file_events = client.search_file_events(_json) else: # Build payload payload = build_query_payload(args) - file_events = client.search_json(payload) + file_events = client.search_file_events(payload) if file_events: for file_event in file_events: code42_context_event = map_to_code42_event_context(file_event) From 7d2e3a3509b798c30f0b10d7422528eec9de29ef Mon Sep 17 00:00:00 2001 From: Juliya Smith Date: Tue, 16 Jun 2020 17:23:37 +0000 Subject: [PATCH 02/17] Add two hr commands --- Packs/Code42/Integrations/Code42/Code42.py | 56 ++++++++++++++++++- .../Code42/Integrations/Code42/Code42_test.py | 27 +++++++++ 2 files changed, 81 insertions(+), 2 deletions(-) diff --git a/Packs/Code42/Integrations/Code42/Code42.py b/Packs/Code42/Integrations/Code42/Code42.py index 1ac188885746..3efa4c2a8d8d 100644 --- a/Packs/Code42/Integrations/Code42/Code42.py +++ b/Packs/Code42/Integrations/Code42/Code42.py @@ -163,6 +163,26 @@ def remove_user_from_departing_employee(self, username): return None return user_id + def add_user_to_high_risk_employee(self, username, risk_tags=None, note=None): + try: + user_id = self.get_user_id(username) + self._sdk.detectionlists.high_risk_employee.add(user_id) + if risk_tags: + self._sdk.detectionlists.add_user_risk_tags(user_id, risk_tags) + if note: + self._sdk.detectionlists.update_user_notes(user_id, note) + except Exception: + return None + return user_id + + def remove_user_from_high_risk_employee(self, username): + try: + user_id = self.get_user_id(username) + self._sdk.detectionlists.high_risk_employee.remove(user_id) + except Exception: + return None + return user_id + def fetch_alerts(self, start_time, event_severity_filter): try: query = _create_alert_query(event_severity_filter, start_time) @@ -467,7 +487,7 @@ def departingemployee_add_command(client, args): note = args.get("note") user_id = client.add_user_to_departing_employee(username, departing_date, note) if not user_id: - return_error(message="Could not add user to Departing Employee List") + return_error(message="Could not add user to the Departing Employee List") de_context = { "UserID": user_id, @@ -490,7 +510,39 @@ def departingemployee_remove_command(client, args): ) return readable_outputs, {"Code42.DepartingEmployee": de_context}, user_id else: - return_error(message="Could not remove user from Departing Employee List") + return_error(message="Could not remove user from the Departing Employee List") + + +@logger +def highriskemployee_add_command(client, args): + tags = args.get("risktags") + username = args["username"] + note = args.get("note") + user_id = client.add_user_to_high_risk_employee(username, tags, note) + if not user_id: + return_error(message="Could not add user to the High Risk Employee List") + + hr_context = { + "UserID": user_id, + "Username": username, + "RiskTags": tags, + } + readable_outputs = tableToMarkdown(f"Code42 High Risk Employee List User Added", hr_context) + return readable_outputs, {"Code42.HighRiskEmployee": hr_context}, user_id + + +@logger +def highriskemployee_remove_command(client, args): + username = args["username"] + user_id = client.remove_user_from_high_risk_employee(username) + if user_id: + hr_context = {"UserID": user_id, "Username": username} + readable_outputs = tableToMarkdown( + f"Code42 High Risk Employee List User Removed", hr_context + ) + return readable_outputs, {"Code42.HighRiskEmployee": hr_context}, user_id + else: + return_error(message="Could not remove user from the High Risk Employee List") def _create_incident_from_alert_details(details): diff --git a/Packs/Code42/Integrations/Code42/Code42_test.py b/Packs/Code42/Integrations/Code42/Code42_test.py index a02b98e779b4..10fd675158cc 100644 --- a/Packs/Code42/Integrations/Code42/Code42_test.py +++ b/Packs/Code42/Integrations/Code42/Code42_test.py @@ -14,6 +14,8 @@ alert_resolve_command, departingemployee_add_command, departingemployee_remove_command, + highriskemployee_add_command, + highriskemployee_remove_command, fetch_incidents, securitydata_search_command, ) @@ -827,6 +829,31 @@ def test_departing_employee_add_command(code42_sdk_mock): code42_sdk_mock.detectionlists.update_user_notes.assert_called_once_with(expected_user_id, "Dummy note") +def test_high_risk_employee_remove_command(code42_sdk_mock): + client = Code42Client( + sdk=code42_sdk_mock, base_url=MOCK_URL, auth=MOCK_AUTH, verify=False, proxy=None + ) + _, _, res = highriskemployee_remove_command(client, {"username": "user1@example.com"}) + expected = "123412341234123412" # value found in GET_USER_RESPONSE + assert res == expected + code42_sdk_mock.detectionlists.high_risk_employee.remove.assert_called_once_with(expected) + + +def test_high_risk_employee_add_command(code42_sdk_mock): + client = Code42Client( + sdk=code42_sdk_mock, base_url=MOCK_URL, auth=MOCK_AUTH, verify=False, proxy=None + ) + _, _, res = highriskemployee_add_command( + client, + {"username": "user1@example.com", "risktags": "FLIGHT_RISK", "note": "Dummy note"}, + ) + expected_user_id = "123412341234123412" # value found in GET_USER_RESPONSE + assert res == expected_user_id + code42_sdk_mock.detectionlists.high_risk_employee.add.assert_called_once_with(expected_user_id) + code42_sdk_mock.detectionlists.add_user_risk_tags.assert_called_once_with(expected_user_id, "FLIGHT_RISK") + code42_sdk_mock.detectionlists.update_user_notes.assert_called_once_with(expected_user_id, "Dummy note") + + def test_security_data_search_command(code42_sdk_mock): client = Code42Client( sdk=code42_sdk_mock, base_url=MOCK_URL, auth=MOCK_AUTH, verify=False, proxy=None From 34aeff1177e49b87a45c85dacaa08d53412444dd Mon Sep 17 00:00:00 2001 From: Juliya Smith Date: Tue, 16 Jun 2020 21:13:49 +0000 Subject: [PATCH 03/17] Update yml --- Packs/Code42/Integrations/Code42/Code42.py | 138 ++++++++++++++++-- Packs/Code42/Integrations/Code42/Code42.yml | 37 ++++- .../Code42/Integrations/Code42/Code42_test.py | 76 ++++++---- 3 files changed, 210 insertions(+), 41 deletions(-) diff --git a/Packs/Code42/Integrations/Code42/Code42.py b/Packs/Code42/Integrations/Code42/Code42.py index 3efa4c2a8d8d..5cebf755453d 100644 --- a/Packs/Code42/Integrations/Code42/Code42.py +++ b/Packs/Code42/Integrations/Code42/Code42.py @@ -163,12 +163,20 @@ def remove_user_from_departing_employee(self, username): return None return user_id - def add_user_to_high_risk_employee(self, username, risk_tags=None, note=None): + def get_all_departing_employees(self): + try: + res = [] + pages = self._sdk.detectionlists.departing_employee.get_all() + for page in pages: + res.extend(page["items"]) + except Exception: + return None + return res + + def add_user_to_high_risk_employee(self, username, note=None): try: user_id = self.get_user_id(username) self._sdk.detectionlists.high_risk_employee.add(user_id) - if risk_tags: - self._sdk.detectionlists.add_user_risk_tags(user_id, risk_tags) if note: self._sdk.detectionlists.update_user_notes(user_id, note) except Exception: @@ -183,6 +191,49 @@ def remove_user_from_high_risk_employee(self, username): return None return user_id + def add_user_risk_tags(self, username, risk_tags): + try: + user_id = self.get_user_id(username) + self._sdk.detectionlists.add_user_risk_tags(user_id, risk_tags) + except Exception: + return None + return user_id + + def remove_user_risk_tags(self, username, risk_tags): + try: + user_id = self.get_user_id(username) + self._sdk.detectionlists.remove_user_risk_tags(user_id, risk_tags) + except Exception: + return None + return user_id + + def get_all_high_risk_employees(self, risk_tags=None): + if isinstance(risk_tags, str): + risk_tags = [risk_tags] + try: + res = [] + pages = self._sdk.detectionlists.high_risk_employee.get_all() + for page in pages: + res.extend(self._get_all_high_risk_employees_from_page(page, risk_tags)) + except Exception: + return None + return res + + def _get_all_high_risk_employees_from_page(self, page, risk_tags): + res = [] + for employee in page["items"]: + if not risk_tags: + res.append(employee) + continue + + username = employee["userName"] + employee_tags = self._sdk.detectionlists.get_user(username)["riskFactors"] + + # If the employee risk tags contain all the given risk tags + if set(risk_tags) <= set(employee_tags): + res.append(employee) + return res + def fetch_alerts(self, start_time, event_severity_filter): try: query = _create_alert_query(event_severity_filter, start_time) @@ -271,6 +322,7 @@ def to_all_query(self): class AlertQueryFilters(Code42SearchFilters): """Class for simplifying building up an alert search query""" + def to_all_query(self): query = AlertQuery.all(*self._filters) query.page_size = 500 @@ -436,6 +488,9 @@ def _map_obj_to_context(obj, context_mapper): return {v: obj.get(k) for k, v in context_mapper.items() if obj.get(k)} +"""Commands""" + + @logger def alert_get_command(client, args): code42_securityalert_context = [] @@ -513,20 +568,33 @@ def departingemployee_remove_command(client, args): return_error(message="Could not remove user from the Departing Employee List") +@logger +def departingemployee_get_all_command(client, args): + employees = client.get_all_departing_employees() + if employees: + employees_context = [{ + "UserID": e["userId"], + "Username": e["userName"], + "DepartureDate": e.get("departureDate"), + "Note": e["notes"] + } for e in employees] + readable_outputs = tableToMarkdown( + f"All Departing Employees", employees_context, headers=DEPARTING_EMPLOYEE_HEADERS + ) + return readable_outputs, {"Code42.DepartingEmployee(val.UserID && val.UserID == obj.UserID)": employees_context}, employees + else: + return_error(message="Could not get all Departing Employees") + + @logger def highriskemployee_add_command(client, args): - tags = args.get("risktags") username = args["username"] note = args.get("note") - user_id = client.add_user_to_high_risk_employee(username, tags, note) + user_id = client.add_user_to_high_risk_employee(username, note) if not user_id: return_error(message="Could not add user to the High Risk Employee List") - hr_context = { - "UserID": user_id, - "Username": username, - "RiskTags": tags, - } + hr_context = {"UserID": user_id, "Username": username} readable_outputs = tableToMarkdown(f"Code42 High Risk Employee List User Added", hr_context) return readable_outputs, {"Code42.HighRiskEmployee": hr_context}, user_id @@ -545,6 +613,46 @@ def highriskemployee_remove_command(client, args): return_error(message="Could not remove user from the High Risk Employee List") +@logger +def highriskemployee_get_all_command(client, args): + tags = args.get("risktags") + employees = client.get_all_high_risk_employees(tags) + if employees: + hr_context = {"HighRiskEmployees": employees} + readable_outputs = tableToMarkdown( + f"Retrieved All High Risk Employees", hr_context + ) + return readable_outputs, {"Code42.HighRiskEmployee": hr_context}, employees + else: + return_error(message="Could not get all High Risk Employees") + + +@logger +def highriskemployee_add_risk_tags_command(client, args): + username = args["username"] + tags = args["risktags"] + user_id = client.add_user_risk_tags(username, tags) + if user_id: + hr_context = {"UserID": user_id, "Username": username, "RiskTags": tags} + readable_outputs = tableToMarkdown(f"Code42 Risk Tags Added", hr_context) + return readable_outputs, {"Code42.HighRiskEmployee": hr_context}, user_id + else: + return_error(message="Could not add user risk tags") + + +@logger +def highriskemployee_remove_risk_tags_command(client, args): + username = args["username"] + tags = args["risktags"] + user_id = client.remove_user_risk_tags(username, tags) + if user_id: + hr_context = {"UserID": user_id, "Username": username, "RiskTags": tags} + readable_outputs = tableToMarkdown(f"Code42 Risk Tags Removed", hr_context) + return readable_outputs, {"Code42.HighRiskEmployee": hr_context}, user_id + else: + return_error(message="Could not remove user risk tags") + + def _create_incident_from_alert_details(details): return {"name": "Code42 - {}".format(details["name"]), "occurred": details["createdAt"]} @@ -594,7 +702,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: @@ -604,7 +712,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): @@ -726,6 +834,12 @@ def main(): "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": diff --git a/Packs/Code42/Integrations/Code42/Code42.yml b/Packs/Code42/Integrations/Code42/Code42.yml index e114581bcdd1..2a0f0eafc3b6 100644 --- a/Packs/Code42/Integrations/Code42/Code42.yml +++ b/Packs/Code42/Integrations/Code42/Code42.yml @@ -263,15 +263,44 @@ script: - contextPath: Code42.DepartingEmployee.DepartureDate description: The departure date for the Departing Employee. description: Adds a user to the Departing Employee List. - - name: code42-departingemployee-remove - arguments: - - name: username - description: The username to remove from the Departing Employee List. + - name: code42-departingemployee-get-all outputs: - contextPath: Code42.DepartingEmployee.UserID description: Internal Code42 User ID for the Departing Employee. + type: string - contextPath: Code42.DepartingEmployee.Username description: The username of the Departing Employee. + type: string + - contextPath: Code42.DepartingEmployee.Note + description: Note associated with the Departing Employee. + type: string + - contextPath: Code42.DepartingEmployee.DepartureDate + description: The departure date for the Departing Employee. + description: Get all employees on the Departing Employee List. + - name: code42-departingemployee-remove + arguments: + - name: username + description: The username to remove from the Departing Employee List. + outputs: + - contextPath: Code42.DepartingEmployee.UserID + description: Internal Code42 User ID for the Departing Employee. + - contextPath: Code42.DepartingEmployee.Username + description: The username of the Departing Employee. + description: Removes a user from the Departing Employee List. + - name: code42-high-risk-employee-add + arguments: + - name: username + description: The username to add to the High Risk Employee List. + - name: note + description: Note to attach to the High Risk Employee. + outputs: + - contextPath: Code42.HighRiskEmployee.UserID + description: Internal Code42 User ID for the High Risk Employee. + - contextPath: Code42.HighRiskEmployee.Username + description: The username of the High Risk Employee. + - contextPath: Code42.DepartingEmployee.Note + description: Note associated with the High Risk Employee. + type: string description: Removes a user from the Departing Employee List. - name: code42-alert-resolve arguments: diff --git a/Packs/Code42/Integrations/Code42/Code42_test.py b/Packs/Code42/Integrations/Code42/Code42_test.py index 10fd675158cc..81442fb8ef48 100644 --- a/Packs/Code42/Integrations/Code42/Code42_test.py +++ b/Packs/Code42/Integrations/Code42/Code42_test.py @@ -16,6 +16,8 @@ departingemployee_remove_command, highriskemployee_add_command, highriskemployee_remove_command, + highriskemployee_add_risk_tags_command, + highriskemployee_remove_risk_tags_command, fetch_incidents, securitydata_search_command, ) @@ -573,14 +575,8 @@ }, { "filterClause": "AND", - "filters": [ - { - "operator": "IS", - "term": "fileCategory", - "value": "IMAGE" - } - ] - } + "filters": [{"operator": "IS", "term": "fileCategory", "value": "IMAGE"}], + }, ], "pgNum": 1, "pgSize": 10000, @@ -620,7 +616,7 @@ {"operator": "IS", "term": "exposure", "value": "IsPublic"}, {"operator": "IS", "term": "exposure", "value": "SharedViaLink"}, ], - } + }, ], "pgNum": 1, "pgSize": 10000, @@ -804,7 +800,24 @@ def test_alert_resolve_command(code42_sdk_mock): assert res["id"] == "36fb8ca5-0533-4d25-9763-e09d35d60610" -def test_departing_employee_remove_command(code42_sdk_mock): +def test_departingemployee_add_command(code42_sdk_mock): + client = Code42Client( + sdk=code42_sdk_mock, base_url=MOCK_URL, auth=MOCK_AUTH, verify=False, proxy=None + ) + _, _, res = departingemployee_add_command( + client, + {"username": "user1@example.com", "departuredate": "2020-01-01", "note": "Dummy note"}, + ) + expected_user_id = "123412341234123412" # value found in GET_USER_RESPONSE + assert res == expected_user_id + add_func = code42_sdk_mock.detectionlists.departing_employee.add + add_func.assert_called_once_with(expected_user_id, departure_date="2020-01-01") + code42_sdk_mock.detectionlists.update_user_notes.assert_called_once_with( + expected_user_id, "Dummy note" + ) + + +def test_departingemployee_remove_command(code42_sdk_mock): client = Code42Client( sdk=code42_sdk_mock, base_url=MOCK_URL, auth=MOCK_AUTH, verify=False, proxy=None ) @@ -814,22 +827,22 @@ def test_departing_employee_remove_command(code42_sdk_mock): code42_sdk_mock.detectionlists.departing_employee.remove.assert_called_once_with(expected) -def test_departing_employee_add_command(code42_sdk_mock): +def test_highriskemployee_add_command(code42_sdk_mock): client = Code42Client( sdk=code42_sdk_mock, base_url=MOCK_URL, auth=MOCK_AUTH, verify=False, proxy=None ) - _, _, res = departingemployee_add_command( - client, - {"username": "user1@example.com", "departuredate": "2020-01-01", "note": "Dummy note"}, + _, _, res = highriskemployee_add_command( + client, {"username": "user1@example.com", "note": "Dummy note"} ) expected_user_id = "123412341234123412" # value found in GET_USER_RESPONSE assert res == expected_user_id - add_func = code42_sdk_mock.detectionlists.departing_employee.add - add_func.assert_called_once_with(expected_user_id, departure_date="2020-01-01") - code42_sdk_mock.detectionlists.update_user_notes.assert_called_once_with(expected_user_id, "Dummy note") + code42_sdk_mock.detectionlists.high_risk_employee.add.assert_called_once_with(expected_user_id) + code42_sdk_mock.detectionlists.update_user_notes.assert_called_once_with( + expected_user_id, "Dummy note" + ) -def test_high_risk_employee_remove_command(code42_sdk_mock): +def test_highriskemployee_remove_command(code42_sdk_mock): client = Code42Client( sdk=code42_sdk_mock, base_url=MOCK_URL, auth=MOCK_AUTH, verify=False, proxy=None ) @@ -839,19 +852,32 @@ def test_high_risk_employee_remove_command(code42_sdk_mock): code42_sdk_mock.detectionlists.high_risk_employee.remove.assert_called_once_with(expected) -def test_high_risk_employee_add_command(code42_sdk_mock): +def test_highriskemployee_add_risk_tags_command(code42_sdk_mock): client = Code42Client( sdk=code42_sdk_mock, base_url=MOCK_URL, auth=MOCK_AUTH, verify=False, proxy=None ) - _, _, res = highriskemployee_add_command( - client, - {"username": "user1@example.com", "risktags": "FLIGHT_RISK", "note": "Dummy note"}, + _, _, res = highriskemployee_add_risk_tags_command( + client, {"username": "user1@example.com", "risktags": "FLIGHT_RISK"} ) expected_user_id = "123412341234123412" # value found in GET_USER_RESPONSE assert res == expected_user_id - code42_sdk_mock.detectionlists.high_risk_employee.add.assert_called_once_with(expected_user_id) - code42_sdk_mock.detectionlists.add_user_risk_tags.assert_called_once_with(expected_user_id, "FLIGHT_RISK") - code42_sdk_mock.detectionlists.update_user_notes.assert_called_once_with(expected_user_id, "Dummy note") + code42_sdk_mock.detectionlists.add_user_risk_tags.assert_called_once_with( + expected_user_id, "FLIGHT_RISK" + ) + + +def test_highriskemployee_remove_risk_tags_command(code42_sdk_mock): + client = Code42Client( + sdk=code42_sdk_mock, base_url=MOCK_URL, auth=MOCK_AUTH, verify=False, proxy=None + ) + _, _, res = highriskemployee_remove_risk_tags_command( + client, {"username": "user1@example.com", "risktags": ["FLIGHT_RISK", "CONTRACT_EMPLOYEE"]} + ) + expected_user_id = "123412341234123412" # value found in GET_USER_RESPONSE + assert res == expected_user_id + code42_sdk_mock.detectionlists.remove_user_risk_tags.assert_called_once_with( + expected_user_id, ["FLIGHT_RISK", "CONTRACT_EMPLOYEE"] + ) def test_security_data_search_command(code42_sdk_mock): From 930a2934cb5a529baa82ee89e5e4a713ee4ba7e3 Mon Sep 17 00:00:00 2001 From: Juliya Smith Date: Tue, 16 Jun 2020 22:13:10 +0000 Subject: [PATCH 04/17] Add all the commands --- Packs/Code42/Integrations/Code42/Code42.yml | 112 ++++-- .../Code42/integration-Code42.yml | 320 ++++++++++++++++-- 2 files changed, 383 insertions(+), 49 deletions(-) diff --git a/Packs/Code42/Integrations/Code42/Code42.yml b/Packs/Code42/Integrations/Code42/Code42.yml index 2a0f0eafc3b6..617ba0d34552 100644 --- a/Packs/Code42/Integrations/Code42/Code42.yml +++ b/Packs/Code42/Integrations/Code42/Code42.yml @@ -241,6 +241,17 @@ script: description: The severity of the alert. type: string description: Retrieve alert details by alert ID + - name: code42-alert-resolve + arguments: + - name: id + required: true + description: The alert ID to resolve. Alert IDs are associated with alerts that + are fetched via fetch-incidents. + outputs: + - contextPath: Code42.SecurityAlert.ID + description: The alert ID of the resolved alert. + type: string + description: Resolves a Code42 Security alert. - name: code42-departingemployee-add arguments: - name: username @@ -263,6 +274,19 @@ script: - contextPath: Code42.DepartingEmployee.DepartureDate description: The departure date for the Departing Employee. description: Adds a user to the Departing Employee List. + - name: code42-departingemployee-remove + arguments: + - name: username + required: true + description: The username to remove from the Departing Employee List. + outputs: + - contextPath: Code42.DepartingEmployee.UserID + description: Internal Code42 User ID for the Departing Employee. + type: string + - contextPath: Code42.DepartingEmployee.Username + description: The username of the Departing Employee. + type: string + description: Removes a user from the Departing Employee List. - name: code42-departingemployee-get-all outputs: - contextPath: Code42.DepartingEmployee.UserID @@ -277,42 +301,84 @@ script: - contextPath: Code42.DepartingEmployee.DepartureDate description: The departure date for the Departing Employee. description: Get all employees on the Departing Employee List. - - name: code42-departingemployee-remove - arguments: - - name: username - description: The username to remove from the Departing Employee List. - outputs: - - contextPath: Code42.DepartingEmployee.UserID - description: Internal Code42 User ID for the Departing Employee. - - contextPath: Code42.DepartingEmployee.Username - description: The username of the Departing Employee. - description: Removes a user from the Departing Employee List. - - name: code42-high-risk-employee-add + - name: code42-highriskemployee-add arguments: - name: username + required: true description: The username to add to the High Risk Employee List. - name: note description: Note to attach to the High Risk Employee. outputs: - contextPath: Code42.HighRiskEmployee.UserID description: Internal Code42 User ID for the High Risk Employee. + type: string - contextPath: Code42.HighRiskEmployee.Username description: The username of the High Risk Employee. - - contextPath: Code42.DepartingEmployee.Note + type: string + - contextPath: Code42.HighRiskEmployee.Note description: Note associated with the High Risk Employee. - type: string - description: Removes a user from the Departing Employee List. - - name: code42-alert-resolve + type: string + description: Removes a user from the High Risk Employee List. + - name: code42-highriskemployee-remove arguments: - - name: id - required: true - description: The alert ID to resolve. Alert IDs are associated with alerts that - are fetched via fetch-incidents. + - name: username + required: true + description: The username to remove from the High Risk Employee List. outputs: - - contextPath: Code42.SecurityAlert.ID - description: The alert ID of the resolved alert. - type: string - description: Resolves a Code42 Security alert. + - contextPath: Code42.HighRiskEmployee.UserID + description: Internal Code42 User ID for the High Risk Employee. + - contextPath: Code42.HighRiskEmployee.Username + description: The username of the High Risk Employee. + description: Removes a user from the High Risk Employee List. + - name: code42-highriskemployee-get-all + arguments: + - name: risktags + description: To filter results by employees who have these risk tags. + outputs: + - contextPath: Code42.HighRiskEmployee.UserID + description: Internal Code42 User ID for the High Risk Employee. + type: string + - contextPath: Code42.HighRiskEmployee.Username + description: The username of the High Risk Employee. + type: string + - contextPath: Code42.HighRiskEmployee.Note + description: Note associated with the High Risk Employee. + type: string + description: Get all employees on the High Risk Employee List. + - name: code42-highriskemployee-add-risk-tags + arguments: + - name: username + required: true + description: The username of the High Risk Employee. + - name: risktags + required: true + description: Risk tags to associate with the High Risk Employee. + outputs: + - contextPath: Code42.HighRiskEmployee.UserID + description: Internal Code42 User ID for the Departing Employee. + type: string + - contextPath: Code42.HighRiskEmployee.Username + description: The username of the High Risk Employee. + type: string + - contextPath: Code42.HighRiskEmployee.RiskTags + description: Risk tags to asspciate with the High Risk Employee. + - name: code42-highriskemployee-remove-risk-tags + arguments: + - name: username + required: true + description: The username of the High Risk Employee. + - name: risktags + required: true + description: Risk tags to disassociate from the High Risk Employee. + outputs: + - contextPath: Code42.HighRiskEmployee.UserID + description: Internal Code42 User ID for the Departing Employee. + type: string + - contextPath: Code42.HighRiskEmployee.Username + description: The username of the High Risk Employee. + type: string + - contextPath: Code42.HighRiskEmployee.RiskTags + description: Risk tags to disassociate from the High Risk Employee. dockerimage: demisto/py42:1.0.0.9242 isfetch: true runonce: false diff --git a/Packs/Code42/Integrations/Code42/integration-Code42.yml b/Packs/Code42/Integrations/Code42/integration-Code42.yml index 3668c3a97b75..2e3c4c5b53fa 100644 --- a/Packs/Code42/Integrations/Code42/integration-Code42.yml +++ b/Packs/Code42/Integrations/Code42/integration-Code42.yml @@ -226,36 +226,100 @@ script: return None return user_id - def fetch_alerts(self, start_time, event_severity_filter): + def remove_user_from_departing_employee(self, username): try: - query = _create_alert_query(event_severity_filter, start_time) - res = self._sdk.alerts.search(query) + user_id = self.get_user_id(username) + self._sdk.detectionlists.departing_employee.remove(user_id) except Exception: return None - return res["alerts"] + return user_id - def get_alert_details(self, alert_id): + def get_all_departing_employees(self): try: - res = self._sdk.alerts.get_details(alert_id) + res = [] + pages = self._sdk.detectionlists.departing_employee.get_all() + for page in pages: + res.extend(page["items"]) except Exception: return None - return res["alerts"][0] + return res - def get_current_user(self): + def add_user_to_high_risk_employee(self, username, note=None): try: - res = self._sdk.users.get_current() + user_id = self.get_user_id(username) + self._sdk.detectionlists.high_risk_employee.add(user_id) + if note: + self._sdk.detectionlists.update_user_notes(user_id, note) except Exception: return None - return res + return user_id - def remove_user_from_departing_employee(self, username): + def remove_user_from_high_risk_employee(self, username): try: user_id = self.get_user_id(username) - self._sdk.detectionlists.departing_employee.remove(user_id) + self._sdk.detectionlists.high_risk_employee.remove(user_id) + except Exception: + return None + return user_id + + def add_user_risk_tags(self, username, risk_tags): + try: + user_id = self.get_user_id(username) + self._sdk.detectionlists.add_user_risk_tags(user_id, risk_tags) + except Exception: + return None + return user_id + + def remove_user_risk_tags(self, username, risk_tags): + try: + user_id = self.get_user_id(username) + self._sdk.detectionlists.remove_user_risk_tags(user_id, risk_tags) except Exception: return None return user_id + def get_all_high_risk_employees(self, risk_tags=None): + if isinstance(risk_tags, str): + risk_tags = [risk_tags] + try: + res = [] + pages = self._sdk.detectionlists.high_risk_employee.get_all() + for page in pages: + res.extend(self._get_all_high_risk_employees_from_page(page, risk_tags)) + except Exception: + return None + return res + + def _get_all_high_risk_employees_from_page(self, page, risk_tags): + res = [] + for employee in page["items"]: + if not risk_tags: + res.append(employee) + continue + + username = employee["userName"] + employee_tags = self._sdk.detectionlists.get_user(username)["riskFactors"] + + # If the employee risk tags contain all the given risk tags + if set(risk_tags) <= set(employee_tags): + res.append(employee) + return res + + def fetch_alerts(self, start_time, event_severity_filter): + try: + query = _create_alert_query(event_severity_filter, start_time) + res = self._sdk.alerts.search(query) + except Exception: + return None + return res["alerts"] + + def get_alert_details(self, alert_id): + try: + res = self._sdk.alerts.get_details(alert_id) + except Exception: + return None + return res["alerts"][0] + def resolve_alert(self, id): try: self._sdk.alerts.resolve(id) @@ -263,6 +327,13 @@ script: return None return id + def get_current_user(self): + try: + res = self._sdk.users.get_current() + except Exception: + return None + return res + def get_user_id(self, username): try: res = self._sdk.users.get_by_username(username) @@ -270,7 +341,7 @@ script: return None return res["users"][0]["userUid"] - def search_json(self, payload): + def search_file_events(self, payload): try: res = self._sdk.securitydata.search_file_events(payload) except Exception: @@ -322,6 +393,7 @@ script: class AlertQueryFilters(Code42SearchFilters): """Class for simplifying building up an alert search query""" + def to_all_query(self): query = AlertQuery.all(*self._filters) query.page_size = 500 @@ -492,6 +564,10 @@ script: return {v: obj.get(k) for k, v in context_mapper.items() if obj.get(k)} + """Commands""" + + + @logger def alert_get_command(client, args): @@ -546,7 +622,7 @@ script: note = args.get("note") user_id = client.add_user_to_departing_employee(username, departing_date, note) if not user_id: - return_error(message="Could not add user to Departing Employee List") + return_error(message="Could not add user to the Departing Employee List") de_context = { "UserID": user_id, @@ -570,7 +646,98 @@ script: ) return readable_outputs, {"Code42.DepartingEmployee": de_context}, user_id else: - return_error(message="Could not remove user from Departing Employee List") + return_error(message="Could not remove user from the Departing Employee List") + + + @logger + + def departingemployee_get_all_command(client, args): + employees = client.get_all_departing_employees() + if employees: + employees_context = [{ + "UserID": e["userId"], + "Username": e["userName"], + "DepartureDate": e.get("departureDate"), + "Note": e["notes"] + } for e in employees] + readable_outputs = tableToMarkdown( + f"All Departing Employees", employees_context, headers=DEPARTING_EMPLOYEE_HEADERS + ) + return readable_outputs, {"Code42.DepartingEmployee(val.UserID && val.UserID == obj.UserID)": employees_context}, employees + else: + return_error(message="Could not get all Departing Employees") + + + @logger + + def highriskemployee_add_command(client, args): + username = args["username"] + note = args.get("note") + user_id = client.add_user_to_high_risk_employee(username, note) + if not user_id: + return_error(message="Could not add user to the High Risk Employee List") + + hr_context = {"UserID": user_id, "Username": username} + readable_outputs = tableToMarkdown(f"Code42 High Risk Employee List User Added", hr_context) + return readable_outputs, {"Code42.HighRiskEmployee": hr_context}, user_id + + + @logger + + def highriskemployee_remove_command(client, args): + username = args["username"] + user_id = client.remove_user_from_high_risk_employee(username) + if user_id: + hr_context = {"UserID": user_id, "Username": username} + readable_outputs = tableToMarkdown( + f"Code42 High Risk Employee List User Removed", hr_context + ) + return readable_outputs, {"Code42.HighRiskEmployee": hr_context}, user_id + else: + return_error(message="Could not remove user from the High Risk Employee List") + + + @logger + + def highriskemployee_get_all_command(client, args): + tags = args.get("risktags") + employees = client.get_all_high_risk_employees(tags) + if employees: + hr_context = {"HighRiskEmployees": employees} + readable_outputs = tableToMarkdown( + f"Retrieved All High Risk Employees", hr_context + ) + return readable_outputs, {"Code42.HighRiskEmployee": hr_context}, employees + else: + return_error(message="Could not get all High Risk Employees") + + + @logger + + def highriskemployee_add_risk_tags_command(client, args): + username = args["username"] + tags = args["risktags"] + user_id = client.add_user_risk_tags(username, tags) + if user_id: + hr_context = {"UserID": user_id, "Username": username, "RiskTags": tags} + readable_outputs = tableToMarkdown(f"Code42 Risk Tags Added", hr_context) + return readable_outputs, {"Code42.HighRiskEmployee": hr_context}, user_id + else: + return_error(message="Could not add user risk tags") + + + @logger + + def highriskemployee_remove_risk_tags_command(client, args): + username = args["username"] + tags = args["risktags"] + user_id = client.remove_user_risk_tags(username, tags) + if user_id: + hr_context = {"UserID": user_id, "Username": username, "RiskTags": tags} + readable_outputs = tableToMarkdown(f"Code42 Risk Tags Removed", hr_context) + return readable_outputs, {"Code42.HighRiskEmployee": hr_context}, user_id + else: + return_error(message="Could not remove user risk tags") def _create_incident_from_alert_details(details): @@ -622,7 +789,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: @@ -632,7 +799,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): @@ -667,7 +834,7 @@ script: def _get_file_events_from_alert_details(self, observation, alert_details): security_data_query = map_observation_to_security_query(observation, alert_details["actor"]) - return self._client.search_json(security_data_query) + return self._client.search_file_events(security_data_query) def fetch_incidents( @@ -699,11 +866,11 @@ script: 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_json(_json) + file_events = client.search_file_events(_json) else: # Build payload payload = build_query_payload(args) - file_events = client.search_json(payload) + file_events = client.search_file_events(payload) if file_events: for file_event in file_events: code42_context_event = map_to_code42_event_context(file_event) @@ -755,6 +922,12 @@ script: "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": @@ -972,6 +1145,16 @@ script: description: The severity of the alert. type: string description: Retrieve alert details by alert ID + - name: code42-alert-resolve + arguments: + - name: id + required: true + description: The alert ID to resolve. Alert IDs are associated with alerts that are fetched via fetch-incidents. + outputs: + - contextPath: Code42.SecurityAlert.ID + description: The alert ID of the resolved alert. + type: string + description: Resolves a Code42 Security alert. - name: code42-departingemployee-add arguments: - name: username @@ -997,23 +1180,108 @@ script: - name: code42-departingemployee-remove arguments: - name: username + required: true description: The username to remove from the Departing Employee List. outputs: - contextPath: Code42.DepartingEmployee.UserID description: Internal Code42 User ID for the Departing Employee. + type: string - contextPath: Code42.DepartingEmployee.Username description: The username of the Departing Employee. + type: string description: Removes a user from the Departing Employee List. - - name: code42-alert-resolve + - name: code42-departingemployee-get-all + outputs: + - contextPath: Code42.DepartingEmployee.UserID + description: Internal Code42 User ID for the Departing Employee. + type: string + - contextPath: Code42.DepartingEmployee.Username + description: The username of the Departing Employee. + type: string + - contextPath: Code42.DepartingEmployee.Note + description: Note associated with the Departing Employee. + type: string + - contextPath: Code42.DepartingEmployee.DepartureDate + description: The departure date for the Departing Employee. + description: Get all employees on the Departing Employee List. + - name: code42-highriskemployee-add arguments: - - name: id + - name: username required: true - description: The alert ID to resolve. Alert IDs are associated with alerts that are fetched via fetch-incidents. + description: The username to add to the High Risk Employee List. + - name: note + description: Note to attach to the High Risk Employee. outputs: - - contextPath: Code42.SecurityAlert.ID - description: The alert ID of the resolved alert. + - contextPath: Code42.HighRiskEmployee.UserID + description: Internal Code42 User ID for the High Risk Employee. type: string - description: Resolves a Code42 Security alert. + - contextPath: Code42.HighRiskEmployee.Username + description: The username of the High Risk Employee. + type: string + - contextPath: Code42.HighRiskEmployee.Note + description: Note associated with the High Risk Employee. + type: string + description: Removes a user from the High Risk Employee List. + - name: code42-highriskemployee-remove + arguments: + - name: username + required: true + description: The username to remove from the High Risk Employee List. + outputs: + - contextPath: Code42.HighRiskEmployee.UserID + description: Internal Code42 User ID for the High Risk Employee. + - contextPath: Code42.HighRiskEmployee.Username + description: The username of the High Risk Employee. + description: Removes a user from the High Risk Employee List. + - name: code42-highriskemployee-get-all + arguments: + - name: risktags + description: To filter results by employees who have these risk tags. + outputs: + - contextPath: Code42.HighRiskEmployee.UserID + description: Internal Code42 User ID for the High Risk Employee. + type: string + - contextPath: Code42.HighRiskEmployee.Username + description: The username of the High Risk Employee. + type: string + - contextPath: Code42.HighRiskEmployee.Note + description: Note associated with the High Risk Employee. + type: string + description: Get all employees on the High Risk Employee List. + - name: code42-highriskemployee-add-risk-tags + arguments: + - name: username + required: true + description: The username of the High Risk Employee. + - name: risktags + required: true + description: Risk tags to associate with the High Risk Employee. + outputs: + - contextPath: Code42.HighRiskEmployee.UserID + description: Internal Code42 User ID for the Departing Employee. + type: string + - contextPath: Code42.HighRiskEmployee.Username + description: The username of the High Risk Employee. + type: string + - contextPath: Code42.HighRiskEmployee.RiskTags + description: Risk tags to asspciate with the High Risk Employee. + - name: code42-highriskemployee-remove-risk-tags + arguments: + - name: username + required: true + description: The username of the High Risk Employee. + - name: risktags + required: true + description: Risk tags to disassociate from the High Risk Employee. + outputs: + - contextPath: Code42.HighRiskEmployee.UserID + description: Internal Code42 User ID for the Departing Employee. + type: string + - contextPath: Code42.HighRiskEmployee.Username + description: The username of the High Risk Employee. + type: string + - contextPath: Code42.HighRiskEmployee.RiskTags + description: Risk tags to disassociate from the High Risk Employee. dockerimage: demisto/py42:1.0.0.9242 isfetch: true runonce: false From d104e5b888218f2a28fb29e218be46d32a734a4c Mon Sep 17 00:00:00 2001 From: Juliya Smith Date: Tue, 16 Jun 2020 23:32:36 +0000 Subject: [PATCH 05/17] Update commands after testing --- Packs/Code42/Integrations/Code42/Code42.py | 46 ++++++++++++------- .../Code42/integration-Code42.yml | 46 ++++++++++++------- 2 files changed, 58 insertions(+), 34 deletions(-) diff --git a/Packs/Code42/Integrations/Code42/Code42.py b/Packs/Code42/Integrations/Code42/Code42.py index 5cebf755453d..c9dd7cea36dd 100644 --- a/Packs/Code42/Integrations/Code42/Code42.py +++ b/Packs/Code42/Integrations/Code42/Code42.py @@ -572,16 +572,23 @@ def departingemployee_remove_command(client, args): def departingemployee_get_all_command(client, args): employees = client.get_all_departing_employees() if employees: - employees_context = [{ - "UserID": e["userId"], - "Username": e["userName"], - "DepartureDate": e.get("departureDate"), - "Note": e["notes"] - } for e in employees] + employees_context = [ + { + "UserID": e["userId"], + "Username": e["userName"], + "DepartureDate": e.get("departureDate"), + "Note": e["notes"], + } + for e in employees + ] readable_outputs = tableToMarkdown( f"All Departing Employees", employees_context, headers=DEPARTING_EMPLOYEE_HEADERS ) - return readable_outputs, {"Code42.DepartingEmployee(val.UserID && val.UserID == obj.UserID)": employees_context}, employees + return ( + readable_outputs, + {"Code42.DepartingEmployee(val.UserID && val.UserID == obj.UserID)": employees_context}, + employees, + ) else: return_error(message="Could not get all Departing Employees") @@ -618,11 +625,16 @@ def highriskemployee_get_all_command(client, args): tags = args.get("risktags") employees = client.get_all_high_risk_employees(tags) if employees: - hr_context = {"HighRiskEmployees": employees} - readable_outputs = tableToMarkdown( - f"Retrieved All High Risk Employees", hr_context + employees_context = [ + {"UserID": e["userId"], "Username": e["userName"], "Note": e["notes"]} + for e in employees + ] + readable_outputs = tableToMarkdown(f"Retrieved All High Risk Employees", employees_context) + return ( + readable_outputs, + {"Code42.HighRiskEmployee(val.UserID && val.UserID == obj.UserID)": employees_context}, + employees, ) - return readable_outputs, {"Code42.HighRiskEmployee": hr_context}, employees else: return_error(message="Could not get all High Risk Employees") @@ -633,9 +645,9 @@ def highriskemployee_add_risk_tags_command(client, args): tags = args["risktags"] user_id = client.add_user_risk_tags(username, tags) if user_id: - hr_context = {"UserID": user_id, "Username": username, "RiskTags": tags} - readable_outputs = tableToMarkdown(f"Code42 Risk Tags Added", hr_context) - return readable_outputs, {"Code42.HighRiskEmployee": hr_context}, user_id + rt_context = {"UserID": user_id, "Username": username, "RiskTags": tags} + readable_outputs = tableToMarkdown(f"Code42 Risk Tags Added", rt_context) + return readable_outputs, {"Code42.HighRiskEmployee": rt_context}, user_id else: return_error(message="Could not add user risk tags") @@ -646,9 +658,9 @@ def highriskemployee_remove_risk_tags_command(client, args): tags = args["risktags"] user_id = client.remove_user_risk_tags(username, tags) if user_id: - hr_context = {"UserID": user_id, "Username": username, "RiskTags": tags} - readable_outputs = tableToMarkdown(f"Code42 Risk Tags Removed", hr_context) - return readable_outputs, {"Code42.HighRiskEmployee": hr_context}, user_id + rt_context = {"UserID": user_id, "Username": username, "RiskTags": tags} + readable_outputs = tableToMarkdown(f"Code42 Risk Tags Removed", rt_context) + return readable_outputs, {"Code42.HighRiskEmployee": rt_context}, user_id else: return_error(message="Could not remove user risk tags") diff --git a/Packs/Code42/Integrations/Code42/integration-Code42.yml b/Packs/Code42/Integrations/Code42/integration-Code42.yml index 2e3c4c5b53fa..e120566d544f 100644 --- a/Packs/Code42/Integrations/Code42/integration-Code42.yml +++ b/Packs/Code42/Integrations/Code42/integration-Code42.yml @@ -654,16 +654,23 @@ script: def departingemployee_get_all_command(client, args): employees = client.get_all_departing_employees() if employees: - employees_context = [{ - "UserID": e["userId"], - "Username": e["userName"], - "DepartureDate": e.get("departureDate"), - "Note": e["notes"] - } for e in employees] + employees_context = [ + { + "UserID": e["userId"], + "Username": e["userName"], + "DepartureDate": e.get("departureDate"), + "Note": e["notes"], + } + for e in employees + ] readable_outputs = tableToMarkdown( f"All Departing Employees", employees_context, headers=DEPARTING_EMPLOYEE_HEADERS ) - return readable_outputs, {"Code42.DepartingEmployee(val.UserID && val.UserID == obj.UserID)": employees_context}, employees + return ( + readable_outputs, + {"Code42.DepartingEmployee(val.UserID && val.UserID == obj.UserID)": employees_context}, + employees, + ) else: return_error(message="Could not get all Departing Employees") @@ -703,11 +710,16 @@ script: tags = args.get("risktags") employees = client.get_all_high_risk_employees(tags) if employees: - hr_context = {"HighRiskEmployees": employees} - readable_outputs = tableToMarkdown( - f"Retrieved All High Risk Employees", hr_context + employees_context = [ + {"UserID": e["userId"], "Username": e["userName"], "Note": e["notes"]} + for e in employees + ] + readable_outputs = tableToMarkdown(f"Retrieved All High Risk Employees", employees_context) + return ( + readable_outputs, + {"Code42.HighRiskEmployee(val.UserID && val.UserID == obj.UserID)": employees_context}, + employees, ) - return readable_outputs, {"Code42.HighRiskEmployee": hr_context}, employees else: return_error(message="Could not get all High Risk Employees") @@ -719,9 +731,9 @@ script: tags = args["risktags"] user_id = client.add_user_risk_tags(username, tags) if user_id: - hr_context = {"UserID": user_id, "Username": username, "RiskTags": tags} - readable_outputs = tableToMarkdown(f"Code42 Risk Tags Added", hr_context) - return readable_outputs, {"Code42.HighRiskEmployee": hr_context}, user_id + rt_context = {"UserID": user_id, "Username": username, "RiskTags": tags} + readable_outputs = tableToMarkdown(f"Code42 Risk Tags Added", rt_context) + return readable_outputs, {"Code42.HighRiskEmployee": rt_context}, user_id else: return_error(message="Could not add user risk tags") @@ -733,9 +745,9 @@ script: tags = args["risktags"] user_id = client.remove_user_risk_tags(username, tags) if user_id: - hr_context = {"UserID": user_id, "Username": username, "RiskTags": tags} - readable_outputs = tableToMarkdown(f"Code42 Risk Tags Removed", hr_context) - return readable_outputs, {"Code42.HighRiskEmployee": hr_context}, user_id + rt_context = {"UserID": user_id, "Username": username, "RiskTags": tags} + readable_outputs = tableToMarkdown(f"Code42 Risk Tags Removed", rt_context) + return readable_outputs, {"Code42.HighRiskEmployee": rt_context}, user_id else: return_error(message="Could not remove user risk tags") From e3621f91465d28cdf9a674dde228521f0fa86034 Mon Sep 17 00:00:00 2001 From: Juliya Smith Date: Wed, 17 Jun 2020 13:47:05 +0000 Subject: [PATCH 06/17] Test new commands --- Packs/Code42/Integrations/Code42/Code42.py | 38 ++- .../Code42/Integrations/Code42/Code42_test.py | 287 +++++++++++++++--- .../Code42/integration-Code42.yml | 38 ++- 3 files changed, 281 insertions(+), 82 deletions(-) diff --git a/Packs/Code42/Integrations/Code42/Code42.py b/Packs/Code42/Integrations/Code42/Code42.py index c9dd7cea36dd..4ba71f9ad47e 100644 --- a/Packs/Code42/Integrations/Code42/Code42.py +++ b/Packs/Code42/Integrations/Code42/Code42.py @@ -109,6 +109,7 @@ "FileCategory", "DeviceUsername", ] + SECURITY_ALERT_HEADERS = ["Type", "Occurred", "Username", "Name", "Description", "State", "ID"] @@ -168,7 +169,8 @@ def get_all_departing_employees(self): res = [] pages = self._sdk.detectionlists.departing_employee.get_all() for page in pages: - res.extend(page["items"]) + employees = page["items"] + res.extend(employees) except Exception: return None return res @@ -226,11 +228,9 @@ def _get_all_high_risk_employees_from_page(self, page, risk_tags): res.append(employee) continue - username = employee["userName"] - employee_tags = self._sdk.detectionlists.get_user(username)["riskFactors"] - + employee_tags = employee.get("riskFactors") # If the employee risk tags contain all the given risk tags - if set(risk_tags) <= set(employee_tags): + if employee_tags and set(risk_tags) <= set(employee_tags): res.append(employee) return res @@ -499,7 +499,7 @@ def alert_get_command(client, args): code42_context = map_to_code42_alert_context(alert) code42_securityalert_context.append(code42_context) readable_outputs = tableToMarkdown( - f"Code42 Security Alert Results", + "Code42 Security Alert Results", code42_securityalert_context, headers=SECURITY_ALERT_HEADERS, ) @@ -524,7 +524,7 @@ def alert_resolve_command(client, args): code42_context = map_to_code42_alert_context(alert_details) code42_security_alert_context.append(code42_context) readable_outputs = tableToMarkdown( - f"Code42 Security Alert Resolved", + "Code42 Security Alert Resolved", code42_security_alert_context, headers=SECURITY_ALERT_HEADERS, ) @@ -550,7 +550,7 @@ def departingemployee_add_command(client, args): "DepartureDate": departing_date, "Note": note, } - readable_outputs = tableToMarkdown(f"Code42 Departing Employee List User Added", de_context) + readable_outputs = tableToMarkdown("Code42 Departing Employee List User Added", de_context) return readable_outputs, {"Code42.DepartingEmployee": de_context}, user_id @@ -561,7 +561,7 @@ def departingemployee_remove_command(client, args): if user_id: de_context = {"UserID": user_id, "Username": username} readable_outputs = tableToMarkdown( - f"Code42 Departing Employee List User Removed", de_context + "Code42 Departing Employee List User Removed", de_context ) return readable_outputs, {"Code42.DepartingEmployee": de_context}, user_id else: @@ -581,9 +581,7 @@ def departingemployee_get_all_command(client, args): } for e in employees ] - readable_outputs = tableToMarkdown( - f"All Departing Employees", employees_context, headers=DEPARTING_EMPLOYEE_HEADERS - ) + readable_outputs = tableToMarkdown("All Departing Employees", employees_context) return ( readable_outputs, {"Code42.DepartingEmployee(val.UserID && val.UserID == obj.UserID)": employees_context}, @@ -602,7 +600,7 @@ def highriskemployee_add_command(client, args): return_error(message="Could not add user to the High Risk Employee List") hr_context = {"UserID": user_id, "Username": username} - readable_outputs = tableToMarkdown(f"Code42 High Risk Employee List User Added", hr_context) + readable_outputs = tableToMarkdown("Code42 High Risk Employee List User Added", hr_context) return readable_outputs, {"Code42.HighRiskEmployee": hr_context}, user_id @@ -613,7 +611,7 @@ def highriskemployee_remove_command(client, args): if user_id: hr_context = {"UserID": user_id, "Username": username} readable_outputs = tableToMarkdown( - f"Code42 High Risk Employee List User Removed", hr_context + "Code42 High Risk Employee List User Removed", hr_context ) return readable_outputs, {"Code42.HighRiskEmployee": hr_context}, user_id else: @@ -629,7 +627,7 @@ def highriskemployee_get_all_command(client, args): {"UserID": e["userId"], "Username": e["userName"], "Note": e["notes"]} for e in employees ] - readable_outputs = tableToMarkdown(f"Retrieved All High Risk Employees", employees_context) + readable_outputs = tableToMarkdown("Retrieved All High Risk Employees", employees_context) return ( readable_outputs, {"Code42.HighRiskEmployee(val.UserID && val.UserID == obj.UserID)": employees_context}, @@ -646,7 +644,7 @@ def highriskemployee_add_risk_tags_command(client, args): user_id = client.add_user_risk_tags(username, tags) if user_id: rt_context = {"UserID": user_id, "Username": username, "RiskTags": tags} - readable_outputs = tableToMarkdown(f"Code42 Risk Tags Added", rt_context) + readable_outputs = tableToMarkdown("Code42 Risk Tags Added", rt_context) return readable_outputs, {"Code42.HighRiskEmployee": rt_context}, user_id else: return_error(message="Could not add user risk tags") @@ -659,7 +657,7 @@ def highriskemployee_remove_risk_tags_command(client, args): user_id = client.remove_user_risk_tags(username, tags) if user_id: rt_context = {"UserID": user_id, "Username": username, "RiskTags": tags} - readable_outputs = tableToMarkdown(f"Code42 Risk Tags Removed", rt_context) + readable_outputs = tableToMarkdown("Code42 Risk Tags Removed", rt_context) return readable_outputs, {"Code42.HighRiskEmployee": rt_context}, user_id else: return_error(message="Could not remove user risk tags") @@ -714,7 +712,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: @@ -724,7 +722,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): @@ -802,7 +800,7 @@ def securitydata_search_command(client, args): file_context_event = map_to_file_context(file_event) file_context.append(file_context_event) readable_outputs = tableToMarkdown( - f"Code42 Security Data Results", + "Code42 Security Data Results", code42_security_data_context, headers=SECURITY_EVENT_HEADERS, ) diff --git a/Packs/Code42/Integrations/Code42/Code42_test.py b/Packs/Code42/Integrations/Code42/Code42_test.py index 81442fb8ef48..ce2f9f8e29da 100644 --- a/Packs/Code42/Integrations/Code42/Code42_test.py +++ b/Packs/Code42/Integrations/Code42/Code42_test.py @@ -14,8 +14,10 @@ alert_resolve_command, departingemployee_add_command, departingemployee_remove_command, + departingemployee_get_all_command, highriskemployee_add_command, highriskemployee_remove_command, + highriskemployee_get_all_command, highriskemployee_add_risk_tags_command, highriskemployee_remove_risk_tags_command, fetch_incidents, @@ -706,6 +708,135 @@ ] }""" +MOCK_GET_ALL_DEPARTING_EMPLOYEES_RESPONSE = """ +{ + "items": [ + { + "type$": "DEPARTING_EMPLOYEE_V2", + "tenantId": 1000, + "userId": "890973079883949999", + "userName": "test@example.com", + "displayName": "Name", + "notes": "", + "createdAt": "2019-10-25T13:31:14.1199010Z", + "status": "OPEN", + "cloudUsernames": ["test@cloud.com"], + "totalBytes": 139856482, + "numEvents": 11 + }, + { + "type$": "DEPARTING_EMPLOYEE_V2", + "tenantId": 1000, + "userId": "123412341234123412", + "userName": "user1@example.com", + "displayName": "Name", + "notes": "", + "createdAt": "2019-10-25T13:31:14.1199010Z", + "status": "OPEN", + "cloudUsernames": ["test@example.com"], + "totalBytes": 139856482, + "numEvents": 11 + }, + { + "type$": "DEPARTING_EMPLOYEE_V2", + "tenantId": 1000, + "userId": "890973079883949999", + "userName": "test@example.com", + "displayName": "Name", + "notes": "", + "createdAt": "2019-10-25T13:31:14.1199010Z", + "status": "OPEN", + "cloudUsernames": ["test@example.com"], + "totalBytes": 139856482, + "numEvents": 11 + } + ], + "totalCount": 3 +} +""" + +MOCK_GET_ALL_HIGH_RISK_EMPLOYEES_RESPONSE = """ +{ + "type$": "HIGH_RISK_SEARCH_RESPONSE_V2", + "items": [ + { + "type$": "HIGH_RISK_EMPLOYEE_V2", + "tenantId": "1d71796f-af5b-4231-9d8e-df6434da4663", + "userId": "91209844444444444", + "userName": "karen@example.com", + "displayName": "Karen", + "notes": "High risk notes", + "createdAt": "2020-05-22T17:47:42.7054310Z", + "status": "OPEN", + "cloudUsernames": [ + "karen+test@example.com", + "karen+manager@example.com" + ], + "totalBytes": 816122, + "numEvents": 13, + "riskFactors": [ + "PERFORMANCE_CONCERNS", + "SUSPICIOUS_SYSTEM_ACTIVITY", + "POOR_SECURITY_PRACTICES" + ] + }, + { + "type$": "HIGH_RISK_EMPLOYEE_V2", + "tenantId": "1d71796f-af5b-4231-9d8e-df6434da4663", + "userId": "94222222975202822222", + "userName": "james.test@example.com", + "displayName": "James Test", + "notes": "tests and more tests", + "createdAt": "2020-05-28T12:39:57.2058370Z", + "status": "OPEN", + "cloudUsernames": [ + "james.test+test@example.com" + ] + }, + { + "type$": "HIGH_RISK_EMPLOYEE_V2", + "tenantId": "1d71796f-af5b-4231-9d8e-df6434da4663", + "userId": "123412341234123412", + "userName": "user1@example.com", + "displayName": "User 1", + "notes": "Test Notes", + "createdAt": "2020-05-22T17:47:42.4836920Z", + "status": "OPEN", + "cloudUsernames": [ + "test@example.com", + "abc123@example.com" + ], + "riskFactors": [ + "PERFORMANCE_CONCERNS" + ] + } + ], + "totalCount": 3, + "rollups": [ + { + "type$": "HIGH_RISK_FILTER_ROLLUP_V2", + "filterType": "OPEN", + "totalCount": 3 + }, + { + "type$": "HIGH_RISK_FILTER_ROLLUP_V2", + "filterType": "EXFILTRATION_24_HOURS", + "totalCount": 0 + }, + { + "type$": "HIGH_RISK_FILTER_ROLLUP_V2", + "filterType": "EXFILTRATION_30_DAYS", + "totalCount": 1 + } + ], + "filterType": "OPEN", + "pgSize": 10, + "pgNum": 1, + "srtKey": "NUM_EVENTS", + "srtDirection": "DESC" +} +""" + @pytest.fixture def code42_sdk_mock(mocker): @@ -729,6 +860,22 @@ def code42_sdk_mock(mocker): ) c42_sdk_mock.securitydata.search_file_events.return_value = search_file_events_response + # Setup get all departing employees + all_departing_employees_response = create_mock_code42_sdk_response_generator( + mocker, [MOCK_GET_ALL_DEPARTING_EMPLOYEES_RESPONSE] + ) + c42_sdk_mock.detectionlists.departing_employee.get_all.return_value = ( + all_departing_employees_response + ) + + # Setup get all high risk employees + all_high_risk_employees_response = create_mock_code42_sdk_response_generator( + mocker, [MOCK_GET_ALL_HIGH_RISK_EMPLOYEES_RESPONSE] + ) + c42_sdk_mock.detectionlists.high_risk_employee.get_all.return_value = ( + all_high_risk_employees_response + ) + return c42_sdk_mock @@ -738,6 +885,14 @@ def create_mock_code42_sdk_response(mocker, response_text): return Py42Response(response_mock) +def create_mock_code42_sdk_response_generator(mocker, response_pages): + return (create_mock_code42_sdk_response(mocker, page) for page in response_pages) + + +def create_client(sdk): + return Code42Client(sdk=sdk, base_url=MOCK_URL, auth=MOCK_AUTH, verify=False, proxy=None) + + """TESTS""" @@ -785,25 +940,19 @@ def test_map_to_file_context(): def test_alert_get_command(code42_sdk_mock): - client = Code42Client( - sdk=code42_sdk_mock, base_url=MOCK_URL, auth=MOCK_AUTH, verify=False, proxy=None - ) + client = create_client(code42_sdk_mock) _, _, res = alert_get_command(client, {"id": "36fb8ca5-0533-4d25-9763-e09d35d60610"}) assert res["ruleId"] == "4576576e-13cb-4f88-be3a-ee77739de649" def test_alert_resolve_command(code42_sdk_mock): - client = Code42Client( - sdk=code42_sdk_mock, base_url=MOCK_URL, auth=MOCK_AUTH, verify=False, proxy=None - ) + client = create_client(code42_sdk_mock) _, _, res = alert_resolve_command(client, {"id": "36fb8ca5-0533-4d25-9763-e09d35d60610"}) assert res["id"] == "36fb8ca5-0533-4d25-9763-e09d35d60610" def test_departingemployee_add_command(code42_sdk_mock): - client = Code42Client( - sdk=code42_sdk_mock, base_url=MOCK_URL, auth=MOCK_AUTH, verify=False, proxy=None - ) + client = create_client(code42_sdk_mock) _, _, res = departingemployee_add_command( client, {"username": "user1@example.com", "departuredate": "2020-01-01", "note": "Dummy note"}, @@ -818,19 +967,43 @@ def test_departingemployee_add_command(code42_sdk_mock): def test_departingemployee_remove_command(code42_sdk_mock): - client = Code42Client( - sdk=code42_sdk_mock, base_url=MOCK_URL, auth=MOCK_AUTH, verify=False, proxy=None - ) + client = create_client(code42_sdk_mock) _, _, res = departingemployee_remove_command(client, {"username": "user1@example.com"}) expected = "123412341234123412" # value found in GET_USER_RESPONSE assert res == expected code42_sdk_mock.detectionlists.departing_employee.remove.assert_called_once_with(expected) -def test_highriskemployee_add_command(code42_sdk_mock): - client = Code42Client( - sdk=code42_sdk_mock, base_url=MOCK_URL, auth=MOCK_AUTH, verify=False, proxy=None +def test_departingemployee_get_all_command(code42_sdk_mock): + client = create_client(code42_sdk_mock) + _, _, res = departingemployee_get_all_command(client, {"username": "user1@example.com"}) + expected = json.loads(MOCK_GET_ALL_DEPARTING_EMPLOYEES_RESPONSE)["items"] + assert res == expected + assert code42_sdk_mock.detectionlists.departing_employee.get_all.call_count == 1 + + +def test_departingemployee_get_all_command_gets_employees_from_multiple_pages( + code42_sdk_mock, mocker +): + # Setup get all departing employees + page = MOCK_GET_ALL_DEPARTING_EMPLOYEES_RESPONSE + # Setup 3 pages of employees + employee_page_generator = ( + create_mock_code42_sdk_response(mocker, page) for page in [page, page, page] ) + code42_sdk_mock.detectionlists.departing_employee.get_all.return_value = employee_page_generator + client = create_client(code42_sdk_mock) + + _, _, res = departingemployee_get_all_command(client, {"username": "user1@example.com"}) + + # Expect to have employees from 3 pages in the result + expected_page = json.loads(MOCK_GET_ALL_DEPARTING_EMPLOYEES_RESPONSE)["items"] + expected = expected_page + expected_page + expected_page + assert res == expected + + +def test_highriskemployee_add_command(code42_sdk_mock): + client = create_client(code42_sdk_mock) _, _, res = highriskemployee_add_command( client, {"username": "user1@example.com", "note": "Dummy note"} ) @@ -843,19 +1016,63 @@ def test_highriskemployee_add_command(code42_sdk_mock): def test_highriskemployee_remove_command(code42_sdk_mock): - client = Code42Client( - sdk=code42_sdk_mock, base_url=MOCK_URL, auth=MOCK_AUTH, verify=False, proxy=None - ) + client = create_client(code42_sdk_mock) _, _, res = highriskemployee_remove_command(client, {"username": "user1@example.com"}) expected = "123412341234123412" # value found in GET_USER_RESPONSE assert res == expected code42_sdk_mock.detectionlists.high_risk_employee.remove.assert_called_once_with(expected) -def test_highriskemployee_add_risk_tags_command(code42_sdk_mock): - client = Code42Client( - sdk=code42_sdk_mock, base_url=MOCK_URL, auth=MOCK_AUTH, verify=False, proxy=None +def test_highriskemployee_get_all_command(code42_sdk_mock): + client = create_client(code42_sdk_mock) + _, _, res = highriskemployee_get_all_command(client, {}) + expected = json.loads(MOCK_GET_ALL_HIGH_RISK_EMPLOYEES_RESPONSE)["items"] + assert res == expected + assert code42_sdk_mock.detectionlists.high_risk_employee.get_all.call_count == 1 + + +def test_highriskemployee_get_all_command_gets_employees_from_multiple_pages( + code42_sdk_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_sdk_mock.detectionlists.high_risk_employee.get_all.return_value = employee_page_generator + client = create_client(code42_sdk_mock) + + _, _, res = highriskemployee_get_all_command(client, {"username": "user1@example.com"}) + + # 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 + + +def test_highriskemployee_get_all_command_when_given_risk_tags_only_gets_employees_with_tags( + code42_sdk_mock +): + client = create_client(code42_sdk_mock) + _, _, res = highriskemployee_get_all_command( + client, + { + "risktags": [ + "PERFORMANCE_CONCERNS", + "SUSPICIOUS_SYSTEM_ACTIVITY", + "POOR_SECURITY_PRACTICES", + ] + }, ) + # 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_sdk_mock.detectionlists.high_risk_employee.get_all.call_count == 1 + + +def test_highriskemployee_add_risk_tags_command(code42_sdk_mock): + client = create_client(code42_sdk_mock) _, _, res = highriskemployee_add_risk_tags_command( client, {"username": "user1@example.com", "risktags": "FLIGHT_RISK"} ) @@ -867,9 +1084,7 @@ def test_highriskemployee_add_risk_tags_command(code42_sdk_mock): def test_highriskemployee_remove_risk_tags_command(code42_sdk_mock): - client = Code42Client( - sdk=code42_sdk_mock, base_url=MOCK_URL, auth=MOCK_AUTH, verify=False, proxy=None - ) + client = create_client(code42_sdk_mock) _, _, res = highriskemployee_remove_risk_tags_command( client, {"username": "user1@example.com", "risktags": ["FLIGHT_RISK", "CONTRACT_EMPLOYEE"]} ) @@ -881,9 +1096,7 @@ def test_highriskemployee_remove_risk_tags_command(code42_sdk_mock): def test_security_data_search_command(code42_sdk_mock): - client = Code42Client( - sdk=code42_sdk_mock, base_url=MOCK_URL, auth=MOCK_AUTH, verify=False, proxy=None - ) + client = create_client(code42_sdk_mock) _, _, res = securitydata_search_command(client, MOCK_SECURITY_DATA_SEARCH_QUERY) assert len(res) == 3 actual_query = code42_sdk_mock.securitydata.search_file_events.call_args[0][0] @@ -899,9 +1112,7 @@ def test_security_data_search_command(code42_sdk_mock): def test_fetch_incidents_handles_single_severity(code42_sdk_mock): - client = Code42Client( - sdk=code42_sdk_mock, base_url=MOCK_URL, auth=MOCK_AUTH, verify=False, proxy=None - ) + client = create_client(code42_sdk_mock) fetch_incidents( client=client, last_run={"last_fetch": None}, @@ -915,9 +1126,7 @@ def test_fetch_incidents_handles_single_severity(code42_sdk_mock): def test_fetch_incidents_handles_multi_severity(code42_sdk_mock): - client = Code42Client( - sdk=code42_sdk_mock, base_url=MOCK_URL, auth=MOCK_AUTH, verify=False, proxy=None - ) + client = create_client(code42_sdk_mock) fetch_incidents( client=client, last_run={"last_fetch": None}, @@ -932,9 +1141,7 @@ def test_fetch_incidents_handles_multi_severity(code42_sdk_mock): def test_fetch_incidents_first_run(code42_sdk_mock): - client = Code42Client( - sdk=code42_sdk_mock, base_url=MOCK_URL, auth=MOCK_AUTH, verify=False, proxy=None - ) + client = create_client(code42_sdk_mock) next_run, incidents, remaining_incidents = fetch_incidents( client=client, last_run={"last_fetch": None}, @@ -951,9 +1158,7 @@ def test_fetch_incidents_first_run(code42_sdk_mock): def test_fetch_incidents_next_run(code42_sdk_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 = Code42Client( - sdk=code42_sdk_mock, base_url=MOCK_URL, auth=MOCK_AUTH, verify=False, proxy=None - ) + client = create_client(code42_sdk_mock) next_run, incidents, remaining_incidents = fetch_incidents( client=client, last_run={"last_fetch": mock_timestamp}, @@ -970,9 +1175,7 @@ def test_fetch_incidents_next_run(code42_sdk_mock): def test_fetch_incidents_fetch_limit(code42_sdk_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 = Code42Client( - sdk=code42_sdk_mock, base_url=MOCK_URL, auth=MOCK_AUTH, verify=False, proxy=None - ) + client = create_client(code42_sdk_mock) next_run, incidents, remaining_incidents = fetch_incidents( client=client, last_run={"last_fetch": mock_timestamp}, diff --git a/Packs/Code42/Integrations/Code42/integration-Code42.yml b/Packs/Code42/Integrations/Code42/integration-Code42.yml index e120566d544f..d0783cd53754 100644 --- a/Packs/Code42/Integrations/Code42/integration-Code42.yml +++ b/Packs/Code42/Integrations/Code42/integration-Code42.yml @@ -179,6 +179,7 @@ script: "DeviceUsername", ] + SECURITY_ALERT_HEADERS = ["Type", "Occurred", "Username", "Name", "Description", "State", "ID"] @@ -239,7 +240,8 @@ script: res = [] pages = self._sdk.detectionlists.departing_employee.get_all() for page in pages: - res.extend(page["items"]) + employees = page["items"] + res.extend(employees) except Exception: return None return res @@ -297,11 +299,9 @@ script: res.append(employee) continue - username = employee["userName"] - employee_tags = self._sdk.detectionlists.get_user(username)["riskFactors"] - + employee_tags = employee.get("riskFactors") # If the employee risk tags contain all the given risk tags - if set(risk_tags) <= set(employee_tags): + if employee_tags and set(risk_tags) <= set(employee_tags): res.append(employee) return res @@ -577,7 +577,7 @@ script: code42_context = map_to_code42_alert_context(alert) code42_securityalert_context.append(code42_context) readable_outputs = tableToMarkdown( - f"Code42 Security Alert Results", + "Code42 Security Alert Results", code42_securityalert_context, headers=SECURITY_ALERT_HEADERS, ) @@ -603,7 +603,7 @@ script: code42_context = map_to_code42_alert_context(alert_details) code42_security_alert_context.append(code42_context) readable_outputs = tableToMarkdown( - f"Code42 Security Alert Resolved", + "Code42 Security Alert Resolved", code42_security_alert_context, headers=SECURITY_ALERT_HEADERS, ) @@ -630,7 +630,7 @@ script: "DepartureDate": departing_date, "Note": note, } - readable_outputs = tableToMarkdown(f"Code42 Departing Employee List User Added", de_context) + readable_outputs = tableToMarkdown("Code42 Departing Employee List User Added", de_context) return readable_outputs, {"Code42.DepartingEmployee": de_context}, user_id @@ -642,7 +642,7 @@ script: if user_id: de_context = {"UserID": user_id, "Username": username} readable_outputs = tableToMarkdown( - f"Code42 Departing Employee List User Removed", de_context + "Code42 Departing Employee List User Removed", de_context ) return readable_outputs, {"Code42.DepartingEmployee": de_context}, user_id else: @@ -663,9 +663,7 @@ script: } for e in employees ] - readable_outputs = tableToMarkdown( - f"All Departing Employees", employees_context, headers=DEPARTING_EMPLOYEE_HEADERS - ) + readable_outputs = tableToMarkdown("All Departing Employees", employees_context) return ( readable_outputs, {"Code42.DepartingEmployee(val.UserID && val.UserID == obj.UserID)": employees_context}, @@ -685,7 +683,7 @@ script: return_error(message="Could not add user to the High Risk Employee List") hr_context = {"UserID": user_id, "Username": username} - readable_outputs = tableToMarkdown(f"Code42 High Risk Employee List User Added", hr_context) + readable_outputs = tableToMarkdown("Code42 High Risk Employee List User Added", hr_context) return readable_outputs, {"Code42.HighRiskEmployee": hr_context}, user_id @@ -697,7 +695,7 @@ script: if user_id: hr_context = {"UserID": user_id, "Username": username} readable_outputs = tableToMarkdown( - f"Code42 High Risk Employee List User Removed", hr_context + "Code42 High Risk Employee List User Removed", hr_context ) return readable_outputs, {"Code42.HighRiskEmployee": hr_context}, user_id else: @@ -714,7 +712,7 @@ script: {"UserID": e["userId"], "Username": e["userName"], "Note": e["notes"]} for e in employees ] - readable_outputs = tableToMarkdown(f"Retrieved All High Risk Employees", employees_context) + readable_outputs = tableToMarkdown("Retrieved All High Risk Employees", employees_context) return ( readable_outputs, {"Code42.HighRiskEmployee(val.UserID && val.UserID == obj.UserID)": employees_context}, @@ -732,7 +730,7 @@ script: user_id = client.add_user_risk_tags(username, tags) if user_id: rt_context = {"UserID": user_id, "Username": username, "RiskTags": tags} - readable_outputs = tableToMarkdown(f"Code42 Risk Tags Added", rt_context) + readable_outputs = tableToMarkdown("Code42 Risk Tags Added", rt_context) return readable_outputs, {"Code42.HighRiskEmployee": rt_context}, user_id else: return_error(message="Could not add user risk tags") @@ -746,7 +744,7 @@ script: user_id = client.remove_user_risk_tags(username, tags) if user_id: rt_context = {"UserID": user_id, "Username": username, "RiskTags": tags} - readable_outputs = tableToMarkdown(f"Code42 Risk Tags Removed", rt_context) + readable_outputs = tableToMarkdown("Code42 Risk Tags Removed", rt_context) return readable_outputs, {"Code42.HighRiskEmployee": rt_context}, user_id else: return_error(message="Could not remove user risk tags") @@ -801,7 +799,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: @@ -811,7 +809,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): @@ -890,7 +888,7 @@ script: file_context_event = map_to_file_context(file_event) file_context.append(file_context_event) readable_outputs = tableToMarkdown( - f"Code42 Security Data Results", + "Code42 Security Data Results", code42_security_data_context, headers=SECURITY_EVENT_HEADERS, ) From c8e30a1ba11991eef09ff26b5fad2b1eb7472637 Mon Sep 17 00:00:00 2001 From: Juliya Smith Date: Wed, 17 Jun 2020 14:03:38 +0000 Subject: [PATCH 07/17] Add changelog entry --- Packs/Code42/Integrations/Code42/CHANGELOG.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Packs/Code42/Integrations/Code42/CHANGELOG.md b/Packs/Code42/Integrations/Code42/CHANGELOG.md index c9cd92c44c86..1b2d74c8b7f9 100644 --- a/Packs/Code42/Integrations/Code42/CHANGELOG.md +++ b/Packs/Code42/Integrations/Code42/CHANGELOG.md @@ -1,4 +1,12 @@ ## [Unreleased] +Added new commands: + - **code42-departingemployee-get-all** that gets all the employees on the Departing Employee List. + - **code42-highriskemployee-add** that takes a username and adds the employee to the High Risk Employee List. + - **code42-highriskemployee-remove** that takes a username and remove the employee from the High Risk Employee List. + - **code42-highriskemployee-get-all** that gets all the employees on the High Risk Employee List. + Optionally takes a list of risk tags and only gets employees who have those risk tags. + - **code42-highriskemployee-add-risk-tags** that takes a username and risk tags and associates the risk tags with the user. + - **code42-highriskemployee-remove-risk-tags** that takes a username and risk tags and disassociates the risk tags from the user. ## [20.3.3] - 2020-03-18 From 527237b63ee9b072506f7c94e46e786b77d0f8d6 Mon Sep 17 00:00:00 2001 From: Juliya Smith Date: Wed, 17 Jun 2020 17:58:26 +0000 Subject: [PATCH 08/17] Typos --- Packs/Code42/Playbooks/playbook-Code42_File_Search.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Packs/Code42/Playbooks/playbook-Code42_File_Search.yml b/Packs/Code42/Playbooks/playbook-Code42_File_Search.yml index c2985a981f7a..50f5c72dfe40 100644 --- a/Packs/Code42/Playbooks/playbook-Code42_File_Search.yml +++ b/Packs/Code42/Playbooks/playbook-Code42_File_Search.yml @@ -296,7 +296,7 @@ outputs: - contextPath: Code42.SecurityData.EmailFrom description: Sender email address for email exfiltration events - contextPath: Code42.SecurityData.EmailTo - description: Recipient emial address for email exfiltration events + description: Recipient email address for email exfiltration events - contextPath: Code42.SecurityData.EmailSubject description: Email subject line for email exfiltration events - contextPath: Code42.SecurityData.EventID @@ -324,7 +324,7 @@ outputs: - contextPath: Code42.SecurityData.DevicePublicIPAddress description: Public IP address of device where event was captured - contextPath: Code42.SecurityData.RemovableMediaType - description: Type of removate media + description: Type of removable media - contextPath: Code42.SecurityData.RemovableMediaCapacity description: Total capacity of removable media in bytes - contextPath: Code42.SecurityData.RemovableMediaMediaName From 341291fafc2d3e43832c70b39fda59c0e55c6e6a Mon Sep 17 00:00:00 2001 From: Juliya Smith Date: Wed, 17 Jun 2020 18:55:51 +0000 Subject: [PATCH 09/17] Update README --- Packs/Code42/Integrations/Code42/README.md | 667 +++++++++++---------- 1 file changed, 342 insertions(+), 325 deletions(-) diff --git a/Packs/Code42/Integrations/Code42/README.md b/Packs/Code42/Integrations/Code42/README.md index f19d5044bfe4..e4b97388a261 100644 --- a/Packs/Code42/Integrations/Code42/README.md +++ b/Packs/Code42/Integrations/Code42/README.md @@ -10,9 +10,6 @@ Code42 provides simple, fast detection and response to everyday data loss from i This integration was integrated and tested with the fully-hosted SaaS implementation of Code42 and requires a Platinum level subscription. -## Code42 Playbook ---- - ## Use Cases --- @@ -20,425 +17,445 @@ This integration was integrated and tested with the fully-hosted SaaS implementa * Management of Departing Employees within Code42 * General file event and metadata search + ## Configure Code42 on Cortex XSOAR ---- -1. Navigate to __Settings__ > __Integrations__ > __Servers & Services__. +1. Navigate to **Settings** > **Integrations** > **Servers & Services**. 2. Search for Code42. -3. Click __Add instance__ to create and configure a new integration instance. - * __Name__: a textual name for the integration instance. - * __credentials__ - * __Code42 Console URL for the pod your Code42 instance is running in__: This defaults to console.us.code42.com for U.S. SaaS Pod customers; replace with the domain that you use to log into your Code42 console if located in a different SaaS pod. - * __Fetch incidents__: Check this box to enable fetching of incidents - * __Incident type__: Select which Cortex XSOAR incident type to map ingested Code42 alerts to - * __Alert severities to fetch when fetching incidents__: If desired, select which Alert severities to ingest. - * __First fetch time range (<number> <time unit>, e.g., 1 hour, 30 minutes)__: When first run, how long to go back to retrieve alerts. - * __Alerts to fetch per run; note that increasing this value may result in slow performance if too many results are returned at once__: Alerts to fetch and process per run. Setting this value too high may have a negative impact on performance. - * __Include the list of files in returned incidents.__: If checked, will also fetch the file events associated with the alert. -4. Click __Test__ to validate the URLs, token, and connection. - -## Fetched Incidents Data ---- - -* ID -* Occurred -* Username -* Name -* Description -* State -* Type -* Severity +3. Click **Add instance** to create and configure a new integration instance. +| **Parameter** | **Description** | **Required** | +| --- | --- | --- | +| console_url | Code42 Console URL for the pod your Code42 instance is running in | True | +| credentials | | True | +| isFetch | Fetch incidents | False | +| incidentType | Incident type | False | +| alert_severity | Alert severities to fetch when fetching incidents | False | +| fetch_time | First fetch time range \(<number> <time unit>, e.g., 1 hour, 30 minutes\) | False | +| fetch_limit | Alerts to fetch per run; note that increasing this value may result in slow performance if too many results are returned at once | False | +| include_files | Include the list of files in returned incidents. | False | + +4. Click **Test** to validate the URLs, token, and connection. ## Commands ---- You can execute these commands from the Demisto CLI, as part of an automation, or in a playbook. After you successfully execute a command, a DBot message appears in the War Room with the command details. +### code42-securitydata-search +*** +Searches for a file in Security Data by JSON query, hash, username, device hostname, exfiltration type, or a combination of parameters. At least one argument must be passed in the command. If a JSON argument is passed, it will be used to the exclusion of other parameters, otherwise parameters will be combined with an AND clause. -1. code42-securitydata-search -2. code42-alert-get -3. code42-departingemployee-add -4. code42-departingemployee-remove -5. code42-alert-resolve - -### 1. code42-securitydata-search ---- -Search for a file in Security Data by JSON query, hash, username, device hostname, exfiltration type, or a combination of parameters. At least one parameter must be passed to the command. If a JSON parameter is passed, it will be used to the exclusion of other parameters, otherwise parameters will be combined with an AND clause. -##### Required Permissions - -This command requires one of the following roles: - -* Security Center User -* Customer Cloud Admin -##### Base Command +#### Base Command `code42-securitydata-search` - -##### Input +#### Input | **Argument Name** | **Description** | **Required** | | --- | --- | --- | -| json | JSON query payload using Code42 query syntax | Optional | -| hash | MD5 or SHA256 hash of file to search for | Optional | -| username | Username to search for | Optional | -| hostname | Hostname to search for | Optional | -| exposure | Exposure types to search for | Optional | -| results | Number of results to return, default is 100 | Optional | +| json | JSON query payload using Code42 query syntax. | Optional | +| hash | MD5 or SHA256 hash of the file to search for. | Optional | +| username | Username to search for. | Optional | +| hostname | Hostname to search for. | Optional | +| exposure | Exposure types to search for. Can be "RemovableMedia", "ApplicationRead", "CloudStorage", "IsPublic", "SharedViaLink", or "SharedViaDomain". | Optional | +| results | The number of results to return. The default is 100. | Optional | -##### Context Output +#### Context Output | **Path** | **Type** | **Description** | | --- | --- | --- | -| Code42.SecurityData.EventTimestamp | date | Timestamp for event | -| Code42.SecurityData.FileCreated | date | File creation date | -| Code42.SecurityData.EndpointID | string | Code42 device ID | -| Code42.SecurityData.DeviceUsername | string | Username that device is associated with in Code42 | -| Code42.SecurityData.EmailFrom | string | Sender email address for email exfiltration events | -| Code42.SecurityData.EmailTo | string | Recipient emial address for email exfiltration events | -| Code42.SecurityData.EmailSubject | string | Email subject line for email exfiltration events | -| Code42.SecurityData.EventID | string | Security Data event ID | -| Code42.SecurityData.EventType | string | Type of Security Data event | -| Code42.SecurityData.FileCategory | string | Type of file as determined by Code42 engine | -| Code42.SecurityData.FileOwner | string | Owner of file | -| Code42.SecurityData.FileName | string | File name | -| Code42.SecurityData.FilePath | string | Path to file | -| Code42.SecurityData.FileSize | number | Size of file in bytes | -| Code42.SecurityData.FileModified | date | File modification date | -| Code42.SecurityData.FileMD5 | string | MD5 hash of file | -| Code42.SecurityData.FileHostname | string | Hostname where file event was captured | -| Code42.SecurityData.DevicePrivateIPAddress | string | Private IP addresses of device where event was captured | -| Code42.SecurityData.DevicePublicIPAddress | string | Public IP address of device where event was captured | -| Code42.SecurityData.RemovableMediaType | string | Type of removate media | -| Code42.SecurityData.RemovableMediaCapacity | number | Total capacity of removable media in bytes | -| Code42.SecurityData.RemovableMediaMediaName | string | Full name of removable media | -| Code42.SecurityData.RemovableMediaName | string | Name of removable media | -| Code42.SecurityData.RemovableMediaSerialNumber | string | Serial number for removable medial device | -| Code42.SecurityData.RemovableMediaVendor | string | Vendor name for removable device | -| Code42.SecurityData.FileSHA256 | string | SHA256 hash of file | -| Code42.SecurityData.FileShared | boolean | Whether file is shared using cloud file service | -| Code42.SecurityData.FileSharedWith | string | Accounts that file is shared with on cloud file service | -| Code42.SecurityData.Source | string | Source of file event, Cloud or Endpoint | -| Code42.SecurityData.ApplicationTabURL | string | URL associated with application read event | -| Code42.SecurityData.ProcessName | string | Process name for application read event | -| Code42.SecurityData.ProcessOwner | string | Process owner for application read event | -| Code42.SecurityData.WindowTitle | string | Process name for application read event | -| Code42.SecurityData.FileURL | string | URL of file on cloud file service | -| Code42.SecurityData.Exposure | string | Exposure type for event | -| Code42.SecurityData.SharingTypeAdded | string | Type of sharing added to file | -| File.Name | string | File name | -| File.Path | string | File path | -| File.Size | number | File size in bytes | -| File.MD5 | string | MD5 hash of file | -| File.SHA256 | string | FSHA256 hash of file | -| File.Hostname | string | Hostname where file event was captured | - - -##### Command Example -``` -!code42-securitydata-search hash=eef8b12d2ed0d6a69fe77699d5640c7b exposure=CloudStorage,ApplicationRead -``` - -##### Context Example -``` -{ - "SecurityData": [ - { - "ApplicationTabURL": "https://mail.google.com/mail/u/0/?zx=78517y156trj#inbox", - "DevicePrivateIPAddress": [ - "192.168.7.7", - "0:0:0:0:0:0:0:1", - "127.0.0.1" - ], - "DeviceUsername": "john.user@123.org", - "EndpointID": "922302903141234234", - "EventID": "0_c346c59b-5ea1-4e5d-ac02-92079567a683_922302903141255753_939560749717017940_751", - "EventTimestamp": "2020-02-03T22:32:10.892Z", - "EventType": "READ_BY_APP", - "Exposure": [ - "ApplicationRead" - ], - "FileCategory": "IMAGE", - "FileCreated": "2019-10-07T21:46:09.281Z", - "FileHostname": "DESKTOP-0004", - "FileMD5": "eef8b12d2ed0d6a69fe77699d5640c7b", - "FileModified": "2019-10-07T21:46:09.889Z", - "FileName": "ProductPhoto.jpg", - "FileOwner": "john.user", - "FilePath": "C:/Users/john.user/Documents/", - "FileSHA256": "5e25e54e1cc43ed07c6e888464cb98e5f5343aa7aa485d174d9649be780a17b9", - "FileSize": 333114, - "ProcessName": "\\Device\\HarddiskVolume4\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe", - "ProcessOwner": "john.user", - "Source": "Endpoint", - "WindowTitle": [ - "Inbox (1) - john.user@c123.org - 123 Org Mail - Google Chrome" - ] - }, - { - "DevicePrivateIPAddress": [ - "192.168.7.7", - "0:0:0:0:0:0:0:1", - "127.0.0.1" - ], - "DeviceUsername": "john.user@123.org", - "EndpointID": "922302903141234234", - "EventID": "0_a2e51c67-8719-4436-a3b5-c7c3724a3144_922302903141255753_939559658795324756_45", - "EventTimestamp": "2020-02-03T22:22:04.375Z", - "EventType": "READ_BY_APP", - "Exposure": [ - "ApplicationRead" - ], - "FileCategory": "IMAGE", - "FileCreated": "2019-10-07T21:46:09.281Z", - "FileHostname": "DESKTOP-0004", - "FileMD5": "eef8b12d2ed0d6a69fe77699d5640c7b", - "FileModified": "2019-10-07T21:46:09.889Z", - "FileName": "ProductPhoto.jpg", - "FileOwner": "john.user", - "FilePath": "C:/Users/john.user/Documents/", - "FileSHA256": "5e25e54e1cc43ed07c6e888464cb98e5f5343aa7aa485d174d9649be780a17b9", - "FileSize": 333114, - "ProcessName": "\\Device\\HarddiskVolume4\\Windows\\System32\\MicrosoftEdgeCP.exe", - "ProcessOwner": "michelle.goldberg", - "Source": "Endpoint", - "WindowTitle": [ - "Inbox (7) - jju12431983@gmail.com - Gmail ‎- Microsoft Edge" - ] - } - ] -} -``` - -##### Human Readable Output +| Code42.SecurityData.EventTimestamp | date | Timestamp for the event. | +| Code42.SecurityData.FileCreated | date | File creation date. | +| Code42.SecurityData.EndpointID | string | Code42 device ID. | +| Code42.SecurityData.DeviceUsername | string | The username that the device is associated with in Code42. | +| Code42.SecurityData.EmailFrom | string | The sender email address for email exfiltration events. | +| Code42.SecurityData.EmailTo | string | The recipient email address for email exfiltration events. | +| Code42.SecurityData.EmailSubject | string | The email subject line for email exfiltration events. | +| Code42.SecurityData.EventID | string | The Security Data event ID. | +| Code42.SecurityData.EventType | string | The type of Security Data event. | +| Code42.SecurityData.FileCategory | string | The file type, as determined by Code42 engine. | +| Code42.SecurityData.FileOwner | string | The owner of the file. | +| Code42.SecurityData.FileName | string | The file name. | +| Code42.SecurityData.FilePath | string | The path to file. | +| Code42.SecurityData.FileSize | number | The size of the file \(in bytes\). | +| Code42.SecurityData.FileModified | date | The date the file was last modified. | +| Code42.SecurityData.FileMD5 | string | MD5 hash of the file. | +| Code42.SecurityData.FileHostname | string | Hostname where the file event was captured. | +| Code42.SecurityData.DevicePrivateIPAddress | string | Private IP addresses of the device where the event was captured. | +| Code42.SecurityData.DevicePublicIPAddress | string | Public IP address of the device where the event was captured. | +| Code42.SecurityData.RemovableMediaType | string | Type of removable media. | +| Code42.SecurityData.RemovableMediaCapacity | number | Total capacity of removable media \(in bytes\). | +| Code42.SecurityData.RemovableMediaMediaName | string | The full name of the removable media. | +| Code42.SecurityData.RemovableMediaName | string | The name of the removable media. | +| Code42.SecurityData.RemovableMediaSerialNumber | string | The serial number for the removable medial device. | +| Code42.SecurityData.RemovableMediaVendor | string | The vendor name for removable device. | +| Code42.SecurityData.FileSHA256 | string | The SHA256 hash of the file. | +| Code42.SecurityData.FileShared | boolean | Whether the file is shared using a cloud file service. | +| Code42.SecurityData.FileSharedWith | string | Accounts that the file is shared with on a cloud file service. | +| Code42.SecurityData.Source | string | The source of the file event. Can be "Cloud" or "Endpoint". | +| Code42.SecurityData.ApplicationTabURL | string | The URL associated with the application read event. | +| Code42.SecurityData.ProcessName | string | The process name for the application read event. | +| Code42.SecurityData.ProcessOwner | string | The process owner for the application read event. | +| Code42.SecurityData.WindowTitle | string | The process name for the application read event. | +| Code42.SecurityData.FileURL | string | The URL of the file on a cloud file service. | +| Code42.SecurityData.Exposure | string | The event exposure type. | +| Code42.SecurityData.SharingTypeAdded | string | The type of sharing added to the file. | +| File.Name | string | The file name. | +| File.Path | string | The file path. | +| File.Size | number | The file size \(in bytes\). | +| File.MD5 | string | The MD5 hash of the file. | +| File.SHA256 | string | The SHA256 hash of the file. | +| File.Hostname | string | The hostname where the file event was captured. | + + +#### Command Example +```!code42-securitydata-search hash=eef8b12d2ed0d6a69fe77699d5640c7b exposure=CloudStorage,ApplicationRead``` + +#### Human Readable Output | **EventType** | **FileName** | **FileSize** | **FileHostname** | **FileOwner** | **FileCategory** | | --- | --- | --- | --- | --- | --- | | READ\_BY\_APP | ProductPhoto.jpg | 333114 | DESKTOP-001 | john.user | IMAGE | -### 2. code42-alert-get ---- +### code42-alert-get +*** Retrieve alert details by alert ID -##### Required Permissions -This command requires one of the following roles: -* Security Center User -* Customer Cloud Admin - -##### Base Command +#### Base Command `code42-alert-get` - -##### Input +#### Input | **Argument Name** | **Description** | **Required** | | --- | --- | --- | -| id | Alert ID to retrieve | Required | +| id | The alert ID to retrieve. Alert IDs are associated with alerts that are fetched via fetch-incidents. | Required | -##### Context Output +#### Context Output | **Path** | **Type** | **Description** | | --- | --- | --- | -| Code42.SecurityAlert.Username | string | Username associated with alert | -| Code42.SecurityAlert.Occurred | date | Alert timestamp | -| Code42.SecurityAlert.Description | string | Description of alert | -| Code42.SecurityAlert.ID | string | Alert ID | -| Code42.SecurityAlert.Name | string | Alert rule name that generated alert | -| Code42.SecurityAlert.State | string | Alert state | -| Code42.SecurityAlert.Type | string | Type of alert | -| Code42.SecurityAlert.Severity | string | Severity of alert | - - -##### Command Example -``` -!code42-alert-get id="a23557a7-8ca9-4ec6-803f-6a46a2aeca62" -``` - -##### Context Example -``` -{ - "SecurityAlert": [ - { - "ID": "a23557a7-8ca9-4ec6-803f-6a46a2aeca62", - "Name": "Google Drive - Public via Direct Link", - "Occurred": "2019-10-08T17:38:19.0801650Z", - "Severity": "LOW", - "State": "OPEN", - "Type": "FED_CLOUD_SHARE_PERMISSIONS", - "Username": "john.user@123.org" - } - ] -} -``` - -##### Human Readable Output +| Code42.SecurityAlert.Username | string | The username associated with the alert. | +| Code42.SecurityAlert.Occurred | date | The timestamp when the alert occurred. | +| Code42.SecurityAlert.Description | string | The description of the alert. | +| Code42.SecurityAlert.ID | string | The alert ID. | +| Code42.SecurityAlert.Name | string | The alert rule name that generated the alert. | +| Code42.SecurityAlert.State | string | The alert state. | +| Code42.SecurityAlert.Type | string | The alert type. | +| Code42.SecurityAlert.Severity | string | The severity of the alert. | + + +#### Command Example +```!code42-alert-get id="a23557a7-8ca9-4ec6-803f-6a46a2aeca62"``` + +#### Human Readable Output | **Type** | **Occurred** | **Username** | **Name** | **Description** | **State** | **ID** | | --- | --- | --- | --- | --- | --- | --- | | FED\_CLOUD\_SHARE_PERMISSIONS | 2019-10-08T17:38:19.0801650Z | john.user@123.org | Google Drive - Public via Direct Link | Alert for public Google Drive files | OPEN | a23557a7-8ca9-4ec6-803f-6a46a2aeca62 | -### 3. code42-departingemployee-add ---- -Add a user to the Departing Employee List -##### Required Permissions +### code42-alert-resolve +*** +Resolves a Code42 Security alert. -This command requires one of the following roles: - -* Customer Cloud Admin -* Security Center User + (Org Security Viewer or Cross Org Security Viewer) -##### Base Command +#### Base Command + +`code42-alert-resolve` +#### Input + +| **Argument Name** | **Description** | **Required** | +| --- | --- | --- | +| id | The alert ID to resolve. Alert IDs are associated with alerts that are fetched via fetch-incidents. | Required | + + +#### Context Output + +| **Path** | **Type** | **Description** | +| --- | --- | --- | +| Code42.SecurityAlert.ID | string | The alert ID of the resolved alert. | + + +#### Command Example +```!code42-alert-resolve id="eb272d18-bc82-4680-b570-ac5d61c6cca6"``` + +#### Human Readable Output + +| **ID** | +| --- | +| eb272d18-bc82-4680-b570-ac5d61c6cca6 | + + +### code42-departingemployee-add +*** +Adds a user to the Departing Employee List. + + +#### Base Command `code42-departingemployee-add` -##### Input +#### Input | **Argument Name** | **Description** | **Required** | | --- | --- | --- | -| username | Username to add to the Departing Employee List | Required | -| departuredate | Departure date for the employee in yyyy-MM-dd format | Optional | -| note | Note to attach to Departing Employee | Optional | +| username | The username to add to the Departing Employee List. | Required | +| departuredate | The departure date for the employee, in the format YYYY-MM-DD. | Optional | +| note | Note to attach to the Departing Employee. | Optional | -##### Context Output +#### Context Output | **Path** | **Type** | **Description** | | --- | --- | --- | -| Code42.DepartingEmployee.UserID | string | Internal Code42 Case ID for Departing Employee | -| Code42.DepartingEmployee.Username | string | Username for Departing Employee | -| Code42.DepartingEmployee.Note | string | Note associated with Departing Employee | -| Code42.DepartingEmployee.DepartureDate | unknown | Departure date for Departing Employee | - - -##### Command Example -``` -!code42-departingemployee-add username="john.user@123.org" departuredate="2020-02-28" note="Leaving for competitor" -``` - -##### Context Example -``` -{ - "DepartingEmployee": { - "UserID": "892", - "DepartureDate": "2020-02-28", - "Note": "Leaving for competitor", - "Username": "john.user@123.org" - } -} -``` - -##### Human Readable Output +| Code42.DepartingEmployee.UserID | string | Internal Code42 User ID for the Departing Employee. | +| Code42.DepartingEmployee.Username | string | The username of the Departing Employee. | +| Code42.DepartingEmployee.Note | string | Note associated with the Departing Employee. | +| Code42.DepartingEmployee.DepartureDate | unknown | The departure date for the Departing Employee. | + + +#### Command Example +```!code42-departingemployee-add username="john.user@123.org" departuredate="2020-02-28" note="Leaving for competitor"``` + +#### Human Readable Output | **UserID** | **DepartureDate** | **Note** | **Username** | | --- | --- | --- | --- | -| 123 | 2020-02-28 | Leaving for competitor | john.user@123.org | +| 123 | 2020-02-28 | Leaving for competitor | john.user@example.com | -### 4. code42-departingemployee-remove ---- -Remove a user from the Departing Employee List -##### Required Permissions - -This command requires one of the following roles: +### code42-departingemployee-remove +*** +Removes a user from the Departing Employee List. -* Customer Cloud Admin -* Security Center User + (Org Security Viewer or Cross Org Security Viewer) -##### Base Command +#### Base Command `code42-departingemployee-remove` -##### Input +#### Input | **Argument Name** | **Description** | **Required** | | --- | --- | --- | -| username | Username to remove from the Departing Employee List | Optional | +| username | The username to remove from the Departing Employee List. | Required | -##### Context Output +#### Context Output | **Path** | **Type** | **Description** | | --- | --- | --- | -| Code42.DepartingEmployee.UserID | unknown | Internal Code42 User ID for Departing Employee | -| Code42.DepartingEmployee.Username | unknown | Username for Departing Employee | - +| Code42.DepartingEmployee.UserID | string | Internal Code42 User ID for the Departing Employee. | +| Code42.DepartingEmployee.Username | string | The username of the Departing Employee. | -##### Command Example -``` -!code42-departingemployee-remove username="john.user@123.org" -``` -##### Context Example -``` -{ - "DepartingEmployee": { - "UserID": "892", - "Username": "john.user@123.org" - } -} -``` +#### Command Example +```!code42-departingemployee-remove username="john.user@example.com"``` -##### Human Readable Output +#### Human Readable Output | **UserID** | **Username** | | --- | --- | -| 123 | john.user@123.org | +| 123 | john.user@example.com | -### 5. code42-alert-resolve ---- -Resolve a Code42 Security alert -##### Required Permissions -This command requires one of the following roles: +### code42-departingemployee-get-all +*** +Get all employees on the Departing Employee List. -* Security Center User -* Customer Cloud Admin -##### Base Command +#### Base Command -`code42-alert-resolve` -##### Input +`code42-departingemployee-get-all` +#### Input + +There are no input arguments for this command. + +#### Context Output + +| **Path** | **Type** | **Description** | +| --- | --- | --- | +| Code42.DepartingEmployee.UserID | string | Internal Code42 User ID for the Departing Employee. | +| Code42.DepartingEmployee.Username | string | The username of the Departing Employee. | +| Code42.DepartingEmployee.Note | string | Note associated with the Departing Employee. | +| Code42.DepartingEmployee.DepartureDate | unknown | The departure date for the Departing Employee. | + + +#### Command Example +```!code42-departingemployee-get-all``` + +#### Human Readable Output + +| **UserID** | **DepartureDate** | **Note** | **Username** | +| --- | --- | --- | --- | +| 123 | 2020-02-28 | Leaving for competitor | john.user@example.com | + + +### code42-highriskemployee-add +*** +Removes a user from the High Risk Employee List. + + +#### Base Command + +`code42-highriskemployee-add` +#### Input | **Argument Name** | **Description** | **Required** | | --- | --- | --- | -| id | Alert ID to resolve | Required | +| username | The username to add to the High Risk Employee List. | Required | +| note | Note to attach to the High Risk Employee. | Optional | -##### Context Output +#### Context Output | **Path** | **Type** | **Description** | | --- | --- | --- | -| Code42.SecurityAlert.ID | string | Alert ID | +| Code42.HighRiskEmployee.UserID | string | Internal Code42 User ID for the High Risk Employee. | +| Code42.HighRiskEmployee.Username | string | The username of the High Risk Employee. | +| Code42.HighRiskEmployee.Note | string | Note associated with the High Risk Employee. | -##### Command Example -``` -!code42-alert-resolve id="eb272d18-bc82-4680-b570-ac5d61c6cca6" -``` +#### Command Example +```!code42-highriskemployee-add username="john.user@123.org" note="Risky activity"``` -##### Context Example -``` -{ - "SecurityAlert": { - "ID": "eb272d18-bc82-4680-b570-ac5d61c6cca6" - } -} -``` +#### Human Readable Output -##### Human Readable Output +| **UserID** | **Note** | **Username** | +| --- | --- | --- | +| 123 | Leaving for competitor | john.user@example.com | -| **ID** | -| --- | -| eb272d18-bc82-4680-b570-ac5d61c6cca6 | -## Additional Information ---- -For additional information on Code42 features and functionality please visit [https://support.code42.com/Administrator/Cloud/Monitoring\_and\_managing](https://support.code42.com/Administrator/Cloud/Monitoring_and_managing) +### code42-highriskemployee-remove +*** +Removes a user from the High Risk Employee List. -## Known Limitations ---- -## Troubleshooting ---- +#### Base Command + +`code42-highriskemployee-remove` +#### Input + +| **Argument Name** | **Description** | **Required** | +| --- | --- | --- | +| username | The username to remove from the High Risk Employee List. | Required | + + +#### Context Output + +| **Path** | **Type** | **Description** | +| --- | --- | --- | +| Code42.HighRiskEmployee.UserID | unknown | Internal Code42 User ID for the High Risk Employee. | +| Code42.HighRiskEmployee.Username | unknown | The username of the High Risk Employee. | + + +#### Command Example +```!code42-highriskemployee-remove username="john.user@example.com"``` + +#### Human Readable Output + +| **UserID** | **Username** | +| --- | --- | +| 123 | john.user@example.com | + + +### code42-highriskemployee-get-all +*** +Get all employees on the High Risk Employee List. + + +#### Base Command + +`code42-highriskemployee-get-all` +#### Input + +| **Argument Name** | **Description** | **Required** | +| --- | --- | --- | +| risktags | To filter results by employees who have these risk tags. | Optional | + + +#### Context Output + +| **Path** | **Type** | **Description** | +| --- | --- | --- | +| Code42.HighRiskEmployee.UserID | string | Internal Code42 User ID for the High Risk Employee. | +| Code42.HighRiskEmployee.Username | string | The username of the High Risk Employee. | +| Code42.HighRiskEmployee.Note | string | Note associated with the High Risk Employee. | + + +#### Command Example +```!code42-highriskemployee-get-all risktags="PERFORMANCE_CONCERNS"``` + +#### Human Readable Output + +| **UserID** | **Note** | **Username** | +| --- | --- | --- | +| 123 | Leaving for competitor | john.user@example.com | + + +### code42-highriskemployee-add-risk-tags +*** + + +#### Base Command + +`code42-highriskemployee-add-risk-tags` +#### Input + +| **Argument Name** | **Description** | **Required** | +| --- | --- | --- | +| username | The username of the High Risk Employee. | Required | +| risktags | Risk tags to associate with the High Risk Employee. | Required | + + +#### Context Output + +| **Path** | **Type** | **Description** | +| --- | --- | --- | +| Code42.HighRiskEmployee.UserID | string | Internal Code42 User ID for the Departing Employee. | +| Code42.HighRiskEmployee.Username | string | The username of the High Risk Employee. | +| Code42.HighRiskEmployee.RiskTags | unknown | Risk tags to asspciate with the High Risk Employee. | + + +#### Command Example +```!code42-highriskemployee-add-risk-tags username="john.user@example.com" risktags="PERFORMANCE_CONCERNS"``` + +#### Human Readable Output + +| **UserID** | **RiskTags** | **Username** | +| --- | --- | --- | +| 123 | FLIGHT_RISK | john.user@example.com | + + +### code42-highriskemployee-remove-risk-tags +*** + + + +#### Base Command + +`code42-highriskemployee-remove-risk-tags` +#### Input + +| **Argument Name** | **Description** | **Required** | +| --- | --- | --- | +| username | The username of the High Risk Employee. | Required | +| risktags | Risk tags to disassociate from the High Risk Employee. | Required | + + +#### Context Output + +| **Path** | **Type** | **Description** | +| --- | --- | --- | +| Code42.HighRiskEmployee.UserID | string | Internal Code42 User ID for the Departing Employee. | +| Code42.HighRiskEmployee.Username | string | The username of the High Risk Employee. | +| Code42.HighRiskEmployee.RiskTags | unknown | Risk tags to disassociate from the High Risk Employee. | + + +#### Command Example +```!code42-highriskemployee-remove-risk-tags username="john.user@example.com" risktags="PERFORMANCE_CONCERNS"``` + +#### Human Readable Output + +| **UserID** | **RiskTags** | **Username** | +| --- | --- | --- | +| 123 | FLIGHT_RISK | john.user@example.com | From f1830a11993eb1bfab6fa53333174bb03492c5fe Mon Sep 17 00:00:00 2001 From: Juliya Smith Date: Wed, 17 Jun 2020 19:19:41 +0000 Subject: [PATCH 10/17] Create decorator for returning none on error --- Packs/Code42/Integrations/Code42/Code42.py | 156 ++++++++---------- .../Code42/Integrations/Code42/Code42_test.py | 9 + .../Code42/integration-Code42.yml | 156 ++++++++---------- 3 files changed, 149 insertions(+), 172 deletions(-) diff --git a/Packs/Code42/Integrations/Code42/Code42.py b/Packs/Code42/Integrations/Code42/Code42.py index 4ba71f9ad47e..f644eca789f9 100644 --- a/Packs/Code42/Integrations/Code42/Code42.py +++ b/Packs/Code42/Integrations/Code42/Code42.py @@ -134,6 +134,31 @@ def _create_alert_query(event_severity_filter, start_time): return alert_query +def return_none_on_error(func): + @logger + def wrapper(*args, **kwargs): + try: + return func(*args, **kwargs) + except Exception as ex: + LOG(str(ex)) + return None + return wrapper + + +def _get_all_high_risk_employees_from_page(page, risk_tags): + res = [] + for employee in page["items"]: + if not risk_tags: + res.append(employee) + continue + + employee_tags = employee.get("riskFactors") + # If the employee risk tags contain all the given risk tags + if employee_tags and set(risk_tags) <= set(employee_tags): + res.append(employee) + return res + + class Code42Client(BaseClient): """ Client will implement the service API, should not contain Cortex XSOAR logic. @@ -146,135 +171,94 @@ def __init__(self, sdk, base_url, auth, verify=True, proxy=False): self._sdk = sdk or py42.sdk.from_local_account(base_url, auth[0], auth[1]) py42.settings.set_user_agent_suffix("Cortex XSOAR") + @return_none_on_error def add_user_to_departing_employee(self, username, departure_date=None, note=None): - try: - user_id = self.get_user_id(username) - self._sdk.detectionlists.departing_employee.add(user_id, departure_date=departure_date) - if note: - self._sdk.detectionlists.update_user_notes(user_id, note) - except Exception: - return None + user_id = self.get_user_id(username) + self._sdk.detectionlists.departing_employee.add(user_id, departure_date=departure_date) + if note: + self._sdk.detectionlists.update_user_notes(user_id, note) return user_id + @return_none_on_error def remove_user_from_departing_employee(self, username): - try: - user_id = self.get_user_id(username) - self._sdk.detectionlists.departing_employee.remove(user_id) - except Exception: - return None + user_id = self.get_user_id(username) + self._sdk.detectionlists.departing_employee.remove(user_id) return user_id + @return_none_on_error def get_all_departing_employees(self): - try: - res = [] - pages = self._sdk.detectionlists.departing_employee.get_all() - for page in pages: - employees = page["items"] - res.extend(employees) - except Exception: - return None + res = [] + pages = self._sdk.detectionlists.departing_employee.get_all() + for page in pages: + employees = page["items"] + res.extend(employees) return res + @return_none_on_error def add_user_to_high_risk_employee(self, username, note=None): - try: - user_id = self.get_user_id(username) - self._sdk.detectionlists.high_risk_employee.add(user_id) - if note: - self._sdk.detectionlists.update_user_notes(user_id, note) - except Exception: - return None + user_id = self.get_user_id(username) + self._sdk.detectionlists.high_risk_employee.add(user_id) + if note: + self._sdk.detectionlists.update_user_notes(user_id, note) return user_id + @return_none_on_error def remove_user_from_high_risk_employee(self, username): - try: - user_id = self.get_user_id(username) - self._sdk.detectionlists.high_risk_employee.remove(user_id) - except Exception: - return None + user_id = self.get_user_id(username) + self._sdk.detectionlists.high_risk_employee.remove(user_id) return user_id + @return_none_on_error def add_user_risk_tags(self, username, risk_tags): - try: - user_id = self.get_user_id(username) - self._sdk.detectionlists.add_user_risk_tags(user_id, risk_tags) - except Exception: - return None + user_id = self.get_user_id(username) + self._sdk.detectionlists.add_user_risk_tags(user_id, risk_tags) return user_id + @return_none_on_error def remove_user_risk_tags(self, username, risk_tags): - try: - user_id = self.get_user_id(username) - self._sdk.detectionlists.remove_user_risk_tags(user_id, risk_tags) - except Exception: - return None + user_id = self.get_user_id(username) + self._sdk.detectionlists.remove_user_risk_tags(user_id, risk_tags) return user_id + @return_none_on_error def get_all_high_risk_employees(self, risk_tags=None): if isinstance(risk_tags, str): risk_tags = [risk_tags] - try: - res = [] - pages = self._sdk.detectionlists.high_risk_employee.get_all() - for page in pages: - res.extend(self._get_all_high_risk_employees_from_page(page, risk_tags)) - except Exception: - return None - return res - - def _get_all_high_risk_employees_from_page(self, page, risk_tags): res = [] - for employee in page["items"]: - if not risk_tags: - res.append(employee) - continue - - employee_tags = employee.get("riskFactors") - # If the employee risk tags contain all the given risk tags - if employee_tags and set(risk_tags) <= set(employee_tags): - res.append(employee) + pages = self._sdk.detectionlists.high_risk_employee.get_all() + for page in pages: + res.extend(_get_all_high_risk_employees_from_page(page, risk_tags)) return res + @return_none_on_error def fetch_alerts(self, start_time, event_severity_filter): - try: - query = _create_alert_query(event_severity_filter, start_time) - res = self._sdk.alerts.search(query) - except Exception: - return None + query = _create_alert_query(event_severity_filter, start_time) + res = self._sdk.alerts.search(query) return res["alerts"] + @return_none_on_error def get_alert_details(self, alert_id): - try: - res = self._sdk.alerts.get_details(alert_id) - except Exception: - return None + res = self._sdk.alerts.get_details(alert_id) return res["alerts"][0] + @return_none_on_error def resolve_alert(self, id): - try: - self._sdk.alerts.resolve(id) - except Exception: - return None + self._sdk.alerts.resolve(id) return id + @return_none_on_error def get_current_user(self): - try: - res = self._sdk.users.get_current() - except Exception: - return None + res = self._sdk.users.get_current() return res + @return_none_on_error def get_user_id(self, username): - try: - res = self._sdk.users.get_by_username(username) - except Exception: - return None + res = self._sdk.users.get_by_username(username) return res["users"][0]["userUid"] + @return_none_on_error def search_file_events(self, payload): - try: - res = self._sdk.securitydata.search_file_events(payload) - except Exception: - return None + res = self._sdk.securitydata.search_file_events(payload) return res["fileEvents"] diff --git a/Packs/Code42/Integrations/Code42/Code42_test.py b/Packs/Code42/Integrations/Code42/Code42_test.py index ce2f9f8e29da..720431dfcd0f 100644 --- a/Packs/Code42/Integrations/Code42/Code42_test.py +++ b/Packs/Code42/Integrations/Code42/Code42_test.py @@ -896,6 +896,15 @@ def create_client(sdk): """TESTS""" +def test_code42client_when_error_occurs_during_method_returns_none(code42_sdk_mock): + def error_effect(): + raise Exception("Test Error") + code42_sdk_mock.detectionlists.departing_employee.add.side_effect = error_effect + client = create_client(code42_sdk_mock) + res = client.add_user_to_departing_employee("test") + assert res is None + + def test_build_query_payload(): query = build_query_payload(MOCK_SECURITY_DATA_SEARCH_QUERY) assert query.sort_key == MOCK_FILE_EVENT_QUERY_PAYLOAD["srtKey"] diff --git a/Packs/Code42/Integrations/Code42/integration-Code42.yml b/Packs/Code42/Integrations/Code42/integration-Code42.yml index d0783cd53754..011603c99e84 100644 --- a/Packs/Code42/Integrations/Code42/integration-Code42.yml +++ b/Packs/Code42/Integrations/Code42/integration-Code42.yml @@ -205,6 +205,31 @@ script: return alert_query + def return_none_on_error(func): + @logger + def wrapper(*args, **kwargs): + try: + return func(*args, **kwargs) + except Exception as ex: + LOG(str(ex)) + return None + return wrapper + + + def _get_all_high_risk_employees_from_page(page, risk_tags): + res = [] + for employee in page["items"]: + if not risk_tags: + res.append(employee) + continue + + employee_tags = employee.get("riskFactors") + # If the employee risk tags contain all the given risk tags + if employee_tags and set(risk_tags) <= set(employee_tags): + res.append(employee) + return res + + class Code42Client(BaseClient): """ Client will implement the service API, should not contain Cortex XSOAR logic. @@ -217,135 +242,94 @@ script: self._sdk = sdk or py42.sdk.from_local_account(base_url, auth[0], auth[1]) py42.settings.set_user_agent_suffix("Cortex XSOAR") + @return_none_on_error def add_user_to_departing_employee(self, username, departure_date=None, note=None): - try: - user_id = self.get_user_id(username) - self._sdk.detectionlists.departing_employee.add(user_id, departure_date=departure_date) - if note: - self._sdk.detectionlists.update_user_notes(user_id, note) - except Exception: - return None + user_id = self.get_user_id(username) + self._sdk.detectionlists.departing_employee.add(user_id, departure_date=departure_date) + if note: + self._sdk.detectionlists.update_user_notes(user_id, note) return user_id + @return_none_on_error def remove_user_from_departing_employee(self, username): - try: - user_id = self.get_user_id(username) - self._sdk.detectionlists.departing_employee.remove(user_id) - except Exception: - return None + user_id = self.get_user_id(username) + self._sdk.detectionlists.departing_employee.remove(user_id) return user_id + @return_none_on_error def get_all_departing_employees(self): - try: - res = [] - pages = self._sdk.detectionlists.departing_employee.get_all() - for page in pages: - employees = page["items"] - res.extend(employees) - except Exception: - return None + res = [] + pages = self._sdk.detectionlists.departing_employee.get_all() + for page in pages: + employees = page["items"] + res.extend(employees) return res + @return_none_on_error def add_user_to_high_risk_employee(self, username, note=None): - try: - user_id = self.get_user_id(username) - self._sdk.detectionlists.high_risk_employee.add(user_id) - if note: - self._sdk.detectionlists.update_user_notes(user_id, note) - except Exception: - return None + user_id = self.get_user_id(username) + self._sdk.detectionlists.high_risk_employee.add(user_id) + if note: + self._sdk.detectionlists.update_user_notes(user_id, note) return user_id + @return_none_on_error def remove_user_from_high_risk_employee(self, username): - try: - user_id = self.get_user_id(username) - self._sdk.detectionlists.high_risk_employee.remove(user_id) - except Exception: - return None + user_id = self.get_user_id(username) + self._sdk.detectionlists.high_risk_employee.remove(user_id) return user_id + @return_none_on_error def add_user_risk_tags(self, username, risk_tags): - try: - user_id = self.get_user_id(username) - self._sdk.detectionlists.add_user_risk_tags(user_id, risk_tags) - except Exception: - return None + user_id = self.get_user_id(username) + self._sdk.detectionlists.add_user_risk_tags(user_id, risk_tags) return user_id + @return_none_on_error def remove_user_risk_tags(self, username, risk_tags): - try: - user_id = self.get_user_id(username) - self._sdk.detectionlists.remove_user_risk_tags(user_id, risk_tags) - except Exception: - return None + user_id = self.get_user_id(username) + self._sdk.detectionlists.remove_user_risk_tags(user_id, risk_tags) return user_id + @return_none_on_error def get_all_high_risk_employees(self, risk_tags=None): if isinstance(risk_tags, str): risk_tags = [risk_tags] - try: - res = [] - pages = self._sdk.detectionlists.high_risk_employee.get_all() - for page in pages: - res.extend(self._get_all_high_risk_employees_from_page(page, risk_tags)) - except Exception: - return None - return res - - def _get_all_high_risk_employees_from_page(self, page, risk_tags): res = [] - for employee in page["items"]: - if not risk_tags: - res.append(employee) - continue - - employee_tags = employee.get("riskFactors") - # If the employee risk tags contain all the given risk tags - if employee_tags and set(risk_tags) <= set(employee_tags): - res.append(employee) + pages = self._sdk.detectionlists.high_risk_employee.get_all() + for page in pages: + res.extend(_get_all_high_risk_employees_from_page(page, risk_tags)) return res + @return_none_on_error def fetch_alerts(self, start_time, event_severity_filter): - try: - query = _create_alert_query(event_severity_filter, start_time) - res = self._sdk.alerts.search(query) - except Exception: - return None + query = _create_alert_query(event_severity_filter, start_time) + res = self._sdk.alerts.search(query) return res["alerts"] + @return_none_on_error def get_alert_details(self, alert_id): - try: - res = self._sdk.alerts.get_details(alert_id) - except Exception: - return None + res = self._sdk.alerts.get_details(alert_id) return res["alerts"][0] + @return_none_on_error def resolve_alert(self, id): - try: - self._sdk.alerts.resolve(id) - except Exception: - return None + self._sdk.alerts.resolve(id) return id + @return_none_on_error def get_current_user(self): - try: - res = self._sdk.users.get_current() - except Exception: - return None + res = self._sdk.users.get_current() return res + @return_none_on_error def get_user_id(self, username): - try: - res = self._sdk.users.get_by_username(username) - except Exception: - return None + res = self._sdk.users.get_by_username(username) return res["users"][0]["userUid"] + @return_none_on_error def search_file_events(self, payload): - try: - res = self._sdk.securitydata.search_file_events(payload) - except Exception: - return None + res = self._sdk.securitydata.search_file_events(payload) return res["fileEvents"] From f621b34d070a989d738cb66bc824b97f914555c6 Mon Sep 17 00:00:00 2001 From: Juliya Smith Date: Wed, 17 Jun 2020 19:49:32 +0000 Subject: [PATCH 11/17] gen playbook readmes --- Packs/Code42/Integrations/Code42/Code42.py | 4 +- .../Code42/Integrations/Code42/Code42_test.py | 51 +++++++++++++++++++ 2 files changed, 53 insertions(+), 2 deletions(-) diff --git a/Packs/Code42/Integrations/Code42/Code42.py b/Packs/Code42/Integrations/Code42/Code42.py index f644eca789f9..00574be13510 100644 --- a/Packs/Code42/Integrations/Code42/Code42.py +++ b/Packs/Code42/Integrations/Code42/Code42.py @@ -555,7 +555,7 @@ def departingemployee_remove_command(client, args): @logger def departingemployee_get_all_command(client, args): employees = client.get_all_departing_employees() - if employees: + if employees is not None: employees_context = [ { "UserID": e["userId"], @@ -606,7 +606,7 @@ def highriskemployee_remove_command(client, args): def highriskemployee_get_all_command(client, args): tags = args.get("risktags") employees = client.get_all_high_risk_employees(tags) - if employees: + if employees is not None: employees_context = [ {"UserID": e["userId"], "Username": e["userName"], "Note": e["notes"]} for e in employees diff --git a/Packs/Code42/Integrations/Code42/Code42_test.py b/Packs/Code42/Integrations/Code42/Code42_test.py index 720431dfcd0f..bf286fb7e745 100644 --- a/Packs/Code42/Integrations/Code42/Code42_test.py +++ b/Packs/Code42/Integrations/Code42/Code42_test.py @@ -893,6 +893,13 @@ def create_client(sdk): return Code42Client(sdk=sdk, base_url=MOCK_URL, auth=MOCK_AUTH, verify=False, proxy=None) +def get_empty_detectionlist_response(mocker, base_text): + no_employees_response_text = json.loads(base_text) + no_employees_response_text["items"] = [] + no_employees_response_text = json.dumps(no_employees_response_text) + return create_mock_code42_sdk_response_generator(mocker, [no_employees_response_text]) + + """TESTS""" @@ -1011,6 +1018,28 @@ def test_departingemployee_get_all_command_gets_employees_from_multiple_pages( assert res == expected +def test_departingemployee_get_all_command_when_no_employees( + code42_sdk_mock, mocker +): + no_employees_response = get_empty_detectionlist_response(mocker, MOCK_GET_ALL_DEPARTING_EMPLOYEES_RESPONSE) + code42_sdk_mock.detectionlists.high_risk_employee.get_all.return_value = no_employees_response + client = create_client(code42_sdk_mock) + _, _, res = highriskemployee_get_all_command( + client, + { + "risktags": [ + "PERFORMANCE_CONCERNS", + "SUSPICIOUS_SYSTEM_ACTIVITY", + "POOR_SECURITY_PRACTICES", + ] + }, + ) + # Only first employee has the given risk tags + expected = [] + assert res == expected + assert code42_sdk_mock.detectionlists.high_risk_employee.get_all.call_count == 1 + + def test_highriskemployee_add_command(code42_sdk_mock): client = create_client(code42_sdk_mock) _, _, res = highriskemployee_add_command( @@ -1080,6 +1109,28 @@ def test_highriskemployee_get_all_command_when_given_risk_tags_only_gets_employe assert code42_sdk_mock.detectionlists.high_risk_employee.get_all.call_count == 1 +def test_highriskemployee_get_all_command_when_no_employees( + code42_sdk_mock, mocker +): + no_employees_response = get_empty_detectionlist_response(mocker, MOCK_GET_ALL_HIGH_RISK_EMPLOYEES_RESPONSE) + code42_sdk_mock.detectionlists.high_risk_employee.get_all.return_value = no_employees_response + client = create_client(code42_sdk_mock) + _, _, res = highriskemployee_get_all_command( + client, + { + "risktags": [ + "PERFORMANCE_CONCERNS", + "SUSPICIOUS_SYSTEM_ACTIVITY", + "POOR_SECURITY_PRACTICES", + ] + }, + ) + # Only first employee has the given risk tags + expected = [] + assert res == expected + assert code42_sdk_mock.detectionlists.high_risk_employee.get_all.call_count == 1 + + def test_highriskemployee_add_risk_tags_command(code42_sdk_mock): client = create_client(code42_sdk_mock) _, _, res = highriskemployee_add_risk_tags_command( From f2e229de79e17dceb048a54a0a869881b6a94c64 Mon Sep 17 00:00:00 2001 From: Juliya Smith Date: Wed, 17 Jun 2020 19:51:00 +0000 Subject: [PATCH 12/17] Gen playbook readmes --- .../Code42/integration-Code42.yml | 4 +- ...ook-Code42_Exfiltration_Playbook_README.md | 41 ++++++++++ .../playbook-Code42_File_Search_README.md | 78 +++++++++++++++++++ 3 files changed, 121 insertions(+), 2 deletions(-) create mode 100644 Packs/Code42/Playbooks/playbook-Code42_Exfiltration_Playbook_README.md create mode 100644 Packs/Code42/Playbooks/playbook-Code42_File_Search_README.md diff --git a/Packs/Code42/Integrations/Code42/integration-Code42.yml b/Packs/Code42/Integrations/Code42/integration-Code42.yml index 011603c99e84..0ffc29e32c63 100644 --- a/Packs/Code42/Integrations/Code42/integration-Code42.yml +++ b/Packs/Code42/Integrations/Code42/integration-Code42.yml @@ -637,7 +637,7 @@ script: def departingemployee_get_all_command(client, args): employees = client.get_all_departing_employees() - if employees: + if employees is not None: employees_context = [ { "UserID": e["userId"], @@ -691,7 +691,7 @@ script: def highriskemployee_get_all_command(client, args): tags = args.get("risktags") employees = client.get_all_high_risk_employees(tags) - if employees: + if employees is not None: employees_context = [ {"UserID": e["userId"], "Username": e["userName"], "Note": e["notes"]} for e in employees diff --git a/Packs/Code42/Playbooks/playbook-Code42_Exfiltration_Playbook_README.md b/Packs/Code42/Playbooks/playbook-Code42_Exfiltration_Playbook_README.md new file mode 100644 index 000000000000..d99283b652ad --- /dev/null +++ b/Packs/Code42/Playbooks/playbook-Code42_Exfiltration_Playbook_README.md @@ -0,0 +1,41 @@ +The Code42 Exfiltration playbook acts on Code42 Security Alerts, retrieves file event data, and allows security teams to remediate file exfiltration events by revoking access rights to cloud files or containing endpoints. + +## Dependencies +This playbook uses the following sub-playbooks, integrations, and scripts. + +### Sub-playbooks +* Active Directory - Get User Manager Details + +### Integrations +* jira-v2 +* CrowdstrikeFalcon + +### Scripts +This playbook does not use any scripts. + +### Commands +* send-mail +* closeInvestigation +* jira-create-issue +* cs-falcon-search-device +* code42-alert-resolve +* cs-falcon-contain-host + +## Playbook Inputs +--- + +| **Name** | **Description** | **Default Value** | **Required** | +| --- | --- | --- | --- | +| JiraProject | Jira Project for created incident ticket | Security | Optional | +| JiraType | Type of Jira ticket to create | Investigation | Optional | +| JiraSummary | Summary to use with Jira ticket creation | Code42 Security Alert for Demisto Incident ${incident.id} | Optional | +| ContainHostsMax | Maximum number of network hosts to contain. | 2 | Optional | +| DemistoInstanceURL | URL of Demisto instance for emails. | https://example.com/ | Optional | + +## Playbook Outputs +--- +There are no outputs for this playbook. + +## Playbook Image +--- +![Code42 Exfiltration Playbook](Insert the link to your image here) \ No newline at end of file diff --git a/Packs/Code42/Playbooks/playbook-Code42_File_Search_README.md b/Packs/Code42/Playbooks/playbook-Code42_File_Search_README.md new file mode 100644 index 000000000000..fdc7ee39b468 --- /dev/null +++ b/Packs/Code42/Playbooks/playbook-Code42_File_Search_README.md @@ -0,0 +1,78 @@ +This playbook searches for files via Code42 security events by either MD5 or SHA256 hash. The data is output to the Code42.SecurityData context for use. + +## Dependencies +This playbook uses the following sub-playbooks, integrations, and scripts. + +### Sub-playbooks +This playbook does not use any sub-playbooks. + +### Integrations +This playbook does not use any integrations. + +### Scripts +This playbook does not use any scripts. + +### Commands +* code42-securitydata-search + +## Playbook Inputs +--- + +| **Name** | **Description** | **Default Value** | **Required** | +| --- | --- | --- | --- | +| MD5 | MD5 hash to search for | File.MD5 | Optional | +| SHA256 | SHA256 hash to search for | File.SHA256 | Optional | + +## Playbook Outputs +--- + +| **Path** | **Description** | **Type** | +| --- | --- | --- | +| Code42.SecurityData | Returned File Results | unknown | +| Code42.SecurityData.EventTimestamp | Timestamp for event | unknown | +| Code42.SecurityData.FileCreated | File creation date | unknown | +| Code42.SecurityData.EndpointID | Code42 device ID | unknown | +| Code42.SecurityData.DeviceUsername | Username that device is associated with in Code42 | unknown | +| Code42.SecurityData.EmailFrom | Sender email address for email exfiltration events | unknown | +| Code42.SecurityData.EmailTo | Recipient email address for email exfiltration events | unknown | +| Code42.SecurityData.EmailSubject | Email subject line for email exfiltration events | unknown | +| Code42.SecurityData.EventID | Security Data event ID | unknown | +| Code42.SecurityData.EventType | Type of Security Data event | unknown | +| Code42.SecurityData.FileCategory | Type of file as determined by Code42 engine | unknown | +| Code42.SecurityData.FileOwner | Owner of file | unknown | +| Code42.SecurityData.FileName | File name | unknown | +| Code42.SecurityData.FilePath | Path to file | unknown | +| Code42.SecurityData.FileSize | Size of file in bytes | unknown | +| Code42.SecurityData.FileModified | File modification date | unknown | +| Code42.SecurityData.FileMD5 | MD5 hash of file | unknown | +| Code42.SecurityData.FileHostname | Hostname where file event was captured | unknown | +| Code42.SecurityData.DevicePrivateIPAddress | Private IP addresses of device where event was captured | unknown | +| Code42.SecurityData.DevicePublicIPAddress | Public IP address of device where event was captured | unknown | +| Code42.SecurityData.RemovableMediaType | Type of removable media | unknown | +| Code42.SecurityData.RemovableMediaCapacity | Total capacity of removable media in bytes | unknown | +| Code42.SecurityData.RemovableMediaMediaName | Full name of removable media | unknown | +| Code42.SecurityData.RemovableMediaName | Name of removable media | unknown | +| Code42.SecurityData.RemovableMediaSerialNumber | Serial number for removable medial device | unknown | +| Code42.SecurityData.RemovableMediaVendor | Vendor name for removable device | unknown | +| Code42.SecurityData.FileSHA256 | SHA256 hash of file | unknown | +| Code42.SecurityData.FileShared | Whether file is shared using cloud file service | unknown | +| Code42.SecurityData.FileSharedWith | Accounts that file is shared with on cloud file service | unknown | +| Code42.SecurityData.Source | Source of file event, Cloud or Endpoint | unknown | +| Code42.SecurityData.ApplicationTabURL | URL associated with application read event | unknown | +| Code42.SecurityData.ProcessName | Process name for application read event | unknown | +| Code42.SecurityData.ProcessOwner | Process owner for application read event | unknown | +| Code42.SecurityData.WindowTitle | Process name for application read event | unknown | +| Code42.SecurityData.FileURL | URL of file on cloud file service | unknown | +| Code42.SecurityData.Exposure | Exposure type for event | unknown | +| Code42.SecurityData.SharingTypeAdded | Type of sharing added to file | unknown | +| File | The file object. | unknown | +| File.Name | File name | unknown | +| File.Path | File path | unknown | +| File.Size | File size in bytes | unknown | +| File.MD5 | MD5 hash of file | unknown | +| File.SHA256 | FSHA256 hash of file | unknown | +| File.Hostname | Hostname where file event was captured | unknown | + +## Playbook Image +--- +![Code42 File Search](Insert the link to your image here) \ No newline at end of file From e5087a68e545c9189e42289f570d88f8e130dbe5 Mon Sep 17 00:00:00 2001 From: Juliya Smith Date: Wed, 17 Jun 2020 19:57:56 +0000 Subject: [PATCH 13/17] Conform yml file indentation --- Packs/Code42/Integrations/Code42/Code42.yml | 138 ++++++++++---------- 1 file changed, 69 insertions(+), 69 deletions(-) diff --git a/Packs/Code42/Integrations/Code42/Code42.yml b/Packs/Code42/Integrations/Code42/Code42.yml index 617ba0d34552..671a598f3e90 100644 --- a/Packs/Code42/Integrations/Code42/Code42.yml +++ b/Packs/Code42/Integrations/Code42/Code42.yml @@ -276,16 +276,16 @@ script: description: Adds a user to the Departing Employee List. - name: code42-departingemployee-remove arguments: - - name: username - required: true - description: The username to remove from the Departing Employee List. + - name: username + required: true + description: The username to remove from the Departing Employee List. outputs: - - contextPath: Code42.DepartingEmployee.UserID - description: Internal Code42 User ID for the Departing Employee. - type: string - - contextPath: Code42.DepartingEmployee.Username - description: The username of the Departing Employee. - type: string + - contextPath: Code42.DepartingEmployee.UserID + description: Internal Code42 User ID for the Departing Employee. + type: string + - contextPath: Code42.DepartingEmployee.Username + description: The username of the Departing Employee. + type: string description: Removes a user from the Departing Employee List. - name: code42-departingemployee-get-all outputs: @@ -303,82 +303,82 @@ script: description: Get all employees on the Departing Employee List. - name: code42-highriskemployee-add arguments: - - name: username - required: true - description: The username to add to the High Risk Employee List. - - name: note - description: Note to attach to the High Risk Employee. + - name: username + required: true + description: The username to add to the High Risk Employee List. + - name: note + description: Note to attach to the High Risk Employee. outputs: - - contextPath: Code42.HighRiskEmployee.UserID - description: Internal Code42 User ID for the High Risk Employee. - type: string - - contextPath: Code42.HighRiskEmployee.Username - description: The username of the High Risk Employee. - type: string - - contextPath: Code42.HighRiskEmployee.Note - description: Note associated with the High Risk Employee. - type: string + - contextPath: Code42.HighRiskEmployee.UserID + description: Internal Code42 User ID for the High Risk Employee. + type: string + - contextPath: Code42.HighRiskEmployee.Username + description: The username of the High Risk Employee. + type: string + - contextPath: Code42.HighRiskEmployee.Note + description: Note associated with the High Risk Employee. + type: string description: Removes a user from the High Risk Employee List. - name: code42-highriskemployee-remove arguments: - - name: username - required: true - description: The username to remove from the High Risk Employee List. + - name: username + required: true + description: The username to remove from the High Risk Employee List. outputs: - - contextPath: Code42.HighRiskEmployee.UserID - description: Internal Code42 User ID for the High Risk Employee. - - contextPath: Code42.HighRiskEmployee.Username - description: The username of the High Risk Employee. + - contextPath: Code42.HighRiskEmployee.UserID + description: Internal Code42 User ID for the High Risk Employee. + - contextPath: Code42.HighRiskEmployee.Username + description: The username of the High Risk Employee. description: Removes a user from the High Risk Employee List. - name: code42-highriskemployee-get-all arguments: - - name: risktags - description: To filter results by employees who have these risk tags. + - name: risktags + description: To filter results by employees who have these risk tags. outputs: - - contextPath: Code42.HighRiskEmployee.UserID - description: Internal Code42 User ID for the High Risk Employee. - type: string - - contextPath: Code42.HighRiskEmployee.Username - description: The username of the High Risk Employee. - type: string - - contextPath: Code42.HighRiskEmployee.Note - description: Note associated with the High Risk Employee. - type: string + - contextPath: Code42.HighRiskEmployee.UserID + description: Internal Code42 User ID for the High Risk Employee. + type: string + - contextPath: Code42.HighRiskEmployee.Username + description: The username of the High Risk Employee. + type: string + - contextPath: Code42.HighRiskEmployee.Note + description: Note associated with the High Risk Employee. + type: string description: Get all employees on the High Risk Employee List. - name: code42-highriskemployee-add-risk-tags arguments: - - name: username - required: true - description: The username of the High Risk Employee. - - name: risktags - required: true - description: Risk tags to associate with the High Risk Employee. + - name: username + required: true + description: The username of the High Risk Employee. + - name: risktags + required: true + description: Risk tags to associate with the High Risk Employee. outputs: - - contextPath: Code42.HighRiskEmployee.UserID - description: Internal Code42 User ID for the Departing Employee. - type: string - - contextPath: Code42.HighRiskEmployee.Username - description: The username of the High Risk Employee. - type: string - - contextPath: Code42.HighRiskEmployee.RiskTags - description: Risk tags to asspciate with the High Risk Employee. + - contextPath: Code42.HighRiskEmployee.UserID + description: Internal Code42 User ID for the Departing Employee. + type: string + - contextPath: Code42.HighRiskEmployee.Username + description: The username of the High Risk Employee. + type: string + - contextPath: Code42.HighRiskEmployee.RiskTags + description: Risk tags to asspciate with the High Risk Employee. - name: code42-highriskemployee-remove-risk-tags arguments: - - name: username - required: true - description: The username of the High Risk Employee. - - name: risktags - required: true - description: Risk tags to disassociate from the High Risk Employee. + - name: username + required: true + description: The username of the High Risk Employee. + - name: risktags + required: true + description: Risk tags to disassociate from the High Risk Employee. outputs: - - contextPath: Code42.HighRiskEmployee.UserID - description: Internal Code42 User ID for the Departing Employee. - type: string - - contextPath: Code42.HighRiskEmployee.Username - description: The username of the High Risk Employee. - type: string - - contextPath: Code42.HighRiskEmployee.RiskTags - description: Risk tags to disassociate from the High Risk Employee. + - contextPath: Code42.HighRiskEmployee.UserID + description: Internal Code42 User ID for the Departing Employee. + type: string + - contextPath: Code42.HighRiskEmployee.Username + description: The username of the High Risk Employee. + type: string + - contextPath: Code42.HighRiskEmployee.RiskTags + description: Risk tags to disassociate from the High Risk Employee. dockerimage: demisto/py42:1.0.0.9242 isfetch: true runonce: false From 667f3b0cbe38f167d4886465635e534a6b8f7b01 Mon Sep 17 00:00:00 2001 From: Juliya Smith Date: Wed, 17 Jun 2020 20:21:57 +0000 Subject: [PATCH 14/17] Split up fixtures --- .../Code42/Integrations/Code42/Code42_test.py | 140 ++++++++++-------- 1 file changed, 81 insertions(+), 59 deletions(-) diff --git a/Packs/Code42/Integrations/Code42/Code42_test.py b/Packs/Code42/Integrations/Code42/Code42_test.py index bf286fb7e745..d7d6c544f390 100644 --- a/Packs/Code42/Integrations/Code42/Code42_test.py +++ b/Packs/Code42/Integrations/Code42/Code42_test.py @@ -840,43 +840,65 @@ @pytest.fixture def code42_sdk_mock(mocker): - c42_sdk_mock = mocker.MagicMock(spec=SDKClient) + code42_mock = mocker.MagicMock(spec=SDKClient) + get_user_response = create_mock_code42_sdk_response(mocker, MOCK_GET_USER_RESPONSE) + code42_mock.users.get_by_username.return_value = get_user_response + return code42_mock + + +@pytest.fixture +def code42_alerts_mock(code42_sdk_mock, mocker): + return create_alerts_mock(code42_sdk_mock, mocker) + + +@pytest.fixture +def code42_file_events_mock(code42_sdk_mock, mocker): + return create_file_events_mock(code42_sdk_mock, mocker) + + +@pytest.fixture +def code42_fetch_incidents_mock(code42_sdk_mock, mocker): + code42_mock = create_alerts_mock(code42_sdk_mock, mocker) + code42_mock = create_file_events_mock(code42_mock, mocker) + return code42_mock + - # Setup mock alert details +def create_alerts_mock(c42_sdk_mock, mocker): alert_details_response = create_mock_code42_sdk_response(mocker, MOCK_ALERT_DETAILS_RESPONSE) c42_sdk_mock.alerts.get_details.return_value = alert_details_response - - # Setup alerts for querying alerts_response = create_mock_code42_sdk_response(mocker, MOCK_ALERTS_RESPONSE) c42_sdk_mock.alerts.search.return_value = alerts_response + return c42_sdk_mock - # Setup mock user - get_user_response = create_mock_code42_sdk_response(mocker, MOCK_GET_USER_RESPONSE) - c42_sdk_mock.users.get_by_username.return_value = get_user_response - # Setup file events +def create_file_events_mock(c42_sdk_mock, mocker): search_file_events_response = create_mock_code42_sdk_response( mocker, MOCK_SECURITY_EVENT_RESPONSE ) c42_sdk_mock.securitydata.search_file_events.return_value = search_file_events_response + return c42_sdk_mock - # Setup get all departing employees + +@pytest.fixture +def code42_departing_employee_mock(code42_sdk_mock, mocker): all_departing_employees_response = create_mock_code42_sdk_response_generator( mocker, [MOCK_GET_ALL_DEPARTING_EMPLOYEES_RESPONSE] ) - c42_sdk_mock.detectionlists.departing_employee.get_all.return_value = ( + code42_sdk_mock.detectionlists.departing_employee.get_all.return_value = ( all_departing_employees_response ) + return code42_sdk_mock - # Setup get all high risk employees + +@pytest.fixture +def code42_high_risk_employee_mock(code42_sdk_mock, mocker): all_high_risk_employees_response = create_mock_code42_sdk_response_generator( mocker, [MOCK_GET_ALL_HIGH_RISK_EMPLOYEES_RESPONSE] ) - c42_sdk_mock.detectionlists.high_risk_employee.get_all.return_value = ( + code42_sdk_mock.detectionlists.high_risk_employee.get_all.return_value = ( all_high_risk_employees_response ) - - return c42_sdk_mock + return code42_sdk_mock def create_mock_code42_sdk_response(mocker, response_text): @@ -955,14 +977,14 @@ def test_map_to_file_context(): assert context == MOCK_FILE_CONTEXT[i] -def test_alert_get_command(code42_sdk_mock): - client = create_client(code42_sdk_mock) +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" -def test_alert_resolve_command(code42_sdk_mock): - client = create_client(code42_sdk_mock) +def test_alert_resolve_command(code42_alerts_mock): + client = create_client(code42_alerts_mock) _, _, res = alert_resolve_command(client, {"id": "36fb8ca5-0533-4d25-9763-e09d35d60610"}) assert res["id"] == "36fb8ca5-0533-4d25-9763-e09d35d60610" @@ -990,16 +1012,16 @@ def test_departingemployee_remove_command(code42_sdk_mock): code42_sdk_mock.detectionlists.departing_employee.remove.assert_called_once_with(expected) -def test_departingemployee_get_all_command(code42_sdk_mock): - client = create_client(code42_sdk_mock) +def test_departingemployee_get_all_command(code42_departing_employee_mock): + client = create_client(code42_departing_employee_mock) _, _, res = departingemployee_get_all_command(client, {"username": "user1@example.com"}) expected = json.loads(MOCK_GET_ALL_DEPARTING_EMPLOYEES_RESPONSE)["items"] assert res == expected - assert code42_sdk_mock.detectionlists.departing_employee.get_all.call_count == 1 + assert code42_departing_employee_mock.detectionlists.departing_employee.get_all.call_count == 1 def test_departingemployee_get_all_command_gets_employees_from_multiple_pages( - code42_sdk_mock, mocker + code42_departing_employee_mock, mocker ): # Setup get all departing employees page = MOCK_GET_ALL_DEPARTING_EMPLOYEES_RESPONSE @@ -1007,8 +1029,8 @@ def test_departingemployee_get_all_command_gets_employees_from_multiple_pages( employee_page_generator = ( create_mock_code42_sdk_response(mocker, page) for page in [page, page, page] ) - code42_sdk_mock.detectionlists.departing_employee.get_all.return_value = employee_page_generator - client = create_client(code42_sdk_mock) + code42_departing_employee_mock.detectionlists.departing_employee.get_all.return_value = employee_page_generator + client = create_client(code42_departing_employee_mock) _, _, res = departingemployee_get_all_command(client, {"username": "user1@example.com"}) @@ -1019,12 +1041,12 @@ def test_departingemployee_get_all_command_gets_employees_from_multiple_pages( def test_departingemployee_get_all_command_when_no_employees( - code42_sdk_mock, mocker + code42_departing_employee_mock, mocker ): no_employees_response = get_empty_detectionlist_response(mocker, MOCK_GET_ALL_DEPARTING_EMPLOYEES_RESPONSE) - code42_sdk_mock.detectionlists.high_risk_employee.get_all.return_value = no_employees_response - client = create_client(code42_sdk_mock) - _, _, res = highriskemployee_get_all_command( + code42_departing_employee_mock.detectionlists.departing_employee.get_all.return_value = no_employees_response + client = create_client(code42_departing_employee_mock) + _, _, res = departingemployee_get_all_command( client, { "risktags": [ @@ -1037,18 +1059,18 @@ def test_departingemployee_get_all_command_when_no_employees( # Only first employee has the given risk tags expected = [] assert res == expected - assert code42_sdk_mock.detectionlists.high_risk_employee.get_all.call_count == 1 + assert code42_departing_employee_mock.detectionlists.departing_employee.get_all.call_count == 1 -def test_highriskemployee_add_command(code42_sdk_mock): - client = create_client(code42_sdk_mock) +def test_highriskemployee_add_command(code42_high_risk_employee_mock): + client = create_client(code42_high_risk_employee_mock) _, _, res = highriskemployee_add_command( client, {"username": "user1@example.com", "note": "Dummy note"} ) expected_user_id = "123412341234123412" # value found in GET_USER_RESPONSE assert res == expected_user_id - code42_sdk_mock.detectionlists.high_risk_employee.add.assert_called_once_with(expected_user_id) - code42_sdk_mock.detectionlists.update_user_notes.assert_called_once_with( + code42_high_risk_employee_mock.detectionlists.high_risk_employee.add.assert_called_once_with(expected_user_id) + code42_high_risk_employee_mock.detectionlists.update_user_notes.assert_called_once_with( expected_user_id, "Dummy note" ) @@ -1061,16 +1083,16 @@ def test_highriskemployee_remove_command(code42_sdk_mock): code42_sdk_mock.detectionlists.high_risk_employee.remove.assert_called_once_with(expected) -def test_highriskemployee_get_all_command(code42_sdk_mock): - client = create_client(code42_sdk_mock) +def test_highriskemployee_get_all_command(code42_high_risk_employee_mock): + client = create_client(code42_high_risk_employee_mock) _, _, res = highriskemployee_get_all_command(client, {}) expected = json.loads(MOCK_GET_ALL_HIGH_RISK_EMPLOYEES_RESPONSE)["items"] assert res == expected - assert code42_sdk_mock.detectionlists.high_risk_employee.get_all.call_count == 1 + assert code42_high_risk_employee_mock.detectionlists.high_risk_employee.get_all.call_count == 1 def test_highriskemployee_get_all_command_gets_employees_from_multiple_pages( - code42_sdk_mock, mocker + code42_high_risk_employee_mock, mocker ): # Setup get all high risk employees page = MOCK_GET_ALL_HIGH_RISK_EMPLOYEES_RESPONSE @@ -1078,8 +1100,8 @@ def test_highriskemployee_get_all_command_gets_employees_from_multiple_pages( employee_page_generator = ( create_mock_code42_sdk_response(mocker, page) for page in [page, page, page] ) - code42_sdk_mock.detectionlists.high_risk_employee.get_all.return_value = employee_page_generator - client = create_client(code42_sdk_mock) + 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, {"username": "user1@example.com"}) @@ -1090,9 +1112,9 @@ def test_highriskemployee_get_all_command_gets_employees_from_multiple_pages( def test_highriskemployee_get_all_command_when_given_risk_tags_only_gets_employees_with_tags( - code42_sdk_mock + code42_high_risk_employee_mock ): - client = create_client(code42_sdk_mock) + client = create_client(code42_high_risk_employee_mock) _, _, res = highriskemployee_get_all_command( client, { @@ -1106,15 +1128,15 @@ def test_highriskemployee_get_all_command_when_given_risk_tags_only_gets_employe # 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_sdk_mock.detectionlists.high_risk_employee.get_all.call_count == 1 + assert code42_high_risk_employee_mock.detectionlists.high_risk_employee.get_all.call_count == 1 def test_highriskemployee_get_all_command_when_no_employees( - code42_sdk_mock, mocker + code42_high_risk_employee_mock, mocker ): no_employees_response = get_empty_detectionlist_response(mocker, MOCK_GET_ALL_HIGH_RISK_EMPLOYEES_RESPONSE) - code42_sdk_mock.detectionlists.high_risk_employee.get_all.return_value = no_employees_response - client = create_client(code42_sdk_mock) + code42_high_risk_employee_mock.detectionlists.high_risk_employee.get_all.return_value = no_employees_response + client = create_client(code42_high_risk_employee_mock) _, _, res = highriskemployee_get_all_command( client, { @@ -1128,7 +1150,7 @@ def test_highriskemployee_get_all_command_when_no_employees( # Only first employee has the given risk tags expected = [] assert res == expected - assert code42_sdk_mock.detectionlists.high_risk_employee.get_all.call_count == 1 + 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): @@ -1155,11 +1177,11 @@ def test_highriskemployee_remove_risk_tags_command(code42_sdk_mock): ) -def test_security_data_search_command(code42_sdk_mock): - client = create_client(code42_sdk_mock) +def test_security_data_search_command(code42_file_events_mock): + client = create_client(code42_file_events_mock) _, _, res = securitydata_search_command(client, MOCK_SECURITY_DATA_SEARCH_QUERY) assert len(res) == 3 - actual_query = code42_sdk_mock.securitydata.search_file_events.call_args[0][0] + actual_query = code42_file_events_mock.securitydata.search_file_events.call_args[0][0] filter_groups = json.loads(str(actual_query))["groups"] assert filter_groups[0]["filters"][0]["term"] == "md5Checksum" assert filter_groups[0]["filters"][0]["value"] == "d41d8cd98f00b204e9800998ecf8427e" @@ -1185,8 +1207,8 @@ def test_fetch_incidents_handles_single_severity(code42_sdk_mock): assert "HIGH" in str(code42_sdk_mock.alerts.search.call_args[0][0]) -def test_fetch_incidents_handles_multi_severity(code42_sdk_mock): - client = create_client(code42_sdk_mock) +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}, @@ -1196,12 +1218,12 @@ def test_fetch_incidents_handles_multi_severity(code42_sdk_mock): include_files=True, integration_context=None, ) - assert "HIGH" in str(code42_sdk_mock.alerts.search.call_args[0][0]) - assert "LOW" in str(code42_sdk_mock.alerts.search.call_args[0][0]) + 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_sdk_mock): - client = create_client(code42_sdk_mock) +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}, @@ -1215,10 +1237,10 @@ def test_fetch_incidents_first_run(code42_sdk_mock): assert next_run["last_fetch"] -def test_fetch_incidents_next_run(code42_sdk_mock): +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_sdk_mock) + client = create_client(code42_fetch_incidents_mock) next_run, incidents, remaining_incidents = fetch_incidents( client=client, last_run={"last_fetch": mock_timestamp}, @@ -1232,10 +1254,10 @@ def test_fetch_incidents_next_run(code42_sdk_mock): assert next_run["last_fetch"] -def test_fetch_incidents_fetch_limit(code42_sdk_mock): +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_sdk_mock) + client = create_client(code42_fetch_incidents_mock) next_run, incidents, remaining_incidents = fetch_incidents( client=client, last_run={"last_fetch": mock_timestamp}, From 54a9afc59f7a95c2a57f29ff5811c8cfa48601a5 Mon Sep 17 00:00:00 2001 From: Juliya Smith Date: Mon, 22 Jun 2020 12:45:59 +0000 Subject: [PATCH 15/17] Fix spelling --- Packs/Code42/Integrations/Code42/Code42.yml | 2 +- Packs/Code42/Integrations/Code42/README.md | 2 +- Packs/Code42/Integrations/Code42/integration-Code42.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Packs/Code42/Integrations/Code42/Code42.yml b/Packs/Code42/Integrations/Code42/Code42.yml index 671a598f3e90..e914c2b7feaa 100644 --- a/Packs/Code42/Integrations/Code42/Code42.yml +++ b/Packs/Code42/Integrations/Code42/Code42.yml @@ -361,7 +361,7 @@ script: description: The username of the High Risk Employee. type: string - contextPath: Code42.HighRiskEmployee.RiskTags - description: Risk tags to asspciate with the High Risk Employee. + description: Risk tags to associate with the High Risk Employee. - name: code42-highriskemployee-remove-risk-tags arguments: - name: username diff --git a/Packs/Code42/Integrations/Code42/README.md b/Packs/Code42/Integrations/Code42/README.md index e4b97388a261..f14c1c322cf5 100644 --- a/Packs/Code42/Integrations/Code42/README.md +++ b/Packs/Code42/Integrations/Code42/README.md @@ -413,7 +413,7 @@ Get all employees on the High Risk Employee List. | --- | --- | --- | | Code42.HighRiskEmployee.UserID | string | Internal Code42 User ID for the Departing Employee. | | Code42.HighRiskEmployee.Username | string | The username of the High Risk Employee. | -| Code42.HighRiskEmployee.RiskTags | unknown | Risk tags to asspciate with the High Risk Employee. | +| Code42.HighRiskEmployee.RiskTags | unknown | Risk tags to associate with the High Risk Employee. | #### Command Example diff --git a/Packs/Code42/Integrations/Code42/integration-Code42.yml b/Packs/Code42/Integrations/Code42/integration-Code42.yml index 0ffc29e32c63..a58536ac6310 100644 --- a/Packs/Code42/Integrations/Code42/integration-Code42.yml +++ b/Packs/Code42/Integrations/Code42/integration-Code42.yml @@ -1258,7 +1258,7 @@ script: description: The username of the High Risk Employee. type: string - contextPath: Code42.HighRiskEmployee.RiskTags - description: Risk tags to asspciate with the High Risk Employee. + description: Risk tags to associate with the High Risk Employee. - name: code42-highriskemployee-remove-risk-tags arguments: - name: username From 35c63ee73816f89bf32056b7a7a01b617b42c1a9 Mon Sep 17 00:00:00 2001 From: Juliya Smith Date: Mon, 22 Jun 2020 13:20:48 +0000 Subject: [PATCH 16/17] error handling --- Packs/Code42/Integrations/Code42/CHANGELOG.md | 2 +- Packs/Code42/Integrations/Code42/Code42.py | 170 ++++++++---------- .../Code42/Integrations/Code42/Code42_test.py | 41 +++-- .../Code42/integration-Code42.yml | 170 ++++++++---------- 4 files changed, 179 insertions(+), 204 deletions(-) diff --git a/Packs/Code42/Integrations/Code42/CHANGELOG.md b/Packs/Code42/Integrations/Code42/CHANGELOG.md index 1b2d74c8b7f9..244b5e76f237 100644 --- a/Packs/Code42/Integrations/Code42/CHANGELOG.md +++ b/Packs/Code42/Integrations/Code42/CHANGELOG.md @@ -7,7 +7,7 @@ Added new commands: Optionally takes a list of risk tags and only gets employees who have those risk tags. - **code42-highriskemployee-add-risk-tags** that takes a username and risk tags and associates the risk tags with the user. - **code42-highriskemployee-remove-risk-tags** that takes a username and risk tags and disassociates the risk tags from the user. - +Improve error messages for all Commands to include exception detail. ## [20.3.3] - 2020-03-18 #### New Integration diff --git a/Packs/Code42/Integrations/Code42/Code42.py b/Packs/Code42/Integrations/Code42/Code42.py index 00574be13510..9a1b82c5e9fe 100644 --- a/Packs/Code42/Integrations/Code42/Code42.py +++ b/Packs/Code42/Integrations/Code42/Code42.py @@ -134,17 +134,6 @@ def _create_alert_query(event_severity_filter, start_time): return alert_query -def return_none_on_error(func): - @logger - def wrapper(*args, **kwargs): - try: - return func(*args, **kwargs) - except Exception as ex: - LOG(str(ex)) - return None - return wrapper - - def _get_all_high_risk_employees_from_page(page, risk_tags): res = [] for employee in page["items"]: @@ -171,7 +160,6 @@ def __init__(self, sdk, base_url, auth, verify=True, proxy=False): self._sdk = sdk or py42.sdk.from_local_account(base_url, auth[0], auth[1]) py42.settings.set_user_agent_suffix("Cortex XSOAR") - @return_none_on_error def add_user_to_departing_employee(self, username, departure_date=None, note=None): user_id = self.get_user_id(username) self._sdk.detectionlists.departing_employee.add(user_id, departure_date=departure_date) @@ -179,13 +167,11 @@ def add_user_to_departing_employee(self, username, departure_date=None, note=Non self._sdk.detectionlists.update_user_notes(user_id, note) return user_id - @return_none_on_error def remove_user_from_departing_employee(self, username): user_id = self.get_user_id(username) self._sdk.detectionlists.departing_employee.remove(user_id) return user_id - @return_none_on_error def get_all_departing_employees(self): res = [] pages = self._sdk.detectionlists.departing_employee.get_all() @@ -194,7 +180,6 @@ def get_all_departing_employees(self): res.extend(employees) return res - @return_none_on_error def add_user_to_high_risk_employee(self, username, note=None): user_id = self.get_user_id(username) self._sdk.detectionlists.high_risk_employee.add(user_id) @@ -202,25 +187,21 @@ def add_user_to_high_risk_employee(self, username, note=None): self._sdk.detectionlists.update_user_notes(user_id, note) return user_id - @return_none_on_error def remove_user_from_high_risk_employee(self, username): user_id = self.get_user_id(username) self._sdk.detectionlists.high_risk_employee.remove(user_id) return user_id - @return_none_on_error def add_user_risk_tags(self, username, risk_tags): user_id = self.get_user_id(username) self._sdk.detectionlists.add_user_risk_tags(user_id, risk_tags) return user_id - @return_none_on_error def remove_user_risk_tags(self, username, risk_tags): user_id = self.get_user_id(username) self._sdk.detectionlists.remove_user_risk_tags(user_id, risk_tags) return user_id - @return_none_on_error def get_all_high_risk_employees(self, risk_tags=None): if isinstance(risk_tags, str): risk_tags = [risk_tags] @@ -230,33 +211,27 @@ def get_all_high_risk_employees(self, risk_tags=None): res.extend(_get_all_high_risk_employees_from_page(page, risk_tags)) return res - @return_none_on_error def fetch_alerts(self, start_time, event_severity_filter): query = _create_alert_query(event_severity_filter, start_time) res = self._sdk.alerts.search(query) return res["alerts"] - @return_none_on_error def get_alert_details(self, alert_id): res = self._sdk.alerts.get_details(alert_id) return res["alerts"][0] - @return_none_on_error def resolve_alert(self, id): self._sdk.alerts.resolve(id) return id - @return_none_on_error def get_current_user(self): res = self._sdk.users.get_current() return res - @return_none_on_error def get_user_id(self, username): res = self._sdk.users.get_by_username(username) return res["users"][0]["userUid"] - @return_none_on_error def search_file_events(self, payload): res = self._sdk.securitydata.search_file_events(payload) return res["fileEvents"] @@ -472,14 +447,21 @@ def _map_obj_to_context(obj, context_mapper): return {v: obj.get(k) for k, v in context_mapper.items() if obj.get(k)} +def create_command_error_message(cmd, ex): + return "Failed to execute command {0} command. Error: {1}".format(cmd, str(ex)) + + """Commands""" @logger def alert_get_command(client, args): code42_securityalert_context = [] - alert = client.get_alert_details(args["id"]) - if alert: + 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( @@ -488,35 +470,39 @@ def alert_get_command(client, args): headers=SECURITY_ALERT_HEADERS, ) return readable_outputs, {"Code42.SecurityAlert": code42_securityalert_context}, alert - else: - return "No results found", {}, {} + except Exception as e: + return_error(create_command_error_message(demisto.command(), e)) @logger def alert_resolve_command(client, args): code42_security_alert_context = [] - alert_id = client.resolve_alert(args["id"]) - if not alert_id: - return "No results found", {}, {} + try: + alert_id = client.resolve_alert(args["id"]) - # 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_security_alert_context.append(code42_context) - readable_outputs = tableToMarkdown( - "Code42 Security Alert Resolved", - code42_security_alert_context, - headers=SECURITY_ALERT_HEADERS, - ) - return ( - readable_outputs, - {"Code42.SecurityAlert": code42_security_alert_context}, - alert_details, - ) + 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_security_alert_context.append(code42_context) + readable_outputs = tableToMarkdown( + "Code42 Security Alert Resolved", + code42_security_alert_context, + headers=SECURITY_ALERT_HEADERS, + ) + return ( + readable_outputs, + {"Code42.SecurityAlert": code42_security_alert_context}, + alert_details, + ) + except Exception as e: + return_error(create_command_error_message(demisto.command(), e)) @logger @@ -524,38 +510,38 @@ def departingemployee_add_command(client, args): departing_date = args.get("departuredate") username = args["username"] note = args.get("note") - user_id = client.add_user_to_departing_employee(username, departing_date, note) - if not user_id: - return_error(message="Could not add user to the Departing Employee List") - - de_context = { - "UserID": user_id, - "Username": username, - "DepartureDate": departing_date, - "Note": note, - } - readable_outputs = tableToMarkdown("Code42 Departing Employee List User Added", de_context) - return readable_outputs, {"Code42.DepartingEmployee": de_context}, user_id + try: + user_id = client.add_user_to_departing_employee(username, departing_date, note) + de_context = { + "UserID": user_id, + "Username": username, + "DepartureDate": departing_date, + "Note": note, + } + readable_outputs = tableToMarkdown("Code42 Departing Employee List User Added", de_context) + return readable_outputs, {"Code42.DepartingEmployee": de_context}, user_id + except Exception as e: + return_error(create_command_error_message(demisto.command(), e)) @logger def departingemployee_remove_command(client, args): username = args["username"] - user_id = client.remove_user_from_departing_employee(username) - if user_id: + try: + user_id = client.remove_user_from_departing_employee(username) de_context = {"UserID": user_id, "Username": username} readable_outputs = tableToMarkdown( "Code42 Departing Employee List User Removed", de_context ) return readable_outputs, {"Code42.DepartingEmployee": de_context}, user_id - else: - return_error(message="Could not remove user from the Departing Employee List") + except Exception as e: + return_error(create_command_error_message(demisto.command(), e)) @logger def departingemployee_get_all_command(client, args): - employees = client.get_all_departing_employees() - if employees is not None: + try: + employees = client.get_all_departing_employees() employees_context = [ { "UserID": e["userId"], @@ -571,42 +557,42 @@ def departingemployee_get_all_command(client, args): {"Code42.DepartingEmployee(val.UserID && val.UserID == obj.UserID)": employees_context}, employees, ) - else: - return_error(message="Could not get all Departing Employees") + except Exception as e: + return_error(create_command_error_message(demisto.command(), e)) @logger def highriskemployee_add_command(client, args): username = args["username"] note = args.get("note") - user_id = client.add_user_to_high_risk_employee(username, note) - if not user_id: - return_error(message="Could not add user to the High Risk Employee List") - - 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 + 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 readable_outputs, {"Code42.HighRiskEmployee": hr_context}, user_id + except Exception as e: + return_error(create_command_error_message(demisto.command(), e)) @logger def highriskemployee_remove_command(client, args): username = args["username"] - user_id = client.remove_user_from_high_risk_employee(username) - if user_id: + 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 readable_outputs, {"Code42.HighRiskEmployee": hr_context}, user_id - else: - return_error(message="Could not remove user from the High Risk Employee List") + except Exception as e: + return_error(create_command_error_message(demisto.command(), e)) @logger def highriskemployee_get_all_command(client, args): tags = args.get("risktags") - employees = client.get_all_high_risk_employees(tags) - if employees is not None: + try: + employees = client.get_all_high_risk_employees(tags) employees_context = [ {"UserID": e["userId"], "Username": e["userName"], "Note": e["notes"]} for e in employees @@ -617,34 +603,34 @@ def highriskemployee_get_all_command(client, args): {"Code42.HighRiskEmployee(val.UserID && val.UserID == obj.UserID)": employees_context}, employees, ) - else: - return_error(message="Could not get all High Risk Employees") + except Exception as e: + return_error(create_command_error_message(demisto.command(), e)) @logger def highriskemployee_add_risk_tags_command(client, args): username = args["username"] tags = args["risktags"] - user_id = client.add_user_risk_tags(username, tags) - if user_id: + 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 readable_outputs, {"Code42.HighRiskEmployee": rt_context}, user_id - else: - return_error(message="Could not add user risk tags") + except Exception as e: + return_error(create_command_error_message(demisto.command(), e)) @logger def highriskemployee_remove_risk_tags_command(client, args): username = args["username"] tags = args["risktags"] - user_id = client.remove_user_risk_tags(username, tags) - if user_id: + 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 readable_outputs, {"Code42.HighRiskEmployee": rt_context}, user_id - else: - return_error(message="Could not remove user risk tags") + except Exception as e: + return_error(create_command_error_message(demisto.command(), e)) def _create_incident_from_alert_details(details): diff --git a/Packs/Code42/Integrations/Code42/Code42_test.py b/Packs/Code42/Integrations/Code42/Code42_test.py index d7d6c544f390..7b7e7e5dd6bf 100644 --- a/Packs/Code42/Integrations/Code42/Code42_test.py +++ b/Packs/Code42/Integrations/Code42/Code42_test.py @@ -925,15 +925,6 @@ def get_empty_detectionlist_response(mocker, base_text): """TESTS""" -def test_code42client_when_error_occurs_during_method_returns_none(code42_sdk_mock): - def error_effect(): - raise Exception("Test Error") - code42_sdk_mock.detectionlists.departing_employee.add.side_effect = error_effect - client = create_client(code42_sdk_mock) - res = client.add_user_to_departing_employee("test") - assert res is None - - def test_build_query_payload(): query = build_query_payload(MOCK_SECURITY_DATA_SEARCH_QUERY) assert query.sort_key == MOCK_FILE_EVENT_QUERY_PAYLOAD["srtKey"] @@ -1029,7 +1020,9 @@ def test_departingemployee_get_all_command_gets_employees_from_multiple_pages( employee_page_generator = ( create_mock_code42_sdk_response(mocker, page) for page in [page, page, page] ) - code42_departing_employee_mock.detectionlists.departing_employee.get_all.return_value = employee_page_generator + code42_departing_employee_mock.detectionlists.departing_employee.get_all.return_value = ( + employee_page_generator + ) client = create_client(code42_departing_employee_mock) _, _, res = departingemployee_get_all_command(client, {"username": "user1@example.com"}) @@ -1043,8 +1036,12 @@ def test_departingemployee_get_all_command_gets_employees_from_multiple_pages( def test_departingemployee_get_all_command_when_no_employees( code42_departing_employee_mock, mocker ): - no_employees_response = get_empty_detectionlist_response(mocker, MOCK_GET_ALL_DEPARTING_EMPLOYEES_RESPONSE) - code42_departing_employee_mock.detectionlists.departing_employee.get_all.return_value = no_employees_response + no_employees_response = get_empty_detectionlist_response( + mocker, MOCK_GET_ALL_DEPARTING_EMPLOYEES_RESPONSE + ) + code42_departing_employee_mock.detectionlists.departing_employee.get_all.return_value = ( + no_employees_response + ) client = create_client(code42_departing_employee_mock) _, _, res = departingemployee_get_all_command( client, @@ -1069,7 +1066,9 @@ def test_highriskemployee_add_command(code42_high_risk_employee_mock): ) expected_user_id = "123412341234123412" # value found in GET_USER_RESPONSE assert res == expected_user_id - code42_high_risk_employee_mock.detectionlists.high_risk_employee.add.assert_called_once_with(expected_user_id) + code42_high_risk_employee_mock.detectionlists.high_risk_employee.add.assert_called_once_with( + expected_user_id + ) code42_high_risk_employee_mock.detectionlists.update_user_notes.assert_called_once_with( expected_user_id, "Dummy note" ) @@ -1100,7 +1099,9 @@ def test_highriskemployee_get_all_command_gets_employees_from_multiple_pages( 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 + 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, {"username": "user1@example.com"}) @@ -1131,11 +1132,13 @@ def test_highriskemployee_get_all_command_when_given_risk_tags_only_gets_employe assert code42_high_risk_employee_mock.detectionlists.high_risk_employee.get_all.call_count == 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 +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) _, _, res = highriskemployee_get_all_command( client, diff --git a/Packs/Code42/Integrations/Code42/integration-Code42.yml b/Packs/Code42/Integrations/Code42/integration-Code42.yml index a58536ac6310..729155f4e56f 100644 --- a/Packs/Code42/Integrations/Code42/integration-Code42.yml +++ b/Packs/Code42/Integrations/Code42/integration-Code42.yml @@ -205,17 +205,6 @@ script: return alert_query - def return_none_on_error(func): - @logger - def wrapper(*args, **kwargs): - try: - return func(*args, **kwargs) - except Exception as ex: - LOG(str(ex)) - return None - return wrapper - - def _get_all_high_risk_employees_from_page(page, risk_tags): res = [] for employee in page["items"]: @@ -242,7 +231,6 @@ script: self._sdk = sdk or py42.sdk.from_local_account(base_url, auth[0], auth[1]) py42.settings.set_user_agent_suffix("Cortex XSOAR") - @return_none_on_error def add_user_to_departing_employee(self, username, departure_date=None, note=None): user_id = self.get_user_id(username) self._sdk.detectionlists.departing_employee.add(user_id, departure_date=departure_date) @@ -250,13 +238,11 @@ script: self._sdk.detectionlists.update_user_notes(user_id, note) return user_id - @return_none_on_error def remove_user_from_departing_employee(self, username): user_id = self.get_user_id(username) self._sdk.detectionlists.departing_employee.remove(user_id) return user_id - @return_none_on_error def get_all_departing_employees(self): res = [] pages = self._sdk.detectionlists.departing_employee.get_all() @@ -265,7 +251,6 @@ script: res.extend(employees) return res - @return_none_on_error def add_user_to_high_risk_employee(self, username, note=None): user_id = self.get_user_id(username) self._sdk.detectionlists.high_risk_employee.add(user_id) @@ -273,25 +258,21 @@ script: self._sdk.detectionlists.update_user_notes(user_id, note) return user_id - @return_none_on_error def remove_user_from_high_risk_employee(self, username): user_id = self.get_user_id(username) self._sdk.detectionlists.high_risk_employee.remove(user_id) return user_id - @return_none_on_error def add_user_risk_tags(self, username, risk_tags): user_id = self.get_user_id(username) self._sdk.detectionlists.add_user_risk_tags(user_id, risk_tags) return user_id - @return_none_on_error def remove_user_risk_tags(self, username, risk_tags): user_id = self.get_user_id(username) self._sdk.detectionlists.remove_user_risk_tags(user_id, risk_tags) return user_id - @return_none_on_error def get_all_high_risk_employees(self, risk_tags=None): if isinstance(risk_tags, str): risk_tags = [risk_tags] @@ -301,33 +282,27 @@ script: res.extend(_get_all_high_risk_employees_from_page(page, risk_tags)) return res - @return_none_on_error def fetch_alerts(self, start_time, event_severity_filter): query = _create_alert_query(event_severity_filter, start_time) res = self._sdk.alerts.search(query) return res["alerts"] - @return_none_on_error def get_alert_details(self, alert_id): res = self._sdk.alerts.get_details(alert_id) return res["alerts"][0] - @return_none_on_error def resolve_alert(self, id): self._sdk.alerts.resolve(id) return id - @return_none_on_error def get_current_user(self): res = self._sdk.users.get_current() return res - @return_none_on_error def get_user_id(self, username): res = self._sdk.users.get_by_username(username) return res["users"][0]["userUid"] - @return_none_on_error def search_file_events(self, payload): res = self._sdk.securitydata.search_file_events(payload) return res["fileEvents"] @@ -548,6 +523,10 @@ script: return {v: obj.get(k) for k, v in context_mapper.items() if obj.get(k)} + def create_command_error_message(cmd, ex): + return "Failed to execute command {0} command. Error: {1}".format(cmd, str(ex)) + + """Commands""" @@ -556,8 +535,11 @@ script: def alert_get_command(client, args): code42_securityalert_context = [] - alert = client.get_alert_details(args["id"]) - if alert: + 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( @@ -566,36 +548,40 @@ script: headers=SECURITY_ALERT_HEADERS, ) return readable_outputs, {"Code42.SecurityAlert": code42_securityalert_context}, alert - else: - return "No results found", {}, {} + except Exception as e: + return_error(create_command_error_message(demisto.command(), e)) @logger def alert_resolve_command(client, args): code42_security_alert_context = [] - alert_id = client.resolve_alert(args["id"]) - if not alert_id: - return "No results found", {}, {} + try: + alert_id = client.resolve_alert(args["id"]) - # 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_security_alert_context.append(code42_context) - readable_outputs = tableToMarkdown( - "Code42 Security Alert Resolved", - code42_security_alert_context, - headers=SECURITY_ALERT_HEADERS, - ) - return ( - readable_outputs, - {"Code42.SecurityAlert": code42_security_alert_context}, - alert_details, - ) + 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_security_alert_context.append(code42_context) + readable_outputs = tableToMarkdown( + "Code42 Security Alert Resolved", + code42_security_alert_context, + headers=SECURITY_ALERT_HEADERS, + ) + return ( + readable_outputs, + {"Code42.SecurityAlert": code42_security_alert_context}, + alert_details, + ) + except Exception as e: + return_error(create_command_error_message(demisto.command(), e)) @logger @@ -604,40 +590,40 @@ script: departing_date = args.get("departuredate") username = args["username"] note = args.get("note") - user_id = client.add_user_to_departing_employee(username, departing_date, note) - if not user_id: - return_error(message="Could not add user to the Departing Employee List") - - de_context = { - "UserID": user_id, - "Username": username, - "DepartureDate": departing_date, - "Note": note, - } - readable_outputs = tableToMarkdown("Code42 Departing Employee List User Added", de_context) - return readable_outputs, {"Code42.DepartingEmployee": de_context}, user_id + try: + user_id = client.add_user_to_departing_employee(username, departing_date, note) + de_context = { + "UserID": user_id, + "Username": username, + "DepartureDate": departing_date, + "Note": note, + } + readable_outputs = tableToMarkdown("Code42 Departing Employee List User Added", de_context) + return readable_outputs, {"Code42.DepartingEmployee": de_context}, user_id + except Exception as e: + return_error(create_command_error_message(demisto.command(), e)) @logger def departingemployee_remove_command(client, args): username = args["username"] - user_id = client.remove_user_from_departing_employee(username) - if user_id: + try: + user_id = client.remove_user_from_departing_employee(username) de_context = {"UserID": user_id, "Username": username} readable_outputs = tableToMarkdown( "Code42 Departing Employee List User Removed", de_context ) return readable_outputs, {"Code42.DepartingEmployee": de_context}, user_id - else: - return_error(message="Could not remove user from the Departing Employee List") + except Exception as e: + return_error(create_command_error_message(demisto.command(), e)) @logger def departingemployee_get_all_command(client, args): - employees = client.get_all_departing_employees() - if employees is not None: + try: + employees = client.get_all_departing_employees() employees_context = [ { "UserID": e["userId"], @@ -653,8 +639,8 @@ script: {"Code42.DepartingEmployee(val.UserID && val.UserID == obj.UserID)": employees_context}, employees, ) - else: - return_error(message="Could not get all Departing Employees") + except Exception as e: + return_error(create_command_error_message(demisto.command(), e)) @logger @@ -662,36 +648,36 @@ script: def highriskemployee_add_command(client, args): username = args["username"] note = args.get("note") - user_id = client.add_user_to_high_risk_employee(username, note) - if not user_id: - return_error(message="Could not add user to the High Risk Employee List") - - 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 + 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 readable_outputs, {"Code42.HighRiskEmployee": hr_context}, user_id + except Exception as e: + return_error(create_command_error_message(demisto.command(), e)) @logger def highriskemployee_remove_command(client, args): username = args["username"] - user_id = client.remove_user_from_high_risk_employee(username) - if user_id: + 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 readable_outputs, {"Code42.HighRiskEmployee": hr_context}, user_id - else: - return_error(message="Could not remove user from the High Risk Employee List") + except Exception as e: + return_error(create_command_error_message(demisto.command(), e)) @logger def highriskemployee_get_all_command(client, args): tags = args.get("risktags") - employees = client.get_all_high_risk_employees(tags) - if employees is not None: + try: + employees = client.get_all_high_risk_employees(tags) employees_context = [ {"UserID": e["userId"], "Username": e["userName"], "Note": e["notes"]} for e in employees @@ -702,8 +688,8 @@ script: {"Code42.HighRiskEmployee(val.UserID && val.UserID == obj.UserID)": employees_context}, employees, ) - else: - return_error(message="Could not get all High Risk Employees") + except Exception as e: + return_error(create_command_error_message(demisto.command(), e)) @logger @@ -711,13 +697,13 @@ script: def highriskemployee_add_risk_tags_command(client, args): username = args["username"] tags = args["risktags"] - user_id = client.add_user_risk_tags(username, tags) - if user_id: + 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 readable_outputs, {"Code42.HighRiskEmployee": rt_context}, user_id - else: - return_error(message="Could not add user risk tags") + except Exception as e: + return_error(create_command_error_message(demisto.command(), e)) @logger @@ -725,13 +711,13 @@ script: def highriskemployee_remove_risk_tags_command(client, args): username = args["username"] tags = args["risktags"] - user_id = client.remove_user_risk_tags(username, tags) - if user_id: + 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 readable_outputs, {"Code42.HighRiskEmployee": rt_context}, user_id - else: - return_error(message="Could not remove user risk tags") + except Exception as e: + return_error(create_command_error_message(demisto.command(), e)) def _create_incident_from_alert_details(details): From ee2ad7f9dda8113c19ed72584762380f0e80de65 Mon Sep 17 00:00:00 2001 From: Juliya Smith Date: Mon, 22 Jun 2020 13:42:59 +0000 Subject: [PATCH 17/17] Improve error messages --- Packs/Code42/Integrations/Code42/Code42.py | 12 ++++++++---- Packs/Code42/Integrations/Code42/Code42_test.py | 14 ++++++++++++++ .../Integrations/Code42/integration-Code42.yml | 12 ++++++++---- 3 files changed, 30 insertions(+), 8 deletions(-) diff --git a/Packs/Code42/Integrations/Code42/Code42.py b/Packs/Code42/Integrations/Code42/Code42.py index 9a1b82c5e9fe..8d83633fe331 100644 --- a/Packs/Code42/Integrations/Code42/Code42.py +++ b/Packs/Code42/Integrations/Code42/Code42.py @@ -217,8 +217,10 @@ def fetch_alerts(self, start_time, event_severity_filter): return res["alerts"] def get_alert_details(self, alert_id): - res = self._sdk.alerts.get_details(alert_id) - return res["alerts"][0] + res = self._sdk.alerts.get_details(alert_id)["alerts"] + if not res: + raise Exception("No alert found with ID {0}.".format(alert_id)) + return res[0] def resolve_alert(self, id): self._sdk.alerts.resolve(id) @@ -229,8 +231,10 @@ def get_current_user(self): return res def get_user_id(self, username): - res = self._sdk.users.get_by_username(username) - return res["users"][0]["userUid"] + res = self._sdk.users.get_by_username(username)["users"] + if not res: + raise Exception("No user found with username {0}.".format(username)) + return res[0]["userUid"] def search_file_events(self, payload): res = self._sdk.securitydata.search_file_events(payload) diff --git a/Packs/Code42/Integrations/Code42/Code42_test.py b/Packs/Code42/Integrations/Code42/Code42_test.py index 7b7e7e5dd6bf..e2d7dba7ce58 100644 --- a/Packs/Code42/Integrations/Code42/Code42_test.py +++ b/Packs/Code42/Integrations/Code42/Code42_test.py @@ -925,6 +925,20 @@ def get_empty_detectionlist_response(mocker, base_text): """TESTS""" +def test_client_when_no_alert_found_raises_exception(code42_sdk_mock): + code42_sdk_mock.alerts.get_details.return_value = """{'type$': 'ALERT_DETAILS_RESPONSE', 'alerts': []}""" + client = create_client(code42_sdk_mock) + with pytest.raises(Exception): + client.get_alert_details("mock-id") + + +def test_client_when_no_user_found_raises_exception(code42_sdk_mock): + code42_sdk_mock.users.get_by_username.return_value = """{'totalCount': 0, 'users': []}""" + client = create_client(code42_sdk_mock) + with pytest.raises(Exception): + client.get_user_id("test@example.com") + + def test_build_query_payload(): query = build_query_payload(MOCK_SECURITY_DATA_SEARCH_QUERY) assert query.sort_key == MOCK_FILE_EVENT_QUERY_PAYLOAD["srtKey"] diff --git a/Packs/Code42/Integrations/Code42/integration-Code42.yml b/Packs/Code42/Integrations/Code42/integration-Code42.yml index 729155f4e56f..1bf98b61977e 100644 --- a/Packs/Code42/Integrations/Code42/integration-Code42.yml +++ b/Packs/Code42/Integrations/Code42/integration-Code42.yml @@ -288,8 +288,10 @@ script: return res["alerts"] def get_alert_details(self, alert_id): - res = self._sdk.alerts.get_details(alert_id) - return res["alerts"][0] + res = self._sdk.alerts.get_details(alert_id)["alerts"] + if not res: + raise Exception("No alert found with ID {0}.".format(alert_id)) + return res[0] def resolve_alert(self, id): self._sdk.alerts.resolve(id) @@ -300,8 +302,10 @@ script: return res def get_user_id(self, username): - res = self._sdk.users.get_by_username(username) - return res["users"][0]["userUid"] + res = self._sdk.users.get_by_username(username)["users"] + if not res: + raise Exception("No user found with username {0}.".format(username)) + return res[0]["userUid"] def search_file_events(self, payload): res = self._sdk.securitydata.search_file_events(payload)