Skip to content

Commit

Permalink
[backend/frontend] modify EntityStixCoreRelationshipsEntitiesComponen…
Browse files Browse the repository at this point in the history
…t to list entities through relations (#6867)
  • Loading branch information
JeremyCloarec authored Jun 17, 2024
1 parent 94b6fe7 commit 2019c63
Show file tree
Hide file tree
Showing 30 changed files with 190 additions and 91 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import { PaginationLocalStorage } from '../../../../../utils/hooks/useLocalStora
import { DataColumns, PaginationOptions } from '../../../../../components/list_lines';
import { EntityStixCoreRelationshipsEntitiesViewLinesPaginationQuery$variables } from './__generated__/EntityStixCoreRelationshipsEntitiesViewLinesPaginationQuery.graphql';
import { isFilterGroupNotEmpty, useRemoveIdAndIncorrectKeysFromFilterGroupObject } from '../../../../../utils/filters/filtersUtils';
import { Filter, FilterGroup } from '../../../../../utils/filters/filtersHelpers-types';
import { FilterGroup } from '../../../../../utils/filters/filtersHelpers-types';

interface EntityStixCoreRelationshipsEntitiesViewProps {
entityId: string;
Expand Down Expand Up @@ -110,26 +110,16 @@ EntityStixCoreRelationshipsEntitiesViewProps

// Filters due to screen context
const userFilters = useRemoveIdAndIncorrectKeysFromFilterGroupObject(filters, stixCoreObjectTypes.length > 0 ? stixCoreObjectTypes : ['Stix-Core-Object']);
const stixCoreObjectFilter: Filter[] = stixCoreObjectTypes.length > 0
? [{ key: 'entity_type', operator: 'eq', mode: 'or', values: stixCoreObjectTypes }]
: [];
const contextFilters: FilterGroup = {
mode: 'and',
filters: [
...stixCoreObjectFilter,
{ key: 'regardingOf',
operator: 'eq',
mode: 'and',
values: [
{ key: 'id', values: [entityId], operator: 'eq', mode: 'or' },
{ key: 'relationship_type', values: relationshipTypes, operator: 'eq', mode: 'or' },
] as unknown as string[], // Workaround for typescript waiting for better solution
},
],
filters: [],
filterGroups: userFilters && isFilterGroupNotEmpty(userFilters) ? [userFilters] : [],
};

const paginationOptions = {
entityId,
relationshipTypes,
types: stixCoreObjectTypes,
search: searchTerm,
orderBy: sortBy && sortBy in dataColumns && dataColumns[sortBy].isSortable ? sortBy : 'name',
orderMode: orderAsc ? 'asc' : 'desc',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ interface EntityStixCoreRelationshipsEntitiesProps {
const entityStixCoreRelationshipsEntitiesFragment = graphql`
fragment EntityStixCoreRelationshipsEntitiesViewLines_data on Query
@argumentDefinitions(
entityId: { type: "ID" }
relationshipTypes: { type: "[String]" }
search: { type: "String" }
count: { type: "Int", defaultValue: 25 }
cursor: { type: "ID" }
Expand All @@ -41,15 +43,17 @@ const entityStixCoreRelationshipsEntitiesFragment = graphql`
types: { type: "[String]" }
)
@refetchable(queryName: "EntityStixCoreRelationshipsEntities_refetch") {
stixCoreObjects(
stixCoreObjectsRegardingOf(
entityId: $entityId
relationshipTypes: $relationshipTypes
search: $search
first: $count
after: $cursor
orderBy: $orderBy
orderMode: $orderMode
filters: $filters
types: $types
) @connection(key: "Pagination_stixCoreObjects") {
) @connection(key: "Pagination_stixCoreObjectsRegardingOf") {
edges {
node {
id
Expand All @@ -67,6 +71,8 @@ const entityStixCoreRelationshipsEntitiesFragment = graphql`

export const entityStixCoreRelationshipsEntitiesQuery = graphql`
query EntityStixCoreRelationshipsEntitiesViewLinesPaginationQuery(
$entityId: ID
$relationshipTypes: [String]
$search: String
$count: Int!
$cursor: ID
Expand All @@ -77,6 +83,8 @@ export const entityStixCoreRelationshipsEntitiesQuery = graphql`
) {
...EntityStixCoreRelationshipsEntitiesViewLines_data
@arguments(
entityId: $entityId
relationshipTypes: $relationshipTypes
search: $search
count: $count
cursor: $cursor
Expand Down Expand Up @@ -109,7 +117,7 @@ EntityStixCoreRelationshipsEntitiesProps
queryRef,
linesQuery: entityStixCoreRelationshipsEntitiesQuery,
linesFragment: entityStixCoreRelationshipsEntitiesFragment,
nodePath: ['stixCoreObjects', 'pageInfo', 'globalCount'],
nodePath: ['stixCoreObjectsRegardingOf', 'pageInfo', 'globalCount'],
setNumberOfElements,
});
return (
Expand All @@ -118,9 +126,9 @@ EntityStixCoreRelationshipsEntitiesProps
loadMore={loadMore}
hasMore={hasMore}
isLoading={isLoadingMore}
dataList={data?.stixCoreObjects?.edges ?? []}
dataList={data?.stixCoreObjectsRegardingOf?.edges ?? []}
globalCount={
data?.stixCoreObjects?.pageInfo?.globalCount ?? nbOfRowsToLoad
data?.stixCoreObjectsRegardingOf?.pageInfo?.globalCount ?? nbOfRowsToLoad
}
LineComponent={EntityStixCoreRelationshipsEntitiesViewLine}
DummyLineComponent={EntityStixCoreRelationshipsEntitiesLineDummy}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7405,6 +7405,7 @@ type Query {
stixCoreObjectRaw(id: String!): String
stixCoreObject(id: String!): StixCoreObject
stixCoreObjects(first: Int, after: ID, types: [String], orderBy: StixCoreObjectsOrdering, orderMode: OrderingMode, filters: FilterGroup, search: String): StixCoreObjectConnection
stixCoreObjectsRegardingOf(entityId: ID, relationshipTypes: [String], first: Int, after: ID, types: [String], orderBy: StixCoreObjectsOrdering, orderMode: OrderingMode, filters: FilterGroup, search: String): StixCoreObjectConnection
globalSearch(first: Int, after: ID, search: String, types: [String], orderBy: StixCoreObjectsOrdering, orderMode: OrderingMode, filters: FilterGroup): StixCoreObjectConnection
stixCoreObjectsExportFiles(first: Int, exportContext: ExportContext!): FileConnection
stixCoreObjectsTimeSeries(authorId: String, field: String!, operation: StatsOperation!, startDate: DateTime!, endDate: DateTime, interval: String!, onlyInferred: Boolean, types: [String], filters: FilterGroup, search: String): [TimeSeries]
Expand Down
11 changes: 11 additions & 0 deletions opencti-platform/opencti-graphql/config/schema/opencti.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -11383,6 +11383,17 @@ type Query {
filters: FilterGroup
search: String
): StixCoreObjectConnection @auth(for: [KNOWLEDGE])
stixCoreObjectsRegardingOf(
entityId: ID,
relationshipTypes: [String],
first: Int
after: ID
types: [String]
orderBy: StixCoreObjectsOrdering
orderMode: OrderingMode
filters: FilterGroup
search: String
): StixCoreObjectConnection @auth(for: [KNOWLEDGE])
globalSearch(
first: Int
after: ID
Expand Down
101 changes: 80 additions & 21 deletions opencti-platform/opencti-graphql/src/database/middleware-loader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -449,14 +449,16 @@ export const listEntitiesPaginated = async <T extends BasicStoreEntity>(context:
return elPaginate(context, user, indices, paginateArgs);
};

export const listEntitiesThroughRelationsPaginated = async <T extends BasicStoreCommon>(context: AuthContext, user: AuthUser, connectedEntityId: string,
relationType: string, entityType: string | string[], reverse_relation: boolean, args: EntityOptions<T> = {}): Promise<StoreCommonConnection<T>> => {
export const listEntitiesThroughRelationsPaginated = async <T extends BasicStoreCommon>(context: AuthContext, user: AuthUser,
connectedEntityId: string, relationType: string | string[], entityType: string | string[],
reverse_relation: boolean, both_ways: boolean, args: EntityOptions<T> = {}): Promise<StoreCommonConnection<T>> => {
const entityTypes = Array.isArray(entityType) ? entityType : [entityType];
const relationTypes = Array.isArray(relationType) ? relationType : [relationType];
const { indices = READ_ENTITIES_INDICES, connectionFormat } = args;
if (connectionFormat === false) {
throw UnsupportedError('List connected entities paginated require connectionFormat option to true');
}
if (UNIMPACTED_ENTITIES_ROLE.includes(`${relationType}_to`)) {
if (relationTypes.some((t) => UNIMPACTED_ENTITIES_ROLE.includes(`${t}_to`))) {
throw UnsupportedError('List connected entities paginated cant be used', { type: entityType });
}
const connectedFilters: FilterGroup = {
Expand All @@ -466,7 +468,7 @@ export const listEntitiesThroughRelationsPaginated = async <T extends BasicStore
key: [INSTANCE_REGARDING_OF],
values: [
{ key: 'id', values: [connectedEntityId] },
{ key: 'relationship_type', values: [relationType] }
{ key: 'relationship_type', values: relationTypes }
]
}
],
Expand All @@ -483,36 +485,93 @@ export const listEntitiesThroughRelationsPaginated = async <T extends BasicStore
// As rel de-normalization are currently not directional, we need to post filters the result
// Some entities could be found because of the none-directionality.
const entityIds = entityPagination.edges.map((e) => e.node.internal_id);
const filters: FilterGroupWithNested = {
mode: FilterMode.And,
filters: [
{
let filters: FilterGroupWithNested;
if (both_ways) {
const normalFilter: FilterGroupWithNested = {
mode: FilterMode.And,
filters: [{
key: ['connections'],
values: [],
nested: [
{ key: 'internal_id', values: reverse_relation ? entityIds : [connectedEntityId] },
...(reverse_relation ? [{ key: 'types', values: entityTypes }] : []),
{ key: 'internal_id', values: [connectedEntityId] },
{ key: 'role', values: ['*_from'], operator: FilterOperator.Wildcard },
]
}, {
key: ['connections'],
values: [],
nested: [
{ key: 'internal_id', values: reverse_relation ? [connectedEntityId] : entityIds },
...(reverse_relation ? [] : [{ key: 'types', values: entityTypes }]),
{ key: 'internal_id', values: entityIds },
{ key: 'types', values: entityTypes },
{ key: 'role', values: ['*_to'], operator: FilterOperator.Wildcard },
],
}],
filterGroups: [],
};
const connectedRelations = await listAllRelations<BasicStoreRelation>(context, user, relationType, { filters, connectionFormat: false });
filterGroups: [],
};
const reverseFilter: FilterGroupWithNested = {
mode: FilterMode.And,
filters: [{
key: ['connections'],
values: [],
nested: [
{ key: 'internal_id', values: entityIds },
{ key: 'types', values: entityTypes },
{ key: 'role', values: ['*_from'], operator: FilterOperator.Wildcard },
]
}, {
key: ['connections'],
values: [],
nested: [
{ key: 'internal_id', values: [connectedEntityId] },
{ key: 'role', values: ['*_to'], operator: FilterOperator.Wildcard },
],
}],
filterGroups: [],
};
filters = {
mode: FilterMode.Or,
filters: [],
filterGroups: [normalFilter, reverseFilter],
};
} else {
filters = {
mode: FilterMode.And,
filters: [
{
key: ['connections'],
values: [],
nested: [
{ key: 'internal_id', values: reverse_relation ? entityIds : [connectedEntityId] },
...(reverse_relation ? [{ key: 'types', values: entityTypes }] : []),
{ key: 'role', values: ['*_from'], operator: FilterOperator.Wildcard },
]
}, {
key: ['connections'],
values: [],
nested: [
{ key: 'internal_id', values: reverse_relation ? [connectedEntityId] : entityIds },
...(reverse_relation ? [] : [{ key: 'types', values: entityTypes }]),
{ key: 'role', values: ['*_to'], operator: FilterOperator.Wildcard },
],
}],
filterGroups: [],
};
}
const connectedRelations = await listAllRelations<BasicStoreRelation>(context, user, relationTypes, { filters, connectionFormat: false });
const relationsEntityMap = new Map();
connectedRelations.forEach((relation) => {
const id = reverse_relation ? relation.fromId : relation.toId;
if (relationsEntityMap.has(id)) {
relationsEntityMap.set(id, [...relationsEntityMap.get(id), relation]);
} else {
relationsEntityMap.set(id, [relation]);
if (entityIds.some((i) => i === relation.fromId)) {
if (relationsEntityMap.has(relation.fromId)) {
relationsEntityMap.set(relation.fromId, [...relationsEntityMap.get(relation.fromId), relation]);
} else {
relationsEntityMap.set(relation.fromId, [relation]);
}
}
if (entityIds.some((i) => i === relation.toId)) {
if (relationsEntityMap.has(relation.toId)) {
relationsEntityMap.set(relation.toId, [...relationsEntityMap.get(relation.toId), relation]);
} else {
relationsEntityMap.set(relation.toId, [relation]);
}
}
});
const rebuildEdges: BasicStoreCommonEdge<T>[] = [];
Expand All @@ -533,7 +592,7 @@ export const listEntitiesThroughRelationsPaginated = async <T extends BasicStore
export const loadEntityThroughRelationsPaginated = async <T extends BasicStoreCommon>(context: AuthContext, user: AuthUser, connectedEntityId: string,
relationType: string, entityType: string | string[], reverse_relation: boolean): Promise<T> => {
const args = { first: 1 };
const pagination = await listEntitiesThroughRelationsPaginated<T>(context, user, connectedEntityId, relationType, entityType, reverse_relation, args);
const pagination = await listEntitiesThroughRelationsPaginated<T>(context, user, connectedEntityId, relationType, entityType, reverse_relation, false, args);
return pagination.edges[0]?.node;
};

Expand Down
8 changes: 4 additions & 4 deletions opencti-platform/opencti-graphql/src/domain/attackPattern.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,11 @@ export const addAttackPattern = async (context: AuthContext, user: AuthUser, att
};

export const parentAttackPatternsPaginated = async (context: AuthContext, user: AuthUser, attackPatternId: string, args: EntityOptions<BasicStoreCommon>) => {
return listEntitiesThroughRelationsPaginated(context, user, attackPatternId, RELATION_SUBTECHNIQUE_OF, ENTITY_TYPE_ATTACK_PATTERN, false, args);
return listEntitiesThroughRelationsPaginated(context, user, attackPatternId, RELATION_SUBTECHNIQUE_OF, ENTITY_TYPE_ATTACK_PATTERN, false, false, args);
};

export const childAttackPatternsPaginated = async (context: AuthContext, user: AuthUser, attackPatternId: string, args: EntityOptions<BasicStoreCommon>) => {
return listEntitiesThroughRelationsPaginated(context, user, attackPatternId, RELATION_SUBTECHNIQUE_OF, ENTITY_TYPE_ATTACK_PATTERN, true, args);
return listEntitiesThroughRelationsPaginated(context, user, attackPatternId, RELATION_SUBTECHNIQUE_OF, ENTITY_TYPE_ATTACK_PATTERN, true, false, args);
};

export const isSubAttackPattern = async (context: AuthContext, user: AuthUser, attackPatternId: string) => {
Expand All @@ -36,9 +36,9 @@ export const isSubAttackPattern = async (context: AuthContext, user: AuthUser, a
};

export const coursesOfActionPaginated = async (context: AuthContext, user: AuthUser, attackPatternId: string, args: EntityOptions<BasicStoreCommon>) => {
return listEntitiesThroughRelationsPaginated(context, user, attackPatternId, RELATION_MITIGATES, ENTITY_TYPE_COURSE_OF_ACTION, true, args);
return listEntitiesThroughRelationsPaginated(context, user, attackPatternId, RELATION_MITIGATES, ENTITY_TYPE_COURSE_OF_ACTION, true, false, args);
};

export const dataComponentsPaginated = async (context: AuthContext, user: AuthUser, attackPatternId: string, args: EntityOptions<BasicStoreCommon>) => {
return listEntitiesThroughRelationsPaginated(context, user, attackPatternId, RELATION_DETECTS, ENTITY_TYPE_DATA_COMPONENT, true, args);
return listEntitiesThroughRelationsPaginated(context, user, attackPatternId, RELATION_DETECTS, ENTITY_TYPE_DATA_COMPONENT, true, false, args);
};
4 changes: 2 additions & 2 deletions opencti-platform/opencti-graphql/src/domain/container.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ export const objects = async (context, user, containerId, args) => {
while (hasNextPage) {
// Force options to prevent connection format and manage search after
const paginateOpts = { ...baseOpts, first: args.first ?? ES_DEFAULT_PAGINATION, after: searchAfter };
const currentPagination = await listEntitiesThroughRelationsPaginated(context, user, containerId, RELATION_OBJECT, types, false, paginateOpts);
const currentPagination = await listEntitiesThroughRelationsPaginated(context, user, containerId, RELATION_OBJECT, types, false, false, paginateOpts);
const noMoreElements = currentPagination.edges.length === 0 || currentPagination.edges.length < paginateOpts.first;
if (noMoreElements) {
hasNextPage = false;
Expand All @@ -69,7 +69,7 @@ export const objects = async (context, user, containerId, args) => {
}
return paginatedElements;
}
return listEntitiesThroughRelationsPaginated(context, user, containerId, RELATION_OBJECT, types, false, baseOpts);
return listEntitiesThroughRelationsPaginated(context, user, containerId, RELATION_OBJECT, types, false, false, baseOpts);
};

// List first 1000 objects of this container
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,5 +20,5 @@ export const addCourseOfAction = async (context, user, courseOfAction) => {
};

export const attackPatternsPaginated = async (context, user, attackPatternId, args) => {
return listEntitiesThroughRelationsPaginated(context, user, attackPatternId, RELATION_MITIGATES, ENTITY_TYPE_ATTACK_PATTERN, false, args);
return listEntitiesThroughRelationsPaginated(context, user, attackPatternId, RELATION_MITIGATES, ENTITY_TYPE_ATTACK_PATTERN, false, false, args);
};
4 changes: 2 additions & 2 deletions opencti-platform/opencti-graphql/src/domain/group.js
Original file line number Diff line number Diff line change
Expand Up @@ -108,11 +108,11 @@ export const defaultMarkingDefinitionsFromGroups = async (context, groupIds) =>
};

export const rolesPaginated = async (context, user, groupId, args) => {
return listEntitiesThroughRelationsPaginated(context, user, groupId, RELATION_HAS_ROLE, ENTITY_TYPE_ROLE, false, args);
return listEntitiesThroughRelationsPaginated(context, user, groupId, RELATION_HAS_ROLE, ENTITY_TYPE_ROLE, false, false, args);
};

export const membersPaginated = async (context, user, groupId, args) => {
return listEntitiesThroughRelationsPaginated(context, user, groupId, RELATION_MEMBER_OF, ENTITY_TYPE_USER, true, args);
return listEntitiesThroughRelationsPaginated(context, user, groupId, RELATION_MEMBER_OF, ENTITY_TYPE_USER, true, false, args);
};

export const groupDelete = async (context, user, groupId) => {
Expand Down
2 changes: 1 addition & 1 deletion opencti-platform/opencti-graphql/src/domain/individual.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ export const addIndividual = async (context, user, individual, opts = {}) => {
};

export const partOfOrganizationsPaginated = async (context, user, individualId, args) => {
return listEntitiesThroughRelationsPaginated(context, user, individualId, RELATION_PART_OF, ENTITY_TYPE_IDENTITY_ORGANIZATION, false, args);
return listEntitiesThroughRelationsPaginated(context, user, individualId, RELATION_PART_OF, ENTITY_TYPE_IDENTITY_ORGANIZATION, false, false, args);
};

export const isUser = async (context, individualContactInformation) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,5 +26,5 @@ export const addIntrusionSet = async (context, user, intrusionSet) => {
};

export const locationsPaginated = async (context, user, intrusionSetId, args) => {
return listEntitiesThroughRelationsPaginated(context, user, intrusionSetId, RELATION_ORIGINATES_FROM, ENTITY_TYPE_LOCATION, false, args);
return listEntitiesThroughRelationsPaginated(context, user, intrusionSetId, RELATION_ORIGINATES_FROM, ENTITY_TYPE_LOCATION, false, false, args);
};
Loading

0 comments on commit 2019c63

Please sign in to comment.