Skip to content

Commit

Permalink
[backend] add askAnalysis mutation to request file or fields analysis (
Browse files Browse the repository at this point in the history
  • Loading branch information
JeremyCloarec authored Jun 17, 2024
1 parent afd8915 commit 98165c6
Show file tree
Hide file tree
Showing 18 changed files with 177 additions and 6 deletions.
1 change: 1 addition & 0 deletions opencti-platform/opencti-front/lang/front/de.json
Original file line number Diff line number Diff line change
Expand Up @@ -1287,6 +1287,7 @@
"Instance trigger successfully created. You can click again on the bell to edit the options.": "Instanz-Trigger erfolgreich erstellt. Sie können erneut auf die Glocke klicken, um die Optionen zu bearbeiten.",
"instance_trigger": "Instanz-Trigger",
"intermediate": "intermediate",
"INTERNAL_ANALYSIS": "Analyse",
"INTERNAL_ENRICHMENT": "Anreicherung",
"INTERNAL_EXPORT_FILE": "Dateien exportieren",
"INTERNAL_IMPORT_FILE": "Dateien importieren",
Expand Down
1 change: 1 addition & 0 deletions opencti-platform/opencti-front/lang/front/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -1287,6 +1287,7 @@
"Instance trigger successfully created. You can click again on the bell to edit the options.": "Instance trigger successfully created. You can click again on the bell to edit the options.",
"instance_trigger": "Instance trigger",
"intermediate": "intermediate",
"INTERNAL_ANALYSIS": "Analysis",
"INTERNAL_ENRICHMENT": "Enrichment",
"INTERNAL_EXPORT_FILE": "Files export",
"INTERNAL_IMPORT_FILE": "Files import",
Expand Down
1 change: 1 addition & 0 deletions opencti-platform/opencti-front/lang/front/es.json
Original file line number Diff line number Diff line change
Expand Up @@ -1287,6 +1287,7 @@
"Instance trigger successfully created. You can click again on the bell to edit the options.": "Desencadenador de instancia creado correctamente. Puede hacer clic de nuevo en la campana para editar las opciones.",
"instance_trigger": "Desencadenador de instancia",
"intermediate": "Intermedia",
"INTERNAL_ANALYSIS": "Análisis",
"INTERNAL_ENRICHMENT": "Enriquecimiento",
"INTERNAL_EXPORT_FILE": "Exportación de ficheros",
"INTERNAL_IMPORT_FILE": "Importación de ficheros",
Expand Down
1 change: 1 addition & 0 deletions opencti-platform/opencti-front/lang/front/fr.json
Original file line number Diff line number Diff line change
Expand Up @@ -1287,6 +1287,7 @@
"Instance trigger successfully created. You can click again on the bell to edit the options.": "Déclencheur d’instance créé avec succès. Vous pouvez cliquer à nouveau sur la cloche pour modifier les options.",
"instance_trigger": "Déclencheur d’instance",
"intermediate": "intermédiaire",
"INTERNAL_ANALYSIS": "Analyse",
"INTERNAL_ENRICHMENT": "Enrichissement",
"INTERNAL_EXPORT_FILE": "Export de fichiers",
"INTERNAL_IMPORT_FILE": "Import de fichiers",
Expand Down
1 change: 1 addition & 0 deletions opencti-platform/opencti-front/lang/front/ja.json
Original file line number Diff line number Diff line change
Expand Up @@ -1287,6 +1287,7 @@
"Instance trigger successfully created. You can click again on the bell to edit the options.": "インスタンス トリガーが正常に作成されました。ベルをもう一度クリックしてオプションを編集できます。",
"instance_trigger": "インスタンストリガー",
"intermediate": "中級",
"INTERNAL_ANALYSIS": "分析",
"INTERNAL_ENRICHMENT": "データエンリッチメント",
"INTERNAL_EXPORT_FILE": "ファイルエクスポート",
"INTERNAL_IMPORT_FILE": "ファイルインポート",
Expand Down
1 change: 1 addition & 0 deletions opencti-platform/opencti-front/lang/front/zh.json
Original file line number Diff line number Diff line change
Expand Up @@ -1287,6 +1287,7 @@
"Instance trigger successfully created. You can click again on the bell to edit the options.": "实例触发器创建成功。您可以再次按两下铃以编辑选项。",
"instance_trigger": "实例触发器",
"intermediate": "中间",
"INTERNAL_ANALYSIS": "分析",
"INTERNAL_ENRICHMENT": "丰富",
"INTERNAL_EXPORT_FILE": "导出文件",
"INTERNAL_IMPORT_FILE": "导入文件",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1650,6 +1650,7 @@ enum ConnectorType {
EXTERNAL_IMPORT
INTERNAL_IMPORT_FILE
INTERNAL_ENRICHMENT
INTERNAL_ANALYSIS
INTERNAL_EXPORT_FILE
STREAM
}
Expand Down Expand Up @@ -7367,6 +7368,7 @@ type Query {
connectorsForWorker: [Connector]
connectorsForExport: [Connector]
connectorsForImport: [Connector]
connectorsForAnalysis: [Connector]
connectorsForNotification: [Connector]
work(id: ID!): Work
works(first: Int, after: ID, orderBy: WorksOrdering, orderMode: OrderingMode, search: String, filters: FilterGroup): WorkConnection
Expand Down Expand Up @@ -7779,6 +7781,11 @@ type KillChainPhaseEditMutations {
relationDelete(toId: StixRef!, relationship_type: String!): KillChainPhase
}

enum AnalysisContentType {
fields
file
}

type StixCoreObjectEditMutations {
delete: ID
relationAdd(input: StixRefRelationshipAddInput!): StixRefRelationship
Expand All @@ -7790,6 +7797,7 @@ type StixCoreObjectEditMutations {
importPush(file: Upload!, fileMarkings: [String], version: DateTime, noTriggerImport: Boolean): File
exportAsk(input: ExportAskInput!): [File!]
exportPush(file: Upload!): Boolean
askAnalysis(contentSource: String!, contentType: AnalysisContentType!, connectorId: ID): Work
}

input StixDomainObjectFileEditInput {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1591,6 +1591,7 @@ enum ConnectorType {
EXTERNAL_IMPORT
INTERNAL_IMPORT_FILE
INTERNAL_ENRICHMENT
INTERNAL_ANALYSIS
INTERNAL_EXPORT_FILE
STREAM
}
Expand Down Expand Up @@ -11234,6 +11235,7 @@ type Query {
connectorsForWorker: [Connector] @auth(for: [MODULES])
connectorsForExport: [Connector] @auth(for: [KNOWLEDGE])
connectorsForImport: [Connector] @auth(for: [KNOWLEDGE])
connectorsForAnalysis: [Connector] @auth(for: [KNOWLEDGE])
connectorsForNotification: [Connector] @auth(for: [SETTINGS_SETACCESSES])
work(id: ID!): Work @auth(for: [MODULES])
works(
Expand Down Expand Up @@ -12424,6 +12426,11 @@ type KillChainPhaseEditMutations {

######## STIX CORE OBJECT ENTITIES

enum AnalysisContentType {
fields
file
}

type StixCoreObjectEditMutations {
delete: ID @auth(for: [KNOWLEDGE_KNUPDATE_KNDELETE])
relationAdd(input: StixRefRelationshipAddInput!): StixRefRelationship
Expand All @@ -12435,6 +12442,7 @@ type StixCoreObjectEditMutations {
importPush(file: Upload!, fileMarkings: [String], version: DateTime, noTriggerImport: Boolean): File @auth(for: [KNOWLEDGE_KNUPLOAD])
exportAsk(input: ExportAskInput!): [File!] @auth(for: [KNOWLEDGE_KNGETEXPORT_KNASKEXPORT])
exportPush(file: Upload!): Boolean @auth(for: [CONNECTORAPI])
askAnalysis(contentSource: String!, contentType: AnalysisContentType!, connectorId: ID): Work @auth(for: [KNOWLEDGE_KNUPDATE])
}

######## STIX DOMAIN OBJECT ENTITIES
Expand Down
6 changes: 5 additions & 1 deletion opencti-platform/opencti-graphql/src/database/repository.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { filter, includes, map, pipe } from 'ramda';
import { ENTITY_TYPE_CONNECTOR } from '../schema/internalObject';
import { connectorConfig } from './rabbitmq';
import { sinceNowInMinutes } from '../utils/format';
import { CONNECTOR_INTERNAL_ENRICHMENT, CONNECTOR_INTERNAL_IMPORT_FILE, CONNECTOR_INTERNAL_NOTIFICATION } from '../schema/general';
import { CONNECTOR_INTERNAL_ANALYSIS, CONNECTOR_INTERNAL_ENRICHMENT, CONNECTOR_INTERNAL_IMPORT_FILE, CONNECTOR_INTERNAL_NOTIFICATION } from '../schema/general';
import { listEntities, storeLoadById } from './middleware-loader';
import { INTERNAL_PLAYBOOK_QUEUE, INTERNAL_SYNC_QUEUE, isEmptyField } from './utils';
import { BUILTIN_NOTIFIERS_CONNECTORS } from '../modules/notifier/notifier-statics';
Expand Down Expand Up @@ -91,6 +91,10 @@ export const connectorsForImport = async (context, user, scope, onlyAlive = fals
return connectorsFor(context, user, CONNECTOR_INTERNAL_IMPORT_FILE, scope, onlyAlive, onlyAuto, onlyContextual);
};

export const connectorsForAnalysis = async (context, user, scope = null, onlyAlive = true, onlyAuto = false, onlyContextual = false) => {
return connectorsFor(context, user, CONNECTOR_INTERNAL_ANALYSIS, scope, onlyAlive, onlyAuto, onlyContextual);
};

export const connectorsForNotification = async (context, user, scope, onlyAlive = false, onlyAuto = false, onlyContextual = false) => {
const notificationConnectors = await connectorsFor(context, user, CONNECTOR_INTERNAL_NOTIFICATION, scope, onlyAlive, onlyAuto, onlyContextual);
return [...notificationConnectors, ...Object.values(BUILTIN_NOTIFIERS_CONNECTORS)];
Expand Down
1 change: 1 addition & 0 deletions opencti-platform/opencti-graphql/src/domain/connector.js
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ export const registerConnector = async (context, user, connectorData) => {
updated_at: now(),
connector_user_id: user.id,
connector_scope: scope && scope.length > 0 ? scope.join(',') : null,
connector_type: type,
auto,
only_contextual,
playbook_compatible
Expand Down
112 changes: 110 additions & 2 deletions opencti-platform/opencti-graphql/src/domain/stixCoreObject.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { createEntity, createRelationRaw, deleteElementById, distributionEntitie
import { internalFindByIds, internalLoadById, listEntitiesPaginated, listEntitiesThroughRelationsPaginated, storeLoadById, storeLoadByIds } from '../database/middleware-loader';
import { findAll as relationFindAll } from './stixCoreRelationship';
import { delEditContext, lockResource, notify, setEditContext, storeUpdateEvent } from '../database/redis';
import { BUS_TOPICS } from '../config/conf';
import { BUS_TOPICS, logApp } from '../config/conf';
import { FunctionalError, LockTimeoutError, TYPE_LOCK_ERROR, UnsupportedError } from '../config/errors';
import { isStixCoreObject, stixCoreObjectOptions } from '../schema/stixCoreObject';
import { findById as findStatusById } from './status';
Expand All @@ -12,6 +12,7 @@ import {
ABSTRACT_STIX_CORE_OBJECT,
ABSTRACT_STIX_DOMAIN_OBJECT,
buildRefRelationKey,
CONNECTOR_INTERNAL_ANALYSIS,
CONNECTOR_INTERNAL_ENRICHMENT,
ENTITY_TYPE_CONTAINER,
INPUT_EXTERNAL_REFS,
Expand All @@ -30,7 +31,7 @@ import { createWork, workToExportFile } from './work';
import { pushToConnector } from '../database/rabbitmq';
import { now } from '../utils/format';
import { ENTITY_TYPE_CONNECTOR } from '../schema/internalObject';
import { deleteFile, storeFileConverter } from '../database/file-storage';
import { deleteFile, loadFile, storeFileConverter } from '../database/file-storage';
import { findById as documentFindById } from '../modules/internal/document/document-domain';
import { elCount, elUpdateElement } from '../database/engine';
import { generateStandardId, getInstanceIds } from '../schema/identifier';
Expand All @@ -49,6 +50,7 @@ import { ENTITY_TYPE_CONTAINER_GROUPING } from '../modules/grouping/grouping-typ
import { getEntitiesMapFromCache } from '../database/cache';
import { isUserCanAccessStoreElement, SYSTEM_USER } from '../utils/access';
import { uploadToStorage } from '../database/file-storage-helper';
import { connectorsForAnalysis } from '../database/repository';

export const findAll = async (context, user, args) => {
let types = [];
Expand Down Expand Up @@ -248,6 +250,112 @@ export const askElementEnrichmentForConnector = async (context, user, enrichedId
return work;
};

export const CONTENT_TYPE_FIELDS = 'fields';
export const CONTENT_TYPE_FILE = 'file';

export const askElementAnalysisForConnector = async (context, user, analyzedId, contentSource, contentType, connectorId) => {
logApp.debug(`[JOBS] ask analysis for content type ${contentType} and content source ${contentSource}`);

if (contentType === CONTENT_TYPE_FIELDS) return await askFieldsAnalysisForConnector(context, user, analyzedId, contentSource, connectorId);
if (contentType === CONTENT_TYPE_FILE) return await askFileAnalysisForConnector(context, user, analyzedId, contentSource, connectorId);
throw new Error(`Content type ${contentType} not recognized`);
};

export const CONTENT_SOURCE_CONTENT_MAPPING = 'content_mapping';

const askFieldsAnalysisForConnector = async (context, user, analyzedId, contentSource, connectorId) => {
let connectors = await connectorsForAnalysis(context, user);
if (connectorId) {
connectors = R.filter((n) => n.id === connectorId, connectors);
}
if (connectors.length > 0) {
// If a connectorId was specified, we use it, otherwise we get the first available connector by default. This way query can be called even without specifiying connectorId
const connector = connectors[0];
const element = await internalLoadById(context, user, analyzedId);
const work = await createWork(context, user, connector, 'Content fields analysis', element.standard_id);

if (contentSource !== CONTENT_SOURCE_CONTENT_MAPPING) {
throw new Error(`Fields content source not handled: ${contentSource}`);
}

const contentMappingFields = ['description', 'content'];
const content_fields = contentMappingFields.join(' ');

const message = {
internal: {
work_id: work.id, // Related action for history
applicant_id: null, // No specific user asking for the analysis
},
event: {
event_type: CONNECTOR_INTERNAL_ANALYSIS,
entity_id: element.standard_id,
entity_type: element.entity_type,
content_type: CONTENT_TYPE_FIELDS,
content_source: contentSource,
content_fields,
},
};

await pushToConnector(connector.internal_id, message);
await publishAnalysisAction(user, analyzedId, connector, element);
return work;
}
throw new Error('No connector found for analysis');
};

const askFileAnalysisForConnector = async (context, user, analyzedId, contentSource, connectorId) => {
const file = await loadFile(user, contentSource);

let connectors = await connectorsForAnalysis(context, user, file.metaData.mimetype);
if (connectorId) {
connectors = R.filter((n) => n.id === connectorId, connectors);
}
if (connectors.length > 0) {
const connector = connectors[0];
const element = await internalLoadById(context, user, analyzedId);
const work = await createWork(context, user, connector, 'Content file analysis', element.standard_id);

const message = {
internal: {
work_id: work.id, // Related action for history
applicant_id: null, // No specific user asking for the analysis
},
event: {
event_type: CONNECTOR_INTERNAL_ANALYSIS,
entity_id: element.standard_id,
entity_type: element.entity_type,
content_type: CONTENT_TYPE_FILE,
file_id: file.id,
file_mime: file.metaData.mimetype,
file_fetch: `/storage/get/${file.id}`, // Path to get the file
},
};

await pushToConnector(connector.internal_id, message);
await publishAnalysisAction(user, analyzedId, connector, element);
return work;
}
throw new Error('No connector found for analysis');
};

const publishAnalysisAction = async (user, analyzedId, connector, element) => {
const baseData = {
id: analyzedId,
connector_id: connector.id,
connector_name: connector.name,
entity_name: extractEntityRepresentativeName(element),
entity_type: element.entity_type
};
const contextData = completeContextDataForEntity(baseData, element);
await publishUserAction({
user,
event_access: 'extended',
event_type: 'command',
event_scope: 'analyze',
context_data: contextData,
});
};

// region stats
export const stixCoreObjectsTimeSeries = (context, user, args) => {
let types = [];
Expand Down
18 changes: 18 additions & 0 deletions opencti-platform/opencti-graphql/src/generated/graphql.ts
Original file line number Diff line number Diff line change
Expand Up @@ -304,6 +304,11 @@ export enum AdministrativeAreasOrdering {
XOpenctiWorkflowId = 'x_opencti_workflow_id'
}

export enum AnalysisContentType {
Fields = 'fields',
File = 'file'
}

export type AppDebugDistribution = {
__typename?: 'AppDebugDistribution';
label: Scalars['String']['output'];
Expand Down Expand Up @@ -3447,6 +3452,7 @@ export type ConnectorMetadata = {

export enum ConnectorType {
ExternalImport = 'EXTERNAL_IMPORT',
InternalAnalysis = 'INTERNAL_ANALYSIS',
InternalEnrichment = 'INTERNAL_ENRICHMENT',
InternalExportFile = 'INTERNAL_EXPORT_FILE',
InternalImportFile = 'INTERNAL_IMPORT_FILE',
Expand Down Expand Up @@ -17816,6 +17822,7 @@ export type Query = {
city?: Maybe<City>;
connector?: Maybe<Connector>;
connectors?: Maybe<Array<Maybe<Connector>>>;
connectorsForAnalysis?: Maybe<Array<Maybe<Connector>>>;
connectorsForExport?: Maybe<Array<Maybe<Connector>>>;
connectorsForImport?: Maybe<Array<Maybe<Connector>>>;
connectorsForNotification?: Maybe<Array<Maybe<Connector>>>;
Expand Down Expand Up @@ -22310,6 +22317,7 @@ export type StixCoreObjectEdge = {

export type StixCoreObjectEditMutations = {
__typename?: 'StixCoreObjectEditMutations';
askAnalysis?: Maybe<Work>;
askEnrichment?: Maybe<Work>;
delete?: Maybe<Scalars['ID']['output']>;
exportAsk?: Maybe<Array<File>>;
Expand All @@ -22323,6 +22331,13 @@ export type StixCoreObjectEditMutations = {
};


export type StixCoreObjectEditMutationsAskAnalysisArgs = {
connectorId?: InputMaybe<Scalars['ID']['input']>;
contentSource: Scalars['String']['input'];
contentType: AnalysisContentType;
};


export type StixCoreObjectEditMutationsAskEnrichmentArgs = {
connectorId: Scalars['ID']['input'];
};
Expand Down Expand Up @@ -28661,6 +28676,7 @@ export type ResolversTypes = ResolversObject<{
AdministrativeAreaConnection: ResolverTypeWrapper<Omit<AdministrativeAreaConnection, 'edges'> & { edges?: Maybe<Array<ResolversTypes['AdministrativeAreaEdge']>> }>;
AdministrativeAreaEdge: ResolverTypeWrapper<Omit<AdministrativeAreaEdge, 'node'> & { node: ResolversTypes['AdministrativeArea'] }>;
AdministrativeAreasOrdering: AdministrativeAreasOrdering;
AnalysisContentType: AnalysisContentType;
Any: ResolverTypeWrapper<Scalars['Any']['output']>;
AppDebugDistribution: ResolverTypeWrapper<AppDebugDistribution>;
AppDebugStatistics: ResolverTypeWrapper<AppDebugStatistics>;
Expand Down Expand Up @@ -35790,6 +35806,7 @@ export type QueryResolvers<ContextType = any, ParentType extends ResolversParent
city?: Resolver<Maybe<ResolversTypes['City']>, ParentType, ContextType, Partial<QueryCityArgs>>;
connector?: Resolver<Maybe<ResolversTypes['Connector']>, ParentType, ContextType, RequireFields<QueryConnectorArgs, 'id'>>;
connectors?: Resolver<Maybe<Array<Maybe<ResolversTypes['Connector']>>>, ParentType, ContextType>;
connectorsForAnalysis?: Resolver<Maybe<Array<Maybe<ResolversTypes['Connector']>>>, ParentType, ContextType>;
connectorsForExport?: Resolver<Maybe<Array<Maybe<ResolversTypes['Connector']>>>, ParentType, ContextType>;
connectorsForImport?: Resolver<Maybe<Array<Maybe<ResolversTypes['Connector']>>>, ParentType, ContextType>;
connectorsForNotification?: Resolver<Maybe<Array<Maybe<ResolversTypes['Connector']>>>, ParentType, ContextType>;
Expand Down Expand Up @@ -36790,6 +36807,7 @@ export type StixCoreObjectEdgeResolvers<ContextType = any, ParentType extends Re
}>;

export type StixCoreObjectEditMutationsResolvers<ContextType = any, ParentType extends ResolversParentTypes['StixCoreObjectEditMutations'] = ResolversParentTypes['StixCoreObjectEditMutations']> = ResolversObject<{
askAnalysis?: Resolver<Maybe<ResolversTypes['Work']>, ParentType, ContextType, RequireFields<StixCoreObjectEditMutationsAskAnalysisArgs, 'contentSource' | 'contentType'>>;
askEnrichment?: Resolver<Maybe<ResolversTypes['Work']>, ParentType, ContextType, RequireFields<StixCoreObjectEditMutationsAskEnrichmentArgs, 'connectorId'>>;
delete?: Resolver<Maybe<ResolversTypes['ID']>, ParentType, ContextType>;
exportAsk?: Resolver<Maybe<Array<ResolversTypes['File']>>, ParentType, ContextType, RequireFields<StixCoreObjectEditMutationsExportAskArgs, 'input'>>;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,15 @@ export interface ElementContextData {
workspace_type?: string
labels_ids?: string[]
}
export interface UserAnalyzeActionContextData extends ElementContextData {
connector_id: string
connector_name: string
}
export interface UserAnalyzeAction extends BasicUserAction {
event_type: 'command'
event_scope: 'analyze'
context_data: UserAnalyzeActionContextData
}
export interface UserEnrichActionContextData extends ElementContextData {
connector_id: string
connector_name: string
Expand Down Expand Up @@ -128,7 +137,7 @@ export interface UserLogoutAction extends BasicUserAction {
}
// endregion

export type UserAction = UserReadAction | UserFileAction | UserLoginAction | UserEnrichAction | UserImportAction |
export type UserAction = UserReadAction | UserFileAction | UserLoginAction | UserEnrichAction | UserAnalyzeAction | UserImportAction |
UserLogoutAction | UserExportAction | UserModificationAction | UserForbiddenAction | UserSearchAction;

export interface ActionListener {
Expand Down
Loading

0 comments on commit 98165c6

Please sign in to comment.