pycti 6.3.13__tar.gz → 6.4.0__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 (79) hide show
  1. {pycti-6.3.13 → pycti-6.4.0}/PKG-INFO +1 -1
  2. {pycti-6.3.13 → pycti-6.4.0}/pycti/__init__.py +1 -1
  3. {pycti-6.3.13 → pycti-6.4.0}/pycti/api/opencti_api_client.py +31 -0
  4. {pycti-6.3.13 → pycti-6.4.0}/pycti/connector/opencti_connector_helper.py +35 -8
  5. {pycti-6.3.13 → pycti-6.4.0}/pycti/entities/indicator/opencti_indicator_properties.py +8 -0
  6. {pycti-6.3.13 → pycti-6.4.0}/pycti/entities/opencti_attack_pattern.py +6 -2
  7. {pycti-6.3.13 → pycti-6.4.0}/pycti/entities/opencti_campaign.py +4 -0
  8. {pycti-6.3.13 → pycti-6.4.0}/pycti/entities/opencti_case_incident.py +4 -0
  9. {pycti-6.3.13 → pycti-6.4.0}/pycti/entities/opencti_case_rfi.py +4 -0
  10. {pycti-6.3.13 → pycti-6.4.0}/pycti/entities/opencti_case_rft.py +4 -0
  11. {pycti-6.3.13 → pycti-6.4.0}/pycti/entities/opencti_channel.py +4 -0
  12. {pycti-6.3.13 → pycti-6.4.0}/pycti/entities/opencti_course_of_action.py +5 -2
  13. {pycti-6.3.13 → pycti-6.4.0}/pycti/entities/opencti_data_component.py +4 -0
  14. {pycti-6.3.13 → pycti-6.4.0}/pycti/entities/opencti_data_source.py +4 -0
  15. {pycti-6.3.13 → pycti-6.4.0}/pycti/entities/opencti_event.py +4 -0
  16. {pycti-6.3.13 → pycti-6.4.0}/pycti/entities/opencti_external_reference.py +6 -0
  17. {pycti-6.3.13 → pycti-6.4.0}/pycti/entities/opencti_feedback.py +4 -0
  18. {pycti-6.3.13 → pycti-6.4.0}/pycti/entities/opencti_grouping.py +9 -2
  19. {pycti-6.3.13 → pycti-6.4.0}/pycti/entities/opencti_identity.py +5 -2
  20. {pycti-6.3.13 → pycti-6.4.0}/pycti/entities/opencti_incident.py +4 -0
  21. {pycti-6.3.13 → pycti-6.4.0}/pycti/entities/opencti_indicator.py +4 -0
  22. {pycti-6.3.13 → pycti-6.4.0}/pycti/entities/opencti_infrastructure.py +4 -0
  23. {pycti-6.3.13 → pycti-6.4.0}/pycti/entities/opencti_intrusion_set.py +4 -0
  24. {pycti-6.3.13 → pycti-6.4.0}/pycti/entities/opencti_kill_chain_phase.py +4 -0
  25. {pycti-6.3.13 → pycti-6.4.0}/pycti/entities/opencti_language.py +4 -0
  26. {pycti-6.3.13 → pycti-6.4.0}/pycti/entities/opencti_location.py +22 -4
  27. {pycti-6.3.13 → pycti-6.4.0}/pycti/entities/opencti_malware.py +4 -0
  28. {pycti-6.3.13 → pycti-6.4.0}/pycti/entities/opencti_malware_analysis.py +10 -2
  29. {pycti-6.3.13 → pycti-6.4.0}/pycti/entities/opencti_marking_definition.py +6 -0
  30. {pycti-6.3.13 → pycti-6.4.0}/pycti/entities/opencti_narrative.py +4 -0
  31. {pycti-6.3.13 → pycti-6.4.0}/pycti/entities/opencti_note.py +12 -4
  32. {pycti-6.3.13 → pycti-6.4.0}/pycti/entities/opencti_observed_data.py +4 -0
  33. {pycti-6.3.13 → pycti-6.4.0}/pycti/entities/opencti_opinion.py +19 -3
  34. {pycti-6.3.13 → pycti-6.4.0}/pycti/entities/opencti_report.py +4 -0
  35. {pycti-6.3.13 → pycti-6.4.0}/pycti/entities/opencti_stix_core_relationship.py +22 -0
  36. {pycti-6.3.13 → pycti-6.4.0}/pycti/entities/opencti_stix_sighting_relationship.py +28 -5
  37. {pycti-6.3.13 → pycti-6.4.0}/pycti/entities/opencti_task.py +5 -2
  38. {pycti-6.3.13 → pycti-6.4.0}/pycti/entities/opencti_threat_actor.py +10 -3
  39. {pycti-6.3.13 → pycti-6.4.0}/pycti/entities/opencti_threat_actor_group.py +4 -0
  40. {pycti-6.3.13 → pycti-6.4.0}/pycti/entities/opencti_threat_actor_individual.py +4 -0
  41. {pycti-6.3.13 → pycti-6.4.0}/pycti/entities/opencti_tool.py +4 -0
  42. {pycti-6.3.13 → pycti-6.4.0}/pycti/entities/opencti_vocabulary.py +15 -0
  43. {pycti-6.3.13 → pycti-6.4.0}/pycti/entities/opencti_vulnerability.py +4 -0
  44. {pycti-6.3.13 → pycti-6.4.0}/pycti/utils/opencti_stix2.py +75 -50
  45. {pycti-6.3.13 → pycti-6.4.0}/pycti.egg-info/PKG-INFO +1 -1
  46. {pycti-6.3.13 → pycti-6.4.0}/LICENSE +0 -0
  47. {pycti-6.3.13 → pycti-6.4.0}/README.md +0 -0
  48. {pycti-6.3.13 → pycti-6.4.0}/pycti/api/__init__.py +0 -0
  49. {pycti-6.3.13 → pycti-6.4.0}/pycti/api/opencti_api_connector.py +0 -0
  50. {pycti-6.3.13 → pycti-6.4.0}/pycti/api/opencti_api_playbook.py +0 -0
  51. {pycti-6.3.13 → pycti-6.4.0}/pycti/api/opencti_api_work.py +0 -0
  52. {pycti-6.3.13 → pycti-6.4.0}/pycti/connector/__init__.py +0 -0
  53. {pycti-6.3.13 → pycti-6.4.0}/pycti/connector/opencti_connector.py +0 -0
  54. {pycti-6.3.13 → pycti-6.4.0}/pycti/connector/opencti_metric_handler.py +0 -0
  55. {pycti-6.3.13 → pycti-6.4.0}/pycti/entities/__init__.py +0 -0
  56. {pycti-6.3.13 → pycti-6.4.0}/pycti/entities/indicator/__init__.py +0 -0
  57. {pycti-6.3.13 → pycti-6.4.0}/pycti/entities/opencti_label.py +0 -0
  58. {pycti-6.3.13 → pycti-6.4.0}/pycti/entities/opencti_stix.py +0 -0
  59. {pycti-6.3.13 → pycti-6.4.0}/pycti/entities/opencti_stix_core_object.py +0 -0
  60. {pycti-6.3.13 → pycti-6.4.0}/pycti/entities/opencti_stix_cyber_observable.py +0 -0
  61. {pycti-6.3.13 → pycti-6.4.0}/pycti/entities/opencti_stix_domain_object.py +0 -0
  62. {pycti-6.3.13 → pycti-6.4.0}/pycti/entities/opencti_stix_nested_ref_relationship.py +0 -0
  63. {pycti-6.3.13 → pycti-6.4.0}/pycti/entities/opencti_stix_object_or_stix_relationship.py +0 -0
  64. {pycti-6.3.13 → pycti-6.4.0}/pycti/entities/stix_cyber_observable/__init__.py +0 -0
  65. {pycti-6.3.13 → pycti-6.4.0}/pycti/entities/stix_cyber_observable/opencti_stix_cyber_observable_deprecated.py +0 -0
  66. {pycti-6.3.13 → pycti-6.4.0}/pycti/entities/stix_cyber_observable/opencti_stix_cyber_observable_properties.py +0 -0
  67. {pycti-6.3.13 → pycti-6.4.0}/pycti/utils/__init__.py +0 -0
  68. {pycti-6.3.13 → pycti-6.4.0}/pycti/utils/constants.py +0 -0
  69. {pycti-6.3.13 → pycti-6.4.0}/pycti/utils/opencti_logger.py +0 -0
  70. {pycti-6.3.13 → pycti-6.4.0}/pycti/utils/opencti_stix2_identifier.py +0 -0
  71. {pycti-6.3.13 → pycti-6.4.0}/pycti/utils/opencti_stix2_splitter.py +0 -0
  72. {pycti-6.3.13 → pycti-6.4.0}/pycti/utils/opencti_stix2_update.py +0 -0
  73. {pycti-6.3.13 → pycti-6.4.0}/pycti/utils/opencti_stix2_utils.py +0 -0
  74. {pycti-6.3.13 → pycti-6.4.0}/pycti.egg-info/SOURCES.txt +0 -0
  75. {pycti-6.3.13 → pycti-6.4.0}/pycti.egg-info/dependency_links.txt +0 -0
  76. {pycti-6.3.13 → pycti-6.4.0}/pycti.egg-info/requires.txt +0 -0
  77. {pycti-6.3.13 → pycti-6.4.0}/pycti.egg-info/top_level.txt +0 -0
  78. {pycti-6.3.13 → pycti-6.4.0}/pyproject.toml +0 -0
  79. {pycti-6.3.13 → pycti-6.4.0}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: pycti
