feat(osint): display execution history
This commit is contained in:
parent
1cbbc6acb1
commit
bb8295af85
9 changed files with 473 additions and 13 deletions
|
|
@ -153,7 +153,9 @@ Le socle actuel comprend notamment :
|
||||||
- création transactionnelle des relations DNS `resolves_to`, `aliases_to` et
|
- création transactionnelle des relations DNS `resolves_to`, `aliases_to` et
|
||||||
`uses_name_server` depuis l'entité interrogée ;
|
`uses_name_server` depuis l'entité interrogée ;
|
||||||
- provenance OSINT SQLite V3 conservant les arguments, sorties brutes,
|
- provenance OSINT SQLite V3 conservant les arguments, sorties brutes,
|
||||||
empreinte SHA-256 et liaisons vers les entités et relations intégrées.
|
empreinte SHA-256 et liaisons vers les entités et relations intégrées ;
|
||||||
|
- historique OSINT contextuel en lecture seule avec détail des exécutions,
|
||||||
|
sorties standard et d'erreur, et objets créés ou réutilisés.
|
||||||
|
|
||||||
Les outils actuellement présents dans le catalogue initial sont :
|
Les outils actuellement présents dans le catalogue initial sont :
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1125,6 +1125,11 @@ transaction que l'intégration DNS.
|
||||||
Les descriptions métier restent présentes pour la lisibilité, mais les
|
Les descriptions métier restent présentes pour la lisibilité, mais les
|
||||||
entités DNS demeurent des résultats OSINT à vérifier et non des faits établis.
|
entités DNS demeurent des résultats OSINT à vérifier et non des faits établis.
|
||||||
|
|
||||||
|
Le menu contextuel du workspace permet de consulter cet historique pour
|
||||||
|
l'entité ou la relation sélectionnée. La vue est strictement en lecture seule
|
||||||
|
et expose les métadonnées, les sorties brutes rendues en UTF-8, l'empreinte et
|
||||||
|
les objets liés avec leur disposition `created` ou `reused`.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
# 6. Tables de liaison
|
# 6. Tables de liaison
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,25 @@ gboolean osint_execution_dao_insert(
|
||||||
OsintExecutionRecord *osint_execution_dao_find_by_identifier(
|
OsintExecutionRecord *osint_execution_dao_find_by_identifier(
|
||||||
OsintExecutionDao *dao, const char *identifier, GError **error
|
OsintExecutionDao *dao, const char *identifier, GError **error
|
||||||
);
|
);
|
||||||
|
/**
|
||||||
|
* @brief Liste les exécutions d'une sélection, de la plus récente à l'ancienne.
|
||||||
|
*
|
||||||
|
* @return Tableau possédé de OsintExecutionRecord, ou NULL en cas d'erreur.
|
||||||
|
*/
|
||||||
|
GPtrArray *osint_execution_dao_list_by_selection(
|
||||||
|
OsintExecutionDao *dao, const char *selection_kind,
|
||||||
|
const char *selection_identifier, GError **error
|
||||||
|
);
|
||||||
|
/**
|
||||||
|
* @brief Liste les objets liés à une exécution sous forme de libellés possédés.
|
||||||
|
*
|
||||||
|
* Chaque libellé indique la nature, l'UUID et la disposition created/reused.
|
||||||
|
*
|
||||||
|
* @return Tableau possédé de chaînes, ou NULL en cas d'erreur.
|
||||||
|
*/
|
||||||
|
GPtrArray *osint_execution_dao_list_linked_objects(
|
||||||
|
OsintExecutionDao *dao, const char *execution_identifier, GError **error
|
||||||
|
);
|
||||||
/** @brief Lie une entité créée ou réutilisée à une exécution. */
|
/** @brief Lie une entité créée ou réutilisée à une exécution. */
|
||||||
gboolean osint_execution_dao_link_entity(
|
gboolean osint_execution_dao_link_entity(
|
||||||
OsintExecutionDao *dao, const char *execution_identifier,
|
OsintExecutionDao *dao, const char *execution_identifier,
|
||||||
|
|
|
||||||
32
include/views/osint_execution_history_dialog.h
Normal file
32
include/views/osint_execution_history_dialog.h
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
/******************************************************************************
|
||||||
|
* @file osint_execution_history_dialog.h
|
||||||
|
* @brief Consultation en lecture seule de l'historique OSINT.
|
||||||
|
******************************************************************************/
|
||||||
|
|
||||||
|
#ifndef LABFY_INVESTIGATION_OSINT_EXECUTION_HISTORY_DIALOG_H
|
||||||
|
#define LABFY_INVESTIGATION_OSINT_EXECUTION_HISTORY_DIALOG_H
|
||||||
|
|
||||||
|
#include <gtk/gtk.h>
|
||||||
|
|
||||||
|
G_BEGIN_DECLS
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Affiche les exécutions et leurs objets liés dans une fenêtre modale.
|
||||||
|
*
|
||||||
|
* La fonction conserve une référence sur les deux conteneurs jusqu'à la
|
||||||
|
* fermeture. records contient des OsintExecutionRecord. linked_objects associe
|
||||||
|
* chaque UUID d'exécution à un GPtrArray de chaînes.
|
||||||
|
*
|
||||||
|
* @param parent_window Fenêtre parente, ou NULL.
|
||||||
|
* @param records Historique non vide, de la plus récente à l'ancienne.
|
||||||
|
* @param linked_objects Table des objets liés par UUID d'exécution.
|
||||||
|
*/
|
||||||
|
void osint_execution_history_dialog_present(
|
||||||
|
GtkWindow *parent_window,
|
||||||
|
GPtrArray *records,
|
||||||
|
GHashTable *linked_objects
|
||||||
|
);
|
||||||
|
|
||||||
|
G_END_DECLS
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
@ -34,6 +34,7 @@
|
||||||
#include "views/evidence_import_dialog.h"
|
#include "views/evidence_import_dialog.h"
|
||||||
#include "views/create_relation_dialog.h"
|
#include "views/create_relation_dialog.h"
|
||||||
#include "views/osint_dns_review_dialog.h"
|
#include "views/osint_dns_review_dialog.h"
|
||||||
|
#include "views/osint_execution_history_dialog.h"
|
||||||
#include "dao/evidence_dao.h"
|
#include "dao/evidence_dao.h"
|
||||||
#include "dao/entity_dao.h"
|
#include "dao/entity_dao.h"
|
||||||
#include "dao/osint_execution_dao.h"
|
#include "dao/osint_execution_dao.h"
|
||||||
|
|
@ -1476,6 +1477,61 @@ static void application_start_dns_lookup(
|
||||||
tool_task_free(tool_task);
|
tool_task_free(tool_task);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @brief Charge et présente l'historique OSINT d'une sélection. */
|
||||||
|
static void application_present_osint_history(
|
||||||
|
Application *application,
|
||||||
|
const char *selection_kind,
|
||||||
|
const char *selection_identifier
|
||||||
|
)
|
||||||
|
{
|
||||||
|
Database *database = NULL;
|
||||||
|
OsintExecutionDao *dao = NULL;
|
||||||
|
GPtrArray *records = NULL;
|
||||||
|
GHashTable *linked_objects = NULL;
|
||||||
|
GError *error = NULL;
|
||||||
|
if (application == NULL || application->session == NULL ||
|
||||||
|
application->main_window == NULL) return;
|
||||||
|
database = investigation_session_get_database(application->session);
|
||||||
|
dao = osint_execution_dao_new(database, &error);
|
||||||
|
if (dao != NULL) records = osint_execution_dao_list_by_selection(
|
||||||
|
dao, selection_kind, selection_identifier, &error);
|
||||||
|
linked_objects = g_hash_table_new_full(
|
||||||
|
g_str_hash, g_str_equal, g_free, (GDestroyNotify) g_ptr_array_unref);
|
||||||
|
for (guint index = 0U; records != NULL && index < records->len; index++)
|
||||||
|
{
|
||||||
|
const OsintExecutionRecord *record = g_ptr_array_index(records, index);
|
||||||
|
GPtrArray *objects = osint_execution_dao_list_linked_objects(
|
||||||
|
dao, osint_execution_record_get_identifier(record), &error);
|
||||||
|
if (objects == NULL) break;
|
||||||
|
g_hash_table_insert(linked_objects,
|
||||||
|
g_strdup(osint_execution_record_get_identifier(record)), objects);
|
||||||
|
}
|
||||||
|
osint_execution_dao_free(dao);
|
||||||
|
if (error != NULL)
|
||||||
|
{
|
||||||
|
application_present_error(application, "Historique OSINT indisponible",
|
||||||
|
error->message);
|
||||||
|
g_clear_error(&error);
|
||||||
|
}
|
||||||
|
else if (records == NULL || records->len == 0U)
|
||||||
|
{
|
||||||
|
application_message_dialog_present(
|
||||||
|
main_window_get_window(application->main_window),
|
||||||
|
APPLICATION_MESSAGE_DIALOG_INFORMATION,
|
||||||
|
"Historique OSINT",
|
||||||
|
"Aucune exécution OSINT n'est enregistrée pour cette sélection."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
osint_execution_history_dialog_present(
|
||||||
|
main_window_get_window(application->main_window),
|
||||||
|
records, linked_objects);
|
||||||
|
}
|
||||||
|
g_clear_pointer(&records, g_ptr_array_unref);
|
||||||
|
g_hash_table_unref(linked_objects);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Traite une action OSINT relayée par la fenêtre principale.
|
* @brief Traite une action OSINT relayée par la fenêtre principale.
|
||||||
*/
|
*/
|
||||||
|
|
@ -1488,6 +1544,18 @@ static void application_on_osint_action_requested(
|
||||||
{
|
{
|
||||||
Application *application = user_data;
|
Application *application = user_data;
|
||||||
|
|
||||||
|
if (g_strcmp0(action_identifier, "history-entity") == 0 ||
|
||||||
|
g_strcmp0(action_identifier, "history-relation") == 0)
|
||||||
|
{
|
||||||
|
application_present_osint_history(
|
||||||
|
application,
|
||||||
|
g_strcmp0(action_identifier, "history-relation") == 0
|
||||||
|
? "relation" : "entity",
|
||||||
|
target_identifier
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (g_strcmp0(action_identifier, "dns-preview") == 0)
|
if (g_strcmp0(action_identifier, "dns-preview") == 0)
|
||||||
{
|
{
|
||||||
application_start_dns_lookup(
|
application_start_dns_lookup(
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,21 @@ static const char *const find_sql =
|
||||||
"selection_kind,target_value,arguments,started_at,finished_at,exit_code,"
|
"selection_kind,target_value,arguments,started_at,finished_at,exit_code,"
|
||||||
"final_state,stdout_raw,stderr_raw,output_sha256 FROM osint_executions "
|
"final_state,stdout_raw,stderr_raw,output_sha256 FROM osint_executions "
|
||||||
"WHERE id=?;";
|
"WHERE id=?;";
|
||||||
|
static const char *const list_by_selection_sql =
|
||||||
|
"SELECT id,tool_identifier,tool_version,action_identifier,selection_id,"
|
||||||
|
"selection_kind,target_value,arguments,started_at,finished_at,exit_code,"
|
||||||
|
"final_state,stdout_raw,stderr_raw,output_sha256 FROM osint_executions "
|
||||||
|
"WHERE (selection_kind=? AND selection_id=?) "
|
||||||
|
"OR (?='entity' AND id IN (SELECT execution_id "
|
||||||
|
"FROM osint_execution_entities WHERE entity_id=?)) "
|
||||||
|
"OR (?='relation' AND id IN (SELECT execution_id "
|
||||||
|
"FROM osint_execution_relations WHERE relation_id=?)) "
|
||||||
|
"ORDER BY finished_at DESC,id DESC;";
|
||||||
|
static const char *const list_linked_objects_sql =
|
||||||
|
"SELECT 'Entité',entity_id,disposition FROM osint_execution_entities "
|
||||||
|
"WHERE execution_id=? UNION ALL "
|
||||||
|
"SELECT 'Relation',relation_id,disposition FROM osint_execution_relations "
|
||||||
|
"WHERE execution_id=? ORDER BY 1,2;";
|
||||||
|
|
||||||
static void osint_execution_dao_set_error(
|
static void osint_execution_dao_set_error(
|
||||||
OsintExecutionDao *dao, GError **error, const char *context
|
OsintExecutionDao *dao, GError **error, const char *context
|
||||||
|
|
@ -101,22 +116,16 @@ cleanup:
|
||||||
return success;
|
return success;
|
||||||
}
|
}
|
||||||
|
|
||||||
OsintExecutionRecord *osint_execution_dao_find_by_identifier(
|
static OsintExecutionRecord *osint_execution_dao_read_record(
|
||||||
OsintExecutionDao *dao, const char *identifier, GError **error
|
DatabaseStatement *statement, GError **error
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
DatabaseStatement *statement = NULL;
|
|
||||||
OsintExecutionRecord *record = NULL;
|
OsintExecutionRecord *record = NULL;
|
||||||
char *values[12] = {0};
|
char *values[12] = {0};
|
||||||
GBytes *stdout_raw = NULL;
|
GBytes *stdout_raw = NULL;
|
||||||
GBytes *stderr_raw = NULL;
|
GBytes *stderr_raw = NULL;
|
||||||
int64_t exit_code = 0;
|
int64_t exit_code = 0;
|
||||||
bool exit_is_null = true;
|
bool exit_is_null = true;
|
||||||
if (dao == NULL || identifier == NULL) return NULL;
|
|
||||||
statement = database_statement_prepare(dao->database, find_sql);
|
|
||||||
if (statement == NULL || !database_statement_bind_text(statement, 1, identifier) ||
|
|
||||||
database_statement_step(statement) != DATABASE_STATEMENT_STEP_ROW)
|
|
||||||
goto cleanup;
|
|
||||||
if (!database_statement_column_text(statement, 0, &values[0]) ||
|
if (!database_statement_column_text(statement, 0, &values[0]) ||
|
||||||
!database_statement_column_text(statement, 1, &values[1]) ||
|
!database_statement_column_text(statement, 1, &values[1]) ||
|
||||||
!database_statement_column_text(statement, 2, &values[2]) ||
|
!database_statement_column_text(statement, 2, &values[2]) ||
|
||||||
|
|
@ -138,16 +147,108 @@ OsintExecutionRecord *osint_execution_dao_find_by_identifier(
|
||||||
values[6], values[7], values[8], values[9], !exit_is_null,
|
values[6], values[7], values[8], values[9], !exit_is_null,
|
||||||
(gint) exit_code, values[10], stdout_raw, stderr_raw, values[11], error);
|
(gint) exit_code, values[10], stdout_raw, stderr_raw, values[11], error);
|
||||||
cleanup:
|
cleanup:
|
||||||
if (record == NULL && error != NULL && *error == NULL)
|
|
||||||
osint_execution_dao_set_error(dao, error,
|
|
||||||
"Impossible de lire l'exécution OSINT");
|
|
||||||
for (guint index = 0; index < G_N_ELEMENTS(values); index++) g_free(values[index]);
|
for (guint index = 0; index < G_N_ELEMENTS(values); index++) g_free(values[index]);
|
||||||
g_clear_pointer(&stdout_raw, g_bytes_unref);
|
g_clear_pointer(&stdout_raw, g_bytes_unref);
|
||||||
g_clear_pointer(&stderr_raw, g_bytes_unref);
|
g_clear_pointer(&stderr_raw, g_bytes_unref);
|
||||||
|
return record;
|
||||||
|
}
|
||||||
|
|
||||||
|
OsintExecutionRecord *osint_execution_dao_find_by_identifier(
|
||||||
|
OsintExecutionDao *dao, const char *identifier, GError **error
|
||||||
|
)
|
||||||
|
{
|
||||||
|
DatabaseStatement *statement = NULL;
|
||||||
|
OsintExecutionRecord *record = NULL;
|
||||||
|
if (dao == NULL || identifier == NULL) return NULL;
|
||||||
|
statement = database_statement_prepare(dao->database, find_sql);
|
||||||
|
if (statement != NULL && database_statement_bind_text(statement, 1, identifier) &&
|
||||||
|
database_statement_step(statement) == DATABASE_STATEMENT_STEP_ROW)
|
||||||
|
record = osint_execution_dao_read_record(statement, error);
|
||||||
|
if (record == NULL && error != NULL && *error == NULL)
|
||||||
|
osint_execution_dao_set_error(dao, error,
|
||||||
|
"Impossible de lire l'exécution OSINT");
|
||||||
database_statement_finalize(statement);
|
database_statement_finalize(statement);
|
||||||
return record;
|
return record;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
GPtrArray *osint_execution_dao_list_by_selection(
|
||||||
|
OsintExecutionDao *dao, const char *selection_kind,
|
||||||
|
const char *selection_identifier, GError **error
|
||||||
|
)
|
||||||
|
{
|
||||||
|
DatabaseStatement *statement = NULL;
|
||||||
|
GPtrArray *records = NULL;
|
||||||
|
DatabaseStatementStepResult step = DATABASE_STATEMENT_STEP_ERROR;
|
||||||
|
if (dao == NULL || selection_identifier == NULL ||
|
||||||
|
(g_strcmp0(selection_kind, "entity") != 0 &&
|
||||||
|
g_strcmp0(selection_kind, "relation") != 0)) return NULL;
|
||||||
|
statement = database_statement_prepare(dao->database, list_by_selection_sql);
|
||||||
|
records = g_ptr_array_new_with_free_func(
|
||||||
|
(GDestroyNotify) osint_execution_record_free);
|
||||||
|
if (statement == NULL || records == NULL ||
|
||||||
|
!database_statement_bind_text(statement, 1, selection_kind) ||
|
||||||
|
!database_statement_bind_text(statement, 2, selection_identifier) ||
|
||||||
|
!database_statement_bind_text(statement, 3, selection_kind) ||
|
||||||
|
!database_statement_bind_text(statement, 4, selection_identifier) ||
|
||||||
|
!database_statement_bind_text(statement, 5, selection_kind) ||
|
||||||
|
!database_statement_bind_text(statement, 6, selection_identifier))
|
||||||
|
goto failure;
|
||||||
|
while ((step = database_statement_step(statement)) == DATABASE_STATEMENT_STEP_ROW)
|
||||||
|
{
|
||||||
|
OsintExecutionRecord *record = osint_execution_dao_read_record(statement, error);
|
||||||
|
if (record == NULL) goto failure;
|
||||||
|
g_ptr_array_add(records, record);
|
||||||
|
}
|
||||||
|
if (step != DATABASE_STATEMENT_STEP_DONE) goto failure;
|
||||||
|
database_statement_finalize(statement);
|
||||||
|
return records;
|
||||||
|
failure:
|
||||||
|
osint_execution_dao_set_error(dao, error,
|
||||||
|
"Impossible de lister l'historique OSINT");
|
||||||
|
database_statement_finalize(statement);
|
||||||
|
g_clear_pointer(&records, g_ptr_array_unref);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
GPtrArray *osint_execution_dao_list_linked_objects(
|
||||||
|
OsintExecutionDao *dao, const char *execution_identifier, GError **error
|
||||||
|
)
|
||||||
|
{
|
||||||
|
DatabaseStatement *statement = NULL;
|
||||||
|
GPtrArray *objects = NULL;
|
||||||
|
DatabaseStatementStepResult step = DATABASE_STATEMENT_STEP_ERROR;
|
||||||
|
if (dao == NULL || execution_identifier == NULL) return NULL;
|
||||||
|
statement = database_statement_prepare(dao->database, list_linked_objects_sql);
|
||||||
|
objects = g_ptr_array_new_with_free_func(g_free);
|
||||||
|
if (statement == NULL || objects == NULL ||
|
||||||
|
!database_statement_bind_text(statement, 1, execution_identifier) ||
|
||||||
|
!database_statement_bind_text(statement, 2, execution_identifier))
|
||||||
|
goto failure;
|
||||||
|
while ((step = database_statement_step(statement)) == DATABASE_STATEMENT_STEP_ROW)
|
||||||
|
{
|
||||||
|
char *kind = NULL; char *identifier = NULL; char *disposition = NULL;
|
||||||
|
char *description = NULL;
|
||||||
|
if (!database_statement_column_text(statement, 0, &kind) ||
|
||||||
|
!database_statement_column_text(statement, 1, &identifier) ||
|
||||||
|
!database_statement_column_text(statement, 2, &disposition))
|
||||||
|
{ g_free(kind); g_free(identifier); g_free(disposition); goto failure; }
|
||||||
|
description = g_strdup_printf("%s %s — %s", kind, identifier,
|
||||||
|
g_strcmp0(disposition, "created") == 0 ? "créée" : "réutilisée");
|
||||||
|
g_free(kind); g_free(identifier); g_free(disposition);
|
||||||
|
if (description == NULL) goto failure;
|
||||||
|
g_ptr_array_add(objects, description);
|
||||||
|
}
|
||||||
|
if (step != DATABASE_STATEMENT_STEP_DONE) goto failure;
|
||||||
|
database_statement_finalize(statement);
|
||||||
|
return objects;
|
||||||
|
failure:
|
||||||
|
osint_execution_dao_set_error(dao, error,
|
||||||
|
"Impossible de lister les objets liés à l'exécution OSINT");
|
||||||
|
database_statement_finalize(statement);
|
||||||
|
g_clear_pointer(&objects, g_ptr_array_unref);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
static gboolean osint_execution_dao_link(
|
static gboolean osint_execution_dao_link(
|
||||||
OsintExecutionDao *dao, const char *table, const char *object_column,
|
OsintExecutionDao *dao, const char *table, const char *object_column,
|
||||||
const char *execution_identifier, const char *object_identifier,
|
const char *execution_identifier, const char *object_identifier,
|
||||||
|
|
|
||||||
173
src/views/osint_execution_history_dialog.c
Normal file
173
src/views/osint_execution_history_dialog.c
Normal file
|
|
@ -0,0 +1,173 @@
|
||||||
|
/******************************************************************************
|
||||||
|
* @file osint_execution_history_dialog.c
|
||||||
|
* @brief Consultation en lecture seule de l'historique OSINT.
|
||||||
|
******************************************************************************/
|
||||||
|
|
||||||
|
#include "views/osint_execution_history_dialog.h"
|
||||||
|
|
||||||
|
#include "models/osint_execution_record.h"
|
||||||
|
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
GtkWindow *window;
|
||||||
|
GtkTextBuffer *details_buffer;
|
||||||
|
GPtrArray *records;
|
||||||
|
GHashTable *linked_objects;
|
||||||
|
} OsintExecutionHistoryDialogContext;
|
||||||
|
|
||||||
|
/** @brief Libère les données conservées par la fenêtre. */
|
||||||
|
static void osint_execution_history_dialog_context_free(gpointer data)
|
||||||
|
{
|
||||||
|
OsintExecutionHistoryDialogContext *context = data;
|
||||||
|
if (context == NULL) return;
|
||||||
|
g_clear_pointer(&context->records, g_ptr_array_unref);
|
||||||
|
g_clear_pointer(&context->linked_objects, g_hash_table_unref);
|
||||||
|
g_free(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @brief Convertit une sortie brute en texte UTF-8 affichable. */
|
||||||
|
static char *osint_execution_history_dialog_output_to_text(GBytes *bytes)
|
||||||
|
{
|
||||||
|
gconstpointer data = NULL;
|
||||||
|
gsize size = 0U;
|
||||||
|
if (bytes == NULL) return g_strdup("");
|
||||||
|
data = g_bytes_get_data(bytes, &size);
|
||||||
|
return data != NULL && size > 0U
|
||||||
|
? g_utf8_make_valid(data, (gssize) size) : g_strdup("");
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @brief Construit le détail textuel complet d'une exécution. */
|
||||||
|
static char *osint_execution_history_dialog_build_details(
|
||||||
|
const OsintExecutionRecord *record, GPtrArray *linked_objects
|
||||||
|
)
|
||||||
|
{
|
||||||
|
GBytes *stdout_raw = osint_execution_record_ref_stdout(record);
|
||||||
|
GBytes *stderr_raw = osint_execution_record_ref_stderr(record);
|
||||||
|
char *stdout_text = osint_execution_history_dialog_output_to_text(stdout_raw);
|
||||||
|
char *stderr_text = osint_execution_history_dialog_output_to_text(stderr_raw);
|
||||||
|
GString *details = g_string_new(NULL);
|
||||||
|
g_string_append_printf(details,
|
||||||
|
"Exécution : %s\nOutil : %s%s%s\nAction : %s\nÉtat : %s\n"
|
||||||
|
"Sélection : %s %s\nCible : %s\nDébut : %s\nFin : %s\n"
|
||||||
|
"Code de sortie : ",
|
||||||
|
osint_execution_record_get_identifier(record),
|
||||||
|
osint_execution_record_get_tool_identifier(record),
|
||||||
|
osint_execution_record_get_tool_version(record) != NULL ? " — " : "",
|
||||||
|
osint_execution_record_get_tool_version(record) != NULL
|
||||||
|
? osint_execution_record_get_tool_version(record) : "",
|
||||||
|
osint_execution_record_get_action_identifier(record),
|
||||||
|
osint_execution_record_get_final_state(record),
|
||||||
|
osint_execution_record_get_selection_kind(record),
|
||||||
|
osint_execution_record_get_selection_identifier(record),
|
||||||
|
osint_execution_record_get_target_value(record),
|
||||||
|
osint_execution_record_get_started_at(record),
|
||||||
|
osint_execution_record_get_finished_at(record));
|
||||||
|
if (osint_execution_record_has_exit_code(record))
|
||||||
|
g_string_append_printf(details, "%d", osint_execution_record_get_exit_code(record));
|
||||||
|
else g_string_append(details, "indisponible");
|
||||||
|
g_string_append_printf(details,
|
||||||
|
"\nArguments : %s\nSHA-256 : %s\n\nObjets liés :\n",
|
||||||
|
osint_execution_record_get_arguments(record),
|
||||||
|
osint_execution_record_get_output_sha256(record));
|
||||||
|
if (linked_objects == NULL || linked_objects->len == 0U)
|
||||||
|
g_string_append(details, "Aucun objet intégré.\n");
|
||||||
|
else for (guint index = 0U; index < linked_objects->len; index++)
|
||||||
|
g_string_append_printf(details, "- %s\n",
|
||||||
|
(const char *) g_ptr_array_index(linked_objects, index));
|
||||||
|
g_string_append_printf(details, "\nSortie standard :\n%s\n\nSortie d'erreur :\n%s",
|
||||||
|
stdout_text[0] != '\0' ? stdout_text : "(vide)",
|
||||||
|
stderr_text[0] != '\0' ? stderr_text : "(vide)");
|
||||||
|
g_bytes_unref(stdout_raw); g_bytes_unref(stderr_raw);
|
||||||
|
g_free(stdout_text); g_free(stderr_text);
|
||||||
|
return g_string_free(details, FALSE);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @brief Affiche le détail correspondant au bouton d'historique activé. */
|
||||||
|
static void osint_execution_history_dialog_on_record_clicked(
|
||||||
|
GtkButton *button, gpointer user_data
|
||||||
|
)
|
||||||
|
{
|
||||||
|
OsintExecutionHistoryDialogContext *context = user_data;
|
||||||
|
guint encoded_index = GPOINTER_TO_UINT(g_object_get_data(
|
||||||
|
G_OBJECT(button), "osint-execution-index"));
|
||||||
|
OsintExecutionRecord *record = NULL;
|
||||||
|
GPtrArray *linked_objects = NULL;
|
||||||
|
char *details = NULL;
|
||||||
|
if (context == NULL || encoded_index == 0U ||
|
||||||
|
encoded_index > context->records->len) return;
|
||||||
|
record = g_ptr_array_index(context->records, encoded_index - 1U);
|
||||||
|
linked_objects = g_hash_table_lookup(context->linked_objects,
|
||||||
|
osint_execution_record_get_identifier(record));
|
||||||
|
details = osint_execution_history_dialog_build_details(record, linked_objects);
|
||||||
|
gtk_text_buffer_set_text(context->details_buffer, details, -1);
|
||||||
|
g_free(details);
|
||||||
|
}
|
||||||
|
|
||||||
|
void osint_execution_history_dialog_present(
|
||||||
|
GtkWindow *parent_window, GPtrArray *records, GHashTable *linked_objects
|
||||||
|
)
|
||||||
|
{
|
||||||
|
OsintExecutionHistoryDialogContext *context = NULL;
|
||||||
|
GtkWidget *main_box = NULL; GtkWidget *content_box = NULL;
|
||||||
|
GtkWidget *history_scroll = NULL; GtkWidget *history_box = NULL;
|
||||||
|
GtkWidget *details_scroll = NULL; GtkWidget *details_view = NULL;
|
||||||
|
GtkWidget *close_button = NULL;
|
||||||
|
if (records == NULL || records->len == 0U || linked_objects == NULL) return;
|
||||||
|
context = g_new0(OsintExecutionHistoryDialogContext, 1);
|
||||||
|
context->window = GTK_WINDOW(gtk_window_new());
|
||||||
|
context->records = g_ptr_array_ref(records);
|
||||||
|
context->linked_objects = g_hash_table_ref(linked_objects);
|
||||||
|
gtk_window_set_title(context->window, "Historique OSINT");
|
||||||
|
gtk_window_set_default_size(context->window, 980, 650);
|
||||||
|
gtk_window_set_modal(context->window, TRUE);
|
||||||
|
gtk_window_set_destroy_with_parent(context->window, TRUE);
|
||||||
|
if (parent_window != NULL) gtk_window_set_transient_for(context->window, parent_window);
|
||||||
|
g_object_set_data_full(G_OBJECT(context->window), "osint-history-context",
|
||||||
|
context, osint_execution_history_dialog_context_free);
|
||||||
|
main_box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 12);
|
||||||
|
gtk_widget_set_margin_start(main_box, 16); gtk_widget_set_margin_end(main_box, 16);
|
||||||
|
gtk_widget_set_margin_top(main_box, 16); gtk_widget_set_margin_bottom(main_box, 16);
|
||||||
|
content_box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 12);
|
||||||
|
gtk_widget_set_vexpand(content_box, TRUE);
|
||||||
|
history_box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 6);
|
||||||
|
history_scroll = gtk_scrolled_window_new();
|
||||||
|
gtk_widget_set_size_request(history_scroll, 300, -1);
|
||||||
|
gtk_scrolled_window_set_child(GTK_SCROLLED_WINDOW(history_scroll), history_box);
|
||||||
|
for (guint index = 0U; index < records->len; index++)
|
||||||
|
{
|
||||||
|
OsintExecutionRecord *record = g_ptr_array_index(records, index);
|
||||||
|
char *label = g_strdup_printf("%s\n%s — %s",
|
||||||
|
osint_execution_record_get_finished_at(record),
|
||||||
|
osint_execution_record_get_tool_identifier(record),
|
||||||
|
osint_execution_record_get_final_state(record));
|
||||||
|
GtkWidget *button = gtk_button_new_with_label(label);
|
||||||
|
gtk_widget_set_halign(button, GTK_ALIGN_FILL);
|
||||||
|
g_object_set_data(G_OBJECT(button), "osint-execution-index",
|
||||||
|
GUINT_TO_POINTER(index + 1U));
|
||||||
|
g_signal_connect(button, "clicked",
|
||||||
|
G_CALLBACK(osint_execution_history_dialog_on_record_clicked), context);
|
||||||
|
gtk_box_append(GTK_BOX(history_box), button);
|
||||||
|
g_free(label);
|
||||||
|
}
|
||||||
|
details_view = gtk_text_view_new();
|
||||||
|
context->details_buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(details_view));
|
||||||
|
gtk_text_view_set_editable(GTK_TEXT_VIEW(details_view), FALSE);
|
||||||
|
gtk_text_view_set_cursor_visible(GTK_TEXT_VIEW(details_view), TRUE);
|
||||||
|
gtk_text_view_set_monospace(GTK_TEXT_VIEW(details_view), TRUE);
|
||||||
|
gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(details_view), GTK_WRAP_WORD_CHAR);
|
||||||
|
details_scroll = gtk_scrolled_window_new();
|
||||||
|
gtk_widget_set_hexpand(details_scroll, TRUE);
|
||||||
|
gtk_scrolled_window_set_child(GTK_SCROLLED_WINDOW(details_scroll), details_view);
|
||||||
|
gtk_box_append(GTK_BOX(content_box), history_scroll);
|
||||||
|
gtk_box_append(GTK_BOX(content_box), details_scroll);
|
||||||
|
close_button = gtk_button_new_with_label("Fermer");
|
||||||
|
gtk_widget_set_halign(close_button, GTK_ALIGN_END);
|
||||||
|
g_signal_connect_swapped(close_button, "clicked",
|
||||||
|
G_CALLBACK(gtk_window_destroy), context->window);
|
||||||
|
gtk_box_append(GTK_BOX(main_box), content_box);
|
||||||
|
gtk_box_append(GTK_BOX(main_box), close_button);
|
||||||
|
gtk_window_set_child(context->window, main_box);
|
||||||
|
osint_execution_history_dialog_on_record_clicked(
|
||||||
|
GTK_BUTTON(gtk_widget_get_first_child(history_box)), context);
|
||||||
|
gtk_window_present(context->window);
|
||||||
|
}
|
||||||
|
|
@ -183,7 +183,9 @@ static void workspace_on_osint_action_clicked(
|
||||||
|
|
||||||
gtk_label_set_text(
|
gtk_label_set_text(
|
||||||
GTK_LABEL(workspace->osint_tools_status_label),
|
GTK_LABEL(workspace->osint_tools_status_label),
|
||||||
"Exécution lancée ; suivez sa progression dans les tâches."
|
g_str_has_prefix(action_identifier, "history-")
|
||||||
|
? "Ouverture de l'historique enregistré."
|
||||||
|
: "Exécution lancée ; suivez sa progression dans les tâches."
|
||||||
);
|
);
|
||||||
|
|
||||||
gtk_popover_popdown(
|
gtk_popover_popdown(
|
||||||
|
|
@ -290,6 +292,31 @@ static void workspace_update_osint_tools_menu(
|
||||||
context
|
context
|
||||||
);
|
);
|
||||||
|
|
||||||
|
{
|
||||||
|
GtkWidget *history_button = gtk_button_new_with_label(
|
||||||
|
"Historique OSINT"
|
||||||
|
);
|
||||||
|
const char *history_action =
|
||||||
|
osint_selection_context_get_kind(context) ==
|
||||||
|
OSINT_SELECTION_CONTEXT_KIND_RELATION
|
||||||
|
? "history-relation" : "history-entity";
|
||||||
|
gtk_widget_set_tooltip_text(
|
||||||
|
history_button,
|
||||||
|
"Consulter les exécutions OSINT enregistrées pour cette sélection"
|
||||||
|
);
|
||||||
|
g_object_set_data_full(
|
||||||
|
G_OBJECT(history_button), "osint-action-identifier",
|
||||||
|
g_strdup(history_action), g_free
|
||||||
|
);
|
||||||
|
g_signal_connect(
|
||||||
|
history_button, "clicked",
|
||||||
|
G_CALLBACK(workspace_on_osint_action_clicked), workspace
|
||||||
|
);
|
||||||
|
gtk_box_append(
|
||||||
|
GTK_BOX(workspace->osint_tools_actions_box), history_button
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
for (guint action_index = 0;
|
for (guint action_index = 0;
|
||||||
compatible_actions != NULL &&
|
compatible_actions != NULL &&
|
||||||
action_index < compatible_actions->len;
|
action_index < compatible_actions->len;
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@
|
||||||
|
|
||||||
#include "dao/osint_execution_dao.h"
|
#include "dao/osint_execution_dao.h"
|
||||||
#include "database/database.h"
|
#include "database/database.h"
|
||||||
|
#include "database/statement.h"
|
||||||
|
|
||||||
#include <glib.h>
|
#include <glib.h>
|
||||||
#include <glib/gstdio.h>
|
#include <glib/gstdio.h>
|
||||||
|
|
@ -22,6 +23,9 @@ static void test_insert_and_read_raw_execution(void)
|
||||||
GBytes *stdout_raw = g_bytes_new_static(raw_data, sizeof(raw_data));
|
GBytes *stdout_raw = g_bytes_new_static(raw_data, sizeof(raw_data));
|
||||||
GBytes *stderr_raw = g_bytes_new_static("", 0U);
|
GBytes *stderr_raw = g_bytes_new_static("", 0U);
|
||||||
GBytes *loaded_stdout = NULL;
|
GBytes *loaded_stdout = NULL;
|
||||||
|
GPtrArray *history = NULL;
|
||||||
|
GPtrArray *linked_objects = NULL;
|
||||||
|
DatabaseStatement *statement = NULL;
|
||||||
|
|
||||||
g_assert_true(database_initialize(path, "OSINT", directory));
|
g_assert_true(database_initialize(path, "OSINT", directory));
|
||||||
database = database_open(path);
|
database = database_open(path);
|
||||||
|
|
@ -37,6 +41,18 @@ static void test_insert_and_read_raw_execution(void)
|
||||||
);
|
);
|
||||||
g_assert_nonnull(record);
|
g_assert_nonnull(record);
|
||||||
g_assert_true(osint_execution_dao_insert(dao, record, &error));
|
g_assert_true(osint_execution_dao_insert(dao, record, &error));
|
||||||
|
statement = database_statement_prepare(database,
|
||||||
|
"INSERT INTO entites(id,type_id,valeur,confiance,created_at,updated_at,status) "
|
||||||
|
"VALUES('bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb',"
|
||||||
|
"(SELECT id FROM types_entite LIMIT 1),'linked.example',50,"
|
||||||
|
"'2026-01-01T00:00:00Z','2026-01-01T00:00:00Z','active');");
|
||||||
|
g_assert_nonnull(statement);
|
||||||
|
g_assert_cmpint(database_statement_step(statement), ==,
|
||||||
|
DATABASE_STATEMENT_STEP_DONE);
|
||||||
|
database_statement_finalize(statement);
|
||||||
|
g_assert_true(osint_execution_dao_link_entity(
|
||||||
|
dao, "eeeeeeee-eeee-4eee-8eee-eeeeeeeeeeee",
|
||||||
|
"bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb", "created", &error));
|
||||||
loaded = osint_execution_dao_find_by_identifier(
|
loaded = osint_execution_dao_find_by_identifier(
|
||||||
dao, "eeeeeeee-eeee-4eee-8eee-eeeeeeeeeeee", &error
|
dao, "eeeeeeee-eeee-4eee-8eee-eeeeeeeeeeee", &error
|
||||||
);
|
);
|
||||||
|
|
@ -47,7 +63,24 @@ static void test_insert_and_read_raw_execution(void)
|
||||||
g_assert_true(osint_execution_record_has_exit_code(loaded));
|
g_assert_true(osint_execution_record_has_exit_code(loaded));
|
||||||
loaded_stdout = osint_execution_record_ref_stdout(loaded);
|
loaded_stdout = osint_execution_record_ref_stdout(loaded);
|
||||||
g_assert_true(g_bytes_equal(stdout_raw, loaded_stdout));
|
g_assert_true(g_bytes_equal(stdout_raw, loaded_stdout));
|
||||||
|
history = osint_execution_dao_list_by_selection(
|
||||||
|
dao, "entity", "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa", &error);
|
||||||
|
g_assert_nonnull(history);
|
||||||
|
g_assert_cmpuint(history->len, ==, 1U);
|
||||||
|
g_ptr_array_unref(history);
|
||||||
|
history = osint_execution_dao_list_by_selection(
|
||||||
|
dao, "entity", "bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb", &error);
|
||||||
|
g_assert_nonnull(history);
|
||||||
|
g_assert_cmpuint(history->len, ==, 1U);
|
||||||
|
linked_objects = osint_execution_dao_list_linked_objects(
|
||||||
|
dao, "eeeeeeee-eeee-4eee-8eee-eeeeeeeeeeee", &error);
|
||||||
|
g_assert_nonnull(linked_objects);
|
||||||
|
g_assert_cmpuint(linked_objects->len, ==, 1U);
|
||||||
|
g_assert_nonnull(g_strstr_len(
|
||||||
|
g_ptr_array_index(linked_objects, 0U), -1, "créée"));
|
||||||
|
|
||||||
|
g_ptr_array_unref(linked_objects);
|
||||||
|
g_ptr_array_unref(history);
|
||||||
g_bytes_unref(loaded_stdout);
|
g_bytes_unref(loaded_stdout);
|
||||||
osint_execution_record_free(loaded);
|
osint_execution_record_free(loaded);
|
||||||
osint_execution_record_free(record);
|
osint_execution_record_free(record);
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue