psengine 2.0.4__py3-none-any.whl

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 (115) hide show
  1. psengine/__init__.py +22 -0
  2. psengine/_sdk_id.py +16 -0
  3. psengine/_version.py +14 -0
  4. psengine/analyst_notes/__init__.py +32 -0
  5. psengine/analyst_notes/constants.py +15 -0
  6. psengine/analyst_notes/errors.py +42 -0
  7. psengine/analyst_notes/helpers.py +90 -0
  8. psengine/analyst_notes/models.py +219 -0
  9. psengine/analyst_notes/note.py +149 -0
  10. psengine/analyst_notes/note_mgr.py +400 -0
  11. psengine/base_http_client.py +285 -0
  12. psengine/classic_alerts/__init__.py +24 -0
  13. psengine/classic_alerts/classic_alert.py +275 -0
  14. psengine/classic_alerts/classic_alert_mgr.py +507 -0
  15. psengine/classic_alerts/constants.py +31 -0
  16. psengine/classic_alerts/errors.py +38 -0
  17. psengine/classic_alerts/helpers.py +87 -0
  18. psengine/classic_alerts/markdown/__init__.py +13 -0
  19. psengine/classic_alerts/markdown/markdown.py +359 -0
  20. psengine/classic_alerts/models.py +141 -0
  21. psengine/collective_insights/__init__.py +29 -0
  22. psengine/collective_insights/collective_insights.py +164 -0
  23. psengine/collective_insights/constants.py +44 -0
  24. psengine/collective_insights/errors.py +18 -0
  25. psengine/collective_insights/insight.py +89 -0
  26. psengine/collective_insights/models.py +81 -0
  27. psengine/common_models.py +89 -0
  28. psengine/config/__init__.py +15 -0
  29. psengine/config/config.py +284 -0
  30. psengine/config/errors.py +18 -0
  31. psengine/constants.py +63 -0
  32. psengine/detection/__init__.py +17 -0
  33. psengine/detection/detection_mgr.py +135 -0
  34. psengine/detection/detection_rule.py +85 -0
  35. psengine/detection/errors.py +26 -0
  36. psengine/detection/helpers.py +56 -0
  37. psengine/detection/models.py +47 -0
  38. psengine/endpoints.py +98 -0
  39. psengine/enrich/__init__.py +28 -0
  40. psengine/enrich/constants.py +73 -0
  41. psengine/enrich/errors.py +26 -0
  42. psengine/enrich/lookup.py +299 -0
  43. psengine/enrich/lookup_mgr.py +341 -0
  44. psengine/enrich/models/__init__.py +13 -0
  45. psengine/enrich/models/base_enriched_entity.py +43 -0
  46. psengine/enrich/models/lookup.py +271 -0
  47. psengine/enrich/models/soar.py +138 -0
  48. psengine/enrich/soar.py +89 -0
  49. psengine/enrich/soar_mgr.py +176 -0
  50. psengine/entity_lists/__init__.py +16 -0
  51. psengine/entity_lists/constants.py +19 -0
  52. psengine/entity_lists/entity_list.py +435 -0
  53. psengine/entity_lists/entity_list_mgr.py +185 -0
  54. psengine/entity_lists/errors.py +26 -0
  55. psengine/entity_lists/models.py +87 -0
  56. psengine/entity_match/__init__.py +16 -0
  57. psengine/entity_match/entity_match.py +90 -0
  58. psengine/entity_match/entity_match_mgr.py +235 -0
  59. psengine/entity_match/errors.py +18 -0
  60. psengine/entity_match/models.py +22 -0
  61. psengine/errors.py +41 -0
  62. psengine/helpers/__init__.py +23 -0
  63. psengine/helpers/helpers.py +471 -0
  64. psengine/logger/__init__.py +15 -0
  65. psengine/logger/constants.py +39 -0
  66. psengine/logger/errors.py +18 -0
  67. psengine/logger/rf_logger.py +148 -0
  68. psengine/markdown/__init__.py +21 -0
  69. psengine/markdown/markdown.py +169 -0
  70. psengine/markdown/models.py +22 -0
  71. psengine/playbook_alerts/__init__.py +34 -0
  72. psengine/playbook_alerts/constants.py +35 -0
  73. psengine/playbook_alerts/errors.py +35 -0
  74. psengine/playbook_alerts/helpers.py +80 -0
  75. psengine/playbook_alerts/mappings.py +44 -0
  76. psengine/playbook_alerts/markdown/__init__.py +13 -0
  77. psengine/playbook_alerts/markdown/markdown.py +98 -0
  78. psengine/playbook_alerts/markdown/markdown_code_repo.py +64 -0
  79. psengine/playbook_alerts/markdown/markdown_domain_abuse.py +118 -0
  80. psengine/playbook_alerts/markdown/markdown_identity_exposure.py +158 -0
  81. psengine/playbook_alerts/models/__init__.py +36 -0
  82. psengine/playbook_alerts/models/common_models.py +18 -0
  83. psengine/playbook_alerts/models/panel_log.py +329 -0
  84. psengine/playbook_alerts/models/panel_status.py +70 -0
  85. psengine/playbook_alerts/models/pba_code_repo_leak.py +52 -0
  86. psengine/playbook_alerts/models/pba_cyber_vulnerability.py +53 -0
  87. psengine/playbook_alerts/models/pba_domain_abuse.py +139 -0
  88. psengine/playbook_alerts/models/pba_identity_exposures.py +93 -0
  89. psengine/playbook_alerts/models/pba_third_party_risk.py +103 -0
  90. psengine/playbook_alerts/models/search_endpoint.py +68 -0
  91. psengine/playbook_alerts/pa_category.py +37 -0
  92. psengine/playbook_alerts/playbook_alert_mgr.py +593 -0
  93. psengine/playbook_alerts/playbook_alerts.py +393 -0
  94. psengine/rf_client.py +430 -0
  95. psengine/risklists/__init__.py +17 -0
  96. psengine/risklists/constants.py +15 -0
  97. psengine/risklists/errors.py +20 -0
  98. psengine/risklists/models.py +65 -0
  99. psengine/risklists/risklist_mgr.py +156 -0
  100. psengine/stix2/__init__.py +21 -0
  101. psengine/stix2/base_stix_entity.py +62 -0
  102. psengine/stix2/complex_entity.py +372 -0
  103. psengine/stix2/constants.py +81 -0
  104. psengine/stix2/enriched_indicator.py +261 -0
  105. psengine/stix2/errors.py +22 -0
  106. psengine/stix2/helpers.py +68 -0
  107. psengine/stix2/rf_bundle.py +240 -0
  108. psengine/stix2/simple_entity.py +145 -0
  109. psengine/stix2/util.py +53 -0
  110. psengine-2.0.4.dist-info/METADATA +189 -0
  111. psengine-2.0.4.dist-info/RECORD +115 -0
  112. psengine-2.0.4.dist-info/WHEEL +5 -0
  113. psengine-2.0.4.dist-info/entry_points.txt +2 -0
  114. psengine-2.0.4.dist-info/licenses/LICENSE +21 -0
  115. psengine-2.0.4.dist-info/top_level.txt +1 -0
@@ -0,0 +1,393 @@
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 Optional
16
+
17
+ from pydantic import Field, NonNegativeInt, PositiveInt, model_validator
18
+
19
+ from ..common_models import RFBaseModel
20
+ from ..constants import DEFAULT_LIMIT, TIMESTAMP_STR
21
+ from ..playbook_alerts.markdown.markdown import _markdown_playbook_alert
22
+ from .models import (
23
+ CodeRepoEvidencePanel,
24
+ CodeRepoPanelStatus,
25
+ CyberVulnerabilityEvidencePanel,
26
+ CyberVulnerabilityPanelStatus,
27
+ DatetimeRange,
28
+ DomainAbuseEvidenceDns,
29
+ DomainAbuseEvidenceSummary,
30
+ DomainAbuseEvidenceWhois,
31
+ DomainAbusePanelStatus,
32
+ IdentityEvidencePanel,
33
+ IdentityPanelStatus,
34
+ TPREvidencePanel,
35
+ TPRPanelStatus,
36
+ )
37
+ from .models.panel_log import (
38
+ CodeRepoLeakageEvidenceChange,
39
+ DomainAbuseDnsChange,
40
+ DomainAbuseLogoTypeChange,
41
+ DomainAbuseMaliciousDnsChange,
42
+ DomainAbuseMaliciousUrlChange,
43
+ DomainAbuseReregistrationRecordChange,
44
+ DomainAbuseScreenshotMentions,
45
+ DomainAbuseWhoisChange,
46
+ PanelLogV2,
47
+ ThirdPartyAssessmentChange,
48
+ VulnerabilityLifecycleChange,
49
+ )
50
+ from .models.panel_status import PanelAction, PanelStatus
51
+ from .pa_category import PACategory
52
+
53
+
54
+ @total_ordering
55
+ class PBA_Generic(RFBaseModel):
56
+ """Base Model for Playbook Alerts. Removes the deprecated panel_log.
57
+ This model is intended to be inherited and should not be used on its own.
58
+
59
+ Methods:
60
+ __hash__:
61
+ Returns hash value based on ``playbook_alert_id`` and updated timestamp in panel status.
62
+
63
+ __eq__:
64
+ Checks equality between two BasePlaybookAlert instances based on ``playbook_alert_id``
65
+ and updated timestamp in panel status.
66
+
67
+ __gt__:
68
+ Defines a greater-than comparison between two ``BasePlaybookAlert`` instances based on
69
+ ``playbook_alert_id`` and updated timestamp in panel status.
70
+
71
+ __str__:
72
+ Returns a string representation of the BasePlaybookAlert instance with:
73
+ ``playbook_alert_id``, updated timestamp, case rule label, and status.
74
+
75
+ .. code-block:: python
76
+
77
+ >>> print(playbook_alert)
78
+ Playbook Alert ID: task:a1ccb1c8-5554-42af, Updated: 2024-05-21 10:42:30AM,
79
+ Category: Third Party Risk, Lookup Status: New
80
+
81
+ Total Ordering:
82
+ The ordering of BasePlaybookAlert instances is determined primarily by the updated timestamp
83
+ of the panel status. If two instances have the same updated timestamp, their
84
+ ``playbook_alert_id`` is used as a secondary criterion for ordering.
85
+ """
86
+
87
+ playbook_alert_id: str
88
+ panel_log_v2: Optional[list[PanelLogV2]] = []
89
+ panel_status: Optional[PanelStatus] = Field(default_factory=PanelStatus)
90
+
91
+ category: str = PACategory.UNMAPPED_ALERT.value
92
+
93
+ @model_validator(mode='before')
94
+ @classmethod
95
+ def remove_panel_log(cls, data):
96
+ """Remove panel_log since it is deprecated."""
97
+ if 'panel_log' in data:
98
+ del data['panel_log']
99
+ return data
100
+
101
+ def __hash__(self):
102
+ return hash((self.playbook_alert_id, self.panel_status.updated))
103
+
104
+ def __eq__(self, other: 'PBA_Generic'):
105
+ return (self.playbook_alert_id, self.panel_status.updated) == (
106
+ other.playbook_alert_id,
107
+ other.panel_status.updated,
108
+ )
109
+
110
+ def __gt__(self, other: 'PBA_Generic'):
111
+ return (self.panel_status.updated, self.playbook_alert_id) > (
112
+ other.panel_status.updated,
113
+ other.playbook_alert_id,
114
+ )
115
+
116
+ def __str__(self):
117
+ return (
118
+ f'Playbook Alert ID: {self.playbook_alert_id}, '
119
+ f'Updated: {self.panel_status.updated.strftime(TIMESTAMP_STR)}, '
120
+ f'Category: {self.panel_status.case_rule_label}, '
121
+ f'Lookup Status: {self.panel_status.status}'
122
+ )
123
+
124
+ def markdown(
125
+ self,
126
+ html_tags: bool = True,
127
+ character_limit: Optional[int] = None,
128
+ defang_iocs: bool = False,
129
+ ):
130
+ """Markdown implementation for Playbook Alerts."""
131
+ return _markdown_playbook_alert(
132
+ self, html_tags=html_tags, character_limit=character_limit, defang_iocs=defang_iocs
133
+ )
134
+
135
+ def _get_changes(self, change_type):
136
+ """Filter for a specific change type from the v2 panel log."""
137
+ changes = [obj.changes for obj in self.panel_log_v2]
138
+ changes = [x for y in changes for x in y]
139
+ return list(filter(lambda x: isinstance(x, change_type), changes))
140
+
141
+
142
+ class PBA_CodeRepoLeakage(PBA_Generic):
143
+ """Model for Code Repo Leakage. Inherit behaviours from BasePlaybookAlert."""
144
+
145
+ __doc__ = __doc__ + '\n\n' + PBA_Generic.__doc__ # noqa: A003
146
+
147
+ category: str = PACategory.CODE_REPO_LEAKAGE.value
148
+
149
+ panel_status: Optional[CodeRepoPanelStatus] = Field(default_factory=CodeRepoPanelStatus)
150
+ panel_evidence_summary: Optional[CodeRepoEvidencePanel] = Field(
151
+ default_factory=CodeRepoEvidencePanel
152
+ )
153
+
154
+ @property
155
+ def log_code_repo_leakage_evidence_changes(self) -> list:
156
+ """Code Repo Leakage Evidence change."""
157
+ return self._get_changes(CodeRepoLeakageEvidenceChange)
158
+
159
+
160
+ class PBA_ThirdPartyRisk(PBA_Generic):
161
+ """Model for Third Party Risk. Inherit behaviours from BasePlaybookAlert."""
162
+
163
+ __doc__ = __doc__ + '\n\n' + PBA_Generic.__doc__ # noqa: A003
164
+
165
+ category: str = PACategory.THIRD_PARTY_RISK.value
166
+
167
+ panel_status: Optional[TPRPanelStatus] = Field(default_factory=TPRPanelStatus)
168
+ panel_evidence_summary: Optional[TPREvidencePanel] = Field(default_factory=TPREvidencePanel)
169
+
170
+ @property
171
+ def log_third_party_assessment_changes(self) -> list:
172
+ """Third Party Assessment change."""
173
+ return self._get_changes(ThirdPartyAssessmentChange)
174
+
175
+
176
+ class PBA_CyberVulnerability(PBA_Generic):
177
+ """Model for Cyber Vulnerability. Inherit behaviours from BasePlaybookAlert."""
178
+
179
+ __doc__ = __doc__ + '\n\n' + PBA_Generic.__doc__ # noqa: A003
180
+
181
+ category: str = PACategory.CYBER_VULNERABILITY.value
182
+
183
+ panel_status: Optional[CyberVulnerabilityPanelStatus] = Field(
184
+ default_factory=CyberVulnerabilityPanelStatus
185
+ )
186
+ panel_evidence_summary: Optional[CyberVulnerabilityEvidencePanel] = Field(
187
+ default_factory=CyberVulnerabilityEvidencePanel
188
+ )
189
+
190
+ @property
191
+ def lifecycle_stage(self) -> str:
192
+ """Get playbook alert lifecycle_stage."""
193
+ if stage := self.panel_status.lifecycle_stage:
194
+ return stage
195
+ return self.panel_evidence_summary.summary.lifecycle_stage
196
+
197
+ @property
198
+ def log_vulnerability_lifecycle_changes(self) -> list:
199
+ """Get ``VulnerabilityLifecycleChange`` log changes."""
200
+ return self._get_changes(VulnerabilityLifecycleChange)
201
+
202
+
203
+ class PBA_IdentityNovelExposure(PBA_Generic):
204
+ """Model for Identity Exposure. Inherit behaviours from BasePlaybookAlert."""
205
+
206
+ __doc__ = __doc__ + '\n\n' + PBA_Generic.__doc__ # noqa: A003
207
+
208
+ category: str = PACategory.IDENTITY_NOVEL_EXPOSURES.value
209
+
210
+ panel_status: Optional[IdentityPanelStatus] = Field(default_factory=IdentityPanelStatus)
211
+ panel_evidence_summary: Optional[IdentityEvidencePanel] = Field(
212
+ default_factory=IdentityEvidencePanel
213
+ )
214
+
215
+ @property
216
+ def assessment_names(self) -> list[str]:
217
+ """Assessments contain name and criticality, this returns all assessment names.
218
+
219
+ Returns:
220
+ List[str]: assessments names or [] if not found
221
+ """
222
+ if not self.panel_evidence_summary or not self.panel_evidence_summary.assessments:
223
+ return []
224
+ return [assessment.name for assessment in self.panel_evidence_summary.assessments]
225
+
226
+ @property
227
+ def technology_names(self) -> list[str]:
228
+ """Novel Identity Exposure: Return the technologies names list.
229
+
230
+ Returns:
231
+ list[str]: List of technologies names
232
+ """
233
+ if not self.panel_evidence_summary or not self.panel_evidence_summary.technologies:
234
+ return []
235
+ return [tech.name for tech in self.panel_evidence_summary.technologies]
236
+
237
+
238
+ class PBA_DomainAbuse(PBA_Generic):
239
+ """Model for Domain Abuse. Inherit behaviours from BasePlaybookAlert."""
240
+
241
+ __doc__ = __doc__ + '\n\n' + PBA_Generic.__doc__ # noqa: A003
242
+
243
+ _images: Optional[dict] = {}
244
+
245
+ category: str = PACategory.DOMAIN_ABUSE.value
246
+
247
+ panel_action: Optional[list[PanelAction]] = []
248
+ panel_status: Optional[DomainAbusePanelStatus] = Field(default_factory=DomainAbusePanelStatus)
249
+ panel_evidence_summary: Optional[DomainAbuseEvidenceSummary] = Field(
250
+ default_factory=DomainAbuseEvidenceSummary
251
+ )
252
+ panel_evidence_dns: Optional[DomainAbuseEvidenceDns] = Field(
253
+ default_factory=DomainAbuseEvidenceDns
254
+ )
255
+ panel_evidence_whois: Optional[DomainAbuseEvidenceWhois] = Field(
256
+ default_factory=DomainAbuseEvidenceWhois
257
+ )
258
+
259
+ def store_image(self, image_id: str, image_bytes: bytes) -> None:
260
+ """Domain Abuse: store image bytes in ``self._images`` dictionary.
261
+
262
+ Args:
263
+ image_id (str): image id
264
+ image_bytes (bytes): image bytes
265
+
266
+ Raises:
267
+ ValueError: if the image_id is not present in alert screenshots list
268
+ """
269
+ image_id_matches = list(
270
+ filter(
271
+ lambda x: x.image_id == image_id,
272
+ self.panel_evidence_summary.screenshots,
273
+ )
274
+ )
275
+ if len(image_id_matches) == 0:
276
+ raise ValueError(
277
+ f"Alert '{self.playbook_alert_id}' does not contain image id: '{image_id}'"
278
+ )
279
+ image_info = image_id_matches[0]
280
+ self._images[image_id] = {}
281
+ self._images[image_id]['description'] = image_info.description
282
+ self._images[image_id]['created'] = image_info.created
283
+ self._images[image_id]['image_bytes'] = image_bytes
284
+
285
+ @property
286
+ def image_ids(self) -> list:
287
+ """Domain Abuse: get the playbook alert image ids.
288
+
289
+ Returns:
290
+ list: Alert image ids or empty list if not found
291
+ """
292
+ ids = []
293
+ if self.panel_evidence_summary.screenshots:
294
+ ids = [screenshot.image_id for screenshot in self.panel_evidence_summary.screenshots]
295
+ return ids
296
+
297
+ @property
298
+ def images(self) -> dict:
299
+ """Domain Abuse: get raw bytes of the screenshots.
300
+
301
+ This data is stored in the following format:
302
+
303
+ .. code-block::
304
+
305
+ {
306
+ image_id : {
307
+ 'description': "awesome image description",
308
+ 'created': "date",
309
+ 'image_bytes': b'xyz'
310
+ }
311
+ }
312
+
313
+ Returns:
314
+ dict: Alert images raw bytes or {} if not found
315
+ """
316
+ return self._images
317
+
318
+ @property
319
+ def log_dns_changes(self) -> list:
320
+ """DNS change."""
321
+ return self._get_changes(DomainAbuseDnsChange)
322
+
323
+ @property
324
+ def log_whois_changes(self) -> list:
325
+ """WHOIS change."""
326
+ return self._get_changes(DomainAbuseWhoisChange)
327
+
328
+ @property
329
+ def log_logotype_changes(self) -> list:
330
+ """Logotype change."""
331
+ return self._get_changes(DomainAbuseLogoTypeChange)
332
+
333
+ @property
334
+ def log_malicious_dns_changes(self) -> list:
335
+ """Malaicious DNS change."""
336
+ return self._get_changes(DomainAbuseMaliciousDnsChange)
337
+
338
+ @property
339
+ def log_reregistration_changes(self) -> list:
340
+ """Reregistration change."""
341
+ return self._get_changes(DomainAbuseReregistrationRecordChange)
342
+
343
+ @property
344
+ def log_malicious_url_changes(self) -> list:
345
+ """Malicious URL change."""
346
+ return self._get_changes(DomainAbuseMaliciousUrlChange)
347
+
348
+ @property
349
+ def log_screenshot_mentions_changes(self) -> list:
350
+ """Screenshot mentions change."""
351
+ return self._get_changes(DomainAbuseScreenshotMentions)
352
+
353
+
354
+ class SearchIn(RFBaseModel):
355
+ """Model for payload sent to ``/search`` endpoint."""
356
+
357
+ from_: Optional[NonNegativeInt] = Field(alias='from', default=None)
358
+ limit: Optional[PositiveInt] = DEFAULT_LIMIT
359
+ order_by: Optional[str] = None
360
+ direction: Optional[str] = None
361
+ entity: Optional[list] = None
362
+ statuses: Optional[list[str]] = None
363
+ priority: Optional[list[str]] = None
364
+ category: Optional[list[str]] = None
365
+ assignee: Optional[list[str]] = None
366
+ created_range: Optional[DatetimeRange] = None
367
+ updated_range: Optional[DatetimeRange] = None
368
+
369
+
370
+ class PreviewAlertOut(PanelStatus):
371
+ """Model for payload received by GET ``/common/{alert_id}`` endpoint."""
372
+
373
+ playbook_alert_id: str
374
+ title: str
375
+ category: str
376
+
377
+
378
+ class UpdateAlertIn(RFBaseModel):
379
+ """Model for payload sent to PUT ``/common/{playbook_alert_id}`` endpoint."""
380
+
381
+ priority: Optional[str] = None
382
+ status: Optional[str] = None
383
+ assignee: Optional[str] = None
384
+ log_entry: Optional[str] = None
385
+ reopen: Optional[str] = None
386
+ added_actions_taken: Optional[list[str]] = None
387
+ removed_actions_taken: Optional[list[str]] = None
388
+
389
+
390
+ class LookupAlertIn(RFBaseModel):
391
+ """Model for playbook alert POST ``{playbook_alert_id}`` endpoints."""
392
+
393
+ panels: Optional[list] = None