pycti 6.5.5__py3-none-any.whl → 6.5.7__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 +4 -3
- pycti/api/opencti_api_connector.py +1 -0
- pycti/connector/opencti_connector.py +3 -0
- pycti/connector/opencti_connector_helper.py +219 -49
- pycti/entities/opencti_threat_actor.py +26 -3
- pycti/entities/opencti_threat_actor_group.py +30 -3
- pycti/entities/opencti_threat_actor_individual.py +34 -3
- {pycti-6.5.5.dist-info → pycti-6.5.7.dist-info}/METADATA +4 -2
- {pycti-6.5.5.dist-info → pycti-6.5.7.dist-info}/RECORD +13 -13
- {pycti-6.5.5.dist-info → pycti-6.5.7.dist-info}/WHEEL +1 -1
- {pycti-6.5.5.dist-info → pycti-6.5.7.dist-info}/LICENSE +0 -0
- {pycti-6.5.5.dist-info → pycti-6.5.7.dist-info}/top_level.txt +0 -0
pycti/__init__.py
CHANGED
pycti/api/opencti_api_client.py
CHANGED
|
@@ -751,6 +751,7 @@ class OpenCTIApiClient:
|
|
|
751
751
|
"""
|
|
752
752
|
|
|
753
753
|
connector_id = kwargs.get("connector_id", None)
|
|
754
|
+
work_id = kwargs.get("work_id", None)
|
|
754
755
|
bundle = kwargs.get("bundle", None)
|
|
755
756
|
|
|
756
757
|
if connector_id is not None and bundle is not None:
|
|
@@ -758,13 +759,13 @@ class OpenCTIApiClient:
|
|
|
758
759
|
"Pushing a bundle to queue through API", {connector_id}
|
|
759
760
|
)
|
|
760
761
|
mutation = """
|
|
761
|
-
mutation StixBundlePush($connectorId: String!, $bundle: String
|
|
762
|
-
stixBundlePush(connectorId: $connectorId, bundle: $bundle)
|
|
762
|
+
mutation StixBundlePush($connectorId: String!, $bundle: String!, $work_id: String) {
|
|
763
|
+
stixBundlePush(connectorId: $connectorId, bundle: $bundle, work_id: $work_id)
|
|
763
764
|
}
|
|
764
765
|
"""
|
|
765
766
|
return self.query(
|
|
766
767
|
mutation,
|
|
767
|
-
{"connectorId": connector_id, "bundle": bundle},
|
|
768
|
+
{"connectorId": connector_id, "bundle": bundle, "work_id": work_id},
|
|
768
769
|
)
|
|
769
770
|
else:
|
|
770
771
|
self.app_logger.error(
|
|
@@ -43,6 +43,7 @@ class OpenCTIConnector:
|
|
|
43
43
|
auto: bool,
|
|
44
44
|
only_contextual: bool,
|
|
45
45
|
playbook_compatible: bool,
|
|
46
|
+
listen_callback_uri=None,
|
|
46
47
|
):
|
|
47
48
|
self.id = connector_id
|
|
48
49
|
self.name = connector_name
|
|
@@ -56,6 +57,7 @@ class OpenCTIConnector:
|
|
|
56
57
|
self.auto = auto
|
|
57
58
|
self.only_contextual = only_contextual
|
|
58
59
|
self.playbook_compatible = playbook_compatible
|
|
60
|
+
self.listen_callback_uri = listen_callback_uri
|
|
59
61
|
|
|
60
62
|
def to_input(self) -> dict:
|
|
61
63
|
"""connector input to use in API query
|
|
@@ -72,5 +74,6 @@ class OpenCTIConnector:
|
|
|
72
74
|
"auto": self.auto,
|
|
73
75
|
"only_contextual": self.only_contextual,
|
|
74
76
|
"playbook_compatible": self.playbook_compatible,
|
|
77
|
+
"listen_callback_uri": self.listen_callback_uri,
|
|
75
78
|
}
|
|
76
79
|
}
|
|
@@ -18,6 +18,9 @@ from queue import Queue
|
|
|
18
18
|
from typing import Callable, Dict, List, Optional, Union
|
|
19
19
|
|
|
20
20
|
import pika
|
|
21
|
+
import uvicorn
|
|
22
|
+
from fastapi import FastAPI, Request
|
|
23
|
+
from fastapi.responses import JSONResponse
|
|
21
24
|
from filigran_sseclient import SSEClient
|
|
22
25
|
from pika.exceptions import NackError, UnroutableError
|
|
23
26
|
from pydantic import TypeAdapter
|
|
@@ -30,6 +33,8 @@ from pycti.utils.opencti_stix2_splitter import OpenCTIStix2Splitter
|
|
|
30
33
|
TRUTHY: List[str] = ["yes", "true", "True"]
|
|
31
34
|
FALSY: List[str] = ["no", "false", "False"]
|
|
32
35
|
|
|
36
|
+
app = FastAPI()
|
|
37
|
+
|
|
33
38
|
|
|
34
39
|
def killProgramHook(etype, value, tb):
|
|
35
40
|
os.kill(os.getpid(), signal.SIGTERM)
|
|
@@ -141,6 +146,35 @@ def ssl_cert_chain(ssl_context, cert_data, key_data, passphrase):
|
|
|
141
146
|
os.unlink(key_file_path)
|
|
142
147
|
|
|
143
148
|
|
|
149
|
+
def create_callback_ssl_context(config) -> ssl.SSLContext:
|
|
150
|
+
listen_protocol_api_ssl_key = get_config_variable(
|
|
151
|
+
"LISTEN_PROTOCOL_API_SSL_KEY",
|
|
152
|
+
["connector", "listen_protocol_api_ssl_key"],
|
|
153
|
+
config,
|
|
154
|
+
default="",
|
|
155
|
+
)
|
|
156
|
+
listen_protocol_api_ssl_cert = get_config_variable(
|
|
157
|
+
"LISTEN_PROTOCOL_API_SSL_CERT",
|
|
158
|
+
["connector", "listen_protocol_api_ssl_cert"],
|
|
159
|
+
config,
|
|
160
|
+
default="",
|
|
161
|
+
)
|
|
162
|
+
listen_protocol_api_ssl_passphrase = get_config_variable(
|
|
163
|
+
"LISTEN_PROTOCOL_API_SSL_PASSPHRASE",
|
|
164
|
+
["connector", "listen_protocol_api_ssl_passphrase"],
|
|
165
|
+
config,
|
|
166
|
+
default="",
|
|
167
|
+
)
|
|
168
|
+
ssl_context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)
|
|
169
|
+
ssl_cert_chain(
|
|
170
|
+
ssl_context,
|
|
171
|
+
listen_protocol_api_ssl_cert,
|
|
172
|
+
listen_protocol_api_ssl_key,
|
|
173
|
+
listen_protocol_api_ssl_passphrase,
|
|
174
|
+
)
|
|
175
|
+
return ssl_context
|
|
176
|
+
|
|
177
|
+
|
|
144
178
|
def create_mq_ssl_context(config) -> ssl.SSLContext:
|
|
145
179
|
use_ssl_ca = get_config_variable("MQ_USE_SSL_CA", ["mq", "use_ssl_ca"], config)
|
|
146
180
|
use_ssl_cert = get_config_variable(
|
|
@@ -183,9 +217,14 @@ class ListenQueue(threading.Thread):
|
|
|
183
217
|
def __init__(
|
|
184
218
|
self,
|
|
185
219
|
helper,
|
|
220
|
+
opencti_token,
|
|
186
221
|
config: Dict,
|
|
187
222
|
connector_config: Dict,
|
|
188
223
|
applicant_id,
|
|
224
|
+
listen_protocol,
|
|
225
|
+
listen_protocol_api_ssl,
|
|
226
|
+
listen_protocol_api_path,
|
|
227
|
+
listen_protocol_api_port,
|
|
189
228
|
callback,
|
|
190
229
|
) -> None:
|
|
191
230
|
threading.Thread.__init__(self)
|
|
@@ -196,6 +235,11 @@ class ListenQueue(threading.Thread):
|
|
|
196
235
|
self.helper = helper
|
|
197
236
|
self.callback = callback
|
|
198
237
|
self.config = config
|
|
238
|
+
self.opencti_token = opencti_token
|
|
239
|
+
self.listen_protocol = listen_protocol
|
|
240
|
+
self.listen_protocol_api_ssl = listen_protocol_api_ssl
|
|
241
|
+
self.listen_protocol_api_path = listen_protocol_api_path
|
|
242
|
+
self.listen_protocol_api_port = listen_protocol_api_port
|
|
199
243
|
self.connector_applicant_id = applicant_id
|
|
200
244
|
self.host = connector_config["connection"]["host"]
|
|
201
245
|
self.vhost = connector_config["connection"]["vhost"]
|
|
@@ -375,52 +419,122 @@ class ListenQueue(threading.Thread):
|
|
|
375
419
|
"Failing reporting the processing"
|
|
376
420
|
)
|
|
377
421
|
|
|
422
|
+
async def _http_process_callback(self, request: Request):
|
|
423
|
+
# 01. Check the authentication
|
|
424
|
+
authorization: str = request.headers.get("Authorization", "")
|
|
425
|
+
items = authorization.split() if isinstance(authorization, str) else []
|
|
426
|
+
if (
|
|
427
|
+
len(items) != 2
|
|
428
|
+
or items[0].lower() != "bearer"
|
|
429
|
+
or items[1] != self.opencti_token
|
|
430
|
+
):
|
|
431
|
+
return JSONResponse(
|
|
432
|
+
status_code=401, content={"error": "Invalid credentials"}
|
|
433
|
+
)
|
|
434
|
+
# 02. Parse the data and execute
|
|
435
|
+
try:
|
|
436
|
+
data = await request.json() # Get the JSON payload
|
|
437
|
+
except json.JSONDecodeError as e:
|
|
438
|
+
self.helper.connector_logger.error(
|
|
439
|
+
"Invalid JSON payload", {"cause": str(e)}
|
|
440
|
+
)
|
|
441
|
+
return JSONResponse(
|
|
442
|
+
status_code=400,
|
|
443
|
+
content={"error": "Invalid JSON payload"},
|
|
444
|
+
)
|
|
445
|
+
try:
|
|
446
|
+
self._data_handler(data)
|
|
447
|
+
except Exception as e:
|
|
448
|
+
self.helper.connector_logger.error(
|
|
449
|
+
"Error processing message", {"cause": str(e)}
|
|
450
|
+
)
|
|
451
|
+
return JSONResponse(
|
|
452
|
+
status_code=500,
|
|
453
|
+
content={"error": "Error processing message"},
|
|
454
|
+
)
|
|
455
|
+
# all good
|
|
456
|
+
return JSONResponse(
|
|
457
|
+
status_code=202, content={"message": "Message successfully received"}
|
|
458
|
+
)
|
|
459
|
+
|
|
378
460
|
def run(self) -> None:
|
|
379
|
-
self.
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
self.helper.connector_logger.info("ListenQueue connecting to rabbitMq.")
|
|
383
|
-
# Connect the broker
|
|
384
|
-
self.pika_credentials = pika.PlainCredentials(self.user, self.password)
|
|
385
|
-
self.pika_parameters = pika.ConnectionParameters(
|
|
386
|
-
heartbeat=10,
|
|
387
|
-
blocked_connection_timeout=30,
|
|
388
|
-
host=self.host,
|
|
389
|
-
port=self.port,
|
|
390
|
-
virtual_host=self.vhost,
|
|
391
|
-
credentials=self.pika_credentials,
|
|
392
|
-
ssl_options=(
|
|
393
|
-
pika.SSLOptions(create_mq_ssl_context(self.config), self.host)
|
|
394
|
-
if self.use_ssl
|
|
395
|
-
else None
|
|
396
|
-
),
|
|
397
|
-
)
|
|
398
|
-
self.pika_connection = pika.BlockingConnection(self.pika_parameters)
|
|
399
|
-
self.channel = self.pika_connection.channel()
|
|
461
|
+
if self.listen_protocol == "AMQP":
|
|
462
|
+
self.helper.connector_logger.info("Starting ListenQueue thread")
|
|
463
|
+
while not self.exit_event.is_set():
|
|
400
464
|
try:
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
465
|
+
self.helper.connector_logger.info(
|
|
466
|
+
"ListenQueue connecting to rabbitMq."
|
|
467
|
+
)
|
|
468
|
+
# Connect the broker
|
|
469
|
+
self.pika_credentials = pika.PlainCredentials(
|
|
470
|
+
self.user, self.password
|
|
471
|
+
)
|
|
472
|
+
self.pika_parameters = pika.ConnectionParameters(
|
|
473
|
+
heartbeat=10,
|
|
474
|
+
blocked_connection_timeout=30,
|
|
475
|
+
host=self.host,
|
|
476
|
+
port=self.port,
|
|
477
|
+
virtual_host=self.vhost,
|
|
478
|
+
credentials=self.pika_credentials,
|
|
479
|
+
ssl_options=(
|
|
480
|
+
pika.SSLOptions(
|
|
481
|
+
create_mq_ssl_context(self.config), self.host
|
|
482
|
+
)
|
|
483
|
+
if self.use_ssl
|
|
484
|
+
else None
|
|
485
|
+
),
|
|
486
|
+
)
|
|
487
|
+
self.pika_connection = pika.BlockingConnection(self.pika_parameters)
|
|
488
|
+
self.channel = self.pika_connection.channel()
|
|
489
|
+
try:
|
|
490
|
+
# confirm_delivery is only for cluster mode rabbitMQ
|
|
491
|
+
# when not in cluster mode this line raise an exception
|
|
492
|
+
self.channel.confirm_delivery()
|
|
493
|
+
except Exception as err: # pylint: disable=broad-except
|
|
494
|
+
self.helper.connector_logger.debug(str(err))
|
|
495
|
+
self.channel.basic_qos(prefetch_count=1)
|
|
496
|
+
assert self.channel is not None
|
|
497
|
+
self.channel.basic_consume(
|
|
498
|
+
queue=self.queue_name, on_message_callback=self._process_message
|
|
499
|
+
)
|
|
500
|
+
self.channel.start_consuming()
|
|
404
501
|
except Exception as err: # pylint: disable=broad-except
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
self.pika_connection.close()
|
|
415
|
-
except Exception as errInException:
|
|
416
|
-
self.helper.connector_logger.debug(
|
|
417
|
-
type(errInException).__name__, {"reason": str(errInException)}
|
|
502
|
+
try:
|
|
503
|
+
self.pika_connection.close()
|
|
504
|
+
except Exception as errInException:
|
|
505
|
+
self.helper.connector_logger.debug(
|
|
506
|
+
type(errInException).__name__,
|
|
507
|
+
{"reason": str(errInException)},
|
|
508
|
+
)
|
|
509
|
+
self.helper.connector_logger.error(
|
|
510
|
+
type(err).__name__, {"reason": str(err)}
|
|
418
511
|
)
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
512
|
+
# Wait some time and then retry ListenQueue again.
|
|
513
|
+
time.sleep(10)
|
|
514
|
+
elif self.listen_protocol == "API":
|
|
515
|
+
self.helper.connector_logger.info("Starting Listen HTTP thread")
|
|
516
|
+
app.add_api_route(
|
|
517
|
+
self.listen_protocol_api_path,
|
|
518
|
+
self._http_process_callback,
|
|
519
|
+
methods=["POST"],
|
|
520
|
+
)
|
|
521
|
+
config = uvicorn.Config(
|
|
522
|
+
app,
|
|
523
|
+
host="0.0.0.0",
|
|
524
|
+
port=self.listen_protocol_api_port,
|
|
525
|
+
reload=False,
|
|
526
|
+
log_config=None,
|
|
527
|
+
log_level=None,
|
|
528
|
+
)
|
|
529
|
+
config.load() # Manually calling the .load() to trigger needed actions outside HTTPS
|
|
530
|
+
if self.listen_protocol_api_ssl:
|
|
531
|
+
ssl_ctx = create_callback_ssl_context(self.config)
|
|
532
|
+
config.ssl = ssl_ctx
|
|
533
|
+
server = uvicorn.Server(config)
|
|
534
|
+
server.run()
|
|
535
|
+
|
|
536
|
+
else:
|
|
537
|
+
raise ValueError("Unsupported listen protocol type")
|
|
424
538
|
|
|
425
539
|
def stop(self):
|
|
426
540
|
self.helper.connector_logger.info("Preparing ListenQueue for clean shutdown")
|
|
@@ -790,8 +904,39 @@ class OpenCTIConnectorHelper: # pylint: disable=too-many-public-methods
|
|
|
790
904
|
self.connect_id = get_config_variable(
|
|
791
905
|
"CONNECTOR_ID", ["connector", "id"], config
|
|
792
906
|
)
|
|
793
|
-
self.
|
|
794
|
-
"
|
|
907
|
+
self.listen_protocol = get_config_variable(
|
|
908
|
+
"CONNECTOR_LISTEN_PROTOCOL",
|
|
909
|
+
["connector", "listen_protocol"],
|
|
910
|
+
config,
|
|
911
|
+
default="AMQP",
|
|
912
|
+
).upper()
|
|
913
|
+
self.listen_protocol_api_port = get_config_variable(
|
|
914
|
+
"CONNECTOR_LISTEN_PROTOCOL_API_PORT",
|
|
915
|
+
["connector", "listen_protocol_api_port"],
|
|
916
|
+
config,
|
|
917
|
+
default=7070,
|
|
918
|
+
)
|
|
919
|
+
self.listen_protocol_api_path = get_config_variable(
|
|
920
|
+
"CONNECTOR_LISTEN_PROTOCOL_API_PATH",
|
|
921
|
+
["connector", "listen_protocol_api_path"],
|
|
922
|
+
config,
|
|
923
|
+
default="/api/callback",
|
|
924
|
+
)
|
|
925
|
+
self.listen_protocol_api_ssl = get_config_variable(
|
|
926
|
+
"CONNECTOR_LISTEN_PROTOCOL_API_SSL",
|
|
927
|
+
["connector", "listen_protocol_api_ssl"],
|
|
928
|
+
config,
|
|
929
|
+
default=False,
|
|
930
|
+
)
|
|
931
|
+
self.listen_protocol_api_uri = get_config_variable(
|
|
932
|
+
"CONNECTOR_LISTEN_PROTOCOL_API_URI",
|
|
933
|
+
["connector", "listen_protocol_api_uri"],
|
|
934
|
+
config,
|
|
935
|
+
default=(
|
|
936
|
+
"https://127.0.0.1:7070"
|
|
937
|
+
if self.listen_protocol_api_ssl
|
|
938
|
+
else "http://127.0.0.1:7070"
|
|
939
|
+
),
|
|
795
940
|
)
|
|
796
941
|
self.connect_type = get_config_variable(
|
|
797
942
|
"CONNECTOR_TYPE", ["connector", "type"], config
|
|
@@ -957,6 +1102,7 @@ class OpenCTIConnectorHelper: # pylint: disable=too-many-public-methods
|
|
|
957
1102
|
self.connect_auto,
|
|
958
1103
|
self.connect_only_contextual,
|
|
959
1104
|
playbook_compatible,
|
|
1105
|
+
self.listen_protocol_api_uri + self.listen_protocol_api_path,
|
|
960
1106
|
)
|
|
961
1107
|
connector_configuration = self.api.connector.register(self.connector)
|
|
962
1108
|
self.connector_logger.info(
|
|
@@ -972,6 +1118,25 @@ class OpenCTIConnectorHelper: # pylint: disable=too-many-public-methods
|
|
|
972
1118
|
self.connector_state = connector_configuration["connector_state"]
|
|
973
1119
|
self.connector_config = connector_configuration["config"]
|
|
974
1120
|
|
|
1121
|
+
# Configure the push information protocol
|
|
1122
|
+
self.queue_protocol = get_config_variable(
|
|
1123
|
+
env_var="CONNECTOR_QUEUE_PROTOCOL",
|
|
1124
|
+
yaml_path=["connector", "queue_protocol"],
|
|
1125
|
+
config=config,
|
|
1126
|
+
)
|
|
1127
|
+
if not self.queue_protocol: # for backwards compatibility
|
|
1128
|
+
self.queue_protocol = get_config_variable(
|
|
1129
|
+
env_var="QUEUE_PROTOCOL",
|
|
1130
|
+
yaml_path=["connector", "queue_protocol"],
|
|
1131
|
+
config=config,
|
|
1132
|
+
)
|
|
1133
|
+
if self.queue_protocol:
|
|
1134
|
+
self.connector_logger.error(
|
|
1135
|
+
"QUEUE_PROTOCOL is deprecated, please use CONNECTOR_QUEUE_PROTOCOL instead."
|
|
1136
|
+
)
|
|
1137
|
+
if not self.queue_protocol:
|
|
1138
|
+
self.queue_protocol = "amqp"
|
|
1139
|
+
|
|
975
1140
|
# Overwrite connector config for RabbitMQ if given manually / in conf
|
|
976
1141
|
self.connector_config["connection"]["host"] = get_config_variable(
|
|
977
1142
|
"MQ_HOST",
|
|
@@ -1441,9 +1606,14 @@ class OpenCTIConnectorHelper: # pylint: disable=too-many-public-methods
|
|
|
1441
1606
|
|
|
1442
1607
|
self.listen_queue = ListenQueue(
|
|
1443
1608
|
self,
|
|
1609
|
+
self.opencti_token,
|
|
1444
1610
|
self.config,
|
|
1445
1611
|
self.connector_config,
|
|
1446
1612
|
self.applicant_id,
|
|
1613
|
+
self.listen_protocol,
|
|
1614
|
+
self.listen_protocol_api_ssl,
|
|
1615
|
+
self.listen_protocol_api_path,
|
|
1616
|
+
self.listen_protocol_api_port,
|
|
1447
1617
|
message_callback,
|
|
1448
1618
|
)
|
|
1449
1619
|
self.listen_queue.start()
|
|
@@ -1742,13 +1912,13 @@ class OpenCTIConnectorHelper: # pylint: disable=too-many-public-methods
|
|
|
1742
1912
|
raise ValueError("Nothing to import")
|
|
1743
1913
|
|
|
1744
1914
|
if bundle_send_to_queue:
|
|
1745
|
-
if work_id:
|
|
1746
|
-
self.api.work.
|
|
1747
|
-
if draft_id:
|
|
1748
|
-
self.api.work.add_draft_context(work_id, draft_id)
|
|
1915
|
+
if work_id and draft_id:
|
|
1916
|
+
self.api.work.add_draft_context(work_id, draft_id)
|
|
1749
1917
|
if entities_types is None:
|
|
1750
1918
|
entities_types = []
|
|
1751
1919
|
if self.queue_protocol == "amqp":
|
|
1920
|
+
if work_id:
|
|
1921
|
+
self.api.work.add_expectations(work_id, expectations_number)
|
|
1752
1922
|
pika_credentials = pika.PlainCredentials(
|
|
1753
1923
|
self.connector_config["connection"]["user"],
|
|
1754
1924
|
self.connector_config["connection"]["pass"],
|
|
@@ -1791,7 +1961,7 @@ class OpenCTIConnectorHelper: # pylint: disable=too-many-public-methods
|
|
|
1791
1961
|
pika_connection.close()
|
|
1792
1962
|
elif self.queue_protocol == "api":
|
|
1793
1963
|
self.api.send_bundle_to_api(
|
|
1794
|
-
connector_id=self.connector_id, bundle=bundle
|
|
1964
|
+
connector_id=self.connector_id, bundle=bundle, work_id=work_id
|
|
1795
1965
|
)
|
|
1796
1966
|
else:
|
|
1797
1967
|
raise ValueError(
|
|
@@ -178,6 +178,7 @@ class ThreatActor:
|
|
|
178
178
|
order_by = kwargs.get("orderBy", None)
|
|
179
179
|
order_mode = kwargs.get("orderMode", None)
|
|
180
180
|
custom_attributes = kwargs.get("customAttributes", None)
|
|
181
|
+
get_all = kwargs.get("getAll", False)
|
|
181
182
|
with_pagination = kwargs.get("withPagination", False)
|
|
182
183
|
|
|
183
184
|
self.opencti.app_logger.info(
|
|
@@ -216,9 +217,31 @@ class ThreatActor:
|
|
|
216
217
|
"orderMode": order_mode,
|
|
217
218
|
},
|
|
218
219
|
)
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
220
|
+
if get_all:
|
|
221
|
+
final_data = []
|
|
222
|
+
data = self.opencti.process_multiple(result["data"]["threatActors"])
|
|
223
|
+
final_data = final_data + data
|
|
224
|
+
while result["data"]["threatActors"]["pageInfo"]["hasNextPage"]:
|
|
225
|
+
after = result["data"]["threatActors"]["pageInfo"]["endCursor"]
|
|
226
|
+
self.opencti.app_logger.info("Listing threatActors", {"after": after})
|
|
227
|
+
result = self.opencti.query(
|
|
228
|
+
query,
|
|
229
|
+
{
|
|
230
|
+
"filters": filters,
|
|
231
|
+
"search": search,
|
|
232
|
+
"first": first,
|
|
233
|
+
"after": after,
|
|
234
|
+
"orderBy": order_by,
|
|
235
|
+
"orderMode": order_mode,
|
|
236
|
+
},
|
|
237
|
+
)
|
|
238
|
+
data = self.opencti.process_multiple(result["data"]["threatActors"])
|
|
239
|
+
final_data = final_data + data
|
|
240
|
+
return final_data
|
|
241
|
+
else:
|
|
242
|
+
return self.opencti.process_multiple(
|
|
243
|
+
result["data"]["threatActors"], with_pagination
|
|
244
|
+
)
|
|
222
245
|
|
|
223
246
|
def read(self, **kwargs) -> Union[dict, None]:
|
|
224
247
|
"""Read a Threat-Actor object
|
|
@@ -171,6 +171,7 @@ class ThreatActorGroup:
|
|
|
171
171
|
order_by = kwargs.get("orderBy", None)
|
|
172
172
|
order_mode = kwargs.get("orderMode", None)
|
|
173
173
|
custom_attributes = kwargs.get("customAttributes", None)
|
|
174
|
+
get_all = kwargs.get("getAll", False)
|
|
174
175
|
with_pagination = kwargs.get("withPagination", False)
|
|
175
176
|
|
|
176
177
|
self.opencti.app_logger.info(
|
|
@@ -209,9 +210,35 @@ class ThreatActorGroup:
|
|
|
209
210
|
"orderMode": order_mode,
|
|
210
211
|
},
|
|
211
212
|
)
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
213
|
+
if get_all:
|
|
214
|
+
final_data = []
|
|
215
|
+
data = self.opencti.process_multiple(result["data"]["threatActorsGroup"])
|
|
216
|
+
final_data = final_data + data
|
|
217
|
+
while result["data"]["threatActorsGroup"]["pageInfo"]["hasNextPage"]:
|
|
218
|
+
after = result["data"]["threatActorsGroup"]["pageInfo"]["endCursor"]
|
|
219
|
+
self.opencti.app_logger.info(
|
|
220
|
+
"Listing threatActorsGroup", {"after": after}
|
|
221
|
+
)
|
|
222
|
+
result = self.opencti.query(
|
|
223
|
+
query,
|
|
224
|
+
{
|
|
225
|
+
"filters": filters,
|
|
226
|
+
"search": search,
|
|
227
|
+
"first": first,
|
|
228
|
+
"after": after,
|
|
229
|
+
"orderBy": order_by,
|
|
230
|
+
"orderMode": order_mode,
|
|
231
|
+
},
|
|
232
|
+
)
|
|
233
|
+
data = self.opencti.process_multiple(
|
|
234
|
+
result["data"]["threatActorsGroup"]
|
|
235
|
+
)
|
|
236
|
+
final_data = final_data + data
|
|
237
|
+
return final_data
|
|
238
|
+
else:
|
|
239
|
+
return self.opencti.process_multiple(
|
|
240
|
+
result["data"]["threatActorsGroup"], with_pagination
|
|
241
|
+
)
|
|
215
242
|
|
|
216
243
|
def read(self, **kwargs) -> Union[dict, None]:
|
|
217
244
|
"""Read a Threat-Actor-Group object
|
|
@@ -171,6 +171,7 @@ class ThreatActorIndividual:
|
|
|
171
171
|
order_by = kwargs.get("orderBy", None)
|
|
172
172
|
order_mode = kwargs.get("orderMode", None)
|
|
173
173
|
custom_attributes = kwargs.get("customAttributes", None)
|
|
174
|
+
get_all = kwargs.get("getAll", False)
|
|
174
175
|
with_pagination = kwargs.get("withPagination", False)
|
|
175
176
|
|
|
176
177
|
self.opencti.app_logger.info(
|
|
@@ -210,9 +211,39 @@ class ThreatActorIndividual:
|
|
|
210
211
|
"orderMode": order_mode,
|
|
211
212
|
},
|
|
212
213
|
)
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
214
|
+
if get_all:
|
|
215
|
+
final_data = []
|
|
216
|
+
data = self.opencti.process_multiple(
|
|
217
|
+
result["data"]["threatActorsIndividuals"]
|
|
218
|
+
)
|
|
219
|
+
final_data = final_data + data
|
|
220
|
+
while result["data"]["threatActorsIndividuals"]["pageInfo"]["hasNextPage"]:
|
|
221
|
+
after = result["data"]["threatActorsIndividuals"]["pageInfo"][
|
|
222
|
+
"endCursor"
|
|
223
|
+
]
|
|
224
|
+
self.opencti.app_logger.info(
|
|
225
|
+
"Listing threatActorsIndividuals", {"after": after}
|
|
226
|
+
)
|
|
227
|
+
result = self.opencti.query(
|
|
228
|
+
query,
|
|
229
|
+
{
|
|
230
|
+
"filters": filters,
|
|
231
|
+
"search": search,
|
|
232
|
+
"first": first,
|
|
233
|
+
"after": after,
|
|
234
|
+
"orderBy": order_by,
|
|
235
|
+
"orderMode": order_mode,
|
|
236
|
+
},
|
|
237
|
+
)
|
|
238
|
+
data = self.opencti.process_multiple(
|
|
239
|
+
result["data"]["threatActorsIndividuals"]
|
|
240
|
+
)
|
|
241
|
+
final_data = final_data + data
|
|
242
|
+
return final_data
|
|
243
|
+
else:
|
|
244
|
+
return self.opencti.process_multiple(
|
|
245
|
+
result["data"]["threatActorsIndividuals"], with_pagination
|
|
246
|
+
)
|
|
216
247
|
|
|
217
248
|
def read(self, **kwargs) -> Union[dict, None]:
|
|
218
249
|
"""Read a Threat-Actor-Individual object
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.2
|
|
2
2
|
Name: pycti
|
|
3
|
-
Version: 6.5.
|
|
3
|
+
Version: 6.5.7
|
|
4
4
|
Summary: Python API client for OpenCTI.
|
|
5
5
|
Home-page: https://github.com/OpenCTI-Platform/client-python
|
|
6
6
|
Author: Filigran
|
|
@@ -28,13 +28,15 @@ Requires-Dist: python-magic~=0.4.27; sys_platform == "linux" or sys_platform ==
|
|
|
28
28
|
Requires-Dist: python-magic-bin~=0.4.14; sys_platform == "win32"
|
|
29
29
|
Requires-Dist: python_json_logger~=2.0.4
|
|
30
30
|
Requires-Dist: PyYAML~=6.0
|
|
31
|
-
Requires-Dist: requests
|
|
31
|
+
Requires-Dist: requests<=2.32.3,>=2.32.0
|
|
32
32
|
Requires-Dist: setuptools~=71.1.0
|
|
33
33
|
Requires-Dist: cachetools~=5.5.0
|
|
34
34
|
Requires-Dist: prometheus-client~=0.21.1
|
|
35
35
|
Requires-Dist: opentelemetry-api<=1.30.0,>=1.22.0
|
|
36
36
|
Requires-Dist: opentelemetry-sdk<=1.30.0,>=1.22.0
|
|
37
37
|
Requires-Dist: deprecation~=2.1.0
|
|
38
|
+
Requires-Dist: fastapi<0.116.0,>=0.115.8
|
|
39
|
+
Requires-Dist: uvicorn[standard]<0.35.0,>=0.33.0
|
|
38
40
|
Requires-Dist: filigran-sseclient>=1.0.2
|
|
39
41
|
Requires-Dist: stix2~=3.0.1
|
|
40
42
|
Provides-Extra: dev
|
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
pycti/__init__.py,sha256=
|
|
1
|
+
pycti/__init__.py,sha256=tUaG9tFoyfxztiAONz0rD2yJ8qbBAR_uJpC7PRpqvdI,5218
|
|
2
2
|
pycti/api/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
3
|
-
pycti/api/opencti_api_client.py,sha256=
|
|
4
|
-
pycti/api/opencti_api_connector.py,sha256=
|
|
3
|
+
pycti/api/opencti_api_client.py,sha256=l-k8UclU4-yyc0rn_N3CZcRDiHUmD4-boOnQ6mngCO8,32848
|
|
4
|
+
pycti/api/opencti_api_connector.py,sha256=ZCatDnkwqGvNwtagd3f3MziG4nuplGj7BEw8xSXh47o,5491
|
|
5
5
|
pycti/api/opencti_api_playbook.py,sha256=456We78vESukfSOi_CctfZ9dbBJEi76EHClRc2f21Js,1628
|
|
6
6
|
pycti/api/opencti_api_work.py,sha256=qIRJMCfyC9odXf7LMRg9ImYizqF2WHUOU7Ty5IUFGg8,8351
|
|
7
7
|
pycti/connector/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
8
|
-
pycti/connector/opencti_connector.py,sha256=
|
|
9
|
-
pycti/connector/opencti_connector_helper.py,sha256=
|
|
8
|
+
pycti/connector/opencti_connector.py,sha256=8lCZFvcA9-S1x6vFl756hgWAlzKfrnq-C4AIdDJr-Kg,2715
|
|
9
|
+
pycti/connector/opencti_connector_helper.py,sha256=r8xu5frk0zD0HUBn5xG9aXH1H5QvpydXsC7fUzq_85k,87688
|
|
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=QXJaMMZlnVCxxHGZNGSKPLmHL3TgM08aUIS8SypmIek,22193
|
|
@@ -48,9 +48,9 @@ pycti/entities/opencti_stix_nested_ref_relationship.py,sha256=7USJlfTanPFY16aFIH
|
|
|
48
48
|
pycti/entities/opencti_stix_object_or_stix_relationship.py,sha256=5qutzML6SyYzDhZ-QpI9Vh23hzLEs-xeFAAZOpGHZ2g,18049
|
|
49
49
|
pycti/entities/opencti_stix_sighting_relationship.py,sha256=PO4RK3UBkA2b_xcqjiqWnLSKgvaQy291al_yunQ96h4,28736
|
|
50
50
|
pycti/entities/opencti_task.py,sha256=rYfiUKtsSEq8A-Qa2wi7QBias1oiiHsaq3g_aLcWgl0,25290
|
|
51
|
-
pycti/entities/opencti_threat_actor.py,sha256=
|
|
52
|
-
pycti/entities/opencti_threat_actor_group.py,sha256=
|
|
53
|
-
pycti/entities/opencti_threat_actor_individual.py,sha256=
|
|
51
|
+
pycti/entities/opencti_threat_actor.py,sha256=vFPeo0pOYSqHBKVlWc4o8RjuP2PP0A09KWU6rsYXnvA,11201
|
|
52
|
+
pycti/entities/opencti_threat_actor_group.py,sha256=ANvs1C_ugpYv_jNwW9mOwn4jXKTmkV7tL5wdPgt3PXA,20661
|
|
53
|
+
pycti/entities/opencti_threat_actor_individual.py,sha256=i41YIdC7Mc5qMzdYmzItI1qVdDqngqsA1kMWGngRDGo,21011
|
|
54
54
|
pycti/entities/opencti_tool.py,sha256=YbOp0Ur5Do7ToLzfIKGX-MtlBQf-Dt9Qtgk1lI9Q7aU,15295
|
|
55
55
|
pycti/entities/opencti_vocabulary.py,sha256=xupdHJ6TznCmvI3sVYU261SnfblSNc1nwg19MG9yrao,6499
|
|
56
56
|
pycti/entities/opencti_vulnerability.py,sha256=ssMH7EB7WC--Nv2bq-D-_wLBGXMgP3ZLK-X8SslpVJQ,22614
|
|
@@ -67,8 +67,8 @@ pycti/utils/opencti_stix2_identifier.py,sha256=k8L1z4q1xdCBfxqUba4YS_kT-MmbJFxYh
|
|
|
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.7.dist-info/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
|
|
71
|
+
pycti-6.5.7.dist-info/METADATA,sha256=PsYaRUnQ7Jo2kvJnQpFyxx2UA_XkPsUCleHi9GysvHg,5542
|
|
72
|
+
pycti-6.5.7.dist-info/WHEEL,sha256=52BFRY2Up02UkjOa29eZOS2VxUrpPORXg1pkohGGUS8,91
|
|
73
|
+
pycti-6.5.7.dist-info/top_level.txt,sha256=cqEpxitAhHP4VgSA6xmrak6Yk9MeBkwoMTB6k7d2ZnE,6
|
|
74
|
+
pycti-6.5.7.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|