feat(osint): Lier et exploiter les extractions dans l’enquête

This commit is contained in:
grayTerminal-sh 2026-07-23 12:54:23 +02:00
parent 6cfd26c2bd
commit 8adfc3f106
14 changed files with 296 additions and 58 deletions

View file

@ -1,53 +0,0 @@
# Ticket 104 — Lier et exploiter les extractions dans lenquête
## Objectif
Rendre les fichiers produits par les outils danalyse (OSINT, ExifTool,
analyse EML, OCR, récupération PDF, etc.) traçables et directement
exploitables depuis linterface de lenquête.
## Besoins
- Enregistrer chaque extraction comme une preuve traitée identifiable.
- Conserver la référence de lentité ou de la preuve à lorigine de
lextraction.
- Afficher les extractions liées dans la fiche de lobjet source.
- Permettre la prévisualisation des fichiers texte avec défilement.
- Afficher les résultats texte dans les fiches concernées sans ouvrir le
gestionnaire de fichiers.
- Depuis longlet **Dossier**, permettre de glisser-déposer une extraction
sur le graphe afin de proposer sa création ou son rattachement à une
entité.
- Préserver lintégrité et le chemin relatif des fichiers produits dans
`02_Preuves_Traitees/Extractions`.
## Contraintes dimplémentation
- Utiliser les associations persistées preuveentité existantes, sans
dupliquer les fichiers ni les résultats.
- Refuser les chemins sortant du dossier de lenquête.
- Ne jamais exécuter automatiquement le contenu dune extraction déposée
dans le graphe.
- Conserver les commentaires Doxygen des nouvelles fonctions publiques.
## Critères dacceptation
1. Une extraction terminée apparaît comme preuve traitée et indique sa
source.
2. La fiche de la source liste les extractions associées.
3. Un fichier `.txt` peut être lu dans une zone défilante depuis sa fiche et
depuis longlet Dossier.
4. Un glisser-déposer sur le graphe ouvre une action explicite de création ou
dassociation, sans modification silencieuse.
5. Les associations et chemins sont conservés après fermeture et réouverture
de lenquête.
6. Les tests unitaires couvrent lassociation, les chemins invalides et le
transfert Dossier → graphe.
## Vérification
- `make -j2`
- `make test`
- Vérification manuelle avec une extraction OSINT et un fichier texte dans
`02_Preuves_Traitees/Extractions`.

45
TICKET-105.md Normal file
View file

@ -0,0 +1,45 @@
# Ticket 105 — Glisser-déposer des extractions vers le graphe
## Objectif
Permettre de prendre une extraction depuis `Dossier → Extractions` et de la
déposer dans le graphe afin de créer ou compléter les liens de lenquête.
## Fonctionnement attendu
1. Une extraction sélectionnée dans la Sidebar peut être déplacée.
2. Le graphe accepte le dépôt uniquement sur une zone valide.
3. Une fenêtre de confirmation affiche le fichier, sa source et son contenu
textuel disponible.
4. Lutilisateur choisit explicitement :
- créer une nouvelle entité à partir de lextraction ;
- rattacher lextraction à une entité existante ;
- annuler.
5. Lassociation est enregistrée en base et visible dans la fiche concernée.
6. Une relation graphique peut être créée sans déplacer ni dupliquer le
fichier original.
## Contraintes
- Ne jamais créer de relation silencieusement.
- Refuser les fichiers hors du dossier de lenquête.
- Conserver les associations déjà présentes.
- Utiliser les API GTK de glisser-déposer et les commentaires Doxygen prévus
par le projet.
## Critères dacceptation
- Un fichier `.txt` dOSINT est déplaçable depuis la Sidebar.
- Le dépôt sur le graphe ouvre le dialogue de choix.
- Lentité ou lassociation créée survit à la réouverture de lenquête.
- Les dépôts annulés ne modifient ni la base ni le graphe.
- Tests unitaires des chemins invalides, du choix daction et de la
persistance.
## Vérification
```sh
make -j2
make test
```

View file

