Skip to content

Download file #15

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 42 commits into from
Jul 2, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
b986610
Save
Jun 30, 2020
b2b29be
Merge branch 'user-mgmt' into download-file
Jun 30, 2020
78023a7
Save
Jun 30, 2020
450b3c6
Merge branch 'user-mgmt' into download-file
Jun 30, 2020
4ca73ec
Test dwnld file
Jun 30, 2020
e3bf357
Merge branch 'user-mgmt' into download-file
Jun 30, 2020
01a5342
Merge branch 'master' into download-file
Jul 1, 2020
77deb16
save
Jul 1, 2020
6a7240e
Chunks
Jul 1, 2020
19e321a
Lint
Jul 1, 2020
fc12be7
lint
Jul 1, 2020
1dec53b
lint
Jul 1, 2020
aaad704
Download file pb
Jul 1, 2020
5087455
Remove params from a test that arent used
Jul 1, 2020
ca149e4
Update exil pb
Jul 1, 2020
ce54558
Playbooks
Jul 1, 2020
c4dc843
Add missing cl
Jul 1, 2020
09b4cba
Use correct checksum from incident
Jul 1, 2020
6a0479f
Add evidence and ignore errors for file download
Jul 1, 2020
b4d95db
Remove outputs
Jul 1, 2020
650c7d2
Fix description
Jul 1, 2020
da40cdc
Fix ID
Jul 1, 2020
6b85c58
Fix validation errorS
Jul 1, 2020
86dd4f0
Fix validation errors
Jul 1, 2020
cdb1cd3
Fix validation error
Jul 1, 2020
2159d9d
Fix validation error
Jul 1, 2020
3de8568
Removed integration file - not supposed to be there i guess
Jul 1, 2020
934bb04
Update release notes and fix validation::
Jul 1, 2020
6edf3c0
Remove breaking change i guess
Jul 1, 2020
4d80ac4
Fix playbook
Jul 1, 2020
8aa6272
Fix validation errors
Jul 1, 2020
6022de8
readme
Jul 1, 2020
899d1a5
Use enum
Jul 2, 2020
9031bf5
Add outside trusted domains to predefined exposure type list for fetch
Jul 2, 2020
3730cdb
Slight refactor
Jul 2, 2020
3e762ed
Rename var
Jul 2, 2020
65324bb
Not files
Jul 2, 2020
d69031f
Remove integ file
Jul 2, 2020
fd3d30d
Undo accident
Jul 2, 2020
095a84e
changelog fix
Jul 2, 2020
3388f4a
Call isinstance only once
Jul 2, 2020
278eb43
Simplify command
Jul 2, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 18 additions & 14 deletions Packs/Code42/Integrations/Code42/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,18 +1,22 @@
## [Unreleased]
Added new commands:
- **code42-departingemployee-get-all** that gets all the employees on the Departing Employee List.
- **code42-highriskemployee-add** that takes a username and adds the employee to the High Risk Employee List.
- **code42-highriskemployee-remove** that takes a username and remove the employee from the High Risk Employee List.
- **code42-highriskemployee-get-all** that gets all the employees on the High Risk Employee List.
Optionally takes a list of risk tags and only gets employees who have those risk tags.
- **code42-highriskemployee-add-risk-tags** that takes a username and risk tags and associates the risk tags with the user.
- **code42-highriskemployee-remove-risk-tags** that takes a username and risk tags and disassociates the risk tags from the user.
- **code42-user-deactivate** that deactivates a user in Code42.
- **code42-user-reactivate** that reactivates a user in Code42.
- **code42-user-block** that blocks a user in Code42.
- **code42-user-unblock** that unblocks a user in Code42.
- **code42-user-create** that creates a user in Code42.
Improve error messages for all Commands to include exception detail.
- Internal code improvements.
- Added new commands:
- **code42-departingemployee-get-all**
- **code42-highriskemployee-add**
- **code42-highriskemployee-remove**
- **code42-highriskemployee-get-all**
- **code42-highriskemployee-add-risk-tags**
- **code42-highriskemployee-remove-risk-tags**
- **code42-user-deactivate**
- **code42-user-reactivate**
- **code42-user-block**
- **code42-user-unblock**
- **code42-user-create**
- **code42-file-download**
- Improve error messages for all Commands to include exception detail.
- Fixed bug in Fetch where errors occurred when `FileCategory` was set to include only one category.
- Fixed bug in Fetch to handle new Code42 exposure type **Outside trusted domains**.
- Improved Fetch to handle unsupported exposure types better.

## [20.3.3] - 2020-03-18
#### New Integration
Expand Down
64 changes: 46 additions & 18 deletions Packs/Code42/Integrations/Code42/Code42.py
Original file line number Diff line number Diff line change
Expand Up @@ -306,6 +306,15 @@ def search_file_events(self, payload):
res = self._get_sdk().securitydata.search_file_events(payload)
return res["fileEvents"]

def download_file(self, hash_arg):
security_module = self._get_sdk().securitydata
if _hash_is_md5(hash_arg):
return security_module.stream_file_by_md5(hash_arg)
elif _hash_is_sha256(hash_arg):
return security_module.stream_file_by_sha256(hash_arg)
else:
raise Exception("Unsupported hash. Must be SHA256 or MD5.")

def _get_user_id(self, username):
user_id = self.get_user(username).get("userUid")
if user_id:
Expand Down Expand Up @@ -419,12 +428,18 @@ def build_query_payload(args):
return query


def _hash_is_sha256(hash_arg):
return hash_arg and len(hash_arg) == 64


def _hash_is_md5(hash_arg):
return hash_arg and len(hash_arg) == 32


def _create_hash_filter(hash_arg):
if not hash_arg:
return None
elif len(hash_arg) == 32:
if _hash_is_md5(hash_arg):
return MD5.eq(hash_arg)
elif len(hash_arg) == 64:
elif _hash_is_sha256(hash_arg):
return SHA256.eq(hash_arg)


Expand Down Expand Up @@ -455,7 +470,7 @@ class ObservationToSecurityQueryMapper(object):
exposure_type_map = {
"PublicSearchableShare": ExposureType.IS_PUBLIC,
"PublicLinkShare": ExposureType.SHARED_VIA_LINK,
"SharedOutsideTrustedDomain": "OutsideTrustedDomains",
"SharedOutsideTrustedDomain": ExposureType.OUTSIDE_TRUSTED_DOMAINS,
}

def __init__(self, observation, actor):
Expand Down Expand Up @@ -935,6 +950,13 @@ def user_reactivate_command(client, args):
)


def download_file_command(client, args):
file_hash = args.get("hash")
response = client.download_file(file_hash)
file_chunks = [c for c in response.iter_content(chunk_size=128) if c]
return fileResult(file_hash, data=b"".join(file_chunks))

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this mean XSOAR will have the file stored with the hash as the filename? If the XSOAR user wants to download it to their machine do they then get it with the hash as the name too?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, that is exactly what happens. What do you think about that?



"""Fetching"""


Expand Down Expand Up @@ -974,7 +996,7 @@ def __init__(
self._first_fetch_time = first_fetch_time
self._event_severity_filter = event_severity_filter
self._fetch_limit = fetch_limit
self._include_files = (include_files,)
self._include_files = include_files
self._integration_context = integration_context

@logger
Expand All @@ -996,7 +1018,7 @@ def _fetch_remaining_incidents_from_last_run(self):
if remaining_incidents:
return (
self._last_run,
remaining_incidents[: self._fetch_limit],
remaining_incidents[:self._fetch_limit],
remaining_incidents[self._fetch_limit:],
)

Expand All @@ -1021,7 +1043,8 @@ def _fetch_alerts(self, start_query_time):
def _create_incident_from_alert(self, alert):
details = self._client.get_alert_details(alert["id"])
incident = _create_incident_from_alert_details(details)
details = self._relate_files_to_alert(details)
if self._include_files:
details = self._relate_files_to_alert(details)
incident["rawJSON"] = json.dumps(details)
return incident

Expand Down Expand Up @@ -1095,6 +1118,7 @@ def get_command_map():
"code42-user-unblock": user_unblock_command,
"code42-user-deactivate": user_deactivate_command,
"code42_user-reactivate": user_reactivate_command,
"code42-download-file": download_file_command,
}


Expand Down Expand Up @@ -1123,39 +1147,43 @@ def handle_fetch_command(client):
demisto.setIntegrationContext(integration_context)


