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,24 @@
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 .classic_alert import AlertRuleOut, ClassicAlert, ClassicAlertHit
15
+ from .classic_alert_mgr import ClassicAlertMgr
16
+ from .constants import ALL_CA_FIELDS, REQUIRED_CA_FIELDS
17
+ from .errors import (
18
+ AlertFetchError,
19
+ AlertImageFetchError,
20
+ AlertMarkdownError,
21
+ AlertSearchError,
22
+ AlertUpdateError,
23
+ NoRulesFoundError,
24
+ )
@@ -0,0 +1,275 @@
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 functools import total_ordering
16
+ from itertools import chain
17
+ from typing import Optional
18
+
19
+ from pydantic import Field, field_validator
20
+
21
+ from ..common_models import IdName, IdNameTypeDescription, RFBaseModel
22
+ from ..constants import TIMESTAMP_STR
23
+ from .markdown.markdown import _markdown_alert
24
+ from .models import (
25
+ AlertAiInsight,
26
+ AlertDeprecation,
27
+ AlertLog,
28
+ AlertReview,
29
+ AlertURL,
30
+ ClassicAlertHit,
31
+ EnrichedEntity,
32
+ NotificationSettings,
33
+ OwnerOrganisationDetails,
34
+ TriggeredBy,
35
+ )
36
+
37
+
38
+ @total_ordering
39
+ class ClassicAlert(RFBaseModel):
40
+ """Validate data received from ``/v3/alerts/{id}``.
41
+
42
+ Methods:
43
+ __hash__:
44
+ Returns a hash value based on the ``id_``.
45
+
46
+ __eq__:
47
+ Checks equality between two ClassicAlert instances based on their ``id_``.
48
+
49
+ __gt__:
50
+ Defines a greater-than comparison between two ClassicAlert instances based on their
51
+ log triggered timestamp.
52
+
53
+ __str__:
54
+ Returns a string representation of the ClassicAlert instance with:
55
+ ``id_``, triggered timestamp, title, and alerting rule name.
56
+
57
+ .. code-block:: python
58
+
59
+ >>> print(alert_id_response)
60
+ Classic Alert ID: a123, Triggered: 2024-05-21 10:42:30AM, Title: Example Alert
61
+
62
+ Total Ordering:
63
+ The ordering of ClassicAlert instances is determined primarily by the log triggered
64
+ timestamp. If two instances have the same triggered timestamp, their ``id_`` is used as a
65
+ secondary criterion for ordering.
66
+ """
67
+
68
+ id_: str = Field(alias='id')
69
+ log: AlertLog
70
+ title: str
71
+ review: Optional[AlertReview] = None
72
+ owner_organisation_details: Optional[OwnerOrganisationDetails] = None
73
+ url: Optional[AlertURL] = None
74
+ rule: Optional[AlertDeprecation] = None
75
+ hits: Optional[list[ClassicAlertHit]] = None
76
+ enriched_entities: Optional[list[EnrichedEntity]] = None
77
+ ai_insights: Optional[AlertAiInsight] = None
78
+ type_: str = Field(alias='type', default=None)
79
+ triggered_by: Optional[list[TriggeredBy]] = None
80
+
81
+ _images: Optional[dict] = {}
82
+
83
+ @field_validator('triggered_by', mode='before')
84
+ @classmethod
85
+ def parse_trigger_by(cls, data: list[dict]) -> list[dict]:
86
+ """Parses a list of data dictionaries to extract and format entity paths.
87
+
88
+ Each entity path is transformed into a formatted string where each entity is represented as
89
+ 'EntityName (EntityType)', joined by ' -> '.
90
+ If an entity's type is 'MetaType', it is formatted as 'Any EntityName' instead.
91
+
92
+ Args:
93
+ data (List[Dict]): A list of dictionaries, each containing a 'reference_id' and an
94
+ 'entity_paths' list.
95
+
96
+ Returns:
97
+ List[Dict]: A list of dictionaries with 'reference_id' and a list of unique formatted
98
+ 'triggered_by_strings' paths.
99
+
100
+ Example:
101
+ Input:
102
+
103
+ .. code-block::
104
+
105
+ [
106
+ {
107
+ 'reference_id': '123',
108
+ 'entity_paths': [
109
+ [
110
+ {'entity': {'name': 'URL1', 'type': 'URL'}},
111
+ {'entity': {'name': 'Domain1', 'type': 'InternetDomainName'}}
112
+ ],
113
+ [
114
+ {'entity': {'name': 'URL1', 'type': 'URL'}},
115
+ {'entity': {'name': 'Domain1', 'type': 'InternetDomainName'}}
116
+ ]
117
+ ]
118
+ }
119
+ ]
120
+
121
+ Output:
122
+
123
+ .. code-block::
124
+
125
+ [
126
+ {
127
+ 'reference_id': '123',
128
+ 'triggered_by_strings': [
129
+ 'URL1 (URL) -> Domain1 (InternetDomainName)'
130
+ ]
131
+ }
132
+ ]
133
+ """
134
+ result = []
135
+ for item in data:
136
+ reference_id = item.get('reference_id')
137
+ entity_paths = item.get('entity_paths', [])
138
+ seen_strings = set()
139
+ to_string = []
140
+
141
+ for path in entity_paths:
142
+ formatted_entities = [
143
+ (
144
+ f'Any {entity["name"]}'
145
+ if entity.get('type') == 'MetaType'
146
+ else f'{entity["name"]} ({entity["type"]})'
147
+ )
148
+ for obj in path
149
+ if (entity := obj.get('entity'))
150
+ ]
151
+ parsed_string = ' -> '.join(formatted_entities)
152
+ if parsed_string not in seen_strings:
153
+ seen_strings.add(parsed_string)
154
+ to_string.append(parsed_string)
155
+
156
+ result.append({'reference_id': reference_id, 'triggered_by_strings': to_string})
157
+ return result
158
+
159
+ def __hash__(self):
160
+ return hash(self.id_)
161
+
162
+ def __eq__(self, other: 'ClassicAlert'):
163
+ return self.id_ == other.id_
164
+
165
+ def __gt__(self, other: 'ClassicAlert'):
166
+ return self.log.triggered > other.log.triggered
167
+
168
+ def __str__(self):
169
+ return (
170
+ f'Classic Alert ID: {self.id_}, '
171
+ f'Triggered: {self.log.triggered.strftime(TIMESTAMP_STR)}, '
172
+ f'Title: {self.title}, Alerting Rule: {self.rule.name}'
173
+ )
174
+
175
+ def triggered_by_from_hit(self, hit: ClassicAlertHit) -> list[str]:
176
+ """From an Alert Hit block, returns the related Triggered By string representation."""
177
+ return list(
178
+ chain.from_iterable(
179
+ t.triggered_by_strings for t in self.triggered_by if t.reference_id == hit.id_
180
+ )
181
+ )
182
+
183
+ def store_image(self, image_id: str, image_bytes: bytes) -> None:
184
+ """Stores the image id and image bytes in ``@images`` dictionary.
185
+
186
+ Example:
187
+ .. code-block:: python
188
+
189
+ {
190
+ image_id: image_bytes,
191
+ image_id: image_bytes
192
+ }
193
+
194
+
195
+ Args:
196
+ image_id (str): image id
197
+ image_bytes (bytes): image bytes
198
+ """
199
+ self._images[image_id] = image_bytes
200
+
201
+ def markdown(
202
+ self,
203
+ owner_org: bool = False,
204
+ ai_insights: bool = True,
205
+ fragment_entities: bool = True,
206
+ triggered_by: bool = True,
207
+ html_tags: bool = False,
208
+ character_limit: int = None,
209
+ defang_iocs: bool = False,
210
+ ):
211
+ """Returns a markdown string representation of the ``ClassicAlert`` instance.
212
+
213
+ This function works on ``ClassicAlert`` instances returned by ``ClassicAlertMgr.fetch()``,
214
+ if you are passing the result of ``ClassicAlertMgr.search()`` make sure the ``search``
215
+ method has been called with all the fields. Keep in mind that this will make the
216
+ ``search`` slower.
217
+
218
+ Args:
219
+ self (ClassicAlert): ClassicAlert instance to create markdown from.
220
+ owner_org (bool, optional): Include owner org details. Defaults to False.
221
+ ai_insights (bool, optional): Include AI insights. Defaults to True.
222
+ fragment_entities (bool, optional): Include fragment entities. Defaults to True.
223
+ triggered_by (bool, optional): Include triggered by. Defaults to True.
224
+ html_tags (bool, optional): Include HTML tags in the markdown. Defaults to False.
225
+ character_limit (int, optional): Character limit for the markdown. Defaults to None.
226
+ defang_iocs (bool, optional): Defang IOCs in hits. Defaults to False.
227
+
228
+ Raises:
229
+ AlertMarkdownError: If fields are not available.
230
+
231
+ Returns:
232
+ str: Markdown representation of the alert.
233
+ """
234
+ return _markdown_alert(
235
+ self,
236
+ owner_org=owner_org,
237
+ ai_insights=ai_insights,
238
+ fragment_entities=fragment_entities,
239
+ triggered_by=triggered_by,
240
+ html_tags=html_tags,
241
+ character_limit=character_limit,
242
+ defang_iocs=defang_iocs,
243
+ )
244
+
245
+ @property
246
+ def images(self) -> dict:
247
+ """If the alert has images, then return them in a dict.
248
+
249
+ Example:
250
+ .. code-block:: python
251
+
252
+ {
253
+ image_id: image_bytes,
254
+ image_id: image_bytes
255
+ }
256
+
257
+
258
+ Returns:
259
+ dict: dictionary of image ids and image bytes
260
+ """
261
+ return self._images
262
+
263
+
264
+ class AlertRuleOut(RFBaseModel):
265
+ """Validate data received from ``v2/alert/rule``."""
266
+
267
+ intelligence_goals: list[IdName]
268
+ priority: bool = None
269
+ tags: list[IdNameTypeDescription] = None
270
+ id_: str = Field(alias='id')
271
+ owner: IdName
272
+ title: str
273
+ created: datetime
274
+ notification_settings: NotificationSettings
275
+ enabled: bool