@ -65,6 +65,23 @@ CREATE TABLE IF NOT EXISTS graph_node_positions
* des relations. L'ancienne table reste présente pour assurer la compatibilité * des relations. L'ancienne table reste présente pour assurer la compatibilité
* avec les enquêtes créées avant l'introduction des nœuds de relation. * avec les enquêtes créées avant l'introduction des nœuds de relation.
*/ */
CREATE TABLE IF NOT EXISTS extractions
(
id TEXT PRIMARY KEY,
evidence_id TEXT,
source_kind TEXT NOT NULL,
source_id TEXT NOT NULL,
tool_id TEXT NOT NULL,
created_at TEXT NOT NULL,
FOREIGN KEY (evidence_id) REFERENCES preuves(id) ON DELETE CASCADE,
CHECK (source_kind IN ('evidence', 'entity')),
CHECK (length(trim(source_id)) > 0),
CHECK (length(trim(tool_id)) > 0),
CHECK (length(created_at) = 20)
);
CREATE INDEX IF NOT EXISTS idx_extractions_source
ON extractions(source_kind, source_id);
CREATE TABLE IF NOT EXISTS graph_layout_positions CREATE TABLE IF NOT EXISTS graph_layout_positions
( (
node_id TEXT PRIMARY KEY, node_id TEXT PRIMARY KEY,

17
database/schema_v7.sql Normal file
View file

@ -0,0 +1,17 @@
/* Migration SQLite V6 vers V7 : traçabilité des extractions. */
CREATE TABLE IF NOT EXISTS extractions
(
id TEXT PRIMARY KEY,
evidence_id TEXT,
source_kind TEXT NOT NULL,
source_id TEXT NOT NULL,
tool_id TEXT NOT NULL,
created_at TEXT NOT NULL,
FOREIGN KEY (evidence_id) REFERENCES preuves(id) ON DELETE CASCADE,
CHECK (source_kind IN ('evidence', 'entity')),
CHECK (length(trim(source_id)) > 0),
CHECK (length(trim(tool_id)) > 0),
CHECK (length(created_at) = 20)
);
CREATE INDEX IF NOT EXISTS idx_extractions_source
ON extractions(source_kind, source_id);

90
docs/DEPENDENCE.md Normal file
View file

@ -0,0 +1,90 @@
# Dépendances externes
Ce fichier recense les outils utilisés par LabFy Investigation et leur
installation. Il sera complété à chaque intégration dun nouvel outil.
## Principe dinstallation
Les dépendances système sont installées avec le gestionnaire de paquets de la
distribution. Les outils OSINT Python sont installés dans un environnement
isolé afin de ne pas modifier le Python système.
Le dossier recommandé pour les exécutables OSINT est :
```text
~/.local/share/labfy-osint/bin
```
Lapplication ajoute automatiquement ce dossier au `PATH` lorsquil existe.
Pour les terminaux et les scripts, on peut également ajouter dans
`~/.profile` (ou `~/.zshrc`) :
```sh
export PATH="$HOME/.local/share/labfy-osint/bin:$PATH"
```
## Outils système
| Fonction | Exécutable | Arch Linux (pacman) | Ubuntu/Debian (apt) |
| --- | --- | --- | --- |
| OCR | `tesseract` | `sudo pacman -S tesseract tesseract-data-fra` | `sudo apt install tesseract-ocr tesseract-ocr-fra` |
| Métadonnées | `exiftool` | `sudo pacman -S perl-image-exiftool` | `sudo apt install libimage-exiftool-perl` |
| Recherche DNS | `dig` | `sudo pacman -S bind` | `sudo apt install dnsutils` |
| Résolution réseau | `host` | `sudo pacman -S bind` | `sudo apt install bind9-host` |
| WHOIS | `whois` | `sudo pacman -S whois` | `sudo apt install whois` |
| Requêtes HTTPS | `curl` | `sudo pacman -S curl` | `sudo apt install curl` |
| Certificats/TLS | `openssl` | `sudo pacman -S openssl` | `sudo apt install openssl` |
| Récupération PDF | `qpdf` | `sudo pacman -S qpdf` | `sudo apt install qpdf` |
| Audit de mot de passe PDF | `john`, `pdf2john` | `sudo pacman -S john` | `sudo apt install john` |
Après installation, vérifier les outils avec :
```sh
command -v tesseract exiftool dig host whois curl openssl qpdf john pdf2john
```
## Outils OSINT Python
Préparer lenvironnement local :
```sh
python3 -m venv ~/.local/share/labfy-osint
~/.local/share/labfy-osint/bin/python -m pip install --upgrade pip
~/.local/share/labfy-osint/bin/pip install sherlock-project maigret holehe
```
Les commandes attendues par lapplication sont :
```text
~/.local/share/labfy-osint/bin/sherlock
~/.local/share/labfy-osint/bin/maigret
~/.local/share/labfy-osint/bin/holehe
```
Sur Ubuntu, installer au préalable les outils de compilation et Python :
```sh
sudo apt install python3 python3-venv python3-pip
```
Sur Arch Linux :
```sh
sudo pacman -S python python-pip
```
## Vérification dans lapplication
Au démarrage, le catalogue des outils indique le nombre dexécutables
disponibles et leurs versions. Une commande non détectée doit dabord être
vérifiée avec `command -v`, puis avec son option de version (`--version` ou
`-V` selon loutil).
## Notes
- Ne pas installer ces outils dans le dépôt Git.
- Ne jamais stocker de jetons, mots de passe ou données privées dans ce
fichier.
- Les versions peuvent varier selon la distribution ; noter ici toute
commande particulière nécessaire à Ubuntu des forces de lordre.

View file

@ -0,0 +1,15 @@
#ifndef LABFY_INVESTIGATION_EXTRACTION_DAO_H
#define LABFY_INVESTIGATION_EXTRACTION_DAO_H
#include "database/database.h"
#include <glib.h>
typedef struct ExtractionDao ExtractionDao;
ExtractionDao *extraction_dao_new(Database *database, GError **error);
void extraction_dao_free(ExtractionDao *dao);
gboolean extraction_dao_insert(ExtractionDao *dao, const char *id,
const char *evidence_id, const char *source_kind, const char *source_id,
const char *tool_id, const char *created_at, GError **error);
#endif

View file

@ -87,6 +87,8 @@ bool schema_install_v5(Database *database);
/** @brief Installe la catégorie d'identité usurpée du schéma V6. */ /** @brief Installe la catégorie d'identité usurpée du schéma V6. */
bool schema_install_v6(Database *database); bool schema_install_v6(Database *database);
bool schema_install_v7(Database *database);
/** /**
* @brief Garantit la présence des extensions du schéma courant V2. * @brief Garantit la présence des extensions du schéma courant V2.
* *

4
meline59760.txt Normal file
View file

@ -0,0 +1,4 @@
https://forum.velomania.ru/member.php?username=meline59760
https://www.youtube.com/@meline59760
https://www.baby.ru/u/meline59760
Total Websites Username Detected On : 3

View file

@ -44,6 +44,7 @@
#include "dao/relation_dao.h" #include "dao/relation_dao.h"
#include "dao/relation_evidence_dao.h" #include "dao/relation_evidence_dao.h"
#include "dao/osint_execution_dao.h" #include "dao/osint_execution_dao.h"
#include "dao/extraction_dao.h"
#include "models/evidence_type.h" #include "models/evidence_type.h"
#include "widgets/evidence_list_model.h" #include "widgets/evidence_list_model.h"
#include "widgets/evidence_category_model.h" #include "widgets/evidence_category_model.h"
@ -426,6 +427,7 @@ static void application_on_osint_search_completed(BackgroundTask *task,
char *output_directory = NULL; char *output_directory = NULL;
char *output_name = NULL; char *output_name = NULL;
char *output_path = NULL; char *output_path = NULL;
guint output_suffix = 0;
const InvestigationProject *project = NULL; const InvestigationProject *project = NULL;
GError *error = NULL; GError *error = NULL;
if (context == NULL || context->application == NULL) return; if (context == NULL || context->application == NULL) return;
@ -458,12 +460,40 @@ static void application_on_osint_search_completed(BackgroundTask *task,
output_name = g_strdup_printf("%s-%s.txt", context->target_identifier, output_name = g_strdup_printf("%s-%s.txt", context->target_identifier,
context->action_identifier); context->action_identifier);
output_path = g_build_filename(output_directory, output_name, NULL); output_path = g_build_filename(output_directory, output_name, NULL);
while (g_file_test(output_path, G_FILE_TEST_EXISTS))
{
char *unique_name = g_strdup_printf("%s-%s (%u).txt",
context->target_identifier, context->action_identifier,
++output_suffix);
g_free(output_name);
g_free(output_path);
output_name = unique_name;
output_path = g_build_filename(output_directory, output_name, NULL);
}
details = g_strdup_printf("Cible : %s\nAction : %s\n\n%s%s%s", details = g_strdup_printf("Cible : %s\nAction : %s\n\n%s%s%s",
context->target_value, context->action_identifier, context->target_value, context->action_identifier,
stdout_text != NULL ? stdout_text : "", stdout_text != NULL ? stdout_text : "",
stderr_text != NULL && stderr_text[0] != '\0' ? "\n\nErreur :\n" : "", stderr_text != NULL && stderr_text[0] != '\0' ? "\n\nErreur :\n" : "",
stderr_text != NULL ? stderr_text : ""); stderr_text != NULL ? stderr_text : "");
if (!g_file_set_contents(output_path, details, -1, &error)) goto failure; if (!g_file_set_contents(output_path, details, -1, &error)) goto failure;
{
ExtractionDao *extraction_dao = extraction_dao_new(
investigation_session_get_database(context->application->session),
&error);
char *extraction_id = g_uuid_string_random();
char *created_at = application_osint_create_timestamp();
if (extraction_dao == NULL || extraction_id == NULL || created_at == NULL ||
!extraction_dao_insert(extraction_dao, extraction_id, NULL, "entity",
context->target_identifier, context->action_identifier,
created_at, &error))
{
extraction_dao_free(extraction_dao);
g_free(extraction_id); g_free(created_at);
goto failure;
}
extraction_dao_free(extraction_dao);
g_free(extraction_id); g_free(created_at);
}
application_message_dialog_present_details_action( application_message_dialog_present_details_action(
main_window_get_window(context->application->main_window), main_window_get_window(context->application->main_window),
APPLICATION_MESSAGE_DIALOG_INFORMATION, "Résultat de la recherche OSINT", APPLICATION_MESSAGE_DIALOG_INFORMATION, "Résultat de la recherche OSINT",
@ -5018,6 +5048,9 @@ static void application_on_tree_node_selected(
else else
{ {
g_print("Type : fichier\n"); g_print("Type : fichier\n");
main_window_set_evidence_preview(
application->main_window,
investigation_node_get_path(node), node_name);
} }
} }

44
src/dao/extraction_dao.c Normal file
View file

@ -0,0 +1,44 @@
#include "dao/extraction_dao.h"
#include "database/statement.h"
#include <glib.h>
#include <gio/gio.h>
struct ExtractionDao { Database *database; };
ExtractionDao *extraction_dao_new(Database *database, GError **error)
{
if (database == NULL) { g_set_error_literal(error, G_IO_ERROR,
G_IO_ERROR_INVALID_ARGUMENT, "Connexion SQLite invalide."); return NULL; }
ExtractionDao *dao = g_new0(ExtractionDao, 1);
dao->database = database;
return dao;
}
void extraction_dao_free(ExtractionDao *dao) { g_free(dao); }
gboolean extraction_dao_insert(ExtractionDao *dao, const char *id,
const char *evidence_id, const char *source_kind, const char *source_id,
const char *tool_id, const char *created_at, GError **error)
{
DatabaseStatement *statement = NULL;
gboolean ok = FALSE;
(void) error;
if (dao == NULL || id == NULL || evidence_id == NULL || source_kind == NULL ||
source_id == NULL || tool_id == NULL || created_at == NULL) return FALSE;
statement = database_statement_prepare(dao->database,
"INSERT INTO extractions(id,evidence_id,source_kind,source_id,tool_id,created_at) VALUES(?,?,?,?,?,?);");
if (statement == NULL) return FALSE;
ok = database_statement_bind_text(statement, 1, id) &&
(evidence_id != NULL ? database_statement_bind_text(statement, 2, evidence_id) :
database_statement_bind_null(statement, 2)) &&
database_statement_bind_text(statement, 3, source_kind) &&
database_statement_bind_text(statement, 4, source_id) &&
database_statement_bind_text(statement, 5, tool_id) &&
database_statement_bind_text(statement, 6, created_at) &&
database_statement_step(statement) == DATABASE_STATEMENT_STEP_DONE;
if (!ok && error != NULL && *error == NULL)
g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_FAILED,
"Impossible d'insérer la traçabilité de l'extraction.");
database_statement_finalize(statement);
return ok;
}

View file

@ -16,12 +16,12 @@
/** /**
* @brief Version actuelle du schéma SQLite. * @brief Version actuelle du schéma SQLite.
*/ */
#define DATABASE_SCHEMA_VERSION_CURRENT 6 #define DATABASE_SCHEMA_VERSION_CURRENT 7
/** /**
* @brief Version actuelle sous forme textuelle pour metadata. * @brief Version actuelle sous forme textuelle pour metadata.
*/ */
#define DATABASE_SCHEMA_VERSION_CURRENT_TEXT "6" #define DATABASE_SCHEMA_VERSION_CURRENT_TEXT "7"
/** /**
* @brief Nom de l'application enregistré dans les métadonnées. * @brief Nom de l'application enregistré dans les métadonnées.
@ -958,6 +958,12 @@ bool database_migrate_to_latest(
schema_version = 6; schema_version = 6;
break; break;
case 6:
if (!schema_install_v7(database))
return false;
schema_version = 7;
break;
default: default:
database_set_error( database_set_error(
database, database,

View file

@ -227,6 +227,12 @@ bool schema_install_v6(Database *database)
"la migration SQLite V6"); "la migration SQLite V6");
} }
bool schema_install_v7(Database *database)
{
return schema_execute_file(database, "database/schema_v7.sql",
"la migration SQLite V7");
}
bool schema_ensure_current( bool schema_ensure_current(
Database *database Database *database
) )

View file

@ -2620,9 +2620,18 @@ void workspace_set_selected_node(
gtk_stack_set_visible_child_name( gtk_stack_set_visible_child_name(
GTK_STACK(workspace->stack), GTK_STACK(workspace->stack),
WORKSPACE_PAGE_NODE_INFORMATION node_type == INVESTIGATION_NODE_FILE
? WORKSPACE_PAGE_EVIDENCE_INFORMATION
: WORKSPACE_PAGE_NODE_INFORMATION
); );
if (node_type == INVESTIGATION_NODE_FILE)
{
gtk_label_set_text(GTK_LABEL(workspace->evidence_name_label),
node_name != NULL ? node_name : "Fichier");
workspace_set_evidence_preview(workspace, node_path, node_name);
}
g_free(children_text); g_free(children_text);
g_free(parent_text); g_free(parent_text);
} }
@ -2888,7 +2897,10 @@ void workspace_set_evidence_preview(Workspace *workspace,
else else
{ {
char *message = NULL; char *message = NULL;
if (mime_type != NULL && g_strcmp0(mime_type, "text/plain") == 0) if ((mime_type != NULL && g_strcmp0(mime_type, "text/plain") == 0) ||
g_str_has_suffix(file_path, ".txt") ||
g_str_has_suffix(file_path, ".log") ||
g_str_has_suffix(file_path, ".json"))
{ {
char *contents = NULL; char *contents = NULL;
gsize length = 0; gsize length = 0;

View file

@ -525,7 +525,7 @@ static void test_database_initialize_valid_database(void)
"FROM investigation;" "FROM investigation;"
); );
assert(strcmp(schema_version, "6") == 0); assert(strcmp(schema_version, "7") == 0);
test_database_assert_table_exists(database, "osint_executions"); test_database_assert_table_exists(database, "osint_executions");
test_database_assert_table_exists(database, "osint_execution_entities"); test_database_assert_table_exists(database, "osint_execution_entities");
test_database_assert_table_exists(database, "osint_execution_relations"); test_database_assert_table_exists(database, "osint_execution_relations");