pycti 6.0.8__tar.gz → 6.0.10__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 (73) hide show
  1. {pycti-6.0.8 → pycti-6.0.10}/PKG-INFO +7 -7
  2. {pycti-6.0.8 → pycti-6.0.10}/pycti/__init__.py +11 -1
  3. {pycti-6.0.8 → pycti-6.0.10}/pycti/api/opencti_api_client.py +2 -0
  4. {pycti-6.0.8 → pycti-6.0.10}/pycti/api/opencti_api_work.py +63 -54
  5. {pycti-6.0.8 → pycti-6.0.10}/pycti/connector/opencti_connector_helper.py +139 -40
  6. {pycti-6.0.8 → pycti-6.0.10}/pycti/entities/opencti_incident.py +4 -0
  7. {pycti-6.0.8 → pycti-6.0.10}/pycti/entities/opencti_indicator.py +11 -0
  8. {pycti-6.0.8 → pycti-6.0.10}/pycti/entities/opencti_intrusion_set.py +12 -1
  9. {pycti-6.0.8 → pycti-6.0.10}/pycti/entities/opencti_malware_analysis.py +11 -0
  10. {pycti-6.0.8 → pycti-6.0.10}/pycti/entities/opencti_stix_core_object.py +6 -0
  11. {pycti-6.0.8 → pycti-6.0.10}/pycti/entities/opencti_stix_cyber_observable.py +68 -10
  12. {pycti-6.0.8 → pycti-6.0.10}/pycti/utils/constants.py +110 -0
  13. {pycti-6.0.8 → pycti-6.0.10}/pycti/utils/opencti_stix2.py +15 -0
  14. {pycti-6.0.8 → pycti-6.0.10}/pycti/utils/opencti_stix2_utils.py +12 -1
  15. {pycti-6.0.8 → pycti-6.0.10}/pycti.egg-info/PKG-INFO +7 -7
  16. {pycti-6.0.8 → pycti-6.0.10}/pycti.egg-info/requires.txt +6 -6
  17. {pycti-6.0.8 → pycti-6.0.10}/setup.cfg +6 -6
  18. {pycti-6.0.8 → pycti-6.0.10}/LICENSE +0 -0
  19. {pycti-6.0.8 → pycti-6.0.10}/README.md +0 -0
  20. {pycti-6.0.8 → pycti-6.0.10}/pycti/api/__init__.py +0 -0
  21. {pycti-6.0.8 → pycti-6.0.10}/pycti/api/opencti_api_connector.py +0 -0
  22. {pycti-6.0.8 → pycti-6.0.10}/pycti/api/opencti_api_playbook.py +0 -0
  23. {pycti-6.0.8 → pycti-6.0.10}/pycti/connector/__init__.py +0 -0
  24. {pycti-6.0.8 → pycti-6.0.10}/pycti/connector/opencti_connector.py +0 -0
  25. {pycti-6.0.8 → pycti-6.0.10}/pycti/connector/opencti_metric_handler.py +0 -0
  26. {pycti-6.0.8 → pycti-6.0.10}/pycti/entities/__init__.py +0 -0
  27. {pycti-6.0.8 → pycti-6.0.10}/pycti/entities/opencti_attack_pattern.py +0 -0
  28. {pycti-6.0.8 → pycti-6.0.10}/pycti/entities/opencti_campaign.py +0 -0
  29. {pycti-6.0.8 → pycti-6.0.10}/pycti/entities/opencti_case_incident.py +0 -0
  30. {pycti-6.0.8 → pycti-6.0.10}/pycti/entities/opencti_case_rfi.py +0 -0
  31. {pycti-6.0.8 → pycti-6.0.10}/pycti/entities/opencti_case_rft.py +0 -0
  32. {pycti-6.0.8 → pycti-6.0.10}/pycti/entities/opencti_channel.py +0 -0
  33. {pycti-6.0.8 → pycti-6.0.10}/pycti/entities/opencti_course_of_action.py +0 -0
  34. {pycti-6.0.8 → pycti-6.0.10}/pycti/entities/opencti_data_component.py +0 -0
  35. {pycti-6.0.8 → pycti-6.0.10}/pycti/entities/opencti_data_source.py +0 -0
  36. {pycti-6.0.8 → pycti-6.0.10}/pycti/entities/opencti_event.py +0 -0
  37. {pycti-6.0.8 → pycti-6.0.10}/pycti/entities/opencti_external_reference.py +0 -0
  38. {pycti-6.0.8 → pycti-6.0.10}/pycti/entities/opencti_feedback.py +0 -0
  39. {pycti-6.0.8 → pycti-6.0.10}/pycti/entities/opencti_grouping.py +0 -0
  40. {pycti-6.0.8 → pycti-6.0.10}/pycti/entities/opencti_identity.py +0 -0
  41. {pycti-6.0.8 → pycti-6.0.10}/pycti/entities/opencti_infrastructure.py +0 -0
  42. {pycti-6.0.8 → pycti-6.0.10}/pycti/entities/opencti_kill_chain_phase.py +0 -0
  43. {pycti-6.0.8 → pycti-6.0.10}/pycti/entities/opencti_label.py +0 -0
  44. {pycti-6.0.8 → pycti-6.0.10}/pycti/entities/opencti_language.py +0 -0
  45. {pycti-6.0.8 → pycti-6.0.10}/pycti/entities/opencti_location.py +0 -0
  46. {pycti-6.0.8 → pycti-6.0.10}/pycti/entities/opencti_malware.py +0 -0
  47. {pycti-6.0.8 → pycti-6.0.10}/pycti/entities/opencti_marking_definition.py +0 -0
  48. {pycti-6.0.8 → pycti-6.0.10}/pycti/entities/opencti_narrative.py +0 -0
  49. {pycti-6.0.8 → pycti-6.0.10}/pycti/entities/opencti_note.py +0 -0
  50. {pycti-6.0.8 → pycti-6.0.10}/pycti/entities/opencti_observed_data.py +0 -0
  51. {pycti-6.0.8 → pycti-6.0.10}/pycti/entities/opencti_opinion.py +0 -0
  52. {pycti-6.0.8 → pycti-6.0.10}/pycti/entities/opencti_report.py +0 -0
  53. {pycti-6.0.8 → pycti-6.0.10}/pycti/entities/opencti_stix.py +0 -0
  54. {pycti-6.0.8 → pycti-6.0.10}/pycti/entities/opencti_stix_core_relationship.py +0 -0
  55. {pycti-6.0.8 → pycti-6.0.10}/pycti/entities/opencti_stix_domain_object.py +0 -0
  56. {pycti-6.0.8 → pycti-6.0.10}/pycti/entities/opencti_stix_nested_ref_relationship.py +0 -0
  57. {pycti-6.0.8 → pycti-6.0.10}/pycti/entities/opencti_stix_object_or_stix_relationship.py +0 -0
  58. {pycti-6.0.8 → pycti-6.0.10}/pycti/entities/opencti_stix_sighting_relationship.py +0 -0
  59. {pycti-6.0.8 → pycti-6.0.10}/pycti/entities/opencti_task.py +0 -0
  60. {pycti-6.0.8 → pycti-6.0.10}/pycti/entities/opencti_threat_actor.py +0 -0
  61. {pycti-6.0.8 → pycti-6.0.10}/pycti/entities/opencti_threat_actor_group.py +0 -0
  62. {pycti-6.0.8 → pycti-6.0.10}/pycti/entities/opencti_threat_actor_individual.py +0 -0
  63. {pycti-6.0.8 → pycti-6.0.10}/pycti/entities/opencti_tool.py +0 -0
  64. {pycti-6.0.8 → pycti-6.0.10}/pycti/entities/opencti_vocabulary.py +0 -0
  65. {pycti-6.0.8 → pycti-6.0.10}/pycti/entities/opencti_vulnerability.py +0 -0
  66. {pycti-6.0.8 → pycti-6.0.10}/pycti/utils/__init__.py +0 -0
  67. {pycti-6.0.8 → pycti-6.0.10}/pycti/utils/opencti_logger.py +0 -0
  68. {pycti-6.0.8 → pycti-6.0.10}/pycti/utils/opencti_stix2_splitter.py +0 -0
  69. {pycti-6.0.8 → pycti-6.0.10}/pycti/utils/opencti_stix2_update.py +0 -0
  70. {pycti-6.0.8 → pycti-6.0.10}/pycti.egg-info/SOURCES.txt +0 -0
  71. {pycti-6.0.8 → pycti-6.0.10}/pycti.egg-info/dependency_links.txt +0 -0
  72. {pycti-6.0.8 → pycti-6.0.10}/pycti.egg-info/top_level.txt +0 -0
  73. {pycti-6.0.8 → pycti-6.0.10}/pyproject.toml +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: pycti
3
- Version: 6.0.8
3
+ Version: 6.0.10
4
4
  Summary: Python API client for OpenCTI.
5
5
  Home-page: https://github.com/OpenCTI-Platform/client-python
6
6
  Author: Filigran
@@ -29,25 +29,25 @@ Requires-Dist: python-magic-bin~=0.4.14; sys_platform == "win32"
29
29
  Requires-Dist: python_json_logger~=2.0.4
30
30
  Requires-Dist: pyyaml~=6.0
31
31
  Requires-Dist: requests~=2.31.0
32
- Requires-Dist: setuptools~=69.2.0
32
+ Requires-Dist: setuptools~=69.5.1
33
33
  Requires-Dist: filigran-sseclient~=1.0.0
34
34
  Requires-Dist: stix2~=3.0.1
35
35
  Requires-Dist: cachetools~=5.3.0
36
36
  Provides-Extra: dev
37
- Requires-Dist: black~=24.3.0; extra == "dev"
38
- Requires-Dist: build~=1.1.1; extra == "dev"
37
+ Requires-Dist: black~=24.4.0; extra == "dev"
38
+ Requires-Dist: build~=1.2.1; extra == "dev"
39
39
  Requires-Dist: isort~=5.13.0; extra == "dev"
40
40
  Requires-Dist: types-pytz~=2024.1.0.20240203; extra == "dev"
41
- Requires-Dist: pre-commit~=3.6.0; extra == "dev"
41
+ Requires-Dist: pre-commit~=3.7.0; extra == "dev"
42
42
  Requires-Dist: pytest-cases~=3.8.0; extra == "dev"
43
- Requires-Dist: pytest-cov~=4.1.0; extra == "dev"
43
+ Requires-Dist: pytest-cov~=5.0.0; extra == "dev"
44
44
  Requires-Dist: pytest_randomly~=3.15.0; extra == "dev"
45
45
  Requires-Dist: pytest~=8.1.1; extra == "dev"
46
46
  Requires-Dist: types-python-dateutil~=2.9.0; extra == "dev"
47
47
  Requires-Dist: wheel~=0.43.0; extra == "dev"
48
48
  Provides-Extra: doc
49
49
  Requires-Dist: autoapi~=2.0.1; extra == "doc"
50
- Requires-Dist: sphinx-autodoc-typehints~=2.0.0; extra == "doc"
50
+ Requires-Dist: sphinx-autodoc-typehints~=2.1.0; extra == "doc"
51
51
  Requires-Dist: sphinx-rtd-theme~=2.0.0; extra == "doc"
52
52
 
53
53
  # OpenCTI client for Python
@@ -1,5 +1,5 @@
1
1
  # -*- coding: utf-8 -*-
2
- __version__ = "6.0.8"
2
+ __version__ = "6.0.10"
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
@@ -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 = """
@@ -728,6 +728,32 @@ class OpenCTIConnectorHelper: # pylint: disable=too-many-public-methods
728
728
  self.connect_auto = get_config_variable(
729
729
  "CONNECTOR_AUTO", ["connector", "auto"], config, False, False
730
730
  )
731
+ self.bundle_send_to_queue = get_config_variable(
732
+ "CONNECTOR_SEND_TO_QUEUE",
733
+ ["connector", "send_to_queue"],
734
+ config,
735
+ False,
736
+ True,
737
+ )
738
+ self.bundle_send_to_directory = get_config_variable(
739
+ "CONNECTOR_SEND_TO_DIRECTORY",
740
+ ["connector", "send_to_directory"],
741
+ config,
742
+ False,
743
+ False,
744
+ )
745
+ self.bundle_send_to_directory_path = get_config_variable(
746
+ "CONNECTOR_SEND_TO_DIRECTORY_PATH",
747
+ ["connector", "send_to_directory_path"],
748
+ config,
749
+ )
750
+ self.bundle_send_to_directory_retention = get_config_variable(
751
+ "CONNECTOR_SEND_TO_DIRECTORY_RETENTION",
752
+ ["connector", "send_to_directory_retention"],
753
+ config,
754
+ True,
755
+ 7,
756
+ )
731
757
  self.connect_only_contextual = get_config_variable(
732
758
  "CONNECTOR_ONLY_CONTEXTUAL",
733
759
  ["connector", "only_contextual"],
@@ -749,6 +775,8 @@ class OpenCTIConnectorHelper: # pylint: disable=too-many-public-methods
749
775
  "CONNECTOR_VALIDATE_BEFORE_IMPORT",
750
776
  ["connector", "validate_before_import"],
751
777
  config,
778
+ False,
779
+ False,
752
780
  )
753
781
  # Start up the server to expose the metrics.
754
782
  expose_metrics = get_config_variable(
@@ -769,6 +797,7 @@ class OpenCTIConnectorHelper: # pylint: disable=too-many-public-methods
769
797
  self.opencti_token,
770
798
  self.log_level,
771
799
  json_logging=self.opencti_json_logging,
800
+ bundle_send_to_queue=self.bundle_send_to_queue,
772
801
  )
773
802
  # - Impersonate API that will use applicant id
774
803
  # Behave like standard api if applicant not found
@@ -777,6 +806,7 @@ class OpenCTIConnectorHelper: # pylint: disable=too-many-public-methods
777
806
  self.opencti_token,
778
807
  self.log_level,
779
808
  json_logging=self.opencti_json_logging,
809
+ bundle_send_to_queue=self.bundle_send_to_queue,
780
810
  )
781
811
  self.connector_logger = self.api.logger_class(self.connect_name)
782
812
  # For retro compatibility
@@ -1075,6 +1105,16 @@ class OpenCTIConnectorHelper: # pylint: disable=too-many-public-methods
1075
1105
  bypass_validation = kwargs.get("bypass_validation", False)
1076
1106
  entity_id = kwargs.get("entity_id", None)
1077
1107
  file_name = kwargs.get("file_name", None)
1108
+ bundle_send_to_queue = kwargs.get("send_to_queue", self.bundle_send_to_queue)
1109
+ bundle_send_to_directory = kwargs.get(
1110
+ "send_to_directory", self.bundle_send_to_directory
1111
+ )
1112
+ bundle_send_to_directory_path = kwargs.get(
1113
+ "send_to_directory_path", self.bundle_send_to_directory_path
1114
+ )
1115
+ bundle_send_to_directory_retention = kwargs.get(
1116
+ "send_to_directory_retention", self.bundle_send_to_directory_retention
1117
+ )
1078
1118
 
1079
1119
  # In case of enrichment ingestion, ensure the sharing if needed
1080
1120
  if self.enrichment_shared_organizations is not None:
@@ -1114,13 +1154,14 @@ class OpenCTIConnectorHelper: # pylint: disable=too-many-public-methods
1114
1154
  )
1115
1155
  bundle = json.dumps(bundle_data)
1116
1156
 
1157
+ # If execution in playbook, callback the api
1117
1158
  if self.playbook is not None:
1118
1159
  self.api.playbook.playbook_step_execution(self.playbook, bundle)
1119
1160
  return [bundle]
1120
1161
 
1162
+ # Upload workbench in case of pending validation
1121
1163
  if not file_name and work_id:
1122
1164
  file_name = f"{work_id}.json"
1123
-
1124
1165
  if self.connect_validate_before_import and not bypass_validation and file_name:
1125
1166
  self.api.upload_pending_file(
1126
1167
  file_name=file_name,
@@ -1130,8 +1171,61 @@ class OpenCTIConnectorHelper: # pylint: disable=too-many-public-methods
1130
1171
  )
1131
1172
  return []
1132
1173
 
1133
- if entities_types is None:
1134
- entities_types = []
1174
+ # If directory setup, write the bundle to the target directory
1175
+ if bundle_send_to_directory and bundle_send_to_directory_path is not None:
1176
+ self.connector_logger.info(
1177
+ "The connector sending bundle to directory",
1178
+ {
1179
+ "connector": self.connect_name,
1180
+ "directory": bundle_send_to_directory_path,
1181
+ "also_queuing": bundle_send_to_queue,
1182
+ },
1183
+ )
1184
+ bundle_file = (
1185
+ self.connect_name.lower().replace(" ", "_")
1186
+ + "-"
1187
+ + time.strftime("%Y%m%d-%H%M%S-")
1188
+ + str(time.time())
1189
+ + ".json"
1190
+ )
1191
+ write_file = os.path.join(
1192
+ bundle_send_to_directory_path, bundle_file + ".tmp"
1193
+ )
1194
+ message_bundle = {
1195
+ "bundle_type": "DIRECTORY_BUNDLE",
1196
+ "applicant_id": self.applicant_id,
1197
+ "connector": {
1198
+ "id": self.connect_id,
1199
+ "name": self.connect_name,
1200
+ "type": self.connect_type,
1201
+ "scope": self.connect_scope,
1202
+ "auto": self.connect_auto,
1203
+ "validate_before_import": self.connect_validate_before_import,
1204
+ },
1205
+ "entities_types": entities_types,
1206
+ "bundle": json.loads(bundle),
1207
+ "update": update,
1208
+ }
1209
+ # Maintains the list of files under control
1210
+ if bundle_send_to_directory_retention > 0: # If 0, disable the auto remove
1211
+ current_time = time.time()
1212
+ for f in os.listdir(bundle_send_to_directory_path):
1213
+ if f.endswith(".json"):
1214
+ file_location = os.path.join(bundle_send_to_directory_path, f)
1215
+ file_time = os.stat(file_location).st_mtime
1216
+ is_expired_file = (
1217
+ file_time
1218
+ < current_time - 86400 * bundle_send_to_directory_retention
1219
+ ) # 86400 = 1 day
1220
+ if is_expired_file:
1221
+ os.remove(file_location)
1222
+ # Write the bundle to target directory
1223
+ with open(write_file, "w") as f:
1224
+ str_bundle = json.dumps(message_bundle)
1225
+ f.write(str_bundle)
1226
+ # Rename the file after full write
1227
+ final_write_file = os.path.join(bundle_send_to_directory_path, bundle_file)
1228
+ os.rename(write_file, final_write_file)
1135
1229
 
1136
1230
  if bypass_split:
1137
1231
  bundles = [bundle]
@@ -1143,44 +1237,48 @@ class OpenCTIConnectorHelper: # pylint: disable=too-many-public-methods
1143
1237
  self.metric.inc("error_count")
1144
1238
  raise ValueError("Nothing to import")
1145
1239
 
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,
1240
+ if bundle_send_to_queue:
1241
+ if work_id:
1242
+ self.api.work.add_expectations(work_id, len(bundles))
1243
+ if entities_types is None:
1244
+ entities_types = []
1245
+ pika_credentials = pika.PlainCredentials(
1246
+ self.connector_config["connection"]["user"],
1247
+ self.connector_config["connection"]["pass"],
1248
+ )
1249
+ pika_parameters = pika.ConnectionParameters(
1250
+ host=self.connector_config["connection"]["host"],
1251
+ port=self.connector_config["connection"]["port"],
1252
+ virtual_host=self.connector_config["connection"]["vhost"],
1253
+ credentials=pika_credentials,
1254
+ ssl_options=(
1255
+ pika.SSLOptions(
1256
+ create_mq_ssl_context(self.config),
1257
+ self.connector_config["connection"]["host"],
1258
+ )
1259
+ if self.connector_config["connection"]["use_ssl"]
1260
+ else None
1261
+ ),
1181
1262
  )
1182
- channel.close()
1183
- pika_connection.close()
1263
+ pika_connection = pika.BlockingConnection(pika_parameters)
1264
+ channel = pika_connection.channel()
1265
+ try:
1266
+ channel.confirm_delivery()
1267
+ except Exception as err: # pylint: disable=broad-except
1268
+ self.connector_logger.warning(str(err))
1269
+ self.connector_logger.info(self.connect_name + " sending bundle to queue")
1270
+ for sequence, bundle in enumerate(bundles, start=1):
1271
+ self._send_bundle(
1272
+ channel,
1273
+ bundle,
1274
+ work_id=work_id,
1275
+ entities_types=entities_types,
1276
+ sequence=sequence,
1277
+ update=update,
1278
+ )
1279
+ channel.close()
1280
+ pika_connection.close()
1281
+
1184
1282
  return bundles
1185
1283
 
1186
1284
  def _send_bundle(self, channel, bundle, **kwargs) -> None:
@@ -1212,6 +1310,7 @@ class OpenCTIConnectorHelper: # pylint: disable=too-many-public-methods
1212
1310
  # if self.current_work_id is None:
1213
1311
  # raise ValueError('The job id must be specified')
1214
1312
  message = {
1313
+ "bundle_type": "QUEUE_BUNDLE",
1215
1314
  "applicant_id": self.applicant_id,
1216
1315
  "action_sequence": sequence,
1217
1316
  "entities_types": entities_types,
@@ -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:
@@ -363,6 +363,7 @@ class IntrusionSet:
363
363
  secondary_motivations = kwargs.get("secondary_motivations", None)
364
364
  x_opencti_stix_ids = kwargs.get("x_opencti_stix_ids", None)
365
365
  granted_refs = kwargs.get("objectOrganization", None)
366
+ x_opencti_workflow_id = kwargs.get("x_opencti_workflow_id", None)
366
367
  update = kwargs.get("update", False)
367
368
 
368
369
  if name is not None:
@@ -402,6 +403,7 @@ class IntrusionSet:
402
403
  "primary_motivation": primary_motivation,
403
404
  "secondary_motivations": secondary_motivations,
404
405
  "x_opencti_stix_ids": x_opencti_stix_ids,
406
+ "x_opencti_workflow_id": x_opencti_workflow_id,
405
407
  "update": update,
406
408
  }
407
409
  },
@@ -435,7 +437,11 @@ class IntrusionSet:
435
437
  stix_object["x_opencti_granted_refs"] = (
436
438
  self.opencti.get_attribute_in_extension("granted_refs", stix_object)
437
439
  )
438
-
440
+ if "x_opencti_workflow_id" not in stix_object:
441
+ stix_object["x_opencti_workflow_id"] = (
442
+ self.opencti.get_attribute_in_extension("workflow_id", stix_object)
443
+ )
444
+ 7
439
445
  return self.create(
440
446
  stix_id=stix_object["id"],
441
447
  createdBy=(
@@ -500,6 +506,11 @@ class IntrusionSet:
500
506
  if "x_opencti_granted_refs" in stix_object
501
507
  else None
502
508
  ),
509
+ x_opencti_workflow_id=(
510
+ stix_object["x_opencti_workflow_id"]
511
+ if "x_opencti_workflow_id" in stix_object
512
+ else None
513
+ ),
503
514
  update=update,
504
515
  )
505
516
  else:
@@ -401,6 +401,7 @@ class MalwareAnalysis:
401
401
  analysisSco = kwargs.get("analysisSco", None)
402
402
  x_opencti_stix_ids = kwargs.get("x_opencti_stix_ids", None)
403
403
  granted_refs = kwargs.get("objectOrganization", None)
404
+ x_opencti_workflow_id = kwargs.get("x_opencti_workflow_id", None)
404
405
  update = kwargs.get("update", False)
405
406
 
406
407
  if product is not None and result_name is not None:
@@ -449,6 +450,7 @@ class MalwareAnalysis:
449
450
  "analysisSample": sample,
450
451
  "analysisSco": analysisSco,
451
452
  "x_opencti_stix_ids": x_opencti_stix_ids,
453
+ "x_opencti_workflow_id": x_opencti_workflow_id,
452
454
  "update": update,
453
455
  }
454
456
  },
@@ -482,6 +484,10 @@ class MalwareAnalysis:
482
484
  stix_object["x_opencti_granted_refs"] = (
483
485
  self.opencti.get_attribute_in_extension("granted_refs", stix_object)
484
486
  )
487
+ if "x_opencti_workflow_id" not in stix_object:
488
+ stix_object["x_opencti_workflow_id"] = (
489
+ self.opencti.get_attribute_in_extension("workflow_id", stix_object)
490
+ )
485
491
 
486
492
  return self.create(
487
493
  stix_id=stix_object["id"],
@@ -574,6 +580,11 @@ class MalwareAnalysis:
574
580
  if "x_opencti_granted_refs" in stix_object
575
581
  else None
576
582
  ),
583
+ x_opencti_workflow_id=(
584
+ stix_object["x_opencti_workflow_id"]
585
+ if "x_opencti_workflow_id" in stix_object
586
+ else None
587
+ ),
577
588
  update=update,
578
589
  )
579
590
  else:
@@ -615,6 +615,12 @@ class StixCoreObject:
615
615
  ... on PhoneNumber {
616
616
  value
617
617
  }
618
+ ... on TrackingNumber {
619
+ value
620
+ }
621
+ ... on Credential {
622
+ value
623
+ }
618
624
  ... on PaymentCard {
619
625
  card_number
620
626
  expiration_date