psengine 2.4.1__tar.gz → 2.4.3__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.4.1 → psengine-2.4.3}/PKG-INFO +3 -3
  2. {psengine-2.4.1 → psengine-2.4.3}/psengine/__init__.py +0 -1
  3. {psengine-2.4.1 → psengine-2.4.3}/psengine/_sdk_id.py +2 -2
  4. {psengine-2.4.1 → psengine-2.4.3}/psengine/constants.py +1 -1
  5. {psengine-2.4.1 → psengine-2.4.3}/psengine/detection/detection_mgr.py +1 -1
  6. {psengine-2.4.1 → psengine-2.4.3}/psengine/malware_intel/malware_intel.py +5 -1
  7. {psengine-2.4.1 → psengine-2.4.3}/psengine/malware_intel/malware_intel_mgr.py +4 -2
  8. {psengine-2.4.1 → psengine-2.4.3}/psengine/playbook_alerts/constants.py +0 -2
  9. {psengine-2.4.1 → psengine-2.4.3}/psengine/playbook_alerts/markdown/markdown.py +1 -1
  10. {psengine-2.4.1 → psengine-2.4.3}/psengine/playbook_alerts/models/common_models.py +6 -0
  11. {psengine-2.4.1 → psengine-2.4.3}/psengine/playbook_alerts/models/panel_status.py +2 -0
  12. {psengine-2.4.1 → psengine-2.4.3}/psengine/playbook_alerts/models/pba_geopolitics_facility.py +2 -2
  13. {psengine-2.4.1 → psengine-2.4.3}/psengine/playbook_alerts/models/search_endpoint.py +2 -0
  14. {psengine-2.4.1 → psengine-2.4.3}/psengine/playbook_alerts/playbook_alert_mgr.py +11 -9
  15. {psengine-2.4.1 → psengine-2.4.3}/psengine/playbook_alerts/playbook_alerts.py +1 -1
  16. {psengine-2.4.1 → psengine-2.4.3}/psengine/rf_client.py +15 -1
  17. {psengine-2.4.1 → psengine-2.4.3}/psengine.egg-info/PKG-INFO +3 -3
  18. {psengine-2.4.1 → psengine-2.4.3}/psengine.egg-info/SOURCES.txt +0 -1
  19. {psengine-2.4.1 → psengine-2.4.3}/pyproject.toml +4 -3
  20. psengine-2.4.1/psengine/_version.py +0 -14
  21. {psengine-2.4.1 → psengine-2.4.3}/LICENSE +0 -0
  22. {psengine-2.4.1 → psengine-2.4.3}/README.md +0 -0
  23. {psengine-2.4.1 → psengine-2.4.3}/psengine/analyst_notes/__init__.py +0 -0
  24. {psengine-2.4.1 → psengine-2.4.3}/psengine/analyst_notes/constants.py +0 -0
  25. {psengine-2.4.1 → psengine-2.4.3}/psengine/analyst_notes/errors.py +0 -0
  26. {psengine-2.4.1 → psengine-2.4.3}/psengine/analyst_notes/helpers.py +0 -0
  27. {psengine-2.4.1 → psengine-2.4.3}/psengine/analyst_notes/markdown.py +0 -0
  28. {psengine-2.4.1 → psengine-2.4.3}/psengine/analyst_notes/models.py +0 -0
  29. {psengine-2.4.1 → psengine-2.4.3}/psengine/analyst_notes/note.py +0 -0
  30. {psengine-2.4.1 → psengine-2.4.3}/psengine/analyst_notes/note_mgr.py +0 -0
  31. {psengine-2.4.1 → psengine-2.4.3}/psengine/base_http_client.py +0 -0
  32. {psengine-2.4.1 → psengine-2.4.3}/psengine/classic_alerts/__init__.py +0 -0
  33. {psengine-2.4.1 → psengine-2.4.3}/psengine/classic_alerts/classic_alert.py +0 -0
  34. {psengine-2.4.1 → psengine-2.4.3}/psengine/classic_alerts/classic_alert_mgr.py +0 -0
  35. {psengine-2.4.1 → psengine-2.4.3}/psengine/classic_alerts/constants.py +0 -0
  36. {psengine-2.4.1 → psengine-2.4.3}/psengine/classic_alerts/errors.py +0 -0
  37. {psengine-2.4.1 → psengine-2.4.3}/psengine/classic_alerts/helpers.py +0 -0
  38. {psengine-2.4.1 → psengine-2.4.3}/psengine/classic_alerts/markdown/__init__.py +0 -0
  39. {psengine-2.4.1 → psengine-2.4.3}/psengine/classic_alerts/markdown/markdown.py +0 -0
  40. {psengine-2.4.1 → psengine-2.4.3}/psengine/classic_alerts/models.py +0 -0
  41. {psengine-2.4.1 → psengine-2.4.3}/psengine/collective_insights/__init__.py +0 -0
  42. {psengine-2.4.1 → psengine-2.4.3}/psengine/collective_insights/collective_insights.py +0 -0
  43. {psengine-2.4.1 → psengine-2.4.3}/psengine/collective_insights/constants.py +0 -0
  44. {psengine-2.4.1 → psengine-2.4.3}/psengine/collective_insights/errors.py +0 -0
  45. {psengine-2.4.1 → psengine-2.4.3}/psengine/collective_insights/insight.py +0 -0
  46. {psengine-2.4.1 → psengine-2.4.3}/psengine/collective_insights/models.py +0 -0
  47. {psengine-2.4.1 → psengine-2.4.3}/psengine/common_models.py +0 -0
  48. {psengine-2.4.1 → psengine-2.4.3}/psengine/config/__init__.py +0 -0
  49. {psengine-2.4.1 → psengine-2.4.3}/psengine/config/config.py +0 -0
  50. {psengine-2.4.1 → psengine-2.4.3}/psengine/config/errors.py +0 -0
  51. {psengine-2.4.1 → psengine-2.4.3}/psengine/detection/__init__.py +0 -0
  52. {psengine-2.4.1 → psengine-2.4.3}/psengine/detection/detection_rule.py +0 -0
  53. {psengine-2.4.1 → psengine-2.4.3}/psengine/detection/errors.py +0 -0
  54. {psengine-2.4.1 → psengine-2.4.3}/psengine/detection/helpers.py +0 -0
  55. {psengine-2.4.1 → psengine-2.4.3}/psengine/detection/models.py +0 -0
  56. {psengine-2.4.1 → psengine-2.4.3}/psengine/endpoints.py +0 -0
  57. {psengine-2.4.1 → psengine-2.4.3}/psengine/enrich/__init__.py +0 -0
  58. {psengine-2.4.1 → psengine-2.4.3}/psengine/enrich/constants.py +0 -0
  59. {psengine-2.4.1 → psengine-2.4.3}/psengine/enrich/errors.py +0 -0
  60. {psengine-2.4.1 → psengine-2.4.3}/psengine/enrich/lookup.py +0 -0
  61. {psengine-2.4.1 → psengine-2.4.3}/psengine/enrich/lookup_mgr.py +0 -0
  62. {psengine-2.4.1 → psengine-2.4.3}/psengine/enrich/models/__init__.py +0 -0
  63. {psengine-2.4.1 → psengine-2.4.3}/psengine/enrich/models/base_enriched_entity.py +0 -0
  64. {psengine-2.4.1 → psengine-2.4.3}/psengine/enrich/models/lookup.py +0 -0
  65. {psengine-2.4.1 → psengine-2.4.3}/psengine/enrich/models/soar.py +0 -0
  66. {psengine-2.4.1 → psengine-2.4.3}/psengine/enrich/soar.py +0 -0
  67. {psengine-2.4.1 → psengine-2.4.3}/psengine/enrich/soar_mgr.py +0 -0
  68. {psengine-2.4.1 → psengine-2.4.3}/psengine/entity_lists/__init__.py +0 -0
  69. {psengine-2.4.1 → psengine-2.4.3}/psengine/entity_lists/constants.py +0 -0
  70. {psengine-2.4.1 → psengine-2.4.3}/psengine/entity_lists/entity_list.py +0 -0
  71. {psengine-2.4.1 → psengine-2.4.3}/psengine/entity_lists/entity_list_mgr.py +0 -0
  72. {psengine-2.4.1 → psengine-2.4.3}/psengine/entity_lists/errors.py +0 -0
  73. {psengine-2.4.1 → psengine-2.4.3}/psengine/entity_lists/models.py +0 -0
  74. {psengine-2.4.1 → psengine-2.4.3}/psengine/entity_match/__init__.py +0 -0
  75. {psengine-2.4.1 → psengine-2.4.3}/psengine/entity_match/entity_match.py +0 -0
  76. {psengine-2.4.1 → psengine-2.4.3}/psengine/entity_match/entity_match_mgr.py +0 -0
  77. {psengine-2.4.1 → psengine-2.4.3}/psengine/entity_match/errors.py +0 -0
  78. {psengine-2.4.1 → psengine-2.4.3}/psengine/entity_match/models.py +0 -0
  79. {psengine-2.4.1 → psengine-2.4.3}/psengine/errors.py +0 -0
  80. {psengine-2.4.1 → psengine-2.4.3}/psengine/fusion/__init__.py +0 -0
  81. {psengine-2.4.1 → psengine-2.4.3}/psengine/fusion/errors.py +0 -0
  82. {psengine-2.4.1 → psengine-2.4.3}/psengine/fusion/fusion_mgr.py +0 -0
  83. {psengine-2.4.1 → psengine-2.4.3}/psengine/fusion/models.py +0 -0
  84. {psengine-2.4.1 → psengine-2.4.3}/psengine/helpers/__init__.py +0 -0
  85. {psengine-2.4.1 → psengine-2.4.3}/psengine/helpers/helpers.py +0 -0
  86. {psengine-2.4.1 → psengine-2.4.3}/psengine/identity/__init__.py +0 -0
  87. {psengine-2.4.1 → psengine-2.4.3}/psengine/identity/constants.py +0 -0
  88. {psengine-2.4.1 → psengine-2.4.3}/psengine/identity/errors.py +0 -0
  89. {psengine-2.4.1 → psengine-2.4.3}/psengine/identity/identity.py +0 -0
  90. {psengine-2.4.1 → psengine-2.4.3}/psengine/identity/identity_mgr.py +0 -0
  91. {psengine-2.4.1 → psengine-2.4.3}/psengine/identity/models/__init__.py +0 -0
  92. {psengine-2.4.1 → psengine-2.4.3}/psengine/identity/models/common_models.py +0 -0
  93. {psengine-2.4.1 → psengine-2.4.3}/psengine/identity/models/detections.py +0 -0
  94. {psengine-2.4.1 → psengine-2.4.3}/psengine/identity/models/incident_report.py +0 -0
  95. {psengine-2.4.1 → psengine-2.4.3}/psengine/identity/models/lookup.py +0 -0
  96. {psengine-2.4.1 → psengine-2.4.3}/psengine/logger/__init__.py +0 -0
  97. {psengine-2.4.1 → psengine-2.4.3}/psengine/logger/constants.py +0 -0
  98. {psengine-2.4.1 → psengine-2.4.3}/psengine/logger/errors.py +0 -0
  99. {psengine-2.4.1 → psengine-2.4.3}/psengine/logger/rf_logger.py +0 -0
  100. {psengine-2.4.1 → psengine-2.4.3}/psengine/malware_intel/__init__.py +0 -0
  101. {psengine-2.4.1 → psengine-2.4.3}/psengine/malware_intel/errors.py +0 -0
  102. {psengine-2.4.1 → psengine-2.4.3}/psengine/malware_intel/models.py +0 -0
  103. {psengine-2.4.1 → psengine-2.4.3}/psengine/markdown/__init__.py +0 -0
  104. {psengine-2.4.1 → psengine-2.4.3}/psengine/markdown/markdown.py +0 -0
  105. {psengine-2.4.1 → psengine-2.4.3}/psengine/markdown/models.py +0 -0
  106. {psengine-2.4.1 → psengine-2.4.3}/psengine/playbook_alerts/__init__.py +0 -0
  107. {psengine-2.4.1 → psengine-2.4.3}/psengine/playbook_alerts/errors.py +0 -0
  108. {psengine-2.4.1 → psengine-2.4.3}/psengine/playbook_alerts/helpers.py +0 -0
  109. {psengine-2.4.1 → psengine-2.4.3}/psengine/playbook_alerts/mappings.py +0 -0
  110. {psengine-2.4.1 → psengine-2.4.3}/psengine/playbook_alerts/markdown/__init__.py +0 -0
  111. {psengine-2.4.1 → psengine-2.4.3}/psengine/playbook_alerts/markdown/markdown_code_repo.py +0 -0
  112. {psengine-2.4.1 → psengine-2.4.3}/psengine/playbook_alerts/markdown/markdown_cyber_vulnerability.py +0 -0
  113. {psengine-2.4.1 → psengine-2.4.3}/psengine/playbook_alerts/markdown/markdown_domain_abuse.py +0 -0
  114. {psengine-2.4.1 → psengine-2.4.3}/psengine/playbook_alerts/markdown/markdown_geopolitics_facility.py +0 -0
  115. {psengine-2.4.1 → psengine-2.4.3}/psengine/playbook_alerts/markdown/markdown_identity_exposure.py +0 -0
  116. {psengine-2.4.1 → psengine-2.4.3}/psengine/playbook_alerts/markdown/markdown_malware_report.py +0 -0
  117. {psengine-2.4.1 → psengine-2.4.3}/psengine/playbook_alerts/markdown/markdown_third_party_risk.py +0 -0
  118. {psengine-2.4.1 → psengine-2.4.3}/psengine/playbook_alerts/models/__init__.py +0 -0
  119. {psengine-2.4.1 → psengine-2.4.3}/psengine/playbook_alerts/models/panel_log.py +0 -0
  120. {psengine-2.4.1 → psengine-2.4.3}/psengine/playbook_alerts/models/pba_code_repo_leak.py +0 -0
  121. {psengine-2.4.1 → psengine-2.4.3}/psengine/playbook_alerts/models/pba_cyber_vulnerability.py +0 -0
  122. {psengine-2.4.1 → psengine-2.4.3}/psengine/playbook_alerts/models/pba_domain_abuse.py +0 -0
  123. {psengine-2.4.1 → psengine-2.4.3}/psengine/playbook_alerts/models/pba_identity_exposures.py +0 -0
  124. {psengine-2.4.1 → psengine-2.4.3}/psengine/playbook_alerts/models/pba_malware_report.py +0 -0
  125. {psengine-2.4.1 → psengine-2.4.3}/psengine/playbook_alerts/models/pba_third_party_risk.py +0 -0
  126. {psengine-2.4.1 → psengine-2.4.3}/psengine/playbook_alerts/pa_category.py +0 -0
  127. {psengine-2.4.1 → psengine-2.4.3}/psengine/py.typed +0 -0
  128. {psengine-2.4.1 → psengine-2.4.3}/psengine/risk_history/__init__.py +0 -0
  129. {psengine-2.4.1 → psengine-2.4.3}/psengine/risk_history/errors.py +0 -0
  130. {psengine-2.4.1 → psengine-2.4.3}/psengine/risk_history/models.py +0 -0
  131. {psengine-2.4.1 → psengine-2.4.3}/psengine/risk_history/risk_history_mgr.py +0 -0
  132. {psengine-2.4.1 → psengine-2.4.3}/psengine/risklists/__init__.py +0 -0
  133. {psengine-2.4.1 → psengine-2.4.3}/psengine/risklists/constants.py +0 -0
  134. {psengine-2.4.1 → psengine-2.4.3}/psengine/risklists/errors.py +0 -0
  135. {psengine-2.4.1 → psengine-2.4.3}/psengine/risklists/models.py +0 -0
  136. {psengine-2.4.1 → psengine-2.4.3}/psengine/risklists/risklist_mgr.py +0 -0
  137. {psengine-2.4.1 → psengine-2.4.3}/psengine/stix2/__init__.py +0 -0
  138. {psengine-2.4.1 → psengine-2.4.3}/psengine/stix2/base_stix_entity.py +0 -0
  139. {psengine-2.4.1 → psengine-2.4.3}/psengine/stix2/complex_entity.py +0 -0
  140. {psengine-2.4.1 → psengine-2.4.3}/psengine/stix2/constants.py +0 -0
  141. {psengine-2.4.1 → psengine-2.4.3}/psengine/stix2/enriched_indicator.py +0 -0
  142. {psengine-2.4.1 → psengine-2.4.3}/psengine/stix2/errors.py +0 -0
  143. {psengine-2.4.1 → psengine-2.4.3}/psengine/stix2/helpers.py +0 -0
  144. {psengine-2.4.1 → psengine-2.4.3}/psengine/stix2/rf_bundle.py +0 -0
  145. {psengine-2.4.1 → psengine-2.4.3}/psengine/stix2/simple_entity.py +0 -0
  146. {psengine-2.4.1 → psengine-2.4.3}/psengine/stix2/util.py +0 -0
  147. {psengine-2.4.1 → psengine-2.4.3}/psengine.egg-info/dependency_links.txt +0 -0
  148. {psengine-2.4.1 → psengine-2.4.3}/psengine.egg-info/requires.txt +0 -0
  149. {psengine-2.4.1 → psengine-2.4.3}/psengine.egg-info/top_level.txt +0 -0
  150. {psengine-2.4.1 → psengine-2.4.3}/setup.cfg +0 -0
@@ -1,11 +1,11 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: psengine
3
- Version: 2.4.1
3
+ Version: 2.4.3
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
7
- Project-URL: Homepage, https://github.com/RecordedFuture-ProfessionalServices/psengine
8
- Project-URL: Changelog, https://recordedfuture-professionalservices.github.io/psengine/CHANGELOG/
7
+ Project-URL: Homepage, https://recordedfuture-professionalservices.github.io/psengine/latest/
8
+ Project-URL: Changelog, https://recordedfuture-professionalservices.github.io/psengine/latest/CHANGELOG/
9
9
  Keywords: API,Recorded Future,Cyber Security Engineering,Threat Intelligence
10
10
  Classifier: Development Status :: 5 - Production/Stable
11
11
  Classifier: Programming Language :: Python
@@ -13,7 +13,6 @@
13
13
 
14
14
  import logging
15
15
 
16
- from ._version import __version__ as version
17
16
  from .base_http_client import BaseHTTPClient
18
17
  from .errors import ReadFileError, RecordedFutureError, WriteFileError
19
18
  from .rf_client import RFClient
@@ -11,6 +11,6 @@
11
11
  # accessed from any third party API. #
12
12
  ##############################################################################################
13
13
 
14
- from ._version import __version__
14
+ from importlib.metadata import version
15
15
 