3
- Version: 6.3.13
3
+ Version: 6.4.0
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.13"
2
+ __version__ = "6.4.0"
3
3
 
4
4
  from .api.opencti_api_client import OpenCTIApiClient
5
5
  from .api.opencti_api_connector import OpenCTIApiConnector
@@ -213,6 +213,9 @@ class OpenCTIApiClient:
213
213
  def set_event_id(self, event_id):
214
214
  self.request_headers["opencti-event-id"] = event_id
215
215
 
216
+ def set_draft_id(self, draft_id):
217
+ self.request_headers["opencti-draft-id"] = draft_id
218
+
216
219
  def set_synchronized_upsert_header(self, synchronized):
217
220
  self.request_headers["synchronized-upsert"] = (
218
221
  "true" if synchronized is True else "false"
@@ -666,6 +669,34 @@ class OpenCTIApiClient:
666
669
  self.app_logger.error("[upload] Missing parameter: file_name")
667
670
  return None
668
671
 
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
+
669
700
  def upload_pending_file(self, **kwargs):
670
701
  """upload a file to OpenCTI API
671
702
 
@@ -253,10 +253,18 @@ class ListenQueue(threading.Thread):
253
253
  event_data = json_data["event"]
254
254
  entity_id = event_data.get("entity_id")
255
255
  entity_type = event_data.get("entity_type")
256
+ validation_mode = event_data.get("validation_mode", "workbench")
256
257
  # 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", "")
258
261
  self.helper.work_id = work_id
259
262
 
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
+
260
268
  self.helper.playbook = None
261
269
  self.helper.enrichment_shared_organizations = None
262
270
  if self.helper.connect_type == "INTERNAL_ENRICHMENT":
@@ -952,6 +960,8 @@ class OpenCTIConnectorHelper: # pylint: disable=too-many-public-methods
952
960
  "Connector registered with ID", {"id": self.connect_id}
953
961
  )
954
962
  self.work_id = None
963
+ self.validation_mode = "workbench"
964
+ self.draft_id = None
955
965
  self.playbook = None
956
966
  self.enrichment_shared_organizations = None
957
967
  self.connector_id = connector_configuration["id"]
@@ -1550,6 +1560,7 @@ class OpenCTIConnectorHelper: # pylint: disable=too-many-public-methods
1550
1560
  """send a stix2 bundle to the API
1551
1561
 
1552
1562
  :param work_id: a valid work id
1563
+ :param draft_id: a draft context to send the bundle to
1553
1564
  :param bundle: valid stix2 bundle
1554
1565
  :type bundle:
1555
1566
  :param entities_types: list of entities, defaults to None
@@ -1563,6 +1574,8 @@ class OpenCTIConnectorHelper: # pylint: disable=too-many-public-methods
1563
1574
  :rtype: list
1564
1575
  """
1565
1576
  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)
1566
1579
  entities_types = kwargs.get("entities_types", None)
1567
1580
  update = kwargs.get("update", False)
1568
1581
  event_version = kwargs.get("event_version", None)
@@ -1627,14 +1640,23 @@ class OpenCTIConnectorHelper: # pylint: disable=too-many-public-methods
1627
1640
  # Upload workbench in case of pending validation
1628
1641
  if not file_name and work_id:
1629
1642
  file_name = f"{work_id}.json"
1643
+
1630
1644
  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 []
1638
1660
 
1639
1661
  # If directory setup, write the bundle to the target directory
1640
1662
  if bundle_send_to_directory and bundle_send_to_directory_path is not None:
@@ -1749,6 +1771,7 @@ class OpenCTIConnectorHelper: # pylint: disable=too-many-public-methods
1749
1771
  entities_types=entities_types,
1750
1772
  sequence=sequence,
1751
1773
  update=update,
1774
+ draft_id=draft_id,
1752
1775
  )
1753
1776
  channel.close()
1754
1777
  pika_connection.close()
@@ -1774,11 +1797,14 @@ class OpenCTIConnectorHelper: # pylint: disable=too-many-public-methods
1774
1797
  :type entities_types: list, optional
1775
1798
  :param update: whether to update data in the database, defaults to False
1776
1799
  :type update: bool, optional
1800
+ :param draft_id: if draft_id is set, bundle must be set in draft context
1801
+ :type draft_id:
1777
1802
  """
1778
1803
  work_id = kwargs.get("work_id", None)
1779
1804
  sequence = kwargs.get("sequence", 0)
1780
1805
  update = kwargs.get("update", False)
1781
1806
  entities_types = kwargs.get("entities_types", None)
1807
+ draft_id = kwargs.get("draft_id", None)
1782
1808
 
1783
1809
  if entities_types is None:
1784
1810
  entities_types = []
@@ -1800,6 +1826,7 @@ class OpenCTIConnectorHelper: # pylint: disable=too-many-public-methods
1800
1826
  "utf-8"
1801
1827
  ),
