Skip to content

Commit

Permalink
[frontend] Enable enrichment with the selectAll of observables and st…
Browse files Browse the repository at this point in the history
…ix-domain-objects of the same type (#5582)
  • Loading branch information
Gwendoline-FAVRE-FELIX authored Oct 21, 2024
1 parent 17fd433 commit 6d54d59
Show file tree
Hide file tree
Showing 3 changed files with 236 additions and 65 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,12 @@ import { hexToRGB } from '../../../utils/Colors';
import { externalReferencesQueriesSearchQuery } from '../analyses/external_references/ExternalReferencesQueries';
import StixDomainObjectCreation from '../common/stix_domain_objects/StixDomainObjectCreation';
import ItemMarkings from '../../../components/ItemMarkings';
import { findFilterFromKey, removeIdAndIncorrectKeysFromFilterGroupObject, serializeFilterGroupForBackend } from '../../../utils/filters/filtersUtils';
import {
findFilterFromKey,
getEntityTypeTwoFirstLevelsFilterValues,
removeIdAndIncorrectKeysFromFilterGroupObject,
serializeFilterGroupForBackend,
} from '../../../utils/filters/filtersUtils';
import { getMainRepresentative } from '../../../utils/defaultRepresentatives';
import { isNotEmptyField } from '../../../utils/utils';
import EETooltip from '../common/entreprise_edition/EETooltip';
Expand Down Expand Up @@ -1277,10 +1282,9 @@ class DataTableToolBar extends Component {
this.setState((prevState) => ({ promoteToContainer: !prevState.promoteToContainer }));
}

getSelectedTypes() {
const entityTypeFilterValues = findFilterFromKey(this.props.filters?.filters ?? [], 'entity_type', 'eq')?.values ?? [];
getSelectedTypes(observableTypes, domainObjectTypes) {
const entityTypeFilterValues = getEntityTypeTwoFirstLevelsFilterValues(this.props.filters, observableTypes, domainObjectTypes);
const selectedElementsList = Object.values(this.props.selectedElements || {});

const selectedTypes = R.uniq([...selectedElementsList.map((o) => o.entity_type), ...entityTypeFilterValues]
.filter((entity_type) => entity_type !== undefined));
return { entityTypeFilterValues, selectedElementsList, selectedTypes };
Expand Down Expand Up @@ -1310,66 +1314,6 @@ class DataTableToolBar extends Component {
taskScope,
} = this.props;
const { actions, keptEntityId, mergingElement, actionsInputs, promoteToContainer } = this.state;
const { entityTypeFilterValues, selectedElementsList, selectedTypes } = this.getSelectedTypes();

// Some filter types are high level, we do not want to check them as "Different"
// We might need to add some other types here before refactoring the toolbar
const typesAreDifferent = (selectedTypes.filter((type) => !['Stix-Domain-Object', 'stix-core-relationship', 'Stix-Cyber-Observable'].includes(type))).length > 1;
const preventMerge = selectedTypes.at(0) === 'Vocabulary'
&& Object.values(selectedElements).some(({ builtIn }) => Boolean(builtIn));
// region update
const typesAreNotUpdatable = notUpdatableTypes.includes(selectedTypes[0])
|| (entityTypeFilterValues.length === 1
&& notUpdatableTypes.includes(entityTypeFilterValues[0]));
// endregion
// region rules
const typesAreNotScannable = notScannableTypes.includes(selectedTypes[0])
|| (entityTypeFilterValues.length === 1
&& notScannableTypes.includes(entityTypeFilterValues[0]));
// endregion
// region enrich
const isManualEnrichSelect = !selectAll && (selectedTypes.filter((st) => !['Stix-Cyber-Observable'].includes(st))).length === 1;
const isAllEnrichSelect = selectAll
&& entityTypeFilterValues.length === 1
&& entityTypeFilterValues[0] !== 'Stix-Cyber-Observable'
&& entityTypeFilterValues[0] !== 'Stix-Domain-Object';
const enrichDisable = notEnrichableTypes.includes(selectedTypes[0])
|| (entityTypeFilterValues.length === 1
&& notEnrichableTypes.includes(entityTypeFilterValues[0]))
|| (!isManualEnrichSelect && !isAllEnrichSelect);
// endregion
// region orgaSharing
const isShareableType = !notShareableTypes.includes(selectedTypes[0]);
// endregion
const typesAreNotMergable = notMergableTypes.includes(selectedTypes[0]);
const enableMerge = !typesAreNotMergable && !mergeDisable;
const typesAreNotAddableInContainer = notAddableTypes.includes(selectedTypes[0])
|| (entityTypeFilterValues.length === 1
&& notScannableTypes.includes(entityTypeFilterValues[0]));
const titleCopy = this.titleCopy();
let keptElement = null;
let newAliases = [];
if (!typesAreNotMergable && !typesAreDifferent) {
keptElement = keptEntityId
? selectedElementsList.find((o) => o.id === keptEntityId)
: selectedElementsList[0];
if (keptElement) {
const names = selectedElementsList
.map((el) => el.name)
.filter((name) => name !== keptElement.name);
const aliases = keptElement.aliases !== null
? selectedElementsList
.map((el) => el.aliases)
.flat()
.filter((alias) => alias !== null && alias !== undefined)
: selectedElementsList
.map((el) => el.x_opencti_aliases)
.flat()
.filter((alias) => alias !== null && alias !== undefined);

newAliases = names.concat(aliases).filter((o) => o && o.length > 0);
}
}

let deleteCapability = KNOWLEDGE_KNUPDATE_KNDELETE;
if (taskScope === 'DASHBOARD') deleteCapability = EXPLORE_EXUPDATE_EXDELETE;
Expand All @@ -1379,6 +1323,69 @@ class DataTableToolBar extends Component {
return (
<UserContext.Consumer>
{({ schema, settings }) => {
const stixCyberObservableSubTypes = schema.scos.map((sco) => sco.id);
const stixDomainObjectSubTypes = schema.sdos.map((sdo) => sdo.id);
const { entityTypeFilterValues, selectedElementsList, selectedTypes } = this.getSelectedTypes(stixCyberObservableSubTypes, stixDomainObjectSubTypes);
// Some filter types are high level, we do not want to check them as "Different"
// We might need to add some other types here before refactoring the toolbar
const typesAreDifferent = (selectedTypes.filter((type) => !['Stix-Domain-Object', 'stix-core-relationship', 'Stix-Cyber-Observable'].includes(type))).length > 1;
const preventMerge = selectedTypes.at(0) === 'Vocabulary'
&& Object.values(selectedElements).some(({ builtIn }) => Boolean(builtIn));
// region update
const typesAreNotUpdatable = notUpdatableTypes.includes(selectedTypes[0])
|| (entityTypeFilterValues.length === 1
&& notUpdatableTypes.includes(entityTypeFilterValues[0]));
// endregion
// region rules
const typesAreNotScannable = notScannableTypes.includes(selectedTypes[0])
|| (entityTypeFilterValues.length === 1
&& notScannableTypes.includes(entityTypeFilterValues[0]));
// endregion
// region enrich
const isManualEnrichSelect = !selectAll && (selectedTypes.filter((st) => !['Stix-Cyber-Observable', 'Stix-Domain-Object'].includes(st))).length === 1;
const isAllEnrichSelect = selectAll
&& entityTypeFilterValues.length === 1
&& entityTypeFilterValues[0] !== 'Stix-Cyber-Observable'
&& entityTypeFilterValues[0] !== 'Stix-Domain-Object';
const enrichDisable = notEnrichableTypes.includes(selectedTypes[0])
|| (entityTypeFilterValues.length === 1
&& notEnrichableTypes.includes(entityTypeFilterValues[0]))
|| (!isManualEnrichSelect && !isAllEnrichSelect);
// endregion
// region orgaSharing
const isShareableType = !notShareableTypes.includes(selectedTypes[0]);
// endregion
// region merge
const typesAreNotMergable = notMergableTypes.includes(selectedTypes[0]);
const enableMerge = !typesAreNotMergable && !mergeDisable;
const typesAreNotAddableInContainer = notAddableTypes.includes(selectedTypes[0])
|| (entityTypeFilterValues.length === 1
&& notScannableTypes.includes(entityTypeFilterValues[0]));
const titleCopy = this.titleCopy();
let keptElement = null;
let newAliases = [];
if (!typesAreNotMergable && !typesAreDifferent) {
keptElement = keptEntityId
? selectedElementsList.find((o) => o.id === keptEntityId)
: selectedElementsList[0];
if (keptElement) {
const names = selectedElementsList
.map((el) => el.name)
.filter((name) => name !== keptElement.name);
const aliases = keptElement.aliases !== null
? selectedElementsList
.map((el) => el.aliases)
.flat()
.filter((alias) => alias !== null && alias !== undefined)
: selectedElementsList
.map((el) => el.x_opencti_aliases)
.flat()
.filter((alias) => alias !== null && alias !== undefined);

newAliases = names.concat(aliases).filter((o) => o && o.length > 0);
}
}
// endregion
// region EE
const isEnterpriseEdition = isNotEmptyField(settings.enterprise_edition);
// endregion
Expand Down
30 changes: 30 additions & 0 deletions opencti-platform/opencti-front/src/utils/filters/filtersUtils.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,36 @@ export const removeEntityTypeAllFromFilterGroup = (inputFilters?: FilterGroup) =
return inputFilters;
};

// fetch the entity type filters possible values of first and second levels
// and remove Observable if the filters target only some sub observable types
// exemple: Observable AND (Domain-Name) --> [Domain-Name]
// exemple: Domain-Name OR Observable --> [Domain-Name, Observable]
// exemple: Stix-Domain-Object AND (Malware OR (Country AND City)) --> [Stix-Domain-Object, Malware]
export const getEntityTypeTwoFirstLevelsFilterValues = (filters?: FilterGroup, observableTypes?: string[], domainObjectTypes?: string []) => {
if (!filters) {
return [];
}
let firstLevelValues = findFilterFromKey(filters.filters, 'entity_type', 'eq')?.values ?? [];
const subFiltersSeparatedWithAnd = filters.filterGroups
.filter((fg) => fg.mode === 'and' || (fg.mode === 'or' && fg.filters.length === 1))
.map((fg) => fg.filters)
.flat();
if (subFiltersSeparatedWithAnd.length > 0) {
const secondLevelValues = findFilterFromKey(subFiltersSeparatedWithAnd, 'entity_type', 'eq')?.values ?? [];
if (filters.mode === 'and') {
// if all second values are observables sub types : remove observable from firstLevelValue
if (secondLevelValues.every((type) => observableTypes?.includes(type))) {
firstLevelValues = firstLevelValues.filter((type) => type !== 'Stix-Cyber-Observable');
}
if (secondLevelValues.every((type) => domainObjectTypes?.includes(type))) {
firstLevelValues = firstLevelValues.filter((type) => type !== 'Stix-Domain-Object');
}
}
return [...firstLevelValues, ...secondLevelValues];
}
return firstLevelValues;
};

// construct filters and options for widgets
export const buildFiltersAndOptionsForWidgets = (
inputFilters: FilterGroup | undefined,
Expand Down
136 changes: 135 additions & 1 deletion opencti-platform/opencti-front/src/utils/tests/filtersUtils.test.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { describe, it, expect } from 'vitest';
import { renderHook } from '@testing-library/react';
import React from 'react';
import { useBuildFilterKeysMapFromEntityType } from '../filters/filtersUtils';
import { getEntityTypeTwoFirstLevelsFilterValues, useBuildFilterKeysMapFromEntityType } from '../filters/filtersUtils';
import { createMockUserContext, ProvidersWrapper, ProvidersWrapperProps } from './test-render';
import { BYPASS } from '../hooks/useGranted';
import filterKeysSchema from './FilterUtilsConstants';
Expand Down Expand Up @@ -44,4 +44,138 @@ describe('Filters utils', () => {
expect(result.current).toStrictEqual(filterKeysSchema.get(stixCoreObjectKey));
});
});

describe('getEntityTypeTwoFirstLevelsFilterValues', () => {
it('should return only observable subtypes when filter with AND Observables', () => {
// filters: Observable AND Domain-Name
// result: Domain-Name
const filters = {
mode: 'and',
filters: [{ key: 'entity_type', operator: 'eq', values: ['Stix-Cyber-Observable'] }],
filterGroups: [
{
mode: 'and',
filters: [{ key: 'entity_type', operator: 'eq', values: ['Domain-Name'] }],
filterGroups: [],
},
],
};
const result = getEntityTypeTwoFirstLevelsFilterValues(filters, ['Domain-Name', 'File']);
expect(result).toEqual(['Domain-Name']);
});

it('should return both the observable subtypes and observable when filter with OR Observables and filter groups', () => {
// filters: Observable OR Domain-Name
// result: Observable, Domain-Name
const filters = {
mode: 'or',
filters: [{ key: 'entity_type', operator: 'eq', values: ['Stix-Cyber-Observable'] }],
filterGroups: [
{
mode: 'and',
filters: [
{ key: 'entity_type', operator: 'eq', values: ['Domain-Name'] },
],
filterGroups: [],
},
],
};
const result = getEntityTypeTwoFirstLevelsFilterValues(filters, ['Domain-Name', 'File'], ['Stix-Domain-Object']);
expect(result).toEqual(['Stix-Cyber-Observable', 'Domain-Name']);
});

it('should return both the observable subtypes and observable when filter with OR Observables', () => {
// filters: Domain-Name OR Observable
// result: Domain-Name, Observable
const filters = {
mode: 'or',
filters: [{ key: 'entity_type', operator: 'eq', values: ['Domain-Name', 'Stix-Cyber-Observable'] }],
filterGroups: [],
};
const result = getEntityTypeTwoFirstLevelsFilterValues(filters, ['Domain-Name', 'File'], ['Stix-Domain-Object']);
expect(result).toEqual(['Domain-Name', 'Stix-Cyber-Observable']);
});

it('should return only observable subtypes when filter with AND Observables and filter groups', () => {
// filters: Observable AND (Domain-Name AND label=label1)
// result: Domain-Name
const filters = {
mode: 'and',
filters: [{ key: 'entity_type', operator: 'eq', values: ['Stix-Cyber-Observable'] }],
filterGroups: [
{
mode: 'and',
filters: [
{ key: 'entity_type', operator: 'eq', values: ['Domain-Name'] },
{ key: 'objectLabel', operator: 'eq', values: ['label1'] },
],
filterGroups: [],
},
],
};
const result = getEntityTypeTwoFirstLevelsFilterValues(filters, ['Domain-Name', 'File']);
expect(result).toEqual(['Domain-Name']);
});

it('should return only observable when filter with OR Observables and filter groups', () => {
// filters: Observable AND (Domain-Name OR label=label1)
// result: Observable
const filters = {
mode: 'and',
filters: [{ key: 'entity_type', operator: 'eq', values: ['Stix-Cyber-Observable'] }],
filterGroups: [
{
mode: 'or',
filters: [
{ key: 'entity_type', operator: 'eq', values: ['Domain-Name'] },
{ key: 'objectLabel', operator: 'eq', values: ['label1'] },
],
filterGroups: [],
},
],
};
const result = getEntityTypeTwoFirstLevelsFilterValues(filters, ['Domain-Name', 'File'], ['Report', 'Malware']);
expect(result).toEqual(['Stix-Cyber-Observable']);
});

it('should return only observable subtypes when filter with AND Observables', () => {
// filters: Observable AND (Domain-Name OR File)
// result: Domain-Name, File
const filters = {
mode: 'and',
filters: [
{ key: 'entity_type', operator: 'eq', values: ['Stix-Cyber-Observable'] },
],
filterGroups: [
{
mode: 'and',
filters: [
{ key: 'entity_type', operator: 'eq', values: ['Domain-Name', 'File'] },
],
filterGroups: [],
},
],
};
const result = getEntityTypeTwoFirstLevelsFilterValues(filters, ['Domain-Name', 'File'], ['Report', 'Malware']);
expect(result).toEqual(['Domain-Name', 'File']);
});

it('should return only the domain subtype when filter with OR and only one type is provided', () => {
// filters: Stix-Domain-Object AND Malware
// result: Malware
const filters = {
mode: 'and',
filters: [{ key: 'entity_type', operator: 'eq', values: ['Stix-Domain-Object'] }],
filterGroups: [
{
mode: 'or',
filters: [{ key: 'entity_type', operator: 'eq', values: ['Malware'] }],
filterGroups: [],
},
],
};
const result = getEntityTypeTwoFirstLevelsFilterValues(filters, ['Domain-Name', 'File'], ['Malware', 'Artifact', 'Country', 'City']);
expect(result).toEqual(['Malware']);
});
});
});

0 comments on commit 6d54d59

Please sign in to comment.