psengine 2.2.0__tar.gz → 2.3.1__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.
Files changed (150) hide show
  1. {psengine-2.2.0 → psengine-2.3.1}/PKG-INFO +16 -11
  2. {psengine-2.2.0 → psengine-2.3.1}/README.md +13 -10
  3. {psengine-2.2.0 → psengine-2.3.1}/psengine/_version.py +1 -1
  4. {psengine-2.2.0 → psengine-2.3.1}/psengine/analyst_notes/errors.py +6 -6
  5. {psengine-2.2.0 → psengine-2.3.1}/psengine/base_http_client.py +3 -2
  6. {psengine-2.2.0 → psengine-2.3.1}/psengine/endpoints.py +15 -0
  7. {psengine-2.2.0 → psengine-2.3.1}/psengine/enrich/lookup.py +2 -0
  8. {psengine-2.2.0 → psengine-2.3.1}/psengine/enrich/models/lookup.py +44 -0
  9. psengine-2.3.1/psengine/fusion/__init__.py +22 -0
  10. psengine-2.3.1/psengine/fusion/errors.py +34 -0
  11. psengine-2.3.1/psengine/fusion/fusion_mgr.py +197 -0
  12. psengine-2.3.1/psengine/fusion/models.py +59 -0
  13. psengine-2.3.1/psengine/malware_intel/__init__.py +16 -0
  14. psengine-2.3.1/psengine/malware_intel/errors.py +18 -0
  15. psengine-2.3.1/psengine/malware_intel/malware_intel.py +107 -0
  16. psengine-2.3.1/psengine/malware_intel/malware_intel_mgr.py +81 -0
  17. psengine-2.3.1/psengine/malware_intel/models.py +270 -0
  18. {psengine-2.2.0 → psengine-2.3.1}/psengine/playbook_alerts/markdown/markdown_identity_exposure.py +5 -1
  19. {psengine-2.2.0 → psengine-2.3.1}/psengine/rf_client.py +7 -4
  20. psengine-2.3.1/psengine/risk_history/__init__.py +16 -0
  21. psengine-2.3.1/psengine/risk_history/errors.py +18 -0
  22. psengine-2.3.1/psengine/risk_history/models.py +68 -0
  23. psengine-2.3.1/psengine/risk_history/risk_history_mgr.py +61 -0
  24. {psengine-2.2.0 → psengine-2.3.1}/psengine.egg-info/PKG-INFO +16 -11
  25. {psengine-2.2.0 → psengine-2.3.1}/psengine.egg-info/SOURCES.txt +13 -0
  26. {psengine-2.2.0 → psengine-2.3.1}/psengine.egg-info/requires.txt +2 -0
  27. {psengine-2.2.0 → psengine-2.3.1}/pyproject.toml +5 -1
  28. {psengine-2.2.0 → psengine-2.3.1}/LICENSE +0 -0
  29. {psengine-2.2.0 → psengine-2.3.1}/psengine/__init__.py +0 -0
  30. {psengine-2.2.0 → psengine-2.3.1}/psengine/_sdk_id.py +0 -0
  31. {psengine-2.2.0 → psengine-2.3.1}/psengine/analyst_notes/__init__.py +0 -0
  32. {psengine-2.2.0 → psengine-2.3.1}/psengine/analyst_notes/constants.py +0 -0
  33. {psengine-2.2.0 → psengine-2.3.1}/psengine/analyst_notes/helpers.py +0 -0
  34. {psengine-2.2.0 → psengine-2.3.1}/psengine/analyst_notes/markdown.py +0 -0
  35. {psengine-2.2.0 → psengine-2.3.1}/psengine/analyst_notes/models.py +0 -0
  36. {psengine-2.2.0 → psengine-2.3.1}/psengine/analyst_notes/note.py +0 -0
  37. {psengine-2.2.0 → psengine-2.3.1}/psengine/analyst_notes/note_mgr.py +0 -0
  38. {psengine-2.2.0 → psengine-2.3.1}/psengine/classic_alerts/__init__.py +0 -0
  39. {psengine-2.2.0 → psengine-2.3.1}/psengine/classic_alerts/classic_alert.py +0 -0
  40. {psengine-2.2.0 → psengine-2.3.1}/psengine/classic_alerts/classic_alert_mgr.py +0 -0
  41. {psengine-2.2.0 → psengine-2.3.1}/psengine/classic_alerts/constants.py +0 -0
  42. {psengine-2.2.0 → psengine-2.3.1}/psengine/classic_alerts/errors.py +0 -0
  43. {psengine-2.2.0 → psengine-2.3.1}/psengine/classic_alerts/helpers.py +0 -0
  44. {psengine-2.2.0 → psengine-2.3.1}/psengine/classic_alerts/markdown/__init__.py +0 -0
  45. {psengine-2.2.0 → psengine-2.3.1}/psengine/classic_alerts/markdown/markdown.py +0 -0
  46. {psengine-2.2.0 → psengine-2.3.1}/psengine/classic_alerts/models.py +0 -0
  47. {psengine-2.2.0 → psengine-2.3.1}/psengine/collective_insights/__init__.py +0 -0
  48. {psengine-2.2.0 → psengine-2.3.1}/psengine/collective_insights/collective_insights.py +0 -0
  49. {psengine-2.2.0 → psengine-2.3.1}/psengine/collective_insights/constants.py +0 -0
  50. {psengine-2.2.0 → psengine-2.3.1}/psengine/collective_insights/errors.py +0 -0
  51. {psengine-2.2.0 → psengine-2.3.1}/psengine/collective_insights/insight.py +0 -0
  52. {psengine-2.2.0 → psengine-2.3.1}/psengine/collective_insights/models.py +0 -0
  53. {psengine-2.2.0 → psengine-2.3.1}/psengine/common_models.py +0 -0
  54. {psengine-2.2.0 → psengine-2.3.1}/psengine/config/__init__.py +0 -0
  55. {psengine-2.2.0 → psengine-2.3.1}/psengine/config/config.py +0 -0
  56. {psengine-2.2.0 → psengine-2.3.1}/psengine/config/errors.py +0 -0
  57. {psengine-2.2.0 → psengine-2.3.1}/psengine/constants.py +0 -0
  58. {psengine-2.2.0 → psengine-2.3.1}/psengine/detection/__init__.py +0 -0
  59. {psengine-2.2.0 → psengine-2.3.1}/psengine/detection/detection_mgr.py +0 -0
  60. {psengine-2.2.0 → psengine-2.3.1}/psengine/detection/detection_rule.py +0 -0
  61. {psengine-2.2.0 → psengine-2.3.1}/psengine/detection/errors.py +0 -0
  62. {psengine-2.2.0 → psengine-2.3.1}/psengine/detection/helpers.py +0 -0
  63. {psengine-2.2.0 → psengine-2.3.1}/psengine/detection/models.py +0 -0
  64. {psengine-2.2.0 → psengine-2.3.1}/psengine/enrich/__init__.py +0 -0
  65. {psengine-2.2.0 → psengine-2.3.1}/psengine/enrich/constants.py +0 -0
  66. {psengine-2.2.0 → psengine-2.3.1}/psengine/enrich/errors.py +0 -0
  67. {psengine-2.2.0 → psengine-2.3.1}/psengine/enrich/lookup_mgr.py +0 -0
  68. {psengine-2.2.0 → psengine-2.3.1}/psengine/enrich/models/__init__.py +0 -0
  69. {psengine-2.2.0 → psengine-2.3.1}/psengine/enrich/models/base_enriched_entity.py +0 -0
  70. {psengine-2.2.0 → psengine-2.3.1}/psengine/enrich/models/soar.py +0 -0
  71. {psengine-2.2.0 → psengine-2.3.1}/psengine/enrich/soar.py +0 -0
  72. {psengine-2.2.0 → psengine-2.3.1}/psengine/enrich/soar_mgr.py +0 -0
  73. {psengine-2.2.0 → psengine-2.3.1}/psengine/entity_lists/__init__.py +0 -0
  74. {psengine-2.2.0 → psengine-2.3.1}/psengine/entity_lists/constants.py +0 -0
  75. {psengine-2.2.0 → psengine-2.3.1}/psengine/entity_lists/entity_list.py +0 -0
  76. {psengine-2.2.0 → psengine-2.3.1}/psengine/entity_lists/entity_list_mgr.py +0 -0
  77. {psengine-2.2.0 → psengine-2.3.1}/psengine/entity_lists/errors.py +0 -0
  78. {psengine-2.2.0 → psengine-2.3.1}/psengine/entity_lists/models.py +0 -0
  79. {psengine-2.2.0 → psengine-2.3.1}/psengine/entity_match/__init__.py +0 -0
  80. {psengine-2.2.0 → psengine-2.3.1}/psengine/entity_match/entity_match.py +0 -0
  81. {psengine-2.2.0 → psengine-2.3.1}/psengine/entity_match/entity_match_mgr.py +0 -0
  82. {psengine-2.2.0 → psengine-2.3.1}/psengine/entity_match/errors.py +0 -0
  83. {psengine-2.2.0 → psengine-2.3.1}/psengine/entity_match/models.py +0 -0
  84. {psengine-2.2.0 → psengine-2.3.1}/psengine/errors.py +0 -0
  85. {psengine-2.2.0 → psengine-2.3.1}/psengine/helpers/__init__.py +0 -0
  86. {psengine-2.2.0 → psengine-2.3.1}/psengine/helpers/helpers.py +0 -0
  87. {psengine-2.2.0 → psengine-2.3.1}/psengine/identity/__init__.py +0 -0
  88. {psengine-2.2.0 → psengine-2.3.1}/psengine/identity/constants.py +0 -0
  89. {psengine-2.2.0 → psengine-2.3.1}/psengine/identity/errors.py +0 -0
  90. {psengine-2.2.0 → psengine-2.3.1}/psengine/identity/identity.py +0 -0
  91. {psengine-2.2.0 → psengine-2.3.1}/psengine/identity/identity_mgr.py +0 -0
  92. {psengine-2.2.0 → psengine-2.3.1}/psengine/identity/models/__init__.py +0 -0
  93. {psengine-2.2.0 → psengine-2.3.1}/psengine/identity/models/common_models.py +0 -0
  94. {psengine-2.2.0 → psengine-2.3.1}/psengine/identity/models/detections.py +0 -0
  95. {psengine-2.2.0 → psengine-2.3.1}/psengine/identity/models/incident_report.py +0 -0
  96. {psengine-2.2.0 → psengine-2.3.1}/psengine/identity/models/lookup.py +0 -0
  97. {psengine-2.2.0 → psengine-2.3.1}/psengine/logger/__init__.py +0 -0
  98. {psengine-2.2.0 → psengine-2.3.1}/psengine/logger/constants.py +0 -0
  99. {psengine-2.2.0 → psengine-2.3.1}/psengine/logger/errors.py +0 -0
  100. {psengine-2.2.0 → psengine-2.3.1}/psengine/logger/rf_logger.py +0 -0
  101. {psengine-2.2.0 → psengine-2.3.1}/psengine/markdown/__init__.py +0 -0
  102. {psengine-2.2.0 → psengine-2.3.1}/psengine/markdown/markdown.py +0 -0
  103. {psengine-2.2.0 → psengine-2.3.1}/psengine/markdown/models.py +0 -0
  104. {psengine-2.2.0 → psengine-2.3.1}/psengine/playbook_alerts/__init__.py +0 -0
  105. {psengine-2.2.0 → psengine-2.3.1}/psengine/playbook_alerts/constants.py +0 -0
  106. {psengine-2.2.0 → psengine-2.3.1}/psengine/playbook_alerts/errors.py +0 -0
  107. {psengine-2.2.0 → psengine-2.3.1}/psengine/playbook_alerts/helpers.py +0 -0
  108. {psengine-2.2.0 → psengine-2.3.1}/psengine/playbook_alerts/mappings.py +0 -0
  109. {psengine-2.2.0 → psengine-2.3.1}/psengine/playbook_alerts/markdown/__init__.py +0 -0
  110. {psengine-2.2.0 → psengine-2.3.1}/psengine/playbook_alerts/markdown/markdown.py +0 -0
  111. {psengine-2.2.0 → psengine-2.3.1}/psengine/playbook_alerts/markdown/markdown_code_repo.py +0 -0
  112. {psengine-2.2.0 → psengine-2.3.1}/psengine/playbook_alerts/markdown/markdown_cyber_vulnerability.py +0 -0
  113. {psengine-2.2.0 → psengine-2.3.1}/psengine/playbook_alerts/markdown/markdown_domain_abuse.py +0 -0
  114. {psengine-2.2.0 → psengine-2.3.1}/psengine/playbook_alerts/markdown/markdown_geopolitics_facility.py +0 -0
  115. {psengine-2.2.0 → psengine-2.3.1}/psengine/playbook_alerts/markdown/markdown_malware_report.py +0 -0
  116. {psengine-2.2.0 → psengine-2.3.1}/psengine/playbook_alerts/markdown/markdown_third_party_risk.py +0 -0
  117. {psengine-2.2.0 → psengine-2.3.1}/psengine/playbook_alerts/models/__init__.py +0 -0
  118. {psengine-2.2.0 → psengine-2.3.1}/psengine/playbook_alerts/models/common_models.py +0 -0
  119. {psengine-2.2.0 → psengine-2.3.1}/psengine/playbook_alerts/models/panel_log.py +0 -0
  120. {psengine-2.2.0 → psengine-2.3.1}/psengine/playbook_alerts/models/panel_status.py +0 -0
  121. {psengine-2.2.0 → psengine-2.3.1}/psengine/playbook_alerts/models/pba_code_repo_leak.py +0 -0
  122. {psengine-2.2.0 → psengine-2.3.1}/psengine/playbook_alerts/models/pba_cyber_vulnerability.py +0 -0
  123. {psengine-2.2.0 → psengine-2.3.1}/psengine/playbook_alerts/models/pba_domain_abuse.py +0 -0
  124. {psengine-2.2.0 → psengine-2.3.1}/psengine/playbook_alerts/models/pba_geopolitics_facility.py +0 -0
  125. {psengine-2.2.0 → psengine-2.3.1}/psengine/playbook_alerts/models/pba_identity_exposures.py +0 -0
  126. {psengine-2.2.0 → psengine-2.3.1}/psengine/playbook_alerts/models/pba_malware_report.py +0 -0
  127. {psengine-2.2.0 → psengine-2.3.1}/psengine/playbook_alerts/models/pba_third_party_risk.py +0 -0
  128. {psengine-2.2.0 → psengine-2.3.1}/psengine/playbook_alerts/models/search_endpoint.py +0 -0
  129. {psengine-2.2.0 → psengine-2.3.1}/psengine/playbook_alerts/pa_category.py +0 -0
  130. {psengine-2.2.0 → psengine-2.3.1}/psengine/playbook_alerts/playbook_alert_mgr.py +0 -0
  131. {psengine-2.2.0 → psengine-2.3.1}/psengine/playbook_alerts/playbook_alerts.py +0 -0
  132. {psengine-2.2.0 → psengine-2.3.1}/psengine/py.typed +0 -0
  133. {psengine-2.2.0 → psengine-2.3.1}/psengine/risklists/__init__.py +0 -0
  134. {psengine-2.2.0 → psengine-2.3.1}/psengine/risklists/constants.py +0 -0
  135. {psengine-2.2.0 → psengine-2.3.1}/psengine/risklists/errors.py +0 -0
  136. {psengine-2.2.0 → psengine-2.3.1}/psengine/risklists/models.py +0 -0
  137. {psengine-2.2.0 → psengine-2.3.1}/psengine/risklists/risklist_mgr.py +0 -0
  138. {psengine-2.2.0 → psengine-2.3.1}/psengine/stix2/__init__.py +0 -0
  139. {psengine-2.2.0 → psengine-2.3.1}/psengine/stix2/base_stix_entity.py +0 -0
  140. {psengine-2.2.0 → psengine-2.3.1}/psengine/stix2/complex_entity.py +0 -0
  141. {psengine-2.2.0 → psengine-2.3.1}/psengine/stix2/constants.py +0 -0
  142. {psengine-2.2.0 → psengine-2.3.1}/psengine/stix2/enriched_indicator.py +0 -0
  143. {psengine-2.2.0 → psengine-2.3.1}/psengine/stix2/errors.py +0 -0
  144. {psengine-2.2.0 → psengine-2.3.1}/psengine/stix2/helpers.py +0 -0
  145. {psengine-2.2.0 → psengine-2.3.1}/psengine/stix2/rf_bundle.py +0 -0
  146. {psengine-2.2.0 → psengine-2.3.1}/psengine/stix2/simple_entity.py +0 -0
  147. {psengine-2.2.0 → psengine-2.3.1}/psengine/stix2/util.py +0 -0
  148. {psengine-2.2.0 → psengine-2.3.1}/psengine.egg-info/dependency_links.txt +0 -0
  149. {psengine-2.2.0 → psengine-2.3.1}/psengine.egg-info/top_level.txt +0 -0
  150. {psengine-2.2.0 → psengine-2.3.1}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: psengine
3
- Version: 2.2.0
3
+ Version: 2.3.1
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
- * Analyst Notes
87
- * Collective Insights
88
- * Classic & Playbook Alerts
89
- * Detection Rules
88
+ - Analyst Notes
89
+ - Collective Insights
90
+ - Classic & Playbook Alerts
91
+ - Detection Rules
92
+ - Fusion File management
90
93
  - Identity Exposures management
91
- * List management
92
- * On demand IOC enrichment
93
- * Risklists
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
- * Built-in logging
100
- * Easy configuration management
104
+ - Built-in logging
105
+ - Easy configuration management
101
106
  - Markdown creation from certain data types
102
- * Proxy support
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
- * Analyst Notes
33
- * Collective Insights
34
- * Classic & Playbook Alerts
35
- * Detection Rules
32
+ - Analyst Notes
33
+ - Collective Insights
34
+ - Classic & Playbook Alerts
35
+ - Detection Rules
36
+ - Fusion File management
36
37
  - Identity Exposures management
37
- * List management
38
- * On demand IOC enrichment
39
- * Risklists
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
- * Built-in logging
46
- * Easy configuration management
48
+ - Built-in logging
49
+ - Easy configuration management
47
50
  - Markdown creation from certain data types
48
- * Proxy support
51
+ - Proxy support
49
52
 
@@ -11,4 +11,4 @@
11
11
  # accessed from any third party API. #
12
12
  ##############################################################################################
13
13
 
14
- __version__ = '2.2.0'
14
+ __version__ = '2.3.0'
@@ -19,24 +19,24 @@ class AnalystNoteError(RecordedFutureError):
19
19
 
20
20
 
21
21
  class AnalystNoteLookupError(RecordedFutureError):
22
- """Error raise when cannot lookup an analyst note."""
22
+ """Raised when an analyst note cannot be retrieved."""
23
23
 
24
24
 
25
25
  class AnalystNoteSearchError(RecordedFutureError):
26
- """Error raise when cannot search analyst notes."""
26
+ """Raised when an analyst note cannot be searched."""
27
27
 
28
28
 
29
29
  class AnalystNoteAttachmentError(RecordedFutureError):
30
- """Error raise when cannot lookup an analyst note."""
30
+ """Raised when an analyst note attachment cannot be retrieved."""
31
31
 
32
32
 
33
33
  class AnalystNoteDeleteError(RecordedFutureError):
34
- """Error raise when cannot delete an analyst note."""
34
+ """Raised when an analyst note cannot be deleted."""
35
35
 
36
36
 
37
37
  class AnalystNotePreviewError(RecordedFutureError):
38
- """Error raise when cannot post to preview endpoint."""
38
+ """Raised when an analyst note cannot be previewed."""
39
39
 
40
40
 
41
41
  class AnalystNotePublishError(RecordedFutureError):
42
- """Error raise when cannot post to publish endpoint."""
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
- data = json.dumps(data) if data is not None else None
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'
@@ -21,6 +21,7 @@ from .models.base_enriched_entity import BaseEnrichedEntity
21
21
  from .models.lookup import (
22
22
  CVSS,
23
23
  CVSSV3,
24
+ CVSSV4,
24
25
  CVSSRating,
25
26
  DnsPortCert,
26
27
  EnterpriseList,
@@ -103,6 +104,7 @@ class EnrichedVulnerability(BaseEnrichedEntity):
103
104
  cvss: Optional[CVSS] = None
104
105
  cvss_ratings: list[CVSSRating] = Field(alias='cvssRatings', default=None)
105
106
  cvssv3: Optional[CVSSV3] = None
107
+ cvssv4: Optional[CVSSV4] = None
106
108
  nvd_description: Optional[str] = Field(alias='nvdDescription', default=None)
107
109
  nvd_references: Optional[list[NvdReference]] = Field(alias='nvdReferences', default=None)
108
110
  raw_risk: Optional[list[RawRisk]] = Field(alias='rawrisk', default=None)
@@ -206,6 +206,50 @@ class CVSSV3(RFBaseModel):
206
206
  availability_impact: Optional[str] = Field(alias='availabilityImpact', default=None)
207
207
 
208
208
 
209
+ class CVSSV4(RFBaseModel):
210
+ subsequent_system_integrity: Optional[str] = Field(
211
+ alias='subsequentSystemIntegrity', default=None
212
+ )
213
+ provider_urgency: Optional[str] = Field(alias='providerUrgency', default=None)
214
+ attack_requirements: Optional[str] = Field(alias='attackRequirements', default=None)
215
+ vulnerable_system_confidentiality: Optional[str] = Field(
216
+ alias='vulnerableSystemConfidentiality', default=None
217
+ )
218
+ vulnerability_response_effort: Optional[str] = Field(
219
+ alias='vulnerabilityResponseEffort', default=None
220
+ )
221
+ threat_score: Optional[float] = Field(alias='threatScore', default=None)
222
+ subsequent_system_availability: Optional[str] = Field(
223
+ alias='subsequentSystemAvailability', default=None
224
+ )
225
+ base_severity: Optional[str] = Field(alias='baseSeverity', default=None)
226
+ base_score: Optional[float] = Field(alias='baseScore', default=None)
227
+ user_interaction: Optional[str] = Field(alias='userInteraction', default=None)
228
+ attack_vector: Optional[str] = Field(alias='attackVector', default=None)
229
+ source: Optional[str] = None
230
+ vulnerable_system_integrity: Optional[str] = Field(
231
+ alias='vulnerableSystemIntegrity', default=None
232
+ )
233
+ vulnerable_system_availability: Optional[str] = Field(
234
+ alias='vulnerableSystemAvailability', default=None
235
+ )
236
+ modified: Optional[datetime] = None
237
+ vector_string: Optional[str] = Field(alias='vectorString', default=None)
238
+ recovery: Optional[str] = None
239
+ version: Optional[str] = None
240
+ threat_severity: Optional[str] = Field(alias='threatSeverity', default=None)
241
+ privileges_required: Optional[str] = Field(alias='privilegesRequired', default=None)
242
+ exploit_maturity: Optional[str] = Field(alias='exploitMaturity', default=None)
243
+ safety: Optional[str] = None
244
+ subsequent_system_confidentiality: Optional[str] = Field(
245
+ alias='subsequentSystemConfidentiality', default=None
246
+ )
247
+ automatable: Optional[str] = None
248
+ value_density: Optional[str] = Field(alias='valueDensity', default=None)
249
+ attack_complexity: Optional[str] = Field(alias='attackComplexity', default=None)
250
+ created: Optional[datetime] = None
251
+
252
+
209
253
  ###########################################################
210
254
  # Raw Risk
211
255
  ###########################################################
@@ -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."""