def try_run_command(command):
def run_command(command):
try:
results = command()
if not isinstance(results, tuple) and not isinstance(results, list):
if not isinstance(results, (tuple, list)):
results = [results]
for result in results:
return_results(result)
except Exception as e:
return_error(create_command_error_message(demisto.command(), e))


def run_code42_integration():
def create_client():
username = demisto.params().get("credentials").get("identifier")
password = demisto.params().get("credentials").get("password")
base_url = demisto.params().get("console_url")
verify_certificate = not demisto.params().get("insecure", False)
proxy = demisto.params().get("proxy", False)
LOG("Command being called is {0}.".format(demisto.command()))
client = Code42Client(
return Code42Client(
base_url=base_url,
sdk=None,
auth=(username, password),
verify=verify_certificate,
proxy=proxy,
)


def run_code42_integration():
client = create_client()
commands = get_command_map()
command = demisto.command()
if command == "test-module":
command_key = demisto.command()
LOG("Command being called is {0}.".format(command_key))
if command_key == "test-module":
handle_test_command(client)
elif command == "fetch-incidents":
elif command_key == "fetch-incidents":
handle_fetch_command(client)
elif command in commands:
try_run_command(lambda: commands[command](client, demisto.args()))
elif command_key in commands:
run_command(lambda: commands[command_key](client, demisto.args()))


def main():
Expand Down
18 changes: 15 additions & 3 deletions Packs/Code42/Integrations/Code42/Code42.yml
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ script:
- auto: PREDEFINED
default: false
description: Exposure types to search for. Can be "RemovableMedia", "ApplicationRead",
"CloudStorage", "IsPublic", "SharedViaLink", or "SharedViaDomain".
"CloudStorage", "IsPublic", "SharedViaLink", "SharedViaDomain", or "OutsideTrustedDomains".
isArray: true
name: exposure
predefined:
Expand All @@ -89,6 +89,7 @@ script:
- IsPublic
- SharedViaLink
- SharedViaDomain
- OutsideTrustedDomains
required: false
secret: false
- default: false
Expand Down Expand Up @@ -331,7 +332,7 @@ script:
description: The username to remove from the Departing Employee List.
isArray: false
name: username
required: true
required: false
secret: false
deprecated: false
description: Removes a user from the Departing Employee List.
Expand Down Expand Up @@ -596,7 +597,18 @@ script:
- contextPath: Code42.User.UserID
description: The ID of a Code42 User.
type: String
dockerimage: demisto/py42:1.0.0.9323
- arguments:
- default: false
description: Either the SHA256 or MD5 hash of the file.
isArray: false
name: hash
required: true
secret: false
deprecated: false
description: Downloads a file from Code42 servers.
execution: false
name: code42-download-file
dockerimage: demisto/py42:1.0.0.9653
feed: false
isfetch: true
longRunning: false
Expand Down
68 changes: 56 additions & 12 deletions Packs/Code42/Integrations/Code42/Code42_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
user_unblock_command,
user_deactivate_command,
user_reactivate_command,
download_file_command,
fetch_incidents,
)
import time
Expand Down Expand Up @@ -1379,16 +1380,7 @@ def test_departingemployee_get_all_command_when_no_employees(
no_employees_response
)
client = create_client(code42_departing_employee_mock)
cmd_res = departingemployee_get_all_command(
client,
{
"risktags": [
"PERFORMANCE_CONCERNS",
"SUSPICIOUS_SYSTEM_ACTIVITY",
"POOR_SECURITY_PRACTICES",
]
},
)
cmd_res = departingemployee_get_all_command(client,{})
assert cmd_res.outputs_prefix == "Code42.DepartingEmployee"
assert cmd_res.outputs_key_field == "UserID"
assert cmd_res.raw_response == {}
Expand Down Expand Up @@ -1635,6 +1627,25 @@ def test_security_data_search_command(code42_file_events_mock):
assert output_item == mapped_event


def test_download_file_command_when_given_md5(code42_sdk_mock, mocker):
fr = mocker.patch("Code42.fileResult")
client = create_client(code42_sdk_mock)
_ = download_file_command(client, {"hash": "b6312dbe4aa4212da94523ccb28c5c16"})
code42_sdk_mock.securitydata.stream_file_by_md5.assert_called_once_with(
"b6312dbe4aa4212da94523ccb28c5c16"
)
assert fr.call_count == 1


