Skip to content

Commit fdbe971

Browse files
author
Juliya Smith
authored
Merge pull request #23 from code42/download-file
Address PANW PR Feedback
2 parents 3405a2e + 038c7af commit fdbe971

File tree

7 files changed

+107
-37
lines changed

7 files changed

+107
-37
lines changed

Packs/Code42/IncidentFields/incidentfield-Code42_SecurityAlert_Timestamp.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@
3131
"system": false,
3232
"systemAssociatedTypes": null,
3333
"threshold": 72,
34-
"type": "shortText",
34+
"type": "date",
3535
"unmapped": false,
3636
"unsearchable": false,
3737
"useAsKpi": false,

Packs/Code42/IncidentFields/incidentfield-Code42_Username.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
2-
"associatedToAll": true,
2+
"associatedToAll": false,
33
"associatedTypes": null,
44
"breachScript": "",
55
"caseInsensitive": true,

Packs/Code42/Integrations/Code42/Code42.py

Lines changed: 37 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -136,8 +136,7 @@ def _create_alert_query(event_severity_filter, start_time):
136136

137137
def _get_all_high_risk_employees_from_page(page, risk_tags):
138138
res = []
139-
# Note: page is a `Py42Response` and has no `get()` method.
140-
employees = page["items"]
139+
employees = page.get("items") or []
141140
for employee in employees:
142141
if not risk_tags:
143142
res.append(employee)
@@ -198,8 +197,8 @@ def get_all_departing_employees(self, results):
198197
results = int(results) if results else None
199198
pages = self._get_sdk().detectionlists.departing_employee.get_all()
200199
for page in pages:
201-
# Note: page is a `Py42Response` and has no `get()` method.
202-
employees = page["items"]
200+
page_json = json.loads(page.text)
201+
employees = page_json.get("items") or []
203202
for employee in employees:
204203
res.append(employee)
205204
if results and len(res) == results:
@@ -236,7 +235,8 @@ def get_all_high_risk_employees(self, risk_tags, results):
236235
res = []
237236
pages = self._get_sdk().detectionlists.high_risk_employee.get_all()
238237
for page in pages:
239-
employees = _get_all_high_risk_employees_from_page(page, risk_tags)
238+
page_json = json.loads(page.text)
239+
employees = _get_all_high_risk_employees_from_page(page_json, risk_tags)
240240
for employee in employees:
241241
res.append(employee)
242242
if results and len(res) == results:
@@ -246,10 +246,11 @@ def get_all_high_risk_employees(self, risk_tags, results):
246246
def fetch_alerts(self, start_time, event_severity_filter):
247247
query = _create_alert_query(event_severity_filter, start_time)
248248
res = self._get_sdk().alerts.search(query)
249-
return res["alerts"]
249+
return json.loads(res.text).get("alerts")
250250

251251
def get_alert_details(self, alert_id):
252-
res = self._get_sdk().alerts.get_details(alert_id)["alerts"]
252+
py42_res = self._get_sdk().alerts.get_details(alert_id)
253+
res = json.loads(py42_res.text).get("alerts")
253254
if not res:
254255
raise Code42AlertNotFoundError(alert_id)
255256
return res[0]
@@ -263,7 +264,8 @@ def get_current_user(self):
263264
return res
264265

265266
def get_user(self, username):
266-
res = self._get_sdk().users.get_by_username(username)["users"]
267+
py42_res = self._get_sdk().users.get_by_username(username)
268+
res = json.loads(py42_res.text).get("users")
267269
if not res:
268270
raise Code42UserNotFoundError(username)
269271
return res[0]
@@ -320,15 +322,16 @@ def remove_user_from_legal_hold_matter(self, username, matter_name):
320322
def get_org(self, org_name):
321323
org_pages = self._get_sdk().orgs.get_all()
322324
for org_page in org_pages:
323-
orgs = org_page["orgs"]
325+
page_json = json.loads(org_page.text)
326+
orgs = page_json.get("orgs")
324327
for org in orgs:
325328
if org.get("orgName") == org_name:
326329
return org
327330
raise Code42OrgNotFoundError(org_name)
328331

329332
def search_file_events(self, payload):
330-
res = self._get_sdk().securitydata.search_file_events(payload)
331-
return res["fileEvents"]
333+
py42_res = self._get_sdk().securitydata.search_file_events(payload)
334+
return json.loads(py42_res.text).get("fileEvents")
332335

333336
def download_file(self, hash_arg):
334337
security_module = self._get_sdk().securitydata
@@ -337,7 +340,7 @@ def download_file(self, hash_arg):
337340
elif _hash_is_sha256(hash_arg):
338341
return security_module.stream_file_by_sha256(hash_arg)
339342
else:
340-
raise Exception("Unsupported hash. Must be SHA256 or MD5.")
343+
raise Code42UnsupportedHashError()
341344

342345
def _get_user_id(self, username):
343346
user_id = self.get_user(username).get("userUid")
@@ -391,6 +394,20 @@ def __init__(self, org_name):
391394
)
392395

393396

397+
class Code42UnsupportedHashError(Exception):
398+
def __init__(self):
399+
super(Code42UnsupportedHashError, self).__init__(
400+
"Unsupported hash. Must be SHA256 or MD5."
401+
)
402+
403+
404+
class Code42MissingSearchArgumentsError(Exception):
405+
def __init__(self):
406+
super(Code42MissingSearchArgumentsError, self).__init__(
407+
"No query args provided for searching Code42 security events."
408+
)
409+
410+
394411
class Code42LegalHoldMatterNotFoundError(Exception):
395412
def __init__(self, matter_name):
396413
super(Code42LegalHoldMatterNotFoundError, self).__init__(
@@ -401,8 +418,9 @@ def __init__(self, matter_name):
401418
class Code42InvalidLegalHoldMembershipError(Exception):
402419
def __init__(self, username, matter_name):
403420
super(Code42InvalidLegalHoldMembershipError, self).__init__(
404-
"User '{0}' is not an active member of legal hold matter '{1}'".format(username,
405-
matter_name)
421+
"User '{0}' is not an active member of legal hold matter '{1}'".format(
422+
username, matter_name
423+
)
406424
)
407425

408426

@@ -468,6 +486,9 @@ def build_query_payload(args):
468486
username = args.get("username")
469487
exposure = args.get("exposure")
470488

489+
if not _hash and not hostname and not username and not exposure:
490+
raise Code42MissingSearchArgumentsError()
491+
471492
search_args = FileEventQueryFilters(pg_size)
472493
search_args.append_result(_hash, _create_hash_filter)
473494
search_args.append_result(hostname, OSHostname.eq)
@@ -1143,7 +1164,7 @@ def _fetch_alerts(self, start_query_time):
11431164
return self._client.fetch_alerts(start_query_time, self._event_severity_filter)
11441165

11451166
def _create_incident_from_alert(self, alert):
1146-
details = self._client.get_alert_details(alert["id"])
1167+
details = self._client.get_alert_details(alert.get("id"))
11471168
incident = _create_incident_from_alert_details(details)
11481169
if self._include_files:
11491170
details = self._relate_files_to_alert(details)
@@ -1161,7 +1182,7 @@ def _relate_files_to_alert(self, alert_details):
11611182
return alert_details
11621183

11631184
def _get_file_events_from_alert_details(self, observation, alert_details):
1164-
security_data_query = map_observation_to_security_query(observation, alert_details["actor"])
1185+
security_data_query = map_observation_to_security_query(observation, alert_details.get("actor"))
11651186
return self._client.search_file_events(security_data_query)
11661187

11671188

Packs/Code42/Integrations/Code42/Code42_test.py

Lines changed: 63 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,6 @@
55
from py42.response import Py42Response
66
from Code42 import (
77
Code42Client,
8-
Code42AlertNotFoundError,
9-
Code42UserNotFoundError,
108
Code42LegalHoldMatterNotFoundError,
119
Code42InvalidLegalHoldMembershipError,
1210
build_query_payload,
@@ -34,6 +32,11 @@
3432
legal_hold_remove_user_command,
3533
download_file_command,
3634
fetch_incidents,
35+
Code42AlertNotFoundError,
36+
Code42UserNotFoundError,
37+
Code42OrgNotFoundError,
38+
Code42UnsupportedHashError,
39+
Code42MissingSearchArgumentsError,
3740
)
3841
import time
3942

@@ -1293,8 +1296,13 @@ def assert_detection_list_outputs_match_response_items(outputs_list, response_it
12931296
"""TESTS"""
12941297

12951298

1296-
def test_client_lazily_inits_sdk(mocker):
1297-
mocker.patch("py42.sdk.from_local_account")
1299+
def test_client_lazily_inits_sdk(mocker, code42_sdk_mock):
1300+
sdk_factory_mock = mocker.patch("py42.sdk.from_local_account")
1301+
response_json_mock = """{"total": 1, "users": [{"username": "Test"}]}"""
1302+
code42_sdk_mock.users.get_by_username.return_value = create_mock_code42_sdk_response(
1303+
mocker, response_json_mock
1304+
)
1305+
sdk_factory_mock.return_value = code42_sdk_mock
12981306

12991307
# test that sdk does not init during ctor
13001308
client = Code42Client(sdk=None, base_url=MOCK_URL, auth=MOCK_AUTH, verify=False, proxy=False)
@@ -1305,23 +1313,28 @@ def test_client_lazily_inits_sdk(mocker):
13051313
assert client._sdk is not None
13061314

13071315

1308-
def test_client_when_no_alert_found_raises_alert_not_found(code42_sdk_mock):
1309-
code42_sdk_mock.alerts.get_details.return_value = (
1310-
json.loads('{"type$": "ALERT_DETAILS_RESPONSE", "alerts": []}')
1316+
def test_client_when_no_alert_found_raises_alert_not_found(mocker, code42_sdk_mock):
1317+
response_json = """{"alerts": []}"""
1318+
code42_sdk_mock.alerts.get_details.return_value = create_mock_code42_sdk_response(
1319+
mocker, response_json
13111320
)
13121321
client = create_client(code42_sdk_mock)
13131322
with pytest.raises(Code42AlertNotFoundError):
13141323
client.get_alert_details("mock-id")
13151324

13161325

1317-
def test_client_when_no_user_found_raises_user_not_found(code42_sdk_mock):
1318-
code42_sdk_mock.users.get_by_username.return_value = json.loads('{"totalCount":0, "users":[]}')
1326+
def test_client_when_no_user_found_raises_user_not_found(mocker, code42_sdk_mock):
1327+
response_json = """{"totalCount": 0, "users": []}"""
1328+
code42_sdk_mock.users.get_by_username.return_value = create_mock_code42_sdk_response(
1329+
mocker, response_json
1330+
)
13191331
client = create_client(code42_sdk_mock)
13201332
with pytest.raises(Code42UserNotFoundError):
13211333
client.get_user("[email protected]")
13221334

13231335

13241336
def test_client_add_to_matter_when_no_legal_hold_matter_found_raises_matter_not_found(code42_sdk_mock, mocker):
1337+
13251338
code42_sdk_mock.legalhold.get_all_matters.return_value = (
13261339
get_empty_legalhold_matters_response(mocker, MOCK_GET_ALL_MATTERS_RESPONSE)
13271340
)
@@ -1331,8 +1344,9 @@ def test_client_add_to_matter_when_no_legal_hold_matter_found_raises_matter_not_
13311344
client.add_user_to_legal_hold_matter("TESTUSERNAME", "TESTMATTERNAME")
13321345

13331346

1334-
def test_client_add_to_matter_when_no_user_found_raises_user_not_found(code42_sdk_mock):
1335-
code42_sdk_mock.users.get_by_username.return_value = json.loads('{"totalCount":0, "users":[]}')
1347+
def test_client_add_to_matter_when_no_user_found_raises_user_not_found(mocker, code42_sdk_mock):
1348+
response_json = '{"totalCount":0, "users":[]}'
1349+
code42_sdk_mock.users.get_by_username.return_value = create_mock_code42_sdk_response(mocker, response_json)
13361350
client = create_client(code42_sdk_mock)
13371351
with pytest.raises(Code42UserNotFoundError):
13381352
client.add_user_to_legal_hold_matter("TESTUSERNAME", "TESTMATTERNAME")
@@ -1348,8 +1362,9 @@ def test_client_remove_from_matter_when_no_legal_hold_matter_found_raises_except
13481362
client.remove_user_from_legal_hold_matter("TESTUSERNAME", "TESTMATTERNAME")
13491363

13501364

1351-
def test_client_remove_from_matter_when_no_user_found_raises_user_not_found(code42_sdk_mock):
1352-
code42_sdk_mock.users.get_by_username.return_value = json.loads('{"totalCount":0, "users":[]}')
1365+
def test_client_remove_from_matter_when_no_user_found_raises_user_not_found(mocker, code42_sdk_mock):
1366+
response_json = '{"totalCount":0, "users":[]}'
1367+
code42_sdk_mock.users.get_by_username.return_value = create_mock_code42_sdk_response(mocker, response_json)
13531368
client = create_client(code42_sdk_mock)
13541369
with pytest.raises(Code42UserNotFoundError):
13551370
client.remove_user_from_legal_hold_matter("TESTUSERNAME", "TESTMATTERNAME")
@@ -1725,6 +1740,23 @@ def test_user_create_command(code42_users_mock):
17251740
assert cmd_res.outputs["Email"] == "[email protected]"
17261741

17271742

1743+
def test_user_create_command_when_org_not_found_raises_org_not_found(mocker, code42_users_mock):
1744+
response_json = """{"total": 0, "orgs": []}"""
1745+
code42_users_mock.orgs.get_all.return_value = create_mock_code42_sdk_response_generator(
1746+
mocker, [response_json]
1747+
)
1748+
client = create_client(code42_users_mock)
1749+
with pytest.raises(Code42OrgNotFoundError):
1750+
user_create_command(
1751+
client,
1752+
{
1753+
"orgname": _TEST_ORG_NAME,
1754+
"username": "[email protected]",
1755+
"email": "[email protected]",
1756+
}
1757+
)
1758+
1759+
17281760
def test_user_block_command(code42_users_mock):
17291761
client = create_client(code42_users_mock)
17301762
cmd_res = user_block_command(client, {"username": "[email protected]"})
@@ -1798,6 +1830,12 @@ def test_security_data_search_command(code42_file_events_mock):
17981830
assert output_item == mapped_event
17991831

18001832

1833+
def test_securitydata_search_command_when_not_given_any_queryable_args_raises_error(code42_file_events_mock):
1834+
client = create_client(code42_file_events_mock)
1835+
with pytest.raises(Code42MissingSearchArgumentsError):
1836+
securitydata_search_command(client, {})
1837+
1838+
18011839
def test_download_file_command_when_given_md5(code42_sdk_mock, mocker):
18021840
fr = mocker.patch("Code42.fileResult")
18031841
client = create_client(code42_sdk_mock)
@@ -1817,6 +1855,15 @@ def test_download_file_command_when_given_sha256(code42_sdk_mock, mocker):
18171855
assert fr.call_count == 1
18181856

18191857

1858+
def test_download_file_when_given_other_hash_raises_unsupported_hash(code42_sdk_mock, mocker):
1859+
mocker.patch("Code42.fileResult")
1860+
_hash = "41966f10cc59ab466444add08974fde4cd37f88d79321d42da8e4c79b51c214941966f10cc59ab466444add08974fde4cd37" \
1861+
"f88d79321d42da8e4c79b51c2149"
1862+
client = create_client(code42_sdk_mock)
1863+
with pytest.raises(Code42UnsupportedHashError):
1864+
_ = download_file_command(client, {"hash": _hash})
1865+
1866+
18201867
def test_fetch_when_no_significant_file_categories_ignores_filter(
18211868
code42_fetch_incidents_mock, mocker
18221869
):
@@ -1840,8 +1887,8 @@ def test_fetch_when_no_significant_file_categories_ignores_filter(
18401887
assert "IMAGE" not in actual_query
18411888

18421889

1843-
def test_fetch_incidents_handles_single_severity(code42_sdk_mock):
1844-
client = create_client(code42_sdk_mock)
1890+
def test_fetch_incidents_handles_single_severity(code42_fetch_incidents_mock):
1891+
client = create_client(code42_fetch_incidents_mock)
18451892
fetch_incidents(
18461893
client=client,
18471894
last_run={"last_fetch": None},
@@ -1851,7 +1898,7 @@ def test_fetch_incidents_handles_single_severity(code42_sdk_mock):
18511898
include_files=True,
18521899
integration_context=None,
18531900
)
1854-
assert "HIGH" in str(code42_sdk_mock.alerts.search.call_args[0][0])
1901+
assert "HIGH" in str(code42_fetch_incidents_mock.alerts.search.call_args[0][0])
18551902

18561903

18571904
def test_fetch_incidents_handles_multi_severity(code42_fetch_incidents_mock):

Packs/Code42/Integrations/Code42/README.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
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.
2-
This integration was integrated and tested with version xx of Code42
32
## Configure Code42 on Cortex XSOAR
43

54
1. Navigate to **Settings** > **Integrations** > **Servers & Services**.

Packs/Code42/Integrations/Code42/command_examples.txt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@
33
!code42-departingemployee-add username="[email protected]" departuredate="2020-010-28" note="Leaving for competitor"
44
!code42-departingemployee-remove username="[email protected]"
55
!code42-departingemployee-get-all
6-
!code42-highriskemployee-add username="[email protected]" note="Risky activity"
7-
!code42-highriskemployee-remove username="[email protected]" note="Risky activity"
6+
!code42-highriskemployee-add username="[email protected]" note="Approved activity"
7+
!code42-highriskemployee-remove username="[email protected]" note="Approved activity"
88
!code42-highriskemployee-get-all
99
!code42-highriskemployee-add-risk-tags username="[email protected]" note="PERFORMANCE_CONCERN"
1010
!code42-highriskemployee-remove-risk-tags username="[email protected]" risktags="PERFORMANCE_CONCERNS"

Packs/Code42/ReleaseNotes/2_0_0.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11

22
#### Integrations
33
##### Code42
4+
## [Unreleased]
45
- Internal code improvements.
56
- Added new commands:
67
- **code42-departingemployee-get-all**
@@ -14,6 +15,8 @@
1415
- **code42-user-block**
1516
- **code42-user-unblock**
1617
- **code42-user-create**
18+
- **code42-legalhold-add-user**
19+
- **code42-legalhold-remove-user**
1720
- **code42-file-download**
1821
- Improve error messages for all Commands to include exception detail.
1922
- **Code42 Exfiltration Playbook** now downloads the file in replace of a manual step for retrieving file contents.

0 commit comments

Comments
 (0)