psengine 2.3.0__tar.gz → 2.4.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.
Files changed (150) hide show
  1. {psengine-2.3.0 → psengine-2.4.0}/PKG-INFO +1 -1
  2. {psengine-2.3.0 → psengine-2.4.0}/psengine/_version.py +1 -1
  3. {psengine-2.3.0 → psengine-2.4.0}/psengine/analyst_notes/note_mgr.py +1 -1
  4. {psengine-2.3.0 → psengine-2.4.0}/psengine/enrich/lookup.py +4 -0
  5. {psengine-2.3.0 → psengine-2.4.0}/psengine/enrich/models/lookup.py +83 -0
  6. {psengine-2.3.0 → psengine-2.4.0}/psengine/entity_match/entity_match.py +17 -0
  7. {psengine-2.3.0 → psengine-2.4.0}/psengine/entity_match/entity_match_mgr.py +2 -2
  8. {psengine-2.3.0 → psengine-2.4.0}/psengine.egg-info/PKG-INFO +1 -1
  9. {psengine-2.3.0 → psengine-2.4.0}/pyproject.toml +1 -1
  10. {psengine-2.3.0 → psengine-2.4.0}/LICENSE +0 -0
  11. {psengine-2.3.0 → psengine-2.4.0}/README.md +0 -0
  12. {psengine-2.3.0 → psengine-2.4.0}/psengine/__init__.py +0 -0
  13. {psengine-2.3.0 → psengine-2.4.0}/psengine/_sdk_id.py +0 -0
  14. {psengine-2.3.0 → psengine-2.4.0}/psengine/analyst_notes/__init__.py +0 -0
  15. {psengine-2.3.0 → psengine-2.4.0}/psengine/analyst_notes/constants.py +0 -0
  16. {psengine-2.3.0 → psengine-2.4.0}/psengine/analyst_notes/errors.py +0 -0
  17. {psengine-2.3.0 → psengine-2.4.0}/psengine/analyst_notes/helpers.py +0 -0
  18. {psengine-2.3.0 → psengine-2.4.0}/psengine/analyst_notes/markdown.py +0 -0
  19. {psengine-2.3.0 → psengine-2.4.0}/psengine/analyst_notes/models.py +0 -0
  20. {psengine-2.3.0 → psengine-2.4.0}/psengine/analyst_notes/note.py +0 -0
  21. {psengine-2.3.0 → psengine-2.4.0}/psengine/base_http_client.py +0 -0
  22. {psengine-2.3.0 → psengine-2.4.0}/psengine/classic_alerts/__init__.py +0 -0
  23. {psengine-2.3.0 → psengine-2.4.0}/psengine/classic_alerts/classic_alert.py +0 -0
  24. {psengine-2.3.0 → psengine-2.4.0}/psengine/classic_alerts/classic_alert_mgr.py +0 -0
  25. {psengine-2.3.0 → psengine-2.4.0}/psengine/classic_alerts/constants.py +0 -0
  26. {psengine-2.3.0 → psengine-2.4.0}/psengine/classic_alerts/errors.py +0 -0
  27. {psengine-2.3.0 → psengine-2.4.0}/psengine/classic_alerts/helpers.py +0 -0
  28. {psengine-2.3.0 → psengine-2.4.0}/psengine/classic_alerts/markdown/__init__.py +0 -0
  29. {psengine-2.3.0 → psengine-2.4.0}/psengine/classic_alerts/markdown/markdown.py +0 -0
  30. {psengine-2.3.0 → psengine-2.4.0}/psengine/classic_alerts/models.py +0 -0
  31. {psengine-2.3.0 → psengine-2.4.0}/psengine/collective_insights/__init__.py +0 -0
  32. {psengine-2.3.0 → psengine-2.4.0}/psengine/collective_insights/collective_insights.py +0 -0
  33. {psengine-2.3.0 → psengine-2.4.0}/psengine/collective_insights/constants.py +0 -0
  34. {psengine-2.3.0 → psengine-2.4.0}/psengine/collective_insights/errors.py +0 -0
  35. {psengine-2.3.0 → psengine-2.4.0}/psengine/collective_insights/insight.py +0 -0
  36. {psengine-2.3.0 → psengine-2.4.0}/psengine/collective_insights/models.py +0 -0
  37. {psengine-2.3.0 → psengine-2.4.0}/psengine/common_models.py +0 -0
  38. {psengine-2.3.0 → psengine-2.4.0}/psengine/config/__init__.py +0 -0
  39. {psengine-2.3.0 → psengine-2.4.0}/psengine/config/config.py +0 -0
  40. {psengine-2.3.0 → psengine-2.4.0}/psengine/config/errors.py +0 -0
  41. {psengine-2.3.0 → psengine-2.4.0}/psengine/constants.py +0 -0
  42. {psengine-2.3.0 → psengine-2.4.0}/psengine/detection/__init__.py +0 -0
  43. {psengine-2.3.0 → psengine-2.4.0}/psengine/detection/detection_mgr.py +0 -0
  44. {psengine-2.3.0 → psengine-2.4.0}/psengine/detection/detection_rule.py +0 -0
  45. {psengine-2.3.0 → psengine-2.4.0}/psengine/detection/errors.py +0 -0
  46. {psengine-2.3.0 → psengine-2.4.0}/psengine/detection/helpers.py +0 -0
  47. {psengine-2.3.0 → psengine-2.4.0}/psengine/detection/models.py +0 -0
  48. {psengine-2.3.0 → psengine-2.4.0}/psengine/endpoints.py +0 -0
  49. {psengine-2.3.0 → psengine-2.4.0}/psengine/enrich/__init__.py +0 -0
  50. {psengine-2.3.0 → psengine-2.4.0}/psengine/enrich/constants.py +0 -0
  51. {psengine-2.3.0 → psengine-2.4.0}/psengine/enrich/errors.py +0 -0
  52. {psengine-2.3.0 → psengine-2.4.0}/psengine/enrich/lookup_mgr.py +0 -0
  53. {psengine-2.3.0 → psengine-2.4.0}/psengine/enrich/models/__init__.py +0 -0
  54. {psengine-2.3.0 → psengine-2.4.0}/psengine/enrich/models/base_enriched_entity.py +0 -0
  55. {psengine-2.3.0 → psengine-2.4.0}/psengine/enrich/models/soar.py +0 -0
  56. {psengine-2.3.0 → psengine-2.4.0}/psengine/enrich/soar.py +0 -0
  57. {psengine-2.3.0 → psengine-2.4.0}/psengine/enrich/soar_mgr.py +0 -0
  58. {psengine-2.3.0 → psengine-2.4.0}/psengine/entity_lists/__init__.py +0 -0
  59. {psengine-2.3.0 → psengine-2.4.0}/psengine/entity_lists/constants.py +0 -0
  60. {psengine-2.3.0 → psengine-2.4.0}/psengine/entity_lists/entity_list.py +0 -0
  61. {psengine-2.3.0 → psengine-2.4.0}/psengine/entity_lists/entity_list_mgr.py +0 -0
  62. {psengine-2.3.0 → psengine-2.4.0}/psengine/entity_lists/errors.py +0 -0
  63. {psengine-2.3.0 → psengine-2.4.0}/psengine/entity_lists/models.py +0 -0
  64. {psengine-2.3.0 → psengine-2.4.0}/psengine/entity_match/__init__.py +0 -0
  65. {psengine-2.3.0 → psengine-2.4.0}/psengine/entity_match/errors.py +0 -0
  66. {psengine-2.3.0 → psengine-2.4.0}/psengine/entity_match/models.py +0 -0
  67. {psengine-2.3.0 → psengine-2.4.0}/psengine/errors.py +0 -0
  68. {psengine-2.3.0 → psengine-2.4.0}/psengine/fusion/__init__.py +0 -0
  69. {psengine-2.3.0 → psengine-2.4.0}/psengine/fusion/errors.py +0 -0
  70. {psengine-2.3.0 → psengine-2.4.0}/psengine/fusion/fusion_mgr.py +0 -0
  71. {psengine-2.3.0 → psengine-2.4.0}/psengine/fusion/models.py +0 -0
  72. {psengine-2.3.0 → psengine-2.4.0}/psengine/helpers/__init__.py +0 -0
  73. {psengine-2.3.0 → psengine-2.4.0}/psengine/helpers/helpers.py +0 -0
  74. {psengine-2.3.0 → psengine-2.4.0}/psengine/identity/__init__.py +0 -0
  75. {psengine-2.3.0 → psengine-2.4.0}/psengine/identity/constants.py +0 -0
  76. {psengine-2.3.0 → psengine-2.4.0}/psengine/identity/errors.py +0 -0
  77. {psengine-2.3.0 → psengine-2.4.0}/psengine/identity/identity.py +0 -0
  78. {psengine-2.3.0 → psengine-2.4.0}/psengine/identity/identity_mgr.py +0 -0
  79. {psengine-2.3.0 → psengine-2.4.0}/psengine/identity/models/__init__.py +0 -0
  80. {psengine-2.3.0 → psengine-2.4.0}/psengine/identity/models/common_models.py +0 -0
  81. {psengine-2.3.0 → psengine-2.4.0}/psengine/identity/models/detections.py +0 -0
  82. {psengine-2.3.0 → psengine-2.4.0}/psengine/identity/models/incident_report.py +0 -0
  83. {psengine-2.3.0 → psengine-2.4.0}/psengine/identity/models/lookup.py +0 -0
  84. {psengine-2.3.0 → psengine-2.4.0}/psengine/logger/__init__.py +0 -0
  85. {psengine-2.3.0 → psengine-2.4.0}/psengine/logger/constants.py +0 -0
  86. {psengine-2.3.0 → psengine-2.4.0}/psengine/logger/errors.py +0 -0
  87. {psengine-2.3.0 → psengine-2.4.0}/psengine/logger/rf_logger.py +0 -0
  88. {psengine-2.3.0 → psengine-2.4.0}/psengine/malware_intel/__init__.py +0 -0
  89. {psengine-2.3.0 → psengine-2.4.0}/psengine/malware_intel/errors.py +0 -0
  90. {psengine-2.3.0 → psengine-2.4.0}/psengine/malware_intel/malware_intel.py +0 -0
  91. {psengine-2.3.0 → psengine-2.4.0}/psengine/malware_intel/malware_intel_mgr.py +0 -0
  92. {psengine-2.3.0 → psengine-2.4.0}/psengine/malware_intel/models.py +0 -0
  93. {psengine-2.3.0 → psengine-2.4.0}/psengine/markdown/__init__.py +0 -0
  94. {psengine-2.3.0 → psengine-2.4.0}/psengine/markdown/markdown.py +0 -0
  95. {psengine-2.3.0 → psengine-2.4.0}/psengine/markdown/models.py +0 -0
  96. {psengine-2.3.0 → psengine-2.4.0}/psengine/playbook_alerts/__init__.py +0 -0
  97. {psengine-2.3.0 → psengine-2.4.0}/psengine/playbook_alerts/constants.py +0 -0
  98. {psengine-2.3.0 → psengine-2.4.0}/psengine/playbook_alerts/errors.py +0 -0
  99. {psengine-2.3.0 → psengine-2.4.0}/psengine/playbook_alerts/helpers.py +0 -0
  100. {psengine-2.3.0 → psengine-2.4.0}/psengine/playbook_alerts/mappings.py +0 -0
  101. {psengine-2.3.0 → psengine-2.4.0}/psengine/playbook_alerts/markdown/__init__.py +0 -0
  102. {psengine-2.3.0 → psengine-2.4.0}/psengine/playbook_alerts/markdown/markdown.py +0 -0
  103. {psengine-2.3.0 → psengine-2.4.0}/psengine/playbook_alerts/markdown/markdown_code_repo.py +0 -0
  104. {psengine-2.3.0 → psengine-2.4.0}/psengine/playbook_alerts/markdown/markdown_cyber_vulnerability.py +0 -0
  105. {psengine-2.3.0 → psengine-2.4.0}/psengine/playbook_alerts/markdown/markdown_domain_abuse.py +0 -0
  106. {psengine-2.3.0 → psengine-2.4.0}/psengine/playbook_alerts/markdown/markdown_geopolitics_facility.py +0 -0
  107. {psengine-2.3.0 → psengine-2.4.0}/psengine/playbook_alerts/markdown/markdown_identity_exposure.py +0 -0
  108. {psengine-2.3.0 → psengine-2.4.0}/psengine/playbook_alerts/markdown/markdown_malware_report.py +0 -0
  109. {psengine-2.3.0 → psengine-2.4.0}/psengine/playbook_alerts/markdown/markdown_third_party_risk.py +0 -0
  110. {psengine-2.3.0 → psengine-2.4.0}/psengine/playbook_alerts/models/__init__.py +0 -0
  111. {psengine-2.3.0 → psengine-2.4.0}/psengine/playbook_alerts/models/common_models.py +0 -0
  112. {psengine-2.3.0 → psengine-2.4.0}/psengine/playbook_alerts/models/panel_log.py +0 -0
  113. {psengine-2.3.0 → psengine-2.4.0}/psengine/playbook_alerts/models/panel_status.py +0 -0
  114. {psengine-2.3.0 → psengine-2.4.0}/psengine/playbook_alerts/models/pba_code_repo_leak.py +0 -0
  115. {psengine-2.3.0 → psengine-2.4.0}/psengine/playbook_alerts/models/pba_cyber_vulnerability.py +0 -0
  116. {psengine-2.3.0 → psengine-2.4.0}/psengine/playbook_alerts/models/pba_domain_abuse.py +0 -0
  117. {psengine-2.3.0 → psengine-2.4.0}/psengine/playbook_alerts/models/pba_geopolitics_facility.py +0 -0
  118. {psengine-2.3.0 → psengine-2.4.0}/psengine/playbook_alerts/models/pba_identity_exposures.py +0 -0
  119. {psengine-2.3.0 → psengine-2.4.0}/psengine/playbook_alerts/models/pba_malware_report.py +0 -0
  120. {psengine-2.3.0 → psengine-2.4.0}/psengine/playbook_alerts/models/pba_third_party_risk.py +0 -0
  121. {psengine-2.3.0 → psengine-2.4.0}/psengine/playbook_alerts/models/search_endpoint.py +0 -0
  122. {psengine-2.3.0 → psengine-2.4.0}/psengine/playbook_alerts/pa_category.py +0 -0
  123. {psengine-2.3.0 → psengine-2.4.0}/psengine/playbook_alerts/playbook_alert_mgr.py +0 -0
  124. {psengine-2.3.0 → psengine-2.4.0}/psengine/playbook_alerts/playbook_alerts.py +0 -0
  125. {psengine-2.3.0 → psengine-2.4.0}/psengine/py.typed +0 -0
  126. {psengine-2.3.0 → psengine-2.4.0}/psengine/rf_client.py +0 -0
  127. {psengine-2.3.0 → psengine-2.4.0}/psengine/risk_history/__init__.py +0 -0
  128. {psengine-2.3.0 → psengine-2.4.0}/psengine/risk_history/errors.py +0 -0
  129. {psengine-2.3.0 → psengine-2.4.0}/psengine/risk_history/models.py +0 -0
  130. {psengine-2.3.0 → psengine-2.4.0}/psengine/risk_history/risk_history_mgr.py +0 -0
  131. {psengine-2.3.0 → psengine-2.4.0}/psengine/risklists/__init__.py +0 -0
  132. {psengine-2.3.0 → psengine-2.4.0}/psengine/risklists/constants.py +0 -0
  133. {psengine-2.3.0 → psengine-2.4.0}/psengine/risklists/errors.py +0 -0
  134. {psengine-2.3.0 → psengine-2.4.0}/psengine/risklists/models.py +0 -0
  135. {psengine-2.3.0 → psengine-2.4.0}/psengine/risklists/risklist_mgr.py +0 -0
  136. {psengine-2.3.0 → psengine-2.4.0}/psengine/stix2/__init__.py +0 -0
  137. {psengine-2.3.0 → psengine-2.4.0}/psengine/stix2/base_stix_entity.py +0 -0
  138. {psengine-2.3.0 → psengine-2.4.0}/psengine/stix2/complex_entity.py +0 -0
  139. {psengine-2.3.0 → psengine-2.4.0}/psengine/stix2/constants.py +0 -0
  140. {psengine-2.3.0 → psengine-2.4.0}/psengine/stix2/enriched_indicator.py +0 -0
  141. {psengine-2.3.0 → psengine-2.4.0}/psengine/stix2/errors.py +0 -0
  142. {psengine-2.3.0 → psengine-2.4.0}/psengine/stix2/helpers.py +0 -0
  143. {psengine-2.3.0 → psengine-2.4.0}/psengine/stix2/rf_bundle.py +0 -0
  144. {psengine-2.3.0 → psengine-2.4.0}/psengine/stix2/simple_entity.py +0 -0
  145. {psengine-2.3.0 → psengine-2.4.0}/psengine/stix2/util.py +0 -0
  146. {psengine-2.3.0 → psengine-2.4.0}/psengine.egg-info/SOURCES.txt +0 -0
  147. {psengine-2.3.0 → psengine-2.4.0}/psengine.egg-info/dependency_links.txt +0 -0
  148. {psengine-2.3.0 → psengine-2.4.0}/psengine.egg-info/requires.txt +0 -0
  149. {psengine-2.3.0 → psengine-2.4.0}/psengine.egg-info/top_level.txt +0 -0
  150. {psengine-2.3.0 → psengine-2.4.0}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: psengine
