pycti 6.3.0__tar.gz → 6.3.2__tar.gz

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.

Files changed (80) hide show
  1. {pycti-6.3.0 → pycti-6.3.2}/PKG-INFO +1 -1
  2. {pycti-6.3.0 → pycti-6.3.2}/pycti/__init__.py +1 -1
  3. {pycti-6.3.0 → pycti-6.3.2}/pycti/connector/opencti_connector_helper.py +14 -14
  4. {pycti-6.3.0 → pycti-6.3.2}/pycti/entities/opencti_case_rfi.py +1 -1
  5. {pycti-6.3.0 → pycti-6.3.2}/pycti/entities/opencti_case_rft.py +1 -1
  6. {pycti-6.3.0 → pycti-6.3.2}/pycti/entities/opencti_kill_chain_phase.py +4 -6
  7. {pycti-6.3.0 → pycti-6.3.2}/pycti/utils/opencti_stix2.py +3 -4
  8. pycti-6.3.2/pycti/utils/opencti_stix2_identifier.py +22 -0
  9. pycti-6.3.2/pycti/utils/opencti_stix2_splitter.py +263 -0
  10. {pycti-6.3.0 → pycti-6.3.2}/pycti/utils/opencti_stix2_utils.py +40 -0
  11. {pycti-6.3.0 → pycti-6.3.2}/pycti.egg-info/PKG-INFO +1 -1
  12. {pycti-6.3.0 → pycti-6.3.2}/pycti.egg-info/SOURCES.txt +1 -0
  13. pycti-6.3.0/pycti/utils/opencti_stix2_splitter.py +0 -144
  14. {pycti-6.3.0 → pycti-6.3.2}/LICENSE +0 -0
  15. {pycti-6.3.0 → pycti-6.3.2}/README.md +0 -0
  16. {pycti-6.3.0 → pycti-6.3.2}/pycti/api/__init__.py +0 -0
  17. {pycti-6.3.0 → pycti-6.3.2}/pycti/api/opencti_api_client.py +0 -0
  18. {pycti-6.3.0 → pycti-6.3.2}/pycti/api/opencti_api_connector.py +0 -0
  19. {pycti-6.3.0 → pycti-6.3.2}/pycti/api/opencti_api_playbook.py +0 -0
  20. {pycti-6.3.0 → pycti-6.3.2}/pycti/api/opencti_api_work.py +0 -0
  21. {pycti-6.3.0 → pycti-6.3.2}/pycti/connector/__init__.py +0 -0
  22. {pycti-6.3.0 → pycti-6.3.2}/pycti/connector/opencti_connector.py +0 -0
  23. {pycti-6.3.0 → pycti-6.3.2}/pycti/connector/opencti_metric_handler.py +0 -0
  24. {pycti-6.3.0 → pycti-6.3.2}/pycti/entities/__init__.py +0 -0
  25. {pycti-6.3.0 → pycti-6.3.2}/pycti/entities/indicator/__init__.py +0 -0
  26. {pycti-6.3.0 → pycti-6.3.2}/pycti/entities/indicator/opencti_indicator_properties.py +0 -0
  27. {pycti-6.3.0 → pycti-6.3.2}/pycti/entities/opencti_attack_pattern.py +0 -0
  28. {pycti-6.3.0 → pycti-6.3.2}/pycti/entities/opencti_campaign.py +0 -0
  29. {pycti-6.3.0 → pycti-6.3.2}/pycti/entities/opencti_case_incident.py +0 -0
  30. {pycti-6.3.0 → pycti-6.3.2}/pycti/entities/opencti_channel.py +0 -0
  31. {pycti-6.3.0 → pycti-6.3.2}/pycti/entities/opencti_course_of_action.py +0 -0
  32. {pycti-6.3.0 → pycti-6.3.2}/pycti/entities/opencti_data_component.py +0 -0
  33. {pycti-6.3.0 → pycti-6.3.2}/pycti/entities/opencti_data_source.py +0 -0
  34. {pycti-6.3.0 → pycti-6.3.2}/pycti/entities/opencti_event.py +0 -0
  35. {pycti-6.3.0 → pycti-6.3.2}/pycti/entities/opencti_external_reference.py +0 -0
  36. {pycti-6.3.0 → pycti-6.3.2}/pycti/entities/opencti_feedback.py +0 -0
  37. {pycti-6.3.0 → pycti-6.3.2}/pycti/entities/opencti_grouping.py +0 -0
  38. {pycti-6.3.0 → pycti-6.3.2}/pycti/entities/opencti_identity.py +0 -0
  39. {pycti-6.3.0 → pycti-6.3.2}/pycti/entities/opencti_incident.py +0 -0
  40. {pycti-6.3.0 → pycti-6.3.2}/pycti/entities/opencti_indicator.py +0 -0
  41. {pycti-6.3.0 → pycti-6.3.2}/pycti/entities/opencti_infrastructure.py +0 -0
  42. {pycti-6.3.0 → pycti-6.3.2}/pycti/entities/opencti_intrusion_set.py +0 -0
  43. {pycti-6.3.0 → pycti-6.3.2}/pycti/entities/opencti_label.py +0 -0
  44. {pycti-6.3.0 → pycti-6.3.2}/pycti/entities/opencti_language.py +0 -0
  45. {pycti-6.3.0 → pycti-6.3.2}/pycti/entities/opencti_location.py +0 -0
  46. {pycti-6.3.0 → pycti-6.3.2}/pycti/entities/opencti_malware.py +0 -0
  47. {pycti-6.3.0 → pycti-6.3.2}/pycti/entities/opencti_malware_analysis.py +0 -0
  48. {pycti-6.3.0 → pycti-6.3.2}/pycti/entities/opencti_marking_definition.py +0 -0
  49. {pycti-6.3.0 → pycti-6.3.2}/pycti/entities/opencti_narrative.py +0 -0
  50. {pycti-6.3.0 → pycti-6.3.2}/pycti/entities/opencti_note.py +0 -0
  51. {pycti-6.3.0 → pycti-6.3.2}/pycti/entities/opencti_observed_data.py +0 -0
  52. {pycti-6.3.0 → pycti-6.3.2}/pycti/entities/opencti_opinion.py +0 -0
  53. {pycti-6.3.0 → pycti-6.3.2}/pycti/entities/opencti_report.py +0 -0
  54. {pycti-6.3.0 → pycti-6.3.2}/pycti/entities/opencti_stix.py +0 -0
  55. {pycti-6.3.0 → pycti-6.3.2}/pycti/entities/opencti_stix_core_object.py +0 -0
  56. {pycti-6.3.0 → pycti-6.3.2}/pycti/entities/opencti_stix_core_relationship.py +0 -0
  57. {pycti-6.3.0 → pycti-6.3.2}/pycti/entities/opencti_stix_cyber_observable.py +0 -0
  58. {pycti-6.3.0 → pycti-6.3.2}/pycti/entities/opencti_stix_domain_object.py +0 -0
  59. {pycti-6.3.0 → pycti-6.3.2}/pycti/entities/opencti_stix_nested_ref_relationship.py +0 -0
  60. {pycti-6.3.0 → pycti-6.3.2}/pycti/entities/opencti_stix_object_or_stix_relationship.py +0 -0
  61. {pycti-6.3.0 → pycti-6.3.2}/pycti/entities/opencti_stix_sighting_relationship.py +0 -0
  62. {pycti-6.3.0 → pycti-6.3.2}/pycti/entities/opencti_task.py +0 -0
  63. {pycti-6.3.0 → pycti-6.3.2}/pycti/entities/opencti_threat_actor.py +0 -0
  64. {pycti-6.3.0 → pycti-6.3.2}/pycti/entities/opencti_threat_actor_group.py +0 -0
  65. {pycti-6.3.0 → pycti-6.3.2}/pycti/entities/opencti_threat_actor_individual.py +0 -0
  66. {pycti-6.3.0 → pycti-6.3.2}/pycti/entities/opencti_tool.py +0 -0
  67. {pycti-6.3.0 → pycti-6.3.2}/pycti/entities/opencti_vocabulary.py +0 -0
  68. {pycti-6.3.0 → pycti-6.3.2}/pycti/entities/opencti_vulnerability.py +0 -0
  69. {pycti-6.3.0 → pycti-6.3.2}/pycti/entities/stix_cyber_observable/__init__.py +0 -0
  70. {pycti-6.3.0 → pycti-6.3.2}/pycti/entities/stix_cyber_observable/opencti_stix_cyber_observable_deprecated.py +0 -0
  71. {pycti-6.3.0 → pycti-6.3.2}/pycti/entities/stix_cyber_observable/opencti_stix_cyber_observable_properties.py +0 -0
  72. {pycti-6.3.0 → pycti-6.3.2}/pycti/utils/__init__.py +0 -0
  73. {pycti-6.3.0 → pycti-6.3.2}/pycti/utils/constants.py +0 -0
  74. {pycti-6.3.0 → pycti-6.3.2}/pycti/utils/opencti_logger.py +0 -0
  75. {pycti-6.3.0 → pycti-6.3.2}/pycti/utils/opencti_stix2_update.py +0 -0
  76. {pycti-6.3.0 → pycti-6.3.2}/pycti.egg-info/dependency_links.txt +0 -0
  77. {pycti-6.3.0 → pycti-6.3.2}/pycti.egg-info/requires.txt +0 -0
  78. {pycti-6.3.0 → pycti-6.3.2}/pycti.egg-info/top_level.txt +0 -0
  79. {pycti-6.3.0 → pycti-6.3.2}/pyproject.toml +0 -0
  80. {pycti-6.3.0 → pycti-6.3.2}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: pycti
