Skip to content

[backend] refresh cache for Resolved filters at playbook/stream/trigger update (#10593) #10704

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Apr 17, 2025
Merged
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
84 changes: 52 additions & 32 deletions opencti-platform/opencti-graphql/src/manager/cacheManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import type { AuthContext, AuthUser } from '../types/user';
import { ENTITY_TYPE_RESOLVED_FILTERS } from '../schema/stixDomainObject';
import { ENTITY_TYPE_ENTITY_SETTING } from '../modules/entitySetting/entitySetting-types';
import { FilterMode, OrderingMode } from '../generated/graphql';
import { FilterMode, OrderingMode, type StreamCollection } from '../generated/graphql';
import { extractFilterGroupValuesToResolveForCache } from '../utils/filtering/filtering-resolution';
import { type BasicStoreEntityTrigger, ENTITY_TYPE_TRIGGER } from '../modules/notification/notification-types';
import { stixLoadByIds } from '../database/middleware';
Expand Down Expand Up @@ -50,8 +50,9 @@
import { getAllowedMarkings } from '../modules/publicDashboard/publicDashboard-domain';
import type { BasicStoreEntityConnector } from '../types/connector';
import { getEnterpriseEditionInfoFromPem } from '../modules/settings/licensing';
import { convertStoreToStix } from '../database/stix-2-1-converter';
import { ENTITY_TYPE_DRAFT_WORKSPACE } from '../modules/draftWorkspace/draftWorkspace-types';
import { emptyFilterGroup } from '../utils/filtering/filtering-utils';
import { FunctionalError } from '../config/errors';

const ADDS_TOPIC = `${TOPIC_PREFIX}*ADDED_TOPIC`;
const EDITS_TOPIC = `${TOPIC_PREFIX}*EDIT_TOPIC`;
Expand All @@ -69,37 +70,49 @@
};
return { values: null, fn: reloadStatuses };
};
// extract the filters of the instance in case of resolved filters cache update
const extractResolvedFiltersFromInstance = (instance: BasicStoreCommon) => {
const initialFilterGroup = JSON.stringify(emptyFilterGroup);
const filteringIds = []; // will contain the ids that are in the instance filters values
if (instance.entity_type === ENTITY_TYPE_STREAM_COLLECTION) {
const streamFilterIds = extractFilterGroupValuesToResolveForCache(
JSON.parse((instance as StreamCollection).filters ?? initialFilterGroup)
);
filteringIds.push(...streamFilterIds);

Check warning on line 81 in opencti-platform/opencti-graphql/src/manager/cacheManager.ts

View check run for this annotation

Codecov / codecov/patch

opencti-platform/opencti-graphql/src/manager/cacheManager.ts#L78-L81

Added lines #L78 - L81 were not covered by tests
} else if (instance.entity_type === ENTITY_TYPE_TRIGGER) {
const triggerFilterIds = extractFilterGroupValuesToResolveForCache(
JSON.parse((instance as BasicStoreEntityTrigger).filters ?? initialFilterGroup)
);
filteringIds.push(...triggerFilterIds);
} else if (instance.entity_type === ENTITY_TYPE_CONNECTOR) {
const connFilters = (instance as BasicStoreEntityConnector).connector_trigger_filters?.length > 0
? (instance as BasicStoreEntityConnector).connector_trigger_filters

Check warning on line 89 in opencti-platform/opencti-graphql/src/manager/cacheManager.ts

View check run for this annotation

Codecov / codecov/patch

opencti-platform/opencti-graphql/src/manager/cacheManager.ts#L89

Added line #L89 was not covered by tests
: initialFilterGroup;
const connFilterIds = extractFilterGroupValuesToResolveForCache(JSON.parse(connFilters));
filteringIds.push(...connFilterIds);
} else if (instance.entity_type === ENTITY_TYPE_PLAYBOOK) {
const playbookFilterIds = ((JSON.parse((instance as BasicStoreEntityPlaybook).playbook_definition)) as ComponentDefinition)
.nodes.map((n) => JSON.parse(n.configuration))
.map((config) => config.filters)
.filter((f) => isNotEmptyField(f))
.map((f) => extractFilterGroupValuesToResolveForCache(JSON.parse(f)))
.flat();
filteringIds.push(...playbookFilterIds);
} else {
throw FunctionalError(`Resolved filters are only saved in cache for streams, triggers, connectors and playbooks, not for ${instance.entity_type}`);
}

Check warning on line 103 in opencti-platform/opencti-graphql/src/manager/cacheManager.ts

View check run for this annotation

Codecov / codecov/patch

opencti-platform/opencti-graphql/src/manager/cacheManager.ts#L94-L103

Added lines #L94 - L103 were not covered by tests
return filteringIds;
};
const platformResolvedFilters = (context: AuthContext) => {
const reloadFilters = async () => {
const filteringIds = [];
const initialFilterGroup = JSON.stringify({
mode: 'and',
filters: [],
filterGroups: [],
});
// Stream filters
// Fetch streams, triggers, connectors (for enrichment connectors) and playbooks
const streams = await listAllEntities<BasicStreamEntity>(context, SYSTEM_USER, [ENTITY_TYPE_STREAM_COLLECTION], { connectionFormat: false });
filteringIds.push(...streams.map((s) => extractFilterGroupValuesToResolveForCache(JSON.parse(s.filters ?? initialFilterGroup))).flat());
// Trigger filters
const triggers = await listAllEntities<BasicTriggerEntity>(context, SYSTEM_USER, [ENTITY_TYPE_TRIGGER], { connectionFormat: false });
filteringIds.push(...triggers.map((s) => extractFilterGroupValuesToResolveForCache(JSON.parse(s.filters ?? initialFilterGroup))).flat());
// Connectors filters (for enrichment connectors)
const connectors = await listAllEntities<BasicStoreEntityConnector>(context, SYSTEM_USER, [ENTITY_TYPE_CONNECTOR], { connectionFormat: false });
filteringIds.push(...connectors.map((s) => {
const connFilters = s.connector_trigger_filters?.length > 0 ? s.connector_trigger_filters : initialFilterGroup;
return extractFilterGroupValuesToResolveForCache(JSON.parse(connFilters));
}).flat());
// Playbook filters
const playbooks = await listAllEntities<BasicStoreEntityPlaybook>(context, SYSTEM_USER, [ENTITY_TYPE_PLAYBOOK], { connectionFormat: false });
const playbookFilterIds = playbooks
.map((p) => JSON.parse(p.playbook_definition) as ComponentDefinition)
.map((c) => c.nodes.map((n) => JSON.parse(n.configuration))).flat()
.map((config) => config.filters)
.filter((f) => isNotEmptyField(f))
.map((f) => extractFilterGroupValuesToResolveForCache(JSON.parse(f)))
.flat();
filteringIds.push(...playbookFilterIds);
// Resolve filteringIds
// Fetch the filters of those entities
const filteringIds = [...streams, ...triggers, ...connectors, ...playbooks].map((s) => extractResolvedFiltersFromInstance(s)).flat();
// Resolve the filters ids
if (filteringIds.length > 0) {
const resolvingIds = R.uniq(filteringIds);
const loadedDependencies = await stixLoadByIds(context, SYSTEM_USER, resolvingIds);
Expand All @@ -108,11 +121,18 @@
return new Map();
};
const refreshFilter = async (values: Map<string, StixObject>, instance: BasicStoreCommon) => {
const currentFiltersValues = values;
if (currentFiltersValues?.has(instance.internal_id)) {
const convertedInstance = convertStoreToStix(instance);
currentFiltersValues.set(instance.internal_id, convertedInstance);
}
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);
}

Check warning on line 131 in opencti-platform/opencti-graphql/src/manager/cacheManager.ts

View check run for this annotation

Codecov / codecov/patch

opencti-platform/opencti-graphql/src/manager/cacheManager.ts#L129-L131

Added lines #L129 - L131 were not covered by tests
});
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));
return currentFiltersValues;
};
return { values: null, fn: reloadFilters, refresh: refreshFilter };
Expand Down