3
- Version: 2.3.0
3
+ Version: 2.4.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
@@ -11,4 +11,4 @@
11
11
  # accessed from any third party API. #
12
12
  ##############################################################################################
13
13
 
14
- __version__ = '2.3.0'
14
+ __version__ = '2.4.0'
@@ -111,7 +111,7 @@ class AnalystNoteMgr:
111
111
  'label': label,
112
112
  'source': source,
113
113
  'serialization': serialization,
114
- 'taggedText': tagged_text,
114
+ 'tagged_text': tagged_text,
115
115
  'limit': min(max_results, notes_per_page),
116
116
  }
117
117
  data = {key: val for key, val in data.items() if val is not None}
@@ -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,
@@ -32,6 +33,7 @@ from .models.lookup import (
32
33
  RawRisk,
33
34
  RiskMapping,
34
35
  RiskyCIDRPIP,
36
+ Scanner,
35
37
  )
36
38
 
37
39
 
@@ -46,6 +48,7 @@ class EnrichedIP(BaseEnrichedEntity):
46
48
  dns_port_cert: Optional[DnsPortCert] = Field(alias='dnsPortCert', default=None)
47
49
  location: Optional[IPLocation] = None
48
50
  risky_cidr_ips: Optional[list[RiskyCIDRPIP]] = Field(alias='riskyCIDRIPs', default=None)