3
- Version: 6.3.0
3
+ Version: 6.3.2
4
4
  Summary: Python API client for OpenCTI.
5
5
  Home-page: https://github.com/OpenCTI-Platform/client-python
6
6
  Author: Filigran
@@ -1,5 +1,5 @@
1
1
  # -*- coding: utf-8 -*-
2
- __version__ = "6.3.0"
2
+ __version__ = "6.3.2"
3
3
 
4
4
  from .api.opencti_api_client import OpenCTIApiClient
5
5
  from .api.opencti_api_connector import OpenCTIApiConnector
@@ -376,7 +376,7 @@ class ListenQueue(threading.Thread):
376
376
  self.pika_credentials = pika.PlainCredentials(self.user, self.password)
377
377
  self.pika_parameters = pika.ConnectionParameters(
378
378
  heartbeat=10,
379
- blocked_connection_timeout=10,
379
+ blocked_connection_timeout=30,
380
380
  host=self.host,
381
381
  port=self.port,
382
382
  virtual_host=self.vhost,
@@ -1556,6 +1556,8 @@ class OpenCTIConnectorHelper: # pylint: disable=too-many-public-methods
1556
1556
  :type entities_types: list, optional
1557
1557
  :param update: whether to updated data in the database, defaults to False
1558
1558
  :type update: bool, optional
1559
+ :param bypass_split: use to prevent splitting of the bundle. This option has been removed since 6.3 and is no longer used.
1560
+ :type bypass_split: bool, optional
1559
1561
  :raises ValueError: if the bundle is empty
1560
1562
  :return: list of bundles
1561
1563
  :rtype: list
@@ -1564,11 +1566,11 @@ class OpenCTIConnectorHelper: # pylint: disable=too-many-public-methods
1564
1566
  entities_types = kwargs.get("entities_types", None)
1565
1567
  update = kwargs.get("update", False)
1566
1568
  event_version = kwargs.get("event_version", None)
1567
- bypass_split = kwargs.get("bypass_split", False)
1568
1569
  bypass_validation = kwargs.get("bypass_validation", False)
1569
1570
  entity_id = kwargs.get("entity_id", None)
1570
1571
  file_name = kwargs.get("file_name", None)
1571
1572
  bundle_send_to_queue = kwargs.get("send_to_queue", self.bundle_send_to_queue)
1573
+ cleanup_inconsistent_bundle = kwargs.get("cleanup_inconsistent_bundle", False)
1572
1574
  bundle_send_to_directory = kwargs.get(
1573
1575
  "send_to_directory", self.bundle_send_to_directory
1574
1576
  )
@@ -1690,17 +1692,16 @@ class OpenCTIConnectorHelper: # pylint: disable=too-many-public-methods
1690
1692
  final_write_file = os.path.join(bundle_send_to_directory_path, bundle_file)
1691
1693
  os.rename(write_file, final_write_file)
1692
1694
 
1693
- if bypass_split:
1694
- bundles = [bundle]
1695
- expectations_number = len(json.loads(bundle)["objects"])
1696
- else:
1697
- stix2_splitter = OpenCTIStix2Splitter()
1698
- (
1699
- expectations_number,
1700
- bundles,
1701
- ) = stix2_splitter.split_bundle_with_expectations(
1702
- bundle, True, event_version
1703
- )
1695
+ stix2_splitter = OpenCTIStix2Splitter()
1696
+ (
1697
+ expectations_number,
1698
+ bundles,
1699
+ ) = stix2_splitter.split_bundle_with_expectations(
1700
+ bundle=bundle,
1701
+ use_json=True,
1702
+ event_version=event_version,
1703
+ cleanup_inconsistent_bundle=cleanup_inconsistent_bundle,
1704
+ )
1704
1705
 
1705
1706
  if len(bundles) == 0:
1706
1707
  self.metric.inc("error_count")
@@ -1718,7 +1719,6 @@ class OpenCTIConnectorHelper: # pylint: disable=too-many-public-methods
1718
1719
  )
