Skip to content
This repository was archived by the owner on Dec 5, 2025. It is now read-only.

Commit 1e31a59

Browse files
Merge branch 'master' into renovate/pre-commit-3.x
2 parents bc8704f + d55b812 commit 1e31a59

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

53 files changed

+753
-130
lines changed

.drone.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -63,9 +63,9 @@ steps:
6363

6464
services:
6565
- name: redis
66-
image: redis:7.2.5
66+
image: redis:7.4.1
6767
- name: elastic
68-
image: docker.elastic.co/elasticsearch/elasticsearch:8.14.3
68+
image: docker.elastic.co/elasticsearch/elasticsearch:8.15.3
6969
environment:
7070
discovery.type: single-node
7171
xpack.security.enabled: false
@@ -77,7 +77,7 @@ services:
7777
MINIO_ROOT_PASSWORD: ChangeMe
7878
command: [ server, /data ]
7979
- name: rabbitmq
80-
image: rabbitmq:3.13-management
80+
image: rabbitmq:4.0-management
8181
- name: opencti
8282
image: nikolaik/python-nodejs:python3.10-nodejs18-alpine
8383
environment:

docs/requirements.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
autoapi==2.0.1
22
sphinx==7.4.6
33
sphinx-autodoc-typehints==2.2.3
4-
sphinx_rtd_theme==2.0.0
4+
sphinx_rtd_theme==3.0.2

pycti/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# -*- coding: utf-8 -*-
2-
__version__ = "6.3.8"
2+
__version__ = "6.4.5"
33

44
from .api.opencti_api_client import OpenCTIApiClient
55
from .api.opencti_api_connector import OpenCTIApiConnector

pycti/api/opencti_api_client.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,9 @@ def set_playbook_id_header(self, playbook_id):
213213
def set_event_id(self, event_id):
214214
self.request_headers["opencti-event-id"] = event_id
215215