51
+ scanner: Optional[Scanner] = None
49
52
 
50
53
 
51
54
  class EnrichedDomain(BaseEnrichedEntity):
@@ -103,6 +106,7 @@ class EnrichedVulnerability(BaseEnrichedEntity):
103
106
  cvss: Optional[CVSS] = None
104
107
  cvss_ratings: list[CVSSRating] = Field(alias='cvssRatings', default=None)
105
108
  cvssv3: Optional[CVSSV3] = None
109
+ cvssv4: Optional[CVSSV4] = None
106
110
  nvd_description: Optional[str] = Field(alias='nvdDescription', default=None)
107
111
  nvd_references: Optional[list[NvdReference]] = Field(alias='nvdReferences', default=None)
108
112
  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
  ###########################################################
@@ -260,6 +304,45 @@ class DnsPortCert(RFBaseModel):
260
304
  ports: Optional[list[Port]] = None
261
305
 
262
306
 
307
+ ###########################################################
308
+ # Scanner
309
+ ###########################################################
310
+ class Tag(RFBaseModel):
311
+ verdict_details: Optional[list[str]] = Field(default=None, alias='verdictDetails')
312
+ entity: list[IdNameType]
313
+
314
+
315
+ class Ports(RFBaseModel):
316
+ tcp: list[int]
317
+
318
+
319
+ class Evidence(RFBaseModel):
320
+ name: str = Field(alias='Name')
321
+ mitigation_string: str = Field(default=None, alias='MitigationString')
322
+ evidence_string: str = Field(alias='EvidenceString', default=None)
323
+ rule: str = Field(alias='Rule')
324
+ criticality: float = Field(alias='Criticality')
325
+ timestamp: datetime = Field(alias='Timestamp')
326
+ criticality_label: str = Field(alias='CriticalityLabel')
327
+ sources_count: float = Field(alias='SourcesCount')
328
+ sightings_count: float = Field(alias='SightingsCount')
329
+ sources: list[str] = Field(alias='Sources')
330
+
331
+
332
+ class Scanner(RFBaseModel):
333
+ last_seen: str = Field(alias='lastSeen')
334
+ tags: Tag
335
+ verdict: str
336
+ scanned_ip_countries: list[str] = Field(alias='scannedIpCountries')
337
+ rdns: list[str]
338
+ scanner_country: str = Field(alias='scannerCountry')
339
+ ports: Ports
340
+ global_scanner: bool = Field(alias='globalScanner')
341
+ user_agents: list[str] = Field(alias='userAgents', default=None)
342
+ web_requests: list[str] = Field(alias='webRequests', default=None)
343
+ evidence: Optional[list[Evidence]] = []
344
+
345
+
263
346
  ###########################################################
