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

Commit 6799e12

Browse files
[client] Improve relationship standard id generation (#659)
1 parent 69450d7 commit 6799e12

File tree

5 files changed

+81
-19
lines changed

5 files changed

+81
-19
lines changed

pycti/entities/opencti_stix_core_relationship.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -336,6 +336,7 @@ def generate_id(
336336
start_time = start_time.isoformat()
337337
if isinstance(stop_time, datetime.datetime):
338338
stop_time = stop_time.isoformat()
339+
339340
if start_time is not None and stop_time is not None:
340341
data = {
341342
"relationship_type": relationship_type,
@@ -376,6 +377,16 @@ def generate_id(
376377
:return List of stix_core_relationship objects
377378
"""
378379

380+
@staticmethod
381+
def generate_id_from_data(data):
382+
return StixCoreRelationship.generate_id(
383+
data["relationship_type"],
384+
data["source_ref"],
385+
data["target_ref"],
386+
data.get("start_time"),
387+
data.get("stop_time"),
388+
)
389+
379390
def list(self, **kwargs):
380391
from_or_to_id = kwargs.get("fromOrToId", None)
381392
element_with_target_types = kwargs.get("elementWithTargetTypes", None)

pycti/entities/opencti_stix_sighting_relationship.py

Lines changed: 30 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -262,28 +262,53 @@ def __init__(self, opencti):
262262
"""
263263

264264
@staticmethod
265-
def generate_id(source_ref, target_ref, first_seen=None, last_seen=None):
265+
def generate_id(
266+
relationship_type,
267+
sighting_of_ref,
268+
where_sighted_refs,
269+
first_seen=None,
270+
last_seen=None,
271+
):
266272
if isinstance(first_seen, datetime.datetime):
267273
first_seen = first_seen.isoformat()
268274
if isinstance(last_seen, datetime.datetime):
269275
last_seen = last_seen.isoformat()
270276

271277
if first_seen is not None and last_seen is not None:
272278
data = {
273-
"source_ref": source_ref,
274-
"target_ref": target_ref,
279+
"type": relationship_type,
280+
"sighting_of_ref": sighting_of_ref,
281+
"where_sighted_refs": where_sighted_refs,
275282
"first_seen": first_seen,
276283
"last_seen": last_seen,
277284
}
285+
elif first_seen is not None:
286+
data = {
287+
"type": relationship_type,
288+
"sighting_of_ref": sighting_of_ref,
289+
"where_sighted_refs": where_sighted_refs,
290+
"first_seen": first_seen,
291+
}
278292
else:
279293
data = {
280-
"source_ref": source_ref,
281-
"target_ref": target_ref,
294+
"type": relationship_type,
295+
"sighting_of_ref": sighting_of_ref,
296+
"where_sighted_refs": where_sighted_refs,
282297
}
283298
data = canonicalize(data, utf8=False)
284299
id = str(uuid.uuid5(uuid.UUID("00abedb4-aa42-466c-9c01-fed23315a9b7"), data))
285300
return "sighting--" + id
286301

302+
@staticmethod
303+
def generate_id_from_data(data):
304+
return StixSightingRelationship.generate_id(
305+
data["type"],
306+
data["sighting_of_ref"],
307+
data["where_sighted_refs"],
308+
data.get("first_seen"),
309+
data.get("last_seen"),
310+
)
311+
287312
"""
288313
List stix_sightings objects
289314

pycti/utils/opencti_stix2.py

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -855,6 +855,7 @@ def get_reader(self, entity_type: str):
855855
def get_stix_helper(self):
856856
# Import
857857
return {
858+
# entities
858859
"attack-pattern": self.opencti.attack_pattern,
859860
"campaign": self.opencti.campaign,
860861
"note": self.opencti.note,
@@ -894,6 +895,9 @@ def get_stix_helper(self):
894895
"task": self.opencti.task,
895896
"x-opencti-task": self.opencti.task,
896897
"vocabulary": self.opencti.vocabulary,
898+
# relationships
899+
"relationship": self.opencti.stix_core_relationship,
900+
"sighting": self.opencti.stix_sighting_relationship,
897901
}
898902

899903
def generate_standard_id_from_stix(self, data):
@@ -2399,23 +2403,21 @@ def prepare_bundle_ids(self, bundle, use_json=True, keep_original_id=False):
23992403
# First iteration to cache all entity ids
24002404
stix_helpers = self.get_stix_helper()
24012405
for item in bundle_data["objects"]:
2402-
if item["type"] != "relationship" and item["type"] != "sighting":
2403-
helper = stix_helpers.get(item["type"])
2404-
if hasattr(helper, "generate_id_from_data"):
2405-
standard_id = helper.generate_id_from_data(item)
2406-
cache_ids[item["id"]] = standard_id
2406+
helper = stix_helpers.get(item["type"])
2407+
if hasattr(helper, "generate_id_from_data"):
2408+
standard_id = helper.generate_id_from_data(item)
2409+
cache_ids[item["id"]] = standard_id
24072410
# Second iteration to replace and remap
24082411
for item in bundle_data["objects"]:
24092412
# For entities, try to replace the main id
24102413
# Keep the current one if needed
2411-
if item["type"] != "relationship" and item["type"] != "sighting":
2412-
if cache_ids.get(item["id"]):
2413-
original_id = item["id"]
2414-
item["id"] = cache_ids[original_id]
2415-
if keep_original_id:
2416-
item["x_opencti_stix_ids"] = item.get(
2417-
"x_opencti_stix_ids", []
2418-
) + [original_id]
2414+
if cache_ids.get(item["id"]):
2415+
original_id = item["id"]
2416+
item["id"] = cache_ids[original_id]
2417+
if keep_original_id:
2418+
item["x_opencti_stix_ids"] = item.get("x_opencti_stix_ids", []) + [
2419+
original_id
2420+
]
24192421
# For all elements, replace all refs (source_ref, object_refs, ...)
24202422
ref_keys = list(
24212423
filter(lambda i: i.endswith("_ref") or i.endswith("_refs"), item.keys())

tests/01-unit/stix/test_bundle_ids_rewrite.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ def load_test_file():
1717
return bundle_data
1818

1919

20+
# !! WARNING !!, this need to be changed along with 01-unit/domain/identifier-test.js
2021
# fmt: off
2122
def test_ids_generation():
2223
gen_id = get_cti_helper().generate_standard_id_from_stix
@@ -95,6 +96,16 @@ def test_ids_generation():
9596
assert gen_id({"type": "threat-actor", "name": "CARD04", "x_opencti_type": "Threat-Actor-Individual"}) == "threat-actor--af15b6ae-a3dd-54d3-8fa0-3adfe0391d01"
9697
# vocabulary
9798
assert gen_id({"type": "vocabulary", "name": "facebook", "category": "account_type_ov"}) == "vocabulary--85ae7185-ff6f-509b-a011-3069921614aa"
99+
# relationship
100+
base_relationship = {"type": "relationship", "relationship_type": "based-on", "source_ref": "from_id", "target_ref": "to_id"}
101+
assert gen_id(base_relationship) == "relationship--0b11fa67-da01-5d34-9864-67d4d71c3740"
102+
assert gen_id({**base_relationship, "start_time": "2022-11-25T19:00:05.000Z"}) == "relationship--c5e1e2ce-14d6-535b-911d-267e92119e01"
103+
assert gen_id({**base_relationship, "start_time": "2022-11-25T19:00:05.000Z", "stop_time": "2022-11-26T19:00:05.000Z"}) == "relationship--a7778a7d-a743-5193-9912-89f88f9ed0b4"
104+
# sighting
105+
base_sighting = {"type": "sighting", "sighting_of_ref": "from_id", "where_sighted_refs": ["to_id"]}
106+
assert gen_id(base_sighting) == 'sighting--161901df-21bb-527a-b96b-354119279fe2'
107+
assert gen_id({**base_sighting, "first_seen": "2022-11-25T19:00:05.000Z"}) == "sighting--3c59ceea-8e41-5adb-a257-d070d19e6d2b"
108+
assert gen_id({**base_sighting, "first_seen": "2022-11-25T19:00:05.000Z", "last_seen": "2022-11-26T19:00:05.000Z"}) == "sighting--b4d307b6-d22c-5f22-b530-876c298493da"
98109
# fmt: on
99110

100111

tests/data/bundle_ids_sample.json

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@
5959
]
6060
},
6161
{
62-
"id": "relationship--ba52fced-422a-4bee-816a-85aa21c9eaca",
62+
"id": "relationship--ba52fced-422a-4bee-816a-85aa21c9eacc",
6363
"type": "relationship",
6464
"spec_version": "2.1",
6565
"relationship_type": "related-to",
@@ -71,6 +71,19 @@
7171
"stop_time": "1900-01-01T00:00:00.000Z",
7272
"created_by_ref": "identity--7b82b010-b1c0-4dae-981f-7756374a17da",
7373
"object_marking_refs": ["marking-definition--78ca4366-f5b8-4764-83f7-34ce38198e27"]
74+
},
75+
{
76+
"type": "sighting",
77+
"spec_version": "2.1",
78+
"id": "sighting--ee20065d-2555-424f-ad9e-0f8428623c75",
79+
"created": "2016-08-06T20:08:31.000Z",
80+
"modified": "2016-09-06T20:08:31.000Z",
81+
"sighting_of_ref": "malware--d650c5b9-4b43-5781-8576-ea52bd6c7ce5",
82+
"where_sighted_refs": ["identity--7b82b010-b1c0-4dae-981f-7756374a17da"],
83+
"first_seen": "2016-08-06T20:08:31.000Z",
84+
"last_seen": "2016-08-07T20:08:31.000Z",
85+
"count": 12,
86+
"x_opencti_negative": true
7487
}
7588
]
7689
}

0 commit comments

Comments
 (0)