216+
def set_draft_id(self, draft_id):
217+
self.request_headers["opencti-draft-id"] = draft_id
218+
216219
def set_synchronized_upsert_header(self, synchronized):
217220
self.request_headers["synchronized-upsert"] = (
218221
"true" if synchronized is True else "false"
@@ -666,6 +669,34 @@ def upload_file(self, **kwargs):
666669
self.app_logger.error("[upload] Missing parameter: file_name")
667670
return None
668671

672+
def create_draft(self, **kwargs):
673+
"""create a draft in OpenCTI API
674+
:param `**kwargs`: arguments for file name creating draft (required: `draft_name`)
675+
:return: returns the query response for the draft creation
676+
:rtype: id
677+
"""
678+
679+
draft_name = kwargs.get("draft_name", None)
680+
entity_id = kwargs.get("entity_id", None)
681+
682+
if draft_name is not None:
683+
self.app_logger.info("Creating a draft.")
684+
query = """
685+
mutation draftWorkspaceAdd($input: DraftWorkspaceAddInput!) {
686+
draftWorkspaceAdd(input: $input) {
687+
id
688+
}
689+
}
690+
"""
691+
queryResult = self.query(
692+
query,
693+
{"input": {"name": draft_name, "entity_id": entity_id}},
694+
)
695+
return queryResult["data"]["draftWorkspaceAdd"]["id"]
696+
else:
697+
self.app_logger.error("[create_draft] Missing parameter: draft_name")
698+
return None
699+
669700
def upload_pending_file(self, **kwargs):
670701
"""upload a file to OpenCTI API
671702

pycti/connector/opencti_connector_helper.py

Lines changed: 35 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -253,10 +253,18 @@ def _data_handler(self, json_data) -> None:
253253
event_data = json_data["event"]
254254
entity_id = event_data.get("entity_id")
255255
entity_type = event_data.get("entity_type")
256+
validation_mode = event_data.get("validation_mode", "workbench")
256257
# Set the API headers
257-
work_id = json_data["internal"]["work_id"]
258+
internal_data = json_data["internal"]
259+
work_id = internal_data["work_id"]
260+
draft_id = internal_data.get("draft_id", "")
258261
self.helper.work_id = work_id
259262

263+
self.helper.validation_mode = validation_mode
264+
self.helper.draft_id = draft_id
265+
self.helper.api.set_draft_id(draft_id)
266+
self.helper.api_impersonate.set_draft_id(draft_id)
267+
260268
self.helper.playbook = None
261269
self.helper.enrichment_shared_organizations = None
262270
if self.helper.connect_type == "INTERNAL_ENRICHMENT":
@@ -952,6 +960,8 @@ def __init__(self, config: Dict, playbook_compatible=False) -> None:
952960
"Connector registered with ID", {"id": self.connect_id}
953961
)
954962
self.work_id = None
963+
self.validation_mode = "workbench"
964+
self.draft_id = None
955965
self.playbook = None
956966
self.enrichment_shared_organizations = None
957967
self.connector_id = connector_configuration["id"]
@@ -1550,6 +1560,7 @@ def send_stix2_bundle(self, bundle: str, **kwargs) -> list:
15501560
"""send a stix2 bundle to the API
15511561
15521562
:param work_id: a valid work id
1563+
:param draft_id: a draft context to send the bundle to
15531564
:param bundle: valid stix2 bundle
15541565
:type bundle:
15551566
:param entities_types: list of entities, defaults to None
@@ -1563,6 +1574,8 @@ def send_stix2_bundle(self, bundle: str, **kwargs) -> list:
15631574
:rtype: list
15641575
"""
15651576
work_id = kwargs.get("work_id", self.work_id)
1577+
validation_mode = kwargs.get("validation_mode", self.validation_mode)
1578+
draft_id = kwargs.get("draft_id", self.draft_id)
15661579
entities_types = kwargs.get("entities_types", None)
15671580
update = kwargs.get("update", False)
15681581
event_version = kwargs.get("event_version", None)
@@ -1627,14 +1640,23 @@ def send_stix2_bundle(self, bundle: str, **kwargs) -> list:
16271640
# Upload workbench in case of pending validation
16281641
if not file_name and work_id:
16291642
file_name = f"{work_id}.json"
1643+
16301644
if self.connect_validate_before_import and not bypass_validation and file_name:
1631-
self.api.upload_pending_file(
1632-
file_name=file_name,
1633-
data=bundle,
1634-
mime_type="application/json",
1635-
entity_id=entity_id,
1636-
)
1637-
return []
1645+
if validation_mode == "workbench":
1646+
self.api.upload_pending_file(
1647+
file_name=file_name,
1648+
data=bundle,
1649+
mime_type="application/json",
1650+
entity_id=entity_id,
1651+
)
1652+
return []
1653+
elif validation_mode == "draft" and not draft_id:
1654+
draft_id = self.api.create_draft(
1655+
draft_name=file_name, entity_id=entity_id
1656+
)
1657+
if not draft_id:
1658+
self.connector_logger.error("Draft couldn't be created")
1659+
return []
16381660

16391661
# If directory setup, write the bundle to the target directory
16401662
if bundle_send_to_directory and bundle_send_to_directory_path is not None:
@@ -1749,6 +1771,7 @@ def send_stix2_bundle(self, bundle: str, **kwargs) -> list:
17491771
entities_types=entities_types,
17501772
sequence=sequence,
17511773
update=update,
1774+
draft_id=draft_id,
17521775
)
17531776
channel.close()
17541777
pika_connection.close()
@@ -1774,11 +1797,14 @@ def _send_bundle(self, channel, bundle, **kwargs) -> None:
17741797
:type entities_types: list, optional
17751798
:param update: whether to update data in the database, defaults to False
17761799
:type update: bool, optional
1800+
:param draft_id: if draft_id is set, bundle must be set in draft context
1801+
:type draft_id:
17771802
"""
17781803
work_id = kwargs.get("work_id", None)
17791804
sequence = kwargs.get("sequence", 0)
17801805
update = kwargs.get("update", False)
17811806
entities_types = kwargs.get("entities_types", None)
1807+
draft_id = kwargs.get("draft_id", None)
17821808

17831809
if entities_types is None:
17841810
entities_types = []
@@ -1800,6 +1826,7 @@ def _send_bundle(self, channel, bundle, **kwargs) -> None:
18001826
"utf-8"
18011827
),
18021828
"update": update,
1829+
"draft_id": draft_id,
18031830
}
18041831
if work_id is not None:
18051832
message["work_id"] = work_id

pycti/entities/indicator/opencti_indicator_properties.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,10 @@
9292
x_opencti_score
9393
x_opencti_detection
9494
x_opencti_main_observable_type
95+
x_opencti_observable_values {
96+
type
97+
value
98+
}
9599
x_mitre_platforms
96100
observables {
97101
edges {
@@ -220,6 +224,10 @@
220224
x_opencti_score
221225
x_opencti_detection
222226
x_opencti_main_observable_type
227+
x_opencti_observable_values {
228+
type
229+
value
230+
}
223231
x_mitre_platforms
224232
observables {
225233
edges {

pycti/entities/opencti_attack_pattern.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -222,15 +222,19 @@ def __init__(self, opencti):
222222

223223
@staticmethod
224224
def generate_id(name, x_mitre_id=None):
225-
name = name.lower().strip()
226225
if x_mitre_id is not None:
227226
data = {"x_mitre_id": x_mitre_id}
228227
else:
229-
data = {"name": name}
228+
data = {"name": name.lower().strip()}
230229
data = canonicalize(data, utf8=False)
231230
id = str(uuid.uuid5(uuid.UUID("00abedb4-aa42-466c-9c01-fed23315a9b7"), data))
232231
return "attack-pattern--" + id
233232

233+
@staticmethod
234+
def generate_id_from_data(data):
235+
external_id = data.get("x_mitre_id") or data.get("x_opencti_external_id")
236+
return AttackPattern.generate_id(data.get("name"), external_id)
237+
234238
"""
235239
List Attack-Pattern objects
236240

pycti/entities/opencti_campaign.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,10 @@ def generate_id(name):
216216
id = str(uuid.uuid5(uuid.UUID("00abedb4-aa42-466c-9c01-fed23315a9b7"), data))
217217
return "campaign--" + id
218218

219+
@staticmethod
220+
def generate_id_from_data(data):
221+
return Campaign.generate_id(data["name"])
222+
219223
"""
220224
List Campaign objects
221225

pycti/entities/opencti_case_incident.py

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -461,6 +461,10 @@ def generate_id(name, created):
461461
id = str(uuid.uuid5(uuid.UUID("00abedb4-aa42-466c-9c01-fed23315a9b7"), data))
462462
return "case-incident--" + id
463463

464+
@staticmethod
465+
def generate_id_from_data(data):
466+
return CaseIncident.generate_id(data["name"], data["created"])
467+
464468
"""
465469
List Case Incident objects
466470
@@ -686,6 +690,7 @@ def create(self, **kwargs):
686690
priority = kwargs.get("priority", None)
687691
x_opencti_stix_ids = kwargs.get("x_opencti_stix_ids", None)
688692
object_assignee = kwargs.get("objectAssignee", None)
693+
object_participant = kwargs.get("objectParticipant", None)
689694
granted_refs = kwargs.get("objectOrganization", None)
690695
response_types = kwargs.get("response_types", None)
691696
x_opencti_workflow_id = kwargs.get("x_opencti_workflow_id", None)
@@ -713,6 +718,7 @@ def create(self, **kwargs):
713718
"objectLabel": object_label,
714719
"objectOrganization": granted_refs,
715720
"objectAssignee": object_assignee,
721+
"objectParticipant": object_participant,
716722
"objects": objects,
717723
"externalReferences": external_references,
718724
"revoked": revoked,
@@ -861,7 +867,12 @@ def import_from_stix2(self, **kwargs):
861867
stix_object["x_opencti_assignee_ids"] = (
862868
self.opencti.get_attribute_in_extension("assignee_ids", stix_object)
863869
)
864-
870+
if "x_opencti_participant_ids" not in stix_object:
871+
stix_object["x_opencti_participant_ids"] = (
872+
self.opencti.get_attribute_in_extension(
873+
"participant_ids", stix_object
874+
)
875+
)
865876
return self.create(
866877
stix_id=stix_object["id"],
867878
createdBy=(
@@ -916,6 +927,11 @@ def import_from_stix2(self, **kwargs):
916927
if "x_opencti_assignee_ids" in stix_object
917928
else None
918929
),
930+
objectParticipant=(
931+
stix_object["x_opencti_participant_ids"]
932+
if "x_opencti_participant_ids" in stix_object
933+
else None
934+
),
919935
x_opencti_workflow_id=(
920936
stix_object["x_opencti_workflow_id"]
921937
if "x_opencti_workflow_id" in stix_object

pycti/entities/opencti_case_rfi.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -457,6 +457,10 @@ def generate_id(name, created):
457457
id = str(uuid.uuid5(uuid.UUID("00abedb4-aa42-466c-9c01-fed23315a9b7"), data))
458458
return "case-rfi--" + id
459459

460+
@staticmethod
461+
def generate_id_from_data(data):
462+
return CaseRfi.generate_id(data["name"], data["created"])
463+
460464
"""
461465
List Case Rfi objects
462466
@@ -670,6 +674,8 @@ def create(self, **kwargs):
670674
objects = kwargs.get("objects", None)
671675
object_marking = kwargs.get("objectMarking", None)
672676
object_label = kwargs.get("objectLabel", None)
677+
object_assignee = kwargs.get("objectAssignee", None)
678+
object_participant = kwargs.get("objectParticipant", None)
673679
external_references = kwargs.get("externalReferences", None)
674680
revoked = kwargs.get("revoked", None)
675681
confidence = kwargs.get("confidence", None)
@@ -705,6 +711,8 @@ def create(self, **kwargs):
705711
"objectMarking": object_marking,
706712
"objectLabel": object_label,
707713
"objectOrganization": granted_refs,
714+
"objectAssignee": object_assignee,
715+
"objectParticipant": object_participant,
708716
"objects": objects,
709717
"externalReferences": external_references,
710718
"revoked": revoked,
@@ -842,6 +850,16 @@ def import_from_stix2(self, **kwargs):
842850
stix_object["x_opencti_workflow_id"] = (
843851
self.opencti.get_attribute_in_extension("workflow_id", stix_object)
844852
)
853+
if "x_opencti_assignee_ids" not in stix_object:
854+
stix_object["x_opencti_assignee_ids"] = (
855+
self.opencti.get_attribute_in_extension("assignee_ids", stix_object)
856+
)
857+
if "x_opencti_participant_ids" not in stix_object:
858+
stix_object["x_opencti_participant_ids"] = (
859+
self.opencti.get_attribute_in_extension(
860+
"participant_ids", stix_object
861+
)
862+
)
845863

846864
return self.create(
847865
stix_id=stix_object["id"],
@@ -885,6 +903,16 @@ def import_from_stix2(self, **kwargs):
885903
if "x_opencti_granted_refs" in stix_object
886904
else None
887905
),
906+
objectAssignee=(
907+
stix_object["x_opencti_assignee_ids"]
908+
if "x_opencti_assignee_ids" in stix_object
909+
else None
910+
),
911+
objectParticipant=(
912+
stix_object["x_opencti_participant_ids"]
913+
if "x_opencti_participant_ids" in stix_object
914+
else None
915+
),
888916
x_opencti_workflow_id=(
889917
stix_object["x_opencti_workflow_id"]
890918
if "x_opencti_workflow_id" in stix_object

0 commit comments

Comments
 (0)