From e966f8619b37a171d75e6c774673f0aa1d9b3590 Mon Sep 17 00:00:00 2001 From: Juliya Smith Date: Wed, 10 Jun 2020 20:17:38 +0000 Subject: [PATCH 001/293] Update tests --- Packs/Code42/Integrations/Code42/Code42.py | 78 ++++++++++--------- Packs/Code42/Integrations/Code42/Code42.yml | 2 +- .../Code42/Integrations/Code42/Code42_test.py | 2 +- 3 files changed, 42 insertions(+), 40 deletions(-) diff --git a/Packs/Code42/Integrations/Code42/Code42.py b/Packs/Code42/Integrations/Code42/Code42.py index 85545717e75f..7c338c638013 100644 --- a/Packs/Code42/Integrations/Code42/Code42.py +++ b/Packs/Code42/Integrations/Code42/Code42.py @@ -1,12 +1,12 @@ from typing import Optional, Dict, Any -import demistomock as demisto -from CommonServerPython import * + ''' IMPORTS ''' import json import requests -from py42.sdk import SDK +import py42.sdk import py42.settings -from py42.sdk.file_event_query import ( +from py42.sdk.queries.fileevents.file_event_query import FileEventQuery +from py42.sdk.queries.fileevents.filters import ( MD5, SHA256, Actor, @@ -14,16 +14,16 @@ OSHostname, DeviceUsername, ExposureType, - EventType, - FileEventQuery + EventType ) -from py42.sdk.alert_query import ( +from py42.sdk.queries.alerts.alert_query import AlertQuery +from py42.sdk.queries.alerts.filters import ( DateObserved, Severity, - AlertState, - AlertQuery + AlertState ) import time + # Disable insecure warnings requests.packages.urllib3.disable_warnings() @@ -102,29 +102,30 @@ 'Archive': 'ARCHIVE' } -SECURITY_EVENT_HEADERS = ['EventType', 'FileName', 'FileSize', 'FileHostname', 'FileOwner', 'FileCategory', 'DeviceUsername'] +SECURITY_EVENT_HEADERS = ['EventType', 'FileName', 'FileSize', 'FileHostname', 'FileOwner', 'FileCategory', + 'DeviceUsername'] SECURITY_ALERT_HEADERS = ['Type', 'Occurred', 'Username', 'Name', 'Description', 'State', 'ID'] class Code42Client(BaseClient): """ - Client will implement the service API, should not contain Demisto logic. + Client will implement the service API, should not contain Cortex XSOAR logic. Should do requests and return data """ def __init__(self, sdk, base_url, auth, verify=True, proxy=False): super().__init__(base_url, verify=verify, proxy=proxy) - # Create the Code42 SDK instnace - self._sdk = sdk.create_using_local_account(base_url, auth[0], auth[1]) + # Create the Code42 SDK instance + self._sdk = py42.sdk.from_local_account(base_url, auth[0], auth[1]) py42.settings.set_user_agent_suffix("Demisto") def add_user_to_departing_employee(self, username, departure_epoch=None, note=None): - de = self._sdk.employee_case_management.departing_employee try: - res = de.create_departing_employee(username, departure_epoch=departure_epoch, notes=note) + res = self._sdk.detectionlists.departing_employee.add(username, departure_epoch=departure_epoch) + not note or self._sdk.detectionlists.update_user_notes(note) except Exception: return None - return res.json().get('caseId') + return res["userId"] def fetch_alerts(self, start_time, event_severity_filter=None): alert_filter = [] @@ -135,57 +136,56 @@ def fetch_alerts(self, start_time, event_severity_filter=None): alert_filter.append(DateObserved.on_or_after(start_time)) alert_query = AlertQuery(self._sdk.user_context.get_current_tenant_id(), *alert_filter) alert_query.sort_direction = "asc" - alerts = self._sdk.security.alerts try: - res = alerts.search_alerts(alert_query) + res = self._sdk.alerts.search_alerts(alert_query) except Exception: return None - return res.json().get('alerts') + return res['alerts'] def get_alert_details(self, alert_id): - alerts = self._sdk.security.alerts try: - res = alerts.get_query_details([alert_id]) + res = self._sdk.alerts.get_details(alert_id) except Exception: return None else: # There will only ever be one alert since we search on just one ID - return res.json().get('alerts')[0] + return res['alerts'][0] def get_current_user(self): try: res = self._sdk.users.get_current_user() except Exception: return None - return res.json() + return res def remove_user_from_departing_employee(self, username): try: - de = self._sdk.employee_case_management.departing_employee - res = de.get_case_by_username(username) + user_id = self.get_user_id(username) + self._sdk.detectionlists.departing_employee.resolve(user_id) except Exception: return None - case_id = res.json().get('caseId') + return user_id + + def resolve_alert(self, alert_id): try: - de.resolve_departing_employee(case_id) + self._sdk.alerts.resolve(alert_id) except Exception: return None - return case_id + return alert_id - def resolve_alert(self, id): - alerts = self._sdk.security.alerts + def get_user_id(self, username): try: - alerts.resolve_alert(id) + res = self._sdk.users.get_by_username(username) except Exception: return None - return id + return res["users"][0]["userUid"] def search_json(self, payload): try: - res = self._sdk.security.search_file_events(payload) + res = self._sdk.securitydata.search_file_events(payload) except Exception: return None - return res.json().get('fileEvents') + return res['fileEvents'] @logger @@ -340,12 +340,13 @@ def alert_resolve_command(client, args): def departingemployee_add_command(client, args): departure_epoch: Optional[int] # Convert date to epoch + departure_epoch = None if args.get('departuredate'): try: departure_epoch = int(time.mktime(time.strptime(args['departuredate'], '%Y-%m-%d'))) except Exception: return_error(message='Could not add user to Departing Employee Lens: ' - 'unable to parse departure date. Is it in YYYY-MM-DD format?') + 'unable to parse departure date. Is it in YYYY-MM-DD format?') else: departure_epoch = None case = client.add_user_to_departing_employee(args['username'], departure_epoch, args.get('note')) @@ -398,7 +399,7 @@ def fetch_incidents(client, last_run, first_fetch_time, event_severity_filter, if not start_query_time: start_query_time, _ = parse_date_range(first_fetch_time, to_timestamp=True, utc=True) start_query_time /= 1000 - alerts = client.fetch_alerts(start_query_time, demisto.params().get('alert_severity')) + alerts = client.fetch_alerts(start_query_time, event_severity_filter) for alert in alerts: details = client.get_alert_details(alert['id']) incident = { @@ -449,8 +450,9 @@ def securitydata_search_command(client, args): code42_securitydata_context, headers=SECURITY_EVENT_HEADERS ) - return readable_outputs, {'Code42.SecurityData(val.EventID && val.EventID == obj.EventID)': code42_securitydata_context, - 'File': file_context}, file_events + return readable_outputs, { + 'Code42.SecurityData(val.EventID && val.EventID == obj.EventID)': code42_securitydata_context, + 'File': file_context}, file_events else: return 'No results found', {}, {} diff --git a/Packs/Code42/Integrations/Code42/Code42.yml b/Packs/Code42/Integrations/Code42/Code42.yml index f70e7772b347..dd9006d76b52 100644 --- a/Packs/Code42/Integrations/Code42/Code42.yml +++ b/Packs/Code42/Integrations/Code42/Code42.yml @@ -284,7 +284,7 @@ script: description: The alert ID of the resolved alert. type: string description: Resolves a Code42 Security alert. - dockerimage: demisto/py42:1.0.0.6301 + dockerimage: devdemisto/py42:1.0.0.9079 isfetch: true runonce: false subtype: python3 diff --git a/Packs/Code42/Integrations/Code42/Code42_test.py b/Packs/Code42/Integrations/Code42/Code42_test.py index 85cd7e10cadd..f7677611014a 100644 --- a/Packs/Code42/Integrations/Code42/Code42_test.py +++ b/Packs/Code42/Integrations/Code42/Code42_test.py @@ -1,5 +1,5 @@ import json -from py42.sdk import SDK +import py42.sdk from Code42 import ( Code42Client, build_query_payload, From 5f8a51786375c9ff3a2dadaee124b16e7cdab253 Mon Sep 17 00:00:00 2001 From: Juliya Smith Date: Wed, 10 Jun 2020 20:28:52 +0000 Subject: [PATCH 002/293] Fix common python imports --- Packs/Code42/Integrations/Code42/Code42.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Packs/Code42/Integrations/Code42/Code42.py b/Packs/Code42/Integrations/Code42/Code42.py index 7c338c638013..80e58c4a3c41 100644 --- a/Packs/Code42/Integrations/Code42/Code42.py +++ b/Packs/Code42/Integrations/Code42/Code42.py @@ -1,5 +1,6 @@ from typing import Optional, Dict, Any - +import demistomock as demisto +from CommonServerPython import * ''' IMPORTS ''' import json import requests From 955427530229b2523c9052b0bc82ade80d645e34 Mon Sep 17 00:00:00 2001 From: Juliya Smith Date: Thu, 11 Jun 2020 12:37:25 +0000 Subject: [PATCH 003/293] refactorings --- Packs/Code42/Integrations/Code42/Code42.py | 497 ++++---- .../Code42/Integrations/Code42/Code42_test.py | 1103 +++++++---------- 2 files changed, 740 insertions(+), 860 deletions(-) diff --git a/Packs/Code42/Integrations/Code42/Code42.py b/Packs/Code42/Integrations/Code42/Code42.py index 80e58c4a3c41..112eb7ecd2f0 100644 --- a/Packs/Code42/Integrations/Code42/Code42.py +++ b/Packs/Code42/Integrations/Code42/Code42.py @@ -1,7 +1,8 @@ from typing import Optional, Dict, Any import demistomock as demisto from CommonServerPython import * -''' IMPORTS ''' + +""" IMPORTS """ import json import requests import py42.sdk @@ -15,97 +16,100 @@ OSHostname, DeviceUsername, ExposureType, - EventType + EventType, ) from py42.sdk.queries.alerts.alert_query import AlertQuery -from py42.sdk.queries.alerts.filters import ( - DateObserved, - Severity, - AlertState -) +from py42.sdk.queries.alerts.filters import DateObserved, Severity, AlertState import time # Disable insecure warnings requests.packages.urllib3.disable_warnings() -''' CONSTANTS ''' +""" CONSTANTS """ CODE42_EVENT_CONTEXT_FIELD_MAPPER = { - 'eventTimestamp': 'EventTimestamp', - 'createTimestamp': 'FileCreated', - 'deviceUid': 'EndpointID', - 'deviceUserName': 'DeviceUsername', - 'emailFrom': 'EmailFrom', - 'emailRecipients': 'EmailTo', - 'emailSubject': 'EmailSubject', - 'eventId': 'EventID', - 'eventType': 'EventType', - 'fileCategory': 'FileCategory', - 'fileOwner': 'FileOwner', - 'fileName': 'FileName', - 'filePath': 'FilePath', - 'fileSize': 'FileSize', - 'modifyTimestamp': 'FileModified', - 'md5Checksum': 'FileMD5', - 'osHostName': 'FileHostname', - 'privateIpAddresses': 'DevicePrivateIPAddress', - 'publicIpAddresses': 'DevicePublicIPAddress', - 'removableMediaBusType': 'RemovableMediaType', - 'removableMediaCapacity': 'RemovableMediaCapacity', - 'removableMediaMediaName': 'RemovableMediaMediaName', - 'removableMediaName': 'RemovableMediaName', - 'removableMediaSerialNumber': 'RemovableMediaSerialNumber', - 'removableMediaVendor': 'RemovableMediaVendor', - 'sha256Checksum': 'FileSHA256', - 'shared': 'FileShared', - 'sharedWith': 'FileSharedWith', - 'source': 'Source', - 'tabUrl': 'ApplicationTabURL', - 'url': 'FileURL', - 'processName': 'ProcessName', - 'processOwner': 'ProcessOwner', - 'windowTitle': 'WindowTitle', - 'exposure': 'Exposure', - 'sharingTypeAdded': 'SharingTypeAdded' + "eventTimestamp": "EventTimestamp", + "createTimestamp": "FileCreated", + "deviceUid": "EndpointID", + "deviceUserName": "DeviceUsername", + "emailFrom": "EmailFrom", + "emailRecipients": "EmailTo", + "emailSubject": "EmailSubject", + "eventId": "EventID", + "eventType": "EventType", + "fileCategory": "FileCategory", + "fileOwner": "FileOwner", + "fileName": "FileName", + "filePath": "FilePath", + "fileSize": "FileSize", + "modifyTimestamp": "FileModified", + "md5Checksum": "FileMD5", + "osHostName": "FileHostname", + "privateIpAddresses": "DevicePrivateIPAddress", + "publicIpAddresses": "DevicePublicIPAddress", + "removableMediaBusType": "RemovableMediaType", + "removableMediaCapacity": "RemovableMediaCapacity", + "removableMediaMediaName": "RemovableMediaMediaName", + "removableMediaName": "RemovableMediaName", + "removableMediaSerialNumber": "RemovableMediaSerialNumber", + "removableMediaVendor": "RemovableMediaVendor", + "sha256Checksum": "FileSHA256", + "shared": "FileShared", + "sharedWith": "FileSharedWith", + "source": "Source", + "tabUrl": "ApplicationTabURL", + "url": "FileURL", + "processName": "ProcessName", + "processOwner": "ProcessOwner", + "windowTitle": "WindowTitle", + "exposure": "Exposure", + "sharingTypeAdded": "SharingTypeAdded", } CODE42_ALERT_CONTEXT_FIELD_MAPPER = { - 'actor': 'Username', - 'createdAt': 'Occurred', - 'description': 'Description', - 'id': 'ID', - 'name': 'Name', - 'state': 'State', - 'type': 'Type', - 'severity': 'Severity' + "actor": "Username", + "createdAt": "Occurred", + "description": "Description", + "id": "ID", + "name": "Name", + "state": "State", + "type": "Type", + "severity": "Severity", } FILE_CONTEXT_FIELD_MAPPER = { - 'fileName': 'Name', - 'filePath': 'Path', - 'fileSize': 'Size', - 'md5Checksum': 'MD5', - 'sha256Checksum': 'SHA256', - 'osHostName': 'Hostname' + "fileName": "Name", + "filePath": "Path", + "fileSize": "Size", + "md5Checksum": "MD5", + "sha256Checksum": "SHA256", + "osHostName": "Hostname", } CODE42_FILE_TYPE_MAPPER = { - 'SourceCode': 'SOURCE_CODE', - 'Audio': 'AUDIO', - 'Executable': 'EXECUTABLE', - 'Document': 'DOCUMENT', - 'Image': 'IMAGE', - 'PDF': 'PDF', - 'Presentation': 'PRESENTATION', - 'Script': 'SCRIPT', - 'Spreadsheet': 'SPREADSHEET', - 'Video': 'VIDEO', - 'VirtualDiskImage': 'VIRTUAL_DISK_IMAGE', - 'Archive': 'ARCHIVE' + "SourceCode": "SOURCE_CODE", + "Audio": "AUDIO", + "Executable": "EXECUTABLE", + "Document": "DOCUMENT", + "Image": "IMAGE", + "PDF": "PDF", + "Presentation": "PRESENTATION", + "Script": "SCRIPT", + "Spreadsheet": "SPREADSHEET", + "Video": "VIDEO", + "VirtualDiskImage": "VIRTUAL_DISK_IMAGE", + "Archive": "ARCHIVE", } -SECURITY_EVENT_HEADERS = ['EventType', 'FileName', 'FileSize', 'FileHostname', 'FileOwner', 'FileCategory', - 'DeviceUsername'] -SECURITY_ALERT_HEADERS = ['Type', 'Occurred', 'Username', 'Name', 'Description', 'State', 'ID'] +SECURITY_EVENT_HEADERS = [ + "EventType", + "FileName", + "FileSize", + "FileHostname", + "FileOwner", + "FileCategory", + "DeviceUsername", +] +SECURITY_ALERT_HEADERS = ["Type", "Occurred", "Username", "Name", "Description", "State", "ID"] class Code42Client(BaseClient): @@ -117,12 +121,14 @@ class Code42Client(BaseClient): def __init__(self, sdk, base_url, auth, verify=True, proxy=False): super().__init__(base_url, verify=verify, proxy=proxy) # Create the Code42 SDK instance - self._sdk = py42.sdk.from_local_account(base_url, auth[0], auth[1]) - py42.settings.set_user_agent_suffix("Demisto") + self._sdk = sdk or py42.sdk.from_local_account(base_url, auth[0], auth[1]) + py42.settings.set_user_agent_suffix("Cortex XSOAR") def add_user_to_departing_employee(self, username, departure_epoch=None, note=None): try: - res = self._sdk.detectionlists.departing_employee.add(username, departure_epoch=departure_epoch) + res = self._sdk.detectionlists.departing_employee.add( + username, departure_epoch=departure_epoch + ) not note or self._sdk.detectionlists.update_user_notes(note) except Exception: return None @@ -132,16 +138,21 @@ def fetch_alerts(self, start_time, event_severity_filter=None): alert_filter = [] # Create alert filter if event_severity_filter: - alert_filter.append(Severity.is_in(list(map(lambda x: x.upper(), event_severity_filter)))) + if isinstance(event_severity_filter, str): + severity_filter = event_severity_filter.upper() + else: + severity_filter = list(map(lambda x: x.upper(), event_severity_filter)) + alert_filter.append(Severity.is_in(severity_filter)) alert_filter.append(AlertState.eq(AlertState.OPEN)) alert_filter.append(DateObserved.on_or_after(start_time)) - alert_query = AlertQuery(self._sdk.user_context.get_current_tenant_id(), *alert_filter) + tenant_id = self._sdk.usercontext.get_current_tenant_id() + alert_query = AlertQuery(tenant_id, *alert_filter) alert_query.sort_direction = "asc" try: res = self._sdk.alerts.search_alerts(alert_query) except Exception: return None - return res['alerts'] + return res["alerts"] def get_alert_details(self, alert_id): try: @@ -150,7 +161,7 @@ def get_alert_details(self, alert_id): return None else: # There will only ever be one alert since we search on just one ID - return res['alerts'][0] + return res["alerts"][0] def get_current_user(self): try: @@ -186,7 +197,7 @@ def search_json(self, payload): res = self._sdk.securitydata.search_file_events(payload) except Exception: return None - return res['fileEvents'] + return res["fileEvents"] @logger @@ -195,73 +206,85 @@ def build_query_payload(args): Build a query payload combining passed args """ search_args = [] - if args.get('hash'): - if len(args['hash']) == 32: - search_args.append(MD5.eq(args['hash'])) - elif len(args['hash']) == 64: - search_args.append(SHA256.eq(args['hash'])) - if args.get('hostname'): - search_args.append(OSHostname.eq(args['hostname'])) - if args.get('username'): - search_args.append(DeviceUsername.eq(args['username'])) - if args.get('exposure'): + if args.get("hash"): + if len(args["hash"]) == 32: + search_args.append(MD5.eq(args["hash"])) + elif len(args["hash"]) == 64: + search_args.append(SHA256.eq(args["hash"])) + if args.get("hostname"): + search_args.append(OSHostname.eq(args["hostname"])) + if args.get("username"): + search_args.append(DeviceUsername.eq(args["username"])) + if args.get("exposure"): # Because the CLI can't accept lists, convert the args to a list if the type is string. - if isinstance(args['exposure'], str): - args['exposure'] = args['exposure'].split(',') - search_args.append(ExposureType.is_in(args['exposure'])) + if isinstance(args["exposure"], str): + args["exposure"] = args["exposure"].split(",") + search_args.append(ExposureType.is_in(args["exposure"])) # Convert list of search criteria to *args query = FileEventQuery.all(*search_args) - query.page_size = args.get('results') - LOG('File Event Query: {}'.format(query)) + query.page_size = args.get("results") + LOG("File Event Query: {}".format(query)) return str(query) @logger def map_observation_to_security_query(observation, actor): file_categories: Dict[str, Any] - observation_data = json.loads(observation['data']) + observation_data = observation["data"] search_args = [] exp_types = [] - exposure_types = observation_data['exposureTypes'] - begin_time = observation_data['firstActivityAt'] - end_time = observation_data['lastActivityAt'] - if observation['type'] == 'FedEndpointExfiltration': + exposure_types = observation_data["exposureTypes"] + begin_time = observation_data["firstActivityAt"] + end_time = observation_data["lastActivityAt"] + if observation["type"] == "FedEndpointExfiltration": search_args.append(DeviceUsername.eq(actor)) else: search_args.append(Actor.eq(actor)) - search_args.append(EventTimestamp.on_or_after( - int(time.mktime(time.strptime(begin_time.replace('0000000', '000'), "%Y-%m-%dT%H:%M:%S.000Z"))))) - search_args.append(EventTimestamp.on_or_before( - int(time.mktime(time.strptime(end_time.replace('0000000', '000'), "%Y-%m-%dT%H:%M:%S.000Z"))))) + search_args.append( + EventTimestamp.on_or_after( + int( + time.mktime( + time.strptime(begin_time.replace("0000000", "000"), "%Y-%m-%dT%H:%M:%S.000Z") + ) + ) + ) + ) + search_args.append( + EventTimestamp.on_or_before( + int( + time.mktime( + time.strptime(end_time.replace("0000000", "000"), "%Y-%m-%dT%H:%M:%S.000Z") + ) + ) + ) + ) # Determine exposure types based on alert type - if observation['type'] == 'FedCloudSharePermissions': - if 'PublicSearchableShare' in exposure_types: + if observation["type"] == "FedCloudSharePermissions": + if "PublicSearchableShare" in exposure_types: exp_types.append(ExposureType.IS_PUBLIC) - if 'PublicLinkShare' in exposure_types: + if "PublicLinkShare" in exposure_types: exp_types.append(ExposureType.SHARED_VIA_LINK) - elif observation['type'] == 'FedEndpointExfiltration': + elif observation["type"] == "FedEndpointExfiltration": exp_types = exposure_types - search_args.append(EventType.is_in(['CREATED', 'MODIFIED', 'READ_BY_APP'])) + search_args.append(EventType.is_in(["CREATED", "MODIFIED", "READ_BY_APP"])) search_args.append(ExposureType.is_in(exp_types)) # Determine if file categorization is significant - file_categories = { - "filterClause": "OR" - } + file_categories = {"filterClause": "OR"} filters = [] - for filetype in observation_data['fileCategories']: - if filetype['isSignificant']: + for filetype in observation_data["fileCategories"]: + if filetype["isSignificant"]: file_category = { "operator": "IS", "term": "fileCategory", - "value": CODE42_FILE_TYPE_MAPPER.get(filetype['category'], 'UNCATEGORIZED') + "value": CODE42_FILE_TYPE_MAPPER.get(filetype["category"], "UNCATEGORIZED"), } filters.append(file_category) if len(filters): - file_categories['filters'] = filters + file_categories["filters"] = filters search_args.append(json.dumps(file_categories)) # Convert list of search criteria to *args query = FileEventQuery.all(*search_args) - LOG('Alert Observation Query: {}'.format(query)) + LOG("Alert Observation Query: {}".format(query)) return str(query) @@ -272,11 +295,11 @@ def map_to_code42_event_context(obj): if obj.get(k): code42_context[v] = obj.get(k) # FileSharedWith is a special case and needs to be converted to a list - if code42_context.get('FileSharedWith'): + if code42_context.get("FileSharedWith"): shared_list = [] - for shared_with in code42_context['FileSharedWith']: - shared_list.append(shared_with['cloudUsername']) - code42_context['FileSharedWith'] = str(shared_list) + for shared_with in code42_context["FileSharedWith"]: + shared_list.append(shared_with["cloudUsername"]) + code42_context["FileSharedWith"] = str(shared_list) return code42_context @@ -301,40 +324,45 @@ def map_to_file_context(obj): @logger def alert_get_command(client, args): code42_securityalert_context = [] - alert = client.get_alert_details(args['id']) + alert = client.get_alert_details(args["id"]) if alert: code42_context = map_to_code42_alert_context(alert) code42_securityalert_context.append(code42_context) readable_outputs = tableToMarkdown( - f'Code42 Security Alert Results', + f"Code42 Security Alert Results", code42_securityalert_context, - headers=SECURITY_ALERT_HEADERS + headers=SECURITY_ALERT_HEADERS, ) - return readable_outputs, {'Code42.SecurityAlert': code42_securityalert_context}, alert + return readable_outputs, {"Code42.SecurityAlert": code42_securityalert_context}, alert else: - return 'No results found', {}, {} + return "No results found", {}, {} @logger def alert_resolve_command(client, args): - code42_securityalert_context = [] - alert = client.resolve_alert(args['id']) - if alert: - # Retrieve new alert details - updated_alert = client.get_alert_details(args['id']) - if updated_alert: - code42_context = map_to_code42_alert_context(updated_alert) - code42_securityalert_context.append(code42_context) - readable_outputs = tableToMarkdown( - f'Code42 Security Alert Resolved', - code42_securityalert_context, - headers=SECURITY_ALERT_HEADERS - ) - return readable_outputs, {'Code42.SecurityAlert': code42_securityalert_context}, updated_alert - else: - return 'Error retrieving updated alert', {}, {} - else: - return 'No results found', {}, {} + code42_security_alert_context = [] + alert_id = client.resolve_alert(args["id"]) + + if not alert_id: + return "No results found", {}, {} + + # Retrieve new alert details + alert_details = client.get_alert_details(alert_id) + if not alert_details: + return "Error retrieving updated alert", {}, {} + + code42_context = map_to_code42_alert_context(alert_details) + code42_security_alert_context.append(code42_context) + readable_outputs = tableToMarkdown( + f"Code42 Security Alert Resolved", + code42_security_alert_context, + headers=SECURITY_ALERT_HEADERS, + ) + return ( + readable_outputs, + {"Code42.SecurityAlert": code42_security_alert_context}, + alert_details, + ) @logger @@ -342,51 +370,54 @@ def departingemployee_add_command(client, args): departure_epoch: Optional[int] # Convert date to epoch departure_epoch = None - if args.get('departuredate'): + if args.get("departuredate"): try: - departure_epoch = int(time.mktime(time.strptime(args['departuredate'], '%Y-%m-%d'))) + departure_epoch = int(time.mktime(time.strptime(args["departuredate"], "%Y-%m-%d"))) except Exception: - return_error(message='Could not add user to Departing Employee Lens: ' - 'unable to parse departure date. Is it in YYYY-MM-DD format?') - else: - departure_epoch = None - case = client.add_user_to_departing_employee(args['username'], departure_epoch, args.get('note')) - if case: - de_context = { - 'CaseID': case, - 'Username': args['username'], - 'DepartureDate': args.get('departuredate'), - 'Note': args.get('note') - } - readable_outputs = tableToMarkdown( - f'Code42 Departing Employee Lens User Added', - de_context - ) - return readable_outputs, {'Code42.DepartingEmployee': de_context}, case - else: - return_error(message='Could not add user to Departing Employee Lens') + return_error( + message="Could not add user to Departing Employee Lens: " + "unable to parse departure date. Is it in yyyy-MM-dd format?" + ) + user_id = client.add_user_to_departing_employee( + args["username"], departure_epoch, args.get("note") + ) + if not user_id: + return_error(message="Could not add user to Departing Employee List") + + de_context = { + "UserID": user_id, + "Username": args["username"], + "DepartureDate": args.get("departuredate"), + "Note": args.get("note"), + } + readable_outputs = tableToMarkdown(f"Code42 Departing Employee Lens User Added", de_context) + return readable_outputs, {"Code42.DepartingEmployee": de_context}, user_id + @logger def departingemployee_remove_command(client, args): - case = client.remove_user_from_departing_employee(args['username']) + case = client.remove_user_from_departing_employee(args["username"]) if case: - de_context = { - 'CaseID': case, - 'Username': args['username'], - } + de_context = {"CaseID": case, "Username": args["username"]} readable_outputs = tableToMarkdown( - f'Code42 Departing Employee Lens User Removed', - de_context + f"Code42 Departing Employee Lens User Removed", de_context ) - return readable_outputs, {'Code42.DepartingEmployee': de_context}, case + return readable_outputs, {"Code42.DepartingEmployee": de_context}, case else: - return_error(message='Could not remove user from Departing Employee Lens') + return_error(message="Could not remove user from Departing Employee Lens") @logger -def fetch_incidents(client, last_run, first_fetch_time, event_severity_filter, - fetch_limit, include_files, integration_context=None): +def fetch_incidents( + client, + last_run, + first_fetch_time, + event_severity_filter, + fetch_limit, + include_files, + integration_context=None, +): incidents = [] # Determine if there are remaining incidents from last fetch run if integration_context: @@ -395,37 +426,32 @@ def fetch_incidents(client, last_run, first_fetch_time, event_severity_filter, if remaining_incidents: return last_run, remaining_incidents[:fetch_limit], remaining_incidents[fetch_limit:] # Get the last fetch time, if exists - start_query_time = last_run.get('last_fetch') + start_query_time = last_run.get("last_fetch") # Handle first time fetch, fetch incidents retroactively if not start_query_time: start_query_time, _ = parse_date_range(first_fetch_time, to_timestamp=True, utc=True) start_query_time /= 1000 alerts = client.fetch_alerts(start_query_time, event_severity_filter) for alert in alerts: - details = client.get_alert_details(alert['id']) - incident = { - 'name': 'Code42 - {}'.format(details['name']), - 'occurred': details['createdAt'], - } + details = client.get_alert_details(alert["id"]) + incident = {"name": "Code42 - {}".format(details["name"]), "occurred": details["createdAt"]} if include_files: - details['fileevents'] = [] - for obs in details['observations']: - security_data_query = map_observation_to_security_query(obs, details['actor']) + details["fileevents"] = [] + for obs in details["observations"]: + security_data_query = map_observation_to_security_query(obs, details["actor"]) file_events = client.search_json(security_data_query) for event in file_events: # We need to convert certain fields to a stringified list or React.JS will throw an error - if event.get('sharedWith'): - shared_list = [] - for shared_with in event['sharedWith']: - shared_list.append(shared_with['cloudUsername']) - event['sharedWith'] = str(shared_list) - if event.get('privateIpAddresses'): - event['privateIpAddresses'] = str(event['privateIpAddresses']) - details['fileevents'].append(event) - incident['rawJSON'] = json.dumps(details) + if event.get("sharedWith"): + shared_list = [u["cloudUsername"] for u in event["sharedWith"]] + event["sharedWith"] = str(shared_list) + if event.get("privateIpAddresses"): + event["privateIpAddresses"] = str(event["privateIpAddresses"]) + details["fileevents"].append(event) + incident["rawJSON"] = json.dumps(details) incidents.append(incident) save_time = datetime.utcnow().timestamp() - next_run = {'last_fetch': save_time} + next_run = {"last_fetch": save_time} return next_run, incidents[:fetch_limit], incidents[fetch_limit:] @@ -434,8 +460,8 @@ def securitydata_search_command(client, args): code42_securitydata_context = [] file_context = [] # If JSON payload is passed as an argument, ignore all other args and search by JSON payload - if args.get('json') is not None: - file_events = client.search_json(args.get('json')) + if args.get("json") is not None: + file_events = client.search_json(args.get("json")) else: # Build payload payload = build_query_payload(args) @@ -447,80 +473,83 @@ 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', + f"Code42 Security Data Results", code42_securitydata_context, - headers=SECURITY_EVENT_HEADERS + headers=SECURITY_EVENT_HEADERS, + ) + return ( + readable_outputs, + { + "Code42.SecurityData(val.EventID && val.EventID == obj.EventID)": code42_securitydata_context, + "File": file_context, + }, + file_events, ) - return readable_outputs, { - 'Code42.SecurityData(val.EventID && val.EventID == obj.EventID)': code42_securitydata_context, - 'File': file_context}, file_events else: - return 'No results found', {}, {} + return "No results found", {}, {} def test_module(client): - user = client.get_current_user() - if user: - return 'ok' - else: - return 'Invalid credentials or host address. Check that the username and password are correct, \ - that the host is available and reachable, and that you have supplied the full scheme, \ - domain, and port (e.g. https://myhost.code42.com:4285)' + if client.get_current_user(): + return "ok" + return "Invalid credentials or host address. Check that the username and password are correct, \ + that the host is available and reachable, and that you have supplied the full scheme, \ + domain, and port (e.g. https://myhost.code42.com:4285)" def main(): """ PARSE AND VALIDATE INTEGRATION PARAMS """ - username = demisto.params().get('credentials').get('identifier') - password = demisto.params().get('credentials').get('password') - base_url = demisto.params().get('console_url') + username = demisto.params().get("credentials").get("identifier") + password = demisto.params().get("credentials").get("password") + base_url = demisto.params().get("console_url") # Remove trailing slash to prevent wrong URL path to service - verify_certificate = not demisto.params().get('insecure', False) - proxy = demisto.params().get('proxy', False) - LOG(f'Command being called is {demisto.command()}') + verify_certificate = not demisto.params().get("insecure", False) + proxy = demisto.params().get("proxy", False) + LOG(f"Command being called is {demisto.command()}") try: client = Code42Client( - sdk=SDK, base_url=base_url, auth=(username, password), verify=verify_certificate, - proxy=proxy) + proxy=proxy, + ) commands = { - 'code42-alert-get': alert_get_command, - 'code42-alert-resolve': alert_resolve_command, - 'code42-securitydata-search': securitydata_search_command, - 'code42-departingemployee-add': departingemployee_add_command, - 'code42-departingemployee-remove': departingemployee_remove_command + "code42-alert-get": alert_get_command, + "code42-alert-resolve": alert_resolve_command, + "code42-securitydata-search": securitydata_search_command, + "code42-departingemployee-add": departingemployee_add_command, + "code42-departingemployee-remove": departingemployee_remove_command, } command = demisto.command() - if command == 'test-module': + if command == "test-module": # This is the call made when pressing the integration Test button. result = test_module(client) demisto.results(result) - elif command == 'fetch-incidents': + elif command == "fetch-incidents": integration_context = demisto.getIntegrationContext() # Set and define the fetch incidents command to run after activated via integration settings. next_run, incidents, remaining_incidents = fetch_incidents( client=client, last_run=demisto.getLastRun(), - first_fetch_time=demisto.params().get('fetch_time'), - event_severity_filter=demisto.params().get('alert_severity'), - fetch_limit=int(demisto.params().get('fetch_limit')), - include_files=demisto.params().get('include_files'), - integration_context=integration_context + first_fetch_time=demisto.params().get("fetch_time"), + event_severity_filter=demisto.params().get("alert_severity"), + fetch_limit=int(demisto.params().get("fetch_limit")), + include_files=demisto.params().get("include_files"), + integration_context=integration_context, ) demisto.setLastRun(next_run) demisto.incidents(incidents) # Store remaining incidents in integration context - integration_context['remaining_incidents'] = remaining_incidents + integration_context["remaining_incidents"] = remaining_incidents demisto.setIntegrationContext(integration_context) elif command in commands: return_outputs(*commands[command](client, demisto.args())) # Log exceptions except Exception as e: - return_error(f'Failed to execute {demisto.command()} command. Error: {str(e)}') + return_error(f"Failed to execute {demisto.command()} command. Error: {str(e)}") -if __name__ in ('__main__', '__builtin__', 'builtins'): +if __name__ in ("__main__", "__builtin__", "builtins"): main() diff --git a/Packs/Code42/Integrations/Code42/Code42_test.py b/Packs/Code42/Integrations/Code42/Code42_test.py index f7677611014a..3a22d2c02f6c 100644 --- a/Packs/Code42/Integrations/Code42/Code42_test.py +++ b/Packs/Code42/Integrations/Code42/Code42_test.py @@ -1,5 +1,8 @@ import json -import py42.sdk +import pytest +from requests import Response +from py42.sdk import SDKClient +from py42.response import Py42Response from Code42 import ( Code42Client, build_query_payload, @@ -12,313 +15,327 @@ departingemployee_add_command, departingemployee_remove_command, fetch_incidents, - securitydata_search_command + securitydata_search_command, ) import time MOCK_URL = "https://123-fake-api.com" -MOCK_SECURITYDATA_SEARCH_QUERY = { +MOCK_SECURITY_DATA_SEARCH_QUERY = { "hash": "d41d8cd98f00b204e9800998ecf8427e", "hostname": "DESKTOP-0001", "username": "user3@example.com", "exposure": "ApplicationRead", - "results": 50 + "results": 50, } -MOCK_SECURITY_EVENT_RESPONSE = { - "fileEvents": [ +MOCK_SECURITY_EVENT_RESPONSE = """ +{ + "totalCount":3, + "fileEvents":[ { - "actor": None, - "cloudDriveId": None, - "createTimestamp": "2019-02-14T22:16:32.977Z", - "detectionSourceAlias": None, - "deviceUid": "902443375841117412", - "deviceUserName": "user1@example.com", - "directoryId": [ - ], - "domainName": "10.0.1.24", - "emailDlpPolicyNames": None, - "emailFrom": None, - "emailRecipients": None, - "emailSender": None, - "emailSubject": None, - "eventId": "0_39550347-381e-490e-8397-46629a0e7af6_902443373841117412_941153704842724615_952", - "eventTimestamp": "2019-10-02T16:57:31.990Z", - "eventType": "READ_BY_APP", - "exposure": [ - "ApplicationRead" - ], - "fileCategory": "IMAGE", - "fileId": None, - "fileName": "data.jpg", - "fileOwner": "user1", - "filePath": "C:/Users/user1/Pictures/", - "fileSize": 9875, - "fileType": "FILE", - "insertionTimestamp": "2019-02-14T22:22:06.126Z", - "md5Checksum": "8cfe4d76431ee20dd82fbd3778b6396f", - "modifyTimestamp": "2019-02-14T22:16:34.664Z", - "osHostName": "LAPTOP-012", - "privateIpAddresses": [ - "10.0.1.24", - "0:0:0:0:0:0:0:1", - "127.0.0.1", - "fe80:0:0:0:bd2b:9ac6:5b3a:b47f%eth0" - ], - "processName": "\\Device\\HarddiskVolume2\\Users\\user1\\AppData\\Local\\slack\\app-4.3.4\\slack.exe", - "processOwner": "user1", - "publicIpAddress": "126.18.85.1", - "removableMediaBusType": None, - "removableMediaCapacity": None, - "removableMediaMediaName": None, - "removableMediaName": None, - "removableMediaPartitionId": [ - ], - "removableMediaSerialNumber": None, - "removableMediaVendor": None, - "removableMediaVolumeName": [ - ], - "sha256Checksum": "adb54dbe1f8268ce39351bad43eddbc419a08e6db3bbf7eb7b601a5d88b8d03b", - "shared": None, - "sharedWith": [ - ], - "sharingTypeAdded": [ - ], - "source": "Endpoint", - "syncDestination": None, - "tabUrl": None, - "url": None, - "userUid": "902428473202285579", - "windowTitle": [ - "Slack | cats_omg | Sysadmin buddies" - ] + "eventId":"0_1d71796f-af5b-4231-9d8e-df6434da4663_935873453596901068_956171635867906205_5", + "eventType":"READ_BY_APP", + "eventTimestamp":"2020-05-28T12:46:39.838Z", + "insertionTimestamp":"2020-05-28T12:51:50.040Z", + "fieldErrors":[], + "filePath":"C:/Users/QA/Downloads/", + "fileName":"company_secrets.txt", + "fileType":"FILE", + "fileCategory":"IMAGE", + "fileCategoryByBytes":"Image", + "fileCategoryByExtension":"Image", + "fileSize":265122, + "fileOwner":"Test", + "md5Checksum":"9cea266b4e07974df1982ae3b9de92ce", + "sha256Checksum":"34d0c9fc9c907ec374cf7e8ca1ff8a172e36eccee687f0a9b69dd169debb81e1", + "createTimestamp":"2020-05-28T12:43:34.902Z", + "modifyTimestamp":"2020-05-28T12:43:35.105Z", + "deviceUserName":"test@example.com", + "osHostName":"HOSTNAME", + "domainName":"host.docker.internal", + "publicIpAddress":"162.222.47.183", + "privateIpAddresses":["172.20.128.36","127.0.0.1"], + "deviceUid":"935873453596901068", + "userUid":"912098363086307495", + "actor":null, + "directoryId":[], + "source":"Endpoint", + "url":null, + "shared":null, + "sharedWith":[], + "sharingTypeAdded":[], + "cloudDriveId":null, + "detectionSourceAlias":null, + "fileId":null, + "exposure":["ApplicationRead"], + "processOwner":"QA", + "processName":"chrome.exe", + "windowTitle":["Jira"], + "tabUrl":"example.com", + "removableMediaVendor":null, + "removableMediaName":null, + "removableMediaSerialNumber":null, + "removableMediaCapacity":null, + "removableMediaBusType":null, + "removableMediaMediaName":null, + "removableMediaVolumeName":[], + "removableMediaPartitionId":[], + "syncDestination":null, + "emailDlpPolicyNames":null, + "emailSubject":null, + "emailSender":null, + "emailFrom":null, + "emailRecipients":null, + "outsideActiveHours":false, + "mimeTypeByBytes":"image/png", + "mimeTypeByExtension":"image/png", + "mimeTypeMismatch":false, + "printJobName":null, + "printerName":null, + "printedFilesBackupPath":null, + "remoteActivity":"UNKNOWN", + "trusted":false }, { - "actor": "user2@example.com", - "cloudDriveId": "0BUjUO34z2CQnUk9PVA", - "createTimestamp": "2019-09-02T19:55:26.389Z", - "detectionSourceAlias": "Google Drive", - "deviceUid": None, - "deviceUserName": "NAME_NOT_AVAILABLE", - "directoryId": [ - "0BUjQW60z2RMnUk9CFE" - ], - "domainName": None, - "emailDlpPolicyNames": None, - "emailFrom": None, - "emailRecipients": None, - "emailSender": None, - "emailSubject": None, - "eventId": "0798_6go4TW7QFQxF5UuBdCFddpX7ZbB9_1_13b87573-f82f-47aa-891c-6966f6e4ec54", - "eventTimestamp": "2019-10-02T15:00:09.745Z", - "eventType": "CREATED", - "exposure": [], - "fileCategory": "IMAGE", - "fileId": "0798_6go4TW7QFQxF5UuBdCFddpX7ZbB9", - "fileName": "Kitties", - "fileOwner": "user2@example.com", - "filePath": None, - "fileSize": 333114, - "fileType": "FILE", - "insertionTimestamp": "2020-10-02T15:02:18.390Z", - "md5Checksum": "eef8b12d2ed0d6a69fe77699d5640c7b", - "modifyTimestamp": "2019-10-02T14:55:26.389Z", - "osHostName": None, - "privateIpAddresses": [], - "processName": None, - "processOwner": None, - "publicIpAddress": None, - "removableMediaBusType": None, - "removableMediaCapacity": None, - "removableMediaMediaName": None, - "removableMediaName": None, - "removableMediaPartitionId": [], - "removableMediaSerialNumber": None, - "removableMediaVendor": None, - "removableMediaVolumeName": [], - "sha256Checksum": "5e25e54e1cc43ed07c6e888464cb98e5f5343aa7aa485d174d9649be780a17b9", - "shared": "FALSE", - "sharedWith": [], - "sharingTypeAdded": [], - "source": "GoogleDrive", - "syncDestination": None, - "tabUrl": None, - "url": "https://drive.google.com/a/c42se.com/file/d/1tm4_6go4TW7QFQxF5UuBdCFddpX7ZbB9/view?usp=drivesdk", - "userUid": "UNKNOWN", - "windowTitle": [] + "eventId":"0_1d71796f-af5b-4231-9d8e-df6434da4663_935873453596901068_956171635867906205_5", + "eventType":"READ_BY_APP", + "eventTimestamp":"2020-05-28T12:46:39.838Z", + "insertionTimestamp":"2020-05-28T12:51:50.040Z", + "fieldErrors":[], + "filePath":"C:/Users/QA/Downloads/", + "fileName":"data.jpg", + "fileType":"FILE", + "fileCategory":"IMAGE", + "fileCategoryByBytes":"Image", + "fileCategoryByExtension":"Image", + "fileSize":265122, + "fileOwner":"QA", + "md5Checksum":"9cea266b4e07974df1982ae3b9de92ce", + "sha256Checksum":"34d0c9fc9c907ec374cf7e8ca1ff8a172e36eccee687f0a9b69dd169debb81e1", + "createTimestamp":"2020-05-28T12:43:34.902Z", + "modifyTimestamp":"2020-05-28T12:43:35.105Z", + "deviceUserName":"test@example.com", + "osHostName":"TEST'S MAC", + "domainName":"host.docker.internal", + "publicIpAddress":"162.222.47.183", + "privateIpAddresses":["127.0.0.1"], + "deviceUid":"935873453596901068", + "userUid":"912098363086307495", + "actor":null, + "directoryId":[], + "source":"Endpoint", + "url":null, + "shared":null, + "sharedWith":[], + "sharingTypeAdded":[], + "cloudDriveId":null, + "detectionSourceAlias":null, + "fileId":null, + "exposure":["ApplicationRead"], + "processOwner":"QA", + "processName":"chrome.exe", + "windowTitle":["Jira"], + "tabUrl":"example.com/test", + "removableMediaVendor":null, + "removableMediaName":null, + "removableMediaSerialNumber":null, + "removableMediaCapacity":null, + "removableMediaBusType":null, + "removableMediaMediaName":null, + "removableMediaVolumeName":[], + "removableMediaPartitionId":[], + "syncDestination":null, + "emailDlpPolicyNames":null, + "emailSubject":null, + "emailSender":null, + "emailFrom":null, + "emailRecipients":null, + "outsideActiveHours":false, + "mimeTypeByBytes":"image/png", + "mimeTypeByExtension":"image/png", + "mimeTypeMismatch":false, + "printJobName":null, + "printerName":null, + "printedFilesBackupPath":null, + "remoteActivity":"UNKNOWN", + "trusted":false }, { - "actor": None, - "cloudDriveId": None, - "createTimestamp": "2019-08-10T22:22:07.460Z", - "detectionSourceAlias": None, - "deviceUid": "920258207244664650", - "deviceUserName": "user3@example.com", - "directoryId": [ - ], - "domainName": "USER3-DEMO01", - "emailDlpPolicyNames": None, - "emailFrom": None, - "emailRecipients": None, - "emailSender": None, - "emailSubject": None, - "eventId": "0_25e609cd-ccee-4b40-ba62-165f312ed8f4_920258207243264650_940574180289182590_4", - "eventTimestamp": "2019-10-02T16:55:08.772Z", - "eventType": "MODIFIED", - "exposure": [ - "RemovableMedia" - ], - "fileCategory": "PDF", - "fileId": None, - "fileName": "Blueprints.pdf", - "fileOwner": "Everyone", - "filePath": "F:/", - "fileSize": 946814, - "fileType": "FILE", - "insertionTimestamp": "2019-10-02T17:00:01.806Z", - "md5Checksum": "f61e05de73f798b9f43c11b299653894", - "modifyTimestamp": "2019-09-10T22:22:08Z", - "osHostName": "USER3-DEMO01", - "privateIpAddresses": [ - "0:0:0:0:0:0:0:1", - "127.0.0.1", - "172.16.1.1" - ], - "processName": None, - "processOwner": None, - "publicIpAddress": "8.8.14.14", - "removableMediaBusType": "USB", - "removableMediaCapacity": 30751588352, - "removableMediaMediaName": "SanDisk Ultra USB 3.0 Media", - "removableMediaName": "Ultra USB 3.0", - "removableMediaPartitionId": [ - "5b0acc46-0000-0000-0000-100000000000" - ], - "removableMediaSerialNumber": "4C532378360368700544", - "removableMediaVendor": "SanDisk", - "removableMediaVolumeName": [ - "DIGI (F:)" - ], - "sha256Checksum": "92e5bcca6b7d2c081e4169ee293098a76d5887081b6db33b841ab6440dfc08a0", - "shared": None, - "sharedWith": [ - ], - "sharingTypeAdded": [ - ], - "source": "Endpoint", - "syncDestination": None, - "tabUrl": None, - "url": None, - "userUid": "920256648733700844", - "windowTitle": [ - ] + "eventId":"0_1d71796f-af5b-4231-9d8e-df6434da4663_935873453596901068_956171635867906205_5", + "eventType":"READ_BY_APP", + "eventTimestamp":"2020-05-28T12:46:39.838Z", + "insertionTimestamp":"2020-05-28T12:51:50.040Z", + "fieldErrors":[], + "filePath":"C:/Users/QA/Downloads/", + "fileName":"confidential.pdf", + "fileType":"FILE", + "fileCategory":"IMAGE", + "fileCategoryByBytes":"Image", + "fileCategoryByExtension":"Image", + "fileSize":265122, + "fileOwner":"Mock", + "md5Checksum":"9cea266b4e07974df1982ae3b9de92ce", + "sha256Checksum":"34d0c9fc9c907ec374cf7e8ca1ff8a172e36eccee687f0a9b69dd169debb81e1", + "createTimestamp":"2020-05-28T12:43:34.902Z", + "modifyTimestamp":"2020-05-28T12:43:35.105Z", + "deviceUserName":"test@example.com", + "osHostName":"Test's Windows", + "domainName":"host.docker.internal", + "publicIpAddress":"162.222.47.183", + "privateIpAddresses":["0:0:0:0:0:0:0:1","127.0.0.1"], + "deviceUid":"935873453596901068", + "userUid":"912098363086307495", + "actor":null, + "directoryId":[], + "source":"Endpoint", + "url":null, + "shared":null, + "sharedWith":[], + "sharingTypeAdded":[], + "cloudDriveId":null, + "detectionSourceAlias":null, + "fileId":null, + "exposure":["ApplicationRead"], + "processOwner":"QA", + "processName":"chrome.exe", + "windowTitle":["Jira"], + "tabUrl":"example.com/foo", + "removableMediaVendor":null, + "removableMediaName":null, + "removableMediaSerialNumber":null, + "removableMediaCapacity":null, + "removableMediaBusType":null, + "removableMediaMediaName":null, + "removableMediaVolumeName":[], + "removableMediaPartitionId":[], + "syncDestination":null, + "emailDlpPolicyNames":null, + "emailSubject":null, + "emailSender":null, + "emailFrom":null, + "emailRecipients":null, + "outsideActiveHours":false, + "mimeTypeByBytes":"image/png", + "mimeTypeByExtension":"image/png", + "mimeTypeMismatch":false, + "printJobName":null, + "printerName":null, + "printedFilesBackupPath":null, + "remoteActivity":"UNKNOWN", + "trusted":false } ] } +""" MOCK_CODE42_EVENT_CONTEXT = [ { - "DevicePrivateIPAddress": ["10.0.1.24", - "0:0:0:0:0:0:0:1", - "127.0.0.1", - "fe80:0:0:0:bd2b:9ac6:5b3a:b47f%eth0"], - "DeviceUsername": "user1@example.com", - "EndpointID": "902443375841117412", - "EventID": "0_39550347-381e-490e-8397-46629a0e7af6_902443373841117412_941153704842724615_952", - "EventTimestamp": "2019-10-02T16:57:31.990Z", + "ApplicationTabURL": "example.com", + "DevicePrivateIPAddress": ["172.20.128.36", "127.0.0.1"], + "DeviceUsername": "test@example.com", + "EndpointID": "935873453596901068", + "EventID": "0_1d71796f-af5b-4231-9d8e-df6434da4663_935873453596901068_956171635867906205_5", + "EventTimestamp": "2020-05-28T12:46:39.838Z", "EventType": "READ_BY_APP", "Exposure": ["ApplicationRead"], "FileCategory": "IMAGE", - "FileCreated": "2019-02-14T22:16:32.977Z", - "FileHostname": "LAPTOP-012", - "FileMD5": "8cfe4d76431ee20dd82fbd3778b6396f", - "FileModified": "2019-02-14T22:16:34.664Z", - "FileName": "data.jpg", - "FileOwner": "user1", - "FilePath": "C:/Users/user1/Pictures/", - "FileSHA256": "adb54dbe1f8268ce39351bad43eddbc419a08e6db3bbf7eb7b601a5d88b8d03b", - "FileSize": 9875, - "ProcessName": "\\Device\\HarddiskVolume2\\Users\\user1\\AppData\\Local\\slack\\app-4.3.4\\slack.exe", - "ProcessOwner": "user1", + "FileCreated": "2020-05-28T12:43:34.902Z", + "FileHostname": "HOSTNAME", + "FileMD5": "9cea266b4e07974df1982ae3b9de92ce", + "FileModified": "2020-05-28T12:43:35.105Z", + "FileName": "company_secrets.txt", + "FileOwner": "Test", + "FilePath": "C:/Users/QA/Downloads/", + "FileSHA256": "34d0c9fc9c907ec374cf7e8ca1ff8a172e36eccee687f0a9b69dd169debb81e1", + "FileSize": 265122, + "ProcessName": "chrome.exe", + "ProcessOwner": "QA", "Source": "Endpoint", - "WindowTitle": ["Slack | cats_omg | Sysadmin buddies"] + "WindowTitle": ["Jira"], }, { - "DeviceUsername": "NAME_NOT_AVAILABLE", - "EventID": "0798_6go4TW7QFQxF5UuBdCFddpX7ZbB9_1_13b87573-f82f-47aa-891c-6966f6e4ec54", - "EventTimestamp": "2019-10-02T15:00:09.745Z", - "EventType": "CREATED", + "ApplicationTabURL": "example.com/test", + "DevicePrivateIPAddress": ["127.0.0.1"], + "DeviceUsername": "test@example.com", + "EndpointID": "935873453596901068", + "EventID": "0_1d71796f-af5b-4231-9d8e-df6434da4663_935873453596901068_956171635867906205_5", + "EventTimestamp": "2020-05-28T12:46:39.838Z", + "EventType": "READ_BY_APP", + "Exposure": ["ApplicationRead"], "FileCategory": "IMAGE", - "FileCreated": "2019-09-02T19:55:26.389Z", - "FileMD5": "eef8b12d2ed0d6a69fe77699d5640c7b", - "FileModified": "2019-10-02T14:55:26.389Z", - "FileName": "Kitties", - "FileOwner": "user2@example.com", - "FileSHA256": "5e25e54e1cc43ed07c6e888464cb98e5f5343aa7aa485d174d9649be780a17b9", - "FileShared": "FALSE", - "FileSize": 333114, - "FileURL": "https://drive.google.com/a/c42se.com/file/d/1tm4_6go4TW7QFQxF5UuBdCFddpX7ZbB9/view?usp=drivesdk", - "Source": "GoogleDrive" + "FileCreated": "2020-05-28T12:43:34.902Z", + "FileHostname": "TEST'S MAC", + "FileMD5": "9cea266b4e07974df1982ae3b9de92ce", + "FileModified": "2020-05-28T12:43:35.105Z", + "FileName": "data.jpg", + "FileOwner": "QA", + "FilePath": "C:/Users/QA/Downloads/", + "FileSHA256": "34d0c9fc9c907ec374cf7e8ca1ff8a172e36eccee687f0a9b69dd169debb81e1", + "FileSize": 265122, + "ProcessName": "chrome.exe", + "ProcessOwner": "QA", + "Source": "Endpoint", + "WindowTitle": ["Jira"], }, { - "DevicePrivateIPAddress": ["0:0:0:0:0:0:0:1", "127.0.0.1", "172.16.1.1"], - "DeviceUsername": "user3@example.com", - "EndpointID": "920258207244664650", - "EventID": "0_25e609cd-ccee-4b40-ba62-165f312ed8f4_920258207243264650_940574180289182590_4", - "EventTimestamp": "2019-10-02T16:55:08.772Z", - "EventType": "MODIFIED", - "Exposure": ["RemovableMedia"], - "FileCategory": "PDF", - "FileCreated": "2019-08-10T22:22:07.460Z", - "FileHostname": "USER3-DEMO01", - "FileMD5": "f61e05de73f798b9f43c11b299653894", - "FileModified": "2019-09-10T22:22:08Z", - "FileName": "Blueprints.pdf", - "FileOwner": "Everyone", - "FilePath": "F:/", - "FileSHA256": "92e5bcca6b7d2c081e4169ee293098a76d5887081b6db33b841ab6440dfc08a0", - "FileSize": 946814, - "RemovableMediaCapacity": 30751588352, - "RemovableMediaMediaName": "SanDisk Ultra USB 3.0 Media", - "RemovableMediaName": "Ultra USB 3.0", - "RemovableMediaSerialNumber": "4C532378360368700544", - "RemovableMediaType": "USB", - "RemovableMediaVendor": "SanDisk", - "Source": "Endpoint" - } + "ApplicationTabURL": "example.com/foo", + "DevicePrivateIPAddress": ["0:0:0:0:0:0:0:1", "127.0.0.1"], + "DeviceUsername": "test@example.com", + "EndpointID": "935873453596901068", + "EventID": "0_1d71796f-af5b-4231-9d8e-df6434da4663_935873453596901068_956171635867906205_5", + "EventTimestamp": "2020-05-28T12:46:39.838Z", + "EventType": "READ_BY_APP", + "Exposure": ["ApplicationRead"], + "FileCategory": "IMAGE", + "FileCreated": "2020-05-28T12:43:34.902Z", + "FileHostname": "Test's Windows", + "FileMD5": "9cea266b4e07974df1982ae3b9de92ce", + "FileModified": "2020-05-28T12:43:35.105Z", + "FileName": "confidential.pdf", + "FileOwner": "Mock", + "FilePath": "C:/Users/QA/Downloads/", + "FileSHA256": "34d0c9fc9c907ec374cf7e8ca1ff8a172e36eccee687f0a9b69dd169debb81e1", + "FileSize": 265122, + "ProcessName": "chrome.exe", + "ProcessOwner": "QA", + "Source": "Endpoint", + "WindowTitle": ["Jira"], + }, ] MOCK_FILE_CONTEXT = [ { - "Hostname": "LAPTOP-012", - "MD5": "8cfe4d76431ee20dd82fbd3778b6396f", - "Name": "data.jpg", - "Path": "C:/Users/user1/Pictures/", - "SHA256": "adb54dbe1f8268ce39351bad43eddbc419a08e6db3bbf7eb7b601a5d88b8d03b", - "Size": 9875 + "Hostname": "HOSTNAME", + "MD5": "9cea266b4e07974df1982ae3b9de92ce", + "Name": "company_secrets.txt", + "Path": "C:/Users/QA/Downloads/", + "SHA256": "34d0c9fc9c907ec374cf7e8ca1ff8a172e36eccee687f0a9b69dd169debb81e1", + "Size": 265122, }, { - "MD5": "eef8b12d2ed0d6a69fe77699d5640c7b", - "Name": "Kitties", - "SHA256": "5e25e54e1cc43ed07c6e888464cb98e5f5343aa7aa485d174d9649be780a17b9", - "Size": 333114 + "Hostname": "TEST'S MAC", + "MD5": "9cea266b4e07974df1982ae3b9de92ce", + "Name": "data.jpg", + "Path": "C:/Users/QA/Downloads/", + "SHA256": "34d0c9fc9c907ec374cf7e8ca1ff8a172e36eccee687f0a9b69dd169debb81e1", + "Size": 265122, }, { - "Hostname": "USER3-DEMO01", - "MD5": "f61e05de73f798b9f43c11b299653894", - "Name": "Blueprints.pdf", - "Path": "F:/", - "SHA256": "92e5bcca6b7d2c081e4169ee293098a76d5887081b6db33b841ab6440dfc08a0", - "Size": 946814 - } + "Hostname": "Test's Windows", + "MD5": "9cea266b4e07974df1982ae3b9de92ce", + "Name": "confidential.pdf", + "Path": "C:/Users/QA/Downloads/", + "SHA256": "34d0c9fc9c907ec374cf7e8ca1ff8a172e36eccee687f0a9b69dd169debb81e1", + "Size": 265122, + }, ] MOCK_ALERT_RESPONSE = { "alerts": [ { "actor": "user1@example.com", - "createdAt": "2019-10-02T17:02:23.5867670Z", + "createdAt": "2020-05-28T12:50:23.5867670Z", "description": "", "id": "36fb8ca5-0533-4d25-9763-e09d35d60610", "name": "Departing Employee Alert", @@ -327,7 +344,7 @@ "target": "N/A", "tenantId": "fef27d1d-e835-465c-be8f-ac9db7a54684", "type": "FED_ENDPOINT_EXFILTRATION", - "type$": "ALERT_SUMMARY" + "type$": "ALERT_SUMMARY", }, { "actor": "user2@example.com", @@ -340,7 +357,7 @@ "target": "N/A", "tenantId": "fef27d1d-e835-465c-be8f-ac9db7a54684", "type": "FED_CLOUD_SHARE_PERMISSIONS", - "type$": "ALERT_SUMMARY" + "type$": "ALERT_SUMMARY", }, { "actor": "user3@exmaple.com", @@ -353,128 +370,98 @@ "target": "N/A", "tenantId": "fef27d1d-e835-465c-be8f-ac9db7a54684", "type": "FED_ENDPOINT_EXFILTRATION", - "type$": "ALERT_SUMMARY" - } + "type$": "ALERT_SUMMARY", + }, ], - "type$": "ALERT_QUERY_RESPONSE" + "type$": "ALERT_QUERY_RESPONSE", } -MOCK_ALERT_DETAILS_RESPONSE = [ - { - "alerts": [ - { - "actor": "user1@example.com", - "createdAt": "2019-10-02T17:02:23.5867670Z", - "description": "", - "id": "36fb8ca5-0533-4d25-9763-e09d35d60610", - "name": "Departing Employee Alert", - "notes": "Departing Employee Notes", - "observations": [ - { - "data": r"""{"type$":"OBSERVED_ENDPOINT_ACTIVITY","id":"e940d9de-bd73-4665-8b3c-196aca6b8a53", - "sources":["Endpoint"],"exposureTypes":["ApplicationRead"],"firstActivityAt": - "2019-10-02T16:50:00.0000000Z","lastActivityAt":"2019-10-02T16:55:00.0000000Z", - "fileCount":7,"totalFileSize":66119,"fileCategories":[{"type$":"OBSERVED_FILE_CATEGORY", - "category":"SourceCode","fileCount":7, - "totalFileSize":66119,"isSignificant":false}],"syncToServices":[]}""", - "id": "e940d9de-bd73-4665-8b3c-196aca6b8a53", - "observedAt": "2019-10-02T17:00:00.0000000Z", - "type": "FedEndpointExfiltration", - "type$": "OBSERVATION" - } - ], - "ruleId": "c4404ee8-503c-4a21-98f5-37561ee4caf0", - "ruleSource": "Departing Employee", - "severity": "HIGH", - "state": "OPEN", - "target": "N/A", - "tenantId": "fef27d1d-e835-465c-be8f-ac9db7a54684", - "type": "FED_ENDPOINT_EXFILTRATION", - "type$": "ALERT_SUMMARY" - } - ] - }, - { - "alerts": [ - { - "actor": "user2@example.com", - "createdAt": "2019-10-02T17:02:24.2071980Z", - "description": "", - "id": "18ac641d-7d9c-4d37-a48f-c89396c07d03", - "name": "High-Risk Employee Alert", - "notes": "High-Risk Employee Notes", - "observations": [ - { - "data": r"""{"type$":"OBSERVED_CLOUD_SHARE_ACTIVITY","id":"495fb9a2-ab18-4b62-98bd-c141ed776de5", - "sources":["GoogleDrive"],"exposureTypes":["PublicSearchableShare","PublicLinkShare"], - "firstActivityAt":"2019-10-02T16:50:00.0000000Z","lastActivityAt": - "2019-10-02T16:55:00.0000000Z","fileCount":1,"totalFileSize":8089, - "fileCategories":[{"type$":"OBSERVED_FILE_CATEGORY", - "category":"Document","fileCount":1,"totalFileSize":8089,"isSignificant":false}]}""", - "id": "495fb9a2-ab18-4b62-98bd-c141ed776de5", - "observedAt": "2019-10-02T16:57:00.0000000Z", - "type": "FedCloudSharePermissions", - "type$": "OBSERVATION" - } - ], - "ruleId": "c4404ee8-503c-4a21-98f5-37561ee4caf0", - "ruleSource": "High-Risk Employee", - "severity": "MEDIUM", - "state": "OPEN", - "target": "N/A", - "tenantId": "fef27d1d-e835-465c-be8f-ac9db7a54684", - "type": "FED_CLOUD_SHARE_PERMISSIONS", - "type$": "ALERT_SUMMARY" - } - ] - }, - { - "alerts": [ +MOCK_ALERT_DETAILS_RESPONSE = """{ + "type$": "ALERT_DETAILS_RESPONSE", + "alerts": [ + {"type$": "ALERT_DETAILS", + "tenantId": "1d71796f-af5b-4231-9d8e-df6434da4663", + "type": "FED_ENDPOINT_EXFILTRATION", + "name": "Departing Employee Alert", + "description": "Cortex XSOAR is cool.", + "actor": "user1@example.com", + "actorId": "912098363086307495", + "target": "N/A", + "severity": "HIGH", + "ruleId": "4576576e-13cb-4f88-be3a-ee77739de649", + "ruleSource": "Alerting", + "id": "36fb8ca5-0533-4d25-9763-e09d35d60610", + "createdAt": "2019-10-02T17:02:23.5867670Z", + "state": "OPEN", + "observations": [ { - "actor": "user3@example.com", - "createdAt": "2019-10-02T17:03:28.2885720Z", - "description": "", - "id": "3137ff1b-b824-42e4-a476-22bccdd8ddb8", - "name": "Custom Alert 1", - "notes": "Removable Media Alert", - "observations": [ - { - "data": r"""{"type$":"OBSERVED_ENDPOINT_ACTIVITY","id":"a1fac38d-4816-4090-bf1c-9c429f6265f0", - "sources":["Endpoint"],"exposureTypes":["RemovableMedia"],"firstActivityAt": - "2019-10-02T16:45:00.0000000Z","lastActivityAt":"2019-10-02T16:50:00.0000000Z","fileCount":4, - "totalFileSize":997653,"fileCategories":[{"type$":"OBSERVED_FILE_CATEGORY", - "category":"Document","fileCount":2,"totalFileSize":50839,"isSignificant":false}, - {"type$":"OBSERVED_FILE_CATEGORY","category":"Pdf","fileCount":2, - "totalFileSize":946814,"isSignificant":false}],"syncToServices":[]}""", - "id": "a1fac38d-4816-4090-bf1c-9c429f6265f0", - "observedAt": "2019-10-02T17:00:00.0000000Z", - "type": "FedEndpointExfiltration", - "type$": "OBSERVATION" + "type$": "OBSERVATION", + "id": "240526fc-3a32-4755-85ab-c6ee6e7f31ce", + "observedAt": "2020-05-28T12:50:00.0000000Z", + "type": "FedEndpointExfiltration", + "data": { + "type$": "OBSERVED_ENDPOINT_ACTIVITY", + "id": "240526fc-3a32-4755-85ab-c6ee6e7f31ce", + "sources": ["Endpoint"], + "exposureTypes": ["ApplicationRead"], + "firstActivityAt": "2020-05-28T12:50:00.0000000Z", + "lastActivityAt": "2020-05-28T12:50:00.0000000Z", + "fileCount": 3, + "totalFileSize": 533846, + "fileCategories": [ + { + "type$": "OBSERVED_FILE_CATEGORY", + "category": "Image", + "fileCount": 3, + "totalFileSize": 533846, + "isSignificant": false + } + ], + "files": [ + { + "type$": "OBSERVED_FILE", + "eventId": "0_1d71796f-af5b-4231-9d8e-df6434da4663_935873453596901068_956171635867906205_5", + "path": "C:/Users/QA/Downloads/", + "name": "Customers..jpg", + "category": "Image", + "size": 265122 + }, + { + "type$": "OBSERVED_FILE", + "eventId": "0_1d71796f-af5b-4231-9d8e-df6434da4663_935873453596901068_956171635867906205_6", + "path": "C:/Users/QA/Downloads/", + "name": "data.png", + "category": "Image", + "size": 129129 + }, + { + "type$": "OBSERVED_FILE", + "eventId": "0_1d71796f-af5b-4231-9d8e-df6434da4663_935873453596901068_956171635867906205_7", + "path": "C:/Users/QA/Downloads/", + "name": "company_secrets.ps", + "category": "Image", + "size": 139595 + } + ], + "syncToServices": [], + "sendingIpAddresses": ["127.0.0.1"] } - ], - "ruleId": "dcee3e9c-c914-424a-bb23-bb60f0ae9f0f", - "ruleSource": "Alerting", - "severity": "LOW", - "state": "OPEN", - "target": "N/A", - "tenantId": "fef27d1d-e835-465c-be8f-ac9db7a54684", - "type": "FED_ENDPOINT_EXFILTRATION", - "type$": "ALERT_SUMMARY" - } - ] - } -] - + } + ] + } + ] +}""" MOCK_CODE42_ALERT_CONTEXT = [ { "ID": "36fb8ca5-0533-4d25-9763-e09d35d60610", "Name": "Departing Employee Alert", + "Description": "Cortex XSOAR is cool.", "Occurred": "2019-10-02T17:02:23.5867670Z", "Severity": "HIGH", "State": "OPEN", "Type": "FED_ENDPOINT_EXFILTRATION", - "Username": "user1@example.com" + "Username": "user1@example.com", }, { "ID": "18ac641d-7d9c-4d37-a48f-c89396c07d03", @@ -483,7 +470,7 @@ "Severity": "MEDIUM", "State": "OPEN", "Type": "FED_CLOUD_SHARE_PERMISSIONS", - "Username": "user2@example.com" + "Username": "user2@example.com", }, { "ID": "3137ff1b-b824-42e4-a476-22bccdd8ddb8", @@ -492,11 +479,11 @@ "Severity": "LOW", "State": "OPEN", "Type": "FED_ENDPOINT_EXFILTRATION", - "Username": "user3@example.com" - } + "Username": "user3@example.com", + }, ] -MOCK_QUERY_PAYLOAD = { +MOCK_FILE_EVENT_QUERY_PAYLOAD = { "groupClause": "AND", "groups": [ { @@ -505,45 +492,27 @@ { "operator": "IS", "term": "md5Checksum", - "value": "d41d8cd98f00b204e9800998ecf8427e" + "value": "d41d8cd98f00b204e9800998ecf8427e", } - ] + ], }, { "filterClause": "AND", - "filters": [ - { - "operator": "IS", - "term": "osHostName", - "value": "DESKTOP-0001" - } - ] + "filters": [{"operator": "IS", "term": "osHostName", "value": "DESKTOP-0001"}], }, { "filterClause": "AND", - "filters": [ - { - "operator": "IS", - "term": "deviceUserName", - "value": "user3@example.com" - } - ] + "filters": [{"operator": "IS", "term": "deviceUserName", "value": "user3@example.com"}], }, { "filterClause": "AND", - "filters": [ - { - "operator": "IS", - "term": "exposure", - "value": "ApplicationRead" - } - ] - } + "filters": [{"operator": "IS", "term": "exposure", "value": "ApplicationRead"}], + }, ], "pgNum": 1, "pgSize": 50, "srtDir": "asc", - "srtKey": "eventId" + "srtKey": "eventId", } MOCK_OBSERVATION_QUERIES = [ @@ -553,12 +522,8 @@ { "filterClause": "AND", "filters": [ - { - "operator": "IS", - "term": "deviceUserName", - "value": "user1@example.com" - } - ] + {"operator": "IS", "term": "deviceUserName", "value": "user1@example.com"} + ], }, { "filterClause": "AND", @@ -566,9 +531,9 @@ { "operator": "ON_OR_AFTER", "term": "eventTimestamp", - "value": "2019-10-02T16:50:00.000Z" + "value": "2020-05-28T12:50:00.000Z", } - ] + ], }, { "filterClause": "AND", @@ -576,58 +541,34 @@ { "operator": "ON_OR_BEFORE", "term": "eventTimestamp", - "value": "2019-10-02T16:55:00.000Z" + "value": "2020-05-28T12:50:00.000Z", } - ] + ], }, { "filterClause": "OR", "filters": [ - { - "operator": "IS", - "term": "eventType", - "value": "CREATED" - }, - { - "operator": "IS", - "term": "eventType", - "value": "MODIFIED" - }, - { - "operator": "IS", - "term": "eventType", - "value": "READ_BY_APP" - } - ] + {"operator": "IS", "term": "eventType", "value": "CREATED"}, + {"operator": "IS", "term": "eventType", "value": "MODIFIED"}, + {"operator": "IS", "term": "eventType", "value": "READ_BY_APP"}, + ], }, { "filterClause": "AND", - "filters": [ - { - "operator": "IS", - "term": "exposure", - "value": "ApplicationRead" - } - ] + "filters": [{"operator": "IS", "term": "exposure", "value": "ApplicationRead"}], }, ], "pgNum": 1, - "pgSize": 100, + "pgSize": 10000, "srtDir": "asc", - "srtKey": "eventId" + "srtKey": "eventId", }, { "groupClause": "AND", "groups": [ { "filterClause": "AND", - "filters": [ - { - "operator": "IS", - "term": "actor", - "value": "user2@example.com" - } - ] + "filters": [{"operator": "IS", "term": "actor", "value": "user2@example.com"}], }, { "filterClause": "AND", @@ -635,9 +576,9 @@ { "operator": "ON_OR_AFTER", "term": "eventTimestamp", - "value": "2019-10-02T16:50:00.000Z" + "value": "2019-10-02T16:50:00.000Z", } - ] + ], }, { "filterClause": "AND", @@ -645,30 +586,22 @@ { "operator": "ON_OR_BEFORE", "term": "eventTimestamp", - "value": "2019-10-02T16:55:00.000Z" + "value": "2019-10-02T16:55:00.000Z", } - ] + ], }, { "filterClause": "OR", "filters": [ - { - "operator": "IS", - "term": "exposure", - "value": "IsPublic" - }, - { - "operator": "IS", - "term": "exposure", - "value": "SharedViaLink" - } - ] - } + {"operator": "IS", "term": "exposure", "value": "IsPublic"}, + {"operator": "IS", "term": "exposure", "value": "SharedViaLink"}, + ], + }, ], "pgNum": 1, - "pgSize": 100, + "pgSize": 10000, "srtDir": "asc", - "srtKey": "eventId" + "srtKey": "eventId", }, { "groupClause": "AND", @@ -676,12 +609,8 @@ { "filterClause": "AND", "filters": [ - { - "operator": "IS", - "term": "deviceUserName", - "value": "user3@example.com" - } - ] + {"operator": "IS", "term": "deviceUserName", "value": "user3@example.com"} + ], }, { "filterClause": "AND", @@ -689,9 +618,9 @@ { "operator": "ON_OR_AFTER", "term": "eventTimestamp", - "value": "2019-10-02T16:45:00.000Z" + "value": "2019-10-02T16:50:00.000Z", } - ] + ], }, { "filterClause": "AND", @@ -699,268 +628,190 @@ { "operator": "ON_OR_BEFORE", "term": "eventTimestamp", - "value": "2019-10-02T16:50:00.000Z" + "value": "2019-10-02T16:50:00.000Z", } - ] + ], }, { "filterClause": "OR", "filters": [ - { - "operator": "IS", - "term": "eventType", - "value": "CREATED" - }, - { - "operator": "IS", - "term": "eventType", - "value": "MODIFIED" - }, - { - "operator": "IS", - "term": "eventType", - "value": "READ_BY_APP" - } - ] + {"operator": "IS", "term": "eventType", "value": "CREATED"}, + {"operator": "IS", "term": "eventType", "value": "MODIFIED"}, + {"operator": "IS", "term": "eventType", "value": "READ_BY_APP"}, + ], }, { "filterClause": "AND", - "filters": [ - { - "operator": "IS", - "term": "exposure", - "value": "RemovableMedia" - } - ] - } + "filters": [{"operator": "IS", "term": "exposure", "value": "RemovableMedia"}], + }, ], "pgNum": 1, - "pgSize": 100, + "pgSize": 10000, "srtDir": "asc", - "srtKey": "eventId" - } + "srtKey": "eventId", + }, ] -def create_alert_mocks(requests_mock): - requests_mock.get(MOCK_URL + '/c42api/v3/auth/jwt?useBody=true', json={"data": {"v3_user_token": "faketoken"}}) - requests_mock.get(MOCK_URL + '/api/User/my', json={}) - requests_mock.get(MOCK_URL + '/c42api/v3/customer/my', json={"data": {"tenantUid": "123"}}) - requests_mock.get(MOCK_URL + '/api/ServerEnv', json={"stsBaseUrl": MOCK_URL}) - requests_mock.get(MOCK_URL + '/v1/AlertService-API_URL', text=MOCK_URL + '/svc/api') +@pytest.fixture +def code42_sdk_mock(mocker): + return mocker.MagicMock(spec=SDKClient) -def create_departingemployee_mocks(requests_mock): - requests_mock.get(MOCK_URL + '/c42api/v3/auth/jwt?useBody=true', json={"data": {"v3_user_token": "faketoken"}}) - requests_mock.get(MOCK_URL + '/api/User/my', json={}) - requests_mock.get(MOCK_URL + '/c42api/v3/customer/my', json={"data": {"tenantUid": "123"}}) - requests_mock.get(MOCK_URL + '/api/ServerEnv', json={"stsBaseUrl": MOCK_URL}) - requests_mock.get(MOCK_URL + '/v1/FedObserver-API_URL', text=MOCK_URL + '/svc/api') - requests_mock.get(MOCK_URL + '/v1/employeecasemanagement-API_URL', text=MOCK_URL + '/svc/apiv/v1') - requests_mock.get(MOCK_URL + '/v1/visualization-services-API_URL', text=MOCK_URL + '/svc/apiv/v1') - - -def create_securitydata_mocks(requests_mock): - requests_mock.get(MOCK_URL + '/c42api/v3/auth/jwt?useBody=true', json={"data": {"v3_user_token": "faketoken"}}) - requests_mock.get(MOCK_URL + '/api/User/my', json={}) - requests_mock.get(MOCK_URL + '/c42api/v3/customer/my', json={"data": {"tenantUid": "123"}}) - requests_mock.get(MOCK_URL + '/api/ServerEnv', json={"stsBaseUrl": MOCK_URL}) +def create_mock_code42_sdk_response(mocker, response_text): + response_mock = mocker.MagicMock(spec=Response) + response_mock.text = response_text + return Py42Response(response_mock) def test_build_query_payload(): - query = build_query_payload(MOCK_SECURITYDATA_SEARCH_QUERY) - assert json.loads(query) == MOCK_QUERY_PAYLOAD + query = build_query_payload(MOCK_SECURITY_DATA_SEARCH_QUERY) + assert json.loads(query) == MOCK_FILE_EVENT_QUERY_PAYLOAD def test_map_observation_to_security_query(): - for i in range(0, len(MOCK_ALERT_DETAILS_RESPONSE)): - query = map_observation_to_security_query( - MOCK_ALERT_DETAILS_RESPONSE[i]['alerts'][0]['observations'][0], MOCK_ALERT_DETAILS_RESPONSE[i]['alerts'][0]['actor']) + response = json.loads(MOCK_ALERT_DETAILS_RESPONSE) + alerts = response["alerts"] + for i in range(0, len(alerts)): + observation = alerts[i]["observations"][0] + actor = alerts[i]["actor"] + query = map_observation_to_security_query(observation, actor) assert json.loads(query) == MOCK_OBSERVATION_QUERIES[i] def test_map_to_code42_event_context(): - for i in range(0, len(MOCK_SECURITY_EVENT_RESPONSE['fileEvents'])): - context = map_to_code42_event_context(MOCK_SECURITY_EVENT_RESPONSE['fileEvents'][i]) + response = json.loads(MOCK_SECURITY_EVENT_RESPONSE) + file_events = response["fileEvents"] + for i in range(0, len(file_events)): + context = map_to_code42_event_context(file_events[i]) assert context == MOCK_CODE42_EVENT_CONTEXT[i] def test_map_to_code42_alert_context(): - for i in range(0, len(MOCK_ALERT_DETAILS_RESPONSE)): - context = map_to_code42_alert_context(MOCK_ALERT_DETAILS_RESPONSE[i]['alerts'][0]) + response = json.loads(MOCK_ALERT_DETAILS_RESPONSE) + alerts = response["alerts"] + for i in range(0, len(alerts)): + context = map_to_code42_alert_context(alerts[i]) assert context == MOCK_CODE42_ALERT_CONTEXT[i] def test_map_to_file_context(): - for i in range(0, len(MOCK_SECURITY_EVENT_RESPONSE['fileEvents'])): - context = map_to_file_context(MOCK_SECURITY_EVENT_RESPONSE['fileEvents'][i]) + response = json.loads(MOCK_SECURITY_EVENT_RESPONSE) + file_events = response["fileEvents"] + for i in range(0, len(file_events)): + context = map_to_file_context(file_events[i]) assert context == MOCK_FILE_CONTEXT[i] -def test_alert_get_command(requests_mock): - create_alert_mocks(requests_mock) - requests_mock.post(MOCK_URL + '/svc/api/v1/query-details', json=MOCK_ALERT_DETAILS_RESPONSE[0]) +def test_alert_get_command(code42_sdk_mock, mocker): + response_mock = create_mock_code42_sdk_response(mocker, MOCK_ALERT_DETAILS_RESPONSE) + code42_sdk_mock.alerts.get_details.return_value = response_mock client = Code42Client( - sdk=SDK, - base_url=MOCK_URL, - auth=("123", "123"), - verify=False, - proxy=None + sdk=code42_sdk_mock, base_url=MOCK_URL, auth=("123", "123"), verify=False, proxy=None ) - _, _, res = alert_get_command(client, {'id': '36fb8ca5-0533-4d25-9763-e09d35d60610'}) - assert res['ruleId'] == "c4404ee8-503c-4a21-98f5-37561ee4caf0" + _, _, res = alert_get_command(client, {"id": "36fb8ca5-0533-4d25-9763-e09d35d60610"}) + assert res["ruleId"] == "4576576e-13cb-4f88-be3a-ee77739de649" -def test_alert_resolve_command(requests_mock): - create_alert_mocks(requests_mock) - requests_mock.post(MOCK_URL + '/svc/api/v1/resolve-alert', json={'dummyresponse': True}) - requests_mock.post(MOCK_URL + '/svc/api/v1/query-details', json=MOCK_ALERT_DETAILS_RESPONSE[0]) +def test_alert_resolve_command(code42_sdk_mock): client = Code42Client( - sdk=SDK, - base_url=MOCK_URL, - auth=("123", "123"), - verify=False, - proxy=None + sdk=code42_sdk_mock, base_url=MOCK_URL, auth=("123", "123"), verify=False, proxy=None ) - _, _, res = alert_resolve_command(client, {'id': '36fb8ca5-0533-4d25-9763-e09d35d60610'}) - assert res['id'] == '36fb8ca5-0533-4d25-9763-e09d35d60610' + _, _, res = alert_resolve_command(client, {"id": "36fb8ca5-0533-4d25-9763-e09d35d60610"}) + assert res["id"] == "36fb8ca5-0533-4d25-9763-e09d35d60610" -def test_departingemployee_remove_command(requests_mock): - create_departingemployee_mocks(requests_mock) - requests_mock.post(MOCK_URL + '/svc/api/v1/departingemployee/search', - json={'cases': [{'username': 'user1@example.com', 'caseId': 123, 'tenantId': '123'}]}) - requests_mock.post(MOCK_URL + '/svc/api/v1/departingemployee/details', - json={'username': 'user1@example.com', 'caseId': 123, 'tenantId': '123'}) - requests_mock.post(MOCK_URL + '/svc/api/v1/departingemployee/resolve') +def test_departing_employee_remove_command(code42_sdk_mock): client = Code42Client( - sdk=SDK, - base_url=MOCK_URL, - auth=("123", "123"), - verify=False, - proxy=None + sdk=code42_sdk_mock, base_url=MOCK_URL, auth=("123", "123"), verify=False, proxy=None ) - _, _, res = departingemployee_remove_command(client, {'username': 'user1@example.com'}) + _, _, res = departingemployee_remove_command(client, {"username": "user1@example.com"}) assert res == 123 -def test_departingemployee_add_command(requests_mock): - create_departingemployee_mocks(requests_mock) - requests_mock.post(MOCK_URL + '/svc/api/v1/departingemployee/create', json={'caseId': 123}) +def test_departing_employee_add_command(code42_sdk_mock): client = Code42Client( - sdk=SDK, - base_url=MOCK_URL, - auth=("123", "123"), - verify=False, - proxy=None + sdk=code42_sdk_mock, base_url=MOCK_URL, auth=("123", "123"), verify=False, proxy=None + ) + _, _, res = departingemployee_add_command( + client, + {"username": "user1@example.com", "departuredate": "2020-01-01", "notes": "Dummy note"}, ) - _, _, res = departingemployee_add_command(client, {'username': 'user1@example.com', - 'departuredate': '2020-01-01', 'notes': 'Dummy note'}) assert res == 123 -def test_securitydata_search_command(requests_mock): - create_securitydata_mocks(requests_mock) - requests_mock.post(MOCK_URL + '/forensic-search/queryservice/api/v1/fileevent', json=MOCK_SECURITY_EVENT_RESPONSE) +def test_security_data_search_command(code42_sdk_mock): client = Code42Client( - sdk=SDK, - base_url=MOCK_URL, - auth=("123", "123"), - verify=False, - proxy=None + sdk=code42_sdk_mock, base_url=MOCK_URL, auth=("123", "123"), verify=False, proxy=None ) - _, _, res = securitydata_search_command(client, MOCK_SECURITYDATA_SEARCH_QUERY) + _, _, res = securitydata_search_command(client, MOCK_SECURITY_DATA_SEARCH_QUERY) assert len(res) == 3 -def test_fetch_incidents_first_run(requests_mock, mocker): - create_alert_mocks(requests_mock) - requests_mock.post(MOCK_URL + '/svc/api/v1/query-alerts', json=MOCK_ALERT_RESPONSE) - requests_mock.post(MOCK_URL + '/svc/api/v1/query-details', json=MOCK_ALERT_DETAILS_RESPONSE[0]) - requests_mock.post(MOCK_URL + '/forensic-search/queryservice/api/v1/fileevent', json=MOCK_SECURITY_EVENT_RESPONSE) +def test_fetch_incidents_first_run(code42_sdk_mock): client = Code42Client( - sdk=SDK, - base_url=MOCK_URL, - auth=("123", "123"), - verify=False, - proxy=None + sdk=code42_sdk_mock, base_url=MOCK_URL, auth=("123", "123"), verify=False, proxy=None ) next_run, incidents, remaining_incidents = fetch_incidents( client=client, - last_run={'last_fetch': None}, + last_run={"last_fetch": None}, first_fetch_time="24 hours", event_severity_filter=None, fetch_limit=int("10"), include_files=True, - integration_context=None + integration_context=None, ) assert len(incidents) == 3 - assert next_run['last_fetch'] + assert next_run["last_fetch"] -def test_fetch_incidents_next_run(requests_mock, mocker): +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"))) - create_alert_mocks(requests_mock) - requests_mock.post(MOCK_URL + '/svc/api/v1/query-alerts', json=MOCK_ALERT_RESPONSE) - requests_mock.post(MOCK_URL + '/svc/api/v1/query-details', json=MOCK_ALERT_DETAILS_RESPONSE[0]) - requests_mock.post(MOCK_URL + '/forensic-search/queryservice/api/v1/fileevent', json=MOCK_SECURITY_EVENT_RESPONSE) client = Code42Client( - sdk=SDK, - base_url=MOCK_URL, - auth=("123", "123"), - verify=False, - proxy=None + sdk=code42_sdk_mock, base_url=MOCK_URL, auth=("123", "123"), verify=False, proxy=None ) next_run, incidents, remaining_incidents = fetch_incidents( client=client, - last_run={'last_fetch': mock_timestamp}, + last_run={"last_fetch": mock_timestamp}, first_fetch_time="24 hours", event_severity_filter=None, fetch_limit=int("10"), include_files=True, - integration_context=None + integration_context=None, ) assert len(incidents) == 3 - assert next_run['last_fetch'] + assert next_run["last_fetch"] -def test_fetch_incidents_fetch_limit(requests_mock, mocker): +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"))) - create_alert_mocks(requests_mock) - requests_mock.post(MOCK_URL + '/svc/api/v1/query-alerts', json=MOCK_ALERT_RESPONSE) - requests_mock.post(MOCK_URL + '/svc/api/v1/query-details', json=MOCK_ALERT_DETAILS_RESPONSE[0]) - requests_mock.post(MOCK_URL + '/forensic-search/queryservice/api/v1/fileevent', json=MOCK_SECURITY_EVENT_RESPONSE) client = Code42Client( - sdk=SDK, - base_url=MOCK_URL, - auth=("123", "123"), - verify=False, - proxy=None + sdk=code42_sdk_mock, base_url=MOCK_URL, auth=("123", "123"), verify=False, proxy=None ) next_run, incidents, remaining_incidents = fetch_incidents( client=client, - last_run={'last_fetch': mock_timestamp}, + last_run={"last_fetch": mock_timestamp}, first_fetch_time="24 hours", event_severity_filter=None, fetch_limit=int("2"), include_files=True, - integration_context=None + integration_context=None, ) assert len(incidents) == 2 - assert next_run['last_fetch'] + assert next_run["last_fetch"] assert len(remaining_incidents) == 1 # Run again to get the last incident next_run, incidents, remaining_incidents = fetch_incidents( client=client, - last_run={'last_fetch': mock_timestamp}, + last_run={"last_fetch": mock_timestamp}, first_fetch_time="24 hours", event_severity_filter=None, fetch_limit=int("2"), include_files=True, - integration_context={'remaining_incidents': remaining_incidents} + integration_context={"remaining_incidents": remaining_incidents}, ) assert len(incidents) == 1 - assert next_run['last_fetch'] + assert next_run["last_fetch"] assert not remaining_incidents From c93d008aea0d4a154e25beaa0d81c327ef655f95 Mon Sep 17 00:00:00 2001 From: Juliya Smith Date: Thu, 11 Jun 2020 13:14:02 +0000 Subject: [PATCH 004/293] Beginnings of better mocks --- Packs/Code42/Integrations/Code42/Code42.py | 27 +++++++------------ .../Code42/Integrations/Code42/Code42_test.py | 9 ++++--- 2 files changed, 14 insertions(+), 22 deletions(-) diff --git a/Packs/Code42/Integrations/Code42/Code42.py b/Packs/Code42/Integrations/Code42/Code42.py index 112eb7ecd2f0..8bdb5aae13fe 100644 --- a/Packs/Code42/Integrations/Code42/Code42.py +++ b/Packs/Code42/Integrations/Code42/Code42.py @@ -290,35 +290,26 @@ def map_observation_to_security_query(observation, actor): @logger def map_to_code42_event_context(obj): - code42_context = {} - for (k, v) in CODE42_EVENT_CONTEXT_FIELD_MAPPER.items(): - if obj.get(k): - code42_context[v] = obj.get(k) + code42_context = _map_obj_to_context(obj, CODE42_EVENT_CONTEXT_FIELD_MAPPER) # FileSharedWith is a special case and needs to be converted to a list if code42_context.get("FileSharedWith"): - shared_list = [] - for shared_with in code42_context["FileSharedWith"]: - shared_list.append(shared_with["cloudUsername"]) - code42_context["FileSharedWith"] = str(shared_list) + shared_list = [u["cloudUsername"] for u in code42_context["FileSharedWith"]] + code42_context["FileSharedWith"] = str(shared_list) return code42_context @logger def map_to_code42_alert_context(obj): - code42_context = {} - for (k, v) in CODE42_ALERT_CONTEXT_FIELD_MAPPER.items(): - if obj.get(k): - code42_context[v] = obj.get(k) - return code42_context + return _map_obj_to_context(obj, CODE42_ALERT_CONTEXT_FIELD_MAPPER) @logger def map_to_file_context(obj): - file_context = {} - for (k, v) in FILE_CONTEXT_FIELD_MAPPER.items(): - if obj.get(k): - file_context[v] = obj.get(k) - return file_context + return _map_obj_to_context(obj, FILE_CONTEXT_FIELD_MAPPER) + + +def _map_obj_to_context(obj, context_mapper): + return {v: obj.get(k) for k, v in context_mapper.items() if obj.get(k)} @logger diff --git a/Packs/Code42/Integrations/Code42/Code42_test.py b/Packs/Code42/Integrations/Code42/Code42_test.py index 3a22d2c02f6c..85fcf763d672 100644 --- a/Packs/Code42/Integrations/Code42/Code42_test.py +++ b/Packs/Code42/Integrations/Code42/Code42_test.py @@ -655,7 +655,10 @@ @pytest.fixture def code42_sdk_mock(mocker): - return mocker.MagicMock(spec=SDKClient) + c42_sdk_mock = mocker.MagicMock(spec=SDKClient) + response_mock = create_mock_code42_sdk_response(mocker, MOCK_ALERT_DETAILS_RESPONSE) + c42_sdk_mock.alerts.get_details.return_value = response_mock + return c42_sdk_mock def create_mock_code42_sdk_response(mocker, response_text): @@ -703,9 +706,7 @@ def test_map_to_file_context(): assert context == MOCK_FILE_CONTEXT[i] -def test_alert_get_command(code42_sdk_mock, mocker): - response_mock = create_mock_code42_sdk_response(mocker, MOCK_ALERT_DETAILS_RESPONSE) - code42_sdk_mock.alerts.get_details.return_value = response_mock +def test_alert_get_command(code42_sdk_mock): client = Code42Client( sdk=code42_sdk_mock, base_url=MOCK_URL, auth=("123", "123"), verify=False, proxy=None ) From 0c48d8060a9bd39faf4e464a6698f58b5e93775c Mon Sep 17 00:00:00 2001 From: Juliya Smith Date: Thu, 11 Jun 2020 13:59:33 +0000 Subject: [PATCH 005/293] Everytgin except fetch incidents --- Packs/Code42/Integrations/Code42/Code42.py | 34 +++++------- .../Code42/Integrations/Code42/Code42_test.py | 53 +++++++++++++++++-- 2 files changed, 63 insertions(+), 24 deletions(-) diff --git a/Packs/Code42/Integrations/Code42/Code42.py b/Packs/Code42/Integrations/Code42/Code42.py index 8bdb5aae13fe..d8e62960f2dc 100644 --- a/Packs/Code42/Integrations/Code42/Code42.py +++ b/Packs/Code42/Integrations/Code42/Code42.py @@ -126,22 +126,21 @@ def __init__(self, sdk, base_url, auth, verify=True, proxy=False): def add_user_to_departing_employee(self, username, departure_epoch=None, note=None): try: - res = self._sdk.detectionlists.departing_employee.add( - username, departure_epoch=departure_epoch + user_id = self.get_user_id(username) + self._sdk.detectionlists.departing_employee.add( + user_id, departure_epoch=departure_epoch ) not note or self._sdk.detectionlists.update_user_notes(note) except Exception: return None - return res["userId"] + return user_id def fetch_alerts(self, start_time, event_severity_filter=None): alert_filter = [] # Create alert filter if event_severity_filter: - if isinstance(event_severity_filter, str): - severity_filter = event_severity_filter.upper() - else: - severity_filter = list(map(lambda x: x.upper(), event_severity_filter)) + f = event_severity_filter + severity_filter = (f.upper() if isinstance(f, str) else list(map(lambda x: x.upper(), f))) alert_filter.append(Severity.is_in(severity_filter)) alert_filter.append(AlertState.eq(AlertState.OPEN)) alert_filter.append(DateObserved.on_or_after(start_time)) @@ -159,9 +158,8 @@ def get_alert_details(self, alert_id): res = self._sdk.alerts.get_details(alert_id) except Exception: return None - else: - # There will only ever be one alert since we search on just one ID - return res["alerts"][0] + details = res["alerts"][0] if res["alerts"] else None + return details def get_current_user(self): try: @@ -190,7 +188,7 @@ def get_user_id(self, username): res = self._sdk.users.get_by_username(username) except Exception: return None - return res["users"][0]["userUid"] + return res["users"][0]["userUid"] if res["users"] else None def search_json(self, payload): try: @@ -333,15 +331,15 @@ def alert_get_command(client, args): 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", {}, {} - + # 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( @@ -385,7 +383,6 @@ def departingemployee_add_command(client, args): return readable_outputs, {"Code42.DepartingEmployee": de_context}, user_id - @logger def departingemployee_remove_command(client, args): case = client.remove_user_from_departing_employee(args["username"]) @@ -434,7 +431,7 @@ def fetch_incidents( for event in file_events: # We need to convert certain fields to a stringified list or React.JS will throw an error if event.get("sharedWith"): - shared_list = [u["cloudUsername"] for u in event["sharedWith"]] + shared_list = [u["cloudUsername"] for u in event["sharedWith"]] event["sharedWith"] = str(shared_list) if event.get("privateIpAddresses"): event["privateIpAddresses"] = str(event["privateIpAddresses"]) @@ -501,10 +498,7 @@ def main(): LOG(f"Command being called is {demisto.command()}") try: client = Code42Client( - base_url=base_url, - auth=(username, password), - verify=verify_certificate, - proxy=proxy, + base_url=base_url, auth=(username, password), verify=verify_certificate, proxy=proxy ) commands = { "code42-alert-get": alert_get_command, diff --git a/Packs/Code42/Integrations/Code42/Code42_test.py b/Packs/Code42/Integrations/Code42/Code42_test.py index 85fcf763d672..8cb9433138c4 100644 --- a/Packs/Code42/Integrations/Code42/Code42_test.py +++ b/Packs/Code42/Integrations/Code42/Code42_test.py @@ -653,11 +653,56 @@ ] +MOCK_GET_USER_RESPONSE = """ +{ + "totalCount": 1, + "users": [ + { + "userId": 123456, + "userUid": "123412341234123412", + "status": "Active", + "username": "test.testerson@example.com", + "email": "test.testerson@example.com", + "firstName": "Test", + "lastName": "Testerson", + "quotaInBytes": -1, + "orgId": 1111, + "orgUid": "81111247111106706", + "orgName": "Testers", + "userExtRef": null, + "notes": null, + "active": true, + "blocked": false, + "emailPromo": true, + "invited": false, + "orgType": "ENTERPRISE", + "usernameIsAnEmail": true, + "creationDate": "2019-09-30T21:03:08.587Z", + "modificationDate": "2020-04-10T11:49:49.987Z", + "passwordReset": false, + "localAuthenticationOnly": false, + "licenses": ["admin.securityTools"] + } + ] +}""" + + @pytest.fixture def code42_sdk_mock(mocker): c42_sdk_mock = mocker.MagicMock(spec=SDKClient) - response_mock = create_mock_code42_sdk_response(mocker, MOCK_ALERT_DETAILS_RESPONSE) - c42_sdk_mock.alerts.get_details.return_value = response_mock + + # Setup mock alert details + 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 mock get 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 securitydata search file events + 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 @@ -727,7 +772,7 @@ def test_departing_employee_remove_command(code42_sdk_mock): sdk=code42_sdk_mock, base_url=MOCK_URL, auth=("123", "123"), verify=False, proxy=None ) _, _, res = departingemployee_remove_command(client, {"username": "user1@example.com"}) - assert res == 123 + assert res == "123412341234123412" # value found in GET_USER_RESPONSE def test_departing_employee_add_command(code42_sdk_mock): @@ -738,7 +783,7 @@ def test_departing_employee_add_command(code42_sdk_mock): client, {"username": "user1@example.com", "departuredate": "2020-01-01", "notes": "Dummy note"}, ) - assert res == 123 + assert res == "123412341234123412" def test_security_data_search_command(code42_sdk_mock): From e9609cf14cdf576e69d12a1e47e5a54f3403f39f Mon Sep 17 00:00:00 2001 From: Juliya Smith Date: Thu, 11 Jun 2020 14:22:08 +0000 Subject: [PATCH 006/293] Refactor fetching --- Packs/Code42/Integrations/Code42/Code42.py | 129 +++++++++++++----- .../Code42/Integrations/Code42/Code42_test.py | 36 ++--- 2 files changed, 114 insertions(+), 51 deletions(-) diff --git a/Packs/Code42/Integrations/Code42/Code42.py b/Packs/Code42/Integrations/Code42/Code42.py index d8e62960f2dc..d08ff4daf7bf 100644 --- a/Packs/Code42/Integrations/Code42/Code42.py +++ b/Packs/Code42/Integrations/Code42/Code42.py @@ -7,6 +7,7 @@ import requests import py42.sdk import py42.settings +from datetime import datetime from py42.sdk.queries.fileevents.file_event_query import FileEventQuery from py42.sdk.queries.fileevents.filters import ( MD5, @@ -396,6 +397,95 @@ def departingemployee_remove_command(client, args): return_error(message="Could not remove user from Departing Employee Lens") +def _create_incident_from_alert_details(details): + return {"name": "Code42 - {}".format(details["name"]), "occurred": details["createdAt"]} + + +def _stringify_lists_if_needed(event): + # We need to convert certain fields to a stringified list or React.JS will throw an error + if event.get("sharedWith"): + shared_list = [u["cloudUsername"] for u in event["sharedWith"]] + event["sharedWith"] = str(shared_list) + if event.get("privateIpAddresses"): + event["privateIpAddresses"] = str(event["privateIpAddresses"]) + + +def _process_event_from_observation(event): + _stringify_lists_if_needed(event) + return event + + +class Code42SecurityIncidentFetcher(object): + def __init__(self, + client, + last_run, + first_fetch_time, + event_severity_filter, + fetch_limit, + include_files, + integration_context=None, + ): + self._client = client + self._last_run = last_run + self._first_fetch_time = first_fetch_time + self._event_severity_filter = event_severity_filter + self._fetch_limit = fetch_limit + self._include_files = include_files, + self._integration_context = integration_context + + def fetch(self): + incidents = [] + + remaining_incidents_from_last_run = self._fetch_remaining_incidents_from_last_run() + if remaining_incidents_from_last_run: + return remaining_incidents_from_last_run + + start_query_time = self._get_start_query_time() + alerts = self._fetch_alerts(start_query_time) + + for alert in alerts: + details = self._client.get_alert_details(alert["id"]) + incident = _create_incident_from_alert_details(details) + self._relate_files_to_alert(details) + incident["rawJSON"] = json.dumps(details) + incidents.append(incident) + save_time = datetime.utcnow().timestamp() + next_run = {"last_fetch": save_time} + return next_run, incidents[:self._fetch_limit], incidents[self._fetch_limit:] + + def _fetch_remaining_incidents_from_last_run(self): + if self._integration_context: + remaining_incidents = self._integration_context.get("remaining_incidents") + # return incidents if exists in context. + if remaining_incidents: + return self._last_run, remaining_incidents[:self._fetch_limit], remaining_incidents[self._fetch_limit:] + + def _get_start_query_time(self): + start_query_time = self._try_get_last_fetch_time() + + # Handle first time fetch, fetch incidents retroactively + if not start_query_time: + start_query_time, _ = parse_date_range(self._first_fetch_time, to_timestamp=True, utc=True) + start_query_time /= 1000 + + return start_query_time + + def _try_get_last_fetch_time(self): + return self._last_run.get("last_fetch") + + def _fetch_alerts(self, start_query_time): + return self._client.fetch_alerts(start_query_time, self._event_severity_filter) + + def _relate_files_to_alert(self, alert_details): + for obs in alert_details["observations"]: + file_events = self._get_file_events_from_alert_details(obs, alert_details) + alert_details["fileevents"] = [_process_event_from_observation(e) for e in file_events] + + 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) + + @logger def fetch_incidents( client, @@ -406,41 +496,10 @@ def fetch_incidents( include_files, integration_context=None, ): - incidents = [] - # Determine if there are remaining incidents from last fetch run - if integration_context: - remaining_incidents = integration_context.get("remaining_incidents") - # return incidents if exists in context. - if remaining_incidents: - return last_run, remaining_incidents[:fetch_limit], remaining_incidents[fetch_limit:] - # Get the last fetch time, if exists - start_query_time = last_run.get("last_fetch") - # Handle first time fetch, fetch incidents retroactively - if not start_query_time: - start_query_time, _ = parse_date_range(first_fetch_time, to_timestamp=True, utc=True) - start_query_time /= 1000 - alerts = client.fetch_alerts(start_query_time, event_severity_filter) - for alert in alerts: - details = client.get_alert_details(alert["id"]) - incident = {"name": "Code42 - {}".format(details["name"]), "occurred": details["createdAt"]} - if include_files: - details["fileevents"] = [] - for obs in details["observations"]: - security_data_query = map_observation_to_security_query(obs, details["actor"]) - file_events = client.search_json(security_data_query) - for event in file_events: - # We need to convert certain fields to a stringified list or React.JS will throw an error - if event.get("sharedWith"): - shared_list = [u["cloudUsername"] for u in event["sharedWith"]] - event["sharedWith"] = str(shared_list) - if event.get("privateIpAddresses"): - event["privateIpAddresses"] = str(event["privateIpAddresses"]) - details["fileevents"].append(event) - incident["rawJSON"] = json.dumps(details) - incidents.append(incident) - save_time = datetime.utcnow().timestamp() - next_run = {"last_fetch": save_time} - return next_run, incidents[:fetch_limit], incidents[fetch_limit:] + fetcher = Code42SecurityIncidentFetcher( + client, last_run, first_fetch_time, event_severity_filter, fetch_limit, include_files, integration_context + ) + return fetcher.fetch() @logger diff --git a/Packs/Code42/Integrations/Code42/Code42_test.py b/Packs/Code42/Integrations/Code42/Code42_test.py index 8cb9433138c4..39df48953f07 100644 --- a/Packs/Code42/Integrations/Code42/Code42_test.py +++ b/Packs/Code42/Integrations/Code42/Code42_test.py @@ -21,6 +21,10 @@ MOCK_URL = "https://123-fake-api.com" +MOCK_AUTH = ("123", "123") + +MOCK_FETCH_TIME = "24 hours" + MOCK_SECURITY_DATA_SEARCH_QUERY = { "hash": "d41d8cd98f00b204e9800998ecf8427e", "hostname": "DESKTOP-0001", @@ -753,7 +757,7 @@ 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=("123", "123"), verify=False, proxy=None + sdk=code42_sdk_mock, base_url=MOCK_URL, auth=MOCK_AUTH, verify=False, proxy=None ) _, _, res = alert_get_command(client, {"id": "36fb8ca5-0533-4d25-9763-e09d35d60610"}) assert res["ruleId"] == "4576576e-13cb-4f88-be3a-ee77739de649" @@ -761,7 +765,7 @@ def test_alert_get_command(code42_sdk_mock): def test_alert_resolve_command(code42_sdk_mock): client = Code42Client( - sdk=code42_sdk_mock, base_url=MOCK_URL, auth=("123", "123"), verify=False, proxy=None + sdk=code42_sdk_mock, base_url=MOCK_URL, auth=MOCK_AUTH, verify=False, proxy=None ) _, _, res = alert_resolve_command(client, {"id": "36fb8ca5-0533-4d25-9763-e09d35d60610"}) assert res["id"] == "36fb8ca5-0533-4d25-9763-e09d35d60610" @@ -769,7 +773,7 @@ def test_alert_resolve_command(code42_sdk_mock): def test_departing_employee_remove_command(code42_sdk_mock): client = Code42Client( - sdk=code42_sdk_mock, base_url=MOCK_URL, auth=("123", "123"), verify=False, proxy=None + sdk=code42_sdk_mock, base_url=MOCK_URL, auth=MOCK_AUTH, verify=False, proxy=None ) _, _, res = departingemployee_remove_command(client, {"username": "user1@example.com"}) assert res == "123412341234123412" # value found in GET_USER_RESPONSE @@ -777,7 +781,7 @@ def test_departing_employee_remove_command(code42_sdk_mock): def test_departing_employee_add_command(code42_sdk_mock): client = Code42Client( - sdk=code42_sdk_mock, base_url=MOCK_URL, auth=("123", "123"), verify=False, proxy=None + sdk=code42_sdk_mock, base_url=MOCK_URL, auth=MOCK_AUTH, verify=False, proxy=None ) _, _, res = departingemployee_add_command( client, @@ -788,7 +792,7 @@ def test_departing_employee_add_command(code42_sdk_mock): def test_security_data_search_command(code42_sdk_mock): client = Code42Client( - sdk=code42_sdk_mock, base_url=MOCK_URL, auth=("123", "123"), verify=False, proxy=None + sdk=code42_sdk_mock, base_url=MOCK_URL, auth=MOCK_AUTH, verify=False, proxy=None ) _, _, res = securitydata_search_command(client, MOCK_SECURITY_DATA_SEARCH_QUERY) assert len(res) == 3 @@ -796,14 +800,14 @@ def test_security_data_search_command(code42_sdk_mock): def test_fetch_incidents_first_run(code42_sdk_mock): client = Code42Client( - sdk=code42_sdk_mock, base_url=MOCK_URL, auth=("123", "123"), verify=False, proxy=None + sdk=code42_sdk_mock, base_url=MOCK_URL, auth=MOCK_AUTH, verify=False, proxy=None ) next_run, incidents, remaining_incidents = fetch_incidents( client=client, last_run={"last_fetch": None}, - first_fetch_time="24 hours", + first_fetch_time=MOCK_FETCH_TIME, event_severity_filter=None, - fetch_limit=int("10"), + fetch_limit=10, include_files=True, integration_context=None, ) @@ -815,14 +819,14 @@ 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=("123", "123"), verify=False, proxy=None + sdk=code42_sdk_mock, base_url=MOCK_URL, auth=MOCK_AUTH, verify=False, proxy=None ) next_run, incidents, remaining_incidents = fetch_incidents( client=client, last_run={"last_fetch": mock_timestamp}, - first_fetch_time="24 hours", + first_fetch_time=MOCK_FETCH_TIME, event_severity_filter=None, - fetch_limit=int("10"), + fetch_limit=10, include_files=True, integration_context=None, ) @@ -834,14 +838,14 @@ 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=("123", "123"), verify=False, proxy=None + sdk=code42_sdk_mock, base_url=MOCK_URL, auth=MOCK_AUTH, verify=False, proxy=None ) next_run, incidents, remaining_incidents = fetch_incidents( client=client, last_run={"last_fetch": mock_timestamp}, - first_fetch_time="24 hours", + first_fetch_time=MOCK_FETCH_TIME, event_severity_filter=None, - fetch_limit=int("2"), + fetch_limit=2, include_files=True, integration_context=None, ) @@ -852,9 +856,9 @@ def test_fetch_incidents_fetch_limit(code42_sdk_mock): next_run, incidents, remaining_incidents = fetch_incidents( client=client, last_run={"last_fetch": mock_timestamp}, - first_fetch_time="24 hours", + first_fetch_time=MOCK_FETCH_TIME, event_severity_filter=None, - fetch_limit=int("2"), + fetch_limit=2, include_files=True, integration_context={"remaining_incidents": remaining_incidents}, ) From a807081bace58c848184a2c7136bb94b3afd062f Mon Sep 17 00:00:00 2001 From: Juliya Smith Date: Thu, 11 Jun 2020 14:52:35 +0000 Subject: [PATCH 007/293] Got tests to pass --- Packs/Code42/Integrations/Code42/Code42.py | 42 +++---- .../Code42/Integrations/Code42/Code42_test.py | 103 ++++++++++-------- 2 files changed, 81 insertions(+), 64 deletions(-) diff --git a/Packs/Code42/Integrations/Code42/Code42.py b/Packs/Code42/Integrations/Code42/Code42.py index d08ff4daf7bf..864cf9a1ada1 100644 --- a/Packs/Code42/Integrations/Code42/Code42.py +++ b/Packs/Code42/Integrations/Code42/Code42.py @@ -137,22 +137,26 @@ def add_user_to_departing_employee(self, username, departure_epoch=None, note=No return user_id def fetch_alerts(self, start_time, event_severity_filter=None): - alert_filter = [] + try: + query = self._create_alert_query(event_severity_filter, start_time) + res = self._sdk.alerts.search_alerts(query) + except Exception: + return None + return res["alerts"] + + def _create_alert_query(self, event_severity_filter, start_time): + alert_filters = [] # Create alert filter if event_severity_filter: f = event_severity_filter severity_filter = (f.upper() if isinstance(f, str) else list(map(lambda x: x.upper(), f))) - alert_filter.append(Severity.is_in(severity_filter)) - alert_filter.append(AlertState.eq(AlertState.OPEN)) - alert_filter.append(DateObserved.on_or_after(start_time)) + alert_filters.append(Severity.is_in(severity_filter)) + alert_filters.append(AlertState.eq(AlertState.OPEN)) + alert_filters.append(DateObserved.on_or_after(start_time)) tenant_id = self._sdk.usercontext.get_current_tenant_id() - alert_query = AlertQuery(tenant_id, *alert_filter) + alert_query = AlertQuery(tenant_id, *alert_filters) alert_query.sort_direction = "asc" - try: - res = self._sdk.alerts.search_alerts(alert_query) - except Exception: - return None - return res["alerts"] + return alert_query def get_alert_details(self, alert_id): try: @@ -434,21 +438,12 @@ def __init__(self, self._integration_context = integration_context def fetch(self): - incidents = [] - remaining_incidents_from_last_run = self._fetch_remaining_incidents_from_last_run() if remaining_incidents_from_last_run: return remaining_incidents_from_last_run - start_query_time = self._get_start_query_time() alerts = self._fetch_alerts(start_query_time) - - for alert in alerts: - details = self._client.get_alert_details(alert["id"]) - incident = _create_incident_from_alert_details(details) - self._relate_files_to_alert(details) - incident["rawJSON"] = json.dumps(details) - incidents.append(incident) + 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:] @@ -476,6 +471,13 @@ def _try_get_last_fetch_time(self): def _fetch_alerts(self, start_query_time): return self._client.fetch_alerts(start_query_time, self._event_severity_filter) + def _create_incident_from_alert(self, alert): + details = self._client.get_alert_details(alert["id"]) + incident = _create_incident_from_alert_details(details) + self._relate_files_to_alert(details) + incident["rawJSON"] = json.dumps(details) + return incident + def _relate_files_to_alert(self, alert_details): for obs in alert_details["observations"]: file_events = self._get_file_events_from_alert_details(obs, alert_details) diff --git a/Packs/Code42/Integrations/Code42/Code42_test.py b/Packs/Code42/Integrations/Code42/Code42_test.py index 39df48953f07..7c9db018d256 100644 --- a/Packs/Code42/Integrations/Code42/Code42_test.py +++ b/Packs/Code42/Integrations/Code42/Code42_test.py @@ -335,50 +335,58 @@ }, ] -MOCK_ALERT_RESPONSE = { - "alerts": [ - { - "actor": "user1@example.com", - "createdAt": "2020-05-28T12:50:23.5867670Z", - "description": "", - "id": "36fb8ca5-0533-4d25-9763-e09d35d60610", - "name": "Departing Employee Alert", - "severity": "HIGH", - "state": "OPEN", - "target": "N/A", - "tenantId": "fef27d1d-e835-465c-be8f-ac9db7a54684", - "type": "FED_ENDPOINT_EXFILTRATION", - "type$": "ALERT_SUMMARY", - }, - { - "actor": "user2@example.com", - "createdAt": "2019-10-02T17:02:24.2071980Z", - "description": "", - "id": "18ac641d-7d9c-4d37-a48f-c89396c07d03", - "name": "High-Risk Employee Alert", - "severity": "MEDIUM", - "state": "OPEN", - "target": "N/A", - "tenantId": "fef27d1d-e835-465c-be8f-ac9db7a54684", - "type": "FED_CLOUD_SHARE_PERMISSIONS", - "type$": "ALERT_SUMMARY", - }, - { - "actor": "user3@exmaple.com", - "createdAt": "2019-10-02T17:03:28.2885720Z", - "description": "", - "id": "3137ff1b-b824-42e4-a476-22bccdd8ddb8", - "name": "Custom Alert 1", - "severity": "LOW", - "state": "OPEN", - "target": "N/A", - "tenantId": "fef27d1d-e835-465c-be8f-ac9db7a54684", - "type": "FED_ENDPOINT_EXFILTRATION", - "type$": "ALERT_SUMMARY", - }, - ], - "type$": "ALERT_QUERY_RESPONSE", -} +MOCK_ALERTS_RESPONSE = """{ + "type$": "ALERT_QUERY_RESPONSE", + "alerts": [ + { + "type$": "ALERT_SUMMARY", + "tenantId": "1d700000-af5b-4231-9d8e-df6434d00000", + "type": "FED_ENDPOINT_EXFILTRATION", + "name": "Exposure on an endpoint", + "description": "This default rule alerts you when departing employees move data from an endpoint.", + "actor": "test.testerson@example.com", + "target": "N/A", + "severity": "HIGH", + "ruleId": "9befe477-3487-40b7-89a6-bbcced4cf1fe", + "ruleSource": "Departing Employee", + "id": "fbeaabc1-9205-4620-ad53-95d0633429a3", + "createdAt": "2020-05-04T20:46:45.8106280Z", + "state": "OPEN" + }, + { + "type$": "ALERT_SUMMARY", + "tenantId": "1d700000-af5b-4231-9d8e-df6434d00000", + "type": "FED_ENDPOINT_EXFILTRATION", + "name": "Exposure on an endpoint", + "description": "This default rule alerts you when departing employees move data from an endpoint.", + "actor": "test.testerson@example.com", + "target": "N/A", + "severity": "LOW", + "ruleId": "9befe477-3487-40b7-89a6-bbcced4cf1fe", + "ruleSource": "Departing Employee", + "id": "6bb7ca1e-c8cf-447d-a732-9652869e42d0", + "createdAt": "2020-05-04T20:35:54.2400240Z", + "state": "OPEN" + }, + { + "type$": "ALERT_SUMMARY", + "tenantId": "1d700000-af5b-4231-9d8e-df6434d00000", + "type": "FED_ENDPOINT_EXFILTRATION", + "name": "Exposure on an endpoint", + "description": "This default rule alerts you when departing employees move data from an endpoint.", + "actor": "test.testerson@example.com", + "target": "N/A", + "severity": "HIGH", + "ruleId": "9befe477-3487-40b7-89a6-bbcced4cf1fe", + "ruleSource": "Departing Employee", + "id": "c2c3aef3-8fd9-4e7a-a04e-16bec9e27625", + "createdAt": "2020-05-04T20:19:34.7121300Z", + "state": "OPEN" + } + ], + "totalCount": 3, + "problems": [] +}""" MOCK_ALERT_DETAILS_RESPONSE = """{ "type$": "ALERT_DETAILS_RESPONSE", @@ -698,6 +706,10 @@ def code42_sdk_mock(mocker): # Setup mock alert details 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_alerts.return_value = alerts_response # Setup mock get user get_user_response = create_mock_code42_sdk_response(mocker, MOCK_GET_USER_RESPONSE) @@ -706,6 +718,9 @@ def code42_sdk_mock(mocker): # Setup securitydata search file events 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 + + # Setup tenant ID call + c42_sdk_mock.usercontext.get_current_tenant_id.return_value = "MOCK-TENANT-ID" return c42_sdk_mock From cb5dfa949536108e58a64d5f38bd356ccf8b7292 Mon Sep 17 00:00:00 2001 From: Juliya Smith Date: Thu, 11 Jun 2020 15:17:21 +0000 Subject: [PATCH 008/293] Run demisto-sdk linter --- Packs/Code42/Integrations/Code42/Code42.py | 37 +++-- .../Code42/Integrations/Code42/Code42_test.py | 128 +++++++++--------- 2 files changed, 82 insertions(+), 83 deletions(-) diff --git a/Packs/Code42/Integrations/Code42/Code42.py b/Packs/Code42/Integrations/Code42/Code42.py index 864cf9a1ada1..92f5b6386fb8 100644 --- a/Packs/Code42/Integrations/Code42/Code42.py +++ b/Packs/Code42/Integrations/Code42/Code42.py @@ -143,7 +143,7 @@ def fetch_alerts(self, start_time, event_severity_filter=None): except Exception: return None return res["alerts"] - + def _create_alert_query(self, event_severity_filter, start_time): alert_filters = [] # Create alert filter @@ -421,14 +421,13 @@ def _process_event_from_observation(event): class Code42SecurityIncidentFetcher(object): def __init__(self, - client, - last_run, - first_fetch_time, - event_severity_filter, - fetch_limit, - include_files, - integration_context=None, - ): + client, + last_run, + first_fetch_time, + event_severity_filter, + fetch_limit, + include_files, + integration_context=None): self._client = client self._last_run = last_run self._first_fetch_time = first_fetch_time @@ -436,7 +435,7 @@ def __init__(self, self._fetch_limit = fetch_limit self._include_files = include_files, self._integration_context = integration_context - + def fetch(self): remaining_incidents_from_last_run = self._fetch_remaining_incidents_from_last_run() if remaining_incidents_from_last_run: @@ -447,14 +446,14 @@ def fetch(self): save_time = datetime.utcnow().timestamp() next_run = {"last_fetch": save_time} return next_run, incidents[:self._fetch_limit], incidents[self._fetch_limit:] - + def _fetch_remaining_incidents_from_last_run(self): if self._integration_context: remaining_incidents = self._integration_context.get("remaining_incidents") # return incidents if exists in context. if remaining_incidents: return self._last_run, remaining_incidents[:self._fetch_limit], remaining_incidents[self._fetch_limit:] - + def _get_start_query_time(self): start_query_time = self._try_get_last_fetch_time() @@ -462,27 +461,27 @@ def _get_start_query_time(self): if not start_query_time: start_query_time, _ = parse_date_range(self._first_fetch_time, to_timestamp=True, utc=True) start_query_time /= 1000 - + return start_query_time - + def _try_get_last_fetch_time(self): return self._last_run.get("last_fetch") - + def _fetch_alerts(self, start_query_time): return self._client.fetch_alerts(start_query_time, self._event_severity_filter) - + def _create_incident_from_alert(self, alert): details = self._client.get_alert_details(alert["id"]) incident = _create_incident_from_alert_details(details) self._relate_files_to_alert(details) incident["rawJSON"] = json.dumps(details) return incident - + def _relate_files_to_alert(self, alert_details): for obs in alert_details["observations"]: file_events = self._get_file_events_from_alert_details(obs, alert_details) alert_details["fileevents"] = [_process_event_from_observation(e) for e in file_events] - + 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) @@ -559,7 +558,7 @@ def main(): LOG(f"Command being called is {demisto.command()}") try: client = Code42Client( - base_url=base_url, auth=(username, password), verify=verify_certificate, proxy=proxy + base_url=base_url, sdk=None, auth=(username, password), verify=verify_certificate, proxy=proxy ) commands = { "code42-alert-get": alert_get_command, diff --git a/Packs/Code42/Integrations/Code42/Code42_test.py b/Packs/Code42/Integrations/Code42/Code42_test.py index 7c9db018d256..90222bd6f197 100644 --- a/Packs/Code42/Integrations/Code42/Code42_test.py +++ b/Packs/Code42/Integrations/Code42/Code42_test.py @@ -389,52 +389,52 @@ }""" MOCK_ALERT_DETAILS_RESPONSE = """{ - "type$": "ALERT_DETAILS_RESPONSE", + "type$": "ALERT_DETAILS_RESPONSE", "alerts": [ - {"type$": "ALERT_DETAILS", - "tenantId": "1d71796f-af5b-4231-9d8e-df6434da4663", - "type": "FED_ENDPOINT_EXFILTRATION", - "name": "Departing Employee Alert", - "description": "Cortex XSOAR is cool.", - "actor": "user1@example.com", - "actorId": "912098363086307495", - "target": "N/A", - "severity": "HIGH", - "ruleId": "4576576e-13cb-4f88-be3a-ee77739de649", - "ruleSource": "Alerting", - "id": "36fb8ca5-0533-4d25-9763-e09d35d60610", - "createdAt": "2019-10-02T17:02:23.5867670Z", - "state": "OPEN", + {"type$": "ALERT_DETAILS", + "tenantId": "1d71796f-af5b-4231-9d8e-df6434da4663", + "type": "FED_ENDPOINT_EXFILTRATION", + "name": "Departing Employee Alert", + "description": "Cortex XSOAR is cool.", + "actor": "user1@example.com", + "actorId": "912098363086307495", + "target": "N/A", + "severity": "HIGH", + "ruleId": "4576576e-13cb-4f88-be3a-ee77739de649", + "ruleSource": "Alerting", + "id": "36fb8ca5-0533-4d25-9763-e09d35d60610", + "createdAt": "2019-10-02T17:02:23.5867670Z", + "state": "OPEN", "observations": [ { - "type$": "OBSERVATION", - "id": "240526fc-3a32-4755-85ab-c6ee6e7f31ce", - "observedAt": "2020-05-28T12:50:00.0000000Z", - "type": "FedEndpointExfiltration", + "type$": "OBSERVATION", + "id": "240526fc-3a32-4755-85ab-c6ee6e7f31ce", + "observedAt": "2020-05-28T12:50:00.0000000Z", + "type": "FedEndpointExfiltration", "data": { - "type$": "OBSERVED_ENDPOINT_ACTIVITY", - "id": "240526fc-3a32-4755-85ab-c6ee6e7f31ce", - "sources": ["Endpoint"], + "type$": "OBSERVED_ENDPOINT_ACTIVITY", + "id": "240526fc-3a32-4755-85ab-c6ee6e7f31ce", + "sources": ["Endpoint"], "exposureTypes": ["ApplicationRead"], "firstActivityAt": "2020-05-28T12:50:00.0000000Z", - "lastActivityAt": "2020-05-28T12:50:00.0000000Z", - "fileCount": 3, - "totalFileSize": 533846, + "lastActivityAt": "2020-05-28T12:50:00.0000000Z", + "fileCount": 3, + "totalFileSize": 533846, "fileCategories": [ { - "type$": "OBSERVED_FILE_CATEGORY", - "category": "Image", - "fileCount": 3, - "totalFileSize": 533846, + "type$": "OBSERVED_FILE_CATEGORY", + "category": "Image", + "fileCount": 3, + "totalFileSize": 533846, "isSignificant": false } - ], + ], "files": [ { - "type$": "OBSERVED_FILE", + "type$": "OBSERVED_FILE", "eventId": "0_1d71796f-af5b-4231-9d8e-df6434da4663_935873453596901068_956171635867906205_5", - "path": "C:/Users/QA/Downloads/", - "name": "Customers..jpg", + "path": "C:/Users/QA/Downloads/", + "name": "Customers..jpg", "category": "Image", "size": 265122 }, @@ -450,12 +450,12 @@ "type$": "OBSERVED_FILE", "eventId": "0_1d71796f-af5b-4231-9d8e-df6434da4663_935873453596901068_956171635867906205_7", "path": "C:/Users/QA/Downloads/", - "name": "company_secrets.ps", - "category": "Image", + "name": "company_secrets.ps", + "category": "Image", "size": 139595 } - ], - "syncToServices": [], + ], + "syncToServices": [], "sendingIpAddresses": ["127.0.0.1"] } } @@ -667,32 +667,32 @@ MOCK_GET_USER_RESPONSE = """ { - "totalCount": 1, + "totalCount": 1, "users": [ { - "userId": 123456, - "userUid": "123412341234123412", - "status": "Active", - "username": "test.testerson@example.com", - "email": "test.testerson@example.com", - "firstName": "Test", - "lastName": "Testerson", - "quotaInBytes": -1, - "orgId": 1111, - "orgUid": "81111247111106706", - "orgName": "Testers", - "userExtRef": null, - "notes": null, - "active": true, - "blocked": false, - "emailPromo": true, - "invited": false, - "orgType": "ENTERPRISE", - "usernameIsAnEmail": true, - "creationDate": "2019-09-30T21:03:08.587Z", - "modificationDate": "2020-04-10T11:49:49.987Z", - "passwordReset": false, - "localAuthenticationOnly": false, + "userId": 123456, + "userUid": "123412341234123412", + "status": "Active", + "username": "test.testerson@example.com", + "email": "test.testerson@example.com", + "firstName": "Test", + "lastName": "Testerson", + "quotaInBytes": -1, + "orgId": 1111, + "orgUid": "81111247111106706", + "orgName": "Testers", + "userExtRef": null, + "notes": null, + "active": true, + "blocked": false, + "emailPromo": true, + "invited": false, + "orgType": "ENTERPRISE", + "usernameIsAnEmail": true, + "creationDate": "2019-09-30T21:03:08.587Z", + "modificationDate": "2020-04-10T11:49:49.987Z", + "passwordReset": false, + "localAuthenticationOnly": false, "licenses": ["admin.securityTools"] } ] @@ -706,7 +706,7 @@ def code42_sdk_mock(mocker): # Setup mock alert details 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_alerts.return_value = alerts_response @@ -714,11 +714,11 @@ def code42_sdk_mock(mocker): # Setup mock get 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 securitydata search file events 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 - + # Setup tenant ID call c42_sdk_mock.usercontext.get_current_tenant_id.return_value = "MOCK-TENANT-ID" From 819ac1ec01fd649fd017bc133e0e29543bab6510 Mon Sep 17 00:00:00 2001 From: Juliya Smith Date: Thu, 11 Jun 2020 15:48:14 +0000 Subject: [PATCH 009/293] Put back original param name --- Packs/Code42/Integrations/Code42/Code42.py | 37 ++++++------------- Packs/Code42/Integrations/Code42/Code42.yml | 8 ++-- Packs/Code42/Integrations/Code42/README.md | 10 ++--- .../TestPlaybooks/playbook-Code42-Test.yml | 2 +- 4 files changed, 22 insertions(+), 35 deletions(-) diff --git a/Packs/Code42/Integrations/Code42/Code42.py b/Packs/Code42/Integrations/Code42/Code42.py index 92f5b6386fb8..a033c074d21d 100644 --- a/Packs/Code42/Integrations/Code42/Code42.py +++ b/Packs/Code42/Integrations/Code42/Code42.py @@ -181,12 +181,12 @@ def remove_user_from_departing_employee(self, username): return None return user_id - def resolve_alert(self, alert_id): + def resolve_alert(self, id): try: - self._sdk.alerts.resolve(alert_id) + self._sdk.alerts.resolve(id) except Exception: return None - return alert_id + return id def get_user_id(self, username): try: @@ -243,24 +243,11 @@ def map_observation_to_security_query(observation, actor): search_args.append(DeviceUsername.eq(actor)) else: search_args.append(Actor.eq(actor)) - search_args.append( - EventTimestamp.on_or_after( - int( - time.mktime( - time.strptime(begin_time.replace("0000000", "000"), "%Y-%m-%dT%H:%M:%S.000Z") - ) - ) - ) - ) - search_args.append( - EventTimestamp.on_or_before( - int( - time.mktime( - time.strptime(end_time.replace("0000000", "000"), "%Y-%m-%dT%H:%M:%S.000Z") - ) - ) - ) - ) + + begin_time = time.mktime(time.strptime(begin_time.replace("0000000", "000"), "%Y-%m-%dT%H:%M:%S.000Z")) + search_args.append(EventTimestamp.on_or_after(int(begin_time))) + end_time = time.mktime(time.strptime(end_time.replace("0000000", "000"), "%Y-%m-%dT%H:%M:%S.000Z")) + search_args.append(int(end_time)) # Determine exposure types based on alert type if observation["type"] == "FedCloudSharePermissions": if "PublicSearchableShare" in exposure_types: @@ -369,7 +356,7 @@ def departingemployee_add_command(client, args): departure_epoch = int(time.mktime(time.strptime(args["departuredate"], "%Y-%m-%d"))) except Exception: return_error( - message="Could not add user to Departing Employee Lens: " + message="Could not add user to Departing Employee List: " "unable to parse departure date. Is it in yyyy-MM-dd format?" ) user_id = client.add_user_to_departing_employee( @@ -384,7 +371,7 @@ def departingemployee_add_command(client, args): "DepartureDate": args.get("departuredate"), "Note": args.get("note"), } - readable_outputs = tableToMarkdown(f"Code42 Departing Employee Lens User Added", de_context) + readable_outputs = tableToMarkdown(f"Code42 Departing Employee List User Added", de_context) return readable_outputs, {"Code42.DepartingEmployee": de_context}, user_id @@ -394,11 +381,11 @@ def departingemployee_remove_command(client, args): if case: de_context = {"CaseID": case, "Username": args["username"]} readable_outputs = tableToMarkdown( - f"Code42 Departing Employee Lens User Removed", de_context + f"Code42 Departing Employee List User Removed", de_context ) return readable_outputs, {"Code42.DepartingEmployee": de_context}, case else: - return_error(message="Could not remove user from Departing Employee Lens") + return_error(message="Could not remove user from Departing Employee List") def _create_incident_from_alert_details(details): diff --git a/Packs/Code42/Integrations/Code42/Code42.yml b/Packs/Code42/Integrations/Code42/Code42.yml index dd9006d76b52..efafe9909024 100644 --- a/Packs/Code42/Integrations/Code42/Code42.yml +++ b/Packs/Code42/Integrations/Code42/Code42.yml @@ -245,7 +245,7 @@ script: arguments: - name: username required: true - description: The username to add to the Departing Employee Lens. + description: The username to add to the Departing Employee List. - name: departuredate description: The departure date for the employee, in the format YYYY-MM-DD. - name: note @@ -262,17 +262,17 @@ script: type: string - contextPath: Code42.DepartingEmployee.DepartureDate description: The departure date for the Departing Employee. - description: Adds a user to the Departing Employee Lens. + 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 Lens. + description: The username to remove from the Departing Employee List. outputs: - contextPath: Code42.DepartingEmployee.CaseID description: Internal Code42 Case ID for the Departing Employee. - contextPath: Code42.DepartingEmployee.Username description: The username of the Departing Employee. - description: Removes a user from the Departing Employee Lens. + description: Removes a user from the Departing Employee List. - name: code42-alert-resolve arguments: - name: id diff --git a/Packs/Code42/Integrations/Code42/README.md b/Packs/Code42/Integrations/Code42/README.md index 03352df262b4..1658ef95cca4 100644 --- a/Packs/Code42/Integrations/Code42/README.md +++ b/Packs/Code42/Integrations/Code42/README.md @@ -282,7 +282,7 @@ This command requires one of the following roles: ### 3. code42-departingemployee-add --- -Add a user to the Departing Employee Lens +Add a user to the Departing Employee List ##### Required Permissions This command requires one of the following roles: @@ -297,8 +297,8 @@ This command requires one of the following roles: | **Argument Name** | **Description** | **Required** | | --- | --- | --- | -| username | Username to add to the Departing Employee Lens | Required | -| departuredate | Departure date for the employee in YYYY-MM-DD format | Optional | +| 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 | @@ -338,7 +338,7 @@ This command requires one of the following roles: ### 4. code42-departingemployee-remove --- -Remove a user from the Departing Employee Lens +Remove a user from the Departing Employee List ##### Required Permissions This command requires one of the following roles: @@ -353,7 +353,7 @@ This command requires one of the following roles: | **Argument Name** | **Description** | **Required** | | --- | --- | --- | -| username | Username to remove from the Departing Employee Lens | Optional | +| username | Username to remove from the Departing Employee List | Optional | ##### Context Output diff --git a/Packs/Code42/TestPlaybooks/playbook-Code42-Test.yml b/Packs/Code42/TestPlaybooks/playbook-Code42-Test.yml index efed8ea17704..407f25b0bcce 100644 --- a/Packs/Code42/TestPlaybooks/playbook-Code42-Test.yml +++ b/Packs/Code42/TestPlaybooks/playbook-Code42-Test.yml @@ -597,7 +597,7 @@ inputs: value: simple: partner.demisto@example.com required: false - description: Username for add/remove to Departing Employee Lens + description: Username for add/remove to Departing Employee List - key: search-payload value: simple: |- From 06d38e7dae8d2953cf9d98e834b3c9bc36d63cba Mon Sep 17 00:00:00 2001 From: Juliya Smith Date: Thu, 11 Jun 2020 15:56:35 +0000 Subject: [PATCH 010/293] Put back filter --- Packs/Code42/Integrations/Code42/Code42.py | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/Packs/Code42/Integrations/Code42/Code42.py b/Packs/Code42/Integrations/Code42/Code42.py index a033c074d21d..1af655fe7d52 100644 --- a/Packs/Code42/Integrations/Code42/Code42.py +++ b/Packs/Code42/Integrations/Code42/Code42.py @@ -163,8 +163,7 @@ def get_alert_details(self, alert_id): res = self._sdk.alerts.get_details(alert_id) except Exception: return None - details = res["alerts"][0] if res["alerts"] else None - return details + return res["alerts"][0] def get_current_user(self): try: @@ -193,7 +192,7 @@ def get_user_id(self, username): res = self._sdk.users.get_by_username(username) except Exception: return None - return res["users"][0]["userUid"] if res["users"] else None + return res["users"][0]["userUid"] def search_json(self, payload): try: @@ -244,10 +243,10 @@ def map_observation_to_security_query(observation, actor): else: search_args.append(Actor.eq(actor)) - begin_time = time.mktime(time.strptime(begin_time.replace("0000000", "000"), "%Y-%m-%dT%H:%M:%S.000Z")) - search_args.append(EventTimestamp.on_or_after(int(begin_time))) - end_time = time.mktime(time.strptime(end_time.replace("0000000", "000"), "%Y-%m-%dT%H:%M:%S.000Z")) - search_args.append(int(end_time)) + begin = time.mktime(time.strptime(begin_time.replace("0000000", "000"), "%Y-%m-%dT%H:%M:%S.000Z")) + search_args.append(EventTimestamp.on_or_after(int(begin))) + end = time.mktime(time.strptime(end_time.replace("0000000", "000"), "%Y-%m-%dT%H:%M:%S.000Z")) + search_args.append(EventTimestamp.on_or_before(int(end))) # Determine exposure types based on alert type if observation["type"] == "FedCloudSharePermissions": if "PublicSearchableShare" in exposure_types: From d9a809e90d227ab72f5b1650aa339de3af88fa98 Mon Sep 17 00:00:00 2001 From: Juliya Smith Date: Thu, 11 Jun 2020 16:00:40 +0000 Subject: [PATCH 011/293] Call remove and add test --- Packs/Code42/Integrations/Code42/Code42.py | 2 +- Packs/Code42/Integrations/Code42/Code42_test.py | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/Packs/Code42/Integrations/Code42/Code42.py b/Packs/Code42/Integrations/Code42/Code42.py index 1af655fe7d52..dd4a8b249dac 100644 --- a/Packs/Code42/Integrations/Code42/Code42.py +++ b/Packs/Code42/Integrations/Code42/Code42.py @@ -175,7 +175,7 @@ def get_current_user(self): def remove_user_from_departing_employee(self, username): try: user_id = self.get_user_id(username) - self._sdk.detectionlists.departing_employee.resolve(user_id) + self._sdk.detectionlists.departing_employee.remove(user_id) except Exception: return None return user_id diff --git a/Packs/Code42/Integrations/Code42/Code42_test.py b/Packs/Code42/Integrations/Code42/Code42_test.py index 90222bd6f197..b4ca7de8ff76 100644 --- a/Packs/Code42/Integrations/Code42/Code42_test.py +++ b/Packs/Code42/Integrations/Code42/Code42_test.py @@ -791,7 +791,9 @@ def test_departing_employee_remove_command(code42_sdk_mock): sdk=code42_sdk_mock, base_url=MOCK_URL, auth=MOCK_AUTH, verify=False, proxy=None ) _, _, res = departingemployee_remove_command(client, {"username": "user1@example.com"}) - assert res == "123412341234123412" # value found in GET_USER_RESPONSE + 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_departing_employee_add_command(code42_sdk_mock): From 1d117091a8ff730b865772829fb5ea719bdbd78e Mon Sep 17 00:00:00 2001 From: Juliya Smith Date: Thu, 11 Jun 2020 16:10:04 +0000 Subject: [PATCH 012/293] Add more tests --- Packs/Code42/Integrations/Code42/Code42_test.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/Packs/Code42/Integrations/Code42/Code42_test.py b/Packs/Code42/Integrations/Code42/Code42_test.py index b4ca7de8ff76..2793dd663052 100644 --- a/Packs/Code42/Integrations/Code42/Code42_test.py +++ b/Packs/Code42/Integrations/Code42/Code42_test.py @@ -804,7 +804,9 @@ def test_departing_employee_add_command(code42_sdk_mock): client, {"username": "user1@example.com", "departuredate": "2020-01-01", "notes": "Dummy note"}, ) - assert res == "123412341234123412" + expected = "123412341234123412" # value found in GET_USER_RESPONSE + assert res == expected + code42_sdk_mock.detectionlists.departing_employee.add.assert_called_once_with(expected, departure_epoch=1577836800) def test_security_data_search_command(code42_sdk_mock): @@ -813,6 +815,15 @@ def test_security_data_search_command(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] + assert "md5Checksum" in actual_query + assert "d41d8cd98f00b204e9800998ecf8427e" in actual_query + assert "osHostName" in actual_query + assert "DESKTOP-0001" in actual_query + assert "deviceUserName" in actual_query + assert "user3@example.com" in actual_query + assert "exposure" in actual_query + assert "ApplicationRead" in actual_query def test_fetch_incidents_first_run(code42_sdk_mock): From 7770f069abb9d2b8102f5b1c6967b789aa2b84d6 Mon Sep 17 00:00:00 2001 From: Juliya Smith Date: Thu, 11 Jun 2020 16:10:56 +0000 Subject: [PATCH 013/293] casing issue --- Packs/Code42/Integrations/Code42/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Packs/Code42/Integrations/Code42/README.md b/Packs/Code42/Integrations/Code42/README.md index 1658ef95cca4..9c4630ae543c 100644 --- a/Packs/Code42/Integrations/Code42/README.md +++ b/Packs/Code42/Integrations/Code42/README.md @@ -297,7 +297,7 @@ This command requires one of the following roles: | **Argument Name** | **Description** | **Required** | | --- | --- | --- | -| username | Username to add to the Departing Employee LIst | 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 | From a014e8224b93f62c80b7dc54404d051ab81db1e3 Mon Sep 17 00:00:00 2001 From: Juliya Smith Date: Thu, 11 Jun 2020 16:23:33 +0000 Subject: [PATCH 014/293] Refactor building query --- Packs/Code42/Integrations/Code42/Code42.py | 43 +++++++++++++++------- 1 file changed, 29 insertions(+), 14 deletions(-) diff --git a/Packs/Code42/Integrations/Code42/Code42.py b/Packs/Code42/Integrations/Code42/Code42.py index dd4a8b249dac..ac663b168bb8 100644 --- a/Packs/Code42/Integrations/Code42/Code42.py +++ b/Packs/Code42/Integrations/Code42/Code42.py @@ -207,21 +207,22 @@ def build_query_payload(args): """ Build a query payload combining passed args """ + _hash = args.get("hash") + hostname = args.get("hostname") + username = args.get("username") + exposure = args.get("exposure") + search_args = [] - if args.get("hash"): - if len(args["hash"]) == 32: - search_args.append(MD5.eq(args["hash"])) - elif len(args["hash"]) == 64: - search_args.append(SHA256.eq(args["hash"])) - if args.get("hostname"): - search_args.append(OSHostname.eq(args["hostname"])) - if args.get("username"): - search_args.append(DeviceUsername.eq(args["username"])) - if args.get("exposure"): - # Because the CLI can't accept lists, convert the args to a list if the type is string. - if isinstance(args["exposure"], str): - args["exposure"] = args["exposure"].split(",") - search_args.append(ExposureType.is_in(args["exposure"])) + hash_filter = _create_hash_filter(_hash) + if hash_filter: + search_args.append(hash_filter) + if hostname: + search_args.append(OSHostname.eq(hostname)) + if username: + search_args.append(DeviceUsername.eq(username)) + if exposure: + search_args.append(_create_exposure_filter(exposure)) + # Convert list of search criteria to *args query = FileEventQuery.all(*search_args) query.page_size = args.get("results") @@ -229,6 +230,20 @@ def build_query_payload(args): return str(query) +def _create_hash_filter(hash_arg): + if len(hash_arg) == 32: + return MD5.eq(hash_arg) + elif len(hash_arg) == 64: + return SHA256.eq(hash_arg) + + +def _create_exposure_filter(exposure_arg): + # Because the CLI can't accept lists, convert the args to a list if the type is string. + if isinstance(exposure_arg, str): + exposure_arg = exposure_arg.split(",") + return ExposureType.is_in(exposure_arg) + + @logger def map_observation_to_security_query(observation, actor): file_categories: Dict[str, Any] From f2f15f19bfa36e163b5226f4d377422ded99e43a Mon Sep 17 00:00:00 2001 From: Juliya Smith Date: Thu, 11 Jun 2020 16:30:03 +0000 Subject: [PATCH 015/293] Remove using tenant ID --- Packs/Code42/Integrations/Code42/Code42.py | 3 +-- Packs/Code42/Integrations/Code42/Code42_test.py | 3 --- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/Packs/Code42/Integrations/Code42/Code42.py b/Packs/Code42/Integrations/Code42/Code42.py index ac663b168bb8..c9277a015637 100644 --- a/Packs/Code42/Integrations/Code42/Code42.py +++ b/Packs/Code42/Integrations/Code42/Code42.py @@ -153,8 +153,7 @@ def _create_alert_query(self, event_severity_filter, start_time): alert_filters.append(Severity.is_in(severity_filter)) alert_filters.append(AlertState.eq(AlertState.OPEN)) alert_filters.append(DateObserved.on_or_after(start_time)) - tenant_id = self._sdk.usercontext.get_current_tenant_id() - alert_query = AlertQuery(tenant_id, *alert_filters) + alert_query = AlertQuery(*alert_filters) alert_query.sort_direction = "asc" return alert_query diff --git a/Packs/Code42/Integrations/Code42/Code42_test.py b/Packs/Code42/Integrations/Code42/Code42_test.py index 2793dd663052..a4cf8a70b354 100644 --- a/Packs/Code42/Integrations/Code42/Code42_test.py +++ b/Packs/Code42/Integrations/Code42/Code42_test.py @@ -719,9 +719,6 @@ def code42_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 - # Setup tenant ID call - c42_sdk_mock.usercontext.get_current_tenant_id.return_value = "MOCK-TENANT-ID" - return c42_sdk_mock From e88ae3c77458834b66d43636caf9dcda2cd23b63 Mon Sep 17 00:00:00 2001 From: Juliya Smith Date: Thu, 11 Jun 2020 16:38:53 +0000 Subject: [PATCH 016/293] Refactor and replace case id with user id --- Packs/Code42/Integrations/Code42/Code42.py | 36 ++++++++++++--------- Packs/Code42/Integrations/Code42/Code42.yml | 2 +- 2 files changed, 21 insertions(+), 17 deletions(-) diff --git a/Packs/Code42/Integrations/Code42/Code42.py b/Packs/Code42/Integrations/Code42/Code42.py index c9277a015637..59b42226f865 100644 --- a/Packs/Code42/Integrations/Code42/Code42.py +++ b/Packs/Code42/Integrations/Code42/Code42.py @@ -363,26 +363,27 @@ def alert_resolve_command(client, args): def departingemployee_add_command(client, args): departure_epoch: Optional[int] # Convert date to epoch + departing_date = args.get("departuredate") + username = args["username"] + note = args.get("note") departure_epoch = None - if args.get("departuredate"): + if departing_date: try: - departure_epoch = int(time.mktime(time.strptime(args["departuredate"], "%Y-%m-%d"))) + departure_epoch = int(time.mktime(time.strptime(departing_date, "%Y-%m-%d"))) except Exception: return_error( message="Could not add user to Departing Employee List: " "unable to parse departure date. Is it in yyyy-MM-dd format?" ) - user_id = client.add_user_to_departing_employee( - args["username"], departure_epoch, args.get("note") - ) + user_id = client.add_user_to_departing_employee(username, departure_epoch, note) if not user_id: return_error(message="Could not add user to Departing Employee List") de_context = { "UserID": user_id, - "Username": args["username"], - "DepartureDate": args.get("departuredate"), - "Note": args.get("note"), + "Username": username, + "DepartureDate": departing_date, + "Note": note, } readable_outputs = tableToMarkdown(f"Code42 Departing Employee List User Added", de_context) return readable_outputs, {"Code42.DepartingEmployee": de_context}, user_id @@ -390,13 +391,14 @@ def departingemployee_add_command(client, args): @logger def departingemployee_remove_command(client, args): - case = client.remove_user_from_departing_employee(args["username"]) - if case: - de_context = {"CaseID": case, "Username": args["username"]} + username = args["username"] + user_id = client.remove_user_from_departing_employee(username) + if user_id: + de_context = {"UserID": user_id, "Username": username} readable_outputs = tableToMarkdown( f"Code42 Departing Employee List User Removed", de_context ) - return readable_outputs, {"Code42.DepartingEmployee": de_context}, case + return readable_outputs, {"Code42.DepartingEmployee": de_context}, user_id else: return_error(message="Could not remove user from Departing Employee List") @@ -407,11 +409,13 @@ def _create_incident_from_alert_details(details): def _stringify_lists_if_needed(event): # We need to convert certain fields to a stringified list or React.JS will throw an error - if event.get("sharedWith"): - shared_list = [u["cloudUsername"] for u in event["sharedWith"]] + shared_with = event.get("sharedWith") + private_ip_addresses = event.get("privateIpAddresses") + if shared_with: + shared_list = [u["cloudUsername"] for u in shared_with] event["sharedWith"] = str(shared_list) - if event.get("privateIpAddresses"): - event["privateIpAddresses"] = str(event["privateIpAddresses"]) + if private_ip_addresses: + event["privateIpAddresses"] = str(private_ip_addresses) def _process_event_from_observation(event): diff --git a/Packs/Code42/Integrations/Code42/Code42.yml b/Packs/Code42/Integrations/Code42/Code42.yml index efafe9909024..55abf65826c4 100644 --- a/Packs/Code42/Integrations/Code42/Code42.yml +++ b/Packs/Code42/Integrations/Code42/Code42.yml @@ -251,7 +251,7 @@ script: - name: note description: Note to attach to the Departing Employee. outputs: - - contextPath: Code42.DepartingEmployee.CaseID + - contextPath: Code42.DepartingEmployee.UserID description: Internal Code42 Case ID for the Departing Employee. type: string - contextPath: Code42.DepartingEmployee.Username From 8df06f4d6b657fa5b1a1df2b8640dd15a25125b8 Mon Sep 17 00:00:00 2001 From: Juliya Smith Date: Thu, 11 Jun 2020 16:41:23 +0000 Subject: [PATCH 017/293] Remove epoch conversion for departure date --- Packs/Code42/Integrations/Code42/Code42.py | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/Packs/Code42/Integrations/Code42/Code42.py b/Packs/Code42/Integrations/Code42/Code42.py index 59b42226f865..3345a4540165 100644 --- a/Packs/Code42/Integrations/Code42/Code42.py +++ b/Packs/Code42/Integrations/Code42/Code42.py @@ -361,21 +361,10 @@ def alert_resolve_command(client, args): @logger def departingemployee_add_command(client, args): - departure_epoch: Optional[int] - # Convert date to epoch departing_date = args.get("departuredate") username = args["username"] note = args.get("note") - departure_epoch = None - if departing_date: - try: - departure_epoch = int(time.mktime(time.strptime(departing_date, "%Y-%m-%d"))) - except Exception: - return_error( - message="Could not add user to Departing Employee List: " - "unable to parse departure date. Is it in yyyy-MM-dd format?" - ) - user_id = client.add_user_to_departing_employee(username, departure_epoch, 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") From 520d7d10c0afc5ca1a6ffa6847632fa7f2b48e5d Mon Sep 17 00:00:00 2001 From: Juliya Smith Date: Thu, 11 Jun 2020 16:47:30 +0000 Subject: [PATCH 018/293] Fix missed updates --- Packs/Code42/Integrations/Code42/Code42.py | 6 +++--- Packs/Code42/Integrations/Code42/Code42_test.py | 3 ++- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/Packs/Code42/Integrations/Code42/Code42.py b/Packs/Code42/Integrations/Code42/Code42.py index 3345a4540165..109642114294 100644 --- a/Packs/Code42/Integrations/Code42/Code42.py +++ b/Packs/Code42/Integrations/Code42/Code42.py @@ -1,4 +1,4 @@ -from typing import Optional, Dict, Any +from typing import Dict, Any import demistomock as demisto from CommonServerPython import * @@ -125,11 +125,11 @@ 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") - def add_user_to_departing_employee(self, username, departure_epoch=None, note=None): + 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_epoch=departure_epoch + user_id, departure_date=departure_date ) not note or self._sdk.detectionlists.update_user_notes(note) except Exception: diff --git a/Packs/Code42/Integrations/Code42/Code42_test.py b/Packs/Code42/Integrations/Code42/Code42_test.py index a4cf8a70b354..a7a81614e9fa 100644 --- a/Packs/Code42/Integrations/Code42/Code42_test.py +++ b/Packs/Code42/Integrations/Code42/Code42_test.py @@ -803,7 +803,8 @@ def test_departing_employee_add_command(code42_sdk_mock): ) expected = "123412341234123412" # value found in GET_USER_RESPONSE assert res == expected - code42_sdk_mock.detectionlists.departing_employee.add.assert_called_once_with(expected, departure_epoch=1577836800) + add_func = code42_sdk_mock.detectionlists.departing_employee.add + add_func.assert_called_once_with(expected, departure_date="2020-01-01") def test_security_data_search_command(code42_sdk_mock): From 5a44dc04db44e6a8e61f1b1b5febaddd0f43192a Mon Sep 17 00:00:00 2001 From: Juliya Smith Date: Thu, 11 Jun 2020 16:58:44 +0000 Subject: [PATCH 019/293] Simplify making time args --- Packs/Code42/Integrations/Code42/Code42.py | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/Packs/Code42/Integrations/Code42/Code42.py b/Packs/Code42/Integrations/Code42/Code42.py index 109642114294..1d91ed121704 100644 --- a/Packs/Code42/Integrations/Code42/Code42.py +++ b/Packs/Code42/Integrations/Code42/Code42.py @@ -21,7 +21,6 @@ ) from py42.sdk.queries.alerts.alert_query import AlertQuery from py42.sdk.queries.alerts.filters import DateObserved, Severity, AlertState -import time # Disable insecure warnings requests.packages.urllib3.disable_warnings() @@ -250,17 +249,17 @@ def map_observation_to_security_query(observation, actor): search_args = [] exp_types = [] exposure_types = observation_data["exposureTypes"] - begin_time = observation_data["firstActivityAt"] - end_time = observation_data["lastActivityAt"] + + begin_time = _convert_date_arg_to_epoch(observation_data["firstActivityAt"]) + end_time = _convert_date_arg_to_epoch(observation_data["lastActivityAt"]) + if observation["type"] == "FedEndpointExfiltration": search_args.append(DeviceUsername.eq(actor)) else: search_args.append(Actor.eq(actor)) - begin = time.mktime(time.strptime(begin_time.replace("0000000", "000"), "%Y-%m-%dT%H:%M:%S.000Z")) - search_args.append(EventTimestamp.on_or_after(int(begin))) - end = time.mktime(time.strptime(end_time.replace("0000000", "000"), "%Y-%m-%dT%H:%M:%S.000Z")) - search_args.append(EventTimestamp.on_or_before(int(end))) + search_args.append(EventTimestamp.on_or_after(begin_time)) + search_args.append(EventTimestamp.on_or_before(end_time)) # Determine exposure types based on alert type if observation["type"] == "FedCloudSharePermissions": if "PublicSearchableShare" in exposure_types: @@ -291,6 +290,11 @@ def map_observation_to_security_query(observation, actor): return str(query) +def _convert_date_arg_to_epoch(date_arg): + date_arg = date_arg[:25] + return (datetime.strptime(date_arg, u"%Y-%m-%dT%H:%M:%S.%f") - datetime.utcfromtimestamp(0)).total_seconds() + + @logger def map_to_code42_event_context(obj): code42_context = _map_obj_to_context(obj, CODE42_EVENT_CONTEXT_FIELD_MAPPER) From 9b96d7bf916eae562c5868fd43c1aa2586908974 Mon Sep 17 00:00:00 2001 From: Juliya Smith Date: Thu, 11 Jun 2020 17:53:07 +0000 Subject: [PATCH 020/293] Refactor mapper --- Packs/Code42/Integrations/Code42/Code42.py | 136 ++++++++++++++------- 1 file changed, 91 insertions(+), 45 deletions(-) diff --git a/Packs/Code42/Integrations/Code42/Code42.py b/Packs/Code42/Integrations/Code42/Code42.py index 1d91ed121704..f9a9c8bd2ed4 100644 --- a/Packs/Code42/Integrations/Code42/Code42.py +++ b/Packs/Code42/Integrations/Code42/Code42.py @@ -18,6 +18,7 @@ DeviceUsername, ExposureType, EventType, + FileCategory, ) from py42.sdk.queries.alerts.alert_query import AlertQuery from py42.sdk.queries.alerts.filters import DateObserved, Severity, AlertState @@ -242,52 +243,97 @@ def _create_exposure_filter(exposure_arg): return ExposureType.is_in(exposure_arg) -@logger -def map_observation_to_security_query(observation, actor): - file_categories: Dict[str, Any] - observation_data = observation["data"] - search_args = [] - exp_types = [] - exposure_types = observation_data["exposureTypes"] +class ObservationToSecurityQueryMapper(object): + _ENDPOINT_TYPE = "FedEndpointExfiltration" + _CLOUD_TYPE = "FedCloudSharePermissions" + _PUBLIC_SEARCHABLE = "PublicSearchableShare" + _PUBLIC_LINK = "PublicLinkShare" + + def __init__(self, observation, actor): + self._obs = observation + self._actor = actor + + @property + def _observation_data(self): + return self._obs["data"] + + @property + def _exfiltration_type(self): + return self._obs["type"] + + @property + def _is_endpoint_exfiltration(self): + return self._exfiltration_type == self._ENDPOINT_TYPE + + @property + def _is_cloud_exfiltration(self): + return self._exfiltration_type == self._CLOUD_TYPE + + @property + def _user_filter(self): + return DeviceUsername.eq(self._actor) if self._is_endpoint_exfiltration else Actor.eq(self._actor) + + def map(self): + search_args = self._create_search_args() + query = self._create_query(search_args) + return str(query) + + def _create_search_args(self): + search_args = [] + exposure_types = self._observation_data["exposureTypes"] + begin_time = _convert_date_arg_to_epoch(self._observation_data["firstActivityAt"]) + end_time = _convert_date_arg_to_epoch(self._observation_data["lastActivityAt"]) + search_args.append(self._user_filter) + search_args.append(EventTimestamp.on_or_after(begin_time)) + search_args.append(EventTimestamp.on_or_before(end_time)) + + exposure_filters = self._create_exposure_filters(exposure_types) or [] + for _filter in exposure_filters: + search_args.append(_filter) + + category_filters = self._create_file_category_filters() + if category_filters: + search_args.append(category_filters) + return search_args + + def _create_exposure_filters(self, exposure_types): + """ + Determine exposure types based on alert type + """ + if self._is_cloud_exfiltration: + exp_types = [] + if self._PUBLIC_SEARCHABLE in exposure_types: + exp_types.append(ExposureType.IS_PUBLIC) + if self._PUBLIC_LINK in exposure_types: + exp_types.append(ExposureType.SHARED_VIA_LINK) + return [ExposureType.is_in(exp_types)] + elif self._is_endpoint_exfiltration: + return [EventType.is_in(["CREATED", "MODIFIED", "READ_BY_APP"]), ExposureType.is_in(exposure_types)] + + def _create_file_category_filters(self): + """Determine if file categorization is significant""" + file_categories: Dict[str, Any] + filters = [] + for file_type in self._observation_data["fileCategories"]: + if file_type["isSignificant"]: + category_value = CODE42_FILE_TYPE_MAPPER.get(file_type["category"], "UNCATEGORIZED") + file_category = FileCategory.eq(category_value) + filters.append(file_category) + if len(filters): + file_categories = {"filterClause": "OR", "filters": filters} + return json.dumps(file_categories) + + @logger + def _create_query(self, search_args): + """Convert list of search criteria to *args""" + query = FileEventQuery.all(*search_args) + LOG("Alert Observation Query: {}".format(query)) + return query - begin_time = _convert_date_arg_to_epoch(observation_data["firstActivityAt"]) - end_time = _convert_date_arg_to_epoch(observation_data["lastActivityAt"]) - if observation["type"] == "FedEndpointExfiltration": - search_args.append(DeviceUsername.eq(actor)) - else: - search_args.append(Actor.eq(actor)) - - search_args.append(EventTimestamp.on_or_after(begin_time)) - search_args.append(EventTimestamp.on_or_before(end_time)) - # Determine exposure types based on alert type - if observation["type"] == "FedCloudSharePermissions": - if "PublicSearchableShare" in exposure_types: - exp_types.append(ExposureType.IS_PUBLIC) - if "PublicLinkShare" in exposure_types: - exp_types.append(ExposureType.SHARED_VIA_LINK) - elif observation["type"] == "FedEndpointExfiltration": - exp_types = exposure_types - search_args.append(EventType.is_in(["CREATED", "MODIFIED", "READ_BY_APP"])) - search_args.append(ExposureType.is_in(exp_types)) - # Determine if file categorization is significant - file_categories = {"filterClause": "OR"} - filters = [] - for filetype in observation_data["fileCategories"]: - if filetype["isSignificant"]: - file_category = { - "operator": "IS", - "term": "fileCategory", - "value": CODE42_FILE_TYPE_MAPPER.get(filetype["category"], "UNCATEGORIZED"), - } - filters.append(file_category) - if len(filters): - file_categories["filters"] = filters - search_args.append(json.dumps(file_categories)) - # Convert list of search criteria to *args - query = FileEventQuery.all(*search_args) - LOG("Alert Observation Query: {}".format(query)) - return str(query) +def map_observation_to_security_query(observation, actor): + mapper = ObservationToSecurityQueryMapper(observation, actor) + return mapper.map() def _convert_date_arg_to_epoch(date_arg): @@ -433,6 +479,7 @@ def __init__(self, self._include_files = include_files, self._integration_context = integration_context + @logger def fetch(self): remaining_incidents_from_last_run = self._fetch_remaining_incidents_from_last_run() if remaining_incidents_from_last_run: @@ -484,7 +531,6 @@ def _get_file_events_from_alert_details(self, observation, alert_details): return self._client.search_json(security_data_query) -@logger def fetch_incidents( client, last_run, From 6dbcc428d44f87cd152f8cfb08ae335be0f713a6 Mon Sep 17 00:00:00 2001 From: Juliya Smith Date: Thu, 11 Jun 2020 17:55:20 +0000 Subject: [PATCH 021/293] Add --- Packs/Code42/Integrations/Code42/Code42.py | 1 + 1 file changed, 1 insertion(+) diff --git a/Packs/Code42/Integrations/Code42/Code42.py b/Packs/Code42/Integrations/Code42/Code42.py index f9a9c8bd2ed4..1ea2568475a4 100644 --- a/Packs/Code42/Integrations/Code42/Code42.py +++ b/Packs/Code42/Integrations/Code42/Code42.py @@ -361,6 +361,7 @@ def map_to_file_context(obj): return _map_obj_to_context(obj, FILE_CONTEXT_FIELD_MAPPER) +@logger def _map_obj_to_context(obj, context_mapper): return {v: obj.get(k) for k, v in context_mapper.items() if obj.get(k)} From 29a5edab00bcd7d40810a3441c654f2dbf223cbd Mon Sep 17 00:00:00 2001 From: Juliya Smith Date: Thu, 11 Jun 2020 18:11:41 +0000 Subject: [PATCH 022/293] Replace more case with userid --- Packs/Code42/Integrations/Code42/Code42.yml | 6 +- Packs/Code42/Integrations/Code42/README.md | 12 +- .../Code42/integration-Code42.yml | 974 ++++++++++++++++++ 3 files changed, 983 insertions(+), 9 deletions(-) create mode 100644 Packs/Code42/Integrations/Code42/integration-Code42.yml diff --git a/Packs/Code42/Integrations/Code42/Code42.yml b/Packs/Code42/Integrations/Code42/Code42.yml index 55abf65826c4..564b3decfff4 100644 --- a/Packs/Code42/Integrations/Code42/Code42.yml +++ b/Packs/Code42/Integrations/Code42/Code42.yml @@ -252,7 +252,7 @@ script: description: Note to attach to the Departing Employee. outputs: - contextPath: Code42.DepartingEmployee.UserID - description: Internal Code42 Case ID for the Departing Employee. + description: Internal Code42 User ID for the Departing Employee. type: string - contextPath: Code42.DepartingEmployee.Username description: The username of the Departing Employee. @@ -268,8 +268,8 @@ script: - name: username description: The username to remove from the Departing Employee List. outputs: - - contextPath: Code42.DepartingEmployee.CaseID - description: Internal Code42 Case ID for the Departing Employee. + - 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. diff --git a/Packs/Code42/Integrations/Code42/README.md b/Packs/Code42/Integrations/Code42/README.md index 9c4630ae543c..0eb917cd555e 100644 --- a/Packs/Code42/Integrations/Code42/README.md +++ b/Packs/Code42/Integrations/Code42/README.md @@ -306,7 +306,7 @@ This command requires one of the following roles: | **Path** | **Type** | **Description** | | --- | --- | --- | -| Code42.DepartingEmployee.CaseID | string | Internal Code42 Case ID for Departing Employee | +| 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 | @@ -321,7 +321,7 @@ This command requires one of the following roles: ``` { "DepartingEmployee": { - "CaseID": "892", + "UserID": "892", "DepartureDate": "2020-02-28", "Note": "Leaving for competitor", "Username": "john.user@123.org" @@ -331,7 +331,7 @@ This command requires one of the following roles: ##### Human Readable Output -| **CaseID** | **DepartureDate** | **Note** | **Username** | +| **UserID** | **DepartureDate** | **Note** | **Username** | | --- | --- | --- | --- | | 123 | 2020-02-28 | Leaving for competitor | john.user@123.org | @@ -360,7 +360,7 @@ This command requires one of the following roles: | **Path** | **Type** | **Description** | | --- | --- | --- | -| Code42.DepartingEmployee.CaseID | unknown | Internal Code42 Case ID for Departing Employee | +| Code42.DepartingEmployee.UserID | unknown | Internal Code42 User ID for Departing Employee | | Code42.DepartingEmployee.Username | unknown | Username for Departing Employee | @@ -373,7 +373,7 @@ This command requires one of the following roles: ``` { "DepartingEmployee": { - "CaseID": "892", + "UserID": "892", "Username": "john.user@123.org" } } @@ -381,7 +381,7 @@ This command requires one of the following roles: ##### Human Readable Output -| **CaseID** | **Username** | +| **UserID** | **Username** | | --- | --- | | 123 | john.user@123.org | diff --git a/Packs/Code42/Integrations/Code42/integration-Code42.yml b/Packs/Code42/Integrations/Code42/integration-Code42.yml new file mode 100644 index 000000000000..b4f6a0351e3d --- /dev/null +++ b/Packs/Code42/Integrations/Code42/integration-Code42.yml @@ -0,0 +1,974 @@ +commonfields: + id: Code42 + version: -1 +name: Code42 +display: Code42 +category: Endpoint +description: Use the Code42 integration to identify potential data exfiltration from insider threats while speeding investigation and response by providing fast access to file events and metadata across physical and cloud environments. +configuration: +- display: Code42 Console URL for the pod your Code42 instance is running in + name: console_url + defaultvalue: console.us.code42.com + type: 0 + required: true +- display: "" + name: credentials + defaultvalue: "" + type: 9 + required: true +- display: Fetch incidents + name: isFetch + type: 8 + required: false +- display: Incident type + name: incidentType + type: 13 + required: false +- display: Alert severities to fetch when fetching incidents + name: alert_severity + defaultvalue: "" + type: 16 + required: false + options: + - High + - Medium + - Low +- display: First fetch time range (