Skip to content

Commit

Permalink
[frontend/backend] fix Threat Actor Knowledge filters + add filters i…
Browse files Browse the repository at this point in the history
…n Indicator Knowledge (#4939)

Co-authored-by: Adrien Servel <adrien.servel@filigran.io>
  • Loading branch information
Archidoit and Kedae authored Feb 27, 2024
1 parent cb26a80 commit af2a02b
Show file tree
Hide file tree
Showing 8 changed files with 98 additions and 78 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ EntityStixCoreRelationshipsEntitiesViewProps
defaultStopTime,
localStorage,
relationshipTypes,
stixCoreObjectTypes = [],
stixCoreObjectTypes = ['Stix-Core-Object'],
isRelationReversed,
currentView,
enableNestedView,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,36 +1,48 @@
import React, { Component } from 'react';
import * as PropTypes from 'prop-types';
import { UserContext } from '../../../../utils/hooks/useAuth';
import React from 'react';
import useAuth from '../../../../utils/hooks/useAuth';
import { QueryRenderer } from '../../../../relay/environment';
import ListLines from '../../../../components/list_lines/ListLines';
import IndicatorEntitiesLines, { indicatorEntitiesLinesQuery } from './IndicatorEntitiesLines';
import StixCoreRelationshipCreationFromEntity from '../../common/stix_core_relationships/StixCoreRelationshipCreationFromEntity';
import Security from '../../../../utils/Security';
import { KNOWLEDGE_KNUPDATE } from '../../../../utils/hooks/useGranted';
import { usePaginationLocalStorage } from '../../../../utils/hooks/useLocalStorage';
import { emptyFilterGroup } from '../../../../utils/filters/filtersUtils';

class IndicatorEntities extends Component {
constructor(props) {
super(props);
this.state = {
sortBy: null,
orderAsc: false,
const IndicatorEntities = ({ indicatorId, relationshipType, defaultStartTime, defaultStopTime }) => {
const LOCAL_STORAGE_KEY = 'indicator-entities';

const { viewStorage, helpers, paginationOptions: rawPaginationOptions } = usePaginationLocalStorage(
LOCAL_STORAGE_KEY,
{
searchTerm: '',
view: 'lines',
};
}
sortBy: 'created_at',
orderAsc: false,
openExports: false,
filters: emptyFilterGroup,
numberOfElements: {
number: 0,
symbol: '',
},
},
);

handleSort(field, orderAsc) {
this.setState({ sortBy: field, orderAsc });
}
const {
sortBy,
orderAsc,
filters,
} = viewStorage;
const paginationOptions = {
...rawPaginationOptions,
fromId: indicatorId,
relationship_type: relationshipType || 'stix-core-relationship',
};

handleSearch(value) {
this.setState({ searchTerm: value });
}
const {
platformModuleHelpers: { isRuntimeFieldEnable },
} = useAuth();

renderLines(platformModuleHelpers, paginationOptions) {
const isRuntimeSort = platformModuleHelpers.isRuntimeFieldEnable();
const { indicatorId } = this.props;
const { sortBy, orderAsc } = this.state;
const renderLines = () => {
const link = `/dashboard/observations/indicators/${indicatorId}/knowledge`;
const dataColumns = {
relationship_type: {
Expand All @@ -39,7 +51,7 @@ class IndicatorEntities extends Component {
isSortable: true,
},
entity_type: {
label: 'Entity type',
label: 'Target type',
width: '12%',
isSortable: false,
},
Expand All @@ -51,12 +63,12 @@ class IndicatorEntities extends Component {
createdBy: {
label: 'Author',
width: '12%',
isSortable: isRuntimeSort,
isSortable: isRuntimeFieldEnable(),
},
creator: {
label: 'Creators',
width: '12%',
isSortable: isRuntimeSort,
isSortable: isRuntimeFieldEnable(),
},
start_time: {
label: 'First obs.',
Expand All @@ -75,14 +87,22 @@ class IndicatorEntities extends Component {
};
return (
<ListLines
helpers={helpers}
sortBy={sortBy}
orderAsc={orderAsc}
dataColumns={dataColumns}
handleSort={this.handleSort.bind(this)}
handleSearch={this.handleSearch.bind(this)}
handleSort={helpers.handleSort}
handleSearch={helpers.handleSearch}
handleAddFilter={helpers.handleAddFilter}
handleRemoveFilter={helpers.handleRemoveFilter}
handleSwitchGlobalMode={helpers.handleSwitchGlobalMode}
handleSwitchLocalMode={helpers.handleSwitchLocalMode}
displayImport={true}
secondaryAction={true}
noBottomPadding={true}
filters={filters}
paginationOptions={paginationOptions}
entityTypes={['stix-core-relationship']}
>
<QueryRenderer
query={indicatorEntitiesLinesQuery}
Expand All @@ -101,56 +121,34 @@ class IndicatorEntities extends Component {
/>
</ListLines>
);
}
};

render() {
const { sortBy, orderAsc, searchTerm } = this.state;
const { indicatorId, relationshipType, defaultStartTime, defaultStopTime } = this.props;
const paginationOptions = {
fromId: indicatorId,
relationship_type: relationshipType || 'stix-core-relationship',
search: searchTerm,
orderBy: sortBy,
orderMode: orderAsc ? 'asc' : 'desc',
};
return (
<UserContext.Consumer>
{({ platformModuleHelpers }) => (
<>
{this.renderLines(platformModuleHelpers, paginationOptions)}
<Security needs={[KNOWLEDGE_KNUPDATE]}>
<StixCoreRelationshipCreationFromEntity
paginationOptions={paginationOptions}
entityId={indicatorId}
isRelationReversed={false}
targetStixDomainObjectTypes={[
'Threat-Actor',
'Intrusion-Set',
'Campaign',
'Incident',
'Malware',
'Infrastructure',
'Tool',
'Vulnerability',
'Attack-Pattern',
'Indicator',
]}
defaultStartTime={defaultStartTime}
defaultStopTime={defaultStopTime}
/>
</Security>
</>
)}
</UserContext.Consumer>
);
}
}

IndicatorEntities.propTypes = {
indicatorId: PropTypes.string,
relationshipType: PropTypes.string,
defaultStartTime: PropTypes.string,
defaultStopTime: PropTypes.string,
return (
<>
{renderLines()}
<Security needs={[KNOWLEDGE_KNUPDATE]}>
<StixCoreRelationshipCreationFromEntity
paginationOptions={paginationOptions}
entityId={indicatorId}
isRelationReversed={false}
targetStixDomainObjectTypes={[
'Threat-Actor',
'Intrusion-Set',
'Campaign',
'Incident',
'Malware',
'Infrastructure',
'Tool',
'Vulnerability',
'Attack-Pattern',
'Indicator',
]}
defaultStartTime={defaultStartTime}
defaultStopTime={defaultStopTime}
/>
</Security>
</>
);
};

export default IndicatorEntities;
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ export const indicatorEntitiesLinesQuery = graphql`
$cursor: ID
$orderBy: StixCoreRelationshipsOrdering
$orderMode: OrderingMode
$filters: FilterGroup
) {
...IndicatorEntitiesLines_data
@arguments(
Expand All @@ -108,6 +109,7 @@ export const indicatorEntitiesLinesQuery = graphql`
cursor: $cursor
orderBy: $orderBy
orderMode: $orderMode
filters: $filters
)
}
`;
Expand All @@ -133,6 +135,7 @@ export default createPaginationContainer(
defaultValue: start_time
}
orderMode: { type: "OrderingMode" }
filters: { type: "FilterGroup" }
) {
stixCoreRelationships(
fromId: $fromId
Expand All @@ -147,6 +150,7 @@ export default createPaginationContainer(
after: $cursor
orderBy: $orderBy
orderMode: $orderMode
filters: $filters
) @connection(key: "Pagination_stixCoreRelationships") {
edges {
node {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -682,6 +682,11 @@ const useSearchEntities = ({
.filter((type) => type !== 'Container')
.concat(containerTypes);
}
if (availableEntityTypes.includes('Threat-Actor')) {
completedAvailableEntityTypes = completedAvailableEntityTypes
.filter((type) => type !== 'Threat-Actor')
.concat(['Threat-Actor-Individual', 'Threat-Actor-Group']);
}
const entitiesTypes = completedAvailableEntityTypes
.map((n) => ({
label: t_i18n(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { schemaAttributesDefinition } from '../../schema/schema-attributes';
import { STIX_CORE_RELATIONSHIPS } from '../../schema/stixCoreRelationship';

export const stixCoreRelationshipsAttributes: Array<AttributeDefinition> = [
{ name: 'entity_type', label: 'Entity type', type: 'string', format: 'short', editDefault: false, mandatoryType: 'no', multiple: true, upsert: true, isFilterable: false },
{ name: 'start_time', label: 'First observation', type: 'date', mandatoryType: 'customizable', editDefault: true, multiple: false, upsert: true, isFilterable: true },
{ name: 'stop_time', label: 'Last observation', type: 'date', mandatoryType: 'customizable', editDefault: true, multiple: false, upsert: true, isFilterable: true },
{ name: 'description', label: 'Description', type: 'string', format: 'text', mandatoryType: 'customizable', editDefault: true, multiple: false, upsert: true, isFilterable: true },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import {
xOpenctiReliability
} from '../../schema/attribute-definition';
import { schemaAttributesDefinition } from '../../schema/schema-attributes';
import { ABSTRACT_STIX_DOMAIN_OBJECT, ENTITY_TYPE_CONTAINER, ENTITY_TYPE_IDENTITY, ENTITY_TYPE_LOCATION } from '../../schema/general';
import { ABSTRACT_STIX_DOMAIN_OBJECT, ENTITY_TYPE_CONTAINER, ENTITY_TYPE_IDENTITY, ENTITY_TYPE_LOCATION, ENTITY_TYPE_THREAT_ACTOR } from '../../schema/general';
import {
ENTITY_TYPE_ATTACK_PATTERN,
ENTITY_TYPE_CAMPAIGN,
Expand Down Expand Up @@ -216,6 +216,7 @@ const stixDomainObjectsAttributes: { [k: string]: Array<AttributeDefinition> } =
{ name: 'secondary_motivations', label: 'Secondary motivation', type: 'string', format: 'vocabulary', vocabularyCategory: 'attack_motivation_ov', mandatoryType: 'no', editDefault: false, multiple: true, upsert: true, isFilterable: true },
{ name: 'personal_motivations', label: 'Personal motivation', type: 'string', format: 'vocabulary', vocabularyCategory: 'attack_motivation_ov', mandatoryType: 'no', editDefault: false, multiple: true, upsert: false, isFilterable: false },
],
[ENTITY_TYPE_THREAT_ACTOR]: [],
[ENTITY_TYPE_TOOL]: [
iAliasedIds,
aliases,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import { ENTITY_TYPE_CONTAINER_CASE_RFI } from '../modules/case/case-rfi/case-rf
import { ENTITY_TYPE_CONTAINER_CASE_RFT } from '../modules/case/case-rft/case-rft-types';
import { ENTITY_TYPE_CONTAINER_FEEDBACK } from '../modules/case/feedback/feedback-types';
import { ENTITY_TYPE_CONTAINER_TASK } from '../modules/task/task-types';
import { ENTITY_TYPE_THREAT_ACTOR_INDIVIDUAL } from '../modules/threatActorIndividual/threatActorIndividual-types';

export const ATTRIBUTE_NAME = 'name';
export const ATTRIBUTE_ABSTRACT = 'attribute_abstract';
Expand Down Expand Up @@ -106,6 +107,7 @@ export const isStixDomainObjectLocation = (type: string): boolean => schemaTypes

const STIX_DOMAIN_OBJECT_THREAT_ACTORS: Array<string> = [
ENTITY_TYPE_THREAT_ACTOR_GROUP,
ENTITY_TYPE_THREAT_ACTOR_INDIVIDUAL,
];
schemaTypesDefinition.register(ENTITY_TYPE_THREAT_ACTOR, STIX_DOMAIN_OBJECT_THREAT_ACTORS);
export const isStixDomainObjectThreatActor = (type: string): boolean => schemaTypesDefinition.isTypeIncludedIn(type, ENTITY_TYPE_THREAT_ACTOR)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
ABSTRACT_STIX_CYBER_OBSERVABLE,
ENTITY_TYPE_CONTAINER,
ENTITY_TYPE_LOCATION,
ENTITY_TYPE_THREAT_ACTOR,
INPUT_CREATED_BY,
INPUT_KILLCHAIN,
INPUT_LABELS
Expand Down Expand Up @@ -185,6 +186,14 @@ describe('Filter keys schema generation testing', async () => {
// Location
filterDefinition = filterKeysSchema.get(ENTITY_TYPE_LOCATION)?.get(INPUT_CREATED_BY);
expect(filterDefinition?.subEntityTypes.length).toEqual(6); // 5 locations + abstract type 'Location'
// Threat Actor
filterDefinition = filterKeysSchema.get(ENTITY_TYPE_THREAT_ACTOR)?.get('threat_actor_types');
expect(filterDefinition?.type).toEqual('vocabulary');
expect(filterDefinition?.subEntityTypes.length).toEqual(2); // 'Threat-Actor-Group' and 'Threat-Actor-Individual'
filterDefinition = filterKeysSchema.get(ENTITY_TYPE_THREAT_ACTOR)?.get('hair_color');
expect(filterDefinition?.type).toEqual('vocabulary');
expect(filterDefinition?.subEntityTypes.length).toEqual(1); // 'Threat-Actor-Individual'

// Stix Core Relationships
filterDefinition = filterKeysSchema.get(ABSTRACT_STIX_CORE_RELATIONSHIP)?.get('fromId');
expect(filterDefinition?.subEntityTypes.length).toEqual(49); // 48 stix core relationship types + abstract type 'stix-core-relationships'
Expand Down

0 comments on commit af2a02b

Please sign in to comment.