From 1302227e37518feb69e39275ee4c9a8a3e3fd267 Mon Sep 17 00:00:00 2001 From: Souad Hadjiat Date: Thu, 31 Oct 2024 14:08:45 +0100 Subject: [PATCH] [backend/frontend] Fix playbook organization sharing schema (#8767) --- .../data/playbooks/PlaybookAddComponents.jsx | 13 ++++++ .../modules/playbook/playbook-components.ts | 42 +++++++------------ .../src/modules/playbook/playbook-domain.ts | 38 ++++++++++++++++- .../modules/playbook/playbook-resolvers.ts | 2 + 4 files changed, 66 insertions(+), 29 deletions(-) diff --git a/opencti-platform/opencti-front/src/private/components/data/playbooks/PlaybookAddComponents.jsx b/opencti-platform/opencti-front/src/private/components/data/playbooks/PlaybookAddComponents.jsx index 2a5e8484155e..861355986a76 100644 --- a/opencti-platform/opencti-front/src/private/components/data/playbooks/PlaybookAddComponents.jsx +++ b/opencti-platform/opencti-front/src/private/components/data/playbooks/PlaybookAddComponents.jsx @@ -20,6 +20,7 @@ import Box from '@mui/material/Box'; import Alert from '@mui/material/Alert'; import Drawer from '../../common/drawer/Drawer'; import ObjectMembersField from '../../common/form/ObjectMembersField'; +import ObjectOrganizationField from '../../common/form/ObjectOrganizationField'; import CreatedByField from '../../common/form/CreatedByField'; import Filters from '../../common/lists/Filters'; import FilterIconButton from '../../../../components/FilterIconButton'; @@ -458,6 +459,18 @@ const PlaybookAddComponentsContent = ({ /> ); } + if (k === 'organizations') { + return ( + + ); + } if (k === 'filters') { return (
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 913e644ff8e9..0c50e9cf15d7 100644 --- a/opencti-platform/opencti-graphql/src/modules/playbook/playbook-components.ts +++ b/opencti-platform/opencti-graphql/src/modules/playbook/playbook-components.ts @@ -49,21 +49,14 @@ import { import type { CyberObjectExtension, StixBundle, StixCoreObject, StixCyberObject, StixDomainObject, StixObject, StixOpenctiExtension } from '../../types/stix-common'; import { STIX_EXT_OCTI, STIX_EXT_OCTI_SCO } from '../../types/stix-extensions'; import { connectorsForPlaybook } from '../../database/repository'; -import { listAllEntities, listAllRelations, storeLoadById } from '../../database/middleware-loader'; -import type { BasicStoreEntityOrganization } from '../organization/organization-types'; +import { internalFindByIds, listAllRelations, storeLoadById } from '../../database/middleware-loader'; import { ENTITY_TYPE_IDENTITY_ORGANIZATION } from '../organization/organization-types'; import { getEntitiesListFromCache, getEntitiesMapFromCache } from '../../database/cache'; import { createdBy, objectLabel, objectMarking } from '../../schema/stixRefRelationship'; import { logApp } from '../../config/conf'; import { FunctionalError } from '../../config/errors'; import { extractStixRepresentative } from '../../database/stix-representative'; -import { - isEmptyField, - isNotEmptyField, - READ_ENTITIES_INDICES_WITHOUT_INFERRED, - READ_RELATIONSHIPS_INDICES, - READ_RELATIONSHIPS_INDICES_WITHOUT_INFERRED -} from '../../database/utils'; +import { isEmptyField, isNotEmptyField, READ_RELATIONSHIPS_INDICES, READ_RELATIONSHIPS_INDICES_WITHOUT_INFERRED } from '../../database/utils'; import { schemaAttributesDefinition } from '../../schema/schema-attributes'; import { schemaRelationsRefDefinition } from '../../schema/schema-relationsRef'; import { stixLoadByIds } from '../../database/middleware'; @@ -496,8 +489,8 @@ const PLAYBOOK_CONTAINER_WRAPPER_COMPONENT: PlaybookComponent = { type: 'object', @@ -521,25 +514,20 @@ const PLAYBOOK_SHARING_COMPONENT: PlaybookComponent = { is_internal: true, ports: [{ id: 'out', type: 'out' }], configuration_schema: PLAYBOOK_SHARING_COMPONENT_SCHEMA, - schema: async () => { - const context = executionContext('playbook_components'); - const organizations = await listAllEntities( - context, - SYSTEM_USER, - [ENTITY_TYPE_IDENTITY_ORGANIZATION], - { connectionFormat: false, indices: READ_ENTITIES_INDICES_WITHOUT_INFERRED } - ); - const elements = organizations.map((c) => ({ const: c.id, title: c.name })); - const schemaElement = { properties: { organizations: { items: { oneOf: elements } } } }; - return R.mergeDeepRight, any>(PLAYBOOK_SHARING_COMPONENT_SCHEMA, schemaElement); - }, + schema: async () => PLAYBOOK_SHARING_COMPONENT_SCHEMA, executor: async ({ dataInstanceId, playbookNode, bundle }) => { const context = executionContext('playbook_components'); - const allOrganizations = await getEntitiesListFromCache(context, SYSTEM_USER, ENTITY_TYPE_IDENTITY_ORGANIZATION); const { organizations } = playbookNode.configuration; - const organizationIds = allOrganizations - .filter((o) => (organizations ?? []).includes(o.internal_id)) - .map((o) => o.standard_id); + const organizationsValues = organizations.map((o) => (typeof o !== 'string' ? o.value : o)); + const organizationsByIds = await internalFindByIds(context, SYSTEM_USER, organizationsValues, { + type: ENTITY_TYPE_IDENTITY_ORGANIZATION, + baseData: true, + baseFields: ['standard_id'] + }); + if (organizationsByIds.length === 0) { + return { output_port: 'out', bundle }; // nothing to do since organizations are empty + } + const organizationIds = organizationsByIds.map((o) => o.standard_id); const baseData = bundle.objects.find((o) => o.id === dataInstanceId) as StixCoreObject; baseData.extensions[STIX_EXT_OCTI].granted_refs = [...(baseData.extensions[STIX_EXT_OCTI].granted_refs ?? []), ...organizationIds]; return { output_port: 'out', bundle }; 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 fd3e98cba864..4be5a38e3098 100644 --- a/opencti-platform/opencti-graphql/src/modules/playbook/playbook-domain.ts +++ b/opencti-platform/opencti-graphql/src/modules/playbook/playbook-domain.ts @@ -16,7 +16,7 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. import { v4 as uuidv4 } from 'uuid'; import { BUS_TOPICS, logApp } from '../../config/conf'; import { createEntity, deleteElementById, patchAttribute, updateAttribute } from '../../database/middleware'; -import { type EntityOptions, listAllEntities, listEntitiesPaginated, storeLoadById } from '../../database/middleware-loader'; +import { type EntityOptions, internalFindByIds, listAllEntities, listEntitiesPaginated, storeLoadById } from '../../database/middleware-loader'; import { notify } from '../../database/redis'; import type { DomainFindById } from '../../domain/domainTypes'; import { ABSTRACT_INTERNAL_OBJECT } from '../../schema/general'; @@ -24,8 +24,10 @@ import type { AuthContext, AuthUser } from '../../types/user'; import type { EditInput, FilterGroup, PlaybookAddInput, PlaybookAddLinkInput, PlaybookAddNodeInput, PositionInput } from '../../generated/graphql'; import type { BasicStoreEntityPlaybook, ComponentDefinition, LinkDefinition, NodeDefinition } from './playbook-types'; import { ENTITY_TYPE_PLAYBOOK } from './playbook-types'; -import { PLAYBOOK_COMPONENTS } from './playbook-components'; +import { PLAYBOOK_COMPONENTS, type SharingConfiguration } from './playbook-components'; import { UnsupportedError } from '../../config/errors'; +import { type BasicStoreEntityOrganization, ENTITY_TYPE_IDENTITY_ORGANIZATION } from '../organization/organization-types'; +import { SYSTEM_USER } from '../../utils/access'; import { validateFilterGroupForStixMatch } from '../../utils/filtering/filtering-stix/stix-filtering'; import { registerConnectorQueues, unregisterConnector } from '../../database/rabbitmq'; @@ -45,6 +47,38 @@ export const availableComponents = () => { return Object.values(PLAYBOOK_COMPONENTS); }; +export const getPlaybookDefinition = async (context: AuthContext, playbook: BasicStoreEntityPlaybook) => { + if (playbook.playbook_definition && playbook.playbook_definition.includes('PLAYBOOK_SHARING_COMPONENT')) { + // parse playbook definition in case there is a sharing with organization component, in order to parse organizations to get their label + const definition = JSON.parse(playbook.playbook_definition) as ComponentDefinition; + const sharingComponent = definition.nodes.find((n) => n.component_id === 'PLAYBOOK_SHARING_COMPONENT'); + if (sharingComponent && sharingComponent.configuration) { + const sharingConfiguration = JSON.parse(sharingComponent.configuration) as SharingConfiguration; + const organizationsIds = sharingConfiguration.organizations.filter((o) => typeof o === 'string'); + if (organizationsIds.length === 0) { + return playbook.playbook_definition; // nothing to map, already mapped + } + const organizationsByIds = await internalFindByIds(context, SYSTEM_USER, organizationsIds, { + type: ENTITY_TYPE_IDENTITY_ORGANIZATION, + baseData: true, + baseFields: ['internal_id', 'name'], + toMap: true, + }) as unknown as { [k: string]: BasicStoreEntityOrganization }; + const organizationsWithNames = []; + for (let i = 0; i < organizationsIds.length; i += 1) { + const orgId = organizationsIds[i]; + if (organizationsByIds[orgId]) { + organizationsWithNames.push({ label: organizationsByIds[orgId].name, value: orgId }); + } + } + sharingConfiguration.organizations = organizationsWithNames; + sharingComponent.configuration = JSON.stringify(sharingConfiguration); + return JSON.stringify(definition); + } + } + return playbook.playbook_definition; +}; + export const playbookAddNode = async (context: AuthContext, user: AuthUser, id: string, input: PlaybookAddNodeInput) => { // our stix matching is currently limited, we need to validate the input filters if (input.configuration) { diff --git a/opencti-platform/opencti-graphql/src/modules/playbook/playbook-resolvers.ts b/opencti-platform/opencti-graphql/src/modules/playbook/playbook-resolvers.ts index 25aec50c0eb1..06fecadcb6ea 100644 --- a/opencti-platform/opencti-graphql/src/modules/playbook/playbook-resolvers.ts +++ b/opencti-platform/opencti-graphql/src/modules/playbook/playbook-resolvers.ts @@ -28,6 +28,7 @@ import { playbookDeleteNode, playbookDeleteLink, playbookUpdatePositions, + getPlaybookDefinition } from './playbook-domain'; import { playbookStepExecution } from '../../manager/playbookManager'; import { getLastPlaybookExecutions } from '../../database/redis'; @@ -42,6 +43,7 @@ const playbookResolvers: Resolvers = { playbookComponents: () => availableComponents(), }, Playbook: { + playbook_definition: async (current, _, context) => getPlaybookDefinition(context, current), last_executions: async (current) => getLastPlaybookExecutions(current.id), queue_messages: async (current, _, context) => getConnectorQueueSize(context, context.user, current.id) },