def test_download_file_command_when_given_sha256(code42_sdk_mock, mocker):
fr = mocker.patch("Code42.fileResult")
_hash = "41966f10cc59ab466444add08974fde4cd37f88d79321d42da8e4c79b51c2149"
client = create_client(code42_sdk_mock)
_ = download_file_command(client, {"hash": _hash})
code42_sdk_mock.securitydata.stream_file_by_sha256.assert_called_once_with(_hash)
assert fr.call_count == 1


def test_fetch_when_no_significant_file_categories_ignores_filter(
code42_fetch_incidents_mock, mocker
):
Expand Down Expand Up @@ -1683,8 +1694,41 @@ def test_fetch_incidents_handles_multi_severity(code42_fetch_incidents_mock):
include_files=True,
integration_context=None,
)
assert "HIGH" in str(code42_fetch_incidents_mock.alerts.search.call_args[0][0])
assert "LOW" in str(code42_fetch_incidents_mock.alerts.search.call_args[0][0])
call_args = str(code42_fetch_incidents_mock.alerts.search.call_args[0][0])
assert "HIGH" in call_args
assert "LOW" in call_args


def test_fetch_when_include_files_includes_files(code42_fetch_incidents_mock):
client = create_client(code42_fetch_incidents_mock)
_, incidents, _ = fetch_incidents(
client=client,
last_run={"last_fetch": None},
first_fetch_time=MOCK_FETCH_TIME,
event_severity_filter=["High", "Low"],
fetch_limit=10,
include_files=True,
integration_context=None,
)
for i in incidents:
_json = json.loads(i["rawJSON"])
assert len(_json["fileevents"])


def test_fetch_when_not_include_files_excludes_files(code42_fetch_incidents_mock):
client = create_client(code42_fetch_incidents_mock)
_, incidents, _ = fetch_incidents(
client=client,
last_run={"last_fetch": None},
first_fetch_time=MOCK_FETCH_TIME,
event_severity_filter=["High", "Low"],
fetch_limit=10,
include_files=False,
integration_context=None,
)
for i in incidents:
_json = json.loads(i["rawJSON"])
assert not _json.get("fileevents")


def test_fetch_incidents_first_run(code42_fetch_incidents_mock):
Expand Down
24 changes: 24 additions & 0 deletions Packs/Code42/Integrations/Code42/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -717,3 +717,27 @@ Reactivates the user with the given username.
| UserID |
| ------ |
| 123456790 |


### code42-download-file
***
Downloads a file from Code42 servers.

#### Base Command

`code42-download-file`
#### Input

| **Argument Name** | **Description** | **Required** |
| --- | --- | --- |
| hash | Either the SHA256 or MD5 hash of the file. | Required |


#### Command Example
```!code42-download-file hash="bf6b326107d4d85eb485eed84b28133a"```

#### Human Readable Output
### Code42 User Deactivated
| Type | Size | Info | MD5 | SHA1 | SHA256 | SHA512 | SSDeep |
| ------ | ---- | ---- | --- | ---- | ------ | ------ | ------ |
| application/vnd.ms-excel | 41,472 bytes | Composite Document File V2 Document, Little Endian, Os: MacOS, Version 14.10, Code page: 10000, Last Saved By: John Doe, Name of Creating Application: Microsoft Macintosh Excel, Create Time/Date: Fri Feb 21 17:35:19 2020, Last Saved Time/Date: Mon Apr 13 11:54:08 2020, Security: 0 | 2e45562437ec4f41387f2e14c3850dd6 | 59e552e637bfe5254b163bb4e426a2322d10f50d | d3f8566d04df5dc34bf2607ac803a585ac81e06f28afe81f35cc2e5fe63d2ab5 | 776bd9626761cd567a4b498bafe4f5f896c3f4bc9f3c60513ccacd14251a2568fa3ba44060000affa8b57fb768c417cf271500086e4e49272f26b26a90627abb | 768:pudkQzl3ZpWh+QO3uMdS9dSttRJwyE/KtxA1almvy6mhk+GlESOwWoqSY7bTKCUv:siQzl3ZpWh+QO3uMdS9dSttRJwyE/KtF |
Loading