pycti 6.5.0__py3-none-any.whl → 6.5.2__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.
Potentially problematic release.
This version of pycti might be problematic. Click here for more details.
- pycti/__init__.py +1 -1
- pycti/api/opencti_api_client.py +17 -12
- pycti/api/opencti_api_work.py +18 -0
- pycti/connector/opencti_connector_helper.py +14 -0
- pycti/entities/stix_cyber_observable/opencti_stix_cyber_observable_deprecated.py +1 -1
- pycti/utils/opencti_logger.py +3 -3
- pycti/utils/opencti_stix2.py +15 -0
- {pycti-6.5.0.dist-info → pycti-6.5.2.dist-info}/METADATA +3 -3
- {pycti-6.5.0.dist-info → pycti-6.5.2.dist-info}/RECORD +12 -12
- {pycti-6.5.0.dist-info → pycti-6.5.2.dist-info}/LICENSE +0 -0
- {pycti-6.5.0.dist-info → pycti-6.5.2.dist-info}/WHEEL +0 -0
- {pycti-6.5.0.dist-info → pycti-6.5.2.dist-info}/top_level.txt +0 -0
pycti/__init__.py
CHANGED
pycti/api/opencti_api_client.py
CHANGED
|
@@ -3,7 +3,7 @@ import base64
|
|
|
3
3
|
import datetime
|
|
4
4
|
import io
|
|
5
5
|
import json
|
|
6
|
-
from typing import Union
|
|
6
|
+
from typing import Dict, Tuple, Union
|
|
7
7
|
|
|
8
8
|
import magic
|
|
9
9
|
import requests
|
|
@@ -83,13 +83,13 @@ class OpenCTIApiClient:
|
|
|
83
83
|
:param log_level: log level for the client
|
|
84
84
|
:type log_level: str, optional
|
|
85
85
|
:param ssl_verify: Requiring the requests to verify the TLS certificate at the server.
|
|
86
|
-
:type ssl_verify: bool, optional
|
|
86
|
+
:type ssl_verify: bool, str, optional
|
|
87
87
|
:param proxies:
|
|
88
88
|
:type proxies: dict, optional, The proxy configuration, would have `http` and `https` attributes. Defaults to {}
|
|
89
89
|
```
|
|
90
90
|
proxies: {
|
|
91
|
-
"http: "http://my_proxy:8080"
|
|
92
|
-
"https: "http://my_proxy:8080"
|
|
91
|
+
"http": "http://my_proxy:8080"
|
|
92
|
+
"https": "http://my_proxy:8080"
|
|
93
93
|
}
|
|
94
94
|
```
|
|
95
95
|
:param json_logging: format the logs as json if set to True
|
|
@@ -102,14 +102,14 @@ class OpenCTIApiClient:
|
|
|
102
102
|
|
|
103
103
|
def __init__(
|
|
104
104
|
self,
|
|
105
|
-
url,
|
|
106
|
-
token,
|
|
105
|
+
url: str,
|
|
106
|
+
token: str,
|
|
107
107
|
log_level="info",
|
|
108
|
-
ssl_verify=False,
|
|
109
|
-
proxies=None,
|
|
108
|
+
ssl_verify: Union[bool, str] = False,
|
|
109
|
+
proxies: Union[Dict[str, str], None] = None,
|
|
110
110
|
json_logging=False,
|
|
111
111
|
bundle_send_to_queue=True,
|
|
112
|
-
cert=None,
|
|
112
|
+
cert: Union[str, Tuple[str, str], None] = None,
|
|
113
113
|
auth=None,
|
|
114
114
|
perform_health_check=True,
|
|
115
115
|
):
|
|
@@ -712,12 +712,13 @@ class OpenCTIApiClient:
|
|
|
712
712
|
data = kwargs.get("data", None)
|
|
713
713
|
mime_type = kwargs.get("mime_type", "text/plain")
|
|
714
714
|
entity_id = kwargs.get("entity_id", None)
|
|
715
|
+
file_markings = kwargs.get("file_markings", [])
|
|
715
716
|
|
|
716
717
|
if file_name is not None:
|
|
717
718
|
self.app_logger.info("Uploading a file.")
|
|
718
719
|
query = """
|
|
719
|
-
mutation UploadPending($file: Upload!, $entityId: String) {
|
|
720
|
-
uploadPending(file: $file, entityId: $entityId) {
|
|
720
|
+
mutation UploadPending($file: Upload!, $entityId: String, $file_markings: [String!]) {
|
|
721
|
+
uploadPending(file: $file, entityId: $entityId, file_markings: $file_markings) {
|
|
721
722
|
id
|
|
722
723
|
name
|
|
723
724
|
}
|
|
@@ -731,7 +732,11 @@ class OpenCTIApiClient:
|
|
|
731
732
|
mime_type = magic.from_file(file_name, mime=True)
|
|
732
733
|
return self.query(
|
|
733
734
|
query,
|
|
734
|
-
{
|
|
735
|
+
{
|
|
736
|
+
"file": (File(file_name, data, mime_type)),
|
|
737
|
+
"entityId": entity_id,
|
|
738
|
+
"file_markings": file_markings,
|
|
739
|
+
},
|
|
735
740
|
)
|
|
736
741
|
else:
|
|
737
742
|
self.app_logger.error("[upload] Missing parameter: file_name")
|
pycti/api/opencti_api_work.py
CHANGED
|
@@ -82,6 +82,24 @@ class OpenCTIApiWork:
|
|
|
82
82
|
except:
|
|
83
83
|
self.api.app_logger.error("Cannot report expectation")
|
|
84
84
|
|
|
85
|
+
def add_draft_context(self, work_id: str, draft_context: str):
|
|
86
|
+
if self.api.bundle_send_to_queue:
|
|
87
|
+
self.api.app_logger.info(
|
|
88
|
+
"Update draft context",
|
|
89
|
+
{"work_id": work_id, "draft_context": draft_context},
|
|
90
|
+
)
|
|
91
|
+
query = """
|
|
92
|
+
mutation addDraftContext($id: ID!, $draftContext: String) {
|
|
93
|
+
workEdit(id: $id) {
|
|
94
|
+
addDraftContext(draftContext: $draftContext)
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
"""
|
|
98
|
+
try:
|
|
99
|
+
self.api.query(query, {"id": work_id, "draftContext": draft_context})
|
|
100
|
+
except:
|
|
101
|
+
self.api.app_logger.error("Cannot report draft context")
|
|
102
|
+
|
|
85
103
|
def initiate_work(self, connector_id: str, friendly_name: str) -> str:
|
|
86
104
|
if self.api.bundle_send_to_queue:
|
|
87
105
|
self.api.app_logger.info("Initiate work", {"connector_id": connector_id})
|
|
@@ -769,6 +769,9 @@ class OpenCTIConnectorHelper: # pylint: disable=too-many-public-methods
|
|
|
769
769
|
def __init__(self, config: Dict, playbook_compatible=False) -> None:
|
|
770
770
|
sys.excepthook = killProgramHook
|
|
771
771
|
|
|
772
|
+
# Cache
|
|
773
|
+
self.stream_collections = {}
|
|
774
|
+
|
|
772
775
|
# Load API config
|
|
773
776
|
self.config = config
|
|
774
777
|
self.opencti_url = get_config_variable(
|
|
@@ -1063,6 +1066,9 @@ class OpenCTIConnectorHelper: # pylint: disable=too-many-public-methods
|
|
|
1063
1066
|
"stream_live": True,
|
|
1064
1067
|
"stream_public": False,
|
|
1065
1068
|
}
|
|
1069
|
+
# Get from cache
|
|
1070
|
+
elif self.connect_live_stream_id in self.stream_collections:
|
|
1071
|
+
return self.stream_collections[self.connect_live_stream_id]
|
|
1066
1072
|
else:
|
|
1067
1073
|
query = """
|
|
1068
1074
|
query StreamCollection($id: String!) {
|
|
@@ -1076,6 +1082,10 @@ class OpenCTIConnectorHelper: # pylint: disable=too-many-public-methods
|
|
|
1076
1082
|
}
|
|
1077
1083
|
"""
|
|
1078
1084
|
result = self.api.query(query, {"id": self.connect_live_stream_id})
|
|
1085
|
+
# Put in cache
|
|
1086
|
+
self.stream_collections[self.connect_live_stream_id] = result["data"][
|
|
1087
|
+
"streamCollection"
|
|
1088
|
+
]
|
|
1079
1089
|
return result["data"]["streamCollection"]
|
|
1080
1090
|
else:
|
|
1081
1091
|
raise ValueError("This connector is not connected to any stream")
|
|
@@ -1581,6 +1591,7 @@ class OpenCTIConnectorHelper: # pylint: disable=too-many-public-methods
|
|
|
1581
1591
|
event_version = kwargs.get("event_version", None)
|
|
1582
1592
|
bypass_validation = kwargs.get("bypass_validation", False)
|
|
1583
1593
|
entity_id = kwargs.get("entity_id", None)
|
|
1594
|
+
file_markings = kwargs.get("file_markings", None)
|
|
1584
1595
|
file_name = kwargs.get("file_name", None)
|
|
1585
1596
|
bundle_send_to_queue = kwargs.get("send_to_queue", self.bundle_send_to_queue)
|
|
1586
1597
|
cleanup_inconsistent_bundle = kwargs.get("cleanup_inconsistent_bundle", False)
|
|
@@ -1648,6 +1659,7 @@ class OpenCTIConnectorHelper: # pylint: disable=too-many-public-methods
|
|
|
1648
1659
|
data=bundle,
|
|
1649
1660
|
mime_type="application/json",
|
|
1650
1661
|
entity_id=entity_id,
|
|
1662
|
+
file_markings=file_markings,
|
|
1651
1663
|
)
|
|
1652
1664
|
return []
|
|
1653
1665
|
elif validation_mode == "draft" and not draft_id:
|
|
@@ -1732,6 +1744,8 @@ class OpenCTIConnectorHelper: # pylint: disable=too-many-public-methods
|
|
|
1732
1744
|
if bundle_send_to_queue:
|
|
1733
1745
|
if work_id:
|
|
1734
1746
|
self.api.work.add_expectations(work_id, expectations_number)
|
|
1747
|
+
if draft_id:
|
|
1748
|
+
self.api.work.add_draft_context(work_id, draft_id)
|
|
1735
1749
|
if entities_types is None:
|
|
1736
1750
|
entities_types = []
|
|
1737
1751
|
if self.queue_protocol == "amqp":
|
pycti/utils/opencti_logger.py
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import datetime
|
|
2
1
|
import logging
|
|
2
|
+
from datetime import datetime, timezone
|
|
3
3
|
|
|
4
4
|
from pythonjsonlogger import jsonlogger
|
|
5
5
|
|
|
@@ -9,8 +9,8 @@ class CustomJsonFormatter(jsonlogger.JsonFormatter):
|
|
|
9
9
|
super(CustomJsonFormatter, self).add_fields(log_record, record, message_dict)
|
|
10
10
|
if not log_record.get("timestamp"):
|
|
11
11
|
# This doesn't use record.created, so it is slightly off
|
|
12
|
-
now = datetime.
|
|
13
|
-
log_record["timestamp"] = now
|
|
12
|
+
now = datetime.now(tz=timezone.utc)
|
|
13
|
+
log_record["timestamp"] = now.strftime("%Y-%m-%dT%H:%M:%S.%fZ")
|
|
14
14
|
if log_record.get("level"):
|
|
15
15
|
log_record["level"] = log_record["level"].upper()
|
|
16
16
|
else:
|
pycti/utils/opencti_stix2.py
CHANGED
|
@@ -2407,6 +2407,19 @@ class OpenCTIStix2:
|
|
|
2407
2407
|
|
|
2408
2408
|
return bundle
|
|
2409
2409
|
|
|
2410
|
+
def apply_patch(self, item):
|
|
2411
|
+
input = item["opencti_field_patch"]
|
|
2412
|
+
if item["type"] == "relationship":
|
|
2413
|
+
self.opencti.stix_core_relationship.update_field(id=item["id"], input=input)
|
|
2414
|
+
elif item["type"] == "sighting":
|
|
2415
|
+
self.opencti.stix_sighting_relationship.update_field(
|
|
2416
|
+
id=item["id"], input=input
|
|
2417
|
+
)
|
|
2418
|
+
elif StixCyberObservableTypes.has_value(item["type"]):
|
|
2419
|
+
self.opencti.stix_cyber_observable.update_field(id=item["id"], input=input)
|
|
2420
|
+
else:
|
|
2421
|
+
self.opencti.stix_domain_object.update_field(id=item["id"], input=input)
|
|
2422
|
+
|
|
2410
2423
|
def import_item(
|
|
2411
2424
|
self,
|
|
2412
2425
|
item,
|
|
@@ -2426,6 +2439,8 @@ class OpenCTIStix2:
|
|
|
2426
2439
|
target_id = item["merge_target_id"]
|
|
2427
2440
|
source_ids = item["merge_source_ids"]
|
|
2428
2441
|
self.opencti.stix.merge(id=target_id, object_ids=source_ids)
|
|
2442
|
+
elif item["opencti_operation"] == "patch":
|
|
2443
|
+
self.apply_patch(item=item)
|
|
2429
2444
|
else:
|
|
2430
2445
|
raise ValueError("Not supported opencti_operation")
|
|
2431
2446
|
elif item["type"] == "relationship":
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.2
|
|
2
2
|
Name: pycti
|
|
3
|
-
Version: 6.5.
|
|
3
|
+
Version: 6.5.2
|
|
4
4
|
Summary: Python API client for OpenCTI.
|
|
5
5
|
Home-page: https://github.com/OpenCTI-Platform/client-python
|
|
6
6
|
Author: Filigran
|
|
@@ -40,8 +40,8 @@ Requires-Dist: stix2~=3.0.1
|
|
|
40
40
|
Provides-Extra: dev
|
|
41
41
|
Requires-Dist: black~=24.4.0; extra == "dev"
|
|
42
42
|
Requires-Dist: build~=1.2.1; extra == "dev"
|
|
43
|
-
Requires-Dist: isort~=
|
|
44
|
-
Requires-Dist: types-pytz~=
|
|
43
|
+
Requires-Dist: isort~=6.0.0; extra == "dev"
|
|
44
|
+
Requires-Dist: types-pytz~=2025.1.0.20250204; extra == "dev"
|
|
45
45
|
Requires-Dist: pre-commit~=3.8.0; extra == "dev"
|
|
46
46
|
Requires-Dist: pytest-cases~=3.8.0; extra == "dev"
|
|
47
47
|
Requires-Dist: pytest-cov~=5.0.0; extra == "dev"
|
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
pycti/__init__.py,sha256=
|
|
1
|
+
pycti/__init__.py,sha256=vuINZwJahwqFmxz7ZhxCxVAk8eEKHZO6_pveLWsEQ3U,5218
|
|
2
2
|
pycti/api/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
3
|
-
pycti/api/opencti_api_client.py,sha256=
|
|
3
|
+
pycti/api/opencti_api_client.py,sha256=6TKvtgAk0iYQL2RaLTUV6cxCBefEgwmlL_iVHgzf_ow,32745
|
|
4
4
|
pycti/api/opencti_api_connector.py,sha256=ubM_zPjTD8L33TEugCQgf_YF9zugDFg_7FgNubGlwJw,5447
|
|
5
5
|
pycti/api/opencti_api_playbook.py,sha256=456We78vESukfSOi_CctfZ9dbBJEi76EHClRc2f21Js,1628
|
|
6
|
-
pycti/api/opencti_api_work.py,sha256=
|
|
6
|
+
pycti/api/opencti_api_work.py,sha256=qIRJMCfyC9odXf7LMRg9ImYizqF2WHUOU7Ty5IUFGg8,8351
|
|
7
7
|
pycti/connector/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
8
8
|
pycti/connector/opencti_connector.py,sha256=5oVvS27KWhzRiofJAeQPDtba-EP83FziSistyEd5l-U,2561
|
|
9
|
-
pycti/connector/opencti_connector_helper.py,sha256=
|
|
9
|
+
pycti/connector/opencti_connector_helper.py,sha256=ee8Ej43Ox9W7G44PEfHNGZuU_NmBVNl7dE3V3BzE4sE,81286
|
|
10
10
|
pycti/connector/opencti_metric_handler.py,sha256=4jXHeJflomtHjuQ_YU0b36TG7o26vOWbY_jvU8Ezobs,3725
|
|
11
11
|
pycti/entities/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
12
12
|
pycti/entities/opencti_attack_pattern.py,sha256=rj3o2bFCoXniLmD9Ithi09S9Us8ab1G-GFLgqS8jll0,22237
|
|
@@ -57,18 +57,18 @@ pycti/entities/opencti_vulnerability.py,sha256=PxlfRKrwmkqU5E8o8WF04YRXJtCxBu5QB
|
|
|
57
57
|
pycti/entities/indicator/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
58
58
|
pycti/entities/indicator/opencti_indicator_properties.py,sha256=8X8YkCshM0gkHc9sQZ_WSNvVxOA4aTJmLta8ZG93HOU,5087
|
|
59
59
|
pycti/entities/stix_cyber_observable/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
60
|
-
pycti/entities/stix_cyber_observable/opencti_stix_cyber_observable_deprecated.py,sha256=
|
|
60
|
+
pycti/entities/stix_cyber_observable/opencti_stix_cyber_observable_deprecated.py,sha256=q-2G6OOqvUC1U2hSKxD8uT5T18M_IDkl72Tn1KoumQI,1847
|
|
61
61
|
pycti/entities/stix_cyber_observable/opencti_stix_cyber_observable_properties.py,sha256=MN56CW8RWZwB0Pr8UiHZy_4nSzbgFbwdhSFKpsZ_d1Y,11293
|
|
62
62
|
pycti/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
63
63
|
pycti/utils/constants.py,sha256=ZgOVxY5bnrHiNvPgOfZLWk16sSDnaE_tg8JVjZpw24Q,11831
|
|
64
|
-
pycti/utils/opencti_logger.py,sha256=
|
|
65
|
-
pycti/utils/opencti_stix2.py,sha256=
|
|
64
|
+
pycti/utils/opencti_logger.py,sha256=BHNy9fJuTUTn_JEYSCmyvVwd6y-9ZJKxO40mY4iZ0bc,2226
|
|
65
|
+
pycti/utils/opencti_stix2.py,sha256=pUx1Oglb1EnREheMzmJBrmRS17vGnZzF8PCmB-pjNM0,117680
|
|
66
66
|
pycti/utils/opencti_stix2_identifier.py,sha256=k8L1z4q1xdCBfxqUba4YS_kT-MmbJFxYh0RvfGOmrOs,837
|
|
67
67
|
pycti/utils/opencti_stix2_splitter.py,sha256=etnAWMDzNi2JCovSUJ5Td-XLVdzgKRdsV1XfpXOGols,11070
|
|
68
68
|
pycti/utils/opencti_stix2_update.py,sha256=CnMyqkeVA0jgyxEcgqna8sABU4YPMjkEJ228GVurIn4,14658
|
|
69
69
|
pycti/utils/opencti_stix2_utils.py,sha256=xgBZzm7HC76rLQYwTKkaUd_w9jJnVMoryHx7KDDIB_g,5065
|
|
70
|
-
pycti-6.5.
|
|
71
|
-
pycti-6.5.
|
|
72
|
-
pycti-6.5.
|
|
73
|
-
pycti-6.5.
|
|
74
|
-
pycti-6.5.
|
|
70
|
+
pycti-6.5.2.dist-info/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
|
|
71
|
+
pycti-6.5.2.dist-info/METADATA,sha256=t7YyxQyOxdyFCqF7DF_iWCG9XK-Hj8vMWuB9vHvrRlQ,5418
|
|
72
|
+
pycti-6.5.2.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
|
|
73
|
+
pycti-6.5.2.dist-info/top_level.txt,sha256=cqEpxitAhHP4VgSA6xmrak6Yk9MeBkwoMTB6k7d2ZnE,6
|
|
74
|
+
pycti-6.5.2.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|