1719
1720
  pika_parameters = pika.ConnectionParameters(
1720
1721
  heartbeat=10,
1721
- blocked_connection_timeout=10,
1722
1722
  host=self.connector_config["connection"]["host"],
1723
1723
  port=self.connector_config["connection"]["port"],
1724
1724
  virtual_host=self.connector_config["connection"]["vhost"],
@@ -747,7 +747,7 @@ class CaseRfi:
747
747
  },
748
748
  )
749
749
  query = """
750
- mutation CaseRfiEditRelationAdd($id: ID!, $input: StixRefRelationshipAddInput) {
750
+ mutation CaseRfiEditRelationAdd($id: ID!, $input: StixRefRelationshipAddInput!) {
751
751
  stixDomainObjectEdit(id: $id) {
752
752
  relationAdd(input: $input) {
753
753
  id
@@ -746,7 +746,7 @@ class CaseRft:
746
746
  },
747
747
  )
748
748
  query = """
749
- mutation CaseRftEditRelationAdd($id: ID!, $input: StixRefRelationshipAddInput) {
749
+ mutation CaseRftEditRelationAdd($id: ID!, $input: StixRefRelationshipAddInput!) {
750
750
  stixDomainObjectEdit(id: $id) {
751
751
  relationAdd(input: $input) {
752
752
  id
@@ -1,9 +1,8 @@
1
1
  # coding: utf-8
2
2
 
3
3
  import json
4
- import uuid
5
4
 
6
- from stix2.canonicalization.Canonicalize import canonicalize
5
+ from pycti.utils.opencti_stix2_identifier import kill_chain_phase_generate_id
7
6
 
8
7
 
9
8
  class KillChainPhase:
@@ -25,10 +24,9 @@ class KillChainPhase:
25
24
 
26
25
  @staticmethod
27
26
  def generate_id(phase_name, kill_chain_name):
28
- data = {"phase_name": phase_name, "kill_chain_name": kill_chain_name}
29
- data = canonicalize(data, utf8=False)
30
- id = str(uuid.uuid5(uuid.UUID("00abedb4-aa42-466c-9c01-fed23315a9b7"), data))
31
- return "kill-chain-phase--" + id
27
+ return kill_chain_phase_generate_id(
28
+ phase_name=phase_name, kill_chain_name=kill_chain_name
29
+ )
32
30
 
33
31
  """
34
32
  List Kill-Chain-Phase objects
@@ -2619,10 +2619,9 @@ class OpenCTIStix2:
2619
2619
  else None
2620
2620
  )
2621
2621
  stix2_splitter = OpenCTIStix2Splitter()
2622
- try:
2623
- bundles = stix2_splitter.split_bundle(stix_bundle, False, event_version)
2624
- except RecursionError:
2625
- bundles = [stix_bundle]
2622
+ _, bundles = stix2_splitter.split_bundle_with_expectations(
2623
+ stix_bundle, False, event_version
2624
+ )
2626
2625
  # Import every element in a specific order
2627
2626
  imported_elements = []
2628
2627
  for bundle in bundles:
@@ -0,0 +1,22 @@
1
+ import uuid
2
+
3
+ from stix2.canonicalization.Canonicalize import canonicalize
4
+
5
+
6
+ def external_reference_generate_id(url=None, source_name=None, external_id=None):
7
+ if url is not None:
8
+ data = {"url": url}
9
+ elif source_name is not None and external_id is not None:
10
+ data = {"source_name": source_name, "external_id": external_id}
11
+ else:
12
+ return None
13
+ data = canonicalize(data, utf8=False)
14
+ id = str(uuid.uuid5(uuid.UUID("00abedb4-aa42-466c-9c01-fed23315a9b7"), data))
15
+ return "external-reference--" + id
16
+
17
+
18
+ def kill_chain_phase_generate_id(phase_name, kill_chain_name):
19
+ data = {"phase_name": phase_name, "kill_chain_name": kill_chain_name}
20
+ data = canonicalize(data, utf8=False)
21
+ id = str(uuid.uuid5(uuid.UUID("00abedb4-aa42-466c-9c01-fed23315a9b7"), data))
22
+ return "kill-chain-phase--" + id
@@ -0,0 +1,263 @@
1
+ import json
2
+ import uuid
3
+ from typing import Tuple
4
+
5
+ from typing_extensions import deprecated
6
+
7
+ from pycti.utils.opencti_stix2_identifier import (
8
+ external_reference_generate_id,
9
+ kill_chain_phase_generate_id,
10
+ )
11
+ from pycti.utils.opencti_stix2_utils import (
12
+ STIX_CYBER_OBSERVABLE_MAPPING,
13
+ SUPPORTED_STIX_DOMAIN_OBJECTS,
14
+ )
15
+
16
+ supported_types = (
17
+ SUPPORTED_STIX_DOMAIN_OBJECTS # entities
18
+ + list(STIX_CYBER_OBSERVABLE_MAPPING.keys()) # observables
19
+ + ["relationship", "sighting"] # relationships
20
+ )
21
+
22
+
23
+ def is_id_supported(key):
24
+ id_type = key.split("--")[0]
25
+ return id_type in supported_types
26
+
27
+
28
+ class OpenCTIStix2Splitter:
29
+ def __init__(self):
30
+ self.cache_index = {}
31
+ self.cache_refs = {}
32
+ self.elements = []
33
+
34
+ def enlist_element(
35
+ self, item_id, raw_data, cleanup_inconsistent_bundle, parent_acc
36
+ ):
37
+ nb_deps = 1
38
+ if item_id not in raw_data:
39
+ return 0
40
+
41
+ existing_item = self.cache_index.get(item_id)
42
+ if existing_item is not None:
43
+ return existing_item["nb_deps"]
44
+
45
+ item = raw_data[item_id]
46
+ if self.cache_refs.get(item_id) is None:
47
+ self.cache_refs[item_id] = []
48
+ for key in list(item.keys()):
49
+ value = item[key]
50
+ # Recursive enlist for every refs
51
+ if key.endswith("_refs"):
52
+ to_keep = []
53
+ for element_ref in item[key]:
54
+ # We need to check if this ref is not already a reference
55
+ is_missing_ref = raw_data.get(element_ref) is None
56
+ must_be_cleaned = is_missing_ref and cleanup_inconsistent_bundle
57
+ not_dependency_ref = (
58
+ self.cache_refs.get(element_ref) is None
59
+ or item_id not in self.cache_refs[element_ref]
60
+ )
61
+ # Prevent any self reference
62
+ if (
63
+ is_id_supported(element_ref)
64
+ and not must_be_cleaned
65
+ and element_ref not in parent_acc
66
+ and element_ref != item_id
67
+ and not_dependency_ref
68
+ ):
69
+ self.cache_refs[item_id].append(element_ref)
70
+ nb_deps += self.enlist_element(
71
+ element_ref,
72
+ raw_data,
73
+ cleanup_inconsistent_bundle,
74
+ parent_acc + [element_ref],
75
+ )
76
+ if element_ref not in to_keep:
77
+ to_keep.append(element_ref)
78
+ item[key] = to_keep
79
+ elif key.endswith("_ref"):
80
+ is_missing_ref = raw_data.get(value) is None
81
+ must_be_cleaned = is_missing_ref and cleanup_inconsistent_bundle
82
+ not_dependency_ref = (
83
+ self.cache_refs.get(value) is None
84
+ or item_id not in self.cache_refs[value]
85
+ )
86
+ # Prevent any self reference
87
+ if (
88
+ value is not None
89
+ and not must_be_cleaned
90
+ and value not in parent_acc
91
+ and is_id_supported(value)
92
+ and value != item_id
93
+ and not_dependency_ref
94
+ ):
95
+ self.cache_refs[item_id].append(value)
96
+ nb_deps += self.enlist_element(
97
+ value,
98
+ raw_data,
99
+ cleanup_inconsistent_bundle,
100
+ parent_acc + [value],
101
+ )
102
+ else:
103
+ item[key] = None
104
+ # Case for embedded elements (deduplicating and cleanup)
105
+ elif key == "external_references":
106
+ # specific case of splitting external references
107
+ # reference_ids = []
108
+ deduplicated_references = []
109
+ deduplicated_references_cache = {}
110
+ references = item[key]
111
+ for reference in references:
112
+ reference_id = external_reference_generate_id(
113
+ url=reference.get("url"),
114
+ source_name=reference.get("source_name"),
115
+ external_id=reference.get("external_id"),
116
+ )
117
+ if (
118
+ reference_id is not None
119
+ and deduplicated_references_cache.get(reference_id) is None
120
+ ):
121
+ deduplicated_references_cache[reference_id] = reference_id
122
+ deduplicated_references.append(reference)
123
+ # - Needed for a future move of splitting the elements
124
+ # reference["id"] = reference_id
125
+ # reference["type"] = "External-Reference"
126
+ # raw_data[reference_id] = reference
127
+ # if reference_id not in reference_ids:
128
+ # reference_ids.append(reference_id)
129
+ # nb_deps += self.enlist_element(reference_id, raw_data)
130
+ item[key] = deduplicated_references
131
+ elif key == "kill_chain_phases":
132
+ # specific case of splitting kill_chain phases
133
+ # kill_chain_ids = []
134
+ deduplicated_kill_chain = []
135
+ deduplicated_kill_chain_cache = {}
136
+ kill_chains = item[key]
137
+ for kill_chain in kill_chains:
138
+ kill_chain_id = kill_chain_phase_generate_id(
139
+ kill_chain_name=kill_chain.get("kill_chain_name"),
140
+ phase_name=kill_chain.get("phase_name"),
141
+ )
142
+ if (
143
+ kill_chain_id is not None
144
+ and deduplicated_kill_chain_cache.get(kill_chain_id) is None
145
+ ):
146
+ deduplicated_kill_chain_cache[kill_chain_id] = kill_chain_id
147
+ deduplicated_kill_chain.append(kill_chain)
148
+ # - Needed for a future move of splitting the elements
149
+ # kill_chain["id"] = kill_chain_id
150
+ # kill_chain["type"] = "Kill-Chain-Phase"
151
+ # raw_data[kill_chain_id] = kill_chain
152
+ # if kill_chain_id not in kill_chain_ids:
153
+ # kill_chain_ids.append(kill_chain_id)
154
+ # nb_deps += self.enlist_element(kill_chain_id, raw_data)
155
+ item[key] = deduplicated_kill_chain
156
+
157
+ # Get the final dep counting and add in cache
158
+ item["nb_deps"] = nb_deps
159
+ # Put in cache
160
+ if self.cache_index.get(item_id) is None:
161
+ # enlist only if compatible
162
+ if item["type"] == "relationship":
163
+ is_compatible = (
164
+ item["source_ref"] is not None and item["target_ref"] is not None
165
+ )
166
+ elif item["type"] == "sighting":
167
+ is_compatible = (
168
+ item["sighting_of_ref"] is not None
169
+ and len(item["where_sighted_refs"]) > 0
170
+ )
171
+ else:
172
+ is_compatible = is_id_supported(item_id)
173
+ if is_compatible:
174
+ self.elements.append(item)
175
+ self.cache_index[item_id] = item
176
+
177
+ return nb_deps
178
+
179
+ def split_bundle_with_expectations(
180
+ self,
181
+ bundle,
182
+ use_json=True,
183
+ event_version=None,
184
+ cleanup_inconsistent_bundle=False,
185
+ ) -> Tuple[int, list]:
186
+ """splits a valid stix2 bundle into a list of bundles"""
187
+ if use_json:
188
+ try:
189
+ bundle_data = json.loads(bundle)
190
+ except:
191
+ raise Exception("File data is not a valid JSON")
192
+ else:
193
+ bundle_data = bundle
194
+
195
+ if "objects" not in bundle_data:
196
+ raise Exception("File data is not a valid bundle")
197
+ if "id" not in bundle_data:
198
+ bundle_data["id"] = "bundle--" + str(uuid.uuid4())
199
+
200
+ raw_data = {}
201
+
202
+ # Build flat list of elements
203
+ for item in bundle_data["objects"]:
204
+ raw_data[item["id"]] = item
205
+ for item in bundle_data["objects"]:
206
+ self.enlist_element(item["id"], raw_data, cleanup_inconsistent_bundle, [])
207
+
208
+ # Build the bundles
209
+ bundles = []
210
+
211
+ def by_dep_size(elem):
212
+ return elem["nb_deps"]
213
+
214
+ self.elements.sort(key=by_dep_size)
215
+
216
+ elements_with_deps = list(
217
+ map(lambda e: {"nb_deps": e["nb_deps"], "elements": [e]}, self.elements)
218
+ )
219
+
220
+ number_expectations = 0
221
+ for entity in elements_with_deps:
222
+ number_expectations += len(entity["elements"])
223
+ bundles.append(
224
+ self.stix2_create_bundle(
225
+ bundle_data["id"],
226
+ entity["nb_deps"],
227
+ entity["elements"],
228
+ use_json,
229
+ event_version,
230
+ )
231
+ )
232
+
233
+ return number_expectations, bundles
234
+
235
+ @deprecated("Use split_bundle_with_expectations instead")
236
+ def split_bundle(self, bundle, use_json=True, event_version=None) -> list:
237
+ expectations, bundles = self.split_bundle_with_expectations(
238
+ bundle, use_json, event_version
239
+ )
240
+ return bundles
241
+
242
+ @staticmethod
243
+ def stix2_create_bundle(bundle_id, bundle_seq, items, use_json, event_version=None):
244
+ """create a stix2 bundle with items
245
+
246
+ :param items: valid stix2 items
247
+ :type items:
248
+ :param use_json: use JSON?
249
+ :type use_json:
250
+ :return: JSON of the stix2 bundle
251
+ :rtype:
252
+ """
253
+
254
+ bundle = {
255
+ "type": "bundle",
256
+ "id": bundle_id,
257
+ "spec_version": "2.1",
258
+ "x_opencti_seq": bundle_seq,
259
+ "objects": items,
260
+ }
261
+ if event_version is not None:
262
+ bundle["x_opencti_event_version"] = event_version
263
+ return json.dumps(bundle) if use_json else bundle
@@ -2,6 +2,46 @@ from typing import Any, Dict
2
2
 
3
3
  from stix2 import EqualityComparisonExpression, ObjectPath, ObservationExpression
4
4
 
5
+ SUPPORTED_STIX_DOMAIN_OBJECTS = [
6
+ "marking-definition",
7
+ "attack-pattern",
8
+ "campaign",
9
+ "channel",
10
+ "event",
11
+ "note",
12
+ "observed-data",
13
+ "opinion",
14
+ "report",
15
+ "grouping",
16
+ "case-rfi",
17
+ "x-opencti-case-rfi",
18
+ "case-rft",
19
+ "x-opencti-case-rft",
20
+ "task",
21
+ "x-opencti-task",
22
+ "case-incident",
23
+ "x-opencti-case-incident",
24
+ "feedback",
25
+ "x-opencti-feedback",
26
+ "course-of-action",
27
+ "data-component",
28
+ "x-mitre-data-component",
29
+ "data-source",
30
+ "x-mitre-data-source",
31
+ "identity",
32
+ "indicator",
33
+ "infrastructure",
34
+ "intrusion-set",
35
+ "location",
36
+ "malware",
37
+ "malware-analysis",
38
+ "threat-actor",
39
+ "tool",
40
+ "narrative",
41
+ "vulnerability",
42
+ "incident",
43
+ ]
44
+
5
45
  STIX_CYBER_OBSERVABLE_MAPPING = {
6
46
  "autonomous-system": "Autonomous-System",
7
47
  "directory": "Directory",
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: pycti
3
- Version: 6.3.0
3
+ Version: 6.3.2
4
4
  Summary: Python API client for OpenCTI.
5
5
  Home-page: https://github.com/OpenCTI-Platform/client-python
6
6
  Author: Filigran
@@ -72,6 +72,7 @@ pycti/utils/__init__.py
72
72
  pycti/utils/constants.py
73
73
  pycti/utils/opencti_logger.py
74
74
  pycti/utils/opencti_stix2.py
75
+ pycti/utils/opencti_stix2_identifier.py
75
76
  pycti/utils/opencti_stix2_splitter.py
76
77
  pycti/utils/opencti_stix2_update.py
77
78
  pycti/utils/opencti_stix2_utils.py
@@ -1,144 +0,0 @@
1
- import json
2
- import re
3
- import uuid
4
- from typing import Tuple
5
-
6
- from typing_extensions import deprecated
7
-
8
- MITRE_X_CAPEC = (
9
- "x_capec_*" # https://github.com/mitre-attack/attack-stix-data/issues/34
10
- )
11
- unsupported_ref_patterns = [MITRE_X_CAPEC]
12
-
13
-
14
- class OpenCTIStix2Splitter:
15
- def __init__(self):
16
- self.cache_index = {}
17
- self.elements = []
18
- self.unsupported_patterns = list(
19
- map(lambda pattern: re.compile(pattern), unsupported_ref_patterns)
20
- )
21
-
22
- def is_ref_key_supported(self, key):
23
- for pattern in self.unsupported_patterns:
24
- if pattern.match(key):
25
- return False
26
- return True
27
-
28
- def enlist_element(self, item_id, raw_data):
29
- nb_deps = 1
30
- if item_id not in raw_data:
31
- return 0
32
- existing_item = self.cache_index.get(item_id)
33
- if existing_item is not None:
34
- return existing_item["nb_deps"]
35
- # Recursive enlist for every refs
36
- item = raw_data[item_id]
37
- for key in list(item.keys()):
38
- value = item[key]
39
- if key.endswith("_refs") and self.is_ref_key_supported(key):
40
- to_keep = []
41
- for element_ref in item[key]:
42
- if element_ref != item_id:
43
- nb_deps += self.enlist_element(element_ref, raw_data)
44
- to_keep.append(element_ref)
45
- item[key] = to_keep
46
- elif key.endswith("_ref") and self.is_ref_key_supported(key):
47
- if item[key] == item_id:
48
- item[key] = None
49
- else:
50
- # Need to handle the special case of recursive ref for created by ref
51
- is_created_by_ref = key == "created_by_ref"
52
- if is_created_by_ref:
53
- is_marking = item["id"].startswith("marking-definition--")
54
- if is_marking is False:
55
- nb_deps += self.enlist_element(value, raw_data)
56
- else:
57
- nb_deps += self.enlist_element(value, raw_data)
58
- # Get the final dep counting and add in cache
59
- item["nb_deps"] = nb_deps
60
- self.elements.append(item)
61
- self.cache_index[item_id] = item # Put in cache
62
- return nb_deps
63
-
64
- def split_bundle_with_expectations(
65
- self, bundle, use_json=True, event_version=None
66
- ) -> Tuple[int, list]:
67
- """splits a valid stix2 bundle into a list of bundles"""
68
- if use_json:
69
- try:
70
- bundle_data = json.loads(bundle)
71
- except:
72
- raise Exception("File data is not a valid JSON")
73
- else:
74
- bundle_data = bundle
75
-
76
- if "objects" not in bundle_data:
77
- raise Exception("File data is not a valid bundle")
78
- if "id" not in bundle_data:
79
- bundle_data["id"] = "bundle--" + str(uuid.uuid4())
80
-
81
- raw_data = {}
82
-
83
- # Build flat list of elements
84
- for item in bundle_data["objects"]:
85
- raw_data[item["id"]] = item
86
- for item in bundle_data["objects"]:
87
- self.enlist_element(item["id"], raw_data)
88
-
89
- # Build the bundles
90
- bundles = []
91
-
92
- def by_dep_size(elem):
93
- return elem["nb_deps"]
94
-
95
- self.elements.sort(key=by_dep_size)
96
-
97
- elements_with_deps = list(
98
- map(lambda e: {"nb_deps": e["nb_deps"], "elements": [e]}, self.elements)
99
- )
100
-
101
- number_expectations = 0
102
- for entity in elements_with_deps:
103
- number_expectations += len(entity["elements"])
104
- bundles.append(
105
- self.stix2_create_bundle(
106
- bundle_data["id"],
107
- entity["nb_deps"],
108
- entity["elements"],
109
- use_json,
110
- event_version,
111
- )
112
- )
113
-
114
- return number_expectations, bundles
115
-
116
- @deprecated("Use split_bundle_with_expectations instead")
117
- def split_bundle(self, bundle, use_json=True, event_version=None) -> list:
118
- expectations, bundles = self.split_bundle_with_expectations(
119
- bundle, use_json, event_version
120
- )
121
- return bundles
122
-
123
- @staticmethod
124
- def stix2_create_bundle(bundle_id, bundle_seq, items, use_json, event_version=None):
125
- """create a stix2 bundle with items
126
-
127
- :param items: valid stix2 items
128
- :type items:
129
- :param use_json: use JSON?
130
- :type use_json:
131
- :return: JSON of the stix2 bundle
132
- :rtype:
133
- """
134
-
135
- bundle = {
136
- "type": "bundle",
137
- "id": bundle_id,
138
- "spec_version": "2.1",
139
- "x_opencti_seq": bundle_seq,
140
- "objects": items,
141
- }
142
- if event_version is not None:
143
- bundle["x_opencti_event_version"] = event_version
144
- return json.dumps(bundle) if use_json else bundle
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes