pycti 6.0.9__py3-none-any.whl → 6.1.0__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.0.9"
2
+ __version__ = "6.1.0"
3
3
 
4
4
  from .api.opencti_api_client import OpenCTIApiClient
5
5
  from .api.opencti_api_connector import OpenCTIApiConnector
@@ -53,9 +53,14 @@ from .entities.opencti_vulnerability import Vulnerability
53
53
  from .utils.constants import (
54
54
  CustomObjectCaseIncident,
55
55
  CustomObjectTask,
56
+ CustomObservableBankAccount,
57
+ CustomObservableCredential,
56
58
  CustomObservableCryptocurrencyWallet,
57
59
  CustomObservableHostname,
60
+ CustomObservablePaymentCard,
61
+ CustomObservablePhoneNumber,
58
62
  CustomObservableText,
63
+ CustomObservableTrackingNumber,
59
64
  CustomObservableUserAgent,
60
65
  MultipleRefRelationship,
61
66
  StixCyberObservableTypes,
@@ -128,9 +133,14 @@ __all__ = [
128
133
  "CustomObjectCaseIncident",
129
134
  "CustomObjectTask",
130
135
  "StixCyberObservableTypes",
136
+ "CustomObservableCredential",
131
137
  "CustomObservableHostname",
132
138
  "CustomObservableUserAgent",
139
+ "CustomObservableBankAccount",
133
140
  "CustomObservableCryptocurrencyWallet",
141
+ "CustomObservablePaymentCard",
142
+ "CustomObservablePhoneNumber",
143
+ "CustomObservableTrackingNumber",
134
144
  "CustomObservableText",
135
145
  "STIX_EXT_MITRE",
136
146
  "STIX_EXT_OCTI_SCO",
@@ -108,6 +108,7 @@ class OpenCTIApiClient:
108
108
  ssl_verify=False,
109
109
  proxies=None,
110
110
  json_logging=False,
111
+ bundle_send_to_queue=True,
111
112
  cert=None,
112
113
  auth=None,
113
114
  perform_health_check=True,
@@ -115,6 +116,7 @@ class OpenCTIApiClient:
115
116
  """Constructor method"""
116
117
 
117
118
  # Check configuration
119
+ self.bundle_send_to_queue = bundle_send_to_queue
118
120
  self.ssl_verify = ssl_verify
119
121
  self.cert = cert
120
122
  self.proxies = proxies
@@ -205,6 +207,12 @@ class OpenCTIApiClient:
205
207
  def set_applicant_id_header(self, applicant_id):
206
208
  self.request_headers["opencti-applicant-id"] = applicant_id
207
209
 
210
+ def set_playbook_id_header(self, playbook_id):
211
+ self.request_headers["opencti-playbook-id"] = playbook_id
212
+
213
+ def set_event_id(self, event_id):
214
+ self.request_headers["opencti-event-id"] = event_id
215
+
208
216
  def set_synchronized_upsert_header(self, synchronized):
209
217
  self.request_headers["synchronized-upsert"] = (
210
218
  "true" if synchronized is True else "false"
@@ -614,18 +622,19 @@ class OpenCTIApiClient:
614
622
  """upload a file to OpenCTI API
615
623
 
616
624
  :param `**kwargs`: arguments for file upload (required: `file_name` and `data`)
617
- :return: returns the query respons for the file upload
625
+ :return: returns the query response for the file upload
618
626
  :rtype: dict
619
627
  """
620
628
 
621
629
  file_name = kwargs.get("file_name", None)
630
+ file_markings = kwargs.get("file_markings", None)
622
631
  data = kwargs.get("data", None)
623
632
  mime_type = kwargs.get("mime_type", "text/plain")
624
633
  if file_name is not None:
625
634
  self.app_logger.info("Uploading a file.")
626
635
  query = """
627
- mutation UploadImport($file: Upload!) {
628
- uploadImport(file: $file) {
636
+ mutation UploadImport($file: Upload!, $fileMarkings: [String]) {
637
+ uploadImport(file: $file, fileMarkings: $fileMarkings) {
629
638
  id
630
639
  name
631
640
  }
@@ -637,8 +646,11 @@ class OpenCTIApiClient:
637
646
  mime_type = "application/json"
638
647
  else:
639
648
  mime_type = magic.from_file(file_name, mime=True)
640
-
641
- return self.query(query, {"file": (File(file_name, data, mime_type))})
649
+ query_vars = {"file": (File(file_name, data, mime_type))}
650
+ # optional file markings
651
+ if file_markings is not None:
652
+ query_vars["fileMarkings"] = file_markings
653
+ return self.query(query, query_vars)
642
654
  else:
643
655
  self.app_logger.error("[upload] Missing parameter: file_name")
644
656
  return None
@@ -9,14 +9,15 @@ class OpenCTIApiPlaybook:
9
9
  "Executing playbook step", {"playbook_id": playbook["playbook_id"]}
10
10
  )
11
11
  query = """
12
- mutation PlaybookStepExecution($execution_id: ID!, $execution_start: DateTime!, $data_instance_id: ID!, $playbook_id: ID!, $previous_step_id: ID!, $step_id: ID!, $previous_bundle: String!, $bundle: String!) {
13
- playbookStepExecution(execution_id: $execution_id, execution_start: $execution_start, data_instance_id: $data_instance_id, playbook_id: $playbook_id, previous_step_id: $previous_step_id, step_id: $step_id, previous_bundle: $previous_bundle, bundle: $bundle)
12
+ mutation PlaybookStepExecution($execution_id: ID!, $event_id: ID!, $execution_start: DateTime!, $data_instance_id: ID!, $playbook_id: ID!, $previous_step_id: ID!, $step_id: ID!, $previous_bundle: String!, $bundle: String!) {
13
+ playbookStepExecution(execution_id: $execution_id, event_id: $event_id, execution_start: $execution_start, data_instance_id: $data_instance_id, playbook_id: $playbook_id, previous_step_id: $previous_step_id, step_id: $step_id, previous_bundle: $previous_bundle, bundle: $bundle)
14
14
  }
15
15
  """
16
16
  self.api.query(
17
17
  query,
18
18
  {
19
19
  "execution_id": playbook["execution_id"],
20
+ "event_id": playbook["event_id"],
20
21
  "execution_start": playbook["execution_start"],
21
22
  "playbook_id": playbook["playbook_id"],
22
23
  "data_instance_id": playbook["data_instance_id"],
@@ -9,28 +9,34 @@ class OpenCTIApiWork:
9
9
  self.api = api
10
10
 
11
11
  def to_received(self, work_id: str, message: str):
12
- self.api.app_logger.info("Reporting work update_received", {"work_id": work_id})
13
- query = """
14
- mutation workToReceived($id: ID!, $message: String) {
15
- workEdit(id: $id) {
16
- toReceived (message: $message)
12
+ if self.api.bundle_send_to_queue:
13
+ self.api.app_logger.info(
14
+ "Reporting work update_received", {"work_id": work_id}
15
+ )
16
+ query = """
17
+ mutation workToReceived($id: ID!, $message: String) {
18
+ workEdit(id: $id) {
19
+ toReceived (message: $message)
20
+ }
17
21
  }
18
- }
19
- """
20
- self.api.query(query, {"id": work_id, "message": message})
22
+ """
23
+ self.api.query(query, {"id": work_id, "message": message})
21
24
 
22
25
  def to_processed(self, work_id: str, message: str, in_error: bool = False):
23
- self.api.app_logger.info(
24
- "Reporting work update_processed", {"work_id": work_id}
25
- )
26
- query = """
27
- mutation workToProcessed($id: ID!, $message: String, $inError: Boolean) {
28
- workEdit(id: $id) {
29
- toProcessed (message: $message, inError: $inError)
26
+ if self.api.bundle_send_to_queue:
27
+ self.api.app_logger.info(
28
+ "Reporting work update_processed", {"work_id": work_id}
29
+ )
30
+ query = """
31
+ mutation workToProcessed($id: ID!, $message: String, $inError: Boolean) {
32
+ workEdit(id: $id) {
33
+ toProcessed (message: $message, inError: $inError)
34
+ }
30
35
  }
31
- }
32
- """
33
- self.api.query(query, {"id": work_id, "message": message, "inError": in_error})
36
+ """
37
+ self.api.query(
38
+ query, {"id": work_id, "message": message, "inError": in_error}
39
+ )
34
40
 
35
41
  def ping(self, work_id: str):
36
42
  self.api.app_logger.info("Ping work", {"work_id": work_id})
@@ -44,49 +50,52 @@ class OpenCTIApiWork:
44
50
  self.api.query(query, {"id": work_id})
45
51
 
46
52
  def report_expectation(self, work_id: str, error):
47
- self.api.app_logger.info("Report expectation", {"work_id": work_id})
48
- query = """
49
- mutation reportExpectation($id: ID!, $error: WorkErrorInput) {
50
- workEdit(id: $id) {
51
- reportExpectation(error: $error)
53
+ if self.api.bundle_send_to_queue:
54
+ self.api.app_logger.info("Report expectation", {"work_id": work_id})
55
+ query = """
56
+ mutation reportExpectation($id: ID!, $error: WorkErrorInput) {
57
+ workEdit(id: $id) {
58
+ reportExpectation(error: $error)
59
+ }
52
60
  }
53
- }
54
- """
55
- try:
56
- self.api.query(query, {"id": work_id, "error": error})
57
- except:
58
- self.api.app_logger.error("Cannot report expectation")
61
+ """
62
+ try:
63
+ self.api.query(query, {"id": work_id, "error": error})
64
+ except:
65
+ self.api.app_logger.error("Cannot report expectation")
59
66
 
60
67
  def add_expectations(self, work_id: str, expectations: int):
61
- self.api.app_logger.info(
62
- "Update action expectations",
63
- {"work_id": work_id, "expectations": expectations},
64
- )
65
- query = """
66
- mutation addExpectations($id: ID!, $expectations: Int) {
67
- workEdit(id: $id) {
68
- addExpectations(expectations: $expectations)
68
+ if self.api.bundle_send_to_queue:
69
+ self.api.app_logger.info(
70
+ "Update action expectations",
71
+ {"work_id": work_id, "expectations": expectations},
72
+ )
73
+ query = """
74
+ mutation addExpectations($id: ID!, $expectations: Int) {
75
+ workEdit(id: $id) {
76
+ addExpectations(expectations: $expectations)
77
+ }
69
78
  }
70
- }
71
- """
72
- try:
73
- self.api.query(query, {"id": work_id, "expectations": expectations})
74
- except:
75
- self.api.app_logger.error("Cannot report expectation")
79
+ """
80
+ try:
81
+ self.api.query(query, {"id": work_id, "expectations": expectations})
82
+ except:
83
+ self.api.app_logger.error("Cannot report expectation")
76
84
 
77
85
  def initiate_work(self, connector_id: str, friendly_name: str) -> str:
78
- self.api.app_logger.info("Initiate work", {"connector_id": connector_id})
79
- query = """
80
- mutation workAdd($connectorId: String!, $friendlyName: String) {
81
- workAdd(connectorId: $connectorId, friendlyName: $friendlyName) {
82
- id
86
+ if self.api.bundle_send_to_queue:
87
+ self.api.app_logger.info("Initiate work", {"connector_id": connector_id})
88
+ query = """
89
+ mutation workAdd($connectorId: String!, $friendlyName: String) {
90
+ workAdd(connectorId: $connectorId, friendlyName: $friendlyName) {
91
+ id
92
+ }
83
93
  }
84
- }
85
- """
86
- work = self.api.query(
87
- query, {"connectorId": connector_id, "friendlyName": friendly_name}
88
- )
89
- return work["data"]["workAdd"]["id"]
94
+ """
95
+ work = self.api.query(
96
+ query, {"connectorId": connector_id, "friendlyName": friendly_name}
97
+ )
98
+ return work["data"]["workAdd"]["id"]
90
99
 
91
100
  def delete_work(self, work_id: str):
92
101
  query = """
@@ -282,18 +282,20 @@ class ListenQueue(threading.Thread):
282
282
  is_playbook = "playbook" in json_data["internal"]
283
283
  # If playbook, compute object on data bundle
284
284
  if is_playbook:
285
- execution_id = json_data["internal"]["playbook"]["execution_id"]
286
285
  execution_start = self.helper.date_now()
287
- playbook_id = json_data["internal"]["playbook"]["playbook_id"]
288
- data_instance_id = json_data["internal"]["playbook"][
286
+ event_id = json_data["internal"]["playbook"].get("event_id")
287
+ execution_id = json_data["internal"]["playbook"].get("execution_id")
288
+ playbook_id = json_data["internal"]["playbook"].get("playbook_id")
289
+ data_instance_id = json_data["internal"]["playbook"].get(
289
290
  "data_instance_id"
290
- ]
291
+ )
291
292
  previous_bundle = json.dumps((json_data["event"]["bundle"]))
292
293
  step_id = json_data["internal"]["playbook"]["step_id"]
293
294
  previous_step_id = json_data["internal"]["playbook"][
294
295
  "previous_step_id"
295
296
  ]
296
297
  playbook_data = {
298
+ "event_id": event_id,
297
299
  "execution_id": execution_id,
298
300
  "execution_start": execution_start,
299
301
  "playbook_id": playbook_id,
@@ -728,6 +730,32 @@ class OpenCTIConnectorHelper: # pylint: disable=too-many-public-methods
728
730
  self.connect_auto = get_config_variable(
729
731
  "CONNECTOR_AUTO", ["connector", "auto"], config, False, False
730
732
  )
733
+ self.bundle_send_to_queue = get_config_variable(
734
+ "CONNECTOR_SEND_TO_QUEUE",
735
+ ["connector", "send_to_queue"],
736
+ config,
737
+ False,
738
+ True,
739
+ )
740
+ self.bundle_send_to_directory = get_config_variable(
741
+ "CONNECTOR_SEND_TO_DIRECTORY",
742
+ ["connector", "send_to_directory"],
743
+ config,
744
+ False,
745
+ False,
746
+ )
747
+ self.bundle_send_to_directory_path = get_config_variable(
748
+ "CONNECTOR_SEND_TO_DIRECTORY_PATH",
749
+ ["connector", "send_to_directory_path"],
750
+ config,
751
+ )
752
+ self.bundle_send_to_directory_retention = get_config_variable(
753
+ "CONNECTOR_SEND_TO_DIRECTORY_RETENTION",
754
+ ["connector", "send_to_directory_retention"],
755
+ config,
756
+ True,
757
+ 7,
758
+ )
731
759
  self.connect_only_contextual = get_config_variable(
732
760
  "CONNECTOR_ONLY_CONTEXTUAL",
733
761
  ["connector", "only_contextual"],
@@ -749,6 +777,8 @@ class OpenCTIConnectorHelper: # pylint: disable=too-many-public-methods
749
777
  "CONNECTOR_VALIDATE_BEFORE_IMPORT",
750
778
  ["connector", "validate_before_import"],
751
779
  config,
780
+ False,
781
+ False,
752
782
  )
753
783
  # Start up the server to expose the metrics.
754
784
  expose_metrics = get_config_variable(
@@ -769,6 +799,7 @@ class OpenCTIConnectorHelper: # pylint: disable=too-many-public-methods
769
799
  self.opencti_token,
770
800
  self.log_level,
771
801
  json_logging=self.opencti_json_logging,
802
+ bundle_send_to_queue=self.bundle_send_to_queue,
772
803
  )
773
804
  # - Impersonate API that will use applicant id
774
805
  # Behave like standard api if applicant not found
@@ -777,6 +808,7 @@ class OpenCTIConnectorHelper: # pylint: disable=too-many-public-methods
777
808
  self.opencti_token,
778
809
  self.log_level,
779
810
  json_logging=self.opencti_json_logging,
811
+ bundle_send_to_queue=self.bundle_send_to_queue,
780
812
  )
781
813
  self.connector_logger = self.api.logger_class(self.connect_name)
782
814
  # For retro compatibility
@@ -1075,6 +1107,16 @@ class OpenCTIConnectorHelper: # pylint: disable=too-many-public-methods
1075
1107
  bypass_validation = kwargs.get("bypass_validation", False)
1076
1108
  entity_id = kwargs.get("entity_id", None)
1077
1109
  file_name = kwargs.get("file_name", None)
1110
+ bundle_send_to_queue = kwargs.get("send_to_queue", self.bundle_send_to_queue)
1111
+ bundle_send_to_directory = kwargs.get(
1112
+ "send_to_directory", self.bundle_send_to_directory
1113
+ )
1114
+ bundle_send_to_directory_path = kwargs.get(
1115
+ "send_to_directory_path", self.bundle_send_to_directory_path
1116
+ )
1117
+ bundle_send_to_directory_retention = kwargs.get(
1118
+ "send_to_directory_retention", self.bundle_send_to_directory_retention
1119
+ )
1078
1120
 
1079
1121
  # In case of enrichment ingestion, ensure the sharing if needed
1080
1122
  if self.enrichment_shared_organizations is not None:
@@ -1114,13 +1156,14 @@ class OpenCTIConnectorHelper: # pylint: disable=too-many-public-methods
1114
1156
  )
1115
1157
  bundle = json.dumps(bundle_data)
1116
1158
 
1159
+ # If execution in playbook, callback the api
1117
1160
  if self.playbook is not None:
1118
1161
  self.api.playbook.playbook_step_execution(self.playbook, bundle)
1119
1162
  return [bundle]
1120
1163
 
1164
+ # Upload workbench in case of pending validation
1121
1165
  if not file_name and work_id:
1122
1166
  file_name = f"{work_id}.json"
1123
-
1124
1167
  if self.connect_validate_before_import and not bypass_validation and file_name:
1125
1168
  self.api.upload_pending_file(
1126
1169
  file_name=file_name,
@@ -1130,8 +1173,61 @@ class OpenCTIConnectorHelper: # pylint: disable=too-many-public-methods
1130
1173
  )
1131
1174
  return []
1132
1175
 
1133
- if entities_types is None:
1134
- entities_types = []
1176
+ # If directory setup, write the bundle to the target directory
1177
+ if bundle_send_to_directory and bundle_send_to_directory_path is not None:
1178
+ self.connector_logger.info(
1179
+ "The connector sending bundle to directory",
1180
+ {
1181
+ "connector": self.connect_name,
1182
+ "directory": bundle_send_to_directory_path,
1183
+ "also_queuing": bundle_send_to_queue,
1184
+ },
1185
+ )
1186
+ bundle_file = (
1187
+ self.connect_name.lower().replace(" ", "_")
1188
+ + "-"
1189
+ + time.strftime("%Y%m%d-%H%M%S-")
1190
+ + str(time.time())
1191
+ + ".json"
1192
+ )
1193
+ write_file = os.path.join(
1194
+ bundle_send_to_directory_path, bundle_file + ".tmp"
1195
+ )
1196
+ message_bundle = {
1197
+ "bundle_type": "DIRECTORY_BUNDLE",
1198
+ "applicant_id": self.applicant_id,
1199
+ "connector": {
1200
+ "id": self.connect_id,
1201
+ "name": self.connect_name,
1202
+ "type": self.connect_type,
1203
+ "scope": self.connect_scope,
1204
+ "auto": self.connect_auto,
1205
+ "validate_before_import": self.connect_validate_before_import,
1206
+ },
1207
+ "entities_types": entities_types,
1208
+ "bundle": json.loads(bundle),
1209
+ "update": update,
1210
+ }
1211
+ # Maintains the list of files under control
1212
+ if bundle_send_to_directory_retention > 0: # If 0, disable the auto remove
1213
+ current_time = time.time()
1214
+ for f in os.listdir(bundle_send_to_directory_path):
1215
+ if f.endswith(".json"):
1216
+ file_location = os.path.join(bundle_send_to_directory_path, f)
1217
+ file_time = os.stat(file_location).st_mtime
1218
+ is_expired_file = (
1219
+ file_time
1220
+ < current_time - 86400 * bundle_send_to_directory_retention
1221
+ ) # 86400 = 1 day
1222
+ if is_expired_file:
1223
+ os.remove(file_location)
1224
+ # Write the bundle to target directory
1225
+ with open(write_file, "w") as f:
1226
+ str_bundle = json.dumps(message_bundle)
1227
+ f.write(str_bundle)
1228
+ # Rename the file after full write
1229
+ final_write_file = os.path.join(bundle_send_to_directory_path, bundle_file)
1230
+ os.rename(write_file, final_write_file)
1135
1231
 
1136
1232
  if bypass_split:
1137
1233
  bundles = [bundle]
@@ -1143,44 +1239,48 @@ class OpenCTIConnectorHelper: # pylint: disable=too-many-public-methods
1143
1239
  self.metric.inc("error_count")
1144
1240
  raise ValueError("Nothing to import")
1145
1241
 
1146
- if work_id:
1147
- self.api.work.add_expectations(work_id, len(bundles))
1148
-
1149
- pika_credentials = pika.PlainCredentials(
1150
- self.connector_config["connection"]["user"],
1151
- self.connector_config["connection"]["pass"],
1152
- )
1153
- pika_parameters = pika.ConnectionParameters(
1154
- host=self.connector_config["connection"]["host"],
1155
- port=self.connector_config["connection"]["port"],
1156
- virtual_host=self.connector_config["connection"]["vhost"],
1157
- credentials=pika_credentials,
1158
- ssl_options=(
1159
- pika.SSLOptions(
1160
- create_mq_ssl_context(self.config),
1161
- self.connector_config["connection"]["host"],
1162
- )
1163
- if self.connector_config["connection"]["use_ssl"]
1164
- else None
1165
- ),
1166
- )
1167
- pika_connection = pika.BlockingConnection(pika_parameters)
1168
- channel = pika_connection.channel()
1169
- try:
1170
- channel.confirm_delivery()
1171
- except Exception as err: # pylint: disable=broad-except
1172
- self.connector_logger.warning(str(err))
1173
- for sequence, bundle in enumerate(bundles, start=1):
1174
- self._send_bundle(
1175
- channel,
1176
- bundle,
1177
- work_id=work_id,
1178
- entities_types=entities_types,
1179
- sequence=sequence,
1180
- update=update,
1242
+ if bundle_send_to_queue:
1243
+ if work_id:
1244
+ self.api.work.add_expectations(work_id, len(bundles))
1245
+ if entities_types is None:
1246
+ entities_types = []
1247
+ pika_credentials = pika.PlainCredentials(
1248
+ self.connector_config["connection"]["user"],
1249
+ self.connector_config["connection"]["pass"],
1181
1250
  )
1182
- channel.close()
1183
- pika_connection.close()
1251
+ pika_parameters = pika.ConnectionParameters(
1252
+ host=self.connector_config["connection"]["host"],
1253
+ port=self.connector_config["connection"]["port"],
1254
+ virtual_host=self.connector_config["connection"]["vhost"],
1255
+ credentials=pika_credentials,
1256
+ ssl_options=(
1257
+ pika.SSLOptions(
1258
+ create_mq_ssl_context(self.config),
1259
+ self.connector_config["connection"]["host"],
1260
+ )
1261
+ if self.connector_config["connection"]["use_ssl"]
1262
+ else None
1263
+ ),
1264
+ )
1265
+ pika_connection = pika.BlockingConnection(pika_parameters)
1266
+ channel = pika_connection.channel()
1267
+ try:
1268
+ channel.confirm_delivery()
1269
+ except Exception as err: # pylint: disable=broad-except
1270
+ self.connector_logger.warning(str(err))
1271
+ self.connector_logger.info(self.connect_name + " sending bundle to queue")
1272
+ for sequence, bundle in enumerate(bundles, start=1):
1273
+ self._send_bundle(
1274
+ channel,
1275
+ bundle,
1276
+ work_id=work_id,
1277
+ entities_types=entities_types,
1278
+ sequence=sequence,
1279
+ update=update,
1280
+ )
1281
+ channel.close()
1282
+ pika_connection.close()
1283
+
1184
1284
  return bundles
1185
1285
 
1186
1286
  def _send_bundle(self, channel, bundle, **kwargs) -> None:
@@ -1212,6 +1312,7 @@ class OpenCTIConnectorHelper: # pylint: disable=too-many-public-methods
1212
1312
  # if self.current_work_id is None:
1213
1313
  # raise ValueError('The job id must be specified')
1214
1314
  message = {
1315
+ "bundle_type": "QUEUE_BUNDLE",
1215
1316
  "applicant_id": self.applicant_id,
1216
1317
  "action_sequence": sequence,
1217
1318
  "entities_types": entities_types,
@@ -530,7 +530,7 @@ class CaseIncident:
530
530
  data = self.opencti.process_multiple(result["data"]["caseIncidents"])
531
531
  final_data = final_data + data
532
532
  while result["data"]["caseIncidents"]["pageInfo"]["hasNextPage"]:
533
- after = result["date"]["caseIncidents"]["pageInfo"]["endCursor"]
533
+ after = result["data"]["caseIncidents"]["pageInfo"]["endCursor"]
534
534
  self.opencti.app_logger.info("Listing Case Incidents", {"after": after})
535
535
  result = self.opencti.query(
536
536
  query,
@@ -458,6 +458,10 @@ class Incident:
458
458
  stix_object["x_opencti_granted_refs"] = (
459
459
  self.opencti.get_attribute_in_extension("granted_refs", stix_object)
460
460
  )
461
+ if "x_opencti_workflow_id" not in stix_object:
462
+ stix_object["x_opencti_workflow_id"] = (
463
+ self.opencti.get_attribute_in_extension("workflow_id", stix_object)
464
+ )
461
465
 
462
466
  return self.create(
463
467
  stix_id=stix_object["id"],
@@ -468,6 +468,7 @@ class Indicator:
468
468
  x_opencti_stix_ids = kwargs.get("x_opencti_stix_ids", None)
469
469
  create_observables = kwargs.get("x_opencti_create_observables", False)
470
470
  granted_refs = kwargs.get("objectOrganization", None)
471
+ x_opencti_workflow_id = kwargs.get("x_opencti_workflow_id", None)
471
472
  update = kwargs.get("update", False)
472
473
 
473
474
  if (
@@ -528,6 +529,7 @@ class Indicator:
528
529
  "x_opencti_stix_ids": x_opencti_stix_ids,
529
530
  "killChainPhases": kill_chain_phases,
530
531
  "createObservables": create_observables,
532
+ "x_opencti_workflow_id": x_opencti_workflow_id,
531
533
  "update": update,
532
534
  }
533
535
  },
@@ -642,6 +644,10 @@ class Indicator:
642
644
  stix_object["x_opencti_granted_refs"] = (
643
645
  self.opencti.get_attribute_in_extension("granted_refs", stix_object)
644
646
  )
647
+ if "x_opencti_workflow_id" not in stix_object:
648
+ stix_object["x_opencti_workflow_id"] = (
649
+ self.opencti.get_attribute_in_extension("workflow_id", stix_object)
650
+ )
645
651
 
646
652
  return self.create(
647
653
  stix_id=stix_object["id"],
@@ -740,6 +746,11 @@ class Indicator:
740
746
  if "x_opencti_granted_refs" in stix_object
741
747
  else None
742
748
  ),
749
+ x_opencti_workflow_id=(
750
+ stix_object["x_opencti_workflow_id"]
751
+ if "x_opencti_workflow_id" in stix_object
752
+ else None
753
+ ),
743
754
  update=update,
744
755
  )
745
756
  else: