From a837f71b49ae5205a80f53b3ed27737a5e74a156 Mon Sep 17 00:00:00 2001 From: Landry Trebon <33682259+lndrtrbn@users.noreply.github.com> Date: Tue, 23 Dec 2025 14:34:05 +0100 Subject: [PATCH 001/126] [backend] conf load development file correctly (#13821) --- .../opencti-dev/1 - Backend start.run.xml | 3 +-- ...Backend start cluster (second platform).run.xml | 3 +-- opencti-platform/opencti-graphql/.gitignore | 14 ++++---------- opencti-platform/opencti-graphql/package.json | 6 +++--- .../opencti-graphql/src/config/conf.js | 10 ++++++++-- 5 files changed, 17 insertions(+), 19 deletions(-) diff --git a/opencti-platform/opencti-dev/1 - Backend start.run.xml b/opencti-platform/opencti-dev/1 - Backend start.run.xml index c2626fbb7fc7..a0fd0c825b3e 100644 --- a/opencti-platform/opencti-dev/1 - Backend start.run.xml +++ b/opencti-platform/opencti-dev/1 - Backend start.run.xml @@ -9,8 +9,7 @@ - - + diff --git a/opencti-platform/opencti-dev/2 - Backend start cluster (second platform).run.xml b/opencti-platform/opencti-dev/2 - Backend start cluster (second platform).run.xml index d68b5be3a76e..d56b5fc78f0a 100644 --- a/opencti-platform/opencti-dev/2 - Backend start cluster (second platform).run.xml +++ b/opencti-platform/opencti-dev/2 - Backend start cluster (second platform).run.xml @@ -9,8 +9,7 @@ - - + diff --git a/opencti-platform/opencti-graphql/.gitignore b/opencti-platform/opencti-graphql/.gitignore index 4c1235a957e2..569b3e2f0ada 100644 --- a/opencti-platform/opencti-graphql/.gitignore +++ b/opencti-platform/opencti-graphql/.gitignore @@ -5,16 +5,10 @@ build dist public config/ssl -config/demo.json -config/development.json -config/development.json.bak -config/production.json -config/reference.json -config/tanium.json -config/test.json -config/local.json -config/sync.json -config/prod-*.json +config/*.json +config/*.json.bak +!config/default.json +!config/test.json coverage test-results logs diff --git a/opencti-platform/opencti-graphql/package.json b/opencti-platform/opencti-graphql/package.json index 9553d9aa2ebd..6af662087c73 100644 --- a/opencti-platform/opencti-graphql/package.json +++ b/opencti-platform/opencti-graphql/package.json @@ -16,9 +16,9 @@ "frontend:stack:analysis": "node builder/dev/stack.js && node --env-file=.env build/script-stack-analysis front", "lint": "DEBUG=eslint:cli-engine TIMING=1 eslint --quiet --cache --config eslint.config.mjs src", "build": "yarn install:python && yarn build:prod", - "start": "NODE_ENV=dev yarn build:dev && node --enable-source-maps build/back.js", - "start:e2e": "NODE_ENV=dev yarn build:dev && APP__ENABLED_DEV_FEATURES='[\"*\"]' EXPIRATION_SCHEDULER__ENABLED=false REDIS__NAMESPACE=e2e-start RABBITMQ__QUEUE_PREFIX=e2e MINIO__BUCKET_NAME=e2e-start-bucket ELASTICSEARCH__INDEX_PREFIX=e2e-start node build/back.js", - "start:light": "NODE_ENV=dev node build/back.js", + "start": "NODE_ENV=development yarn build:dev && node --enable-source-maps build/back.js", + "start:e2e": "NODE_ENV=development yarn build:dev && APP__ENABLED_DEV_FEATURES='[\"*\"]' EXPIRATION_SCHEDULER__ENABLED=false REDIS__NAMESPACE=e2e-start RABBITMQ__QUEUE_PREFIX=e2e MINIO__BUCKET_NAME=e2e-start-bucket ELASTICSEARCH__INDEX_PREFIX=e2e-start node build/back.js", + "start:light": "NODE_ENV=development node build/back.js", "start:cluster": "NODE_ENV=cluster node build/back.js", "serv": "node build/back.js", "insert:dev": "node build/script-insert-dataset.js --datasets=DATA-TEST-STIX2_v2,poisonivy", diff --git a/opencti-platform/opencti-graphql/src/config/conf.js b/opencti-platform/opencti-graphql/src/config/conf.js index b8838fc24b73..ef86cc22a3d8 100644 --- a/opencti-platform/opencti-graphql/src/config/conf.js +++ b/opencti-platform/opencti-graphql/src/config/conf.js @@ -1,4 +1,4 @@ -import { lstatSync, readFileSync } from 'node:fs'; +import { lstatSync, readFileSync, existsSync } from 'node:fs'; import path from 'node:path'; import nconf from 'nconf'; import * as R from 'ramda'; @@ -86,7 +86,13 @@ const { timestamp } = format; const currentPath = process.env.INIT_CWD || process.cwd(); const resolvePath = (relativePath) => path.join(currentPath, relativePath); export const environment = nconf.get('env') || nconf.get('node_env') || process.env.NODE_ENV || DEFAULT_ENV; -const resolveEnvFile = (env) => path.join(resolvePath('config'), `${env.toLowerCase()}.json`); +const resolveEnvFile = (env) => { + const filePath = path.join(resolvePath('config'), `${env.toLowerCase()}.json`); + if (env.toLowerCase() === 'dev' && !existsSync(filePath)) { + return path.join(resolvePath('config'), 'development.json'); + } + return filePath; +}; export const DEV_MODE = environment !== 'production'; const externalConfigurationFile = nconf.get('conf'); export const NODE_INSTANCE_ID = nconf.get('app:node_identifier') || uuid(); From de47c03d749f013b6cb345618f5e00632e17e6e7 Mon Sep 17 00:00:00 2001 From: Archidoit <75783086+Archidoit@users.noreply.github.com> Date: Tue, 23 Dec 2025 16:46:04 +0100 Subject: [PATCH 002/126] [backend] Fix ejs escape option for undefined values (#13785) --- .../opencti-graphql/src/utils/safeEjs.worker.ts | 15 +++++++++++---- .../01-unit/utils/safeEjs-test.template-5.json | 17 +++++++++++++++++ .../tests/01-unit/utils/safeEjs-test.ts | 7 +++++-- 3 files changed, 33 insertions(+), 6 deletions(-) create mode 100644 opencti-platform/opencti-graphql/tests/01-unit/utils/safeEjs-test.template-5.json diff --git a/opencti-platform/opencti-graphql/src/utils/safeEjs.worker.ts b/opencti-platform/opencti-graphql/src/utils/safeEjs.worker.ts index df898305fda3..40e4f1ec5714 100644 --- a/opencti-platform/opencti-graphql/src/utils/safeEjs.worker.ts +++ b/opencti-platform/opencti-graphql/src/utils/safeEjs.worker.ts @@ -16,6 +16,16 @@ export interface WorkerReply { error?: string; } +export const customEscapeFunction = (value: any): string => { + if (Array.isArray(value) && value.length === 0) { + return ''; + } + const result = JSON.stringify(value); + return result && result.startsWith('"') && result.endsWith('"') + ? result.slice(1, -1) + : result; +}; + // Main worker execution const executeWorker = async () => { const { template, data, options, useJsonEscape } = workerData as WorkerRequest; @@ -24,10 +34,7 @@ const executeWorker = async () => { const safeEjsOptions = { ...options }; if (useJsonEscape) { // Recreate the escape function for JSON stringification - safeEjsOptions.escape = (value: any) => { - const result = JSON.stringify(value); - return result.startsWith('"') && result.endsWith('"') ? result.slice(1, -1) : result; - }; + safeEjsOptions.escape = customEscapeFunction; } // Use the core logic from safeEjs (await in case it returns a Promise) diff --git a/opencti-platform/opencti-graphql/tests/01-unit/utils/safeEjs-test.template-5.json b/opencti-platform/opencti-graphql/tests/01-unit/utils/safeEjs-test.template-5.json new file mode 100644 index 000000000000..8bfef84c5bf4 --- /dev/null +++ b/opencti-platform/opencti-graphql/tests/01-unit/utils/safeEjs-test.template-5.json @@ -0,0 +1,17 @@ +<% + var inst = (data && data[0] && data[0].instance) ? data[0].instance : {}; +%>{ + "source": "OpenCTI", + "event": "New OpenCTI Report", + "id": "<%=notification.id%>3", + "timestamp":"<%= ((+new Date()) / 1000) | 0 %>", + "reports": [ + { + "title": "<%= (content && content[0] && content[0].title) ? content[0].title : '' %>", + "description": "<%= inst.description || '' %>", + "report_id":"<%=content[0].events[0].instance_id %>", + "published": "<%= inst.published || '' %>", + "author": "<%= inst.created_by_ref || inst.created_by_ref_id || 'Unknown' %>", + } + ] +} \ No newline at end of file diff --git a/opencti-platform/opencti-graphql/tests/01-unit/utils/safeEjs-test.ts b/opencti-platform/opencti-graphql/tests/01-unit/utils/safeEjs-test.ts index 636815db8749..fb1f47cd23e4 100644 --- a/opencti-platform/opencti-graphql/tests/01-unit/utils/safeEjs-test.ts +++ b/opencti-platform/opencti-graphql/tests/01-unit/utils/safeEjs-test.ts @@ -4,6 +4,7 @@ import { fileURLToPath } from 'node:url'; import { render } from 'ejs'; import { safeRender } from '../../../src/utils/safeEjs'; import { safeRender as safeRenderClient } from '../../../src/utils/safeEjs.client'; +import { customEscapeFunction } from '../../../src/utils/safeEjs.worker'; const testFilename = fileURLToPath(import.meta.url); @@ -318,6 +319,7 @@ describe('check safeRender on real files', () => { 'template-2.html', 'template-3.html', 'template-4.html', + 'template-5.json', 'template-6.json', 'template-7.json', ]; @@ -327,9 +329,10 @@ describe('check safeRender on real files', () => { async ({ name }) => { const templateFile = `${testFilename.substring(0, testFilename.lastIndexOf('.'))}.${name}`; const template = await fs.readFile(templateFile, 'utf8'); - const safeRendered = safeRender(template, data, { useNotificationTool: true }); + const escape = name.includes('.json') ? customEscapeFunction : undefined; + const safeRendered = await safeRender(template, data, { useNotificationTool: true, escape }); const unsafeRendered = render(template, data); expect(safeRendered).toEqual(unsafeRendered); - } + }, ); }); From c189d85519f81645998f55deadd767fafb3c2d92 Mon Sep 17 00:00:00 2001 From: Filigran Automation Date: Tue, 23 Dec 2025 16:26:57 +0000 Subject: [PATCH 003/126] [backend/worker] Release 6.9.4 --- client-python/pycti/__init__.py | 2 +- opencti-platform/opencti-front/package.json | 2 +- opencti-platform/opencti-graphql/package.json | 2 +- opencti-platform/opencti-graphql/src/python/requirements.txt | 2 +- opencti-worker/src/requirements.txt | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/client-python/pycti/__init__.py b/client-python/pycti/__init__.py index ffde85595899..911bde893f33 100644 --- a/client-python/pycti/__init__.py +++ b/client-python/pycti/__init__.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -__version__ = "6.9.3" +__version__ = "6.9.4" from .api.opencti_api_client import OpenCTIApiClient from .api.opencti_api_connector import OpenCTIApiConnector diff --git a/opencti-platform/opencti-front/package.json b/opencti-platform/opencti-front/package.json index 6e7d03088a6d..bc6a68b6607b 100644 --- a/opencti-platform/opencti-front/package.json +++ b/opencti-platform/opencti-front/package.json @@ -1,6 +1,6 @@ { "name": "opencti-front", - "version": "6.9.3", + "version": "6.9.4", "private": true, "workspaces": [ "packages/*" diff --git a/opencti-platform/opencti-graphql/package.json b/opencti-platform/opencti-graphql/package.json index 6af662087c73..da26b005724c 100644 --- a/opencti-platform/opencti-graphql/package.json +++ b/opencti-platform/opencti-graphql/package.json @@ -1,6 +1,6 @@ { "name": "opencti-graphql", - "version": "6.9.3", + "version": "6.9.4", "private": true, "scripts": { "check-ts": "tsc --noEmit", diff --git a/opencti-platform/opencti-graphql/src/python/requirements.txt b/opencti-platform/opencti-graphql/src/python/requirements.txt index 513517814965..f39a15695b6f 100644 --- a/opencti-platform/opencti-graphql/src/python/requirements.txt +++ b/opencti-platform/opencti-graphql/src/python/requirements.txt @@ -1,4 +1,4 @@ -pycti==6.9.3 +pycti==6.9.4 parsuricata==0.4.1 yara-python==4.5.2 sigmatools==0.23.1 diff --git a/opencti-worker/src/requirements.txt b/opencti-worker/src/requirements.txt index 15a3252ce908..2e66bcb7e013 100644 --- a/opencti-worker/src/requirements.txt +++ b/opencti-worker/src/requirements.txt @@ -1,4 +1,4 @@ -pycti==6.9.3 +pycti==6.9.4 opentelemetry-api~=1.35.0 opentelemetry-sdk~=1.35.0 opentelemetry-exporter-prometheus==0.56b0 From ceea2fde8d1ff484e5dc9667fec06cb0af3142a4 Mon Sep 17 00:00:00 2001 From: Marie Flores <73853856+marieflorescontact@users.noreply.github.com> Date: Tue, 23 Dec 2025 18:22:33 +0100 Subject: [PATCH 004/126] [backend] Fix default dashboard for a group not taken into account (#11811) --- .../settings/groups/GroupEditionOverview.tsx | 1 + .../opencti-graphql/src/domain/group.js | 2 +- .../03-integration/02-resolvers/group-test.ts | 175 ++++++++++-------- 3 files changed, 103 insertions(+), 75 deletions(-) diff --git a/opencti-platform/opencti-front/src/private/components/settings/groups/GroupEditionOverview.tsx b/opencti-platform/opencti-front/src/private/components/settings/groups/GroupEditionOverview.tsx index 861e00bf4a3d..c4d53a6ceb60 100644 --- a/opencti-platform/opencti-front/src/private/components/settings/groups/GroupEditionOverview.tsx +++ b/opencti-platform/opencti-front/src/private/components/settings/groups/GroupEditionOverview.tsx @@ -264,6 +264,7 @@ const GroupEditionOverview = createFragmentContainer( name authorizedMembers { id + member_id } } ...GroupHiddenTypesField_group diff --git a/opencti-platform/opencti-graphql/src/domain/group.js b/opencti-platform/opencti-graphql/src/domain/group.js index 07fde01c10de..b15d0a181e84 100644 --- a/opencti-platform/opencti-graphql/src/domain/group.js +++ b/opencti-platform/opencti-graphql/src/domain/group.js @@ -163,7 +163,7 @@ export const groupEditField = async (context, user, groupId, input) => { context_data: { id: groupId, entity_type: ENTITY_TYPE_GROUP, input }, }); // on editing the group confidence level, all members might have changed their effective level - if (input.find((i) => ['group_confidence_level', 'max_shareable_markings', 'restrict_delete'].includes(i.key))) { + if (input.find((i) => ['group_confidence_level', 'max_shareable_markings', 'restrict_delete', 'default_dashboard'].includes(i.key))) { await groupUsersCacheRefresh(context, user, groupId); } return notify(BUS_TOPICS[ENTITY_TYPE_GROUP].EDIT_TOPIC, element, user); diff --git a/opencti-platform/opencti-graphql/tests/03-integration/02-resolvers/group-test.ts b/opencti-platform/opencti-graphql/tests/03-integration/02-resolvers/group-test.ts index fb2c666251bb..25b632fcc979 100644 --- a/opencti-platform/opencti-graphql/tests/03-integration/02-resolvers/group-test.ts +++ b/opencti-platform/opencti-graphql/tests/03-integration/02-resolvers/group-test.ts @@ -118,83 +118,110 @@ describe('Group resolver standard behavior', () => { describe('when a group has a default dashboard', () => { let dashboardId = ''; + let dashboardUpdatedId: string; + const dashboardToDeleteIds: string[] = []; + const CREATE_DASHBOARD_QUERY = gql` + mutation CreateDashboard($input: WorkspaceAddInput!){ + workspaceAdd(input: $input){ + id + } + }`; + const EDIT_DEFAULT_DASHBOARD_MUTATION = gql` + mutation setDefaultDashboard($groupId: ID!, $editInput: [EditInput]!) { + groupEdit(id: $groupId) { + fieldPatch(input: $editInput) { + default_dashboard { + id + name + } + } + } + }`; beforeAll(async () => { const dashboardCreationQuery = await queryAsAdmin({ - query: gql` - mutation CreateDashboard($input: WorkspaceAddInput!){ - workspaceAdd(input: $input){ - id - } - }`, + query: CREATE_DASHBOARD_QUERY, variables: { input: { type: 'dashboard', - name: 'dashboard de test' - } - } + name: 'dashboard de test', + }, + }, }); dashboardId = dashboardCreationQuery?.data?.workspaceAdd.id; + dashboardToDeleteIds.push(dashboardId); }); afterAll(async () => { - await queryAsAdmin({ - query: gql` - mutation workspaceDelete($id: ID!) { - workspaceDelete(id: $id) - }`, - variables: { - id: dashboardId - } - }); + // Delete the groups + for (let i = 0; i < dashboardToDeleteIds.length; i += 1) { + const dashboardId = dashboardToDeleteIds[i]; + await queryAsAdmin({ + query: gql` + mutation workspaceDelete($id: ID!) { + workspaceDelete(id: $id) + }`, + variables: { + id: dashboardId, + }, + }); + } }); it('can have a reference to it', async () => { const setDefaultDashboardMutation = await queryAsAdmin({ - query: gql` - mutation setDefaultDashboard($groupId: ID!, $editInput: [EditInput]!) { - groupEdit(id: $groupId) { - fieldPatch(input: $editInput) { - default_dashboard { - id - name - } - } - } - }`, + query: EDIT_DEFAULT_DASHBOARD_MUTATION, variables: { groupId: groupInternalId, editInput: [{ key: 'default_dashboard', - value: dashboardId - }] - } + value: dashboardId, + }], + }, }); expect(setDefaultDashboardMutation?.data?.groupEdit.fieldPatch.default_dashboard.id).toEqual(dashboardId); expect(setDefaultDashboardMutation?.data?.groupEdit.fieldPatch.default_dashboard.name).toEqual('dashboard de test'); }); + it('should edit default dashboard', async () => { + // Create new dashboard + const newDashboardCreationQuery = await queryAsAdmin({ + query: CREATE_DASHBOARD_QUERY, + variables: { + input: { + type: 'dashboard', + name: 'new dashboard', + }, + }, + }); + dashboardUpdatedId = newDashboardCreationQuery?.data?.workspaceAdd.id; + dashboardToDeleteIds.push(dashboardUpdatedId); + + // Edit default dashboard + const editDefaultDashboardMutation = await queryAsAdmin({ + query: EDIT_DEFAULT_DASHBOARD_MUTATION, + variables: { + groupId: groupInternalId, + editInput: [{ + key: 'default_dashboard', + value: [dashboardUpdatedId], + }], + }, + }); + expect(editDefaultDashboardMutation?.data?.groupEdit.fieldPatch.default_dashboard.id).toEqual(dashboardUpdatedId); + }); + it('can remove the reference to the default dashboard', async () => { const removeDefaultDashboardMutation = await queryAsAdmin({ - query: gql` - mutation removeDefaultDashboardMutation($groupId: ID!, $editInput: [EditInput]!) { - groupEdit(id: $groupId) { - fieldPatch(input: $editInput) { - default_dashboard { - id - name - } - } - } - }`, + query: EDIT_DEFAULT_DASHBOARD_MUTATION, variables: { groupId: groupInternalId, editInput: [{ key: 'default_dashboard', - value: [null] - }] - } + value: [null], + }], + }, }); expect(removeDefaultDashboardMutation?.data?.groupEdit.fieldPatch.default_dashboard).toBeNull(); }); @@ -239,13 +266,13 @@ describe('Group resolver standard behavior', () => { { key: 'auto_integration_assignation', values: [ - 'global' - ] - } + 'global', + ], + }, ], - filterGroups: [] - } - } + filterGroups: [], + }, + }, }); expect(queryDefaultIngestionGroup?.data?.groups.edges.length).toBe(1); expect(queryDefaultIngestionGroup?.data?.groups.edges[0].node.name).toBe('Connectors'); @@ -275,7 +302,7 @@ describe('Group resolver standard behavior', () => { defaultIngestionGroupCount } `, - variables: {} + variables: {}, }); expect(defaultIngestionGroupCountResultNoGroup?.data?.defaultIngestionGroupCount).toBe(0); @@ -293,7 +320,7 @@ describe('Group resolver standard behavior', () => { defaultIngestionGroupCount } `, - variables: {} + variables: {}, }); expect(defaultIngestionGroupCountResult?.data?.defaultIngestionGroupCount).toBe(1); }); @@ -329,7 +356,7 @@ describe('Group resolver standard behavior', () => { defaultIngestionGroupCount } `, - variables: {} + variables: {}, }); expect(defaultIngestionGroupCountResult?.data?.defaultIngestionGroupCount).toBe(1); }); @@ -449,7 +476,7 @@ describe('Group resolver standard behavior', () => { query: UPDATE_QUERY, variables: { id: groupInternalId, - input: { key: 'group_confidence_level', value: [group_confidence_level] } + input: { key: 'group_confidence_level', value: [group_confidence_level] }, }, }); expect(queryResult?.data?.groupEdit.fieldPatch.group_confidence_level).toEqual(group_confidence_level); @@ -459,7 +486,7 @@ describe('Group resolver standard behavior', () => { query: UPDATE_QUERY, variables: { id: groupInternalId, - input: { key: 'group_confidence_level', object_path: '/group_confidence_level/max_confidence', value: [87] } + input: { key: 'group_confidence_level', object_path: '/group_confidence_level/max_confidence', value: [87] }, }, }); expect(queryResult?.data?.groupEdit.fieldPatch.group_confidence_level).toEqual({ @@ -475,9 +502,9 @@ describe('Group resolver standard behavior', () => { object_path: '/group_confidence_level/overrides', value: [ { entity_type: 'Report', max_confidence: 70 }, - { entity_type: 'Malware', max_confidence: 25 } + { entity_type: 'Malware', max_confidence: 25 }, ], - } + }, }, }); expect(queryResult?.data?.groupEdit.fieldPatch.group_confidence_level).toEqual({ @@ -491,7 +518,7 @@ describe('Group resolver standard behavior', () => { query: UPDATE_QUERY, variables: { id: groupInternalId, - input: { key: 'group_confidence_level', object_path: '/group_confidence_level/overrides/0', value: [{ entity_type: 'Case-Rfi', max_confidence: 70 }] } + input: { key: 'group_confidence_level', object_path: '/group_confidence_level/overrides/0', value: [{ entity_type: 'Case-Rfi', max_confidence: 70 }] }, }, }); expect(queryResult?.data?.groupEdit.fieldPatch.group_confidence_level).toEqual({ @@ -505,7 +532,7 @@ describe('Group resolver standard behavior', () => { query: UPDATE_QUERY, variables: { id: groupInternalId, - input: { key: 'group_confidence_level', object_path: '/group_confidence_level/overrides/1/max_confidence', value: [63] } + input: { key: 'group_confidence_level', object_path: '/group_confidence_level/overrides/1/max_confidence', value: [63] }, }, }); expect(queryResult?.data?.groupEdit.fieldPatch.group_confidence_level).toEqual({ @@ -519,7 +546,7 @@ describe('Group resolver standard behavior', () => { query: UPDATE_QUERY, variables: { id: groupInternalId, - input: { key: 'group_confidence_level', object_path: '/group_confidence_level/overrides/1', value: [], operation: 'remove' } + input: { key: 'group_confidence_level', object_path: '/group_confidence_level/overrides/1', value: [], operation: 'remove' }, }, }); expect(queryResult?.data?.groupEdit.fieldPatch.group_confidence_level).toEqual({ @@ -532,7 +559,7 @@ describe('Group resolver standard behavior', () => { query: UPDATE_QUERY, variables: { id: groupInternalId, - input: { key: 'group_confidence_level', object_path: '/group_confidence_level/overrides', value: [] } + input: { key: 'group_confidence_level', object_path: '/group_confidence_level/overrides', value: [] }, }, }); expect(queryResult?.data?.groupEdit.fieldPatch.group_confidence_level).toEqual({ @@ -559,9 +586,9 @@ describe('Group resolver standard behavior', () => { object_path: '/group_confidence_level/overrides', value: [ { entity_type: 'Report', max_confidence: 70 }, - { entity_type: 'Malware', max_confidence: null } + { entity_type: 'Malware', max_confidence: null }, ], - } + }, }, }, 'Validation against schema failed on attribute [max_confidence]: this mandatory field cannot be nil'); @@ -573,11 +600,11 @@ describe('Group resolver standard behavior', () => { input: { key: 'group_confidence_level', object_path: '/group_confidence_level/overrides/1', - value: { entity_type: 'Malware' } - } + value: { entity_type: 'Malware' }, + }, }, }, - 'Validation against schema failed on attribute [overrides]: mandatory field [max_confidence] is not present' + 'Validation against schema failed on attribute [overrides]: mandatory field [max_confidence] is not present', ); await adminQueryWithError( @@ -589,11 +616,11 @@ describe('Group resolver standard behavior', () => { key: 'group_confidence_level', value: { max_confidence: 87, - } - } + }, + }, }, }, - 'Validation against schema failed on attribute [group_confidence_level]: mandatory field [overrides] is not present' + 'Validation against schema failed on attribute [group_confidence_level]: mandatory field [overrides] is not present', ); await adminQueryWithError( @@ -603,11 +630,11 @@ describe('Group resolver standard behavior', () => { id: groupInternalId, input: { key: 'group_confidence_level', - value: 45 - } + value: 45, + }, }, }, - 'Validation against schema failed on attribute [group_confidence_level]: value must be an object' + 'Validation against schema failed on attribute [group_confidence_level]: value must be an object', ); }); it('should context patch group', async () => { From a894e3cbd3dfa45858891f424f70a725b5bcca51 Mon Sep 17 00:00:00 2001 From: Gwendoline-FAVRE-FELIX Date: Tue, 23 Dec 2025 22:01:01 +0100 Subject: [PATCH 005/126] [DOC] Update CONTRIBUTING.md to add more components on title convention (#13783) --- CONTRIBUTING.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 97155dd733c5..76ff975cfda3 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -49,10 +49,13 @@ For general suggestions or questions about the project or the documentation, you * If you are interested in contributing to developing OpenCTI, please refer to the [detailed documentation](https://docs.opencti.io/latest/). It can be either a to fix an issue which is meaningful to you, or to develop a feature requested by others. * All commits messages must be formatted as: `[component] Message (#issuenumber)` where component should be: - * api + * backend * frontend + * client-python * worker - * doc + * docs + * tools + * CI * All commit must be signed, if you need to configure your git environement please see [Github documentation on signed commit][https://docs.github.com/en/authentication/managing-commit-signature-verification/signing-commits] From 35d74f009a7d46a3648cf898b013525572754ffc Mon Sep 17 00:00:00 2001 From: Julien Richard Date: Wed, 24 Dec 2025 09:27:53 +0100 Subject: [PATCH 006/126] [backend] Users cache is not refreshed when changing some group attributes (#13826) --- opencti-platform/opencti-graphql/src/domain/group.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/opencti-platform/opencti-graphql/src/domain/group.js b/opencti-platform/opencti-graphql/src/domain/group.js index b15d0a181e84..9fc583fd520a 100644 --- a/opencti-platform/opencti-graphql/src/domain/group.js +++ b/opencti-platform/opencti-graphql/src/domain/group.js @@ -152,6 +152,7 @@ export const groupDelete = async (context, user, groupId) => { return notify(BUS_TOPICS[ENTITY_TYPE_GROUP].DELETE_TOPIC, group, user).then(() => groupId); }; +const groupAttributesUserCacheNoReset = ['name', 'description']; export const groupEditField = async (context, user, groupId, input) => { const { element } = await updateAttribute(context, user, groupId, ENTITY_TYPE_GROUP, input); await publishUserAction({ @@ -162,8 +163,8 @@ export const groupEditField = async (context, user, groupId, input) => { message: `updates \`${input.map((i) => i.key).join(', ')}\` for group \`${element.name}\``, context_data: { id: groupId, entity_type: ENTITY_TYPE_GROUP, input }, }); - // on editing the group confidence level, all members might have changed their effective level - if (input.find((i) => ['group_confidence_level', 'max_shareable_markings', 'restrict_delete', 'default_dashboard'].includes(i.key))) { + // On editing the group, refresh the cache (only when really needed) + if (input.find((i) => !groupAttributesUserCacheNoReset.includes(i.key))) { await groupUsersCacheRefresh(context, user, groupId); } return notify(BUS_TOPICS[ENTITY_TYPE_GROUP].EDIT_TOPIC, element, user); From 9e838511a5719c971b56ec6257e8f0d3ff5a0a14 Mon Sep 17 00:00:00 2001 From: Marie Flores <73853856+marieflorescontact@users.noreply.github.com> Date: Mon, 29 Dec 2025 11:44:13 +0100 Subject: [PATCH 007/126] [frontend] fix spacing in history tab (#13811) --- .../stix_core_objects/StixCoreObjectHistory.tsx | 17 +++++++++++++---- .../StixCoreObjectHistoryLines.tsx | 4 ++++ 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/opencti-platform/opencti-front/src/private/components/common/stix_core_objects/StixCoreObjectHistory.tsx b/opencti-platform/opencti-front/src/private/components/common/stix_core_objects/StixCoreObjectHistory.tsx index e303d5314f02..cd340a638be6 100644 --- a/opencti-platform/opencti-front/src/private/components/common/stix_core_objects/StixCoreObjectHistory.tsx +++ b/opencti-platform/opencti-front/src/private/components/common/stix_core_objects/StixCoreObjectHistory.tsx @@ -13,6 +13,7 @@ import SearchInput from '../../../../components/SearchInput'; import Loader, { LoaderVariant } from '../../../../components/Loader'; import useQueryLoading from '../../../../utils/hooks/useQueryLoading'; import type { Theme } from '../../../../components/Theme'; +import { Box } from '@mui/material'; type StixCoreObjectHistoryProps = { stixCoreObjectId: string; @@ -125,13 +126,17 @@ const StixCoreObjectHistory = ({ stixCoreObjectId, withoutRelations }: StixCoreO > {t_i18n('Entity')} -
+ -
+
{objectsQueryRef && ( @@ -156,13 +161,17 @@ const StixCoreObjectHistory = ({ stixCoreObjectId, withoutRelations }: StixCoreO > {t_i18n('Relations of the entity')} -
+ -
+
{relationsQueryRef && ( diff --git a/opencti-platform/opencti-front/src/private/components/common/stix_core_objects/StixCoreObjectHistoryLines.tsx b/opencti-platform/opencti-front/src/private/components/common/stix_core_objects/StixCoreObjectHistoryLines.tsx index 69fd40dc2ff0..668d8624acf0 100644 --- a/opencti-platform/opencti-front/src/private/components/common/stix_core_objects/StixCoreObjectHistoryLines.tsx +++ b/opencti-platform/opencti-front/src/private/components/common/stix_core_objects/StixCoreObjectHistoryLines.tsx @@ -17,6 +17,8 @@ import useInterval from '../../../../utils/hooks/useInterval'; import { FIVE_SECONDS } from '../../../../utils/Time'; import { useFormatter } from '../../../../components/i18n'; import { StixCoreObjectHistoryLine_node$key } from '@components/common/stix_core_objects/__generated__/StixCoreObjectHistoryLine_node.graphql'; +import { useTheme } from '@mui/material/styles'; +import type { Theme } from '../../../../components/Theme'; export const stixCoreObjectHistoryLinesQuery = graphql` query StixCoreObjectHistoryLinesQuery( @@ -62,6 +64,7 @@ const StixCoreObjectHistoryLines: FunctionComponent { const { t_i18n } = useFormatter(); + const theme = useTheme(); const [open, setOpen] = useState(false); const [selectedLog, setSelectedLog] = useState(undefined); const queryData = usePreloadedQuery(stixCoreObjectHistoryLinesQuery, queryRef); @@ -88,6 +91,7 @@ const StixCoreObjectHistoryLines: FunctionComponent Date: Tue, 30 Dec 2025 15:39:59 +0100 Subject: [PATCH 008/126] [frontend] AutocompleteField to Typescript (#13845) --- .../src/components/AutocompleteField.jsx | 127 --------------- .../src/components/AutocompleteField.tsx | 154 ++++++++++++++++++ .../common/form/CaseTemplateField.tsx | 19 +-- .../common/form/CaseTemplateTasks.tsx | 46 +++--- .../components/common/form/CountryField.tsx | 65 ++++---- .../components/common/form/CreatorField.tsx | 55 ++++--- .../components/common/form/DashboardField.tsx | 16 +- .../ingestionJson/IngestionJsonCreation.tsx | 4 +- .../activity/configuration/Configuration.tsx | 7 +- .../opencti-front/src/utils/field.ts | 17 -- .../opencti-front/src/utils/field.tsx | 32 ++++ 11 files changed, 288 insertions(+), 254 deletions(-) delete mode 100644 opencti-platform/opencti-front/src/components/AutocompleteField.jsx create mode 100644 opencti-platform/opencti-front/src/components/AutocompleteField.tsx delete mode 100644 opencti-platform/opencti-front/src/utils/field.ts create mode 100644 opencti-platform/opencti-front/src/utils/field.tsx diff --git a/opencti-platform/opencti-front/src/components/AutocompleteField.jsx b/opencti-platform/opencti-front/src/components/AutocompleteField.jsx deleted file mode 100644 index a55abdce2fa7..000000000000 --- a/opencti-platform/opencti-front/src/components/AutocompleteField.jsx +++ /dev/null @@ -1,127 +0,0 @@ -import React from 'react'; -import TextField from '@mui/material/TextField'; -import IconButton from '@mui/material/IconButton'; -import { Add } from '@mui/icons-material'; -import MUIAutocomplete from '@mui/material/Autocomplete'; -import { fieldToAutocomplete } from 'formik-mui'; -import { useField } from 'formik'; -import { isNil } from 'ramda'; -import { truncate } from '../utils/String'; -import { useFormatter } from './i18n'; - -const AutocompleteField = (props) => { - // Separate props used only for this component - // and props to be passed to MUI Autocomplete. - const { - optionLength = 40, - ...otherProps - } = props; - const { - form: { setFieldValue, setFieldTouched, submitCount }, - field: { name }, - onChange, - onFocus, - noOptionsText, - renderOption, - required = false, - isOptionEqualToValue, - textfieldprops, - openCreate, - getOptionLabel, - onInternalChange, - endAdornment, - } = otherProps; - const [, meta] = useField(name); - const { t_i18n } = useFormatter(); - const internalOnChange = React.useCallback( - (_, value) => { - if (typeof onInternalChange === 'function') { - onInternalChange(name, value || ''); - } else { - setFieldValue(name, value); - if (typeof onChange === 'function') { - onChange(name, value || ''); - } - } - }, - [setFieldValue, name, onChange], - ); - const internalOnFocus = React.useCallback(() => { - if (typeof onFocus === 'function') { - onFocus(name); - } - }, [onFocus, name]); - const internalOnBlur = React.useCallback(() => { - setFieldTouched(name, true); - }, [setFieldTouched]); - const fieldProps = fieldToAutocomplete(otherProps); - delete fieldProps.helperText; - delete fieldProps.openCreate; - // Properly handle no selected option - if (fieldProps.value === '') { - fieldProps.value = null; - } - const defaultOptionToValue = (option, value) => option.value === value.value; - const defaultGetOptionLabel = (option) => ( - typeof option === 'object' ? truncate(option.label, optionLength) : truncate(option, optionLength) - ); - - const showError = !isNil(meta.error) && (meta.touched || submitCount > 0); - - return ( -
- ( - - )} - onChange={internalOnChange} - onFocus={internalOnFocus} - onBlur={internalOnBlur} - isOptionEqualToValue={isOptionEqualToValue ?? defaultOptionToValue} - slotProps={{ - paper: { - elevation: 2, - }, - }} - /> - {typeof openCreate === 'function' && ( - openCreate()} - edge="end" - style={{ position: 'absolute', top: 5, right: 35 }} - size="large" - title={t_i18n('Add')} - > - - - )} -
- ); -}; - -export default AutocompleteField; diff --git a/opencti-platform/opencti-front/src/components/AutocompleteField.tsx b/opencti-platform/opencti-front/src/components/AutocompleteField.tsx new file mode 100644 index 000000000000..40b389b2e552 --- /dev/null +++ b/opencti-platform/opencti-front/src/components/AutocompleteField.tsx @@ -0,0 +1,154 @@ +import { ReactNode, useCallback } from 'react'; +import { Add } from '@mui/icons-material'; +import { TextField, IconButton, Autocomplete, AutocompleteProps, TextFieldProps, AutocompleteValue } from '@mui/material'; +import { FieldProps, useField } from 'formik'; +import { truncate } from '../utils/String'; +import { useFormatter } from './i18n'; +import { isNilField } from '../utils/utils'; +import { FieldOption } from '../utils/field'; +import { fieldToAutocomplete } from 'formik-mui'; + +type Bool = boolean | undefined; +type PossibleValue = FieldOption | string; + +export type AutocompleteFieldProps< + M extends Bool = true, + Value extends PossibleValue = FieldOption, + DC extends Bool = boolean, + FSolo extends Bool = false, +> = Omit, 'onChange' | 'onBlur' | 'onFocus' | 'renderInput'> + & FieldProps + & { + optionLength?: number; + required?: boolean; + endAdornment?: ReactNode; + textfieldprops?: TextFieldProps; + onFocus?: (name: string) => void; + onChange?: (name: string, value: AutocompleteValue) => void; + onInternalChange?: (name: string, value: AutocompleteValue) => void; + openCreate?: () => void; + }; + +const AutocompleteField = < + M extends Bool = true, + Value extends PossibleValue = FieldOption, + DC extends Bool = boolean, + FSolo extends Bool = false, +>({ + optionLength = 40, + required = false, + onChange, + onFocus, + onInternalChange, + openCreate, + ...muiProps +}: AutocompleteFieldProps) => { + type MuiProps = AutocompleteProps; + + const { + form: { setFieldValue, setFieldTouched, submitCount }, + field: { name }, + noOptionsText, + renderOption, + isOptionEqualToValue, + textfieldprops, + getOptionLabel, + endAdornment, + disabled, + } = muiProps; + + const [, meta] = useField(name); + const { t_i18n } = useFormatter(); + + const internalOnChange = useCallback>((_, value) => { + if (onInternalChange) { + onInternalChange(name, value); + } else { + setFieldValue(name, value); + onChange?.(name, value); + } + }, [setFieldValue, name, onChange, onInternalChange]); + + const internalOnFocus = useCallback>(() => { + onFocus?.(name); + }, [onFocus, name]); + + const internalOnBlur = useCallback>(() => { + setFieldTouched(name, true); + }, [setFieldTouched]); + + const defaultOptionToValue = (option: Value, value: Value) => { + const optionVal = typeof option === 'object' ? option.value : option; + const valueVal = typeof value === 'object' ? value.value : value; + return optionVal === valueVal; + }; + + const defaultGetOptionLabel: MuiProps['getOptionLabel'] = (option) => { + return typeof option === 'object' + ? truncate(option.label, optionLength) + : truncate(option, optionLength); + }; + + const helperText = textfieldprops?.helperText; + const showError = !isNilField(meta.error) && (meta.touched || submitCount > 0); + const fieldProps = fieldToAutocomplete({ + ...muiProps, + renderInput: ({ inputProps: { value, ...inputProps }, InputProps, ...params }) => ( + + ), + }); + + return ( +
+ + + {openCreate && ( + openCreate()} + edge="end" + style={{ position: 'absolute', top: 5, right: 35 }} + size="large" + title={t_i18n('Add')} + > + + + )} +
+ ); +}; + +export default AutocompleteField; diff --git a/opencti-platform/opencti-front/src/private/components/common/form/CaseTemplateField.tsx b/opencti-platform/opencti-front/src/private/components/common/form/CaseTemplateField.tsx index bff95006791e..20d12fee4b56 100644 --- a/opencti-platform/opencti-front/src/private/components/common/form/CaseTemplateField.tsx +++ b/opencti-platform/opencti-front/src/private/components/common/form/CaseTemplateField.tsx @@ -1,14 +1,13 @@ import makeStyles from '@mui/styles/makeStyles'; -import { Field } from 'formik'; import React, { FunctionComponent } from 'react'; import { graphql, PreloadedQuery, usePreloadedQuery } from 'react-relay'; -import AutocompleteField from '../../../../components/AutocompleteField'; +import AutocompleteField, { AutocompleteFieldProps } from '../../../../components/AutocompleteField'; import { useFormatter } from '../../../../components/i18n'; import ItemIcon from '../../../../components/ItemIcon'; import Loader, { LoaderVariant } from '../../../../components/Loader'; import useQueryLoading from '../../../../utils/hooks/useQueryLoading'; import { CaseTemplateFieldQuery } from './__generated__/CaseTemplateFieldQuery.graphql'; -import { FieldOption } from '../../../../utils/field'; +import Field, { FieldOption } from '../../../../utils/field'; // Deprecated - https://mui.com/system/styles/basics/ // Do not use it for new code. @@ -63,11 +62,14 @@ const CaseTemplateFieldComponent: FunctionComponent ({ value: node.id, label: node.name })); + const caseTemplates = (data.caseTemplates?.edges ?? []).map(({ node }) => ({ + value: node.id, + label: node.name, + })); return (
- component={AutocompleteField} name="caseTemplates" multiple @@ -76,7 +78,7 @@ const CaseTemplateFieldComponent: FunctionComponent { + onChange={(name, value) => { onChange?.(name, value); onSubmit?.(name, value); }} @@ -84,10 +86,7 @@ const CaseTemplateFieldComponent: FunctionComponent, - option: { color: string; label: string }, - ) => ( + renderOption={(props, option) => (
  • diff --git a/opencti-platform/opencti-front/src/private/components/common/form/CaseTemplateTasks.tsx b/opencti-platform/opencti-front/src/private/components/common/form/CaseTemplateTasks.tsx index 3d8e173fb655..e22cea662a81 100644 --- a/opencti-platform/opencti-front/src/private/components/common/form/CaseTemplateTasks.tsx +++ b/opencti-platform/opencti-front/src/private/components/common/form/CaseTemplateTasks.tsx @@ -4,17 +4,17 @@ import Dialog from '@mui/material/Dialog'; import DialogActions from '@mui/material/DialogActions'; import DialogTitle from '@mui/material/DialogTitle'; import makeStyles from '@mui/styles/makeStyles'; -import { Field, Form, Formik } from 'formik'; +import { Form, Formik } from 'formik'; import { FormikConfig } from 'formik/dist/types'; import * as R from 'ramda'; -import React, { FunctionComponent, useState } from 'react'; +import React, { FunctionComponent, SyntheticEvent, useState } from 'react'; import { graphql } from 'react-relay'; -import AutocompleteField from '../../../../components/AutocompleteField'; +import AutocompleteField, { AutocompleteFieldProps } from '../../../../components/AutocompleteField'; import { useFormatter } from '../../../../components/i18n'; import MarkdownField from '../../../../components/fields/MarkdownField'; import TextField from '../../../../components/TextField'; import { fetchQuery, handleErrorInForm } from '../../../../relay/environment'; -import { FieldOption, fieldSpacingContainerStyle } from '../../../../utils/field'; +import Field, { FieldOption, fieldSpacingContainerStyle } from '../../../../utils/field'; import { CaseTemplateTasksCreationMutation, TaskTemplateAddInput } from './__generated__/CaseTemplateTasksCreationMutation.graphql'; import { CaseTemplateTasksSearchQuery$data } from './__generated__/CaseTemplateTasksSearchQuery.graphql'; import ItemIcon from '../../../../components/ItemIcon'; @@ -79,20 +79,24 @@ const CaseTemplateTasks: FunctionComponent = ({ const [commitTaskCreation] = useApiMutation( CaseTemplateTasksCreation, ); - const searchTasks = (event: React.ChangeEvent) => { - const search = event?.target?.value ?? ''; - fetchQuery(CaseTemplateTasksQuery, { search }) - .toPromise() - .then((data) => { - const newTasks = ( - data as CaseTemplateTasksSearchQuery$data - )?.taskTemplates?.edges?.map(({ node }) => ({ - value: node.id, - label: node.name, - })) ?? []; - setTasks(R.uniq([...tasks, ...newTasks])); - }); + + const searchTasks = (event?: SyntheticEvent) => { + if (event?.target instanceof HTMLInputElement) { + const search = event.target.value ?? ''; + fetchQuery(CaseTemplateTasksQuery, { search }) + .toPromise() + .then((data) => { + const newTasks = ( + data as CaseTemplateTasksSearchQuery$data + )?.taskTemplates?.edges?.map(({ node }) => ({ + value: node.id, + label: node.name, + })) ?? []; + setTasks(R.uniq([...tasks, ...newTasks])); + }); + } }; + const submitTaskCreation: FormikConfig['onSubmit'] = ( submitValues, { setSubmitting, setErrors, resetForm }, @@ -123,9 +127,10 @@ const CaseTemplateTasks: FunctionComponent = ({ }, }); }; + return ( <> - component={AutocompleteField} style={fieldSpacingContainerStyle} name="tasks" @@ -141,10 +146,7 @@ const CaseTemplateTasks: FunctionComponent = ({ onChange={onChange} openCreate={() => setOpenCreation(true)} classes={{ clearIndicator: classes.autoCompleteIndicator }} - renderOption={( - props: React.HTMLAttributes, - option: FieldOption, - ) => ( + renderOption={(props, option) => (
  • diff --git a/opencti-platform/opencti-front/src/private/components/common/form/CountryField.tsx b/opencti-platform/opencti-front/src/private/components/common/form/CountryField.tsx index be6a4279e337..61b049b68121 100644 --- a/opencti-platform/opencti-front/src/private/components/common/form/CountryField.tsx +++ b/opencti-platform/opencti-front/src/private/components/common/form/CountryField.tsx @@ -1,13 +1,12 @@ -import React, { FunctionComponent, useState } from 'react'; -import { Field } from 'formik'; +import React, { FunctionComponent, SyntheticEvent, useState } from 'react'; import makeStyles from '@mui/styles/makeStyles'; import { graphql } from 'react-relay'; import { CountryFieldSearchQuery$data } from '@components/common/form/__generated__/CountryFieldSearchQuery.graphql'; import { fetchQuery } from '../../../../relay/environment'; -import AutocompleteField from '../../../../components/AutocompleteField'; +import AutocompleteField, { AutocompleteFieldProps } from '../../../../components/AutocompleteField'; import { useFormatter } from '../../../../components/i18n'; import ItemIcon from '../../../../components/ItemIcon'; -import { FieldOption } from '../../../../utils/field'; +import Field, { FieldOption } from '../../../../utils/field'; // Deprecated - https://mui.com/system/styles/basics/ // Do not use it for new code. @@ -30,7 +29,7 @@ interface CountryFieldProps { id: string; name: string; label: string; - onChange?: (name: string, value: FieldOption) => void; + onChange?: (name: string, value: FieldOption | null) => void; containerStyle?: Record; helpertext?: string; required?: boolean; @@ -60,42 +59,39 @@ const CountryField: FunctionComponent = ({ }) => { const classes = useStyles(); const { t_i18n } = useFormatter(); - const [countries, setCountries] = useState< - { - label: string | undefined; - value: string | undefined; - }[] - >([]); + const [countries, setCountries] = useState([]); - const searchCountries = (event: React.ChangeEvent) => { - fetchQuery(CountryFieldQuery, { - search: event && event.target.value ? event.target.value : '', - }) - .toPromise() - .then((data) => { - const NewCountries = ( - (data as CountryFieldSearchQuery$data)?.countries?.edges ?? [] - ).map((n) => ({ - label: n?.node.name, - value: n?.node.id, - })); - const templateValues = [...countries, ...NewCountries]; - // Keep only the unique list of options - const uniqTemplates = templateValues.filter((item, index) => { - return ( - templateValues.findIndex((e) => e.value === item.value) === index - ); + const searchCountries = (event?: SyntheticEvent) => { + if (event?.target instanceof HTMLInputElement) { + const search = event.target.value ?? ''; + fetchQuery(CountryFieldQuery, { search }) + .toPromise() + .then((data) => { + const NewCountries = ( + (data as CountryFieldSearchQuery$data)?.countries?.edges ?? [] + ).map((n) => ({ + label: n?.node.name, + value: n?.node.id, + })); + const templateValues = [...countries, ...NewCountries]; + // Keep only the unique list of options + const uniqTemplates = templateValues.filter((item, index) => { + return ( + templateValues.findIndex((e) => e.value === item.value) === index + ); + }); + setCountries(uniqTemplates); }); - setCountries(uniqTemplates); - }); + } }; return (
    - > id={id} component={AutocompleteField} name={name} + multiple={false} required={required} textfieldprops={{ variant: 'standard', @@ -109,10 +105,7 @@ const CountryField: FunctionComponent = ({ noOptionsText={t_i18n('No available options')} options={countries} onInputChange={searchCountries} - renderOption={( - props: React.HTMLAttributes, - option: { color: string; label: string }, - ) => ( + renderOption={(props, option) => (
  • diff --git a/opencti-platform/opencti-front/src/private/components/common/form/CreatorField.tsx b/opencti-platform/opencti-front/src/private/components/common/form/CreatorField.tsx index 1ae94a335721..36888537c570 100644 --- a/opencti-platform/opencti-front/src/private/components/common/form/CreatorField.tsx +++ b/opencti-platform/opencti-front/src/private/components/common/form/CreatorField.tsx @@ -1,22 +1,21 @@ -import React, { FunctionComponent, ReactNode, useState } from 'react'; -import { Field } from 'formik'; +import React, { FunctionComponent, ReactNode, SyntheticEvent, useState } from 'react'; import { graphql } from 'react-relay'; import Box from '@mui/material/Box'; import { Link } from 'react-router-dom'; import { OpenInNewOutlined } from '@mui/icons-material'; import IconButton from '@mui/material/IconButton'; import { fetchQuery } from '../../../../relay/environment'; -import AutocompleteField from '../../../../components/AutocompleteField'; +import AutocompleteField, { AutocompleteFieldProps } from '../../../../components/AutocompleteField'; import { useFormatter } from '../../../../components/i18n'; import { CreatorFieldSearchQuery$data } from './__generated__/CreatorFieldSearchQuery.graphql'; import ItemIcon from '../../../../components/ItemIcon'; import useGranted, { SETTINGS_SETACCESSES } from '../../../../utils/hooks/useGranted'; -import { FieldOption } from '../../../../utils/field'; +import Field, { FieldOption } from '../../../../utils/field'; interface CreatorFieldProps { name: string; label: string; - onChange?: (name: string, value: FieldOption) => void; + onChange?: (name: string, value: FieldOption | null) => void; containerStyle?: Record; showConfidence?: boolean; helpertext?: string; @@ -93,32 +92,34 @@ const CreatorField: FunctionComponent = ({ return null; }; - const searchCreators = (event: React.ChangeEvent) => { - fetchQuery(CreatorFieldQuery, { - search: event && event.target.value ? event.target.value : '', - }) - .toPromise() - .then((data) => { - const NewCreators = ( - (data as CreatorFieldSearchQuery$data)?.members?.edges ?? [] - ).map((n) => ({ - label: n?.node.name ?? t_i18n('Unknown'), - value: n?.node.id, - extra: getExtraFromNode(n?.node), - })); - const templateValues = [...creatorOptions, ...NewCreators]; - // Keep only the unique list of options - const uniqTemplates = templateValues.filter((item, index) => { - return ( - templateValues.findIndex((e) => e.value === item.value) === index - ); + const searchCreators = (event?: SyntheticEvent) => { + if (event?.target instanceof HTMLInputElement) { + const search = event.target.value ?? ''; + fetchQuery(CreatorFieldQuery, { search }) + .toPromise() + .then((data) => { + const NewCreators = ( + (data as CreatorFieldSearchQuery$data)?.members?.edges ?? [] + ).map((n) => ({ + label: n?.node.name ?? t_i18n('Unknown'), + value: n?.node.id, + extra: getExtraFromNode(n?.node), + })); + const templateValues = [...creatorOptions, ...NewCreators]; + // Keep only the unique list of options + const uniqTemplates = templateValues.filter((item, index) => { + return ( + templateValues.findIndex((e) => e.value === item.value) === index + ); + }); + setCreatorOptions(uniqTemplates); }); - setCreatorOptions(uniqTemplates); - }); + } }; + return (
    - > disabled={disabled} component={AutocompleteField} name={name} diff --git a/opencti-platform/opencti-front/src/private/components/common/form/DashboardField.tsx b/opencti-platform/opencti-front/src/private/components/common/form/DashboardField.tsx index 984dae83ef1c..4a002e012f71 100644 --- a/opencti-platform/opencti-front/src/private/components/common/form/DashboardField.tsx +++ b/opencti-platform/opencti-front/src/private/components/common/form/DashboardField.tsx @@ -1,12 +1,11 @@ -import { Field } from 'formik'; import React, { FunctionComponent } from 'react'; import { graphql, PreloadedQuery, usePreloadedQuery } from 'react-relay'; import makeStyles from '@mui/styles/makeStyles'; -import AutocompleteField from '../../../../components/AutocompleteField'; +import AutocompleteField, { AutocompleteFieldProps } from '../../../../components/AutocompleteField'; import { useFormatter } from '../../../../components/i18n'; import Loader, { LoaderVariant } from '../../../../components/Loader'; import { SubscriptionFocus } from '../../../../components/Subscription'; -import { FieldOption, fieldSpacingContainerStyle } from '../../../../utils/field'; +import Field, { fieldSpacingContainerStyle } from '../../../../utils/field'; import useQueryLoading from '../../../../utils/hooks/useQueryLoading'; import { DashboardFieldQuery } from './__generated__/DashboardFieldQuery.graphql'; import ItemIcon from '../../../../components/ItemIcon'; @@ -57,12 +56,12 @@ const DashboardFieldComponent: FunctionComponent = ({ queryRef, ); return ( - > component={AutocompleteField} name="default_dashboard" multiple={false} - onChange={(name: string, value: FieldOption) => onChange(name, value?.value ?? null)} - isOptionEqualToValue={(option: FieldOption, { value }: FieldOption) => option.value === value} + onChange={(name, value) => onChange(name, value?.value ?? '')} + isOptionEqualToValue={(option, { value }) => option.value === value} textfieldprops={{ variant: 'standard', label: t_i18n('Default dashboard'), @@ -77,10 +76,7 @@ const DashboardFieldComponent: FunctionComponent = ({ type: 'Dashboard', }))} style={fieldSpacingContainerStyle} - renderOption={( - props: React.HTMLAttributes, - option: FieldOption, - ) => ( + renderOption={(props, option) => (
  • diff --git a/opencti-platform/opencti-front/src/private/components/data/ingestionJson/IngestionJsonCreation.tsx b/opencti-platform/opencti-front/src/private/components/data/ingestionJson/IngestionJsonCreation.tsx index e454c81c2fe7..1c567a0070d7 100644 --- a/opencti-platform/opencti-front/src/private/components/data/ingestionJson/IngestionJsonCreation.tsx +++ b/opencti-platform/opencti-front/src/private/components/data/ingestionJson/IngestionJsonCreation.tsx @@ -140,8 +140,8 @@ const IngestionJsonCreation: FunctionComponent = ({ const isGranted = useGranted([SETTINGS_SETACCESSES, VIRTUAL_ORGANIZATION_ADMIN]); const { me } = useAuth(); - const onCreatorSelection = async (option: FieldOption) => { - setCreatorId(option.value); + const onCreatorSelection = async (option: FieldOption | null) => { + setCreatorId(option?.value ?? ''); }; const updateObjectMarkingField = async ( setFieldValue: (field: string, value: FieldOption[], shouldValidate?: boolean) => Promise>, diff --git a/opencti-platform/opencti-front/src/private/components/settings/activity/configuration/Configuration.tsx b/opencti-platform/opencti-front/src/private/components/settings/activity/configuration/Configuration.tsx index 86d83447344e..f71db95c786a 100644 --- a/opencti-platform/opencti-front/src/private/components/settings/activity/configuration/Configuration.tsx +++ b/opencti-platform/opencti-front/src/private/components/settings/activity/configuration/Configuration.tsx @@ -116,9 +116,10 @@ const ConfigurationComponent: FunctionComponent< ); const currentListeners = (settings.activity_listeners ?? []).map((a) => a.id); const onChangeData = (resetForm: () => void) => { - return (name: string, data: FieldOption) => { - if (!currentListeners.includes(data.value)) { - const value = R.uniq([...currentListeners, data.value]); + return (name: string, data: FieldOption | null) => { + const val = data?.value ?? ''; + if (!currentListeners.includes(val)) { + const value = R.uniq([...currentListeners, val]); commit({ variables: { id: settings?.id, diff --git a/opencti-platform/opencti-front/src/utils/field.ts b/opencti-platform/opencti-front/src/utils/field.ts deleted file mode 100644 index d457facd0acb..000000000000 --- a/opencti-platform/opencti-front/src/utils/field.ts +++ /dev/null @@ -1,17 +0,0 @@ -export const fieldSpacingContainerStyle = { marginTop: 20, width: '100%' }; - -export interface FieldOption { - id?: string; - value: string; - label: string; - color?: string; - type?: string; - standard_id?: string; -} - -// TODO move this interface inside file KillChainPhasesField -// when it has been transformed it TypeScript. -export interface KillChainPhaseFieldOption extends FieldOption { - kill_chain_name: string; - phase_name: string; -} diff --git a/opencti-platform/opencti-front/src/utils/field.tsx b/opencti-platform/opencti-front/src/utils/field.tsx new file mode 100644 index 000000000000..2b487062f081 --- /dev/null +++ b/opencti-platform/opencti-front/src/utils/field.tsx @@ -0,0 +1,32 @@ +import { ComponentType } from 'react'; +import { FieldConfig, Field as FormikField } from 'formik'; + +export const fieldSpacingContainerStyle = { marginTop: 20, width: '100%' }; + +export interface FieldOption { + id?: string; + value: string; + label: string; + color?: string; + type?: string; + standard_id?: string; +} + +// TODO move this interface inside file KillChainPhasesField +// when it has been transformed it TypeScript. +export interface KillChainPhaseFieldOption extends FieldOption { + kill_chain_name: string; + phase_name: string; +} + +type FormikFieldConfig

    = Omit, 'component' | 'as' | 'render' | 'children'>; +type NoMetaProps

    = Omit; +type FieldProps = FormikFieldConfig & NoMetaProps & { + component: ComponentType; +}; + +const Field = (props: FieldProps) => { + return ; +}; + +export default Field; From d91c19e1e7a50c2642d28de7f1ccf54666b1eb48 Mon Sep 17 00:00:00 2001 From: Xavier Fournet <461943+xfournet@users.noreply.github.com> Date: Tue, 30 Dec 2025 16:11:59 +0100 Subject: [PATCH 009/126] [backend] Improve template engine (#13831) --- .../opencti-graphql/src/utils/safeEjs.ts | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/opencti-platform/opencti-graphql/src/utils/safeEjs.ts b/opencti-platform/opencti-graphql/src/utils/safeEjs.ts index 14c5d1d761f8..2be4dd1643d2 100644 --- a/opencti-platform/opencti-graphql/src/utils/safeEjs.ts +++ b/opencti-platform/opencti-graphql/src/utils/safeEjs.ts @@ -111,10 +111,11 @@ const createSafeContext = (async: boolean, { maxExecutedStatementCount = 0, maxE }, [safeName('property')]: (propertyName: unknown) => { - if (typeof propertyName === 'string' && (propertyName.startsWith(safeReservedPrefix) || forbiddenProperties.has(propertyName))) { - throw new VerifierIllegalAccessError(`Forbidden property access ${JSON.stringify({ propertyName })}`); + const name = String(propertyName); + if (name.startsWith(safeReservedPrefix) || forbiddenProperties.has(name)) { + throw new VerifierIllegalAccessError(`Forbidden property access ${JSON.stringify({ propertyName: name })}`); } - return propertyName; + return name; }, [safeName('Object')]: Object.freeze({ @@ -127,11 +128,12 @@ const createSafeContext = (async: boolean, { maxExecutedStatementCount = 0, maxE .filter((src) => src && typeof src === 'object') .forEach((src) => { Object.entries(src).forEach(([key, value]) => { - if (key.startsWith(safeReservedPrefix) || forbiddenProperties.has(key)) { - throw new VerifierIllegalAccessError(`Forbidden property access ${JSON.stringify({ propertyName: key })}`); + const name = String(key); // key should already be a string, but enforce it anyway + if (name.startsWith(safeReservedPrefix) || forbiddenProperties.has(name)) { + throw new VerifierIllegalAccessError(`Forbidden property access ${JSON.stringify({ propertyName: name })}`); } - target[key] = value; + target[name] = value; }); }); return target; From 2518d2e397e74edf612a9c67d72c69bbb8e23872 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9line=20S=C3=A8be?= <87119259+CelineSebe@users.noreply.github.com> Date: Tue, 30 Dec 2025 17:19:00 +0100 Subject: [PATCH 010/126] [frontend] TypesField & SelectField to ts (#13845) --- .../{SelectField.jsx => SelectField.tsx} | 62 +++++++---- .../components/observations/TypesField.jsx | 63 ----------- .../components/observations/TypesField.tsx | 104 ++++++++++++++++++ 3 files changed, 143 insertions(+), 86 deletions(-) rename opencti-platform/opencti-front/src/components/fields/{SelectField.jsx => SelectField.tsx} (58%) delete mode 100644 opencti-platform/opencti-front/src/private/components/observations/TypesField.jsx create mode 100644 opencti-platform/opencti-front/src/private/components/observations/TypesField.tsx diff --git a/opencti-platform/opencti-front/src/components/fields/SelectField.jsx b/opencti-platform/opencti-front/src/components/fields/SelectField.tsx similarity index 58% rename from opencti-platform/opencti-front/src/components/fields/SelectField.jsx rename to opencti-platform/opencti-front/src/components/fields/SelectField.tsx index 6e802fc46765..e8340bb464d4 100644 --- a/opencti-platform/opencti-front/src/components/fields/SelectField.jsx +++ b/opencti-platform/opencti-front/src/components/fields/SelectField.tsx @@ -1,39 +1,52 @@ import React from 'react'; import { isNil } from 'ramda'; -import { getIn, useField } from 'formik'; +import { FieldProps, getIn, useField } from 'formik'; import { v4 as uuid } from 'uuid'; import MuiSelect from '@mui/material/Select'; import InputLabel from '@mui/material/InputLabel'; import FormControl from '@mui/material/FormControl'; import FormHelperText from '@mui/material/FormHelperText'; +import { SelectProps } from '@mui/material'; -const fieldToSelect = ({ - disabled, - field: { onChange: fieldOnChange, ...field }, - form: { isSubmitting, touched, errors, setFieldTouched, setFieldValue }, - onClose, - ...props -}) => { +export type SelectFieldProps = FieldProps & Omit, 'onChange' | 'onFocus'> & { + required: boolean; + onChange?: (name: string, value: string) => void; + onFocus?: (name: string) => void; + onSubmit?: (name: string, value: string) => void; + containerstyle?: Record; + helpertext?: string; +}; + +const fieldToSelect = (muiProps: SelectFieldProps) => { + const { + disabled, + field: { onChange: fieldOnChange, ...field }, + form: { isSubmitting, touched, errors, setFieldTouched, setFieldValue }, + onClose, + } = muiProps; const fieldError = getIn(errors, field.name); const showError = getIn(touched, field.name) && !!fieldError; + return { + ...field, + ...muiProps, disabled: disabled ?? isSubmitting, error: showError, onBlur: () => {}, onChange: fieldOnChange ?? (() => {}), onClose: onClose ?? (async (e) => { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore const { dataset } = e.target; if (dataset && dataset.value) { await setFieldValue(field.name, dataset.value); } setFieldTouched(field.name, true); }), - ...field, - ...props, }; }; -const SelectField = (props) => { +const SelectField = (muiProps: SelectFieldProps) => { const { form: { setFieldValue, setFieldTouched }, field: { name }, @@ -41,8 +54,11 @@ const SelectField = (props) => { onChange, onFocus, onSubmit, - } = props; - const internalOnChange = React.useCallback( + containerstyle, + helpertext, + } = muiProps; + + const internalOnChange = React.useCallback['onChange']>>( (event) => { const { value } = event.target; setFieldValue(name, value); @@ -52,12 +68,12 @@ const SelectField = (props) => { }, [setFieldValue, onChange, name], ); - const internalOnFocus = React.useCallback(() => { + const internalOnFocus = React.useCallback['onFocus']>>(() => { if (typeof onFocus === 'function') { onFocus(name); } }, [onFocus, name]); - const internalOnBlur = React.useCallback( + const internalOnBlur = React.useCallback['onBlur']>>( (event) => { const { value } = event.target; setFieldTouched(name, true); @@ -68,22 +84,22 @@ const SelectField = (props) => { [setFieldTouched, onSubmit, name], ); const [, meta] = useField(name); - const { value, ...otherProps } = fieldToSelect(props); + const { value, ...otherProps } = fieldToSelect(muiProps); const labelId = uuid(); return ( - {props.label} + {muiProps.label} { labelId={labelId} /> - {meta.touched && !isNil(meta.error) ? meta.error : props.helpertext} + {meta.touched && !isNil(meta.error) ? meta.error : helpertext} ); diff --git a/opencti-platform/opencti-front/src/private/components/observations/TypesField.jsx b/opencti-platform/opencti-front/src/private/components/observations/TypesField.jsx deleted file mode 100644 index 207af8dbfc6a..000000000000 --- a/opencti-platform/opencti-front/src/private/components/observations/TypesField.jsx +++ /dev/null @@ -1,63 +0,0 @@ -import React, { Component } from 'react'; -import * as PropTypes from 'prop-types'; -import { Field } from 'formik'; -import { assoc, compose, map, pipe, prop, sortBy, toLower } from 'ramda'; -import withStyles from '@mui/styles/withStyles'; -import MenuItem from '@mui/material/MenuItem'; -import inject18n from '../../../components/i18n'; -import SelectField from '../../../components/fields/SelectField'; -import { QueryRenderer } from '../../../relay/environment'; -import { stixCyberObservablesLinesSubTypesQuery } from './stix_cyber_observables/StixCyberObservablesLines'; - -const styles = () => ({ - container: { - margin: 0, - }, -}); - -class TypesField extends Component { - render() { - const { t, name, label, containerstyle } = this.props; - return ( - { - if (props && props.subTypes) { - const subTypesEdges = props.subTypes.edges; - const sortByLabel = sortBy(compose(toLower, prop('tlabel'))); - const translatedOrderedList = pipe( - map((n) => n.node), - map((n) => assoc('tlabel', t(`entity_${n.label}`), n)), - sortByLabel, - )(subTypesEdges); - return ( - - {translatedOrderedList.map((subType) => ( - - {subType.tlabel} - - ))} - - ); - } - return

    ; - }} - /> - ); - } -} - -TypesField.propTypes = { - classes: PropTypes.object, - t: PropTypes.func, -}; - -export default compose(inject18n, withStyles(styles))(TypesField); diff --git a/opencti-platform/opencti-front/src/private/components/observations/TypesField.tsx b/opencti-platform/opencti-front/src/private/components/observations/TypesField.tsx new file mode 100644 index 000000000000..282fc37f4fef --- /dev/null +++ b/opencti-platform/opencti-front/src/private/components/observations/TypesField.tsx @@ -0,0 +1,104 @@ +import React, { Suspense } from 'react'; +import { Field } from 'formik'; +import MenuItem from '@mui/material/MenuItem'; +import { useFormatter } from '../../../components/i18n'; +import SelectField from '../../../components/fields/SelectField'; +import { stixCyberObservablesLinesSubTypesQuery } from './stix_cyber_observables/StixCyberObservablesLines'; +import useQueryLoading from 'src/utils/hooks/useQueryLoading'; +import { PreloadedQuery, usePreloadedQuery } from 'react-relay'; +import { StixCyberObservablesLinesSubTypesQuery } from '@components/observations/stix_cyber_observables/__generated__/StixCyberObservablesLinesSubTypesQuery.graphql'; + +interface TypesFieldComponentProps { + queryRef: PreloadedQuery; + name: string; + label?: string; + required?: boolean; + containerstyle?: Record; + multiple?: boolean; + onChange?: (name: string, value: string | string[]) => void; +} +interface TLabel { + tlabel: string; +} + +const TypesFieldComponent = ({ + name, + label, + required = false, + containerstyle, + queryRef, +}: TypesFieldComponentProps) => { + const { t_i18n } = useFormatter(); + const { subTypes } = usePreloadedQuery(stixCyberObservablesLinesSubTypesQuery, queryRef); + + if (subTypes) { + const subTypesEdges = subTypes.edges; + const sortByLabel = (arr: T[]): T[] => + [...arr].sort((a, b) => { + const labelA = String(a.tlabel).toLowerCase(); + const labelB = String(b.tlabel).toLowerCase(); + return labelA.localeCompare(labelB); + }); + + const translatedOrderedList = sortByLabel( + subTypesEdges + .map((n) => n.node) + .map((n) => ({ + ...n, + tlabel: t_i18n(`entity_${n.label}`), + })), + ); + return ( + + {translatedOrderedList.map((subType) => ( + + {subType.tlabel} + + ))} + + ); + } +}; + +type TypesFieldProps = Omit; + +const TypesField = (props: TypesFieldProps) => { + const { name, label, containerstyle, required } = props; + + const queryRef = useQueryLoading( + stixCyberObservablesLinesSubTypesQuery, + { type: 'Stix-Cyber-Observable' }, + ); + const FallbackSelect = ( + + ); + + return ( + + {queryRef && ( + + )} + + ); +}; + +export default TypesField; From fbf8846263d8a90ebf43851ba85205090a563e31 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 30 Dec 2025 19:03:06 +0100 Subject: [PATCH 011/126] [deps] Update dependency nodemailer to v7.0.12 (#13834) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- opencti-platform/opencti-graphql/package.json | 2 +- opencti-platform/opencti-graphql/yarn.lock | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/opencti-platform/opencti-graphql/package.json b/opencti-platform/opencti-graphql/package.json index da26b005724c..1a4115d2a5fd 100644 --- a/opencti-platform/opencti-graphql/package.json +++ b/opencti-platform/opencti-graphql/package.json @@ -142,7 +142,7 @@ "node-calls-python": "1.11.1", "node-fetch": "3.3.2", "node-forge": "1.3.3", - "nodemailer": "7.0.11", + "nodemailer": "7.0.12", "openai": "4.104.0", "openid-client": "5.7.1", "opentelemetry-node-metrics": "3.0.0", diff --git a/opencti-platform/opencti-graphql/yarn.lock b/opencti-platform/opencti-graphql/yarn.lock index 167636e3179d..ad33a7e0fe4e 100644 --- a/opencti-platform/opencti-graphql/yarn.lock +++ b/opencti-platform/opencti-graphql/yarn.lock @@ -10657,10 +10657,10 @@ __metadata: languageName: node linkType: hard -"nodemailer@npm:7.0.11": - version: 7.0.11 - resolution: "nodemailer@npm:7.0.11" - checksum: 10c0/208f108fdb4c5dd0e3a2f013578d53dad505cf1b9c7a084f6d22fc9d6f3912daafb4a23793ca568ff848afc35f15f4eb24382d3f6f9fb8ede4a8410d4ca63618 +"nodemailer@npm:7.0.12": + version: 7.0.12 + resolution: "nodemailer@npm:7.0.12" + checksum: 10c0/b03948744423386b5fd7cb5bdf60797f2f71c4d3db202eedf7e0037429b5887cb90b641e7ca244af3b96e0b76949e3135cba96336fb0d3ade00ae33727ca8cf8 languageName: node linkType: hard @@ -11036,7 +11036,7 @@ __metadata: node-calls-python: "npm:1.11.1" node-fetch: "npm:3.3.2" node-forge: "npm:1.3.3" - nodemailer: "npm:7.0.11" + nodemailer: "npm:7.0.12" openai: "npm:4.104.0" openid-client: "npm:5.7.1" opentelemetry-node-metrics: "npm:3.0.0" From bad8a0e12c1b503a2382dcbf78d25517a3a92a52 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 30 Dec 2025 19:06:22 +0100 Subject: [PATCH 012/126] [deps] Update dependency slack to v6.1.2 (#13836) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 9f3ce984f5e8..a9c6638be27b 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -1,6 +1,6 @@ version: 2.1 orbs: - slack: circleci/slack@6.1.1 + slack: circleci/slack@6.1.2 kubernetes: circleci/kubernetes@1.3.1 jobs: # Client-python steps From 3ec07d3add0ad91021ded2e238e3e33ad5e07e1d Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 30 Dec 2025 19:06:51 +0100 Subject: [PATCH 013/126] [deps] Update dependency file-type to v21.2.0 (#13843) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- opencti-platform/opencti-graphql/package.json | 2 +- opencti-platform/opencti-graphql/yarn.lock | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/opencti-platform/opencti-graphql/package.json b/opencti-platform/opencti-graphql/package.json index 1a4115d2a5fd..90eeab2fa220 100644 --- a/opencti-platform/opencti-graphql/package.json +++ b/opencti-platform/opencti-graphql/package.json @@ -110,7 +110,7 @@ "express-rate-limit": "8.2.1", "express-session": "1.18.2", "fast-json-patch": "3.1.1", - "file-type": "21.1.1", + "file-type": "21.2.0", "github-api": "3.4.0", "graphql": "16.12.0", "graphql-constraint-directive": "6.0.0", diff --git a/opencti-platform/opencti-graphql/yarn.lock b/opencti-platform/opencti-graphql/yarn.lock index ad33a7e0fe4e..af9624bf5016 100644 --- a/opencti-platform/opencti-graphql/yarn.lock +++ b/opencti-platform/opencti-graphql/yarn.lock @@ -8191,15 +8191,15 @@ __metadata: languageName: node linkType: hard -"file-type@npm:21.1.1": - version: 21.1.1 - resolution: "file-type@npm:21.1.1" +"file-type@npm:21.2.0": + version: 21.2.0 + resolution: "file-type@npm:21.2.0" dependencies: "@tokenizer/inflate": "npm:^0.4.1" strtok3: "npm:^10.3.4" token-types: "npm:^6.1.1" uint8array-extras: "npm:^1.4.0" - checksum: 10c0/2df4375a934ddb74ad071c70012d863c993a90792a01c6c9128c02dc47e0adf724e9eff8b0489d880ee345d34df713a34536564a39a9c6db780475710fb9f366 + checksum: 10c0/af72f992fb8a6d9ff3a43dfe9293cab4f6b4faec7dc7f950e10c30fda9d5e5799e5fc45ad749fe5b2c1076699716f47ebcf0d573a6ad8640da82e35f36a481cf languageName: node linkType: hard @@ -11002,7 +11002,7 @@ __metadata: express-rate-limit: "npm:8.2.1" express-session: "npm:1.18.2" fast-json-patch: "npm:3.1.1" - file-type: "npm:21.1.1" + file-type: "npm:21.2.0" github-api: "npm:3.4.0" globals: "npm:16.5.0" graphql: "npm:16.12.0" From 56e820a403de8fc5b23a52dce2d2afc7d34ca0b9 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 30 Dec 2025 19:07:17 +0100 Subject: [PATCH 014/126] [deps] Update dependency typescript-eslint to v8.50.1 (#13842) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- opencti-platform/opencti-front/package.json | 2 +- opencti-platform/opencti-front/yarn.lock | 146 +++++++++--------- opencti-platform/opencti-graphql/package.json | 2 +- opencti-platform/opencti-graphql/yarn.lock | 146 +++++++++--------- 4 files changed, 148 insertions(+), 148 deletions(-) diff --git a/opencti-platform/opencti-front/package.json b/opencti-platform/opencti-front/package.json index bc6a68b6607b..b9eca95014bf 100644 --- a/opencti-platform/opencti-front/package.json +++ b/opencti-platform/opencti-front/package.json @@ -172,7 +172,7 @@ "relay-compiler": "20.1.1", "relay-test-utils": "20.1.1", "typescript": "5.9.3", - "typescript-eslint": "8.50.0", + "typescript-eslint": "8.50.1", "vite": "7.3.0", "vite-plugin-relay": "2.1.0", "vite-plugin-static-copy": "3.1.4", diff --git a/opencti-platform/opencti-front/yarn.lock b/opencti-platform/opencti-front/yarn.lock index 8df86446e1bc..8600214fef86 100644 --- a/opencti-platform/opencti-front/yarn.lock +++ b/opencti-platform/opencti-front/yarn.lock @@ -5102,94 +5102,94 @@ __metadata: languageName: node linkType: hard -"@typescript-eslint/eslint-plugin@npm:8.50.0": - version: 8.50.0 - resolution: "@typescript-eslint/eslint-plugin@npm:8.50.0" +"@typescript-eslint/eslint-plugin@npm:8.50.1": + version: 8.50.1 + resolution: "@typescript-eslint/eslint-plugin@npm:8.50.1" dependencies: "@eslint-community/regexpp": "npm:^4.10.0" - "@typescript-eslint/scope-manager": "npm:8.50.0" - "@typescript-eslint/type-utils": "npm:8.50.0" - "@typescript-eslint/utils": "npm:8.50.0" - "@typescript-eslint/visitor-keys": "npm:8.50.0" + "@typescript-eslint/scope-manager": "npm:8.50.1" + "@typescript-eslint/type-utils": "npm:8.50.1" + "@typescript-eslint/utils": "npm:8.50.1" + "@typescript-eslint/visitor-keys": "npm:8.50.1" ignore: "npm:^7.0.0" natural-compare: "npm:^1.4.0" ts-api-utils: "npm:^2.1.0" peerDependencies: - "@typescript-eslint/parser": ^8.50.0 + "@typescript-eslint/parser": ^8.50.1 eslint: ^8.57.0 || ^9.0.0 typescript: ">=4.8.4 <6.0.0" - checksum: 10c0/032038ee029d1e0984e7c189c3e8173dc4fb909c3ab4d272227e62e6d1872eb9853699c72d46e269c0a084f113ea01fa00d4b61620190276b224fa1b5a5cbd80 + checksum: 10c0/cae56cec414dc5d8347f1ff9fc01ec7b82c7988bcca9597569564b69e1715594e044487805a72ce7a9b4e6e81c3632db92c3d4b6b991874dafa402e1fcb508d5 languageName: node linkType: hard -"@typescript-eslint/parser@npm:8.50.0": - version: 8.50.0 - resolution: "@typescript-eslint/parser@npm:8.50.0" +"@typescript-eslint/parser@npm:8.50.1": + version: 8.50.1 + resolution: "@typescript-eslint/parser@npm:8.50.1" dependencies: - "@typescript-eslint/scope-manager": "npm:8.50.0" - "@typescript-eslint/types": "npm:8.50.0" - "@typescript-eslint/typescript-estree": "npm:8.50.0" - "@typescript-eslint/visitor-keys": "npm:8.50.0" + "@typescript-eslint/scope-manager": "npm:8.50.1" + "@typescript-eslint/types": "npm:8.50.1" + "@typescript-eslint/typescript-estree": "npm:8.50.1" + "@typescript-eslint/visitor-keys": "npm:8.50.1" debug: "npm:^4.3.4" peerDependencies: eslint: ^8.57.0 || ^9.0.0 typescript: ">=4.8.4 <6.0.0" - checksum: 10c0/3bdc9e7b2190285abf7350039056b104725fa70cbd769695717f9948669de4987db7103a7011d33d25d44e9474fe02404746816b8eba72642e17815cb6b0b2e6 + checksum: 10c0/60a2591745650b35cd8d425bb1959ef40d598245481bdfdc2654ed1f7878364c2c442ba70ca7105b650d0df2b6109727dd43214be76045667de0d32a221f3955 languageName: node linkType: hard -"@typescript-eslint/project-service@npm:8.50.0": - version: 8.50.0 - resolution: "@typescript-eslint/project-service@npm:8.50.0" +"@typescript-eslint/project-service@npm:8.50.1": + version: 8.50.1 + resolution: "@typescript-eslint/project-service@npm:8.50.1" dependencies: - "@typescript-eslint/tsconfig-utils": "npm:^8.50.0" - "@typescript-eslint/types": "npm:^8.50.0" + "@typescript-eslint/tsconfig-utils": "npm:^8.50.1" + "@typescript-eslint/types": "npm:^8.50.1" debug: "npm:^4.3.4" peerDependencies: typescript: ">=4.8.4 <6.0.0" - checksum: 10c0/54fdf4c8540eb8e592ab4818345935300bf5776621274cdc8bb942e72e84a4d2566b047b77218f6c851de26eab759c45153a39557ed2c2d1054d180d587d9780 + checksum: 10c0/50fee0882188c2d704deddfb39f5283618adf7e5f72418143e9f69a8f3771233d55a3e0fc2673fa09c62e230ec53e500f95c0f1ed331ffac5f6a7f8e7b7a2e8c languageName: node linkType: hard -"@typescript-eslint/scope-manager@npm:8.50.0": - version: 8.50.0 - resolution: "@typescript-eslint/scope-manager@npm:8.50.0" +"@typescript-eslint/scope-manager@npm:8.50.1": + version: 8.50.1 + resolution: "@typescript-eslint/scope-manager@npm:8.50.1" dependencies: - "@typescript-eslint/types": "npm:8.50.0" - "@typescript-eslint/visitor-keys": "npm:8.50.0" - checksum: 10c0/62a374aaa0bf7d185be43a4d7dd420d7135ab8f13f5cb4e602e16fdf712f0e00e6ab3fc8a31321e19922d27b867579b0b08c4040b23d528853f4b73e9ebcff3b + "@typescript-eslint/types": "npm:8.50.1" + "@typescript-eslint/visitor-keys": "npm:8.50.1" + checksum: 10c0/ef0df092745f5d4e3684a3d770dc47735ab3195456de4ac5825931aeed1857a7e8d7cec14cc9c78c5ed049b3d83b0f8ac43b9463c5032ba548558a06bebb5539 languageName: node linkType: hard -"@typescript-eslint/tsconfig-utils@npm:8.50.0, @typescript-eslint/tsconfig-utils@npm:^8.50.0": - version: 8.50.0 - resolution: "@typescript-eslint/tsconfig-utils@npm:8.50.0" +"@typescript-eslint/tsconfig-utils@npm:8.50.1, @typescript-eslint/tsconfig-utils@npm:^8.50.1": + version: 8.50.1 + resolution: "@typescript-eslint/tsconfig-utils@npm:8.50.1" peerDependencies: typescript: ">=4.8.4 <6.0.0" - checksum: 10c0/5398d26e4a7bec866cc783f5f329a4fed1bc07cd4d21c5c32929a7524b1ebf8ae8e15ca7a035d1177630d86b614ecd3243d63289228bbe292526dbcbf9fae430 + checksum: 10c0/6a1ffb0cd2d9e820ed0c7555a43ebb21438ca80f26c9632e0753bd09e764d9b8e9a352215e4ae60f6d570ab1e77751c9460a00515648b9a2f13f56c56a068a94 languageName: node linkType: hard -"@typescript-eslint/type-utils@npm:8.50.0": - version: 8.50.0 - resolution: "@typescript-eslint/type-utils@npm:8.50.0" +"@typescript-eslint/type-utils@npm:8.50.1": + version: 8.50.1 + resolution: "@typescript-eslint/type-utils@npm:8.50.1" dependencies: - "@typescript-eslint/types": "npm:8.50.0" - "@typescript-eslint/typescript-estree": "npm:8.50.0" - "@typescript-eslint/utils": "npm:8.50.0" + "@typescript-eslint/types": "npm:8.50.1" + "@typescript-eslint/typescript-estree": "npm:8.50.1" + "@typescript-eslint/utils": "npm:8.50.1" debug: "npm:^4.3.4" ts-api-utils: "npm:^2.1.0" peerDependencies: eslint: ^8.57.0 || ^9.0.0 typescript: ">=4.8.4 <6.0.0" - checksum: 10c0/7ebd9a1ebd0cbb6eca9864439f80c2432545bd3ac38dee706be0004c78a26a9908003aa4f0825c0745f4fa1356ffacc0848dd230eae22a6516a02710ab645157 + checksum: 10c0/e4bfd3dd2459e936f7b6d9ee4b60fdedbf4b8f6b3d832e11d3cb1b58c1ce6da098880daafe3b65b2d33e2f79aba0e75c4b6eafdfa2a66c6e00a9ad3132b8e90d languageName: node linkType: hard -"@typescript-eslint/types@npm:8.50.0, @typescript-eslint/types@npm:^8.50.0": - version: 8.50.0 - resolution: "@typescript-eslint/types@npm:8.50.0" - checksum: 10c0/15ec0d75deb331c5ccda726ad95d7f2801fde0f5edfe70425bbdede9e3c9e93b18e7890c9bc42f86ebd65221ebce75e6cc536a65cb1fbbdb0763df22ac392c7a +"@typescript-eslint/types@npm:8.50.1, @typescript-eslint/types@npm:^8.50.1": + version: 8.50.1 + resolution: "@typescript-eslint/types@npm:8.50.1" + checksum: 10c0/04e3c296d81293e370578762be6736fccd1581476f9d534938d42fe93968571fcaf26d7d8c3de52ed63a5af2c0b2da922b8ee2011fa5fb9fb401fc7f0916367a languageName: node linkType: hard @@ -5200,14 +5200,14 @@ __metadata: languageName: node linkType: hard -"@typescript-eslint/typescript-estree@npm:8.50.0": - version: 8.50.0 - resolution: "@typescript-eslint/typescript-estree@npm:8.50.0" +"@typescript-eslint/typescript-estree@npm:8.50.1": + version: 8.50.1 + resolution: "@typescript-eslint/typescript-estree@npm:8.50.1" dependencies: - "@typescript-eslint/project-service": "npm:8.50.0" - "@typescript-eslint/tsconfig-utils": "npm:8.50.0" - "@typescript-eslint/types": "npm:8.50.0" - "@typescript-eslint/visitor-keys": "npm:8.50.0" + "@typescript-eslint/project-service": "npm:8.50.1" + "@typescript-eslint/tsconfig-utils": "npm:8.50.1" + "@typescript-eslint/types": "npm:8.50.1" + "@typescript-eslint/visitor-keys": "npm:8.50.1" debug: "npm:^4.3.4" minimatch: "npm:^9.0.4" semver: "npm:^7.6.0" @@ -5215,32 +5215,32 @@ __metadata: ts-api-utils: "npm:^2.1.0" peerDependencies: typescript: ">=4.8.4 <6.0.0" - checksum: 10c0/30344ba5aab687dc50d805c33d4b481cc68c96acdcc679e8a1f46c5b4d8ba1ee562e3f377a4dc1c6418adf5b3fd342b31e5d30e54d0e7b18628ef6b1fb484341 + checksum: 10c0/697b53fd3355619271a7bf543c5880731670b96567da63f554a3c3cd4d746feb8153628ec912c8a2df95e3123472e9a77df43c32fad72946b69ace89c2cf8b7e languageName: node linkType: hard -"@typescript-eslint/utils@npm:8.50.0": - version: 8.50.0 - resolution: "@typescript-eslint/utils@npm:8.50.0" +"@typescript-eslint/utils@npm:8.50.1": + version: 8.50.1 + resolution: "@typescript-eslint/utils@npm:8.50.1" dependencies: "@eslint-community/eslint-utils": "npm:^4.7.0" - "@typescript-eslint/scope-manager": "npm:8.50.0" - "@typescript-eslint/types": "npm:8.50.0" - "@typescript-eslint/typescript-estree": "npm:8.50.0" + "@typescript-eslint/scope-manager": "npm:8.50.1" + "@typescript-eslint/types": "npm:8.50.1" + "@typescript-eslint/typescript-estree": "npm:8.50.1" peerDependencies: eslint: ^8.57.0 || ^9.0.0 typescript: ">=4.8.4 <6.0.0" - checksum: 10c0/4069fbf56717401629c86ea1e36df3a7dc1bbbf5c11ec7b26add2b61cdb9070b48786dc45c8e35a872a0cddced1edef654557e27420b9a666616cead539b3ec0 + checksum: 10c0/66b19a9c8981b0b601af3a477fdcabdd110b0805591f28eefa11b32bbb88518d80b928e49eaa4c40d42ea8d71605bf5cd2ee5e39802022d1daec2800f1b198df languageName: node linkType: hard -"@typescript-eslint/visitor-keys@npm:8.50.0": - version: 8.50.0 - resolution: "@typescript-eslint/visitor-keys@npm:8.50.0" +"@typescript-eslint/visitor-keys@npm:8.50.1": + version: 8.50.1 + resolution: "@typescript-eslint/visitor-keys@npm:8.50.1" dependencies: - "@typescript-eslint/types": "npm:8.50.0" + "@typescript-eslint/types": "npm:8.50.1" eslint-visitor-keys: "npm:^4.2.1" - checksum: 10c0/a13337ecc2042229b922b03882d6691df63053445aa8860f6fcc1da59d04d05f75d4e0ee132551b76d5c5f665e881eb89f327a6f0e83240860f913dff5d745ee + checksum: 10c0/b23839d04b2e5e7964a4006317d75cdc3cf76e56f4c5fde1e0bcd23f3bb78dca910e3dcadca80606f76a09ff9e44b3363ee1e1d6394e3f7479da74a641a8870f languageName: node linkType: hard @@ -12574,7 +12574,7 @@ __metadata: rxjs: "npm:7.8.2" three-spritetext: "npm:1.10.0" typescript: "npm:5.9.3" - typescript-eslint: "npm:8.50.0" + typescript-eslint: "npm:8.50.1" use-analytics: "npm:1.1.0" uuid: "npm:11.1.0" vite: "npm:7.3.0" @@ -15450,18 +15450,18 @@ __metadata: languageName: node linkType: hard -"typescript-eslint@npm:8.50.0": - version: 8.50.0 - resolution: "typescript-eslint@npm:8.50.0" +"typescript-eslint@npm:8.50.1": + version: 8.50.1 + resolution: "typescript-eslint@npm:8.50.1" dependencies: - "@typescript-eslint/eslint-plugin": "npm:8.50.0" - "@typescript-eslint/parser": "npm:8.50.0" - "@typescript-eslint/typescript-estree": "npm:8.50.0" - "@typescript-eslint/utils": "npm:8.50.0" + "@typescript-eslint/eslint-plugin": "npm:8.50.1" + "@typescript-eslint/parser": "npm:8.50.1" + "@typescript-eslint/typescript-estree": "npm:8.50.1" + "@typescript-eslint/utils": "npm:8.50.1" peerDependencies: eslint: ^8.57.0 || ^9.0.0 typescript: ">=4.8.4 <6.0.0" - checksum: 10c0/63f96505fdfc7d0ff0b5d0338c5877a76ef0933ea3a0c90b2a5d73a7f0ee18d778dc673d9345de3bcb6f37ae02fd930301ef13b2e162c4850f08ad89f1c19613 + checksum: 10c0/481095a249c48fa1d3551c50ceb8dcfba22413d6175f586ee802200342478a24b566b49d59e618c835631e4071ba1902d8549dc6467f47adb3079d00394d614f languageName: node linkType: hard diff --git a/opencti-platform/opencti-graphql/package.json b/opencti-platform/opencti-graphql/package.json index 90eeab2fa220..ecba8b2b95d0 100644 --- a/opencti-platform/opencti-graphql/package.json +++ b/opencti-platform/opencti-graphql/package.json @@ -204,7 +204,7 @@ "globals": "16.5.0", "graphql-tag": "2.12.6", "typescript": "5.9.3", - "typescript-eslint": "8.50.0", + "typescript-eslint": "8.50.1", "vitest": "3.2.4" }, "resolutions": { diff --git a/opencti-platform/opencti-graphql/yarn.lock b/opencti-platform/opencti-graphql/yarn.lock index af9624bf5016..9ab82efedd0d 100644 --- a/opencti-platform/opencti-graphql/yarn.lock +++ b/opencti-platform/opencti-graphql/yarn.lock @@ -5159,94 +5159,94 @@ __metadata: languageName: node linkType: hard -"@typescript-eslint/eslint-plugin@npm:8.50.0": - version: 8.50.0 - resolution: "@typescript-eslint/eslint-plugin@npm:8.50.0" +"@typescript-eslint/eslint-plugin@npm:8.50.1": + version: 8.50.1 + resolution: "@typescript-eslint/eslint-plugin@npm:8.50.1" dependencies: "@eslint-community/regexpp": "npm:^4.10.0" - "@typescript-eslint/scope-manager": "npm:8.50.0" - "@typescript-eslint/type-utils": "npm:8.50.0" - "@typescript-eslint/utils": "npm:8.50.0" - "@typescript-eslint/visitor-keys": "npm:8.50.0" + "@typescript-eslint/scope-manager": "npm:8.50.1" + "@typescript-eslint/type-utils": "npm:8.50.1" + "@typescript-eslint/utils": "npm:8.50.1" + "@typescript-eslint/visitor-keys": "npm:8.50.1" ignore: "npm:^7.0.0" natural-compare: "npm:^1.4.0" ts-api-utils: "npm:^2.1.0" peerDependencies: - "@typescript-eslint/parser": ^8.50.0 + "@typescript-eslint/parser": ^8.50.1 eslint: ^8.57.0 || ^9.0.0 typescript: ">=4.8.4 <6.0.0" - checksum: 10c0/032038ee029d1e0984e7c189c3e8173dc4fb909c3ab4d272227e62e6d1872eb9853699c72d46e269c0a084f113ea01fa00d4b61620190276b224fa1b5a5cbd80 + checksum: 10c0/cae56cec414dc5d8347f1ff9fc01ec7b82c7988bcca9597569564b69e1715594e044487805a72ce7a9b4e6e81c3632db92c3d4b6b991874dafa402e1fcb508d5 languageName: node linkType: hard -"@typescript-eslint/parser@npm:8.50.0": - version: 8.50.0 - resolution: "@typescript-eslint/parser@npm:8.50.0" +"@typescript-eslint/parser@npm:8.50.1": + version: 8.50.1 + resolution: "@typescript-eslint/parser@npm:8.50.1" dependencies: - "@typescript-eslint/scope-manager": "npm:8.50.0" - "@typescript-eslint/types": "npm:8.50.0" - "@typescript-eslint/typescript-estree": "npm:8.50.0" - "@typescript-eslint/visitor-keys": "npm:8.50.0" + "@typescript-eslint/scope-manager": "npm:8.50.1" + "@typescript-eslint/types": "npm:8.50.1" + "@typescript-eslint/typescript-estree": "npm:8.50.1" + "@typescript-eslint/visitor-keys": "npm:8.50.1" debug: "npm:^4.3.4" peerDependencies: eslint: ^8.57.0 || ^9.0.0 typescript: ">=4.8.4 <6.0.0" - checksum: 10c0/3bdc9e7b2190285abf7350039056b104725fa70cbd769695717f9948669de4987db7103a7011d33d25d44e9474fe02404746816b8eba72642e17815cb6b0b2e6 + checksum: 10c0/60a2591745650b35cd8d425bb1959ef40d598245481bdfdc2654ed1f7878364c2c442ba70ca7105b650d0df2b6109727dd43214be76045667de0d32a221f3955 languageName: node linkType: hard -"@typescript-eslint/project-service@npm:8.50.0": - version: 8.50.0 - resolution: "@typescript-eslint/project-service@npm:8.50.0" +"@typescript-eslint/project-service@npm:8.50.1": + version: 8.50.1 + resolution: "@typescript-eslint/project-service@npm:8.50.1" dependencies: - "@typescript-eslint/tsconfig-utils": "npm:^8.50.0" - "@typescript-eslint/types": "npm:^8.50.0" + "@typescript-eslint/tsconfig-utils": "npm:^8.50.1" + "@typescript-eslint/types": "npm:^8.50.1" debug: "npm:^4.3.4" peerDependencies: typescript: ">=4.8.4 <6.0.0" - checksum: 10c0/54fdf4c8540eb8e592ab4818345935300bf5776621274cdc8bb942e72e84a4d2566b047b77218f6c851de26eab759c45153a39557ed2c2d1054d180d587d9780 + checksum: 10c0/50fee0882188c2d704deddfb39f5283618adf7e5f72418143e9f69a8f3771233d55a3e0fc2673fa09c62e230ec53e500f95c0f1ed331ffac5f6a7f8e7b7a2e8c languageName: node linkType: hard -"@typescript-eslint/scope-manager@npm:8.50.0": - version: 8.50.0 - resolution: "@typescript-eslint/scope-manager@npm:8.50.0" +"@typescript-eslint/scope-manager@npm:8.50.1": + version: 8.50.1 + resolution: "@typescript-eslint/scope-manager@npm:8.50.1" dependencies: - "@typescript-eslint/types": "npm:8.50.0" - "@typescript-eslint/visitor-keys": "npm:8.50.0" - checksum: 10c0/62a374aaa0bf7d185be43a4d7dd420d7135ab8f13f5cb4e602e16fdf712f0e00e6ab3fc8a31321e19922d27b867579b0b08c4040b23d528853f4b73e9ebcff3b + "@typescript-eslint/types": "npm:8.50.1" + "@typescript-eslint/visitor-keys": "npm:8.50.1" + checksum: 10c0/ef0df092745f5d4e3684a3d770dc47735ab3195456de4ac5825931aeed1857a7e8d7cec14cc9c78c5ed049b3d83b0f8ac43b9463c5032ba548558a06bebb5539 languageName: node linkType: hard -"@typescript-eslint/tsconfig-utils@npm:8.50.0, @typescript-eslint/tsconfig-utils@npm:^8.50.0": - version: 8.50.0 - resolution: "@typescript-eslint/tsconfig-utils@npm:8.50.0" +"@typescript-eslint/tsconfig-utils@npm:8.50.1, @typescript-eslint/tsconfig-utils@npm:^8.50.1": + version: 8.50.1 + resolution: "@typescript-eslint/tsconfig-utils@npm:8.50.1" peerDependencies: typescript: ">=4.8.4 <6.0.0" - checksum: 10c0/5398d26e4a7bec866cc783f5f329a4fed1bc07cd4d21c5c32929a7524b1ebf8ae8e15ca7a035d1177630d86b614ecd3243d63289228bbe292526dbcbf9fae430 + checksum: 10c0/6a1ffb0cd2d9e820ed0c7555a43ebb21438ca80f26c9632e0753bd09e764d9b8e9a352215e4ae60f6d570ab1e77751c9460a00515648b9a2f13f56c56a068a94 languageName: node linkType: hard -"@typescript-eslint/type-utils@npm:8.50.0": - version: 8.50.0 - resolution: "@typescript-eslint/type-utils@npm:8.50.0" +"@typescript-eslint/type-utils@npm:8.50.1": + version: 8.50.1 + resolution: "@typescript-eslint/type-utils@npm:8.50.1" dependencies: - "@typescript-eslint/types": "npm:8.50.0" - "@typescript-eslint/typescript-estree": "npm:8.50.0" - "@typescript-eslint/utils": "npm:8.50.0" + "@typescript-eslint/types": "npm:8.50.1" + "@typescript-eslint/typescript-estree": "npm:8.50.1" + "@typescript-eslint/utils": "npm:8.50.1" debug: "npm:^4.3.4" ts-api-utils: "npm:^2.1.0" peerDependencies: eslint: ^8.57.0 || ^9.0.0 typescript: ">=4.8.4 <6.0.0" - checksum: 10c0/7ebd9a1ebd0cbb6eca9864439f80c2432545bd3ac38dee706be0004c78a26a9908003aa4f0825c0745f4fa1356ffacc0848dd230eae22a6516a02710ab645157 + checksum: 10c0/e4bfd3dd2459e936f7b6d9ee4b60fdedbf4b8f6b3d832e11d3cb1b58c1ce6da098880daafe3b65b2d33e2f79aba0e75c4b6eafdfa2a66c6e00a9ad3132b8e90d languageName: node linkType: hard -"@typescript-eslint/types@npm:8.50.0, @typescript-eslint/types@npm:^8.50.0": - version: 8.50.0 - resolution: "@typescript-eslint/types@npm:8.50.0" - checksum: 10c0/15ec0d75deb331c5ccda726ad95d7f2801fde0f5edfe70425bbdede9e3c9e93b18e7890c9bc42f86ebd65221ebce75e6cc536a65cb1fbbdb0763df22ac392c7a +"@typescript-eslint/types@npm:8.50.1, @typescript-eslint/types@npm:^8.50.1": + version: 8.50.1 + resolution: "@typescript-eslint/types@npm:8.50.1" + checksum: 10c0/04e3c296d81293e370578762be6736fccd1581476f9d534938d42fe93968571fcaf26d7d8c3de52ed63a5af2c0b2da922b8ee2011fa5fb9fb401fc7f0916367a languageName: node linkType: hard @@ -5257,14 +5257,14 @@ __metadata: languageName: node linkType: hard -"@typescript-eslint/typescript-estree@npm:8.50.0": - version: 8.50.0 - resolution: "@typescript-eslint/typescript-estree@npm:8.50.0" +"@typescript-eslint/typescript-estree@npm:8.50.1": + version: 8.50.1 + resolution: "@typescript-eslint/typescript-estree@npm:8.50.1" dependencies: - "@typescript-eslint/project-service": "npm:8.50.0" - "@typescript-eslint/tsconfig-utils": "npm:8.50.0" - "@typescript-eslint/types": "npm:8.50.0" - "@typescript-eslint/visitor-keys": "npm:8.50.0" + "@typescript-eslint/project-service": "npm:8.50.1" + "@typescript-eslint/tsconfig-utils": "npm:8.50.1" + "@typescript-eslint/types": "npm:8.50.1" + "@typescript-eslint/visitor-keys": "npm:8.50.1" debug: "npm:^4.3.4" minimatch: "npm:^9.0.4" semver: "npm:^7.6.0" @@ -5272,32 +5272,32 @@ __metadata: ts-api-utils: "npm:^2.1.0" peerDependencies: typescript: ">=4.8.4 <6.0.0" - checksum: 10c0/30344ba5aab687dc50d805c33d4b481cc68c96acdcc679e8a1f46c5b4d8ba1ee562e3f377a4dc1c6418adf5b3fd342b31e5d30e54d0e7b18628ef6b1fb484341 + checksum: 10c0/697b53fd3355619271a7bf543c5880731670b96567da63f554a3c3cd4d746feb8153628ec912c8a2df95e3123472e9a77df43c32fad72946b69ace89c2cf8b7e languageName: node linkType: hard -"@typescript-eslint/utils@npm:8.50.0": - version: 8.50.0 - resolution: "@typescript-eslint/utils@npm:8.50.0" +"@typescript-eslint/utils@npm:8.50.1": + version: 8.50.1 + resolution: "@typescript-eslint/utils@npm:8.50.1" dependencies: "@eslint-community/eslint-utils": "npm:^4.7.0" - "@typescript-eslint/scope-manager": "npm:8.50.0" - "@typescript-eslint/types": "npm:8.50.0" - "@typescript-eslint/typescript-estree": "npm:8.50.0" + "@typescript-eslint/scope-manager": "npm:8.50.1" + "@typescript-eslint/types": "npm:8.50.1" + "@typescript-eslint/typescript-estree": "npm:8.50.1" peerDependencies: eslint: ^8.57.0 || ^9.0.0 typescript: ">=4.8.4 <6.0.0" - checksum: 10c0/4069fbf56717401629c86ea1e36df3a7dc1bbbf5c11ec7b26add2b61cdb9070b48786dc45c8e35a872a0cddced1edef654557e27420b9a666616cead539b3ec0 + checksum: 10c0/66b19a9c8981b0b601af3a477fdcabdd110b0805591f28eefa11b32bbb88518d80b928e49eaa4c40d42ea8d71605bf5cd2ee5e39802022d1daec2800f1b198df languageName: node linkType: hard -"@typescript-eslint/visitor-keys@npm:8.50.0": - version: 8.50.0 - resolution: "@typescript-eslint/visitor-keys@npm:8.50.0" +"@typescript-eslint/visitor-keys@npm:8.50.1": + version: 8.50.1 + resolution: "@typescript-eslint/visitor-keys@npm:8.50.1" dependencies: - "@typescript-eslint/types": "npm:8.50.0" + "@typescript-eslint/types": "npm:8.50.1" eslint-visitor-keys: "npm:^4.2.1" - checksum: 10c0/a13337ecc2042229b922b03882d6691df63053445aa8860f6fcc1da59d04d05f75d4e0ee132551b76d5c5f665e881eb89f327a6f0e83240860f913dff5d745ee + checksum: 10c0/b23839d04b2e5e7964a4006317d75cdc3cf76e56f4c5fde1e0bcd23f3bb78dca910e3dcadca80606f76a09ff9e44b3363ee1e1d6394e3f7479da74a641a8870f languageName: node linkType: hard @@ -11056,7 +11056,7 @@ __metadata: tough-cookie: "npm:6.0.0" turndown: "npm:7.2.2" typescript: "npm:5.9.3" - typescript-eslint: "npm:8.50.0" + typescript-eslint: "npm:8.50.1" unzipper: "npm:0.12.3" uuid: "npm:11.1.0" uuid-time: "npm:1.0.0" @@ -13210,18 +13210,18 @@ __metadata: languageName: node linkType: hard -"typescript-eslint@npm:8.50.0": - version: 8.50.0 - resolution: "typescript-eslint@npm:8.50.0" +"typescript-eslint@npm:8.50.1": + version: 8.50.1 + resolution: "typescript-eslint@npm:8.50.1" dependencies: - "@typescript-eslint/eslint-plugin": "npm:8.50.0" - "@typescript-eslint/parser": "npm:8.50.0" - "@typescript-eslint/typescript-estree": "npm:8.50.0" - "@typescript-eslint/utils": "npm:8.50.0" + "@typescript-eslint/eslint-plugin": "npm:8.50.1" + "@typescript-eslint/parser": "npm:8.50.1" + "@typescript-eslint/typescript-estree": "npm:8.50.1" + "@typescript-eslint/utils": "npm:8.50.1" peerDependencies: eslint: ^8.57.0 || ^9.0.0 typescript: ">=4.8.4 <6.0.0" - checksum: 10c0/63f96505fdfc7d0ff0b5d0338c5877a76ef0933ea3a0c90b2a5d73a7f0ee18d778dc673d9345de3bcb6f37ae02fd930301ef13b2e162c4850f08ad89f1c19613 + checksum: 10c0/481095a249c48fa1d3551c50ceb8dcfba22413d6175f586ee802200342478a24b566b49d59e618c835631e4071ba1902d8549dc6467f47adb3079d00394d614f languageName: node linkType: hard From f21fcb9eaa6818cf671b68e65f1731af83c4435a Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 30 Dec 2025 19:07:51 +0100 Subject: [PATCH 015/126] [deps] Update GitHub Artifact Actions (major) (#13704) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/ci-docker-build.yml | 4 ++-- .github/workflows/ci-test-api.yml | 12 ++++++------ .github/workflows/ci-test-client-python.yml | 2 +- .github/workflows/ci-test-frontend.yml | 2 +- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/.github/workflows/ci-docker-build.yml b/.github/workflows/ci-docker-build.yml index bc9a2ac4a4ca..36c62ec50869 100644 --- a/.github/workflows/ci-docker-build.yml +++ b/.github/workflows/ci-docker-build.yml @@ -83,7 +83,7 @@ jobs: - name: Upload opencti docker image artifact if: ${{ inputs.publish_to_registry == 'false' }} - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v6 with: name: docker-image-opencti-platform path: /tmp/opencti-platform.tar @@ -135,7 +135,7 @@ jobs: labels: ${{ steps.meta.outputs.labels }} - name: Upload artifact - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v6 with: name: docker-image-opencti-worker path: /tmp/opencti-worker.tar diff --git a/.github/workflows/ci-test-api.yml b/.github/workflows/ci-test-api.yml index d1544a0f9bd8..ba10bae5b791 100644 --- a/.github/workflows/ci-test-api.yml +++ b/.github/workflows/ci-test-api.yml @@ -28,13 +28,13 @@ jobs: - name: Download image platform continue-on-error: true - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v7 with: name: docker-image-opencti-platform path: /tmp - name: Download image worker - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v7 with: name: docker-image-opencti-worker path: /tmp @@ -87,7 +87,7 @@ jobs: ' - name: Upload backend test result - uses: actions/upload-artifact@v5 + uses: actions/upload-artifact@v6 if: always() continue-on-error: true with: @@ -150,13 +150,13 @@ jobs: - name: Download image platform continue-on-error: true - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v7 with: name: docker-image-opencti-platform path: /tmp - name: Download image worker - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v7 with: name: docker-image-opencti-worker path: /tmp @@ -225,7 +225,7 @@ jobs: - name: Download image platform continue-on-error: true - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v7 with: name: docker-image-opencti-platform path: /tmp diff --git a/.github/workflows/ci-test-client-python.yml b/.github/workflows/ci-test-client-python.yml index f591da03df4b..bfc7dc882c44 100644 --- a/.github/workflows/ci-test-client-python.yml +++ b/.github/workflows/ci-test-client-python.yml @@ -39,7 +39,7 @@ jobs: - name: Download image platform continue-on-error: true - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v7 with: name: docker-image-opencti-platform path: /tmp diff --git a/.github/workflows/ci-test-frontend.yml b/.github/workflows/ci-test-frontend.yml index 6534f613b4b7..747811443417 100644 --- a/.github/workflows/ci-test-frontend.yml +++ b/.github/workflows/ci-test-frontend.yml @@ -163,7 +163,7 @@ jobs: ' - name: Upload front e2e test results - uses: actions/upload-artifact@v5 + uses: actions/upload-artifact@v6 if: always() continue-on-error: true with: From d5f983e67b95e7ca17777cff63fe9e458667ccff Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 30 Dec 2025 21:42:29 +0100 Subject: [PATCH 016/126] [deps] Update dependency html-react-parser to v5.2.11 (#13813) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- opencti-platform/opencti-front/package.json | 2 +- opencti-platform/opencti-front/yarn.lock | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/opencti-platform/opencti-front/package.json b/opencti-platform/opencti-front/package.json index b9eca95014bf..7efcf3b4b437 100644 --- a/opencti-platform/opencti-front/package.json +++ b/opencti-platform/opencti-front/package.json @@ -71,7 +71,7 @@ "graphiql": "4.1.2", "graphql": "16.12.0", "graphql-ws": "5.16.2", - "html-react-parser": "5.2.10", + "html-react-parser": "5.2.11", "html-to-image": "1.11.13", "html-to-pdfmake": "2.5.32", "invert-color": "2.0.0", diff --git a/opencti-platform/opencti-front/yarn.lock b/opencti-platform/opencti-front/yarn.lock index 8600214fef86..7767529e7f6d 100644 --- a/opencti-platform/opencti-front/yarn.lock +++ b/opencti-platform/opencti-front/yarn.lock @@ -9620,9 +9620,9 @@ __metadata: languageName: node linkType: hard -"html-react-parser@npm:5.2.10": - version: 5.2.10 - resolution: "html-react-parser@npm:5.2.10" +"html-react-parser@npm:5.2.11": + version: 5.2.11 + resolution: "html-react-parser@npm:5.2.11" dependencies: domhandler: "npm:5.0.3" html-dom-parser: "npm:5.1.2" @@ -9634,7 +9634,7 @@ __metadata: peerDependenciesMeta: "@types/react": optional: true - checksum: 10c0/af93dc554eb47633a2a8d1a5c9276768730606fd30f7355de30e02ad6ef16bb269b0e4b4ae7190ba1a056da43acf2c17cd4fae0f3981486d117f6b3646245974 + checksum: 10c0/9173df6db4523aab142e1e4c4b55bf9ae6a18a6581d98a2ce9d92af0d3a5dccebb39f26719cf3b5d35d7cc21d49fc774508dda54146771ac7e67e9a1fa252d8a languageName: node linkType: hard @@ -12514,7 +12514,7 @@ __metadata: graphiql: "npm:4.1.2" graphql: "npm:16.12.0" graphql-ws: "npm:5.16.2" - html-react-parser: "npm:5.2.10" + html-react-parser: "npm:5.2.11" html-to-image: "npm:1.11.13" html-to-pdfmake: "npm:2.5.32" http-proxy-middleware: "npm:3.0.5" From c6105c3a45d034cf9d646d9439e8f32b437f20b1 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 30 Dec 2025 21:43:36 +0100 Subject: [PATCH 017/126] [deps] Update dependency pdfmake to v0.2.21 (#13835) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- opencti-platform/opencti-front/package.json | 2 +- opencti-platform/opencti-front/yarn.lock | 37 +++++++++++++-------- 2 files changed, 24 insertions(+), 15 deletions(-) diff --git a/opencti-platform/opencti-front/package.json b/opencti-platform/opencti-front/package.json index 7efcf3b4b437..7b175f71c8bf 100644 --- a/opencti-platform/opencti-front/package.json +++ b/opencti-platform/opencti-front/package.json @@ -85,7 +85,7 @@ "mdi-material-ui": "7.9.4", "moment": "2.30.1", "moment-timezone": "0.6.0", - "pdfmake": "0.2.20", + "pdfmake": "0.2.21", "prop-types": "15.8.1", "qrcode": "1.5.4", "ramda": "0.32.0", diff --git a/opencti-platform/opencti-front/yarn.lock b/opencti-platform/opencti-front/yarn.lock index 7767529e7f6d..23f32dea1fd4 100644 --- a/opencti-platform/opencti-front/yarn.lock +++ b/opencti-platform/opencti-front/yarn.lock @@ -9812,7 +9812,7 @@ __metadata: languageName: node linkType: hard -"iconv-lite@npm:0.6.3, iconv-lite@npm:^0.6.2, iconv-lite@npm:^0.6.3": +"iconv-lite@npm:0.6.3, iconv-lite@npm:^0.6.2": version: 0.6.3 resolution: "iconv-lite@npm:0.6.3" dependencies: @@ -9830,6 +9830,15 @@ __metadata: languageName: node linkType: hard +"iconv-lite@npm:^0.7.1": + version: 0.7.1 + resolution: "iconv-lite@npm:0.7.1" + dependencies: + safer-buffer: "npm:>= 2.1.2 < 3.0.0" + checksum: 10c0/f5c9e2bddd7101a71b07a381ace44ebdc65ca76a10be0e9e64d372b511132acc4ee41b830962f438840d492cd6f9e08c237289f760d6a7fed754e61cffcb6757 + languageName: node + linkType: hard + "ieee754@npm:^1.2.1": version: 1.2.1 resolution: "ieee754@npm:1.2.1" @@ -12533,7 +12542,7 @@ __metadata: moment: "npm:2.30.1" moment-timezone: "npm:0.6.0" monocart-reporter: "npm:2.9.23" - pdfmake: "npm:0.2.20" + pdfmake: "npm:0.2.21" prop-types: "npm:15.8.1" qrcode: "npm:1.5.4" ramda: "npm:0.32.0" @@ -12828,15 +12837,15 @@ __metadata: languageName: node linkType: hard -"pdfmake@npm:0.2.20": - version: 0.2.20 - resolution: "pdfmake@npm:0.2.20" +"pdfmake@npm:0.2.21": + version: 0.2.21 + resolution: "pdfmake@npm:0.2.21" dependencies: "@foliojs-fork/linebreak": "npm:^1.1.2" "@foliojs-fork/pdfkit": "npm:^0.15.3" - iconv-lite: "npm:^0.6.3" - xmldoc: "npm:^2.0.1" - checksum: 10c0/6464d408aa7c214561753f01701ffa387e8ba2784e6000977fc1b7e360958d2a256989451f72a73bf9d4d313a0b77d0d11378bf8451a65edfa95c56c82eac043 + iconv-lite: "npm:^0.7.1" + xmldoc: "npm:^2.0.3" + checksum: 10c0/72385405832823f72adc82f7956c34d8ad6a8821ae0c48145346508c924a64979008fb88ef3d2686fc67ca18da93e0ac2823873f3e4d7f8f0364d9d487a5d5e2 languageName: node linkType: hard @@ -14266,7 +14275,7 @@ __metadata: languageName: node linkType: hard -"sax@npm:^1.2.4, sax@npm:^1.4.1": +"sax@npm:^1.4.1, sax@npm:^1.4.3": version: 1.4.3 resolution: "sax@npm:1.4.3" checksum: 10c0/45bba07561d93f184a8686e1a543418ced8c844b994fbe45cc49d5cd2fc8ac7ec949dae38565e35e388ad0cca2b75997a29b6857c927bf6553da3f80ed0e4e62 @@ -16494,12 +16503,12 @@ __metadata: languageName: node linkType: hard -"xmldoc@npm:^2.0.1": - version: 2.0.2 - resolution: "xmldoc@npm:2.0.2" +"xmldoc@npm:^2.0.3": + version: 2.0.3 + resolution: "xmldoc@npm:2.0.3" dependencies: - sax: "npm:^1.2.4" - checksum: 10c0/dbf42e50a136e99be811238dd6b5bf7a0ff2af539b8aa0253f4932c9cc26cf8a20122e287fc0f12553d8cd2dc726a4ecac90cd1922654b788ac7bb1dc3e8cfa8 + sax: "npm:^1.4.3" + checksum: 10c0/8b01fc2b49f7dfa3fc0506d7ae4bab41476e15836114a1ab3794a5dc925498e2f4c248391479f57426e166a718082e6628c7112a89e6bf015ed780c9772f4406 languageName: node linkType: hard From 3537eced7b4ab6fe9dd70b087e25a89f56add5b3 Mon Sep 17 00:00:00 2001 From: hervyt Date: Wed, 31 Dec 2025 14:39:09 +0100 Subject: [PATCH 018/126] [frontend/backend] - (TaxiiFeeds) Add service account #13848 --- ...g.tsx => IngestionEditionUserHandling.tsx} | 72 ++++------ .../data/ingestionCsv/IngestionCsvEdition.tsx | 21 ++- .../ingestionTaxii/IngestionTaxiiCreation.jsx | 13 +- .../ingestionTaxii/IngestionTaxiiEdition.tsx | 35 ++++- .../src/schema/relay.schema.graphql | 10 +- .../opencti-graphql/src/generated/graphql.ts | 19 ++- .../ingestion/ingestion-taxii-domain.ts | 22 ++- .../ingestion/ingestion-taxii-resolver.ts | 13 +- .../modules/ingestion/ingestion-taxii.graphql | 10 +- .../02-resolvers/ingestion-taxii-test.ts | 126 ++++++++++++++++-- .../04-manager/ingestionManager-test.ts | 52 ++++---- 11 files changed, 293 insertions(+), 100 deletions(-) rename opencti-platform/opencti-front/src/private/components/data/{ingestionCsv/IngestionCsvEditionUserHandling.tsx => IngestionEditionUserHandling.tsx} (65%) diff --git a/opencti-platform/opencti-front/src/private/components/data/ingestionCsv/IngestionCsvEditionUserHandling.tsx b/opencti-platform/opencti-front/src/private/components/data/IngestionEditionUserHandling.tsx similarity index 65% rename from opencti-platform/opencti-front/src/private/components/data/ingestionCsv/IngestionCsvEditionUserHandling.tsx rename to opencti-platform/opencti-front/src/private/components/data/IngestionEditionUserHandling.tsx index a5b3324da9c6..d8c81737dd66 100644 --- a/opencti-platform/opencti-front/src/private/components/data/ingestionCsv/IngestionCsvEditionUserHandling.tsx +++ b/opencti-platform/opencti-front/src/private/components/data/IngestionEditionUserHandling.tsx @@ -11,16 +11,17 @@ import { graphql } from 'react-relay'; import { FormikConfig } from 'formik/dist/types'; import { Formik } from 'formik'; import * as Yup from 'yup'; -import { IngestionCsvEditionUserHandlingQuery$data } from '@components/data/ingestionCsv/__generated__/IngestionCsvEditionUserHandlingQuery.graphql'; import Alert from '@mui/material/Alert'; -import { fetchQuery } from '../../../../relay/environment'; -import { useFormatter } from '../../../../components/i18n'; -import { fieldSpacingContainerStyle } from '../../../../utils/field'; -import Transition from '../../../../components/Transition'; -import useApiMutation from '../../../../utils/hooks/useApiMutation'; - -export const ingestionCsvEditionUserHandlingQuery = graphql` - query IngestionCsvEditionUserHandlingQuery( +import { fetchQuery } from '../../../relay/environment'; +import { useFormatter } from '../../../components/i18n'; +import { fieldSpacingContainerStyle } from '../../../utils/field'; +import Transition from '../../../components/Transition'; +import useApiMutation from '../../../utils/hooks/useApiMutation'; +import { IngestionEditionUserHandlingQuery$data } from '@components/data/__generated__/IngestionEditionUserHandlingQuery.graphql'; +import { GraphQLTaggedNode } from 'relay-runtime/lib/query/RelayModernGraphQLTag'; + +export const ingestionEditionUserHandlingQuery = graphql` + query IngestionEditionUserHandlingQuery( $name: String! ) { userAlreadyExists( @@ -29,72 +30,53 @@ export const ingestionCsvEditionUserHandlingQuery = graphql` } `; -export const ingestionCsvEditionUserHandlingFragment = graphql` - fragment IngestionCsvEditionUserHandlingFragment_ingestionCsv on IngestionCsv { - id - name - user { - id - entity_type - name - } - } -`; -export const ingestionCsvEditionUserHandlingPatch = graphql` - mutation IngestionCsvEditionUserHandlingMutation($id: ID!, $input: IngestionCsvAddAutoUserInput!) { - ingestionCsvAddAutoUser(id: $id, input: $input) { - ...IngestionCsvEditionUserHandlingFragment_ingestionCsv - } - } -`; - -export interface EditionCsvAddAutoUserInput { +export interface IngestionEditionAddAutoUserInput { user_name: string; confidence_level: number; } -interface IngestionCsvEditionUserHandlingProps { +interface IngestionEditionUserHandlingProps { feedName: string; - ingestionCsvDataId: string; + dataId: string; onAutoUserCreated: () => void; + mutation: GraphQLTaggedNode; } -const IngestionCsvEditionUserHandling: FunctionComponent = ({ feedName, ingestionCsvDataId, onAutoUserCreated }) => { +const IngestionEditionUserHandling: FunctionComponent = ({ feedName, dataId, onAutoUserCreated, mutation }) => { const { t_i18n } = useFormatter(); const [openDialog, setOpenDialog] = useState(false); - const [commitUpdate] = useApiMutation(ingestionCsvEditionUserHandlingPatch); - const ingestionCsvCreationValidation = () => Yup.object().shape({ + const [commitUpdate] = useApiMutation(mutation); + const validationSchema = () => Yup.object().shape({ user_name: Yup.string(), - confidence_level: Yup.string(), + confidence_level: Yup.number(), }); - const initialValues: EditionCsvAddAutoUserInput = { + const initialValues: IngestionEditionAddAutoUserInput = { user_name: `[F] ${feedName}`, confidence_level: 50, }; - const onSubmit: FormikConfig['onSubmit'] = async ( + const onSubmit: FormikConfig['onSubmit'] = async ( values, { setSubmitting, setFieldError }, ) => { - const existingUsers = await fetchQuery(ingestionCsvEditionUserHandlingQuery, { + const existingUsers = await fetchQuery(ingestionEditionUserHandlingQuery, { name: values.user_name, }) .toPromise(); - if ((existingUsers as IngestionCsvEditionUserHandlingQuery$data)?.userAlreadyExists) { + if ((existingUsers as IngestionEditionUserHandlingQuery$data)?.userAlreadyExists) { setSubmitting(false); setFieldError('user_name', t_i18n('This service account already exists. Change the feed\'s name to change the automatically created service account name')); return; } - // send data to backend commitUpdate({ variables: { - id: ingestionCsvDataId, + id: dataId, input: { user_name: values.user_name, - confidence_level: values.confidence_level, + confidence_level: Number(values.confidence_level), }, }, onCompleted: () => { @@ -120,9 +102,9 @@ const IngestionCsvEditionUserHandling: FunctionComponent - + initialValues={initialValues} - validationSchema={ingestionCsvCreationValidation} + validationSchema={validationSchema} onSubmit={onSubmit} > {({ submitForm, resetForm }) => ( @@ -187,4 +169,4 @@ const IngestionCsvEditionUserHandling: FunctionComponent { return { @@ -512,11 +526,12 @@ const IngestionCsvEdition: FunctionComponent = ({ /> {ingestionCsvData.user?.name === 'SYSTEM' && ( - setFieldValue('user_id', `[F] ${values.name}`)} - ingestionCsvDataId={ingestionCsvData.id} + dataId={ingestionCsvData.id} + mutation={ingestionCsvEditionUserHandlingPatch} /> ) } diff --git a/opencti-platform/opencti-front/src/private/components/data/ingestionTaxii/IngestionTaxiiCreation.jsx b/opencti-platform/opencti-front/src/private/components/data/ingestionTaxii/IngestionTaxiiCreation.jsx index bd5a11b5bb38..0e4eb8466eaf 100644 --- a/opencti-platform/opencti-front/src/private/components/data/ingestionTaxii/IngestionTaxiiCreation.jsx +++ b/opencti-platform/opencti-front/src/private/components/data/ingestionTaxii/IngestionTaxiiCreation.jsx @@ -12,7 +12,6 @@ import Drawer from '../../common/drawer/Drawer'; import inject18n from '../../../../components/i18n'; import { commitMutation } from '../../../../relay/environment'; import TextField from '../../../../components/TextField'; -import CreatorField from '../../common/form/CreatorField'; import { fieldSpacingContainerStyle } from '../../../../utils/field'; import { insertNode } from '../../../../utils/store'; import SelectField from '../../../../components/fields/SelectField'; @@ -20,6 +19,7 @@ import DateTimePickerField from '../../../../components/DateTimePickerField'; import SwitchField from '../../../../components/fields/SwitchField'; import PasswordTextField from '../../../../components/PasswordTextField'; import CreateEntityControlledDial from '../../../../components/CreateEntityControlledDial'; +import IngestionCreationUserHandling from '@components/data/IngestionCreationUserHandling'; const styles = (theme) => ({ buttons: { @@ -82,6 +82,8 @@ const IngestionTaxiiCreation = (props) => { authentication_value: authentifcationValueResolved, added_after_start: values.added_after_start, user_id: values.user_id?.value, + automatic_user: values.automatic_user ?? true, + ...((values.automatic_user !== false) && { confidence_level: Number(values.confidence_level) }), confidence_to_score: values.confidence_to_score, }; commitMutation({ @@ -122,6 +124,7 @@ const IngestionTaxiiCreation = (props) => { authentication_type: 'none', authentication_value: '', user_id: '', + automatic_user: true, username: '', password: '', cert: '', @@ -245,11 +248,9 @@ const IngestionTaxiiCreation = (props) => { /> )} - { return { ...{ @@ -46,6 +61,7 @@ export const initIngestionValue = (ingestionTaxiiData: IngestionTaxiiEditionFrag user_id: convertUser(ingestionTaxiiData, 'user'), added_after_start: ingestionTaxiiData.added_after_start, confidence_to_score: ingestionTaxiiData.confidence_to_score, + automatic_user: true, }, ...(ingestionTaxiiData.authentication_type === BEARER_AUTH ? { @@ -401,11 +417,22 @@ const IngestionTaxiiEdition: FunctionComponent = ({ )} + {ingestionTaxiiData.user?.name === 'SYSTEM' + && ( + setFieldValue('user_id', `[F] ${values.name}`)} + dataId={ingestionTaxiiData.id} + mutation={ingestionTaxiiEditionUserHandlingPatch} + /> + ) + } ; authentication_type: IngestionAuthType; authentication_value?: InputMaybe; + automatic_user?: InputMaybe; collection: Scalars['String']['input']; + confidence_level?: InputMaybe; confidence_to_score?: InputMaybe; description?: InputMaybe; ingestion_running?: InputMaybe; name: Scalars['String']['input']; uri: Scalars['String']['input']; - user_id?: InputMaybe; + user_id: Scalars['String']['input']; version: TaxiiVersion; }; @@ -15162,6 +15169,7 @@ export type Mutation = { ingestionRssDelete?: Maybe; ingestionRssFieldPatch?: Maybe; ingestionTaxiiAdd?: Maybe; + ingestionTaxiiAddAutoUser?: Maybe; ingestionTaxiiCollectionAdd?: Maybe; ingestionTaxiiCollectionDelete?: Maybe; ingestionTaxiiCollectionFieldPatch?: Maybe; @@ -16452,6 +16460,12 @@ export type MutationIngestionTaxiiAddArgs = { }; +export type MutationIngestionTaxiiAddAutoUserArgs = { + id: Scalars['ID']['input']; + input: IngestionTaxiiAddAutoUserInput; +}; + + export type MutationIngestionTaxiiCollectionAddArgs = { input: IngestionTaxiiCollectionAddInput; }; @@ -36351,6 +36365,7 @@ export type ResolversTypes = ResolversObject<{ IngestionRssEdge: ResolverTypeWrapper & { node: ResolversTypes['IngestionRss'] }>; IngestionRssOrdering: IngestionRssOrdering; IngestionTaxii: ResolverTypeWrapper; + IngestionTaxiiAddAutoUserInput: IngestionTaxiiAddAutoUserInput; IngestionTaxiiAddInput: IngestionTaxiiAddInput; IngestionTaxiiCollection: ResolverTypeWrapper; IngestionTaxiiCollectionAddInput: IngestionTaxiiCollectionAddInput; @@ -37316,6 +37331,7 @@ export type ResolversParentTypes = ResolversObject<{ IngestionRssConnection: Omit & { edges: Array }; IngestionRssEdge: Omit & { node: ResolversParentTypes['IngestionRss'] }; IngestionTaxii: BasicStoreEntityIngestionTaxii; + IngestionTaxiiAddAutoUserInput: IngestionTaxiiAddAutoUserInput; IngestionTaxiiAddInput: IngestionTaxiiAddInput; IngestionTaxiiCollection: BasicStoreEntityIngestionTaxiiCollection; IngestionTaxiiCollectionAddInput: IngestionTaxiiCollectionAddInput; @@ -43155,6 +43171,7 @@ export type MutationResolvers, ParentType, ContextType, RequireFields>; ingestionRssFieldPatch?: Resolver, ParentType, ContextType, RequireFields>; ingestionTaxiiAdd?: Resolver, ParentType, ContextType, RequireFields>; + ingestionTaxiiAddAutoUser?: Resolver, ParentType, ContextType, RequireFields>; ingestionTaxiiCollectionAdd?: Resolver, ParentType, ContextType, RequireFields>; ingestionTaxiiCollectionDelete?: Resolver, ParentType, ContextType, RequireFields>; ingestionTaxiiCollectionFieldPatch?: Resolver, ParentType, ContextType, RequireFields>; diff --git a/opencti-platform/opencti-graphql/src/modules/ingestion/ingestion-taxii-domain.ts b/opencti-platform/opencti-graphql/src/modules/ingestion/ingestion-taxii-domain.ts index c1892f891ff2..fc309f0071e2 100644 --- a/opencti-platform/opencti-graphql/src/modules/ingestion/ingestion-taxii-domain.ts +++ b/opencti-platform/opencti-graphql/src/modules/ingestion/ingestion-taxii-domain.ts @@ -6,9 +6,10 @@ import { publishUserAction } from '../../listener/UserActionListener'; import { notify } from '../../database/redis'; import { ABSTRACT_INTERNAL_OBJECT } from '../../schema/general'; import type { AuthContext, AuthUser } from '../../types/user'; -import { type EditInput, type IngestionTaxiiAddInput } from '../../generated/graphql'; +import { type EditInput, type IngestionTaxiiAddAutoUserInput, type IngestionTaxiiAddInput } from '../../generated/graphql'; import { addAuthenticationCredentials, removeAuthenticationCredentials, verifyIngestionAuthenticationContent } from './ingestion-common'; import { registerConnectorForIngestion, unregisterConnectorForIngestion } from '../../domain/connector'; +import { createOnTheFlyUser } from '../user/user-domain'; export const findById = async (context: AuthContext, user: AuthUser, ingestionId: string, removeCredentials = false) => { const taxiiIngestion = await storeLoadById(context, user, ingestionId, ENTITY_TYPE_INGESTION_TAXII); @@ -27,10 +28,20 @@ export const findAllTaxiiIngestion = async (context: AuthContext, user: AuthUser }; export const addIngestion = async (context: AuthContext, user: AuthUser, input: IngestionTaxiiAddInput) => { + if (input.automatic_user) { + const onTheFlyCreatedUser = await createOnTheFlyUser( + context, + user, + { userName: input.user_id, serviceAccount: true, confidenceLevel: input.confidence_level }, + ); + input = { ...input, user_id: onTheFlyCreatedUser.id }; + } if (input.authentication_value) { verifyIngestionAuthenticationContent(input.authentication_type, input.authentication_value); } - const { element, isCreation } = await createEntity(context, user, input, ENTITY_TYPE_INGESTION_TAXII, { complete: true }); + + const { automatic_user: _automatic_user, confidence_level: _confidence_level, ...taxiiFeedToCreate } = input; + const { element, isCreation } = await createEntity(context, user, taxiiFeedToCreate, ENTITY_TYPE_INGESTION_TAXII, { complete: true }); if (isCreation) { await registerConnectorForIngestion(context, { id: element.id, @@ -164,3 +175,10 @@ export const ingestionTaxiiResetState = async (context: AuthContext, user: AuthU }); return notify(BUS_TOPICS[ABSTRACT_INTERNAL_OBJECT].EDIT_TOPIC, ingestionUpdated, user); }; + +export const ingestionTaxiiAddAutoUser = async (context: AuthContext, user: AuthUser, ingestionId: string, input: IngestionTaxiiAddAutoUserInput) => { + const onTheFlyCreatedUser = await createOnTheFlyUser(context, user, + { userName: input.user_name, confidenceLevel: input.confidence_level, serviceAccount: true }); + + return ingestionEditField(context, user, ingestionId, [{ key: 'user_id', value: [onTheFlyCreatedUser.id] }]); +}; diff --git a/opencti-platform/opencti-graphql/src/modules/ingestion/ingestion-taxii-resolver.ts b/opencti-platform/opencti-graphql/src/modules/ingestion/ingestion-taxii-resolver.ts index 33c374b8fdb5..1fb7c46ed913 100644 --- a/opencti-platform/opencti-graphql/src/modules/ingestion/ingestion-taxii-resolver.ts +++ b/opencti-platform/opencti-graphql/src/modules/ingestion/ingestion-taxii-resolver.ts @@ -1,4 +1,12 @@ -import { addIngestion, findTaxiiIngestionPaginated, findById, ingestionDelete, ingestionEditField, ingestionTaxiiResetState } from './ingestion-taxii-domain'; +import { + addIngestion, + findTaxiiIngestionPaginated, + findById, + ingestionDelete, + ingestionEditField, + ingestionTaxiiResetState, + ingestionTaxiiAddAutoUser, +} from './ingestion-taxii-domain'; import type { Resolvers } from '../../generated/graphql'; const ingestionTaxiiResolvers: Resolvers = { @@ -22,6 +30,9 @@ const ingestionTaxiiResolvers: Resolvers = { ingestionTaxiiFieldPatch: (_, { id, input }, context) => { return ingestionEditField(context, context.user, id, input); }, + ingestionTaxiiAddAutoUser: (_, { id, input }, context) => { + return ingestionTaxiiAddAutoUser(context, context.user, id, input); + }, }, }; diff --git a/opencti-platform/opencti-graphql/src/modules/ingestion/ingestion-taxii.graphql b/opencti-platform/opencti-graphql/src/modules/ingestion/ingestion-taxii.graphql index 5a635f83ebdc..32bb97e89789 100644 --- a/opencti-platform/opencti-graphql/src/modules/ingestion/ingestion-taxii.graphql +++ b/opencti-platform/opencti-graphql/src/modules/ingestion/ingestion-taxii.graphql @@ -78,7 +78,14 @@ input IngestionTaxiiAddInput { added_after_start: DateTime ingestion_running: Boolean confidence_to_score: Boolean - user_id: String + user_id: String! + automatic_user: Boolean + confidence_level: Int +} + +input IngestionTaxiiAddAutoUserInput { + user_name: String! + confidence_level: Int! } type Mutation { @@ -86,4 +93,5 @@ type Mutation { ingestionTaxiiDelete(id: ID!): ID @auth(for: [INGESTION_SETINGESTIONS]) ingestionTaxiiResetState(id: ID!): IngestionTaxii @auth(for: [INGESTION_SETINGESTIONS]) ingestionTaxiiFieldPatch(id: ID!, input: [EditInput!]!): IngestionTaxii @auth(for: [INGESTION_SETINGESTIONS]) + ingestionTaxiiAddAutoUser(id: ID!, input: IngestionTaxiiAddAutoUserInput!): IngestionTaxii @auth(for: [INGESTION_SETINGESTIONS]) } diff --git a/opencti-platform/opencti-graphql/tests/03-integration/02-resolvers/ingestion-taxii-test.ts b/opencti-platform/opencti-graphql/tests/03-integration/02-resolvers/ingestion-taxii-test.ts index 8045e40c4c89..9f92fbe03d63 100644 --- a/opencti-platform/opencti-graphql/tests/03-integration/02-resolvers/ingestion-taxii-test.ts +++ b/opencti-platform/opencti-graphql/tests/03-integration/02-resolvers/ingestion-taxii-test.ts @@ -3,13 +3,36 @@ import gql from 'graphql-tag'; import type { GraphQLFormattedError } from 'graphql/error'; import { queryAsAdminWithSuccess } from '../../utils/testQueryHelper'; import { IngestionAuthType, TaxiiVersion } from '../../../src/generated/graphql'; -import { ADMIN_USER, queryAsAdmin, testContext } from '../../utils/testQuery'; +import { ADMIN_USER, adminQuery, queryAsAdmin, testContext } from '../../utils/testQuery'; import { now } from '../../../src/utils/format'; import { findById as findIngestionById, patchTaxiiIngestion } from '../../../src/modules/ingestion/ingestion-taxii-domain'; +import { findById as findUserById } from '../../../src/domain/user'; + +const DELETE_USER_QUERY = gql` + mutation userDelete($id: ID!) { + userEdit(id: $id) { + delete + } + } +`; + +const READ_USER_QUERY = gql` + query user($id: String!) { + user(id: $id) { + id + name + description + user_confidence_level { + max_confidence + } + } + } +`; describe('TAXII ingestion resolver standard behavior', () => { - let createdTaxiiIngesterId: string; - it('should create a TAXII ingester', async () => { + let createdTaxiiIngesterId: string = ''; + + it('should create a TAXII ingester with existing user', async () => { const INGESTER_TO_CREATE = { input: { authentication_type: IngestionAuthType.Basic, @@ -17,8 +40,9 @@ describe('TAXII ingestion resolver standard behavior', () => { name: 'Taxii ingester for integration test', version: TaxiiVersion.V21, collection: 'TaxiCollection', - uri: 'http://taxiserver.invalid' - } + uri: 'http://taxiserver.invalid', + user_id: ADMIN_USER.id, + }, }; const ingesterQueryResult = await queryAsAdminWithSuccess({ query: gql` @@ -30,12 +54,55 @@ describe('TAXII ingestion resolver standard behavior', () => { } }, `, - variables: INGESTER_TO_CREATE + variables: INGESTER_TO_CREATE, }); expect(ingesterQueryResult.data?.ingestionTaxiiAdd.id).toBeDefined(); createdTaxiiIngesterId = ingesterQueryResult.data?.ingestionTaxiiAdd.id; }); + it('should create a TAXII feed with automatic user', async () => { + const INGESTER_TO_CREATE = { + input: { + authentication_type: IngestionAuthType.Basic, + authentication_value: 'username:P@ssw0rd!', + name: 'Taxii ingester for integration test', + version: TaxiiVersion.V21, + collection: 'TaxiCollection', + uri: 'http://taxiserver.invalid', + automatic_user: true, + user_id: '[F] Taxii ingester for integration test', + }, + }; + const ingesterQueryResult = await queryAsAdminWithSuccess({ + query: gql` + mutation createTaxiiIngester($input: IngestionTaxiiAddInput!) { + ingestionTaxiiAdd(input: $input) { + id + entity_type + ingestion_running + user_id + } + }, + `, + variables: INGESTER_TO_CREATE, + }); + expect(ingesterQueryResult.data?.ingestionTaxiiAdd.id).toBeDefined(); + const createdTaxiiIngester = ingesterQueryResult.data?.ingestionTaxiiAdd; + + const userIdCreated = createdTaxiiIngester.user_id; + const createdUser = await findUserById(testContext, ADMIN_USER, userIdCreated); + expect(createdUser.name).toBe('[F] Taxii ingester for integration test'); + // Delete just created user + await adminQuery({ + query: DELETE_USER_QUERY, + variables: { id: createdUser.id }, + }); + // Verify no longer found + const queryResult = await adminQuery({ query: READ_USER_QUERY, variables: { id: createdUser.id } }); + expect(queryResult).not.toBeNull(); + expect(queryResult.data.user).toBeNull(); + }); + it('should edit a TAXII ingester', async () => { const ingesterQueryResult = await queryAsAdminWithSuccess({ query: gql` @@ -47,13 +114,48 @@ describe('TAXII ingestion resolver standard behavior', () => { } } `, - variables: { id: createdTaxiiIngesterId, input: [{ key: 'authentication_value', value: ['username:P@ssw0rd!'] }] } + variables: { id: createdTaxiiIngesterId, input: [{ key: 'authentication_value', value: ['username:P@ssw0rd!'] }] }, }); expect(ingesterQueryResult.data?.ingestionTaxiiFieldPatch.id).toBeDefined(); expect(ingesterQueryResult.data?.ingestionTaxiiFieldPatch.authentication_type).toEqual(IngestionAuthType.Basic); expect(ingesterQueryResult.data?.ingestionTaxiiFieldPatch.authentication_value).toEqual('username:undefined'); }); + + it('should add auto user and update Taxii feed ingester with it', async () => { + const TAXII_FEED_AUTO_USER_UPDATE = { + id: createdTaxiiIngesterId, + input: { + user_name: 'AutoUser', + confidence_level: 86, + }, + }; + const updateTaxiiFeedWithAutoUserResult = await queryAsAdminWithSuccess({ + query: gql` + mutation updateTaxiiFeedWithAutoUser($id: ID!, $input: IngestionTaxiiAddAutoUserInput!) { + ingestionTaxiiAddAutoUser(id: $id, input: $input){ + id + user { + id + name + } + } + } + `, + variables: TAXII_FEED_AUTO_USER_UPDATE, + }); + expect(updateTaxiiFeedWithAutoUserResult?.data?.ingestionTaxiiAddAutoUser?.user?.name).toBe('AutoUser'); + // Delete just created user + await adminQuery({ + query: DELETE_USER_QUERY, + variables: { id: updateTaxiiFeedWithAutoUserResult?.data?.ingestionTaxiiAddAutoUser?.user?.id }, + }); + // Verify no longer found + const queryResult = await adminQuery({ query: READ_USER_QUERY, variables: { id: updateTaxiiFeedWithAutoUserResult?.data?.ingestionTaxiiAddAutoUser?.user?.id } }); + expect(queryResult).not.toBeNull(); + expect(queryResult.data.user).toBeNull(); + }); + it('should reset cursor when a user change the start date', async () => { // shortcut to set a cursor that is defined const state = { current_state_cursor: 'aaaaaaaaaaaaaaaaaaa', last_execution_date: now() }; @@ -70,7 +172,7 @@ describe('TAXII ingestion resolver standard behavior', () => { } } `, - variables: { id: createdTaxiiIngesterId, input: [{ key: 'added_after_start', value: [now()] }] } + variables: { id: createdTaxiiIngesterId, input: [{ key: 'added_after_start', value: [now()] }] }, }); expect(ingesterChangeDateResult.data?.ingestionTaxiiFieldPatch.id).toBeDefined(); @@ -90,11 +192,11 @@ describe('TAXII ingestion resolver standard behavior', () => { } } `, - variables: { id: createdTaxiiIngesterId, input: { key: 'authentication_value', value: ['user:name:P@ssw0rd!'] } } + variables: { id: createdTaxiiIngesterId, input: { key: 'authentication_value', value: ['user:name:P@ssw0rd!'] } }, }); expect(ingesterQueryResult.errors).toBeDefined(); if (ingesterQueryResult.errors) { // above expect is not taken by eslint - const error:GraphQLFormattedError = ingesterQueryResult.errors[0]; + const error: GraphQLFormattedError = ingesterQueryResult.errors[0]; expect(error.message).toContain('Username and password cannot have : character.'); } }); @@ -117,7 +219,7 @@ describe('TAXII ingestion resolver standard behavior', () => { } } `, - variables: { id: createdTaxiiIngesterId } + variables: { id: createdTaxiiIngesterId }, }); expect(ingesterQueryResult.data?.ingestionTaxiiResetState.id).toBeDefined(); expect(ingesterQueryResult.data?.ingestionTaxiiResetState.current_state_cursor).toBeNull(); @@ -130,7 +232,7 @@ describe('TAXII ingestion resolver standard behavior', () => { ingestionTaxiiDelete(id: $id) } `, - variables: { id: createdTaxiiIngesterId } + variables: { id: createdTaxiiIngesterId }, }); expect(ingesterQueryResult.data?.ingestionTaxiiDelete).toEqual(createdTaxiiIngesterId); }); diff --git a/opencti-platform/opencti-graphql/tests/03-integration/04-manager/ingestionManager-test.ts b/opencti-platform/opencti-graphql/tests/03-integration/04-manager/ingestionManager-test.ts index 1b85399f2bd9..380713846e1a 100644 --- a/opencti-platform/opencti-graphql/tests/03-integration/04-manager/ingestionManager-test.ts +++ b/opencti-platform/opencti-graphql/tests/03-integration/04-manager/ingestionManager-test.ts @@ -17,13 +17,14 @@ import { connectorIdFromIngestId, queueDetails } from '../../../src/domain/conne describe('Verify taxii ingestion', () => { it('should Taxii server response with no pagination (no next, no more, no x-taxii-date-added-last)', async () => { // 1. Create ingestion in opencti - const input : IngestionTaxiiAddInput = { + const input: IngestionTaxiiAddInput = { authentication_type: IngestionAuthType.None, collection: 'testcollection', ingestion_running: true, name: 'taxii ingestion with no pagination', uri: 'http://test.invalid', version: TaxiiVersion.V21, + user_id: ADMIN_USER.id, }; const ingestionNotPagination = await addTaxiiIngestion(testContext, ADMIN_USER, input); expect(ingestionNotPagination.id).toBeDefined(); @@ -45,9 +46,9 @@ describe('Verify taxii ingestion', () => { revoked: false, spec_version: '2.1', type: 'report' } as unknown as StixReport], - more: undefined + more: undefined, }, - addedLastHeader: undefined + addedLastHeader: undefined, }; await processTaxiiResponse(testContext, ingestionNotPagination, taxiResponse); @@ -61,14 +62,15 @@ describe('Verify taxii ingestion', () => { it('should taxii server response with data and next page and start date', async () => { // 1. Create ingestion in opencti - const input2 : IngestionTaxiiAddInput = { + const input2: IngestionTaxiiAddInput = { authentication_type: IngestionAuthType.None, collection: 'testcollection', ingestion_running: true, name: 'taxii ingestion with pagination and start date', uri: 'http://test.invalid', version: TaxiiVersion.V21, - added_after_start: '2024-01-01T20:35:44.000Z' + added_after_start: '2024-01-01T20:35:44.000Z', + user_id: ADMIN_USER.id, }; const ingestionPaginatedWithStartDate = await addTaxiiIngestion(testContext, ADMIN_USER, input2); expect(ingestionPaginatedWithStartDate.id).toBeDefined(); @@ -91,9 +93,9 @@ describe('Verify taxii ingestion', () => { revoked: false, spec_version: '2.1', type: 'report' } as unknown as StixReport], - more: true + more: true, }, - addedLastHeader: '2024-02-01T20:35:44.000Z' + addedLastHeader: '2024-02-01T20:35:44.000Z', }; await processTaxiiResponse(testContext, ingestionPaginatedWithStartDate, taxiResponse1); @@ -118,9 +120,9 @@ describe('Verify taxii ingestion', () => { revoked: false, spec_version: '2.1', type: 'report' } as unknown as StixReport], - more: false + more: false, }, - addedLastHeader: '2024-03-01T20:35:44.000Z' + addedLastHeader: '2024-03-01T20:35:44.000Z', }; await processTaxiiResponse(testContext, taxiiEntityAfterfirstRequest, taxiResponse); @@ -134,13 +136,14 @@ describe('Verify taxii ingestion', () => { it('should taxii server response with no start date, and next page', async () => { // 1. Create ingestion in opencti - const input3 : IngestionTaxiiAddInput = { + const input3: IngestionTaxiiAddInput = { authentication_type: IngestionAuthType.None, collection: 'testcollection', ingestion_running: true, name: 'taxii ingestion with pagination no start date', uri: 'http://test.invalid', version: TaxiiVersion.V21, + user_id: ADMIN_USER.id, }; const ingestionPaginatedWithNoStartDate = await addTaxiiIngestion(testContext, ADMIN_USER, input3); expect(ingestionPaginatedWithNoStartDate.id).toBeDefined(); @@ -163,9 +166,9 @@ describe('Verify taxii ingestion', () => { revoked: false, spec_version: '2.1', type: 'report' } as unknown as StixReport], - more: true + more: true, }, - addedLastHeader: '2024-02-01T20:35:44.000Z' + addedLastHeader: '2024-02-01T20:35:44.000Z', }; await processTaxiiResponse(testContext, ingestionPaginatedWithNoStartDate, taxiResponse); @@ -190,9 +193,9 @@ describe('Verify taxii ingestion', () => { revoked: false, spec_version: '2.1', type: 'report' } as unknown as StixReport], - more: false + more: false, }, - addedLastHeader: '2024-03-01T20:44:44.000Z' + addedLastHeader: '2024-03-01T20:44:44.000Z', }; await processTaxiiResponse(testContext, taxiiEntityAfterFirstCall, taxiResponse2); @@ -206,14 +209,15 @@ describe('Verify taxii ingestion', () => { it('should store nothing when no data', async () => { // 1. Create ingestion in opencti - const input2 : IngestionTaxiiAddInput = { + const input2: IngestionTaxiiAddInput = { authentication_type: IngestionAuthType.None, collection: 'testcollection', ingestion_running: true, name: 'taxii ingestion with pagination and start date', uri: 'http://test.invalid', version: TaxiiVersion.V21, - added_after_start: '2023-01-01T20:35:44.000Z' + added_after_start: '2023-01-01T20:35:44.000Z', + user_id: ADMIN_USER.id, }; const ingestionPaginatedWithStartDate = await addTaxiiIngestion(testContext, ADMIN_USER, input2); expect(ingestionPaginatedWithStartDate.id).toBeDefined(); @@ -223,9 +227,9 @@ describe('Verify taxii ingestion', () => { data: { next: undefined, objects: [], - more: false + more: false, }, - addedLastHeader: '2021-11-11T11:11:11.111Z' + addedLastHeader: '2021-11-11T11:11:11.111Z', }; await processTaxiiResponse(testContext, ingestionPaginatedWithStartDate, taxiResponse); @@ -241,13 +245,14 @@ describe('Verify taxii ingestion', () => { describe('Verify taxii ingestion - patch part', () => { it('should Taxii server response next as number be transform', async () => { // 1. Create ingestion in opencti - const input : IngestionTaxiiAddInput = { + const input: IngestionTaxiiAddInput = { authentication_type: IngestionAuthType.None, collection: 'testcollection', ingestion_running: true, name: 'taxii ingestion for patch test', uri: 'http://test.invalid', version: TaxiiVersion.V21, + user_id: ADMIN_USER.id, }; const ingestion = await addTaxiiIngestion(testContext, ADMIN_USER, input); expect(ingestion.id).toBeDefined(); @@ -271,22 +276,22 @@ describe('Verify csv ingestion', () => { it('should prepare ingestion data', async () => { const mapper = csvMapperMockCities as CsvMapperParsed; - const csvMapperInput : CsvMapperAddInput = { + const csvMapperInput: CsvMapperAddInput = { has_header: mapper.has_header, name: 'testCsvIngestionMapper', representations: JSON.stringify(mapper.representations), separator: mapper.separator, - skipLineChar: mapper.skipLineChar + skipLineChar: mapper.skipLineChar, }; const mapperCreated = await createCsvMapper(testContext, ADMIN_USER, csvMapperInput); - const ingestionCsvInput : IngestionCsvAddInput = { + const ingestionCsvInput: IngestionCsvAddInput = { authentication_type: IngestionAuthType.None, ingestion_running: true, name: 'csv ingestion', uri: 'http://test.invalid', csv_mapper_id: mapperCreated.id, - user_id: ADMIN_USER.id + user_id: ADMIN_USER.id, }; ingestionCsv = await addIngestionCsv(testContext, ADMIN_USER, ingestionCsvInput); expect(ingestionCsv.id).toBeDefined(); @@ -300,7 +305,6 @@ describe('Verify csv ingestion', () => { return false; } }, 10000, 6); // Wait for the queue result to exist - max 1 minute - csvMapperParsed = parseCsvMapper(mapperCreated); csvLines = await readCsvFromFileStream('./tests/03-integration/04-manager/ingestionManager', 'csv-file-cities.csv'); From fbe717424ba3aec2c830f8ba4a36348de089bee0 Mon Sep 17 00:00:00 2001 From: Julien Richard Date: Fri, 2 Jan 2026 15:01:10 +0100 Subject: [PATCH 019/126] [docs] Document prometheus exposed metrics (#13725) --- docs/docs/deployment/configuration.md | 2 + docs/docs/deployment/telemetry.md | 66 +++++++++++++++++++++++++++ docs/mkdocs.yml | 1 + 3 files changed, 69 insertions(+) create mode 100644 docs/docs/deployment/telemetry.md diff --git a/docs/docs/deployment/configuration.md b/docs/docs/deployment/configuration.md index 7478a8990885..103427c34185 100644 --- a/docs/docs/deployment/configuration.md +++ b/docs/docs/deployment/configuration.md @@ -100,6 +100,8 @@ Here are the configuration keys, for both containers (environment variables) and | app:telemetry:metrics:exporter_otlp | APP__TELEMETRY__METRICS__EXPORTER_OTLP | | Port to expose the OTLP endpoint | | app:telemetry:metrics:exporter_prometheus | APP__TELEMETRY__METRICS__EXPORTER_PROMETHEUS | 14269 | Port to expose the Prometheus endpoint | +For a detailed list of exposed metrics, please refer to the [Telemetry](../deployment/telemetry.md) page. + #### Maps & references diff --git a/docs/docs/deployment/telemetry.md b/docs/docs/deployment/telemetry.md new file mode 100644 index 000000000000..bd44f260b499 --- /dev/null +++ b/docs/docs/deployment/telemetry.md @@ -0,0 +1,66 @@ +# Telemetry + +OpenCTI exposes metrics using the OpenTelemetry standard, which can be exported to various systems such as Prometheus or OTLP collectors. These metrics provide insights into the platform's performance, usage, and health. + +## Configuration + +To enable metrics, you need to configure the telemetry section in your platform configuration. See [Configuration](configuration.md#telemetry) for details on how to enable and configure exporters. + +## Available Metrics + +The following metrics are exposed by the OpenCTI API. + +| Metric Name | Type | Description | Unit | +| :--- | :--- | :--- | :--- | +| `opencti_sent_email` | Counter | Counts the total number of emails sent by the platform. | Count | +| `opencti_api_requests` | Counter | Counts the total number of API requests received. | Count | +| `opencti_api_errors` | Counter | Counts the total number of API errors encountered. | Count | +| `opencti_api_latency` | Histogram | Measures the latency of API query execution. | Milliseconds | +| `opencti_api_direct_bulk` | Gauge | Measures the size of bulks for direct ingestion (fast path). | Count | +| `opencti_api_side_bulk` | Gauge | Measures the size of bulks for absorption impacts (worker path). | Count | + +## Metric Attributes + +Metrics exported by OpenCTI include various attributes (labels) to provide granular context. + +### API Metrics Attributes +Applies to: `opencti_api_requests`, `opencti_api_errors`, `opencti_api_latency` + +| Attribute | Description | Example | +|:---|:---|:---| +| `operation` | The GraphQL operation type. | `query`, `mutation`, `subscription` | +| `name` | The name of the GraphQL operation. | `StixCoreObjectFind`, `Unspecified` | +| `status` | The outcome of the request. | `SUCCESS`, `ERROR` | +| `type` | The error type (only present if status is ERROR). | `AUTH_REQUIRED`, `FORBIDDEN_ACCESS` | +| `user_agent` | The client user agent initiating the request. | `Mozilla/5.0...`, `OpenCTI-Client` | + +### Email Metrics Attributes +Applies to: `opencti_sent_email` + +| Attribute | Description | Example | +|:---|:---|:---| +| `category` | The functional category of the email. | `hub-registration`, `dissemination`, `password-reset`, `notification` | +| `identifier` | The ID of the related entity (e.g., user, trigger, list). | `uuid-v4-string` | + +### Bulk Metrics Attributes +Applies to: `opencti_api_direct_bulk`, `opencti_api_side_bulk` + +| Attribute | Description | Example | +|:---|:---|:---| +| `type` | The context or source of the indexing operation. | `import`, `connector` | + +## Node.js Runtime Metrics + +In addition to the application-specific metrics above, OpenCTI also exposes standard Node.js runtime metrics provided by `opentelemetry-node-metrics`. These include metrics for: + +- **Process**: CPU usage, memory usage, uptime, etc. +- **Event Loop**: Lag, active handles, etc. +- **GC**: Garbage collection duration and counts. +- **Memory**: Heap usage, heap limits, etc. + +Common examples include: +- `process_cpu_user_seconds_total` +- `process_cpu_system_seconds_total` +- `process_resident_memory_bytes` +- `nodejs_eventloop_lag_seconds` +- `nodejs_gc_duration_seconds` diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml index 8582b76b23ff..f1ceadc11788 100644 --- a/docs/mkdocs.yml +++ b/docs/mkdocs.yml @@ -116,6 +116,7 @@ nav: - Installation: deployment/installation.md - Configuration: deployment/configuration.md - Authentication: deployment/authentication.md + - Telemetry: deployment/telemetry.md - Upgrade: deployment/upgrade.md - Ecosystem: - Connectors: deployment/connectors.md From a519ddcf28ee5749aa267cd7d20a70cdb013b9cc Mon Sep 17 00:00:00 2001 From: Archidoit <75783086+Archidoit@users.noreply.github.com> Date: Sun, 4 Jan 2026 21:23:58 +0100 Subject: [PATCH 020/126] [backend] Check available playbooks for an entity according to filters (#13856) Co-authored-by: Samuel Hassine --- .../StixCoreObjectEnrollPlaybookLines.jsx | 2 ++ .../src/manager/playbookManager/playbookManager.ts | 10 ++++++---- .../src/modules/playbook/playbook-domain.ts | 10 ++-------- 3 files changed, 10 insertions(+), 12 deletions(-) diff --git a/opencti-platform/opencti-front/src/private/components/common/stix_core_objects/StixCoreObjectEnrollPlaybookLines.jsx b/opencti-platform/opencti-front/src/private/components/common/stix_core_objects/StixCoreObjectEnrollPlaybookLines.jsx index fa918e3503d1..4bbd8fa9216e 100644 --- a/opencti-platform/opencti-front/src/private/components/common/stix_core_objects/StixCoreObjectEnrollPlaybookLines.jsx +++ b/opencti-platform/opencti-front/src/private/components/common/stix_core_objects/StixCoreObjectEnrollPlaybookLines.jsx @@ -34,6 +34,8 @@ const styles = (theme) => ({ noResult: { color: theme.palette.text.primary, fontSize: 15, + textAlign: 'center', + marginTop: 20, }, itemIcon: { color: theme.palette.primary.main, diff --git a/opencti-platform/opencti-graphql/src/manager/playbookManager/playbookManager.ts b/opencti-platform/opencti-graphql/src/manager/playbookManager/playbookManager.ts index 9c120a902971..e6ec57939c5d 100644 --- a/opencti-platform/opencti-graphql/src/manager/playbookManager/playbookManager.ts +++ b/opencti-platform/opencti-graphql/src/manager/playbookManager/playbookManager.ts @@ -26,7 +26,7 @@ import { AUTOMATION_MANAGER_USER, executionContext, RETENTION_MANAGER_USER, SYST import type { SseEvent, StreamDataEvent } from '../../types/event'; import type { StixBundle, StixObject } from '../../types/stix-2-1-common'; import { streamEventId, utcDate } from '../../utils/format'; -import { findById } from '../../modules/playbook/playbook-domain'; +import { findById, findPlaybooksForEntity } from '../../modules/playbook/playbook-domain'; import { type CronConfiguration, PLAYBOOK_INTERNAL_DATA_CRON, type StreamConfiguration } from '../../modules/playbook/playbook-components'; import { PLAYBOOK_COMPONENTS } from '../../modules/playbook/playbook-components'; import type { BasicStoreEntityPlaybook, ComponentDefinition } from '../../modules/playbook/playbook-types'; @@ -132,15 +132,17 @@ const playbookStreamHandler = async (streamEvents: Array { - const playbooks = await getEntitiesListFromCache(context, SYSTEM_USER, ENTITY_TYPE_PLAYBOOK); + // fetch playbooks allowed for this entity + const playbooks = await findPlaybooksForEntity(context, RETENTION_MANAGER_USER, entityId); let playbook = null; + // keep the playbook corresponding to the id const filteredPlaybooks = playbooks.filter((n) => n.id === id); if (filteredPlaybooks.length > 0) { playbook = filteredPlaybooks.at(0); } else { - throw FunctionalError('Playbook does not exist', { id }); + throw FunctionalError('Playbook does not exist for this entity', { id }); } - // Execute only of definition is available + // Execute only if definition is available if (playbook && playbook.playbook_definition) { const def = JSON.parse(playbook.playbook_definition) as ComponentDefinition; const instance = def.nodes.find((n) => n.id === playbook.playbook_start); diff --git a/opencti-platform/opencti-graphql/src/modules/playbook/playbook-domain.ts b/opencti-platform/opencti-graphql/src/modules/playbook/playbook-domain.ts index 5f915595d44a..e0c5bec19505 100644 --- a/opencti-platform/opencti-graphql/src/modules/playbook/playbook-domain.ts +++ b/opencti-platform/opencti-graphql/src/modules/playbook/playbook-domain.ts @@ -22,7 +22,7 @@ import { notify } from '../../database/redis'; import type { DomainFindById } from '../../domain/domainTypes'; import { ABSTRACT_INTERNAL_OBJECT } from '../../schema/general'; import type { AuthContext, AuthUser } from '../../types/user'; -import { type EditInput, FilterMode, type PlaybookAddInput, type PlaybookAddLinkInput, type PlaybookAddNodeInput, type PositionInput } from '../../generated/graphql'; +import { type EditInput, type PlaybookAddInput, type PlaybookAddLinkInput, type PlaybookAddNodeInput, type PositionInput } from '../../generated/graphql'; import type { BasicStoreEntityPlaybook, ComponentDefinition } from './playbook-types'; import { ENTITY_TYPE_PLAYBOOK } from './playbook-types'; import { PLAYBOOK_COMPONENTS, type SharingConfiguration, type StreamConfiguration } from './playbook-components'; @@ -32,7 +32,6 @@ import { isStixMatchFilterGroup } from '../../utils/filtering/filtering-stix/sti import { registerConnectorQueues, unregisterConnector } from '../../database/rabbitmq'; import { getEntitiesListFromCache } from '../../database/cache'; import { SYSTEM_USER } from '../../utils/access'; -import { findFiltersFromKey } from '../../utils/filtering/filtering-utils'; import { checkEnterpriseEdition, isEnterpriseEdition } from '../../enterprise-edition/ee'; import pjson from '../../../package.json'; import { extractContentFrom } from '../../utils/fileToContent'; @@ -72,12 +71,7 @@ export const findPlaybooksForEntity = async (context: AuthContext, user: AuthUse if (instance && (instance.component_id === 'PLAYBOOK_INTERNAL_DATA_STREAM' || instance.component_id === 'PLAYBOOK_INTERNAL_MANUAL_TRIGGER')) { const { filters } = JSON.parse(instance.configuration ?? '{}') as StreamConfiguration; const jsonFilters = filters ? JSON.parse(filters) : null; - const newFilters = { - mode: FilterMode.And, - filters: findFiltersFromKey(jsonFilters?.filters ?? [], 'entity_type'), - filterGroups: [], - }; - const isMatch = await isStixMatchFilterGroup(context, SYSTEM_USER, stixEntity, newFilters); + const isMatch = await isStixMatchFilterGroup(context, SYSTEM_USER, stixEntity, jsonFilters); if (isMatch) { filteredPlaybooks.push(playbook); } From 6e958f69385d836cf77a0680efa41ca9e387897c Mon Sep 17 00:00:00 2001 From: Julien Richard Date: Sun, 4 Jan 2026 21:25:32 +0100 Subject: [PATCH 021/126] [backend] Improve license management supporting official OIDs and grace period (#13840) --- .../src/modules/settings/licensing.js | 26 ++++++++++++------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/opencti-platform/opencti-graphql/src/modules/settings/licensing.js b/opencti-platform/opencti-graphql/src/modules/settings/licensing.js index 566d1061e48e..cabf64c753cb 100644 --- a/opencti-platform/opencti-graphql/src/modules/settings/licensing.js +++ b/opencti-platform/opencti-graphql/src/modules/settings/licensing.js @@ -26,12 +26,20 @@ export const IS_LTS_PLATFORM = PLATFORM_VERSION.includes('lts'); // https://www.iana.org/assignments/enterprise-numbers/enterprise-numbers // 62944 - Filigran -export const LICENSE_OPTION_TYPE = '6.2.9.4.4.10'; -export const LICENSE_OPTION_PRODUCT = '6.2.9.4.4.20'; -export const LICENSE_OPTION_CREATOR = '6.2.9.4.4.30'; +export const LICENSE_OID_TYPE = '1.3.6.1.4.1.62944.10'; +export const LICENSE_OID_PRODUCT = '1.3.6.1.4.1.62944.20'; +export const LICENSE_OID_CREATOR = '1.3.6.1.4.1.62944.30'; +// Legacy OIDs +export const LICENSE_LEGACY_TYPE = '6.2.9.4.4.10'; +export const LICENSE_LEGACY_PRODUCT = '6.2.9.4.4.20'; +export const LICENSE_LEGACY_CREATOR = '6.2.9.4.4.30'; -const getExtensionValue = (clientCrt, extension) => { - return clientCrt.extensions.find((ext) => ext.id === extension)?.value; +const getExtensionValue = (clientCrt, standardOid, legacyOid) => { + const extStandard = clientCrt.extensions.find((ext) => ext.id === standardOid); + if (extStandard) { + return extStandard.value; + } + return clientCrt.extensions.find((ext) => ext.id === legacyOid)?.value; }; export const getEnterpriseEditionActivePem = (rawPem) => { @@ -47,10 +55,10 @@ export const getEnterpriseEditionInfoFromPem = (platformInstanceId, rawPem) => { try { const clientCrt = forge.pki.certificateFromPem(pem); const license_valid_cert = OPENCTI_CA.verify(clientCrt); - const license_type = getExtensionValue(clientCrt, LICENSE_OPTION_TYPE); + const license_type = getExtensionValue(clientCrt, LICENSE_OID_TYPE, LICENSE_LEGACY_TYPE); const valid_type = IS_LTS_PLATFORM ? license_type === LICENSE_OPTION_LTS : true; - const license_creator = getExtensionValue(clientCrt, LICENSE_OPTION_CREATOR); - const valid_product = getExtensionValue(clientCrt, LICENSE_OPTION_PRODUCT) === 'opencti'; + const license_creator = getExtensionValue(clientCrt, LICENSE_OID_CREATOR, LICENSE_LEGACY_CREATOR); + const valid_product = getExtensionValue(clientCrt, LICENSE_OID_PRODUCT, LICENSE_LEGACY_PRODUCT) === 'opencti'; const license_customer = clientCrt.subject.getField('O').value; const license_platform = clientCrt.subject.getField('OU').value; const license_platform_match = valid_product && valid_type && (license_platform === GLOBAL_LICENSE_OPTION || platformInstanceId === license_platform); @@ -66,7 +74,7 @@ export const getEnterpriseEditionInfoFromPem = (platformInstanceId, rawPem) => { // If trial license, deactivation for expiration is direct if (license_type !== LICENSE_OPTION_TRIAL) { // If standard or lts license, a 3 months safe period is granted - const license_extra_expiration_date = utcDate(clientCrt.validity.notBefore).add(3, 'months'); + const license_extra_expiration_date = utcDate(clientCrt.validity.notAfter).add(3, 'months'); license_extra_expiration_days = license_extra_expiration_date.diff(utcDate(), 'days'); license_extra_expiration = new Date() < license_extra_expiration_date.toDate(); license_validated = license_extra_expiration; From be4ab13c30d154adc3cfc49ba128b2039b93e348 Mon Sep 17 00:00:00 2001 From: Filigran Automation Date: Sun, 4 Jan 2026 20:32:10 +0000 Subject: [PATCH 022/126] [backend/worker] Release 6.9.5 --- client-python/pycti/__init__.py | 2 +- opencti-platform/opencti-front/package.json | 2 +- opencti-platform/opencti-graphql/package.json | 2 +- opencti-platform/opencti-graphql/src/python/requirements.txt | 2 +- opencti-worker/src/requirements.txt | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/client-python/pycti/__init__.py b/client-python/pycti/__init__.py index 911bde893f33..48efbce349e7 100644 --- a/client-python/pycti/__init__.py +++ b/client-python/pycti/__init__.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -__version__ = "6.9.4" +__version__ = "6.9.5" from .api.opencti_api_client import OpenCTIApiClient from .api.opencti_api_connector import OpenCTIApiConnector diff --git a/opencti-platform/opencti-front/package.json b/opencti-platform/opencti-front/package.json index 7b175f71c8bf..0356d1985da9 100644 --- a/opencti-platform/opencti-front/package.json +++ b/opencti-platform/opencti-front/package.json @@ -1,6 +1,6 @@ { "name": "opencti-front", - "version": "6.9.4", + "version": "6.9.5", "private": true, "workspaces": [ "packages/*" diff --git a/opencti-platform/opencti-graphql/package.json b/opencti-platform/opencti-graphql/package.json index ecba8b2b95d0..a2cf0c3e7ae4 100644 --- a/opencti-platform/opencti-graphql/package.json +++ b/opencti-platform/opencti-graphql/package.json @@ -1,6 +1,6 @@ { "name": "opencti-graphql", - "version": "6.9.4", + "version": "6.9.5", "private": true, "scripts": { "check-ts": "tsc --noEmit", diff --git a/opencti-platform/opencti-graphql/src/python/requirements.txt b/opencti-platform/opencti-graphql/src/python/requirements.txt index f39a15695b6f..f11e54d1106b 100644 --- a/opencti-platform/opencti-graphql/src/python/requirements.txt +++ b/opencti-platform/opencti-graphql/src/python/requirements.txt @@ -1,4 +1,4 @@ -pycti==6.9.4 +pycti==6.9.5 parsuricata==0.4.1 yara-python==4.5.2 sigmatools==0.23.1 diff --git a/opencti-worker/src/requirements.txt b/opencti-worker/src/requirements.txt index 2e66bcb7e013..17b88b1ba9b5 100644 --- a/opencti-worker/src/requirements.txt +++ b/opencti-worker/src/requirements.txt @@ -1,4 +1,4 @@ -pycti==6.9.4 +pycti==6.9.5 opentelemetry-api~=1.35.0 opentelemetry-sdk~=1.35.0 opentelemetry-exporter-prometheus==0.56b0 From 65db32317844eefe7f8840dd768fbc942f9b7ddd Mon Sep 17 00:00:00 2001 From: Archidoit <75783086+Archidoit@users.noreply.github.com> Date: Mon, 5 Jan 2026 09:58:18 +0100 Subject: [PATCH 023/126] [frontend] Add has-label in relationship type filter values for knowledge widgets (#13837) --- .../src/private/components/widgets/WidgetFilters.tsx | 2 +- .../src/utils/filters/useSearchEntities.tsx | 12 ++++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/opencti-platform/opencti-front/src/private/components/widgets/WidgetFilters.tsx b/opencti-platform/opencti-front/src/private/components/widgets/WidgetFilters.tsx index c2dcc1bb8cf4..6a9a83268f4e 100644 --- a/opencti-platform/opencti-front/src/private/components/widgets/WidgetFilters.tsx +++ b/opencti-platform/opencti-front/src/private/components/widgets/WidgetFilters.tsx @@ -34,7 +34,7 @@ const WidgetFilters: FunctionComponent = ({ perspective, typ let availableEntityTypes; let searchContext; if (perspective === 'relationships') { - searchContext = { entityTypes: ['stix-core-relationship', 'stix-sighting-relationship', 'contains'] }; + searchContext = { entityTypes: ['stix-core-relationship', 'stix-sighting-relationship', 'contains', 'object-label'] }; } else if (perspective === 'audits') { availableEntityTypes = ['History', 'Activity']; searchContext = { entityTypes: ['History'] }; diff --git a/opencti-platform/opencti-front/src/utils/filters/useSearchEntities.tsx b/opencti-platform/opencti-front/src/utils/filters/useSearchEntities.tsx index 7d71ce2b2cc0..bf5078d67122 100644 --- a/opencti-platform/opencti-front/src/utils/filters/useSearchEntities.tsx +++ b/opencti-platform/opencti-front/src/utils/filters/useSearchEntities.tsx @@ -325,6 +325,11 @@ const useSearchEntities = ({ value: 'object', type: 'stix-internal-relationship', }; + const labelRelationshipType = { + label: t_i18n('relationship_object-label'), + value: 'objectLabel', + type: 'stix-meta-relationship', + }; const unionSetEntities = (key: string, newEntities: EntityValue[]) => setEntities((c) => ({ ...c, @@ -918,6 +923,7 @@ const useSearchEntities = ({ ...scrTypes, abstractTypeFilterValue('stix-sighting-relationship'), objectRelationshipType, + labelRelationshipType, ]; } else { // display relationship types according to searchContext.entityTypes const { entityTypes } = searchContext; @@ -936,6 +942,12 @@ const useSearchEntities = ({ objectRelationshipType, ]; } + if (entityTypes.includes('object-label')) { + relationshipsTypes = [ + ...relationshipsTypes, + labelRelationshipType, + ]; + } } unionSetEntities(filterKey, relationshipsTypes.sort((a, b) => a.label.localeCompare(b.label))); break; From 54e5d5a06ca747765afad6918a48eca8d3b15daf Mon Sep 17 00:00:00 2001 From: Archidoit <75783086+Archidoit@users.noreply.github.com> Date: Mon, 5 Jan 2026 10:30:05 +0100 Subject: [PATCH 024/126] [frontend] ai powered label and tooltip in CE (#13810) --- .../opencti-front/lang/front/de.json | 8 +- .../opencti-front/lang/front/en.json | 8 +- .../opencti-front/lang/front/es.json | 8 +- .../opencti-front/lang/front/fr.json | 8 +- .../opencti-front/lang/front/it.json | 8 +- .../opencti-front/lang/front/ja.json | 8 +- .../opencti-front/lang/front/ko.json | 8 +- .../opencti-front/lang/front/ru.json | 8 +- .../opencti-front/lang/front/zh.json | 8 +- .../common/entreprise_edition/EEChip.tsx | 74 +++++++++---------- .../private/components/settings/Settings.tsx | 33 +++++++-- 11 files changed, 106 insertions(+), 73 deletions(-) diff --git a/opencti-platform/opencti-front/lang/front/de.json b/opencti-platform/opencti-front/lang/front/de.json index bc4403d0e1db..923285063042 100644 --- a/opencti-platform/opencti-front/lang/front/de.json +++ b/opencti-platform/opencti-front/lang/front/de.json @@ -195,6 +195,7 @@ "Agentic AI (Ariane Assistant)": "Agentische KI (Ariane-Assistent)", "Agentic AI capabilities": "Agentische KI-Fähigkeiten", "AI Insights": "AI-Einblicke", + "AI is not enabled": "AI ist nicht aktiviert", "AI Powered": "KI-gesteuert", "AI Summary": "AI Zusammenfassung", "alert_GROUP_WITH_NULL_CONFIDENCE_LEVEL": "Beginnend mit OpenCTI 6.0 wird ein maximales Vertrauensniveau für Gruppen und Benutzer hinzugefügt. Damit haben Plattformadministratoren eine bessere Kontrolle über die Auswirkungen importierter Daten und können unerwünschte Datenaktualisierungen verhindern. Bitte lesen Sie {link_blogpost} für weitere Informationen.\n \nAuf dieser Plattform sind \"Max Confidence Level\" einiger Gruppen derzeit undefiniert. Deren Mitglieder können in der Version 6.X der Plattform keine Daten erstellen. Um dies zu verhindern, stellen Sie sicher, dass für alle Gruppen eine maximale Vertrauensstufe festgelegt ist. Mehr dazu in {link_blogpost}. Achten Sie besonders auf Benutzer, die mit Connectors, Feeds und Streams verbunden sind; Sie können die individuelle Vertrauensstufe so einstellen, dass sie die Gruppen-Vertrauensstufe überschreibt, wenn dies erforderlich ist. Treten Sie {link_slack} bei, wenn Sie Ratschläge benötigen.", @@ -1918,12 +1919,12 @@ "Heatmap": "Heatmap", "Height": "Größe", "here": "hier", + "Hidden": "Versteckt", "Hidden entity types": "Ausgeblendete Entitätstypen", "Hidden in ": "Versteckt in", "Hidden in groups": "Versteckt in Gruppen", "Hidden in interface": "Versteckt in der Schnittstelle", "Hidden in organizations": "Versteckt in Organisationen", - "Hidden": "Versteckt", "Hide": "Ausblenden", "Hide column": "Spalte ausblenden", "Hide entities in container": "Entitäten im Container ausblenden", @@ -3802,8 +3803,8 @@ "text/markdown": "MD", "text/plain": "TXT", "Textarea": "Textarea", - "Thank you!": "Vielen Dank!", "Thank you for reaching out, we'll get back to you shortly.": "Vielen Dank für Ihre Anfrage, wir werden Sie in Kürze kontaktieren.", + "Thank you!": "Vielen Dank!", "The alias has been added": "Der Alias wurde hinzugefügt", "The alias has been removed": "Der Alias wurde entfernt", "The amount of results can differ based on the data/relationships, as each occurrence of an inferred relation is counted for this widget.": "Die Anzahl der Ergebnisse kann je nach Daten/Beziehungen unterschiedlich sein, da jedes Auftreten einer abgeleiteten Beziehung für dieses Widget gezählt wird.", @@ -4515,6 +4516,7 @@ "You need to enable EE License to use this feature": "Sie müssen die EE-Lizenz aktivieren, um diese Funktion nutzen zu können", "You need to validate your two-factor authentication. Please type the code generated in your application": "Sie müssen Ihre Zwei-Faktor-Authentifizierung validieren. Bitte geben Sie den in Ihrer Anwendung generierten Code ein.", "You see only marking definitions that can be shared (defined by the admin)": "Sie sehen nur Markierungsdefinitionen, die freigegeben werden können (vom Administrator definiert)", + "You should activate EE to use this feature": "Sie sollten EE aktivieren, um diese Funktion zu nutzen", "You should provide a variable name": "Sie sollten einen Variablennamen angeben", "You will be able to revert this change if needed. ": "Sie können diese Änderung bei Bedarf rückgängig machen.", "You will be automatically logged out at end of the timer.": "Sie werden nach Ablauf der Zeit automatisch abgemeldet.", @@ -4541,4 +4543,4 @@ "Zoom": "Vergrößern", "Zoom in": "Vergrößern", "Zoom out": "Verkleinern" -} +} \ No newline at end of file diff --git a/opencti-platform/opencti-front/lang/front/en.json b/opencti-platform/opencti-front/lang/front/en.json index e44082d12643..501ef2781757 100644 --- a/opencti-platform/opencti-front/lang/front/en.json +++ b/opencti-platform/opencti-front/lang/front/en.json @@ -195,6 +195,7 @@ "Agentic AI (Ariane Assistant)": "Agentic AI (Ariane Assistant)", "Agentic AI capabilities": "Agentic AI capabilities", "AI Insights": "AI Insights", + "AI is not enabled": "AI is not enabled", "AI Powered": "AI Powered", "AI Summary": "AI Summary", "alert_GROUP_WITH_NULL_CONFIDENCE_LEVEL": "Starting with OpenCTI 6.0, a Max Confidence Level will be added to groups and users. It will provide platform administrators with more control on the impact of imported data and prevent any unwanted update on data. Please read {link_blogpost} for more information.\n \nOn this platform, \"Max Confidence Level\" of some groups are currently undefined. Their members will not be able to create any data in version 6.X of the platform. In order to prevent that, make sure that all groups have a Max Confidence Level set. More info in {link_blogpost}. Be particularly mindful of users associated to Connectors, Feeds and Streams; you can set individual confidence level to override group confidence level if needed. Join {link_slack} if you need advices.", @@ -1918,12 +1919,12 @@ "Heatmap": "Heatmap", "Height": "Height", "here": "here", + "Hidden": "Hidden", "Hidden entity types": "Hidden entity types", "Hidden in ": "Hidden in ", "Hidden in groups": "Hidden in groups", "Hidden in interface": "Hidden in interface", "Hidden in organizations": "Hidden in organizations", - "Hidden": "Hidden", "Hide": "Hide", "Hide column": "Hide column", "Hide entities in container": "Hide entities in container", @@ -3802,8 +3803,8 @@ "text/markdown": "MD", "text/plain": "TXT", "Textarea": "Textarea", - "Thank you!": "Thank you!", "Thank you for reaching out, we'll get back to you shortly.": "Thank you for reaching out, we'll get back to you shortly.", + "Thank you!": "Thank you!", "The alias has been added": "The alias has been added", "The alias has been removed": "The alias has been removed", "The amount of results can differ based on the data/relationships, as each occurrence of an inferred relation is counted for this widget.": "The amount of results can differ based on the data/relationships, as each occurrence of an inferred relation is counted for this widget.", @@ -4515,6 +4516,7 @@ "You need to enable EE License to use this feature": "You need to enable EE License to use this feature", "You need to validate your two-factor authentication. Please type the code generated in your application": "You need to validate your two-factor authentication. Please type the code generated in your application.", "You see only marking definitions that can be shared (defined by the admin)": "You see only marking definitions that can be shared (defined by the admin)", + "You should activate EE to use this feature": "You should activate EE to use this feature", "You should provide a variable name": "You should provide a variable name", "You will be able to revert this change if needed. ": "You will be able to revert this change if needed. ", "You will be automatically logged out at end of the timer.": "You will be automatically logged out at end of the timer.", @@ -4541,4 +4543,4 @@ "Zoom": "Zoom", "Zoom in": "Zoom in", "Zoom out": "Zoom out" -} +} \ No newline at end of file diff --git a/opencti-platform/opencti-front/lang/front/es.json b/opencti-platform/opencti-front/lang/front/es.json index 54b746bdf0ce..ee0f39cd46a3 100644 --- a/opencti-platform/opencti-front/lang/front/es.json +++ b/opencti-platform/opencti-front/lang/front/es.json @@ -195,6 +195,7 @@ "Agentic AI (Ariane Assistant)": "IA Agenética (Asistente Ariane)", "Agentic AI capabilities": "Capacidades de IA agéntica", "AI Insights": "AI Insights", + "AI is not enabled": "La IA no está activada", "AI Powered": "AI Powered", "AI Summary": "Resumen AI", "alert_GROUP_WITH_NULL_CONFIDENCE_LEVEL": "A partir de OpenCTI 6.0, se añadirá un nivel de confianza máximo a los grupos y usuarios. Proporcionará a los administradores de la plataforma un mayor control sobre el impacto de los datos importados y evitará cualquier actualización no deseada de los datos. Lea {link_blogpost} para obtener más información.\n \nEn esta plataforma, \"el nivel máximo de confianza\" de algunos grupos no están definidos actualmente. Sus miembros no podrán crear ningún dato en la versión 6.X de la plataforma. Para evitarlo, asegúrate de que todos los grupos tienen un nivel de confianza máximo definido. Más información en {link_blogpost}. Ten especial cuidado con los usuarios asociados a Conectores, Feeds y Streams; puedes establecer el nivel de confianza individual para anular el nivel de confianza de grupo si es necesario. Únete a {link_slack} si necesitas consejos.", @@ -1918,12 +1919,12 @@ "Heatmap": "Mapa de calor", "Height": "Altura", "here": "aquí", + "Hidden": "¿Escondido", "Hidden entity types": "Tipos de entidad ocultos", "Hidden in ": "Oculto en ", "Hidden in groups": "Oculto en grupos", "Hidden in interface": "Oculto en la interfaz", "Hidden in organizations": "Oculto en las organizaciones", - "Hidden": "¿Escondido", "Hide": "Ocultar", "Hide column": "Ocultar columna", "Hide entities in container": "Ocultar entidades en el contenedor", @@ -3802,8 +3803,8 @@ "text/markdown": "MD", "text/plain": "TXT", "Textarea": "Área de texto", - "Thank you!": "¡Gracias!", "Thank you for reaching out, we'll get back to you shortly.": "¡Gracias por contactarnos, te responderemos lo antes posible!", + "Thank you!": "¡Gracias!", "The alias has been added": "El alias has sido añadido", "The alias has been removed": "El alias ha sido eliminado", "The amount of results can differ based on the data/relationships, as each occurrence of an inferred relation is counted for this widget.": "La cantidad de resultados puede variar en función de los datos/relaciones, ya que cada aparición de una relación inferida se cuenta para este widget.", @@ -4515,6 +4516,7 @@ "You need to enable EE License to use this feature": "Debe activar la licencia EE para utilizar esta función", "You need to validate your two-factor authentication. Please type the code generated in your application": "Debe validar su autenticación de dos factores. Por favor, escriba el código generado en su aplicación.", "You see only marking definitions that can be shared (defined by the admin)": "Sólo se ven las definiciones de marcado que se pueden compartir (definidas por el administrador)", + "You should activate EE to use this feature": "Debe activar EE para utilizar esta función", "You should provide a variable name": "Debes proporcionar un nombre de variable", "You will be able to revert this change if needed. ": "Podrás revertir este cambio si es necesario.", "You will be automatically logged out at end of the timer.": "Se cerrará la sesión automáticamente al final del temporizador.", @@ -4541,4 +4543,4 @@ "Zoom": "Zoom", "Zoom in": "Ampliar", "Zoom out": "Alejar" -} +} \ No newline at end of file diff --git a/opencti-platform/opencti-front/lang/front/fr.json b/opencti-platform/opencti-front/lang/front/fr.json index 6cfd0ec4e59e..4fbb93364246 100644 --- a/opencti-platform/opencti-front/lang/front/fr.json +++ b/opencti-platform/opencti-front/lang/front/fr.json @@ -195,6 +195,7 @@ "Agentic AI (Ariane Assistant)": "IA agentique (assistant Ariane)", "Agentic AI capabilities": "Capacités de l'IA agentique", "AI Insights": "Aperçu par AI", + "AI is not enabled": "L'IA n'est pas activée", "AI Powered": "AI Powered", "AI Summary": "Résumé AI", "alert_GROUP_WITH_NULL_CONFIDENCE_LEVEL": "À partir d'OpenCTI 6.0, un niveau de confiance maximal sera ajouté aux groupes et aux utilisateurs. Cela permettra aux administrateurs de la plateforme de mieux contrôler l'impact des données importées et d'éviter toute mise à jour indésirable des données. Veuillez lire {link_blogpost} pour plus d'informations.\n \nSur cette plateforme, le niveau de confiance maximum de certains groupes est actuellement indéfini. Leurs membres ne pourront pas créer de données dans la version 6.X de la plateforme. Pour éviter cela, assurez-vous que tous les groupes ont un niveau de confiance maximum défini. Plus d'informations dans {link_blogpost}. Soyez particulièrement attentif aux utilisateurs associés aux connecteurs, aux streams et aux feeds ; vous pouvez définir un niveau de confiance individuel pour remplacer le niveau de confiance du groupe si nécessaire. Rejoignez {link_slack} si vous avez besoin de conseils.", @@ -1918,12 +1919,12 @@ "Heatmap": "Carte de chaleur", "Height": "Hauteur", "here": "ici", + "Hidden": "Caché", "Hidden entity types": "Types d'entité cachés", "Hidden in ": "Caché dans ", "Hidden in groups": "Caché dans les groupes", "Hidden in interface": "Caché dans l'interface", "Hidden in organizations": "Caché dans les organisations", - "Hidden": "Caché", "Hide": "Cacher", "Hide column": "Masquer la colonne", "Hide entities in container": "Cacher les entités dans le conteneur", @@ -3802,8 +3803,8 @@ "text/markdown": "MD", "text/plain": "TXT", "Textarea": "Zone de texte", - "Thank you!": "Merci!", "Thank you for reaching out, we'll get back to you shortly.": "Merci d'avoir contacté notre équipe, nous vous répondrons dans les plus brefs délais.", + "Thank you!": "Merci!", "The alias has been added": "L'alias a bien été ajouté", "The alias has been removed": "L'alias a été supprimé", "The amount of results can differ based on the data/relationships, as each occurrence of an inferred relation is counted for this widget.": "Le nombre de résultats peut varier en fonction des données/relations, car chaque occurrence d'une relation déduite est comptée pour ce widget.", @@ -4515,6 +4516,7 @@ "You need to enable EE License to use this feature": "Vous devez activer la licence EE pour utiliser cette fonction", "You need to validate your two-factor authentication. Please type the code generated in your application": "Vous devez valider votre authentification à deux facteurs. Veuillez saisir le code généré dans votre application.", "You see only marking definitions that can be shared (defined by the admin)": "Vous ne voyez que les définitions de marquage qui peuvent être partagées (définies par l'administrateur)", + "You should activate EE to use this feature": "Vous devez activer l'EE pour utiliser cette fonction", "You should provide a variable name": "Vous devez fournir un nom de variable", "You will be able to revert this change if needed. ": "Vous pourrez revenir sur cette modification si nécessaire.", "You will be automatically logged out at end of the timer.": "Vous serez automatiquement déconnecté à la fin du décompte.", @@ -4541,4 +4543,4 @@ "Zoom": "Zoom", "Zoom in": "Zoomer", "Zoom out": "Zoom arrière" -} +} \ No newline at end of file diff --git a/opencti-platform/opencti-front/lang/front/it.json b/opencti-platform/opencti-front/lang/front/it.json index 3c9eaf3d8811..a07f2bb00329 100644 --- a/opencti-platform/opencti-front/lang/front/it.json +++ b/opencti-platform/opencti-front/lang/front/it.json @@ -195,6 +195,7 @@ "Agentic AI (Ariane Assistant)": "IA agenziale (Assistente Arianna)", "Agentic AI capabilities": "Funzionalità di IA agenziale", "AI Insights": "AI Insights", + "AI is not enabled": "L'intelligenza artificiale non è abilitata", "AI Powered": "AI Powered", "AI Summary": "AI Summary", "alert_GROUP_WITH_NULL_CONFIDENCE_LEVEL": "A partire da OpenCTI 6.0, verrà aggiunto un livello di fiducia massimo ai gruppi e agli utenti. Questo permetterà agli amministratori della piattaforma di avere un maggiore controllo sull'impatto dei dati importati e di prevenire aggiornamenti indesiderati sui dati. Per maggiori informazioni, consultare {link_blogpost}.\n\nSu questa piattaforma, il \"Livello di Fiducia Massimo\" di alcuni gruppi non è attualmente definito. I loro membri non potranno creare dati nella versione 6.X della piattaforma. Per evitare questo problema, assicurati che tutti i gruppi abbiano un livello di fiducia massimo impostato. Più informazioni su {link_blogpost}. Presta particolare attenzione agli utenti associati a Connettori, Feed e Stream; se necessario, puoi impostare un livello di fiducia individuale per sovrascrivere quello del gruppo. Unisciti a {link_slack} se hai bisogno di consigli.", @@ -1918,12 +1919,12 @@ "Heatmap": "Mappa di calore", "Height": "Altezza", "here": "qui", + "Hidden": "Nascosto", "Hidden entity types": "Tipi di entità nascosti", "Hidden in ": "Nascosto in ", "Hidden in groups": "Nascosto nei gruppi", "Hidden in interface": "Nascosto nell'interfaccia", "Hidden in organizations": "Nascosto nelle organizzazioni", - "Hidden": "Nascosto", "Hide": "Nascondi", "Hide column": "Nascondi colonna", "Hide entities in container": "Nascondi entità nel contenitore", @@ -3802,8 +3803,8 @@ "text/markdown": "MD", "text/plain": "TXT", "Textarea": "Area di testo", - "Thank you!": "Grazie!", "Thank you for reaching out, we'll get back to you shortly.": "Grazie per averci contattato, ti risponderemo a breve.", + "Thank you!": "Grazie!", "The alias has been added": "L'alias è stato aggiunto", "The alias has been removed": "L'alias è stato rimosso", "The amount of results can differ based on the data/relationships, as each occurrence of an inferred relation is counted for this widget.": "La quantità di risultati può variare in base ai dati/alle relazioni, poiché ogni occorrenza di una relazione dedotta viene contata per questo widget.", @@ -4515,6 +4516,7 @@ "You need to enable EE License to use this feature": "È necessario abilitare la licenza EE per utilizzare questa funzione", "You need to validate your two-factor authentication. Please type the code generated in your application": "Devi convalidare la tua autenticazione a due fattori. Inserisci il codice generato nella tua applicazione.", "You see only marking definitions that can be shared (defined by the admin)": "Vedi solo le definizioni di marcatura che possono essere condivise (definite dall'amministratore)", + "You should activate EE to use this feature": "È necessario attivare EE per utilizzare questa funzione", "You should provide a variable name": "Dovresti fornire un nome per la variabile", "You will be able to revert this change if needed. ": "Sarà possibile ripristinare questa modifica se necessario.", "You will be automatically logged out at end of the timer.": "Verrai disconnesso automaticamente alla fine del timer.", @@ -4541,4 +4543,4 @@ "Zoom": "Zoom", "Zoom in": "Ingrandisci", "Zoom out": "Riduci" -} +} \ No newline at end of file diff --git a/opencti-platform/opencti-front/lang/front/ja.json b/opencti-platform/opencti-front/lang/front/ja.json index d7499f0484e9..856cc95f8e36 100644 --- a/opencti-platform/opencti-front/lang/front/ja.json +++ b/opencti-platform/opencti-front/lang/front/ja.json @@ -195,6 +195,7 @@ "Agentic AI (Ariane Assistant)": "エージェント型AI(アリアン・アシスタント)", "Agentic AI capabilities": "エージェントAIの能力", "AI Insights": "AIインサイト", + "AI is not enabled": "AIは有効ではない", "AI Powered": "AI搭載", "AI Summary": "AIサマリー", "alert_GROUP_WITH_NULL_CONFIDENCE_LEVEL": "OpenCTI 6.0 から、グループとユーザーに最大信頼度が追加されます。これにより、プラットフォーム管理者はインポートされたデータの影響をよりコントロールできるようになり、データの不要な更新を防ぐことができます。詳しくは{link_blogpost}をお読みください。\n \nこのプラットフォームでは、いくつかのグループの最大信頼度が現在未定義です。そのグループのメンバーはバージョン6.Xではデータを作成することができません。これを防ぐには、すべてのグループに最大信頼度が設定されていることを確認してください。詳細は{link_blogpost}を参照してください。コネクター、フィード、ストリームに関連するユーザーには特に注意してください。必要であれば、グループの信頼度よりも個人の信頼度を優先して設定できます。アドバイスが必要な場合は{link_slack}に参加してください。", @@ -1918,12 +1919,12 @@ "Heatmap": "ヒートマップ", "Height": "身長", "here": "ここ", + "Hidden": "隠された", "Hidden entity types": "非表示のエンティティ種別", "Hidden in ": "に隠されています", "Hidden in groups": "グループに隠れている", "Hidden in interface": "インターフェイスに隠されている", "Hidden in organizations": "組織に隠れて", - "Hidden": "隠された", "Hide": "隠す", "Hide column": "列を隠す", "Hide entities in container": "コンテナ内のエンティティを隠す", @@ -3802,8 +3803,8 @@ "text/markdown": "MD", "text/plain": "TXT", "Textarea": "テキストエリア", - "Thank you!": "ありがとうございます!", "Thank you for reaching out, we'll get back to you shortly.": "ご連絡ありがとうございます。ご連絡を承りました。", + "Thank you!": "ありがとうございます!", "The alias has been added": "別名を追加しました", "The alias has been removed": "エイリアスは削除されました", "The amount of results can differ based on the data/relationships, as each occurrence of an inferred relation is counted for this widget.": "推論された関係の各出現がこのウィジェットのためにカウントされるので,結果の量は,データ/関係によって異なる可能性がある.", @@ -4515,6 +4516,7 @@ "You need to enable EE License to use this feature": "この機能を使用するには、EEライセンスを有効にする必要があります。", "You need to validate your two-factor authentication. Please type the code generated in your application": "二要素認証を検証する必要があります。アプリケーションで生成されたコードを入力してください。", "You see only marking definitions that can be shared (defined by the admin)": "共有可能な(管理者によって定義された)マーキング定義のみが表示されます。", + "You should activate EE to use this feature": "この機能を使用するにはEEを有効にする必要がある", "You should provide a variable name": "変数名を指定してください", "You will be able to revert this change if needed. ": "必要に応じて、この変更を元に戻すことができます。", "You will be automatically logged out at end of the timer.": "タイマーが終了すると自動的にログアウトされます", @@ -4541,4 +4543,4 @@ "Zoom": "ズーム", "Zoom in": "ズームイン", "Zoom out": "ズームアウト" -} +} \ No newline at end of file diff --git a/opencti-platform/opencti-front/lang/front/ko.json b/opencti-platform/opencti-front/lang/front/ko.json index d2cd009e4a03..49b29c8a7a47 100644 --- a/opencti-platform/opencti-front/lang/front/ko.json +++ b/opencti-platform/opencti-front/lang/front/ko.json @@ -195,6 +195,7 @@ "Agentic AI (Ariane Assistant)": "에이전트 AI(아리안 어시스턴트)", "Agentic AI capabilities": "에이전트 AI 기능", "AI Insights": "AI 인사이트", + "AI is not enabled": "AI가 활성화되지 않았습니다", "AI Powered": "AI 지원", "AI Summary": "AI 요약", "alert_GROUP_WITH_NULL_CONFIDENCE_LEVEL": "OpenCTI 6.0부터 그룹과 사용자에게 최대 신뢰 수준이 추가됩니다. 이는 플랫폼 관리자에게 가져온 데이터의 영향을 더 많이 제어할 수 있도록 하며 데이터의 원치 않는 업데이트를 방지합니다. 자세한 내용은 {link_blogpost}을(를) 참조하십시오.\n \n이 플랫폼에서는 일부 그룹의 '최대 신뢰 수준'이 현재 정의되지 않았습니다. 그들의 멤버는 플랫폼의 버전 6.X에서 데이터를 생성할 수 없습니다. 이를 방지하려면 모든 그룹에 최대 신뢰 수준이 설정되어 있는지 확인하십시오. 자세한 내용은 {link_blogpost}을 참조하십시오. 커넥터, 피드 및 스트림과 연결된 사용자에 특히 주의해야 합니다. 필요 시 개별 신뢰 수준을 설정하여 그룹 신뢰 수준을 재정의할 수 있습니다. 도움이 필요하면 {link_slack}에 참여하십시오.", @@ -1918,12 +1919,12 @@ "Heatmap": "히트맵", "Height": "키", "here": "여기", + "Hidden": "숨겨져 있나요", "Hidden entity types": "숨겨진 엔터티 유형", "Hidden in ": "숨겨진 ", "Hidden in groups": "그룹에서 숨겨짐", "Hidden in interface": "인터페이스에서 숨겨짐", "Hidden in organizations": "조직에서 숨겨짐", - "Hidden": "숨겨져 있나요", "Hide": "숨기기", "Hide column": "열 숨기기", "Hide entities in container": "컨테이너에서 엔티티 숨기기", @@ -3802,8 +3803,8 @@ "text/markdown": "MD", "text/plain": "TXT", "Textarea": "텍스트 영역", - "Thank you!": "감사합니다!", "Thank you for reaching out, we'll get back to you shortly.": "감사합니다. 곧 연락드리겠습니다.", + "Thank you!": "감사합니다!", "The alias has been added": "별명이 추가되었습니다", "The alias has been removed": "별명이 제거되었습니다", "The amount of results can differ based on the data/relationships, as each occurrence of an inferred relation is counted for this widget.": "이 위젯에서는 추론된 관계가 발생할 때마다 계산되므로 결과의 양은 데이터/관계에 따라 달라질 수 있습니다.", @@ -4515,6 +4516,7 @@ "You need to enable EE License to use this feature": "이 기능을 사용하려면 EE 라이선스를 활성화해야 합니다", "You need to validate your two-factor authentication. Please type the code generated in your application": "이중 인증을 검증해야 합니다. 애플리케이션에서 생성된 코드를 입력하십시오", "You see only marking definitions that can be shared (defined by the admin)": "공유할 수 있는 마킹 정의만 표시됩니다 (관리자가 정의한 것)", + "You should activate EE to use this feature": "이 기능을 사용하려면 EE를 활성화해야 합니다", "You should provide a variable name": "변수 이름을 입력해야 합니다", "You will be able to revert this change if needed. ": "필요한 경우 이 변경 사항을 되돌릴 수 있습니다.", "You will be automatically logged out at end of the timer.": "타이머가 끝나면 자동으로 로그아웃됩니다.", @@ -4541,4 +4543,4 @@ "Zoom": "확대/축소", "Zoom in": "확대", "Zoom out": "축소" -} +} \ No newline at end of file diff --git a/opencti-platform/opencti-front/lang/front/ru.json b/opencti-platform/opencti-front/lang/front/ru.json index 7b4effa552e3..bfc8145dbf69 100644 --- a/opencti-platform/opencti-front/lang/front/ru.json +++ b/opencti-platform/opencti-front/lang/front/ru.json @@ -195,6 +195,7 @@ "Agentic AI (Ariane Assistant)": "Агентный искусственный интеллект (помощник Ариана)", "Agentic AI capabilities": "Возможности агентного ИИ", "AI Insights": "Инсайты ИИ", + "AI is not enabled": "ИИ не включен", "AI Powered": "На базе ИИ", "AI Summary": "Краткое резюме ИИ", "alert_GROUP_WITH_NULL_CONFIDENCE_LEVEL": "alert_GROUP_WITH_NULL_CONFIDENCE_LEVEL", @@ -1918,12 +1919,12 @@ "Heatmap": "Тепловая карта", "Height": "Высота", "here": "здесь", + "Hidden": "Скрытый", "Hidden entity types": "Скрытые типы сущностей", "Hidden in ": "Скрыто в", "Hidden in groups": "Скрытые в группах", "Hidden in interface": "Скрыто в интерфейсе", "Hidden in organizations": "Скрытые в организациях", - "Hidden": "Скрытый", "Hide": "Скрыть", "Hide column": "Скрыть колонку", "Hide entities in container": "Скрыть сущности в контейнере", @@ -3802,8 +3803,8 @@ "text/markdown": "текст/маркдаун", "text/plain": "текст/plain", "Textarea": "Textarea", - "Thank you!": "Спасибо!", "Thank you for reaching out, we'll get back to you shortly.": "Спасибо за обращение, мы скоро вам ответим.", + "Thank you!": "Спасибо!", "The alias has been added": "Псевдоним был добавлен", "The alias has been removed": "Псевдоним был удален", "The amount of results can differ based on the data/relationships, as each occurrence of an inferred relation is counted for this widget.": "Количество результатов может отличаться в зависимости от данных/отношений, поскольку для этого виджета учитывается каждое появление предполагаемого отношения.", @@ -4515,6 +4516,7 @@ "You need to enable EE License to use this feature": "Для использования этой функции необходимо включить лицензию EE License.", "You need to validate your two-factor authentication. Please type the code generated in your application": "Вам необходимо подтвердить двухфакторную аутентификацию. Пожалуйста, введите код, сгенерированный в вашем приложении", "You see only marking definitions that can be shared (defined by the admin)": "Вы видите только те определения маркировки, которые могут быть доступны (определены администратором).", + "You should activate EE to use this feature": "Чтобы использовать эту функцию, необходимо активировать EE", "You should provide a variable name": "Вы должны указать имя переменной", "You will be able to revert this change if needed. ": "При необходимости вы сможете отменить это изменение.", "You will be automatically logged out at end of the timer.": "По окончании таймера вы автоматически выйдете из системы.", @@ -4541,4 +4543,4 @@ "Zoom": "Zoom", "Zoom in": "Увеличить", "Zoom out": "Уменьшить масштаб" -} +} \ No newline at end of file diff --git a/opencti-platform/opencti-front/lang/front/zh.json b/opencti-platform/opencti-front/lang/front/zh.json index e20b23409590..e0078c387a95 100644 --- a/opencti-platform/opencti-front/lang/front/zh.json +++ b/opencti-platform/opencti-front/lang/front/zh.json @@ -195,6 +195,7 @@ "Agentic AI (Ariane Assistant)": "人工智能代理(阿丽亚娜助理)", "Agentic AI capabilities": "人工智能代理能力", "AI Insights": "人工智能洞察", + "AI is not enabled": "未启用人工智能", "AI Powered": "人工智能技术", "AI Summary": "人工智能摘要", "alert_GROUP_WITH_NULL_CONFIDENCE_LEVEL": "从 OpenCTI 6.0 开始,将为组和用户添加最大置信度。这将为平台管理员提供更多对导入数据影响的控制,并防止任何不必要的数据更新。请阅读 {link_blogpost} 获取更多信息。\n \n在本平台中,某些组的 \"Max Confidence Level\" 目前未定义。这些组的成员将无法在 6.X 版平台中创建任何数据。为避免出现这种情况,请确保所有组都设置了最大置信度。更多信息请参见 {link_blogpost}。请特别注意与连接器、馈送和数据流相关联的用户;必要时,您可以设置个人置信度来覆盖组置信度。如果需要建议,请加入 {link_slack} 。", @@ -1918,12 +1919,12 @@ "Heatmap": "热图", "Height": "高度", "here": "这里", + "Hidden": "隐藏", "Hidden entity types": "隐藏的实体类型", "Hidden in ": "隐藏在", "Hidden in groups": "隐藏在群体中", "Hidden in interface": "隐藏在界面中", "Hidden in organizations": "隐藏在组织中", - "Hidden": "隐藏", "Hide": "隐藏", "Hide column": "隐藏栏", "Hide entities in container": "隐藏容器中的实体", @@ -3802,8 +3803,8 @@ "text/markdown": "MD", "text/plain": "TXT", "Textarea": "文本区域", - "Thank you!": "感谢您的联系", "Thank you for reaching out, we'll get back to you shortly.": "感谢您的联系,我们会尽快回复您。", + "Thank you!": "感谢您的联系", "The alias has been added": "别名已添加", "The alias has been removed": "别名已删除", "The amount of results can differ based on the data/relationships, as each occurrence of an inferred relation is counted for this widget.": "结果的数量可能因数据/关系而异,因为推断关系的每次出现都会计入该 widget 中。", @@ -4515,6 +4516,7 @@ "You need to enable EE License to use this feature": "您需要启用 EE 许可才能使用此功能", "You need to validate your two-factor authentication. Please type the code generated in your application": "您需要验证您的双因素身份验证。请输入在您的应用程序中生成的代码。", "You see only marking definitions that can be shared (defined by the admin)": "您只能看到可以共享的标记定义(由管理员定义)", + "You should activate EE to use this feature": "您应激活 EE 才能使用此功能", "You should provide a variable name": "您应该提供一个变量名", "You will be able to revert this change if needed. ": "如果需要,您可以恢复这一更改。", "You will be automatically logged out at end of the timer.": "计时器结束时您将自动注销", @@ -4541,4 +4543,4 @@ "Zoom": "放大", "Zoom in": "放大", "Zoom out": "缩小" -} +} \ No newline at end of file diff --git a/opencti-platform/opencti-front/src/private/components/common/entreprise_edition/EEChip.tsx b/opencti-platform/opencti-front/src/private/components/common/entreprise_edition/EEChip.tsx index 3d4411ff1832..06e31344f70a 100644 --- a/opencti-platform/opencti-front/src/private/components/common/entreprise_edition/EEChip.tsx +++ b/opencti-platform/opencti-front/src/private/components/common/entreprise_edition/EEChip.tsx @@ -1,5 +1,4 @@ -import makeStyles from '@mui/styles/makeStyles'; -import React, { MouseEvent, useState } from 'react'; +import React, { CSSProperties, MouseEvent, useState } from 'react'; import FeedbackCreation from '@components/cases/feedbacks/FeedbackCreation'; import EnterpriseEditionAgreement from '@components/common/entreprise_edition/EnterpriseEditionAgreement'; import { useFormatter } from '../../../../components/i18n'; @@ -7,45 +6,11 @@ import type { Theme } from '../../../../components/Theme'; import useEnterpriseEdition from '../../../../utils/hooks/useEnterpriseEdition'; import useGranted, { SETTINGS_SETPARAMETERS } from '../../../../utils/hooks/useGranted'; import useAuth from '../../../../utils/hooks/useAuth'; - -// Deprecated - https://mui.com/system/styles/basics/ -// Do not use it for new code. -const useStyles = makeStyles((theme) => ({ - container: { - fontSize: 'xx-small', - height: 18, - display: 'inline-flex', - justifyContent: 'center', - alignItems: 'center', - width: 21, - margin: 'auto', - marginLeft: 6, - borderRadius: theme.borderRadius, - border: `1px solid ${theme.palette.ee.main}`, - color: theme.palette.ee.main, - backgroundColor: theme.palette.ee.background, - cursor: 'pointer', - }, - containerFloating: { - float: 'left', - fontSize: 'xx-small', - height: 18, - display: 'inline-flex', - justifyContent: 'center', - alignItems: 'center', - width: 21, - margin: '2px 0 0 6px', - borderRadius: theme.borderRadius, - border: `1px solid ${theme.palette.ee.main}`, - color: theme.palette.ee.main, - backgroundColor: theme.palette.ee.background, - cursor: 'pointer', - }, -})); +import { useTheme } from '@mui/material/styles'; const EEChip = React.forwardRef(({ feature, clickable = true, floating = false }, ref) => { - const classes = useStyles(); const isEnterpriseEdition = useEnterpriseEdition(); + const theme = useTheme(); const { t_i18n } = useFormatter(); const [displayDialog, setDisplayDialog] = useState(false); const isAdmin = useGranted([SETTINGS_SETPARAMETERS]); @@ -56,12 +21,43 @@ const EEChip = React.forwardRef
    onClick(e)} > EE diff --git a/opencti-platform/opencti-front/src/private/components/settings/Settings.tsx b/opencti-platform/opencti-front/src/private/components/settings/Settings.tsx index 13277a4f856d..e268eb60dbed 100644 --- a/opencti-platform/opencti-front/src/private/components/settings/Settings.tsx +++ b/opencti-platform/opencti-front/src/private/components/settings/Settings.tsx @@ -211,6 +211,23 @@ const SettingsComponent = ({ queryRef }: SettingsComponentProps) => { setTitle(t_i18n('Parameters | Settings')); + // generate AI Powered label and tooltip + let aiPoweredLabel; + let aiPoweredTooltip; + if (!isEnterpriseEditionValid) { + aiPoweredLabel = t_i18n('Disabled'); + aiPoweredTooltip = t_i18n('You should activate EE to use this feature'); + } else if (!settings.platform_ai_enabled) { + aiPoweredLabel = t_i18n('Disabled'); + aiPoweredTooltip = t_i18n('AI is not enabled'); + } else if (settings.platform_ai_has_token) { + aiPoweredLabel = settings.platform_ai_type; + aiPoweredTooltip = `${settings.platform_ai_type} - ${settings.platform_ai_model}`; + } else { + aiPoweredLabel = `${settings.platform_ai_type} - ${t_i18n('Missing token')}`; + aiPoweredTooltip = t_i18n('The token is missing in your platform configuration, please ask your Filigran representative to provide you with it or with on-premise deployment instructions. Your can open a support ticket to do so.'); + }; + const settingsValidation = () => Yup.object().shape({ platform_title: Yup.string().required(t_i18n('This field is required')), platform_favicon: Yup.string().nullable(), @@ -688,16 +705,18 @@ const SettingsComponent = ({ queryRef }: SettingsComponentProps) => { + {t_i18n('AI Powered')} + + + )} /> From a6e5357db6012b75a7bad205065104bfb2511f40 Mon Sep 17 00:00:00 2001 From: Landry Trebon <33682259+lndrtrbn@users.noreply.github.com> Date: Mon, 5 Jan 2026 11:47:49 +0100 Subject: [PATCH 025/126] [frontend] Move component KillchainPhasesField to Typescript (#13845) --- .../arsenal/tools/ToolEditionOverview.tsx | 1 - .../common/form/KillChainPhasesField.jsx | 96 ---------------- .../common/form/KillChainPhasesField.tsx | 106 ++++++++++++++++++ .../StixCoreRelationshipEditionOverview.tsx | 3 +- .../PlaybookActionValueField.tsx | 4 +- .../InfrastructureEditionOverview.tsx | 1 - .../opencti-front/src/utils/field.tsx | 7 -- 7 files changed, 109 insertions(+), 109 deletions(-) delete mode 100644 opencti-platform/opencti-front/src/private/components/common/form/KillChainPhasesField.jsx create mode 100644 opencti-platform/opencti-front/src/private/components/common/form/KillChainPhasesField.tsx diff --git a/opencti-platform/opencti-front/src/private/components/arsenal/tools/ToolEditionOverview.tsx b/opencti-platform/opencti-front/src/private/components/arsenal/tools/ToolEditionOverview.tsx index dbcdc80e1aa7..31795aa15112 100644 --- a/opencti-platform/opencti-front/src/private/components/arsenal/tools/ToolEditionOverview.tsx +++ b/opencti-platform/opencti-front/src/private/components/arsenal/tools/ToolEditionOverview.tsx @@ -280,7 +280,6 @@ const ToolEditionOverview: FunctionComponent = ({ } onChange={editor.changeKillChainPhases} diff --git a/opencti-platform/opencti-front/src/private/components/common/form/KillChainPhasesField.jsx b/opencti-platform/opencti-front/src/private/components/common/form/KillChainPhasesField.jsx deleted file mode 100644 index f84ec32c9ac3..000000000000 --- a/opencti-platform/opencti-front/src/private/components/common/form/KillChainPhasesField.jsx +++ /dev/null @@ -1,96 +0,0 @@ -import React, { Component } from 'react'; -import { compose, pathOr, pipe, map, sortWith, ascend, path, union } from 'ramda'; -import { Field } from 'formik'; -import withStyles from '@mui/styles/withStyles'; -import { fetchQuery } from '../../../../relay/environment'; -import AutocompleteField from '../../../../components/AutocompleteField'; -import inject18n from '../../../../components/i18n'; -import { killChainPhasesSearchQuery } from '../../settings/KillChainPhases'; -import ItemIcon from '../../../../components/ItemIcon'; - -const styles = () => ({ - icon: { - paddingTop: 4, - display: 'inline-block', - }, - text: { - display: 'inline-block', - flexGrow: 1, - marginLeft: 10, - }, -}); - -class KillChainPhasesField extends Component { - constructor(props) { - super(props); - this.state = { - killChainPhases: [], - }; - } - - searchKillChainPhases(event) { - fetchQuery(killChainPhasesSearchQuery, { - search: event && event.target.value, - }) - .toPromise() - .then((data) => { - const killChainPhases = pipe( - pathOr([], ['killChainPhases', 'edges']), - sortWith([ascend(path(['node', 'x_opencti_order']))]), - map((n) => ({ - label: `[${n.node.kill_chain_name}] ${n.node.phase_name}`, - value: n.node.id, - kill_chain_name: n.node.kill_chain_name, - phase_name: n.node.phase_name, - })), - )(data); - this.setState({ - killChainPhases: union(this.state.killChainPhases, killChainPhases), - }); - }); - } - - render() { - const { - t, - name, - style, - classes, - onChange, - helpertext, - disabled, - required = false, - } = this.props; - return ( - ( -
  • -
    - -
    -
    {option.label}
    -
  • - )} - /> - ); - } -} - -export default compose(inject18n, withStyles(styles))(KillChainPhasesField); diff --git a/opencti-platform/opencti-front/src/private/components/common/form/KillChainPhasesField.tsx b/opencti-platform/opencti-front/src/private/components/common/form/KillChainPhasesField.tsx new file mode 100644 index 000000000000..6ca07a6db1f8 --- /dev/null +++ b/opencti-platform/opencti-front/src/private/components/common/form/KillChainPhasesField.tsx @@ -0,0 +1,106 @@ +import { CSSProperties, HTMLAttributes, ReactNode, SyntheticEvent, useState } from 'react'; +import { union } from 'ramda'; +import { Field } from 'formik'; +import { fetchQuery } from '../../../../relay/environment'; +import AutocompleteField from '../../../../components/AutocompleteField'; +import { killChainPhasesSearchQuery } from '../../settings/KillChainPhases'; +import ItemIcon from '../../../../components/ItemIcon'; +import { useFormatter } from '../../../../components/i18n'; +import { FieldOption } from '../../../../utils/field'; +import { KillChainPhasesSearchQuery$data } from '../../settings/__generated__/KillChainPhasesSearchQuery.graphql'; +import { getNodes } from '../../../../utils/connection'; + +interface KillChainPhaseFieldOption extends FieldOption { + kill_chain_name: string; + phase_name: string; +} + +interface KillChainPhasesFieldProps { + name: string; + onChange?: (name: string, value: KillChainPhaseFieldOption[]) => void; + style?: CSSProperties; + helpertext?: ReactNode; + disabled?: boolean; + required?: boolean; +} + +const KillChainPhasesField = ({ + style, + name, + onChange, + helpertext, + disabled, + required = false, +}: KillChainPhasesFieldProps) => { + const { t_i18n } = useFormatter(); + const [killChainPhases, setKillChainPhases] = useState([]); + + const searchKillChainPhases = (event?: SyntheticEvent) => { + if (event?.target instanceof HTMLInputElement) { + const search = event.target.value ?? ''; + fetchQuery(killChainPhasesSearchQuery, { search }) + .toPromise() + .then((data) => { + const dataNodes = getNodes((data as KillChainPhasesSearchQuery$data).killChainPhases); + dataNodes.sort((a, b) => (a.x_opencti_order ?? 0) - (b.x_opencti_order ?? 0)); + const kcp = dataNodes.map((node) => { + return { + label: `[${node.kill_chain_name}] ${node.phase_name}`, + value: node.id, + kill_chain_name: node.kill_chain_name, + phase_name: node.phase_name, + }; + }); + setKillChainPhases(union(killChainPhases, kcp)); + }); + } + }; + + return ( + , + option: KillChainPhaseFieldOption, + ) => ( +
  • +
    + +
    +
    {option.label} +
    +
  • + )} + /> + ); +}; + +export default KillChainPhasesField; diff --git a/opencti-platform/opencti-front/src/private/components/common/stix_core_relationships/StixCoreRelationshipEditionOverview.tsx b/opencti-platform/opencti-front/src/private/components/common/stix_core_relationships/StixCoreRelationshipEditionOverview.tsx index b9888835ed28..25b422740c9f 100644 --- a/opencti-platform/opencti-front/src/private/components/common/stix_core_relationships/StixCoreRelationshipEditionOverview.tsx +++ b/opencti-platform/opencti-front/src/private/components/common/stix_core_relationships/StixCoreRelationshipEditionOverview.tsx @@ -420,7 +420,6 @@ const StixCoreRelationshipEditionOverviewComponent: FunctionComponent< ; } - // eslint-disable-next-line max-len + const stixCoreRelationship = useFragment( StixCoreRelationshipEditionOverviewFragment, queryData.stixCoreRelationship, diff --git a/opencti-platform/opencti-front/src/private/components/data/playbooks/playbookFlow/playbookFlowFields/playbookFlowFieldsActions/PlaybookActionValueField.tsx b/opencti-platform/opencti-front/src/private/components/data/playbooks/playbookFlow/playbookFlowFields/playbookFlowFieldsActions/PlaybookActionValueField.tsx index 3c6954375b09..d521a8374bfb 100644 --- a/opencti-platform/opencti-front/src/private/components/data/playbooks/playbookFlow/playbookFlowFields/playbookFlowFieldsActions/PlaybookActionValueField.tsx +++ b/opencti-platform/opencti-front/src/private/components/data/playbooks/playbookFlow/playbookFlowFields/playbookFlowFieldsActions/PlaybookActionValueField.tsx @@ -15,7 +15,7 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. import { Field, useFormikContext } from 'formik'; import SwitchField from '../../../../../../../components/fields/SwitchField'; -import { FieldOption, KillChainPhaseFieldOption } from '../../../../../../../utils/field'; +import { FieldOption } from '../../../../../../../utils/field'; import { isEmptyField } from '../../../../../../../utils/utils'; import CreatedByField from '../../../../../common/form/CreatedByField'; import KillChainPhasesField from '../../../../../common/form/KillChainPhasesField'; @@ -250,7 +250,7 @@ const PlaybookActionValueField = ({ return ( { + onChange={(_, killChainPhases) => { setFieldValue( valueName, killChainPhases.map((kcp) => ({ diff --git a/opencti-platform/opencti-front/src/private/components/observations/infrastructures/InfrastructureEditionOverview.tsx b/opencti-platform/opencti-front/src/private/components/observations/infrastructures/InfrastructureEditionOverview.tsx index 2e4e790c046f..eae0d4b6106f 100644 --- a/opencti-platform/opencti-front/src/private/components/observations/infrastructures/InfrastructureEditionOverview.tsx +++ b/opencti-platform/opencti-front/src/private/components/observations/infrastructures/InfrastructureEditionOverview.tsx @@ -347,7 +347,6 @@ const InfrastructureEditionOverviewComponent: FunctionComponent = Omit, 'component' | 'as' | 'render' | 'children'>; type NoMetaProps

    = Omit; type FieldProps = FormikFieldConfig & NoMetaProps & { From c365f61fb957c592ac955c8fb610d7c3aedcf17b Mon Sep 17 00:00:00 2001 From: "A. Jard" Date: Mon, 5 Jan 2026 12:58:03 +0100 Subject: [PATCH 026/126] [docs] update documentation on yarn command for tests (#13317) --- docs/docs/development/platform.md | 151 ++++++++++++++++-------------- 1 file changed, 83 insertions(+), 68 deletions(-) diff --git a/docs/docs/development/platform.md b/docs/docs/development/platform.md index abb80571b3af..5d42fa5167b5 100644 --- a/docs/docs/development/platform.md +++ b/docs/docs/development/platform.md @@ -34,20 +34,6 @@ Fork and clone the git repositories - [https://github.com/OpenCTI-Platform/opencti/](https://github.com/OpenCTI-Platform/opencti/) - frontend / backend - [https://github.com/OpenCTI-Platform/connectors](https://github.com/OpenCTI-Platform/connectors) - connectors -- [https://github.com/OpenCTI-Platform/docker](https://github.com/OpenCTI-Platform/docker) - docker stack - -## Dependencies containers - -In development dependencies are deployed trough containers. -A development compose file is available in `~/opencti/opencti-platform/opencti-dev` - -```bash -cd ~/docker -#Start the stack in background -docker-compose -f ./docker-compose-dev.yml up -d -``` - -You now have all the dependencies of OpenCTI running and waiting for product to run. ## Backend / API @@ -86,9 +72,28 @@ At minimum adapt the admin part for the password and token. } ``` -### Install / start +### Install / start OpenCTI for development + +For development setup, you need to run both the backend and frontend. -Before starting the backend you need to install the nodejs modules +#### Start infrastructure with docker composer + +```bash +cd ~/opencti/opencti-platform/opencti-dev +docker compose up -d +``` + +You should have: +- elasticsearch +- kibana +- redis +- redis insight +- rabbitmq +- minio + +#### Start backend + +Before starting the backend you need to install the nodejs modules and be sure to have started docker compose stack. ```bash cd ~/opencti/opencti-platform/opencti-graphql @@ -114,23 +119,61 @@ The platform will start logging some interesting information {"category":"APP","level":"info","message":"[OPENCTI] API ready on port 4000","timestamp":"2023-07-02T16:37:12.382Z","version":"5.8.7"} ``` -If you want to start on another profile you can use the -e parameter. -For example here to use the profile.json configuration file. +OpenCTI api should be now up and running on [http://127.0.0.1:4000](http://127.0.0.1:4000). Note that without frontend started only api call are avlaible. Please follow next section for a complete development setup. + +#### Start frontend + +Before starting the frontend you need to install. + +```bash +cd ~/opencti/opencti-platform/opencti-front +yarn install +``` + +Then you can start the frontend ```bash -yarn start -e profile +cd ~/opencti/opencti-platform/opencti-front +yarn start ``` + +OpenCTI should be now up and running on [http://127.0.0.1:3000](http://127.0.0.1:3000) + +The frontend will start with some interesting information + +```log +[INFO] [default] compiling... +[INFO] [default] compiled documents: 1592 reader, 1072 normalization, 1596 operation text +[INFO] Compilation completed. +[INFO] Done. +[HPM] Proxy created: /stream -> http://localhost:4000 +[HPM] Proxy created: /storage -> http://localhost:4000 +[HPM] Proxy created: /taxii2 -> http://localhost:4000 +[HPM] Proxy created: /feeds -> http://localhost:4000 +[HPM] Proxy created: /graphql -> http://localhost:4000 +[HPM] Proxy created: /auth/** -> http://localhost:4000 +[HPM] Proxy created: /static/flags/** -> http://localhost:4000 +``` + ### Code check Before pushing your code you need to validate the syntax and ensure the testing will be validated. #### For validation -`yarn lint` +```bash +cd ~/opencti/opencti-platform/opencti-front +yarn lint +yarn check-ts + +cd ~/opencti/opencti-platform/opencti-graphql +yarn lint +yarn check-ts +``` -`yarn check-ts` +### For testing -#### For testing +#### Backend tests For starting the test you will need to create a test.json configuration file. You can use the same dependencies by only adapting all prefix for all dependencies. @@ -144,68 +187,40 @@ The file `vitest.config.test.ts` can be edited to run only a specific file patte `yarn test:dev` We also provide utility scripts to ease the development of new tests, especially integration tests that rely on the sample data -loaded after executing `00-inject/loader-test.ts`. +loaded after executing `tests/02-dataInjection`. To solely initialize the test database with this sample dataset run: `yarn test:dev:init` -And then, execute the following command to run the pattern specified in the file `vitest.config.test.ts`, or add a file name -to the command line to run only this test file. +And then, execute the following command to run only this test file. -`yarn test:dev:resume` +`yarn test:dev:resume ` This last command will NOT cleanup & initialize the test database and thus will be quicker to execute. -## Frontend - -### Install / start - -Before starting the backend you need to install the nodejs modules +Some yarn command are made for CI but can be used locally too: ```bash -cd ~/opencti/opencti-platform/opencti-front -yarn install -``` +cd ~/opencti/opencti-platform/opencti-graphql -Then you can simply start the frontend with the yarn start command +# Run unit tests, no need for elastic or redis +yarn test:ci-unit -```bash -cd ~/opencti/opencti-platform/opencti-front -yarn start -``` +# Run data injection + integration domain & resolvers test, but not sync part +yarn test:ci-integration -The frontend will start with some interesting information +# Run data injection + integration domain & resolvers test, including sync part +yarn test:ci-integration-sync -```log -[INFO] [default] compiling... -[INFO] [default] compiled documents: 1592 reader, 1072 normalization, 1596 operation text -[INFO] Compilation completed. -[INFO] Done. -[HPM] Proxy created: /stream -> http://localhost:4000 -[HPM] Proxy created: /storage -> http://localhost:4000 -[HPM] Proxy created: /taxii2 -> http://localhost:4000 -[HPM] Proxy created: /feeds -> http://localhost:4000 -[HPM] Proxy created: /graphql -> http://localhost:4000 -[HPM] Proxy created: /auth/** -> http://localhost:4000 -[HPM] Proxy created: /static/flags/** -> http://localhost:4000 +# Run data injection + rule tests +test:ci-rules-and-others ``` +Those command works also for a single file, for example to run only one unit test: -The web UI should be accessible on [http://127.0.0.1:3000](http://127.0.0.1:3000) - -### Code check - -Before pushing your code you need to validate the syntax and ensure the testing will be validated. - -#### For validation - -`yarn lint` - -`yarn check-ts` - -#### For testing - -`yarn test` +```bash +yarn test:ci-unit +``` ## Worker @@ -264,4 +279,4 @@ The Pull Request on ***opencti*** repository should be (issue or bug)/number + o The pull request on ***connector*** should refer to the opencti one by starting with "opencti/" and then the same name. Example: `opencti/issue/7062-contributing` -Note that if there are several matches, the first one is taken. So for example having `issue/7062-contributing` and `issue/7062` that are both marked as "multi-repository" is not a good idea. +Note that if there are several matches, the first one is taken. So for example having `issue/7062-contributing` and `issue/7062` that are both marked as "multi-repository" is not a good idea. \ No newline at end of file From 5621343f80042b9f3882c844411b0e7203b962a0 Mon Sep 17 00:00:00 2001 From: hervyt Date: Mon, 5 Jan 2026 14:21:28 +0100 Subject: [PATCH 027/126] [frontend/backend] - (TaxiiFeeds) Add import/export of taxii feeds #13848 --- docs/docs/usage/assets/taxii-feeds-export.png | Bin 0 -> 35960 bytes .../usage/assets/taxii-feeds-import-icon.png | Bin 0 -> 17409 bytes docs/docs/usage/import/taxii-feed.md | 17 ++ .../opencti-front/lang/front/de.json | 2 + .../opencti-front/lang/front/en.json | 2 + .../opencti-front/lang/front/es.json | 2 + .../opencti-front/lang/front/fr.json | 2 + .../opencti-front/lang/front/it.json | 2 + .../opencti-front/lang/front/ja.json | 2 + .../opencti-front/lang/front/ko.json | 2 + .../opencti-front/lang/front/ru.json | 2 + .../opencti-front/lang/front/zh.json | 2 + .../private/components/data/IngestionCsv.tsx | 2 +- ...ngestionTaxiis.jsx => IngestionTaxiis.tsx} | 58 ++++- ...reation.jsx => IngestionTaxiiCreation.tsx} | 237 ++++++++++-------- .../ingestionTaxii/IngestionTaxiiImport.tsx | 119 +++++++++ .../ingestionTaxii/IngestionTaxiiPopover.tsx | 36 ++- .../src/schema/relay.schema.graphql | 13 + .../opencti-graphql/src/generated/graphql.ts | 35 +++ .../ingestion/ingestion-taxii-domain.ts | 46 +++- .../ingestion/ingestion-taxii-resolver.ts | 4 + .../modules/ingestion/ingestion-taxii.graphql | 15 ++ .../02-resolvers/ingestion-taxii-test.ts | 61 ++++- .../tests/data/taxiiFeed/test-taxii-feed.json | 9 + 24 files changed, 550 insertions(+), 120 deletions(-) create mode 100644 docs/docs/usage/assets/taxii-feeds-export.png create mode 100644 docs/docs/usage/assets/taxii-feeds-import-icon.png rename opencti-platform/opencti-front/src/private/components/data/{IngestionTaxiis.jsx => IngestionTaxiis.tsx} (63%) rename opencti-platform/opencti-front/src/private/components/data/ingestionTaxii/{IngestionTaxiiCreation.jsx => IngestionTaxiiCreation.tsx} (54%) create mode 100644 opencti-platform/opencti-front/src/private/components/data/ingestionTaxii/IngestionTaxiiImport.tsx create mode 100644 opencti-platform/opencti-graphql/tests/data/taxiiFeed/test-taxii-feed.json diff --git a/docs/docs/usage/assets/taxii-feeds-export.png b/docs/docs/usage/assets/taxii-feeds-export.png new file mode 100644 index 0000000000000000000000000000000000000000..3b25993b64417eeeca0a0a3b87788a9f3e6a822a GIT binary patch literal 35960 zcmcG0WmH>Tw=R?xO0fbhZly>muEEn5ic2Z9xH|+3ZUIWM;_jryDemqXw73U%C%EON z@B5uI&bWW>k9+UR$Vm3i%3gcPoa>qMd6JM%O44}GD4wCAq2bBONPb2`!yrOKL!W($ zi8@2%gcpx`L3jKt{Q<3Hm}&=g@Ywvl;(Ii-@<^OJ!zZX?YI72&c9FeUb}n~ zG_){0S;_ZmZhHHR?mAy4Z+cHg&|ly_mhC2Ykdvo!=pkff9H;94#5$;Lp~-hYQIZl* z4yZ1YX|y!`Tq`-iH}OYq0yj_cV>!>n^NAPyk6*p_L)@-WEvK>P$ECDg-h0>xnircB zn8Qc(tPgC!1zNpf!GDVkiy#?})M4Ze&Uz90eiGKdKPV{G0`d9x{%E^`d z@b^-Za*F@m77_N}I{&XO6b&|kUzqTWuWAVNYmA2nIKWJDySYX*&Jz_GFWh*rfaIW} ztJIQ7@P^gre{SkCe0?f=X-56HoaHoG)bIT{xUYug_Le?4i57OJtgdksR3^;gHyKIU zAvaW~Q&0GBpIFStTUF6wI`El9`ER zgHz;MJLWXIau~}Fvt;lys=CsC=u29xeY<)7m6?$kKJ-iuwK;W|E=6 zgfMwJoD0wH= zd_vve+d1T~d1V@`7$qV;xkffVOS$y^==l+0TXrpIuNaP(nX!c{kZWG%@B?5wm42mX z_wl&^*_1eSaB3b91Frb1d&d6uP`L!oCt4NEz4jsb?w4XcySkldjd)1u-x>;Sl|B|K zq4I0$Nf&jrJG7XqU&kqz97eNiDLW`HDHC3)$Ny(YE$$^dS_gh1x(d#~5wBN6hI*cQ zh^;Flh`B{YIAYxBEXP5Zk_^iWEK!yLD&%(+W&)H1n!BxYv3}AAxh?}Nnt+}Q%l?z? zg0O2R+Nh46GCKMT;c@rA@0n#qk_n>s`0|NG1_vDT*ES}M6#^h$+3YfIwv@%4!-BaV3_0hyyK}?7-?3V>6Su?m+^bdh)+F2YzeR558!utMq|=H)_19DmX+J+5uIu zi^fVs6k=6_1|E3yMMdeD2gE~!967K^BU4;VeFjn_W5ASuE;bU-u5b}q0n*P-^vN;) zd&%lir?j5|%qk5&|0u`wy&B(H-ha!`6o-)pgMXg%^s&ah8{;+~ z&`hn-4s@~e5>A4#{#?=?MVW2I%?aim4=@h>h+^wrV2%e4AyXdpu#6+g+q%kA^8xKQUG8zr`!&+XU|6&Yka~Bfu#FX`%Y&KkJH+WxQi4+$2jC4N9VO#=)NK zyw*HJE$)936gCR3&i}_f_TjzLP0cP+3H>k6`Ty3N{U6+7vd-@*Zc3bwk|jd1u!q(- zPP08_`F0<|`XVWDJXs|nD>jmPLzGEJTaTo$B~cY2xmlv$9D#^JGE=WFoav2F4nK|` z<+j)7c#_%bzh%9~XTq`vqs3xl#b#BP&_#dxppu~xgUP!o%4_B*zS*QRc>PX~#%xfI zN%F@p$q)S$DBll0NK#0mQb z$dEry{dzi38Ga2K+;W7q+S54dWiXC@;(7HyZsd&&{ac|`jahQ(>D2u7L#dqKhk^|a zD;l#;R2j{wsHoVAiR762pH$AyrL%AnAg*=5m6foe!PO6&nqXeuzJZ(Z7`63HJ*MP0 z1v=|i4dpGaaWXQRS&|>JaSL_oi4G4B)f^nkcj%qGlZMQOP0eGYquZ|$r2DMJi^vH5_Cfb}0Lll-{x?^}fS#8VFTDcd)$|oW4i4Qzc6WB9 z=8dAMrK1D3k>tb%Jz+2t7XIYH3#83%sIF4^yy~FKY=Z6!^|6Z?JN0OzO~6BKXeFBn z=a%S>2!0erb!ymm2Jo0tw5LGUo8;wHTct0Qw_|Qs;|mK7q!Gg$F3$Ao5_(DkKKrwM zzjEvxEz$0z|TN>*FZq{%2& zD>ErDDMa84s)0LTi+G7zfHjD`XUgL3GyHg6kV{&zNQv*P*;eh(CIhVMV@Ub=N0H?x zBIQp+dPs-#?OSK4%~Po@29p%0kTvs`cgmwmp6}h;1vC;&hMqmMqeRY6%nqxMAs+>9 zkU@KiXrTv9E(0s8=Q?p=I(JJIH1m@A>l>)Y@m{EYbc;1`)+deO5`=kOzs0?^HOol! zx3`8pV8-WQ-SrKVO5@!qN~Yvb^sh`&n`Jc5Z>jLzb(1Z70p4*9XWcK(j-9jJ$*Oru zqzNy>LhZo?A2ui^?JYGD1HChP#%o1dOOa{*$kgtDuuM0W*0+oMN|hn6^BwJ!$BcaF zcMguO5m$zP#~SKgV$t*%&1_B-`HElUf|u>%`kc`9n%1iRt@?YZ`^W~55J`-{R^e33 ztcH~}^!3fC46>mCg6|c}QQTikT2cG2V$Q`|ySogosd)AVx^x-xFWub%ML;AUiN33* z&x7ns($uWpgwC<$WwdxIhx}Cq)5za-OJjt@Z$k17IPRj`{WL?lliRY4RnrywD=mE3Z%|Vl zS`nT5U&v&@POUOnKGZOyyzUWd@IFf=E9CCM(+dQzudhiXN4S!?lBu|agp7Xge;-%m z5c~@6!Ih(b`xD_~Vj$q3<;X}?Shu>pjxty5SpStUU9-vtKA7{?_C9!2Fuj637;tDK zd*jNxEy5BL9z+Kj5(#UvLsx0dT^^kwn1I{b zVT7uT%w-X}Q9ocYoJoJ(18nppa9fK0GspRkgkvc#M3dH1A7UZ!`Y5%1>|2TAeO`sI zc=TWu3ctk&H3+3ebuhgCJ}C?9@}^N*Ae8ss^t0GJBT!VLzVJ0VM|_Q8Ro~ya4cSCruLQ=;ISKn^my*rKxevMDN}6n3)welcu(Cq1GAo- zNE>P1R}9^3_CzIDrHy2+_9@6S*>b$|HKgWdqcX04d{OxdD(6CmWMAmb`U5%%mCqc;mNxNFVU|9Q)AsxU_#wglB{+W37rz+~`ni8M<@q-h3`g)lWPA^>78GmKAL) zalNuq#;m4r;SEIpv-ip;1sc3E>yU7jtyoF^JlfYW?_DnX9~#;c$=l+ag9fJZV*RmbYUfBE zzVH`p?ka5kkyN5)N&2w8|BQp?$+?ipyv@63vWyuj{iFm0eXu!@y2qvtJ_il|LI1u& zvf>rfHo%*(+!AKO)Lr2Lb`U`%!fBv?uDhsZ)+ZeHjO%Mz1lx0@oX%70)BI&WA{6#^ z@s)bsgbGZG(J`lo*94e@`y9@HlAwvm$~os5PPZqJ{BlvX)CL6U*GSF`pG2a(8U1t+ z%<1GOsDSHiE>wqkZ@LmVnNFf>)60q$&RiyJ){+B#D0iwzPb)CfUh2g#ACpDS)*P1# z!;*_|1p@iAr0z3eLzT-;dCWqw8)2b~n0!AK8;N>2quzN43pUO%qfh=8j7^7(l?>|P z#vbAP)U%PF&OJ%}Ii2*!NQ;Q0TC9B#2QParp?o6W#+yx_stk{xy;4#em+o8cm2Hzqi>n&onDQ% zJg&~lMV+=b#y5?bmhA7nZ7^yyC=K!U^a8k1kP;YS>Qjui*+eQyX?^#fuz=dP{kz!4 zNC@DYeId%A74^NO*~*+8Z-P)Ou%PXC?cw|LDY3Bb>0m8}w>+^*^Nip{5DMk@k}|D2 zBH6z7G7{g)zG(($x(nFzB@A=nkR5kDasl8PI_vNxb61k>zZhD_Vw!U;y{7C43anVp z=NxYdW8`H|n0z%Pc-Q@iZHw=l<{EPu-E86?PGeYDC_|AoCM^))XOBiCBfJR`vW`7x zlc9v-o1H$}zWiEc>K+MLPQ3nwTi5mTG{5YL*jlGRD!~0|8t!LPV-iI0Mo~pRW?-1SC*)7B|<6yjX1e!8BP2ZDp+BRJ$?^Z zeGQ0(v-Noh6j-bhZ2a+xJKz7j$IJr)^DT&n&QzHYwc!3*d3f4`t81VOc`KWjz9TAB zam@B9*%FpEM>7l*ll!1vL&B2PL^;7Mv=Ll>_2Wxp_3MK?c1(k$JaH5;2hk1gy%qP(6Q2D#YDXGAg1FmKoBJ$wLR*;~S3tto zq+qU81l!ih2*C07w9w&;e)QP2t&Euc8^Kp?z^z@~S*`x~-q%7mFPf;x)SOIDT~tGK z$sZBIuavpF^rcZzLVtd1tc=><8_M%a{iuh}=k$rF*SzuY$l6)tlH+n~rJ(C-l4-K8$Cy=v zDJ4$BN;dPp(Ndvmffr(^%Eww9e|R`aA;DHS0cpRQY5}>4%sC-%EALmVN*0Q>vY96l zr%^DADAgQL=IyY!YI~X@N@BG5l{aa*3$Rq~b1}beW`@4J|0UK;Ip9;J9RUhBXEWb* zm6kSl&fa6OpYz-HaIPxy5c?P}WpkZ^he=CXSCTv}Ul4T-bC1NH#Cikafk=uU)@zt| zd>aQHJiV#E|6DBk#5TS85liXvsZtrs>SC`$d1Qn=%%MaxyoUBJ6`I#1E!AOn6-F&H zj-)iK95H3$_n>TTqH4cZ-3dU4o~bEtW47kE6QwiFt*^F-29_-eDaI$Hdqdzo&H!^S{XMb%YD@8tkpXRs zg{O1@>o9bxH!Ev96fY#U8sEIi#a1*gZJ+1q7&Dr#>}skX%Wa!_H|Hf9w2QU_t=xmb zva3@ij?6JhoQ3+9x=SMon7w6isc&bUV1gGM^BMZ`>C-J8^P|UZ#gT}S>!ILC_LQ6{ z-jFJgx)UwOiO#jpyS5oUMmwMP-m(Z%(dq695q=7pH7xy1$aqX+XdKEf*8ds`e}yh*H2AU%VvKon99UB(7VDNGy_+T!G=UJ%!vOUMs6O2 zuc)iQn%9-Pi;a7;-1=Y}mH;A~2n4#==I$hIaR~{lRLkOMHH$`O>2n%)z5@!t$3w>FTY6pRC#qrsnksEjTWkMs`*3;-w-eO03p>Pcx?m9AA)AiO|$}7|* zQfUJPij4r36(Tbd3OqR>DTH4&d_-?3jT+8h{XObS@iYKIFIb{Q0oi0i1?k<3*O48VaB1_Ke=HNqUSt5R}XZ zR^B6^piIoM%{9qKMXE=Z_kB-kN1`*m&NGvIBZ;xO7zoQki7Dq)@`7-BU0$%YqQeb8 zBC1Ym83>+SiY$Gp;?>_H?>q$xFc6|0Y`&NrfzYTo9#k+9wq5J0WZy1H59#VV@bsEN zf?W8#_r-_3g_Df~A~^%rukAX#?Ur>(^UpbjeW)bHZ>DT2#D{l{iLu?xewyYztQo}w zf9MufH?|3k(AwfGZ>hUJ$gobmAy_+Z6bs5GCx`(F;nRI?845Aj;nvT!VcngIVvqt2s7 zZ4F)Ry7`ji=*Ydb2?h;;$9#U|eO+qHB)3UIRThm4Mhx-W=j+UV@`Oo$c3TW1nw{7{ z#lhyEdA{sUJ^3khsboT}v~r9TXXX^4bkw8CtG$!yk15Tk<*PfUz;)jO3*L=>-o?Y| zDwli7m7LG}#+P`>hH3AU+({uIo=d5{q^J(<)@W^QU_eN@2QHyzx%@@V!r<+lWB7(0 z<4&yZ1VOs=;?1ET8a}}F*NH0a@?zQB>mE1Opg@cQRUgtF#tcZsW8g=JqFK6>$M{`_ zLt8JFXdK1T7|w5XCk_zwdej17Q0RR!h-$7$W`O4QivN@n?1!Laa$MdwUJ&?MpsSPo z2}V6lgRlU+@yF0FVmsk~$pXOt$d#MQ$#3PcwrQDeb-N1SSf!=4FGi(b9>GSM+u#7G z!N{PGW+S&IJ|XnlUBuH&tRn+J)0yVkE;v7F$ZqytfG*6R}Sp*~RqPrf-KWbYxZ1H#g5 zkyike4*PE1*|UD<@Z%2gQ8$Q<<64F-6yg!If?l-Mwhp;wc_Om&4d}P7#k`Z!%~C^kTe1r`K!~2(V<wgfT{8y$bxXmnhkX%ZE+gS|tI z_E&Vr7IO1a3c*FSV-1D%Exl#?F474C3d!+2+Rd3$JD9k`oR&5&@kD(dfj4o2cCNSn z*ln+J!4zzo%$-{zRC4i`Sp9vQ`2ysa_vY@55S6CvRky27?~K^kV0S4jDdCkC)Nf@3 zkPmRaM)t`;fMfWPJvBM3AwK!!+ydh@rK%vaxqg`9LH=Y3)1Nr4MQbowNVrqBy%qLv9$Rwx&Z`+$iYDq*xIVxee=Sxfg6~-TribQaVjE z&h9&O`PktiQSx&F+iL*tKtpj&`g|xVce|b@`tcC@B(l2#&)i2y<|YH`P_)_sJ7+xV zBfYvWJTF2}8zpPun@aSmq|Ig+;5H_$yrR85^&B7zVS26UI^b{e;FXk6DPiv<;DS+E zHVC=iI=Crca#%cG)mo6)QbC%{CxzY~%tzQQCstEg3j|ZpBs7+!V}5p*cww2dZS6Qp z782THpc4|CZF?QkkY0>ImUh%t*!dN*1UU(404r%pxo**j8CaBGHHMAE8x~1(8#C

    $#-qfL<52P$5p}nQlnkM4?}-=ZOt^!tv@?Nc@*V<&D?3SK}Ix z=uGPALxLKAGlDjoF#;*X>QD-W#nC zv`|O2m==3i*sn@Jw7$lYI3|1sTJiwx*stXpO*^1FIzPWpNfFj_phzAyG(BCcKWJ(Whl7^S@e4+MD!N(2{WXCLT_EDvnm<{&*edx(;t-ea*5uMd} z3r?*6YbIvq-3!`py~Q>{*yLOL=Ewb`ES?3+|RM#>* zll-t&0Bd=IVBW!I2JHyzuzPft9ObPi8=bS;@BIad>N73$QoGHO8U3^RKcuh|%gjPo z-`Zqs4eG}_fL(K>jxJEn!ajdp=D^b<%5fK0s32jlU5n=1&^HC91zXE^!y$ZN-XBW= zoIA}fll=RE>N)J$v(Rgj2OZciPFKVIY|KyfN38|WGoFyfA9i?ezdlGsbWh~%fNrz4 zsjjJLa}1Sil8l@khF)=x@H<1!&$Ut_6hNM+s!RP< zlM_z$WP6uhxnbAsA9>}dtO_N{s$ae9M{y(W+}o)va)27sJ^;UhjeVjEfam|R-XfnqoRsCike`lNIb^nyzVpE ztFY9@+Rf=i;M*p>0PJ~gA}ILQ54nPvd57QfP`}8}4${=(`k{UOW%Ew}W@obu#9OpC z!X%1d9a$bP!3*7xAxYSiArq>~I@#t(yE36MFRjx4e5an4??cOvmuFQxw<4Yqi2YGNZo)y^} z4Zy7s2xo>=izpsd*Xsoa>$v`#Z%@G)tA1#`Jl+-dJqpy-gs#me=0r8Pzx0H=4QhNT zVr`^bq~c1}^syrsUCh3-sinPptZ+O~$vwUC%}F!$VMrNT($w{2=VEkXlLB`g+4T$A z7j*e>GG;S8u)4l>lJ{1uZ)ba9uS~&}z z5CZ(e`XL9IIGHJV&EC2%fM{7a^fq=6lel9OV?4!H*eV|f|r$Fb}5X+JWw=kQ3S)qXK#~^j}y@qm=%o9W_w2qrIzZ-j7h6+&wQjhRWH&`h5@&56^z=Q#Q{8CgP-jDBR@# zkg1bW#V1z4r;#KOK{b~ax88Bvpy`_>TdccP>XS@Vck+HNK-`}^+r@G53ca@ruj1si z-D<3>V_j5fb;}5)QRf!fB}H=XTx`_#Ld0emXc*agUEFK-_>y6$*ykn^OXy_MmgjEW{LzWAL$r<4j@yXZKEXQD zSpS{C?Q92r;t&WYCp{<*I8-Qt5#@b@rc#+zq?8D4#Nl!OIMK*c7+}`@dxW zWh(IgX6*{NlI@+EL*x!^)qd|rWEE0I#Orc>0r#}Gx5qrzy!f0GR>{Ya#J#22+iPMT zo0{AmFj$GoNkt`|o2w8Ra*(|?Y(yI%PaoVA?E@HZ-qcjdb{lk{qv&^qh^Zr}k9f%P zTP7m$U(sDH%hGcx_8F-N{M*HEPt&>lf;%?9 z2~|fG={HNnyoghsv4CtqG>T4a3|;HCUCP<@6<=ZSB7a(F#{1Cy1a$V)Xn26wE#F#L z9ut!b!a8N@WKd|1wib>U(EqFLDoOul(g8!q%Ig>W`up*hG>3dOa_PMOB&uR*^ms{> zK!Mmm$e5HA3={Ez$B3qlFe1Ng6e)Hy5hf(FhdkZPl){g;Zhy@54Bu#hZ0Wr^b2Qmt z#r;avB5yR)<;S@=fWcfXw?dOp3OeMMPU5Icxc(~_Z7#O6UY{7nfwp{2w!QMmoi+Mk zPPQhs3i|@#e4{4l~@iIDJ{?R^ExHxu<5@N+- z2x<~x{iA#+qYX2ZVi_3R^lA$e8u7p^{a5*r@k>Ni_B39muiA7qaC_Kf?>7UH_h#&I z4lpQmekY?TO_f+w!)P%XKU|Kk2#%k5dPhDnNDg+CnR`B*e+w@^Tn5msH$T;*ntDON zOvf5fZ5PA^s;TyzKrxF)O3YS~0Dq5(szKx;=#ZwoZEVayV<|`Xt!AAXHJ243)|LyM zrOQ*~vw6^Xzaf9^)uGBS73}Ym@b=Gl;C~hH>q_}Yxvc`4qfv)@e!K{WQriv& z8@q&Vg>8{?RB#;7jMt1e$q~^vGQbGj65ZTKtfNE|u7_0h0prk={?RzpTlcLBc5{n< zp-7?W6a^g>BERY!x!7E5!T4$2pW0%_4Ds2kE_Ea8QU1Ic-k|MDNcLOIr5F1@ga%!QLQfYNsa8+Ze^-?*&$-yYe`X zFH)T56oMI6+#X@oa&A>xzOQ1*_n}7vjL2L#`aV^*yX7W<4;^dp`*W``aaj*-&^neb zH(EgK(NSbNiv47KTTA&XHjjjzz(V77(5xT4?#ZB8sx{!l5)QpN++aZzgBMQ@$@hVQF^B z&)`_}U<}y^Q2Gzj#U}3)w|Sh25>YZ=B=M8tmjH3p27Eg5W~N|pWLk}enGug3 z8TK!M`0XpYHj3zC3aUEqdFH>X_joNd2M$urt^>G;fCeaUDLEIx85<>#c^hR z`JLA6%g%2*#N;H~%FS3=vUV<{ziK?M5}mJm_lbp}br>OO!nVb&D6a6m>{g<-hwx&{ z@+ScyG^MFqRWsri>>EXWNf1gEDO+BY0kR}~i}N478tGovEOb{XRQ!G54@`x$M3!#&*B!0aeMl(g5vExuBX1S zPP%cmcjE48#14~#TxLr9HfS8V5FaoKYE0Sqt+e-A1Pyd#Sh~5cThMkVYn*H-TCqLC zv8%0D>Y`Bu*I(f(;Op^1(@@!LE`5e;L4mvi?zad{3xJo! zyV5_`-<`l_$cvY4W}BtJcL(#G$-KWA(QKrK=v{np5r;LpZH!bNevGML3toYkkqqpg zczD|{@1E^#;^&65s@v>KZk_yUI(g6Ma5O6lw>!#o|4L68O3C^;lhu3h@CpsZ5*m6+ zJW`eO7Z2W0Tk!fYhi+nzPsceQ^Ee*;UwLkSboMb_PZOoQ()6c~km#r@qQQFjUyM=&(o2O=_JZx)3 z9YOGsGdss^eoGVyTwVn6ep(IDYRk{y&=A7r_BJka8-ohC7`I$y5+N1u)r1_26#90u!kxB9tn>jj>8uj=nk{Mf z9E(X#A*OnAYBP^A!FSlVOv!^)8YC`5hC73#97|SC{(pA#KcaNSwrJ`bXQep?Lian< z-Fhrq>vH)h>ici`ABtlX=0ypV7tl&vry*}`Jy zSfx%ee&iK$-6MRh-LFf(GUo6Y>F)7VJkQwecH?r!e(w45Zf{*8&02vuf+WIYkXi>l*@-I=nK1FyUXYVU3aAu2Gd}fVnV)vduGMe z0vfnl!DIUf$7FJb1D&DF%LIh{K#;qQ7hMKywCQ?G8Z_`IvV;IJHtu&t6>c^>J&Wt@ z_LLn=tZ~+(kgyN)+g)_u?Jhqn%@~ZC&a1Rgk8mKCEXg1er({1&?~*5Q$*aM!U!k(> zC7Pum9?I;a6?AxV{sanh;RgXFwgg=T6I|7EiJq%{a*c4z`y9N~6ww{6Pi%L$uXX)c zyjy;<{2WF}-KjF}`4Tr~e~#8OQvJ{{%3o$8G_cwPAGwVS}!` z29xy(a25b9G+_dIq!P(xJ@o0m<2j6Oi`t`iYx{r2_N2TT^};bomX zrg;3`=30DK&V%f9Dc?E?voY>NELbQn`$ylzRAH;XA7Iw89n> zHnxG^w|J%80P6aR75@VBWGFe zLvm?C3Ha3%CZJK} zd{c|hW+`C)h+NWViR!{`A$0yA@%K{uteJ%SX~-|9ivp!D0P_V=(fmzKYCtbg=4vQj z#2;tZ>QkO3md6wP)fV3#lSAF!2xIraVo=X=NUWQcfk=u#i9GyQanVe&*5KAiyj2Fx z#94lad(dw_>+O%#Db*Hk@~^!p)be%}hwB#i(KW9pj^L^IiHic}3r)MDna#j^j5(Ea z8MdYS7~5Hf&IU2-i7eAgJzN@|U=9G|>0-}PUq=PvZUP4>{VB1hDu1ivqz*5w&Q$O{ z`cS@VGf8!-K&EVG!$ch{{4EVl-MXntfhOmpIliB}ib_*9h@ZLI96Y&7PqrzS#Mm*%N=T>AlPMSJiy z6k{~sTx5RSkW2vG3|%?>i0iA=;zqrMo^0qUb#F=e;Bz8x~SAKeS!)uUUu5R@Bvmw|wptua#u zSF&)pH-BXubRE(;I{!lV5)}4bMAy?LFCELo+`rd`dS`JCN5^|-tny{GjOh?gmc?`q z=|P~8C$oL&G1{3o&@}#=^t3mS=b0n?X_|MyS`^ zcB5_8T1>oM)6(0X^gW!*XY9_CK+O_%rdmc$CRGMj*`3+*b@OoEBUYDd8r^n7qN^O( z=N_YJx#qbt+v$KE+qzSS#e~OtYQzEY#Zx0t<;t3?s(o1~T7X{w>5^D^sWJCT)Y3R62x72x&a#Q@9au&(ef>?TKg!>=Q zvvE=0Kc}KMPN#W$?88xb6ILION`_{I5%1>~=SZL}BNc9Tn5f@!SahUcp$4Ea?<;uy zQ4~#YW-q&`_|V+yxQpOOY1KVrYX%46-o)#{?x0&txwUQY$a{!WNbihBEWtd(|wa2LK_KSqOXVvpp&cvKJo_Lq%9DPmSXh2wgakNx_YqF!{D+xR+w`_yb`aV~t zW)P35OUOxK(IWZUAfz_#c`y_DPyh^!+GJ)-0_j|W!oNv3Y?1z-7WP-7iD zGGX}RIjI8=2}7lT6@R0W$lBVL@-)*$wN1m#cs)nj%o#sr#0MLokB4j1#IpzToh6^4 z)1z3}g@sh2ZSip%YUMPJF9`YDuuetC?R6K6-R*py5?}Kl6L`e3|7^bd6(>m*~lA=fh->bp9B$c-TR%@^tMwZ`(_~&oImOOxRiaq zzkbQJ@_bx&VSJ`8ZovLD^OBhMSbo)Zl{74?5{uzrexQZu#&aHwUZLDoVWy5ra}ank z_-V~Ht*=_;)V!0-GCDn3Nnlx}m?;B5Zf&#IeEi!==;;x7{wc|Ucuzgs0@s{2Gzy}PnCc|&SuY>&#A zt`rO~P%zr2I{)IR#oah?Zj`;TVMLPehofdmdzHA}sW+21+kC?}mX~I-`M9Y&6292v zAk-YR9j{->E)=B2-ZV>T84;*Re zj9vO5-nvcM`z?URDnVM%N}fI#4BgySv{0+kkN_yvdW$C;a^l5bz*(KfGjneIVN{eJ z5Z1EbEYDB0O3&t6Tl)G?hTrRG+Zs39l3IBHJ@a>lx{tJoBjhhh%1jV>s`;9(a+?7H(9Ah& zflvaqnn=VH3m~X!KQmo2alQdHO#qI%KVIA8xRy_6mGr9iW*2jD)bN=hZ#AT;C~Fv^ zoNk`E@P*C`G)5NT&`d5mz+_EeVLph@CMUX~`!w?Jt>s+=6dM+mNbSt+e}7yier>t( zOwCT}C?jF(CI-xVwIMy<8z(dNk}9cgu4Mlfc6?_y*Ul+Df;Q}g{q$#eEn@+qu2|&0 zAt5S<qcl#r>5AP;|a_Xe+vw@VYNT8 z@9kvtx1ntsOp8r`xC6t|?{Vso?TR^JijQF z-##u6$R)o5z`5ESA7V0nn&Y@8PpkOydYKn7Q>}G7-dJ3X0kRbaazbU1kc9Kc5iEj*sxY|)f%$T345^c`eiQrRkD;4z zHwsdtFIO5msiOZr)Zy8ymt4oms z4K@kARk>H9n<)*MN6c#o;Jy%C59%jB9YlU<@9G7P)vV<4b0W}}5*0}CEO9DbKf{js z%-dE>A`XAT>pps054buCmB>$YO+K1l7-&jh^hN?)xAqH&O+>zQ=DAmPlt4I~Q&&_6 zsK=C}ociZVjHq7|o?Q^6^*Q%$k=R+OR}RyGF~})rs7gEcwyowVj?ccLc7@Fn^d~BM z3Y1H+W(C^G@FX$%jbK=||nZ}_yH`wt}LVk8G% zx-9bKwUlcu?qd+*O1djgWNq9=u#)6Hl3yNZI2!k=Djyy;oUX0fOFkBneR@m2M!|7( z1t`?K^X74LHSY>;%m?!YtmQe{Tkb3W@%BEc-{ANbc(t+?(|HsWE2~&Jiw#p`IL-R# z_2mbXXLM_Sp6X=`kTSi&!P*(k{P)CB+cr{RZOC=&1S;LI7VY`XW`4D7e>_1AX{=IV zaGQKL!l&n9^^_eaf`fEoD7n{1pGuZ85*6Mc{cN7dzB`XNJDmNYRS)wzA8StVYihYX z{p`e}hWgnD(u|a#w(bL?t@fAspMotntJbUIiZhyw-|m&=01jl0BHZK#{grjKR|Ge)F~1Zq7kh4!qx zU={K!c7SC!3K;i|$GRQ(9XjDt^kzG}CZpzyJIr9_yubIDGSlpt25-O9^Qg@}Li&T| ztNgR6T;~>U`8dT^rHi!>SFg9&Be%LTG-{~JsG-qAfg1xM5Bxz|>497~wsBXK)g#Xq&ajD=ruHg0P5zSGGScOODwm1={cYIx&4-R8|4WIT*vHMk#bulGh@E>DMv2#w>sf!AYSFoOaZk(WU+(pl^JGujce6|~gOu{W!s)OBPq@C#RG zcPejpM_&eP*&`OaS-w{r&PK5pg!omeS+y;{D(^lu3GyvMO_&k&$(Y_E21Eae2F2zj~DSZR@9jcJIkdU68xz zAIiimV8yoo$V`p7&=I>#<6mkjWU9}bmlu%5lpL`{yj=G1)6hR+iG{w%Q>Yk8_$%p_)|0?jO=hOA0t zUUYV5%GR4Q$R~2FZI30A9XNj5h#n{@g4}8=T%VC{zZe|L8JOY8V&0LhLH*sqT5;*; zeD}+7&@3uIIl@|faD2hrY4c(IM|>mB*l3-|+96I`Z;h-FfV47}SZKCj|K{#>%MC2I zv8$PYH#;2bfA#hjP;saMQts(R`<`|N$rema{pdza-J1BrP(3WJhFXUc*F z1F*~bYIQ$bPPhI%@27BWN)+4)az&oqaybxzR(%(7+NXnUCR(uz;%4-muPD1(OK7K^ z4wnkx62fF1OaWI|i%eg^Xwqr)eqZ(6JI^|gBNDi`AZq#oHRf9L@C~Z`tEgVzatN&z znWFN+kn~WUwv>NLP(EL&rDYSIG)_;o&%#CL@z^nmNGauHg;TJWO^W{Oah#ht*wT;# zy{pU!n})P(`kO9|Uy8Ib}gpPy|=MdDqoCyU>X|hj&20Z1MjSy8bVB2%Sf_ z60z9I0UD%wcMJrew=^(CnqdYGy{+^P=nCTQX{>kWK%~O*Papub=TiYKs?YKwOnb1D z?yC1b=C60C)tf!Gvg%E_D;mms-swh}2t8RYa56PUu$w1G8y#n&hy>EsAB-F6XwM!t z7i32{cu-hqcAS@}f23%gpO@$_;D6>^@vS1C;3N}nIfAfnl}hKQ7EcPX$7!F$`Dw=i zq6g4}TB2SAln+#>S3ciyi8e$2a*Mxtb_u)A3Wom^^q0$n4`$olo(bl|o_r;V@%xR? z=JT-wc&4wesDQ*pSe+_-H5HB_w`DuJwSmX7Lp94kooz!zfx2h5;^Dj>d6-~Iz zosshiFIZ|yqOI_SaRo`QCBMk!gHKtQxrCDmFA03r>g5j{+?oz_n?69=?jEEwhIpNX zd8-^;d()Gp{v;R!j81-K4i8Wg)+<%>45_5N-|fvCF%#Q;QtWcxdm>c*8XqW;fCszb z3x_Md-EUezw=slT!D(BXB@gXaX6kjD9M0I9HWWQXxMv-Z)PA7RRk!kt!@&ocd^|ck z?XXvar82%8o~=}ktYWX{nH)CT&a;{Jme5PCl~lL=>qG)c;VTM7Z!__t>vqVe+_Hgw zg6GWe!vLto0fnfXb8RVl3A48#A*c$o6!Dq5*nt|=mce>ps|t z?ORTkSt>lb%3dM@2xG1Kui$b)d zttF>XD{U-};|Qfh@=(V6OA<4vsa>E?>^Vl>+Sn~=ZJ50MhLx3iMYIH41(QPjigx!) zLYst^x-8+222mhdXrvK!UwyL#-Rhqj0d&YR_aHp4$Fys=BK=gA~urhDj0}tpc2#{3>`8+lRvNt>Is{KBt2EVdakNt-U2O#}j8?TOpu12PH z=!6kOKY5G*en`N4BHLW!(N1}&|l$!Jw;)lQ9RFJAwzF-ro$M6dWCgTCEau6{d8%b zP-kQHsF5Kp{G!O{N8KU8LL1V8d2+ppGpCTq^oy*f#nok`_xmY$rVhm!Oai8c#TAik z#(b?dF?C(xgmXFtY81`N7-wibGG}72s<7KWoUY&GrvSidN#q)V~N4C9zevj)6{N1nXB`z;(XnKkchpofg zEL4lNo`HF$?;>y)w_jfoauw$s@*I!ncXy!YEONWnVb2%N7sjY3`9->}(F1MPdAeIM zLzlDI?}NUpXAc!4^9%DDp@bprSFmG$sxk^`x#$|fY7GR&z*fE_*;)cH^M^6hyx!*5 z4f9KKzvc;N++f0-ck#Sb4hcWB%Pvt7_`?ASdwQc$YM&%%DuG>ol>ud%7Qs^ep9A=) z68+?#Mddl*2BZh(@M*Df#9CU+_nRLZ?hD&(WAsyY5Ewu$ezdDC$yNexw(G<-k1Qr& z$a1vpgkwJ6$9ZvM0dyz@z0zp~6pLY&e*6GIb0uKHqT$%1h<4qOmCIhWJKAx$KeduF zRr_#%Wvcr!Qin?BAguI2rAb(9*l8Gn75|)~q84>Avo=U{u5=og4E*S^0@SCD#gJ^)U1_pqu*zGj}OZeB47u-#*`g@GU4&_i;Vv( zoIaUxTB^YvF&nl@ZV+uop8EYz6l>xU^BkI!0qqUZuE47;>N&kM$|>{)YfDK|J0le;!Y zIlTR(-ta6KtCLfM-*1^#%TcvcZ9AJL5Q@;JTjKyHfJ-N*l?NM&Iv(gaEY5&_JCo9| z#<*^{opPx&csfQQQO;jaU%)-{vOAUz;ddRL7wB20B8x$IBl6mm^aT{PDGFMEP+Z5&<&_HB6>6ytAw%+0xEnVbuR<>X!_a{nW2dyFF@4X z1lS*3&YF7zMAt{ZDe0u?V;D!U3P{8cOxtKu3bk9)PzP6UUP%zx#vvm)*^u{|2MVB^ zfSzfe&7w~eqjoNg5L9{KOe*oj+Hdd}#T59A#y|!`I;CDD#C{JjyFtn)tEQaiUXxWZdygynAvD zQkQm({?Rjo<=K~QeGP44_W>Ouu#Ci7)}%@5%dvc$MB)LRh0lcO{k>4_G2l;~x>DAu zbR6tvk-|OF^;lx-dEtPdg@WL+f6=;OL%4({gpNjGrt&*>j~cwwM>*5&r9$VrHOAqS zA@_Ze-R8s4KcQRK^Tq8946s+h9hu)ipY}s8qJ)NSpqTc`4||96CDL0Jc=X1eIE@eE z3eb6%Y#0*mPOPpom&~T0;rniUKW(2d0!4D|Vp|v;fs4S z-ITRpEbUUkjM;?Ef?P__3MvKzhGW0m8!wN8Ga`~2U=&(m}QV{h2n>b73E)y3gGekUCib z0s7|rty;;Bjhk)1Sj=v4mbtf!{2(25tkH=6(N?3#I;?%GZ}h3NKJTlfS<&e-=tJ_; z8bu{stkxC-XhVJ&C%C4T+Iz`vz$?MB^_bx;ZM<2Ho3{(*$D)q+E9Me6+|gb#(c>XO zB%P&A8X%H>lv0#pbDXyNgB8C7f@klC!#TopZ!nCL7P5q&A#+UyhQ(GUrrpF~f%Bn4 zTjJD6*?mucJ*yFV%0Pma?abG%8F?2Jx7avX6O`LzW!q>HJMn~j zmm5Y)9#d`1Oa(f9VL|UKLz5tN31PXTWFP|rJ_bc-%~VXDot;g(YS%B7etS@nam)gJ zt2;7k6uK6o+TC^IdIc$6#obKu1Yn4Z0CSV0PqS1>Zwi)K0oq#4A;%_56ma7xTv)wX zZp=DFB;0-d?lfSVaKs_?S`XF+Gj(NgYA;?*_yHwCNqL8;FClFKdzf`FG1=jHZLqxa z^XY_~`ilAozo5h1BOhT4O}=vfEir zm+wQrJjKVN41`Tpq3%CCCjgVKbscE}E?-R%1(xk359}mbtYOG|NKU?MK(1sKE1q85YRgyRt zeEDb&dW7RN-pi`wy|H8XR$eS|NE#&kEkAER+-pC*hslrmN>5U>NaZMlY$6;IOTw%V zmVwFLPIyh|bIZ=>k0o7a5Wg9L^k0Kg22*BA|JHqP zKJ+}yMC#v5;Ao)F_yAP%nA3>}X2S9d+><@c1wfG;zA2q-vk|G;)aNu^O|@-SR%{qc z3ggt0O)`8_^<-tY>AV}DiMp~%&(hWnu!;k)#w)%MT@$`CluB?yj{4mJS|#z_;4@-e zTapTsiI(OsWaKNfnu3BSDasaj{A&z@S|&9ZIY7}~dZ6)jS3n0D2A5=q1RJ(G7Ehi1 znkHJva+`ldpyR42JStxMdI6vHLGcG~UJU5pKfir5Rl;cp{&h@=XNYg0x?tVGrAl)f z=ZAkhtFh=X*uC3hGF~y?WB3L*PW42-=-G~ep^b`&`AS0< z?c@4mna)$Gk}u9H0D<7`;*m%&Hf@jHG;a?85V(oEhstr+@p{&3A+3ji-xeVeCuT+H z?gAP?UN)a0${9jXs_bWJ-6(ldpnEVlUqzt*C_pu3d)A7t!lDU{O3_~TbI#TE{ExIW za!q>8?sF1*>%&D0?i+VGyt|?!?*Vnt``1*Rp1K?E0f3tu<;a4jl;ike)J5z?ECW@v z#PUAs^R4?&90W-)ab+&NcOZvwv@EoajY;bc1c#M%oVHaq&hqEa4Q$__SH@lD!`tAG z^|>`)Tx1nYh}sn?X=`e1f-sWjtEZ%rDDeSG8uIMOcCbBn>{f#H5dO+n?}WmNEe<#8 z(ECCc1N51o>X(ym04i6EiuExD@YLPkt7ohCF& zk$F=CNGQa$nr4E4>0<3i_v-JkY#$uSt(z6VAq}rJ1DYAgFRst12yd?4ZyVR;f(z7v zwrTpM-PDFy!iRh3FJlu6hkf=th?{d>6`JnN(_<+tD{I}XM#IlMau~id86De5r6+$G zUfr7Iai9S4INQB`z4i>oH&CYy=GE%@I_B-C(7DovdgJ5yn}BD|dy<(0v65Z71L4-I zZyu7GLg!r(N^ECP>n@Rs>@DJ*^$vTSTIcUmC-58>P@;*U8^tN8tDur75r#S_Dcm5Q z8zU1`p>z2#SV(pJ&)4tI**HtS!igiNiIIC0XDdl1mD@SKL+?w}e*Po(Mq1C-MMpa( zJB_J`adzKTY|-TTaoKbOYB_T1?G_z#ez#+a(+JF-g=Bq`QcA|D$ZL?U7sL+21eH< zUhac9!QF0w;11!k>jHGAsoiQU?9Thy%yJ@=xv2vzkrQz{6KF%R;yGM@SOq@w9K<{3 z#K9TaSlcXs?E1!C*Paww)mmcXg7lxUrFvn;TZO#^Y{rfOf~s!^meuAg+(;7yz4e12 zC9EGsS?rA~WE?WBG3%8f!iE0UF(RgumoSnl zn*1rsEjOIu3EaPf)xDrL&q5h^WA?_s5RxzTc-TWxT1rzHRMMg7jMIC#(mGhe%^a>I zD}MWj51PFn!sN^5uGdzBOY|+Ek9S5(P987ql~m>J1dn&)1%{C)5~S>i9mdUsc8H&g zsDQm1fwmz&d0f&7ks5KpA#_{mT91{t3hq;yHjXjtZp&a8obY!6S`BAiDBFbdH!dOE zJWRd7`3mf*Xw14!B-!cV;Td%I^|cQ*4;9vXXx>z!G+8im+3%&Qck0g zjqe$)L}4NOP>!y%N~NiGe)RBk@wdC2{qjv?T!Dql2PGnw1t+A*XxNa_@zD*C-51nJ00_1M$xSD1o%{HW~C6Xa?J|89T46IhD84>(l5{>>-`&Aqi zZ0dhZ8>dl|yf(0=)1U@DG$L@qiW!EPg~{4; zc~V+lz(U+;R6!5Rj&vPfZ|oN9g=%nNaB*E}1t%XGd&&&0!Nqjlcr~b|IdcEc_*u=t zS=H4*G3w|s2x!}q4%EN)k}moE&itpO2nRA1{BV6O3ur0}?Xd5b3XijHvsV(t%aSJg z%&6O+EtM6|${*Vw;-Hm_^A<@3gC6s}(&$@n8#9jptkC3^4nG{+L!H2k+6Q@s0hG*` zDm~}n2zD$cYQJt1+A+ZVTN&)g*p~;ROr5VD^)coqV0_|WO=SMr{`ZixBT6&>#hMQ+ zgIqV$0OA1f7gfn_-u#wb?r7tsEvxqOYqZonX0^m(MEul9fT0f|dlb}womp-KTCD39H|7JUOe+;s%qI#pGAyNP0H5Gc zg0M%5=HF>ahyW92-Lp>BSX?D1gG1FL$r6OmMh&=Ih1Y_+gGBxvNCI&A|A`sAe(hRi zu)c08z$irm_N7Nzb81TZ;Dlz=8jH$kpp_JVc}?i+-(hb*#60?FMhM6`|G*`rIP6G% z)sCA|(a)4gqZG7y^p#*Ivo*s6$d3Pshy5*``a63q0NLo*b59kz4E}y^XR2;5<7ni~ zd-NuuDMTN5MK>yv)TQSYvlNxpc}Q#YR#m$$CF)E-)87~K6g^EP2qsmDF=CH;@nFyMPehsv zY03tWO~(@){+x;xGFTX7RkY7PmOvnTHUeX3i?t!CS8dNMp8RC&S=1If^3K~w=9LfA z-MSDuxHlmEH5T6DM$m)bRDR#_Ya0#Nlj})3)I?RxTZL!5-q|d(p9tZ-dJVbscph)l1~jAM>KsT3lAf(7pxXlgBBG?t99) zss2iBRA9Bcp?2@4uTZhk9gW*`S)5tPf@Ke#unNsHS0-M_8xRspnfy9!L?+f#U}BYA{;vQ%bpz1LPdmMak?`Z zxxnMyNri1&^TW|+39Oz6$uR7HzdHz1imsr~g*il27x?%grXa$<-DW3z_~~X%1OYy| zG@q@POQ`NcFwne10W27Dzw@frX8--bzWUWfYZ^zQi#PDj;p)hM+*sdpDypoOE9diF zF|j`wkUYo_;bT`{-|Nd%Qcz|iZby3e2xpj6xM)Py*Neq4(5>^b8~{-L43ZPXeiM03 z)g4fE_;5=AhenQ3WlBFZRf0VlLttT`jFUsq@ehg@G(<}vI;$nF1}fAR?7(?J%AAYK z5@!64mm^_q6G&RqdFQ%s#Kt4F>XyRs29^V^<7tqPCWXXm-Th9M{) zv3&w!^N*33y(Ip%6&J0>`mdqT-4NOXWJ;xXok?v6qFPr!K37+Bj5Mm7Ojno$d8Ltu zi8oTXNkh-L&x+3W&jDB)yZ>RihXwEE@+%?tCBcz1<|I+d?>=_t<%!0EIVYskt+^z9 z$prL^LJF+xI#)9tA~T#fqzM<{m^cX)2@gb?_+ z6pB(b0Nwy!Hoh6m>6h;hK`6w{89Kj1ejg}Vv8PL5!Rf{wmAFgD&69_h*VWTQzOt*V zYn6&ztm{%F2T5;6|G<@PDbe~&O_W%7IqazE!;k4S#TwR-pjOjn)31v?I| z>o(i~PM?~Z!qxbYUToFQ#eHLm?nVdmY++5Jh;&EhPIv2zv%}CV2l8Og7+&dD4)c7_ zIPWd0N!jf)x3S@OrxT;;@p&vs{8*f}=~_ssWpAq;lvKt412}t=%GR#)cLkDc5|>i( zNlLz@6ZMVg?)_8yCM;XI?45pD+=zSnRMbrF>Z-`TkD636cYC*7R*rlQCR^@;DGJQ* zB3IS#3+%B;b?1YHx+L#xwtBY57I0g46M3AYd-G|rT?s{f>0P%5GZ@^GU=Gbz21apO z1DEtPT8APW@BG8-Oe^qSe!;~GZ8_>CeACt1vOJkG6Isa;ME3$J&}_kDw@nG@yWUK| zYKlr`mBuORKdY?&rQ>l27fZW0~=aG(DU;}~8c(u9^XzIcdktab$0 zY*tT-1JaKR%s?gI(+VW+Gy8MSMnm<~MfIl^@vKH|bM|xHgU$9|!7LF(Ty{7eoWRWm zaGKA^;47H!{(kL^LZ02}3g6p$ZXwv`Z}Trq7RJp3VLc$e@Bf8P6%ESWTG1c_-+p#J zVdmz%W$cVH+4Y)9!L=fG|2`=Qa_)t94y#vNE*!^TL!90$p7@W}WHKOF7PDzu-Z`A^ z#6|PV!Em>MUaIs6E>UL%r5Hx9EpgOAmtLgtK9paIx`rbUqS%0u@TUS8+PJgexogwc zK7IP#shOuc+vfSTAIq+cxol9AEr_ay{*LzkjgTE5k33Xz>o)$gXrWnxLTQ21mUs^o z2~if)QwQ4?p>`mM`~~+!s2?@_uqLzqnTIvsNwe9P4hX<>-eL0Kh=G8Bb;|} zV^xD=%^8v0JbM^Ndu>U7wllVNhsLMBQ%U6V(2C7_yD0~ltm+Q=es$a^nBYi=Q|1sN zGv6?8RKga_3f`M4u_dw?K-8VsZvd7H(B4gpMy1Kgp#n_nS%Bz4;;J>ZbvKY-#p!kn z`&GZGB}BDR_p-suKWMH4tz-tNDzE<1ZCAW^Zm<70d(--E?bd9!e#-+(5Cw<~7yA@i zW~-6=)QJMoXoxmpRl2{b4DYOKy+tqIl`OWqNA{@39XoF&++Ushn_D5GcLOU7tbfAe~^uJ^%|g+ z{;qqMW;2?+5hLssMD+d_$k}fq>fZ+8Kg|WUrzs$^1JEq{m*V`EzCt9@fL%Yp`_|SN zf=mA?tUqz^fNq{+5+MSYW&T!AF(}y8DraO+2?fIIbo>g)Hj#7GzcMP?pFoJ2o{uh+ zxN?kT0_lbsn-Z%B(-GV-NY9uQ}{K0`F5^gw3Lch(ff zucn$vzzQ<%gSgvh`1~2U$sIH~?968NTdqg&uH4nWQQJe zw7=`!#@v~g;ui{Lvp=?pu5kl^)jJ?5c5ZDA(`7PLAWCga*oOXM+`^*x7#lvIahj)w##La^H%VrSz$NmJ1kuxty?$xW3whqECArEr6=z-kfof1qxD zT>Jh2;IW^Y8Nh~379SzRmm5mIyr`A8DiXhuY5>?UYq;-d&ndRDaHWXm|Kzl)ULBX& zLquzy2eoINsHQY7A@yt~rGN%6@TUZzMf|$PN=|zWUCfbx3boRY8czD8lBcjjB=czk zn)^sQ3aNJsZlvpQ)zY*m zQIJ`WZT=Z2dzo0MqIPZ@_rN2pz`%^XGBg{)!e_RzxxH*pX{)z$+#gDidm)vKXo!2094Fp%QBObiAqsu_p98Q4{&pshl7+wTKb6EVqHv)kMLjI&RTqC=^@LIM!o1-NGp1V9Gj75Fp~!CQ?U846nwD~?I-$|8+tTXU4{Wz5@_KZ2J73uO*N$=@ zO9Pr>09IT@hZ4PB(qlP54P`hg;Ck_PShBNM&#eolvoL9X z;JNX(QUKlaY~eXw+H3c#K4kF6Gzt`{3lAQ?3VenybUAy0=(wva8%(&?bb>PsSm9Ng z&scRkwzU{Ya9AOPcLVE20QFPAx({25N?ZZe<*?(fFS?r}x`8YW4k%Bfj;qeTq9Cm5 zwWr|?H@f3j!cH+%mx3-~f!Q6GYkT*gwli15^o5dcqb$4nmzffwXNaV!5}G}?lLTOL z-aP#}?===*$l9prjvWa^fPY<31eL1U5$34HseM-<2&|Y&cW;{lrv1+>gn)Ng@ zO;fef^e#-AR~^Fq(Ed=85b98_wcjztQC%$RmD6xMjn&~3(wUJE7fey6MMm~McwE8Z zU=WVboVd4z!K@CWW@Usi?n>I5Szkp!+o%TWCZOm@5H?6@@$1##69nvz%5bYw+;AC69l}Te)0(bw1 zdu>cmm*Sl?^v?8<=I0X)S_$C*oFlBLtVw=9kmf=<| ze*a0hT_;~Z66xjeTCXtVbEq4?X948hIZ$SWHcI>oWG1cP2mvI5)?ZUe`%-%|fgix* z(rpV2ep?B$9xtMU1N7XPcJg@GJ&mdiu-NEB4UlVPCI69W@B=GH{8g8rj2V_sXnFKg zuoE<090G?1zxW^!IA@*V@H8;uH-{RK$8sGHPZ*k@L!<4!w{{C}j@gqE)t*A0gYuoh zx?axw>Bq=Dagne`umz@^txMHHaDL9Q>!43DI86< zApT2JXXvb8D(h_#vaDc~RL<{bjyB}wO$eM)#G|$CnI^JNEG~~MqXk*kUnL7#e@t80 zKQwIxK$Z++p^hGlsGFiy5_>9|DWp-HNN}(mwd~;L{;?2}q9rKL3Mxsck{RoZ@_)FX zs4#Ja5Vd4{t|@Waf}JhM6hu<%oz-(TTf2%JjZ3Kr2L`tC6y*CP#wfl`9MXbVK%GJ& z&!(QLQXk$%zKzsm^qkg54K%$_6WTOmc3>F^{n6~E#Ji54`L5mzTb;2lw@%}6`^!C< z&;g$tsfFItYq%3|nvfYJ1I9&r|39oH@u{%fDue848AL_Fn0}&5#{+Cg;`MbPaHGv% zzD2_sSc$_OMr-Q$Gp*Vw9#n%f`pF&=AeIFK8eva#5%gkJYj?Gx?Mg+lkkt?InhwF+C!#2XXL99=uT|#`1ZQf8zqE+rRB2E zuSuijtWFS(5JH8w%HdG$wcIhw}-&n&LJ-vuKACLgR$UNZLb0RgK{E^Ze~z`^Pa zSi43?348Vh4l4Q*-tWtfSG20U-`wW=Llau>KQEb@5sO*Qar9UR_U!Vdc(+5X3c#*h zoazIMI_SfX^vvLSoUZl>NFbG>beIDLWDk;BpIGeG5As>D`MzZu- zSuz=oNXjtu8V!l6*>M?2Rjvr94xmI%_xb4Qm*MqB-{_mv1O(jSMXYuJpD`DJR6$}; zR{@KF09J_rnuS*lfB>_<6@UI;wu^a;kiZXNix3}s2jNcT2@8|wJIHcKONYaQ1lxz5 z(`ifZ%M+T_9R348OeDn{hzJ61WVEwZX_KlA|7yJ4zhf83q5oMl@Hp8(*x>)ZLX~y0 zmb>h>T=8PZOWe$&XL0+MyB9FR+u(O2?i_c^Y#!J1gIDc&Vwm;hM}#$4vHd5FnDui@ zHsh5mmRa29>oP2_|4VKr;1^6!gz#(sTk`yvW+kA)nwyP9sjAxQoi-(tvNMDXp#}X0 zl4#w{&ldn4c?fK{0YzQ^Qg@Jx*$1fMKsKsRi=Ff75%gL~@@p_-LoE)?q7@Ot1hvaT zu@SShE5W`W>nO;cPYo%#%$>Ynk+M6O7Z1W`AIYpJD!I;OiMw}-Nfwg|h9mMWj7>{R zD-lU=0Z_fbXLfe@1r`q=L0|@yZ0MUga}?k@D?%~?0lIoau>}&}?(S|}`j?@Ausu}7 zw=_BNuyVncVr{yoOXn?lYzUpu{PY!UR1M#25A@V%tvabK_>0Jv&D{yY}t_I*b zd(+j82GvRIo97S7m#2Cgd&4y_1ml83H4!8bs(YaS0MT_>CZR$l(0y0a^iG?OZ*QtM zIcedf`L@b%I30sUBe^$lOYZPEx4F02$fp1Wm}UQT*uF)cxNuzk_|Dc8ZQNYV>!2op zQWF2@WS$OELZGHYAowQ_$SW?dD%OJHiVl}NEqT?@`}2y_n(kA9bWmp~vx32F=F)n5 zlmwvvegbHgo9JCIF!Pfx1~&&f-zw)iIxsHP9D!i@a0Pb8>Ece#>mWko=e!BoA`Kz{ zOV+N|t5)s(v|spOI#=mc006;SSLv3GMvE#sx4ju=o5Q&_^Le4p!m5+wz$y70Ssy1` zW0tdaMoP)#e*e)3iFod$I)o6|pTZY~ZRS%#0F~;vz2z>~lSkQ%1`lNkP_48#)~^xJ z?mG$!YMIa4i^a{;L>xcwWBNZQaQ`V9a1KeffSjxRU~~p3N>y+&bOYpa&C8%3l&YnnbT<=gX@BSN#p4X~U&EvZb}P&QNCn1j8ls)Glvs zc6C0ewj5#Q+@jY7#Xe6sJTek_;DYJmbO(ly)M$}n3_y+^iA@r4AC0EJjlZz&PZ@Z% zkSHC5KW4P%OwiM))}8I!aeRlS8kp)s>~0Mmk=4I==`Rl(33h@f8vXO$DOaau4B3iBs3f!w3Ep#j#WqJR;&y&-bN9Rhtxc*^2$rif=uAfL&WWvqzf_HYX_#P z3xXdO1n!hghcvc3 z?Y)t_SXZIRY`XcrwDg{0!IYAZFJ*Riwx{2j)AiG{ zkQ;=yQd}frJvGk7uKO37 z3H#AHHZlXbN@r44avA6CMQMmwofGceK4nH7G@#O9`bH6Dt5ag?0HC5ppHVmSNqub( z!~^8^)eY_b0k2yZS$1K$)-_KnC~GgyL>DE0?^#PAKFC{6i{@8H0`%6ozxSG5^FRY$ z*_3HP`D~$jgV<7{wJyLAvy%lq)?5FZ1X(BvOo?mJebO%47FoaOrJ@t`@!5=JUr@1A z{FBZCn414p`BmxxQr=^(KDDJeQ+4qckadUmFCCKZKbvzr!j}J;4*Jh3{C|u5nX2YH z*&|H=_Y_I~KPs~mIdTK&4D9%j;|tUVL!eVl#*RoNju?bJc|67cY|Cd9__(j_U5ALv z^^ptyl>AZQ&6?Iyxg)i;$94lUY8mjHV`o2wR$(hfN2;9 z@JKFdBL1@P&Zh;?_m>qGt>x5MBJUD=>8e9ab?e;S*-ZKCyAoSvD_lShME!c`{ z<+-XN80c}B^?PbbR1`#Lsx-alt+l*1?~6G-n21>j55HROiDETXO36wg?DR_eJ~G$D z0x7SeQ?&;zuCDq;M37@8yZeWBrmSt2tU&5nfLOOU(fE9Y`1$&3w;VS*ckrOWeNcCE z+lX{N6*_EKVyvC}+d(4TQT zMP}#S-be%G?SX<=z+ zl`6RtZL>3tAuKFR7DK;LmHx$Uf4X(Ms42tgEMaT3B%4joqD5MOUcLW&5Z~cO$_7#o zuuSV;dTD!jMh~asHY+6pLgRyWS;@4NxjC_vRIq%J8;iNQd2FBMzM7r3tG)s@um?8} z&+a-_*v`}H_G&n~Bt(*QMe(r`j6lGjwUK+NEAonpO4$1A35V-78Z!$EQ`}FnmoY6) z?23xLS6As788dxUWD_Azl+yZs-34*2?M_$e+FRx7O_unD;YeTN>ZaoP?(VSjJrx`& z`-QQP#9hB0e}9AFa^J3KI#ucQ!Tw_7ys_c;bQCpEE?eXyxsp_+k!GHx;&Qwt zyKL8iRmGBgF(2|1&;V~4mU)2X+tJotSS%V?QXt)EeU}gC`#eI0+l?@n7UxT}B{-ce z$9d5cl;*RGwMbS(rpj;7fohI80q^uKOk)!n#T?I3}8xieA7Gs35D&;3;&=e%P+spcym$xf-g+XjAW z9QnD+0q2HP-%wiT^6A#d5m!?Mr`^LIzY&Y2KllAh(7W+dp8GS|+v`38otdnvjtgRE z?t7<=s$qTZ`zY{hk@wAgiDeXYGU!?J_!8nP!@|ee(fIW6zJJ<5GCA@>1bG_=nvKQh z?5ZC^9Sl5S_qv8DK_F5V7R*nU`(T_HJb$U^B0R+TI6TDZFg!%aTOXi7pswCw$~PQ6 z3dbh<@y~~0N^S@S#g;kdjA zQJRhni#ux-V6vQgtI%1$ z!=9msw7E^^KCATRb4=5zW&=&(^%w!c42Uvk#Z%xRU-_scWF9l&pqdA~VC8IDf0cQP z=@BeTW7`g#ie%@}gvJk9X=x`;IC`qJrg$lxl`)RbZ0N9+|kSQ8@b5H zl3n*Tmn-}1vtoum?hw1tCpVe6?im;#6{wQUuOmvuT#sflxY(JP-i#(OrB$($ku}~W z@f^hsxU)3`MrYp}H|-alcOW-iELEG|geF(L~e&F+5@Ew1uFN`sdb{naPK`@eL?wDk8QA#3J*|qQvBX5jMsu?$6q`4ImHx;BGb9Qb+kI&Oh|M5`65Hsh;=VW zkne`vkiiocWK)kA{jPn|I;-ThG>%tCJ2)I619$HhzNP#zy=f_ti8bWi7|@a z&0U~C78$)1W={k-`bT3C^yeMWf_EgDQhVxB#03ef^;OPLbgx7}@3miR|1w&2lEJ6F zt#NV3U)ZqX{ceRk=mgxU7b+ufDy&#{Vk^^L$&XgCy-ei+PRQefF_)p!u6coWJz2Th zmg_|R(ODGelTuKthZvMF+>I}4dPcO;yW%gWO@fA`Bx0_2$jFyXC?JpXQd2Ea6jUOK zp2135GiZ<@f@Fxh&pD+C2n32l!~}uf>jPg33XKD9E$GYt&dX8P*EkBpWSPhijIV2*4BBqb5_ImbC5OgF62LH= zRKPBBI%6BeKR#cWhCsHc8Df!yZ;547_(=Ui)3@<;`uwMKgd#l{7)<2`iYms;Q$gj| zB5sn3BynBO>okLs8{;rAtnN?!C!II`oMLWIno7>chJm_r*6dxW={|TVT!gVd?9~Kp z@HtkdKJVgtdh#|VYJF1zHV#xCD(#arA36`(ySl4}tp&c<_5F2l5ygYRSo^t-YDR1N zd50}u8D73{jgLeun5#i}z>KKcc-CV?O-80)q(pR5xDXjl{ZrvA?6}L4@cw9_eF!H7 zo9riyty<`23fo)W^C4B86QZilbuROeAohhw4d_7`v5Z38>9V{nj{9kGne8o6hAa=2 zF=6fs#CNKlaQmX}p~ zRKW6&w>a$xd^Y?}h>VDkm~uNX;P#^Pya64uh43B&>l411#o?`EL%r|h8@ujGS-22} zyp*V^p~%&=J&nd*4s|tQ^#Qt?yYXgIuH-a6q;2+F1tccSR$lF&Vj}JTGbn@&xZLLf zA5Mh)5-bXkwedr8k`W?)d2LAjaaPI?#rxfnqhr@5f+Fr>Ix~1{sZii%bi1VDwvTV~ zF6T%NPCd=$EK45$n9XS$*Q&^C#jMtTm=HWhj45({t7D)^=Rr(BATW_9Il3bwWOYIQ zBFx|Btl!UZ$BkT{P8q`h_0Ar^5x`CFbsTVBzVweU!kdmJg@Q8(jdeE&-VK>o?n_xY zB=B81GawE)KAbA2CTk^?0fIx$J~`G^6rT%6wCnADY}0m97)%^q5b=p`;cr zWo$1JE$HYAyWLDmcxBZh_Q0F1UtU^E#KG@CSVwYT+?PXr@T@N>2QXBZ zL@4m2FasA8_ulUr}!`?oYi(gb2%&h(-o-Zj;15>YraDm zW-g~=`S< literal 0 HcmV?d00001 diff --git a/docs/docs/usage/assets/taxii-feeds-import-icon.png b/docs/docs/usage/assets/taxii-feeds-import-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..4aa77180b80e359b649c4b8f1bd82415f89ceb90 GIT binary patch literal 17409 zcmce;Wpo=+vn^hM1X|8DnNr;Db+QA~GTn5VbLgZw4^nV+4B%4JQZ)l>UD&$YHxuV+e>J$&x=sl-+gD zH$AkK_dz|^(-5o$WYGEBuoz)d`JwIk0qS(=UO1*YlUnAQ=J!hNmZlk8jnb_rDt7oy zlCCm?R_4^^1|eOE@o7(w`tp!ol_fNcztCHCO4sKhIu zI39>+2g{=qYNqcdN)iGkyHB|9N%+l2u8m)9;Slz~nuUfNmy5>4U1I2`U@=?p`<&vl ztQ2jw6*1@}_T<;7Y5<1+akw^O`G-wqQ7}&Bkyf~0Sie*MDLSV)vGp%k_KL8U zFOn6TOW`E67_B{x+2_2nCw)N$=>V?*r$FSG%6sR!LT+@IrhbOd+IT+tsxVy(dp5Oxmu017ScsNoj}-!Cmzo-Y}ORN|GuUkykUDX-RM>a zP0}x2*4Eh33I*QJH^-0CDHhXSS8N5Y#i-wU!lG|GhU)=%A%qXY*-=id&}U}zU>q_e z$Y{Z)wE-(dTeBBB6i5k(WO52;8PGx$6QFwtONqqWYyd6ZuN5MhF{P8Nt9i26S{aZ@Mtq8{8k|_MjL4Zd}DonqpCPI z{9@;&kLs)q_}k)7g`a{sxay4d8BBJxnE#ah@KOIT#JgyGbEqMfUKubpA^$(80GW`M z*<2BWpEqHFi1-*VII}&XD2Ak6$>5PNH_Bhpq}|vy?`R=9)?y&72_tYo6_=7Qwl?ca zq>CkW2{YE#Op)RA?8|?zQNCZUdg{vW(6MV?(DF?={lsNr>?hctfFd{Y!qCnoQ(qDW zW#DrXCYH|?j~9<+9yI5nV80pm)7m__C|kk$6qJ(DCr17!1_7B65$;4PmK4_+WeAS~ zVFeCdSE8?^} z@Tyb6U^ZU=ew3J^C$?HQmZYNhd?G*9CD2X^Abc2({LE}5R#aD5u53E2*8ax2PFF8v zayKqmrQCY+vb$1)VX~ms#hX~)pkv+Cb@+=(7)h9!y8M8Df>=Wew;gTl4i zhDbdRK-FsdCj7-$&(v2E!Wfal+F!ElrRmWGmeCaw$#jDLw8BRysV4E0Op~(~DZ@97 zf;!-bTG!hC>hjmiMX}lF8~$W3?*|v+wY)puUoe41df#4aQGbrxmCTs$9cfj}Uh)L& z#N4Jb9x58Qg}0W@kjGY#;7MmZs{VqYy$JnY;J>K^yTR6*`NHxBr5f{u1-&Et z9sDLs#44Mc#EoK;e-Lpa=B-bG+UzP0RmWk)B02=lgduA7X1;)@Z^PZ0qm3K!H$ad| zQ81hcX0ZI>X!Twp36;}@X>}%F7|S7)XmCLMy(2^_g2%3sW#f6CxoGJ6;am0n+57fo zQ@8m=c{t+UoBPSThBWb77{oVy*8FJ+;JKNRhWB?3)eR4p%E`>{=O54WFD53s`&3+? zK!{)i+S6;zm+spAH}&O1ws!-p4A8Q&@`Ly#<(Arwi`V80IM@GGbhv?yDX*3I*ZE5oHpy{G(=OI$ND8n~=PH(6JULrxgolU!s+m?Q zlP}AZZ{;p{bSR_Dwx`K7Yq zFl8xI=8~SI#ZD@)iu5jbIqPExMMtZD+2(gWu{}sdBJhV(UPE`IP8iwJWX)zVrv~+C zq&*}+9PIc?^hubqj8moMEx%pP)7_ahfE?JejMb%?S(jlJ4CEJ5n{Bfzv?=|Bm_+k( z6Tr0I%i0aj#xrlOMNn_OQap6=b1D!C{Jf>2s{Z=)ItULa!2Q(oo>o#$T5GlJYJ%!cRND|atL&?PE zuMY8F*^k!qzvO4#*6%icbXztnY7x0)|Ql}nfyY=54Y^r;`(xeZBq1}BE!9UjAhjW#3$6H!J1jVD3R^`T`c zvKu|j8(QLQx4EP&Y`~*-vUE?~%eee$YLhyl3&`04$Q`F-)=r^HQrti7YIvQvnfaq5 z+{4FeTz&+Zp=JZ6>NGx|C(^!4>j&$Q<~mf6wST4_J4Zzmti+9q(VA8{RmYIY3e934 zRJ`7o*yzOI?qf;zeBRxfat!hb5$pT-mlno&?J=M%pHB}wzVclx6#sX^$gm#>$9Q18 zYryc#IHXCI9z*jW-uW$gTNBhG)Ro$~_NBP1#{7^G91{eK>ozH5ZLuGP>!rH^+i)U4 z9*1Urb0M#UeDfMfBL#p)VJ5@_74t!r@e~KLk#TdpA+2AS$uWr(DAZ=SXE>+p;rz&z z4Oju)1DV_vJBIEJe}urC?R&m(rj9W=ApilV{%xwR{PH)0|!mQ%QDG;KI{NRY`8~HwcQ7(r4<)_NxVF zon6ShbP}QV#s(e-e*lrK&-($V~rN1(s<>p zG%v^*h*)Byg>=3tT+p~r$inTsjg_{qt3Vj3ia7LEg;JLCInunLk!X%cI$LxD=jd9N zzl?|XhE+*}udGu4I^nwf=JqTZNw)H?*;#V!PpFc`w>Z;@V*OS+lTPmnxz0kP%)l~S+_v&$h zD61`Tl&d7{$L8I-$nZqdYkQ)}E}CZdc-jl@Ylm+0`C>7kSRbICOSaT=YoRo1zUh1X z0zmR5KM0*msW*cJWbZ0wT%IwdArz7A-RG zpVs)47R#s8%m>3_JFPZU99CK}``yE5h{*;t^F{$Y&Fpn8Q+(@=Nq@r6J4@jjmkcxs ze~co7SJMMdnCq9TAYaFw#}b_@zHrc z6w%0R`3R^%vDr0O_suLp%SuN4kkWF7A$c5>n8TPP@Ihj1ALkO5q`ypN#Z0{X1{qW@ zgC6>E(ANi1uPD70`H$f+Dy{ZRYkh?pNaK1I7=6)J!d93tR{lCB-}k{5RWYo&N3JPc z)*73$S$QEKS=ky7kKGicb>$Q~lG~uOlP}%;PihZwXZEtfTqhVl1YTZUGzK7$)UB{idd9>G zUoc+V&ca;c=p>FdheeJEj3_&<)}gscgA?^g-FT6nc{YuVV#@mn^}Z;{4jc}mapMg` z&+d*?J;H&%oHF9NH_|WdR4m7<2+{Li&1AOv3=z|x*zVhvZyXgh2r&mnKNyD`WoRde z*s=%P^bNbEsF}X`9}_!0zujLCK*q`*Xf*DZtDLIj((MMbSYY3!CKy}THIRnj4$i&u z;<`}D>PhFbL>>zrjf_j0NQzUAn{|vjn)IGNn9&&l8n9Q)bw}j&#uY{4wXCf9a;D)fHsmDAdt+PcMn@y^?Y5|?j*cb}gP{DnUyfw6 zRA~V0)5SIx94`0}-5qQ}QX#H%5gY6wU8TyUnabR)s(Ilhe2Z_V`N*~d{hemee;uy+ zqmu@q^Ior+6d*(N06qH!uPrz2(N-z6ra!)h?9V$Bzihnqm;8> z4vLAtAto((9^g6i^FzXQwJ9`hhfkMF)4SnQHxctAi-NAF(eKdLFxWmImDp9=`boI) z*-3#M*Ujc&rQ1v~)@5nVG^Co{Z+K38ojq632NiBBC%`I-pdq~;p0-i*`b}}?(Y$Oujy~+CcN_xTRbjj(e^O)V;6&|%4Yxinnu9DOjI8xb~dUFFQ|GGe7 zZjIE~<|eX#79{viCNA`Zn-W9RytiB_a#}>1kaA53M+8|gaWyL^+=^u0kv zkfcP83i=rN!y-BJwV^iqwn|ordqD2)UANJ5ic#JN zMQQ_S1?JTQClo0Z_a9qDs^PKc!(*H*=~vxEr2_+kDb0{>lRx!?*~=0j_b8o!PQf5= zK|(u-_vSMX6AYfb`Y`uKxlv{DS(4~z-_P&Ajn^LjW?=BP|0cGpESZ97K&USMFuY`lphCbmf5uD z4->UvEP|zu+gJEoz7f8%8z}+MKu;!Lk;OZr$5#KGpmOCsuix_AzJKPtw~+bO?W(;| zo3>p>npdrH6Xjt6uvL6?N_MXBNh_-Rb$WTdfC%8`Y~M8*&RDq z!AIEc;tUV8o-kc`93rH0JGNZq4f$to;8ZW(m#kHZE1pj@9s^x8ZZt@*VIOZ%uo3?G zi1;R&#LVfZAE#?5h10d#C%GKy`ypzPzt3wN9cvA_V(RO_hH_t*vh`-Zp|c2+obwFL z0{2~3Q?|D*M?@hG@8BE)!TurB>A-~Wmmlq4BXHPoKMo(-+m2zHzRfDx{USimF+*cH z&1pGz56=RSG<;nQiJfK(ZLmdG@`@$o3ZAF-V@jmQeD;8i3nxQ)h;5`x9ydDW%vO}wlKe-Kjp7VhVyHbS6-bIe$%GmM@L(E9p1>wEK&5#Ad*$ORJ6JrBAyCRl2I81>BDqGSv8Zj7NWed&lK zt5(N9v@G15v$1-2yd5vUw68wD5t1pmFA!q|c%fwNUow+l*wv@hy;(CWxg|$t zv>4(TUHFrsfR_CS ziRr+cO6c@Jb!qSXBbR|SW$V-tKTWsyE!~BuXEunG8B&c)2Dq}H;$Gpx&>npW!vExD;7=D)ku;uL)3mzWtm1)yV$Cy-L?2 zO?~3KZsSBO#lPMi`wcpAvbNN(YrF13`4TngvhS|G*r0J5v%_X{zMtEg-As**fOr>W z4Is+1k$?s*-0r%BOqFX)LcI@Wv{0jEM~JU*RRh<^3Wz!+Ig@5PX}MH)mUa)t5%E0m z^ulKE3tJGHn>IB_Me%vB&~6{NH3?CwEIZgwh3hA?GnbbYK8JnU2F96B) zx3bLd`DWEd{D$O|m*!;EyuNI&|Li_~rZU*(oL+ICp&*my3oTl*eD!)PKL|az{P)pL zglgS}uS>{8&4TZ+IS5FoiD({bcc_`USv%b2PdN6nQv1FqnWJeNcSWlsBem!FQ?KE- zlDV8Ev2@{r#-?1A}tMmv>9;C z)X!n2*4`AUdNfG=?&OHzNFX}o=)2mk-0RMg@!beg|6s0*{ohl@31H87u{BkuGR~4c zt-3_tcv*Lr!9Ck1SpAi5)64!@VpM*z^>R63gD$@6J-^kPJ5UKw*^`)JcV6)hEXDwr zQHO4EnRRN3BZ^Q{P)WtRFVUt{I5Gk1zGNo1uP0H~@Fe=H>Bz=P>v`I1Y1Is>>A=`& z-{#div5OtEm6;=+8j*TVfFNv6co8cb zx=pV5Ds2``Cw69=bfZnT^paF7Hs#Qn^>1Dg$uBH0Mg-u;G_qAHJJ5MfU(c#exf`}+ zkZ^sO%5oXBg1`4;->}61NebOBB7ccS&2a9$CmSFmNuA*0Xz!e@B`tUvl1-^#8}XIj zJnrXqu)5wOudZgwtx9+L`zY}6LFWSeARfh#BLM5}>{eroLU&j=>cdvnlNpnlFP7N& zq@WV+noI$$#3T#1Y{V$F*@f$&)hen?c^*C`=X(MgpHY{<2P^!^KcYvE#<^u`n7A=@ z!+i{c<7*x;x0Nxc$I#je#VLvy+|Is?SOMAl2J6@rTy)@wfJK}+Fg;jc@iAn7t~Euf zn%pr7QkPp|p)3K^!xZ-ttsuYHoDv5%R?6|T^vc8<^R-<5a-HSUjE~PLU?E&?cx!A=%c!^`NegE`YD5E|*Y1t*4*9xXkXXM} zfzncs6Y~pM1~OBe&;Z&h2yIKW0lrHwE{eiyPIX-eB~bZo%_n;+gv9JeWwp@ zcH&iD_Rm)HE2Z0SGu@O!B0>l`C^hpOUHi(y*&1J6dfj(0hOEcmXTF8#FUS7+<3%YH zslk)#IF6Dx_Nq}~kIcKHWJ1U@Mth{K(I0mCkef*bXMYYtJu&N&8Op8`|1qwd*tfd( z!k)R$9lp>c=Q4HCaXn%#N)L{oFMs8T#2-LxgqKEP#<+S#p4s&|KEkuP&81pwi9NV4 zv9~!EnM|*{nY_LgAq^mq3dlHy&HOEJ0;KhBtGt?~pF{F{Y!%<= z!Q-JzFzFW#gfy!8* z^|HKhv=LBX=cnr3&5QkDwbuolHPXYlQY4M`d#dUeI6u=6yBY(VrPIl8fRf;mAW{G7 zd@B~DsGs%WK;$xamfjRwMb>u&qAmYQrUR#E(-P6gYMKg=z9pS4wqoa`zIIdPhthmS z^8wbOpJ22dUq}giz>9-r$OZY=N!CuwH4rL_nV=NrsY*GcP4O(dJ90?ZuQG2nAKh_k zzj3^W>igWC1lPxL%;R9g zL;BLCg%DNh(x0)QtARDZ(!IyJ8~2i%p!mAGi7H}2~(LheD^0*lbiL3WLtI;%JD1|{FTxCd1`_)6}ahrQq)E*V>R zhJOGRC_o5vgoA(u`D4pZjYcoUcG56tc@>Zo_9emmiLQ%ERFO7Ih2%+-!};Gjty>BlNzsgdUWk}7f?~jb{2KMi@sZ_~o)G2PCUN!0MCm6|n2S4Io6IZo=#+9Mna$s(} z1>z0(NQuTB(;xJGM>qu!E%*;c(C5r>aBO=mnHn7}yL;c6FdZ+A0RKP*v-m6Q=P1~T z)Uy1l9d*@>N9|4lW~1U6^TNMmIo}h|o$?Tz6yjXc8>t4E*a?VEK0CCJPG_PuUtEY& zCdvEv{?=-=9NXiY0>e_DkzBjsZA5ew(6zr7miCOOyqh?(xfXmM?4Op6)xUrQo}7#H zBg~e{=K(yRlrE^lBVog(ml=d8SKcQ|w_`f#Q11Vc067zgT+Jc*C^cE04X^-Hg$@6}V!#oR24KR2LE_eV%YVZhE9=82_Y_ ztgTGHZY2qddM%h}zFeNn8a}-*QGU<3kds^mJ|xw+o?{V25oNW(zB7rlyg87}?`w~< zTgBa*_NX%kl#;Aua$eT-X;jgz#-KGz=FRq8jJ~&e8+PAYGOP>Lwg31ZOQld-rgwLD zmscqeaS35U;K-Q7!b93SIN*hKQ14n*p_f;27+O`4`ER5WB#-E=c`v$N$RS&OGN<>3 z0vU3@NDe_y44;(PuE5viJp5p7{oK@3Frj88Ieb?akW)9dX`QRwCR?^kPbp9TuDSIA zXc3Bj?Pa|BT0;E#aQ0Zns`L+`Tz05a{rGhOO2Bhs)Njj_+EIZvO&FbXyk77|KcHYE zLFJ;gzv8xRIHQ5gYWW7X5zvzhC2d#MkBjSfa8c`VU{78Y)=^+9+U#Y9nZT<8l3ANy z3^<{NSNrt4?#N~-GWE{~unL4cTMntjn6{4L-g)U&Nt)G@_Q;o~q6$ftk~rFX=S!8= zk>@J+X-uN0bMw<{fc)0AEa^}vbxES=pbeS-#{0-iDWewi;hAa_J;{Y~*@b_72H#XQ zfwIa1HxG|SfV@;989mSVb~?UOo{LXm?{7M$94n6$mi>LjRKFssP`34@kyS4J*8D|l z#vlqd(~C2SdUxhhdnhoNom7ASIK7DqEbS3yB;)j7;L=K{XN+Dp`-q^tm>q~Fod&d( zuCnl?ZXqUe*yb3F&!qw;g@KV=Y1a<0+5Lv8F9*!ueP3~HNHRoC9Y(>lojag*+Izb$ z$;`LtyUYA-zk0meB}2uA*$H1Se!tVw!wkW=$kph^C{F7~Fq%}?)nWn$?Q5)iGb6wd zTQ#>@8I;N**o+5P2eHAr)#xJtFPKcR^;-?otY|*h=r(?`Q4QG(|Hjvqt#D zl7e)D;J{ZZ{O3Go#B0}0e(;myyM_Z*bn+|B)z$W=c4whtQ0hbwf#jVn@i?(Y6u}c? z8DDU3JaZq-T2v%*)@V2*oV(eA;sXw?E|WS~b!OVM4$!Lf=TMqz?%9}F`IYwJHb4*e z+{TImxz5$vrGq9@oAGVThyVrXp`zWYTQX=6%>?BO`Xv7hqTQ*16pT-TH(Ji zC`*?lmqKItt%jK`$@k@B#T|IxrG45C|_jxv~GIK4aTFuuv2NobkQ>C&;(IQd|Y zpbchdifo{JaPwJ4T&hliLy9_l>fkh<3g5Y$Z(fVK(U|<|<;ls(l6BnZR-eh7{h3v> z*b#SQJN1X2fv7iwz+074OIsB~Hr`~R3&elH!9J3u>!RqnO?$&K(lo|H?oz}`j9w`N z7@RDtYiSauw8PiU2PEd&zaxg#brDK}F01?`^9|F!#T>9^e5RvkS1|4fU$syA9_Zf} z03|)Rj=g$OQ1v(WcOO*X{%4h4l?fUvW;Z1fqNO;^Rpa)k_4k*>dF2S)RpVsrD{WT8 zpHXUD?G2tHSNqHXoo@&knMgW@8muLl5dVYbVF!cJJj&#QIfFUF?5k_J8a zU9uQ1xSLkDU$cauo{H}7MjUUhkv=ZfQl;~v^I80o^b!tC7c&)o-X-CQAhyj4)xnE$ z@b%m1%*U(Olp6`XmTtz*X8V)cgJddr5!0@QRbvx4R24w9R)KUe?e#*b-+Ke*QJP8H!sV6jmxG!quLN#Mw6@tIOJ6H2z$ z`=-6{=nX#RSWi*t|HjE$x!jiJ7Tt(twiH7-Y=i;ao{Ez8>7|VZXR#gUF_afdnauD> zX@F!f=xA-dC+K$b;Yqk75;1$iR3cYgkxpPSjh6RD^$a!I;8t##k}r5;XuP?m}8PQYh;J!w3GwQkM97 zg|xiw^M2-lSxa&oeqCI4Tfs2`6TXV!-21#*)_2#{gbAP<4f7ZJNY82N zJk%4>N}nK?A6$Y5zgI*HiO0A5c&tGG@VK$|F5hR-PNO~qXfX)f)7R1Z&~+$*tuR^+ zadBN!dMjEDv7cWt|C{3YmnG~!kQ+|b$d!?0vpzfRoJREs-M%5Gl%#k~$b?GVLM5qk z-QrD&E?^L>j+De<=x9u`-78P=W2DiKxkSR4BW zf~yVA`1Hmqj}L)j>pi(&rgPg8CyPzeRneGo}zh zNLi96amEKi27n%yO!ox4COicEw4UV}F^mAxo&GJ8{ti%3i-LjG;aW`Cet}%PgqFEm zhtNa5gD)(dNfkE3@+3(;93({EEQ3W6JlRL#{{o;fh?rAT{1>}ZgmpI`zm^Q!Xjom9 z>J-ez90icfCU9RL&#C#G%<(Myu^jV&$p&l~O#>{38>$eBL&!#Ra+=xM2|vN0&_ax7 z-fByt#)nda1F-{i&OsJwRFGUGEeCB-`#sKz7`wZcK8I`^=Y$(jTjzMRD8iZWaKnfkB(*w+mCV6`AAJ+rD049uyrUr zJQ~r$3^d*U6>ZMTa?NX_D+jzAq$21~1qDWTWGW{+v)6eKWpI&jbPkW4i-YNE{8#jQ z50riZ`wxVGl;^@v^}}~etf*} z|2u&Y3Xwd>eH%^mX6fPxeILy5_sws5^ek=da}J>Sb|F`z5C`{U3fD>uvl!6hG|l)$@{&K= z=Hu+Q!=^OF)<@BTFY6Kt1@OI#;llvW;G${4TX7dOq=Z1d50T2B!q$2E6@&aSrHI{! zM1mkO0`E7CoUHMhQ&w3O zWHolI6KCN0sN?|coAi>*p4A%4PR=eYnr40Z=G5me+-YM09IHRY)^9$4X|WIQ)Z+Tf zu;cZh+wj$vmdui2UhL0~7faNi%Sruv1PDJ*+02m@Mv!uqqGD4|Z2vx#TDe!NLAM)3 zwGPmH$S&0>`G@{U+Q;ul?u2tTShohFG5%aWK%4B&S#!!co3-ID zT2KMLcl|W{Ru=y2l7y${>C-{~H233EMbVVdvLlbn3&sg=W2qx+S}Pk)-7(TOAS+BM z_|`m@X)zO7`>Xt_O5K;1zY%t(6J)+QwcJ&bIkk~K4+^~O)i551Jatar5DyuQ<$7+t znQ1;V>0pq`y+#s0=bk_D&ku>B+tY-t0toS*-dqHwE|(%bcd7IFV0TWdV0{=v+TA!P=*bTI=%TH)M~8FNL16M$ zJv~KiJ(uIwq4fQhE=I>HKJ%^8!SjBe)%LHKmK1k~5*^b!c9cO*krXtkm=^0?9#UVP zH1g_okBc%Ms`arXOfnt2Zh7C%^#j?qUR@*ZU%~zj2gwuMU29p3m*AMC`F@x+D^>5O(G5 z6%Nam*LOEVo6o3oeonjB22CC53ka4!+?DlU#bM){{h-^Yap*nqe|{if;;+*t2K&3v z@z-XlbQ=d|kj8W|hbzQz6%~i@sZ_M@92XTOH_@wQm18dSz)fp*v76taLHcef*Ax$R z2IJgBlmd7HIJwR;2;6c!*)6cerbpzLe>Y9dJ_4wEM>fS$>TjmO%h^XWUaOmp3>;k)Y( zKbj4Srn*Z?(xt>voT$0OyLKka^kREIny}_Rs?s{%n^6{8-}q7lPLyhrnHt|ofIB^H zgZEagd-b#~(0sC*P&|OrFow)q-ghleC3g^NDXw&gEb-Bq+1^u8F+X2E*CYpirCvr~ z?YMkNzrL&C~W-)i@AtnY#$HtI&*lof6KGZoHg4lidcQzz+htk94OkJ0lD3bHk3`d$+ ztbH4N@d8tXR;>Rdc@k$({(WVjuP|q|iz}hWw;z9N3GTh=57OmUc5&}H z-qpdQF)!Tg<+It*V{GF!aGEa2myI)aANXT9x;LBz-XZ2_(_eoYAQ81bUH4=4iT!*G zLNrd46qKTz&UO)XJ#qSorZM`lBRa0XbH40>H?BryZ+iN9%37y4kD7_O$Cu^-_Bqa| zxYj0+`f}ot^v3sEx#rKu%&7HiZ+bVizs+qh`my@YH%ah1)#e^-A9V0GzPuF)tjsMi z=;*7kdZH{GJ!!~PRObgofQDk>Qtt#Y1Pz!xS^OmGotaq0pxM-; z|DEJov|w6)9+hz96H6+B^Z2cu71HZkD#_oYmdocMl1f%BnSHb>~;Ww5ujiCxT zp<$`()~9Ckpk(VN>gxTB0x_}_nXlbOn+lW!fCFdv@-l0MP_M1n5n*rHvqNGsmveOI zEq|o5vM<@e+{* z!fON}tAZ!<8#1dw!o6m~;nyD-JyUW=vleK?CDvku`X4qpgCRe(dZ(R^R>0=~lE99` zAD@zR>u5orz(_hPK-J(U`;D;QotY}l`$U{KDim+iz)`M`%~a)9=@>xCcYv9cJF=?GUo7nCi@!o$OtQF}z1d@JV0QNveMK&`DF zXL_4W=n+=rkm}H2Na*PJXzfM8_6%-hr%0}vEub>Wk^tHGB3N(q%?A17>@r3yr?;sS zU)#^{7URX6vZZv^^L2Z%lmk8MZB~0jd@lWRHIEIrUde`DbrxO2zqS@vWz3uIlR@_$GeILbYe(CY6Hb1I@c=g6g$d0RTUfS2fJz zjsedv>UFwcS1i@Uh<0+`M~n4t>lS;Jj0YISW5ZQ81OP*QieRtHWmt`hIG@5qaEI zd=cb}5-m9o4;7MuYHc6OLD}S;S4VVrJ6Jqi&xdJ1ZKlpo$f^7L=e~X1oYnEXoOPZB zVE&rDc)HU5EsA)3Zl(6zjPso5SJNy88Ml3tEe$14a6FU6BxP2Z`F;#6aui&&kuWrK z%M;pKWBn#pWn5)olTTr-g%)obt{pIqB4j`>Ij~%CI4! z4LhED%Y1?Jm0Dl~lLC{_4@|BezR=L>WG5e;Vr`VmI-z1D*6~)fJy%wURK~cw)z1$H zUNfLkfA(+pJ9JYY$(uxiP6+bz%ep2#PK80)oyves}-|>-XC{rO8%zx=L22{D3 z(7zw2fEen1Go_qET%GYa1OG@K8>f52AG3v96twN==vSEtc~;iGJLnhonAc4U7u*Y1 zjG7G7+TEUn5T4xxwTRzc2><2!B)%@zSIm+t_{4tFgZAopF2=?&7jYLPC(-qW)(`Lu z&iGLCLiFB{SF@QIK}N{@Yx9G!z_mJHSY}WBxy4^F!`-BPu!;G({@c;XhB zs?Xk(GTW?)CwUUahMDfk^TkOiJo!cMGQRSt4yoA-3;%%H=(T6baMEb1G2kHA>4(3E z`OM+|{EEI^0g3t41W%b?}^Pb;Kkp8|4OL#2XA_=kv&XtENVsy{=l^<7^ zOI;e#7CNgAdp0H!t4LbA8Z9)?fp*O2(AWi8W4A&_E(f*~qVv}tVgi6Ph=gbttGQXL zK>V9I`&BD}J2o{on6IA4+lFs?r1-9E4tc(MC8ee1IwxPX0@Tz$^HH)yHT;cL4*m?`)G zlrDoq>M%`S#e7zr-504ap&ww^`;x0;#>*~W0Jz8R=hMO|3yB0pc6xF^p7YKw5DLpI zzS!V_rpvrL^aVe>^Vl?%aQIvc4Z`Tg#sMlAl@{-z?iTcuK;vF=9xeApG^AcTeHYYwu-#5PQlPw=kVm;)x-9(L~0g&HTy| zeUM#C50EudD%gq7W63S>Mt(c&E|_|4r+P6iO2`|lFzv?Fh*B@z=G;_rJkt)tqQCJ| z(7lQ6yg;1AVF4{z9JR21*<(tFJs$yy($n4zir}8YQ`icMUCdXgjPkWkM{8`_QJ=K< z+f%&DcsH5v4Me&-+-#Q*;z=p_;hlr4^{PzqY+x+yFw6O$r6AqY18UxPI@*jSQjyb>~wqqBce(K5FjWnir>B3O_C+?F&sj7e59akWj~H zJ<9e~Dcfpr(ENIm{^k|nTj*W7K25v2W~4K}<@g{}pf1==4a9&ZvptqL?72pktM~$@ z>JccboI0*lER+6?oMiLDAMkE}S*KlhwXz)Tzj3 zb!pp_H6pqT2zLc7_P}_rqv$WBDnxoV@yTdApGl|>bbomDDCTXv#dCa5_sqfal6MdOHqcD`U{#WRnaR`5Ly_*-i_?6tf)>iJU`xz5|b7ahQV6)G4kU z24G5g6~eVbf(vsJF9rQ0-pMaC%G=u;jScxNI;K40xJ4+^j^{M z7<{lVEcvR=JZNKP%{Ek%PpY{As;aCeD?h9&r@8LLe5k6)6=DT*5BAxJ&<%UP3lhl za;$~TdW#)3>9p2Rv6L$^G?DiB2}k=(`_uewnSZ!6`93(j4glrTIjJuylM1p06GzjD`m6ZAvZsi*w~9^AN18QiFaNz9*9r;5B&Xn&-S*btT=0%C8OZfDp=?Obyz7(uZ+@p z1daE}wElg)M#F7^wQ)zL*W*OKD2+BDS0_%J2-lqPt&5JeVy0dD>MzKo5~W_88Oc7Tt2usV z!Kh({2qo@DLuS-b@D9+}K0oxyFR=boo8-R&7fF+nq^myv>-|;!-?o{-4VZ!ugofG? z0!5I^SL801hnDpFzq*zGHxBauwDSVCKIq?$3H#Q3X31FIyxF_pMxX~qij6MFd<}=6 z!+)ySi08eA;(P@I8$9CVhkWM_Ai+%@^MV54Z4u51YtvJ-+w4k|G+vvE&b6>JQZL*q zflUejsTG$(7_B|;UYM-X*o^*(3<5r0oYP>Xc$ND(d9y{w9g|Ls*gkxtVm7}}jg*07 ze)kcw(xg3*i=q%t87NFi=xxfWiT3yXn$YoP@y^;~=n1!S$pyw>&(@oas?3hHu#A>O zo{p|3P{7^nN&f%U%}$ItIbUU2`#4N@#=j%}nhJ_uMa)mDos}4w(HMaJ-t%gA<2-MT zK|5TxRcj2x(=&&w$O8n5@6slE>9N9h)&;D { const classes = useStyles(); const { settings, isXTMHubAccessible } = useContext(UserContext); const importFromHubUrl = isNotEmptyField(settings?.platform_xtmhub_url) - ? `${settings.platform_xtmhub_url}/redirect/octi_integration_feeds?platform_id=${settings.id}` + ? `${settings.platform_xtmhub_url}/redirect/opencti_integrations?platform_id=${settings.id}` : ''; const { t_i18n } = useFormatter(); diff --git a/opencti-platform/opencti-front/src/private/components/data/IngestionTaxiis.jsx b/opencti-platform/opencti-front/src/private/components/data/IngestionTaxiis.tsx similarity index 63% rename from opencti-platform/opencti-front/src/private/components/data/IngestionTaxiis.jsx rename to opencti-platform/opencti-front/src/private/components/data/IngestionTaxiis.tsx index dd21a520f039..9ba3ca61ccfb 100644 --- a/opencti-platform/opencti-front/src/private/components/data/IngestionTaxiis.jsx +++ b/opencti-platform/opencti-front/src/private/components/data/IngestionTaxiis.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { useContext } from 'react'; import Alert from '@mui/material/Alert'; import makeStyles from '@mui/styles/makeStyles'; import { QueryRenderer } from '../../../relay/environment'; @@ -6,7 +6,7 @@ import ListLines from '../../../components/list_lines/ListLines'; import IngestionTaxiiLines, { IngestionTaxiiLinesQuery } from './ingestionTaxii/IngestionTaxiiLines'; import IngestionTaxiiCreation from './ingestionTaxii/IngestionTaxiiCreation'; import { usePaginationLocalStorage } from '../../../utils/hooks/useLocalStorage'; -import useAuth from '../../../utils/hooks/useAuth'; +import useAuth, { UserContext } from '../../../utils/hooks/useAuth'; import { useFormatter } from '../../../components/i18n'; import { INGESTION_MANAGER } from '../../../utils/platformModulesHelper'; import IngestionMenu from './IngestionMenu'; @@ -14,11 +14,14 @@ import Breadcrumbs from '../../../components/Breadcrumbs'; import Security from '../../../utils/Security'; import { INGESTION_SETINGESTIONS } from '../../../utils/hooks/useGranted'; import useConnectedDocumentModifier from '../../../utils/hooks/useConnectedDocumentModifier'; +import IngestionTaxiiImport from '@components/data/ingestionTaxii/IngestionTaxiiImport'; +import { isNotEmptyField } from '../../../utils/utils'; +import GradientButton from '../../../components/GradientButton'; +import { PaginationOptions } from '../../../components/list_lines'; +import { IngestionTaxiiLinesPaginationQuery } from '@components/data/ingestionTaxii/__generated__/IngestionTaxiiLinesPaginationQuery.graphql'; const LOCAL_STORAGE_KEY = 'ingestionTaxii'; -// Deprecated - https://mui.com/system/styles/basics/ -// Do not use it for new code. const useStyles = makeStyles(() => ({ container: { margin: 0, @@ -29,18 +32,27 @@ const useStyles = makeStyles(() => ({ const IngestionTaxii = () => { const classes = useStyles(); const { t_i18n } = useFormatter(); + const { settings, isXTMHubAccessible } = useContext(UserContext); const { setTitle } = useConnectedDocumentModifier(); + setTitle(t_i18n('TAXII Feeds | Ingestion | Data')); + const { platformModuleHelpers } = useAuth(); + + const importFromHubUrl = isNotEmptyField(settings?.platform_xtmhub_url) + ? `${settings.platform_xtmhub_url}/redirect/opencti_integrations?platform_id=${settings.id}` + : ''; + const { viewStorage, paginationOptions, helpers: storageHelpers, - } = usePaginationLocalStorage(LOCAL_STORAGE_KEY, { + } = usePaginationLocalStorage(LOCAL_STORAGE_KEY, { sortBy: 'name', orderAsc: false, searchTerm: '', }); + const dataColumns = { name: { label: 'Name', @@ -73,19 +85,29 @@ const IngestionTaxii = () => { isSortable: false, }, }; + if (!platformModuleHelpers.isIngestionManagerEnable()) { return (

    - {t_i18n(platformModuleHelpers.generateDisableMessage(INGESTION_MANAGER))} + {t_i18n( + platformModuleHelpers.generateDisableMessage(INGESTION_MANAGER), + )}
    ); } + return (
    - + { handleSort={storageHelpers.handleSort} handleSearch={storageHelpers.handleSearch} displayImport={false} - secondaryAction={true} + secondaryAction keyword={viewStorage.searchTerm} createButton={( - + <> + + {isXTMHubAccessible && isNotEmptyField(importFromHubUrl) && ( + + {t_i18n('Import from Hub')} + + )} + + )} > ( + render={({ props }: { + props: IngestionTaxiiLinesPaginationQuery['response'] | null; + }) => ( ({ - buttons: { - marginTop: 20, - textAlign: 'right', - }, - button: { - marginLeft: theme.spacing(2), - }, -}); +import useApiMutation from '../../../../utils/hooks/useApiMutation'; +import { PaginationOptions } from '../../../../components/list_lines'; +import { IngestionTaxiiImportQuery$data } from '@components/data/ingestionTaxii/__generated__/IngestionTaxiiImportQuery.graphql'; +import { FormikHelpers } from 'formik/dist/types'; const IngestionTaxiiCreationMutation = graphql` mutation IngestionTaxiiCreationMutation($input: IngestionTaxiiAddInput!) { @@ -39,39 +29,86 @@ const IngestionTaxiiCreationMutation = graphql` } `; -const ingestionTaxiiCreationValidation = (t) => Yup.object().shape({ - name: Yup.string().required(t('This field is required')), - description: Yup.string().nullable(), - uri: Yup.string().required(t('This field is required')), - version: Yup.string().required(t('This field is required')), - collection: Yup.string().required(t('This field is required')), - authentication_type: Yup.string().required(t('This field is required')), - authentication_value: Yup.string().nullable(), - username: Yup.string().nullable(), - password: Yup.string().nullable(), - cert: Yup.string().nullable(), - key: Yup.string().nullable(), - ca: Yup.string().nullable(), - user_id: Yup.object().nullable(), - added_after_start: Yup.date() - .typeError(t('The value must be a datetime (yyyy-MM-dd hh:mm (a|p)m)')) - .nullable(), - confidence_to_score: Yup.bool().nullable(), -}); +const ingestionTaxiiCreationValidation = () => { + const { t_i18n } = useFormatter(); + return Yup.object().shape({ + name: Yup.string().required(t_i18n('This field is required')), + description: Yup.string().nullable(), + uri: Yup.string().required(t_i18n('This field is required')), + version: Yup.string().required(t_i18n('This field is required')), + collection: Yup.string().required(t_i18n('This field is required')), + authentication_type: Yup.string().required(t_i18n('This field is required')), + authentication_value: Yup.string().nullable(), + username: Yup.string().nullable(), + password: Yup.string().nullable(), + cert: Yup.string().nullable(), + key: Yup.string().nullable(), + ca: Yup.string().nullable(), + user_id: Yup.object().nullable(), + added_after_start: Yup.date() + .typeError(t_i18n('The value must be a datetime (yyyy-MM-dd hh:mm (a|p)m)')) + .nullable(), + confidence_to_score: Yup.bool().nullable(), + }); +}; + +interface IngestionTaxiiAddInput { + name: string; + description?: string | null; + uri: string; + version: string; + collection: string; + authentication_type: string; + authentication_value?: string; + added_after_start?: Date | null; + user_id: string | FieldOption; + automatic_user?: boolean; + confidence_level?: string; + username?: string; + password?: string; + cert?: string; + key?: string; + ca?: string; + confidence_to_score?: boolean; +} -const CreateIngestionTaxiiControlledDial = (props) => ( +interface IngestionTaxiiCreationProps { + paginationOptions?: PaginationOptions; + handleClose?: () => void; + ingestionTaxiiData?: IngestionTaxiiImportQuery$data['taxiiFeedAddInputFromImport']; + triggerButton?: boolean; + open?: boolean; + drawerSettings?: { + title: string; + button: string; + }; +} + +const CreateIngestionTaxiiControlledDial = (props: DrawerControlledDialProps) => ( ); -const IngestionTaxiiCreation = (props) => { - const { t, classes } = props; +const IngestionTaxiiCreation: FunctionComponent = ({ + paginationOptions, + handleClose, + ingestionTaxiiData, + triggerButton = true, + open = false, + drawerSettings, +}) => { + const { t_i18n } = useFormatter(); - const onSubmit = (values, { setSubmitting, resetForm }) => { - const authentifcationValueResolved = getAuthenticationValue(values); + const [commit] = useApiMutation(IngestionTaxiiCreationMutation); + const handleSubmit = (values: IngestionTaxiiAddInput, { setSubmitting, resetForm }: FormikHelpers) => { + const authenticationValue = getAuthenticationValue(values); + const userId + = typeof values.user_id === 'object' + ? values.user_id?.value + : values.user_id; const input = { name: values.name, description: values.description, @@ -79,15 +116,15 @@ const IngestionTaxiiCreation = (props) => { version: values.version, collection: values.collection, authentication_type: values.authentication_type, - authentication_value: authentifcationValueResolved, + authentication_value: authenticationValue, added_after_start: values.added_after_start, - user_id: values.user_id?.value, + user_id: userId, automatic_user: values.automatic_user ?? true, ...((values.automatic_user !== false) && { confidence_level: Number(values.confidence_level) }), confidence_to_score: values.confidence_to_score, }; - commitMutation({ - mutation: IngestionTaxiiCreationMutation, + + commit({ variables: { input, }, @@ -95,11 +132,10 @@ const IngestionTaxiiCreation = (props) => { insertNode( store, 'Pagination_ingestionTaxiis', - props.paginationOptions, + paginationOptions, 'ingestionTaxiiAdd', ); }, - setSubmitting, onCompleted: () => { setSubmitting(false); resetForm(); @@ -107,33 +143,31 @@ const IngestionTaxiiCreation = (props) => { }); }; + const initialValues: IngestionTaxiiAddInput = { + name: ingestionTaxiiData?.name || '', + description: ingestionTaxiiData?.description || '', + uri: ingestionTaxiiData?.uri || '', + version: ingestionTaxiiData?.version || '', + collection: ingestionTaxiiData?.collection || '', + added_after_start: ingestionTaxiiData?.added_after_start ? new Date(ingestionTaxiiData?.added_after_start) : null, + authentication_type: ingestionTaxiiData?.authentication_type || 'none', + user_id: '', + automatic_user: true, + confidence_to_score: false, + }; + return ( {({ onClose }) => ( {({ submitForm, handleReset, isSubmitting, values }) => ( @@ -142,14 +176,14 @@ const IngestionTaxiiCreation = (props) => { component={TextField} variant="standard" name="name" - label={t('Name')} + label={t_i18n('Name')} fullWidth={true} /> @@ -157,7 +191,7 @@ const IngestionTaxiiCreation = (props) => { component={TextField} variant="standard" name="uri" - label={t('TAXII server URL')} + label={t_i18n('TAXII server URL')} fullWidth={true} style={fieldSpacingContainerStyle} /> @@ -165,20 +199,20 @@ const IngestionTaxiiCreation = (props) => { component={SelectField} variant="standard" name="version" - label={t('TAXII version')} + label={t_i18n('TAXII version')} fullWidth={true} containerstyle={{ width: '100%', marginTop: 20, }} > - {t('TAXII 2.1')} + {t_i18n('TAXII 2.1')} @@ -186,20 +220,20 @@ const IngestionTaxiiCreation = (props) => { component={SelectField} variant="standard" name="authentication_type" - label={t('Authentication type')} + label={t_i18n('Authentication type')} fullWidth={true} containerstyle={{ width: '100%', marginTop: 20, }} > - {t('None')} + {t_i18n('None')} - {t('Basic user / password')} + {t_i18n('Basic user / password')} - {t('Bearer token')} + {t_i18n('Bearer token')} - {t('Client certificate')} + {t_i18n('Client certificate')} {values.authentication_type === BASIC_AUTH && ( @@ -208,20 +242,20 @@ const IngestionTaxiiCreation = (props) => { component={TextField} variant="standard" name="username" - label={t('Username')} + label={t_i18n('Username')} fullWidth={true} style={fieldSpacingContainerStyle} /> )} {values.authentication_type === BEARER_AUTH && ( )} {values.authentication_type === CERT_AUTH && ( @@ -230,19 +264,19 @@ const IngestionTaxiiCreation = (props) => { component={TextField} variant="standard" name="cert" - label={t('Certificate (base64)')} + label={t_i18n('Certificate (base64)')} fullWidth={true} style={fieldSpacingContainerStyle} /> @@ -256,7 +290,7 @@ const IngestionTaxiiCreation = (props) => { component={DateTimePickerField} name="added_after_start" textFieldProps={{ - label: t( + label: t_i18n( 'Import from date (empty = all TAXII collection possible items)', ), variant: 'standard', @@ -268,26 +302,29 @@ const IngestionTaxiiCreation = (props) => { component={SwitchField} type="checkbox" name="confidence_to_score" - label={t('Copy confidence level to OpenCTI scores for indicators')} + label={t_i18n('Copy confidence level to OpenCTI scores for indicators')} containerstyle={fieldSpacingContainerStyle} /> -
    +
    @@ -298,14 +335,4 @@ const IngestionTaxiiCreation = (props) => { ); }; -IngestionTaxiiCreation.propTypes = { - paginationOptions: PropTypes.object, - classes: PropTypes.object, - theme: PropTypes.object, - t: PropTypes.func, -}; - -export default R.compose( - inject18n, - withStyles(styles, { withTheme: true }), -)(IngestionTaxiiCreation); +export default IngestionTaxiiCreation; diff --git a/opencti-platform/opencti-front/src/private/components/data/ingestionTaxii/IngestionTaxiiImport.tsx b/opencti-platform/opencti-front/src/private/components/data/ingestionTaxii/IngestionTaxiiImport.tsx new file mode 100644 index 000000000000..1ce00f9c6434 --- /dev/null +++ b/opencti-platform/opencti-front/src/private/components/data/ingestionTaxii/IngestionTaxiiImport.tsx @@ -0,0 +1,119 @@ +import React, { BaseSyntheticEvent, FunctionComponent, useRef, useState } from 'react'; +import VisuallyHiddenInput from '@components/common/VisuallyHiddenInput'; +import { graphql } from 'react-relay'; +import { FileUploadOutlined } from '@mui/icons-material'; +import ToggleButton from '@mui/material/ToggleButton/ToggleButton'; +import { useNavigate, useParams } from 'react-router-dom'; +import XtmHubDialogConnectivityLost from '@components/xtm_hub/dialog/connectivity-lost'; +import { fetchQuery, MESSAGING$ } from '../../../../relay/environment'; +import { useFormatter } from '../../../../components/i18n'; +import { RelayError } from '../../../../relay/relayTypes'; +import useXtmHubDownloadDocument from '../../../../utils/hooks/useXtmHubDownloadDocument'; +import { PaginationOptions } from '../../../../components/list_lines'; +import { IngestionTaxiiImportQuery$data } from '@components/data/ingestionTaxii/__generated__/IngestionTaxiiImportQuery.graphql'; +import IngestionTaxiiCreation from '@components/data/ingestionTaxii/IngestionTaxiiCreation'; + +export const taxiiFeedImportQuery = graphql` + query IngestionTaxiiImportQuery($file: Upload!) { + taxiiFeedAddInputFromImport(file: $file) { + name + description + uri + version + collection + authentication_type + added_after_start + } + } +`; + +interface IngestionTaxiiImportProps { + paginationOptions: PaginationOptions; +} +const IngestionTaxiiImport: FunctionComponent = ({ paginationOptions }) => { + const { fileId, serviceInstanceId } = useParams(); + const navigate = useNavigate(); + const inputFileRef = useRef(null); + const [open, setOpen] = useState(false); + const [ingestTaxiiData, setIngestTaxiiData] = useState(undefined); + const { t_i18n } = useFormatter(); + + const handleFileImport = async (file: File) => { + if (!file) return; + try { + const data = await fetchQuery(taxiiFeedImportQuery, { file }).toPromise(); + const { taxiiFeedAddInputFromImport } = data as IngestionTaxiiImportQuery$data; + setIngestTaxiiData(taxiiFeedAddInputFromImport); + setOpen(true); + if (inputFileRef.current) { + inputFileRef.current.value = ''; + } + } catch (e) { + const { errors } = (e as unknown as RelayError).res; + MESSAGING$.notifyError(errors.at(0)?.message); + } + }; + + const fileImport = (event: BaseSyntheticEvent) => { + const file = event.target.files[0]; + handleFileImport(file); + }; + + const handleDownloadError = () => { + navigate('/dashboard/data/ingestion/taxii'); + MESSAGING$.notifyError(t_i18n('An error occurred while importing Taxii Feed configuration.')); + }; + + const { dialogConnectivityLostStatus } = useXtmHubDownloadDocument({ + serviceInstanceId, + fileId, + onSuccess: handleFileImport, + onError: handleDownloadError, + }); + + const handleConfirm = () => { + navigate('/dashboard/settings/experience'); + }; + + const handleCancel = () => { + navigate('/dashboard/workspaces/dashboards'); + }; + + return ( + <> + + inputFileRef?.current?.click()} + > + + + + setOpen(false)} + ingestionTaxiiData={ingestTaxiiData} + paginationOptions={paginationOptions} + triggerButton={false} + drawerSettings={{ + title: t_i18n('Import a Taxii Feed'), + button: t_i18n('Create'), + }} + /> + + ); +}; + +export default IngestionTaxiiImport; diff --git a/opencti-platform/opencti-front/src/private/components/data/ingestionTaxii/IngestionTaxiiPopover.tsx b/opencti-platform/opencti-front/src/private/components/data/ingestionTaxii/IngestionTaxiiPopover.tsx index 0e4725c3153d..87956c73b195 100644 --- a/opencti-platform/opencti-front/src/private/components/data/ingestionTaxii/IngestionTaxiiPopover.tsx +++ b/opencti-platform/opencti-front/src/private/components/data/ingestionTaxii/IngestionTaxiiPopover.tsx @@ -1,4 +1,4 @@ -import React, { Dispatch, FunctionComponent, Suspense, useState } from 'react'; +import React, { Dispatch, FunctionComponent, Suspense, UIEvent, useState } from 'react'; import { graphql, useQueryLoader } from 'react-relay'; import Menu from '@mui/material/Menu'; import MenuItem from '@mui/material/MenuItem'; @@ -20,6 +20,10 @@ import useApiMutation from '../../../../utils/hooks/useApiMutation'; import Transition from '../../../../components/Transition'; import DeleteDialog from '../../../../components/DeleteDialog'; import useDeletion from '../../../../utils/hooks/useDeletion'; +import { fetchQuery } from '../../../../relay/environment'; +import fileDownload from 'js-file-download'; +import stopEvent from '../../../../utils/domEvent'; +import { IngestionTaxiiPopoverExportQuery$data } from '@components/data/ingestionTaxii/__generated__/IngestionTaxiiPopoverExportQuery.graphql'; const ingestionTaxiiPopoverDeletionMutation = graphql` mutation IngestionTaxiiPopoverDeletionMutation($id: ID!) { @@ -35,6 +39,14 @@ const ingestionTaxiiPopoverResetStateMutation = graphql` } `; +const ingestionTaxiiPopoverExportQuery = graphql` + query IngestionTaxiiPopoverExportQuery($id: String!) { + ingestionTaxii(id: $id) { + name + toConfigurationExport + } + } +`; interface IngestionTaxiiPopoverProps { ingestionTaxiiId: string; running?: boolean | null; @@ -170,6 +182,25 @@ const IngestionTaxiiPopover: FunctionComponent = ({ }); }; + const exportTaxiiFeed = async () => { + const { ingestionTaxii } = await fetchQuery( + ingestionTaxiiPopoverExportQuery, + { id: ingestionTaxiiId }, + ).toPromise() as IngestionTaxiiPopoverExportQuery$data; + + if (ingestionTaxii) { + const blob = new Blob([ingestionTaxii.toConfigurationExport], { type: 'text/json' }); + const [day, month, year] = new Date().toLocaleDateString('fr-FR').split('/'); + const fileName = `${year}${month}${day}_csvFeed_${ingestionTaxii.name}.json`; + fileDownload(blob, fileName); + } + }; + const handleExport = async (e: UIEvent) => { + stopEvent(e); + setAnchorEl(undefined); + await exportTaxiiFeed(); + }; + return (
    = ({ {t_i18n('Reset state')} + + {t_i18n('Export')} + {displayUpdate && queryRef && ( diff --git a/opencti-platform/opencti-front/src/schema/relay.schema.graphql b/opencti-platform/opencti-front/src/schema/relay.schema.graphql index 636a6f94bb1d..db11cbbc40da 100644 --- a/opencti-platform/opencti-front/src/schema/relay.schema.graphql +++ b/opencti-platform/opencti-front/src/schema/relay.schema.graphql @@ -9032,6 +9032,7 @@ type Query { ingestionRsss(first: Int, after: ID, orderBy: IngestionRssOrdering, orderMode: OrderingMode, filters: FilterGroup, includeAuthorities: Boolean, search: String): IngestionRssConnection ingestionTaxii(id: String!): IngestionTaxii ingestionTaxiis(first: Int, after: ID, orderBy: IngestionTaxiiOrdering, orderMode: OrderingMode, filters: FilterGroup, includeAuthorities: Boolean, search: String): IngestionTaxiiConnection + taxiiFeedAddInputFromImport(file: Upload!): TaxiiFeedAddInputFromImport! ingestionTaxiiCollection(id: String!): IngestionTaxiiCollection ingestionTaxiiCollections(first: Int, after: ID, orderBy: IngestionTaxiiCollectionOrdering, orderMode: OrderingMode, filters: FilterGroup, includeAuthorities: Boolean, search: String): IngestionTaxiiCollectionConnection ingestionCsv(id: String!): IngestionCsv @@ -12786,6 +12787,7 @@ type IngestionTaxii implements InternalObject & BasicObject { ingestion_running: Boolean last_execution_date: DateTime confidence_to_score: Boolean + toConfigurationExport: String! } enum IngestionTaxiiOrdering { @@ -12807,6 +12809,17 @@ type IngestionTaxiiEdge { node: IngestionTaxii! } +type TaxiiFeedAddInputFromImport { + name: String! + description: String! + uri: String! + version: String! + collection: String! + authentication_type: String! + authentication_value: String! + added_after_start: String +} + input IngestionTaxiiAddInput { "*Constraints:*\n* Minimal length: `2`\n* Must match format: `not-blank`\n" name: String! diff --git a/opencti-platform/opencti-graphql/src/generated/graphql.ts b/opencti-platform/opencti-graphql/src/generated/graphql.ts index c1b3c84c1c38..46113d8457c6 100644 --- a/opencti-platform/opencti-graphql/src/generated/graphql.ts +++ b/opencti-platform/opencti-graphql/src/generated/graphql.ts @@ -12262,6 +12262,7 @@ export type IngestionTaxii = BasicObject & InternalObject & { parent_types: Array; refreshed_at?: Maybe; standard_id: Scalars['String']['output']; + toConfigurationExport: Scalars['String']['output']; updated_at?: Maybe; uri: Scalars['String']['output']; user?: Maybe; @@ -22169,6 +22170,7 @@ export type Query = { tasks?: Maybe; taxiiCollection?: Maybe; taxiiCollections?: Maybe; + taxiiFeedAddInputFromImport: TaxiiFeedAddInputFromImport; theme?: Maybe; themes?: Maybe; threatActor?: Maybe; @@ -24964,6 +24966,11 @@ export type QueryTaxiiCollectionsArgs = { }; +export type QueryTaxiiFeedAddInputFromImportArgs = { + file: Scalars['Upload']['input']; +}; + + export type QueryThemeArgs = { id: Scalars['ID']['input']; }; @@ -31015,6 +31022,18 @@ export enum TaxiiCollectionOrdering { Name = 'name' } +export type TaxiiFeedAddInputFromImport = { + __typename?: 'TaxiiFeedAddInputFromImport'; + added_after_start?: Maybe; + authentication_type: Scalars['String']['output']; + authentication_value: Scalars['String']['output']; + collection: Scalars['String']['output']; + description: Scalars['String']['output']; + name: Scalars['String']['output']; + uri: Scalars['String']['output']; + version: Scalars['String']['output']; +}; + export enum TaxiiVersion { V1 = 'v1', V2 = 'v2', @@ -36844,6 +36863,7 @@ export type ResolversTypes = ResolversObject<{ TaxiiCollectionEdge: ResolverTypeWrapper; TaxiiCollectionEditMutations: ResolverTypeWrapper; TaxiiCollectionOrdering: TaxiiCollectionOrdering; + TaxiiFeedAddInputFromImport: ResolverTypeWrapper; TaxiiVersion: TaxiiVersion; Text: ResolverTypeWrapper & { cases?: Maybe, connectors?: Maybe>>, containers?: Maybe, createdBy?: Maybe, editContext?: Maybe>, exportFiles?: Maybe, externalReferences?: Maybe, groupings?: Maybe, importFiles?: Maybe, indicators?: Maybe, jobs?: Maybe>>, notes?: Maybe, objectLabel?: Maybe>, objectMarking?: Maybe>, objectOrganization?: Maybe>, observedData?: Maybe, opinions?: Maybe, pendingFiles?: Maybe, reports?: Maybe, stixCoreObjectsDistribution?: Maybe>>, stixCoreRelationships?: Maybe, stixCoreRelationshipsDistribution?: Maybe>>, x_opencti_inferences?: Maybe>> }>; TextAddInput: TextAddInput; @@ -37746,6 +37766,7 @@ export type ResolversParentTypes = ResolversObject<{ TaxiiCollectionConnection: TaxiiCollectionConnection; TaxiiCollectionEdge: TaxiiCollectionEdge; TaxiiCollectionEditMutations: TaxiiCollectionEditMutations; + TaxiiFeedAddInputFromImport: TaxiiFeedAddInputFromImport; Text: Omit & { cases?: Maybe, connectors?: Maybe>>, containers?: Maybe, createdBy?: Maybe, editContext?: Maybe>, exportFiles?: Maybe, externalReferences?: Maybe, groupings?: Maybe, importFiles?: Maybe, indicators?: Maybe, jobs?: Maybe>>, notes?: Maybe, objectLabel?: Maybe>, objectMarking?: Maybe>, objectOrganization?: Maybe>, observedData?: Maybe, opinions?: Maybe, pendingFiles?: Maybe, reports?: Maybe, stixCoreObjectsDistribution?: Maybe>>, stixCoreRelationships?: Maybe, stixCoreRelationshipsDistribution?: Maybe>>, x_opencti_inferences?: Maybe>> }; TextAddInput: TextAddInput; Theme: BasicStoreEntityTheme; @@ -41909,6 +41930,7 @@ export type IngestionTaxiiResolvers, ParentType, ContextType>; refreshed_at?: Resolver, ParentType, ContextType>; standard_id?: Resolver; + toConfigurationExport?: Resolver; updated_at?: Resolver, ParentType, ContextType>; uri?: Resolver; user?: Resolver, ParentType, ContextType>; @@ -45076,6 +45098,7 @@ export type QueryResolvers, ParentType, ContextType, Partial>; taxiiCollection?: Resolver, ParentType, ContextType, RequireFields>; taxiiCollections?: Resolver, ParentType, ContextType, Partial>; + taxiiFeedAddInputFromImport?: Resolver>; theme?: Resolver, ParentType, ContextType, RequireFields>; themes?: Resolver, ParentType, ContextType, Partial>; threatActor?: Resolver, ParentType, ContextType, Partial>; @@ -47079,6 +47102,17 @@ export type TaxiiCollectionEditMutationsResolvers, ParentType, ContextType, RequireFields>; }>; +export type TaxiiFeedAddInputFromImportResolvers = ResolversObject<{ + added_after_start?: Resolver, ParentType, ContextType>; + authentication_type?: Resolver; + authentication_value?: Resolver; + collection?: Resolver; + description?: Resolver; + name?: Resolver; + uri?: Resolver; + version?: Resolver; +}>; + export type TextResolvers = ResolversObject<{ cases?: Resolver, ParentType, ContextType, Partial>; connectors?: Resolver>>, ParentType, ContextType, Partial>; @@ -48971,6 +49005,7 @@ export type Resolvers = ResolversObject<{ TaxiiCollectionConnection?: TaxiiCollectionConnectionResolvers; TaxiiCollectionEdge?: TaxiiCollectionEdgeResolvers; TaxiiCollectionEditMutations?: TaxiiCollectionEditMutationsResolvers; + TaxiiFeedAddInputFromImport?: TaxiiFeedAddInputFromImportResolvers; Text?: TextResolvers; Theme?: ThemeResolvers; ThemeConnection?: ThemeConnectionResolvers; diff --git a/opencti-platform/opencti-graphql/src/modules/ingestion/ingestion-taxii-domain.ts b/opencti-platform/opencti-graphql/src/modules/ingestion/ingestion-taxii-domain.ts index fc309f0071e2..6658a8c4afa8 100644 --- a/opencti-platform/opencti-graphql/src/modules/ingestion/ingestion-taxii-domain.ts +++ b/opencti-platform/opencti-graphql/src/modules/ingestion/ingestion-taxii-domain.ts @@ -1,7 +1,7 @@ import { type BasicStoreEntityIngestionTaxii, ENTITY_TYPE_INGESTION_TAXII } from './ingestion-types'; import { createEntity, deleteElementById, patchAttribute, updateAttribute } from '../../database/middleware'; import { fullEntitiesList, pageEntitiesConnection, storeLoadById } from '../../database/middleware-loader'; -import { BUS_TOPICS } from '../../config/conf'; +import { BUS_TOPICS, PLATFORM_VERSION } from '../../config/conf'; import { publishUserAction } from '../../listener/UserActionListener'; import { notify } from '../../database/redis'; import { ABSTRACT_INTERNAL_OBJECT } from '../../schema/general'; @@ -10,6 +10,11 @@ import { type EditInput, type IngestionTaxiiAddAutoUserInput, type IngestionTaxi import { addAuthenticationCredentials, removeAuthenticationCredentials, verifyIngestionAuthenticationContent } from './ingestion-common'; import { registerConnectorForIngestion, unregisterConnectorForIngestion } from '../../domain/connector'; import { createOnTheFlyUser } from '../user/user-domain'; +import type { FileHandle } from 'fs/promises'; +import { extractContentFrom } from '../../utils/fileToContent'; +import { isCompatibleVersionWithMinimal } from '../../utils/version'; +import { FunctionalError } from '../../config/errors'; +const MINIMAL_TAXII_FEED_COMPATIBLE_VERSION = '6.9.4'; export const findById = async (context: AuthContext, user: AuthUser, ingestionId: string, removeCredentials = false) => { const taxiiIngestion = await storeLoadById(context, user, ingestionId, ENTITY_TYPE_INGESTION_TAXII); @@ -182,3 +187,42 @@ export const ingestionTaxiiAddAutoUser = async (context: AuthContext, user: Auth return ingestionEditField(context, user, ingestionId, [{ key: 'user_id', value: [onTheFlyCreatedUser.id] }]); }; + +export const taxiiFeedAddInputFromImport = async (file: Promise) => { + const parsedData = await extractContentFrom(file); + + // check platform version compatibility + if (!isCompatibleVersionWithMinimal(parsedData.openCTI_version, MINIMAL_TAXII_FEED_COMPATIBLE_VERSION)) { + throw FunctionalError( + `Invalid version of the platform. Please upgrade your OpenCTI. Minimal version required: ${MINIMAL_TAXII_FEED_COMPATIBLE_VERSION}`, + { reason: parsedData.openCTI_version }, + ); + } + + return parsedData.configuration; +}; + +export const taxiiFeedExport = async (ingestionTaxii: BasicStoreEntityIngestionTaxii) => { + const { + name, + description, + uri, + version, + collection, + authentication_type, + added_after_start, + } = ingestionTaxii; + return JSON.stringify({ + openCTI_version: PLATFORM_VERSION, + type: 'taxiiFeeds', + configuration: { + name, + description, + uri, + version, + collection, + authentication_type, + added_after_start, + }, + }); +}; diff --git a/opencti-platform/opencti-graphql/src/modules/ingestion/ingestion-taxii-resolver.ts b/opencti-platform/opencti-graphql/src/modules/ingestion/ingestion-taxii-resolver.ts index 1fb7c46ed913..9ab9c83b3183 100644 --- a/opencti-platform/opencti-graphql/src/modules/ingestion/ingestion-taxii-resolver.ts +++ b/opencti-platform/opencti-graphql/src/modules/ingestion/ingestion-taxii-resolver.ts @@ -6,6 +6,8 @@ import { ingestionEditField, ingestionTaxiiResetState, ingestionTaxiiAddAutoUser, + taxiiFeedAddInputFromImport, + taxiiFeedExport, } from './ingestion-taxii-domain'; import type { Resolvers } from '../../generated/graphql'; @@ -13,9 +15,11 @@ const ingestionTaxiiResolvers: Resolvers = { Query: { ingestionTaxii: (_, { id }, context) => findById(context, context.user, id, true), ingestionTaxiis: (_, args, context) => findTaxiiIngestionPaginated(context, context.user, args), + taxiiFeedAddInputFromImport: (_, { file }) => taxiiFeedAddInputFromImport(file), }, IngestionTaxii: { user: (ingestionTaxii, _, context) => context.batch.creatorBatchLoader.load(ingestionTaxii.user_id), + toConfigurationExport: (ingestionTaxii) => taxiiFeedExport(ingestionTaxii), }, Mutation: { ingestionTaxiiAdd: (_, { input }, context) => { diff --git a/opencti-platform/opencti-graphql/src/modules/ingestion/ingestion-taxii.graphql b/opencti-platform/opencti-graphql/src/modules/ingestion/ingestion-taxii.graphql index 32bb97e89789..284d9003661a 100644 --- a/opencti-platform/opencti-graphql/src/modules/ingestion/ingestion-taxii.graphql +++ b/opencti-platform/opencti-graphql/src/modules/ingestion/ingestion-taxii.graphql @@ -34,6 +34,7 @@ type IngestionTaxii implements InternalObject & BasicObject { ingestion_running: Boolean last_execution_date: DateTime confidence_to_score: Boolean + toConfigurationExport: String! } enum IngestionTaxiiOrdering { name @@ -52,6 +53,17 @@ type IngestionTaxiiEdge { node: IngestionTaxii! } +type TaxiiFeedAddInputFromImport { + name: String! + description: String! + uri: String! + version: String! + collection: String! + authentication_type: String! + authentication_value: String! + added_after_start: String +} + # Queries type Query { ingestionTaxii(id: String!): IngestionTaxii @auth(for: [INGESTION]) @@ -64,6 +76,9 @@ type Query { includeAuthorities: Boolean search: String ): IngestionTaxiiConnection @auth(for: [INGESTION]) + taxiiFeedAddInputFromImport( + file: Upload! + ): TaxiiFeedAddInputFromImport! @auth(for: [INGESTION]) } # Mutations diff --git a/opencti-platform/opencti-graphql/tests/03-integration/02-resolvers/ingestion-taxii-test.ts b/opencti-platform/opencti-graphql/tests/03-integration/02-resolvers/ingestion-taxii-test.ts index 9f92fbe03d63..96eca3241bef 100644 --- a/opencti-platform/opencti-graphql/tests/03-integration/02-resolvers/ingestion-taxii-test.ts +++ b/opencti-platform/opencti-graphql/tests/03-integration/02-resolvers/ingestion-taxii-test.ts @@ -1,7 +1,7 @@ import { describe, expect, it } from 'vitest'; import gql from 'graphql-tag'; import type { GraphQLFormattedError } from 'graphql/error'; -import { queryAsAdminWithSuccess } from '../../utils/testQueryHelper'; +import { createUploadFromTestDataFile, queryAsAdminWithSuccess } from '../../utils/testQueryHelper'; import { IngestionAuthType, TaxiiVersion } from '../../../src/generated/graphql'; import { ADMIN_USER, adminQuery, queryAsAdmin, testContext } from '../../utils/testQuery'; import { now } from '../../../src/utils/format'; @@ -32,6 +32,41 @@ const READ_USER_QUERY = gql` describe('TAXII ingestion resolver standard behavior', () => { let createdTaxiiIngesterId: string = ''; + it('should send data when getting a json file', async () => { + const upload = await createUploadFromTestDataFile('taxiiFeed/test-taxii-feed.json', 'test-taxii-feed.json', 'application/json'); + const TEST_MUTATION = gql` + query TaxiiFeedAddInputFromImport($file: Upload!) { + taxiiFeedAddInputFromImport(file: $file){ + name + description + uri + version + collection + authentication_type + authentication_value + } + } + `; + const queryResult = await queryAsAdmin({ + query: TEST_MUTATION, + variables: { + file: upload, + }, + }); + expect(queryResult.data?.taxiiFeedAddInputFromImport).toBeDefined(); + expect(queryResult.data?.taxiiFeedAddInputFromImport).toMatchObject({ + name: 'taxiiFeedsAuto', + description: 'Nice description', + uri: 'https://pastebin.com/raw/7CC8nHB0', + version: 'v21', + collection: 'taxii_collection', + authentication_type: '', + authentication_value: '', + }); + }); + + + it('should create a TAXII ingester with existing user', async () => { const INGESTER_TO_CREATE = { input: { @@ -60,6 +95,29 @@ describe('TAXII ingestion resolver standard behavior', () => { createdTaxiiIngesterId = ingesterQueryResult.data?.ingestionTaxiiAdd.id; }); + it('should generate correct export configuration', async () => { + const QUERY_TAXII_FEED = gql(` + query QueryTaxiiFeed($id: String!) { + ingestionTaxii(id: $id) { + name + toConfigurationExport + } + } + `); + const { data } = await queryAsAdmin({ + query: QUERY_TAXII_FEED, + variables: { id: createdTaxiiIngesterId }, + }); + const taxiiFeedIngestion = JSON.parse(data?.ingestionTaxii.toConfigurationExport); + expect(taxiiFeedIngestion.configuration).toMatchObject({ + name: 'Taxii ingester for integration test', + uri: 'http://taxiserver.invalid', + version: TaxiiVersion.V21, + collection: 'TaxiCollection', + authentication_type: IngestionAuthType.Basic, + }); + }); + it('should create a TAXII feed with automatic user', async () => { const INGESTER_TO_CREATE = { input: { @@ -121,7 +179,6 @@ describe('TAXII ingestion resolver standard behavior', () => { expect(ingesterQueryResult.data?.ingestionTaxiiFieldPatch.authentication_value).toEqual('username:undefined'); }); - it('should add auto user and update Taxii feed ingester with it', async () => { const TAXII_FEED_AUTO_USER_UPDATE = { id: createdTaxiiIngesterId, diff --git a/opencti-platform/opencti-graphql/tests/data/taxiiFeed/test-taxii-feed.json b/opencti-platform/opencti-graphql/tests/data/taxiiFeed/test-taxii-feed.json new file mode 100644 index 000000000000..e7c728a47a45 --- /dev/null +++ b/opencti-platform/opencti-graphql/tests/data/taxiiFeed/test-taxii-feed.json @@ -0,0 +1,9 @@ +{"openCTI_version":"6.9.4","type":"taxiiFeeds","configuration": { + "name": "taxiiFeedsAuto", + "description": "Nice description", + "uri": "https://pastebin.com/raw/7CC8nHB0", + "version": "v21", + "collection": "taxii_collection", + "authentication_type": "", + "authentication_value": "" +}} From adb3b03ac44994bfee75f9adfc1b4ff4a17d1a6d Mon Sep 17 00:00:00 2001 From: antoinemzs Date: Mon, 5 Jan 2026 14:58:50 +0100 Subject: [PATCH 028/126] [frontend] fix ISO format for security coverage time period Signed-off-by: Antoine MAZEAS --- .../opencti-front/src/components/fields/PeriodicityField.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/opencti-platform/opencti-front/src/components/fields/PeriodicityField.tsx b/opencti-platform/opencti-front/src/components/fields/PeriodicityField.tsx index 774fa40542d8..90e863040c50 100644 --- a/opencti-platform/opencti-front/src/components/fields/PeriodicityField.tsx +++ b/opencti-platform/opencti-front/src/components/fields/PeriodicityField.tsx @@ -66,8 +66,10 @@ const PeriodicityField: React.FC = ({ durationString = `P${value}W`; } else if (unit === 'D') { durationString = `P${value}D`; + } else if (unit === 'M') { + durationString = `P${value}M`; } else if (unit === 'H') { - durationString = `P${value}H`; + durationString = `PT${value}H`; } else { durationString = `PT${value}${unit}`; } From 6ba0a605b8e50bca1ae198730b03c5502c946b33 Mon Sep 17 00:00:00 2001 From: Xavier Fournet <461943+xfournet@users.noreply.github.com> Date: Mon, 5 Jan 2026 21:04:00 +0100 Subject: [PATCH 029/126] [backend] Cache decoded EE license (#13823) --- .../src/http/httpChatbotProxy.ts | 2 +- .../settings/{licensing.js => licensing.ts} | 42 ++++++++++++------- 2 files changed, 29 insertions(+), 15 deletions(-) rename opencti-platform/opencti-graphql/src/modules/settings/{licensing.js => licensing.ts} (79%) diff --git a/opencti-platform/opencti-graphql/src/http/httpChatbotProxy.ts b/opencti-platform/opencti-graphql/src/http/httpChatbotProxy.ts index 1b2f59b40232..e017d3cfedcf 100644 --- a/opencti-platform/opencti-graphql/src/http/httpChatbotProxy.ts +++ b/opencti-platform/opencti-graphql/src/http/httpChatbotProxy.ts @@ -25,7 +25,7 @@ export const getChatbotProxy = async (req: Express.Request, res: Express.Respons const isChatbotCGUAccepted: boolean = settings.filigran_chatbot_ai_cgu_status === CguStatus.Enabled; const license_pem = getEnterpriseEditionActivePem(settings.enterprise_license); const licenseInfo = getEnterpriseEditionInfo(settings); - const isLicenseValidated = licenseInfo.license_validated; + const isLicenseValidated = license_pem !== undefined && licenseInfo.license_validated; if (!isChatbotCGUAccepted || !isLicenseValidated) { logApp.error('Error in chatbot proxy', { cguStatus: settings.filigran_chatbot_ai_cgu_status, isLicenseValidated, chatbotUrl: XTM_ONE_CHATBOT_URL }); diff --git a/opencti-platform/opencti-graphql/src/modules/settings/licensing.js b/opencti-platform/opencti-graphql/src/modules/settings/licensing.ts similarity index 79% rename from opencti-platform/opencti-graphql/src/modules/settings/licensing.js rename to opencti-platform/opencti-graphql/src/modules/settings/licensing.ts index cabf64c753cb..04b77c3f2cf6 100644 --- a/opencti-platform/opencti-graphql/src/modules/settings/licensing.js +++ b/opencti-platform/opencti-graphql/src/modules/settings/licensing.ts @@ -18,6 +18,8 @@ import { isNotEmptyField } from '../../database/utils'; import { now, utcDate } from '../../utils/format'; import { OPENCTI_CA } from '../../enterprise-edition/opencti_ca'; import conf, { PLATFORM_VERSION } from '../../config/conf'; +import type { BasicStoreSettings } from '../../types/settings'; +import type { PlatformEe } from '../../generated/graphql'; const GLOBAL_LICENSE_OPTION = 'global'; export const LICENSE_OPTION_TRIAL = 'trial'; @@ -34,7 +36,7 @@ export const LICENSE_LEGACY_TYPE = '6.2.9.4.4.10'; export const LICENSE_LEGACY_PRODUCT = '6.2.9.4.4.20'; export const LICENSE_LEGACY_CREATOR = '6.2.9.4.4.30'; -const getExtensionValue = (clientCrt, standardOid, legacyOid) => { +const getExtensionValue = (clientCrt: forge.pki.Certificate, standardOid: string, legacyOid: string) => { const extStandard = clientCrt.extensions.find((ext) => ext.id === standardOid); if (extStandard) { return extStandard.value; @@ -42,16 +44,9 @@ const getExtensionValue = (clientCrt, standardOid, legacyOid) => { return clientCrt.extensions.find((ext) => ext.id === legacyOid)?.value; }; -export const getEnterpriseEditionActivePem = (rawPem) => { - const pemFromConfig = conf.get('app:enterprise_edition_license'); - return isNotEmptyField(pemFromConfig) ? pemFromConfig : rawPem; -}; - -export const getEnterpriseEditionInfoFromPem = (platformInstanceId, rawPem) => { - const pemFromConfig = conf.get('app:enterprise_edition_license'); - const pem = getEnterpriseEditionActivePem(rawPem); - const license_enterprise = isNotEmptyField(pem); - if (isNotEmptyField(pem)) { +const decodeLicense = (platformInstanceId: string, licenseByConfiguration: boolean, pem: string | undefined): PlatformEe => { + const license_enterprise = pem !== undefined && isNotEmptyField(pem); + if (license_enterprise) { try { const clientCrt = forge.pki.certificateFromPem(pem); const license_valid_cert = OPENCTI_CA.verify(clientCrt); @@ -82,7 +77,7 @@ export const getEnterpriseEditionInfoFromPem = (platformInstanceId, rawPem) => { } return { license_enterprise, // If EE activated - license_by_configuration: isNotEmptyField(pemFromConfig), + license_by_configuration: licenseByConfiguration, license_validated, // If EE license is ok (identifier, dates, ...) license_valid_cert, license_customer, @@ -105,7 +100,7 @@ export const getEnterpriseEditionInfoFromPem = (platformInstanceId, rawPem) => { return { license_enterprise, license_validated: false, - license_by_configuration: isNotEmptyField(pemFromConfig), + license_by_configuration: licenseByConfiguration, license_valid_cert: false, license_extra_expiration: false, license_extra_expiration_days: 0, @@ -122,6 +117,25 @@ export const getEnterpriseEditionInfoFromPem = (platformInstanceId, rawPem) => { }; }; -export const getEnterpriseEditionInfo = (settings) => { +export const getEnterpriseEditionActivePem = (rawPem: string | undefined) => { + const pemFromConfig: string | undefined = conf.get('app:enterprise_edition_license'); + return isNotEmptyField(pemFromConfig) ? pemFromConfig : rawPem; +}; + +let cachedLicence: PlatformEe | undefined = undefined; +let cachedPem: string | undefined = undefined; + +export const getEnterpriseEditionInfoFromPem = (platformInstanceId: string, rawPem: string | undefined) => { + const pem = getEnterpriseEditionActivePem(rawPem); + if (cachedLicence === undefined || cachedPem !== pem) { + const pemFromConfig = conf.get('app:enterprise_edition_license'); + const licenseByConfiguration = isNotEmptyField(pemFromConfig); + cachedLicence = decodeLicense(platformInstanceId, licenseByConfiguration, pem); + cachedPem = pem; + } + return cachedLicence; +}; + +export const getEnterpriseEditionInfo = (settings: BasicStoreSettings) => { return getEnterpriseEditionInfoFromPem(settings.internal_id, settings.enterprise_license); }; From f83d62ead816dfcafc722feadfc8825f49235b2a Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 5 Jan 2026 21:07:47 +0100 Subject: [PATCH 030/126] [deps] Update dependency @escape.tech/graphql-armor to v3.2.0 (#13864) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- opencti-platform/opencti-graphql/package.json | 2 +- opencti-platform/opencti-graphql/yarn.lock | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/opencti-platform/opencti-graphql/package.json b/opencti-platform/opencti-graphql/package.json index a2cf0c3e7ae4..dd228b9ab558 100644 --- a/opencti-platform/opencti-graphql/package.json +++ b/opencti-platform/opencti-graphql/package.json @@ -57,7 +57,7 @@ "@aws-sdk/lib-storage": "3.955.0", "@datadog/pprof": "5.13.1", "@elastic/elasticsearch": "8.19.1", - "@escape.tech/graphql-armor": "3.1.7", + "@escape.tech/graphql-armor": "3.2.0", "@graphql-tools/merge": "9.1.6", "@graphql-tools/schema": "10.0.30", "@graphql-tools/utils": "10.11.0", diff --git a/opencti-platform/opencti-graphql/yarn.lock b/opencti-platform/opencti-graphql/yarn.lock index 9ab82efedd0d..968c196ba3f1 100644 --- a/opencti-platform/opencti-graphql/yarn.lock +++ b/opencti-platform/opencti-graphql/yarn.lock @@ -1803,9 +1803,9 @@ __metadata: languageName: node linkType: hard -"@escape.tech/graphql-armor@npm:3.1.7": - version: 3.1.7 - resolution: "@escape.tech/graphql-armor@npm:3.1.7" +"@escape.tech/graphql-armor@npm:3.2.0": + version: 3.2.0 + resolution: "@escape.tech/graphql-armor@npm:3.2.0" dependencies: "@escape.tech/graphql-armor-block-field-suggestions": "npm:3.0.1" "@escape.tech/graphql-armor-cost-limit": "npm:2.4.3" @@ -1815,7 +1815,7 @@ __metadata: "@escape.tech/graphql-armor-max-tokens": "npm:2.5.1" graphql: "npm:^16.10.0" peerDependencies: - "@apollo/server": ^4.0.0 + "@apollo/server": ^4.0.0 || ^5.0.0 "@envelop/core": ^5.0.0 "@escape.tech/graphql-armor-types": 0.7.0 peerDependenciesMeta: @@ -1825,7 +1825,7 @@ __metadata: optional: true "@escape.tech/graphql-armor-types": optional: true - checksum: 10c0/0795987bbfc1d79e22c1701fd53ef8a6b3da3b0bb03b1881f738040173a0c0e676d7a907076ca2a5827b1f1bf8e541b34f6c18d2600dac38cf9c7c6ea97aa9ac + checksum: 10c0/d33208dcc4ab1e49d611cd93f707a5833abe6c385389f5499937aa40c15cfd7fe206baec4a6f982d4c3cf955daa1281a99e7d3bfc570110a06a71ac5411f3c80 languageName: node linkType: hard @@ -10920,7 +10920,7 @@ __metadata: "@aws-sdk/lib-storage": "npm:3.955.0" "@datadog/pprof": "npm:5.13.1" "@elastic/elasticsearch": "npm:8.19.1" - "@escape.tech/graphql-armor": "npm:3.1.7" + "@escape.tech/graphql-armor": "npm:3.2.0" "@eslint/js": "npm:9.39.2" "@graphql-codegen/cli": "npm:6.1.0" "@graphql-codegen/introspection": "npm:5.0.0" From 9baf2f5fe3b4a27089a7e9b851d34d606c74ae72 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 5 Jan 2026 21:10:39 +0100 Subject: [PATCH 031/126] [deps] Update dependency globals to v17 (#13877) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- opencti-platform/opencti-front/package.json | 2 +- opencti-platform/opencti-front/yarn.lock | 10 +++++----- opencti-platform/opencti-graphql/package.json | 2 +- opencti-platform/opencti-graphql/yarn.lock | 10 +++++----- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/opencti-platform/opencti-front/package.json b/opencti-platform/opencti-front/package.json index 0356d1985da9..f2a17d5748b1 100644 --- a/opencti-platform/opencti-front/package.json +++ b/opencti-platform/opencti-front/package.json @@ -163,7 +163,7 @@ "eslint-plugin-import-newlines": "1.4.0", "eslint-plugin-react": "7.37.5", "express": "5.2.1", - "globals": "16.5.0", + "globals": "17.0.0", "http-proxy-middleware": "3.0.5", "i18n-auto-translation": "2.2.3", "jsdom": "27.3.0", diff --git a/opencti-platform/opencti-front/yarn.lock b/opencti-platform/opencti-front/yarn.lock index 23f32dea1fd4..04d40e2ff361 100644 --- a/opencti-platform/opencti-front/yarn.lock +++ b/opencti-platform/opencti-front/yarn.lock @@ -9294,10 +9294,10 @@ __metadata: languageName: node linkType: hard -"globals@npm:16.5.0": - version: 16.5.0 - resolution: "globals@npm:16.5.0" - checksum: 10c0/615241dae7851c8012f5aa0223005b1ed6607713d6813de0741768bd4ddc39353117648f1a7086b4b0fa45eae733f1c0a0fe369aa4e543bb63f8de8990178ea9 +"globals@npm:17.0.0": + version: 17.0.0 + resolution: "globals@npm:17.0.0" + checksum: 10c0/e3c169fdcb0fc6755707b697afb367bea483eb29992cfc0de1637382eb893146e17f8f96db6d7453e3696b478a7863ae2000e6c71cd2f4061410285106d3847a languageName: node linkType: hard @@ -12519,7 +12519,7 @@ __metadata: formik: "npm:2.4.9" formik-mui: "npm:5.0.0-alpha.0" formik-mui-lab: "npm:1.0.0" - globals: "npm:16.5.0" + globals: "npm:17.0.0" graphiql: "npm:4.1.2" graphql: "npm:16.12.0" graphql-ws: "npm:5.16.2" diff --git a/opencti-platform/opencti-graphql/package.json b/opencti-platform/opencti-graphql/package.json index dd228b9ab558..144409f2aacd 100644 --- a/opencti-platform/opencti-graphql/package.json +++ b/opencti-platform/opencti-graphql/package.json @@ -201,7 +201,7 @@ "eslint-import-resolver-typescript": "4.4.4", "eslint-plugin-import": "2.32.0", "eslint-plugin-import-newlines": "1.4.0", - "globals": "16.5.0", + "globals": "17.0.0", "graphql-tag": "2.12.6", "typescript": "5.9.3", "typescript-eslint": "8.50.1", diff --git a/opencti-platform/opencti-graphql/yarn.lock b/opencti-platform/opencti-graphql/yarn.lock index 968c196ba3f1..04b9a3592389 100644 --- a/opencti-platform/opencti-graphql/yarn.lock +++ b/opencti-platform/opencti-graphql/yarn.lock @@ -8591,10 +8591,10 @@ __metadata: languageName: node linkType: hard -"globals@npm:16.5.0": - version: 16.5.0 - resolution: "globals@npm:16.5.0" - checksum: 10c0/615241dae7851c8012f5aa0223005b1ed6607713d6813de0741768bd4ddc39353117648f1a7086b4b0fa45eae733f1c0a0fe369aa4e543bb63f8de8990178ea9 +"globals@npm:17.0.0": + version: 17.0.0 + resolution: "globals@npm:17.0.0" + checksum: 10c0/e3c169fdcb0fc6755707b697afb367bea483eb29992cfc0de1637382eb893146e17f8f96db6d7453e3696b478a7863ae2000e6c71cd2f4061410285106d3847a languageName: node linkType: hard @@ -11004,7 +11004,7 @@ __metadata: fast-json-patch: "npm:3.1.1" file-type: "npm:21.2.0" github-api: "npm:3.4.0" - globals: "npm:16.5.0" + globals: "npm:17.0.0" graphql: "npm:16.12.0" graphql-constraint-directive: "npm:6.0.0" graphql-no-alias: "npm:3.0.3" From 91409fe3041349d9b7d91d8f057703eba29a5bde Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 5 Jan 2026 21:42:35 +0100 Subject: [PATCH 032/126] [deps] Update devDependencies (non-major) (#13875) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- opencti-platform/opencti-front/package.json | 4 +- opencti-platform/opencti-front/yarn.lock | 207 +++++++++--------- opencti-platform/opencti-graphql/package.json | 2 +- opencti-platform/opencti-graphql/yarn.lock | 160 +++++++------- 4 files changed, 188 insertions(+), 185 deletions(-) diff --git a/opencti-platform/opencti-front/package.json b/opencti-platform/opencti-front/package.json index f2a17d5748b1..4400adbd85e7 100644 --- a/opencti-platform/opencti-front/package.json +++ b/opencti-platform/opencti-front/package.json @@ -166,13 +166,13 @@ "globals": "17.0.0", "http-proxy-middleware": "3.0.5", "i18n-auto-translation": "2.2.3", - "jsdom": "27.3.0", + "jsdom": "27.4.0", "license-checker-rseidelsohn": "4.4.2", "monocart-reporter": "2.9.23", "relay-compiler": "20.1.1", "relay-test-utils": "20.1.1", "typescript": "5.9.3", - "typescript-eslint": "8.50.1", + "typescript-eslint": "8.51.0", "vite": "7.3.0", "vite-plugin-relay": "2.1.0", "vite-plugin-static-copy": "3.1.4", diff --git a/opencti-platform/opencti-front/yarn.lock b/opencti-platform/opencti-front/yarn.lock index 04d40e2ff361..37eb6f4c06c5 100644 --- a/opencti-platform/opencti-front/yarn.lock +++ b/opencti-platform/opencti-front/yarn.lock @@ -1845,6 +1845,18 @@ __metadata: languageName: node linkType: hard +"@exodus/bytes@npm:^1.6.0": + version: 1.7.0 + resolution: "@exodus/bytes@npm:1.7.0" + peerDependencies: + "@exodus/crypto": ^1.0.0-rc.4 + peerDependenciesMeta: + "@exodus/crypto": + optional: true + checksum: 10c0/f919d7d26c44a31ae71a280baf821b38826bdca97ac6c6dbe7d02bf6952104ef2106534cb5566c17f7a1dc1ae627a78f2fabce3a09d6bbc65a9b1bdaeaeebfc5 + languageName: node + linkType: hard + "@faker-js/faker@npm:10.1.0": version: 10.1.0 resolution: "@faker-js/faker@npm:10.1.0" @@ -5102,94 +5114,94 @@ __metadata: languageName: node linkType: hard -"@typescript-eslint/eslint-plugin@npm:8.50.1": - version: 8.50.1 - resolution: "@typescript-eslint/eslint-plugin@npm:8.50.1" +"@typescript-eslint/eslint-plugin@npm:8.51.0": + version: 8.51.0 + resolution: "@typescript-eslint/eslint-plugin@npm:8.51.0" dependencies: "@eslint-community/regexpp": "npm:^4.10.0" - "@typescript-eslint/scope-manager": "npm:8.50.1" - "@typescript-eslint/type-utils": "npm:8.50.1" - "@typescript-eslint/utils": "npm:8.50.1" - "@typescript-eslint/visitor-keys": "npm:8.50.1" + "@typescript-eslint/scope-manager": "npm:8.51.0" + "@typescript-eslint/type-utils": "npm:8.51.0" + "@typescript-eslint/utils": "npm:8.51.0" + "@typescript-eslint/visitor-keys": "npm:8.51.0" ignore: "npm:^7.0.0" natural-compare: "npm:^1.4.0" - ts-api-utils: "npm:^2.1.0" + ts-api-utils: "npm:^2.2.0" peerDependencies: - "@typescript-eslint/parser": ^8.50.1 + "@typescript-eslint/parser": ^8.51.0 eslint: ^8.57.0 || ^9.0.0 typescript: ">=4.8.4 <6.0.0" - checksum: 10c0/cae56cec414dc5d8347f1ff9fc01ec7b82c7988bcca9597569564b69e1715594e044487805a72ce7a9b4e6e81c3632db92c3d4b6b991874dafa402e1fcb508d5 + checksum: 10c0/3140e66a0f722338d56bf3de2b7cbb9a74a812d8da90fc61975ea029f6a401252c0824063d4c4baab9827de6f0209b34f4bbdc46e3f5fefd8fa2ff4a3980406f languageName: node linkType: hard -"@typescript-eslint/parser@npm:8.50.1": - version: 8.50.1 - resolution: "@typescript-eslint/parser@npm:8.50.1" +"@typescript-eslint/parser@npm:8.51.0": + version: 8.51.0 + resolution: "@typescript-eslint/parser@npm:8.51.0" dependencies: - "@typescript-eslint/scope-manager": "npm:8.50.1" - "@typescript-eslint/types": "npm:8.50.1" - "@typescript-eslint/typescript-estree": "npm:8.50.1" - "@typescript-eslint/visitor-keys": "npm:8.50.1" + "@typescript-eslint/scope-manager": "npm:8.51.0" + "@typescript-eslint/types": "npm:8.51.0" + "@typescript-eslint/typescript-estree": "npm:8.51.0" + "@typescript-eslint/visitor-keys": "npm:8.51.0" debug: "npm:^4.3.4" peerDependencies: eslint: ^8.57.0 || ^9.0.0 typescript: ">=4.8.4 <6.0.0" - checksum: 10c0/60a2591745650b35cd8d425bb1959ef40d598245481bdfdc2654ed1f7878364c2c442ba70ca7105b650d0df2b6109727dd43214be76045667de0d32a221f3955 + checksum: 10c0/b6aab1d82cc98a77aaae7637bf2934980104799793b3fd5b893065d930fe9b23cd6c2059d6f73fb454ea08f9e956e84fa940310d8435092a14be645a42062d94 languageName: node linkType: hard -"@typescript-eslint/project-service@npm:8.50.1": - version: 8.50.1 - resolution: "@typescript-eslint/project-service@npm:8.50.1" +"@typescript-eslint/project-service@npm:8.51.0": + version: 8.51.0 + resolution: "@typescript-eslint/project-service@npm:8.51.0" dependencies: - "@typescript-eslint/tsconfig-utils": "npm:^8.50.1" - "@typescript-eslint/types": "npm:^8.50.1" + "@typescript-eslint/tsconfig-utils": "npm:^8.51.0" + "@typescript-eslint/types": "npm:^8.51.0" debug: "npm:^4.3.4" peerDependencies: typescript: ">=4.8.4 <6.0.0" - checksum: 10c0/50fee0882188c2d704deddfb39f5283618adf7e5f72418143e9f69a8f3771233d55a3e0fc2673fa09c62e230ec53e500f95c0f1ed331ffac5f6a7f8e7b7a2e8c + checksum: 10c0/c6e6efbf79e126261e1742990b0872a34bbbe9931d99f0aabd12cb70a65a361e02d626db4b632dabee2b2c26b7e5b48344fc5a796c56438ae0788535e2bbe092 languageName: node linkType: hard -"@typescript-eslint/scope-manager@npm:8.50.1": - version: 8.50.1 - resolution: "@typescript-eslint/scope-manager@npm:8.50.1" +"@typescript-eslint/scope-manager@npm:8.51.0": + version: 8.51.0 + resolution: "@typescript-eslint/scope-manager@npm:8.51.0" dependencies: - "@typescript-eslint/types": "npm:8.50.1" - "@typescript-eslint/visitor-keys": "npm:8.50.1" - checksum: 10c0/ef0df092745f5d4e3684a3d770dc47735ab3195456de4ac5825931aeed1857a7e8d7cec14cc9c78c5ed049b3d83b0f8ac43b9463c5032ba548558a06bebb5539 + "@typescript-eslint/types": "npm:8.51.0" + "@typescript-eslint/visitor-keys": "npm:8.51.0" + checksum: 10c0/dd1e75fc13e6b1119954612d9e8ad3f2d91bc37dcde85fd00e959171aaf6c716c4c265c90c5accf24b5831bd3f48510b0775e5583085b8fa2ad5c37c8980ae1a languageName: node linkType: hard -"@typescript-eslint/tsconfig-utils@npm:8.50.1, @typescript-eslint/tsconfig-utils@npm:^8.50.1": - version: 8.50.1 - resolution: "@typescript-eslint/tsconfig-utils@npm:8.50.1" +"@typescript-eslint/tsconfig-utils@npm:8.51.0, @typescript-eslint/tsconfig-utils@npm:^8.51.0": + version: 8.51.0 + resolution: "@typescript-eslint/tsconfig-utils@npm:8.51.0" peerDependencies: typescript: ">=4.8.4 <6.0.0" - checksum: 10c0/6a1ffb0cd2d9e820ed0c7555a43ebb21438ca80f26c9632e0753bd09e764d9b8e9a352215e4ae60f6d570ab1e77751c9460a00515648b9a2f13f56c56a068a94 + checksum: 10c0/46cab9a5342b4a8f8a1d05aaee4236c5262a540ad0bca1f0e8dad5d63ed1e634b88ce0c82a612976dab09861e21086fc995a368df0435ac43fb960e0b9e5cde2 languageName: node linkType: hard -"@typescript-eslint/type-utils@npm:8.50.1": - version: 8.50.1 - resolution: "@typescript-eslint/type-utils@npm:8.50.1" +"@typescript-eslint/type-utils@npm:8.51.0": + version: 8.51.0 + resolution: "@typescript-eslint/type-utils@npm:8.51.0" dependencies: - "@typescript-eslint/types": "npm:8.50.1" - "@typescript-eslint/typescript-estree": "npm:8.50.1" - "@typescript-eslint/utils": "npm:8.50.1" + "@typescript-eslint/types": "npm:8.51.0" + "@typescript-eslint/typescript-estree": "npm:8.51.0" + "@typescript-eslint/utils": "npm:8.51.0" debug: "npm:^4.3.4" - ts-api-utils: "npm:^2.1.0" + ts-api-utils: "npm:^2.2.0" peerDependencies: eslint: ^8.57.0 || ^9.0.0 typescript: ">=4.8.4 <6.0.0" - checksum: 10c0/e4bfd3dd2459e936f7b6d9ee4b60fdedbf4b8f6b3d832e11d3cb1b58c1ce6da098880daafe3b65b2d33e2f79aba0e75c4b6eafdfa2a66c6e00a9ad3132b8e90d + checksum: 10c0/7c17214e54bc3a4fe4551d9251ffbac52e84ca46eeae840c0f981994b7cbcc837ef32a2b6d510b02d958a8f568df355e724d9c6938a206716271a1b0c00801b7 languageName: node linkType: hard -"@typescript-eslint/types@npm:8.50.1, @typescript-eslint/types@npm:^8.50.1": - version: 8.50.1 - resolution: "@typescript-eslint/types@npm:8.50.1" - checksum: 10c0/04e3c296d81293e370578762be6736fccd1581476f9d534938d42fe93968571fcaf26d7d8c3de52ed63a5af2c0b2da922b8ee2011fa5fb9fb401fc7f0916367a +"@typescript-eslint/types@npm:8.51.0, @typescript-eslint/types@npm:^8.51.0": + version: 8.51.0 + resolution: "@typescript-eslint/types@npm:8.51.0" + checksum: 10c0/eb3473d0bb71eb886438f35887b620ffadae7853b281752a40c73158aee644d136adeb82549be7d7c30f346fe888b2e979dff7e30e67b35377e8281018034529 languageName: node linkType: hard @@ -5200,47 +5212,47 @@ __metadata: languageName: node linkType: hard -"@typescript-eslint/typescript-estree@npm:8.50.1": - version: 8.50.1 - resolution: "@typescript-eslint/typescript-estree@npm:8.50.1" +"@typescript-eslint/typescript-estree@npm:8.51.0": + version: 8.51.0 + resolution: "@typescript-eslint/typescript-estree@npm:8.51.0" dependencies: - "@typescript-eslint/project-service": "npm:8.50.1" - "@typescript-eslint/tsconfig-utils": "npm:8.50.1" - "@typescript-eslint/types": "npm:8.50.1" - "@typescript-eslint/visitor-keys": "npm:8.50.1" + "@typescript-eslint/project-service": "npm:8.51.0" + "@typescript-eslint/tsconfig-utils": "npm:8.51.0" + "@typescript-eslint/types": "npm:8.51.0" + "@typescript-eslint/visitor-keys": "npm:8.51.0" debug: "npm:^4.3.4" minimatch: "npm:^9.0.4" semver: "npm:^7.6.0" tinyglobby: "npm:^0.2.15" - ts-api-utils: "npm:^2.1.0" + ts-api-utils: "npm:^2.2.0" peerDependencies: typescript: ">=4.8.4 <6.0.0" - checksum: 10c0/697b53fd3355619271a7bf543c5880731670b96567da63f554a3c3cd4d746feb8153628ec912c8a2df95e3123472e9a77df43c32fad72946b69ace89c2cf8b7e + checksum: 10c0/5386acc67298a6757681b6264c29a6b9304be7a188f11498bbaa82bb0a3095fd79394ad80d6520bdff3fa3093199f9a438246604ee3281b76f7ed574b7516854 languageName: node linkType: hard -"@typescript-eslint/utils@npm:8.50.1": - version: 8.50.1 - resolution: "@typescript-eslint/utils@npm:8.50.1" +"@typescript-eslint/utils@npm:8.51.0": + version: 8.51.0 + resolution: "@typescript-eslint/utils@npm:8.51.0" dependencies: "@eslint-community/eslint-utils": "npm:^4.7.0" - "@typescript-eslint/scope-manager": "npm:8.50.1" - "@typescript-eslint/types": "npm:8.50.1" - "@typescript-eslint/typescript-estree": "npm:8.50.1" + "@typescript-eslint/scope-manager": "npm:8.51.0" + "@typescript-eslint/types": "npm:8.51.0" + "@typescript-eslint/typescript-estree": "npm:8.51.0" peerDependencies: eslint: ^8.57.0 || ^9.0.0 typescript: ">=4.8.4 <6.0.0" - checksum: 10c0/66b19a9c8981b0b601af3a477fdcabdd110b0805591f28eefa11b32bbb88518d80b928e49eaa4c40d42ea8d71605bf5cd2ee5e39802022d1daec2800f1b198df + checksum: 10c0/ffb8237cfb33a1998ae2812b136d42fb65e7497f185d46097d19e43112e41b3ef59f901ba679c2e5372ad3007026f6e5add3a3de0f2e75ce6896918713fa38a8 languageName: node linkType: hard -"@typescript-eslint/visitor-keys@npm:8.50.1": - version: 8.50.1 - resolution: "@typescript-eslint/visitor-keys@npm:8.50.1" +"@typescript-eslint/visitor-keys@npm:8.51.0": + version: 8.51.0 + resolution: "@typescript-eslint/visitor-keys@npm:8.51.0" dependencies: - "@typescript-eslint/types": "npm:8.50.1" + "@typescript-eslint/types": "npm:8.51.0" eslint-visitor-keys: "npm:^4.2.1" - checksum: 10c0/b23839d04b2e5e7964a4006317d75cdc3cf76e56f4c5fde1e0bcd23f3bb78dca910e3dcadca80606f76a09ff9e44b3363ee1e1d6394e3f7479da74a641a8870f + checksum: 10c0/fce5603961cf336e71095f7599157de65e3182f61cbd6cab33a43551ee91485b4e9bf6cacc1b275cf6f3503b92f8568fe2267a45c82e60e386ee73db727a26ca languageName: node linkType: hard @@ -9597,12 +9609,12 @@ __metadata: languageName: node linkType: hard -"html-encoding-sniffer@npm:^4.0.0": - version: 4.0.0 - resolution: "html-encoding-sniffer@npm:4.0.0" +"html-encoding-sniffer@npm:^6.0.0": + version: 6.0.0 + resolution: "html-encoding-sniffer@npm:6.0.0" dependencies: - whatwg-encoding: "npm:^3.1.1" - checksum: 10c0/523398055dc61ac9b34718a719cb4aa691e4166f29187e211e1607de63dc25ac7af52ca7c9aead0c4b3c0415ffecb17326396e1202e2e86ff4bca4c0ee4c6140 + "@exodus/bytes": "npm:^1.6.0" + checksum: 10c0/66dc3f6f5539cc3beb814fcbfae7eacf4ec38cf824d6e1425b72039b51a40f4456bd8541ba66f4f4fe09cdf885ab5cd5bae6ec6339d6895a930b2fdb83c53025 languageName: node linkType: hard @@ -9812,7 +9824,7 @@ __metadata: languageName: node linkType: hard -"iconv-lite@npm:0.6.3, iconv-lite@npm:^0.6.2": +"iconv-lite@npm:^0.6.2": version: 0.6.3 resolution: "iconv-lite@npm:0.6.3" dependencies: @@ -10557,16 +10569,17 @@ __metadata: languageName: node linkType: hard -"jsdom@npm:27.3.0": - version: 27.3.0 - resolution: "jsdom@npm:27.3.0" +"jsdom@npm:27.4.0": + version: 27.4.0 + resolution: "jsdom@npm:27.4.0" dependencies: "@acemir/cssom": "npm:^0.9.28" "@asamuzakjp/dom-selector": "npm:^6.7.6" + "@exodus/bytes": "npm:^1.6.0" cssstyle: "npm:^5.3.4" data-urls: "npm:^6.0.0" decimal.js: "npm:^10.6.0" - html-encoding-sniffer: "npm:^4.0.0" + html-encoding-sniffer: "npm:^6.0.0" http-proxy-agent: "npm:^7.0.2" https-proxy-agent: "npm:^7.0.6" is-potential-custom-element-name: "npm:^1.0.1" @@ -10576,7 +10589,6 @@ __metadata: tough-cookie: "npm:^6.0.0" w3c-xmlserializer: "npm:^5.0.0" webidl-conversions: "npm:^8.0.0" - whatwg-encoding: "npm:^3.1.1" whatwg-mimetype: "npm:^4.0.0" whatwg-url: "npm:^15.1.0" ws: "npm:^8.18.3" @@ -10586,7 +10598,7 @@ __metadata: peerDependenciesMeta: canvas: optional: true - checksum: 10c0/b022ed8f6ce175afd97fbd42eb65b03b2be3b23df86cf87f018b6d2e757682fe8348e719a14780d6fa3fe8a65e531ba71b38db80f312818a32b77f01e31f267e + checksum: 10c0/291bb71a611dbaed81ce516587b71a5ffd9d43337d65bbd0731e7924cd7018f5871cf66614facadfd0dffec2b23a0fc57b2ee36b5a39e20f0f569e2949b3418c languageName: node linkType: hard @@ -12532,7 +12544,7 @@ __metadata: is-svg: "npm:6.1.0" js-base64: "npm:3.7.8" js-file-download: "npm:0.4.12" - jsdom: "npm:27.3.0" + jsdom: "npm:27.4.0" json5: "npm:2.2.3" leaflet: "npm:1.9.4" license-checker-rseidelsohn: "npm:4.4.2" @@ -12583,7 +12595,7 @@ __metadata: rxjs: "npm:7.8.2" three-spritetext: "npm:1.10.0" typescript: "npm:5.9.3" - typescript-eslint: "npm:8.50.1" + typescript-eslint: "npm:8.51.0" use-analytics: "npm:1.1.0" uuid: "npm:11.1.0" vite: "npm:7.3.0" @@ -15312,12 +15324,12 @@ __metadata: languageName: node linkType: hard -"ts-api-utils@npm:^2.1.0": - version: 2.1.0 - resolution: "ts-api-utils@npm:2.1.0" +"ts-api-utils@npm:^2.2.0": + version: 2.3.0 + resolution: "ts-api-utils@npm:2.3.0" peerDependencies: typescript: ">=4.8.4" - checksum: 10c0/9806a38adea2db0f6aa217ccc6bc9c391ddba338a9fe3080676d0d50ed806d305bb90e8cef0276e793d28c8a929f400abb184ddd7ff83a416959c0f4d2ce754f + checksum: 10c0/9f2aadb8ac55926c79db03e37ee3b014135923d1705f6868b9e787e6b8822d2fd8e19df2f9002563f4e6268c994425ddaad61df24d0dad833a4be9f26f789213 languageName: node linkType: hard @@ -15459,18 +15471,18 @@ __metadata: languageName: node linkType: hard -"typescript-eslint@npm:8.50.1": - version: 8.50.1 - resolution: "typescript-eslint@npm:8.50.1" +"typescript-eslint@npm:8.51.0": + version: 8.51.0 + resolution: "typescript-eslint@npm:8.51.0" dependencies: - "@typescript-eslint/eslint-plugin": "npm:8.50.1" - "@typescript-eslint/parser": "npm:8.50.1" - "@typescript-eslint/typescript-estree": "npm:8.50.1" - "@typescript-eslint/utils": "npm:8.50.1" + "@typescript-eslint/eslint-plugin": "npm:8.51.0" + "@typescript-eslint/parser": "npm:8.51.0" + "@typescript-eslint/typescript-estree": "npm:8.51.0" + "@typescript-eslint/utils": "npm:8.51.0" peerDependencies: eslint: ^8.57.0 || ^9.0.0 typescript: ">=4.8.4 <6.0.0" - checksum: 10c0/481095a249c48fa1d3551c50ceb8dcfba22413d6175f586ee802200342478a24b566b49d59e618c835631e4071ba1902d8549dc6467f47adb3079d00394d614f + checksum: 10c0/ae26e783e7c2e1e2b20c278e5d743fc9aa0ea0ce3c3c06c53f7b0913617ec75d40cb8732fec85e95071e87a5a90fa0867e2c3a11d7b8fec986c55460cc652b47 languageName: node linkType: hard @@ -16278,15 +16290,6 @@ __metadata: languageName: node linkType: hard -"whatwg-encoding@npm:^3.1.1": - version: 3.1.1 - resolution: "whatwg-encoding@npm:3.1.1" - dependencies: - iconv-lite: "npm:0.6.3" - checksum: 10c0/273b5f441c2f7fda3368a496c3009edbaa5e43b71b09728f90425e7f487e5cef9eb2b846a31bd760dd8077739c26faf6b5ca43a5f24033172b003b72cf61a93e - languageName: node - linkType: hard - "whatwg-mimetype@npm:^4.0.0": version: 4.0.0 resolution: "whatwg-mimetype@npm:4.0.0" diff --git a/opencti-platform/opencti-graphql/package.json b/opencti-platform/opencti-graphql/package.json index 144409f2aacd..73daa7b4ec20 100644 --- a/opencti-platform/opencti-graphql/package.json +++ b/opencti-platform/opencti-graphql/package.json @@ -204,7 +204,7 @@ "globals": "17.0.0", "graphql-tag": "2.12.6", "typescript": "5.9.3", - "typescript-eslint": "8.50.1", + "typescript-eslint": "8.51.0", "vitest": "3.2.4" }, "resolutions": { diff --git a/opencti-platform/opencti-graphql/yarn.lock b/opencti-platform/opencti-graphql/yarn.lock index 04b9a3592389..589a6fa8779b 100644 --- a/opencti-platform/opencti-graphql/yarn.lock +++ b/opencti-platform/opencti-graphql/yarn.lock @@ -5159,94 +5159,94 @@ __metadata: languageName: node linkType: hard -"@typescript-eslint/eslint-plugin@npm:8.50.1": - version: 8.50.1 - resolution: "@typescript-eslint/eslint-plugin@npm:8.50.1" +"@typescript-eslint/eslint-plugin@npm:8.51.0": + version: 8.51.0 + resolution: "@typescript-eslint/eslint-plugin@npm:8.51.0" dependencies: "@eslint-community/regexpp": "npm:^4.10.0" - "@typescript-eslint/scope-manager": "npm:8.50.1" - "@typescript-eslint/type-utils": "npm:8.50.1" - "@typescript-eslint/utils": "npm:8.50.1" - "@typescript-eslint/visitor-keys": "npm:8.50.1" + "@typescript-eslint/scope-manager": "npm:8.51.0" + "@typescript-eslint/type-utils": "npm:8.51.0" + "@typescript-eslint/utils": "npm:8.51.0" + "@typescript-eslint/visitor-keys": "npm:8.51.0" ignore: "npm:^7.0.0" natural-compare: "npm:^1.4.0" - ts-api-utils: "npm:^2.1.0" + ts-api-utils: "npm:^2.2.0" peerDependencies: - "@typescript-eslint/parser": ^8.50.1 + "@typescript-eslint/parser": ^8.51.0 eslint: ^8.57.0 || ^9.0.0 typescript: ">=4.8.4 <6.0.0" - checksum: 10c0/cae56cec414dc5d8347f1ff9fc01ec7b82c7988bcca9597569564b69e1715594e044487805a72ce7a9b4e6e81c3632db92c3d4b6b991874dafa402e1fcb508d5 + checksum: 10c0/3140e66a0f722338d56bf3de2b7cbb9a74a812d8da90fc61975ea029f6a401252c0824063d4c4baab9827de6f0209b34f4bbdc46e3f5fefd8fa2ff4a3980406f languageName: node linkType: hard -"@typescript-eslint/parser@npm:8.50.1": - version: 8.50.1 - resolution: "@typescript-eslint/parser@npm:8.50.1" +"@typescript-eslint/parser@npm:8.51.0": + version: 8.51.0 + resolution: "@typescript-eslint/parser@npm:8.51.0" dependencies: - "@typescript-eslint/scope-manager": "npm:8.50.1" - "@typescript-eslint/types": "npm:8.50.1" - "@typescript-eslint/typescript-estree": "npm:8.50.1" - "@typescript-eslint/visitor-keys": "npm:8.50.1" + "@typescript-eslint/scope-manager": "npm:8.51.0" + "@typescript-eslint/types": "npm:8.51.0" + "@typescript-eslint/typescript-estree": "npm:8.51.0" + "@typescript-eslint/visitor-keys": "npm:8.51.0" debug: "npm:^4.3.4" peerDependencies: eslint: ^8.57.0 || ^9.0.0 typescript: ">=4.8.4 <6.0.0" - checksum: 10c0/60a2591745650b35cd8d425bb1959ef40d598245481bdfdc2654ed1f7878364c2c442ba70ca7105b650d0df2b6109727dd43214be76045667de0d32a221f3955 + checksum: 10c0/b6aab1d82cc98a77aaae7637bf2934980104799793b3fd5b893065d930fe9b23cd6c2059d6f73fb454ea08f9e956e84fa940310d8435092a14be645a42062d94 languageName: node linkType: hard -"@typescript-eslint/project-service@npm:8.50.1": - version: 8.50.1 - resolution: "@typescript-eslint/project-service@npm:8.50.1" +"@typescript-eslint/project-service@npm:8.51.0": + version: 8.51.0 + resolution: "@typescript-eslint/project-service@npm:8.51.0" dependencies: - "@typescript-eslint/tsconfig-utils": "npm:^8.50.1" - "@typescript-eslint/types": "npm:^8.50.1" + "@typescript-eslint/tsconfig-utils": "npm:^8.51.0" + "@typescript-eslint/types": "npm:^8.51.0" debug: "npm:^4.3.4" peerDependencies: typescript: ">=4.8.4 <6.0.0" - checksum: 10c0/50fee0882188c2d704deddfb39f5283618adf7e5f72418143e9f69a8f3771233d55a3e0fc2673fa09c62e230ec53e500f95c0f1ed331ffac5f6a7f8e7b7a2e8c + checksum: 10c0/c6e6efbf79e126261e1742990b0872a34bbbe9931d99f0aabd12cb70a65a361e02d626db4b632dabee2b2c26b7e5b48344fc5a796c56438ae0788535e2bbe092 languageName: node linkType: hard -"@typescript-eslint/scope-manager@npm:8.50.1": - version: 8.50.1 - resolution: "@typescript-eslint/scope-manager@npm:8.50.1" +"@typescript-eslint/scope-manager@npm:8.51.0": + version: 8.51.0 + resolution: "@typescript-eslint/scope-manager@npm:8.51.0" dependencies: - "@typescript-eslint/types": "npm:8.50.1" - "@typescript-eslint/visitor-keys": "npm:8.50.1" - checksum: 10c0/ef0df092745f5d4e3684a3d770dc47735ab3195456de4ac5825931aeed1857a7e8d7cec14cc9c78c5ed049b3d83b0f8ac43b9463c5032ba548558a06bebb5539 + "@typescript-eslint/types": "npm:8.51.0" + "@typescript-eslint/visitor-keys": "npm:8.51.0" + checksum: 10c0/dd1e75fc13e6b1119954612d9e8ad3f2d91bc37dcde85fd00e959171aaf6c716c4c265c90c5accf24b5831bd3f48510b0775e5583085b8fa2ad5c37c8980ae1a languageName: node linkType: hard -"@typescript-eslint/tsconfig-utils@npm:8.50.1, @typescript-eslint/tsconfig-utils@npm:^8.50.1": - version: 8.50.1 - resolution: "@typescript-eslint/tsconfig-utils@npm:8.50.1" +"@typescript-eslint/tsconfig-utils@npm:8.51.0, @typescript-eslint/tsconfig-utils@npm:^8.51.0": + version: 8.51.0 + resolution: "@typescript-eslint/tsconfig-utils@npm:8.51.0" peerDependencies: typescript: ">=4.8.4 <6.0.0" - checksum: 10c0/6a1ffb0cd2d9e820ed0c7555a43ebb21438ca80f26c9632e0753bd09e764d9b8e9a352215e4ae60f6d570ab1e77751c9460a00515648b9a2f13f56c56a068a94 + checksum: 10c0/46cab9a5342b4a8f8a1d05aaee4236c5262a540ad0bca1f0e8dad5d63ed1e634b88ce0c82a612976dab09861e21086fc995a368df0435ac43fb960e0b9e5cde2 languageName: node linkType: hard -"@typescript-eslint/type-utils@npm:8.50.1": - version: 8.50.1 - resolution: "@typescript-eslint/type-utils@npm:8.50.1" +"@typescript-eslint/type-utils@npm:8.51.0": + version: 8.51.0 + resolution: "@typescript-eslint/type-utils@npm:8.51.0" dependencies: - "@typescript-eslint/types": "npm:8.50.1" - "@typescript-eslint/typescript-estree": "npm:8.50.1" - "@typescript-eslint/utils": "npm:8.50.1" + "@typescript-eslint/types": "npm:8.51.0" + "@typescript-eslint/typescript-estree": "npm:8.51.0" + "@typescript-eslint/utils": "npm:8.51.0" debug: "npm:^4.3.4" - ts-api-utils: "npm:^2.1.0" + ts-api-utils: "npm:^2.2.0" peerDependencies: eslint: ^8.57.0 || ^9.0.0 typescript: ">=4.8.4 <6.0.0" - checksum: 10c0/e4bfd3dd2459e936f7b6d9ee4b60fdedbf4b8f6b3d832e11d3cb1b58c1ce6da098880daafe3b65b2d33e2f79aba0e75c4b6eafdfa2a66c6e00a9ad3132b8e90d + checksum: 10c0/7c17214e54bc3a4fe4551d9251ffbac52e84ca46eeae840c0f981994b7cbcc837ef32a2b6d510b02d958a8f568df355e724d9c6938a206716271a1b0c00801b7 languageName: node linkType: hard -"@typescript-eslint/types@npm:8.50.1, @typescript-eslint/types@npm:^8.50.1": - version: 8.50.1 - resolution: "@typescript-eslint/types@npm:8.50.1" - checksum: 10c0/04e3c296d81293e370578762be6736fccd1581476f9d534938d42fe93968571fcaf26d7d8c3de52ed63a5af2c0b2da922b8ee2011fa5fb9fb401fc7f0916367a +"@typescript-eslint/types@npm:8.51.0, @typescript-eslint/types@npm:^8.51.0": + version: 8.51.0 + resolution: "@typescript-eslint/types@npm:8.51.0" + checksum: 10c0/eb3473d0bb71eb886438f35887b620ffadae7853b281752a40c73158aee644d136adeb82549be7d7c30f346fe888b2e979dff7e30e67b35377e8281018034529 languageName: node linkType: hard @@ -5257,47 +5257,47 @@ __metadata: languageName: node linkType: hard -"@typescript-eslint/typescript-estree@npm:8.50.1": - version: 8.50.1 - resolution: "@typescript-eslint/typescript-estree@npm:8.50.1" +"@typescript-eslint/typescript-estree@npm:8.51.0": + version: 8.51.0 + resolution: "@typescript-eslint/typescript-estree@npm:8.51.0" dependencies: - "@typescript-eslint/project-service": "npm:8.50.1" - "@typescript-eslint/tsconfig-utils": "npm:8.50.1" - "@typescript-eslint/types": "npm:8.50.1" - "@typescript-eslint/visitor-keys": "npm:8.50.1" + "@typescript-eslint/project-service": "npm:8.51.0" + "@typescript-eslint/tsconfig-utils": "npm:8.51.0" + "@typescript-eslint/types": "npm:8.51.0" + "@typescript-eslint/visitor-keys": "npm:8.51.0" debug: "npm:^4.3.4" minimatch: "npm:^9.0.4" semver: "npm:^7.6.0" tinyglobby: "npm:^0.2.15" - ts-api-utils: "npm:^2.1.0" + ts-api-utils: "npm:^2.2.0" peerDependencies: typescript: ">=4.8.4 <6.0.0" - checksum: 10c0/697b53fd3355619271a7bf543c5880731670b96567da63f554a3c3cd4d746feb8153628ec912c8a2df95e3123472e9a77df43c32fad72946b69ace89c2cf8b7e + checksum: 10c0/5386acc67298a6757681b6264c29a6b9304be7a188f11498bbaa82bb0a3095fd79394ad80d6520bdff3fa3093199f9a438246604ee3281b76f7ed574b7516854 languageName: node linkType: hard -"@typescript-eslint/utils@npm:8.50.1": - version: 8.50.1 - resolution: "@typescript-eslint/utils@npm:8.50.1" +"@typescript-eslint/utils@npm:8.51.0": + version: 8.51.0 + resolution: "@typescript-eslint/utils@npm:8.51.0" dependencies: "@eslint-community/eslint-utils": "npm:^4.7.0" - "@typescript-eslint/scope-manager": "npm:8.50.1" - "@typescript-eslint/types": "npm:8.50.1" - "@typescript-eslint/typescript-estree": "npm:8.50.1" + "@typescript-eslint/scope-manager": "npm:8.51.0" + "@typescript-eslint/types": "npm:8.51.0" + "@typescript-eslint/typescript-estree": "npm:8.51.0" peerDependencies: eslint: ^8.57.0 || ^9.0.0 typescript: ">=4.8.4 <6.0.0" - checksum: 10c0/66b19a9c8981b0b601af3a477fdcabdd110b0805591f28eefa11b32bbb88518d80b928e49eaa4c40d42ea8d71605bf5cd2ee5e39802022d1daec2800f1b198df + checksum: 10c0/ffb8237cfb33a1998ae2812b136d42fb65e7497f185d46097d19e43112e41b3ef59f901ba679c2e5372ad3007026f6e5add3a3de0f2e75ce6896918713fa38a8 languageName: node linkType: hard -"@typescript-eslint/visitor-keys@npm:8.50.1": - version: 8.50.1 - resolution: "@typescript-eslint/visitor-keys@npm:8.50.1" +"@typescript-eslint/visitor-keys@npm:8.51.0": + version: 8.51.0 + resolution: "@typescript-eslint/visitor-keys@npm:8.51.0" dependencies: - "@typescript-eslint/types": "npm:8.50.1" + "@typescript-eslint/types": "npm:8.51.0" eslint-visitor-keys: "npm:^4.2.1" - checksum: 10c0/b23839d04b2e5e7964a4006317d75cdc3cf76e56f4c5fde1e0bcd23f3bb78dca910e3dcadca80606f76a09ff9e44b3363ee1e1d6394e3f7479da74a641a8870f + checksum: 10c0/fce5603961cf336e71095f7599157de65e3182f61cbd6cab33a43551ee91485b4e9bf6cacc1b275cf6f3503b92f8568fe2267a45c82e60e386ee73db727a26ca languageName: node linkType: hard @@ -11056,7 +11056,7 @@ __metadata: tough-cookie: "npm:6.0.0" turndown: "npm:7.2.2" typescript: "npm:5.9.3" - typescript-eslint: "npm:8.50.1" + typescript-eslint: "npm:8.51.0" unzipper: "npm:0.12.3" uuid: "npm:11.1.0" uuid-time: "npm:1.0.0" @@ -13063,12 +13063,12 @@ __metadata: languageName: node linkType: hard -"ts-api-utils@npm:^2.1.0": - version: 2.1.0 - resolution: "ts-api-utils@npm:2.1.0" +"ts-api-utils@npm:^2.2.0": + version: 2.3.0 + resolution: "ts-api-utils@npm:2.3.0" peerDependencies: typescript: ">=4.8.4" - checksum: 10c0/9806a38adea2db0f6aa217ccc6bc9c391ddba338a9fe3080676d0d50ed806d305bb90e8cef0276e793d28c8a929f400abb184ddd7ff83a416959c0f4d2ce754f + checksum: 10c0/9f2aadb8ac55926c79db03e37ee3b014135923d1705f6868b9e787e6b8822d2fd8e19df2f9002563f4e6268c994425ddaad61df24d0dad833a4be9f26f789213 languageName: node linkType: hard @@ -13210,18 +13210,18 @@ __metadata: languageName: node linkType: hard -"typescript-eslint@npm:8.50.1": - version: 8.50.1 - resolution: "typescript-eslint@npm:8.50.1" +"typescript-eslint@npm:8.51.0": + version: 8.51.0 + resolution: "typescript-eslint@npm:8.51.0" dependencies: - "@typescript-eslint/eslint-plugin": "npm:8.50.1" - "@typescript-eslint/parser": "npm:8.50.1" - "@typescript-eslint/typescript-estree": "npm:8.50.1" - "@typescript-eslint/utils": "npm:8.50.1" + "@typescript-eslint/eslint-plugin": "npm:8.51.0" + "@typescript-eslint/parser": "npm:8.51.0" + "@typescript-eslint/typescript-estree": "npm:8.51.0" + "@typescript-eslint/utils": "npm:8.51.0" peerDependencies: eslint: ^8.57.0 || ^9.0.0 typescript: ">=4.8.4 <6.0.0" - checksum: 10c0/481095a249c48fa1d3551c50ceb8dcfba22413d6175f586ee802200342478a24b566b49d59e618c835631e4071ba1902d8549dc6467f47adb3079d00394d614f + checksum: 10c0/ae26e783e7c2e1e2b20c278e5d743fc9aa0ea0ce3c3c06c53f7b0913617ec75d40cb8732fec85e95071e87a5a90fa0867e2c3a11d7b8fec986c55460cc652b47 languageName: node linkType: hard From 4cef80baa03299ba4b39da743fd6c350d44de5e3 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 6 Jan 2026 01:43:41 +0100 Subject: [PATCH 033/126] [deps] Lock file maintenance (#13680) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: Xavier Fournet <461943+xfournet@users.noreply.github.com> --- opencti-platform/opencti-front/yarn.lock | 961 ++++++------------ .../src/utils/safeEjs.client.ts | 4 +- opencti-platform/opencti-graphql/yarn.lock | 784 +++++--------- 3 files changed, 552 insertions(+), 1197 deletions(-) diff --git a/opencti-platform/opencti-front/yarn.lock b/opencti-platform/opencti-front/yarn.lock index 37eb6f4c06c5..255833e14d29 100644 --- a/opencti-platform/opencti-front/yarn.lock +++ b/opencti-platform/opencti-front/yarn.lock @@ -19,9 +19,9 @@ __metadata: linkType: hard "@acemir/cssom@npm:^0.9.28": - version: 0.9.29 - resolution: "@acemir/cssom@npm:0.9.29" - checksum: 10c0/029fdaf1f7e6e9e6267ba0d94301de15001066cc729956851cedfe486a82e6d925eb8fc8f7f488e6618ce0a615ae9a5e660d9ab13406c8396e2852df5bcbbdd6 + version: 0.9.30 + resolution: "@acemir/cssom@npm:0.9.30" + checksum: 10c0/f90dd766826315904ab71361aa7b3e0f1ff01124c73b2abc4c6f676ccb036cc6d4fe7c834f89384302e3dfbbd1e819681816fd29da594bb94a5739c634e8282f languageName: node linkType: hard @@ -106,16 +106,16 @@ __metadata: languageName: node linkType: hard -"@asamuzakjp/css-color@npm:^4.1.0": - version: 4.1.0 - resolution: "@asamuzakjp/css-color@npm:4.1.0" +"@asamuzakjp/css-color@npm:^4.1.1": + version: 4.1.1 + resolution: "@asamuzakjp/css-color@npm:4.1.1" dependencies: "@csstools/css-calc": "npm:^2.1.4" "@csstools/css-color-parser": "npm:^3.1.0" "@csstools/css-parser-algorithms": "npm:^3.0.5" "@csstools/css-tokenizer": "npm:^3.0.4" - lru-cache: "npm:^11.2.2" - checksum: 10c0/097b9270a5befb765885dda43d6914ccbaa575565525d307e8ba3ba07f98e466062f4a77b9657e65160db9110c41c59cf5a6937479647f73637aeddf5c421579 + lru-cache: "npm:^11.2.4" + checksum: 10c0/2948ae9cd4c2f326ab5470d6ac7d415bb8062150ef254f830d774b6a77d6dccfbdb4b84ed4ef5c86c5643d42c52d77204b8d94d0d90f2e2cea9ec9b6cbb9c336 languageName: node linkType: hard @@ -1166,12 +1166,10 @@ __metadata: languageName: node linkType: hard -"@csstools/css-syntax-patches-for-csstree@npm:1.0.14": - version: 1.0.14 - resolution: "@csstools/css-syntax-patches-for-csstree@npm:1.0.14" - peerDependencies: - postcss: ^8.4 - checksum: 10c0/e431cf5aa4ccd6a40f4a417663ac7178c822c5427b9c8473e466257dc46dd9698e3852d5517ec220b7d1d1ea911e9007ecb429464329ae169a0aa68b56f1c3ac +"@csstools/css-syntax-patches-for-csstree@npm:^1.0.21": + version: 1.0.22 + resolution: "@csstools/css-syntax-patches-for-csstree@npm:1.0.22" + checksum: 10c0/5ec39dc28b30bb0d5e212e598979707786e152698be820502bdee4b0204983604170dd960a8684de8de89f28376cf4a151de81d9da166d97eeb35f869a44ae08 languageName: node linkType: hard @@ -1393,13 +1391,6 @@ __metadata: languageName: node linkType: hard -"@esbuild/aix-ppc64@npm:0.25.12": - version: 0.25.12 - resolution: "@esbuild/aix-ppc64@npm:0.25.12" - conditions: os=aix & cpu=ppc64 - languageName: node - linkType: hard - "@esbuild/aix-ppc64@npm:0.27.2": version: 0.27.2 resolution: "@esbuild/aix-ppc64@npm:0.27.2" @@ -1407,13 +1398,6 @@ __metadata: languageName: node linkType: hard -"@esbuild/android-arm64@npm:0.25.12": - version: 0.25.12 - resolution: "@esbuild/android-arm64@npm:0.25.12" - conditions: os=android & cpu=arm64 - languageName: node - linkType: hard - "@esbuild/android-arm64@npm:0.27.2": version: 0.27.2 resolution: "@esbuild/android-arm64@npm:0.27.2" @@ -1421,13 +1405,6 @@ __metadata: languageName: node linkType: hard -"@esbuild/android-arm@npm:0.25.12": - version: 0.25.12 - resolution: "@esbuild/android-arm@npm:0.25.12" - conditions: os=android & cpu=arm - languageName: node - linkType: hard - "@esbuild/android-arm@npm:0.27.2": version: 0.27.2 resolution: "@esbuild/android-arm@npm:0.27.2" @@ -1435,13 +1412,6 @@ __metadata: languageName: node linkType: hard -"@esbuild/android-x64@npm:0.25.12": - version: 0.25.12 - resolution: "@esbuild/android-x64@npm:0.25.12" - conditions: os=android & cpu=x64 - languageName: node - linkType: hard - "@esbuild/android-x64@npm:0.27.2": version: 0.27.2 resolution: "@esbuild/android-x64@npm:0.27.2" @@ -1449,13 +1419,6 @@ __metadata: languageName: node linkType: hard -"@esbuild/darwin-arm64@npm:0.25.12": - version: 0.25.12 - resolution: "@esbuild/darwin-arm64@npm:0.25.12" - conditions: os=darwin & cpu=arm64 - languageName: node - linkType: hard - "@esbuild/darwin-arm64@npm:0.27.2": version: 0.27.2 resolution: "@esbuild/darwin-arm64@npm:0.27.2" @@ -1463,13 +1426,6 @@ __metadata: languageName: node linkType: hard -"@esbuild/darwin-x64@npm:0.25.12": - version: 0.25.12 - resolution: "@esbuild/darwin-x64@npm:0.25.12" - conditions: os=darwin & cpu=x64 - languageName: node - linkType: hard - "@esbuild/darwin-x64@npm:0.27.2": version: 0.27.2 resolution: "@esbuild/darwin-x64@npm:0.27.2" @@ -1477,13 +1433,6 @@ __metadata: languageName: node linkType: hard -"@esbuild/freebsd-arm64@npm:0.25.12": - version: 0.25.12 - resolution: "@esbuild/freebsd-arm64@npm:0.25.12" - conditions: os=freebsd & cpu=arm64 - languageName: node - linkType: hard - "@esbuild/freebsd-arm64@npm:0.27.2": version: 0.27.2 resolution: "@esbuild/freebsd-arm64@npm:0.27.2" @@ -1491,13 +1440,6 @@ __metadata: languageName: node linkType: hard -"@esbuild/freebsd-x64@npm:0.25.12": - version: 0.25.12 - resolution: "@esbuild/freebsd-x64@npm:0.25.12" - conditions: os=freebsd & cpu=x64 - languageName: node - linkType: hard - "@esbuild/freebsd-x64@npm:0.27.2": version: 0.27.2 resolution: "@esbuild/freebsd-x64@npm:0.27.2" @@ -1505,13 +1447,6 @@ __metadata: languageName: node linkType: hard -"@esbuild/linux-arm64@npm:0.25.12": - version: 0.25.12 - resolution: "@esbuild/linux-arm64@npm:0.25.12" - conditions: os=linux & cpu=arm64 - languageName: node - linkType: hard - "@esbuild/linux-arm64@npm:0.27.2": version: 0.27.2 resolution: "@esbuild/linux-arm64@npm:0.27.2" @@ -1519,13 +1454,6 @@ __metadata: languageName: node linkType: hard -"@esbuild/linux-arm@npm:0.25.12": - version: 0.25.12 - resolution: "@esbuild/linux-arm@npm:0.25.12" - conditions: os=linux & cpu=arm - languageName: node - linkType: hard - "@esbuild/linux-arm@npm:0.27.2": version: 0.27.2 resolution: "@esbuild/linux-arm@npm:0.27.2" @@ -1533,13 +1461,6 @@ __metadata: languageName: node linkType: hard -"@esbuild/linux-ia32@npm:0.25.12": - version: 0.25.12 - resolution: "@esbuild/linux-ia32@npm:0.25.12" - conditions: os=linux & cpu=ia32 - languageName: node - linkType: hard - "@esbuild/linux-ia32@npm:0.27.2": version: 0.27.2 resolution: "@esbuild/linux-ia32@npm:0.27.2" @@ -1547,13 +1468,6 @@ __metadata: languageName: node linkType: hard -"@esbuild/linux-loong64@npm:0.25.12": - version: 0.25.12 - resolution: "@esbuild/linux-loong64@npm:0.25.12" - conditions: os=linux & cpu=loong64 - languageName: node - linkType: hard - "@esbuild/linux-loong64@npm:0.27.2": version: 0.27.2 resolution: "@esbuild/linux-loong64@npm:0.27.2" @@ -1561,13 +1475,6 @@ __metadata: languageName: node linkType: hard -"@esbuild/linux-mips64el@npm:0.25.12": - version: 0.25.12 - resolution: "@esbuild/linux-mips64el@npm:0.25.12" - conditions: os=linux & cpu=mips64el - languageName: node - linkType: hard - "@esbuild/linux-mips64el@npm:0.27.2": version: 0.27.2 resolution: "@esbuild/linux-mips64el@npm:0.27.2" @@ -1575,13 +1482,6 @@ __metadata: languageName: node linkType: hard -"@esbuild/linux-ppc64@npm:0.25.12": - version: 0.25.12 - resolution: "@esbuild/linux-ppc64@npm:0.25.12" - conditions: os=linux & cpu=ppc64 - languageName: node - linkType: hard - "@esbuild/linux-ppc64@npm:0.27.2": version: 0.27.2 resolution: "@esbuild/linux-ppc64@npm:0.27.2" @@ -1589,13 +1489,6 @@ __metadata: languageName: node linkType: hard -"@esbuild/linux-riscv64@npm:0.25.12": - version: 0.25.12 - resolution: "@esbuild/linux-riscv64@npm:0.25.12" - conditions: os=linux & cpu=riscv64 - languageName: node - linkType: hard - "@esbuild/linux-riscv64@npm:0.27.2": version: 0.27.2 resolution: "@esbuild/linux-riscv64@npm:0.27.2" @@ -1603,13 +1496,6 @@ __metadata: languageName: node linkType: hard -"@esbuild/linux-s390x@npm:0.25.12": - version: 0.25.12 - resolution: "@esbuild/linux-s390x@npm:0.25.12" - conditions: os=linux & cpu=s390x - languageName: node - linkType: hard - "@esbuild/linux-s390x@npm:0.27.2": version: 0.27.2 resolution: "@esbuild/linux-s390x@npm:0.27.2" @@ -1617,13 +1503,6 @@ __metadata: languageName: node linkType: hard -"@esbuild/linux-x64@npm:0.25.12": - version: 0.25.12 - resolution: "@esbuild/linux-x64@npm:0.25.12" - conditions: os=linux & cpu=x64 - languageName: node - linkType: hard - "@esbuild/linux-x64@npm:0.27.2": version: 0.27.2 resolution: "@esbuild/linux-x64@npm:0.27.2" @@ -1631,13 +1510,6 @@ __metadata: languageName: node linkType: hard -"@esbuild/netbsd-arm64@npm:0.25.12": - version: 0.25.12 - resolution: "@esbuild/netbsd-arm64@npm:0.25.12" - conditions: os=netbsd & cpu=arm64 - languageName: node - linkType: hard - "@esbuild/netbsd-arm64@npm:0.27.2": version: 0.27.2 resolution: "@esbuild/netbsd-arm64@npm:0.27.2" @@ -1645,13 +1517,6 @@ __metadata: languageName: node linkType: hard -"@esbuild/netbsd-x64@npm:0.25.12": - version: 0.25.12 - resolution: "@esbuild/netbsd-x64@npm:0.25.12" - conditions: os=netbsd & cpu=x64 - languageName: node - linkType: hard - "@esbuild/netbsd-x64@npm:0.27.2": version: 0.27.2 resolution: "@esbuild/netbsd-x64@npm:0.27.2" @@ -1659,13 +1524,6 @@ __metadata: languageName: node linkType: hard -"@esbuild/openbsd-arm64@npm:0.25.12": - version: 0.25.12 - resolution: "@esbuild/openbsd-arm64@npm:0.25.12" - conditions: os=openbsd & cpu=arm64 - languageName: node - linkType: hard - "@esbuild/openbsd-arm64@npm:0.27.2": version: 0.27.2 resolution: "@esbuild/openbsd-arm64@npm:0.27.2" @@ -1673,13 +1531,6 @@ __metadata: languageName: node linkType: hard -"@esbuild/openbsd-x64@npm:0.25.12": - version: 0.25.12 - resolution: "@esbuild/openbsd-x64@npm:0.25.12" - conditions: os=openbsd & cpu=x64 - languageName: node - linkType: hard - "@esbuild/openbsd-x64@npm:0.27.2": version: 0.27.2 resolution: "@esbuild/openbsd-x64@npm:0.27.2" @@ -1687,13 +1538,6 @@ __metadata: languageName: node linkType: hard -"@esbuild/openharmony-arm64@npm:0.25.12": - version: 0.25.12 - resolution: "@esbuild/openharmony-arm64@npm:0.25.12" - conditions: os=openharmony & cpu=arm64 - languageName: node - linkType: hard - "@esbuild/openharmony-arm64@npm:0.27.2": version: 0.27.2 resolution: "@esbuild/openharmony-arm64@npm:0.27.2" @@ -1701,13 +1545,6 @@ __metadata: languageName: node linkType: hard -"@esbuild/sunos-x64@npm:0.25.12": - version: 0.25.12 - resolution: "@esbuild/sunos-x64@npm:0.25.12" - conditions: os=sunos & cpu=x64 - languageName: node - linkType: hard - "@esbuild/sunos-x64@npm:0.27.2": version: 0.27.2 resolution: "@esbuild/sunos-x64@npm:0.27.2" @@ -1715,13 +1552,6 @@ __metadata: languageName: node linkType: hard -"@esbuild/win32-arm64@npm:0.25.12": - version: 0.25.12 - resolution: "@esbuild/win32-arm64@npm:0.25.12" - conditions: os=win32 & cpu=arm64 - languageName: node - linkType: hard - "@esbuild/win32-arm64@npm:0.27.2": version: 0.27.2 resolution: "@esbuild/win32-arm64@npm:0.27.2" @@ -1729,13 +1559,6 @@ __metadata: languageName: node linkType: hard -"@esbuild/win32-ia32@npm:0.25.12": - version: 0.25.12 - resolution: "@esbuild/win32-ia32@npm:0.25.12" - conditions: os=win32 & cpu=ia32 - languageName: node - linkType: hard - "@esbuild/win32-ia32@npm:0.27.2": version: 0.27.2 resolution: "@esbuild/win32-ia32@npm:0.27.2" @@ -1743,13 +1566,6 @@ __metadata: languageName: node linkType: hard -"@esbuild/win32-x64@npm:0.25.12": - version: 0.25.12 - resolution: "@esbuild/win32-x64@npm:0.25.12" - conditions: os=win32 & cpu=x64 - languageName: node - linkType: hard - "@esbuild/win32-x64@npm:0.27.2": version: 0.27.2 resolution: "@esbuild/win32-x64@npm:0.27.2" @@ -1758,13 +1574,13 @@ __metadata: linkType: hard "@eslint-community/eslint-utils@npm:^4.7.0, @eslint-community/eslint-utils@npm:^4.8.0, @eslint-community/eslint-utils@npm:^4.9.0": - version: 4.9.0 - resolution: "@eslint-community/eslint-utils@npm:4.9.0" + version: 4.9.1 + resolution: "@eslint-community/eslint-utils@npm:4.9.1" dependencies: eslint-visitor-keys: "npm:^3.4.3" peerDependencies: eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 - checksum: 10c0/8881e22d519326e7dba85ea915ac7a143367c805e6ba1374c987aa2fbdd09195cc51183d2da72c0e2ff388f84363e1b220fd0d19bef10c272c63455162176817 + checksum: 10c0/dc4ab5e3e364ef27e33666b11f4b86e1a6c1d7cbf16f0c6ff87b1619b3562335e9201a3d6ce806221887ff780ec9d828962a290bb910759fd40a674686503f02 languageName: node linkType: hard @@ -1846,14 +1662,14 @@ __metadata: linkType: hard "@exodus/bytes@npm:^1.6.0": - version: 1.7.0 - resolution: "@exodus/bytes@npm:1.7.0" + version: 1.8.0 + resolution: "@exodus/bytes@npm:1.8.0" peerDependencies: "@exodus/crypto": ^1.0.0-rc.4 peerDependenciesMeta: "@exodus/crypto": optional: true - checksum: 10c0/f919d7d26c44a31ae71a280baf821b38826bdca97ac6c6dbe7d02bf6952104ef2106534cb5566c17f7a1dc1ae627a78f2fabce3a09d6bbc65a9b1bdaeaeebfc5 + checksum: 10c0/1878868519230fa564b80d3d12fa7e2b559dfed143f4e48969eb5f98083caea431bbc70356d6b2f90ba80c8148295fe85af6a5ed8a746dd53baefe4f1ed086e8 languageName: node linkType: hard @@ -2199,12 +2015,12 @@ __metadata: linkType: hard "@grpc/grpc-js@npm:^1.12.6": - version: 1.14.2 - resolution: "@grpc/grpc-js@npm:1.14.2" + version: 1.14.3 + resolution: "@grpc/grpc-js@npm:1.14.3" dependencies: "@grpc/proto-loader": "npm:^0.8.0" "@js-sdsl/ordered-map": "npm:^4.4.2" - checksum: 10c0/12e7b885cd5033db53fc14099b7ada55d82a7e358fece4d4a39a8520885332f871353fd1630828e77c4777f2ec6b99531b087263298482b7b9c5cf20e540d371 + checksum: 10c0/f41f06a311b93cca8c472d56e21387e0f7b57bb2337a91d15ea4279bac8ec4fa0de6bd0d881201229ab800c0f0c55277911ecb850e057f20a828d0ddd623551d languageName: node linkType: hard @@ -2937,90 +2753,98 @@ __metadata: languageName: node linkType: hard -"@napi-rs/canvas-android-arm64@npm:0.1.83": - version: 0.1.83 - resolution: "@napi-rs/canvas-android-arm64@npm:0.1.83" +"@napi-rs/canvas-android-arm64@npm:0.1.88": + version: 0.1.88 + resolution: "@napi-rs/canvas-android-arm64@npm:0.1.88" conditions: os=android & cpu=arm64 languageName: node linkType: hard -"@napi-rs/canvas-darwin-arm64@npm:0.1.83": - version: 0.1.83 - resolution: "@napi-rs/canvas-darwin-arm64@npm:0.1.83" +"@napi-rs/canvas-darwin-arm64@npm:0.1.88": + version: 0.1.88 + resolution: "@napi-rs/canvas-darwin-arm64@npm:0.1.88" conditions: os=darwin & cpu=arm64 languageName: node linkType: hard -"@napi-rs/canvas-darwin-x64@npm:0.1.83": - version: 0.1.83 - resolution: "@napi-rs/canvas-darwin-x64@npm:0.1.83" +"@napi-rs/canvas-darwin-x64@npm:0.1.88": + version: 0.1.88 + resolution: "@napi-rs/canvas-darwin-x64@npm:0.1.88" conditions: os=darwin & cpu=x64 languageName: node linkType: hard -"@napi-rs/canvas-linux-arm-gnueabihf@npm:0.1.83": - version: 0.1.83 - resolution: "@napi-rs/canvas-linux-arm-gnueabihf@npm:0.1.83" +"@napi-rs/canvas-linux-arm-gnueabihf@npm:0.1.88": + version: 0.1.88 + resolution: "@napi-rs/canvas-linux-arm-gnueabihf@npm:0.1.88" conditions: os=linux & cpu=arm languageName: node linkType: hard -"@napi-rs/canvas-linux-arm64-gnu@npm:0.1.83": - version: 0.1.83 - resolution: "@napi-rs/canvas-linux-arm64-gnu@npm:0.1.83" +"@napi-rs/canvas-linux-arm64-gnu@npm:0.1.88": + version: 0.1.88 + resolution: "@napi-rs/canvas-linux-arm64-gnu@npm:0.1.88" conditions: os=linux & cpu=arm64 & libc=glibc languageName: node linkType: hard -"@napi-rs/canvas-linux-arm64-musl@npm:0.1.83": - version: 0.1.83 - resolution: "@napi-rs/canvas-linux-arm64-musl@npm:0.1.83" +"@napi-rs/canvas-linux-arm64-musl@npm:0.1.88": + version: 0.1.88 + resolution: "@napi-rs/canvas-linux-arm64-musl@npm:0.1.88" conditions: os=linux & cpu=arm64 & libc=musl languageName: node linkType: hard -"@napi-rs/canvas-linux-riscv64-gnu@npm:0.1.83": - version: 0.1.83 - resolution: "@napi-rs/canvas-linux-riscv64-gnu@npm:0.1.83" +"@napi-rs/canvas-linux-riscv64-gnu@npm:0.1.88": + version: 0.1.88 + resolution: "@napi-rs/canvas-linux-riscv64-gnu@npm:0.1.88" conditions: os=linux & cpu=riscv64 & libc=glibc languageName: node linkType: hard -"@napi-rs/canvas-linux-x64-gnu@npm:0.1.83": - version: 0.1.83 - resolution: "@napi-rs/canvas-linux-x64-gnu@npm:0.1.83" +"@napi-rs/canvas-linux-x64-gnu@npm:0.1.88": + version: 0.1.88 + resolution: "@napi-rs/canvas-linux-x64-gnu@npm:0.1.88" conditions: os=linux & cpu=x64 & libc=glibc languageName: node linkType: hard -"@napi-rs/canvas-linux-x64-musl@npm:0.1.83": - version: 0.1.83 - resolution: "@napi-rs/canvas-linux-x64-musl@npm:0.1.83" +"@napi-rs/canvas-linux-x64-musl@npm:0.1.88": + version: 0.1.88 + resolution: "@napi-rs/canvas-linux-x64-musl@npm:0.1.88" conditions: os=linux & cpu=x64 & libc=musl languageName: node linkType: hard -"@napi-rs/canvas-win32-x64-msvc@npm:0.1.83": - version: 0.1.83 - resolution: "@napi-rs/canvas-win32-x64-msvc@npm:0.1.83" +"@napi-rs/canvas-win32-arm64-msvc@npm:0.1.88": + version: 0.1.88 + resolution: "@napi-rs/canvas-win32-arm64-msvc@npm:0.1.88" + conditions: os=win32 & cpu=arm64 + languageName: node + linkType: hard + +"@napi-rs/canvas-win32-x64-msvc@npm:0.1.88": + version: 0.1.88 + resolution: "@napi-rs/canvas-win32-x64-msvc@npm:0.1.88" conditions: os=win32 & cpu=x64 languageName: node linkType: hard "@napi-rs/canvas@npm:^0.1.80": - version: 0.1.83 - resolution: "@napi-rs/canvas@npm:0.1.83" - dependencies: - "@napi-rs/canvas-android-arm64": "npm:0.1.83" - "@napi-rs/canvas-darwin-arm64": "npm:0.1.83" - "@napi-rs/canvas-darwin-x64": "npm:0.1.83" - "@napi-rs/canvas-linux-arm-gnueabihf": "npm:0.1.83" - "@napi-rs/canvas-linux-arm64-gnu": "npm:0.1.83" - "@napi-rs/canvas-linux-arm64-musl": "npm:0.1.83" - "@napi-rs/canvas-linux-riscv64-gnu": "npm:0.1.83" - "@napi-rs/canvas-linux-x64-gnu": "npm:0.1.83" - "@napi-rs/canvas-linux-x64-musl": "npm:0.1.83" - "@napi-rs/canvas-win32-x64-msvc": "npm:0.1.83" + version: 0.1.88 + resolution: "@napi-rs/canvas@npm:0.1.88" + dependencies: + "@napi-rs/canvas-android-arm64": "npm:0.1.88" + "@napi-rs/canvas-darwin-arm64": "npm:0.1.88" + "@napi-rs/canvas-darwin-x64": "npm:0.1.88" + "@napi-rs/canvas-linux-arm-gnueabihf": "npm:0.1.88" + "@napi-rs/canvas-linux-arm64-gnu": "npm:0.1.88" + "@napi-rs/canvas-linux-arm64-musl": "npm:0.1.88" + "@napi-rs/canvas-linux-riscv64-gnu": "npm:0.1.88" + "@napi-rs/canvas-linux-x64-gnu": "npm:0.1.88" + "@napi-rs/canvas-linux-x64-musl": "npm:0.1.88" + "@napi-rs/canvas-win32-arm64-msvc": "npm:0.1.88" + "@napi-rs/canvas-win32-x64-msvc": "npm:0.1.88" dependenciesMeta: "@napi-rs/canvas-android-arm64": optional: true @@ -3040,9 +2864,11 @@ __metadata: optional: true "@napi-rs/canvas-linux-x64-musl": optional: true + "@napi-rs/canvas-win32-arm64-msvc": + optional: true "@napi-rs/canvas-win32-x64-msvc": optional: true - checksum: 10c0/fb7abc2823fe14f85039edda11a6f98aa5dfa07a0f77799f4e053345ad7c99d7700670d4df7f3d6203a90b17cb2d85ea1361d8acbe54da846eb7fb7bcaf62a7a + checksum: 10c0/00543b86c66b0b12aa0c60922099ae69d83379d8a47345c1c6d079a37788dc1c6231b64d72dca20a581d817121efa0a37c965a71ec96d248c91a4627872753d3 languageName: node linkType: hard @@ -3779,34 +3605,34 @@ __metadata: linkType: hard "@react-aria/focus@npm:^3.20.2": - version: 3.21.2 - resolution: "@react-aria/focus@npm:3.21.2" + version: 3.21.3 + resolution: "@react-aria/focus@npm:3.21.3" dependencies: - "@react-aria/interactions": "npm:^3.25.6" - "@react-aria/utils": "npm:^3.31.0" + "@react-aria/interactions": "npm:^3.26.0" + "@react-aria/utils": "npm:^3.32.0" "@react-types/shared": "npm:^3.32.1" "@swc/helpers": "npm:^0.5.0" clsx: "npm:^2.0.0" peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - checksum: 10c0/bfcdbb8d47bf038c035b025df6b9c292eeea9a2af7c77ec2ac27c302cb64dc481cfe80bb6575b399301ad1516feba134dec01e3c112ca2cf912ca13b47965917 + checksum: 10c0/c1169f2047908dd2641439ed49b51d1482df00514f5adc569d73727bc6375150198dd1b6e345a79fc31f3571d7d09549743ba2e6b3168ed8d6a554708d48fa9b languageName: node linkType: hard -"@react-aria/interactions@npm:^3.25.0, @react-aria/interactions@npm:^3.25.6": - version: 3.25.6 - resolution: "@react-aria/interactions@npm:3.25.6" +"@react-aria/interactions@npm:^3.25.0, @react-aria/interactions@npm:^3.26.0": + version: 3.26.0 + resolution: "@react-aria/interactions@npm:3.26.0" dependencies: "@react-aria/ssr": "npm:^3.9.10" - "@react-aria/utils": "npm:^3.31.0" + "@react-aria/utils": "npm:^3.32.0" "@react-stately/flags": "npm:^3.1.2" "@react-types/shared": "npm:^3.32.1" "@swc/helpers": "npm:^0.5.0" peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - checksum: 10c0/000300ee3cfab724228c89f7261e94e1357f91f746256c352466a014ab6e1e907a3e6c6a2c0e73a6dd7efc97c1a608c96462de5b41a3eebda22cbc97550a797d + checksum: 10c0/542044d08c02aec337ceda1ed55e5b01f6fa3e76c930b0063bc4a2146102d39659df81570912b7bef4782e268c08bbfdca82a44df413ec8ce8f1bdf930e97051 languageName: node linkType: hard @@ -3821,20 +3647,20 @@ __metadata: languageName: node linkType: hard -"@react-aria/utils@npm:^3.31.0": - version: 3.31.0 - resolution: "@react-aria/utils@npm:3.31.0" +"@react-aria/utils@npm:^3.32.0": + version: 3.32.0 + resolution: "@react-aria/utils@npm:3.32.0" dependencies: "@react-aria/ssr": "npm:^3.9.10" "@react-stately/flags": "npm:^3.1.2" - "@react-stately/utils": "npm:^3.10.8" + "@react-stately/utils": "npm:^3.11.0" "@react-types/shared": "npm:^3.32.1" "@swc/helpers": "npm:^0.5.0" clsx: "npm:^2.0.0" peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - checksum: 10c0/a6b5c6b85a51fa9ca204f045f70d36a55e16b56b85141d556eaacb7b74c4c0915189f6d2baea06df59bdd2926dcca08c2313c98478dbb50ed8e59f9b6754735c + checksum: 10c0/10fd9b162f8c752bf70070f5e091eaf3bd2c163b0a86e1f29c306c766b6b1acbbefa85c1ed6c28973b858afeafd638faa783361440c679890698c3d78bb50121 languageName: node linkType: hard @@ -3858,14 +3684,14 @@ __metadata: languageName: node linkType: hard -"@react-stately/utils@npm:^3.10.8": - version: 3.10.8 - resolution: "@react-stately/utils@npm:3.10.8" +"@react-stately/utils@npm:^3.11.0": + version: 3.11.0 + resolution: "@react-stately/utils@npm:3.11.0" dependencies: "@swc/helpers": "npm:^0.5.0" peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - checksum: 10c0/a97cc292986e3eeb2ceb1626671ce60e8342a3ff35ab92bcfcb94bd6b28729836cc592e3fe4df2fba603e5fdd26291be77b7f60441920298c282bb93f424feba + checksum: 10c0/09b38438df19fd8ff14d3147b2f9e5d42869b3ee637b0e33d6f2ab5cba93612e640c6de339b766b8c825d7bef828851fd551d5a197a037eb1331913546b8516c languageName: node linkType: hard @@ -3975,8 +3801,8 @@ __metadata: linkType: hard "@reduxjs/toolkit@npm:1.x.x || 2.x.x": - version: 2.11.0 - resolution: "@reduxjs/toolkit@npm:2.11.0" + version: 2.11.2 + resolution: "@reduxjs/toolkit@npm:2.11.2" dependencies: "@standard-schema/spec": "npm:^1.0.0" "@standard-schema/utils": "npm:^0.3.0" @@ -3992,7 +3818,7 @@ __metadata: optional: true react-redux: optional: true - checksum: 10c0/8dce17761d289ccdcc7599cabfe3a2f28eaa13e2971ced8f9bd3160af31d96ea11ddecfcbe9fe0c07b5a9fba592140a202d4581cb9aa1c486c5ba0e01316dfce + checksum: 10c0/4d388b96dc4b12a577af23607c252b3647c1b3b5136dbb0212e1dbbef9bb309e93d3ba6a95795ee165e87e4286453025cd67a98b5b3bb6d244b93ea487dd1ac0 languageName: node linkType: hard @@ -4055,156 +3881,156 @@ __metadata: languageName: node linkType: hard -"@rollup/rollup-android-arm-eabi@npm:4.53.3": - version: 4.53.3 - resolution: "@rollup/rollup-android-arm-eabi@npm:4.53.3" +"@rollup/rollup-android-arm-eabi@npm:4.54.0": + version: 4.54.0 + resolution: "@rollup/rollup-android-arm-eabi@npm:4.54.0" conditions: os=android & cpu=arm languageName: node linkType: hard -"@rollup/rollup-android-arm64@npm:4.53.3": - version: 4.53.3 - resolution: "@rollup/rollup-android-arm64@npm:4.53.3" +"@rollup/rollup-android-arm64@npm:4.54.0": + version: 4.54.0 + resolution: "@rollup/rollup-android-arm64@npm:4.54.0" conditions: os=android & cpu=arm64 languageName: node linkType: hard -"@rollup/rollup-darwin-arm64@npm:4.53.3": - version: 4.53.3 - resolution: "@rollup/rollup-darwin-arm64@npm:4.53.3" +"@rollup/rollup-darwin-arm64@npm:4.54.0": + version: 4.54.0 + resolution: "@rollup/rollup-darwin-arm64@npm:4.54.0" conditions: os=darwin & cpu=arm64 languageName: node linkType: hard -"@rollup/rollup-darwin-x64@npm:4.53.3": - version: 4.53.3 - resolution: "@rollup/rollup-darwin-x64@npm:4.53.3" +"@rollup/rollup-darwin-x64@npm:4.54.0": + version: 4.54.0 + resolution: "@rollup/rollup-darwin-x64@npm:4.54.0" conditions: os=darwin & cpu=x64 languageName: node linkType: hard -"@rollup/rollup-freebsd-arm64@npm:4.53.3": - version: 4.53.3 - resolution: "@rollup/rollup-freebsd-arm64@npm:4.53.3" +"@rollup/rollup-freebsd-arm64@npm:4.54.0": + version: 4.54.0 + resolution: "@rollup/rollup-freebsd-arm64@npm:4.54.0" conditions: os=freebsd & cpu=arm64 languageName: node linkType: hard -"@rollup/rollup-freebsd-x64@npm:4.53.3": - version: 4.53.3 - resolution: "@rollup/rollup-freebsd-x64@npm:4.53.3" +"@rollup/rollup-freebsd-x64@npm:4.54.0": + version: 4.54.0 + resolution: "@rollup/rollup-freebsd-x64@npm:4.54.0" conditions: os=freebsd & cpu=x64 languageName: node linkType: hard -"@rollup/rollup-linux-arm-gnueabihf@npm:4.53.3": - version: 4.53.3 - resolution: "@rollup/rollup-linux-arm-gnueabihf@npm:4.53.3" +"@rollup/rollup-linux-arm-gnueabihf@npm:4.54.0": + version: 4.54.0 + resolution: "@rollup/rollup-linux-arm-gnueabihf@npm:4.54.0" conditions: os=linux & cpu=arm & libc=glibc languageName: node linkType: hard -"@rollup/rollup-linux-arm-musleabihf@npm:4.53.3": - version: 4.53.3 - resolution: "@rollup/rollup-linux-arm-musleabihf@npm:4.53.3" +"@rollup/rollup-linux-arm-musleabihf@npm:4.54.0": + version: 4.54.0 + resolution: "@rollup/rollup-linux-arm-musleabihf@npm:4.54.0" conditions: os=linux & cpu=arm & libc=musl languageName: node linkType: hard -"@rollup/rollup-linux-arm64-gnu@npm:4.53.3": - version: 4.53.3 - resolution: "@rollup/rollup-linux-arm64-gnu@npm:4.53.3" +"@rollup/rollup-linux-arm64-gnu@npm:4.54.0": + version: 4.54.0 + resolution: "@rollup/rollup-linux-arm64-gnu@npm:4.54.0" conditions: os=linux & cpu=arm64 & libc=glibc languageName: node linkType: hard -"@rollup/rollup-linux-arm64-musl@npm:4.53.3": - version: 4.53.3 - resolution: "@rollup/rollup-linux-arm64-musl@npm:4.53.3" +"@rollup/rollup-linux-arm64-musl@npm:4.54.0": + version: 4.54.0 + resolution: "@rollup/rollup-linux-arm64-musl@npm:4.54.0" conditions: os=linux & cpu=arm64 & libc=musl languageName: node linkType: hard -"@rollup/rollup-linux-loong64-gnu@npm:4.53.3": - version: 4.53.3 - resolution: "@rollup/rollup-linux-loong64-gnu@npm:4.53.3" +"@rollup/rollup-linux-loong64-gnu@npm:4.54.0": + version: 4.54.0 + resolution: "@rollup/rollup-linux-loong64-gnu@npm:4.54.0" conditions: os=linux & cpu=loong64 & libc=glibc languageName: node linkType: hard -"@rollup/rollup-linux-ppc64-gnu@npm:4.53.3": - version: 4.53.3 - resolution: "@rollup/rollup-linux-ppc64-gnu@npm:4.53.3" +"@rollup/rollup-linux-ppc64-gnu@npm:4.54.0": + version: 4.54.0 + resolution: "@rollup/rollup-linux-ppc64-gnu@npm:4.54.0" conditions: os=linux & cpu=ppc64 & libc=glibc languageName: node linkType: hard -"@rollup/rollup-linux-riscv64-gnu@npm:4.53.3": - version: 4.53.3 - resolution: "@rollup/rollup-linux-riscv64-gnu@npm:4.53.3" +"@rollup/rollup-linux-riscv64-gnu@npm:4.54.0": + version: 4.54.0 + resolution: "@rollup/rollup-linux-riscv64-gnu@npm:4.54.0" conditions: os=linux & cpu=riscv64 & libc=glibc languageName: node linkType: hard -"@rollup/rollup-linux-riscv64-musl@npm:4.53.3": - version: 4.53.3 - resolution: "@rollup/rollup-linux-riscv64-musl@npm:4.53.3" +"@rollup/rollup-linux-riscv64-musl@npm:4.54.0": + version: 4.54.0 + resolution: "@rollup/rollup-linux-riscv64-musl@npm:4.54.0" conditions: os=linux & cpu=riscv64 & libc=musl languageName: node linkType: hard -"@rollup/rollup-linux-s390x-gnu@npm:4.53.3": - version: 4.53.3 - resolution: "@rollup/rollup-linux-s390x-gnu@npm:4.53.3" +"@rollup/rollup-linux-s390x-gnu@npm:4.54.0": + version: 4.54.0 + resolution: "@rollup/rollup-linux-s390x-gnu@npm:4.54.0" conditions: os=linux & cpu=s390x & libc=glibc languageName: node linkType: hard -"@rollup/rollup-linux-x64-gnu@npm:4.53.3": - version: 4.53.3 - resolution: "@rollup/rollup-linux-x64-gnu@npm:4.53.3" +"@rollup/rollup-linux-x64-gnu@npm:4.54.0": + version: 4.54.0 + resolution: "@rollup/rollup-linux-x64-gnu@npm:4.54.0" conditions: os=linux & cpu=x64 & libc=glibc languageName: node linkType: hard -"@rollup/rollup-linux-x64-musl@npm:4.53.3": - version: 4.53.3 - resolution: "@rollup/rollup-linux-x64-musl@npm:4.53.3" +"@rollup/rollup-linux-x64-musl@npm:4.54.0": + version: 4.54.0 + resolution: "@rollup/rollup-linux-x64-musl@npm:4.54.0" conditions: os=linux & cpu=x64 & libc=musl languageName: node linkType: hard -"@rollup/rollup-openharmony-arm64@npm:4.53.3": - version: 4.53.3 - resolution: "@rollup/rollup-openharmony-arm64@npm:4.53.3" +"@rollup/rollup-openharmony-arm64@npm:4.54.0": + version: 4.54.0 + resolution: "@rollup/rollup-openharmony-arm64@npm:4.54.0" conditions: os=openharmony & cpu=arm64 languageName: node linkType: hard -"@rollup/rollup-win32-arm64-msvc@npm:4.53.3": - version: 4.53.3 - resolution: "@rollup/rollup-win32-arm64-msvc@npm:4.53.3" +"@rollup/rollup-win32-arm64-msvc@npm:4.54.0": + version: 4.54.0 + resolution: "@rollup/rollup-win32-arm64-msvc@npm:4.54.0" conditions: os=win32 & cpu=arm64 languageName: node linkType: hard -"@rollup/rollup-win32-ia32-msvc@npm:4.53.3": - version: 4.53.3 - resolution: "@rollup/rollup-win32-ia32-msvc@npm:4.53.3" +"@rollup/rollup-win32-ia32-msvc@npm:4.54.0": + version: 4.54.0 + resolution: "@rollup/rollup-win32-ia32-msvc@npm:4.54.0" conditions: os=win32 & cpu=ia32 languageName: node linkType: hard -"@rollup/rollup-win32-x64-gnu@npm:4.53.3": - version: 4.53.3 - resolution: "@rollup/rollup-win32-x64-gnu@npm:4.53.3" +"@rollup/rollup-win32-x64-gnu@npm:4.54.0": + version: 4.54.0 + resolution: "@rollup/rollup-win32-x64-gnu@npm:4.54.0" conditions: os=win32 & cpu=x64 languageName: node linkType: hard -"@rollup/rollup-win32-x64-msvc@npm:4.53.3": - version: 4.53.3 - resolution: "@rollup/rollup-win32-x64-msvc@npm:4.53.3" +"@rollup/rollup-win32-x64-msvc@npm:4.54.0": + version: 4.54.0 + resolution: "@rollup/rollup-win32-x64-msvc@npm:4.54.0" conditions: os=win32 & cpu=x64 languageName: node linkType: hard @@ -4217,9 +4043,9 @@ __metadata: linkType: hard "@standard-schema/spec@npm:^1.0.0": - version: 1.0.0 - resolution: "@standard-schema/spec@npm:1.0.0" - checksum: 10c0/a1ab9a8bdc09b5b47aa8365d0e0ec40cc2df6437be02853696a0e377321653b0d3ac6f079a8c67d5ddbe9821025584b1fb71d9cc041a6666a96f1fadf2ece15f + version: 1.1.0 + resolution: "@standard-schema/spec@npm:1.1.0" + checksum: 10c0/d90f55acde4b2deb983529c87e8025fa693de1a5e8b49ecc6eb84d1fd96328add0e03d7d551442156c7432fd78165b2c26ff561b970a9a881f046abb78d6a526 languageName: node linkType: hard @@ -4291,30 +4117,30 @@ __metadata: linkType: hard "@swc/helpers@npm:^0.5.0": - version: 0.5.17 - resolution: "@swc/helpers@npm:0.5.17" + version: 0.5.18 + resolution: "@swc/helpers@npm:0.5.18" dependencies: tslib: "npm:^2.8.0" - checksum: 10c0/fe1f33ebb968558c5a0c595e54f2e479e4609bff844f9ca9a2d1ffd8dd8504c26f862a11b031f48f75c95b0381c2966c3dd156e25942f90089badd24341e7dbb + checksum: 10c0/cb32d72e32f775c30287bffbcf61c89ea3a963608cb3a4a675a3f9af545b8b3ab0bc9930432a5520a7307daaa87538158e253584ae1cf39f3e7e6e83408a2d51 languageName: node linkType: hard "@tanstack/react-virtual@npm:^3.13.9": - version: 3.13.12 - resolution: "@tanstack/react-virtual@npm:3.13.12" + version: 3.13.14 + resolution: "@tanstack/react-virtual@npm:3.13.14" dependencies: - "@tanstack/virtual-core": "npm:3.13.12" + "@tanstack/virtual-core": "npm:3.13.14" peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - checksum: 10c0/0eda3d5691ec3bf93a1cdaa955f4972c7aa9a5026179622824bb52ff8c47e59ee4634208e52d77f43ffb3ce435ee39a0899d6a81f6316918ce89d68122490371 + checksum: 10c0/dc861a1c2ee29370ec8123e317eee9d3fe086951568d6ea629d00c3ad967076270137599419c4c60027ab771a900d0b349bb3079688f1c99bf3799a907105eac languageName: node linkType: hard -"@tanstack/virtual-core@npm:3.13.12": - version: 3.13.12 - resolution: "@tanstack/virtual-core@npm:3.13.12" - checksum: 10c0/483f38761b73db05c181c10181f0781c1051be3350ae5c378e65057e5f1fdd6606e06e17dbaad8a5e36c04b208ea1a1344cacd4eca0dcde60f335cf398e4d698 +"@tanstack/virtual-core@npm:3.13.14": + version: 3.13.14 + resolution: "@tanstack/virtual-core@npm:3.13.14" + checksum: 10c0/1c26733c2dd2ec153d81357cd842f5c3d43e69ad791c12f680b617ca17591b8a7bc5c00a7b97a42ce6cae987a6b5a0d156136bfe44424431dfa88e69661a1d5b languageName: node linkType: hard @@ -4904,11 +4730,11 @@ __metadata: linkType: hard "@types/node@npm:*, @types/node@npm:>=13.7.0": - version: 24.10.1 - resolution: "@types/node@npm:24.10.1" + version: 25.0.3 + resolution: "@types/node@npm:25.0.3" dependencies: undici-types: "npm:~7.16.0" - checksum: 10c0/d6bca7a78f550fbb376f236f92b405d676003a8a09a1b411f55920ef34286ee3ee51f566203920e835478784df52662b5b2af89159d9d319352e9ea21801c002 + checksum: 10c0/b7568f0d765d9469621615e2bb257c7fd1953d95e9acbdb58dffb6627a2c4150d405a4600aa1ad8a40182a94fe5f903cafd3c0a2f5132814debd0e3bfd61f835 languageName: node linkType: hard @@ -5198,20 +5024,13 @@ __metadata: languageName: node linkType: hard -"@typescript-eslint/types@npm:8.51.0, @typescript-eslint/types@npm:^8.51.0": +"@typescript-eslint/types@npm:8.51.0, @typescript-eslint/types@npm:^8.47.0, @typescript-eslint/types@npm:^8.51.0": version: 8.51.0 resolution: "@typescript-eslint/types@npm:8.51.0" checksum: 10c0/eb3473d0bb71eb886438f35887b620ffadae7853b281752a40c73158aee644d136adeb82549be7d7c30f346fe888b2e979dff7e30e67b35377e8281018034529 languageName: node linkType: hard -"@typescript-eslint/types@npm:^8.47.0": - version: 8.48.1 - resolution: "@typescript-eslint/types@npm:8.48.1" - checksum: 10c0/366b8140f4c69319f1796b66b33c0c6e16eb6cbe543b9517003104e12ed143b620c1433ccf60d781a629d9433bd509a363c0c9d21fd438c17bb8840733af6caa - languageName: node - linkType: hard - "@typescript-eslint/typescript-estree@npm:8.51.0": version: 8.51.0 resolution: "@typescript-eslint/typescript-estree@npm:8.51.0" @@ -6102,13 +5921,13 @@ __metadata: linkType: hard "ast-v8-to-istanbul@npm:^0.3.8": - version: 0.3.8 - resolution: "ast-v8-to-istanbul@npm:0.3.8" + version: 0.3.10 + resolution: "ast-v8-to-istanbul@npm:0.3.10" dependencies: "@jridgewell/trace-mapping": "npm:^0.3.31" estree-walker: "npm:^3.0.3" js-tokens: "npm:^9.0.1" - checksum: 10c0/6f7d74fc36011699af6d4ad88ecd8efc7d74bd90b8e8dbb1c69d43c8f4bec0ed361fb62a5b5bd98bbee02ee87c62cd8bcc25a39634964e45476bf5489dfa327f + checksum: 10c0/8a7a07c04f8f130b8a5abb76cdb31cce06a8eb4b7d4abbe207bc721132127ae332e857b96aa415ac43ec2c6c9312508210c598f61a7de2d0e3db5615e6b03183 languageName: node linkType: hard @@ -6226,11 +6045,11 @@ __metadata: linkType: hard "baseline-browser-mapping@npm:^2.9.0": - version: 2.9.2 - resolution: "baseline-browser-mapping@npm:2.9.2" + version: 2.9.11 + resolution: "baseline-browser-mapping@npm:2.9.11" bin: baseline-browser-mapping: dist/cli.js - checksum: 10c0/4f9be09e20261ed26f19e9b95454dcb8d8371b87983c57cd9f70b9572e9b3053577f0d8d6d91297bdb605337747680686e22f62522a6e57ae2488fcacf641188 + checksum: 10c0/eba49fcc1b33ab994aeeb73a4848f2670e06a0886dd5b903689ae6f60d47e7f1bea9262dbb2548c48179e858f7eda2b82ddf941ae783b862f4dcc51085a246f2 languageName: node linkType: hard @@ -6462,9 +6281,9 @@ __metadata: linkType: hard "caniuse-lite@npm:^1.0.30001759": - version: 1.0.30001759 - resolution: "caniuse-lite@npm:1.0.30001759" - checksum: 10c0/b0f415960ba34995cda18e0d25c4e602f6917b9179290a76bdd0311423505b78cc93e558a90c98a22a1cc6b1781ab720ef6beea24ec7e29a1c1164ca72eac3a2 + version: 1.0.30001762 + resolution: "caniuse-lite@npm:1.0.30001762" + checksum: 10c0/93707eac5b0240af3f2ce6e2d7ab504a6fefcf9c2f9cd8fb9d488e496a333c61e557dab0472c1b00c17bc386a5dbb792aa4c778cda2d768e17f986617d7aec53 languageName: node linkType: hard @@ -6485,9 +6304,9 @@ __metadata: linkType: hard "chai@npm:^6.2.1": - version: 6.2.1 - resolution: "chai@npm:6.2.1" - checksum: 10c0/0c2d84392d7c6d44ca5d14d94204f1760e22af68b83d1f4278b5c4d301dabfc0242da70954dd86b1eda01e438f42950de6cf9d569df2103678538e4014abe50b + version: 6.2.2 + resolution: "chai@npm:6.2.2" + checksum: 10c0/e6c69e5f0c11dffe6ea13d0290936ebb68fcc1ad688b8e952e131df6a6d5797d5e860bc55cef1aca2e950c3e1f96daf79e9d5a70fb7dbaab4e46355e2635ed53 languageName: node linkType: hard @@ -7034,13 +6853,14 @@ __metadata: linkType: hard "cssstyle@npm:^5.3.4": - version: 5.3.4 - resolution: "cssstyle@npm:5.3.4" + version: 5.3.6 + resolution: "cssstyle@npm:5.3.6" dependencies: - "@asamuzakjp/css-color": "npm:^4.1.0" - "@csstools/css-syntax-patches-for-csstree": "npm:1.0.14" + "@asamuzakjp/css-color": "npm:^4.1.1" + "@csstools/css-syntax-patches-for-csstree": "npm:^1.0.21" css-tree: "npm:^3.1.0" - checksum: 10c0/7499ea8cbc2f759ded275428e0811d147baa6a964a44577711cee5edabee2230cf76b6bd20a556603f99ebc6fff80afdcba6c00bcbb1d41ae50cd09cd9fe9a2d + lru-cache: "npm:^11.2.4" + checksum: 10c0/246aba502819f42817fb88ba3d69e0c52b6fd8874779721ef39dc279ea87c96431f2c322f306bd4656d2c7a2d3b3b42ca00c7f223718c0f58c1b2a1f0ed4e89f languageName: node linkType: hard @@ -7462,7 +7282,7 @@ __metadata: languageName: node linkType: hard -"debug@npm:4, debug@npm:^4.0.0, debug@npm:^4.1.0, debug@npm:^4.1.1, debug@npm:^4.3.1, debug@npm:^4.3.2, debug@npm:^4.3.4, debug@npm:^4.3.5, debug@npm:^4.3.6, debug@npm:^4.4.0, debug@npm:^4.4.1, debug@npm:^4.4.3": +"debug@npm:4, debug@npm:^4.0.0, debug@npm:^4.1.0, debug@npm:^4.1.1, debug@npm:^4.3.1, debug@npm:^4.3.2, debug@npm:^4.3.4, debug@npm:^4.3.6, debug@npm:^4.4.0, debug@npm:^4.4.1, debug@npm:^4.4.3": version: 4.4.3 resolution: "debug@npm:4.4.3" dependencies: @@ -7800,9 +7620,9 @@ __metadata: linkType: hard "electron-to-chromium@npm:^1.5.263": - version: 1.5.265 - resolution: "electron-to-chromium@npm:1.5.265" - checksum: 10c0/f0409d15b02b61df5004d2bf6db0a2a01f42273fb46944c243d13139c83e8a07ad556656fd5f09758ae9b612ebf9862535afc6bffd47395890d5df7551274a4b + version: 1.5.267 + resolution: "electron-to-chromium@npm:1.5.267" + checksum: 10c0/0732bdb891b657f2e43266a3db8cf86fff6cecdcc8d693a92beff214e136cb5c2ee7dc5945ed75fa1db16e16bad0c38695527a020d15f39e79084e0b2e447621 languageName: node linkType: hard @@ -7899,9 +7719,9 @@ __metadata: languageName: node linkType: hard -"es-abstract@npm:^1.17.5, es-abstract@npm:^1.23.2, es-abstract@npm:^1.23.3, es-abstract@npm:^1.23.5, es-abstract@npm:^1.23.6, es-abstract@npm:^1.23.9, es-abstract@npm:^1.24.0": - version: 1.24.0 - resolution: "es-abstract@npm:1.24.0" +"es-abstract@npm:^1.17.5, es-abstract@npm:^1.23.2, es-abstract@npm:^1.23.3, es-abstract@npm:^1.23.5, es-abstract@npm:^1.23.6, es-abstract@npm:^1.23.9, es-abstract@npm:^1.24.0, es-abstract@npm:^1.24.1": + version: 1.24.1 + resolution: "es-abstract@npm:1.24.1" dependencies: array-buffer-byte-length: "npm:^1.0.2" arraybuffer.prototype.slice: "npm:^1.0.4" @@ -7957,7 +7777,7 @@ __metadata: typed-array-length: "npm:^1.0.7" unbox-primitive: "npm:^1.1.0" which-typed-array: "npm:^1.1.19" - checksum: 10c0/b256e897be32df5d382786ce8cce29a1dd8c97efbab77a26609bd70f2ed29fbcfc7a31758cb07488d532e7ccccdfca76c1118f2afe5a424cdc05ca007867c318 + checksum: 10c0/fca062ef8b5daacf743732167d319a212d45cb655b0bb540821d38d715416ae15b04b84fc86da9e2c89135aa7b337337b6c867f84dcde698d75d55688d5d765c languageName: node linkType: hard @@ -7976,26 +7796,26 @@ __metadata: linkType: hard "es-iterator-helpers@npm:^1.2.1": - version: 1.2.1 - resolution: "es-iterator-helpers@npm:1.2.1" + version: 1.2.2 + resolution: "es-iterator-helpers@npm:1.2.2" dependencies: call-bind: "npm:^1.0.8" - call-bound: "npm:^1.0.3" + call-bound: "npm:^1.0.4" define-properties: "npm:^1.2.1" - es-abstract: "npm:^1.23.6" + es-abstract: "npm:^1.24.1" es-errors: "npm:^1.3.0" - es-set-tostringtag: "npm:^2.0.3" + es-set-tostringtag: "npm:^2.1.0" function-bind: "npm:^1.1.2" - get-intrinsic: "npm:^1.2.6" + get-intrinsic: "npm:^1.3.0" globalthis: "npm:^1.0.4" gopd: "npm:^1.2.0" has-property-descriptors: "npm:^1.0.2" has-proto: "npm:^1.2.0" has-symbols: "npm:^1.1.0" internal-slot: "npm:^1.1.0" - iterator.prototype: "npm:^1.1.4" + iterator.prototype: "npm:^1.1.5" safe-array-concat: "npm:^1.1.3" - checksum: 10c0/97e3125ca472d82d8aceea11b790397648b52c26d8768ea1c1ee6309ef45a8755bb63225a43f3150c7591cffc17caf5752459f1e70d583b4184370a8f04ebd2f + checksum: 10c0/1ced8abf845a45e660dd77b5f3a64358421df70e4a0bd1897d5ddfefffed8409a6a2ca21241b9367e639df9eca74abc1c678b3020bffe6bee1f1826393658ddb languageName: node linkType: hard @@ -8022,7 +7842,7 @@ __metadata: languageName: node linkType: hard -"es-set-tostringtag@npm:^2.0.3, es-set-tostringtag@npm:^2.1.0": +"es-set-tostringtag@npm:^2.1.0": version: 2.1.0 resolution: "es-set-tostringtag@npm:2.1.0" dependencies: @@ -8055,14 +7875,14 @@ __metadata: linkType: hard "es-toolkit@npm:^1.39.3": - version: 1.42.0 - resolution: "es-toolkit@npm:1.42.0" + version: 1.43.0 + resolution: "es-toolkit@npm:1.43.0" dependenciesMeta: "@trivago/prettier-plugin-sort-imports@4.3.0": unplugged: true prettier-plugin-sort-re-exports@0.0.1: unplugged: true - checksum: 10c0/ee577b23336296116be423a5d01a6af827c80a10971507ae26cdb146b60ce0a930bf7bdb719ac0dc4f962ec74542a2423b934e24eb47efe1bc911862b12da109 + checksum: 10c0/bbff0b591fd01be9f37a34dad7964b590e4952fc594c1230140771687f05136caa6ab21962a6e9cde7c4b529a149171ed5179d6379d4a8e656dbf7e8d126999c languageName: node linkType: hard @@ -8155,95 +7975,6 @@ __metadata: languageName: node linkType: hard -"esbuild@npm:^0.25.0": - version: 0.25.12 - resolution: "esbuild@npm:0.25.12" - dependencies: - "@esbuild/aix-ppc64": "npm:0.25.12" - "@esbuild/android-arm": "npm:0.25.12" - "@esbuild/android-arm64": "npm:0.25.12" - "@esbuild/android-x64": "npm:0.25.12" - "@esbuild/darwin-arm64": "npm:0.25.12" - "@esbuild/darwin-x64": "npm:0.25.12" - "@esbuild/freebsd-arm64": "npm:0.25.12" - "@esbuild/freebsd-x64": "npm:0.25.12" - "@esbuild/linux-arm": "npm:0.25.12" - "@esbuild/linux-arm64": "npm:0.25.12" - "@esbuild/linux-ia32": "npm:0.25.12" - "@esbuild/linux-loong64": "npm:0.25.12" - "@esbuild/linux-mips64el": "npm:0.25.12" - "@esbuild/linux-ppc64": "npm:0.25.12" - "@esbuild/linux-riscv64": "npm:0.25.12" - "@esbuild/linux-s390x": "npm:0.25.12" - "@esbuild/linux-x64": "npm:0.25.12" - "@esbuild/netbsd-arm64": "npm:0.25.12" - "@esbuild/netbsd-x64": "npm:0.25.12" - "@esbuild/openbsd-arm64": "npm:0.25.12" - "@esbuild/openbsd-x64": "npm:0.25.12" - "@esbuild/openharmony-arm64": "npm:0.25.12" - "@esbuild/sunos-x64": "npm:0.25.12" - "@esbuild/win32-arm64": "npm:0.25.12" - "@esbuild/win32-ia32": "npm:0.25.12" - "@esbuild/win32-x64": "npm:0.25.12" - dependenciesMeta: - "@esbuild/aix-ppc64": - optional: true - "@esbuild/android-arm": - optional: true - "@esbuild/android-arm64": - optional: true - "@esbuild/android-x64": - optional: true - "@esbuild/darwin-arm64": - optional: true - "@esbuild/darwin-x64": - optional: true - "@esbuild/freebsd-arm64": - optional: true - "@esbuild/freebsd-x64": - optional: true - "@esbuild/linux-arm": - optional: true - "@esbuild/linux-arm64": - optional: true - "@esbuild/linux-ia32": - optional: true - "@esbuild/linux-loong64": - optional: true - "@esbuild/linux-mips64el": - optional: true - "@esbuild/linux-ppc64": - optional: true - "@esbuild/linux-riscv64": - optional: true - "@esbuild/linux-s390x": - optional: true - "@esbuild/linux-x64": - optional: true - "@esbuild/netbsd-arm64": - optional: true - "@esbuild/netbsd-x64": - optional: true - "@esbuild/openbsd-arm64": - optional: true - "@esbuild/openbsd-x64": - optional: true - "@esbuild/openharmony-arm64": - optional: true - "@esbuild/sunos-x64": - optional: true - "@esbuild/win32-arm64": - optional: true - "@esbuild/win32-ia32": - optional: true - "@esbuild/win32-x64": - optional: true - bin: - esbuild: bin/esbuild - checksum: 10c0/c205357531423220a9de8e1e6c6514242bc9b1666e762cd67ccdf8fdfdc3f1d0bd76f8d9383958b97ad4c953efdb7b6e8c1f9ca5951cd2b7c5235e8755b34a6b - languageName: node - linkType: hard - "escalade@npm:^3.1.1, escalade@npm:^3.2.0": version: 3.2.0 resolution: "escalade@npm:3.2.0" @@ -8517,11 +8248,11 @@ __metadata: linkType: hard "esquery@npm:^1.5.0": - version: 1.6.0 - resolution: "esquery@npm:1.6.0" + version: 1.7.0 + resolution: "esquery@npm:1.7.0" dependencies: estraverse: "npm:^5.1.0" - checksum: 10c0/cb9065ec605f9da7a76ca6dadb0619dfb611e37a81e318732977d90fab50a256b95fee2d925fba7c2f3f0523aa16f91587246693bc09bc34d5a59575fe6e93d2 + checksum: 10c0/77d5173db450b66f3bc685d11af4c90cffeedb340f34a39af96d43509a335ce39c894fd79233df32d38f5e4e219fa0f7076f6ec90bae8320170ba082c0db4793 languageName: node linkType: hard @@ -8600,9 +8331,9 @@ __metadata: linkType: hard "expect-type@npm:^1.2.2": - version: 1.2.2 - resolution: "expect-type@npm:1.2.2" - checksum: 10c0/6019019566063bbc7a690d9281d920b1a91284a4a093c2d55d71ffade5ac890cf37a51e1da4602546c4b56569d2ad2fc175a2ccee77d1ae06cb3af91ef84f44b + version: 1.3.0 + resolution: "expect-type@npm:1.3.0" + checksum: 10c0/8412b3fe4f392c420ab41dae220b09700e4e47c639a29ba7ba2e83cc6cffd2b4926f7ac9e47d7e277e8f4f02acda76fd6931cb81fd2b382fa9477ef9ada953fd languageName: node linkType: hard @@ -8990,8 +8721,8 @@ __metadata: linkType: hard "framer-motion@npm:^12": - version: 12.23.25 - resolution: "framer-motion@npm:12.23.25" + version: 12.23.26 + resolution: "framer-motion@npm:12.23.26" dependencies: motion-dom: "npm:^12.23.23" motion-utils: "npm:^12.23.6" @@ -9007,7 +8738,7 @@ __metadata: optional: true react-dom: optional: true - checksum: 10c0/cfcc2da1f3ffe50abe6f657e08b61912acbafffc0a0652d2518c1928e1e23a01a580a2e7feeb9234d7c8028e573a9896519b38fa0f570390b2e4624bafdf4063 + checksum: 10c0/7dbbc4a392b969804e04a056e3d89a55bf31572dc7f6cd79050b90616fbb84a1762e4ac4e2537c735d347bbf4ceebea126f5c090234149b12cffa3ea6c518a34 languageName: node linkType: hard @@ -9707,7 +9438,7 @@ __metadata: languageName: node linkType: hard -"http-errors@npm:^2.0.0, http-errors@npm:~2.0.1": +"http-errors@npm:^2.0.0, http-errors@npm:^2.0.1, http-errors@npm:~2.0.1": version: 2.0.1 resolution: "http-errors@npm:2.0.1" dependencies: @@ -9833,16 +9564,7 @@ __metadata: languageName: node linkType: hard -"iconv-lite@npm:^0.7.0, iconv-lite@npm:~0.7.0": - version: 0.7.0 - resolution: "iconv-lite@npm:0.7.0" - dependencies: - safer-buffer: "npm:>= 2.1.2 < 3.0.0" - checksum: 10c0/2382400469071c55b6746c531eed5fa4d033e5db6690b7331fb2a5f59a30d7a9782932e92253db26df33c1cf46fa200a3fbe524a2a7c62037c762283f188ec2f - languageName: node - linkType: hard - -"iconv-lite@npm:^0.7.1": +"iconv-lite@npm:^0.7.0, iconv-lite@npm:^0.7.1, iconv-lite@npm:~0.7.0": version: 0.7.1 resolution: "iconv-lite@npm:0.7.1" dependencies: @@ -9880,9 +9602,9 @@ __metadata: linkType: hard "immer@npm:^11.0.0": - version: 11.0.1 - resolution: "immer@npm:11.0.1" - checksum: 10c0/d505c1aa6d5d7ff03196e0a508af2853839eb1a3d5af51d55ca5815c46981b74765faf357fbd276b151109b07dd6b564077be92b0247b0a4effa78130166030d + version: 11.1.3 + resolution: "immer@npm:11.1.3" + checksum: 10c0/2d0bcb7946274bb99254b8129026da9579591569afdd4f29e5ef3ebf231375c7ab97c344ffbfc9eeabea27a2145623fca1287c17e3ebd007c8cdcfd82954eeb7 languageName: node linkType: hard @@ -10457,7 +10179,7 @@ __metadata: languageName: node linkType: hard -"iterator.prototype@npm:^1.1.4": +"iterator.prototype@npm:^1.1.5": version: 1.1.5 resolution: "iterator.prototype@npm:1.1.5" dependencies: @@ -10977,7 +10699,14 @@ __metadata: languageName: node linkType: hard -"lodash-es@npm:4, lodash-es@npm:4.17.21, lodash-es@npm:^4.17.15, lodash-es@npm:^4.17.21": +"lodash-es@npm:4, lodash-es@npm:^4.17.15, lodash-es@npm:^4.17.21": + version: 4.17.22 + resolution: "lodash-es@npm:4.17.22" + checksum: 10c0/5f28a262183cca43e08c580622557f393cb889386df2d8adf7c852bfdff7a84c5e629df5aa6c5c6274e83b38172f239d3e4e72e1ad27352d9ae9766627338089 + languageName: node + linkType: hard + +"lodash-es@npm:4.17.21": version: 4.17.21 resolution: "lodash-es@npm:4.17.21" checksum: 10c0/fb407355f7e6cd523a9383e76e6b455321f0f153a6c9625e21a8827d10c54c2a2341bd2ae8d034358b60e07325e1330c14c224ff582d04612a46a4f0479ff2f2 @@ -11061,7 +10790,7 @@ __metadata: languageName: node linkType: hard -"lru-cache@npm:^11.0.0, lru-cache@npm:^11.1.0, lru-cache@npm:^11.2.1, lru-cache@npm:^11.2.2, lru-cache@npm:^11.2.4": +"lru-cache@npm:^11.0.0, lru-cache@npm:^11.1.0, lru-cache@npm:^11.2.1, lru-cache@npm:^11.2.4": version: 11.2.4 resolution: "lru-cache@npm:11.2.4" checksum: 10c0/4a24f9b17537619f9144d7b8e42cd5a225efdfd7076ebe7b5e7dc02b860a818455201e67fbf000765233fe7e339d3c8229fc815e9b58ee6ede511e07608c19b2 @@ -11882,7 +11611,7 @@ __metadata: languageName: node linkType: hard -"mime-types@npm:^3.0.0, mime-types@npm:^3.0.1": +"mime-types@npm:^3.0.0, mime-types@npm:^3.0.1, mime-types@npm:^3.0.2": version: 3.0.2 resolution: "mime-types@npm:3.0.2" dependencies: @@ -12264,9 +11993,9 @@ __metadata: linkType: hard "nodemailer@npm:^7.0.6": - version: 7.0.11 - resolution: "nodemailer@npm:7.0.11" - checksum: 10c0/208f108fdb4c5dd0e3a2f013578d53dad505cf1b9c7a084f6d22fc9d6f3912daafb4a23793ca568ff848afc35f15f4eb24382d3f6f9fb8ede4a8410d4ca63618 + version: 7.0.12 + resolution: "nodemailer@npm:7.0.12" + checksum: 10c0/b03948744423386b5fd7cb5bdf60797f2f71c4d3db202eedf7e0037429b5887cb90b641e7ca244af3b96e0b76949e3135cba96336fb0d3ade00ae33727ca8cf8 languageName: node linkType: hard @@ -12960,9 +12689,9 @@ __metadata: linkType: hard "preact@npm:10": - version: 10.28.0 - resolution: "preact@npm:10.28.0" - checksum: 10c0/039c5077ccce61d6a781d5f63df24c5c7205270abe52464708852b8e8d7fc926cdd07fc4a116af282e5a1e41b62e8a4065a5999e0e2db521faad6a1c033a8b1c + version: 10.28.1 + resolution: "preact@npm:10.28.1" + checksum: 10c0/864aa7550f0a4b9f4f944a1e59f46f788b987d24bf5a532afefdb41e437c15fe80e933b7e8561980b8abe16565e39a224ec4b743382792bee0e1854d12e5771c languageName: node linkType: hard @@ -13116,11 +12845,11 @@ __metadata: linkType: hard "qs@npm:^6.14.0": - version: 6.14.0 - resolution: "qs@npm:6.14.0" + version: 6.14.1 + resolution: "qs@npm:6.14.1" dependencies: side-channel: "npm:^1.1.0" - checksum: 10c0/8ea5d91bf34f440598ee389d4a7d95820e3b837d3fd9f433871f7924801becaa0cd3b3b4628d49a7784d06a8aea9bc4554d2b6d8d584e2d221dc06238a42909c + checksum: 10c0/0e3b22dc451f48ce5940cbbc7c7d9068d895074f8c969c0801ac15c1313d1859c4d738e46dc4da2f498f41a9ffd8c201bd9fb12df67799b827db94cc373d2613 languageName: node linkType: hard @@ -13248,7 +12977,7 @@ __metadata: languageName: node linkType: hard -"react-draggable@npm:4.5.0, react-draggable@npm:^4.0.3, react-draggable@npm:^4.4.6": +"react-draggable@npm:4.5.0, react-draggable@npm:^4.4.6, react-draggable@npm:^4.5.0": version: 4.5.0 resolution: "react-draggable@npm:4.5.0" dependencies: @@ -13355,9 +13084,9 @@ __metadata: linkType: hard "react-is@npm:^19.0.0, react-is@npm:^19.2.0": - version: 19.2.1 - resolution: "react-is@npm:19.2.1" - checksum: 10c0/0ebeaedb4ff615055cbcd758c7e22ba9644e21110adbd293dd1aada3591abf7399152a786cd120e324c10706d75e28c2130c27d1b9b5ae637aef4c52f4d17a91 + version: 19.2.3 + resolution: "react-is@npm:19.2.3" + checksum: 10c0/2b54c422c21b8dbd68a435a1cce21ecd5b6f06f48659531f7d53dd7368365da5a67e946f352fb2010d11ca40658aa67bec90995f0f1ec5556c0f71dbffe54994 languageName: node linkType: hard @@ -13586,14 +13315,15 @@ __metadata: linkType: hard "react-resizable@npm:^3.0.5": - version: 3.0.5 - resolution: "react-resizable@npm:3.0.5" + version: 3.1.3 + resolution: "react-resizable@npm:3.1.3" dependencies: prop-types: "npm:15.x" - react-draggable: "npm:^4.0.3" + react-draggable: "npm:^4.5.0" peerDependencies: react: ">= 16.3" - checksum: 10c0/cfe50aa6efb79e0aa09bd681a5beab2fcd1186737c4952eb4c3974ed9395d5d263ccd1130961d06b8f5e24c8f544dd2967b5c740ce68719962d1771de7bdb350 + react-dom: ">= 16.3" + checksum: 10c0/de0f4447369381a446c0ce5f5770e85ec3a522a540d8eaf11998ec2d0b37835f879eb58f1765a89269ead53018c1eabf10b293eae5cb41c41a89057f2741563d languageName: node linkType: hard @@ -14137,31 +13867,31 @@ __metadata: linkType: hard "rollup@npm:^4.43.0": - version: 4.53.3 - resolution: "rollup@npm:4.53.3" - dependencies: - "@rollup/rollup-android-arm-eabi": "npm:4.53.3" - "@rollup/rollup-android-arm64": "npm:4.53.3" - "@rollup/rollup-darwin-arm64": "npm:4.53.3" - "@rollup/rollup-darwin-x64": "npm:4.53.3" - "@rollup/rollup-freebsd-arm64": "npm:4.53.3" - "@rollup/rollup-freebsd-x64": "npm:4.53.3" - "@rollup/rollup-linux-arm-gnueabihf": "npm:4.53.3" - "@rollup/rollup-linux-arm-musleabihf": "npm:4.53.3" - "@rollup/rollup-linux-arm64-gnu": "npm:4.53.3" - "@rollup/rollup-linux-arm64-musl": "npm:4.53.3" - "@rollup/rollup-linux-loong64-gnu": "npm:4.53.3" - "@rollup/rollup-linux-ppc64-gnu": "npm:4.53.3" - "@rollup/rollup-linux-riscv64-gnu": "npm:4.53.3" - "@rollup/rollup-linux-riscv64-musl": "npm:4.53.3" - "@rollup/rollup-linux-s390x-gnu": "npm:4.53.3" - "@rollup/rollup-linux-x64-gnu": "npm:4.53.3" - "@rollup/rollup-linux-x64-musl": "npm:4.53.3" - "@rollup/rollup-openharmony-arm64": "npm:4.53.3" - "@rollup/rollup-win32-arm64-msvc": "npm:4.53.3" - "@rollup/rollup-win32-ia32-msvc": "npm:4.53.3" - "@rollup/rollup-win32-x64-gnu": "npm:4.53.3" - "@rollup/rollup-win32-x64-msvc": "npm:4.53.3" + version: 4.54.0 + resolution: "rollup@npm:4.54.0" + dependencies: + "@rollup/rollup-android-arm-eabi": "npm:4.54.0" + "@rollup/rollup-android-arm64": "npm:4.54.0" + "@rollup/rollup-darwin-arm64": "npm:4.54.0" + "@rollup/rollup-darwin-x64": "npm:4.54.0" + "@rollup/rollup-freebsd-arm64": "npm:4.54.0" + "@rollup/rollup-freebsd-x64": "npm:4.54.0" + "@rollup/rollup-linux-arm-gnueabihf": "npm:4.54.0" + "@rollup/rollup-linux-arm-musleabihf": "npm:4.54.0" + "@rollup/rollup-linux-arm64-gnu": "npm:4.54.0" + "@rollup/rollup-linux-arm64-musl": "npm:4.54.0" + "@rollup/rollup-linux-loong64-gnu": "npm:4.54.0" + "@rollup/rollup-linux-ppc64-gnu": "npm:4.54.0" + "@rollup/rollup-linux-riscv64-gnu": "npm:4.54.0" + "@rollup/rollup-linux-riscv64-musl": "npm:4.54.0" + "@rollup/rollup-linux-s390x-gnu": "npm:4.54.0" + "@rollup/rollup-linux-x64-gnu": "npm:4.54.0" + "@rollup/rollup-linux-x64-musl": "npm:4.54.0" + "@rollup/rollup-openharmony-arm64": "npm:4.54.0" + "@rollup/rollup-win32-arm64-msvc": "npm:4.54.0" + "@rollup/rollup-win32-ia32-msvc": "npm:4.54.0" + "@rollup/rollup-win32-x64-gnu": "npm:4.54.0" + "@rollup/rollup-win32-x64-msvc": "npm:4.54.0" "@types/estree": "npm:1.0.8" fsevents: "npm:~2.3.2" dependenciesMeta: @@ -14213,7 +13943,7 @@ __metadata: optional: true bin: rollup: dist/bin/rollup - checksum: 10c0/a21305aac72013083bd0dec92162b0f7f24cacf57c876ca601ec76e892895952c9ea592c1c07f23b8c125f7979c2b17f7fb565e386d03ee4c1f0952ac4ab0d75 + checksum: 10c0/62e5fd5d43e72751ac631f13fd7e70bec0fc3809231d5e087c3c0811945e7b8f0956620c5bed4e0cd67085325324266989e5ea4d22985c2677119ac7809b6455 languageName: node linkType: hard @@ -14348,21 +14078,21 @@ __metadata: linkType: hard "send@npm:^1.1.0, send@npm:^1.2.0": - version: 1.2.0 - resolution: "send@npm:1.2.0" + version: 1.2.1 + resolution: "send@npm:1.2.1" dependencies: - debug: "npm:^4.3.5" + debug: "npm:^4.4.3" encodeurl: "npm:^2.0.0" escape-html: "npm:^1.0.3" etag: "npm:^1.8.1" fresh: "npm:^2.0.0" - http-errors: "npm:^2.0.0" - mime-types: "npm:^3.0.1" + http-errors: "npm:^2.0.1" + mime-types: "npm:^3.0.2" ms: "npm:^2.1.3" on-finished: "npm:^2.4.1" range-parser: "npm:^1.2.1" - statuses: "npm:^2.0.1" - checksum: 10c0/531bcfb5616948d3468d95a1fd0adaeb0c20818ba4a500f439b800ca2117971489e02074ce32796fd64a6772ea3e7235fe0583d8241dbd37a053dc3378eff9a5 + statuses: "npm:^2.0.2" + checksum: 10c0/fbbbbdc902a913d65605274be23f3d604065cfc3ee3d78bf9fc8af1dc9fc82667c50d3d657f5e601ac657bac9b396b50ee97bd29cd55436320cf1cddebdcec72 languageName: node linkType: hard @@ -14392,14 +14122,14 @@ __metadata: linkType: hard "serve-static@npm:^2.2.0": - version: 2.2.0 - resolution: "serve-static@npm:2.2.0" + version: 2.2.1 + resolution: "serve-static@npm:2.2.1" dependencies: encodeurl: "npm:^2.0.0" escape-html: "npm:^1.0.3" parseurl: "npm:^1.3.3" send: "npm:^1.2.0" - checksum: 10c0/30e2ed1dbff1984836cfd0c65abf5d3f3f83bcd696c99d2d3c97edbd4e2a3ff4d3f87108a7d713640d290a7b6fe6c15ddcbc61165ab2eaad48ea8d3b52c7f913 + checksum: 10c0/37986096e8572e2dfaad35a3925fa8da0c0969f8814fd7788e84d4d388bc068cf0c06d1658509788e55bed942a6b6d040a8a267fa92bb9ffb1179f8bacde5fd7 languageName: node linkType: hard @@ -14744,7 +14474,7 @@ __metadata: languageName: node linkType: hard -"statuses@npm:^2.0.1, statuses@npm:~2.0.2": +"statuses@npm:^2.0.1, statuses@npm:^2.0.2, statuses@npm:~2.0.2": version: 2.0.2 resolution: "statuses@npm:2.0.2" checksum: 10c0/a9947d98ad60d01f6b26727570f3bcceb6c8fa789da64fe6889908fe2e294d57503b14bf2b5af7605c2d36647259e856635cd4c49eab41667658ec9d0080ec3f @@ -15030,9 +14760,9 @@ __metadata: linkType: hard "tabbable@npm:^6.0.0": - version: 6.3.0 - resolution: "tabbable@npm:6.3.0" - checksum: 10c0/57ba019d29b5cfa0c862248883bcec0e6d29d8f156ba52a1f425e7cfeca4a0fc701ab8d035c4c86ddf74ecdbd0e9f454a88d9b55d924a51f444038e9cd14d7a0 + version: 6.4.0 + resolution: "tabbable@npm:6.4.0" + checksum: 10c0/d931427f4a96b801fd8801ba296a702119e06f70ad262fed8abc5271225c9f1ca51b89fdec4fb2f22e1d35acb3d2881db0a17cedc758272e9ecb540d00299d76 languageName: node linkType: hard @@ -15149,9 +14879,9 @@ __metadata: linkType: hard "three@npm:>=0.118 <1": - version: 0.181.2 - resolution: "three@npm:0.181.2" - checksum: 10c0/b34b6240fbedebc7f8a9317c062f7ee5339de0e56250ba2ae3de52edb9c517dc8e9aaf6fe242e1bfff56808081e095db28be5ef1a05947e0c82aefada95c628b + version: 0.182.0 + resolution: "three@npm:0.182.0" + checksum: 10c0/8ddb3380dc881c75a91cb9fb29a3610b1d01de7feff4b7610fb305a24efc51c98d51b8bcef6ab74e82a1af1947bc81343a9eab1dc9ebe17b504eab19a321430b languageName: node linkType: hard @@ -15325,11 +15055,11 @@ __metadata: linkType: hard "ts-api-utils@npm:^2.2.0": - version: 2.3.0 - resolution: "ts-api-utils@npm:2.3.0" + version: 2.4.0 + resolution: "ts-api-utils@npm:2.4.0" peerDependencies: typescript: ">=4.8.4" - checksum: 10c0/9f2aadb8ac55926c79db03e37ee3b014135923d1705f6868b9e787e6b8822d2fd8e19df2f9002563f4e6268c994425ddaad61df24d0dad833a4be9f26f789213 + checksum: 10c0/ed185861aef4e7124366a3f6561113557a57504267d4d452a51e0ba516a9b6e713b56b4aeaab9fa13de9db9ab755c65c8c13a777dba9133c214632cb7b65c083 languageName: node linkType: hard @@ -15789,8 +15519,8 @@ __metadata: linkType: hard "update-browserslist-db@npm:^1.2.0": - version: 1.2.2 - resolution: "update-browserslist-db@npm:1.2.2" + version: 1.2.3 + resolution: "update-browserslist-db@npm:1.2.3" dependencies: escalade: "npm:^3.2.0" picocolors: "npm:^1.1.1" @@ -15798,7 +15528,7 @@ __metadata: browserslist: ">= 4.21.0" bin: update-browserslist-db: cli.js - checksum: 10c0/39c3ea08b397ffc8dc3a1c517f5c6ed5cc4179b5e185383dab9bf745879623c12062a2e6bf4f9427cc59389c7bfa0010e86858b923c1e349e32fdddd9b043bb2 + checksum: 10c0/13a00355ea822388f68af57410ce3255941d5fb9b7c49342c4709a07c9f230bbef7f7499ae0ca7e0de532e79a82cc0c4edbd125f1a323a1845bf914efddf8bec languageName: node linkType: hard @@ -16020,7 +15750,7 @@ __metadata: languageName: node linkType: hard -"vite@npm:7.3.0": +"vite@npm:7.3.0, vite@npm:^6.0.0 || ^7.0.0": version: 7.3.0 resolution: "vite@npm:7.3.0" dependencies: @@ -16075,61 +15805,6 @@ __metadata: languageName: node linkType: hard -"vite@npm:^6.0.0 || ^7.0.0": - version: 7.2.6 - resolution: "vite@npm:7.2.6" - dependencies: - esbuild: "npm:^0.25.0" - fdir: "npm:^6.5.0" - fsevents: "npm:~2.3.3" - picomatch: "npm:^4.0.3" - postcss: "npm:^8.5.6" - rollup: "npm:^4.43.0" - tinyglobby: "npm:^0.2.15" - peerDependencies: - "@types/node": ^20.19.0 || >=22.12.0 - jiti: ">=1.21.0" - less: ^4.0.0 - lightningcss: ^1.21.0 - sass: ^1.70.0 - sass-embedded: ^1.70.0 - stylus: ">=0.54.8" - sugarss: ^5.0.0 - terser: ^5.16.0 - tsx: ^4.8.1 - yaml: ^2.4.2 - dependenciesMeta: - fsevents: - optional: true - peerDependenciesMeta: - "@types/node": - optional: true - jiti: - optional: true - less: - optional: true - lightningcss: - optional: true - sass: - optional: true - sass-embedded: - optional: true - stylus: - optional: true - sugarss: - optional: true - terser: - optional: true - tsx: - optional: true - yaml: - optional: true - bin: - vite: bin/vite.js - checksum: 10c0/d444a159ab8f0f854d596d1938f201b449d59ed4d336e587be9dc89005467214d85848c212c2495f76a8421372ffe4d061d023d659600f1aaa3ba5ac13e804f7 - languageName: node - linkType: hard - "vitest@npm:4.0.16": version: 4.0.16 resolution: "vitest@npm:4.0.16" @@ -16215,12 +15890,12 @@ __metadata: linkType: hard "watchpack@npm:^2.4.4": - version: 2.4.4 - resolution: "watchpack@npm:2.4.4" + version: 2.5.0 + resolution: "watchpack@npm:2.5.0" dependencies: glob-to-regexp: "npm:^0.4.1" graceful-fs: "npm:^4.1.2" - checksum: 10c0/6c0901f75ce245d33991225af915eea1c5ae4ba087f3aee2b70dd377d4cacb34bef02a48daf109da9d59b2d31ec6463d924a0d72f8618ae1643dd07b95de5275 + checksum: 10c0/19944a2c05f8905b4b76dbbb317ae0efb18defa3eb7d3281caf1bb128c01302d00875a2fa0e48ec0242645d2e7e5c62c4efe8c60f9d30f176517f97dad1455f9 languageName: node linkType: hard @@ -16239,9 +15914,9 @@ __metadata: linkType: hard "webidl-conversions@npm:^8.0.0": - version: 8.0.0 - resolution: "webidl-conversions@npm:8.0.0" - checksum: 10c0/3244e8a6534163bc3ee5f5f48b507b4bb74e34e7cc7c86a50cd02734753042b88343dae48321f34ad61ddc6b5c90cb1a5b2ee757b8be8e6fadc587a9f3db76cd + version: 8.0.1 + resolution: "webidl-conversions@npm:8.0.1" + checksum: 10c0/3f6f327ca5fa0c065ed8ed0ef3b72f33623376e68f958e9b7bd0df49fdb0b908139ac2338d19fb45bd0e05595bda96cb6d1622222a8b413daa38a17aacc4dd46 languageName: node linkType: hard diff --git a/opencti-platform/opencti-graphql/src/utils/safeEjs.client.ts b/opencti-platform/opencti-graphql/src/utils/safeEjs.client.ts index 42e0bc6e7f9d..ef4de3a50add 100644 --- a/opencti-platform/opencti-graphql/src/utils/safeEjs.client.ts +++ b/opencti-platform/opencti-graphql/src/utils/safeEjs.client.ts @@ -80,8 +80,8 @@ export const safeRender = async (template: string, data: Data, options?: SafeRen } }); - worker.on('error', (error) => { - once(() => reject(new Error(`Worker error: ${error.message}`))); + worker.on('error', (error: Error) => { + once(() => reject(new Error(`Worker error: ${error.message}`, { cause: error }))); }); worker.on('exit', (code) => { diff --git a/opencti-platform/opencti-graphql/yarn.lock b/opencti-platform/opencti-graphql/yarn.lock index 589a6fa8779b..56730bd0cafd 100644 --- a/opencti-platform/opencti-graphql/yarn.lock +++ b/opencti-platform/opencti-graphql/yarn.lock @@ -895,12 +895,12 @@ __metadata: linkType: hard "@aws-sdk/types@npm:^3.222.0": - version: 3.936.0 - resolution: "@aws-sdk/types@npm:3.936.0" + version: 3.957.0 + resolution: "@aws-sdk/types@npm:3.957.0" dependencies: - "@smithy/types": "npm:^4.9.0" + "@smithy/types": "npm:^4.11.0" tslib: "npm:^2.6.2" - checksum: 10c0/6f7eeabd0ada675b3b8e969d512f7ce29602a1dd6af154e3d6977f0a6f03084ca3be9498d091142369636a7b7d9f1b22e58156c741d1d088c4939581848054bb + checksum: 10c0/8dd9826eff9806689a3321f58ffa51c7731599f0d30b22fb744e82bf0914a1e714b622d319ee00315d4a3d814b895efaff87ea1edf23f0f50c26d2b4de31fe7b languageName: node linkType: hard @@ -927,11 +927,11 @@ __metadata: linkType: hard "@aws-sdk/util-locate-window@npm:^3.0.0": - version: 3.893.0 - resolution: "@aws-sdk/util-locate-window@npm:3.893.0" + version: 3.957.0 + resolution: "@aws-sdk/util-locate-window@npm:3.957.0" dependencies: tslib: "npm:^2.6.2" - checksum: 10c0/ed2232d1eff567a7fa96bed87d56f03ac183dc20ba0ea262edb35f0b66aea201b987f447a5c383adc5694c80275700345946c0ad3183b30a6f9ec2f89be789d8 + checksum: 10c0/d641fe27859a5a9a087765bfe744da6f6d6b58e943faf1dc6c813e608353d398ba2ee5a793abaa32ccac38c311fd96ce6af9866d938df8c902d8e6dc1f6a2a5f languageName: node linkType: hard @@ -1190,10 +1190,10 @@ __metadata: languageName: node linkType: hard -"@borewit/text-codec@npm:^0.1.0": - version: 0.1.1 - resolution: "@borewit/text-codec@npm:0.1.1" - checksum: 10c0/c92606b355111053f9db47d485c8679cc09a5be0eb2738aad5b922d3744465f2fce47144ffb27d5106fa431d1d2e5a2e0140d0a22351dccf49693098702c0274 +"@borewit/text-codec@npm:^0.2.1": + version: 0.2.1 + resolution: "@borewit/text-codec@npm:0.2.1" + checksum: 10c0/aabd9c86497197aacc9ddb413857f112a98a9fd4be9ed56a24971a47bbec7c0d5d449efcad830f9895009c1a5914e5c448f972a0c968e97c4ebf99297dea7a6b languageName: node linkType: hard @@ -1262,8 +1262,8 @@ __metadata: linkType: hard "@elastic/transport@npm:^8.9.6": - version: 8.10.0 - resolution: "@elastic/transport@npm:8.10.0" + version: 8.10.1 + resolution: "@elastic/transport@npm:8.10.1" dependencies: "@opentelemetry/api": "npm:1.x" "@opentelemetry/core": "npm:2.x" @@ -1273,7 +1273,7 @@ __metadata: secure-json-parse: "npm:^3.0.1" tslib: "npm:^2.8.1" undici: "npm:^6.21.1" - checksum: 10c0/7bfe8daadbd31fb992d6f7dcfff4fe24c08476b1952eac49fcc197ae625fe0e13412644ae3ce3468c7e2e3444a1dfa5a4a67d9a7e83dd91897c2189d2b7f3765 + checksum: 10c0/4839e94822fa586b680cdfd5a938f25bdbb90fa4f5ae262b023e7cfb1969830650f838bba34e90e69b0cc5528f4e340f9a5d84768f49ea542175463a630e4ee2 languageName: node linkType: hard @@ -1337,13 +1337,6 @@ __metadata: languageName: node linkType: hard -"@esbuild/aix-ppc64@npm:0.25.12": - version: 0.25.12 - resolution: "@esbuild/aix-ppc64@npm:0.25.12" - conditions: os=aix & cpu=ppc64 - languageName: node - linkType: hard - "@esbuild/aix-ppc64@npm:0.27.2": version: 0.27.2 resolution: "@esbuild/aix-ppc64@npm:0.27.2" @@ -1351,13 +1344,6 @@ __metadata: languageName: node linkType: hard -"@esbuild/android-arm64@npm:0.25.12": - version: 0.25.12 - resolution: "@esbuild/android-arm64@npm:0.25.12" - conditions: os=android & cpu=arm64 - languageName: node - linkType: hard - "@esbuild/android-arm64@npm:0.27.2": version: 0.27.2 resolution: "@esbuild/android-arm64@npm:0.27.2" @@ -1365,13 +1351,6 @@ __metadata: languageName: node linkType: hard -"@esbuild/android-arm@npm:0.25.12": - version: 0.25.12 - resolution: "@esbuild/android-arm@npm:0.25.12" - conditions: os=android & cpu=arm - languageName: node - linkType: hard - "@esbuild/android-arm@npm:0.27.2": version: 0.27.2 resolution: "@esbuild/android-arm@npm:0.27.2" @@ -1379,13 +1358,6 @@ __metadata: languageName: node linkType: hard -"@esbuild/android-x64@npm:0.25.12": - version: 0.25.12 - resolution: "@esbuild/android-x64@npm:0.25.12" - conditions: os=android & cpu=x64 - languageName: node - linkType: hard - "@esbuild/android-x64@npm:0.27.2": version: 0.27.2 resolution: "@esbuild/android-x64@npm:0.27.2" @@ -1393,13 +1365,6 @@ __metadata: languageName: node linkType: hard -"@esbuild/darwin-arm64@npm:0.25.12": - version: 0.25.12 - resolution: "@esbuild/darwin-arm64@npm:0.25.12" - conditions: os=darwin & cpu=arm64 - languageName: node - linkType: hard - "@esbuild/darwin-arm64@npm:0.27.2": version: 0.27.2 resolution: "@esbuild/darwin-arm64@npm:0.27.2" @@ -1407,13 +1372,6 @@ __metadata: languageName: node linkType: hard -"@esbuild/darwin-x64@npm:0.25.12": - version: 0.25.12 - resolution: "@esbuild/darwin-x64@npm:0.25.12" - conditions: os=darwin & cpu=x64 - languageName: node - linkType: hard - "@esbuild/darwin-x64@npm:0.27.2": version: 0.27.2 resolution: "@esbuild/darwin-x64@npm:0.27.2" @@ -1421,13 +1379,6 @@ __metadata: languageName: node linkType: hard -"@esbuild/freebsd-arm64@npm:0.25.12": - version: 0.25.12 - resolution: "@esbuild/freebsd-arm64@npm:0.25.12" - conditions: os=freebsd & cpu=arm64 - languageName: node - linkType: hard - "@esbuild/freebsd-arm64@npm:0.27.2": version: 0.27.2 resolution: "@esbuild/freebsd-arm64@npm:0.27.2" @@ -1435,13 +1386,6 @@ __metadata: languageName: node linkType: hard -"@esbuild/freebsd-x64@npm:0.25.12": - version: 0.25.12 - resolution: "@esbuild/freebsd-x64@npm:0.25.12" - conditions: os=freebsd & cpu=x64 - languageName: node - linkType: hard - "@esbuild/freebsd-x64@npm:0.27.2": version: 0.27.2 resolution: "@esbuild/freebsd-x64@npm:0.27.2" @@ -1449,13 +1393,6 @@ __metadata: languageName: node linkType: hard -"@esbuild/linux-arm64@npm:0.25.12": - version: 0.25.12 - resolution: "@esbuild/linux-arm64@npm:0.25.12" - conditions: os=linux & cpu=arm64 - languageName: node - linkType: hard - "@esbuild/linux-arm64@npm:0.27.2": version: 0.27.2 resolution: "@esbuild/linux-arm64@npm:0.27.2" @@ -1463,13 +1400,6 @@ __metadata: languageName: node linkType: hard -"@esbuild/linux-arm@npm:0.25.12": - version: 0.25.12 - resolution: "@esbuild/linux-arm@npm:0.25.12" - conditions: os=linux & cpu=arm - languageName: node - linkType: hard - "@esbuild/linux-arm@npm:0.27.2": version: 0.27.2 resolution: "@esbuild/linux-arm@npm:0.27.2" @@ -1477,13 +1407,6 @@ __metadata: languageName: node linkType: hard -"@esbuild/linux-ia32@npm:0.25.12": - version: 0.25.12 - resolution: "@esbuild/linux-ia32@npm:0.25.12" - conditions: os=linux & cpu=ia32 - languageName: node - linkType: hard - "@esbuild/linux-ia32@npm:0.27.2": version: 0.27.2 resolution: "@esbuild/linux-ia32@npm:0.27.2" @@ -1491,13 +1414,6 @@ __metadata: languageName: node linkType: hard -"@esbuild/linux-loong64@npm:0.25.12": - version: 0.25.12 - resolution: "@esbuild/linux-loong64@npm:0.25.12" - conditions: os=linux & cpu=loong64 - languageName: node - linkType: hard - "@esbuild/linux-loong64@npm:0.27.2": version: 0.27.2 resolution: "@esbuild/linux-loong64@npm:0.27.2" @@ -1505,13 +1421,6 @@ __metadata: languageName: node linkType: hard -"@esbuild/linux-mips64el@npm:0.25.12": - version: 0.25.12 - resolution: "@esbuild/linux-mips64el@npm:0.25.12" - conditions: os=linux & cpu=mips64el - languageName: node - linkType: hard - "@esbuild/linux-mips64el@npm:0.27.2": version: 0.27.2 resolution: "@esbuild/linux-mips64el@npm:0.27.2" @@ -1519,13 +1428,6 @@ __metadata: languageName: node linkType: hard -"@esbuild/linux-ppc64@npm:0.25.12": - version: 0.25.12 - resolution: "@esbuild/linux-ppc64@npm:0.25.12" - conditions: os=linux & cpu=ppc64 - languageName: node - linkType: hard - "@esbuild/linux-ppc64@npm:0.27.2": version: 0.27.2 resolution: "@esbuild/linux-ppc64@npm:0.27.2" @@ -1533,13 +1435,6 @@ __metadata: languageName: node linkType: hard -"@esbuild/linux-riscv64@npm:0.25.12": - version: 0.25.12 - resolution: "@esbuild/linux-riscv64@npm:0.25.12" - conditions: os=linux & cpu=riscv64 - languageName: node - linkType: hard - "@esbuild/linux-riscv64@npm:0.27.2": version: 0.27.2 resolution: "@esbuild/linux-riscv64@npm:0.27.2" @@ -1547,13 +1442,6 @@ __metadata: languageName: node linkType: hard -"@esbuild/linux-s390x@npm:0.25.12": - version: 0.25.12 - resolution: "@esbuild/linux-s390x@npm:0.25.12" - conditions: os=linux & cpu=s390x - languageName: node - linkType: hard - "@esbuild/linux-s390x@npm:0.27.2": version: 0.27.2 resolution: "@esbuild/linux-s390x@npm:0.27.2" @@ -1561,13 +1449,6 @@ __metadata: languageName: node linkType: hard -"@esbuild/linux-x64@npm:0.25.12": - version: 0.25.12 - resolution: "@esbuild/linux-x64@npm:0.25.12" - conditions: os=linux & cpu=x64 - languageName: node - linkType: hard - "@esbuild/linux-x64@npm:0.27.2": version: 0.27.2 resolution: "@esbuild/linux-x64@npm:0.27.2" @@ -1575,13 +1456,6 @@ __metadata: languageName: node linkType: hard -"@esbuild/netbsd-arm64@npm:0.25.12": - version: 0.25.12 - resolution: "@esbuild/netbsd-arm64@npm:0.25.12" - conditions: os=netbsd & cpu=arm64 - languageName: node - linkType: hard - "@esbuild/netbsd-arm64@npm:0.27.2": version: 0.27.2 resolution: "@esbuild/netbsd-arm64@npm:0.27.2" @@ -1589,13 +1463,6 @@ __metadata: languageName: node linkType: hard -"@esbuild/netbsd-x64@npm:0.25.12": - version: 0.25.12 - resolution: "@esbuild/netbsd-x64@npm:0.25.12" - conditions: os=netbsd & cpu=x64 - languageName: node - linkType: hard - "@esbuild/netbsd-x64@npm:0.27.2": version: 0.27.2 resolution: "@esbuild/netbsd-x64@npm:0.27.2" @@ -1603,13 +1470,6 @@ __metadata: languageName: node linkType: hard -"@esbuild/openbsd-arm64@npm:0.25.12": - version: 0.25.12 - resolution: "@esbuild/openbsd-arm64@npm:0.25.12" - conditions: os=openbsd & cpu=arm64 - languageName: node - linkType: hard - "@esbuild/openbsd-arm64@npm:0.27.2": version: 0.27.2 resolution: "@esbuild/openbsd-arm64@npm:0.27.2" @@ -1617,13 +1477,6 @@ __metadata: languageName: node linkType: hard -"@esbuild/openbsd-x64@npm:0.25.12": - version: 0.25.12 - resolution: "@esbuild/openbsd-x64@npm:0.25.12" - conditions: os=openbsd & cpu=x64 - languageName: node - linkType: hard - "@esbuild/openbsd-x64@npm:0.27.2": version: 0.27.2 resolution: "@esbuild/openbsd-x64@npm:0.27.2" @@ -1631,13 +1484,6 @@ __metadata: languageName: node linkType: hard -"@esbuild/openharmony-arm64@npm:0.25.12": - version: 0.25.12 - resolution: "@esbuild/openharmony-arm64@npm:0.25.12" - conditions: os=openharmony & cpu=arm64 - languageName: node - linkType: hard - "@esbuild/openharmony-arm64@npm:0.27.2": version: 0.27.2 resolution: "@esbuild/openharmony-arm64@npm:0.27.2" @@ -1645,13 +1491,6 @@ __metadata: languageName: node linkType: hard -"@esbuild/sunos-x64@npm:0.25.12": - version: 0.25.12 - resolution: "@esbuild/sunos-x64@npm:0.25.12" - conditions: os=sunos & cpu=x64 - languageName: node - linkType: hard - "@esbuild/sunos-x64@npm:0.27.2": version: 0.27.2 resolution: "@esbuild/sunos-x64@npm:0.27.2" @@ -1659,13 +1498,6 @@ __metadata: languageName: node linkType: hard -"@esbuild/win32-arm64@npm:0.25.12": - version: 0.25.12 - resolution: "@esbuild/win32-arm64@npm:0.25.12" - conditions: os=win32 & cpu=arm64 - languageName: node - linkType: hard - "@esbuild/win32-arm64@npm:0.27.2": version: 0.27.2 resolution: "@esbuild/win32-arm64@npm:0.27.2" @@ -1673,13 +1505,6 @@ __metadata: languageName: node linkType: hard -"@esbuild/win32-ia32@npm:0.25.12": - version: 0.25.12 - resolution: "@esbuild/win32-ia32@npm:0.25.12" - conditions: os=win32 & cpu=ia32 - languageName: node - linkType: hard - "@esbuild/win32-ia32@npm:0.27.2": version: 0.27.2 resolution: "@esbuild/win32-ia32@npm:0.27.2" @@ -1687,13 +1512,6 @@ __metadata: languageName: node linkType: hard -"@esbuild/win32-x64@npm:0.25.12": - version: 0.25.12 - resolution: "@esbuild/win32-x64@npm:0.25.12" - conditions: os=win32 & cpu=x64 - languageName: node - linkType: hard - "@esbuild/win32-x64@npm:0.27.2": version: 0.27.2 resolution: "@esbuild/win32-x64@npm:0.27.2" @@ -1830,13 +1648,13 @@ __metadata: linkType: hard "@eslint-community/eslint-utils@npm:^4.7.0, @eslint-community/eslint-utils@npm:^4.8.0, @eslint-community/eslint-utils@npm:^4.9.0": - version: 4.9.0 - resolution: "@eslint-community/eslint-utils@npm:4.9.0" + version: 4.9.1 + resolution: "@eslint-community/eslint-utils@npm:4.9.1" dependencies: eslint-visitor-keys: "npm:^3.4.3" peerDependencies: eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 - checksum: 10c0/8881e22d519326e7dba85ea915ac7a143367c805e6ba1374c987aa2fbdd09195cc51183d2da72c0e2ff388f84363e1b220fd0d19bef10c272c63455162176817 + checksum: 10c0/dc4ab5e3e364ef27e33666b11f4b86e1a6c1d7cbf16f0c6ff87b1619b3562335e9201a3d6ce806221887ff780ec9d828962a290bb910759fd40a674686503f02 languageName: node linkType: hard @@ -1990,18 +1808,18 @@ __metadata: linkType: hard "@graphql-codegen/client-preset@npm:^5.2.0": - version: 5.2.1 - resolution: "@graphql-codegen/client-preset@npm:5.2.1" + version: 5.2.2 + resolution: "@graphql-codegen/client-preset@npm:5.2.2" dependencies: "@babel/helper-plugin-utils": "npm:^7.20.2" "@babel/template": "npm:^7.20.7" "@graphql-codegen/add": "npm:^6.0.0" - "@graphql-codegen/gql-tag-operations": "npm:5.1.1" + "@graphql-codegen/gql-tag-operations": "npm:5.1.2" "@graphql-codegen/plugin-helpers": "npm:^6.1.0" - "@graphql-codegen/typed-document-node": "npm:^6.1.4" - "@graphql-codegen/typescript": "npm:^5.0.6" - "@graphql-codegen/typescript-operations": "npm:^5.0.6" - "@graphql-codegen/visitor-plugin-common": "npm:^6.2.1" + "@graphql-codegen/typed-document-node": "npm:^6.1.5" + "@graphql-codegen/typescript": "npm:^5.0.7" + "@graphql-codegen/typescript-operations": "npm:^5.0.7" + "@graphql-codegen/visitor-plugin-common": "npm:^6.2.2" "@graphql-tools/documents": "npm:^1.0.0" "@graphql-tools/utils": "npm:^10.0.0" "@graphql-typed-document-node/core": "npm:3.2.0" @@ -2012,7 +1830,7 @@ __metadata: peerDependenciesMeta: graphql-sock: optional: true - checksum: 10c0/1c099eb08d7b5da4ad259249c1a12bdb6d1a897e2c8526355ba710bc30e7697913ce2a0e1d6d7308172c81babb2b904eb0c1dee53153f9615323e63941691ef6 + checksum: 10c0/d497166596234a2ab17519ae92341bd0968cadc190f65d7b2c762d99208d65e81002995b93cecf5cc2641add212b9ca79854646ddcf169cfd93e3a04bd301973 languageName: node linkType: hard @@ -2030,18 +1848,18 @@ __metadata: languageName: node linkType: hard -"@graphql-codegen/gql-tag-operations@npm:5.1.1": - version: 5.1.1 - resolution: "@graphql-codegen/gql-tag-operations@npm:5.1.1" +"@graphql-codegen/gql-tag-operations@npm:5.1.2": + version: 5.1.2 + resolution: "@graphql-codegen/gql-tag-operations@npm:5.1.2" dependencies: "@graphql-codegen/plugin-helpers": "npm:^6.1.0" - "@graphql-codegen/visitor-plugin-common": "npm:6.2.1" + "@graphql-codegen/visitor-plugin-common": "npm:6.2.2" "@graphql-tools/utils": "npm:^10.0.0" auto-bind: "npm:~4.0.0" tslib: "npm:~2.6.0" peerDependencies: graphql: ^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 - checksum: 10c0/b70b10cd1ef93a5d7054ce918feba857ae392495a8078e07dfcd10ce5d7b8ddee2e32bcaefe0135384d2052ae3766fe1ca8e978aa91b7722519a2c5a00d16776 + checksum: 10c0/5a543dabbe488c181153d3a997215d2ef9ea170844818d78b23ff418a9bd911449f2d530e30d4e2d7b4705e4296772a5997f046c607262564bf6f88b0eb61c29 languageName: node linkType: hard @@ -2087,28 +1905,28 @@ __metadata: languageName: node linkType: hard -"@graphql-codegen/typed-document-node@npm:^6.1.4": - version: 6.1.4 - resolution: "@graphql-codegen/typed-document-node@npm:6.1.4" +"@graphql-codegen/typed-document-node@npm:^6.1.5": + version: 6.1.5 + resolution: "@graphql-codegen/typed-document-node@npm:6.1.5" dependencies: "@graphql-codegen/plugin-helpers": "npm:^6.1.0" - "@graphql-codegen/visitor-plugin-common": "npm:6.2.1" + "@graphql-codegen/visitor-plugin-common": "npm:6.2.2" auto-bind: "npm:~4.0.0" change-case-all: "npm:1.0.15" tslib: "npm:~2.6.0" peerDependencies: graphql: ^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 - checksum: 10c0/0ebd4a6344fcd9ea17c5c620e0d63af66ee13f9bd7a22cecaf59c48062d1310b945db00e93aa1d3a6333d3adf9bcee31a3fedd2b7ea88992c0395491c42b018b + checksum: 10c0/faf4c13f83edc9e9534796214d012fe09f46d2d698bf124e16473f2eb47a77c91fdbf888892dda7291b9fe50806ad1031e5c159aa68be5b1071e2818c293fc34 languageName: node linkType: hard -"@graphql-codegen/typescript-operations@npm:^5.0.6": - version: 5.0.6 - resolution: "@graphql-codegen/typescript-operations@npm:5.0.6" +"@graphql-codegen/typescript-operations@npm:^5.0.7": + version: 5.0.7 + resolution: "@graphql-codegen/typescript-operations@npm:5.0.7" dependencies: "@graphql-codegen/plugin-helpers": "npm:^6.1.0" - "@graphql-codegen/typescript": "npm:^5.0.6" - "@graphql-codegen/visitor-plugin-common": "npm:6.2.1" + "@graphql-codegen/typescript": "npm:^5.0.7" + "@graphql-codegen/visitor-plugin-common": "npm:6.2.2" auto-bind: "npm:~4.0.0" tslib: "npm:~2.6.0" peerDependencies: @@ -2117,7 +1935,7 @@ __metadata: peerDependenciesMeta: graphql-sock: optional: true - checksum: 10c0/8e74dae9b96771fcc6d35210b858205f996e1496bae0429c5e2c917a3540390aef26402f06b9baa976d658b0a09e67bf00c427264b307686ab6e24c623fa8d2c + checksum: 10c0/e815abd0db11fd3c612486ce64cfa07cb5cc9d7a04a8298982281cd94fc2313c40103bd38d37b60aaefcf2dd635d953c29fc3a1817c05ba7e0ef7caced6a8a31 languageName: node linkType: hard @@ -2141,21 +1959,6 @@ __metadata: languageName: node linkType: hard -"@graphql-codegen/typescript@npm:^5.0.6": - version: 5.0.6 - resolution: "@graphql-codegen/typescript@npm:5.0.6" - dependencies: - "@graphql-codegen/plugin-helpers": "npm:^6.1.0" - "@graphql-codegen/schema-ast": "npm:^5.0.0" - "@graphql-codegen/visitor-plugin-common": "npm:6.2.1" - auto-bind: "npm:~4.0.0" - tslib: "npm:~2.6.0" - peerDependencies: - graphql: ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 - checksum: 10c0/61ddb5c7b6cdff65fd73d82689a7c19acb0bc3adfa0887d4b66fca3659ced7796a5b605886446a471c2b9a2cc1e62a26cfa374f0068ce26015b19aa26e968d39 - languageName: node - linkType: hard - "@graphql-codegen/typescript@npm:^5.0.7": version: 5.0.7 resolution: "@graphql-codegen/typescript@npm:5.0.7" @@ -2171,27 +1974,7 @@ __metadata: languageName: node linkType: hard -"@graphql-codegen/visitor-plugin-common@npm:6.2.1, @graphql-codegen/visitor-plugin-common@npm:^6.0.0, @graphql-codegen/visitor-plugin-common@npm:^6.2.1": - version: 6.2.1 - resolution: "@graphql-codegen/visitor-plugin-common@npm:6.2.1" - dependencies: - "@graphql-codegen/plugin-helpers": "npm:^6.1.0" - "@graphql-tools/optimize": "npm:^2.0.0" - "@graphql-tools/relay-operation-optimizer": "npm:^7.0.0" - "@graphql-tools/utils": "npm:^10.0.0" - auto-bind: "npm:~4.0.0" - change-case-all: "npm:1.0.15" - dependency-graph: "npm:^1.0.0" - graphql-tag: "npm:^2.11.0" - parse-filepath: "npm:^1.0.2" - tslib: "npm:~2.6.0" - peerDependencies: - graphql: ^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 - checksum: 10c0/7d48f86438edf86ea3d602d673ec57eb6c8c6a58869fccef78efa23788c28a3b4795ca9223aa946a51e01551f0b0c7134c7e20763929a150bdced5a9ece342d1 - languageName: node - linkType: hard - -"@graphql-codegen/visitor-plugin-common@npm:6.2.2": +"@graphql-codegen/visitor-plugin-common@npm:6.2.2, @graphql-codegen/visitor-plugin-common@npm:^6.0.0, @graphql-codegen/visitor-plugin-common@npm:^6.2.2": version: 6.2.2 resolution: "@graphql-codegen/visitor-plugin-common@npm:6.2.2" dependencies: @@ -3172,9 +2955,9 @@ __metadata: linkType: hard "@lezer/common@npm:^1.0.0, @lezer/common@npm:^1.2.0, @lezer/common@npm:^1.3.0": - version: 1.4.0 - resolution: "@lezer/common@npm:1.4.0" - checksum: 10c0/6b7a0b5f9b969d5eb87b1a6407abf90809ed4cb290afa4ce0fd412102176144b1d01748f243b27fef1300e0444eb690a519395388cda936fbbb536fd0abb8ebc + version: 1.5.0 + resolution: "@lezer/common@npm:1.5.0" + checksum: 10c0/12c4b0ea9d621eedb6aa13e41a0628e25f80efe6a1d44cf68a9ea1808db4c8fc124d0691458de69c833860f3235526fbec574005d812c710e693c2dc6e65e3a8 languageName: node linkType: hard @@ -3199,11 +2982,11 @@ __metadata: linkType: hard "@lezer/lr@npm:^1.3.0": - version: 1.4.4 - resolution: "@lezer/lr@npm:1.4.4" + version: 1.4.5 + resolution: "@lezer/lr@npm:1.4.5" dependencies: "@lezer/common": "npm:^1.0.0" - checksum: 10c0/376595b126162cd81ea0ec533a44d42fca636144ae73baa6781c2230349f6402721cbf6d4f4b013ed1693bd15134b4863f9912c7cb91dc502885aa9f822decac + checksum: 10c0/06a6fcba5093eb654303f0bcca5009c404fe865d19c5e302e095c3d317ca50ceafd5753bb05aa26403996fc039dffa12288154fb9e201f49a94dea798d0cb6bc languageName: node linkType: hard @@ -3230,12 +3013,12 @@ __metadata: linkType: hard "@mistralai/mistralai@npm:^1.3.1": - version: 1.10.0 - resolution: "@mistralai/mistralai@npm:1.10.0" + version: 1.11.0 + resolution: "@mistralai/mistralai@npm:1.11.0" dependencies: zod: "npm:^3.20.0" zod-to-json-schema: "npm:^3.24.1" - checksum: 10c0/1fb5948a18559f14e39ed55d780648baa988446799d8ebdd5d609670a9cc6ef41afca90aa2864685e5b4c3c6b2c95d18356ab0c191943f246ac9f5626fdec485 + checksum: 10c0/bc724028dda964333d12017c8d2fb29edd697b41135e4c5eb46d4993b2fc7698ea43992d22cc1918c3f9d256d8ff64dad574c60e2a488b69564d37c0fe8856da languageName: node linkType: hard @@ -3796,156 +3579,156 @@ __metadata: languageName: node linkType: hard -"@rollup/rollup-android-arm-eabi@npm:4.53.3": - version: 4.53.3 - resolution: "@rollup/rollup-android-arm-eabi@npm:4.53.3" +"@rollup/rollup-android-arm-eabi@npm:4.54.0": + version: 4.54.0 + resolution: "@rollup/rollup-android-arm-eabi@npm:4.54.0" conditions: os=android & cpu=arm languageName: node linkType: hard -"@rollup/rollup-android-arm64@npm:4.53.3": - version: 4.53.3 - resolution: "@rollup/rollup-android-arm64@npm:4.53.3" +"@rollup/rollup-android-arm64@npm:4.54.0": + version: 4.54.0 + resolution: "@rollup/rollup-android-arm64@npm:4.54.0" conditions: os=android & cpu=arm64 languageName: node linkType: hard -"@rollup/rollup-darwin-arm64@npm:4.53.3": - version: 4.53.3 - resolution: "@rollup/rollup-darwin-arm64@npm:4.53.3" +"@rollup/rollup-darwin-arm64@npm:4.54.0": + version: 4.54.0 + resolution: "@rollup/rollup-darwin-arm64@npm:4.54.0" conditions: os=darwin & cpu=arm64 languageName: node linkType: hard -"@rollup/rollup-darwin-x64@npm:4.53.3": - version: 4.53.3 - resolution: "@rollup/rollup-darwin-x64@npm:4.53.3" +"@rollup/rollup-darwin-x64@npm:4.54.0": + version: 4.54.0 + resolution: "@rollup/rollup-darwin-x64@npm:4.54.0" conditions: os=darwin & cpu=x64 languageName: node linkType: hard -"@rollup/rollup-freebsd-arm64@npm:4.53.3": - version: 4.53.3 - resolution: "@rollup/rollup-freebsd-arm64@npm:4.53.3" +"@rollup/rollup-freebsd-arm64@npm:4.54.0": + version: 4.54.0 + resolution: "@rollup/rollup-freebsd-arm64@npm:4.54.0" conditions: os=freebsd & cpu=arm64 languageName: node linkType: hard -"@rollup/rollup-freebsd-x64@npm:4.53.3": - version: 4.53.3 - resolution: "@rollup/rollup-freebsd-x64@npm:4.53.3" +"@rollup/rollup-freebsd-x64@npm:4.54.0": + version: 4.54.0 + resolution: "@rollup/rollup-freebsd-x64@npm:4.54.0" conditions: os=freebsd & cpu=x64 languageName: node linkType: hard -"@rollup/rollup-linux-arm-gnueabihf@npm:4.53.3": - version: 4.53.3 - resolution: "@rollup/rollup-linux-arm-gnueabihf@npm:4.53.3" +"@rollup/rollup-linux-arm-gnueabihf@npm:4.54.0": + version: 4.54.0 + resolution: "@rollup/rollup-linux-arm-gnueabihf@npm:4.54.0" conditions: os=linux & cpu=arm & libc=glibc languageName: node linkType: hard -"@rollup/rollup-linux-arm-musleabihf@npm:4.53.3": - version: 4.53.3 - resolution: "@rollup/rollup-linux-arm-musleabihf@npm:4.53.3" +"@rollup/rollup-linux-arm-musleabihf@npm:4.54.0": + version: 4.54.0 + resolution: "@rollup/rollup-linux-arm-musleabihf@npm:4.54.0" conditions: os=linux & cpu=arm & libc=musl languageName: node linkType: hard -"@rollup/rollup-linux-arm64-gnu@npm:4.53.3": - version: 4.53.3 - resolution: "@rollup/rollup-linux-arm64-gnu@npm:4.53.3" +"@rollup/rollup-linux-arm64-gnu@npm:4.54.0": + version: 4.54.0 + resolution: "@rollup/rollup-linux-arm64-gnu@npm:4.54.0" conditions: os=linux & cpu=arm64 & libc=glibc languageName: node linkType: hard -"@rollup/rollup-linux-arm64-musl@npm:4.53.3": - version: 4.53.3 - resolution: "@rollup/rollup-linux-arm64-musl@npm:4.53.3" +"@rollup/rollup-linux-arm64-musl@npm:4.54.0": + version: 4.54.0 + resolution: "@rollup/rollup-linux-arm64-musl@npm:4.54.0" conditions: os=linux & cpu=arm64 & libc=musl languageName: node linkType: hard -"@rollup/rollup-linux-loong64-gnu@npm:4.53.3": - version: 4.53.3 - resolution: "@rollup/rollup-linux-loong64-gnu@npm:4.53.3" +"@rollup/rollup-linux-loong64-gnu@npm:4.54.0": + version: 4.54.0 + resolution: "@rollup/rollup-linux-loong64-gnu@npm:4.54.0" conditions: os=linux & cpu=loong64 & libc=glibc languageName: node linkType: hard -"@rollup/rollup-linux-ppc64-gnu@npm:4.53.3": - version: 4.53.3 - resolution: "@rollup/rollup-linux-ppc64-gnu@npm:4.53.3" +"@rollup/rollup-linux-ppc64-gnu@npm:4.54.0": + version: 4.54.0 + resolution: "@rollup/rollup-linux-ppc64-gnu@npm:4.54.0" conditions: os=linux & cpu=ppc64 & libc=glibc languageName: node linkType: hard -"@rollup/rollup-linux-riscv64-gnu@npm:4.53.3": - version: 4.53.3 - resolution: "@rollup/rollup-linux-riscv64-gnu@npm:4.53.3" +"@rollup/rollup-linux-riscv64-gnu@npm:4.54.0": + version: 4.54.0 + resolution: "@rollup/rollup-linux-riscv64-gnu@npm:4.54.0" conditions: os=linux & cpu=riscv64 & libc=glibc languageName: node linkType: hard -"@rollup/rollup-linux-riscv64-musl@npm:4.53.3": - version: 4.53.3 - resolution: "@rollup/rollup-linux-riscv64-musl@npm:4.53.3" +"@rollup/rollup-linux-riscv64-musl@npm:4.54.0": + version: 4.54.0 + resolution: "@rollup/rollup-linux-riscv64-musl@npm:4.54.0" conditions: os=linux & cpu=riscv64 & libc=musl languageName: node linkType: hard -"@rollup/rollup-linux-s390x-gnu@npm:4.53.3": - version: 4.53.3 - resolution: "@rollup/rollup-linux-s390x-gnu@npm:4.53.3" +"@rollup/rollup-linux-s390x-gnu@npm:4.54.0": + version: 4.54.0 + resolution: "@rollup/rollup-linux-s390x-gnu@npm:4.54.0" conditions: os=linux & cpu=s390x & libc=glibc languageName: node linkType: hard -"@rollup/rollup-linux-x64-gnu@npm:4.53.3": - version: 4.53.3 - resolution: "@rollup/rollup-linux-x64-gnu@npm:4.53.3" +"@rollup/rollup-linux-x64-gnu@npm:4.54.0": + version: 4.54.0 + resolution: "@rollup/rollup-linux-x64-gnu@npm:4.54.0" conditions: os=linux & cpu=x64 & libc=glibc languageName: node linkType: hard -"@rollup/rollup-linux-x64-musl@npm:4.53.3": - version: 4.53.3 - resolution: "@rollup/rollup-linux-x64-musl@npm:4.53.3" +"@rollup/rollup-linux-x64-musl@npm:4.54.0": + version: 4.54.0 + resolution: "@rollup/rollup-linux-x64-musl@npm:4.54.0" conditions: os=linux & cpu=x64 & libc=musl languageName: node linkType: hard -"@rollup/rollup-openharmony-arm64@npm:4.53.3": - version: 4.53.3 - resolution: "@rollup/rollup-openharmony-arm64@npm:4.53.3" +"@rollup/rollup-openharmony-arm64@npm:4.54.0": + version: 4.54.0 + resolution: "@rollup/rollup-openharmony-arm64@npm:4.54.0" conditions: os=openharmony & cpu=arm64 languageName: node linkType: hard -"@rollup/rollup-win32-arm64-msvc@npm:4.53.3": - version: 4.53.3 - resolution: "@rollup/rollup-win32-arm64-msvc@npm:4.53.3" +"@rollup/rollup-win32-arm64-msvc@npm:4.54.0": + version: 4.54.0 + resolution: "@rollup/rollup-win32-arm64-msvc@npm:4.54.0" conditions: os=win32 & cpu=arm64 languageName: node linkType: hard -"@rollup/rollup-win32-ia32-msvc@npm:4.53.3": - version: 4.53.3 - resolution: "@rollup/rollup-win32-ia32-msvc@npm:4.53.3" +"@rollup/rollup-win32-ia32-msvc@npm:4.54.0": + version: 4.54.0 + resolution: "@rollup/rollup-win32-ia32-msvc@npm:4.54.0" conditions: os=win32 & cpu=ia32 languageName: node linkType: hard -"@rollup/rollup-win32-x64-gnu@npm:4.53.3": - version: 4.53.3 - resolution: "@rollup/rollup-win32-x64-gnu@npm:4.53.3" +"@rollup/rollup-win32-x64-gnu@npm:4.54.0": + version: 4.54.0 + resolution: "@rollup/rollup-win32-x64-gnu@npm:4.54.0" conditions: os=win32 & cpu=x64 languageName: node linkType: hard -"@rollup/rollup-win32-x64-msvc@npm:4.53.3": - version: 4.53.3 - resolution: "@rollup/rollup-win32-x64-msvc@npm:4.53.3" +"@rollup/rollup-win32-x64-msvc@npm:4.54.0": + version: 4.54.0 + resolution: "@rollup/rollup-win32-x64-msvc@npm:4.54.0" conditions: os=win32 & cpu=x64 languageName: node linkType: hard @@ -4372,15 +4155,6 @@ __metadata: languageName: node linkType: hard -"@smithy/types@npm:^4.9.0": - version: 4.9.0 - resolution: "@smithy/types@npm:4.9.0" - dependencies: - tslib: "npm:^2.6.2" - checksum: 10c0/7068428d2e98eafb7f7e03d10f919ae0e7ea2f339b5afca1631be3d6a6cb3512d5dc57ca95d4dab533a3ad587eeba3a1c77305eb4e563fbc067abda170482ff5 - languageName: node - linkType: hard - "@smithy/url-parser@npm:^4.2.6, @smithy/url-parser@npm:^4.2.7": version: 4.2.7 resolution: "@smithy/url-parser@npm:4.2.7" @@ -4610,17 +4384,17 @@ __metadata: linkType: hard "@swc/helpers@npm:^0.5.11": - version: 0.5.17 - resolution: "@swc/helpers@npm:0.5.17" + version: 0.5.18 + resolution: "@swc/helpers@npm:0.5.18" dependencies: tslib: "npm:^2.8.0" - checksum: 10c0/fe1f33ebb968558c5a0c595e54f2e479e4609bff844f9ca9a2d1ffd8dd8504c26f862a11b031f48f75c95b0381c2966c3dd156e25942f90089badd24341e7dbb + checksum: 10c0/cb32d72e32f775c30287bffbcf61c89ea3a963608cb3a4a675a3f9af545b8b3ab0bc9930432a5520a7307daaa87538158e253584ae1cf39f3e7e6e83408a2d51 languageName: node linkType: hard "@theguild/federation-composition@npm:^0.21.0": - version: 0.21.0 - resolution: "@theguild/federation-composition@npm:0.21.0" + version: 0.21.1 + resolution: "@theguild/federation-composition@npm:0.21.1" dependencies: constant-case: "npm:^3.0.4" debug: "npm:4.4.3" @@ -4628,7 +4402,7 @@ __metadata: lodash.sortby: "npm:^4.7.0" peerDependencies: graphql: ^16.0.0 - checksum: 10c0/5f2b45f986cf9e341d3c5b2e89d1ef7bb54051a455c56babca81ac2c1c81829d207c892fe482c1ae10cd7c53e34d8d32bb4f8d27435d9901e532328e2ac22d98 + checksum: 10c0/9eb422dfb29807d8a1da52d57e3ae411d1cabc4e8e804cf083627cdfc9ebe0c206aaca168171c8f593ed755d87cd799f69bd975acfd11a05a2d13f70d5a75687 languageName: node linkType: hard @@ -4981,12 +4755,12 @@ __metadata: languageName: node linkType: hard -"@types/node@npm:*, @types/node@npm:>=13.7.0, @types/node@npm:^24.0.3": - version: 24.10.1 - resolution: "@types/node@npm:24.10.1" +"@types/node@npm:*, @types/node@npm:>=13.7.0": + version: 25.0.3 + resolution: "@types/node@npm:25.0.3" dependencies: undici-types: "npm:~7.16.0" - checksum: 10c0/d6bca7a78f550fbb376f236f92b405d676003a8a09a1b411f55920ef34286ee3ee51f566203920e835478784df52662b5b2af89159d9d319352e9ea21801c002 + checksum: 10c0/b7568f0d765d9469621615e2bb257c7fd1953d95e9acbdb58dffb6627a2c4150d405a4600aa1ad8a40182a94fe5f903cafd3c0a2f5132814debd0e3bfd61f835 languageName: node linkType: hard @@ -4999,6 +4773,15 @@ __metadata: languageName: node linkType: hard +"@types/node@npm:^24.0.3": + version: 24.10.4 + resolution: "@types/node@npm:24.10.4" + dependencies: + undici-types: "npm:~7.16.0" + checksum: 10c0/069639cb7233ee747df1897b5e784f6b6c5da765c96c94773c580aac888fa1a585048d2a6e95eb8302d89c7a9df75801c8b5a0b7d0221d4249059cf09a5f4228 + languageName: node + linkType: hard + "@types/object-path@npm:^0.11.1": version: 0.11.4 resolution: "@types/object-path@npm:0.11.4" @@ -5243,20 +5026,13 @@ __metadata: languageName: node linkType: hard -"@typescript-eslint/types@npm:8.51.0, @typescript-eslint/types@npm:^8.51.0": +"@typescript-eslint/types@npm:8.51.0, @typescript-eslint/types@npm:^8.47.0, @typescript-eslint/types@npm:^8.51.0": version: 8.51.0 resolution: "@typescript-eslint/types@npm:8.51.0" checksum: 10c0/eb3473d0bb71eb886438f35887b620ffadae7853b281752a40c73158aee644d136adeb82549be7d7c30f346fe888b2e979dff7e30e67b35377e8281018034529 languageName: node linkType: hard -"@typescript-eslint/types@npm:^8.47.0": - version: 8.48.1 - resolution: "@typescript-eslint/types@npm:8.48.1" - checksum: 10c0/366b8140f4c69319f1796b66b33c0c6e16eb6cbe543b9517003104e12ed143b620c1433ccf60d781a629d9433bd509a363c0c9d21fd438c17bb8840733af6caa - languageName: node - linkType: hard - "@typescript-eslint/typescript-estree@npm:8.51.0": version: 8.51.0 resolution: "@typescript-eslint/typescript-estree@npm:8.51.0" @@ -5993,13 +5769,13 @@ __metadata: linkType: hard "ast-v8-to-istanbul@npm:^0.3.3": - version: 0.3.8 - resolution: "ast-v8-to-istanbul@npm:0.3.8" + version: 0.3.10 + resolution: "ast-v8-to-istanbul@npm:0.3.10" dependencies: "@jridgewell/trace-mapping": "npm:^0.3.31" estree-walker: "npm:^3.0.3" js-tokens: "npm:^9.0.1" - checksum: 10c0/6f7d74fc36011699af6d4ad88ecd8efc7d74bd90b8e8dbb1c69d43c8f4bec0ed361fb62a5b5bd98bbee02ee87c62cd8bcc25a39634964e45476bf5489dfa327f + checksum: 10c0/8a7a07c04f8f130b8a5abb76cdb31cce06a8eb4b7d4abbe207bc721132127ae332e857b96aa415ac43ec2c6c9312508210c598f61a7de2d0e3db5615e6b03183 languageName: node linkType: hard @@ -6148,11 +5924,11 @@ __metadata: linkType: hard "baseline-browser-mapping@npm:^2.9.0": - version: 2.9.2 - resolution: "baseline-browser-mapping@npm:2.9.2" + version: 2.9.11 + resolution: "baseline-browser-mapping@npm:2.9.11" bin: baseline-browser-mapping: dist/cli.js - checksum: 10c0/4f9be09e20261ed26f19e9b95454dcb8d8371b87983c57cd9f70b9572e9b3053577f0d8d6d91297bdb605337747680686e22f62522a6e57ae2488fcacf641188 + checksum: 10c0/eba49fcc1b33ab994aeeb73a4848f2670e06a0886dd5b903689ae6f60d47e7f1bea9262dbb2548c48179e858f7eda2b82ddf941ae783b862f4dcc51085a246f2 languageName: node linkType: hard @@ -6388,9 +6164,9 @@ __metadata: linkType: hard "caniuse-lite@npm:^1.0.30001759": - version: 1.0.30001759 - resolution: "caniuse-lite@npm:1.0.30001759" - checksum: 10c0/b0f415960ba34995cda18e0d25c4e602f6917b9179290a76bdd0311423505b78cc93e558a90c98a22a1cc6b1781ab720ef6beea24ec7e29a1c1164ca72eac3a2 + version: 1.0.30001762 + resolution: "caniuse-lite@npm:1.0.30001762" + checksum: 10c0/93707eac5b0240af3f2ce6e2d7ab504a6fefcf9c2f9cd8fb9d488e496a333c61e557dab0472c1b00c17bc386a5dbb792aa4c778cda2d768e17f986617d7aec53 languageName: node linkType: hard @@ -6978,7 +6754,7 @@ __metadata: languageName: node linkType: hard -"debug@npm:4, debug@npm:4.4.3, debug@npm:^4.1.0, debug@npm:^4.1.1, debug@npm:^4.3.1, debug@npm:^4.3.2, debug@npm:^4.3.4, debug@npm:^4.3.5, debug@npm:^4.4.0, debug@npm:^4.4.1, debug@npm:^4.4.3": +"debug@npm:4, debug@npm:4.4.3, debug@npm:^4.1.0, debug@npm:^4.1.1, debug@npm:^4.3.1, debug@npm:^4.3.2, debug@npm:^4.3.4, debug@npm:^4.4.0, debug@npm:^4.4.1, debug@npm:^4.4.3": version: 4.4.3 resolution: "debug@npm:4.4.3" dependencies: @@ -7172,9 +6948,9 @@ __metadata: linkType: hard "electron-to-chromium@npm:^1.5.263": - version: 1.5.265 - resolution: "electron-to-chromium@npm:1.5.265" - checksum: 10c0/f0409d15b02b61df5004d2bf6db0a2a01f42273fb46944c243d13139c83e8a07ad556656fd5f09758ae9b612ebf9862535afc6bffd47395890d5df7551274a4b + version: 1.5.267 + resolution: "electron-to-chromium@npm:1.5.267" + checksum: 10c0/0732bdb891b657f2e43266a3db8cf86fff6cecdcc8d693a92beff214e136cb5c2ee7dc5945ed75fa1db16e16bad0c38695527a020d15f39e79084e0b2e447621 languageName: node linkType: hard @@ -7253,8 +7029,8 @@ __metadata: linkType: hard "es-abstract@npm:^1.23.2, es-abstract@npm:^1.23.5, es-abstract@npm:^1.23.9, es-abstract@npm:^1.24.0": - version: 1.24.0 - resolution: "es-abstract@npm:1.24.0" + version: 1.24.1 + resolution: "es-abstract@npm:1.24.1" dependencies: array-buffer-byte-length: "npm:^1.0.2" arraybuffer.prototype.slice: "npm:^1.0.4" @@ -7310,7 +7086,7 @@ __metadata: typed-array-length: "npm:^1.0.7" unbox-primitive: "npm:^1.1.0" which-typed-array: "npm:^1.1.19" - checksum: 10c0/b256e897be32df5d382786ce8cce29a1dd8c97efbab77a26609bd70f2ed29fbcfc7a31758cb07488d532e7ccccdfca76c1118f2afe5a424cdc05ca007867c318 + checksum: 10c0/fca062ef8b5daacf743732167d319a212d45cb655b0bb540821d38d715416ae15b04b84fc86da9e2c89135aa7b337337b6c867f84dcde698d75d55688d5d765c languageName: node linkType: hard @@ -7432,7 +7208,7 @@ __metadata: languageName: node linkType: hard -"esbuild@npm:0.27.2": +"esbuild@npm:0.27.2, esbuild@npm:^0.27.0": version: 0.27.2 resolution: "esbuild@npm:0.27.2" dependencies: @@ -7521,95 +7297,6 @@ __metadata: languageName: node linkType: hard -"esbuild@npm:^0.25.0": - version: 0.25.12 - resolution: "esbuild@npm:0.25.12" - dependencies: - "@esbuild/aix-ppc64": "npm:0.25.12" - "@esbuild/android-arm": "npm:0.25.12" - "@esbuild/android-arm64": "npm:0.25.12" - "@esbuild/android-x64": "npm:0.25.12" - "@esbuild/darwin-arm64": "npm:0.25.12" - "@esbuild/darwin-x64": "npm:0.25.12" - "@esbuild/freebsd-arm64": "npm:0.25.12" - "@esbuild/freebsd-x64": "npm:0.25.12" - "@esbuild/linux-arm": "npm:0.25.12" - "@esbuild/linux-arm64": "npm:0.25.12" - "@esbuild/linux-ia32": "npm:0.25.12" - "@esbuild/linux-loong64": "npm:0.25.12" - "@esbuild/linux-mips64el": "npm:0.25.12" - "@esbuild/linux-ppc64": "npm:0.25.12" - "@esbuild/linux-riscv64": "npm:0.25.12" - "@esbuild/linux-s390x": "npm:0.25.12" - "@esbuild/linux-x64": "npm:0.25.12" - "@esbuild/netbsd-arm64": "npm:0.25.12" - "@esbuild/netbsd-x64": "npm:0.25.12" - "@esbuild/openbsd-arm64": "npm:0.25.12" - "@esbuild/openbsd-x64": "npm:0.25.12" - "@esbuild/openharmony-arm64": "npm:0.25.12" - "@esbuild/sunos-x64": "npm:0.25.12" - "@esbuild/win32-arm64": "npm:0.25.12" - "@esbuild/win32-ia32": "npm:0.25.12" - "@esbuild/win32-x64": "npm:0.25.12" - dependenciesMeta: - "@esbuild/aix-ppc64": - optional: true - "@esbuild/android-arm": - optional: true - "@esbuild/android-arm64": - optional: true - "@esbuild/android-x64": - optional: true - "@esbuild/darwin-arm64": - optional: true - "@esbuild/darwin-x64": - optional: true - "@esbuild/freebsd-arm64": - optional: true - "@esbuild/freebsd-x64": - optional: true - "@esbuild/linux-arm": - optional: true - "@esbuild/linux-arm64": - optional: true - "@esbuild/linux-ia32": - optional: true - "@esbuild/linux-loong64": - optional: true - "@esbuild/linux-mips64el": - optional: true - "@esbuild/linux-ppc64": - optional: true - "@esbuild/linux-riscv64": - optional: true - "@esbuild/linux-s390x": - optional: true - "@esbuild/linux-x64": - optional: true - "@esbuild/netbsd-arm64": - optional: true - "@esbuild/netbsd-x64": - optional: true - "@esbuild/openbsd-arm64": - optional: true - "@esbuild/openbsd-x64": - optional: true - "@esbuild/openharmony-arm64": - optional: true - "@esbuild/sunos-x64": - optional: true - "@esbuild/win32-arm64": - optional: true - "@esbuild/win32-ia32": - optional: true - "@esbuild/win32-x64": - optional: true - bin: - esbuild: bin/esbuild - checksum: 10c0/c205357531423220a9de8e1e6c6514242bc9b1666e762cd67ccdf8fdfdc3f1d0bd76f8d9383958b97ad4c953efdb7b6e8c1f9ca5951cd2b7c5235e8755b34a6b - languageName: node - linkType: hard - "escalade@npm:^3.1.1, escalade@npm:^3.2.0": version: 3.2.0 resolution: "escalade@npm:3.2.0" @@ -7830,11 +7517,11 @@ __metadata: linkType: hard "esquery@npm:^1.5.0": - version: 1.6.0 - resolution: "esquery@npm:1.6.0" + version: 1.7.0 + resolution: "esquery@npm:1.7.0" dependencies: estraverse: "npm:^5.1.0" - checksum: 10c0/cb9065ec605f9da7a76ca6dadb0619dfb611e37a81e318732977d90fab50a256b95fee2d925fba7c2f3f0523aa16f91587246693bc09bc34d5a59575fe6e93d2 + checksum: 10c0/77d5173db450b66f3bc685d11af4c90cffeedb340f34a39af96d43509a335ce39c894fd79233df32d38f5e4e219fa0f7076f6ec90bae8320170ba082c0db4793 languageName: node linkType: hard @@ -8105,11 +7792,11 @@ __metadata: linkType: hard "fastq@npm:^1.6.0": - version: 1.19.1 - resolution: "fastq@npm:1.19.1" + version: 1.20.1 + resolution: "fastq@npm:1.20.1" dependencies: reusify: "npm:^1.0.4" - checksum: 10c0/ebc6e50ac7048daaeb8e64522a1ea7a26e92b3cee5cd1c7f2316cdca81ba543aa40a136b53891446ea5c3a67ec215fbaca87ad405f102dd97012f62916905630 + checksum: 10c0/e5dd725884decb1f11e5c822221d76136f239d0236f176fab80b7b8f9e7619ae57e6b4e5b73defc21e6b9ef99437ee7b545cff8e6c2c337819633712fa9d352e languageName: node linkType: hard @@ -8389,13 +8076,13 @@ __metadata: linkType: hard "fs-extra@npm:^11.2.0": - version: 11.3.2 - resolution: "fs-extra@npm:11.3.2" + version: 11.3.3 + resolution: "fs-extra@npm:11.3.3" dependencies: graceful-fs: "npm:^4.2.0" jsonfile: "npm:^6.0.1" universalify: "npm:^2.0.0" - checksum: 10c0/f5d629e1bb646d5dedb4d8b24c5aad3deb8cc1d5438979d6f237146cd10e113b49a949ae1b54212c2fbc98e2d0995f38009a9a1d0520f0287943335e65fe919b + checksum: 10c0/984924ff4104e3e9f351b658a864bf3b354b2c90429f57aec0acd12d92c4e6b762cbacacdffb4e745b280adce882e1f980c485d9f02c453f769ab4e7fc646ce3 languageName: node linkType: hard @@ -8927,7 +8614,7 @@ __metadata: languageName: node linkType: hard -"http-errors@npm:^2.0.0, http-errors@npm:~2.0.1": +"http-errors@npm:^2.0.0, http-errors@npm:^2.0.1, http-errors@npm:~2.0.1": version: 2.0.1 resolution: "http-errors@npm:2.0.1" dependencies: @@ -8979,11 +8666,11 @@ __metadata: linkType: hard "iconv-lite@npm:^0.7.0, iconv-lite@npm:~0.7.0": - version: 0.7.0 - resolution: "iconv-lite@npm:0.7.0" + version: 0.7.1 + resolution: "iconv-lite@npm:0.7.1" dependencies: safer-buffer: "npm:>= 2.1.2 < 3.0.0" - checksum: 10c0/2382400469071c55b6746c531eed5fa4d033e5db6690b7331fb2a5f59a30d7a9782932e92253db26df33c1cf46fa200a3fbe524a2a7c62037c762283f188ec2f + checksum: 10c0/f5c9e2bddd7101a71b07a381ace44ebdc65ca76a10be0e9e64d372b511132acc4ee41b830962f438840d492cd6f9e08c237289f760d6a7fed754e61cffcb6757 languageName: node linkType: hard @@ -9785,8 +9472,8 @@ __metadata: linkType: hard "langsmith@npm:^0.3.33": - version: 0.3.82 - resolution: "langsmith@npm:0.3.82" + version: 0.3.87 + resolution: "langsmith@npm:0.3.87" dependencies: "@types/uuid": "npm:^10.0.0" chalk: "npm:^4.1.2" @@ -9808,7 +9495,7 @@ __metadata: optional: true openai: optional: true - checksum: 10c0/359cadb04616ecb233ca6a6b9bdd6fff0f423261c32d9d6f576c93d8c6ac53ed21770c68c779b861861e5efb3894966e8a3be4c71c9ae79292066fe1569465bd + checksum: 10c0/eb1ac6baea51063b881a9dc9a61d2d699bfc9b7aa3443865e575775abe084d94704344275958c94f4e1f03154103e9dd896cde7285797e4afb6af570da6db3b8 languageName: node linkType: hard @@ -10268,7 +9955,7 @@ __metadata: languageName: node linkType: hard -"mime-types@npm:3.0.2, mime-types@npm:^3.0.0, mime-types@npm:^3.0.1": +"mime-types@npm:3.0.2, mime-types@npm:^3.0.0, mime-types@npm:^3.0.2": version: 3.0.2 resolution: "mime-types@npm:3.0.2" dependencies: @@ -11643,11 +11330,11 @@ __metadata: linkType: hard "qs@npm:^6.14.0": - version: 6.14.0 - resolution: "qs@npm:6.14.0" + version: 6.14.1 + resolution: "qs@npm:6.14.1" dependencies: side-channel: "npm:^1.1.0" - checksum: 10c0/8ea5d91bf34f440598ee389d4a7d95820e3b837d3fd9f433871f7924801becaa0cd3b3b4628d49a7784d06a8aea9bc4554d2b6d8d584e2d221dc06238a42909c + checksum: 10c0/0e3b22dc451f48ce5940cbbc7c7d9068d895074f8c969c0801ac15c1313d1859c4d738e46dc4da2f498f41a9ffd8c201bd9fb12df67799b827db94cc373d2613 languageName: node linkType: hard @@ -11981,31 +11668,31 @@ __metadata: linkType: hard "rollup@npm:^4.43.0": - version: 4.53.3 - resolution: "rollup@npm:4.53.3" - dependencies: - "@rollup/rollup-android-arm-eabi": "npm:4.53.3" - "@rollup/rollup-android-arm64": "npm:4.53.3" - "@rollup/rollup-darwin-arm64": "npm:4.53.3" - "@rollup/rollup-darwin-x64": "npm:4.53.3" - "@rollup/rollup-freebsd-arm64": "npm:4.53.3" - "@rollup/rollup-freebsd-x64": "npm:4.53.3" - "@rollup/rollup-linux-arm-gnueabihf": "npm:4.53.3" - "@rollup/rollup-linux-arm-musleabihf": "npm:4.53.3" - "@rollup/rollup-linux-arm64-gnu": "npm:4.53.3" - "@rollup/rollup-linux-arm64-musl": "npm:4.53.3" - "@rollup/rollup-linux-loong64-gnu": "npm:4.53.3" - "@rollup/rollup-linux-ppc64-gnu": "npm:4.53.3" - "@rollup/rollup-linux-riscv64-gnu": "npm:4.53.3" - "@rollup/rollup-linux-riscv64-musl": "npm:4.53.3" - "@rollup/rollup-linux-s390x-gnu": "npm:4.53.3" - "@rollup/rollup-linux-x64-gnu": "npm:4.53.3" - "@rollup/rollup-linux-x64-musl": "npm:4.53.3" - "@rollup/rollup-openharmony-arm64": "npm:4.53.3" - "@rollup/rollup-win32-arm64-msvc": "npm:4.53.3" - "@rollup/rollup-win32-ia32-msvc": "npm:4.53.3" - "@rollup/rollup-win32-x64-gnu": "npm:4.53.3" - "@rollup/rollup-win32-x64-msvc": "npm:4.53.3" + version: 4.54.0 + resolution: "rollup@npm:4.54.0" + dependencies: + "@rollup/rollup-android-arm-eabi": "npm:4.54.0" + "@rollup/rollup-android-arm64": "npm:4.54.0" + "@rollup/rollup-darwin-arm64": "npm:4.54.0" + "@rollup/rollup-darwin-x64": "npm:4.54.0" + "@rollup/rollup-freebsd-arm64": "npm:4.54.0" + "@rollup/rollup-freebsd-x64": "npm:4.54.0" + "@rollup/rollup-linux-arm-gnueabihf": "npm:4.54.0" + "@rollup/rollup-linux-arm-musleabihf": "npm:4.54.0" + "@rollup/rollup-linux-arm64-gnu": "npm:4.54.0" + "@rollup/rollup-linux-arm64-musl": "npm:4.54.0" + "@rollup/rollup-linux-loong64-gnu": "npm:4.54.0" + "@rollup/rollup-linux-ppc64-gnu": "npm:4.54.0" + "@rollup/rollup-linux-riscv64-gnu": "npm:4.54.0" + "@rollup/rollup-linux-riscv64-musl": "npm:4.54.0" + "@rollup/rollup-linux-s390x-gnu": "npm:4.54.0" + "@rollup/rollup-linux-x64-gnu": "npm:4.54.0" + "@rollup/rollup-linux-x64-musl": "npm:4.54.0" + "@rollup/rollup-openharmony-arm64": "npm:4.54.0" + "@rollup/rollup-win32-arm64-msvc": "npm:4.54.0" + "@rollup/rollup-win32-ia32-msvc": "npm:4.54.0" + "@rollup/rollup-win32-x64-gnu": "npm:4.54.0" + "@rollup/rollup-win32-x64-msvc": "npm:4.54.0" "@types/estree": "npm:1.0.8" fsevents: "npm:~2.3.2" dependenciesMeta: @@ -12057,7 +11744,7 @@ __metadata: optional: true bin: rollup: dist/bin/rollup - checksum: 10c0/a21305aac72013083bd0dec92162b0f7f24cacf57c876ca601ec76e892895952c9ea592c1c07f23b8c125f7979c2b17f7fb565e386d03ee4c1f0952ac4ab0d75 + checksum: 10c0/62e5fd5d43e72751ac631f13fd7e70bec0fc3809231d5e087c3c0811945e7b8f0956620c5bed4e0cd67085325324266989e5ea4d22985c2677119ac7809b6455 languageName: node linkType: hard @@ -12192,21 +11879,21 @@ __metadata: linkType: hard "send@npm:^1.1.0, send@npm:^1.2.0": - version: 1.2.0 - resolution: "send@npm:1.2.0" + version: 1.2.1 + resolution: "send@npm:1.2.1" dependencies: - debug: "npm:^4.3.5" + debug: "npm:^4.4.3" encodeurl: "npm:^2.0.0" escape-html: "npm:^1.0.3" etag: "npm:^1.8.1" fresh: "npm:^2.0.0" - http-errors: "npm:^2.0.0" - mime-types: "npm:^3.0.1" + http-errors: "npm:^2.0.1" + mime-types: "npm:^3.0.2" ms: "npm:^2.1.3" on-finished: "npm:^2.4.1" range-parser: "npm:^1.2.1" - statuses: "npm:^2.0.1" - checksum: 10c0/531bcfb5616948d3468d95a1fd0adaeb0c20818ba4a500f439b800ca2117971489e02074ce32796fd64a6772ea3e7235fe0583d8241dbd37a053dc3378eff9a5 + statuses: "npm:^2.0.2" + checksum: 10c0/fbbbbdc902a913d65605274be23f3d604065cfc3ee3d78bf9fc8af1dc9fc82667c50d3d657f5e601ac657bac9b396b50ee97bd29cd55436320cf1cddebdcec72 languageName: node linkType: hard @@ -12222,14 +11909,14 @@ __metadata: linkType: hard "serve-static@npm:^2.2.0": - version: 2.2.0 - resolution: "serve-static@npm:2.2.0" + version: 2.2.1 + resolution: "serve-static@npm:2.2.1" dependencies: encodeurl: "npm:^2.0.0" escape-html: "npm:^1.0.3" parseurl: "npm:^1.3.3" send: "npm:^1.2.0" - checksum: 10c0/30e2ed1dbff1984836cfd0c65abf5d3f3f83bcd696c99d2d3c97edbd4e2a3ff4d3f87108a7d713640d290a7b6fe6c15ddcbc61165ab2eaad48ea8d3b52c7f913 + checksum: 10c0/37986096e8572e2dfaad35a3925fa8da0c0969f8814fd7788e84d4d388bc068cf0c06d1658509788e55bed942a6b6d040a8a267fa92bb9ffb1179f8bacde5fd7 languageName: node linkType: hard @@ -12597,7 +12284,7 @@ __metadata: languageName: node linkType: hard -"statuses@npm:^2.0.1, statuses@npm:~2.0.2": +"statuses@npm:^2.0.1, statuses@npm:^2.0.2, statuses@npm:~2.0.2": version: 2.0.2 resolution: "statuses@npm:2.0.2" checksum: 10c0/a9947d98ad60d01f6b26727570f3bcceb6c8fa789da64fe6889908fe2e294d57503b14bf2b5af7605c2d36647259e856635cd4c49eab41667658ec9d0080ec3f @@ -12797,9 +12484,9 @@ __metadata: linkType: hard "strnum@npm:^2.1.0": - version: 2.1.1 - resolution: "strnum@npm:2.1.1" - checksum: 10c0/1f9bd1f9b4c68333f25c2b1f498ea529189f060cd50aa59f1876139c994d817056de3ce57c12c970f80568d75df2289725e218bd9e3cdf73cd1a876c9c102733 + version: 2.1.2 + resolution: "strnum@npm:2.1.2" + checksum: 10c0/4e04753b793540d79cd13b2c3e59e298440477bae2b853ab78d548138385193b37d766d95b63b7046475d68d44fb1fca692f0a3f72b03f4168af076c7b246df9 languageName: node linkType: hard @@ -13023,13 +12710,13 @@ __metadata: linkType: hard "token-types@npm:^6.1.1": - version: 6.1.1 - resolution: "token-types@npm:6.1.1" + version: 6.1.2 + resolution: "token-types@npm:6.1.2" dependencies: - "@borewit/text-codec": "npm:^0.1.0" + "@borewit/text-codec": "npm:^0.2.1" "@tokenizer/token": "npm:^0.3.0" ieee754: "npm:^1.2.1" - checksum: 10c0/e2405e7789d41693a09c478b53c47ffadd735a5f4c826d9885787d022ab10e26cc4a67b03593285748bf3b0c0237e0ea2ab268abcb953ea314727201d0f6504d + checksum: 10c0/8786e28e3cb65b9e890bc3c38def98e6dfe4565538237f8c0e47dbe549ed8f5f00de8dc464717868308abb4729f1958f78f69e1c4c3deebbb685729113a6fee8 languageName: node linkType: hard @@ -13064,11 +12751,11 @@ __metadata: linkType: hard "ts-api-utils@npm:^2.2.0": - version: 2.3.0 - resolution: "ts-api-utils@npm:2.3.0" + version: 2.4.0 + resolution: "ts-api-utils@npm:2.4.0" peerDependencies: typescript: ">=4.8.4" - checksum: 10c0/9f2aadb8ac55926c79db03e37ee3b014135923d1705f6868b9e787e6b8822d2fd8e19df2f9002563f4e6268c994425ddaad61df24d0dad833a4be9f26f789213 + checksum: 10c0/ed185861aef4e7124366a3f6561113557a57504267d4d452a51e0ba516a9b6e713b56b4aeaab9fa13de9db9ab755c65c8c13a777dba9133c214632cb7b65c083 languageName: node linkType: hard @@ -13446,8 +13133,8 @@ __metadata: linkType: hard "update-browserslist-db@npm:^1.2.0": - version: 1.2.2 - resolution: "update-browserslist-db@npm:1.2.2" + version: 1.2.3 + resolution: "update-browserslist-db@npm:1.2.3" dependencies: escalade: "npm:^3.2.0" picocolors: "npm:^1.1.1" @@ -13455,7 +13142,7 @@ __metadata: browserslist: ">= 4.21.0" bin: update-browserslist-db: cli.js - checksum: 10c0/39c3ea08b397ffc8dc3a1c517f5c6ed5cc4179b5e185383dab9bf745879623c12062a2e6bf4f9427cc59389c7bfa0010e86858b923c1e349e32fdddd9b043bb2 + checksum: 10c0/13a00355ea822388f68af57410ce3255941d5fb9b7c49342c4709a07c9f230bbef7f7499ae0ca7e0de532e79a82cc0c4edbd125f1a323a1845bf914efddf8bec languageName: node linkType: hard @@ -13559,20 +13246,13 @@ __metadata: languageName: node linkType: hard -"validator@npm:13.15.26": +"validator@npm:13.15.26, validator@npm:^13.6.0": version: 13.15.26 resolution: "validator@npm:13.15.26" checksum: 10c0/d66041685c531423f6b514d0481228503b96682fe30ed7925ad77ff3cd08c3983dc94f45e18457e44f62f89027b94a3342009d65421800ce65f6e0d2c6eaf7fc languageName: node linkType: hard -"validator@npm:^13.6.0": - version: 13.15.23 - resolution: "validator@npm:13.15.23" - checksum: 10c0/22a05ec6a98d48d2b6fb34d43ce854af61d15842362d142e64cfca0325d4d0c2d1051d9f9d3a0f741e58ea888f73a35baf7a2a810f5aed0f89183bd5040f0177 - languageName: node - linkType: hard - "vary@npm:^1, vary@npm:^1.1.2, vary@npm:~1.1.2": version: 1.1.2 resolution: "vary@npm:1.1.2" @@ -13627,10 +13307,10 @@ __metadata: linkType: hard "vite@npm:^5.0.0 || ^6.0.0 || ^7.0.0-0": - version: 7.2.7 - resolution: "vite@npm:7.2.7" + version: 7.3.0 + resolution: "vite@npm:7.3.0" dependencies: - esbuild: "npm:^0.25.0" + esbuild: "npm:^0.27.0" fdir: "npm:^6.5.0" fsevents: "npm:~2.3.3" picomatch: "npm:^4.0.3" @@ -13677,7 +13357,7 @@ __metadata: optional: true bin: vite: bin/vite.js - checksum: 10c0/0c502d9eb898d9c05061dbd8fd199f280b524bbb4c12ab5f88c7b12779947386684a269e4dd0aa424aa35bcd857f1aa44aadb9ea764702a5043af433052455b5 + checksum: 10c0/0457c196cdd5761ec351c0f353945430fbad330e615b9eeab729c8ae163334f18acdc1d9cd7d9d673dbf111f07f6e4f0b25d4ac32360e65b4a6df9991046f3ff languageName: node linkType: hard @@ -14175,11 +13855,11 @@ __metadata: linkType: hard "zod-to-json-schema@npm:^3.22.3, zod-to-json-schema@npm:^3.22.4, zod-to-json-schema@npm:^3.24.1": - version: 3.25.0 - resolution: "zod-to-json-schema@npm:3.25.0" + version: 3.25.1 + resolution: "zod-to-json-schema@npm:3.25.1" peerDependencies: zod: ^3.25 || ^4 - checksum: 10c0/2d2cf6ca49752bf3dc5fb37bc8f275eddbbc4020e7958d9c198ea88cd197a5f527459118188a0081b889da6a6474d64c4134cd60951fa70178c125138761c680 + checksum: 10c0/711b30e34d1f1211f1afe64bf457f0d799234199dc005cca720b236ea808804c03164039c232f5df33c46f462023874015a8a0b3aab1585eca14124c324db7e2 languageName: node linkType: hard From c01f64c5c2641b974e95190233fec513748f21d7 Mon Sep 17 00:00:00 2001 From: Archidoit <75783086+Archidoit@users.noreply.github.com> Date: Tue, 6 Jan 2026 09:57:53 +0100 Subject: [PATCH 034/126] [frontend/backend] Dynamic PIR filters format refacto to handle several PIR ids (#13344) --- .../pir/pir_knowledge/PirKnowledge.tsx | 4 +- .../pir_knowledge/PirKnowledgeEntities.tsx | 6 +- .../src/utils/filters/filtersUtils.test.tsx | 50 +++++++ .../src/utils/filters/filtersUtils.tsx | 76 +++++++--- .../playbookManager/listenPirEventsUtils.ts | 10 +- .../filtering-completeSpecialFilterKeys.ts | 28 ++-- .../utils/filtering/filtering-constants.ts | 12 +- .../filtering/filtering-stix/stix-testers.ts | 6 +- .../src/utils/filtering/filtering-utils.ts | 4 - .../03-integration/02-resolvers/pir-test.ts | 133 +++++++++++++++--- 10 files changed, 257 insertions(+), 72 deletions(-) diff --git a/opencti-platform/opencti-front/src/private/components/pir/pir_knowledge/PirKnowledge.tsx b/opencti-platform/opencti-front/src/private/components/pir/pir_knowledge/PirKnowledge.tsx index 5b9a993e20c8..65b043acc6e4 100644 --- a/opencti-platform/opencti-front/src/private/components/pir/pir_knowledge/PirKnowledge.tsx +++ b/opencti-platform/opencti-front/src/private/components/pir/pir_knowledge/PirKnowledge.tsx @@ -34,12 +34,12 @@ interface PirKnowledgeProps { const PirKnowledge = ({ data }: PirKnowledgeProps) => { const pir = useFragment(knowledgeFragment, data); const pirId = pir.id; - const LOCAL_STORAGE_KEY = `PirSourcesFlaggedList-${pirId}`; + const LOCAL_STORAGE_KEY = `Pir-SourcesFlaggedList-${pirId}`; const initialValues = { filters: { ...emptyFilterGroup, - filters: useGetDefaultFilterObject([`pir_score.${pirId}`, `last_pir_score_date.${pirId}`], ['Stix-Domain-Object']), + filters: useGetDefaultFilterObject(['pir_score', 'last_pir_score_date'], ['Stix-Domain-Object']), }, searchTerm: '', sortBy: 'pir_score', diff --git a/opencti-platform/opencti-front/src/private/components/pir/pir_knowledge/PirKnowledgeEntities.tsx b/opencti-platform/opencti-front/src/private/components/pir/pir_knowledge/PirKnowledgeEntities.tsx index cdc0451ff06e..b01d1fb136a7 100644 --- a/opencti-platform/opencti-front/src/private/components/pir/pir_knowledge/PirKnowledgeEntities.tsx +++ b/opencti-platform/opencti-front/src/private/components/pir/pir_knowledge/PirKnowledgeEntities.tsx @@ -23,7 +23,7 @@ import { PirKnowledgeEntitiesSourcesFlaggedListQuery$variables, } from './__generated__/PirKnowledgeEntitiesSourcesFlaggedListQuery.graphql'; import { PirKnowledgeEntities_SourceFlaggedFragment$data } from './__generated__/PirKnowledgeEntities_SourceFlaggedFragment.graphql'; -import { isFilterGroupNotEmpty, useRemoveIdAndIncorrectKeysFromFilterGroupObject } from '../../../../utils/filters/filtersUtils'; +import { formatFiltersInPirContext, isFilterGroupNotEmpty, useRemoveIdAndIncorrectKeysFromFilterGroupObject } from '../../../../utils/filters/filtersUtils'; import { PaginationLocalStorage } from '../../../../utils/hooks/useLocalStorage'; import useQueryLoading from '../../../../utils/hooks/useQueryLoading'; import { DataTableProps } from '../../../../components/dataGrid/dataTableTypes'; @@ -176,7 +176,7 @@ const PirKnowledgeEntities = ({ pirId, localStorage, initialValues, additionalHe }, ], filterGroups: filters && isFilterGroupNotEmpty(filters) - ? [filters] + ? [formatFiltersInPirContext(filters, pirId)] : [], }; const queryPaginationOptions = { @@ -262,7 +262,7 @@ const PirKnowledgeEntities = ({ pirId, localStorage, initialValues, additionalHe return computeLink(e); }} additionalHeaderButtons={additionalHeaderButtons} - additionalFilterKeys={[`pir_score.${pirId}`, `last_pir_score_date.${pirId}`]} + additionalFilterKeys={['pir_score', 'last_pir_score_date']} /> )} diff --git a/opencti-platform/opencti-front/src/utils/filters/filtersUtils.test.tsx b/opencti-platform/opencti-front/src/utils/filters/filtersUtils.test.tsx index c80e9bef48a9..b3a4c1218ecc 100644 --- a/opencti-platform/opencti-front/src/utils/filters/filtersUtils.test.tsx +++ b/opencti-platform/opencti-front/src/utils/filters/filtersUtils.test.tsx @@ -3,6 +3,7 @@ import { buildFiltersAndOptionsForWidgets, emptyFilterGroup, findFiltersFromKeys, + formatFiltersInPirContext, getEntityTypeTwoFirstLevelsFilterValues, isRegardingOfFilterWarning, removeIdAndIncorrectKeysFromFilterGroupObject, @@ -703,3 +704,52 @@ describe('buildFiltersAndOptionsForWidgets', () => { expect(filters).toStrictEqual(expectedFilters); }); }); + +describe('formatFiltersInPirContext', () => { + it('should format filters in a PIR context', () => { + const initialFilters = { + mode: 'and', + filters: [ + { key: 'entity_type', values: ['Malware'] }, + { key: 'pir_score', values: ['50'], operator: 'gt' }, + ], + filterGroups: [ + { + mode: 'or', + filters: [ + { key: 'objectLabel', values: ['label-1'] }, + { key: 'last_pir_score_date', values: ['now-7d', 'now'], operator: 'within' }, + ], + filterGroups: [], + }, + ], + }; + const expectedFormattedFilters = { + mode: 'and', + filters: [ + { key: 'entity_type', values: ['Malware'] }, + { key: 'pir_score', + values: [ + { key: 'score', values: ['50'], operator: 'gt' }, + { key: 'pir_ids', values: ['pir-id-1'] }, + ] }, + ], + filterGroups: [ + { + mode: 'or', + filters: [ + { key: 'objectLabel', values: ['label-1'] }, + { key: 'last_pir_score_date', + values: [ + { key: 'date', values: ['now-7d', 'now'], operator: 'within' }, + { key: 'pir_ids', values: ['pir-id-1'] }, + ] }, + ], + filterGroups: [], + }, + ], + }; + const result = formatFiltersInPirContext(initialFilters, 'pir-id-1'); + expect(result).toEqual(expectedFormattedFilters); + }); +}); diff --git a/opencti-platform/opencti-front/src/utils/filters/filtersUtils.tsx b/opencti-platform/opencti-front/src/utils/filters/filtersUtils.tsx index 2dcd7cc9f026..e3e5c0042824 100644 --- a/opencti-platform/opencti-front/src/utils/filters/filtersUtils.tsx +++ b/opencti-platform/opencti-front/src/utils/filters/filtersUtils.tsx @@ -45,32 +45,45 @@ export const ME_FILTER_VALUE = '@me'; // 'within' operator filter constants export const DEFAULT_WITHIN_FILTER_VALUES = ['now-1d', 'now']; +const PIR_SCORE_FILTER = 'pir_score'; +const LAST_PIR_SCORE_DATE_FILTER = 'last_pir_score_date'; + export const FiltersVariant = { list: 'list', dialog: 'dialog', }; -const NOT_CLEANABLE_FILTER_KEYS = ['entity_type', 'authorized_members.id', 'user_id', 'internal_id', 'entity_id', 'ids', 'bulkSearchKeywords']; +const NOT_CLEANABLE_FILTER_KEYS = [ + 'entity_type', + 'authorized_members.id', + 'user_id', + 'internal_id', + 'entity_id', + 'ids', + 'bulkSearchKeywords', + PIR_SCORE_FILTER, + LAST_PIR_SCORE_DATE_FILTER, +]; -const pirScoreFilterDefinition = (pirId: string) => ({ - filterKey: `pir_score.${pirId}`, +const pirScoreFilterDefinition = { + filterKey: PIR_SCORE_FILTER, label: 'PIR Score', multiple: false, type: 'integer', subFilters: [], subEntityTypes: [], elementsForFilterValuesSearch: [], -}); +}; -const lastPirScoreDateFilterDefinition = (pirId: string) => ({ - filterKey: `last_pir_score_date.${pirId}`, +const lastPirScoreDateFilterDefinition = { + filterKey: LAST_PIR_SCORE_DATE_FILTER, label: 'Last PIR Score date', multiple: false, type: 'date', subFilters: [], subEntityTypes: [], elementsForFilterValuesSearch: [], -}); +}; // filters which possible values are entity types or relationship types export const entityTypesFilters = [ @@ -815,7 +828,7 @@ export const useAvailableFilterKeysForEntityTypes = (entityTypes: string[]) => { const isFilterKeyAvailable = (key: string, availableFilterKeys: string[]) => { const completedAvailableFilterKeys = availableFilterKeys.concat(NOT_CLEANABLE_FILTER_KEYS); - return completedAvailableFilterKeys.includes(key) || key.startsWith('pir_score') || key.startsWith('last_pir_score_date'); + return completedAvailableFilterKeys.includes(key); }; export const removeIdFromFilterGroupObject = (filters?: FilterGroup | null): FilterGroup | undefined => { @@ -906,13 +919,11 @@ export const getFilterDefinitionFromFilterKeysMap = ( filterKeysMap: Map, ): FilterDefinition | undefined => { const filterKey = getStringFilterKey(key); - if (filterKey.startsWith('pir_score.')) { - const pirId = filterKey.split('.')[1]; - return pirScoreFilterDefinition(pirId); + if (filterKey === PIR_SCORE_FILTER) { + return pirScoreFilterDefinition; } - if (filterKey.startsWith('last_pir_score_date.')) { - const pirId = filterKey.split('.')[1]; - return lastPirScoreDateFilterDefinition(pirId); + if (filterKey === LAST_PIR_SCORE_DATE_FILTER) { + return lastPirScoreDateFilterDefinition; } return filterKeysMap.get(filterKey); }; @@ -923,13 +934,11 @@ export const useFilterDefinition = ( subKey?: string, ): FilterDefinition | undefined => { const filterKey = getStringFilterKey(key); - if (filterKey.startsWith('pir_score.')) { - const pirId = filterKey.split('.')[1]; - return pirScoreFilterDefinition(pirId); + if (filterKey === PIR_SCORE_FILTER) { + return pirScoreFilterDefinition; } - if (filterKey.startsWith('last_pir_score_date.')) { - const pirId = filterKey.split('.')[1]; - return lastPirScoreDateFilterDefinition(pirId); + if (filterKey === LAST_PIR_SCORE_DATE_FILTER) { + return lastPirScoreDateFilterDefinition; } const filterDefinition = useBuildFilterKeysMapFromEntityType(entityTypes).get(filterKey); if (subKey) { @@ -1113,3 +1122,30 @@ export const getFilterKeyValues = (filterKey: string, filterGroup: FilterGroup) }); return values; }; + +// add pirId to pir filters +export const formatFiltersInPirContext = (f: FilterGroup, pirId: string): FilterGroup => { + const formattedFilters = []; + for (const filter of f.filters) { + const filterKey = filter.key; + if (filterKey === PIR_SCORE_FILTER || filterKey === LAST_PIR_SCORE_DATE_FILTER) { + const subKey = filterKey === PIR_SCORE_FILTER ? 'score' : 'date'; + formattedFilters.push({ + key: filterKey, + values: [ + { ...filter, key: subKey }, + { key: 'pir_ids', values: [pirId] }, + ], + }); + } else { + formattedFilters.push(filter); + } + } + return { + mode: f.mode, + filters: formattedFilters, + filterGroups: f.filterGroups.length > 0 + ? f.filterGroups.map((fg) => formatFiltersInPirContext(fg, pirId)) + : [], + }; +}; diff --git a/opencti-platform/opencti-graphql/src/manager/playbookManager/listenPirEventsUtils.ts b/opencti-platform/opencti-graphql/src/manager/playbookManager/listenPirEventsUtils.ts index 9b2866aa68dd..27a13931ff07 100644 --- a/opencti-platform/opencti-graphql/src/manager/playbookManager/listenPirEventsUtils.ts +++ b/opencti-platform/opencti-graphql/src/manager/playbookManager/listenPirEventsUtils.ts @@ -14,7 +14,7 @@ import { isStixMatchFilterGroup } from '../../utils/filtering/filtering-stix/sti import { isEventCreateRelationship, isEventInPirRelationship, isEventUpdateOnEntity, isValidEventType } from './playbookManagerUtils'; import { STIX_SPEC_VERSION } from '../../database/stix'; import { playbookExecutor } from './playbookExecutor'; -import { PIR_SCORE_FILTER } from '../../utils/filtering/filtering-constants'; +import { PIR_IDS_SUBFILTER, PIR_SCORE_FILTER, PIR_SCORE_SUBFILTER } from '../../utils/filtering/filtering-constants'; import { STIX_EXT_OCTI } from '../../types/stix-2-1-extensions'; /** @@ -138,8 +138,8 @@ export const formatFiltersForPirPlaybookComponent = (sourceFilters: string, inPi return { key: [PIR_SCORE_FILTER], values: [ - { ...filter, key: 'score' }, - { key: 'pir_ids', values: (inPirFilters ?? []).map((pir) => pir.value) }, + { ...filter, key: PIR_SCORE_SUBFILTER }, + { key: PIR_IDS_SUBFILTER, values: (inPirFilters ?? []).map((pir) => pir.value) }, ], }; } @@ -174,7 +174,7 @@ export const listenPirEvents = async ( AUTOMATION_MANAGER_USER, data.source_ref, ABSTRACT_STIX_CORE_OBJECT, - ) as unknown as StixObject; ; + ) as unknown as StixObject; } else if (isUpdateEvent) { // Event update on flagged entity. stixEntity = data; @@ -185,7 +185,7 @@ export const listenPirEvents = async ( AUTOMATION_MANAGER_USER, stixIdLinked, ABSTRACT_STIX_CORE_OBJECT, - ) as unknown as StixObject; ; + ) as unknown as StixObject; } // Having an entity means we have a matched PIR. diff --git a/opencti-platform/opencti-graphql/src/utils/filtering/filtering-completeSpecialFilterKeys.ts b/opencti-platform/opencti-graphql/src/utils/filtering/filtering-completeSpecialFilterKeys.ts index 670145441a95..3e9e7d2786f8 100644 --- a/opencti-platform/opencti-graphql/src/utils/filtering/filtering-completeSpecialFilterKeys.ts +++ b/opencti-platform/opencti-graphql/src/utils/filtering/filtering-completeSpecialFilterKeys.ts @@ -13,8 +13,11 @@ import { INSTANCE_RELATION_TYPES_FILTER, IS_INFERRED_FILTER, isComplexConversionFilterKey, - LAST_PIR_SCORE_DATE_FILTER_PREFIX, - PIR_SCORE_FILTER_PREFIX, + LAST_PIR_SCORE_DATE_FILTER, + LAST_PIR_SCORE_DATE_SUBFILTER, + PIR_IDS_SUBFILTER, + PIR_SCORE_FILTER, + PIR_SCORE_SUBFILTER, RELATION_DYNAMIC_FROM_FILTER, RELATION_DYNAMIC_SUBFILTER, RELATION_DYNAMIC_TO_FILTER, @@ -524,22 +527,21 @@ const adaptFilterToIsInferredFilterKey = (filter: Filter) => { }; const adaptFilterToPirFilterKeys = async (context: AuthContext, user: AuthUser, filterKey: string, filter: Filter) => { - // the key should be of format: pir_score.PIR_ID - const splittedKey = filterKey.split('.'); - if (splittedKey.length !== 2) { - throw FunctionalError('The filter key should be followed by a dot and the Pir ID', { filterKey }); + const pirIds: string[] = filter.values.find((v) => v.key === PIR_IDS_SUBFILTER)?.values ?? []; + if (pirIds.length === 0) { + throw FunctionalError('This filter should be related to at least 1 Pir', { filter }); } - const pirKey = splittedKey[0]; - const pirId = splittedKey[1]; // check the user has access to the PIR - await getPirWithAccessCheck(context, user, pirId); - // push the nested pir_score filter associated to the given PIR ID + await Promise.all(pirIds.map((pirId) => getPirWithAccessCheck(context, user, pirId))); + // push the nested pir filter associated to the given PIR IDs + const subKey = filterKey === PIR_SCORE_FILTER ? PIR_SCORE_SUBFILTER : LAST_PIR_SCORE_DATE_SUBFILTER; + const subFilter = filter.values.find((v) => v.key === subKey); const newFilter = { key: ['pir_information'], values: [], nested: [ - { ...filter, key: pirKey }, - { key: 'pir_id', values: [pirId], operator: FilterOperator.Eq }, + { ...subFilter, key: filterKey }, + { key: 'pir_id', values: pirIds, operator: FilterOperator.Eq }, ], }; return { newFilter, newFilterGroup: undefined }; @@ -811,7 +813,7 @@ export const completeSpecialFilterKeys = async ( finalFilterGroups.push(newFilterGroup); } } - if (filterKey.startsWith(PIR_SCORE_FILTER_PREFIX) || filterKey.startsWith(LAST_PIR_SCORE_DATE_FILTER_PREFIX)) { + if (filterKey === PIR_SCORE_FILTER || filterKey === LAST_PIR_SCORE_DATE_FILTER) { const { newFilter } = await adaptFilterToPirFilterKeys(context, user, filterKey, filter); finalFilters.push(newFilter); } diff --git a/opencti-platform/opencti-graphql/src/utils/filtering/filtering-constants.ts b/opencti-platform/opencti-graphql/src/utils/filtering/filtering-constants.ts index 54d4437f81d5..16f31b769af8 100644 --- a/opencti-platform/opencti-graphql/src/utils/filtering/filtering-constants.ts +++ b/opencti-platform/opencti-graphql/src/utils/filtering/filtering-constants.ts @@ -101,9 +101,11 @@ export const ALIAS_FILTER = 'alias'; // handle both 'aliases' and 'x_opencti_ali export const IS_INFERRED_FILTER = 'is_inferred'; // if an entity or relationship is inferred // for PIR -export const PIR_SCORE_FILTER = 'pir_score'; // used in stix filtering -export const PIR_SCORE_FILTER_PREFIX = 'pir_score'; // used in dynamic filtering -export const LAST_PIR_SCORE_DATE_FILTER_PREFIX = 'last_pir_score_date'; +export const PIR_SCORE_FILTER = 'pir_score'; +export const LAST_PIR_SCORE_DATE_FILTER = 'last_pir_score_date'; +export const PIR_SCORE_SUBFILTER = 'score'; +export const LAST_PIR_SCORE_DATE_SUBFILTER = 'date'; +export const PIR_IDS_SUBFILTER = 'pir_ids'; // for users export const USER_SERVICE_ACCOUNT_FILTER = 'user_service_account'; @@ -136,12 +138,12 @@ const COMPLEX_CONVERSION_FILTER_KEYS = [ 'authorized_members.id', // nested filter on restricted members => kept for retro compatibility (TODO remove after renaming) 'restricted_members.id', // nested filter on restricted members BULK_SEARCH_KEYWORDS_FILTER, // set of keywords used in bulk search + PIR_SCORE_FILTER, // should be associated to Pir Ids + LAST_PIR_SCORE_DATE_FILTER, // should be associated to Pir Ids ]; export const isComplexConversionFilterKey = (filterKey: string) => { return COMPLEX_CONVERSION_FILTER_KEYS.includes(filterKey) - || filterKey.startsWith(PIR_SCORE_FILTER_PREFIX) - || filterKey.startsWith(LAST_PIR_SCORE_DATE_FILTER_PREFIX) || isMetricsName(filterKey); }; diff --git a/opencti-platform/opencti-graphql/src/utils/filtering/filtering-stix/stix-testers.ts b/opencti-platform/opencti-graphql/src/utils/filtering/filtering-stix/stix-testers.ts index e59bf6c97d79..07f1a15ea092 100644 --- a/opencti-platform/opencti-graphql/src/utils/filtering/filtering-stix/stix-testers.ts +++ b/opencti-platform/opencti-graphql/src/utils/filtering/filtering-stix/stix-testers.ts @@ -46,6 +46,8 @@ import { WORKFLOW_FILTER, PATTERN_TYPE_FILTER, PIR_SCORE_FILTER, + PIR_SCORE_SUBFILTER, + PIR_IDS_SUBFILTER, } from '../filtering-constants'; import type { Filter } from '../../../generated/graphql'; import { STIX_RESOLUTION_MAP_PATHS } from '../filtering-resolution'; @@ -460,8 +462,8 @@ export const testCvssSeverity = (stix: any, filter: Filter) => { export const testPirScore = (stix: any, filter: Filter) => { // Retrieve data from the filter. - const pirIds = filter.values.find((v) => v.key === 'pir_ids')?.values ?? []; - const pirScoreFilter = filter.values.find((v) => v.key === 'score'); + const pirIds = filter.values.find((v) => v.key === PIR_IDS_SUBFILTER)?.values ?? []; + const pirScoreFilter = filter.values.find((v) => v.key === PIR_SCORE_SUBFILTER); // Determine which PIR of the stix entity is of interest. // If no PIR IDs set in the filter, then we want to check on all PIR of the stix entity. const pirInformation: PirInformation[] | undefined = pirIds.length === 0 diff --git a/opencti-platform/opencti-graphql/src/utils/filtering/filtering-utils.ts b/opencti-platform/opencti-graphql/src/utils/filtering/filtering-utils.ts index b308473b9beb..0ef3bd3259c7 100644 --- a/opencti-platform/opencti-graphql/src/utils/filtering/filtering-utils.ts +++ b/opencti-platform/opencti-graphql/src/utils/filtering/filtering-utils.ts @@ -14,7 +14,6 @@ import { INSTANCE_DYNAMIC_REGARDING_OF, INSTANCE_REGARDING_OF, LABEL_FILTER, - LAST_PIR_SCORE_DATE_FILTER_PREFIX, ME_FILTER_VALUE, MEMBERS_GROUP_FILTER, MEMBERS_ORGANIZATION_FILTER, @@ -23,7 +22,6 @@ import { OPINIONS_METRICS_MEAN_FILTER, OPINIONS_METRICS_MIN_FILTER, OPINIONS_METRICS_TOTAL_FILTER, - PIR_SCORE_FILTER_PREFIX, RELATION_DYNAMIC_FROM_FILTER, RELATION_DYNAMIC_TO_FILTER, SIGHTED_BY_FILTER, @@ -463,8 +461,6 @@ const checkFilterKeys = (filterGroup: FilterGroup) => { .map((k) => k.split('.')[0]) // keep only the first part of the key to handle composed keys .filter((k) => !(getAvailableKeys().has(k) || k.startsWith(RULE_PREFIX) - || k.startsWith(PIR_SCORE_FILTER_PREFIX) - || k.startsWith(LAST_PIR_SCORE_DATE_FILTER_PREFIX) || getMetricsAttributesNames().includes(k) )); diff --git a/opencti-platform/opencti-graphql/tests/03-integration/02-resolvers/pir-test.ts b/opencti-platform/opencti-graphql/tests/03-integration/02-resolvers/pir-test.ts index e8a8e2bbc8c5..73081d2f43c5 100644 --- a/opencti-platform/opencti-graphql/tests/03-integration/02-resolvers/pir-test.ts +++ b/opencti-platform/opencti-graphql/tests/03-integration/02-resolvers/pir-test.ts @@ -7,15 +7,14 @@ import { SYSTEM_USER } from '../../../src/utils/access'; import { internalLoadById, pageEntitiesConnection, pageRelationsConnection } from '../../../src/database/middleware-loader'; import { ENTITY_TYPE_MALWARE } from '../../../src/schema/stixDomainObject'; import type { BasicStoreEntity } from '../../../src/types/store'; -import { addFilter } from '../../../src/utils/filtering/filtering-utils'; import { ABSTRACT_STIX_DOMAIN_OBJECT } from '../../../src/schema/general'; -import { LAST_PIR_SCORE_DATE_FILTER_PREFIX, PIR_SCORE_FILTER_PREFIX } from '../../../src/utils/filtering/filtering-constants'; import { resetCacheForEntity } from '../../../src/database/cache'; import { type BasicStoreRelationPir, ENTITY_TYPE_PIR } from '../../../src/modules/pir/pir-types'; import { RELATION_IN_PIR } from '../../../src/schema/internalRelationship'; import { connectorsForWorker } from '../../../src/database/repository'; import { pirRelationshipsDistribution, pirRelationshipsMultiTimeSeries } from '../../../src/modules/pir/pir-domain'; import { ENTITY_TYPE_IDENTITY_ORGANIZATION } from '../../../src/modules/organization/organization-types'; +import { LAST_PIR_SCORE_DATE_FILTER, PIR_SCORE_FILTER } from '../../../src/utils/filtering/filtering-constants'; const LIST_QUERY = gql` query pirs( @@ -406,17 +405,46 @@ describe('PIR resolver standard behavior', () => { it('should filter entities by a pir score', async () => { // return no entities if the pir id matches no PIR - const filtersWithFakePirId = addFilter(undefined, `${PIR_SCORE_FILTER_PREFIX}.fakeId}`, ['50'], 'gt'); + const filtersWithFakePirId = { + mode: FilterMode.And, + filters: [{ + key: [PIR_SCORE_FILTER], + values: [ + { key: 'score', values: ['50'], operator: 'gt' }, + { key: 'pir_ids', values: ['fakeId'] }, + ], + }], + filterGroups: [], + }; await expect(async () => { await pageEntitiesConnection(testContext, SYSTEM_USER, [ABSTRACT_STIX_DOMAIN_OBJECT], { filters: filtersWithFakePirId }); }).rejects.toThrowError('No PIR found'); - // return error if the filter key is not in a correct format - const filtersInIncorrectFormat = addFilter(undefined, PIR_SCORE_FILTER_PREFIX, ['50'], 'gt'); + // return error if no pir id is provided + const filtersInIncorrectFormat = { + mode: FilterMode.And, + filters: [{ + key: [PIR_SCORE_FILTER], + values: [ + { key: 'score', values: ['50'], operator: 'gt' }, + ], + }], + filterGroups: [], + }; await expect(async () => { await pageEntitiesConnection(testContext, SYSTEM_USER, [ABSTRACT_STIX_DOMAIN_OBJECT], { filters: filtersInIncorrectFormat }); - }).rejects.toThrowError('The filter key should be followed by a dot and the Pir ID'); + }).rejects.toThrowError('This filter should be related to at least 1 Pir'); // return error if the pir is not accessible for the user - const filtersWithGtOperator = addFilter(undefined, `${PIR_SCORE_FILTER_PREFIX}.${pirInternalId1}`, ['50'], 'gt'); + const filtersWithGtOperator = { + mode: FilterMode.And, + filters: [{ + key: [PIR_SCORE_FILTER], + values: [ + { key: 'score', values: ['50'], operator: 'gt' }, + { key: 'pir_ids', values: [pirInternalId1] }, + ], + }], + filterGroups: [], + }; await expect(async () => { await pageEntitiesConnection(testContext, userUpdate, [ABSTRACT_STIX_DOMAIN_OBJECT], { filters: filtersWithGtOperator }); }).rejects.toThrowError('Unauthorized Pir access'); @@ -425,38 +453,107 @@ describe('PIR resolver standard behavior', () => { expect(stixDomainObjects1.edges.length).toEqual(1); expect(stixDomainObjects1.edges[0].node.internal_id).toEqual(flaggedElementId); // fetch entities with a score < 50 for a given PIR - const filtersWithLtOperator = addFilter(undefined, `${PIR_SCORE_FILTER_PREFIX}.${pirInternalId1}`, ['50'], 'lt'); + const filtersWithLtOperator = { + mode: FilterMode.And, + filters: [{ + key: [PIR_SCORE_FILTER], + values: [ + { key: 'score', values: ['50'], operator: 'lt' }, + { key: 'pir_ids', values: [pirInternalId1] }, + ], + }], + filterGroups: [], + }; const stixDomainObjects2 = await pageEntitiesConnection(testContext, SYSTEM_USER, [ABSTRACT_STIX_DOMAIN_OBJECT], { filters: filtersWithLtOperator }); expect(stixDomainObjects2.edges.length).toEqual(0); }); it('should filter entities by last pir score date', async () => { // return no entities if the pir id matches no PIR - const filtersWithFakePirId = addFilter(undefined, `${LAST_PIR_SCORE_DATE_FILTER_PREFIX}.fakeId}`, [now().toString()], 'lt'); + const filtersWithFakePirId = { + mode: FilterMode.And, + filters: [{ + key: [LAST_PIR_SCORE_DATE_FILTER], + values: [ + { key: 'date', values: [now().toString()], operator: 'lt' }, + { key: 'pir_ids', values: ['fakeId'] }, + ], + }], + filterGroups: [], + }; await expect(async () => { await pageEntitiesConnection(testContext, SYSTEM_USER, [ABSTRACT_STIX_DOMAIN_OBJECT], { filters: filtersWithFakePirId }); }).rejects.toThrowError('No PIR found'); - // return error if the filter key is not in a correct format - const filtersInIncorrectFormat = addFilter(undefined, LAST_PIR_SCORE_DATE_FILTER_PREFIX, [now().toString()], 'lt'); + // return error if no pir id is provided + const filtersInIncorrectFormat = { + mode: FilterMode.And, + filters: [{ + key: [LAST_PIR_SCORE_DATE_FILTER], + values: [ + { key: 'date', values: [now().toString()], operator: 'lt' }, + ], + }], + filterGroups: [], + }; await expect(async () => { await pageEntitiesConnection(testContext, SYSTEM_USER, [ABSTRACT_STIX_DOMAIN_OBJECT], { filters: filtersInIncorrectFormat }); - }).rejects.toThrowError('The filter key should be followed by a dot and the Pir ID'); + }).rejects.toThrowError('This filter should be related to at least 1 Pir'); // fetch entities scored before now for the pir - const filtersWithGtOperator = addFilter(undefined, `${LAST_PIR_SCORE_DATE_FILTER_PREFIX}.${pirInternalId1}`, [now().toString()], 'lt'); - const stixDomainObjects1 = await pageEntitiesConnection(testContext, SYSTEM_USER, [ABSTRACT_STIX_DOMAIN_OBJECT], { filters: filtersWithGtOperator }); + const filtersWithLtOperator = { + mode: FilterMode.And, + filters: [{ + key: [LAST_PIR_SCORE_DATE_FILTER], + values: [ + { key: 'date', values: [now().toString()], operator: 'lt' }, + { key: 'pir_ids', values: [pirInternalId1] }, + ], + }], + filterGroups: [], + }; + const stixDomainObjects1 = await pageEntitiesConnection(testContext, SYSTEM_USER, [ABSTRACT_STIX_DOMAIN_OBJECT], { filters: filtersWithLtOperator }); expect(stixDomainObjects1.edges.length).toEqual(1); expect(stixDomainObjects1.edges[0].node.internal_id).toEqual(flaggedElementId); // fetch entities scored after now for the pir - const filtersWithLtOperator = addFilter(undefined, `${LAST_PIR_SCORE_DATE_FILTER_PREFIX}.${pirInternalId1}`, [now().toString()], 'gt'); - const stixDomainObjects2 = await pageEntitiesConnection(testContext, SYSTEM_USER, [ABSTRACT_STIX_DOMAIN_OBJECT], { filters: filtersWithLtOperator }); + const filtersWithGtOperator = { + mode: FilterMode.And, + filters: [{ + key: [LAST_PIR_SCORE_DATE_FILTER], + values: [ + { key: 'date', values: [now().toString()], operator: 'gt' }, + { key: 'pir_ids', values: [pirInternalId1] }, + ], + }], + filterGroups: [], + }; + const stixDomainObjects2 = await pageEntitiesConnection(testContext, SYSTEM_USER, [ABSTRACT_STIX_DOMAIN_OBJECT], { filters: filtersWithGtOperator }); expect(stixDomainObjects2.edges.length).toEqual(0); // fetch entities scored today - const filtersWithinToday = addFilter(undefined, `${LAST_PIR_SCORE_DATE_FILTER_PREFIX}.${pirInternalId1}`, ['now-1d', 'now'], 'within'); + const filtersWithinToday = { + mode: FilterMode.And, + filters: [{ + key: [LAST_PIR_SCORE_DATE_FILTER], + values: [ + { key: 'date', values: ['now-1d', 'now'], operator: 'within' }, + { key: 'pir_ids', values: [pirInternalId1] }, + ], + }], + filterGroups: [], + }; const stixDomainObjects3 = await pageEntitiesConnection(testContext, SYSTEM_USER, [ABSTRACT_STIX_DOMAIN_OBJECT], { filters: filtersWithinToday }); expect(stixDomainObjects3.edges.length).toEqual(1); expect(stixDomainObjects3.edges[0].node.internal_id).toEqual(flaggedElementId); // fetch entities scored tomorrow - const filtersWithinTomorrow = addFilter(undefined, `${LAST_PIR_SCORE_DATE_FILTER_PREFIX}.${pirInternalId1}`, ['now', 'now+1d'], 'within'); + const filtersWithinTomorrow = { + mode: FilterMode.And, + filters: [{ + key: [LAST_PIR_SCORE_DATE_FILTER], + values: [ + { key: 'date', values: ['now', 'now+1d'], operator: 'within' }, + { key: 'pir_ids', values: [pirInternalId1] }, + ], + }], + filterGroups: [], + }; const stixDomainObjects4 = await pageEntitiesConnection(testContext, SYSTEM_USER, [ABSTRACT_STIX_DOMAIN_OBJECT], { filters: filtersWithinTomorrow }); expect(stixDomainObjects4.edges.length).toEqual(0); }); From 26166073e474ca9dfed3a235826ee44164a7662c Mon Sep 17 00:00:00 2001 From: Sarah Bocognano <98532359+SarahBocognano@users.noreply.github.com> Date: Tue, 6 Jan 2026 10:00:20 +0100 Subject: [PATCH 035/126] [backend] Rework enrich connector component for playbooks (#13642) --- .../modules/playbook/playbook-components.ts | 46 +++-- .../enrich-connector-component-test.ts | 185 ++++++++++++++++++ 2 files changed, 220 insertions(+), 11 deletions(-) create mode 100644 opencti-platform/opencti-graphql/tests/03-integration/01-database/playbookComponents/enrich-connector-component-test.ts diff --git a/opencti-platform/opencti-graphql/src/modules/playbook/playbook-components.ts b/opencti-platform/opencti-graphql/src/modules/playbook/playbook-components.ts index ea7bfcea4073..2517f1dd9e4a 100644 --- a/opencti-platform/opencti-graphql/src/modules/playbook/playbook-components.ts +++ b/opencti-platform/opencti-graphql/src/modules/playbook/playbook-components.ts @@ -367,7 +367,7 @@ const extendsBundleElementsWithExtensions = (bundle: StixBundle): StixBundle => }); return newBundle; }; -const PLAYBOOK_CONNECTOR_COMPONENT: PlaybookComponent = { +export const PLAYBOOK_CONNECTOR_COMPONENT: PlaybookComponent = { id: 'PLAYBOOK_CONNECTOR_COMPONENT', name: 'Enrich through connector', description: 'Use a registered platform connector for enrichment', @@ -384,8 +384,7 @@ const PLAYBOOK_CONNECTOR_COMPONENT: PlaybookComponent = const schemaElement = { properties: { connector: { oneOf: elements } } }; return R.mergeDeepRight, any>(PLAYBOOK_CONNECTOR_COMPONENT_SCHEMA, schemaElement); }, - notify: async ({ executionId, eventId, playbookId, playbookNode, - previousPlaybookNodeId, dataInstanceId, bundle }) => { + notify: async ({ executionId, eventId, playbookId, playbookNode, previousPlaybookNodeId, dataInstanceId, bundle }) => { if (playbookNode.configuration.connector) { const baseData = extractBundleBaseElement(dataInstanceId, bundle); const message = { @@ -411,17 +410,42 @@ const PLAYBOOK_CONNECTOR_COMPONENT: PlaybookComponent = await pushToConnector(playbookNode.configuration.connector, message); } }, - executor: async ({ bundle }) => { + executor: async ({ bundle, previousStepBundle }) => { // Add extensions if needed // This is needed as the rest of playbook expecting STIX2.1 format with extensions const stixBundle = extendsBundleElementsWithExtensions(bundle); - // TODO Could be reactivated after improvement of enrichment connectors - // if (previousStepBundle) { - // const diffOperations = jsonpatch.compare(previousStepBundle.objects, bundle.objects); - // if (diffOperations.length === 0) { - // return { output_port: 'unmodified', bundle }; - // } - // } + const resolveDuplicate = (a: any, b: any) => { + if (Array.isArray(a) && Array.isArray(b)) { + return R.uniq([...a, ...b]); + } + return b; + }; + if (previousStepBundle) { + const previousObjectsIndex: Record = {}; + previousStepBundle.objects.forEach((obj) => { + previousObjectsIndex[obj.id] = obj; + }); + // Check if new bundle objects has the same object ids of previous bundle objects + const enrichedObjects = stixBundle.objects.map((newObj) => { + const prevObj = previousObjectsIndex[newObj.id]; + if (prevObj) { + // Merge both objects if same ids + return R.mergeDeepWith(resolveDuplicate, prevObj, newObj); + } + return newObj; + }); + + // Check if new bundle contains objects of previous bundle and add them if not in it + const existingIds = new Set(stixBundle.objects.map((o) => o.id)); + const missingObjects = previousStepBundle.objects.filter( + (prevObj) => !existingIds.has(prevObj.id), + ); + if (missingObjects.length > 0) { + enrichedObjects.push(...missingObjects); + } + stixBundle.objects = enrichedObjects; + return { output_port: 'out', bundle: stixBundle }; + } return { output_port: 'out', bundle: stixBundle }; }, }; diff --git a/opencti-platform/opencti-graphql/tests/03-integration/01-database/playbookComponents/enrich-connector-component-test.ts b/opencti-platform/opencti-graphql/tests/03-integration/01-database/playbookComponents/enrich-connector-component-test.ts new file mode 100644 index 000000000000..adae82992410 --- /dev/null +++ b/opencti-platform/opencti-graphql/tests/03-integration/01-database/playbookComponents/enrich-connector-component-test.ts @@ -0,0 +1,185 @@ +import { describe, expect, it } from 'vitest'; +import type { StixBundle, StixObject, StixOpenctiExtension } from '../../../../src/types/stix-2-1-common'; +import { STIX_EXT_OCTI } from '../../../../src/types/stix-2-1-extensions'; +import { PLAYBOOK_CONNECTOR_COMPONENT } from '../../../../src/modules/playbook/playbook-components'; + +describe('PLAYBOOK_ENRICH_CONNECTOR_COMPONENT', () => { + const baseBundle: StixBundle = { + type: 'bundle', + spec_version: '2.1', + id: 'bundle--id', + objects: [], + } as StixBundle; + + const ipv4ObjectId = 'bundle--ipv4-id'; + + const baseBundleObject = { + type: 'sco', + spec_version: '2.1', + id: ipv4ObjectId, + name: 'Playbook Object 1', + extensions: { + [STIX_EXT_OCTI]: { + id: 'internal-uuid', + type: 'IPv4-Addr', + extension_type: 'property-extension', + }, + }, + }; + + const baseExecutorParams = { + ipv4ObjectId, + eventId: '', + executionId: '', + playbookId: '', + previousPlaybookNodeId: undefined, + }; + + const dummyPlaybookNode = { + id: 'playbook-node', + name: 'node', + component_id: 'test', + configuration: { + all: false, + connector: 'virus-total', + }, + }; + + it('should add previousStepBundle objects that are not present in the new bundle', async () => { + const idInBoth = 'sco--in-both'; + const idOnlyInPrevious = 'sco--only-in-previous'; + + const previousStepBundle: StixBundle = { + ...baseBundle, + id: 'bundle--previous', + objects: [ + { + id: idInBoth, + type: 'sco', + spec_version: '2.1', + name: 'Object in both bundles', + extensions: { + [STIX_EXT_OCTI]: { + id: 'ext-both', + type: 'sco', + extension_type: 'property-extension', + labels_ids: ['label-in-both'], + } as StixOpenctiExtension, + }, + } as StixObject, + { + id: idOnlyInPrevious, + type: 'sco', + spec_version: '2.1', + name: 'Object only in previous bundle', + extensions: { + [STIX_EXT_OCTI]: { + id: 'ext-prev-only', + type: 'sco', + extension_type: 'property-extension', + labels_ids: ['label-prev-only'], + } as StixOpenctiExtension, + }, + } as StixObject, + ], + }; + + const bundle: StixBundle = { + ...baseBundle, + id: 'bundle--current', + objects: [ + { + id: idInBoth, + type: 'sco', + spec_version: '2.1', + name: 'Object in both bundles (current)', + extensions: { + [STIX_EXT_OCTI]: { + id: 'ext-both', + type: 'sco', + extension_type: 'property-extension', + labels_ids: ['label-in-both'], + } as StixOpenctiExtension, + }, + } as StixObject, + ], + }; + + const result = await PLAYBOOK_CONNECTOR_COMPONENT.executor({ + ...baseExecutorParams, + dataInstanceId: idInBoth, + previousStepBundle, + bundle, + playbookNode: dummyPlaybookNode, + }); + + const resultIds = result.bundle.objects.map((o: StixObject) => o.id); + + expect(resultIds).toContain(idInBoth); + expect(resultIds).toContain(idOnlyInPrevious); + const added = result.bundle.objects.find((o) => o.id === idOnlyInPrevious) as StixObject; + const addedExt = added.extensions![STIX_EXT_OCTI] as StixOpenctiExtension; + expect(addedExt.labels_ids).toEqual(['label-prev-only']); + }); + + it('should merge previousStepBundle objects and new bundle objects with same id', async () => { + const previousStepBundle = { + ...baseBundle, + objects: [{ + ...baseBundleObject, + labels: ['label-id-1'], + extensions: { + [STIX_EXT_OCTI]: { + id: 'some--id', + type: 'sco', + extension_type: 'property-extension', + labels_ids: ['label-id-1'], + } as StixOpenctiExtension, + }, + } as StixObject], + } as StixBundle; + + const bundle = { + ...baseBundle, + objects: [ + { + ...baseBundleObject, + labels: ['label-id-2'], + extensions: { + [STIX_EXT_OCTI]: { + id: 'some--id', + type: 'sco', + extension_type: 'property-extension', + labels_ids: ['label-id-2'], + } as StixOpenctiExtension, + }, + } as StixObject, + { + ...baseBundleObject, + id: 'some--id-only-in-current-bundle', + labels: ['label-id-3'], + extensions: { + [STIX_EXT_OCTI]: { + id: 'some--id-only-in-current-bundle', + type: 'sco', + extension_type: 'property-extension', + labels_ids: ['label-id-3'], + } as StixOpenctiExtension, + }, + } as StixObject, + ], + } as StixBundle; + + const result = await PLAYBOOK_CONNECTOR_COMPONENT.executor({ + dataInstanceId: '', + ...baseExecutorParams, + previousStepBundle, + bundle, + playbookNode: dummyPlaybookNode, + }); + const extensions = result.bundle.objects.map((object) => object.extensions[STIX_EXT_OCTI]); + expect(extensions).toHaveLength(2); + expect(extensions[0].labels_ids).toEqual(['label-id-1', 'label-id-2']); + expect(extensions[1].labels_ids).toEqual(['label-id-3']); + }); +}); From 34e84f7cc30762738f238d624a2792af5ca05ba6 Mon Sep 17 00:00:00 2001 From: Samuel Hassine Date: Tue, 6 Jan 2026 11:46:58 +0200 Subject: [PATCH 036/126] [client] Add x_mitre_id in aliases of attack patterns (#13893) --- client-python/pycti/entities/opencti_attack_pattern.py | 9 ++++++++- .../StixCoreObjectKillChainPhasesView.tsx | 6 ++++-- .../03-integration/01-database/file-storage-test.js | 2 +- .../tests/03-integration/01-database/stix-test.js | 2 +- .../tests/11-sync/02-Direct/sync-direct-test.js | 4 ++-- .../opencti-graphql/tests/11-sync/sync-utils.js | 2 +- .../opencti-graphql/tests/data/DATA-TEST-STIX2_v2.json | 1 + 7 files changed, 18 insertions(+), 8 deletions(-) diff --git a/client-python/pycti/entities/opencti_attack_pattern.py b/client-python/pycti/entities/opencti_attack_pattern.py index c3934553b176..73ff7a544f71 100644 --- a/client-python/pycti/entities/opencti_attack_pattern.py +++ b/client-python/pycti/entities/opencti_attack_pattern.py @@ -568,6 +568,13 @@ def import_from_stix2(self, **kwargs): self.opencti.get_attribute_in_extension("modified_at", stix_object) ) + # Add x_mitre_id in aliases + aliases = self.opencti.stix2.pick_aliases(stix_object) + if aliases is None: + aliases = [] + if x_mitre_id is not None and x_mitre_id not in aliases: + aliases.append(x_mitre_id) + return self.create( stix_id=stix_object["id"], createdBy=( @@ -599,7 +606,7 @@ def import_from_stix2(self, **kwargs): if "description" in stix_object else None ), - aliases=self.opencti.stix2.pick_aliases(stix_object), + aliases=aliases, x_mitre_platforms=( stix_object["x_mitre_platforms"] if "x_mitre_platforms" in stix_object diff --git a/opencti-platform/opencti-front/src/private/components/common/stix_core_objects/StixCoreObjectKillChainPhasesView.tsx b/opencti-platform/opencti-front/src/private/components/common/stix_core_objects/StixCoreObjectKillChainPhasesView.tsx index 93f3fc4059c4..ead177d5c830 100644 --- a/opencti-platform/opencti-front/src/private/components/common/stix_core_objects/StixCoreObjectKillChainPhasesView.tsx +++ b/opencti-platform/opencti-front/src/private/components/common/stix_core_objects/StixCoreObjectKillChainPhasesView.tsx @@ -4,10 +4,11 @@ import ListItem from '@mui/material/ListItem'; import ListItemIcon from '@mui/material/ListItemIcon'; import ListItemText from '@mui/material/ListItemText'; import React, { FunctionComponent } from 'react'; -import { makeStyles } from '@mui/styles'; +import { makeStyles, useTheme } from '@mui/styles'; import ItemIcon from '../../../../components/ItemIcon'; import { useFormatter } from '../../../../components/i18n'; import FieldOrEmpty from '../../../../components/FieldOrEmpty'; +import type { Theme } from '../../../../components/Theme'; // Deprecated - https://mui.com/system/styles/basics/ // Do not use it for new code. @@ -35,6 +36,7 @@ interface StixCoreObjectKillChainPhasesViewProps { const StixCoreObjectKillChainPhasesView: FunctionComponent = ({ killChainPhases, firstLine }) => { const { t_i18n } = useFormatter(); const classes = useStyles(); + const theme = useTheme(); return (
    @@ -53,7 +55,7 @@ const StixCoreObjectKillChainPhasesView: FunctionComponent - + {killChainPhase.kill_chain_name}} /> ); })} diff --git a/opencti-platform/opencti-graphql/tests/03-integration/01-database/file-storage-test.js b/opencti-platform/opencti-graphql/tests/03-integration/01-database/file-storage-test.js index 27a4130c0e34..ddab6b3010a7 100644 --- a/opencti-platform/opencti-graphql/tests/03-integration/01-database/file-storage-test.js +++ b/opencti-platform/opencti-graphql/tests/03-integration/01-database/file-storage-test.js @@ -12,7 +12,7 @@ import { MARKING_TLP_AMBER_STRICT } from '../../../src/schema/identifier'; const exportFileName = '(ExportFileStix)_Malware-Paradise Ransomware_all.json'; const exportFileId = (malware) => `export/Malware/${malware.id}/${exportFileName.toLowerCase()}`; const importFileId = `import/global/${exportFileName.toLowerCase()}`; -const FILE_SIZE = 10700; +const FILE_SIZE = 10828; describe('File storage file listing', () => { it('should file upload succeed', async () => { diff --git a/opencti-platform/opencti-graphql/tests/03-integration/01-database/stix-test.js b/opencti-platform/opencti-graphql/tests/03-integration/01-database/stix-test.js index 8026bce62f12..9a832a3e07d7 100644 --- a/opencti-platform/opencti-graphql/tests/03-integration/01-database/stix-test.js +++ b/opencti-platform/opencti-graphql/tests/03-integration/01-database/stix-test.js @@ -9,7 +9,7 @@ import { ENTITY_TYPE_INTRUSION_SET, ENTITY_TYPE_MALWARE, isStixDomainObject, - isStixDomainObjectLocation + isStixDomainObjectLocation, } from '../../../src/schema/stixDomainObject'; import { isStixRelationship } from '../../../src/schema/stixRelationship'; import { ENTITY_TYPE_MARKING_DEFINITION } from '../../../src/schema/stixMetaObject'; diff --git a/opencti-platform/opencti-graphql/tests/11-sync/02-Direct/sync-direct-test.js b/opencti-platform/opencti-graphql/tests/11-sync/02-Direct/sync-direct-test.js index 3f0525041378..077decfd6e8b 100644 --- a/opencti-platform/opencti-graphql/tests/11-sync/02-Direct/sync-direct-test.js +++ b/opencti-platform/opencti-graphql/tests/11-sync/02-Direct/sync-direct-test.js @@ -9,7 +9,7 @@ import { FIFTEEN_MINUTES, SYNC_DIRECT_START_REMOTE_URI, SYNC_TEST_REMOTE_URI, - testContext + testContext, } from '../../utils/testQuery'; import { checkPostSyncContent, checkPreSyncContent, REPORT_QUERY, SYNC_CREATION_QUERY, SYNC_START_QUERY, UPLOADED_FILE_SIZE } from '../sync-utils'; import { SYSTEM_USER } from '../../../src/utils/access'; @@ -59,6 +59,6 @@ describe('Database sync direct', () => { expect(uploadedFile.name).toEqual(DATA_FILE_TEST); expect(uploadedFile.size).toEqual(UPLOADED_FILE_SIZE); }, - FIFTEEN_MINUTES + FIFTEEN_MINUTES, ); }); diff --git a/opencti-platform/opencti-graphql/tests/11-sync/sync-utils.js b/opencti-platform/opencti-graphql/tests/11-sync/sync-utils.js index 9e73c2c28e07..f962db4a5ea5 100644 --- a/opencti-platform/opencti-graphql/tests/11-sync/sync-utils.js +++ b/opencti-platform/opencti-graphql/tests/11-sync/sync-utils.js @@ -67,7 +67,7 @@ export const VOCABULARY_NUMBERS = 357; export const INDICATOR_NUMBERS = 28; export const MALWARE_NUMBERS = 27; export const LABEL_NUMBERS = 17; -export const UPLOADED_FILE_SIZE = 42204; +export const UPLOADED_FILE_SIZE = 42232; const filterOutDeleteOperationRefs = { mode: 'and', diff --git a/opencti-platform/opencti-graphql/tests/data/DATA-TEST-STIX2_v2.json b/opencti-platform/opencti-graphql/tests/data/DATA-TEST-STIX2_v2.json index de2f6ee8d15a..d4292bfb01f9 100644 --- a/opencti-platform/opencti-graphql/tests/data/DATA-TEST-STIX2_v2.json +++ b/opencti-platform/opencti-graphql/tests/data/DATA-TEST-STIX2_v2.json @@ -580,6 +580,7 @@ "spec_version": "2.1", "x_mitre_id": "T1369", "name": "Spear phishing messages with malicious links", + "aliases": ["T1369"], "labels": ["attack-pattern"], "description": "This technique has been deprecated. Please see ATT&CK's Initial Access and Execution tactics for replacement techniques.\n\nEmails with malicious links are designed to get a user to click on the link in order to deliver malware payloads. (Citation: GoogleDrive Phishing) (Citation: RSASEThreat)", "created": "2017-12-14T16:46:06.044Z", From 21c6c116d3a4de69f50e20ec3e1e25f9f15ed929 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 6 Jan 2026 11:00:03 +0100 Subject: [PATCH 037/126] [deps] Update dependency react-pdf to v10.3.0 (#13873) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- opencti-platform/opencti-front/package.json | 2 +- opencti-platform/opencti-front/yarn.lock | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/opencti-platform/opencti-front/package.json b/opencti-platform/opencti-front/package.json index 4400adbd85e7..2b730be577f6 100644 --- a/opencti-platform/opencti-front/package.json +++ b/opencti-platform/opencti-front/package.json @@ -106,7 +106,7 @@ "react-material-ui-carousel": "3.4.2", "react-mde": "11.5.0", "react-otp-input": "3.1.1", - "react-pdf": "10.2.0", + "react-pdf": "10.3.0", "react-rectangle-selection": "1.0.4", "react-relay": "20.1.1", "react-relay-network-modern": "6.2.2", diff --git a/opencti-platform/opencti-front/yarn.lock b/opencti-platform/opencti-front/yarn.lock index 255833e14d29..6aa7e2767599 100644 --- a/opencti-platform/opencti-front/yarn.lock +++ b/opencti-platform/opencti-front/yarn.lock @@ -12304,7 +12304,7 @@ __metadata: react-material-ui-carousel: "npm:3.4.2" react-mde: "npm:11.5.0" react-otp-input: "npm:3.1.1" - react-pdf: "npm:10.2.0" + react-pdf: "npm:10.3.0" react-rectangle-selection: "npm:1.0.4" react-relay: "npm:20.1.1" react-relay-network-modern: "npm:6.2.2" @@ -13185,9 +13185,9 @@ __metadata: languageName: node linkType: hard -"react-pdf@npm:10.2.0": - version: 10.2.0 - resolution: "react-pdf@npm:10.2.0" +"react-pdf@npm:10.3.0": + version: 10.3.0 + resolution: "react-pdf@npm:10.3.0" dependencies: clsx: "npm:^2.0.0" dequal: "npm:^2.0.3" @@ -13204,7 +13204,7 @@ __metadata: peerDependenciesMeta: "@types/react": optional: true - checksum: 10c0/a9138b9c54c41899efc98165b99149bc8089aff98f82030d7810764940066ffde584929f0899c10bd527949c19afdce01a10e10a0e49d8c8131c877361132198 + checksum: 10c0/433d55ce4bc1b93655ab0edbac6f08b27d544939e76756ed7c34f26cf8b0f39e0f6ef965a771da4511a98ba9b126a6e7cecb18c616e4ab54eb2ee0b1cfba5bfa languageName: node linkType: hard From 3639e4600a9446955106b2627cbdb165b3897b46 Mon Sep 17 00:00:00 2001 From: Samuel Hassine Date: Tue, 6 Jan 2026 14:17:49 +0200 Subject: [PATCH 038/126] [backend] Resolve x_mitre_id from the name (#13893) --- .../pycti/entities/opencti_attack_pattern.py | 9 +-------- .../opencti-graphql/src/domain/attackPattern.ts | 15 +++++++++++++-- .../01-database/file-storage-test.js | 2 +- .../tests/03-integration/01-database/stix-test.js | 2 +- .../tests/11-sync/02-Direct/sync-direct-test.js | 4 ++-- .../opencti-graphql/tests/11-sync/sync-utils.js | 2 +- .../tests/data/DATA-TEST-STIX2_v2.json | 1 - 7 files changed, 19 insertions(+), 16 deletions(-) diff --git a/client-python/pycti/entities/opencti_attack_pattern.py b/client-python/pycti/entities/opencti_attack_pattern.py index 73ff7a544f71..c3934553b176 100644 --- a/client-python/pycti/entities/opencti_attack_pattern.py +++ b/client-python/pycti/entities/opencti_attack_pattern.py @@ -568,13 +568,6 @@ def import_from_stix2(self, **kwargs): self.opencti.get_attribute_in_extension("modified_at", stix_object) ) - # Add x_mitre_id in aliases - aliases = self.opencti.stix2.pick_aliases(stix_object) - if aliases is None: - aliases = [] - if x_mitre_id is not None and x_mitre_id not in aliases: - aliases.append(x_mitre_id) - return self.create( stix_id=stix_object["id"], createdBy=( @@ -606,7 +599,7 @@ def import_from_stix2(self, **kwargs): if "description" in stix_object else None ), - aliases=aliases, + aliases=self.opencti.stix2.pick_aliases(stix_object), x_mitre_platforms=( stix_object["x_mitre_platforms"] if "x_mitre_platforms" in stix_object diff --git a/opencti-platform/opencti-graphql/src/domain/attackPattern.ts b/opencti-platform/opencti-graphql/src/domain/attackPattern.ts index 82e2129c44aa..b09a5e1dd052 100644 --- a/opencti-platform/opencti-graphql/src/domain/attackPattern.ts +++ b/opencti-platform/opencti-graphql/src/domain/attackPattern.ts @@ -1,7 +1,8 @@ +import * as R from 'ramda'; import { createEntity } from '../database/middleware'; import { BUS_TOPICS } from '../config/conf'; import { notify } from '../database/redis'; -import { READ_INDEX_STIX_DOMAIN_OBJECTS, READ_INDEX_STIX_META_OBJECTS } from '../database/utils'; +import { isEmptyField, READ_INDEX_STIX_DOMAIN_OBJECTS, READ_INDEX_STIX_META_OBJECTS } from '../database/utils'; import { ENTITY_TYPE_ATTACK_PATTERN, ENTITY_TYPE_COURSE_OF_ACTION, ENTITY_TYPE_DATA_COMPONENT } from '../schema/stixDomainObject'; import { ABSTRACT_STIX_DOMAIN_OBJECT } from '../schema/general'; import { RELATION_DETECTS, RELATION_MITIGATES, RELATION_SUBTECHNIQUE_OF } from '../schema/stixCoreRelationship'; @@ -29,7 +30,17 @@ export const findAttackPatternPaginated = (context: AuthContext, user: AuthUser, }; export const addAttackPattern = async (context: AuthContext, user: AuthUser, attackPattern: AttackPatternAddInput) => { - const created = await createEntity(context, user, attackPattern, ENTITY_TYPE_ATTACK_PATTERN); + let xMitreId = null; + if (isEmptyField(attackPattern.x_mitre_id)) { + // Extract x_mitre_id from name if not already provided + // Match patterns like T0015.001, T0015, FT048 + const mitreIdMatch = attackPattern.name?.match(/\b([TF]T?\d+(?:\.\d+)?)\b/); + if (mitreIdMatch) { + xMitreId = mitreIdMatch[1]; + } + } + const attackPatternToCreate = xMitreId ? R.assoc('x_mitre_id', xMitreId, attackPattern) : attackPattern; + const created = await createEntity(context, user, attackPatternToCreate, ENTITY_TYPE_ATTACK_PATTERN); return notify(BUS_TOPICS[ABSTRACT_STIX_DOMAIN_OBJECT].ADDED_TOPIC, created, user); }; diff --git a/opencti-platform/opencti-graphql/tests/03-integration/01-database/file-storage-test.js b/opencti-platform/opencti-graphql/tests/03-integration/01-database/file-storage-test.js index ddab6b3010a7..27a4130c0e34 100644 --- a/opencti-platform/opencti-graphql/tests/03-integration/01-database/file-storage-test.js +++ b/opencti-platform/opencti-graphql/tests/03-integration/01-database/file-storage-test.js @@ -12,7 +12,7 @@ import { MARKING_TLP_AMBER_STRICT } from '../../../src/schema/identifier'; const exportFileName = '(ExportFileStix)_Malware-Paradise Ransomware_all.json'; const exportFileId = (malware) => `export/Malware/${malware.id}/${exportFileName.toLowerCase()}`; const importFileId = `import/global/${exportFileName.toLowerCase()}`; -const FILE_SIZE = 10828; +const FILE_SIZE = 10700; describe('File storage file listing', () => { it('should file upload succeed', async () => { diff --git a/opencti-platform/opencti-graphql/tests/03-integration/01-database/stix-test.js b/opencti-platform/opencti-graphql/tests/03-integration/01-database/stix-test.js index 9a832a3e07d7..8026bce62f12 100644 --- a/opencti-platform/opencti-graphql/tests/03-integration/01-database/stix-test.js +++ b/opencti-platform/opencti-graphql/tests/03-integration/01-database/stix-test.js @@ -9,7 +9,7 @@ import { ENTITY_TYPE_INTRUSION_SET, ENTITY_TYPE_MALWARE, isStixDomainObject, - isStixDomainObjectLocation, + isStixDomainObjectLocation } from '../../../src/schema/stixDomainObject'; import { isStixRelationship } from '../../../src/schema/stixRelationship'; import { ENTITY_TYPE_MARKING_DEFINITION } from '../../../src/schema/stixMetaObject'; diff --git a/opencti-platform/opencti-graphql/tests/11-sync/02-Direct/sync-direct-test.js b/opencti-platform/opencti-graphql/tests/11-sync/02-Direct/sync-direct-test.js index 077decfd6e8b..3f0525041378 100644 --- a/opencti-platform/opencti-graphql/tests/11-sync/02-Direct/sync-direct-test.js +++ b/opencti-platform/opencti-graphql/tests/11-sync/02-Direct/sync-direct-test.js @@ -9,7 +9,7 @@ import { FIFTEEN_MINUTES, SYNC_DIRECT_START_REMOTE_URI, SYNC_TEST_REMOTE_URI, - testContext, + testContext } from '../../utils/testQuery'; import { checkPostSyncContent, checkPreSyncContent, REPORT_QUERY, SYNC_CREATION_QUERY, SYNC_START_QUERY, UPLOADED_FILE_SIZE } from '../sync-utils'; import { SYSTEM_USER } from '../../../src/utils/access'; @@ -59,6 +59,6 @@ describe('Database sync direct', () => { expect(uploadedFile.name).toEqual(DATA_FILE_TEST); expect(uploadedFile.size).toEqual(UPLOADED_FILE_SIZE); }, - FIFTEEN_MINUTES, + FIFTEEN_MINUTES ); }); diff --git a/opencti-platform/opencti-graphql/tests/11-sync/sync-utils.js b/opencti-platform/opencti-graphql/tests/11-sync/sync-utils.js index f962db4a5ea5..9e73c2c28e07 100644 --- a/opencti-platform/opencti-graphql/tests/11-sync/sync-utils.js +++ b/opencti-platform/opencti-graphql/tests/11-sync/sync-utils.js @@ -67,7 +67,7 @@ export const VOCABULARY_NUMBERS = 357; export const INDICATOR_NUMBERS = 28; export const MALWARE_NUMBERS = 27; export const LABEL_NUMBERS = 17; -export const UPLOADED_FILE_SIZE = 42232; +export const UPLOADED_FILE_SIZE = 42204; const filterOutDeleteOperationRefs = { mode: 'and', diff --git a/opencti-platform/opencti-graphql/tests/data/DATA-TEST-STIX2_v2.json b/opencti-platform/opencti-graphql/tests/data/DATA-TEST-STIX2_v2.json index d4292bfb01f9..de2f6ee8d15a 100644 --- a/opencti-platform/opencti-graphql/tests/data/DATA-TEST-STIX2_v2.json +++ b/opencti-platform/opencti-graphql/tests/data/DATA-TEST-STIX2_v2.json @@ -580,7 +580,6 @@ "spec_version": "2.1", "x_mitre_id": "T1369", "name": "Spear phishing messages with malicious links", - "aliases": ["T1369"], "labels": ["attack-pattern"], "description": "This technique has been deprecated. Please see ATT&CK's Initial Access and Execution tactics for replacement techniques.\n\nEmails with malicious links are designed to get a user to click on the link in order to deliver malware payloads. (Citation: GoogleDrive Phishing) (Citation: RSASEThreat)", "created": "2017-12-14T16:46:06.044Z", From a1674d9e98116094ba4962501935cf6e9a11e929 Mon Sep 17 00:00:00 2001 From: MTorbay-Filigran Date: Tue, 6 Jan 2026 17:26:30 +0100 Subject: [PATCH 039/126] [CI] Mocking the EE for integration tests (#13736) --- .../src/manager/fileIndexManager.ts | 2 +- .../01-database/disseminationList-test.ts | 15 +- .../01-database/indicator-domain-test.ts | 22 +-- .../01-database/ingestion-csv-domain-test.ts | 30 +-- .../middleware-organization-test.ts | 18 +- .../middleware-restricted-members-test.ts | 45 +++-- .../01-database/playbook-components-test.ts | 77 ++++---- .../01-database/rabbitmq-test.js | 21 +- .../01-database/requestAccess-domain-test.ts | 101 +++++----- .../01-database/sorting-test.ts | 4 + .../01-database/user-domain-test.ts | 36 ++-- .../container-authorized-members-test.ts | 186 +++++++++--------- .../disseminationListResolver-test.ts | 16 +- .../02-resolvers/emailTemplate-test.ts | 37 ++-- .../02-resolvers/fintelDesign-test.ts | 9 +- .../02-resolvers/fintelTemplate-test.ts | 23 ++- .../02-resolvers/organization-sharing-test.ts | 179 +++++++++-------- .../03-integration/02-resolvers/pir-test.ts | 40 ++-- .../02-resolvers/playbook-test.ts | 32 +-- .../02-resolvers/report-test.js | 6 +- .../02-resolvers/requestAccess-test.ts | 76 +++---- .../02-resolvers/stixDomainObject-test.js | 2 + .../02-resolvers/stream-test.ts | 24 +-- .../02-resolvers/taxiiCollection-test.ts | 26 +-- .../03-integration/02-resolvers/user-test.ts | 43 ++-- .../04-manager/connectorManager-test.ts | 22 ++- .../04-manager/telemetryManager-test.ts | 4 +- .../opencti-graphql/tests/utils/testQuery.ts | 88 +++++---- .../tests/utils/testQueryHelper.ts | 66 ++----- 29 files changed, 666 insertions(+), 584 deletions(-) diff --git a/opencti-platform/opencti-graphql/src/manager/fileIndexManager.ts b/opencti-platform/opencti-graphql/src/manager/fileIndexManager.ts index 77280bb74f3f..7f7e4603dc96 100644 --- a/opencti-platform/opencti-graphql/src/manager/fileIndexManager.ts +++ b/opencti-platform/opencti-graphql/src/manager/fileIndexManager.ts @@ -150,7 +150,7 @@ const initFileIndexManager = () => { const fileIndexHandler = async () => { const context = executionContext(FILE_INDEX_MANAGER_NAME); const settings = await getEntityFromCache(context, SYSTEM_USER, ENTITY_TYPE_SETTINGS); - if (settings.valid_enterprise_edition === true) { + if (settings && settings.valid_enterprise_edition === true) { let lock; try { // Lock the manager diff --git a/opencti-platform/opencti-graphql/tests/03-integration/01-database/disseminationList-test.ts b/opencti-platform/opencti-graphql/tests/03-integration/01-database/disseminationList-test.ts index da3d3e4d5efe..a6a987fa8fc6 100644 --- a/opencti-platform/opencti-graphql/tests/03-integration/01-database/disseminationList-test.ts +++ b/opencti-platform/opencti-graphql/tests/03-integration/01-database/disseminationList-test.ts @@ -1,23 +1,30 @@ -import { describe, it, expect } from 'vitest'; +import { describe, it, expect, vi, beforeAll } from 'vitest'; import type { DisseminationListAddInput, EditInput } from '../../../src/generated/graphql'; import { addDisseminationList, deleteDisseminationList, fieldPatchDisseminationList } from '../../../src/modules/disseminationList/disseminationList-domain'; import { buildStandardUser, testContext } from '../../utils/testQuery'; import type { StoreEntityDisseminationList } from '../../../src/modules/disseminationList/disseminationList-types'; +import * as entrepriseEdition from '../../../src/enterprise-edition/ee'; const TEST_DISSEMINATION_USER_SET = buildStandardUser([], [], [{ name: 'SETTINGS_SETDISSEMINATE' }]); const TEST_DISSEMINATION_LIST_CREATE_INPUT: DisseminationListAddInput = { name: 'Dissemination list', description: 'Description', - emails: ['example1@email.com', 'sample.account@email.com', 'firstname.lastname@email.com', 'user123@email.com', 'contact@domain.com', 'info@example.net', 'test.email@email.org', 'random.user@email.co', 'support@email.io', 'myaddress@email.biz'] + emails: ['example1@email.com', 'sample.account@email.com', 'firstname.lastname@email.com', 'user123@email.com', 'contact@domain.com', 'info@example.net', 'test.email@email.org', 'random.user@email.co', 'support@email.io', 'myaddress@email.biz'], }; const TEST_DISSEMINATION_LIST_UPDATE_INPUT: EditInput[] = [ { key: 'name', value: ['New Dissemination list'] }, { key: 'description', value: ['New description'] }, - { key: 'emails', value: ['example1@email.com', 'sample.account@email.com'] } + { key: 'emails', value: ['example1@email.com', 'sample.account@email.com'] }, ]; -describe('Create dissemination list', () => { +describe('Create dissemination list', async () => { + beforeAll(() => { + // Activate EE for this test + vi.spyOn(entrepriseEdition, 'checkEnterpriseEdition').mockResolvedValue(); + vi.spyOn(entrepriseEdition, 'isEnterpriseEdition').mockResolvedValue(true); + }); let data: StoreEntityDisseminationList; + it('should create a dissemination list for settings user', async () => { data = await addDisseminationList(testContext, TEST_DISSEMINATION_USER_SET, TEST_DISSEMINATION_LIST_CREATE_INPUT); expect(data.name, 'List created').toBe('Dissemination list'); diff --git a/opencti-platform/opencti-graphql/tests/03-integration/01-database/indicator-domain-test.ts b/opencti-platform/opencti-graphql/tests/03-integration/01-database/indicator-domain-test.ts index 078a996a994e..66da8392287e 100644 --- a/opencti-platform/opencti-graphql/tests/03-integration/01-database/indicator-domain-test.ts +++ b/opencti-platform/opencti-graphql/tests/03-integration/01-database/indicator-domain-test.ts @@ -24,8 +24,8 @@ describe('Testing field patch and upsert on indicator for trio {score, valid unt }); // Region cleanup of created data - const indicatorCreatedIds : string[] = []; - const createIndicator = async (user:AuthUser, input: IndicatorAddInput): Promise => { + const indicatorCreatedIds: string[] = []; + const createIndicator = async (user: AuthUser, input: IndicatorAddInput): Promise => { const indicator = await addIndicator(testContext, user, input); indicatorCreatedIds.push(indicator.id); return indicator; @@ -84,7 +84,7 @@ describe('Testing field patch and upsert on indicator for trio {score, valid unt pattern_type: STIX_PATTERN_TYPE, x_opencti_score: 85, valid_from: inPast90Days, - valid_until: tomorrow + valid_until: tomorrow, }; const indicatorWithoutDecay = await createIndicator(ADMIN_USER, indicatorNoDecayInput); @@ -111,7 +111,7 @@ describe('Testing field patch and upsert on indicator for trio {score, valid unt pattern_type: STIX_PATTERN_TYPE, x_opencti_score: 83, valid_until: tomorrow, - created: fiveDaysAgo + created: fiveDaysAgo, }; const indicatorWithoutValidFrom = await createIndicator(ADMIN_USER, indicatorInput); const indicatorCreated = await findById(testContext, ADMIN_USER, indicatorWithoutValidFrom.id); @@ -165,7 +165,7 @@ describe('Testing field patch and upsert on indicator for trio {score, valid unt const lastHistoryEntry = history[0]; expect( lastHistoryEntry?.score, - 'The lifecycle history should have a new entry with the score set to revoke score' + 'The lifecycle history should have a new entry with the score set to revoke score', ).toBe(indicatorWithDecay.decay_applied_rule.decay_revoke_score); }); @@ -203,7 +203,7 @@ describe('Testing field patch and upsert on indicator for trio {score, valid unt const lastHistoryEntry = history[0]; expect( lastHistoryEntry.score, - 'The lifecycle history should have a new entry with the score updated' + 'The lifecycle history should have a new entry with the score updated', ).toBe(indicatorWithDecay.decay_base_score); }); @@ -217,7 +217,7 @@ describe('Testing field patch and upsert on indicator for trio {score, valid unt pattern_type: STIX_PATTERN_TYPE, x_opencti_score: 85, valid_from: inPast90Days, - valid_until: tomorrow + valid_until: tomorrow, }; const indicatorWithoutDecay = await createIndicator(ADMIN_USER, indicatorNoDecayInput); @@ -291,7 +291,7 @@ describe('Testing field patch and upsert on indicator for trio {score, valid unt x_opencti_score: 5, valid_from: inPast90Days, valid_until: fiveDaysAgo, - revoked: true + revoked: true, }; const indicatorRevoked = await createIndicator(ADMIN_USER, indicatorNoDecayInput); expect(indicatorRevoked.revoked).toBeTruthy(); @@ -304,7 +304,7 @@ describe('Testing field patch and upsert on indicator for trio {score, valid unt pattern_type: STIX_PATTERN_TYPE, valid_from: inPast90Days, valid_until: tomorrow, - revoked: false + revoked: false, }; const indicatorUpsertEntity = await createIndicator(ADMIN_USER, indicatorUpsert); expect(indicatorUpsertEntity.revoked).toBeFalsy(); @@ -322,7 +322,7 @@ describe('Testing field patch and upsert on indicator for trio {score, valid unt x_opencti_score: 5, valid_from: inPast90Days, valid_until: fiveDaysAgo, - revoked: true + revoked: true, }; const indicatorRevoked = await createIndicator(ADMIN_USER, indicatorNoDecayInput); expect(indicatorRevoked.revoked).toBeTruthy(); @@ -336,7 +336,7 @@ describe('Testing field patch and upsert on indicator for trio {score, valid unt valid_from: inPast90Days, valid_until: tomorrow, x_opencti_score: 80, - revoked: false + revoked: false, }; const indicatorUpsertEntity = await createIndicator(ADMIN_USER, indicatorUpsert); expect(indicatorUpsertEntity.revoked).toBeFalsy(); diff --git a/opencti-platform/opencti-graphql/tests/03-integration/01-database/ingestion-csv-domain-test.ts b/opencti-platform/opencti-graphql/tests/03-integration/01-database/ingestion-csv-domain-test.ts index d7b7c42d343b..837aaf1bda71 100644 --- a/opencti-platform/opencti-graphql/tests/03-integration/01-database/ingestion-csv-domain-test.ts +++ b/opencti-platform/opencti-graphql/tests/03-integration/01-database/ingestion-csv-domain-test.ts @@ -1,15 +1,16 @@ -import { afterAll, beforeAll, describe, it, expect } from 'vitest'; +import { afterAll, beforeAll, describe, it, expect, vi } from 'vitest'; import gql from 'graphql-tag'; import { addIngestionCsv, deleteIngestionCsv, ingestionCsvAddAutoUser } from '../../../src/modules/ingestion/ingestion-csv-domain'; import { adminQuery, PLATFORM_ORGANIZATION, USER_EDITOR } from '../../utils/testQuery'; import { type EditInput, IngestionAuthType, type IngestionCsv, type IngestionCsvAddAutoUserInput, type IngestionCsvAddInput } from '../../../src/generated/graphql'; -import { enableCEAndUnSetOrganization, enableEEAndSetOrganization } from '../../utils/testQueryHelper'; +import { unSetOrganization, setOrganization } from '../../utils/testQueryHelper'; import { getFakeAuthUser, getOrganizationEntity } from '../../utils/domainQueryHelper'; import type { AuthContext, AuthUser } from '../../../src/types/user'; import { findDefaultIngestionGroups, groupEditField } from '../../../src/domain/group'; import type { BasicGroupEntity } from '../../../src/types/store'; import { findById as findUserById } from '../../../src/domain/user'; import { executionContext, SYSTEM_USER } from '../../../src/utils/access'; +import * as entrepriseEdition from '../../../src/enterprise-edition/ee'; const DELETE_USER_QUERY = gql` mutation userDelete($id: ID!) { @@ -39,13 +40,16 @@ describe('Ingestion CSV domain - create CSV Feed coverage', async () => { let ingestionDefaultGroupId: string; beforeAll(async () => { + // Activate EE for this test + vi.spyOn(entrepriseEdition, 'checkEnterpriseEdition').mockResolvedValue(); + vi.spyOn(entrepriseEdition, 'isEnterpriseEdition').mockResolvedValue(true); ingestionUser = getFakeAuthUser('CsvFeedIngestionDomain'); ingestionUser.capabilities = [{ name: 'KNOWLEDGE' }, { name: 'INGESTION_SETINGESTIONS' }]; currentTestContext = executionContext('testContext', ingestionUser); }); afterAll(async () => { - await enableCEAndUnSetOrganization(); + await unSetOrganization(); for (let i = 0; i < ingestionCreatedIds.length; i += 1) { await deleteIngestionCsv(currentTestContext, ingestionUser, ingestionCreatedIds[i]); } @@ -66,7 +70,7 @@ describe('Ingestion CSV domain - create CSV Feed coverage', async () => { uri: 'http://fakefeed.invalid', user_id: '[F] CSV Feed to test auto user creation without platform org', automatic_user: true, - confidence_level: 42 + confidence_level: 42, }; const ingestionCreated = await addIngestionCsv(currentTestContext, ingestionUser, ingestionCsvInput); expect(ingestionCreated.name).toBe('CSV Feed to test auto user creation without platform org'); @@ -94,7 +98,7 @@ describe('Ingestion CSV domain - create CSV Feed coverage', async () => { }); it('should create a CSV Feed with auto user creation works fine with platform org', async () => { - await enableEEAndSetOrganization(PLATFORM_ORGANIZATION); + await setOrganization(PLATFORM_ORGANIZATION); const platformOrganization = await getOrganizationEntity(PLATFORM_ORGANIZATION); const ingestionCsvInput: IngestionCsvAddInput = { @@ -103,7 +107,7 @@ describe('Ingestion CSV domain - create CSV Feed coverage', async () => { uri: 'http://fakefeed.invalid', user_id: '[F] CSV Feed to test auto user creation with platform org', automatic_user: true, - confidence_level: 81 + confidence_level: 81, }; const ingestionCreated = await addIngestionCsv(currentTestContext, ingestionUser, ingestionCsvInput); expect(ingestionCreated.name).toBe('CSV Feed to test auto user creation with platform org'); @@ -140,7 +144,7 @@ describe('Ingestion CSV domain - create CSV Feed coverage', async () => { authentication_type: IngestionAuthType.None, name: 'CSV Feed to test with system user', uri: 'http://fakefeed.invalid', - user_id: '' + user_id: '', }; await expect(async () => { @@ -154,7 +158,7 @@ describe('Ingestion CSV domain - create CSV Feed coverage', async () => { name: 'CSV Feed to test existing user setup', uri: 'http://fakefeed.invalid', user_id: USER_EDITOR.id, - confidence_level: 88 + confidence_level: 88, }; const ingestionCreated = await addIngestionCsv(currentTestContext, ingestionUser, ingestionCsvInput); expect(ingestionCreated.name).toBe('CSV Feed to test existing user setup'); @@ -173,7 +177,7 @@ describe('Ingestion CSV domain - create CSV Feed coverage', async () => { name: 'MyFééd @ Testing @mail.fr 🌈🍅 - CSVフィードの作成', uri: 'http://fakefeed.invalid', user_id: '[F] MyFééd @ Testing @mail.fr 🌈🍅 - CSVフィードの作成', - automatic_user: true + automatic_user: true, }; const ingestionCreated = await addIngestionCsv(currentTestContext, ingestionUser, ingestionCsvInput); expect(ingestionCreated.name).toBe('MyFééd @ Testing @mail.fr 🌈🍅 - CSVフィードの作成'); @@ -210,7 +214,7 @@ describe('Ingestion CSV domain - create CSV Feed coverage', async () => { name: 'Feed created with auto user by no default ingestion group in config', uri: 'http://fakefeed.invalid', user_id: '[F] should not be created', - automatic_user: true + automatic_user: true, }; await expect(async () => { await addIngestionCsv(currentTestContext, ingestionUser, ingestionCsvInput); @@ -233,7 +237,7 @@ describe('Ingestion CSV domain - create CSV Feed coverage', async () => { name: 'Feed not created because auto service account already exists', uri: 'http://fakefeed.invalid', user_id: '[F] Feed not created because auto service account already exists', - automatic_user: true + automatic_user: true, }; // First call const firstIngestionCreated = await addIngestionCsv(currentTestContext, ingestionUser, ingestionCsvInput); @@ -272,7 +276,7 @@ describe('Ingestion CSV domain - ingestionCsvAddAutoUser', async () => { uri: 'http://fakefeed.invalid', user_id: '[F] CSV Feed to test with auto user', automatic_user: true, - confidence_level: 32 + confidence_level: 32, }; ingestionCreated = await addIngestionCsv(currentTestContext, ingestionUser, ingestionCsvInput); }); @@ -298,7 +302,7 @@ describe('Ingestion CSV domain - ingestionCsvAddAutoUser', async () => { it('should create an automatic user and associate it to the ingestion feed', async () => { const ingestionCsvAddAutoUserInput: IngestionCsvAddAutoUserInput = { user_name: '[F] should create automatic user', - confidence_level: 63 + confidence_level: 63, }; const ingestionModified = await ingestionCsvAddAutoUser(currentTestContext, ingestionUser, ingestionCreated.id, ingestionCsvAddAutoUserInput); diff --git a/opencti-platform/opencti-graphql/tests/03-integration/01-database/middleware-organization-test.ts b/opencti-platform/opencti-graphql/tests/03-integration/01-database/middleware-organization-test.ts index bd441ce7ac0b..7817934c5930 100644 --- a/opencti-platform/opencti-graphql/tests/03-integration/01-database/middleware-organization-test.ts +++ b/opencti-platform/opencti-graphql/tests/03-integration/01-database/middleware-organization-test.ts @@ -1,7 +1,7 @@ -import { beforeAll, afterAll, describe, expect, it } from 'vitest'; +import { beforeAll, afterAll, describe, expect, it, vi } from 'vitest'; import { now } from 'moment'; import { GraphQLError } from 'graphql/index'; -import { enableCEAndUnSetOrganization, enableEEAndSetOrganization } from '../../utils/testQueryHelper'; +import { unSetOrganization, setOrganization } from '../../utils/testQueryHelper'; import { ADMIN_USER, PLATFORM_ORGANIZATION, testContext, TEST_ORGANIZATION, GREEN_GROUP, inPlatformContext } from '../../utils/testQuery'; import type { ThreatActorIndividualAddInput } from '../../../src/generated/graphql'; import { type BasicStoreEntityOrganization } from '../../../src/modules/organization/organization-types'; @@ -12,6 +12,7 @@ import { MARKING_TLP_RED } from '../../../src/schema/identifier'; import { stixDomainObjectDelete } from '../../../src/domain/stixDomainObject'; import { DEFAULT_ROLE } from '../../../src/utils/access'; import { getFakeAuthUser, getGroupEntity, getOrganizationEntity } from '../../utils/domainQueryHelper'; +import * as entrepriseEdition from '../../../src/enterprise-edition/ee'; describe('Middleware test coverage on organization sharing verification', () => { let userInPlatformOrg: AuthUser; @@ -20,7 +21,10 @@ describe('Middleware test coverage on organization sharing verification', () => let platformOrganizationEntity: BasicStoreEntityOrganization; beforeAll(async () => { - await enableEEAndSetOrganization(PLATFORM_ORGANIZATION); + // Activate EE for this test + vi.spyOn(entrepriseEdition, 'checkEnterpriseEdition').mockResolvedValue(); + vi.spyOn(entrepriseEdition, 'isEnterpriseEdition').mockResolvedValue(true); + await setOrganization(PLATFORM_ORGANIZATION); platformOrganizationEntity = await getOrganizationEntity(PLATFORM_ORGANIZATION); externalOrganizationEntity = await getOrganizationEntity(TEST_ORGANIZATION); @@ -40,7 +44,7 @@ describe('Middleware test coverage on organization sharing verification', () => }); afterAll(async () => { - await enableCEAndUnSetOrganization(); + await unSetOrganization(); }); describe('Trying to create an existing entity that is not shared to user should raise a dedicated exception.', () => { @@ -48,7 +52,7 @@ describe('Middleware test coverage on organization sharing verification', () => const threatActorIndividualName = `Testing org segregation ${now()}`; const inputOne: ThreatActorIndividualAddInput = { name: threatActorIndividualName, - description: 'Created by user in org platform' + description: 'Created by user in org platform', }; const threatActor = await addThreatActorIndividual(inPlatformContext, userInPlatformOrg, inputOne); @@ -56,7 +60,7 @@ describe('Middleware test coverage on organization sharing verification', () => try { const inputNext: ThreatActorIndividualAddInput = { name: threatActorIndividualName, - description: 'Created by external user' + description: 'Created by external user', }; await addThreatActorIndividual(testContext, userInExternalOrg, inputNext); expect(true, 'An exception should been raised before this line').toBeFalsy(); @@ -72,7 +76,7 @@ describe('Middleware test coverage on organization sharing verification', () => const inputOne: ThreatActorIndividualAddInput = { name: threatActorIndividualName, description: 'Created by user with TLP:RED', - objectMarking: [MARKING_TLP_RED] + objectMarking: [MARKING_TLP_RED], }; const threatActor = await addThreatActorIndividual(testContext, ADMIN_USER, inputOne); const inputNext: ThreatActorIndividualAddInput = { diff --git a/opencti-platform/opencti-graphql/tests/03-integration/01-database/middleware-restricted-members-test.ts b/opencti-platform/opencti-graphql/tests/03-integration/01-database/middleware-restricted-members-test.ts index c90a2c312082..ef476d328a27 100644 --- a/opencti-platform/opencti-graphql/tests/03-integration/01-database/middleware-restricted-members-test.ts +++ b/opencti-platform/opencti-graphql/tests/03-integration/01-database/middleware-restricted-members-test.ts @@ -1,7 +1,7 @@ -import { afterAll, beforeAll, describe, expect, it } from 'vitest'; +import { afterAll, beforeAll, describe, expect, it, vi } from 'vitest'; import type { AuthUser } from '../../../src/types/user'; import type { BasicStoreEntityOrganization } from '../../../src/modules/organization/organization-types'; -import { enableCEAndUnSetOrganization, enableEE, enableEEAndSetOrganization } from '../../utils/testQueryHelper'; +import { unSetOrganization, setOrganization } from '../../utils/testQueryHelper'; import { AMBER_GROUP, GREEN_GROUP, PLATFORM_ORGANIZATION, TEST_ORGANIZATION, testContext } from '../../utils/testQuery'; import { getFakeAuthUser, getGroupEntity, getOrganizationEntity } from '../../utils/domainQueryHelper'; import { DEFAULT_ROLE, SYSTEM_USER } from '../../../src/utils/access'; @@ -11,6 +11,7 @@ import type { Group } from '../../../src/types/group'; import { internalDeleteElementById } from '../../../src/database/middleware'; import { now } from '../../../src/utils/format'; import { ENTITY_TYPE_CONTAINER_CASE_RFI } from '../../../src/modules/case/case-rfi/case-rfi-types'; +import * as entrepriseEdition from '../../../src/enterprise-edition/ee'; describe('Middleware test coverage on restricted_members configuration', () => { let userPlatformOrgGreenGroup: AuthUser; @@ -26,7 +27,10 @@ describe('Middleware test coverage on restricted_members configuration', () => { const idToDelete: string[] = []; beforeAll(async () => { - await enableEEAndSetOrganization(PLATFORM_ORGANIZATION); + // Activate EE for this test + vi.spyOn(entrepriseEdition, 'checkEnterpriseEdition').mockResolvedValue(); + vi.spyOn(entrepriseEdition, 'isEnterpriseEdition').mockResolvedValue(true); + await setOrganization(PLATFORM_ORGANIZATION); platformOrganizationEntity = await getOrganizationEntity(PLATFORM_ORGANIZATION); testOrganizationEntity = await getOrganizationEntity(TEST_ORGANIZATION); @@ -62,7 +66,7 @@ describe('Middleware test coverage on restricted_members configuration', () => { for (let i = 0; i < idToDelete.length; i += 1) { await internalDeleteElementById(testContext, SYSTEM_USER, idToDelete[i], ENTITY_TYPE_CONTAINER_CASE_RFI); // +5 RFI deleted } - await enableCEAndUnSetOrganization(); + await unSetOrganization(); }); it('should User in intersection group X org has access', async () => { @@ -73,10 +77,10 @@ describe('Middleware test coverage on restricted_members configuration', () => { { id: platformOrganizationEntity.id, access_right: 'admin', - groups_restriction_ids: [greenGroup.id] - } + groups_restriction_ids: [greenGroup.id], + }, ], - revoked: false + revoked: false, }; const requestForInformation = await addCaseRfi(testContext, SYSTEM_USER, rfiInput); idToDelete.push(requestForInformation.id); @@ -91,12 +95,11 @@ describe('Middleware test coverage on restricted_members configuration', () => { }); it('Should everyone has access if no restricted member and no org platform', async () => { // disable org sharing - await enableCEAndUnSetOrganization(); - await enableEE(); + await unSetOrganization(); const rfiInput: CaseRfiAddInput = { name: 'CaseRFI no restricted members', created: now(), - revoked: false + revoked: false, }; const requestForInformation = await addCaseRfi(testContext, SYSTEM_USER, rfiInput); idToDelete.push(requestForInformation.id); @@ -108,7 +111,9 @@ describe('Middleware test coverage on restricted_members configuration', () => { expect(result_userTestOrgGreenGroup).toBeDefined(); // enable EE and org sharing - await enableEEAndSetOrganization(PLATFORM_ORGANIZATION); + await setOrganization(PLATFORM_ORGANIZATION); + vi.spyOn(entrepriseEdition, 'checkEnterpriseEdition').mockResolvedValue(); + vi.spyOn(entrepriseEdition, 'isEnterpriseEdition').mockResolvedValue(true); }); it('Should User not in group X org but in additional User restricted member has access', async () => { const rfiInput: CaseRfiAddInput = { @@ -118,14 +123,14 @@ describe('Middleware test coverage on restricted_members configuration', () => { { id: platformOrganizationEntity.id, access_right: 'admin', - groups_restriction_ids: [greenGroup.id] + groups_restriction_ids: [greenGroup.id], }, { id: userTestOrgAmberGroup.id, access_right: 'admin', - } + }, ], - revoked: false + revoked: false, }; const requestForInformation = await addCaseRfi(testContext, SYSTEM_USER, rfiInput); idToDelete.push(requestForInformation.id); @@ -142,14 +147,14 @@ describe('Middleware test coverage on restricted_members configuration', () => { { id: platformOrganizationEntity.id, access_right: 'admin', - groups_restriction_ids: [greenGroup.id] + groups_restriction_ids: [greenGroup.id], }, { id: amberGroup.id, access_right: 'admin', - } + }, ], - revoked: false + revoked: false, }; const requestForInformation = await addCaseRfi(testContext, SYSTEM_USER, rfiInput); idToDelete.push(requestForInformation.id); @@ -166,14 +171,14 @@ describe('Middleware test coverage on restricted_members configuration', () => { { id: platformOrganizationEntity.id, access_right: 'admin', - groups_restriction_ids: [greenGroup.id] + groups_restriction_ids: [greenGroup.id], }, { id: testOrganizationEntity.id, access_right: 'admin', - } + }, ], - revoked: false + revoked: false, }; const requestForInformation = await addCaseRfi(testContext, SYSTEM_USER, rfiInput); idToDelete.push(requestForInformation.id); diff --git a/opencti-platform/opencti-graphql/tests/03-integration/01-database/playbook-components-test.ts b/opencti-platform/opencti-graphql/tests/03-integration/01-database/playbook-components-test.ts index b233e7fcad27..51a69ff02fb8 100644 --- a/opencti-platform/opencti-graphql/tests/03-integration/01-database/playbook-components-test.ts +++ b/opencti-platform/opencti-graphql/tests/03-integration/01-database/playbook-components-test.ts @@ -1,25 +1,29 @@ -import { afterAll, beforeAll, describe, expect, it } from 'vitest'; +import { afterAll, beforeAll, describe, expect, it, vi } from 'vitest'; import { PLAYBOOK_CONTAINER_WRAPPER_COMPONENT, PLAYBOOK_SHARING_COMPONENT } from '../../../src/modules/playbook/playbook-components'; import type { StixBundle } from '../../../src/types/stix-2-1-common'; import type { BasicStoreEntityOrganization } from '../../../src/modules/organization/organization-types'; -import { enableCEAndUnSetOrganization, enableEEAndSetOrganization } from '../../utils/testQueryHelper'; +import { unSetOrganization, setOrganization } from '../../utils/testQueryHelper'; import { PLATFORM_ORGANIZATION, TEST_ORGANIZATION } from '../../utils/testQuery'; import { getOrganizationEntity } from '../../utils/domainQueryHelper'; import { sharing_component_bundle } from './playbookComponents/playbook-sharing-component'; import { container_wrapper_component_bundle } from './playbookComponents/playbook-container-wrapper-component'; import { STIX_EXT_OCTI } from '../../../src/types/stix-2-1-extensions'; +import * as entrepriseEdition from '../../../src/enterprise-edition/ee'; describe('playbook sharing component', () => { let externalOrganizationEntity: BasicStoreEntityOrganization; beforeAll(async () => { - await enableEEAndSetOrganization(PLATFORM_ORGANIZATION); + // Activate EE for this test + vi.spyOn(entrepriseEdition, 'checkEnterpriseEdition').mockResolvedValue(); + vi.spyOn(entrepriseEdition, 'isEnterpriseEdition').mockResolvedValue(true); + await setOrganization(PLATFORM_ORGANIZATION); externalOrganizationEntity = await getOrganizationEntity(TEST_ORGANIZATION); }); afterAll(async () => { - await enableCEAndUnSetOrganization(); + await unSetOrganization(); }); it('should share report and contained entities with "all" option', async () => { @@ -30,16 +34,16 @@ describe('playbook sharing component', () => { organizations: [ { label: externalOrganizationEntity.name, - value: externalOrganizationEntity.id - } + value: externalOrganizationEntity.id, + }, ], - all: true + all: true, }, id: '651475c0-04ae-423d-88d3-734c35e65c07', name: 'Share with organizations', position: { - y: 150 - } + y: 150, + }, }; const bundleToIngest = { id: '411628bf-745b-43f6-8194-cbe441edecfd', @@ -52,26 +56,26 @@ describe('playbook sharing component', () => { 'extension-definition--ea279b3e-5c71-4632-ac08-831c66a786ba': { created_at: '2025-03-25T09:59:17.024Z', creator_ids: [ - '88ec0c6a-13ce-5e39-b486-354fe4a7084f' + '88ec0c6a-13ce-5e39-b486-354fe4a7084f', ], extension_type: 'property-extension', granted_refs: [externalOrganizationEntity.standard_id], id: '82e80255-9793-4283-b34b-872b30f23f57', type: 'Report', updated_at: '2025-03-25T09:59:44.832Z', - workflow_id: 'a4b90e6f-06ae-461a-8dac-666cdb4a5ae7' - } + workflow_id: 'a4b90e6f-06ae-461a-8dac-666cdb4a5ae7', + }, }, id: 'report--b70b1781-f963-5790-9fe7-55aec16c05f4', lang: 'en', modified: '2025-03-25T09:59:44.832Z', name: 'report 28', object_refs: [ - 'campaign--fdcacc8e-de4d-5a13-8886-401d363664fd' + 'campaign--fdcacc8e-de4d-5a13-8886-401d363664fd', ], published: '2025-03-25T09:59:10.000Z', spec_version: '2.1', - type: 'report' + type: 'report', }, { id: 'campaign--fdcacc8e-de4d-5a13-8886-401d363664fd', @@ -86,10 +90,10 @@ describe('playbook sharing component', () => { updated_at: '2025-03-26T10:00:10.363Z', is_inferred: false, creator_ids: [ - '88ec0c6a-13ce-5e39-b486-354fe4a7084f' + '88ec0c6a-13ce-5e39-b486-354fe4a7084f', ], - granted_refs: [externalOrganizationEntity.standard_id] - } + granted_refs: [externalOrganizationEntity.standard_id], + }, }, created: '2020-02-29T14:48:31.601Z', modified: '2025-04-10T16:34:18.572Z', @@ -97,7 +101,7 @@ describe('playbook sharing component', () => { confidence: 100, lang: 'en', labels: [ - 'campaign' + 'campaign', ], name: 'admin@338', description: 'description', @@ -105,7 +109,7 @@ describe('playbook sharing component', () => { }, ], spec_version: '2.1', - type: 'bundle' + type: 'bundle', } as unknown as StixBundle; const result = await PLAYBOOK_SHARING_COMPONENT.executor({ @@ -116,7 +120,7 @@ describe('playbook sharing component', () => { previousPlaybookNodeId: '', previousStepBundle: sharing_component_bundle, playbookNode, - bundle: sharing_component_bundle + bundle: sharing_component_bundle, }); expect(result.bundle).toEqual(bundleToIngest); }); @@ -124,11 +128,14 @@ describe('playbook sharing component', () => { describe('playbook container wrapper component', () => { beforeAll(async () => { - await enableEEAndSetOrganization(PLATFORM_ORGANIZATION); + // Activate EE for this test + vi.spyOn(entrepriseEdition, 'checkEnterpriseEdition').mockResolvedValue(); + vi.spyOn(entrepriseEdition, 'isEnterpriseEdition').mockResolvedValue(true); + await setOrganization(PLATFORM_ORGANIZATION); }); afterAll(async () => { - await enableCEAndUnSetOrganization(); + await unSetOrganization(); }); it('should wrap Incident in Incident Response container', async () => { const dataInstanceId = 'incident--c6c2b96d-fe70-5099-a033-87cbfe2d6be2'; @@ -137,7 +144,7 @@ describe('playbook container wrapper component', () => { name: 'Container wrapper', position: { x: 0, - y: 150 + y: 150, }, component_id: 'PLAYBOOK_CONTAINER_WRAPPER_COMPONENT', configuration: { @@ -145,7 +152,7 @@ describe('playbook container wrapper component', () => { all: false, newContainer: false, caseTemplates: [], - } + }, }; const expectedBundleToIngest = { id: '1c775f39-6cea-4b14-92f8-7843d2443af7', @@ -165,9 +172,9 @@ describe('playbook container wrapper component', () => { updated_at: '2025-05-09T10:03:11.288Z', is_inferred: false, creator_ids: [ - '88ec0c6a-13ce-5e39-b486-354fe4a7084f' - ] - } + '88ec0c6a-13ce-5e39-b486-354fe4a7084f', + ], + }, }, created: '2025-02-25T08:13:45.851Z', modified: '2025-05-09T10:03:11.288Z', @@ -175,7 +182,7 @@ describe('playbook container wrapper component', () => { confidence: 100, lang: 'en', name: 'Test Incident', - description: '' + description: '', }, { id: 'case-incident--b52ce838-2972-51ea-a538-005b36189e19', @@ -185,16 +192,16 @@ describe('playbook container wrapper component', () => { 'extension-definition--ea279b3e-5c71-4632-ac08-831c66a786ba': { extension_type: 'new-sdo', id: 'fe30c5e6-d68d-4cbe-a318-a046abaaacc8', - type: 'Case-Incident' - } + type: 'Case-Incident', + }, }, created: '2025-02-25T08:13:45.863Z', name: 'Test Incident', object_refs: [ - 'incident--c6c2b96d-fe70-5099-a033-87cbfe2d6be2' - ] - } - ] + 'incident--c6c2b96d-fe70-5099-a033-87cbfe2d6be2', + ], + }, + ], } as unknown as StixBundle; const result = await PLAYBOOK_CONTAINER_WRAPPER_COMPONENT.executor({ @@ -205,7 +212,7 @@ describe('playbook container wrapper component', () => { previousPlaybookNodeId: '', previousStepBundle: container_wrapper_component_bundle, playbookNode, - bundle: container_wrapper_component_bundle + bundle: container_wrapper_component_bundle, }); expect(result.bundle.objects.length).toEqual(2); expect(result.bundle.objects[1].id).toEqual(expectedBundleToIngest.objects[1].id); diff --git a/opencti-platform/opencti-graphql/tests/03-integration/01-database/rabbitmq-test.js b/opencti-platform/opencti-graphql/tests/03-integration/01-database/rabbitmq-test.js index 10326b774592..2ab0b834202f 100644 --- a/opencti-platform/opencti-graphql/tests/03-integration/01-database/rabbitmq-test.js +++ b/opencti-platform/opencti-graphql/tests/03-integration/01-database/rabbitmq-test.js @@ -1,4 +1,4 @@ -import { expect, it, describe } from 'vitest'; +import { expect, it, describe, afterAll, beforeAll } from 'vitest'; import { v4 as uuid } from 'uuid'; import { getConnectorQueueDetails, metrics, purgeConnectorQueues, pushToConnector, registerConnectorQueues, unregisterConnector } from '../../../src/database/rabbitmq'; import { CONNECTOR_INTERNAL_IMPORT_FILE } from '../../../src/schema/general'; @@ -10,6 +10,25 @@ describe('Rabbit connector management', () => { const connectorName = 'MY STIX IMPORTER'; const connectorType = CONNECTOR_INTERNAL_IMPORT_FILE; const connectorScope = 'application/json'; + + beforeAll(async () => { + try { + await purgeConnectorQueues({ id: testConnectorId }); + await unregisterConnector(testConnectorId); + } catch (e) { + console.warn(`${e} : unregisterConnector failed in rabbitmq-test`); + } + }); + + afterAll(async () => { + try { + await purgeConnectorQueues({ id: testConnectorId }); + await unregisterConnector(testConnectorId); + } catch (e) { + console.warn(`${e} : unregisterConnector failed in rabbitmq-test`); + } + }); + it('should register the connector', async () => { const config = await registerConnectorQueues(testConnectorId, connectorName, connectorType, connectorScope); expect(config.uri).not.toBeNull(); diff --git a/opencti-platform/opencti-graphql/tests/03-integration/01-database/requestAccess-domain-test.ts b/opencti-platform/opencti-graphql/tests/03-integration/01-database/requestAccess-domain-test.ts index cd98b1f6f9e2..6bf33fcd7983 100644 --- a/opencti-platform/opencti-graphql/tests/03-integration/01-database/requestAccess-domain-test.ts +++ b/opencti-platform/opencti-graphql/tests/03-integration/01-database/requestAccess-domain-test.ts @@ -1,4 +1,4 @@ -import { beforeAll, afterAll, describe, expect, it } from 'vitest'; +import { beforeAll, afterAll, describe, expect, it, vi } from 'vitest'; import { ADMIN_USER, getGroupIdByName, getOrganizationIdByName, GREEN_GROUP, PLATFORM_ORGANIZATION, TEST_ORGANIZATION, testContext } from '../../utils/testQuery'; import { entitySettingEditField, findByType as findEntitySettingsByType } from '../../../src/modules/entitySetting/entitySetting-domain'; import { ENTITY_TYPE_CONTAINER_CASE_RFI } from '../../../src/modules/case/case-rfi/case-rfi-types'; @@ -12,7 +12,7 @@ import { type RequestAccessConfigureInput, StatusOrdering, StatusScope, - type StatusTemplate + type StatusTemplate, } from '../../../src/generated/graphql'; import type { BasicStoreCommon, BasicStoreEntity, BasicWorkflowStatus, BasicWorkflowTemplateEntity } from '../../../src/types/store'; import { logApp } from '../../../src/config/conf'; @@ -24,16 +24,17 @@ import { computeAuthorizedMembersForRequestAccess, configureRequestAccess, declineRequestAccess, - getRfiEntitySettings + getRfiEntitySettings, } from '../../../src/modules/requestAccess/requestAccess-domain'; import { executionContext, MEMBER_ACCESS_RIGHT_ADMIN, MEMBER_ACCESS_RIGHT_EDIT } from '../../../src/utils/access'; import type { BasicStoreEntityEntitySetting, RequestAccessFlow } from '../../../src/modules/entitySetting/entitySetting-types'; -import { enableCEAndUnSetOrganization, enableEEAndSetOrganization } from '../../utils/testQueryHelper'; +import { unSetOrganization, setOrganization } from '../../utils/testQueryHelper'; import { OPENCTI_ADMIN_UUID } from '../../../src/schema/general'; import { verifyRequestAccessEnabled } from '../../../src/modules/requestAccess/requestAccessUtils'; import type { BasicStoreSettings } from '../../../src/types/settings'; import { getFakeAuthUser, getGroupEntity } from '../../utils/domainQueryHelper'; import type { Group } from '../../../src/types/group'; +import * as entrepriseEdition from '../../../src/enterprise-edition/ee'; describe('Request access domain - initialized status', async () => { it('should initial data be created', async () => { @@ -63,24 +64,24 @@ describe('Request access domain - initialized status', async () => { it('should get request access scope status', async () => { statusTemplateGlobalRfi = await createStatusTemplate(testContext, ADMIN_USER, { name: 'GLOBAL_RFI', - color: '#b83f13' + color: '#b83f13', }); statusTemplateRequestAccess = await createStatusTemplate(testContext, ADMIN_USER, { name: 'REQUEST_ACCESS_SCOPE', - color: '#b83f13' + color: '#b83f13', }); await createStatus( testContext, ADMIN_USER, ENTITY_TYPE_CONTAINER_CASE_RFI, - { template_id: statusTemplateGlobalRfi.id, order: 666, scope: StatusScope.Global } + { template_id: statusTemplateGlobalRfi.id, order: 666, scope: StatusScope.Global }, ); await createStatus( testContext, ADMIN_USER, ENTITY_TYPE_CONTAINER_CASE_RFI, - { template_id: statusTemplateRequestAccess.id, order: 111, scope: StatusScope.RequestAccess } + { template_id: statusTemplateRequestAccess.id, order: 111, scope: StatusScope.RequestAccess }, ); const args: QueryStatusesArgs = { @@ -124,7 +125,7 @@ describe('Request access domain - initialized status', async () => { it('should get all status template by GLOBAL scope', async () => { resetCacheForEntity(ENTITY_TYPE_STATUS); const args: QueryStatusTemplatesByStatusScopeArgs = { - scope: StatusScope.Global + scope: StatusScope.Global, }; const globalTemplates: StatusTemplate[] = await findAllTemplatesByStatusScope(testContext, ADMIN_USER, args); logApp.info('[TEST] globalTemplates', { globalTemplates }); @@ -135,7 +136,7 @@ describe('Request access domain - initialized status', async () => { it('should get all status template by REQUEST_ACCESS scope', async () => { resetCacheForEntity(ENTITY_TYPE_STATUS); const args: QueryStatusTemplatesByStatusScopeArgs = { - scope: StatusScope.RequestAccess + scope: StatusScope.RequestAccess, }; const requestAccessTemplates: StatusTemplate[] = await findAllTemplatesByStatusScope(testContext, ADMIN_USER, args); logApp.info('[TEST] requestAccessTemplates', { requestAccessTemplates }); @@ -151,13 +152,19 @@ describe('Request access domain - compute RFI retricted members', async () => { let testOrganizationId: string; beforeAll(async () => { - await enableEEAndSetOrganization(PLATFORM_ORGANIZATION); + // Activate EE for this test + vi.spyOn(entrepriseEdition, 'checkEnterpriseEdition').mockResolvedValue(); + vi.spyOn(entrepriseEdition, 'isEnterpriseEdition').mockResolvedValue(true); + await setOrganization(PLATFORM_ORGANIZATION); platformOrganizationId = await getOrganizationIdByName(PLATFORM_ORGANIZATION.name); testOrganizationId = await getOrganizationIdByName(TEST_ORGANIZATION.name); }); afterAll(async () => { - await enableCEAndUnSetOrganization(); + // Deactivate EE at the end of this test - back to CE + vi.spyOn(entrepriseEdition, 'checkEnterpriseEdition').mockRejectedValue('Enterprise edition is not enabled'); + vi.spyOn(entrepriseEdition, 'isEnterpriseEdition').mockResolvedValue(false); + await unSetOrganization(); }); it('should configure request access settings', async () => { @@ -168,7 +175,7 @@ describe('Request access domain - compute RFI retricted members', async () => { raConfig.approval_admin = [greenGroupId]; const editInput: EditInput[] = [ - { key: 'request_access_workflow', value: [raConfig] } + { key: 'request_access_workflow', value: [raConfig] }, ]; await entitySettingEditField(testContext, ADMIN_USER, rfiEntitySettings.id, editInput); @@ -181,14 +188,14 @@ describe('Request access domain - compute RFI retricted members', async () => { restricted_members: [ { id: '88ec0c6a-13ce-5e39-b486-354fe4a7084f', - access_right: 'admin' + access_right: 'admin', }, { id: '55ec0c6a-13ce-5e39-b486-354fe4a7084f', - access_right: 'view' + access_right: 'view', }, ], - granted: [PLATFORM_ORGANIZATION.id] + granted: [PLATFORM_ORGANIZATION.id], }; await expect(async () => { @@ -203,55 +210,55 @@ describe('Request access domain - compute RFI retricted members', async () => { const authorizedMembers = await computeAuthorizedMembersForRequestAccess(testContext, ADMIN_USER, someEntity as BasicStoreCommon); expect(authorizedMembers.find((member) => member.access_right === MEMBER_ACCESS_RIGHT_ADMIN)).toStrictEqual({ id: OPENCTI_ADMIN_UUID, - access_right: MEMBER_ACCESS_RIGHT_ADMIN + access_right: MEMBER_ACCESS_RIGHT_ADMIN, }); expect(authorizedMembers.find((member) => member.access_right === MEMBER_ACCESS_RIGHT_EDIT)).toStrictEqual({ id: platformOrganizationId, access_right: MEMBER_ACCESS_RIGHT_EDIT, - groups_restriction_ids: [greenGroupId] + groups_restriction_ids: [greenGroupId], }); }); it('should knowledge sharing request on entity with organisation sharing use it', async () => { // No granted part const someEntity: Partial = { - granted: [testOrganizationId] + granted: [testOrganizationId], }; const authorizedMembers = await computeAuthorizedMembersForRequestAccess(testContext, ADMIN_USER, someEntity as BasicStoreCommon); expect(authorizedMembers.find((member) => member.access_right === MEMBER_ACCESS_RIGHT_ADMIN)).toStrictEqual({ id: OPENCTI_ADMIN_UUID, - access_right: MEMBER_ACCESS_RIGHT_ADMIN + access_right: MEMBER_ACCESS_RIGHT_ADMIN, }); expect(authorizedMembers.find((member) => member.access_right === MEMBER_ACCESS_RIGHT_EDIT)).toStrictEqual({ id: testOrganizationId, access_right: MEMBER_ACCESS_RIGHT_EDIT, - groups_restriction_ids: [greenGroupId] + groups_restriction_ids: [greenGroupId], }); }); it('should knowledge sharing request on entity with several organisation sharing use them', async () => { // No granted part const someEntity: Partial = { - granted: [testOrganizationId, platformOrganizationId] + granted: [testOrganizationId, platformOrganizationId], }; const authorizedMembers = await computeAuthorizedMembersForRequestAccess(testContext, ADMIN_USER, someEntity as BasicStoreCommon); expect(authorizedMembers.find((member) => member.access_right === MEMBER_ACCESS_RIGHT_ADMIN)).toStrictEqual({ id: OPENCTI_ADMIN_UUID, - access_right: MEMBER_ACCESS_RIGHT_ADMIN + access_right: MEMBER_ACCESS_RIGHT_ADMIN, }); expect(authorizedMembers.filter((member) => member.access_right === MEMBER_ACCESS_RIGHT_EDIT).length).toBe(2); expect(authorizedMembers.find((member) => member.access_right === MEMBER_ACCESS_RIGHT_EDIT && member.id === testOrganizationId)).toStrictEqual({ id: testOrganizationId, access_right: MEMBER_ACCESS_RIGHT_EDIT, - groups_restriction_ids: [greenGroupId] + groups_restriction_ids: [greenGroupId], }); expect(authorizedMembers.find((member) => member.access_right === MEMBER_ACCESS_RIGHT_EDIT && member.id === platformOrganizationId)).toStrictEqual({ id: platformOrganizationId, access_right: MEMBER_ACCESS_RIGHT_EDIT, - groups_restriction_ids: [greenGroupId] + groups_restriction_ids: [greenGroupId], }); }); }); @@ -260,15 +267,15 @@ describe('Request access domain - conditions for request access activated', asy it('should CE be forbidden to use request access', async () => { const settings: Partial = { valid_enterprise_edition: false, - platform_organization: TEST_ORGANIZATION.id + platform_organization: TEST_ORGANIZATION.id, }; const rfiSettings: Partial = { request_access_workflow: { approval_admin: [GREEN_GROUP.id], approved_workflow_id: '1234', - declined_workflow_id: '5678' - } + declined_workflow_id: '5678', + }, }; const isRequestAccessEnabled = verifyRequestAccessEnabled(settings as BasicStoreSettings, rfiSettings as BasicStoreEntityEntitySetting); @@ -278,15 +285,15 @@ describe('Request access domain - conditions for request access activated', asy it('should request access be disabled when there is no platform organization', async () => { const settings: Partial = { - valid_enterprise_edition: true + valid_enterprise_edition: true, }; const rfiSettings: Partial = { request_access_workflow: { approval_admin: [GREEN_GROUP.id], approved_workflow_id: '1234', - declined_workflow_id: '5678' - } + declined_workflow_id: '5678', + }, }; const isRequestAccessEnabled = verifyRequestAccessEnabled(settings as BasicStoreSettings, rfiSettings as BasicStoreEntityEntitySetting); @@ -297,15 +304,15 @@ describe('Request access domain - conditions for request access activated', asy it('should request access be disabled when admin group is not setup', async () => { const settings: Partial = { valid_enterprise_edition: true, - platform_organization: TEST_ORGANIZATION.id + platform_organization: TEST_ORGANIZATION.id, }; const rfiSettings: Partial = { request_access_workflow: { approval_admin: [], approved_workflow_id: '1234', - declined_workflow_id: '5678' - } + declined_workflow_id: '5678', + }, }; const isRequestAccessEnabled = verifyRequestAccessEnabled(settings as BasicStoreSettings, rfiSettings as BasicStoreEntityEntitySetting); @@ -316,13 +323,13 @@ describe('Request access domain - conditions for request access activated', asy it('should request access be disabled when approved_workflow_id or declined_workflow_id is not setup', async () => { const settings: Partial = { valid_enterprise_edition: true, - platform_organization: TEST_ORGANIZATION.id + platform_organization: TEST_ORGANIZATION.id, }; const rfiSettings: Partial = { request_access_workflow: { approval_admin: [GREEN_GROUP.id], - } + }, }; const isRequestAccessEnabled = verifyRequestAccessEnabled(settings as BasicStoreSettings, rfiSettings as BasicStoreEntityEntitySetting); @@ -333,15 +340,15 @@ describe('Request access domain - conditions for request access activated', asy it('should request access be enabled when everything is configured', async () => { const settings: Partial = { valid_enterprise_edition: true, - platform_organization: TEST_ORGANIZATION.id + platform_organization: TEST_ORGANIZATION.id, }; const rfiSettings: Partial = { request_access_workflow: { approval_admin: [GREEN_GROUP.id], approved_workflow_id: '1234', - declined_workflow_id: '5678' - } + declined_workflow_id: '5678', + }, }; const isRequestAccessEnabled = verifyRequestAccessEnabled(settings as BasicStoreSettings, rfiSettings as BasicStoreEntityEntitySetting); @@ -386,7 +393,7 @@ describe('Request access domain - configuration edition', async () => { testContext, ADMIN_USER, ENTITY_TYPE_CONTAINER_CASE_RFI, - { template_id: statusTemplateRequestAccess.id, order: 3, scope: StatusScope.RequestAccess } + { template_id: statusTemplateRequestAccess.id, order: 3, scope: StatusScope.RequestAccess }, ); greenGroup = await getGroupEntity(GREEN_GROUP); @@ -394,16 +401,16 @@ describe('Request access domain - configuration edition', async () => { afterAll(async () => { const editInput: EditInput[] = [ - { key: 'request_access_workflow', value: [initialConfig] } + { key: 'request_access_workflow', value: [initialConfig] }, ]; await entitySettingEditField(testContext, ADMIN_USER, rfiEntitySettings?.id, editInput); }); it('should disabling approval_admin in request access be allowed', async () => { - const input:RequestAccessConfigureInput = { + const input: RequestAccessConfigureInput = { approval_admin: undefined, approved_status_id: statusTemplateRequestAccess.id, - declined_status_id: statusTemplateRequestAccess.id + declined_status_id: statusTemplateRequestAccess.id, }; const configuration = await configureRequestAccess(testContext, ADMIN_USER, input); @@ -413,10 +420,10 @@ describe('Request access domain - configuration edition', async () => { }); it('should disabling approved_status_id in request access be allowed', async () => { - const input:RequestAccessConfigureInput = { + const input: RequestAccessConfigureInput = { approval_admin: [greenGroup.id], approved_status_id: undefined, - declined_status_id: statusTemplateRequestAccess.id + declined_status_id: statusTemplateRequestAccess.id, }; const configuration = await configureRequestAccess(testContext, ADMIN_USER, input); @@ -427,10 +434,10 @@ describe('Request access domain - configuration edition', async () => { }); it('should disabling approved_status_id in request access be allowed', async () => { - const input:RequestAccessConfigureInput = { + const input: RequestAccessConfigureInput = { approval_admin: [greenGroup.id], approved_status_id: statusTemplateRequestAccess.id, - declined_status_id: undefined + declined_status_id: undefined, }; const configuration = await configureRequestAccess(testContext, ADMIN_USER, input); diff --git a/opencti-platform/opencti-graphql/tests/03-integration/01-database/sorting-test.ts b/opencti-platform/opencti-graphql/tests/03-integration/01-database/sorting-test.ts index 9b6246d76f7b..474741111581 100644 --- a/opencti-platform/opencti-graphql/tests/03-integration/01-database/sorting-test.ts +++ b/opencti-platform/opencti-graphql/tests/03-integration/01-database/sorting-test.ts @@ -2,8 +2,12 @@ import { describe, expect, it, vi } from 'vitest'; import { buildElasticSortingForAttributeCriteria } from '../../../src/utils/sorting'; import { SYSTEM_USER } from '../../../src/utils/access'; import { testContext } from '../../utils/testQuery'; +import * as entrepriseEdition from '../../../src/enterprise-edition/ee'; describe('Sorting utilities', () => { + // Activate EE for this test + vi.spyOn(entrepriseEdition, 'checkEnterpriseEdition').mockResolvedValue(); + vi.spyOn(entrepriseEdition, 'isEnterpriseEdition').mockResolvedValue(true); let sorting; it('buildElasticSortingForAttributeCriteria throws on error if pir sorting and user has not the rights', async () => { sorting = async () => buildElasticSortingForAttributeCriteria(testContext, SYSTEM_USER, 'pir_score', 'asc', 'fakePirId'); diff --git a/opencti-platform/opencti-graphql/tests/03-integration/01-database/user-domain-test.ts b/opencti-platform/opencti-graphql/tests/03-integration/01-database/user-domain-test.ts index a65b3cb2c353..c03dc913c1b6 100644 --- a/opencti-platform/opencti-graphql/tests/03-integration/01-database/user-domain-test.ts +++ b/opencti-platform/opencti-graphql/tests/03-integration/01-database/user-domain-test.ts @@ -1,4 +1,4 @@ -import { afterAll, beforeAll, describe, expect, it } from 'vitest'; +import { afterAll, beforeAll, describe, expect, it, vi } from 'vitest'; import { ADMIN_USER, AMBER_STRICT_GROUP, GREEN_GROUP, PLATFORM_ORGANIZATION, TEST_ORGANIZATION, testContext } from '../../utils/testQuery'; import { generateStandardId } from '../../../src/schema/identifier'; import { ENTITY_TYPE_USER } from '../../../src/schema/internalObject'; @@ -14,13 +14,13 @@ import { findById as findUserById, isUserTheLastAdmin, userAddRelation, - userDelete + userDelete, } from '../../../src/domain/user'; import { addWorkspace, findById as findWorkspaceById, workspaceEditAuthorizedMembers } from '../../../src/modules/workspace/workspace-domain'; import type { NotificationAddInput } from '../../../src/modules/notification/notification-types'; import { getFakeAuthUser, getGroupEntity, getOrganizationEntity } from '../../utils/domainQueryHelper'; import { deleteElementById } from '../../../src/database/middleware'; -import { enableCEAndUnSetOrganization, enableEEAndSetOrganization } from '../../utils/testQueryHelper'; +import { unSetOrganization, setOrganization } from '../../utils/testQueryHelper'; import { type BasicStoreEntityOrganization, ENTITY_TYPE_IDENTITY_ORGANIZATION } from '../../../src/modules/organization/organization-types'; import { SETTINGS_SET_ACCESSES } from '../../../src/utils/access'; import type { Group } from '../../../src/types/group'; @@ -31,6 +31,7 @@ import { RELATION_PARTICIPATE_TO } from '../../../src/schema/internalRelationshi import type { BasicStoreEntity } from '../../../src/types/store'; import { ENTITY_TYPE_IDENTITY_SECTOR } from '../../../src/schema/stixDomainObject'; import type { BasicStoreEntityWorkspace, StoreEntityWorkspace } from '../../../src/modules/workspace/workspace-types'; +import * as entrepriseEdition from '../../../src/enterprise-edition/ee'; /** * Create a new user in elastic for this test purpose using domain APIs only. @@ -47,7 +48,7 @@ const createUserForTest = async (adminContext: AuthContext, adminUser: AuthUser, user_email: `${username}@opencti.io`, name: username, firstname: username, - lastname: 'opencti' + lastname: 'opencti', }; const userAdded = await addUser(adminContext, adminUser, simpleUser); await assignGroupToUser(adminContext, adminUser, userAdded.id, AMBER_STRICT_GROUP.name); @@ -65,7 +66,7 @@ const createNotificationForUser = async (context: AuthContext, user: AuthUser) = notification_type: '', notification_content: [{ title: '', - events: [] + events: [], }], user_id: user.id, }; @@ -93,7 +94,7 @@ describe('Testing user delete on cascade [issue/3720]', () => { const privateInvestigationInput: WorkspaceAddInput = { name: 'investigation-not-shared', description: 'this investigation is not shared to other users.', - type: 'investigation' + type: 'investigation', }; const privateInvestigationData = await addWorkspace(userToDeleteContext, userToDeletedAuth, privateInvestigationInput); @@ -103,12 +104,12 @@ describe('Testing user delete on cascade [issue/3720]', () => { const sharedWithAdminRightsInvestigationInput: WorkspaceAddInput = { name: 'investigation-shared-with-admin-rights', description: 'this investigation will be shared to another user with admin rights.', - type: 'investigation' + type: 'investigation', }; let sharedWithAdminRightsInvestigationData: StoreEntityWorkspace | BasicStoreEntityWorkspace = await addWorkspace( userToDeleteContext, userToDeletedAuth, - sharedWithAdminRightsInvestigationInput + sharedWithAdminRightsInvestigationInput, ); const sharedIAuthMembers: MemberAccessInput[] = sharedWithAdminRightsInvestigationData.restricted_members; sharedIAuthMembers.push({ id: 'ALL', access_right: 'admin' }); @@ -121,7 +122,7 @@ describe('Testing user delete on cascade [issue/3720]', () => { const sharedReadOnlyInvestigationInput: WorkspaceAddInput = { name: 'investigation-shared-read-only', description: 'this investigation will be shared to another user with view rights.', - type: 'investigation' + type: 'investigation', }; let sharedInvestigationData: StoreEntityWorkspace | BasicStoreEntityWorkspace = await addWorkspace(userToDeleteContext, userToDeletedAuth, sharedReadOnlyInvestigationInput); const sharedInvestigationAuthMembers: MemberAccessInput[] = sharedInvestigationData.restricted_members; @@ -135,7 +136,7 @@ describe('Testing user delete on cascade [issue/3720]', () => { const adminInvestigationInput: WorkspaceAddInput = { name: 'investigation-owned-by-admin', description: 'this investigation is owned by the admin, do not delete.', - type: 'investigation' + type: 'investigation', }; const adminInvestigationData = await addWorkspace(adminContext, ADMIN_USER, adminInvestigationInput); @@ -208,7 +209,7 @@ describe('Service account User coverage', async () => { user_service_account: true, groups: [testGroup.id], objectOrganization: [testOrganization.id], - prevent_default_groups: true + prevent_default_groups: true, }; const userAddResult = await addUser(testContext, authUser, userAddInput); const userCreated: AuthUser = await findById(testContext, authUser, userAddResult.id); @@ -256,11 +257,14 @@ describe('Service account with platform organization coverage', async () => { const anotherOrgThanPlatformOne: BasicStoreEntityOrganization = await getOrganizationEntity(TEST_ORGANIZATION); beforeAll(async () => { - await enableEEAndSetOrganization(PLATFORM_ORGANIZATION); + // Activate EE for this test + vi.spyOn(entrepriseEdition, 'checkEnterpriseEdition').mockResolvedValue(); + vi.spyOn(entrepriseEdition, 'isEnterpriseEdition').mockResolvedValue(true); + await setOrganization(PLATFORM_ORGANIZATION); }); afterAll(async () => { - await enableCEAndUnSetOrganization(); + await unSetOrganization(); }); it('Standard user should not be added to platform organization', async () => { @@ -320,7 +324,11 @@ describe('Service account with platform organization coverage', async () => { expect(userCreated.password).toBeUndefined(); // WHEN user log in with token - const fakeReq = { headers: () => { return undefined; }, header: () => { return undefined; }, socket: { remoteAddress: '::1' } }; + const fakeReq = { headers: () => { + return undefined; + }, header: () => { + return undefined; + }, socket: { remoteAddress: '::1' } }; const loggedInUser = await authenticateUserByTokenOrUserId(testContext, fakeReq, userCreated.api_token); expect(loggedInUser).toBeDefined(); diff --git a/opencti-platform/opencti-graphql/tests/03-integration/02-resolvers/container-authorized-members-test.ts b/opencti-platform/opencti-graphql/tests/03-integration/02-resolvers/container-authorized-members-test.ts index a8b3810b1650..6c0fea10a521 100644 --- a/opencti-platform/opencti-graphql/tests/03-integration/02-resolvers/container-authorized-members-test.ts +++ b/opencti-platform/opencti-graphql/tests/03-integration/02-resolvers/container-authorized-members-test.ts @@ -11,9 +11,9 @@ import { securityQuery, TEST_ORGANIZATION, USER_EDITOR, - USER_SECURITY + USER_SECURITY, } from '../../utils/testQuery'; -import { adminQueryWithSuccess, disableEE, enableCEAndUnSetOrganization, enableEE, enableEEAndSetOrganization, queryAsUserIsExpectedForbidden } from '../../utils/testQueryHelper'; +import { adminQueryWithSuccess, unSetOrganization, setOrganization, queryAsUserIsExpectedForbidden } from '../../utils/testQueryHelper'; import { ENTITY_TYPE_CONTAINER_CASE_INCIDENT } from '../../../src/modules/case/case-incident/case-incident-types'; import conf from '../../../src/config/conf'; import { authorizedMembers } from '../../../src/schema/attribute-definition'; @@ -173,6 +173,8 @@ const LIST_RESTRICTED_ENTITIES = gql` } `; +// TODO : find a way to mock EE + describe('Case Incident Response standard behavior with authorized_members activation from entity', () => { let caseIncident: CaseIncident; let userEditorId: string; @@ -182,9 +184,9 @@ describe('Case Incident Response standard behavior with authorized_members activ query: CREATE_QUERY, variables: { input: { - name: 'Case Incident Response With Authorized Members from entity' - } - } + name: 'Case Incident Response With Authorized Members from entity', + }, + }, }); expect(caseIncidentResponseCreateQueryResult).not.toBeNull(); @@ -207,9 +209,9 @@ describe('Case Incident Response standard behavior with authorized_members activ input: [ { id: userEditorId, - access_right: 'view' + access_right: 'view', }, - ] + ], }; await queryAsUserIsExpectedForbidden(USER_SECURITY.client, { query: EDIT_AUTHORIZED_MEMBERS_QUERY, @@ -225,18 +227,18 @@ describe('Case Incident Response standard behavior with authorized_members activ input: [ { id: ADMIN_USER.id, - access_right: 'admin' - } - ] - } + access_right: 'admin', + }, + ], + }, }); expect(caseIncidentResponseUpdatedQueryResult).not.toBeNull(); expect(caseIncidentResponseUpdatedQueryResult?.data?.containerEdit.editAuthorizedMembers.authorized_members).not.toBeUndefined(); expect(caseIncidentResponseUpdatedQueryResult?.data?.containerEdit.editAuthorizedMembers.authorized_members).toEqual([ { member_id: ADMIN_USER.id, - access_right: 'admin' - } + access_right: 'admin', + }, ]); }); it('should Editor user not access Case Incident Response', async () => { @@ -254,14 +256,14 @@ describe('Case Incident Response standard behavior with authorized_members activ input: [ { id: ADMIN_USER.id, - access_right: 'admin' + access_right: 'admin', }, { id: userEditorId, - access_right: 'view' - } - ] - } + access_right: 'view', + }, + ], + }, }); expect(caseIncidentResponseUpdatedQueryResult).not.toBeNull(); expect(caseIncidentResponseUpdatedQueryResult?.data?.containerEdit).not.toBeNull(); @@ -270,12 +272,12 @@ describe('Case Incident Response standard behavior with authorized_members activ expect(caseIncidentResponseUpdatedQueryResult?.data?.containerEdit.editAuthorizedMembers.authorized_members).toEqual([ { member_id: ADMIN_USER.id, - access_right: 'admin' + access_right: 'admin', }, { member_id: userEditorId, - access_right: 'view' - } + access_right: 'view', + }, ]); }); it('should Editor user access Case Incident Response', async () => { @@ -299,14 +301,14 @@ describe('Case Incident Response standard behavior with authorized_members activ input: [ { id: ADMIN_USER.id, - access_right: 'admin' + access_right: 'admin', }, { id: userEditorId, - access_right: 'edit' - } - ] - } + access_right: 'edit', + }, + ], + }, }); expect(caseIncidentResponseUpdatedQueryResult).not.toBeNull(); expect(caseIncidentResponseUpdatedQueryResult?.data?.containerEdit.editAuthorizedMembers.authorized_members).not.toBeUndefined(); @@ -340,14 +342,14 @@ describe('Case Incident Response standard behavior with authorized_members activ input: [ { id: ADMIN_USER.id, - access_right: 'admin' + access_right: 'admin', }, { id: userEditorId, - access_right: 'admin' - } - ] - } + access_right: 'admin', + }, + ], + }, }); expect(caseIncidentResponseUpdatedQueryResult).not.toBeNull(); expect(caseIncidentResponseUpdatedQueryResult?.data?.containerEdit.editAuthorizedMembers.currentUserAccessRight).toEqual('admin'); @@ -355,12 +357,12 @@ describe('Case Incident Response standard behavior with authorized_members activ expect(caseIncidentResponseUpdatedQueryResult?.data?.containerEdit.editAuthorizedMembers.authorized_members).toEqual([ { member_id: ADMIN_USER.id, - access_right: 'admin' + access_right: 'admin', }, { member_id: userEditorId, - access_right: 'admin' - } + access_right: 'admin', + }, ]); }); it('should Editor user Case Incident Response deleted', async () => { @@ -422,10 +424,10 @@ describe('Case Incident Response standard behavior with authorized_members activ default_values: [ JSON.stringify({ id: ADMIN_USER.id, - access_right: 'admin' - }) - ] - } + access_right: 'admin', + }), + ], + }, ]); const updateEntitySettingsResult = await adminQuery({ query: ENTITY_SETTINGS_UPDATE_QUERY, @@ -436,17 +438,17 @@ describe('Case Incident Response standard behavior with authorized_members activ query: CREATE_QUERY, variables: { input: { - name: 'Case Incident Response With Authorized Members via settings' - } - } + name: 'Case Incident Response With Authorized Members via settings', + }, + }, }); expect(caseIncidentResponseAuthorizedMembersData).not.toBeNull(); expect(caseIncidentResponseAuthorizedMembersData?.data?.caseIncidentAdd.authorized_members).not.toBeUndefined(); expect(caseIncidentResponseAuthorizedMembersData?.data?.caseIncidentAdd.authorized_members).toEqual([ { member_id: ADMIN_USER.id, - access_right: 'admin' - } + access_right: 'admin', + }, ]); caseIncidentResponseAuthorizedMembersFromSettings = caseIncidentResponseAuthorizedMembersData?.data?.caseIncidentAdd; // Clean @@ -479,24 +481,21 @@ describe('Case Incident Response and organization sharing standard behavior with query: CREATE_QUERY, variables: { input: { - name: 'Case IR without platform Orga' - } - } + name: 'Case IR without platform Orga', + }, + }, }); expect(caseIRCreateQueryResult).not.toBeNull(); expect(caseIRCreateQueryResult?.data?.caseIncidentAdd.authorized_members).not.toBeUndefined(); caseIrId = caseIRCreateQueryResult?.data?.caseIncidentAdd.id; }); - it('should EE activated', async () => { - await enableEE(); - }); it('should share Case Incident Response with Organization', async () => { // Get organization id organizationId = await getOrganizationIdByName(PLATFORM_ORGANIZATION.name); const organizationSharingQueryResult = await adminQuery({ query: ORGANIZATION_SHARING_QUERY, - variables: { id: caseIrId, organizationId, directContainerSharing: true } + variables: { id: caseIrId, organizationId, directContainerSharing: true }, }); expect(organizationSharingQueryResult).not.toBeNull(); expect(organizationSharingQueryResult?.data?.stixCoreObjectEdit.restrictionOrganizationAdd).not.toBeNull(); @@ -529,9 +528,6 @@ describe('Case Incident Response and organization sharing standard behavior with expect(queryResult).not.toBeNull(); expect(queryResult?.data?.caseIncident).toBeNull(); }); - it('should EE deactivated', async () => { - await disableEE(); - }); }); describe('Case Incident Response and organization sharing standard behavior with platform organization', () => { @@ -567,8 +563,8 @@ describe('Case Incident Response and organization sharing standard behavior with input: [ { key: 'platform_organization', value: platformOrganizationId }, { key: 'enterprise_license', value: conf.get('app:enterprise_edition_license') }, - ] - } + ], + }, }); expect(platformOrganization?.data?.settingsEdit.fieldPatch.platform_organization).not.toBeUndefined(); expect(platformOrganization?.data?.settingsEdit.fieldPatch.platform_enterprise_edition.license_validated).toBeTruthy(); @@ -580,9 +576,9 @@ describe('Case Incident Response and organization sharing standard behavior with query: CREATE_QUERY, variables: { input: { - name: 'Case IR with platform orga' - } - } + name: 'Case IR with platform orga', + }, + }, }); expect(caseIRCreateQueryResult).not.toBeNull(); @@ -603,26 +599,26 @@ describe('Case Incident Response and organization sharing standard behavior with input: [ { id: ADMIN_USER.id, - access_right: 'admin' + access_right: 'admin', }, { id: userEditorId, - access_right: 'view' - } - ] - } + access_right: 'view', + }, + ], + }, }); expect(caseIRUpdatedQueryResult).not.toBeNull(); expect(caseIRUpdatedQueryResult?.data?.containerEdit.editAuthorizedMembers.authorized_members).not.toBeUndefined(); expect(caseIRUpdatedQueryResult?.data?.containerEdit.editAuthorizedMembers.authorized_members).toEqual([ { member_id: ADMIN_USER.id, - access_right: 'admin' + access_right: 'admin', }, { member_id: userEditorId, - access_right: 'view' - } + access_right: 'view', + }, ]); }); it('should Editor user access Case Incident Response out of her organization if authorized members activated', async () => { @@ -635,7 +631,7 @@ describe('Case Incident Response and organization sharing standard behavior with it('user without bypass should not be allowed list all auth member restricted entities', async () => { await queryAsUserIsExpectedForbidden(USER_EDITOR.client, { query: LIST_RESTRICTED_ENTITIES, - variables: { first: 10 } + variables: { first: 10 }, }); }); it('should BYPASS user be allowed list all auth member restricted entities', async () => { @@ -648,7 +644,7 @@ describe('Case Incident Response and organization sharing standard behavior with variables: { id: caseIrId, input: null, - } + }, }); // Verify Editor user has no more access to Case incident const caseIRQueryResult = await editorQuery({ query: READ_QUERY, variables: { id: caseIrId } }); @@ -660,7 +656,7 @@ describe('Case Incident Response and organization sharing standard behavior with testOrganizationId = await getOrganizationIdByName(TEST_ORGANIZATION.name); const organizationSharingQueryResult = await adminQuery({ query: ORGANIZATION_SHARING_QUERY, - variables: { id: caseIrId, organizationId: testOrganizationId, directContainerSharing: true } + variables: { id: caseIrId, organizationId: testOrganizationId, directContainerSharing: true }, }); expect(organizationSharingQueryResult).not.toBeNull(); expect(organizationSharingQueryResult?.data?.stixCoreObjectEdit.restrictionOrganizationAdd).not.toBeNull(); @@ -679,8 +675,8 @@ describe('Case Incident Response and organization sharing standard behavior with input: [ { key: 'platform_organization', value: [] }, { key: 'enterprise_license', value: [] }, - ] - } + ], + }, }); expect(platformOrganization?.data?.settingsEdit.fieldPatch.platform_organization).toBeNull(); }); @@ -702,7 +698,7 @@ describe('Restricted entities listing', () => { let userEditorId: string; let reportId: string; it('should platform organization sharing and EE activated', async () => { - await enableEEAndSetOrganization(PLATFORM_ORGANIZATION); + await setOrganization(PLATFORM_ORGANIZATION); }); it('should Case Incident Response created', async () => { // Create Case Incident Response @@ -710,9 +706,9 @@ describe('Restricted entities listing', () => { query: CREATE_QUERY, variables: { input: { - name: 'Case IR with platform orga' - } - } + name: 'Case IR with platform orga', + }, + }, }); expect(caseIRCreateQueryResult?.data?.caseIncidentAdd).not.toBeUndefined(); caseIrId = caseIRCreateQueryResult?.data?.caseIncidentAdd.id; @@ -726,26 +722,26 @@ describe('Restricted entities listing', () => { input: [ { id: ADMIN_USER.id, - access_right: 'admin' + access_right: 'admin', }, { id: userEditorId, - access_right: 'view' - } - ] - } + access_right: 'view', + }, + ], + }, }); expect(caseIRUpdatedQueryResult).not.toBeNull(); expect(caseIRUpdatedQueryResult?.data?.containerEdit.editAuthorizedMembers.authorized_members).not.toBeUndefined(); expect(caseIRUpdatedQueryResult?.data?.containerEdit.editAuthorizedMembers.authorized_members).toEqual([ { member_id: ADMIN_USER.id, - access_right: 'admin' + access_right: 'admin', }, { member_id: userEditorId, - access_right: 'view' - } + access_right: 'view', + }, ]); }); it('should Report created', async () => { // +1 create @@ -756,8 +752,8 @@ describe('Restricted entities listing', () => { input: { name: 'Report name', published: '2023-06-01T22:00:00.000Z', - } - } + }, + }, }); expect(reportCreateQueryResult?.data?.reportAdd).not.toBeUndefined(); reportId = reportCreateQueryResult?.data?.reportAdd.id; @@ -771,32 +767,32 @@ describe('Restricted entities listing', () => { input: [ { id: ADMIN_USER.id, - access_right: 'admin' + access_right: 'admin', }, { id: userEditorId, - access_right: 'view' - } - ] - } + access_right: 'view', + }, + ], + }, }); expect(reportUpdatedQueryResult?.data?.containerEdit.editAuthorizedMembers.authorized_members).not.toBeUndefined(); expect(reportUpdatedQueryResult?.data?.containerEdit.editAuthorizedMembers.authorized_members).toEqual([ { member_id: ADMIN_USER.id, - access_right: 'admin' + access_right: 'admin', }, { member_id: userEditorId, - access_right: 'view' - } + access_right: 'view', + }, ]); expect(reportUpdatedQueryResult?.data?.containerEdit.editAuthorizedMembers.authorized_members_activation_date).toBeDefined(); }); // +1 update it('using platform org - user without bypass should not be allowed list all auth member restricted entities', async () => { await queryAsUserIsExpectedForbidden(USER_EDITOR.client, { query: LIST_RESTRICTED_ENTITIES, - variables: { first: 10 } + variables: { first: 10 }, }); }); it('using platform org - should BYPASS user be allowed list all auth member restricted entities', async () => { @@ -804,7 +800,7 @@ describe('Restricted entities listing', () => { expect(queryResult?.data?.stixCoreObjectsRestricted.edges.length).toEqual(2); }); it('should platform organization sharing and EE deactivated', async () => { - await enableCEAndUnSetOrganization(); + await unSetOrganization(); }); it('should Case Incident Response deleted', async () => { // Delete the case diff --git a/opencti-platform/opencti-graphql/tests/03-integration/02-resolvers/disseminationListResolver-test.ts b/opencti-platform/opencti-graphql/tests/03-integration/02-resolvers/disseminationListResolver-test.ts index d90026e187c0..c1ceb6088b4e 100644 --- a/opencti-platform/opencti-graphql/tests/03-integration/02-resolvers/disseminationListResolver-test.ts +++ b/opencti-platform/opencti-graphql/tests/03-integration/02-resolvers/disseminationListResolver-test.ts @@ -1,7 +1,7 @@ -import { describe, it, expect } from 'vitest'; -import { disableEE, enableEE } from '../../utils/testQueryHelper'; +import { describe, it, expect, vi } from 'vitest'; import { queryAsAdmin } from '../../utils/testQuery'; import gql from 'graphql-tag'; +import * as entrepriseEdition from '../../../src/enterprise-edition/ee'; const CREATE_QUERY = gql` mutation DisseminationListAdd($input: DisseminationListAddInput!) { @@ -51,12 +51,14 @@ describe('Dissemination list resolver', () => { name: 'dissemination list 1', description: 'My dissemination list description', emails: ['email1@test.com', 'email2@test.com', 'email3@test.com'], - } + }, }; const NEW_DESCRIPTION = 'New description'; it('should dissemination list created', async () => { - await enableEE(); + // Activate EE for this test + vi.spyOn(entrepriseEdition, 'checkEnterpriseEdition').mockResolvedValue(); + vi.spyOn(entrepriseEdition, 'isEnterpriseEdition').mockResolvedValue(true); const disseminationList = await queryAsAdmin({ query: CREATE_QUERY, variables: DISSEMINATION_LIST_TO_CREATE, @@ -83,7 +85,7 @@ describe('Dissemination list resolver', () => { variables: { id: disseminationListInternalId, input: [{ key: 'description', value: [NEW_DESCRIPTION] }], - } + }, }); const disseminationListDescription = queryResult.data?.disseminationListFieldPatch.description; expect(disseminationListDescription).toEqual(NEW_DESCRIPTION); @@ -99,7 +101,5 @@ describe('Dissemination list resolver', () => { const queryResult = await queryAsAdmin({ query: READ_QUERY, variables: { id: disseminationListInternalId } }); expect(queryResult).not.toBeNull(); expect(queryResult.data?.disseminationList).toBeNull(); - await disableEE(); }); - -}); \ No newline at end of file +}); diff --git a/opencti-platform/opencti-graphql/tests/03-integration/02-resolvers/emailTemplate-test.ts b/opencti-platform/opencti-graphql/tests/03-integration/02-resolvers/emailTemplate-test.ts index bbbcfb964455..5dcb185494cc 100644 --- a/opencti-platform/opencti-graphql/tests/03-integration/02-resolvers/emailTemplate-test.ts +++ b/opencti-platform/opencti-graphql/tests/03-integration/02-resolvers/emailTemplate-test.ts @@ -1,7 +1,8 @@ -import { describe, expect, it } from 'vitest'; +import { beforeEach, describe, expect, it, vi } from 'vitest'; import gql from 'graphql-tag'; import { ADMIN_USER, queryAsAdmin, testContext } from '../../utils/testQuery'; import { elLoadById } from '../../../src/database/engine'; +import * as entrepriseEdition from '../../../src/enterprise-edition/ee'; const READ_TEMPLATE_QUERY = gql` query emailTemplate($id: ID!) { @@ -53,18 +54,19 @@ const generateEmailTemplateToCreate = (value: number) => ({ name: `emailTemplate${value}`, email_object: `email@template${value}.com`, sender_email: `sender@email${value}.com`, - template_body: `templateBody${value}` + template_body: `templateBody${value}`, }); describe('Email template resolver standard behavior', () => { const emailTemplateIds: string[] = []; + describe('Email template creation', () => { it('should create email template', async () => { const emailTemplate = await queryAsAdmin({ query: CREATE_TEMPLATE_MUTATION, variables: { input: generateEmailTemplateToCreate(1), - } + }, }); expect(emailTemplate).not.toBeNull(); @@ -78,7 +80,7 @@ describe('Email template resolver standard behavior', () => { query: CREATE_TEMPLATE_MUTATION, variables: { input: generateEmailTemplateToCreate(2), - } + }, }); expect(emailTemplate).not.toBeNull(); @@ -89,14 +91,20 @@ describe('Email template resolver standard behavior', () => { }); describe('Email template field patch', () => { + beforeEach(() => { + // Activate EE for this test + vi.spyOn(entrepriseEdition, 'checkEnterpriseEdition').mockResolvedValue(); + vi.spyOn(entrepriseEdition, 'isEnterpriseEdition').mockResolvedValue(true); + }); + it('should edit the name', async () => { if (!emailTemplateIds.length) return; const emailTemplate = await queryAsAdmin({ query: EDIT_TEMPLATE_MUTATION, variables: { id: emailTemplateIds[0], - input: [{ key: 'name', value: ['emailTemplate11'] }] - } + input: [{ key: 'name', value: ['emailTemplate11'] }], + }, }); expect(emailTemplate).not.toBeNull(); @@ -105,12 +113,17 @@ describe('Email template resolver standard behavior', () => { }); describe('Email template query', () => { + beforeEach(() => { + // Activate EE for this test + vi.spyOn(entrepriseEdition, 'checkEnterpriseEdition').mockResolvedValue(); + vi.spyOn(entrepriseEdition, 'isEnterpriseEdition').mockResolvedValue(true); + }); it('find one', async () => { const emailTemplate = await queryAsAdmin({ query: READ_TEMPLATE_QUERY, variables: { id: emailTemplateIds[0], - } + }, }); expect(emailTemplate).not.toBeNull(); @@ -121,7 +134,7 @@ describe('Email template resolver standard behavior', () => { const listResult = await queryAsAdmin({ query: READ_TEMPLATES_QUERY, variables: { - orderMode: 'desc' + orderMode: 'desc', }, }); @@ -136,8 +149,8 @@ describe('Email template resolver standard behavior', () => { await queryAsAdmin({ query: DELETE_TEMPLATE_MUTATION, variables: { - id: emailTemplateIds[0] - } + id: emailTemplateIds[0], + }, }); const emailTemplate = await elLoadById(testContext, ADMIN_USER, emailTemplateIds[0]); @@ -148,8 +161,8 @@ describe('Email template resolver standard behavior', () => { await queryAsAdmin({ query: DELETE_TEMPLATE_MUTATION, variables: { - id: emailTemplateIds[1] - } + id: emailTemplateIds[1], + }, }); const emailTemplate = await elLoadById(testContext, ADMIN_USER, emailTemplateIds[0]); diff --git a/opencti-platform/opencti-graphql/tests/03-integration/02-resolvers/fintelDesign-test.ts b/opencti-platform/opencti-graphql/tests/03-integration/02-resolvers/fintelDesign-test.ts index da157ba3e9b6..709f8b0087f2 100644 --- a/opencti-platform/opencti-graphql/tests/03-integration/02-resolvers/fintelDesign-test.ts +++ b/opencti-platform/opencti-graphql/tests/03-integration/02-resolvers/fintelDesign-test.ts @@ -1,6 +1,7 @@ -import { expect, it, describe } from 'vitest'; +import { expect, it, describe, vi, beforeAll } from 'vitest'; import gql from 'graphql-tag'; import { queryAsAdmin } from '../../utils/testQuery'; +import * as entrepriseEdition from '../../../src/enterprise-edition/ee'; const LIST_QUERY = gql` query fintelDesigns( @@ -83,6 +84,12 @@ describe('Fintel Design resolver standard behavior', () => { textColor: '#333333', }; + beforeAll(() => { + // Activate EE for this test + vi.spyOn(entrepriseEdition, 'checkEnterpriseEdition').mockResolvedValue(); + vi.spyOn(entrepriseEdition, 'isEnterpriseEdition').mockResolvedValue(true); + }); + it('should create Fintel Design', async () => { // Create fintel design const fintelDesign = await queryAsAdmin({ diff --git a/opencti-platform/opencti-graphql/tests/03-integration/02-resolvers/fintelTemplate-test.ts b/opencti-platform/opencti-graphql/tests/03-integration/02-resolvers/fintelTemplate-test.ts index eaee416d96cc..8e1f76ccfda4 100644 --- a/opencti-platform/opencti-graphql/tests/03-integration/02-resolvers/fintelTemplate-test.ts +++ b/opencti-platform/opencti-graphql/tests/03-integration/02-resolvers/fintelTemplate-test.ts @@ -1,10 +1,10 @@ -import { describe, expect, it } from 'vitest'; +import { describe, expect, it, vi } from 'vitest'; import gql from 'graphql-tag'; import { queryAsAdmin } from '../../utils/testQuery'; import { addFilter } from '../../../src/utils/filtering/filtering-utils'; -import { disableEE, enableEE } from '../../utils/testQueryHelper'; import { type FintelTemplateWidgetAddInput, WidgetPerspective } from '../../../src/generated/graphql'; import { SELF_ID } from '../../../src/utils/fintelTemplate/__fintelTemplateWidgets'; +import * as entrepriseEdition from '../../../src/enterprise-edition/ee'; const FINTEL_TEMPLATE_SETTINGS_LIST_QUERY = gql` query entitySettings( @@ -110,12 +110,13 @@ describe('Fintel template resolver standard behavior', () => { name: 'Fintel template 1', description: 'My fintel template description', start_date: '2025-01-01T19:00:05.000Z', - settings_types: ['Report'] + settings_types: ['Report'], }, }; it('should fintel template created', async () => { - // Activate EE - await enableEE(); + // Activate EE for this test + vi.spyOn(entrepriseEdition, 'checkEnterpriseEdition').mockResolvedValue(); + vi.spyOn(entrepriseEdition, 'isEnterpriseEdition').mockResolvedValue(true); // Create the fintel template const fintelTemplate = await queryAsAdmin({ query: CREATE_QUERY, @@ -160,7 +161,7 @@ describe('Fintel template resolver standard behavior', () => { variables: { id: fintelTemplateInternalId, input: [{ key: 'description', value: ['new description'] }], - } + }, }); const fintelTemplateDescription = queryResult.data?.fintelTemplateFieldPatch.description; expect(fintelTemplateDescription).toEqual('new description'); @@ -197,7 +198,7 @@ describe('Fintel template resolver standard behavior', () => { variables: { id: fintelTemplateInternalId, input: [{ key: 'fintel_template_widgets', object_path: 'fintel_template_widgets/1', value: [fintelTemplateWidgetAddInput] }], - } + }, }); const fintelTemplateWidgets = queryResult.data?.fintelTemplateFieldPatch.fintel_template_widgets; expect(fintelTemplateWidgets.length).toEqual(4); // 4 widgets : the modified one and the built-in @@ -233,7 +234,7 @@ describe('Fintel template resolver standard behavior', () => { variables: { id: fintelTemplateInternalId, input: [{ key: 'fintel_template_widgets', value: [fintelTemplateAttributeWidgetAddInput], operation: 'add' }], - } + }, }); expect(attributeQueryResult.errors?.length).toBe(1); expect(attributeQueryResult.errors?.[0].message).toEqual('Attributes should all have a variable name'); @@ -258,7 +259,7 @@ describe('Fintel template resolver standard behavior', () => { id: fintelTemplateInternalId, input: [{ key: 'fintel_template_widgets', value: [fintelTemplateWidgetAddInput], operation: 'add' }], - } + }, }); expect(queryResult.errors?.length).toBe(1); expect(queryResult.errors?.[0].message).toEqual('Variable names should not contain spaces or special chars (except - and _)'); @@ -284,7 +285,7 @@ describe('Fintel template resolver standard behavior', () => { variables: { id: fintelTemplateInternalId, input: [{ key: 'fintel_template_widgets', value: [fintelTemplateAttributeWidgetAddInput], operation: 'add' }], - } + }, }); expect(attributeQueryResult.errors?.length).toBe(1); expect(attributeQueryResult.errors?.[0].message).toEqual('Variable names should not contain spaces or special chars (except - and _)'); @@ -304,7 +305,5 @@ describe('Fintel template resolver standard behavior', () => { const queryResult = await queryAsAdmin({ query: READ_QUERY, variables: { id: fintelTemplateInternalId } }); expect(queryResult).not.toBeNull(); expect(queryResult.data?.fintelTemplate).toBeNull(); - // Deactivate EE - await disableEE(); }); }); diff --git a/opencti-platform/opencti-graphql/tests/03-integration/02-resolvers/organization-sharing-test.ts b/opencti-platform/opencti-graphql/tests/03-integration/02-resolvers/organization-sharing-test.ts index cb42d2196ef1..57998ccf0bc1 100644 --- a/opencti-platform/opencti-graphql/tests/03-integration/02-resolvers/organization-sharing-test.ts +++ b/opencti-platform/opencti-graphql/tests/03-integration/02-resolvers/organization-sharing-test.ts @@ -1,4 +1,4 @@ -import { describe, expect, it } from 'vitest'; +import { beforeAll, describe, expect, it, vi } from 'vitest'; import gql from 'graphql-tag'; import { ADMIN_API_TOKEN, @@ -10,18 +10,12 @@ import { PYTHON_PATH, TEST_ORGANIZATION, testContext, - USER_EDITOR + USER_EDITOR, } from '../../utils/testQuery'; -import { -adminQueryWithSuccess, -awaitUntilCondition, -enableCEAndUnSetOrganization, -enableEEAndSetOrganization, -queryAsUserIsExpectedError, -queryAsUserWithSuccess -} from '../../utils/testQueryHelper'; +import { adminQueryWithSuccess, awaitUntilCondition, unSetOrganization, setOrganization, queryAsUserIsExpectedError, queryAsUserWithSuccess } from '../../utils/testQueryHelper'; import { findById } from '../../../src/domain/report'; import { execChildPython } from '../../../src/python/pythonBridge'; +import * as entrepriseEdition from '../../../src/enterprise-edition/ee'; const ORGANIZATION_SHARING_QUERY = gql` mutation StixCoreObjectSharingGroupAddMutation( @@ -65,97 +59,102 @@ const REPORT_STIX_DOMAIN_ENTITIES = gql` const importOpts: string[] = [API_URI, ADMIN_API_TOKEN, './tests/data/organization-sharing/20241003_Report_to_test_orga_sharing_full.json']; -describe('Database provision', () => { - it('Should import creation succeed', async () => { - // Inject data - const execution = await execChildPython(testContext, ADMIN_USER, PYTHON_PATH, 'local_importer.py', importOpts); - expect(execution).not.toBeNull(); - expect(execution.status).toEqual('success'); - }, FIVE_MINUTES); - // Python lib is fixed but we need to wait for a new release - it('Should import update succeed', async () => { - const execution = await execChildPython(testContext, ADMIN_USER, PYTHON_PATH, 'local_importer.py', importOpts); - expect(execution).not.toBeNull(); - expect(execution.status).toEqual('success'); - }, FIVE_MINUTES); -}); +// TODO : find a way to mock EE -describe('Organization sharing standard behavior for container', () => { - let reportInternalId: string; - let organizationId: string; - it('should load Report', async () => { - const report = await findById(testContext, ADMIN_USER, 'report--ce32448d-733b-5e34-ac4f-2759ce5db1ae'); - expect(report).not.toBeUndefined(); - reportInternalId = report.id; +describe('oganization-sharing-test', () => { + describe('Database provision', () => { + it('Should import creation succeed', async () => { + // Inject data + const execution = await execChildPython(testContext, ADMIN_USER, PYTHON_PATH, 'local_importer.py', importOpts); + expect(execution).not.toBeNull(); + expect(execution.status).toEqual('success'); + }, FIVE_MINUTES); + // Python lib is fixed but we need to wait for a new release + it('Should import update succeed', async () => { + const execution = await execChildPython(testContext, ADMIN_USER, PYTHON_PATH, 'local_importer.py', importOpts); + expect(execution).not.toBeNull(); + expect(execution.status).toEqual('success'); + }, FIVE_MINUTES); }); - it('should platform organization sharing and EE activated', async () => { - await enableEEAndSetOrganization(PLATFORM_ORGANIZATION); - }); - it('should not delete organization if platform organization', async () => { - const DELETE_QUERY = gql` - mutation organizationDelete($id: ID!) { - organizationDelete(id: $id) - } - `; - // Delete the organization should fail with error - await queryAsUserIsExpectedError(USER_EDITOR.client, { - query: DELETE_QUERY, - variables: { id: PLATFORM_ORGANIZATION.id }, - }, 'Cannot delete the platform organization.', 'FUNCTIONAL_ERROR'); - }); - it('should user from different organization not access the report', async () => { - const queryResult = await queryAsUserWithSuccess(USER_EDITOR.client, { - query: REPORT_STIX_DOMAIN_ENTITIES, - variables: { id: reportInternalId }, + + describe('Organization sharing standard behavior for container', () => { + let reportInternalId: string; + let organizationId: string; + + it('should load Report', async () => { + const report = await findById(testContext, ADMIN_USER, 'report--ce32448d-733b-5e34-ac4f-2759ce5db1ae'); + expect(report).not.toBeUndefined(); + reportInternalId = report.id; + }); + it('should platform organization sharing and EE activated', async () => { + await setOrganization(PLATFORM_ORGANIZATION); + }); + it('should not delete organization if platform organization', async () => { + const DELETE_QUERY = gql` + mutation organizationDelete($id: ID!) { + organizationDelete(id: $id) + } + `; + // Delete the organization should fail with error + await queryAsUserIsExpectedError(USER_EDITOR.client, { + query: DELETE_QUERY, + variables: { id: PLATFORM_ORGANIZATION.id }, + }, 'Cannot delete the platform organization.', 'FUNCTIONAL_ERROR'); + }); + it('should user from different organization not access the report', async () => { + const queryResult = await queryAsUserWithSuccess(USER_EDITOR.client, { + query: REPORT_STIX_DOMAIN_ENTITIES, + variables: { id: reportInternalId }, + }); + expect(queryResult.data.report).toBeNull(); }); - expect(queryResult.data.report).toBeNull(); - }); - // If this test fails, please check that one worker is running. - it('should share Report with Organization - WORKER REQUIRED', async () => { - // Get organization id - organizationId = await getOrganizationIdByName(TEST_ORGANIZATION.name); - const organizationSharingQueryResult = await adminQueryWithSuccess({ - query: ORGANIZATION_SHARING_QUERY, - variables: { id: reportInternalId, organizationId } + // If this test fails, please check that one worker is running. + it('should share Report with Organization - WORKER REQUIRED', async () => { + // Get organization id + organizationId = await getOrganizationIdByName(TEST_ORGANIZATION.name); + const organizationSharingQueryResult = await adminQueryWithSuccess({ + query: ORGANIZATION_SHARING_QUERY, + variables: { id: reportInternalId, organizationId }, + }); + expect(organizationSharingQueryResult?.data?.stixCoreObjectEdit.restrictionOrganizationAdd).not.toBeNull(); }); - expect(organizationSharingQueryResult?.data?.stixCoreObjectEdit.restrictionOrganizationAdd).not.toBeNull(); - }); - it('should Editor user access all objects', async () => { - await awaitUntilCondition(async () => { + it('should Editor user access all objects', async () => { + await awaitUntilCondition(async () => { + const queryResult = await queryAsUserWithSuccess(USER_EDITOR.client, { + query: REPORT_STIX_DOMAIN_ENTITIES, + variables: { id: reportInternalId }, + }); + return queryResult.data.report !== null && queryResult.data.report.objects.edges.length === 8; + }); // wait for task manager & worker to handle organization sharing const queryResult = await queryAsUserWithSuccess(USER_EDITOR.client, { query: REPORT_STIX_DOMAIN_ENTITIES, variables: { id: reportInternalId }, }); - return queryResult.data.report !== null && queryResult.data.report.objects.edges.length === 8; - }); // wait for task manager & worker to handle organization sharing - const queryResult = await queryAsUserWithSuccess(USER_EDITOR.client, { - query: REPORT_STIX_DOMAIN_ENTITIES, - variables: { id: reportInternalId }, + expect(queryResult.data.report.objects.edges.length).toEqual(8); }); - expect(queryResult.data.report.objects.edges.length).toEqual(8); - }); - it('should all entities deleted', async () => { - const PURGE_QUERY = gql` - mutation ReportPopoverDeletionMutation( - $id: ID! - $purgeElements: Boolean - ) { - reportEdit(id: $id) { - delete(purgeElements: $purgeElements) + it('should all entities deleted', async () => { + const PURGE_QUERY = gql` + mutation ReportPopoverDeletionMutation( + $id: ID! + $purgeElements: Boolean + ) { + reportEdit(id: $id) { + delete(purgeElements: $purgeElements) + } } - } - `; - const purgeQueryResult = await adminQueryWithSuccess({ - query: PURGE_QUERY, - variables: { - id: reportInternalId, - purgeElements: true - } + `; + const purgeQueryResult = await adminQueryWithSuccess({ + query: PURGE_QUERY, + variables: { + id: reportInternalId, + purgeElements: true, + }, + }); + expect(purgeQueryResult.data.reportEdit.delete).toEqual(reportInternalId); + }); + it('should plateform organization sharing and EE deactivated', async () => { + await unSetOrganization(); }); - expect(purgeQueryResult.data.reportEdit.delete).toEqual(reportInternalId); - }); - it('should plateform organization sharing and EE deactivated', async () => { - await enableCEAndUnSetOrganization(); }); }); diff --git a/opencti-platform/opencti-graphql/tests/03-integration/02-resolvers/pir-test.ts b/opencti-platform/opencti-graphql/tests/03-integration/02-resolvers/pir-test.ts index 73081d2f43c5..30cd8eafc820 100644 --- a/opencti-platform/opencti-graphql/tests/03-integration/02-resolvers/pir-test.ts +++ b/opencti-platform/opencti-graphql/tests/03-integration/02-resolvers/pir-test.ts @@ -1,5 +1,5 @@ import gql from 'graphql-tag'; -import { beforeAll, describe, expect, it } from 'vitest'; +import { beforeAll, describe, expect, it, vi } from 'vitest'; import { now } from 'moment'; import { ADMIN_USER, buildStandardUser, ONE_MINUTE, queryAsAdmin, testContext } from '../../utils/testQuery'; import { FilterMode, FilterOperator, PirType, StatsOperation } from '../../../src/generated/graphql'; @@ -14,6 +14,7 @@ import { RELATION_IN_PIR } from '../../../src/schema/internalRelationship'; import { connectorsForWorker } from '../../../src/database/repository'; import { pirRelationshipsDistribution, pirRelationshipsMultiTimeSeries } from '../../../src/modules/pir/pir-domain'; import { ENTITY_TYPE_IDENTITY_ORGANIZATION } from '../../../src/modules/organization/organization-types'; +import * as entrepriseEdition from '../../../src/enterprise-edition/ee'; import { LAST_PIR_SCORE_DATE_FILTER, PIR_SCORE_FILTER } from '../../../src/utils/filtering/filtering-constants'; const LIST_QUERY = gql` @@ -125,6 +126,9 @@ describe('PIR resolver standard behavior', () => { let relationshipAuthorId: string = ''; // id of Allied Universal beforeAll(async () => { + // Activate EE for this test + vi.spyOn(entrepriseEdition, 'checkEnterpriseEdition').mockResolvedValue(); + vi.spyOn(entrepriseEdition, 'isEnterpriseEdition').mockResolvedValue(true); const author = await internalLoadById( testContext, SYSTEM_USER, @@ -154,8 +158,8 @@ describe('PIR resolver standard behavior', () => { mode: FilterMode.And, filterGroups: [], filters: [ - { key: ['confidence'], values: ['80'], operator: FilterOperator.Gt } - ] + { key: ['confidence'], values: ['80'], operator: FilterOperator.Gt }, + ], }, pir_criteria: [ { @@ -164,8 +168,8 @@ describe('PIR resolver standard behavior', () => { mode: FilterMode.And, filterGroups: [], filters: [ - { key: ['toId'], values: ['24b6365f-dd85-4ee3-a28d-bb4b37e1619c'] } - ] + { key: ['toId'], values: ['24b6365f-dd85-4ee3-a28d-bb4b37e1619c'] }, + ], }, }, { @@ -174,11 +178,11 @@ describe('PIR resolver standard behavior', () => { mode: FilterMode.And, filterGroups: [], filters: [ - { key: ['toId'], values: ['d17360d5-0b58-4a21-bebc-84aa5a3f32b4'] } - ] + { key: ['toId'], values: ['d17360d5-0b58-4a21-bebc-84aa5a3f32b4'] }, + ], }, }, - ] + ], }, }; const PIR_TO_CREATE_2 = { @@ -190,8 +194,8 @@ describe('PIR resolver standard behavior', () => { mode: FilterMode.And, filterGroups: [], filters: [ - { key: ['confidence'], values: ['60'], operator: FilterOperator.Gt } - ] + { key: ['confidence'], values: ['60'], operator: FilterOperator.Gt }, + ], }, pir_criteria: [ { @@ -200,8 +204,8 @@ describe('PIR resolver standard behavior', () => { mode: FilterMode.And, filterGroups: [], filters: [ - { key: ['toId'], values: ['d17360d5-0b58-4a21-bebc-84aa5a3f32b4'] } // this id is also present in pir1 criteria - ] + { key: ['toId'], values: ['d17360d5-0b58-4a21-bebc-84aa5a3f32b4'] }, // this id is also present in pir1 criteria + ], }, }, { @@ -210,11 +214,11 @@ describe('PIR resolver standard behavior', () => { mode: FilterMode.And, filterGroups: [], filters: [ - { key: ['toId'], values: ['527e5e30-02c5-4ba9-a698-45954d1f3763'] } - ] + { key: ['toId'], values: ['527e5e30-02c5-4ba9-a698-45954d1f3763'] }, + ], }, }, - ] + ], }, }; const pir1 = await queryAsAdmin({ @@ -323,7 +327,7 @@ describe('PIR resolver standard behavior', () => { filters: { mode: FilterMode.And, filters: [ - { key: ['toId'], values: ['24b6365f-dd85-4ee3-a28d-bb4b37e1619c'] } + { key: ['toId'], values: ['24b6365f-dd85-4ee3-a28d-bb4b37e1619c'] }, ], filterGroups: [], }, @@ -571,8 +575,8 @@ describe('PIR resolver standard behavior', () => { mode: FilterMode.And, filterGroups: [], filters: [ - { key: ['toId'], values: ['d17360d5-0b58-4a21-bebc-84aa5a3f32b4'] } - ] + { key: ['toId'], values: ['d17360d5-0b58-4a21-bebc-84aa5a3f32b4'] }, + ], }, weight: 1, }; diff --git a/opencti-platform/opencti-graphql/tests/03-integration/02-resolvers/playbook-test.ts b/opencti-platform/opencti-graphql/tests/03-integration/02-resolvers/playbook-test.ts index 377d01a035c7..ddc2cfbea67d 100644 --- a/opencti-platform/opencti-graphql/tests/03-integration/02-resolvers/playbook-test.ts +++ b/opencti-platform/opencti-graphql/tests/03-integration/02-resolvers/playbook-test.ts @@ -75,6 +75,8 @@ const DELETE_PLAYBOOK = gql` } `; +// TODO : find a way to mock EE + describe('Playbook resolver standard behavior', () => { let playbookId = ''; const playbookName = 'Playbook1'; @@ -93,7 +95,7 @@ describe('Playbook resolver standard behavior', () => { const input = { input: { name: playbookName, - } + }, }; await queryAsUserIsExpectedForbidden(USER_PARTICIPATE.client, { query: CREATE_PLAYBOOK, @@ -104,7 +106,7 @@ describe('Playbook resolver standard behavior', () => { const input = { input: { name: playbookName, - } + }, }; const queryResult = await queryAsUserWithSuccess(USER_SECURITY.client, { query: CREATE_PLAYBOOK, @@ -129,8 +131,8 @@ describe('Playbook resolver standard behavior', () => { id: playbookId, input: [ { key: 'name', value: ['Playbook1 - updated'] }, - ] - } + ], + }, }); }); it('should update playbook with Manage Playbooks capability', async () => { @@ -140,8 +142,8 @@ describe('Playbook resolver standard behavior', () => { id: playbookId, input: [ { key: 'name', value: ['Playbook1 - updated'] }, - ] - } + ], + }, }); expect(queryResult.data?.playbookFieldPatch.name).toEqual('Playbook1 - updated'); }); @@ -163,7 +165,7 @@ describe('Playbook resolver standard behavior', () => { variables: { id: playbookId, input: addNodeInput, - } + }, }); const queryResult = await adminQueryWithSuccess({ query: READ_PLAYBOOK, variables: { id: playbookId } }); const playbookNodes = JSON.parse(queryResult.data?.playbook.playbook_definition).nodes; @@ -192,10 +194,10 @@ describe('Playbook resolver standard behavior', () => { variables: { id: playbookId, input: addNodeInput, - } + }, }, 'Playbook multiple entrypoint is not supported', - UNSUPPORTED_ERROR + UNSUPPORTED_ERROR, ); }); it('should not add unknown component to a playbook', async () => { @@ -217,10 +219,10 @@ describe('Playbook resolver standard behavior', () => { variables: { id: playbookId, input: addNodeInput, - } + }, }, 'Playbook related component not found', - UNSUPPORTED_ERROR + UNSUPPORTED_ERROR, ); }); it('should not add node with incorrect filters for PLAYBOOK_INTERNAL_DATA_CRON component', async () => { @@ -246,10 +248,10 @@ describe('Playbook resolver standard behavior', () => { variables: { id: playbookId, input: addNodeInput, - } + }, }, 'Incorrect filter keys not existing in any schema definition', - UNSUPPORTED_ERROR + UNSUPPORTED_ERROR, ); }); it('should not add node with incorrect filters for components with stix filtering', async () => { @@ -275,10 +277,10 @@ describe('Playbook resolver standard behavior', () => { variables: { id: playbookId, input: addNodeInput, - } + }, }, 'Stix filtering is not compatible with the provided filter key', - UNSUPPORTED_ERROR + UNSUPPORTED_ERROR, ); }); it('should not delete playbook if no Manage Playbooks capability', async () => { diff --git a/opencti-platform/opencti-graphql/tests/03-integration/02-resolvers/report-test.js b/opencti-platform/opencti-graphql/tests/03-integration/02-resolvers/report-test.js index 6bb5b67573a0..394de13d5c5c 100644 --- a/opencti-platform/opencti-graphql/tests/03-integration/02-resolvers/report-test.js +++ b/opencti-platform/opencti-graphql/tests/03-integration/02-resolvers/report-test.js @@ -105,6 +105,8 @@ const READ_QUERY = gql` } `; +// TODO : find a way to mock EE + describe('Report resolver standard behavior', () => { let reportInternalId; let datasetReportInternalId; @@ -457,7 +459,7 @@ describe('Report resolver standard behavior', () => { } `, variables: { - id: reportInternalId + id: reportInternalId, }, }); }; @@ -481,7 +483,7 @@ describe('Report resolver standard behavior', () => { }], filterGroups: [], }, - noFiltersChecking: true + noFiltersChecking: true, }, ); await Promise.all(investigations.map(({ id }) => deleteElementById(testContext, ADMIN_USER, id, ENTITY_TYPE_WORKSPACE))); diff --git a/opencti-platform/opencti-graphql/tests/03-integration/02-resolvers/requestAccess-test.ts b/opencti-platform/opencti-graphql/tests/03-integration/02-resolvers/requestAccess-test.ts index 2315921e2a4c..1f907b6881b3 100644 --- a/opencti-platform/opencti-graphql/tests/03-integration/02-resolvers/requestAccess-test.ts +++ b/opencti-platform/opencti-graphql/tests/03-integration/02-resolvers/requestAccess-test.ts @@ -9,14 +9,14 @@ import { TEST_ORGANIZATION, testContext, USER_DISINFORMATION_ANALYST, - USER_EDITOR + USER_EDITOR, } from '../../utils/testQuery'; import { findById as findRFIById } from '../../../src/modules/case/case-rfi/case-rfi-domain'; -import { enableCEAndUnSetOrganization, enableEEAndSetOrganization, queryAsAdminWithSuccess, queryAsUserWithSuccess } from '../../utils/testQueryHelper'; +import { unSetOrganization, setOrganization, queryAsAdminWithSuccess, queryAsUserWithSuccess } from '../../utils/testQueryHelper'; import { getOrganizationEntity } from '../../utils/domainQueryHelper'; import { ActionStatus, type RequestAccessAction } from '../../../src/modules/requestAccess/requestAccess-domain'; import { ENTITY_TYPE_CONTAINER_CASE_RFI } from '../../../src/modules/case/case-rfi/case-rfi-types'; -import { findTemplatePaginated, } from '../../../src/domain/status'; +import { findTemplatePaginated } from '../../../src/domain/status'; import { FilterMode, OrderingMode, type RequestAccessConfigureInput, RequestAccessType, type StatusAddInput, StatusOrdering, StatusScope } from '../../../src/generated/graphql'; import { logApp } from '../../../src/config/conf'; import { ENTITY_TYPE_STATUS } from '../../../src/schema/internalObject'; @@ -224,6 +224,8 @@ const ADD_REQUEST_ACCESS_STATUS_MUTATION = gql` } `; +// TODO : find a way to mock EE + describe('Add Request Access to an entity and create an RFI.', async () => { let caseRfiIdForApproval: string; let caseRfiIdForReject: string; @@ -235,7 +237,7 @@ describe('Add Request Access to an entity and create an RFI.', async () => { let greenGroupId: string; it('should enable platform organization', async () => { - await enableEEAndSetOrganization(TEST_ORGANIZATION); + await setOrganization(TEST_ORGANIZATION); // Verify initial data required for tests. expect(USER_EDITOR.organizations?.some((organization) => organization.name === TEST_ORGANIZATION.name)); @@ -270,27 +272,27 @@ describe('Add Request Access to an entity and create an RFI.', async () => { const inputPending: StatusAddInput = { order: 3, scope: StatusScope.RequestAccess, - template_id: pendingTemplate?.node.id ?? '' + template_id: pendingTemplate?.node.id ?? '', }; logApp.info('[TEST] inputPending:', { inputPending }); await queryAsAdminWithSuccess({ query: ADD_REQUEST_ACCESS_STATUS_MUTATION, variables: { input: inputPending, - id: ENTITY_TYPE_CONTAINER_CASE_RFI + id: ENTITY_TYPE_CONTAINER_CASE_RFI, }, }); const inputClosed: StatusAddInput = { order: 3, scope: StatusScope.RequestAccess, - template_id: closedTemplate?.node.id ?? '' + template_id: closedTemplate?.node.id ?? '', }; await queryAsAdminWithSuccess({ query: ADD_REQUEST_ACCESS_STATUS_MUTATION, variables: { input: inputClosed, - id: ENTITY_TYPE_CONTAINER_CASE_RFI + id: ENTITY_TYPE_CONTAINER_CASE_RFI, }, }); @@ -312,7 +314,7 @@ describe('Add Request Access to an entity and create an RFI.', async () => { mode: FilterMode.And, filters: [{ key: ['type'], values: [ENTITY_TYPE_CONTAINER_CASE_RFI] }, { key: ['scope'], values: [StatusScope.RequestAccess] }], filterGroups: [], - } + }, }; const allRequestAccessStatuses = await fullEntitiesList(testContext, ADMIN_USER, [ENTITY_TYPE_STATUS], argsFilter); logApp.info('[TEST] allRequestAccessStatuses:', { allRequestAccessStatuses }); @@ -343,13 +345,13 @@ describe('Add Request Access to an entity and create an RFI.', async () => { const input: RequestAccessConfigureInput = { approved_status_id: pendingTemplate?.node.id, declined_status_id: closedTemplate?.node.id, - approval_admin: [greenGroupId] + approval_admin: [greenGroupId], }; await queryAsAdminWithSuccess({ query: CONFIGURE_REQUEST_ACCESS_MUTATION, variables: { - input + input, }, }); @@ -368,12 +370,12 @@ describe('Add Request Access to an entity and create an RFI.', async () => { const inputBackToNormal: RequestAccessConfigureInput = { approved_status_id: approvedTemplate?.node.id, declined_status_id: declinedTemplate?.node.id, - approval_admin: [amberGroupId] + approval_admin: [amberGroupId], }; await queryAsAdminWithSuccess({ query: CONFIGURE_REQUEST_ACCESS_MUTATION, variables: { - input: inputBackToNormal + input: inputBackToNormal, }, }); @@ -403,13 +405,13 @@ describe('Add Request Access to an entity and create an RFI.', async () => { const input: RequestAccessConfigureInput = { approved_status_id: approvedTemplate?.node.id, declined_status_id: declinedTemplate?.node.id, - approval_admin: [amberGroupId] + approval_admin: [amberGroupId], }; await queryAsAdminWithSuccess({ query: CONFIGURE_REQUEST_ACCESS_MUTATION, variables: { - input + input, }, }); @@ -479,12 +481,12 @@ describe('Add Request Access to an entity and create an RFI.', async () => { { member_id: OPENCTI_ADMIN_UUID, access_right: MEMBER_ACCESS_RIGHT_ADMIN, - groups_restriction: [] + groups_restriction: [], }, { member_id: testOrgId, access_right: MEMBER_ACCESS_RIGHT_EDIT, - groups_restriction: [{ id: amberGroupId, name: AMBER_GROUP.name }] - } + groups_restriction: [{ id: amberGroupId, name: AMBER_GROUP.name }], + }, ]); // We need data from database because JSON field x_opencti_request_access is internal (not on API) @@ -500,12 +502,12 @@ describe('Add Request Access to an entity and create an RFI.', async () => { it('should accept the created Case RFI first time be ok', async () => { // FIXME use a user and not admin ! /* - const approvalResult = await queryAsUserWithSuccess(USER_EDITOR.client, { - query: APPROVE_RFI_QUERY, - variables: { id: caseRfiIdForApproval }, - }); - expect(approvalResult?.data?.caseRfiApprove.x_opencti_workflow_id).toBe(approvedStatusId); - */ + const approvalResult = await queryAsUserWithSuccess(USER_EDITOR.client, { + query: APPROVE_RFI_QUERY, + variables: { id: caseRfiIdForApproval }, + }); + expect(approvalResult?.data?.caseRfiApprove.x_opencti_workflow_id).toBe(approvedStatusId); + */ const approvalResult = await queryAsAdminWithSuccess({ query: APPROVE_RFI_QUERY, @@ -520,7 +522,7 @@ describe('Add Request Access to an entity and create an RFI.', async () => { const getRfiQueryResult = await queryAsAdminWithSuccess({ query: READ_RFI_QUERY, - variables: { id: caseRfiIdForApproval } + variables: { id: caseRfiIdForApproval }, }); expect(getRfiQueryResult?.data?.caseRfi).not.toBeNull(); @@ -531,11 +533,11 @@ describe('Add Request Access to an entity and create an RFI.', async () => { it('should accept the created Case RFI second time be ok too', async () => { // FIXME use a user and not admin ! /* - const approvalResult = await queryAsUserWithSuccess(USER_EDITOR.client, { - query: APPROVE_RFI_QUERY, - variables: { id: caseRfiIdForApproval }, - }); - */ + const approvalResult = await queryAsUserWithSuccess(USER_EDITOR.client, { + query: APPROVE_RFI_QUERY, + variables: { id: caseRfiIdForApproval }, + }); + */ const approvalResult = await queryAsAdminWithSuccess({ query: APPROVE_RFI_QUERY, variables: { id: caseRfiIdForApproval }, @@ -569,7 +571,7 @@ describe('Add Request Access to an entity and create an RFI.', async () => { const getRfiQueryResult = await queryAsAdminWithSuccess({ query: READ_RFI_QUERY, - variables: { id: caseRfiIdForReject } + variables: { id: caseRfiIdForReject }, }); expect(getRfiQueryResult?.data?.caseRfi).not.toBeNull(); expect(getRfiQueryResult?.data?.caseRfi.status.template.name).toEqual('DECLINED'); // 'DECLINED' coming from data-initialization @@ -590,11 +592,11 @@ describe('Add Request Access to an entity and create an RFI.', async () => { it('should be ok to accept the Case RFI when already rejected', async () => { /* FIXME - const queryResult = await queryAsUserWithSuccess(USER_EDITOR.client, { - query: APPROVE_RFI_QUERY, - variables: { input: { id: caseRfiIdForReject }, id: caseRfiIdForReject }, - }); - */ + const queryResult = await queryAsUserWithSuccess(USER_EDITOR.client, { + query: APPROVE_RFI_QUERY, + variables: { input: { id: caseRfiIdForReject }, id: caseRfiIdForReject }, + }); + */ const queryResult = await queryAsAdminWithSuccess({ query: APPROVE_RFI_QUERY, variables: { input: { id: caseRfiIdForReject }, id: caseRfiIdForReject }, @@ -609,6 +611,6 @@ describe('Add Request Access to an entity and create an RFI.', async () => { await internalDeleteElementById(testContext, ADMIN_USER, caseRfiIdForReject, ENTITY_TYPE_CONTAINER_CASE_RFI); // revert platform orga - await enableCEAndUnSetOrganization(); + await unSetOrganization(); }); }); diff --git a/opencti-platform/opencti-graphql/tests/03-integration/02-resolvers/stixDomainObject-test.js b/opencti-platform/opencti-graphql/tests/03-integration/02-resolvers/stixDomainObject-test.js index 135ede77136b..25127f27c044 100644 --- a/opencti-platform/opencti-graphql/tests/03-integration/02-resolvers/stixDomainObject-test.js +++ b/opencti-platform/opencti-graphql/tests/03-integration/02-resolvers/stixDomainObject-test.js @@ -69,6 +69,8 @@ const READ_QUERY = gql` } `; +// TODO : find a way to mock EE + describe('StixDomainObject resolver standard behavior', () => { let stixDomainObjectInternalId; const stixDomainObjectStixId = 'tool--34c9875d-8206-4f4b-bf17-f58d9cf7ebec'; diff --git a/opencti-platform/opencti-graphql/tests/03-integration/02-resolvers/stream-test.ts b/opencti-platform/opencti-graphql/tests/03-integration/02-resolvers/stream-test.ts index a1b5cf04883d..3acefc535f1f 100644 --- a/opencti-platform/opencti-graphql/tests/03-integration/02-resolvers/stream-test.ts +++ b/opencti-platform/opencti-graphql/tests/03-integration/02-resolvers/stream-test.ts @@ -7,6 +7,8 @@ import { getGroupEntity } from '../../utils/domainQueryHelper'; import { AMBER_GROUP, USER_CONNECTOR, USER_PARTICIPATE } from '../../utils/testQuery'; import { MEMBER_ACCESS_RIGHT_VIEW } from '../../../src/utils/access'; +// TODO : find a way to mock EE ? + describe('Stream resolver coverage', () => { let publicStreamId: string; let amberRestrictedStreamId: string; @@ -17,7 +19,7 @@ describe('Stream resolver coverage', () => { description: 'Public stream for resolver tests - description', filters: JSON.stringify({ mode: 'and', filters: [{ key: ['entity_type'], operator: 'eq', values: ['Domain-Name'], mode: 'or' }], filterGroups: [] }), name: 'Public stream for resolver tests', - stream_public: true + stream_public: true, }; const publicStreamResponse = await queryAsAdminWithSuccess({ @@ -36,7 +38,7 @@ describe('Stream resolver coverage', () => { } }, `, - variables: { input: publicStreamInput } + variables: { input: publicStreamInput }, }); logApp.info('publicStreamResponse:', publicStreamResponse); @@ -57,7 +59,7 @@ describe('Stream resolver coverage', () => { filters: JSON.stringify({ mode: 'and', filters: [{ key: ['entity_type'], operator: 'eq', values: ['City'], mode: 'or' }], filterGroups: [] }), name: 'Restricted to AMBER stream for resolver tests', stream_public: false, - authorized_members: [{ id: amberGroup.id, access_right: MEMBER_ACCESS_RIGHT_VIEW }] + authorized_members: [{ id: amberGroup.id, access_right: MEMBER_ACCESS_RIGHT_VIEW }], }; const amberRestrictedStreamResponse = await queryAsAdminWithSuccess({ @@ -76,7 +78,7 @@ describe('Stream resolver coverage', () => { } }, `, - variables: { input: amberRestrictedStreamInput } + variables: { input: amberRestrictedStreamInput }, }); logApp.info('amberRestrictedStreamResponse:', amberRestrictedStreamResponse); @@ -114,7 +116,7 @@ describe('Stream resolver coverage', () => { } }, `, - variables: { input: restrictedStreamInput } + variables: { input: restrictedStreamInput }, }); logApp.info('amberRestrictedStreamResponse:', restrictedStreamResponse); @@ -146,7 +148,7 @@ describe('Stream resolver coverage', () => { } }, `, - variables: {} + variables: {}, }); logApp.info('allStreamsResponse:', allStreamsResponse); @@ -181,7 +183,7 @@ describe('Stream resolver coverage', () => { } }, `, - variables: {} + variables: {}, }); logApp.info('allStreamsResponse:', allStreamsResponse); @@ -216,7 +218,7 @@ describe('Stream resolver coverage', () => { } }, `, - variables: {} + variables: {}, }); logApp.info('allStreamsResponse:', allStreamsResponse); @@ -242,7 +244,7 @@ describe('Stream resolver coverage', () => { } }, `, - variables: { id: publicStreamId } + variables: { id: publicStreamId }, }); logApp.info('deletePublicStreamResponse:', deletePublicStreamResponse); expect(deletePublicStreamResponse?.data?.streamCollectionEdit?.delete).toBeDefined(); @@ -257,7 +259,7 @@ describe('Stream resolver coverage', () => { } }, `, - variables: { id: amberRestrictedStreamId } + variables: { id: amberRestrictedStreamId }, }); logApp.info('deleteGroupRestrictedStreamResponse:', deleteGroupRestrictedStreamResponse); expect(deleteGroupRestrictedStreamResponse?.data?.streamCollectionEdit?.delete).toBeDefined(); @@ -272,7 +274,7 @@ describe('Stream resolver coverage', () => { } }, `, - variables: { id: restrictedStreamId } + variables: { id: restrictedStreamId }, }); logApp.info('deleteRestrictedStreamResponse:', deleteRestrictedStreamResponse); expect(deleteRestrictedStreamResponse?.data?.streamCollectionEdit?.delete).toBeDefined(); diff --git a/opencti-platform/opencti-graphql/tests/03-integration/02-resolvers/taxiiCollection-test.ts b/opencti-platform/opencti-graphql/tests/03-integration/02-resolvers/taxiiCollection-test.ts index a582fa930ceb..e2932d972275 100644 --- a/opencti-platform/opencti-graphql/tests/03-integration/02-resolvers/taxiiCollection-test.ts +++ b/opencti-platform/opencti-graphql/tests/03-integration/02-resolvers/taxiiCollection-test.ts @@ -7,6 +7,8 @@ import { getGroupEntity } from '../../utils/domainQueryHelper'; import { AMBER_GROUP, USER_CONNECTOR, USER_PARTICIPATE } from '../../utils/testQuery'; import { MEMBER_ACCESS_RIGHT_VIEW } from '../../../src/utils/access'; +// TODO : find a way to mock EE ? + describe('Taxii collection resolver coverage', () => { let publicCollectionId: string; let amberRestrictedCollectionId: string; @@ -20,7 +22,7 @@ describe('Taxii collection resolver coverage', () => { taxii_public: true, include_inferences: true, score_to_confidence: false, - filters: JSON.stringify({ mode: 'and', filters: [{ key: ['entity_type'], operator: 'eq', values: ['Credential'], mode: 'or' }], filterGroups: [] }) + filters: JSON.stringify({ mode: 'and', filters: [{ key: ['entity_type'], operator: 'eq', values: ['Credential'], mode: 'or' }], filterGroups: [] }), }; const publicTaxiiResponse = await queryAsAdminWithSuccess({ @@ -39,7 +41,7 @@ describe('Taxii collection resolver coverage', () => { } }, `, - variables: { input: publicTaxiiInput } + variables: { input: publicTaxiiInput }, }); logApp.info('publicTaxiiResponse:', publicTaxiiResponse); @@ -62,7 +64,7 @@ describe('Taxii collection resolver coverage', () => { include_inferences: true, score_to_confidence: false, filters: JSON.stringify({ mode: 'and', filters: [{ key: ['entity_type'], operator: 'eq', values: ['Report'], mode: 'or' }], filterGroups: [] }), - authorized_members: [{ id: amberGroup.id, access_right: MEMBER_ACCESS_RIGHT_VIEW }] + authorized_members: [{ id: amberGroup.id, access_right: MEMBER_ACCESS_RIGHT_VIEW }], }; const amberRestrictedTaxiiResponse = await queryAsAdminWithSuccess({ @@ -81,7 +83,7 @@ describe('Taxii collection resolver coverage', () => { } }, `, - variables: { input: amberRestrictedTaxiiInput } + variables: { input: amberRestrictedTaxiiInput }, }); logApp.info('amberRestrictedTaxiiResponse:', amberRestrictedTaxiiResponse); @@ -102,7 +104,7 @@ describe('Taxii collection resolver coverage', () => { taxii_public: false, include_inferences: true, score_to_confidence: false, - filters: JSON.stringify({ mode: 'and', filters: [{ key: ['entity_type'], operator: 'eq', values: ['Campaign'], mode: 'or' }], filterGroups: [] }) + filters: JSON.stringify({ mode: 'and', filters: [{ key: ['entity_type'], operator: 'eq', values: ['Campaign'], mode: 'or' }], filterGroups: [] }), }; const restrictedTaxiiResponse = await queryAsAdminWithSuccess({ @@ -121,7 +123,7 @@ describe('Taxii collection resolver coverage', () => { } }, `, - variables: { input: restrictedTaxiiInput } + variables: { input: restrictedTaxiiInput }, }); logApp.info('amberRestrictedTaxiiResponse:', restrictedTaxiiResponse); @@ -153,7 +155,7 @@ describe('Taxii collection resolver coverage', () => { } }, `, - variables: {} + variables: {}, }); logApp.info('allTaxiisResponse:', allTaxiisResponse); @@ -188,7 +190,7 @@ describe('Taxii collection resolver coverage', () => { } }, `, - variables: {} + variables: {}, }); logApp.info('allTaxiisResponse:', allTaxiisResponse); @@ -223,7 +225,7 @@ describe('Taxii collection resolver coverage', () => { } }, `, - variables: {} + variables: {}, }); logApp.info('allTaxiisResponse:', allTaxiisResponse); @@ -249,7 +251,7 @@ describe('Taxii collection resolver coverage', () => { } }, `, - variables: { id: publicCollectionId } + variables: { id: publicCollectionId }, }); logApp.info('deletePublicTaxiiResponse:', deletePublicTaxiiResponse); expect(deletePublicTaxiiResponse?.data?.taxiiCollectionEdit?.delete).toBeDefined(); @@ -264,7 +266,7 @@ describe('Taxii collection resolver coverage', () => { } }, `, - variables: { id: amberRestrictedCollectionId } + variables: { id: amberRestrictedCollectionId }, }); logApp.info('deleteGroupRestrictedTaxiiResponse:', deleteGroupRestrictedTaxiiResponse); expect(deleteGroupRestrictedTaxiiResponse?.data?.taxiiCollectionEdit?.delete).toBeDefined(); @@ -279,7 +281,7 @@ describe('Taxii collection resolver coverage', () => { } }, `, - variables: { id: restrictedCollectionId } + variables: { id: restrictedCollectionId }, }); logApp.info('deleteRestrictedTaxiiResponse:', deleteRestrictedTaxiiResponse); expect(deleteRestrictedTaxiiResponse?.data?.taxiiCollectionEdit?.delete).toBeDefined(); diff --git a/opencti-platform/opencti-graphql/tests/03-integration/02-resolvers/user-test.ts b/opencti-platform/opencti-graphql/tests/03-integration/02-resolvers/user-test.ts index 104f764b22f6..d6adc0471cc7 100644 --- a/opencti-platform/opencti-graphql/tests/03-integration/02-resolvers/user-test.ts +++ b/opencti-platform/opencti-graphql/tests/03-integration/02-resolvers/user-test.ts @@ -1,5 +1,5 @@ import gql from 'graphql-tag'; -import { afterAll, beforeAll, describe, expect, it } from 'vitest'; +import { afterAll, beforeAll, describe, expect, it, vi } from 'vitest'; import { elLoadById } from '../../../src/database/engine'; import { generateStandardId } from '../../../src/schema/identifier'; import { ENTITY_TYPE_CAPABILITY, ENTITY_TYPE_GROUP, ENTITY_TYPE_USER } from '../../../src/schema/internalObject'; @@ -19,24 +19,25 @@ import { TESTING_USERS, USER_CONNECTOR, USER_DISINFORMATION_ANALYST, - USER_EDITOR + USER_EDITOR, } from '../../utils/testQuery'; import { ENTITY_TYPE_IDENTITY_ORGANIZATION } from '../../../src/modules/organization/organization-types'; import { VIRTUAL_ORGANIZATION_ADMIN } from '../../../src/utils/access'; import { adminQueryWithError, adminQueryWithSuccess, - enableCEAndUnSetOrganization, - enableEEAndSetOrganization, + unSetOrganization, + setOrganization, queryAsAdminWithSuccess, queryAsUserIsExpectedError, queryAsUserIsExpectedForbidden, - queryAsUserWithSuccess + queryAsUserWithSuccess, } from '../../utils/testQueryHelper'; import { OPENCTI_ADMIN_UUID } from '../../../src/schema/general'; import type { Capability, Member, UserAddInput } from '../../../src/generated/graphql'; import { storeLoadById } from '../../../src/database/middleware-loader'; import { entitiesCounter } from '../../02-dataInjection/01-dataCount/entityCountHelper'; +import * as entrepriseEdition from '../../../src/enterprise-edition/ee'; const LIST_QUERY = gql` query users( @@ -307,7 +308,7 @@ describe('User resolver standard behavior', () => { user_confidence_level: { max_confidence: 50, overrides: [{ entity_type: 'Report', max_confidence: 80 }], - } + }, }, }; const user2 = await adminQuery({ @@ -432,7 +433,7 @@ describe('User resolver standard behavior', () => { query: UPDATE_QUERY, variables: { id: userInternalId, - input: { key: 'user_confidence_level', value: { max_confidence: 33, overrides: [] } } + input: { key: 'user_confidence_level', value: { max_confidence: 33, overrides: [] } }, }, }); expect(queryResult.data.userEdit.fieldPatch.user_confidence_level.max_confidence).toEqual(33); @@ -480,7 +481,7 @@ describe('User resolver standard behavior', () => { group_confidence_level: { max_confidence: 60, overrides: [], - } + }, }, }; const group = await adminQuery({ @@ -543,7 +544,7 @@ describe('User resolver standard behavior', () => { query: UPDATE_QUERY, variables: { id: userInternalId, - input: { key: 'user_confidence_level', value: [null] } + input: { key: 'user_confidence_level', value: [null] }, }, }); const { userEdit } = queryResult.data; @@ -796,7 +797,7 @@ describe('User has no capability query behavior', () => { query: GROUP_UPDATE_QUERY, variables: { id: 'group--a7991a4f-6192-59a4-87d3-d006d2c41cc8', - input: { key: 'default_assignation', value: [false] } + input: { key: 'default_assignation', value: [false] }, }, }); // Create the user @@ -829,7 +830,7 @@ describe('User has no capability query behavior', () => { query: GROUP_UPDATE_QUERY, variables: { id: 'group--a7991a4f-6192-59a4-87d3-d006d2c41cc8', - input: { key: 'default_assignation', value: [true] } + input: { key: 'default_assignation', value: [true] }, }, }); }); @@ -1087,7 +1088,7 @@ describe('meUser specific resolvers', async () => { password: USER_EDITOR.password, input: [ { key: 'language', value: 'fr-fr' }, - ] + ], }; const queryResult = await queryAsUserWithSuccess(USER_EDITOR.client, { query: ME_EDIT, @@ -1100,7 +1101,7 @@ describe('meUser specific resolvers', async () => { password: USER_EDITOR.password, input: [ { key: 'api_token', value: 'd434ce02-e58e-4cac-8b4c-42bf16748e84' }, - ] + ], }; await queryAsUserIsExpectedForbidden(USER_EDITOR.client, { query: ME_EDIT, @@ -1113,7 +1114,7 @@ describe('meUser specific resolvers', async () => { input: [ { key: 'language', value: 'en-us' }, { key: 'theme', value: 'dark' }, - ] + ], }; const queryResult = await queryAsUserWithSuccess(USER_EDITOR.client, { query: ME_EDIT, @@ -1127,7 +1128,7 @@ describe('meUser specific resolvers', async () => { input: [ { key: 'language', value: 'fr-fr' }, { key: 'api_token', value: 'd434ce02-e58e-4cac-8b4c-42bf16748e84' }, - ] + ], }; await queryAsUserIsExpectedForbidden(USER_EDITOR.client, { query: ME_EDIT, @@ -1139,7 +1140,7 @@ describe('meUser specific resolvers', async () => { password: 'incorrect_current_password', input: [ { key: 'password', value: 'new_password' }, - ] + ], }; await queryAsUserIsExpectedError(USER_EDITOR.client, { query: ME_EDIT, @@ -1150,7 +1151,7 @@ describe('meUser specific resolvers', async () => { describe('User is impersonated', async () => { it('Applicant user without any organization is rejected when a platform organization is set', async () => { - await enableEEAndSetOrganization(TEST_ORGANIZATION); + await setOrganization(TEST_ORGANIZATION); const CREATE_REPORT_QUERY = gql` mutation ReportAdd($input: ReportAddInput!) { @@ -1176,7 +1177,7 @@ describe('User is impersonated', async () => { expect(reportQuery.errors).toBeDefined(); // revert platform orga - await enableCEAndUnSetOrganization(); + await unSetOrganization(); }); }); @@ -1241,7 +1242,7 @@ describe('Service account User coverage', async () => { query: UPDATE_QUERY, variables: { id: userInternalId, - input: { key: 'user_service_account', value: [false] } + input: { key: 'user_service_account', value: [false] }, }, }); const { userEdit } = queryResult.data; @@ -1255,7 +1256,7 @@ describe('Service account User coverage', async () => { query: UPDATE_QUERY, variables: { id: userInternalId, - input: [{ key: 'user_service_account', value: [true] }, { key: 'password', value: ['toto'] }] + input: [{ key: 'user_service_account', value: [true] }, { key: 'password', value: ['toto'] }], }, }); const { userEdit } = queryResult.data; @@ -1269,7 +1270,7 @@ describe('Service account User coverage', async () => { query: UPDATE_QUERY, variables: { id: userInternalId, - input: [{ key: 'password', value: ['toto'] }] + input: [{ key: 'password', value: ['toto'] }], }, }, 'Cannot update password for Service account'); }); diff --git a/opencti-platform/opencti-graphql/tests/03-integration/04-manager/connectorManager-test.ts b/opencti-platform/opencti-graphql/tests/03-integration/04-manager/connectorManager-test.ts index 32c84b6fabe6..fb1c2bb5c934 100644 --- a/opencti-platform/opencti-graphql/tests/03-integration/04-manager/connectorManager-test.ts +++ b/opencti-platform/opencti-graphql/tests/03-integration/04-manager/connectorManager-test.ts @@ -8,25 +8,27 @@ import type { RegisterConnectorInput } from '../../../src/generated/graphql'; import { ConnectorType } from '../../../src/generated/graphql'; import { elIndex } from '../../../src/database/engine'; import { ENTITY_TYPE_WORK } from '../../../src/schema/internalObject'; -import { INDEX_HISTORY } from '../../../src/database/utils'; +import { INDEX_HISTORY, RABBIT_QUEUE_PREFIX } from '../../../src/database/utils'; import { deleteCompletedWorks } from '../../../src/manager/connectorManager'; import type { BasicStoreEntityConnector } from '../../../src/types/connector'; import type { Work } from '../../../src/types/work'; +import { unregisterConnector, metrics } from '../../../src/database/rabbitmq'; describe('Old work of connector cleanup test', () => { let testConnector: BasicStoreEntityConnector; + const testConnectorId = uuid(); const createConnectorForTest = async () => { const connectorData: RegisterConnectorInput = { - id: uuid(), + id: testConnectorId, name: 'test-connector-manager-fake-connector', - type: ConnectorType.ExternalImport + type: ConnectorType.ExternalImport, }; testConnector = await registerConnector(testContext, ADMIN_USER, connectorData); expect(testConnector.id).toBeDefined(); }; - const createWorkForTest = async (name:string, dateForWork: Date, status: string) => { + const createWorkForTest = async (name: string, dateForWork: Date, status: string) => { // cheat and create a work in the past in elastic const dateForWorkStr = dateForWork.toISOString(); const workId = `work_${testConnector.id}_${dateForWorkStr}`; @@ -87,4 +89,16 @@ describe('Old work of connector cleanup test', () => { expect(allWorkAfterCleanup.some((workItem: Work) => workItem.name === 'Work 9 days old and not complete')).toBeTruthy(); expect(allWorkAfterCleanup.some((workItem: Work) => workItem.name === 'Work 8 days old and complete')).toBeFalsy(); }); + + it('should delete connector', async () => { + const unregister = await unregisterConnector(testConnectorId); + expect(unregister.listen).not.toBeNull(); + expect(unregister.listen.messageCount).toEqual(0); + expect(unregister.push).not.toBeNull(); + expect(unregister.push.messageCount).toEqual(0); + const data = await metrics(testContext, ADMIN_USER); + const aggregationMap = new Map(data.queues.map((queue: any) => [queue.name, queue])); + expect(aggregationMap.get(`${RABBIT_QUEUE_PREFIX}listen_${testConnectorId}`)).toBeUndefined(); + expect(aggregationMap.get(`${RABBIT_QUEUE_PREFIX}push_${testConnectorId}`)).toBeUndefined(); + }); }); diff --git a/opencti-platform/opencti-graphql/tests/03-integration/04-manager/telemetryManager-test.ts b/opencti-platform/opencti-graphql/tests/03-integration/04-manager/telemetryManager-test.ts index bdc9cc50523c..6f98d0f15dc8 100644 --- a/opencti-platform/opencti-graphql/tests/03-integration/04-manager/telemetryManager-test.ts +++ b/opencti-platform/opencti-graphql/tests/03-integration/04-manager/telemetryManager-test.ts @@ -9,6 +9,8 @@ import { addDisseminationCount, fetchTelemetryData, TELEMETRY_GAUGE_DISSEMINATIO import { redisClearTelemetry, redisGetTelemetry, redisSetTelemetryAdd } from '../../../src/database/redis'; import { waitInSec } from '../../../src/database/utils'; +// TODO : find a way to mock EE + describe('Telemetry manager test coverage', () => { test('Verify that metrics get collected from both elastic and redis', async () => { // GIVEN a configured telemetry @@ -16,7 +18,7 @@ describe('Telemetry manager test coverage', () => { [SEMRESATTRS_SERVICE_NAME]: TELEMETRY_SERVICE_NAME, [SEMRESATTRS_SERVICE_VERSION]: PLATFORM_VERSION, [SEMRESATTRS_SERVICE_INSTANCE_ID]: 'api-test-telemetry-id', - 'service.instance.creation': new Date().toUTCString() + 'service.instance.creation': new Date().toUTCString(), }); const resource = Resource.default().merge(filigranResource); diff --git a/opencti-platform/opencti-graphql/tests/utils/testQuery.ts b/opencti-platform/opencti-graphql/tests/utils/testQuery.ts index bd1ed78d289b..44b9661f69a8 100644 --- a/opencti-platform/opencti-graphql/tests/utils/testQuery.ts +++ b/opencti-platform/opencti-graphql/tests/utils/testQuery.ts @@ -75,11 +75,11 @@ export const executeExternalQuery = async (client: AxiosInstance, uri: string, q }; interface QueryOption { - workId?: string - eventId?: string - previousStandard?: string - synchronizedUpsert?: string - applicantId?: string + workId?: string; + eventId?: string; + previousStandard?: string; + synchronizedUpsert?: string; + applicantId?: string; } export const executeInternalQuery = async (client: AxiosInstance, query: unknown, variables = {}, options: QueryOption = {}) => { const headers: any = {}; @@ -98,17 +98,17 @@ export const internalAdminQuery = async (query: unknown, variables = {}, options // Roles interface Role { - id: string, - name: string, - description: string, - capabilities: string[] + id: string; + name: string; + description: string; + capabilities: string[]; } export const TESTING_ROLES: Role[] = []; const ROLE_PARTICIPATE: Role = { id: generateStandardId(ENTITY_TYPE_ROLE, { name: 'Access knowledge and participate' }), name: 'Access knowledge and participate', description: 'Only participate', - capabilities: ['KNOWLEDGE_KNPARTICIPATE', 'EXPLORE_EXUPDATE_EXDELETE'] + capabilities: ['KNOWLEDGE_KNPARTICIPATE', 'EXPLORE_EXUPDATE_EXDELETE'], }; TESTING_ROLES.push(ROLE_PARTICIPATE); export const ROLE_EDITOR: Role = { @@ -121,8 +121,8 @@ export const ROLE_EDITOR: Role = { 'EXPLORE_EXUPDATE_EXDELETE', 'EXPLORE_EXUPDATE_PUBLISH', 'TAXIIAPI_SETCOLLECTIONS', - 'KNOWLEDGE_KNUPDATE_KNMANAGEAUTHMEMBERS' - ] + 'KNOWLEDGE_KNUPDATE_KNMANAGEAUTHMEMBERS', + ], }; TESTING_ROLES.push(ROLE_EDITOR); @@ -130,7 +130,7 @@ export const ROLE_SECURITY: Role = { id: generateStandardId(ENTITY_TYPE_ROLE, { name: 'Access knowledge/exploration/settings and edit/delete' }), name: 'Access knowledge/exploration/settings and edit/delete', description: 'Knowledge/exploration/settings edit/delete', - capabilities: ['KNOWLEDGE_KNUPDATE_KNDELETE', 'KNOWLEDGE_KNUPDATE_KNMERGE', 'EXPLORE_EXUPDATE_EXDELETE', 'INVESTIGATION_INUPDATE_INDELETE', 'SETTINGS_SETACCESSES', 'SETTINGS_SECURITYACTIVITY', 'AUTOMATION_AUTMANAGE'] + capabilities: ['KNOWLEDGE_KNUPDATE_KNDELETE', 'KNOWLEDGE_KNUPDATE_KNMERGE', 'EXPLORE_EXUPDATE_EXDELETE', 'INVESTIGATION_INUPDATE_INDELETE', 'SETTINGS_SETACCESSES', 'SETTINGS_SECURITYACTIVITY', 'AUTOMATION_AUTMANAGE'], }; TESTING_ROLES.push(ROLE_SECURITY); @@ -152,7 +152,7 @@ export const ROLE_TEST_CONNECTOR: Role = { 'TAXIIAPI', 'SETTINGS_SETMARKINGS', 'SETTINGS_SETLABELS', - ] + ], }; TESTING_ROLES.push(ROLE_TEST_CONNECTOR); @@ -174,7 +174,7 @@ export const ROLE_DISINFORMATION_ANALYST: Role = { 'INGESTION_SETINGESTIONS', 'CSVMAPPERS', 'SETTINGS_SETLABELS', - ] + ], }; TESTING_ROLES.push(ROLE_DISINFORMATION_ANALYST); @@ -189,19 +189,19 @@ export const ROLE_PLATFORM_ADMIN: Role = { 'SETTINGS_FILEINDEXING', 'SETTINGS_SUPPORT', 'MODULES_MODMANAGE', - 'EXPLORE_EXUPDATE_PUBLISH' - ] + 'EXPLORE_EXUPDATE_PUBLISH', + ], }; TESTING_ROLES.push(ROLE_PLATFORM_ADMIN); // Groups export interface GroupTestData { - id: string, - name: string, - markings: string[], - roles: Role[], - group_confidence_level: ConfidenceLevel, - max_shareable_markings: string[], + id: string; + name: string; + markings: string[]; + roles: Role[]; + group_confidence_level: ConfidenceLevel; + max_shareable_markings: string[]; } export const TESTING_GROUPS: GroupTestData[] = []; @@ -286,8 +286,8 @@ TESTING_GROUPS.push(PLATFORM_ADMIN_GROUP); // Organization export interface OrganizationTestData { - name: string, - id: string + name: string; + id: string; } export const TESTING_ORGS: OrganizationTestData[] = []; @@ -305,13 +305,13 @@ TESTING_ORGS.push(PLATFORM_ORGANIZATION); // Users interface UserTestData { - id: string, - email: string, - password: string, - roles?: Role[], - organizations?: OrganizationTestData[], - groups: GroupTestData[], - client: AxiosInstance, + id: string; + email: string; + password: string; + roles?: Role[]; + organizations?: OrganizationTestData[]; + groups: GroupTestData[]; + client: AxiosInstance; } export const ADMIN_USER: AuthUser = { @@ -351,7 +351,7 @@ export const USER_PARTICIPATE: UserTestData = { password: 'participate', organizations: [TEST_ORGANIZATION], groups: [GREEN_GROUP], - client: createHttpClient('participate@opencti.io', 'participate') + client: createHttpClient('participate@opencti.io', 'participate'), }; TESTING_USERS.push(USER_PARTICIPATE); export const USER_EDITOR: UserTestData = { @@ -360,7 +360,7 @@ export const USER_EDITOR: UserTestData = { password: 'editor', organizations: [TEST_ORGANIZATION], groups: [AMBER_GROUP], - client: createHttpClient('editor@opencti.io', 'editor') + client: createHttpClient('editor@opencti.io', 'editor'), }; TESTING_USERS.push(USER_EDITOR); @@ -370,7 +370,7 @@ export const USER_SECURITY: UserTestData = { password: 'security', organizations: [PLATFORM_ORGANIZATION], groups: [AMBER_STRICT_GROUP], - client: createHttpClient('security@opencti.io', 'security') + client: createHttpClient('security@opencti.io', 'security'), }; TESTING_USERS.push(USER_SECURITY); @@ -379,7 +379,7 @@ export const USER_CONNECTOR: UserTestData = { email: 'connector@opencti.io', password: 'connector', groups: [CONNECTOR_GROUP], - client: createHttpClient('connector@opencti.io', 'connector') + client: createHttpClient('connector@opencti.io', 'connector'), }; TESTING_USERS.push(USER_CONNECTOR); @@ -389,7 +389,7 @@ export const USER_DISINFORMATION_ANALYST: UserTestData = { password: 'disinformation', organizations: [PLATFORM_ORGANIZATION], groups: [GREEN_DISINFORMATION_ANALYST_GROUP], - client: createHttpClient('anais@opencti.io', 'disinformation') + client: createHttpClient('anais@opencti.io', 'disinformation'), }; TESTING_USERS.push(USER_DISINFORMATION_ANALYST); @@ -398,7 +398,7 @@ export const USER_PLATFORM_ADMIN: UserTestData = { email: 'platform@opencti.io', password: 'platformadmin', groups: [PLATFORM_ADMIN_GROUP], - client: createHttpClient('platform@opencti.io', 'platformadmin') + client: createHttpClient('platform@opencti.io', 'platformadmin'), }; TESTING_USERS.push(USER_PLATFORM_ADMIN); @@ -457,7 +457,7 @@ const GROUP_ASSIGN_MUTATION = ` `; const createGroup = async (input: GroupTestData): Promise => { const { data } = await internalAdminQuery(GROUP_CREATION_MUTATION, { - input: { name: input.name, group_confidence_level: input.group_confidence_level } + input: { name: input.name, group_confidence_level: input.group_confidence_level }, }); for (let index = 0; index < input.markings.length; index += 1) { const marking = input.markings[index]; @@ -468,7 +468,7 @@ const createGroup = async (input: GroupTestData): Promise => { await internalAdminQuery(GROUP_EDITION_SHAREABLE_MARKINGS_MUTATION, { groupId: data.groupAdd.id, input: { key: 'max_shareable_markings', - value: [{ type: 'TLP', value: maxMarking }] + value: [{ type: 'TLP', value: maxMarking }], } }); } for (let index = 0; index < input.roles.length; index += 1) { @@ -556,7 +556,7 @@ const ROLE_EDITION_MUTATION = ` } } `; -const createRole = async (input: { name: string, description: string, capabilities: string[] }): Promise => { +const createRole = async (input: { name: string; description: string; capabilities: string[] }): Promise => { const { data } = await internalAdminQuery(ROLE_CREATION_MUTATION, { name: input.name, description: input.description }); for (let index = 0; index < input.capabilities.length; index += 1) { const capability = input.capabilities[index]; @@ -695,7 +695,7 @@ type markingType = { standard_id: string; internal_id: string }; export const buildStandardUser = ( allowedMarkings: markingType[], allMarkings?: markingType[], - capabilities?: { name: string }[] + capabilities?: { name: string }[], ): AuthUser => { return { administrated_organizations: [], @@ -751,7 +751,9 @@ const serverFromUser = new ApolloServer({ export const queryAsAdmin = async >(request: any, draftContext?: any) => { const execContext = executionContext('test', ADMIN_USER, draftContext ?? undefined); - execContext.changeDraftContext = (draftId) => { execContext.draft_context = draftId; }; + execContext.changeDraftContext = (draftId) => { + execContext.draft_context = draftId; + }; execContext.batch = computeLoaders(execContext, ADMIN_USER); const { body } = await serverFromUser.executeOperation(request, { contextValue: execContext }); if (body.kind === 'single') { diff --git a/opencti-platform/opencti-graphql/tests/utils/testQueryHelper.ts b/opencti-platform/opencti-graphql/tests/utils/testQueryHelper.ts index 4b45d5175141..fa837c27abc5 100644 --- a/opencti-platform/opencti-graphql/tests/utils/testQueryHelper.ts +++ b/opencti-platform/opencti-graphql/tests/utils/testQueryHelper.ts @@ -13,11 +13,11 @@ import { getOrganizationIdByName, type OrganizationTestData, queryAsAdmin, - testContext + testContext, } from './testQuery'; import { downloadFile } from '../../src/database/raw-file-storage'; import { streamConverter } from '../../src/database/file-storage'; -import conf, { logApp } from '../../src/config/conf'; +import { logApp } from '../../src/config/conf'; import { AUTH_REQUIRED, FORBIDDEN_ACCESS } from '../../src/config/errors'; import { getSettings, settingsEditField } from '../../src/domain/settings'; import { fileToReadStream } from '../../src/database/file-storage'; @@ -32,7 +32,7 @@ import { ENTITY_TYPE_SETTINGS } from '../../src/schema/internalObject'; * Execute the query and verify that there is no error before returning result. * @param request */ -export const queryAsAdminWithSuccess = async (request: { query: any, variables: any }) => { +export const queryAsAdminWithSuccess = async (request: { query: any; variables: any }) => { const requestResult = await queryAsAdmin({ query: request.query, variables: request.variables, @@ -45,7 +45,7 @@ export const queryAsAdminWithSuccess = async (request: { query: any, variables: return requestResult; }; -export const adminQueryWithSuccess = async (request: { query: any, variables: any }) => { +export const adminQueryWithSuccess = async (request: { query: any; variables: any }) => { const requestResult = await adminQuery({ query: request.query, variables: request.variables, @@ -59,9 +59,9 @@ export const adminQueryWithSuccess = async (request: { query: any, variables: an }; export const adminQueryWithError = async ( - request: { query: any, variables: any }, + request: { query: any; variables: any }, errorMessage?: string, - errorName?: string + errorName?: string, ) => { const requestResult = await adminQuery({ query: request.query, @@ -83,7 +83,7 @@ export const adminQueryWithError = async ( * @param client * @param request */ -export const queryAsUserWithSuccess = async (client: AxiosInstance, request: { query: any, variables: any }) => { +export const queryAsUserWithSuccess = async (client: AxiosInstance, request: { query: any; variables: any }) => { const requestResult = await executeInternalQuery(client, print(request.query), request.variables); expect(requestResult, `Something is wrong with this query: ${request.query}`).toBeDefined(); if (requestResult.errors) { @@ -98,7 +98,7 @@ export const queryAsUserWithSuccess = async (client: AxiosInstance, request: { q * @param client * @param request */ -export const queryAsUser = async (client: AxiosInstance, request: { query: any, variables: any }) => { +export const queryAsUser = async (client: AxiosInstance, request: { query: any; variables: any }) => { const result = await executeInternalQuery(client, print(request.query), request.variables); return result; }; @@ -162,41 +162,13 @@ export const readCsvFromFileStream = async (filePath: string, fileName: string) const csvLines: string[] = []; // Need an async interator to prevent blocking - + for await (const line of rl) { csvLines.push(line); } return csvLines; }; -/** - * Enable Enterprise edition for test - * @deprecated This function is useless: api-test are always run under EE with an env variable that take priority over settings - */ -export const enableEE = async () => { - const platformSettings: any = await getSettings(testContext); - const input = [ - { key: 'enterprise_license', value: [conf.get('app:enterprise_edition_license')] }, - ]; - const settingsResult = await settingsEditField(testContext, ADMIN_USER, platformSettings.id, input); - expect(settingsResult.platform_enterprise_edition.license_validated).toBeTruthy(); - resetCacheForEntity(ENTITY_TYPE_SETTINGS); -}; - -/** - * Go back to community edition - * @deprecated This function is useless: api-test are always run under EE with an env variable that take priority over settings - */ -export const disableEE = async () => { - const platformSettings: any = await getSettings(testContext); - const input = [ - { key: 'enterprise_license', value: [] }, - ]; - const settingsResult = await settingsEditField(testContext, ADMIN_USER, platformSettings.id, input); - // EE cant be disabled as setup by configuration - expect(settingsResult.platform_enterprise_edition.license_validated).toBeTruthy(); -}; - export const createUploadFromTestDataFile = async (filePathRelativeFromData: string, fileName: string, mimetype: string, encoding?: string) => { const file = fs.createReadStream( path.resolve(__dirname, `../data/${filePathRelativeFromData}`), @@ -217,17 +189,15 @@ export const createUploadFromTestDataFile = async (filePathRelativeFromData: str }; /** - * Enable Enterprise edition and set the platform organisation. + * Set the platform organisation. * @param organization organization to use as platform organisation. */ -export const enableEEAndSetOrganization = async (organization: OrganizationTestData) => { +export const setOrganization = async (organization: OrganizationTestData) => { const platformOrganizationId = await getOrganizationIdByName(organization.name); const platformSettings: any = await getSettings(testContext); - await enableEE(); - const input = [ - { key: 'platform_organization', value: [platformOrganizationId] } + { key: 'platform_organization', value: [platformOrganizationId] }, ]; const settingsResult = await settingsEditField(testContext, ADMIN_USER, platformSettings.id, input); @@ -236,14 +206,12 @@ export const enableEEAndSetOrganization = async (organization: OrganizationTestD }; /** - * Remove any platform organization and go back to community edition. + * Remove any platform organization */ -export const enableCEAndUnSetOrganization = async () => { - await disableEE(); - +export const unSetOrganization = async () => { const platformSettings: any = await getSettings(testContext); const input = [ - { key: 'platform_organization', value: [] } + { key: 'platform_organization', value: [] }, ]; const settingsResult = await settingsEditField(testContext, ADMIN_USER, platformSettings.id, input); expect(settingsResult.platform_organization).toBeUndefined(); @@ -263,9 +231,9 @@ export const awaitUntilCondition = async ( ) => { let isConditionOk = await conditionPromise(); let loopCurrent = 0; - + while (!isConditionOk === expectToBeTrue && loopCurrent < loopCount) { - await new Promise(resolve => setTimeout(resolve, sleepTimeBetweenLoop)); + await new Promise((resolve) => setTimeout(resolve, sleepTimeBetweenLoop)); isConditionOk = await conditionPromise(); loopCurrent += 1; } From fc56e71468648e23d5d255eed10d3b2f827f4059 Mon Sep 17 00:00:00 2001 From: Jeremy Cloarec <159018898+JeremyCloarec@users.noreply.github.com> Date: Tue, 6 Jan 2026 17:58:20 +0100 Subject: [PATCH 040/126] [backend] refresh label/marking in resolved filter cache when it is updated (#13880) --- .../opencti-graphql/src/database/cache.ts | 6 ++++-- .../src/manager/cacheManager.ts | 19 ++++++++++++------- 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/opencti-platform/opencti-graphql/src/database/cache.ts b/opencti-platform/opencti-graphql/src/database/cache.ts index e1c637492bc3..1fd6d3104f0c 100644 --- a/opencti-platform/opencti-graphql/src/database/cache.ts +++ b/opencti-platform/opencti-graphql/src/database/cache.ts @@ -14,15 +14,18 @@ import { type BasicStoreEntityPublicDashboard, ENTITY_TYPE_PUBLIC_DASHBOARD } fr import { wait } from './utils'; import { ENTITY_TYPE_PIR } from '../modules/pir/pir-types'; import { ENTITY_TYPE_DECAY_EXCLUSION_RULE } from '../modules/decayRule/exclusions/decayExclusionRule-types'; +import { ENTITY_TYPE_LABEL, ENTITY_TYPE_MARKING_DEFINITION } from '../schema/stixMetaObject'; const STORE_ENTITIES_LINKS: Record = { - // Resolved Filters in cache must be reset depending on connector/stream/triggers/playbooks/Pir modifications + // Resolved Filters in cache must be reset depending on connector/stream/triggers/playbooks/Pir/label modifications [ENTITY_TYPE_STREAM_COLLECTION]: [ENTITY_TYPE_RESOLVED_FILTERS], [ENTITY_TYPE_TRIGGER]: [ENTITY_TYPE_RESOLVED_FILTERS], [ENTITY_TYPE_PLAYBOOK]: [ENTITY_TYPE_RESOLVED_FILTERS], [ENTITY_TYPE_CONNECTOR]: [ENTITY_TYPE_RESOLVED_FILTERS], [ENTITY_TYPE_PIR]: [ENTITY_TYPE_RESOLVED_FILTERS], [ENTITY_TYPE_DECAY_EXCLUSION_RULE]: [ENTITY_TYPE_RESOLVED_FILTERS], + [ENTITY_TYPE_LABEL]: [ENTITY_TYPE_RESOLVED_FILTERS], + [ENTITY_TYPE_MARKING_DEFINITION]: [ENTITY_TYPE_RESOLVED_FILTERS], }; const cache: any = {}; @@ -151,7 +154,6 @@ export const getEntitiesListFromCache = async ; const result: T[] = []; - // eslint-disable-next-line no-restricted-syntax for (const value of map.values()) { result.push(value); } diff --git a/opencti-platform/opencti-graphql/src/manager/cacheManager.ts b/opencti-platform/opencti-graphql/src/manager/cacheManager.ts index bc59bf09ae34..c7a76ae769dd 100644 --- a/opencti-platform/opencti-graphql/src/manager/cacheManager.ts +++ b/opencti-platform/opencti-graphql/src/manager/cacheManager.ts @@ -25,7 +25,7 @@ import { ENTITY_TYPE_USER, } from '../schema/internalObject'; import { RELATION_MEMBER_OF, RELATION_PARTICIPATE_TO } from '../schema/internalRelationship'; -import { ENTITY_TYPE_MARKING_DEFINITION } from '../schema/stixMetaObject'; +import { ENTITY_TYPE_LABEL, ENTITY_TYPE_MARKING_DEFINITION } from '../schema/stixMetaObject'; import type { BasicStoreSettings } from '../types/settings'; import type { StixObject } from '../types/stix-2-1-common'; import { STIX_EXT_OCTI } from '../types/stix-2-1-extensions'; @@ -145,15 +145,20 @@ const platformResolvedFilters = (context: AuthContext) => { return new Map(); }; const refreshFilter = async (values: Map, instance: BasicStoreCommon) => { - const filteringIds = extractResolvedFiltersFromInstance(instance); // Resolve filters ids that are not already in the cache const currentFiltersValues = values; // current cache map const idsToSolve: string[] = []; // will contain the ids to resolve that are not already in the cache - filteringIds.forEach((id) => { - if (!currentFiltersValues.has(id)) { - idsToSolve.push(id); - } - }); + // If a label or marking was updated, we always need to update it in the cache + if (instance.entity_type === ENTITY_TYPE_LABEL || instance.entity_type === ENTITY_TYPE_MARKING_DEFINITION) { + idsToSolve.push(instance.internal_id); + } else { + const filteringIds = extractResolvedFiltersFromInstance(instance); + filteringIds.forEach((id) => { + if (!currentFiltersValues.has(id)) { + idsToSolve.push(id); + } + }); + } const loadedDependencies = await stixLoadByIds(context, SYSTEM_USER, R.uniq(idsToSolve)); // fetch the stix instance of the ids // Add resolved stix entities to the cache map loadedDependencies.forEach((l: StixObject) => currentFiltersValues.set(l.extensions[STIX_EXT_OCTI].id, l)); From e6863b5d75ee5e3c1d9602c400eae21fc3685fb7 Mon Sep 17 00:00:00 2001 From: "A. Jard" Date: Tue, 6 Jan 2026 20:10:01 +0100 Subject: [PATCH 041/126] [ci] Add SAML and OpenId autoconfiguration for local test (#13418) (#13881) --- .../opencti-dev/docker-compose.yml | 7 +- .../keycloak-configuration/master-realm.json | 2378 +++++++++++++++++ .../master-users-0.json | 146 + 3 files changed, 2529 insertions(+), 2 deletions(-) create mode 100644 opencti-platform/opencti-dev/keycloak-configuration/master-realm.json create mode 100644 opencti-platform/opencti-dev/keycloak-configuration/master-users-0.json diff --git a/opencti-platform/opencti-dev/docker-compose.yml b/opencti-platform/opencti-dev/docker-compose.yml index 154a84be8f98..d9cab0bda744 100644 --- a/opencti-platform/opencti-dev/docker-compose.yml +++ b/opencti-platform/opencti-dev/docker-compose.yml @@ -162,8 +162,11 @@ services: image: quay.io/keycloak/keycloak:26.4.5 volumes: - keycloakdata:/opt/keycloak/data + - ./keycloak-configuration:/opt/keycloak/data/import container_name: opencti-dev-keycloak - command: start-dev + command: start-dev --import-realm --verbose + # use below command to export a configuration. Remember to remove admin user. + #command: export --dir /opt/keycloak/data/import/export --users same_file environment: KC_BOOTSTRAP_ADMIN_USERNAME: "admin" KC_BOOTSTRAP_ADMIN_PASSWORD: "admin" @@ -236,4 +239,4 @@ volumes: ossnapshots: driver: local keycloakdata: - driver: local + driver: local \ No newline at end of file diff --git a/opencti-platform/opencti-dev/keycloak-configuration/master-realm.json b/opencti-platform/opencti-dev/keycloak-configuration/master-realm.json new file mode 100644 index 000000000000..02d58cf2c560 --- /dev/null +++ b/opencti-platform/opencti-dev/keycloak-configuration/master-realm.json @@ -0,0 +1,2378 @@ +{ + "id" : "36b67f38-e744-4dac-aabb-666d0c4c1339", + "realm" : "master", + "displayName" : "Keycloak", + "displayNameHtml" : "
    Keycloak
    ", + "notBefore" : 0, + "defaultSignatureAlgorithm" : "RS256", + "revokeRefreshToken" : false, + "refreshTokenMaxReuse" : 0, + "accessTokenLifespan" : 600, + "accessTokenLifespanForImplicitFlow" : 900, + "ssoSessionIdleTimeout" : 1800, + "ssoSessionMaxLifespan" : 36000, + "ssoSessionIdleTimeoutRememberMe" : 0, + "ssoSessionMaxLifespanRememberMe" : 0, + "offlineSessionIdleTimeout" : 2592000, + "offlineSessionMaxLifespanEnabled" : false, + "offlineSessionMaxLifespan" : 5184000, + "clientSessionIdleTimeout" : 0, + "clientSessionMaxLifespan" : 0, + "clientOfflineSessionIdleTimeout" : 0, + "clientOfflineSessionMaxLifespan" : 0, + "accessCodeLifespan" : 60, + "accessCodeLifespanUserAction" : 300, + "accessCodeLifespanLogin" : 1800, + "actionTokenGeneratedByAdminLifespan" : 43200, + "actionTokenGeneratedByUserLifespan" : 300, + "oauth2DeviceCodeLifespan" : 600, + "oauth2DevicePollingInterval" : 5, + "enabled" : true, + "sslRequired" : "external", + "registrationAllowed" : false, + "registrationEmailAsUsername" : false, + "rememberMe" : false, + "verifyEmail" : false, + "loginWithEmailAllowed" : true, + "duplicateEmailsAllowed" : false, + "resetPasswordAllowed" : false, + "editUsernameAllowed" : false, + "bruteForceProtected" : false, + "permanentLockout" : false, + "maxTemporaryLockouts" : 0, + "bruteForceStrategy" : "MULTIPLE", + "maxFailureWaitSeconds" : 900, + "minimumQuickLoginWaitSeconds" : 60, + "waitIncrementSeconds" : 60, + "quickLoginCheckMilliSeconds" : 1000, + "maxDeltaTimeSeconds" : 43200, + "failureFactor" : 30, + "roles" : { + "realm" : [ { + "id" : "77fb75cb-c77f-4cd8-b6b4-3fbc1bc291d7", + "name" : "connector", + "description" : "Connector group in OpenCTI", + "composite" : false, + "clientRole" : false, + "containerId" : "36b67f38-e744-4dac-aabb-666d0c4c1339", + "attributes" : { + "Group" : [ "Connector" ] + } + }, { + "id" : "ec38db3c-0bb9-4487-b3b7-18c26d9c16c8", + "name" : "administrator", + "description" : "Admin on OpenCTI", + "composite" : false, + "clientRole" : false, + "containerId" : "36b67f38-e744-4dac-aabb-666d0c4c1339", + "attributes" : { + "Group" : [ "Administrator" ] + } + }, { + "id" : "ab3bec5d-6a2a-410f-9d5c-8f6a0ded5f91", + "name" : "create-realm", + "description" : "${role_create-realm}", + "composite" : false, + "clientRole" : false, + "containerId" : "36b67f38-e744-4dac-aabb-666d0c4c1339", + "attributes" : { } + }, { + "id" : "7aec3019-f8ab-4b67-b081-b40f6f48e105", + "name" : "default-roles-master", + "description" : "${role_default-roles}", + "composite" : true, + "composites" : { + "realm" : [ "offline_access", "uma_authorization" ], + "client" : { + "account" : [ "manage-account", "view-profile" ] + } + }, + "clientRole" : false, + "containerId" : "36b67f38-e744-4dac-aabb-666d0c4c1339", + "attributes" : { } + }, { + "id" : "93399c05-b52b-4c9b-a072-5326c81619ae", + "name" : "access-api", + "description" : "", + "composite" : true, + "composites" : { + "realm" : [ "admin", "access-api" ] + }, + "clientRole" : false, + "containerId" : "36b67f38-e744-4dac-aabb-666d0c4c1339", + "attributes" : { } + }, { + "id" : "2f59b833-6118-4918-a0db-2dfd0695102a", + "name" : "offline_access", + "description" : "${role_offline-access}", + "composite" : false, + "clientRole" : false, + "containerId" : "36b67f38-e744-4dac-aabb-666d0c4c1339", + "attributes" : { } + }, { + "id" : "63853894-7ec2-417f-b836-2f35e366f450", + "name" : "uma_authorization", + "description" : "${role_uma_authorization}", + "composite" : false, + "clientRole" : false, + "containerId" : "36b67f38-e744-4dac-aabb-666d0c4c1339", + "attributes" : { } + }, { + "id" : "f116a4f1-b77c-4ce6-953f-069c8a02f70f", + "name" : "admin", + "description" : "${role_admin}", + "composite" : true, + "composites" : { + "realm" : [ "create-realm" ], + "client" : { + "master-realm" : [ "query-clients", "manage-realm", "impersonation", "view-identity-providers", "view-authorization", "manage-identity-providers", "create-client", "manage-authorization", "view-realm", "query-groups", "view-events", "query-realms", "manage-events", "query-users", "view-clients", "manage-clients", "view-users", "manage-users" ] + } + }, + "clientRole" : false, + "containerId" : "36b67f38-e744-4dac-aabb-666d0c4c1339", + "attributes" : { } + } ], + "client" : { + "security-admin-console" : [ ], + "admin-cli" : [ ], + "account-console" : [ ], + "broker" : [ { + "id" : "c4a0a33a-6ce1-4256-b5d9-18559143d499", + "name" : "read-token", + "description" : "${role_read-token}", + "composite" : false, + "clientRole" : true, + "containerId" : "e745d5a8-13ef-4f64-90b7-26e875760dad", + "attributes" : { } + } ], + "master-realm" : [ { + "id" : "f47a8ff0-5272-4e97-bb9f-f52a03b055a4", + "name" : "manage-realm", + "description" : "${role_manage-realm}", + "composite" : false, + "clientRole" : true, + "containerId" : "42a8c57a-2c64-452d-96fa-23911991ebfd", + "attributes" : { } + }, { + "id" : "b60aa9c4-dfa9-4934-9698-e778fdee2b2b", + "name" : "query-clients", + "description" : "${role_query-clients}", + "composite" : false, + "clientRole" : true, + "containerId" : "42a8c57a-2c64-452d-96fa-23911991ebfd", + "attributes" : { } + }, { + "id" : "b5806a1f-c343-4310-9b01-30a084186694", + "name" : "impersonation", + "description" : "${role_impersonation}", + "composite" : false, + "clientRole" : true, + "containerId" : "42a8c57a-2c64-452d-96fa-23911991ebfd", + "attributes" : { } + }, { + "id" : "e5d63eb0-8576-4447-a011-b672c726e734", + "name" : "view-identity-providers", + "description" : "${role_view-identity-providers}", + "composite" : false, + "clientRole" : true, + "containerId" : "42a8c57a-2c64-452d-96fa-23911991ebfd", + "attributes" : { } + }, { + "id" : "254755d0-8227-4238-9e5d-15671d7dd618", + "name" : "view-authorization", + "description" : "${role_view-authorization}", + "composite" : false, + "clientRole" : true, + "containerId" : "42a8c57a-2c64-452d-96fa-23911991ebfd", + "attributes" : { } + }, { + "id" : "178f46b6-c47a-4d60-ab1f-c251f1b4e427", + "name" : "manage-identity-providers", + "description" : "${role_manage-identity-providers}", + "composite" : false, + "clientRole" : true, + "containerId" : "42a8c57a-2c64-452d-96fa-23911991ebfd", + "attributes" : { } + }, { + "id" : "153f6722-3c24-4351-9632-714c7a60117f", + "name" : "create-client", + "description" : "${role_create-client}", + "composite" : false, + "clientRole" : true, + "containerId" : "42a8c57a-2c64-452d-96fa-23911991ebfd", + "attributes" : { } + }, { + "id" : "60f0ab87-f188-4778-a6e1-bf2ed21dceb6", + "name" : "manage-authorization", + "description" : "${role_manage-authorization}", + "composite" : false, + "clientRole" : true, + "containerId" : "42a8c57a-2c64-452d-96fa-23911991ebfd", + "attributes" : { } + }, { + "id" : "bd85692f-6343-475a-99ae-120a06ef2520", + "name" : "query-groups", + "description" : "${role_query-groups}", + "composite" : false, + "clientRole" : true, + "containerId" : "42a8c57a-2c64-452d-96fa-23911991ebfd", + "attributes" : { } + }, { + "id" : "f6f0229d-22fd-4622-8eca-7987a24512fb", + "name" : "view-events", + "description" : "${role_view-events}", + "composite" : false, + "clientRole" : true, + "containerId" : "42a8c57a-2c64-452d-96fa-23911991ebfd", + "attributes" : { } + }, { + "id" : "94c4bc84-0b6e-4260-8b1c-8aeb32e6c21f", + "name" : "view-realm", + "description" : "${role_view-realm}", + "composite" : false, + "clientRole" : true, + "containerId" : "42a8c57a-2c64-452d-96fa-23911991ebfd", + "attributes" : { } + }, { + "id" : "f47f90eb-8b8a-49b6-b933-c5e27d9991b8", + "name" : "query-realms", + "description" : "${role_query-realms}", + "composite" : false, + "clientRole" : true, + "containerId" : "42a8c57a-2c64-452d-96fa-23911991ebfd", + "attributes" : { } + }, { + "id" : "faad97e9-d824-445a-a857-867eebb8f40f", + "name" : "manage-events", + "description" : "${role_manage-events}", + "composite" : false, + "clientRole" : true, + "containerId" : "42a8c57a-2c64-452d-96fa-23911991ebfd", + "attributes" : { } + }, { + "id" : "9eaeda81-8ea4-4969-9818-5e172d0dfab6", + "name" : "query-users", + "description" : "${role_query-users}", + "composite" : false, + "clientRole" : true, + "containerId" : "42a8c57a-2c64-452d-96fa-23911991ebfd", + "attributes" : { } + }, { + "id" : "7f344b6d-38c1-4beb-9212-92544e70cf8c", + "name" : "view-clients", + "description" : "${role_view-clients}", + "composite" : true, + "composites" : { + "client" : { + "master-realm" : [ "query-clients" ] + } + }, + "clientRole" : true, + "containerId" : "42a8c57a-2c64-452d-96fa-23911991ebfd", + "attributes" : { } + }, { + "id" : "17ffcd8d-db0c-404f-96f0-e527bef990b4", + "name" : "manage-clients", + "description" : "${role_manage-clients}", + "composite" : false, + "clientRole" : true, + "containerId" : "42a8c57a-2c64-452d-96fa-23911991ebfd", + "attributes" : { } + }, { + "id" : "33f48ebc-8af3-4d72-9aec-b6ef48e72f96", + "name" : "view-users", + "description" : "${role_view-users}", + "composite" : true, + "composites" : { + "client" : { + "master-realm" : [ "query-users", "query-groups" ] + } + }, + "clientRole" : true, + "containerId" : "42a8c57a-2c64-452d-96fa-23911991ebfd", + "attributes" : { } + }, { + "id" : "6ac44d48-5504-4a66-9777-149be31c37b0", + "name" : "manage-users", + "description" : "${role_manage-users}", + "composite" : false, + "clientRole" : true, + "containerId" : "42a8c57a-2c64-452d-96fa-23911991ebfd", + "attributes" : { } + } ], + "openctioid" : [ { + "id" : "07686e6f-4730-4cdc-a077-e93896b7f106", + "name" : "uma_protection", + "composite" : false, + "clientRole" : true, + "containerId" : "7fbf7ab5-a72c-43a7-908d-b6b87bd40a8a", + "attributes" : { } + } ], + "account" : [ { + "id" : "546c6cb7-149b-4eab-873b-563b3fd79f34", + "name" : "view-groups", + "description" : "${role_view-groups}", + "composite" : false, + "clientRole" : true, + "containerId" : "fc87ede8-637c-47f6-85b2-92016b93ce5c", + "attributes" : { } + }, { + "id" : "7648fd9d-5a97-4264-a722-d26505772d1d", + "name" : "manage-account", + "description" : "${role_manage-account}", + "composite" : true, + "composites" : { + "client" : { + "account" : [ "manage-account-links" ] + } + }, + "clientRole" : true, + "containerId" : "fc87ede8-637c-47f6-85b2-92016b93ce5c", + "attributes" : { } + }, { + "id" : "5559b563-5d8c-44c2-afdc-084b09761099", + "name" : "manage-account-links", + "description" : "${role_manage-account-links}", + "composite" : false, + "clientRole" : true, + "containerId" : "fc87ede8-637c-47f6-85b2-92016b93ce5c", + "attributes" : { } + }, { + "id" : "185ea8fe-3bcf-470f-97b0-080a2710de3e", + "name" : "view-applications", + "description" : "${role_view-applications}", + "composite" : false, + "clientRole" : true, + "containerId" : "fc87ede8-637c-47f6-85b2-92016b93ce5c", + "attributes" : { } + }, { + "id" : "e24a971b-14ef-4741-82c4-df0b4c1c82bd", + "name" : "manage-consent", + "description" : "${role_manage-consent}", + "composite" : true, + "composites" : { + "client" : { + "account" : [ "view-consent" ] + } + }, + "clientRole" : true, + "containerId" : "fc87ede8-637c-47f6-85b2-92016b93ce5c", + "attributes" : { } + }, { + "id" : "135fb7fd-bc70-4e09-a248-679b5b7eda6c", + "name" : "view-profile", + "description" : "${role_view-profile}", + "composite" : false, + "clientRole" : true, + "containerId" : "fc87ede8-637c-47f6-85b2-92016b93ce5c", + "attributes" : { } + }, { + "id" : "d097b495-11b0-43a4-a0b5-b6fe259c49cf", + "name" : "view-consent", + "description" : "${role_view-consent}", + "composite" : false, + "clientRole" : true, + "containerId" : "fc87ede8-637c-47f6-85b2-92016b93ce5c", + "attributes" : { } + }, { + "id" : "de1362d0-d565-48ed-9b77-59f39aca2a41", + "name" : "delete-account", + "description" : "${role_delete-account}", + "composite" : false, + "clientRole" : true, + "containerId" : "fc87ede8-637c-47f6-85b2-92016b93ce5c", + "attributes" : { } + } ], + "openctisaml" : [ ] + } + }, + "groups" : [ { + "id" : "4d41617e-138d-433a-bd7b-6509ef6cf926", + "name" : "Administrator", + "path" : "/Administrator", + "subGroups" : [ ], + "attributes" : { + "groups" : [ "Administrator" ] + }, + "realmRoles" : [ "administrator" ], + "clientRoles" : { } + }, { + "id" : "a36c25b5-731b-4b7d-ba57-4e2c697669ef", + "name" : "Connector", + "path" : "/Connector", + "subGroups" : [ ], + "attributes" : { + "groups" : [ "Connector" ] + }, + "realmRoles" : [ "connector" ], + "clientRoles" : { } + }, { + "id" : "fa3b5147-f0b5-44d2-89b7-89caefde187b", + "name" : "Default", + "path" : "/Default", + "subGroups" : [ ], + "attributes" : { }, + "realmRoles" : [ ], + "clientRoles" : { } + }, { + "id" : "c75bb716-3dfe-431f-881c-d55147bd5988", + "name" : "Externa org", + "path" : "/Externa org", + "subGroups" : [ ], + "attributes" : { }, + "realmRoles" : [ ], + "clientRoles" : { } + }, { + "id" : "bcd9546e-c4ff-4b96-8829-87d4113d4bfd", + "name" : "Filigran org", + "path" : "/Filigran org", + "subGroups" : [ ], + "attributes" : { + "org" : [ "Filigran" ], + "groups" : [ "Filigran" ] + }, + "realmRoles" : [ ], + "clientRoles" : { } + } ], + "defaultRole" : { + "id" : "7aec3019-f8ab-4b67-b081-b40f6f48e105", + "name" : "default-roles-master", + "description" : "${role_default-roles}", + "composite" : true, + "clientRole" : false, + "containerId" : "36b67f38-e744-4dac-aabb-666d0c4c1339" + }, + "requiredCredentials" : [ "password" ], + "otpPolicyType" : "totp", + "otpPolicyAlgorithm" : "HmacSHA1", + "otpPolicyInitialCounter" : 0, + "otpPolicyDigits" : 6, + "otpPolicyLookAheadWindow" : 1, + "otpPolicyPeriod" : 30, + "otpPolicyCodeReusable" : false, + "otpSupportedApplications" : [ "totpAppFreeOTPName", "totpAppGoogleName", "totpAppMicrosoftAuthenticatorName" ], + "localizationTexts" : { }, + "webAuthnPolicyRpEntityName" : "keycloak", + "webAuthnPolicySignatureAlgorithms" : [ "ES256", "RS256" ], + "webAuthnPolicyRpId" : "", + "webAuthnPolicyAttestationConveyancePreference" : "not specified", + "webAuthnPolicyAuthenticatorAttachment" : "not specified", + "webAuthnPolicyRequireResidentKey" : "not specified", + "webAuthnPolicyUserVerificationRequirement" : "not specified", + "webAuthnPolicyCreateTimeout" : 0, + "webAuthnPolicyAvoidSameAuthenticatorRegister" : false, + "webAuthnPolicyAcceptableAaguids" : [ ], + "webAuthnPolicyExtraOrigins" : [ ], + "webAuthnPolicyPasswordlessRpEntityName" : "keycloak", + "webAuthnPolicyPasswordlessSignatureAlgorithms" : [ "ES256", "RS256" ], + "webAuthnPolicyPasswordlessRpId" : "", + "webAuthnPolicyPasswordlessAttestationConveyancePreference" : "not specified", + "webAuthnPolicyPasswordlessAuthenticatorAttachment" : "not specified", + "webAuthnPolicyPasswordlessRequireResidentKey" : "Yes", + "webAuthnPolicyPasswordlessUserVerificationRequirement" : "required", + "webAuthnPolicyPasswordlessCreateTimeout" : 0, + "webAuthnPolicyPasswordlessAvoidSameAuthenticatorRegister" : false, + "webAuthnPolicyPasswordlessAcceptableAaguids" : [ ], + "webAuthnPolicyPasswordlessExtraOrigins" : [ ], + "scopeMappings" : [ { + "clientScope" : "offline_access", + "roles" : [ "offline_access" ] + } ], + "clientScopeMappings" : { + "account" : [ { + "client" : "account-console", + "roles" : [ "manage-account", "view-groups" ] + } ] + }, + "clients" : [ { + "id" : "fc87ede8-637c-47f6-85b2-92016b93ce5c", + "clientId" : "account", + "name" : "${client_account}", + "rootUrl" : "${authBaseUrl}", + "baseUrl" : "/realms/master/account/", + "surrogateAuthRequired" : false, + "enabled" : true, + "alwaysDisplayInConsole" : false, + "clientAuthenticatorType" : "client-secret", + "redirectUris" : [ "/realms/master/account/*" ], + "webOrigins" : [ ], + "notBefore" : 0, + "bearerOnly" : false, + "consentRequired" : false, + "standardFlowEnabled" : true, + "implicitFlowEnabled" : false, + "directAccessGrantsEnabled" : false, + "serviceAccountsEnabled" : false, + "publicClient" : true, + "frontchannelLogout" : false, + "protocol" : "openid-connect", + "attributes" : { + "realm_client" : "false", + "post.logout.redirect.uris" : "+" + }, + "authenticationFlowBindingOverrides" : { }, + "fullScopeAllowed" : false, + "nodeReRegistrationTimeout" : 0, + "defaultClientScopes" : [ "web-origins", "acr", "profile", "roles", "basic", "email" ], + "optionalClientScopes" : [ "address", "phone", "offline_access", "organization", "microprofile-jwt" ] + }, { + "id" : "569ea160-4068-4545-9a71-0bc3e967cdb9", + "clientId" : "account-console", + "name" : "${client_account-console}", + "rootUrl" : "${authBaseUrl}", + "baseUrl" : "/realms/master/account/", + "surrogateAuthRequired" : false, + "enabled" : true, + "alwaysDisplayInConsole" : false, + "clientAuthenticatorType" : "client-secret", + "redirectUris" : [ "/realms/master/account/*" ], + "webOrigins" : [ ], + "notBefore" : 0, + "bearerOnly" : false, + "consentRequired" : false, + "standardFlowEnabled" : true, + "implicitFlowEnabled" : false, + "directAccessGrantsEnabled" : false, + "serviceAccountsEnabled" : false, + "publicClient" : true, + "frontchannelLogout" : false, + "protocol" : "openid-connect", + "attributes" : { + "realm_client" : "false", + "post.logout.redirect.uris" : "+", + "pkce.code.challenge.method" : "S256" + }, + "authenticationFlowBindingOverrides" : { }, + "fullScopeAllowed" : false, + "nodeReRegistrationTimeout" : 0, + "protocolMappers" : [ { + "id" : "6df538a5-4df3-4dcd-b5da-0da84cb23cf5", + "name" : "audience resolve", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-audience-resolve-mapper", + "consentRequired" : false, + "config" : { } + } ], + "defaultClientScopes" : [ "web-origins", "acr", "profile", "roles", "basic", "email" ], + "optionalClientScopes" : [ "address", "phone", "offline_access", "organization", "microprofile-jwt" ] + }, { + "id" : "d9f11af3-c863-4a55-90f6-9c8b9ad9e0f4", + "clientId" : "admin-cli", + "name" : "${client_admin-cli}", + "surrogateAuthRequired" : false, + "enabled" : true, + "alwaysDisplayInConsole" : false, + "clientAuthenticatorType" : "client-secret", + "redirectUris" : [ ], + "webOrigins" : [ ], + "notBefore" : 0, + "bearerOnly" : false, + "consentRequired" : false, + "standardFlowEnabled" : false, + "implicitFlowEnabled" : false, + "directAccessGrantsEnabled" : true, + "serviceAccountsEnabled" : false, + "publicClient" : true, + "frontchannelLogout" : false, + "protocol" : "openid-connect", + "attributes" : { + "realm_client" : "false", + "client.use.lightweight.access.token.enabled" : "true", + "post.logout.redirect.uris" : "+" + }, + "authenticationFlowBindingOverrides" : { }, + "fullScopeAllowed" : true, + "nodeReRegistrationTimeout" : 0, + "defaultClientScopes" : [ "web-origins", "acr", "profile", "roles", "basic", "email" ], + "optionalClientScopes" : [ "address", "phone", "offline_access", "organization", "microprofile-jwt" ] + }, { + "id" : "e745d5a8-13ef-4f64-90b7-26e875760dad", + "clientId" : "broker", + "name" : "${client_broker}", + "surrogateAuthRequired" : false, + "enabled" : true, + "alwaysDisplayInConsole" : false, + "clientAuthenticatorType" : "client-secret", + "redirectUris" : [ ], + "webOrigins" : [ ], + "notBefore" : 0, + "bearerOnly" : true, + "consentRequired" : false, + "standardFlowEnabled" : true, + "implicitFlowEnabled" : false, + "directAccessGrantsEnabled" : false, + "serviceAccountsEnabled" : false, + "publicClient" : false, + "frontchannelLogout" : false, + "protocol" : "openid-connect", + "attributes" : { + "realm_client" : "true", + "post.logout.redirect.uris" : "+" + }, + "authenticationFlowBindingOverrides" : { }, + "fullScopeAllowed" : false, + "nodeReRegistrationTimeout" : 0, + "defaultClientScopes" : [ "web-origins", "acr", "profile", "roles", "basic", "email" ], + "optionalClientScopes" : [ "address", "phone", "offline_access", "organization", "microprofile-jwt" ] + }, { + "id" : "42a8c57a-2c64-452d-96fa-23911991ebfd", + "clientId" : "master-realm", + "name" : "master Realm", + "surrogateAuthRequired" : false, + "enabled" : true, + "alwaysDisplayInConsole" : false, + "clientAuthenticatorType" : "client-secret", + "redirectUris" : [ ], + "webOrigins" : [ ], + "notBefore" : 0, + "bearerOnly" : true, + "consentRequired" : false, + "standardFlowEnabled" : true, + "implicitFlowEnabled" : false, + "directAccessGrantsEnabled" : false, + "serviceAccountsEnabled" : false, + "publicClient" : false, + "frontchannelLogout" : false, + "protocol" : "openid-connect", + "attributes" : { + "realm_client" : "true", + "post.logout.redirect.uris" : "+" + }, + "authenticationFlowBindingOverrides" : { }, + "fullScopeAllowed" : false, + "nodeReRegistrationTimeout" : 0, + "defaultClientScopes" : [ "web-origins", "acr", "profile", "roles", "basic", "email" ], + "optionalClientScopes" : [ "address", "phone", "offline_access", "organization", "microprofile-jwt" ] + }, { + "id" : "7fbf7ab5-a72c-43a7-908d-b6b87bd40a8a", + "clientId" : "openctioid", + "name" : "openctioid", + "description" : "", + "rootUrl" : "http://localhost:4000", + "adminUrl" : "", + "baseUrl" : "http://localhost:4000", + "surrogateAuthRequired" : false, + "enabled" : true, + "alwaysDisplayInConsole" : true, + "clientAuthenticatorType" : "client-secret", + "secret" : "aOBaQuG6WVoQ4FKhOdIWOOdJp9e0M1Fc", + "redirectUris" : [ "*" ], + "webOrigins" : [ "*" ], + "notBefore" : 0, + "bearerOnly" : false, + "consentRequired" : false, + "standardFlowEnabled" : true, + "implicitFlowEnabled" : false, + "directAccessGrantsEnabled" : false, + "serviceAccountsEnabled" : true, + "authorizationServicesEnabled" : true, + "publicClient" : false, + "frontchannelLogout" : true, + "protocol" : "openid-connect", + "attributes" : { + "request.object.signature.alg" : "any", + "frontchannel.logout.session.required" : "true", + "post.logout.redirect.uris" : "+", + "oauth2.device.authorization.grant.enabled" : "false", + "use.jwks.url" : "false", + "backchannel.logout.revoke.offline.tokens" : "false", + "use.refresh.tokens" : "true", + "realm_client" : "false", + "oidc.ciba.grant.enabled" : "false", + "backchannel.logout.session.required" : "true", + "client_credentials.use_refresh_token" : "false", + "require.pushed.authorization.requests" : "false", + "request.object.encryption.enc" : "any", + "dpop.bound.access.tokens" : "false", + "id.token.as.detached.signature" : "false", + "client.secret.creation.time" : "1706016048", + "request.object.encryption.alg" : "any", + "client.introspection.response.allow.jwt.claim.enabled" : "false", + "standard.token.exchange.enabled" : "false", + "login_theme" : "keycloak", + "client.use.lightweight.access.token.enabled" : "false", + "request.object.required" : "not required", + "access.token.header.type.rfc9068" : "false", + "tls.client.certificate.bound.access.tokens" : "false", + "acr.loa.map" : "{}", + "display.on.consent.screen" : "true", + "token.response.type.bearer.lower-case" : "false" + }, + "authenticationFlowBindingOverrides" : { }, + "fullScopeAllowed" : true, + "nodeReRegistrationTimeout" : -1, + "protocolMappers" : [ { + "id" : "9e2a651a-b105-46dc-adce-d11484673e37", + "name" : "Client ID", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usersessionmodel-note-mapper", + "consentRequired" : false, + "config" : { + "user.session.note" : "client_id", + "introspection.token.claim" : "true", + "userinfo.token.claim" : "true", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "client_id", + "jsonType.label" : "String" + } + }, { + "id" : "881fb6eb-0b39-4d5a-ba0a-90eac73da822", + "name" : "Client Host", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usersessionmodel-note-mapper", + "consentRequired" : false, + "config" : { + "user.session.note" : "clientHost", + "introspection.token.claim" : "true", + "userinfo.token.claim" : "true", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "clientHost", + "jsonType.label" : "String" + } + }, { + "id" : "2745e2ec-be1e-45f0-800e-7bb0f21e52ca", + "name" : "Client IP Address", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usersessionmodel-note-mapper", + "consentRequired" : false, + "config" : { + "user.session.note" : "clientAddress", + "introspection.token.claim" : "true", + "userinfo.token.claim" : "true", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "clientAddress", + "jsonType.label" : "String" + } + } ], + "defaultClientScopes" : [ "web-origins", "service_account", "acr", "profile", "roles", "groups", "email" ], + "optionalClientScopes" : [ "address", "phone", "offline_access", "microprofile-jwt" ], + "authorizationSettings" : { + "allowRemoteResourceManagement" : true, + "policyEnforcementMode" : "ENFORCING", + "resources" : [ { + "name" : "Default Resource", + "type" : "urn:openctioid:resources:default", + "ownerManagedAccess" : false, + "attributes" : { }, + "uris" : [ "/*" ] + } ], + "policies" : [ ], + "scopes" : [ ], + "decisionStrategy" : "UNANIMOUS" + } + }, { + "id" : "a8df3b0e-6b9c-4593-9e25-a181071b4f22", + "clientId" : "openctisaml", + "name" : "openctisaml", + "description" : "", + "rootUrl" : "http://localhost:4000", + "adminUrl" : "", + "baseUrl" : "http://localhost:4000", + "surrogateAuthRequired" : false, + "enabled" : true, + "alwaysDisplayInConsole" : true, + "clientAuthenticatorType" : "client-secret", + "redirectUris" : [ "*" ], + "webOrigins" : [ ], + "notBefore" : 0, + "bearerOnly" : false, + "consentRequired" : false, + "standardFlowEnabled" : true, + "implicitFlowEnabled" : false, + "directAccessGrantsEnabled" : true, + "serviceAccountsEnabled" : false, + "publicClient" : true, + "frontchannelLogout" : true, + "protocol" : "saml", + "attributes" : { + "saml.assertion.signature" : "false", + "saml.force.post.binding" : "true", + "saml.encrypt" : "false", + "post.logout.redirect.uris" : "+", + "saml.server.signature" : "true", + "saml.server.signature.keyinfo.ext" : "false", + "saml.signing.certificate" : "MIICpTCCAY0CBgGNNymQaDANBgkqhkiG9w0BAQsFADAWMRQwEgYDVQQDDAtvcGVuY3Rpc2FtbDAeFw0yNDAxMjMxNjI5NDdaFw0zNDAxMjMxNjMxMjdaMBYxFDASBgNVBAMMC29wZW5jdGlzYW1sMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuLaYBqul7TnRmi3dsi+Azcjrpb1XPfRKPuFmzjQIs5a3wbPHk1J0jt2Mt2nA6h7pDIzAkWPRukbhXEPf37fZiRtllmLohJCIq7WZvvKt4wLho0mZrnwR5q6zUR+wNRSYdozxjdvGl9KbF+Ip6chs1STAktcCrOhDAjMcUyIv2Gv4EtN3qxiORMZ5ndsEf4C6GJoOwU9fr/aE4dslrUCNUjqLKr4ZTjO98NVdlYoW6lG9vT+ALbSQvcWCsovgSKxzOuRUK/cBQUeeLuWpXaMP4dIldyEpt/ETVfW/MEwWHj+HkrkeZN3FqqYO/fvyIkWinOqC3Gd8MM7lM9WEUAbM1wIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQBiEL1CwH0QaAsx4Tk6TRbsIXXNs7UQRidLqqmipjE2tr9J5G0m1uJxFzhwtbZZmF4ucR9PaGp9HXTvgWFWAydLfcsj0JdglGPz+hWJ3j1STDzjk7DCOgoFC+gUMbT3W4+15FVlVIQA9I78rEXH3j1Ffj+LhyLDbsGPKNSA+E5iqcvKoTdh+uQMTHhq395TmbWWGobTNd/h3ybTDKICw78WaH+V7m2vUgF1QoPnhqAVD+lQlc1C8nPnU3H4lQ6VTgX8K1ZAqZq1bOSLR52WfTahcL/H3uZFXLhSB+zcYQ3oYddCpo/Qoqd1vHyVmbMilXlviyFToAbhwcU/avPeW9ag", + "realm_client" : "false", + "saml.artifact.binding.identifier" : "PxT8qlPBBHViShc4aoU4VotNxIY=", + "saml.artifact.binding" : "false", + "saml.signature.algorithm" : "RSA_SHA256", + "saml.useMetadataDescriptorUrl" : "false", + "saml_force_name_id_format" : "false", + "saml.client.signature" : "false", + "saml.authnstatement" : "true", + "display.on.consent.screen" : "false", + "saml_name_id_format" : "username", + "saml.signing.private.key" : "MIIEpAIBAAKCAQEAuLaYBqul7TnRmi3dsi+Azcjrpb1XPfRKPuFmzjQIs5a3wbPHk1J0jt2Mt2nA6h7pDIzAkWPRukbhXEPf37fZiRtllmLohJCIq7WZvvKt4wLho0mZrnwR5q6zUR+wNRSYdozxjdvGl9KbF+Ip6chs1STAktcCrOhDAjMcUyIv2Gv4EtN3qxiORMZ5ndsEf4C6GJoOwU9fr/aE4dslrUCNUjqLKr4ZTjO98NVdlYoW6lG9vT+ALbSQvcWCsovgSKxzOuRUK/cBQUeeLuWpXaMP4dIldyEpt/ETVfW/MEwWHj+HkrkeZN3FqqYO/fvyIkWinOqC3Gd8MM7lM9WEUAbM1wIDAQABAoIBACXvAcuk5p+QVzZVowjb7pTUZWiVONx1VeYR/j3su7i+BCDYnezoax2H7EUih8bM8ElugoGZQVIDCncbTVexdxBMOxGmYGARGrBAzEFFr2ZGijYxgEkwG7EHQbYwTsyn3SPDkDv03ZUCYG2IOdlUGt2u/YlqdJcz38cM3g1IoRDNySq4A9gW65kLxR/JK6Ux7pO0IC8Rpdx38Ok2MeZXfb9uGgFxprrKjuPaXoiLPQxLj/+8OZnP/5WOWIXi55VxxH0k3T9/7kM/eBRuDg1tksj2RKWbhxq8m1nLFN508UJLfAF5TQMNH3S4MJwx5eU8LYxY7yW70vTT5cSBmTtOe4ECgYEA/mApckFvF46asi+dpEOkteT/qQela0rcSguY++b7KL1G8zcjakxPFZViqFbcjKCHddcyiqSMnB1Aip8UaG/lWU21LY7CxJqbFJZhAhTNTgZSGf2WIb2LD6oz6+Pm/Z6V7AInNc0uD9q5wMnZRbS+KbM7lq3R/9Ff7RS7p4DQpJcCgYEAueSNU8HT2Qymuc0cCxvugkckn4aKE8bkTIEZwtL4L6EvSpV6J6XAVYh79rdBL9WjUiBJr5iITpqCm+Ws9urqtZgFi4f5OFlXPgdBGgcC6I3uwL6kX0wZF8SqgyxA4rj6FCNPHhdU+L/H/ReJoYujOkleDll56WMgU4Inbea+4cECgYEA3CY/WIn26m1ZxuLczQBZ+a5R8WkTqfLlChRVd5WlQtHlKLNMrD+UpjpeYxCh2fdIpRz0ufbFVoseg5o/4E8PMCXHqsEGIX8ovj2TgWidcmyX+7RzjYnsY0dLnljkXhU07UfDxZVoywHih05qAyD0/0QGS1buCzeajKXH7qTWbcsCgYAKRVN9rjbrRiSsHWYQQxHRhubCHafhYdrZU0S+G/P0hb5cK5gdOq4+y5S10/g5EV+9uOT5W78kQKs4u97roZ0oPWcJB5FAiMcmOTZinsKNYNIxOhdQ4J5+TrJxHu/S1w+SL0U+z2E1gTsmg7dqApIZNVaKCm2O9JgjpQxSqS4gwQKBgQDX9VuC1tPfjjWvmkLbrVzgUZJypOZFZbFBK/mYKqY7N6uLBqOx+6134S0PTSzI+9q/Xig68s6hrVjqxg0Jl7VLrhnZQNa0UE9pCsINkkhccUHF1JaUGtdai1iYZSAaMmK2ABpazx4rKv6daJNmpNs26TWZPctImhPzvvZ7UJyN8Q==", + "saml.allow.ecp.flow" : "false", + "saml_signature_canonicalization_method" : "http://www.w3.org/2001/10/xml-exc-c14n#", + "saml.onetimeuse.condition" : "false", + "saml.server.signature.keyinfo.xmlSigKeyInfoKeyNameTransformer" : "NONE" + }, + "authenticationFlowBindingOverrides" : { }, + "fullScopeAllowed" : true, + "nodeReRegistrationTimeout" : -1, + "protocolMappers" : [ { + "id" : "7e6b91f5-9234-4ba3-8bee-79b26a35cf91", + "name" : "organization", + "protocol" : "saml", + "protocolMapper" : "saml-organization-membership-mapper", + "consentRequired" : false, + "config" : { } + }, { + "id" : "8ecef93d-4bdb-45e6-be70-725c2e9d19ce", + "name" : "role list", + "protocol" : "saml", + "protocolMapper" : "saml-role-list-mapper", + "consentRequired" : false, + "config" : { + "single" : "false", + "attribute.nameformat" : "Basic", + "attribute.name" : "Role" + } + } ], + "defaultClientScopes" : [ "saml_organization", "role_list", "groups_saml" ], + "optionalClientScopes" : [ ] + }, { + "id" : "44a1c418-f163-4d41-8331-613be37e9d8b", + "clientId" : "security-admin-console", + "name" : "${client_security-admin-console}", + "rootUrl" : "${authAdminUrl}", + "baseUrl" : "/admin/master/console/", + "surrogateAuthRequired" : false, + "enabled" : true, + "alwaysDisplayInConsole" : false, + "clientAuthenticatorType" : "client-secret", + "redirectUris" : [ "/admin/master/console/*" ], + "webOrigins" : [ "+" ], + "notBefore" : 0, + "bearerOnly" : false, + "consentRequired" : false, + "standardFlowEnabled" : true, + "implicitFlowEnabled" : false, + "directAccessGrantsEnabled" : false, + "serviceAccountsEnabled" : false, + "publicClient" : true, + "frontchannelLogout" : false, + "protocol" : "openid-connect", + "attributes" : { + "realm_client" : "false", + "client.use.lightweight.access.token.enabled" : "true", + "post.logout.redirect.uris" : "+", + "pkce.code.challenge.method" : "S256" + }, + "authenticationFlowBindingOverrides" : { }, + "fullScopeAllowed" : true, + "nodeReRegistrationTimeout" : 0, + "protocolMappers" : [ { + "id" : "93dd9f3f-271e-4e96-81f7-e916272c0bce", + "name" : "locale", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "introspection.token.claim" : "true", + "userinfo.token.claim" : "true", + "user.attribute" : "locale", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "locale", + "jsonType.label" : "String" + } + } ], + "defaultClientScopes" : [ "web-origins", "acr", "profile", "roles", "basic", "email" ], + "optionalClientScopes" : [ "address", "phone", "offline_access", "organization", "microprofile-jwt" ] + } ], + "clientScopes" : [ { + "id" : "da296e58-f3d8-40e9-a00d-d0af7c4e173d", + "name" : "role", + "description" : "", + "protocol" : "openid-connect", + "attributes" : { + "include.in.token.scope" : "true", + "display.on.consent.screen" : "true", + "gui.order" : "", + "consent.screen.text" : "", + "include.in.openid.provider.metadata" : "true" + }, + "protocolMappers" : [ { + "id" : "9e75c090-e975-4620-8b63-f6d8f1091e39", + "name" : "realm roles", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-realm-role-mapper", + "consentRequired" : false, + "config" : { + "user.attribute" : "foo", + "introspection.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "realm_access.roles", + "jsonType.label" : "String", + "multivalued" : "true" + } + } ] + }, { + "id" : "51867b26-fcd8-4103-9c60-9c235ce4ab86", + "name" : "basic", + "description" : "OpenID Connect scope for add all basic claims to the token", + "protocol" : "openid-connect", + "attributes" : { + "include.in.token.scope" : "false", + "display.on.consent.screen" : "false" + }, + "protocolMappers" : [ { + "id" : "3b838ed9-bb99-4f16-8839-ae355ee361b1", + "name" : "sub", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-sub-mapper", + "consentRequired" : false, + "config" : { + "introspection.token.claim" : "true", + "access.token.claim" : "true" + } + }, { + "id" : "4e9affc2-8410-45b0-b402-b76b6d8d4d01", + "name" : "auth_time", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usersessionmodel-note-mapper", + "consentRequired" : false, + "config" : { + "user.session.note" : "AUTH_TIME", + "introspection.token.claim" : "true", + "userinfo.token.claim" : "true", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "auth_time", + "jsonType.label" : "long" + } + } ] + }, { + "id" : "bda1114e-c755-4d45-8620-43df51384168", + "name" : "audience", + "description" : "", + "protocol" : "openid-connect", + "attributes" : { + "include.in.token.scope" : "false", + "display.on.consent.screen" : "true", + "gui.order" : "", + "consent.screen.text" : "", + "include.in.openid.provider.metadata" : "true" + }, + "protocolMappers" : [ { + "id" : "41f474a6-acc3-4cc7-ad1d-5bfea9278279", + "name" : "faudience", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-audience-mapper", + "consentRequired" : false, + "config" : { + "id.token.claim" : "false", + "lightweight.claim" : "false", + "access.token.claim" : "true", + "introspection.token.claim" : "true", + "userinfo.token.claim" : "false" + } + } ] + }, { + "id" : "8aee4b94-5c57-4839-b38c-633e3130f9f7", + "name" : "role_list", + "description" : "SAML role list", + "protocol" : "saml", + "attributes" : { + "consent.screen.text" : "${samlRoleListScopeConsentText}", + "display.on.consent.screen" : "true" + }, + "protocolMappers" : [ { + "id" : "c32d1363-3bf2-4417-95aa-3471e5cd1d10", + "name" : "role list", + "protocol" : "saml", + "protocolMapper" : "saml-role-list-mapper", + "consentRequired" : false, + "config" : { + "single" : "false", + "attribute.nameformat" : "Basic", + "attribute.name" : "Role" + } + } ] + }, { + "id" : "7395ed6f-c1e0-445f-89dc-06eda587817a", + "name" : "phone", + "description" : "OpenID Connect built-in scope: phone", + "protocol" : "openid-connect", + "attributes" : { + "include.in.token.scope" : "true", + "consent.screen.text" : "${phoneScopeConsentText}", + "display.on.consent.screen" : "true" + }, + "protocolMappers" : [ { + "id" : "db03d510-4eb5-4613-b0f1-d2e0f01df0a8", + "name" : "phone number verified", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "introspection.token.claim" : "true", + "userinfo.token.claim" : "true", + "user.attribute" : "phoneNumberVerified", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "phone_number_verified", + "jsonType.label" : "boolean" + } + }, { + "id" : "381269ef-39ca-4dfe-9bc8-85449e47c28b", + "name" : "phone number", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "introspection.token.claim" : "true", + "userinfo.token.claim" : "true", + "user.attribute" : "phoneNumber", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "phone_number", + "jsonType.label" : "String" + } + } ] + }, { + "id" : "cbf26ccb-ba1d-42ce-ac71-dfaafff27512", + "name" : "groups", + "description" : "", + "protocol" : "openid-connect", + "attributes" : { + "include.in.token.scope" : "true", + "display.on.consent.screen" : "true", + "gui.order" : "", + "consent.screen.text" : "", + "include.in.openid.provider.metadata" : "true" + }, + "protocolMappers" : [ { + "id" : "5052818b-7daf-4e4b-8cfe-f76c0d330b22", + "name" : "groups", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-realm-role-mapper", + "consentRequired" : false, + "config" : { + "introspection.token.claim" : "true", + "multivalued" : "true", + "userinfo.token.claim" : "true", + "user.attribute" : "foo", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "groups", + "jsonType.label" : "String" + } + }, { + "id" : "9d78d692-acd4-4f31-af27-de80615b7ee8", + "name" : "member", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-group-membership-mapper", + "consentRequired" : false, + "config" : { + "full.path" : "true", + "introspection.token.claim" : "true", + "userinfo.token.claim" : "true", + "id.token.claim" : "true", + "lightweight.claim" : "false", + "access.token.claim" : "true", + "claim.name" : "groups" + } + } ] + }, { + "id" : "030ba4c2-6ce0-461d-a936-6964517db933", + "name" : "organization", + "description" : "Additional claims about the organization a subject belongs to", + "protocol" : "openid-connect", + "attributes" : { + "include.in.token.scope" : "true", + "consent.screen.text" : "${organizationScopeConsentText}", + "display.on.consent.screen" : "true" + }, + "protocolMappers" : [ { + "id" : "910d5b09-d06e-4df1-9db0-eaa9b93ff6ad", + "name" : "organization", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-organization-membership-mapper", + "consentRequired" : false, + "config" : { + "introspection.token.claim" : "true", + "multivalued" : "true", + "userinfo.token.claim" : "true", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "organization", + "jsonType.label" : "String" + } + } ] + }, { + "id" : "44bb6371-ccf9-4816-8a0f-030ed40a61e0", + "name" : "profile", + "description" : "OpenID Connect built-in scope: profile", + "protocol" : "openid-connect", + "attributes" : { + "include.in.token.scope" : "true", + "consent.screen.text" : "${profileScopeConsentText}", + "display.on.consent.screen" : "true" + }, + "protocolMappers" : [ { + "id" : "4dfc3c79-878e-43e1-937d-10b0af38cb83", + "name" : "profile", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "introspection.token.claim" : "true", + "userinfo.token.claim" : "true", + "user.attribute" : "profile", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "profile", + "jsonType.label" : "String" + } + }, { + "id" : "a6e73ad4-cd5d-4908-9f86-955d23abcccc", + "name" : "locale", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "introspection.token.claim" : "true", + "userinfo.token.claim" : "true", + "user.attribute" : "locale", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "locale", + "jsonType.label" : "String" + } + }, { + "id" : "fd2bc4ba-ec12-4b85-80e1-48ad0b8bee02", + "name" : "birthdate", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "introspection.token.claim" : "true", + "userinfo.token.claim" : "true", + "user.attribute" : "birthdate", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "birthdate", + "jsonType.label" : "String" + } + }, { + "id" : "92b05a6a-be4c-4f05-97fb-1fea2f64d55b", + "name" : "zoneinfo", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "introspection.token.claim" : "true", + "userinfo.token.claim" : "true", + "user.attribute" : "zoneinfo", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "zoneinfo", + "jsonType.label" : "String" + } + }, { + "id" : "d50f198d-e411-4aa9-8eae-212aa58eaae3", + "name" : "middle name", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "introspection.token.claim" : "true", + "userinfo.token.claim" : "true", + "user.attribute" : "middleName", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "middle_name", + "jsonType.label" : "String" + } + }, { + "id" : "d4fc6db4-28d6-4f4e-8c04-d88a9caf54cf", + "name" : "website", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "introspection.token.claim" : "true", + "userinfo.token.claim" : "true", + "user.attribute" : "website", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "website", + "jsonType.label" : "String" + } + }, { + "id" : "3d3dcaca-b2e4-4ebc-b818-4ec25416da7d", + "name" : "family name", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "introspection.token.claim" : "true", + "userinfo.token.claim" : "true", + "user.attribute" : "lastName", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "family_name", + "jsonType.label" : "String" + } + }, { + "id" : "352a7146-f68b-4e27-a6e3-4456dee1407c", + "name" : "full name", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-full-name-mapper", + "consentRequired" : false, + "config" : { + "id.token.claim" : "true", + "introspection.token.claim" : "true", + "access.token.claim" : "true", + "userinfo.token.claim" : "true" + } + }, { + "id" : "ceb78da2-bc33-4433-909d-2e5085aab469", + "name" : "updated at", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "introspection.token.claim" : "true", + "userinfo.token.claim" : "true", + "user.attribute" : "updatedAt", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "updated_at", + "jsonType.label" : "long" + } + }, { + "id" : "e64a8a8f-c54f-46bd-96cd-22667463b2cc", + "name" : "given name", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "introspection.token.claim" : "true", + "userinfo.token.claim" : "true", + "user.attribute" : "firstName", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "given_name", + "jsonType.label" : "String" + } + }, { + "id" : "487171f9-578f-4632-8345-dd1fab38aa21", + "name" : "username", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "introspection.token.claim" : "true", + "userinfo.token.claim" : "true", + "user.attribute" : "username", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "preferred_username", + "jsonType.label" : "String" + } + }, { + "id" : "7deacf75-8309-458f-b7fe-5fc2c3fe4c40", + "name" : "nickname", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "introspection.token.claim" : "true", + "userinfo.token.claim" : "true", + "user.attribute" : "nickname", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "nickname", + "jsonType.label" : "String" + } + }, { + "id" : "05e413a2-3454-4e6d-a5e2-53c46c71a729", + "name" : "picture", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "introspection.token.claim" : "true", + "userinfo.token.claim" : "true", + "user.attribute" : "picture", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "picture", + "jsonType.label" : "String" + } + }, { + "id" : "4fac2672-e02e-450e-a870-a332b9956e55", + "name" : "gender", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "introspection.token.claim" : "true", + "userinfo.token.claim" : "true", + "user.attribute" : "gender", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "gender", + "jsonType.label" : "String" + } + } ] + }, { + "id" : "9a102d32-9b89-4909-bafe-537d9438dca2", + "name" : "roles", + "description" : "OpenID Connect scope for add user roles to the access token", + "protocol" : "openid-connect", + "attributes" : { + "include.in.token.scope" : "false", + "consent.screen.text" : "${rolesScopeConsentText}", + "display.on.consent.screen" : "true" + }, + "protocolMappers" : [ { + "id" : "e623e917-3590-4874-9605-bc61895879fd", + "name" : "client roles", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-client-role-mapper", + "consentRequired" : false, + "config" : { + "user.attribute" : "foo", + "introspection.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "resource_access.${client_id}.roles", + "jsonType.label" : "String", + "multivalued" : "true" + } + }, { + "id" : "f9c02866-2a03-4c9f-aa6b-efcfc0ba699c", + "name" : "audience resolve", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-audience-resolve-mapper", + "consentRequired" : false, + "config" : { + "introspection.token.claim" : "true", + "access.token.claim" : "true" + } + }, { + "id" : "91129706-c63c-4c07-849a-6e073c5d812b", + "name" : "realm roles", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-realm-role-mapper", + "consentRequired" : false, + "config" : { + "user.attribute" : "foo", + "introspection.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "realm_access.roles", + "jsonType.label" : "String", + "multivalued" : "true" + } + } ] + }, { + "id" : "f44c3ab1-62d8-4fee-8a5d-8b601f3b6661", + "name" : "web-origins", + "description" : "OpenID Connect scope for add allowed web origins to the access token", + "protocol" : "openid-connect", + "attributes" : { + "include.in.token.scope" : "false", + "consent.screen.text" : "", + "display.on.consent.screen" : "false" + }, + "protocolMappers" : [ { + "id" : "8a2069f0-d6b6-4535-b8bf-0587c4625e35", + "name" : "allowed web origins", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-allowed-origins-mapper", + "consentRequired" : false, + "config" : { + "introspection.token.claim" : "true", + "access.token.claim" : "true" + } + } ] + }, { + "id" : "be76de0b-f5dc-47ce-97cb-23c974ad57d2", + "name" : "offline_access", + "description" : "OpenID Connect built-in scope: offline_access", + "protocol" : "openid-connect", + "attributes" : { + "consent.screen.text" : "${offlineAccessScopeConsentText}", + "display.on.consent.screen" : "true" + } + }, { + "id" : "7088158f-3e57-4caa-bb30-b3361c3894e7", + "name" : "address", + "description" : "OpenID Connect built-in scope: address", + "protocol" : "openid-connect", + "attributes" : { + "include.in.token.scope" : "true", + "consent.screen.text" : "${addressScopeConsentText}", + "display.on.consent.screen" : "true" + }, + "protocolMappers" : [ { + "id" : "4799b07e-ee99-4d5d-bbf0-b1532bb79cd6", + "name" : "address", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-address-mapper", + "consentRequired" : false, + "config" : { + "user.attribute.formatted" : "formatted", + "user.attribute.country" : "country", + "introspection.token.claim" : "true", + "user.attribute.postal_code" : "postal_code", + "userinfo.token.claim" : "true", + "user.attribute.street" : "street", + "id.token.claim" : "true", + "user.attribute.region" : "region", + "access.token.claim" : "true", + "user.attribute.locality" : "locality" + } + } ] + }, { + "id" : "9518f5d0-32d9-4fd0-93f4-b545b2033fc1", + "name" : "groups_saml", + "description" : "", + "protocol" : "saml", + "attributes" : { + "include.in.token.scope" : "true", + "display.on.consent.screen" : "true", + "gui.order" : "", + "consent.screen.text" : "", + "include.in.openid.provider.metadata" : "true" + }, + "protocolMappers" : [ { + "id" : "6ae57640-0146-4573-80cd-db85fee07561", + "name" : "role list", + "protocol" : "saml", + "protocolMapper" : "saml-role-list-mapper", + "consentRequired" : false, + "config" : { + "single" : "false", + "attribute.nameformat" : "Basic", + "attribute.name" : "Role" + } + }, { + "id" : "8db7e08f-5e72-4c9b-8599-64317eaa2106", + "name" : "groups", + "protocol" : "saml", + "protocolMapper" : "saml-group-membership-mapper", + "consentRequired" : false, + "config" : { + "single" : "true", + "attribute.nameformat" : "Basic", + "full.path" : "true", + "attribute.name" : "member" + } + } ] + }, { + "id" : "629ff521-47a2-430f-8028-7c2131cefb19", + "name" : "email", + "description" : "OpenID Connect built-in scope: email", + "protocol" : "openid-connect", + "attributes" : { + "include.in.token.scope" : "true", + "consent.screen.text" : "${emailScopeConsentText}", + "display.on.consent.screen" : "true" + }, + "protocolMappers" : [ { + "id" : "494f4625-207f-4443-9588-0bb18c78994e", + "name" : "email", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "introspection.token.claim" : "true", + "userinfo.token.claim" : "true", + "user.attribute" : "email", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "email", + "jsonType.label" : "String" + } + }, { + "id" : "e9cf797e-3cbb-428d-a86b-ab66704ff97a", + "name" : "email verified", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-property-mapper", + "consentRequired" : false, + "config" : { + "introspection.token.claim" : "true", + "userinfo.token.claim" : "true", + "user.attribute" : "emailVerified", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "email_verified", + "jsonType.label" : "boolean" + } + } ] + }, { + "id" : "f88239ff-eb0b-4c9c-bd5a-6f8bc0e59bdf", + "name" : "saml_organization", + "description" : "Organization Membership", + "protocol" : "saml", + "attributes" : { + "display.on.consent.screen" : "false" + }, + "protocolMappers" : [ { + "id" : "1581661c-70ac-42cd-b0e5-69edb9787cce", + "name" : "organization", + "protocol" : "saml", + "protocolMapper" : "saml-organization-membership-mapper", + "consentRequired" : false, + "config" : { } + } ] + }, { + "id" : "4b6c3d8d-5bcf-4da1-860d-31af9cfafb5e", + "name" : "service_account", + "description" : "Specific scope for a client enabled for service accounts", + "protocol" : "openid-connect", + "attributes" : { + "include.in.token.scope" : "false", + "display.on.consent.screen" : "false" + }, + "protocolMappers" : [ { + "id" : "c2f71464-e8e1-4228-84aa-5524855ab7ed", + "name" : "Client Host", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usersessionmodel-note-mapper", + "consentRequired" : false, + "config" : { + "user.session.note" : "clientHost", + "introspection.token.claim" : "true", + "userinfo.token.claim" : "true", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "clientHost", + "jsonType.label" : "String" + } + }, { + "id" : "f8dfd71c-22c8-4a4e-9772-76c09d3da621", + "name" : "Client IP Address", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usersessionmodel-note-mapper", + "consentRequired" : false, + "config" : { + "user.session.note" : "clientAddress", + "introspection.token.claim" : "true", + "userinfo.token.claim" : "true", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "clientAddress", + "jsonType.label" : "String" + } + }, { + "id" : "3de52bca-c561-449b-ae03-4a87ef206c76", + "name" : "Client ID", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usersessionmodel-note-mapper", + "consentRequired" : false, + "config" : { + "user.session.note" : "client_id", + "introspection.token.claim" : "true", + "userinfo.token.claim" : "true", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "client_id", + "jsonType.label" : "String" + } + } ] + }, { + "id" : "e7c5baf7-646b-4b8a-8b40-252f4e4a65a2", + "name" : "acr", + "description" : "OpenID Connect scope for add acr (authentication context class reference) to the token", + "protocol" : "openid-connect", + "attributes" : { + "include.in.token.scope" : "false", + "display.on.consent.screen" : "false" + }, + "protocolMappers" : [ { + "id" : "f2ad8f28-bb7d-4817-b64c-d7207b379a1f", + "name" : "acr loa level", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-acr-mapper", + "consentRequired" : false, + "config" : { + "id.token.claim" : "true", + "introspection.token.claim" : "true", + "access.token.claim" : "true", + "userinfo.token.claim" : "true" + } + } ] + }, { + "id" : "fd56168f-331f-48d0-924a-e8f795106e14", + "name" : "microprofile-jwt", + "description" : "Microprofile - JWT built-in scope", + "protocol" : "openid-connect", + "attributes" : { + "include.in.token.scope" : "true", + "display.on.consent.screen" : "false" + }, + "protocolMappers" : [ { + "id" : "1edc477e-b7a4-4605-8337-cc382522563d", + "name" : "upn", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "introspection.token.claim" : "true", + "userinfo.token.claim" : "true", + "user.attribute" : "username", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "upn", + "jsonType.label" : "String" + } + }, { + "id" : "75e2de97-3c32-4e6b-a2bb-60481d510f8b", + "name" : "groups", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-realm-role-mapper", + "consentRequired" : false, + "config" : { + "introspection.token.claim" : "true", + "multivalued" : "true", + "userinfo.token.claim" : "true", + "user.attribute" : "foo", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "groups", + "jsonType.label" : "String" + } + } ] + } ], + "defaultDefaultClientScopes" : [ "role_list", "saml_organization", "profile", "email", "roles", "web-origins", "acr", "basic", "groups", "audience", "organization", "role", "groups_saml" ], + "defaultOptionalClientScopes" : [ "offline_access", "address", "phone", "microprofile-jwt" ], + "browserSecurityHeaders" : { + "contentSecurityPolicyReportOnly" : "", + "xContentTypeOptions" : "nosniff", + "referrerPolicy" : "no-referrer", + "xRobotsTag" : "none", + "xFrameOptions" : "SAMEORIGIN", + "contentSecurityPolicy" : "frame-src 'self'; frame-ancestors 'self'; object-src 'none';", + "strictTransportSecurity" : "max-age=31536000; includeSubDomains" + }, + "smtpServer" : { }, + "eventsEnabled" : false, + "eventsListeners" : [ "jboss-logging" ], + "enabledEventTypes" : [ ], + "adminEventsEnabled" : false, + "adminEventsDetailsEnabled" : false, + "identityProviders" : [ ], + "identityProviderMappers" : [ ], + "components" : { + "org.keycloak.services.clientregistration.policy.ClientRegistrationPolicy" : [ { + "id" : "d52ba708-adbc-4f2a-9d7f-0212f2554797", + "name" : "Allowed Client Scopes", + "providerId" : "allowed-client-templates", + "subType" : "anonymous", + "subComponents" : { }, + "config" : { + "allow-default-scopes" : [ "true" ] + } + }, { + "id" : "f6e3b60d-0cbf-4637-adf0-c1fa49fa2499", + "name" : "Allowed Protocol Mapper Types", + "providerId" : "allowed-protocol-mappers", + "subType" : "anonymous", + "subComponents" : { }, + "config" : { + "allowed-protocol-mapper-types" : [ "saml-role-list-mapper", "saml-user-property-mapper", "oidc-usermodel-attribute-mapper", "oidc-address-mapper", "saml-user-attribute-mapper", "oidc-usermodel-property-mapper", "oidc-sha256-pairwise-sub-mapper", "oidc-full-name-mapper" ] + } + }, { + "id" : "220517d7-46f7-413e-b001-955d5a78b3da", + "name" : "Allowed Protocol Mapper Types", + "providerId" : "allowed-protocol-mappers", + "subType" : "authenticated", + "subComponents" : { }, + "config" : { + "allowed-protocol-mapper-types" : [ "oidc-address-mapper", "saml-user-attribute-mapper", "oidc-full-name-mapper", "saml-role-list-mapper", "oidc-sha256-pairwise-sub-mapper", "saml-user-property-mapper", "oidc-usermodel-property-mapper", "oidc-usermodel-attribute-mapper" ] + } + }, { + "id" : "6f7a251c-54d8-4acc-9dea-d20012b9cb09", + "name" : "Trusted Hosts", + "providerId" : "trusted-hosts", + "subType" : "anonymous", + "subComponents" : { }, + "config" : { + "host-sending-registration-request-must-match" : [ "true" ], + "client-uris-must-match" : [ "true" ] + } + }, { + "id" : "fe27f456-4380-4d18-9a6f-772a29bddcbf", + "name" : "Allowed Client Scopes", + "providerId" : "allowed-client-templates", + "subType" : "authenticated", + "subComponents" : { }, + "config" : { + "allow-default-scopes" : [ "true" ] + } + }, { + "id" : "f441ac3b-a8c2-4cad-91d8-994c3bb2fe2b", + "name" : "Consent Required", + "providerId" : "consent-required", + "subType" : "anonymous", + "subComponents" : { }, + "config" : { } + }, { + "id" : "d6ad7142-6a18-465e-a3c3-b2f7230fcab3", + "name" : "Full Scope Disabled", + "providerId" : "scope", + "subType" : "anonymous", + "subComponents" : { }, + "config" : { } + }, { + "id" : "04814e09-5473-4ef5-b614-5deffff80287", + "name" : "Max Clients Limit", + "providerId" : "max-clients", + "subType" : "anonymous", + "subComponents" : { }, + "config" : { + "max-clients" : [ "200" ] + } + } ], + "org.keycloak.userprofile.UserProfileProvider" : [ { + "id" : "2e83948f-a9f2-422e-b342-7645a5736770", + "providerId" : "declarative-user-profile", + "subComponents" : { }, + "config" : { + "kc.user.profile.config" : [ "{\"attributes\":[{\"name\":\"username\",\"displayName\":\"${username}\",\"validations\":{\"length\":{\"min\":3,\"max\":255},\"username-prohibited-characters\":{},\"up-username-not-idn-homograph\":{}},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"multivalued\":false},{\"name\":\"email\",\"displayName\":\"${email}\",\"validations\":{\"email\":{},\"length\":{\"max\":255}},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"multivalued\":false},{\"name\":\"firstName\",\"displayName\":\"${firstName}\",\"validations\":{\"length\":{\"max\":255},\"person-name-prohibited-characters\":{}},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"multivalued\":false},{\"name\":\"lastName\",\"displayName\":\"${lastName}\",\"validations\":{\"length\":{\"max\":255},\"person-name-prohibited-characters\":{}},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"multivalued\":false},{\"name\":\"groups\",\"displayName\":\"${groups}\",\"validations\":{},\"annotations\":{},\"permissions\":{\"view\":[],\"edit\":[\"admin\"]},\"multivalued\":false}],\"groups\":[{\"name\":\"user-metadata\",\"displayHeader\":\"User metadata\",\"displayDescription\":\"Attributes, which refer to user metadata\"}]}" ] + } + } ], + "org.keycloak.keys.KeyProvider" : [ { + "id" : "62078160-d785-4eb9-a8e8-6ed7babe5ce8", + "name" : "rsa-enc-generated", + "providerId" : "rsa-enc-generated", + "subComponents" : { }, + "config" : { + "privateKey" : [ "MIIEpAIBAAKCAQEAoS2BNIPycClPSHU6odlskAfetfrcozoSXFBx/h7s5A1o2klBi7jyWkfuC/R8FMVBFZ07sz12wXXBseBGEsnwOMSt/EJ4z5C8B6TRqF+Y0GuRcZI4xjNvgSj5oQiSPtuGZf/7bgYg1l8vPU0TjPCpHgQ8hepJofB96t3P7NCrX2UzW7mhwJDf1UP3u1BCf+eT0vYvQN8pSiSY7SyGMoHeh3U7LospDh0ONSzGA3dqt6fpWMTmchNVhRwP23rLZzFEcbrPMug22VC8wIxHkoD2HHCO8FYQDDC4NUMZiNAgf11ScbhbhQOofdMacyjL93+IGNrYVsbyMAx6zoAtHcaEvQIDAQABAoIBABjEnGFMkXno7zUMRrlOZx9vBiti5Hr01pN3DT1m1TJqWR2KOldVUXDMMumFZXfvpAPFSMJPhKtQBDboZvxGKfMfTjlAob4L2Mk367v7DMmibhRMywqyJsVrXYkAfzDHnUyZXLLYoD0xHVpHOOTkqqhhMEfH7A7FnSP1eBlFK8CZ1tdZ3I3qcKOtPDY0J9KuSh0K6Keq2Bdh2bpDzywSwCpF+4EtF9z/oAsGmzX6l3g5p0OzEIymknKP8RL7SLTONfFZUn8XmUKgEruaYTIB5HlnKuN6/RK14mzRoDb6ZFWi4nuwew5YeZHFU9sku84tznZMK5W0dVIRghTLCmTsW6UCgYEA4Zrz3+ixI2XmNK+0c/AVps+TetUvSvGHsg0MrdPM5Fyq42pK4I8zc0rP3BLZPpyorhanJNLRfFxVHf0KyMkyAOJK5L5Dq1jsi93MTs7E5RubSlr6xCYMoP85D75+wQdpioTeZLEQzfLxyz0cHKeoKzV0GGFZpOgmF7P/J/b4SZ8CgYEAtuR4E9ov8is+wL8WJn35ADjRHAQW+euqkyQBnDr4FCUxwU3u9xMfD7OeQus1YbHIUBfi09rp6264xRto7RWmH8FhOi+m2WGZkBcPdrVe0Lw1oDrQvvsWdl0ol3afEoxiTU/wCOfWsOYqC025vgGvpCl3h5tWzhF0qAibBP//DCMCgYEAhEEFJEbRyRGMYWh7XdfpqW9YYpKk9ccfqEY0H5bhLyIP399I49mu7LB3p+i4yBaoX93RwCmCOugZ0cmsT8Z6dMAz7WKIYuNvUBMHGU/nZvPHFlC9Xs17a8oSlmMzBU+mFFkN0nNmiYUZL+60EyxJzoK4ey5ekeixpAWV18TVCrsCgYBHBoTnWMVj641sNwk4G5XWkzoKAkCWAAJ3L/V8IZ5z3FntMwHJa+CVLXEZldReQzeCezQ4h/xt1MrmqRVfdRfVnzjN6vGF3BRR09LKi/btYxoERrMIZ+Q6RUVdRNDT2DbYxWF3Y+mJO2k6iI3Ij0kRnJTx0c6tKjQ+iVECaRQ4NwKBgQChV8s+kXm5wCNWpyFsV75rJ1x7QO4PeS+SNnf5N+ks8P0BejsUmv+7rZX/r3dHQ5cALHjcEJcVJ3wZAgeqHK5vKrbsLjf0Lp1gy1oei5MoNc804EsyV0V+ol7Iajva/plUS5iePRd87vsl7ar0KnZZnh+xVL4ijpa8JMEaPropoA==" ], + "certificate" : [ "MIICmzCCAYMCBgGbiFigTTANBgkqhkiG9w0BAQsFADARMQ8wDQYDVQQDDAZtYXN0ZXIwHhcNMjYwMTA0MDkyOTI4WhcNMzYwMTA0MDkzMTA4WjARMQ8wDQYDVQQDDAZtYXN0ZXIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQChLYE0g/JwKU9IdTqh2WyQB961+tyjOhJcUHH+HuzkDWjaSUGLuPJaR+4L9HwUxUEVnTuzPXbBdcGx4EYSyfA4xK38QnjPkLwHpNGoX5jQa5FxkjjGM2+BKPmhCJI+24Zl//tuBiDWXy89TROM8KkeBDyF6kmh8H3q3c/s0KtfZTNbuaHAkN/VQ/e7UEJ/55PS9i9A3ylKJJjtLIYygd6HdTsuiykOHQ41LMYDd2q3p+lYxOZyE1WFHA/bestnMURxus8y6DbZULzAjEeSgPYccI7wVhAMMLg1QxmI0CB/XVJxuFuFA6h90xpzKMv3f4gY2thWxvIwDHrOgC0dxoS9AgMBAAEwDQYJKoZIhvcNAQELBQADggEBAGmrQMXqDYHaXFkU8Nz9jUlwIqlc1Pg6nsIBAjOfSvQc+m64QQerwCeFMox7Gwj6t1JRl/dfpkxn+r5HP6SN4jzZXAua1wBZg3f6tdwuOhOUHRt93i0re+8cf4yoQfZdPkvXuSewjpZj7NfDqlQ2JmsvB5SFwd9Hpi6nTZ7vOGaqurlET40KP2eZwdDDxb1YaNO3vm0mjdhAWx+1i0UEKxtcKN/mnEF9qLMawKXunmLzrbLzyo/ktI+f1eZ7l0rQMvPuyfwk3PEU0nPQWyAnCKYh6dsLlzVCFNYiE9Fx69gTFySsY0TZbeGImp5HlFm7feAELwoyUqyxGZ+CCUQK7vo=" ], + "priority" : [ "100" ], + "algorithm" : [ "RSA-OAEP" ] + } + }, { + "id" : "b8bf7983-7205-4f19-8332-0e9119f68328", + "name" : "hmac-generated-hs512", + "providerId" : "hmac-generated", + "subComponents" : { }, + "config" : { + "kid" : [ "06da48aa-c992-4a61-bc1a-c2ffebce653b" ], + "secret" : [ "df1honNeAbVOFk0DlJxgfraQQUJq_F1trKKPfcgzbMjmtFVHpIA9KZg14JBAXnkMZFxWPYdnX4_etlrk9nDnkZSNWJBzzihTlcWXnCDyFU5xGtgQJg5AjFahHx7jJJZt-B07g3kWQAmci87BXhfKLU02X2vF8d5cJWW-cwXD57U" ], + "priority" : [ "100" ], + "algorithm" : [ "HS512" ] + } + }, { + "id" : "674de2a5-7694-4c31-abf1-b2f0e8eed268", + "name" : "aes-generated", + "providerId" : "aes-generated", + "subComponents" : { }, + "config" : { + "kid" : [ "736676a9-b1c1-4e28-bde9-e3281935d5fe" ], + "secret" : [ "ylLCqe6aQzRwrZrWgLmHQw" ], + "priority" : [ "100" ] + } + }, { + "id" : "3ab3cd9a-47b7-40ec-9f8b-818abf85388d", + "name" : "rsa-generated", + "providerId" : "rsa-generated", + "subComponents" : { }, + "config" : { + "privateKey" : [ "MIIEpAIBAAKCAQEA5bgJe69O3F8BKQoJ14jsZYIcp23//zOIeHd+WMV/YCDvdfJB5A1DdEpZvo0+nCU/SfxWXRfetMXty9G7h72GrH1sFP2BNnGYYga7L/gIN6evedCyqt0/dRrbxcrywDW0m+ZQKGNvRwUw2UyJnvt681TxaZfi0DM9Vmo8ecuSo7c3XSA4F1+1Vhs1InLlTNTtYRmDaggGZLDoFG0T/Vln8RnLNNbhTDLW6/EHSh9UvJLyXTF6h0vb6s98uxHjPju651N3rXu7W7S42+Bo3Y20CvluiXB/7zYOVMCRLEf2UDpyPKE/CvDzDCAmovkunE7OoOS/PlBTk5TP2DCuSxWyOQIDAQABAoIBACM1MDrlNQyAgSCFmdmPck/if7rHmLdH8jAofFRlvK6CjcEBhsjJoZ+Gr7eNVV3ANbGEu8Xb3TXOqjIVbI0S5ru0I2caLX4nLehESNdCyBujlzjv3Bpk+49atPldMBR2hr5oL7vo9YqoiIKdgPA9cewuhiudbFVlrQ58DNXL9iciKMOu8RoiigBeGNI+NvnBtvUEGUeFFh8cbASXRS/KLju9MdfyDwJke5iMdJ+mLIIRY4mj26rtkyU509++zB1o1kbRDJLIY7i1GJctXWj0JXjpQJHibiraiYmCmLBLItYfTbwDeV+Hk8IiQL3Kwf9uNz/83BuMCcPBNyGUE3M/VCsCgYEA8tuiGBRt1XPtsMbxSoj10fjP4nyOW+hv4ewGFShAZo1hXZ+aTlVwJ3U+8SRM/NhyBtdZCZxyxqEKZ64fsab0eEGesy8p1PyXY21zHWCSz26MGo8245JVgk2xzWHNnePqVApHNvBWL5ji0aA42bxG0QCtpcWq1Xe46bJEqL5yvdcCgYEA8iZiuD2s0K7iL8WETPFL52Ik2A1WP63F+LacMQ1c2aHsGZngru/KRJj8jc35fJkwG3mgl5YCe/dIgFJd/ida8ajleagHl/jrA/6oGlrNFS/fvOByAuNaFSGmRwlybbKjvjtKyycU3MQE3qZgDG/8mzrNIventvmELVZhIb31bm8CgYEAwyAUrKQ9WzM51r8BP5GrcHkpDiBlgRLQUgUBHh6pH7yYblC9+hJLemiJmdZAxSZi93wu3boFvfHGGmecr3AlHDoc+Hr8cVRBjnFx/DiLvH5lszE+OLpqWbdzFEFwo1tr+voHHe0cUkHUe/jvtIHBpEuXOYoIKFt5Bstkvu/F4SECgYEAnc+kcLxJHyuKX2XTRi3PsPlGV8PBXPjMV73y5wj1ZSRg8YJWasv9v10Q4v3ExY40SwOmRIRQFChhiLelaBiP7YSMIQ//+uwPhef1+E8K7u7nWnnP88/linYGnq8qcxGEI1sS8HkE4KejINO/Lvvg3e2heVN7awUMPlvYK5xYEmMCgYBsAh1yi0tEIfBa98oJIxfC4OZgUZQUaeOb3iQgiFWkcVdGQDZz856spGPXDD8ek9/0WMDKMdEYfjko6yqsijDP/09i8lqQ9e0qz+8MmxizBhAZ5XxYgO7eJ7V/XkTc8hNc54PJL555N8I0dKit3Qqrmxkby78vOaJVZ7TZjGFDOA==" ], + "certificate" : [ "MIICmzCCAYMCBgGbiFigqTANBgkqhkiG9w0BAQsFADARMQ8wDQYDVQQDDAZtYXN0ZXIwHhcNMjYwMTA0MDkyOTI4WhcNMzYwMTA0MDkzMTA4WjARMQ8wDQYDVQQDDAZtYXN0ZXIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDluAl7r07cXwEpCgnXiOxlghynbf//M4h4d35YxX9gIO918kHkDUN0Slm+jT6cJT9J/FZdF960xe3L0buHvYasfWwU/YE2cZhiBrsv+Ag3p6950LKq3T91GtvFyvLANbSb5lAoY29HBTDZTIme+3rzVPFpl+LQMz1Wajx5y5KjtzddIDgXX7VWGzUicuVM1O1hGYNqCAZksOgUbRP9WWfxGcs01uFMMtbr8QdKH1S8kvJdMXqHS9vqz3y7EeM+O7rnU3ete7tbtLjb4GjdjbQK+W6JcH/vNg5UwJEsR/ZQOnI8oT8K8PMMICai+S6cTs6g5L8+UFOTlM/YMK5LFbI5AgMBAAEwDQYJKoZIhvcNAQELBQADggEBADf9Aj0607hPwHkGh9IdGgZm4Xs2E5v9Bzh7qtfxInJAErhZmChc/6AwNwkCIsGXPb6LRK+BGu2EIY8G3WvoBWQK2s16OSSH3QENA+GWIVLqHibEY+oLfoFmtyTGo/5TqXHZ/XlLFxyn+6BUpdZyr4p406uhKx2PCYHgDziLsDo1c/56AnjUbehEI30gCGCI3F0NYfxYzsnCJGK8ZzOzU2h9/Bfemt0t9gQUcwVJn6QHvKnT5mNO1qzJPRBwpz1GiB9P5+UT0SAnt+oxIAH5Gtwr0qI5sb9ZIAaS2FJut/cqiTsKzSjpRBE/AwsyU915Eyygy3HUjVxTxqH0BZga178=" ], + "priority" : [ "100" ] + } + } ] + }, + "internationalizationEnabled" : false, + "authenticationFlows" : [ { + "id" : "729de913-8bd6-47a4-ae43-0d0c6ee8c628", + "alias" : "Account verification options", + "description" : "Method with which to verity the existing account", + "providerId" : "basic-flow", + "topLevel" : false, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "idp-email-verification", + "authenticatorFlow" : false, + "requirement" : "ALTERNATIVE", + "priority" : 10, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticatorFlow" : true, + "requirement" : "ALTERNATIVE", + "priority" : 20, + "autheticatorFlow" : true, + "flowAlias" : "Verify Existing Account by Re-authentication", + "userSetupAllowed" : false + } ] + }, { + "id" : "c9430684-3df1-478a-b46c-fcd49d99ca43", + "alias" : "Browser - Conditional OTP", + "description" : "Flow to determine if the OTP is required for the authentication", + "providerId" : "basic-flow", + "topLevel" : false, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "conditional-user-configured", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 10, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticator" : "auth-otp-form", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 20, + "autheticatorFlow" : false, + "userSetupAllowed" : false + } ] + }, { + "id" : "b2cc065d-7f84-4d67-8e8c-346e26f4283b", + "alias" : "Direct Grant - Conditional OTP", + "description" : "Flow to determine if the OTP is required for the authentication", + "providerId" : "basic-flow", + "topLevel" : false, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "conditional-user-configured", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 10, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticator" : "direct-grant-validate-otp", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 20, + "autheticatorFlow" : false, + "userSetupAllowed" : false + } ] + }, { + "id" : "f2f73298-77ab-406c-9bfe-1107c4b0e696", + "alias" : "First broker login - Conditional OTP", + "description" : "Flow to determine if the OTP is required for the authentication", + "providerId" : "basic-flow", + "topLevel" : false, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "conditional-user-configured", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 10, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticator" : "auth-otp-form", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 20, + "autheticatorFlow" : false, + "userSetupAllowed" : false + } ] + }, { + "id" : "8bbcd9b4-3272-42d5-8dd9-51591faba89f", + "alias" : "Handle Existing Account", + "description" : "Handle what to do if there is existing account with same email/username like authenticated identity provider", + "providerId" : "basic-flow", + "topLevel" : false, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "idp-confirm-link", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 10, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticatorFlow" : true, + "requirement" : "REQUIRED", + "priority" : 20, + "autheticatorFlow" : true, + "flowAlias" : "Account verification options", + "userSetupAllowed" : false + } ] + }, { + "id" : "62a91d8b-6617-43a6-86c0-9898e1f02a70", + "alias" : "Reset - Conditional OTP", + "description" : "Flow to determine if the OTP should be reset or not. Set to REQUIRED to force.", + "providerId" : "basic-flow", + "topLevel" : false, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "conditional-user-configured", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 10, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticator" : "reset-otp", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 20, + "autheticatorFlow" : false, + "userSetupAllowed" : false + } ] + }, { + "id" : "4c6aef61-2d74-49c5-bd8a-7b2dff39bb19", + "alias" : "User creation or linking", + "description" : "Flow for the existing/non-existing user alternatives", + "providerId" : "basic-flow", + "topLevel" : false, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticatorConfig" : "create unique user config", + "authenticator" : "idp-create-user-if-unique", + "authenticatorFlow" : false, + "requirement" : "ALTERNATIVE", + "priority" : 10, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticatorFlow" : true, + "requirement" : "ALTERNATIVE", + "priority" : 20, + "autheticatorFlow" : true, + "flowAlias" : "Handle Existing Account", + "userSetupAllowed" : false + } ] + }, { + "id" : "e792b361-c5b3-4a78-b93c-0ea2fc38532c", + "alias" : "Verify Existing Account by Re-authentication", + "description" : "Reauthentication of existing account", + "providerId" : "basic-flow", + "topLevel" : false, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "idp-username-password-form", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 10, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticatorFlow" : true, + "requirement" : "CONDITIONAL", + "priority" : 20, + "autheticatorFlow" : true, + "flowAlias" : "First broker login - Conditional OTP", + "userSetupAllowed" : false + } ] + }, { + "id" : "5b5a1554-60e2-4154-9b35-4e398a5e81b8", + "alias" : "browser", + "description" : "Browser based authentication", + "providerId" : "basic-flow", + "topLevel" : true, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "auth-cookie", + "authenticatorFlow" : false, + "requirement" : "ALTERNATIVE", + "priority" : 10, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticator" : "auth-spnego", + "authenticatorFlow" : false, + "requirement" : "DISABLED", + "priority" : 20, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticator" : "identity-provider-redirector", + "authenticatorFlow" : false, + "requirement" : "ALTERNATIVE", + "priority" : 25, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticatorFlow" : true, + "requirement" : "ALTERNATIVE", + "priority" : 30, + "autheticatorFlow" : true, + "flowAlias" : "forms", + "userSetupAllowed" : false + } ] + }, { + "id" : "ba17c695-ab27-4581-8060-e13d732c0e7a", + "alias" : "clients", + "description" : "Base authentication for clients", + "providerId" : "client-flow", + "topLevel" : true, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "client-secret", + "authenticatorFlow" : false, + "requirement" : "ALTERNATIVE", + "priority" : 10, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticator" : "client-jwt", + "authenticatorFlow" : false, + "requirement" : "ALTERNATIVE", + "priority" : 20, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticator" : "client-secret-jwt", + "authenticatorFlow" : false, + "requirement" : "ALTERNATIVE", + "priority" : 30, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticator" : "client-x509", + "authenticatorFlow" : false, + "requirement" : "ALTERNATIVE", + "priority" : 40, + "autheticatorFlow" : false, + "userSetupAllowed" : false + } ] + }, { + "id" : "6db9ee9c-e45f-4688-9f88-f56fd6855ace", + "alias" : "direct grant", + "description" : "OpenID Connect Resource Owner Grant", + "providerId" : "basic-flow", + "topLevel" : true, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "direct-grant-validate-username", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 10, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticator" : "direct-grant-validate-password", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 20, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticatorFlow" : true, + "requirement" : "CONDITIONAL", + "priority" : 30, + "autheticatorFlow" : true, + "flowAlias" : "Direct Grant - Conditional OTP", + "userSetupAllowed" : false + } ] + }, { + "id" : "8e0dc68b-3c2b-4b87-b080-4fb623fa367a", + "alias" : "docker auth", + "description" : "Used by Docker clients to authenticate against the IDP", + "providerId" : "basic-flow", + "topLevel" : true, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "docker-http-basic-authenticator", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 10, + "autheticatorFlow" : false, + "userSetupAllowed" : false + } ] + }, { + "id" : "9552ffe0-ecbb-4bc0-bd10-01c8d2f25e95", + "alias" : "first broker login", + "description" : "Actions taken after first broker login with identity provider account, which is not yet linked to any Keycloak account", + "providerId" : "basic-flow", + "topLevel" : true, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticatorConfig" : "review profile config", + "authenticator" : "idp-review-profile", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 10, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticatorFlow" : true, + "requirement" : "REQUIRED", + "priority" : 20, + "autheticatorFlow" : true, + "flowAlias" : "User creation or linking", + "userSetupAllowed" : false + } ] + }, { + "id" : "05f817ff-d674-412b-b478-04b97ca2ace8", + "alias" : "forms", + "description" : "Username, password, otp and other auth forms.", + "providerId" : "basic-flow", + "topLevel" : false, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "auth-username-password-form", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 10, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticatorFlow" : true, + "requirement" : "CONDITIONAL", + "priority" : 20, + "autheticatorFlow" : true, + "flowAlias" : "Browser - Conditional OTP", + "userSetupAllowed" : false + } ] + }, { + "id" : "c53d4ea3-e0dd-468f-b90c-a251ef5f83c6", + "alias" : "registration", + "description" : "Registration flow", + "providerId" : "basic-flow", + "topLevel" : true, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "registration-page-form", + "authenticatorFlow" : true, + "requirement" : "REQUIRED", + "priority" : 10, + "autheticatorFlow" : true, + "flowAlias" : "registration form", + "userSetupAllowed" : false + } ] + }, { + "id" : "6e9ac60c-7d98-46c2-bf51-1392aa5be84b", + "alias" : "registration form", + "description" : "Registration form", + "providerId" : "form-flow", + "topLevel" : false, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "registration-user-creation", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 20, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticator" : "registration-password-action", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 50, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticator" : "registration-recaptcha-action", + "authenticatorFlow" : false, + "requirement" : "DISABLED", + "priority" : 60, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticator" : "registration-terms-and-conditions", + "authenticatorFlow" : false, + "requirement" : "DISABLED", + "priority" : 70, + "autheticatorFlow" : false, + "userSetupAllowed" : false + } ] + }, { + "id" : "21e2a653-5c04-411b-9bfd-9cfe5deaf577", + "alias" : "reset credentials", + "description" : "Reset credentials for a user if they forgot their password or something", + "providerId" : "basic-flow", + "topLevel" : true, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "reset-credentials-choose-user", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 10, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticator" : "reset-credential-email", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 20, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticator" : "reset-password", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 30, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticatorFlow" : true, + "requirement" : "CONDITIONAL", + "priority" : 40, + "autheticatorFlow" : true, + "flowAlias" : "Reset - Conditional OTP", + "userSetupAllowed" : false + } ] + }, { + "id" : "6f707d1e-ae0c-4bbb-9840-b27214fd2db5", + "alias" : "saml ecp", + "description" : "SAML ECP Profile Authentication Flow", + "providerId" : "basic-flow", + "topLevel" : true, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "http-basic-authenticator", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 10, + "autheticatorFlow" : false, + "userSetupAllowed" : false + } ] + } ], + "authenticatorConfig" : [ { + "id" : "7fed3aaf-9c98-44a5-a22b-4aeb900e4a2a", + "alias" : "create unique user config", + "config" : { + "require.password.update.after.registration" : "false" + } + }, { + "id" : "b9955a02-a0e2-449b-886c-f06740e4830e", + "alias" : "review profile config", + "config" : { + "update.profile.on.first.login" : "missing" + } + } ], + "requiredActions" : [ { + "alias" : "CONFIGURE_TOTP", + "name" : "Configure OTP", + "providerId" : "CONFIGURE_TOTP", + "enabled" : true, + "defaultAction" : false, + "priority" : 10, + "config" : { } + }, { + "alias" : "TERMS_AND_CONDITIONS", + "name" : "Terms and Conditions", + "providerId" : "TERMS_AND_CONDITIONS", + "enabled" : false, + "defaultAction" : false, + "priority" : 20, + "config" : { } + }, { + "alias" : "UPDATE_PASSWORD", + "name" : "Update Password", + "providerId" : "UPDATE_PASSWORD", + "enabled" : true, + "defaultAction" : false, + "priority" : 30, + "config" : { } + }, { + "alias" : "UPDATE_PROFILE", + "name" : "Update Profile", + "providerId" : "UPDATE_PROFILE", + "enabled" : false, + "defaultAction" : false, + "priority" : 40, + "config" : { } + }, { + "alias" : "VERIFY_EMAIL", + "name" : "Verify Email", + "providerId" : "VERIFY_EMAIL", + "enabled" : true, + "defaultAction" : false, + "priority" : 50, + "config" : { } + }, { + "alias" : "delete_account", + "name" : "Delete Account", + "providerId" : "delete_account", + "enabled" : false, + "defaultAction" : false, + "priority" : 60, + "config" : { } + }, { + "alias" : "webauthn-register", + "name" : "Webauthn Register", + "providerId" : "webauthn-register", + "enabled" : true, + "defaultAction" : false, + "priority" : 70, + "config" : { } + }, { + "alias" : "webauthn-register-passwordless", + "name" : "Webauthn Register Passwordless", + "providerId" : "webauthn-register-passwordless", + "enabled" : true, + "defaultAction" : false, + "priority" : 80, + "config" : { } + }, { + "alias" : "VERIFY_PROFILE", + "name" : "Verify Profile", + "providerId" : "VERIFY_PROFILE", + "enabled" : true, + "defaultAction" : false, + "priority" : 90, + "config" : { } + }, { + "alias" : "delete_credential", + "name" : "Delete Credential", + "providerId" : "delete_credential", + "enabled" : true, + "defaultAction" : false, + "priority" : 100, + "config" : { } + }, { + "alias" : "idp_link", + "name" : "Linking Identity Provider", + "providerId" : "idp_link", + "enabled" : true, + "defaultAction" : false, + "priority" : 120, + "config" : { } + }, { + "alias" : "update_user_locale", + "name" : "Update User Locale", + "providerId" : "update_user_locale", + "enabled" : true, + "defaultAction" : false, + "priority" : 1000, + "config" : { } + } ], + "browserFlow" : "browser", + "registrationFlow" : "registration", + "directGrantFlow" : "direct grant", + "resetCredentialsFlow" : "reset credentials", + "clientAuthenticationFlow" : "clients", + "dockerAuthenticationFlow" : "docker auth", + "firstBrokerLoginFlow" : "first broker login", + "attributes" : { + "cibaBackchannelTokenDeliveryMode" : "poll", + "cibaAuthRequestedUserHint" : "login_hint", + "clientOfflineSessionMaxLifespan" : "0", + "oauth2DevicePollingInterval" : "5", + "clientSessionIdleTimeout" : "0", + "actionTokenGeneratedByUserLifespan.verify-email" : "", + "actionTokenGeneratedByUserLifespan.idp-verify-account-via-email" : "", + "clientOfflineSessionIdleTimeout" : "0", + "actionTokenGeneratedByUserLifespan.execute-actions" : "", + "cibaInterval" : "5", + "realmReusableOtpCode" : "false", + "cibaExpiresIn" : "120", + "oauth2DeviceCodeLifespan" : "600", + "parRequestUriLifespan" : "60", + "clientSessionMaxLifespan" : "0", + "shortVerificationUri" : "", + "actionTokenGeneratedByUserLifespan.reset-credentials" : "" + }, + "keycloakVersion" : "26.4.5", + "userManagedAccessAllowed" : false, + "organizationsEnabled" : false, + "verifiableCredentialsEnabled" : false, + "adminPermissionsEnabled" : false, + "clientProfiles" : { + "profiles" : [ ] + }, + "clientPolicies" : { + "policies" : [ ] + } +} \ No newline at end of file diff --git a/opencti-platform/opencti-dev/keycloak-configuration/master-users-0.json b/opencti-platform/opencti-dev/keycloak-configuration/master-users-0.json new file mode 100644 index 000000000000..d3eb4438fd37 --- /dev/null +++ b/opencti-platform/opencti-dev/keycloak-configuration/master-users-0.json @@ -0,0 +1,146 @@ +{ + "realm" : "master", + "users" : [ { + "id" : "3be9316d-18f3-41f1-922b-3a6487b9e2c9", + "username" : "admin@external.com", + "email" : "admin@external.com", + "emailVerified" : true, + "enabled" : true, + "createdTimestamp" : 1767554373137, + "totp" : false, + "credentials" : [ { + "id" : "8e297757-4d97-43cc-b4d2-6133c59d63ab", + "type" : "password", + "createdDate" : 1767598129421, + "secretData" : "{\"value\":\"88PeB64jNwArxy0NuneXrlvHQo4fZ6DZvCk3uwxFJOg=\",\"salt\":\"gF09TJwGHhd8t573OKoObg==\",\"additionalParameters\":{}}", + "credentialData" : "{\"hashIterations\":5,\"algorithm\":\"argon2\",\"additionalParameters\":{\"hashLength\":[\"32\"],\"memory\":[\"7168\"],\"type\":[\"id\"],\"version\":[\"1.3\"],\"parallelism\":[\"1\"]}}" + } ], + "disableableCredentialTypes" : [ ], + "requiredActions" : [ ], + "realmRoles" : [ "default-roles-master" ], + "notBefore" : 0, + "groups" : [ "/Administrator" ] + }, { + "id" : "9b4b2bf0-1bc3-4dad-9b06-4723426bdcb2", + "username" : "admin@filigran.io", + "email" : "admin@filigran.io", + "emailVerified" : true, + "enabled" : true, + "createdTimestamp" : 1767554290159, + "totp" : false, + "credentials" : [ { + "id" : "5de4c4e6-35ea-4364-8541-1870bf56d05d", + "type" : "password", + "userLabel" : "My password", + "createdDate" : 1767554302071, + "secretData" : "{\"value\":\"ylqptqYbdPmLn1YQztQbIwMzUmIaLl771QzRe4NGTi8=\",\"salt\":\"oH2M58Jiioa6WG3rnDaO2w==\",\"additionalParameters\":{}}", + "credentialData" : "{\"hashIterations\":5,\"algorithm\":\"argon2\",\"additionalParameters\":{\"hashLength\":[\"32\"],\"memory\":[\"7168\"],\"type\":[\"id\"],\"version\":[\"1.3\"],\"parallelism\":[\"1\"]}}" + } ], + "disableableCredentialTypes" : [ ], + "requiredActions" : [ ], + "realmRoles" : [ "default-roles-master" ], + "notBefore" : 0, + "groups" : [ "/Administrator", "/Filigran org" ] + }, { + "id" : "a88774e3-b4fa-4ef3-8285-e430e08f32b4", + "username" : "connector@external.com", + "email" : "connector@external.com", + "emailVerified" : true, + "enabled" : true, + "createdTimestamp" : 1767554485976, + "totp" : false, + "credentials" : [ { + "id" : "e370af3a-0e82-4bf3-afec-bab133e5fb70", + "type" : "password", + "userLabel" : "My password", + "createdDate" : 1767554497277, + "secretData" : "{\"value\":\"gOnr545Hgk/ngwkrXeu/RKHseXArWMyFJ4QfNRI6WSY=\",\"salt\":\"c+FwNYtkqaHTMu4AnOa/yw==\",\"additionalParameters\":{}}", + "credentialData" : "{\"hashIterations\":5,\"algorithm\":\"argon2\",\"additionalParameters\":{\"hashLength\":[\"32\"],\"memory\":[\"7168\"],\"type\":[\"id\"],\"version\":[\"1.3\"],\"parallelism\":[\"1\"]}}" + } ], + "disableableCredentialTypes" : [ ], + "requiredActions" : [ ], + "realmRoles" : [ "default-roles-master" ], + "notBefore" : 0, + "groups" : [ "/Connector", "/Externa org" ] + }, { + "id" : "91143379-ecb7-418d-b597-8b07330ed9bf", + "username" : "connector@filigran.io", + "email" : "connector@filigran.io", + "emailVerified" : true, + "enabled" : true, + "createdTimestamp" : 1767519159060, + "totp" : false, + "credentials" : [ { + "id" : "46eab7e9-ac5c-4a21-8a17-a18079913723", + "type" : "password", + "userLabel" : "My password", + "createdDate" : 1767519167037, + "secretData" : "{\"value\":\"syVK4o7itx8hUSnZrOQTW8XmBtJo+cOjqj3iyCalUak=\",\"salt\":\"F9fb7UhdkJrvPv6lhBXXmQ==\",\"additionalParameters\":{}}", + "credentialData" : "{\"hashIterations\":5,\"algorithm\":\"argon2\",\"additionalParameters\":{\"hashLength\":[\"32\"],\"memory\":[\"7168\"],\"type\":[\"id\"],\"version\":[\"1.3\"],\"parallelism\":[\"1\"]}}" + } ], + "disableableCredentialTypes" : [ ], + "requiredActions" : [ ], + "realmRoles" : [ "default-roles-master" ], + "notBefore" : 0, + "groups" : [ "/Connector", "/Filigran org" ] + }, { + "id" : "de1a0ff4-7a72-44e5-a852-91a327f4940b", + "username" : "default@external.com", + "email" : "default@external.com", + "emailVerified" : true, + "enabled" : true, + "createdTimestamp" : 1767554454152, + "totp" : false, + "credentials" : [ { + "id" : "1121c9c1-d28d-4391-9f1e-1a849169ce66", + "type" : "password", + "userLabel" : "My password", + "createdDate" : 1767554463181, + "secretData" : "{\"value\":\"i7i6jXb8raMlLaNp1Piqozbu9GIva3nXHZ2GidrdwAU=\",\"salt\":\"gVprwNSpN/jCdARsG2frDw==\",\"additionalParameters\":{}}", + "credentialData" : "{\"hashIterations\":5,\"algorithm\":\"argon2\",\"additionalParameters\":{\"hashLength\":[\"32\"],\"memory\":[\"7168\"],\"type\":[\"id\"],\"version\":[\"1.3\"],\"parallelism\":[\"1\"]}}" + } ], + "disableableCredentialTypes" : [ ], + "requiredActions" : [ "UPDATE_PASSWORD" ], + "realmRoles" : [ "default-roles-master" ], + "notBefore" : 0, + "groups" : [ "/Default", "/Externa org" ] + }, { + "id" : "2e5b5cf0-042b-4a5a-99a1-cd70d6de7fa3", + "username" : "default@filigran.io", + "email" : "default@filigran.io", + "emailVerified" : true, + "enabled" : true, + "createdTimestamp" : 1767554337650, + "totp" : false, + "credentials" : [ { + "id" : "79425aa2-b60b-4e97-8f50-f084f5c031fa", + "type" : "password", + "userLabel" : "My password", + "createdDate" : 1767554351121, + "secretData" : "{\"value\":\"mNERHi0rMf0Ujr0jMPjAEgEXmEAArN67mXhSXNspGY0=\",\"salt\":\"g93tSxCoCRa/c2PBlVhriQ==\",\"additionalParameters\":{}}", + "credentialData" : "{\"hashIterations\":5,\"algorithm\":\"argon2\",\"additionalParameters\":{\"hashLength\":[\"32\"],\"memory\":[\"7168\"],\"type\":[\"id\"],\"version\":[\"1.3\"],\"parallelism\":[\"1\"]}}" + } ], + "disableableCredentialTypes" : [ ], + "requiredActions" : [ ], + "realmRoles" : [ "default-roles-master" ], + "notBefore" : 0, + "groups" : [ "/Default", "/Filigran org" ] + }, { + "id" : "eda57d91-a464-4ca6-828e-c2a7c93fff6d", + "username" : "service-account-openctioid", + "emailVerified" : false, + "enabled" : true, + "createdTimestamp" : 1761217100565, + "totp" : false, + "serviceAccountClientId" : "openctioid", + "credentials" : [ ], + "disableableCredentialTypes" : [ ], + "requiredActions" : [ ], + "realmRoles" : [ "default-roles-master" ], + "clientRoles" : { + "openctioid" : [ "uma_protection" ] + }, + "notBefore" : 0, + "groups" : [ ] + } ] +} \ No newline at end of file From fecac645711bbb26594cdf09acb0e35afc4a614f Mon Sep 17 00:00:00 2001 From: Julien Richard Date: Tue, 6 Jan 2026 23:55:30 +0100 Subject: [PATCH 042/126] [backend/frontend] Add a limit on number of sessions a user can have (#10183) --- .../opencti-front/lang/back/de.json | 1 + .../opencti-front/lang/back/en.json | 1 + .../opencti-front/lang/back/es.json | 1 + .../opencti-front/lang/back/fr.json | 1 + .../opencti-front/lang/back/it.json | 1 + .../opencti-front/lang/back/ja.json | 1 + .../opencti-front/lang/back/ko.json | 1 + .../opencti-front/lang/back/ru.json | 1 + .../opencti-front/lang/back/zh.json | 1 + .../opencti-front/lang/front/de.json | 1 + .../opencti-front/lang/front/en.json | 1 + .../opencti-front/lang/front/es.json | 1 + .../opencti-front/lang/front/fr.json | 1 + .../opencti-front/lang/front/it.json | 1 + .../opencti-front/lang/front/ja.json | 1 + .../opencti-front/lang/front/ko.json | 1 + .../opencti-front/lang/front/ru.json | 1 + .../opencti-front/lang/front/zh.json | 1 + .../private/components/settings/Policies.tsx | 37 +++++++++++++------ .../src/schema/relay.schema.graphql | 1 + .../config/schema/opencti.graphql | 2 +- .../opencti-graphql/src/database/session.js | 11 +++--- .../opencti-graphql/src/domain/user.js | 28 +++++++++++++- .../opencti-graphql/src/generated/graphql.ts | 2 + .../src/listener/UserActionListener.ts | 4 +- .../src/manager/activityListener.ts | 5 ++- .../internalObject-registrationAttributes.ts | 22 +++++++---- 27 files changed, 98 insertions(+), 32 deletions(-) diff --git a/opencti-platform/opencti-front/lang/back/de.json b/opencti-platform/opencti-front/lang/back/de.json index 585214d8101d..66107f52424f 100644 --- a/opencti-platform/opencti-front/lang/back/de.json +++ b/opencti-platform/opencti-front/lang/back/de.json @@ -564,6 +564,7 @@ "Marking type": "Art der Markierung", "Markings": "Markierungen", "Match Pir with source of relationship": "Pirat mit Quelle der Beziehung abgleichen", + "Max concurrent sessions (0 equals no maximum)": "Max. gleichzeitige Sitzungen (0 entspricht keinem Maximum)", "Max Confidence": "Maximale Konfidenz", "Max policy length": "Max policy length", "Max shareable markings": "Maximal teilbare Markierungen", diff --git a/opencti-platform/opencti-front/lang/back/en.json b/opencti-platform/opencti-front/lang/back/en.json index 718d448e7a9e..53969e9c84c0 100644 --- a/opencti-platform/opencti-front/lang/back/en.json +++ b/opencti-platform/opencti-front/lang/back/en.json @@ -564,6 +564,7 @@ "Marking type": "Marking type", "Markings": "Markings", "Match Pir with source of relationship": "Match Pir with source of relationship", + "Max concurrent sessions (0 equals no maximum)": "Max concurrent sessions (0 equals no maximum)", "Max Confidence": "Max Confidence", "Max policy length": "Max policy length", "Max shareable markings": "Max shareable markings", diff --git a/opencti-platform/opencti-front/lang/back/es.json b/opencti-platform/opencti-front/lang/back/es.json index 839aef4d0265..77c4bfc98cee 100644 --- a/opencti-platform/opencti-front/lang/back/es.json +++ b/opencti-platform/opencti-front/lang/back/es.json @@ -564,6 +564,7 @@ "Marking type": "Tipo de marcado", "Markings": "Marcas", "Match Pir with source of relationship": "Emparejar Pir con la fuente de relación", + "Max concurrent sessions (0 equals no maximum)": "Máximo de sesiones simultáneas (0 equivale a ningún máximo)", "Max Confidence": "Confianza máxima", "Max policy length": "Longitud máxima de la política", "Max shareable markings": "Marcas máximas compartibles", diff --git a/opencti-platform/opencti-front/lang/back/fr.json b/opencti-platform/opencti-front/lang/back/fr.json index 510a9b21baba..ae89f7ebdfd1 100644 --- a/opencti-platform/opencti-front/lang/back/fr.json +++ b/opencti-platform/opencti-front/lang/back/fr.json @@ -564,6 +564,7 @@ "Marking type": "Type de marquage", "Markings": "Marques", "Match Pir with source of relationship": "Faire correspondre Pir à la source de la relation", + "Max concurrent sessions (0 equals no maximum)": "Nombre maximal de sessions simultanées (0 signifie qu'il n'y a pas de maximum)", "Max Confidence": "Confiance maximale", "Max policy length": "Longueur maximale de la politique", "Max shareable markings": "Marques maximales partageables", diff --git a/opencti-platform/opencti-front/lang/back/it.json b/opencti-platform/opencti-front/lang/back/it.json index 9223c415f47e..6c8d8b438222 100644 --- a/opencti-platform/opencti-front/lang/back/it.json +++ b/opencti-platform/opencti-front/lang/back/it.json @@ -564,6 +564,7 @@ "Marking type": "Tipo di marcatura", "Markings": "Marcature", "Match Pir with source of relationship": "Abbinare il Pir alla fonte della relazione", + "Max concurrent sessions (0 equals no maximum)": "Sessioni massime contemporanee (0 equivale a nessun massimo)", "Max Confidence": "Massima fiducia", "Max policy length": "Lunghezza massima della politica", "Max shareable markings": "Marcature massime condivisibili", diff --git a/opencti-platform/opencti-front/lang/back/ja.json b/opencti-platform/opencti-front/lang/back/ja.json index f5c153f92acb..2e3b8f269e34 100644 --- a/opencti-platform/opencti-front/lang/back/ja.json +++ b/opencti-platform/opencti-front/lang/back/ja.json @@ -564,6 +564,7 @@ "Marking type": "マーキングタイプ", "Markings": "マーキング", "Match Pir with source of relationship": "海賊と関係元を一致させる", + "Max concurrent sessions (0 equals no maximum)": "最大同時セッション数(0は最大なし)", "Max Confidence": "最大信頼度", "Max policy length": "最大ポリシー長", "Max shareable markings": "共有可能な最大マーキング", diff --git a/opencti-platform/opencti-front/lang/back/ko.json b/opencti-platform/opencti-front/lang/back/ko.json index 1a2d133e9ffe..5db78f3d168c 100644 --- a/opencti-platform/opencti-front/lang/back/ko.json +++ b/opencti-platform/opencti-front/lang/back/ko.json @@ -564,6 +564,7 @@ "Marking type": "마킹 유형", "Markings": "마킹", "Match Pir with source of relationship": "Pir와 관계 소스 일치", + "Max concurrent sessions (0 equals no maximum)": "최대 동시 세션(0은 최대값 없음)", "Max Confidence": "최대 신뢰도", "Max policy length": "최대 정책 길이", "Max shareable markings": "최대 공유 가능 마킹", diff --git a/opencti-platform/opencti-front/lang/back/ru.json b/opencti-platform/opencti-front/lang/back/ru.json index 61003457bb7f..300b5a9d3b4a 100644 --- a/opencti-platform/opencti-front/lang/back/ru.json +++ b/opencti-platform/opencti-front/lang/back/ru.json @@ -564,6 +564,7 @@ "Marking type": "Тип маркировки", "Markings": "Маркировка", "Match Pir with source of relationship": "Сопоставьте Пир с источником отношений", + "Max concurrent sessions (0 equals no maximum)": "Максимальное количество одновременных сеансов (0 означает отсутствие максимума)", "Max Confidence": "Максимальная уверенность", "Max policy length": "Максимальная длина полиса", "Max shareable markings": "Максимально разделяемые метки", diff --git a/opencti-platform/opencti-front/lang/back/zh.json b/opencti-platform/opencti-front/lang/back/zh.json index 73d75fabbeae..1afae949be6a 100644 --- a/opencti-platform/opencti-front/lang/back/zh.json +++ b/opencti-platform/opencti-front/lang/back/zh.json @@ -564,6 +564,7 @@ "Marking type": "标记类型", "Markings": "标记", "Match Pir with source of relationship": "将 Pir 与关系源匹配", + "Max concurrent sessions (0 equals no maximum)": "最大并发会话数(0 表示无最大值)", "Max Confidence": "最大置信度", "Max policy length": "最多长度要求", "Max shareable markings": "最大可共享标记", diff --git a/opencti-platform/opencti-front/lang/front/de.json b/opencti-platform/opencti-front/lang/front/de.json index f5cb50a93bc4..51856460b381 100644 --- a/opencti-platform/opencti-front/lang/front/de.json +++ b/opencti-platform/opencti-front/lang/front/de.json @@ -2461,6 +2461,7 @@ "Matches all indicator main observable types if none is listed.": "Entspricht allen Indikator-Hauptbeobachtungstypen, wenn keiner aufgeführt ist.", "Matrix in line view": "Matrix in line Ansicht", "Matrix view": "Matrixansicht", + "Max concurrent sessions (0 equals no maximum)": "Max. gleichzeitige Sitzungen (0 entspricht keinem Maximum)", "Max Confidence": "Maximalvertrauen", "Max Confidence is overridden for some entity types:": "Max Confidence wird für einige Entitätstypen außer Kraft gesetzt:", "Max Confidence Level": "Max. Konfidenzniveau", diff --git a/opencti-platform/opencti-front/lang/front/en.json b/opencti-platform/opencti-front/lang/front/en.json index c33a55db3892..302a4c55354a 100644 --- a/opencti-platform/opencti-front/lang/front/en.json +++ b/opencti-platform/opencti-front/lang/front/en.json @@ -2461,6 +2461,7 @@ "Matches all indicator main observable types if none is listed.": "Matches all indicator main observable types if none is listed.", "Matrix in line view": "Matrix in line view", "Matrix view": "Matrix view", + "Max concurrent sessions (0 equals no maximum)": "Max concurrent sessions (0 equals no maximum)", "Max Confidence": "Max Confidence", "Max Confidence is overridden for some entity types:": "Max Confidence is overridden for some entity types:", "Max Confidence Level": "Max Confidence Level", diff --git a/opencti-platform/opencti-front/lang/front/es.json b/opencti-platform/opencti-front/lang/front/es.json index 7d7b7cceeeac..c0ffcedb2ed0 100644 --- a/opencti-platform/opencti-front/lang/front/es.json +++ b/opencti-platform/opencti-front/lang/front/es.json @@ -2461,6 +2461,7 @@ "Matches all indicator main observable types if none is listed.": "Coincide con todos los tipos observables principales del indicador si no se enumera ninguno.", "Matrix in line view": "Vista en línea de la matriz", "Matrix view": "Vista en matriz", + "Max concurrent sessions (0 equals no maximum)": "Máximo de sesiones simultáneas (0 equivale a ningún máximo)", "Max Confidence": "Max Confianza", "Max Confidence is overridden for some entity types:": "La confianza máxima se anula para algunos tipos de entidad:", "Max Confidence Level": "Nivel de confianza máximo", diff --git a/opencti-platform/opencti-front/lang/front/fr.json b/opencti-platform/opencti-front/lang/front/fr.json index 78e5a1c4e597..651439261db8 100644 --- a/opencti-platform/opencti-front/lang/front/fr.json +++ b/opencti-platform/opencti-front/lang/front/fr.json @@ -2461,6 +2461,7 @@ "Matches all indicator main observable types if none is listed.": "Correspond à tous les types d'observables principaux des indicateurs si aucun n'est listé.", "Matrix in line view": "Vue en ligne de la matrice", "Matrix view": "Vue matricielle", + "Max concurrent sessions (0 equals no maximum)": "Nombre maximal de sessions simultanées (0 signifie qu'il n'y a pas de maximum)", "Max Confidence": "Confiance max", "Max Confidence is overridden for some entity types:": "La confiance max est surchargée pour certains types d'entités :", "Max Confidence Level": "Niveau de confiance maximal", diff --git a/opencti-platform/opencti-front/lang/front/it.json b/opencti-platform/opencti-front/lang/front/it.json index 5351c49f7613..2d892e5e6c92 100644 --- a/opencti-platform/opencti-front/lang/front/it.json +++ b/opencti-platform/opencti-front/lang/front/it.json @@ -2461,6 +2461,7 @@ "Matches all indicator main observable types if none is listed.": "Corrisponde a tutti i tipi di osservabili principali degli indicatori se nessuno è elencato.", "Matrix in line view": "Matrice in visualizzazione lineare", "Matrix view": "Visualizzazione matrice", + "Max concurrent sessions (0 equals no maximum)": "Sessioni massime contemporanee (0 equivale a nessun massimo)", "Max Confidence": "Massima fiducia", "Max Confidence is overridden for some entity types:": "Massima fiducia è sovrascritto per alcuni tipi di entità:", "Max Confidence Level": "Massimo livello di fiducia", diff --git a/opencti-platform/opencti-front/lang/front/ja.json b/opencti-platform/opencti-front/lang/front/ja.json index 5a72ae6c3524..ae0c6b58df39 100644 --- a/opencti-platform/opencti-front/lang/front/ja.json +++ b/opencti-platform/opencti-front/lang/front/ja.json @@ -2461,6 +2461,7 @@ "Matches all indicator main observable types if none is listed.": "もし何もリストされていなければ、全ての指標となる主要な観測可能な型にマッチします。", "Matrix in line view": "マトリックス・インライン・ビュー", "Matrix view": "マトリックス表示", + "Max concurrent sessions (0 equals no maximum)": "最大同時セッション数(0は最大なし)", "Max Confidence": "マックス・コンフィデンス", "Max Confidence is overridden for some entity types:": "エンティティの種類によっては、最大信頼度がオーバーライドされます:", "Max Confidence Level": "最大信頼度", diff --git a/opencti-platform/opencti-front/lang/front/ko.json b/opencti-platform/opencti-front/lang/front/ko.json index 84b29b638587..9d6afb98a264 100644 --- a/opencti-platform/opencti-front/lang/front/ko.json +++ b/opencti-platform/opencti-front/lang/front/ko.json @@ -2461,6 +2461,7 @@ "Matches all indicator main observable types if none is listed.": "아무 것도 나열되지 않은 경우 모든 지표 주요 관찰 유형과 일치합니다.", "Matrix in line view": "매트릭스 인라인 보기", "Matrix view": "매트릭스 보기", + "Max concurrent sessions (0 equals no maximum)": "최대 동시 세션(0은 최대값 없음)", "Max Confidence": "최대 신뢰도", "Max Confidence is overridden for some entity types:": "일부 엔터티 유형에 대해 최대 신뢰도가 재정의되었습니다:", "Max Confidence Level": "최대 신뢰도 수준", diff --git a/opencti-platform/opencti-front/lang/front/ru.json b/opencti-platform/opencti-front/lang/front/ru.json index 40eb9714ff0d..52b7dbcc4290 100644 --- a/opencti-platform/opencti-front/lang/front/ru.json +++ b/opencti-platform/opencti-front/lang/front/ru.json @@ -2461,6 +2461,7 @@ "Matches all indicator main observable types if none is listed.": "Сопоставляет все основные наблюдаемые типы индикаторов, если ни один из них не указан.", "Matrix in line view": "Матрица в линейном представлении", "Matrix view": "Матричный вид", + "Max concurrent sessions (0 equals no maximum)": "Максимальное количество одновременных сеансов (0 означает отсутствие максимума)", "Max Confidence": "Максимальная уверенность", "Max Confidence is overridden for some entity types:": "Максимальное значение Confidence переопределено для некоторых типов сущностей:", "Max Confidence Level": "Максимальный уровень доверия", diff --git a/opencti-platform/opencti-front/lang/front/zh.json b/opencti-platform/opencti-front/lang/front/zh.json index 97eed3816ee4..f67326037959 100644 --- a/opencti-platform/opencti-front/lang/front/zh.json +++ b/opencti-platform/opencti-front/lang/front/zh.json @@ -2461,6 +2461,7 @@ "Matches all indicator main observable types if none is listed.": "如果没有列出主要观测指标类型,则匹配所有指标类型。", "Matrix in line view": "矩阵内联视图", "Matrix view": "矩阵视图", + "Max concurrent sessions (0 equals no maximum)": "最大并发会话数(0 表示无最大值)", "Max Confidence": "最大置信度", "Max Confidence is overridden for some entity types:": "对于某些实体类型,最大置信度会被覆盖:", "Max Confidence Level": "最大置信度", diff --git a/opencti-platform/opencti-front/src/private/components/settings/Policies.tsx b/opencti-platform/opencti-front/src/private/components/settings/Policies.tsx index 66dcb7184863..5b43e0cecf11 100644 --- a/opencti-platform/opencti-front/src/private/components/settings/Policies.tsx +++ b/opencti-platform/opencti-front/src/private/components/settings/Policies.tsx @@ -69,6 +69,7 @@ const PoliciesFragment = graphql` } otp_mandatory view_all_users + platform_session_max_concurrent } `; @@ -106,6 +107,7 @@ const policiesValidation = () => Yup.object().shape({ platform_consent_confirm_text: Yup.string().nullable(), platform_banner_level: Yup.string().nullable(), platform_banner_text: Yup.string().nullable(), + platform_session_max_concurrent: Yup.number().nullable(), }); interface PoliciesComponentProps { @@ -177,6 +179,7 @@ const PoliciesComponent: FunctionComponent = ({ platform_banner_level: settings.platform_banner_level, platform_banner_text: settings.platform_banner_text, otp_mandatory: settings.otp_mandatory, + platform_session_max_concurrent: settings.platform_session_max_concurrent, default_group_for_ingestion_users: null, view_all_users: settings.view_all_users ?? false, }; @@ -572,7 +575,28 @@ const PoliciesComponent: FunctionComponent = ({ className="paper-for-grid" variant="outlined" > - + handleSubmitField(name, value)} + tooltip={t_i18n( + 'When enforcing 2FA authentication, all users will be asked to enable 2FA to be able to login in the platform.', + )} + /> + handleSubmitField(name, value !== '' ? value : '0')} + /> + {authProviders.map((provider) => ( @@ -590,17 +614,6 @@ const PoliciesComponent: FunctionComponent = ({ ))} - handleSubmitField(name, value)} - tooltip={t_i18n( - 'When enforcing 2FA authentication, all users will be asked to enable 2FA to be able to login in the platform.', - )} - /> diff --git a/opencti-platform/opencti-front/src/schema/relay.schema.graphql b/opencti-platform/opencti-front/src/schema/relay.schema.graphql index db11cbbc40da..c853adfe8fd9 100644 --- a/opencti-platform/opencti-front/src/schema/relay.schema.graphql +++ b/opencti-platform/opencti-front/src/schema/relay.schema.graphql @@ -1576,6 +1576,7 @@ type Settings implements InternalObject & BasicObject & ThemeSettings & IntlSett password_policy_min_lowercase: Int password_policy_min_uppercase: Int platform_messages: [SettingsMessage!] + platform_session_max_concurrent: Int messages_administration: [SettingsMessage!] analytics_google_analytics_v4: String playground_enabled: Boolean! diff --git a/opencti-platform/opencti-graphql/config/schema/opencti.graphql b/opencti-platform/opencti-graphql/config/schema/opencti.graphql index c1d45436b5eb..ded84d3e020e 100644 --- a/opencti-platform/opencti-graphql/config/schema/opencti.graphql +++ b/opencti-platform/opencti-graphql/config/schema/opencti.graphql @@ -1420,7 +1420,6 @@ type PublicSettings implements IntlSettings & ThemeSettings { platform_providers: [PublicProvider!]! platform_enterprise_edition_license_validated: Boolean! playground_enabled: Boolean! - metrics_definition: [MetricDefinition!] } @@ -1494,6 +1493,7 @@ type Settings implements InternalObject & BasicObject & ThemeSettings & IntlSett password_policy_min_lowercase: Int password_policy_min_uppercase: Int platform_messages: [SettingsMessage!] + platform_session_max_concurrent: Int messages_administration: [SettingsMessage!] @auth(for: [SETTINGS_SETPARAMETERS]) analytics_google_analytics_v4: String playground_enabled: Boolean! diff --git a/opencti-platform/opencti-graphql/src/database/session.js b/opencti-platform/opencti-graphql/src/database/session.js index 6adea1985a0f..b469228941e3 100644 --- a/opencti-platform/opencti-graphql/src/database/session.js +++ b/opencti-platform/opencti-graphql/src/database/session.js @@ -82,10 +82,8 @@ export const killSession = async (id) => { }); }; -export const killUserSessions = async (userId) => { +export const killSessions = async (sessionsIds) => { const { store } = applicationSession; - const sessions = await findUserSessions(userId); - const sessionsIds = sessions.map((s) => s.id); const killedSessions = []; for (let index = 0; index < sessionsIds.length; index += 1) { const sessionId = sessionsIds[index]; @@ -96,9 +94,10 @@ export const killUserSessions = async (userId) => { return killedSessions; }; -export const findSessionsForUsers = async (userIds) => { - const sessions = await findSessions(); - return sessions.filter((s) => userIds.includes(s.user_id)).map((s) => s.sessions).flat(); +export const killUserSessions = async (userId) => { + const sessions = await findUserSessions(userId); + const sessionsIds = sessions.map((s) => s.id); + return killSessions(sessionsIds); }; export const applicationSession = createSessionMiddleware(); diff --git a/opencti-platform/opencti-graphql/src/domain/user.js b/opencti-platform/opencti-graphql/src/domain/user.js index d90be0b97ca3..7d681a81b4d2 100644 --- a/opencti-platform/opencti-graphql/src/domain/user.js +++ b/opencti-platform/opencti-graphql/src/domain/user.js @@ -30,7 +30,7 @@ import { storeLoadById, } from '../database/middleware-loader'; import { delEditContext, notify, setEditContext } from '../database/redis'; -import { killUserSessions } from '../database/session'; +import { findUserSessions, killSessions, killUserSessions } from '../database/session'; import { buildPagination, isEmptyField, isNotEmptyField, READ_INDEX_INTERNAL_OBJECTS, READ_INDEX_STIX_DOMAIN_OBJECTS, READ_RELATIONSHIPS_INDICES } from '../database/utils'; import { extractEntityRepresentativeName } from '../database/entity-representative'; import { publishUserAction } from '../listener/UserActionListener'; @@ -1732,6 +1732,27 @@ const validateUser = (user, settings) => { } }; +export const enforceSessionLimit = async (user, settings) => { + if (settings.platform_session_max_concurrent && settings.platform_session_max_concurrent > 0) { + const sessions = await findUserSessions(user.id); + if (sessions.length >= settings.platform_session_max_concurrent) { + const sortedSessions = sessions.sort((a, b) => { + if (a.created < b.created) { + return -1; + } + if (a.created > b.created) { + return 1; + } + return 0; + }); + const sessionsToKill = sortedSessions.slice(0, sessions.length - settings.platform_session_max_concurrent + 1); + await killSessions(sessionsToKill.map((s) => s.id)); + return sessionsToKill.length; + } + } + return 0; +}; + export const sessionAuthenticateUser = async (context, req, user, provider) => { let platformUsers = await getEntitiesMapFromCache(context, SYSTEM_USER, ENTITY_TYPE_USER); let logged = platformUsers.get(user.internal_id); @@ -1745,17 +1766,20 @@ export const sessionAuthenticateUser = async (context, req, user, provider) => { } const settings = await getEntityFromCache(context, SYSTEM_USER, ENTITY_TYPE_SETTINGS); validateUser(logged, settings); + const withOrigin = userWithOrigin(req, logged); + const numberOfKilledSessions = await enforceSessionLimit(withOrigin, settings); // Build and save the session req.session.user = { id: user.id, session_creation: now(), otp_validated: false }; req.session.session_provider = provider; req.session.save(); // Publish the login event - const userOrigin = userWithOrigin(req, logged); + const userOrigin = withOrigin; await publishUserAction({ user: userOrigin, event_type: 'authentication', event_access: 'administration', event_scope: 'login', + session_kill: numberOfKilledSessions, context_data: { provider }, }); return userOrigin; diff --git a/opencti-platform/opencti-graphql/src/generated/graphql.ts b/opencti-platform/opencti-graphql/src/generated/graphql.ts index 46113d8457c6..4f9b1ea58ed8 100644 --- a/opencti-platform/opencti-graphql/src/generated/graphql.ts +++ b/opencti-platform/opencti-graphql/src/generated/graphql.ts @@ -27392,6 +27392,7 @@ export type Settings = BasicObject & InternalObject & IntlSettings & ThemeSettin platform_providers: Array; platform_reference_attachment?: Maybe; platform_session_idle_timeout?: Maybe; + platform_session_max_concurrent?: Maybe; platform_session_timeout?: Maybe; platform_theme?: Maybe; platform_title?: Maybe; @@ -45927,6 +45928,7 @@ export type SettingsResolvers, ParentType, ContextType>; platform_reference_attachment?: Resolver, ParentType, ContextType>; platform_session_idle_timeout?: Resolver, ParentType, ContextType>; + platform_session_max_concurrent?: Resolver, ParentType, ContextType>; platform_session_timeout?: Resolver, ParentType, ContextType>; platform_theme?: Resolver, ParentType, ContextType>; platform_title?: Resolver, ParentType, ContextType>; diff --git a/opencti-platform/opencti-graphql/src/listener/UserActionListener.ts b/opencti-platform/opencti-graphql/src/listener/UserActionListener.ts index db0eed211192..d95ac6ce0d54 100644 --- a/opencti-platform/opencti-graphql/src/listener/UserActionListener.ts +++ b/opencti-platform/opencti-graphql/src/listener/UserActionListener.ts @@ -147,9 +147,10 @@ export interface UserModificationAction extends BasicUserAction { export interface UserLoginAction extends BasicUserAction { event_type: 'authentication'; event_scope: 'login'; + session_kill?: number; context_data: { provider: string; - username: string; + username?: string; }; } export interface UserLogoutAction extends BasicUserAction { @@ -186,7 +187,6 @@ export const registerUserActionListener = (listener: ActionListener): ActionHand export const publishUserAction = async (userAction: UserAction) => { const actionPromises = []; - // eslint-disable-next-line no-restricted-syntax for (const [, listener] of listeners.entries()) { actionPromises.push(listener.next(userAction)); } diff --git a/opencti-platform/opencti-graphql/src/manager/activityListener.ts b/opencti-platform/opencti-graphql/src/manager/activityListener.ts index 896a5ebb8f98..c4730277f4fc 100644 --- a/opencti-platform/opencti-graphql/src/manager/activityListener.ts +++ b/opencti-platform/opencti-graphql/src/manager/activityListener.ts @@ -136,10 +136,11 @@ const initActivityManager = () => { // 02. Handle activities if (action.event_type === 'authentication') { if (action.event_scope === 'login') { - const { provider, username } = action.context_data; + const { session_kill, context_data } = action; + const { provider, username } = context_data; const isFailLogin = action.status === 'error'; const message = isFailLogin ? `detects \`login failure\` for \`${username}\`` - : `login from provider \`${provider}\``; + : `login from provider \`${provider}\` ${(session_kill ?? 0) > 0 ? `(killing ${session_kill} sessions)` : ''}`; await activityLogger(action, message); } if (action.event_scope === 'logout') { diff --git a/opencti-platform/opencti-graphql/src/modules/attributes/internalObject-registrationAttributes.ts b/opencti-platform/opencti-graphql/src/modules/attributes/internalObject-registrationAttributes.ts index ea984e30b18a..627d6080cc42 100644 --- a/opencti-platform/opencti-graphql/src/modules/attributes/internalObject-registrationAttributes.ts +++ b/opencti-platform/opencti-graphql/src/modules/attributes/internalObject-registrationAttributes.ts @@ -239,6 +239,7 @@ const internalObjectsAttributes: { [k: string]: Array } = { { name: 'xtm_hub_registration_status', label: 'XTM Hub registration status', type: 'string', format: 'enum', values: ['registered', 'unregistered', 'lost_connectivity'], editDefault: false, mandatoryType: 'no', multiple: false, upsert: false, isFilterable: false }, { name: 'filigran_chatbot_ai_cgu_status', label: 'XTM1 CGU acceptance status', type: 'string', format: 'enum', values: ['pending', 'disabled', 'enabled'], editDefault: false, mandatoryType: 'no', multiple: false, upsert: false, isFilterable: false }, { name: 'platform_ai_enabled', label: 'AI insight activation', type: 'boolean', mandatoryType: 'no', editDefault: false, multiple: false, upsert: false, isFilterable: false }, + { name: 'platform_session_max_concurrent', label: 'Max concurrent sessions (0 equals no maximum)', type: 'numeric', precision: 'integer', mandatoryType: 'no', editDefault: false, multiple: false, upsert: false, isFilterable: false }, ], [ENTITY_TYPE_MIGRATION_STATUS]: [ { name: 'lastRun', label: 'Last run', type: 'string', format: 'short', mandatoryType: 'no', editDefault: false, multiple: false, upsert: false, isFilterable: false }, @@ -272,7 +273,8 @@ const internalObjectsAttributes: { [k: string]: Array } = { }, { name: 'default_dashboard', label: 'Default dashboard', type: 'string', format: 'short', mandatoryType: 'no', editDefault: false, multiple: false, upsert: true, isFilterable: true }, { name: 'default_hidden_types', label: 'Default hidden types', type: 'string', format: 'short', mandatoryType: 'no', editDefault: false, multiple: true, upsert: false, isFilterable: true }, - { name: 'max_shareable_markings', + { + name: 'max_shareable_markings', label: 'Max shareable markings', type: 'object', format: 'standard', @@ -286,7 +288,8 @@ const internalObjectsAttributes: { [k: string]: Array } = { { name: 'value', label: 'Marking', type: 'string', format: 'id', entityTypes: [ENTITY_TYPE_MARKING_DEFINITION], editDefault: false, mandatoryType: 'no', multiple: false, upsert: true, isFilterable: true }, ], }, - { name: 'group_confidence_level', + { + name: 'group_confidence_level', label: 'Group Confidence Level', type: 'object', format: 'standard', @@ -298,7 +301,8 @@ const internalObjectsAttributes: { [k: string]: Array } = { sortBy: { path: 'group_confidence_level.max_confidence', type: 'numeric' }, mappings: [ { name: 'max_confidence', label: 'Max Confidence', type: 'numeric', precision: 'integer', editDefault: false, mandatoryType: 'internal', multiple: false, upsert: false, isFilterable: true }, - { name: 'overrides', + { + name: 'overrides', label: 'Overrides', type: 'object', format: 'nested', @@ -310,7 +314,8 @@ const internalObjectsAttributes: { [k: string]: Array } = { mappings: [ { name: 'entity_type', label: 'Entity Type', type: 'string', format: 'short', editDefault: false, mandatoryType: 'external', multiple: false, upsert: false, isFilterable: true }, { name: 'max_confidence', label: 'Max Confidence', type: 'numeric', precision: 'integer', editDefault: false, mandatoryType: 'external', multiple: false, upsert: false, isFilterable: true }, - ] }, + ], + }, ], }, { name: 'auto_integration_assignation', label: 'Default Group used for integration user creation', type: 'string', format: 'short', mandatoryType: 'no', editDefault: false, multiple: true, upsert: true, isFilterable: true }, @@ -357,7 +362,8 @@ const internalObjectsAttributes: { [k: string]: Array } = { { name: 'submenu_show_icons', label: 'Show submenu icons', type: 'boolean', mandatoryType: 'no', editDefault: false, multiple: false, upsert: false, isFilterable: false }, { name: 'submenu_auto_collapse', label: 'Auto collapse submenus', type: 'boolean', mandatoryType: 'no', editDefault: false, multiple: false, upsert: false, isFilterable: false }, { name: 'monochrome_labels', label: 'Monochrome labels and entity types', type: 'boolean', mandatoryType: 'no', editDefault: false, multiple: false, upsert: false, isFilterable: false }, - { name: 'user_confidence_level', + { + name: 'user_confidence_level', label: 'User Confidence Level', type: 'object', format: 'standard', @@ -368,7 +374,8 @@ const internalObjectsAttributes: { [k: string]: Array } = { isFilterable: false, mappings: [ { name: 'max_confidence', label: 'Max Confidence', type: 'numeric', precision: 'integer', editDefault: false, mandatoryType: 'no', multiple: false, upsert: false, isFilterable: true }, - { name: 'overrides', + { + name: 'overrides', label: 'Overrides', type: 'object', format: 'nested', @@ -380,7 +387,8 @@ const internalObjectsAttributes: { [k: string]: Array } = { mappings: [ { name: 'entity_type', label: 'Entity Type', type: 'string', format: 'short', editDefault: false, mandatoryType: 'external', multiple: false, upsert: false, isFilterable: true }, { name: 'max_confidence', label: 'Max Confidence', type: 'numeric', precision: 'integer', editDefault: false, mandatoryType: 'external', multiple: false, upsert: false, isFilterable: true }, - ] }, + ], + }, ], }, { name: 'user_service_account', label: 'User service account', type: 'boolean', mandatoryType: 'no', editDefault: false, multiple: false, upsert: false, isFilterable: true }, From b244500e266078289d82b65787926d384c6dd5dd Mon Sep 17 00:00:00 2001 From: Samuel Hassine Date: Wed, 7 Jan 2026 02:07:50 +0200 Subject: [PATCH 043/126] [frontend] Set form intakes to full width in import dialog and fix background in dark mode (#13911) --- .../files/import_files/ImportFilesFormView.tsx | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/opencti-platform/opencti-front/src/private/components/common/files/import_files/ImportFilesFormView.tsx b/opencti-platform/opencti-front/src/private/components/common/files/import_files/ImportFilesFormView.tsx index c751d1a36798..81957a243112 100644 --- a/opencti-platform/opencti-front/src/private/components/common/files/import_files/ImportFilesFormView.tsx +++ b/opencti-platform/opencti-front/src/private/components/common/files/import_files/ImportFilesFormView.tsx @@ -32,7 +32,7 @@ const ImportFilesFormViewContent: React.FC = ({ if (!form) { return ( - + {t_i18n('Form not found')} @@ -42,7 +42,7 @@ const ImportFilesFormViewContent: React.FC = ({ if (!form.active) { return ( - + {t_i18n('This form is currently inactive')} @@ -55,9 +55,10 @@ const ImportFilesFormViewContent: React.FC = ({ @@ -76,7 +77,7 @@ const ImportFilesFormView: React.FC = ({ onSuccess }) if (!selectedFormId) { return ( - + {t_i18n('No form selected. Please go back and select a form.')} @@ -91,7 +92,7 @@ const ImportFilesFormView: React.FC = ({ onSuccess }) if (!queryRef) { return ( - + ); @@ -99,7 +100,7 @@ const ImportFilesFormView: React.FC = ({ onSuccess }) return ( + )} From 7641727239d13ea2df84cec45c596d19c802ddd8 Mon Sep 17 00:00:00 2001 From: Samuel Hassine Date: Wed, 7 Jan 2026 03:03:00 +0200 Subject: [PATCH 044/126] [backend/frontend] Add support for files in playbook container wrapper (#13912) --- .../playbooks/playbookFlow/PlaybookFlowForm.tsx | 8 ++++++++ .../PlaybookFlowFieldBoolean.tsx | 3 +++ .../src/modules/grouping/grouping-domain.ts | 6 ++++-- .../src/modules/playbook/playbook-components.ts | 17 +++++++++++++++-- .../01-database/playbook-components-test.ts | 2 ++ 5 files changed, 32 insertions(+), 4 deletions(-) diff --git a/opencti-platform/opencti-front/src/private/components/data/playbooks/playbookFlow/PlaybookFlowForm.tsx b/opencti-platform/opencti-front/src/private/components/data/playbooks/playbookFlow/PlaybookFlowForm.tsx index 2127e7eed253..86d3ecf5a60c 100644 --- a/opencti-platform/opencti-front/src/private/components/data/playbooks/playbookFlow/PlaybookFlowForm.tsx +++ b/opencti-platform/opencti-front/src/private/components/data/playbooks/playbookFlow/PlaybookFlowForm.tsx @@ -55,6 +55,8 @@ export type PlaybookFlowFormData time?: string; period?: string; day?: string; + // Component: Container wrapper + all?: boolean; }; interface PlaybookFlowFormProps { @@ -298,15 +300,21 @@ const PlaybookFlowForm = ({ } if (property.type === 'boolean') { let helperText = ''; + let disabled = false; if (propName === 'create_rel') { helperText = t_i18n('If both entities are of interest for selected PIR, then the target is kept'); } + // excludeMainElement depends on 'all' being enabled + if (propName === 'excludeMainElement') { + disabled = !values.all; + } return ( ); } diff --git a/opencti-platform/opencti-front/src/private/components/data/playbooks/playbookFlow/playbookFlowFields/PlaybookFlowFieldBoolean.tsx b/opencti-platform/opencti-front/src/private/components/data/playbooks/playbookFlow/playbookFlowFields/PlaybookFlowFieldBoolean.tsx index 47357438202b..62b8cf3227e0 100644 --- a/opencti-platform/opencti-front/src/private/components/data/playbooks/playbookFlow/playbookFlowFields/PlaybookFlowFieldBoolean.tsx +++ b/opencti-platform/opencti-front/src/private/components/data/playbooks/playbookFlow/playbookFlowFields/PlaybookFlowFieldBoolean.tsx @@ -21,12 +21,14 @@ interface PlaybookFlowFieldBooleanProps { name: string; label: string; helperText?: string; + disabled?: boolean; } const PlaybookFlowFieldBoolean = ({ name, label, helperText, + disabled, }: PlaybookFlowFieldBooleanProps) => { return ( ); }; diff --git a/opencti-platform/opencti-graphql/src/modules/grouping/grouping-domain.ts b/opencti-platform/opencti-graphql/src/modules/grouping/grouping-domain.ts index 780957e35771..d75c8e4c0368 100644 --- a/opencti-platform/opencti-graphql/src/modules/grouping/grouping-domain.ts +++ b/opencti-platform/opencti-graphql/src/modules/grouping/grouping-domain.ts @@ -10,11 +10,12 @@ import { type EntityOptions, internalLoadById, pageEntitiesConnection, storeLoad import { isStixId } from '../../schema/schemaUtils'; import { RELATION_CREATED_BY, RELATION_OBJECT } from '../../schema/stixRefRelationship'; import { elCount } from '../../database/engine'; -import { READ_INDEX_STIX_DOMAIN_OBJECTS } from '../../database/utils'; +import { isEmptyField, READ_INDEX_STIX_DOMAIN_OBJECTS } from '../../database/utils'; import type { DomainFindById } from '../../domain/domainTypes'; import { addFilter } from '../../utils/filtering/filtering-utils'; import { type BasicStoreEntityGrouping, ENTITY_TYPE_CONTAINER_GROUPING, type GroupingNumberResult } from './grouping-types'; +import { now } from '../../utils/format'; export const findById: DomainFindById = (context: AuthContext, user: AuthUser, groupingId: string) => { return storeLoadById(context, user, groupingId, ENTITY_TYPE_CONTAINER_GROUPING); @@ -25,7 +26,8 @@ export const findGroupingPaginated = (context: AuthContext, user: AuthUser, opts }; export const addGrouping = async (context: AuthContext, user: AuthUser, grouping: GroupingAddInput) => { - const created = await createEntity(context, user, grouping, ENTITY_TYPE_CONTAINER_GROUPING); + const groupingToCreate = isEmptyField(grouping.created) ? { ...grouping, created: now() } : grouping; + const created = await createEntity(context, user, groupingToCreate, ENTITY_TYPE_CONTAINER_GROUPING); return notify(BUS_TOPICS[ABSTRACT_STIX_DOMAIN_OBJECT].ADDED_TOPIC, created, user); }; diff --git a/opencti-platform/opencti-graphql/src/modules/playbook/playbook-components.ts b/opencti-platform/opencti-graphql/src/modules/playbook/playbook-components.ts index 2517f1dd9e4a..326974b2547e 100644 --- a/opencti-platform/opencti-graphql/src/modules/playbook/playbook-components.ts +++ b/opencti-platform/opencti-graphql/src/modules/playbook/playbook-components.ts @@ -454,6 +454,8 @@ interface ContainerWrapperConfiguration { container_type: string; caseTemplates: { label: string; value: string }[]; all: boolean; + excludeMainElement: boolean; + copyFiles: boolean; newContainer: boolean; } const PLAYBOOK_CONTAINER_WRAPPER_COMPONENT_SCHEMA: JSONSchemaType = { @@ -468,6 +470,8 @@ const PLAYBOOK_CONTAINER_WRAPPER_COMPONENT_SCHEMA: JSONSchemaType, any>(PLAYBOOK_CONTAINER_WRAPPER_COMPONENT_SCHEMA, schemaElement); }, executor: async ({ dataInstanceId, playbookNode, bundle }) => { - const { container_type, all, newContainer, caseTemplates } = playbookNode.configuration; + const { container_type, all, excludeMainElement, copyFiles, newContainer, caseTemplates } = playbookNode.configuration; if (!PLAYBOOK_CONTAINER_WRAPPER_COMPONENT_AVAILABLE_CONTAINERS.includes(container_type)) { throw FunctionalError('this container type is incompatible with the Container Wrapper playbook component', { container_type }); } @@ -570,7 +574,12 @@ export const PLAYBOOK_CONTAINER_WRAPPER_COMPONENT: PlaybookComponent o.id); + // If excludeMainElement is true and all is true, exclude the main element from the container + if (excludeMainElement) { + container.object_refs = bundle.objects.filter((o: StixObject) => o.id !== baseData.id).map((o: StixObject) => o.id); + } else { + container.object_refs = bundle.objects.map((o: StixObject) => o.id); + } } else { container.object_refs = [baseData.id]; } @@ -604,6 +613,10 @@ export const PLAYBOOK_CONTAINER_WRAPPER_COMPONENT: PlaybookComponentbaseData).severity && container_type === ENTITY_TYPE_CONTAINER_CASE_INCIDENT) { (container).severity = (baseData).severity; } + // Copy files from the main element to the container if requested + if (copyFiles && baseData.extensions[STIX_EXT_OCTI].files && baseData.extensions[STIX_EXT_OCTI].files.length > 0) { + container.extensions[STIX_EXT_OCTI].files = baseData.extensions[STIX_EXT_OCTI].files; + } if (STIX_DOMAIN_OBJECT_CONTAINER_CASES.includes(container_type) && caseTemplates.length > 0) { const tasks = await addTaskFromCaseTemplates(caseTemplates, (container as StixContainer)); bundle.objects.push(...tasks); diff --git a/opencti-platform/opencti-graphql/tests/03-integration/01-database/playbook-components-test.ts b/opencti-platform/opencti-graphql/tests/03-integration/01-database/playbook-components-test.ts index 51a69ff02fb8..c6647ac850d4 100644 --- a/opencti-platform/opencti-graphql/tests/03-integration/01-database/playbook-components-test.ts +++ b/opencti-platform/opencti-graphql/tests/03-integration/01-database/playbook-components-test.ts @@ -150,6 +150,8 @@ describe('playbook container wrapper component', () => { configuration: { container_type: 'Case-Incident', all: false, + excludeMainElement: false, + copyFiles: false, newContainer: false, caseTemplates: [], }, From 481f734e3300b0f30b64f8e29ee007b21a8bc40f Mon Sep 17 00:00:00 2001 From: Samuel Hassine Date: Wed, 7 Jan 2026 04:30:50 +0200 Subject: [PATCH 045/126] [frontend] In Form Intakes, allow creation on the fly of entities in lookup (#13913, #13268, #13604, #13918) --- .../opencti-front/lang/front/de.json | 3 + .../opencti-front/lang/front/en.json | 3 + .../opencti-front/lang/front/es.json | 3 + .../opencti-front/lang/front/fr.json | 3 + .../opencti-front/lang/front/it.json | 3 + .../opencti-front/lang/front/ja.json | 3 + .../opencti-front/lang/front/ko.json | 3 + .../opencti-front/lang/front/ru.json | 3 + .../opencti-front/lang/front/zh.json | 3 + .../arsenal/channels/ChannelCreation.tsx | 6 +- .../arsenal/malwares/MalwareCreation.tsx | 6 +- .../components/arsenal/tools/ToolCreation.tsx | 6 +- .../vulnerabilities/VulnerabilityCreation.tsx | 6 +- .../cases/feedbacks/FeedbackCreation.tsx | 2 - .../files/import_files/ImportFilesOptions.tsx | 2 - .../common/form/StixCoreObjectsField.jsx | 345 --------- .../common/form/StixCoreObjectsField.tsx | 717 ++++++++++++++++++ .../private/components/data/forms/Form.d.ts | 3 + .../data/forms/FormSchemaEditor.tsx | 139 +++- .../components/data/forms/FormUtils.ts | 1 + .../components/data/forms/view/FormView.tsx | 2 + .../data/forms/view/FormViewUtils.ts | 45 ++ .../entities/events/EventCreation.tsx | 6 +- .../events/incidents/IncidentCreation.tsx | 8 +- .../observed_data/ObservedDataCreation.tsx | 2 - .../locations/cities/CityCreation.tsx | 6 +- .../indicators/IndicatorCreation.tsx | 6 +- .../StixCyberObservableCreation.jsx | 16 +- .../attack_patterns/AttackPatternCreation.tsx | 6 +- .../CourseOfActionCreation.tsx | 13 +- .../narratives/NarrativeCreation.tsx | 6 +- .../threats/campaigns/CampaignCreation.tsx | 6 +- .../intrusion_sets/IntrusionSetCreation.tsx | 6 +- .../ThreatActorGroupCreation.tsx | 6 +- .../ThreatActorIndividualCreation.tsx | 6 +- .../src/modules/form/form-domain.ts | 26 +- .../opencti-graphql/src/utils/observable.ts | 98 +++ 37 files changed, 1100 insertions(+), 424 deletions(-) delete mode 100644 opencti-platform/opencti-front/src/private/components/common/form/StixCoreObjectsField.jsx create mode 100644 opencti-platform/opencti-front/src/private/components/common/form/StixCoreObjectsField.tsx diff --git a/opencti-platform/opencti-front/lang/front/de.json b/opencti-platform/opencti-front/lang/front/de.json index 51856460b381..7b328b7f33a0 100644 --- a/opencti-platform/opencti-front/lang/front/de.json +++ b/opencti-platform/opencti-front/lang/front/de.json @@ -1062,6 +1062,7 @@ "Disable forces": "Kräfte deaktivieren", "Disable horizontal tree mode": "Deaktivieren des horizontalen Baummodus", "Disable invert colors": "Farben invertieren deaktivieren", + "Disable on-the-fly entity creation": "On-the-fly-Erstellung von Entitäten deaktivieren", "Disable public dashboard": "Öffentliches Dashboard deaktivieren", "Disable public link": "Öffentlichen Link deaktivieren", "Disable timeout on this page": "Timeout auf dieser Seite deaktivieren", @@ -2551,6 +2552,8 @@ "Most relevant - 100": "Am wichtigsten - 100", "Most targeted victims (Last 3 months)": "Die meisten angegriffenen Opfer (letzte 3 Monate)", "Most used filters": "Meist genutzte Filter", + "Move down": "Abwärts bewegen", + "Move up": "Nach oben verschieben", "Multi-Select": "Mehrfachauswahl", "Multiple": "Mehrere", "Multiple (limited to 5)": "Mehrfach (begrenzt auf 5)", diff --git a/opencti-platform/opencti-front/lang/front/en.json b/opencti-platform/opencti-front/lang/front/en.json index 302a4c55354a..3aa63cbf33df 100644 --- a/opencti-platform/opencti-front/lang/front/en.json +++ b/opencti-platform/opencti-front/lang/front/en.json @@ -1062,6 +1062,7 @@ "Disable forces": "Disable forces", "Disable horizontal tree mode": "Disable horizontal tree mode", "Disable invert colors": "Disable invert colors", + "Disable on-the-fly entity creation": "Disable on-the-fly entity creation", "Disable public dashboard": "Disable public dashboard", "Disable public link": "Disable public link", "Disable timeout on this page": "Disable timeout on this page", @@ -2551,6 +2552,8 @@ "Most relevant - 100": "Most relevant - 100", "Most targeted victims (Last 3 months)": "Most targeted victims (Last 3 months)", "Most used filters": "Most used filters", + "Move down": "Move down", + "Move up": "Move up", "Multi-Select": "Multi-Select", "Multiple": "Multiple", "Multiple (limited to 5)": "Multiple (limited to 5)", diff --git a/opencti-platform/opencti-front/lang/front/es.json b/opencti-platform/opencti-front/lang/front/es.json index c0ffcedb2ed0..7a91acb99e93 100644 --- a/opencti-platform/opencti-front/lang/front/es.json +++ b/opencti-platform/opencti-front/lang/front/es.json @@ -1062,6 +1062,7 @@ "Disable forces": "Desactivar las fuerzas", "Disable horizontal tree mode": "Desactivar el modo de árbol horizontal", "Disable invert colors": "Desactivar invertir colores", + "Disable on-the-fly entity creation": "Desactivar la creación de entidades sobre la marcha", "Disable public dashboard": "Desactivar panel público", "Disable public link": "Desactivar enlace público", "Disable timeout on this page": "Deshabilitar el tiempo de espera en esta página", @@ -2551,6 +2552,8 @@ "Most relevant - 100": "Más relevante - 100", "Most targeted victims (Last 3 months)": "Víctimas más atacadas (Últimos 3 meses)", "Most used filters": "Filtros más utilizados", + "Move down": "Bajar", + "Move up": "Subir", "Multi-Select": "Selección múltiple", "Multiple": "Múltiple", "Multiple (limited to 5)": "Múltiple (limitado a 5)", diff --git a/opencti-platform/opencti-front/lang/front/fr.json b/opencti-platform/opencti-front/lang/front/fr.json index 651439261db8..ad73cfb10a7b 100644 --- a/opencti-platform/opencti-front/lang/front/fr.json +++ b/opencti-platform/opencti-front/lang/front/fr.json @@ -1062,6 +1062,7 @@ "Disable forces": "Désactiver les forces", "Disable horizontal tree mode": "Désactiver le mode arbre horizontal", "Disable invert colors": "Désactiver l'inversion des couleurs", + "Disable on-the-fly entity creation": "Désactiver la création d'entités à la volée", "Disable public dashboard": "Désactiver le tableau de bord public", "Disable public link": "Désactiver le lien public", "Disable timeout on this page": "Désactiver le délai d'attente sur cette page", @@ -2551,6 +2552,8 @@ "Most relevant - 100": "Plus pertinent - 100", "Most targeted victims (Last 3 months)": "Victimes les plus ciblées (3 derniers mois)", "Most used filters": "Filtres les plus utilisés", + "Move down": "Déplacer vers le bas", + "Move up": "Déplacer vers le haut", "Multi-Select": "Multi-sélection", "Multiple": "Multiple", "Multiple (limited to 5)": "Multiple (limité à 5)", diff --git a/opencti-platform/opencti-front/lang/front/it.json b/opencti-platform/opencti-front/lang/front/it.json index 2d892e5e6c92..3b463228d0a6 100644 --- a/opencti-platform/opencti-front/lang/front/it.json +++ b/opencti-platform/opencti-front/lang/front/it.json @@ -1062,6 +1062,7 @@ "Disable forces": "Disabilita le forze", "Disable horizontal tree mode": "Disabilita la modalità albero orizzontale", "Disable invert colors": "Disabilita l'inversione dei colori", + "Disable on-the-fly entity creation": "Disabilitare la creazione di entità al volo", "Disable public dashboard": "Disabilita la dashboard pubblica", "Disable public link": "Disabilita il link pubblico", "Disable timeout on this page": "Disabilita il timeout su questa pagina", @@ -2551,6 +2552,8 @@ "Most relevant - 100": "Più rilevante - 100", "Most targeted victims (Last 3 months)": "Vittime più bersagliate (Ultimi 3 mesi)", "Most used filters": "Filtri più utilizzati", + "Move down": "Scendere", + "Move up": "Sposta in alto", "Multi-Select": "Selezione multipla", "Multiple": "Multiplo", "Multiple (limited to 5)": "Multiplo (limitato a 5)", diff --git a/opencti-platform/opencti-front/lang/front/ja.json b/opencti-platform/opencti-front/lang/front/ja.json index ae0c6b58df39..ef117a41afea 100644 --- a/opencti-platform/opencti-front/lang/front/ja.json +++ b/opencti-platform/opencti-front/lang/front/ja.json @@ -1062,6 +1062,7 @@ "Disable forces": "強制モードをやめる", "Disable horizontal tree mode": "水平ツリーモードを無効にする", "Disable invert colors": "色の反転を無効にする", + "Disable on-the-fly entity creation": "オンザフライ・エンティティ生成を無効にする", "Disable public dashboard": "公開ダッシュボードを無効にする", "Disable public link": "公開リンクを無効にする", "Disable timeout on this page": "このページでタイムアウトを無効にする", @@ -2551,6 +2552,8 @@ "Most relevant - 100": "最も関連性が高い - 100", "Most targeted victims (Last 3 months)": "最も標的とされた被害者 (過去 3 ヶ月)", "Most used filters": "最も使用されるフィルター", + "Move down": "下に移動", + "Move up": "上に移動", "Multi-Select": "複数選択", "Multiple": "複数", "Multiple (limited to 5)": "複数(5回まで)", diff --git a/opencti-platform/opencti-front/lang/front/ko.json b/opencti-platform/opencti-front/lang/front/ko.json index 9d6afb98a264..d35c943421ff 100644 --- a/opencti-platform/opencti-front/lang/front/ko.json +++ b/opencti-platform/opencti-front/lang/front/ko.json @@ -1062,6 +1062,7 @@ "Disable forces": "강제 비활성화", "Disable horizontal tree mode": "수평 트리 모드 비활성화", "Disable invert colors": "반전 색상 비활성화", + "Disable on-the-fly entity creation": "즉석 엔티티 생성 비활성화", "Disable public dashboard": "공개 대시보드 비활성화", "Disable public link": "공개 링크 비활성화", "Disable timeout on this page": "이 페이지에서 타임아웃 비활성화", @@ -2551,6 +2552,8 @@ "Most relevant - 100": "가장 관련성 높음 - 100", "Most targeted victims (Last 3 months)": "가장 많이 표적이 된 피해자 수(최근 3개월)", "Most used filters": "가장 많이 사용된 필터", + "Move down": "아래로 이동", + "Move up": "위로 이동", "Multi-Select": "다중 선택", "Multiple": "다중", "Multiple (limited to 5)": "다중 (최대 5개 제한)", diff --git a/opencti-platform/opencti-front/lang/front/ru.json b/opencti-platform/opencti-front/lang/front/ru.json index 52b7dbcc4290..b9b6a76b1d05 100644 --- a/opencti-platform/opencti-front/lang/front/ru.json +++ b/opencti-platform/opencti-front/lang/front/ru.json @@ -1062,6 +1062,7 @@ "Disable forces": "Отключить силы", "Disable horizontal tree mode": "Отключить режим горизонтального дерева", "Disable invert colors": "Отключить инвертирование цветов", + "Disable on-the-fly entity creation": "Отключение создания сущностей \"на лету", "Disable public dashboard": "Отключить публичный дашборд", "Disable public link": "Отключить публичную ссылку", "Disable timeout on this page": "Отключите тайм-аут на этой странице", @@ -2551,6 +2552,8 @@ "Most relevant - 100": "Наиболее актуально - 100", "Most targeted victims (Last 3 months)": "Часто атакуемые (последние 3 месяца)", "Most used filters": "Часто используемые фильтры", + "Move down": "Перемещение вниз", + "Move up": "Переместить вверх", "Multi-Select": "Мультивыбор", "Multiple": "Множество", "Multiple (limited to 5)": "Несколько (не более 5)", diff --git a/opencti-platform/opencti-front/lang/front/zh.json b/opencti-platform/opencti-front/lang/front/zh.json index f67326037959..91e018e12e0e 100644 --- a/opencti-platform/opencti-front/lang/front/zh.json +++ b/opencti-platform/opencti-front/lang/front/zh.json @@ -1062,6 +1062,7 @@ "Disable forces": "禁用强制", "Disable horizontal tree mode": "禁用水平树模式", "Disable invert colors": "禁用反转颜色", + "Disable on-the-fly entity creation": "禁用即时创建实体", "Disable public dashboard": "禁用公共仪表盘", "Disable public link": "禁用公共链接", "Disable timeout on this page": "在此页面上禁用超时", @@ -2551,6 +2552,8 @@ "Most relevant - 100": "最相关 - 100", "Most targeted victims (Last 3 months)": "最多目标受害者(最近 3 个月)", "Most used filters": "最常用的过滤器", + "Move down": "向下移动", + "Move up": "上移", "Multi-Select": "多选", "Multiple": "多个", "Multiple (limited to 5)": "多次(限 5 次)", diff --git a/opencti-platform/opencti-front/src/private/components/arsenal/channels/ChannelCreation.tsx b/opencti-platform/opencti-front/src/private/components/arsenal/channels/ChannelCreation.tsx index 79df224cf60b..1b9d88892fb5 100644 --- a/opencti-platform/opencti-front/src/private/components/arsenal/channels/ChannelCreation.tsx +++ b/opencti-platform/opencti-front/src/private/components/arsenal/channels/ChannelCreation.tsx @@ -63,7 +63,7 @@ interface ChannelAddInput { } interface ChannelFormProps { - updater: (store: RecordSourceSelectorProxy, key: string) => void; + updater: (store: RecordSourceSelectorProxy, key: string, response: ChannelCreationMutation['response']['channelAdd']) => void; onReset?: () => void; onCompleted?: () => void; defaultCreatedBy?: { value: string; label: string }; @@ -116,9 +116,9 @@ export const ChannelCreationForm: FunctionComponent = ({ resetBulk, } = useBulkCommit({ commit, - relayUpdater: (store) => { + relayUpdater: (store, response) => { if (updater) { - updater(store, 'channelAdd'); + updater(store, 'channelAdd', response?.channelAdd); } }, }); diff --git a/opencti-platform/opencti-front/src/private/components/arsenal/malwares/MalwareCreation.tsx b/opencti-platform/opencti-front/src/private/components/arsenal/malwares/MalwareCreation.tsx index 7f25116274fa..00a226872e58 100644 --- a/opencti-platform/opencti-front/src/private/components/arsenal/malwares/MalwareCreation.tsx +++ b/opencti-platform/opencti-front/src/private/components/arsenal/malwares/MalwareCreation.tsx @@ -72,7 +72,7 @@ interface MalwareAddInput { } interface MalwareFormProps { - updater: (store: RecordSourceSelectorProxy, key: string) => void; + updater: (store: RecordSourceSelectorProxy, key: string, response: MalwareCreationMutation['response']['malwareAdd']) => void; onCompleted?: () => void; onReset?: () => void; defaultCreatedBy?: { value: string; label: string }; @@ -132,9 +132,9 @@ export const MalwareCreationForm: FunctionComponent = ({ resetBulk, } = useBulkCommit({ commit, - relayUpdater: (store) => { + relayUpdater: (store, response) => { if (updater) { - updater(store, 'malwareAdd'); + updater(store, 'malwareAdd', response?.malwareAdd); } }, }); diff --git a/opencti-platform/opencti-front/src/private/components/arsenal/tools/ToolCreation.tsx b/opencti-platform/opencti-front/src/private/components/arsenal/tools/ToolCreation.tsx index 27a80033ad42..037fb651e2a3 100644 --- a/opencti-platform/opencti-front/src/private/components/arsenal/tools/ToolCreation.tsx +++ b/opencti-platform/opencti-front/src/private/components/arsenal/tools/ToolCreation.tsx @@ -67,7 +67,7 @@ interface ToolAddInput { } interface ToolFormProps { - updater: (store: RecordSourceSelectorProxy, key: string) => void; + updater: (store: RecordSourceSelectorProxy, key: string, response: ToolCreationMutation['response']['toolAdd']) => void; onReset?: () => void; onCompleted?: () => void; defaultCreatedBy?: { value: string; label: string }; @@ -117,9 +117,9 @@ export const ToolCreationForm: FunctionComponent = ({ resetBulk, } = useBulkCommit({ commit, - relayUpdater: (store) => { + relayUpdater: (store, response) => { if (updater) { - updater(store, 'toolAdd'); + updater(store, 'toolAdd', response?.toolAdd); } }, }); diff --git a/opencti-platform/opencti-front/src/private/components/arsenal/vulnerabilities/VulnerabilityCreation.tsx b/opencti-platform/opencti-front/src/private/components/arsenal/vulnerabilities/VulnerabilityCreation.tsx index 0fcd54908a19..c6982740df4e 100644 --- a/opencti-platform/opencti-front/src/private/components/arsenal/vulnerabilities/VulnerabilityCreation.tsx +++ b/opencti-platform/opencti-front/src/private/components/arsenal/vulnerabilities/VulnerabilityCreation.tsx @@ -75,7 +75,7 @@ interface VulnerabilityCreationProps { } interface VulnerabilityFormProps { - updater: (store: RecordSourceSelectorProxy, key: string) => void; + updater: (store: RecordSourceSelectorProxy, key: string, response: VulnerabilityCreationMutation['response']['vulnerabilityAdd']) => void; onReset?: () => void; onCompleted?: () => void; defaultCreatedBy?: FieldOption; @@ -141,9 +141,9 @@ export const VulnerabilityCreationForm: FunctionComponent({ commit, - relayUpdater: (store) => { + relayUpdater: (store, response) => { if (updater) { - updater(store, 'vulnerabilityAdd'); + updater(store, 'vulnerabilityAdd', response?.vulnerabilityAdd); } }, }); diff --git a/opencti-platform/opencti-front/src/private/components/cases/feedbacks/FeedbackCreation.tsx b/opencti-platform/opencti-front/src/private/components/cases/feedbacks/FeedbackCreation.tsx index 13590acbdc24..3e34a1bf2190 100644 --- a/opencti-platform/opencti-front/src/private/components/cases/feedbacks/FeedbackCreation.tsx +++ b/opencti-platform/opencti-front/src/private/components/cases/feedbacks/FeedbackCreation.tsx @@ -173,8 +173,6 @@ const FeedbackCreation: FunctionComponent<{ name="objects" required={(mandatoryAttributes.includes('objects'))} style={fieldSpacingContainerStyle} - setFieldValue={setFieldValue} - values={values.objects} />
    {importMode !== 'auto' && !draftContext && ( diff --git a/opencti-platform/opencti-front/src/private/components/common/form/StixCoreObjectsField.jsx b/opencti-platform/opencti-front/src/private/components/common/form/StixCoreObjectsField.jsx deleted file mode 100644 index 71f6603be45d..000000000000 --- a/opencti-platform/opencti-front/src/private/components/common/form/StixCoreObjectsField.jsx +++ /dev/null @@ -1,345 +0,0 @@ -import React, { useState } from 'react'; -import * as R from 'ramda'; -import { Field } from 'formik'; -import { graphql } from 'react-relay'; -import InputAdornment from '@mui/material/InputAdornment'; -import { PaletteOutlined } from '@mui/icons-material'; -import Popover from '@mui/material/Popover'; -import MenuList from '@mui/material/MenuList'; -import MenuItem from '@mui/material/MenuItem'; -import Checkbox from '@mui/material/Checkbox'; -import ListItemText from '@mui/material/ListItemText'; -import makeStyles from '@mui/styles/makeStyles'; -import IconButton from '@mui/material/IconButton'; -import ItemIcon from '../../../../components/ItemIcon'; -import { getMainRepresentative } from '../../../../utils/defaultRepresentatives'; -import { useFormatter } from '../../../../components/i18n'; -import AutocompleteField from '../../../../components/AutocompleteField'; -import { fetchQuery } from '../../../../relay/environment'; -import useAttributes from '../../../../utils/hooks/useAttributes'; -import { displayEntityTypeForTranslation } from '../../../../utils/String'; - -export const stixCoreObjectsFieldSearchQuery = graphql` - query StixCoreObjectsFieldSearchQuery($search: String, $types: [String]) { - stixCoreObjects(search: $search, types: $types, first: 100) { - edges { - node { - id - entity_type - parent_types - created_at - createdBy { - ... on Identity { - id - name - entity_type - } - } - objectMarking { - id - definition_type - definition - x_opencti_order - x_opencti_color - } - ... on AttackPattern { - name - description - x_mitre_id - } - ... on Campaign { - name - description - first_seen - last_seen - } - ... on Note { - attribute_abstract - } - ... on ObservedData { - name - first_observed - last_observed - } - ... on Opinion { - opinion - } - ... on Report { - name - description - published - } - ... on Grouping { - name - description - context - } - ... on CourseOfAction { - name - description - } - ... on Individual { - name - description - } - ... on Organization { - name - description - } - ... on Event { - name - description - } - ... on Sector { - name - description - } - ... on System { - name - description - } - ... on Indicator { - name - description - valid_from - } - ... on Infrastructure { - name - description - } - ... on IntrusionSet { - name - description - first_seen - last_seen - } - ... on Position { - name - description - } - ... on City { - name - description - } - ... on AdministrativeArea { - name - description - } - ... on Country { - name - description - } - ... on Region { - name - description - } - ... on Malware { - name - description - first_seen - last_seen - } - ... on ThreatActor { - name - description - first_seen - last_seen - } - ... on Tool { - name - description - } - ... on Vulnerability { - name - description - } - ... on Incident { - name - description - first_seen - last_seen - } - ... on Case { - name - } - ... on Task { - name - } - ... on Channel { - name - } - ... on Narrative { - name - } - ... on Language { - name - } - ... on DataComponent { - name - } - ... on DataSource { - name - } - ... on Case { - name - } - ... on Task { - name - } - ... on StixCyberObservable { - observable_value - x_opencti_description - } - } - } - } - } -`; - -// Deprecated - https://mui.com/system/styles/basics/ -// Do not use it for new code. -const useStyles = makeStyles(() => ({ - icon: { - paddingTop: 4, - display: 'inline-block', - }, - text: { - display: 'inline-block', - flexGrow: 1, - marginLeft: 10, - }, - autoCompleteIndicator: { - display: 'none', - }, -})); - -const StixCoreObjectsField = (props) => { - const { - name, - style, - helpertext, - required = false, - multiple = true, - label, - disabled = false, - types = null, - } = props; - const classes = useStyles(); - const { t_i18n } = useFormatter(); - const { stixCoreObjectTypes: entityTypes } = useAttributes(); - const [anchorElSearchScope, setAnchorElSearchScope] = useState(false); - const [stixCoreObjects, setStixCoreObjects] = useState([]); - const [searchScope, setSearchScope] = useState({}); - const handleOpenSearchScope = (event) => setAnchorElSearchScope(event.currentTarget); - const handleCloseSearchScope = () => setAnchorElSearchScope(undefined); - const handleToggleSearchScope = (key, value) => { - setSearchScope((c) => ({ - ...c, - [key]: (searchScope[key] || []).includes(value) - ? searchScope[key].filter((n) => n !== value) - : [...(searchScope[key] || []), value], - })); - }; - const searchStixCoreObjects = (event) => { - fetchQuery(stixCoreObjectsFieldSearchQuery, { - search: event && event.target.value !== 0 ? event.target.value : '', - types: types ?? searchScope[name] ?? [], - }) - .toPromise() - .then((data) => { - const finalStixCoreObjects = R.pipe( - R.pathOr([], ['stixCoreObjects', 'edges']), - R.map((n) => ({ - label: getMainRepresentative(n.node), - value: n.node.id, - type: n.node.entity_type, - })), - )(data); - setStixCoreObjects(finalStixCoreObjects); - }); - }; - const entitiesTypes = R.pipe( - R.map((n) => ({ - label: t_i18n(displayEntityTypeForTranslation(n)), - value: n, - type: n, - })), - R.sortWith([R.ascend(R.prop('label'))]), - R.filter((type) => (types ? types.includes(type.value) : true)), - )(entityTypes); - return ( - <> - - - 0 ? 'secondary' : 'primary'} - /> - - handleCloseSearchScope()} - anchorOrigin={{ - vertical: 'center', - horizontal: 'right', - }} - transformOrigin={{ - vertical: 'center', - horizontal: 'left', - }} - elevation={8} - > - - {entitiesTypes.map((entityType) => ( - handleToggleSearchScope(name, entityType.value)} - > - - - - ))} - - - - )} - groupBy={(option) => option.type} - noOptionsText={t_i18n('No available options')} - options={stixCoreObjects} - onInputChange={searchStixCoreObjects} - renderOption={(innerProps, option) => ( -
  • -
    - -
    -
    {option.label}
    -
  • - )} - classes={{ clearIndicator: classes.autoCompleteIndicator }} - /> - - ); -}; - -export default StixCoreObjectsField; diff --git a/opencti-platform/opencti-front/src/private/components/common/form/StixCoreObjectsField.tsx b/opencti-platform/opencti-front/src/private/components/common/form/StixCoreObjectsField.tsx new file mode 100644 index 000000000000..a549b6412bc4 --- /dev/null +++ b/opencti-platform/opencti-front/src/private/components/common/form/StixCoreObjectsField.tsx @@ -0,0 +1,717 @@ +import React, { useState, FunctionComponent, useCallback } from 'react'; +import * as R from 'ramda'; +import { Field, useFormikContext } from 'formik'; +import { graphql } from 'react-relay'; +import InputAdornment from '@mui/material/InputAdornment'; +import { Add, PaletteOutlined } from '@mui/icons-material'; +import Popover from '@mui/material/Popover'; +import MenuList from '@mui/material/MenuList'; +import MenuItem from '@mui/material/MenuItem'; +import Checkbox from '@mui/material/Checkbox'; +import ListItemText from '@mui/material/ListItemText'; +import makeStyles from '@mui/styles/makeStyles'; +import IconButton from '@mui/material/IconButton'; +import ItemIcon from '../../../../components/ItemIcon'; +import { getMainRepresentative } from '../../../../utils/defaultRepresentatives'; +import { useFormatter } from '../../../../components/i18n'; +import AutocompleteField from '../../../../components/AutocompleteField'; +import { fetchQuery } from '../../../../relay/environment'; +import useAttributes from '../../../../utils/hooks/useAttributes'; +import { displayEntityTypeForTranslation } from '../../../../utils/String'; +import StixDomainObjectCreation from '../stix_domain_objects/StixDomainObjectCreation'; +import StixCyberObservableCreation from '../../observations/stix_cyber_observables/StixCyberObservableCreation'; +import type { Theme } from '../../../../components/Theme'; +import { FieldOption } from '../../../../utils/field'; + +export const stixCoreObjectsFieldSearchQuery = graphql` + query StixCoreObjectsFieldSearchQuery($search: String, $types: [String]) { + stixCoreObjects(search: $search, types: $types, first: 100) { + edges { + node { + id + entity_type + parent_types + created_at + createdBy { + ... on Identity { + id + name + entity_type + } + } + objectMarking { + id + definition_type + definition + x_opencti_order + x_opencti_color + } + ... on AttackPattern { + name + description + x_mitre_id + } + ... on Campaign { + name + description + first_seen + last_seen + } + ... on Note { + attribute_abstract + } + ... on ObservedData { + name + first_observed + last_observed + } + ... on Opinion { + opinion + } + ... on Report { + name + description + published + } + ... on Grouping { + name + description + context + } + ... on CourseOfAction { + name + description + } + ... on Individual { + name + description + } + ... on Organization { + name + description + } + ... on Event { + name + description + } + ... on Sector { + name + description + } + ... on System { + name + description + } + ... on Indicator { + name + description + valid_from + } + ... on Infrastructure { + name + description + } + ... on IntrusionSet { + name + description + first_seen + last_seen + } + ... on Position { + name + description + } + ... on City { + name + description + } + ... on AdministrativeArea { + name + description + } + ... on Country { + name + description + } + ... on Region { + name + description + } + ... on Malware { + name + description + first_seen + last_seen + } + ... on ThreatActor { + name + description + first_seen + last_seen + } + ... on Tool { + name + description + } + ... on Vulnerability { + name + description + } + ... on Incident { + name + description + first_seen + last_seen + } + ... on Case { + name + } + ... on Task { + name + } + ... on Channel { + name + } + ... on Narrative { + name + } + ... on Language { + name + } + ... on DataComponent { + name + } + ... on DataSource { + name + } + ... on Case { + name + } + ... on Task { + name + } + ... on StixCyberObservable { + observable_value + x_opencti_description + } + } + } + } + } +`; + +// Deprecated - https://mui.com/system/styles/basics/ +// Do not use it for new code. +const useStyles = makeStyles(() => ({ + icon: { + paddingTop: 4, + display: 'inline-block', + }, + text: { + display: 'inline-block', + flexGrow: 1, + marginLeft: 10, + }, + autoCompleteIndicator: { + display: 'none', + }, + createOption: { + fontStyle: 'italic', + }, +})); + +const CREATE_OPTION_VALUE = '__create_new_entity__'; + +interface StixCoreObjectOption { + label: string; + value: string; + type: string; + isCreateOption?: boolean; +} + +interface StixCoreObjectsFieldProps { + name: string; + style?: React.CSSProperties; + helpertext?: string; + required?: boolean; + multiple?: boolean; + label?: string; + disabled?: boolean; + types?: string[] | null; + disableCreation?: boolean; +} + +interface CreatedEntity { + id: string; + name?: string; + observable_value?: string; + entity_type: string; + representative?: { + main?: string; + }; +} + +const StixCoreObjectsField: FunctionComponent = ({ + name, + style, + helpertext, + required = false, + multiple = true, + label, + disabled = false, + types = null, + disableCreation = false, +}) => { + const classes = useStyles(); + const { t_i18n } = useFormatter(); + const { stixCoreObjectTypes: entityTypes, stixCyberObservableTypes, stixDomainObjectTypes } = useAttributes(); + const { setFieldValue, values } = useFormikContext>(); + + const [anchorElSearchScope, setAnchorElSearchScope] = useState(null); + const [stixCoreObjects, setStixCoreObjects] = useState([]); + const [searchScope, setSearchScope] = useState>({}); + const [currentSearchTerm, setCurrentSearchTerm] = useState(''); + + // Creation dialog states + const [openSDOCreation, setOpenSDOCreation] = useState(false); + const [openSCOCreation, setOpenSCOCreation] = useState(false); + const [createEntityType, setCreateEntityType] = useState(null); + const [valueBeforeCreate, setValueBeforeCreate] = useState(null); + // Ref to track if entity was successfully created (prevents handleClose from restoring old value) + const entityCreatedSuccessfully = React.useRef(false); + + const handleOpenSearchScope = (event: React.MouseEvent) => setAnchorElSearchScope(event.currentTarget); + const handleCloseSearchScope = () => setAnchorElSearchScope(null); + + const handleToggleSearchScope = (key: string, value: string) => { + setSearchScope((c) => ({ + ...c, + [key]: (c[key] || []).includes(value) + ? c[key].filter((n) => n !== value) + : [...(c[key] || []), value], + })); + }; + + const isObservableType = useCallback((type: string) => { + return stixCyberObservableTypes.includes(type); + }, [stixCyberObservableTypes]); + + const isDomainObjectType = useCallback((type: string) => { + return stixDomainObjectTypes.includes(type); + }, [stixDomainObjectTypes]); + + const getTargetTypes = useCallback(() => { + return types ?? searchScope[name] ?? []; + }, [types, searchScope, name]); + + const shouldShowCreateOption = useCallback((resultsCount: number) => { + if (disabled || disableCreation) return false; + if (resultsCount >= 10) return false; + const targetTypes = getTargetTypes(); + if (targetTypes.length === 0) return true; + return targetTypes.some((t) => isDomainObjectType(t) || isObservableType(t)); + }, [disabled, disableCreation, getTargetTypes, isDomainObjectType, isObservableType]); + + const getCreationType = useCallback(() => { + const targetTypes = getTargetTypes(); + if (targetTypes.length === 1) { + return targetTypes[0]; + } + return null; + }, [getTargetTypes]); + + const handleOpenCreation = useCallback(() => { + entityCreatedSuccessfully.current = false; + const targetTypes = getTargetTypes(); + + if (targetTypes.length === 1) { + const singleType = targetTypes[0]; + if (isObservableType(singleType)) { + setCreateEntityType(singleType); + setOpenSCOCreation(true); + } else if (isDomainObjectType(singleType)) { + setCreateEntityType(singleType); + setOpenSDOCreation(true); + } + } else if (targetTypes.length > 1) { + const allObservables = targetTypes.every((t) => isObservableType(t)); + const allDomainObjects = targetTypes.every((t) => isDomainObjectType(t)); + + if (allObservables) { + setCreateEntityType(null); + setOpenSCOCreation(true); + } else if (allDomainObjects) { + setCreateEntityType(null); + setOpenSDOCreation(true); + } else { + setCreateEntityType(null); + setOpenSDOCreation(true); + } + } else { + setCreateEntityType(null); + setOpenSDOCreation(true); + } + }, [getTargetTypes, isObservableType, isDomainObjectType]); + + const handleSDOEntityCreated = useCallback((createdEntity: CreatedEntity | undefined) => { + entityCreatedSuccessfully.current = true; + setOpenSDOCreation(false); + setCreateEntityType(null); + setValueBeforeCreate(null); + + if (createdEntity?.id && createdEntity?.entity_type) { + const entityLabel = createdEntity.representative?.main + || createdEntity.name + || createdEntity.observable_value + || createdEntity.id; + const newOption: StixCoreObjectOption = { + label: entityLabel, + value: createdEntity.id, + type: createdEntity.entity_type, + }; + + setStixCoreObjects((prev) => [newOption, ...prev.filter((o) => !o.isCreateOption)]); + + const currentValue = values[name] as StixCoreObjectOption | StixCoreObjectOption[] | null; + if (multiple) { + const currentArray = Array.isArray(currentValue) ? currentValue : []; + setFieldValue(name, [...currentArray, newOption]); + } else { + setFieldValue(name, newOption); + } + } else { + fetchQuery(stixCoreObjectsFieldSearchQuery, { + search: currentSearchTerm, + types: types ?? searchScope[name] ?? [], + }) + .toPromise() + .then((data: unknown) => { + const typedData = data as { stixCoreObjects?: { edges?: Array<{ node: Record }> } }; + const results = R.pipe( + R.pathOr([], ['stixCoreObjects', 'edges']), + R.map((n: { node: Record }) => ({ + label: getMainRepresentative(n.node), + value: n.node.id as string, + type: n.node.entity_type as string, + })), + )(typedData) as StixCoreObjectOption[]; + + const finalResults = [...results]; + if (shouldShowCreateOption(results.length)) { + const creationType = getCreationType(); + const createLabel = creationType + ? `${t_i18n('Create')} ${t_i18n(`entity_${creationType}`)}` + : t_i18n('Create'); + + finalResults.push({ + label: createLabel, + value: CREATE_OPTION_VALUE, + type: 'create', + isCreateOption: true, + }); + } + + setStixCoreObjects(finalResults); + }); + } + }, [currentSearchTerm, getCreationType, multiple, name, searchScope, setFieldValue, shouldShowCreateOption, t_i18n, types, values]); + + const handleSCOEntityCreated = useCallback((createdObservable?: CreatedEntity | null) => { + entityCreatedSuccessfully.current = true; + setOpenSCOCreation(false); + setCreateEntityType(null); + setValueBeforeCreate(null); + + if (createdObservable?.id && createdObservable?.entity_type) { + const entityLabel = createdObservable.representative?.main + || createdObservable.observable_value + || createdObservable.name + || createdObservable.id; + const newOption: StixCoreObjectOption = { + label: entityLabel, + value: createdObservable.id, + type: createdObservable.entity_type, + }; + + setStixCoreObjects((prev) => [newOption, ...prev.filter((o) => !o.isCreateOption)]); + + const currentValue = values[name] as StixCoreObjectOption | StixCoreObjectOption[] | null; + if (multiple) { + const currentArray = Array.isArray(currentValue) ? currentValue : []; + setFieldValue(name, [...currentArray, newOption]); + } else { + setFieldValue(name, newOption); + } + } else { + fetchQuery(stixCoreObjectsFieldSearchQuery, { + search: currentSearchTerm, + types: types ?? searchScope[name] ?? [], + }) + .toPromise() + .then((data: unknown) => { + const typedData = data as { stixCoreObjects?: { edges?: Array<{ node: Record }> } }; + const results = R.pipe( + R.pathOr([], ['stixCoreObjects', 'edges']), + R.map((n: { node: Record }) => ({ + label: getMainRepresentative(n.node), + value: n.node.id as string, + type: n.node.entity_type as string, + })), + )(typedData) as StixCoreObjectOption[]; + + const finalResults = [...results]; + if (shouldShowCreateOption(results.length)) { + const creationType = getCreationType(); + const createLabel = creationType + ? `${t_i18n('Create')} ${t_i18n(`entity_${creationType}`)}` + : t_i18n('Create'); + + finalResults.push({ + label: createLabel, + value: CREATE_OPTION_VALUE, + type: 'create', + isCreateOption: true, + }); + } + + setStixCoreObjects(finalResults); + }); + } + }, [currentSearchTerm, getCreationType, multiple, name, searchScope, setFieldValue, shouldShowCreateOption, t_i18n, types, values]); + + const searchStixCoreObjects = useCallback((event: React.SyntheticEvent | null, newInputValue?: string, reason?: string) => { + const searchValue = newInputValue ?? ''; + + if (reason === 'input' || reason === undefined) { + setCurrentSearchTerm(searchValue); + } + + fetchQuery(stixCoreObjectsFieldSearchQuery, { + search: searchValue, + types: types ?? searchScope[name] ?? [], + }) + .toPromise() + .then((data: unknown) => { + const typedData = data as { stixCoreObjects?: { edges?: Array<{ node: Record }> } }; + const results = R.pipe( + R.pathOr([], ['stixCoreObjects', 'edges']), + R.map((n: { node: Record }) => ({ + label: getMainRepresentative(n.node), + value: n.node.id as string, + type: n.node.entity_type as string, + })), + )(typedData) as StixCoreObjectOption[]; + + const finalResults = [...results]; + if (shouldShowCreateOption(results.length)) { + const creationType = getCreationType(); + const createLabel = creationType + ? `${t_i18n('Create')} ${t_i18n(`entity_${creationType}`)}` + : t_i18n('Create'); + + finalResults.push({ + label: createLabel, + value: CREATE_OPTION_VALUE, + type: 'create', + isCreateOption: true, + }); + } + + setStixCoreObjects(finalResults); + }); + }, [getCreationType, name, searchScope, shouldShowCreateOption, t_i18n, types]); + + const entitiesTypes = R.pipe( + R.map((n: string) => ({ + label: t_i18n(displayEntityTypeForTranslation(n)), + value: n, + type: n, + })), + R.sortWith([R.ascend(R.prop('label'))]), + R.filter((type: { value: string }) => (types ? types.includes(type.value) : true)), + )(entityTypes); + + const handleChange = useCallback(( + fieldName: string, + value: StixCoreObjectOption | StixCoreObjectOption[] | null, + ) => { + if (!value) { + setFieldValue(fieldName, value); + return; + } + + if (Array.isArray(value)) { + const createOptionSelected = value.find((v) => v.value === CREATE_OPTION_VALUE); + if (createOptionSelected) { + const filteredValue = value.filter((v) => v.value !== CREATE_OPTION_VALUE); + setValueBeforeCreate(filteredValue); + setFieldValue(fieldName, filteredValue); + handleOpenCreation(); + return; + } + } else if (value.value === CREATE_OPTION_VALUE) { + const currentValue = values[fieldName] as StixCoreObjectOption | StixCoreObjectOption[] | null; + setValueBeforeCreate(currentValue); + handleOpenCreation(); + return; + } + + setFieldValue(fieldName, value); + }, [handleOpenCreation, setFieldValue, values]); + + return ( + <> + + {!disableCreation && ( + + + + )} + + 0 ? 'secondary' : 'primary'} + /> + + + + {entitiesTypes.map((entityType) => ( + handleToggleSearchScope(name, entityType.value)} + > + + + + ))} + + + + )} + groupBy={(option: StixCoreObjectOption) => option.isCreateOption ? '' : option.type} + noOptionsText={t_i18n('No available options')} + options={stixCoreObjects} + onInputChange={searchStixCoreObjects} + onChange={handleChange} + filterOptions={(options: StixCoreObjectOption[]) => options} + renderOption={(innerProps: React.HTMLAttributes, option: StixCoreObjectOption) => { + if (option.isCreateOption) { + return ( +
  • +
    + +
    +
    + {option.label} +
    +
  • + ); + } + return ( +
  • +
    + +
    +
    {option.label}
    +
  • + ); + }} + isOptionEqualToValue={(option: StixCoreObjectOption, value: FieldOption) => option.value === value.value} + classes={{ clearIndicator: classes.autoCompleteIndicator }} + /> + + { + if (entityCreatedSuccessfully.current) { + setOpenSDOCreation(false); + setCreateEntityType(null); + setValueBeforeCreate(null); + return; + } + setOpenSDOCreation(false); + setCreateEntityType(null); + if (valueBeforeCreate !== null) { + setFieldValue(name, valueBeforeCreate); + } + setValueBeforeCreate(null); + }} + speeddial={true} + stixDomainObjectTypes={createEntityType ? [createEntityType] : (types ?? undefined)} + inputValue={currentSearchTerm} + creationCallback={handleSDOEntityCreated} + confidence={undefined} + defaultCreatedBy={undefined} + defaultMarkingDefinitions={undefined} + paginationKey={undefined} + paginationOptions={undefined} + onCompleted={undefined} + isFromBulkRelation={false} + /> + + {openSCOCreation && ( + { + if (entityCreatedSuccessfully.current) { + setOpenSCOCreation(false); + setCreateEntityType(null); + setValueBeforeCreate(null); + return; + } + setOpenSCOCreation(false); + setCreateEntityType(null); + if (valueBeforeCreate !== null) { + setFieldValue(name, valueBeforeCreate); + } + setValueBeforeCreate(null); + }} + contextual={true} + speeddial={true} + type={createEntityType ?? undefined} + inputValue={currentSearchTerm} + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore JSX component without proper TypeScript types + stixCyberObservableTypes={types || undefined} + onCompleted={handleSCOEntityCreated} + /> + )} + + ); +}; + +export default StixCoreObjectsField; diff --git a/opencti-platform/opencti-front/src/private/components/data/forms/Form.d.ts b/opencti-platform/opencti-front/src/private/components/data/forms/Form.d.ts index d0be06333a9c..421d9c176354 100644 --- a/opencti-platform/opencti-front/src/private/components/data/forms/Form.d.ts +++ b/opencti-platform/opencti-front/src/private/components/data/forms/Form.d.ts @@ -31,6 +31,7 @@ export interface AdditionalEntity { minAmount?: number; // For multiple entities, minimum required instances required?: boolean; // For non-multiple entities, whether it's required lookup?: boolean; // Whether this is an entity lookup (select existing entities) + disableCreation?: boolean; // Whether to disable on-the-fly entity creation in lookup mode fieldMode?: 'multiple' | 'parsed'; // Whether to have multiple fields or parse a single field parseField?: 'text' | 'textarea'; // Type of field when using parsed mode parseMode?: 'comma' | 'line'; // How to parse the field (comma-separated or line-by-line) @@ -56,6 +57,7 @@ export interface FormBuilderData { allowDraftOverride: boolean; // Whether users can override the draft setting mainEntityMultiple: boolean; // Whether main entity allows multiple mainEntityLookup?: boolean; // Whether main entity is an entity lookup (select existing entities) + mainEntityDisableCreation?: boolean; // Whether to disable on-the-fly entity creation in main entity lookup mode mainEntityFieldMode?: 'multiple' | 'parsed'; // Whether to have multiple fields or parse a single field mainEntityParseField?: 'text' | 'textarea'; // Type of field when using parsed mode for main entity mainEntityParseMode?: 'comma' | 'line'; // How to parse the field for main entity @@ -92,6 +94,7 @@ export interface FormSchemaDefinition { allowDraftOverride?: boolean; // Whether users can override the draft setting mainEntityMultiple?: boolean; mainEntityLookup?: boolean; + mainEntityDisableCreation?: boolean; // Whether to disable on-the-fly entity creation in main entity lookup mode mainEntityFieldMode?: 'multiple' | 'parsed'; mainEntityParseField?: 'text' | 'textarea'; mainEntityParseMode?: 'comma' | 'line'; diff --git a/opencti-platform/opencti-front/src/private/components/data/forms/FormSchemaEditor.tsx b/opencti-platform/opencti-front/src/private/components/data/forms/FormSchemaEditor.tsx index cecb91d185d4..1a64d57a914a 100644 --- a/opencti-platform/opencti-front/src/private/components/data/forms/FormSchemaEditor.tsx +++ b/opencti-platform/opencti-front/src/private/components/data/forms/FormSchemaEditor.tsx @@ -1,6 +1,6 @@ import React, { FunctionComponent, useState, useMemo, useCallback, useEffect } from 'react'; import makeStyles from '@mui/styles/makeStyles'; -import { Add, DeleteOutlined, AddCircleOutlined } from '@mui/icons-material'; +import { Add, DeleteOutlined, AddCircleOutlined, ArrowUpward, ArrowDownward } from '@mui/icons-material'; import { Box, IconButton, MenuItem, Tab, Tabs, Typography, TextField, Alert, Button, Select, FormControl, InputLabel, Switch, FormControlLabel } from '@mui/material'; import { useFormatter } from '../../../../components/i18n'; import type { Theme } from '../../../../components/Theme'; @@ -362,6 +362,66 @@ const FormSchemaEditor: FunctionComponent = ({ })); }; + const handleMoveFieldUp = (entityId: string, fieldId: string) => { + updateFormData((prev) => { + // Get fields for this entity in their current order + const entityFields = prev.fields.filter((f) => f.attributeMapping.entity === entityId); + const otherFields = prev.fields.filter((f) => f.attributeMapping.entity !== entityId); + + // Find the index of the field within entity fields + const fieldIndex = entityFields.findIndex((f) => f.id === fieldId); + if (fieldIndex <= 0) return prev; // Can't move up if already at top + + // Swap with the previous field + const newEntityFields = [...entityFields]; + [newEntityFields[fieldIndex - 1], newEntityFields[fieldIndex]] = [newEntityFields[fieldIndex], newEntityFields[fieldIndex - 1]]; + + // Reconstruct fields array maintaining entity grouping + return { + ...prev, + fields: [...otherFields, ...newEntityFields].sort((a, b) => { + // Keep entity groups together, but use new order within each group + if (a.attributeMapping.entity === b.attributeMapping.entity) { + const aIdx = newEntityFields.findIndex((f) => f.id === a.id); + const bIdx = newEntityFields.findIndex((f) => f.id === b.id); + if (aIdx !== -1 && bIdx !== -1) return aIdx - bIdx; + } + return 0; + }), + }; + }); + }; + + const handleMoveFieldDown = (entityId: string, fieldId: string) => { + updateFormData((prev) => { + // Get fields for this entity in their current order + const entityFields = prev.fields.filter((f) => f.attributeMapping.entity === entityId); + const otherFields = prev.fields.filter((f) => f.attributeMapping.entity !== entityId); + + // Find the index of the field within entity fields + const fieldIndex = entityFields.findIndex((f) => f.id === fieldId); + if (fieldIndex < 0 || fieldIndex >= entityFields.length - 1) return prev; // Can't move down if already at bottom + + // Swap with the next field + const newEntityFields = [...entityFields]; + [newEntityFields[fieldIndex], newEntityFields[fieldIndex + 1]] = [newEntityFields[fieldIndex + 1], newEntityFields[fieldIndex]]; + + // Reconstruct fields array maintaining entity grouping + return { + ...prev, + fields: [...otherFields, ...newEntityFields].sort((a, b) => { + // Keep entity groups together, but use new order within each group + if (a.attributeMapping.entity === b.attributeMapping.entity) { + const aIdx = newEntityFields.findIndex((f) => f.id === a.id); + const bIdx = newEntityFields.findIndex((f) => f.id === b.id); + if (aIdx !== -1 && bIdx !== -1) return aIdx - bIdx; + } + return 0; + }), + }; + }); + }; + const renderRelationshipField = (field: FormFieldAttribute, index: number, relationshipIndex: number) => { const fieldPath = `relationships.${relationshipIndex}.fields.${index}`; // Available field types for relationships - exclude checkbox, select, multiselect @@ -495,8 +555,11 @@ const FormSchemaEditor: FunctionComponent = ({ ); }; - const renderField = (field: FormFieldAttribute, index: number, entityType: string) => { + const renderField = (field: FormFieldAttribute, index: number, entityType: string, entityFields: FormFieldAttribute[]) => { const fieldIndex = formData.fields.findIndex((f) => f.id === field.id); + const entityId = field.attributeMapping.entity; + const isFirstInEntity = index === 0; + const isLastInEntity = index === entityFields.length - 1; // Get all attributes for this entity type (not filtered by field type yet) const entity = entityTypes.find((e) => e.value === entityType); @@ -611,14 +674,32 @@ const FormSchemaEditor: FunctionComponent = ({ {field.isMandatory ? `${t_i18n('Field')} ${index + 1} (${t_i18n('Mandatory')})` : `${t_i18n('Field')} ${index + 1}`} - {(!field.isMandatory || isInParsedMode) && ( +
    handleRemoveField(field.id)} + onClick={() => handleMoveFieldUp(entityId, field.id)} + disabled={isFirstInEntity} + title={t_i18n('Move up')} > - + - )} + handleMoveFieldDown(entityId, field.id)} + disabled={isLastInEntity} + title={t_i18n('Move down')} + > + + + {(!field.isMandatory || isInParsedMode) && ( + handleRemoveField(field.id)} + > + + + )} +
    @@ -990,6 +1071,19 @@ const FormSchemaEditor: FunctionComponent = ({ style={{ marginTop: 20, display: 'block' }} /> + {entity.lookup && ( + handleFieldChange(`additionalEntities.${entityIndex}.disableCreation`, e.target.checked)} + /> + )} + label={t_i18n('Disable on-the-fly entity creation')} + style={{ marginTop: 10, marginLeft: 20, display: 'block' }} + /> + )} + = ({ {t_i18n('Fields')} - {entityFields.map((field, idx) => renderField(field, idx, entity.entityType))} + {entityFields.map((field, idx) => renderField(field, idx, entity.entityType, entityFields))}
    Date: Wed, 7 Jan 2026 14:27:23 +0100 Subject: [PATCH 048/126] [backend] SSE message backpressure mechanism (#13908) Co-authored-by: Julien Richard --- .../src/graphql/sseMiddleware.js | 118 ++++++++++-------- 1 file changed, 68 insertions(+), 50 deletions(-) diff --git a/opencti-platform/opencti-graphql/src/graphql/sseMiddleware.js b/opencti-platform/opencti-graphql/src/graphql/sseMiddleware.js index e17f0080e84a..456e41964f90 100644 --- a/opencti-platform/opencti-graphql/src/graphql/sseMiddleware.js +++ b/opencti-platform/opencti-graphql/src/graphql/sseMiddleware.js @@ -1,5 +1,5 @@ +import { once } from 'events'; import * as jsonpatch from 'fast-json-patch'; -import { Promise } from 'bluebird'; import { LRUCache } from 'lru-cache'; import { now } from 'moment'; import conf, { basePath, logApp } from '../config/conf'; @@ -74,12 +74,8 @@ const createBroadcastClient = (channel) => { setChannelDelay: (d) => channel.setDelay(d), setLastEventId: (id) => channel.setLastEventId(id), close: () => channel.close(), - sendEvent: (eventId, topic, event) => { - channel.sendEvent(eventId, topic, event); - }, - sendConnected: (streamInfo) => { - channel.sendEvent(undefined, 'connected', streamInfo); - }, + sendEvent: async (eventId, topic, event) => channel.sendEvent(eventId, topic, event), + sendConnected: async (streamInfo) => channel.sendEvent(undefined, 'connected', streamInfo), }; }; @@ -235,23 +231,56 @@ const createSseMiddleware = () => { const initBroadcasting = async (req, res, client, processor) => { const broadcasterInfo = processor ? await processor.info() : {}; - req.on('close', () => { + let closed = false; + const close = () => { + if (closed) { + return; + } client.close(); delete broadcastClients[client.id]; logApp.info(`[STREAM] Closing stream processor for ${client.id}`); processor.shutdown(); - }); + closed = true; + }; + req.on('close', close); // On closing the request + res.on('close', close); // On closing the response res.writeHead(200, { Connection: 'keep-alive', 'Content-Type': 'text/event-stream; charset=utf-8', 'Access-Control-Allow-Origin': '*', 'Cache-Control': 'no-cache, no-transform', // no-transform is required for dev proxy }); - client.sendConnected({ ...broadcasterInfo, connectionId: client.id }); broadcastClients[client.id] = client; + await client.sendConnected({ ...broadcasterInfo, connectionId: client.id }); }; + const createSseChannel = (req, res, startId) => { let lastEventId = startId; + + const buildMessage = (eventId, topic, event) => { + let message = ''; + if (eventId) { + message += `id: ${eventId}\n`; + } + if (topic) { + message += `event: ${topic}\n`; + } + if (event) { + message += 'data: '; + const isDataTopic = eventId && topic !== 'heartbeat'; + if (isDataTopic && req.user && !isUserHasCapability(req.user, KNOWLEDGE_ORGANIZATION_RESTRICT)) { + const filtered = { ...event }; + delete filtered.data.extensions[STIX_EXT_OCTI].granted_refs; + message += JSON.stringify(filtered); + } else { + message += JSON.stringify(event); + } + message += '\n'; + } + message += '\n'; + return message; + }; + const channel = { id: generateInternalId(), delay: parseInt(extractQueryParameter(req, 'delay') || req.headers['event-delay'] || 0, 10), @@ -266,34 +295,18 @@ const createSseMiddleware = () => { setLastEventId: (id) => { lastEventId = id; }, - connected: () => !res.finished, - sendEvent: (eventId, topic, event) => { + connected: () => !res.finished && res.writable, + sendEvent: async (eventId, topic, event) => { // Write on an already terminated response if (res.finished || !res.writable) { return; } - let message = ''; - if (eventId) { - lastEventId = eventId; - message += `id: ${eventId}\n`; + lastEventId = eventId || lastEventId; + const message = buildMessage(eventId, topic, event); + if (!res.write(message)) { + logApp.debug('[STREAM] Buffer draining', { buffer: res.writableLength, limit: res.writableHighWaterMark }); + await once(res, 'drain'); } - if (topic) { - message += `event: ${topic}\n`; - } - if (event) { - message += 'data: '; - const isDataTopic = eventId && topic !== 'heartbeat'; - if (isDataTopic && req.user && !isUserHasCapability(req.user, KNOWLEDGE_ORGANIZATION_RESTRICT)) { - const filtered = { ...event }; - delete filtered.data.extensions[STIX_EXT_OCTI].granted_refs; - message += JSON.stringify(filtered); - } else { - message += JSON.stringify(event); - } - message += '\n'; - } - message += '\n'; - res.write(message); res.flush(); }, close: () => { @@ -309,11 +322,17 @@ const createSseMiddleware = () => { } }, }; - const heartTimer = () => { - if (lastEventId) { - const [idTime] = lastEventId.split('-'); - const idDate = utcDate(parseInt(idTime, 10)).toISOString(); - channel.sendEvent(lastEventId, 'heartbeat', idDate); + const heartTimer = async () => { + try { + // heartbeat must be sent to maintain the connection + // Only when the last event is accessible and nothing is currently in the socket. + if (lastEventId && res.writableLength === 0) { + const [idTime] = lastEventId.split('-'); + const idDate = utcDate(parseInt(idTime, 10)).toISOString(); + await channel.sendEvent(lastEventId, 'heartbeat', idDate); + } + } catch { + // ignore } }; const heartbeatInterval = setInterval(heartTimer, HEARTBEAT_PERIOD); @@ -338,7 +357,7 @@ const createSseMiddleware = () => { const { id: eventId, event, data } = elements[index]; const instanceAccessible = await isUserCanAccessStixElement(context, user, data.data); if (instanceAccessible) { - client.sendEvent(eventId, event, data); + await client.sendEvent(eventId, event, data); } } client.setLastEventId(lastEventId); @@ -395,7 +414,7 @@ const createSseMiddleware = () => { const message = generateCreateMessage(missingInstance); const origin = { referer: EVENT_TYPE_DEPENDENCIES }; const content = { data: missingData, message, origin, version: EVENT_CURRENT_VERSION }; - channel.sendEvent(eventId, EVENT_TYPE_CREATE, content); + await channel.sendEvent(eventId, EVENT_TYPE_CREATE, content); cache.set(missingData.id, 'hit'); await wait(channel.delay); } @@ -421,7 +440,7 @@ const createSseMiddleware = () => { const message = generateCreateMessage(missingRelation); const origin = { referer: EVENT_TYPE_DEPENDENCIES }; const content = { data: stixRelation, message, origin, version: EVENT_CURRENT_VERSION }; - channel.sendEvent(eventId, EVENT_TYPE_CREATE, content); + await channel.sendEvent(eventId, EVENT_TYPE_CREATE, content); cache.set(stixRelation.id, 'hit'); } } @@ -447,7 +466,6 @@ const createSseMiddleware = () => { const entityTypeFilters = findFiltersFromKey(filters.filters, 'entity_type', 'eq'); const entityTypeFilter = entityTypeFilters.length > 0 ? entityTypeFilters[0] : undefined; const entityTypeFilterValues = entityTypeFilter?.values ?? []; - // eslint-disable-next-line no-restricted-syntax for (const id of entityTypeFilterValues) { // consider the operator if (entityTypeFilter.operator === 'not_eq') { @@ -501,7 +519,7 @@ const createSseMiddleware = () => { // From or to are visible, consider it as a dependency const origin = { referer: EVENT_TYPE_DEPENDENCIES }; const content = { data: stix, message, origin, version: EVENT_CURRENT_VERSION }; - channel.sendEvent(eventId, type, content); + await channel.sendEvent(eventId, type, content); } } }; @@ -600,18 +618,18 @@ const createSseMiddleware = () => { const { newDocument: previous } = jsonpatch.applyPatch(structuredClone(stix), evenContext.reverse_patch); const isPreviouslyVisible = await isStixMatchFilterGroup(context, user, previous, streamFilters); if (isPreviouslyVisible && !isCurrentlyVisible && publishDeletion) { // No longer visible - client.sendEvent(eventId, EVENT_TYPE_DELETE, eventData); + await client.sendEvent(eventId, EVENT_TYPE_DELETE, eventData); cache.set(stix.id, 'hit'); } else if (!isPreviouslyVisible && isCurrentlyVisible) { // Newly visible const isValidResolution = await resolveAndPublishDependencies(context, noDependencies, cache, channel, req, eventId, stix); if (isValidResolution) { - client.sendEvent(eventId, EVENT_TYPE_CREATE, eventData); + await client.sendEvent(eventId, EVENT_TYPE_CREATE, eventData); cache.set(stix.id, 'hit'); } } else if (isCurrentlyVisible) { // Just an update const isValidResolution = await resolveAndPublishDependencies(context, noDependencies, cache, channel, req, eventId, stix); if (isValidResolution) { - client.sendEvent(eventId, event, eventData); + await client.sendEvent(eventId, event, eventData); cache.set(stix.id, 'hit'); } } else if (isRelation && publishDependencies) { // Update but not visible - relation type @@ -630,20 +648,20 @@ const createSseMiddleware = () => { // At least one container is matching the filter, so publishing the event if (countRelatedContainers > 0) { await resolveAndPublishMissingRefs(context, cache, channel, req, eventId, stix); - client.sendEvent(eventId, event, eventData); + await client.sendEvent(eventId, event, eventData); cache.set(stix.id, 'hit'); } } } else if (isCurrentlyVisible) { if (type === EVENT_TYPE_DELETE) { if (publishDeletion) { - client.sendEvent(eventId, event, eventData); + await client.sendEvent(eventId, event, eventData); cache.set(stix.id, 'hit'); } } else { // Create and merge const isValidResolution = await resolveAndPublishDependencies(context, noDependencies, cache, channel, req, eventId, stix); if (isValidResolution) { - client.sendEvent(eventId, event, eventData); + await client.sendEvent(eventId, event, eventData); cache.set(stix.id, 'hit'); } } @@ -689,7 +707,7 @@ const createSseMiddleware = () => { const message = generateCreateMessage(instance); const origin = { referer: EVENT_TYPE_INIT }; const eventData = { data: stixData, message, origin, version: EVENT_CURRENT_VERSION }; - channel.sendEvent(eventId, EVENT_TYPE_CREATE, eventData); + await channel.sendEvent(eventId, EVENT_TYPE_CREATE, eventData); cache.set(stixData.id, 'hit'); } } else { From 69ef01a6523641f1a3799fd53624789fe3b07e58 Mon Sep 17 00:00:00 2001 From: "A. Jard" Date: Wed, 7 Jan 2026 15:14:49 +0100 Subject: [PATCH 049/126] [ci] Add tag EE/CE on end to end tests (#13770) --- .../tests_e2e/artifact/createArtifact.spec.ts | 2 +- .../backgroundTask/_backgroundTask.spec.ts | 4 +- .../backgroundTask/backgroundTask.spec.ts | 5 +- .../tests_e2e/dashboard/dashboard.spec.ts | 2 +- .../dashboard/dashboardRestriction.spec.ts | 2 +- .../tests_e2e/drafts/restrictedDrafts.spec.ts | 2 +- .../addReportObservable.spec.ts | 8 +- .../createIncidentRelationship.spec.ts | 2 +- .../createExternalReference.spec.ts | 2 +- .../tests_e2e/filters/addFilter.spec.ts | 2 +- .../groupings/createGrouping.spec.ts | 2 +- .../incidentResponse/incidentResponse.spec.ts | 8 +- .../createInfrastructureRelationship.spec.ts | 2 +- .../createIntrusionSetRelationship.spec.ts | 2 +- .../createInvestigation.spec.ts | 2 +- .../updateInvestigation.spec.ts | 2 +- .../tests_e2e/navigation/navigation.spec.ts | 290 +++++++++--------- .../tests_e2e/notes/createNote.spec.ts | 2 +- .../opencti-front/tests_e2e/pir/pir.spec.ts | 2 +- .../tests_e2e/report/report.spec.ts | 4 +- .../opencti-front/tests_e2e/rfis/rfis.spec.ts | 2 +- .../opencti-front/tests_e2e/rfts/rfts.spec.ts | 2 +- .../tests_e2e/settings/entitySettings.spec.ts | 2 +- .../tests_e2e/settings/parameters.spec.ts | 2 +- .../tests_e2e/settings/themes.spec.ts | 6 +- .../opencti-front/tests_e2e/z_logout.spec.ts | 2 +- 26 files changed, 183 insertions(+), 180 deletions(-) diff --git a/opencti-platform/opencti-front/tests_e2e/artifact/createArtifact.spec.ts b/opencti-platform/opencti-front/tests_e2e/artifact/createArtifact.spec.ts index 1e563085809d..4f4d5f1c2394 100644 --- a/opencti-platform/opencti-front/tests_e2e/artifact/createArtifact.spec.ts +++ b/opencti-platform/opencti-front/tests_e2e/artifact/createArtifact.spec.ts @@ -2,7 +2,7 @@ import ArtifactPage from '../model/Artifact.pageModel'; import ArtifactImportPage from '../model/ArtifactImport.pageModel'; import { expect, test } from '../fixtures/baseFixtures'; -test('Artifact error message in the absence of a file.', async ({ page }) => { +test('Artifact error message in the absence of a file.', { tag: ['@ce'] }, async ({ page }) => { const artifactPage = new ArtifactPage(page); const artifactImport = new ArtifactImportPage(page); await page.goto('/dashboard/observations/artifacts'); diff --git a/opencti-platform/opencti-front/tests_e2e/backgroundTask/_backgroundTask.spec.ts b/opencti-platform/opencti-front/tests_e2e/backgroundTask/_backgroundTask.spec.ts index 6a891fd0fb39..a85dc5ebd6d2 100644 --- a/opencti-platform/opencti-front/tests_e2e/backgroundTask/_backgroundTask.spec.ts +++ b/opencti-platform/opencti-front/tests_e2e/backgroundTask/_backgroundTask.spec.ts @@ -9,7 +9,7 @@ import { runBackgroundTaskOnIncidentByFilter, runBackgroundTaskOnIncidentBySearc * @param page */ -test('Verify background tasks pre-requisites on incident search', async ({ page }) => { +test('Verify background tasks pre-requisites on incident search', { tag: ['@ce'] }, async ({ page }) => { const incidentPage = new EventsIncidentPage(page); await incidentPage.goto(); @@ -19,6 +19,6 @@ test('Verify background tasks pre-requisites on incident search', async ({ page await runBackgroundTaskOnIncidentBySearch(page, true); }); -test('Verify background tasks pre-requisites on data entity search', async ({ page }) => { +test('Verify background tasks pre-requisites on data entity search', { tag: ['@ce'] }, async ({ page }) => { await searchOnDataEntitiesPerLabels(page, true); }); diff --git a/opencti-platform/opencti-front/tests_e2e/backgroundTask/backgroundTask.spec.ts b/opencti-platform/opencti-front/tests_e2e/backgroundTask/backgroundTask.spec.ts index bf89e93fc31e..7185ac5df533 100644 --- a/opencti-platform/opencti-front/tests_e2e/backgroundTask/backgroundTask.spec.ts +++ b/opencti-platform/opencti-front/tests_e2e/backgroundTask/backgroundTask.spec.ts @@ -20,7 +20,7 @@ import { runBackgroundTaskOnIncidentByFilter, runBackgroundTaskOnIncidentBySearc * @param page */ -const waitAndRefreshUntilFirstTaskInStatus = async (page:Page, tasksPage: DataProcessingTasksPage, status: string, expectVisible: boolean) => { +const waitAndRefreshUntilFirstTaskInStatus = async (page: Page, tasksPage: DataProcessingTasksPage, status: string, expectVisible: boolean) => { await tasksPage.goto(); await expect(tasksPage.getPage()).toBeVisible(); @@ -42,13 +42,12 @@ const waitAndRefreshUntilFirstTaskInStatus = async (page:Page, tasksPage: DataPr let isStatusOk = await isOneStatusTaskOk(); while (!isStatusOk && loopCurrent < loopCount) { - // eslint-disable-next-line no-await-in-loop isStatusOk = await isOneStatusTaskOk(); loopCurrent += 1; } }; -test('Verify background tasks execution', { tag: ['@mutation', '@incident', '@task', '@filter'] }, async ({ page }) => { +test('Verify background tasks execution', { tag: ['@ce', '@mutation'] }, async ({ page }) => { const incidentPage = new EventsIncidentPage(page); const tasksPage = new DataProcessingTasksPage(page); diff --git a/opencti-platform/opencti-front/tests_e2e/dashboard/dashboard.spec.ts b/opencti-platform/opencti-front/tests_e2e/dashboard/dashboard.spec.ts index e170073e70b1..b566257b17a5 100644 --- a/opencti-platform/opencti-front/tests_e2e/dashboard/dashboard.spec.ts +++ b/opencti-platform/opencti-front/tests_e2e/dashboard/dashboard.spec.ts @@ -25,7 +25,7 @@ test.describe.configure({ mode: 'serial' }); * Export/Import a dashboard. * Create Widget - see values - Delete Widget */ -test('Dashboard CRUD', async ({ page }) => { +test('Dashboard CRUD', { tag: ['@ce'] }, async ({ page }) => { const leftBarPage = new LeftBarPage(page); const dashboardPage = new DashboardPage(page); const dashboardForm = new DashboardFormPage(page, 'Create dashboard'); diff --git a/opencti-platform/opencti-front/tests_e2e/dashboard/dashboardRestriction.spec.ts b/opencti-platform/opencti-front/tests_e2e/dashboard/dashboardRestriction.spec.ts index a92b217afbb7..ec0da69573f8 100644 --- a/opencti-platform/opencti-front/tests_e2e/dashboard/dashboardRestriction.spec.ts +++ b/opencti-platform/opencti-front/tests_e2e/dashboard/dashboardRestriction.spec.ts @@ -17,7 +17,7 @@ import DashboardWidgetsPageModel from '../model/DashboardWidgets.pageModel'; import DashboardDetailsPage from '../model/dashboardDetails.pageModel'; import AccessRestrictionPageModel from '../model/AccessRestriction.pageModel'; -test('Dashboard restriction access', async ({ page }) => { +test('Dashboard restriction access', { tag: ['@ce'] }, async ({ page }) => { const leftBar = new LeftBarPage(page); const topBar = new TopMenuProfilePage(page); const dashboardPage = new DashboardPage(page); diff --git a/opencti-platform/opencti-front/tests_e2e/drafts/restrictedDrafts.spec.ts b/opencti-platform/opencti-front/tests_e2e/drafts/restrictedDrafts.spec.ts index bae10f462093..5f0752b9cd11 100644 --- a/opencti-platform/opencti-front/tests_e2e/drafts/restrictedDrafts.spec.ts +++ b/opencti-platform/opencti-front/tests_e2e/drafts/restrictedDrafts.spec.ts @@ -2,7 +2,7 @@ import DraftsPage from 'tests_e2e/model/drafts.pageModel'; import RestrictionsPage from 'tests_e2e/model/restrictions.pageModel'; import { expect, test } from '../fixtures/baseFixtures'; -test.describe('Restricted Drafts', () => { +test.describe('Restricted Drafts', { tag: ['@ce'] }, () => { test('should allow to remove restrictions on a draft', async ({ page }) => { const draftName = `Restricted Draft E2E - ${Date.now()}`; diff --git a/opencti-platform/opencti-front/tests_e2e/enableReferences/addReportObservable.spec.ts b/opencti-platform/opencti-front/tests_e2e/enableReferences/addReportObservable.spec.ts index f4ff55509de6..da201099f79c 100644 --- a/opencti-platform/opencti-front/tests_e2e/enableReferences/addReportObservable.spec.ts +++ b/opencti-platform/opencti-front/tests_e2e/enableReferences/addReportObservable.spec.ts @@ -28,7 +28,7 @@ const noBypassUserName = `NoBypassReferencesUser ${nowTime}`; const noBypassRoleName = `NoBypassReferencesRole ${nowTime}`; const noBypassGroupName = `NoBypassReferencesTestGroup ${nowTime}`; -test.describe('Create user with no references bypass capabilities', () => { +test.describe('Create user with no references bypass capabilities', { tag: ['@ce'] }, () => { test('Create basic user role', async ({ page }) => { const rolesSettingsPage = new RolesSettingsPage(page); const rolePage = new RolePage(page); @@ -93,7 +93,7 @@ test.describe('Create user with no references bypass capabilities', () => { }); }); -test.describe('Authenticate no bypass user', () => { +test.describe('Authenticate no bypass user', { tag: ['@ce'] }, () => { test.use({ storageState: { cookies: [], origins: [] } }); test('Authenticate basic user', async ({ page }) => { const dashboardPage = new DashboardPage(page); @@ -107,7 +107,7 @@ test.describe('Authenticate no bypass user', () => { }); }); -test('Add and remove observable from Observables tab of a Report as Admin user', async ({ page }) => { +test('Add and remove observable from Observables tab of a Report as Admin user', { tag: ['@ce'] }, async ({ page }) => { const reportPage = new ReportPage(page); const reportDetailsPage = new ReportDetailsPage(page); const reportForm = new ReportFormPage(page); @@ -158,7 +158,7 @@ test('Add and remove observable from Observables tab of a Report as Admin user', await page.locator('span').filter({ hasText: 'Enforce references' }).click(); }); -test.describe('Add and remove observable from Observables tab of a Report as noBypass user', () => { +test.describe('Add and remove observable from Observables tab of a Report as noBypass user', { tag: ['@ce'] }, () => { test.use({ storageState: noBypassUserAuthFile }); test('Run test as noBypass user', async ({ page }) => { const reportPage = new ReportPage(page); diff --git a/opencti-platform/opencti-front/tests_e2e/eventsIncident/createIncidentRelationship.spec.ts b/opencti-platform/opencti-front/tests_e2e/eventsIncident/createIncidentRelationship.spec.ts index 98f16ac11866..967caf9e1ab6 100644 --- a/opencti-platform/opencti-front/tests_e2e/eventsIncident/createIncidentRelationship.spec.ts +++ b/opencti-platform/opencti-front/tests_e2e/eventsIncident/createIncidentRelationship.spec.ts @@ -4,7 +4,7 @@ import EventsIncidentPage from '../model/EventsIncident.pageModel'; import EventsIncidentFormPage from '../model/form/eventsIncidentForm.pageModel'; import EventsIncidentDetailsPage from '../model/EventsIncidentDetails.pageModel'; -test('Create a new relationship in incident knowledge', async ({ page }) => { +test('Create a new relationship in incident knowledge', { tag: ['@ce'] }, async ({ page }) => { const eventsIncidentPage = new EventsIncidentPage(page); const eventsIncidentForm = new EventsIncidentFormPage(page); const eventsIncidentDetailsPage = new EventsIncidentDetailsPage(page); diff --git a/opencti-platform/opencti-front/tests_e2e/externalReference/createExternalReference.spec.ts b/opencti-platform/opencti-front/tests_e2e/externalReference/createExternalReference.spec.ts index 9ee5808203e3..69d69bcfaaac 100644 --- a/opencti-platform/opencti-front/tests_e2e/externalReference/createExternalReference.spec.ts +++ b/opencti-platform/opencti-front/tests_e2e/externalReference/createExternalReference.spec.ts @@ -4,7 +4,7 @@ import ExternalReferenceFormPageModel from '../model/form/externalReferenceForm. import ExternalReferencePage from '../model/externalReference.pageModel'; import ExternalReferenceDetailsPage from '../model/externalReferenceDetails.pageModel'; -test('Create a new external reference', async ({ page }) => { +test('Create a new external reference', { tag: ['@ce'] }, async ({ page }) => { const externalReferencePage = new ExternalReferencePage(page); const externalReferenceForm = new ExternalReferenceFormPageModel(page); const externalReferenceDetails = new ExternalReferenceDetailsPage(page); diff --git a/opencti-platform/opencti-front/tests_e2e/filters/addFilter.spec.ts b/opencti-platform/opencti-front/tests_e2e/filters/addFilter.spec.ts index e61e03a4374c..dae7b9932a32 100644 --- a/opencti-platform/opencti-front/tests_e2e/filters/addFilter.spec.ts +++ b/opencti-platform/opencti-front/tests_e2e/filters/addFilter.spec.ts @@ -1,7 +1,7 @@ import { expect, test } from '../fixtures/baseFixtures'; import FiltersPageModel from '../model/filters.pageModel'; -test('Add a new filter in the observables list and check the filter is still present when we come back to the page', async ({ page }) => { +test('Add a new filter in the observables list and check the filter is still present when we come back to the page', { tag: ['@ce'] }, async ({ page }) => { await page.goto('/dashboard/observations/observables'); const filterUtils = new FiltersPageModel(page); await filterUtils.addEntityTypeFilter('Artifact'); diff --git a/opencti-platform/opencti-front/tests_e2e/groupings/createGrouping.spec.ts b/opencti-platform/opencti-front/tests_e2e/groupings/createGrouping.spec.ts index f6afc8c4b8d8..8ce550edab91 100644 --- a/opencti-platform/opencti-front/tests_e2e/groupings/createGrouping.spec.ts +++ b/opencti-platform/opencti-front/tests_e2e/groupings/createGrouping.spec.ts @@ -4,7 +4,7 @@ import GroupingsPage from '../model/grouping.pageModel'; import GroupingFormPage from '../model/form/groupingForm.pageModel'; import GroupingDetailsPage from '../model/groupingDetails.pageModel'; -test('Create a new grouping', async ({ page }) => { +test('Create a new grouping', { tag: ['@ce'] }, async ({ page }) => { const groupingsPage = new GroupingsPage(page); const groupingForm = new GroupingFormPage(page); const groupingDetails = new GroupingDetailsPage(page); diff --git a/opencti-platform/opencti-front/tests_e2e/incidentResponse/incidentResponse.spec.ts b/opencti-platform/opencti-front/tests_e2e/incidentResponse/incidentResponse.spec.ts index bc62629bf023..f122d573b89f 100644 --- a/opencti-platform/opencti-front/tests_e2e/incidentResponse/incidentResponse.spec.ts +++ b/opencti-platform/opencti-front/tests_e2e/incidentResponse/incidentResponse.spec.ts @@ -27,7 +27,7 @@ import EntitiesTabPageModel from '../model/EntitiesTab.pageModel'; * Delete incident response. * Check deletion. */ -test('Incident Response Creation', async ({ page }) => { +test('Incident Response Creation', { tag: ['@ce'] }, async ({ page }) => { await fakeDate(page, 'April 1 2024 12:00:00'); const leftNavigation = new LeftBarPage(page); const incidentResponsePage = new IncidentResponsePage(page); @@ -158,8 +158,8 @@ test('Incident Response Creation', async ({ page }) => { const revoked = incidentResponseDetailsPage.getTextForHeading('Revoked', 'NO'); await expect(revoked).toBeVisible(); - await expect( incidentResponseDetailsPage.overview.getLabel('campaign')).toBeVisible(); - await expect( incidentResponseDetailsPage.overview.getLabel('report')).toBeVisible(); + await expect(incidentResponseDetailsPage.overview.getLabel('campaign')).toBeVisible(); + await expect(incidentResponseDetailsPage.overview.getLabel('report')).toBeVisible(); const creators = incidentResponseDetailsPage.getTextForHeading('Creators', 'ADMIN'); await expect(creators).toBeVisible(); @@ -290,7 +290,7 @@ test('Incident Response Creation', async ({ page }) => { * Delete incident response by background task. * Check deletion. */ -test('Incident response live entities creation and relationships', async ({ page }) => { +test('Incident response live entities creation and relationships', { tag: ['@ce'] }, async ({ page }) => { const leftNavigation = new LeftBarPage(page); const toolbar = new ToolbarPageModel(page); const incidentResponsePage = new IncidentResponsePage(page); diff --git a/opencti-platform/opencti-front/tests_e2e/infrastructure/createInfrastructureRelationship.spec.ts b/opencti-platform/opencti-front/tests_e2e/infrastructure/createInfrastructureRelationship.spec.ts index 454aa615358b..fac7f60983a0 100644 --- a/opencti-platform/opencti-front/tests_e2e/infrastructure/createInfrastructureRelationship.spec.ts +++ b/opencti-platform/opencti-front/tests_e2e/infrastructure/createInfrastructureRelationship.spec.ts @@ -4,7 +4,7 @@ import InfrastructurePage from '../model/infrastructure.pageModel'; import InfrastructureFormPage from '../model/form/infrastructureForm.pageModel'; import InfrastructureDetailsPageModel from '../model/infrastructureDetails.pageModel'; -test('Create a new relationship in infrastructure knowledge', async ({ page }) => { +test('Create a new relationship in infrastructure knowledge', { tag: ['@ce'] }, async ({ page }) => { const infrastructurePage = new InfrastructurePage(page); const infrastructureForm = new InfrastructureFormPage(page); const infrastructureDetailsPage = new InfrastructureDetailsPageModel(page); diff --git a/opencti-platform/opencti-front/tests_e2e/intrusionSet/createIntrusionSetRelationship.spec.ts b/opencti-platform/opencti-front/tests_e2e/intrusionSet/createIntrusionSetRelationship.spec.ts index 28e190f8f50d..f9b971a35a53 100644 --- a/opencti-platform/opencti-front/tests_e2e/intrusionSet/createIntrusionSetRelationship.spec.ts +++ b/opencti-platform/opencti-front/tests_e2e/intrusionSet/createIntrusionSetRelationship.spec.ts @@ -4,7 +4,7 @@ import IntrusionSetFormPage from '../model/form/intrusionSetForm.pageModel'; import IntrusionSetDetailsPage from '../model/intrusionSetDetails.pageModel'; import StixCoreRelationshipCreationFromEntityFormPage from '../model/form/stixCoreRelationshipCreationFromEntityForm.pageModel'; -test('Create a new relationship in intrusion set knowledge', async ({ page }) => { +test('Create a new relationship in intrusion set knowledge', { tag: ['@ce'] }, async ({ page }) => { const intrusionSetPage = new IntrusionSetPage(page); const intrusionSetForm = new IntrusionSetFormPage(page); const intrusionSetDetailsPage = new IntrusionSetDetailsPage(page); diff --git a/opencti-platform/opencti-front/tests_e2e/investigations/createInvestigation.spec.ts b/opencti-platform/opencti-front/tests_e2e/investigations/createInvestigation.spec.ts index 7ec2af84b282..343ec5f7376c 100644 --- a/opencti-platform/opencti-front/tests_e2e/investigations/createInvestigation.spec.ts +++ b/opencti-platform/opencti-front/tests_e2e/investigations/createInvestigation.spec.ts @@ -3,7 +3,7 @@ import InvestigationDetailsPage from '../model/investigationDetails.pageModel'; import InvestigationsFormPage from '../model/form/investigationsForm.pageModel'; import InvestigationsPage from '../model/investigations.pageModel'; -test('Create a new investigations page', async ({ page }) => { +test('Create a new investigations page', { tag: ['@ce'] }, async ({ page }) => { const investigationsPage = new InvestigationsPage(page); const investigationDetailsPage = new InvestigationDetailsPage(page); const investigationsForm = new InvestigationsFormPage(page, 'Create investigation'); diff --git a/opencti-platform/opencti-front/tests_e2e/investigations/updateInvestigation.spec.ts b/opencti-platform/opencti-front/tests_e2e/investigations/updateInvestigation.spec.ts index 57f9e97eb53b..ff686b011bd5 100644 --- a/opencti-platform/opencti-front/tests_e2e/investigations/updateInvestigation.spec.ts +++ b/opencti-platform/opencti-front/tests_e2e/investigations/updateInvestigation.spec.ts @@ -3,7 +3,7 @@ import { expect, test } from '../fixtures/baseFixtures'; import InvestigationDetailsPage from '../model/investigationDetails.pageModel'; import InvestigationsFormPage from '../model/form/investigationsForm.pageModel'; -test('Create a new investigation page and test update', async ({ page }) => { +test('Create a new investigation page and test update', { tag: ['@ce'] }, async ({ page }) => { const investigationsPage = new InvestigationsPage(page); const investigationDetailsPage = new InvestigationDetailsPage(page); const investigationsForm = new InvestigationsFormPage(page, 'Create investigation'); diff --git a/opencti-platform/opencti-front/tests_e2e/navigation/navigation.spec.ts b/opencti-platform/opencti-front/tests_e2e/navigation/navigation.spec.ts index f69a832776a3..19eb60e5480f 100644 --- a/opencti-platform/opencti-front/tests_e2e/navigation/navigation.spec.ts +++ b/opencti-platform/opencti-front/tests_e2e/navigation/navigation.spec.ts @@ -1844,149 +1844,153 @@ const navigateAllMenu = async (page: Page) => { await leftBarPage.expectBreadcrumb('Investigations'); }; -test('Check navigation on all pages', { tag: ['@navigation'] }, async ({ page }) => { - await page.goto('/'); - const leftBarPage = new LeftBarPage(page); - await leftBarPage.open(); - - // For faster debugging, each navigated can be commented. - // so they should be all independent and start from the left menu. - await navigateAllMenu(page); -}); - -test('Check navigation on Analyses menu', async ({ page }) => { - await page.goto('/'); - const leftBarPage = new LeftBarPage(page); - await leftBarPage.open(); - await navigateReports(page); - await navigateGroupings(page); - await navigateMalwareAnalyses(page); - await navigateNotes(page); - await navigateExternalReferences(page); -}); - -test('Check navigation on Cases menu', async ({ page }) => { - await page.goto('/'); - const leftBarPage = new LeftBarPage(page); - await leftBarPage.open(); - await navigateIncidentResponse(page); - await navigateRfi(page); - await navigateRft(page); - await navigateTasks(page); - await navigateFeedbacks(page); -}); - -test('Check navigation on Events menu', async ({ page }) => { - await page.goto('/'); - const leftBarPage = new LeftBarPage(page); - await leftBarPage.open(); - await navigateEventsIncident(page); - await navigateSightings(page); - await navigateObservedData(page); -}); - -test('Check navigation on Observations menu', async ({ page }) => { - await page.goto('/'); - const leftBarPage = new LeftBarPage(page); - await leftBarPage.open(); - await navigateObservables(page); - await navigateArtifact(page); - await navigateIndicators(page); - await navigateInfrastructure(page); -}); - -test('Check navigation on Threats menu', async ({ page }) => { - await page.goto('/'); - const leftBarPage = new LeftBarPage(page); - await leftBarPage.open(); - await navigateIntrusionSet(page); - await navigateCampaign(page); - await navigateThreatActorGroup(page); - await navigateThreatActorIndividual(page); -}); - -test('Check navigation on Arsenal menu', async ({ page }) => { - await page.goto('/'); - const leftBarPage = new LeftBarPage(page); - await leftBarPage.open(); - await navigateMalware(page); - await navigateChannel(page); - await navigateTool(page); - await navigateVulnerability(page); +test.describe('Navigation available on CE', { tag: ['@ce'] }, () => { + test('Check navigation on all pages', async ({ page }) => { + await page.goto('/'); + const leftBarPage = new LeftBarPage(page); + await leftBarPage.open(); + + // For faster debugging, each navigated can be commented. + // so they should be all independent and start from the left menu. + await navigateAllMenu(page); + }); + + test('Check navigation on Analyses menu', async ({ page }) => { + await page.goto('/'); + const leftBarPage = new LeftBarPage(page); + await leftBarPage.open(); + await navigateReports(page); + await navigateGroupings(page); + await navigateMalwareAnalyses(page); + await navigateNotes(page); + await navigateExternalReferences(page); + }); + + test('Check navigation on Cases menu', async ({ page }) => { + await page.goto('/'); + const leftBarPage = new LeftBarPage(page); + await leftBarPage.open(); + await navigateIncidentResponse(page); + await navigateRfi(page); + await navigateRft(page); + await navigateTasks(page); + await navigateFeedbacks(page); + }); + + test('Check navigation on Events menu', async ({ page }) => { + await page.goto('/'); + const leftBarPage = new LeftBarPage(page); + await leftBarPage.open(); + await navigateEventsIncident(page); + await navigateSightings(page); + await navigateObservedData(page); + }); + + test('Check navigation on Observations menu', async ({ page }) => { + await page.goto('/'); + const leftBarPage = new LeftBarPage(page); + await leftBarPage.open(); + await navigateObservables(page); + await navigateArtifact(page); + await navigateIndicators(page); + await navigateInfrastructure(page); + }); + + test('Check navigation on Threats menu', async ({ page }) => { + await page.goto('/'); + const leftBarPage = new LeftBarPage(page); + await leftBarPage.open(); + await navigateIntrusionSet(page); + await navigateCampaign(page); + await navigateThreatActorGroup(page); + await navigateThreatActorIndividual(page); + }); + + test('Check navigation on Arsenal menu', async ({ page }) => { + await page.goto('/'); + const leftBarPage = new LeftBarPage(page); + await leftBarPage.open(); + await navigateMalware(page); + await navigateChannel(page); + await navigateTool(page); + await navigateVulnerability(page); + }); + + test('Check navigation on Techniques menu', async ({ page }) => { + await page.goto('/'); + const leftBarPage = new LeftBarPage(page); + await leftBarPage.open(); + await navigateAttackPattern(page); + await navigateNarrative(page); + await navigateCourseOfAction(page); + await navigateDataComponent(page); + await navigateDataSource(page); + }); + + test('Check navigation on Entities menu', async ({ page }) => { + await page.goto('/'); + const leftBarPage = new LeftBarPage(page); + await leftBarPage.open(); + await navigateSector(page); + await navigateEvent(page); + await navigateOrganization(page); + await navigateSecurityPlatform(page); + await navigateSystem(page); + await navigateIndividual(page); + }); + + test('Check navigation on Locations menu', async ({ page }) => { + await page.goto('/'); + const leftBarPage = new LeftBarPage(page); + await leftBarPage.open(); + await navigateRegion(page); + await navigateCountry(page); + await navigateAdministrativeArea(page); + await navigateCity(page); + await navigatePosition(page); + }); + + test.skip('Check navigation on Data menu', async ({ page }) => { + await page.goto('/'); + const leftBarPage = new LeftBarPage(page); + await leftBarPage.open(); + await navigateDataEntities(page); + await navigateDataRelationships(page); + await navigateIngestion(page); + await navigateDataImport(page); + await navigateProcessing(page); + await navigateDataSharing(page); + await navigateDataManagement(page); + }); + + test('Check navigation on Trash menu', async ({ page }) => { + await page.goto('/'); + const leftBarPage = new LeftBarPage(page); + await leftBarPage.open(); + await navigateTrash(page); + }); }); -test('Check navigation on Techniques menu', async ({ page }) => { - await page.goto('/'); - const leftBarPage = new LeftBarPage(page); - await leftBarPage.open(); - await navigateAttackPattern(page); - await navigateNarrative(page); - await navigateCourseOfAction(page); - await navigateDataComponent(page); - await navigateDataSource(page); -}); - -test('Check navigation on Entities menu', async ({ page }) => { - await page.goto('/'); - const leftBarPage = new LeftBarPage(page); - await leftBarPage.open(); - await navigateSector(page); - await navigateEvent(page); - await navigateOrganization(page); - await navigateSecurityPlatform(page); - await navigateSystem(page); - await navigateIndividual(page); -}); - -test('Check navigation on Locations menu', async ({ page }) => { - await page.goto('/'); - const leftBarPage = new LeftBarPage(page); - await leftBarPage.open(); - await navigateRegion(page); - await navigateCountry(page); - await navigateAdministrativeArea(page); - await navigateCity(page); - await navigatePosition(page); -}); - -test.skip('Check navigation on Data menu', async ({ page }) => { - await page.goto('/'); - const leftBarPage = new LeftBarPage(page); - await leftBarPage.open(); - await navigateDataEntities(page); - await navigateDataRelationships(page); - await navigateIngestion(page); - await navigateDataImport(page); - await navigateProcessing(page); - await navigateDataSharing(page); - await navigateDataManagement(page); -}); - -test('Check navigation on Trash menu', async ({ page }) => { - await page.goto('/'); - const leftBarPage = new LeftBarPage(page); - await leftBarPage.open(); - await navigateTrash(page); -}); - -// Separating settings menu in two to avoid timeout while testing as this part is too long - -test('Check navigation on Settings menu part one', async ({ page }) => { - await page.goto('/'); - const leftBarPage = new LeftBarPage(page); - await leftBarPage.open(); - await navigateSettings(page); - await navigateSecurity(page); - await navigateCustomization(page); - await navigateTaxonomies(page); -}); - -test('Check navigation on Settings menu part two', async ({ page }) => { - await page.goto('/'); - const leftBarPage = new LeftBarPage(page); - await leftBarPage.open(); - await navigateSettings(page); - await navigateActivity(page); - await navigateFileIndexing(page); - await navigateExperience(page); +test.describe('Navigation available only EE', { tag: ['@ee'] }, () => { + // Separating settings menu in two to avoid timeout while testing as this part is too long + + test('Check navigation on Settings menu part one', async ({ page }) => { + await page.goto('/'); + const leftBarPage = new LeftBarPage(page); + await leftBarPage.open(); + await navigateSettings(page); + await navigateSecurity(page); + await navigateCustomization(page); + await navigateTaxonomies(page); + }); + + test('Check navigation on Settings menu part two', async ({ page }) => { + await page.goto('/'); + const leftBarPage = new LeftBarPage(page); + await leftBarPage.open(); + await navigateSettings(page); + await navigateActivity(page); + await navigateFileIndexing(page); + await navigateExperience(page); + }); }); diff --git a/opencti-platform/opencti-front/tests_e2e/notes/createNote.spec.ts b/opencti-platform/opencti-front/tests_e2e/notes/createNote.spec.ts index 26f887083af1..2380c7744e31 100644 --- a/opencti-platform/opencti-front/tests_e2e/notes/createNote.spec.ts +++ b/opencti-platform/opencti-front/tests_e2e/notes/createNote.spec.ts @@ -3,7 +3,7 @@ import NotesPage from '../model/note.pageModel'; import NoteFormPage from '../model/form/noteForm.pageModel'; import NoteDetailsPage from '../model/noteDetails.pageModel'; -test('Create a new note', async ({ page }) => { +test('Create a new note', { tag: ['@ce'] }, async ({ page }) => { const notesPage = new NotesPage(page); const noteForm = new NoteFormPage(page); const noteDetails = new NoteDetailsPage(page); diff --git a/opencti-platform/opencti-front/tests_e2e/pir/pir.spec.ts b/opencti-platform/opencti-front/tests_e2e/pir/pir.spec.ts index 3b9b32ffd20c..4f3590fa897e 100644 --- a/opencti-platform/opencti-front/tests_e2e/pir/pir.spec.ts +++ b/opencti-platform/opencti-front/tests_e2e/pir/pir.spec.ts @@ -15,7 +15,7 @@ import { awaitUntilCondition } from '../utils'; * Test flag entities * Delete PIR */ -test('Pir CRUD', { tag: ['@pir', '@mutation'] }, async ({ page, request }) => { +test('Pir CRUD', { tag: ['@pir', '@mutation', '@ee'] }, async ({ page, request }) => { const leftNavigation = new LeftBarPage(page); const pirPage = new PirPage(page); const pirForm = new PirFormPageModel(page); diff --git a/opencti-platform/opencti-front/tests_e2e/report/report.spec.ts b/opencti-platform/opencti-front/tests_e2e/report/report.spec.ts index c9f3e412687f..1610ebea61f7 100644 --- a/opencti-platform/opencti-front/tests_e2e/report/report.spec.ts +++ b/opencti-platform/opencti-front/tests_e2e/report/report.spec.ts @@ -27,7 +27,7 @@ import EntitiesTabPageModel from '../model/EntitiesTab.pageModel'; * Delete report. * Check deletion. */ -test('Report CRUD', { tag: ['@report', '@knowledge', '@mutation'] }, async ({ page }) => { +test('Report CRUD', { tag: ['@report', '@knowledge', '@mutation', '@ce'] }, async ({ page }) => { await fakeDate(page, 'April 1 2024 12:00:00'); const leftNavigation = new LeftBarPage(page); const reportPage = new ReportPage(page); @@ -301,7 +301,7 @@ test('Report CRUD', { tag: ['@report', '@knowledge', '@mutation'] }, async ({ pa * Delete report by background task. * Check deletion. */ -test('Report live entities creation and relationships', { tag: ['@report', '@knowledge', '@mutation'] }, async ({ page }) => { +test('Report live entities creation and relationships', { tag: ['@report', '@knowledge', '@mutation', '@ce'] }, async ({ page }) => { const leftNavigation = new LeftBarPage(page); // const toolbar = new ToolbarPageModel(page); const reportPage = new ReportPage(page); diff --git a/opencti-platform/opencti-front/tests_e2e/rfis/rfis.spec.ts b/opencti-platform/opencti-front/tests_e2e/rfis/rfis.spec.ts index e0bddb843d4b..ed92fca0ec2d 100644 --- a/opencti-platform/opencti-front/tests_e2e/rfis/rfis.spec.ts +++ b/opencti-platform/opencti-front/tests_e2e/rfis/rfis.spec.ts @@ -6,7 +6,7 @@ import CaseRfiFormPage from '../model/form/caseRfiForm.pageModel'; import CaseRfiDetailsPage from '../model/caseRfiDetails.pageModel'; import ParticipantsFormPage from '../model/form/participantsForm.pageModel'; -test('Request for information CRUD', async ({ page }) => { +test('Request for information CRUD', { tag: ['@ce'] }, async ({ page }) => { const leftBarPage = new LeftBarPage(page); const caseRfiPage = new CaseRfiPage(page); const caseRfiDetailsPage = new CaseRfiDetailsPage(page); diff --git a/opencti-platform/opencti-front/tests_e2e/rfts/rfts.spec.ts b/opencti-platform/opencti-front/tests_e2e/rfts/rfts.spec.ts index 712feeb62d62..c34229257a81 100644 --- a/opencti-platform/opencti-front/tests_e2e/rfts/rfts.spec.ts +++ b/opencti-platform/opencti-front/tests_e2e/rfts/rfts.spec.ts @@ -5,7 +5,7 @@ import CaseRftPage from '../model/caseRft.pageModel'; import CaseRftDetailsPage from '../model/caseRftDetails.pageModel'; import CaseRftFormPage from '../model/form/caseRftForm.pageModel'; -test('Request for takedown CRUD', async ({ page }) => { +test('Request for takedown CRUD', { tag: ['@ce'] }, async ({ page }) => { const leftBarPage = new LeftBarPage(page); const caseRftPage = new CaseRftPage(page); const caseRftDetailsPage = new CaseRftDetailsPage(page); diff --git a/opencti-platform/opencti-front/tests_e2e/settings/entitySettings.spec.ts b/opencti-platform/opencti-front/tests_e2e/settings/entitySettings.spec.ts index 08cdc2447ed0..6cde1dc042bc 100644 --- a/opencti-platform/opencti-front/tests_e2e/settings/entitySettings.spec.ts +++ b/opencti-platform/opencti-front/tests_e2e/settings/entitySettings.spec.ts @@ -3,7 +3,7 @@ import ReportPage from '../model/report.pageModel'; import { expect, test } from '../fixtures/baseFixtures'; import SearchPageModel from '../model/search.pageModel'; -test('Testing content customization for Report', async ({ page }) => { +test('Testing content customization for Report', { tag: ['@ce'] }, async ({ page }) => { await page.goto('/'); const leftBarPage = new LeftBarPage(page); const reportPage = new ReportPage(page); diff --git a/opencti-platform/opencti-front/tests_e2e/settings/parameters.spec.ts b/opencti-platform/opencti-front/tests_e2e/settings/parameters.spec.ts index 1c7bff389748..1d8535bb6c19 100644 --- a/opencti-platform/opencti-front/tests_e2e/settings/parameters.spec.ts +++ b/opencti-platform/opencti-front/tests_e2e/settings/parameters.spec.ts @@ -12,7 +12,7 @@ const openThemeEditMenu = async (themeName: string, page: Page) => { .click(); }; -test('Check Logo replacement', async ({ page }) => { +test('Check Logo replacement', { tag: ['@ce'] }, async ({ page }) => { const leftBarPage = new LeftBarPage(page); await page.goto('/'); diff --git a/opencti-platform/opencti-front/tests_e2e/settings/themes.spec.ts b/opencti-platform/opencti-front/tests_e2e/settings/themes.spec.ts index cd700f2b3cfe..5ecfd8e55692 100644 --- a/opencti-platform/opencti-front/tests_e2e/settings/themes.spec.ts +++ b/opencti-platform/opencti-front/tests_e2e/settings/themes.spec.ts @@ -18,7 +18,7 @@ const openThemeEditMenu = async (themeName: string, page: Page) => { * MUST edit custom theme. * MUST delete custom theme. */ -test('Custom theme creation, edition, and deletion', async ({ page }) => { +test('Custom theme creation, edition, and deletion', { tag: ['@ce'] }, async ({ page }) => { const THEME = { name: `${new Date().toISOString()} Test Theme`, theme_background: '#e72a2a', @@ -41,7 +41,7 @@ test('Custom theme creation, edition, and deletion', async ({ page }) => { await page.getByTestId('create-theme-btn').click(); // disable on purpose because we want that fill to be sequentially - /* eslint-disable no-await-in-loop */ + for (const [key, value] of Object.entries(THEME)) { await page.locator(`input[name="${key}"]`).fill(value); } @@ -93,7 +93,7 @@ test('Custom theme creation, edition, and deletion', async ({ page }) => { /** * MUST ensure cannot delete system theme. */ -test('Cannot delete system theme', async ({ page }) => { +test('Cannot delete system theme', { tag: ['@ce'] }, async ({ page }) => { // Navigate to Settings const leftBarPage = new LeftBarPage(page); await page.goto('/'); diff --git a/opencti-platform/opencti-front/tests_e2e/z_logout.spec.ts b/opencti-platform/opencti-front/tests_e2e/z_logout.spec.ts index a86144c37e58..d5a2257e2125 100644 --- a/opencti-platform/opencti-front/tests_e2e/z_logout.spec.ts +++ b/opencti-platform/opencti-front/tests_e2e/z_logout.spec.ts @@ -3,7 +3,7 @@ import TopMenuProfilePage from './model/menu/topMenuProfile.pageModel'; import LoginFormPageModel from './model/form/loginForm.pageModel'; import DashboardPage from './model/dashboard.pageModel'; -test('test logout', async ({ page }) => { +test('test logout', { tag: ['@ce'] }, async ({ page }) => { const loginPage = new LoginFormPageModel(page); const topMenu = new TopMenuProfilePage(page); const dashboardPage = new DashboardPage(page); From 42e59af85617a4796cb12d3602aa2c23defffe64 Mon Sep 17 00:00:00 2001 From: hervyt Date: Wed, 7 Jan 2026 15:29:49 +0100 Subject: [PATCH 050/126] [frontend] - (TaxiiFeeds - CSV) Update order for Export in popover #13848 (#13925) --- .../components/data/ingestionCsv/IngestionCsvPopover.tsx | 6 +++--- .../data/ingestionTaxii/IngestionTaxiiPopover.tsx | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/opencti-platform/opencti-front/src/private/components/data/ingestionCsv/IngestionCsvPopover.tsx b/opencti-platform/opencti-front/src/private/components/data/ingestionCsv/IngestionCsvPopover.tsx index 97bf0d94f491..867476947161 100644 --- a/opencti-platform/opencti-front/src/private/components/data/ingestionCsv/IngestionCsvPopover.tsx +++ b/opencti-platform/opencti-front/src/private/components/data/ingestionCsv/IngestionCsvPopover.tsx @@ -233,6 +233,9 @@ const IngestionCsvPopover: FunctionComponent = ({ {t_i18n('Update')} + + {t_i18n('Export')} + {t_i18n('Duplicate')} @@ -242,9 +245,6 @@ const IngestionCsvPopover: FunctionComponent = ({ {t_i18n('Delete')} - - {t_i18n('Export')} - {queryRef && ( diff --git a/opencti-platform/opencti-front/src/private/components/data/ingestionTaxii/IngestionTaxiiPopover.tsx b/opencti-platform/opencti-front/src/private/components/data/ingestionTaxii/IngestionTaxiiPopover.tsx index 57d229755dc8..f3c69b840921 100644 --- a/opencti-platform/opencti-front/src/private/components/data/ingestionTaxii/IngestionTaxiiPopover.tsx +++ b/opencti-platform/opencti-front/src/private/components/data/ingestionTaxii/IngestionTaxiiPopover.tsx @@ -230,15 +230,15 @@ const IngestionTaxiiPopover: FunctionComponent = ({ {t_i18n('Update')} + + {t_i18n('Export')} + {t_i18n('Delete')} {t_i18n('Reset state')} - - {t_i18n('Export')} - {displayUpdate && queryRef && ( From 40b133438ef4508b76d6c3fdd1f8efc7305d5729 Mon Sep 17 00:00:00 2001 From: Filigran Automation Date: Wed, 7 Jan 2026 15:14:48 +0000 Subject: [PATCH 051/126] [backend/worker] Release 6.9.6 --- client-python/pycti/__init__.py | 2 +- opencti-platform/opencti-front/package.json | 2 +- opencti-platform/opencti-graphql/package.json | 2 +- opencti-platform/opencti-graphql/src/python/requirements.txt | 2 +- opencti-worker/src/requirements.txt | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/client-python/pycti/__init__.py b/client-python/pycti/__init__.py index 48efbce349e7..0207447c3766 100644 --- a/client-python/pycti/__init__.py +++ b/client-python/pycti/__init__.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -__version__ = "6.9.5" +__version__ = "6.9.6" from .api.opencti_api_client import OpenCTIApiClient from .api.opencti_api_connector import OpenCTIApiConnector diff --git a/opencti-platform/opencti-front/package.json b/opencti-platform/opencti-front/package.json index 2b730be577f6..4220cb0aa696 100644 --- a/opencti-platform/opencti-front/package.json +++ b/opencti-platform/opencti-front/package.json @@ -1,6 +1,6 @@ { "name": "opencti-front", - "version": "6.9.5", + "version": "6.9.6", "private": true, "workspaces": [ "packages/*" diff --git a/opencti-platform/opencti-graphql/package.json b/opencti-platform/opencti-graphql/package.json index 73daa7b4ec20..8af6e849eeb3 100644 --- a/opencti-platform/opencti-graphql/package.json +++ b/opencti-platform/opencti-graphql/package.json @@ -1,6 +1,6 @@ { "name": "opencti-graphql", - "version": "6.9.5", + "version": "6.9.6", "private": true, "scripts": { "check-ts": "tsc --noEmit", diff --git a/opencti-platform/opencti-graphql/src/python/requirements.txt b/opencti-platform/opencti-graphql/src/python/requirements.txt index f11e54d1106b..a862b6052e2f 100644 --- a/opencti-platform/opencti-graphql/src/python/requirements.txt +++ b/opencti-platform/opencti-graphql/src/python/requirements.txt @@ -1,4 +1,4 @@ -pycti==6.9.5 +pycti==6.9.6 parsuricata==0.4.1 yara-python==4.5.2 sigmatools==0.23.1 diff --git a/opencti-worker/src/requirements.txt b/opencti-worker/src/requirements.txt index 17b88b1ba9b5..5d37e018c63b 100644 --- a/opencti-worker/src/requirements.txt +++ b/opencti-worker/src/requirements.txt @@ -1,4 +1,4 @@ -pycti==6.9.5 +pycti==6.9.6 opentelemetry-api~=1.35.0 opentelemetry-sdk~=1.35.0 opentelemetry-exporter-prometheus==0.56b0 From ea63c8739f76878637b300a2ed1d507029238b98 Mon Sep 17 00:00:00 2001 From: hervyt Date: Thu, 8 Jan 2026 10:38:37 +0100 Subject: [PATCH 052/126] [frontend] - (XTMHub - FreeTrials) Change redirection URL on learn more #13933 (#13934) --- .../src/private/components/xtm_hub/StartTrialBanner.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/opencti-platform/opencti-front/src/private/components/xtm_hub/StartTrialBanner.tsx b/opencti-platform/opencti-front/src/private/components/xtm_hub/StartTrialBanner.tsx index e4ed0af025ac..712b64662037 100644 --- a/opencti-platform/opencti-front/src/private/components/xtm_hub/StartTrialBanner.tsx +++ b/opencti-platform/opencti-front/src/private/components/xtm_hub/StartTrialBanner.tsx @@ -10,7 +10,7 @@ const StartTrialBanner = () => { if (!settings || isEmptyField(settings?.platform_xtmhub_url) || !settings.platform_demo) return <>; - const freeTrialUrl = `${settings?.platform_xtmhub_url}/redirect/free-trial`; + const freeTrialUrl = `${settings?.platform_xtmhub_url}/cybersecurity-solutions/free-trial`; const createFreeTrialUrl = `${settings?.platform_xtmhub_url}/redirect/create-free-trial`; const text = ( From bbd3bc60f0142d6aa3d54d83356f959bdc9b21e5 Mon Sep 17 00:00:00 2001 From: Archidoit <75783086+Archidoit@users.noreply.github.com> Date: Thu, 8 Jan 2026 10:41:43 +0100 Subject: [PATCH 053/126] [backend] users add relations in orga admin context (#13901) --- .../opencti-graphql/src/domain/user.js | 80 ++++++++++--------- .../03-integration/02-resolvers/user-test.ts | 25 +++++- 2 files changed, 65 insertions(+), 40 deletions(-) diff --git a/opencti-platform/opencti-graphql/src/domain/user.js b/opencti-platform/opencti-graphql/src/domain/user.js index 7d681a81b4d2..5d0263e85b67 100644 --- a/opencti-platform/opencti-graphql/src/domain/user.js +++ b/opencti-platform/opencti-graphql/src/domain/user.js @@ -184,7 +184,7 @@ const extractTokenFromBasicAuth = async (authorization) => { export const findById = async (context, user, userId) => { if (!isUserHasCapability(user, SETTINGS_SET_ACCESSES) && user.id !== userId) { - // if no organization in common with the logged user + // if no organization in common with the logged user administrated organizations const memberOrganizations = await fullEntitiesThroughRelationsToList(context, user, userId, RELATION_PARTICIPATE_TO, ENTITY_TYPE_IDENTITY_ORGANIZATION); const myOrganizationsIds = user.administrated_organizations.map((organization) => organization.id); if (!memberOrganizations.map((organization) => organization.id).find((orgaId) => myOrganizationsIds.includes(orgaId))) { @@ -525,6 +525,25 @@ const isUserAdministratingOrga = (user, organizationId) => { return user.administrated_organizations.some(({ id }) => id === organizationId); }; +const loadUserToUpdateWithAccessCheck = async (context, user, userId) => { + const userToUpdate = await internalLoadById(context, user, userId, { type: ENTITY_TYPE_USER }); + if (!userToUpdate) { + throw FunctionalError(`${ENTITY_TYPE_USER} cannot be found.`, { userId }); + } + if (!isUserHasCapability(user, SETTINGS_SET_ACCESSES) && user.id !== userId) { + // Check in an organization admin edits a user that's not in its administrated organizations + if (isOnlyOrgaAdmin(user)) { + const myAdministratedOrganizationsIds = user.administrated_organizations.map((orga) => orga.id); + if (!userToUpdate[RELATION_PARTICIPATE_TO]?.find((orga) => myAdministratedOrganizationsIds.includes(orga))) { + throw ForbiddenAccess(); + } + } else { + throw ForbiddenAccess(); + } + } + return userToUpdate; +}; + export const assignOrganizationToUser = async (context, user, userId, organizationId) => { if (isOnlyOrgaAdmin(user)) { // When user is organization admin, we make sure she is also admin of organization added @@ -532,10 +551,9 @@ export const assignOrganizationToUser = async (context, user, userId, organizati throw ForbiddenAccess(); } } - const targetUser = await findById(context, user, userId); - if (!targetUser) { - throw FunctionalError('Cannot add the relation, User cannot be found.', { userId }); - } + // check the user is accessible + const targetUser = await loadUserToUpdateWithAccessCheck(context, user, userId); + const input = { fromId: userId, toId: organizationId, relationship_type: RELATION_PARTICIPATE_TO }; const created = await createRelation(context, user, input); const actionEmail = ENABLED_DEMO_MODE ? REDACTED_USER.user_email : created.from.user_email; @@ -899,14 +917,7 @@ export const roleDeleteRelation = async (context, user, roleId, toId, relationsh // User related export const userEditField = async (context, user, userId, rawInputs) => { const inputs = []; - const userToUpdate = await internalLoadById(context, user, userId); - // Check in an organization admin edits a user that's not in its administrated organizations - const myAdministratedOrganizationsIds = user.administrated_organizations.map((orga) => orga.id); - if (isOnlyOrgaAdmin(user)) { - if (userId !== user.id && !userToUpdate[RELATION_PARTICIPATE_TO].find((orga) => myAdministratedOrganizationsIds.includes(orga))) { - throw ForbiddenAccess(); - } - } + const userToUpdate = await loadUserToUpdateWithAccessCheck(context, user, userId); let skipThisInput = false; for (let index = 0; index < rawInputs.length; index += 1) { const input = rawInputs[index]; @@ -1186,14 +1197,9 @@ export const deleteAllNotificationByUser = async (userId) => { * @returns {Promise<*>} */ export const userDelete = async (context, user, userId) => { - if (isOnlyOrgaAdmin(user)) { - // When user is organization admin, we make sure that the deleted user is in one of the administrated organizations of the admin - const userData = await storeLoadById(context, user, userId, ENTITY_TYPE_USER); - const myAdministratedOrganizationsIds = user.administrated_organizations.map(({ id }) => id); - if (!userData[RELATION_PARTICIPATE_TO].find((orga) => myAdministratedOrganizationsIds.includes(orga))) { - throw ForbiddenAccess(); - } - } + // check rights + await loadUserToUpdateWithAccessCheck(context, user, userId); + await deleteAllTriggerAndDigestByUser(userId); await deleteAllNotificationByUser(userId); await deleteAllWorkspaceForUser(context, user, userId); @@ -1213,14 +1219,14 @@ export const userDelete = async (context, user, userId) => { }; export const userAddRelation = async (context, user, userId, input) => { - const userData = await storeLoadById(context, user, userId, ENTITY_TYPE_USER); - if (!userData) { - throw FunctionalError(`Cannot add the relation, ${ENTITY_TYPE_USER} cannot be found.`, { userId }); - } + // check the user is accessible + const userData = await loadUserToUpdateWithAccessCheck(context, user, userId); + + // check the relationship type if (!isInternalRelationship(input.relationship_type)) { throw FunctionalError(`Only ${ABSTRACT_INTERNAL_RELATIONSHIP} can be added through this method, got ${input.relationship_type}.`); } - // Check in case organization admins adds non-grantable goup a user + // Check in case organization admins adds non-grantable group a user const myGrantableGroups = R.uniq(user.administrated_organizations.map((orga) => orga.grantable_groups).flat()); if (isOnlyOrgaAdmin(user)) { if (input.relationship_type === 'member-of' && !myGrantableGroups.includes(input.toId)) { @@ -1260,10 +1266,9 @@ export const userDeleteRelation = async (context, user, targetUser, toId, relati }; export const userIdDeleteRelation = async (context, user, userId, toId, relationshipType) => { - const userData = await storeLoadById(context, user, userId, ENTITY_TYPE_USER); - if (!userData) { - throw FunctionalError('Cannot delete the relation, User cannot be found.', { userId }); - } + // check the user is accessible + const userData = await loadUserToUpdateWithAccessCheck(context, user, userId); + if (!isInternalRelationship(relationshipType)) { throw FunctionalError(`Only ${ABSTRACT_INTERNAL_RELATIONSHIP} can be deleted through this method, got ${relationshipType}.`); } @@ -1277,10 +1282,8 @@ export const userDeleteOrganizationRelation = async (context, user, userId, toId throw ForbiddenAccess(); } } - const targetUser = await findById(context, user, userId); - if (!targetUser) { - throw FunctionalError('Cannot delete the relation, User cannot be found.', { userId }); - } + // check the user is accessible + const targetUser = await loadUserToUpdateWithAccessCheck(context, user, userId); const { to } = await deleteRelationsByFromAndTo(context, user, userId, toId, RELATION_PARTICIPATE_TO, ABSTRACT_INTERNAL_RELATIONSHIP); if (to.authorized_authorities?.includes(userId)) { @@ -1690,10 +1693,9 @@ export const userRenewToken = async (context, user, userId) => { throw FunctionalError('Cannot renew token of admin user defined in configuration, please change configuration instead.'); } - const userData = await storeLoadById(context, user, userId, ENTITY_TYPE_USER); - if (!userData) { - throw FunctionalError(`Cannot renew token, ${userId} user cannot be found.`); - } + // check the user is accessible + const userData = await loadUserToUpdateWithAccessCheck(context, user, userId); + const patch = { api_token: uuid() }; const { element } = await patchAttribute(context, user, userId, ENTITY_TYPE_USER, patch); @@ -1872,11 +1874,13 @@ export const findDefaultDashboards = async (context, user, currentUser) => { // region context export const userCleanContext = async (context, user, userId) => { + await loadUserToUpdateWithAccessCheck(context, user, userId); await delEditContext(user, userId); return storeLoadById(context, user, userId, ENTITY_TYPE_USER); }; export const userEditContext = async (context, user, userId, input) => { + await loadUserToUpdateWithAccessCheck(context, user, userId); await setEditContext(user, userId, input); return storeLoadById(context, user, userId, ENTITY_TYPE_USER); }; diff --git a/opencti-platform/opencti-graphql/tests/03-integration/02-resolvers/user-test.ts b/opencti-platform/opencti-graphql/tests/03-integration/02-resolvers/user-test.ts index d6adc0471cc7..c662be82354d 100644 --- a/opencti-platform/opencti-graphql/tests/03-integration/02-resolvers/user-test.ts +++ b/opencti-platform/opencti-graphql/tests/03-integration/02-resolvers/user-test.ts @@ -1,5 +1,5 @@ import gql from 'graphql-tag'; -import { afterAll, beforeAll, describe, expect, it, vi } from 'vitest'; +import { afterAll, beforeAll, describe, expect, it } from 'vitest'; import { elLoadById } from '../../../src/database/engine'; import { generateStandardId } from '../../../src/schema/identifier'; import { ENTITY_TYPE_CAPABILITY, ENTITY_TYPE_GROUP, ENTITY_TYPE_USER } from '../../../src/schema/internalObject'; @@ -20,6 +20,7 @@ import { USER_CONNECTOR, USER_DISINFORMATION_ANALYST, USER_EDITOR, + USER_SECURITY, } from '../../utils/testQuery'; import { ENTITY_TYPE_IDENTITY_ORGANIZATION } from '../../../src/modules/organization/organization-types'; import { VIRTUAL_ORGANIZATION_ADMIN } from '../../../src/utils/access'; @@ -37,7 +38,6 @@ import { OPENCTI_ADMIN_UUID } from '../../../src/schema/general'; import type { Capability, Member, UserAddInput } from '../../../src/generated/graphql'; import { storeLoadById } from '../../../src/database/middleware-loader'; import { entitiesCounter } from '../../02-dataInjection/01-dataCount/entityCountHelper'; -import * as entrepriseEdition from '../../../src/enterprise-edition/ee'; const LIST_QUERY = gql` query users( @@ -980,6 +980,18 @@ describe('User has no settings capability and is organization admin query behavi }); expect(queryResult.data.userEdit.fieldPatch.account_status).toEqual('Inactive'); }); + it('should not update user with no organization', async () => { + await queryAsUserIsExpectedForbidden(USER_EDITOR.client, { + query: UPDATE_QUERY, + variables: { id: ADMIN_USER.id, input: { key: 'account_status', value: ['Inactive'] } }, + }); + }); + it('should not update user from an other organization', async () => { + await queryAsUserIsExpectedForbidden(USER_EDITOR.client, { + query: UPDATE_QUERY, + variables: { id: USER_SECURITY.id, input: { key: 'account_status', value: ['Inactive'] } }, + }); + }); it('should not add organization to user if not admin', async () => { platformOrganizationId = await getOrganizationIdByName(PLATFORM_ORGANIZATION.name); await queryAsUserIsExpectedForbidden(USER_EDITOR.client, { @@ -990,6 +1002,15 @@ describe('User has no settings capability and is organization admin query behavi }, }); }); + it('should not add organization to user if user is not in its own organization', async () => { + await queryAsUserIsExpectedForbidden(USER_EDITOR.client, { + query: ORGANIZATION_ADD_QUERY, + variables: { + id: ADMIN_USER.id, + organizationId: testOrganizationId, + }, + }); + }); it('should administrate more than 1 organization', async () => { // Need to add granted_groups to PLATFORM_ORGANIZATION because of line 533 in domain/user.js const grantableGroupQueryResult = await adminQuery({ From 27178b46e6e03db4395d81f8efec6cfc11027c64 Mon Sep 17 00:00:00 2001 From: Archidoit <75783086+Archidoit@users.noreply.github.com> Date: Thu, 8 Jan 2026 14:34:32 +0100 Subject: [PATCH 054/126] [frontend] channel types display in Channel details (#13624) --- .../components/arsenal/channels/Channel.tsx | 2 +- .../arsenal/channels/ChannelDetails.jsx | 138 ++++++++---------- 2 files changed, 65 insertions(+), 75 deletions(-) diff --git a/opencti-platform/opencti-front/src/private/components/arsenal/channels/Channel.tsx b/opencti-platform/opencti-front/src/private/components/arsenal/channels/Channel.tsx index b99f7eff0caa..f444dc527ffb 100644 --- a/opencti-platform/opencti-front/src/private/components/arsenal/channels/Channel.tsx +++ b/opencti-platform/opencti-front/src/private/components/arsenal/channels/Channel.tsx @@ -89,7 +89,7 @@ const Channel: React.FC = ({ case 'details': return ( - + ); case 'basicInformation': diff --git a/opencti-platform/opencti-front/src/private/components/arsenal/channels/ChannelDetails.jsx b/opencti-platform/opencti-front/src/private/components/arsenal/channels/ChannelDetails.jsx index 5bedcbea35c8..6989ea8bea67 100644 --- a/opencti-platform/opencti-front/src/private/components/arsenal/channels/ChannelDetails.jsx +++ b/opencti-platform/opencti-front/src/private/components/arsenal/channels/ChannelDetails.jsx @@ -1,85 +1,75 @@ -import React, { Component } from 'react'; -import * as PropTypes from 'prop-types'; -import * as R from 'ramda'; -import { graphql, createFragmentContainer } from 'react-relay'; -import withStyles from '@mui/styles/withStyles'; +import React from 'react'; +import { graphql, useFragment } from 'react-relay'; import Paper from '@mui/material/Paper'; import Typography from '@mui/material/Typography'; import Grid from '@mui/material/Grid'; import Chip from '@mui/material/Chip'; -import inject18n from '../../../../components/i18n'; +import { useFormatter } from '../../../../components/i18n'; import ExpandableMarkdown from '../../../../components/ExpandableMarkdown'; import FieldOrEmpty from '../../../../components/FieldOrEmpty'; +import { useTheme } from '@mui/styles'; -const styles = (theme) => ({ - paper: { - marginTop: theme.spacing(1), - padding: '15px', - borderRadius: 4, - }, - chip: { - fontSize: 12, - lineHeight: '12px', - backgroundColor: theme.palette.background.accent, - color: theme.palette.text.primary, - textTransform: 'uppercase', - borderRadius: 4, - margin: '0 5px 5px 0', - }, -}); - -class ChannelDetailsComponent extends Component { - render() { - const { t, classes, channel } = this.props; - return ( - <> - - {t('Details')} - - - - - - {t('Description')} - - - - - - {t('Channel types')} - - - {channel.chanel_types?.map((channelType) => ( - - ))} - - - - - - ); +const ChannelDetailsFragment = graphql` + fragment ChannelDetails_channel on Channel { + id + description + channel_types } -} +`; -ChannelDetailsComponent.propTypes = { - channel: PropTypes.object, - classes: PropTypes.object, - t: PropTypes.func, - fld: PropTypes.func, +export const ChannelDetails = ({ + channelData, +}) => { + const { t_i18n } = useFormatter(); + const theme = useTheme(); + const channel = useFragment(ChannelDetailsFragment, channelData); + return ( + <> + + {t_i18n('Details')} + + + + + + {t_i18n('Description')} + + + + + + {t_i18n('Channel types')} + + + {channel.channel_types?.map((channelType) => ( + + ))} + + + + + + ); }; -const ChannelDetails = createFragmentContainer(ChannelDetailsComponent, { - channel: graphql` - fragment ChannelDetails_channel on Channel { - id - description - channel_types - } - `, -}); - -export default R.compose(inject18n, withStyles(styles))(ChannelDetails); +export default ChannelDetails; From 6df0316404fd57c178b08a5023591ba85237817e Mon Sep 17 00:00:00 2001 From: Archidoit <75783086+Archidoit@users.noreply.github.com> Date: Thu, 8 Jan 2026 16:51:04 +0100 Subject: [PATCH 055/126] [backend] refresh status cache at status template modification (#13897) --- opencti-platform/opencti-graphql/src/config/conf.js | 4 ---- opencti-platform/opencti-graphql/src/database/cache.ts | 4 +++- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/opencti-platform/opencti-graphql/src/config/conf.js b/opencti-platform/opencti-graphql/src/config/conf.js index ef86cc22a3d8..88363839e669 100644 --- a/opencti-platform/opencti-graphql/src/config/conf.js +++ b/opencti-platform/opencti-graphql/src/config/conf.js @@ -615,10 +615,6 @@ export const BUS_TOPICS = { ADDED_TOPIC: `${TOPIC_PREFIX}DRAFT_WORKSPACE_ADDED_TOPIC`, DELETE_TOPIC: `${TOPIC_PREFIX}DRAFT_WORKSPACE_DELETE_TOPIC`, }, - [M.ENTITY_TYPE_LABEL]: { - EDIT_TOPIC: `${TOPIC_PREFIX}LABEL_EDIT_TOPIC`, - ADDED_TOPIC: `${TOPIC_PREFIX}LABEL_ADDED_TOPIC`, - }, [ENTITY_TYPE_VOCABULARY]: { EDIT_TOPIC: `${TOPIC_PREFIX}VOCABULARY_EDIT_TOPIC`, ADDED_TOPIC: `${TOPIC_PREFIX}VOCABULARY_ADDED_TOPIC`, diff --git a/opencti-platform/opencti-graphql/src/database/cache.ts b/opencti-platform/opencti-graphql/src/database/cache.ts index 1fd6d3104f0c..b35e2fae9877 100644 --- a/opencti-platform/opencti-graphql/src/database/cache.ts +++ b/opencti-platform/opencti-graphql/src/database/cache.ts @@ -6,7 +6,7 @@ import { UnsupportedError } from '../config/errors'; import { telemetry } from '../config/tracing'; import type { AuthContext, AuthUser } from '../types/user'; import type { StixId, StixObject } from '../types/stix-2-1-common'; -import { ENTITY_TYPE_CONNECTOR, ENTITY_TYPE_STREAM_COLLECTION } from '../schema/internalObject'; +import { ENTITY_TYPE_CONNECTOR, ENTITY_TYPE_STATUS, ENTITY_TYPE_STATUS_TEMPLATE, ENTITY_TYPE_STREAM_COLLECTION } from '../schema/internalObject'; import { ENTITY_TYPE_RESOLVED_FILTERS } from '../schema/stixDomainObject'; import { ENTITY_TYPE_TRIGGER } from '../modules/notification/notification-types'; import { ENTITY_TYPE_PLAYBOOK } from '../modules/playbook/playbook-types'; @@ -26,6 +26,8 @@ const STORE_ENTITIES_LINKS: Record = { [ENTITY_TYPE_DECAY_EXCLUSION_RULE]: [ENTITY_TYPE_RESOLVED_FILTERS], [ENTITY_TYPE_LABEL]: [ENTITY_TYPE_RESOLVED_FILTERS], [ENTITY_TYPE_MARKING_DEFINITION]: [ENTITY_TYPE_RESOLVED_FILTERS], + // Status must be reset depending on status template modifications + [ENTITY_TYPE_STATUS_TEMPLATE]: [ENTITY_TYPE_STATUS], }; const cache: any = {}; From c9f06d7946fac6d2a18e440872680800268730d4 Mon Sep 17 00:00:00 2001 From: Jeremy Cloarec <159018898+JeremyCloarec@users.noreply.github.com> Date: Thu, 8 Jan 2026 18:04:15 +0100 Subject: [PATCH 056/126] [client] refactor recursive import retries to iterative (#13253) --- client-python/pycti/utils/opencti_stix2.py | 454 ++++++++++----------- 1 file changed, 226 insertions(+), 228 deletions(-) diff --git a/client-python/pycti/utils/opencti_stix2.py b/client-python/pycti/utils/opencti_stix2.py index cf8dadae6230..ed491dfdda1c 100644 --- a/client-python/pycti/utils/opencti_stix2.py +++ b/client-python/pycti/utils/opencti_stix2.py @@ -2849,242 +2849,233 @@ def import_item( item, update: bool = False, types: List = None, - processing_count: int = 0, work_id: str = None, ): - worker_logger = self.opencti.logger_class("worker") - # Ultimate protection to avoid infinite retry - if processing_count > MAX_PROCESSING_COUNT: - if work_id is not None: - item_str = json.dumps(item) - self.opencti.work.report_expectation( - work_id, - { - "error": "Max number of retries reached, please see error logs of workers for more details", - "source": ( - item_str if len(item_str) < 50000 else "Bundle too large" - ), - }, - ) - return False - try: - self.opencti.set_retry_number(processing_count) - opencti_operation = self.opencti.get_attribute_in_extension( - "opencti_operation", item - ) - if opencti_operation is not None: - self.apply_opencti_operation(item, opencti_operation) - elif "opencti_operation" in item: - self.apply_opencti_operation(item, item["opencti_operation"]) - elif item["type"] == "relationship": - # Import relationship - self.import_relationship(item, update, types) - elif item["type"] == "sighting": - # region Resolve the to - to_ids = [] - if "x_opencti_where_sighted_refs" in item: - for where_sighted_ref in item["x_opencti_where_sighted_refs"]: - to_ids.append(where_sighted_ref) - elif "where_sighted_refs" in item: - for where_sighted_ref in item["where_sighted_refs"]: - to_ids.append(where_sighted_ref) - # endregion - # region Resolve the from - from_id = None - if "x_opencti_sighting_of_ref" in item: - from_id = item["x_opencti_sighting_of_ref"] - elif "sighting_of_ref" in item: - from_id = item["sighting_of_ref"] - # endregion - # region create the sightings - if len(to_ids) > 0: - if from_id: + opencti_operation = self.opencti.get_attribute_in_extension( + "opencti_operation", item + ) + if opencti_operation is not None: + self.apply_opencti_operation(item, opencti_operation) + elif "opencti_operation" in item: + self.apply_opencti_operation(item, item["opencti_operation"]) + elif item["type"] == "relationship": + # Import relationship + self.import_relationship(item, update, types) + elif item["type"] == "sighting": + # region Resolve the to + to_ids = [] + if "x_opencti_where_sighted_refs" in item: + for where_sighted_ref in item["x_opencti_where_sighted_refs"]: + to_ids.append(where_sighted_ref) + elif "where_sighted_refs" in item: + for where_sighted_ref in item["where_sighted_refs"]: + to_ids.append(where_sighted_ref) + # endregion + # region Resolve the from + from_id = None + if "x_opencti_sighting_of_ref" in item: + from_id = item["x_opencti_sighting_of_ref"] + elif "sighting_of_ref" in item: + from_id = item["sighting_of_ref"] + # endregion + # region create the sightings + if len(to_ids) > 0: + if from_id: + for to_id in to_ids: + self.import_sighting(item, from_id, to_id, update) + # Import observed_data_refs + if "observed_data_refs" in item: + for observed_data_ref in item["observed_data_refs"]: for to_id in to_ids: - self.import_sighting(item, from_id, to_id, update) - # Import observed_data_refs - if "observed_data_refs" in item: - for observed_data_ref in item["observed_data_refs"]: - for to_id in to_ids: - self.import_sighting( - item, observed_data_ref, to_id, update - ) - # endregion - elif item["type"] == "label": - stix_ids = self.opencti.get_attribute_in_extension("stix_ids", item) - self.opencti.label.create( - stix_id=item["id"], - value=item["value"], - color=item["color"], - x_opencti_stix_ids=stix_ids, - update=update, - ) - elif item["type"] == "vocabulary": - stix_ids = self.opencti.get_attribute_in_extension("stix_ids", item) - self.opencti.vocabulary.create( - stix_id=item["id"], - name=item["name"], - category=item["category"], - description=( - item["description"] if "description" in item else None - ), - aliases=item["aliases"] if "aliases" in item else None, - x_opencti_stix_ids=stix_ids, - update=update, - ) - elif item["type"] == "external-reference": - stix_ids = self.opencti.get_attribute_in_extension("stix_ids", item) - self.opencti.external_reference.create( - stix_id=item["id"], - source_name=( - item["source_name"] if "source_name" in item else None - ), - url=item["url"] if "url" in item else None, - external_id=( - item["external_id"] if "external_id" in item else None - ), - description=( - item["description"] if "description" in item else None - ), - x_opencti_stix_ids=stix_ids, - update=update, - ) - elif item["type"] == "kill-chain-phase": - stix_ids = self.opencti.get_attribute_in_extension("stix_ids", item) - self.opencti.kill_chain_phase.create( - stix_id=item["id"], - kill_chain_name=item["kill_chain_name"], - phase_name=item["phase_name"], - x_opencti_order=item["order"] if "order" in item else 0, - x_opencti_stix_ids=stix_ids, - update=update, - ) - elif StixCyberObservableTypes.has_value(item["type"]): - if types is None or len(types) == 0: - self.import_observable(item, update, types) - elif item["type"] in types or "observable" in types: - self.import_observable(item, update, types) + self.import_sighting(item, observed_data_ref, to_id, update) + # endregion + elif item["type"] == "label": + stix_ids = self.opencti.get_attribute_in_extension("stix_ids", item) + self.opencti.label.create( + stix_id=item["id"], + value=item["value"], + color=item["color"], + x_opencti_stix_ids=stix_ids, + update=update, + ) + elif item["type"] == "vocabulary": + stix_ids = self.opencti.get_attribute_in_extension("stix_ids", item) + self.opencti.vocabulary.create( + stix_id=item["id"], + name=item["name"], + category=item["category"], + description=(item["description"] if "description" in item else None), + aliases=item["aliases"] if "aliases" in item else None, + x_opencti_stix_ids=stix_ids, + update=update, + ) + elif item["type"] == "external-reference": + stix_ids = self.opencti.get_attribute_in_extension("stix_ids", item) + self.opencti.external_reference.create( + stix_id=item["id"], + source_name=(item["source_name"] if "source_name" in item else None), + url=item["url"] if "url" in item else None, + external_id=(item["external_id"] if "external_id" in item else None), + description=(item["description"] if "description" in item else None), + x_opencti_stix_ids=stix_ids, + update=update, + ) + elif item["type"] == "kill-chain-phase": + stix_ids = self.opencti.get_attribute_in_extension("stix_ids", item) + self.opencti.kill_chain_phase.create( + stix_id=item["id"], + kill_chain_name=item["kill_chain_name"], + phase_name=item["phase_name"], + x_opencti_order=item["order"] if "order" in item else 0, + x_opencti_stix_ids=stix_ids, + update=update, + ) + elif StixCyberObservableTypes.has_value(item["type"]): + if types is None or len(types) == 0: + self.import_observable(item, update, types) + elif item["type"] in types or "observable" in types: + self.import_observable(item, update, types) + else: + # Check the scope + if item["type"] == "marking-definition" or types is None or len(types) == 0: + self.import_object(item, update, types) + # Handle identity & location if part of the scope + elif item["type"] in types: + self.import_object(item, update, types) else: - # Check the scope - if ( - item["type"] == "marking-definition" - or types is None - or len(types) == 0 - ): - self.import_object(item, update, types) - # Handle identity & location if part of the scope - elif item["type"] in types: - self.import_object(item, update, types) - else: - # Specific OpenCTI scopes - if item["type"] == "identity": - if "identity_class" in item: - if ("class" in types or "sector" in types) and item[ - "identity_class" - ] == "class": - self.import_object(item, update, types) - elif item["identity_class"] in types: - self.import_object(item, update, types) - elif item["type"] == "location": - if "x_opencti_location_type" in item: - if item["x_opencti_location_type"].lower() in types: - self.import_object(item, update, types) - elif ( + # Specific OpenCTI scopes + if item["type"] == "identity": + if "identity_class" in item: + if ("class" in types or "sector" in types) and item[ + "identity_class" + ] == "class": + self.import_object(item, update, types) + elif item["identity_class"] in types: + self.import_object(item, update, types) + elif item["type"] == "location": + if "x_opencti_location_type" in item: + if item["x_opencti_location_type"].lower() in types: + self.import_object(item, update, types) + elif ( + self.opencti.get_attribute_in_extension("location_type", item) + is not None + ): + if ( self.opencti.get_attribute_in_extension( "location_type", item - ) - is not None + ).lower() + in types ): - if ( - self.opencti.get_attribute_in_extension( - "location_type", item - ).lower() - in types - ): - self.import_object(item, update, types) - if work_id is not None: - self.opencti.work.report_expectation(work_id, None) - bundles_success_counter.add(1) - return True - except (RequestException, Timeout): - bundles_timeout_error_counter.add(1) - worker_logger.warning("A connection error or timeout occurred") - # Platform is under heavy load: wait for unlock & retry almost indefinitely. - sleep_jitter = round(random.uniform(10, 30), 2) - time.sleep(sleep_jitter) - return self.import_item(item, update, types, processing_count + 1, work_id) - except Exception as ex: # pylint: disable=broad-except - error = str(ex) - error_msg = traceback.format_exc() - in_retry = processing_count < PROCESSING_COUNT - # Platform is under heavy load, wait for unlock & retry indefinitely. - if ERROR_TYPE_LOCK in error_msg: - bundles_lock_error_counter.add(1) - sleep_jitter = round(random.uniform(1, 3), 2) - time.sleep(sleep_jitter) - return self.import_item( - item, update, types, processing_count + 1, work_id - ) - # Platform detects a missing reference and have to retry - elif ERROR_TYPE_MISSING_REFERENCE in error_msg and in_retry: - bundles_missing_reference_error_counter.add(1) - sleep_jitter = round(random.uniform(1, 3), 2) + self.import_object(item, update, types) + if work_id is not None: + self.opencti.work.report_expectation(work_id, None) + bundles_success_counter.add(1) + return True + + def import_item_with_retries( + self, + item, + update: bool = False, + types: List = None, + work_id: str = None, + ): + processing_count = 0 + worker_logger = self.opencti.logger_class("worker") + while processing_count <= MAX_PROCESSING_COUNT: + try: + self.opencti.set_retry_number(processing_count) + self.import_item(item, update, types, work_id) + return None + except (RequestException, Timeout): + bundles_timeout_error_counter.add(1) + worker_logger.warning("A connection error or timeout occurred") + # Platform is under heavy load: wait for unlock & retry almost indefinitely. + sleep_jitter = round(random.uniform(10, 30), 2) time.sleep(sleep_jitter) - return self.import_item( - item, update, types, processing_count + 1, work_id - ) - # A bad gateway error occurs - elif ERROR_TYPE_BAD_GATEWAY in error_msg: - worker_logger.error( - "Message reprocess for bad gateway", - {"count": processing_count}, - ) - bundles_bad_gateway_error_counter.add(1) - time.sleep(60) - return self.import_item( - item, update, types, processing_count + 1, work_id - ) - # Request timeout error occurs - elif ERROR_TYPE_TIMEOUT in error_msg: - worker_logger.error( - "Message reprocess for request timed out", - {"count": processing_count}, - ) - bundles_timed_out_error_counter.add(1) - time.sleep(60) - return self.import_item( - item, update, types, processing_count + 1, work_id - ) - # A draft lock error occurs - elif ERROR_TYPE_DRAFT_LOCK in error_msg: - bundles_technical_error_counter.add(1) - if work_id is not None: - self.opencti.work.api.set_draft_id("") - self.opencti.work.report_expectation( - work_id, - { - "error": error, - "source": "Draft in read only", - }, + processing_count += 1 + except Exception as ex: # pylint: disable=broad-except + error = str(ex) + error_msg = traceback.format_exc() + in_retry = processing_count < PROCESSING_COUNT + # Platform is under heavy load, wait for unlock & retry indefinitely. + if ERROR_TYPE_LOCK in error_msg: + bundles_lock_error_counter.add(1) + sleep_jitter = round(random.uniform(1, 3), 2) + time.sleep(sleep_jitter) + processing_count += 1 + # Platform detects a missing reference and have to retry + elif ERROR_TYPE_MISSING_REFERENCE in error_msg and in_retry: + bundles_missing_reference_error_counter.add(1) + sleep_jitter = round(random.uniform(1, 3), 2) + time.sleep(sleep_jitter) + processing_count += 1 + # A bad gateway error occurs + elif ERROR_TYPE_BAD_GATEWAY in error_msg: + worker_logger.error( + "Message reprocess for bad gateway", + {"count": processing_count}, ) - return False - # Platform does not know what to do and raises an error: - # That also works for missing reference with too much execution - else: - bundles_technical_error_counter.add(1) - if work_id is not None: - item_str = json.dumps(item) - self.opencti.work.report_expectation( - work_id, - { - "error": error, - "source": ( - item_str - if len(item_str) < 50000 - else "Bundle too large" - ), - }, + bundles_bad_gateway_error_counter.add(1) + time.sleep(60) + processing_count += 1 + # Request timeout error occurs + elif ERROR_TYPE_TIMEOUT in error_msg: + worker_logger.error( + "Message reprocess for request timed out", + {"count": processing_count}, ) - return False + bundles_timed_out_error_counter.add(1) + time.sleep(60) + processing_count += 1 + # A draft lock error occurs + elif ERROR_TYPE_DRAFT_LOCK in error_msg: + bundles_technical_error_counter.add(1) + if work_id is not None: + self.opencti.work.api.set_draft_id("") + self.opencti.work.report_expectation( + work_id, + { + "error": error, + "source": "Draft in read only", + }, + ) + return None + # Platform does not know what to do and raises an error: + # That also works for missing reference with too much execution + else: + bundles_technical_error_counter.add(1) + worker_logger.error( + "Unrecognized error during bundle import", {"error": error} + ) + if work_id is not None: + item_str = json.dumps(item) + self.opencti.work.report_expectation( + work_id, + { + "error": error, + "source": ( + item_str + if len(item_str) < 50000 + else "Bundle too large" + ), + }, + ) + return None + + max_retry_error_message = "Max number of retries reached, please see error logs of workers for more details. Bundle will be sent to dead letter queue." + worker_logger.error(max_retry_error_message) + if work_id is not None: + item_str = json.dumps(item) + self.opencti.work.report_expectation( + work_id, + { + "error": max_retry_error_message, + "source": ( + item_str if len(item_str) < 50000 else "Bundle too large" + ), + }, + ) + return item def import_bundle( self, @@ -3144,8 +3135,15 @@ def import_bundle( ) too_large_elements_bundles.append(item) else: - self.import_item(item, update, types, 0, work_id) - imported_elements.append({"id": item["id"], "type": item["type"]}) + failed_item = self.import_item_with_retries( + item, update, types, work_id + ) + if failed_item is not None: + too_large_elements_bundles.append(item) + else: + imported_elements.append( + {"id": item["id"], "type": item["type"]} + ) return imported_elements, too_large_elements_bundles From 99694fdeb8f386624af6201527e6b23215a84501 Mon Sep 17 00:00:00 2001 From: Samuel Hassine Date: Thu, 8 Jan 2026 22:06:26 +0200 Subject: [PATCH 057/126] [client] Add support of file download during import process (#13944) --- client-python/pycti/utils/opencti_stix2.py | 89 +++++++++++++++++++--- 1 file changed, 77 insertions(+), 12 deletions(-) diff --git a/client-python/pycti/utils/opencti_stix2.py b/client-python/pycti/utils/opencti_stix2.py index ed491dfdda1c..c32ef30c282f 100644 --- a/client-python/pycti/utils/opencti_stix2.py +++ b/client-python/pycti/utils/opencti_stix2.py @@ -601,12 +601,22 @@ def extract_embedded_relationships( )["id"] if "x_opencti_files" in external_reference: for file in external_reference["x_opencti_files"]: + data = None if "data" in file: + data = base64.b64decode(file["data"]) + elif "uri" in file: + url = self.opencti.api_url.replace( + "/graphql", file["uri"] + ) + data = self.opencti.fetch_opencti_file( + fetch_uri=url, binary=True, serialize=False + ) + if data is not None: self.opencti.external_reference.add_file( id=external_reference_id, file_name=file["name"], version=file.get("version", None), - data=base64.b64decode(file["data"]), + data=data, fileMarkings=file.get("object_marking_refs", None), mime_type=file["mime_type"], no_trigger_import=file.get( @@ -622,12 +632,22 @@ def extract_embedded_relationships( for file in self.opencti.get_attribute_in_extension( "files", external_reference ): + data = None if "data" in file: + data = base64.b64decode(file["data"]) + elif "uri" in file: + url = self.opencti.api_url.replace( + "/graphql", file["uri"] + ) + data = self.opencti.fetch_opencti_file( + fetch_uri=url, binary=True, serialize=False + ) + if data is not None: self.opencti.external_reference.add_file( id=external_reference_id, file_name=file["name"], version=file.get("version", None), - data=base64.b64decode(file["data"]), + data=data, fileMarkings=file.get("object_marking_refs", None), mime_type=file["mime_type"], no_trigger_import=file.get( @@ -779,12 +799,20 @@ def extract_embedded_relationships( )["id"] if "x_opencti_files" in external_reference: for file in external_reference["x_opencti_files"]: + data = None if "data" in file: + data = base64.b64decode(file["data"]) + elif "uri" in file: + url = self.opencti.api_url.replace("/graphql", file["uri"]) + data = self.opencti.fetch_opencti_file( + fetch_uri=url, binary=True, serialize=False + ) + if data is not None: self.opencti.external_reference.add_file( id=external_reference_id, file_name=file["name"], version=file.get("version", None), - data=base64.b64decode(file["data"]), + data=data, fileMarkings=file.get("object_marking_refs", None), mime_type=file["mime_type"], no_trigger_import=file.get("no_trigger_import", False), @@ -797,12 +825,20 @@ def extract_embedded_relationships( for file in self.opencti.get_attribute_in_extension( "files", external_reference ): + data = None if "data" in file: + data = base64.b64decode(file["data"]) + elif "uri" in file: + url = self.opencti.api_url.replace("/graphql", file["uri"]) + data = self.opencti.fetch_opencti_file( + fetch_uri=url, binary=True, serialize=False + ) + if data is not None: self.opencti.external_reference.add_file( id=external_reference_id, file_name=file["name"], version=file.get("version", None), - data=base64.b64decode(file["data"]), + data=data, fileMarkings=file.get("object_marking_refs", None), mime_type=file["mime_type"], no_trigger_import=file.get("no_trigger_import", False), @@ -1110,12 +1146,20 @@ def import_object( # Add files if "x_opencti_files" in stix_object: for file in stix_object["x_opencti_files"]: + data = None if "data" in file: + data = base64.b64decode(file["data"]) + elif "uri" in file: + url = self.opencti.api_url.replace("/graphql", file["uri"]) + data = self.opencti.fetch_opencti_file( + fetch_uri=url, binary=True, serialize=False + ) + if data is not None: self.opencti.stix_domain_object.add_file( id=stix_object_result["id"], file_name=file["name"], version=file.get("version", None), - data=base64.b64decode(file["data"]), + data=data, fileMarkings=file.get("object_marking_refs", None), mime_type=file["mime_type"], no_trigger_import=file.get("no_trigger_import", False), @@ -1128,12 +1172,20 @@ def import_object( for file in self.opencti.get_attribute_in_extension( "files", stix_object ): + data = None if "data" in file: + data = base64.b64decode(file["data"]) + elif "uri" in file: + url = self.opencti.api_url.replace("/graphql", file["uri"]) + data = self.opencti.fetch_opencti_file( + fetch_uri=url, binary=True, serialize=False + ) + if data is not None: self.opencti.stix_domain_object.add_file( id=stix_object_result["id"], file_name=file["name"], version=file.get("version", None), - data=base64.b64decode(file["data"]), + data=data, fileMarkings=file.get("object_marking_refs", None), mime_type=file["mime_type"], no_trigger_import=file.get("no_trigger_import", False), @@ -1241,12 +1293,20 @@ def import_observable( # Add files if "x_opencti_files" in stix_object: for file in stix_object["x_opencti_files"]: + data = None if "data" in file: + data = base64.b64decode(file["data"]) + elif "uri" in file: + url = self.opencti.api_url.replace("/graphql", file["uri"]) + data = self.opencti.fetch_opencti_file( + fetch_uri=url, binary=True, serialize=False + ) + if data is not None: self.opencti.stix_cyber_observable.add_file( id=stix_observable_result["id"], file_name=file["name"], version=file.get("version", None), - data=base64.b64decode(file["data"]), + data=data, fileMarkings=file.get("object_marking_refs", None), mime_type=file["mime_type"], no_trigger_import=file.get("no_trigger_import", False), @@ -1259,12 +1319,20 @@ def import_observable( for file in self.opencti.get_attribute_in_extension( "files", stix_object ): + data = None if "data" in file: + data = base64.b64decode(file["data"]) + elif "uri" in file: + url = self.opencti.api_url.replace("/graphql", file["uri"]) + data = self.opencti.fetch_opencti_file( + fetch_uri=url, binary=True, serialize=False + ) + if data is not None: self.opencti.stix_cyber_observable.add_file( id=stix_observable_result["id"], file_name=file["name"], version=file.get("version", None), - data=base64.b64decode(file["data"]), + data=data, fileMarkings=file.get("object_marking_refs", None), mime_type=file["mime_type"], no_trigger_import=file.get("no_trigger_import", False), @@ -1711,10 +1779,7 @@ def generate_export(self, entity: Dict, no_custom_attributes: bool = False) -> D ): external_reference["x_opencti_files"] = [] for file in entity_external_reference["importFiles"]: - url = ( - self.opencti.api_url.replace("graphql", "storage/get/") - + file["id"] - ) + url = self.opencti.api_url.replace("/graphql", file["uri"]) data = self.opencti.fetch_opencti_file( url, binary=True, serialize=True ) From fcdc0371b433f8027902513e327d0f5c593440be Mon Sep 17 00:00:00 2001 From: Jeremy Cloarec <159018898+JeremyCloarec@users.noreply.github.com> Date: Fri, 9 Jan 2026 09:15:52 +0100 Subject: [PATCH 058/126] [backend] fix some mismatch between graphql API and entities from schema(#11800) --- .../src/schema/relay.schema.graphql | 240 ++++----- .../config/schema/opencti.graphql | 164 +++--- .../opencti-graphql/src/generated/graphql.ts | 480 +++++++++--------- .../administrativeArea.graphql | 2 +- .../case/case-incident/case-incident.graphql | 2 +- .../modules/case/case-rfi/case-rfi.graphql | 2 +- .../modules/case/case-rft/case-rft.graphql | 2 +- .../case/case-template/case-template.graphql | 2 +- .../src/modules/case/case.graphql | 2 +- .../modules/case/feedback/feedback.graphql | 2 +- .../src/modules/channel/channel.graphql | 2 +- .../dataComponent/dataComponent.graphql | 2 +- .../src/modules/dataSource/dataSource.graphql | 2 +- .../src/modules/decayRule/decayRule.graphql | 2 +- .../exclusions/decayExclusionRule.graphql | 2 +- .../disseminationList.graphql | 2 +- .../emailTemplate/emailTemplate.graphql | 2 +- .../src/modules/event/event.graphql | 2 +- .../exclusionList/exclusionList.graphql | 2 +- .../modules/fintelDesign/fintelDesign.graphql | 2 +- .../fintelTemplate/fintelTemplate.graphql | 2 +- .../src/modules/grouping/grouping.graphql | 2 +- .../src/modules/indicator/indicator.graphql | 2 +- .../modules/ingestion/ingestion-csv.graphql | 2 +- .../modules/ingestion/ingestion-json.graphql | 2 +- .../internal/csvMapper/csvMapper.graphql | 2 +- .../src/modules/language/language.graphql | 2 +- .../malwareAnalysis/malwareAnalysis.graphql | 2 +- .../src/modules/narrative/narrative.graphql | 2 +- .../modules/notification/notification.graphql | 4 +- .../src/modules/notifier/notifier-statics.ts | 10 +- .../src/modules/notifier/notifier.graphql | 2 +- .../modules/organization/organization.graphql | 2 +- .../src/modules/pir/pir.graphql | 2 +- .../modules/savedFilter/savedFilter.graphql | 2 +- .../securityCoverage/securityCoverage.graphql | 2 +- .../securityPlatform/securityPlatform.graphql | 2 +- .../src/modules/support/support.graphql | 2 +- .../task/task-template/task-template.graphql | 2 +- .../src/modules/task/task.graphql | 2 +- .../threatActorIndividual.graphql | 2 +- .../src/modules/vocabulary/vocabulary.graphql | 2 +- .../src/resolvers/basicObject.ts | 3 + .../opencti-graphql/src/resolvers/stix.js | 5 + .../src/resolvers/stixMetaObject.js | 5 +- .../02-resolvers/threatActorGroup-test.js | 19 + 46 files changed, 521 insertions(+), 483 deletions(-) diff --git a/opencti-platform/opencti-front/src/schema/relay.schema.graphql b/opencti-platform/opencti-front/src/schema/relay.schema.graphql index c853adfe8fd9..2bc5ad6a0638 100644 --- a/opencti-platform/opencti-front/src/schema/relay.schema.graphql +++ b/opencti-platform/opencti-front/src/schema/relay.schema.graphql @@ -1361,7 +1361,7 @@ interface BasicObject { id: ID! standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics: [Metric] } @@ -1636,7 +1636,7 @@ type Group implements InternalObject & BasicObject { standard_id: String! entity_type: String! auto_integration_assignation: [String]! - parent_types: [String]! + parent_types: [String!]! metrics: [Metric] name: String! default_assignation: Boolean @@ -1830,7 +1830,7 @@ type User implements BasicObject & InternalObject { id: ID! standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics: [Metric] user_email: String! api_token: String! @@ -2015,7 +2015,7 @@ type Role implements BasicObject & InternalObject { id: ID! standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics: [Metric] name: String! description: String @@ -2049,7 +2049,7 @@ type Capability implements BasicObject & InternalObject { id: ID! standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics: [Metric] name: String! description: String @@ -2293,7 +2293,7 @@ type Connector implements BasicObject & InternalObject { id: ID! standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics: [Metric] name: String! title: String! @@ -2340,7 +2340,7 @@ type ManagedConnector implements BasicObject & InternalObject { id: ID! standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics: [Metric] name: String! connector_user_id: ID @@ -2368,7 +2368,7 @@ type ConnectorManager implements BasicObject & InternalObject { id: ID! standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics: [Metric] name: String! public_key: String! @@ -2443,7 +2443,7 @@ interface StixObject { id: ID! standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics: [Metric] representative: Representative! x_opencti_stix_ids: [StixId] @@ -2482,7 +2482,7 @@ interface StixMetaObject { id: ID! standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics: [Metric] x_opencti_stix_ids: [StixId] is_inferred: Boolean! @@ -2521,7 +2521,7 @@ type MarkingDefinition implements BasicObject & StixObject & StixMetaObject { id: ID! standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics: [Metric] representative: Representative! x_opencti_stix_ids: [StixId] @@ -2593,7 +2593,7 @@ type Label implements BasicObject & StixObject & StixMetaObject { id: ID! standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics: [Metric] representative: Representative! x_opencti_stix_ids: [StixId] @@ -2653,7 +2653,7 @@ type ExternalReference implements BasicObject & StixObject & StixMetaObject { id: ID! standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics: [Metric] representative: Representative! x_opencti_stix_ids: [StixId] @@ -2726,7 +2726,7 @@ type KillChainPhase implements BasicObject & StixObject & StixMetaObject { id: ID! standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics: [Metric] representative: Representative! x_opencti_stix_ids: [StixId] @@ -2816,7 +2816,7 @@ interface StixCoreObject { id: ID! standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics: [Metric] representative: Representative! x_opencti_stix_ids: [StixId] @@ -2917,7 +2917,7 @@ interface StixDomainObject { id: ID! standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics: [Metric] representative: Representative! x_opencti_stix_ids: [StixId] @@ -3021,7 +3021,7 @@ type AttackPattern implements BasicObject & StixObject & StixCoreObject & StixDo id: ID! standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics: [Metric] representative: Representative! x_opencti_stix_ids: [StixId] @@ -3173,7 +3173,7 @@ type Campaign implements BasicObject & StixObject & StixCoreObject & StixDomainO id: ID! standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics: [Metric] representative: Representative! x_opencti_stix_ids: [StixId] @@ -3290,7 +3290,7 @@ interface Container { id: ID! standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics: [Metric] representative: Representative! x_opencti_stix_ids: [StixId] @@ -3367,7 +3367,7 @@ type Note implements BasicObject & StixObject & StixCoreObject & StixDomainObjec id: ID! standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics: [Metric] representative: Representative! x_opencti_stix_ids: [StixId] @@ -3511,7 +3511,7 @@ type ObservedData implements BasicObject & StixObject & StixCoreObject & StixDom id: ID! standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics: [Metric] representative: Representative! x_opencti_stix_ids: [StixId] @@ -3626,7 +3626,7 @@ type Opinion implements BasicObject & StixObject & StixCoreObject & StixDomainOb id: ID! standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics: [Metric] representative: Representative! x_opencti_stix_ids: [StixId] @@ -3764,7 +3764,7 @@ type Report implements BasicObject & StixObject & StixCoreObject & StixDomainObj id: ID! standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics: [Metric] representative: Representative! x_opencti_stix_ids: [StixId] @@ -3891,7 +3891,7 @@ type CourseOfAction implements BasicObject & StixObject & StixCoreObject & StixD id: ID! standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics: [Metric] representative: Representative! x_opencti_stix_ids: [StixId] @@ -4007,7 +4007,7 @@ interface Identity { id: ID! standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics: [Metric] representative: Representative! x_opencti_stix_ids: [StixId] @@ -4114,7 +4114,7 @@ type Individual implements BasicObject & StixObject & StixCoreObject & StixDomai id: ID! standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics: [Metric] representative: Representative! x_opencti_stix_ids: [StixId] @@ -4233,7 +4233,7 @@ type Sector implements BasicObject & StixObject & StixCoreObject & StixDomainObj id: ID! standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics: [Metric] representative: Representative! x_opencti_stix_ids: [StixId] @@ -4347,7 +4347,7 @@ type System implements BasicObject & StixObject & StixCoreObject & StixDomainObj id: ID! standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics: [Metric] representative: Representative! x_opencti_stix_ids: [StixId] @@ -4470,7 +4470,7 @@ type Infrastructure implements BasicObject & StixObject & StixCoreObject & StixD id: ID! standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics: [Metric] representative: Representative! x_opencti_stix_ids: [StixId] @@ -4586,7 +4586,7 @@ type IntrusionSet implements BasicObject & StixObject & StixCoreObject & StixDom id: ID! standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics: [Metric] representative: Representative! x_opencti_stix_ids: [StixId] @@ -4708,7 +4708,7 @@ interface Location { id: ID! standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics: [Metric] representative: Representative! x_opencti_stix_ids: [StixId] @@ -4816,7 +4816,7 @@ type Position implements BasicObject & StixObject & StixCoreObject & StixDomainO id: ID! standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics: [Metric] representative: Representative! x_opencti_stix_ids: [StixId] @@ -4933,7 +4933,7 @@ type City implements BasicObject & StixObject & StixCoreObject & StixDomainObjec id: ID! standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics: [Metric] representative: Representative! x_opencti_stix_ids: [StixId] @@ -5045,7 +5045,7 @@ type Country implements BasicObject & StixObject & StixCoreObject & StixDomainOb id: ID! standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics: [Metric] representative: Representative! x_opencti_stix_ids: [StixId] @@ -5155,7 +5155,7 @@ type Region implements BasicObject & StixObject & StixCoreObject & StixDomainObj id: ID! standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics: [Metric] representative: Representative! x_opencti_stix_ids: [StixId] @@ -5279,7 +5279,7 @@ type Malware implements BasicObject & StixObject & StixCoreObject & StixDomainOb id: ID! standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics: [Metric] representative: Representative! x_opencti_stix_ids: [StixId] @@ -5407,7 +5407,7 @@ interface ThreatActor implements BasicObject & StixObject & StixCoreObject & Sti id: ID! standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics: [Metric] representative: Representative! x_opencti_stix_ids: [StixId] @@ -5488,7 +5488,7 @@ type ThreatActorGroup implements BasicObject & StixObject & StixCoreObject & Sti id: ID! standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics: [Metric] representative: Representative! x_opencti_stix_ids: [StixId] @@ -5616,7 +5616,7 @@ type Tool implements BasicObject & StixObject & StixCoreObject & StixDomainObjec id: ID! standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics: [Metric] representative: Representative! x_opencti_stix_ids: [StixId] @@ -5731,7 +5731,7 @@ type Vulnerability implements BasicObject & StixObject & StixCoreObject & StixDo id: ID! standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics: [Metric] representative: Representative! x_opencti_stix_ids: [StixId] @@ -5941,7 +5941,7 @@ type Incident implements BasicObject & StixObject & StixCoreObject & StixDomainO id: ID! standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics: [Metric] representative: Representative! x_opencti_stix_ids: [StixId] @@ -6061,7 +6061,7 @@ interface StixCyberObservable { id: ID! standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics: [Metric] representative: Representative! x_opencti_stix_ids: [StixId] @@ -6108,7 +6108,7 @@ type AutonomousSystem implements BasicObject & StixObject & StixCoreObject & Sti id: ID! standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics: [Metric] representative: Representative! x_opencti_stix_ids: [StixId] @@ -6166,7 +6166,7 @@ type Directory implements BasicObject & StixObject & StixCoreObject & StixCyberO id: ID! standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics: [Metric] representative: Representative! x_opencti_stix_ids: [StixId] @@ -6228,7 +6228,7 @@ type DomainName implements BasicObject & StixObject & StixCoreObject & StixCyber id: ID! standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics: [Metric] representative: Representative! x_opencti_stix_ids: [StixId] @@ -6282,7 +6282,7 @@ type EmailAddr implements BasicObject & StixObject & StixCoreObject & StixCyberO id: ID! standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics: [Metric] representative: Representative! x_opencti_stix_ids: [StixId] @@ -6338,7 +6338,7 @@ type EmailMessage implements BasicObject & StixObject & StixCoreObject & StixCyb id: ID! standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics: [Metric] representative: Representative! x_opencti_stix_ids: [StixId] @@ -6404,7 +6404,7 @@ type EmailMimePartType implements BasicObject & StixObject & StixCoreObject & St id: ID! standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics: [Metric] representative: Representative! x_opencti_stix_ids: [StixId] @@ -6485,7 +6485,7 @@ interface HashedObservable { id: ID! standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics: [Metric] representative: Representative! x_opencti_stix_ids: [StixId] @@ -6533,7 +6533,7 @@ type Artifact implements BasicObject & StixObject & StixCoreObject & StixCyberOb id: ID! standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics: [Metric] representative: Representative! x_opencti_stix_ids: [StixId] @@ -6599,7 +6599,7 @@ type StixFile implements BasicObject & StixObject & StixCoreObject & StixCyberOb id: ID! standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics: [Metric] representative: Representative! x_opencti_stix_ids: [StixId] @@ -6674,7 +6674,7 @@ type X509Certificate implements BasicObject & StixObject & StixCoreObject & Stix id: ID! standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics: [Metric] representative: Representative! x_opencti_stix_ids: [StixId] @@ -6782,7 +6782,7 @@ type IPv4Addr implements BasicObject & StixObject & StixCoreObject & StixCyberOb id: ID! standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics: [Metric] representative: Representative! x_opencti_stix_ids: [StixId] @@ -6839,7 +6839,7 @@ type IPv6Addr implements BasicObject & StixObject & StixCoreObject & StixCyberOb id: ID! standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics: [Metric] representative: Representative! x_opencti_stix_ids: [StixId] @@ -6894,7 +6894,7 @@ type MacAddr implements BasicObject & StixObject & StixCoreObject & StixCyberObs id: ID! standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics: [Metric] representative: Representative! x_opencti_stix_ids: [StixId] @@ -6948,7 +6948,7 @@ type Mutex implements BasicObject & StixObject & StixCoreObject & StixCyberObser id: ID! standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics: [Metric] representative: Representative! x_opencti_stix_ids: [StixId] @@ -7002,7 +7002,7 @@ type NetworkTraffic implements BasicObject & StixObject & StixCoreObject & StixC id: ID! standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics: [Metric] representative: Representative! x_opencti_stix_ids: [StixId] @@ -7079,7 +7079,7 @@ type Process implements BasicObject & StixObject & StixCoreObject & StixCyberObs id: ID! standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics: [Metric] representative: Representative! x_opencti_stix_ids: [StixId] @@ -7175,7 +7175,7 @@ type Software implements BasicObject & StixObject & StixCoreObject & StixCyberOb id: ID! standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics: [Metric] representative: Representative! x_opencti_stix_ids: [StixId] @@ -7252,7 +7252,7 @@ type Url implements BasicObject & StixObject & StixCoreObject & StixCyberObserva id: ID! standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics: [Metric] representative: Representative! x_opencti_stix_ids: [StixId] @@ -7306,7 +7306,7 @@ type UserAccount implements BasicObject & StixObject & StixCoreObject & StixCybe id: ID! standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics: [Metric] representative: Representative! x_opencti_stix_ids: [StixId] @@ -7387,7 +7387,7 @@ type WindowsRegistryKey implements BasicObject & StixObject & StixCoreObject & S id: ID! standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics: [Metric] representative: Representative! x_opencti_stix_ids: [StixId] @@ -7445,7 +7445,7 @@ type WindowsRegistryValueType implements BasicObject & StixObject & StixCoreObje id: ID! standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics: [Metric] representative: Representative! x_opencti_stix_ids: [StixId] @@ -7503,7 +7503,7 @@ type CryptographicKey implements BasicObject & StixObject & StixCoreObject & Sti id: ID! standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics: [Metric] representative: Representative! x_opencti_stix_ids: [StixId] @@ -7557,7 +7557,7 @@ type CryptocurrencyWallet implements BasicObject & StixObject & StixCoreObject & id: ID! standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics: [Metric] representative: Representative! x_opencti_stix_ids: [StixId] @@ -7611,7 +7611,7 @@ type Hostname implements BasicObject & StixObject & StixCoreObject & StixCyberOb id: ID! standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics: [Metric] representative: Representative! x_opencti_stix_ids: [StixId] @@ -7665,7 +7665,7 @@ type Text implements BasicObject & StixObject & StixCoreObject & StixCyberObserv id: ID! standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics: [Metric] representative: Representative! x_opencti_stix_ids: [StixId] @@ -7719,7 +7719,7 @@ type UserAgent implements BasicObject & StixObject & StixCoreObject & StixCyberO id: ID! standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics: [Metric] representative: Representative! x_opencti_stix_ids: [StixId] @@ -7773,7 +7773,7 @@ type BankAccount implements BasicObject & StixObject & StixCoreObject & StixCybe id: ID! standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics: [Metric] representative: Representative! x_opencti_stix_ids: [StixId] @@ -7831,7 +7831,7 @@ type TrackingNumber implements BasicObject & StixObject & StixCoreObject & StixC id: ID! standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics: [Metric] representative: Representative! x_opencti_stix_ids: [StixId] @@ -7885,7 +7885,7 @@ type Credential implements BasicObject & StixObject & StixCoreObject & StixCyber id: ID! standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics: [Metric] representative: Representative! x_opencti_stix_ids: [StixId] @@ -7939,7 +7939,7 @@ type PhoneNumber implements BasicObject & StixObject & StixCoreObject & StixCybe id: ID! standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics: [Metric] representative: Representative! x_opencti_stix_ids: [StixId] @@ -7993,7 +7993,7 @@ type PaymentCard implements BasicObject & StixObject & StixCoreObject & StixCybe id: ID! standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics: [Metric] representative: Representative! x_opencti_stix_ids: [StixId] @@ -8053,7 +8053,7 @@ type MediaContent implements BasicObject & StixObject & StixCoreObject & StixCyb id: ID! standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics: [Metric] representative: Representative! x_opencti_stix_ids: [StixId] @@ -8116,7 +8116,7 @@ type Persona implements BasicObject & StixObject & StixCoreObject & StixCyberObs id: ID! standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics: [Metric] representative: Representative! x_opencti_stix_ids: [StixId] @@ -8172,7 +8172,7 @@ type SSHKey implements BasicObject & StixObject & StixCoreObject & StixCyberObse id: ID! standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics: [Metric] representative: Representative! x_opencti_stix_ids: [StixId] @@ -8243,7 +8243,7 @@ interface BasicRelationship { id: ID! standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics: [Metric] fromRole: String toRole: String @@ -8257,7 +8257,7 @@ type InternalRelationship implements BasicRelationship { id: ID! standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics: [Metric] fromRole: String toRole: String @@ -8357,7 +8357,7 @@ interface StixRelationship { id: ID! standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics: [Metric] fromRole: String toRole: String @@ -8435,7 +8435,7 @@ type StixCoreRelationship implements BasicRelationship & StixRelationship { id: ID! standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics: [Metric] fromRole: String toRole: String @@ -8553,7 +8553,7 @@ type StixSightingRelationship implements BasicRelationship & StixRelationship { id: ID! standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics: [Metric] fromRole: String toRole: String @@ -8658,7 +8658,7 @@ type StixRefRelationship implements BasicRelationship & StixRelationship { id: ID! standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics: [Metric] fromRole: String toRole: String @@ -10044,7 +10044,7 @@ type Channel implements BasicObject & StixObject & StixCoreObject & StixDomainOb id: ID! standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics: [Metric] representative: Representative! x_opencti_stix_ids: [StixId] @@ -10185,7 +10185,7 @@ type Language implements BasicObject & StixCoreObject & StixDomainObject & StixO id: ID! standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics: [Metric] representative: Representative! x_opencti_stix_ids: [StixId] @@ -10289,7 +10289,7 @@ type Event implements BasicObject & StixCoreObject & StixDomainObject & StixObje id: ID! standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics: [Metric] representative: Representative! x_opencti_stix_ids: [StixId] @@ -10403,7 +10403,7 @@ type Grouping implements BasicObject & StixObject & StixCoreObject & StixDomainO id: ID! standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics: [Metric] representative: Representative! x_opencti_stix_ids: [StixId] @@ -10529,7 +10529,7 @@ type Narrative implements BasicObject & StixCoreObject & StixDomainObject & Stix id: ID! standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics: [Metric] representative: Representative! x_opencti_stix_ids: [StixId] @@ -10696,7 +10696,7 @@ type Trigger implements InternalObject & BasicObject { id: ID! standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics: [Metric] created: DateTime modified: DateTime @@ -10786,7 +10786,7 @@ type Notification implements InternalObject & BasicObject { created: DateTime name: String! notification_type: String! - parent_types: [String]! + parent_types: [String!]! metrics: [Metric] notification_content: [NotificationContent!]! is_read: Boolean! @@ -10810,7 +10810,7 @@ type DataComponent implements BasicObject & StixObject & StixCoreObject & StixDo id: ID! standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics: [Metric] representative: Representative! x_opencti_stix_ids: [StixId] @@ -10918,7 +10918,7 @@ type DataSource implements BasicObject & StixObject & StixCoreObject & StixDomai id: ID! standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics: [Metric] representative: Representative! x_opencti_stix_ids: [StixId] @@ -11093,7 +11093,7 @@ type Vocabulary implements BasicObject & StixObject & StixMetaObject { id: ID! standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics: [Metric] representative: Representative! creators: [Creator!] @@ -11155,7 +11155,7 @@ type AdministrativeArea implements BasicObject & StixCoreObject & StixDomainObje id: ID! standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics: [Metric] representative: Representative! x_opencti_stix_ids: [StixId] @@ -11267,7 +11267,7 @@ type Task implements Container & StixDomainObject & StixCoreObject & StixObject id: ID! standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics: [Metric] representative: Representative! x_opencti_stix_ids: [StixId] @@ -11379,7 +11379,7 @@ type TaskTemplate implements InternalObject & BasicObject { id: ID! standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics: [Metric] created: DateTime modified: DateTime @@ -11418,7 +11418,7 @@ interface Case implements BasicObject & StixObject & StixCoreObject & StixDomain id: ID! standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics: [Metric] representative: Representative! x_opencti_stix_ids: [StixId] @@ -11511,7 +11511,7 @@ type CaseTemplate implements InternalObject & BasicObject { id: ID! standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics: [Metric] created: DateTime modified: DateTime @@ -11549,7 +11549,7 @@ type CaseIncident implements BasicObject & StixObject & StixCoreObject & StixDom id: ID! standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics: [Metric] representative: Representative! x_opencti_stix_ids: [StixId] @@ -11689,7 +11689,7 @@ type CaseRfi implements BasicObject & StixObject & StixCoreObject & StixDomainOb id: ID! standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics: [Metric] representative: Representative! x_opencti_stix_ids: [StixId] @@ -11825,7 +11825,7 @@ type CaseRft implements BasicObject & StixObject & StixCoreObject & StixDomainOb id: ID! standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics: [Metric] representative: Representative! x_opencti_stix_ids: [StixId] @@ -11958,7 +11958,7 @@ type Feedback implements BasicObject & StixObject & StixCoreObject & StixDomainO id: ID! standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics: [Metric] representative: Representative! x_opencti_stix_ids: [StixId] @@ -12233,7 +12233,7 @@ type MalwareAnalysis implements BasicObject & StixCoreObject & StixDomainObject id: ID! standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics: [Metric] representative: Representative! x_opencti_stix_ids: [StixId] @@ -12400,7 +12400,7 @@ type Notifier implements InternalObject & BasicObject { id: ID! standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics: [Metric] created: DateTime modified: DateTime @@ -12452,7 +12452,7 @@ type ThreatActorIndividual implements BasicObject & StixObject & StixCoreObject id: ID! standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics: [Metric] representative: Representative! x_opencti_stix_ids: [StixId] @@ -12895,7 +12895,7 @@ type IngestionCsv implements InternalObject & BasicObject { id: ID! entity_type: String! standard_id: String! - parent_types: [String]! + parent_types: [String!]! metrics: [Metric] created_at: DateTime updated_at: DateTime @@ -13001,7 +13001,7 @@ type IngestionJson implements InternalObject & BasicObject { entity_type: String! connector_id: String! standard_id: String! - parent_types: [String]! + parent_types: [String!]! metrics: [Metric] created_at: DateTime updated_at: DateTime @@ -13157,7 +13157,7 @@ type Indicator implements BasicObject & StixObject & StixCoreObject & StixDomain id: ID! standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics: [Metric] representative: Representative! x_opencti_stix_ids: [StixId] @@ -13274,7 +13274,7 @@ type DecayRule implements InternalObject & BasicObject { id: ID! standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics: [Metric] created_at: DateTime! updated_at: DateTime! @@ -13330,7 +13330,7 @@ type DecayExclusionRule implements InternalObject & BasicObject { id: ID! standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics: [Metric] created_at: DateTime! name: String! @@ -13367,7 +13367,7 @@ type Organization implements BasicObject & StixObject & StixCoreObject & StixDom id: ID! standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics: [Metric] representative: Representative! x_opencti_stix_ids: [StixId] @@ -13994,7 +13994,7 @@ type SupportPackage implements InternalObject & BasicObject { name: String! standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics: [Metric] created_at: DateTime! package_status: PackageStatus! @@ -14043,7 +14043,7 @@ type ExclusionList implements InternalObject & BasicObject { description: String standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics: [Metric] created_at: DateTime! enabled: Boolean! @@ -14150,7 +14150,7 @@ type FintelTemplate implements BasicObject & InternalObject { id: ID! standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics: [Metric] name: String! settings_types: [String!]! @@ -14198,7 +14198,7 @@ type DisseminationList implements InternalObject & BasicObject { id: ID! standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics: [Metric] created_at: DateTime! updated_at: DateTime! @@ -14243,7 +14243,7 @@ type SavedFilter implements InternalObject & BasicObject { id: ID! standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics: [Metric] name: String! filters: String! @@ -14371,7 +14371,7 @@ type PirRelationship implements BasicRelationship { id: ID! standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics: [Metric] fromRole: String toRole: String @@ -14478,7 +14478,7 @@ type FintelDesign implements BasicObject & InternalObject { id: ID! standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics: [Metric] name: String! description: String @@ -14518,7 +14518,7 @@ type SecurityPlatform implements BasicObject & StixObject & StixCoreObject & Sti id: ID! standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics: [Metric] representative: Representative! x_opencti_stix_ids: [StixId] @@ -14628,7 +14628,7 @@ type SecurityCoverage implements BasicObject & StixObject & StixCoreObject & Sti id: ID! standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics: [Metric] representative: Representative! x_opencti_stix_ids: [StixId] @@ -14781,7 +14781,7 @@ type EmailTemplate implements InternalObject & BasicObject { id: ID! standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics: [Metric] name: String! description: String diff --git a/opencti-platform/opencti-graphql/config/schema/opencti.graphql b/opencti-platform/opencti-graphql/config/schema/opencti.graphql index ded84d3e020e..7a11bf4a2774 100644 --- a/opencti-platform/opencti-graphql/config/schema/opencti.graphql +++ b/opencti-platform/opencti-graphql/config/schema/opencti.graphql @@ -1272,7 +1272,7 @@ interface BasicObject { id: ID! # internal_id standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics:[Metric] } @@ -1554,7 +1554,7 @@ type Group implements InternalObject & BasicObject { standard_id: String! entity_type: String! auto_integration_assignation: [String]! - parent_types: [String]! + parent_types: [String!]! metrics:[Metric] # Group name: String! @@ -1736,7 +1736,7 @@ type User implements BasicObject & InternalObject { id: ID! # internal_id standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics:[Metric] # User user_email: String! @@ -1930,7 +1930,7 @@ type Role implements BasicObject & InternalObject { id: ID! # internal_id standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics:[Metric] # Role name: String! @@ -1962,7 +1962,7 @@ type Capability implements BasicObject & InternalObject { id: ID! # internal_id standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics:[Metric] # Capability name: String! @@ -2202,7 +2202,7 @@ type Connector implements BasicObject & InternalObject { id: ID! # internal_id standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics:[Metric] # Connector name: String! @@ -2250,7 +2250,7 @@ type ManagedConnector implements BasicObject & InternalObject { id: ID! # internal_id standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics:[Metric] # Connector name: String! @@ -2280,7 +2280,7 @@ type ConnectorManager implements BasicObject & InternalObject { id: ID! # internal_id standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics:[Metric] # Manager name: String! @@ -2356,7 +2356,7 @@ interface StixObject { id: ID! # internal_id standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics:[Metric] # StixObject representative: Representative! @@ -2396,7 +2396,7 @@ interface StixMetaObject { id: ID! # internal_id standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics:[Metric] # StixObject x_opencti_stix_ids: [StixId] @@ -2435,7 +2435,7 @@ type MarkingDefinition implements BasicObject & StixObject & StixMetaObject { id: ID! # internal_id standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics:[Metric] # StixObject representative: Representative! @@ -2508,7 +2508,7 @@ type Label implements BasicObject & StixObject & StixMetaObject { id: ID! # internal_id standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics:[Metric] # StixObject representative: Representative! @@ -2570,7 +2570,7 @@ type ExternalReference implements BasicObject & StixObject & StixMetaObject { id: ID! # internal_id standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics:[Metric] # StixObject representative: Representative! @@ -2658,7 +2658,7 @@ type KillChainPhase implements BasicObject & StixObject & StixMetaObject { id: ID! # internal_id standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics:[Metric] # StixObject representative: Representative! @@ -2749,7 +2749,7 @@ interface StixCoreObject { id: ID! # internal_id standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics:[Metric] # StixObject representative: Representative! @@ -2921,7 +2921,7 @@ interface StixDomainObject { id: ID! # internal_id standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics:[Metric] # StixObject representative: Representative! @@ -3093,7 +3093,7 @@ type AttackPattern implements BasicObject & StixObject & StixCoreObject & StixDo id: ID! # internal_id standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics:[Metric] # StixObject representative: Representative! @@ -3331,7 +3331,7 @@ type Campaign implements BasicObject & StixObject & StixCoreObject & StixDomainO id: ID! # internal_id standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics:[Metric] # StixObject representative: Representative! @@ -3518,7 +3518,7 @@ interface Container { id: ID! # internal_id standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics:[Metric] # StixObject representative: Representative! @@ -3678,7 +3678,7 @@ type Note implements BasicObject & StixObject & StixCoreObject & StixDomainObjec id: ID! # internal_id standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics:[Metric] # StixObject representative: Representative! @@ -3916,7 +3916,7 @@ type ObservedData implements BasicObject & StixObject & StixCoreObject & StixDom id: ID! # internal_id standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics:[Metric] # StixObject representative: Representative! @@ -4130,7 +4130,7 @@ type Opinion implements BasicObject & StixObject & StixCoreObject & StixDomainOb id: ID! # internal_id standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics:[Metric] # StixObject representative: Representative! @@ -4364,7 +4364,7 @@ type Report implements BasicObject & StixObject & StixCoreObject & StixDomainObj id: ID! # internal_id standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics:[Metric] # StixObject representative: Representative! @@ -4588,7 +4588,7 @@ type CourseOfAction implements BasicObject & StixObject & StixCoreObject & StixD id: ID! # internal_id standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics:[Metric] # StixObject representative: Representative! @@ -4773,7 +4773,7 @@ interface Identity { id: ID! # internal_id standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics:[Metric] # StixObject representative: Representative! @@ -4949,7 +4949,7 @@ type Individual implements BasicObject & StixObject & StixCoreObject & StixDomai id: ID! # internal_id standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics:[Metric] # StixObject representative: Representative! @@ -5139,7 +5139,7 @@ type Sector implements BasicObject & StixObject & StixCoreObject & StixDomainObj id: ID! # internal_id standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics:[Metric] # StixObject representative: Representative! @@ -5324,7 +5324,7 @@ type System implements BasicObject & StixObject & StixCoreObject & StixDomainObj id: ID! # internal_id standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics:[Metric] # StixObject representative: Representative! @@ -5518,7 +5518,7 @@ type Infrastructure implements BasicObject & StixObject & StixCoreObject & StixD id: ID! # internal_id standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics:[Metric] # StixObject representative: Representative! @@ -5704,7 +5704,7 @@ type IntrusionSet implements BasicObject & StixObject & StixCoreObject & StixDom id: ID! # internal_id standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics:[Metric] # StixObject representative: Representative! @@ -5896,7 +5896,7 @@ interface Location { id: ID! # internal_id standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics:[Metric] # StixObject representative: Representative! @@ -6073,7 +6073,7 @@ type Position implements BasicObject & StixObject & StixCoreObject & StixDomainO id: ID! # internal_id standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics:[Metric] # StixObject representative: Representative! @@ -6261,7 +6261,7 @@ type City implements BasicObject & StixObject & StixCoreObject & StixDomainObjec id: ID! # internal_id standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics:[Metric] # StixObject representative: Representative! @@ -6443,7 +6443,7 @@ type Country implements BasicObject & StixObject & StixCoreObject & StixDomainOb id: ID! # internal_id standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics:[Metric] # StixObject representative: Representative! @@ -6624,7 +6624,7 @@ type Region implements BasicObject & StixObject & StixCoreObject & StixDomainObj id: ID! # internal_id standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics:[Metric] # StixObject representative: Representative! @@ -6818,7 +6818,7 @@ type Malware implements BasicObject & StixObject & StixCoreObject & StixDomainOb id: ID! # internal_id standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics:[Metric] # StixObject representative: Representative! @@ -7018,7 +7018,7 @@ interface ThreatActor implements BasicObject & StixObject & StixCoreObject & Sti id: ID! # internal_id standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics:[Metric] # StixObject representative: Representative! @@ -7174,7 +7174,7 @@ type ThreatActorGroup implements BasicObject & StixObject & StixCoreObject & Sti id: ID! # internal_id standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics:[Metric] # StixObject representative: Representative! @@ -7373,7 +7373,7 @@ type Tool implements BasicObject & StixObject & StixCoreObject & StixDomainObjec id: ID! # internal_id standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics:[Metric] # StixObject representative: Representative! @@ -7558,7 +7558,7 @@ type Vulnerability implements BasicObject & StixObject & StixCoreObject & StixDo id: ID! # internal_id standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics:[Metric] # StixObject representative: Representative! @@ -7846,7 +7846,7 @@ type Incident implements BasicObject & StixObject & StixCoreObject & StixDomainO id: ID! # internal_id standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics:[Metric] # StixObject representative: Representative! @@ -8037,7 +8037,7 @@ interface StixCyberObservable { id: ID! # internal_id standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics:[Metric] # StixObject representative: Representative! @@ -8156,7 +8156,7 @@ type AutonomousSystem implements BasicObject & StixObject & StixCoreObject & Sti id: ID! # internal_id standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics:[Metric] # StixObject representative: Representative! @@ -8287,7 +8287,7 @@ type Directory implements BasicObject & StixObject & StixCoreObject & StixCyberO id: ID! # internal_id standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics:[Metric] # StixObject representative: Representative! @@ -8422,7 +8422,7 @@ type DomainName implements BasicObject & StixObject & StixCoreObject & StixCyber id: ID! # internal_id standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics:[Metric] # StixObject representative: Representative! @@ -8549,7 +8549,7 @@ type EmailAddr implements BasicObject & StixObject & StixCoreObject & StixCyberO id: ID! # internal_id standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics:[Metric] # StixObject representative: Representative! @@ -8678,7 +8678,7 @@ type EmailMessage implements BasicObject & StixObject & StixCoreObject & StixCyb id: ID! # internal_id standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics:[Metric] # StixObject representative: Representative! @@ -8817,7 +8817,7 @@ type EmailMimePartType implements BasicObject & StixObject & StixCoreObject & St id: ID! # internal_id standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics:[Metric] # StixObject representative: Representative! @@ -8966,7 +8966,7 @@ interface HashedObservable { id: ID! # internal_id standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics:[Metric] # StixObject representative: Representative! @@ -9087,7 +9087,7 @@ type Artifact implements BasicObject & StixObject & StixCoreObject & StixCyberOb id: ID! # internal_id standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics:[Metric] # StixObject representative: Representative! @@ -9227,7 +9227,7 @@ type StixFile implements BasicObject & StixObject & StixCoreObject & StixCyberOb id: ID! # internal_id standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics:[Metric] # StixObject representative: Representative! @@ -9376,7 +9376,7 @@ type X509Certificate implements BasicObject & StixObject & StixCoreObject & Stix id: ID! # internal_id standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics:[Metric] # StixObject representative: Representative! @@ -9560,7 +9560,7 @@ type IPv4Addr implements BasicObject & StixObject & StixCoreObject & StixCyberOb id: ID! # internal_id standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics:[Metric] # StixObject representative: Representative! @@ -9690,7 +9690,7 @@ type IPv6Addr implements BasicObject & StixObject & StixCoreObject & StixCyberOb id: ID! # internal_id standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics:[Metric] # StixObject representative: Representative! @@ -9818,7 +9818,7 @@ type MacAddr implements BasicObject & StixObject & StixCoreObject & StixCyberObs id: ID! # internal_id standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics:[Metric] # StixObject representative: Representative! @@ -9945,7 +9945,7 @@ type Mutex implements BasicObject & StixObject & StixCoreObject & StixCyberObser id: ID! # internal_id standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics:[Metric] # StixObject representative: Representative! @@ -10072,7 +10072,7 @@ type NetworkTraffic implements BasicObject & StixObject & StixCoreObject & StixC id: ID! # internal_id standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics:[Metric] # StixObject representative: Representative! @@ -10222,7 +10222,7 @@ type Process implements BasicObject & StixObject & StixCoreObject & StixCyberObs id: ID! # internal_id standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics:[Metric] # StixObject representative: Representative! @@ -10395,7 +10395,7 @@ type Software implements BasicObject & StixObject & StixCoreObject & StixCyberOb id: ID! # internal_id standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics:[Metric] # StixObject representative: Representative! @@ -10543,7 +10543,7 @@ type Url implements BasicObject & StixObject & StixCoreObject & StixCyberObserva id: ID! # internal_id standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics:[Metric] # StixObject representative: Representative! @@ -10670,7 +10670,7 @@ type UserAccount implements BasicObject & StixObject & StixCoreObject & StixCybe id: ID! # internal_id standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics:[Metric] # StixObject representative: Representative! @@ -10824,7 +10824,7 @@ type WindowsRegistryKey implements BasicObject & StixObject & StixCoreObject & S id: ID! # internal_id standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics:[Metric] # StixObject representative: Representative! @@ -10955,7 +10955,7 @@ type WindowsRegistryValueType implements BasicObject & StixObject & StixCoreObje id: ID! # internal_id standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics:[Metric] # StixObject representative: Representative! @@ -11086,7 +11086,7 @@ type CryptographicKey implements BasicObject & StixObject & StixCoreObject & Sti id: ID! # internal_id standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics:[Metric] # StixObject representative: Representative! @@ -11213,7 +11213,7 @@ type CryptocurrencyWallet implements BasicObject & StixObject & StixCoreObject & id: ID! # internal_id standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics:[Metric] # StixObject representative: Representative! @@ -11340,7 +11340,7 @@ type Hostname implements BasicObject & StixObject & StixCoreObject & StixCyberOb id: ID! # internal_id standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics:[Metric] # StixObject representative: Representative! @@ -11467,7 +11467,7 @@ type Text implements BasicObject & StixObject & StixCoreObject & StixCyberObserv id: ID! # internal_id standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics:[Metric] # StixObject representative: Representative! @@ -11594,7 +11594,7 @@ type UserAgent implements BasicObject & StixObject & StixCoreObject & StixCyberO id: ID! # internal_id standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics:[Metric] # StixObject representative: Representative! @@ -11721,7 +11721,7 @@ type BankAccount implements BasicObject & StixObject & StixCoreObject & StixCybe id: ID! # internal_id standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics:[Metric] # StixObject representative: Representative! @@ -11853,7 +11853,7 @@ type TrackingNumber implements BasicObject & StixObject & StixCoreObject & StixC id: ID! # internal_id standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics:[Metric] # StixObject representative: Representative! @@ -11983,7 +11983,7 @@ type Credential implements BasicObject & StixObject & StixCoreObject & StixCyber id: ID! # internal_id standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics:[Metric] # StixObject representative: Representative! @@ -12113,7 +12113,7 @@ type PhoneNumber implements BasicObject & StixObject & StixCoreObject & StixCybe id: ID! # internal_id standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics:[Metric] # StixObject representative: Representative! @@ -12240,7 +12240,7 @@ type PaymentCard implements BasicObject & StixObject & StixCoreObject & StixCybe id: ID! # internal_id standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics:[Metric] # StixObject representative: Representative! @@ -12373,7 +12373,7 @@ type MediaContent implements BasicObject & StixObject & StixCoreObject & StixCyb id: ID! # internal_id standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics:[Metric] # StixObject representative: Representative! @@ -12509,7 +12509,7 @@ type Persona implements BasicObject & StixObject & StixCoreObject & StixCyberObs id: ID! # internal_id standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics:[Metric] # StixObject representative: Representative! @@ -12638,7 +12638,7 @@ type SSHKey implements BasicObject & StixObject & StixCoreObject & StixCyberObse id: ID! # internal_id standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics:[Metric] # StixObject representative: Representative! @@ -12780,7 +12780,7 @@ interface BasicRelationship { id: ID! # internal_id standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics:[Metric] fromRole: String toRole: String @@ -12798,7 +12798,7 @@ type InternalRelationship implements BasicRelationship { id: ID! # internal_id standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics:[Metric] fromRole: String toRole: String @@ -13149,7 +13149,7 @@ interface StixRelationship { id: ID! # internal_id standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics:[Metric] fromRole: String toRole: String @@ -13226,7 +13226,7 @@ type StixCoreRelationship implements BasicRelationship & StixRelationship { id: ID! # internal_id standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics:[Metric] fromRole: String toRole: String @@ -13370,7 +13370,7 @@ type StixSightingRelationship implements BasicRelationship & StixRelationship { id: ID! # internal_id standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics:[Metric] fromRole: String toRole: String @@ -13478,7 +13478,7 @@ type StixRefRelationship implements BasicRelationship & StixRelationship { id: ID! # internal_id standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics:[Metric] fromRole: String toRole: String diff --git a/opencti-platform/opencti-graphql/src/generated/graphql.ts b/opencti-platform/opencti-graphql/src/generated/graphql.ts index 4f9b1ea58ed8..c772e06349b1 100644 --- a/opencti-platform/opencti-graphql/src/generated/graphql.ts +++ b/opencti-platform/opencti-graphql/src/generated/graphql.ts @@ -134,7 +134,7 @@ export type AdministrativeArea = BasicObject & Location & StixCoreObject & StixD observedData?: Maybe; opinions?: Maybe; opinions_metrics?: Maybe; - parent_types: Array>; + parent_types: Array; pendingFiles?: Maybe; pirInformation?: Maybe; precision?: Maybe; @@ -477,7 +477,7 @@ export type Artifact = BasicObject & HashedObservable & StixCoreObject & StixCyb observedData?: Maybe; opinions?: Maybe; opinions_metrics?: Maybe; - parent_types: Array>; + parent_types: Array; payload_bin?: Maybe; pendingFiles?: Maybe; refreshed_at?: Maybe; @@ -726,7 +726,7 @@ export type AttackPattern = BasicObject & StixCoreObject & StixDomainObject & St opinions?: Maybe; opinions_metrics?: Maybe; parentAttackPatterns?: Maybe; - parent_types: Array>; + parent_types: Array; pendingFiles?: Maybe; pirInformation?: Maybe; refreshed_at?: Maybe; @@ -1181,7 +1181,7 @@ export type AutonomousSystem = BasicObject & StixCoreObject & StixCyberObservabl observedData?: Maybe; opinions?: Maybe; opinions_metrics?: Maybe; - parent_types: Array>; + parent_types: Array; pendingFiles?: Maybe; refreshed_at?: Maybe; reports?: Maybe; @@ -1512,7 +1512,7 @@ export type BankAccount = BasicObject & StixCoreObject & StixCyberObservable & S observedData?: Maybe; opinions?: Maybe; opinions_metrics?: Maybe; - parent_types: Array>; + parent_types: Array; pendingFiles?: Maybe; refreshed_at?: Maybe; reports?: Maybe; @@ -1693,7 +1693,7 @@ export type BasicObject = { entity_type: Scalars['String']['output']; id: Scalars['ID']['output']; metrics?: Maybe>>; - parent_types: Array>; + parent_types: Array; standard_id: Scalars['String']['output']; }; @@ -1704,7 +1704,7 @@ export type BasicRelationship = { fromRole?: Maybe; id: Scalars['ID']['output']; metrics?: Maybe>>; - parent_types: Array>; + parent_types: Array; refreshed_at?: Maybe; standard_id: Scalars['String']['output']; toRole?: Maybe; @@ -1771,7 +1771,7 @@ export type Campaign = BasicObject & StixCoreObject & StixDomainObject & StixObj observedData?: Maybe; opinions?: Maybe; opinions_metrics?: Maybe; - parent_types: Array>; + parent_types: Array; pendingFiles?: Maybe; pirInformation?: Maybe; refreshed_at?: Maybe; @@ -2093,7 +2093,7 @@ export type Capability = BasicObject & InternalObject & { id: Scalars['ID']['output']; metrics?: Maybe>>; name: Scalars['String']['output']; - parent_types: Array>; + parent_types: Array; refreshed_at?: Maybe; standard_id: Scalars['String']['output']; updated_at: Scalars['DateTime']['output']; @@ -2155,7 +2155,7 @@ export type Case = { observedData?: Maybe; opinions?: Maybe; opinions_metrics?: Maybe; - parent_types: Array>; + parent_types: Array; pendingFiles?: Maybe; pirInformation?: Maybe; refreshed_at?: Maybe; @@ -2421,7 +2421,7 @@ export type CaseIncident = BasicObject & Case & Container & StixCoreObject & Sti observedData?: Maybe; opinions?: Maybe; opinions_metrics?: Maybe; - parent_types: Array>; + parent_types: Array; pendingFiles?: Maybe; pirInformation?: Maybe; priority?: Maybe; @@ -2743,7 +2743,7 @@ export type CaseRfi = BasicObject & Case & Container & StixCoreObject & StixDoma observedData?: Maybe; opinions?: Maybe; opinions_metrics?: Maybe; - parent_types: Array>; + parent_types: Array; pendingFiles?: Maybe; pirInformation?: Maybe; priority?: Maybe; @@ -3064,7 +3064,7 @@ export type CaseRft = BasicObject & Case & Container & StixCoreObject & StixDoma observedData?: Maybe; opinions?: Maybe; opinions_metrics?: Maybe; - parent_types: Array>; + parent_types: Array; pendingFiles?: Maybe; pirInformation?: Maybe; priority?: Maybe; @@ -3347,7 +3347,7 @@ export type CaseTemplate = BasicObject & InternalObject & { metrics?: Maybe>>; modified?: Maybe; name: Scalars['String']['output']; - parent_types: Array>; + parent_types: Array; standard_id: Scalars['String']['output']; tasks: TaskTemplateConnection; }; @@ -3476,7 +3476,7 @@ export type Channel = BasicObject & StixCoreObject & StixDomainObject & StixObje observedData?: Maybe; opinions?: Maybe; opinions_metrics?: Maybe; - parent_types: Array>; + parent_types: Array; pendingFiles?: Maybe; pirInformation?: Maybe; refreshed_at?: Maybe; @@ -3757,7 +3757,7 @@ export type City = BasicObject & Location & StixCoreObject & StixDomainObject & observedData?: Maybe; opinions?: Maybe; opinions_metrics?: Maybe; - parent_types: Array>; + parent_types: Array; pendingFiles?: Maybe; pirInformation?: Maybe; precision?: Maybe; @@ -4082,7 +4082,7 @@ export type Connector = BasicObject & InternalObject & { metrics?: Maybe>>; name: Scalars['String']['output']; only_contextual?: Maybe; - parent_types: Array>; + parent_types: Array; playbook_compatible?: Maybe; refreshed_at?: Maybe; standard_id: Scalars['String']['output']; @@ -4164,7 +4164,7 @@ export type ConnectorManager = BasicObject & InternalObject & { last_sync_execution?: Maybe; metrics?: Maybe>>; name: Scalars['String']['output']; - parent_types: Array>; + parent_types: Array; public_key: Scalars['String']['output']; standard_id: Scalars['String']['output']; }; @@ -4238,7 +4238,7 @@ export type Container = { observedData?: Maybe; opinions?: Maybe; opinions_metrics?: Maybe; - parent_types: Array>; + parent_types: Array; refreshed_at?: Maybe; relatedContainers?: Maybe; reports?: Maybe; @@ -4544,7 +4544,7 @@ export type Country = BasicObject & Location & StixCoreObject & StixDomainObject observedData?: Maybe; opinions?: Maybe; opinions_metrics?: Maybe; - parent_types: Array>; + parent_types: Array; pendingFiles?: Maybe; pirInformation?: Maybe; precision?: Maybe; @@ -4827,7 +4827,7 @@ export type CourseOfAction = BasicObject & StixCoreObject & StixDomainObject & S observedData?: Maybe; opinions?: Maybe; opinions_metrics?: Maybe; - parent_types: Array>; + parent_types: Array; pendingFiles?: Maybe; pirInformation?: Maybe; refreshed_at?: Maybe; @@ -5140,7 +5140,7 @@ export type Credential = BasicObject & StixCoreObject & StixCyberObservable & St observedData?: Maybe; opinions?: Maybe; opinions_metrics?: Maybe; - parent_types: Array>; + parent_types: Array; pendingFiles?: Maybe; refreshed_at?: Maybe; reports?: Maybe; @@ -5349,7 +5349,7 @@ export type CryptocurrencyWallet = BasicObject & StixCoreObject & StixCyberObser observedData?: Maybe; opinions?: Maybe; opinions_metrics?: Maybe; - parent_types: Array>; + parent_types: Array; pendingFiles?: Maybe; refreshed_at?: Maybe; reports?: Maybe; @@ -5554,7 +5554,7 @@ export type CryptographicKey = BasicObject & StixCoreObject & StixCyberObservabl observedData?: Maybe; opinions?: Maybe; opinions_metrics?: Maybe; - parent_types: Array>; + parent_types: Array; pendingFiles?: Maybe; refreshed_at?: Maybe; reports?: Maybe; @@ -5919,7 +5919,7 @@ export type DataComponent = BasicObject & StixCoreObject & StixDomainObject & St observedData?: Maybe; opinions?: Maybe; opinions_metrics?: Maybe; - parent_types: Array>; + parent_types: Array; pendingFiles?: Maybe; pirInformation?: Maybe; refreshed_at?: Maybe; @@ -6178,7 +6178,7 @@ export type DataSource = BasicObject & StixCoreObject & StixDomainObject & StixO observedData?: Maybe; opinions?: Maybe; opinions_metrics?: Maybe; - parent_types: Array>; + parent_types: Array; pendingFiles?: Maybe; pirInformation?: Maybe; refreshed_at?: Maybe; @@ -6420,7 +6420,7 @@ export type DecayExclusionRule = BasicObject & InternalObject & { id: Scalars['ID']['output']; metrics?: Maybe>>; name: Scalars['String']['output']; - parent_types: Array>; + parent_types: Array; standard_id: Scalars['String']['output']; }; @@ -6480,7 +6480,7 @@ export type DecayRule = BasicObject & InternalObject & { metrics?: Maybe>>; name: Scalars['String']['output']; order: Scalars['Int']['output']; - parent_types: Array>; + parent_types: Array; refreshed_at?: Maybe; standard_id: Scalars['String']['output']; updated_at: Scalars['DateTime']['output']; @@ -6648,7 +6648,7 @@ export type Directory = BasicObject & StixCoreObject & StixCyberObservable & Sti observedData?: Maybe; opinions?: Maybe; opinions_metrics?: Maybe; - parent_types: Array>; + parent_types: Array; path: Scalars['String']['output']; path_enc?: Maybe; pendingFiles?: Maybe; @@ -6855,7 +6855,7 @@ export type DisseminationList = BasicObject & InternalObject & { id: Scalars['ID']['output']; metrics?: Maybe>>; name: Scalars['String']['output']; - parent_types: Array>; + parent_types: Array; refreshed_at?: Maybe; standard_id: Scalars['String']['output']; updated_at: Scalars['DateTime']['output']; @@ -6935,7 +6935,7 @@ export type DomainName = BasicObject & StixCoreObject & StixCyberObservable & St observedData?: Maybe; opinions?: Maybe; opinions_metrics?: Maybe; - parent_types: Array>; + parent_types: Array; pendingFiles?: Maybe; refreshed_at?: Maybe; reports?: Maybe; @@ -7287,7 +7287,7 @@ export type EmailAddr = BasicObject & StixCoreObject & StixCyberObservable & Sti observedData?: Maybe; opinions?: Maybe; opinions_metrics?: Maybe; - parent_types: Array>; + parent_types: Array; pendingFiles?: Maybe; refreshed_at?: Maybe; reports?: Maybe; @@ -7498,7 +7498,7 @@ export type EmailMessage = BasicObject & StixCoreObject & StixCyberObservable & observedData?: Maybe; opinions?: Maybe; opinions_metrics?: Maybe; - parent_types: Array>; + parent_types: Array; pendingFiles?: Maybe; received_lines?: Maybe>>; refreshed_at?: Maybe; @@ -7713,7 +7713,7 @@ export type EmailMimePartType = BasicObject & StixCoreObject & StixCyberObservab observedData?: Maybe; opinions?: Maybe; opinions_metrics?: Maybe; - parent_types: Array>; + parent_types: Array; pendingFiles?: Maybe; refreshed_at?: Maybe; reports?: Maybe; @@ -7897,7 +7897,7 @@ export type EmailTemplate = BasicObject & InternalObject & { id: Scalars['ID']['output']; metrics?: Maybe>>; name: Scalars['String']['output']; - parent_types: Array>; + parent_types: Array; sender_email: Scalars['String']['output']; standard_id: Scalars['String']['output']; template_body: Scalars['String']['output']; @@ -8018,7 +8018,7 @@ export type Event = BasicObject & StixCoreObject & StixDomainObject & StixObject observedData?: Maybe; opinions?: Maybe; opinions_metrics?: Maybe; - parent_types: Array>; + parent_types: Array; pendingFiles?: Maybe; pirInformation?: Maybe; refreshed_at?: Maybe; @@ -8257,7 +8257,7 @@ export type ExclusionList = BasicObject & InternalObject & { id: Scalars['ID']['output']; metrics?: Maybe>>; name: Scalars['String']['output']; - parent_types: Array>; + parent_types: Array; standard_id: Scalars['String']['output']; }; @@ -8333,7 +8333,7 @@ export type ExternalReference = BasicObject & StixMetaObject & StixObject & { jobs?: Maybe>>; metrics?: Maybe>>; modified?: Maybe; - parent_types: Array>; + parent_types: Array; pendingFiles: FileConnection; references?: Maybe; refreshed_at?: Maybe; @@ -8600,7 +8600,7 @@ export type Feedback = BasicObject & Case & Container & StixCoreObject & StixDom observedData?: Maybe; opinions?: Maybe; opinions_metrics?: Maybe; - parent_types: Array>; + parent_types: Array; pendingFiles?: Maybe; pirInformation?: Maybe; rating?: Maybe; @@ -9003,7 +9003,7 @@ export type FintelDesign = BasicObject & InternalObject & { id: Scalars['ID']['output']; metrics?: Maybe>>; name: Scalars['String']['output']; - parent_types: Array>; + parent_types: Array; standard_id: Scalars['String']['output']; textColor?: Maybe; }; @@ -9044,7 +9044,7 @@ export type FintelTemplate = BasicObject & InternalObject & { instance_filters?: Maybe; metrics?: Maybe>>; name: Scalars['String']['output']; - parent_types: Array>; + parent_types: Array; settings_types: Array; standard_id: Scalars['String']['output']; start_date?: Maybe; @@ -9179,7 +9179,7 @@ export type Group = BasicObject & InternalObject & { name: Scalars['String']['output']; no_creators?: Maybe; not_shareable_marking_types: Array; - parent_types: Array>; + parent_types: Array; refreshed_at?: Maybe; restrict_delete?: Maybe; roles?: Maybe; @@ -9309,7 +9309,7 @@ export type Grouping = BasicObject & Container & StixCoreObject & StixDomainObje observedData?: Maybe; opinions?: Maybe; opinions_metrics?: Maybe; - parent_types: Array>; + parent_types: Array; pendingFiles?: Maybe; pirInformation?: Maybe; refreshed_at?: Maybe; @@ -9626,7 +9626,7 @@ export type HashedObservable = { observedData?: Maybe; opinions?: Maybe; opinions_metrics?: Maybe; - parent_types: Array>; + parent_types: Array; pendingFiles?: Maybe; refreshed_at?: Maybe; reports?: Maybe; @@ -9836,7 +9836,7 @@ export type Hostname = BasicObject & StixCoreObject & StixCyberObservable & Stix observedData?: Maybe; opinions?: Maybe; opinions_metrics?: Maybe; - parent_types: Array>; + parent_types: Array; pendingFiles?: Maybe; refreshed_at?: Maybe; reports?: Maybe; @@ -10042,7 +10042,7 @@ export type IPv4Addr = BasicObject & StixCoreObject & StixCyberObservable & Stix observedData?: Maybe; opinions?: Maybe; opinions_metrics?: Maybe; - parent_types: Array>; + parent_types: Array; pendingFiles?: Maybe; refreshed_at?: Maybe; reports?: Maybe; @@ -10250,7 +10250,7 @@ export type IPv6Addr = BasicObject & StixCoreObject & StixCyberObservable & Stix observedData?: Maybe; opinions?: Maybe; opinions_metrics?: Maybe; - parent_types: Array>; + parent_types: Array; pendingFiles?: Maybe; refreshed_at?: Maybe; reports?: Maybe; @@ -10472,7 +10472,7 @@ export type Identity = { observedData?: Maybe; opinions?: Maybe; opinions_metrics?: Maybe; - parent_types: Array>; + parent_types: Array; pendingFiles?: Maybe; refreshed_at?: Maybe; reports?: Maybe; @@ -10769,7 +10769,7 @@ export type Incident = BasicObject & StixCoreObject & StixDomainObject & StixObj observedData?: Maybe; opinions?: Maybe; opinions_metrics?: Maybe; - parent_types: Array>; + parent_types: Array; pendingFiles?: Maybe; pirInformation?: Maybe; refreshed_at?: Maybe; @@ -11114,7 +11114,7 @@ export type Indicator = BasicObject & StixCoreObject & StixDomainObject & StixOb observedData?: Maybe; opinions?: Maybe; opinions_metrics?: Maybe; - parent_types: Array>; + parent_types: Array; pattern?: Maybe; pattern_type?: Maybe; pattern_version?: Maybe; @@ -11434,7 +11434,7 @@ export type Individual = BasicObject & Identity & StixCoreObject & StixDomainObj opinions?: Maybe; opinions_metrics?: Maybe; organizations?: Maybe; - parent_types: Array>; + parent_types: Array; pendingFiles?: Maybe; pirInformation?: Maybe; refreshed_at?: Maybe; @@ -11750,7 +11750,7 @@ export type Infrastructure = BasicObject & StixCoreObject & StixDomainObject & S observedData?: Maybe; opinions?: Maybe; opinions_metrics?: Maybe; - parent_types: Array>; + parent_types: Array; pendingFiles?: Maybe; pirInformation?: Maybe; refreshed_at?: Maybe; @@ -12036,7 +12036,7 @@ export type IngestionCsv = BasicObject & InternalObject & { markings?: Maybe>; metrics?: Maybe>>; name: Scalars['String']['output']; - parent_types: Array>; + parent_types: Array; refreshed_at?: Maybe; scheduling_period?: Maybe; standard_id: Scalars['String']['output']; @@ -12122,7 +12122,7 @@ export type IngestionJson = BasicObject & InternalObject & { pagination_with_sub_page?: Maybe; pagination_with_sub_page_attribute_path?: Maybe; pagination_with_sub_page_query_verb?: Maybe; - parent_types: Array>; + parent_types: Array; query_attributes?: Maybe>; refreshed_at?: Maybe; scheduling_period?: Maybe; @@ -12372,7 +12372,7 @@ export type InternalRelationship = BasicRelationship & { fromRole?: Maybe; id: Scalars['ID']['output']; metrics?: Maybe>>; - parent_types: Array>; + parent_types: Array; refreshed_at?: Maybe; standard_id: Scalars['String']['output']; to?: Maybe; @@ -12433,7 +12433,7 @@ export type IntrusionSet = BasicObject & StixCoreObject & StixDomainObject & Sti observedData?: Maybe; opinions?: Maybe; opinions_metrics?: Maybe; - parent_types: Array>; + parent_types: Array; pendingFiles?: Maybe; pirInformation?: Maybe; primary_motivation?: Maybe; @@ -12824,7 +12824,7 @@ export type KillChainPhase = BasicObject & StixMetaObject & StixObject & { kill_chain_name: Scalars['String']['output']; metrics?: Maybe>>; modified?: Maybe; - parent_types: Array>; + parent_types: Array; phase_name: Scalars['String']['output']; refreshed_at?: Maybe; representative: Representative; @@ -12917,7 +12917,7 @@ export type Label = BasicObject & StixMetaObject & StixObject & { is_inferred: Scalars['Boolean']['output']; metrics?: Maybe>>; modified?: Maybe; - parent_types: Array>; + parent_types: Array; refreshed_at?: Maybe; representative: Representative; spec_version: Scalars['String']['output']; @@ -13024,7 +13024,7 @@ export type Language = BasicObject & StixCoreObject & StixDomainObject & StixObj observedData?: Maybe; opinions?: Maybe; opinions_metrics?: Maybe; - parent_types: Array>; + parent_types: Array; pendingFiles?: Maybe; pirInformation?: Maybe; refreshed_at?: Maybe; @@ -13311,7 +13311,7 @@ export type Location = { observedData?: Maybe; opinions?: Maybe; opinions_metrics?: Maybe; - parent_types: Array>; + parent_types: Array; pendingFiles?: Maybe; precision?: Maybe; refreshed_at?: Maybe; @@ -13644,7 +13644,7 @@ export type MacAddr = BasicObject & StixCoreObject & StixCyberObservable & StixO observedData?: Maybe; opinions?: Maybe; opinions_metrics?: Maybe; - parent_types: Array>; + parent_types: Array; pendingFiles?: Maybe; refreshed_at?: Maybe; reports?: Maybe; @@ -13865,7 +13865,7 @@ export type Malware = BasicObject & StixCoreObject & StixDomainObject & StixObje observedData?: Maybe; opinions?: Maybe; opinions_metrics?: Maybe; - parent_types: Array>; + parent_types: Array; pendingFiles?: Maybe; pirInformation?: Maybe; refreshed_at?: Maybe; @@ -14130,7 +14130,7 @@ export type MalwareAnalysis = BasicObject & StixCoreObject & StixDomainObject & operatingSystem?: Maybe; opinions?: Maybe; opinions_metrics?: Maybe; - parent_types: Array>; + parent_types: Array; pendingFiles?: Maybe; pirInformation?: Maybe; product: Scalars['String']['output']; @@ -14437,7 +14437,7 @@ export type ManagedConnector = BasicObject & InternalObject & { manager_requested_status: Scalars['String']['output']; metrics?: Maybe>>; name: Scalars['String']['output']; - parent_types: Array>; + parent_types: Array; standard_id: Scalars['String']['output']; }; @@ -14505,7 +14505,7 @@ export type MarkingDefinition = BasicObject & StixMetaObject & StixObject & { is_inferred: Scalars['Boolean']['output']; metrics?: Maybe>>; modified?: Maybe; - parent_types: Array>; + parent_types: Array; refreshed_at?: Maybe; representative: Representative; spec_version: Scalars['String']['output']; @@ -14708,7 +14708,7 @@ export type MediaContent = BasicObject & StixCoreObject & StixCyberObservable & observedData?: Maybe; opinions?: Maybe; opinions_metrics?: Maybe; - parent_types: Array>; + parent_types: Array; pendingFiles?: Maybe; publication_date?: Maybe; refreshed_at?: Maybe; @@ -17963,7 +17963,7 @@ export type Mutex = BasicObject & StixCoreObject & StixCyberObservable & StixObj observedData?: Maybe; opinions?: Maybe; opinions_metrics?: Maybe; - parent_types: Array>; + parent_types: Array; pendingFiles?: Maybe; refreshed_at?: Maybe; reports?: Maybe; @@ -18184,7 +18184,7 @@ export type Narrative = BasicObject & StixCoreObject & StixDomainObject & StixOb opinions?: Maybe; opinions_metrics?: Maybe; parentNarratives?: Maybe; - parent_types: Array>; + parent_types: Array; pendingFiles?: Maybe; pirInformation?: Maybe; refreshed_at?: Maybe; @@ -18443,7 +18443,7 @@ export type NetworkTraffic = BasicObject & StixCoreObject & StixCyberObservable observedData?: Maybe; opinions?: Maybe; opinions_metrics?: Maybe; - parent_types: Array>; + parent_types: Array; pendingFiles?: Maybe; protocols?: Maybe>>; refreshed_at?: Maybe; @@ -18680,7 +18680,7 @@ export type Note = BasicObject & Container & StixCoreObject & StixDomainObject & observedData?: Maybe; opinions?: Maybe; opinions_metrics?: Maybe; - parent_types: Array>; + parent_types: Array; pendingFiles?: Maybe; pirInformation?: Maybe; refreshed_at?: Maybe; @@ -19005,7 +19005,7 @@ export type Notification = BasicObject & InternalObject & { name: Scalars['String']['output']; notification_content: Array; notification_type: Scalars['String']['output']; - parent_types: Array>; + parent_types: Array; refreshed_at?: Maybe; standard_id: Scalars['String']['output']; updated_at?: Maybe; @@ -19062,7 +19062,7 @@ export type Notifier = BasicObject & InternalObject & { notifier_configuration: Scalars['String']['output']; notifier_connector: NotifierConnector; notifier_connector_id: Scalars['String']['output']; - parent_types: Array>; + parent_types: Array; standard_id: Scalars['String']['output']; }; @@ -19180,7 +19180,7 @@ export type ObservedData = BasicObject & Container & StixCoreObject & StixDomain observedData?: Maybe; opinions?: Maybe; opinions_metrics?: Maybe; - parent_types: Array>; + parent_types: Array; pendingFiles?: Maybe; pirInformation?: Maybe; refreshed_at?: Maybe; @@ -19526,7 +19526,7 @@ export type Opinion = BasicObject & Container & StixCoreObject & StixDomainObjec opinion: Scalars['String']['output']; opinions?: Maybe; opinions_metrics?: Maybe; - parent_types: Array>; + parent_types: Array; pendingFiles?: Maybe; pirInformation?: Maybe; refreshed_at?: Maybe; @@ -19899,7 +19899,7 @@ export type Organization = BasicObject & Identity & StixCoreObject & StixDomainO opinions?: Maybe; opinions_metrics?: Maybe; parentOrganizations?: Maybe; - parent_types: Array>; + parent_types: Array; pendingFiles?: Maybe; pirInformation?: Maybe; refreshed_at?: Maybe; @@ -20236,7 +20236,7 @@ export type PaymentCard = BasicObject & StixCoreObject & StixCyberObservable & S observedData?: Maybe; opinions?: Maybe; opinions_metrics?: Maybe; - parent_types: Array>; + parent_types: Array; pendingFiles?: Maybe; refreshed_at?: Maybe; reports?: Maybe; @@ -20443,7 +20443,7 @@ export type Persona = BasicObject & StixCoreObject & StixCyberObservable & StixO observedData?: Maybe; opinions?: Maybe; opinions_metrics?: Maybe; - parent_types: Array>; + parent_types: Array; pendingFiles?: Maybe; persona_name: Scalars['String']['output']; persona_type: Scalars['String']['output']; @@ -20649,7 +20649,7 @@ export type PhoneNumber = BasicObject & StixCoreObject & StixCyberObservable & S observedData?: Maybe; opinions?: Maybe; opinions_metrics?: Maybe; - parent_types: Array>; + parent_types: Array; pendingFiles?: Maybe; refreshed_at?: Maybe; reports?: Maybe; @@ -20946,7 +20946,7 @@ export type PirRelationship = BasicRelationship & { fromType: Scalars['String']['output']; id: Scalars['ID']['output']; metrics?: Maybe>>; - parent_types: Array>; + parent_types: Array; pir_explanation?: Maybe>; pir_score?: Maybe; refreshed_at?: Maybe; @@ -21202,7 +21202,7 @@ export type Position = BasicObject & Location & StixCoreObject & StixDomainObjec observedData?: Maybe; opinions?: Maybe; opinions_metrics?: Maybe; - parent_types: Array>; + parent_types: Array; pendingFiles?: Maybe; pirInformation?: Maybe; postal_code?: Maybe; @@ -21510,7 +21510,7 @@ export type Process = BasicObject & StixCoreObject & StixCyberObservable & StixO opinions?: Maybe; opinions_metrics?: Maybe; owner_sid?: Maybe; - parent_types: Array>; + parent_types: Array; pendingFiles?: Maybe; pid?: Maybe; priority?: Maybe; @@ -25321,7 +25321,7 @@ export type Region = BasicObject & Location & StixCoreObject & StixDomainObject opinions?: Maybe; opinions_metrics?: Maybe; parentRegions?: Maybe; - parent_types: Array>; + parent_types: Array; pendingFiles?: Maybe; pirInformation?: Maybe; precision?: Maybe; @@ -25649,7 +25649,7 @@ export type Report = BasicObject & Container & StixCoreObject & StixDomainObject observedData?: Maybe; opinions?: Maybe; opinions_metrics?: Maybe; - parent_types: Array>; + parent_types: Array; pendingFiles?: Maybe; pirInformation?: Maybe; published?: Maybe; @@ -26116,7 +26116,7 @@ export type Role = BasicObject & InternalObject & { id: Scalars['ID']['output']; metrics?: Maybe>>; name: Scalars['String']['output']; - parent_types: Array>; + parent_types: Array; refreshed_at?: Maybe; standard_id: Scalars['String']['output']; updated_at: Scalars['DateTime']['output']; @@ -26258,7 +26258,7 @@ export type SshKey = BasicObject & StixCoreObject & StixCyberObservable & StixOb observedData?: Maybe; opinions?: Maybe; opinions_metrics?: Maybe; - parent_types: Array>; + parent_types: Array; pendingFiles?: Maybe; public_key?: Maybe; refreshed_at?: Maybe; @@ -26446,7 +26446,7 @@ export type SavedFilter = BasicObject & InternalObject & { id: Scalars['ID']['output']; metrics?: Maybe>>; name: Scalars['String']['output']; - parent_types: Array>; + parent_types: Array; scope: Scalars['String']['output']; standard_id: Scalars['String']['output']; }; @@ -26527,7 +26527,7 @@ export type Sector = BasicObject & Identity & StixCoreObject & StixDomainObject opinions?: Maybe; opinions_metrics?: Maybe; parentSectors?: Maybe; - parent_types: Array>; + parent_types: Array; pendingFiles?: Maybe; pirInformation?: Maybe; refreshed_at?: Maybe; @@ -26832,7 +26832,7 @@ export type SecurityCoverage = BasicObject & StixCoreObject & StixDomainObject & observedData?: Maybe; opinions?: Maybe; opinions_metrics?: Maybe; - parent_types: Array>; + parent_types: Array; pendingFiles?: Maybe; periodicity?: Maybe; pirInformation?: Maybe; @@ -27110,7 +27110,7 @@ export type SecurityPlatform = BasicObject & Identity & StixCoreObject & StixDom observedData?: Maybe; opinions?: Maybe; opinions_metrics?: Maybe; - parent_types: Array>; + parent_types: Array; pendingFiles?: Maybe; pirInformation?: Maybe; refreshed_at?: Maybe; @@ -27501,7 +27501,7 @@ export type Software = BasicObject & StixCoreObject & StixCyberObservable & Stix observedData?: Maybe; opinions?: Maybe; opinions_metrics?: Maybe; - parent_types: Array>; + parent_types: Array; pendingFiles?: Maybe; refreshed_at?: Maybe; reports?: Maybe; @@ -27808,7 +27808,7 @@ export type StixCoreObject = { observedData?: Maybe; opinions?: Maybe; opinions_metrics?: Maybe; - parent_types: Array>; + parent_types: Array; pendingFiles?: Maybe; refreshed_at?: Maybe; reports?: Maybe; @@ -28194,7 +28194,7 @@ export type StixCoreRelationship = BasicRelationship & StixRelationship & { objectMarking?: Maybe>; objectOrganization?: Maybe>; opinions?: Maybe; - parent_types: Array>; + parent_types: Array; refreshed_at?: Maybe; relationship_type: Scalars['String']['output']; reports?: Maybe; @@ -28489,7 +28489,7 @@ export type StixCyberObservable = { observedData?: Maybe; opinions?: Maybe; opinions_metrics?: Maybe; - parent_types: Array>; + parent_types: Array; pendingFiles?: Maybe; refreshed_at?: Maybe; reports?: Maybe; @@ -28793,7 +28793,7 @@ export type StixDomainObject = { observedData?: Maybe; opinions?: Maybe; opinions_metrics?: Maybe; - parent_types: Array>; + parent_types: Array; pendingFiles?: Maybe; pirInformation?: Maybe; refreshed_at?: Maybe; @@ -29193,7 +29193,7 @@ export type StixFile = BasicObject & HashedObservable & StixCoreObject & StixCyb observedData?: Maybe; opinions?: Maybe; opinions_metrics?: Maybe; - parent_types: Array>; + parent_types: Array; pendingFiles?: Maybe; refreshed_at?: Maybe; reports?: Maybe; @@ -29400,7 +29400,7 @@ export type StixMetaObject = { is_inferred: Scalars['Boolean']['output']; metrics?: Maybe>>; modified?: Maybe; - parent_types: Array>; + parent_types: Array; refreshed_at?: Maybe; spec_version: Scalars['String']['output']; standard_id: Scalars['String']['output']; @@ -29438,7 +29438,7 @@ export type StixObject = { id: Scalars['ID']['output']; is_inferred: Scalars['Boolean']['output']; metrics?: Maybe>>; - parent_types: Array>; + parent_types: Array; refreshed_at?: Maybe; representative: Representative; spec_version: Scalars['String']['output']; @@ -29519,7 +29519,7 @@ export type StixRefRelationship = BasicRelationship & StixRelationship & { notes?: Maybe; objectMarking?: Maybe>; opinions?: Maybe; - parent_types: Array>; + parent_types: Array; refreshed_at?: Maybe; relationship_type: Scalars['String']['output']; reports?: Maybe; @@ -29660,7 +29660,7 @@ export type StixRelationship = { metrics?: Maybe>>; modified?: Maybe; objectMarking?: Maybe>; - parent_types: Array>; + parent_types: Array; refreshed_at?: Maybe; relationship_type: Scalars['String']['output']; representative: Representative; @@ -29788,7 +29788,7 @@ export type StixSightingRelationship = BasicRelationship & StixRelationship & { objectMarking?: Maybe>; objectOrganization?: Maybe>; opinions?: Maybe; - parent_types: Array>; + parent_types: Array; refreshed_at?: Maybe; relationship_type: Scalars['String']['output']; reports?: Maybe; @@ -30231,7 +30231,7 @@ export type SupportPackage = BasicObject & InternalObject & { package_status: PackageStatus; package_upload_dir?: Maybe; package_url?: Maybe; - parent_types: Array>; + parent_types: Array; standard_id: Scalars['String']['output']; }; @@ -30371,7 +30371,7 @@ export type System = BasicObject & Identity & StixCoreObject & StixDomainObject opinions?: Maybe; opinions_metrics?: Maybe; organizations?: Maybe; - parent_types: Array>; + parent_types: Array; pendingFiles?: Maybe; pirInformation?: Maybe; refreshed_at?: Maybe; @@ -30677,7 +30677,7 @@ export type Task = BasicObject & Container & StixCoreObject & StixDomainObject & observedData?: Maybe; opinions?: Maybe; opinions_metrics?: Maybe; - parent_types: Array>; + parent_types: Array; pendingFiles?: Maybe; pirInformation?: Maybe; refreshed_at?: Maybe; @@ -30923,7 +30923,7 @@ export type TaskTemplate = BasicObject & InternalObject & { metrics?: Maybe>>; modified?: Maybe; name: Scalars['String']['output']; - parent_types: Array>; + parent_types: Array; standard_id: Scalars['String']['output']; }; @@ -31071,7 +31071,7 @@ export type Text = BasicObject & StixCoreObject & StixCyberObservable & StixObje observedData?: Maybe; opinions?: Maybe; opinions_metrics?: Maybe; - parent_types: Array>; + parent_types: Array; pendingFiles?: Maybe; refreshed_at?: Maybe; reports?: Maybe; @@ -31349,7 +31349,7 @@ export type ThreatActor = { observedData?: Maybe; opinions?: Maybe; opinions_metrics?: Maybe; - parent_types: Array>; + parent_types: Array; pendingFiles?: Maybe; personal_motivations?: Maybe>>; pirInformation?: Maybe; @@ -31583,7 +31583,7 @@ export type ThreatActorGroup = BasicObject & StixCoreObject & StixDomainObject & observedData?: Maybe; opinions?: Maybe; opinions_metrics?: Maybe; - parent_types: Array>; + parent_types: Array; pendingFiles?: Maybe; personal_motivations?: Maybe>>; pirInformation?: Maybe; @@ -31893,7 +31893,7 @@ export type ThreatActorIndividual = BasicObject & StixCoreObject & StixDomainObj observedData?: Maybe; opinions?: Maybe; opinions_metrics?: Maybe; - parent_types: Array>; + parent_types: Array; pendingFiles?: Maybe; personal_motivations?: Maybe>>; pirInformation?: Maybe; @@ -32211,7 +32211,7 @@ export type Tool = BasicObject & StixCoreObject & StixDomainObject & StixObject observedData?: Maybe; opinions?: Maybe; opinions_metrics?: Maybe; - parent_types: Array>; + parent_types: Array; pendingFiles?: Maybe; pirInformation?: Maybe; refreshed_at?: Maybe; @@ -32497,7 +32497,7 @@ export type TrackingNumber = BasicObject & StixCoreObject & StixCyberObservable observedData?: Maybe; opinions?: Maybe; opinions_metrics?: Maybe; - parent_types: Array>; + parent_types: Array; pendingFiles?: Maybe; refreshed_at?: Maybe; reports?: Maybe; @@ -32692,7 +32692,7 @@ export type Trigger = BasicObject & InternalObject & { modified?: Maybe; name: Scalars['String']['output']; notifiers?: Maybe>; - parent_types: Array>; + parent_types: Array; period?: Maybe; recipients?: Maybe>; refreshed_at?: Maybe; @@ -32841,7 +32841,7 @@ export type Url = BasicObject & StixCoreObject & StixCyberObservable & StixObjec observedData?: Maybe; opinions?: Maybe; opinions_metrics?: Maybe; - parent_types: Array>; + parent_types: Array; pendingFiles?: Maybe; refreshed_at?: Maybe; reports?: Maybe; @@ -33048,7 +33048,7 @@ export type User = BasicObject & InternalObject & { otp_activated?: Maybe; otp_mandatory?: Maybe; otp_qr?: Maybe; - parent_types: Array>; + parent_types: Array; personal_notifiers?: Maybe>; refreshed_at?: Maybe; restrict_delete?: Maybe; @@ -33135,7 +33135,7 @@ export type UserAccount = BasicObject & StixCoreObject & StixCyberObservable & S observedData?: Maybe; opinions?: Maybe; opinions_metrics?: Maybe; - parent_types: Array>; + parent_types: Array; pendingFiles?: Maybe; refreshed_at?: Maybe; reports?: Maybe; @@ -33376,7 +33376,7 @@ export type UserAgent = BasicObject & StixCoreObject & StixCyberObservable & Sti observedData?: Maybe; opinions?: Maybe; opinions_metrics?: Maybe; - parent_types: Array>; + parent_types: Array; pendingFiles?: Maybe; refreshed_at?: Maybe; reports?: Maybe; @@ -33688,7 +33688,7 @@ export type Vocabulary = BasicObject & StixMetaObject & StixObject & { modified?: Maybe; name: Scalars['String']['output']; order?: Maybe; - parent_types: Array>; + parent_types: Array; refreshed_at?: Maybe; representative: Representative; spec_version: Scalars['String']['output']; @@ -33851,7 +33851,7 @@ export type Vulnerability = BasicObject & StixCoreObject & StixDomainObject & St observedData?: Maybe; opinions?: Maybe; opinions_metrics?: Maybe; - parent_types: Array>; + parent_types: Array; pendingFiles?: Maybe; pirInformation?: Maybe; refreshed_at?: Maybe; @@ -34296,7 +34296,7 @@ export type WindowsRegistryKey = BasicObject & StixCoreObject & StixCyberObserva observedData?: Maybe; opinions?: Maybe; opinions_metrics?: Maybe; - parent_types: Array>; + parent_types: Array; pendingFiles?: Maybe; refreshed_at?: Maybe; reports?: Maybe; @@ -34505,7 +34505,7 @@ export type WindowsRegistryValueType = BasicObject & StixCoreObject & StixCyberO observedData?: Maybe; opinions?: Maybe; opinions_metrics?: Maybe; - parent_types: Array>; + parent_types: Array; pendingFiles?: Maybe; refreshed_at?: Maybe; reports?: Maybe; @@ -34900,7 +34900,7 @@ export type X509Certificate = BasicObject & HashedObservable & StixCoreObject & observedData?: Maybe; opinions?: Maybe; opinions_metrics?: Maybe; - parent_types: Array>; + parent_types: Array; pendingFiles?: Maybe; policy_constraints?: Maybe; policy_mappings?: Maybe; @@ -37944,7 +37944,7 @@ export type AdministrativeAreaResolvers, ParentType, ContextType, Partial>; opinions?: Resolver, ParentType, ContextType, Partial>; opinions_metrics?: Resolver, ParentType, ContextType>; - parent_types?: Resolver>, ParentType, ContextType>; + parent_types?: Resolver, ParentType, ContextType>; pendingFiles?: Resolver, ParentType, ContextType, Partial>; pirInformation?: Resolver, ParentType, ContextType, RequireFields>; precision?: Resolver, ParentType, ContextType>; @@ -38081,7 +38081,7 @@ export type ArtifactResolvers, ParentType, ContextType, Partial>; opinions?: Resolver, ParentType, ContextType, Partial>; opinions_metrics?: Resolver, ParentType, ContextType>; - parent_types?: Resolver>, ParentType, ContextType>; + parent_types?: Resolver, ParentType, ContextType>; payload_bin?: Resolver, ParentType, ContextType>; pendingFiles?: Resolver, ParentType, ContextType, Partial>; refreshed_at?: Resolver, ParentType, ContextType>; @@ -38162,7 +38162,7 @@ export type AttackPatternResolvers, ParentType, ContextType, Partial>; opinions_metrics?: Resolver, ParentType, ContextType>; parentAttackPatterns?: Resolver, ParentType, ContextType, Partial>; - parent_types?: Resolver>, ParentType, ContextType>; + parent_types?: Resolver, ParentType, ContextType>; pendingFiles?: Resolver, ParentType, ContextType, Partial>; pirInformation?: Resolver, ParentType, ContextType, RequireFields>; refreshed_at?: Resolver, ParentType, ContextType>; @@ -38316,7 +38316,7 @@ export type AutonomousSystemResolvers, ParentType, ContextType, Partial>; opinions?: Resolver, ParentType, ContextType, Partial>; opinions_metrics?: Resolver, ParentType, ContextType>; - parent_types?: Resolver>, ParentType, ContextType>; + parent_types?: Resolver, ParentType, ContextType>; pendingFiles?: Resolver, ParentType, ContextType, Partial>; refreshed_at?: Resolver, ParentType, ContextType>; reports?: Resolver, ParentType, ContextType, Partial>; @@ -38412,7 +38412,7 @@ export type BankAccountResolvers, ParentType, ContextType, Partial>; opinions?: Resolver, ParentType, ContextType, Partial>; opinions_metrics?: Resolver, ParentType, ContextType>; - parent_types?: Resolver>, ParentType, ContextType>; + parent_types?: Resolver, ParentType, ContextType>; pendingFiles?: Resolver, ParentType, ContextType, Partial>; refreshed_at?: Resolver, ParentType, ContextType>; reports?: Resolver, ParentType, ContextType, Partial>; @@ -38437,7 +38437,7 @@ export type BasicObjectResolvers; id?: Resolver; metrics?: Resolver>>, ParentType, ContextType>; - parent_types?: Resolver>, ParentType, ContextType>; + parent_types?: Resolver, ParentType, ContextType>; standard_id?: Resolver; }>; @@ -38449,7 +38449,7 @@ export type BasicRelationshipResolvers, ParentType, ContextType>; id?: Resolver; metrics?: Resolver>>, ParentType, ContextType>; - parent_types?: Resolver>, ParentType, ContextType>; + parent_types?: Resolver, ParentType, ContextType>; refreshed_at?: Resolver, ParentType, ContextType>; standard_id?: Resolver; toRole?: Resolver, ParentType, ContextType>; @@ -38508,7 +38508,7 @@ export type CampaignResolvers, ParentType, ContextType, Partial>; opinions?: Resolver, ParentType, ContextType, Partial>; opinions_metrics?: Resolver, ParentType, ContextType>; - parent_types?: Resolver>, ParentType, ContextType>; + parent_types?: Resolver, ParentType, ContextType>; pendingFiles?: Resolver, ParentType, ContextType, Partial>; pirInformation?: Resolver, ParentType, ContextType, RequireFields>; refreshed_at?: Resolver, ParentType, ContextType>; @@ -38560,7 +38560,7 @@ export type CapabilityResolvers; metrics?: Resolver>>, ParentType, ContextType>; name?: Resolver; - parent_types?: Resolver>, ParentType, ContextType>; + parent_types?: Resolver, ParentType, ContextType>; refreshed_at?: Resolver, ParentType, ContextType>; standard_id?: Resolver; updated_at?: Resolver; @@ -38622,7 +38622,7 @@ export type CaseResolvers, ParentType, ContextType, Partial>; opinions?: Resolver, ParentType, ContextType, Partial>; opinions_metrics?: Resolver, ParentType, ContextType>; - parent_types?: Resolver>, ParentType, ContextType>; + parent_types?: Resolver, ParentType, ContextType>; pendingFiles?: Resolver, ParentType, ContextType, Partial>; pirInformation?: Resolver, ParentType, ContextType, RequireFields>; refreshed_at?: Resolver, ParentType, ContextType>; @@ -38700,7 +38700,7 @@ export type CaseIncidentResolvers, ParentType, ContextType, Partial>; opinions?: Resolver, ParentType, ContextType, Partial>; opinions_metrics?: Resolver, ParentType, ContextType>; - parent_types?: Resolver>, ParentType, ContextType>; + parent_types?: Resolver, ParentType, ContextType>; pendingFiles?: Resolver, ParentType, ContextType, Partial>; pirInformation?: Resolver, ParentType, ContextType, RequireFields>; priority?: Resolver, ParentType, ContextType>; @@ -38785,7 +38785,7 @@ export type CaseRfiResolvers, ParentType, ContextType, Partial>; opinions?: Resolver, ParentType, ContextType, Partial>; opinions_metrics?: Resolver, ParentType, ContextType>; - parent_types?: Resolver>, ParentType, ContextType>; + parent_types?: Resolver, ParentType, ContextType>; pendingFiles?: Resolver, ParentType, ContextType, Partial>; pirInformation?: Resolver, ParentType, ContextType, RequireFields>; priority?: Resolver, ParentType, ContextType>; @@ -38869,7 +38869,7 @@ export type CaseRftResolvers, ParentType, ContextType, Partial>; opinions?: Resolver, ParentType, ContextType, Partial>; opinions_metrics?: Resolver, ParentType, ContextType>; - parent_types?: Resolver>, ParentType, ContextType>; + parent_types?: Resolver, ParentType, ContextType>; pendingFiles?: Resolver, ParentType, ContextType, Partial>; pirInformation?: Resolver, ParentType, ContextType, RequireFields>; priority?: Resolver, ParentType, ContextType>; @@ -38915,7 +38915,7 @@ export type CaseTemplateResolvers>>, ParentType, ContextType>; modified?: Resolver, ParentType, ContextType>; name?: Resolver; - parent_types?: Resolver>, ParentType, ContextType>; + parent_types?: Resolver, ParentType, ContextType>; standard_id?: Resolver; tasks?: Resolver; __isTypeOf?: IsTypeOfResolverFn; @@ -38999,7 +38999,7 @@ export type ChannelResolvers, ParentType, ContextType, Partial>; opinions?: Resolver, ParentType, ContextType, Partial>; opinions_metrics?: Resolver, ParentType, ContextType>; - parent_types?: Resolver>, ParentType, ContextType>; + parent_types?: Resolver, ParentType, ContextType>; pendingFiles?: Resolver, ParentType, ContextType, Partial>; pirInformation?: Resolver, ParentType, ContextType, RequireFields>; refreshed_at?: Resolver, ParentType, ContextType>; @@ -39076,7 +39076,7 @@ export type CityResolvers, ParentType, ContextType, Partial>; opinions?: Resolver, ParentType, ContextType, Partial>; opinions_metrics?: Resolver, ParentType, ContextType>; - parent_types?: Resolver>, ParentType, ContextType>; + parent_types?: Resolver, ParentType, ContextType>; pendingFiles?: Resolver, ParentType, ContextType, Partial>; pirInformation?: Resolver, ParentType, ContextType, RequireFields>; precision?: Resolver, ParentType, ContextType>; @@ -39184,7 +39184,7 @@ export type ConnectorResolvers>>, ParentType, ContextType>; name?: Resolver; only_contextual?: Resolver, ParentType, ContextType>; - parent_types?: Resolver>, ParentType, ContextType>; + parent_types?: Resolver, ParentType, ContextType>; playbook_compatible?: Resolver, ParentType, ContextType>; refreshed_at?: Resolver, ParentType, ContextType>; standard_id?: Resolver; @@ -39242,7 +39242,7 @@ export type ConnectorManagerResolvers, ParentType, ContextType>; metrics?: Resolver>>, ParentType, ContextType>; name?: Resolver; - parent_types?: Resolver>, ParentType, ContextType>; + parent_types?: Resolver, ParentType, ContextType>; public_key?: Resolver; standard_id?: Resolver; __isTypeOf?: IsTypeOfResolverFn; @@ -39300,7 +39300,7 @@ export type ContainerResolvers, ParentType, ContextType, Partial>; opinions?: Resolver, ParentType, ContextType, Partial>; opinions_metrics?: Resolver, ParentType, ContextType>; - parent_types?: Resolver>, ParentType, ContextType>; + parent_types?: Resolver, ParentType, ContextType>; refreshed_at?: Resolver, ParentType, ContextType>; relatedContainers?: Resolver, ParentType, ContextType, Partial>; reports?: Resolver, ParentType, ContextType, Partial>; @@ -39395,7 +39395,7 @@ export type CountryResolvers, ParentType, ContextType, Partial>; opinions?: Resolver, ParentType, ContextType, Partial>; opinions_metrics?: Resolver, ParentType, ContextType>; - parent_types?: Resolver>, ParentType, ContextType>; + parent_types?: Resolver, ParentType, ContextType>; pendingFiles?: Resolver, ParentType, ContextType, Partial>; pirInformation?: Resolver, ParentType, ContextType, RequireFields>; precision?: Resolver, ParentType, ContextType>; @@ -39477,7 +39477,7 @@ export type CourseOfActionResolvers, ParentType, ContextType, Partial>; opinions?: Resolver, ParentType, ContextType, Partial>; opinions_metrics?: Resolver, ParentType, ContextType>; - parent_types?: Resolver>, ParentType, ContextType>; + parent_types?: Resolver, ParentType, ContextType>; pendingFiles?: Resolver, ParentType, ContextType, Partial>; pirInformation?: Resolver, ParentType, ContextType, RequireFields>; refreshed_at?: Resolver, ParentType, ContextType>; @@ -39575,7 +39575,7 @@ export type CredentialResolvers, ParentType, ContextType, Partial>; opinions?: Resolver, ParentType, ContextType, Partial>; opinions_metrics?: Resolver, ParentType, ContextType>; - parent_types?: Resolver>, ParentType, ContextType>; + parent_types?: Resolver, ParentType, ContextType>; pendingFiles?: Resolver, ParentType, ContextType, Partial>; refreshed_at?: Resolver, ParentType, ContextType>; reports?: Resolver, ParentType, ContextType, Partial>; @@ -39625,7 +39625,7 @@ export type CryptocurrencyWalletResolvers, ParentType, ContextType, Partial>; opinions?: Resolver, ParentType, ContextType, Partial>; opinions_metrics?: Resolver, ParentType, ContextType>; - parent_types?: Resolver>, ParentType, ContextType>; + parent_types?: Resolver, ParentType, ContextType>; pendingFiles?: Resolver, ParentType, ContextType, Partial>; refreshed_at?: Resolver, ParentType, ContextType>; reports?: Resolver, ParentType, ContextType, Partial>; @@ -39675,7 +39675,7 @@ export type CryptographicKeyResolvers, ParentType, ContextType, Partial>; opinions?: Resolver, ParentType, ContextType, Partial>; opinions_metrics?: Resolver, ParentType, ContextType>; - parent_types?: Resolver>, ParentType, ContextType>; + parent_types?: Resolver, ParentType, ContextType>; pendingFiles?: Resolver, ParentType, ContextType, Partial>; refreshed_at?: Resolver, ParentType, ContextType>; reports?: Resolver, ParentType, ContextType, Partial>; @@ -39820,7 +39820,7 @@ export type DataComponentResolvers, ParentType, ContextType, Partial>; opinions?: Resolver, ParentType, ContextType, Partial>; opinions_metrics?: Resolver, ParentType, ContextType>; - parent_types?: Resolver>, ParentType, ContextType>; + parent_types?: Resolver, ParentType, ContextType>; pendingFiles?: Resolver, ParentType, ContextType, Partial>; pirInformation?: Resolver, ParentType, ContextType, RequireFields>; refreshed_at?: Resolver, ParentType, ContextType>; @@ -39892,7 +39892,7 @@ export type DataSourceResolvers, ParentType, ContextType, Partial>; opinions?: Resolver, ParentType, ContextType, Partial>; opinions_metrics?: Resolver, ParentType, ContextType>; - parent_types?: Resolver>, ParentType, ContextType>; + parent_types?: Resolver, ParentType, ContextType>; pendingFiles?: Resolver, ParentType, ContextType, Partial>; pirInformation?: Resolver, ParentType, ContextType, RequireFields>; refreshed_at?: Resolver, ParentType, ContextType>; @@ -39947,7 +39947,7 @@ export type DecayExclusionRuleResolvers; metrics?: Resolver>>, ParentType, ContextType>; name?: Resolver; - parent_types?: Resolver>, ParentType, ContextType>; + parent_types?: Resolver, ParentType, ContextType>; standard_id?: Resolver; __isTypeOf?: IsTypeOfResolverFn; }>; @@ -39990,7 +39990,7 @@ export type DecayRuleResolvers>>, ParentType, ContextType>; name?: Resolver; order?: Resolver; - parent_types?: Resolver>, ParentType, ContextType>; + parent_types?: Resolver, ParentType, ContextType>; refreshed_at?: Resolver, ParentType, ContextType>; standard_id?: Resolver; updated_at?: Resolver; @@ -40103,7 +40103,7 @@ export type DirectoryResolvers, ParentType, ContextType, Partial>; opinions?: Resolver, ParentType, ContextType, Partial>; opinions_metrics?: Resolver, ParentType, ContextType>; - parent_types?: Resolver>, ParentType, ContextType>; + parent_types?: Resolver, ParentType, ContextType>; path?: Resolver; path_enc?: Resolver, ParentType, ContextType>; pendingFiles?: Resolver, ParentType, ContextType, Partial>; @@ -40149,7 +40149,7 @@ export type DisseminationListResolvers; metrics?: Resolver>>, ParentType, ContextType>; name?: Resolver; - parent_types?: Resolver>, ParentType, ContextType>; + parent_types?: Resolver, ParentType, ContextType>; refreshed_at?: Resolver, ParentType, ContextType>; standard_id?: Resolver; updated_at?: Resolver; @@ -40205,7 +40205,7 @@ export type DomainNameResolvers, ParentType, ContextType, Partial>; opinions?: Resolver, ParentType, ContextType, Partial>; opinions_metrics?: Resolver, ParentType, ContextType>; - parent_types?: Resolver>, ParentType, ContextType>; + parent_types?: Resolver, ParentType, ContextType>; pendingFiles?: Resolver, ParentType, ContextType, Partial>; refreshed_at?: Resolver, ParentType, ContextType>; reports?: Resolver, ParentType, ContextType, Partial>; @@ -40333,7 +40333,7 @@ export type EmailAddrResolvers, ParentType, ContextType, Partial>; opinions?: Resolver, ParentType, ContextType, Partial>; opinions_metrics?: Resolver, ParentType, ContextType>; - parent_types?: Resolver>, ParentType, ContextType>; + parent_types?: Resolver, ParentType, ContextType>; pendingFiles?: Resolver, ParentType, ContextType, Partial>; refreshed_at?: Resolver, ParentType, ContextType>; reports?: Resolver, ParentType, ContextType, Partial>; @@ -40388,7 +40388,7 @@ export type EmailMessageResolvers, ParentType, ContextType, Partial>; opinions?: Resolver, ParentType, ContextType, Partial>; opinions_metrics?: Resolver, ParentType, ContextType>; - parent_types?: Resolver>, ParentType, ContextType>; + parent_types?: Resolver, ParentType, ContextType>; pendingFiles?: Resolver, ParentType, ContextType, Partial>; received_lines?: Resolver>>, ParentType, ContextType>; refreshed_at?: Resolver, ParentType, ContextType>; @@ -40442,7 +40442,7 @@ export type EmailMimePartTypeResolvers, ParentType, ContextType, Partial>; opinions?: Resolver, ParentType, ContextType, Partial>; opinions_metrics?: Resolver, ParentType, ContextType>; - parent_types?: Resolver>, ParentType, ContextType>; + parent_types?: Resolver, ParentType, ContextType>; pendingFiles?: Resolver, ParentType, ContextType, Partial>; refreshed_at?: Resolver, ParentType, ContextType>; reports?: Resolver, ParentType, ContextType, Partial>; @@ -40469,7 +40469,7 @@ export type EmailTemplateResolvers; metrics?: Resolver>>, ParentType, ContextType>; name?: Resolver; - parent_types?: Resolver>, ParentType, ContextType>; + parent_types?: Resolver, ParentType, ContextType>; sender_email?: Resolver; standard_id?: Resolver; template_body?: Resolver; @@ -40559,7 +40559,7 @@ export type EventResolvers, ParentType, ContextType, Partial>; opinions?: Resolver, ParentType, ContextType, Partial>; opinions_metrics?: Resolver, ParentType, ContextType>; - parent_types?: Resolver>, ParentType, ContextType>; + parent_types?: Resolver, ParentType, ContextType>; pendingFiles?: Resolver, ParentType, ContextType, Partial>; pirInformation?: Resolver, ParentType, ContextType, RequireFields>; refreshed_at?: Resolver, ParentType, ContextType>; @@ -40606,7 +40606,7 @@ export type ExclusionListResolvers; metrics?: Resolver>>, ParentType, ContextType>; name?: Resolver; - parent_types?: Resolver>, ParentType, ContextType>; + parent_types?: Resolver, ParentType, ContextType>; standard_id?: Resolver; __isTypeOf?: IsTypeOfResolverFn; }>; @@ -40651,7 +40651,7 @@ export type ExternalReferenceResolvers>>, ParentType, ContextType, Partial>; metrics?: Resolver>>, ParentType, ContextType>; modified?: Resolver, ParentType, ContextType>; - parent_types?: Resolver>, ParentType, ContextType>; + parent_types?: Resolver, ParentType, ContextType>; pendingFiles?: Resolver>; references?: Resolver, ParentType, ContextType, Partial>; refreshed_at?: Resolver, ParentType, ContextType>; @@ -40769,7 +40769,7 @@ export type FeedbackResolvers, ParentType, ContextType, Partial>; opinions?: Resolver, ParentType, ContextType, Partial>; opinions_metrics?: Resolver, ParentType, ContextType>; - parent_types?: Resolver>, ParentType, ContextType>; + parent_types?: Resolver, ParentType, ContextType>; pendingFiles?: Resolver, ParentType, ContextType, Partial>; pirInformation?: Resolver, ParentType, ContextType, RequireFields>; rating?: Resolver, ParentType, ContextType>; @@ -40887,7 +40887,7 @@ export type FintelDesignResolvers; metrics?: Resolver>>, ParentType, ContextType>; name?: Resolver; - parent_types?: Resolver>, ParentType, ContextType>; + parent_types?: Resolver, ParentType, ContextType>; standard_id?: Resolver; textColor?: Resolver, ParentType, ContextType>; __isTypeOf?: IsTypeOfResolverFn; @@ -40911,7 +40911,7 @@ export type FintelTemplateResolvers, ParentType, ContextType>; metrics?: Resolver>>, ParentType, ContextType>; name?: Resolver; - parent_types?: Resolver>, ParentType, ContextType>; + parent_types?: Resolver, ParentType, ContextType>; settings_types?: Resolver, ParentType, ContextType>; standard_id?: Resolver; start_date?: Resolver, ParentType, ContextType>; @@ -40993,7 +40993,7 @@ export type GroupResolvers; no_creators?: Resolver, ParentType, ContextType>; not_shareable_marking_types?: Resolver, ParentType, ContextType>; - parent_types?: Resolver>, ParentType, ContextType>; + parent_types?: Resolver, ParentType, ContextType>; refreshed_at?: Resolver, ParentType, ContextType>; restrict_delete?: Resolver, ParentType, ContextType>; roles?: Resolver, ParentType, ContextType, Partial>; @@ -41067,7 +41067,7 @@ export type GroupingResolvers, ParentType, ContextType, Partial>; opinions?: Resolver, ParentType, ContextType, Partial>; opinions_metrics?: Resolver, ParentType, ContextType>; - parent_types?: Resolver>, ParentType, ContextType>; + parent_types?: Resolver, ParentType, ContextType>; pendingFiles?: Resolver, ParentType, ContextType, Partial>; pirInformation?: Resolver, ParentType, ContextType, RequireFields>; refreshed_at?: Resolver, ParentType, ContextType>; @@ -41139,7 +41139,7 @@ export type HashedObservableResolvers, ParentType, ContextType, Partial>; opinions?: Resolver, ParentType, ContextType, Partial>; opinions_metrics?: Resolver, ParentType, ContextType>; - parent_types?: Resolver>, ParentType, ContextType>; + parent_types?: Resolver, ParentType, ContextType>; pendingFiles?: Resolver, ParentType, ContextType, Partial>; refreshed_at?: Resolver, ParentType, ContextType>; reports?: Resolver, ParentType, ContextType, Partial>; @@ -41186,7 +41186,7 @@ export type HostnameResolvers, ParentType, ContextType, Partial>; opinions?: Resolver, ParentType, ContextType, Partial>; opinions_metrics?: Resolver, ParentType, ContextType>; - parent_types?: Resolver>, ParentType, ContextType>; + parent_types?: Resolver, ParentType, ContextType>; pendingFiles?: Resolver, ParentType, ContextType, Partial>; refreshed_at?: Resolver, ParentType, ContextType>; reports?: Resolver, ParentType, ContextType, Partial>; @@ -41237,7 +41237,7 @@ export type IPv4AddrResolvers, ParentType, ContextType, Partial>; opinions?: Resolver, ParentType, ContextType, Partial>; opinions_metrics?: Resolver, ParentType, ContextType>; - parent_types?: Resolver>, ParentType, ContextType>; + parent_types?: Resolver, ParentType, ContextType>; pendingFiles?: Resolver, ParentType, ContextType, Partial>; refreshed_at?: Resolver, ParentType, ContextType>; reports?: Resolver, ParentType, ContextType, Partial>; @@ -41288,7 +41288,7 @@ export type IPv6AddrResolvers, ParentType, ContextType, Partial>; opinions?: Resolver, ParentType, ContextType, Partial>; opinions_metrics?: Resolver, ParentType, ContextType>; - parent_types?: Resolver>, ParentType, ContextType>; + parent_types?: Resolver, ParentType, ContextType>; pendingFiles?: Resolver, ParentType, ContextType, Partial>; refreshed_at?: Resolver, ParentType, ContextType>; reports?: Resolver, ParentType, ContextType, Partial>; @@ -41347,7 +41347,7 @@ export type IdentityResolvers, ParentType, ContextType, Partial>; opinions?: Resolver, ParentType, ContextType, Partial>; opinions_metrics?: Resolver, ParentType, ContextType>; - parent_types?: Resolver>, ParentType, ContextType>; + parent_types?: Resolver, ParentType, ContextType>; pendingFiles?: Resolver, ParentType, ContextType, Partial>; refreshed_at?: Resolver, ParentType, ContextType>; reports?: Resolver, ParentType, ContextType, Partial>; @@ -41430,7 +41430,7 @@ export type IncidentResolvers, ParentType, ContextType, Partial>; opinions?: Resolver, ParentType, ContextType, Partial>; opinions_metrics?: Resolver, ParentType, ContextType>; - parent_types?: Resolver>, ParentType, ContextType>; + parent_types?: Resolver, ParentType, ContextType>; pendingFiles?: Resolver, ParentType, ContextType, Partial>; pirInformation?: Resolver, ParentType, ContextType, RequireFields>; refreshed_at?: Resolver, ParentType, ContextType>; @@ -41545,7 +41545,7 @@ export type IndicatorResolvers, ParentType, ContextType, Partial>; opinions?: Resolver, ParentType, ContextType, Partial>; opinions_metrics?: Resolver, ParentType, ContextType>; - parent_types?: Resolver>, ParentType, ContextType>; + parent_types?: Resolver, ParentType, ContextType>; pattern?: Resolver, ParentType, ContextType>; pattern_type?: Resolver, ParentType, ContextType>; pattern_version?: Resolver, ParentType, ContextType>; @@ -41643,7 +41643,7 @@ export type IndividualResolvers, ParentType, ContextType, Partial>; opinions_metrics?: Resolver, ParentType, ContextType>; organizations?: Resolver, ParentType, ContextType>; - parent_types?: Resolver>, ParentType, ContextType>; + parent_types?: Resolver, ParentType, ContextType>; pendingFiles?: Resolver, ParentType, ContextType, Partial>; pirInformation?: Resolver, ParentType, ContextType, RequireFields>; refreshed_at?: Resolver, ParentType, ContextType>; @@ -41742,7 +41742,7 @@ export type InfrastructureResolvers, ParentType, ContextType, Partial>; opinions?: Resolver, ParentType, ContextType, Partial>; opinions_metrics?: Resolver, ParentType, ContextType>; - parent_types?: Resolver>, ParentType, ContextType>; + parent_types?: Resolver, ParentType, ContextType>; pendingFiles?: Resolver, ParentType, ContextType, Partial>; pirInformation?: Resolver, ParentType, ContextType, RequireFields>; refreshed_at?: Resolver, ParentType, ContextType>; @@ -41801,7 +41801,7 @@ export type IngestionCsvResolvers>, ParentType, ContextType>; metrics?: Resolver>>, ParentType, ContextType>; name?: Resolver; - parent_types?: Resolver>, ParentType, ContextType>; + parent_types?: Resolver, ParentType, ContextType>; refreshed_at?: Resolver, ParentType, ContextType>; scheduling_period?: Resolver, ParentType, ContextType>; standard_id?: Resolver; @@ -41847,7 +41847,7 @@ export type IngestionJsonResolvers, ParentType, ContextType>; pagination_with_sub_page_attribute_path?: Resolver, ParentType, ContextType>; pagination_with_sub_page_query_verb?: Resolver, ParentType, ContextType>; - parent_types?: Resolver>, ParentType, ContextType>; + parent_types?: Resolver, ParentType, ContextType>; query_attributes?: Resolver>, ParentType, ContextType>; refreshed_at?: Resolver, ParentType, ContextType>; scheduling_period?: Resolver, ParentType, ContextType>; @@ -41993,7 +41993,7 @@ export type InternalRelationshipResolvers, ParentType, ContextType>; id?: Resolver; metrics?: Resolver>>, ParentType, ContextType>; - parent_types?: Resolver>, ParentType, ContextType>; + parent_types?: Resolver, ParentType, ContextType>; refreshed_at?: Resolver, ParentType, ContextType>; standard_id?: Resolver; to?: Resolver, ParentType, ContextType>; @@ -42049,7 +42049,7 @@ export type IntrusionSetResolvers, ParentType, ContextType, Partial>; opinions?: Resolver, ParentType, ContextType, Partial>; opinions_metrics?: Resolver, ParentType, ContextType>; - parent_types?: Resolver>, ParentType, ContextType>; + parent_types?: Resolver, ParentType, ContextType>; pendingFiles?: Resolver, ParentType, ContextType, Partial>; pirInformation?: Resolver, ParentType, ContextType, RequireFields>; primary_motivation?: Resolver, ParentType, ContextType>; @@ -42199,7 +42199,7 @@ export type KillChainPhaseResolvers; metrics?: Resolver>>, ParentType, ContextType>; modified?: Resolver, ParentType, ContextType>; - parent_types?: Resolver>, ParentType, ContextType>; + parent_types?: Resolver, ParentType, ContextType>; phase_name?: Resolver; refreshed_at?: Resolver, ParentType, ContextType>; representative?: Resolver; @@ -42244,7 +42244,7 @@ export type LabelResolvers; metrics?: Resolver>>, ParentType, ContextType>; modified?: Resolver, ParentType, ContextType>; - parent_types?: Resolver>, ParentType, ContextType>; + parent_types?: Resolver, ParentType, ContextType>; refreshed_at?: Resolver, ParentType, ContextType>; representative?: Resolver; spec_version?: Resolver; @@ -42311,7 +42311,7 @@ export type LanguageResolvers, ParentType, ContextType, Partial>; opinions?: Resolver, ParentType, ContextType, Partial>; opinions_metrics?: Resolver, ParentType, ContextType>; - parent_types?: Resolver>, ParentType, ContextType>; + parent_types?: Resolver, ParentType, ContextType>; pendingFiles?: Resolver, ParentType, ContextType, Partial>; pirInformation?: Resolver, ParentType, ContextType, RequireFields>; refreshed_at?: Resolver, ParentType, ContextType>; @@ -42402,7 +42402,7 @@ export type LocationResolvers, ParentType, ContextType, Partial>; opinions?: Resolver, ParentType, ContextType, Partial>; opinions_metrics?: Resolver, ParentType, ContextType>; - parent_types?: Resolver>, ParentType, ContextType>; + parent_types?: Resolver, ParentType, ContextType>; pendingFiles?: Resolver, ParentType, ContextType, Partial>; precision?: Resolver, ParentType, ContextType>; refreshed_at?: Resolver, ParentType, ContextType>; @@ -42508,7 +42508,7 @@ export type MacAddrResolvers, ParentType, ContextType, Partial>; opinions?: Resolver, ParentType, ContextType, Partial>; opinions_metrics?: Resolver, ParentType, ContextType>; - parent_types?: Resolver>, ParentType, ContextType>; + parent_types?: Resolver, ParentType, ContextType>; pendingFiles?: Resolver, ParentType, ContextType, Partial>; refreshed_at?: Resolver, ParentType, ContextType>; reports?: Resolver, ParentType, ContextType, Partial>; @@ -42574,7 +42574,7 @@ export type MalwareResolvers, ParentType, ContextType, Partial>; opinions?: Resolver, ParentType, ContextType, Partial>; opinions_metrics?: Resolver, ParentType, ContextType>; - parent_types?: Resolver>, ParentType, ContextType>; + parent_types?: Resolver, ParentType, ContextType>; pendingFiles?: Resolver, ParentType, ContextType, Partial>; pirInformation?: Resolver, ParentType, ContextType, RequireFields>; refreshed_at?: Resolver, ParentType, ContextType>; @@ -42642,7 +42642,7 @@ export type MalwareAnalysisResolvers, ParentType, ContextType>; opinions?: Resolver, ParentType, ContextType, Partial>; opinions_metrics?: Resolver, ParentType, ContextType>; - parent_types?: Resolver>, ParentType, ContextType>; + parent_types?: Resolver, ParentType, ContextType>; pendingFiles?: Resolver, ParentType, ContextType, Partial>; pirInformation?: Resolver, ParentType, ContextType, RequireFields>; product?: Resolver; @@ -42717,7 +42717,7 @@ export type ManagedConnectorResolvers; metrics?: Resolver>>, ParentType, ContextType>; name?: Resolver; - parent_types?: Resolver>, ParentType, ContextType>; + parent_types?: Resolver, ParentType, ContextType>; standard_id?: Resolver; __isTypeOf?: IsTypeOfResolverFn; }>; @@ -42773,7 +42773,7 @@ export type MarkingDefinitionResolvers; metrics?: Resolver>>, ParentType, ContextType>; modified?: Resolver, ParentType, ContextType>; - parent_types?: Resolver>, ParentType, ContextType>; + parent_types?: Resolver, ParentType, ContextType>; refreshed_at?: Resolver, ParentType, ContextType>; representative?: Resolver; spec_version?: Resolver; @@ -42916,7 +42916,7 @@ export type MediaContentResolvers, ParentType, ContextType, Partial>; opinions?: Resolver, ParentType, ContextType, Partial>; opinions_metrics?: Resolver, ParentType, ContextType>; - parent_types?: Resolver>, ParentType, ContextType>; + parent_types?: Resolver, ParentType, ContextType>; pendingFiles?: Resolver, ParentType, ContextType, Partial>; publication_date?: Resolver, ParentType, ContextType>; refreshed_at?: Resolver, ParentType, ContextType>; @@ -43483,7 +43483,7 @@ export type MutexResolvers, ParentType, ContextType, Partial>; opinions?: Resolver, ParentType, ContextType, Partial>; opinions_metrics?: Resolver, ParentType, ContextType>; - parent_types?: Resolver>, ParentType, ContextType>; + parent_types?: Resolver, ParentType, ContextType>; pendingFiles?: Resolver, ParentType, ContextType, Partial>; refreshed_at?: Resolver, ParentType, ContextType>; reports?: Resolver, ParentType, ContextType, Partial>; @@ -43548,7 +43548,7 @@ export type NarrativeResolvers, ParentType, ContextType, Partial>; opinions_metrics?: Resolver, ParentType, ContextType>; parentNarratives?: Resolver, ParentType, ContextType>; - parent_types?: Resolver>, ParentType, ContextType>; + parent_types?: Resolver, ParentType, ContextType>; pendingFiles?: Resolver, ParentType, ContextType, Partial>; pirInformation?: Resolver, ParentType, ContextType, RequireFields>; refreshed_at?: Resolver, ParentType, ContextType>; @@ -43618,7 +43618,7 @@ export type NetworkTrafficResolvers, ParentType, ContextType, Partial>; opinions?: Resolver, ParentType, ContextType, Partial>; opinions_metrics?: Resolver, ParentType, ContextType>; - parent_types?: Resolver>, ParentType, ContextType>; + parent_types?: Resolver, ParentType, ContextType>; pendingFiles?: Resolver, ParentType, ContextType, Partial>; protocols?: Resolver>>, ParentType, ContextType>; refreshed_at?: Resolver, ParentType, ContextType>; @@ -43689,7 +43689,7 @@ export type NoteResolvers, ParentType, ContextType, Partial>; opinions?: Resolver, ParentType, ContextType, Partial>; opinions_metrics?: Resolver, ParentType, ContextType>; - parent_types?: Resolver>, ParentType, ContextType>; + parent_types?: Resolver, ParentType, ContextType>; pendingFiles?: Resolver, ParentType, ContextType, Partial>; pirInformation?: Resolver, ParentType, ContextType, RequireFields>; refreshed_at?: Resolver, ParentType, ContextType>; @@ -43742,7 +43742,7 @@ export type NotificationResolvers; notification_content?: Resolver, ParentType, ContextType>; notification_type?: Resolver; - parent_types?: Resolver>, ParentType, ContextType>; + parent_types?: Resolver, ParentType, ContextType>; refreshed_at?: Resolver, ParentType, ContextType>; standard_id?: Resolver; updated_at?: Resolver, ParentType, ContextType>; @@ -43788,7 +43788,7 @@ export type NotifierResolvers; notifier_connector?: Resolver; notifier_connector_id?: Resolver; - parent_types?: Resolver>, ParentType, ContextType>; + parent_types?: Resolver, ParentType, ContextType>; standard_id?: Resolver; __isTypeOf?: IsTypeOfResolverFn; }>; @@ -43878,7 +43878,7 @@ export type ObservedDataResolvers, ParentType, ContextType, Partial>; opinions?: Resolver, ParentType, ContextType, Partial>; opinions_metrics?: Resolver, ParentType, ContextType>; - parent_types?: Resolver>, ParentType, ContextType>; + parent_types?: Resolver, ParentType, ContextType>; pendingFiles?: Resolver, ParentType, ContextType, Partial>; pirInformation?: Resolver, ParentType, ContextType, RequireFields>; refreshed_at?: Resolver, ParentType, ContextType>; @@ -43973,7 +43973,7 @@ export type OpinionResolvers; opinions?: Resolver, ParentType, ContextType, Partial>; opinions_metrics?: Resolver, ParentType, ContextType>; - parent_types?: Resolver>, ParentType, ContextType>; + parent_types?: Resolver, ParentType, ContextType>; pendingFiles?: Resolver, ParentType, ContextType, Partial>; pirInformation?: Resolver, ParentType, ContextType, RequireFields>; refreshed_at?: Resolver, ParentType, ContextType>; @@ -44070,7 +44070,7 @@ export type OrganizationResolvers, ParentType, ContextType, Partial>; opinions_metrics?: Resolver, ParentType, ContextType>; parentOrganizations?: Resolver, ParentType, ContextType>; - parent_types?: Resolver>, ParentType, ContextType>; + parent_types?: Resolver, ParentType, ContextType>; pendingFiles?: Resolver, ParentType, ContextType, Partial>; pirInformation?: Resolver, ParentType, ContextType, RequireFields>; refreshed_at?: Resolver, ParentType, ContextType>; @@ -44189,7 +44189,7 @@ export type PaymentCardResolvers, ParentType, ContextType, Partial>; opinions?: Resolver, ParentType, ContextType, Partial>; opinions_metrics?: Resolver, ParentType, ContextType>; - parent_types?: Resolver>, ParentType, ContextType>; + parent_types?: Resolver, ParentType, ContextType>; pendingFiles?: Resolver, ParentType, ContextType, Partial>; refreshed_at?: Resolver, ParentType, ContextType>; reports?: Resolver, ParentType, ContextType, Partial>; @@ -44238,7 +44238,7 @@ export type PersonaResolvers, ParentType, ContextType, Partial>; opinions?: Resolver, ParentType, ContextType, Partial>; opinions_metrics?: Resolver, ParentType, ContextType>; - parent_types?: Resolver>, ParentType, ContextType>; + parent_types?: Resolver, ParentType, ContextType>; pendingFiles?: Resolver, ParentType, ContextType, Partial>; persona_name?: Resolver; persona_type?: Resolver; @@ -44289,7 +44289,7 @@ export type PhoneNumberResolvers, ParentType, ContextType, Partial>; opinions?: Resolver, ParentType, ContextType, Partial>; opinions_metrics?: Resolver, ParentType, ContextType>; - parent_types?: Resolver>, ParentType, ContextType>; + parent_types?: Resolver, ParentType, ContextType>; pendingFiles?: Resolver, ParentType, ContextType, Partial>; refreshed_at?: Resolver, ParentType, ContextType>; reports?: Resolver, ParentType, ContextType, Partial>; @@ -44375,7 +44375,7 @@ export type PirRelationshipResolvers; id?: Resolver; metrics?: Resolver>>, ParentType, ContextType>; - parent_types?: Resolver>, ParentType, ContextType>; + parent_types?: Resolver, ParentType, ContextType>; pir_explanation?: Resolver>, ParentType, ContextType>; pir_score?: Resolver, ParentType, ContextType>; refreshed_at?: Resolver, ParentType, ContextType>; @@ -44553,7 +44553,7 @@ export type PositionResolvers, ParentType, ContextType, Partial>; opinions?: Resolver, ParentType, ContextType, Partial>; opinions_metrics?: Resolver, ParentType, ContextType>; - parent_types?: Resolver>, ParentType, ContextType>; + parent_types?: Resolver, ParentType, ContextType>; pendingFiles?: Resolver, ParentType, ContextType, Partial>; pirInformation?: Resolver, ParentType, ContextType, RequireFields>; postal_code?: Resolver, ParentType, ContextType>; @@ -44641,7 +44641,7 @@ export type ProcessResolvers, ParentType, ContextType, Partial>; opinions_metrics?: Resolver, ParentType, ContextType>; owner_sid?: Resolver, ParentType, ContextType>; - parent_types?: Resolver>, ParentType, ContextType>; + parent_types?: Resolver, ParentType, ContextType>; pendingFiles?: Resolver, ParentType, ContextType, Partial>; pid?: Resolver, ParentType, ContextType>; priority?: Resolver, ParentType, ContextType>; @@ -45236,7 +45236,7 @@ export type RegionResolvers, ParentType, ContextType, Partial>; opinions_metrics?: Resolver, ParentType, ContextType>; parentRegions?: Resolver, ParentType, ContextType>; - parent_types?: Resolver>, ParentType, ContextType>; + parent_types?: Resolver, ParentType, ContextType>; pendingFiles?: Resolver, ParentType, ContextType, Partial>; pirInformation?: Resolver, ParentType, ContextType, RequireFields>; precision?: Resolver, ParentType, ContextType>; @@ -45333,7 +45333,7 @@ export type ReportResolvers, ParentType, ContextType, Partial>; opinions?: Resolver, ParentType, ContextType, Partial>; opinions_metrics?: Resolver, ParentType, ContextType>; - parent_types?: Resolver>, ParentType, ContextType>; + parent_types?: Resolver, ParentType, ContextType>; pendingFiles?: Resolver, ParentType, ContextType, Partial>; pirInformation?: Resolver, ParentType, ContextType, RequireFields>; published?: Resolver, ParentType, ContextType>; @@ -45466,7 +45466,7 @@ export type RoleResolvers; metrics?: Resolver>>, ParentType, ContextType>; name?: Resolver; - parent_types?: Resolver>, ParentType, ContextType>; + parent_types?: Resolver, ParentType, ContextType>; refreshed_at?: Resolver, ParentType, ContextType>; standard_id?: Resolver; updated_at?: Resolver; @@ -45568,7 +45568,7 @@ export type SshKeyResolvers, ParentType, ContextType, Partial>; opinions?: Resolver, ParentType, ContextType, Partial>; opinions_metrics?: Resolver, ParentType, ContextType>; - parent_types?: Resolver>, ParentType, ContextType>; + parent_types?: Resolver, ParentType, ContextType>; pendingFiles?: Resolver, ParentType, ContextType, Partial>; public_key?: Resolver, ParentType, ContextType>; refreshed_at?: Resolver, ParentType, ContextType>; @@ -45595,7 +45595,7 @@ export type SavedFilterResolvers; metrics?: Resolver>>, ParentType, ContextType>; name?: Resolver; - parent_types?: Resolver>, ParentType, ContextType>; + parent_types?: Resolver, ParentType, ContextType>; scope?: Resolver; standard_id?: Resolver; __isTypeOf?: IsTypeOfResolverFn; @@ -45661,7 +45661,7 @@ export type SectorResolvers, ParentType, ContextType, Partial>; opinions_metrics?: Resolver, ParentType, ContextType>; parentSectors?: Resolver, ParentType, ContextType>; - parent_types?: Resolver>, ParentType, ContextType>; + parent_types?: Resolver, ParentType, ContextType>; pendingFiles?: Resolver, ParentType, ContextType, Partial>; pirInformation?: Resolver, ParentType, ContextType, RequireFields>; refreshed_at?: Resolver, ParentType, ContextType>; @@ -45755,7 +45755,7 @@ export type SecurityCoverageResolvers, ParentType, ContextType, Partial>; opinions?: Resolver, ParentType, ContextType, Partial>; opinions_metrics?: Resolver, ParentType, ContextType>; - parent_types?: Resolver>, ParentType, ContextType>; + parent_types?: Resolver, ParentType, ContextType>; pendingFiles?: Resolver, ParentType, ContextType, Partial>; periodicity?: Resolver, ParentType, ContextType>; pirInformation?: Resolver, ParentType, ContextType, RequireFields>; @@ -45834,7 +45834,7 @@ export type SecurityPlatformResolvers, ParentType, ContextType, Partial>; opinions?: Resolver, ParentType, ContextType, Partial>; opinions_metrics?: Resolver, ParentType, ContextType>; - parent_types?: Resolver>, ParentType, ContextType>; + parent_types?: Resolver, ParentType, ContextType>; pendingFiles?: Resolver, ParentType, ContextType, Partial>; pirInformation?: Resolver, ParentType, ContextType, RequireFields>; refreshed_at?: Resolver, ParentType, ContextType>; @@ -46006,7 +46006,7 @@ export type SoftwareResolvers, ParentType, ContextType, Partial>; opinions?: Resolver, ParentType, ContextType, Partial>; opinions_metrics?: Resolver, ParentType, ContextType>; - parent_types?: Resolver>, ParentType, ContextType>; + parent_types?: Resolver, ParentType, ContextType>; pendingFiles?: Resolver, ParentType, ContextType, Partial>; refreshed_at?: Resolver, ParentType, ContextType>; reports?: Resolver, ParentType, ContextType, Partial>; @@ -46108,7 +46108,7 @@ export type StixCoreObjectResolvers, ParentType, ContextType, Partial>; opinions?: Resolver, ParentType, ContextType, Partial>; opinions_metrics?: Resolver, ParentType, ContextType>; - parent_types?: Resolver>, ParentType, ContextType>; + parent_types?: Resolver, ParentType, ContextType>; pendingFiles?: Resolver, ParentType, ContextType, Partial>; refreshed_at?: Resolver, ParentType, ContextType>; reports?: Resolver, ParentType, ContextType, Partial>; @@ -46189,7 +46189,7 @@ export type StixCoreRelationshipResolvers>, ParentType, ContextType>; objectOrganization?: Resolver>, ParentType, ContextType>; opinions?: Resolver, ParentType, ContextType, Partial>; - parent_types?: Resolver>, ParentType, ContextType>; + parent_types?: Resolver, ParentType, ContextType>; refreshed_at?: Resolver, ParentType, ContextType>; relationship_type?: Resolver; reports?: Resolver, ParentType, ContextType, Partial>; @@ -46267,7 +46267,7 @@ export type StixCyberObservableResolvers, ParentType, ContextType, Partial>; opinions?: Resolver, ParentType, ContextType, Partial>; opinions_metrics?: Resolver, ParentType, ContextType>; - parent_types?: Resolver>, ParentType, ContextType>; + parent_types?: Resolver, ParentType, ContextType>; pendingFiles?: Resolver, ParentType, ContextType, Partial>; refreshed_at?: Resolver, ParentType, ContextType>; reports?: Resolver, ParentType, ContextType, Partial>; @@ -46345,7 +46345,7 @@ export type StixDomainObjectResolvers, ParentType, ContextType, Partial>; opinions?: Resolver, ParentType, ContextType, Partial>; opinions_metrics?: Resolver, ParentType, ContextType>; - parent_types?: Resolver>, ParentType, ContextType>; + parent_types?: Resolver, ParentType, ContextType>; pendingFiles?: Resolver, ParentType, ContextType, Partial>; pirInformation?: Resolver, ParentType, ContextType, RequireFields>; refreshed_at?: Resolver, ParentType, ContextType>; @@ -46436,7 +46436,7 @@ export type StixFileResolvers, ParentType, ContextType, Partial>; opinions?: Resolver, ParentType, ContextType, Partial>; opinions_metrics?: Resolver, ParentType, ContextType>; - parent_types?: Resolver>, ParentType, ContextType>; + parent_types?: Resolver, ParentType, ContextType>; pendingFiles?: Resolver, ParentType, ContextType, Partial>; refreshed_at?: Resolver, ParentType, ContextType>; reports?: Resolver, ParentType, ContextType, Partial>; @@ -46482,7 +46482,7 @@ export type StixMetaObjectResolvers; metrics?: Resolver>>, ParentType, ContextType>; modified?: Resolver, ParentType, ContextType>; - parent_types?: Resolver>, ParentType, ContextType>; + parent_types?: Resolver, ParentType, ContextType>; refreshed_at?: Resolver, ParentType, ContextType>; spec_version?: Resolver; standard_id?: Resolver; @@ -46509,7 +46509,7 @@ export type StixObjectResolvers; is_inferred?: Resolver; metrics?: Resolver>>, ParentType, ContextType>; - parent_types?: Resolver>, ParentType, ContextType>; + parent_types?: Resolver, ParentType, ContextType>; refreshed_at?: Resolver, ParentType, ContextType>; representative?: Resolver; spec_version?: Resolver; @@ -46576,7 +46576,7 @@ export type StixRefRelationshipResolvers, ParentType, ContextType, Partial>; objectMarking?: Resolver>, ParentType, ContextType>; opinions?: Resolver, ParentType, ContextType, Partial>; - parent_types?: Resolver>, ParentType, ContextType>; + parent_types?: Resolver, ParentType, ContextType>; refreshed_at?: Resolver, ParentType, ContextType>; relationship_type?: Resolver; reports?: Resolver, ParentType, ContextType, Partial>; @@ -46626,7 +46626,7 @@ export type StixRelationshipResolvers>>, ParentType, ContextType>; modified?: Resolver, ParentType, ContextType>; objectMarking?: Resolver>, ParentType, ContextType>; - parent_types?: Resolver>, ParentType, ContextType>; + parent_types?: Resolver, ParentType, ContextType>; refreshed_at?: Resolver, ParentType, ContextType>; relationship_type?: Resolver; representative?: Resolver; @@ -46700,7 +46700,7 @@ export type StixSightingRelationshipResolvers>, ParentType, ContextType>; objectOrganization?: Resolver>, ParentType, ContextType>; opinions?: Resolver, ParentType, ContextType, Partial>; - parent_types?: Resolver>, ParentType, ContextType>; + parent_types?: Resolver, ParentType, ContextType>; refreshed_at?: Resolver, ParentType, ContextType>; relationship_type?: Resolver; reports?: Resolver, ParentType, ContextType, Partial>; @@ -46845,7 +46845,7 @@ export type SupportPackageResolvers; package_upload_dir?: Resolver, ParentType, ContextType>; package_url?: Resolver, ParentType, ContextType>; - parent_types?: Resolver>, ParentType, ContextType>; + parent_types?: Resolver, ParentType, ContextType>; standard_id?: Resolver; __isTypeOf?: IsTypeOfResolverFn; }>; @@ -46930,7 +46930,7 @@ export type SystemResolvers, ParentType, ContextType, Partial>; opinions_metrics?: Resolver, ParentType, ContextType>; organizations?: Resolver, ParentType, ContextType>; - parent_types?: Resolver>, ParentType, ContextType>; + parent_types?: Resolver, ParentType, ContextType>; pendingFiles?: Resolver, ParentType, ContextType, Partial>; pirInformation?: Resolver, ParentType, ContextType, RequireFields>; refreshed_at?: Resolver, ParentType, ContextType>; @@ -47021,7 +47021,7 @@ export type TaskResolvers, ParentType, ContextType, Partial>; opinions?: Resolver, ParentType, ContextType, Partial>; opinions_metrics?: Resolver, ParentType, ContextType>; - parent_types?: Resolver>, ParentType, ContextType>; + parent_types?: Resolver, ParentType, ContextType>; pendingFiles?: Resolver, ParentType, ContextType, Partial>; pirInformation?: Resolver, ParentType, ContextType, RequireFields>; refreshed_at?: Resolver, ParentType, ContextType>; @@ -47063,7 +47063,7 @@ export type TaskTemplateResolvers>>, ParentType, ContextType>; modified?: Resolver, ParentType, ContextType>; name?: Resolver; - parent_types?: Resolver>, ParentType, ContextType>; + parent_types?: Resolver, ParentType, ContextType>; standard_id?: Resolver; __isTypeOf?: IsTypeOfResolverFn; }>; @@ -47144,7 +47144,7 @@ export type TextResolvers, ParentType, ContextType, Partial>; opinions?: Resolver, ParentType, ContextType, Partial>; opinions_metrics?: Resolver, ParentType, ContextType>; - parent_types?: Resolver>, ParentType, ContextType>; + parent_types?: Resolver, ParentType, ContextType>; pendingFiles?: Resolver, ParentType, ContextType, Partial>; refreshed_at?: Resolver, ParentType, ContextType>; reports?: Resolver, ParentType, ContextType, Partial>; @@ -47247,7 +47247,7 @@ export type ThreatActorResolvers, ParentType, ContextType, Partial>; opinions?: Resolver, ParentType, ContextType, Partial>; opinions_metrics?: Resolver, ParentType, ContextType>; - parent_types?: Resolver>, ParentType, ContextType>; + parent_types?: Resolver, ParentType, ContextType>; pendingFiles?: Resolver, ParentType, ContextType, Partial>; personal_motivations?: Resolver>>, ParentType, ContextType>; pirInformation?: Resolver, ParentType, ContextType, RequireFields>; @@ -47328,7 +47328,7 @@ export type ThreatActorGroupResolvers, ParentType, ContextType, Partial>; opinions?: Resolver, ParentType, ContextType, Partial>; opinions_metrics?: Resolver, ParentType, ContextType>; - parent_types?: Resolver>, ParentType, ContextType>; + parent_types?: Resolver, ParentType, ContextType>; pendingFiles?: Resolver, ParentType, ContextType, Partial>; personal_motivations?: Resolver>>, ParentType, ContextType>; pirInformation?: Resolver, ParentType, ContextType, RequireFields>; @@ -47428,7 +47428,7 @@ export type ThreatActorIndividualResolvers, ParentType, ContextType, Partial>; opinions?: Resolver, ParentType, ContextType, Partial>; opinions_metrics?: Resolver, ParentType, ContextType>; - parent_types?: Resolver>, ParentType, ContextType>; + parent_types?: Resolver, ParentType, ContextType>; pendingFiles?: Resolver, ParentType, ContextType, Partial>; personal_motivations?: Resolver>>, ParentType, ContextType>; pirInformation?: Resolver, ParentType, ContextType, RequireFields>; @@ -47513,7 +47513,7 @@ export type ToolResolvers, ParentType, ContextType, Partial>; opinions?: Resolver, ParentType, ContextType, Partial>; opinions_metrics?: Resolver, ParentType, ContextType>; - parent_types?: Resolver>, ParentType, ContextType>; + parent_types?: Resolver, ParentType, ContextType>; pendingFiles?: Resolver, ParentType, ContextType, Partial>; pirInformation?: Resolver, ParentType, ContextType, RequireFields>; refreshed_at?: Resolver, ParentType, ContextType>; @@ -47586,7 +47586,7 @@ export type TrackingNumberResolvers, ParentType, ContextType, Partial>; opinions?: Resolver, ParentType, ContextType, Partial>; opinions_metrics?: Resolver, ParentType, ContextType>; - parent_types?: Resolver>, ParentType, ContextType>; + parent_types?: Resolver, ParentType, ContextType>; pendingFiles?: Resolver, ParentType, ContextType, Partial>; refreshed_at?: Resolver, ParentType, ContextType>; reports?: Resolver, ParentType, ContextType, Partial>; @@ -47622,7 +47622,7 @@ export type TriggerResolvers, ParentType, ContextType>; name?: Resolver; notifiers?: Resolver>, ParentType, ContextType>; - parent_types?: Resolver>, ParentType, ContextType>; + parent_types?: Resolver, ParentType, ContextType>; period?: Resolver, ParentType, ContextType>; recipients?: Resolver>, ParentType, ContextType>; refreshed_at?: Resolver, ParentType, ContextType>; @@ -47691,7 +47691,7 @@ export type UrlResolvers, ParentType, ContextType, Partial>; opinions?: Resolver, ParentType, ContextType, Partial>; opinions_metrics?: Resolver, ParentType, ContextType>; - parent_types?: Resolver>, ParentType, ContextType>; + parent_types?: Resolver, ParentType, ContextType>; pendingFiles?: Resolver, ParentType, ContextType, Partial>; refreshed_at?: Resolver, ParentType, ContextType>; reports?: Resolver, ParentType, ContextType, Partial>; @@ -47743,7 +47743,7 @@ export type UserResolvers, ParentType, ContextType>; otp_mandatory?: Resolver, ParentType, ContextType>; otp_qr?: Resolver, ParentType, ContextType>; - parent_types?: Resolver>, ParentType, ContextType>; + parent_types?: Resolver, ParentType, ContextType>; personal_notifiers?: Resolver>, ParentType, ContextType>; refreshed_at?: Resolver, ParentType, ContextType>; restrict_delete?: Resolver, ParentType, ContextType>; @@ -47804,7 +47804,7 @@ export type UserAccountResolvers, ParentType, ContextType, Partial>; opinions?: Resolver, ParentType, ContextType, Partial>; opinions_metrics?: Resolver, ParentType, ContextType>; - parent_types?: Resolver>, ParentType, ContextType>; + parent_types?: Resolver, ParentType, ContextType>; pendingFiles?: Resolver, ParentType, ContextType, Partial>; refreshed_at?: Resolver, ParentType, ContextType>; reports?: Resolver, ParentType, ContextType, Partial>; @@ -47854,7 +47854,7 @@ export type UserAgentResolvers, ParentType, ContextType, Partial>; opinions?: Resolver, ParentType, ContextType, Partial>; opinions_metrics?: Resolver, ParentType, ContextType>; - parent_types?: Resolver>, ParentType, ContextType>; + parent_types?: Resolver, ParentType, ContextType>; pendingFiles?: Resolver, ParentType, ContextType, Partial>; refreshed_at?: Resolver, ParentType, ContextType>; reports?: Resolver, ParentType, ContextType, Partial>; @@ -47928,7 +47928,7 @@ export type VocabularyResolvers, ParentType, ContextType>; name?: Resolver; order?: Resolver, ParentType, ContextType>; - parent_types?: Resolver>, ParentType, ContextType>; + parent_types?: Resolver, ParentType, ContextType>; refreshed_at?: Resolver, ParentType, ContextType>; representative?: Resolver; spec_version?: Resolver; @@ -48000,7 +48000,7 @@ export type VulnerabilityResolvers, ParentType, ContextType, Partial>; opinions?: Resolver, ParentType, ContextType, Partial>; opinions_metrics?: Resolver, ParentType, ContextType>; - parent_types?: Resolver>, ParentType, ContextType>; + parent_types?: Resolver, ParentType, ContextType>; pendingFiles?: Resolver, ParentType, ContextType, Partial>; pirInformation?: Resolver, ParentType, ContextType, RequireFields>; refreshed_at?: Resolver, ParentType, ContextType>; @@ -48178,7 +48178,7 @@ export type WindowsRegistryKeyResolvers, ParentType, ContextType, Partial>; opinions?: Resolver, ParentType, ContextType, Partial>; opinions_metrics?: Resolver, ParentType, ContextType>; - parent_types?: Resolver>, ParentType, ContextType>; + parent_types?: Resolver, ParentType, ContextType>; pendingFiles?: Resolver, ParentType, ContextType, Partial>; refreshed_at?: Resolver, ParentType, ContextType>; reports?: Resolver, ParentType, ContextType, Partial>; @@ -48230,7 +48230,7 @@ export type WindowsRegistryValueTypeResolvers, ParentType, ContextType, Partial>; opinions?: Resolver, ParentType, ContextType, Partial>; opinions_metrics?: Resolver, ParentType, ContextType>; - parent_types?: Resolver>, ParentType, ContextType>; + parent_types?: Resolver, ParentType, ContextType>; pendingFiles?: Resolver, ParentType, ContextType, Partial>; refreshed_at?: Resolver, ParentType, ContextType>; reports?: Resolver, ParentType, ContextType, Partial>; @@ -48380,7 +48380,7 @@ export type X509CertificateResolvers, ParentType, ContextType, Partial>; opinions?: Resolver, ParentType, ContextType, Partial>; opinions_metrics?: Resolver, ParentType, ContextType>; - parent_types?: Resolver>, ParentType, ContextType>; + parent_types?: Resolver, ParentType, ContextType>; pendingFiles?: Resolver, ParentType, ContextType, Partial>; policy_constraints?: Resolver, ParentType, ContextType>; policy_mappings?: Resolver, ParentType, ContextType>; diff --git a/opencti-platform/opencti-graphql/src/modules/administrativeArea/administrativeArea.graphql b/opencti-platform/opencti-graphql/src/modules/administrativeArea/administrativeArea.graphql index 3eb8f307720a..7edfd85b00d6 100644 --- a/opencti-platform/opencti-graphql/src/modules/administrativeArea/administrativeArea.graphql +++ b/opencti-platform/opencti-graphql/src/modules/administrativeArea/administrativeArea.graphql @@ -2,7 +2,7 @@ type AdministrativeArea implements BasicObject & StixCoreObject & StixDomainObje id: ID! # internal_id standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics:[Metric] # StixObject representative: Representative! diff --git a/opencti-platform/opencti-graphql/src/modules/case/case-incident/case-incident.graphql b/opencti-platform/opencti-graphql/src/modules/case/case-incident/case-incident.graphql index de1d56aaad77..30775d0a8cbf 100644 --- a/opencti-platform/opencti-graphql/src/modules/case/case-incident/case-incident.graphql +++ b/opencti-platform/opencti-graphql/src/modules/case/case-incident/case-incident.graphql @@ -2,7 +2,7 @@ type CaseIncident implements BasicObject & StixObject & StixCoreObject & StixDom id: ID! # internal_id standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics:[Metric] # StixObject representative: Representative! diff --git a/opencti-platform/opencti-graphql/src/modules/case/case-rfi/case-rfi.graphql b/opencti-platform/opencti-graphql/src/modules/case/case-rfi/case-rfi.graphql index 1c2e02e69f7a..0dc510713f69 100644 --- a/opencti-platform/opencti-graphql/src/modules/case/case-rfi/case-rfi.graphql +++ b/opencti-platform/opencti-graphql/src/modules/case/case-rfi/case-rfi.graphql @@ -7,7 +7,7 @@ type CaseRfi implements BasicObject & StixObject & StixCoreObject & StixDomainOb id: ID! # internal_id standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics:[Metric] # StixObject representative: Representative! diff --git a/opencti-platform/opencti-graphql/src/modules/case/case-rft/case-rft.graphql b/opencti-platform/opencti-graphql/src/modules/case/case-rft/case-rft.graphql index 10c637895625..bed0b2c8e76f 100644 --- a/opencti-platform/opencti-graphql/src/modules/case/case-rft/case-rft.graphql +++ b/opencti-platform/opencti-graphql/src/modules/case/case-rft/case-rft.graphql @@ -2,7 +2,7 @@ type CaseRft implements BasicObject & StixObject & StixCoreObject & StixDomainOb id: ID! # internal_id standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics:[Metric] # StixObject representative: Representative! diff --git a/opencti-platform/opencti-graphql/src/modules/case/case-template/case-template.graphql b/opencti-platform/opencti-graphql/src/modules/case/case-template/case-template.graphql index 70f29c76721f..59916e3eab24 100644 --- a/opencti-platform/opencti-graphql/src/modules/case/case-template/case-template.graphql +++ b/opencti-platform/opencti-graphql/src/modules/case/case-template/case-template.graphql @@ -2,7 +2,7 @@ type CaseTemplate implements InternalObject & BasicObject { id: ID! # internal_id standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics:[Metric] created: DateTime modified: DateTime diff --git a/opencti-platform/opencti-graphql/src/modules/case/case.graphql b/opencti-platform/opencti-graphql/src/modules/case/case.graphql index 9c8011284d6d..9b6825ea9c40 100644 --- a/opencti-platform/opencti-graphql/src/modules/case/case.graphql +++ b/opencti-platform/opencti-graphql/src/modules/case/case.graphql @@ -2,7 +2,7 @@ interface Case implements BasicObject & StixObject & StixCoreObject & StixDomain id: ID! # internal_id standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics:[Metric] # StixObject representative: Representative! diff --git a/opencti-platform/opencti-graphql/src/modules/case/feedback/feedback.graphql b/opencti-platform/opencti-graphql/src/modules/case/feedback/feedback.graphql index 59bcc69c8d38..c37c3455544f 100644 --- a/opencti-platform/opencti-graphql/src/modules/case/feedback/feedback.graphql +++ b/opencti-platform/opencti-graphql/src/modules/case/feedback/feedback.graphql @@ -2,7 +2,7 @@ type Feedback implements BasicObject & StixObject & StixCoreObject & StixDomainO id: ID! # internal_id standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics:[Metric] # StixObject representative: Representative! diff --git a/opencti-platform/opencti-graphql/src/modules/channel/channel.graphql b/opencti-platform/opencti-graphql/src/modules/channel/channel.graphql index 8c95842f5e68..fcbc9760efa2 100644 --- a/opencti-platform/opencti-graphql/src/modules/channel/channel.graphql +++ b/opencti-platform/opencti-graphql/src/modules/channel/channel.graphql @@ -2,7 +2,7 @@ type Channel implements BasicObject & StixObject & StixCoreObject & StixDomainOb id: ID! # internal_id standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics:[Metric] # StixObject representative: Representative! diff --git a/opencti-platform/opencti-graphql/src/modules/dataComponent/dataComponent.graphql b/opencti-platform/opencti-graphql/src/modules/dataComponent/dataComponent.graphql index f050b63314d0..ad5232c89bea 100644 --- a/opencti-platform/opencti-graphql/src/modules/dataComponent/dataComponent.graphql +++ b/opencti-platform/opencti-graphql/src/modules/dataComponent/dataComponent.graphql @@ -3,7 +3,7 @@ type DataComponent implements BasicObject & StixObject & StixCoreObject & StixDo id: ID! # internal_id standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics:[Metric] # Stix Object representative: Representative! diff --git a/opencti-platform/opencti-graphql/src/modules/dataSource/dataSource.graphql b/opencti-platform/opencti-graphql/src/modules/dataSource/dataSource.graphql index c4718335e05c..dd49cca8f7bd 100644 --- a/opencti-platform/opencti-graphql/src/modules/dataSource/dataSource.graphql +++ b/opencti-platform/opencti-graphql/src/modules/dataSource/dataSource.graphql @@ -3,7 +3,7 @@ type DataSource implements BasicObject & StixObject & StixCoreObject & StixDomai id: ID! # internal_id standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics:[Metric] # Stix Object representative: Representative! diff --git a/opencti-platform/opencti-graphql/src/modules/decayRule/decayRule.graphql b/opencti-platform/opencti-graphql/src/modules/decayRule/decayRule.graphql index cb9e1121557b..92260397d8a9 100644 --- a/opencti-platform/opencti-graphql/src/modules/decayRule/decayRule.graphql +++ b/opencti-platform/opencti-graphql/src/modules/decayRule/decayRule.graphql @@ -2,7 +2,7 @@ type DecayRule implements InternalObject & BasicObject { id: ID! standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics:[Metric] created_at: DateTime! updated_at: DateTime! diff --git a/opencti-platform/opencti-graphql/src/modules/decayRule/exclusions/decayExclusionRule.graphql b/opencti-platform/opencti-graphql/src/modules/decayRule/exclusions/decayExclusionRule.graphql index 4b4c90047565..255789941672 100644 --- a/opencti-platform/opencti-graphql/src/modules/decayRule/exclusions/decayExclusionRule.graphql +++ b/opencti-platform/opencti-graphql/src/modules/decayRule/exclusions/decayExclusionRule.graphql @@ -2,7 +2,7 @@ type DecayExclusionRule implements InternalObject & BasicObject { id: ID! standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics:[Metric] created_at: DateTime! # DecayExclusionRule diff --git a/opencti-platform/opencti-graphql/src/modules/disseminationList/disseminationList.graphql b/opencti-platform/opencti-graphql/src/modules/disseminationList/disseminationList.graphql index 1ba7a9de7835..3de9407263a2 100644 --- a/opencti-platform/opencti-graphql/src/modules/disseminationList/disseminationList.graphql +++ b/opencti-platform/opencti-graphql/src/modules/disseminationList/disseminationList.graphql @@ -2,7 +2,7 @@ type DisseminationList implements InternalObject & BasicObject { id: ID! standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics:[Metric] created_at: DateTime! updated_at: DateTime! diff --git a/opencti-platform/opencti-graphql/src/modules/emailTemplate/emailTemplate.graphql b/opencti-platform/opencti-graphql/src/modules/emailTemplate/emailTemplate.graphql index e9a0efa0c749..5be5218bd066 100644 --- a/opencti-platform/opencti-graphql/src/modules/emailTemplate/emailTemplate.graphql +++ b/opencti-platform/opencti-graphql/src/modules/emailTemplate/emailTemplate.graphql @@ -2,7 +2,7 @@ type EmailTemplate implements InternalObject & BasicObject { id: ID! standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics:[Metric] # EmailTemplate name: String! diff --git a/opencti-platform/opencti-graphql/src/modules/event/event.graphql b/opencti-platform/opencti-graphql/src/modules/event/event.graphql index 194bbf99897f..e4534a820724 100644 --- a/opencti-platform/opencti-graphql/src/modules/event/event.graphql +++ b/opencti-platform/opencti-graphql/src/modules/event/event.graphql @@ -2,7 +2,7 @@ type Event implements BasicObject & StixCoreObject & StixDomainObject & StixObje id: ID! # internal_id standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics:[Metric] # StixObject representative: Representative! diff --git a/opencti-platform/opencti-graphql/src/modules/exclusionList/exclusionList.graphql b/opencti-platform/opencti-graphql/src/modules/exclusionList/exclusionList.graphql index 13f413499dcf..35d4b7f0402e 100644 --- a/opencti-platform/opencti-graphql/src/modules/exclusionList/exclusionList.graphql +++ b/opencti-platform/opencti-graphql/src/modules/exclusionList/exclusionList.graphql @@ -4,7 +4,7 @@ type ExclusionList implements InternalObject & BasicObject { description: String standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics:[Metric] created_at: DateTime! enabled: Boolean! diff --git a/opencti-platform/opencti-graphql/src/modules/fintelDesign/fintelDesign.graphql b/opencti-platform/opencti-graphql/src/modules/fintelDesign/fintelDesign.graphql index b0548b143434..0245e9fc098e 100644 --- a/opencti-platform/opencti-graphql/src/modules/fintelDesign/fintelDesign.graphql +++ b/opencti-platform/opencti-graphql/src/modules/fintelDesign/fintelDesign.graphql @@ -2,7 +2,7 @@ type FintelDesign implements BasicObject & InternalObject { id: ID! standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics:[Metric] # Fintel Design name: String! diff --git a/opencti-platform/opencti-graphql/src/modules/fintelTemplate/fintelTemplate.graphql b/opencti-platform/opencti-graphql/src/modules/fintelTemplate/fintelTemplate.graphql index 83db07e6c2ab..1d5b1f1d41b5 100644 --- a/opencti-platform/opencti-graphql/src/modules/fintelTemplate/fintelTemplate.graphql +++ b/opencti-platform/opencti-graphql/src/modules/fintelTemplate/fintelTemplate.graphql @@ -7,7 +7,7 @@ type FintelTemplate implements BasicObject & InternalObject { id: ID! standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics:[Metric] ## Fintel name: String! diff --git a/opencti-platform/opencti-graphql/src/modules/grouping/grouping.graphql b/opencti-platform/opencti-graphql/src/modules/grouping/grouping.graphql index 914b7bf6eb6c..b86c07b56dd6 100644 --- a/opencti-platform/opencti-graphql/src/modules/grouping/grouping.graphql +++ b/opencti-platform/opencti-graphql/src/modules/grouping/grouping.graphql @@ -3,7 +3,7 @@ type Grouping implements BasicObject & StixObject & StixCoreObject & StixDomainO id: ID! # internal_id standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics:[Metric] # StixObject representative: Representative! diff --git a/opencti-platform/opencti-graphql/src/modules/indicator/indicator.graphql b/opencti-platform/opencti-graphql/src/modules/indicator/indicator.graphql index 3149198dee55..2b8af60b045c 100644 --- a/opencti-platform/opencti-graphql/src/modules/indicator/indicator.graphql +++ b/opencti-platform/opencti-graphql/src/modules/indicator/indicator.graphql @@ -69,7 +69,7 @@ type Indicator implements BasicObject & StixObject & StixCoreObject & StixDomain id: ID! # internal_id standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics:[Metric] # StixObject representative: Representative! diff --git a/opencti-platform/opencti-graphql/src/modules/ingestion/ingestion-csv.graphql b/opencti-platform/opencti-graphql/src/modules/ingestion/ingestion-csv.graphql index bd51c3de9a31..9cc5bab7ed48 100644 --- a/opencti-platform/opencti-graphql/src/modules/ingestion/ingestion-csv.graphql +++ b/opencti-platform/opencti-graphql/src/modules/ingestion/ingestion-csv.graphql @@ -3,7 +3,7 @@ type IngestionCsv implements InternalObject & BasicObject { id: ID! entity_type: String! standard_id: String! - parent_types: [String]! + parent_types: [String!]! metrics:[Metric] created_at: DateTime updated_at: DateTime diff --git a/opencti-platform/opencti-graphql/src/modules/ingestion/ingestion-json.graphql b/opencti-platform/opencti-graphql/src/modules/ingestion/ingestion-json.graphql index c2e15e2f08e7..56620da28940 100644 --- a/opencti-platform/opencti-graphql/src/modules/ingestion/ingestion-json.graphql +++ b/opencti-platform/opencti-graphql/src/modules/ingestion/ingestion-json.graphql @@ -18,7 +18,7 @@ type IngestionJson implements InternalObject & BasicObject { entity_type: String! connector_id: String! standard_id: String! - parent_types: [String]! + parent_types: [String!]! metrics:[Metric] created_at: DateTime updated_at: DateTime diff --git a/opencti-platform/opencti-graphql/src/modules/internal/csvMapper/csvMapper.graphql b/opencti-platform/opencti-graphql/src/modules/internal/csvMapper/csvMapper.graphql index a297d4dea838..0bda9ce45d47 100644 --- a/opencti-platform/opencti-graphql/src/modules/internal/csvMapper/csvMapper.graphql +++ b/opencti-platform/opencti-graphql/src/modules/internal/csvMapper/csvMapper.graphql @@ -65,7 +65,7 @@ type CsvMapper implements InternalObject & BasicObject { id: ID! entity_type: String! @auth standard_id: String! @auth - parent_types: [String!]! + parent_types: [String!]! @auth metrics:[Metric] @auth # CsvMapper name: String! @auth diff --git a/opencti-platform/opencti-graphql/src/modules/language/language.graphql b/opencti-platform/opencti-graphql/src/modules/language/language.graphql index 4cbac3dc18bc..89f993f133e2 100644 --- a/opencti-platform/opencti-graphql/src/modules/language/language.graphql +++ b/opencti-platform/opencti-graphql/src/modules/language/language.graphql @@ -2,7 +2,7 @@ type Language implements BasicObject & StixCoreObject & StixDomainObject & StixO id: ID! # internal_id standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics:[Metric] # StixObject representative: Representative! diff --git a/opencti-platform/opencti-graphql/src/modules/malwareAnalysis/malwareAnalysis.graphql b/opencti-platform/opencti-graphql/src/modules/malwareAnalysis/malwareAnalysis.graphql index dd1d333f221a..0a8f963d4a7c 100644 --- a/opencti-platform/opencti-graphql/src/modules/malwareAnalysis/malwareAnalysis.graphql +++ b/opencti-platform/opencti-graphql/src/modules/malwareAnalysis/malwareAnalysis.graphql @@ -3,7 +3,7 @@ type MalwareAnalysis implements BasicObject & StixCoreObject & StixDomainObject id: ID! # internal_id standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics:[Metric] # StixObject representative: Representative! diff --git a/opencti-platform/opencti-graphql/src/modules/narrative/narrative.graphql b/opencti-platform/opencti-graphql/src/modules/narrative/narrative.graphql index d19c22aa4e5d..ed4e6821bfc1 100644 --- a/opencti-platform/opencti-graphql/src/modules/narrative/narrative.graphql +++ b/opencti-platform/opencti-graphql/src/modules/narrative/narrative.graphql @@ -2,7 +2,7 @@ type Narrative implements BasicObject & StixCoreObject & StixDomainObject & Stix id: ID! # internal_id standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics:[Metric] # StixObject representative: Representative! diff --git a/opencti-platform/opencti-graphql/src/modules/notification/notification.graphql b/opencti-platform/opencti-graphql/src/modules/notification/notification.graphql index a98366477919..dba374d79e29 100644 --- a/opencti-platform/opencti-graphql/src/modules/notification/notification.graphql +++ b/opencti-platform/opencti-graphql/src/modules/notification/notification.graphql @@ -53,7 +53,7 @@ type Trigger implements InternalObject & BasicObject { id: ID! # internal_id standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics:[Metric] created: DateTime modified: DateTime @@ -136,7 +136,7 @@ type Notification implements InternalObject & BasicObject { created: DateTime name: String! notification_type: String! - parent_types: [String]! + parent_types: [String!]! metrics:[Metric] notification_content: [NotificationContent!]! is_read: Boolean! diff --git a/opencti-platform/opencti-graphql/src/modules/notifier/notifier-statics.ts b/opencti-platform/opencti-graphql/src/modules/notifier/notifier-statics.ts index e3faf5affbcd..9b6d6e5bc1a1 100644 --- a/opencti-platform/opencti-graphql/src/modules/notifier/notifier-statics.ts +++ b/opencti-platform/opencti-graphql/src/modules/notifier/notifier-statics.ts @@ -1,9 +1,10 @@ import type { JSONSchemaType } from 'ajv'; import type { NotifierConnector } from '../../generated/graphql'; -import type { BasicStoreEntityNotifier } from './notifier-types'; +import { type BasicStoreEntityNotifier, ENTITY_TYPE_NOTIFIER } from './notifier-types'; import { HEADER_TEMPLATE } from '../../utils/emailTemplates/header'; import { FOOTER_TEMPLATE } from '../../utils/emailTemplates/footer'; import { LOGO_TEMPLATE } from '../../utils/emailTemplates/logo'; +import { ABSTRACT_BASIC_OBJECT, ABSTRACT_INTERNAL_OBJECT } from '../../schema/general'; // region Notifier User interface export const NOTIFIER_CONNECTOR_UI = 'f39b8ab2c-8f5c-4167-a249-229f34d9442b'; @@ -159,16 +160,23 @@ export const STATIC_NOTIFIERS: Array = [ // @ts-ignore { id: STATIC_NOTIFIER_UI, + standard_id: `notifier--${STATIC_NOTIFIER_UI}`, + entity_type: ENTITY_TYPE_NOTIFIER, + parent_types: [ABSTRACT_BASIC_OBJECT, ABSTRACT_INTERNAL_OBJECT], internal_id: STATIC_NOTIFIER_UI, built_in: true, name: 'User interface', description: 'Publish notification to the user interface', notifier_connector_id: NOTIFIER_CONNECTOR_UI, + notifier_configuration: '', }, // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore { id: STATIC_NOTIFIER_EMAIL, + standard_id: `notifier--${STATIC_NOTIFIER_EMAIL}`, + entity_type: ENTITY_TYPE_NOTIFIER, + parent_types: [ABSTRACT_BASIC_OBJECT, ABSTRACT_INTERNAL_OBJECT], internal_id: STATIC_NOTIFIER_EMAIL, built_in: true, name: 'Default mailer', diff --git a/opencti-platform/opencti-graphql/src/modules/notifier/notifier.graphql b/opencti-platform/opencti-graphql/src/modules/notifier/notifier.graphql index e2d0489a02af..cbc547cb9e24 100644 --- a/opencti-platform/opencti-graphql/src/modules/notifier/notifier.graphql +++ b/opencti-platform/opencti-graphql/src/modules/notifier/notifier.graphql @@ -23,7 +23,7 @@ type Notifier implements InternalObject & BasicObject { id: ID! # internal_id standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics:[Metric] created: DateTime modified: DateTime diff --git a/opencti-platform/opencti-graphql/src/modules/organization/organization.graphql b/opencti-platform/opencti-graphql/src/modules/organization/organization.graphql index 2bfd6f01cc2b..9cb187709c18 100644 --- a/opencti-platform/opencti-graphql/src/modules/organization/organization.graphql +++ b/opencti-platform/opencti-graphql/src/modules/organization/organization.graphql @@ -2,7 +2,7 @@ type Organization implements BasicObject & StixObject & StixCoreObject & StixDom id: ID! # internal_id standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics:[Metric] # StixObject representative: Representative! diff --git a/opencti-platform/opencti-graphql/src/modules/pir/pir.graphql b/opencti-platform/opencti-graphql/src/modules/pir/pir.graphql index 0b4c24e8c9ce..3e803f390c48 100644 --- a/opencti-platform/opencti-graphql/src/modules/pir/pir.graphql +++ b/opencti-platform/opencti-graphql/src/modules/pir/pir.graphql @@ -65,7 +65,7 @@ type PirRelationship implements BasicRelationship { id: ID! # internal_id standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics:[Metric] fromRole: String toRole: String diff --git a/opencti-platform/opencti-graphql/src/modules/savedFilter/savedFilter.graphql b/opencti-platform/opencti-graphql/src/modules/savedFilter/savedFilter.graphql index 584c493c8ae0..1e2d8f986d64 100644 --- a/opencti-platform/opencti-graphql/src/modules/savedFilter/savedFilter.graphql +++ b/opencti-platform/opencti-graphql/src/modules/savedFilter/savedFilter.graphql @@ -2,7 +2,7 @@ type SavedFilter implements InternalObject & BasicObject { id: ID! standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics:[Metric] # SavedFilters name: String! diff --git a/opencti-platform/opencti-graphql/src/modules/securityCoverage/securityCoverage.graphql b/opencti-platform/opencti-graphql/src/modules/securityCoverage/securityCoverage.graphql index d49c25a474db..ff82a05b4aa6 100644 --- a/opencti-platform/opencti-graphql/src/modules/securityCoverage/securityCoverage.graphql +++ b/opencti-platform/opencti-graphql/src/modules/securityCoverage/securityCoverage.graphql @@ -7,7 +7,7 @@ type SecurityCoverage implements BasicObject & StixObject & StixCoreObject & Sti id: ID! # internal_id standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics:[Metric] # StixObject representative: Representative! diff --git a/opencti-platform/opencti-graphql/src/modules/securityPlatform/securityPlatform.graphql b/opencti-platform/opencti-graphql/src/modules/securityPlatform/securityPlatform.graphql index eab71c4c4e60..cff47fd40188 100644 --- a/opencti-platform/opencti-graphql/src/modules/securityPlatform/securityPlatform.graphql +++ b/opencti-platform/opencti-graphql/src/modules/securityPlatform/securityPlatform.graphql @@ -2,7 +2,7 @@ type SecurityPlatform implements BasicObject & StixObject & StixCoreObject & Sti id: ID! # internal_id standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics:[Metric] # StixObject representative: Representative! diff --git a/opencti-platform/opencti-graphql/src/modules/support/support.graphql b/opencti-platform/opencti-graphql/src/modules/support/support.graphql index f9f6dea6cad3..95d949d0972b 100644 --- a/opencti-platform/opencti-graphql/src/modules/support/support.graphql +++ b/opencti-platform/opencti-graphql/src/modules/support/support.graphql @@ -3,7 +3,7 @@ type SupportPackage implements InternalObject & BasicObject { name: String! standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics:[Metric] created_at: DateTime! package_status: PackageStatus! diff --git a/opencti-platform/opencti-graphql/src/modules/task/task-template/task-template.graphql b/opencti-platform/opencti-graphql/src/modules/task/task-template/task-template.graphql index c19bfc72bf1d..50511aacdf63 100644 --- a/opencti-platform/opencti-graphql/src/modules/task/task-template/task-template.graphql +++ b/opencti-platform/opencti-graphql/src/modules/task/task-template/task-template.graphql @@ -2,7 +2,7 @@ type TaskTemplate implements InternalObject & BasicObject { id: ID! # internal_id standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics:[Metric] created: DateTime modified: DateTime diff --git a/opencti-platform/opencti-graphql/src/modules/task/task.graphql b/opencti-platform/opencti-graphql/src/modules/task/task.graphql index c3f95ed6e284..d33bf16097e0 100644 --- a/opencti-platform/opencti-graphql/src/modules/task/task.graphql +++ b/opencti-platform/opencti-graphql/src/modules/task/task.graphql @@ -2,7 +2,7 @@ type Task implements Container & StixDomainObject & StixCoreObject & StixObject id: ID! # internal_id standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics:[Metric] # StixObject representative: Representative! diff --git a/opencti-platform/opencti-graphql/src/modules/threatActorIndividual/threatActorIndividual.graphql b/opencti-platform/opencti-graphql/src/modules/threatActorIndividual/threatActorIndividual.graphql index 1082606cb64e..ea413e86430f 100644 --- a/opencti-platform/opencti-graphql/src/modules/threatActorIndividual/threatActorIndividual.graphql +++ b/opencti-platform/opencti-graphql/src/modules/threatActorIndividual/threatActorIndividual.graphql @@ -12,7 +12,7 @@ type ThreatActorIndividual implements BasicObject & StixObject & StixCoreObject id: ID! # internal_id standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics:[Metric] # StixObject representative: Representative! diff --git a/opencti-platform/opencti-graphql/src/modules/vocabulary/vocabulary.graphql b/opencti-platform/opencti-graphql/src/modules/vocabulary/vocabulary.graphql index 5ef7d977f138..aef8230e24b9 100644 --- a/opencti-platform/opencti-graphql/src/modules/vocabulary/vocabulary.graphql +++ b/opencti-platform/opencti-graphql/src/modules/vocabulary/vocabulary.graphql @@ -66,7 +66,7 @@ type Vocabulary implements BasicObject & StixObject & StixMetaObject { id: ID! # internal_id standard_id: String! entity_type: String! - parent_types: [String]! + parent_types: [String!]! metrics:[Metric] # StixObject representative: Representative! diff --git a/opencti-platform/opencti-graphql/src/resolvers/basicObject.ts b/opencti-platform/opencti-graphql/src/resolvers/basicObject.ts index 8d61ff0b510f..913969ef17ef 100644 --- a/opencti-platform/opencti-graphql/src/resolvers/basicObject.ts +++ b/opencti-platform/opencti-graphql/src/resolvers/basicObject.ts @@ -15,6 +15,9 @@ const basicObjectResolvers: Resolvers = { /* v8 ignore next */ return 'Unknown'; }, + parent_types(obj) { + return obj.parent_types.filter((t) => t); + }, metrics(obj) { return obj.metrics ? obj.metrics : []; }, diff --git a/opencti-platform/opencti-graphql/src/resolvers/stix.js b/opencti-platform/opencti-graphql/src/resolvers/stix.js index 6b2c19f21ac7..ebccf07ed98d 100644 --- a/opencti-platform/opencti-graphql/src/resolvers/stix.js +++ b/opencti-platform/opencti-graphql/src/resolvers/stix.js @@ -7,6 +7,7 @@ import { INPUT_GRANTED_REFS } from '../schema/general'; import { filterMembersWithUsersOrgs, isUserHasCapability, KNOWLEDGE_ORGANIZATION_RESTRICT, REDACTED_USER } from '../utils/access'; import { ENABLED_DEMO_MODE } from '../config/conf'; import { ENTITY_TYPE_USER } from '../schema/internalObject'; +import { FunctionalError } from '../config/errors'; const internalLoadThroughDenormalized = (context, user, element, inputName) => { if (inputName === INPUT_GRANTED_REFS) { @@ -24,6 +25,10 @@ const internalLoadThroughDenormalized = (context, user, element, inputName) => { } // If not, reload through denormalized relationships const ref = schemaRelationsRefDefinition.getRelationRef(element.entity_type, inputName); + // Some refs defined on API are not part of all entities in schema due to an API/schema misalignment + if (!ref) { + throw FunctionalError('No ref of this type is defined for this entity type in DB schema', { entity_type: element.entity_type, inputName }); + } return context.batch.relsBatchLoader.load({ element, definition: ref }); }; diff --git a/opencti-platform/opencti-graphql/src/resolvers/stixMetaObject.js b/opencti-platform/opencti-graphql/src/resolvers/stixMetaObject.js index 2b2747539b60..100da7d67f69 100644 --- a/opencti-platform/opencti-graphql/src/resolvers/stixMetaObject.js +++ b/opencti-platform/opencti-graphql/src/resolvers/stixMetaObject.js @@ -1,4 +1,5 @@ import { findStixMetaObjectPaginated, findById } from '../domain/stixMetaObject'; +import { getSpecVersionOrDefault } from '../domain/stixRelationship'; const stixMetaObjectResolvers = { Query: { @@ -6,7 +7,7 @@ const stixMetaObjectResolvers = { stixMetaObjects: (_, args, context) => findStixMetaObjectPaginated(context, context.user, args), }, StixMetaObject: { - // eslint-disable-next-line + __resolveType(obj) { if (obj.entity_type) { return obj.entity_type.replace(/(?:^|-|_)(\w)/g, (matches, letter) => letter.toUpperCase()); @@ -14,6 +15,8 @@ const stixMetaObjectResolvers = { /* v8 ignore next */ return 'Unknown'; }, + // Retro compatibility + spec_version: getSpecVersionOrDefault, }, }; diff --git a/opencti-platform/opencti-graphql/tests/03-integration/02-resolvers/threatActorGroup-test.js b/opencti-platform/opencti-graphql/tests/03-integration/02-resolvers/threatActorGroup-test.js index 5c5916e1eeab..a2a5d6192347 100644 --- a/opencti-platform/opencti-graphql/tests/03-integration/02-resolvers/threatActorGroup-test.js +++ b/opencti-platform/opencti-graphql/tests/03-integration/02-resolvers/threatActorGroup-test.js @@ -24,6 +24,12 @@ const LIST_QUERY = gql` id name description + objectAssignee { + id + } + objectMarking { + spec_version + } } } } @@ -36,6 +42,12 @@ const READ_QUERY = gql` id name description + objectAssignee { + id + } + objectMarking { + spec_version + } toStix } } @@ -51,6 +63,12 @@ describe('Threat actor group resolver standard behavior', () => { id name description + objectAssignee { + id + } + objectMarking { + spec_version + } } } `; @@ -76,6 +94,7 @@ describe('Threat actor group resolver standard behavior', () => { expect(queryResult).not.toBeNull(); expect(queryResult.data.threatActorGroup).not.toBeNull(); expect(queryResult.data.threatActorGroup.id).toEqual(threatActorGroupInternalId); + expect(queryResult.data.threatActorGroup.id).toEqual(threatActorGroupInternalId); expect(queryResult.data.threatActorGroup.toStix.length).toBeGreaterThan(5); }); it('should threat actor group loaded by stix id', async () => { From b9878e92afd3923e86caed4b85ee1813a17ba434 Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Fri, 9 Jan 2026 09:40:48 +0100 Subject: [PATCH 059/126] [docs] Add copilot instructions for repository onboarding (#13952) Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: SamuelHassine <1334279+SamuelHassine@users.noreply.github.com> --- .github/copilot-instructions.md | 189 +++++++++++++++++++ opencti-platform/opencti-front/.yarnrc.yml | 14 ++ opencti-platform/opencti-graphql/.yarnrc.yml | 14 ++ 3 files changed, 217 insertions(+) create mode 100644 .github/copilot-instructions.md create mode 100644 opencti-platform/opencti-front/.yarnrc.yml create mode 100644 opencti-platform/opencti-graphql/.yarnrc.yml diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md new file mode 100644 index 000000000000..fd0d303062d1 --- /dev/null +++ b/.github/copilot-instructions.md @@ -0,0 +1,189 @@ +# OpenCTI Copilot Instructions + +## Repository Overview + +OpenCTI is a cyber threat intelligence platform built with a **monorepo structure** containing: +- **opencti-platform/opencti-graphql**: Node.js/TypeScript GraphQL API backend (~60MB) +- **opencti-platform/opencti-front**: React/TypeScript frontend with Relay (~50MB) +- **client-python**: Python library (pycti) for API access (~17MB) +- **opencti-worker**: Python worker for background tasks +- **docs**: MkDocs documentation (~56MB) + +**Tech Stack**: Node.js ≥20, Python 3.9-3.12, Yarn 4.12.0, TypeScript, React 19, GraphQL, ElasticSearch/OpenSearch, Redis, RabbitMQ, MinIO + +## Critical Build Requirements + +### Enable Corepack First +**CRITICAL**: Before any yarn commands, enable corepack to use Yarn 4.12.0: +```bash +corepack enable # One-time setup, downloads correct Yarn version +``` + +### ALWAYS Copy .yarnrc.yml First +**Before any `yarn` command**, copy `.yarnrc.yml` from `opencti-platform/` to the working directory: +```bash +cd opencti-platform/opencti-graphql # or opencti-front +cp ../.yarnrc.yml .yarnrc.yml +yarn install +``` +**Why**: The `.yarnrc.yml` enforces security policies (disables scripts, 4320-min package age gate) and pinned versions. Commands will fail or behave unexpectedly without it. + +### Backend (opencti-graphql) + +**Install & Build**: +```bash +cd opencti-platform/opencti-graphql +cp ../.yarnrc.yml .yarnrc.yml +yarn install # ~5 min first time +yarn install:python # Python deps: pip3 install -r src/python/requirements.txt +yarn build:prod # Production build (~3 min) +# OR: yarn build:dev # Dev build with schema (~2 min) +``` + +**Linting & Type Checking**: +```bash +yarn check-ts # TypeScript (~30s) +yarn lint # ESLint (~45s) +``` + +**Testing** (requires Docker): +```bash +yarn test:ci-unit # Unit tests (~2 min) +yarn test:ci-integration-sync # Integration tests (~10-15 min) +yarn test:ci-rules-and-others # Rules/TAXII tests (~8 min) +``` + +**Common Issues**: +- **Missing Python deps**: Run `yarn install:python` after `yarn install` +- **Build timeout**: Use `NODE_OPTIONS=--max_old_space_size=8192` +- **Schema errors**: Run `yarn build:schema` +- **"Cannot find module opencti-manifest.json"**: Run `yarn get-connectors-manifest` + +### Frontend (opencti-front) + +**Install & Build**: +```bash +cd opencti-platform/opencti-front +cp ../.yarnrc.yml .yarnrc.yml +yarn install # ~4 min +yarn relay # Generate Relay artifacts (~1 min) +yarn build # Production build (~5 min) +``` + +**Linting & Type Checking**: +```bash +yarn check-ts # TypeScript (~40s) +yarn lint # ESLint (~30s) +``` + +**Testing**: +```bash +yarn test # Unit tests with Vitest (~2 min) +yarn test:coverage # With coverage (~3 min) +yarn test:e2e # E2E Playwright tests (~10 min, needs full stack) +node script/verify-translation.js # Translation validation (runs in CI) +``` + +### Client Python (pycti) + +**Setup & Test**: +```bash +cd client-python +pip3 install -r requirements.txt +pip3 install -r test-requirements.txt +pip3 install -e .[dev,doc] # Editable install + +# Linting: flake8 . (ignore E,W), black ., isort . +# Testing: python3 -m pytest --cov=pycti --no-header -vv (requires OpenCTI instance) +``` + +### Worker + +**Setup**: +```bash +cd opencti-worker +pip3 install -r src/requirements.txt +# Requires: OPENCTI_URL and OPENCTI_TOKEN environment variables +python3 src/worker.py # Start worker +``` + +## CI/CD Workflows + +**Main CI**: Docker build (~10 min), API tests (~20 min), Frontend tests (~5-15 min), Client Python matrix (3.9-3.12), License check. **All commits MUST be GPG signed**. + +## Local Development + +**Prerequisites**: Node.js ≥20, Python 3.9-3.12, Docker, `corepack enable`, `sudo sysctl -w vm.max_map_count=262144` + +**Start**: `cd opencti-platform/opencti-dev && docker compose up -d` (Elasticsearch, Redis, RabbitMQ, MinIO, Kibana) + +**Backend**: `cd opencti-platform/opencti-graphql`, copy `.yarnrc.yml`, edit `config/development.json`, run `yarn install && yarn install:python && yarn start` + +**Frontend**: `cd opencti-platform/opencti-front`, copy `.yarnrc.yml`, run `yarn install && yarn start` → http://localhost:3000 + +## Project Structure + +``` +opencti/ +├── opencti-platform/ +│ ├── .yarnrc.yml # MUST copy to subdirs +│ ├── opencti-graphql/ # Backend: src/, config/, tests/, vitest.config.*.ts, eslint.config.mjs +│ ├── opencti-front/ # Frontend: src/, tests_e2e/, lang/, relay.config.json +│ └── opencti-dev/docker-compose.yml +├── client-python/ # pycti/, tests/, pyproject.toml, .flake8, .isort.cfg +├── opencti-worker/src/ +└── scripts/ci/ # docker-compose.yml, ci-common.env +``` + +**Key Configs**: `eslint.config.mjs` (v9), `tsconfig.json` (strict), `vitest.config.*`, `.flake8`, `.isort.cfg`, `pyproject.toml` + +## Common Pitfalls & Solutions + +1. **Yarn command fails**: Missing `.yarnrc.yml` - ALWAYS copy it first +2. **"Module not found" errors**: Run `yarn install:python` for backend, check Relay artifacts for frontend +3. **Test failures in CI**: Local tests pass but CI fails → Check Docker service health, increase timeouts +4. **Build out of memory**: Add `NODE_OPTIONS=--max_old_space_size=8192` before yarn commands +5. **ElasticSearch won't start**: Run `sudo sysctl -w vm.max_map_count=262144` +6. **Unsigned commits rejected**: Configure GPG signing: https://docs.github.com/en/authentication/managing-commit-signature-verification/signing-commits + +## Code Review & Security + +**Pre-commit checks**: Lint (`yarn lint`, `black . && isort . && flake8 .`), types (`yarn check-ts`), tests (`yarn test:ci-unit`, `pytest`). **CodeQL** auto-runs on PRs. + +**Review Focus**: + +**Security Critical**: Check for hardcoded secrets/credentials, SQL injection/XSS vulnerabilities, input validation/sanitization, auth logic. + +**Performance**: Identify N+1 queries, inefficient loops, memory leaks, missing resource cleanup, caching opportunities. + +**Code Quality**: Functions should be focused and appropriately sized, use clear naming, ensure proper error handling. + +**Review Style**: Be specific and actionable, explain the "why", acknowledge good patterns, ask clarifying questions. + +Always prioritize security vulnerabilities and performance issues. Suggest changes for readability: +```js +// Instead of: +if (user.email && user.email.includes('@') && user.email.length > 5) { + submitButton.enabled = true; +} else { + submitButton.enabled = false; +} + +// Consider: +function isValidEmail(email) { + return email && email.includes('@') && email.length > 5; +} +submitButton.enabled = isValidEmail(user.email); +``` + +## Commit Message Format + +**Required**: `[component] Message (#issuenumber)` + +Components: `backend`, `frontend`, `client-python`, `worker`, `docs`, `tools`, `CI` + +Example: `[backend] Fix authentication error handling (#1234)` + +## Trust These Instructions + +These instructions are comprehensive and tested. Only search for additional information if you encounter a specific error not covered here or need details on a feature not mentioned. diff --git a/opencti-platform/opencti-front/.yarnrc.yml b/opencti-platform/opencti-front/.yarnrc.yml new file mode 100644 index 000000000000..69d90c3f07b9 --- /dev/null +++ b/opencti-platform/opencti-front/.yarnrc.yml @@ -0,0 +1,14 @@ +# use pinned version +defaultSemverRangePrefix: '' + +# ensure the best compatibility +nodeLinker: node-modules + +# security: scripts are not enabled by default and required individual activations in package.json +enableScripts: false + +# security: NPM package younger than 3 days won't be considered for installation +npmMinimalAgeGate: 4320 + +npmPreapprovedPackages: + - '@filigran/*' diff --git a/opencti-platform/opencti-graphql/.yarnrc.yml b/opencti-platform/opencti-graphql/.yarnrc.yml new file mode 100644 index 000000000000..69d90c3f07b9 --- /dev/null +++ b/opencti-platform/opencti-graphql/.yarnrc.yml @@ -0,0 +1,14 @@ +# use pinned version +defaultSemverRangePrefix: '' + +# ensure the best compatibility +nodeLinker: node-modules + +# security: scripts are not enabled by default and required individual activations in package.json +enableScripts: false + +# security: NPM package younger than 3 days won't be considered for installation +npmMinimalAgeGate: 4320 + +npmPreapprovedPackages: + - '@filigran/*' From 0022c07e14aab7c87ba2bec2d93af167ed9821e7 Mon Sep 17 00:00:00 2001 From: Samuel Hassine Date: Fri, 9 Jan 2026 10:29:37 +0100 Subject: [PATCH 060/126] [backend/frontend] Remove yarnrc --- opencti-platform/opencti-front/.yarnrc.yml | 14 -------------- opencti-platform/opencti-graphql/.yarnrc.yml | 14 -------------- 2 files changed, 28 deletions(-) delete mode 100644 opencti-platform/opencti-front/.yarnrc.yml delete mode 100644 opencti-platform/opencti-graphql/.yarnrc.yml diff --git a/opencti-platform/opencti-front/.yarnrc.yml b/opencti-platform/opencti-front/.yarnrc.yml deleted file mode 100644 index 69d90c3f07b9..000000000000 --- a/opencti-platform/opencti-front/.yarnrc.yml +++ /dev/null @@ -1,14 +0,0 @@ -# use pinned version -defaultSemverRangePrefix: '' - -# ensure the best compatibility -nodeLinker: node-modules - -# security: scripts are not enabled by default and required individual activations in package.json -enableScripts: false - -# security: NPM package younger than 3 days won't be considered for installation -npmMinimalAgeGate: 4320 - -npmPreapprovedPackages: - - '@filigran/*' diff --git a/opencti-platform/opencti-graphql/.yarnrc.yml b/opencti-platform/opencti-graphql/.yarnrc.yml deleted file mode 100644 index 69d90c3f07b9..000000000000 --- a/opencti-platform/opencti-graphql/.yarnrc.yml +++ /dev/null @@ -1,14 +0,0 @@ -# use pinned version -defaultSemverRangePrefix: '' - -# ensure the best compatibility -nodeLinker: node-modules - -# security: scripts are not enabled by default and required individual activations in package.json -enableScripts: false - -# security: NPM package younger than 3 days won't be considered for installation -npmMinimalAgeGate: 4320 - -npmPreapprovedPackages: - - '@filigran/*' From e6f98d2132ac3c7c115e41b09ef0ca6b8ecd91c2 Mon Sep 17 00:00:00 2001 From: Jeremy Cloarec <159018898+JeremyCloarec@users.noreply.github.com> Date: Fri, 9 Jan 2026 12:09:57 +0100 Subject: [PATCH 061/126] [backend] return null when id cannot be generated for input (#13610) --- opencti-platform/opencti-graphql/src/parser/csv-mapper.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/opencti-platform/opencti-graphql/src/parser/csv-mapper.ts b/opencti-platform/opencti-graphql/src/parser/csv-mapper.ts index f258dc84fc7e..4ec046a1007d 100644 --- a/opencti-platform/opencti-graphql/src/parser/csv-mapper.ts +++ b/opencti-platform/opencti-graphql/src/parser/csv-mapper.ts @@ -1,4 +1,3 @@ -/* eslint-disable no-param-reassign */ import { DateTime } from 'luxon'; import type { AttributeDefinition, AttrType, ObjectAttribute } from '../schema/attribute-definition'; import { entityType, relationshipType, standardId } from '../schema/attribute-definition'; @@ -338,8 +337,11 @@ const mapRecord = async ( if (!isValidInput(filledInput)) { return null; } - - handleId(representation, filledInput); + try { + handleId(representation, filledInput); + } catch { + return null; + } return filledInput; }; From 74e1bae55b11208f8463593ed1781b2f3fc6f20e Mon Sep 17 00:00:00 2001 From: Samuel Hassine Date: Fri, 9 Jan 2026 13:44:06 +0100 Subject: [PATCH 062/126] [docs] Fix Copilot instructions --- .github/copilot-instructions.md | 26 -------------------------- 1 file changed, 26 deletions(-) diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index fd0d303062d1..131da2b37018 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -150,32 +150,6 @@ opencti/ **Pre-commit checks**: Lint (`yarn lint`, `black . && isort . && flake8 .`), types (`yarn check-ts`), tests (`yarn test:ci-unit`, `pytest`). **CodeQL** auto-runs on PRs. -**Review Focus**: - -**Security Critical**: Check for hardcoded secrets/credentials, SQL injection/XSS vulnerabilities, input validation/sanitization, auth logic. - -**Performance**: Identify N+1 queries, inefficient loops, memory leaks, missing resource cleanup, caching opportunities. - -**Code Quality**: Functions should be focused and appropriately sized, use clear naming, ensure proper error handling. - -**Review Style**: Be specific and actionable, explain the "why", acknowledge good patterns, ask clarifying questions. - -Always prioritize security vulnerabilities and performance issues. Suggest changes for readability: -```js -// Instead of: -if (user.email && user.email.includes('@') && user.email.length > 5) { - submitButton.enabled = true; -} else { - submitButton.enabled = false; -} - -// Consider: -function isValidEmail(email) { - return email && email.includes('@') && email.length > 5; -} -submitButton.enabled = isValidEmail(user.email); -``` - ## Commit Message Format **Required**: `[component] Message (#issuenumber)` From 818c36e00a1f5e01395b714d806099c118a12b8b Mon Sep 17 00:00:00 2001 From: Samuel Hassine Date: Fri, 9 Jan 2026 14:58:24 +0100 Subject: [PATCH 063/126] [docs] Create code review instructions Added guidelines for code review focusing on security, performance, and code quality. --- .../instructions/code-review.instructions.md | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 .github/instructions/code-review.instructions.md diff --git a/.github/instructions/code-review.instructions.md b/.github/instructions/code-review.instructions.md new file mode 100644 index 000000000000..e2f36f0c9a63 --- /dev/null +++ b/.github/instructions/code-review.instructions.md @@ -0,0 +1,46 @@ +--- +applyTo: "*" +--- + +When reviewing code, focus on: + +## Security Critical Issues +- Check for hardcoded secrets, API keys, or credentials +- Look for SQL injection and XSS vulnerabilities +- Verify proper input validation and sanitization +- Review authentication and authorization logic + +## Performance Red Flags +- Identify N+1 database query problems +- Spot inefficient loops and algorithmic issues +- Check for memory leaks and resource cleanup +- Review caching opportunities for expensive operations + +## Code Quality Essentials +- Functions should be focused and appropriately sized +- Use clear, descriptive naming conventions +- Ensure proper error handling throughout + +## Review Style +- Be specific and actionable in feedback +- Explain the "why" behind recommendations +- Acknowledge good patterns when you see them +- Ask clarifying questions when code intent is unclear + +Always prioritize security vulnerabilities and performance issues that could impact users. + +Always suggest changes to improve readability. For example, this suggestion seeks to make the code more readable and also makes the validation logic reusable and testable. + +// Instead of: +if (user.email && user.email.includes('@') && user.email.length > 5) { + submitButton.enabled = true; +} else { + submitButton.enabled = false; +} + +// Consider: +function isValidEmail(email) { + return email && email.includes('@') && email.length > 5; +} + +submitButton.enabled = isValidEmail(user.email); From 4d0736a78e18bb9aaa092a199d2fbfcc208988cd Mon Sep 17 00:00:00 2001 From: Samuel Hassine Date: Fri, 9 Jan 2026 15:00:32 +0100 Subject: [PATCH 064/126] [docs] Update applyTo pattern for code review instructions --- .github/instructions/code-review.instructions.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/instructions/code-review.instructions.md b/.github/instructions/code-review.instructions.md index e2f36f0c9a63..a4e496859d94 100644 --- a/.github/instructions/code-review.instructions.md +++ b/.github/instructions/code-review.instructions.md @@ -1,5 +1,5 @@ --- -applyTo: "*" +applyTo: "**/*" --- When reviewing code, focus on: From 438f1a8bb1246a0a72763ac7f8191491418e54bf Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 9 Jan 2026 22:09:55 +0100 Subject: [PATCH 065/126] [deps] Update dependency file-type to v21.3.0 (#13916) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- opencti-platform/opencti-graphql/package.json | 2 +- opencti-platform/opencti-graphql/yarn.lock | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/opencti-platform/opencti-graphql/package.json b/opencti-platform/opencti-graphql/package.json index 8af6e849eeb3..ea0e279174a6 100644 --- a/opencti-platform/opencti-graphql/package.json +++ b/opencti-platform/opencti-graphql/package.json @@ -110,7 +110,7 @@ "express-rate-limit": "8.2.1", "express-session": "1.18.2", "fast-json-patch": "3.1.1", - "file-type": "21.2.0", + "file-type": "21.3.0", "github-api": "3.4.0", "graphql": "16.12.0", "graphql-constraint-directive": "6.0.0", diff --git a/opencti-platform/opencti-graphql/yarn.lock b/opencti-platform/opencti-graphql/yarn.lock index 56730bd0cafd..3f2f6a494ef3 100644 --- a/opencti-platform/opencti-graphql/yarn.lock +++ b/opencti-platform/opencti-graphql/yarn.lock @@ -7878,15 +7878,15 @@ __metadata: languageName: node linkType: hard -"file-type@npm:21.2.0": - version: 21.2.0 - resolution: "file-type@npm:21.2.0" +"file-type@npm:21.3.0": + version: 21.3.0 + resolution: "file-type@npm:21.3.0" dependencies: "@tokenizer/inflate": "npm:^0.4.1" strtok3: "npm:^10.3.4" token-types: "npm:^6.1.1" uint8array-extras: "npm:^1.4.0" - checksum: 10c0/af72f992fb8a6d9ff3a43dfe9293cab4f6b4faec7dc7f950e10c30fda9d5e5799e5fc45ad749fe5b2c1076699716f47ebcf0d573a6ad8640da82e35f36a481cf + checksum: 10c0/1b1fa909e6063044a6da1d2ea348ee4d747ed9286382d3f0d4d6532c11fb2ea9f2e7e67b2bc7d745d1bc937e05dee1aa8cb912c64250933bcb393a3744f4e284 languageName: node linkType: hard @@ -10689,7 +10689,7 @@ __metadata: express-rate-limit: "npm:8.2.1" express-session: "npm:1.18.2" fast-json-patch: "npm:3.1.1" - file-type: "npm:21.2.0" + file-type: "npm:21.3.0" github-api: "npm:3.4.0" globals: "npm:17.0.0" graphql: "npm:16.12.0" From e46607ae629e44d5d2c509f99bc9d7b217521436 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 9 Jan 2026 22:16:16 +0100 Subject: [PATCH 066/126] [deps] Update dependency ws to v8.19.0 (#13947) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- opencti-platform/opencti-graphql/package.json | 2 +- opencti-platform/opencti-graphql/yarn.lock | 19 +++++++++++++++++-- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/opencti-platform/opencti-graphql/package.json b/opencti-platform/opencti-graphql/package.json index ea0e279174a6..985ed5527b0e 100644 --- a/opencti-platform/opencti-graphql/package.json +++ b/opencti-platform/opencti-graphql/package.json @@ -167,7 +167,7 @@ "validator": "13.15.26", "winston": "3.19.0", "winston-daily-rotate-file": "5.0.0", - "ws": "8.18.3", + "ws": "8.19.0", "xml2js": "0.6.2", "zod": "3.25.76" }, diff --git a/opencti-platform/opencti-graphql/yarn.lock b/opencti-platform/opencti-graphql/yarn.lock index 3f2f6a494ef3..1ed1bbfa58fa 100644 --- a/opencti-platform/opencti-graphql/yarn.lock +++ b/opencti-platform/opencti-graphql/yarn.lock @@ -10751,7 +10751,7 @@ __metadata: vitest: "npm:3.2.4" winston: "npm:3.19.0" winston-daily-rotate-file: "npm:5.0.0" - ws: "npm:8.18.3" + ws: "npm:8.19.0" xml2js: "npm:0.6.2" zod: "npm:3.25.76" dependenciesMeta: @@ -13659,7 +13659,22 @@ __metadata: languageName: node linkType: hard -"ws@npm:8.18.3, ws@npm:^8.17.1, ws@npm:^8.18.3": +"ws@npm:8.19.0": + version: 8.19.0 + resolution: "ws@npm:8.19.0" + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: ">=5.0.2" + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + checksum: 10c0/4741d9b9bc3f9c791880882414f96e36b8b254e34d4b503279d6400d9a4b87a033834856dbdd94ee4b637944df17ea8afc4bce0ff4a1560d2166be8855da5b04 + languageName: node + linkType: hard + +"ws@npm:^8.17.1, ws@npm:^8.18.3": version: 8.18.3 resolution: "ws@npm:8.18.3" peerDependencies: From a384a2f4c34782419ccf483384377d05c1c37e69 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 9 Jan 2026 22:17:03 +0100 Subject: [PATCH 067/126] [deps] Update dependency uuid to v13 (#12783) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- opencti-platform/opencti-front/package.json | 2 +- opencti-platform/opencti-front/yarn.lock | 11 +--------- opencti-platform/opencti-graphql/package.json | 2 +- opencti-platform/opencti-graphql/yarn.lock | 21 +++++++++++++------ 4 files changed, 18 insertions(+), 18 deletions(-) diff --git a/opencti-platform/opencti-front/package.json b/opencti-platform/opencti-front/package.json index 4220cb0aa696..3af21b881daa 100644 --- a/opencti-platform/opencti-front/package.json +++ b/opencti-platform/opencti-front/package.json @@ -124,7 +124,7 @@ "rxjs": "7.8.2", "three-spritetext": "1.10.0", "use-analytics": "1.1.0", - "uuid": "11.1.0", + "uuid": "13.0.0", "yup": "1.7.1" }, "devDependencies": { diff --git a/opencti-platform/opencti-front/yarn.lock b/opencti-platform/opencti-front/yarn.lock index 6aa7e2767599..5aadd8d331c5 100644 --- a/opencti-platform/opencti-front/yarn.lock +++ b/opencti-platform/opencti-front/yarn.lock @@ -12326,7 +12326,7 @@ __metadata: typescript: "npm:5.9.3" typescript-eslint: "npm:8.51.0" use-analytics: "npm:1.1.0" - uuid: "npm:11.1.0" + uuid: "npm:13.0.0" vite: "npm:7.3.0" vite-plugin-relay: "npm:2.1.0" vite-plugin-static-copy: "npm:3.1.4" @@ -15600,15 +15600,6 @@ __metadata: languageName: node linkType: hard -"uuid@npm:11.1.0": - version: 11.1.0 - resolution: "uuid@npm:11.1.0" - bin: - uuid: dist/esm/bin/uuid - checksum: 10c0/34aa51b9874ae398c2b799c88a127701408cd581ee89ec3baa53509dd8728cbb25826f2a038f9465f8b7be446f0fbf11558862965b18d21c993684297628d4d3 - languageName: node - linkType: hard - "uuid@npm:13.0.0": version: 13.0.0 resolution: "uuid@npm:13.0.0" diff --git a/opencti-platform/opencti-graphql/package.json b/opencti-platform/opencti-graphql/package.json index 985ed5527b0e..cff4e154e9b1 100644 --- a/opencti-platform/opencti-graphql/package.json +++ b/opencti-platform/opencti-graphql/package.json @@ -162,7 +162,7 @@ "tough-cookie": "6.0.0", "turndown": "7.2.2", "unzipper": "0.12.3", - "uuid": "11.1.0", + "uuid": "13.0.0", "uuid-time": "1.0.0", "validator": "13.15.26", "winston": "3.19.0", diff --git a/opencti-platform/opencti-graphql/yarn.lock b/opencti-platform/opencti-graphql/yarn.lock index 1ed1bbfa58fa..9b65136704a7 100644 --- a/opencti-platform/opencti-graphql/yarn.lock +++ b/opencti-platform/opencti-graphql/yarn.lock @@ -10745,7 +10745,7 @@ __metadata: typescript: "npm:5.9.3" typescript-eslint: "npm:8.51.0" unzipper: "npm:0.12.3" - uuid: "npm:11.1.0" + uuid: "npm:13.0.0" uuid-time: "npm:1.0.0" validator: "npm:13.15.26" vitest: "npm:3.2.4" @@ -13218,12 +13218,12 @@ __metadata: languageName: node linkType: hard -"uuid@npm:11.1.0, uuid@npm:^11.1.0": - version: 11.1.0 - resolution: "uuid@npm:11.1.0" +"uuid@npm:13.0.0": + version: 13.0.0 + resolution: "uuid@npm:13.0.0" bin: - uuid: dist/esm/bin/uuid - checksum: 10c0/34aa51b9874ae398c2b799c88a127701408cd581ee89ec3baa53509dd8728cbb25826f2a038f9465f8b7be446f0fbf11558862965b18d21c993684297628d4d3 + uuid: dist-node/bin/uuid + checksum: 10c0/950e4c18d57fef6c69675344f5700a08af21e26b9eff2bf2180427564297368c538ea11ac9fb2e6528b17fc3966a9fd2c5049361b0b63c7d654f3c550c9b3d67 languageName: node linkType: hard @@ -13236,6 +13236,15 @@ __metadata: languageName: node linkType: hard +"uuid@npm:^11.1.0": + version: 11.1.0 + resolution: "uuid@npm:11.1.0" + bin: + uuid: dist/esm/bin/uuid + checksum: 10c0/34aa51b9874ae398c2b799c88a127701408cd581ee89ec3baa53509dd8728cbb25826f2a038f9465f8b7be446f0fbf11558862965b18d21c993684297628d4d3 + languageName: node + linkType: hard + "validate-npm-package-license@npm:^3.0.4": version: 3.0.4 resolution: "validate-npm-package-license@npm:3.0.4" From 0412c9194a52a7e9bdddbbc7f4976401a85bb96a Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Sat, 10 Jan 2026 12:14:08 +0100 Subject: [PATCH 068/126] [backend] Fix AI Insights Containers Digest returning Markdown instead of HTML (#13818) Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: SamuelHassine <1334279+SamuelHassine@users.noreply.github.com> --- opencti-platform/opencti-graphql/src/domain/container.js | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/opencti-platform/opencti-graphql/src/domain/container.js b/opencti-platform/opencti-graphql/src/domain/container.js index 9e95e900e45a..f805ff9ca64f 100644 --- a/opencti-platform/opencti-graphql/src/domain/container.js +++ b/opencti-platform/opencti-graphql/src/domain/container.js @@ -374,8 +374,13 @@ export const aiSummary = async (context, user, args) => { # Reports ${JSON.stringify(content)} - # Instructions - - Your response should match the provided content format which is HTML, including appropriate HTML tags such as

    ,
      , and any necessary styling or structure. Ensure the content is well-formatted, semantic, and compatible with modern browsers. + # IMPORTANT: Output Format + - Your response MUST be in HTML format only. DO NOT use Markdown syntax. + - Use HTML tags like

      ,

      ,

      ,

        ,
      • , , for formatting. + - DO NOT use Markdown syntax such as ##, ###, **, *, - for lists, etc. + - Example: Use

        Key Findings

        NOT ## Key Findings + - Example: Use
        • Item
        NOT - Item + - Your response should include appropriate HTML tags such as

        ,
          , and any necessary styling or structure. Ensure the content is well-formatted, semantic, and compatible with modern browsers. - Do not include , and tags. `; const userPromptTopics = ` From d9dab7c6652f70cf8cd15bdab3a11325dc730e6c Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Sat, 10 Jan 2026 18:53:28 +0100 Subject: [PATCH 069/126] [backend] Add missing securityCoverage resolver for Campaign entities (#13970) Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: SamuelHassine <1334279+SamuelHassine@users.noreply.github.com> --- opencti-platform/opencti-graphql/.yarnrc.yml | 14 ++++++++++++++ .../opencti-graphql/src/resolvers/campaign.js | 4 ++++ 2 files changed, 18 insertions(+) create mode 100644 opencti-platform/opencti-graphql/.yarnrc.yml diff --git a/opencti-platform/opencti-graphql/.yarnrc.yml b/opencti-platform/opencti-graphql/.yarnrc.yml new file mode 100644 index 000000000000..69d90c3f07b9 --- /dev/null +++ b/opencti-platform/opencti-graphql/.yarnrc.yml @@ -0,0 +1,14 @@ +# use pinned version +defaultSemverRangePrefix: '' + +# ensure the best compatibility +nodeLinker: node-modules + +# security: scripts are not enabled by default and required individual activations in package.json +enableScripts: false + +# security: NPM package younger than 3 days won't be considered for installation +npmMinimalAgeGate: 4320 + +npmPreapprovedPackages: + - '@filigran/*' diff --git a/opencti-platform/opencti-graphql/src/resolvers/campaign.js b/opencti-platform/opencti-graphql/src/resolvers/campaign.js index b793826af3b5..dfd02f85a667 100644 --- a/opencti-platform/opencti-graphql/src/resolvers/campaign.js +++ b/opencti-platform/opencti-graphql/src/resolvers/campaign.js @@ -8,6 +8,7 @@ import { stixDomainObjectEditField, } from '../domain/stixDomainObject'; import { ENTITY_TYPE_CAMPAIGN } from '../schema/stixDomainObject'; +import { findSecurityCoverageByCoveredId } from '../modules/securityCoverage/securityCoverage-domain'; const campaignResolvers = { Query: { @@ -20,6 +21,9 @@ const campaignResolvers = { return campaignsTimeSeries(context, context.user, args); }, }, + Campaign: { + securityCoverage: (campaign, _, context) => findSecurityCoverageByCoveredId(context, context.user, campaign.id), + }, Mutation: { campaignEdit: (_, { id }, context) => ({ delete: () => stixDomainObjectDelete(context, context.user, id, ENTITY_TYPE_CAMPAIGN), From 2db41025f4dbfb1aa5d1afa737f983304a1c2a9a Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Sat, 10 Jan 2026 19:03:18 +0100 Subject: [PATCH 070/126] [frontend] Fix Form Intakes Toggle field ignoring defaultValue on initialization (#13888) Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: SamuelHassine <1334279+SamuelHassine@users.noreply.github.com> --- opencti-platform/opencti-front/.gitignore | 1 + opencti-platform/opencti-front/.yarnrc.yml | 14 ++++++++++++++ .../components/data/forms/view/FormView.tsx | 12 ++++++------ 3 files changed, 21 insertions(+), 6 deletions(-) create mode 100644 opencti-platform/opencti-front/.yarnrc.yml diff --git a/opencti-platform/opencti-front/.gitignore b/opencti-platform/opencti-front/.gitignore index 20a9b0f8d022..d07389742e87 100644 --- a/opencti-platform/opencti-front/.gitignore +++ b/opencti-platform/opencti-front/.gitignore @@ -27,3 +27,4 @@ __generated__ /playwright/.cache/ /.nyc_output/ /tests_e2e/.setup/ +.yarnrc.yml diff --git a/opencti-platform/opencti-front/.yarnrc.yml b/opencti-platform/opencti-front/.yarnrc.yml new file mode 100644 index 000000000000..69d90c3f07b9 --- /dev/null +++ b/opencti-platform/opencti-front/.yarnrc.yml @@ -0,0 +1,14 @@ +# use pinned version +defaultSemverRangePrefix: '' + +# ensure the best compatibility +nodeLinker: node-modules + +# security: scripts are not enabled by default and required individual activations in package.json +enableScripts: false + +# security: NPM package younger than 3 days won't be considered for installation +npmMinimalAgeGate: 4320 + +npmPreapprovedPackages: + - '@filigran/*' diff --git a/opencti-platform/opencti-front/src/private/components/data/forms/view/FormView.tsx b/opencti-platform/opencti-front/src/private/components/data/forms/view/FormView.tsx index 09c8e5390c7a..1e1125f096d3 100644 --- a/opencti-platform/opencti-front/src/private/components/data/forms/view/FormView.tsx +++ b/opencti-platform/opencti-front/src/private/components/data/forms/view/FormView.tsx @@ -195,7 +195,7 @@ const FormViewInner: FunctionComponent = ({ queryRef, embedd const fieldsObj: Record = {}; mainEntityFields.forEach((field) => { if (field.type === 'checkbox' || field.type === 'toggle') { - fieldsObj[field.name] = false; + fieldsObj[field.name] = field.defaultValue !== undefined ? field.defaultValue : false; } else if (field.type === 'multiselect' || field.type === 'objectMarking' || field.type === 'objectLabel' || field.type === 'externalReferences' || field.type === 'files') { fieldsObj[field.name] = field.defaultValue || []; } else if (field.type === 'datetime') { @@ -210,7 +210,7 @@ const FormViewInner: FunctionComponent = ({ queryRef, embedd const fieldGroup: Record = {}; mainEntityFields.forEach((field) => { if (field.type === 'checkbox' || field.type === 'toggle') { - fieldGroup[field.name] = false; + fieldGroup[field.name] = field.defaultValue !== undefined ? field.defaultValue : false; } else if (field.type === 'multiselect' || field.type === 'objectMarking' || field.type === 'objectLabel' || field.type === 'externalReferences' || field.type === 'files') { fieldGroup[field.name] = field.defaultValue || []; } else if (field.type === 'datetime') { @@ -224,7 +224,7 @@ const FormViewInner: FunctionComponent = ({ queryRef, embedd // Single entity mode mainEntityFields.forEach((field) => { if (field.type === 'checkbox' || field.type === 'toggle') { - initialValues[field.name] = false; + initialValues[field.name] = field.defaultValue !== undefined ? field.defaultValue : false; } else if (field.type === 'multiselect' || field.type === 'objectMarking' || field.type === 'objectLabel' || field.type === 'externalReferences' || field.type === 'files') { initialValues[field.name] = field.defaultValue || []; } else if (field.type === 'datetime') { @@ -244,7 +244,7 @@ const FormViewInner: FunctionComponent = ({ queryRef, embedd const relationshipFields: Record = {}; relationship.fields.forEach((field) => { if (field.type === 'checkbox' || field.type === 'toggle') { - relationshipFields[field.name] = false; + relationshipFields[field.name] = field.defaultValue !== undefined ? field.defaultValue : false; } else if (field.type === 'multiselect' || field.type === 'objectMarking' || field.type === 'objectLabel' || field.type === 'externalReferences') { relationshipFields[field.name] = field.defaultValue || []; } else if (field.type === 'datetime') { @@ -277,7 +277,7 @@ const FormViewInner: FunctionComponent = ({ queryRef, embedd const fieldsObj: Record = {}; entityFields.forEach((field) => { if (field.type === 'checkbox' || field.type === 'toggle') { - fieldsObj[field.name] = false; + fieldsObj[field.name] = field.defaultValue !== undefined ? field.defaultValue : false; } else if (field.type === 'multiselect' || field.type === 'objectMarking' || field.type === 'objectLabel' || field.type === 'externalReferences' || field.type === 'files') { fieldsObj[field.name] = field.defaultValue || []; } else if (field.type === 'datetime') { @@ -297,7 +297,7 @@ const FormViewInner: FunctionComponent = ({ queryRef, embedd const fieldGroup: Record = {}; entityFields.forEach((field) => { if (field.type === 'checkbox' || field.type === 'toggle') { - fieldGroup[field.name] = false; + fieldGroup[field.name] = field.defaultValue !== undefined ? field.defaultValue : false; } else if (field.type === 'multiselect' || field.type === 'objectMarking' || field.type === 'objectLabel' || field.type === 'externalReferences' || field.type === 'files') { fieldGroup[field.name] = field.defaultValue || []; } else if (field.type === 'datetime') { From c69df325f2e2a6cedf195b216107c214f6ed482b Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Sat, 10 Jan 2026 19:12:37 +0100 Subject: [PATCH 071/126] [frontend] Remove unused Interval parameter from bookmark widget configuration (#12445) Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: SamuelHassine <1334279+SamuelHassine@users.noreply.github.com> --- .../opencti-front/src/utils/widget/widgetUtils.test.tsx | 2 +- opencti-platform/opencti-front/src/utils/widget/widgetUtils.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/opencti-platform/opencti-front/src/utils/widget/widgetUtils.test.tsx b/opencti-platform/opencti-front/src/utils/widget/widgetUtils.test.tsx index 25f44e71e8b8..e5df99fdf589 100644 --- a/opencti-platform/opencti-front/src/utils/widget/widgetUtils.test.tsx +++ b/opencti-platform/opencti-front/src/utils/widget/widgetUtils.test.tsx @@ -40,7 +40,7 @@ describe('widgetUtils', () => { expect(getCurrentCategory('heatmap')).toBe('timeseries'); expect(getCurrentCategory('tree')).toBe('distribution'); expect(getCurrentCategory('map')).toBe('distribution'); - expect(getCurrentCategory('bookmark')).toBe('timeseries'); + expect(getCurrentCategory('bookmark')).toBe('list'); expect(getCurrentCategory('wordcloud')).toBe('distribution'); }); diff --git a/opencti-platform/opencti-front/src/utils/widget/widgetUtils.tsx b/opencti-platform/opencti-front/src/utils/widget/widgetUtils.tsx index 44640197c020..ad5bdbcd4a96 100644 --- a/opencti-platform/opencti-front/src/utils/widget/widgetUtils.tsx +++ b/opencti-platform/opencti-front/src/utils/widget/widgetUtils.tsx @@ -185,7 +185,7 @@ const widgetVisualizationTypes = [ key: 'bookmark', name: 'Bookmark', dataSelectionLimit: 1, - category: 'timeseries', + category: 'list', availableParameters: [], isRelationships: false, isEntities: true, From b21a404c286a0f0826ec24de739807e04a5dd185 Mon Sep 17 00:00:00 2001 From: Samuel Hassine Date: Sat, 10 Jan 2026 19:52:26 +0100 Subject: [PATCH 072/126] [client/backend/frontend] Upload file at creation of entities instead of after the creation (#13971, #13940, #13058) --- .gitignore | 1 + .../.readthedocs.yml => .readthedocs.yaml | 18 +- client-python/README.md | 6 +- client-python/docs/conf.py | 81 +- client-python/docs/requirements.txt | 8 +- client-python/pycti/__init__.py | 17 +- client-python/pycti/api/opencti_api_client.py | 406 ++++-- .../pycti/api/opencti_api_connector.py | 73 +- client-python/pycti/api/opencti_api_draft.py | 29 +- .../pycti/api/opencti_api_internal_file.py | 22 +- .../pycti/api/opencti_api_notification.py | 48 +- client-python/pycti/api/opencti_api_pir.py | 47 +- .../pycti/api/opencti_api_playbook.py | 39 +- .../pycti/api/opencti_api_public_dashboard.py | 30 +- client-python/pycti/api/opencti_api_trash.py | 34 +- client-python/pycti/api/opencti_api_work.py | 159 ++- .../pycti/api/opencti_api_workspace.py | 28 +- .../pycti/connector/opencti_connector.py | 128 +- .../connector/opencti_connector_helper.py | 1114 +++++++++++++---- .../pycti/connector/opencti_metric_handler.py | 149 ++- .../pycti/entities/opencti_attack_pattern.py | 203 ++- .../pycti/entities/opencti_campaign.py | 123 +- .../pycti/entities/opencti_capability.py | 8 + .../pycti/entities/opencti_case_incident.py | 247 ++-- .../pycti/entities/opencti_case_rfi.py | 298 +++-- .../pycti/entities/opencti_case_rft.py | 300 +++-- .../pycti/entities/opencti_channel.py | 194 ++- .../entities/opencti_course_of_action.py | 162 ++- .../pycti/entities/opencti_data_component.py | 210 +++- .../pycti/entities/opencti_data_source.py | 213 +++- client-python/pycti/entities/opencti_event.py | 165 ++- .../entities/opencti_external_reference.py | 198 ++- .../pycti/entities/opencti_feedback.py | 257 ++-- client-python/pycti/entities/opencti_group.py | 17 +- .../pycti/entities/opencti_grouping.py | 277 ++-- .../pycti/entities/opencti_identity.py | 110 +- .../pycti/entities/opencti_incident.py | 158 ++- .../pycti/entities/opencti_indicator.py | 174 ++- .../pycti/entities/opencti_infrastructure.py | 168 ++- .../pycti/entities/opencti_intrusion_set.py | 96 +- .../entities/opencti_kill_chain_phase.py | 107 +- client-python/pycti/entities/opencti_label.py | 112 +- .../pycti/entities/opencti_language.py | 134 +- .../pycti/entities/opencti_location.py | 187 ++- .../pycti/entities/opencti_malware.py | 101 +- .../entities/opencti_malware_analysis.py | 102 +- .../entities/opencti_marking_definition.py | 131 +- .../pycti/entities/opencti_narrative.py | 194 ++- client-python/pycti/entities/opencti_note.py | 267 ++-- .../pycti/entities/opencti_observed_data.py | 234 ++-- .../pycti/entities/opencti_opinion.py | 265 ++-- .../pycti/entities/opencti_report.py | 281 +++-- client-python/pycti/entities/opencti_role.py | 17 +- .../entities/opencti_security_coverage.py | 132 +- .../pycti/entities/opencti_settings.py | 21 +- client-python/pycti/entities/opencti_stix.py | 37 +- .../entities/opencti_stix_core_object.py | 285 +++-- .../opencti_stix_core_relationship.py | 402 ++++-- .../entities/opencti_stix_cyber_observable.py | 450 +++++-- .../entities/opencti_stix_domain_object.py | 340 +++-- .../opencti_stix_nested_ref_relationship.py | 117 +- ...pencti_stix_object_or_stix_relationship.py | 43 +- .../opencti_stix_sighting_relationship.py | 270 ++-- client-python/pycti/entities/opencti_task.py | 184 ++- .../pycti/entities/opencti_threat_actor.py | 103 +- .../entities/opencti_threat_actor_group.py | 163 ++- .../opencti_threat_actor_individual.py | 167 ++- client-python/pycti/entities/opencti_tool.py | 100 +- client-python/pycti/entities/opencti_user.py | 30 +- .../pycti/entities/opencti_vocabulary.py | 99 +- .../pycti/entities/opencti_vulnerability.py | 186 ++- ...pencti_stix_cyber_observable_deprecated.py | 21 +- ...pencti_stix_cyber_observable_properties.py | 4 + client-python/pycti/utils/constants.py | 369 +++++- client-python/pycti/utils/opencti_logger.py | 18 +- client-python/pycti/utils/opencti_stix2.py | 873 +++++++++---- .../pycti/utils/opencti_stix2_identifier.py | 32 +- .../pycti/utils/opencti_stix2_splitter.py | 102 +- .../pycti/utils/opencti_stix2_update.py | 353 +++++- .../pycti/utils/opencti_stix2_utils.py | 57 +- client-python/setup.cfg | 9 +- .../tests/01-unit/utils/test_opencti_stix2.py | 2 +- opencti-platform/opencti-front/.gitignore | 1 + .../opencti-front/lang/front/de.json | 1 + .../opencti-front/lang/front/en.json | 1 + .../opencti-front/lang/front/es.json | 1 + .../opencti-front/lang/front/fr.json | 1 + .../opencti-front/lang/front/it.json | 1 + .../opencti-front/lang/front/ja.json | 1 + .../opencti-front/lang/front/ko.json | 1 + .../opencti-front/lang/front/ru.json | 1 + .../opencti-front/lang/front/zh.json | 1 + .../opencti-front/src/private/Root.tsx | 8 +- .../private/components/data/forms/Form.d.ts | 3 +- .../data/forms/FormSchemaEditor.tsx | 16 +- .../components/data/forms/FormUtils.ts | 1 + .../data/forms/view/FormFieldRenderer.tsx | 55 +- .../components/profile/ProfileOverview.jsx | 8 +- .../src/schema/relay.schema.graphql | 87 +- .../utils/pdfUnnecessarytHtml.test.ts | 145 +++ .../htmlToPdf/utils/pdfUnnecessarytHtml.ts | 35 +- .../config/schema/opencti.graphql | 63 + .../src/database/data-builder.js | 1 + .../src/database/middleware.js | 2 +- .../opencti-graphql/src/generated/graphql.ts | 86 ++ .../administrativeArea.graphql | 1 + .../case/case-incident/case-incident.graphql | 1 + .../modules/case/case-rfi/case-rfi.graphql | 1 + .../modules/case/case-rft/case-rft.graphql | 1 + .../modules/case/feedback/feedback.graphql | 1 + .../src/modules/channel/channel.graphql | 1 + .../dataComponent/dataComponent.graphql | 1 + .../src/modules/dataSource/dataSource.graphql | 1 + .../src/modules/event/event.graphql | 1 + .../src/modules/form/form-types.ts | 2 +- .../src/modules/grouping/grouping.graphql | 1 + .../src/modules/indicator/indicator.graphql | 1 + .../src/modules/language/language.graphql | 2 + .../malwareAnalysis/malwareAnalysis.graphql | 1 + .../src/modules/narrative/narrative.graphql | 1 + .../modules/organization/organization.graphql | 1 + .../securityCoverage/securityCoverage.graphql | 2 + .../securityPlatform/securityPlatform.graphql | 2 + .../src/modules/task/task.graphql | 2 + .../threatActorIndividual.graphql | 1 + .../tests/utils/syncCountHelper.ts | 2 +- scripts/clone-dependencies.sh | 20 +- 127 files changed, 10241 insertions(+), 3442 deletions(-) rename client-python/.readthedocs.yml => .readthedocs.yaml (53%) create mode 100644 opencti-platform/opencti-front/src/utils/htmlToPdf/utils/pdfUnnecessarytHtml.test.ts diff --git a/.gitignore b/.gitignore index 814ddeb4e11d..066dd4b3bd33 100644 --- a/.gitignore +++ b/.gitignore @@ -19,3 +19,4 @@ node_modules/ .venv venv /licenses/ +opencti-platform/opencti-front/.yarnrc.yml diff --git a/client-python/.readthedocs.yml b/.readthedocs.yaml similarity index 53% rename from client-python/.readthedocs.yml rename to .readthedocs.yaml index 5b8a44a7a92a..355b64a72548 100644 --- a/client-python/.readthedocs.yml +++ b/.readthedocs.yaml @@ -1,6 +1,5 @@ ---- -# .readthedocs.yml -# Read the Docs configuration file +# .readthedocs.yaml +# Read the Docs configuration file for the Python client documentation # See https://docs.readthedocs.io/en/stable/config-file/v2.html for details # Required @@ -11,13 +10,9 @@ build: tools: python: "3.12" -# Build documentation in the docs/ directory with Sphinx +# Build documentation in the client-python/docs/ directory with Sphinx sphinx: - configuration: docs/conf.py - -# Build documentation with MkDocs -#mkdocs: -# configuration: mkdocs.yml + configuration: client-python/docs/conf.py # Optionally build your docs in additional formats such as PDF and ePub formats: all @@ -25,5 +20,6 @@ formats: all # Optionally set the version of Python and requirements required to build your docs python: install: - - requirements: requirements.txt - - requirements: docs/requirements.txt + - requirements: client-python/requirements.txt + - requirements: client-python/docs/requirements.txt + diff --git a/client-python/README.md b/client-python/README.md index de02fd6d3bc5..005376f17ccd 100644 --- a/client-python/README.md +++ b/client-python/README.md @@ -1,7 +1,7 @@ # OpenCTI client for Python [![Website](https://img.shields.io/badge/website-opencti.io-blue.svg)](https://opencti.io) -[![readthedocs](https://readthedocs.org/projects/opencti-client-for-python/badge/?style=flat)](https://opencti-client-for-python.readthedocs.io/en/latest/) +[![readthedocs](https://readthedocs.org/projects/opencti-python-client/badge/?style=flat)](https://opencti-python-client.readthedocs.io/en/latest/) [![Number of PyPI downloads](https://img.shields.io/pypi/dm/pycti.svg)](https://pypi.python.org/pypi/pycti/) [![Slack Status](https://img.shields.io/badge/slack-3K%2B%20members-4A154B)](https://community.filigran.io) @@ -53,11 +53,11 @@ $ pip install -e . ### Client usage -To learn about how to use the OpenCTI Python client and read some examples and cases, refer to [the client documentation](https://opencti-client-for-python.readthedocs.io/en/latest/client_usage/getting_started.html). +To learn about how to use the OpenCTI Python client and read some examples and cases, refer to [the client documentation](https://opencti-python-client.readthedocs.io/en/latest/client_usage/getting_started.html). ### API reference -To learn about the methods available for executing queries and retrieving their answers, refer to [the client API Reference](https://opencti-client-for-python.readthedocs.io/en/latest/pycti/pycti.html). +To learn about the methods available for executing queries and retrieving their answers, refer to [the client API Reference](https://opencti-python-client.readthedocs.io/en/latest/pycti/pycti.html). ## Tests diff --git a/client-python/docs/conf.py b/client-python/docs/conf.py index 7e3af910b83c..aced7f5eb290 100644 --- a/client-python/docs/conf.py +++ b/client-python/docs/conf.py @@ -11,23 +11,39 @@ # documentation root, use os.path.abspath to make it absolute, like shown here. # import os +import re import sys -sys.path.insert(0, os.path.abspath("..")) +# Add the client-python directory to the path for pycti module discovery +# This works both when building from client-python/ locally and from repo root via ReadTheDocs +docs_dir = os.path.dirname(os.path.abspath(__file__)) +client_python_dir = os.path.dirname(docs_dir) +sys.path.insert(0, client_python_dir) # -- Project information ----------------------------------------------------- -project = "OpenCTI client for Python" +project = "OpenCTI Python Client" copyright = "2025, Filigran" author = "OpenCTI Project" + # The full version, including alpha/beta/rc tags -release = "6.0.7" +# Read version from pycti/__init__.py without importing (avoids dependency issues) +def get_version(): + init_path = os.path.join(client_python_dir, "pycti", "__init__.py") + with open(init_path, "r") as f: + content = f.read() + match = re.search(r'^__version__\s*=\s*["\']([^"\']+)["\']', content, re.MULTILINE) + if match: + return match.group(1) + return "unknown" -master_doc = "index" -autoapi_modules = {"pycti": {"prune": True}} +release = get_version() +version = release + +master_doc = "index" pygments_style = "sphinx" @@ -38,11 +54,62 @@ # ones. extensions = [ "sphinx.ext.autodoc", - "sphinx.ext.inheritance_diagram", - "autoapi.sphinx", + "sphinx.ext.viewcode", + "sphinx.ext.napoleon", + "autoapi.extension", "sphinx_autodoc_typehints", ] +# Napoleon settings for Google/NumPy style docstrings +napoleon_google_docstring = True +napoleon_numpy_docstring = True +napoleon_include_init_with_doc = True +napoleon_include_private_with_doc = False +napoleon_include_special_with_doc = True +napoleon_use_admonition_for_examples = False +napoleon_use_admonition_for_notes = False +napoleon_use_admonition_for_references = False +napoleon_use_ivar = False +napoleon_use_param = True +napoleon_use_rtype = True +napoleon_type_aliases = None + +# AutoAPI configuration for sphinx-autoapi +autoapi_type = "python" +autoapi_dirs = [os.path.join(client_python_dir, "pycti")] +autoapi_options = [ + "members", + "undoc-members", + "show-inheritance", + "show-module-summary", + "imported-members", +] +autoapi_python_class_content = ( + "both" # Include both class docstring and __init__ docstring +) +autoapi_member_order = "bysource" +autoapi_keep_files = False +autoapi_add_toctree_entry = True + +# Mock imports for modules that can't be installed on ReadTheDocs +autodoc_mock_imports = [ + "magic", + "pika", + "stix2", + "pydantic", + "yaml", + "requests", + "cachetools", + "prometheus_client", + "opentelemetry", + "deprecation", + "fastapi", + "uvicorn", + "sseclient", + "datefinder", + "python_json_logger", +] + # Add any paths that contain templates here, relative to this directory. templates_path = ["_templates"] diff --git a/client-python/docs/requirements.txt b/client-python/docs/requirements.txt index 31356520e3cf..8186f6ff9994 100644 --- a/client-python/docs/requirements.txt +++ b/client-python/docs/requirements.txt @@ -1,4 +1,4 @@ -autoapi==2.0.1 -sphinx==9.0.4 -sphinx-autodoc-typehints==3.2.0 -sphinx_rtd_theme==3.0.2 +sphinx-autoapi>=3.0.0 +sphinx>=8.2,<9 +sphinx-autodoc-typehints>=3.0.0 +sphinx_rtd_theme>=3.0.0 diff --git a/client-python/pycti/__init__.py b/client-python/pycti/__init__.py index 0207447c3766..d89f344a27bf 100644 --- a/client-python/pycti/__init__.py +++ b/client-python/pycti/__init__.py @@ -12,8 +12,6 @@ from .connector.opencti_metric_handler import OpenCTIMetricHandler from .entities.opencti_attack_pattern import AttackPattern from .entities.opencti_campaign import Campaign - -# Administrative entities from .entities.opencti_capability import Capability from .entities.opencti_case_incident import CaseIncident from .entities.opencti_case_rfi import CaseRfi @@ -22,6 +20,7 @@ from .entities.opencti_course_of_action import CourseOfAction from .entities.opencti_data_component import DataComponent from .entities.opencti_data_source import DataSource +from .entities.opencti_event import Event from .entities.opencti_external_reference import ExternalReference from .entities.opencti_feedback import Feedback from .entities.opencti_group import Group @@ -33,16 +32,21 @@ from .entities.opencti_intrusion_set import IntrusionSet from .entities.opencti_kill_chain_phase import KillChainPhase from .entities.opencti_label import Label +from .entities.opencti_language import Language from .entities.opencti_location import Location from .entities.opencti_malware import Malware from .entities.opencti_malware_analysis import MalwareAnalysis from .entities.opencti_marking_definition import MarkingDefinition +from .entities.opencti_narrative import Narrative from .entities.opencti_note import Note from .entities.opencti_observed_data import ObservedData from .entities.opencti_opinion import Opinion from .entities.opencti_report import Report from .entities.opencti_role import Role +from .entities.opencti_security_coverage import SecurityCoverage from .entities.opencti_settings import Settings +from .entities.opencti_stix import Stix +from .entities.opencti_stix_core_object import StixCoreObject from .entities.opencti_stix_core_relationship import StixCoreRelationship from .entities.opencti_stix_cyber_observable import StixCyberObservable from .entities.opencti_stix_domain_object import StixDomainObject @@ -57,6 +61,7 @@ from .entities.opencti_threat_actor_individual import ThreatActorIndividual from .entities.opencti_tool import Tool from .entities.opencti_user import User +from .entities.opencti_vocabulary import Vocabulary from .entities.opencti_vulnerability import Vulnerability from .utils.constants import ( CustomObjectCaseIncident, @@ -100,6 +105,7 @@ "CourseOfAction", "DataComponent", "DataSource", + "Event", "ExternalReference", "Feedback", "Grouping", @@ -110,10 +116,12 @@ "IntrusionSet", "KillChainPhase", "Label", + "Language", "Location", "Malware", "MalwareAnalysis", "MarkingDefinition", + "Narrative", "Note", "ObservedData", "OpenCTIApiClient", @@ -128,6 +136,9 @@ "OpenCTIStix2Utils", "Opinion", "Report", + "SecurityCoverage", + "Stix", + "StixCoreObject", "StixCoreRelationship", "StixCyberObservable", "StixNestedRefRelationship", @@ -141,12 +152,12 @@ "ThreatActorGroup", "ThreatActorIndividual", "Tool", + "Vocabulary", "Vulnerability", "get_config_variable", "CustomObjectCaseIncident", "CustomObjectTask", "CustomObjectChannel", - "StixCyberObservableTypes", "CustomObservableCredential", "CustomObservableHostname", "CustomObservableUserAgent", diff --git a/client-python/pycti/api/opencti_api_client.py b/client-python/pycti/api/opencti_api_client.py index 76309d8e9ede..29cff1c72c7e 100644 --- a/client-python/pycti/api/opencti_api_client.py +++ b/client-python/pycti/api/opencti_api_client.py @@ -10,7 +10,7 @@ import signal import tempfile import threading -from typing import Dict, Tuple, Union +from typing import Any, Dict, Optional, Tuple, Union import magic import requests @@ -93,6 +93,19 @@ def build_request_headers(token: str, custom_headers: str, app_logger, provider: str): + """Build request headers for OpenCTI API requests. + + :param token: the API authentication token + :type token: str + :param custom_headers: custom headers in format "header01:value;header02:value" + :type custom_headers: str + :param app_logger: the application logger instance + :type app_logger: logging.Logger + :param provider: the provider string for User-Agent header + :type provider: str + :return: dictionary of request headers + :rtype: dict + """ pycti_user_agent = "pycti/" + __version__ if provider is not None: pycti_user_agent += " " + provider @@ -115,7 +128,28 @@ def build_request_headers(token: str, custom_headers: str, app_logger, provider: class File: + """File object for OpenCTI file uploads. + + Represents a file to be uploaded via the OpenCTI API. + + :param name: the filename + :type name: str + :param data: the file content (string or bytes) + :type data: str or bytes + :param mime: the MIME type of the file, defaults to "text/plain" + :type mime: str, optional + """ + def __init__(self, name, data, mime="text/plain"): + """Initialize the File instance. + + :param name: the filename + :type name: str + :param data: the file content + :type data: str or bytes + :param mime: the MIME type of the file (default: "text/plain") + :type mime: str + """ self.name = name self.data = data self.mime = mime @@ -132,14 +166,8 @@ class OpenCTIApiClient: :type log_level: str, optional :param ssl_verify: Requiring the requests to verify the TLS certificate at the server. :type ssl_verify: bool, str, optional - :param proxies: - :type proxies: dict, optional, The proxy configuration, would have `http` and `https` attributes. Defaults to {} - ``` - proxies: { - "http": "http://my_proxy:8080" - "https": "http://my_proxy:8080" - } - ``` + :param proxies: proxy configuration with "http" and "https" keys (e.g., {"http": "http://my_proxy:8080", "https": "http://my_proxy:8080"}) + :type proxies: dict, optional :param json_logging: format the logs as json if set to True :type json_logging: bool, optional :param bundle_send_to_queue: if bundle will be sent to queue @@ -166,12 +194,40 @@ def __init__( json_logging: bool = False, bundle_send_to_queue: bool = True, cert: Union[str, Tuple[str, str], None] = None, - custom_headers: str = None, + custom_headers: Optional[str] = None, perform_health_check: bool = True, requests_timeout: int = 300, - provider: str = None, + provider: Optional[str] = None, ): - """Constructor method""" + """Initialize the OpenCTIApiClient instance. + + :param url: OpenCTI platform URL + :type url: str + :param token: OpenCTI API authentication token + :type token: str + :param log_level: logging level (default: "info") + :type log_level: str + :param ssl_verify: SSL certificate verification setting + :type ssl_verify: Union[bool, str] + :param proxies: proxy configuration dictionary with "http" and "https" keys + :type proxies: Dict[str, str] or None + :param json_logging: whether to format logs as JSON (default: False) + :type json_logging: bool + :param bundle_send_to_queue: whether bundles are sent to queue (default: True) + :type bundle_send_to_queue: bool + :param cert: client certificate path or tuple of (cert, key) paths + :type cert: str, tuple, or None + :param custom_headers: custom headers in format "header01:value;header02:value" + :type custom_headers: str or None + :param perform_health_check: whether to check API access on init (default: True) + :type perform_health_check: bool + :param requests_timeout: timeout for API requests in seconds (default: 300) + :type requests_timeout: int + :param provider: client provider for User-Agent header (format: provider/version) + :type provider: str or None + + :raises ValueError: If URL or token is missing or invalid + """ # Check configuration self.bundle_send_to_queue = bundle_send_to_queue @@ -218,20 +274,21 @@ def __init__( self.stix2 = OpenCTIStix2(self) self.pir = OpenCTIApiPir(self) self.internal_file = OpenCTIApiInternalFile(self) + self.file = File # File class for creating upload objects # Define the entities self.vocabulary = Vocabulary(self) self.label = Label(self) self.marking_definition = MarkingDefinition(self) - self.external_reference = ExternalReference(self, File) + self.external_reference = ExternalReference(self) self.kill_chain_phase = KillChainPhase(self) self.opencti_stix_object_or_stix_relationship = StixObjectOrStixRelationship( self ) self.stix = Stix(self) - self.stix_domain_object = StixDomainObject(self, File) - self.stix_core_object = StixCoreObject(self, File) - self.stix_cyber_observable = StixCyberObservable(self, File) + self.stix_domain_object = StixDomainObject(self) + self.stix_core_object = StixCoreObject(self) + self.stix_cyber_observable = StixCyberObservable(self) self.stix_core_relationship = StixCoreRelationship(self) self.stix_sighting_relationship = StixSightingRelationship(self) self.stix_nested_ref_relationship = StixNestedRefRelationship(self) @@ -427,44 +484,135 @@ def _get_certificate_content(self, https_ca_certificates): ) def set_applicant_id_header(self, applicant_id): + """Set the applicant ID header for impersonation. + + :param applicant_id: the ID of the user to impersonate + :type applicant_id: str + """ self.request_headers["opencti-applicant-id"] = applicant_id def set_playbook_id_header(self, playbook_id): + """Set the playbook ID header for tracking playbook execution. + + :param playbook_id: the ID of the playbook being executed + :type playbook_id: str + """ self.request_headers["opencti-playbook-id"] = playbook_id def set_event_id(self, event_id): + """Set the event ID header for event tracking. + + :param event_id: the ID of the event + :type event_id: str + """ self.request_headers["opencti-event-id"] = event_id def get_draft_id(self): + """Get the current draft ID. + + :return: the current draft ID or empty string if not set + :rtype: str + """ if self.draft_id is None: return "" return self.draft_id def set_draft_id(self, draft_id): + """Set the draft ID header for draft mode operations. + + :param draft_id: the ID of the draft workspace + :type draft_id: str + """ self.draft_id = draft_id self.request_headers["opencti-draft-id"] = draft_id def set_synchronized_upsert_header(self, synchronized): + """Set the synchronized upsert header. + + :param synchronized: whether upsert should be synchronized + :type synchronized: bool + """ self.request_headers["synchronized-upsert"] = ( "true" if synchronized is True else "false" ) def set_previous_standard_header(self, previous_standard): + """Set the previous standard header for update operations. + + :param previous_standard: the previous standard ID + :type previous_standard: str + """ self.request_headers["previous-standard"] = previous_standard def get_request_headers(self, hide_token=True): + """Get a copy of current request headers. + + :param hide_token: if True, masks the Authorization token with asterisks + :type hide_token: bool + :return: copy of request headers + :rtype: dict + """ request_headers_copy = self.request_headers.copy() if hide_token and "Authorization" in request_headers_copy: request_headers_copy["Authorization"] = "*****" return request_headers_copy def set_retry_number(self, retry_number): + """Set the retry number header for tracking retries. + + :param retry_number: the current retry attempt number, or None to clear + :type retry_number: int or None + """ self.request_headers["opencti-retry-number"] = ( "" if retry_number is None else str(retry_number) ) + def _extract_files(self, obj, path_prefix=""): + """Recursively extract File objects from nested dictionaries. + + :param obj: the object to search for File objects + :type obj: any + :param path_prefix: the current path prefix for nested keys + :type path_prefix: str + :return: tuple of (cleaned_obj, files_vars) where cleaned_obj has Files replaced with None + :rtype: tuple + """ + if isinstance(obj, File): + return None, [{"key": path_prefix, "file": obj, "multiple": False}] + + if ( + isinstance(obj, list) + and len(obj) > 0 + and all(map(lambda x: isinstance(x, File), obj)) + ): + return [None] * len(obj), [ + {"key": path_prefix, "file": obj, "multiple": True} + ] + + if isinstance(obj, dict): + cleaned = {} + files_vars = [] + for key, val in obj.items(): + new_path = f"{path_prefix}.{key}" if path_prefix else key + cleaned_val, nested_files = self._extract_files(val, new_path) + cleaned[key] = cleaned_val + files_vars.extend(nested_files) + return cleaned, files_vars + + if isinstance(obj, list): + cleaned = [] + files_vars = [] + for i, item in enumerate(obj): + new_path = f"{path_prefix}.{i}" if path_prefix else str(i) + cleaned_item, nested_files = self._extract_files(item, new_path) + cleaned.append(cleaned_item) + files_vars.extend(nested_files) + return cleaned, files_vars + + return obj, [] + def query(self, query, variables=None, disable_impersonate=False): - """submit a query to the OpenCTI GraphQL API + """Submit a query to the OpenCTI GraphQL API. :param query: GraphQL query string :type query: str @@ -472,29 +620,16 @@ def query(self, query, variables=None, disable_impersonate=False): :type variables: dict, optional :param disable_impersonate: removes impersonate header if set to True, defaults to False :type disable_impersonate: bool, optional - :return: returns the response json content - :rtype: Any + :return: returns the response JSON content + :rtype: dict + :raises ValueError: if the API returns an error or non-200 status code """ variables = variables or {} - query_var = {} - files_vars = [] # Implementation of spec https://github.com/jaydenseric/graphql-multipart-request-spec # Support for single or multiple upload # Batching or mixed upload or not supported - var_keys = variables.keys() - for key in var_keys: - val = variables[key] - is_file = type(val) is File - is_files = ( - isinstance(val, list) - and len(val) > 0 - and all(map(lambda x: isinstance(x, File), val)) - ) - if is_file or is_files: - files_vars.append({"key": key, "file": val, "multiple": is_files}) - query_var[key] = None if is_file else [None] * len(val) - else: - query_var[key] = val + # Recursively extract File objects from nested dictionaries + query_var, files_vars = self._extract_files(variables) query_headers = self.request_headers.copy() if disable_impersonate and "opencti-applicant-id" in query_headers: @@ -608,36 +743,50 @@ def query(self, query, variables=None, disable_impersonate=False): raise ValueError(r.text) def fetch_opencti_file(self, fetch_uri, binary=False, serialize=False): - """get file from the OpenCTI API + """Get file from the OpenCTI API. :param fetch_uri: download URI to use :type fetch_uri: str - :param binary: [description], defaults to False + :param binary: if True, returns raw bytes; if False, returns text, defaults to False :type binary: bool, optional - :return: returns either the file content as text or bytes based on `binary` - :rtype: str or bytes + :param serialize: if True, returns base64-encoded content, defaults to False + :type serialize: bool, optional + :return: returns either the file content as text, bytes, base64-encoded string, or None on failure + :rtype: str, bytes, or None """ - - r = self.session.get( - fetch_uri, - headers=self.request_headers, - verify=self.ssl_verify, - cert=self.cert, - proxies=self.proxies, - timeout=self.session_requests_timeout, - ) - if binary: + try: + r = self.session.get( + fetch_uri, + headers=self.request_headers, + verify=self.ssl_verify, + cert=self.cert, + proxies=self.proxies, + timeout=self.session_requests_timeout, + ) + # Check if request was successful + if not r.ok: + self.app_logger.warning( + "Failed to fetch file", + {"uri": fetch_uri, "status_code": r.status_code}, + ) + return None + if binary: + if serialize: + return base64.b64encode(r.content).decode("utf-8") + return r.content if serialize: - return base64.b64encode(r.content).decode("utf-8") - return r.content - if serialize: - return base64.b64encode(r.text).decode("utf-8") - return r.text + return base64.b64encode(r.text.encode("utf-8")).decode("utf-8") + return r.text + except Exception as e: + self.app_logger.warning( + "Error fetching file", {"uri": fetch_uri, "error": str(e)} + ) + return None def health_check(self): - """submit an example request to the OpenCTI API. + """Submit an example request to the OpenCTI API. - :return: returns `True` if the health check has been successful + :return: returns True if the health check has been successful :rtype: bool """ try: @@ -659,10 +808,10 @@ def health_check(self): return False def get_logs_worker_config(self): - """get the logsWorkerConfig + """Get the logs worker configuration from the OpenCTI platform. - return: the logsWorkerConfig - rtype: dict + :return: the logs worker configuration including Elasticsearch settings + :rtype: dict """ self.app_logger.info("Getting logs worker config...") @@ -683,11 +832,11 @@ def get_logs_worker_config(self): return result["data"]["logsWorkerConfig"] def not_empty(self, value): - """check if a value is empty for str, list and int + """Check if a value is empty for str, list and int. :param value: value to check :type value: str or list or int or float or bool or datetime.date - :return: returns `True` if the value is one of the supported types and not empty + :return: returns True if the value is one of the supported types and not empty :rtype: bool """ @@ -697,10 +846,7 @@ def not_empty(self, value): if isinstance(value, datetime.date): return True if isinstance(value, str): - if len(value) > 0: - return True - else: - return False + return len(value) > 0 if isinstance(value, dict): return bool(value) if isinstance(value, list): @@ -713,17 +859,18 @@ def not_empty(self, value): return True if isinstance(value, int): return True - else: - return False - else: return False + return False def process_multiple(self, data: dict, with_pagination=False) -> Union[dict, list]: - """processes data returned by the OpenCTI API with multiple entities + """Process data returned by the OpenCTI API with multiple entities. :param data: data to process - :param with_pagination: whether to use pagination with the API - :returns: returns either a dict or list with the processes entities + :type data: dict + :param with_pagination: whether to use pagination with the API, defaults to False + :type with_pagination: bool, optional + :return: returns either a dict or list with the processed entities + :rtype: dict or list """ if with_pagination: @@ -743,7 +890,7 @@ def process_multiple(self, data: dict, with_pagination=False) -> Union[dict, lis result.append(self.process_multiple_fields(row)) return result - # -- When data is wrapper in edges + # -- When data is wrapped in edges for edge in ( data["edges"] if "edges" in data and data["edges"] is not None else [] ): @@ -759,10 +906,12 @@ def process_multiple(self, data: dict, with_pagination=False) -> Union[dict, lis return result def process_multiple_ids(self, data) -> list: - """processes data returned by the OpenCTI API with multiple ids + """Process data returned by the OpenCTI API with multiple ids. :param data: data to process + :type data: list :return: returns a list of ids + :rtype: list """ result = [] @@ -775,7 +924,7 @@ def process_multiple_ids(self, data) -> list: return result def process_multiple_fields(self, data): - """processes data returned by the OpenCTI API with multiple fields + """Process data returned by the OpenCTI API with multiple fields. :param data: data to process :type data: dict @@ -891,12 +1040,13 @@ def upload_file(self, **kwargs): } """ if data is None: - data = open(file_name, "rb") + with open(file_name, "rb") as f: + data = f.read() if file_name.endswith(".json"): mime_type = "application/json" else: mime_type = magic.from_file(file_name, mime=True) - query_vars = {"file": (File(file_name, data, mime_type))} + query_vars = {"file": File(file_name, data, mime_type)} # optional file markings if file_markings is not None: query_vars["fileMarkings"] = file_markings @@ -906,10 +1056,14 @@ def upload_file(self, **kwargs): return None def create_draft(self, **kwargs): - """create a draft in OpenCTI API - :param `**kwargs`: arguments for file name creating draft (required: `draft_name`) - :return: returns the query response for the draft creation - :rtype: id + """Create a draft in OpenCTI API. + + :param draft_name: the name of the draft to create (required) + :type draft_name: str + :param entity_id: the entity ID to associate with the draft + :type entity_id: str, optional + :return: returns the draft workspace ID + :rtype: str """ draft_name = kwargs.get("draft_name", None) @@ -934,9 +1088,18 @@ def create_draft(self, **kwargs): return None def upload_pending_file(self, **kwargs): - """upload a file to OpenCTI API - - :param `**kwargs`: arguments for file upload (required: `file_name` and `data`) + """Upload a pending file to OpenCTI API. + + :param file_name: the name of the file to upload (required) + :type file_name: str + :param data: the file content, defaults to reading from file_name path + :type data: str or bytes, optional + :param mime_type: the MIME type of the file, defaults to "text/plain" + :type mime_type: str, optional + :param entity_id: the entity ID to associate with the file + :type entity_id: str, optional + :param file_markings: list of marking definition IDs to apply + :type file_markings: list, optional :return: returns the query response for the file upload :rtype: dict """ @@ -958,7 +1121,8 @@ def upload_pending_file(self, **kwargs): } """ if data is None: - data = open(file_name, "rb") + with open(file_name, "rb") as f: + data = f.read() if file_name.endswith(".json"): mime_type = "application/json" else: @@ -966,7 +1130,7 @@ def upload_pending_file(self, **kwargs): return self.query( query, { - "file": (File(file_name, data, mime_type)), + "file": File(file_name, data, mime_type), "entityId": entity_id, "file_markings": file_markings, }, @@ -976,9 +1140,14 @@ def upload_pending_file(self, **kwargs): return None def send_bundle_to_api(self, **kwargs): - """Push a bundle to a queue through OpenCTI API - - :param `**kwargs`: arguments for bundle push (required: `connectorId` and `bundle`) + """Push a bundle to a queue through OpenCTI API. + + :param connector_id: the connector ID (required) + :type connector_id: str + :param bundle: the STIX bundle to push (required) + :type bundle: str + :param work_id: the work ID to associate with the bundle + :type work_id: str, optional :return: returns the query response for the bundle push :rtype: dict """ @@ -1007,10 +1176,12 @@ def send_bundle_to_api(self, **kwargs): return None def get_stix_content(self, id): - """get the STIX content of any entity + """Get the STIX content of any entity. - return: the STIX content in JSON - rtype: dict + :param id: the ID of the entity + :type id: str + :return: the STIX content in JSON + :rtype: dict """ self.app_logger.info("Entity in JSON", {"id": id}) @@ -1023,47 +1194,68 @@ def get_stix_content(self, id): return json.loads(result["data"]["stix"]) @staticmethod - def get_attribute_in_extension(key, object) -> any: + def get_attribute_in_extension(key, stix_object) -> Any: + """Get an attribute value from OpenCTI STIX extensions. + + Searches for the key in OpenCTI extension definitions, or falls back + to the object's top-level attributes. + + :param key: the attribute key to retrieve + :type key: str + :param stix_object: the STIX object containing extensions + :type stix_object: dict + :return: the attribute value if found, None otherwise + :rtype: Any + """ if ( - "extensions" in object + "extensions" in stix_object and "extension-definition--ea279b3e-5c71-4632-ac08-831c66a786ba" - in object["extensions"] + in stix_object["extensions"] and key - in object["extensions"][ + in stix_object["extensions"][ "extension-definition--ea279b3e-5c71-4632-ac08-831c66a786ba" ] ): - return object["extensions"][ + return stix_object["extensions"][ "extension-definition--ea279b3e-5c71-4632-ac08-831c66a786ba" ][key] elif ( - "extensions" in object + "extensions" in stix_object and "extension-definition--f93e2c80-4231-4f9a-af8b-95c9bd566a82" - in object["extensions"] + in stix_object["extensions"] and key - in object["extensions"][ + in stix_object["extensions"][ "extension-definition--f93e2c80-4231-4f9a-af8b-95c9bd566a82" ] ): - return object["extensions"][ + return stix_object["extensions"][ "extension-definition--f93e2c80-4231-4f9a-af8b-95c9bd566a82" ][key] - elif key in object and key not in ["type"]: - return object[key] + elif key in stix_object and key not in ["type"]: + return stix_object[key] return None @staticmethod - def get_attribute_in_mitre_extension(key, object) -> any: + def get_attribute_in_mitre_extension(key, stix_object) -> Any: + """Get an attribute value from MITRE ATT&CK STIX extension. + + :param key: the attribute key to retrieve + :type key: str + :param stix_object: the STIX object containing extensions + :type stix_object: dict + :return: the attribute value if found, None otherwise + :rtype: Any + """ if ( - "extensions" in object + "extensions" in stix_object and "extension-definition--322b8f77-262a-4cb8-a915-1e441e00329b" - in object["extensions"] + in stix_object["extensions"] and key - in object["extensions"][ + in stix_object["extensions"][ "extension-definition--322b8f77-262a-4cb8-a915-1e441e00329b" ] ): - return object["extensions"][ + return stix_object["extensions"][ "extension-definition--322b8f77-262a-4cb8-a915-1e441e00329b" ][key] return None diff --git a/client-python/pycti/api/opencti_api_connector.py b/client-python/pycti/api/opencti_api_connector.py index d65a23876764..c2adb5e58195 100644 --- a/client-python/pycti/api/opencti_api_connector.py +++ b/client-python/pycti/api/opencti_api_connector.py @@ -1,22 +1,35 @@ import json -from typing import Any, Dict +from typing import Any, Dict, List from pycti.connector.opencti_connector import OpenCTIConnector class OpenCTIApiConnector: - """OpenCTIApiConnector""" + """OpenCTI Connector API class. + + Manages connector operations including registration, pinging, and listing. + + :param api: instance of :py:class:`~pycti.api.opencti_api_client.OpenCTIApiClient` + :type api: OpenCTIApiClient + """ def __init__(self, api): + """Initialize the OpenCTIApiConnector instance. + + :param api: OpenCTI API client instance + :type api: OpenCTIApiClient + """ self.api = api def read(self, connector_id: str) -> Dict: - """Reading the connector and its details + """Read the connector and its details. + :param connector_id: the id of the connector + :type connector_id: str :return: return all the connector details :rtype: dict """ - self.api.app_logger.info("[INFO] Getting connector details ...") + self.api.app_logger.info("Getting connector details ...") query = """ query GetConnector($id: String!) { connector(id: $id) { @@ -32,27 +45,27 @@ def read(self, connector_id: str) -> Dict: messages_number messages_size } - updated_at - created_at - config { - listen - listen_exchange - push - push_exchange - push_routing - } - built_in + updated_at + created_at + config { + listen + listen_exchange + push + push_exchange + push_routing + } + built_in } } """ result = self.api.query(query, {"id": connector_id}) return result["data"]["connector"] - def list(self) -> Dict: - """list available connectors + def list(self) -> List[Dict]: + """List available connectors. - :return: return dict with connectors - :rtype: dict + :return: list of connector dictionaries + :rtype: list[dict] """ self.api.app_logger.info("Getting connectors ...") @@ -91,14 +104,14 @@ def list(self) -> Dict: def ping( self, connector_id: str, connector_state: Any, connector_info: Dict ) -> Dict: - """pings a connector by id and state + """Ping a connector by ID and state. - :param connector_id: the connectors id + :param connector_id: the connector id :type connector_id: str :param connector_state: state for the connector - :type connector_state: - :param connector_info: all details connector - :type connector_info: Dict + :type connector_state: Any + :param connector_info: all details about the connector + :type connector_info: dict :return: the response pingConnector data dict :rtype: dict """ @@ -115,7 +128,7 @@ def ping( queue_messages_size next_run_datetime last_run_datetime - } + } } } """ @@ -130,9 +143,9 @@ def ping( return result["data"]["pingConnector"] def register(self, connector: OpenCTIConnector) -> Dict: - """register a connector with OpenCTI + """Register a connector with OpenCTI. - :param connector: `OpenCTIConnector` connector object + :param connector: OpenCTIConnector connector object :type connector: OpenCTIConnector :return: the response registerConnector data dict :rtype: dict @@ -167,11 +180,11 @@ def register(self, connector: OpenCTIConnector) -> Dict: return result["data"]["registerConnector"] def unregister(self, _id: str) -> Dict: - """unregister a connector with OpenCTI + """Unregister a connector with OpenCTI. - :param _id: `OpenCTIConnector` connector id - :type _id: string - :return: the response registerConnector data dict + :param _id: the connector id to unregister + :type _id: str + :return: the response deleteConnector data dict :rtype: dict """ query = """ diff --git a/client-python/pycti/api/opencti_api_draft.py b/client-python/pycti/api/opencti_api_draft.py index 9869c96191de..72d38e9c9862 100644 --- a/client-python/pycti/api/opencti_api_draft.py +++ b/client-python/pycti/api/opencti_api_draft.py @@ -1,11 +1,34 @@ class OpenCTIApiDraft: - """OpenCTIApiDraft""" + """OpenCTI Draft API class. + + Manages draft workspace operations. + + :param api: instance of :py:class:`~pycti.api.opencti_api_client.OpenCTIApiClient` + :type api: OpenCTIApiClient + """ def __init__(self, api): + """Initialize the OpenCTIApiDraft instance. + + :param api: OpenCTI API client instance + :type api: OpenCTIApiClient + """ self.api = api def delete(self, **kwargs): - id = kwargs.get("id", None) + """Delete a draft workspace. + + :param id: the draft workspace id + :type id: str + :return: None + :rtype: None + """ + draft_id = kwargs.get("id", None) + if draft_id is None: + self.api.app_logger.error( + "[opencti_draft] Cannot delete draft workspace, missing parameter: id" + ) + return None query = """ mutation DraftWorkspaceDelete($id: ID!) { draftWorkspaceDelete(id: $id) @@ -14,6 +37,6 @@ def delete(self, **kwargs): self.api.query( query, { - "id": id, + "id": draft_id, }, ) diff --git a/client-python/pycti/api/opencti_api_internal_file.py b/client-python/pycti/api/opencti_api_internal_file.py index a49b040797ae..196e3f0d6b75 100644 --- a/client-python/pycti/api/opencti_api_internal_file.py +++ b/client-python/pycti/api/opencti_api_internal_file.py @@ -1,10 +1,28 @@ class OpenCTIApiInternalFile: - """OpenCTIApiInternalFile""" + """OpenCTI Internal File API class. + + Manages internal file operations. + + :param api: instance of :py:class:`~pycti.api.opencti_api_client.OpenCTIApiClient` + :type api: OpenCTIApiClient + """ def __init__(self, api): + """Initialize the OpenCTIApiInternalFile instance. + + :param api: OpenCTI API client instance + :type api: OpenCTIApiClient + """ self.api = api def delete(self, **kwargs): + """Delete an internal file. + + :param item: the item containing file information with id in extensions + :type item: dict + :return: None + :rtype: None + """ item = kwargs.get("item", None) file_name = self.api.get_attribute_in_extension("id", item) if file_name is not None: @@ -21,6 +39,6 @@ def delete(self, **kwargs): ) else: self.api.app_logger.error( - "[stix_internal_file] Cant delete internal file, missing parameters: fileName" + "[opencti_internal_file] Cannot delete internal file, missing parameters: fileName" ) return None diff --git a/client-python/pycti/api/opencti_api_notification.py b/client-python/pycti/api/opencti_api_notification.py index 6f49b280a3de..4956fbad082d 100644 --- a/client-python/pycti/api/opencti_api_notification.py +++ b/client-python/pycti/api/opencti_api_notification.py @@ -1,13 +1,31 @@ class OpenCTIApiNotification: - """OpenCTIApiJob""" + """OpenCTI Notification API class. + + Manages notification operations. + + :param api: instance of :py:class:`~pycti.api.opencti_api_client.OpenCTIApiClient` + :type api: OpenCTIApiClient + """ def __init__(self, api): + """Initialize the OpenCTIApiNotification instance. + + :param api: OpenCTI API client instance + :type api: OpenCTIApiClient + """ self.api = api def delete(self, **kwargs): + """Delete a notification. + + :param id: the notification id + :type id: str + :return: None + :rtype: None + """ notification_id = kwargs.get("id", None) self.api.app_logger.info( - "Deleting notifcation", {"notification_id": notification_id} + "Deleting notification", {"notification_id": notification_id} ) query = """ mutation notificationDelete($id: ID!) { @@ -17,16 +35,36 @@ def delete(self, **kwargs): self.api.query(query, {"id": notification_id}) def update_field(self, **kwargs): + """Update a notification field. + + :param id: the notification id + :type id: str + :param input: the input fields to update (list of key/value dicts) + :type input: list + :return: None + :rtype: None + """ notification_id = kwargs.get("id", None) - input = kwargs.get("input", None) - for input_value in input: + field_input = kwargs.get("input", None) + if field_input is None: + return None + for input_value in field_input: if input_value["key"] == "is_read": is_read_value = bool(input_value["value"][0]) self.mark_as_read(notification_id, is_read_value) def mark_as_read(self, notification_id: str, read: bool): + """Mark a notification as read or unread. + + :param notification_id: the notification id + :type notification_id: str + :param read: whether to mark as read (True) or unread (False) + :type read: bool + :return: None + :rtype: None + """ self.api.app_logger.info( - "Marking notifcation as read", + "Marking notification as read", {"notification_id": notification_id, "read": read}, ) query = """ diff --git a/client-python/pycti/api/opencti_api_pir.py b/client-python/pycti/api/opencti_api_pir.py index b3554e87ff41..ede7af1da12f 100644 --- a/client-python/pycti/api/opencti_api_pir.py +++ b/client-python/pycti/api/opencti_api_pir.py @@ -1,12 +1,32 @@ class OpenCTIApiPir: - """OpenCTIApiPir""" + """OpenCTI PIR (Priority Intelligence Requirements) API class. + + Manages PIR flagging operations on elements. + + :param api: instance of :py:class:`~pycti.api.opencti_api_client.OpenCTIApiClient` + :type api: OpenCTIApiClient + """ def __init__(self, api): + """Initialize the OpenCTIApiPir instance. + + :param api: OpenCTI API client instance + :type api: OpenCTIApiClient + """ self.api = api def pir_flag_element(self, **kwargs): - id = kwargs.get("id", None) - input = kwargs.get("input", None) + """Flag an element with a PIR. + + :param id: the element id + :type id: str + :param input: the PIR flag input (PirFlagElementInput format) + :type input: dict + :return: None + :rtype: None + """ + element_id = kwargs.get("id", None) + pir_input = kwargs.get("input", None) query = """ mutation PirFlagElement($id: ID!, $input: PirFlagElementInput!) { pirFlagElement(id: $id, input: $input) @@ -15,14 +35,23 @@ def pir_flag_element(self, **kwargs): self.api.query( query, { - "id": id, - "input": input, + "id": element_id, + "input": pir_input, }, ) def pir_unflag_element(self, **kwargs): - id = kwargs.get("id", None) - input = kwargs.get("input", None) + """Unflag an element from a PIR. + + :param id: the element id + :type id: str + :param input: the PIR unflag input (PirUnflagElementInput format) + :type input: dict + :return: None + :rtype: None + """ + element_id = kwargs.get("id", None) + pir_input = kwargs.get("input", None) query = """ mutation PirUnflagElement($id: ID!, $input: PirUnflagElementInput!) { pirUnflagElement(id: $id, input: $input) @@ -31,7 +60,7 @@ def pir_unflag_element(self, **kwargs): self.api.query( query, { - "id": id, - "input": input, + "id": element_id, + "input": pir_input, }, ) diff --git a/client-python/pycti/api/opencti_api_playbook.py b/client-python/pycti/api/opencti_api_playbook.py index 96677fc0f3b0..7b206e95a4e9 100644 --- a/client-python/pycti/api/opencti_api_playbook.py +++ b/client-python/pycti/api/opencti_api_playbook.py @@ -1,10 +1,32 @@ class OpenCTIApiPlaybook: - """OpenCTIApiPlaybook""" + """OpenCTI Playbook API class. + + Manages playbook operations. + + :param api: instance of :py:class:`~pycti.api.opencti_api_client.OpenCTIApiClient` + :type api: OpenCTIApiClient + """ def __init__(self, api): + """Initialize the OpenCTIApiPlaybook instance. + + :param api: OpenCTI API client instance + :type api: OpenCTIApiClient + """ self.api = api def playbook_step_execution(self, playbook: dict, bundle: str): + """Execute a playbook step. + + :param playbook: the playbook configuration dict containing execution_id, event_id, + execution_start, playbook_id, data_instance_id, step_id, previous_step_id, + and previous_bundle + :type playbook: dict + :param bundle: the STIX bundle to process + :type bundle: str + :return: None + :rtype: None + """ self.api.app_logger.info( "Executing playbook step", { @@ -34,8 +56,15 @@ def playbook_step_execution(self, playbook: dict, bundle: str): ) def delete(self, **kwargs): - id = kwargs.get("id", None) - if id is not None: + """Delete a playbook. + + :param id: the playbook id + :type id: str + :return: None + :rtype: None + """ + playbook_id = kwargs.get("id", None) + if playbook_id is not None: query = """ mutation PlaybookDelete($id: ID!) { playbookDelete(id: $id) @@ -44,11 +73,11 @@ def delete(self, **kwargs): self.api.query( query, { - "id": id, + "id": playbook_id, }, ) else: self.api.app_logger.error( - "[stix_playbook] Cant delete playbook, missing parameters: id" + "[opencti_playbook] Cannot delete playbook, missing parameter: id" ) return None diff --git a/client-python/pycti/api/opencti_api_public_dashboard.py b/client-python/pycti/api/opencti_api_public_dashboard.py index f52c6a3cc66c..ad5e7c5e8a4f 100644 --- a/client-python/pycti/api/opencti_api_public_dashboard.py +++ b/client-python/pycti/api/opencti_api_public_dashboard.py @@ -1,12 +1,30 @@ class OpenCTIApiPublicDashboard: - """OpenCTIApiPublicDashboard""" + """OpenCTI Public Dashboard API class. + + Manages public dashboard operations. + + :param api: instance of :py:class:`~pycti.api.opencti_api_client.OpenCTIApiClient` + :type api: OpenCTIApiClient + """ def __init__(self, api): + """Initialize the OpenCTIApiPublicDashboard instance. + + :param api: OpenCTI API client instance + :type api: OpenCTIApiClient + """ self.api = api def delete(self, **kwargs): - id = kwargs.get("id", None) - if id is not None: + """Delete a public dashboard. + + :param id: the public dashboard id + :type id: str + :return: None + :rtype: None + """ + dashboard_id = kwargs.get("id", None) + if dashboard_id is not None: query = """ mutation PublicDashboardDelete($id: ID!) { publicDashboardDelete(id: $id) @@ -15,11 +33,11 @@ def delete(self, **kwargs): self.api.query( query, { - "id": id, + "id": dashboard_id, }, ) else: - self.opencti.app_logger.error( - "[stix_public_dashboard] Cant delete public dashboard, missing parameters: id" + self.api.app_logger.error( + "[opencti_public_dashboard] Cannot delete public dashboard, missing parameter: id" ) return None diff --git a/client-python/pycti/api/opencti_api_trash.py b/client-python/pycti/api/opencti_api_trash.py index eaf835f209d7..7b076c974a23 100644 --- a/client-python/pycti/api/opencti_api_trash.py +++ b/client-python/pycti/api/opencti_api_trash.py @@ -1,10 +1,28 @@ class OpenCTIApiTrash: - """OpenCTIApiTrash""" + """OpenCTI Trash API class. + + Manages trash/delete operations. + + :param api: instance of :py:class:`~pycti.api.opencti_api_client.OpenCTIApiClient` + :type api: OpenCTIApiClient + """ def __init__(self, api): + """Initialize the OpenCTIApiTrash instance. + + :param api: OpenCTI API client instance + :type api: OpenCTIApiClient + """ self.api = api def restore(self, operation_id: str): + """Restore a deleted item from trash. + + :param operation_id: the delete operation id + :type operation_id: str + :return: None + :rtype: None + """ query = """ mutation DeleteOperationRestore($id: ID!) { deleteOperationRestore(id: $id) @@ -18,15 +36,17 @@ def restore(self, operation_id: str): ) def delete(self, **kwargs): - """Delete a trash item given its ID + """Delete a trash item given its ID. - :param id: ID for the delete operation on the platform. + :param id: ID for the delete operation on the platform :type id: str + :return: None + :rtype: None """ - id = kwargs.get("id", None) - if id is None: + delete_operation_id = kwargs.get("id", None) + if delete_operation_id is None: self.api.admin_logger.error( - "[opencti_trash] Cant confirm delete, missing parameter: id" + "[opencti_trash] Cannot confirm delete, missing parameter: id" ) return None query = """ @@ -37,6 +57,6 @@ def delete(self, **kwargs): self.api.query( query, { - "id": id, + "id": delete_operation_id, }, ) diff --git a/client-python/pycti/api/opencti_api_work.py b/client-python/pycti/api/opencti_api_work.py index dd230742497d..469b5bd51558 100644 --- a/client-python/pycti/api/opencti_api_work.py +++ b/client-python/pycti/api/opencti_api_work.py @@ -1,14 +1,34 @@ import time -from typing import Dict, List +from typing import Dict, List, Optional class OpenCTIApiWork: - """OpenCTIApiJob""" + """OpenCTI Work API class. + + Manages work/job operations for connectors. + + :param api: instance of :py:class:`~pycti.api.opencti_api_client.OpenCTIApiClient` + :type api: OpenCTIApiClient + """ def __init__(self, api): + """Initialize the OpenCTIApiWork instance. + + :param api: OpenCTI API client instance + :type api: OpenCTIApiClient + """ self.api = api def to_received(self, work_id: str, message: str): + """Mark work as received. + + :param work_id: the work id + :type work_id: str + :param message: the message to report + :type message: str + :return: None + :rtype: None + """ if self.api.bundle_send_to_queue: self.api.app_logger.info( "Reporting work update_received", {"work_id": work_id} @@ -23,6 +43,17 @@ def to_received(self, work_id: str, message: str): self.api.query(query, {"id": work_id, "message": message}, True) def to_processed(self, work_id: str, message: str, in_error: bool = False): + """Mark work as processed. + + :param work_id: the work id + :type work_id: str + :param message: the message to report + :type message: str + :param in_error: whether the work completed with error, defaults to False + :type in_error: bool, optional + :return: None + :rtype: None + """ if self.api.bundle_send_to_queue: self.api.app_logger.info( "Reporting work update_processed", {"work_id": work_id} @@ -39,6 +70,13 @@ def to_processed(self, work_id: str, message: str, in_error: bool = False): ) def ping(self, work_id: str): + """Ping a work to keep it alive. + + :param work_id: the work id + :type work_id: str + :return: None + :rtype: None + """ self.api.app_logger.info("Ping work", {"work_id": work_id}) query = """ mutation pingWork($id: ID!) { @@ -50,6 +88,15 @@ def ping(self, work_id: str): self.api.query(query, {"id": work_id}) def report_expectation(self, work_id: str, error): + """Report a work expectation. + + :param work_id: the work id + :type work_id: str + :param error: the error to report (WorkErrorInput format) + :type error: dict + :return: None + :rtype: None + """ if self.api.bundle_send_to_queue: self.api.app_logger.info("Report expectation", {"work_id": work_id}) query = """ @@ -61,10 +108,19 @@ def report_expectation(self, work_id: str, error): """ try: self.api.query(query, {"id": work_id, "error": error}, True) - except: + except Exception: self.api.app_logger.error("Cannot report expectation") def add_expectations(self, work_id: str, expectations: int): + """Add expectations to a work. + + :param work_id: the work id + :type work_id: str + :param expectations: the number of expectations to add + :type expectations: int + :return: None + :rtype: None + """ if self.api.bundle_send_to_queue: self.api.app_logger.info( "Update action expectations", @@ -81,10 +137,19 @@ def add_expectations(self, work_id: str, expectations: int): self.api.query( query, {"id": work_id, "expectations": expectations}, True ) - except: - self.api.app_logger.error("Cannot report expectation") + except Exception: + self.api.app_logger.error("Cannot add expectations") def add_draft_context(self, work_id: str, draft_context: str): + """Add draft context to a work. + + :param work_id: the work id + :type work_id: str + :param draft_context: the draft context to add + :type draft_context: str + :return: None + :rtype: None + """ if self.api.bundle_send_to_queue: self.api.app_logger.info( "Update draft context", @@ -101,10 +166,19 @@ def add_draft_context(self, work_id: str, draft_context: str): self.api.query( query, {"id": work_id, "draftContext": draft_context}, True ) - except: - self.api.app_logger.error("Cannot report draft context") + except Exception: + self.api.app_logger.error("Cannot add draft context") + + def initiate_work(self, connector_id: str, friendly_name: str) -> Optional[str]: + """Initiate a new work for a connector. - def initiate_work(self, connector_id: str, friendly_name: str) -> str: + :param connector_id: the connector id + :type connector_id: str + :param friendly_name: the friendly name for the work + :type friendly_name: str + :return: the work id or None if bundle_send_to_queue is False + :rtype: str or None + """ if self.api.bundle_send_to_queue: self.api.app_logger.info("Initiate work", {"connector_id": connector_id}) query = """ @@ -120,22 +194,33 @@ def initiate_work(self, connector_id: str, friendly_name: str) -> str: True, ) return work["data"]["workAdd"]["id"] + return None def delete_work(self, work_id: str): - query = """ - mutation ConnectorWorksMutation($workId: ID!) { - workEdit(id: $workId) { - delete - } - }""" - work = self.api.query(query, {"workId": work_id}, True) - return work["data"] + """Delete a work. + + .. deprecated:: + Use :meth:`delete` instead. + + :param work_id: the work id + :type work_id: str + :return: the response data + :rtype: dict + """ + return self.delete(id=work_id) def delete(self, **kwargs): - id = kwargs.get("id", None) - if id is None: - self.opencti.admin_logger.error( - "[opencti_work] Cant delete work, missing parameter: id" + """Delete a work by id. + + :param id: the work id + :type id: str + :return: the response data + :rtype: dict or None + """ + work_id = kwargs.get("id", None) + if work_id is None: + self.api.admin_logger.error( + "[opencti_work] Cannot delete work, missing parameter: id" ) return None query = """ @@ -146,13 +231,19 @@ def delete(self, **kwargs): }""" work = self.api.query( query, - {"workId": id}, + {"workId": work_id}, ) return work["data"] def wait_for_work_to_finish(self, work_id: str): + """Wait for a work to finish. + + :param work_id: the work id + :type work_id: str + :return: empty string if error, None otherwise + :rtype: str or None + """ status = "" - cnt = 0 while status != "complete": state = self.get_work(work_id=work_id) if len(state) > 0: @@ -165,9 +256,15 @@ def wait_for_work_to_finish(self, work_id: str): return "" time.sleep(1) - cnt += 1 def get_work(self, work_id: str) -> Dict: + """Get a work by id. + + :param work_id: the work id + :type work_id: str + :return: the work data + :rtype: dict + """ query = """ query WorkQuery($id: ID!) { work(id: $id) { @@ -204,7 +301,14 @@ def get_work(self, work_id: str) -> Dict: result = self.api.query(query, {"id": work_id}, True) return result["data"]["work"] - def get_is_work_alive(self, work_id: str) -> Dict: + def get_is_work_alive(self, work_id: str) -> bool: + """Check if a work is alive. + + :param work_id: the work id + :type work_id: str + :return: whether the work is alive + :rtype: bool + """ query = """ query WorkAliveQuery($id: ID!) { isWorkAlive(id: $id) @@ -214,6 +318,13 @@ def get_is_work_alive(self, work_id: str) -> Dict: return result["data"]["isWorkAlive"] def get_connector_works(self, connector_id: str) -> List[Dict]: + """Get all works for a connector. + + :param connector_id: the connector id + :type connector_id: str + :return: list of work dictionaries sorted by timestamp + :rtype: list[dict] + """ query = """ query ConnectorWorksQuery( $count: Int diff --git a/client-python/pycti/api/opencti_api_workspace.py b/client-python/pycti/api/opencti_api_workspace.py index 2fcee4277b7b..7c7462e0c323 100644 --- a/client-python/pycti/api/opencti_api_workspace.py +++ b/client-python/pycti/api/opencti_api_workspace.py @@ -1,14 +1,32 @@ class OpenCTIApiWorkspace: - """OpenCTIApiWorkspace""" + """OpenCTI Workspace API class. + + Manages workspace operations. + + :param api: instance of :py:class:`~pycti.api.opencti_api_client.OpenCTIApiClient` + :type api: OpenCTIApiClient + """ def __init__(self, api): + """Initialize the OpenCTIApiWorkspace instance. + + :param api: OpenCTI API client instance + :type api: OpenCTIApiClient + """ self.api = api def delete(self, **kwargs): - id = kwargs.get("id", None) - if id is None: + """Delete a workspace. + + :param id: the workspace id + :type id: str + :return: None + :rtype: None + """ + workspace_id = kwargs.get("id", None) + if workspace_id is None: self.api.admin_logger.error( - "[opencti_workspace] Cant delete workspace, missing parameter: id" + "[opencti_workspace] Cannot delete workspace, missing parameter: id" ) return None query = """ @@ -19,6 +37,6 @@ def delete(self, **kwargs): self.api.query( query, { - "id": id, + "id": workspace_id, }, ) diff --git a/client-python/pycti/connector/opencti_connector.py b/client-python/pycti/connector/opencti_connector.py index e25b4eebf797..e6e3c171556a 100644 --- a/client-python/pycti/connector/opencti_connector.py +++ b/client-python/pycti/connector/opencti_connector.py @@ -1,37 +1,81 @@ -from enum import Enum +"""OpenCTI Connector module. + +This module defines the connector types and the main OpenCTIConnector class +used to register and configure connectors with the OpenCTI platform. +""" -# Scope definition -# EXTERNAL_IMPORT = None -# INTERNAL_IMPORT_FILE = Files mime types to support (application/json, ...) -# INTERNAL_ENRICHMENT = Entity types to support (Report, Hash, ...) -# INTERNAL_EXPORT_FILE = Files mime types to generate (application/pdf, ...) +from enum import Enum class ConnectorType(Enum): - EXTERNAL_IMPORT = "EXTERNAL_IMPORT" # From remote sources to OpenCTI stix2 - INTERNAL_IMPORT_FILE = ( - "INTERNAL_IMPORT_FILE" # From OpenCTI file system to OpenCTI stix2 - ) - INTERNAL_ENRICHMENT = "INTERNAL_ENRICHMENT" # From OpenCTI stix2 to OpenCTI stix2 - INTERNAL_ANALYSIS = "INTERNAL_ANALYSIS" # From OpenCTI file system or OpenCTI stix2 to OpenCTI file system - INTERNAL_EXPORT_FILE = ( - "INTERNAL_EXPORT_FILE" # From OpenCTI stix2 to OpenCTI file system - ) - STREAM = "STREAM" # Read the stream and do something + """Enumeration of OpenCTI connector types. + + Each connector type defines a specific data flow pattern: + + - EXTERNAL_IMPORT: Imports data from remote sources into OpenCTI as STIX2 + - INTERNAL_IMPORT_FILE: Converts files from OpenCTI file system to STIX2 + - INTERNAL_ENRICHMENT: Enriches existing STIX2 data with additional information + - INTERNAL_ANALYSIS: Analyzes files or STIX2 data and produces file output + - INTERNAL_EXPORT_FILE: Exports STIX2 data to files in OpenCTI file system + - STREAM: Reads the event stream and performs custom actions + + Scope definition varies by type: + - EXTERNAL_IMPORT: None (imports everything) + - INTERNAL_IMPORT_FILE: MIME types to support (e.g., application/json) + - INTERNAL_ENRICHMENT: Entity types to support (e.g., Report, Hash) + - INTERNAL_EXPORT_FILE: MIME types to generate (e.g., application/pdf) + """ + + EXTERNAL_IMPORT = "EXTERNAL_IMPORT" + INTERNAL_IMPORT_FILE = "INTERNAL_IMPORT_FILE" + INTERNAL_ENRICHMENT = "INTERNAL_ENRICHMENT" + INTERNAL_ANALYSIS = "INTERNAL_ANALYSIS" + INTERNAL_EXPORT_FILE = "INTERNAL_EXPORT_FILE" + STREAM = "STREAM" class OpenCTIConnector: - """Main class for OpenCTI connector + """Main class for OpenCTI connector registration and configuration. - :param connector_id: id for the connector (valid uuid4) + This class represents a connector instance that can be registered with + the OpenCTI platform. It holds all configuration parameters needed for + the connector to operate. + + :param connector_id: Unique identifier for the connector (valid UUID4) :type connector_id: str - :param connector_name: name for the connector + :param connector_name: Human-readable name for the connector :type connector_name: str - :param connector_type: valid OpenCTI connector type (see `ConnectorType`) + :param connector_type: Type of connector (see :class:`ConnectorType`) :type connector_type: str - :param scope: connector scope + :param scope: Connector scope as a comma-separated string (e.g., "Report,Indicator") :type scope: str - :raises ValueError: if the connector type is not valid + :param auto: Whether the connector runs automatically on matching entities + :type auto: bool + :param only_contextual: Whether the connector only processes contextual data + :type only_contextual: bool + :param playbook_compatible: Whether the connector can be used in playbooks + :type playbook_compatible: bool + :param auto_update: Whether to automatically update existing entities + :type auto_update: bool + :param enrichment_resolution: Strategy for resolving enrichment conflicts + :type enrichment_resolution: str + :param listen_callback_uri: Optional callback URI for API-based listening + :type listen_callback_uri: str or None + + :raises ValueError: If the connector type is not a valid ConnectorType value + + Example: + >>> connector = OpenCTIConnector( + ... connector_id="550e8400-e29b-41d4-a716-446655440000", + ... connector_name="My Connector", + ... connector_type="EXTERNAL_IMPORT", + ... scope="Report,Indicator", + ... auto=False, + ... only_contextual=False, + ... playbook_compatible=True, + ... auto_update=False, + ... enrichment_resolution="none" + ... ) """ def __init__( @@ -47,12 +91,35 @@ def __init__( enrichment_resolution: str, listen_callback_uri=None, ): + """Initialize the OpenCTIConnector instance. + + :param connector_id: Unique identifier for the connector (valid UUID4) + :type connector_id: str + :param connector_name: Human-readable name for the connector + :type connector_name: str + :param connector_type: Type of connector (see :class:`ConnectorType`) + :type connector_type: str + :param scope: Connector scope as a comma-separated string + :type scope: str + :param auto: Whether the connector runs automatically + :type auto: bool + :param only_contextual: Whether to process only contextual data + :type only_contextual: bool + :param playbook_compatible: Whether the connector works with playbooks + :type playbook_compatible: bool + :param auto_update: Whether to auto-update existing entities + :type auto_update: bool + :param enrichment_resolution: Enrichment conflict resolution strategy + :type enrichment_resolution: str + :param listen_callback_uri: Optional callback URI for API listening + :type listen_callback_uri: str or None + + :raises ValueError: If connector_type is not a valid ConnectorType + """ self.id = connector_id self.name = connector_name self.type = ConnectorType(connector_type) - if self.type is None: - raise ValueError("Invalid connector type: " + connector_type) - if scope and len(scope) > 0: + if scope: self.scope = scope.split(",") else: self.scope = [] @@ -64,10 +131,17 @@ def __init__( self.listen_callback_uri = listen_callback_uri def to_input(self) -> dict: - """connector input to use in API query + """Convert connector configuration to API input format. - :return: dict with connector data + Generates a dictionary structure suitable for use in GraphQL API + queries to register or update the connector. + + :return: Dictionary containing connector data wrapped in an "input" key :rtype: dict + + Example: + >>> connector.to_input() + {'input': {'id': '...', 'name': 'My Connector', ...}} """ return { "input": { diff --git a/client-python/pycti/connector/opencti_connector_helper.py b/client-python/pycti/connector/opencti_connector_helper.py index 052b29907902..3025ec248cc7 100644 --- a/client-python/pycti/connector/opencti_connector_helper.py +++ b/client-python/pycti/connector/opencti_connector_helper.py @@ -1,3 +1,22 @@ +"""OpenCTI Connector Helper module. + +This module provides the main helper class and utilities for building OpenCTI +connectors. It handles connector registration, message queue communication, +stream listening, scheduling, and STIX2 bundle processing. + +Key components: + - OpenCTIConnectorHelper: Main class for connector development + - ListenQueue: Handles RabbitMQ message consumption + - ListenStream: Handles SSE stream consumption + - PingAlive: Maintains connector heartbeat with the platform + - ConnectorInfo: Stores connector runtime information + +Example: + >>> from pycti import OpenCTIConnectorHelper + >>> helper = OpenCTIConnectorHelper(config) + >>> helper.listen(callback_function) +""" + import asyncio import base64 import copy @@ -32,23 +51,36 @@ from pycti.utils.opencti_stix2_splitter import OpenCTIStix2Splitter TRUTHY: List[str] = ["yes", "true", "True"] +"""List of string values considered as boolean True.""" + FALSY: List[str] = ["no", "false", "False"] +"""List of string values considered as boolean False.""" app = FastAPI() -def killProgramHook(etype, value, tb): - """Exception hook to terminate the program. +def killProgramHook(etype, value, tb) -> None: + """Exception hook to terminate the program on unhandled exceptions. - :param etype: Exception type - :param value: Exception value - :param tb: Traceback object + This function is used as a system exception hook to ensure the program + terminates cleanly when an unhandled exception occurs, particularly + useful for background threads. + + :param etype: The exception type (class) + :type etype: type + :param value: The exception instance + :type value: BaseException + :param tb: The traceback object + :type tb: types.TracebackType """ os.kill(os.getpid(), signal.SIGTERM) -def start_loop(loop): - """Start an asyncio event loop. +def start_loop(loop) -> None: + """Start an asyncio event loop and run it forever. + + Sets the given event loop as the current loop for the thread and + runs it indefinitely until stopped. :param loop: The asyncio event loop to start :type loop: asyncio.AbstractEventLoop @@ -60,19 +92,48 @@ def start_loop(loop): def get_config_variable( env_var: str, yaml_path: List, - config: Dict = {}, + config: Optional[Dict] = None, isNumber: Optional[bool] = False, default=None, required=False, ) -> Union[bool, int, None, str]: - """[summary] + """Retrieve a configuration variable from environment or YAML config. + + Looks up configuration values with the following precedence: + 1. Environment variable (highest priority) + 2. YAML configuration file + 3. Default value (lowest priority) - :param env_var: environment variable name - :param yaml_path: path to yaml config - :param config: client config dict, defaults to {} - :param isNumber: specify if the variable is a number, defaults to False - :param default: default value + Boolean string values ("yes", "true", "True", "no", "false", "False") + are automatically converted to Python bool. + + :param env_var: Name of the environment variable to check + :type env_var: str + :param yaml_path: Two-element list specifying [section, key] in YAML config + :type yaml_path: List[str] + :param config: Configuration dictionary loaded from YAML file + :type config: Dict + :param isNumber: If True, convert the value to integer + :type isNumber: bool + :param default: Default value if not found in env or config + :type default: any + :param required: If True and no value found, raise ValueError + :type required: bool + + :return: The configuration value as bool, int, str, or None + :rtype: Union[bool, int, None, str] + + :raises ValueError: If required=True and no value is found + + Example: + >>> get_config_variable("OPENCTI_URL", ["opencti", "url"], config) + 'http://localhost:8080' + >>> get_config_variable("CONNECTOR_LOG_LEVEL", ["connector", "log_level"], + ... config, default="INFO") + 'INFO' """ + if config is None: + config = {} if os.getenv(env_var) is not None: result = os.getenv(env_var) @@ -105,26 +166,27 @@ def get_config_variable( def normalize_email_prefix(email: str) -> str: - """ - Normalize the prefix (local part) of an email address by replacing - invalid characters with hyphens. + """Normalize the local part of an email address by replacing invalid characters. + + Replaces any characters not valid in email prefixes with hyphens. + Valid characters include: a-z, A-Z, 0-9, and special chars: . _ + - + Consecutive hyphens are collapsed and leading/trailing hyphens are removed. - Valid characters in email prefix: a-z, A-Z, 0-9, and special chars: . _ + - - All other characters are replaced with '-' + :param email: Email address to normalize + :type email: str - Args: - email: Email address to normalize + :return: Normalized email address with valid local part + :rtype: str - Returns: - Normalized email address + :raises ValueError: If the email address does not contain an '@' symbol - Examples: + Example: >>> normalize_email_prefix("john.doe@example.com") 'john.doe@example.com' >>> normalize_email_prefix("john@doe@example.com") 'john-doe@example.com' - >>> normalize_email_prefix("user!name#test@example.com") - 'user-name-test@example.com' + >>> normalize_email_prefix("user!name@domain.com") + 'user-name@domain.com' """ if "@" not in email: raise ValueError("Invalid email: missing '@' symbol") @@ -149,24 +211,43 @@ def normalize_email_prefix(email: str) -> str: return f"{normalized_prefix}@{domain}" -def is_memory_certificate(certificate): +def is_memory_certificate(certificate: str) -> bool: """Check if a certificate is provided as a PEM string in memory. - :param certificate: The certificate data to check + Determines whether the certificate data is an in-memory PEM-formatted + string (starting with "-----BEGIN") rather than a file path. + + :param certificate: The certificate data to check (PEM string or file path) :type certificate: str - :return: True if the certificate is a PEM string, False otherwise + + :return: True if the certificate is a PEM string, False if it's a file path :rtype: bool + + Example: + >>> is_memory_certificate("-----BEGIN CERTIFICATE-----\\n...") + True + >>> is_memory_certificate("/path/to/cert.pem") + False """ return certificate.startswith("-----BEGIN") -def ssl_verify_locations(ssl_context, certdata): - """Load certificate verification locations into SSL context. +def ssl_verify_locations(ssl_context: ssl.SSLContext, certdata: Optional[str]) -> None: + """Load CA certificate verification locations into an SSL context. + + Configures the SSL context with certificate authority (CA) certificates + for verifying peer certificates. Supports both file paths and in-memory + PEM-formatted certificates. :param ssl_context: The SSL context to configure :type ssl_context: ssl.SSLContext - :param certdata: Certificate data (file path or PEM string) + :param certdata: CA certificate data as file path or PEM string, or None to skip :type certdata: str or None + + Example: + >>> ssl_ctx = ssl.create_default_context() + >>> ssl_verify_locations(ssl_ctx, "/path/to/ca-bundle.crt") + >>> ssl_verify_locations(ssl_ctx, "-----BEGIN CERTIFICATE-----\\n...") """ if certdata is None: return @@ -177,16 +258,27 @@ def ssl_verify_locations(ssl_context, certdata): ssl_context.load_verify_locations(cafile=certdata) -def data_to_temp_file(data): +def data_to_temp_file(data: str) -> str: """Write data to a temporary file securely. - Creates a temporary file in the most secure manner possible. - The file is readable and writable only by the creating user ID. + Creates a temporary file with secure permissions (readable and writable + only by the creating user). The file descriptor is not inherited by + child processes. + + .. note:: + The caller is responsible for deleting the temporary file when + it is no longer needed. - :param data: The data to write to the temporary file + :param data: The string data to write to the temporary file :type data: str - :return: Path to the created temporary file + + :return: Absolute path to the created temporary file :rtype: str + + Example: + >>> path = data_to_temp_file("-----BEGIN PRIVATE KEY-----\\n...") + >>> # Use the file... + >>> os.unlink(path) # Clean up when done """ # The file is readable and writable only by the creating user ID. # If the operating system uses permission bits to indicate whether a @@ -195,21 +287,34 @@ def data_to_temp_file(data): file_descriptor, file_path = tempfile.mkstemp() with os.fdopen(file_descriptor, "w") as open_file: open_file.write(data) - open_file.close() return file_path -def ssl_cert_chain(ssl_context, cert_data, key_data, passphrase): - """Load certificate chain into SSL context. +def ssl_cert_chain( + ssl_context: ssl.SSLContext, + cert_data: Optional[str], + key_data: Optional[str], + passphrase: Optional[str], +) -> None: + """Load a certificate chain and private key into an SSL context. + + Configures the SSL context with a client certificate and private key + for mutual TLS authentication. Supports both file paths and in-memory + PEM-formatted certificates/keys. Temporary files are created and + cleaned up automatically when using in-memory data. :param ssl_context: The SSL context to configure :type ssl_context: ssl.SSLContext - :param cert_data: Certificate data (file path or PEM string) + :param cert_data: Certificate data as file path or PEM string, or None to skip :type cert_data: str or None - :param key_data: Private key data (file path or PEM string) + :param key_data: Private key data as file path or PEM string, or None :type key_data: str or None - :param passphrase: Passphrase for the private key + :param passphrase: Passphrase for encrypted private key, or None if unencrypted :type passphrase: str or None + + Example: + >>> ssl_ctx = ssl.create_default_context() + >>> ssl_cert_chain(ssl_ctx, "/path/to/cert.pem", "/path/to/key.pem", None) """ if cert_data is None: return @@ -236,12 +341,22 @@ def ssl_cert_chain(ssl_context, cert_data, key_data, passphrase): os.unlink(key_file_path) -def create_callback_ssl_context(config) -> ssl.SSLContext: - """Create SSL context for API callback server. +def create_callback_ssl_context(config: Dict) -> ssl.SSLContext: + """Create an SSL context for the API callback server. + + Creates and configures an SSL context suitable for the HTTPS callback + server used in API listen protocol mode. Loads certificate chain from + configuration. - :param config: Configuration dictionary - :type config: dict - :return: Configured SSL context + Configuration keys used: + - LISTEN_PROTOCOL_API_SSL_KEY: Path or PEM string for SSL private key + - LISTEN_PROTOCOL_API_SSL_CERT: Path or PEM string for SSL certificate + - LISTEN_PROTOCOL_API_SSL_PASSPHRASE: Optional passphrase for private key + + :param config: Configuration dictionary containing SSL settings + :type config: Dict + + :return: Configured SSL context for client authentication :rtype: ssl.SSLContext """ listen_protocol_api_ssl_key = get_config_variable( @@ -272,12 +387,24 @@ def create_callback_ssl_context(config) -> ssl.SSLContext: return ssl_context -def create_mq_ssl_context(config) -> ssl.SSLContext: - """Create SSL context for message queue connections. +def create_mq_ssl_context(config: Dict) -> ssl.SSLContext: + """Create an SSL context for RabbitMQ message queue connections. + + Creates and configures an SSL context for secure connections to RabbitMQ. + Supports CA verification, client certificates, and optional certificate + verification bypass. - :param config: Configuration dictionary - :type config: dict - :return: Configured SSL context for MQ connections + Configuration keys used: + - MQ_USE_SSL_CA: CA certificate for server verification + - MQ_USE_SSL_CERT: Client certificate for mutual TLS + - MQ_USE_SSL_KEY: Client private key for mutual TLS + - MQ_USE_SSL_REJECT_UNAUTHORIZED: Whether to verify server certificate + - MQ_USE_SSL_PASSPHRASE: Optional passphrase for private key + + :param config: Configuration dictionary containing MQ SSL settings + :type config: Dict + + :return: Configured SSL context for RabbitMQ connections :rtype: ssl.SSLContext """ use_ssl_ca = get_config_variable("MQ_USE_SSL_CA", ["mq", "use_ssl_ca"], config) @@ -308,29 +435,74 @@ def create_mq_ssl_context(config) -> ssl.SSLContext: class ListenQueue(threading.Thread): - """Main class for the ListenQueue used in OpenCTIConnectorHelper + """Thread class for consuming messages from RabbitMQ or HTTP API. + + Handles message consumption from either RabbitMQ (AMQP protocol) or + an HTTP API endpoint, depending on the configured listen protocol. + Messages are processed through a callback function provided at initialization. + + This class supports two listen protocols: + - AMQP: Connects to RabbitMQ and consumes messages from a queue + - API: Starts an HTTP server and receives messages via POST requests - :param helper: instance of a `OpenCTIConnectorHelper` class + :param helper: The OpenCTIConnectorHelper instance :type helper: OpenCTIConnectorHelper - :param config: dict containing client config + :param opencti_token: Authentication token for OpenCTI API + :type opencti_token: str + :param config: Global configuration dictionary :type config: Dict - :param callback: callback function to process queue - :type callback: callable + :param connector_config: Connector-specific configuration from registration + :type connector_config: Dict + :param applicant_id: ID of the user/connector making requests + :type applicant_id: str + :param listen_protocol: Protocol to use ("AMQP" or "API") + :type listen_protocol: str + :param listen_protocol_api_ssl: Whether to use SSL for API protocol + :type listen_protocol_api_ssl: bool + :param listen_protocol_api_path: URL path for API endpoint + :type listen_protocol_api_path: str + :param listen_protocol_api_port: Port for API server + :type listen_protocol_api_port: int + :param callback: Function to call when processing messages + :type callback: Callable[[Dict], str] """ def __init__( self, helper, - opencti_token, + opencti_token: str, config: Dict, connector_config: Dict, - applicant_id, - listen_protocol, - listen_protocol_api_ssl, - listen_protocol_api_path, - listen_protocol_api_port, - callback, + applicant_id: str, + listen_protocol: str, + listen_protocol_api_ssl: bool, + listen_protocol_api_path: str, + listen_protocol_api_port: int, + callback: Callable[[Dict], str], ) -> None: + """Initialize the ListenQueue thread. + + :param helper: The OpenCTIConnectorHelper instance + :type helper: OpenCTIConnectorHelper + :param opencti_token: Authentication token for OpenCTI API + :type opencti_token: str + :param config: Global configuration dictionary + :type config: Dict + :param connector_config: Connector configuration from registration + :type connector_config: Dict + :param applicant_id: ID of the user/connector making requests + :type applicant_id: str + :param listen_protocol: Protocol to use ("AMQP" or "API") + :type listen_protocol: str + :param listen_protocol_api_ssl: Whether to use SSL for API protocol + :type listen_protocol_api_ssl: bool + :param listen_protocol_api_path: URL path for API endpoint + :type listen_protocol_api_path: str + :param listen_protocol_api_port: Port for API server + :type listen_protocol_api_port: int + :param callback: Function to process received messages + :type callback: Callable[[Dict], str] + """ threading.Thread.__init__(self) self.pika_credentials = None self.pika_parameters = None @@ -357,16 +529,20 @@ def __init__( # noinspection PyUnusedLocal def _process_message(self, channel, method, properties, body) -> None: - """process a message from the rabbit queue - - :param channel: channel instance - :type channel: callable - :param method: message methods - :type method: callable - :param properties: unused - :type properties: str - :param body: message body (data) - :type body: str or bytes or bytearray + """Process a message from the RabbitMQ queue. + + Acknowledges the message immediately before processing to prevent + infinite re-delivery if the connector fails. Spawns a separate thread + for data handling and maintains the connection alive during processing. + + :param channel: The RabbitMQ channel instance + :type channel: pika.channel.Channel + :param method: Message delivery method with routing info and delivery tag + :type method: pika.spec.Basic.Deliver + :param properties: Message properties (unused) + :type properties: pika.spec.BasicProperties + :param body: The message body containing JSON data + :type body: bytes """ json_data = json.loads(body) # Message should be ack before processing as we don't own the processing @@ -405,7 +581,21 @@ def _set_draft_id(self, draft_id): self.helper.api.set_draft_id(draft_id) self.helper.api_impersonate.set_draft_id(draft_id) - def _data_handler(self, json_data) -> None: + def _data_handler(self, json_data: Dict) -> None: + """Process incoming message data and execute the callback. + + Handles the full message processing workflow including: + - Extracting event data and entity information + - Setting up draft and work contexts + - Resolving enrichment entity data for enrichment connectors + - Handling playbook execution context + - Managing organization sharing propagation + - Executing the user-provided callback function + + :param json_data: The parsed JSON message data containing event and internal info + :type json_data: Dict + """ + work_id = None # Execute the callback try: event_data = json_data["event"] @@ -448,7 +638,7 @@ def _data_handler(self, json_data) -> None: opencti_entity = do_read(id=entity_id, withFiles=True) if opencti_entity is None: raise ValueError( - "Unable to read/access to the entity, please check that the connector permission" + "Unable to read/access the entity, please check the connector permissions" ) event_data["enrichment_entity"] = opencti_entity # Handle action vs playbook behavior @@ -513,7 +703,7 @@ def _data_handler(self, json_data) -> None: ) ) - # Handle applicant_id for in-personalization + # Handle applicant_id for impersonation self.helper.applicant_id = self.connector_applicant_id self.helper.api_impersonate.set_applicant_id_header( self.connector_applicant_id @@ -542,13 +732,30 @@ def _data_handler(self, json_data) -> None: if work_id: try: self.helper.api.work.to_processed(work_id, str(e), True) - except: # pylint: disable=bare-except + except Exception: # pylint: disable=broad-except self.helper.metric.inc("error_count") self.helper.connector_logger.error( "Failing reporting the processing" ) - async def _http_process_callback(self, request: Request): + async def _http_process_callback(self, request: Request) -> JSONResponse: + """Handle incoming HTTP POST requests for API listen protocol. + + Validates the request authentication using Bearer token and processes + the JSON payload through the data handler. + + :param request: The incoming FastAPI request object + :type request: Request + + :return: JSON response with status code and message + :rtype: JSONResponse + + Response codes: + - 202: Message successfully received and queued for processing + - 400: Invalid JSON payload + - 401: Invalid or missing authentication credentials + - 500: Error occurred during message processing + """ # 01. Check the authentication authorization: str = request.headers.get("Authorization", "") items = authorization.split() if isinstance(authorization, str) else [] @@ -587,12 +794,22 @@ async def _http_process_callback(self, request: Request): ) def run(self) -> None: + """Execute the message listening thread. + + Starts the appropriate listener based on the configured protocol: + - AMQP: Connects to RabbitMQ and consumes messages from the queue + - API: Starts a FastAPI/Uvicorn HTTP server to receive messages + + The thread runs until stopped via the stop() method or an error occurs. + + :raises ValueError: If an unsupported listen protocol is configured + """ if self.listen_protocol == "AMQP": self.helper.connector_logger.info("Starting ListenQueue thread") while not self.exit_event.is_set(): try: self.helper.connector_logger.info( - "ListenQueue connecting to rabbitMq." + "ListenQueue connecting to RabbitMQ." ) # Connect the broker self.pika_credentials = pika.PlainCredentials( @@ -616,7 +833,7 @@ def run(self) -> None: self.pika_connection = pika.BlockingConnection(self.pika_parameters) self.channel = self.pika_connection.channel() try: - # confirm_delivery is only for cluster mode rabbitMQ + # confirm_delivery is only for cluster mode RabbitMQ # when not in cluster mode this line raise an exception self.channel.confirm_delivery() except Exception as err: # pylint: disable=broad-except @@ -679,16 +896,54 @@ def stop(self): class PingAlive(threading.Thread): + """Daemon thread that maintains connector heartbeat with OpenCTI platform. + + Periodically pings the OpenCTI API to indicate the connector is alive + and synchronizes connector state between local and remote instances. + + :param connector_logger: Logger instance for the connector + :type connector_logger: logging.Logger + :param connector_id: Unique identifier of the connector + :type connector_id: str + :param api: OpenCTI API client instance + :type api: OpenCTIApiClient + :param get_state: Function to retrieve current connector state + :type get_state: Callable[[], Optional[Dict]] + :param set_state: Function to update connector state + :type set_state: Callable[[str], None] + :param metric: Metric handler for recording ping statistics + :type metric: OpenCTIMetricHandler + :param connector_info: ConnectorInfo instance with runtime details + :type connector_info: ConnectorInfo + """ + def __init__( self, connector_logger, - connector_id, + connector_id: str, api, - get_state, - set_state, + get_state: Callable[[], Optional[Dict]], + set_state: Callable[[str], None], metric, connector_info, ) -> None: + """Initialize the PingAlive daemon thread. + + :param connector_logger: Logger instance for the connector + :type connector_logger: logging.Logger + :param connector_id: Unique identifier of the connector + :type connector_id: str + :param api: OpenCTI API client instance + :type api: OpenCTIApiClient + :param get_state: Function to retrieve current connector state + :type get_state: Callable[[], Optional[Dict]] + :param set_state: Function to update connector state + :type set_state: Callable[[str], None] + :param metric: Metric handler for recording ping statistics + :type metric: OpenCTIMetricHandler + :param connector_info: ConnectorInfo instance with runtime details + :type connector_info: ConnectorInfo + """ threading.Thread.__init__(self, daemon=True) self.connector_logger = connector_logger self.connector_id = connector_id @@ -701,6 +956,18 @@ def __init__( self.connector_info = connector_info def ping(self) -> None: + """Execute the ping loop to maintain connector heartbeat. + + Continuously pings the OpenCTI API every 40 seconds to: + - Signal that the connector is alive + - Send current connector state and info + - Receive and apply any remote state updates + + If the remote state differs from local state, the local state + is updated to match. This allows state resets from the UI. + + The loop continues until the exit_event is set. + """ while not self.exit_event.is_set(): try: self.connector_logger.debug("PingAlive running.") @@ -736,22 +1003,55 @@ def ping(self) -> None: self.exit_event.wait(40) def run(self) -> None: + """Start the PingAlive thread execution. + + Entry point for the thread that initiates the ping loop. + """ self.connector_logger.info("Starting PingAlive thread") self.ping() def stop(self) -> None: + """Stop the PingAlive thread gracefully. + + Sets the exit event to signal the ping loop to terminate. + """ self.connector_logger.info("Preparing PingAlive for clean shutdown") self.exit_event.set() class StreamAlive(threading.Thread): - def __init__(self, helper, q) -> None: + """Watchdog thread that monitors SSE stream health via heartbeat messages. + + Monitors a queue for heartbeat signals from the stream listener. + If no heartbeat is received within 45 seconds, the connector is + stopped to allow for reconnection. + + :param helper: The OpenCTIConnectorHelper instance + :type helper: OpenCTIConnectorHelper + :param q: Queue for receiving heartbeat signals from stream listener + :type q: Queue + """ + + def __init__(self, helper, q: Queue) -> None: + """Initialize the StreamAlive watchdog thread. + + :param helper: The OpenCTIConnectorHelper instance + :type helper: OpenCTIConnectorHelper + :param q: Queue for receiving heartbeat signals + :type q: Queue + """ threading.Thread.__init__(self) self.helper = helper self.q = q self.exit_event = threading.Event() def run(self) -> None: + """Execute the stream health monitoring loop. + + Checks every 5 seconds for heartbeat signals in the queue. + If no signal is received for 45 seconds, terminates the connector + to trigger a reconnection. + """ try: self.helper.connector_logger.info("Starting StreamAlive thread") time_since_last_heartbeat = 0 @@ -779,25 +1079,84 @@ def run(self) -> None: sys.excepthook(*sys.exc_info()) def stop(self) -> None: + """Stop the StreamAlive watchdog thread gracefully. + + Sets the exit event to signal the monitoring loop to terminate. + """ self.helper.connector_logger.info("Preparing StreamAlive for clean shutdown") self.exit_event.set() class ListenStream(threading.Thread): + """Thread class for consuming events from OpenCTI SSE stream. + + Connects to an OpenCTI event stream and processes events through + a callback function. Supports recovery from a specific point in time + and various filtering options. + + :param helper: The OpenCTIConnectorHelper instance + :type helper: OpenCTIConnectorHelper + :param callback: Function to call for each stream event + :type callback: Callable + :param url: Base URL for the stream endpoint + :type url: str + :param token: Authentication token for the stream + :type token: str + :param verify_ssl: Whether to verify SSL certificates + :type verify_ssl: bool + :param start_timestamp: Timestamp to start reading from (format: "timestamp-0") + :type start_timestamp: str or None + :param live_stream_id: ID of the specific stream to connect to + :type live_stream_id: str or None + :param listen_delete: Whether to receive delete events + :type listen_delete: bool + :param no_dependencies: Whether to exclude dependency objects + :type no_dependencies: bool + :param recover_iso_date: ISO date to recover events from + :type recover_iso_date: str or None + :param with_inferences: Whether to include inferred relationships + :type with_inferences: bool + """ + def __init__( self, helper, - callback, - url, - token, - verify_ssl, - start_timestamp, - live_stream_id, - listen_delete, - no_dependencies, - recover_iso_date, - with_inferences, + callback: Callable, + url: str, + token: str, + verify_ssl: bool, + start_timestamp: Optional[str], + live_stream_id: Optional[str], + listen_delete: bool, + no_dependencies: bool, + recover_iso_date: Optional[str], + with_inferences: bool, ) -> None: + """Initialize the ListenStream thread. + + :param helper: The OpenCTIConnectorHelper instance + :type helper: OpenCTIConnectorHelper + :param callback: Function to process stream events + :type callback: Callable + :param url: Base URL for the stream endpoint + :type url: str + :param token: Authentication token + :type token: str + :param verify_ssl: Whether to verify SSL certificates + :type verify_ssl: bool + :param start_timestamp: Starting timestamp for stream position + :type start_timestamp: str or None + :param live_stream_id: Specific stream ID to connect to + :type live_stream_id: str or None + :param listen_delete: Whether to receive delete events + :type listen_delete: bool + :param no_dependencies: Whether to exclude dependencies + :type no_dependencies: bool + :param recover_iso_date: ISO date for event recovery + :type recover_iso_date: str or None + :param with_inferences: Whether to include inferences + :type with_inferences: bool + """ threading.Thread.__init__(self) self.helper = helper self.callback = callback @@ -813,6 +1172,17 @@ def __init__( self.exit_event = threading.Event() def run(self) -> None: # pylint: disable=too-many-branches + """Execute the stream listening loop. + + Connects to the OpenCTI SSE stream and processes events: + - Initializes or restores stream position from connector state + - Starts a StreamAlive watchdog for health monitoring + - Processes heartbeat, connected, and data events + - Updates connector state with latest event ID + - Calls the callback function for each data event + + The loop continues until stopped or an error occurs. + """ try: self.helper.connector_logger.info("Starting ListenStream thread") current_state = self.helper.get_state() @@ -850,7 +1220,7 @@ def run(self) -> None: # pylint: disable=too-many-branches stream_alive.start() # Computing args building live_stream_url = self.url - # In case no recover is explicitely set + # In case no recover is explicitly set if recover_until is not False and recover_until not in [ "no", "none", @@ -928,6 +1298,31 @@ def stop(self): class ConnectorInfo: + """Container for connector runtime information and status. + + Stores runtime metrics and status information about the connector, + which is sent to the OpenCTI platform during ping operations. + + :param run_and_terminate: Whether connector runs once and terminates + :type run_and_terminate: bool + :param buffering: Whether connector is currently buffering due to queue limits + :type buffering: bool + :param queue_threshold: Maximum allowed queue size in MB before buffering + :type queue_threshold: float + :param queue_messages_size: Current size of queued messages in MB + :type queue_messages_size: float + :param next_run_datetime: Scheduled datetime for next connector run + :type next_run_datetime: datetime or None + :param last_run_datetime: Datetime of the last connector run + :type last_run_datetime: datetime or None + + Example: + >>> info = ConnectorInfo(run_and_terminate=False, queue_threshold=500.0) + >>> info.buffering = True + >>> info.queue_messages_size = 450.0 + >>> details = info.all_details + """ + def __init__( self, run_and_terminate: bool = False, @@ -937,6 +1332,21 @@ def __init__( next_run_datetime: datetime = None, last_run_datetime: datetime = None, ): + """Initialize ConnectorInfo with runtime parameters. + + :param run_and_terminate: Whether connector runs once and terminates + :type run_and_terminate: bool + :param buffering: Whether connector is buffering + :type buffering: bool + :param queue_threshold: Maximum queue size in MB + :type queue_threshold: float + :param queue_messages_size: Current queue size in MB + :type queue_messages_size: float + :param next_run_datetime: Next scheduled run time + :type next_run_datetime: datetime or None + :param last_run_datetime: Last run time + :type last_run_datetime: datetime or None + """ self._run_and_terminate = run_and_terminate self._buffering = buffering self._queue_threshold = queue_threshold @@ -962,10 +1372,15 @@ def all_details(self): @property def run_and_terminate(self) -> bool: + """Get the run_and_terminate flag. + + :return: Whether the connector runs once and terminates + :rtype: bool + """ return self._run_and_terminate @run_and_terminate.setter - def run_and_terminate(self, value): + def run_and_terminate(self, value: bool) -> None: """Set the run_and_terminate flag. :param value: Whether the connector should run once and terminate @@ -975,10 +1390,15 @@ def run_and_terminate(self, value): @property def buffering(self) -> bool: + """Get the buffering status. + + :return: Whether the connector is currently buffering + :rtype: bool + """ return self._buffering @buffering.setter - def buffering(self, value): + def buffering(self, value: bool) -> None: """Set the buffering status. :param value: Whether the connector is currently buffering @@ -988,10 +1408,15 @@ def buffering(self, value): @property def queue_threshold(self) -> float: + """Get the queue threshold value. + + :return: Maximum allowed queue size in MB + :rtype: float + """ return self._queue_threshold @queue_threshold.setter - def queue_threshold(self, value): + def queue_threshold(self, value: float) -> None: """Set the queue threshold value. :param value: The queue size threshold in MB @@ -1001,10 +1426,15 @@ def queue_threshold(self, value): @property def queue_messages_size(self) -> float: + """Get the current queue messages size. + + :return: Current size of queued messages in MB + :rtype: float + """ return self._queue_messages_size @queue_messages_size.setter - def queue_messages_size(self, value): + def queue_messages_size(self, value: float) -> None: """Set the current queue messages size. :param value: The current size of messages in the queue in MB @@ -1014,10 +1444,15 @@ def queue_messages_size(self, value): @property def next_run_datetime(self) -> datetime: + """Get the next scheduled run datetime. + + :return: Datetime for the next scheduled run, or None if not scheduled + :rtype: datetime or None + """ return self._next_run_datetime @next_run_datetime.setter - def next_run_datetime(self, value): + def next_run_datetime(self, value: datetime) -> None: """Set the next scheduled run datetime. :param value: The datetime for the next scheduled run @@ -1027,10 +1462,15 @@ def next_run_datetime(self, value): @property def last_run_datetime(self) -> datetime: + """Get the last run datetime. + + :return: Datetime of the last connector run, or None if never run + :rtype: datetime or None + """ return self._last_run_datetime @last_run_datetime.setter - def last_run_datetime(self, value): + def last_run_datetime(self, value: datetime) -> None: """Set the last run datetime. :param value: The datetime of the last run @@ -1040,13 +1480,50 @@ def last_run_datetime(self, value): class OpenCTIConnectorHelper: # pylint: disable=too-many-public-methods - """Python API for OpenCTI connector + """Main helper class for developing OpenCTI connectors. + + Provides a comprehensive API for connector development, handling: + - Connector registration and configuration + - Message queue communication (RabbitMQ/API) + - SSE stream consumption + - STIX2 bundle creation and submission + - Scheduling and lifecycle management + - Metrics and logging - :param config: dict standard config + :param config: Configuration dictionary containing OpenCTI and connector settings :type config: Dict + :param playbook_compatible: Whether the connector can be used in playbooks + :type playbook_compatible: bool + + Example: + >>> config = { + ... "opencti": {"url": "http://localhost:8080", "token": "xxx"}, + ... "connector": {"id": "xxx", "name": "My Connector", "type": "EXTERNAL_IMPORT"} + ... } + >>> helper = OpenCTIConnectorHelper(config) + >>> helper.listen(my_callback_function) + + Attributes: + api: OpenCTI API client for connector operations + api_impersonate: API client that impersonates the request applicant + connector_logger: Logger instance for connector messages + connector_info: Runtime information about the connector + metric: Prometheus metric handler """ class TimeUnit(Enum): + """Time unit enumeration for scheduling intervals (deprecated). + + Use ISO 8601 duration format with schedule_iso() instead. + + :cvar SECONDS: 1 second + :cvar MINUTES: 60 seconds + :cvar HOURS: 3600 seconds + :cvar DAYS: 86400 seconds + :cvar WEEKS: 604800 seconds + :cvar YEARS: 31536000 seconds + """ + SECONDS = 1 MINUTES = 60 HOURS = 3600 @@ -1054,7 +1531,14 @@ class TimeUnit(Enum): WEEKS = 604800 YEARS = 31536000 - def __init__(self, config: Dict, playbook_compatible=False) -> None: + def __init__(self, config: Dict, playbook_compatible: bool = False) -> None: + """Initialize the OpenCTIConnectorHelper. + + :param config: Configuration dictionary with OpenCTI and connector settings + :type config: Dict + :param playbook_compatible: Whether the connector can be used in playbooks + :type playbook_compatible: bool + """ sys.excepthook = killProgramHook # Cache @@ -1566,9 +2050,12 @@ def get_validate_before_import(self) -> Optional[Union[bool, int, str]]: return self.connect_validate_before_import def set_state(self, state) -> None: - """sets the connector state + """Set the connector state. + + Stores the connector state as a JSON string for persistence across runs. + The state can be retrieved later using get_state(). - :param state: state object + :param state: State object to store, or None to clear the state :type state: Dict or None """ if isinstance(state, Dict): @@ -1577,12 +2064,14 @@ def set_state(self, state) -> None: self.connector_state = None def get_state(self) -> Optional[Dict]: - """get the connector state + """Get the connector state. - :return: returns the current state of the connector if there is any - :rtype: - """ + Retrieves the current connector state that was previously stored. + The state is used to track progress and resume operations across runs. + :return: The current state of the connector, or None if no state exists + :rtype: Optional[Dict] + """ try: if self.connector_state: state = json.loads(self.connector_state) @@ -1622,11 +2111,10 @@ def force_ping(self): self.connector_logger.error("Error pinging the API", {"reason": str(e)}) def next_run_datetime(self, duration_period_in_seconds: Union[int, float]) -> None: - """ - Lets you know what the next run of the scheduler will be in iso datetime format + """Calculate and set the next scheduled run datetime in ISO format. - :param duration_period_in_seconds: Duration in seconds - :return: None + :param duration_period_in_seconds: Duration in seconds until next run + :type duration_period_in_seconds: Union[int, float] """ try: duration_timedelta = datetime.timedelta(seconds=duration_period_in_seconds) @@ -1636,43 +2124,38 @@ def next_run_datetime(self, duration_period_in_seconds: Union[int, float]) -> No "%Y-%m-%dT%H:%M:%SZ" ) self.connector_logger.info( - "[INFO] Schedule next run of connector: ", + "Schedule next run of connector", {"next_run_datetime": self.connector_info.next_run_datetime}, ) - return except Exception as err: self.metric.inc("error_count") self.connector_logger.error( - "[ERROR] An error occurred while calculating the next run in datetime", + "An error occurred while calculating the next run in datetime", {"reason": str(err)}, ) sys.excepthook(*sys.exc_info()) def last_run_datetime(self) -> None: - """ - Lets you know what the last run of the connector the scheduler processed, will be in iso datetime format - - :return: None - """ + """Set the last run datetime to the current UTC time in ISO format.""" try: current_datetime = datetime.datetime.utcnow() # Set last_run_datetime self.connector_info.last_run_datetime = current_datetime.strftime( "%Y-%m-%dT%H:%M:%SZ" ) - return except Exception as err: self.metric.inc("error_count") self.connector_logger.error( - "[ERROR] An error occurred while converting the last run in datetime", + "An error occurred while converting the last run in datetime", {"reason": str(err)}, ) sys.excepthook(*sys.exc_info()) def check_connector_buffering(self) -> bool: - """ - Lets you know if the RabbitMQ queue has exceeded the allowed threshold defined by the connector or not - :return: boolean + """Check if the RabbitMQ queue has exceeded the allowed threshold. + + :return: True if queue size exceeds threshold, False otherwise + :rtype: bool """ try: connector_details = self.api.connector.read(connector_id=self.connector_id) @@ -1684,11 +2167,11 @@ def check_connector_buffering(self) -> bool: queue_messages_size_byte = connector_queue_details["messages_size"] queue_threshold = float(self.connect_queue_threshold) - # Convert queue_messages_size to Mo (decimal) + # Convert queue_messages_size to MB (decimal) queue_messages_size_mo = queue_messages_size_byte / 1000000 self.connector_logger.debug( - "[DEBUG] Connector queue details ...", + "Connector queue details", { "connector_queue_id": connector_queue_id, "queue_threshold": queue_threshold, @@ -1707,7 +2190,7 @@ def check_connector_buffering(self) -> bool: return False else: self.connector_logger.info( - "[INFO] Connector will not run until the queue messages size is reduced under queue threshold" + "Connector will not run until the queue messages size is reduced under queue threshold" ) # Set buffering self.connector_info.buffering = True @@ -1716,13 +2199,13 @@ def check_connector_buffering(self) -> bool: else: self.metric.inc("error_count") self.connector_logger.error( - "[ERROR] An error occurred while retrieving connector details" + "An error occurred while retrieving connector details" ) sys.excepthook(*sys.exc_info()) except Exception as err: self.metric.inc("error_count") self.connector_logger.error( - "[ERROR] An error occurred while checking the queue size", + "An error occurred while checking the queue size", {"reason": str(err)}, ) sys.excepthook(*sys.exc_info()) @@ -1733,17 +2216,17 @@ def schedule_unit( duration_period: Union[int, float, str], time_unit: TimeUnit, ) -> None: - """ - This (deprecated) method is there to manage backward compatibility of intervals on connectors, - allows you to calculate the duration period of connectors in seconds with time_unit and will be - replaced by the "schedule_iso" method. It uses a TimeUnit enum. - - :param message_callback: Corresponds to the connector process - :param duration_period: Corresponds to the connector interval, it can vary depending on the connector - configuration. - :param time_unit: The unit of time for the duration_period. - Enum TimeUnit Valid (YEARS, WEEKS, DAYS, HOURS, MINUTES, SECONDS) - :return: None + """Schedule connector execution with a time unit (deprecated). + + This method manages backward compatibility of intervals on connectors. + Use schedule_iso method instead. + + :param message_callback: The connector process callback function + :type message_callback: Callable[[], None] + :param duration_period: The connector interval value + :type duration_period: Union[int, float, str] + :param time_unit: The unit of time (YEARS, WEEKS, DAYS, HOURS, MINUTES, SECONDS) + :type time_unit: TimeUnit """ try: # Calculates the duration period in seconds @@ -1756,7 +2239,7 @@ def schedule_unit( except Exception as err: self.metric.inc("error_count") self.connector_logger.error( - "[ERROR] An unexpected error occurred during schedule_unit", + "An unexpected error occurred during schedule_unit", {"reason": str(err)}, ) sys.excepthook(*sys.exc_info()) @@ -1764,13 +2247,12 @@ def schedule_unit( def schedule_iso( self, message_callback: Callable[[], None], duration_period: str ) -> None: - """ - This method allows you to calculate the duration period of connectors in seconds from ISO 8601 format - and start the scheduler process. + """Schedule connector execution using ISO 8601 duration format. - :param message_callback: Corresponds to the connector process - :param duration_period: Corresponds to a string in ISO 8601 format "P18Y9W4DT11H9M8S" - :return: None + :param message_callback: The connector process callback function + :type message_callback: Callable[[], None] + :param duration_period: Duration in ISO 8601 format (e.g., "P18Y9W4DT11H9M8S") + :type duration_period: str """ try: if duration_period == "0": @@ -1787,7 +2269,7 @@ def schedule_iso( except Exception as err: self.metric.inc("error_count") self.connector_logger.error( - "[ERROR] An unexpected error occurred during schedule_iso", + "An unexpected error occurred during schedule_iso", {"reason": str(err)}, ) sys.excepthook(*sys.exc_info()) @@ -1798,18 +2280,20 @@ def _schedule_process( message_callback: Callable[[], None], duration_period: Union[int, float], ) -> None: - """ - When scheduling, the function retrieves the details of the connector queue, - and the connector process starts only if the size of the queue messages is less than or - equal to the queue_threshold variable. - - :param scheduler: Scheduler contains a list of all tasks to be started - :param message_callback: Corresponds to the connector process - :param duration_period: Corresponds to the connector's interval - :return: None + """Execute scheduled connector process if queue is not buffering. + + The connector process starts only if the queue messages size is less than + or equal to the queue_threshold variable. + + :param scheduler: Scheduler containing tasks to be started + :type scheduler: sched.scheduler + :param message_callback: The connector process callback function + :type message_callback: Callable[[], None] + :param duration_period: The connector's interval in seconds + :type duration_period: Union[int, float] """ try: - self.connector_logger.info("[INFO] Starting schedule") + self.connector_logger.info("Starting schedule") check_connector_buffering = self.check_connector_buffering() if not check_connector_buffering: @@ -1821,7 +2305,7 @@ def _schedule_process( except Exception as err: self.metric.inc("error_count") self.connector_logger.error( - "[ERROR] An error occurred while checking the queue size", + "An error occurred while checking the queue size", {"reason": str(err)}, ) sys.excepthook(*sys.exc_info()) @@ -1840,19 +2324,21 @@ def _schedule_process( def schedule_process( self, message_callback: Callable[[], None], duration_period: Union[int, float] ) -> None: - """ - This method schedules the execution of a connector process. - If `duration_period' is zero or `self.connect_run_and_terminate' is True, the process will run and terminate. - Otherwise, it schedules the next run based on the interval. + """Schedule the execution of a connector process. + + If duration_period is zero or connect_run_and_terminate is True, + the process will run once and terminate. Otherwise, it schedules + the next run based on the interval. - :param message_callback: Corresponds to the connector process - :param duration_period: Corresponds to the connector's interval in seconds - :return: None + :param message_callback: The connector process callback function + :type message_callback: Callable[[], None] + :param duration_period: The connector's interval in seconds + :type duration_period: Union[int, float] """ try: # In the case where the duration_period_converted is zero, we consider it to be a run and terminate if self.connect_run_and_terminate or duration_period == 0: - self.connector_logger.info("[INFO] Starting run and terminate") + self.connector_logger.info("Starting run and terminate") # Set run_and_terminate self.connector_info.run_and_terminate = True check_connector_buffering = self.check_connector_buffering() @@ -1863,7 +2349,7 @@ def schedule_process( # Lets you know what is the last run of the connector datetime self.last_run_datetime() - self.connector_logger.info("[INFO] Closing run and terminate") + self.connector_logger.info("Closing run and terminate") self.force_ping() sys.exit(0) else: @@ -1888,14 +2374,14 @@ def schedule_process( except SystemExit: self.connector_logger.info("SystemExit caught, stopping the scheduler") if self.connect_run_and_terminate: - self.connector_logger.info("[INFO] Closing run and terminate") + self.connector_logger.info("Closing run and terminate") self.force_ping() sys.exit(0) except Exception as err: self.metric.inc("error_count") self.connector_logger.error( - "[ERROR] An unexpected error occurred during schedule", + "An unexpected error occurred during schedule", {"reason": str(err)}, ) sys.excepthook(*sys.exc_info()) @@ -1904,12 +2390,17 @@ def listen( self, message_callback: Callable[[Dict], str], ) -> None: - """listen for messages and register callback function + """Listen for messages from the queue and process them via callback. - :param message_callback: callback function to process messages + Starts a listener thread that consumes messages from RabbitMQ or HTTP API + (depending on configured listen protocol) and processes each message + through the provided callback function. This method blocks until the + listener is stopped. + + :param message_callback: Function to process incoming messages. Receives + event data dict and should return a status message string. :type message_callback: Callable[[Dict], str] """ - self.listen_queue = ListenQueue( self, self.opencti_token, @@ -1927,20 +2418,45 @@ def listen( def listen_stream( self, - message_callback, - url=None, - token=None, - verify_ssl=None, - start_timestamp=None, - live_stream_id=None, - listen_delete=None, - no_dependencies=None, - recover_iso_date=None, - with_inferences=None, + message_callback: Callable, + url: Optional[str] = None, + token: Optional[str] = None, + verify_ssl: Optional[bool] = None, + start_timestamp: Optional[str] = None, + live_stream_id: Optional[str] = None, + listen_delete: Optional[bool] = None, + no_dependencies: Optional[bool] = None, + recover_iso_date: Optional[str] = None, + with_inferences: Optional[bool] = None, ) -> ListenStream: - """listen for messages and register callback function - - :param message_callback: callback function to process messages + """Start listening to an OpenCTI event stream. + + Connects to an SSE stream and processes events through the callback. + Parameters default to connector configuration values if not specified. + + :param message_callback: Function to call for each stream event + :type message_callback: Callable + :param url: Base URL for stream (defaults to opencti_url) + :type url: str or None + :param token: Authentication token (defaults to opencti_token) + :type token: str or None + :param verify_ssl: Whether to verify SSL certificates + :type verify_ssl: bool or None + :param start_timestamp: Stream position to start from + :type start_timestamp: str or None + :param live_stream_id: Specific stream ID to connect to + :type live_stream_id: str or None + :param listen_delete: Whether to receive delete events + :type listen_delete: bool or None + :param no_dependencies: Whether to exclude dependencies + :type no_dependencies: bool or None + :param recover_iso_date: ISO date to recover events from + :type recover_iso_date: str or None + :param with_inferences: Whether to include inferred data + :type with_inferences: bool or None + + :return: The started ListenStream thread + :rtype: ListenStream """ # URL if url is None: @@ -2034,9 +2550,16 @@ def get_connector(self) -> OpenCTIConnector: return self.connector def date_now(self) -> str: - """get the current date (UTC) - :return: current datetime for utc + """Get the current UTC datetime in ISO 8601 format. + + Returns the current time with timezone offset notation (+00:00). + + :return: Current UTC datetime as ISO 8601 string (e.g., "2024-01-15T10:30:00+00:00") :rtype: str + + Example: + >>> helper.date_now() + '2024-01-15T10:30:00+00:00' """ return ( datetime.datetime.utcnow() @@ -2045,9 +2568,17 @@ def date_now(self) -> str: ) def date_now_z(self) -> str: - """get the current date (UTC) - :return: current datetime for utc + """Get the current UTC datetime in ISO 8601 format with Z suffix. + + Returns the current time with 'Z' suffix instead of '+00:00'. + This format is commonly used in STIX objects. + + :return: Current UTC datetime as ISO 8601 string (e.g., "2024-01-15T10:30:00Z") :rtype: str + + Example: + >>> helper.date_now_z() + '2024-01-15T10:30:00Z' """ return ( datetime.datetime.utcnow() @@ -2058,21 +2589,51 @@ def date_now_z(self) -> str: # Push Stix2 helper def send_stix2_bundle(self, bundle: str, **kwargs) -> list: - """send a stix2 bundle to the API - - :param work_id: a valid work id - :param draft_id: a draft context to send the bundle to - :param bundle: valid stix2 bundle - :type bundle: - :param entities_types: list of entities, defaults to None + """Send a STIX2 bundle to the OpenCTI platform. + + Processes and sends a STIX2 bundle to OpenCTI via the message queue or API. + The bundle is split into smaller chunks and sent with proper sequencing. + Supports validation workflows, draft mode, and directory export. + + :param bundle: Valid STIX2 bundle as a JSON string + :type bundle: str + :param work_id: Work ID for tracking the import job (default: self.work_id) + :type work_id: str, optional + :param validation_mode: Validation mode - "workbench" or "draft" (default: self.validation_mode) + :type validation_mode: str, optional + :param draft_id: Draft context ID to send the bundle to (default: self.draft_id) + :type draft_id: str, optional + :param entities_types: List of entity types to filter (default: None) :type entities_types: list, optional - :param update: whether to updated data in the database, defaults to False + :param update: Whether to update existing data in the database (default: False) :type update: bool, optional - :param bypass_split: use to prevent splitting of the bundle. This option has been removed since 6.3 and is no longer used. - :type bypass_split: bool, optional - :raises ValueError: if the bundle is empty - :return: list of bundles + :param event_version: Event version for the bundle (default: None) + :type event_version: str, optional + :param bypass_validation: Skip validation workflow (default: False) + :type bypass_validation: bool, optional + :param force_validation: Force validation even if not configured (default: self.force_validation) + :type force_validation: bool, optional + :param entity_id: Entity ID for context (default: None) + :type entity_id: str, optional + :param file_markings: File markings to apply (default: None) + :type file_markings: list, optional + :param file_name: File name for workbench upload (default: None) + :type file_name: str, optional + :param send_to_queue: Whether to send to message queue (default: self.bundle_send_to_queue) + :type send_to_queue: bool, optional + :param cleanup_inconsistent_bundle: Clean up inconsistent bundle data (default: False) + :type cleanup_inconsistent_bundle: bool, optional + :param send_to_directory: Whether to write bundle to directory (default: self.bundle_send_to_directory) + :type send_to_directory: bool, optional + :param send_to_directory_path: Directory path for bundle export (default: self.bundle_send_to_directory_path) + :type send_to_directory_path: str, optional + :param send_to_directory_retention: Days to retain exported files (default: self.bundle_send_to_directory_retention) + :type send_to_directory_retention: int, optional + + :return: List of processed bundle chunks :rtype: list + + :raises ValueError: If the bundle is empty or contains no valid objects """ work_id = kwargs.get("work_id", self.work_id) validation_mode = kwargs.get("validation_mode", self.validation_mode) @@ -2297,18 +2858,25 @@ def send_stix2_bundle(self, bundle: str, **kwargs) -> list: return bundles def _send_bundle(self, channel, bundle, **kwargs) -> None: - """send a STIX2 bundle to RabbitMQ to be consumed by workers - - :param channel: RabbitMQ channel - :type channel: callable - :param bundle: valid stix2 bundle - :type bundle: - :param entities_types: list of entity types, defaults to None + """Send a STIX2 bundle to RabbitMQ to be consumed by workers. + + Publishes a bundle message to the configured RabbitMQ exchange with + persistent delivery mode. Retries automatically on delivery failure. + + :param channel: RabbitMQ channel for publishing + :type channel: pika.channel.Channel + :param bundle: Valid STIX2 bundle as a JSON string + :type bundle: str + :param work_id: Work ID for tracking (default: None) + :type work_id: str, optional + :param sequence: Sequence number for bundle ordering (default: 0) + :type sequence: int, optional + :param entities_types: List of entity types to filter (default: None) :type entities_types: list, optional - :param update: whether to update data in the database, defaults to False + :param update: Whether to update existing data in the database (default: False) :type update: bool, optional - :param draft_id: if draft_id is set, bundle must be set in draft context - :type draft_id: + :param draft_id: Draft context ID for the bundle (default: None) + :type draft_id: str, optional """ work_id = kwargs.get("work_id", None) sequence = kwargs.get("sequence", 0) @@ -2361,11 +2929,14 @@ def _send_bundle(self, channel, bundle, **kwargs) -> None: @staticmethod def stix2_deduplicate_objects(items) -> list: - """deduplicate stix2 items + """Deduplicate STIX2 objects by their ID. - :param items: valid stix2 items - :type items: - :return: de-duplicated list of items + Removes duplicate STIX2 objects from a list, keeping only the first + occurrence of each unique ID. + + :param items: List of STIX2 objects to deduplicate + :type items: list + :return: Deduplicated list of STIX2 objects :rtype: list """ @@ -2379,12 +2950,15 @@ def stix2_deduplicate_objects(items) -> list: @staticmethod def stix2_create_bundle(items) -> Optional[str]: - """create a stix2 bundle with items + """Create a STIX2 bundle from a list of objects. + + Wraps STIX2 objects in a valid bundle structure with a generated UUID. + Automatically serializes objects if they are STIX2 library instances. - :param items: valid stix2 items - :type items: - :return: JSON of the stix2 bundle - :rtype: + :param items: List of STIX2 objects (dicts or STIX2 library objects) + :type items: list + :return: JSON string of the STIX2 bundle, or None if items is empty + :rtype: Optional[str] """ # Check if item are native STIX 2 lib @@ -2402,16 +2976,25 @@ def stix2_create_bundle(items) -> Optional[str]: @staticmethod def check_max_tlp(tlp: str, max_tlp: str) -> bool: - """check the allowed TLP levels for a TLP string + """Check if a TLP level is within the allowed maximum TLP level. - :param tlp: string for TLP level to check + Validates that the given TLP marking is at or below the maximum + allowed TLP level. Useful for filtering data based on sharing + restrictions. + + :param tlp: The TLP level to check (e.g., "TLP:GREEN", "TLP:AMBER") :type tlp: str - :param max_tlp: the highest allowed TLP level + :param max_tlp: The highest allowed TLP level for comparison :type max_tlp: str - :return: TLP level in allowed TLPs + :return: True if the TLP level is within the allowed range, False otherwise :rtype: bool - """ + Example: + >>> OpenCTIConnectorHelper.check_max_tlp("TLP:GREEN", "TLP:AMBER") + True + >>> OpenCTIConnectorHelper.check_max_tlp("TLP:RED", "TLP:GREEN") + False + """ if tlp is None or max_tlp is None: return True @@ -2440,47 +3023,84 @@ def check_max_tlp(tlp: str, max_tlp: str) -> bool: return tlp.upper() in allowed_tlps[max_tlp.upper()] @staticmethod - def get_attribute_in_extension(key, object) -> any: + def get_attribute_in_extension(key: str, stix_object: Dict) -> any: + """Get an attribute from OpenCTI STIX extensions. + + Retrieves a value from OpenCTI's custom STIX extension definitions. + Checks both the primary OpenCTI extension and the SDO extension, + falling back to the object's root attributes if not found in extensions. + + :param key: The attribute key to retrieve + :type key: str + :param stix_object: A STIX object dictionary + :type stix_object: Dict + + :return: The attribute value, or None if not found + :rtype: any + + Example: + >>> obj = {"extensions": {"extension-definition--ea279b3e-...": {"score": 85}}} + >>> OpenCTIConnectorHelper.get_attribute_in_extension("score", obj) + 85 + """ if ( - "extensions" in object + "extensions" in stix_object and "extension-definition--ea279b3e-5c71-4632-ac08-831c66a786ba" - in object["extensions"] + in stix_object["extensions"] and key - in object["extensions"][ + in stix_object["extensions"][ "extension-definition--ea279b3e-5c71-4632-ac08-831c66a786ba" ] ): - return object["extensions"][ + return stix_object["extensions"][ "extension-definition--ea279b3e-5c71-4632-ac08-831c66a786ba" ][key] elif ( - "extensions" in object + "extensions" in stix_object and "extension-definition--f93e2c80-4231-4f9a-af8b-95c9bd566a82" - in object["extensions"] + in stix_object["extensions"] and key - in object["extensions"][ + in stix_object["extensions"][ "extension-definition--f93e2c80-4231-4f9a-af8b-95c9bd566a82" ] ): - return object["extensions"][ + return stix_object["extensions"][ "extension-definition--f93e2c80-4231-4f9a-af8b-95c9bd566a82" ][key] - elif key in object and key not in ["type"]: - return object[key] + elif key in stix_object and key not in ["type"]: + return stix_object[key] return None @staticmethod - def get_attribute_in_mitre_extension(key, object) -> any: + def get_attribute_in_mitre_extension(key: str, stix_object: Dict) -> any: + """Get an attribute from MITRE ATT&CK STIX extension. + + Retrieves a value from the MITRE ATT&CK custom STIX extension + definition used for attack patterns and techniques. + + :param key: The attribute key to retrieve + :type key: str + :param stix_object: A STIX object dictionary + :type stix_object: Dict + + :return: The attribute value, or None if not found + :rtype: any + + Example: + >>> obj = {"extensions": {"extension-definition--322b8f77-...": {"x_mitre_version": "1.0"}}} + >>> OpenCTIConnectorHelper.get_attribute_in_mitre_extension("x_mitre_version", obj) + '1.0' + """ if ( - "extensions" in object + "extensions" in stix_object and "extension-definition--322b8f77-262a-4cb8-a915-1e441e00329b" - in object["extensions"] + in stix_object["extensions"] and key - in object["extensions"][ + in stix_object["extensions"][ "extension-definition--322b8f77-262a-4cb8-a915-1e441e00329b" ] ): - return object["extensions"][ + return stix_object["extensions"][ "extension-definition--322b8f77-262a-4cb8-a915-1e441e00329b" ][key] return None diff --git a/client-python/pycti/connector/opencti_metric_handler.py b/client-python/pycti/connector/opencti_metric_handler.py index 988ad7cb2b4b..1796980bf4de 100644 --- a/client-python/pycti/connector/opencti_metric_handler.py +++ b/client-python/pycti/connector/opencti_metric_handler.py @@ -1,9 +1,46 @@ +"""OpenCTI Metric Handler module. + +This module provides Prometheus metrics support for OpenCTI connectors, +allowing monitoring of connector performance and health. +""" + from typing import Type, Union from prometheus_client import Counter, Enum, start_http_server class OpenCTIMetricHandler: + """Handler for Prometheus metrics in OpenCTI connectors. + + This class manages Prometheus metrics for monitoring connector behavior, + including bundle sends, records processed, run counts, API pings, and errors. + + When activated, it starts an HTTP server to expose metrics for scraping + by Prometheus or compatible monitoring systems. + + :param connector_logger: Logger instance for the connector + :type connector_logger: logging.Logger + :param activated: Whether to enable metrics collection and exposure + :type activated: bool + :param namespace: Prometheus metrics namespace prefix + :type namespace: str + :param subsystem: Prometheus metrics subsystem prefix + :type subsystem: str + :param port: Port number for the Prometheus HTTP server + :type port: int + + Example: + >>> handler = OpenCTIMetricHandler( + ... connector_logger=logger, + ... activated=True, + ... namespace="opencti", + ... subsystem="connector", + ... port=9095 + ... ) + >>> handler.inc("bundle_send") + >>> handler.state("running") + """ + def __init__( self, connector_logger, @@ -12,19 +49,18 @@ def __init__( subsystem: str = "", port: int = 9095, ): - """ - Init of OpenCTIMetricHandler class. - - Parameters - ---------- - activated : bool, default False - If True use metrics in client and connectors. - namespace: str, default empty - Namespace for the prometheus metrics. - subsystem: str, default empty - Subsystem for the prometheus metrics. - port : int, default 9095 - Port for prometheus server. + """Initialize the OpenCTIMetricHandler instance. + + :param connector_logger: Logger instance for the connector + :type connector_logger: logging.Logger + :param activated: Whether to enable metrics (default: False) + :type activated: bool + :param namespace: Prometheus metrics namespace prefix (default: "") + :type namespace: str + :param subsystem: Prometheus metrics subsystem prefix (default: "") + :type subsystem: str + :param port: Port for Prometheus HTTP server (default: 9095) + :type port: int """ self.activated = activated self.connector_logger = connector_logger @@ -46,7 +82,7 @@ def __init__( ), "run_count": Counter( "runs_total", - "Number of run", + "Number of runs", namespace=namespace, subsystem=subsystem, ), @@ -86,22 +122,18 @@ def __init__( def _metric_exists( self, name: str, expected_type: Union[Type[Counter], Type[Enum]] ) -> bool: - """ - Check if a metric exists and has the correct type. + """Check if a metric exists and has the expected type. - If it does not, log an error and return False. + Validates that the named metric is registered and matches the expected + Prometheus metric type. Logs an error if validation fails. - Parameters - ---------- - name : str - Name of the metric to check. - expected_type : Counter or Enum - Expected type of the metric. + :param name: Name of the metric to validate + :type name: str + :param expected_type: Expected Prometheus metric type (Counter or Enum) + :type expected_type: Union[Type[Counter], Type[Enum]] - Returns - ------- - bool - True if the metric exists and is of the correct type else False. + :return: True if metric exists and has correct type, False otherwise + :rtype: bool """ if name not in self._metrics: self.connector_logger.error("Metric does not exist.", {"name": name}) @@ -114,31 +146,54 @@ def _metric_exists( return False return True - def inc(self, name: str, n: int = 1): - """ - Increment the metric (counter) `name` by `n`. - - Parameters - ---------- - name : str - Name of the metric to increment. - n : int, default 1 - Increment the counter by `n`. + def inc(self, name: str, n: int = 1) -> None: + """Increment a counter metric by a specified amount. + + Increments the named counter metric. If metrics are not activated + or the metric does not exist, this method does nothing. + + Available counter metrics: + - bundle_send: Number of bundles sent + - record_send: Number of records sent + - run_count: Number of connector runs + - ping_api_count: Number of API pings + - ping_api_error: Number of API ping errors + - error_count: Total number of errors + - client_error_count: Number of client errors + + :param name: Name of the counter metric to increment + :type name: str + :param n: Amount to increment the counter by (default: 1) + :type n: int + + Example: + >>> handler.inc("bundle_send") + >>> handler.inc("record_send", 10) """ if self.activated: if self._metric_exists(name, Counter): self._metrics[name].inc(n) - def state(self, state: str, name: str = "state"): - """ - Set the state `state` for metric `name`. - - Parameters - ---------- - state : str - State to set. - name : str, default = "state" - Name of the metric to set. + def state(self, state: str, name: str = "state") -> None: + """Set the state of an Enum metric. + + Updates the named Enum metric to the specified state value. + If metrics are not activated or the metric does not exist, + this method does nothing. + + Available states for the default "state" metric: + - idle: Connector is idle + - running: Connector is running + - stopped: Connector is stopped + + :param state: State value to set (must be a valid state for the metric) + :type state: str + :param name: Name of the Enum metric to update (default: "state") + :type name: str + + Example: + >>> handler.state("running") + >>> handler.state("idle", "state") """ if self.activated: if self._metric_exists(name, Enum): diff --git a/client-python/pycti/entities/opencti_attack_pattern.py b/client-python/pycti/entities/opencti_attack_pattern.py index c3934553b176..f7a72f1a53e7 100644 --- a/client-python/pycti/entities/opencti_attack_pattern.py +++ b/client-python/pycti/entities/opencti_attack_pattern.py @@ -9,10 +9,18 @@ class AttackPattern: """Main AttackPattern class for OpenCTI + Manages MITRE ATT&CK patterns and techniques in the OpenCTI platform. + :param opencti: instance of :py:class:`~pycti.api.opencti_api_client.OpenCTIApiClient` + :type opencti: OpenCTIApiClient """ def __init__(self, opencti): + """Initialize the AttackPattern instance. + + :param opencti: OpenCTI API client instance + :type opencti: OpenCTIApiClient + """ self.opencti = opencti self.properties = """ id @@ -60,6 +68,11 @@ def __init__(self, opencti): x_opencti_lastname } } + objectOrganization { + id + standard_id + name + } objectMarking { id standard_id @@ -160,6 +173,11 @@ def __init__(self, opencti): x_opencti_lastname } } + objectOrganization { + id + standard_id + name + } objectMarking { id standard_id @@ -276,15 +294,25 @@ def list(self, **kwargs): """List Attack Pattern objects. :param filters: the filters to apply + :type filters: dict :param search: the search keyword + :type search: str :param first: return the first n rows from the after ID (or the beginning if not set) + :type first: int :param after: ID of the first row for pagination + :type after: str :param orderBy: field to order results by + :type orderBy: str :param orderMode: ordering mode (asc/desc) + :type orderMode: str :param customAttributes: custom attributes to return + :type customAttributes: str :param getAll: whether to retrieve all results + :type getAll: bool :param withPagination: whether to include pagination info + :type withPagination: bool :param withFiles: whether to include files + :type withFiles: bool :return: List of Attack Pattern objects :rtype: list """ @@ -367,15 +395,20 @@ def list(self, **kwargs): result["data"]["attackPatterns"], with_pagination ) - """ - Read a Attack-Pattern object + def read(self, **kwargs): + """Read an Attack Pattern object. - :param id: the id of the Attack-Pattern + :param id: the id of the Attack Pattern + :type id: str :param filters: the filters to apply if no id provided - :return Attack-Pattern object - """ - - def read(self, **kwargs): + :type filters: dict + :param customAttributes: custom attributes to return + :type customAttributes: str + :param withFiles: whether to include files + :type withFiles: bool + :return: Attack Pattern object + :rtype: dict or None + """ id = kwargs.get("id", None) filters = kwargs.get("filters", None) custom_attributes = kwargs.get("customAttributes", None) @@ -411,14 +444,62 @@ def read(self, **kwargs): ) return None - """ - Create a Attack-Pattern object - - :param name: the name of the Attack Pattern - :return Attack-Pattern object - """ - def create(self, **kwargs): + """Create an Attack Pattern object. + + :param stix_id: (optional) the STIX ID + :type stix_id: str + :param createdBy: (optional) the author ID + :type createdBy: str + :param objectMarking: (optional) list of marking definition IDs + :type objectMarking: list + :param objectLabel: (optional) list of label IDs + :type objectLabel: list + :param externalReferences: (optional) list of external reference IDs + :type externalReferences: list + :param revoked: (optional) whether the attack pattern is revoked + :type revoked: bool + :param confidence: (optional) confidence level (0-100) + :type confidence: int + :param lang: (optional) language + :type lang: str + :param created: (optional) creation date + :type created: str + :param modified: (optional) modification date + :type modified: str + :param name: the name of the Attack Pattern (required) + :type name: str + :param description: (optional) description + :type description: str + :param aliases: (optional) list of aliases + :type aliases: list + :param x_mitre_platforms: (optional) list of MITRE platforms + :type x_mitre_platforms: list + :param x_mitre_permissions_required: (optional) list of required permissions + :type x_mitre_permissions_required: list + :param x_mitre_detection: (optional) detection guidance + :type x_mitre_detection: str + :param x_mitre_id: (optional) MITRE ATT&CK ID + :type x_mitre_id: str + :param killChainPhases: (optional) list of kill chain phase IDs + :type killChainPhases: list + :param x_opencti_stix_ids: (optional) list of additional STIX IDs + :type x_opencti_stix_ids: list + :param objectOrganization: (optional) list of organization IDs + :type objectOrganization: list + :param x_opencti_workflow_id: (optional) workflow ID + :type x_opencti_workflow_id: str + :param x_opencti_modified_at: (optional) custom modification date + :type x_opencti_modified_at: str + :param update: (optional) whether to update if exists (default: False) + :type update: bool + :param file: (optional) File object to attach + :type file: dict + :param fileMarkings: (optional) list of marking definition IDs for the file + :type fileMarkings: list + :return: Attack Pattern object + :rtype: dict or None + """ stix_id = kwargs.get("stix_id", None) created_by = kwargs.get("createdBy", None) object_marking = kwargs.get("objectMarking", None) @@ -442,6 +523,8 @@ def create(self, **kwargs): x_opencti_workflow_id = kwargs.get("x_opencti_workflow_id", None) x_opencti_modified_at = kwargs.get("x_opencti_modified_at", None) update = kwargs.get("update", False) + file = kwargs.get("file", None) + file_markings = kwargs.get("fileMarkings", None) if name is not None: self.opencti.app_logger.info("Creating Attack-Pattern", {"name": name}) @@ -455,52 +538,55 @@ def create(self, **kwargs): } } """ - result = self.opencti.query( - query, - { - "input": { - "stix_id": stix_id, - "createdBy": created_by, - "objectMarking": object_marking, - "objectLabel": object_label, - "objectOrganization": granted_refs, - "externalReferences": external_references, - "revoked": revoked, - "confidence": confidence, - "lang": lang, - "created": created, - "modified": modified, - "name": name, - "description": description, - "aliases": aliases, - "x_mitre_platforms": x_mitre_platforms, - "x_mitre_permissions_required": x_mitre_permissions_required, - "x_mitre_detection": x_mitre_detection, - "x_mitre_id": x_mitre_id, - "killChainPhases": kill_chain_phases, - "x_opencti_stix_ids": x_opencti_stix_ids, - "x_opencti_workflow_id": x_opencti_workflow_id, - "x_opencti_modified_at": x_opencti_modified_at, - "update": update, - } - }, - ) + input_variables = { + "stix_id": stix_id, + "createdBy": created_by, + "objectMarking": object_marking, + "objectLabel": object_label, + "objectOrganization": granted_refs, + "externalReferences": external_references, + "revoked": revoked, + "confidence": confidence, + "lang": lang, + "created": created, + "modified": modified, + "name": name, + "description": description, + "aliases": aliases, + "x_mitre_platforms": x_mitre_platforms, + "x_mitre_permissions_required": x_mitre_permissions_required, + "x_mitre_detection": x_mitre_detection, + "x_mitre_id": x_mitre_id, + "killChainPhases": kill_chain_phases, + "x_opencti_stix_ids": x_opencti_stix_ids, + "x_opencti_workflow_id": x_opencti_workflow_id, + "x_opencti_modified_at": x_opencti_modified_at, + "update": update, + "file": file, + "fileMarkings": file_markings, + } + result = self.opencti.query(query, {"input": input_variables}) return self.opencti.process_multiple_fields( result["data"]["attackPatternAdd"] ) else: self.opencti.app_logger.error( - "[opencti_attack_pattern] Missing parameters: name and description" + "[opencti_attack_pattern] Missing parameters: name" ) - - """ - Import an Attack-Pattern object from a STIX2 object - - :param stixObject: the Stix-Object Attack-Pattern - :return Attack-Pattern object - """ + return None def import_from_stix2(self, **kwargs): + """Import an Attack Pattern object from a STIX2 object. + + :param stixObject: the STIX2 Attack Pattern object + :type stixObject: dict + :param extras: extra parameters including created_by_id, object_marking_ids, etc. + :type extras: dict + :param update: whether to update if the entity already exists + :type update: bool + :return: Attack Pattern object + :rtype: dict or None + """ stix_object = kwargs.get("stixObject", None) extras = kwargs.get("extras", {}) update = kwargs.get("update", False) @@ -646,13 +732,22 @@ def import_from_stix2(self, **kwargs): else None ), update=update, + file=extras.get("file"), + fileMarkings=extras.get("fileMarkings"), ) else: self.opencti.app_logger.error( "[opencti_attack_pattern] Missing parameters: stixObject" ) + return None def delete(self, **kwargs): + """Delete an Attack Pattern object. + + :param id: the id of the Attack Pattern to delete + :type id: str + :return: None + """ id = kwargs.get("id", None) if id is not None: self.opencti.app_logger.info("Deleting Attack Pattern", {"id": id}) @@ -665,5 +760,7 @@ def delete(self, **kwargs): """ self.opencti.query(query, {"id": id}) else: - self.opencti.app_logger.error("[attack_pattern] Missing parameters: id") + self.opencti.app_logger.error( + "[opencti_attack_pattern] Missing parameters: id" + ) return None diff --git a/client-python/pycti/entities/opencti_campaign.py b/client-python/pycti/entities/opencti_campaign.py index eef61b9b6c32..12b7d6286ae5 100644 --- a/client-python/pycti/entities/opencti_campaign.py +++ b/client-python/pycti/entities/opencti_campaign.py @@ -9,10 +9,18 @@ class Campaign: """Main Campaign class for OpenCTI + Manages threat campaigns in the OpenCTI platform. + :param opencti: instance of :py:class:`~pycti.api.opencti_api_client.OpenCTIApiClient` + :type opencti: OpenCTIApiClient """ def __init__(self, opencti): + """Initialize the Campaign instance. + + :param opencti: OpenCTI API client instance + :type opencti: OpenCTIApiClient + """ self.opencti = opencti self.properties = """ id @@ -259,15 +267,25 @@ def list(self, **kwargs): """List Campaign objects. :param filters: the filters to apply + :type filters: dict :param search: the search keyword + :type search: str :param first: return the first n rows from the after ID (or the beginning if not set) + :type first: int :param after: ID of the first row for pagination + :type after: str :param orderBy: field to order results by + :type orderBy: str :param orderMode: ordering mode (asc/desc) + :type orderMode: str :param customAttributes: custom attributes to return + :type customAttributes: str :param getAll: whether to retrieve all results + :type getAll: bool :param withPagination: whether to include pagination info + :type withPagination: bool :param withFiles: whether to include files + :type withFiles: bool :return: List of Campaign objects :rtype: list """ @@ -348,15 +366,20 @@ def list(self, **kwargs): result["data"]["campaigns"], with_pagination ) - """ - Read a Campaign object + def read(self, **kwargs): + """Read a Campaign object. :param id: the id of the Campaign + :type id: str :param filters: the filters to apply if no id provided - :return Campaign object - """ - - def read(self, **kwargs): + :type filters: dict + :param customAttributes: custom attributes to return + :type customAttributes: str + :param withFiles: whether to include files + :type withFiles: bool + :return: Campaign object + :rtype: dict or None + """ id = kwargs.get("id", None) filters = kwargs.get("filters", None) custom_attributes = kwargs.get("customAttributes", None) @@ -392,14 +415,58 @@ def read(self, **kwargs): ) return None - """ - Create a Campaign object - - :param name: the name of the Campaign - :return Campaign object - """ - def create(self, **kwargs): + """Create a Campaign object. + + :param stix_id: (optional) the STIX ID + :type stix_id: str + :param createdBy: (optional) the author ID + :type createdBy: str + :param objectMarking: (optional) list of marking definition IDs + :type objectMarking: list + :param objectLabel: (optional) list of label IDs + :type objectLabel: list + :param externalReferences: (optional) list of external reference IDs + :type externalReferences: list + :param revoked: (optional) whether the campaign is revoked + :type revoked: bool + :param confidence: (optional) confidence level (0-100) + :type confidence: int + :param lang: (optional) language + :type lang: str + :param created: (optional) creation date + :type created: str + :param modified: (optional) modification date + :type modified: str + :param name: the name of the Campaign (required) + :type name: str + :param description: (optional) description + :type description: str + :param aliases: (optional) list of aliases + :type aliases: list + :param first_seen: (optional) first seen date + :type first_seen: str + :param last_seen: (optional) last seen date + :type last_seen: str + :param objective: (optional) objective of the campaign + :type objective: str + :param objectOrganization: (optional) list of organization IDs + :type objectOrganization: list + :param x_opencti_stix_ids: (optional) list of additional STIX IDs + :type x_opencti_stix_ids: list + :param x_opencti_workflow_id: (optional) workflow ID + :type x_opencti_workflow_id: str + :param x_opencti_modified_at: (optional) custom modification date + :type x_opencti_modified_at: str + :param update: (optional) whether to update if exists (default: False) + :type update: bool + :param file: (optional) File object to attach + :type file: dict + :param fileMarkings: (optional) list of marking definition IDs for the file + :type fileMarkings: list + :return: Campaign object + :rtype: dict or None + """ stix_id = kwargs.get("stix_id", None) created_by = kwargs.get("createdBy", None) object_marking = kwargs.get("objectMarking", None) @@ -421,6 +488,8 @@ def create(self, **kwargs): x_opencti_workflow_id = kwargs.get("x_opencti_workflow_id", None) x_opencti_modified_at = kwargs.get("x_opencti_modified_at", None) update = kwargs.get("update", False) + file = kwargs.get("file", None) + file_markings = kwargs.get("fileMarkings", None) if name is not None: self.opencti.app_logger.info("Creating Campaign", {"name": name}) @@ -459,23 +528,28 @@ def create(self, **kwargs): "x_opencti_workflow_id": x_opencti_workflow_id, "x_opencti_modified_at": x_opencti_modified_at, "x_opencti_stix_ids": x_opencti_stix_ids, + "file": file, + "fileMarkings": file_markings, } }, ) return self.opencti.process_multiple_fields(result["data"]["campaignAdd"]) else: - self.opencti.app_logger.error( - "[opencti_campaign] Missing parameters: name and description" - ) - - """ - Import a Campaign object from a STIX2 object - - :param stixObject: the Stix-Object Campaign - :return Campaign object - """ + self.opencti.app_logger.error("[opencti_campaign] Missing parameters: name") + return None def import_from_stix2(self, **kwargs): + """Import a Campaign object from a STIX2 object. + + :param stixObject: the STIX2 Campaign object + :type stixObject: dict + :param extras: extra parameters including created_by_id, object_marking_ids, etc. + :type extras: dict + :param update: whether to update if the entity already exists + :type update: bool + :return: Campaign object + :rtype: dict or None + """ stix_object = kwargs.get("stixObject", None) extras = kwargs.get("extras", {}) update = kwargs.get("update", False) @@ -560,8 +634,11 @@ def import_from_stix2(self, **kwargs): else None ), update=update, + file=extras.get("file"), + fileMarkings=extras.get("fileMarkings"), ) else: self.opencti.app_logger.error( "[opencti_campaign] Missing parameters: stixObject" ) + return None diff --git a/client-python/pycti/entities/opencti_capability.py b/client-python/pycti/entities/opencti_capability.py index bd6fbb53e583..0eb28bea62a1 100644 --- a/client-python/pycti/entities/opencti_capability.py +++ b/client-python/pycti/entities/opencti_capability.py @@ -6,9 +6,17 @@ class Capability: See the properties attribute to understand which properties are fetched by default from the graphql queries. + + :param opencti: instance of :py:class:`~pycti.api.opencti_api_client.OpenCTIApiClient` + :type opencti: OpenCTIApiClient """ def __init__(self, opencti): + """Initialize the Capability instance. + + :param opencti: OpenCTI API client instance + :type opencti: OpenCTIApiClient + """ self.opencti = opencti self.properties = """ id diff --git a/client-python/pycti/entities/opencti_case_incident.py b/client-python/pycti/entities/opencti_case_incident.py index ca9d4eead86a..48c662497cfb 100644 --- a/client-python/pycti/entities/opencti_case_incident.py +++ b/client-python/pycti/entities/opencti_case_incident.py @@ -12,9 +12,15 @@ class CaseIncident: Manages incident response cases in the OpenCTI platform. :param opencti: instance of :py:class:`~pycti.api.opencti_api_client.OpenCTIApiClient` + :type opencti: OpenCTIApiClient """ def __init__(self, opencti): + """Initialize the CaseIncident instance. + + :param opencti: OpenCTI API client instance + :type opencti: OpenCTIApiClient + """ self.opencti = opencti self.properties = """ id @@ -476,6 +482,15 @@ def __init__(self, opencti): @staticmethod def generate_id(name, created): + """Generate a STIX ID for a Case Incident object. + + :param name: the name of the Case Incident + :type name: str + :param created: the creation date of the Case Incident + :type created: str or datetime.datetime + :return: STIX ID for the Case Incident + :rtype: str + """ name = name.lower().strip() if isinstance(created, datetime.datetime): created = created.isoformat() @@ -486,19 +501,29 @@ def generate_id(name, created): @staticmethod def generate_id_from_data(data): + """Generate a STIX ID from Case Incident data. + + :param data: Dictionary containing 'name' and 'created' keys + :type data: dict + :return: STIX ID for the Case Incident + :rtype: str + """ return CaseIncident.generate_id(data["name"], data["created"]) - """ - List Case Incident objects + def list(self, **kwargs): + """List Case Incident objects. :param filters: the filters to apply + :type filters: dict :param search: the search keyword + :type search: str :param first: return the first n rows from the after ID (or the beginning if not set) + :type first: int :param after: ID of the first row for pagination - :return List of Case Incident objects - """ - - def list(self, **kwargs): + :type after: str + :return: List of Case Incident objects + :rtype: list + """ filters = kwargs.get("filters", None) search = kwargs.get("search", None) first = kwargs.get("first", 500) @@ -578,15 +603,16 @@ def list(self, **kwargs): result["data"]["caseIncidents"], with_pagination ) - """ - Read a Case Incident object + def read(self, **kwargs): + """Read a Case Incident object. :param id: the id of the Case Incident + :type id: str :param filters: the filters to apply if no id provided - :return Case Incident object - """ - - def read(self, **kwargs): + :type filters: dict + :return: Case Incident object + :rtype: dict or None + """ id = kwargs.get("id", None) filters = kwargs.get("filters", None) custom_attributes = kwargs.get("customAttributes", None) @@ -617,16 +643,18 @@ def read(self, **kwargs): else: return None - """ - Read a Case Incident object by stix_id or name - - :param type: the Stix-Domain-Entity type - :param stix_id: the STIX ID of the Stix-Domain-Entity - :param name: the name of the Stix-Domain-Entity - :return Stix-Domain-Entity object - """ - def get_by_stix_id_or_name(self, **kwargs): + """Read a Case Incident object by stix_id or name. + + :param stix_id: the STIX ID of the Case Incident + :type stix_id: str + :param name: the name of the Case Incident + :type name: str + :param created: the creation date of the Case Incident + :type created: str + :return: Case Incident object + :rtype: dict or None + """ stix_id = kwargs.get("stix_id", None) name = kwargs.get("name", None) created = kwargs.get("created", None) @@ -649,15 +677,16 @@ def get_by_stix_id_or_name(self, **kwargs): ) return object_result - """ - Check if a case incident already contains a thing (Stix Object or Stix Relationship) + def contains_stix_object_or_stix_relationship(self, **kwargs): + """Check if a case incident already contains a thing (Stix Object or Stix Relationship). :param id: the id of the Case Incident + :type id: str :param stixObjectOrStixRelationshipId: the id of the Stix-Entity - :return Boolean - """ - - def contains_stix_object_or_stix_relationship(self, **kwargs): + :type stixObjectOrStixRelationshipId: str + :return: True if contained, False otherwise + :rtype: bool or None + """ id = kwargs.get("id", None) stix_object_or_stix_relationship_id = kwargs.get( "stixObjectOrStixRelationshipId", None @@ -685,17 +714,43 @@ def contains_stix_object_or_stix_relationship(self, **kwargs): return result["data"]["caseIncidentContainsStixObjectOrStixRelationship"] else: self.opencti.app_logger.error( - "[opencti_caseIncident] Missing parameters: id or stixObjectOrStixRelationshipId" + "[opencti_case_incident] Missing parameters: id or stixObjectOrStixRelationshipId" ) + return None - """ + def create(self, **kwargs): + """ Create a Case Incident object - :param name: the name of the Case Incident - :return Case Incident object - """ - - def create(self, **kwargs): + :param stix_id: (optional) the STIX ID + :param createdBy: (optional) the author ID + :param objects: (optional) list of STIX object IDs contained in the case + :param objectMarking: (optional) list of marking definition IDs + :param objectLabel: (optional) list of label IDs + :param externalReferences: (optional) list of external reference IDs + :param revoked: (optional) whether the case is revoked + :param confidence: (optional) confidence level (0-100) + :param lang: (optional) language + :param created: (optional) creation date + :param modified: (optional) modification date + :param name: the name of the Case Incident (required) + :param description: (optional) description + :param content: (optional) content + :param severity: (optional) severity level + :param priority: (optional) priority level + :param x_opencti_stix_ids: (optional) list of additional STIX IDs + :param objectAssignee: (optional) list of assignee IDs + :param objectParticipant: (optional) list of participant IDs + :param objectOrganization: (optional) list of organization IDs + :param response_types: (optional) list of response types + :param x_opencti_workflow_id: (optional) workflow ID + :param x_opencti_modified_at: (optional) custom modification date + :param update: (optional) whether to update if exists (default: False) + :param file: (optional) File object to attach + :param fileMarkings: (optional) list of marking definition IDs for the file + :return: Case Incident object + :rtype: dict or None + """ stix_id = kwargs.get("stix_id", None) created_by = kwargs.get("createdBy", None) objects = kwargs.get("objects", None) @@ -720,6 +775,8 @@ def create(self, **kwargs): x_opencti_workflow_id = kwargs.get("x_opencti_workflow_id", None) x_opencti_modified_at = kwargs.get("x_opencti_modified_at", None) update = kwargs.get("update", False) + file = kwargs.get("file", None) + file_markings = kwargs.get("fileMarkings", None) if name is not None: self.opencti.app_logger.info("Creating Case Incident", {"name": name}) @@ -733,54 +790,54 @@ def create(self, **kwargs): } } """ - result = self.opencti.query( - query, - { - "input": { - "stix_id": stix_id, - "createdBy": created_by, - "objectMarking": object_marking, - "objectLabel": object_label, - "objectOrganization": granted_refs, - "objectAssignee": object_assignee, - "objectParticipant": object_participant, - "objects": objects, - "externalReferences": external_references, - "revoked": revoked, - "confidence": confidence, - "lang": lang, - "created": created, - "modified": modified, - "name": name, - "description": description, - "content": content, - "severity": severity, - "priority": priority, - "x_opencti_stix_ids": x_opencti_stix_ids, - "response_types": response_types, - "x_opencti_workflow_id": x_opencti_workflow_id, - "x_opencti_modified_at": x_opencti_modified_at, - "update": update, - } - }, - ) + input_variables = { + "stix_id": stix_id, + "createdBy": created_by, + "objectMarking": object_marking, + "objectLabel": object_label, + "objectOrganization": granted_refs, + "objectAssignee": object_assignee, + "objectParticipant": object_participant, + "objects": objects, + "externalReferences": external_references, + "revoked": revoked, + "confidence": confidence, + "lang": lang, + "created": created, + "modified": modified, + "name": name, + "description": description, + "content": content, + "severity": severity, + "priority": priority, + "x_opencti_stix_ids": x_opencti_stix_ids, + "response_types": response_types, + "x_opencti_workflow_id": x_opencti_workflow_id, + "x_opencti_modified_at": x_opencti_modified_at, + "update": update, + "file": file, + "fileMarkings": file_markings, + } + result = self.opencti.query(query, {"input": input_variables}) return self.opencti.process_multiple_fields( result["data"]["caseIncidentAdd"] ) else: self.opencti.app_logger.error( - "[opencti_caseIncident] Missing parameters: name" + "[opencti_case_incident] Missing parameters: name" ) + return None - """ - Add a Stix-Entity object to Case Incident object (object_refs) + def add_stix_object_or_stix_relationship(self, **kwargs): + """Add a Stix-Entity object to Case Incident object (object_refs). :param id: the id of the Case Incident + :type id: str :param stixObjectOrStixRelationshipId: the id of the Stix-Entity - :return Boolean - """ - - def add_stix_object_or_stix_relationship(self, **kwargs): + :type stixObjectOrStixRelationshipId: str + :return: True if successful, False otherwise + :rtype: bool + """ id = kwargs.get("id", None) stix_object_or_stix_relationship_id = kwargs.get( "stixObjectOrStixRelationshipId", None @@ -816,26 +873,27 @@ def add_stix_object_or_stix_relationship(self, **kwargs): return True else: self.opencti.app_logger.error( - "[opencti_caseIncident] Missing parameters: id and stixObjectOrStixRelationshipId" + "[opencti_case_incident] Missing parameters: id and stixObjectOrStixRelationshipId" ) return False - """ - Remove a Stix-Entity object to Case Incident object (object_refs) + def remove_stix_object_or_stix_relationship(self, **kwargs): + """Remove a Stix-Entity object from Case Incident object (object_refs). :param id: the id of the Case Incident + :type id: str :param stixObjectOrStixRelationshipId: the id of the Stix-Entity - :return Boolean - """ - - def remove_stix_object_or_stix_relationship(self, **kwargs): + :type stixObjectOrStixRelationshipId: str + :return: True if successful, False otherwise + :rtype: bool + """ id = kwargs.get("id", None) stix_object_or_stix_relationship_id = kwargs.get( "stixObjectOrStixRelationshipId", None ) if id is not None and stix_object_or_stix_relationship_id is not None: self.opencti.app_logger.info( - "Removing StixObjectOrStixRelationship to CaseIncident", + "Removing StixObjectOrStixRelationship from CaseIncident", { "stix_object_or_stix_relationship_id": stix_object_or_stix_relationship_id, "id": id, @@ -861,18 +919,22 @@ def remove_stix_object_or_stix_relationship(self, **kwargs): return True else: self.opencti.app_logger.error( - "[opencti_caseIncident] Missing parameters: id and stixObjectOrStixRelationshipId" + "[opencti_case_incident] Missing parameters: id and stixObjectOrStixRelationshipId" ) return False - """ - Import a Case Incident object from a STIX2 object + def import_from_stix2(self, **kwargs): + """Import a Case Incident object from a STIX2 object. :param stixObject: the Stix-Object Case Incident - :return Case Incident object - """ - - def import_from_stix2(self, **kwargs): + :type stixObject: dict + :param extras: additional parameters like created_by_id, object_marking_ids + :type extras: dict + :param update: whether to update existing object + :type update: bool + :return: Case Incident object + :rtype: dict or None + """ stix_object = kwargs.get("stixObject", None) extras = kwargs.get("extras", {}) update = kwargs.get("update", False) @@ -985,13 +1047,22 @@ def import_from_stix2(self, **kwargs): else None ), update=update, + file=extras.get("file"), + fileMarkings=extras.get("fileMarkings"), ) else: self.opencti.app_logger.error( - "[opencti_caseIncident] Missing parameters: stixObject" + "[opencti_case_incident] Missing parameters: stixObject" ) + return None def delete(self, **kwargs): + """Delete a Case Incident object. + + :param id: the id of the Case Incident to delete + :type id: str + :return: None + """ id = kwargs.get("id", None) if id is not None: self.opencti.app_logger.info("Deleting Case Incident", {"id": id}) diff --git a/client-python/pycti/entities/opencti_case_rfi.py b/client-python/pycti/entities/opencti_case_rfi.py index 22296921b6ea..8fa5439825da 100644 --- a/client-python/pycti/entities/opencti_case_rfi.py +++ b/client-python/pycti/entities/opencti_case_rfi.py @@ -12,9 +12,15 @@ class CaseRfi: Manages RFI cases in the OpenCTI platform. :param opencti: instance of :py:class:`~pycti.api.opencti_api_client.OpenCTIApiClient` + :type opencti: OpenCTIApiClient """ def __init__(self, opencti): + """Initialize the CaseRfi instance. + + :param opencti: OpenCTI API client instance + :type opencti: OpenCTIApiClient + """ self.opencti = opencti self.properties = """ id @@ -474,6 +480,15 @@ def __init__(self, opencti): @staticmethod def generate_id(name, created): + """Generate a STIX ID for a Case RFI object. + + :param name: the name of the Case RFI + :type name: str + :param created: the creation date of the Case RFI + :type created: str or datetime.datetime + :return: STIX ID for the Case RFI + :rtype: str + """ name = name.lower().strip() if isinstance(created, datetime.datetime): created = created.isoformat() @@ -484,19 +499,41 @@ def generate_id(name, created): @staticmethod def generate_id_from_data(data): + """Generate a STIX ID from Case RFI data. + + :param data: Dictionary containing 'name' and 'created' keys + :type data: dict + :return: STIX ID for the Case RFI + :rtype: str + """ return CaseRfi.generate_id(data["name"], data["created"]) - """ - List Case Rfi objects + def list(self, **kwargs): + """List Case RFI objects. :param filters: the filters to apply + :type filters: dict :param search: the search keyword + :type search: str :param first: return the first n rows from the after ID (or the beginning if not set) + :type first: int :param after: ID of the first row for pagination - :return List of Case Rfi objects - """ - - def list(self, **kwargs): + :type after: str + :param orderBy: field to order results by + :type orderBy: str + :param orderMode: ordering mode (asc/desc) + :type orderMode: str + :param customAttributes: custom attributes to return + :type customAttributes: str + :param getAll: whether to retrieve all results + :type getAll: bool + :param withPagination: whether to include pagination info + :type withPagination: bool + :param withFiles: whether to include files + :type withFiles: bool + :return: List of Case RFI objects + :rtype: list + """ filters = kwargs.get("filters", None) search = kwargs.get("search", None) first = kwargs.get("first", 500) @@ -574,15 +611,20 @@ def list(self, **kwargs): result["data"]["caseRfis"], with_pagination ) - """ - Read a Case Rfi object + def read(self, **kwargs): + """Read a Case RFI object. - :param id: the id of the Case Rfi + :param id: the id of the Case RFI + :type id: str :param filters: the filters to apply if no id provided - :return Case Rfi object - """ - - def read(self, **kwargs): + :type filters: dict + :param customAttributes: custom attributes to return + :type customAttributes: str + :param withFiles: whether to include files + :type withFiles: bool + :return: Case RFI object + :rtype: dict or None + """ id = kwargs.get("id", None) filters = kwargs.get("filters", None) custom_attributes = kwargs.get("customAttributes", None) @@ -613,16 +655,18 @@ def read(self, **kwargs): else: return None - """ - Read a Case Rfi object by stix_id or name - - :param type: the Stix-Domain-Entity type - :param stix_id: the STIX ID of the Stix-Domain-Entity - :param name: the name of the Stix-Domain-Entity - :return Stix-Domain-Entity object - """ - def get_by_stix_id_or_name(self, **kwargs): + """Read a Case RFI object by stix_id or name. + + :param stix_id: the STIX ID of the Case RFI + :type stix_id: str + :param name: the name of the Case RFI + :type name: str + :param created: the creation date of the Case RFI + :type created: str + :return: Case RFI object + :rtype: dict or None + """ stix_id = kwargs.get("stix_id", None) name = kwargs.get("name", None) created = kwargs.get("created", None) @@ -645,15 +689,16 @@ def get_by_stix_id_or_name(self, **kwargs): ) return object_result - """ - Check if a case rfi already contains a thing (Stix Object or Stix Relationship) + def contains_stix_object_or_stix_relationship(self, **kwargs): + """Check if a Case RFI already contains a STIX Object or Relationship. - :param id: the id of the Case Rfi + :param id: the id of the Case RFI + :type id: str :param stixObjectOrStixRelationshipId: the id of the Stix-Entity - :return Boolean - """ - - def contains_stix_object_or_stix_relationship(self, **kwargs): + :type stixObjectOrStixRelationshipId: str + :return: Boolean indicating if the entity is contained + :rtype: bool or None + """ id = kwargs.get("id", None) stix_object_or_stix_relationship_id = kwargs.get( "stixObjectOrStixRelationshipId", None @@ -681,17 +726,68 @@ def contains_stix_object_or_stix_relationship(self, **kwargs): return result["data"]["caseRfiContainsStixObjectOrStixRelationship"] else: self.opencti.app_logger.error( - "[opencti_caseRfi] Missing parameters: id or stixObjectOrStixRelationshipId" + "[opencti_case_rfi] Missing parameters: id or stixObjectOrStixRelationshipId" ) - - """ - Create a Case Rfi object - - :param name: the name of the Case Rfi - :return Case Rfi object - """ + return None def create(self, **kwargs): + """Create a Case RFI (Request for Information) object. + + :param stix_id: (optional) the STIX ID + :type stix_id: str + :param createdBy: (optional) the author ID + :type createdBy: str + :param objects: (optional) list of STIX object IDs contained in the case + :type objects: list + :param objectMarking: (optional) list of marking definition IDs + :type objectMarking: list + :param objectLabel: (optional) list of label IDs + :type objectLabel: list + :param objectAssignee: (optional) list of assignee IDs + :type objectAssignee: list + :param objectParticipant: (optional) list of participant IDs + :type objectParticipant: list + :param externalReferences: (optional) list of external reference IDs + :type externalReferences: list + :param revoked: (optional) whether the case is revoked + :type revoked: bool + :param severity: (optional) severity level + :type severity: str + :param priority: (optional) priority level + :type priority: str + :param confidence: (optional) confidence level (0-100) + :type confidence: int + :param lang: (optional) language + :type lang: str + :param created: (optional) creation date + :type created: str + :param modified: (optional) modification date + :type modified: str + :param name: the name of the Case RFI (required) + :type name: str + :param content: (optional) content + :type content: str + :param description: (optional) description + :type description: str + :param x_opencti_stix_ids: (optional) list of additional STIX IDs + :type x_opencti_stix_ids: list + :param objectOrganization: (optional) list of organization IDs + :type objectOrganization: list + :param x_opencti_workflow_id: (optional) workflow ID + :type x_opencti_workflow_id: str + :param x_opencti_modified_at: (optional) custom modification date + :type x_opencti_modified_at: str + :param update: (optional) whether to update if exists (default: False) + :type update: bool + :param information_types: (optional) list of information types + :type information_types: list + :param file: (optional) File object to attach + :type file: dict + :param fileMarkings: (optional) list of marking definition IDs for the file + :type fileMarkings: list + :return: Case RFI object + :rtype: dict or None + """ stix_id = kwargs.get("stix_id", None) created_by = kwargs.get("createdBy", None) objects = kwargs.get("objects", None) @@ -716,6 +812,8 @@ def create(self, **kwargs): x_opencti_modified_at = kwargs.get("x_opencti_modified_at", None) update = kwargs.get("update", False) information_types = kwargs.get("information_types", None) + file = kwargs.get("file", None) + file_markings = kwargs.get("fileMarkings", None) if name is not None: self.opencti.app_logger.info("Creating Case Rfi", {"name": name}) @@ -729,50 +827,50 @@ def create(self, **kwargs): } } """ - result = self.opencti.query( - query, - { - "input": { - "stix_id": stix_id, - "createdBy": created_by, - "objectMarking": object_marking, - "objectLabel": object_label, - "objectOrganization": granted_refs, - "objectAssignee": object_assignee, - "objectParticipant": object_participant, - "objects": objects, - "externalReferences": external_references, - "revoked": revoked, - "severity": severity, - "priority": priority, - "confidence": confidence, - "lang": lang, - "created": created, - "modified": modified, - "name": name, - "description": description, - "content": content, - "x_opencti_stix_ids": x_opencti_stix_ids, - "x_opencti_workflow_id": x_opencti_workflow_id, - "x_opencti_modified_at": x_opencti_modified_at, - "update": update, - "information_types": information_types, - } - }, - ) + input_variables = { + "stix_id": stix_id, + "createdBy": created_by, + "objectMarking": object_marking, + "objectLabel": object_label, + "objectOrganization": granted_refs, + "objectAssignee": object_assignee, + "objectParticipant": object_participant, + "objects": objects, + "externalReferences": external_references, + "revoked": revoked, + "severity": severity, + "priority": priority, + "confidence": confidence, + "lang": lang, + "created": created, + "modified": modified, + "name": name, + "description": description, + "content": content, + "x_opencti_stix_ids": x_opencti_stix_ids, + "x_opencti_workflow_id": x_opencti_workflow_id, + "x_opencti_modified_at": x_opencti_modified_at, + "update": update, + "information_types": information_types, + "file": file, + "fileMarkings": file_markings, + } + result = self.opencti.query(query, {"input": input_variables}) return self.opencti.process_multiple_fields(result["data"]["caseRfiAdd"]) else: - self.opencti.app_logger.error("[opencti_caseRfi] Missing parameters: name") + self.opencti.app_logger.error("[opencti_case_rfi] Missing parameters: name") + return None - """ - Add a Stix-Entity object to Case Rfi object (object_refs) + def add_stix_object_or_stix_relationship(self, **kwargs): + """Add a Stix-Entity object to Case RFI object (object_refs). - :param id: the id of the Case Rfi + :param id: the id of the Case RFI + :type id: str :param stixObjectOrStixRelationshipId: the id of the Stix-Entity - :return Boolean - """ - - def add_stix_object_or_stix_relationship(self, **kwargs): + :type stixObjectOrStixRelationshipId: str + :return: Boolean indicating success + :rtype: bool + """ id = kwargs.get("id", None) stix_object_or_stix_relationship_id = kwargs.get( "stixObjectOrStixRelationshipId", None @@ -806,20 +904,21 @@ def add_stix_object_or_stix_relationship(self, **kwargs): ) return True else: - self.opencti.app_logger.info( - "[opencti_caseRfi] Missing parameters: id and stixObjectOrStixRelationshipId" + self.opencti.app_logger.error( + "[opencti_case_rfi] Missing parameters: id and stixObjectOrStixRelationshipId" ) return False - """ - Remove a Stix-Entity object to Case Rfi object (object_refs) + def remove_stix_object_or_stix_relationship(self, **kwargs): + """Remove a Stix-Entity object from Case RFI object (object_refs). - :param id: the id of the Case Rfi + :param id: the id of the Case RFI + :type id: str :param stixObjectOrStixRelationshipId: the id of the Stix-Entity - :return Boolean - """ - - def remove_stix_object_or_stix_relationship(self, **kwargs): + :type stixObjectOrStixRelationshipId: str + :return: Boolean indicating success + :rtype: bool + """ id = kwargs.get("id", None) stix_object_or_stix_relationship_id = kwargs.get( "stixObjectOrStixRelationshipId", None @@ -852,18 +951,22 @@ def remove_stix_object_or_stix_relationship(self, **kwargs): return True else: self.opencti.app_logger.error( - "[opencti_caseRfi] Missing parameters: id and stixObjectOrStixRelationshipId" + "[opencti_case_rfi] Missing parameters: id and stixObjectOrStixRelationshipId" ) return False - """ - Import a Case Rfi object from a STIX2 object + def import_from_stix2(self, **kwargs): + """Import a Case RFI object from a STIX2 object. - :param stixObject: the Stix-Object Case Rfi - :return Case Rfi object + :param stixObject: the STIX2 Case RFI object + :type stixObject: dict + :param extras: extra parameters including created_by_id, object_marking_ids, etc. + :type extras: dict + :param update: whether to update if the entity already exists + :type update: bool + :return: Case RFI object + :rtype: dict or None """ - - def import_from_stix2(self, **kwargs): stix_object = kwargs.get("stixObject", None) extras = kwargs.get("extras", {}) update = kwargs.get("update", False) @@ -978,13 +1081,22 @@ def import_from_stix2(self, **kwargs): if "information_types" in stix_object else None ), + file=extras.get("file"), + fileMarkings=extras.get("fileMarkings"), ) else: self.opencti.app_logger.error( - "[opencti_caseRfi] Missing parameters: stixObject" + "[opencti_case_rfi] Missing parameters: stixObject" ) + return None def delete(self, **kwargs): + """Delete a Case RFI object. + + :param id: the id of the Case RFI to delete + :type id: str + :return: None + """ id = kwargs.get("id", None) if id is not None: self.opencti.app_logger.info("Deleting Case RFI", {"id": id}) diff --git a/client-python/pycti/entities/opencti_case_rft.py b/client-python/pycti/entities/opencti_case_rft.py index 458386d9d01c..875d290f6166 100644 --- a/client-python/pycti/entities/opencti_case_rft.py +++ b/client-python/pycti/entities/opencti_case_rft.py @@ -12,9 +12,15 @@ class CaseRft: Manages RFT cases in the OpenCTI platform. :param opencti: instance of :py:class:`~pycti.api.opencti_api_client.OpenCTIApiClient` + :type opencti: OpenCTIApiClient """ def __init__(self, opencti): + """Initialize the CaseRft instance. + + :param opencti: OpenCTI API client instance + :type opencti: OpenCTIApiClient + """ self.opencti = opencti self.properties = """ id @@ -474,6 +480,15 @@ def __init__(self, opencti): @staticmethod def generate_id(name, created): + """Generate a STIX ID for a Case RFT object. + + :param name: the name of the Case RFT + :type name: str + :param created: the creation date of the Case RFT + :type created: str or datetime.datetime + :return: STIX ID for the Case RFT + :rtype: str + """ name = name.lower().strip() if isinstance(created, datetime.datetime): created = created.isoformat() @@ -484,19 +499,41 @@ def generate_id(name, created): @staticmethod def generate_id_from_data(data): + """Generate a STIX ID from Case RFT data. + + :param data: Dictionary containing 'name' and 'created' keys + :type data: dict + :return: STIX ID for the Case RFT + :rtype: str + """ return CaseRft.generate_id(data["name"], data["created"]) - """ - List Case Rft objects + def list(self, **kwargs): + """List Case RFT objects. :param filters: the filters to apply + :type filters: dict :param search: the search keyword + :type search: str :param first: return the first n rows from the after ID (or the beginning if not set) + :type first: int :param after: ID of the first row for pagination - :return List of Case Rft objects - """ - - def list(self, **kwargs): + :type after: str + :param orderBy: field to order results by + :type orderBy: str + :param orderMode: ordering mode (asc/desc) + :type orderMode: str + :param customAttributes: custom attributes to return + :type customAttributes: str + :param getAll: whether to retrieve all results + :type getAll: bool + :param withPagination: whether to include pagination info + :type withPagination: bool + :param withFiles: whether to include files + :type withFiles: bool + :return: List of Case RFT objects + :rtype: list + """ filters = kwargs.get("filters", None) search = kwargs.get("search", None) first = kwargs.get("first", 500) @@ -573,15 +610,20 @@ def list(self, **kwargs): result["data"]["caseRfts"], with_pagination ) - """ - Read a Case Rft object + def read(self, **kwargs): + """Read a Case RFT object. - :param id: the id of the Case Rft + :param id: the id of the Case RFT + :type id: str :param filters: the filters to apply if no id provided - :return Case Rft object - """ - - def read(self, **kwargs): + :type filters: dict + :param customAttributes: custom attributes to return + :type customAttributes: str + :param withFiles: whether to include files + :type withFiles: bool + :return: Case RFT object + :rtype: dict or None + """ id = kwargs.get("id", None) filters = kwargs.get("filters", None) custom_attributes = kwargs.get("customAttributes", None) @@ -612,16 +654,20 @@ def read(self, **kwargs): else: return None - """ - Read a Case Rft object by stix_id or name - - :param type: the Stix-Domain-Entity type - :param stix_id: the STIX ID of the Stix-Domain-Entity - :param name: the name of the Stix-Domain-Entity - :return Stix-Domain-Entity object - """ - def get_by_stix_id_or_name(self, **kwargs): + """Read a Case RFT object by stix_id or name. + + :param stix_id: the STIX ID of the Case RFT + :type stix_id: str + :param name: the name of the Case RFT + :type name: str + :param created: the creation date + :type created: str + :param customAttributes: custom attributes to return + :type customAttributes: str + :return: Case RFT object + :rtype: dict or None + """ stix_id = kwargs.get("stix_id", None) name = kwargs.get("name", None) created = kwargs.get("created", None) @@ -644,15 +690,16 @@ def get_by_stix_id_or_name(self, **kwargs): ) return object_result - """ - Check if a case rft already contains a thing (Stix Object or Stix Relationship) + def contains_stix_object_or_stix_relationship(self, **kwargs): + """Check if a Case RFT already contains a STIX Object or Relationship. - :param id: the id of the Case Rft + :param id: the id of the Case RFT + :type id: str :param stixObjectOrStixRelationshipId: the id of the Stix-Entity - :return Boolean - """ - - def contains_stix_object_or_stix_relationship(self, **kwargs): + :type stixObjectOrStixRelationshipId: str + :return: Boolean indicating if the entity is contained + :rtype: bool or None + """ id = kwargs.get("id", None) stix_object_or_stix_relationship_id = kwargs.get( "stixObjectOrStixRelationshipId", None @@ -680,17 +727,68 @@ def contains_stix_object_or_stix_relationship(self, **kwargs): return result["data"]["caseRftContainsStixObjectOrStixRelationship"] else: self.opencti.app_logger.error( - "[opencti_caseRft] Missing parameters: id or stixObjectOrStixRelationshipId" + "[opencti_case_rft] Missing parameters: id or stixObjectOrStixRelationshipId" ) - - """ - Create a Case Rft object - - :param name: the name of the Case Rft - :return Case Rft object - """ + return None def create(self, **kwargs): + """Create a Case RFT (Request for Takedown) object. + + :param stix_id: (optional) the STIX ID + :type stix_id: str + :param createdBy: (optional) the author ID + :type createdBy: str + :param objects: (optional) list of STIX object IDs contained in the case + :type objects: list + :param objectMarking: (optional) list of marking definition IDs + :type objectMarking: list + :param objectLabel: (optional) list of label IDs + :type objectLabel: list + :param objectAssignee: (optional) list of assignee IDs + :type objectAssignee: list + :param objectParticipant: (optional) list of participant IDs + :type objectParticipant: list + :param externalReferences: (optional) list of external reference IDs + :type externalReferences: list + :param revoked: (optional) whether the case is revoked + :type revoked: bool + :param severity: (optional) severity level + :type severity: str + :param priority: (optional) priority level + :type priority: str + :param confidence: (optional) confidence level (0-100) + :type confidence: int + :param lang: (optional) language + :type lang: str + :param content: (optional) content + :type content: str + :param created: (optional) creation date + :type created: str + :param modified: (optional) modification date + :type modified: str + :param name: the name of the Case RFT (required) + :type name: str + :param description: (optional) description + :type description: str + :param x_opencti_stix_ids: (optional) list of additional STIX IDs + :type x_opencti_stix_ids: list + :param objectOrganization: (optional) list of organization IDs + :type objectOrganization: list + :param x_opencti_workflow_id: (optional) workflow ID + :type x_opencti_workflow_id: str + :param x_opencti_modified_at: (optional) custom modification date + :type x_opencti_modified_at: str + :param update: (optional) whether to update if exists (default: False) + :type update: bool + :param takedown_types: (optional) list of takedown types + :type takedown_types: list + :param file: (optional) File object to attach + :type file: dict + :param fileMarkings: (optional) list of marking definition IDs for the file + :type fileMarkings: list + :return: Case RFT object + :rtype: dict or None + """ stix_id = kwargs.get("stix_id", None) created_by = kwargs.get("createdBy", None) objects = kwargs.get("objects", None) @@ -715,6 +813,8 @@ def create(self, **kwargs): x_opencti_modified_at = kwargs.get("x_opencti_modified_at", None) update = kwargs.get("update", False) takedown_types = kwargs.get("takedown_types", None) + file = kwargs.get("file", None) + file_markings = kwargs.get("fileMarkings", None) if name is not None: self.opencti.app_logger.info("Creating Case Rft", {"name": name}) @@ -728,50 +828,50 @@ def create(self, **kwargs): } } """ - result = self.opencti.query( - query, - { - "input": { - "stix_id": stix_id, - "createdBy": created_by, - "objectMarking": object_marking, - "objectLabel": object_label, - "objectOrganization": granted_refs, - "objectAssignee": object_assignee, - "objectParticipant": object_participant, - "objects": objects, - "externalReferences": external_references, - "revoked": revoked, - "severity": severity, - "priority": priority, - "content": content, - "confidence": confidence, - "lang": lang, - "created": created, - "modified": modified, - "name": name, - "description": description, - "x_opencti_stix_ids": x_opencti_stix_ids, - "x_opencti_workflow_id": x_opencti_workflow_id, - "x_opencti_modified_at": x_opencti_modified_at, - "update": update, - "takedown_types": takedown_types, - } - }, - ) + input_variables = { + "stix_id": stix_id, + "createdBy": created_by, + "objectMarking": object_marking, + "objectLabel": object_label, + "objectOrganization": granted_refs, + "objectAssignee": object_assignee, + "objectParticipant": object_participant, + "objects": objects, + "externalReferences": external_references, + "revoked": revoked, + "severity": severity, + "priority": priority, + "content": content, + "confidence": confidence, + "lang": lang, + "created": created, + "modified": modified, + "name": name, + "description": description, + "x_opencti_stix_ids": x_opencti_stix_ids, + "x_opencti_workflow_id": x_opencti_workflow_id, + "x_opencti_modified_at": x_opencti_modified_at, + "update": update, + "takedown_types": takedown_types, + "file": file, + "fileMarkings": file_markings, + } + result = self.opencti.query(query, {"input": input_variables}) return self.opencti.process_multiple_fields(result["data"]["caseRftAdd"]) else: - self.opencti.app_logger.error("[opencti_caseRft] Missing parameters: name") + self.opencti.app_logger.error("[opencti_case_rft] Missing parameters: name") + return None - """ - Add a Stix-Entity object to Case Rft object (object_refs) + def add_stix_object_or_stix_relationship(self, **kwargs): + """Add a Stix-Entity object to Case RFT object (object_refs). - :param id: the id of the Case Rft + :param id: the id of the Case RFT + :type id: str :param stixObjectOrStixRelationshipId: the id of the Stix-Entity - :return Boolean - """ - - def add_stix_object_or_stix_relationship(self, **kwargs): + :type stixObjectOrStixRelationshipId: str + :return: Boolean indicating success + :rtype: bool + """ id = kwargs.get("id", None) stix_object_or_stix_relationship_id = kwargs.get( "stixObjectOrStixRelationshipId", None @@ -805,20 +905,21 @@ def add_stix_object_or_stix_relationship(self, **kwargs): ) return True else: - self.opencti.app_logger.info( - "[opencti_caseRft] Missing parameters: id and stixObjectOrStixRelationshipId" + self.opencti.app_logger.error( + "[opencti_case_rft] Missing parameters: id and stixObjectOrStixRelationshipId" ) return False - """ - Remove a Stix-Entity object to Case Rft object (object_refs) + def remove_stix_object_or_stix_relationship(self, **kwargs): + """Remove a Stix-Entity object from Case RFT object (object_refs). - :param id: the id of the Case Rft + :param id: the id of the Case RFT + :type id: str :param stixObjectOrStixRelationshipId: the id of the Stix-Entity - :return Boolean - """ - - def remove_stix_object_or_stix_relationship(self, **kwargs): + :type stixObjectOrStixRelationshipId: str + :return: Boolean indicating success + :rtype: bool + """ id = kwargs.get("id", None) stix_object_or_stix_relationship_id = kwargs.get( "stixObjectOrStixRelationshipId", None @@ -851,18 +952,22 @@ def remove_stix_object_or_stix_relationship(self, **kwargs): return True else: self.opencti.app_logger.error( - "[opencti_caseRft] Missing parameters: id and stixObjectOrStixRelationshipId" + "[opencti_case_rft] Missing parameters: id and stixObjectOrStixRelationshipId" ) return False - """ - Import a Case Rft object from a STIX2 object + def import_from_stix2(self, **kwargs): + """Import a Case RFT object from a STIX2 object. - :param stixObject: the Stix-Object Case Rft - :return Case Rft object + :param stixObject: the STIX2 Case RFT object + :type stixObject: dict + :param extras: extra parameters including created_by_id, object_marking_ids, etc. + :type extras: dict + :param update: whether to update if the entity already exists + :type update: bool + :return: Case RFT object + :rtype: dict or None """ - - def import_from_stix2(self, **kwargs): stix_object = kwargs.get("stixObject", None) extras = kwargs.get("extras", {}) update = kwargs.get("update", False) @@ -976,13 +1081,22 @@ def import_from_stix2(self, **kwargs): else None ), update=update, + file=extras.get("file"), + fileMarkings=extras.get("fileMarkings"), ) else: self.opencti.app_logger.error( - "[opencti_caseRft] Missing parameters: stixObject" + "[opencti_case_rft] Missing parameters: stixObject" ) + return None def delete(self, **kwargs): + """Delete a Case RFT object. + + :param id: the id of the Case RFT to delete + :type id: str + :return: None + """ id = kwargs.get("id", None) if id is not None: self.opencti.app_logger.info("Deleting Case RFT", {"id": id}) diff --git a/client-python/pycti/entities/opencti_channel.py b/client-python/pycti/entities/opencti_channel.py index a0d8ee72f125..51e32b66d306 100644 --- a/client-python/pycti/entities/opencti_channel.py +++ b/client-python/pycti/entities/opencti_channel.py @@ -12,9 +12,15 @@ class Channel: Manages communication channels used by threat actors in the OpenCTI platform. :param opencti: instance of :py:class:`~pycti.api.opencti_api_client.OpenCTIApiClient` + :type opencti: OpenCTIApiClient """ def __init__(self, opencti): + """Initialize the Channel instance. + + :param opencti: OpenCTI API client instance + :type opencti: OpenCTIApiClient + """ self.opencti = opencti self.properties = """ id @@ -213,6 +219,13 @@ def __init__(self, opencti): @staticmethod def generate_id(name): + """Generate a STIX ID for a Channel. + + :param name: the name of the Channel + :type name: str + :return: STIX ID for the Channel + :rtype: str + """ name = name.lower().strip() data = {"name": name} data = canonicalize(data, utf8=False) @@ -221,19 +234,41 @@ def generate_id(name): @staticmethod def generate_id_from_data(data): + """Generate a STIX ID from Channel data. + + :param data: Dictionary containing a 'name' key + :type data: dict + :return: STIX ID for the Channel + :rtype: str + """ return Channel.generate_id(data["name"]) - """ - List Channel objects + def list(self, **kwargs): + """List Channel objects. :param filters: the filters to apply + :type filters: dict :param search: the search keyword + :type search: str :param first: return the first n rows from the after ID (or the beginning if not set) + :type first: int :param after: ID of the first row for pagination - :return List of Channel objects - """ - - def list(self, **kwargs): + :type after: str + :param orderBy: field to order results by + :type orderBy: str + :param orderMode: ordering mode (asc/desc) + :type orderMode: str + :param customAttributes: custom attributes to return + :type customAttributes: str + :param getAll: whether to retrieve all results + :type getAll: bool + :param withPagination: whether to include pagination info + :type withPagination: bool + :param withFiles: whether to include files + :type withFiles: bool + :return: List of Channel objects + :rtype: list + """ filters = kwargs.get("filters", None) search = kwargs.get("search", None) first = kwargs.get("first", 100) @@ -311,15 +346,20 @@ def list(self, **kwargs): result["data"]["channels"], with_pagination ) - """ - Read a Channel object + def read(self, **kwargs): + """Read a Channel object. :param id: the id of the Channel + :type id: str :param filters: the filters to apply if no id provided - :return Channel object - """ - - def read(self, **kwargs): + :type filters: dict + :param customAttributes: custom attributes to return + :type customAttributes: str + :param withFiles: whether to include files + :type withFiles: bool + :return: Channel object + :rtype: dict or None + """ id = kwargs.get("id", None) filters = kwargs.get("filters", None) custom_attributes = kwargs.get("customAttributes", None) @@ -355,14 +395,52 @@ def read(self, **kwargs): ) return None - """ - Create a Channel object - - :param name: the name of the Channel - :return Channel object - """ - def create(self, **kwargs): + """Create a Channel object. + + :param stix_id: (optional) the STIX ID + :type stix_id: str + :param createdBy: (optional) the author ID + :type createdBy: str + :param objectMarking: (optional) list of marking definition IDs + :type objectMarking: list + :param objectLabel: (optional) list of label IDs + :type objectLabel: list + :param externalReferences: (optional) list of external reference IDs + :type externalReferences: list + :param revoked: (optional) whether the channel is revoked + :type revoked: bool + :param confidence: (optional) confidence level (0-100) + :type confidence: int + :param lang: (optional) language + :type lang: str + :param created: (optional) creation date + :type created: str + :param modified: (optional) modification date + :type modified: str + :param name: the name of the Channel (required) + :type name: str + :param description: (optional) description + :type description: str + :param aliases: (optional) list of aliases + :type aliases: list + :param channel_types: (optional) list of channel types + :type channel_types: list + :param x_opencti_stix_ids: (optional) list of additional STIX IDs + :type x_opencti_stix_ids: list + :param objectOrganization: (optional) list of organization IDs + :type objectOrganization: list + :param x_opencti_modified_at: (optional) custom modification date + :type x_opencti_modified_at: str + :param update: (optional) whether to update if exists (default: False) + :type update: bool + :param file: (optional) File object to attach + :type file: dict + :param fileMarkings: (optional) list of marking definition IDs for the file + :type fileMarkings: list + :return: Channel object + :rtype: dict or None + """ stix_id = kwargs.get("stix_id", None) created_by = kwargs.get("createdBy", None) object_marking = kwargs.get("objectMarking", None) @@ -381,6 +459,8 @@ def create(self, **kwargs): granted_refs = kwargs.get("objectOrganization", None) x_opencti_modified_at = kwargs.get("x_opencti_modified_at", None) update = kwargs.get("update", False) + file = kwargs.get("file", None) + file_markings = kwargs.get("fileMarkings", None) if name is not None: self.opencti.app_logger.info("Creating Channel", {"name": name}) @@ -394,45 +474,46 @@ def create(self, **kwargs): } } """ - result = self.opencti.query( - query, - { - "input": { - "stix_id": stix_id, - "createdBy": created_by, - "objectMarking": object_marking, - "objectLabel": object_label, - "objectOrganization": granted_refs, - "externalReferences": external_references, - "revoked": revoked, - "confidence": confidence, - "lang": lang, - "created": created, - "modified": modified, - "name": name, - "description": description, - "aliases": aliases, - "channel_types": channel_types, - "x_opencti_stix_ids": x_opencti_stix_ids, - "x_opencti_modified_at": x_opencti_modified_at, - "update": update, - } - }, - ) + input_variables = { + "stix_id": stix_id, + "createdBy": created_by, + "objectMarking": object_marking, + "objectLabel": object_label, + "objectOrganization": granted_refs, + "externalReferences": external_references, + "revoked": revoked, + "confidence": confidence, + "lang": lang, + "created": created, + "modified": modified, + "name": name, + "description": description, + "aliases": aliases, + "channel_types": channel_types, + "x_opencti_stix_ids": x_opencti_stix_ids, + "x_opencti_modified_at": x_opencti_modified_at, + "update": update, + "file": file, + "fileMarkings": file_markings, + } + result = self.opencti.query(query, {"input": input_variables}) return self.opencti.process_multiple_fields(result["data"]["channelAdd"]) else: - self.opencti.app_logger.error( - "[opencti_channel] Missing parameters: name and description" - ) - - """ - Import an Channel object from a STIX2 object - - :param stixObject: the Stix-Object Channel - :return Channel object - """ + self.opencti.app_logger.error("[opencti_channel] Missing parameters: name") + return None def import_from_stix2(self, **kwargs): + """Import a Channel object from a STIX2 object. + + :param stixObject: the STIX2 Channel object + :type stixObject: dict + :param extras: extra parameters including created_by_id, object_marking_ids, etc. + :type extras: dict + :param update: whether to update if the entity already exists + :type update: bool + :return: Channel object + :rtype: dict or None + """ stix_object = kwargs.get("stixObject", None) extras = kwargs.get("extras", {}) update = kwargs.get("update", False) @@ -451,7 +532,7 @@ def import_from_stix2(self, **kwargs): self.opencti.get_attribute_in_extension("modified_at", stix_object) ) - return self.opencti.channel.create( + return self.create( stix_id=stix_object["id"], createdBy=( extras["created_by_id"] if "created_by_id" in extras else None @@ -504,8 +585,11 @@ def import_from_stix2(self, **kwargs): else None ), update=update, + file=extras.get("file"), + fileMarkings=extras.get("fileMarkings"), ) else: self.opencti.app_logger.error( "[opencti_channel] Missing parameters: stixObject" ) + return None diff --git a/client-python/pycti/entities/opencti_course_of_action.py b/client-python/pycti/entities/opencti_course_of_action.py index 8f00a37fed01..5023cdae0068 100644 --- a/client-python/pycti/entities/opencti_course_of_action.py +++ b/client-python/pycti/entities/opencti_course_of_action.py @@ -12,9 +12,15 @@ class CourseOfAction: Manages courses of action (mitigations) in the OpenCTI platform. :param opencti: instance of :py:class:`~pycti.api.opencti_api_client.OpenCTIApiClient` + :type opencti: OpenCTIApiClient """ def __init__(self, opencti): + """Initialize the CourseOfAction instance. + + :param opencti: OpenCTI API client instance + :type opencti: OpenCTIApiClient + """ self.opencti = opencti self.properties = """ id @@ -62,6 +68,11 @@ def __init__(self, opencti): x_opencti_lastname } } + objectOrganization { + id + standard_id + name + } objectMarking { id standard_id @@ -149,6 +160,11 @@ def __init__(self, opencti): x_opencti_lastname } } + objectOrganization { + id + standard_id + name + } objectMarking { id standard_id @@ -219,8 +235,17 @@ def __init__(self, opencti): @staticmethod def generate_id(name, x_mitre_id=None): + """Generate a STIX ID for a Course of Action. + + :param name: The name of the course of action + :type name: str + :param x_mitre_id: Optional MITRE ATT&CK ID + :type x_mitre_id: str or None + :return: STIX ID for the course of action + :rtype: str + """ if x_mitre_id is not None: - data = {"x_mitre_id": x_mitre_id} + data = {"x_mitre_id": x_mitre_id.strip()} else: data = {"name": name.lower().strip()} data = canonicalize(data, utf8=False) @@ -229,19 +254,41 @@ def generate_id(name, x_mitre_id=None): @staticmethod def generate_id_from_data(data): + """Generate a STIX ID from course of action data. + + :param data: Dictionary containing 'name' and optionally 'x_mitre_id' keys + :type data: dict + :return: STIX ID for the course of action + :rtype: str + """ return CourseOfAction.generate_id(data.get("name"), data.get("x_mitre_id")) - """ - List Course-Of-Action objects + def list(self, **kwargs): + """List Course of Action objects. :param filters: the filters to apply + :type filters: dict :param search: the search keyword + :type search: str :param first: return the first n rows from the after ID (or the beginning if not set) + :type first: int :param after: ID of the first row for pagination - :return List of Course-Of-Action objects - """ - - def list(self, **kwargs): + :type after: str + :param orderBy: field to order results by + :type orderBy: str + :param orderMode: ordering mode (asc/desc) + :type orderMode: str + :param customAttributes: custom attributes to return + :type customAttributes: str + :param getAll: whether to retrieve all results + :type getAll: bool + :param withPagination: whether to include pagination info + :type withPagination: bool + :param withFiles: whether to include files + :type withFiles: bool + :return: List of Course of Action objects + :rtype: list + """ filters = kwargs.get("filters", None) search = kwargs.get("search", None) first = kwargs.get("first", 500) @@ -321,15 +368,20 @@ def list(self, **kwargs): result["data"]["coursesOfAction"], with_pagination ) - """ - Read a Course-Of-Action object + def read(self, **kwargs): + """Read a Course of Action object. - :param id: the id of the Course-Of-Action + :param id: the id of the Course of Action + :type id: str :param filters: the filters to apply if no id provided - :return Course-Of-Action object - """ - - def read(self, **kwargs): + :type filters: dict + :param customAttributes: custom attributes to return + :type customAttributes: str + :param withFiles: whether to include files + :type withFiles: bool + :return: Course of Action object + :rtype: dict or None + """ id = kwargs.get("id", None) filters = kwargs.get("filters", None) custom_attributes = kwargs.get("customAttributes", None) @@ -367,14 +419,54 @@ def read(self, **kwargs): ) return None - """ - Create a Course Of Action object - - :param name: the name of the Course Of Action - :return Course Of Action object - """ - def create(self, **kwargs): + """Create a Course of Action object. + + :param name: the name of the Course of Action (required) + :type name: str + :param stix_id: (optional) the STIX ID + :type stix_id: str + :param createdBy: (optional) the author ID + :type createdBy: str + :param objectMarking: (optional) list of marking definition IDs + :type objectMarking: list + :param objectLabel: (optional) list of label IDs + :type objectLabel: list + :param externalReferences: (optional) list of external reference IDs + :type externalReferences: list + :param revoked: (optional) whether the course of action is revoked + :type revoked: bool + :param confidence: (optional) confidence level (0-100) + :type confidence: int + :param lang: (optional) language + :type lang: str + :param created: (optional) creation date + :type created: str + :param modified: (optional) modification date + :type modified: str + :param description: (optional) description + :type description: str + :param x_opencti_aliases: (optional) list of aliases + :type x_opencti_aliases: list + :param x_mitre_id: (optional) MITRE ATT&CK ID + :type x_mitre_id: str + :param x_opencti_stix_ids: (optional) list of additional STIX IDs + :type x_opencti_stix_ids: list + :param objectOrganization: (optional) list of organization IDs + :type objectOrganization: list + :param x_opencti_workflow_id: (optional) workflow ID + :type x_opencti_workflow_id: str + :param x_opencti_modified_at: (optional) custom modification date + :type x_opencti_modified_at: str + :param update: (optional) whether to update if exists (default: False) + :type update: bool + :param file: (optional) File object to attach + :type file: dict + :param fileMarkings: (optional) list of marking definition IDs for the file + :type fileMarkings: list + :return: Course of Action object + :rtype: dict or None + """ stix_id = kwargs.get("stix_id", None) created_by = kwargs.get("createdBy", None) object_marking = kwargs.get("objectMarking", None) @@ -394,6 +486,8 @@ def create(self, **kwargs): x_opencti_workflow_id = kwargs.get("x_opencti_workflow_id", None) x_opencti_modified_at = kwargs.get("x_opencti_modified_at", None) update = kwargs.get("update", False) + file = kwargs.get("file", None) + file_markings = kwargs.get("fileMarkings", None) if name is not None: self.opencti.app_logger.info("Creating Course Of Action", {"name": name}) @@ -430,6 +524,8 @@ def create(self, **kwargs): "x_opencti_workflow_id": x_opencti_workflow_id, "x_opencti_modified_at": x_opencti_modified_at, "update": update, + "file": file, + "fileMarkings": file_markings, } }, ) @@ -438,17 +534,22 @@ def create(self, **kwargs): ) else: self.opencti.app_logger.error( - "[opencti_course_of_action] Missing parameters: name and description" + "[opencti_course_of_action] Missing parameters: name" ) - - """ - Import an Course-Of-Action object from a STIX2 object - - :param stixObject: the Stix-Object Course-Of-Action - :return Course-Of-Action object - """ + return None def import_from_stix2(self, **kwargs): + """Import a Course of Action object from a STIX2 object. + + :param stixObject: the STIX2 Course of Action object + :type stixObject: dict + :param extras: extra parameters including created_by_id, object_marking_ids, etc. + :type extras: dict + :param update: whether to update if the entity already exists + :type update: bool + :return: Course of Action object + :rtype: dict or None + """ stix_object = kwargs.get("stixObject", None) extras = kwargs.get("extras", {}) update = kwargs.get("update", False) @@ -550,8 +651,11 @@ def import_from_stix2(self, **kwargs): else None ), update=update, + file=extras.get("file"), + fileMarkings=extras.get("fileMarkings"), ) else: self.opencti.app_logger.error( "[opencti_course_of_action] Missing parameters: stixObject" ) + return None diff --git a/client-python/pycti/entities/opencti_data_component.py b/client-python/pycti/entities/opencti_data_component.py index c61b5acced19..b67c1f338a0b 100644 --- a/client-python/pycti/entities/opencti_data_component.py +++ b/client-python/pycti/entities/opencti_data_component.py @@ -12,9 +12,15 @@ class DataComponent: Manages MITRE ATT&CK data components in the OpenCTI platform. :param opencti: instance of :py:class:`~pycti.api.opencti_api_client.OpenCTIApiClient` + :type opencti: OpenCTIApiClient """ def __init__(self, opencti): + """Initialize the DataComponent instance. + + :param opencti: OpenCTI API client instance + :type opencti: OpenCTIApiClient + """ self.opencti = opencti self.properties = """ id @@ -263,6 +269,13 @@ def __init__(self, opencti): @staticmethod def generate_id(name): + """Generate a STIX ID for a Data Component. + + :param name: the name of the Data Component + :type name: str + :return: STIX ID for the Data Component + :rtype: str + """ name = name.lower().strip() data = {"name": name} data = canonicalize(data, utf8=False) @@ -271,19 +284,41 @@ def generate_id(name): @staticmethod def generate_id_from_data(data): + """Generate a STIX ID from Data Component data. + + :param data: Dictionary containing a 'name' key + :type data: dict + :return: STIX ID for the Data Component + :rtype: str + """ return DataComponent.generate_id(data["name"]) - """ - List Data-Component objects + def list(self, **kwargs): + """List Data Component objects. :param filters: the filters to apply + :type filters: dict :param search: the search keyword + :type search: str :param first: return the first n rows from the after ID (or the beginning if not set) + :type first: int :param after: ID of the first row for pagination - :return List of Data-Component objects - """ - - def list(self, **kwargs): + :type after: str + :param orderBy: field to order results by + :type orderBy: str + :param orderMode: ordering mode (asc/desc) + :type orderMode: str + :param customAttributes: custom attributes to return + :type customAttributes: str + :param getAll: whether to retrieve all results + :type getAll: bool + :param withPagination: whether to include pagination info + :type withPagination: bool + :param withFiles: whether to include files + :type withFiles: bool + :return: List of Data Component objects + :rtype: list + """ filters = kwargs.get("filters", None) search = kwargs.get("search", None) first = kwargs.get("first", 500) @@ -363,15 +398,20 @@ def list(self, **kwargs): result["data"]["dataComponents"], with_pagination ) - """ - Read a Data-Component object + def read(self, **kwargs): + """Read a Data Component object. - :param id: the id of the Data-Component + :param id: the id of the Data Component + :type id: str :param filters: the filters to apply if no id provided - :return Data-Component object - """ - - def read(self, **kwargs): + :type filters: dict + :param customAttributes: custom attributes to return + :type customAttributes: str + :param withFiles: whether to include files + :type withFiles: bool + :return: Data Component object + :rtype: dict or None + """ id = kwargs.get("id", None) filters = kwargs.get("filters", None) custom_attributes = kwargs.get("customAttributes", None) @@ -407,14 +447,54 @@ def read(self, **kwargs): ) return None - """ - Create a Data Component object - - :param name: the name of the Data Component - :return Data Component object - """ - def create(self, **kwargs): + """Create a Data Component object. + + :param stix_id: (optional) the STIX ID + :type stix_id: str + :param createdBy: (optional) the author ID + :type createdBy: str + :param objectMarking: (optional) list of marking definition IDs + :type objectMarking: list + :param objectLabel: (optional) list of label IDs + :type objectLabel: list + :param externalReferences: (optional) list of external reference IDs + :type externalReferences: list + :param revoked: (optional) whether the data component is revoked + :type revoked: bool + :param confidence: (optional) confidence level (0-100) + :type confidence: int + :param lang: (optional) language + :type lang: str + :param created: (optional) creation date + :type created: str + :param modified: (optional) modification date + :type modified: str + :param name: the name of the Data Component (required) + :type name: str + :param description: (optional) description + :type description: str + :param dataSource: (optional) the data source ID + :type dataSource: str + :param aliases: (optional) list of aliases + :type aliases: list + :param x_opencti_stix_ids: (optional) list of additional STIX IDs + :type x_opencti_stix_ids: list + :param objectOrganization: (optional) list of organization IDs + :type objectOrganization: list + :param x_opencti_workflow_id: (optional) workflow ID + :type x_opencti_workflow_id: str + :param x_opencti_modified_at: (optional) custom modification date + :type x_opencti_modified_at: str + :param update: (optional) whether to update if exists (default: False) + :type update: bool + :param file: (optional) File object to attach + :type file: dict + :param fileMarkings: (optional) list of marking definition IDs for the file + :type fileMarkings: list + :return: Data Component object + :rtype: dict or None + """ stix_id = kwargs.get("stix_id", None) created_by = kwargs.get("createdBy", None) object_marking = kwargs.get("objectMarking", None) @@ -434,12 +514,11 @@ def create(self, **kwargs): x_opencti_workflow_id = kwargs.get("x_opencti_workflow_id", None) x_opencti_modified_at = kwargs.get("x_opencti_modified_at", None) update = kwargs.get("update", False) + file = kwargs.get("file", None) + file_markings = kwargs.get("fileMarkings", None) if name is not None: self.opencti.app_logger.info("Creating Data Component", {"name": name}) - self.opencti.app_logger.info( - "Creating Data Component", {"data": str(kwargs)} - ) query = """ mutation DataComponentAdd($input: DataComponentAddInput!) { dataComponentAdd(input: $input) { @@ -450,48 +529,51 @@ def create(self, **kwargs): } } """ - result = self.opencti.query( - query, - { - "input": { - "stix_id": stix_id, - "createdBy": created_by, - "objectMarking": object_marking, - "objectLabel": object_label, - "objectOrganization": granted_refs, - "externalReferences": external_references, - "revoked": revoked, - "confidence": confidence, - "lang": lang, - "created": created, - "modified": modified, - "name": name, - "description": description, - "aliases": aliases, - "dataSource": dataSource, - "x_opencti_stix_ids": x_opencti_stix_ids, - "x_opencti_workflow_id": x_opencti_workflow_id, - "x_opencti_modified_at": x_opencti_modified_at, - "update": update, - } - }, - ) + input_variables = { + "stix_id": stix_id, + "createdBy": created_by, + "objectMarking": object_marking, + "objectLabel": object_label, + "objectOrganization": granted_refs, + "externalReferences": external_references, + "revoked": revoked, + "confidence": confidence, + "lang": lang, + "created": created, + "modified": modified, + "name": name, + "description": description, + "aliases": aliases, + "dataSource": dataSource, + "x_opencti_stix_ids": x_opencti_stix_ids, + "x_opencti_workflow_id": x_opencti_workflow_id, + "x_opencti_modified_at": x_opencti_modified_at, + "update": update, + "file": file, + "fileMarkings": file_markings, + } + result = self.opencti.query(query, {"input": input_variables}) return self.opencti.process_multiple_fields( result["data"]["dataComponentAdd"] ) else: self.opencti.app_logger.error( - "[opencti_data_component] Missing parameters: name and description" + "[opencti_data_component] Missing parameters: name" ) - - """ - Import an Data-Component object from a STIX2 object - - :param stixObject: the Stix-Object Data-Component - :return Data-Component object - """ + return None def import_from_stix2(self, **kwargs): + """Import a Data Component object from a STIX2 object. + + :param stixObject: the STIX2 Data Component object + :type stixObject: dict + :param extras: extra parameters including created_by_id, object_marking_ids, etc. + :type extras: dict + :param update: whether to update if the entity already exists + :type update: bool + :return: Data Component object + :rtype: dict or None + """ stix_object = kwargs.get("stixObject", None) extras = kwargs.get("extras", {}) update = kwargs.get("update", False) @@ -527,7 +609,7 @@ def import_from_stix2(self, **kwargs): self.opencti.get_attribute_in_extension("modified_at", stix_object) ) - return self.opencti.data_component.create( + return self.create( stix_id=stix_object["id"], createdBy=( extras["created_by_id"] if "created_by_id" in extras else None @@ -583,13 +665,23 @@ def import_from_stix2(self, **kwargs): else None ), update=update, + file=extras.get("file"), + fileMarkings=extras.get("fileMarkings"), ) else: self.opencti.app_logger.error( - "[opencti_data_source] Missing parameters: stixObject" + "[opencti_data_component] Missing parameters: stixObject" ) + return None def process_multiple_fields(self, data): + """Process Data Component fields to extract related data source ID. + + :param data: the Data Component data dictionary + :type data: dict + :return: Processed data with dataSourceId field added + :rtype: dict + """ if "dataSource" in data and data["dataSource"] is not None: data["dataSourceId"] = data["dataSource"]["id"] else: diff --git a/client-python/pycti/entities/opencti_data_source.py b/client-python/pycti/entities/opencti_data_source.py index 1fcbbd3f09b1..025a971d5123 100644 --- a/client-python/pycti/entities/opencti_data_source.py +++ b/client-python/pycti/entities/opencti_data_source.py @@ -12,9 +12,15 @@ class DataSource: Manages MITRE ATT&CK data sources in the OpenCTI platform. :param opencti: instance of :py:class:`~pycti.api.opencti_api_client.OpenCTIApiClient` + :type opencti: OpenCTIApiClient """ def __init__(self, opencti): + """Initialize the DataSource instance. + + :param opencti: OpenCTI API client instance + :type opencti: OpenCTIApiClient + """ self.opencti = opencti self.properties = """ id @@ -62,6 +68,11 @@ def __init__(self, opencti): x_opencti_lastname } } + objectOrganization { + id + standard_id + name + } objectMarking { id standard_id @@ -150,6 +161,11 @@ def __init__(self, opencti): x_opencti_lastname } } + objectOrganization { + id + standard_id + name + } objectMarking { id standard_id @@ -221,6 +237,13 @@ def __init__(self, opencti): @staticmethod def generate_id(name): + """Generate a STIX ID for a Data Source. + + :param name: the name of the Data Source + :type name: str + :return: STIX ID for the Data Source + :rtype: str + """ name = name.lower().strip() data = {"name": name} data = canonicalize(data, utf8=False) @@ -229,19 +252,41 @@ def generate_id(name): @staticmethod def generate_id_from_data(data): + """Generate a STIX ID from Data Source data. + + :param data: Dictionary containing a 'name' key + :type data: dict + :return: STIX ID for the Data Source + :rtype: str + """ return DataSource.generate_id(data["name"]) - """ - List Data-Source objects + def list(self, **kwargs): + """List Data Source objects. :param filters: the filters to apply + :type filters: dict :param search: the search keyword + :type search: str :param first: return the first n rows from the after ID (or the beginning if not set) + :type first: int :param after: ID of the first row for pagination - :return List of Data-Source objects - """ - - def list(self, **kwargs): + :type after: str + :param orderBy: field to order results by + :type orderBy: str + :param orderMode: ordering mode (asc/desc) + :type orderMode: str + :param customAttributes: custom attributes to return + :type customAttributes: str + :param getAll: whether to retrieve all results + :type getAll: bool + :param withPagination: whether to include pagination info + :type withPagination: bool + :param withFiles: whether to include files + :type withFiles: bool + :return: List of Data Source objects + :rtype: list + """ filters = kwargs.get("filters", None) search = kwargs.get("search", None) first = kwargs.get("first", 500) @@ -319,15 +364,20 @@ def list(self, **kwargs): result["data"]["dataSources"], with_pagination ) - """ - Read a Data-Source object + def read(self, **kwargs): + """Read a Data Source object. - :param id: the id of the Data-Source + :param id: the id of the Data Source + :type id: str :param filters: the filters to apply if no id provided - :return Data-Source object - """ - - def read(self, **kwargs): + :type filters: dict + :param customAttributes: custom attributes to return + :type customAttributes: str + :param withFiles: whether to include files + :type withFiles: bool + :return: Data Source object + :rtype: dict or None + """ id = kwargs.get("id", None) filters = kwargs.get("filters", None) custom_attributes = kwargs.get("customAttributes", None) @@ -363,14 +413,56 @@ def read(self, **kwargs): ) return None - """ - Create a Data Source object - - :param name: the name of the Data Source - :return Data Source object - """ - def create(self, **kwargs): + """Create a Data Source object. + + :param stix_id: (optional) the STIX ID + :type stix_id: str + :param createdBy: (optional) the author ID + :type createdBy: str + :param objectMarking: (optional) list of marking definition IDs + :type objectMarking: list + :param objectLabel: (optional) list of label IDs + :type objectLabel: list + :param externalReferences: (optional) list of external reference IDs + :type externalReferences: list + :param revoked: (optional) whether the data source is revoked + :type revoked: bool + :param confidence: (optional) confidence level (0-100) + :type confidence: int + :param lang: (optional) language + :type lang: str + :param created: (optional) creation date + :type created: str + :param modified: (optional) modification date + :type modified: str + :param name: the name of the Data Source (required) + :type name: str + :param description: (optional) description + :type description: str + :param aliases: (optional) list of aliases + :type aliases: list + :param platforms: (optional) list of platforms + :type platforms: list + :param collection_layers: (optional) list of collection layers + :type collection_layers: list + :param x_opencti_stix_ids: (optional) list of additional STIX IDs + :type x_opencti_stix_ids: list + :param objectOrganization: (optional) list of organization IDs + :type objectOrganization: list + :param x_opencti_workflow_id: (optional) workflow ID + :type x_opencti_workflow_id: str + :param x_opencti_modified_at: (optional) custom modification date + :type x_opencti_modified_at: str + :param update: (optional) whether to update if exists (default: False) + :type update: bool + :param file: (optional) File object to attach + :type file: dict + :param fileMarkings: (optional) list of marking definition IDs for the file + :type fileMarkings: list + :return: Data Source object + :rtype: dict or None + """ stix_id = kwargs.get("stix_id", None) created_by = kwargs.get("createdBy", None) object_marking = kwargs.get("objectMarking", None) @@ -391,10 +483,11 @@ def create(self, **kwargs): x_opencti_workflow_id = kwargs.get("x_opencti_workflow_id", None) x_opencti_modified_at = kwargs.get("x_opencti_modified_at", None) update = kwargs.get("update", False) + file = kwargs.get("file", None) + file_markings = kwargs.get("fileMarkings", None) if name is not None: self.opencti.app_logger.info("Creating Data Source", {"name": name}) - self.opencti.app_logger.info("Creating Data Source", {"data": str(kwargs)}) query = """ mutation DataSourceAdd($input: DataSourceAddInput!) { dataSourceAdd(input: $input) { @@ -405,47 +498,50 @@ def create(self, **kwargs): } } """ - result = self.opencti.query( - query, - { - "input": { - "stix_id": stix_id, - "createdBy": created_by, - "objectMarking": object_marking, - "objectLabel": object_label, - "objectOrganization": granted_refs, - "externalReferences": external_references, - "revoked": revoked, - "confidence": confidence, - "lang": lang, - "created": created, - "modified": modified, - "name": name, - "description": description, - "aliases": aliases, - "x_mitre_platforms": platforms, - "collection_layers": collection_layers, - "x_opencti_stix_ids": x_opencti_stix_ids, - "x_opencti_workflow_id": x_opencti_workflow_id, - "x_opencti_modified_at": x_opencti_modified_at, - "update": update, - } - }, - ) + input_variables = { + "stix_id": stix_id, + "createdBy": created_by, + "objectMarking": object_marking, + "objectLabel": object_label, + "objectOrganization": granted_refs, + "externalReferences": external_references, + "revoked": revoked, + "confidence": confidence, + "lang": lang, + "created": created, + "modified": modified, + "name": name, + "description": description, + "aliases": aliases, + "x_mitre_platforms": platforms, + "collection_layers": collection_layers, + "x_opencti_stix_ids": x_opencti_stix_ids, + "x_opencti_workflow_id": x_opencti_workflow_id, + "x_opencti_modified_at": x_opencti_modified_at, + "update": update, + "file": file, + "fileMarkings": file_markings, + } + result = self.opencti.query(query, {"input": input_variables}) return self.opencti.process_multiple_fields(result["data"]["dataSourceAdd"]) else: self.opencti.app_logger.error( - "[opencti_data_source] Missing parameters: name and description" + "[opencti_data_source] Missing parameters: name" ) - - """ - Import an Data-Source object from a STIX2 object - - :param stixObject: the Stix-Object Data-Source - :return Data-Source object - """ + return None def import_from_stix2(self, **kwargs): + """Import a Data Source object from a STIX2 object. + + :param stixObject: the STIX2 Data Source object + :type stixObject: dict + :param extras: extra parameters including created_by_id, object_marking_ids, etc. + :type extras: dict + :param update: whether to update if the entity already exists + :type update: bool + :return: Data Source object + :rtype: dict or None + """ stix_object = kwargs.get("stixObject", None) extras = kwargs.get("extras", {}) update = kwargs.get("update", False) @@ -483,7 +579,7 @@ def import_from_stix2(self, **kwargs): self.opencti.get_attribute_in_extension("modified_at", stix_object) ) - return self.opencti.data_source.create( + return self.create( stix_id=stix_object["id"], createdBy=( extras["created_by_id"] if "created_by_id" in extras else None @@ -544,8 +640,11 @@ def import_from_stix2(self, **kwargs): else None ), update=update, + file=extras.get("file"), + fileMarkings=extras.get("fileMarkings"), ) else: self.opencti.app_logger.error( "[opencti_data_source] Missing parameters: stixObject" ) + return None diff --git a/client-python/pycti/entities/opencti_event.py b/client-python/pycti/entities/opencti_event.py index 7c1556dc5139..ce63a0b7b14d 100644 --- a/client-python/pycti/entities/opencti_event.py +++ b/client-python/pycti/entities/opencti_event.py @@ -12,9 +12,15 @@ class Event: Manages security events in the OpenCTI platform. :param opencti: instance of :py:class:`~pycti.api.opencti_api_client.OpenCTIApiClient` + :type opencti: OpenCTIApiClient """ def __init__(self, opencti): + """Initialize the Event instance. + + :param opencti: OpenCTI API client instance + :type opencti: OpenCTIApiClient + """ self.opencti = opencti self.properties = """ id @@ -261,15 +267,25 @@ def list(self, **kwargs): """List Event objects. :param filters: the filters to apply + :type filters: dict :param search: the search keyword + :type search: str :param first: return the first n rows from the after ID (or the beginning if not set) + :type first: int :param after: ID of the first row for pagination + :type after: str :param orderBy: field to order results by + :type orderBy: str :param orderMode: ordering mode (asc/desc) + :type orderMode: str :param customAttributes: custom attributes to return + :type customAttributes: str :param getAll: whether to retrieve all results + :type getAll: bool :param withPagination: whether to include pagination info + :type withPagination: bool :param withFiles: whether to include files + :type withFiles: bool :return: List of Event objects :rtype: list """ @@ -350,15 +366,20 @@ def list(self, **kwargs): result["data"]["events"], with_pagination ) - """ - Read a Event object + def read(self, **kwargs): + """Read an Event object. :param id: the id of the Event + :type id: str :param filters: the filters to apply if no id provided - :return Event object - """ - - def read(self, **kwargs): + :type filters: dict + :param customAttributes: custom attributes to return + :type customAttributes: str + :param withFiles: whether to include files + :type withFiles: bool + :return: Event object + :rtype: dict or None + """ id = kwargs.get("id", None) filters = kwargs.get("filters", None) custom_attributes = kwargs.get("customAttributes", None) @@ -394,14 +415,54 @@ def read(self, **kwargs): ) return None - """ - Create a Event object - - :param name: the name of the Event - :return Event object - """ - def create(self, **kwargs): + """Create an Event object. + + :param stix_id: the STIX ID (optional) + :type stix_id: str + :param createdBy: the author ID (optional) + :type createdBy: str + :param objectMarking: list of marking definition IDs (optional) + :type objectMarking: list + :param objectLabel: list of label IDs (optional) + :type objectLabel: list + :param externalReferences: list of external reference IDs (optional) + :type externalReferences: list + :param revoked: whether the event is revoked (optional) + :type revoked: bool + :param confidence: confidence level 0-100 (optional) + :type confidence: int + :param lang: language (optional) + :type lang: str + :param created: creation date (optional) + :type created: str + :param modified: modification date (optional) + :type modified: str + :param name: the name of the Event (required) + :type name: str + :param description: description (optional) + :type description: str + :param aliases: list of aliases (optional) + :type aliases: list + :param start_time: start time of the event (optional) + :type start_time: str + :param stop_time: stop time of the event (optional) + :type stop_time: str + :param event_types: list of event types (optional) + :type event_types: list + :param x_opencti_stix_ids: list of additional STIX IDs (optional) + :type x_opencti_stix_ids: list + :param x_opencti_modified_at: custom modification date (optional) + :type x_opencti_modified_at: str + :param update: whether to update if exists (default: False) + :type update: bool + :param file: File object to attach (optional) + :type file: File + :param fileMarkings: list of marking definition IDs for the file (optional) + :type fileMarkings: list + :return: Event object + :rtype: dict or None + """ stix_id = kwargs.get("stix_id", None) created_by = kwargs.get("createdBy", None) object_marking = kwargs.get("objectMarking", None) @@ -421,6 +482,8 @@ def create(self, **kwargs): x_opencti_stix_ids = kwargs.get("x_opencti_stix_ids", None) x_opencti_modified_at = kwargs.get("x_opencti_modified_at", None) update = kwargs.get("update", False) + file = kwargs.get("file", None) + file_markings = kwargs.get("fileMarkings", None) if name is not None: self.opencti.app_logger.info("Creating Event", {"name": name}) @@ -434,46 +497,47 @@ def create(self, **kwargs): } } """ - result = self.opencti.query( - query, - { - "input": { - "stix_id": stix_id, - "createdBy": created_by, - "objectMarking": object_marking, - "objectLabel": object_label, - "externalReferences": external_references, - "revoked": revoked, - "confidence": confidence, - "lang": lang, - "created": created, - "modified": modified, - "name": name, - "description": description, - "aliases": aliases, - "start_time": start_time, - "stop_time": stop_time, - "event_types": event_types, - "x_opencti_stix_ids": x_opencti_stix_ids, - "x_opencti_modified_at": x_opencti_modified_at, - "update": update, - } - }, - ) + input_variables = { + "stix_id": stix_id, + "createdBy": created_by, + "objectMarking": object_marking, + "objectLabel": object_label, + "externalReferences": external_references, + "revoked": revoked, + "confidence": confidence, + "lang": lang, + "created": created, + "modified": modified, + "name": name, + "description": description, + "aliases": aliases, + "start_time": start_time, + "stop_time": stop_time, + "event_types": event_types, + "x_opencti_stix_ids": x_opencti_stix_ids, + "x_opencti_modified_at": x_opencti_modified_at, + "update": update, + "file": file, + "fileMarkings": file_markings, + } + result = self.opencti.query(query, {"input": input_variables}) return self.opencti.process_multiple_fields(result["data"]["eventAdd"]) else: - self.opencti.app_logger.error( - "[opencti_event] Missing parameters: name and description" - ) + self.opencti.app_logger.error("[opencti_event] Missing parameters: name") + return None - """ - Import an Event object from a STIX2 object + def import_from_stix2(self, **kwargs): + """Import an Event object from a STIX2 object. :param stixObject: the Stix-Object Event - :return Event object - """ - - def import_from_stix2(self, **kwargs): + :type stixObject: dict + :param extras: additional parameters like created_by_id, object_marking_ids + :type extras: dict + :param update: whether to update existing object + :type update: bool + :return: Event object + :rtype: dict or None + """ stix_object = kwargs.get("stixObject", None) extras = kwargs.get("extras", {}) update = kwargs.get("update", False) @@ -492,7 +556,7 @@ def import_from_stix2(self, **kwargs): self.opencti.get_attribute_in_extension("modified_at", stix_object) ) - return self.opencti.event.create( + return self.create( stix_id=stix_object["id"], createdBy=( extras["created_by_id"] if "created_by_id" in extras else None @@ -544,8 +608,11 @@ def import_from_stix2(self, **kwargs): else None ), update=update, + file=extras.get("file"), + fileMarkings=extras.get("fileMarkings"), ) else: self.opencti.app_logger.error( "[opencti_event] Missing parameters: stixObject" ) + return None diff --git a/client-python/pycti/entities/opencti_external_reference.py b/client-python/pycti/entities/opencti_external_reference.py index b8f0b05fe6d6..0889e55fe3c9 100644 --- a/client-python/pycti/entities/opencti_external_reference.py +++ b/client-python/pycti/entities/opencti_external_reference.py @@ -14,12 +14,16 @@ class ExternalReference: Manages external references and citations in the OpenCTI platform. :param opencti: instance of :py:class:`~pycti.api.opencti_api_client.OpenCTIApiClient` - :param file: file handling configuration + :type opencti: OpenCTIApiClient """ - def __init__(self, opencti, file): + def __init__(self, opencti): + """Initialize the ExternalReference instance. + + :param opencti: OpenCTI API client instance + :type opencti: OpenCTIApiClient + """ self.opencti = opencti - self.file = file self.properties = """ id standard_id @@ -66,6 +70,17 @@ def __init__(self, opencti, file): @staticmethod def generate_id(url=None, source_name=None, external_id=None): + """Generate a STIX ID for an External Reference. + + :param url: The URL of the external reference + :type url: str or None + :param source_name: The source name + :type source_name: str or None + :param external_id: The external ID + :type external_id: str or None + :return: STIX ID for the external reference, or None if insufficient parameters + :rtype: str or None + """ if url is not None: data = {"url": url} elif source_name is not None and external_id is not None: @@ -78,20 +93,41 @@ def generate_id(url=None, source_name=None, external_id=None): @staticmethod def generate_id_from_data(data): + """Generate a STIX ID from external reference data. + + :param data: Dictionary containing 'url', 'source_name', or 'external_id' keys + :type data: dict + :return: STIX ID for the external reference + :rtype: str or None + """ return ExternalReference.generate_id( data.get("url"), data.get("source_name"), data.get("external_id") ) - """ - List External-Reference objects + def list(self, **kwargs): + """List External-Reference objects. :param filters: the filters to apply + :type filters: dict :param first: return the first n rows from the after ID (or the beginning if not set) + :type first: int :param after: ID of the first row for pagination - :return List of External-Reference objects - """ - - def list(self, **kwargs): + :type after: str + :param orderBy: field to order results by + :type orderBy: str + :param orderMode: ordering mode (asc/desc) + :type orderMode: str + :param customAttributes: custom attributes to return + :type customAttributes: list + :param getAll: whether to retrieve all results + :type getAll: bool + :param withPagination: whether to include pagination info + :type withPagination: bool + :param withFiles: whether to include files + :type withFiles: bool + :return: List of External-Reference objects + :rtype: list + """ filters = kwargs.get("filters", None) first = kwargs.get("first", 500) after = kwargs.get("after", None) @@ -147,7 +183,7 @@ def list(self, **kwargs): final_data = final_data + data while result["data"]["externalReferences"]["pageInfo"]["hasNextPage"]: after = result["data"]["externalReferences"]["pageInfo"]["endCursor"] - self.opencti.app_logger.info( + self.opencti.app_logger.debug( "Listing External-References", {"after": after} ) result = self.opencti.query( @@ -170,15 +206,16 @@ def list(self, **kwargs): result["data"]["externalReferences"], with_pagination ) - """ - Read a External-Reference object + def read(self, **kwargs): + """Read an External-Reference object. :param id: the id of the External-Reference + :type id: str :param filters: the filters to apply if no id provided - :return External-Reference object - """ - - def read(self, **kwargs): + :type filters: dict + :return: External-Reference object + :rtype: dict or None + """ id = kwargs.get("id", None) filters = kwargs.get("filters", None) if id is not None: @@ -210,14 +247,34 @@ def read(self, **kwargs): ) return None - """ - Create a External Reference object - - :param source_name: the source_name of the External Reference - :return External Reference object - """ - def create(self, **kwargs): + """Create an External Reference object. + + :param stix_id: (optional) the STIX ID + :type stix_id: str + :param created: (optional) creation date + :type created: datetime + :param modified: (optional) modification date + :type modified: datetime + :param source_name: the source name of the External Reference (required if no url) + :type source_name: str + :param url: (optional) the URL of the external reference (required if no source_name) + :type url: str + :param external_id: (optional) the external ID + :type external_id: str + :param description: (optional) description + :type description: str + :param x_opencti_stix_ids: (optional) list of additional STIX IDs + :type x_opencti_stix_ids: list + :param update: (optional) whether to update if exists (default: False) + :type update: bool + :param file: (optional) File object to attach + :type file: dict + :param fileMarkings: (optional) list of marking definition IDs for the file + :type fileMarkings: list + :return: External Reference object + :rtype: dict or None + """ stix_id = kwargs.get("stix_id", None) created = kwargs.get("created", None) modified = kwargs.get("modified", None) @@ -227,6 +284,8 @@ def create(self, **kwargs): description = kwargs.get("description", None) x_opencti_stix_ids = kwargs.get("x_opencti_stix_ids", None) update = kwargs.get("update", False) + file = kwargs.get("file", None) + file_markings = kwargs.get("fileMarkings", None) if source_name is not None or url is not None: self.opencti.app_logger.info( @@ -243,22 +302,20 @@ def create(self, **kwargs): } """ ) - result = self.opencti.query( - query, - { - "input": { - "stix_id": stix_id, - "created": created, - "modified": modified, - "source_name": source_name, - "external_id": external_id, - "description": description, - "url": url, - "x_opencti_stix_ids": x_opencti_stix_ids, - "update": update, - } - }, - ) + input_variables = { + "stix_id": stix_id, + "created": created, + "modified": modified, + "source_name": source_name, + "external_id": external_id, + "description": description, + "url": url, + "x_opencti_stix_ids": x_opencti_stix_ids, + "update": update, + "file": file, + "fileMarkings": file_markings, + } + result = self.opencti.query(query, {"input": input_variables}) return self.opencti.process_multiple_fields( result["data"]["externalReferenceAdd"] ) @@ -266,17 +323,30 @@ def create(self, **kwargs): self.opencti.app_logger.error( "[opencti_external_reference] Missing parameters: source_name and url" ) - - """ - Upload a file in this External-Reference - - :param id: the Stix-Domain-Object id - :param file_name - :param data - :return void - """ + return None def add_file(self, **kwargs): + """Upload a file in this External-Reference. + + :param id: the External-Reference id + :type id: str + :param file_name: the name of the file to upload + :type file_name: str + :param data: the file data (if None, reads from file_name path) + :type data: bytes or None + :param version: (optional) the file version date + :type version: datetime + :param fileMarkings: (optional) list of marking definition IDs for the file + :type fileMarkings: list + :param mime_type: (optional) MIME type (default: text/plain) + :type mime_type: str + :param no_trigger_import: (optional) don't trigger import (default: False) + :type no_trigger_import: bool + :param embedded: (optional) embed the file (default: False) + :type embedded: bool + :return: File upload result + :rtype: dict or None + """ id = kwargs.get("id", None) file_name = kwargs.get("file_name", None) data = kwargs.get("data", None) @@ -304,14 +374,14 @@ def add_file(self, **kwargs): else: mime_type = magic.from_file(file_name, mime=True) self.opencti.app_logger.info( - "Uploading a file in Stix-Domain-Object", + "Uploading a file in External-Reference", {"file": final_file_name, "id": id}, ) return self.opencti.query( query, { "id": id, - "file": (self.file(final_file_name, data, mime_type)), + "file": (self.opencti.file(final_file_name, data, mime_type)), "fileMarkings": file_markings, "version": version, "noTriggerImport": ( @@ -324,19 +394,20 @@ def add_file(self, **kwargs): ) else: self.opencti.app_logger.error( - "[opencti_stix_domain_object] Missing parameters: id or file_name" + "[opencti_external_reference] Missing parameters: id or file_name" ) return None - """ - Update a External Reference object field + def update_field(self, **kwargs): + """Update an External Reference object field. :param id: the External Reference id + :type id: str :param input: the input of the field - :return The updated External Reference object - """ - - def update_field(self, **kwargs): + :type input: list + :return: The updated External Reference object + :rtype: dict or None + """ id = kwargs.get("id", None) input = kwargs.get("input", None) if id is not None and input is not None: @@ -361,6 +432,12 @@ def update_field(self, **kwargs): return None def delete(self, id): + """Delete an External-Reference object. + + :param id: the id of the External-Reference to delete + :type id: str + :return: None + """ self.opencti.app_logger.info("Deleting External-Reference", {"id": id}) query = """ mutation ExternalReferenceEdit($id: ID!) { @@ -372,6 +449,13 @@ def delete(self, id): self.opencti.query(query, {"id": id}) def list_files(self, **kwargs): + """List files attached to an External-Reference. + + :param id: the id of the External-Reference + :type id: str + :return: List of files + :rtype: list + """ id = kwargs.get("id", None) self.opencti.app_logger.debug("Listing files of External-Reference", {"id": id}) query = """ diff --git a/client-python/pycti/entities/opencti_feedback.py b/client-python/pycti/entities/opencti_feedback.py index b1aa43ad2d41..cb995f4c2887 100644 --- a/client-python/pycti/entities/opencti_feedback.py +++ b/client-python/pycti/entities/opencti_feedback.py @@ -11,9 +11,15 @@ class Feedback: Manages feedback and analyst assessments in the OpenCTI platform. :param opencti: instance of :py:class:`~pycti.api.opencti_api_client.OpenCTIApiClient` + :type opencti: OpenCTIApiClient """ def __init__(self, opencti): + """Initialize the Feedback instance. + + :param opencti: OpenCTI API client instance + :type opencti: OpenCTIApiClient + """ self.opencti = opencti self.properties = """ id @@ -436,6 +442,13 @@ def __init__(self, opencti): @staticmethod def generate_id(name): + """Generate a STIX ID for a Feedback object. + + :param name: the name of the Feedback + :type name: str + :return: STIX ID for the Feedback + :rtype: str + """ name = name.lower().strip() data = {"name": name} data = canonicalize(data, utf8=False) @@ -444,19 +457,29 @@ def generate_id(name): @staticmethod def generate_id_from_data(data): + """Generate a STIX ID from Feedback data. + + :param data: Dictionary containing a 'name' key + :type data: dict + :return: STIX ID for the Feedback + :rtype: str + """ return Feedback.generate_id(data["name"]) - """ - List Feedback objects - + def list(self, **kwargs): + """List Feedback objects. + :param filters: the filters to apply + :type filters: dict :param search: the search keyword + :type search: str :param first: return the first n rows from the after ID (or the beginning if not set) + :type first: int :param after: ID of the first row for pagination - :return List of Feedback objects - """ - - def list(self, **kwargs): + :type after: str + :return: List of Feedback objects + :rtype: list + """ filters = kwargs.get("filters", None) search = kwargs.get("search", None) first = kwargs.get("first", 500) @@ -534,15 +557,16 @@ def list(self, **kwargs): result["data"]["feedbacks"], with_pagination ) - """ - Read a Feedback object + def read(self, **kwargs): + """Read a Feedback object. :param id: the id of the Feedback + :type id: str :param filters: the filters to apply if no id provided - :return Feedback object - """ - - def read(self, **kwargs): + :type filters: dict + :return: Feedback object + :rtype: dict or None + """ id = kwargs.get("id", None) filters = kwargs.get("filters", None) custom_attributes = kwargs.get("customAttributes", None) @@ -573,18 +597,18 @@ def read(self, **kwargs): else: return None - # TODO: read by stix id or name ? - - """ - Read a Feedback object by stix_id or name - - :param type: the Stix-Domain-Entity type - :param stix_id: the STIX ID of the Stix-Domain-Entity - :param name: the name of the Stix-Domain-Entity - :return Stix-Domain-Entity object - """ - def get_by_stix_id_or_name(self, **kwargs): + """Read a Feedback object by stix_id or name. + + :param stix_id: the STIX ID of the Feedback + :type stix_id: str + :param name: the name of the Feedback + :type name: str + :param created: the creation date of the Feedback + :type created: str + :return: Feedback object + :rtype: dict or None + """ stix_id = kwargs.get("stix_id", None) name = kwargs.get("name", None) created = kwargs.get("created", None) @@ -607,15 +631,16 @@ def get_by_stix_id_or_name(self, **kwargs): ) return object_result - """ - Check if a feedback already contains a thing (Stix Object or Stix Relationship) + def contains_stix_object_or_stix_relationship(self, **kwargs): + """Check if a feedback already contains a thing (Stix Object or Stix Relationship). :param id: the id of the Feedback + :type id: str :param stixObjectOrStixRelationshipId: the id of the Stix-Entity - :return Boolean - """ - - def contains_stix_object_or_stix_relationship(self, **kwargs): + :type stixObjectOrStixRelationshipId: str + :return: True if contained, False otherwise + :rtype: bool or None + """ id = kwargs.get("id", None) stix_object_or_stix_relationship_id = kwargs.get( "stixObjectOrStixRelationshipId", None @@ -645,15 +670,56 @@ def contains_stix_object_or_stix_relationship(self, **kwargs): self.opencti.app_logger.error( "[opencti_feedback] Missing parameters: id or stixObjectOrStixRelationshipId" ) - - """ - Create a Feedback object - - :param name: the name of the Feedback - :return Feedback object - """ + return None def create(self, **kwargs): + """Create a Feedback object. + + :param stix_id: the STIX ID (optional) + :type stix_id: str + :param createdBy: the author ID (optional) + :type createdBy: str + :param objects: list of STIX object IDs (optional) + :type objects: list + :param objectMarking: list of marking definition IDs (optional) + :type objectMarking: list + :param objectLabel: list of label IDs (optional) + :type objectLabel: list + :param externalReferences: list of external reference IDs (optional) + :type externalReferences: list + :param revoked: whether the feedback is revoked (optional) + :type revoked: bool + :param confidence: confidence level 0-100 (optional) + :type confidence: int + :param lang: language (optional) + :type lang: str + :param created: creation date (optional) + :type created: str + :param modified: modification date (optional) + :type modified: str + :param name: the name of the Feedback (required) + :type name: str + :param description: description (optional) + :type description: str + :param rating: rating value (optional) + :type rating: int + :param x_opencti_stix_ids: list of additional STIX IDs (optional) + :type x_opencti_stix_ids: list + :param objectOrganization: list of organization IDs (optional) + :type objectOrganization: list + :param x_opencti_workflow_id: workflow ID (optional) + :type x_opencti_workflow_id: str + :param x_opencti_modified_at: custom modification date (optional) + :type x_opencti_modified_at: str + :param update: whether to update if exists (default: False) + :type update: bool + :param file: File object to attach (optional) + :type file: File + :param fileMarkings: list of marking definition IDs for the file (optional) + :type fileMarkings: list + :return: Feedback object + :rtype: dict or None + """ stix_id = kwargs.get("stix_id", None) created_by = kwargs.get("createdBy", None) objects = kwargs.get("objects", None) @@ -673,6 +739,8 @@ def create(self, **kwargs): x_opencti_workflow_id = kwargs.get("x_opencti_workflow_id", None) x_opencti_modified_at = kwargs.get("x_opencti_modified_at", None) update = kwargs.get("update", False) + file = kwargs.get("file", None) + file_markings = kwargs.get("fileMarkings", None) if name is not None: self.opencti.app_logger.info("Creating Feedback", {"name": name}) @@ -686,37 +754,45 @@ def create(self, **kwargs): } } """ - result = self.opencti.query( - query, - { - "input": { - "stix_id": stix_id, - "createdBy": created_by, - "objectMarking": object_marking, - "objectLabel": object_label, - "objectOrganization": granted_refs, - "objects": objects, - "externalReferences": external_references, - "revoked": revoked, - "confidence": confidence, - "lang": lang, - "created": created, - "modified": modified, - "name": name, - "description": description, - "rating": rating, - "x_opencti_stix_ids": x_opencti_stix_ids, - "x_opencti_workflow_id": x_opencti_workflow_id, - "x_opencti_modified_at": x_opencti_modified_at, - "update": update, - } - }, - ) + input_variables = { + "stix_id": stix_id, + "createdBy": created_by, + "objectMarking": object_marking, + "objectLabel": object_label, + "objectOrganization": granted_refs, + "objects": objects, + "externalReferences": external_references, + "revoked": revoked, + "confidence": confidence, + "lang": lang, + "created": created, + "modified": modified, + "name": name, + "description": description, + "rating": rating, + "x_opencti_stix_ids": x_opencti_stix_ids, + "x_opencti_workflow_id": x_opencti_workflow_id, + "x_opencti_modified_at": x_opencti_modified_at, + "update": update, + "file": file, + "fileMarkings": file_markings, + } + result = self.opencti.query(query, {"input": input_variables}) return self.opencti.process_multiple_fields(result["data"]["feedbackAdd"]) else: self.opencti.app_logger.error("[opencti_feedback] Missing parameters: name") + return None def update_field(self, **kwargs): + """Update a field of a Feedback object. + + :param id: the id of the Feedback + :type id: str + :param input: the input containing field(s) to update + :type input: list + :return: Feedback object + :rtype: dict or None + """ self.opencti.app_logger.info("Updating Feedback", {"data": json.dumps(kwargs)}) id = kwargs.get("id", None) input = kwargs.get("input", None) @@ -728,7 +804,7 @@ def update_field(self, **kwargs): id ... on Feedback { name - description + description } } } @@ -744,15 +820,16 @@ def update_field(self, **kwargs): ) return None - """ - Add a Stix-Entity object to Feedback object (object_refs) + def add_stix_object_or_stix_relationship(self, **kwargs): + """Add a Stix-Entity object to Feedback object (object_refs). :param id: the id of the Feedback + :type id: str :param stixObjectOrStixRelationshipId: the id of the Stix-Entity - :return Boolean - """ - - def add_stix_object_or_stix_relationship(self, **kwargs): + :type stixObjectOrStixRelationshipId: str + :return: True if successful, False otherwise + :rtype: bool + """ id = kwargs.get("id", None) stix_object_or_stix_relationship_id = kwargs.get( "stixObjectOrStixRelationshipId", None @@ -791,15 +868,16 @@ def add_stix_object_or_stix_relationship(self, **kwargs): ) return False - """ - Remove a Stix-Entity object to Case object (object_refs) + def remove_stix_object_or_stix_relationship(self, **kwargs): + """Remove a Stix-Entity object from Feedback object (object_refs). - :param id: the id of the Case + :param id: the id of the Feedback + :type id: str :param stixObjectOrStixRelationshipId: the id of the Stix-Entity - :return Boolean - """ - - def remove_stix_object_or_stix_relationship(self, **kwargs): + :type stixObjectOrStixRelationshipId: str + :return: True if successful, False otherwise + :rtype: bool + """ id = kwargs.get("id", None) stix_object_or_stix_relationship_id = kwargs.get( "stixObjectOrStixRelationshipId", None @@ -836,14 +914,18 @@ def remove_stix_object_or_stix_relationship(self, **kwargs): ) return False - """ - Import a Feedback object from a STIX2 object + def import_from_stix2(self, **kwargs): + """Import a Feedback object from a STIX2 object. - :param stixObject: the Stix-Object Feedback - :return Feedback object + :param stixObject: the STIX2 Feedback object + :type stixObject: dict + :param extras: extra parameters including created_by_id, object_marking_ids, etc. + :type extras: dict + :param update: whether to update if the entity already exists + :type update: bool + :return: Feedback object + :rtype: dict or None """ - - def import_from_stix2(self, **kwargs): stix_object = kwargs.get("stixObject", None) extras = kwargs.get("extras", {}) update = kwargs.get("update", False) @@ -920,13 +1002,22 @@ def import_from_stix2(self, **kwargs): else None ), update=update, + file=extras.get("file"), + fileMarkings=extras.get("fileMarkings"), ) else: self.opencti.app_logger.error( "[opencti_feedback] Missing parameters: stixObject" ) + return None def delete(self, **kwargs): + """Delete a Feedback object. + + :param id: the id of the Feedback to delete + :type id: str + :return: None + """ id = kwargs.get("id", None) if id is not None: self.opencti.app_logger.info("Deleting Feedback", {"id": id}) diff --git a/client-python/pycti/entities/opencti_group.py b/client-python/pycti/entities/opencti_group.py index dcc21ec3a68d..9a30e606a6e1 100644 --- a/client-python/pycti/entities/opencti_group.py +++ b/client-python/pycti/entities/opencti_group.py @@ -19,9 +19,17 @@ class Group: See the properties attribute to understand what properties are fetched by default from GraphQL queries. + + :param opencti: instance of :py:class:`~pycti.api.opencti_api_client.OpenCTIApiClient` + :type opencti: OpenCTIApiClient """ def __init__(self, opencti): + """Initialize the Group instance. + + :param opencti: OpenCTI API client instance + :type opencti: OpenCTIApiClient + """ self.opencti = opencti self.properties = """ id @@ -327,7 +335,7 @@ def delete(self, **kwargs): id = kwargs.get("id", None) if id is None: self.opencti.admin_logger.error( - "[opencti_group] Cant delete group, missing parameter: id" + "[opencti_group] Cannot delete group, missing parameter: id" ) return None self.opencti.admin_logger.info("Deleting group", {"id": id}) @@ -716,6 +724,13 @@ def delete_allowed_marking(self, **kwargs) -> Optional[Dict]: ) def process_multiple_fields(self, data): + """Process and normalize fields in group data. + + :param data: the group data dictionary to process + :type data: dict + :return: the processed group data with normalized fields + :rtype: dict + """ if "roles" in data: data["roles"] = self.opencti.process_multiple(data["roles"]) data["rolesIds"] = self.opencti.process_multiple_ids(data["roles"]) diff --git a/client-python/pycti/entities/opencti_grouping.py b/client-python/pycti/entities/opencti_grouping.py index 53c17711aa42..0acd42e66536 100644 --- a/client-python/pycti/entities/opencti_grouping.py +++ b/client-python/pycti/entities/opencti_grouping.py @@ -13,9 +13,15 @@ class Grouping: Manages STIX grouping objects in the OpenCTI platform. :param opencti: instance of :py:class:`~pycti.api.opencti_api_client.OpenCTIApiClient` + :type opencti: OpenCTIApiClient """ def __init__(self, opencti): + """Initialize the Grouping instance. + + :param opencti: OpenCTI API client instance + :type opencti: OpenCTIApiClient + """ self.opencti = opencti self.properties = """ id @@ -186,7 +192,7 @@ def __init__(self, opencti): } ... on StixCyberObservable { observable_value - } + } ... on StixCoreRelationship { standard_id spec_version @@ -386,7 +392,7 @@ def __init__(self, opencti): } ... on StixCyberObservable { observable_value - } + } ... on StixCoreRelationship { standard_id spec_version @@ -420,6 +426,17 @@ def __init__(self, opencti): @staticmethod def generate_id(name, context, created=None): + """Generate a STIX ID for a Grouping. + + :param name: The name of the grouping + :type name: str + :param context: The grouping context + :type context: str + :param created: Optional creation date + :type created: datetime or str or None + :return: STIX ID for the grouping + :rtype: str + """ name = name.lower().strip() context = context.lower().strip() if isinstance(created, datetime.datetime): @@ -434,19 +451,41 @@ def generate_id(name, context, created=None): @staticmethod def generate_id_from_data(data): + """Generate a STIX ID from grouping data. + + :param data: Dictionary containing 'name', 'context', and 'created' keys + :type data: dict + :return: STIX ID for the grouping + :rtype: str + """ return Grouping.generate_id(data["name"], data["context"], data["created"]) - """ - List Grouping objects + def list(self, **kwargs): + """List Grouping objects. :param filters: the filters to apply + :type filters: dict :param search: the search keyword + :type search: str :param first: return the first n rows from the after ID (or the beginning if not set) + :type first: int :param after: ID of the first row for pagination - :return List of Grouping objects - """ - - def list(self, **kwargs): + :type after: str + :param orderBy: field to order results by + :type orderBy: str + :param orderMode: ordering mode (asc/desc) + :type orderMode: str + :param customAttributes: custom attributes to return + :type customAttributes: list + :param getAll: whether to retrieve all results + :type getAll: bool + :param withPagination: whether to include pagination info + :type withPagination: bool + :param withFiles: whether to include files + :type withFiles: bool + :return: List of Grouping objects + :rtype: list + """ filters = kwargs.get("filters", None) search = kwargs.get("search", None) first = kwargs.get("first", 100) @@ -524,15 +563,20 @@ def list(self, **kwargs): result["data"]["groupings"], with_pagination ) - """ - Read a Grouping object + def read(self, **kwargs): + """Read a Grouping object. :param id: the id of the Grouping + :type id: str :param filters: the filters to apply if no id provided - :return Grouping object - """ - - def read(self, **kwargs): + :type filters: dict + :param customAttributes: custom attributes to return + :type customAttributes: list + :param withFiles: whether to include files + :type withFiles: bool + :return: Grouping object + :rtype: dict or None + """ id = kwargs.get("id", None) filters = kwargs.get("filters", None) custom_attributes = kwargs.get("customAttributes", None) @@ -562,17 +606,26 @@ def read(self, **kwargs): return result[0] else: return None - - """ - Read a Grouping object by stix_id or name - - :param type: the Stix-Domain-Entity type - :param stix_id: the STIX ID of the Stix-Domain-Entity - :param name: the name of the Stix-Domain-Entity - :return Stix-Domain-Entity object - """ + else: + self.opencti.app_logger.error( + "[opencti_grouping] Missing parameters: id or filters" + ) + return None def get_by_stix_id_or_name(self, **kwargs): + """Read a Grouping object by stix_id or name. + + :param stix_id: the STIX ID of the Grouping + :type stix_id: str + :param name: the name of the Grouping + :type name: str + :param context: the context of the Grouping + :type context: str + :param customAttributes: custom attributes to return + :type customAttributes: list + :return: Grouping object + :rtype: dict or None + """ stix_id = kwargs.get("stix_id", None) name = kwargs.get("name", None) context = kwargs.get("context", None) @@ -594,15 +647,16 @@ def get_by_stix_id_or_name(self, **kwargs): ) return object_result - """ - Check if a grouping already contains a thing (Stix Object or Stix Relationship) + def contains_stix_object_or_stix_relationship(self, **kwargs): + """Check if a grouping already contains a thing (Stix Object or Stix Relationship). :param id: the id of the Grouping + :type id: str :param stixObjectOrStixRelationshipId: the id of the Stix-Entity - :return Boolean - """ - - def contains_stix_object_or_stix_relationship(self, **kwargs): + :type stixObjectOrStixRelationshipId: str + :return: Boolean + :rtype: bool + """ id = kwargs.get("id", None) stix_object_or_stix_relationship_id = kwargs.get( "stixObjectOrStixRelationshipId", None @@ -629,15 +683,60 @@ def contains_stix_object_or_stix_relationship(self, **kwargs): self.opencti.app_logger.error( "[opencti_grouping] Missing parameters: id or stixObjectOrStixRelationshipId" ) - - """ - Create a Grouping object - - :param name: the name of the Grouping - :return Grouping object - """ + return None def create(self, **kwargs): + """Create a Grouping object. + + :param stix_id: (optional) the STIX ID + :type stix_id: str + :param createdBy: (optional) the author ID + :type createdBy: str + :param objects: (optional) list of STIX object IDs + :type objects: list + :param objectMarking: (optional) list of marking definition IDs + :type objectMarking: list + :param objectLabel: (optional) list of label IDs + :type objectLabel: list + :param externalReferences: (optional) list of external reference IDs + :type externalReferences: list + :param revoked: (optional) whether the grouping is revoked + :type revoked: bool + :param confidence: (optional) confidence level (0-100) + :type confidence: int + :param lang: (optional) language + :type lang: str + :param created: (optional) creation date + :type created: datetime + :param modified: (optional) modification date + :type modified: datetime + :param name: the name of the Grouping (required) + :type name: str + :param context: the grouping context (required) + :type context: str + :param content: (optional) content + :type content: str + :param description: (optional) description + :type description: str + :param x_opencti_aliases: (optional) list of aliases + :type x_opencti_aliases: list + :param x_opencti_stix_ids: (optional) list of additional STIX IDs + :type x_opencti_stix_ids: list + :param objectOrganization: (optional) list of organization IDs + :type objectOrganization: list + :param x_opencti_workflow_id: (optional) workflow ID + :type x_opencti_workflow_id: str + :param x_opencti_modified_at: (optional) custom modification date + :type x_opencti_modified_at: datetime + :param update: (optional) whether to update if exists (default: False) + :type update: bool + :param file: (optional) File object to attach + :type file: dict + :param fileMarkings: (optional) list of marking definition IDs for the file + :type fileMarkings: list + :return: Grouping object + :rtype: dict or None + """ stix_id = kwargs.get("stix_id", None) created_by = kwargs.get("createdBy", None) objects = kwargs.get("objects", None) @@ -659,6 +758,8 @@ def create(self, **kwargs): x_opencti_workflow_id = kwargs.get("x_opencti_workflow_id", None) x_opencti_modified_at = kwargs.get("x_opencti_modified_at", None) update = kwargs.get("update", False) + file = kwargs.get("file", None) + file_markings = kwargs.get("fileMarkings", None) if name is not None and context is not None: self.opencti.app_logger.info("Creating Grouping", {"name": name}) @@ -672,49 +773,49 @@ def create(self, **kwargs): } } """ - result = self.opencti.query( - query, - { - "input": { - "stix_id": stix_id, - "createdBy": created_by, - "objectMarking": object_marking, - "objectLabel": object_label, - "objectOrganization": granted_refs, - "objects": objects, - "externalReferences": external_references, - "revoked": revoked, - "confidence": confidence, - "lang": lang, - "created": created, - "modified": modified, - "name": name, - "context": context, - "content": content, - "description": description, - "x_opencti_aliases": x_opencti_aliases, - "x_opencti_stix_ids": x_opencti_stix_ids, - "x_opencti_workflow_id": x_opencti_workflow_id, - "x_opencti_modified_at": x_opencti_modified_at, - "update": update, - } - }, - ) + input_variables = { + "stix_id": stix_id, + "createdBy": created_by, + "objectMarking": object_marking, + "objectLabel": object_label, + "objectOrganization": granted_refs, + "objects": objects, + "externalReferences": external_references, + "revoked": revoked, + "confidence": confidence, + "lang": lang, + "created": created, + "modified": modified, + "name": name, + "context": context, + "content": content, + "description": description, + "x_opencti_aliases": x_opencti_aliases, + "x_opencti_stix_ids": x_opencti_stix_ids, + "x_opencti_workflow_id": x_opencti_workflow_id, + "x_opencti_modified_at": x_opencti_modified_at, + "update": update, + "file": file, + "fileMarkings": file_markings, + } + result = self.opencti.query(query, {"input": input_variables}) return self.opencti.process_multiple_fields(result["data"]["groupingAdd"]) else: self.opencti.app_logger.error( - "[opencti_grouping] Missing parameters: name and description and context" + "[opencti_grouping] Missing parameters: name and context" ) + return None - """ - Add a Stix-Entity object to Grouping object (object_refs) + def add_stix_object_or_stix_relationship(self, **kwargs): + """Add a Stix-Entity object to Grouping object (object_refs). :param id: the id of the Grouping + :type id: str :param stixObjectOrStixRelationshipId: the id of the Stix-Entity - :return Boolean - """ - - def add_stix_object_or_stix_relationship(self, **kwargs): + :type stixObjectOrStixRelationshipId: str + :return: Boolean + :rtype: bool + """ id = kwargs.get("id", None) stix_object_or_stix_relationship_id = kwargs.get( "stixObjectOrStixRelationshipId", None @@ -748,22 +849,23 @@ def add_stix_object_or_stix_relationship(self, **kwargs): ) return False - """ - Remove a Stix-Entity object to Grouping object (object_refs) + def remove_stix_object_or_stix_relationship(self, **kwargs): + """Remove a Stix-Entity object from Grouping object (object_refs). :param id: the id of the Grouping + :type id: str :param stixObjectOrStixRelationshipId: the id of the Stix-Entity - :return Boolean - """ - - def remove_stix_object_or_stix_relationship(self, **kwargs): + :type stixObjectOrStixRelationshipId: str + :return: Boolean + :rtype: bool + """ id = kwargs.get("id", None) stix_object_or_stix_relationship_id = kwargs.get( "stixObjectOrStixRelationshipId", None ) if id is not None and stix_object_or_stix_relationship_id is not None: self.opencti.app_logger.info( - "Removing StixObjectOrStixRelationship to Grouping", + "Removing StixObjectOrStixRelationship from Grouping", {"id": stix_object_or_stix_relationship_id, "grouping": id}, ) query = """ @@ -788,14 +890,18 @@ def remove_stix_object_or_stix_relationship(self, **kwargs): ) return False - """ - Import a Grouping object from a STIX2 object + def import_from_stix2(self, **kwargs): + """Import a Grouping object from a STIX2 object. :param stixObject: the Stix-Object Grouping - :return Grouping object - """ - - def import_from_stix2(self, **kwargs): + :type stixObject: dict + :param extras: extra dict + :type extras: dict + :param update: set the update flag on import + :type update: bool + :return: Grouping object + :rtype: dict or None + """ stix_object = kwargs.get("stixObject", None) extras = kwargs.get("extras", {}) update = kwargs.get("update", False) @@ -889,8 +995,11 @@ def import_from_stix2(self, **kwargs): else None ), update=update, + file=extras.get("file"), + fileMarkings=extras.get("fileMarkings"), ) else: self.opencti.app_logger.error( "[opencti_grouping] Missing parameters: stixObject" ) + return None diff --git a/client-python/pycti/entities/opencti_identity.py b/client-python/pycti/entities/opencti_identity.py index 99601c88bed6..6ba73f20536e 100644 --- a/client-python/pycti/entities/opencti_identity.py +++ b/client-python/pycti/entities/opencti_identity.py @@ -14,9 +14,15 @@ class Identity: Manages individual, organization, and system identities in OpenCTI. :param opencti: instance of :py:class:`~pycti.api.opencti_api_client.OpenCTIApiClient` + :type opencti: OpenCTIApiClient """ def __init__(self, opencti): + """Initialize the Identity instance. + + :param opencti: OpenCTI API client instance + :type opencti: OpenCTIApiClient + """ self.opencti = opencti self.properties = """ id @@ -294,16 +300,27 @@ def list(self, **kwargs): """List Identity objects. :param types: the list of types + :type types: list :param filters: the filters to apply + :type filters: dict :param search: the search keyword + :type search: str :param first: return the first n rows from the after ID (or the beginning if not set) + :type first: int :param after: ID of the first row for pagination + :type after: str :param orderBy: field to order results by + :type orderBy: str :param orderMode: ordering mode (asc/desc) + :type orderMode: str :param customAttributes: custom attributes to return + :type customAttributes: str :param getAll: whether to retrieve all results + :type getAll: bool :param withPagination: whether to include pagination info + :type withPagination: bool :param withFiles: whether to include files + :type withFiles: bool :return: List of Identity objects :rtype: list """ @@ -370,6 +387,7 @@ def list(self, **kwargs): result = self.opencti.query( query, { + "types": types, "filters": filters, "search": search, "first": first, @@ -386,15 +404,20 @@ def list(self, **kwargs): result["data"]["identities"], with_pagination ) - """ - Read a Identity object + def read(self, **kwargs): + """Read an Identity object. :param id: the id of the Identity + :type id: str :param filters: the filters to apply if no id provided - :return Identity object - """ - - def read(self, **kwargs): + :type filters: dict + :param customAttributes: custom attributes to return + :type customAttributes: str + :param withFiles: whether to include files + :type withFiles: bool + :return: Identity object + :rtype: dict or None + """ id = kwargs.get("id", None) filters = kwargs.get("filters", None) custom_attributes = kwargs.get("customAttributes", None) @@ -430,14 +453,41 @@ def read(self, **kwargs): ) return None - """ - Create a Identity object - - :param name: the name of the Identity - :return Identity object - """ - def create(self, **kwargs): + """Create an Identity object. + + :param type: the type of identity (Organization, Individual, System, etc.) (required) + :param stix_id: (optional) the STIX ID + :param createdBy: (optional) the author ID + :param objectMarking: (optional) list of marking definition IDs + :param objectLabel: (optional) list of label IDs + :param externalReferences: (optional) list of external reference IDs + :param revoked: (optional) whether the identity is revoked + :param confidence: (optional) confidence level (0-100) + :param lang: (optional) language + :param created: (optional) creation date + :param modified: (optional) modification date + :param name: the name of the Identity (required) + :param description: (optional) description + :param contact_information: (optional) contact information + :param roles: (optional) list of roles + :param x_opencti_aliases: (optional) list of aliases + :param security_platform_type: (optional) type of security platform + :param x_opencti_organization_type: (optional) organization type + :param x_opencti_reliability: (optional) reliability level + :param x_opencti_score: (optional) score + :param x_opencti_firstname: (optional) first name for individuals + :param x_opencti_lastname: (optional) last name for individuals + :param x_opencti_stix_ids: (optional) list of additional STIX IDs + :param objectOrganization: (optional) list of organization IDs + :param x_opencti_workflow_id: (optional) workflow ID + :param x_opencti_modified_at: (optional) custom modification date + :param update: (optional) whether to update if exists (default: False) + :param file: (optional) file object to attach + :param fileMarkings: (optional) list of marking definition IDs for the file + :return: Identity object + :rtype: dict or None + """ type = kwargs.get("type", None) stix_id = kwargs.get("stix_id", None) created_by = kwargs.get("createdBy", None) @@ -465,6 +515,8 @@ def create(self, **kwargs): x_opencti_workflow_id = kwargs.get("x_opencti_workflow_id", None) x_opencti_modified_at = kwargs.get("x_opencti_modified_at", None) update = kwargs.get("update", False) + file = kwargs.get("file", None) + file_markings = kwargs.get("fileMarkings", None) if type is not None and name is not None: self.opencti.app_logger.info("Creating Identity", {"name": name}) @@ -488,6 +540,8 @@ def create(self, **kwargs): "x_opencti_workflow_id": x_opencti_workflow_id, "x_opencti_modified_at": x_opencti_modified_at, "update": update, + "file": file, + "fileMarkings": file_markings, } if type == IdentityTypes.ORGANIZATION.value: query = """ @@ -580,17 +634,22 @@ def create(self, **kwargs): ) else: self.opencti.app_logger.error( - "Missing parameters: type, name and description" + "[opencti_identity] Missing parameters: type and name" ) - - """ - Import an Identity object from a STIX2 object - - :param stixObject: the Stix-Object Identity - :return Identity object - """ + return None def import_from_stix2(self, **kwargs): + """Import an Identity object from a STIX2 object. + + :param stixObject: the STIX2 Identity object + :type stixObject: dict + :param extras: extra parameters including created_by_id, object_marking_ids, etc. + :type extras: dict + :param update: whether to update if the entity already exists + :type update: bool + :return: Identity object + :rtype: dict or None + """ stix_object = kwargs.get("stixObject", None) extras = kwargs.get("extras", {}) update = kwargs.get("update", False) @@ -631,12 +690,6 @@ def import_from_stix2(self, **kwargs): stix_object["x_opencti_score"] = ( self.opencti.get_attribute_in_extension("score", stix_object) ) - if "x_opencti_organization_type" not in stix_object: - stix_object["x_opencti_organization_type"] = ( - self.opencti.get_attribute_in_extension( - "organization_type", stix_object - ) - ) if "x_opencti_firstname" not in stix_object: stix_object["x_opencti_firstname"] = ( self.opencti.get_attribute_in_extension("firstname", stix_object) @@ -754,8 +807,11 @@ def import_from_stix2(self, **kwargs): else None ), update=update, + file=extras.get("file"), + fileMarkings=extras.get("fileMarkings"), ) else: self.opencti.app_logger.error( "[opencti_identity] Missing parameters: stixObject" ) + return None diff --git a/client-python/pycti/entities/opencti_incident.py b/client-python/pycti/entities/opencti_incident.py index 5b8be76041d4..54cbca99ad2c 100644 --- a/client-python/pycti/entities/opencti_incident.py +++ b/client-python/pycti/entities/opencti_incident.py @@ -13,9 +13,15 @@ class Incident: Manages security incidents in the OpenCTI platform. :param opencti: instance of :py:class:`~pycti.api.opencti_api_client.OpenCTIApiClient` + :type opencti: OpenCTIApiClient """ def __init__(self, opencti): + """Initialize the Incident instance. + + :param opencti: OpenCTI API client instance + :type opencti: OpenCTIApiClient + """ self.opencti = opencti self.properties = """ id @@ -240,6 +246,15 @@ def __init__(self, opencti): @staticmethod def generate_id(name, created): + """Generate a STIX ID for an Incident. + + :param name: The name of the incident + :type name: str + :param created: The creation date of the incident + :type created: str or datetime.datetime + :return: STIX ID for the incident + :rtype: str + """ name = name.lower().strip() if isinstance(created, datetime.datetime): created = created.isoformat() @@ -250,19 +265,41 @@ def generate_id(name, created): @staticmethod def generate_id_from_data(data): + """Generate a STIX ID from incident data. + + :param data: Dictionary containing 'name' and 'created' keys + :type data: dict + :return: STIX ID for the incident + :rtype: str + """ return Incident.generate_id(data["name"], data["created"]) - """ - List Incident objects + def list(self, **kwargs): + """List Incident objects. :param filters: the filters to apply + :type filters: dict :param search: the search keyword + :type search: str :param first: return the first n rows from the after ID (or the beginning if not set) + :type first: int :param after: ID of the first row for pagination - :return List of Incident objects - """ - - def list(self, **kwargs): + :type after: str + :param orderBy: field to order results by + :type orderBy: str + :param orderMode: ordering mode (asc/desc) + :type orderMode: str + :param customAttributes: custom attributes to return + :type customAttributes: str + :param getAll: whether to retrieve all results + :type getAll: bool + :param withPagination: whether to include pagination info + :type withPagination: bool + :param withFiles: whether to include files + :type withFiles: bool + :return: List of Incident objects + :rtype: list + """ filters = kwargs.get("filters", None) search = kwargs.get("search", None) first = kwargs.get("first", 500) @@ -340,15 +377,20 @@ def list(self, **kwargs): result["data"]["incidents"], with_pagination ) - """ - Read a Incident object + def read(self, **kwargs): + """Read an Incident object. :param id: the id of the Incident + :type id: str :param filters: the filters to apply if no id provided - :return Incident object - """ - - def read(self, **kwargs): + :type filters: dict + :param customAttributes: custom attributes to return + :type customAttributes: str + :param withFiles: whether to include files + :type withFiles: bool + :return: Incident object + :rtype: dict or None + """ id = kwargs.get("id", None) filters = kwargs.get("filters", None) custom_attributes = kwargs.get("customAttributes", None) @@ -384,14 +426,64 @@ def read(self, **kwargs): ) return None - """ - Create a Incident object - - :param name: the name of the Incident - :return Incident object - """ - def create(self, **kwargs): + """Create an Incident object. + + :param name: the name of the Incident (required) + :type name: str + :param stix_id: (optional) the STIX ID + :type stix_id: str + :param createdBy: (optional) the author ID + :type createdBy: str + :param objectMarking: (optional) list of marking definition IDs + :type objectMarking: list + :param objectLabel: (optional) list of label IDs + :type objectLabel: list + :param externalReferences: (optional) list of external reference IDs + :type externalReferences: list + :param revoked: (optional) whether the incident is revoked + :type revoked: bool + :param confidence: (optional) confidence level (0-100) + :type confidence: int + :param lang: (optional) language + :type lang: str + :param created: (optional) creation date + :type created: str + :param modified: (optional) modification date + :type modified: str + :param description: (optional) description + :type description: str + :param aliases: (optional) list of aliases + :type aliases: list + :param first_seen: (optional) first seen date + :type first_seen: str + :param last_seen: (optional) last seen date + :type last_seen: str + :param objective: (optional) objective of the incident + :type objective: str + :param incident_type: (optional) type of incident + :type incident_type: str + :param severity: (optional) severity level + :type severity: str + :param source: (optional) source of the incident + :type source: str + :param x_opencti_stix_ids: (optional) list of additional STIX IDs + :type x_opencti_stix_ids: list + :param objectOrganization: (optional) list of organization IDs + :type objectOrganization: list + :param x_opencti_workflow_id: (optional) workflow ID + :type x_opencti_workflow_id: str + :param x_opencti_modified_at: (optional) custom modification date + :type x_opencti_modified_at: str + :param update: (optional) whether to update if exists (default: False) + :type update: bool + :param file: (optional) File object to attach + :type file: dict + :param fileMarkings: (optional) list of marking definition IDs for the file + :type fileMarkings: list + :return: Incident object + :rtype: dict or None + """ stix_id = kwargs.get("stix_id", None) created_by = kwargs.get("createdBy", None) object_marking = kwargs.get("objectMarking", None) @@ -416,6 +508,8 @@ def create(self, **kwargs): x_opencti_workflow_id = kwargs.get("x_opencti_workflow_id", None) x_opencti_modified_at = kwargs.get("x_opencti_modified_at", None) update = kwargs.get("update", False) + file = kwargs.get("file", None) + file_markings = kwargs.get("fileMarkings", None) if name is not None: self.opencti.app_logger.info("Creating Incident", {"name": name}) @@ -457,21 +551,28 @@ def create(self, **kwargs): "x_opencti_workflow_id": x_opencti_workflow_id, "x_opencti_modified_at": x_opencti_modified_at, "update": update, + "file": file, + "fileMarkings": file_markings, } }, ) return self.opencti.process_multiple_fields(result["data"]["incidentAdd"]) else: - self.opencti.app_logger.error("Missing parameters: name and description") - - """ - Import a Incident object from a STIX2 object - - :param stixObject: the Stix-Object Incident - :return Incident object - """ + self.opencti.app_logger.error("[opencti_incident] Missing parameters: name") + return None def import_from_stix2(self, **kwargs): + """Import an Incident object from a STIX2 object. + + :param stixObject: the STIX2 Incident object + :type stixObject: dict + :param extras: extra parameters including created_by_id, object_marking_ids, etc. + :type extras: dict + :param update: whether to update if the entity already exists + :type update: bool + :return: Incident object + :rtype: dict or None + """ stix_object = kwargs.get("stixObject", None) extras = kwargs.get("extras", {}) update = kwargs.get("update", False) @@ -563,8 +664,11 @@ def import_from_stix2(self, **kwargs): else None ), update=update, + file=extras.get("file"), + fileMarkings=extras.get("fileMarkings"), ) else: self.opencti.app_logger.error( "[opencti_incident] Missing parameters: stixObject" ) + return None diff --git a/client-python/pycti/entities/opencti_indicator.py b/client-python/pycti/entities/opencti_indicator.py index 3662372b5962..622a8e484833 100644 --- a/client-python/pycti/entities/opencti_indicator.py +++ b/client-python/pycti/entities/opencti_indicator.py @@ -14,10 +14,18 @@ class Indicator: """Main Indicator class for OpenCTI + Manages threat indicators and detection patterns in the OpenCTI platform. + :param opencti: instance of :py:class:`~pycti.api.opencti_api_client.OpenCTIApiClient` + :type opencti: OpenCTIApiClient """ def __init__(self, opencti): + """Initialize the Indicator instance. + + :param opencti: OpenCTI API client instance + :type opencti: OpenCTIApiClient + """ self.opencti = opencti self.properties = INDICATOR_PROPERTIES self.properties_with_files = INDICATOR_PROPERTIES_WITH_FILES @@ -48,22 +56,30 @@ def generate_id_from_data(data): return Indicator.generate_id(data["pattern"]) def list(self, **kwargs): - """List Indicator objects - - The list method accepts the following kwargs: - - :param list filters: (optional) the filters to apply - :param str search: (optional) a search keyword to apply for the listing - :param int first: (optional) return the first n rows from the `after` ID - or the beginning if not set - :param str after: (optional) OpenCTI object ID of the first row for pagination - :param str orderBy: (optional) the field to order the response on - :param bool orderMode: (optional) either "`asc`" or "`desc`" - :param list customAttributes: (optional) list of attributes keys to return - :param bool getAll: (optional) switch to return all entries (be careful to use this without any other filters) - :param bool withPagination: (optional) switch to use pagination - :param bool toStix: (optional) get in STIX - + """List Indicator objects. + + :param filters: (optional) the filters to apply + :type filters: dict + :param search: (optional) a search keyword to apply for the listing + :type search: str + :param first: (optional) return the first n rows from the `after` ID or the beginning if not set + :type first: int + :param after: (optional) OpenCTI object ID of the first row for pagination + :type after: str + :param orderBy: (optional) the field to order the response on + :type orderBy: str + :param orderMode: (optional) either "asc" or "desc" + :type orderMode: str + :param customAttributes: (optional) list of attributes keys to return + :type customAttributes: str + :param getAll: (optional) switch to return all entries (be careful to use this without any other filters) + :type getAll: bool + :param withPagination: (optional) switch to use pagination + :type withPagination: bool + :param withFiles: (optional) include files in response + :type withFiles: bool + :param toStix: (optional) get in STIX format + :type toStix: bool :return: List of Indicators :rtype: list """ @@ -141,6 +157,7 @@ def list(self, **kwargs): "after": after, "orderBy": order_by, "orderMode": order_mode, + "toStix": to_stix, }, ) data = self.opencti.process_multiple(result["data"]["indicators"]) @@ -152,20 +169,23 @@ def list(self, **kwargs): ) def read(self, **kwargs): - """Read an Indicator object + """Read an Indicator object. - read can be either used with a known OpenCTI entity `id` or by using a + Read can be either used with a known OpenCTI entity `id` or by using a valid filter to search and return a single Indicator entity or None. - The list method accepts the following kwargs. - Note: either `id` or `filters` is required. - :param str id: the id of the Threat-Actor-Group - :param list filters: the filters to apply if no id provided - + :param id: the id of the Indicator + :type id: str + :param filters: the filters to apply if no id provided + :type filters: dict + :param customAttributes: custom attributes to return + :type customAttributes: str + :param withFiles: whether to include files + :type withFiles: bool :return: Indicator object - :rtype: Indicator + :rtype: dict or None """ id = kwargs.get("id", None) @@ -204,15 +224,72 @@ def read(self, **kwargs): return None def create(self, **kwargs): - """ - Create an Indicator object - - :param str name: the name of the Indicator - :param str pattern: stix indicator pattern - :param str x_opencti_main_observable_type: type of the observable - + """Create an Indicator object. + + :param stix_id: (optional) the STIX ID + :type stix_id: str + :param createdBy: (optional) the author ID + :type createdBy: str + :param objectMarking: (optional) list of marking definition IDs + :type objectMarking: list + :param objectLabel: (optional) list of label IDs + :type objectLabel: list + :param externalReferences: (optional) list of external reference IDs + :type externalReferences: list + :param revoked: (optional) whether the indicator is revoked + :type revoked: bool + :param confidence: (optional) confidence level (0-100) + :type confidence: int + :param lang: (optional) language + :type lang: str + :param created: (optional) creation date + :type created: str + :param modified: (optional) modification date + :type modified: str + :param pattern_type: the pattern type (required) + :type pattern_type: str + :param pattern_version: (optional) the pattern version + :type pattern_version: str + :param pattern: the indicator pattern (required) + :type pattern: str + :param name: the name of the Indicator (defaults to pattern) + :type name: str + :param description: (optional) description + :type description: str + :param indicator_types: (optional) list of indicator types + :type indicator_types: list + :param valid_from: (optional) valid from date + :type valid_from: str + :param valid_until: (optional) valid until date + :type valid_until: str + :param x_opencti_score: (optional) score (default: 50) + :type x_opencti_score: int + :param x_opencti_detection: (optional) detection flag (default: False) + :type x_opencti_detection: bool + :param x_opencti_main_observable_type: the main observable type (required) + :type x_opencti_main_observable_type: str + :param x_mitre_platforms: (optional) list of MITRE platforms + :type x_mitre_platforms: list + :param killChainPhases: (optional) list of kill chain phase IDs + :type killChainPhases: list + :param x_opencti_stix_ids: (optional) list of additional STIX IDs + :type x_opencti_stix_ids: list + :param x_opencti_create_observables: (optional) create observables (default: False) + :type x_opencti_create_observables: bool + :param objectOrganization: (optional) list of organization IDs + :type objectOrganization: list + :param x_opencti_workflow_id: (optional) workflow ID + :type x_opencti_workflow_id: str + :param x_opencti_modified_at: (optional) custom modification date + :type x_opencti_modified_at: str + :param update: (optional) whether to update if exists (default: False) + :type update: bool + :param file: (optional) File object to attach + :type file: dict + :param fileMarkings: (optional) list of marking definition IDs for the file + :type fileMarkings: list :return: Indicator object - :rtype: Indicator + :rtype: dict or None """ stix_id = kwargs.get("stix_id", None) created_by = kwargs.get("createdBy", None) @@ -245,6 +322,8 @@ def create(self, **kwargs): x_opencti_workflow_id = kwargs.get("x_opencti_workflow_id", None) x_opencti_modified_at = kwargs.get("x_opencti_modified_at", None) update = kwargs.get("update", False) + file = kwargs.get("file", None) + file_markings = kwargs.get("fileMarkings", None) if ( name is not None @@ -307,6 +386,8 @@ def create(self, **kwargs): "x_opencti_workflow_id": x_opencti_workflow_id, "x_opencti_modified_at": x_opencti_modified_at, "update": update, + "file": file, + "fileMarkings": file_markings, } }, ) @@ -316,12 +397,15 @@ def create(self, **kwargs): "[opencti_indicator] Missing parameters: " "name or pattern or pattern_type or x_opencti_main_observable_type" ) + return None def update_field(self, **kwargs): """Update an Indicator object field. :param id: the Indicator id + :type id: str :param input: the input of the field + :type input: list :return: Updated indicator object :rtype: dict or None """ @@ -350,19 +434,21 @@ def update_field(self, **kwargs): ) else: self.opencti.app_logger.error( - "[opencti_stix_domain_object] Cant update indicator field, missing parameters: id and input" + "[opencti_indicator] Cannot update indicator field, missing parameters: id and input" ) return None def add_stix_cyber_observable(self, **kwargs): - """ - Add a Stix-Cyber-Observable object to Indicator object (based-on) + """Add a Stix-Cyber-Observable object to Indicator object (based-on). :param id: the id of the Indicator + :type id: str :param indicator: Indicator object + :type indicator: dict :param stix_cyber_observable_id: the id of the Stix-Observable - - :return: Boolean True if there has been no import error + :type stix_cyber_observable_id: str + :return: True if there has been no import error + :rtype: bool """ id = kwargs.get("id", None) indicator = kwargs.get("indicator", None) @@ -408,15 +494,16 @@ def add_stix_cyber_observable(self, **kwargs): return False def import_from_stix2(self, **kwargs): - """ - Import an Indicator object from a STIX2 object + """Import an Indicator object from a STIX2 object. :param stixObject: the Stix-Object Indicator + :type stixObject: dict :param extras: extra dict - :param bool update: set the update flag on import - + :type extras: dict + :param update: set the update flag on import + :type update: bool :return: Indicator object - :rtype: Indicator + :rtype: dict or None """ stix_object = kwargs.get("stixObject", None) extras = kwargs.get("extras", {}) @@ -580,8 +667,11 @@ def import_from_stix2(self, **kwargs): else None ), update=update, + file=extras.get("file"), + fileMarkings=extras.get("fileMarkings"), ) else: self.opencti.app_logger.error( "[opencti_indicator] Missing parameters: stixObject" ) + return None diff --git a/client-python/pycti/entities/opencti_infrastructure.py b/client-python/pycti/entities/opencti_infrastructure.py index a6749dc65286..cfb9cd590887 100644 --- a/client-python/pycti/entities/opencti_infrastructure.py +++ b/client-python/pycti/entities/opencti_infrastructure.py @@ -9,10 +9,18 @@ class Infrastructure: """Main Infrastructure class for OpenCTI + Manages threat infrastructure (servers, domains, etc.) in the OpenCTI platform. + :param opencti: instance of :py:class:`~pycti.api.opencti_api_client.OpenCTIApiClient` + :type opencti: OpenCTIApiClient """ def __init__(self, opencti): + """Initialize the Infrastructure instance. + + :param opencti: OpenCTI API client instance + :type opencti: OpenCTIApiClient + """ self.opencti = opencti self.properties = """ id @@ -249,6 +257,13 @@ def __init__(self, opencti): @staticmethod def generate_id(name): + """Generate a STIX ID for an Infrastructure. + + :param name: The name of the infrastructure + :type name: str + :return: STIX ID for the infrastructure + :rtype: str + """ name = name.lower().strip() data = {"name": name} data = canonicalize(data, utf8=False) @@ -257,23 +272,40 @@ def generate_id(name): @staticmethod def generate_id_from_data(data): + """Generate a STIX ID from infrastructure data. + + :param data: Dictionary containing 'name' key + :type data: dict + :return: STIX ID for the infrastructure + :rtype: str + """ return Infrastructure.generate_id(data["name"]) def list(self, **kwargs): - """List Infrastructure objects - - The list method accepts the following kwargs: + """List Infrastructure objects. - :param list filters: (optional) the filters to apply - :param str search: (optional) a search keyword to apply for the listing - :param int first: (optional) return the first n rows from the `after` ID - or the beginning if not set - :param str after: (optional) OpenCTI object ID of the first row for pagination - :param str orderBy: (optional) the field to order the response on - :param bool orderMode: (optional) either "`asc`" or "`desc`" - :param list customAttributes: (optional) list of attributes keys to return - :param bool getAll: (optional) switch to return all entries (be careful to use this without any other filters) - :param bool withPagination: (optional) switch to use pagination + :param filters: (optional) the filters to apply + :type filters: dict + :param search: (optional) a search keyword to apply for the listing + :type search: str + :param first: (optional) return the first n rows from the `after` ID or the beginning if not set + :type first: int + :param after: (optional) OpenCTI object ID of the first row for pagination + :type after: str + :param orderBy: (optional) the field to order the response on + :type orderBy: str + :param orderMode: (optional) either "asc" or "desc" + :type orderMode: str + :param customAttributes: (optional) list of attributes keys to return + :type customAttributes: str + :param getAll: (optional) switch to return all entries (be careful to use this without any other filters) + :type getAll: bool + :param withPagination: (optional) switch to use pagination + :type withPagination: bool + :param withFiles: (optional) include files in response + :type withFiles: bool + :return: List of Infrastructure objects + :rtype: list """ filters = kwargs.get("filters", None) @@ -334,7 +366,7 @@ def list(self, **kwargs): final_data = final_data + data while result["data"]["infrastructures"]["pageInfo"]["hasNextPage"]: after = result["data"]["infrastructures"]["pageInfo"]["endCursor"] - self.opencti.app_logger.info( + self.opencti.app_logger.debug( "Listing Infrastructures", {"after": after} ) result = self.opencti.query( @@ -357,19 +389,24 @@ def list(self, **kwargs): ) def read(self, **kwargs): - """Read an Infrastructure object + """Read an Infrastructure object. - read can be either used with a known OpenCTI entity `id` or by using a + Read can be either used with a known OpenCTI entity `id` or by using a valid filter to search and return a single Infrastructure entity or None. - The list method accepts the following kwargs. - Note: either `id` or `filters` is required. - :param str id: the id of the Threat-Actor-Group - :param list filters: the filters to apply if no id provided + :param id: the id of the Infrastructure + :type id: str + :param filters: the filters to apply if no id provided + :type filters: dict + :param customAttributes: custom attributes to return + :type customAttributes: str + :param withFiles: whether to include files + :type withFiles: bool + :return: Infrastructure object + :rtype: dict or None """ - id = kwargs.get("id", None) filters = kwargs.get("filters", None) custom_attributes = kwargs.get("customAttributes", None) @@ -407,14 +444,60 @@ def read(self, **kwargs): ) return None - """ - Create a Infrastructure object - - :param name: the name of the Infrastructure - :return Infrastructure object - """ - def create(self, **kwargs): + """Create an Infrastructure object. + + :param name: the name of the Infrastructure (required) + :type name: str + :param stix_id: (optional) the STIX ID + :type stix_id: str + :param createdBy: (optional) the author ID + :type createdBy: str + :param objectMarking: (optional) list of marking definition IDs + :type objectMarking: list + :param objectLabel: (optional) list of label IDs + :type objectLabel: list + :param externalReferences: (optional) list of external reference IDs + :type externalReferences: list + :param revoked: (optional) whether the infrastructure is revoked + :type revoked: bool + :param confidence: (optional) confidence level (0-100) + :type confidence: int + :param lang: (optional) language + :type lang: str + :param created: (optional) creation date + :type created: str + :param modified: (optional) modification date + :type modified: str + :param description: (optional) description + :type description: str + :param aliases: (optional) list of aliases + :type aliases: list + :param infrastructure_types: (optional) list of infrastructure types + :type infrastructure_types: list + :param first_seen: (optional) first seen date + :type first_seen: str + :param last_seen: (optional) last seen date + :type last_seen: str + :param killChainPhases: (optional) list of kill chain phase IDs + :type killChainPhases: list + :param x_opencti_stix_ids: (optional) list of additional STIX IDs + :type x_opencti_stix_ids: list + :param objectOrganization: (optional) list of organization IDs + :type objectOrganization: list + :param x_opencti_workflow_id: (optional) workflow ID + :type x_opencti_workflow_id: str + :param x_opencti_modified_at: (optional) custom modification date + :type x_opencti_modified_at: str + :param update: (optional) whether to update if exists (default: False) + :type update: bool + :param file: (optional) File object to attach + :type file: dict + :param fileMarkings: (optional) list of marking definition IDs for the file + :type fileMarkings: list + :return: Infrastructure object + :rtype: dict or None + """ stix_id = kwargs.get("stix_id", None) created_by = kwargs.get("createdBy", None) object_marking = kwargs.get("objectMarking", None) @@ -437,6 +520,8 @@ def create(self, **kwargs): x_opencti_workflow_id = kwargs.get("x_opencti_workflow_id", None) x_opencti_modified_at = kwargs.get("x_opencti_modified_at", None) update = kwargs.get("update", False) + file = kwargs.get("file", None) + file_markings = kwargs.get("fileMarkings", None) if name is not None: self.opencti.app_logger.info("Creating Infrastructure", {"name": name}) @@ -476,6 +561,8 @@ def create(self, **kwargs): "x_opencti_workflow_id": x_opencti_workflow_id, "x_opencti_modified_at": x_opencti_modified_at, "update": update, + "file": file, + "fileMarkings": file_markings, } }, ) @@ -484,18 +571,22 @@ def create(self, **kwargs): ) else: self.opencti.app_logger.error( - "[opencti_infrastructure] Missing parameters: " - "name and infrastructure_pattern and main_observable_type" + "[opencti_infrastructure] Missing parameters: name" ) - - """ - Import an Infrastructure object from a STIX2 object - - :param stixObject: the Stix-Object Infrastructure - :return Infrastructure object - """ + return None def import_from_stix2(self, **kwargs): + """Import an Infrastructure object from a STIX2 object. + + :param stixObject: the STIX2 Infrastructure object + :type stixObject: dict + :param extras: extra parameters including created_by_id, object_marking_ids, etc. + :type extras: dict + :param update: whether to update if the entity already exists + :type update: bool + :return: Infrastructure object + :rtype: dict or None + """ stix_object = kwargs.get("stixObject", None) extras = kwargs.get("extras", {}) update = kwargs.get("update", False) @@ -587,8 +678,11 @@ def import_from_stix2(self, **kwargs): else None ), update=update, + file=extras.get("file"), + fileMarkings=extras.get("fileMarkings"), ) else: self.opencti.app_logger.error( "[opencti_infrastructure] Missing parameters: stixObject" ) + return None diff --git a/client-python/pycti/entities/opencti_intrusion_set.py b/client-python/pycti/entities/opencti_intrusion_set.py index 6c0c9c47b900..1f420e12641e 100644 --- a/client-python/pycti/entities/opencti_intrusion_set.py +++ b/client-python/pycti/entities/opencti_intrusion_set.py @@ -12,9 +12,15 @@ class IntrusionSet: Manages intrusion sets (APT groups) in the OpenCTI platform. :param opencti: instance of :py:class:`~pycti.api.opencti_api_client.OpenCTIApiClient` + :type opencti: OpenCTIApiClient """ def __init__(self, opencti): + """Initialize the IntrusionSet instance. + + :param opencti: OpenCTI API client instance + :type opencti: OpenCTIApiClient + """ self.opencti = opencti self.properties = """ id @@ -267,15 +273,25 @@ def list(self, **kwargs): """List Intrusion Set objects. :param filters: the filters to apply + :type filters: dict :param search: the search keyword + :type search: str :param first: return the first n rows from the after ID (or the beginning if not set) + :type first: int :param after: ID of the first row for pagination + :type after: str :param orderBy: field to order results by + :type orderBy: str :param orderMode: ordering mode (asc/desc) + :type orderMode: str :param customAttributes: custom attributes to return + :type customAttributes: str :param getAll: whether to retrieve all results + :type getAll: bool :param withPagination: whether to include pagination info + :type withPagination: bool :param withFiles: whether to include files + :type withFiles: bool :return: List of Intrusion Set objects :rtype: list """ @@ -360,9 +376,13 @@ def read(self, **kwargs): """Read an Intrusion Set object. :param id: the id of the Intrusion Set + :type id: str :param filters: the filters to apply if no id provided + :type filters: dict :param customAttributes: custom attributes to return + :type customAttributes: str :param withFiles: whether to include files + :type withFiles: bool :return: Intrusion Set object :rtype: dict or None """ @@ -404,20 +424,58 @@ def read(self, **kwargs): def create(self, **kwargs): """Create an Intrusion Set object. - :param name: the name of the Intrusion Set + :param stix_id: (optional) the STIX ID + :type stix_id: str + :param name: the name of the Intrusion Set (required) + :type name: str :param description: description of the intrusion set + :type description: str :param aliases: list of aliases + :type aliases: list :param first_seen: first seen date + :type first_seen: str :param last_seen: last seen date + :type last_seen: str :param goals: goals of the intrusion set + :type goals: list :param resource_level: resource level + :type resource_level: str :param primary_motivation: primary motivation + :type primary_motivation: str :param secondary_motivations: secondary motivations - :param createdBy: creator identity - :param objectMarking: marking definitions - :param objectLabel: labels - :param externalReferences: external references + :type secondary_motivations: list + :param createdBy: creator identity ID + :type createdBy: str + :param objectMarking: marking definition IDs + :type objectMarking: list + :param objectLabel: label IDs + :type objectLabel: list + :param externalReferences: external reference IDs + :type externalReferences: list + :param objectOrganization: organization IDs + :type objectOrganization: list + :param revoked: whether the intrusion set is revoked + :type revoked: bool + :param confidence: confidence level (0-100) + :type confidence: int + :param lang: language + :type lang: str + :param created: creation date + :type created: str + :param modified: modification date + :type modified: str + :param x_opencti_stix_ids: additional STIX IDs + :type x_opencti_stix_ids: list + :param x_opencti_workflow_id: workflow ID + :type x_opencti_workflow_id: str + :param x_opencti_modified_at: custom modification date + :type x_opencti_modified_at: str :param update: whether to update existing intrusion set + :type update: bool + :param file: (optional) File object to attach + :type file: dict + :param fileMarkings: (optional) list of marking definition IDs for the file + :type fileMarkings: list :return: Intrusion Set object :rtype: dict or None """ @@ -445,6 +503,8 @@ def create(self, **kwargs): x_opencti_workflow_id = kwargs.get("x_opencti_workflow_id", None) x_opencti_modified_at = kwargs.get("x_opencti_modified_at", None) update = kwargs.get("update", False) + file = kwargs.get("file", None) + file_markings = kwargs.get("fileMarkings", None) if name is not None: self.opencti.app_logger.info("Creating Intrusion-Set", {"name": name}) @@ -486,6 +546,8 @@ def create(self, **kwargs): "x_opencti_workflow_id": x_opencti_workflow_id, "x_opencti_modified_at": x_opencti_modified_at, "update": update, + "file": file, + "fileMarkings": file_markings, } }, ) @@ -494,17 +556,22 @@ def create(self, **kwargs): ) else: self.opencti.app_logger.error( - "[opencti_intrusion_set] Missing parameters: name and description" + "[opencti_intrusion_set] Missing parameters: name" ) - - """ - Import an Intrusion-Set object from a STIX2 object - - :param stixObject: the Stix-Object Intrusion-Set - :return Intrusion-Set object - """ + return None def import_from_stix2(self, **kwargs): + """Import an Intrusion Set object from a STIX2 object. + + :param stixObject: the STIX2 Intrusion Set object + :type stixObject: dict + :param extras: extra parameters including created_by_id, object_marking_ids, etc. + :type extras: dict + :param update: whether to update if the entity already exists + :type update: bool + :return: Intrusion Set object + :rtype: dict or None + """ stix_object = kwargs.get("stixObject", None) extras = kwargs.get("extras", {}) update = kwargs.get("update", False) @@ -602,8 +669,11 @@ def import_from_stix2(self, **kwargs): else None ), update=update, + file=extras.get("file"), + fileMarkings=extras.get("fileMarkings"), ) else: self.opencti.app_logger.error( "[opencti_intrusion_set] Missing parameters: stixObject" ) + return None diff --git a/client-python/pycti/entities/opencti_kill_chain_phase.py b/client-python/pycti/entities/opencti_kill_chain_phase.py index 0a2fdb71ec8e..b4c1f725b9aa 100644 --- a/client-python/pycti/entities/opencti_kill_chain_phase.py +++ b/client-python/pycti/entities/opencti_kill_chain_phase.py @@ -11,9 +11,15 @@ class KillChainPhase: Manages kill chain phases (ATT&CK tactics) in the OpenCTI platform. :param opencti: instance of :py:class:`~pycti.api.opencti_api_client.OpenCTIApiClient` + :type opencti: OpenCTIApiClient """ def __init__(self, opencti): + """Initialize the KillChainPhase instance. + + :param opencti: OpenCTI API client instance + :type opencti: OpenCTIApiClient + """ self.opencti = opencti self.properties = """ id @@ -31,24 +37,50 @@ def __init__(self, opencti): @staticmethod def generate_id(phase_name, kill_chain_name): + """Generate a STIX ID for a Kill Chain Phase. + + :param phase_name: The phase name + :type phase_name: str + :param kill_chain_name: The kill chain name + :type kill_chain_name: str + :return: STIX ID for the kill chain phase + :rtype: str + """ return kill_chain_phase_generate_id( phase_name=phase_name, kill_chain_name=kill_chain_name ) @staticmethod def generate_id_from_data(data): + """Generate a STIX ID from kill chain phase data. + + :param data: Dictionary containing 'phase_name' and 'kill_chain_name' keys + :type data: dict + :return: STIX ID for the kill chain phase + :rtype: str + """ return KillChainPhase.generate_id(data["phase_name"], data["kill_chain_name"]) - """ - List Kill-Chain-Phase objects + def list(self, **kwargs): + """List Kill-Chain-Phase objects. :param filters: the filters to apply + :type filters: dict :param first: return the first n rows from the after ID (or the beginning if not set) + :type first: int :param after: ID of the first row for pagination - :return List of Kill-Chain-Phase objects - """ - - def list(self, **kwargs): + :type after: str + :param orderBy: field to order results by + :type orderBy: str + :param orderMode: ordering mode (asc/desc) + :type orderMode: str + :param customAttributes: custom attributes to return + :type customAttributes: list + :param withPagination: whether to include pagination info + :type withPagination: bool + :return: List of Kill-Chain-Phase objects + :rtype: list + """ filters = kwargs.get("filters", None) first = kwargs.get("first", 500) after = kwargs.get("after", None) @@ -96,15 +128,16 @@ def list(self, **kwargs): result["data"]["killChainPhases"], with_pagination ) - """ - Read a Kill-Chain-Phase object + def read(self, **kwargs): + """Read a Kill-Chain-Phase object. :param id: the id of the Kill-Chain-Phase + :type id: str :param filters: the filters to apply if no id provided - :return Kill-Chain-Phase object - """ - - def read(self, **kwargs): + :type filters: dict + :return: Kill-Chain-Phase object + :rtype: dict or None + """ id = kwargs.get("id", None) filters = kwargs.get("filters", None) if id is not None: @@ -136,14 +169,26 @@ def read(self, **kwargs): ) return None - """ - Create a Kill-Chain-Phase object - - :param name: the name of the Kill-Chain-Phase - :return Kill-Chain-Phase object - """ - def create(self, **kwargs): + """Create a Kill-Chain-Phase object. + + :param stix_id: (optional) the STIX ID + :type stix_id: str + :param created: (optional) creation date + :type created: datetime + :param modified: (optional) modification date + :type modified: datetime + :param kill_chain_name: the kill chain name (required) + :type kill_chain_name: str + :param phase_name: the phase name (required) + :type phase_name: str + :param x_opencti_order: (optional) order (default: 0) + :type x_opencti_order: int + :param update: (optional) whether to update if exists (default: False) + :type update: bool + :return: Kill-Chain-Phase object + :rtype: dict or None + """ stix_id = kwargs.get("stix_id", None) created = kwargs.get("created", None) modified = kwargs.get("modified", None) @@ -188,16 +233,18 @@ def create(self, **kwargs): self.opencti.app_logger.error( "[opencti_kill_chain_phase] Missing parameters: kill_chain_name and phase_name", ) + return None - """ - Update a Kill chain object field + def update_field(self, **kwargs): + """Update a Kill Chain Phase object field. - :param id: the Kill chain id + :param id: the Kill Chain Phase id + :type id: str :param input: the input of the field - :return The updated Kill chain object - """ - - def update_field(self, **kwargs): + :type input: list + :return: The updated Kill Chain Phase object + :rtype: dict or None + """ id = kwargs.get("id", None) input = kwargs.get("input", None) if id is not None and input is not None: @@ -225,11 +272,17 @@ def update_field(self, **kwargs): ) else: self.opencti.app_logger.error( - "[opencti_kill_chain] Missing parameters: id and key and value" + "[opencti_kill_chain_phase] Missing parameters: id and input" ) return None def delete(self, **kwargs): + """Delete a Kill-Chain-Phase object. + + :param id: the id of the Kill-Chain-Phase to delete + :type id: str + :return: None + """ id = kwargs.get("id", None) if id is not None: self.opencti.app_logger.info("Deleting Kill-Chain-Phase", {"id": id}) diff --git a/client-python/pycti/entities/opencti_label.py b/client-python/pycti/entities/opencti_label.py index d50f6b43caa0..29cfe428cb1a 100644 --- a/client-python/pycti/entities/opencti_label.py +++ b/client-python/pycti/entities/opencti_label.py @@ -12,9 +12,15 @@ class Label: Manages labels and tags in the OpenCTI platform. :param opencti: instance of :py:class:`~pycti.api.opencti_api_client.OpenCTIApiClient` + :type opencti: OpenCTIApiClient """ def __init__(self, opencti): + """Initialize the Label instance. + + :param opencti: OpenCTI API client instance + :type opencti: OpenCTIApiClient + """ self.opencti = opencti self.properties = """ id @@ -27,21 +33,42 @@ def __init__(self, opencti): @staticmethod def generate_id(value): + """Generate a STIX ID for a Label. + + :param value: The label value + :type value: str + :return: STIX ID for the label + :rtype: str + """ data = {"value": value} data = canonicalize(data, utf8=False) id = str(uuid.uuid5(uuid.UUID("00abedb4-aa42-466c-9c01-fed23315a9b7"), data)) return "label--" + id - """ - List Label objects + def list(self, **kwargs): + """List Label objects. :param filters: the filters to apply + :type filters: dict + :param search: the search keyword + :type search: str :param first: return the first n rows from the after ID (or the beginning if not set) + :type first: int :param after: ID of the first row for pagination - :return List of Label objects - """ - - def list(self, **kwargs): + :type after: str + :param orderBy: field to order results by + :type orderBy: str + :param orderMode: ordering mode (asc/desc) + :type orderMode: str + :param customAttributes: custom attributes to return + :type customAttributes: list + :param getAll: whether to retrieve all results + :type getAll: bool + :param withPagination: whether to include pagination info + :type withPagination: bool + :return: List of Label objects + :rtype: list + """ filters = kwargs.get("filters", None) search = kwargs.get("search", None) first = kwargs.get("first", 500) @@ -114,15 +141,16 @@ def list(self, **kwargs): result["data"]["labels"], with_pagination ) - """ - Read a Label object + def read(self, **kwargs): + """Read a Label object. :param id: the id of the Label + :type id: str :param filters: the filters to apply if no id provided - :return Label object - """ - - def read(self, **kwargs): + :type filters: dict + :return: Label object + :rtype: dict or None + """ id = kwargs.get("id", None) filters = kwargs.get("filters", None) if id is not None: @@ -152,15 +180,22 @@ def read(self, **kwargs): ) return None - """ - Create a Label object - - :param value: the value - :param color: the color - :return label object - """ - def create(self, **kwargs): + """Create a Label object. + + :param stix_id: (optional) the STIX ID + :type stix_id: str + :param value: the label value (required) + :type value: str + :param color: (optional) the label color + :type color: str + :param x_opencti_stix_ids: (optional) list of additional STIX IDs + :type x_opencti_stix_ids: list + :param update: (optional) whether to update if exists (default: False) + :type update: bool + :return: Label object + :rtype: dict or None + """ stix_id = kwargs.get("stix_id", None) value = kwargs.get("value", None) color = kwargs.get("color", None) @@ -195,14 +230,18 @@ def create(self, **kwargs): return self.opencti.process_multiple_fields(result["data"]["labelAdd"]) else: self.opencti.app_logger.error("[opencti_label] Missing parameters: value") - - """ - Read or create a Label - If the user has no rights to create the label, return None - :return The available or created Label object - """ + return None def read_or_create_unchecked(self, **kwargs): + """Read or create a Label. + + If the user has no rights to create the label, return None. + + :param value: the label value + :type value: str + :return: The available or created Label object + :rtype: dict or None + """ value = kwargs.get("value", None) label = self.read( filters={ @@ -218,15 +257,16 @@ def read_or_create_unchecked(self, **kwargs): return None return label - """ - Update a Label object field + def update_field(self, **kwargs): + """Update a Label object field. :param id: the Label id + :type id: str :param input: the input of the field - :return The updated Label object - """ - - def update_field(self, **kwargs): + :type input: list + :return: The updated Label object + :rtype: dict or None + """ id = kwargs.get("id", None) input = kwargs.get("input", None) if id is not None and input is not None: @@ -254,11 +294,17 @@ def update_field(self, **kwargs): ) else: self.opencti.app_logger.error( - "[opencti_label] Missing parameters: id and key and value" + "[opencti_label] Missing parameters: id and input" ) return None def delete(self, **kwargs): + """Delete a Label object. + + :param id: the id of the Label to delete + :type id: str + :return: None + """ id = kwargs.get("id", None) if id is not None: self.opencti.app_logger.info("Deleting Label", {"id": id}) diff --git a/client-python/pycti/entities/opencti_language.py b/client-python/pycti/entities/opencti_language.py index 965421599842..146962f7f59e 100644 --- a/client-python/pycti/entities/opencti_language.py +++ b/client-python/pycti/entities/opencti_language.py @@ -12,9 +12,15 @@ class Language: Manages language entities in the OpenCTI platform. :param opencti: instance of :py:class:`~pycti.api.opencti_api_client.OpenCTIApiClient` + :type opencti: OpenCTIApiClient """ def __init__(self, opencti): + """Initialize the Language instance. + + :param opencti: OpenCTI API client instance + :type opencti: OpenCTIApiClient + """ self.opencti = opencti self.properties = """ id @@ -62,6 +68,11 @@ def __init__(self, opencti): x_opencti_lastname } } + objectOrganization { + id + standard_id + name + } objectMarking { id standard_id @@ -173,6 +184,11 @@ def __init__(self, opencti): x_opencti_lastname } } + objectOrganization { + id + standard_id + name + } objectMarking { id standard_id @@ -241,6 +257,13 @@ def __init__(self, opencti): @staticmethod def generate_id(name): + """Generate a STIX ID for a Language. + + :param name: the name of the Language + :type name: str + :return: STIX ID for the Language + :rtype: str + """ name = name.lower().strip() data = {"name": name} data = canonicalize(data, utf8=False) @@ -249,19 +272,29 @@ def generate_id(name): @staticmethod def generate_id_from_data(data): + """Generate a STIX ID from Language data. + + :param data: Dictionary containing a 'name' key + :type data: dict + :return: STIX ID for the Language + :rtype: str + """ return Language.generate_id(data["name"]) - """ - List Language objects + def list(self, **kwargs): + """List Language objects. :param filters: the filters to apply + :type filters: dict :param search: the search keyword + :type search: str :param first: return the first n rows from the after ID (or the beginning if not set) + :type first: int :param after: ID of the first row for pagination - :return List of Language objects - """ - - def list(self, **kwargs): + :type after: str + :return: List of Language objects + :rtype: list + """ filters = kwargs.get("filters", None) search = kwargs.get("search", None) first = kwargs.get("first", 100) @@ -339,15 +372,20 @@ def list(self, **kwargs): result["data"]["languages"], with_pagination ) - """ - Read a Language object + def read(self, **kwargs): + """Read a Language object. :param id: the id of the Language + :type id: str :param filters: the filters to apply if no id provided - :return Language object - """ - - def read(self, **kwargs): + :type filters: dict + :param customAttributes: custom attributes to return + :type customAttributes: str + :param withFiles: whether to include files + :type withFiles: bool + :return: Language object + :rtype: dict or None + """ id = kwargs.get("id", None) filters = kwargs.get("filters", None) custom_attributes = kwargs.get("customAttributes", None) @@ -383,14 +421,44 @@ def read(self, **kwargs): ) return None - """ - Create a Language object - - :param name: the name of the Language - :return Language object - """ - def create(self, **kwargs): + """Create a Language object. + + :param stix_id: (optional) the STIX ID + :type stix_id: str + :param createdBy: (optional) the author ID + :type createdBy: str + :param objectMarking: (optional) list of marking definition IDs + :type objectMarking: list + :param objectLabel: (optional) list of label IDs + :type objectLabel: list + :param externalReferences: (optional) list of external reference IDs + :type externalReferences: list + :param revoked: (optional) whether the language is revoked + :type revoked: bool + :param confidence: (optional) confidence level (0-100) + :type confidence: int + :param lang: (optional) language code + :type lang: str + :param created: (optional) creation date + :type created: str + :param modified: (optional) modification date + :type modified: str + :param name: the name of the Language (required) + :type name: str + :param aliases: (optional) list of aliases + :type aliases: list + :param x_opencti_stix_ids: (optional) list of additional STIX IDs + :type x_opencti_stix_ids: list + :param x_opencti_modified_at: (optional) custom modification date + :type x_opencti_modified_at: str + :param update: (optional) whether to update if exists (default: False) + :type update: bool + :param file: (optional) file object to attach + :param fileMarkings: (optional) list of marking definition IDs for the file + :return: Language object + :rtype: dict or None + """ stix_id = kwargs.get("stix_id", None) created_by = kwargs.get("createdBy", None) object_marking = kwargs.get("objectMarking", None) @@ -406,6 +474,8 @@ def create(self, **kwargs): x_opencti_stix_ids = kwargs.get("x_opencti_stix_ids", None) x_opencti_modified_at = kwargs.get("x_opencti_modified_at", None) update = kwargs.get("update", False) + file = kwargs.get("file", None) + file_markings = kwargs.get("fileMarkings", None) if name is not None: self.opencti.app_logger.info("Creating Language", {"name": name}) @@ -438,21 +508,28 @@ def create(self, **kwargs): "x_opencti_stix_ids": x_opencti_stix_ids, "x_opencti_modified_at": x_opencti_modified_at, "update": update, + "file": file, + "fileMarkings": file_markings, } }, ) return self.opencti.process_multiple_fields(result["data"]["languageAdd"]) else: self.opencti.app_logger.error("[opencti_language] Missing parameters: name") - - """ - Import an Language object from a STIX2 object - - :param stixObject: the Stix-Object Language - :return Language object - """ + return None def import_from_stix2(self, **kwargs): + """Import a Language object from a STIX2 object. + + :param stixObject: the STIX2 Language object + :type stixObject: dict + :param extras: extra parameters including created_by_id, object_marking_ids, etc. + :type extras: dict + :param update: whether to update if the entity already exists + :type update: bool + :return: Language object + :rtype: dict or None + """ stix_object = kwargs.get("stixObject", None) extras = kwargs.get("extras", {}) update = kwargs.get("update", False) @@ -471,7 +548,7 @@ def import_from_stix2(self, **kwargs): self.opencti.get_attribute_in_extension("modified_at", stix_object) ) - return self.opencti.language.create( + return self.create( stix_id=stix_object["id"], createdBy=( extras["created_by_id"] if "created_by_id" in extras else None @@ -509,8 +586,11 @@ def import_from_stix2(self, **kwargs): else None ), update=update, + file=extras.get("file"), + fileMarkings=extras.get("fileMarkings"), ) else: self.opencti.app_logger.error( "[opencti_language] Missing parameters: stixObject" ) + return None diff --git a/client-python/pycti/entities/opencti_location.py b/client-python/pycti/entities/opencti_location.py index f83234401d41..c213f0bf6e81 100644 --- a/client-python/pycti/entities/opencti_location.py +++ b/client-python/pycti/entities/opencti_location.py @@ -12,9 +12,15 @@ class Location: Manages geographic locations (countries, cities, regions) in the OpenCTI platform. :param opencti: instance of :py:class:`~pycti.api.opencti_api_client.OpenCTIApiClient` + :type opencti: OpenCTIApiClient """ def __init__(self, opencti): + """Initialize the Location instance. + + :param opencti: OpenCTI API client instance + :type opencti: OpenCTIApiClient + """ self.opencti = opencti self.properties = """ id @@ -233,6 +239,19 @@ def __init__(self, opencti): @staticmethod def generate_id(name, x_opencti_location_type, latitude=None, longitude=None): + """Generate a STIX ID for a Location. + + :param name: The name of the location + :type name: str + :param x_opencti_location_type: The type of location (Country, City, Region, Position) + :type x_opencti_location_type: str + :param latitude: Optional latitude coordinate + :type latitude: float or None + :param longitude: Optional longitude coordinate + :type longitude: float or None + :return: STIX ID for the location + :rtype: str + """ if x_opencti_location_type == "Position": if latitude is not None and longitude is None: data = {"latitude": latitude} @@ -253,6 +272,13 @@ def generate_id(name, x_opencti_location_type, latitude=None, longitude=None): @staticmethod def generate_id_from_data(data): + """Generate a STIX ID from location data. + + :param data: Dictionary containing 'name', 'x_opencti_location_type', and optionally 'latitude'/'longitude' + :type data: dict + :return: STIX ID for the location + :rtype: str + """ return Location.generate_id( data.get("name"), data.get("x_opencti_location_type"), @@ -260,18 +286,34 @@ def generate_id_from_data(data): data.get("longitude"), ) - """ - List Location objects + def list(self, **kwargs): + """List Location objects. - :param types: the list of types + :param types: the list of location types to filter by + :type types: list :param filters: the filters to apply + :type filters: dict :param search: the search keyword + :type search: str :param first: return the first n rows from the after ID (or the beginning if not set) + :type first: int :param after: ID of the first row for pagination - :return List of Location objects - """ - - def list(self, **kwargs): + :type after: str + :param orderBy: field to order results by + :type orderBy: str + :param orderMode: ordering mode (asc/desc) + :type orderMode: str + :param customAttributes: custom attributes to return + :type customAttributes: list + :param getAll: whether to retrieve all results + :type getAll: bool + :param withPagination: whether to include pagination info + :type withPagination: bool + :param withFiles: whether to include files + :type withFiles: bool + :return: List of Location objects + :rtype: list + """ types = kwargs.get("types", None) filters = kwargs.get("filters", None) search = kwargs.get("search", None) @@ -280,6 +322,7 @@ def list(self, **kwargs): order_by = kwargs.get("orderBy", None) order_mode = kwargs.get("orderMode", None) custom_attributes = kwargs.get("customAttributes", None) + get_all = kwargs.get("getAll", False) with_pagination = kwargs.get("withPagination", False) with_files = kwargs.get("withFiles", False) @@ -324,19 +367,47 @@ def list(self, **kwargs): "orderMode": order_mode, }, ) - return self.opencti.process_multiple( - result["data"]["locations"], with_pagination - ) + if get_all: + final_data = [] + data = self.opencti.process_multiple(result["data"]["locations"]) + final_data = final_data + data + while result["data"]["locations"]["pageInfo"]["hasNextPage"]: + after = result["data"]["locations"]["pageInfo"]["endCursor"] + self.opencti.app_logger.debug("Listing Locations", {"after": after}) + result = self.opencti.query( + query, + { + "types": types, + "filters": filters, + "search": search, + "first": first, + "after": after, + "orderBy": order_by, + "orderMode": order_mode, + }, + ) + data = self.opencti.process_multiple(result["data"]["locations"]) + final_data = final_data + data + return final_data + else: + return self.opencti.process_multiple( + result["data"]["locations"], with_pagination + ) - """ - Read a Location object + def read(self, **kwargs): + """Read a Location object. :param id: the id of the Location + :type id: str :param filters: the filters to apply if no id provided - :return Location object - """ - - def read(self, **kwargs): + :type filters: dict + :param customAttributes: custom attributes to return + :type customAttributes: list + :param withFiles: whether to include files + :type withFiles: bool + :return: Location object + :rtype: dict or None + """ id = kwargs.get("id", None) filters = kwargs.get("filters", None) custom_attributes = kwargs.get("customAttributes", None) @@ -372,14 +443,56 @@ def read(self, **kwargs): ) return None - """ - Create a Location object - - :param name: the name of the Location - :return Location object - """ - def create(self, **kwargs): + """Create a Location object. + + :param type: the type of location (Country, City, Region, Position) + :type type: str + :param stix_id: (optional) the STIX ID + :type stix_id: str + :param createdBy: (optional) the author ID + :type createdBy: str + :param objectMarking: (optional) list of marking definition IDs + :type objectMarking: list + :param objectLabel: (optional) list of label IDs + :type objectLabel: list + :param externalReferences: (optional) list of external reference IDs + :type externalReferences: list + :param revoked: (optional) whether the location is revoked + :type revoked: bool + :param confidence: (optional) confidence level (0-100) + :type confidence: int + :param lang: (optional) language + :type lang: str + :param created: (optional) creation date + :type created: datetime + :param modified: (optional) modification date + :type modified: datetime + :param name: the name of the Location (required) + :type name: str + :param description: (optional) description + :type description: str + :param latitude: (optional) latitude coordinate + :type latitude: float + :param longitude: (optional) longitude coordinate + :type longitude: float + :param precision: (optional) precision in meters + :type precision: float + :param x_opencti_aliases: (optional) list of aliases + :type x_opencti_aliases: list + :param x_opencti_stix_ids: (optional) list of additional STIX IDs + :type x_opencti_stix_ids: list + :param x_opencti_workflow_id: (optional) workflow ID + :type x_opencti_workflow_id: str + :param x_opencti_modified_at: (optional) custom modification date + :type x_opencti_modified_at: datetime + :param update: (optional) whether to update if exists (default: False) + :type update: bool + :param file: (optional) file object to attach + :param fileMarkings: (optional) list of marking definition IDs for the file + :return: Location object + :rtype: dict or None + """ type = kwargs.get("type", None) stix_id = kwargs.get("stix_id", None) created_by = kwargs.get("createdBy", None) @@ -401,6 +514,8 @@ def create(self, **kwargs): x_opencti_workflow_id = kwargs.get("x_opencti_workflow_id", None) x_opencti_modified_at = kwargs.get("x_opencti_modified_at", None) update = kwargs.get("update", False) + file = kwargs.get("file", None) + file_markings = kwargs.get("fileMarkings", None) if name is not None: self.opencti.app_logger.info("Creating Location", {"name": name}) @@ -439,21 +554,28 @@ def create(self, **kwargs): "x_opencti_workflow_id": x_opencti_workflow_id, "x_opencti_modified_at": x_opencti_modified_at, "update": update, + "file": file, + "fileMarkings": file_markings, } }, ) return self.opencti.process_multiple_fields(result["data"]["locationAdd"]) else: - self.opencti.app_logger.error("Missing parameters: name") + self.opencti.app_logger.error("[opencti_location] Missing parameters: name") + return None - """ - Import an Location object from a STIX2 object + def import_from_stix2(self, **kwargs): + """Import a Location object from a STIX2 object. :param stixObject: the Stix-Object Location - :return Location object - """ - - def import_from_stix2(self, **kwargs): + :type stixObject: dict + :param extras: extra dict + :type extras: dict + :param update: set the update flag on import + :type update: bool + :return: Location object + :rtype: dict or None + """ stix_object = kwargs.get("stixObject", None) extras = kwargs.get("extras", {}) update = kwargs.get("update", False) @@ -467,7 +589,7 @@ def import_from_stix2(self, **kwargs): name = stix_object["region"] else: self.opencti.app_logger.error("[opencti_location] Missing name") - return + return None if "x_opencti_location_type" in stix_object: type = stix_object["x_opencti_location_type"] elif self.opencti.get_attribute_in_extension("type", stix_object) is not None: @@ -556,8 +678,11 @@ def import_from_stix2(self, **kwargs): else None ), update=update, + file=extras.get("file"), + fileMarkings=extras.get("fileMarkings"), ) else: self.opencti.app_logger.error( "[opencti_location] Missing parameters: stixObject" ) + return None diff --git a/client-python/pycti/entities/opencti_malware.py b/client-python/pycti/entities/opencti_malware.py index 7aac182ed385..b7538b6b4c38 100644 --- a/client-python/pycti/entities/opencti_malware.py +++ b/client-python/pycti/entities/opencti_malware.py @@ -12,9 +12,15 @@ class Malware: Manages malware families and variants in the OpenCTI platform. :param opencti: instance of :py:class:`~pycti.api.opencti_api_client.OpenCTIApiClient` + :type opencti: OpenCTIApiClient """ def __init__(self, opencti): + """Initialize the Malware instance. + + :param opencti: OpenCTI API client instance + :type opencti: OpenCTIApiClient + """ self.opencti = opencti self.properties = """ id @@ -295,15 +301,25 @@ def list(self, **kwargs): """List Malware objects. :param filters: the filters to apply + :type filters: dict :param search: the search keyword + :type search: str :param first: return the first n rows from the after ID (or the beginning if not set) + :type first: int :param after: ID of the first row for pagination + :type after: str :param orderBy: field to order results by + :type orderBy: str :param orderMode: ordering mode (asc/desc) + :type orderMode: str :param customAttributes: custom attributes to return + :type customAttributes: str :param getAll: whether to retrieve all results + :type getAll: bool :param withPagination: whether to include pagination info + :type withPagination: bool :param withFiles: whether to include files + :type withFiles: bool :return: List of Malware objects :rtype: list """ @@ -389,9 +405,13 @@ def read(self, **kwargs): """Read a Malware object. :param id: the id of the Malware + :type id: str :param filters: the filters to apply if no id provided + :type filters: dict :param customAttributes: custom attributes to return + :type customAttributes: str :param withFiles: whether to include files + :type withFiles: bool :return: Malware object :rtype: dict or None """ @@ -433,23 +453,64 @@ def read(self, **kwargs): def create(self, **kwargs): """Create a Malware object. - :param name: the name of the Malware + :param stix_id: (optional) the STIX ID + :type stix_id: str + :param name: the name of the Malware (required) + :type name: str :param description: description of the malware + :type description: str :param aliases: list of aliases + :type aliases: list :param malware_types: types of malware + :type malware_types: list :param is_family: whether this is a malware family + :type is_family: bool :param first_seen: first seen date + :type first_seen: str :param last_seen: last seen date + :type last_seen: str :param architecture_execution_envs: execution environments + :type architecture_execution_envs: list :param implementation_languages: implementation languages + :type implementation_languages: list :param capabilities: malware capabilities + :type capabilities: list :param killChainPhases: kill chain phases + :type killChainPhases: list :param samples: malware samples - :param createdBy: creator identity - :param objectMarking: marking definitions - :param objectLabel: labels - :param externalReferences: external references + :type samples: list + :param createdBy: creator identity ID + :type createdBy: str + :param objectMarking: marking definition IDs + :type objectMarking: list + :param objectLabel: label IDs + :type objectLabel: list + :param externalReferences: external reference IDs + :type externalReferences: list + :param objectOrganization: organization IDs + :type objectOrganization: list + :param revoked: whether the malware is revoked + :type revoked: bool + :param confidence: confidence level (0-100) + :type confidence: int + :param lang: language + :type lang: str + :param created: creation date + :type created: str + :param modified: modification date + :type modified: str + :param x_opencti_stix_ids: additional STIX IDs + :type x_opencti_stix_ids: list + :param x_opencti_workflow_id: workflow ID + :type x_opencti_workflow_id: str + :param x_opencti_modified_at: custom modification date + :type x_opencti_modified_at: str :param update: whether to update existing malware + :type update: bool + :param file: (optional) File object to attach + :type file: dict + :param fileMarkings: (optional) list of marking definition IDs for the file + :type fileMarkings: list :return: Malware object :rtype: dict or None """ @@ -480,6 +541,8 @@ def create(self, **kwargs): x_opencti_modified_at = kwargs.get("x_opencti_modified_at", None) samples = kwargs.get("samples", None) update = kwargs.get("update", False) + file = kwargs.get("file", None) + file_markings = kwargs.get("fileMarkings", None) if name is not None: self.opencti.app_logger.info("Creating Malware", {"name": name}) @@ -524,23 +587,28 @@ def create(self, **kwargs): "x_opencti_modified_at": x_opencti_modified_at, "samples": samples, "update": update, + "file": file, + "fileMarkings": file_markings, } }, ) return self.opencti.process_multiple_fields(result["data"]["malwareAdd"]) else: - self.opencti.app_logger.error( - "[opencti_malware] Missing parameters: name and description" - ) - - """ - Import an Malware object from a STIX2 object - - :param stixObject: the Stix-Object Malware - :return Malware object - """ + self.opencti.app_logger.error("[opencti_malware] Missing parameters: name") + return None def import_from_stix2(self, **kwargs): + """Import a Malware object from a STIX2 object. + + :param stixObject: the STIX2 Malware object + :type stixObject: dict + :param extras: extra parameters including created_by_id, object_marking_ids, etc. + :type extras: dict + :param update: whether to update if the entity already exists + :type update: bool + :return: Malware object + :rtype: dict or None + """ stix_object = kwargs.get("stixObject", None) extras = kwargs.get("extras", {}) update = kwargs.get("update", False) @@ -651,8 +719,11 @@ def import_from_stix2(self, **kwargs): ), samples=(extras["sample_ids"] if "sample_ids" in extras else None), update=update, + file=extras.get("file"), + fileMarkings=extras.get("fileMarkings"), ) else: self.opencti.app_logger.error( "[opencti_malware] Missing parameters: stixObject" ) + return None diff --git a/client-python/pycti/entities/opencti_malware_analysis.py b/client-python/pycti/entities/opencti_malware_analysis.py index 84cacc89ff56..cd766c4ce036 100644 --- a/client-python/pycti/entities/opencti_malware_analysis.py +++ b/client-python/pycti/entities/opencti_malware_analysis.py @@ -12,9 +12,15 @@ class MalwareAnalysis: Manages malware analysis reports and results in the OpenCTI platform. :param opencti: instance of :py:class:`~pycti.api.opencti_api_client.OpenCTIApiClient` + :type opencti: OpenCTIApiClient """ def __init__(self, opencti): + """Initialize the MalwareAnalysis instance. + + :param opencti: OpenCTI API client instance + :type opencti: OpenCTIApiClient + """ self.opencti = opencti self.properties = """ id @@ -243,6 +249,17 @@ def __init__(self, opencti): @staticmethod def generate_id(result_name, product=None, submitted=None): + """Generate a STIX ID for a Malware Analysis. + + :param result_name: the result name of the analysis + :type result_name: str + :param product: the product that performed the analysis (optional) + :type product: str + :param submitted: the submission date (optional) + :type submitted: str + :return: STIX ID for the Malware Analysis + :rtype: str + """ result_name = result_name.lower().strip() data = {"result_name": result_name, "product": product} if submitted is not None: @@ -253,21 +270,31 @@ def generate_id(result_name, product=None, submitted=None): @staticmethod def generate_id_from_data(data): + """Generate a STIX ID from Malware Analysis data. + + :param data: Dictionary containing 'result_name', 'product', and optionally 'submitted' keys + :type data: dict + :return: STIX ID for the Malware Analysis + :rtype: str + """ return MalwareAnalysis.generate_id( data["result_name"], data["product"], data.get("submitted") ) - """ - List Malware analysis objects + def list(self, **kwargs): + """List Malware analysis objects. :param filters: the filters to apply + :type filters: dict :param search: the search keyword + :type search: str :param first: return the first n rows from the after ID (or the beginning if not set) + :type first: int :param after: ID of the first row for pagination - :return List of MalwareAnalysis objects - """ - - def list(self, **kwargs): + :type after: str + :return: List of MalwareAnalysis objects + :rtype: list + """ filters = kwargs.get("filters", None) search = kwargs.get("search", None) first = kwargs.get("first", 500) @@ -326,7 +353,7 @@ def list(self, **kwargs): final_data = final_data + data while result["data"]["malwareAnalyses"]["pageInfo"]["hasNextPage"]: after = result["data"]["malwareAnalyses"]["pageInfo"]["endCursor"] - self.opencti.app_logger.info( + self.opencti.app_logger.debug( "Listing Malware analyses", {"after": after} ) result = self.opencti.query( @@ -348,15 +375,16 @@ def list(self, **kwargs): result["data"]["malwareAnalyses"], with_pagination ) - """ - Read a Malware analysis object + def read(self, **kwargs): + """Read a Malware analysis object. :param id: the id of the Malware analysis + :type id: str :param filters: the filters to apply if no id provided - :return Malware analysis object - """ - - def read(self, **kwargs): + :type filters: dict + :return: Malware analysis object + :rtype: dict or None + """ id = kwargs.get("id", None) filters = kwargs.get("filters", None) custom_attributes = kwargs.get("customAttributes", None) @@ -390,18 +418,24 @@ def read(self, **kwargs): return None else: self.opencti.app_logger.error( - "[opencti_malwareAnalysis] Missing parameters: id or filters" + "[opencti_malware_analysis] Missing parameters: id or filters" ) return None - """ - Create a Malware analysis object - - :param name: the name of the Malware analysis - :return Malware analysis object - """ - def create(self, **kwargs): + """Create a Malware analysis object. + + :param product: the product that performed the analysis (required) + :type product: str + :param result_name: the result name of the analysis (required) + :type result_name: str + :param file: File object to attach (optional) + :type file: File + :param fileMarkings: list of marking definition IDs for the file (optional) + :type fileMarkings: list + :return: Malware analysis object + :rtype: dict or None + """ stix_id = kwargs.get("stix_id", None) created_by = kwargs.get("createdBy", None) object_marking = kwargs.get("objectMarking", None) @@ -433,6 +467,8 @@ def create(self, **kwargs): x_opencti_workflow_id = kwargs.get("x_opencti_workflow_id", None) x_opencti_modified_at = kwargs.get("x_opencti_modified_at", None) update = kwargs.get("update", False) + file = kwargs.get("file", None) + file_markings = kwargs.get("fileMarkings", None) if product is not None and result_name is not None: self.opencti.app_logger.info( @@ -483,6 +519,8 @@ def create(self, **kwargs): "x_opencti_workflow_id": x_opencti_workflow_id, "x_opencti_modified_at": x_opencti_modified_at, "update": update, + "file": file, + "fileMarkings": file_markings, } }, ) @@ -491,17 +529,22 @@ def create(self, **kwargs): ) else: self.opencti.app_logger.error( - "[opencti_malwareAnalysis] Missing parameters: product and result_name" + "[opencti_malware_analysis] Missing parameters: product and result_name" ) + return None - """ - Import an Malware analysis object from a STIX2 object + def import_from_stix2(self, **kwargs): + """Import a Malware analysis object from a STIX2 object. :param stixObject: the Stix-Object Malware analysis - :return Malware analysis object - """ - - def import_from_stix2(self, **kwargs): + :type stixObject: dict + :param extras: additional parameters like created_by_id, object_marking_ids + :type extras: dict + :param update: whether to update existing object + :type update: bool + :return: Malware analysis object + :rtype: dict or None + """ stix_object = kwargs.get("stixObject", None) extras = kwargs.get("extras", {}) update = kwargs.get("update", False) @@ -626,8 +669,11 @@ def import_from_stix2(self, **kwargs): else None ), update=update, + file=extras.get("file"), + fileMarkings=extras.get("fileMarkings"), ) else: self.opencti.app_logger.error( "[opencti_malware_analysis] Missing parameters: stixObject" ) + return None diff --git a/client-python/pycti/entities/opencti_marking_definition.py b/client-python/pycti/entities/opencti_marking_definition.py index 01ac52671154..4204625cbdc0 100644 --- a/client-python/pycti/entities/opencti_marking_definition.py +++ b/client-python/pycti/entities/opencti_marking_definition.py @@ -12,9 +12,15 @@ class MarkingDefinition: Manages marking definitions (TLP, statements) in the OpenCTI platform. :param opencti: instance of :py:class:`~pycti.api.opencti_api_client.OpenCTIApiClient` + :type opencti: OpenCTIApiClient """ def __init__(self, opencti): + """Initialize the MarkingDefinition instance. + + :param opencti: OpenCTI API client instance + :type opencti: OpenCTIApiClient + """ self.opencti = opencti self.properties = """ id @@ -33,6 +39,15 @@ def __init__(self, opencti): @staticmethod def generate_id(definition_type, definition): + """Generate a STIX ID for a Marking Definition. + + :param definition_type: The type of marking (TLP, statement, etc.) + :type definition_type: str + :param definition: The definition value + :type definition: str + :return: STIX ID for the marking definition + :rtype: str + """ # Handle static IDs from OpenCTI if definition_type == "TLP": if definition == "TLP:CLEAR" or definition == "TLP:WHITE": @@ -53,20 +68,37 @@ def generate_id(definition_type, definition): @staticmethod def generate_id_from_data(data): + """Generate a STIX ID from marking definition data. + + :param data: Dictionary containing 'definition_type' and 'definition' keys + :type data: dict + :return: STIX ID for the marking definition + :rtype: str + """ return MarkingDefinition.generate_id( data["definition_type"], data["definition"] ) - """ - List Marking-Definition objects + def list(self, **kwargs): + """List Marking-Definition objects. :param filters: the filters to apply + :type filters: dict :param first: return the first n rows from the after ID (or the beginning if not set) + :type first: int :param after: ID of the first row for pagination - :return List of Marking-Definition objects - """ - - def list(self, **kwargs): + :type after: str + :param orderBy: field to order results by + :type orderBy: str + :param orderMode: ordering mode (asc/desc) + :type orderMode: str + :param customAttributes: custom attributes to return + :type customAttributes: list + :param withPagination: whether to include pagination info + :type withPagination: bool + :return: List of Marking-Definition objects + :rtype: list + """ filters = kwargs.get("filters", None) first = kwargs.get("first", 500) after = kwargs.get("after", None) @@ -114,15 +146,16 @@ def list(self, **kwargs): result["data"]["markingDefinitions"], with_pagination ) - """ - Read a Marking-Definition object + def read(self, **kwargs): + """Read a Marking-Definition object. :param id: the id of the Marking-Definition + :type id: str :param filters: the filters to apply if no id provided - :return Marking-Definition object - """ - - def read(self, **kwargs): + :type filters: dict + :return: Marking-Definition object + :rtype: dict or None + """ id = kwargs.get("id", None) filters = kwargs.get("filters", None) if id is not None: @@ -154,15 +187,30 @@ def read(self, **kwargs): ) return None - """ - Create a Marking-Definition object - - :param definition_type: the definition_type - :param definition: the definition - :return Marking-Definition object - """ - def create(self, **kwargs): + """Create a Marking-Definition object. + + :param stix_id: (optional) the STIX ID + :type stix_id: str + :param created: (optional) creation date + :type created: datetime + :param modified: (optional) modification date + :type modified: datetime + :param definition_type: the definition type (required) + :type definition_type: str + :param definition: the definition value (required) + :type definition: str + :param x_opencti_order: (optional) order (default: 0) + :type x_opencti_order: int + :param x_opencti_color: (optional) color + :type x_opencti_color: str + :param x_opencti_stix_ids: (optional) list of additional STIX IDs + :type x_opencti_stix_ids: list + :param update: (optional) whether to update if exists (default: False) + :type update: bool + :return: Marking-Definition object + :rtype: dict or None + """ stix_id = kwargs.get("stix_id", None) created = kwargs.get("created", None) modified = kwargs.get("modified", None) @@ -208,16 +256,18 @@ def create(self, **kwargs): self.opencti.app_logger.error( "[opencti_marking_definition] Missing parameters: definition and definition_type", ) + return None - """ - Update a Marking definition object field + def update_field(self, **kwargs): + """Update a Marking Definition object field. - :param id: the Marking definition id + :param id: the Marking Definition id + :type id: str :param input: the input of the field - :return The updated Marking definition object - """ - - def update_field(self, **kwargs): + :type input: list + :return: The updated Marking Definition object + :rtype: dict or None + """ id = kwargs.get("id", None) input = kwargs.get("input", None) if id is not None and input is not None: @@ -245,18 +295,20 @@ def update_field(self, **kwargs): ) else: self.opencti.app_logger.error( - "[opencti_marking_definition] Missing parameters: id and key and value" + "[opencti_marking_definition] Missing parameters: id and input" ) return None - """ - Import an Marking Definition object from a STIX2 object - - :param stixObject: the MarkingDefinition - :return MarkingDefinition object - """ - def import_from_stix2(self, **kwargs): + """Import a Marking Definition object from a STIX2 object. + + :param stixObject: the Stix-Object Marking Definition + :type stixObject: dict + :param update: set the update flag on import + :type update: bool + :return: Marking Definition object + :rtype: dict or None + """ stix_object = kwargs.get("stixObject", None) update = kwargs.get("update", False) if stix_object is not None: @@ -326,7 +378,7 @@ def import_from_stix2(self, **kwargs): self.opencti.get_attribute_in_extension("stix_ids", stix_object) ) - return self.opencti.marking_definition.create( + return self.create( stix_id=stix_object["id"], created=stix_object["created"] if "created" in stix_object else None, modified=stix_object["modified"] if "modified" in stix_object else None, @@ -353,8 +405,15 @@ def import_from_stix2(self, **kwargs): self.opencti.app_logger.error( "[opencti_marking_definition] Missing parameters: stixObject" ) + return None def delete(self, **kwargs): + """Delete a Marking-Definition object. + + :param id: the id of the Marking-Definition to delete + :type id: str + :return: None + """ id = kwargs.get("id", None) if id is not None: self.opencti.app_logger.info("Deleting Marking-Definition", {"id": id}) diff --git a/client-python/pycti/entities/opencti_narrative.py b/client-python/pycti/entities/opencti_narrative.py index 72df537a029d..256aeba6dc64 100644 --- a/client-python/pycti/entities/opencti_narrative.py +++ b/client-python/pycti/entities/opencti_narrative.py @@ -12,9 +12,15 @@ class Narrative: Manages narratives and disinformation campaigns in the OpenCTI platform. :param opencti: instance of :py:class:`~pycti.api.opencti_api_client.OpenCTIApiClient` + :type opencti: OpenCTIApiClient """ def __init__(self, opencti): + """Initialize the Narrative instance. + + :param opencti: OpenCTI API client instance + :type opencti: OpenCTIApiClient + """ self.opencti = opencti self.properties = """ id @@ -62,6 +68,11 @@ def __init__(self, opencti): x_opencti_lastname } } + objectOrganization { + id + standard_id + name + } objectMarking { id standard_id @@ -149,6 +160,11 @@ def __init__(self, opencti): x_opencti_lastname } } + objectOrganization { + id + standard_id + name + } objectMarking { id standard_id @@ -219,6 +235,13 @@ def __init__(self, opencti): @staticmethod def generate_id(name): + """Generate a STIX ID for a Narrative. + + :param name: the name of the Narrative + :type name: str + :return: STIX ID for the Narrative + :rtype: str + """ name = name.lower().strip() data = {"name": name} data = canonicalize(data, utf8=False) @@ -227,19 +250,29 @@ def generate_id(name): @staticmethod def generate_id_from_data(data): + """Generate a STIX ID from Narrative data. + + :param data: Dictionary containing a 'name' key + :type data: dict + :return: STIX ID for the Narrative + :rtype: str + """ return Narrative.generate_id(data["name"]) - """ - List Narrative objects + def list(self, **kwargs): + """List Narrative objects. :param filters: the filters to apply + :type filters: dict :param search: the search keyword + :type search: str :param first: return the first n rows from the after ID (or the beginning if not set) + :type first: int :param after: ID of the first row for pagination - :return List of Narrative objects - """ - - def list(self, **kwargs): + :type after: str + :return: List of Narrative objects + :rtype: list + """ filters = kwargs.get("filters", None) search = kwargs.get("search", None) first = kwargs.get("first", 100) @@ -317,15 +350,20 @@ def list(self, **kwargs): result["data"]["narratives"], with_pagination ) - """ - Read a Narrative object + def read(self, **kwargs): + """Read a Narrative object. :param id: the id of the Narrative + :type id: str :param filters: the filters to apply if no id provided - :return Narrative object - """ - - def read(self, **kwargs): + :type filters: dict + :param customAttributes: custom attributes to return + :type customAttributes: str + :param withFiles: whether to include files + :type withFiles: bool + :return: Narrative object + :rtype: dict or None + """ id = kwargs.get("id", None) filters = kwargs.get("filters", None) custom_attributes = kwargs.get("customAttributes", None) @@ -361,14 +399,54 @@ def read(self, **kwargs): ) return None - """ - Create a Narrative object - - :param name: the name of the Narrative - :return Narrative object - """ - def create(self, **kwargs): + """Create a Narrative object. + + :param stix_id: (optional) the STIX ID + :type stix_id: str + :param createdBy: (optional) the author ID + :type createdBy: str + :param objectMarking: (optional) list of marking definition IDs + :type objectMarking: list + :param objectLabel: (optional) list of label IDs + :type objectLabel: list + :param externalReferences: (optional) list of external reference IDs + :type externalReferences: list + :param revoked: (optional) whether the narrative is revoked + :type revoked: bool + :param confidence: (optional) confidence level (0-100) + :type confidence: int + :param lang: (optional) language + :type lang: str + :param created: (optional) creation date + :type created: str + :param modified: (optional) modification date + :type modified: str + :param name: the name of the Narrative (required) + :type name: str + :param description: (optional) description + :type description: str + :param aliases: (optional) list of aliases + :type aliases: list + :param narrative_types: (optional) list of narrative types + :type narrative_types: list + :param x_opencti_stix_ids: (optional) list of additional STIX IDs + :type x_opencti_stix_ids: list + :param objectOrganization: (optional) list of organization IDs + :type objectOrganization: list + :param x_opencti_workflow_id: (optional) workflow ID + :type x_opencti_workflow_id: str + :param x_opencti_modified_at: (optional) custom modification date + :type x_opencti_modified_at: str + :param update: (optional) whether to update if exists (default: False) + :type update: bool + :param file: (optional) File object to attach + :type file: dict + :param fileMarkings: (optional) list of marking definition IDs for the file + :type fileMarkings: list + :return: Narrative object + :rtype: dict or None + """ stix_id = kwargs.get("stix_id", None) created_by = kwargs.get("createdBy", None) object_marking = kwargs.get("objectMarking", None) @@ -388,6 +466,8 @@ def create(self, **kwargs): x_opencti_workflow_id = kwargs.get("x_opencti_workflow_id", None) x_opencti_modified_at = kwargs.get("x_opencti_modified_at", None) update = kwargs.get("update", False) + file = kwargs.get("file", None) + file_markings = kwargs.get("fileMarkings", None) if name is not None: self.opencti.app_logger.info("Creating Narrative", {"name": name}) @@ -401,46 +481,49 @@ def create(self, **kwargs): } } """ - result = self.opencti.query( - query, - { - "input": { - "stix_id": stix_id, - "createdBy": created_by, - "objectMarking": object_marking, - "objectLabel": object_label, - "objectOrganization": granted_refs, - "externalReferences": external_references, - "revoked": revoked, - "confidence": confidence, - "lang": lang, - "created": created, - "modified": modified, - "name": name, - "description": description, - "aliases": aliases, - "narrative_types": narrative_types, - "x_opencti_stix_ids": x_opencti_stix_ids, - "x_opencti_workflow_id": x_opencti_workflow_id, - "x_opencti_modified_at": x_opencti_modified_at, - "update": update, - } - }, - ) + input_variables = { + "stix_id": stix_id, + "createdBy": created_by, + "objectMarking": object_marking, + "objectLabel": object_label, + "objectOrganization": granted_refs, + "externalReferences": external_references, + "revoked": revoked, + "confidence": confidence, + "lang": lang, + "created": created, + "modified": modified, + "name": name, + "description": description, + "aliases": aliases, + "narrative_types": narrative_types, + "x_opencti_stix_ids": x_opencti_stix_ids, + "x_opencti_workflow_id": x_opencti_workflow_id, + "x_opencti_modified_at": x_opencti_modified_at, + "update": update, + "file": file, + "fileMarkings": file_markings, + } + result = self.opencti.query(query, {"input": input_variables}) return self.opencti.process_multiple_fields(result["data"]["narrativeAdd"]) else: self.opencti.app_logger.error( - "[opencti_narrative] Missing parameters: name and description" + "[opencti_narrative] Missing parameters: name" ) - - """ - Import an Narrative object from a STIX2 object - - :param stixObject: the Stix-Object Narrative - :return Narrative object - """ + return None def import_from_stix2(self, **kwargs): + """Import a Narrative object from a STIX2 object. + + :param stixObject: the STIX2 Narrative object + :type stixObject: dict + :param extras: extra parameters including created_by_id, object_marking_ids, etc. + :type extras: dict + :param update: whether to update if the entity already exists + :type update: bool + :return: Narrative object + :rtype: dict or None + """ stix_object = kwargs.get("stixObject", None) extras = kwargs.get("extras", {}) update = kwargs.get("update", False) @@ -463,7 +546,7 @@ def import_from_stix2(self, **kwargs): self.opencti.get_attribute_in_extension("modified_at", stix_object) ) - return self.opencti.narrative.create( + return self.create( stix_id=stix_object["id"], createdBy=( extras["created_by_id"] if "created_by_id" in extras else None @@ -521,8 +604,11 @@ def import_from_stix2(self, **kwargs): else None ), update=update, + file=extras.get("file"), + fileMarkings=extras.get("fileMarkings"), ) else: self.opencti.app_logger.error( "[opencti_narrative] Missing parameters: stixObject" ) + return None diff --git a/client-python/pycti/entities/opencti_note.py b/client-python/pycti/entities/opencti_note.py index 1bc21774da71..3eee5372e361 100644 --- a/client-python/pycti/entities/opencti_note.py +++ b/client-python/pycti/entities/opencti_note.py @@ -13,9 +13,15 @@ class Note: Manages notes and annotations in the OpenCTI platform. :param opencti: instance of :py:class:`~pycti.api.opencti_api_client.OpenCTIApiClient` + :type opencti: OpenCTIApiClient """ def __init__(self, opencti): + """Initialize the Note instance. + + :param opencti: OpenCTI API client instance + :type opencti: OpenCTIApiClient + """ self.opencti = opencti self.properties = """ id @@ -208,7 +214,7 @@ def __init__(self, opencti): } ... on StixCyberObservable { observable_value - } + } ... on StixCoreRelationship { standard_id spec_version @@ -221,7 +227,7 @@ def __init__(self, opencti): spec_version created_at updated_at - } + } } } } @@ -272,6 +278,11 @@ def __init__(self, opencti): x_opencti_lastname } } + objectOrganization { + id + standard_id + name + } objectMarking { id standard_id @@ -425,7 +436,7 @@ def __init__(self, opencti): } ... on StixCyberObservable { observable_value - } + } ... on StixCoreRelationship { standard_id spec_version @@ -438,7 +449,7 @@ def __init__(self, opencti): spec_version created_at updated_at - } + } } } } @@ -459,6 +470,16 @@ def __init__(self, opencti): @staticmethod def generate_id(created, content): + """Generate a STIX ID for a Note. + + :param created: The creation date of the note + :type created: datetime or str or None + :param content: The content of the note (required) + :type content: str + :return: STIX ID for the note + :rtype: str + :raises ValueError: If content is None + """ if content is None: raise ValueError("content is required") if created is not None: @@ -473,19 +494,41 @@ def generate_id(created, content): @staticmethod def generate_id_from_data(data): + """Generate a STIX ID from note data. + + :param data: Dictionary containing 'content' and optionally 'created' keys + :type data: dict + :return: STIX ID for the note + :rtype: str + """ return Note.generate_id(data.get("created"), data["content"]) - """ - List Note objects + def list(self, **kwargs): + """List Note objects. :param filters: the filters to apply + :type filters: dict :param search: the search keyword + :type search: str :param first: return the first n rows from the after ID (or the beginning if not set) + :type first: int :param after: ID of the first row for pagination - :return List of Note objects - """ - - def list(self, **kwargs): + :type after: str + :param orderBy: field to order results by + :type orderBy: str + :param orderMode: ordering mode (asc/desc) + :type orderMode: str + :param customAttributes: custom attributes to return + :type customAttributes: list + :param getAll: whether to retrieve all results + :type getAll: bool + :param withPagination: whether to include pagination info + :type withPagination: bool + :param withFiles: whether to include files + :type withFiles: bool + :return: List of Note objects + :rtype: list + """ filters = kwargs.get("filters", None) search = kwargs.get("search", None) first = kwargs.get("first", 100) @@ -563,15 +606,20 @@ def list(self, **kwargs): result["data"]["notes"], with_pagination ) - """ - Read a Note object + def read(self, **kwargs): + """Read a Note object. :param id: the id of the Note + :type id: str :param filters: the filters to apply if no id provided - :return Note object - """ - - def read(self, **kwargs): + :type filters: dict + :param customAttributes: custom attributes to return + :type customAttributes: list + :param withFiles: whether to include files + :type withFiles: bool + :return: Note object + :rtype: dict or None + """ id = kwargs.get("id", None) filters = kwargs.get("filters", None) custom_attributes = kwargs.get("customAttributes", None) @@ -601,14 +649,22 @@ def read(self, **kwargs): return result[0] else: return None - - """ - Check if a note already contains a STIX entity - - :return Boolean - """ + else: + self.opencti.app_logger.error( + "[opencti_note] Missing parameters: id or filters" + ) + return None def contains_stix_object_or_stix_relationship(self, **kwargs): + """Check if a note already contains a STIX entity. + + :param id: the id of the Note + :type id: str + :param stixObjectOrStixRelationshipId: the id of the Stix-Entity + :type stixObjectOrStixRelationshipId: str + :return: Boolean + :rtype: bool + """ id = kwargs.get("id", None) stix_object_or_stix_relationship_id = kwargs.get( "stixObjectOrStixRelationshipId", None @@ -638,15 +694,60 @@ def contains_stix_object_or_stix_relationship(self, **kwargs): self.opencti.app_logger.error( "[opencti_note] Missing parameters: id or entity_id" ) - - """ - Create a Note object - - :param name: the name of the Note - :return Note object - """ + return None def create(self, **kwargs): + """Create a Note object. + + :param stix_id: (optional) the STIX ID + :type stix_id: str + :param createdBy: (optional) the author ID + :type createdBy: str + :param objects: (optional) list of STIX object IDs + :type objects: list + :param objectMarking: (optional) list of marking definition IDs + :type objectMarking: list + :param objectLabel: (optional) list of label IDs + :type objectLabel: list + :param externalReferences: (optional) list of external reference IDs + :type externalReferences: list + :param revoked: (optional) whether the note is revoked + :type revoked: bool + :param confidence: (optional) confidence level (0-100) + :type confidence: int + :param lang: (optional) language + :type lang: str + :param created: (optional) creation date + :type created: datetime + :param modified: (optional) modification date + :type modified: datetime + :param abstract: (optional) abstract summary + :type abstract: str + :param content: the content of the Note (required) + :type content: str + :param authors: (optional) list of authors + :type authors: list + :param note_types: (optional) list of note types + :type note_types: list + :param likelihood: (optional) likelihood value + :type likelihood: int + :param x_opencti_stix_ids: (optional) list of additional STIX IDs + :type x_opencti_stix_ids: list + :param objectOrganization: (optional) list of organization IDs + :type objectOrganization: list + :param x_opencti_workflow_id: (optional) workflow ID + :type x_opencti_workflow_id: str + :param x_opencti_modified_at: (optional) custom modification date + :type x_opencti_modified_at: datetime + :param update: (optional) whether to update if exists (default: False) + :type update: bool + :param file: (optional) File object to attach + :type file: dict + :param fileMarkings: (optional) list of marking definition IDs for the file + :type fileMarkings: list + :return: Note object + :rtype: dict or None + """ stix_id = kwargs.get("stix_id", None) created_by = kwargs.get("createdBy", None) objects = kwargs.get("objects", None) @@ -668,6 +769,8 @@ def create(self, **kwargs): x_opencti_workflow_id = kwargs.get("x_opencti_workflow_id", None) x_opencti_modified_at = kwargs.get("x_opencti_modified_at", None) update = kwargs.get("update", False) + file = kwargs.get("file", None) + file_markings = kwargs.get("fileMarkings", None) if content is not None: self.opencti.app_logger.info("Creating Note", {"content": content}) @@ -681,47 +784,47 @@ def create(self, **kwargs): } } """ - result = self.opencti.query( - query, - { - "input": { - "stix_id": stix_id, - "createdBy": created_by, - "objectMarking": object_marking, - "objectLabel": object_label, - "objectOrganization": granted_refs, - "objects": objects, - "externalReferences": external_references, - "revoked": revoked, - "confidence": confidence, - "lang": lang, - "created": created, - "modified": modified, - "attribute_abstract": abstract, - "content": content, - "authors": authors, - "note_types": note_types, - "likelihood": likelihood, - "x_opencti_stix_ids": x_opencti_stix_ids, - "x_opencti_workflow_id": x_opencti_workflow_id, - "x_opencti_modified_at": x_opencti_modified_at, - "update": update, - } - }, - ) + input_variables = { + "stix_id": stix_id, + "createdBy": created_by, + "objectMarking": object_marking, + "objectLabel": object_label, + "objectOrganization": granted_refs, + "objects": objects, + "externalReferences": external_references, + "revoked": revoked, + "confidence": confidence, + "lang": lang, + "created": created, + "modified": modified, + "attribute_abstract": abstract, + "content": content, + "authors": authors, + "note_types": note_types, + "likelihood": likelihood, + "x_opencti_stix_ids": x_opencti_stix_ids, + "x_opencti_workflow_id": x_opencti_workflow_id, + "x_opencti_modified_at": x_opencti_modified_at, + "update": update, + "file": file, + "fileMarkings": file_markings, + } + result = self.opencti.query(query, {"input": input_variables}) return self.opencti.process_multiple_fields(result["data"]["noteAdd"]) else: self.opencti.app_logger.error("[opencti_note] Missing parameters: content") + return None - """ - Add a Stix-Entity object to Note object (object_refs) + def add_stix_object_or_stix_relationship(self, **kwargs): + """Add a Stix-Entity object to Note object (object_refs). :param id: the id of the Note - :param entity_id: the id of the Stix-Entity - :return Boolean - """ - - def add_stix_object_or_stix_relationship(self, **kwargs): + :type id: str + :param stixObjectOrStixRelationshipId: the id of the Stix-Entity + :type stixObjectOrStixRelationshipId: str + :return: Boolean + :rtype: bool + """ id = kwargs.get("id", None) stix_object_or_stix_relationship_id = kwargs.get( "stixObjectOrStixRelationshipId", None @@ -765,15 +868,16 @@ def add_stix_object_or_stix_relationship(self, **kwargs): ) return False - """ - Remove a Stix-Entity object to Note object (object_refs) + def remove_stix_object_or_stix_relationship(self, **kwargs): + """Remove a Stix-Entity object from Note object (object_refs). :param id: the id of the Note - :param entity_id: the id of the Stix-Entity - :return Boolean - """ - - def remove_stix_object_or_stix_relationship(self, **kwargs): + :type id: str + :param stixObjectOrStixRelationshipId: the id of the Stix-Entity + :type stixObjectOrStixRelationshipId: str + :return: Boolean + :rtype: bool + """ id = kwargs.get("id", None) stix_object_or_stix_relationship_id = kwargs.get( "stixObjectOrStixRelationshipId", None @@ -787,7 +891,7 @@ def remove_stix_object_or_stix_relationship(self, **kwargs): }, ) query = """ - mutation NotetEditRelationDelete($id: ID!, $toId: StixRef!, $relationship_type: String!) { + mutation NoteEditRelationDelete($id: ID!, $toId: StixRef!, $relationship_type: String!) { noteEdit(id: $id) { relationDelete(toId: $toId, relationship_type: $relationship_type) { id @@ -810,14 +914,18 @@ def remove_stix_object_or_stix_relationship(self, **kwargs): ) return False - """ - Import a Note object from a STIX2 object + def import_from_stix2(self, **kwargs): + """Import a Note object from a STIX2 object. :param stixObject: the Stix-Object Note - :return Note object - """ - - def import_from_stix2(self, **kwargs): + :type stixObject: dict + :param extras: extra dict + :type extras: dict + :param update: set the update flag on import + :type update: bool + :return: Note object + :rtype: dict or None + """ stix_object = kwargs.get("stixObject", None) extras = kwargs.get("extras", {}) update = kwargs.get("update", False) @@ -904,8 +1012,11 @@ def import_from_stix2(self, **kwargs): else None ), update=update, + file=extras.get("file"), + fileMarkings=extras.get("fileMarkings"), ) else: self.opencti.app_logger.error( "[opencti_note] Missing parameters: stixObject" ) + return None diff --git a/client-python/pycti/entities/opencti_observed_data.py b/client-python/pycti/entities/opencti_observed_data.py index bfc6739a3a56..1c95840efca2 100644 --- a/client-python/pycti/entities/opencti_observed_data.py +++ b/client-python/pycti/entities/opencti_observed_data.py @@ -12,9 +12,15 @@ class ObservedData: Manages observed data and raw intelligence in the OpenCTI platform. :param opencti: instance of :py:class:`~pycti.api.opencti_api_client.OpenCTIApiClient` + :type opencti: OpenCTIApiClient """ def __init__(self, opencti): + """Initialize the ObservedData instance. + + :param opencti: OpenCTI API client instance + :type opencti: OpenCTIApiClient + """ self.opencti = opencti self.properties = """ id @@ -457,6 +463,13 @@ def __init__(self, opencti): @staticmethod def generate_id(object_ids): + """Generate a STIX ID for an Observed Data object. + + :param object_ids: list of object IDs contained in the observed data + :type object_ids: list + :return: STIX ID for the Observed Data + :rtype: str + """ data = {"objects": object_ids} data = canonicalize(data, utf8=False) id = str(uuid.uuid5(uuid.UUID("00abedb4-aa42-466c-9c01-fed23315a9b7"), data)) @@ -464,19 +477,29 @@ def generate_id(object_ids): @staticmethod def generate_id_from_data(data): + """Generate a STIX ID from Observed Data data. + + :param data: Dictionary containing an 'object_refs' key + :type data: dict + :return: STIX ID for the Observed Data + :rtype: str + """ return ObservedData.generate_id(data["object_refs"]) - """ - List ObservedData objects + def list(self, **kwargs): + """List ObservedData objects. :param filters: the filters to apply + :type filters: dict :param search: the search keyword + :type search: str :param first: return the first n rows from the after ID (or the beginning if not set) + :type first: int :param after: ID of the first row for pagination - :return List of ObservedData objects - """ - - def list(self, **kwargs): + :type after: str + :return: List of ObservedData objects + :rtype: list + """ filters = kwargs.get("filters", None) search = kwargs.get("search", None) first = kwargs.get("first", 500) @@ -531,15 +554,16 @@ def list(self, **kwargs): result["data"]["observedDatas"], with_pagination ) - """ - Read a ObservedData object + def read(self, **kwargs): + """Read an ObservedData object. :param id: the id of the ObservedData + :type id: str :param filters: the filters to apply if no id provided - :return ObservedData object - """ - - def read(self, **kwargs): + :type filters: dict + :return: ObservedData object + :rtype: dict or None + """ id = kwargs.get("id", None) filters = kwargs.get("filters", None) custom_attributes = kwargs.get("customAttributes", None) @@ -569,14 +593,22 @@ def read(self, **kwargs): return result[0] else: return None - - """ - Check if a observedData already contains a STIX entity - - :return Boolean - """ + else: + self.opencti.app_logger.error( + "[opencti_observed_data] Missing parameters: id or filters" + ) + return None def contains_stix_object_or_stix_relationship(self, **kwargs): + """Check if an observedData already contains a STIX entity. + + :param id: the id of the ObservedData + :type id: str + :param stixObjectOrStixRelationshipId: the id of the STIX entity + :type stixObjectOrStixRelationshipId: str + :return: True if contained, False otherwise + :rtype: bool or None + """ id = kwargs.get("id", None) stix_object_or_stix_relationship_id = kwargs.get( "stixObjectOrStixRelationshipId", None @@ -604,17 +636,58 @@ def contains_stix_object_or_stix_relationship(self, **kwargs): return result["data"]["observedDataContainsStixObjectOrStixRelationship"] else: self.opencti.app_logger.error( - "[opencti_observedData] Missing parameters: id or entity_id" + "[opencti_observed_data] Missing parameters: id or entity_id" ) - - """ - Create a ObservedData object - - :param name: the name of the ObservedData - :return ObservedData object - """ + return None def create(self, **kwargs): + """Create an ObservedData object. + + :param stix_id: the STIX ID (optional) + :type stix_id: str + :param createdBy: the author ID (optional) + :type createdBy: str + :param objects: list of STIX object IDs (required) + :type objects: list + :param objectMarking: list of marking definition IDs (optional) + :type objectMarking: list + :param objectLabel: list of label IDs (optional) + :type objectLabel: list + :param externalReferences: list of external reference IDs (optional) + :type externalReferences: list + :param revoked: whether the observed data is revoked (optional) + :type revoked: bool + :param confidence: confidence level 0-100 (optional) + :type confidence: int + :param lang: language (optional) + :type lang: str + :param created: creation date (optional) + :type created: str + :param modified: modification date (optional) + :type modified: str + :param first_observed: the first observed datetime (required) + :type first_observed: str + :param last_observed: the last observed datetime (required) + :type last_observed: str + :param number_observed: number of times observed (optional) + :type number_observed: int + :param x_opencti_stix_ids: list of additional STIX IDs (optional) + :type x_opencti_stix_ids: list + :param objectOrganization: list of organization IDs (optional) + :type objectOrganization: list + :param x_opencti_workflow_id: workflow ID (optional) + :type x_opencti_workflow_id: str + :param x_opencti_modified_at: custom modification date (optional) + :type x_opencti_modified_at: str + :param update: whether to update if exists (default: False) + :type update: bool + :param file: File object to attach (optional) + :type file: File + :param fileMarkings: list of marking definition IDs for the file (optional) + :type fileMarkings: list + :return: ObservedData object + :rtype: dict or None + """ stix_id = kwargs.get("stix_id", None) created_by = kwargs.get("createdBy", None) objects = kwargs.get("objects", None) @@ -634,6 +707,8 @@ def create(self, **kwargs): x_opencti_workflow_id = kwargs.get("x_opencti_workflow_id", None) x_opencti_modified_at = kwargs.get("x_opencti_modified_at", None) update = kwargs.get("update", False) + file = kwargs.get("file", None) + file_markings = kwargs.get("fileMarkings", None) if ( first_observed is not None @@ -641,6 +716,7 @@ def create(self, **kwargs): and objects is not None ): self.opencti.app_logger.info("Creating ObservedData") + query = """ mutation ObservedDataAdd($input: ObservedDataAddInput!) { observedDataAdd(input: $input) { @@ -651,50 +727,50 @@ def create(self, **kwargs): } } """ - result = self.opencti.query( - query, - { - "input": { - "stix_id": stix_id, - "createdBy": created_by, - "objectMarking": object_marking, - "objectLabel": object_label, - "objectOrganization": granted_refs, - "objects": objects, - "externalReferences": external_references, - "revoked": revoked, - "confidence": confidence, - "lang": lang, - "created": created, - "modified": modified, - "first_observed": first_observed, - "last_observed": last_observed, - "number_observed": number_observed, - "x_opencti_stix_ids": x_opencti_stix_ids, - "x_opencti_workflow_id": x_opencti_workflow_id, - "x_opencti_modified_at": x_opencti_modified_at, - "update": update, - } - }, - ) + input_variables = { + "stix_id": stix_id, + "createdBy": created_by, + "objectMarking": object_marking, + "objectLabel": object_label, + "objectOrganization": granted_refs, + "objects": objects, + "externalReferences": external_references, + "revoked": revoked, + "confidence": confidence, + "lang": lang, + "created": created, + "modified": modified, + "first_observed": first_observed, + "last_observed": last_observed, + "number_observed": number_observed, + "x_opencti_stix_ids": x_opencti_stix_ids, + "x_opencti_workflow_id": x_opencti_workflow_id, + "x_opencti_modified_at": x_opencti_modified_at, + "update": update, + "file": file, + "fileMarkings": file_markings, + } + result = self.opencti.query(query, {"input": input_variables}) return self.opencti.process_multiple_fields( result["data"]["observedDataAdd"] ) else: self.opencti.app_logger.error( - "[opencti_observedData] Missing parameters: " + "[opencti_observed_data] Missing parameters: " "first_observed, last_observed or objects" ) + return None - """ - Add a Stix-Core-Object or stix_relationship to ObservedData object (object) + def add_stix_object_or_stix_relationship(self, **kwargs): + """Add a Stix-Core-Object or stix_relationship to ObservedData object (object). :param id: the id of the ObservedData - :param entity_id: the id of the Stix-Core-Object or stix_relationship - :return Boolean - """ - - def add_stix_object_or_stix_relationship(self, **kwargs): + :type id: str + :param stixObjectOrStixRelationshipId: the id of the Stix-Core-Object or stix_relationship + :type stixObjectOrStixRelationshipId: str + :return: True if successful, False otherwise + :rtype: bool + """ id = kwargs.get("id", None) stix_object_or_stix_relationship_id = kwargs.get( "stixObjectOrStixRelationshipId", None @@ -734,27 +810,28 @@ def add_stix_object_or_stix_relationship(self, **kwargs): return True else: self.opencti.app_logger.error( - "[opencti_observedData] Missing parameters: " + "[opencti_observed_data] Missing parameters: " "id and stix_object_or_stix_relationship_id" ) return False - """ - Remove a Stix-Core-Object or stix_relationship to Observed-Data object (object_refs) + def remove_stix_object_or_stix_relationship(self, **kwargs): + """Remove a Stix-Core-Object or stix_relationship from Observed-Data object. :param id: the id of the Observed-Data - :param entity_id: the id of the Stix-Core-Object or stix_relationship - :return Boolean - """ - - def remove_stix_object_or_stix_relationship(self, **kwargs): + :type id: str + :param stixObjectOrStixRelationshipId: the id of the Stix-Core-Object or stix_relationship + :type stixObjectOrStixRelationshipId: str + :return: True if successful, False otherwise + :rtype: bool + """ id = kwargs.get("id", None) stix_object_or_stix_relationship_id = kwargs.get( "stixObjectOrStixRelationshipId", None ) if id is not None and stix_object_or_stix_relationship_id is not None: self.opencti.app_logger.info( - "Removing StixObjectOrStixRelationship to Observed-Data", + "Removing StixObjectOrStixRelationship from Observed-Data", { "id": id, "stixObjectOrStixRelationshipId": stix_object_or_stix_relationship_id, @@ -784,14 +861,18 @@ def remove_stix_object_or_stix_relationship(self, **kwargs): ) return False - """ - Import a ObservedData object from a STIX2 object + def import_from_stix2(self, **kwargs): + """Import an ObservedData object from a STIX2 object. :param stixObject: the Stix-Object ObservedData - :return ObservedData object - """ - - def import_from_stix2(self, **kwargs): + :type stixObject: dict + :param extras: additional parameters like created_by_id, object_marking_ids + :type extras: dict + :param update: whether to update existing object + :type update: bool + :return: ObservedData object + :rtype: dict or None + """ stix_object = kwargs.get("stixObject", None) extras = kwargs.get("extras", {}) update = kwargs.get("update", False) @@ -909,6 +990,8 @@ def import_from_stix2(self, **kwargs): else None ), update=update, + file=extras.get("file"), + fileMarkings=extras.get("fileMarkings"), ) return observed_data_result @@ -916,3 +999,4 @@ def import_from_stix2(self, **kwargs): self.opencti.app_logger.error( "[opencti_observed_data] Missing parameters: stixObject" ) + return None diff --git a/client-python/pycti/entities/opencti_opinion.py b/client-python/pycti/entities/opencti_opinion.py index ac83bc60622c..de0cdbaee2c5 100644 --- a/client-python/pycti/entities/opencti_opinion.py +++ b/client-python/pycti/entities/opencti_opinion.py @@ -1,4 +1,5 @@ # coding: utf-8 + import datetime import json import uuid @@ -12,9 +13,15 @@ class Opinion: Manages analyst opinions and assessments in the OpenCTI platform. :param opencti: instance of :py:class:`~pycti.api.opencti_api_client.OpenCTIApiClient` + :type opencti: OpenCTIApiClient """ def __init__(self, opencti): + """Initialize the Opinion instance. + + :param opencti: OpenCTI API client instance + :type opencti: OpenCTIApiClient + """ self.opencti = opencti self.properties = """ id @@ -62,6 +69,11 @@ def __init__(self, opencti): x_opencti_lastname } } + objectOrganization { + id + standard_id + name + } objectMarking { id standard_id @@ -195,7 +207,7 @@ def __init__(self, opencti): } ... on StixCyberObservable { observable_value - } + } ... on StixCoreRelationship { standard_id spec_version @@ -208,7 +220,7 @@ def __init__(self, opencti): spec_version created_at updated_at - } + } } } } @@ -229,6 +241,16 @@ def __init__(self, opencti): @staticmethod def generate_id(created, opinion): + """Generate a STIX ID for an Opinion. + + :param created: The creation date of the opinion + :type created: datetime or str or None + :param opinion: The opinion value (required) + :type opinion: str + :return: STIX ID for the opinion + :rtype: str + :raises ValueError: If opinion is None + """ if opinion is None: raise ValueError("opinion is required") if created is not None: @@ -243,19 +265,39 @@ def generate_id(created, opinion): @staticmethod def generate_id_from_data(data): + """Generate a STIX ID from opinion data. + + :param data: Dictionary containing 'opinion' and optionally 'created' keys + :type data: dict + :return: STIX ID for the opinion + :rtype: str + """ return Opinion.generate_id(data.get("created"), data["opinion"]) - """ - List Opinion objects + def list(self, **kwargs): + """List Opinion objects. :param filters: the filters to apply + :type filters: dict :param search: the search keyword + :type search: str :param first: return the first n rows from the after ID (or the beginning if not set) + :type first: int :param after: ID of the first row for pagination - :return List of Opinion objects - """ - - def list(self, **kwargs): + :type after: str + :param orderBy: field to order results by + :type orderBy: str + :param orderMode: ordering mode (asc/desc) + :type orderMode: str + :param customAttributes: custom attributes to return + :type customAttributes: list + :param getAll: whether to retrieve all results + :type getAll: bool + :param withPagination: whether to include pagination info + :type withPagination: bool + :return: List of Opinion objects + :rtype: list + """ filters = kwargs.get("filters", None) search = kwargs.get("search", None) first = kwargs.get("first", 100) @@ -328,15 +370,18 @@ def list(self, **kwargs): result["data"]["opinions"], with_pagination ) - """ - Read a Opinion object + def read(self, **kwargs): + """Read an Opinion object. :param id: the id of the Opinion + :type id: str :param filters: the filters to apply if no id provided - :return Opinion object - """ - - def read(self, **kwargs): + :type filters: dict + :param customAttributes: custom attributes to return + :type customAttributes: list + :return: Opinion object + :rtype: dict or None + """ id = kwargs.get("id", None) filters = kwargs.get("filters", None) custom_attributes = kwargs.get("customAttributes", None) @@ -365,14 +410,22 @@ def read(self, **kwargs): return result[0] else: return None - - """ - Check if a opinion already contains a STIX entity - - :return Boolean - """ + else: + self.opencti.app_logger.error( + "[opencti_opinion] Missing parameters: id or filters" + ) + return None def contains_stix_object_or_stix_relationship(self, **kwargs): + """Check if an opinion already contains a STIX entity. + + :param id: the id of the Opinion + :type id: str + :param stixObjectOrStixRelationshipId: the id of the Stix-Entity + :type stixObjectOrStixRelationshipId: str + :return: Boolean + :rtype: bool + """ id = kwargs.get("id", None) stix_object_or_stix_relationship_id = kwargs.get( "stixObjectOrStixRelationshipId", None @@ -400,17 +453,56 @@ def contains_stix_object_or_stix_relationship(self, **kwargs): return result["data"]["opinionContainsStixObjectOrStixRelationship"] else: self.opencti.app_logger.error( - "[opencti_opinion] Missing parameters: id or entity_id" + "[opencti_opinion] Missing parameters: id or stixObjectOrStixRelationshipId" ) - - """ - Create a Opinion object - - :param name: the name of the Opinion - :return Opinion object - """ + return None def create(self, **kwargs): + """Create an Opinion object. + + :param stix_id: (optional) the STIX ID + :type stix_id: str + :param createdBy: (optional) the author ID + :type createdBy: str + :param objects: (optional) list of STIX object IDs + :type objects: list + :param objectMarking: (optional) list of marking definition IDs + :type objectMarking: list + :param objectLabel: (optional) list of label IDs + :type objectLabel: list + :param externalReferences: (optional) list of external reference IDs + :type externalReferences: list + :param revoked: (optional) whether the opinion is revoked + :type revoked: bool + :param confidence: (optional) confidence level (0-100) + :type confidence: int + :param lang: (optional) language + :type lang: str + :param created: (optional) creation date + :type created: datetime + :param modified: (optional) modification date + :type modified: datetime + :param explanation: (optional) explanation text + :type explanation: str + :param authors: (optional) list of authors + :type authors: list + :param opinion: the opinion value (required) + :type opinion: str + :param x_opencti_stix_ids: (optional) list of additional STIX IDs + :type x_opencti_stix_ids: list + :param objectOrganization: (optional) list of organization IDs + :type objectOrganization: list + :param x_opencti_modified_at: (optional) custom modification date + :type x_opencti_modified_at: datetime + :param update: (optional) whether to update if exists (default: False) + :type update: bool + :param file: (optional) File object to attach + :type file: dict + :param fileMarkings: (optional) list of marking definition IDs for the file + :type fileMarkings: list + :return: Opinion object + :rtype: dict or None + """ stix_id = kwargs.get("stix_id", None) created_by = kwargs.get("createdBy", None) objects = kwargs.get("objects", None) @@ -427,8 +519,11 @@ def create(self, **kwargs): opinion = kwargs.get("opinion", None) x_opencti_stix_ids = kwargs.get("x_opencti_stix_ids", None) granted_refs = kwargs.get("objectOrganization", None) + x_opencti_workflow_id = kwargs.get("x_opencti_workflow_id", None) x_opencti_modified_at = kwargs.get("x_opencti_modified_at", None) update = kwargs.get("update", False) + file = kwargs.get("file", None) + file_markings = kwargs.get("fileMarkings", None) if opinion is not None: self.opencti.app_logger.info("Creating Opinion", {"opinion": opinion}) @@ -442,46 +537,47 @@ def create(self, **kwargs): } } """ - result = self.opencti.query( - query, - { - "input": { - "stix_id": stix_id, - "createdBy": created_by, - "objectMarking": object_marking, - "objectLabel": object_label, - "objectOrganization": granted_refs, - "objects": objects, - "externalReferences": external_references, - "revoked": revoked, - "confidence": confidence, - "lang": lang, - "created": created, - "modified": modified, - "explanation": explanation, - "authors": authors, - "opinion": opinion, - "x_opencti_stix_ids": x_opencti_stix_ids, - "x_opencti_modified_at": x_opencti_modified_at, - "update": update, - } - }, - ) + input_variables = { + "stix_id": stix_id, + "createdBy": created_by, + "objectMarking": object_marking, + "objectLabel": object_label, + "objectOrganization": granted_refs, + "objects": objects, + "externalReferences": external_references, + "revoked": revoked, + "confidence": confidence, + "lang": lang, + "created": created, + "modified": modified, + "explanation": explanation, + "authors": authors, + "opinion": opinion, + "x_opencti_stix_ids": x_opencti_stix_ids, + "x_opencti_workflow_id": x_opencti_workflow_id, + "x_opencti_modified_at": x_opencti_modified_at, + "update": update, + "file": file, + "fileMarkings": file_markings, + } + result = self.opencti.query(query, {"input": input_variables}) return self.opencti.process_multiple_fields(result["data"]["opinionAdd"]) else: self.opencti.app_logger.error( - "[opencti_opinion] Missing parameters: content" + "[opencti_opinion] Missing parameters: opinion" ) + return None - """ - Add a Stix-Entity object to Opinion object (object_refs) + def add_stix_object_or_stix_relationship(self, **kwargs): + """Add a Stix-Entity object to Opinion object (object_refs). :param id: the id of the Opinion - :param entity_id: the id of the Stix-Entity - :return Boolean - """ - - def add_stix_object_or_stix_relationship(self, **kwargs): + :type id: str + :param stixObjectOrStixRelationshipId: the id of the Stix-Entity + :type stixObjectOrStixRelationshipId: str + :return: Boolean + :rtype: bool + """ id = kwargs.get("id", None) stix_object_or_stix_relationship_id = kwargs.get( "stixObjectOrStixRelationshipId", None @@ -525,22 +621,23 @@ def add_stix_object_or_stix_relationship(self, **kwargs): ) return False - """ - Remove a Stix-Entity object from Opinion object (object_refs) + def remove_stix_object_or_stix_relationship(self, **kwargs): + """Remove a Stix-Entity object from Opinion object (object_refs). :param id: the id of the Opinion - :param entity_id: the id of the Stix-Entity - :return Boolean - """ - - def remove_stix_object_or_stix_relationship(self, **kwargs): + :type id: str + :param stixObjectOrStixRelationshipId: the id of the Stix-Entity + :type stixObjectOrStixRelationshipId: str + :return: Boolean + :rtype: bool + """ id = kwargs.get("id", None) stix_object_or_stix_relationship_id = kwargs.get( "stixObjectOrStixRelationshipId", None ) if id is not None and stix_object_or_stix_relationship_id is not None: self.opencti.app_logger.info( - "Removing StixObjectOrStixRelationship to Opinion", + "Removing StixObjectOrStixRelationship from Opinion", { "id": id, "stixObjectOrStixRelationshipId": stix_object_or_stix_relationship_id, @@ -566,18 +663,22 @@ def remove_stix_object_or_stix_relationship(self, **kwargs): return True else: self.opencti.app_logger.error( - "[opencti_opinion] Missing parameters: id and entity_id" + "[opencti_opinion] Missing parameters: id and stixObjectOrStixRelationshipId" ) return False - """ - Import a Opinion object from a STIX2 object + def import_from_stix2(self, **kwargs): + """Import an Opinion object from a STIX2 object. :param stixObject: the Stix-Object Opinion - :return Opinion object - """ - - def import_from_stix2(self, **kwargs): + :type stixObject: dict + :param extras: extra dict + :type extras: dict + :param update: set the update flag on import + :type update: bool + :return: Opinion object + :rtype: dict or None + """ stix_object = kwargs.get("stixObject", None) extras = kwargs.get("extras", {}) update = kwargs.get("update", False) @@ -595,6 +696,10 @@ def import_from_stix2(self, **kwargs): stix_object["x_opencti_modified_at"] = ( self.opencti.get_attribute_in_extension("modified_at", stix_object) ) + if "x_opencti_workflow_id" not in stix_object: + stix_object["x_opencti_workflow_id"] = ( + self.opencti.get_attribute_in_extension("workflow_id", stix_object) + ) return self.create( stix_id=stix_object["id"], @@ -638,6 +743,11 @@ def import_from_stix2(self, **kwargs): if "x_opencti_modified_at" in stix_object else None ), + x_opencti_workflow_id=( + stix_object["x_opencti_workflow_id"] + if "x_opencti_workflow_id" in stix_object + else None + ), opinion=stix_object["opinion"] if "opinion" in stix_object else None, objectOrganization=( stix_object["x_opencti_granted_refs"] @@ -645,8 +755,11 @@ def import_from_stix2(self, **kwargs): else None ), update=update, + file=extras.get("file"), + fileMarkings=extras.get("fileMarkings"), ) else: self.opencti.app_logger.error( "[opencti_opinion] Missing parameters: stixObject" ) + return None diff --git a/client-python/pycti/entities/opencti_report.py b/client-python/pycti/entities/opencti_report.py index c243d324820b..446a398054d4 100644 --- a/client-python/pycti/entities/opencti_report.py +++ b/client-python/pycti/entities/opencti_report.py @@ -14,9 +14,15 @@ class Report: Manages threat intelligence reports in the OpenCTI platform. :param opencti: instance of :py:class:`~pycti.api.opencti_api_client.OpenCTIApiClient` + :type opencti: OpenCTIApiClient """ def __init__(self, opencti): + """Initialize the Report instance. + + :param opencti: OpenCTI API client instance + :type opencti: OpenCTIApiClient + """ self.opencti = opencti self.properties = """ id @@ -210,7 +216,7 @@ def __init__(self, opencti): } ... on StixCyberObservable { observable_value - } + } ... on StixCoreRelationship { standard_id spec_version @@ -432,7 +438,7 @@ def __init__(self, opencti): } ... on StixCyberObservable { observable_value - } + } ... on StixCoreRelationship { standard_id spec_version @@ -466,6 +472,15 @@ def __init__(self, opencti): @staticmethod def generate_id(name, published): + """Generate a STIX ID for a Report. + + :param name: The name of the report + :type name: str + :param published: The published date of the report + :type published: str or datetime.datetime + :return: STIX ID for the report + :rtype: str + """ name = name.lower().strip() if isinstance(published, datetime.datetime): published = published.isoformat() @@ -476,6 +491,15 @@ def generate_id(name, published): @staticmethod def generate_fixed_fake_id(name, published=None): + """Generate a fixed fake STIX ID for a Report (used for testing). + + :param name: The name of the report + :type name: str + :param published: (optional) The published date of the report + :type published: str or datetime.datetime or None + :return: STIX ID for the report + :rtype: str + """ name = name.lower().strip() if isinstance(published, datetime.datetime): published = published.isoformat() @@ -489,19 +513,41 @@ def generate_fixed_fake_id(name, published=None): @staticmethod def generate_id_from_data(data): + """Generate a STIX ID from report data. + + :param data: Dictionary containing 'name' and 'published' keys + :type data: dict + :return: STIX ID for the report + :rtype: str + """ return Report.generate_id(data["name"], data["published"]) - """ - List Report objects + def list(self, **kwargs): + """List Report objects. :param filters: the filters to apply + :type filters: dict :param search: the search keyword + :type search: str :param first: return the first n rows from the after ID (or the beginning if not set) + :type first: int :param after: ID of the first row for pagination - :return List of Report objects - """ - - def list(self, **kwargs): + :type after: str + :param orderBy: field to order results by + :type orderBy: str + :param orderMode: ordering mode (asc/desc) + :type orderMode: str + :param customAttributes: custom attributes to return + :type customAttributes: str + :param getAll: whether to retrieve all results + :type getAll: bool + :param withPagination: whether to include pagination info + :type withPagination: bool + :param withFiles: whether to include files + :type withFiles: bool + :return: List of Report objects + :rtype: list + """ filters = kwargs.get("filters", None) search = kwargs.get("search", None) first = kwargs.get("first", 100) @@ -515,7 +561,7 @@ def list(self, **kwargs): self.opencti.app_logger.info( "Listing Reports with filters", - {"filters": json.dumps(filters), "with_files:": with_files}, + {"filters": json.dumps(filters), "with_files": with_files}, ) query = ( """ @@ -580,15 +626,20 @@ def list(self, **kwargs): result["data"]["reports"], with_pagination ) - """ - Read a Report object + def read(self, **kwargs): + """Read a Report object. :param id: the id of the Report + :type id: str :param filters: the filters to apply if no id provided - :return Report object - """ - - def read(self, **kwargs): + :type filters: dict + :param customAttributes: custom attributes to return + :type customAttributes: str + :param withFiles: whether to include files + :type withFiles: bool + :return: Report object + :rtype: dict or None + """ id = kwargs.get("id", None) filters = kwargs.get("filters", None) custom_attributes = kwargs.get("customAttributes", None) @@ -620,17 +671,26 @@ def read(self, **kwargs): return result[0] else: return None - - """ - Read a Report object by stix_id or name - - :param type: the Stix-Domain-Entity type - :param stix_id: the STIX ID of the Stix-Domain-Entity - :param name: the name of the Stix-Domain-Entity - :return Stix-Domain-Entity object - """ + else: + self.opencti.app_logger.error( + "[opencti_report] Missing parameters: id or filters" + ) + return None def get_by_stix_id_or_name(self, **kwargs): + """Read a Report object by stix_id or name. + + :param stix_id: the STIX ID of the Report + :type stix_id: str + :param name: the name of the Report + :type name: str + :param published: the published date of the Report + :type published: str + :param customAttributes: custom attributes to return + :type customAttributes: str + :return: Report object + :rtype: dict or None + """ stix_id = kwargs.get("stix_id", None) name = kwargs.get("name", None) published = kwargs.get("published", None) @@ -653,15 +713,16 @@ def get_by_stix_id_or_name(self, **kwargs): ) return object_result - """ - Check if a report already contains a thing (Stix Object or Stix Relationship) + def contains_stix_object_or_stix_relationship(self, **kwargs): + """Check if a report already contains a STIX object or relationship. :param id: the id of the Report - :param stixObjectOrStixRelationshipId: the id of the Stix-Entity - :return Boolean - """ - - def contains_stix_object_or_stix_relationship(self, **kwargs): + :type id: str + :param stixObjectOrStixRelationshipId: the id of the STIX object or relationship + :type stixObjectOrStixRelationshipId: str + :return: True if the report contains the entity, False otherwise + :rtype: bool + """ id = kwargs.get("id", None) stix_object_or_stix_relationship_id = kwargs.get( "stixObjectOrStixRelationshipId", None @@ -691,15 +752,40 @@ def contains_stix_object_or_stix_relationship(self, **kwargs): self.opencti.app_logger.error( "[opencti_report] Missing parameters: id or stixObjectOrStixRelationshipId", ) - - """ - Create a Report object - - :param name: the name of the Report - :return Report object - """ + return None def create(self, **kwargs): + """Create a Report object. + + :param stix_id: (optional) the STIX ID of the Report + :param createdBy: (optional) the author ID + :param objects: (optional) list of STIX object IDs contained in the report + :param objectMarking: (optional) list of marking definition IDs + :param objectAssignee: (optional) list of assignee IDs + :param objectParticipant: (optional) list of participant IDs + :param objectLabel: (optional) list of label IDs + :param externalReferences: (optional) list of external reference IDs + :param revoked: (optional) whether the report is revoked + :param confidence: (optional) confidence level (0-100) + :param lang: (optional) language of the report + :param created: (optional) creation date + :param modified: (optional) modification date + :param name: the name of the Report (required) + :param description: (optional) description of the report + :param content: (optional) content of the report + :param report_types: (optional) list of report types + :param published: the publication date (required) + :param x_opencti_reliability: (optional) reliability level + :param x_opencti_stix_ids: (optional) list of additional STIX IDs + :param objectOrganization: (optional) list of organization IDs + :param x_opencti_workflow_id: (optional) workflow ID + :param x_opencti_modified_at: (optional) custom modification date + :param update: (optional) whether to update if exists (default: False) + :param file: (optional) File object to attach + :param fileMarkings: (optional) list of marking definition IDs for the file + :return: Report object + :rtype: dict or None + """ stix_id = kwargs.get("stix_id", None) created_by = kwargs.get("createdBy", None) objects = kwargs.get("objects", None) @@ -724,6 +810,8 @@ def create(self, **kwargs): x_opencti_workflow_id = kwargs.get("x_opencti_workflow_id", None) x_opencti_modified_at = kwargs.get("x_opencti_modified_at", None) update = kwargs.get("update", False) + file = kwargs.get("file", None) + file_markings = kwargs.get("fileMarkings", None) if name is not None and published is not None: self.opencti.app_logger.info("Creating Report", {"name": name}) @@ -737,53 +825,52 @@ def create(self, **kwargs): } } """ - result = self.opencti.query( - query, - { - "input": { - "stix_id": stix_id, - "createdBy": created_by, - "objectMarking": object_marking, - "objectLabel": object_label, - "objectOrganization": granted_refs, - "objectAssignee": object_assignee, - "objectParticipant": object_participant, - "objects": objects, - "externalReferences": external_references, - "revoked": revoked, - "confidence": confidence, - "lang": lang, - "created": created, - "modified": modified, - "name": name, - "description": description, - "content": content, - "report_types": report_types, - "published": published, - "x_opencti_reliability": x_opencti_reliability, - "x_opencti_stix_ids": x_opencti_stix_ids, - "x_opencti_workflow_id": x_opencti_workflow_id, - "x_opencti_modified_at": x_opencti_modified_at, - "update": update, - } - }, - ) + input_variables = { + "stix_id": stix_id, + "createdBy": created_by, + "objectMarking": object_marking, + "objectLabel": object_label, + "objectOrganization": granted_refs, + "objectAssignee": object_assignee, + "objectParticipant": object_participant, + "objects": objects, + "externalReferences": external_references, + "revoked": revoked, + "confidence": confidence, + "lang": lang, + "created": created, + "modified": modified, + "name": name, + "description": description, + "content": content, + "report_types": report_types, + "published": published, + "x_opencti_reliability": x_opencti_reliability, + "x_opencti_stix_ids": x_opencti_stix_ids, + "x_opencti_workflow_id": x_opencti_workflow_id, + "x_opencti_modified_at": x_opencti_modified_at, + "update": update, + "file": file, + "fileMarkings": file_markings, + } + result = self.opencti.query(query, {"input": input_variables}) return self.opencti.process_multiple_fields(result["data"]["reportAdd"]) else: self.opencti.app_logger.error( - "[opencti_report] " - "Missing parameters: name and description and published and report_class" + "[opencti_report] Missing parameters: name and published" ) + return None - """ - Add a Stix-Entity object to Report object (object_refs) + def add_stix_object_or_stix_relationship(self, **kwargs): + """Add a STIX object or relationship to Report object (object_refs). :param id: the id of the Report - :param stixObjectOrStixRelationshipId: the id of the Stix-Entity - :return Boolean - """ - - def add_stix_object_or_stix_relationship(self, **kwargs): + :type id: str + :param stixObjectOrStixRelationshipId: the id of the STIX object or relationship + :type stixObjectOrStixRelationshipId: str + :return: True if successful, False otherwise + :rtype: bool + """ id = kwargs.get("id", None) stix_object_or_stix_relationship_id = kwargs.get( "stixObjectOrStixRelationshipId", None @@ -822,22 +909,23 @@ def add_stix_object_or_stix_relationship(self, **kwargs): ) return False - """ - Remove a Stix-Entity object to Report object (object_refs) + def remove_stix_object_or_stix_relationship(self, **kwargs): + """Remove a STIX object or relationship from Report object (object_refs). :param id: the id of the Report - :param stixObjectOrStixRelationshipId: the id of the Stix-Entity - :return Boolean - """ - - def remove_stix_object_or_stix_relationship(self, **kwargs): + :type id: str + :param stixObjectOrStixRelationshipId: the id of the STIX object or relationship + :type stixObjectOrStixRelationshipId: str + :return: True if successful, False otherwise + :rtype: bool + """ id = kwargs.get("id", None) stix_object_or_stix_relationship_id = kwargs.get( "stixObjectOrStixRelationshipId", None ) if id is not None and stix_object_or_stix_relationship_id is not None: self.opencti.app_logger.info( - "Removing StixObjectOrStixRelationship to Report", + "Removing StixObjectOrStixRelationship from Report", { "id": id, "stixObjectOrStixRelationshipId": stix_object_or_stix_relationship_id, @@ -867,14 +955,18 @@ def remove_stix_object_or_stix_relationship(self, **kwargs): ) return False - """ - Import a Report object from a STIX2 object - - :param stixObject: the Stix-Object Report - :return Report object - """ - def import_from_stix2(self, **kwargs): + """Import a Report object from a STIX2 object. + + :param stixObject: the STIX2 Report object + :type stixObject: dict + :param extras: extra parameters including created_by_id, object_marking_ids, object_ids, etc. + :type extras: dict + :param update: whether to update if the entity already exists + :type update: bool + :return: Report object + :rtype: dict or None + """ stix_object = kwargs.get("stixObject", None) extras = kwargs.get("extras", {}) update = kwargs.get("update", False) @@ -997,8 +1089,11 @@ def import_from_stix2(self, **kwargs): else None ), update=update, + file=extras.get("file"), + fileMarkings=extras.get("fileMarkings"), ) else: self.opencti.app_logger.error( "[opencti_report] Missing parameters: stixObject" ) + return None diff --git a/client-python/pycti/entities/opencti_role.py b/client-python/pycti/entities/opencti_role.py index 6e0878e596dd..9981d13122d0 100644 --- a/client-python/pycti/entities/opencti_role.py +++ b/client-python/pycti/entities/opencti_role.py @@ -10,9 +10,17 @@ class Role: Check the properties attribute of the class to understand what default properties are fetched. + + :param opencti: instance of :py:class:`~pycti.api.opencti_api_client.OpenCTIApiClient` + :type opencti: OpenCTIApiClient """ def __init__(self, opencti): + """Initialize the Role instance. + + :param opencti: OpenCTI API client instance + :type opencti: OpenCTIApiClient + """ self.opencti = opencti self.properties = """ id @@ -317,7 +325,7 @@ def add_capability(self, **kwargs) -> Optional[Dict]: capability_id = kwargs.get("capability_id", None) if id is None or capability_id is None: - self.opencti.admin_logger( + self.opencti.admin_logger.error( "[opencti_role] Missing parameters: id and capability_id" ) return None @@ -405,6 +413,13 @@ def delete_capability(self, **kwargs) -> Optional[Dict]: ) def process_multiple_fields(self, data): + """Process and normalize fields in role data. + + :param data: the role data dictionary to process + :type data: dict + :return: the processed role data with normalized fields + :rtype: dict + """ if "capabilities" in data: data["capabilities"] = self.opencti.process_multiple(data["capabilities"]) data["capabilitiesIds"] = self.opencti.process_multiple_ids( diff --git a/client-python/pycti/entities/opencti_security_coverage.py b/client-python/pycti/entities/opencti_security_coverage.py index a03638a6e2d9..c3d7a2542321 100644 --- a/client-python/pycti/entities/opencti_security_coverage.py +++ b/client-python/pycti/entities/opencti_security_coverage.py @@ -7,7 +7,20 @@ class SecurityCoverage: + """Main SecurityCoverage class for OpenCTI + + Manages security coverage entities in the OpenCTI platform. + + :param opencti: instance of :py:class:`~pycti.api.opencti_api_client.OpenCTIApiClient` + :type opencti: OpenCTIApiClient + """ + def __init__(self, opencti): + """Initialize the SecurityCoverage instance. + + :param opencti: OpenCTI API client instance + :type opencti: OpenCTIApiClient + """ self.opencti = opencti self.properties = """ id @@ -39,6 +52,13 @@ def __init__(self, opencti): @staticmethod def generate_id(covered_ref): + """Generate a STIX ID for a Security Coverage. + + :param covered_ref: The reference to the covered object + :type covered_ref: str + :return: STIX ID for the security coverage + :rtype: str + """ data = {"covered_ref": covered_ref.lower().strip()} data = canonicalize(data, utf8=False) id = str(uuid.uuid5(uuid.UUID("00abedb4-aa42-466c-9c01-fed23315a9b7"), data)) @@ -46,19 +66,25 @@ def generate_id(covered_ref): @staticmethod def generate_id_from_data(data): + """Generate a STIX ID from security coverage data. + + :param data: Dictionary containing 'covered_ref' key + :type data: dict + :return: STIX ID for the security coverage + :rtype: str + """ return SecurityCoverage.generate_id(data["covered_ref"]) - """ - List securityCoverage objects + def list(self, **kwargs): + """List SecurityCoverage objects. :param filters: the filters to apply :param search: the search keyword :param first: return the first n rows from the after ID (or the beginning if not set) :param after: ID of the first row for pagination - :return List of SecurityCoverage objects - """ - - def list(self, **kwargs): + :return: List of SecurityCoverage objects + :rtype: list + """ filters = kwargs.get("filters", None) search = kwargs.get("search", None) first = kwargs.get("first", 100) @@ -136,15 +162,18 @@ def list(self, **kwargs): result["data"]["securityCoverages"], with_pagination ) - """ - Read a SecurityCoverage object + def read(self, **kwargs): + """Read a SecurityCoverage object. :param id: the id of the SecurityCoverage + :type id: str :param filters: the filters to apply if no id provided - :return SecurityCoverage object - """ - - def read(self, **kwargs): + :type filters: dict + :param customAttributes: custom attributes to return + :type customAttributes: str + :return: SecurityCoverage object + :rtype: dict or None + """ id = kwargs.get("id", None) filters = kwargs.get("filters", None) custom_attributes = kwargs.get("customAttributes", None) @@ -181,13 +210,52 @@ def read(self, **kwargs): ) return None - """ - Create a Security coverage object - - :return Security Coverage object - """ - def create(self, **kwargs): + """Create a Security coverage object. + + :param name: the name of the Security Coverage (required) + :type name: str + :param objectCovered: the covered object ID (required) + :type objectCovered: str + :param stix_id: (optional) the STIX ID + :type stix_id: str + :param description: (optional) description + :type description: str + :param createdBy: (optional) the author ID + :type createdBy: str + :param objectMarking: (optional) list of marking definition IDs + :type objectMarking: list + :param objectLabel: (optional) list of label IDs + :type objectLabel: list + :param externalReferences: (optional) list of external reference IDs + :type externalReferences: list + :param external_uri: (optional) external URI + :type external_uri: str + :param coverage_last_result: (optional) last result date + :type coverage_last_result: str + :param coverage_valid_from: (optional) valid from date + :type coverage_valid_from: str + :param coverage_valid_to: (optional) valid to date + :type coverage_valid_to: str + :param coverage_information: (optional) coverage information + :type coverage_information: list + :param auto_enrichment_disable: (optional) disable auto enrichment + :type auto_enrichment_disable: bool + :param periodicity: (optional) periodicity + :type periodicity: str + :param duration: (optional) duration + :type duration: str + :param type_affinity: (optional) type affinity + :type type_affinity: str + :param platforms_affinity: (optional) platforms affinity + :type platforms_affinity: list + :param file: (optional) File object to attach + :type file: dict + :param fileMarkings: (optional) list of marking definition IDs for the file + :type fileMarkings: list + :return: Security Coverage object + :rtype: dict or None + """ stix_id = kwargs.get("stix_id", None) name = kwargs.get("name", None) description = kwargs.get("description", None) @@ -206,6 +274,9 @@ def create(self, **kwargs): duration = kwargs.get("duration", None) type_affinity = kwargs.get("type_affinity", None) platforms_affinity = kwargs.get("platforms_affinity", None) + x_opencti_stix_ids = kwargs.get("x_opencti_stix_ids", None) + file = kwargs.get("file", None) + file_markings = kwargs.get("fileMarkings", None) if name is not None and object_covered is not None: self.opencti.app_logger.info("Creating Security Coverage", {"name": name}) @@ -241,6 +312,9 @@ def create(self, **kwargs): "duration": duration, "type_affinity": type_affinity, "platforms_affinity": platforms_affinity, + "x_opencti_stix_ids": x_opencti_stix_ids, + "file": file, + "fileMarkings": file_markings, } }, ) @@ -252,15 +326,20 @@ def create(self, **kwargs): "[opencti_security_coverage] " "Missing parameters: name or object_covered" ) - - """ - Import a Security coverage from a STIX2 object - - :param stixObject: the Stix-Object Security coverage - :return Security coverage object - """ + return None def import_from_stix2(self, **kwargs): + """Import a Security coverage from a STIX2 object. + + :param stixObject: the STIX2 Security coverage object + :type stixObject: dict + :param extras: extra parameters including created_by_id, object_marking_ids, etc. + :type extras: dict + :param update: whether to update if the entity already exists + :type update: bool + :return: Security coverage object + :rtype: dict or None + """ stix_object = kwargs.get("stixObject", None) extras = kwargs.get("extras", {}) if stix_object is not None: @@ -353,8 +432,11 @@ def import_from_stix2(self, **kwargs): if "x_opencti_stix_ids" in stix_object else None ), + file=extras.get("file"), + fileMarkings=extras.get("fileMarkings"), ) else: self.opencti.app_logger.error( "[opencti_security_coverage] Missing parameters: stixObject" ) + return None diff --git a/client-python/pycti/entities/opencti_settings.py b/client-python/pycti/entities/opencti_settings.py index 0882d92fd54d..06e98f3fea5c 100644 --- a/client-python/pycti/entities/opencti_settings.py +++ b/client-python/pycti/entities/opencti_settings.py @@ -10,9 +10,17 @@ class Settings: See the properties attribute to understand which properties are fetched by default on graphql queries. + + :param opencti: instance of :py:class:`~pycti.api.opencti_api_client.OpenCTIApiClient` + :type opencti: OpenCTIApiClient """ def __init__(self, opencti): + """Initialize the Settings instance. + + :param opencti: OpenCTI API client instance + :type opencti: OpenCTIApiClient + """ self.opencti = opencti self.properties = """ id @@ -322,8 +330,10 @@ def delete_message(self, **kwargs) -> Optional[Dict]: """ id = kwargs.get("id", None) input = kwargs.get("input", None) - if id is None: - self.opencti.admin_logger.info("[opencti_settings] Missing parameters: id") + if id is None or input is None: + self.opencti.admin_logger.error( + "[opencti_settings] Missing parameters: id and input" + ) return None query = ( @@ -346,6 +356,13 @@ def delete_message(self, **kwargs) -> Optional[Dict]: ) def process_multiple_fields(self, data): + """Process and normalize fields in settings data. + + :param data: the settings data dictionary to process + :type data: dict + :return: the processed settings data with normalized fields + :rtype: dict + """ if "platform_messages" in data: data["platform_messages"] = self.opencti.process_multiple( data["platform_messages"] diff --git a/client-python/pycti/entities/opencti_stix.py b/client-python/pycti/entities/opencti_stix.py index 10ca0f7a71cc..43c035555e33 100644 --- a/client-python/pycti/entities/opencti_stix.py +++ b/client-python/pycti/entities/opencti_stix.py @@ -4,19 +4,26 @@ class Stix: Provides generic STIX object operations in the OpenCTI platform. :param opencti: instance of :py:class:`~pycti.api.opencti_api_client.OpenCTIApiClient` + :type opencti: OpenCTIApiClient """ def __init__(self, opencti): + """Initialize the Stix instance. + + :param opencti: OpenCTI API client instance + :type opencti: OpenCTIApiClient + """ self.opencti = opencti - """ - Delete a Stix element + def delete(self, **kwargs): + """Delete a Stix element. :param id: the Stix element id - :return void - """ - - def delete(self, **kwargs): + :type id: str + :param force_delete: force deletion (default: True) + :type force_delete: bool + :return: None + """ id = kwargs.get("id", None) force_delete = kwargs.get("force_delete", True) if id is not None: @@ -33,16 +40,16 @@ def delete(self, **kwargs): self.opencti.app_logger.error("[opencti_stix] Missing parameters: id") return None - """ - Merge a Stix-Object object field - - :param id: the Stix-Object id - :param key: the key of the field - :param value: the value of the field - :return The updated Stix-Object object - """ - def merge(self, **kwargs): + """Merge STIX objects into one. + + :param id: the target Stix-Object id + :type id: str + :param object_ids: list of source STIX object IDs to merge into target + :type object_ids: list + :return: The merged Stix-Object object + :rtype: dict or None + """ id = kwargs.get("id") stix_objects_ids = kwargs.get("object_ids") if id is not None and stix_objects_ids is not None: diff --git a/client-python/pycti/entities/opencti_stix_core_object.py b/client-python/pycti/entities/opencti_stix_core_object.py index 6ace4d21ed57..c14d4b0dd0f7 100644 --- a/client-python/pycti/entities/opencti_stix_core_object.py +++ b/client-python/pycti/entities/opencti_stix_core_object.py @@ -8,12 +8,16 @@ class StixCoreObject: Base class for managing STIX core objects in the OpenCTI platform. :param opencti: instance of :py:class:`~pycti.api.opencti_api_client.OpenCTIApiClient` - :param file: file handling configuration + :type opencti: OpenCTIApiClient """ - def __init__(self, opencti, file): + def __init__(self, opencti): + """Initialize the StixCoreObject instance. + + :param opencti: OpenCTI API client instance + :type opencti: OpenCTIApiClient + """ self.opencti = opencti - self.file = file self.properties = """ id standard_id @@ -435,18 +439,6 @@ def __init__(self, opencti, file): aliases narrative_types } - ... on DataComponent { - name - description - } - ... on DataSource { - name - description - } - ... on DataSource { - name - description - } ... on Case { name description @@ -1162,18 +1154,6 @@ def __init__(self, opencti, file): aliases narrative_types } - ... on DataComponent { - name - description - } - ... on DataSource { - name - description - } - ... on DataSource { - name - description - } ... on Case { name description @@ -1418,6 +1398,12 @@ def __init__(self, opencti, file): ... on PhoneNumber { value } + ... on TrackingNumber { + value + } + ... on Credential { + value + } ... on PaymentCard { card_number expiration_date @@ -1437,18 +1423,34 @@ def __init__(self, opencti, file): } """ - """ - List Stix-Core-Object objects + def list(self, **kwargs): + """List Stix-Core-Object objects. :param types: the list of types + :type types: list :param filters: the filters to apply + :type filters: dict :param search: the search keyword + :type search: str :param first: return the first n rows from the after ID (or the beginning if not set) + :type first: int :param after: ID of the first row for pagination - :return List of Stix-Core-Object objects - """ - - def list(self, **kwargs): + :type after: str + :param orderBy: field to order results by + :type orderBy: str + :param orderMode: ordering mode (asc/desc) + :type orderMode: str + :param customAttributes: custom attributes to return + :type customAttributes: str + :param getAll: whether to retrieve all results + :type getAll: bool + :param withPagination: whether to include pagination info + :type withPagination: bool + :param withFiles: whether to include files + :type withFiles: bool + :return: List of Stix-Core-Object objects + :rtype: list + """ types = kwargs.get("types", None) filters = kwargs.get("filters", None) search = kwargs.get("search", None) @@ -1509,7 +1511,7 @@ def list(self, **kwargs): final_data = final_data + data while result["data"]["stixCoreObjects"]["pageInfo"]["hasNextPage"]: after = result["data"]["stixCoreObjects"]["pageInfo"]["endCursor"] - self.opencti.app_logger.info( + self.opencti.app_logger.debug( "Listing Stix-Core-Objects", {"after": after} ) result = self.opencti.query( @@ -1532,16 +1534,22 @@ def list(self, **kwargs): result["data"]["stixCoreObjects"], with_pagination ) - """ - Read a Stix-Core-Object object + def read(self, **kwargs): + """Read a Stix-Core-Object object. - :param id: the id of the Stix-Core-Object - :param types: list of Stix Core Entity types - :param filters: the filters to apply if no id provided - :return Stix-Core-Object object + :param id: the id of the Stix-Core-Object + :type id: str + :param types: list of Stix Core Entity types + :type types: list + :param filters: the filters to apply if no id provided + :type filters: dict + :param customAttributes: custom attributes to return + :type customAttributes: str + :param withFiles: whether to include files + :type withFiles: bool + :return: Stix-Core-Object object + :rtype: dict or None """ - - def read(self, **kwargs): id = kwargs.get("id", None) types = kwargs.get("types", None) filters = kwargs.get("filters", None) @@ -1583,6 +1591,13 @@ def read(self, **kwargs): return None def list_files(self, **kwargs): + """List files of a Stix-Core-Object. + + :param id: the id of the Stix-Core-Object + :type id: str + :return: List of files associated with the object + :rtype: list + """ id = kwargs.get("id", None) self.opencti.app_logger.debug("Listing files of Stix-Core-Object", {"id": id}) query = """ @@ -1618,6 +1633,23 @@ def push_list_export( list_filters="", mime_type=None, ): + """Push a list export file for Stix-Core-Objects. + + :param entity_id: the id of the entity (optional) + :type entity_id: str or None + :param entity_type: the type of the entity + :type entity_type: str + :param file_name: the name of the file to export + :type file_name: str + :param file_markings: list of marking definition ids to apply + :type file_markings: list + :param data: the data content to export + :type data: str + :param list_filters: filters to apply on the list (default: "") + :type list_filters: str + :param mime_type: the MIME type of the file (optional) + :type mime_type: str or None + """ query = """ mutation StixCoreObjectsExportPush($entity_id: String, $entity_type: String!, $file: Upload!, $file_markings: [String]!, $listFilters: String) { stixCoreObjectsExportPush(entity_id: $entity_id, entity_type: $entity_type, file: $file, file_markings: $file_markings, listFilters: $listFilters) @@ -1625,9 +1657,9 @@ def push_list_export( """ if mime_type is None: - file = self.file(file_name, data) + file = self.opencti.file(file_name, data) else: - file = self.file(file_name, data, mime_type) + file = self.opencti.file(file_name, data, mime_type) self.opencti.query( query, { @@ -1648,6 +1680,21 @@ def push_analysis( content_type, analysis_type, ): + """Push an analysis file for a Stix-Core-Object. + + :param entity_id: the id of the Stix-Core-Object + :type entity_id: str + :param file_name: the name of the analysis file + :type file_name: str + :param data: the analysis data content + :type data: str + :param content_source: the source of the content + :type content_source: str + :param content_type: the type of analysis content + :type content_type: str + :param analysis_type: the type of analysis + :type analysis_type: str + """ query = """ mutation StixCoreObjectEdit( $id: ID!, $file: Upload!, $contentSource: String!, $contentType: AnalysisContentType!, $analysisType: String! @@ -1661,7 +1708,7 @@ def push_analysis( } """ - file = self.file(file_name, data) + file = self.opencti.file(file_name, data) self.opencti.query( query, { @@ -1673,14 +1720,14 @@ def push_analysis( }, ) - """ - Get the reports about a Stix-Core-Object object + def reports(self, **kwargs): + """Get the reports about a Stix-Core-Object object. :param id: the id of the Stix-Core-Object - :return List of reports - """ - - def reports(self, **kwargs): + :type id: str + :return: List of reports + :rtype: list or None + """ id = kwargs.get("id", None) if id is not None: self.opencti.app_logger.info( @@ -1800,15 +1847,14 @@ def reports(self, **kwargs): self.opencti.app_logger.error("Missing parameters: id") return None - """ - Apply rule to Stix-Core-Object object + def rule_apply(self, **kwargs): + """Apply rule to Stix-Core-Object object. :param element_id: the Stix-Core-Object id + :type element_id: str :param rule_id: the rule to apply - :return void - """ - - def rule_apply(self, **kwargs): + :type rule_id: str + """ rule_id = kwargs.get("rule_id", None) element_id = kwargs.get("element_id", None) if element_id is not None and rule_id is not None: @@ -1823,19 +1869,18 @@ def rule_apply(self, **kwargs): self.opencti.query(query, {"elementId": element_id, "ruleId": rule_id}) else: self.opencti.app_logger.error( - "[stix_core_object] Cant apply rule, missing parameters: id" + "[stix_core_object] Cannot apply rule, missing parameters: id" ) return None - """ - Apply rule clear to Stix-Core-Object object + def rule_clear(self, **kwargs): + """Apply rule clear to Stix-Core-Object object. :param element_id: the Stix-Core-Object id - :param rule_id: the rule to apply - :return void - """ - - def rule_clear(self, **kwargs): + :type element_id: str + :param rule_id: the rule to clear + :type rule_id: str + """ rule_id = kwargs.get("rule_id", None) element_id = kwargs.get("element_id", None) if element_id is not None and rule_id is not None: @@ -1850,18 +1895,16 @@ def rule_clear(self, **kwargs): self.opencti.query(query, {"elementId": element_id, "ruleId": rule_id}) else: self.opencti.app_logger.error( - "[stix_core_object] Cant clear rule, missing parameters: id" + "[stix_core_object] Cannot clear rule, missing parameters: id" ) return None - """ - Apply rules rescan to Stix-Core-Object object + def rules_rescan(self, **kwargs): + """Apply rules rescan to Stix-Core-Object object. :param element_id: the Stix-Core-Object id - :return void - """ - - def rules_rescan(self, **kwargs): + :type element_id: str + """ element_id = kwargs.get("element_id", None) if element_id is not None: self.opencti.app_logger.info( @@ -1875,18 +1918,16 @@ def rules_rescan(self, **kwargs): self.opencti.query(query, {"elementId": element_id}) else: self.opencti.app_logger.error( - "[stix_core_object] Cant rescan rule, missing parameters: id" + "[stix_core_object] Cannot rescan rule, missing parameters: id" ) return None - """ - Ask clear restriction + def clear_access_restriction(self, **kwargs): + """Ask clear restriction on a Stix-Core-Object. :param element_id: the Stix-Core-Object id - :return void - """ - - def clear_access_restriction(self, **kwargs): + :type element_id: str + """ element_id = kwargs.get("element_id", None) if element_id is not None: query = """ @@ -1906,19 +1947,18 @@ def clear_access_restriction(self, **kwargs): ) else: self.opencti.app_logger.error( - "[stix_core_object] Cant clear access restriction, missing parameters: id" + "[stix_core_object] Cannot clear access restriction, missing parameters: id" ) return None - """ - Ask enrichment with single connector + def ask_enrichment(self, **kwargs): + """Ask enrichment with a single connector. :param element_id: the Stix-Core-Object id - :param connector_id the connector - :return void - """ - - def ask_enrichment(self, **kwargs): + :type element_id: str + :param connector_id: the connector id + :type connector_id: str + """ element_id = kwargs.get("element_id", None) connector_id = kwargs.get("connector_id", None) query = """ @@ -1938,15 +1978,14 @@ def ask_enrichment(self, **kwargs): }, ) - """ - Ask enrichment with multiple connectors + def ask_enrichments(self, **kwargs): + """Ask enrichment with multiple connectors. :param element_id: the Stix-Core-Object id - :param connector_ids the connectors - :return void - """ - - def ask_enrichments(self, **kwargs): + :type element_id: str + :param connector_ids: list of connector ids + :type connector_ids: list + """ element_id = kwargs.get("element_id", None) connector_ids = kwargs.get("connector_ids", None) query = """ @@ -1966,15 +2005,16 @@ def ask_enrichments(self, **kwargs): }, ) - """ - Share element to multiple organizations + def organization_share(self, entity_id, organization_ids, sharing_direct_container): + """Share element to multiple organizations. :param entity_id: the Stix-Core-Object id - :param organization_id:s the organization to share with - :return void - """ - - def organization_share(self, entity_id, organization_ids, sharing_direct_container): + :type entity_id: str + :param organization_ids: list of organization ids to share with + :type organization_ids: list + :param sharing_direct_container: whether to share direct containers + :type sharing_direct_container: bool + """ query = """ mutation StixCoreObjectEdit($id: ID!, $organizationId: [ID!]!, $directContainerSharing: Boolean) { stixCoreObjectEdit(id: $id) { @@ -1993,17 +2033,18 @@ def organization_share(self, entity_id, organization_ids, sharing_direct_contain }, ) - """ - Unshare element from multiple organizations - - :param entity_id: the Stix-Core-Object id - :param organization_id:s the organization to share with - :return void - """ - def organization_unshare( self, entity_id, organization_ids, sharing_direct_container ): + """Unshare element from multiple organizations. + + :param entity_id: the Stix-Core-Object id + :type entity_id: str + :param organization_ids: list of organization ids to unshare from + :type organization_ids: list + :param sharing_direct_container: whether to unshare direct containers + :type sharing_direct_container: bool + """ query = """ mutation StixCoreObjectEdit($id: ID!, $organizationId: [ID!]!, $directContainerSharing: Boolean) { stixCoreObjectEdit(id: $id) { @@ -2022,14 +2063,12 @@ def organization_unshare( }, ) - """ - Delete a Stix-Core-Object object + def delete(self, **kwargs): + """Delete a Stix-Core-Object object. :param id: the Stix-Core-Object id - :return void - """ - - def delete(self, **kwargs): + :type id: str + """ id = kwargs.get("id", None) if id is not None: self.opencti.app_logger.info("Deleting stix_core_object", {"id": id}) @@ -2042,17 +2081,17 @@ def delete(self, **kwargs): """ self.opencti.query(query, {"id": id}) else: - self.opencti.app_logger.error("[stix_core_object] Missing parameters: id") + self.opencti.app_logger.error( + "[opencti_stix_core_object] Missing parameters: id" + ) return None - """ - Remove a Stix-Core-Object object from draft (revert) + def remove_from_draft(self, **kwargs): + """Remove a Stix-Core-Object object from draft (revert). :param id: the Stix-Core-Object id - :return void - """ - - def remove_from_draft(self, **kwargs): + :type id: str + """ id = kwargs.get("id", None) if id is not None: self.opencti.app_logger.info("Draft remove stix_core_object", {"id": id}) @@ -2066,6 +2105,6 @@ def remove_from_draft(self, **kwargs): self.opencti.query(query, {"id": id}) else: self.opencti.app_logger.error( - "[stix_core_object] Cant remove from draft, missing parameters: id" + "[stix_core_object] Cannot remove from draft, missing parameters: id" ) return None diff --git a/client-python/pycti/entities/opencti_stix_core_relationship.py b/client-python/pycti/entities/opencti_stix_core_relationship.py index 11599c8bfd52..d6e01d23db70 100644 --- a/client-python/pycti/entities/opencti_stix_core_relationship.py +++ b/client-python/pycti/entities/opencti_stix_core_relationship.py @@ -12,9 +12,15 @@ class StixCoreRelationship: Manages STIX relationships between entities in the OpenCTI platform. :param opencti: instance of :py:class:`~pycti.api.opencti_api_client.OpenCTIApiClient` + :type opencti: OpenCTIApiClient """ def __init__(self, opencti): + """Initialize the StixCoreRelationship instance. + + :param opencti: OpenCTI API client instance + :type opencti: OpenCTIApiClient + """ self.opencti = opencti self.properties = """ id @@ -347,6 +353,21 @@ def __init__(self, opencti): def generate_id( relationship_type, source_ref, target_ref, start_time=None, stop_time=None ): + """Generate a STIX ID for a relationship. + + :param relationship_type: The type of relationship + :type relationship_type: str + :param source_ref: The source entity reference ID + :type source_ref: str + :param target_ref: The target entity reference ID + :type target_ref: str + :param start_time: (optional) The start time of the relationship + :type start_time: str or datetime.datetime or None + :param stop_time: (optional) The stop time of the relationship + :type stop_time: str or datetime.datetime or None + :return: STIX ID for the relationship + :rtype: str + """ if isinstance(start_time, datetime.datetime): start_time = start_time.isoformat() if isinstance(stop_time, datetime.datetime): @@ -377,23 +398,15 @@ def generate_id( id = str(uuid.uuid5(uuid.UUID("00abedb4-aa42-466c-9c01-fed23315a9b7"), data)) return "relationship--" + id - """ - List stix_core_relationship objects - - :param fromId: the id of the source entity of the relation - :param toId: the id of the target entity of the relation - :param relationship_type: the relation type - :param startTimeStart: the start_time date start filter - :param startTimeStop: the start_time date stop filter - :param stopTimeStart: the stop_time date start filter - :param stopTimeStop: the stop_time date stop filter - :param first: return the first n rows from the after ID (or the beginning if not set) - :param after: ID of the first row for pagination - :return List of stix_core_relationship objects - """ - @staticmethod def generate_id_from_data(data): + """Generate a STIX ID from relationship data. + + :param data: Dictionary containing relationship_type, source_ref, target_ref, and optionally start_time/stop_time + :type data: dict + :return: STIX ID for the relationship + :rtype: str + """ return StixCoreRelationship.generate_id( data["relationship_type"], data["source_ref"], @@ -403,6 +416,51 @@ def generate_id_from_data(data): ) def list(self, **kwargs): + """List stix_core_relationship objects. + + :param fromOrToId: the id of an entity (source or target) + :type fromOrToId: str + :param elementWithTargetTypes: filter by target types + :type elementWithTargetTypes: list + :param fromId: the id of the source entity of the relation + :type fromId: str + :param fromTypes: filter by source entity types + :type fromTypes: list + :param toId: the id of the target entity of the relation + :type toId: str + :param toTypes: filter by target entity types + :type toTypes: list + :param relationship_type: the relation type + :type relationship_type: str + :param startTimeStart: the start_time date start filter + :type startTimeStart: str + :param startTimeStop: the start_time date stop filter + :type startTimeStop: str + :param stopTimeStart: the stop_time date start filter + :type stopTimeStart: str + :param stopTimeStop: the stop_time date stop filter + :type stopTimeStop: str + :param filters: additional filters to apply + :type filters: dict + :param first: return the first n rows from the after ID (or the beginning if not set) + :type first: int + :param after: ID of the first row for pagination + :type after: str + :param orderBy: field to order results by + :type orderBy: str + :param orderMode: ordering mode (asc/desc) + :type orderMode: str + :param customAttributes: custom attributes to return + :type customAttributes: str + :param getAll: whether to retrieve all results + :type getAll: bool + :param withPagination: whether to include pagination info + :type withPagination: bool + :param search: search keyword + :type search: str + :return: List of stix_core_relationship objects + :rtype: list + """ from_or_to_id = kwargs.get("fromOrToId", None) element_with_target_types = kwargs.get("elementWithTargetTypes", None) from_id = kwargs.get("fromId", None) @@ -489,7 +547,7 @@ def list(self, **kwargs): final_data = final_data + data while result["data"]["stixCoreRelationships"]["pageInfo"]["hasNextPage"]: after = result["data"]["stixCoreRelationships"]["pageInfo"]["endCursor"] - self.opencti.app_logger.info( + self.opencti.app_logger.debug( "Listing StixCoreRelationships", {"after": after} ) result = self.opencti.query( @@ -511,6 +569,7 @@ def list(self, **kwargs): "after": after, "orderBy": order_by, "orderMode": order_mode, + "search": search, }, ) data = self.opencti.process_multiple( @@ -523,22 +582,34 @@ def list(self, **kwargs): result["data"]["stixCoreRelationships"], with_pagination ) - """ - Read a stix_core_relationship object + def read(self, **kwargs): + """Read a stix_core_relationship object. :param id: the id of the stix_core_relationship - :param fromOrToId: the id of the entity of the relation + :type id: str + :param fromOrToId: the id of an entity (source or target) + :type fromOrToId: str :param fromId: the id of the source entity of the relation + :type fromId: str :param toId: the id of the target entity of the relation + :type toId: str :param relationship_type: the relation type + :type relationship_type: str :param startTimeStart: the start_time date start filter + :type startTimeStart: str :param startTimeStop: the start_time date stop filter + :type startTimeStop: str :param stopTimeStart: the stop_time date start filter + :type stopTimeStart: str :param stopTimeStop: the stop_time date stop filter - :return stix_core_relationship object - """ - - def read(self, **kwargs): + :type stopTimeStop: str + :param filters: filters to apply + :type filters: dict + :param customAttributes: custom attributes to return + :type customAttributes: str + :return: stix_core_relationship object + :rtype: dict or None + """ id = kwargs.get("id", None) from_or_to_id = kwargs.get("fromOrToId", None) from_id = kwargs.get("fromId", None) @@ -596,14 +667,58 @@ def read(self, **kwargs): self.opencti.app_logger.error("Missing parameters: id or from_id and to_id") return None - """ - Create a stix_core_relationship object - - :param name: the name of the Attack Pattern - :return stix_core_relationship object - """ - def create(self, **kwargs): + """Create a stix_core_relationship object. + + :param fromId: the id of the source entity + :type fromId: str + :param toId: the id of the target entity + :type toId: str + :param stix_id: (optional) the STIX ID + :type stix_id: str + :param relationship_type: the type of relationship + :type relationship_type: str + :param description: (optional) description + :type description: str + :param start_time: (optional) start time of the relationship + :type start_time: str + :param stop_time: (optional) stop time of the relationship + :type stop_time: str + :param revoked: (optional) whether the relationship is revoked + :type revoked: bool + :param confidence: (optional) confidence level (0-100) + :type confidence: int + :param lang: (optional) language + :type lang: str + :param created: (optional) creation date + :type created: str + :param modified: (optional) modification date + :type modified: str + :param createdBy: (optional) the author ID + :type createdBy: str + :param objectMarking: (optional) list of marking definition IDs + :type objectMarking: list + :param objectLabel: (optional) list of label IDs + :type objectLabel: list + :param externalReferences: (optional) list of external reference IDs + :type externalReferences: list + :param killChainPhases: (optional) list of kill chain phase IDs + :type killChainPhases: list + :param objectOrganization: (optional) list of organization IDs + :type objectOrganization: list + :param x_opencti_workflow_id: (optional) workflow ID + :type x_opencti_workflow_id: str + :param x_opencti_stix_ids: (optional) list of additional STIX IDs + :type x_opencti_stix_ids: list + :param x_opencti_modified_at: (optional) custom modification date + :type x_opencti_modified_at: str + :param coverage_information: (optional) coverage information + :type coverage_information: list + :param update: (optional) whether to update if exists (default: False) + :type update: bool + :return: stix_core_relationship object + :rtype: dict or None + """ from_id = kwargs.get("fromId", None) to_id = kwargs.get("toId", None) stix_id = kwargs.get("stix_id", None) @@ -680,15 +795,16 @@ def create(self, **kwargs): result["data"]["stixCoreRelationshipAdd"] ) - """ - Update a stix_core_relationship object field + def update_field(self, **kwargs): + """Update a stix_core_relationship object field. :param id: the stix_core_relationship id + :type id: str :param input: the input of the field - :return The updated stix_core_relationship object - """ - - def update_field(self, **kwargs): + :type input: list + :return: The updated stix_core_relationship object + :rtype: dict or None + """ id = kwargs.get("id", None) input = kwargs.get("input", None) if id is not None and input is not None: @@ -716,18 +832,17 @@ def update_field(self, **kwargs): ) else: self.opencti.app_logger.error( - "[opencti_stix_core_relationship] Missing parameters: id and key and value", + "[opencti_stix_core_relationship] Missing parameters: id and input", ) return None - """ - Delete a stix_core_relationship + def delete(self, **kwargs): + """Delete a stix_core_relationship. :param id: the stix_core_relationship id - :return void - """ - - def delete(self, **kwargs): + :type id: str + :return: None + """ id = kwargs.get("id", None) if id is not None: self.opencti.app_logger.info("Deleting stix_core_relationship", {"id": id}) @@ -745,15 +860,16 @@ def delete(self, **kwargs): ) return None - """ - Add a Marking-Definition object to stix_core_relationship object (object_marking_refs) + def add_marking_definition(self, **kwargs): + """Add a Marking-Definition object to stix_core_relationship object (object_marking_refs). :param id: the id of the stix_core_relationship + :type id: str :param marking_definition_id: the id of the Marking-Definition - :return Boolean - """ - - def add_marking_definition(self, **kwargs): + :type marking_definition_id: str + :return: True if successful, False otherwise + :rtype: bool + """ id = kwargs.get("id", None) marking_definition_id = kwargs.get("marking_definition_id", None) if id is not None and marking_definition_id is not None: @@ -812,15 +928,16 @@ def add_marking_definition(self, **kwargs): ) return False - """ - Remove a Marking-Definition object to stix_core_relationship + def remove_marking_definition(self, **kwargs): + """Remove a Marking-Definition object from stix_core_relationship. :param id: the id of the stix_core_relationship + :type id: str :param marking_definition_id: the id of the Marking-Definition - :return Boolean - """ - - def remove_marking_definition(self, **kwargs): + :type marking_definition_id: str + :return: True if successful, False otherwise + :rtype: bool + """ id = kwargs.get("id", None) marking_definition_id = kwargs.get("marking_definition_id", None) if id is not None and marking_definition_id is not None: @@ -847,18 +964,23 @@ def remove_marking_definition(self, **kwargs): ) return True else: - self.opencti.app_logger.error("Missing parameters: id and label_id") + self.opencti.app_logger.error( + "Missing parameters: id and marking_definition_id" + ) return False - """ - Add a Label object to stix_core_relationship(labelging) + def add_label(self, **kwargs): + """Add a Label object to stix_core_relationship (labeling). :param id: the id of the stix_core_relationship + :type id: str :param label_id: the id of the Label - :return Boolean - """ - - def add_label(self, **kwargs): + :type label_id: str + :param label_name: (optional) the name of the Label (will create if not exists) + :type label_name: str + :return: True if successful, False otherwise + :rtype: bool + """ id = kwargs.get("id", None) label_id = kwargs.get("label_id", None) label_name = kwargs.get("label_name", None) @@ -904,15 +1026,18 @@ def add_label(self, **kwargs): self.opencti.app_logger.error("Missing parameters: id and label_id") return False - """ - Remove a Label object to stix_core_relationship + def remove_label(self, **kwargs): + """Remove a Label object from stix_core_relationship. :param id: the id of the stix_core_relationship + :type id: str :param label_id: the id of the Label - :return Boolean - """ - - def remove_label(self, **kwargs): + :type label_id: str + :param label_name: (optional) the name of the Label + :type label_name: str + :return: True if successful, False otherwise + :rtype: bool + """ id = kwargs.get("id", None) label_id = kwargs.get("label_id", None) label_name = kwargs.get("label_name", None) @@ -953,15 +1078,16 @@ def remove_label(self, **kwargs): self.opencti.app_logger.error("Missing parameters: id and label_id") return False - """ - Add a External-Reference object to stix_core_relationship (external-reference) + def add_external_reference(self, **kwargs): + """Add an External-Reference object to stix_core_relationship. :param id: the id of the stix_core_relationship - :param marking_definition_id: the id of the Marking-Definition - :return Boolean - """ - - def add_external_reference(self, **kwargs): + :type id: str + :param external_reference_id: the id of the External-Reference + :type external_reference_id: str + :return: True if successful, False otherwise + :rtype: bool + """ id = kwargs.get("id", None) external_reference_id = kwargs.get("external_reference_id", None) if id is not None and external_reference_id is not None: @@ -995,15 +1121,16 @@ def add_external_reference(self, **kwargs): ) return False - """ - Remove a External-Reference object to stix_core_relationship object + def remove_external_reference(self, **kwargs): + """Remove an External-Reference object from stix_core_relationship. :param id: the id of the stix_core_relationship + :type id: str :param external_reference_id: the id of the External-Reference - :return Boolean - """ - - def remove_external_reference(self, **kwargs): + :type external_reference_id: str + :return: True if successful, False otherwise + :rtype: bool + """ id = kwargs.get("id", None) external_reference_id = kwargs.get("external_reference_id", None) if id is not None and external_reference_id is not None: @@ -1030,18 +1157,21 @@ def remove_external_reference(self, **kwargs): ) return True else: - self.opencti.app_logger.error("Missing parameters: id and label_id") + self.opencti.app_logger.error( + "Missing parameters: id and external_reference_id" + ) return False - """ - Add a Kill-Chain-Phase object to stix_core_relationship object (kill_chain_phases) + def add_kill_chain_phase(self, **kwargs): + """Add a Kill-Chain-Phase object to stix_core_relationship object (kill_chain_phases). :param id: the id of the stix_core_relationship + :type id: str :param kill_chain_phase_id: the id of the Kill-Chain-Phase - :return Boolean - """ - - def add_kill_chain_phase(self, **kwargs): + :type kill_chain_phase_id: str + :return: True if successful, False otherwise + :rtype: bool + """ id = kwargs.get("id", None) kill_chain_phase_id = kwargs.get("kill_chain_phase_id", None) if id is not None and kill_chain_phase_id is not None: @@ -1075,15 +1205,16 @@ def add_kill_chain_phase(self, **kwargs): ) return False - """ - Remove a Kill-Chain-Phase object to stix_core_relationship object + def remove_kill_chain_phase(self, **kwargs): + """Remove a Kill-Chain-Phase object from stix_core_relationship. :param id: the id of the stix_core_relationship + :type id: str :param kill_chain_phase_id: the id of the Kill-Chain-Phase - :return Boolean - """ - - def remove_kill_chain_phase(self, **kwargs): + :type kill_chain_phase_id: str + :return: True if successful, False otherwise + :rtype: bool + """ id = kwargs.get("id", None) kill_chain_phase_id = kwargs.get("kill_chain_phase_id", None) if id is not None and kill_chain_phase_id is not None: @@ -1110,20 +1241,21 @@ def remove_kill_chain_phase(self, **kwargs): ) return True else: - self.opencti.app_loggererror( + self.opencti.app_logger.error( "[stix_core_relationship] Missing parameters: id and kill_chain_phase_id" ) return False - """ - Update the Identity author of a stix_core_relationship object (created_by) + def update_created_by(self, **kwargs): + """Update the Identity author of a stix_core_relationship (created_by). :param id: the id of the stix_core_relationship + :type id: str :param identity_id: the id of the Identity - :return Boolean - """ - - def update_created_by(self, **kwargs): + :type identity_id: str + :return: True if successful, False otherwise + :rtype: bool + """ id = kwargs.get("id", None) identity_id = kwargs.get("identity_id", None) if id is not None: @@ -1197,14 +1329,20 @@ def update_created_by(self, **kwargs): self.opencti.app_logger.error("Missing parameters: id") return False - """ - Import an Indicator object from a STIX2 object - - :param stixObject: the Stix-Object Indicator - :return Indicator object - """ - def import_from_stix2(self, **kwargs): + """Import a stix_core_relationship from a STIX2 object. + + :param stixRelation: the STIX2 relationship object + :type stixRelation: dict + :param extras: extra parameters including created_by_id, object_marking_ids, etc. + :type extras: dict + :param update: whether to update if the entity already exists + :type update: bool + :param defaultDate: default date to use for start/stop times + :type defaultDate: str or bool + :return: stix_core_relationship object + :rtype: dict or None + """ stix_relation = kwargs.get("stixRelation", None) extras = kwargs.get("extras", {}) update = kwargs.get("update", False) @@ -1332,16 +1470,19 @@ def import_from_stix2(self, **kwargs): self.opencti.app_logger.error( "[opencti_stix_core_relationship] Missing parameters: stixObject" ) + return None - """ - Share element to multiple organizations + def organization_share(self, entity_id, organization_ids, sharing_direct_container): + """Share element to multiple organizations. :param entity_id: the stix_core_relationship id - :param organization_id:s the organization to share with - :return void - """ - - def organization_share(self, entity_id, organization_ids, sharing_direct_container): + :type entity_id: str + :param organization_ids: the organization IDs to share with + :type organization_ids: list + :param sharing_direct_container: whether to share direct container + :type sharing_direct_container: bool + :return: None + """ query = """ mutation StixCoreRelationshipEdit($id: ID!, $organizationId: [ID!]!, $directContainerSharing: Boolean) { stixCoreRelationshipEdit(id: $id) { @@ -1360,17 +1501,19 @@ def organization_share(self, entity_id, organization_ids, sharing_direct_contain }, ) - """ - Unshare element from multiple organizations - - :param entity_id: the stix_core_relationship id - :param organization_id:s the organization to share with - :return void - """ - def organization_unshare( self, entity_id, organization_ids, sharing_direct_container ): + """Unshare element from multiple organizations. + + :param entity_id: the stix_core_relationship id + :type entity_id: str + :param organization_ids: the organization IDs to unshare from + :type organization_ids: list + :param sharing_direct_container: whether to unshare direct container + :type sharing_direct_container: bool + :return: None + """ query = """ mutation StixCoreRelationshipEdit($id: ID!, $organizationId: [ID!]!, $directContainerSharing: Boolean) { stixCoreRelationshipEdit(id: $id) { @@ -1389,14 +1532,13 @@ def organization_unshare( }, ) - """ - Remove a stix_core_relationship object from draft (revert) + def remove_from_draft(self, **kwargs): + """Remove a stix_core_relationship object from draft (revert). :param id: the stix_core_relationship id - :return void - """ - - def remove_from_draft(self, **kwargs): + :type id: str + :return: None + """ id = kwargs.get("id", None) if id is not None: self.opencti.app_logger.info( @@ -1412,6 +1554,6 @@ def remove_from_draft(self, **kwargs): self.opencti.query(query, {"id": id}) else: self.opencti.app_logger.error( - "[stix_core_relationship] Cant remove from draft, missing parameters: id" + "[stix_core_relationship] Cannot remove from draft, missing parameters: id" ) return None diff --git a/client-python/pycti/entities/opencti_stix_cyber_observable.py b/client-python/pycti/entities/opencti_stix_cyber_observable.py index 0d7a18623dd6..3d5e4ebfbb3e 100644 --- a/client-python/pycti/entities/opencti_stix_cyber_observable.py +++ b/client-python/pycti/entities/opencti_stix_cyber_observable.py @@ -23,28 +23,47 @@ class StixCyberObservable(StixCyberObservableDeprecatedMixin): Note: Deprecated methods are available through StixCyberObservableDeprecatedMixin. :param opencti: instance of :py:class:`~pycti.api.opencti_api_client.OpenCTIApiClient` - :param file: file handling configuration + :type opencti: OpenCTIApiClient """ - def __init__(self, opencti, file): + def __init__(self, opencti): + """Initialize the StixCyberObservable instance. + :param opencti: OpenCTI API client instance + :type opencti: OpenCTIApiClient + """ self.opencti = opencti - self.file = file self.properties = SCO_PROPERTIES self.properties_with_files = SCO_PROPERTIES_WITH_FILES - """ - List StixCyberObservable objects + def list(self, **kwargs): + """List StixCyberObservable objects. :param types: the array of types + :type types: list :param filters: the filters to apply + :type filters: dict :param search: the search keyword + :type search: str :param first: return the first n rows from the after ID (or the beginning if not set) - :param after: ID of the first row - :return List of StixCyberObservable objects - """ - - def list(self, **kwargs): + :type first: int + :param after: ID of the first row for pagination + :type after: str + :param orderBy: field to order results by + :type orderBy: str + :param orderMode: ordering mode (asc/desc) + :type orderMode: str + :param customAttributes: custom attributes to return + :type customAttributes: str + :param getAll: whether to retrieve all results + :type getAll: bool + :param withPagination: whether to include pagination info + :type withPagination: bool + :param withFiles: whether to include files + :type withFiles: bool + :return: List of StixCyberObservable objects + :rtype: list + """ types = kwargs.get("types", None) filters = kwargs.get("filters", None) search = kwargs.get("search", None) @@ -131,15 +150,20 @@ def list(self, **kwargs): result["data"]["stixCyberObservables"], with_pagination ) - """ - Read a StixCyberObservable object + def read(self, **kwargs): + """Read a StixCyberObservable object. :param id: the id of the StixCyberObservable + :type id: str :param filters: the filters to apply if no id provided - :return StixCyberObservable object - """ - - def read(self, **kwargs): + :type filters: dict + :param customAttributes: custom attributes to return + :type customAttributes: str + :param withFiles: whether to include files + :type withFiles: bool + :return: StixCyberObservable object + :rtype: dict or None + """ id = kwargs.get("id", None) filters = kwargs.get("filters", None) custom_attributes = kwargs.get("customAttributes", None) @@ -177,16 +201,28 @@ def read(self, **kwargs): ) return None - """ - Upload a file in this Observable + def add_file(self, **kwargs): + """Upload a file in this Observable. :param id: the Stix-Cyber-Observable id - :param file_name - :param data - :return void - """ - - def add_file(self, **kwargs): + :type id: str + :param file_name: name of the file to upload + :type file_name: str + :param data: the file data + :type data: bytes or str + :param fileMarkings: list of marking definition IDs for the file + :type fileMarkings: list + :param version: file version + :type version: str + :param mime_type: MIME type of the file (default: text/plain) + :type mime_type: str + :param no_trigger_import: whether to skip import trigger + :type no_trigger_import: bool + :param embedded: whether the file is embedded + :type embedded: bool + :return: updated StixCyberObservable object + :rtype: dict or None + """ id = kwargs.get("id", None) file_name = kwargs.get("file_name", None) data = kwargs.get("data", None) @@ -221,7 +257,7 @@ def add_file(self, **kwargs): query, { "id": id, - "file": (self.file(final_file_name, data, mime_type)), + "file": (self.opencti.file(final_file_name, data, mime_type)), "fileMarkings": file_markings, "version": version, "noTriggerImport": ( @@ -234,18 +270,46 @@ def add_file(self, **kwargs): ) else: self.opencti.app_logger.error( - "[opencti_stix_cyber_observable Missing parameters: id or file_name" + "[opencti_stix_cyber_observable] Missing parameters: id or file_name" ) return None - """ - Create a Stix-Observable object + def create(self, **kwargs): + """Create a Stix-Cyber-Observable object. :param observableData: the data of the observable (STIX2 structure) - :return Stix-Observable object - """ - - def create(self, **kwargs): + :type observableData: dict + :param simple_observable_id: (optional) simple observable STIX ID + :type simple_observable_id: str + :param simple_observable_key: (optional) simple observable key (e.g., "IPv4-Addr.value") + :type simple_observable_key: str + :param simple_observable_value: (optional) simple observable value + :type simple_observable_value: str + :param simple_observable_description: (optional) simple observable description + :type simple_observable_description: str + :param x_opencti_score: (optional) score (0-100) + :type x_opencti_score: int + :param createdBy: (optional) the author ID + :type createdBy: str + :param objectMarking: (optional) list of marking definition IDs + :type objectMarking: list + :param objectLabel: (optional) list of label IDs + :type objectLabel: list + :param externalReferences: (optional) list of external reference IDs + :type externalReferences: list + :param objectOrganization: (optional) list of organization IDs + :type objectOrganization: list + :param update: (optional) whether to update if exists (default: False) + :type update: bool + :param resolve_result_indicators: (optional) resolve result indicators (default: True) + :type resolve_result_indicators: bool + :param file: (optional) File object to attach + :type file: dict + :param fileMarkings: (optional) list of marking definition IDs for the file + :type fileMarkings: list + :return: Stix-Cyber-Observable object + :rtype: dict or None + """ observable_data = kwargs.get("observableData", {}) simple_observable_id = kwargs.get("simple_observable_id", None) simple_observable_key = kwargs.get("simple_observable_key", None) @@ -261,6 +325,8 @@ def create(self, **kwargs): granted_refs = kwargs.get("objectOrganization", None) update = kwargs.get("update", False) resolve_result_indicators = kwargs.get("resolve_result_indicators", True) + file = kwargs.get("file", None) + file_markings = kwargs.get("fileMarkings", None) create_indicator = ( observable_data["x_opencti_create_indicator"] @@ -505,6 +571,8 @@ def create(self, **kwargs): observable_data["name"] if "name" in observable_data else None ), "rir": observable_data["rir"] if "rir" in observable_data else None, + "file": file, + "fileMarkings": file_markings, } elif type == "Directory": input_variables["Directory"] = { @@ -523,9 +591,15 @@ def create(self, **kwargs): "atime": ( observable_data["atime"] if "atime" in observable_data else None ), + "file": file, + "fileMarkings": file_markings, } elif type == "Domain-Name": - input_variables["DomainName"] = {"value": observable_data["value"]} + input_variables["DomainName"] = { + "value": observable_data["value"], + "file": file, + "fileMarkings": file_markings, + } if attribute is not None: input_variables["DomainName"][attribute] = simple_observable_value elif type == "Email-Addr": @@ -536,6 +610,8 @@ def create(self, **kwargs): if "display_name" in observable_data else None ), + "file": file, + "fileMarkings": file_markings, } elif type == "Email-Message": input_variables["EmailMessage"] = { @@ -565,6 +641,8 @@ def create(self, **kwargs): "body": ( observable_data["body"] if "body" in observable_data else None ), + "file": file, + "fileMarkings": file_markings, } elif type == "Email-Mime-Part-Type": input_variables["EmailMimePartType"] = { @@ -581,6 +659,8 @@ def create(self, **kwargs): if "content_disposition" in observable_data else None ), + "file": file, + "fileMarkings": file_markings, } elif type == "Artifact": if ( @@ -618,6 +698,8 @@ def create(self, **kwargs): if "x_opencti_additional_names" in observable_data else None ), + "file": file, + "fileMarkings": file_markings, } elif type == "StixFile": if ( @@ -669,6 +751,8 @@ def create(self, **kwargs): if "x_opencti_additional_names" in observable_data else None ), + "file": file, + "fileMarkings": file_markings, } elif type == "X509-Certificate": input_variables["X509Certificate"] = { @@ -808,6 +892,8 @@ def create(self, **kwargs): if "policy_mappings" in observable_data else None ), + "file": file, + "fileMarkings": file_markings, } elif type == "SSH-Key" or type.lower() == "ssh-key": input_variables["SSHKey"] = { @@ -851,30 +937,40 @@ def create(self, **kwargs): if "expiration_date" in observable_data else None ), + "file": file, + "fileMarkings": file_markings, } elif type == "IPv4-Addr": input_variables["IPv4Addr"] = { "value": ( observable_data["value"] if "value" in observable_data else None ), + "file": file, + "fileMarkings": file_markings, } elif type == "IPv6-Addr": input_variables["IPv6Addr"] = { "value": ( observable_data["value"] if "value" in observable_data else None ), + "file": file, + "fileMarkings": file_markings, } elif type == "Mac-Addr": input_variables["MacAddr"] = { "value": ( observable_data["value"] if "value" in observable_data else None ), + "file": file, + "fileMarkings": file_markings, } elif type == "Mutex": input_variables["Mutex"] = { "name": ( observable_data["name"] if "name" in observable_data else None ), + "file": file, + "fileMarkings": file_markings, } elif type == "Network-Traffic": input_variables["NetworkTraffic"] = { @@ -932,6 +1028,8 @@ def create(self, **kwargs): if "dst_packets" in observable_data else None ), + "file": file, + "fileMarkings": file_markings, } elif type == "Process": input_variables["Process"] = { @@ -957,6 +1055,8 @@ def create(self, **kwargs): if "environment_variables" in observable_data else None ), + "file": file, + "fileMarkings": file_markings, } elif type == "Software": if ( @@ -999,12 +1099,16 @@ def create(self, **kwargs): if "x_opencti_product" in observable_data else None ), + "file": file, + "fileMarkings": file_markings, } elif type == "Url": input_variables["Url"] = { "value": ( observable_data["value"] if "value" in observable_data else None ), + "file": file, + "fileMarkings": file_markings, } elif type == "User-Account": input_variables["UserAccount"] = { @@ -1078,6 +1182,8 @@ def create(self, **kwargs): if "account_last_login" in observable_data else None ), + "file": file, + "fileMarkings": file_markings, } elif type == "Windows-Registry-Key": input_variables["WindowsRegistryKey"] = { @@ -1094,6 +1200,8 @@ def create(self, **kwargs): if "number_of_subkeys" in observable_data else None ), + "file": file, + "fileMarkings": file_markings, } elif type == "Windows-Registry-Value-Type": input_variables["WindowsRegistryValueType"] = { @@ -1108,30 +1216,40 @@ def create(self, **kwargs): if "data_type" in observable_data else None ), + "file": file, + "fileMarkings": file_markings, } elif type == "User-Agent": input_variables["UserAgent"] = { "value": ( observable_data["value"] if "value" in observable_data else None ), + "file": file, + "fileMarkings": file_markings, } elif type == "Cryptographic-Key": input_variables["CryptographicKey"] = { "value": ( observable_data["value"] if "value" in observable_data else None ), + "file": file, + "fileMarkings": file_markings, } elif type == "Hostname": input_variables["Hostname"] = { "value": ( observable_data["value"] if "value" in observable_data else None ), + "file": file, + "fileMarkings": file_markings, } elif type == "Text": input_variables["Text"] = { "value": ( observable_data["value"] if "value" in observable_data else None ), + "file": file, + "fileMarkings": file_markings, } elif type == "Bank-Account": input_variables["BankAccount"] = { @@ -1144,12 +1262,16 @@ def create(self, **kwargs): if "account_number" in observable_data else None ), + "file": file, + "fileMarkings": file_markings, } elif type == "Phone-Number": input_variables["PhoneNumber"] = { "value": ( observable_data["value"] if "value" in observable_data else None ), + "file": file, + "fileMarkings": file_markings, } elif type == "Payment-Card": input_variables["PaymentCard"] = { @@ -1169,6 +1291,8 @@ def create(self, **kwargs): if "holder_name" in observable_data else None ), + "file": file, + "fileMarkings": file_markings, } elif type == "Media-Content": input_variables["MediaContent"] = { @@ -1191,6 +1315,8 @@ def create(self, **kwargs): if "publication_date" in observable_data else None ), + "file": file, + "fileMarkings": file_markings, } elif type == "Persona": input_variables["Persona"] = { @@ -1204,6 +1330,8 @@ def create(self, **kwargs): if "persona_type" in observable_data else None ), + "file": file, + "fileMarkings": file_markings, } elif type == "Payment-Card" or type.lower() == "x-opencti-payment-card": input_variables["PaymentCard"] = { @@ -1223,6 +1351,8 @@ def create(self, **kwargs): if "holder_name" in observable_data else None ), + "file": file, + "fileMarkings": file_markings, } elif ( type == "Cryptocurrency-Wallet" @@ -1232,12 +1362,16 @@ def create(self, **kwargs): "value": ( observable_data["value"] if "value" in observable_data else None ), + "file": file, + "fileMarkings": file_markings, } elif type == "Credential" or type.lower() == "x-opencti-credential": input_variables["Credential"] = { "value": ( observable_data["value"] if "value" in observable_data else None ), + "file": file, + "fileMarkings": file_markings, } elif ( type == "Tracking-Number" or type.lower() == "x-opencti-tracking-number" @@ -1246,6 +1380,8 @@ def create(self, **kwargs): "value": ( observable_data["value"] if "value" in observable_data else None ), + "file": file, + "fileMarkings": file_markings, } result = self.opencti.query(query, input_variables) if "payload_bin" in observable_data and "mime_type" in observable_data: @@ -1265,15 +1401,30 @@ def create(self, **kwargs): ) else: self.opencti.app_logger.error("Missing parameters: type") - - """ - Upload an artifact - - :param file_path: the file path - :return Stix-Observable object - """ + return None def upload_artifact(self, **kwargs): + """Upload an artifact. + + :param file_name: the file name or path + :type file_name: str + :param data: the file data (optional, reads from file_name if not provided) + :type data: bytes + :param mime_type: the MIME type (default: text/plain) + :type mime_type: str + :param x_opencti_description: description for the artifact + :type x_opencti_description: str + :param createdBy: the author ID + :type createdBy: str + :param objectMarking: list of marking definition IDs + :type objectMarking: list + :param objectLabel: list of label IDs + :type objectLabel: list + :param createIndicator: whether to create an indicator (default: False) + :type createIndicator: bool + :return: Stix-Observable object + :rtype: dict or None + """ file_name = kwargs.get("file_name", None) data = kwargs.get("data", None) mime_type = kwargs.get("mime_type", "text/plain") @@ -1403,7 +1554,7 @@ def upload_artifact(self, **kwargs): result = self.opencti.query( query, { - "file": (self.file(final_file_name, data, mime_type)), + "file": (self.opencti.file(final_file_name, data, mime_type)), "x_opencti_description": x_opencti_description, "createdBy": created_by, "objectMarking": object_marking, @@ -1415,16 +1566,18 @@ def upload_artifact(self, **kwargs): ) else: self.opencti.app_logger.error("Missing parameters: type") + return None - """ - Update a Stix-Observable object field + def update_field(self, **kwargs): + """Update a Stix-Observable object field. :param id: the Stix-Observable id - :param input: the input of the field - :return The updated Stix-Observable object - """ - - def update_field(self, **kwargs): + :type id: str + :param input: the input of the field to update + :type input: list + :return: The updated Stix-Observable object + :rtype: dict or None + """ id = kwargs.get("id", None) input = kwargs.get("input", None) if id is not None and input is not None: @@ -1456,14 +1609,16 @@ def update_field(self, **kwargs): ) return None - """ - Promote a Stix-Observable to an Indicator + def promote_to_indicator_v2(self, **kwargs): + """Promote a Stix-Observable to an Indicator. :param id: the Stix-Observable id - :return the newly created indicator - """ - - def promote_to_indicator_v2(self, **kwargs): + :type id: str + :param customAttributes: custom attributes to return for the indicator + :type customAttributes: str + :return: The newly created indicator + :rtype: dict or None + """ id = kwargs.get("id", None) custom_attributes = kwargs.get("customAttributes", None) if id is not None: @@ -1495,14 +1650,14 @@ def promote_to_indicator_v2(self, **kwargs): ) return None - """ - Delete a Stix-Observable + def delete(self, **kwargs): + """Delete a Stix-Observable. :param id: the Stix-Observable id - :return void - """ - - def delete(self, **kwargs): + :type id: str + :return: None + :rtype: None + """ id = kwargs.get("id", None) if id is not None: self.opencti.app_logger.info("Deleting Stix-Observable", {"id": id}) @@ -1520,15 +1675,16 @@ def delete(self, **kwargs): ) return None - """ - Update the Identity author of a Stix-Cyber-Observable object (created_by) + def update_created_by(self, **kwargs): + """Update the Identity author of a Stix-Cyber-Observable object (created_by). :param id: the id of the Stix-Cyber-Observable + :type id: str :param identity_id: the id of the Identity - :return Boolean - """ - - def update_created_by(self, **kwargs): + :type identity_id: str + :return: True on success, False on failure + :rtype: bool + """ id = kwargs.get("id", None) identity_id = kwargs.get("identity_id", None) if id is not None: @@ -1602,15 +1758,16 @@ def update_created_by(self, **kwargs): self.opencti.app_logger.error("Missing parameters: id") return False - """ - Add a Marking-Definition object to Stix-Cyber-Observable object (object_marking_refs) + def add_marking_definition(self, **kwargs): + """Add a Marking-Definition object to Stix-Cyber-Observable object (object_marking_refs). :param id: the id of the Stix-Cyber-Observable + :type id: str :param marking_definition_id: the id of the Marking-Definition - :return Boolean - """ - - def add_marking_definition(self, **kwargs): + :type marking_definition_id: str + :return: True on success, False on failure + :rtype: bool + """ id = kwargs.get("id", None) marking_definition_id = kwargs.get("marking_definition_id", None) if id is not None and marking_definition_id is not None: @@ -1666,15 +1823,16 @@ def add_marking_definition(self, **kwargs): ) return False - """ - Remove a Marking-Definition object to Stix-Cyber-Observable object + def remove_marking_definition(self, **kwargs): + """Remove a Marking-Definition object from Stix-Cyber-Observable object. :param id: the id of the Stix-Cyber-Observable + :type id: str :param marking_definition_id: the id of the Marking-Definition - :return Boolean - """ - - def remove_marking_definition(self, **kwargs): + :type marking_definition_id: str + :return: True on success, False on failure + :rtype: bool + """ id = kwargs.get("id", None) marking_definition_id = kwargs.get("marking_definition_id", None) if id is not None and marking_definition_id is not None: @@ -1701,18 +1859,23 @@ def remove_marking_definition(self, **kwargs): ) return True else: - self.opencti.app_logger.error("Missing parameters: id and label_id") + self.opencti.app_logger.error( + "Missing parameters: id and marking_definition_id" + ) return False - """ - Add a Label object to Stix-Cyber-Observable object + def add_label(self, **kwargs): + """Add a Label object to Stix-Cyber-Observable object. :param id: the id of the Stix-Cyber-Observable + :type id: str :param label_id: the id of the Label - :return Boolean - """ - - def add_label(self, **kwargs): + :type label_id: str + :param label_name: the name of the Label (will create if not exists) + :type label_name: str + :return: True on success, False on failure + :rtype: bool + """ id = kwargs.get("id", None) label_id = kwargs.get("label_id", None) label_name = kwargs.get("label_name", None) @@ -1758,15 +1921,18 @@ def add_label(self, **kwargs): self.opencti.app_logger.error("Missing parameters: id and label_id") return False - """ - Remove a Label object to Stix-Cyber-Observable object + def remove_label(self, **kwargs): + """Remove a Label object from Stix-Cyber-Observable object. :param id: the id of the Stix-Cyber-Observable + :type id: str :param label_id: the id of the Label - :return Boolean - """ - - def remove_label(self, **kwargs): + :type label_id: str + :param label_name: the name of the Label (alternative to label_id) + :type label_name: str + :return: True on success, False on failure + :rtype: bool + """ id = kwargs.get("id", None) label_id = kwargs.get("label_id", None) label_name = kwargs.get("label_name", None) @@ -1807,15 +1973,16 @@ def remove_label(self, **kwargs): self.opencti.app_logger.error("Missing parameters: id and label_id") return False - """ - Add a External-Reference object to Stix-Cyber-Observable object (object_marking_refs) + def add_external_reference(self, **kwargs): + """Add an External-Reference object to Stix-Cyber-Observable object. :param id: the id of the Stix-Cyber-Observable - :param marking_definition_id: the id of the Marking-Definition - :return Boolean - """ - - def add_external_reference(self, **kwargs): + :type id: str + :param external_reference_id: the id of the External-Reference + :type external_reference_id: str + :return: True on success, False on failure + :rtype: bool + """ id = kwargs.get("id", None) external_reference_id = kwargs.get("external_reference_id", None) if id is not None and external_reference_id is not None: @@ -1877,15 +2044,16 @@ def add_external_reference(self, **kwargs): ) return False - """ - Remove a Label object to Stix-Cyber-Observable object + def remove_external_reference(self, **kwargs): + """Remove an External-Reference object from Stix-Cyber-Observable object. :param id: the id of the Stix-Cyber-Observable - :param label_id: the id of the Label - :return Boolean - """ - - def remove_external_reference(self, **kwargs): + :type id: str + :param external_reference_id: the id of the External-Reference + :type external_reference_id: str + :return: True on success, False on failure + :rtype: bool + """ id = kwargs.get("id", None) external_reference_id = kwargs.get("external_reference_id", None) if id is not None and external_reference_id is not None: @@ -1912,7 +2080,9 @@ def remove_external_reference(self, **kwargs): ) return True else: - self.opencti.app_logger.error("Missing parameters: id and label_id") + self.opencti.app_logger.error( + "Missing parameters: id and external_reference_id" + ) return False def push_list_export( @@ -1925,6 +2095,25 @@ def push_list_export( list_filters="", mime_type=None, ): + """Push a list export for Stix-Cyber-Observables. + + :param entity_id: the entity ID + :type entity_id: str + :param entity_type: the entity type + :type entity_type: str + :param file_name: the file name + :type file_name: str + :param file_markings: list of marking definition IDs for the file + :type file_markings: list + :param data: the file data + :type data: bytes + :param list_filters: the list filters (default: "") + :type list_filters: str + :param mime_type: the MIME type (optional) + :type mime_type: str + :return: None + :rtype: None + """ query = """ mutation StixCyberObservablesExportPush( $entity_id: String, @@ -1943,9 +2132,9 @@ def push_list_export( } """ if mime_type is None: - file = self.file(file_name, data) + file = self.opencti.file(file_name, data) else: - file = self.file(file_name, data, mime_type) + file = self.opencti.file(file_name, data, mime_type) self.opencti.query( query, { @@ -1958,6 +2147,15 @@ def push_list_export( ) def ask_for_enrichment(self, **kwargs) -> str: + """Ask for enrichment of a Stix-Cyber-Observable. + + :param id: the id of the Stix-Cyber-Observable + :type id: str + :param connector_id: the id of the enrichment connector + :type connector_id: str + :return: The work ID + :rtype: str + """ id = kwargs.get("id", None) connector_id = kwargs.get("connector_id", None) @@ -1985,14 +2183,14 @@ def ask_for_enrichment(self, **kwargs) -> str: # return work_id return result["data"]["stixCoreObjectEdit"]["askEnrichment"]["id"] - """ - Get the reports about a Stix-Cyber-Observable object + def reports(self, **kwargs): + """Get the reports about a Stix-Cyber-Observable object. :param id: the id of the Stix-Cyber-Observable - :return List of reports - """ - - def reports(self, **kwargs): + :type id: str + :return: List of reports + :rtype: list or None + """ id = kwargs.get("id", None) if id is not None: self.opencti.app_logger.info( @@ -2112,14 +2310,14 @@ def reports(self, **kwargs): self.opencti.app_logger.error("Missing parameters: id") return None - """ - Get the notes about a Stix-Cyber-Observable object + def notes(self, **kwargs): + """Get the notes about a Stix-Cyber-Observable object. :param id: the id of the Stix-Cyber-Observable - :return List of notes - """ - - def notes(self, **kwargs): + :type id: str + :return: List of notes + :rtype: list or None + """ id = kwargs.get("id", None) if id is not None: self.opencti.app_logger.info( @@ -2240,14 +2438,14 @@ def notes(self, **kwargs): self.opencti.app_logger.error("Missing parameters: id") return None - """ - Get the observed data of a Stix-Cyber-Observable object + def observed_data(self, **kwargs): + """Get the observed data of a Stix-Cyber-Observable object. :param id: the id of the Stix-Cyber-Observable - :return List of observed data - """ - - def observed_data(self, **kwargs): + :type id: str + :return: List of observed data + :rtype: list or None + """ id = kwargs.get("id", None) if id is not None: self.opencti.app_logger.info( diff --git a/client-python/pycti/entities/opencti_stix_domain_object.py b/client-python/pycti/entities/opencti_stix_domain_object.py index 64b0eda41659..b9b71b9a13d8 100644 --- a/client-python/pycti/entities/opencti_stix_domain_object.py +++ b/client-python/pycti/entities/opencti_stix_domain_object.py @@ -12,12 +12,16 @@ class StixDomainObject: Manages STIX Domain Objects in the OpenCTI platform. :param opencti: instance of :py:class:`~pycti.api.opencti_api_client.OpenCTIApiClient` - :param file: file handling configuration + :type opencti: OpenCTIApiClient """ - def __init__(self, opencti, file): + def __init__(self, opencti): + """Initialize the StixDomainObject instance. + + :param opencti: OpenCTI API client instance + :type opencti: OpenCTIApiClient + """ self.opencti = opencti - self.file = file self.properties = """ id standard_id @@ -1095,18 +1099,34 @@ def __init__(self, opencti, file): } """ - """ - List Stix-Domain-Object objects + def list(self, **kwargs): + """List Stix-Domain-Object objects. :param types: the list of types + :type types: list :param filters: the filters to apply + :type filters: dict :param search: the search keyword + :type search: str :param first: return the first n rows from the after ID (or the beginning if not set) + :type first: int :param after: ID of the first row for pagination - :return List of Stix-Domain-Object objects - """ - - def list(self, **kwargs): + :type after: str + :param orderBy: field to order results by + :type orderBy: str + :param orderMode: ordering mode (asc/desc) + :type orderMode: str + :param customAttributes: custom attributes to return + :type customAttributes: str + :param getAll: whether to retrieve all results + :type getAll: bool + :param withPagination: whether to include pagination info + :type withPagination: bool + :param withFiles: whether to include files + :type withFiles: bool + :return: List of Stix-Domain-Object objects + :rtype: list + """ types = kwargs.get("types", None) filters = kwargs.get("filters", None) search = kwargs.get("search", None) @@ -1167,7 +1187,7 @@ def list(self, **kwargs): final_data = final_data + data while result["data"]["stixDomainObjects"]["pageInfo"]["hasNextPage"]: after = result["data"]["stixDomainObjects"]["pageInfo"]["endCursor"] - self.opencti.app_logger.info( + self.opencti.app_logger.debug( "Listing Stix-Domain-Objects", {"after": after} ) result = self.opencti.query( @@ -1192,16 +1212,22 @@ def list(self, **kwargs): result["data"]["stixDomainObjects"], with_pagination ) - """ - Read a Stix-Domain-Object object + def read(self, **kwargs): + """Read a Stix-Domain-Object object. :param id: the id of the Stix-Domain-Object + :type id: str :param types: list of Stix Domain Entity types + :type types: list :param filters: the filters to apply if no id provided - :return Stix-Domain-Object object - """ - - def read(self, **kwargs): + :type filters: dict + :param customAttributes: custom attributes to return + :type customAttributes: str + :param withFiles: whether to include files + :type withFiles: bool + :return: Stix-Domain-Object object + :rtype: dict or None + """ id = kwargs.get("id", None) types = kwargs.get("types", None) filters = kwargs.get("filters", None) @@ -1242,16 +1268,24 @@ def read(self, **kwargs): ) return None - """ - Get a Stix-Domain-Object object by stix_id or name + def get_by_stix_id_or_name(self, **kwargs): + """Get a Stix-Domain-Object object by stix_id or name. :param types: a list of Stix-Domain-Object types + :type types: list :param stix_id: the STIX ID of the Stix-Domain-Object + :type stix_id: str :param name: the name of the Stix-Domain-Object - :return Stix-Domain-Object object - """ - - def get_by_stix_id_or_name(self, **kwargs): + :type name: str + :param aliases: list of aliases to search + :type aliases: list + :param fieldName: the field name to use for alias search + :type fieldName: str + :param customAttributes: custom attributes to return + :type customAttributes: str + :return: Stix-Domain-Object object + :rtype: dict or None + """ types = kwargs.get("types", None) stix_id = kwargs.get("stix_id", None) name = kwargs.get("name", None) @@ -1295,14 +1329,16 @@ def get_by_stix_id_or_name(self, **kwargs): ) return object_result - """ - Update a Stix-Domain-Object object field + def update_field(self, **kwargs): + """Update a Stix-Domain-Object object field. :param id: the Stix-Domain-Object id + :type id: str :param input: the input of the field - """ - - def update_field(self, **kwargs): + :type input: list + :return: Updated Stix-Domain-Object object + :rtype: dict or None + """ id = kwargs.get("id", None) input = kwargs.get("input", None) if id is not None and input is not None: @@ -1334,14 +1370,13 @@ def update_field(self, **kwargs): ) return None - """ - Delete a Stix-Domain-Object + def delete(self, **kwargs): + """Delete a Stix-Domain-Object. :param id: the Stix-Domain-Object id - :return void - """ - - def delete(self, **kwargs): + :type id: str + :return: None + """ id = kwargs.get("id", None) if id is not None: self.opencti.app_logger.info("Deleting Stix-Domain-Object", {"id": id}) @@ -1359,16 +1394,28 @@ def delete(self, **kwargs): ) return None - """ - Upload a file in this Stix-Domain-Object + def add_file(self, **kwargs): + """Upload a file to this Stix-Domain-Object. :param id: the Stix-Domain-Object id - :param file_name - :param data - :return void - """ - - def add_file(self, **kwargs): + :type id: str + :param file_name: the file name or path + :type file_name: str + :param data: the file data (optional, will read from file_name if not provided) + :type data: bytes or None + :param fileMarkings: list of marking definition IDs for the file + :type fileMarkings: list + :param version: version datetime + :type version: str + :param mime_type: MIME type of the file + :type mime_type: str + :param no_trigger_import: whether to skip triggering import + :type no_trigger_import: bool + :param embedded: whether the file is embedded + :type embedded: bool + :return: File upload result + :rtype: dict or None + """ id = kwargs.get("id", None) file_name = kwargs.get("file_name", None) data = kwargs.get("data", None) @@ -1403,7 +1450,7 @@ def add_file(self, **kwargs): query, { "id": id, - "file": (self.file(final_file_name, data, mime_type)), + "file": (self.opencti.file(final_file_name, data, mime_type)), "fileMarkings": file_markings, "version": version, "noTriggerImport": ( @@ -1430,15 +1477,33 @@ def push_list_export( list_filters="", mime_type=None, ): + """Push a list export file. + + :param entity_id: the entity id + :type entity_id: str + :param entity_type: the entity type + :type entity_type: str + :param file_name: the file name + :type file_name: str + :param file_markings: list of marking definition IDs + :type file_markings: list + :param data: the file data + :type data: bytes or str + :param list_filters: filters applied to the list export + :type list_filters: str + :param mime_type: MIME type of the file + :type mime_type: str or None + :return: None + """ query = """ mutation StixDomainObjectsExportPush($entity_id: String, $entity_type: String!, $file: Upload!, $file_markings: [String]!, $listFilters: String) { stixDomainObjectsExportPush(entity_id: $entity_id, entity_type: $entity_type, file: $file, file_markings: $file_markings, listFilters: $listFilters) } """ if mime_type is None: - file = self.file(file_name, data) + file = self.opencti.file(file_name, data) else: - file = self.file(file_name, data, mime_type) + file = self.opencti.file(file_name, data, mime_type) self.opencti.query( query, { @@ -1453,6 +1518,20 @@ def push_list_export( def push_entity_export( self, entity_id, file_name, data, file_markings=None, mime_type=None ): + """Push an entity export file. + + :param entity_id: the entity id + :type entity_id: str + :param file_name: the file name + :type file_name: str + :param data: the file data + :type data: bytes or str + :param file_markings: list of marking definition IDs + :type file_markings: list or None + :param mime_type: MIME type of the file + :type mime_type: str or None + :return: None + """ if file_markings is None: file_markings = [] query = """ @@ -1469,22 +1548,23 @@ def push_entity_export( } """ if mime_type is None: - file = self.file(file_name, data) + file = self.opencti.file(file_name, data) else: - file = self.file(file_name, data, mime_type) + file = self.opencti.file(file_name, data, mime_type) self.opencti.query( query, {"id": entity_id, "file": file, "file_markings": file_markings} ) - """ - Update the Identity author of a Stix-Domain-Object object (created_by) + def update_created_by(self, **kwargs): + """Update the Identity author of a Stix-Domain-Object object (created_by). :param id: the id of the Stix-Domain-Object + :type id: str :param identity_id: the id of the Identity - :return Boolean - """ - - def update_created_by(self, **kwargs): + :type identity_id: str + :return: True if successful, False otherwise + :rtype: bool + """ id = kwargs.get("id", None) identity_id = kwargs.get("identity_id", None) if id is not None: @@ -1558,15 +1638,16 @@ def update_created_by(self, **kwargs): self.opencti.app_logger.error("Missing parameters: id") return False - """ - Add a Marking-Definition object to Stix-Domain-Object object (object_marking_refs) + def add_marking_definition(self, **kwargs): + """Add a Marking-Definition object to Stix-Domain-Object object (object_marking_refs). :param id: the id of the Stix-Domain-Object + :type id: str :param marking_definition_id: the id of the Marking-Definition - :return Boolean - """ - - def add_marking_definition(self, **kwargs): + :type marking_definition_id: str + :return: True if successful, False otherwise + :rtype: bool + """ id = kwargs.get("id", None) marking_definition_id = kwargs.get("marking_definition_id", None) if id is not None and marking_definition_id is not None: @@ -1623,15 +1704,16 @@ def add_marking_definition(self, **kwargs): ) return False - """ - Remove a Marking-Definition object to Stix-Domain-Object object + def remove_marking_definition(self, **kwargs): + """Remove a Marking-Definition object from Stix-Domain-Object object. :param id: the id of the Stix-Domain-Object + :type id: str :param marking_definition_id: the id of the Marking-Definition - :return Boolean - """ - - def remove_marking_definition(self, **kwargs): + :type marking_definition_id: str + :return: True if successful, False otherwise + :rtype: bool + """ id = kwargs.get("id", None) marking_definition_id = kwargs.get("marking_definition_id", None) if id is not None and marking_definition_id is not None: @@ -1658,18 +1740,23 @@ def remove_marking_definition(self, **kwargs): ) return True else: - self.opencti.app_logger.error("Missing parameters: id and label_id") + self.opencti.app_logger.error( + "Missing parameters: id and marking_definition_id" + ) return False - """ - Add a Label object to Stix-Domain-Object object + def add_label(self, **kwargs): + """Add a Label object to Stix-Domain-Object object. :param id: the id of the Stix-Domain-Object + :type id: str :param label_id: the id of the Label - :return Boolean - """ - - def add_label(self, **kwargs): + :type label_id: str + :param label_name: the name of the Label (alternative to label_id) + :type label_name: str + :return: True if successful, False otherwise + :rtype: bool + """ id = kwargs.get("id", None) label_id = kwargs.get("label_id", None) label_name = kwargs.get("label_name", None) @@ -1714,15 +1801,18 @@ def add_label(self, **kwargs): self.opencti.app_logger.error("Missing parameters: id and label_id") return False - """ - Remove a Label object to Stix-Domain-Object object + def remove_label(self, **kwargs): + """Remove a Label object from Stix-Domain-Object object. :param id: the id of the Stix-Domain-Object + :type id: str :param label_id: the id of the Label - :return Boolean - """ - - def remove_label(self, **kwargs): + :type label_id: str + :param label_name: the name of the Label (alternative to label_id) + :type label_name: str + :return: True if successful, False otherwise + :rtype: bool + """ id = kwargs.get("id", None) label_id = kwargs.get("label_id", None) label_name = kwargs.get("label_name", None) @@ -1763,15 +1853,16 @@ def remove_label(self, **kwargs): self.opencti.app_logger.error("Missing parameters: id and label_id") return False - """ - Add a External-Reference object to Stix-Domain-Object object (object_marking_refs) + def add_external_reference(self, **kwargs): + """Add an External-Reference object to Stix-Domain-Object object. :param id: the id of the Stix-Domain-Object - :param marking_definition_id: the id of the Marking-Definition - :return Boolean - """ - - def add_external_reference(self, **kwargs): + :type id: str + :param external_reference_id: the id of the External-Reference + :type external_reference_id: str + :return: True if successful, False otherwise + :rtype: bool + """ id = kwargs.get("id", None) external_reference_id = kwargs.get("external_reference_id", None) if id is not None and external_reference_id is not None: @@ -1805,15 +1896,16 @@ def add_external_reference(self, **kwargs): ) return False - """ - Remove a Label object to Stix-Domain-Object object + def remove_external_reference(self, **kwargs): + """Remove an External-Reference object from Stix-Domain-Object object. :param id: the id of the Stix-Domain-Object - :param label_id: the id of the Label - :return Boolean - """ - - def remove_external_reference(self, **kwargs): + :type id: str + :param external_reference_id: the id of the External-Reference + :type external_reference_id: str + :return: True if successful, False otherwise + :rtype: bool + """ id = kwargs.get("id", None) external_reference_id = kwargs.get("external_reference_id", None) if id is not None and external_reference_id is not None: @@ -1840,18 +1932,21 @@ def remove_external_reference(self, **kwargs): ) return True else: - self.opencti.app_logger.error("Missing parameters: id and label_id") + self.opencti.app_logger.error( + "Missing parameters: id and external_reference_id" + ) return False - """ - Add a Kill-Chain-Phase object to Stix-Domain-Object object (kill_chain_phases) + def add_kill_chain_phase(self, **kwargs): + """Add a Kill-Chain-Phase object to Stix-Domain-Object object (kill_chain_phases). :param id: the id of the Stix-Domain-Object + :type id: str :param kill_chain_phase_id: the id of the Kill-Chain-Phase - :return Boolean - """ - - def add_kill_chain_phase(self, **kwargs): + :type kill_chain_phase_id: str + :return: True if successful, False otherwise + :rtype: bool + """ id = kwargs.get("id", None) kill_chain_phase_id = kwargs.get("kill_chain_phase_id", None) if id is not None and kill_chain_phase_id is not None: @@ -1885,15 +1980,16 @@ def add_kill_chain_phase(self, **kwargs): ) return False - """ - Remove a Kill-Chain-Phase object to Stix-Domain-Object object + def remove_kill_chain_phase(self, **kwargs): + """Remove a Kill-Chain-Phase object from Stix-Domain-Object object. :param id: the id of the Stix-Domain-Object + :type id: str :param kill_chain_phase_id: the id of the Kill-Chain-Phase - :return Boolean - """ - - def remove_kill_chain_phase(self, **kwargs): + :type kill_chain_phase_id: str + :return: True if successful, False otherwise + :rtype: bool + """ id = kwargs.get("id", None) kill_chain_phase_id = kwargs.get("kill_chain_phase_id", None) if id is not None and kill_chain_phase_id is not None: @@ -1925,14 +2021,14 @@ def remove_kill_chain_phase(self, **kwargs): ) return False - """ - Get the reports about a Stix-Domain-Object object + def reports(self, **kwargs): + """Get the reports about a Stix-Domain-Object object. :param id: the id of the Stix-Domain-Object - :return List of reports - """ - - def reports(self, **kwargs): + :type id: str + :return: List of reports + :rtype: list or None + """ id = kwargs.get("id", None) if id is not None: self.opencti.app_logger.info( @@ -2052,14 +2148,14 @@ def reports(self, **kwargs): self.opencti.app_logger.error("Missing parameters: id") return None - """ - Get the notes about a Stix-Domain-Object object + def notes(self, **kwargs): + """Get the notes about a Stix-Domain-Object object. :param id: the id of the Stix-Domain-Object - :return List of notes - """ - - def notes(self, **kwargs): + :type id: str + :return: List of notes + :rtype: list or None + """ id = kwargs.get("id", None) if id is not None: self.opencti.app_logger.info( @@ -2180,14 +2276,14 @@ def notes(self, **kwargs): self.opencti.app_logger.error("Missing parameters: id") return None - """ - Get the observed data of a Stix-Domain-Object object + def observed_data(self, **kwargs): + """Get the observed data of a Stix-Domain-Object object. :param id: the id of the Stix-Domain-Object - :return List of observed data - """ - - def observed_data(self, **kwargs): + :type id: str + :return: List of observed data + :rtype: list or None + """ id = kwargs.get("id", None) if id is not None: self.opencti.app_logger.info( diff --git a/client-python/pycti/entities/opencti_stix_nested_ref_relationship.py b/client-python/pycti/entities/opencti_stix_nested_ref_relationship.py index 369022893c18..2cab25c13f39 100644 --- a/client-python/pycti/entities/opencti_stix_nested_ref_relationship.py +++ b/client-python/pycti/entities/opencti_stix_nested_ref_relationship.py @@ -4,9 +4,15 @@ class StixNestedRefRelationship: Manages nested reference relationships in the OpenCTI platform. :param opencti: instance of :py:class:`~pycti.api.opencti_api_client.OpenCTIApiClient` + :type opencti: OpenCTIApiClient """ def __init__(self, opencti): + """Initialize the StixNestedRefRelationship instance. + + :param opencti: OpenCTI API client instance + :type opencti: OpenCTIApiClient + """ self.opencti = opencti self.properties = """ id @@ -67,22 +73,36 @@ def __init__(self, opencti): } """ - """ - List stix_observable_relationship objects + def list(self, **kwargs): + """List stix nested ref relationship objects. + :param fromOrToId: the id of either the source or target entity + :type fromOrToId: str :param fromId: the id of the source entity of the relation + :type fromId: str + :param fromTypes: the types of the source entities + :type fromTypes: list :param toId: the id of the target entity of the relation + :type toId: str + :param toTypes: the types of the target entities + :type toTypes: list :param relationship_type: the relation type + :type relationship_type: str :param startTimeStart: the first_seen date start filter + :type startTimeStart: str :param startTimeStop: the first_seen date stop filter + :type startTimeStop: str :param stopTimeStart: the last_seen date start filter + :type stopTimeStart: str :param stopTimeStop: the last_seen date stop filter + :type stopTimeStop: str :param first: return the first n rows from the after ID (or the beginning if not set) + :type first: int :param after: ID of the first row for pagination - :return List of stix_observable_relationship objects - """ - - def list(self, **kwargs): + :type after: str + :return: List of stix nested ref relationship objects + :rtype: list + """ from_or_to_id = kwargs.get("fromOrToId", None) from_id = kwargs.get("fromId", None) from_types = kwargs.get("fromTypes", None) @@ -156,22 +176,34 @@ def list(self, **kwargs): result["data"]["stixNestedRefRelationships"], with_pagination ) - """ - Read a stix_observable_relationship object + def read(self, **kwargs): + """Read a stix nested ref relationship object. - :param id: the id of the stix_observable_relationship - :param stix_id: the STIX id of the stix_observable_relationship + :param id: the id of the stix nested ref relationship + :type id: str + :param fromOrToId: the id of either the source or target entity + :type fromOrToId: str :param fromId: the id of the source entity of the relation + :type fromId: str :param toId: the id of the target entity of the relation + :type toId: str :param relationship_type: the relation type + :type relationship_type: str :param startTimeStart: the first_seen date start filter + :type startTimeStart: str :param startTimeStop: the first_seen date stop filter + :type startTimeStop: str :param stopTimeStart: the last_seen date start filter + :type stopTimeStart: str :param stopTimeStop: the last_seen date stop filter - :return stix_observable_relationship object - """ - - def read(self, **kwargs): + :type stopTimeStop: str + :param customAttributes: custom attributes to return + :type customAttributes: str + :param filters: the filters to apply + :type filters: dict + :return: stix nested ref relationship object + :rtype: dict or None + """ id = kwargs.get("id", None) from_or_to_id = kwargs.get("fromOrToId", None) from_id = kwargs.get("fromId", None) @@ -228,14 +260,36 @@ def read(self, **kwargs): else: return None - """ - Create a stix_observable_relationship object - - :param from_id: id of the source entity - :return stix_observable_relationship object - """ - def create(self, **kwargs): + """Create a stix nested ref relationship object. + + :param fromId: the id of the source entity + :type fromId: str + :param toId: the id of the target entity + :type toId: str + :param relationship_type: the type of the relationship + :type relationship_type: str + :param start_time: (optional) the start time of the relationship + :type start_time: str + :param stop_time: (optional) the stop time of the relationship + :type stop_time: str + :param stix_id: (optional) the STIX ID + :type stix_id: str + :param created: (optional) creation date + :type created: str + :param modified: (optional) modification date + :type modified: str + :param createdBy: (optional) the creator ID + :type createdBy: str + :param objectMarking: (optional) list of marking definition IDs + :type objectMarking: list + :param x_opencti_stix_ids: (optional) list of additional STIX IDs + :type x_opencti_stix_ids: list + :param update: (optional) whether to update if exists + :type update: bool + :return: stix nested ref relationship object + :rtype: dict + """ from_id = kwargs.get("fromId", None) to_id = kwargs.get("toId", None) relationship_type = kwargs.get("relationship_type", None) @@ -297,15 +351,16 @@ def create(self, **kwargs): result["data"]["stixRefRelationshipAdd"] ) - """ - Update a stix_observable_relationship object field - - :param id: the stix_observable_relationship id - :param input: the input of the field - :return The updated stix_observable_relationship object - """ - def update_field(self, **kwargs): + """Update a stix nested ref relationship object field. + + :param id: the stix nested ref relationship id + :type id: str + :param input: the input of the field to update + :type input: list + :return: The updated stix nested ref relationship object + :rtype: dict or None + """ id = kwargs.get("id", None) input = kwargs.get("input", None) if id is not None and input is not None: @@ -330,5 +385,7 @@ def update_field(self, **kwargs): result["data"]["stixRefRelationshipEdit"]["fieldPatch"] ) else: - self.opencti.app_logger.error("Missing parameters: id and key and value") + self.opencti.app_logger.error( + "[opencti_stix_nested_ref_relationship] Missing parameters: id and input" + ) return None diff --git a/client-python/pycti/entities/opencti_stix_object_or_stix_relationship.py b/client-python/pycti/entities/opencti_stix_object_or_stix_relationship.py index 036e9ea2038b..dd1ba553258c 100644 --- a/client-python/pycti/entities/opencti_stix_object_or_stix_relationship.py +++ b/client-python/pycti/entities/opencti_stix_object_or_stix_relationship.py @@ -7,9 +7,15 @@ class StixObjectOrStixRelationship: Manages generic STIX objects and relationships in the OpenCTI platform. :param opencti: instance of :py:class:`~pycti.api.opencti_api_client.OpenCTIApiClient` + :type opencti: OpenCTIApiClient """ def __init__(self, opencti): + """Initialize the StixObjectOrStixRelationship instance. + + :param opencti: OpenCTI API client instance + :type opencti: OpenCTIApiClient + """ self.opencti = opencti self.properties = """ ... on StixObject { @@ -521,14 +527,18 @@ def __init__(self, opencti): } """ - """ - Read a StixObjectOrStixRelationship object + def read(self, **kwargs): + """Read a StixObjectOrStixRelationship object. :param id: the id of the StixObjectOrStixRelationship - :return StixObjectOrStixRelationship object - """ - - def read(self, **kwargs): + :type id: str + :param customAttributes: custom attributes to return + :type customAttributes: str + :param filters: the filters to apply + :type filters: dict + :return: StixObjectOrStixRelationship object + :rtype: dict or None + """ id = kwargs.get("id", None) custom_attributes = kwargs.get("customAttributes", None) filters = kwargs.get("filters", None) @@ -566,6 +576,25 @@ def read(self, **kwargs): return None def list(self, **kwargs): + """List StixObjectOrStixRelationship objects. + + :param filters: the filters to apply + :type filters: dict + :param search: the search keyword + :type search: str + :param first: return the first n rows from the after ID (or the beginning if not set) + :type first: int + :param after: ID of the first row for pagination + :type after: str + :param getAll: whether to retrieve all results + :type getAll: bool + :param with_pagination: whether to include pagination info + :type with_pagination: bool + :param customAttributes: custom attributes to return + :type customAttributes: str + :return: List of StixObjectOrStixRelationship objects + :rtype: list + """ filters = kwargs.get("filters", None) search = kwargs.get("search", None) first = kwargs.get("first", 100) @@ -623,7 +652,7 @@ def list(self, **kwargs): after = result["data"]["stixObjectOrStixRelationships"]["pageInfo"][ "endCursor" ] - self.opencti.app_logger.info( + self.opencti.app_logger.debug( "Listing stixObjectOrStixRelationships", {"after": after} ) after_variables = {**variables, **{"after": after}} diff --git a/client-python/pycti/entities/opencti_stix_sighting_relationship.py b/client-python/pycti/entities/opencti_stix_sighting_relationship.py index 9ff3f63c9eb1..eaae31d96b01 100644 --- a/client-python/pycti/entities/opencti_stix_sighting_relationship.py +++ b/client-python/pycti/entities/opencti_stix_sighting_relationship.py @@ -12,9 +12,15 @@ class StixSightingRelationship: Manages STIX sighting relationships in the OpenCTI platform. :param opencti: instance of :py:class:`~pycti.api.opencti_api_client.OpenCTIApiClient` + :type opencti: OpenCTIApiClient """ def __init__(self, opencti): + """Initialize the StixSightingRelationship instance. + + :param opencti: OpenCTI API client instance + :type opencti: OpenCTIApiClient + """ self.opencti = opencti self.properties = """ id @@ -283,6 +289,19 @@ def generate_id( first_seen=None, last_seen=None, ): + """Generate a STIX ID for a Sighting relationship. + + :param sighting_of_ref: The STIX ID of the entity being sighted + :type sighting_of_ref: str + :param where_sighted_refs: The STIX IDs of where the sighting occurred + :type where_sighted_refs: list + :param first_seen: (optional) The first seen date + :type first_seen: str or datetime.datetime or None + :param last_seen: (optional) The last seen date + :type last_seen: str or datetime.datetime or None + :return: STIX ID for the sighting + :rtype: str + """ if isinstance(first_seen, datetime.datetime): first_seen = first_seen.isoformat() if isinstance(last_seen, datetime.datetime): @@ -315,6 +334,13 @@ def generate_id( @staticmethod def generate_id_from_data(data): + """Generate a STIX ID from sighting data. + + :param data: Dictionary containing sighting_of_ref, where_sighted_refs, and optionally first_seen/last_seen + :type data: dict + :return: STIX ID for the sighting + :rtype: str + """ return StixSightingRelationship.generate_id( data["sighting_of_ref"], data["where_sighted_refs"], @@ -322,21 +348,48 @@ def generate_id_from_data(data): data.get("last_seen"), ) - """ - List stix_sightings objects + def list(self, **kwargs): + """List stix_sighting_relationship objects. + :param fromOrToId: the id of an entity (source or target) + :type fromOrToId: str :param fromId: the id of the source entity of the relation + :type fromId: str + :param fromTypes: filter by source entity types + :type fromTypes: list :param toId: the id of the target entity of the relation + :type toId: str + :param toTypes: filter by target entity types + :type toTypes: list :param firstSeenStart: the first_seen date start filter + :type firstSeenStart: str :param firstSeenStop: the first_seen date stop filter + :type firstSeenStop: str :param lastSeenStart: the last_seen date start filter + :type lastSeenStart: str :param lastSeenStop: the last_seen date stop filter + :type lastSeenStop: str + :param filters: additional filters to apply + :type filters: dict :param first: return the first n rows from the after ID (or the beginning if not set) + :type first: int :param after: ID of the first row for pagination - :return List of stix_sighting objects - """ - - def list(self, **kwargs): + :type after: str + :param orderBy: field to order results by + :type orderBy: str + :param orderMode: ordering mode (asc/desc) + :type orderMode: str + :param customAttributes: custom attributes to return + :type customAttributes: str + :param getAll: whether to retrieve all results + :type getAll: bool + :param withPagination: whether to include pagination info + :type withPagination: bool + :param search: search keyword + :type search: str + :return: List of stix_sighting_relationship objects + :rtype: list + """ from_or_to_id = kwargs.get("fromOrToId", None) from_id = kwargs.get("fromId", None) from_types = kwargs.get("fromTypes", None) @@ -414,7 +467,7 @@ def list(self, **kwargs): after = result["data"]["stixSightingRelationships"]["pageInfo"][ "endCursor" ] - self.opencti.app_logger.info( + self.opencti.app_logger.debug( "Listing StixSightingRelationships", {"after": after} ) result = self.opencti.query( @@ -434,6 +487,7 @@ def list(self, **kwargs): "after": after, "orderBy": order_by, "orderMode": order_mode, + "search": search, }, ) data = self.opencti.process_multiple( @@ -446,20 +500,32 @@ def list(self, **kwargs): result["data"]["stixSightingRelationships"], with_pagination ) - """ - Read a stix_sighting object + def read(self, **kwargs): + """Read a stix_sighting_relationship object. - :param id: the id of the stix_sighting + :param id: the id of the stix_sighting_relationship + :type id: str + :param fromOrToId: the id of an entity (source or target) + :type fromOrToId: str :param fromId: the id of the source entity of the relation + :type fromId: str :param toId: the id of the target entity of the relation + :type toId: str :param firstSeenStart: the first_seen date start filter + :type firstSeenStart: str :param firstSeenStop: the first_seen date stop filter + :type firstSeenStop: str :param lastSeenStart: the last_seen date start filter + :type lastSeenStart: str :param lastSeenStop: the last_seen date stop filter - :return stix_sighting object - """ - - def read(self, **kwargs): + :type lastSeenStop: str + :param customAttributes: custom attributes to return + :type customAttributes: str + :param filters: filters to apply + :type filters: dict + :return: stix_sighting_relationship object + :rtype: dict or None + """ id = kwargs.get("id", None) from_or_to_id = kwargs.get("fromOrToId", None) from_id = kwargs.get("fromId", None) @@ -515,14 +581,52 @@ def read(self, **kwargs): self.opencti.app_logger.error("Missing parameters: id or from_id and to_id") return None - """ - Create a stix_sighting object - - :param name: the name of the Attack Pattern - :return stix_sighting object - """ - def create(self, **kwargs): + """Create a stix_sighting_relationship object. + + :param fromId: the id of the source entity + :type fromId: str + :param toId: the id of the target entity + :type toId: str + :param stix_id: (optional) the STIX ID + :type stix_id: str + :param description: (optional) description + :type description: str + :param first_seen: (optional) first seen date + :type first_seen: str + :param last_seen: (optional) last seen date + :type last_seen: str + :param count: (optional) sighting count + :type count: int + :param x_opencti_negative: (optional) whether this is a negative sighting + :type x_opencti_negative: bool + :param created: (optional) creation date + :type created: str + :param modified: (optional) modification date + :type modified: str + :param confidence: (optional) confidence level (0-100) + :type confidence: int + :param createdBy: (optional) the author ID + :type createdBy: str + :param objectMarking: (optional) list of marking definition IDs + :type objectMarking: list + :param objectLabel: (optional) list of label IDs + :type objectLabel: list + :param externalReferences: (optional) list of external reference IDs + :type externalReferences: list + :param x_opencti_stix_ids: (optional) list of additional STIX IDs + :type x_opencti_stix_ids: list + :param x_opencti_workflow_id: (optional) workflow ID + :type x_opencti_workflow_id: str + :param x_opencti_modified_at: (optional) custom modification date + :type x_opencti_modified_at: str + :param objectOrganization: (optional) list of organization IDs + :type objectOrganization: list + :param update: (optional) whether to update if exists (default: False) + :type update: bool + :return: stix_sighting_relationship object + :rtype: dict or None + """ from_id = kwargs.get("fromId", None) to_id = kwargs.get("toId", None) stix_id = kwargs.get("stix_id", None) @@ -588,15 +692,16 @@ def create(self, **kwargs): result["data"]["stixSightingRelationshipAdd"] ) - """ - Update a stix_sighting object field + def update_field(self, **kwargs): + """Update a stix_sighting_relationship object field. - :param id: the stix_sighting id + :param id: the stix_sighting_relationship id + :type id: str :param input: the input of the field - :return The updated stix_sighting object - """ - - def update_field(self, **kwargs): + :type input: list + :return: The updated stix_sighting_relationship object + :rtype: dict or None + """ id = kwargs.get("id", None) input = kwargs.get("input", None) if id is not None and input is not None: @@ -622,19 +727,20 @@ def update_field(self, **kwargs): ) else: self.opencti.app_logger.error( - "[opencti_stix_sighting] Missing parameters: id and key and value" + "[opencti_stix_sighting] Missing parameters: id and input" ) return None - """ - Add a Marking-Definition object to stix_sighting_relationship object (object_marking_refs) + def add_marking_definition(self, **kwargs): + """Add a Marking-Definition object to stix_sighting_relationship object (object_marking_refs). :param id: the id of the stix_sighting_relationship + :type id: str :param marking_definition_id: the id of the Marking-Definition - :return Boolean - """ - - def add_marking_definition(self, **kwargs): + :type marking_definition_id: str + :return: True if successful, False otherwise + :rtype: bool + """ id = kwargs.get("id", None) marking_definition_id = kwargs.get("marking_definition_id", None) if id is not None and marking_definition_id is not None: @@ -693,15 +799,16 @@ def add_marking_definition(self, **kwargs): ) return False - """ - Remove a Marking-Definition object to stix_sighting_relationship + def remove_marking_definition(self, **kwargs): + """Remove a Marking-Definition object from stix_sighting_relationship. :param id: the id of the stix_sighting_relationship + :type id: str :param marking_definition_id: the id of the Marking-Definition - :return Boolean - """ - - def remove_marking_definition(self, **kwargs): + :type marking_definition_id: str + :return: True if successful, False otherwise + :rtype: bool + """ id = kwargs.get("id", None) marking_definition_id = kwargs.get("marking_definition_id", None) if id is not None and marking_definition_id is not None: @@ -728,18 +835,21 @@ def remove_marking_definition(self, **kwargs): ) return True else: - self.opencti.app_logger.error("Missing parameters: id and label_id") + self.opencti.app_logger.error( + "Missing parameters: id and marking_definition_id" + ) return False - """ - Update the Identity author of a stix_sighting_relationship object (created_by) + def update_created_by(self, **kwargs): + """Update the Identity author of a stix_sighting_relationship object (created_by). :param id: the id of the stix_sighting_relationship + :type id: str :param identity_id: the id of the Identity - :return Boolean - """ - - def update_created_by(self, **kwargs): + :type identity_id: str + :return: True if successful, False otherwise + :rtype: bool + """ id = kwargs.get("id", None) identity_id = kwargs.get("identity_id", None) if id is not None: @@ -813,15 +923,17 @@ def update_created_by(self, **kwargs): self.opencti.app_logger.error("Missing parameters: id") return False - """ - Share element to multiple organizations - - :param entity_id: the stix_sighting id - :param organization_id:s the organization to share with - :return void - """ - def organization_share(self, entity_id, organization_ids, sharing_direct_container): + """Share element to multiple organizations. + + :param entity_id: the stix_sighting_relationship id + :type entity_id: str + :param organization_ids: the organization IDs to share with + :type organization_ids: list + :param sharing_direct_container: whether to share direct container + :type sharing_direct_container: bool + :return: None + """ query = """ mutation StixSightingRelationshipEdit($id: ID!, $organizationId: [ID!]!, $directContainerSharing: Boolean) { stixSightingRelationshipEdit(id: $id) { @@ -840,17 +952,19 @@ def organization_share(self, entity_id, organization_ids, sharing_direct_contain }, ) - """ - Unshare element from multiple organizations - - :param entity_id: the stix_sighting id - :param organization_id:s the organization to share with - :return void - """ - def organization_unshare( self, entity_id, organization_ids, sharing_direct_container ): + """Unshare element from multiple organizations. + + :param entity_id: the stix_sighting_relationship id + :type entity_id: str + :param organization_ids: the organization IDs to unshare from + :type organization_ids: list + :param sharing_direct_container: whether to unshare direct container + :type sharing_direct_container: bool + :return: None + """ query = """ mutation StixSightingRelationshipEdit($id: ID!, $organizationId: [ID!]!, $directContainerSharing: Boolean) { stixSightingRelationshipEdit(id: $id) { @@ -869,14 +983,13 @@ def organization_unshare( }, ) - """ - Remove a stix_sighting object from draft (revert) - - :param id: the stix_sighting id - :return void - """ - def remove_from_draft(self, **kwargs): + """Remove a stix_sighting_relationship object from draft (revert). + + :param id: the stix_sighting_relationship id + :type id: str + :return: None + """ id = kwargs.get("id", None) if id is not None: self.opencti.app_logger.info("Draft remove stix_sighting", {"id": id}) @@ -890,18 +1003,17 @@ def remove_from_draft(self, **kwargs): self.opencti.query(query, {"id": id}) else: self.opencti.app_logger.error( - "[stix_sighting] Cant remove from draft, missing parameters: id" + "[stix_sighting] Cannot remove from draft, missing parameters: id" ) return None - """ - Delete a stix_sighting - - :param id: the stix_sighting id - :return void - """ - def delete(self, **kwargs): + """Delete a stix_sighting_relationship. + + :param id: the stix_sighting_relationship id + :type id: str + :return: None + """ id = kwargs.get("id", None) if id is not None: self.opencti.app_logger.info("Deleting stix_sighting", {"id": id}) diff --git a/client-python/pycti/entities/opencti_task.py b/client-python/pycti/entities/opencti_task.py index 4dbd5b35a094..7808335302f7 100644 --- a/client-python/pycti/entities/opencti_task.py +++ b/client-python/pycti/entities/opencti_task.py @@ -12,9 +12,15 @@ class Task: Manages tasks and to-do items in the OpenCTI platform. :param opencti: instance of :py:class:`~pycti.api.opencti_api_client.OpenCTIApiClient` + :type opencti: OpenCTIApiClient """ def __init__(self, opencti): + """Initialize the Task instance. + + :param opencti: OpenCTI API client instance + :type opencti: OpenCTIApiClient + """ self.opencti = opencti self.properties = """ id @@ -241,6 +247,15 @@ def __init__(self, opencti): @staticmethod def generate_id(name, created): + """Generate a STIX ID for a Task object. + + :param name: the name of the Task + :type name: str + :param created: the creation date of the Task + :type created: str or datetime.datetime + :return: STIX ID for the Task + :rtype: str + """ if isinstance(created, datetime.datetime): created = created.isoformat() data = {"name": name.lower().strip(), "created": created} @@ -250,19 +265,29 @@ def generate_id(name, created): @staticmethod def generate_id_from_data(data): + """Generate a STIX ID from Task data. + + :param data: Dictionary containing 'name' and 'created' keys + :type data: dict + :return: STIX ID for the Task + :rtype: str + """ return Task.generate_id(data["name"], data["created"]) - """ - List Task objects - + def list(self, **kwargs): + """List Task objects. + :param filters: the filters to apply + :type filters: dict :param search: the search keyword + :type search: str :param first: return the first n rows from the after ID (or the beginning if not set) + :type first: int :param after: ID of the first row for pagination - :return List of Task objects - """ - - def list(self, **kwargs): + :type after: str + :return: List of Task objects + :rtype: list + """ filters = kwargs.get("filters", None) search = kwargs.get("search", None) first = kwargs.get("first", 500) @@ -335,15 +360,16 @@ def list(self, **kwargs): result["data"]["tasks"], with_pagination ) - """ - Read a Task object + def read(self, **kwargs): + """Read a Task object. :param id: the id of the Task + :type id: str :param filters: the filters to apply if no id provided - :return Task object - """ - - def read(self, **kwargs): + :type filters: dict + :return: Task object + :rtype: dict or None + """ id = kwargs.get("id", None) filters = kwargs.get("filters", None) custom_attributes = kwargs.get("customAttributes", None) @@ -372,17 +398,24 @@ def read(self, **kwargs): return result[0] else: return None - - """ - Read a Task object by stix_id or name - - :param type: the Stix-Domain-Entity type - :param stix_id: the STIX ID of the Stix-Domain-Entity - :param name: the name of the Stix-Domain-Entity - :return Stix-Domain-Entity object - """ + else: + self.opencti.app_logger.error( + "[opencti_task] Missing parameters: id or filters" + ) + return None def get_by_stix_id_or_name(self, **kwargs): + """Read a Task object by stix_id or name. + + :param stix_id: the STIX ID of the Task + :type stix_id: str + :param name: the name of the Task + :type name: str + :param created: the creation date of the Task + :type created: str + :return: Task object + :rtype: dict or None + """ stix_id = kwargs.get("stix_id", None) name = kwargs.get("name", None) created = kwargs.get("created", None) @@ -405,15 +438,16 @@ def get_by_stix_id_or_name(self, **kwargs): ) return object_result - """ - Check if a task already contains a thing (Stix Object or Stix Relationship) + def contains_stix_object_or_stix_relationship(self, **kwargs): + """Check if a task already contains a thing (Stix Object or Stix Relationship). :param id: the id of the Task + :type id: str :param stixObjectOrStixRelationshipId: the id of the Stix-Entity - :return Boolean - """ - - def contains_stix_object_or_stix_relationship(self, **kwargs): + :type stixObjectOrStixRelationshipId: str + :return: True if the task contains the entity, False otherwise + :rtype: bool or None + """ id = kwargs.get("id", None) stix_object_or_stix_relationship_id = kwargs.get( "stixObjectOrStixRelationshipId", None @@ -441,17 +475,28 @@ def contains_stix_object_or_stix_relationship(self, **kwargs): return result["data"]["taskContainsStixObjectOrStixRelationship"] else: self.opencti.app_logger.error( - "[opencti_Task] Missing parameters: id or stixObjectOrStixRelationshipId" + "[opencti_task] Missing parameters: id or stixObjectOrStixRelationshipId" ) + return None - """ - Create a Task object + def create(self, **kwargs): + """Create a Task object. :param name: the name of the Task - :return Task object - """ - - def create(self, **kwargs): + :type name: str + :param description: the description of the Task + :type description: str + :param due_date: the due date of the Task + :type due_date: str + :param createdBy: the creator of the Task + :type createdBy: str + :param update: (optional) whether to update if exists (default: False) + :type update: bool + :param file: (optional) file object to attach + :param fileMarkings: (optional) list of marking definition IDs for the file + :return: Task object + :rtype: dict or None + """ objects = kwargs.get("objects", None) created = kwargs.get("created", None) name = kwargs.get("name", None) @@ -466,6 +511,8 @@ def create(self, **kwargs): x_opencti_workflow_id = kwargs.get("x_opencti_workflow_id", None) x_opencti_modified_at = kwargs.get("x_opencti_modified_at", None) update = kwargs.get("update", False) + file = kwargs.get("file", None) + file_markings = kwargs.get("fileMarkings", None) if name is not None: self.opencti.app_logger.info("Creating Task", {"name": name}) @@ -497,14 +544,26 @@ def create(self, **kwargs): "x_opencti_workflow_id": x_opencti_workflow_id, "x_opencti_modified_at": x_opencti_modified_at, "update": update, + "file": file, + "fileMarkings": file_markings, } }, ) return self.opencti.process_multiple_fields(result["data"]["taskAdd"]) else: self.opencti.app_logger.error("[opencti_task] Missing parameters: name") + return None def update_field(self, **kwargs): + """Update a field of a Task object. + + :param id: the id of the Task + :type id: str + :param input: the input containing field(s) to update + :type input: list + :return: Task object + :rtype: dict or None + """ self.opencti.app_logger.info("Updating Task", {"data": json.dumps(kwargs)}) id = kwargs.get("id", None) input = kwargs.get("input", None) @@ -524,19 +583,20 @@ def update_field(self, **kwargs): ) else: self.opencti.app_logger.error( - "[opencti_Task] Missing parameters: id and key and value" + "[opencti_task] Missing parameters: id and input" ) return None - """ - Add a Stix-Entity object to Task object (object_refs) + def add_stix_object_or_stix_relationship(self, **kwargs): + """Add a Stix-Entity object to Task object (object_refs). :param id: the id of the Task + :type id: str :param stixObjectOrStixRelationshipId: the id of the Stix-Entity - :return Boolean - """ - - def add_stix_object_or_stix_relationship(self, **kwargs): + :type stixObjectOrStixRelationshipId: str + :return: True if successful, None if parameters are missing + :rtype: bool or None + """ id = kwargs.get("id", None) stix_object_or_stix_relationship_id = kwargs.get( "stixObjectOrStixRelationshipId", None @@ -573,15 +633,16 @@ def add_stix_object_or_stix_relationship(self, **kwargs): ) return False - """ - Remove a Stix-Entity object to Task object (object_refs) + def remove_stix_object_or_stix_relationship(self, **kwargs): + """Remove a Stix-Entity object from Task object (object_refs). :param id: the id of the Task + :type id: str :param stixObjectOrStixRelationshipId: the id of the Stix-Entity - :return Boolean - """ - - def remove_stix_object_or_stix_relationship(self, **kwargs): + :type stixObjectOrStixRelationshipId: str + :return: True if successful, False otherwise + :rtype: bool + """ id = kwargs.get("id", None) stix_object_or_stix_relationship_id = kwargs.get( "stixObjectOrStixRelationshipId", None @@ -596,7 +657,7 @@ def remove_stix_object_or_stix_relationship(self, **kwargs): ) query = """ mutation taskEditRelationDelete($id: ID!, $toId: StixRef!, $relationship_type: String!) { - taskRelationAdd(id: $id, toId: $toId, relationship_type: $relationship_type) { + taskRelationDelete(id: $id, toId: $toId, relationship_type: $relationship_type) { id } } @@ -616,14 +677,18 @@ def remove_stix_object_or_stix_relationship(self, **kwargs): ) return False - """ - Import a Task object from a STIX2 object + def import_from_stix2(self, **kwargs): + """Import a Task object from a STIX2 object. :param stixObject: the Stix-Object Task - :return Task object - """ - - def import_from_stix2(self, **kwargs): + :type stixObject: dict + :param extras: additional parameters like created_by_id, object_marking_ids + :type extras: dict + :param update: whether to update existing object + :type update: bool + :return: Task object + :rtype: dict or None + """ stix_object = kwargs.get("stixObject", None) extras = kwargs.get("extras", {}) update = kwargs.get("update", False) @@ -703,13 +768,22 @@ def import_from_stix2(self, **kwargs): else None ), update=update, + file=extras.get("file"), + fileMarkings=extras.get("fileMarkings"), ) else: self.opencti.app_logger.error( "[opencti_task] Missing parameters: stixObject" ) + return None def delete(self, **kwargs): + """Delete a Task object. + + :param id: the id of the Task to delete + :type id: str + :return: None + """ id = kwargs.get("id", None) if id is not None: self.opencti.app_logger.info("Deleting Task", {"id": id}) diff --git a/client-python/pycti/entities/opencti_threat_actor.py b/client-python/pycti/entities/opencti_threat_actor.py index 47cce516ac88..06ef8face89b 100644 --- a/client-python/pycti/entities/opencti_threat_actor.py +++ b/client-python/pycti/entities/opencti_threat_actor.py @@ -2,6 +2,7 @@ import json import uuid +import warnings from typing import Union from stix2.canonicalization.Canonicalize import canonicalize @@ -13,12 +14,18 @@ class ThreatActor: """Main ThreatActor class for OpenCTI + Manages threat actor entities (groups and individuals) in the OpenCTI platform. + :param opencti: instance of :py:class:`~pycti.api.opencti_api_client.OpenCTIApiClient` + :type opencti: OpenCTIApiClient """ def __init__(self, opencti): - """Create an instance of ThreatActor""" + """Initialize the ThreatActor instance. + :param opencti: OpenCTI API client instance + :type opencti: OpenCTIApiClient + """ self.opencti = opencti self.threat_actor_group = ThreatActorGroup(opencti) self.threat_actor_individual = ThreatActorIndividual(opencti) @@ -143,12 +150,28 @@ def __init__(self, opencti): @staticmethod def generate_id(name, opencti_type): + """Generate a STIX ID for a Threat Actor. + + :param name: the name of the Threat Actor + :type name: str + :param opencti_type: the type of the Threat Actor (e.g., 'Threat-Actor-Group') + :type opencti_type: str + :return: STIX ID for the Threat Actor + :rtype: str + """ data = {"name": name.lower().strip(), "opencti_type": opencti_type} data = canonicalize(data, utf8=False) id = str(uuid.uuid5(uuid.UUID("00abedb4-aa42-466c-9c01-fed23315a9b7"), data)) return "threat-actor--" + id def generate_id_from_data(self, data): + """Generate a STIX ID from Threat Actor data. + + :param data: Dictionary containing 'name' and optionally 'x_opencti_type' keys + :type data: dict + :return: STIX ID for the Threat Actor + :rtype: str + """ data_type = "Threat-Actor-Group" if "x_opencti_type" in data: data_type = data["x_opencti_type"] @@ -157,18 +180,28 @@ def generate_id_from_data(self, data): return ThreatActor.generate_id(data["name"], data_type) def list(self, **kwargs) -> dict: - """List Threat-Actor objects - - The list method accepts the following kwargs: + """List Threat-Actor objects. - :param list filters: (optional) the filters to apply - :param str search: (optional) a search keyword to apply for the listing - :param int first: (optional) return the first n rows from the `after` ID - or the beginning if not set - :param str after: (optional) OpenCTI object ID of the first row for pagination - :param str orderBy: (optional) the field to order the response on - :param bool orderMode: (optional) either "`asc`" or "`desc`" - :param bool withPagination: (optional) switch to use pagination + :param filters: the filters to apply + :type filters: dict + :param search: the search keyword + :type search: str + :param first: return the first n rows from the after ID (or the beginning if not set) + :type first: int + :param after: ID of the first row for pagination + :type after: str + :param orderBy: field to order results by + :type orderBy: str + :param orderMode: ordering mode (asc/desc) + :type orderMode: str + :param customAttributes: custom attributes to return + :type customAttributes: str + :param getAll: whether to retrieve all results + :type getAll: bool + :param withPagination: whether to include pagination info + :type withPagination: bool + :return: List of Threat-Actor objects + :rtype: list """ filters = kwargs.get("filters", None) @@ -244,17 +277,16 @@ def list(self, **kwargs) -> dict: ) def read(self, **kwargs) -> Union[dict, None]: - """Read a Threat-Actor object - - read can be either used with a known OpenCTI entity `id` or by using a - valid filter to search and return a single Threat-Actor entity or None. - - The list method accepts the following kwargs. - - Note: either `id` or `filters` is required. + """Read a Threat-Actor object. - :param str id: the id of the Threat-Actor - :param list filters: the filters to apply if no id provided + :param id: the id of the Threat-Actor + :type id: str + :param filters: the filters to apply if no id provided + :type filters: dict + :param customAttributes: custom attributes to return + :type customAttributes: str + :return: Threat-Actor object + :rtype: dict or None """ id = kwargs.get("id", None) @@ -291,19 +323,28 @@ def read(self, **kwargs) -> Union[dict, None]: ) return None - @DeprecationWarning def create(self, **kwargs): - # For backward compatibility, please use threat_actor_group or threat_actor_individual - return self.threat_actor_group.create(**kwargs) - - """ - Import an Threat-Actor object from a STIX2 object + """Create a Threat-Actor-Group object (deprecated). - :param stixObject: the Stix-Object Intrusion-Set - :return Intrusion-Set object - """ + .. deprecated:: + Use :meth:`threat_actor_group.create` or :meth:`threat_actor_individual.create` instead. + """ + warnings.warn( + "ThreatActor.create() is deprecated, use threat_actor_group.create() " + "or threat_actor_individual.create() instead", + DeprecationWarning, + stacklevel=2, + ) + return self.threat_actor_group.create(**kwargs) def import_from_stix2(self, **kwargs): + """Import a Threat-Actor object from a STIX2 object. + + :param stixObject: the STIX2 Threat-Actor object + :type stixObject: dict + :return: Threat-Actor object + :rtype: dict or None + """ stix_object = kwargs.get("stixObject", None) if "x_opencti_type" in stix_object: type = stix_object["x_opencti_type"].lower() diff --git a/client-python/pycti/entities/opencti_threat_actor_group.py b/client-python/pycti/entities/opencti_threat_actor_group.py index 358e4674db51..c6971d846f06 100644 --- a/client-python/pycti/entities/opencti_threat_actor_group.py +++ b/client-python/pycti/entities/opencti_threat_actor_group.py @@ -10,12 +10,18 @@ class ThreatActorGroup: """Main ThreatActorGroup class for OpenCTI + Manages threat actor group entities in the OpenCTI platform. + :param opencti: instance of :py:class:`~pycti.api.opencti_api_client.OpenCTIApiClient` + :type opencti: OpenCTIApiClient """ def __init__(self, opencti): - """Create an instance of ThreatActorGroup""" + """Initialize the ThreatActorGroup instance. + :param opencti: OpenCTI API client instance + :type opencti: OpenCTIApiClient + """ self.opencti = opencti self.properties = """ id @@ -147,6 +153,13 @@ def __init__(self, opencti): @staticmethod def generate_id(name): + """Generate a STIX ID for a Threat Actor Group. + + :param name: The name of the threat actor group + :type name: str + :return: STIX ID for the threat actor group + :rtype: str + """ name = name.lower().strip() data = {"name": name, "opencti_type": "Threat-Actor-Group"} data = canonicalize(data, utf8=False) @@ -155,21 +168,35 @@ def generate_id(name): @staticmethod def generate_id_from_data(data): + """Generate a STIX ID from threat actor group data. + + :param data: Dictionary containing 'name' key + :type data: dict + :return: STIX ID for the threat actor group + :rtype: str + """ return ThreatActorGroup.generate_id(data["name"]) def list(self, **kwargs) -> dict: """List Threat-Actor-Group objects - The list method accepts the following kwargs: - - :param list filters: (optional) the filters to apply - :param str search: (optional) a search keyword to apply for the listing - :param int first: (optional) return the first n rows from the `after` ID + :param filters: (optional) the filters to apply + :type filters: list + :param search: (optional) a search keyword to apply for the listing + :type search: str + :param first: (optional) return the first n rows from the `after` ID or the beginning if not set - :param str after: (optional) OpenCTI object ID of the first row for pagination - :param str orderBy: (optional) the field to order the response on - :param bool orderMode: (optional) either "`asc`" or "`desc`" - :param bool withPagination: (optional) switch to use pagination + :type first: int + :param after: (optional) OpenCTI object ID of the first row for pagination + :type after: str + :param orderBy: (optional) the field to order the response on + :type orderBy: str + :param orderMode: (optional) either "`asc`" or "`desc`" + :type orderMode: str + :param withPagination: (optional) switch to use pagination + :type withPagination: bool + :return: List of Threat-Actor-Group objects + :rtype: list """ filters = kwargs.get("filters", None) @@ -224,7 +251,7 @@ def list(self, **kwargs) -> dict: final_data = final_data + data while result["data"]["threatActorsGroup"]["pageInfo"]["hasNextPage"]: after = result["data"]["threatActorsGroup"]["pageInfo"]["endCursor"] - self.opencti.app_logger.info( + self.opencti.app_logger.debug( "Listing threatActorsGroup", {"after": after} ) result = self.opencti.query( @@ -254,12 +281,14 @@ def read(self, **kwargs) -> Union[dict, None]: read can be either used with a known OpenCTI entity `id` or by using a valid filter to search and return a single Threat-Actor-Group entity or None. - The list method accepts the following kwargs. - Note: either `id` or `filters` is required. - :param str id: the id of the Threat-Actor-Group - :param list filters: the filters to apply if no id provided + :param id: the id of the Threat-Actor-Group + :type id: str + :param filters: the filters to apply if no id provided + :type filters: list + :return: Threat-Actor-Group object + :rtype: dict or None """ id = kwargs.get("id", None) @@ -305,34 +334,58 @@ def create(self, **kwargs): By setting `update` to `True` it acts like an upsert and updates fields of an existing Threat-Actor-Group entity. - The create method accepts the following kwargs. - Note: `name` and `description` or `stix_id` is required. - :param str stix_id: stix2 id reference for the Threat-Actor-Group entity - :param str createdBy: (optional) id of the organization that created the knowledge - :param list objectMarking: (optional) list of OpenCTI markin definition ids - :param list objectLabel: (optional) list of OpenCTI label ids - :param list externalReferences: (optional) list of OpenCTI external references ids - :param bool revoked: is this entity revoked - :param int confidence: confidence level - :param str lang: language - :param str created: (optional) date in OpenCTI date format - :param str modified: (optional) date in OpenCTI date format - :param str name: name of the threat actor group - :param str description: description of the threat actor group - :param list aliases: (optional) list of alias names for the Threat-Actor-Group - :param list threat_actor_types: (optional) list of threat actor types - :param str first_seen: (optional) date in OpenCTI date format - :param str last_seen: (optional) date in OpenCTI date format - :param list roles: (optional) list of roles - :param list goals: (optional) list of goals - :param str sophistication: (optional) describe the actors sophistication in text - :param str resource_level: (optional) describe the actors resource_level in text - :param str primary_motivation: (optional) describe the actors primary_motivation in text - :param list secondary_motivations: (optional) describe the actors secondary_motivations in list of string - :param list personal_motivations: (optional) describe the actors personal_motivations in list of strings - :param bool update: (optional) choose to updated an existing Threat-Actor-Group entity, default `False` + :param stix_id: stix2 id reference for the Threat-Actor-Group entity + :type stix_id: str + :param createdBy: (optional) id of the organization that created the knowledge + :type createdBy: str + :param objectMarking: (optional) list of OpenCTI marking definition ids + :type objectMarking: list + :param objectLabel: (optional) list of OpenCTI label ids + :type objectLabel: list + :param externalReferences: (optional) list of OpenCTI external references ids + :type externalReferences: list + :param revoked: is this entity revoked + :type revoked: bool + :param confidence: confidence level + :type confidence: int + :param lang: language + :type lang: str + :param created: (optional) date in OpenCTI date format + :type created: str + :param modified: (optional) date in OpenCTI date format + :type modified: str + :param name: name of the threat actor group + :type name: str + :param description: description of the threat actor group + :type description: str + :param aliases: (optional) list of alias names for the Threat-Actor-Group + :type aliases: list + :param threat_actor_types: (optional) list of threat actor types + :type threat_actor_types: list + :param first_seen: (optional) date in OpenCTI date format + :type first_seen: str + :param last_seen: (optional) date in OpenCTI date format + :type last_seen: str + :param roles: (optional) list of roles + :type roles: list + :param goals: (optional) list of goals + :type goals: list + :param sophistication: (optional) describe the actors sophistication in text + :type sophistication: str + :param resource_level: (optional) describe the actors resource_level in text + :type resource_level: str + :param primary_motivation: (optional) describe the actors primary_motivation in text + :type primary_motivation: str + :param secondary_motivations: (optional) describe the actors secondary_motivations in list of string + :type secondary_motivations: list + :param personal_motivations: (optional) describe the actors personal_motivations in list of strings + :type personal_motivations: list + :param update: (optional) choose to updated an existing Threat-Actor-Group entity, default `False` + :type update: bool + :return: Threat-Actor-Group object + :rtype: dict or None """ stix_id = kwargs.get("stix_id", None) @@ -351,6 +404,7 @@ def create(self, **kwargs): threat_actor_types = kwargs.get("threat_actor_types", None) first_seen = kwargs.get("first_seen", None) last_seen = kwargs.get("last_seen", None) + roles = kwargs.get("roles", None) goals = kwargs.get("goals", None) sophistication = kwargs.get("sophistication", None) resource_level = kwargs.get("resource_level", None) @@ -362,6 +416,8 @@ def create(self, **kwargs): x_opencti_workflow_id = kwargs.get("x_opencti_workflow_id", None) x_opencti_modified_at = kwargs.get("x_opencti_modified_at", None) update = kwargs.get("update", False) + file = kwargs.get("file", None) + file_markings = kwargs.get("fileMarkings", None) if name is not None: self.opencti.app_logger.info("Creating Threat-Actor-Group", {"name": name}) @@ -396,6 +452,7 @@ def create(self, **kwargs): "threat_actor_types": threat_actor_types, "first_seen": first_seen, "last_seen": last_seen, + "roles": roles, "goals": goals, "sophistication": sophistication, "resource_level": resource_level, @@ -406,6 +463,8 @@ def create(self, **kwargs): "x_opencti_workflow_id": x_opencti_workflow_id, "x_opencti_modified_at": x_opencti_modified_at, "update": update, + "file": file, + "fileMarkings": file_markings, } }, ) @@ -414,17 +473,19 @@ def create(self, **kwargs): ) else: self.opencti.app_logger.error( - "[opencti_threat_actor_group] Missing parameters: name and description" + "[opencti_threat_actor_group] Missing parameters: name" ) - - """ - Import an Threat-Actor-Group object from a STIX2 object - - :param stixObject: the Stix-Object Intrusion-Set - :return Intrusion-Set object - """ + return None def import_from_stix2(self, **kwargs): + """Import a Threat Actor Group object from a STIX2 object. + + :param stixObject: the STIX2 Threat Actor object + :param extras: extra parameters including created_by_id, object_marking_ids, etc. + :param update: whether to update if the entity already exists + :return: Threat Actor Group object + :rtype: dict or None + """ stix_object = kwargs.get("stixObject", None) extras = kwargs.get("extras", {}) update = kwargs.get("update", False) @@ -490,6 +551,7 @@ def import_from_stix2(self, **kwargs): last_seen=( stix_object["last_seen"] if "last_seen" in stix_object else None ), + roles=stix_object["roles"] if "roles" in stix_object else None, goals=stix_object["goals"] if "goals" in stix_object else None, sophistication=( stix_object["sophistication"] @@ -537,8 +599,11 @@ def import_from_stix2(self, **kwargs): else None ), update=update, + file=extras.get("file"), + fileMarkings=extras.get("fileMarkings"), ) else: self.opencti.app_logger.error( "[opencti_threat_actor_group] Missing parameters: stixObject" ) + return None diff --git a/client-python/pycti/entities/opencti_threat_actor_individual.py b/client-python/pycti/entities/opencti_threat_actor_individual.py index 8b994c1b2e3a..6277764196f0 100644 --- a/client-python/pycti/entities/opencti_threat_actor_individual.py +++ b/client-python/pycti/entities/opencti_threat_actor_individual.py @@ -10,12 +10,18 @@ class ThreatActorIndividual: """Main ThreatActorIndividual class for OpenCTI + Manages individual threat actor entities in the OpenCTI platform. + :param opencti: instance of :py:class:`~pycti.api.opencti_api_client.OpenCTIApiClient` + :type opencti: OpenCTIApiClient """ def __init__(self, opencti): - """Create an instance of ThreatActorIndividual""" + """Initialize the ThreatActorIndividual instance. + :param opencti: OpenCTI API client instance + :type opencti: OpenCTIApiClient + """ self.opencti = opencti self.properties = """ id @@ -147,6 +153,13 @@ def __init__(self, opencti): @staticmethod def generate_id(name): + """Generate a STIX ID for a Threat Actor Individual. + + :param name: The name of the threat actor individual + :type name: str + :return: STIX ID for the threat actor individual + :rtype: str + """ name = name.lower().strip() data = {"name": name, "opencti_type": "Threat-Actor-Individual"} data = canonicalize(data, utf8=False) @@ -155,6 +168,13 @@ def generate_id(name): @staticmethod def generate_id_from_data(data): + """Generate a STIX ID from threat actor individual data. + + :param data: Dictionary containing 'name' key + :type data: dict + :return: STIX ID for the threat actor individual + :rtype: str + """ return ThreatActorIndividual.generate_id(data["name"]) def list(self, **kwargs) -> dict: @@ -162,14 +182,23 @@ def list(self, **kwargs) -> dict: The list method accepts the following kwargs: - :param list filters: (optional) the filters to apply - :param str search: (optional) a search keyword to apply for the listing - :param int first: (optional) return the first n rows from the `after` ID + :param filters: (optional) the filters to apply + :type filters: list + :param search: (optional) a search keyword to apply for the listing + :type search: str + :param first: (optional) return the first n rows from the `after` ID or the beginning if not set - :param str after: (optional) OpenCTI object ID of the first row for pagination - :param str orderBy: (optional) the field to order the response on - :param bool orderMode: (optional) either "`asc`" or "`desc`" - :param bool withPagination: (optional) switch to use pagination + :type first: int + :param after: (optional) OpenCTI object ID of the first row for pagination + :type after: str + :param orderBy: (optional) the field to order the response on + :type orderBy: str + :param orderMode: (optional) either "`asc`" or "`desc`" + :type orderMode: str + :param withPagination: (optional) switch to use pagination + :type withPagination: bool + :return: List of Threat-Actor-Individual objects + :rtype: list """ filters = kwargs.get("filters", None) @@ -229,7 +258,7 @@ def list(self, **kwargs) -> dict: after = result["data"]["threatActorsIndividuals"]["pageInfo"][ "endCursor" ] - self.opencti.app_logger.info( + self.opencti.app_logger.debug( "Listing threatActorsIndividuals", {"after": after} ) result = self.opencti.query( @@ -257,14 +286,16 @@ def read(self, **kwargs) -> Union[dict, None]: """Read a Threat-Actor-Individual object read can be either used with a known OpenCTI entity `id` or by using a - valid filter to search and return a single Threat-Actor-Group entity or None. - - The list method accepts the following kwargs. + valid filter to search and return a single Threat-Actor-Individual entity or None. Note: either `id` or `filters` is required. - :param str id: the id of the Threat-Actor-Individual - :param list filters: the filters to apply if no id provided + :param id: the id of the Threat-Actor-Individual + :type id: str + :param filters: the filters to apply if no id provided + :type filters: list + :return: Threat-Actor-Individual object + :rtype: dict or None """ id = kwargs.get("id", None) @@ -310,34 +341,62 @@ def create(self, **kwargs): By setting `update` to `True` it acts like an upsert and updates fields of an existing Threat-Actor-Individual entity. - The create method accepts the following kwargs. - Note: `name` and `description` or `stix_id` is required. - :param str stix_id: stix2 id reference for the Threat-Actor-Individual entity - :param str createdBy: (optional) id of the organization that created the knowledge - :param list objectMarking: (optional) list of OpenCTI markin definition ids - :param list objectLabel: (optional) list of OpenCTI label ids - :param list externalReferences: (optional) list of OpenCTI external references ids - :param bool revoked: is this entity revoked - :param int confidence: confidence level - :param str lang: language - :param str created: (optional) date in OpenCTI date format - :param str modified: (optional) date in OpenCTI date format - :param str name: name of the threat actor individual - :param str description: description of the threat actor individual - :param list aliases: (optional) list of alias names for the Threat-Actor-Individual - :param list threat_actor_types: (optional) list of threat actor types - :param str first_seen: (optional) date in OpenCTI date format - :param str last_seen: (optional) date in OpenCTI date format - :param list roles: (optional) list of roles - :param list goals: (optional) list of goals - :param str sophistication: (optional) describe the actors sophistication in text - :param str resource_level: (optional) describe the actors resource_level in text - :param str primary_motivation: (optional) describe the actors primary_motivation in text - :param list secondary_motivations: (optional) describe the actors secondary_motivations in list of string - :param list personal_motivations: (optional) describe the actors personal_motivations in list of strings - :param bool update: (optional) choose to updated an existing Threat-Actor-Individual entity, default `False` + :param stix_id: stix2 id reference for the Threat-Actor-Individual entity + :type stix_id: str + :param createdBy: (optional) id of the organization that created the knowledge + :type createdBy: str + :param objectMarking: (optional) list of OpenCTI marking definition ids + :type objectMarking: list + :param objectLabel: (optional) list of OpenCTI label ids + :type objectLabel: list + :param externalReferences: (optional) list of OpenCTI external references ids + :type externalReferences: list + :param revoked: is this entity revoked + :type revoked: bool + :param confidence: confidence level + :type confidence: int + :param lang: language + :type lang: str + :param created: (optional) date in OpenCTI date format + :type created: str + :param modified: (optional) date in OpenCTI date format + :type modified: str + :param name: name of the threat actor individual + :type name: str + :param description: description of the threat actor individual + :type description: str + :param aliases: (optional) list of alias names for the Threat-Actor-Individual + :type aliases: list + :param threat_actor_types: (optional) list of threat actor types + :type threat_actor_types: list + :param first_seen: (optional) date in OpenCTI date format + :type first_seen: str + :param last_seen: (optional) date in OpenCTI date format + :type last_seen: str + :param roles: (optional) list of roles + :type roles: list + :param goals: (optional) list of goals + :type goals: list + :param sophistication: (optional) describe the actors sophistication in text + :type sophistication: str + :param resource_level: (optional) describe the actors resource_level in text + :type resource_level: str + :param primary_motivation: (optional) describe the actors primary_motivation in text + :type primary_motivation: str + :param secondary_motivations: (optional) describe the actors secondary_motivations in list of string + :type secondary_motivations: list + :param personal_motivations: (optional) describe the actors personal_motivations in list of strings + :type personal_motivations: list + :param update: (optional) choose to updated an existing Threat-Actor-Individual entity, default `False` + :type update: bool + :param file: (optional) File object to attach + :type file: dict + :param fileMarkings: (optional) list of marking definition IDs for the file + :type fileMarkings: list + :return: Threat-Actor-Individual object + :rtype: dict or None """ stix_id = kwargs.get("stix_id", None) @@ -356,6 +415,7 @@ def create(self, **kwargs): threat_actor_types = kwargs.get("threat_actor_types", None) first_seen = kwargs.get("first_seen", None) last_seen = kwargs.get("last_seen", None) + roles = kwargs.get("roles", None) goals = kwargs.get("goals", None) sophistication = kwargs.get("sophistication", None) resource_level = kwargs.get("resource_level", None) @@ -367,6 +427,8 @@ def create(self, **kwargs): x_opencti_workflow_id = kwargs.get("x_opencti_workflow_id", None) x_opencti_modified_at = kwargs.get("x_opencti_modified_at", None) update = kwargs.get("update", False) + file = kwargs.get("file", None) + file_markings = kwargs.get("fileMarkings", None) if name is not None: self.opencti.app_logger.info( @@ -403,6 +465,7 @@ def create(self, **kwargs): "threat_actor_types": threat_actor_types, "first_seen": first_seen, "last_seen": last_seen, + "roles": roles, "goals": goals, "sophistication": sophistication, "resource_level": resource_level, @@ -413,6 +476,8 @@ def create(self, **kwargs): "x_opencti_workflow_id": x_opencti_workflow_id, "x_opencti_modified_at": x_opencti_modified_at, "update": update, + "file": file, + "fileMarkings": file_markings, } }, ) @@ -421,17 +486,19 @@ def create(self, **kwargs): ) else: self.opencti.app_logger.error( - "[opencti_threat_actor_individual] Missing parameters: name and description" + "[opencti_threat_actor_individual] Missing parameters: name" ) - - """ - Import an Threat-Actor-Individual object from a STIX2 object - - :param stixObject: the Stix-Object Intrusion-Set - :return Intrusion-Set object - """ + return None def import_from_stix2(self, **kwargs): + """Import a Threat-Actor-Individual object from a STIX2 object. + + :param stixObject: the STIX2 Threat-Actor-Individual object + :param extras: extra parameters including created_by_id, object_marking_ids, file, fileMarkings, etc. + :param update: whether to update if the entity already exists + :return: Threat-Actor-Individual object + :rtype: dict or None + """ stix_object = kwargs.get("stixObject", None) extras = kwargs.get("extras", {}) update = kwargs.get("update", False) @@ -497,6 +564,7 @@ def import_from_stix2(self, **kwargs): last_seen=( stix_object["last_seen"] if "last_seen" in stix_object else None ), + roles=stix_object["roles"] if "roles" in stix_object else None, goals=stix_object["goals"] if "goals" in stix_object else None, sophistication=( stix_object["sophistication"] @@ -544,8 +612,11 @@ def import_from_stix2(self, **kwargs): else None ), update=update, + file=extras.get("file"), + fileMarkings=extras.get("fileMarkings"), ) else: self.opencti.app_logger.error( "[opencti_threat_actor_individual] Missing parameters: stixObject" ) + return None diff --git a/client-python/pycti/entities/opencti_tool.py b/client-python/pycti/entities/opencti_tool.py index 17db51738c8b..d2bb4bf2dfb4 100644 --- a/client-python/pycti/entities/opencti_tool.py +++ b/client-python/pycti/entities/opencti_tool.py @@ -12,9 +12,15 @@ class Tool: Manages tools used by threat actors in the OpenCTI platform. :param opencti: instance of :py:class:`~pycti.api.opencti_api_client.OpenCTIApiClient` + :type opencti: OpenCTIApiClient """ def __init__(self, opencti): + """Initialize the Tool instance. + + :param opencti: OpenCTI API client instance + :type opencti: OpenCTIApiClient + """ self.opencti = opencti self.properties = """ id @@ -62,6 +68,11 @@ def __init__(self, opencti): x_opencti_lastname } } + objectOrganization { + id + standard_id + name + } objectMarking { id standard_id @@ -171,14 +182,23 @@ def list(self, **kwargs): """List Tool objects. :param filters: the filters to apply + :type filters: dict :param search: the search keyword + :type search: str :param first: return the first n rows from the after ID (or the beginning if not set) + :type first: int :param after: ID of the first row for pagination + :type after: str :param orderBy: field to order results by + :type orderBy: str :param orderMode: ordering mode (asc/desc) + :type orderMode: str :param customAttributes: custom attributes to return + :type customAttributes: str :param getAll: whether to retrieve all results + :type getAll: bool :param withPagination: whether to include pagination info + :type withPagination: bool :return: List of Tool objects :rtype: list """ @@ -258,8 +278,11 @@ def read(self, **kwargs): """Read a Tool object. :param id: the id of the Tool + :type id: str :param filters: the filters to apply if no id provided + :type filters: dict :param customAttributes: custom attributes to return + :type customAttributes: str :return: Tool object :rtype: dict or None """ @@ -300,17 +323,52 @@ def read(self, **kwargs): def create(self, **kwargs): """Create a Tool object. - :param name: the name of the Tool + :param stix_id: (optional) the STIX ID + :type stix_id: str + :param name: the name of the Tool (required) + :type name: str :param description: description of the tool + :type description: str :param aliases: list of aliases + :type aliases: list :param tool_types: types of tool + :type tool_types: list :param tool_version: version of the tool + :type tool_version: str :param killChainPhases: kill chain phases - :param createdBy: creator identity - :param objectMarking: marking definitions - :param objectLabel: labels - :param externalReferences: external references + :type killChainPhases: list + :param createdBy: creator identity ID + :type createdBy: str + :param objectMarking: marking definition IDs + :type objectMarking: list + :param objectLabel: label IDs + :type objectLabel: list + :param externalReferences: external reference IDs + :type externalReferences: list + :param objectOrganization: organization IDs + :type objectOrganization: list + :param revoked: whether the tool is revoked + :type revoked: bool + :param confidence: confidence level (0-100) + :type confidence: int + :param lang: language + :type lang: str + :param created: creation date + :type created: str + :param modified: modification date + :type modified: str + :param x_opencti_stix_ids: additional STIX IDs + :type x_opencti_stix_ids: list + :param x_opencti_workflow_id: workflow ID + :type x_opencti_workflow_id: str + :param x_opencti_modified_at: custom modification date + :type x_opencti_modified_at: str :param update: whether to update existing tool + :type update: bool + :param file: (optional) File object to attach + :type file: dict + :param fileMarkings: (optional) list of marking definition IDs for the file + :type fileMarkings: list :return: Tool object :rtype: dict or None """ @@ -335,6 +393,8 @@ def create(self, **kwargs): x_opencti_workflow_id = kwargs.get("x_opencti_workflow_id", None) x_opencti_modified_at = kwargs.get("x_opencti_modified_at", None) update = kwargs.get("update", False) + file = kwargs.get("file", None) + file_markings = kwargs.get("fileMarkings", None) if name is not None: self.opencti.app_logger.info("Creating Tool", {"name": name}) @@ -373,23 +433,28 @@ def create(self, **kwargs): "x_opencti_workflow_id": x_opencti_workflow_id, "x_opencti_modified_at": x_opencti_modified_at, "update": update, + "file": file, + "fileMarkings": file_markings, } }, ) return self.opencti.process_multiple_fields(result["data"]["toolAdd"]) else: - self.opencti.app_logger.error( - "[opencti_tool] Missing parameters: name and description" - ) - - """ - Import an Tool object from a STIX2 object - - :param stixObject: the Stix-Object Tool - :return Tool object - """ + self.opencti.app_logger.error("[opencti_tool] Missing parameters: name") + return None def import_from_stix2(self, **kwargs): + """Import a Tool object from a STIX2 object. + + :param stixObject: the STIX2 Tool object + :type stixObject: dict + :param extras: extra parameters including created_by_id, object_marking_ids, etc. + :type extras: dict + :param update: whether to update if the entity already exists + :type update: bool + :return: Tool object + :rtype: dict or None + """ stix_object = kwargs.get("stixObject", None) extras = kwargs.get("extras", {}) update = kwargs.get("update", False) @@ -412,7 +477,7 @@ def import_from_stix2(self, **kwargs): self.opencti.get_attribute_in_extension("modified_at", stix_object) ) - return self.opencti.tool.create( + return self.create( stix_id=stix_object["id"], createdBy=( extras["created_by_id"] if "created_by_id" in extras else None @@ -478,8 +543,11 @@ def import_from_stix2(self, **kwargs): else None ), update=update, + file=extras.get("file"), + fileMarkings=extras.get("fileMarkings"), ) else: self.opencti.app_logger.error( "[opencti_tool] Missing parameters: stixObject" ) + return None diff --git a/client-python/pycti/entities/opencti_user.py b/client-python/pycti/entities/opencti_user.py index 7bc7ee877ffe..801c382c6bab 100644 --- a/client-python/pycti/entities/opencti_user.py +++ b/client-python/pycti/entities/opencti_user.py @@ -15,9 +15,17 @@ class User: You can view the properties, token_properties, session_properties, and me_properties attributes of a User object to view what attributes will be present in a User or MeUser object. + + :param opencti: instance of :py:class:`~pycti.api.opencti_api_client.OpenCTIApiClient` + :type opencti: OpenCTIApiClient """ def __init__(self, opencti): + """Initialize the User instance. + + :param opencti: OpenCTI API client instance + :type opencti: OpenCTIApiClient + """ self.opencti = opencti self.properties = """ id @@ -352,7 +360,7 @@ def read(self, **kwargs) -> Optional[Dict]: ) else: self.opencti.admin_logger.error( - "[opencti_user] Missing paramters: id, search, or filters" + "[opencti_user] Missing parameters: id, search, or filters" ) return None @@ -654,7 +662,7 @@ def delete_membership(self, **kwargs) -> Optional[Dict]: return None self.opencti.admin_logger.info( - "Removing used from group", {"id": id, "group_id": group_id} + "Removing user from group", {"id": id, "group_id": group_id} ) query = ( """ @@ -690,6 +698,7 @@ def add_organization(self, **kwargs) -> Optional[Dict]: self.opencti.admin_logger.error( "[opencti_user] Missing parameters: id and organization_id" ) + return None self.opencti.admin_logger.info( "Adding user to organization", @@ -731,6 +740,7 @@ def delete_organization(self, **kwargs) -> Optional[Dict]: self.opencti.admin_logger.error( "[opencti_user] Missing parameters: id and organization_id" ) + return None self.opencti.admin_logger.info( "Removing user from organization", @@ -794,12 +804,21 @@ def token_renew(self, **kwargs) -> Optional[Dict]: ) def send_mail(self, **kwargs): + """Send an email to a user using a template. + + :param id: the user ID to send the email to + :type id: str + :param template_id: the email template ID to use + :type template_id: str + :return: None + """ id = kwargs.get("id", None) template_id = kwargs.get("template_id", None) if id is None or template_id is None: self.opencti.admin_logger.error( "[opencti_user] Missing parameters: id and template_id" ) + return None self.opencti.admin_logger.info( "Send email to user", {"id": id, "template_id": template_id} @@ -816,6 +835,13 @@ def send_mail(self, **kwargs): self.opencti.query(query, {"input": input}) def process_multiple_fields(self, data): + """Process and normalize fields in user data. + + :param data: the user data dictionary to process + :type data: dict + :return: the processed user data with normalized fields + :rtype: dict + """ if "roles" in data: data["roles"] = self.opencti.process_multiple(data["roles"]) data["rolesIds"] = self.opencti.process_multiple_ids(data["roles"]) diff --git a/client-python/pycti/entities/opencti_vocabulary.py b/client-python/pycti/entities/opencti_vocabulary.py index dc090300ae1e..8ae69b4aa92c 100644 --- a/client-python/pycti/entities/opencti_vocabulary.py +++ b/client-python/pycti/entities/opencti_vocabulary.py @@ -10,9 +10,15 @@ class Vocabulary: Manages vocabularies and controlled vocabularies in the OpenCTI platform. :param opencti: instance of :py:class:`~pycti.api.opencti_api_client.OpenCTIApiClient` + :type opencti: OpenCTIApiClient """ def __init__(self, opencti): + """Initialize the Vocabulary instance. + + :param opencti: OpenCTI API client instance + :type opencti: OpenCTIApiClient + """ self.opencti = opencti self.properties = """ id @@ -27,6 +33,15 @@ def __init__(self, opencti): @staticmethod def generate_id(name, category): + """Generate a STIX ID for a Vocabulary. + + :param name: the name of the Vocabulary + :type name: str + :param category: the category of the Vocabulary + :type category: str + :return: STIX ID for the Vocabulary + :rtype: str + """ name = name.lower().strip() data = {"name": name, "category": category} data = canonicalize(data, utf8=False) @@ -35,9 +50,23 @@ def generate_id(name, category): @staticmethod def generate_id_from_data(data): + """Generate a STIX ID from Vocabulary data. + + :param data: Dictionary containing 'name' and 'category' keys + :type data: dict + :return: STIX ID for the Vocabulary + :rtype: str + """ return Vocabulary.generate_id(data["name"], data["category"]) def list(self, **kwargs): + """List Vocabulary objects. + + :param filters: the filters to apply + :type filters: dict + :return: List of Vocabulary objects + :rtype: list + """ filters = kwargs.get("filters", None) self.opencti.app_logger.info( "Listing Vocabularies with filters", {"filters": json.dumps(filters)} @@ -66,6 +95,15 @@ def list(self, **kwargs): return self.opencti.process_multiple(result["data"]["vocabularies"]) def read(self, **kwargs): + """Read a Vocabulary object. + + :param id: the id of the Vocabulary + :type id: str + :param filters: the filters to apply if no id provided + :type filters: dict + :return: Vocabulary object + :rtype: dict or None + """ id = kwargs.get("id", None) filters = kwargs.get("filters", None) if id is not None: @@ -96,6 +134,17 @@ def read(self, **kwargs): return None def read_or_create_unchecked_with_cache(self, vocab, cache, field): + """Read or create a Vocabulary using a cache for optimization. + + :param vocab: the vocabulary name + :type vocab: str + :param cache: the cache dictionary + :type cache: dict + :param field: the field configuration containing 'required' and 'key' + :type field: dict + :return: Vocabulary object or None + :rtype: dict or None + """ if "vocab_" + vocab in cache: vocab_data = cache["vocab_" + vocab] else: @@ -109,6 +158,29 @@ def read_or_create_unchecked_with_cache(self, vocab, cache, field): return vocab_data def create(self, **kwargs): + """Create a Vocabulary object. + + :param stix_id: (optional) the STIX ID + :type stix_id: str + :param name: the name of the Vocabulary (required) + :type name: str + :param category: the category of the Vocabulary (required) + :type category: str + :param description: (optional) description + :type description: str + :param created: (optional) creation date + :type created: str + :param modified: (optional) modification date + :type modified: str + :param aliases: (optional) list of aliases + :type aliases: list + :param x_opencti_stix_ids: (optional) list of additional STIX IDs + :type x_opencti_stix_ids: list + :param update: (optional) whether to update if exists (default: False) + :type update: bool + :return: Vocabulary object + :rtype: dict or None + """ stix_id = kwargs.get("stix_id", None) name = kwargs.get("name", None) category = kwargs.get("category", None) @@ -155,8 +227,22 @@ def create(self, **kwargs): self.opencti.app_logger.error( "[opencti_vocabulary] Missing parameters: name or category", ) + return None def read_or_create_unchecked(self, **kwargs): + """Read or create a Vocabulary. + + If the user has no rights to create the vocabulary, return None. + + :param name: the vocabulary name + :type name: str + :param required: whether the vocabulary is required + :type required: bool + :param category: the category of the vocabulary + :type category: str + :return: The available or created Vocabulary object + :rtype: dict or None + """ value = kwargs.get("name", None) vocab = self.read( filters={ @@ -173,13 +259,22 @@ def read_or_create_unchecked(self, **kwargs): return vocab def update_field(self, **kwargs): + """Update a Vocabulary object field. + + :param id: the Vocabulary id + :type id: str + :param input: the input of the field + :type input: list + :return: The updated Vocabulary object + :rtype: dict or None + """ id = kwargs.get("id", None) input = kwargs.get("input", None) if id is not None and input is not None: self.opencti.app_logger.info("Updating Vocabulary", {"id": id}) query = """ mutation VocabularyEdit($id: ID!, $input: [EditInput!]!) { - vocabularyFieldPatch(id: $id, input: $input) { + vocabularyFieldPatch(id: $id, input: $input) { id standard_id entity_type @@ -198,6 +293,6 @@ def update_field(self, **kwargs): ) else: self.opencti.app_logger.error( - "[opencti_vocabulary] Missing parameters: id and key and value" + "[opencti_vocabulary] Missing parameters: id and input" ) return None diff --git a/client-python/pycti/entities/opencti_vulnerability.py b/client-python/pycti/entities/opencti_vulnerability.py index ba3dc99ae915..86d3b0430bc4 100644 --- a/client-python/pycti/entities/opencti_vulnerability.py +++ b/client-python/pycti/entities/opencti_vulnerability.py @@ -12,9 +12,15 @@ class Vulnerability: Manages vulnerability information including CVE data in the OpenCTI platform. :param opencti: instance of :py:class:`~pycti.api.opencti_api_client.OpenCTIApiClient` + :type opencti: OpenCTIApiClient """ def __init__(self, opencti): + """Initialize the Vulnerability instance. + + :param opencti: OpenCTI API client instance + :type opencti: OpenCTIApiClient + """ self.opencti = opencti self.properties = """ id @@ -62,6 +68,11 @@ def __init__(self, opencti): x_opencti_lastname } } + objectOrganization { + id + standard_id + name + } objectMarking { id standard_id @@ -179,6 +190,13 @@ def __init__(self, opencti): @staticmethod def generate_id(name): + """Generate a STIX ID for a Vulnerability. + + :param name: The name of the vulnerability (e.g., CVE ID) + :type name: str + :return: STIX ID for the vulnerability + :rtype: str + """ name = name.lower().strip() data = {"name": name} data = canonicalize(data, utf8=False) @@ -187,19 +205,39 @@ def generate_id(name): @staticmethod def generate_id_from_data(data): + """Generate a STIX ID from vulnerability data. + + :param data: Dictionary containing 'name' key + :type data: dict + :return: STIX ID for the vulnerability + :rtype: str + """ return Vulnerability.generate_id(data["name"]) - """ - List Vulnerability objects + def list(self, **kwargs): + """List Vulnerability objects. :param filters: the filters to apply + :type filters: dict :param search: the search keyword + :type search: str :param first: return the first n rows from the after ID (or the beginning if not set) + :type first: int :param after: ID of the first row for pagination - :return List of Vulnerability objects - """ - - def list(self, **kwargs): + :type after: str + :param orderBy: field to order results by + :type orderBy: str + :param orderMode: ordering mode (asc/desc) + :type orderMode: str + :param customAttributes: custom attributes to return + :type customAttributes: str + :param getAll: whether to retrieve all results + :type getAll: bool + :param withPagination: whether to include pagination info + :type withPagination: bool + :return: List of Vulnerability objects + :rtype: list + """ filters = kwargs.get("filters", None) search = kwargs.get("search", None) first = kwargs.get("first", 100) @@ -253,7 +291,7 @@ def list(self, **kwargs): final_data = final_data + data while result["data"]["vulnerabilities"]["pageInfo"]["hasNextPage"]: after = result["data"]["vulnerabilities"]["pageInfo"]["endCursor"] - self.opencti.app_logger.info( + self.opencti.app_logger.debug( "Listing Vulnerabilities", {"after": after} ) result = self.opencti.query( @@ -275,15 +313,18 @@ def list(self, **kwargs): result["data"]["vulnerabilities"], with_pagination ) - """ - Read a Vulnerability object + def read(self, **kwargs): + """Read a Vulnerability object. :param id: the id of the Vulnerability + :type id: str :param filters: the filters to apply if no id provided - :return Vulnerability object - """ - - def read(self, **kwargs): + :type filters: dict + :param customAttributes: custom attributes to return + :type customAttributes: str + :return: Vulnerability object + :rtype: dict or None + """ id = kwargs.get("id", None) filters = kwargs.get("filters", None) custom_attributes = kwargs.get("customAttributes", None) @@ -314,18 +355,74 @@ def read(self, **kwargs): return None else: self.opencti.app_logger.error( - "[opencti_tool] Missing parameters: id or filters" + "[opencti_vulnerability] Missing parameters: id or filters" ) return None - """ - Create a Vulnerability object - - :param name: the name of the Vulnerability - :return Vulnerability object - """ - def create(self, **kwargs): + """Create a Vulnerability object. + + :param name: the name of the Vulnerability (required) + :type name: str + :param stix_id: (optional) the STIX ID + :type stix_id: str + :param createdBy: (optional) the author ID + :type createdBy: str + :param objectMarking: (optional) list of marking definition IDs + :type objectMarking: list + :param objectLabel: (optional) list of label IDs + :type objectLabel: list + :param externalReferences: (optional) list of external reference IDs + :type externalReferences: list + :param revoked: (optional) whether the vulnerability is revoked + :type revoked: bool + :param confidence: (optional) confidence level (0-100) + :type confidence: int + :param lang: (optional) language + :type lang: str + :param created: (optional) creation date + :type created: str + :param modified: (optional) modification date + :type modified: str + :param description: (optional) description + :type description: str + :param x_opencti_aliases: (optional) list of aliases + :type x_opencti_aliases: list + :param x_opencti_cvss_vector_string: (optional) CVSS v3 vector string + :type x_opencti_cvss_vector_string: str + :param x_opencti_cvss_base_score: (optional) CVSS v3 base score + :type x_opencti_cvss_base_score: float + :param x_opencti_cvss_base_severity: (optional) CVSS v3 base severity + :type x_opencti_cvss_base_severity: str + :param x_opencti_cwe: (optional) CWE ID + :type x_opencti_cwe: str + :param x_opencti_cisa_kev: (optional) CISA KEV flag + :type x_opencti_cisa_kev: bool + :param x_opencti_epss_score: (optional) EPSS score + :type x_opencti_epss_score: float + :param x_opencti_epss_percentile: (optional) EPSS percentile + :type x_opencti_epss_percentile: float + :param x_opencti_score: (optional) OpenCTI score + :type x_opencti_score: int + :param x_opencti_first_seen_active: (optional) first seen active date + :type x_opencti_first_seen_active: str + :param x_opencti_stix_ids: (optional) list of additional STIX IDs + :type x_opencti_stix_ids: list + :param objectOrganization: (optional) list of organization IDs + :type objectOrganization: list + :param x_opencti_workflow_id: (optional) workflow ID + :type x_opencti_workflow_id: str + :param x_opencti_modified_at: (optional) custom modification date + :type x_opencti_modified_at: str + :param update: (optional) whether to update if exists (default: False) + :type update: bool + :param file: (optional) File object to attach + :type file: dict + :param fileMarkings: (optional) list of marking definition IDs for the file + :type fileMarkings: list + :return: Vulnerability object + :rtype: dict or None + """ stix_id = kwargs.get("stix_id", None) created_by = kwargs.get("createdBy", None) object_marking = kwargs.get("objectMarking", None) @@ -466,6 +563,8 @@ def create(self, **kwargs): x_opencti_workflow_id = kwargs.get("x_opencti_workflow_id", None) x_opencti_modified_at = kwargs.get("x_opencti_modified_at", None) update = kwargs.get("update", False) + file = kwargs.get("file", None) + file_markings = kwargs.get("fileMarkings", None) if name is not None: self.opencti.app_logger.info("Creating Vulnerability", {"name": name}) @@ -553,6 +652,8 @@ def create(self, **kwargs): "x_opencti_workflow_id": x_opencti_workflow_id, "x_opencti_modified_at": x_opencti_modified_at, "update": update, + "file": file, + "fileMarkings": file_markings, } }, ) @@ -561,22 +662,40 @@ def create(self, **kwargs): ) else: self.opencti.app_logger.error( - "[opencti_vulnerability] Missing parameters: name and description" + "[opencti_vulnerability] Missing parameters: name" ) - - """ - Import an Vulnerability object from a STIX2 object - - :param stixObject: the Stix-Object Vulnerability - :return Vulnerability object - """ + return None def import_from_stix2(self, **kwargs): + """Import a Vulnerability object from a STIX2 object. + + :param stixObject: the STIX2 Vulnerability object + :type stixObject: dict + :param extras: extra parameters including created_by_id, object_marking_ids, etc. + :type extras: dict + :param update: whether to update if the entity already exists + :type update: bool + :return: Vulnerability object + :rtype: dict or None + """ stix_object = kwargs.get("stixObject", None) extras = kwargs.get("extras", {}) update = kwargs.get("update", False) if stix_object is not None: + # Search in extensions + if "x_opencti_stix_ids" not in stix_object: + stix_object["x_opencti_stix_ids"] = ( + self.opencti.get_attribute_in_extension("stix_ids", stix_object) + ) + if "x_opencti_granted_refs" not in stix_object: + stix_object["x_opencti_granted_refs"] = ( + self.opencti.get_attribute_in_extension("granted_refs", stix_object) + ) + if "x_opencti_workflow_id" not in stix_object: + stix_object["x_opencti_workflow_id"] = ( + self.opencti.get_attribute_in_extension("workflow_id", stix_object) + ) # Backward compatibility if "x_opencti_base_score" in stix_object: stix_object["x_opencti_cvss_base_score"] = stix_object[ @@ -849,6 +968,12 @@ def import_from_stix2(self, **kwargs): "cvss_v4_availability_impact_v", stix_object ) ) + if "x_opencti_cvss_v4_availability_impact_s" not in stix_object: + stix_object["x_opencti_cvss_v4_availability_impact_s"] = ( + self.opencti.get_attribute_in_extension( + "cvss_v4_availability_impact_s", stix_object + ) + ) if "x_opencti_cvss_v4_exploit_maturity" not in stix_object: stix_object["x_opencti_cvss_v4_exploit_maturity"] = ( self.opencti.get_attribute_in_extension( @@ -1191,8 +1316,11 @@ def import_from_stix2(self, **kwargs): else None ), update=update, + file=extras.get("file"), + fileMarkings=extras.get("fileMarkings"), ) else: self.opencti.app_logger.error( "[opencti_vulnerability] Missing parameters: stixObject" ) + return None diff --git a/client-python/pycti/entities/stix_cyber_observable/opencti_stix_cyber_observable_deprecated.py b/client-python/pycti/entities/stix_cyber_observable/opencti_stix_cyber_observable_deprecated.py index aad2291c2de8..e3a4a30b09eb 100644 --- a/client-python/pycti/entities/stix_cyber_observable/opencti_stix_cyber_observable_deprecated.py +++ b/client-python/pycti/entities/stix_cyber_observable/opencti_stix_cyber_observable_deprecated.py @@ -2,12 +2,9 @@ class StixCyberObservableDeprecatedMixin: - """ - deprecated [>=6.2 & <6.8]` - Promote a Stix-Observable to an Indicator + """Deprecated mixin for Stix-Cyber-Observable promotion [>=6.2 & <6.8]. - :param id: the Stix-Observable id - :return the observable + Contains deprecated method to promote a Stix-Observable to an Indicator. """ @deprecation.deprecated( @@ -16,6 +13,20 @@ class StixCyberObservableDeprecatedMixin: details="Use promote_to_indicator_v2 instead.", ) def promote_to_indicator(self, **kwargs): + """Promote a Stix-Cyber-Observable to an Indicator (deprecated). + + .. deprecated:: 6.2 + Use :meth:`promote_to_indicator_v2` instead. + + :param id: the id of the Stix-Cyber-Observable + :type id: str + :param customAttributes: custom attributes to return + :type customAttributes: str + :param withFiles: whether to include files + :type withFiles: bool + :return: Indicator object + :rtype: dict or None + """ id = kwargs.get("id", None) custom_attributes = kwargs.get("customAttributes", None) with_files = kwargs.get("withFiles", False) diff --git a/client-python/pycti/entities/stix_cyber_observable/opencti_stix_cyber_observable_properties.py b/client-python/pycti/entities/stix_cyber_observable/opencti_stix_cyber_observable_properties.py index 72569b6387f3..1a5fec0bac17 100644 --- a/client-python/pycti/entities/stix_cyber_observable/opencti_stix_cyber_observable_properties.py +++ b/client-python/pycti/entities/stix_cyber_observable/opencti_stix_cyber_observable_properties.py @@ -468,6 +468,10 @@ id name size + metaData { + mimetype + version + } } } } diff --git a/client-python/pycti/utils/constants.py b/client-python/pycti/utils/constants.py index e1f577af2d04..7264ddf24023 100644 --- a/client-python/pycti/utils/constants.py +++ b/client-python/pycti/utils/constants.py @@ -12,7 +12,25 @@ from stix2.utils import NOW -class StixCyberObservableTypes(Enum): +class CaseInsensitiveEnum(Enum): + """Base Enum class with case-insensitive value lookup.""" + + @classmethod + def has_value(cls, value: str) -> bool: + """Check if the enum contains the given value (case-insensitive). + + :param value: Value to check + :type value: str + :return: True if value exists in enum, False otherwise + :rtype: bool + """ + lower_values = [v.lower() for v in cls._value2member_map_] + return value.lower() in lower_values + + +class StixCyberObservableTypes(CaseInsensitiveEnum): + """Enumeration of STIX Cyber Observable types supported by OpenCTI.""" + AUTONOMOUS_SYSTEM = "Autonomous-System" DIRECTORY = "Directory" DOMAIN_NAME = "Domain-Name" @@ -48,49 +66,37 @@ class StixCyberObservableTypes(Enum): PERSONA = "Persona" SSH_KEY = "SSH-Key" - @classmethod - def has_value(cls, value: str) -> bool: - lower_attr = list(map(lambda x: x.lower(), cls._value2member_map_)) - return value.lower() in lower_attr +class IdentityTypes(CaseInsensitiveEnum): + """Enumeration of Identity types supported by OpenCTI.""" -class IdentityTypes(Enum): SECTOR = "Sector" ORGANIZATION = "Organization" INDIVIDUAL = "Individual" SYSTEM = "System" SECURITYPLATFORM = "SecurityPlatform" - @classmethod - def has_value(cls, value): - lower_attr = list(map(lambda x: x.lower(), cls._value2member_map_)) - return value.lower() in lower_attr +class ThreatActorTypes(CaseInsensitiveEnum): + """Enumeration of Threat Actor types supported by OpenCTI.""" -class ThreatActorTypes(Enum): THREAT_ACTOR_GROUP = "Threat-Actor-Group" THREAT_ACTOR_INDIVIDUAL = "Threat-Actor-Individual" - @classmethod - def has_value(cls, value): - lower_attr = list(map(lambda x: x.lower(), cls._value2member_map_)) - return value.lower() in lower_attr +class LocationTypes(CaseInsensitiveEnum): + """Enumeration of Location types supported by OpenCTI.""" -class LocationTypes(Enum): REGION = "Region" COUNTRY = "Country" ADMINISTRATIVE_AREA = "Administrative-Area" CITY = "City" POSITION = "Position" - @classmethod - def has_value(cls, value): - lower_attr = list(map(lambda x: x.lower(), cls._value2member_map_)) - return value.lower() in lower_attr +class ContainerTypes(CaseInsensitiveEnum): + """Enumeration of Container types supported by OpenCTI.""" -class ContainerTypes(Enum): NOTE = "Note" OBSERVED_DATA = "Observed-Data" OPINION = "Opinion" @@ -98,25 +104,19 @@ class ContainerTypes(Enum): GROUPING = "Grouping" CASE = "Case" - @classmethod - def has_value(cls, value): - lower_attr = list(map(lambda x: x.lower(), cls._value2member_map_)) - return value.lower() in lower_attr +class StixMetaTypes(CaseInsensitiveEnum): + """Enumeration of STIX Meta Object types supported by OpenCTI.""" -class StixMetaTypes(Enum): MARKING_DEFINITION = "Marking-Definition" LABEL = "Label" EXTERNAL_REFERENCE = "External-Reference" KILL_CHAIN_PHASE = "Kill-Chain-Phase" - @classmethod - def has_value(cls, value): - lower_attr = list(map(lambda x: x.lower(), cls._value2member_map_)) - return value.lower() in lower_attr +class MultipleRefRelationship(CaseInsensitiveEnum): + """Enumeration of relationship types that can have multiple references.""" -class MultipleRefRelationship(Enum): OPERATING_SYSTEM = "operating-system" SAMPLE = "sample" CONTAINS = "contains" @@ -130,15 +130,10 @@ class MultipleRefRelationship(Enum): CHILD = "child" BODY_MULTIPART = "body-multipart" VALUES = "values" - SERVICE_DDL = "service-dll" + SERVICE_DLL = "service-dll" INSTALLED_SOFTWARE = "installed-software" RELATION_ANALYSIS_SCO = "analysis-sco" - @classmethod - def has_value(cls, value): - lower_attr = list(map(lambda x: x.lower(), cls._value2member_map_)) - return value.lower() in lower_attr - # Custom objects @@ -164,7 +159,32 @@ def has_value(cls, value): ], ) class CustomObjectCaseIncident: - """Case-Incident object.""" + """Custom STIX2 Case-Incident object for OpenCTI. + + Represents a case-incident container with associated metadata including + name, description, severity, priority, and response types. + + :param name: Name of the case incident (required) + :type name: str + :param spec_version: STIX specification version, fixed to "2.1" + :type spec_version: str + :param description: Description of the case incident + :type description: str + :param severity: Severity level of the incident + :type severity: str + :param priority: Priority level of the incident + :type priority: str + :param response_types: List of response types + :type response_types: list + :param x_opencti_workflow_id: OpenCTI workflow identifier + :type x_opencti_workflow_id: str + :param x_opencti_assignee_ids: List of assignee identifiers + :type x_opencti_assignee_ids: list + :param external_references: List of external references + :type external_references: list + :param object_refs: List of referenced STIX objects + :type object_refs: list + """ pass @@ -189,8 +209,33 @@ class CustomObjectCaseIncident: ), ], ) -class CustomObjectCaseRfit: - """Case-Rfi object.""" +class CustomObjectCaseRfi: + """Custom STIX2 Case-RFI (Request For Information) object for OpenCTI. + + Represents a request for information container with associated metadata + including name, description, severity, priority, and information types. + + :param name: Name of the RFI case (required) + :type name: str + :param spec_version: STIX specification version, fixed to "2.1" + :type spec_version: str + :param description: Description of the RFI case + :type description: str + :param severity: Severity level of the RFI + :type severity: str + :param priority: Priority level of the RFI + :type priority: str + :param information_types: List of information types requested + :type information_types: list + :param x_opencti_workflow_id: OpenCTI workflow identifier + :type x_opencti_workflow_id: str + :param x_opencti_assignee_ids: List of assignee identifiers + :type x_opencti_assignee_ids: list + :param external_references: List of external references + :type external_references: list + :param object_refs: List of referenced STIX objects + :type object_refs: list + """ pass @@ -218,7 +263,26 @@ class CustomObjectCaseRfit: ], ) class CustomObjectTask: - """Task object.""" + """Custom STIX2 Task object for OpenCTI. + + Represents a task with associated metadata including name, description, + due date, and assignees. + + :param name: Name of the task (required) + :type name: str + :param spec_version: STIX specification version, fixed to "2.1" + :type spec_version: str + :param description: Description of the task + :type description: str + :param due_date: Due date timestamp for the task + :type due_date: datetime + :param x_opencti_workflow_id: OpenCTI workflow identifier + :type x_opencti_workflow_id: str + :param x_opencti_assignee_ids: List of assignee identifiers + :type x_opencti_assignee_ids: list + :param object_refs: List of referenced STIX objects + :type object_refs: list + """ pass @@ -237,7 +301,28 @@ class CustomObjectTask: ], ) class CustomObjectChannel: - """Channel object.""" + """Custom STIX2 Channel object for OpenCTI. + + Represents a communication channel with associated metadata including + name, description, aliases, and channel types. + + :param name: Name of the channel (required) + :type name: str + :param spec_version: STIX specification version, fixed to "2.1" + :type spec_version: str + :param description: Description of the channel + :type description: str + :param aliases: List of alternative names for the channel + :type aliases: list + :param channel_types: List of channel types + :type channel_types: list + :param x_opencti_workflow_id: OpenCTI workflow identifier + :type x_opencti_workflow_id: str + :param x_opencti_assignee_ids: List of assignee identifiers + :type x_opencti_assignee_ids: list + :param external_references: List of external references + :type external_references: list + """ pass @@ -260,7 +345,17 @@ class CustomObjectChannel: ["value"], ) class CustomObservableHostname: - """Hostname observable.""" + """Custom STIX2 Hostname observable for OpenCTI. + + Represents a hostname cyber observable with its associated value. + + :param value: The hostname value (required) + :type value: str + :param spec_version: STIX specification version, fixed to "2.1" + :type spec_version: str + :param object_marking_refs: List of marking definition references + :type object_marking_refs: list + """ pass @@ -280,7 +375,17 @@ class CustomObservableHostname: ["value"], ) class CustomObservableText: - """Text observable.""" + """Custom STIX2 Text observable for OpenCTI. + + Represents a generic text cyber observable with its associated value. + + :param value: The text value (required) + :type value: str + :param spec_version: STIX specification version, fixed to "2.1" + :type spec_version: str + :param object_marking_refs: List of marking definition references + :type object_marking_refs: list + """ pass @@ -304,7 +409,25 @@ class CustomObservableText: ["card_number"], ) class CustomObservablePaymentCard: - """Payment card observable.""" + """Custom STIX2 Payment Card observable for OpenCTI. + + Represents a payment card cyber observable with card details. + + :param value: Display value for the payment card (required) + :type value: str + :param card_number: The payment card number (required) + :type card_number: str + :param expiration_date: Card expiration date + :type expiration_date: str + :param cvv: Card verification value + :type cvv: str + :param holder_name: Name of the card holder + :type holder_name: str + :param spec_version: STIX specification version, fixed to "2.1" + :type spec_version: str + :param object_marking_refs: List of marking definition references + :type object_marking_refs: list + """ pass @@ -327,7 +450,23 @@ class CustomObservablePaymentCard: ["iban"], ) class CustomObservableBankAccount: - """Bank Account observable.""" + """Custom STIX2 Bank Account observable for OpenCTI. + + Represents a bank account cyber observable with account details. + + :param value: Display value for the bank account (required) + :type value: str + :param iban: International Bank Account Number (required) + :type iban: str + :param bic: Bank Identifier Code + :type bic: str + :param account_number: Bank account number + :type account_number: str + :param spec_version: STIX specification version, fixed to "2.1" + :type spec_version: str + :param object_marking_refs: List of marking definition references + :type object_marking_refs: list + """ pass @@ -347,7 +486,17 @@ class CustomObservableBankAccount: ["value"], ) class CustomObservableCredential: - """Credential observable.""" + """Custom STIX2 Credential observable for OpenCTI. + + Represents a credential cyber observable such as a password or access token. + + :param value: The credential value (required) + :type value: str + :param spec_version: STIX specification version, fixed to "2.1" + :type spec_version: str + :param object_marking_refs: List of marking definition references + :type object_marking_refs: list + """ pass @@ -367,7 +516,17 @@ class CustomObservableCredential: ["value"], ) class CustomObservableCryptocurrencyWallet: - """Cryptocurrency wallet observable.""" + """Custom STIX2 Cryptocurrency Wallet observable for OpenCTI. + + Represents a cryptocurrency wallet address cyber observable. + + :param value: The wallet address value (required) + :type value: str + :param spec_version: STIX specification version, fixed to "2.1" + :type spec_version: str + :param object_marking_refs: List of marking definition references + :type object_marking_refs: list + """ pass @@ -387,7 +546,17 @@ class CustomObservableCryptocurrencyWallet: ["value"], ) class CustomObservablePhoneNumber: - """Phone number observable.""" + """Custom STIX2 Phone Number observable for OpenCTI. + + Represents a phone number cyber observable. + + :param value: The phone number value (required) + :type value: str + :param spec_version: STIX specification version, fixed to "2.1" + :type spec_version: str + :param object_marking_refs: List of marking definition references + :type object_marking_refs: list + """ pass @@ -407,7 +576,17 @@ class CustomObservablePhoneNumber: ["value"], ) class CustomObservableTrackingNumber: - """Tracking number observable.""" + """Custom STIX2 Tracking Number observable for OpenCTI. + + Represents a tracking number cyber observable (e.g., package tracking). + + :param value: The tracking number value (required) + :type value: str + :param spec_version: STIX specification version, fixed to "2.1" + :type spec_version: str + :param object_marking_refs: List of marking definition references + :type object_marking_refs: list + """ pass @@ -427,7 +606,17 @@ class CustomObservableTrackingNumber: ["value"], ) class CustomObservableUserAgent: - """User-Agent observable.""" + """Custom STIX2 User-Agent observable for OpenCTI. + + Represents a User-Agent string cyber observable from HTTP headers. + + :param value: The User-Agent string value (required) + :type value: str + :param spec_version: STIX specification version, fixed to "2.1" + :type spec_version: str + :param object_marking_refs: List of marking definition references + :type object_marking_refs: list + """ pass @@ -452,7 +641,27 @@ class CustomObservableUserAgent: ["url"], ) class CustomObservableMediaContent: - """Media-Content observable.""" + """Custom STIX2 Media-Content observable for OpenCTI. + + Represents a media content cyber observable such as articles or posts. + + :param title: Title of the media content + :type title: str + :param description: Description of the media content + :type description: str + :param content: The actual content body + :type content: str + :param media_category: Category of the media + :type media_category: str + :param url: URL of the media content (required) + :type url: str + :param publication_date: Publication date timestamp + :type publication_date: datetime + :param spec_version: STIX specification version, fixed to "2.1" + :type spec_version: str + :param object_marking_refs: List of marking definition references + :type object_marking_refs: list + """ pass @@ -473,7 +682,19 @@ class CustomObservableMediaContent: ["persona_name", "persona_type"], ) class CustomObservablePersona: - """Persona observable.""" + """Custom STIX2 Persona observable for OpenCTI. + + Represents a persona or online identity cyber observable. + + :param persona_name: Name of the persona (required) + :type persona_name: str + :param persona_type: Type of the persona (required) + :type persona_type: str + :param spec_version: STIX specification version, fixed to "2.1" + :type spec_version: str + :param object_marking_refs: List of marking definition references + :type object_marking_refs: list + """ pass @@ -493,6 +714,46 @@ class CustomObservablePersona: ["value"], ) class CustomObservableCryptographicKey: - """Cryptographic-Key observable.""" + """Custom STIX2 Cryptographic-Key observable for OpenCTI. + + Represents a cryptographic key cyber observable such as API keys or encryption keys. + + :param value: The cryptographic key value (required) + :type value: str + :param spec_version: STIX specification version, fixed to "2.1" + :type spec_version: str + :param object_marking_refs: List of marking definition references + :type object_marking_refs: list + """ + + pass + + +@CustomObservable( + "ssh-key", + [ + ("value", StringProperty(required=True)), + ("spec_version", StringProperty(fixed="2.1")), + ( + "object_marking_refs", + ListProperty( + ReferenceProperty(valid_types="marking-definition", spec_version="2.1") + ), + ), + ], + ["value"], +) +class CustomObservableSshKey: + """Custom STIX2 SSH-Key observable for OpenCTI. + + Represents an SSH key cyber observable such as public or private SSH keys. + + :param value: The SSH key value (required) + :type value: str + :param spec_version: STIX specification version, fixed to "2.1" + :type spec_version: str + :param object_marking_refs: List of marking definition references + :type object_marking_refs: list + """ pass diff --git a/client-python/pycti/utils/opencti_logger.py b/client-python/pycti/utils/opencti_logger.py index e9512ca7b898..0a797395cd6a 100644 --- a/client-python/pycti/utils/opencti_logger.py +++ b/client-python/pycti/utils/opencti_logger.py @@ -14,7 +14,7 @@ def add_fields(self, log_record, record, message_dict): :param record: The LogRecord instance :param message_dict: The message dictionary """ - super(CustomJsonFormatter, self).add_fields(log_record, record, message_dict) + super().add_fields(log_record, record, message_dict) if not log_record.get("timestamp"): # This doesn't use record.created, so it is slightly off now = datetime.now(tz=timezone.utc) @@ -29,15 +29,15 @@ def logger(level, json_logging=True): """Create a logger with JSON or standard formatting. :param level: Logging level (e.g., logging.INFO, logging.DEBUG) + :type level: int :param json_logging: Whether to use JSON formatting for logs :type json_logging: bool :return: AppLogger class :rtype: class """ - # Exceptions + # Suppress verbose logging from third-party libraries logging.getLogger("urllib3").setLevel(logging.WARNING) logging.getLogger("pika").setLevel(logging.ERROR) - # Exceptions if json_logging: log_handler = logging.StreamHandler() log_handler.setLevel(level) @@ -48,7 +48,18 @@ def logger(level, json_logging=True): logging.basicConfig(level=level) class AppLogger: + """Application logger class with metadata support. + + Provides debug, info, warning, and error logging methods with + optional metadata support for structured logging. + """ + def __init__(self, name): + """Initialize the application logger. + + :param name: Name of the logger instance + :type name: str + """ self.local_logger = logging.getLogger(name) @staticmethod @@ -69,6 +80,7 @@ def setup_logger_level(lib, log_level): :param lib: Library name :type lib: str :param log_level: Logging level to set + :type log_level: int """ logging.getLogger(lib).setLevel(log_level) diff --git a/client-python/pycti/utils/opencti_stix2.py b/client-python/pycti/utils/opencti_stix2.py index c32ef30c282f..e6de7fc7a203 100644 --- a/client-python/pycti/utils/opencti_stix2.py +++ b/client-python/pycti/utils/opencti_stix2.py @@ -1,5 +1,3 @@ -# coding: utf-8 - import base64 import datetime import json @@ -16,6 +14,7 @@ from cachetools import LRUCache from opentelemetry import metrics from requests import RequestException, Timeout +from typing_extensions import deprecated from pycti.entities.opencti_identity import Identity from pycti.utils.constants import ( @@ -38,6 +37,10 @@ datefinder.ValueError = ValueError, OverflowError utc = pytz.UTC +# For Python 3.11+, datetime.UTC is preferred over datetime.timezone.utc +# Fallback to datetime.timezone.utc for older Python versions +UTC = getattr(datetime, "UTC", datetime.timezone.utc) + # Spec version SPEC_VERSION = "2.1" ERROR_TYPE_LOCK = "LOCK_ERROR" @@ -85,24 +88,47 @@ class OpenCTIStix2: - """Python API for Stix2 in OpenCTI + """Python API for Stix2 in OpenCTI. - :param opencti: OpenCTI instance + Handles conversion between STIX2 format and OpenCTI internal format, + including import/export operations and bundle processing. + + :param opencti: OpenCTI API client instance + :type opencti: OpenCTIApiClient """ def __init__(self, opencti): + """Initialize the OpenCTIStix2 helper. + + :param opencti: OpenCTI API client instance + :type opencti: OpenCTIApiClient + """ self.opencti = opencti self.stix2_update = OpenCTIStix2Update(opencti) self.mapping_cache = LRUCache(maxsize=50000) self.mapping_cache_permanent = {} def get_in_cache(self, data_id): + """Get an item from the cache. + + :param data_id: ID of the data to retrieve + :type data_id: str + :return: Cached data or None if not found + :rtype: dict or None + """ api_draft_id = self.opencti.get_draft_id() if data_id + api_draft_id in self.mapping_cache: return self.mapping_cache[data_id + api_draft_id] return None def set_in_cache(self, data_id, data): + """Store an item in the cache. + + :param data_id: ID of the data to store + :type data_id: str + :param data: Data to cache + :type data: dict + """ api_draft_id = self.opencti.get_draft_id() self.mapping_cache[data_id + api_draft_id] = data @@ -119,11 +145,11 @@ def unknown_type(self, stix_object: Dict) -> None: ) def convert_markdown(self, text: str) -> str: - """converts input text to markdown style code annotation + """Convert input text to markdown style code annotation. - :param text: input text + :param text: Input text to convert :type text: str - :return: sanitized text with markdown style code annotation + :return: Sanitized text with markdown style code annotation :rtype: str """ if text is not None: @@ -132,20 +158,13 @@ def convert_markdown(self, text: str) -> str: return None def format_date(self, date: Any = None) -> str: - """Format a date to ISO 8601 string format. + """Convert multiple input date formats to OpenCTI style dates. - :param date: Date to format (various formats supported) + :param date: Input date (datetime, date, str or None) :type date: Any :return: ISO 8601 formatted date string :rtype: str """ - """converts multiple input date formats to OpenCTI style dates - - :param date: input date - :type date: Any [datetime, date, str or none] - :return: OpenCTI style date - :rtype: string - """ if isinstance(date, datetime.datetime): date_value = date elif isinstance(date, datetime.date): @@ -156,22 +175,22 @@ def format_date(self, date: Any = None) -> str: except (dateutil.parser.ParserError, TypeError, OverflowError) as e: raise ValueError(f"{e}: {date} does not contain a valid date string") else: - date_value = datetime.datetime.utcnow() + date_value = datetime.datetime.now(tz=UTC) if not date_value.tzinfo: self.opencti.app_logger.info("No timezone found. Setting to UTC") - date_value = date_value.replace(tzinfo=datetime.timezone.utc) + date_value = date_value.replace(tzinfo=UTC) return date_value.isoformat(timespec="milliseconds").replace("+00:00", "Z") def filter_objects(self, uuids: List, objects: List) -> List: - """filters objects based on UUIDs + """Filter objects based on UUIDs. - :param uuids: list of UUIDs + :param uuids: List of UUIDs to filter by :type uuids: list - :param objects: list of objects to filter + :param objects: List of objects to filter :type objects: list - :return: list of filtered objects + :return: List of filtered objects not in the uuids list :rtype: list """ @@ -183,12 +202,12 @@ def filter_objects(self, uuids: List, objects: List) -> List: return result def pick_aliases(self, stix_object: Dict) -> Optional[List]: - """check stix2 object for multiple aliases and return a list + """Check STIX2 object for multiple aliases and return a list. - :param stix_object: valid stix2 object - :type stix_object: - :return: list of aliases - :rtype: list + :param stix_object: Valid STIX2 object + :type stix_object: Dict + :return: List of aliases or None if no aliases found + :rtype: list or None """ # Add aliases @@ -208,19 +227,19 @@ def import_bundle_from_file( update: bool = False, types: List = None, ) -> Optional[Tuple[list, list]]: - """import a stix2 bundle from a file + """Import a STIX2 bundle from a file. - :param file_path: valid path to the file + :param file_path: Valid path to the file :type file_path: str - :param update: whether to updated data in the database, defaults to False + :param update: Whether to update data in the database, defaults to False :type update: bool, optional - :param types: list of stix2 types, defaults to None + :param types: List of STIX2 types to filter, defaults to None :type types: list, optional - :return: list of imported stix2 objects - :rtype: List + :return: Tuple of (imported objects, failed objects) or None if file not found + :rtype: Tuple[list, list] or None """ if not os.path.isfile(file_path): - self.opencti.app_logger.error("The bundle file does not exists") + self.opencti.app_logger.error("The bundle file does not exist") return None with open(os.path.join(file_path), encoding="utf-8") as file: data = json.load(file) @@ -234,24 +253,32 @@ def import_bundle_from_json( work_id: str = None, objects_max_refs: int = 0, ) -> Tuple[list, list]: - """import a stix2 bundle from JSON data + """Import a STIX2 bundle from JSON data. - :param json_data: JSON data - :type json_data: - :param update: whether to updated data in the database, defaults to False + :param json_data: JSON data as string or bytes + :type json_data: str or bytes + :param update: Whether to update data in the database, defaults to False :type update: bool, optional - :param types: list of stix2 types, defaults to None + :param types: List of STIX2 types to filter, defaults to None :type types: list, optional - :param work_id work_id: str, optional - :param objects_max_refs: max deps amount of objects, reject object import if larger than configured amount + :param work_id: Work ID for tracking import progress + :type work_id: str, optional + :param objects_max_refs: Maximum object references; rejects import if exceeded :type objects_max_refs: int, optional - :return: list of imported stix2 objects and a list of stix2 objects with too many deps - :rtype: Tuple[List,List] + :return: Tuple of (imported objects, objects with too many dependencies) + :rtype: Tuple[list, list] """ data = json.loads(json_data) return self.import_bundle(data, update, types, work_id, objects_max_refs) def resolve_author(self, title: str) -> Optional[Identity]: + """Resolve an author identity from a title string. + + :param title: Title to search for known author names + :type title: str + :return: Identity object if author found, None otherwise + :rtype: Identity or None + """ if "fireeye" in title.lower() or "mandiant" in title.lower(): return self.get_author("FireEye") if "eset" in title.lower(): @@ -293,6 +320,13 @@ def resolve_author(self, title: str) -> Optional[Identity]: return None def get_author(self, name: str) -> Identity: + """Get or create an author identity by name. + + :param name: Name of the author organization + :type name: str + :return: Identity object for the author + :rtype: Identity + """ name_in_cache = self.get_in_cache(name) if name_in_cache is not None: return name_in_cache @@ -308,13 +342,13 @@ def get_author(self, name: str) -> Identity: def extract_embedded_relationships( self, stix_object: Dict, types: List = None ) -> Dict: - """extracts embedded relationship objects from a stix2 entity + """Extract embedded relationship objects from a STIX2 entity. - :param stix_object: valid stix2 object - :type stix_object: - :param types: list of stix2 types, defaults to None + :param stix_object: Valid STIX2 object + :type stix_object: Dict + :param types: List of STIX2 types to filter, defaults to None :type types: list, optional - :return: embedded relationships as dict + :return: Dictionary containing embedded relationships and references :rtype: dict """ @@ -589,6 +623,50 @@ def extract_embedded_relationships( if generated_ref_id is None: continue else: + # Collect files for external reference + ext_ref_files = [] + if "x_opencti_files" in external_reference: + ext_ref_files.extend(external_reference["x_opencti_files"]) + if ( + self.opencti.get_attribute_in_extension( + "files", external_reference + ) + is not None + ): + ext_ref_files.extend( + self.opencti.get_attribute_in_extension( + "files", external_reference + ) + ) + + # Prepare first file for upload during creation + file_to_upload = None + file_markings = None + if len(ext_ref_files) > 0: + first_file = ext_ref_files[0] + data = None + if "data" in first_file: + data = base64.b64decode(first_file["data"]) + elif "uri" in first_file: + file_url = self.opencti.api_url.replace( + "/graphql", first_file["uri"] + ) + data = self.opencti.fetch_opencti_file( + fetch_uri=file_url, binary=True, serialize=False + ) + if data is not None: + file_to_upload = self.opencti.file( + first_file["name"], + data, + first_file.get( + "mime_type", "application/octet-stream" + ), + ) + file_markings = first_file.get( + "object_marking_refs", None + ) + + # Create external reference with first file attached external_reference_id = self.opencti.external_reference.create( source_name=source_name, url=url, @@ -598,18 +676,22 @@ def extract_embedded_relationships( if "description" in external_reference else None ), + file=file_to_upload, + fileMarkings=file_markings, )["id"] - if "x_opencti_files" in external_reference: - for file in external_reference["x_opencti_files"]: + + # Upload additional files after creation (first file attached during creation) + if len(ext_ref_files) > 1: + for file in ext_ref_files[1:]: data = None if "data" in file: data = base64.b64decode(file["data"]) elif "uri" in file: - url = self.opencti.api_url.replace( + file_url = self.opencti.api_url.replace( "/graphql", file["uri"] ) data = self.opencti.fetch_opencti_file( - fetch_uri=url, binary=True, serialize=False + fetch_uri=file_url, binary=True, serialize=False ) if data is not None: self.opencti.external_reference.add_file( @@ -618,38 +700,9 @@ def extract_embedded_relationships( version=file.get("version", None), data=data, fileMarkings=file.get("object_marking_refs", None), - mime_type=file["mime_type"], - no_trigger_import=file.get( - "no_trigger_import", False + mime_type=file.get( + "mime_type", "application/octet-stream" ), - ) - if ( - self.opencti.get_attribute_in_extension( - "files", external_reference - ) - is not None - ): - for file in self.opencti.get_attribute_in_extension( - "files", external_reference - ): - data = None - if "data" in file: - data = base64.b64decode(file["data"]) - elif "uri" in file: - url = self.opencti.api_url.replace( - "/graphql", file["uri"] - ) - data = self.opencti.fetch_opencti_file( - fetch_uri=url, binary=True, serialize=False - ) - if data is not None: - self.opencti.external_reference.add_file( - id=external_reference_id, - file_name=file["name"], - version=file.get("version", None), - data=data, - fileMarkings=file.get("object_marking_refs", None), - mime_type=file["mime_type"], no_trigger_import=file.get( "no_trigger_import", False ), @@ -678,7 +731,7 @@ def extract_embedded_relationships( source_name, base_date=datetime.datetime.fromtimestamp(0), ) - except: + except (TypeError, OverflowError): matches = None published = None yesterday = datetime.datetime.now() - datetime.timedelta(days=1) @@ -692,7 +745,7 @@ def extract_embedded_relationships( ): published = match.strftime("%Y-%m-%dT%H:%M:%SZ") break - except: + except (TypeError, OverflowError): pass if published is None: published = default_date.strftime("%Y-%m-%dT%H:%M:%SZ") @@ -761,7 +814,7 @@ def extract_embedded_relationships( update=True, ) reports[external_reference_id] = report - except: + except Exception: self.opencti.app_logger.warning( "Cannot generate external reference" ) @@ -787,6 +840,42 @@ def extract_embedded_relationships( if generated_ref_id is None: continue else: + # Prepare file for direct upload during creation + file_to_upload = None + file_markings = None + all_files = [] + if "x_opencti_files" in external_reference: + all_files = external_reference["x_opencti_files"] + elif ( + self.opencti.get_attribute_in_extension( + "files", external_reference + ) + is not None + ): + all_files = self.opencti.get_attribute_in_extension( + "files", external_reference + ) + + if len(all_files) > 0: + file = all_files[0] + data = None + if "data" in file: + data = base64.b64decode(file["data"]) + elif "uri" in file: + file_url = self.opencti.api_url.replace( + "/graphql", file["uri"] + ) + data = self.opencti.fetch_opencti_file( + fetch_uri=file_url, binary=True, serialize=False + ) + if data is not None: + file_to_upload = self.opencti.file( + file["name"], + data, + file.get("mime_type", "application/octet-stream"), + ) + file_markings = file.get("object_marking_refs", None) + external_reference_id = self.opencti.external_reference.create( source_name=source_name, url=url, @@ -796,42 +885,21 @@ def extract_embedded_relationships( if "description" in external_reference else None ), + file=file_to_upload, + fileMarkings=file_markings, )["id"] - if "x_opencti_files" in external_reference: - for file in external_reference["x_opencti_files"]: + + # Upload additional files (beyond the first one) + for file in all_files[1:]: data = None if "data" in file: data = base64.b64decode(file["data"]) elif "uri" in file: - url = self.opencti.api_url.replace("/graphql", file["uri"]) - data = self.opencti.fetch_opencti_file( - fetch_uri=url, binary=True, serialize=False - ) - if data is not None: - self.opencti.external_reference.add_file( - id=external_reference_id, - file_name=file["name"], - version=file.get("version", None), - data=data, - fileMarkings=file.get("object_marking_refs", None), - mime_type=file["mime_type"], - no_trigger_import=file.get("no_trigger_import", False), - embedded=file.get("embedded", False), + file_url = self.opencti.api_url.replace( + "/graphql", file["uri"] ) - if ( - self.opencti.get_attribute_in_extension("files", external_reference) - is not None - ): - for file in self.opencti.get_attribute_in_extension( - "files", external_reference - ): - data = None - if "data" in file: - data = base64.b64decode(file["data"]) - elif "uri" in file: - url = self.opencti.api_url.replace("/graphql", file["uri"]) data = self.opencti.fetch_opencti_file( - fetch_uri=url, binary=True, serialize=False + fetch_uri=file_url, binary=True, serialize=False ) if data is not None: self.opencti.external_reference.add_file( @@ -840,7 +908,9 @@ def extract_embedded_relationships( version=file.get("version", None), data=data, fileMarkings=file.get("object_marking_refs", None), - mime_type=file["mime_type"], + mime_type=file.get( + "mime_type", "application/octet-stream" + ), no_trigger_import=file.get("no_trigger_import", False), embedded=file.get("embedded", False), ) @@ -984,6 +1054,7 @@ def get_stix_helper(self): "tool": self.opencti.tool, "vulnerability": self.opencti.vulnerability, "incident": self.opencti.incident, + "x-opencti-incident": self.opencti.incident, "marking-definition": self.opencti.marking_definition, "case-rfi": self.opencti.case_rfi, "x-opencti-case-rfi": self.opencti.case_rfi, @@ -1052,16 +1123,16 @@ def generate_standard_id_from_stix(self, data): def import_object( self, stix_object: Dict, update: bool = False, types: List = None ) -> Optional[List]: - """import a stix2 object + """Import a STIX2 object into OpenCTI. - :param stix_object: valid stix2 object - :type stix_object: - :param update: whether to updated data in the database, defaults to False + :param stix_object: Valid STIX2 object to import + :type stix_object: Dict + :param update: Whether to update data in the database, defaults to False :type update: bool, optional - :param types: list of stix2 types, defaults to None + :param types: List of STIX2 types to filter, defaults to None :type types: list, optional - :return: list of imported stix2 objects - :rtype: list + :return: List of imported STIX2 objects or None on failure + :rtype: list or None """ self.opencti.app_logger.info( @@ -1081,6 +1152,36 @@ def import_object( reports = embedded_relationships["reports"] sample_refs_ids = embedded_relationships["sample_refs"] + # Extract files + x_opencti_files = [] + if "x_opencti_files" in stix_object: + x_opencti_files.extend(stix_object["x_opencti_files"]) + if self.opencti.get_attribute_in_extension("files", stix_object) is not None: + x_opencti_files.extend( + self.opencti.get_attribute_in_extension("files", stix_object) + ) + + # Prepare first file for direct upload during creation + file_to_upload = None + file_markings = None + if len(x_opencti_files) > 0: + first_file = x_opencti_files[0] + data = None + if "data" in first_file: + data = base64.b64decode(first_file["data"]) + elif "uri" in first_file: + url = self.opencti.api_url.replace("/graphql", first_file["uri"]) + data = self.opencti.fetch_opencti_file( + fetch_uri=url, binary=True, serialize=False + ) + if data is not None: + file_to_upload = self.opencti.file( + first_file["name"], + data, + first_file.get("mime_type", "application/octet-stream"), + ) + file_markings = first_file.get("object_marking_refs", None) + # Extra extras = { "created_by_id": created_by_id, @@ -1092,6 +1193,8 @@ def import_object( "external_references_ids": external_references_ids, "reports": reports, "sample_ids": sample_refs_ids, + "file": file_to_upload, + "fileMarkings": file_markings, } stix_helper = self.get_stix_helper().get(stix_object["type"]) @@ -1143,9 +1246,10 @@ def import_object( id=reports[external_reference_id]["id"], stixObjectOrStixRelationshipId=stix_object_result["id"], ) - # Add files - if "x_opencti_files" in stix_object: - for file in stix_object["x_opencti_files"]: + # Add additional files (first file is attached during creation) + # Upload remaining files after entity creation + if x_opencti_files is not None and len(x_opencti_files) > 1: + for file in x_opencti_files[1:]: data = None if "data" in file: data = base64.b64decode(file["data"]) @@ -1161,33 +1265,7 @@ def import_object( version=file.get("version", None), data=data, fileMarkings=file.get("object_marking_refs", None), - mime_type=file["mime_type"], - no_trigger_import=file.get("no_trigger_import", False), - embedded=file.get("embedded", False), - ) - if ( - self.opencti.get_attribute_in_extension("files", stix_object) - is not None - ): - for file in self.opencti.get_attribute_in_extension( - "files", stix_object - ): - data = None - if "data" in file: - data = base64.b64decode(file["data"]) - elif "uri" in file: - url = self.opencti.api_url.replace("/graphql", file["uri"]) - data = self.opencti.fetch_opencti_file( - fetch_uri=url, binary=True, serialize=False - ) - if data is not None: - self.opencti.stix_domain_object.add_file( - id=stix_object_result["id"], - file_name=file["name"], - version=file.get("version", None), - data=data, - fileMarkings=file.get("object_marking_refs", None), - mime_type=file["mime_type"], + mime_type=file.get("mime_type", "application/octet-stream"), no_trigger_import=file.get("no_trigger_import", False), embedded=file.get("embedded", False), ) @@ -1196,6 +1274,15 @@ def import_object( def import_observable( self, stix_object: Dict, update: bool = False, types: List = None ) -> None: + """Import a STIX cyber observable into OpenCTI. + + :param stix_object: Valid STIX2 cyber observable object + :type stix_object: Dict + :param update: Whether to update existing data in the database, defaults to False + :type update: bool, optional + :param types: List of STIX2 types to filter, defaults to None + :type types: list, optional + """ # Extract embedded_relationships = self.extract_embedded_relationships(stix_object, types) created_by_id = embedded_relationships["created_by"] @@ -1209,6 +1296,36 @@ def import_observable( reports = embedded_relationships["reports"] sample_refs_ids = embedded_relationships["sample_refs"] + # Extract files + x_opencti_files = [] + if "x_opencti_files" in stix_object: + x_opencti_files.extend(stix_object["x_opencti_files"]) + if self.opencti.get_attribute_in_extension("files", stix_object) is not None: + x_opencti_files.extend( + self.opencti.get_attribute_in_extension("files", stix_object) + ) + + # Prepare first file for direct upload during creation (all observable types support files) + file_to_upload = None + file_markings = None + if len(x_opencti_files) > 0: + first_file = x_opencti_files[0] + data = None + if "data" in first_file: + data = base64.b64decode(first_file["data"]) + elif "uri" in first_file: + url = self.opencti.api_url.replace("/graphql", first_file["uri"]) + data = self.opencti.fetch_opencti_file( + fetch_uri=url, binary=True, serialize=False + ) + if data is not None: + file_to_upload = self.opencti.file( + first_file["name"], + data, + first_file.get("mime_type", "application/octet-stream"), + ) + file_markings = first_file.get("object_marking_refs", None) + # Extra extras = { "created_by_id": created_by_id, @@ -1221,6 +1338,8 @@ def import_observable( "external_references_ids": external_references_ids, "reports": reports, "sample_ids": sample_refs_ids, + "file": file_to_upload, + "fileMarkings": file_markings, } if stix_object["type"] == "simple-observable": stix_observable_result = self.opencti.stix_cyber_observable.create( @@ -1264,6 +1383,8 @@ def import_observable( extras["granted_refs_ids"] if "granted_refs_ids" in extras else [] ), update=update, + file=file_to_upload, + fileMarkings=file_markings, ) else: stix_observable_result = self.opencti.stix_cyber_observable.create( @@ -1288,37 +1409,18 @@ def import_observable( extras["granted_refs_ids"] if "granted_refs_ids" in extras else [] ), update=update, + file=file_to_upload, + fileMarkings=file_markings, ) if stix_observable_result is not None: - # Add files - if "x_opencti_files" in stix_object: - for file in stix_object["x_opencti_files"]: - data = None - if "data" in file: - data = base64.b64decode(file["data"]) - elif "uri" in file: - url = self.opencti.api_url.replace("/graphql", file["uri"]) - data = self.opencti.fetch_opencti_file( - fetch_uri=url, binary=True, serialize=False - ) - if data is not None: - self.opencti.stix_cyber_observable.add_file( - id=stix_observable_result["id"], - file_name=file["name"], - version=file.get("version", None), - data=data, - fileMarkings=file.get("object_marking_refs", None), - mime_type=file["mime_type"], - no_trigger_import=file.get("no_trigger_import", False), - embedded=file.get("embedded", False), - ) - if ( - self.opencti.get_attribute_in_extension("files", stix_object) - is not None - ): - for file in self.opencti.get_attribute_in_extension( - "files", stix_object - ): + # Upload files after observable creation + # All observable types support file at creation, skip the first file + # and upload additional files after creation + files_to_upload_after = ( + x_opencti_files[1:] if len(x_opencti_files) > 1 else [] + ) + if files_to_upload_after is not None and len(files_to_upload_after) > 0: + for file in files_to_upload_after: data = None if "data" in file: data = base64.b64decode(file["data"]) @@ -1334,7 +1436,7 @@ def import_observable( version=file.get("version", None), data=data, fileMarkings=file.get("object_marking_refs", None), - mime_type=file["mime_type"], + mime_type=file.get("mime_type", "application/octet-stream"), no_trigger_import=file.get("no_trigger_import", False), embedded=file.get("embedded", False), ) @@ -1398,6 +1500,15 @@ def import_observable( def import_relationship( self, stix_relation: Dict, update: bool = False, types: List = None ) -> None: + """Import a STIX core relationship into OpenCTI. + + :param stix_relation: Valid STIX2 relationship object + :type stix_relation: Dict + :param update: Whether to update existing data in the database, defaults to False + :type update: bool, optional + :param types: List of STIX2 types to filter, defaults to None + :type types: list, optional + """ # Extract embedded_relationships = self.extract_embedded_relationships( stix_relation, types @@ -1444,7 +1555,7 @@ def import_relationship( external_reference["source_name"], base_date=datetime.datetime.fromtimestamp(0), ) - except: + except (TypeError, OverflowError): matches = None date = None yesterday = datetime.datetime.now() - datetime.timedelta(days=1) @@ -1457,7 +1568,7 @@ def import_relationship( ): date = match.strftime("%Y-%m-%dT%H:%M:%SZ") break - except: + except (TypeError, OverflowError): date = None stix_relation_result = self.opencti.stix_core_relationship.import_from_stix2( @@ -1498,6 +1609,19 @@ def import_sighting( update: bool = False, types: List = None, ) -> None: + """Import a STIX sighting relationship into OpenCTI. + + :param stix_sighting: Valid STIX2 sighting object + :type stix_sighting: Dict + :param from_id: ID of the source entity (sighting_of_ref) + :type from_id: str + :param to_id: ID of the target entity (where_sighted_ref) + :type to_id: str + :param update: Whether to update existing data in the database, defaults to False + :type update: bool, optional + :param types: List of STIX2 types to filter, defaults to None + :type types: list, optional + """ # Extract embedded_relationships = self.extract_embedded_relationships( stix_sighting, types @@ -1614,6 +1738,15 @@ def import_sighting( # region export def generate_export(self, entity: Dict, no_custom_attributes: bool = False) -> Dict: + """Generate a STIX2 export from an OpenCTI entity. + + :param entity: OpenCTI entity dictionary to export + :type entity: Dict + :param no_custom_attributes: Whether to exclude custom x_opencti attributes, defaults to False + :type no_custom_attributes: bool, optional + :return: STIX2 formatted entity dictionary + :rtype: Dict + """ # Handle model deviation original_entity_type = entity["entity_type"] @@ -1801,8 +1934,8 @@ def generate_export(self, entity: Dict, no_custom_attributes: bool = False) -> D if "hashes" in entity: hashes = entity["hashes"] entity["hashes"] = {} - for hash in hashes: - entity["hashes"][hash["algorithm"]] = hash["hash"] + for hash_item in hashes: + entity["hashes"][hash_item["algorithm"]] = hash_item["hash"] # Final entity["x_opencti_id"] = entity["id"] @@ -1822,8 +1955,17 @@ def generate_export(self, entity: Dict, no_custom_attributes: bool = False) -> D @staticmethod def prepare_id_filters_export( - id: Union[str, List[str]], access_filter: Dict = None + entity_id: Union[str, List[str]], access_filter: Dict = None ) -> Dict: + """Prepare filter configuration for entity ID-based export queries. + + :param entity_id: Single entity ID or list of entity IDs to filter + :type entity_id: Union[str, List[str]] + :param access_filter: Additional access filter to combine, defaults to None + :type access_filter: Dict, optional + :return: Filter configuration dictionary for API queries + :rtype: Dict + """ if access_filter is not None: return { "mode": "and", @@ -1833,7 +1975,11 @@ def prepare_id_filters_export( "filters": [ { "key": "ids", - "values": id if isinstance(id, list) else [id], + "values": ( + entity_id + if isinstance(entity_id, list) + else [entity_id] + ), } ], "filterGroups": [], @@ -1850,7 +1996,9 @@ def prepare_id_filters_export( { "key": "ids", "mode": "or", - "values": id if isinstance(id, list) else [id], + "values": ( + entity_id if isinstance(entity_id, list) else [entity_id] + ), } ], } @@ -1862,9 +2010,21 @@ def prepare_export( access_filter: Dict = None, no_custom_attributes: bool = False, ) -> List: + """Prepare an entity for STIX2 export with related objects. + + :param entity: Entity dictionary to prepare for export + :type entity: Dict + :param mode: Export mode - 'simple' for entity only, 'full' for entity with relations + :type mode: str + :param access_filter: Access filter for the export, defaults to None + :type access_filter: Dict, optional + :param no_custom_attributes: Whether to exclude custom attributes, defaults to False + :type no_custom_attributes: bool, optional + :return: List of STIX2 objects ready for export + :rtype: List + """ result = [] objects_to_get = [] - relations_to_get = [] # CreatedByRef if ( @@ -2051,7 +2211,7 @@ def prepare_export( del entity["attribute_count"] from_to_check = entity["from"]["id"] relationships_from_filter = self.prepare_id_filters_export( - id=from_to_check, access_filter=access_filter + entity_id=from_to_check, access_filter=access_filter ) x = self.opencti.opencti_stix_object_or_stix_relationship.list( filters=relationships_from_filter @@ -2065,7 +2225,7 @@ def prepare_export( to_to_check = [entity["to"]["id"]] relationships_to_filter = self.prepare_id_filters_export( - id=to_to_check, access_filter=access_filter + entity_id=to_to_check, access_filter=access_filter ) y = self.opencti.opencti_stix_object_or_stix_relationship.list( filters=relationships_to_filter @@ -2082,7 +2242,7 @@ def prepare_export( if "from" in entity: from_to_check = entity["from"]["id"] relationships_from_filter = self.prepare_id_filters_export( - id=from_to_check, access_filter=access_filter + entity_id=from_to_check, access_filter=access_filter ) x = self.opencti.opencti_stix_object_or_stix_relationship.list( filters=relationships_from_filter @@ -2097,7 +2257,7 @@ def prepare_export( if "to" in entity: to_to_check = [entity["to"]["id"]] relationships_to_filter = self.prepare_id_filters_export( - id=to_to_check, access_filter=access_filter + entity_id=to_to_check, access_filter=access_filter ) y = self.opencti.opencti_stix_object_or_stix_relationship.list( filters=relationships_to_filter @@ -2196,8 +2356,8 @@ def prepare_export( # Get extra refs for key in entity.keys(): if key.endswith("_ref"): - type = entity[key].split("--")[0] - if type in STIX_CYBER_OBSERVABLE_MAPPING: + stix_type = entity[key].split("--")[0] + if stix_type in STIX_CYBER_OBSERVABLE_MAPPING: objects_to_get.append( { "id": entity[key], @@ -2215,8 +2375,8 @@ def prepare_export( ) elif key.endswith("_refs"): for value in entity[key]: - type = value.split("--")[0] - if type in STIX_CYBER_OBSERVABLE_MAPPING: + stix_type = value.split("--")[0] + if stix_type in STIX_CYBER_OBSERVABLE_MAPPING: objects_to_get.append( { "id": value, @@ -2304,25 +2464,6 @@ def prepare_export( ) uuids = uuids + [x["id"] for x in entity_object_bundle] result = result + entity_object_bundle - for ( - relation_object - ) in relations_to_get: # never appended after initialization - - def find_relation_object_data(current_relation_object): - return current_relation_object.id == relation_object["id"] - - relation_object_data = self.prepare_export( - entity=filter( - find_relation_object_data, - self.opencti.stix_core_relationship.list(filters=access_filter), - ) - ) - relation_object_bundle = self.filter_objects( - uuids, relation_object_data - ) - uuids = uuids + [x["id"] for x in relation_object_bundle] - result = result + relation_object_bundle - # Get extra reports """ for uuid in uuids: @@ -2359,21 +2500,21 @@ def find_relation_object_data(current_relation_object): # Refilter all the reports object refs final_result = [] - for entity in result: - if entity["type"] in [ + for result_entity in result: + if result_entity["type"] in [ "report", "note", "opinion", "observed-data", "grouping", ]: - if "object_refs" in entity: - entity["object_refs"] = [ - k for k in entity["object_refs"] if k in uuids + if "object_refs" in result_entity: + result_entity["object_refs"] = [ + k for k in result_entity["object_refs"] if k in uuids ] - final_result.append(entity) + final_result.append(result_entity) else: - final_result.append(entity) + final_result.append(result_entity) return final_result else: return [] @@ -2387,6 +2528,23 @@ def get_stix_bundle_or_object_from_entity_id( no_custom_attributes: bool = False, only_entity: bool = False, ) -> Dict: + """Get a STIX2 bundle or single object from an entity ID. + + :param entity_type: Type of the entity to export + :type entity_type: str + :param entity_id: ID of the entity to export + :type entity_id: str + :param mode: Export mode - 'simple' or 'full', defaults to 'simple' + :type mode: str + :param access_filter: Access filter for the export, defaults to None + :type access_filter: Dict, optional + :param no_custom_attributes: Whether to exclude custom attributes, defaults to False + :type no_custom_attributes: bool, optional + :param only_entity: If True, return only the entity object instead of a bundle + :type only_entity: bool, optional + :return: STIX2 bundle dictionary or single STIX2 object if only_entity is True + :rtype: Dict + """ bundle = { "type": "bundle", "id": "bundle--" + str(uuid.uuid4()), @@ -2415,7 +2573,7 @@ def get_stix_bundle_or_object_from_entity_id( return bundle # Please use get_stix_bundle_or_object_from_entity_id instead - @DeprecationWarning + @deprecated("Use get_stix_bundle_or_object_from_entity_id instead") def export_entity( self, entity_type: str, @@ -2425,6 +2583,26 @@ def export_entity( no_custom_attributes: bool = False, only_entity: bool = False, ) -> Dict: + """Export an entity as a STIX2 bundle. + + .. deprecated:: + Use :meth:`get_stix_bundle_or_object_from_entity_id` instead. + + :param entity_type: Type of the entity to export + :type entity_type: str + :param entity_id: ID of the entity to export + :type entity_id: str + :param mode: Export mode - 'simple' or 'full', defaults to 'simple' + :type mode: str + :param access_filter: Access filter for the export, defaults to None + :type access_filter: Dict, optional + :param no_custom_attributes: Whether to exclude custom attributes, defaults to False + :type no_custom_attributes: bool, optional + :param only_entity: If True, return only the entity object instead of a bundle + :type only_entity: bool, optional + :return: STIX2 bundle dictionary or single STIX2 object + :rtype: Dict + """ return self.get_stix_bundle_or_object_from_entity_id( entity_type=entity_type, entity_id=entity_id, @@ -2443,7 +2621,26 @@ def export_entities_list( orderMode: str = None, getAll: bool = True, withFiles: bool = False, - ) -> [Dict]: + ) -> List[Dict]: + """List entities for export based on type and filters. + + :param entity_type: Type of entities to list + :type entity_type: str + :param search: Search parameters, defaults to None + :type search: Dict, optional + :param filters: Filter parameters, defaults to None + :type filters: Dict, optional + :param orderBy: Field to order results by, defaults to None + :type orderBy: str, optional + :param orderMode: Order direction ('asc' or 'desc'), defaults to None + :type orderMode: str, optional + :param getAll: Whether to get all results, defaults to True + :type getAll: bool, optional + :param withFiles: Whether to include files in the export, defaults to False + :type withFiles: bool, optional + :return: List of entity dictionaries + :rtype: List[Dict] + """ if IdentityTypes.has_value(entity_type): entity_type = "Identity" @@ -2525,6 +2722,25 @@ def export_list( mode: str = "simple", access_filter: Dict = None, ) -> Dict: + """Export a list of entities as a STIX2 bundle. + + :param entity_type: Type of entities to export + :type entity_type: str + :param search: Search parameters, defaults to None + :type search: Dict, optional + :param filters: Filter parameters, defaults to None + :type filters: Dict, optional + :param order_by: Field to order results by, defaults to None + :type order_by: str, optional + :param order_mode: Order direction ('asc' or 'desc'), defaults to None + :type order_mode: str, optional + :param mode: Export mode - 'simple' or 'full', defaults to 'simple' + :type mode: str + :param access_filter: Access filter for the export, defaults to None + :type access_filter: Dict, optional + :return: STIX2 bundle containing all exported entities + :rtype: Dict + """ bundle = { "type": "bundle", "id": "bundle--" + str(uuid.uuid4()), @@ -2566,11 +2782,21 @@ def export_list( def export_selected( self, - entities_list: [dict], + entities_list: List[dict], mode: str = "simple", access_filter: Dict = None, ) -> Dict: - + """Export selected entities as a STIX2 bundle. + + :param entities_list: List of entities to export + :type entities_list: List[dict] + :param mode: Export mode ('simple' or 'full'), defaults to 'simple' + :type mode: str + :param access_filter: Access filter for the export + :type access_filter: Dict + :return: STIX2 bundle containing exported entities + :rtype: Dict + """ bundle = { "type": "bundle", "id": "bundle--" + str(uuid.uuid4()), @@ -2595,6 +2821,11 @@ def export_selected( return bundle def apply_patch_files(self, item): + """Apply file patches to an item. + + :param item: Item containing file patch operations + :type item: dict + """ field_patch = self.opencti.get_attribute_in_extension( "opencti_field_patch", item ) @@ -2626,6 +2857,11 @@ def apply_patch_files(self, item): ) def apply_patch(self, item): + """Apply field patches to an item. + + :param item: Item containing field patch operations + :type item: dict + """ field_patch = self.opencti.get_attribute_in_extension( "opencti_field_patch", item ) @@ -2673,21 +2909,41 @@ def apply_patch(self, item): self.apply_patch_files(item) def rule_apply(self, item): + """Apply a rule to an item. + + :param item: Item to apply the rule to + :type item: dict + """ rule_id = self.opencti.get_attribute_in_extension("opencti_rule", item) if rule_id is None: rule_id = item["opencti_rule"] self.opencti.stix_core_object.rule_apply(element_id=item["id"], rule_id=rule_id) def rule_clear(self, item): + """Clear a rule from an item. + + :param item: Item to clear the rule from + :type item: dict + """ rule_id = self.opencti.get_attribute_in_extension("opencti_rule", item) if rule_id is None: rule_id = item["opencti_rule"] self.opencti.stix_core_object.rule_clear(element_id=item["id"], rule_id=rule_id) def rules_rescan(self, item): + """Rescan rules for an item. + + :param item: Item to rescan rules for + :type item: dict + """ self.opencti.stix_core_object.rules_rescan(element_id=item["id"]) def organization_share(self, item): + """Share an item with organizations. + + :param item: Item to share + :type item: dict + """ organization_ids = self.opencti.get_attribute_in_extension( "sharing_organization_ids", item ) @@ -2714,6 +2970,11 @@ def organization_share(self, item): ) def organization_unshare(self, item): + """Unshare an item from organizations. + + :param item: Item to unshare + :type item: dict + """ organization_ids = self.opencti.get_attribute_in_extension( "sharing_organization_ids", item ) @@ -2739,6 +3000,12 @@ def organization_unshare(self, item): ) def element_add_organizations(self, item): + """Add organizations to an element. + + :param item: Item to add organizations to + :type item: dict + :raises ValueError: If the operation is not compatible with the item type + """ organization_ids = self.opencti.get_attribute_in_extension( "organization_ids", item ) @@ -2756,6 +3023,12 @@ def element_add_organizations(self, item): ) def element_remove_organizations(self, item): + """Remove organizations from an element. + + :param item: Item to remove organizations from + :type item: dict + :raises ValueError: If the operation is not compatible with the item type + """ organization_ids = self.opencti.get_attribute_in_extension( "organization_ids", item ) @@ -2773,6 +3046,12 @@ def element_remove_organizations(self, item): ) def element_add_groups(self, item): + """Add groups to an element. + + :param item: Item to add groups to + :type item: dict + :raises ValueError: If the operation is not compatible with the item type + """ group_ids = self.opencti.get_attribute_in_extension("group_ids", item) if group_ids is None: group_ids = item["group_ids"] @@ -2785,6 +3064,12 @@ def element_add_groups(self, item): ) def element_remove_groups(self, item): + """Remove groups from an element. + + :param item: Item to remove groups from + :type item: dict + :raises ValueError: If the operation is not compatible with the item type + """ group_ids = self.opencti.get_attribute_in_extension("group_ids", item) if group_ids is None: group_ids = item["group_ids"] @@ -2798,6 +3083,12 @@ def element_remove_groups(self, item): ) def send_email(self, item): + """Send an email for an item. + + :param item: Item to send email for + :type item: dict + :raises ValueError: If the operation is not supported for the item type + """ template_id = self.opencti.get_attribute_in_extension("template_id", item) if template_id is None: template_id = item["template_id"] @@ -2810,6 +3101,14 @@ def send_email(self, item): ) def element_operation_delete(self, item, operation): + """Delete an element. + + :param item: Item to delete + :type item: dict + :param operation: Delete operation type ('delete' or 'delete_force') + :type operation: str + :raises ValueError: If the delete operation fails or helper not found + """ # If data is stix, just use the generic stix function for deletion force_delete = operation == "delete_force" if item["type"] == "relationship": @@ -2835,6 +3134,11 @@ def element_operation_delete(self, item, operation): ) def element_remove_from_draft(self, item): + """Remove an element from draft. + + :param item: Item to remove from draft + :type item: dict + """ if item["type"] == "relationship": self.opencti.stix_core_relationship.remove_from_draft(id=item["id"]) elif item["type"] == "sighting": @@ -2844,6 +3148,14 @@ def element_remove_from_draft(self, item): self.opencti.stix_core_object.remove_from_draft(id=item["id"]) def apply_opencti_operation(self, item, operation): + """Apply an OpenCTI operation to an item. + + :param item: Item to apply the operation to + :type item: dict + :param operation: Operation to apply (delete, restore, merge, patch, etc.) + :type operation: str + :raises ValueError: If the operation is not supported + """ if operation == "delete" or operation == "delete_force": self.element_operation_delete(item=item, operation=operation) elif operation == "revert_draft": @@ -2863,13 +3175,13 @@ def apply_opencti_operation(self, item, operation): elif operation == "patch": self.apply_patch(item=item) elif operation == "pir_flag_element": - id = item["id"] - input = item["input"] - self.opencti.pir.pir_flag_element(id=id, input=input) + element_id = item["id"] + pir_input = item["input"] + self.opencti.pir.pir_flag_element(id=element_id, input=pir_input) elif operation == "pir_unflag_element": - id = item["id"] - input = item["input"] - self.opencti.pir.pir_unflag_element(id=id, input=input) + element_id = item["id"] + pir_input = item["input"] + self.opencti.pir.pir_unflag_element(id=element_id, input=pir_input) elif operation == "rule_apply": self.rule_apply(item=item) elif operation == "rule_clear": @@ -2916,6 +3228,19 @@ def import_item( types: List = None, work_id: str = None, ): + """Import a single STIX2 item into OpenCTI. + + :param item: STIX2 item to import + :type item: dict + :param update: Whether to update existing data, defaults to False + :type update: bool, optional + :param types: List of STIX2 types to filter, defaults to None + :type types: List, optional + :param work_id: Work ID for tracking import progress, defaults to None + :type work_id: str, optional + :return: True on success + :rtype: bool + """ opencti_operation = self.opencti.get_attribute_in_extension( "opencti_operation", item ) @@ -3044,6 +3369,22 @@ def import_item_with_retries( types: List = None, work_id: str = None, ): + """Import a single STIX2 item with automatic retry on failures. + + Handles various error types including timeouts, lock errors, missing references, + and bad gateway errors with appropriate retry strategies. + + :param item: STIX2 item to import + :type item: dict + :param update: Whether to update existing data, defaults to False + :type update: bool, optional + :param types: List of STIX2 types to filter, defaults to None + :type types: List, optional + :param work_id: Work ID for tracking import progress, defaults to None + :type work_id: str, optional + :return: None on success, the failed item on permanent failure + :rtype: dict or None + """ processing_count = 0 worker_logger = self.opencti.logger_class("worker") while processing_count <= MAX_PROCESSING_COUNT: @@ -3150,6 +3491,23 @@ def import_bundle( work_id: str = None, objects_max_refs: int = 0, ) -> Tuple[list, list]: + """Import a complete STIX2 bundle into OpenCTI. + + :param stix_bundle: STIX2 bundle dictionary to import + :type stix_bundle: Dict + :param update: Whether to update existing data, defaults to False + :type update: bool, optional + :param types: List of STIX2 types to filter, defaults to None + :type types: List, optional + :param work_id: Work ID for tracking import progress, defaults to None + :type work_id: str, optional + :param objects_max_refs: Maximum number of object references allowed; objects exceeding + this limit will be rejected. Set to 0 to disable the limit. + :type objects_max_refs: int, optional + :return: Tuple of (list of successfully imported elements, list of failed/too-large elements) + :rtype: Tuple[list, list] + :raises ValueError: If the bundle is not properly formatted or empty + """ # Check if the bundle is correctly formatted if "type" not in stix_bundle or stix_bundle["type"] != "bundle": raise ValueError("JSON data type is not a STIX2 bundle") @@ -3214,24 +3572,39 @@ def import_bundle( @staticmethod def put_attribute_in_extension( - object, extension_id, key, value, multiple=False + stix_object, extension_id, key, value, multiple=False ) -> any: - if ("x_opencti_" + key) in object: - del object["x_opencti_" + key] - if ("x_mitre_" + key) in object: - del object["x_mitre_" + key] - if "extensions" not in object: - object["extensions"] = {} - if extension_id not in object["extensions"]: - object["extensions"][extension_id] = {} - if key in object["extensions"][extension_id]: + """Add or update an attribute in a STIX object's extension. + + :param stix_object: STIX object to modify + :type stix_object: dict + :param extension_id: ID of the extension to add the attribute to + :type extension_id: str + :param key: Attribute key name + :type key: str + :param value: Attribute value to set + :type value: any + :param multiple: If True, append value to a list; if False, replace the value + :type multiple: bool + :return: Modified STIX object + :rtype: dict + """ + if ("x_opencti_" + key) in stix_object: + del stix_object["x_opencti_" + key] + if ("x_mitre_" + key) in stix_object: + del stix_object["x_mitre_" + key] + if "extensions" not in stix_object: + stix_object["extensions"] = {} + if extension_id not in stix_object["extensions"]: + stix_object["extensions"][extension_id] = {} + if key in stix_object["extensions"][extension_id]: if multiple: - object["extensions"][extension_id][key].append(value) + stix_object["extensions"][extension_id][key].append(value) else: - object["extensions"][extension_id][key] = value + stix_object["extensions"][extension_id][key] = value else: if multiple: - object["extensions"][extension_id][key] = [value] + stix_object["extensions"][extension_id][key] = [value] else: - object["extensions"][extension_id][key] = value - return object + stix_object["extensions"][extension_id][key] = value + return stix_object diff --git a/client-python/pycti/utils/opencti_stix2_identifier.py b/client-python/pycti/utils/opencti_stix2_identifier.py index 8755e1695919..619c885d5f3b 100644 --- a/client-python/pycti/utils/opencti_stix2_identifier.py +++ b/client-python/pycti/utils/opencti_stix2_identifier.py @@ -4,6 +4,17 @@ def external_reference_generate_id(url=None, source_name=None, external_id=None): + """Generate a STIX ID for an external reference. + + :param url: URL of the external reference + :type url: str + :param source_name: Source name of the external reference + :type source_name: str + :param external_id: External ID of the reference + :type external_id: str + :return: Generated STIX ID or None if insufficient data + :rtype: str or None + """ if url is not None: data = {"url": url} elif source_name is not None and external_id is not None: @@ -11,12 +22,25 @@ def external_reference_generate_id(url=None, source_name=None, external_id=None) else: return None data = canonicalize(data, utf8=False) - id = str(uuid.uuid5(uuid.UUID("00abedb4-aa42-466c-9c01-fed23315a9b7"), data)) - return "external-reference--" + id + generated_id = str( + uuid.uuid5(uuid.UUID("00abedb4-aa42-466c-9c01-fed23315a9b7"), data) + ) + return "external-reference--" + generated_id def kill_chain_phase_generate_id(phase_name, kill_chain_name): + """Generate a STIX ID for a kill chain phase. + + :param phase_name: Name of the phase + :type phase_name: str + :param kill_chain_name: Name of the kill chain + :type kill_chain_name: str + :return: Generated STIX ID + :rtype: str + """ data = {"phase_name": phase_name, "kill_chain_name": kill_chain_name} data = canonicalize(data, utf8=False) - id = str(uuid.uuid5(uuid.UUID("00abedb4-aa42-466c-9c01-fed23315a9b7"), data)) - return "kill-chain-phase--" + id + generated_id = str( + uuid.uuid5(uuid.UUID("00abedb4-aa42-466c-9c01-fed23315a9b7"), data) + ) + return "kill-chain-phase--" + generated_id diff --git a/client-python/pycti/utils/opencti_stix2_splitter.py b/client-python/pycti/utils/opencti_stix2_splitter.py index 75f6c9e3be6d..2c89618df139 100644 --- a/client-python/pycti/utils/opencti_stix2_splitter.py +++ b/client-python/pycti/utils/opencti_stix2_splitter.py @@ -26,6 +26,13 @@ def is_id_supported(key): + """Check if a STIX ID type is supported for processing. + + :param key: STIX ID or identifier to check + :type key: str + :return: True if the ID type is supported, False otherwise + :rtype: bool + """ if "--" in key: id_type = key.split("--")[0] return id_type in supported_types @@ -34,18 +41,31 @@ def is_id_supported(key): class OpenCTIStix2Splitter: - """STIX2 bundle splitter for OpenCTI + """STIX2 bundle splitter for OpenCTI. - Splits large STIX2 bundles into smaller chunks for processing. + Splits large STIX2 bundles into smaller chunks for processing, + handling dependencies between objects and deduplicating references. """ def __init__(self): + """Initialize the STIX2 bundle splitter. + + Sets up internal caches for tracking processed elements, + references, and incompatible items. + """ self.cache_index = {} self.cache_refs = {} self.elements = [] self.incompatible_items = [] def get_internal_ids_in_extension(self, item): + """Get internal IDs from OpenCTI extensions in a STIX object. + + :param item: the STIX object to extract IDs from + :type item: dict + :return: list of internal IDs found in extensions + :rtype: list + """ ids = [] if item.get("x_opencti_id"): ids.append(item["x_opencti_id"]) @@ -60,6 +80,19 @@ def get_internal_ids_in_extension(self, item): def enlist_element( self, item_id, raw_data, cleanup_inconsistent_bundle, parent_acc ): + """Enlist an element and its dependencies for processing. + + :param item_id: the ID of the item to enlist + :type item_id: str + :param raw_data: the raw data dictionary of all items + :type raw_data: dict + :param cleanup_inconsistent_bundle: whether to cleanup inconsistent references + :type cleanup_inconsistent_bundle: bool + :param parent_acc: accumulator of parent IDs to prevent circular references + :type parent_acc: list + :return: number of dependencies enlisted + :rtype: int + """ nb_deps = 1 if item_id not in raw_data: return 0 @@ -191,8 +224,8 @@ def enlist_element( ) elif item["type"] == "sighting": is_compatible = ( - item["sighting_of_ref"] is not None - and len(item["where_sighted_refs"]) > 0 + item.get("sighting_of_ref") is not None + and len(item.get("where_sighted_refs", [])) > 0 ) else: is_compatible = is_id_supported(item_id) @@ -214,12 +247,24 @@ def split_bundle_with_expectations( event_version=None, cleanup_inconsistent_bundle=False, ) -> Tuple[int, list, list]: - """splits a valid stix2 bundle into a list of bundles""" + """Split a valid STIX2 bundle into a list of bundles. + + :param bundle: the STIX2 bundle to split + :type bundle: str or dict + :param use_json: whether the bundle is JSON string (True) or dict (False) + :type use_json: bool + :param event_version: (optional) event version to include in bundles + :type event_version: str or None + :param cleanup_inconsistent_bundle: whether to cleanup inconsistent references + :type cleanup_inconsistent_bundle: bool + :return: tuple of (number of expectations, incompatible items, list of bundles) + :rtype: Tuple[int, list, list] + """ if use_json: try: bundle_data = json.loads(bundle) - except: - raise Exception("File data is not a valid JSON") + except json.JSONDecodeError as e: + raise Exception(f"File data is not a valid JSON: {e}") else: bundle_data = bundle @@ -242,6 +287,13 @@ def split_bundle_with_expectations( bundles = [] def by_dep_size(elem): + """Get the dependency count for sorting elements. + + :param elem: Element dictionary containing nb_deps + :type elem: dict + :return: Number of dependencies + :rtype: int + """ return elem["nb_deps"] self.elements.sort(key=by_dep_size) @@ -271,6 +323,20 @@ def by_dep_size(elem): @deprecated("Use split_bundle_with_expectations instead") def split_bundle(self, bundle, use_json=True, event_version=None) -> list: + """Split a valid STIX2 bundle into a list of bundles. + + .. deprecated:: + Use :meth:`split_bundle_with_expectations` instead. + + :param bundle: the STIX2 bundle to split + :type bundle: str or dict + :param use_json: whether the bundle is JSON string (True) or dict (False) + :type use_json: bool + :param event_version: (optional) event version to include in bundles + :type event_version: str or None + :return: list of STIX2 bundles + :rtype: list + """ _, _, bundles = self.split_bundle_with_expectations( bundle, use_json, event_version ) @@ -278,14 +344,20 @@ def split_bundle(self, bundle, use_json=True, event_version=None) -> list: @staticmethod def stix2_create_bundle(bundle_id, bundle_seq, items, use_json, event_version=None): - """create a stix2 bundle with items - - :param items: valid stix2 items - :type items: - :param use_json: use JSON? - :type use_json: - :return: JSON of the stix2 bundle - :rtype: + """Create a STIX2 bundle with items. + + :param bundle_id: the bundle ID + :type bundle_id: str + :param bundle_seq: the bundle sequence number + :type bundle_seq: int + :param items: valid STIX2 items + :type items: list + :param use_json: whether to return JSON string (True) or dict (False) + :type use_json: bool + :param event_version: (optional) event version to include + :type event_version: str or None + :return: STIX2 bundle as JSON string or dict + :rtype: str or dict """ bundle = { diff --git a/client-python/pycti/utils/opencti_stix2_update.py b/client-python/pycti/utils/opencti_stix2_update.py index 5c8b0f9e55d4..fc958ea52e36 100644 --- a/client-python/pycti/utils/opencti_stix2_update.py +++ b/client-python/pycti/utils/opencti_stix2_update.py @@ -1,63 +1,107 @@ -# coding: utf-8 - from pycti.utils.constants import StixCyberObservableTypes class OpenCTIStix2Update: - """Python API for Stix2 Update in OpenCTI + """Python API for Stix2 Update in OpenCTI. + + Provides methods to update STIX2 objects in OpenCTI, including + adding/removing marking definitions, labels, external references, + kill chain phases, and object references. - :param opencti: OpenCTI instance + :param opencti: OpenCTI API client instance + :type opencti: OpenCTIApiClient """ def __init__(self, opencti): + """Initialize the OpenCTIStix2Update helper. + + :param opencti: OpenCTI API client instance + :type opencti: OpenCTIApiClient + """ self.opencti = opencti - self.mapping_cache = {} - def add_object_marking_refs(self, entity_type, id, object_marking_refs, version=2): + def add_object_marking_refs( + self, entity_type, entity_id, object_marking_refs, version=2 + ): + """Add marking definition references to an entity. + + :param entity_type: Type of the entity + :type entity_type: str + :param entity_id: ID of the entity + :type entity_id: str + :param object_marking_refs: List of marking definition references + :type object_marking_refs: list + :param version: Version of the patch format (default: 2) + :type version: int + """ for object_marking_ref in object_marking_refs: if version == 2: object_marking_ref = object_marking_ref["value"] if entity_type == "relationship": self.opencti.stix_core_relationship.add_marking_definition( - id=id, marking_definition_id=object_marking_ref + id=entity_id, marking_definition_id=object_marking_ref ) elif entity_type == "sighting": self.opencti.stix_sighting_relationship.add_marking_definition( - id=id, marking_definition_id=object_marking_ref + id=entity_id, marking_definition_id=object_marking_ref ) elif StixCyberObservableTypes.has_value(entity_type): self.opencti.stix_cyber_observable.add_marking_definition( - id=id, marking_definition_id=object_marking_ref + id=entity_id, marking_definition_id=object_marking_ref ) else: self.opencti.stix_domain_object.add_marking_definition( - id=id, marking_definition_id=object_marking_ref + id=entity_id, marking_definition_id=object_marking_ref ) def remove_object_marking_refs( - self, entity_type, id, object_marking_refs, version=2 + self, entity_type, entity_id, object_marking_refs, version=2 ): + """Remove marking definition references from an entity. + + :param entity_type: Type of the entity + :type entity_type: str + :param entity_id: ID of the entity + :type entity_id: str + :param object_marking_refs: List of marking definition references + :type object_marking_refs: list + :param version: Version of the patch format (default: 2) + :type version: int + """ for object_marking_ref in object_marking_refs: if version == 2: object_marking_ref = object_marking_ref["value"] if entity_type == "relationship": self.opencti.stix_core_relationship.remove_marking_definition( - id=id, marking_definition_id=object_marking_ref + id=entity_id, marking_definition_id=object_marking_ref ) elif entity_type == "sighting": self.opencti.stix_sighting_relationship.remove_marking_definition( - id=id, marking_definition_id=object_marking_ref + id=entity_id, marking_definition_id=object_marking_ref ) elif StixCyberObservableTypes.has_value(entity_type): self.opencti.stix_cyber_observable.remove_marking_definition( - id=id, marking_definition_id=object_marking_ref + id=entity_id, marking_definition_id=object_marking_ref ) else: self.opencti.stix_domain_object.remove_marking_definition( - id=id, marking_definition_id=object_marking_ref + id=entity_id, marking_definition_id=object_marking_ref ) - def add_external_references(self, entity_type, id, external_references, version=2): + def add_external_references( + self, entity_type, entity_id, external_references, version=2 + ): + """Add external references to an entity. + + :param entity_type: Type of the entity + :type entity_type: str + :param entity_id: ID of the entity + :type entity_id: str + :param external_references: List of external references + :type external_references: list + :param version: Version of the patch format (default: 2) + :type version: int + """ for external_reference in external_references: if version == 2: external_reference = external_reference["value"] @@ -82,33 +126,55 @@ def add_external_references(self, entity_type, id, external_references, version= )["id"] if entity_type == "relationship": self.opencti.stix_core_relationship.add_external_reference( - id=id, external_reference_id=external_reference_id + id=entity_id, external_reference_id=external_reference_id ) elif StixCyberObservableTypes.has_value(entity_type): self.opencti.stix_cyber_observable.add_external_reference( - id=id, external_reference_id=external_reference_id + id=entity_id, external_reference_id=external_reference_id ) else: self.opencti.stix_domain_object.add_external_reference( - id=id, external_reference_id=external_reference_id + id=entity_id, external_reference_id=external_reference_id ) - def remove_external_references(self, entity_type, id, external_references): + def remove_external_references(self, entity_type, entity_id, external_references): + """Remove external references from an entity. + + :param entity_type: Type of the entity + :type entity_type: str + :param entity_id: ID of the entity + :type entity_id: str + :param external_references: List of external references + :type external_references: list + """ for external_reference in external_references: if entity_type == "relationship": self.opencti.stix_core_relationship.remove_external_reference( - id=id, external_reference_id=external_reference["id"] + id=entity_id, external_reference_id=external_reference["id"] ) elif StixCyberObservableTypes.has_value(entity_type): self.opencti.stix_cyber_observable.remove_external_reference( - id=id, external_reference_id=external_reference["id"] + id=entity_id, external_reference_id=external_reference["id"] ) else: self.opencti.stix_domain_object.remove_external_reference( - id=id, external_reference_id=external_reference["id"] + id=entity_id, external_reference_id=external_reference["id"] ) - def add_kill_chain_phases(self, entity_type, id, kill_chain_phases, version=2): + def add_kill_chain_phases( + self, entity_type, entity_id, kill_chain_phases, version=2 + ): + """Add kill chain phases to an entity. + + :param entity_type: Type of the entity + :type entity_type: str + :param entity_id: ID of the entity + :type entity_id: str + :param kill_chain_phases: List of kill chain phases + :type kill_chain_phases: list + :param version: Version of the patch format (default: 2) + :type version: int + """ for kill_chain_phase in kill_chain_phases: if version == 2: kill_chain_phase = kill_chain_phase["value"] @@ -124,147 +190,295 @@ def add_kill_chain_phases(self, entity_type, id, kill_chain_phases, version=2): )["id"] if entity_type == "relationship": self.opencti.stix_core_relationship.add_kill_chain_phase( - id=id, kill_chain_phase_id=kill_chain_phase_id + id=entity_id, kill_chain_phase_id=kill_chain_phase_id ) elif StixCyberObservableTypes.has_value(entity_type): self.opencti.stix_cyber_observable.add_kill_chain_phase( - id=id, kill_chain_phase_id=kill_chain_phase_id + id=entity_id, kill_chain_phase_id=kill_chain_phase_id ) else: self.opencti.stix_domain_object.add_kill_chain_phase( - id=id, kill_chain_phase_id=kill_chain_phase_id + id=entity_id, kill_chain_phase_id=kill_chain_phase_id ) - def remove_kill_chain_phases(self, entity_type, id, kill_chain_phases): + def remove_kill_chain_phases(self, entity_type, entity_id, kill_chain_phases): + """Remove kill chain phases from an entity. + + :param entity_type: Type of the entity + :type entity_type: str + :param entity_id: ID of the entity + :type entity_id: str + :param kill_chain_phases: List of kill chain phases + :type kill_chain_phases: list + """ for kill_chain_phase in kill_chain_phases: if entity_type == "relationship": self.opencti.stix_core_relationship.remove_kill_chain_phase( - id=id, kill_chain_phase_id=kill_chain_phase["id"] + id=entity_id, kill_chain_phase_id=kill_chain_phase["id"] ) elif StixCyberObservableTypes.has_value(entity_type): self.opencti.stix_cyber_observable.remove_kill_chain_phase( - id=id, kill_chain_phase_id=kill_chain_phase["id"] + id=entity_id, kill_chain_phase_id=kill_chain_phase["id"] ) else: self.opencti.stix_domain_object.remove_kill_chain_phase( - id=id, kill_chain_phase_id=kill_chain_phase["id"] + id=entity_id, kill_chain_phase_id=kill_chain_phase["id"] ) - def add_object_refs(self, entity_type, id, object_refs, version=2): + def add_object_refs(self, entity_type, entity_id, object_refs, version=2): + """Add object references to a container entity. + + :param entity_type: Type of the container entity (report, note, etc.) + :type entity_type: str + :param entity_id: ID of the container entity + :type entity_id: str + :param object_refs: List of object references to add + :type object_refs: list + :param version: Version of the patch format (default: 2) + :type version: int + """ for object_ref in object_refs: if version == 2: object_ref = object_ref["value"] if entity_type == "report": self.opencti.report.add_stix_object_or_stix_relationship( - id=id, stixObjectOrStixRelationshipId=object_ref + id=entity_id, stixObjectOrStixRelationshipId=object_ref ) elif entity_type == "note": self.opencti.note.add_stix_object_or_stix_relationship( - id=id, stixObjectOrStixRelationshipId=object_ref + id=entity_id, stixObjectOrStixRelationshipId=object_ref ) elif entity_type == "observed-data": self.opencti.observed_data.add_stix_object_or_stix_relationship( - id=id, stixObjectOrStixRelationshipId=object_ref + id=entity_id, stixObjectOrStixRelationshipId=object_ref ) elif entity_type == "opinion": self.opencti.opinion.add_stix_object_or_stix_relationship( - id=id, stixObjectOrStixRelationshipId=object_ref + id=entity_id, stixObjectOrStixRelationshipId=object_ref + ) + elif entity_type == "grouping": + self.opencti.grouping.add_stix_object_or_stix_relationship( + id=entity_id, stixObjectOrStixRelationshipId=object_ref + ) + elif entity_type == "case-incident": + self.opencti.case_incident.add_stix_object_or_stix_relationship( + id=entity_id, stixObjectOrStixRelationshipId=object_ref + ) + elif entity_type == "case-rfi": + self.opencti.case_rfi.add_stix_object_or_stix_relationship( + id=entity_id, stixObjectOrStixRelationshipId=object_ref + ) + elif entity_type == "case-rft": + self.opencti.case_rft.add_stix_object_or_stix_relationship( + id=entity_id, stixObjectOrStixRelationshipId=object_ref + ) + elif entity_type == "feedback": + self.opencti.feedback.add_stix_object_or_stix_relationship( + id=entity_id, stixObjectOrStixRelationshipId=object_ref + ) + elif entity_type == "task": + self.opencti.task.add_stix_object_or_stix_relationship( + id=entity_id, stixObjectOrStixRelationshipId=object_ref ) - def remove_object_refs(self, entity_type, id, object_refs, version=2): + def remove_object_refs(self, entity_type, entity_id, object_refs, version=2): + """Remove object references from a container entity. + + :param entity_type: Type of the container entity (report, note, etc.) + :type entity_type: str + :param entity_id: ID of the container entity + :type entity_id: str + :param object_refs: List of object references to remove + :type object_refs: list + :param version: Version of the patch format (default: 2) + :type version: int + """ for object_ref in object_refs: if version == 2: object_ref = object_ref["value"] if entity_type == "report": self.opencti.report.remove_stix_object_or_stix_relationship( - id=id, stixObjectOrStixRelationshipId=object_ref + id=entity_id, stixObjectOrStixRelationshipId=object_ref ) elif entity_type == "note": self.opencti.note.remove_stix_object_or_stix_relationship( - id=id, stixObjectOrStixRelationshipId=object_ref + id=entity_id, stixObjectOrStixRelationshipId=object_ref ) elif entity_type == "observed-data": self.opencti.observed_data.remove_stix_object_or_stix_relationship( - id=id, stixObjectOrStixRelationshipId=object_ref + id=entity_id, stixObjectOrStixRelationshipId=object_ref ) elif entity_type == "opinion": self.opencti.opinion.remove_stix_object_or_stix_relationship( - id=id, stixObjectOrStixRelationshipId=object_ref + id=entity_id, stixObjectOrStixRelationshipId=object_ref + ) + elif entity_type == "grouping": + self.opencti.grouping.remove_stix_object_or_stix_relationship( + id=entity_id, stixObjectOrStixRelationshipId=object_ref ) + elif entity_type == "case-incident": + self.opencti.case_incident.remove_stix_object_or_stix_relationship( + id=entity_id, stixObjectOrStixRelationshipId=object_ref + ) + elif entity_type == "case-rfi": + self.opencti.case_rfi.remove_stix_object_or_stix_relationship( + id=entity_id, stixObjectOrStixRelationshipId=object_ref + ) + elif entity_type == "case-rft": + self.opencti.case_rft.remove_stix_object_or_stix_relationship( + id=entity_id, stixObjectOrStixRelationshipId=object_ref + ) + elif entity_type == "feedback": + self.opencti.feedback.remove_stix_object_or_stix_relationship( + id=entity_id, stixObjectOrStixRelationshipId=object_ref + ) + elif entity_type == "task": + self.opencti.task.remove_stix_object_or_stix_relationship( + id=entity_id, stixObjectOrStixRelationshipId=object_ref + ) + + def add_labels(self, entity_type, entity_id, labels, version=2): + """Add labels to an entity. - def add_labels(self, entity_type, id, labels, version=2): + :param entity_type: Type of the entity + :type entity_type: str + :param entity_id: ID of the entity + :type entity_id: str + :param labels: List of labels to add + :type labels: list + :param version: Version of the patch format (default: 2) + :type version: int + """ for label in labels: if version == 2: label = label["value"] if entity_type == "relationship": - self.opencti.stix_core_relationship.add_label(id=id, label_name=label) + self.opencti.stix_core_relationship.add_label( + id=entity_id, label_name=label + ) elif StixCyberObservableTypes.has_value(entity_type): - self.opencti.stix_cyber_observable.add_label(id=id, label_name=label) + self.opencti.stix_cyber_observable.add_label( + id=entity_id, label_name=label + ) else: - self.opencti.stix_domain_object.add_label(id=id, label_name=label) + self.opencti.stix_domain_object.add_label( + id=entity_id, label_name=label + ) - def remove_labels(self, entity_type, id, labels, version=2): + def remove_labels(self, entity_type, entity_id, labels, version=2): + """Remove labels from an entity. + + :param entity_type: Type of the entity + :type entity_type: str + :param entity_id: ID of the entity + :type entity_id: str + :param labels: List of labels to remove + :type labels: list + :param version: Version of the patch format (default: 2) + :type version: int + """ for label in labels: if version == 2: label = label["value"] if entity_type == "relationship": self.opencti.stix_core_relationship.remove_label( - id=id, label_name=label + id=entity_id, label_name=label ) elif StixCyberObservableTypes.has_value(entity_type): - self.opencti.stix_cyber_observable.remove_label(id=id, label_name=label) + self.opencti.stix_cyber_observable.remove_label( + id=entity_id, label_name=label + ) else: - self.opencti.stix_domain_object.remove_label(id=id, label_name=label) + self.opencti.stix_domain_object.remove_label( + id=entity_id, label_name=label + ) + + def replace_created_by_ref(self, entity_type, entity_id, created_by_ref, version=2): + """Replace the created_by reference of an entity. - def replace_created_by_ref(self, entity_type, id, created_by_ref, version=2): + :param entity_type: Type of the entity + :type entity_type: str + :param entity_id: ID of the entity + :type entity_id: str + :param created_by_ref: New created_by reference + :type created_by_ref: str or list + :param version: Version of the patch format (default: 2) + :type version: int + """ if version == 2: created_by_ref = ( created_by_ref[0]["value"] if created_by_ref is not None else None ) if entity_type == "relationship": self.opencti.stix_core_relationship.update_created_by( - id=id, identity_id=created_by_ref + id=entity_id, identity_id=created_by_ref ) elif entity_type == "sighting": self.opencti.stix_sighting_relationship.update_created_by( - id=id, identity_id=created_by_ref + id=entity_id, identity_id=created_by_ref ) elif StixCyberObservableTypes.has_value(entity_type): self.opencti.stix_cyber_observable.update_created_by( - id=id, identity_id=created_by_ref + id=entity_id, identity_id=created_by_ref ) else: self.opencti.stix_domain_object.update_created_by( - id=id, identity_id=created_by_ref + id=entity_id, identity_id=created_by_ref ) - def update_attribute(self, entity_type, id, input): + def update_attribute(self, entity_type, entity_id, field_input): + """Update an attribute of an entity. + + :param entity_type: Type of the entity + :type entity_type: str + :param entity_id: ID of the entity + :type entity_id: str + :param field_input: Input containing the attribute update + :type field_input: list + """ # Relations if entity_type == "relationship": - self.opencti.stix_core_relationship.update_field(id=id, input=input) + self.opencti.stix_core_relationship.update_field( + id=entity_id, input=field_input + ) elif entity_type == "sighting": - self.opencti.stix_sighting_relationship.update_field(id=id, input=input) + self.opencti.stix_sighting_relationship.update_field( + id=entity_id, input=field_input + ) # Observables elif StixCyberObservableTypes.has_value(entity_type): - self.opencti.stix_cyber_observable.update_field(id=id, input=input) + self.opencti.stix_cyber_observable.update_field( + id=entity_id, input=field_input + ) # Meta elif entity_type == "marking-definition": - self.opencti.marking_definition.update_field(id=id, input=input) + self.opencti.marking_definition.update_field( + id=entity_id, input=field_input + ) elif entity_type == "label": - self.opencti.label.update_field(id=id, input=input) + self.opencti.label.update_field(id=entity_id, input=field_input) elif entity_type == "vocabulary": - self.opencti.vocabulary.update_field(id=id, input=input) + self.opencti.vocabulary.update_field(id=entity_id, input=field_input) elif entity_type == "kill-chain-phase": - self.opencti.kill_chain_phase.update_field(id=id, input=input) + self.opencti.kill_chain_phase.update_field(id=entity_id, input=field_input) elif entity_type == "external-reference": - self.opencti.external_reference.update_field(id=id, input=input) + self.opencti.external_reference.update_field( + id=entity_id, input=field_input + ) # Remaining stix domain else: - self.opencti.stix_domain_object.update_field(id=id, input=input) + self.opencti.stix_domain_object.update_field( + id=entity_id, input=field_input + ) def process_update(self, data): + """Process a STIX2 patch/update operation. + + :param data: Data containing x_opencti_patch operations + :type data: dict + """ try: - # Build the inputs fo update api + # Build the inputs for update api inputs = [] if "add" in data["x_opencti_patch"]: for key in data["x_opencti_patch"]["add"].keys(): @@ -283,15 +497,15 @@ def process_update(self, data): ): # ID replace is a side effect handled by the platform val = data["x_opencti_patch"]["replace"][key] current_val = val["current"] - if type(current_val) is list: + if isinstance(current_val, list): values = list( map( lambda x: ( x["value"] - if (type(current_val) is dict and "value" in x) + if (isinstance(x, dict) and "value" in x) else x ), - str(current_val), + current_val, ) ) inputs.append({"key": key, "value": values}) @@ -299,7 +513,8 @@ def process_update(self, data): values = ( current_val["value"] if ( - type(current_val) is dict and "value" in current_val + isinstance(current_val, dict) + and "value" in current_val ) else str(current_val) ) diff --git a/client-python/pycti/utils/opencti_stix2_utils.py b/client-python/pycti/utils/opencti_stix2_utils.py index c14344fb5d43..87f4fd3544f4 100644 --- a/client-python/pycti/utils/opencti_stix2_utils.py +++ b/client-python/pycti/utils/opencti_stix2_utils.py @@ -1,3 +1,9 @@ +"""STIX2 utility functions and mappings for OpenCTI. + +This module provides utility classes and constants for working with STIX2 objects +in OpenCTI, including type mappings, pattern generation, and object reference counting. +""" + from typing import Any, Dict from stix2 import EqualityComparisonExpression, ObjectPath, ObservationExpression @@ -132,7 +138,7 @@ "Process": ["pid"], "Software": ["name"], "Url": ["value"], - "User-Account": ["acount_login"], + "User-Account": ["account_login"], "Windows-Registry-Key": ["key"], "Windows-Registry-Value-Type": ["name"], "Hostname": ["value"], @@ -152,9 +158,10 @@ class OpenCTIStix2Utils: - """Utility class for STIX2 operations in OpenCTI + """Utility class for STIX2 operations in OpenCTI. - Provides helper methods for STIX2 conversions and pattern generation. + Provides helper methods for STIX2 conversions and pattern generation, + including type mappings, observable pattern creation, and reference counting. """ @staticmethod @@ -213,12 +220,13 @@ def generate_random_stix_id(stix_type): ) @staticmethod - def retrieveClassForMethod( - openCTIApiClient, entity: Dict, type_path: str, method: str + def retrieve_class_for_method( + opencti_api_client, entity: Dict, type_path: str, method: str ) -> Any: """Retrieve the appropriate API class for a given entity type and method. - :param openCTIApiClient: OpenCTI API client instance + :param opencti_api_client: OpenCTI API client instance + :type opencti_api_client: OpenCTIApiClient :param entity: Entity dictionary containing the type :type entity: Dict :param type_path: Path to the type field in the entity @@ -229,15 +237,46 @@ def retrieveClassForMethod( :rtype: Any """ if entity is not None and type_path in entity: - attributeName = entity[type_path].lower().replace("-", "_") - if hasattr(openCTIApiClient, attributeName): - attribute = getattr(openCTIApiClient, attributeName) + attribute_name = entity[type_path].lower().replace("-", "_") + if hasattr(opencti_api_client, attribute_name): + attribute = getattr(opencti_api_client, attribute_name) if hasattr(attribute, method): return attribute return None + @staticmethod + def retrieveClassForMethod( + openCTIApiClient, entity: Dict, type_path: str, method: str + ) -> Any: + """Retrieve the appropriate API class for a given entity type and method. + + .. deprecated:: + Use :meth:`retrieve_class_for_method` instead. + + :param openCTIApiClient: OpenCTI API client instance + :type openCTIApiClient: OpenCTIApiClient + :param entity: Entity dictionary containing the type + :type entity: Dict + :param type_path: Path to the type field in the entity + :type type_path: str + :param method: Name of the method to check for + :type method: str + :return: The API class that has the specified method, or None + :rtype: Any + """ + return OpenCTIStix2Utils.retrieve_class_for_method( + openCTIApiClient, entity, type_path, method + ) + @staticmethod def compute_object_refs_number(entity: Dict): + """Compute the number of object references in an entity. + + :param entity: Entity dictionary to analyze + :type entity: Dict + :return: Total number of references + :rtype: int + """ refs_number = 0 for key in list(entity.keys()): if key.endswith("_refs") and entity[key] is not None: diff --git a/client-python/setup.cfg b/client-python/setup.cfg index c27bbd3c3fca..826609c499ef 100644 --- a/client-python/setup.cfg +++ b/client-python/setup.cfg @@ -4,7 +4,7 @@ version = attr: pycti.__version__ author = Filigran author_email = contact@filigran.io maintainer = Filigran -url = https://github.com/OpenCTI-Platform/opencti +url = https://github.com/OpenCTI-Platform/opencti/client-python description = Python API client for OpenCTI. long_description = file: README.md long_description_content_type = text/markdown @@ -69,6 +69,7 @@ dev = types-python-dateutil~=2.9.0 wheel~=0.45.1 doc = - autoapi~=2.0.1 - sphinx-autodoc-typehints~=3.2.0 - sphinx-rtd-theme~=3.0.2 + sphinx-autoapi>=3.0.0 + sphinx>=8.2,<9 + sphinx-autodoc-typehints>=3.0.0 + sphinx-rtd-theme>=3.0.0 diff --git a/client-python/tests/01-unit/utils/test_opencti_stix2.py b/client-python/tests/01-unit/utils/test_opencti_stix2.py index 63677856e6bf..f2ad0bdb89cf 100644 --- a/client-python/tests/01-unit/utils/test_opencti_stix2.py +++ b/client-python/tests/01-unit/utils/test_opencti_stix2.py @@ -113,4 +113,4 @@ def test_import_bundle_from_file(opencti_stix2: OpenCTIStix2, caplog) -> None: opencti_stix2.import_bundle_from_file("foo.txt") for record in caplog.records: assert record.levelname == "ERROR" - assert "The bundle file does not exists" in caplog.text + assert "The bundle file does not exist" in caplog.text diff --git a/opencti-platform/opencti-front/.gitignore b/opencti-platform/opencti-front/.gitignore index d07389742e87..fb484156bb5f 100644 --- a/opencti-platform/opencti-front/.gitignore +++ b/opencti-platform/opencti-front/.gitignore @@ -1,5 +1,6 @@ .eslintcache config/development.json +.yarnrc.yml # testing /coverage diff --git a/opencti-platform/opencti-front/lang/front/de.json b/opencti-platform/opencti-front/lang/front/de.json index 7b328b7f33a0..70419bc4ba73 100644 --- a/opencti-platform/opencti-front/lang/front/de.json +++ b/opencti-platform/opencti-front/lang/front/de.json @@ -222,6 +222,7 @@ "All types of target": "Alle Arten von Ziel", "All years": "Alle Jahre", "Allow modification of sensitive configuration": "Änderung der sensiblen Konfiguration zulassen", + "Allow multiple files": "Mehrere Dateien zulassen", "Allow multiple instances": "Mehrere Instanzen zulassen", "Allow multiple instances of main entity": "Mehrere Instanzen der Hauptentität zulassen", "allow to deploy in one-click threat management resources such as feeds, dashboards, playbooks, etc.": "ermöglicht die Bereitstellung von Bedrohungsmanagement-Ressourcen wie Feeds, Dashboards, Playbooks usw. mit nur einem Klick", diff --git a/opencti-platform/opencti-front/lang/front/en.json b/opencti-platform/opencti-front/lang/front/en.json index 3aa63cbf33df..73bc779cd98a 100644 --- a/opencti-platform/opencti-front/lang/front/en.json +++ b/opencti-platform/opencti-front/lang/front/en.json @@ -222,6 +222,7 @@ "All types of target": "All types of target", "All years": "All years", "Allow modification of sensitive configuration": "Allow modification of sensitive configuration", + "Allow multiple files": "Allow multiple files", "Allow multiple instances": "Allow multiple instances", "Allow multiple instances of main entity": "Allow multiple instances of main entity", "allow to deploy in one-click threat management resources such as feeds, dashboards, playbooks, etc.": "allow to deploy in one-click threat management resources such as feeds, dashboards, playbooks, etc.", diff --git a/opencti-platform/opencti-front/lang/front/es.json b/opencti-platform/opencti-front/lang/front/es.json index 7a91acb99e93..b2d9ea2ca127 100644 --- a/opencti-platform/opencti-front/lang/front/es.json +++ b/opencti-platform/opencti-front/lang/front/es.json @@ -222,6 +222,7 @@ "All types of target": "Todo los tipos de objetivo", "All years": "Todos los años", "Allow modification of sensitive configuration": "Permitir la modificación de la configuración sensible", + "Allow multiple files": "Permitir varios archivos", "Allow multiple instances": "Permitir múltiples instancias", "Allow multiple instances of main entity": "Permitir múltiples instancias de la entidad principal", "allow to deploy in one-click threat management resources such as feeds, dashboards, playbooks, etc.": "permiten desplegar en un clic recursos de gestión de amenazas como feeds, cuadros de mando, playbooks, etc.", diff --git a/opencti-platform/opencti-front/lang/front/fr.json b/opencti-platform/opencti-front/lang/front/fr.json index ad73cfb10a7b..75462d78c470 100644 --- a/opencti-platform/opencti-front/lang/front/fr.json +++ b/opencti-platform/opencti-front/lang/front/fr.json @@ -222,6 +222,7 @@ "All types of target": "Tous les types de cible", "All years": "Toutes les années", "Allow modification of sensitive configuration": "Permettre la modification d'une configuration sensible", + "Allow multiple files": "Autoriser les fichiers multiples", "Allow multiple instances": "Autoriser les instances multiples", "Allow multiple instances of main entity": "Autoriser plusieurs instances de l'entité principale", "allow to deploy in one-click threat management resources such as feeds, dashboards, playbooks, etc.": "permet de déployer en un clic des ressources de gestion des menaces telles que des flux, des tableaux de bord, des playbooks, etc.", diff --git a/opencti-platform/opencti-front/lang/front/it.json b/opencti-platform/opencti-front/lang/front/it.json index 3b463228d0a6..1c082fa2a2f8 100644 --- a/opencti-platform/opencti-front/lang/front/it.json +++ b/opencti-platform/opencti-front/lang/front/it.json @@ -222,6 +222,7 @@ "All types of target": "Tutti i tipi di target", "All years": "Tutti gli anni", "Allow modification of sensitive configuration": "Consenti la modifica della configurazione sensibile", + "Allow multiple files": "Consentire più file", "Allow multiple instances": "Consentire istanze multiple", "Allow multiple instances of main entity": "Consentire istanze multiple dell'entità principale", "allow to deploy in one-click threat management resources such as feeds, dashboards, playbooks, etc.": "consentono di distribuire in un solo clic risorse di gestione delle minacce come feed, dashboard, playbook, ecc.", diff --git a/opencti-platform/opencti-front/lang/front/ja.json b/opencti-platform/opencti-front/lang/front/ja.json index ef117a41afea..f0b20c52af67 100644 --- a/opencti-platform/opencti-front/lang/front/ja.json +++ b/opencti-platform/opencti-front/lang/front/ja.json @@ -222,6 +222,7 @@ "All types of target": "あらゆる種類のターゲット", "All years": "すべての年", "Allow modification of sensitive configuration": "機密設定の変更を許可する", + "Allow multiple files": "複数のファイルを許可する", "Allow multiple instances": "複数のインスタンスを許可する", "Allow multiple instances of main entity": "主エンティティの複数インスタンスを許可する", "allow to deploy in one-click threat management resources such as feeds, dashboards, playbooks, etc.": "フィード、ダッシュボード、プレイブックなどの脅威管理リソースをワンクリックで展開できる。", diff --git a/opencti-platform/opencti-front/lang/front/ko.json b/opencti-platform/opencti-front/lang/front/ko.json index d35c943421ff..232972a46eae 100644 --- a/opencti-platform/opencti-front/lang/front/ko.json +++ b/opencti-platform/opencti-front/lang/front/ko.json @@ -222,6 +222,7 @@ "All types of target": "모든 대상 유형", "All years": "모든 연도", "Allow modification of sensitive configuration": "민감한 구성의 수정 허용", + "Allow multiple files": "여러 파일 허용", "Allow multiple instances": "여러 인스턴스 허용", "Allow multiple instances of main entity": "주 엔터티의 여러 인스턴스 허용", "allow to deploy in one-click threat management resources such as feeds, dashboards, playbooks, etc.": "피드, 대시보드, 플레이북 등과 같은 원클릭 위협 관리 리소스에 배포할 수 있습니다.", diff --git a/opencti-platform/opencti-front/lang/front/ru.json b/opencti-platform/opencti-front/lang/front/ru.json index b9b6a76b1d05..449e3754c531 100644 --- a/opencti-platform/opencti-front/lang/front/ru.json +++ b/opencti-platform/opencti-front/lang/front/ru.json @@ -222,6 +222,7 @@ "All types of target": "Все типы мишеней", "All years": "Все годы", "Allow modification of sensitive configuration": "Позволяет изменять конфиденциальную конфигурацию", + "Allow multiple files": "Разрешить несколько файлов", "Allow multiple instances": "Разрешить несколько экземпляров", "Allow multiple instances of main entity": "Разрешить несколько экземпляров главной сущности", "allow to deploy in one-click threat management resources such as feeds, dashboards, playbooks, etc.": "позволяют в один клик развернуть ресурсы для управления угрозами, такие как фиды, панели управления, плейбуки и т. д.", diff --git a/opencti-platform/opencti-front/lang/front/zh.json b/opencti-platform/opencti-front/lang/front/zh.json index 91e018e12e0e..a8b5e90f95a9 100644 --- a/opencti-platform/opencti-front/lang/front/zh.json +++ b/opencti-platform/opencti-front/lang/front/zh.json @@ -222,6 +222,7 @@ "All types of target": "所有类型的目标", "All years": "所有年份", "Allow modification of sensitive configuration": "允许修改敏感配置", + "Allow multiple files": "允许多个文件", "Allow multiple instances": "允许多个实例", "Allow multiple instances of main entity": "允许主实体有多个实例", "allow to deploy in one-click threat management resources such as feeds, dashboards, playbooks, etc.": "允许一键部署威胁管理资源,如馈送、仪表板、播放簿等。", diff --git a/opencti-platform/opencti-front/src/private/Root.tsx b/opencti-platform/opencti-front/src/private/Root.tsx index 4dced6a38468..40e647b6ea27 100644 --- a/opencti-platform/opencti-front/src/private/Root.tsx +++ b/opencti-platform/opencti-front/src/private/Root.tsx @@ -256,10 +256,10 @@ const meUserFragment = graphql` definition_type x_opencti_order } - # personal_notifiers { - # id - # name - # } + personal_notifiers { + id + name + } can_manage_sensitive_config } `; diff --git a/opencti-platform/opencti-front/src/private/components/data/forms/Form.d.ts b/opencti-platform/opencti-front/src/private/components/data/forms/Form.d.ts index 421d9c176354..82667f4ff4d8 100644 --- a/opencti-platform/opencti-front/src/private/components/data/forms/Form.d.ts +++ b/opencti-platform/opencti-front/src/private/components/data/forms/Form.d.ts @@ -12,6 +12,7 @@ export interface FormFieldAttribute { required: boolean; isMandatory?: boolean; // Whether this field is for a mandatory attribute width?: 'full' | 'half' | 'third'; // Field width in grid: full (12), half (6), third (4) + multiple?: boolean; // For openvocab, select/multiselect, and files fields entityType?: string; // The entity type this field belongs to (for field type filtering) attributeMapping: { entity: string; // Entity ID this field maps to (main_entity or additional entity ID) @@ -116,7 +117,7 @@ export interface FormFieldDefinition { required: boolean; isMandatory?: boolean; width?: 'full' | 'half' | 'third'; // Field width in grid: full (12), half (6), third (4) - multiple?: boolean; // For openvocab and select/multiselect fields + multiple?: boolean; // For openvocab, select/multiselect, and files fields relationship?: { type: string; target: string; diff --git a/opencti-platform/opencti-front/src/private/components/data/forms/FormSchemaEditor.tsx b/opencti-platform/opencti-front/src/private/components/data/forms/FormSchemaEditor.tsx index 1a64d57a914a..ca1c6ed4d50d 100644 --- a/opencti-platform/opencti-front/src/private/components/data/forms/FormSchemaEditor.tsx +++ b/opencti-platform/opencti-front/src/private/components/data/forms/FormSchemaEditor.tsx @@ -973,6 +973,20 @@ const FormSchemaEditor: FunctionComponent = ({ + {/* Multiple files option for files type */} + {field.type === 'files' && ( + handleFieldChange(`fields.${fieldIndex}.multiple`, e.target.checked)} + /> + )} + label={t_i18n('Allow multiple files')} + style={{ marginTop: 20, display: 'block' }} + /> + )} + = ({ /> )} label={t_i18n('Required')} - style={{ marginTop: 20 }} + style={{ marginTop: 20, display: 'block' }} /> ); diff --git a/opencti-platform/opencti-front/src/private/components/data/forms/FormUtils.ts b/opencti-platform/opencti-front/src/private/components/data/forms/FormUtils.ts index 129c09ed5e1c..dbea29cafe03 100644 --- a/opencti-platform/opencti-front/src/private/components/data/forms/FormUtils.ts +++ b/opencti-platform/opencti-front/src/private/components/data/forms/FormUtils.ts @@ -454,6 +454,7 @@ export const convertFormBuilderDataToSchema = ( required: field.required, isMandatory: field.isMandatory, // Preserve mandatory flag width: field.width, // Preserve field width configuration + multiple: field.multiple, // For openvocab, select/multiselect, and files fields options: field.options, attributeMapping: field.attributeMapping, defaultValue: field.defaultValue, diff --git a/opencti-platform/opencti-front/src/private/components/data/forms/view/FormFieldRenderer.tsx b/opencti-platform/opencti-front/src/private/components/data/forms/view/FormFieldRenderer.tsx index a66a7afb3cef..ed318834064a 100644 --- a/opencti-platform/opencti-front/src/private/components/data/forms/view/FormFieldRenderer.tsx +++ b/opencti-platform/opencti-front/src/private/components/data/forms/view/FormFieldRenderer.tsx @@ -136,8 +136,16 @@ const FormFieldRenderer: FunctionComponent = ({ // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore Promise.all(filePromises).then((fileData: { name?: string; data?: string }[]) => { - const currentFiles = (fieldValue || []) as { name?: string; data?: string }[]; - setFieldValue(field.name, [...currentFiles, ...fileData]); + // multiple defaults to false (single file mode) + // Set multiple=true explicitly to allow multiple files + const allowMultiple = field.multiple === true; + if (allowMultiple) { + const currentFiles = (fieldValue || []) as { name?: string; data?: string }[]; + setFieldValue(field.name, [...currentFiles, ...fileData]); + } else { + // Single file mode: replace existing file + setFieldValue(field.name, [fileData[0]]); + } }); } }; @@ -414,27 +422,37 @@ const FormFieldRenderer: FunctionComponent = ({ /> ); - case 'files': + case 'files': { + // multiple defaults to false (single file mode) + // Set multiple=true explicitly to allow multiple files + const allowMultipleFiles = field.multiple === true; + const hasExistingFile = fieldValue && Array.isArray(fieldValue) && fieldValue.length > 0; + // In single file mode, hide upload button if file already exists + const showUploadButton = allowMultipleFiles || !hasExistingFile; return (
          {displayLabel}
          - - - {t_i18n('Upload files')} + {showUploadButton && ( + <> + + + {allowMultipleFiles ? t_i18n('Upload files') : t_i18n('Upload file')} + + )}
          - {fieldValue && Array.isArray(fieldValue) && fieldValue.length > 0 ? ( + {hasExistingFile ? (
          {(fieldValue as Array<{ name?: string; url?: string }>).map((file, index: number) => ( = ({ )}
          ); + } default: return ( diff --git a/opencti-platform/opencti-front/src/private/components/profile/ProfileOverview.jsx b/opencti-platform/opencti-front/src/private/components/profile/ProfileOverview.jsx index 5cd862ae86b8..edf0a8e5abb8 100644 --- a/opencti-platform/opencti-front/src/private/components/profile/ProfileOverview.jsx +++ b/opencti-platform/opencti-front/src/private/components/profile/ProfileOverview.jsx @@ -744,10 +744,10 @@ const ProfileOverview = createFragmentContainer(ProfileOverviewComponent, { submenu_show_icons submenu_auto_collapse monochrome_labels - # personal_notifiers { - # id - # name - # } + personal_notifiers { + id + name + } objectOrganization { edges { node { diff --git a/opencti-platform/opencti-front/src/schema/relay.schema.graphql b/opencti-platform/opencti-front/src/schema/relay.schema.graphql index 2bc5ad6a0638..a70f029a896c 100644 --- a/opencti-platform/opencti-front/src/schema/relay.schema.graphql +++ b/opencti-platform/opencti-front/src/schema/relay.schema.graphql @@ -2693,6 +2693,7 @@ input ExternalReferenceAddInput { url: String hash: String file: Upload + fileMarkings: [String] external_id: String created: DateTime modified: DateTime @@ -3114,6 +3115,7 @@ input AttackPatternAddInput { clientMutationId: String update: Boolean file: Upload + fileMarkings: [String] } type AttackPatternForMatrix { @@ -3259,6 +3261,7 @@ input CampaignAddInput { clientMutationId: String update: Boolean file: Upload + fileMarkings: [String] } enum ContainersOrdering { @@ -3457,6 +3460,7 @@ input NoteAddInput { clientMutationId: String update: Boolean file: Upload + fileMarkings: [String] } input NoteUserAddInput { @@ -3596,6 +3600,7 @@ input ObservedDataAddInput { clientMutationId: String update: Boolean file: Upload + fileMarkings: [String] } enum OpinionsOrdering { @@ -3712,6 +3717,7 @@ input OpinionAddInput { clientMutationId: String update: Boolean file: Upload + fileMarkings: [String] } input OpinionUserAddInput { @@ -3862,6 +3868,7 @@ input ReportAddInput { clientMutationId: String update: Boolean file: Upload + fileMarkings: [String] authorized_members: [MemberAccessInput!] } @@ -3974,6 +3981,7 @@ input CourseOfActionAddInput { clientMutationId: String update: Boolean file: Upload + fileMarkings: [String] } enum IdentitiesOrdering { @@ -4086,6 +4094,8 @@ input IdentityAddInput { clientMutationId: String created: DateTime modified: DateTime + file: Upload + fileMarkings: [String] update: Boolean } @@ -4205,6 +4215,7 @@ input IndividualAddInput { clientMutationId: String update: Boolean file: Upload + fileMarkings: [String] } enum SectorsOrdering { @@ -4320,6 +4331,7 @@ input SectorAddInput { clientMutationId: String update: Boolean file: Upload + fileMarkings: [String] } enum SystemsOrdering { @@ -4437,6 +4449,7 @@ input SystemAddInput { clientMutationId: String update: Boolean file: Upload + fileMarkings: [String] } enum InfrastructuresOrdering { @@ -4556,6 +4569,7 @@ input InfrastructureAddInput { clientMutationId: String update: Boolean file: Upload + fileMarkings: [String] } enum IntrusionSetsOrdering { @@ -4679,6 +4693,7 @@ input IntrusionSetAddInput { clientMutationId: String update: Boolean file: Upload + fileMarkings: [String] } enum LocationsOrdering { @@ -4788,6 +4803,8 @@ input LocationAddInput { x_opencti_modified_at: DateTime x_opencti_workflow_id: String update: Boolean + file: Upload + fileMarkings: [String] } enum PositionsOrdering { @@ -4904,6 +4921,7 @@ input PositionAddInput { clientMutationId: String update: Boolean file: Upload + fileMarkings: [String] } enum CitiesOrdering { @@ -5019,6 +5037,7 @@ input CityAddInput { clientMutationId: String update: Boolean file: Upload + fileMarkings: [String] } enum CountriesOrdering { @@ -5129,6 +5148,7 @@ input CountryAddInput { clientMutationId: String update: Boolean file: Upload + fileMarkings: [String] } enum RegionsOrdering { @@ -5241,6 +5261,7 @@ input RegionAddInput { clientMutationId: String update: Boolean file: Upload + fileMarkings: [String] } enum MalwaresOrdering { @@ -5376,6 +5397,7 @@ input MalwareAddInput { clientMutationId: String update: Boolean file: Upload + fileMarkings: [String] } enum ThreatActorsOrdering { @@ -5589,6 +5611,7 @@ input ThreatActorGroupAddInput { clientMutationId: String update: Boolean file: Upload + fileMarkings: [String] } enum ToolsOrdering { @@ -5700,6 +5723,7 @@ input ToolAddInput { clientMutationId: String update: Boolean file: Upload + fileMarkings: [String] } enum VulnerabilitiesOrdering { @@ -5906,6 +5930,7 @@ input VulnerabilityAddInput { clientMutationId: String update: Boolean file: Upload + fileMarkings: [String] } enum IncidentsOrdering { @@ -6034,6 +6059,7 @@ input IncidentAddInput { clientMutationId: String update: Boolean file: Upload + fileMarkings: [String] } enum StixCyberObservablesOrdering { @@ -6160,6 +6186,7 @@ input AutonomousSystemAddInput { name: String rir: String file: Upload + fileMarkings: [String] } type Directory implements BasicObject & StixObject & StixCoreObject & StixCyberObservable { @@ -6222,6 +6249,7 @@ input DirectoryAddInput { mtime: DateTime atime: DateTime file: Upload + fileMarkings: [String] } type DomainName implements BasicObject & StixObject & StixCoreObject & StixCyberObservable { @@ -6276,6 +6304,7 @@ type DomainName implements BasicObject & StixObject & StixCoreObject & StixCyber input DomainNameAddInput { value: String! file: Upload + fileMarkings: [String] } type EmailAddr implements BasicObject & StixObject & StixCoreObject & StixCyberObservable { @@ -6332,6 +6361,7 @@ input EmailAddrAddInput { value: String display_name: String file: Upload + fileMarkings: [String] } type EmailMessage implements BasicObject & StixObject & StixCoreObject & StixCyberObservable { @@ -6398,6 +6428,7 @@ input EmailMessageAddInput { received_lines: [String] body: String file: Upload + fileMarkings: [String] } type EmailMimePartType implements BasicObject & StixObject & StixCoreObject & StixCyberObservable { @@ -6456,6 +6487,7 @@ input EmailMimePartTypeAddInput { content_type: String content_disposition: String file: Upload + fileMarkings: [String] } input HashInput { @@ -6593,6 +6625,7 @@ input ArtifactAddInput { decryption_key: String x_opencti_additional_names: [String] file: Upload + fileMarkings: [String] } type StixFile implements BasicObject & StixObject & StixCoreObject & StixCyberObservable & HashedObservable { @@ -6668,6 +6701,7 @@ input StixFileAddInput { x_opencti_additional_names: [String] obsContent: ID file: Upload + fileMarkings: [String] } type X509Certificate implements BasicObject & StixObject & StixCoreObject & StixCyberObservable & HashedObservable { @@ -6776,6 +6810,7 @@ input X509CertificateAddInput { certificate_policies: String policy_mappings: String file: Upload + fileMarkings: [String] } type IPv4Addr implements BasicObject & StixObject & StixCoreObject & StixCyberObservable { @@ -6833,6 +6868,7 @@ input IPv4AddrAddInput { belongsTo: [String] resolvesTo: [String] file: Upload + fileMarkings: [String] } type IPv6Addr implements BasicObject & StixObject & StixCoreObject & StixCyberObservable { @@ -6888,6 +6924,7 @@ type IPv6Addr implements BasicObject & StixObject & StixCoreObject & StixCyberOb input IPv6AddrAddInput { value: String file: Upload + fileMarkings: [String] } type MacAddr implements BasicObject & StixObject & StixCoreObject & StixCyberObservable { @@ -6942,6 +6979,7 @@ type MacAddr implements BasicObject & StixObject & StixCoreObject & StixCyberObs input MacAddrAddInput { value: String file: Upload + fileMarkings: [String] } type Mutex implements BasicObject & StixObject & StixCoreObject & StixCyberObservable { @@ -6996,6 +7034,7 @@ type Mutex implements BasicObject & StixObject & StixCoreObject & StixCyberObser input MutexAddInput { name: String file: Upload + fileMarkings: [String] } type NetworkTraffic implements BasicObject & StixObject & StixCoreObject & StixCyberObservable { @@ -7073,6 +7112,7 @@ input NetworkTrafficAddInput { src_packets: Int dst_packets: Int file: Upload + fileMarkings: [String] } type Process implements BasicObject & StixObject & StixCoreObject & StixCyberObservable { @@ -7169,6 +7209,7 @@ input ProcessAddInput { service_type: String service_status: String file: Upload + fileMarkings: [String] } type Software implements BasicObject & StixObject & StixCoreObject & StixCyberObservable { @@ -7246,6 +7287,7 @@ input SoftwareAddInput { version: String x_opencti_product: String file: Upload + fileMarkings: [String] } type Url implements BasicObject & StixObject & StixCoreObject & StixCyberObservable { @@ -7300,6 +7342,7 @@ type Url implements BasicObject & StixObject & StixCoreObject & StixCyberObserva input UrlAddInput { value: String file: Upload + fileMarkings: [String] } type UserAccount implements BasicObject & StixObject & StixCoreObject & StixCyberObservable { @@ -7381,6 +7424,7 @@ input UserAccountAddInput { account_first_login: DateTime account_last_login: DateTime file: Upload + fileMarkings: [String] } type WindowsRegistryKey implements BasicObject & StixObject & StixCoreObject & StixCyberObservable { @@ -7438,6 +7482,7 @@ input WindowsRegistryKeyAddInput { attribute_key: String modified_time: DateTime file: Upload + fileMarkings: [String] number_of_subkeys: Int } @@ -7497,6 +7542,7 @@ input WindowsRegistryValueTypeAddInput { data: String data_type: String file: Upload + fileMarkings: [String] } type CryptographicKey implements BasicObject & StixObject & StixCoreObject & StixCyberObservable { @@ -7551,6 +7597,7 @@ type CryptographicKey implements BasicObject & StixObject & StixCoreObject & Sti input CryptographicKeyAddInput { value: String file: Upload + fileMarkings: [String] } type CryptocurrencyWallet implements BasicObject & StixObject & StixCoreObject & StixCyberObservable { @@ -7605,6 +7652,7 @@ type CryptocurrencyWallet implements BasicObject & StixObject & StixCoreObject & input CryptocurrencyWalletAddInput { value: String file: Upload + fileMarkings: [String] } type Hostname implements BasicObject & StixObject & StixCoreObject & StixCyberObservable { @@ -7659,6 +7707,7 @@ type Hostname implements BasicObject & StixObject & StixCoreObject & StixCyberOb input HostnameAddInput { value: String file: Upload + fileMarkings: [String] } type Text implements BasicObject & StixObject & StixCoreObject & StixCyberObservable { @@ -7713,6 +7762,7 @@ type Text implements BasicObject & StixObject & StixCoreObject & StixCyberObserv input TextAddInput { value: String file: Upload + fileMarkings: [String] } type UserAgent implements BasicObject & StixObject & StixCoreObject & StixCyberObservable { @@ -7767,6 +7817,7 @@ type UserAgent implements BasicObject & StixObject & StixCoreObject & StixCyberO input UserAgentAddInput { value: String file: Upload + fileMarkings: [String] } type BankAccount implements BasicObject & StixObject & StixCoreObject & StixCyberObservable { @@ -7825,6 +7876,7 @@ input BankAccountAddInput { bic: String account_number: String file: Upload + fileMarkings: [String] } type TrackingNumber implements BasicObject & StixObject & StixCoreObject & StixCyberObservable { @@ -7879,6 +7931,7 @@ type TrackingNumber implements BasicObject & StixObject & StixCoreObject & StixC input TrackingNumberAddInput { value: String file: Upload + fileMarkings: [String] } type Credential implements BasicObject & StixObject & StixCoreObject & StixCyberObservable { @@ -7933,6 +7986,7 @@ type Credential implements BasicObject & StixObject & StixCoreObject & StixCyber input CredentialAddInput { value: String file: Upload + fileMarkings: [String] } type PhoneNumber implements BasicObject & StixObject & StixCoreObject & StixCyberObservable { @@ -7987,6 +8041,7 @@ type PhoneNumber implements BasicObject & StixObject & StixCoreObject & StixCybe input PhoneNumberAddInput { value: String file: Upload + fileMarkings: [String] } type PaymentCard implements BasicObject & StixObject & StixCoreObject & StixCyberObservable { @@ -8047,6 +8102,7 @@ input PaymentCardAddInput { cvv: Int holder_name: String file: Upload + fileMarkings: [String] } type MediaContent implements BasicObject & StixObject & StixCoreObject & StixCyberObservable { @@ -8110,6 +8166,7 @@ input MediaContentAddInput { url: String! publication_date: DateTime file: Upload + fileMarkings: [String] } type Persona implements BasicObject & StixObject & StixCoreObject & StixCyberObservable { @@ -8166,6 +8223,8 @@ input PersonaAddInput { "*Constraints:*\n* Minimal length: `2`\n* Must match format: `not-blank`\n" persona_name: String! persona_type: String! + file: Upload + fileMarkings: [String] } type SSHKey implements BasicObject & StixObject & StixCoreObject & StixCyberObservable { @@ -8237,6 +8296,8 @@ input SSHKeyAddInput { comment: String created: DateTime expiration_date: DateTime + file: Upload + fileMarkings: [String] } interface BasicRelationship { @@ -8717,6 +8778,7 @@ input StixRefRelationshipAddInput { clientMutationId: String update: Boolean file: Upload + fileMarkings: [String] } input StixRefRelationshipsAddInput { @@ -9746,7 +9808,7 @@ type Mutation { stixCyberObservableEdit(id: ID!): StixCyberObservableEditMutations stixCyberObservablesExportAsk(input: StixCyberObservablesExportAskInput!): [File!] stixCyberObservablesExportPush(entity_id: String, entity_type: String!, file: Upload!, file_markings: [String]!, listFilters: String): Boolean - artifactImport(file: Upload!, x_opencti_description: String, createdBy: String, objectMarking: [String], objectLabel: [String]): Artifact + artifactImport(file: Upload!, fileMarkings: [String], x_opencti_description: String, createdBy: String, objectMarking: [String], objectLabel: [String]): Artifact stixRelationshipEdit(id: ID!): StixRelationshipEditMutations stixCoreRelationshipAdd(input: StixCoreRelationshipAddInput, reversedReturn: Boolean): StixCoreRelationship stixCoreRelationshipEdit(id: ID!): StixCoreRelationshipEditMutations @@ -10148,6 +10210,7 @@ input ChannelAddInput { clientMutationId: String update: Boolean file: Upload + fileMarkings: [String] } type Catalog implements InternalObject & BasicObject { @@ -10283,6 +10346,8 @@ input LanguageAddInput { x_opencti_workflow_id: String x_opencti_modified_at: DateTime update: Boolean + file: Upload + fileMarkings: [String] } type Event implements BasicObject & StixCoreObject & StixDomainObject & StixObject { @@ -10397,6 +10462,7 @@ input EventAddInput { x_opencti_modified_at: DateTime update: Boolean file: Upload + fileMarkings: [String] } type Grouping implements BasicObject & StixObject & StixCoreObject & StixDomainObject & Container { @@ -10522,6 +10588,7 @@ input GroupingAddInput { x_opencti_modified_at: DateTime update: Boolean file: Upload + fileMarkings: [String] authorized_members: [MemberAccessInput!] } @@ -10633,6 +10700,7 @@ input NarrativeAddInput { clientMutationId: String update: Boolean file: Upload + fileMarkings: [String] x_opencti_modified_at: DateTime x_opencti_workflow_id: String } @@ -10910,6 +10978,7 @@ input DataComponentAddInput { aliases: [String] dataSource: String file: Upload + fileMarkings: [String] x_opencti_workflow_id: String x_opencti_modified_at: DateTime } @@ -11021,6 +11090,7 @@ input DataSourceAddInput { collection_layers: [String!] dataComponents: [String] file: Upload + fileMarkings: [String] x_opencti_workflow_id: String x_opencti_modified_at: DateTime } @@ -11261,6 +11331,7 @@ input AdministrativeAreaAddInput { x_opencti_modified_at: DateTime update: Boolean file: Upload + fileMarkings: [String] } type Task implements Container & StixDomainObject & StixCoreObject & StixObject & BasicObject { @@ -11373,6 +11444,8 @@ input TaskAddInput { x_opencti_workflow_id: String x_opencti_modified_at: DateTime update: Boolean + file: Upload + fileMarkings: [String] } type TaskTemplate implements InternalObject & BasicObject { @@ -11675,6 +11748,7 @@ input CaseIncidentAddInput { x_opencti_modified_at: DateTime x_opencti_workflow_id: String file: Upload + fileMarkings: [String] clientMutationId: String update: Boolean authorized_members: [MemberAccessInput!] @@ -11813,6 +11887,7 @@ input CaseRfiAddInput { x_opencti_modified_at: DateTime x_opencti_workflow_id: String file: Upload + fileMarkings: [String] clientMutationId: String update: Boolean information_types: [String!] @@ -11945,6 +12020,7 @@ input CaseRftAddInput { created: DateTime modified: DateTime file: Upload + fileMarkings: [String] clientMutationId: String x_opencti_modified_at: DateTime x_opencti_workflow_id: String @@ -12073,6 +12149,7 @@ input FeedbackAddInput { x_opencti_workflow_id: String x_opencti_modified_at: DateTime file: Upload + fileMarkings: [String] clientMutationId: String update: Boolean rating: Int @@ -12360,6 +12437,7 @@ input MalwareAnalysisAddInput { x_opencti_modified_at: DateTime update: Boolean file: Upload + fileMarkings: [String] } type ManagerConfiguration implements InternalObject & BasicObject { @@ -12599,6 +12677,7 @@ input ThreatActorIndividualAddInput { x_opencti_modified_at: DateTime update: Boolean file: Upload + fileMarkings: [String] } type PlayBookExecutionStep { @@ -13267,6 +13346,7 @@ input IndicatorAddInput { x_opencti_modified_at: DateTime x_opencti_workflow_id: String file: Upload + fileMarkings: [String] basedOn: [String!] } @@ -13492,6 +13572,7 @@ input OrganizationAddInput { clientMutationId: String update: Boolean file: Upload + fileMarkings: [String] } type MeOrganization { @@ -14617,6 +14698,8 @@ input SecurityPlatformAddInput { x_opencti_modified_at: DateTime externalReferences: [String] update: Boolean + file: Upload + fileMarkings: [String] } type CoverageResult { @@ -14741,6 +14824,8 @@ input SecurityCoverageAddInput { x_opencti_modified_at: DateTime external_uri: String externalReferences: [String] + file: Upload + fileMarkings: [String] update: Boolean auto_enrichment_disable: Boolean! periodicity: String diff --git a/opencti-platform/opencti-front/src/utils/htmlToPdf/utils/pdfUnnecessarytHtml.test.ts b/opencti-platform/opencti-front/src/utils/htmlToPdf/utils/pdfUnnecessarytHtml.test.ts new file mode 100644 index 000000000000..fe6d990931ad --- /dev/null +++ b/opencti-platform/opencti-front/src/utils/htmlToPdf/utils/pdfUnnecessarytHtml.test.ts @@ -0,0 +1,145 @@ +import { describe, it, expect } from 'vitest'; +import removeUnnecessaryHtml from './pdfUnnecessarytHtml'; + +describe('Utils: removeUnnecessaryHtml', () => { + it('should remove script tags and their content', () => { + const html = '
          ContentMore content
          '; + const result = removeUnnecessaryHtml(html); + expect(result).not.toContain(' +

          Regular paragraph content.

          + + +

          More important content that must be preserved.

          + +
          +

    + `; + const result = removeUnnecessaryHtml(html); + + // Verify structure is preserved + expect(result).toContain('article-container'); + expect(result).toContain('Article Title'); + expect(result).toContain('article-body'); + + // Verify all paragraphs are preserved + expect(result).toContain('Lead paragraph with critical info.'); + expect(result).toContain('Regular paragraph content.'); + expect(result).toContain('More important content that must be preserved.'); + + // Verify unwanted elements are removed + expect(result).not.toContain(''); + expect(result).not.toContain(''); + expect(result).not.toContain(''); + expect(result).not.toContain('Please enable JavaScript'); + }); +}); diff --git a/opencti-platform/opencti-front/src/utils/htmlToPdf/utils/pdfUnnecessarytHtml.ts b/opencti-platform/opencti-front/src/utils/htmlToPdf/utils/pdfUnnecessarytHtml.ts index 9b11dbc0a1c1..69435bae1f47 100644 --- a/opencti-platform/opencti-front/src/utils/htmlToPdf/utils/pdfUnnecessarytHtml.ts +++ b/opencti-platform/opencti-front/src/utils/htmlToPdf/utils/pdfUnnecessarytHtml.ts @@ -1,13 +1,40 @@ /** * There are some tags we don't want in the generated PDF. + * This function cleans up HTML content to ensure proper PDF generation. * * @param content The content to analyse. - * @returns Content without necessary stuff. + * @returns Content without unnecessary stuff but preserving article body. */ const removeUnnecessaryHtml = (content: string) => { - return content - .replaceAll('id="undefined" ', '') // ??? - .replaceAll(/]+src=(\\?["'])[^'"]+\.gif\1[^>]*\/?>/gi, ''); // Remove GIFs from content. + let cleanedContent = content + .replace(/id="undefined" /g, '') // Remove undefined IDs + .replace(/]+src=(\\?["'])[^'"]+\.gif\1[^>]*\/?>/gi, ''); // Remove GIFs from content + + // Remove script tags and their content + cleanedContent = cleanedContent.replace(/)<[^<]*)*<\/script>/gi, ''); + + // Remove style tags and their content (inline styles on elements are preserved) + cleanedContent = cleanedContent.replace(/)<[^<]*)*<\/style>/gi, ''); + + // Remove iframe tags + cleanedContent = cleanedContent.replace(/)<[^<]*)*<\/iframe>/gi, ''); + + // Remove noscript tags + cleanedContent = cleanedContent.replace(/)<[^<]*)*<\/noscript>/gi, ''); + + // Remove comments + cleanedContent = cleanedContent.replace(//g, ''); + + // Remove empty class attributes + cleanedContent = cleanedContent.replace(/\s+class=""\s*/g, ' '); + + // Remove empty style attributes + cleanedContent = cleanedContent.replace(/\s+style=""\s*/g, ' '); + + // Clean up multiple consecutive whitespace (but preserve single spaces and newlines) + cleanedContent = cleanedContent.replace(/[ \t]+/g, ' '); + + return cleanedContent; }; export default removeUnnecessaryHtml; diff --git a/opencti-platform/opencti-graphql/config/schema/opencti.graphql b/opencti-platform/opencti-graphql/config/schema/opencti.graphql index 7a11bf4a2774..3d8f98674264 100644 --- a/opencti-platform/opencti-graphql/config/schema/opencti.graphql +++ b/opencti-platform/opencti-graphql/config/schema/opencti.graphql @@ -2627,6 +2627,7 @@ input ExternalReferenceAddInput { url: String hash: String file: Upload + fileMarkings: [String] external_id: String created: DateTime modified: DateTime @@ -3279,6 +3280,7 @@ input AttackPatternAddInput { clientMutationId: String update: Boolean file: Upload + fileMarkings: [String] } type AttackPatternForMatrix { attack_pattern_id: String! @@ -3489,6 +3491,7 @@ input CampaignAddInput { clientMutationId: String update: Boolean file: Upload + fileMarkings: [String] } ############## Containers @@ -3867,6 +3870,7 @@ input NoteAddInput { clientMutationId: String update: Boolean file: Upload + fileMarkings: [String] } input NoteUserAddInput { stix_id: String @@ -4102,6 +4106,7 @@ input ObservedDataAddInput { clientMutationId: String update: Boolean file: Upload + fileMarkings: [String] } ################ Opinions @@ -4315,6 +4320,7 @@ input OpinionAddInput { clientMutationId: String update: Boolean file: Upload + fileMarkings: [String] } input OpinionUserAddInput { stix_id: String @@ -4561,6 +4567,7 @@ input ReportAddInput { clientMutationId: String update: Boolean file: Upload + fileMarkings: [String] authorized_members: [MemberAccessInput!] } @@ -4743,6 +4750,7 @@ input CourseOfActionAddInput { clientMutationId: String update: Boolean file: Upload + fileMarkings: [String] } ############## Identities @@ -4923,6 +4931,8 @@ input IdentityAddInput { clientMutationId: String created: DateTime modified: DateTime + file: Upload + fileMarkings: [String] update: Boolean } @@ -5113,6 +5123,7 @@ input IndividualAddInput { clientMutationId: String update: Boolean file: Upload + fileMarkings: [String] } ################ Sectors @@ -5299,6 +5310,7 @@ input SectorAddInput { clientMutationId: String update: Boolean file: Upload + fileMarkings: [String] } ################ Systems @@ -5487,6 +5499,7 @@ input SystemAddInput { clientMutationId: String update: Boolean file: Upload + fileMarkings: [String] } ############## Infrastructures @@ -5676,6 +5689,7 @@ input InfrastructureAddInput { clientMutationId: String update: Boolean file: Upload + fileMarkings: [String] } ############## IntrusionSets @@ -5869,6 +5883,7 @@ input IntrusionSetAddInput { clientMutationId: String update: Boolean file: Upload + fileMarkings: [String] } ############## Locations @@ -6047,6 +6062,8 @@ input LocationAddInput { x_opencti_modified_at: DateTime x_opencti_workflow_id: String update: Boolean + file: Upload + fileMarkings: [String] } ################ Positions @@ -6234,6 +6251,7 @@ input PositionAddInput { clientMutationId: String update: Boolean file: Upload + fileMarkings: [String] } ################ Cities @@ -6420,6 +6438,7 @@ input CityAddInput { clientMutationId: String update: Boolean file: Upload + fileMarkings: [String] } enum CountriesOrdering { @@ -6600,6 +6619,7 @@ input CountryAddInput { clientMutationId: String update: Boolean file: Upload + fileMarkings: [String] } ################ Regions @@ -6783,6 +6803,7 @@ input RegionAddInput { clientMutationId: String update: Boolean file: Upload + fileMarkings: [String] } ############## Malware @@ -6987,6 +7008,7 @@ input MalwareAddInput { clientMutationId: String update: Boolean file: Upload + fileMarkings: [String] } ############## ThreatActorsGroup @@ -7348,6 +7370,7 @@ input ThreatActorGroupAddInput { clientMutationId: String update: Boolean file: Upload + fileMarkings: [String] } ############## Tools @@ -7529,6 +7552,7 @@ input ToolAddInput { clientMutationId: String update: Boolean file: Upload + fileMarkings: [String] } ############## Vulnerabilities @@ -7813,6 +7837,7 @@ input VulnerabilityAddInput { clientMutationId: String update: Boolean file: Upload + fileMarkings: [String] } ############## Incident @@ -8011,6 +8036,7 @@ input IncidentAddInput { clientMutationId: String update: Boolean file: Upload + fileMarkings: [String] } ######## STIX CYBER OBSERVABLES ENTITIES @@ -8282,6 +8308,7 @@ input AutonomousSystemAddInput { name: String rir: String file: Upload + fileMarkings: [String] } type Directory implements BasicObject & StixObject & StixCoreObject & StixCyberObservable { id: ID! # internal_id @@ -8417,6 +8444,7 @@ input DirectoryAddInput { mtime: DateTime atime: DateTime file: Upload + fileMarkings: [String] } type DomainName implements BasicObject & StixObject & StixCoreObject & StixCyberObservable { id: ID! # internal_id @@ -8544,6 +8572,7 @@ type DomainName implements BasicObject & StixObject & StixCoreObject & StixCyber input DomainNameAddInput { value: String! file: Upload + fileMarkings: [String] } type EmailAddr implements BasicObject & StixObject & StixCoreObject & StixCyberObservable { id: ID! # internal_id @@ -8673,6 +8702,7 @@ input EmailAddrAddInput { value: String display_name: String file: Upload + fileMarkings: [String] } type EmailMessage implements BasicObject & StixObject & StixCoreObject & StixCyberObservable { id: ID! # internal_id @@ -8812,6 +8842,7 @@ input EmailMessageAddInput { received_lines: [String] body: String file: Upload + fileMarkings: [String] } type EmailMimePartType implements BasicObject & StixObject & StixCoreObject & StixCyberObservable { id: ID! # internal_id @@ -8943,6 +8974,7 @@ input EmailMimePartTypeAddInput { content_type: String content_disposition: String file: Upload + fileMarkings: [String] } ############## HashedObservable input HashInput { @@ -9222,6 +9254,7 @@ input ArtifactAddInput { decryption_key: String x_opencti_additional_names: [String] file: Upload + fileMarkings: [String] } type StixFile implements BasicObject & StixObject & StixCoreObject & StixCyberObservable & HashedObservable { id: ID! # internal_id @@ -9371,6 +9404,7 @@ input StixFileAddInput { x_opencti_additional_names: [String] obsContent: ID file: Upload + fileMarkings: [String] } type X509Certificate implements BasicObject & StixObject & StixCoreObject & StixCyberObservable & HashedObservable { id: ID! # internal_id @@ -9555,6 +9589,7 @@ input X509CertificateAddInput { certificate_policies: String policy_mappings: String file: Upload + fileMarkings: [String] } type IPv4Addr implements BasicObject & StixObject & StixCoreObject & StixCyberObservable { id: ID! # internal_id @@ -9685,6 +9720,7 @@ input IPv4AddrAddInput { belongsTo: [String] resolvesTo: [String] file: Upload + fileMarkings: [String] } type IPv6Addr implements BasicObject & StixObject & StixCoreObject & StixCyberObservable { id: ID! # internal_id @@ -9813,6 +9849,7 @@ type IPv6Addr implements BasicObject & StixObject & StixCoreObject & StixCyberOb input IPv6AddrAddInput { value: String file: Upload + fileMarkings: [String] } type MacAddr implements BasicObject & StixObject & StixCoreObject & StixCyberObservable { id: ID! # internal_id @@ -9940,6 +9977,7 @@ type MacAddr implements BasicObject & StixObject & StixCoreObject & StixCyberObs input MacAddrAddInput { value: String file: Upload + fileMarkings: [String] } type Mutex implements BasicObject & StixObject & StixCoreObject & StixCyberObservable { id: ID! # internal_id @@ -10067,6 +10105,7 @@ type Mutex implements BasicObject & StixObject & StixCoreObject & StixCyberObser input MutexAddInput { name: String file: Upload + fileMarkings: [String] } type NetworkTraffic implements BasicObject & StixObject & StixCoreObject & StixCyberObservable { id: ID! # internal_id @@ -10217,6 +10256,7 @@ input NetworkTrafficAddInput { src_packets: Int dst_packets: Int file: Upload + fileMarkings: [String] } type Process implements BasicObject & StixObject & StixCoreObject & StixCyberObservable { id: ID! # internal_id @@ -10390,6 +10430,7 @@ input ProcessAddInput { service_type: String # windows-service-type-enum service_status: String # windows-service-status-enum file: Upload + fileMarkings: [String] } type Software implements BasicObject & StixObject & StixCoreObject & StixCyberObservable { id: ID! # internal_id @@ -10538,6 +10579,7 @@ input SoftwareAddInput { version: String x_opencti_product: String file: Upload + fileMarkings: [String] } type Url implements BasicObject & StixObject & StixCoreObject & StixCyberObservable { id: ID! # internal_id @@ -10665,6 +10707,7 @@ type Url implements BasicObject & StixObject & StixCoreObject & StixCyberObserva input UrlAddInput { value: String file: Upload + fileMarkings: [String] } type UserAccount implements BasicObject & StixObject & StixCoreObject & StixCyberObservable { id: ID! # internal_id @@ -10819,6 +10862,7 @@ input UserAccountAddInput { account_first_login: DateTime account_last_login: DateTime file: Upload + fileMarkings: [String] } type WindowsRegistryKey implements BasicObject & StixObject & StixCoreObject & StixCyberObservable { id: ID! # internal_id @@ -10949,6 +10993,7 @@ input WindowsRegistryKeyAddInput { attribute_key: String modified_time: DateTime file: Upload + fileMarkings: [String] number_of_subkeys: Int } type WindowsRegistryValueType implements BasicObject & StixObject & StixCoreObject & StixCyberObservable { @@ -11081,6 +11126,7 @@ input WindowsRegistryValueTypeAddInput { data: String data_type: String file: Upload + fileMarkings: [String] } type CryptographicKey implements BasicObject & StixObject & StixCoreObject & StixCyberObservable { id: ID! # internal_id @@ -11208,6 +11254,7 @@ type CryptographicKey implements BasicObject & StixObject & StixCoreObject & Sti input CryptographicKeyAddInput { value: String file: Upload + fileMarkings: [String] } type CryptocurrencyWallet implements BasicObject & StixObject & StixCoreObject & StixCyberObservable { id: ID! # internal_id @@ -11335,6 +11382,7 @@ type CryptocurrencyWallet implements BasicObject & StixObject & StixCoreObject & input CryptocurrencyWalletAddInput { value: String file: Upload + fileMarkings: [String] } type Hostname implements BasicObject & StixObject & StixCoreObject & StixCyberObservable { id: ID! # internal_id @@ -11462,6 +11510,7 @@ type Hostname implements BasicObject & StixObject & StixCoreObject & StixCyberOb input HostnameAddInput { value: String file: Upload + fileMarkings: [String] } type Text implements BasicObject & StixObject & StixCoreObject & StixCyberObservable { id: ID! # internal_id @@ -11589,6 +11638,7 @@ type Text implements BasicObject & StixObject & StixCoreObject & StixCyberObserv input TextAddInput { value: String file: Upload + fileMarkings: [String] } type UserAgent implements BasicObject & StixObject & StixCoreObject & StixCyberObservable { id: ID! # internal_id @@ -11716,6 +11766,7 @@ type UserAgent implements BasicObject & StixObject & StixCoreObject & StixCyberO input UserAgentAddInput { value: String file: Upload + fileMarkings: [String] } type BankAccount implements BasicObject & StixObject & StixCoreObject & StixCyberObservable { id: ID! # internal_id @@ -11848,6 +11899,7 @@ input BankAccountAddInput { bic: String account_number: String file: Upload + fileMarkings: [String] } type TrackingNumber implements BasicObject & StixObject & StixCoreObject & StixCyberObservable { id: ID! # internal_id @@ -11978,6 +12030,7 @@ type TrackingNumber implements BasicObject & StixObject & StixCoreObject & StixC input TrackingNumberAddInput { value: String file: Upload + fileMarkings: [String] } type Credential implements BasicObject & StixObject & StixCoreObject & StixCyberObservable { id: ID! # internal_id @@ -12108,6 +12161,7 @@ type Credential implements BasicObject & StixObject & StixCoreObject & StixCyber input CredentialAddInput { value: String file: Upload + fileMarkings: [String] } type PhoneNumber implements BasicObject & StixObject & StixCoreObject & StixCyberObservable { id: ID! # internal_id @@ -12235,6 +12289,7 @@ type PhoneNumber implements BasicObject & StixObject & StixCoreObject & StixCybe input PhoneNumberAddInput { value: String file: Upload + fileMarkings: [String] } type PaymentCard implements BasicObject & StixObject & StixCoreObject & StixCyberObservable { id: ID! # internal_id @@ -12368,6 +12423,7 @@ input PaymentCardAddInput { cvv: Int holder_name: String file: Upload + fileMarkings: [String] } type MediaContent implements BasicObject & StixObject & StixCoreObject & StixCyberObservable { id: ID! # internal_id @@ -12504,6 +12560,7 @@ input MediaContentAddInput { url: String! publication_date: DateTime file: Upload + fileMarkings: [String] } type Persona implements BasicObject & StixObject & StixCoreObject & StixCyberObservable { id: ID! # internal_id @@ -12632,6 +12689,8 @@ type Persona implements BasicObject & StixObject & StixCoreObject & StixCyberObs input PersonaAddInput { persona_name: String! @constraint(minLength: 2, format: "not-blank") persona_type: String! + file: Upload + fileMarkings: [String] } type SSHKey implements BasicObject & StixObject & StixCoreObject & StixCyberObservable { @@ -12773,6 +12832,8 @@ input SSHKeyAddInput { comment: String created: DateTime expiration_date: DateTime + file: Upload + fileMarkings: [String] } ###### RELATIONSHIPS @@ -13541,6 +13602,7 @@ input StixRefRelationshipAddInput { clientMutationId: String update: Boolean file: Upload + fileMarkings: [String] } input StixRefRelationshipsAddInput { relationship_type: String! @@ -15524,6 +15586,7 @@ type Mutation { stixCyberObservablesExportPush(entity_id: String, entity_type: String!, file: Upload!, file_markings: [String]!, listFilters: String): Boolean @auth(for: [CONNECTORAPI]) artifactImport( file: Upload! + fileMarkings: [String] x_opencti_description: String createdBy: String objectMarking: [String] diff --git a/opencti-platform/opencti-graphql/src/database/data-builder.js b/opencti-platform/opencti-graphql/src/database/data-builder.js index 5e57c464819a..2849ac2bccdc 100644 --- a/opencti-platform/opencti-graphql/src/database/data-builder.js +++ b/opencti-platform/opencti-graphql/src/database/data-builder.js @@ -43,6 +43,7 @@ export const buildEntityData = async (context, user, input, type, opts = {}) => R.assoc('creator_id', [user.internal_id]), R.dissoc('update'), R.dissoc('file'), + R.dissoc('fileMarkings'), R.omit(schemaRelationsRefDefinition.getInputNames(input.entity_type)), )(input); if (inferred) { diff --git a/opencti-platform/opencti-graphql/src/database/middleware.js b/opencti-platform/opencti-graphql/src/database/middleware.js index e5b4b4d9519b..36f7d29790a1 100644 --- a/opencti-platform/opencti-graphql/src/database/middleware.js +++ b/opencti-platform/opencti-graphql/src/database/middleware.js @@ -3345,7 +3345,7 @@ const createEntityRaw = async (context, user, rawInput, type, opts = {}) => { const path = `import/${type}/${dataEntity.element[ID_INTERNAL]}`; const key = `${path}/${filename}`; const meta = isAutoExternal ? { external_reference_id: generateStandardId(ENTITY_TYPE_EXTERNAL_REFERENCE, { url: `/storage/get/${key}` }) } : {}; - const file_markings = resolvedInput.objectMarking?.map(({ id }) => id); + const file_markings = isNotEmptyField(resolvedInput.fileMarkings) ? resolvedInput.fileMarkings : resolvedInput.objectMarking?.map(({ id }) => id); const { upload: file } = await uploadToStorage(context, user, path, input.file, { entity: dataEntity.element, file_markings, meta }); dataEntity.element = { ...dataEntity.element, x_opencti_files: [storeFileConverter(user, file)] }; // Add external references from files if necessary diff --git a/opencti-platform/opencti-graphql/src/generated/graphql.ts b/opencti-platform/opencti-graphql/src/generated/graphql.ts index c772e06349b1..a2cc3f829335 100644 --- a/opencti-platform/opencti-graphql/src/generated/graphql.ts +++ b/opencti-platform/opencti-graphql/src/generated/graphql.ts @@ -316,6 +316,7 @@ export type AdministrativeAreaAddInput = { description?: InputMaybe; externalReferences?: InputMaybe>>; file?: InputMaybe; + fileMarkings?: InputMaybe>>; lang?: InputMaybe; latitude?: InputMaybe; longitude?: InputMaybe; @@ -653,6 +654,7 @@ export type ArtifactAddInput = { decryption_key?: InputMaybe; encryption_algorithm?: InputMaybe; file?: InputMaybe; + fileMarkings?: InputMaybe>>; hashes?: InputMaybe>>; mime_type?: InputMaybe; payload_bin?: InputMaybe; @@ -942,6 +944,7 @@ export type AttackPatternAddInput = { description?: InputMaybe; externalReferences?: InputMaybe>>; file?: InputMaybe; + fileMarkings?: InputMaybe>>; killChainPhases?: InputMaybe>>; lang?: InputMaybe; modified?: InputMaybe; @@ -1353,6 +1356,7 @@ export type AutonomousSystemToStixArgs = { export type AutonomousSystemAddInput = { file?: InputMaybe; + fileMarkings?: InputMaybe>>; name?: InputMaybe; number: Scalars['Int']['input']; rir?: InputMaybe; @@ -1686,6 +1690,7 @@ export type BankAccountAddInput = { account_number?: InputMaybe; bic?: InputMaybe; file?: InputMaybe; + fileMarkings?: InputMaybe>>; iban?: InputMaybe; }; @@ -1953,6 +1958,7 @@ export type CampaignAddInput = { description?: InputMaybe; externalReferences?: InputMaybe>>; file?: InputMaybe; + fileMarkings?: InputMaybe>>; first_seen?: InputMaybe; lang?: InputMaybe; last_seen?: InputMaybe; @@ -2647,6 +2653,7 @@ export type CaseIncidentAddInput = { description?: InputMaybe; externalReferences?: InputMaybe>>; file?: InputMaybe; + fileMarkings?: InputMaybe>>; lang?: InputMaybe; modified?: InputMaybe; name: Scalars['String']['input']; @@ -2969,6 +2976,7 @@ export type CaseRfiAddInput = { description?: InputMaybe; externalReferences?: InputMaybe>>; file?: InputMaybe; + fileMarkings?: InputMaybe>>; information_types?: InputMaybe>; lang?: InputMaybe; modified?: InputMaybe; @@ -3288,6 +3296,7 @@ export type CaseRftAddInput = { description?: InputMaybe; externalReferences?: InputMaybe>>; file?: InputMaybe; + fileMarkings?: InputMaybe>>; lang?: InputMaybe; modified?: InputMaybe; name: Scalars['String']['input']; @@ -3658,6 +3667,7 @@ export type ChannelAddInput = { description?: InputMaybe; externalReferences?: InputMaybe>>; file?: InputMaybe; + fileMarkings?: InputMaybe>>; lang?: InputMaybe; modified?: InputMaybe; name: Scalars['String']['input']; @@ -3939,6 +3949,7 @@ export type CityAddInput = { description?: InputMaybe; externalReferences?: InputMaybe>>; file?: InputMaybe; + fileMarkings?: InputMaybe>>; lang?: InputMaybe; latitude?: InputMaybe; longitude?: InputMaybe; @@ -4727,6 +4738,7 @@ export type CountryAddInput = { description?: InputMaybe; externalReferences?: InputMaybe>>; file?: InputMaybe; + fileMarkings?: InputMaybe>>; lang?: InputMaybe; latitude?: InputMaybe; longitude?: InputMaybe; @@ -5011,6 +5023,7 @@ export type CourseOfActionAddInput = { description?: InputMaybe; externalReferences?: InputMaybe>>; file?: InputMaybe; + fileMarkings?: InputMaybe>>; lang?: InputMaybe; modified?: InputMaybe; name: Scalars['String']['input']; @@ -5316,6 +5329,7 @@ export type CredentialToStixArgs = { export type CredentialAddInput = { file?: InputMaybe; + fileMarkings?: InputMaybe>>; value?: InputMaybe; }; @@ -5521,6 +5535,7 @@ export type CryptocurrencyWalletToStixArgs = { export type CryptocurrencyWalletAddInput = { file?: InputMaybe; + fileMarkings?: InputMaybe>>; value?: InputMaybe; }; @@ -5726,6 +5741,7 @@ export type CryptographicKeyToStixArgs = { export type CryptographicKeyAddInput = { file?: InputMaybe; + fileMarkings?: InputMaybe>>; value?: InputMaybe; }; @@ -6101,6 +6117,7 @@ export type DataComponentAddInput = { description?: InputMaybe; externalReferences?: InputMaybe>>; file?: InputMaybe; + fileMarkings?: InputMaybe>>; lang?: InputMaybe; modified?: InputMaybe; name: Scalars['String']['input']; @@ -6362,6 +6379,7 @@ export type DataSourceAddInput = { description?: InputMaybe; externalReferences?: InputMaybe>>; file?: InputMaybe; + fileMarkings?: InputMaybe>>; lang?: InputMaybe; modified?: InputMaybe; name: Scalars['String']['input']; @@ -6823,6 +6841,7 @@ export type DirectoryAddInput = { atime?: InputMaybe; ctime?: InputMaybe; file?: InputMaybe; + fileMarkings?: InputMaybe>>; mtime?: InputMaybe; path: Scalars['String']['input']; path_enc?: InputMaybe; @@ -7107,6 +7126,7 @@ export type DomainNameToStixArgs = { export type DomainNameAddInput = { file?: InputMaybe; + fileMarkings?: InputMaybe>>; value: Scalars['String']['input']; }; @@ -7460,6 +7480,7 @@ export type EmailAddrToStixArgs = { export type EmailAddrAddInput = { display_name?: InputMaybe; file?: InputMaybe; + fileMarkings?: InputMaybe>>; value?: InputMaybe; }; @@ -7674,6 +7695,7 @@ export type EmailMessageAddInput = { body?: InputMaybe; content_type?: InputMaybe; file?: InputMaybe; + fileMarkings?: InputMaybe>>; is_multipart?: InputMaybe; message_id?: InputMaybe; received_lines?: InputMaybe>>; @@ -7887,6 +7909,7 @@ export type EmailMimePartTypeAddInput = { content_disposition?: InputMaybe; content_type?: InputMaybe; file?: InputMaybe; + fileMarkings?: InputMaybe>>; }; export type EmailTemplate = BasicObject & InternalObject & { @@ -8202,6 +8225,7 @@ export type EventAddInput = { event_types?: InputMaybe>; externalReferences?: InputMaybe>>; file?: InputMaybe; + fileMarkings?: InputMaybe>>; lang?: InputMaybe; modified?: InputMaybe; name: Scalars['String']['input']; @@ -8395,6 +8419,7 @@ export type ExternalReferenceAddInput = { description?: InputMaybe; external_id?: InputMaybe; file?: InputMaybe; + fileMarkings?: InputMaybe>>; hash?: InputMaybe; modified?: InputMaybe; source_name: Scalars['String']['input']; @@ -8820,6 +8845,7 @@ export type FeedbackAddInput = { description?: InputMaybe; externalReferences?: InputMaybe>>; file?: InputMaybe; + fileMarkings?: InputMaybe>>; lang?: InputMaybe; modified?: InputMaybe; name: Scalars['String']['input']; @@ -9531,6 +9557,7 @@ export type GroupingAddInput = { description?: InputMaybe; externalReferences?: InputMaybe>>; file?: InputMaybe; + fileMarkings?: InputMaybe>>; lang?: InputMaybe; modified?: InputMaybe; name: Scalars['String']['input']; @@ -10008,6 +10035,7 @@ export type HostnameToStixArgs = { export type HostnameAddInput = { file?: InputMaybe; + fileMarkings?: InputMaybe>>; value?: InputMaybe; }; @@ -10215,6 +10243,7 @@ export type IPv4AddrToStixArgs = { export type IPv4AddrAddInput = { belongsTo?: InputMaybe>>; file?: InputMaybe; + fileMarkings?: InputMaybe>>; resolvesTo?: InputMaybe>>; value?: InputMaybe; }; @@ -10422,6 +10451,7 @@ export type IPv6AddrToStixArgs = { export type IPv6AddrAddInput = { file?: InputMaybe; + fileMarkings?: InputMaybe>>; value?: InputMaybe; }; @@ -10648,6 +10678,8 @@ export type IdentityAddInput = { createdBy?: InputMaybe; description?: InputMaybe; externalReferences?: InputMaybe>>; + file?: InputMaybe; + fileMarkings?: InputMaybe>>; lang?: InputMaybe; modified?: InputMaybe; name: Scalars['String']['input']; @@ -10953,6 +10985,7 @@ export type IncidentAddInput = { description?: InputMaybe; externalReferences?: InputMaybe>>; file?: InputMaybe; + fileMarkings?: InputMaybe>>; first_seen?: InputMaybe; incident_type?: InputMaybe; lang?: InputMaybe; @@ -11311,6 +11344,7 @@ export type IndicatorAddInput = { description?: InputMaybe; externalReferences?: InputMaybe>; file?: InputMaybe; + fileMarkings?: InputMaybe>>; indicator_types?: InputMaybe>; killChainPhases?: InputMaybe>; lang?: InputMaybe; @@ -11620,6 +11654,7 @@ export type IndividualAddInput = { description?: InputMaybe; externalReferences?: InputMaybe>>; file?: InputMaybe; + fileMarkings?: InputMaybe>>; lang?: InputMaybe; modified?: InputMaybe; name: Scalars['String']['input']; @@ -11931,6 +11966,7 @@ export type InfrastructureAddInput = { description?: InputMaybe; externalReferences?: InputMaybe>>; file?: InputMaybe; + fileMarkings?: InputMaybe>>; first_seen?: InputMaybe; infrastructure_types?: InputMaybe>>; killChainPhases?: InputMaybe>>; @@ -12618,6 +12654,7 @@ export type IntrusionSetAddInput = { description?: InputMaybe; externalReferences?: InputMaybe>>; file?: InputMaybe; + fileMarkings?: InputMaybe>>; first_seen?: InputMaybe; goals?: InputMaybe>>; lang?: InputMaybe; @@ -13204,6 +13241,8 @@ export type LanguageAddInput = { createdBy?: InputMaybe; description?: InputMaybe; externalReferences?: InputMaybe>>; + file?: InputMaybe; + fileMarkings?: InputMaybe>>; lang?: InputMaybe; modified?: InputMaybe; name: Scalars['String']['input']; @@ -13485,6 +13524,8 @@ export type LocationAddInput = { createdBy?: InputMaybe; description?: InputMaybe; externalReferences?: InputMaybe>>; + file?: InputMaybe; + fileMarkings?: InputMaybe>>; lang?: InputMaybe; latitude?: InputMaybe; longitude?: InputMaybe; @@ -13816,6 +13857,7 @@ export type MacAddrToStixArgs = { export type MacAddrAddInput = { file?: InputMaybe; + fileMarkings?: InputMaybe>>; value?: InputMaybe; }; @@ -14049,6 +14091,7 @@ export type MalwareAddInput = { description?: InputMaybe; externalReferences?: InputMaybe>>; file?: InputMaybe; + fileMarkings?: InputMaybe>>; first_seen?: InputMaybe; implementation_languages?: InputMaybe>>; is_family?: InputMaybe; @@ -14322,6 +14365,7 @@ export type MalwareAnalysisAddInput = { createdBy?: InputMaybe; externalReferences?: InputMaybe>>; file?: InputMaybe; + fileMarkings?: InputMaybe>>; hostVm?: InputMaybe; installedSoftware?: InputMaybe>>; lang?: InputMaybe; @@ -14883,6 +14927,7 @@ export type MediaContentToStixArgs = { export type MediaContentAddInput = { content?: InputMaybe; file?: InputMaybe; + fileMarkings?: InputMaybe>>; media_category?: InputMaybe; publication_date?: InputMaybe; title?: InputMaybe; @@ -15582,6 +15627,7 @@ export type MutationAiVictimGenerateReportArgs = { export type MutationArtifactImportArgs = { createdBy?: InputMaybe; file: Scalars['Upload']['input']; + fileMarkings?: InputMaybe>>; objectLabel?: InputMaybe>>; objectMarking?: InputMaybe>>; x_opencti_description?: InputMaybe; @@ -18134,6 +18180,7 @@ export type MutexToStixArgs = { export type MutexAddInput = { file?: InputMaybe; + fileMarkings?: InputMaybe>>; name?: InputMaybe; }; @@ -18366,6 +18413,7 @@ export type NarrativeAddInput = { description?: InputMaybe; externalReferences?: InputMaybe>>; file?: InputMaybe; + fileMarkings?: InputMaybe>>; lang?: InputMaybe; modified?: InputMaybe; name: Scalars['String']['input']; @@ -18624,6 +18672,7 @@ export type NetworkTrafficAddInput = { dst_port?: InputMaybe; end?: InputMaybe; file?: InputMaybe; + fileMarkings?: InputMaybe>>; is_active?: InputMaybe; networkDst?: InputMaybe; networkSrc?: InputMaybe; @@ -18898,6 +18947,7 @@ export type NoteAddInput = { createdBy?: InputMaybe; externalReferences?: InputMaybe>>; file?: InputMaybe; + fileMarkings?: InputMaybe>>; lang?: InputMaybe; likelihood?: InputMaybe; modified?: InputMaybe; @@ -19395,6 +19445,7 @@ export type ObservedDataAddInput = { createdBy?: InputMaybe; externalReferences?: InputMaybe>>; file?: InputMaybe; + fileMarkings?: InputMaybe>>; first_observed: Scalars['DateTime']['input']; lang?: InputMaybe; last_observed: Scalars['DateTime']['input']; @@ -19743,6 +19794,7 @@ export type OpinionAddInput = { explanation?: InputMaybe; externalReferences?: InputMaybe>>; file?: InputMaybe; + fileMarkings?: InputMaybe>>; lang?: InputMaybe; modified?: InputMaybe; objectLabel?: InputMaybe>>; @@ -20098,6 +20150,7 @@ export type OrganizationAddInput = { description?: InputMaybe; externalReferences?: InputMaybe>>; file?: InputMaybe; + fileMarkings?: InputMaybe>>; lang?: InputMaybe; modified?: InputMaybe; name: Scalars['String']['input']; @@ -20410,6 +20463,7 @@ export type PaymentCardAddInput = { cvv?: InputMaybe; expiration_date?: InputMaybe; file?: InputMaybe; + fileMarkings?: InputMaybe>>; holder_name?: InputMaybe; }; @@ -20615,6 +20669,8 @@ export type PersonaToStixArgs = { }; export type PersonaAddInput = { + file?: InputMaybe; + fileMarkings?: InputMaybe>>; persona_name: Scalars['String']['input']; persona_type: Scalars['String']['input']; }; @@ -20821,6 +20877,7 @@ export type PhoneNumberToStixArgs = { export type PhoneNumberAddInput = { file?: InputMaybe; + fileMarkings?: InputMaybe>>; value?: InputMaybe; }; @@ -21386,6 +21443,7 @@ export type PositionAddInput = { description?: InputMaybe; externalReferences?: InputMaybe>>; file?: InputMaybe; + fileMarkings?: InputMaybe>>; lang?: InputMaybe; latitude?: InputMaybe; longitude?: InputMaybe; @@ -21698,6 +21756,7 @@ export type ProcessAddInput = { display_name?: InputMaybe; environment_variables?: InputMaybe>>; file?: InputMaybe; + fileMarkings?: InputMaybe>>; group_name?: InputMaybe; integrity_level?: InputMaybe; is_hidden?: InputMaybe; @@ -25504,6 +25563,7 @@ export type RegionAddInput = { description?: InputMaybe; externalReferences?: InputMaybe>>; file?: InputMaybe; + fileMarkings?: InputMaybe>>; lang?: InputMaybe; latitude?: InputMaybe; longitude?: InputMaybe; @@ -25872,6 +25932,7 @@ export type ReportAddInput = { description?: InputMaybe; externalReferences?: InputMaybe>>; file?: InputMaybe; + fileMarkings?: InputMaybe>>; lang?: InputMaybe; modified?: InputMaybe; name: Scalars['String']['input']; @@ -26432,6 +26493,8 @@ export type SshKeyAddInput = { comment?: InputMaybe; created?: InputMaybe; expiration_date?: InputMaybe; + file?: InputMaybe; + fileMarkings?: InputMaybe>>; fingerprint_md5?: InputMaybe; fingerprint_sha256: Scalars['String']['input']; key_length?: InputMaybe; @@ -26713,6 +26776,7 @@ export type SectorAddInput = { description?: InputMaybe; externalReferences?: InputMaybe>>; file?: InputMaybe; + fileMarkings?: InputMaybe>>; lang?: InputMaybe; modified?: InputMaybe; name: Scalars['String']['input']; @@ -27025,6 +27089,8 @@ export type SecurityCoverageAddInput = { duration?: InputMaybe; externalReferences?: InputMaybe>>; external_uri?: InputMaybe; + file?: InputMaybe; + fileMarkings?: InputMaybe>>; modified?: InputMaybe; name: Scalars['String']['input']; objectCovered: Scalars['String']['input']; @@ -27292,6 +27358,8 @@ export type SecurityPlatformAddInput = { createdBy?: InputMaybe; description?: InputMaybe; externalReferences?: InputMaybe>>; + file?: InputMaybe; + fileMarkings?: InputMaybe>>; modified?: InputMaybe; name: Scalars['String']['input']; objectLabel?: InputMaybe>>; @@ -27678,6 +27746,7 @@ export type SoftwareToStixArgs = { export type SoftwareAddInput = { cpe?: InputMaybe; file?: InputMaybe; + fileMarkings?: InputMaybe>>; languages?: InputMaybe>>; name?: InputMaybe; swid?: InputMaybe; @@ -29368,6 +29437,7 @@ export type StixFileAddInput = { atime?: InputMaybe; ctime?: InputMaybe; file?: InputMaybe; + fileMarkings?: InputMaybe>>; hashes?: InputMaybe>>; magic_number_hex?: InputMaybe; mime_type?: InputMaybe; @@ -29578,6 +29648,7 @@ export type StixRefRelationshipAddInput = { created?: InputMaybe; createdBy?: InputMaybe; file?: InputMaybe; + fileMarkings?: InputMaybe>>; fromId?: InputMaybe; modified?: InputMaybe; objectLabel?: InputMaybe>>; @@ -30557,6 +30628,7 @@ export type SystemAddInput = { description?: InputMaybe; externalReferences?: InputMaybe>>; file?: InputMaybe; + fileMarkings?: InputMaybe>>; lang?: InputMaybe; modified?: InputMaybe; name: Scalars['String']['input']; @@ -30890,6 +30962,8 @@ export type TaskAddInput = { createdBy?: InputMaybe; description?: InputMaybe; due_date?: InputMaybe; + file?: InputMaybe; + fileMarkings?: InputMaybe>>; name: Scalars['String']['input']; objectAssignee?: InputMaybe>>; objectLabel?: InputMaybe>>; @@ -31243,6 +31317,7 @@ export type TextToStixArgs = { export type TextAddInput = { file?: InputMaybe; + fileMarkings?: InputMaybe>>; value?: InputMaybe; }; @@ -31771,6 +31846,7 @@ export type ThreatActorGroupAddInput = { description?: InputMaybe; externalReferences?: InputMaybe>>; file?: InputMaybe; + fileMarkings?: InputMaybe>>; first_seen?: InputMaybe; goals?: InputMaybe>>; lang?: InputMaybe; @@ -32087,6 +32163,7 @@ export type ThreatActorIndividualAddInput = { externalReferences?: InputMaybe>>; eye_color?: InputMaybe; file?: InputMaybe; + fileMarkings?: InputMaybe>>; first_seen?: InputMaybe; gender?: InputMaybe; goals?: InputMaybe>>; @@ -32394,6 +32471,7 @@ export type ToolAddInput = { description?: InputMaybe; externalReferences?: InputMaybe>>; file?: InputMaybe; + fileMarkings?: InputMaybe>>; killChainPhases?: InputMaybe>>; lang?: InputMaybe; modified?: InputMaybe; @@ -32673,6 +32751,7 @@ export type TrackingNumberToStixArgs = { export type TrackingNumberAddInput = { file?: InputMaybe; + fileMarkings?: InputMaybe>>; value?: InputMaybe; }; @@ -33013,6 +33092,7 @@ export type UrlToStixArgs = { export type UrlAddInput = { file?: InputMaybe; + fileMarkings?: InputMaybe>>; value?: InputMaybe; }; @@ -33317,6 +33397,7 @@ export type UserAccountAddInput = { credential_last_changed?: InputMaybe; display_name?: InputMaybe; file?: InputMaybe; + fileMarkings?: InputMaybe>>; is_disabled?: InputMaybe; is_privileged?: InputMaybe; is_service_account?: InputMaybe; @@ -33548,6 +33629,7 @@ export type UserAgentToStixArgs = { export type UserAgentAddInput = { file?: InputMaybe; + fileMarkings?: InputMaybe>>; value?: InputMaybe; }; @@ -34090,6 +34172,7 @@ export type VulnerabilityAddInput = { description?: InputMaybe; externalReferences?: InputMaybe>>; file?: InputMaybe; + fileMarkings?: InputMaybe>>; lang?: InputMaybe; modified?: InputMaybe; name: Scalars['String']['input']; @@ -34468,6 +34551,7 @@ export type WindowsRegistryKeyToStixArgs = { export type WindowsRegistryKeyAddInput = { attribute_key?: InputMaybe; file?: InputMaybe; + fileMarkings?: InputMaybe>>; modified_time?: InputMaybe; number_of_subkeys?: InputMaybe; }; @@ -34678,6 +34762,7 @@ export type WindowsRegistryValueTypeAddInput = { data?: InputMaybe; data_type?: InputMaybe; file?: InputMaybe; + fileMarkings?: InputMaybe>>; name?: InputMaybe; }; @@ -35092,6 +35177,7 @@ export type X509CertificateAddInput = { crl_distribution_points?: InputMaybe; extended_key_usage?: InputMaybe; file?: InputMaybe; + fileMarkings?: InputMaybe>>; hashes?: InputMaybe>>; inhibit_any_policy?: InputMaybe; is_self_signed?: InputMaybe; diff --git a/opencti-platform/opencti-graphql/src/modules/administrativeArea/administrativeArea.graphql b/opencti-platform/opencti-graphql/src/modules/administrativeArea/administrativeArea.graphql index 7edfd85b00d6..a24e51c069da 100644 --- a/opencti-platform/opencti-graphql/src/modules/administrativeArea/administrativeArea.graphql +++ b/opencti-platform/opencti-graphql/src/modules/administrativeArea/administrativeArea.graphql @@ -198,6 +198,7 @@ input AdministrativeAreaAddInput { x_opencti_modified_at: DateTime update: Boolean file: Upload + fileMarkings: [String] } type Mutation { diff --git a/opencti-platform/opencti-graphql/src/modules/case/case-incident/case-incident.graphql b/opencti-platform/opencti-graphql/src/modules/case/case-incident/case-incident.graphql index 30775d0a8cbf..c498ba239a61 100644 --- a/opencti-platform/opencti-graphql/src/modules/case/case-incident/case-incident.graphql +++ b/opencti-platform/opencti-graphql/src/modules/case/case-incident/case-incident.graphql @@ -246,6 +246,7 @@ input CaseIncidentAddInput { x_opencti_modified_at: DateTime x_opencti_workflow_id: String file: Upload + fileMarkings: [String] clientMutationId: String update: Boolean authorized_members: [MemberAccessInput!] diff --git a/opencti-platform/opencti-graphql/src/modules/case/case-rfi/case-rfi.graphql b/opencti-platform/opencti-graphql/src/modules/case/case-rfi/case-rfi.graphql index 0dc510713f69..ebc01dd75f33 100644 --- a/opencti-platform/opencti-graphql/src/modules/case/case-rfi/case-rfi.graphql +++ b/opencti-platform/opencti-graphql/src/modules/case/case-rfi/case-rfi.graphql @@ -250,6 +250,7 @@ input CaseRfiAddInput { x_opencti_modified_at: DateTime x_opencti_workflow_id: String file: Upload + fileMarkings: [String] clientMutationId: String update: Boolean information_types: [String!] diff --git a/opencti-platform/opencti-graphql/src/modules/case/case-rft/case-rft.graphql b/opencti-platform/opencti-graphql/src/modules/case/case-rft/case-rft.graphql index bed0b2c8e76f..86a064fa028b 100644 --- a/opencti-platform/opencti-graphql/src/modules/case/case-rft/case-rft.graphql +++ b/opencti-platform/opencti-graphql/src/modules/case/case-rft/case-rft.graphql @@ -241,6 +241,7 @@ input CaseRftAddInput { created: DateTime modified: DateTime file: Upload + fileMarkings: [String] clientMutationId: String x_opencti_modified_at: DateTime x_opencti_workflow_id: String diff --git a/opencti-platform/opencti-graphql/src/modules/case/feedback/feedback.graphql b/opencti-platform/opencti-graphql/src/modules/case/feedback/feedback.graphql index c37c3455544f..06652d3f59bf 100644 --- a/opencti-platform/opencti-graphql/src/modules/case/feedback/feedback.graphql +++ b/opencti-platform/opencti-graphql/src/modules/case/feedback/feedback.graphql @@ -236,6 +236,7 @@ input FeedbackAddInput { x_opencti_workflow_id: String x_opencti_modified_at: DateTime file: Upload + fileMarkings: [String] clientMutationId: String update: Boolean rating: Int diff --git a/opencti-platform/opencti-graphql/src/modules/channel/channel.graphql b/opencti-platform/opencti-graphql/src/modules/channel/channel.graphql index fcbc9760efa2..9d35a3173141 100644 --- a/opencti-platform/opencti-graphql/src/modules/channel/channel.graphql +++ b/opencti-platform/opencti-graphql/src/modules/channel/channel.graphql @@ -194,6 +194,7 @@ input ChannelAddInput { clientMutationId: String update: Boolean file: Upload + fileMarkings: [String] } type Mutation { diff --git a/opencti-platform/opencti-graphql/src/modules/dataComponent/dataComponent.graphql b/opencti-platform/opencti-graphql/src/modules/dataComponent/dataComponent.graphql index ad5232c89bea..687f9f21d552 100644 --- a/opencti-platform/opencti-graphql/src/modules/dataComponent/dataComponent.graphql +++ b/opencti-platform/opencti-graphql/src/modules/dataComponent/dataComponent.graphql @@ -192,6 +192,7 @@ input DataComponentAddInput { aliases: [String] dataSource: String file: Upload + fileMarkings: [String] x_opencti_workflow_id: String x_opencti_modified_at: DateTime } diff --git a/opencti-platform/opencti-graphql/src/modules/dataSource/dataSource.graphql b/opencti-platform/opencti-graphql/src/modules/dataSource/dataSource.graphql index dd49cca8f7bd..c6e1012a82be 100644 --- a/opencti-platform/opencti-graphql/src/modules/dataSource/dataSource.graphql +++ b/opencti-platform/opencti-graphql/src/modules/dataSource/dataSource.graphql @@ -194,6 +194,7 @@ input DataSourceAddInput { collection_layers: [String!] dataComponents: [String] file: Upload + fileMarkings: [String] x_opencti_workflow_id: String x_opencti_modified_at: DateTime } diff --git a/opencti-platform/opencti-graphql/src/modules/event/event.graphql b/opencti-platform/opencti-graphql/src/modules/event/event.graphql index e4534a820724..ce16adc636b2 100644 --- a/opencti-platform/opencti-graphql/src/modules/event/event.graphql +++ b/opencti-platform/opencti-graphql/src/modules/event/event.graphql @@ -198,6 +198,7 @@ input EventAddInput { x_opencti_modified_at: DateTime update: Boolean file: Upload + fileMarkings: [String] } type Mutation { diff --git a/opencti-platform/opencti-graphql/src/modules/form/form-types.ts b/opencti-platform/opencti-graphql/src/modules/form/form-types.ts index 72e05203c7ff..985012d144e4 100644 --- a/opencti-platform/opencti-graphql/src/modules/form/form-types.ts +++ b/opencti-platform/opencti-graphql/src/modules/form/form-types.ts @@ -57,7 +57,7 @@ export interface FormFieldDefinition { required: boolean; isMandatory?: boolean; // Whether this field is for a mandatory attribute width?: 'full' | 'half' | 'third'; // Field width in grid: full (12), half (6), third (4) - multiple?: boolean; // For openvocab and select/multiselect fields + multiple?: boolean; // For openvocab, select/multiselect, and files fields (defaults to false for files) attributeMapping: { entity: string; // Entity ID this field maps to (main_entity or additional entity ID) attributeName: string; // The attribute name on that entity diff --git a/opencti-platform/opencti-graphql/src/modules/grouping/grouping.graphql b/opencti-platform/opencti-graphql/src/modules/grouping/grouping.graphql index b86c07b56dd6..0ea90c4d575e 100644 --- a/opencti-platform/opencti-graphql/src/modules/grouping/grouping.graphql +++ b/opencti-platform/opencti-graphql/src/modules/grouping/grouping.graphql @@ -270,6 +270,7 @@ input GroupingAddInput { x_opencti_modified_at: DateTime update: Boolean file: Upload + fileMarkings: [String] authorized_members: [MemberAccessInput!] } diff --git a/opencti-platform/opencti-graphql/src/modules/indicator/indicator.graphql b/opencti-platform/opencti-graphql/src/modules/indicator/indicator.graphql index 2b8af60b045c..cd009bbc02ab 100644 --- a/opencti-platform/opencti-graphql/src/modules/indicator/indicator.graphql +++ b/opencti-platform/opencti-graphql/src/modules/indicator/indicator.graphql @@ -251,6 +251,7 @@ input IndicatorAddInput { x_opencti_modified_at: DateTime x_opencti_workflow_id: String file: Upload + fileMarkings: [String] basedOn: [String!] } diff --git a/opencti-platform/opencti-graphql/src/modules/language/language.graphql b/opencti-platform/opencti-graphql/src/modules/language/language.graphql index 89f993f133e2..618804c7e13a 100644 --- a/opencti-platform/opencti-graphql/src/modules/language/language.graphql +++ b/opencti-platform/opencti-graphql/src/modules/language/language.graphql @@ -187,6 +187,8 @@ input LanguageAddInput { x_opencti_workflow_id: String x_opencti_modified_at: DateTime update: Boolean + file: Upload + fileMarkings: [String] } type Mutation { diff --git a/opencti-platform/opencti-graphql/src/modules/malwareAnalysis/malwareAnalysis.graphql b/opencti-platform/opencti-graphql/src/modules/malwareAnalysis/malwareAnalysis.graphql index 0a8f963d4a7c..dc9c9b4fcd51 100644 --- a/opencti-platform/opencti-graphql/src/modules/malwareAnalysis/malwareAnalysis.graphql +++ b/opencti-platform/opencti-graphql/src/modules/malwareAnalysis/malwareAnalysis.graphql @@ -220,6 +220,7 @@ input MalwareAnalysisAddInput { x_opencti_modified_at: DateTime update: Boolean file: Upload + fileMarkings: [String] } type Mutation { diff --git a/opencti-platform/opencti-graphql/src/modules/narrative/narrative.graphql b/opencti-platform/opencti-graphql/src/modules/narrative/narrative.graphql index ed4e6821bfc1..27511e4b3d3c 100644 --- a/opencti-platform/opencti-graphql/src/modules/narrative/narrative.graphql +++ b/opencti-platform/opencti-graphql/src/modules/narrative/narrative.graphql @@ -194,6 +194,7 @@ input NarrativeAddInput { clientMutationId: String update: Boolean file: Upload + fileMarkings: [String] x_opencti_modified_at: DateTime x_opencti_workflow_id: String } diff --git a/opencti-platform/opencti-graphql/src/modules/organization/organization.graphql b/opencti-platform/opencti-graphql/src/modules/organization/organization.graphql index 9cb187709c18..dbb731473553 100644 --- a/opencti-platform/opencti-graphql/src/modules/organization/organization.graphql +++ b/opencti-platform/opencti-graphql/src/modules/organization/organization.graphql @@ -231,6 +231,7 @@ input OrganizationAddInput { clientMutationId: String update: Boolean file: Upload + fileMarkings: [String] } type Mutation { diff --git a/opencti-platform/opencti-graphql/src/modules/securityCoverage/securityCoverage.graphql b/opencti-platform/opencti-graphql/src/modules/securityCoverage/securityCoverage.graphql index ff82a05b4aa6..5913ef997fa4 100644 --- a/opencti-platform/opencti-graphql/src/modules/securityCoverage/securityCoverage.graphql +++ b/opencti-platform/opencti-graphql/src/modules/securityCoverage/securityCoverage.graphql @@ -215,6 +215,8 @@ input SecurityCoverageAddInput { x_opencti_modified_at: DateTime external_uri: String externalReferences: [String] + file: Upload + fileMarkings: [String] update: Boolean # coverage specific auto_enrichment_disable: Boolean! diff --git a/opencti-platform/opencti-graphql/src/modules/securityPlatform/securityPlatform.graphql b/opencti-platform/opencti-graphql/src/modules/securityPlatform/securityPlatform.graphql index cff47fd40188..f99823d35b9a 100644 --- a/opencti-platform/opencti-graphql/src/modules/securityPlatform/securityPlatform.graphql +++ b/opencti-platform/opencti-graphql/src/modules/securityPlatform/securityPlatform.graphql @@ -193,6 +193,8 @@ input SecurityPlatformAddInput { x_opencti_modified_at: DateTime externalReferences: [String] update: Boolean + file: Upload + fileMarkings: [String] } type Mutation { diff --git a/opencti-platform/opencti-graphql/src/modules/task/task.graphql b/opencti-platform/opencti-graphql/src/modules/task/task.graphql index d33bf16097e0..d9e8a24ff30b 100644 --- a/opencti-platform/opencti-graphql/src/modules/task/task.graphql +++ b/opencti-platform/opencti-graphql/src/modules/task/task.graphql @@ -225,6 +225,8 @@ input TaskAddInput { x_opencti_workflow_id: String x_opencti_modified_at: DateTime update: Boolean + file: Upload + fileMarkings: [String] } type Mutation { diff --git a/opencti-platform/opencti-graphql/src/modules/threatActorIndividual/threatActorIndividual.graphql b/opencti-platform/opencti-graphql/src/modules/threatActorIndividual/threatActorIndividual.graphql index ea413e86430f..2c5e1eaa2851 100644 --- a/opencti-platform/opencti-graphql/src/modules/threatActorIndividual/threatActorIndividual.graphql +++ b/opencti-platform/opencti-graphql/src/modules/threatActorIndividual/threatActorIndividual.graphql @@ -230,6 +230,7 @@ input ThreatActorIndividualAddInput { x_opencti_modified_at: DateTime update: Boolean file: Upload + fileMarkings: [String] } type Query { diff --git a/opencti-platform/opencti-graphql/tests/utils/syncCountHelper.ts b/opencti-platform/opencti-graphql/tests/utils/syncCountHelper.ts index 5eacd4d92ac0..07438a8e26b7 100644 --- a/opencti-platform/opencti-graphql/tests/utils/syncCountHelper.ts +++ b/opencti-platform/opencti-graphql/tests/utils/syncCountHelper.ts @@ -72,7 +72,7 @@ testUpdatedCounter.location = 15; testUpdatedCounter['attack-pattern'] = 3; testUpdatedCounter['case-incident'] = 11; testUpdatedCounter.feedback = 1; -testUpdatedCounter.report = 13; +testUpdatedCounter.report = 12; testUpdatedCounter['course-of-action'] = 3; testUpdatedCounter['data-source'] = 1; testUpdatedCounter['external-reference'] = 1; diff --git a/scripts/clone-dependencies.sh b/scripts/clone-dependencies.sh index 4ecc560837c7..801e1a58e035 100644 --- a/scripts/clone-dependencies.sh +++ b/scripts/clone-dependencies.sh @@ -26,13 +26,23 @@ clone_for_pr_build() { gh repo clone https://github.com/OpenCTI-Platform/connectors ${CONNECTOR_DIR} -- --depth=1 cd ${WORKSPACE} - CHANGES_OUSTIDE_FRONT_COUNT=$(gh pr diff ${PR_NUMBER} --name-only | grep -v "opencti-platform/opencti-front" | wc -l) - if [[ ${CHANGES_OUSTIDE_FRONT_COUNT} -eq 0 ]] + DIFF_OUTPUT=$(gh pr diff ${PR_NUMBER} --name-only 2>&1) + DIFF_EXIT_CODE=$? + + # Check if diff failed (non-zero exit OR error message in output indicating diff too large) + if [[ ${DIFF_EXIT_CODE} -ne 0 ]] || echo "${DIFF_OUTPUT}" | grep -qi "too_large\|exceeded the maximum\|HTTP 406" then - echo "[CLONE-DEPS][BUILD] Only frontend changes on this PR, api-test can be skipped." - touch "${WORKSPACE}/api-test.skip" + echo "[CLONE-DEPS][BUILD] Failed to get PR diff (possibly too large), assuming non-frontend changes exist. api-test will be run." + echo "[CLONE-DEPS][BUILD] gh pr diff output: ${DIFF_OUTPUT}" else - echo "[CLONE-DEPS][BUILD] There is more than frontend changes, api-test will be run." + CHANGES_OUSTIDE_FRONT_COUNT=$(echo "${DIFF_OUTPUT}" | grep -v "opencti-platform/opencti-front" | wc -l) + if [[ ${CHANGES_OUSTIDE_FRONT_COUNT} -eq 0 ]] + then + echo "[CLONE-DEPS][BUILD] Only frontend changes on this PR, api-test can be skipped." + touch "${WORKSPACE}/api-test.skip" + else + echo "[CLONE-DEPS][BUILD] There is more than frontend changes, api-test will be run." + fi fi } From 2cb45394acf4837f925199d9efd0292de7cfd602 Mon Sep 17 00:00:00 2001 From: Samuel Hassine Date: Sat, 10 Jan 2026 20:59:42 +0100 Subject: [PATCH 073/126] [backend] Allow escape function in safeEjs for Simple Mailer templates (#13753) --- .readthedocs.yaml | 2 + client-python/docs/conf.py | 21 ++++- client-python/docs/pycti/pycti.rst | 78 ++++++++-------- client-python/pycti/utils/opencti_stix2.py | 12 ++- .../opencti-graphql/src/utils/safeEjs.ts | 15 ++- .../tests/01-unit/utils/safeEjs-test.ts | 91 +++++++++++++++++++ 6 files changed, 173 insertions(+), 46 deletions(-) diff --git a/.readthedocs.yaml b/.readthedocs.yaml index 355b64a72548..6bd91c3cbf7e 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -9,6 +9,8 @@ build: os: ubuntu-22.04 tools: python: "3.12" + apt_packages: + - graphviz # Build documentation in the client-python/docs/ directory with Sphinx sphinx: diff --git a/client-python/docs/conf.py b/client-python/docs/conf.py index aced7f5eb290..8341fb728302 100644 --- a/client-python/docs/conf.py +++ b/client-python/docs/conf.py @@ -56,10 +56,26 @@ def get_version(): "sphinx.ext.autodoc", "sphinx.ext.viewcode", "sphinx.ext.napoleon", + "sphinx.ext.graphviz", + "sphinx.ext.inheritance_diagram", "autoapi.extension", "sphinx_autodoc_typehints", ] +# Graphviz configuration +graphviz_output_format = "svg" +inheritance_graph_attrs = { + "rankdir": "TB", + "size": '"6.0, 8.0"', +} +inheritance_node_attrs = { + "shape": "box", + "fontsize": 10, + "height": 0.25, + "style": '"setlinewidth(0.5),filled"', + "fillcolor": "white", +} + # Napoleon settings for Google/NumPy style docstrings napoleon_google_docstring = True napoleon_numpy_docstring = True @@ -82,7 +98,7 @@ def get_version(): "undoc-members", "show-inheritance", "show-module-summary", - "imported-members", + "special-members", ] autoapi_python_class_content = ( "both" # Include both class docstring and __init__ docstring @@ -90,6 +106,9 @@ def get_version(): autoapi_member_order = "bysource" autoapi_keep_files = False autoapi_add_toctree_entry = True +# Ignore top-level __init__.py to prevent "Undocumented" for re-exported classes +# Classes will be documented in their original modules with proper docstrings +autoapi_ignore = ["*/pycti/__init__.py"] # Mock imports for modules that can't be installed on ReadTheDocs autodoc_mock_imports = [ diff --git a/client-python/docs/pycti/pycti.rst b/client-python/docs/pycti/pycti.rst index 0a7a9ea49104..c04bd070d4e3 100644 --- a/client-python/docs/pycti/pycti.rst +++ b/client-python/docs/pycti/pycti.rst @@ -24,52 +24,52 @@ Classes ======= - :py:class:`AttackPattern`: - Undocumented. + Main AttackPattern class for OpenCTI - :py:class:`Campaign`: - Undocumented. + Main Campaign class for OpenCTI - :py:class:`CaseIncident`: - Undocumented. + Main CaseIncident class for OpenCTI - :py:class:`CaseRfi`: - Undocumented. + Main CaseRfi class for OpenCTI - :py:class:`CaseRft`: - Undocumented. + Main CaseRft class for OpenCTI - :py:class:`Channel`: - Undocumented. + Main Channel class for OpenCTI - :py:class:`Task`: - Undocumented. + Main Task class for OpenCTI - :py:class:`ConnectorType`: An enumeration. - :py:class:`CourseOfAction`: - Undocumented. + Main CourseOfAction class for OpenCTI - :py:class:`DataComponent`: - Undocumented. + Main DataComponent class for OpenCTI - :py:class:`DataSource`: - Undocumented. + Main DataSource class for OpenCTI - :py:class:`ExternalReference`: - Undocumented. + Main ExternalReference class for OpenCTI - :py:class:`Feedback`: - Undocumented. + Main Feedback class for OpenCTI - :py:class:`Grouping`: - Undocumented. + Main Grouping class for OpenCTI - :py:class:`Identity`: - Undocumented. + Main Identity class for OpenCTI - :py:class:`Incident`: - Undocumented. + Main Incident class for OpenCTI - :py:class:`Indicator`: Main Indicator class for OpenCTI @@ -78,40 +78,40 @@ Classes Main Infrastructure class for OpenCTI - :py:class:`IntrusionSet`: - Undocumented. + Main IntrusionSet class for OpenCTI - :py:class:`KillChainPhase`: - Undocumented. + Main KillChainPhase class for OpenCTI - :py:class:`Label`: - Undocumented. + Main Label class for OpenCTI - :py:class:`Location`: - Undocumented. + Main Location class for OpenCTI - :py:class:`Malware`: - Undocumented. + Main Malware class for OpenCTI - :py:class:`MalwareAnalysis`: - Undocumented. + Main MalwareAnalysis class for OpenCTI - :py:class:`MarkingDefinition`: - Undocumented. + Main MarkingDefinition class for OpenCTI - :py:class:`Note`: - Undocumented. + Main Note class for OpenCTI - :py:class:`ObservedData`: - Undocumented. + Main ObservedData class for OpenCTI - :py:class:`OpenCTIApiClient`: Main API client for OpenCTI - :py:class:`OpenCTIApiConnector`: - OpenCTIApiConnector + OpenCTI API Connector client - :py:class:`OpenCTIApiWork`: - OpenCTIApiJob + OpenCTI API Work client - :py:class:`OpenCTIConnector`: Main class for OpenCTI connector @@ -120,40 +120,40 @@ Classes Python API for OpenCTI connector - :py:class:`OpenCTIMetricHandler`: - Undocumented. + Main OpenCTI Metric Handler class - :py:class:`OpenCTIStix2`: Python API for Stix2 in OpenCTI - :py:class:`OpenCTIStix2Splitter`: - Undocumented. + Main OpenCTI Stix2 Splitter class - :py:class:`OpenCTIStix2Update`: Python API for Stix2 Update in OpenCTI - :py:class:`OpenCTIStix2Utils`: - Undocumented. + Main OpenCTI Stix2 Utils class - :py:class:`Opinion`: - Undocumented. + Main Opinion class for OpenCTI - :py:class:`Report`: - Undocumented. + Main Report class for OpenCTI - :py:class:`StixCoreRelationship`: - Undocumented. + Main StixCoreRelationship class for OpenCTI - :py:class:`StixCyberObservable`: - deprecated [>=6.2 & <6.5]` + Deprecated StixCyberObservable class [>=6.2 & <6.5] - :py:class:`StixNestedRefRelationship`: - Undocumented. + Main StixNestedRefRelationship class for OpenCTI - :py:class:`StixCyberObservableTypes`: An enumeration. - :py:class:`StixDomainObject`: - Undocumented. + Main StixDomainObject class for OpenCTI - :py:class:`StixMetaTypes`: An enumeration. @@ -162,10 +162,10 @@ Classes An enumeration. - :py:class:`StixObjectOrStixRelationship`: - Undocumented. + Main StixObjectOrStixRelationship class for OpenCTI - :py:class:`StixSightingRelationship`: - Undocumented. + Main StixSightingRelationship class for OpenCTI - :py:class:`ThreatActor`: Main ThreatActor class for OpenCTI @@ -177,10 +177,10 @@ Classes Main ThreatActorIndividual class for OpenCTI - :py:class:`Tool`: - Undocumented. + Main Tool class for OpenCTI - :py:class:`Vulnerability`: - Undocumented. + Main Vulnerability class for OpenCTI - :py:class:`CustomObjectCaseIncident`: Case-Incident object. diff --git a/client-python/pycti/utils/opencti_stix2.py b/client-python/pycti/utils/opencti_stix2.py index e6de7fc7a203..8d083dc524dc 100644 --- a/client-python/pycti/utils/opencti_stix2.py +++ b/client-python/pycti/utils/opencti_stix2.py @@ -49,10 +49,14 @@ ERROR_TYPE_DRAFT_LOCK = "DRAFT_LOCKED" ERROR_TYPE_TIMEOUT = "Request timed out" -# Extensions -STIX_EXT_OCTI = "extension-definition--ea279b3e-5c71-4632-ac08-831c66a786ba" -STIX_EXT_OCTI_SCO = "extension-definition--f93e2c80-4231-4f9a-af8b-95c9bd566a82" -STIX_EXT_MITRE = "extension-definition--322b8f77-262a-4cb8-a915-1e441e00329b" +#: STIX Extension ID for OpenCTI custom objects and properties +STIX_EXT_OCTI: str = "extension-definition--ea279b3e-5c71-4632-ac08-831c66a786ba" + +#: STIX Extension ID for OpenCTI custom Cyber Observables (SCO) +STIX_EXT_OCTI_SCO: str = "extension-definition--f93e2c80-4231-4f9a-af8b-95c9bd566a82" + +#: STIX Extension ID for MITRE ATT&CK framework objects +STIX_EXT_MITRE: str = "extension-definition--322b8f77-262a-4cb8-a915-1e441e00329b" PROCESSING_COUNT: int = 4 MAX_PROCESSING_COUNT: int = 100 diff --git a/opencti-platform/opencti-graphql/src/utils/safeEjs.ts b/opencti-platform/opencti-graphql/src/utils/safeEjs.ts index 2be4dd1643d2..7f78df30b129 100644 --- a/opencti-platform/opencti-graphql/src/utils/safeEjs.ts +++ b/opencti-platform/opencti-graphql/src/utils/safeEjs.ts @@ -69,6 +69,7 @@ const authorizeGlobals = new Map([ ['encodeURIComponent', true], ['decodeURI', true], ['decodeURIComponent', true], + ['escape', true], ]); const forbiddenGlobals = [ @@ -82,7 +83,10 @@ const forbiddenGlobals = [ const noop = () => {}; -const createSafeContext = (async: boolean, { maxExecutedStatementCount = 0, maxExecutionDuration = 0, yieldMethod }: SafeOptions) => { +const createSafeContext = ( + async: boolean, + { maxExecutedStatementCount = 0, maxExecutionDuration = 0, yieldMethod, escape }: SafeOptions & { escape?: (str: string) => string }, +) => { let executedStatementCount = 0; const checkMaxExecutedStatementCount = maxExecutedStatementCount > 0 ? () => { executedStatementCount += 1; @@ -98,7 +102,7 @@ const createSafeContext = (async: boolean, { maxExecutedStatementCount = 0, maxE } } : noop; - return { + const context: Record = { [safeName('statement')]: async ? async () => { checkMaxExecutedStatementCount(); @@ -140,6 +144,13 @@ const createSafeContext = (async: boolean, { maxExecutedStatementCount = 0, maxE }, }), }; + + // If a custom escape function is provided, make it available in template context + if (escape) { + context.escape = escape; + } + + return context; }; const extractEJSCode = (template: string, openTag: string, closeTag: string) => { diff --git a/opencti-platform/opencti-graphql/tests/01-unit/utils/safeEjs-test.ts b/opencti-platform/opencti-graphql/tests/01-unit/utils/safeEjs-test.ts index fb1f47cd23e4..bf7ef378db42 100644 --- a/opencti-platform/opencti-graphql/tests/01-unit/utils/safeEjs-test.ts +++ b/opencti-platform/opencti-graphql/tests/01-unit/utils/safeEjs-test.ts @@ -277,6 +277,97 @@ describe('check safeRenderClient error handling and worker termination detection }); }); +describe('check safeRender with escape function', () => { + it('should allow escape function in templates when provided via options', () => { + // Use <%- %> (raw output) to avoid double-escaping when escape() is called explicitly + const template = '<% function parseLink(text) { return escape(text); } %><%- parseLink("") %>'; + const data = {}; + const escapeFunc = (str: any) => String(str).replace(/[&<>"']/g, (char) => { + const escapeMap: Record = { + '&': '&', + '<': '<', + '>': '>', + '"': '"', + "'": ''' + }; + return escapeMap[char] || char; + }); + + const result = safeRender(template, data, { escape: escapeFunc }); + expect(result).toContain('<script>'); + expect(result).toContain('</script>'); + expect(result).not.toContain('](javascript:alert(1))') %> + `; + const data = {}; + const escapeFunc = (str: any) => String(str).replace(/[&<>"']/g, (char) => { + const escapeMap: Record = { + '&': '&', + '<': '<', + '>': '>', + '"': '"', + "'": ''' + }; + return escapeMap[char] || char; + }); + + const result = safeRender(template, data, { escape: escapeFunc }); + // The escape function should escape the special characters + expect(result).not.toContain('