psengine 2.2.0__tar.gz → 2.3.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {psengine-2.2.0 → psengine-2.3.0}/PKG-INFO +16 -11
- {psengine-2.2.0 → psengine-2.3.0}/README.md +13 -10
- {psengine-2.2.0 → psengine-2.3.0}/psengine/_version.py +1 -1
- {psengine-2.2.0 → psengine-2.3.0}/psengine/analyst_notes/errors.py +6 -6
- {psengine-2.2.0 → psengine-2.3.0}/psengine/base_http_client.py +3 -2
- {psengine-2.2.0 → psengine-2.3.0}/psengine/endpoints.py +15 -0
- psengine-2.3.0/psengine/fusion/__init__.py +22 -0
- psengine-2.3.0/psengine/fusion/errors.py +34 -0
- psengine-2.3.0/psengine/fusion/fusion_mgr.py +197 -0
- psengine-2.3.0/psengine/fusion/models.py +59 -0
- psengine-2.3.0/psengine/malware_intel/__init__.py +16 -0
- psengine-2.3.0/psengine/malware_intel/errors.py +18 -0
- psengine-2.3.0/psengine/malware_intel/malware_intel.py +107 -0
- psengine-2.3.0/psengine/malware_intel/malware_intel_mgr.py +81 -0
- psengine-2.3.0/psengine/malware_intel/models.py +270 -0
- {psengine-2.2.0 → psengine-2.3.0}/psengine/playbook_alerts/markdown/markdown_identity_exposure.py +5 -1
- {psengine-2.2.0 → psengine-2.3.0}/psengine/rf_client.py +7 -4
- psengine-2.3.0/psengine/risk_history/__init__.py +16 -0
- psengine-2.3.0/psengine/risk_history/errors.py +18 -0
- psengine-2.3.0/psengine/risk_history/models.py +68 -0
- psengine-2.3.0/psengine/risk_history/risk_history_mgr.py +61 -0
- {psengine-2.2.0 → psengine-2.3.0}/psengine.egg-info/PKG-INFO +16 -11
- {psengine-2.2.0 → psengine-2.3.0}/psengine.egg-info/SOURCES.txt +13 -0
- {psengine-2.2.0 → psengine-2.3.0}/psengine.egg-info/requires.txt +2 -0
- {psengine-2.2.0 → psengine-2.3.0}/pyproject.toml +5 -1
- {psengine-2.2.0 → psengine-2.3.0}/LICENSE +0 -0
- {psengine-2.2.0 → psengine-2.3.0}/psengine/__init__.py +0 -0
- {psengine-2.2.0 → psengine-2.3.0}/psengine/_sdk_id.py +0 -0
- {psengine-2.2.0 → psengine-2.3.0}/psengine/analyst_notes/__init__.py +0 -0
- {psengine-2.2.0 → psengine-2.3.0}/psengine/analyst_notes/constants.py +0 -0
- {psengine-2.2.0 → psengine-2.3.0}/psengine/analyst_notes/helpers.py +0 -0
- {psengine-2.2.0 → psengine-2.3.0}/psengine/analyst_notes/markdown.py +0 -0
- {psengine-2.2.0 → psengine-2.3.0}/psengine/analyst_notes/models.py +0 -0
- {psengine-2.2.0 → psengine-2.3.0}/psengine/analyst_notes/note.py +0 -0
- {psengine-2.2.0 → psengine-2.3.0}/psengine/analyst_notes/note_mgr.py +0 -0
- {psengine-2.2.0 → psengine-2.3.0}/psengine/classic_alerts/__init__.py +0 -0
- {psengine-2.2.0 → psengine-2.3.0}/psengine/classic_alerts/classic_alert.py +0 -0
- {psengine-2.2.0 → psengine-2.3.0}/psengine/classic_alerts/classic_alert_mgr.py +0 -0
- {psengine-2.2.0 → psengine-2.3.0}/psengine/classic_alerts/constants.py +0 -0
- {psengine-2.2.0 → psengine-2.3.0}/psengine/classic_alerts/errors.py +0 -0
- {psengine-2.2.0 → psengine-2.3.0}/psengine/classic_alerts/helpers.py +0 -0
- {psengine-2.2.0 → psengine-2.3.0}/psengine/classic_alerts/markdown/__init__.py +0 -0
- {psengine-2.2.0 → psengine-2.3.0}/psengine/classic_alerts/markdown/markdown.py +0 -0
- {psengine-2.2.0 → psengine-2.3.0}/psengine/classic_alerts/models.py +0 -0
- {psengine-2.2.0 → psengine-2.3.0}/psengine/collective_insights/__init__.py +0 -0
- {psengine-2.2.0 → psengine-2.3.0}/psengine/collective_insights/collective_insights.py +0 -0
- {psengine-2.2.0 → psengine-2.3.0}/psengine/collective_insights/constants.py +0 -0
- {psengine-2.2.0 → psengine-2.3.0}/psengine/collective_insights/errors.py +0 -0
- {psengine-2.2.0 → psengine-2.3.0}/psengine/collective_insights/insight.py +0 -0
- {psengine-2.2.0 → psengine-2.3.0}/psengine/collective_insights/models.py +0 -0
- {psengine-2.2.0 → psengine-2.3.0}/psengine/common_models.py +0 -0
- {psengine-2.2.0 → psengine-2.3.0}/psengine/config/__init__.py +0 -0
- {psengine-2.2.0 → psengine-2.3.0}/psengine/config/config.py +0 -0
- {psengine-2.2.0 → psengine-2.3.0}/psengine/config/errors.py +0 -0
- {psengine-2.2.0 → psengine-2.3.0}/psengine/constants.py +0 -0
- {psengine-2.2.0 → psengine-2.3.0}/psengine/detection/__init__.py +0 -0
- {psengine-2.2.0 → psengine-2.3.0}/psengine/detection/detection_mgr.py +0 -0
- {psengine-2.2.0 → psengine-2.3.0}/psengine/detection/detection_rule.py +0 -0
- {psengine-2.2.0 → psengine-2.3.0}/psengine/detection/errors.py +0 -0
- {psengine-2.2.0 → psengine-2.3.0}/psengine/detection/helpers.py +0 -0
- {psengine-2.2.0 → psengine-2.3.0}/psengine/detection/models.py +0 -0
- {psengine-2.2.0 → psengine-2.3.0}/psengine/enrich/__init__.py +0 -0
- {psengine-2.2.0 → psengine-2.3.0}/psengine/enrich/constants.py +0 -0
- {psengine-2.2.0 → psengine-2.3.0}/psengine/enrich/errors.py +0 -0
- {psengine-2.2.0 → psengine-2.3.0}/psengine/enrich/lookup.py +0 -0
- {psengine-2.2.0 → psengine-2.3.0}/psengine/enrich/lookup_mgr.py +0 -0
- {psengine-2.2.0 → psengine-2.3.0}/psengine/enrich/models/__init__.py +0 -0
- {psengine-2.2.0 → psengine-2.3.0}/psengine/enrich/models/base_enriched_entity.py +0 -0
- {psengine-2.2.0 → psengine-2.3.0}/psengine/enrich/models/lookup.py +0 -0
- {psengine-2.2.0 → psengine-2.3.0}/psengine/enrich/models/soar.py +0 -0
- {psengine-2.2.0 → psengine-2.3.0}/psengine/enrich/soar.py +0 -0
- {psengine-2.2.0 → psengine-2.3.0}/psengine/enrich/soar_mgr.py +0 -0
- {psengine-2.2.0 → psengine-2.3.0}/psengine/entity_lists/__init__.py +0 -0
- {psengine-2.2.0 → psengine-2.3.0}/psengine/entity_lists/constants.py +0 -0
- {psengine-2.2.0 → psengine-2.3.0}/psengine/entity_lists/entity_list.py +0 -0
- {psengine-2.2.0 → psengine-2.3.0}/psengine/entity_lists/entity_list_mgr.py +0 -0
- {psengine-2.2.0 → psengine-2.3.0}/psengine/entity_lists/errors.py +0 -0
- {psengine-2.2.0 → psengine-2.3.0}/psengine/entity_lists/models.py +0 -0
- {psengine-2.2.0 → psengine-2.3.0}/psengine/entity_match/__init__.py +0 -0
- {psengine-2.2.0 → psengine-2.3.0}/psengine/entity_match/entity_match.py +0 -0
- {psengine-2.2.0 → psengine-2.3.0}/psengine/entity_match/entity_match_mgr.py +0 -0
- {psengine-2.2.0 → psengine-2.3.0}/psengine/entity_match/errors.py +0 -0
- {psengine-2.2.0 → psengine-2.3.0}/psengine/entity_match/models.py +0 -0
- {psengine-2.2.0 → psengine-2.3.0}/psengine/errors.py +0 -0
- {psengine-2.2.0 → psengine-2.3.0}/psengine/helpers/__init__.py +0 -0
- {psengine-2.2.0 → psengine-2.3.0}/psengine/helpers/helpers.py +0 -0
- {psengine-2.2.0 → psengine-2.3.0}/psengine/identity/__init__.py +0 -0
- {psengine-2.2.0 → psengine-2.3.0}/psengine/identity/constants.py +0 -0
- {psengine-2.2.0 → psengine-2.3.0}/psengine/identity/errors.py +0 -0
- {psengine-2.2.0 → psengine-2.3.0}/psengine/identity/identity.py +0 -0
- {psengine-2.2.0 → psengine-2.3.0}/psengine/identity/identity_mgr.py +0 -0
- {psengine-2.2.0 → psengine-2.3.0}/psengine/identity/models/__init__.py +0 -0
- {psengine-2.2.0 → psengine-2.3.0}/psengine/identity/models/common_models.py +0 -0
- {psengine-2.2.0 → psengine-2.3.0}/psengine/identity/models/detections.py +0 -0
- {psengine-2.2.0 → psengine-2.3.0}/psengine/identity/models/incident_report.py +0 -0
- {psengine-2.2.0 → psengine-2.3.0}/psengine/identity/models/lookup.py +0 -0
- {psengine-2.2.0 → psengine-2.3.0}/psengine/logger/__init__.py +0 -0
- {psengine-2.2.0 → psengine-2.3.0}/psengine/logger/constants.py +0 -0
- {psengine-2.2.0 → psengine-2.3.0}/psengine/logger/errors.py +0 -0
- {psengine-2.2.0 → psengine-2.3.0}/psengine/logger/rf_logger.py +0 -0
- {psengine-2.2.0 → psengine-2.3.0}/psengine/markdown/__init__.py +0 -0
- {psengine-2.2.0 → psengine-2.3.0}/psengine/markdown/markdown.py +0 -0
- {psengine-2.2.0 → psengine-2.3.0}/psengine/markdown/models.py +0 -0
- {psengine-2.2.0 → psengine-2.3.0}/psengine/playbook_alerts/__init__.py +0 -0
- {psengine-2.2.0 → psengine-2.3.0}/psengine/playbook_alerts/constants.py +0 -0
- {psengine-2.2.0 → psengine-2.3.0}/psengine/playbook_alerts/errors.py +0 -0
- {psengine-2.2.0 → psengine-2.3.0}/psengine/playbook_alerts/helpers.py +0 -0
- {psengine-2.2.0 → psengine-2.3.0}/psengine/playbook_alerts/mappings.py +0 -0
- {psengine-2.2.0 → psengine-2.3.0}/psengine/playbook_alerts/markdown/__init__.py +0 -0
- {psengine-2.2.0 → psengine-2.3.0}/psengine/playbook_alerts/markdown/markdown.py +0 -0
- {psengine-2.2.0 → psengine-2.3.0}/psengine/playbook_alerts/markdown/markdown_code_repo.py +0 -0
- {psengine-2.2.0 → psengine-2.3.0}/psengine/playbook_alerts/markdown/markdown_cyber_vulnerability.py +0 -0
- {psengine-2.2.0 → psengine-2.3.0}/psengine/playbook_alerts/markdown/markdown_domain_abuse.py +0 -0
- {psengine-2.2.0 → psengine-2.3.0}/psengine/playbook_alerts/markdown/markdown_geopolitics_facility.py +0 -0
- {psengine-2.2.0 → psengine-2.3.0}/psengine/playbook_alerts/markdown/markdown_malware_report.py +0 -0
- {psengine-2.2.0 → psengine-2.3.0}/psengine/playbook_alerts/markdown/markdown_third_party_risk.py +0 -0
- {psengine-2.2.0 → psengine-2.3.0}/psengine/playbook_alerts/models/__init__.py +0 -0
- {psengine-2.2.0 → psengine-2.3.0}/psengine/playbook_alerts/models/common_models.py +0 -0
- {psengine-2.2.0 → psengine-2.3.0}/psengine/playbook_alerts/models/panel_log.py +0 -0
- {psengine-2.2.0 → psengine-2.3.0}/psengine/playbook_alerts/models/panel_status.py +0 -0
- {psengine-2.2.0 → psengine-2.3.0}/psengine/playbook_alerts/models/pba_code_repo_leak.py +0 -0
- {psengine-2.2.0 → psengine-2.3.0}/psengine/playbook_alerts/models/pba_cyber_vulnerability.py +0 -0
- {psengine-2.2.0 → psengine-2.3.0}/psengine/playbook_alerts/models/pba_domain_abuse.py +0 -0
- {psengine-2.2.0 → psengine-2.3.0}/psengine/playbook_alerts/models/pba_geopolitics_facility.py +0 -0
- {psengine-2.2.0 → psengine-2.3.0}/psengine/playbook_alerts/models/pba_identity_exposures.py +0 -0
- {psengine-2.2.0 → psengine-2.3.0}/psengine/playbook_alerts/models/pba_malware_report.py +0 -0
- {psengine-2.2.0 → psengine-2.3.0}/psengine/playbook_alerts/models/pba_third_party_risk.py +0 -0
- {psengine-2.2.0 → psengine-2.3.0}/psengine/playbook_alerts/models/search_endpoint.py +0 -0
- {psengine-2.2.0 → psengine-2.3.0}/psengine/playbook_alerts/pa_category.py +0 -0
- {psengine-2.2.0 → psengine-2.3.0}/psengine/playbook_alerts/playbook_alert_mgr.py +0 -0
- {psengine-2.2.0 → psengine-2.3.0}/psengine/playbook_alerts/playbook_alerts.py +0 -0
- {psengine-2.2.0 → psengine-2.3.0}/psengine/py.typed +0 -0
- {psengine-2.2.0 → psengine-2.3.0}/psengine/risklists/__init__.py +0 -0
- {psengine-2.2.0 → psengine-2.3.0}/psengine/risklists/constants.py +0 -0
- {psengine-2.2.0 → psengine-2.3.0}/psengine/risklists/errors.py +0 -0
- {psengine-2.2.0 → psengine-2.3.0}/psengine/risklists/models.py +0 -0
- {psengine-2.2.0 → psengine-2.3.0}/psengine/risklists/risklist_mgr.py +0 -0
- {psengine-2.2.0 → psengine-2.3.0}/psengine/stix2/__init__.py +0 -0
- {psengine-2.2.0 → psengine-2.3.0}/psengine/stix2/base_stix_entity.py +0 -0
- {psengine-2.2.0 → psengine-2.3.0}/psengine/stix2/complex_entity.py +0 -0
- {psengine-2.2.0 → psengine-2.3.0}/psengine/stix2/constants.py +0 -0
- {psengine-2.2.0 → psengine-2.3.0}/psengine/stix2/enriched_indicator.py +0 -0
- {psengine-2.2.0 → psengine-2.3.0}/psengine/stix2/errors.py +0 -0
- {psengine-2.2.0 → psengine-2.3.0}/psengine/stix2/helpers.py +0 -0
- {psengine-2.2.0 → psengine-2.3.0}/psengine/stix2/rf_bundle.py +0 -0
- {psengine-2.2.0 → psengine-2.3.0}/psengine/stix2/simple_entity.py +0 -0
- {psengine-2.2.0 → psengine-2.3.0}/psengine/stix2/util.py +0 -0
- {psengine-2.2.0 → psengine-2.3.0}/psengine.egg-info/dependency_links.txt +0 -0
- {psengine-2.2.0 → psengine-2.3.0}/psengine.egg-info/top_level.txt +0 -0
- {psengine-2.2.0 → psengine-2.3.0}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: psengine
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.3.0
|
|
4
4
|
Summary: psengine is a simple, yet elegant, library for rapid development of integrations with Recorded Future.
|
|
5
5
|
Author-email: Moise Medici <moise.medici@recordedfuture.com>, Patrick Kinsella <patrick.kinsella@recordedfuture.com>, Ernest Bartosevic <ernest.bartosevic@recordedfuture.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -50,6 +50,8 @@ Requires-Dist: griffe-typingdoc~=0.2.8; extra == "docs"
|
|
|
50
50
|
Requires-Dist: mkdocs-codeinclude-plugin~=0.2.1; extra == "docs"
|
|
51
51
|
Requires-Dist: markdown-include~=0.8.1; extra == "docs"
|
|
52
52
|
Requires-Dist: mkdocs-exclude~=1.0.2; extra == "docs"
|
|
53
|
+
Requires-Dist: rich; extra == "docs"
|
|
54
|
+
Requires-Dist: logging-tree; extra == "docs"
|
|
53
55
|
Dynamic: license-file
|
|
54
56
|
|
|
55
57
|
**Documentation**: <https://recordedfuture-professionalservices.github.io/psengine/>
|
|
@@ -83,21 +85,24 @@ PSEngine is ready for the demands of building robust and reliable integrations.
|
|
|
83
85
|
|
|
84
86
|
It can easily interact with the following Recorded Future datasets:
|
|
85
87
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
88
|
+
- Analyst Notes
|
|
89
|
+
- Collective Insights
|
|
90
|
+
- Classic & Playbook Alerts
|
|
91
|
+
- Detection Rules
|
|
92
|
+
- Fusion File management
|
|
90
93
|
- Identity Exposures management
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
+
- List management
|
|
95
|
+
- Malware Sandbox reports download
|
|
96
|
+
- On demand IOC enrichment
|
|
97
|
+
- Risklists
|
|
98
|
+
- Risk History
|
|
94
99
|
- STIX conversion
|
|
95
100
|
|
|
96
101
|
|
|
97
102
|
And facilitate the development with features like:
|
|
98
103
|
|
|
99
|
-
|
|
100
|
-
|
|
104
|
+
- Built-in logging
|
|
105
|
+
- Easy configuration management
|
|
101
106
|
- Markdown creation from certain data types
|
|
102
|
-
|
|
107
|
+
- Proxy support
|
|
103
108
|
|
|
@@ -29,21 +29,24 @@ PSEngine is ready for the demands of building robust and reliable integrations.
|
|
|
29
29
|
|
|
30
30
|
It can easily interact with the following Recorded Future datasets:
|
|
31
31
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
32
|
+
- Analyst Notes
|
|
33
|
+
- Collective Insights
|
|
34
|
+
- Classic & Playbook Alerts
|
|
35
|
+
- Detection Rules
|
|
36
|
+
- Fusion File management
|
|
36
37
|
- Identity Exposures management
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
38
|
+
- List management
|
|
39
|
+
- Malware Sandbox reports download
|
|
40
|
+
- On demand IOC enrichment
|
|
41
|
+
- Risklists
|
|
42
|
+
- Risk History
|
|
40
43
|
- STIX conversion
|
|
41
44
|
|
|
42
45
|
|
|
43
46
|
And facilitate the development with features like:
|
|
44
47
|
|
|
45
|
-
|
|
46
|
-
|
|
48
|
+
- Built-in logging
|
|
49
|
+
- Easy configuration management
|
|
47
50
|
- Markdown creation from certain data types
|
|
48
|
-
|
|
51
|
+
- Proxy support
|
|
49
52
|
|
|
@@ -19,24 +19,24 @@ class AnalystNoteError(RecordedFutureError):
|
|
|
19
19
|
|
|
20
20
|
|
|
21
21
|
class AnalystNoteLookupError(RecordedFutureError):
|
|
22
|
-
"""
|
|
22
|
+
"""Raised when an analyst note cannot be retrieved."""
|
|
23
23
|
|
|
24
24
|
|
|
25
25
|
class AnalystNoteSearchError(RecordedFutureError):
|
|
26
|
-
"""
|
|
26
|
+
"""Raised when an analyst note cannot be searched."""
|
|
27
27
|
|
|
28
28
|
|
|
29
29
|
class AnalystNoteAttachmentError(RecordedFutureError):
|
|
30
|
-
"""
|
|
30
|
+
"""Raised when an analyst note attachment cannot be retrieved."""
|
|
31
31
|
|
|
32
32
|
|
|
33
33
|
class AnalystNoteDeleteError(RecordedFutureError):
|
|
34
|
-
"""
|
|
34
|
+
"""Raised when an analyst note cannot be deleted."""
|
|
35
35
|
|
|
36
36
|
|
|
37
37
|
class AnalystNotePreviewError(RecordedFutureError):
|
|
38
|
-
"""
|
|
38
|
+
"""Raised when an analyst note cannot be previewed."""
|
|
39
39
|
|
|
40
40
|
|
|
41
41
|
class AnalystNotePublishError(RecordedFutureError):
|
|
42
|
-
"""
|
|
42
|
+
"""Raised when an analyst note cannot be published."""
|
|
@@ -97,7 +97,7 @@ class BaseHTTPClient:
|
|
|
97
97
|
self,
|
|
98
98
|
method: Annotated[str, Doc('An HTTP method.')],
|
|
99
99
|
url: Annotated[str, Doc('A URL to make the request to.')],
|
|
100
|
-
data: Annotated[Union[dict, list[dict], None], Doc('A request body.')] = None,
|
|
100
|
+
data: Annotated[Union[dict, list[dict], bytes, None], Doc('A request body.')] = None,
|
|
101
101
|
*,
|
|
102
102
|
params: Annotated[Union[dict, None], Doc('HTTP query parameters.')] = None,
|
|
103
103
|
headers: Annotated[
|
|
@@ -124,7 +124,8 @@ class BaseHTTPClient:
|
|
|
124
124
|
if 'User-Agent' not in headers:
|
|
125
125
|
headers['User-Agent'] = self._get_user_agent_header()
|
|
126
126
|
|
|
127
|
-
|
|
127
|
+
if not isinstance(data, bytes):
|
|
128
|
+
data = json.dumps(data) if data is not None else None
|
|
128
129
|
|
|
129
130
|
try:
|
|
130
131
|
response = method_func(
|
|
@@ -45,6 +45,9 @@ EP_CLASSIC_ALERTS_ID = EP_CLASSIC_ALERTS_V3 + '/alerts/{}'
|
|
|
45
45
|
EP_RISKLIST = f'{BASE_URL}/{API_VERSION}/' + '{}/risklist'
|
|
46
46
|
EP_FUSION_FILES = CONNECT_API_BASE_URL + '/fusion/files'
|
|
47
47
|
|
|
48
|
+
EP_FUSION_FILES_V3 = f'{BASE_URL}/fusion/v3/files/'
|
|
49
|
+
EP_FUSION_DIR_V3 = f'{BASE_URL}/fusion/v3/files/directory/'
|
|
50
|
+
|
|
48
51
|
###############################################################################
|
|
49
52
|
# Playbook Alert Endpoints
|
|
50
53
|
###############################################################################
|
|
@@ -111,3 +114,15 @@ EP_IDENTITY_IP_LOOKUP = EP_IDENTITY + 'ip/lookup'
|
|
|
111
114
|
EP_IDENTITY_CREDENTIALS_SEARCH = EP_IDENTITY + 'credentials/search'
|
|
112
115
|
EP_IDENTITY_CREDENTIALS_LOOKUP = EP_IDENTITY + 'credentials/lookup'
|
|
113
116
|
EP_IDENTITY_DUMP_SEARCH = EP_IDENTITY + 'metadata/dump/search'
|
|
117
|
+
|
|
118
|
+
###############################################################################
|
|
119
|
+
# Malware Intelligence API Endpoints
|
|
120
|
+
###############################################################################
|
|
121
|
+
EP_MALWARE_INTELLIGENCE = BASE_URL + '/malware-intelligence/v1/'
|
|
122
|
+
EP_MALWARE_INTEL_REPORTS = EP_MALWARE_INTELLIGENCE + 'reports'
|
|
123
|
+
|
|
124
|
+
###############################################################################
|
|
125
|
+
# Risk History API Endpoints
|
|
126
|
+
###############################################################################
|
|
127
|
+
EP_RISK_HISTORY_BASE = BASE_URL + '/risk'
|
|
128
|
+
EP_RISK_HISTORY = EP_RISK_HISTORY_BASE + '/history'
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
##################################### TERMS OF USE ###########################################
|
|
2
|
+
# The following code is provided for demonstration purpose only, and should not be used #
|
|
3
|
+
# without independent verification. Recorded Future makes no representations or warranties, #
|
|
4
|
+
# express, implied, statutory, or otherwise, regarding any aspect of this code or of the #
|
|
5
|
+
# information it may retrieve, and provides it both strictly “as-is” and without assuming #
|
|
6
|
+
# responsibility for any information it may retrieve. Recorded Future shall not be liable #
|
|
7
|
+
# for, and you assume all risk of using, the foregoing. By using this code, Customer #
|
|
8
|
+
# represents that it is solely responsible for having all necessary licenses, permissions, #
|
|
9
|
+
# rights, and/or consents to connect to third party APIs, and that it is solely responsible #
|
|
10
|
+
# for having all necessary licenses, permissions, rights, and/or consents to any data #
|
|
11
|
+
# accessed from any third party API. #
|
|
12
|
+
##############################################################################################
|
|
13
|
+
|
|
14
|
+
from .errors import (
|
|
15
|
+
FusionDeleteFileError,
|
|
16
|
+
FusionGetFileError,
|
|
17
|
+
FusionHeadFileError,
|
|
18
|
+
FusionListDirError,
|
|
19
|
+
FusionPostFileError,
|
|
20
|
+
)
|
|
21
|
+
from .fusion_mgr import FusionMgr
|
|
22
|
+
from .models import DirectoryListOut, FileDeleteOut, FileGetOut, FileHeadOut, FileInfoOut
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
##################################### TERMS OF USE ###########################################
|
|
2
|
+
# The following code is provided for demonstration purpose only, and should not be used #
|
|
3
|
+
# without independent verification. Recorded Future makes no representations or warranties, #
|
|
4
|
+
# express, implied, statutory, or otherwise, regarding any aspect of this code or of the #
|
|
5
|
+
# information it may retrieve, and provides it both strictly “as-is” and without assuming #
|
|
6
|
+
# responsibility for any information it may retrieve. Recorded Future shall not be liable #
|
|
7
|
+
# for, and you assume all risk of using, the foregoing. By using this code, Customer #
|
|
8
|
+
# represents that it is solely responsible for having all necessary licenses, permissions, #
|
|
9
|
+
# rights, and/or consents to connect to third party APIs, and that it is solely responsible #
|
|
10
|
+
# for having all necessary licenses, permissions, rights, and/or consents to any data #
|
|
11
|
+
# accessed from any third party API. #
|
|
12
|
+
##############################################################################################
|
|
13
|
+
|
|
14
|
+
from ..errors import RecordedFutureError
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class FusionGetFileError(RecordedFutureError):
|
|
18
|
+
"""Error raise when the get file operation fails."""
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class FusionHeadFileError(RecordedFutureError):
|
|
22
|
+
"""Error raise when the head file operation fails."""
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class FusionPostFileError(RecordedFutureError):
|
|
26
|
+
"""Error raise when the post file operation fails."""
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class FusionDeleteFileError(RecordedFutureError):
|
|
30
|
+
"""Error raise when the delete file operation fails."""
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class FusionListDirError(RecordedFutureError):
|
|
34
|
+
"""Error raise when the list dir operation fails."""
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
##################################### TERMS OF USE ###########################################
|
|
2
|
+
# The following code is provided for demonstration purpose only, and should not be used #
|
|
3
|
+
# without independent verification. Recorded Future makes no representations or warranties, #
|
|
4
|
+
# express, implied, statutory, or otherwise, regarding any aspect of this code or of the #
|
|
5
|
+
# information it may retrieve, and provides it both strictly “as-is” and without assuming #
|
|
6
|
+
# responsibility for any information it may retrieve. Recorded Future shall not be liable #
|
|
7
|
+
# for, and you assume all risk of using, the foregoing. By using this code, Customer #
|
|
8
|
+
# represents that it is solely responsible for having all necessary licenses, permissions, #
|
|
9
|
+
# rights, and/or consents to connect to third party APIs, and that it is solely responsible #
|
|
10
|
+
# for having all necessary licenses, permissions, rights, and/or consents to any data #
|
|
11
|
+
# accessed from any third party API. #
|
|
12
|
+
##############################################################################################
|
|
13
|
+
|
|
14
|
+
import logging
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
from typing import Annotated, Union
|
|
17
|
+
from urllib.parse import quote
|
|
18
|
+
|
|
19
|
+
from pydantic import validate_call
|
|
20
|
+
from typing_extensions import Doc
|
|
21
|
+
|
|
22
|
+
from ..endpoints import EP_FUSION_DIR_V3, EP_FUSION_FILES_V3
|
|
23
|
+
from ..helpers import debug_call
|
|
24
|
+
from ..helpers.helpers import connection_exceptions
|
|
25
|
+
from ..rf_client import RFClient
|
|
26
|
+
from .errors import (
|
|
27
|
+
FusionDeleteFileError,
|
|
28
|
+
FusionGetFileError,
|
|
29
|
+
FusionHeadFileError,
|
|
30
|
+
FusionListDirError,
|
|
31
|
+
FusionPostFileError,
|
|
32
|
+
)
|
|
33
|
+
from .models import DirectoryListOut, FileDeleteOut, FileGetOut, FileHeadOut, FileInfoOut
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class FusionMgr:
|
|
37
|
+
"""Manages requests for Recorded Future Fusion files."""
|
|
38
|
+
|
|
39
|
+
def __init__(self, rf_token: str = None):
|
|
40
|
+
"""Initializes the `FusionMgr` object.
|
|
41
|
+
|
|
42
|
+
Args:
|
|
43
|
+
rf_token (str, optional): Recorded Future API token.
|
|
44
|
+
"""
|
|
45
|
+
self.log = logging.getLogger(__name__)
|
|
46
|
+
self.rf_client = RFClient(api_token=rf_token) if rf_token else RFClient()
|
|
47
|
+
|
|
48
|
+
@debug_call
|
|
49
|
+
@validate_call
|
|
50
|
+
@connection_exceptions(ignore_status_code=[], exception_to_raise=FusionGetFileError)
|
|
51
|
+
def get_files(
|
|
52
|
+
self, file_paths: Annotated[Union[str, list[str]], Doc('One or more paths to fetch')]
|
|
53
|
+
) -> Annotated[list[FileGetOut], Doc('A FusionFile object with name and content of the file')]:
|
|
54
|
+
"""Get one or more files.
|
|
55
|
+
|
|
56
|
+
Endpoint:
|
|
57
|
+
`/fusion/v3/files/`
|
|
58
|
+
|
|
59
|
+
Raises:
|
|
60
|
+
ValidationError: If any supplied parameter is of incorrect type.
|
|
61
|
+
FusionGetFileError: If API error occurs.
|
|
62
|
+
"""
|
|
63
|
+
returned_files = []
|
|
64
|
+
file_paths = file_paths if isinstance(file_paths, list) else [file_paths]
|
|
65
|
+
file_paths = [f'/{p}' if not p.startswith('/') else p for p in file_paths]
|
|
66
|
+
|
|
67
|
+
for file in file_paths:
|
|
68
|
+
data = self._get_file(file)
|
|
69
|
+
if data:
|
|
70
|
+
returned_files.append(
|
|
71
|
+
FileGetOut.model_validate(
|
|
72
|
+
{'path': file, 'content': data.content, 'exists': True}
|
|
73
|
+
)
|
|
74
|
+
)
|
|
75
|
+
else:
|
|
76
|
+
returned_files.append(
|
|
77
|
+
FileGetOut.model_validate({'path': file, 'content': '', 'exists': False})
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
return returned_files
|
|
81
|
+
|
|
82
|
+
@debug_call
|
|
83
|
+
@validate_call
|
|
84
|
+
@connection_exceptions(ignore_status_code=[], exception_to_raise=FusionPostFileError)
|
|
85
|
+
def post_file(
|
|
86
|
+
self,
|
|
87
|
+
file_path: Annotated[Path, Doc('Path of the local file')],
|
|
88
|
+
fusion_path: Annotated[str, Doc('Path of the fusion file')],
|
|
89
|
+
) -> Annotated[list[FileInfoOut], Doc('Info of the file that have been posted')]:
|
|
90
|
+
"""Post a file.
|
|
91
|
+
|
|
92
|
+
Endpoint:
|
|
93
|
+
`/fusion/v3/files/`
|
|
94
|
+
|
|
95
|
+
Raises:
|
|
96
|
+
ValidationError: If any supplied parameter is of incorrect type.
|
|
97
|
+
FusionPostFileError: If API error occurs or the input file cannot be read.
|
|
98
|
+
"""
|
|
99
|
+
if not file_path.exists():
|
|
100
|
+
raise FusionPostFileError(f'The file {file_path} does not exist')
|
|
101
|
+
|
|
102
|
+
data = file_path.read_bytes()
|
|
103
|
+
|
|
104
|
+
headers = 'application/octet-stream'
|
|
105
|
+
returned_data = self.rf_client.request(
|
|
106
|
+
'post',
|
|
107
|
+
EP_FUSION_FILES_V3 + quote(fusion_path, safe='.'),
|
|
108
|
+
data=data,
|
|
109
|
+
content_type_header=headers,
|
|
110
|
+
).json()
|
|
111
|
+
|
|
112
|
+
return FileInfoOut.model_validate(returned_data)
|
|
113
|
+
|
|
114
|
+
@debug_call
|
|
115
|
+
@validate_call
|
|
116
|
+
def delete_files(
|
|
117
|
+
self, file_paths: Annotated[Union[str, list[str]], Doc('One or more paths to delete')]
|
|
118
|
+
) -> Annotated[list[FileDeleteOut], Doc('A list of deleted files.')]:
|
|
119
|
+
"""Delete one or more files.
|
|
120
|
+
|
|
121
|
+
Endpoint:
|
|
122
|
+
`/fusion/v3/files/`
|
|
123
|
+
|
|
124
|
+
Raises:
|
|
125
|
+
ValidationError: If any supplied parameter is of incorrect type.
|
|
126
|
+
FusionDeleteFileError: If API error occurs.
|
|
127
|
+
"""
|
|
128
|
+
returned_files = []
|
|
129
|
+
file_paths = file_paths if isinstance(file_paths, list) else [file_paths]
|
|
130
|
+
file_paths = [f'/{p}' if not p.startswith('/') else p for p in file_paths]
|
|
131
|
+
|
|
132
|
+
for file in file_paths:
|
|
133
|
+
data = self._delete_file(file)
|
|
134
|
+
returned_files.append(
|
|
135
|
+
FileDeleteOut.model_validate({'path': file, 'deleted': bool(data)})
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
return returned_files
|
|
139
|
+
|
|
140
|
+
@debug_call
|
|
141
|
+
@validate_call
|
|
142
|
+
def head_files(
|
|
143
|
+
self, file_paths: Annotated[Union[str, list[str]], Doc('One or more paths to check')]
|
|
144
|
+
) -> Annotated[list[FileHeadOut], Doc('List of headers info for the requested files.')]:
|
|
145
|
+
"""Head of one or more files.
|
|
146
|
+
|
|
147
|
+
Endpoint:
|
|
148
|
+
`/fusion/v3/files/`
|
|
149
|
+
|
|
150
|
+
Raises:
|
|
151
|
+
ValidationError: If any supplied parameter is of incorrect type.
|
|
152
|
+
FusionHeadFileError: If API error occurs.
|
|
153
|
+
"""
|
|
154
|
+
returned_files = []
|
|
155
|
+
file_paths = file_paths if isinstance(file_paths, list) else [file_paths]
|
|
156
|
+
file_paths = [f'/{p}' if not p.startswith('/') else p for p in file_paths]
|
|
157
|
+
|
|
158
|
+
for file in file_paths:
|
|
159
|
+
data = self._head_file(file)
|
|
160
|
+
if data:
|
|
161
|
+
returned_files.append(
|
|
162
|
+
FileHeadOut.model_validate({'path': file, 'exists': True, **data.headers})
|
|
163
|
+
)
|
|
164
|
+
else:
|
|
165
|
+
returned_files.append(FileHeadOut.model_validate({'path': file, 'exists': False}))
|
|
166
|
+
|
|
167
|
+
return returned_files
|
|
168
|
+
|
|
169
|
+
@debug_call
|
|
170
|
+
@validate_call
|
|
171
|
+
@connection_exceptions(ignore_status_code=[], exception_to_raise=FusionListDirError)
|
|
172
|
+
def list_dir(
|
|
173
|
+
self, file_path: Annotated[str, Doc('Directory to list')]
|
|
174
|
+
) -> Annotated[DirectoryListOut, Doc('The tree structure.')]:
|
|
175
|
+
"""Get directory, subdirectory and file information of a path.
|
|
176
|
+
|
|
177
|
+
Endpoint:
|
|
178
|
+
`/fusion/v3/files/directory`
|
|
179
|
+
|
|
180
|
+
Raises:
|
|
181
|
+
ValidationError: If any supplied parameter is of incorrect type.
|
|
182
|
+
FusionListDirError: If API error occurs.
|
|
183
|
+
"""
|
|
184
|
+
data = self.rf_client.request('get', EP_FUSION_DIR_V3 + quote(file_path, safe='.')).json()
|
|
185
|
+
return DirectoryListOut.model_validate(data)
|
|
186
|
+
|
|
187
|
+
@connection_exceptions(ignore_status_code=[404], exception_to_raise=FusionHeadFileError)
|
|
188
|
+
def _head_file(self, file):
|
|
189
|
+
return self.rf_client.request('head', EP_FUSION_FILES_V3 + quote(file, safe='.'))
|
|
190
|
+
|
|
191
|
+
@connection_exceptions(ignore_status_code=[404], exception_to_raise=FusionDeleteFileError)
|
|
192
|
+
def _delete_file(self, file):
|
|
193
|
+
return self.rf_client.request('delete', EP_FUSION_FILES_V3 + quote(file, safe='.'))
|
|
194
|
+
|
|
195
|
+
@connection_exceptions(ignore_status_code=[404], exception_to_raise=FusionGetFileError)
|
|
196
|
+
def _get_file(self, file):
|
|
197
|
+
return self.rf_client.request('get', EP_FUSION_FILES_V3 + quote(file, safe='.'))
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
##################################### TERMS OF USE ###########################################
|
|
2
|
+
# The following code is provided for demonstration purpose only, and should not be used #
|
|
3
|
+
# without independent verification. Recorded Future makes no representations or warranties, #
|
|
4
|
+
# express, implied, statutory, or otherwise, regarding any aspect of this code or of the #
|
|
5
|
+
# information it may retrieve, and provides it both strictly “as-is” and without assuming #
|
|
6
|
+
# responsibility for any information it may retrieve. Recorded Future shall not be liable #
|
|
7
|
+
# for, and you assume all risk of using, the foregoing. By using this code, Customer #
|
|
8
|
+
# represents that it is solely responsible for having all necessary licenses, permissions, #
|
|
9
|
+
# rights, and/or consents to connect to third party APIs, and that it is solely responsible #
|
|
10
|
+
# for having all necessary licenses, permissions, rights, and/or consents to any data #
|
|
11
|
+
# accessed from any third party API. #
|
|
12
|
+
##############################################################################################
|
|
13
|
+
|
|
14
|
+
from datetime import datetime
|
|
15
|
+
from typing import Optional
|
|
16
|
+
|
|
17
|
+
from pydantic import Field
|
|
18
|
+
|
|
19
|
+
from ..common_models import RFBaseModel
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class FileInfoOut(RFBaseModel):
|
|
23
|
+
type_: str = Field(alias='type')
|
|
24
|
+
name: str
|
|
25
|
+
path: str
|
|
26
|
+
format: Optional[str] = None
|
|
27
|
+
hash: Optional[str] = None
|
|
28
|
+
created: Optional[datetime] = None
|
|
29
|
+
size: Optional[int] = None
|
|
30
|
+
flow: Optional[str] = None
|
|
31
|
+
owner: Optional[str] = None
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class DirectoryListOut(RFBaseModel):
|
|
35
|
+
name: str
|
|
36
|
+
path: str
|
|
37
|
+
files: list[FileInfoOut]
|
|
38
|
+
type_: str = Field(alias='type')
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class FileGetOut(RFBaseModel):
|
|
42
|
+
path: str
|
|
43
|
+
content: bytes
|
|
44
|
+
exists: bool
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class FileDeleteOut(RFBaseModel):
|
|
48
|
+
path: str
|
|
49
|
+
deleted: bool
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class FileHeadOut(RFBaseModel):
|
|
53
|
+
path: str
|
|
54
|
+
exists: bool
|
|
55
|
+
content_disposition: Optional[str] = Field(alias='content-disposition', default=None)
|
|
56
|
+
content_length: Optional[int] = Field(alias='Content-Length', default=None)
|
|
57
|
+
content_type: Optional[str] = Field(alias='content-type', default=None)
|
|
58
|
+
etag: Optional[str] = None
|
|
59
|
+
last_modified: Optional[str] = Field(alias='last-modified', default=None)
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
##################################### TERMS OF USE ###########################################
|
|
2
|
+
# The following code is provided for demonstration purpose only, and should not be used #
|
|
3
|
+
# without independent verification. Recorded Future makes no representations or warranties, #
|
|
4
|
+
# express, implied, statutory, or otherwise, regarding any aspect of this code or of the #
|
|
5
|
+
# information it may retrieve, and provides it both strictly “as-is” and without assuming #
|
|
6
|
+
# responsibility for any information it may retrieve. Recorded Future shall not be liable #
|
|
7
|
+
# for, and you assume all risk of using, the foregoing. By using this code, Customer #
|
|
8
|
+
# represents that it is solely responsible for having all necessary licenses, permissions, #
|
|
9
|
+
# rights, and/or consents to connect to third party APIs, and that it is solely responsible #
|
|
10
|
+
# for having all necessary licenses, permissions, rights, and/or consents to any data #
|
|
11
|
+
# accessed from any third party API. #
|
|
12
|
+
##############################################################################################
|
|
13
|
+
|
|
14
|
+
from .errors import MalwareIntelReportError
|
|
15
|
+
from .malware_intel import SandboxReport
|
|
16
|
+
from .malware_intel_mgr import MalwareIntelMgr
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
##################################### TERMS OF USE ###########################################
|
|
2
|
+
# The following code is provided for demonstration purpose only, and should not be used #
|
|
3
|
+
# without independent verification. Recorded Future makes no representations or warranties, #
|
|
4
|
+
# express, implied, statutory, or otherwise, regarding any aspect of this code or of the #
|
|
5
|
+
# information it may retrieve, and provides it both strictly “as-is” and without assuming #
|
|
6
|
+
# responsibility for any information it may retrieve. Recorded Future shall not be liable #
|
|
7
|
+
# for, and you assume all risk of using, the foregoing. By using this code, Customer #
|
|
8
|
+
# represents that it is solely responsible for having all necessary licenses, permissions, #
|
|
9
|
+
# rights, and/or consents to connect to third party APIs, and that it is solely responsible #
|
|
10
|
+
# for having all necessary licenses, permissions, rights, and/or consents to any data #
|
|
11
|
+
# accessed from any third party API. #
|
|
12
|
+
##############################################################################################
|
|
13
|
+
|
|
14
|
+
from ..errors import RecordedFutureError
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class MalwareIntelReportError(RecordedFutureError):
|
|
18
|
+
"""Error raise when the reports operation fails."""
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
##################################### TERMS OF USE ###########################################
|
|
2
|
+
# The following code is provided for demonstration purpose only, and should not be used #
|
|
3
|
+
# without independent verification. Recorded Future makes no representations or warranties, #
|
|
4
|
+
# express, implied, statutory, or otherwise, regarding any aspect of this code or of the #
|
|
5
|
+
# information it may retrieve, and provides it both strictly “as-is” and without assuming #
|
|
6
|
+
# responsibility for any information it may retrieve. Recorded Future shall not be liable #
|
|
7
|
+
# for, and you assume all risk of using, the foregoing. By using this code, Customer #
|
|
8
|
+
# represents that it is solely responsible for having all necessary licenses, permissions, #
|
|
9
|
+
# rights, and/or consents to connect to third party APIs, and that it is solely responsible #
|
|
10
|
+
# for having all necessary licenses, permissions, rights, and/or consents to any data #
|
|
11
|
+
# accessed from any third party API. #
|
|
12
|
+
##############################################################################################
|
|
13
|
+
|
|
14
|
+
from functools import total_ordering
|
|
15
|
+
from typing import Annotated, Optional
|
|
16
|
+
|
|
17
|
+
from pydantic import AfterValidator, BeforeValidator, Field
|
|
18
|
+
|
|
19
|
+
from ..common_models import RFBaseModel
|
|
20
|
+
from ..helpers.helpers import Validators
|
|
21
|
+
from .models import DynamicInfo, Metadata, PEInfo, SampleInfo, StaticInfo
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@total_ordering
|
|
25
|
+
class SandboxReport(RFBaseModel):
|
|
26
|
+
"""Validate data received from the `/v1/reports` endpoint.
|
|
27
|
+
|
|
28
|
+
This class supports hashing, equality comparison, string representation, and total ordering
|
|
29
|
+
of `SandboxReport` instances.
|
|
30
|
+
|
|
31
|
+
Hashing:
|
|
32
|
+
Returns a hash value based on the report `file`, `task`, `sample.completed`.
|
|
33
|
+
|
|
34
|
+
Equality:
|
|
35
|
+
Checks equality between two `SandboxReport` instances by `id_` when both are present;
|
|
36
|
+
otherwise falls back to comparing (`file`, `task`, `sample.completed`).
|
|
37
|
+
|
|
38
|
+
Greater-than Comparison:
|
|
39
|
+
Defines a greater-than comparison between two `SandboxReport` instances based on the sample
|
|
40
|
+
completed time, then created time, and finally the `file` name.
|
|
41
|
+
|
|
42
|
+
String Representation:
|
|
43
|
+
Returns a string representation of the `SandboxReport` instance including the `file`,
|
|
44
|
+
`score`, `task`, and created timestamp.
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
```python
|
|
48
|
+
>>> print(report)
|
|
49
|
+
Sandbox Report of: sample.exe, Score: 85, Task: behavioural1, Submitted: 2024-05-21 10:42:30
|
|
50
|
+
```
|
|
51
|
+
Ordering:
|
|
52
|
+
The ordering of `SandboxReport` instances is determined primarily by the sample completed
|
|
53
|
+
time. If two instances have the same completed time, the created time is used as a secondary
|
|
54
|
+
criterion, followed by the report `file` name.
|
|
55
|
+
"""
|
|
56
|
+
|
|
57
|
+
file: str
|
|
58
|
+
id_: Optional[str] = None
|
|
59
|
+
task: str
|
|
60
|
+
dynamic: DynamicInfo
|
|
61
|
+
metadata: Metadata
|
|
62
|
+
pe: Optional[PEInfo] = None
|
|
63
|
+
sample: SampleInfo
|
|
64
|
+
static: StaticInfo
|
|
65
|
+
|
|
66
|
+
def __hash__(self):
|
|
67
|
+
return hash((self.file, self.task, self.sample.completed))
|
|
68
|
+
|
|
69
|
+
def __eq__(self, other: 'SandboxReport'):
|
|
70
|
+
return (self.file, self.task, self.sample.completed) == (
|
|
71
|
+
other.file,
|
|
72
|
+
other.task,
|
|
73
|
+
other.sample.completed,
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
def __gt__(self, other: 'SandboxReport'):
|
|
77
|
+
if self.sample.completed and other.sample.completed:
|
|
78
|
+
return self.sample.completed > other.sample.completed
|
|
79
|
+
|
|
80
|
+
if self.sample.created and other.sample.created:
|
|
81
|
+
return self.sample.created > other.sample.created
|
|
82
|
+
|
|
83
|
+
return self.file > other.file
|
|
84
|
+
|
|
85
|
+
def __str__(self):
|
|
86
|
+
return 'Sandbox Report of: {}, Score: {}, Task: {}, Submitted: {}'.format(
|
|
87
|
+
self.file, self.sample.score or 'N/A', self.task, self.sample.created or 'N/A'
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def _split_time(value: str):
|
|
92
|
+
return value.split('T')[0]
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
class MalwareReportIn(RFBaseModel):
|
|
96
|
+
"""Validate data sent to the `/v1/reports` endpoint."""
|
|
97
|
+
|
|
98
|
+
query: str
|
|
99
|
+
sha256: str
|
|
100
|
+
start_date: Annotated[
|
|
101
|
+
str, BeforeValidator(Validators.convert_relative_time), AfterValidator(_split_time)
|
|
102
|
+
]
|
|
103
|
+
end_date: Annotated[
|
|
104
|
+
str, BeforeValidator(Validators.convert_relative_time), AfterValidator(_split_time)
|
|
105
|
+
]
|
|
106
|
+
my_enterprise: bool
|
|
107
|
+
limit: int = Field(ge=1, le=10)
|