1802
1828
  "update": update,
1829
+ "draft_id": draft_id,
1803
1830
  }
1804
1831
  if work_id is not None:
1805
1832
  message["work_id"] = work_id
@@ -92,6 +92,10 @@ INDICATOR_PROPERTIES = """
92
92
  x_opencti_score
93
93
  x_opencti_detection
94
94
  x_opencti_main_observable_type
95
+ x_opencti_observable_values {
96
+ type
97
+ value
98
+ }
95
99
  x_mitre_platforms
96
100
  observables {
97
101
  edges {
@@ -220,6 +224,10 @@ INDICATOR_PROPERTIES_WITH_FILES = """
220
224
  x_opencti_score
221
225
  x_opencti_detection
222
226
  x_opencti_main_observable_type
227
+ x_opencti_observable_values {
228
+ type
229
+ value
230
+ }
223
231
  x_mitre_platforms
224
232
  observables {
225
233
  edges {
@@ -222,15 +222,19 @@ class AttackPattern:
222
222
 
223
223
  @staticmethod
224
224
  def generate_id(name, x_mitre_id=None):
225
- name = name.lower().strip()
226
225
  if x_mitre_id is not None:
227
226
  data = {"x_mitre_id": x_mitre_id}
228
227
  else:
229
- data = {"name": name}
228
+ data = {"name": name.lower().strip()}
230
229
  data = canonicalize(data, utf8=False)
231
230
  id = str(uuid.uuid5(uuid.UUID("00abedb4-aa42-466c-9c01-fed23315a9b7"), data))
232
231
  return "attack-pattern--" + id
233
232
 
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
+
234
238
  """
235
239
  List Attack-Pattern objects
236
240
 
@@ -216,6 +216,10 @@ class Campaign:
216
216
  id = str(uuid.uuid5(uuid.UUID("00abedb4-aa42-466c-9c01-fed23315a9b7"), data))
217
217
  return "campaign--" + id
218
218
 
219
+ @staticmethod
220
+ def generate_id_from_data(data):
221
+ return Campaign.generate_id(data["name"])
222
+
219
223
  """
220
224
  List Campaign objects
221
225
 
@@ -461,6 +461,10 @@ class CaseIncident:
461
461
  id = str(uuid.uuid5(uuid.UUID("00abedb4-aa42-466c-9c01-fed23315a9b7"), data))
462
462
  return "case-incident--" + id
463
463
 
464
+ @staticmethod
465
+ def generate_id_from_data(data):
466
+ return CaseIncident.generate_id(data["name"], data["created"])
467
+
464
468
  """
465
469
  List Case Incident objects
466
470
 
@@ -457,6 +457,10 @@ class CaseRfi:
457
457
  id = str(uuid.uuid5(uuid.UUID("00abedb4-aa42-466c-9c01-fed23315a9b7"), data))
458
458
  return "case-rfi--" + id
459
459
 
460
+ @staticmethod
461
+ def generate_id_from_data(data):
462
+ return CaseRfi.generate_id(data["name"], data["created"])
463
+
460
464
  """
461
465
  List Case Rfi objects
462
466
 
@@ -457,6 +457,10 @@ class CaseRft:
457
457
  id = str(uuid.uuid5(uuid.UUID("00abedb4-aa42-466c-9c01-fed23315a9b7"), data))
458
458
  return "case-rft--" + id
459
459
 
460
+ @staticmethod
461
+ def generate_id_from_data(data):
462
+ return CaseRft.generate_id(data["name"], data["created"])
463
+
460
464
  """
461
465
  List Case Rft objects
462
466
 
@@ -212,6 +212,10 @@ class Channel:
212
212
  id = str(uuid.uuid5(uuid.UUID("00abedb4-aa42-466c-9c01-fed23315a9b7"), data))
213
213
  return "channel--" + id
214
214
 
215
+ @staticmethod
216
+ def generate_id_from_data(data):
217
+ return Channel.generate_id(data["name"])
218
+
215
219
  """
216
220
  List Channel objects
217
221
 
@@ -196,15 +196,18 @@ class CourseOfAction:
196
196
 
197
197
  @staticmethod
198
198
  def generate_id(name, x_mitre_id=None):
199
- name = name.lower().strip()
200
199
  if x_mitre_id is not None:
201
200
  data = {"x_mitre_id": x_mitre_id}
202
201
  else:
203
- data = {"name": name}
202
+ data = {"name": name.lower().strip()}
204
203
  data = canonicalize(data, utf8=False)
205
204
  id = str(uuid.uuid5(uuid.UUID("00abedb4-aa42-466c-9c01-fed23315a9b7"), data))
206
205
  return "course-of-action--" + id
207
206
 
207
+ @staticmethod
208
+ def generate_id_from_data(data):
209
+ return CourseOfAction.generate_id(data.get("name"), data.get("x_mitre_id"))
210
+
208
211
  """
209
212
  List Course-Of-Action objects
210
213
 
@@ -246,6 +246,10 @@ class DataComponent:
246
246
  id = str(uuid.uuid5(uuid.UUID("00abedb4-aa42-466c-9c01-fed23315a9b7"), data))
247
247
  return "data-component--" + id
248
248
 
249
+ @staticmethod
250
+ def generate_id_from_data(data):
251
+ return DataComponent.generate_id(data["name"])
252
+
249
253
  """
250
254
  List Data-Component objects
251
255
 
@@ -204,6 +204,10 @@ class DataSource:
204
204
  id = str(uuid.uuid5(uuid.UUID("00abedb4-aa42-466c-9c01-fed23315a9b7"), data))
205
205
  return "data-source--" + id
206
206
 
207
+ @staticmethod
208
+ def generate_id_from_data(data):
209
+ return DataSource.generate_id(data["name"])
210
+
207
211
  """
208
212
  List Data-Source objects
209
213
 
@@ -216,6 +216,10 @@ class Event:
216
216
  id = str(uuid.uuid5(uuid.UUID("00abedb4-aa42-466c-9c01-fed23315a9b7"), data))
217
217
  return "event--" + id
218
218
 
219
+ @staticmethod
220
+ def generate_id_from_data(data):
221
+ return Event.generate_id(data["name"])
222
+
219
223
  """
220
224
  List Event objects
221
225
 
@@ -68,6 +68,12 @@ class ExternalReference:
68
68
  id = str(uuid.uuid5(uuid.UUID("00abedb4-aa42-466c-9c01-fed23315a9b7"), data))
69
69
  return "external-reference--" + id
70
70
 
71
+ @staticmethod
72
+ def generate_id_from_data(data):
73
+ return ExternalReference.generate_id(
74
+ data.get("url"), data.get("source_name"), data.get("external_id")
75
+ )
76
+
71
77
  """
72
78
  List External-Reference objects
73
79
 
@@ -419,6 +419,10 @@ class Feedback:
419
419
  id = str(uuid.uuid5(uuid.UUID("00abedb4-aa42-466c-9c01-fed23315a9b7"), data))
420
420
  return "feedback--" + id
421
421
 
422
+ @staticmethod
423
+ def generate_id_from_data(data):
424
+ return Feedback.generate_id(data["name"])
425
+
422
426
  """
423
427
  List Feedback objects
424
428
 
@@ -396,16 +396,23 @@ class Grouping:
396
396
  """
397
397
 
398
398
  @staticmethod
399
- def generate_id(name, context, created):
399
+ def generate_id(name, context, created=None):
400
400
  name = name.lower().strip()
401
401
  context = context.lower().strip()
402
402
  if isinstance(created, datetime.datetime):
403
403
  created = created.isoformat()
404
- data = {"name": name, "context": context, "created": created}
404
+ if created is None:
405
+ data = {"name": name, "context": context}
406
+ else:
407
+ data = {"name": name, "context": context, "created": created}
405
408
  data = canonicalize(data, utf8=False)
406
409
  id = str(uuid.uuid5(uuid.UUID("00abedb4-aa42-466c-9c01-fed23315a9b7"), data))
407
410
  return "grouping--" + id
408
411
 
412
+ @staticmethod
413
+ def generate_id_from_data(data):
414
+ return Grouping.generate_id(data["name"], data["context"], data["created"])
415
+
409
416
  """
410
417
  List Grouping objects
411
418
 
@@ -226,12 +226,15 @@ class Identity:
226
226
 
227
227
  @staticmethod
228
228
  def generate_id(name, identity_class):
229
- name = name.lower().strip()
230
- data = {"name": name, "identity_class": identity_class}
229
+ data = {"name": name.lower().strip(), "identity_class": identity_class.lower()}
231
230
  data = canonicalize(data, utf8=False)
232
231
  id = str(uuid.uuid5(uuid.UUID("00abedb4-aa42-466c-9c01-fed23315a9b7"), data))
233
232
  return "identity--" + id
234
233
 
234
+ @staticmethod
235
+ def generate_id_from_data(data):
236
+ return Identity.generate_id(data["name"], data["identity_class"])
237
+
235
238
  """
236
239
  List Identity objects
237
240
 
@@ -225,6 +225,10 @@ class Incident:
225
225
  id = str(uuid.uuid5(uuid.UUID("00abedb4-aa42-466c-9c01-fed23315a9b7"), data))
226
226
  return "incident--" + id
227
227
 
228
+ @staticmethod
229
+ def generate_id_from_data(data):
230
+ return Incident.generate_id(data["name"], data["created"])
231
+
228
232
  """
229
233
  List Incident objects
230
234
 
@@ -29,6 +29,10 @@ class Indicator:
29
29
  id = str(uuid.uuid5(uuid.UUID("00abedb4-aa42-466c-9c01-fed23315a9b7"), data))
30
30
  return "indicator--" + id
31
31
 
32
+ @staticmethod
33
+ def generate_id_from_data(data):
34
+ return Indicator.generate_id(data["pattern"])
35
+
32
36
  def list(self, **kwargs):
33
37
  """List Indicator objects
34
38
 
@@ -239,6 +239,10 @@ class Infrastructure:
239
239
  id = str(uuid.uuid5(uuid.UUID("00abedb4-aa42-466c-9c01-fed23315a9b7"), data))
240
240
  return "infrastructure--" + id
241
241
 
242
+ @staticmethod
243
+ def generate_id_from_data(data):
244
+ return Infrastructure.generate_id(data["name"])
245
+
242
246
  def list(self, **kwargs):
243
247
  """List Infrastructure objects
244
248
 
@@ -222,6 +222,10 @@ class IntrusionSet:
222
222
  id = str(uuid.uuid5(uuid.UUID("00abedb4-aa42-466c-9c01-fed23315a9b7"), data))
223
223
  return "intrusion-set--" + id
224
224
 
225
+ @staticmethod
226
+ def generate_id_from_data(data):
227
+ return IntrusionSet.generate_id(data["name"])
228
+
225
229
  """
226
230
  List Intrusion-Set objects
227
231
 
@@ -28,6 +28,10 @@ class KillChainPhase:
28
28
  phase_name=phase_name, kill_chain_name=kill_chain_name
29
29
  )
30
30
 
31
+ @staticmethod
32
+ def generate_id_from_data(data):
33
+ return KillChainPhase.generate_id(data["phase_name"], data["kill_chain_name"])
34
+
31
35
  """
32
36
  List Kill-Chain-Phase objects
33
37
 
@@ -224,6 +224,10 @@ class Language:
224
224
  id = str(uuid.uuid5(uuid.UUID("00abedb4-aa42-466c-9c01-fed23315a9b7"), data))
225
225
  return "language--" + id
226
226
 
227
+ @staticmethod
228
+ def generate_id_from_data(data):
229
+ return Language.generate_id(data["name"])
230
+
227
231
  """
228
232
  List Language objects
229
233
 
@@ -210,15 +210,33 @@ class Location:
210
210
 
211
211
  @staticmethod
212
212
  def generate_id(name, x_opencti_location_type, latitude=None, longitude=None):
213
- name = name.lower().strip()
214
- if x_opencti_location_type == "position":
215
- data = {"name": name, "latitude": latitude, "longitude": longitude}
213
+ if x_opencti_location_type == "Position":
214
+ if latitude is not None and longitude is None:
215
+ data = {"latitude": latitude}
216
+ elif latitude is None and longitude is not None:
217
+ data = {"longitude": longitude}
218
+ elif latitude is not None and longitude is not None:
219
+ data = {"latitude": latitude, "longitude": longitude}
220
+ else:
221
+ data = {"name": name.lower().strip()}
216
222
  else:
217
- data = {"name": name, "x_opencti_location_type": x_opencti_location_type}
223
+ data = {
224
+ "name": name.lower().strip(),
225
+ "x_opencti_location_type": x_opencti_location_type,
226
+ }
218
227
  data = canonicalize(data, utf8=False)
219
228
  id = str(uuid.uuid5(uuid.UUID("00abedb4-aa42-466c-9c01-fed23315a9b7"), data))
220
229
  return "location--" + id
221
230
 
231
+ @staticmethod
232
+ def generate_id_from_data(data):
233
+ return Location.generate_id(
234
+ data.get("name"),
235
+ data.get("x_opencti_location_type"),
236
+ data.get("latitude"),
237
+ data.get("longitude"),
238
+ )
239
+
222
240
  """
223
241
  List Location objects
224
242
 
@@ -250,6 +250,10 @@ class Malware:
250
250
  id = str(uuid.uuid5(uuid.UUID("00abedb4-aa42-466c-9c01-fed23315a9b7"), data))
251
251
  return "malware--" + id
252
252
 
253
+ @staticmethod
254
+ def generate_id_from_data(data):
255
+ return Malware.generate_id(data["name"])
256
+
253
257
  """
254
258
  List Malware objects
255
259
 
@@ -219,13 +219,21 @@ class MalwareAnalysis:
219
219
  """
220
220
 
221
221
  @staticmethod
222
- def generate_id(result_name):
222
+ def generate_id(result_name, product=None, submitted=None):
223
223
  result_name = result_name.lower().strip()
224
- data = {"result_name": result_name}
224
+ data = {"result_name": result_name, "product": product}
225
+ if submitted is not None:
226
+ data = {**data, "submitted": submitted}
225
227
  data = canonicalize(data, utf8=False)
226
228
  id = str(uuid.uuid5(uuid.UUID("00abedb4-aa42-466c-9c01-fed23315a9b7"), data))
227
229
  return "malware-analysis--" + id
228
230
 
231
+ @staticmethod
232
+ def generate_id_from_data(data):
233
+ return MalwareAnalysis.generate_id(
234
+ data["result_name"], data["product"], data.get("submitted")
235
+ )
236
+
229
237
  """
230
238
  List Malware analysis objects
231
239
 
@@ -31,6 +31,12 @@ class MarkingDefinition:
31
31
  id = str(uuid.uuid5(uuid.UUID("00abedb4-aa42-466c-9c01-fed23315a9b7"), data))
32
32
  return "marking-definition--" + id
33
33
 
34
+ @staticmethod
35
+ def generate_id_from_data(data):
36
+ return MarkingDefinition.generate_id(
37
+ data["definition"], data["definition_type"]
38
+ )
39
+
34
40
  """
35
41
  List Marking-Definition objects
36
42
 
@@ -202,6 +202,10 @@ class Narrative:
202
202
  id = str(uuid.uuid5(uuid.UUID("00abedb4-aa42-466c-9c01-fed23315a9b7"), data))
203
203
  return "narrative--" + id
204
204
 
205
+ @staticmethod
206
+ def generate_id_from_data(data):
207
+ return Narrative.generate_id(data["name"])
208
+
205
209
  """
206
210
  List Narrative objects
207
211
 
@@ -436,14 +436,22 @@ class Note:
436
436
 
437
437
  @staticmethod
438
438
  def generate_id(created, content):
439
- content = content.lower().strip()
440
- if isinstance(created, datetime.datetime):
441
- created = created.isoformat()
442
- data = {"content": content, "created": created}
439
+ if content is None:
440
+ raise ValueError("content is required")
441
+ if created is not None:
442
+ if isinstance(created, datetime.datetime):
443
+ created = created.isoformat()
444
+ data = {"content": content, "created": created}
445
+ else:
446
+ data = {"content": content}
443
447
  data = canonicalize(data, utf8=False)
444
448
  id = str(uuid.uuid5(uuid.UUID("00abedb4-aa42-466c-9c01-fed23315a9b7"), data))
445
449
  return "note--" + id
446
450
 
451
+ @staticmethod
452
+ def generate_id_from_data(data):
453
+ return Note.generate_id(data.get("created"), data["content"])
454
+
447
455
  """
448
456
  List Note objects
449
457
 
@@ -439,6 +439,10 @@ class ObservedData:
439
439
  id = str(uuid.uuid5(uuid.UUID("00abedb4-aa42-466c-9c01-fed23315a9b7"), data))
440
440
  return "observed-data--" + id
441
441
 
442
+ @staticmethod
443
+ def generate_id_from_data(data):
444
+ return ObservedData.generate_id(data["object_refs"])
445
+
442
446
  """
443
447
  List ObservedData objects
444
448
 
@@ -1,8 +1,10 @@
1
1
  # coding: utf-8
2
-
2
+ import datetime
3
3
  import json
4
4
  import uuid
5
5
 
6
+ from stix2.canonicalization.Canonicalize import canonicalize
7
+
6
8
 
7
9
  class Opinion:
8
10
  def __init__(self, opencti):
@@ -211,8 +213,22 @@ class Opinion:
211
213
  """
212
214
 
213
215
  @staticmethod
214
- def generate_id():
215
- return "opinion--" + str(uuid.uuid4())
216
+ def generate_id(created, opinion):
217
+ if opinion is None:
218
+ raise ValueError("opinion is required")
219
+ if created is not None:
220
+ if isinstance(created, datetime.datetime):
221
+ created = created.isoformat()
222
+ data = {"opinion": opinion, "created": created}
223
+ else:
224
+ data = {"opinion": opinion}
225
+ data = canonicalize(data, utf8=False)
226
+ id = str(uuid.uuid5(uuid.UUID("00abedb4-aa42-466c-9c01-fed23315a9b7"), data))
227
+ return "opinion--" + id
228
+
229
+ @staticmethod
230
+ def generate_id_from_data(data):
231
+ return Opinion.generate_id(data.get("created"), data["opinion"])
216
232
 
217
233
  """
218
234
  List Opinion objects