16
- SDK_ID = f'psengine-py/{__version__}'
16
+ SDK_ID = f'psengine-py/{version("psengine")}'
@@ -23,7 +23,7 @@ DEFAULT_MAX_WORKERS = 10
23
23
  # Recorded Future API
24
24
  #####################
25
25
  RF_TOKEN_ENV_VAR = 'RF_TOKEN' # noqa: S105
26
- RF_TOKEN_VALIDATION_REGEX = r'^[a-f0-9]{32}$' # noqa: S105
26
+ RF_TOKEN_VALIDATION_REGEX = r'^[a-z0-9]{32}$' # noqa: S105
27
27
 
28
28
  #####################
29
29
  # Recorded Future Portal
@@ -93,7 +93,7 @@ class DetectionMgr:
93
93
  data = {
94
94
  'filter': filters,
95
95
  'tagged_entities': tagged_entities,
96
- 'limit': SEARCH_LIMIT,
96
+ 'limit': min(SEARCH_LIMIT, max_results) if max_results else SEARCH_LIMIT,
97
97
  }
98
98
 
99
99
  data = DetectionRuleSearchOut.model_validate(data)
@@ -89,6 +89,8 @@ class SandboxReport(RFBaseModel):
89
89
 
90
90
 
91
91
  def _split_time(value: str):
