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.
- psengine/__init__.py +22 -0
- psengine/_sdk_id.py +16 -0
- psengine/_version.py +14 -0
- psengine/analyst_notes/__init__.py +32 -0
- psengine/analyst_notes/constants.py +15 -0
- psengine/analyst_notes/errors.py +42 -0
- psengine/analyst_notes/helpers.py +90 -0
- psengine/analyst_notes/models.py +219 -0
- psengine/analyst_notes/note.py +149 -0
- psengine/analyst_notes/note_mgr.py +400 -0
- psengine/base_http_client.py +285 -0
- psengine/classic_alerts/__init__.py +24 -0
- psengine/classic_alerts/classic_alert.py +275 -0
- psengine/classic_alerts/classic_alert_mgr.py +507 -0
- psengine/classic_alerts/constants.py +31 -0
- psengine/classic_alerts/errors.py +38 -0
- psengine/classic_alerts/helpers.py +87 -0
- psengine/classic_alerts/markdown/__init__.py +13 -0
- psengine/classic_alerts/markdown/markdown.py +359 -0
- psengine/classic_alerts/models.py +141 -0
- psengine/collective_insights/__init__.py +29 -0
- psengine/collective_insights/collective_insights.py +164 -0
- psengine/collective_insights/constants.py +44 -0
- psengine/collective_insights/errors.py +18 -0
- psengine/collective_insights/insight.py +89 -0
- psengine/collective_insights/models.py +81 -0
- psengine/common_models.py +89 -0
- psengine/config/__init__.py +15 -0
- psengine/config/config.py +284 -0
- psengine/config/errors.py +18 -0
- psengine/constants.py +63 -0
- psengine/detection/__init__.py +17 -0
- psengine/detection/detection_mgr.py +135 -0
- psengine/detection/detection_rule.py +85 -0
- psengine/detection/errors.py +26 -0
- psengine/detection/helpers.py +56 -0
- psengine/detection/models.py +47 -0
- psengine/endpoints.py +98 -0
- psengine/enrich/__init__.py +28 -0
- psengine/enrich/constants.py +73 -0
- psengine/enrich/errors.py +26 -0
- psengine/enrich/lookup.py +299 -0
- psengine/enrich/lookup_mgr.py +341 -0
- psengine/enrich/models/__init__.py +13 -0
- psengine/enrich/models/base_enriched_entity.py +43 -0
- psengine/enrich/models/lookup.py +271 -0
- psengine/enrich/models/soar.py +138 -0
- psengine/enrich/soar.py +89 -0
- psengine/enrich/soar_mgr.py +176 -0
- psengine/entity_lists/__init__.py +16 -0
- psengine/entity_lists/constants.py +19 -0
- psengine/entity_lists/entity_list.py +435 -0
- psengine/entity_lists/entity_list_mgr.py +185 -0
- psengine/entity_lists/errors.py +26 -0
- psengine/entity_lists/models.py +87 -0
- psengine/entity_match/__init__.py +16 -0
- psengine/entity_match/entity_match.py +90 -0
- psengine/entity_match/entity_match_mgr.py +235 -0
- psengine/entity_match/errors.py +18 -0
- psengine/entity_match/models.py +22 -0
- psengine/errors.py +41 -0
- psengine/helpers/__init__.py +23 -0
- psengine/helpers/helpers.py +471 -0
- psengine/logger/__init__.py +15 -0
- psengine/logger/constants.py +39 -0
- psengine/logger/errors.py +18 -0
- psengine/logger/rf_logger.py +148 -0
- psengine/markdown/__init__.py +21 -0
- psengine/markdown/markdown.py +169 -0
- psengine/markdown/models.py +22 -0
- psengine/playbook_alerts/__init__.py +34 -0
- psengine/playbook_alerts/constants.py +35 -0
- psengine/playbook_alerts/errors.py +35 -0
- psengine/playbook_alerts/helpers.py +80 -0
- psengine/playbook_alerts/mappings.py +44 -0
- psengine/playbook_alerts/markdown/__init__.py +13 -0
- psengine/playbook_alerts/markdown/markdown.py +98 -0
- psengine/playbook_alerts/markdown/markdown_code_repo.py +64 -0
- psengine/playbook_alerts/markdown/markdown_domain_abuse.py +118 -0
- psengine/playbook_alerts/markdown/markdown_identity_exposure.py +158 -0
- psengine/playbook_alerts/models/__init__.py +36 -0
- psengine/playbook_alerts/models/common_models.py +18 -0
- psengine/playbook_alerts/models/panel_log.py +329 -0
- psengine/playbook_alerts/models/panel_status.py +70 -0
- psengine/playbook_alerts/models/pba_code_repo_leak.py +52 -0
- psengine/playbook_alerts/models/pba_cyber_vulnerability.py +53 -0
- psengine/playbook_alerts/models/pba_domain_abuse.py +139 -0
- psengine/playbook_alerts/models/pba_identity_exposures.py +93 -0
- psengine/playbook_alerts/models/pba_third_party_risk.py +103 -0
- psengine/playbook_alerts/models/search_endpoint.py +68 -0
- psengine/playbook_alerts/pa_category.py +37 -0
- psengine/playbook_alerts/playbook_alert_mgr.py +593 -0
- psengine/playbook_alerts/playbook_alerts.py +393 -0
- psengine/rf_client.py +430 -0
- psengine/risklists/__init__.py +17 -0
- psengine/risklists/constants.py +15 -0
- psengine/risklists/errors.py +20 -0
- psengine/risklists/models.py +65 -0
- psengine/risklists/risklist_mgr.py +156 -0
- psengine/stix2/__init__.py +21 -0
- psengine/stix2/base_stix_entity.py +62 -0
- psengine/stix2/complex_entity.py +372 -0
- psengine/stix2/constants.py +81 -0
- psengine/stix2/enriched_indicator.py +261 -0
- psengine/stix2/errors.py +22 -0
- psengine/stix2/helpers.py +68 -0
- psengine/stix2/rf_bundle.py +240 -0
- psengine/stix2/simple_entity.py +145 -0
- psengine/stix2/util.py +53 -0
- psengine-2.0.4.dist-info/METADATA +189 -0
- psengine-2.0.4.dist-info/RECORD +115 -0
- psengine-2.0.4.dist-info/WHEEL +5 -0
- psengine-2.0.4.dist-info/entry_points.txt +2 -0
- psengine-2.0.4.dist-info/licenses/LICENSE +21 -0
- psengine-2.0.4.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,87 @@
|
|
|
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 typing import Optional
|
|
15
|
+
|
|
16
|
+
from pydantic import Field
|
|
17
|
+
|
|
18
|
+
from ..common_models import RFBaseModel
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class EntityID(RFBaseModel):
|
|
22
|
+
id_: str = Field(alias='id')
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class Organisation(RFBaseModel):
|
|
26
|
+
organisation_id: str
|
|
27
|
+
organisation_name: str
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class OwnerOrganisationDetails(RFBaseModel):
|
|
31
|
+
owner_id: Optional[str] = None
|
|
32
|
+
owner_name: Optional[str] = None
|
|
33
|
+
organisations: Optional[list[Organisation]] = []
|
|
34
|
+
enterprise_id: Optional[str] = None
|
|
35
|
+
enterprise_name: Optional[str] = None
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class CreateRequestModel(RFBaseModel):
|
|
39
|
+
"""Validate data sent to ``/create`` endpoint."""
|
|
40
|
+
|
|
41
|
+
name: str
|
|
42
|
+
type_: str = Field(alias='type', default=None)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class SearchInModel(RFBaseModel):
|
|
46
|
+
"""Validate data sent to ``/search`` endpoint."""
|
|
47
|
+
|
|
48
|
+
name: Optional[str] = None
|
|
49
|
+
type_: str = Field(alias='type', default=None)
|
|
50
|
+
limit: Optional[int] = None
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class InfoRequestModel(RFBaseModel):
|
|
54
|
+
"""Validate data sent to ``/{listId}/info`` endpoint."""
|
|
55
|
+
|
|
56
|
+
list_id: str
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class StatusRequestModel(RFBaseModel):
|
|
60
|
+
"""Validate data sent to ``/{listId}/status`` endpoint."""
|
|
61
|
+
|
|
62
|
+
list_id: str
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
class EntitiesRequestModel(RFBaseModel):
|
|
66
|
+
"""Validate data sent to ``/{listId}/entities`` endpoint."""
|
|
67
|
+
|
|
68
|
+
list_id: str
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
class AddEntityRequestModel(RFBaseModel):
|
|
72
|
+
"""Validate data sent to ``/{listId}/entity/add`` endpoint."""
|
|
73
|
+
|
|
74
|
+
entity: EntityID
|
|
75
|
+
context: Optional[dict] = None
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
class RemoveEntityRequestModel(RFBaseModel):
|
|
79
|
+
"""Validate data sent to ``/{listId}/entity/remove`` endpoint."""
|
|
80
|
+
|
|
81
|
+
entity: EntityID
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
class ListEntityOperationResponse(RFBaseModel):
|
|
85
|
+
"""Validate data received from ``/{listId}/entity/remove`` endpoint."""
|
|
86
|
+
|
|
87
|
+
result: str
|
|
@@ -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 .entity_match import EntityLookup, ResolvedEntity
|
|
15
|
+
from .entity_match_mgr import EntityMatchMgr
|
|
16
|
+
from .errors import MatchApiError
|
|
@@ -0,0 +1,90 @@
|
|
|
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 typing import Optional, Union
|
|
15
|
+
|
|
16
|
+
from pydantic import Field
|
|
17
|
+
|
|
18
|
+
from ..common_models import IdNameType, RFBaseModel
|
|
19
|
+
from .models import Attributes
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class EntityMatchIn(RFBaseModel):
|
|
23
|
+
"""Model to validate data sent to ``entity-match/match`` endpoint."""
|
|
24
|
+
|
|
25
|
+
name: str
|
|
26
|
+
type_: Optional[list[str]] = Field(alias='type', default=[])
|
|
27
|
+
limit: int = Field(default=10)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class EntityLookup(RFBaseModel):
|
|
31
|
+
"""Model to validate data received from ``entity-match/entity/{id}`` endpoint.
|
|
32
|
+
|
|
33
|
+
Methods:
|
|
34
|
+
__str__:
|
|
35
|
+
Returns a string representation of the EntityLookup instance with:
|
|
36
|
+
entity match name, type and ID.
|
|
37
|
+
|
|
38
|
+
.. code-block:: python
|
|
39
|
+
|
|
40
|
+
>>> print(entity)
|
|
41
|
+
Entity Name: BlueDelta, Type: Organization, ID: L37nw-'
|
|
42
|
+
|
|
43
|
+
__eq__:
|
|
44
|
+
Validate equality between two EntityLookup objects by entity Id.
|
|
45
|
+
|
|
46
|
+
__hash__:
|
|
47
|
+
Defines the uniqueness of an EntityLookup object by entity Id.
|
|
48
|
+
"""
|
|
49
|
+
|
|
50
|
+
id_: str = Field(alias='id')
|
|
51
|
+
type_: str = Field(alias='type')
|
|
52
|
+
attributes: Attributes
|
|
53
|
+
|
|
54
|
+
def __hash__(self):
|
|
55
|
+
return hash(self.id_)
|
|
56
|
+
|
|
57
|
+
def __eq__(self, other: 'EntityLookup'):
|
|
58
|
+
return self.id_ == other.id_
|
|
59
|
+
|
|
60
|
+
def __str__(self):
|
|
61
|
+
return f'Entity Name: {self.attributes.name}, Type: {self.type_}, ID: {self.id_}'
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
class ResolvedEntity(RFBaseModel):
|
|
65
|
+
"""Model to validate data received from ``entity-match/match`` endpoint.
|
|
66
|
+
|
|
67
|
+
Methods:
|
|
68
|
+
__str__:
|
|
69
|
+
Returns a string representation of the EntityMatch instance with:
|
|
70
|
+
entity match name, type and ID.
|
|
71
|
+
|
|
72
|
+
.. code-block:: python
|
|
73
|
+
|
|
74
|
+
>>> print(entity_match)
|
|
75
|
+
[Entity: Wannacry, Type: Username, ID: Ub_GAO]
|
|
76
|
+
"""
|
|
77
|
+
|
|
78
|
+
entity: str
|
|
79
|
+
is_found: bool
|
|
80
|
+
content: Union[str, IdNameType]
|
|
81
|
+
|
|
82
|
+
def __str__(self):
|
|
83
|
+
if isinstance(self.content, IdNameType):
|
|
84
|
+
return f'Entity: {self.entity}, Type: {self.content.type_}, ID: {self.content.id_}'
|
|
85
|
+
return f'Entity: {self.entity}, {self.content}'
|
|
86
|
+
|
|
87
|
+
def __repr__(self):
|
|
88
|
+
if isinstance(self.content, IdNameType):
|
|
89
|
+
return f'Entity: {self.entity}, Type: {self.content.type_}, ID: {self.content.id_}'
|
|
90
|
+
return f'Entity: {self.entity}, {self.content}'
|
|
@@ -0,0 +1,235 @@
|
|
|
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 typing import Annotated, Optional, Union
|
|
16
|
+
from urllib.parse import quote
|
|
17
|
+
|
|
18
|
+
from pydantic import Field, validate_call
|
|
19
|
+
|
|
20
|
+
from ..common_models import IdNameType
|
|
21
|
+
from ..constants import DEFAULT_LIMIT, DEFAULT_MAX_WORKERS
|
|
22
|
+
from ..endpoints import EP_ENTITY_LOOKUP, EP_ENTITY_MATCH
|
|
23
|
+
from ..helpers import MultiThreadingHelper, connection_exceptions, debug_call
|
|
24
|
+
from ..rf_client import RFClient
|
|
25
|
+
from .entity_match import EntityLookup, EntityMatchIn, ResolvedEntity
|
|
26
|
+
from .errors import MatchApiError
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class EntityMatchMgr:
|
|
30
|
+
"""Manages requests for Recorded Future Entity Match API."""
|
|
31
|
+
|
|
32
|
+
def __init__(self, rf_token: str = None):
|
|
33
|
+
"""Initializes the EntityMatchMgr object.
|
|
34
|
+
|
|
35
|
+
Args:
|
|
36
|
+
rf_token (str, optional): Recorded Future API token. Defaults to None
|
|
37
|
+
"""
|
|
38
|
+
self.log = logging.getLogger(__name__)
|
|
39
|
+
self.rf_client = RFClient(api_token=rf_token) if rf_token else RFClient()
|
|
40
|
+
|
|
41
|
+
@debug_call
|
|
42
|
+
@validate_call
|
|
43
|
+
@connection_exceptions(ignore_status_code=[], exception_to_raise=MatchApiError)
|
|
44
|
+
def match(
|
|
45
|
+
self,
|
|
46
|
+
entity_name: str,
|
|
47
|
+
entity_type: Optional[Union[list, str]] = None,
|
|
48
|
+
limit: int = DEFAULT_LIMIT,
|
|
49
|
+
) -> list[ResolvedEntity]:
|
|
50
|
+
"""Matches a text string on entity match API.
|
|
51
|
+
|
|
52
|
+
Endpoint:
|
|
53
|
+
``entity-match/match``
|
|
54
|
+
|
|
55
|
+
Args:
|
|
56
|
+
entity_name (str): name of the entity.
|
|
57
|
+
entity_type (Optional[Union[list, str]): the type(s) of the entity, if known.
|
|
58
|
+
limit (int, optional): maximum number of matches to return. Default to 10.
|
|
59
|
+
|
|
60
|
+
Raises:
|
|
61
|
+
ValidationError if any supplied parameter is of incorrect type.
|
|
62
|
+
MatchApiError: if connection error occurs.
|
|
63
|
+
|
|
64
|
+
Returns:
|
|
65
|
+
list: list of ResolvedEntity
|
|
66
|
+
"""
|
|
67
|
+
if entity_type is not None:
|
|
68
|
+
entity_type = entity_type if isinstance(entity_type, list) else [entity_type]
|
|
69
|
+
|
|
70
|
+
request_body = EntityMatchIn(name=entity_name, type=entity_type, limit=limit)
|
|
71
|
+
response = self.rf_client.request('post', EP_ENTITY_MATCH, data=request_body.json())
|
|
72
|
+
response = [IdNameType.model_validate(d) for d in response.json()]
|
|
73
|
+
return (
|
|
74
|
+
[ResolvedEntity(entity=d.name, is_found=bool(d.id_), content=d) for d in response]
|
|
75
|
+
if response
|
|
76
|
+
else [ResolvedEntity(entity=entity_name, is_found=False, content='Entity ID not found')]
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
@debug_call
|
|
80
|
+
@validate_call
|
|
81
|
+
def resolve_entity_id(
|
|
82
|
+
self,
|
|
83
|
+
entity_name: str,
|
|
84
|
+
entity_type: Optional[Annotated[str, Field(min_length=2)]] = None,
|
|
85
|
+
limit: Optional[int] = DEFAULT_LIMIT,
|
|
86
|
+
) -> ResolvedEntity:
|
|
87
|
+
"""Resolves an entity name and type (optional) to an ID.
|
|
88
|
+
|
|
89
|
+
Endpoint:
|
|
90
|
+
``entity-match/match``
|
|
91
|
+
|
|
92
|
+
Args:
|
|
93
|
+
entity_name (str): name of the entity.
|
|
94
|
+
entity_type (Optional[str]): the type of the entity, if known.
|
|
95
|
+
limit (Optional[int]): limit of results to check for matches. Default 10
|
|
96
|
+
|
|
97
|
+
Raises:
|
|
98
|
+
ValidationError if any supplied parameter is of incorrect type.
|
|
99
|
+
MatchApiError: if connection error occurs.
|
|
100
|
+
|
|
101
|
+
Returns:
|
|
102
|
+
ResolvedEntity
|
|
103
|
+
"""
|
|
104
|
+
matches = self.match(entity_name, entity_type=entity_type, limit=limit)
|
|
105
|
+
if len(matches) > 1:
|
|
106
|
+
exact_count = 0
|
|
107
|
+
exact_match = None
|
|
108
|
+
for match in matches:
|
|
109
|
+
if match.entity == entity_name:
|
|
110
|
+
if entity_type is not None and match.content.type_ != entity_type:
|
|
111
|
+
continue
|
|
112
|
+
exact_match = match
|
|
113
|
+
exact_count += 1
|
|
114
|
+
if (not exact_match) or exact_count > 1:
|
|
115
|
+
message = f"Multiple matches found for '{entity_name}'"
|
|
116
|
+
if entity_type is None:
|
|
117
|
+
message += '. No type set. Consider specifying entity type'
|
|
118
|
+
else:
|
|
119
|
+
message += f" of type '{entity_type}'"
|
|
120
|
+
return ResolvedEntity(entity=entity_name, is_found=False, content=message)
|
|
121
|
+
else:
|
|
122
|
+
return matches[0]
|
|
123
|
+
|
|
124
|
+
return exact_match
|
|
125
|
+
|
|
126
|
+
@debug_call
|
|
127
|
+
@validate_call
|
|
128
|
+
def _bulk_resolution_helper(
|
|
129
|
+
self, entity: tuple[str, Optional[str]], limit: Optional[int] = DEFAULT_LIMIT
|
|
130
|
+
) -> ResolvedEntity:
|
|
131
|
+
"""Helper function for multithreading.
|
|
132
|
+
|
|
133
|
+
Args:
|
|
134
|
+
entity (Tuple[str, Optional[str]]): entity name and type tuple.
|
|
135
|
+
limit (Optional[int]): limit of results to check for matches. Default 10
|
|
136
|
+
|
|
137
|
+
Returns:
|
|
138
|
+
ResolvedEntity: resolved entity object.
|
|
139
|
+
"""
|
|
140
|
+
return self.resolve_entity_id(entity[0], entity[1], limit)
|
|
141
|
+
|
|
142
|
+
@debug_call
|
|
143
|
+
@validate_call
|
|
144
|
+
def resolve_entity_ids(
|
|
145
|
+
self,
|
|
146
|
+
entities: Union[list[str], list[tuple[str, str]]],
|
|
147
|
+
limit: Optional[int] = DEFAULT_LIMIT,
|
|
148
|
+
max_workers: Optional[int] = DEFAULT_MAX_WORKERS,
|
|
149
|
+
) -> list[ResolvedEntity]:
|
|
150
|
+
"""Resolves a list of entities to IDs.
|
|
151
|
+
|
|
152
|
+
Endpoint:
|
|
153
|
+
``entity-match/match``
|
|
154
|
+
|
|
155
|
+
Args:
|
|
156
|
+
entities (list): list of entity name strings or entity name, type tuples
|
|
157
|
+
limit (Optional[int]): limit of results to return. Default 10
|
|
158
|
+
max_workers (int, optional): number of workers to multithread requests.
|
|
159
|
+
|
|
160
|
+
Raises:
|
|
161
|
+
ValidationError if any supplied parameter is of incorrect type.
|
|
162
|
+
MatchApiError: if connection error occurs.
|
|
163
|
+
|
|
164
|
+
Returns:
|
|
165
|
+
list of ResolvedEntity objects for each entity
|
|
166
|
+
"""
|
|
167
|
+
lookup_entities = [
|
|
168
|
+
(entity, None) if isinstance(entity, str) else entity for entity in entities
|
|
169
|
+
]
|
|
170
|
+
if max_workers > 1:
|
|
171
|
+
results = MultiThreadingHelper.multithread_it(
|
|
172
|
+
max_workers,
|
|
173
|
+
self._bulk_resolution_helper,
|
|
174
|
+
iterator=lookup_entities,
|
|
175
|
+
limit=limit,
|
|
176
|
+
)
|
|
177
|
+
|
|
178
|
+
else:
|
|
179
|
+
results = [
|
|
180
|
+
self.resolve_entity_id(entity_name, entity_type, limit)
|
|
181
|
+
for entity_name, entity_type in lookup_entities
|
|
182
|
+
]
|
|
183
|
+
|
|
184
|
+
return results
|
|
185
|
+
|
|
186
|
+
@debug_call
|
|
187
|
+
@validate_call
|
|
188
|
+
@connection_exceptions(ignore_status_code=[404], exception_to_raise=MatchApiError)
|
|
189
|
+
def lookup(self, id_: str) -> EntityLookup:
|
|
190
|
+
"""Lookup a Recorded Future ID for entity details.
|
|
191
|
+
|
|
192
|
+
Endpoint:
|
|
193
|
+
``entity-match/entity/{id}``
|
|
194
|
+
|
|
195
|
+
Args:
|
|
196
|
+
id_ (str): id to lookup.
|
|
197
|
+
|
|
198
|
+
Raises:
|
|
199
|
+
ValidationError if any supplied parameter is of incorrect type.
|
|
200
|
+
MatchApiError: if connection error occurs.
|
|
201
|
+
|
|
202
|
+
Returns:
|
|
203
|
+
EntityLookup object
|
|
204
|
+
"""
|
|
205
|
+
id_ = quote(id_, safe='.')
|
|
206
|
+
response = self.rf_client.request('get', EP_ENTITY_LOOKUP.format(id_)).json()['data']
|
|
207
|
+
return EntityLookup.model_validate(response)
|
|
208
|
+
|
|
209
|
+
@debug_call
|
|
210
|
+
@validate_call
|
|
211
|
+
def lookup_bulk(self, ids: list[str], max_workers: Optional[int] = 0) -> list[EntityLookup]:
|
|
212
|
+
"""Lookup multiple Recorded Future ID for entity details.
|
|
213
|
+
|
|
214
|
+
Endpoint:
|
|
215
|
+
``entity-match/entity/{id}``
|
|
216
|
+
|
|
217
|
+
Args:
|
|
218
|
+
ids (str): id to lookup.
|
|
219
|
+
max_workers (int, optional): number of workers to multithread requests.
|
|
220
|
+
|
|
221
|
+
Raises:
|
|
222
|
+
ValidationError if any supplied parameter is of incorrect type.
|
|
223
|
+
MatchApiError: if connection error occurs.
|
|
224
|
+
|
|
225
|
+
Returns:
|
|
226
|
+
List[EntityLookup] object
|
|
227
|
+
"""
|
|
228
|
+
ids = [quote(id_, safe='.') for id_ in ids]
|
|
229
|
+
if max_workers:
|
|
230
|
+
return MultiThreadingHelper.multithread_it(
|
|
231
|
+
max_workers,
|
|
232
|
+
self.lookup,
|
|
233
|
+
iterator=ids,
|
|
234
|
+
)
|
|
235
|
+
return [self.lookup(id_) for id_ in ids]
|
|
@@ -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 MatchApiError(RecordedFutureError):
|
|
18
|
+
"""Error raised when an exception occurs performing a match API operation."""
|
|
@@ -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
|
+
|
|
15
|
+
from ..common_models import RFBaseModel
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class Attributes(RFBaseModel):
|
|
19
|
+
name: str
|
|
20
|
+
common_names: list[str]
|
|
21
|
+
alias: list[str]
|
|
22
|
+
is_threat_actor: bool
|
psengine/errors.py
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
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
|
+
|
|
15
|
+
class RecordedFutureError(Exception):
|
|
16
|
+
"""Base class for exceptions in PSEngine."""
|
|
17
|
+
|
|
18
|
+
def __init__(
|
|
19
|
+
self,
|
|
20
|
+
message='An error occurred. Raise exceptions with a message argument to see additional information', # noqa: E501
|
|
21
|
+
*args,
|
|
22
|
+
):
|
|
23
|
+
super().__init__(message, *args)
|
|
24
|
+
self.message = message
|
|
25
|
+
|
|
26
|
+
def __str__(self):
|
|
27
|
+
return self.message
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class ReadFileError(RecordedFutureError):
|
|
31
|
+
"""Error raised when PSEngine classes cannot read from file."""
|
|
32
|
+
|
|
33
|
+
def __init__(self, message='Error reading from file', *args):
|
|
34
|
+
super().__init__(message.format(*args), *args)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class WriteFileError(RecordedFutureError):
|
|
38
|
+
"""Error raised when PSEngine classes cannot write to file."""
|
|
39
|
+
|
|
40
|
+
def __init__(self, message='Error writing to file', *args):
|
|
41
|
+
super().__init__(message.format(*args), *args)
|
|
@@ -0,0 +1,23 @@
|
|
|
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 .helpers import (
|
|
15
|
+
FileHelpers,
|
|
16
|
+
FormattingHelpers,
|
|
17
|
+
MultiThreadingHelper,
|
|
18
|
+
OSHelpers,
|
|
19
|
+
TimeHelpers,
|
|
20
|
+
connection_exceptions,
|
|
21
|
+
debug_call,
|
|
22
|
+
dump_models,
|
|
23
|
+
)
|