Skip to content

Commit

Permalink
[frontend/backend] New filters bug fixes #4 (#2686)
Browse files Browse the repository at this point in the history
Co-authored-by: Laurent Bonnet <[email protected]>
  • Loading branch information
Archidoit and labo-flg authored Nov 30, 2023
1 parent 6f45074 commit 9fa43b4
Show file tree
Hide file tree
Showing 8 changed files with 144 additions and 27 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -836,7 +836,7 @@ const StixRelationshipsMultiHorizontalBars = ({
subSelectionToTypes = R.head(subSelectionFiltersContent.filter((n) => n.key === 'toTypes'))
?.values || null;
subSelectionFiltersContent = subSelectionFiltersContent.filter(
(n) => !['fromId', 'toId', 'fromTypes', 'toTypes'].includes(n.key),
(n) => !['relationship_type', 'fromId', 'toId', 'fromTypes', 'toTypes'].includes(n.key),
);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,9 +75,10 @@ const StixRelationshipsMultiLineChart = ({
...selection.filters,
filters: filtersContent.filter(
(n) => ![
'entity_type',
'elementId',
'relationship_type',
'fromId',
'toId',
'fromTypes',
'toTypes',
].includes(n.key),
),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ import {
stixObjectOrRelationshipAddRefRelations,
stixObjectOrRelationshipDeleteRefRelation
} from './stixObjectOrStixRelationship';
import { addFilter } from '../utils/filtering/filtering-utils';
import { addFilter, isFilterGroupNotEmpty } from '../utils/filtering/filtering-utils';

export const findAll = async (context, user, args) => {
return listRelations(context, user, R.propOr(ABSTRACT_STIX_CORE_RELATIONSHIP, 'relationship_type', args), args);
Expand All @@ -74,38 +74,46 @@ export const findById = (context, user, stixCoreRelationshipId) => {
export const stixCoreRelationshipsDistribution = async (context, user, args) => {
const { dynamicFrom, dynamicTo } = args;
let finalArgs = args;
if (isNotEmptyField(dynamicFrom)) {
if (isFilterGroupNotEmpty(dynamicFrom)) {
const fromIds = await listEntities(context, user, [ABSTRACT_STIX_CORE_OBJECT], buildDynamicFilterArgs(dynamicFrom))
.then((result) => result.map((n) => n.id));
if (fromIds.length > 0) {
finalArgs = { ...finalArgs, fromId: args.fromId ? [...fromIds, args.fromId] : fromIds };
}
} else {
finalArgs = { ...finalArgs, dynamicFrom: undefined };
}
if (isNotEmptyField(dynamicTo)) {
if (isFilterGroupNotEmpty(dynamicTo)) {
const toIds = await listEntities(context, user, [ABSTRACT_STIX_CORE_OBJECT], buildDynamicFilterArgs(dynamicTo))
.then((result) => result.map((n) => n.id));
if (toIds.length > 0) {
finalArgs = { ...finalArgs, toId: args.toId ? [...toIds, args.toId] : toIds };
}
} else {
finalArgs = { ...finalArgs, dynamicTo: undefined };
}
return distributionRelations(context, context.user, finalArgs);
};
export const stixCoreRelationshipsNumber = async (context, user, args) => {
const { relationship_type = [ABSTRACT_STIX_CORE_RELATIONSHIP], dynamicFrom, dynamicTo, authorId } = args;
let finalArgs = args;
if (isNotEmptyField(dynamicFrom)) {
if (isFilterGroupNotEmpty(dynamicFrom)) {
const fromIds = await listEntities(context, user, [ABSTRACT_STIX_CORE_OBJECT], buildDynamicFilterArgs(dynamicFrom))
.then((result) => result.map((n) => n.id));
if (fromIds.length > 0) {
finalArgs = { ...finalArgs, fromId: args.fromId ? [...fromIds, args.fromId] : fromIds };
}
} else {
finalArgs = { ...finalArgs, dynamicFrom: undefined };
}
if (isNotEmptyField(dynamicTo)) {
if (isFilterGroupNotEmpty(dynamicTo)) {
const toIds = await listEntities(context, user, [ABSTRACT_STIX_CORE_OBJECT], buildDynamicFilterArgs(dynamicTo))
.then((result) => result.map((n) => n.id));
if (toIds.length > 0) {
finalArgs = { ...finalArgs, toId: args.toId ? [...toIds, args.toId] : toIds };
}
} else {
finalArgs = { ...finalArgs, dynamicTo: undefined };
}
if (isNotEmptyField(authorId)) {
const filters = addFilter(args.filters, buildRefRelationKey(RELATION_CREATED_BY, '*'), authorId);
Expand All @@ -122,19 +130,23 @@ export const stixCoreRelationshipsMultiTimeSeries = async (context, user, args)
return Promise.all(args.timeSeriesParameters.map(async (timeSeriesParameter) => {
const { dynamicFrom, dynamicTo } = timeSeriesParameter;
let finalTimeSeriesParameter = timeSeriesParameter;
if (isNotEmptyField(dynamicFrom)) {
if (isFilterGroupNotEmpty(dynamicFrom)) {
const fromIds = await listEntities(context, user, [ABSTRACT_STIX_CORE_OBJECT], buildDynamicFilterArgs(dynamicFrom))
.then((result) => result.map((n) => n.id));
if (fromIds.length > 0) {
finalTimeSeriesParameter = { ...finalTimeSeriesParameter, fromId: args.fromId ? [...fromIds, args.fromId] : fromIds };
}
} else {
finalTimeSeriesParameter = { ...finalTimeSeriesParameter, dynamicFrom: undefined };
}
if (isNotEmptyField(dynamicTo)) {
if (isFilterGroupNotEmpty(dynamicTo)) {
const toIds = await listEntities(context, user, [ABSTRACT_STIX_CORE_OBJECT], buildDynamicFilterArgs(dynamicTo))
.then((result) => result.map((n) => n.id));
if (toIds.length > 0) {
finalTimeSeriesParameter = { ...finalTimeSeriesParameter, toId: args.toId ? [...toIds, args.toId] : toIds };
}
} else {
finalTimeSeriesParameter = { ...finalTimeSeriesParameter, dynamicTo: undefined };
}
return { data: timeSeriesRelations(context, user, { ...args, ...finalTimeSeriesParameter }) };
}));
Expand Down
34 changes: 25 additions & 9 deletions opencti-platform/opencti-graphql/src/domain/stixRelationship.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import {
import { ABSTRACT_STIX_CORE_OBJECT, ABSTRACT_STIX_RELATIONSHIP, ENTITY_TYPE_IDENTITY } from '../schema/general';
import { buildEntityFilters, listEntities, listRelations, storeLoadById } from '../database/middleware-loader';
import {
isNotEmptyField,
READ_INDEX_INFERRED_RELATIONSHIPS,
READ_INDEX_STIX_CORE_RELATIONSHIPS,
READ_INDEX_STIX_SIGHTING_RELATIONSHIPS
Expand All @@ -20,23 +19,28 @@ import { ENTITY_TYPE_MARKING_DEFINITION } from '../schema/stixMetaObject';
import { STIX_SPEC_VERSION, stixCoreRelationshipsMapping } from '../database/stix';
import { UnsupportedError } from '../config/errors';
import { schemaTypesDefinition } from '../schema/schema-types';
import { isFilterGroupNotEmpty } from '../utils/filtering/filtering-utils';

export const findAll = async (context, user, args) => {
const { dynamicFrom, dynamicTo } = args;
let finalArgs = args;
if (isNotEmptyField(dynamicFrom)) {
if (isFilterGroupNotEmpty(dynamicFrom)) {
const fromIds = await listEntities(context, user, [ABSTRACT_STIX_CORE_OBJECT], buildDynamicFilterArgs(dynamicFrom))
.then((result) => result.map((n) => n.id));
if (fromIds.length > 0) {
finalArgs = { ...finalArgs, fromId: args.fromId ? [...fromIds, args.fromId] : fromIds };
}
} else {
finalArgs = { ...finalArgs, dynamicFrom: undefined };
}
if (isNotEmptyField(dynamicTo)) {
if (isFilterGroupNotEmpty(dynamicTo)) {
const toIds = await listEntities(context, user, [ABSTRACT_STIX_CORE_OBJECT], buildDynamicFilterArgs(dynamicTo))
.then((result) => result.map((n) => n.id));
if (toIds.length > 0) {
finalArgs = { ...finalArgs, toId: args.toId ? [...toIds, args.toId] : toIds };
}
} else {
finalArgs = { ...finalArgs, dynamicTo: undefined };
}
return listRelations(context, user, R.propOr(ABSTRACT_STIX_RELATIONSHIP, 'relationship_type', finalArgs), finalArgs);
};
Expand All @@ -54,38 +58,46 @@ export const stixRelationshipDelete = async (context, user, stixRelationshipId)
export const stixRelationshipsDistribution = async (context, user, args) => {
const { dynamicFrom, dynamicTo } = args;
let finalArgs = args;
if (isNotEmptyField(dynamicFrom)) {
if (isFilterGroupNotEmpty(dynamicFrom)) {
const fromIds = await listEntities(context, user, [ABSTRACT_STIX_CORE_OBJECT], buildDynamicFilterArgs(dynamicFrom))
.then((result) => result.map((n) => n.id));
if (fromIds.length > 0) {
finalArgs = { ...finalArgs, fromId: args.fromId ? [...fromIds, args.fromId] : fromIds };
}
} else {
finalArgs = { ...finalArgs, dynamicFrom: undefined };
}
if (isNotEmptyField(dynamicTo)) {
if (isFilterGroupNotEmpty(dynamicTo)) {
const toIds = await listEntities(context, user, [ABSTRACT_STIX_CORE_OBJECT], buildDynamicFilterArgs(dynamicTo))
.then((result) => result.map((n) => n.id));
if (toIds.length > 0) {
finalArgs = { ...finalArgs, toId: args.toId ? [...toIds, args.toId] : toIds };
}
} else {
finalArgs = { ...finalArgs, dynamicTo: undefined };
}
return distributionRelations(context, context.user, finalArgs);
};
export const stixRelationshipsNumber = async (context, user, args) => {
const { relationship_type = [ABSTRACT_STIX_RELATIONSHIP], dynamicFrom, dynamicTo } = args;
let finalArgs = args;
if (isNotEmptyField(dynamicFrom)) {
if (isFilterGroupNotEmpty(dynamicFrom)) {
const fromIds = await listEntities(context, user, [ABSTRACT_STIX_CORE_OBJECT], buildDynamicFilterArgs(dynamicFrom))
.then((result) => result.map((n) => n.id));
if (fromIds.length > 0) {
finalArgs = { ...finalArgs, fromId: args.fromId ? [...fromIds, args.fromId] : fromIds };
}
} else {
finalArgs = { ...finalArgs, dynamicFrom: undefined };
}
if (isNotEmptyField(dynamicTo)) {
if (isFilterGroupNotEmpty(dynamicTo)) {
const toIds = await listEntities(context, user, [ABSTRACT_STIX_CORE_OBJECT], buildDynamicFilterArgs(dynamicTo))
.then((result) => result.map((n) => n.id));
if (toIds.length > 0) {
finalArgs = { ...finalArgs, toId: args.toId ? [...toIds, args.toId] : toIds };
}
} else {
finalArgs = { ...finalArgs, dynamicTo: undefined };
}
const numberArgs = buildEntityFilters({ ...finalArgs, types: relationship_type });
// eslint-disable-next-line max-len
Expand All @@ -99,19 +111,23 @@ export const stixRelationshipsMultiTimeSeries = async (context, user, args) => {
return Promise.all(args.timeSeriesParameters.map(async (timeSeriesParameter) => {
const { dynamicFrom, dynamicTo } = timeSeriesParameter;
let finalTimeSeriesParameter = timeSeriesParameter;
if (isNotEmptyField(dynamicFrom)) {
if (isFilterGroupNotEmpty(dynamicFrom)) {
const fromIds = await listEntities(context, user, [ABSTRACT_STIX_CORE_OBJECT], buildDynamicFilterArgs(dynamicFrom))
.then((result) => result.map((n) => n.id));
if (fromIds.length > 0) {
finalTimeSeriesParameter = { ...finalTimeSeriesParameter, fromId: args.fromId ? [...fromIds, args.fromId] : fromIds };
}
} else {
finalTimeSeriesParameter = { ...finalTimeSeriesParameter, dynamicFrom: undefined };
}
if (isNotEmptyField(dynamicTo)) {
if (isFilterGroupNotEmpty(dynamicTo)) {
const toIds = await listEntities(context, user, [ABSTRACT_STIX_CORE_OBJECT], buildDynamicFilterArgs(dynamicTo))
.then((result) => result.map((n) => n.id));
if (toIds.length > 0) {
finalTimeSeriesParameter = { ...finalTimeSeriesParameter, toId: args.toId ? [...toIds, args.toId] : toIds };
}
} else {
finalTimeSeriesParameter = { ...finalTimeSeriesParameter, dynamicTo: undefined };
}
return { data: timeSeriesRelations(context, user, { ...args, ...finalTimeSeriesParameter }) };
}));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ const STIX_RESOLUTION_MAP_PATHS: Record<string, string> = {
* For instance, labels are entities internally, and filter.values would contain these entities internal ids.
* In Stix, the labels are stored in plain text: we need to replace the ids in filter.values with their resolution.
*/
export const resolveFilter = (filter: Filter, resolutionMap: FilterResolutionMap): Filter => {
const resolveFilter = (filter: Filter, resolutionMap: FilterResolutionMap): Filter => {
const newFilterValues: string [] = [];
filter.values.forEach((v) => {
const resolution = resolutionMap.get(v);
Expand Down Expand Up @@ -122,6 +122,18 @@ const buildResolutionMapForFilter = async (context: AuthContext, user: AuthUser,
return map;
};

const mergeMaps = <K, V>(mapArray: Map<K, V>[]) : Map<K, V> => {
const mergedMap = new Map<K, V>();

mapArray.forEach((map) => {
map.forEach((value, key) => {
mergedMap.set(key, value);
});
});

return mergedMap;
};

/**
* recursively call buildResolutionMapForFilter inside a filter group
*/
Expand All @@ -134,7 +146,7 @@ export const buildResolutionMapForFilterGroup = async (
const filtersMaps = await Promise.all(filterGroup.filters.map((f) => buildResolutionMapForFilter(context, user, f, cache)));
const filterGroupsMaps = await Promise.all(filterGroup.filterGroups.map((fg) => buildResolutionMapForFilterGroup(context, user, fg, cache)));
// merge all maps into one; for a given unique key the last value wins
return new Map([...new Map(...filtersMaps), ...new Map(...filterGroupsMaps)]);
return mergeMaps<string, string>([mergeMaps<string, string>(filtersMaps), mergeMaps<string, string>(filterGroupsMaps)]);
};

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -231,12 +231,12 @@ export const testRelationTo = (stix: any, filter: Filter) => {
*/
export const testRelationFromTypes = (stix: any, filter: Filter) => {
if (stix.type === STIX_TYPE_RELATION) {
const stixValue = stix.extensions?.[STIX_EXT_OCTI].source_type;
const stixValue = stix.extensions?.[STIX_EXT_OCTI].source_type ?? [];
const extendedStixValues: string[] = [...toValidArray(stixValue), ...getParentTypes(stixValue)];
return testStringFilter(filter, extendedStixValues);
}
if (stix.type === STIX_TYPE_SIGHTING) {
const stixValue = stix.extensions?.[STIX_EXT_OCTI].sighting_of_type;
const stixValue = stix.extensions?.[STIX_EXT_OCTI].sighting_of_type ?? [];
const extendedStixValues: string[] = [...toValidArray(stixValue), ...getParentTypes(stixValue)];
return testStringFilter(filter, extendedStixValues);
}
Expand All @@ -250,7 +250,7 @@ export const testRelationFromTypes = (stix: any, filter: Filter) => {
*/
export const testRelationToTypes = (stix: any, filter: Filter) => {
if (stix.type === STIX_TYPE_RELATION) {
const stixValue: string = stix.extensions?.[STIX_EXT_OCTI].target_type;
const stixValue: string = stix.extensions?.[STIX_EXT_OCTI].target_type ?? [];
const extendedStixValues: string[] = [...toValidArray(stixValue), ...getParentTypes(stixValue)];
return testStringFilter(filter, extendedStixValues);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,16 @@ import {
ABSTRACT_INTERNAL_OBJECT,
ABSTRACT_STIX_CORE_OBJECT,
ENTITY_TYPE_CONTAINER,
ENTITY_TYPE_LOCATION,
ID_INTERNAL
} from '../../../src/schema/general';
import { ENTITY_TYPE_CONTAINER_REPORT, ENTITY_TYPE_MALWARE } from '../../../src/schema/stixDomainObject';
import {
ENTITY_TYPE_CONTAINER_REPORT,
ENTITY_TYPE_INTRUSION_SET,
ENTITY_TYPE_MALWARE
} from '../../../src/schema/stixDomainObject';
import { IDS_FILTER, SOURCE_RELIABILITY_FILTER } from '../../../src/utils/filtering/filtering-constants';
import { storeLoadById } from '../../../src/database/middleware-loader';

// test queries involving dynamic filters

Expand Down Expand Up @@ -1094,6 +1100,48 @@ describe('Complex filters combinations for elastic queries', () => {
} });
const numberOfEntitiesWithSourceReliabilityNotAOrNotB = queryResult.data.globalSearch.edges.length;
expect(numberOfEntitiesWithSourceReliabilityNotAOrNotB - numberOfEntitiesWithSourceReliabilityNotAAndNotB).toEqual(9); // number of entities with source_reliability A or B
// --- 16. filters with a relationship type key --- //
const location = await storeLoadById(testContext, ADMIN_USER, 'location--c3794ffd-0e71-4670-aa4d-978b4cbdc72c', ENTITY_TYPE_LOCATION);
const locationInternalId = location.internal_id;
const intrusionSet = await storeLoadById(testContext, ADMIN_USER, 'intrusion-set--18854f55-ac7c-4634-bd9a-352dd07613b7', ENTITY_TYPE_INTRUSION_SET);
const intrusionSetInternalId = intrusionSet.internal_id;
// (objects = internal-id-of-a-location)
queryResult = await queryAsAdmin({ query: LIST_QUERY,
variables: {
first: 20,
filters: {
mode: 'or',
filters: [
{
key: 'objects',
operator: 'eq',
values: [locationInternalId],
mode: 'or',
}
],
filterGroups: [],
},
} });
expect(queryResult.data.globalSearch.edges.length).toEqual(1); // 1 report contains this location
// (targets = internal-id-of-a-location)
queryResult = await queryAsAdmin({ query: LIST_QUERY,
variables: {
first: 20,
filters: {
mode: 'or',
filters: [
{
key: 'targets',
operator: 'eq',
values: [locationInternalId],
mode: 'or',
}
],
filterGroups: [],
},
} });
expect(queryResult.data.globalSearch.edges.length).toEqual(1); // 1 intrusion-set targets this location
expect(queryResult.data.globalSearch.edges[0].node.id).toEqual(intrusionSetInternalId);
});
it('should test environnement deleted', async () => {
const DELETE_REPORT_QUERY = gql`
Expand Down
Loading

0 comments on commit 9fa43b4

Please sign in to comment.