264
347
  # NVD
265
348
  ###########################################################
@@ -68,6 +68,13 @@ class ResolvedEntity(RFBaseModel):
68
68
 
69
69
  This class supports string representation of `ResolvedEntity` instances.
70
70
 
71
+ Hashing:
72
+ Returns a hash value based on the entity `id_` if found, else the hash of the name.
73
+
74
+ Equality:
75
+ Checks equality between two `ResolvedEntity` instances based on the `id_` if the entity was
76
+ found else the name.
77
+
71
78
  String Representation:
72
79
  Returns a string representation of the `ResolvedEntity` instance including the
73
80
  entity match name, type, and ID.
@@ -91,3 +98,13 @@ class ResolvedEntity(RFBaseModel):
91
98
  if isinstance(self.content, IdNameType):
92
99
  return f'Entity: {self.entity}, Type: {self.content.type_}, ID: {self.content.id_}'
93
100
  return f'Entity: {self.entity}, {self.content}'
101
+
102
+ def __hash__(self):
103
+ if hasattr(self.content, 'id_'):
104
+ return hash(self.content.id_)
105
+ return hash(self.entity)
106
+
107
+ def __eq__(self, other: 'ResolvedEntity'):
108
+ if hasattr(self.content, 'id_') and hasattr(other.content, 'id_'):
109
+ return self.content.id_ == other.content.id_
110
+ return self.entity == other.entity
@@ -48,7 +48,7 @@ class EntityMatchMgr:
48
48
  Optional[Union[list, str]], Doc('Type or list of types of the entity, if known.')