92
+ if not value:
93
+ return value
92
94
  return value.split('T')[0]
93
95
 
94
96
 
@@ -101,7 +103,9 @@ class MalwareReportIn(RFBaseModel):
101
103
  str, BeforeValidator(Validators.convert_relative_time), AfterValidator(_split_time)
102
104
  ]
103
105
  end_date: Annotated[
104
- str, BeforeValidator(Validators.convert_relative_time), AfterValidator(_split_time)
106
+ Optional[str],
107
+ BeforeValidator(Validators.convert_relative_time),
108
+ AfterValidator(_split_time),
105
109
  ]
106
110
  my_enterprise: bool
107
111
  limit: int = Field(ge=1, le=10)
@@ -12,7 +12,7 @@
12
12
  ##############################################################################################
13
13
 
14
14
  import logging
15
- from typing import Annotated
15
+ from typing import Annotated, Optional
16
16
 
17
17
  from pydantic import validate_call
18
18
  from typing_extensions import Doc
@@ -48,7 +48,9 @@ class MalwareIntelMgr:
48
48
  start_date: Annotated[
49
49
  str, Doc('The starting date, format YYYY-MM-DD or relative like -1d.')
50
50
  ],
51
- end_date: Annotated[str, Doc('The ending date, format YYYY-MM-DD or relative like -1d.')],
51
+ end_date: Annotated[
52
+ Optional[str], Doc('The ending date, format YYYY-MM-DD or relative like -1d.')
53
+ ] = None,
52
54
  my_enterprise: Annotated[
53
55
  bool, Doc('If the report has been submitted by your enterprise.')
54
56
  ] = False,
@@ -58,5 +58,3 @@ PBA_WITH_IMAGES_VALIDATOR = Union[
58
58
  PBA_WITH_IMAGES_INST = (PBA_DomainAbuse, PBA_GeopoliticsFacility)
59
59
 
60
60
  ALERTS_PER_PAGE = 50
61
-
62
- BULK_LOOKUP_BATCH_SIZE = 200
@@ -42,7 +42,7 @@ MARKDOWN_BY_PBA_TYPE = {
42
42
 
43
43
 
44
44
  def _generic_pba_summary(pba, md_maker: MarkdownMaker):
45
- md_maker.add_title(pba.panel_status.case_rule_label)
45
+ md_maker.add_title(pba.panel_status.alert_rule.name or pba.panel_status.alert_rule.label)
46
46
  id_ = pba.playbook_alert_id
47
47
  general_info = [
48
48
  f'{bold("ID:")} {id_} ',
@@ -29,3 +29,9 @@ class PBAInsiktNote(RFBaseModel):
29
29
  published: Optional[datetime] = None
30
30
  topic: Optional[str] = None
31
31
  fragment: Optional[str] = None
32
+
33
+
34
+ class AlertRule(RFBaseModel):
35
+ id_: str = Field(alias='id')
36
+ label: str
37
+ name: Optional[str] = None
@@ -19,6 +19,7 @@ from pydantic import model_validator
19
19
 
20
20
  from ...common_models import RFBaseModel
21
21
  from ..models.common_models import ResolvedEntity
22
+ from .common_models import AlertRule
22
23
 
23
24
 
24
25
  class Organisation(RFBaseModel):
@@ -42,6 +43,7 @@ class PanelStatus(RFBaseModel):
42
43
  updated: datetime
43
44
  case_rule_id: Optional[str] = None
44
45
  case_rule_label: Optional[str] = None
46
+ alert_rule: AlertRule
45
47
  creator_name: Optional[str] = None
46
48
  creator_id: Optional[str] = None
47
49
  owner_organisation_details: Optional[OwnerOrganisationDetails] = None
@@ -14,7 +14,7 @@
14
14
  from datetime import datetime
15
15
  from typing import Optional
16
16
 
17
- from pydantic import Field, HttpUrl
17
+ from pydantic import Field
18
18
 
19
19
  from ...common_models import RFBaseModel
20
20
  from ..models.panel_status import PanelStatus
@@ -28,7 +28,7 @@ class Assessment(RFBaseModel):
28
28
  class Event(RFBaseModel):
29
29
  text: str = None
30
30
  source: str = None
31
- url: HttpUrl = None
31
+ url: str = None
32
32
  assessments: list[Assessment] = []
33
33
  document_id: str = None
34
34
  time: datetime = None
@@ -19,6 +19,7 @@ from pydantic import Field, model_validator
19
19
 
20
20
  from ...common_models import RFBaseModel
21
21
  from ..models.panel_status import OwnerOrganisationDetails
22
+ from .common_models import AlertRule
22
23
 
23
24
 
24
25
  class DatetimeRange(RFBaseModel):
@@ -33,6 +34,7 @@ class SearchStatus(RFBaseModel):
33
34
 
34
35
  class SearchData(RFBaseModel):
35
36
  playbook_alert_id: str
37
+ alert_rule: AlertRule
36
38
  status: str
37
39
  priority: str
38
40
  reopen: Optional[str] = None
@@ -30,7 +30,6 @@ from ..helpers import TimeHelpers, connection_exceptions, debug_call
30
30
  from ..rf_client import RFClient
31
31
  from .constants import (
32
32
  ALERTS_PER_PAGE,
33
- BULK_LOOKUP_BATCH_SIZE,
34
33
  PBA_WITH_IMAGES_INST,
35
34
  PBA_WITH_IMAGES_TYPE,
36
35
  PBA_WITH_IMAGES_VALIDATOR,
@@ -141,9 +140,9 @@ class PlaybookAlertMgr:
141
140
  alerts_per_page: Annotated[
142
141
  Optional[int], Doc('Number of alerts per page (pagination).')
143
142
  ] = Field(ge=1, le=10000, default=ALERTS_PER_PAGE),
144
- max_results: Annotated[
145
- Optional[int], Doc('Maximum number of alerts to fetch.')
146
- ] = DEFAULT_LIMIT,
143
+ max_results: Annotated[Optional[int], Doc('Maximum number of alerts to fetch.')] = Field(
144
+ ge=1, le=10_000, default=DEFAULT_LIMIT
145
+ ),
147
146
  order_by: Annotated[
148
147
  Optional[str], Doc('Field to order alerts by, e.g. `created` or `updated`.')
149
148
  ] = None,
@@ -208,7 +207,9 @@ class PlaybookAlertMgr:
208
207
  in_cat_alerts = filter(lambda x: x['category'] == cat, alerts)
209
208
  in_cat_ids = [x['id'] for x in in_cat_alerts]
210
209
  try:
211
- fetched_alerts.extend(self._do_bulk(in_cat_ids, cat, fetch_images, panels or []))
210
+ fetched_alerts.extend(
211
+ self._do_bulk(in_cat_ids, cat, fetch_images, panels or [], alerts_per_page)
212
+ )
212
213
  except (PlaybookAlertBulkFetchError, PlaybookAlertRetrieveImageError) as err: # noqa: PERF203
213
214
  errors += 1
214
215
  self.log.error(err)
@@ -229,7 +230,7 @@ class PlaybookAlertMgr:
229
230
  ),
230
231
  max_results: Annotated[
231
232
  Optional[int], Doc('Maximum total number of alerts to fetch.')
232
- ] = DEFAULT_LIMIT,
233
+ ] = Field(ge=1, le=10_000, default=DEFAULT_LIMIT),
233
234
  order_by: Annotated[
234
235
  Optional[str], Doc('Field to order alerts by, e.g. `created` or `updated`.')
235
236
  ] = None,
@@ -286,7 +287,7 @@ class PlaybookAlertMgr:
286
287
  data=request_body,
287
288
  max_results=max_results,
288
289
  results_path='data',
289
- offset_key='from',
290
+ offset_key='offset',
290
291
  )
291
292
 
292
293
  # To avoid a breaking change have to reconstruct the SearchResponse model manually
@@ -547,7 +548,7 @@ class PlaybookAlertMgr:
547
548
  @validate_call
548
549
  @connection_exceptions(ignore_status_code=[], exception_to_raise=PlaybookAlertBulkFetchError)
549
550
  def _do_bulk(
550
- self, alert_ids: list, category: str, fetch_image: bool, panels: list
551
+ self, alert_ids: list, category: str, fetch_image: bool, panels: list, alerts_per_page: int
551
552
  ) -> list[PLAYBOOK_ALERT_TYPE]:
552
553
  """Does bulk fetch (used by bulk() after alert IDs have been sorted by category).
553
554
 
@@ -556,6 +557,7 @@ class PlaybookAlertMgr:
556
557
  category (str): Category of alert to fetch
557
558
  fetch_image (bool): Whether to fetch images for Domain Abuse alerts
558
559
  panels (list): List of panels to fetch
560
+ alerts_per_page (int): Number of alerts to fetch per page for bulk search results
559
561
 
560
562
  Raises:
561
563
  ValidationError: if any supplied parameter is of incorrect type
@@ -576,7 +578,7 @@ class PlaybookAlertMgr:
576
578
  self.log.info(f'Fetching {len(alert_ids)} {category} alerts')
577
579
 
578
580
  results = []
579
- for batch in batched(alert_ids, BULK_LOOKUP_BATCH_SIZE):
581
+ for batch in batched(alert_ids, alerts_per_page):
580
582
  data['playbook_alert_ids'] = batch
581
583
  response = self.rf_client.request('post', url=CATEGORY_ENDPOINTS[category], data=data)
582
584
  results += response.json()['data']
@@ -127,7 +127,7 @@ class PBA_Generic(RFBaseModel):
127
127
  return (
128
128
  f'Playbook Alert ID: {self.playbook_alert_id}, '
129
129
  f'Updated: {self.panel_status.updated.strftime(TIMESTAMP_STR)}, '
130
- f'Category: {self.panel_status.case_rule_label}, '
130
+ f'Category: {self.panel_status.alert_rule.name or self.panel_status.alert_rule.label}, '
131
131
  f'Lookup Status: {self.panel_status.status}'
132
132
  )
133
133
 
@@ -184,8 +184,13 @@ class RFClient(BaseHTTPClient):
184
184
  **kwargs,
185
185
  ):
186
186
  if 'next_offset' in json_response:
187
+ current_len = 0
187
188
  while 'next_offset' in json_response:
188
189
  data[offset_key] = json_response['next_offset']
190
+ data['limit'] = min(data['limit'], max_results - current_len)
191
+ if data['limit'] <= 0:
192
+ break
193
+
189
194
  json_response = self.request(
190
195
  method=method,
191
196
  url=url,
@@ -202,11 +207,15 @@ class RFClient(BaseHTTPClient):
202
207
  if any(len(v) >= max_results for v in dict_results.values()):
203
208
  dict_results = {k: v[:max_results] for k, v in dict_results.items()}
204
209
  break
210
+ current_len = max(len(v) for v in dict_results.values())
211
+
205
212
  else:
206
213
  all_results += self._get_matches(results_expr, json_response)
207
- if len(all_results) >= max_results:
214
+ current_len = len(all_results)
215
+ if current_len >= max_results:
208
216
  all_results = all_results[:max_results]
209
217
  break
218
+
210
219
  else:
211
220
  seen = json_response['counts']['returned']
212
221
  if json_response['counts']['total'] > max_results:
@@ -324,6 +333,9 @@ class RFClient(BaseHTTPClient):
324
333
  with suppress(KeyError):
325
334
  dict_results[str(expr)].extend(self._get_matches(expr, json_response))
326
335
 
336
+ if len(all_results) >= max_results:
337
+ return all_results[:max_results]
338
+
327
339
  if method.lower() == 'get':
328
340
  return self._request_paged_get(
329
341
  url=url,
@@ -340,6 +352,8 @@ class RFClient(BaseHTTPClient):
340
352
  )
341
353
 
342
354
  if method.lower() == 'post':
355
+ data['limit'] = min(data['limit'], max_results - len(all_results))
356
+
343
357
  return self._request_paged_post(
344
358
  url=url,
345
359
  method=method,
@@ -1,11 +1,11 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: psengine
3
- Version: 2.4.1
3
+ Version: 2.4.3
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
7
- Project-URL: Homepage, https://github.com/RecordedFuture-ProfessionalServices/psengine
8
- Project-URL: Changelog, https://recordedfuture-professionalservices.github.io/psengine/CHANGELOG/
7
+ Project-URL: Homepage, https://recordedfuture-professionalservices.github.io/psengine/latest/
8
+ Project-URL: Changelog, https://recordedfuture-professionalservices.github.io/psengine/latest/CHANGELOG/
9
9
  Keywords: API,Recorded Future,Cyber Security Engineering,Threat Intelligence
10
10
  Classifier: Development Status :: 5 - Production/Stable
11
11
  Classifier: Programming Language :: Python
@@ -3,7 +3,6 @@ README.md
3
3
  pyproject.toml
4
4
  psengine/__init__.py
5
5
  psengine/_sdk_id.py
6
- psengine/_version.py
7
6
  psengine/base_http_client.py
8
7
  psengine/common_models.py
9
8
  psengine/constants.py
@@ -1,10 +1,11 @@
1
1
  [project]
2
2
  name = "psengine"
3
- version = "2.4.1"
3
+ version = "2.4.3"
4
4
  readme = "README.md"
5
5
  license = "MIT"
6
6
  requires-python = ">=3.9, <3.14"
7
7
  description = "psengine is a simple, yet elegant, library for rapid development of integrations with Recorded Future."
8
+
8
9
  authors = [
9
10
  {name = "Moise Medici", email = "moise.medici@recordedfuture.com"},
10
11
  {name = "Patrick Kinsella", email = "patrick.kinsella@recordedfuture.com"},
@@ -77,8 +78,8 @@ requires = [
77
78
  build-backend = "setuptools.build_meta"
78
79
 
79
80
  [project.urls]
80
- Homepage = "https://github.com/RecordedFuture-ProfessionalServices/psengine"
81
- Changelog = "https://recordedfuture-professionalservices.github.io/psengine/CHANGELOG/"
81
+ Homepage = "https://recordedfuture-professionalservices.github.io/psengine/latest/"
82
+ Changelog = "https://recordedfuture-professionalservices.github.io/psengine/latest/CHANGELOG/"
82
83
 
83
84
  [tool.setuptools]
84
85
  packages = [
@@ -1,14 +0,0 @@
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
- __version__ = '2.4.1'
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes