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

Commit be29884

Browse files
authored
Merge branch 'master' into master
2 parents eedf994 + a1c8d3a commit be29884

File tree

77 files changed

+3726
-322
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

77 files changed

+3726
-322
lines changed

.circleci/config.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ orbs:
55
jobs:
66
ensure_formatting:
77
docker:
8-
- image: cimg/python:3.12
8+
- image: cimg/python:3.13
99
working_directory: ~/repo
1010
steps:
1111
- checkout
@@ -44,7 +44,7 @@ jobs:
4444
build:
4545
working_directory: ~/opencti-client
4646
docker:
47-
- image: cimg/python:3.12
47+
- image: cimg/python:3.13
4848
steps:
4949
- checkout
5050
- run:
@@ -75,7 +75,7 @@ jobs:
7575
deploy:
7676
working_directory: ~/opencti-client
7777
docker:
78-
- image: cimg/python:3.12
78+
- image: cimg/python:3.13
7979
steps:
8080
- checkout
8181
- attach_workspace:

.drone.yml

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,15 @@ kind: pipeline
33
name: client-python-tests
44

55
steps:
6+
- name: Runner information
7+
image: alpine:3.19
8+
commands:
9+
- echo DRONE_STAGE_MACHINE ${DRONE_STAGE_MACHINE}
10+
611
- name: sleep-for-opencti
712
image: python:3.11
813
commands:
914
- sleep 180
10-
- name: client-test-38
11-
image: python:3.8
12-
commands:
13-
- pip3 install -r requirements.txt --user
14-
- pip3 install -r test-requirements.txt --user
15-
- python3 -m pytest --no-header -vv --disable-warnings --cov=pycti --drone
1615
- name: client-test-39
1716
image: python:3.9
1817
commands:
@@ -63,9 +62,9 @@ steps:
6362

6463
services:
6564
- name: redis
66-
image: redis:7.4.2
65+
image: redis:8.2.0
6766
- name: elastic
68-
image: docker.elastic.co/elasticsearch/elasticsearch:8.17.2
67+
image: docker.elastic.co/elasticsearch/elasticsearch:8.18.2
6968
environment:
7069
discovery.type: single-node
7170
xpack.security.enabled: false
@@ -77,12 +76,14 @@ services:
7776
MINIO_ROOT_PASSWORD: ChangeMe
7877
command: [ server, /data ]
7978
- name: rabbitmq
80-
image: rabbitmq:4.0-management
79+
image: rabbitmq:4.1-management
8180
- name: opencti
82-
image: nikolaik/python-nodejs:python3.10-nodejs18-alpine
81+
image: nikolaik/python-nodejs:python3.11-nodejs22-alpine
8382
environment:
8483
APP__ADMIN__PASSWORD: admin
8584
APP__ADMIN__TOKEN: bfa014e0-e02e-4aa6-a42b-603b19dcf159
85+
APP__ENTERPRISE_EDITION_LICENSE:
86+
from_secret: ee_license
8687
REDIS__HOSTNAME: redis
8788
REDIS__NAMESPACE: raw-start
8889
ELASTICSEARCH__URL: http://elastic:9200
@@ -97,11 +98,13 @@ services:
9798
commands:
9899
- apk add build-base git libffi-dev cargo github-cli
99100
- chmod 777 scripts/*
101+
- pip3 install . --upgrade --force
100102
- echo "DRONE_SOURCE_BRANCH:${DRONE_SOURCE_BRANCH}, DRONE_PULL_REQUEST:${DRONE_PULL_REQUEST}"
101103
- ./scripts/clone-opencti.sh "${DRONE_SOURCE_BRANCH}" "${DRONE_TARGET_BRANCH}" "$(pwd)" "${DRONE_PULL_REQUEST}"
102104
- ls -lart
103105
- cd opencti/opencti-platform/opencti-graphql
104106
- yarn install
107+
- sed -i '/^pycti==/d' src/python/requirements.txt
105108
- yarn install:python
106109
- NODE_OPTIONS=--max_old_space_size=8192 yarn start
107110

.github/workflows/auto-set-labels.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@ on: pull_request
33
jobs:
44
build:
55
runs-on: ubuntu-latest
6+
permissions:
7+
contents: read
8+
pull-requests: write
69
steps:
710
- uses: actions/checkout@v4
811
- name: Setting labels
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
name: Check signed commits in PR
2+
on: [pull_request,pull_request_target]
3+
jobs:
4+
check-signed-commits:
5+
name: Check signed commits in PR
6+
runs-on: ubuntu-latest
7+
permissions:
8+
contents: read
9+
pull-requests: write
10+
steps:
11+
- name: Information about how to sign commits see https://docs.github.com/en/authentication/managing-commit-signature-verification/signing-commits
12+
# "with comment" below does not work for forks.
13+
run: |
14+
echo "If you need to sign commits, Please see https://docs.github.com/en/authentication/managing-commit-signature-verification/signing-commits"
15+
- name: Check signed commits in PR on fail see above information.
16+
uses: 1Password/check-signed-commits-action@v1
17+
with:
18+
comment: |
19+
Thank you for your contribution, but we need you to sign your commits. Please see https://docs.github.com/en/authentication/managing-commit-signature-verification/signing-commits

.github/workflows/codeql-analysis.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,11 @@ on:
2424
schedule:
2525
- cron: "21 11 * * 0"
2626

27+
permissions:
28+
actions: read
29+
contents: read
30+
security-events: write
31+
2732
jobs:
2833
analyze:
2934
name: Analyze

docs/requirements.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
autoapi==2.0.1
22
sphinx==8.2.3
3-
sphinx-autodoc-typehints==2.5.0
3+
sphinx-autodoc-typehints==3.2.0
44
sphinx_rtd_theme==3.0.2

pycti/__init__.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# -*- coding: utf-8 -*-
2-
__version__ = "6.6.3"
2+
__version__ = "6.8.0"
33

44
from .api.opencti_api_client import OpenCTIApiClient
55
from .api.opencti_api_connector import OpenCTIApiConnector
@@ -65,9 +65,11 @@
6565
CustomObservableBankAccount,
6666
CustomObservableCredential,
6767
CustomObservableCryptocurrencyWallet,
68+
CustomObservableCryptographicKey,
6869
CustomObservableHostname,
6970
CustomObservableMediaContent,
7071
CustomObservablePaymentCard,
72+
CustomObservablePersona,
7173
CustomObservablePhoneNumber,
7274
CustomObservableText,
7375
CustomObservableTrackingNumber,
@@ -149,8 +151,10 @@
149151
"CustomObservableHostname",
150152
"CustomObservableUserAgent",
151153
"CustomObservableBankAccount",
154+
"CustomObservableCryptographicKey",
152155
"CustomObservableCryptocurrencyWallet",
153156
"CustomObservablePaymentCard",
157+
"CustomObservablePersona",
154158
"CustomObservablePhoneNumber",
155159
"CustomObservableTrackingNumber",
156160
"CustomObservableText",

pycti/api/opencti_api_client.py

Lines changed: 56 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,15 @@
1010

1111
from pycti import __version__
1212
from pycti.api.opencti_api_connector import OpenCTIApiConnector
13+
from pycti.api.opencti_api_draft import OpenCTIApiDraft
14+
from pycti.api.opencti_api_internal_file import OpenCTIApiInternalFile
15+
from pycti.api.opencti_api_notification import OpenCTIApiNotification
16+
from pycti.api.opencti_api_pir import OpenCTIApiPir
1317
from pycti.api.opencti_api_playbook import OpenCTIApiPlaybook
18+
from pycti.api.opencti_api_public_dashboard import OpenCTIApiPublicDashboard
19+
from pycti.api.opencti_api_trash import OpenCTIApiTrash
1420
from pycti.api.opencti_api_work import OpenCTIApiWork
21+
from pycti.api.opencti_api_workspace import OpenCTIApiWorkspace
1522
from pycti.entities.opencti_attack_pattern import AttackPattern
1623
from pycti.entities.opencti_campaign import Campaign
1724
from pycti.entities.opencti_capability import Capability
@@ -71,6 +78,25 @@
7178
from pycti.utils.opencti_stix2_utils import OpenCTIStix2Utils
7279

7380

81+
def build_request_headers(token: str, custom_headers: str, app_logger):
82+
headers_dict = {
83+
"User-Agent": "pycti/" + __version__,
84+
"Authorization": "Bearer " + token,
85+
}
86+
# Build and add custom headers
87+
if custom_headers is not None:
88+
for header_pair in custom_headers.strip().split(";"):
89+
if header_pair: # Skip empty header pairs
90+
try:
91+
key, value = header_pair.split(":", 1)
92+
headers_dict[key.strip()] = value.strip()
93+
except ValueError:
94+
app_logger.warning(
95+
"Ignored invalid header pair", {"header_pair": header_pair}
96+
)
97+
return headers_dict
98+
99+
74100
class File:
75101
def __init__(self, name, data, mime="text/plain"):
76102
self.name = name
@@ -99,24 +125,28 @@ class OpenCTIApiClient:
99125
```
100126
:param json_logging: format the logs as json if set to True
101127
:type json_logging: bool, optional
128+
:param bundle_send_to_queue: if bundle will be sent to queue
129+
:type bundle_send_to_queue: bool, optional
102130
:param cert: If String, file path to pem file. If Tuple, a ('path_to_cert.crt', 'path_to_key.key') pair representing the certificate and the key.
103131
:type cert: str, tuple, optional
104-
:param auth: Add a AuthBase class with custom authentication for you OpenCTI infrastructure.
105-
:type auth: requests.auth.AuthBase, optional
132+
:param custom_headers: Add custom headers to use with the graphql queries
133+
:type custom_headers: str, optional must in the format header01:value;header02:value
134+
:param perform_health_check: if client init must check the api access
135+
:type perform_health_check: bool, optional
106136
"""
107137

108138
def __init__(
109139
self,
110140
url: str,
111141
token: str,
112-
log_level="info",
142+
log_level: str = "info",
113143
ssl_verify: Union[bool, str] = False,
114144
proxies: Union[Dict[str, str], None] = None,
115-
json_logging=False,
116-
bundle_send_to_queue=True,
145+
json_logging: bool = False,
146+
bundle_send_to_queue: bool = True,
117147
cert: Union[str, Tuple[str, str], None] = None,
118-
auth=None,
119-
perform_health_check=True,
148+
custom_headers: str = None,
149+
perform_health_check: bool = True,
120150
):
121151
"""Constructor method"""
122152

@@ -138,22 +168,22 @@ def __init__(
138168
# Define API
139169
self.api_token = token
140170
self.api_url = url + "/graphql"
141-
self.request_headers = {
142-
"User-Agent": "pycti/" + __version__,
143-
"Authorization": "Bearer " + token,
144-
}
145-
146-
if auth is not None:
147-
self.session = requests.session()
148-
self.session.auth = auth
149-
else:
150-
self.session = requests.session()
151-
171+
self.request_headers = build_request_headers(
172+
token, custom_headers, self.app_logger
173+
)
174+
self.session = requests.session()
152175
# Define the dependencies
153176
self.work = OpenCTIApiWork(self)
177+
self.notification = OpenCTIApiNotification(self)
178+
self.trash = OpenCTIApiTrash(self)
179+
self.draft = OpenCTIApiDraft(self)
180+
self.workspace = OpenCTIApiWorkspace(self)
181+
self.public_dashboard = OpenCTIApiPublicDashboard(self)
154182
self.playbook = OpenCTIApiPlaybook(self)
155183
self.connector = OpenCTIApiConnector(self)
156184
self.stix2 = OpenCTIStix2(self)
185+
self.pir = OpenCTIApiPir(self)
186+
self.internal_file = OpenCTIApiInternalFile(self)
157187

158188
# Define the entities
159189
self.vocabulary = Vocabulary(self)
@@ -248,13 +278,15 @@ def set_retry_number(self, retry_number):
248278
"" if retry_number is None else str(retry_number)
249279
)
250280

251-
def query(self, query, variables=None):
281+
def query(self, query, variables=None, disable_impersonate=False):
252282
"""submit a query to the OpenCTI GraphQL API
253283
254284
:param query: GraphQL query string
255285
:type query: str
256286
:param variables: GraphQL query variables, defaults to {}
257287
:type variables: dict, optional
288+
:param disable_impersonate: removes impersonate header if set to True, defaults to False
289+
:type disable_impersonate: bool, optional
258290
:return: returns the response json content
259291
:rtype: Any
260292
"""
@@ -279,6 +311,9 @@ def query(self, query, variables=None):
279311
else:
280312
query_var[key] = val
281313

314+
query_headers = self.request_headers.copy()
315+
if disable_impersonate and "opencti-applicant-id" in query_headers:
316+
del query_headers["opencti-applicant-id"]
282317
# If yes, transform variable (file to null) and create multipart query
283318
if len(files_vars) > 0:
284319
multipart_data = {
@@ -345,7 +380,7 @@ def query(self, query, variables=None):
345380
self.api_url,
346381
data=multipart_data,
347382
files=multipart_files,
348-
headers=self.request_headers,
383+
headers=query_headers,
349384
verify=self.ssl_verify,
350385
cert=self.cert,
351386
proxies=self.proxies,
@@ -356,7 +391,7 @@ def query(self, query, variables=None):
356391
r = self.session.post(
357392
self.api_url,
358393
json={"query": query, "variables": variables},
359-
headers=self.request_headers,
394+
headers=query_headers,
360395
verify=self.ssl_verify,
361396
cert=self.cert,
362397
proxies=self.proxies,

pycti/api/opencti_api_draft.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
class OpenCTIApiDraft:
2+
"""OpenCTIApiDraft"""
3+
4+
def __init__(self, api):
5+
self.api = api
6+
7+
def delete(self, **kwargs):
8+
id = kwargs.get("id", None)
9+
query = """
10+
mutation DraftWorkspaceDelete($id: ID!) {
11+
draftWorkspaceDelete(id: $id)
12+
}
13+
"""
14+
self.api.query(
15+
query,
16+
{
17+
"id": id,
18+
},
19+
)
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
class OpenCTIApiInternalFile:
2+
"""OpenCTIApiInternalFile"""
3+
4+
def __init__(self, api):
5+
self.api = api
6+
7+
def delete(self, **kwargs):
8+
item = kwargs.get("item", None)
9+
file_name = self.api.get_attribute_in_extension("id", item)
10+
if file_name is not None:
11+
query = """
12+
mutation InternalFileDelete($fileName: String) {
13+
deleteImport(fileName: $fileName)
14+
}
15+
"""
16+
self.api.query(
17+
query,
18+
{
19+
"fileName": file_name,
20+
},
21+
)
22+
else:
23+
self.api.app_logger.error(
24+
"[stix_internal_file] Cant delete internal file, missing parameters: fileName"
25+
)
26+
return None

0 commit comments

Comments
 (0)