49
49
  ] = None,
50
50
  limit: Annotated[int, Doc('Maximum number of matches to return.')] = DEFAULT_LIMIT,
51
- ) -> Annotated[list[ResolvedEntity], Doc('List of resolved entity matches.')]:
51
+ ) -> Annotated[list[ResolvedEntity], Doc('List of deduplicated resolved entity matches.')]:
52
52
  """Match a text string using the entity match API.
53
53
 
54
54
  Endpoint:
@@ -65,7 +65,7 @@ class EntityMatchMgr:
65
65
  response = self.rf_client.request('post', EP_ENTITY_MATCH, data=request_body.json())
66
66
  response = [IdNameType.model_validate(d) for d in response.json()]
67
67
  return (
68
- [ResolvedEntity(entity=d.name, is_found=bool(d.id_), content=d) for d in response]
68
+ list({ResolvedEntity(entity=d.name, is_found=bool(d.id_), content=d) for d in response})
69
69
  if response
70
70
  else [ResolvedEntity(entity=entity_name, is_found=False, content='Entity ID not found')]
71
71
  )
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: psengine
3
- Version: 2.3.0
3
+ Version: 2.4.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
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "psengine"
3
- version = "2.3.0"
3
+ version = "2.4.0"
4
4
  readme = "README.md"
5
5
  license = "MIT"
6
6
  requires-python = ">=3.9, <3.14"
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes