feat(osint): add selection context

This commit is contained in:
grayTerminal-sh 2026-07-22 11:20:06 +02:00
parent 13315846e3
commit bc70257e0a
6 changed files with 390 additions and 15 deletions

View file

@ -113,6 +113,7 @@ TEST_INVESTIGATION_GRAPH_MODEL := tests/test_investigation_graph_model
TEST_INVESTIGATION_GRAPH_LOADER := tests/test_investigation_graph_loader TEST_INVESTIGATION_GRAPH_LOADER := tests/test_investigation_graph_loader
TEST_INVESTIGATION_GRAPH_LOAD_TASK := tests/test_investigation_graph_load_task TEST_INVESTIGATION_GRAPH_LOAD_TASK := tests/test_investigation_graph_load_task
TEST_GRAPH_NODE_POSITION_DAO := tests/test_graph_node_position_dao TEST_GRAPH_NODE_POSITION_DAO := tests/test_graph_node_position_dao
TEST_OSINT_SELECTION_CONTEXT := tests/test_osint_selection_context
all: $(TARGET) all: $(TARGET)
@ -528,6 +529,13 @@ $(TEST_GRAPH_NODE_POSITION_DAO): \
$(CC) $(GRAPH_NODE_POSITION_DAO_TEST_CFLAGS) $^ -o $@ \ $(CC) $(GRAPH_NODE_POSITION_DAO_TEST_CFLAGS) $^ -o $@ \
$(TEST_LDFLAGS) -lsqlite3 $(TEST_LDFLAGS) -lsqlite3
$(TEST_OSINT_SELECTION_CONTEXT): \
tests/test_osint_selection_context.c \
src/models/osint_selection_context.c \
src/models/entity_record.c \
src/models/relation_record.c
$(CC) $(TEST_CFLAGS) $^ -o $@ $(TEST_LDFLAGS)
$(TEST_INVESTIGATION_GRAPH_LOAD_TASK): \ $(TEST_INVESTIGATION_GRAPH_LOAD_TASK): \
tests/test_investigation_graph_load_task.c \ tests/test_investigation_graph_load_task.c \
src/core/investigation_graph_load_task.c \ src/core/investigation_graph_load_task.c \
@ -595,7 +603,8 @@ test: \
$(TEST_INVESTIGATION_GRAPH_MODEL) \ $(TEST_INVESTIGATION_GRAPH_MODEL) \
$(TEST_INVESTIGATION_GRAPH_LOADER) \ $(TEST_INVESTIGATION_GRAPH_LOADER) \
$(TEST_INVESTIGATION_GRAPH_LOAD_TASK) \ $(TEST_INVESTIGATION_GRAPH_LOAD_TASK) \
$(TEST_GRAPH_NODE_POSITION_DAO) $(TEST_GRAPH_NODE_POSITION_DAO) \
$(TEST_OSINT_SELECTION_CONTEXT)
@echo "Exécution des tests..." @echo "Exécution des tests..."
@./$(TEST_NODE) @./$(TEST_NODE)
@./$(TEST_TREE_MODEL) @./$(TEST_TREE_MODEL)
@ -643,6 +652,7 @@ test: \
@$(TEST_INVESTIGATION_GRAPH_LOADER) @$(TEST_INVESTIGATION_GRAPH_LOADER)
@$(TEST_INVESTIGATION_GRAPH_LOAD_TASK) @$(TEST_INVESTIGATION_GRAPH_LOAD_TASK)
@$(TEST_GRAPH_NODE_POSITION_DAO) @$(TEST_GRAPH_NODE_POSITION_DAO)
@$(TEST_OSINT_SELECTION_CONTEXT)
@echo "Tous les tests sont valides." @echo "Tous les tests sont valides."
%.o: %.c %.o: %.c
@ -696,7 +706,8 @@ clean:
$(TEST_INVESTIGATION_GRAPH_MODEL) \ $(TEST_INVESTIGATION_GRAPH_MODEL) \
$(TEST_INVESTIGATION_GRAPH_LOADER) \ $(TEST_INVESTIGATION_GRAPH_LOADER) \
$(TEST_INVESTIGATION_GRAPH_LOAD_TASK) \ $(TEST_INVESTIGATION_GRAPH_LOAD_TASK) \
$(TEST_GRAPH_NODE_POSITION_DAO) $(TEST_GRAPH_NODE_POSITION_DAO) \
$(TEST_OSINT_SELECTION_CONTEXT)
-include $(DEP) -include $(DEP)

View file

@ -141,7 +141,8 @@ Le socle actuel comprend notamment :
- exécution doutils en tâche de fond ; - exécution doutils en tâche de fond ;
- catalogue initial doutils ; - catalogue initial doutils ;
- détection de présence et de version ; - détection de présence et de version ;
- menu OSINT contextuel préparé pour les entités et relations du graphe. - menu OSINT contextuel préparé pour les entités et relations du graphe ;
- contexte de sélection OSINT indépendant de GTK et validé par des tests.
Les outils actuellement présents dans le catalogue initial sont : Les outils actuellement présents dans le catalogue initial sont :

View file

@ -0,0 +1,104 @@
/******************************************************************************
* @file osint_selection_context.h
* @brief Contexte métier transmis aux futurs adaptateurs OSINT.
******************************************************************************/
#ifndef LABFY_INVESTIGATION_OSINT_SELECTION_CONTEXT_H
#define LABFY_INVESTIGATION_OSINT_SELECTION_CONTEXT_H
#include "models/entity_record.h"
#include "models/relation_record.h"
#include <gio/gio.h>
G_BEGIN_DECLS
/**
* @brief Nature de la cible sélectionnée pour un outil OSINT.
*/
typedef enum
{
OSINT_SELECTION_CONTEXT_KIND_UNKNOWN,
OSINT_SELECTION_CONTEXT_KIND_ENTITY,
OSINT_SELECTION_CONTEXT_KIND_RELATION
} OsintSelectionContextKind;
/**
* @brief Contexte OSINT opaque et indépendant de GTK.
*/
typedef struct OsintSelectionContext OsintSelectionContext;
/**
* @brief Crée un contexte depuis une entité.
*
* Toutes les données utiles sont copiées.
*
* @param entity_record Entité empruntée.
* @param error Emplacement facultatif recevant une erreur.
*
* @return Nouveau contexte, ou NULL en cas d'échec.
*/
OsintSelectionContext *osint_selection_context_new_entity(
const EntityRecord *entity_record,
GError **error
);
/**
* @brief Crée un contexte depuis une relation et ses extrémités.
*
* @param relation_record Relation empruntée.
* @param source_entity Entité source empruntée.
* @param target_entity Entité cible empruntée.
* @param error Emplacement facultatif recevant une erreur.
*
* @return Nouveau contexte, ou NULL en cas d'échec.
*/
OsintSelectionContext *osint_selection_context_new_relation(
const RelationRecord *relation_record,
const EntityRecord *source_entity,
const EntityRecord *target_entity,
GError **error
);
/**
* @brief Libère un contexte OSINT.
*
* Cette fonction accepte NULL.
*
* @param context Contexte à libérer.
*/
void osint_selection_context_free(OsintSelectionContext *context);
/** @brief Retourne la nature du contexte. */
OsintSelectionContextKind osint_selection_context_get_kind(
const OsintSelectionContext *context
);
/** @brief Retourne l'UUID copié de la cible. */
const char *osint_selection_context_get_identifier(
const OsintSelectionContext *context
);
/** @brief Retourne le type métier copié de la cible. */
const char *osint_selection_context_get_type(
const OsintSelectionContext *context
);
/** @brief Retourne la valeur principale copiée de la cible. */
const char *osint_selection_context_get_value(
const OsintSelectionContext *context
);
/** @brief Retourne la valeur source copiée d'une relation, ou NULL. */
const char *osint_selection_context_get_source_value(
const OsintSelectionContext *context
);
/** @brief Retourne la valeur cible copiée d'une relation, ou NULL. */
const char *osint_selection_context_get_target_value(
const OsintSelectionContext *context
);
G_END_DECLS
#endif

View file

@ -0,0 +1,152 @@
/******************************************************************************
* @file osint_selection_context.c
* @brief Implémentation du contexte métier OSINT.
******************************************************************************/
#include "models/osint_selection_context.h"
#include <gio/gio.h>
struct OsintSelectionContext
{
OsintSelectionContextKind kind;
char *identifier;
char *type;
char *value;
char *source_value;
char *target_value;
};
/**
* @brief Alloue et copie les champs communs d'un contexte.
*/
static OsintSelectionContext *osint_selection_context_new(
OsintSelectionContextKind kind,
const char *identifier,
const char *type,
const char *value,
GError **error
)
{
OsintSelectionContext *context = NULL;
if ((error != NULL && *error != NULL) ||
(kind != OSINT_SELECTION_CONTEXT_KIND_ENTITY &&
kind != OSINT_SELECTION_CONTEXT_KIND_RELATION) ||
identifier == NULL || !g_uuid_string_is_valid(identifier) ||
type == NULL || type[0] == '\0' ||
value == NULL || value[0] == '\0')
{
g_set_error_literal(
error,
G_IO_ERROR,
G_IO_ERROR_INVALID_ARGUMENT,
"Le contexte de sélection OSINT est invalide."
);
return NULL;
}
context = g_try_new0(OsintSelectionContext, 1);
if (context != NULL)
{
context->kind = kind;
context->identifier = g_strdup(identifier);
context->type = g_strdup(type);
context->value = g_strdup(value);
}
if (context == NULL || context->identifier == NULL ||
context->type == NULL || context->value == NULL)
{
osint_selection_context_free(context);
g_set_error_literal(
error,
G_IO_ERROR,
G_IO_ERROR_NO_SPACE,
"Impossible d'allouer le contexte de sélection OSINT."
);
return NULL;
}
return context;
}
OsintSelectionContext *osint_selection_context_new_entity(
const EntityRecord *entity_record,
GError **error
)
{
return osint_selection_context_new(
OSINT_SELECTION_CONTEXT_KIND_ENTITY,
entity_record_get_identifier(entity_record),
entity_record_get_type_identifier(entity_record),
entity_record_get_value(entity_record),
error
);
}
OsintSelectionContext *osint_selection_context_new_relation(
const RelationRecord *relation_record,
const EntityRecord *source_entity,
const EntityRecord *target_entity,
GError **error
)
{
const char *label = relation_record_get_label(relation_record);
OsintSelectionContext *context = osint_selection_context_new(
OSINT_SELECTION_CONTEXT_KIND_RELATION,
relation_record_get_identifier(relation_record),
relation_record_get_relation_type(relation_record),
label != NULL && label[0] != '\0'
? label
: relation_record_get_relation_type(relation_record),
error
);
if (context == NULL || source_entity == NULL || target_entity == NULL)
{
osint_selection_context_free(context);
if (context != NULL)
{
g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT,
"Les extrémités de la relation OSINT sont invalides.");
}
return NULL;
}
context->source_value = g_strdup(entity_record_get_value(source_entity));
context->target_value = g_strdup(entity_record_get_value(target_entity));
if (context->source_value == NULL || context->target_value == NULL)
{
osint_selection_context_free(context);
g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_NO_SPACE,
"Impossible de copier les extrémités de la relation OSINT.");
return NULL;
}
return context;
}
void osint_selection_context_free(OsintSelectionContext *context)
{
if (context == NULL) return;
g_free(context->target_value);
g_free(context->source_value);
g_free(context->value);
g_free(context->type);
g_free(context->identifier);
g_free(context);
}
OsintSelectionContextKind osint_selection_context_get_kind(const OsintSelectionContext *context)
{ return context != NULL ? context->kind : OSINT_SELECTION_CONTEXT_KIND_UNKNOWN; }
const char *osint_selection_context_get_identifier(const OsintSelectionContext *context)
{ return context != NULL ? context->identifier : NULL; }
const char *osint_selection_context_get_type(const OsintSelectionContext *context)
{ return context != NULL ? context->type : NULL; }
const char *osint_selection_context_get_value(const OsintSelectionContext *context)
{ return context != NULL ? context->value : NULL; }
const char *osint_selection_context_get_source_value(const OsintSelectionContext *context)
{ return context != NULL ? context->source_value : NULL; }
const char *osint_selection_context_get_target_value(const OsintSelectionContext *context)
{ return context != NULL ? context->target_value : NULL; }

View file

@ -7,6 +7,7 @@
#include "models/entity_record.h" #include "models/entity_record.h"
#include "models/investigation_graph_model.h" #include "models/investigation_graph_model.h"
#include "models/osint_selection_context.h"
#include "models/relation_record.h" #include "models/relation_record.h"
#include "widgets/entity_details_panel.h" #include "widgets/entity_details_panel.h"
#include "widgets/investigation_graph_view.h" #include "widgets/investigation_graph_view.h"
@ -118,6 +119,8 @@ struct Workspace
const InvestigationGraphModel *graph_model; const InvestigationGraphModel *graph_model;
const InvestigationGraphLayout *graph_layout; const InvestigationGraphLayout *graph_layout;
OsintSelectionContext *osint_selection_context;
WorkspaceGraphState graph_state; WorkspaceGraphState graph_state;
}; };
@ -129,8 +132,7 @@ struct Workspace
*/ */
static void workspace_update_osint_tools_menu( static void workspace_update_osint_tools_menu(
Workspace *workspace, Workspace *workspace,
const EntityRecord *entity_record, const OsintSelectionContext *context
const RelationRecord *relation_record
) )
{ {
const char *selection_kind = const char *selection_kind =
@ -150,25 +152,23 @@ static void workspace_update_osint_tools_menu(
return; return;
} }
if (entity_record != NULL) if (osint_selection_context_get_kind(context) ==
OSINT_SELECTION_CONTEXT_KIND_ENTITY)
{ {
selection_kind = selection_kind =
"Entité"; "Entité";
selection_identifier = selection_identifier =
entity_record_get_identifier( osint_selection_context_get_identifier(context);
entity_record
);
} }
else if (relation_record != NULL) else if (osint_selection_context_get_kind(context) ==
OSINT_SELECTION_CONTEXT_KIND_RELATION)
{ {
selection_kind = selection_kind =
"Relation"; "Relation";
selection_identifier = selection_identifier =
relation_record_get_identifier( osint_selection_context_get_identifier(context);
relation_record
);
} }
gtk_widget_set_sensitive( gtk_widget_set_sensitive(
@ -438,6 +438,17 @@ static void workspace_on_graph_node_selected(
return; return;
} }
g_clear_pointer(
&workspace->osint_selection_context,
osint_selection_context_free
);
if (entity_record != NULL)
{
workspace->osint_selection_context =
osint_selection_context_new_entity(entity_record, NULL);
}
if (workspace->entity_details_panel != NULL) if (workspace->entity_details_panel != NULL)
{ {
entity_details_panel_set_entity( entity_details_panel_set_entity(
@ -468,6 +479,14 @@ static void workspace_on_graph_node_selected(
workspace->graph_model, workspace->graph_model,
relation_record_get_target_entity_identifier(relation_record) relation_record_get_target_entity_identifier(relation_record)
); );
workspace->osint_selection_context =
osint_selection_context_new_relation(
relation_record,
source_entity,
target_entity,
NULL
);
const char *label = relation_record_get_label(relation_record); const char *label = relation_record_get_label(relation_record);
char *summary = g_strdup_printf( char *summary = g_strdup_printf(
"%s → %s\n\nType : %s\nConfiance : %d %%\nUUID : %s", "%s → %s\n\nType : %s\nConfiance : %d %%\nUUID : %s",
@ -491,8 +510,7 @@ static void workspace_on_graph_node_selected(
workspace_update_osint_tools_menu( workspace_update_osint_tools_menu(
workspace, workspace,
entity_record, workspace->osint_selection_context
relation_record
); );
} }
@ -2614,6 +2632,11 @@ void workspace_free(Workspace *workspace)
return; return;
} }
g_clear_pointer(
&workspace->osint_selection_context,
osint_selection_context_free
);
investigation_graph_view_set_selection_callback( investigation_graph_view_set_selection_callback(
workspace->graph_view, workspace->graph_view,
NULL, NULL,

View file

@ -0,0 +1,84 @@
#include "models/osint_selection_context.h"
#include <glib.h>
static EntityRecord *create_entity(const char *identifier, const char *value)
{
return entity_record_new(
identifier, "domain", value, NULL, NULL, 80,
"2026-01-01T00:00:00Z", "2026-01-01T00:00:00Z",
ENTITY_STATUS_ACTIVE, NULL
);
}
static void test_entity_context(void)
{
EntityRecord *entity = create_entity(
"11111111-1111-4111-8111-111111111111", "example.org"
);
OsintSelectionContext *context =
osint_selection_context_new_entity(entity, NULL);
g_assert_nonnull(context);
g_assert_cmpint(osint_selection_context_get_kind(context), ==,
OSINT_SELECTION_CONTEXT_KIND_ENTITY);
g_assert_cmpstr(osint_selection_context_get_type(context), ==, "domain");
g_assert_cmpstr(osint_selection_context_get_value(context), ==, "example.org");
g_assert_null(osint_selection_context_get_source_value(context));
entity_record_free(entity);
g_assert_cmpstr(osint_selection_context_get_value(context), ==, "example.org");
osint_selection_context_free(context);
}
static void test_relation_context(void)
{
EntityRecord *source = create_entity(
"11111111-1111-4111-8111-111111111111", "source.example"
);
EntityRecord *target = create_entity(
"22222222-2222-4222-8222-222222222222", "target.example"
);
RelationRecord *relation = relation_record_new(
"33333333-3333-4333-8333-333333333333",
entity_record_get_identifier(source), entity_record_get_identifier(target),
"redirects_to", "Redirection", NULL, 90,
"2026-01-01T00:00:00Z", "2026-01-01T00:00:00Z",
RELATION_STATUS_ACTIVE, NULL
);
OsintSelectionContext *context = osint_selection_context_new_relation(
relation, source, target, NULL
);
g_assert_nonnull(context);
g_assert_cmpint(osint_selection_context_get_kind(context), ==,
OSINT_SELECTION_CONTEXT_KIND_RELATION);
g_assert_cmpstr(osint_selection_context_get_value(context), ==, "Redirection");
g_assert_cmpstr(osint_selection_context_get_source_value(context), ==,
"source.example");
g_assert_cmpstr(osint_selection_context_get_target_value(context), ==,
"target.example");
osint_selection_context_free(context);
relation_record_free(relation);
entity_record_free(target);
entity_record_free(source);
}
static void test_invalid_context(void)
{
GError *error = NULL;
g_assert_null(osint_selection_context_new_entity(NULL, &error));
g_assert_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT);
g_clear_error(&error);
osint_selection_context_free(NULL);
}
int main(int argc, char **argv)
{
g_test_init(&argc, &argv, NULL);
g_test_add_func("/osint-selection-context/entity", test_entity_context);
g_test_add_func("/osint-selection-context/relation", test_relation_context);
g_test_add_func("/osint-selection-context/invalid", test_invalid_context);
return g_test_run();
}