Skip to content
This repository was archived by the owner on Dec 5, 2025. It is now read-only.

Commit 4506e54

Browse files
2 parents bc3824d + ed5d211 commit 4506e54

21 files changed

+1885
-958
lines changed

pycti/api/opencti_api_connector.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,3 +100,18 @@ def register(self, connector: OpenCTIConnector) -> Dict:
100100
"""
101101
result = self.api.query(query, connector.to_input())
102102
return result["data"]["registerConnector"]
103+
104+
def unregister(self, _id: str) -> Dict:
105+
"""unregister a connector with OpenCTI
106+
107+
:param _id: `OpenCTIConnector` connector id
108+
:type _id: string
109+
:return: the response registerConnector data dict
110+
:rtype: dict
111+
"""
112+
query = """
113+
mutation ConnectorDeletionMutation($id: ID!) {
114+
deleteConnector(id: $id)
115+
}
116+
"""
117+
return self.api.query(query, {"id": _id})

pycti/connector/opencti_connector_helper.py

Lines changed: 34 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,8 @@ def __init__(self, helper, config: Dict, callback) -> None:
106106
self.user = config["connection"]["user"]
107107
self.password = config["connection"]["pass"]
108108
self.queue_name = config["listen"]
109+
self.exit_event = threading.Event()
110+
self.thread = None
109111

110112
# noinspection PyUnusedLocal
111113
def _process_message(self, channel, method, properties, body) -> None:
@@ -122,9 +124,9 @@ def _process_message(self, channel, method, properties, body) -> None:
122124
"""
123125

124126
json_data = json.loads(body)
125-
thread = threading.Thread(target=self._data_handler, args=[json_data])
126-
thread.start()
127-
while thread.is_alive(): # Loop while the thread is processing
127+
self.thread = threading.Thread(target=self._data_handler, args=[json_data])
128+
self.thread.start()
129+
while self.thread.is_alive(): # Loop while the thread is processing
128130
assert self.pika_connection is not None
129131
self.pika_connection.sleep(1.0)
130132
logging.info(
@@ -159,7 +161,7 @@ def _data_handler(self, json_data) -> None:
159161
logging.error("Failing reporting the processing")
160162

161163
def run(self) -> None:
162-
while True:
164+
while not self.exit_event.is_set():
163165
try:
164166
# Connect the broker
165167
self.pika_credentials = pika.PlainCredentials(self.user, self.password)
@@ -186,6 +188,11 @@ def run(self) -> None:
186188
self.helper.log_error(str(e))
187189
time.sleep(10)
188190

191+
def stop(self):
192+
self.exit_event.set()
193+
if self.thread:
194+
self.thread.join()
195+
189196

190197
class PingAlive(threading.Thread):
191198
def __init__(self, connector_id, api, get_state, set_state) -> None:
@@ -195,9 +202,10 @@ def __init__(self, connector_id, api, get_state, set_state) -> None:
195202
self.api = api
196203
self.get_state = get_state
197204
self.set_state = set_state
205+
self.exit_event = threading.Event()
198206

199207
def ping(self) -> None:
200-
while True:
208+
while not self.exit_event.is_set():
201209
try:
202210
initial_state = self.get_state()
203211
result = self.api.connector.ping(self.connector_id, initial_state)
@@ -221,12 +229,16 @@ def ping(self) -> None:
221229
except Exception: # pylint: disable=broad-except
222230
self.in_error = True
223231
logging.error("Error pinging the API")
224-
time.sleep(40)
232+
self.exit_event.wait(40)
225233

226234
def run(self) -> None:
227235
logging.info("Starting ping alive thread")
228236
self.ping()
229237

238+
def stop(self) -> None:
239+
logging.info("Preparing for clean shutdown")
240+
self.exit_event.set()
241+
230242

231243
class ListenStream(threading.Thread):
232244
def __init__(
@@ -239,6 +251,7 @@ def __init__(
239251
self.token = token
240252
self.verify_ssl = verify_ssl
241253
self.start_timestamp = start_timestamp
254+
self.exit_event = threading.Event()
242255

243256
def run(self) -> None: # pylint: disable=too-many-branches
244257
current_state = self.helper.get_state()
@@ -432,6 +445,17 @@ def __init__(self, config: Dict) -> None:
432445
)
433446
self.ping.start()
434447

448+
# self.listen_stream = None
449+
self.listen_queue = None
450+
451+
def stop(self) -> None:
452+
if self.listen_queue:
453+
self.listen_queue.stop()
454+
# if self.listen_stream:
455+
# self.listen_stream.stop()
456+
self.ping.stop()
457+
self.api.connector.unregister(self.connector_id)
458+
435459
def get_name(self) -> Optional[Union[bool, int, str]]:
436460
return self.connect_name
437461

@@ -470,8 +494,8 @@ def listen(self, message_callback: Callable[[Dict], str]) -> None:
470494
:type message_callback: Callable[[Dict], str]
471495
"""
472496

473-
listen_queue = ListenQueue(self, self.config, message_callback)
474-
listen_queue.start()
497+
self.listen_queue = ListenQueue(self, self.config, message_callback)
498+
self.listen_queue.start()
475499

476500
def listen_stream(
477501
self,
@@ -486,10 +510,10 @@ def listen_stream(
486510
:param message_callback: callback function to process messages
487511
"""
488512

489-
listen_stream = ListenStream(
513+
self.listen_stream = ListenStream(
490514
self, message_callback, url, token, verify_ssl, start_timestamp
491515
)
492-
listen_stream.start()
516+
self.listen_stream.start()
493517

494518
def get_opencti_url(self) -> Optional[Union[bool, int, str]]:
495519
return self.opencti_url

pycti/entities/opencti_attack_pattern.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -406,3 +406,19 @@ def import_from_stix2(self, **kwargs):
406406
self.opencti.log(
407407
"error", "[opencti_attack_pattern] Missing parameters: stixObject"
408408
)
409+
410+
def delete(self, **kwargs):
411+
id = kwargs.get("id", None)
412+
if id is not None:
413+
self.opencti.log("info", "Deleting Attack Pattern {" + id + "}.")
414+
query = """
415+
mutation AttackPatternEdit($id: ID!) {
416+
attackPatternEdit(id: $id) {
417+
delete
418+
}
419+
}
420+
"""
421+
self.opencti.query(query, {"id": id})
422+
else:
423+
self.opencti.log("error", "[attack_pattern] Missing parameters: id")
424+
return None

pycti/entities/opencti_identity.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -248,7 +248,7 @@ def create(self, **kwargs):
248248
x_opencti_lastname = kwargs.get("x_opencti_lastname", None)
249249
update = kwargs.get("update", False)
250250

251-
if name is not None and description is not None:
251+
if type is not None and name is not None and description is not None:
252252
self.opencti.log("info", "Creating Identity {" + name + "}.")
253253
input_variables = {
254254
"stix_id": stix_id,
@@ -321,7 +321,7 @@ def create(self, **kwargs):
321321
result["data"][result_data_field]
322322
)
323323
else:
324-
self.opencti.log("error", "Missing parameters: name and description")
324+
self.opencti.log("error", "Missing parameters: type, name and description")
325325

326326
"""
327327
Import an Identity object from a STIX2 object

pycti/entities/opencti_kill_chain_phase.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -219,3 +219,21 @@ def update_field(self, **kwargs):
219219
"[opencti_kill_chain] Missing parameters: id and key and value",
220220
)
221221
return None
222+
223+
def delete(self, **kwargs):
224+
id = kwargs.get("id", None)
225+
if id is not None:
226+
self.opencti.log("info", "Deleting Kill-Chain-Phase {" + id + "}.")
227+
query = """
228+
mutation KillChainPhaseEdit($id: ID!) {
229+
killChainPhaseEdit(id: $id) {
230+
delete
231+
}
232+
}
233+
"""
234+
self.opencti.query(query, {"id": id})
235+
else:
236+
self.opencti.log(
237+
"error", "[opencti_kill_chain_phase] Missing parameters: id"
238+
)
239+
return None

pycti/entities/opencti_label.py

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -200,4 +200,20 @@ def update_field(self, **kwargs):
200200
"error",
201201
"[opencti_label] Missing parameters: id and key and value",
202202
)
203-
return None
203+
return None
204+
205+
def delete(self, **kwargs):
206+
id = kwargs.get("id", None)
207+
if id is not None:
208+
self.opencti.log("info", "Deleting Label {" + id + "}.")
209+
query = """
210+
mutation LabelEdit($id: ID!) {
211+
labelEdit(id: $id) {
212+
delete
213+
}
214+
}
215+
"""
216+
self.opencti.query(query, {"id": id})
217+
else:
218+
self.opencti.log("error", "[opencti_label] Missing parameters: id")
219+
return None

pycti/entities/opencti_marking_definition.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -270,3 +270,21 @@ def import_from_stix2(self, **kwargs):
270270
self.opencti.log(
271271
"error", "[opencti_marking_definition] Missing parameters: stixObject"
272272
)
273+
274+
def delete(self, **kwargs):
275+
id = kwargs.get("id", None)
276+
if id is not None:
277+
self.opencti.log("info", "Deleting Marking-Definition {" + id + "}.")
278+
query = """
279+
mutation MarkingDefinitionEdit($id: ID!) {
280+
markingDefinitionEdit(id: $id) {
281+
delete
282+
}
283+
}
284+
"""
285+
self.opencti.query(query, {"id": id})
286+
else:
287+
self.opencti.log(
288+
"error", "[opencti_marking_definition] Missing parameters: id"
289+
)
290+
return None

pycti/entities/opencti_observed_data.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -387,7 +387,7 @@ def create(self, **kwargs):
387387
else:
388388
self.opencti.log(
389389
"error",
390-
"[opencti_observedData] Missing parameters: first_observed or last_observed",
390+
"[opencti_observedData] Missing parameters: first_observed, last_observed or objects",
391391
)
392392

393393
"""

pycti/entities/opencti_stix_cyber_observable.py

Lines changed: 36 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import json
44
import os
5+
56
import magic
67

78

@@ -320,11 +321,11 @@ def list(self, **kwargs):
320321
)
321322
query = (
322323
"""
323-
query StixCyberObservables($types: [String], $filters: [StixCyberObservablesFiltering], $search: String, $first: Int, $after: ID, $orderBy: StixCyberObservablesOrdering, $orderMode: OrderingMode) {
324-
stixCyberObservables(types: $types, filters: $filters, search: $search, first: $first, after: $after, orderBy: $orderBy, orderMode: $orderMode) {
325-
edges {
326-
node {
327-
"""
324+
query StixCyberObservables($types: [String], $filters: [StixCyberObservablesFiltering], $search: String, $first: Int, $after: ID, $orderBy: StixCyberObservablesOrdering, $orderMode: OrderingMode) {
325+
stixCyberObservables(types: $types, filters: $filters, search: $search, first: $first, after: $after, orderBy: $orderBy, orderMode: $orderMode) {
326+
edges {
327+
node {
328+
"""
328329
+ (custom_attributes if custom_attributes is not None else self.properties)
329330
+ """
330331
}
@@ -398,9 +399,9 @@ def read(self, **kwargs):
398399
self.opencti.log("info", "Reading StixCyberObservable {" + id + "}.")
399400
query = (
400401
"""
401-
query StixCyberObservable($id: String!) {
402-
stixCyberObservable(id: $id) {
403-
"""
402+
query StixCyberObservable($id: String!) {
403+
stixCyberObservable(id: $id) {
404+
"""
404405
+ (
405406
custom_attributes
406407
if custom_attributes is not None
@@ -1736,3 +1737,30 @@ def push_list_export(self, file_name, data, list_filters=""):
17361737
"listFilters": list_filters,
17371738
},
17381739
)
1740+
1741+
def ask_for_enrichment(self, **kwargs):
1742+
id = kwargs.get("id", None)
1743+
connector_id = kwargs.get("connector_id", None)
1744+
1745+
if id is None or connector_id is None:
1746+
self.opencti.log("error", "Missing parameters: id and connector_id")
1747+
return False
1748+
1749+
query = """
1750+
mutation StixCoreObjectEnrichmentLinesMutation($id: ID!, $connectorId: ID!) {
1751+
stixCoreObjectEdit(id: $id) {
1752+
askEnrichment(connectorId: $connectorId) {
1753+
id
1754+
}
1755+
}
1756+
}
1757+
"""
1758+
1759+
self.opencti.query(
1760+
query,
1761+
{
1762+
"id": id,
1763+
"connectorId": connector_id,
1764+
},
1765+
)
1766+
return True

requirements.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,4 @@ black==20.8b1
1111
python-magic==0.4.22; sys_platform == 'linux' or sys_platform == 'darwin'
1212
python-magic-bin==0.4.14; sys_platform == 'win32'
1313
pytest_randomly==3.8.0
14+
pytest-cases==3.6.3

0 commit comments

Comments
 (0)