pycti 6.5.0__py3-none-any.whl → 6.5.2__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 CHANGED
@@ -1,5 +1,5 @@
1
1
  # -*- coding: utf-8 -*-
2
- __version__ = "6.5.0"
2
+ __version__ = "6.5.2"
3
3
 
4
4
  from .api.opencti_api_client import OpenCTIApiClient
5
5
  from .api.opencti_api_connector import OpenCTIApiConnector
@@ -3,7 +3,7 @@ import base64
3
3
  import datetime
4
4
  import io
5
5
  import json
6
- from typing import Union
6
+ from typing import Dict, Tuple, Union
7
7
 
8
8
  import magic
9
9
  import requests
@@ -83,13 +83,13 @@ class OpenCTIApiClient:
83
83
  :param log_level: log level for the client
84
84
  :type log_level: str, optional
85
85
  :param ssl_verify: Requiring the requests to verify the TLS certificate at the server.
86
- :type ssl_verify: bool, optional
86
+ :type ssl_verify: bool, str, optional
87
87
  :param proxies:
88
88
  :type proxies: dict, optional, The proxy configuration, would have `http` and `https` attributes. Defaults to {}
89
89
  ```
90
90
  proxies: {
91
- "http: "http://my_proxy:8080"
92
- "https: "http://my_proxy:8080"
91
+ "http": "http://my_proxy:8080"
92
+ "https": "http://my_proxy:8080"
93
93
  }
94
94
  ```
95
95
  :param json_logging: format the logs as json if set to True
@@ -102,14 +102,14 @@ class OpenCTIApiClient:
102
102
 
103
103
  def __init__(
104
104
  self,
105
- url,
106
- token,
105
+ url: str,
106
+ token: str,
107
107
  log_level="info",
108
- ssl_verify=False,
109
- proxies=None,
108
+ ssl_verify: Union[bool, str] = False,
109
+ proxies: Union[Dict[str, str], None] = None,
110
110
  json_logging=False,
111
111
  bundle_send_to_queue=True,
112
- cert=None,
112
+ cert: Union[str, Tuple[str, str], None] = None,
113
113
  auth=None,
114
114
  perform_health_check=True,
115
115
  ):
@@ -712,12 +712,13 @@ class OpenCTIApiClient:
712
712
  data = kwargs.get("data", None)
713
713
  mime_type = kwargs.get("mime_type", "text/plain")
714
714
  entity_id = kwargs.get("entity_id", None)
715
+ file_markings = kwargs.get("file_markings", [])
715
716
 
716
717
  if file_name is not None:
717
718
  self.app_logger.info("Uploading a file.")
718
719
  query = """
719
- mutation UploadPending($file: Upload!, $entityId: String) {
720
- uploadPending(file: $file, entityId: $entityId) {
720
+ mutation UploadPending($file: Upload!, $entityId: String, $file_markings: [String!]) {
721
+ uploadPending(file: $file, entityId: $entityId, file_markings: $file_markings) {
721
722
  id
722
723
  name
723
724
  }
@@ -731,7 +732,11 @@ class OpenCTIApiClient:
731
732
  mime_type = magic.from_file(file_name, mime=True)
732
733
  return self.query(
733
734
  query,
734
- {"file": (File(file_name, data, mime_type)), "entityId": entity_id},
735
+ {
736
+ "file": (File(file_name, data, mime_type)),
737
+ "entityId": entity_id,
738
+ "file_markings": file_markings,
739
+ },
735
740
  )
736
741
  else:
737
742
  self.app_logger.error("[upload] Missing parameter: file_name")
@@ -82,6 +82,24 @@ class OpenCTIApiWork:
82
82
  except:
83
83
  self.api.app_logger.error("Cannot report expectation")
84
84
 
85
+ def add_draft_context(self, work_id: str, draft_context: str):
86
+ if self.api.bundle_send_to_queue:
87
+ self.api.app_logger.info(
88
+ "Update draft context",
89
+ {"work_id": work_id, "draft_context": draft_context},
90
+ )
91
+ query = """
92
+ mutation addDraftContext($id: ID!, $draftContext: String) {
93
+ workEdit(id: $id) {
94
+ addDraftContext(draftContext: $draftContext)
95
+ }
96
+ }
97
+ """
98
+ try:
99
+ self.api.query(query, {"id": work_id, "draftContext": draft_context})
100
+ except:
101
+ self.api.app_logger.error("Cannot report draft context")
102
+
85
103
  def initiate_work(self, connector_id: str, friendly_name: str) -> str:
86
104
  if self.api.bundle_send_to_queue:
87
105
  self.api.app_logger.info("Initiate work", {"connector_id": connector_id})
@@ -769,6 +769,9 @@ class OpenCTIConnectorHelper: # pylint: disable=too-many-public-methods
769
769
  def __init__(self, config: Dict, playbook_compatible=False) -> None:
770
770
  sys.excepthook = killProgramHook
771
771
 
772
+ # Cache
773
+ self.stream_collections = {}
774
+
772
775
  # Load API config
773
776
  self.config = config
774
777
  self.opencti_url = get_config_variable(
@@ -1063,6 +1066,9 @@ class OpenCTIConnectorHelper: # pylint: disable=too-many-public-methods
1063
1066
  "stream_live": True,
1064
1067
  "stream_public": False,
1065
1068
  }
1069
+ # Get from cache
1070
+ elif self.connect_live_stream_id in self.stream_collections:
1071
+ return self.stream_collections[self.connect_live_stream_id]
1066
1072
  else:
1067
1073
  query = """
1068
1074
  query StreamCollection($id: String!) {
@@ -1076,6 +1082,10 @@ class OpenCTIConnectorHelper: # pylint: disable=too-many-public-methods
1076
1082
  }
1077
1083
  """
1078
1084
  result = self.api.query(query, {"id": self.connect_live_stream_id})
1085
+ # Put in cache
1086
+ self.stream_collections[self.connect_live_stream_id] = result["data"][
1087
+ "streamCollection"
1088
+ ]
1079
1089
  return result["data"]["streamCollection"]
1080
1090
  else:
1081
1091
  raise ValueError("This connector is not connected to any stream")
@@ -1581,6 +1591,7 @@ class OpenCTIConnectorHelper: # pylint: disable=too-many-public-methods
1581
1591
  event_version = kwargs.get("event_version", None)
1582
1592
  bypass_validation = kwargs.get("bypass_validation", False)
1583
1593
  entity_id = kwargs.get("entity_id", None)
1594
+ file_markings = kwargs.get("file_markings", None)
1584
1595
  file_name = kwargs.get("file_name", None)
1585
1596
  bundle_send_to_queue = kwargs.get("send_to_queue", self.bundle_send_to_queue)
1586
1597
  cleanup_inconsistent_bundle = kwargs.get("cleanup_inconsistent_bundle", False)
@@ -1648,6 +1659,7 @@ class OpenCTIConnectorHelper: # pylint: disable=too-many-public-methods
1648
1659
  data=bundle,
1649
1660
  mime_type="application/json",
1650
1661
  entity_id=entity_id,
1662
+ file_markings=file_markings,
1651
1663
  )
1652
1664
  return []
1653
1665
  elif validation_mode == "draft" and not draft_id:
@@ -1732,6 +1744,8 @@ class OpenCTIConnectorHelper: # pylint: disable=too-many-public-methods
1732
1744
  if bundle_send_to_queue:
1733
1745
  if work_id:
1734
1746
  self.api.work.add_expectations(work_id, expectations_number)
1747
+ if draft_id:
1748
+ self.api.work.add_draft_context(work_id, draft_id)
1735
1749
  if entities_types is None:
1736
1750
  entities_types = []
1737
1751
  if self.queue_protocol == "amqp":
@@ -3,7 +3,7 @@ import deprecation
3
3
 
4
4
  class StixCyberObservableDeprecatedMixin:
5
5
  """
6
- deprecated [>=6.2 & <6.5]`
6
+ deprecated [>=6.2 & <6.8]`
7
7
  Promote a Stix-Observable to an Indicator
8
8
 
9
9
  :param id: the Stix-Observable id
@@ -1,5 +1,5 @@
1
- import datetime
2
1
  import logging
2
+ from datetime import datetime, timezone
3
3
 
4
4
  from pythonjsonlogger import jsonlogger
5
5
 
@@ -9,8 +9,8 @@ class CustomJsonFormatter(jsonlogger.JsonFormatter):
9
9
  super(CustomJsonFormatter, self).add_fields(log_record, record, message_dict)
10
10
  if not log_record.get("timestamp"):
11
11
  # This doesn't use record.created, so it is slightly off
12
- now = datetime.datetime.utcnow().strftime("%Y-%m-%dT%H:%M:%S.%fZ")
13
- log_record["timestamp"] = now
12
+ now = datetime.now(tz=timezone.utc)
13
+ log_record["timestamp"] = now.strftime("%Y-%m-%dT%H:%M:%S.%fZ")
14
14
  if log_record.get("level"):
15
15
  log_record["level"] = log_record["level"].upper()
16
16
  else:
@@ -2407,6 +2407,19 @@ class OpenCTIStix2:
2407
2407
 
2408
2408
  return bundle
2409
2409
 
2410
+ def apply_patch(self, item):
2411
+ input = item["opencti_field_patch"]
2412
+ if item["type"] == "relationship":
2413
+ self.opencti.stix_core_relationship.update_field(id=item["id"], input=input)
2414
+ elif item["type"] == "sighting":
2415
+ self.opencti.stix_sighting_relationship.update_field(
2416
+ id=item["id"], input=input
2417
+ )
2418
+ elif StixCyberObservableTypes.has_value(item["type"]):
2419
+ self.opencti.stix_cyber_observable.update_field(id=item["id"], input=input)
2420
+ else:
2421
+ self.opencti.stix_domain_object.update_field(id=item["id"], input=input)
2422
+
2410
2423
  def import_item(
2411
2424
  self,
2412
2425
  item,
@@ -2426,6 +2439,8 @@ class OpenCTIStix2:
2426
2439
  target_id = item["merge_target_id"]
2427
2440
  source_ids = item["merge_source_ids"]
2428
2441
  self.opencti.stix.merge(id=target_id, object_ids=source_ids)
2442
+ elif item["opencti_operation"] == "patch":
2443
+ self.apply_patch(item=item)
2429
2444
  else:
2430
2445
  raise ValueError("Not supported opencti_operation")
2431
2446
  elif item["type"] == "relationship":
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: pycti
3
- Version: 6.5.0
3
+ Version: 6.5.2
4
4
  Summary: Python API client for OpenCTI.
5
5
  Home-page: https://github.com/OpenCTI-Platform/client-python
6
6
  Author: Filigran
@@ -40,8 +40,8 @@ Requires-Dist: stix2~=3.0.1
40
40
  Provides-Extra: dev
41
41
  Requires-Dist: black~=24.4.0; extra == "dev"
42
42
  Requires-Dist: build~=1.2.1; extra == "dev"
43
- Requires-Dist: isort~=5.13.0; extra == "dev"
44
- Requires-Dist: types-pytz~=2024.2.0.20241221; extra == "dev"
43
+ Requires-Dist: isort~=6.0.0; extra == "dev"
44
+ Requires-Dist: types-pytz~=2025.1.0.20250204; extra == "dev"
45
45
  Requires-Dist: pre-commit~=3.8.0; extra == "dev"
46
46
  Requires-Dist: pytest-cases~=3.8.0; extra == "dev"
47
47
  Requires-Dist: pytest-cov~=5.0.0; extra == "dev"
@@ -1,12 +1,12 @@
1
- pycti/__init__.py,sha256=nb50J5QFy0XAEMlG0_I8V8vArmUilQt29UORF9rxK1c,5218
1
+ pycti/__init__.py,sha256=vuINZwJahwqFmxz7ZhxCxVAk8eEKHZO6_pveLWsEQ3U,5218
2
2
  pycti/api/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3
- pycti/api/opencti_api_client.py,sha256=OBXRNBbkYsbgqjxeY5iltHXIDeWqpFN3-SKsinkr66U,32402
3
+ pycti/api/opencti_api_client.py,sha256=6TKvtgAk0iYQL2RaLTUV6cxCBefEgwmlL_iVHgzf_ow,32745
4
4
  pycti/api/opencti_api_connector.py,sha256=ubM_zPjTD8L33TEugCQgf_YF9zugDFg_7FgNubGlwJw,5447
5
5
  pycti/api/opencti_api_playbook.py,sha256=456We78vESukfSOi_CctfZ9dbBJEi76EHClRc2f21Js,1628
6
- pycti/api/opencti_api_work.py,sha256=JLfl7oy6Cq9IrYW_kUrqwzN46FoVzyIn1JJQKyK0h_w,7615
6
+ pycti/api/opencti_api_work.py,sha256=qIRJMCfyC9odXf7LMRg9ImYizqF2WHUOU7Ty5IUFGg8,8351
7
7
  pycti/connector/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
8
8
  pycti/connector/opencti_connector.py,sha256=5oVvS27KWhzRiofJAeQPDtba-EP83FziSistyEd5l-U,2561
9
- pycti/connector/opencti_connector_helper.py,sha256=b-6ymDF3e4pfVxRhB13vj8Vwzo3h2TcV1iud6pPrKOk,80672
9
+ pycti/connector/opencti_connector_helper.py,sha256=ee8Ej43Ox9W7G44PEfHNGZuU_NmBVNl7dE3V3BzE4sE,81286
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=rj3o2bFCoXniLmD9Ithi09S9Us8ab1G-GFLgqS8jll0,22237
@@ -57,18 +57,18 @@ pycti/entities/opencti_vulnerability.py,sha256=PxlfRKrwmkqU5E8o8WF04YRXJtCxBu5QB
57
57
  pycti/entities/indicator/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
58
58
  pycti/entities/indicator/opencti_indicator_properties.py,sha256=8X8YkCshM0gkHc9sQZ_WSNvVxOA4aTJmLta8ZG93HOU,5087
59
59
  pycti/entities/stix_cyber_observable/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
60
- pycti/entities/stix_cyber_observable/opencti_stix_cyber_observable_deprecated.py,sha256=BH65h6xBz7naG61t5nvBdhnGYnobNuiUZf8CJC4-nnc,1847
60
+ pycti/entities/stix_cyber_observable/opencti_stix_cyber_observable_deprecated.py,sha256=q-2G6OOqvUC1U2hSKxD8uT5T18M_IDkl72Tn1KoumQI,1847
61
61
  pycti/entities/stix_cyber_observable/opencti_stix_cyber_observable_properties.py,sha256=MN56CW8RWZwB0Pr8UiHZy_4nSzbgFbwdhSFKpsZ_d1Y,11293
62
62
  pycti/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
63
63
  pycti/utils/constants.py,sha256=ZgOVxY5bnrHiNvPgOfZLWk16sSDnaE_tg8JVjZpw24Q,11831
64
- pycti/utils/opencti_logger.py,sha256=0dvB75V0SuPFGxL539dAQrxTt1N5Acx0A3Ogwl5WMJ8,2199
65
- pycti/utils/opencti_stix2.py,sha256=1b63TSl3ImiKkSJQKhNgqrUbMRIEc-o2_1GQ1G-Y5bY,116949
64
+ pycti/utils/opencti_logger.py,sha256=BHNy9fJuTUTn_JEYSCmyvVwd6y-9ZJKxO40mY4iZ0bc,2226
65
+ pycti/utils/opencti_stix2.py,sha256=pUx1Oglb1EnREheMzmJBrmRS17vGnZzF8PCmB-pjNM0,117680
66
66
  pycti/utils/opencti_stix2_identifier.py,sha256=k8L1z4q1xdCBfxqUba4YS_kT-MmbJFxYh0RvfGOmrOs,837
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.0.dist-info/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
71
- pycti-6.5.0.dist-info/METADATA,sha256=TcX9lETk9rErFrgsy_g5cLqE2uvnc2T58bWc6EHaXNc,5419
72
- pycti-6.5.0.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
73
- pycti-6.5.0.dist-info/top_level.txt,sha256=cqEpxitAhHP4VgSA6xmrak6Yk9MeBkwoMTB6k7d2ZnE,6
74
- pycti-6.5.0.dist-info/RECORD,,
70
+ pycti-6.5.2.dist-info/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
71
+ pycti-6.5.2.dist-info/METADATA,sha256=t7YyxQyOxdyFCqF7DF_iWCG9XK-Hj8vMWuB9vHvrRlQ,5418
72
+ pycti-6.5.2.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
73
+ pycti-6.5.2.dist-info/top_level.txt,sha256=cqEpxitAhHP4VgSA6xmrak6Yk9MeBkwoMTB6k7d2ZnE,6
74
+ pycti-6.5.2.dist-info/RECORD,,
File without changes
File without changes