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 l’enquête
## Objectif
Rendre les fichiers produits par les outils d’analyse (OSINT, ExifTool,
analyse EML, OCR, récupération PDF, etc.) traçables et directement
exploitables depuis l’interface de l’enquête.
## Besoins
- Enregistrer chaque extraction comme une preuve traitée identifiable.
- Conserver la référence de l’entité ou de la preuve à l’origine de
l’extraction.
- Afficher les extractions liées dans la fiche de l’objet 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 l’onglet **Dossier**, permettre de glisser-déposer une extraction
sur le graphe afin de proposer sa création ou son rattachement à une
entité.
- Préserver l’intégrité et le chemin relatif des fichiers produits dans
`02_Preuves_Traitees/Extractions`.
## Contraintes d’implémentation
- Utiliser les associations persistées preuve–entité existantes, sans
dupliquer les fichiers ni les résultats.
- Refuser les chemins sortant du dossier de l’enquête.
- Ne jamais exécuter automatiquement le contenu d’une extraction déposée
dans le graphe.
- Conserver les commentaires Doxygen des nouvelles fonctions publiques.
## Critères d’acceptation
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 l’onglet Dossier.
4. Un glisser-déposer sur le graphe ouvre une action explicite de création ou
d’association, sans modification silencieuse.
5. Les associations et chemins sont conservés après fermeture et réouverture
de l’enquête.
6. Les tests unitaires couvrent l’association, 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 l’enquê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. L’utilisateur choisit explicitement :
- créer une nouvelle entité à partir de l’extraction ;
- rattacher l’extraction à une entité existante ;
- annuler.
5. L’association 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 l’enquê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 d’acceptation
- Un fichier `.txt` d’OSINT est déplaçable depuis la Sidebar.
- Le dépôt sur le graphe ouvre le dialogue de choix.
- L’entité ou l’association créée survit à la réouverture de l’enquête.
- Les dépôts annulés ne modifient ni la base ni le graphe.
- Tests unitaires des chemins invalides, du choix d’action 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é
* 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
(
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 d’un nouvel outil.
## Principe d’installation
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
```
L’application ajoute automatiquement ce dossier au `PATH` lorsqu’il 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 l’environnement 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 l’application 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 l’application
Au démarrage, le catalogue des outils indique le nombre d’exécutables
disponibles et leurs versions. Une commande non détectée doit d’abord être
vérifiée avec `command -v`, puis avec son option de version (`--version` ou
`-V` selon l’outil).
## 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 l’ordre.

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. */
bool schema_install_v6(Database *database);
bool schema_install_v7(Database *database);
/**
* @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_evidence_dao.h"
#include "dao/osint_execution_dao.h"
#include "dao/extraction_dao.h"
#include "models/evidence_type.h"
#include "widgets/evidence_list_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_name = NULL;
char *output_path = NULL;
guint output_suffix = 0;
const InvestigationProject *project = NULL;
GError *error = NULL;
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,
context->action_identifier);
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",
context->target_value, context->action_identifier,
stdout_text != NULL ? stdout_text : "",
stderr_text != NULL && stderr_text[0] != '\0' ? "\n\nErreur :\n" : "",
stderr_text != NULL ? stderr_text : "");
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(
main_window_get_window(context->application->main_window),
APPLICATION_MESSAGE_DIALOG_INFORMATION, "Résultat de la recherche OSINT",
@ -5018,6 +5048,9 @@ static void application_on_tree_node_selected(
else
{
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.
*/
#define DATABASE_SCHEMA_VERSION_CURRENT 6
#define DATABASE_SCHEMA_VERSION_CURRENT 7
/**
* @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.
@ -958,6 +958,12 @@ bool database_migrate_to_latest(
schema_version = 6;
break;
case 6:
if (!schema_install_v7(database))
return false;
schema_version = 7;
break;
default:
database_set_error(
database,

View file

@ -227,6 +227,12 @@ bool schema_install_v6(Database *database)
"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(
Database *database
)

View file

@ -2620,9 +2620,18 @@ void workspace_set_selected_node(
gtk_stack_set_visible_child_name(
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(parent_text);
}
@ -2888,7 +2897,10 @@ void workspace_set_evidence_preview(Workspace *workspace,
else
{
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;
gsize length = 0;

View file

@ -525,7 +525,7 @@ static void test_database_initialize_valid_database(void)
"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_execution_entities");
test_database_assert_table_exists(database, "osint_execution_relations");