feat(osint): integrate public username and email research tools

This commit is contained in:
grayTerminal-sh 2026-07-23 12:08:24 +02:00
parent 4e97d0979a
commit 6cfd26c2bd
5 changed files with 105 additions and 2 deletions

53
TICKET-104.md Normal file
View file

@ -0,0 +1,53 @@
# 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`.

View file

@ -0,0 +1 @@
{"Instagram": {"site": {"tags": ["networking", "photo", "social"], "checkType": "message", "urlProbe": "https://www.instagram.com/api/v1/users/web_profile_info/?username={username}", "headers": {"x-ig-app-id": "936619743392459", "referer": "https://www.instagram.com/"}, "presenseStrs": ["\"biography\""], "absenceStrs": ["Sorry, this page isn't available.", "dialog-404"], "errors": {"Login \u2022 Instagram": "Login required", "\"routePath\":\"\\/\"": "Login required (rate-limited or session blocked)", "Please wait a few minutes before you try again.": "Rate limited"}, "alexaRank": 4, "urlMain": "https://www.instagram.com/", "url": "https://www.instagram.com/{username}/", "usernameClaimed": "cristiano", "usernameUnclaimed": "noonewouldeverusethis77777", "protection": ["tls_fingerprint"]}, "username": "meline59760", "keywords": [], "parsing_enabled": true, "url_main": "https://www.instagram.com/", "cookies": null, "url_user": "https://www.instagram.com/meline59760/", "url_probe": "https://www.instagram.com/api/v1/users/web_profile_info/?username=meline59760", "ids_usernames": {"meline59760": "username"}, "ids_links": [], "status": {"username": "meline59760", "site_name": "Instagram", "url": "https://www.instagram.com/meline59760/", "status": "Claimed", "ids": {"username": "meline59760", "fullname": "M\u00e9line Mignon", "id": "23685360083", "image": "https://scontent-cdg4-1.cdninstagram.com/v/t51.82787-19/724092486_18109543660784084_9144824295371353753_n.jpg?stp=dst-jpg_s320x320_tt6&efg=eyJ2ZW5jb2RlX3RhZyI6InByb2ZpbGVfcGljLmRqYW5nby4zMjAuYzIifQ&_nc_ht=scontent-cdg4-1.cdninstagram.com&_nc_cat=108&_nc_oc=Q6cZ2gHFderv5RK6nIlzUJzRyHTOdeaqKM3gUV4Vo2DVognufvoKgRywYw-bMdEu_y4IFC4&_nc_ohc=_hNKnSuKlbUQ7kNvwE9OlKd&_nc_gid=n6RBziFUv7ZEDBwwlMp9Uw&edm=AOQ1c0wBAAAA&ccb=7-5&oh=00_AQD6CBmIB7n96DndM11ZrubcUJ5rJVODRwrNSpuzpDMFRg&oe=6A67B191&_nc_sid=8b3546", "bio": "\ud83d\udc95", "facebook_uid": "17841423522510670", "is_business": "False", "is_joined_recently": "False", "is_private": "True", "is_verified": "False", "follower_count": "104", "following_count": "16", "_extractor": "Instagram GraphQL"}, "tags": ["networking", "photo", "social"], "keywords": [], "keyword_match_status": "No Keywords"}, "http_status": 200, "is_similar": false, "rank": 4}, "ChaturBate": {"site": {"protection": ["ip_reputation", "tls_fingerprint"], "tags": ["porn", "streaming", "video", "webcam"], "checkType": "status_code", "alexaRank": 3611, "urlMain": "https://chaturbate.com", "url": "https://chaturbate.com/{username}/", "usernameClaimed": "cute18cute", "usernameUnclaimed": "noonewouldeverusethis77777", "errors": {"Just a moment": "Cloudflare challenge", "challenges.cloudflare.com": "Cloudflare challenge"}}, "username": "meline59760", "keywords": [], "parsing_enabled": true, "url_main": "https://chaturbate.com", "cookies": null, "url_user": "https://chaturbate.com/meline59760/", "url_probe": "https://chaturbate.com/meline59760/", "status": {"username": "meline59760", "site_name": "ChaturBate", "url": "https://chaturbate.com/meline59760/", "status": "Claimed", "ids": {}, "tags": ["porn", "streaming", "video", "webcam"], "keywords": [], "keyword_match_status": "No Keywords"}, "http_status": 200, "is_similar": false, "rank": 3611}}

View file

@ -29,7 +29,7 @@
/** /**
* @brief Position initiale de la séparation verticale. * @brief Position initiale de la séparation verticale.
*/ */
#define MAIN_WINDOW_TASK_PANEL_POSITION 430 #define MAIN_WINDOW_TASK_PANEL_POSITION 390
/** /**
* @brief Titre par défaut de la fenêtre. * @brief Titre par défaut de la fenêtre.

View file

@ -32,6 +32,7 @@ typedef struct
GtkWidget *progress_bar; GtkWidget *progress_bar;
GtkWidget *detail_label; GtkWidget *detail_label;
GtkWidget *cancel_button; GtkWidget *cancel_button;
GtkWidget *remove_button;
} TaskPanelRow; } TaskPanelRow;
/** /**
@ -188,6 +189,19 @@ static void task_panel_on_clear_clicked(
); );
} }
/** @brief Retire uniquement la tâche terminée de cette ligne. */
static void task_panel_on_remove_clicked(GtkButton *button, gpointer user_data)
{
TaskPanel *task_panel = user_data;
BackgroundTask *task = NULL;
if (task_panel == NULL || task_panel->task_manager == NULL) return;
task = g_object_get_data(G_OBJECT(button), "labfy-background-task");
if (task != NULL && background_task_get_state(task) !=
BACKGROUND_TASK_STATE_RUNNING && background_task_get_state(task) !=
BACKGROUND_TASK_STATE_PENDING)
task_manager_remove(task_panel->task_manager, task);
}
/** /**
* @brief Réagit aux changements structurels du gestionnaire. * @brief Réagit aux changements structurels du gestionnaire.
* *
@ -437,6 +451,9 @@ static void task_panel_row_update(
task_row->cancel_button, task_row->cancel_button,
state == BACKGROUND_TASK_STATE_RUNNING state == BACKGROUND_TASK_STATE_RUNNING
); );
gtk_widget_set_visible(task_row->remove_button,
state != BACKGROUND_TASK_STATE_RUNNING &&
state != BACKGROUND_TASK_STATE_PENDING);
g_clear_error( g_clear_error(
&error &error
@ -564,6 +581,7 @@ static TaskPanelRow *task_panel_create_task_row(
gtk_button_new_with_label( gtk_button_new_with_label(
"Annuler" "Annuler"
); );
task_row->remove_button = gtk_button_new_with_label("Nettoyer");
gtk_widget_set_halign( gtk_widget_set_halign(
task_row->cancel_button, task_row->cancel_button,
@ -593,6 +611,10 @@ static TaskPanelRow *task_panel_create_task_row(
), ),
task_panel task_panel
); );
g_object_set_data(G_OBJECT(task_row->remove_button),
"labfy-background-task", task_row->task);
g_signal_connect(task_row->remove_button, "clicked",
G_CALLBACK(task_panel_on_remove_clicked), task_panel);
gtk_box_append( gtk_box_append(
GTK_BOX(header_box), GTK_BOX(header_box),
@ -608,6 +630,7 @@ static TaskPanelRow *task_panel_create_task_row(
GTK_BOX(header_box), GTK_BOX(header_box),
task_row->cancel_button task_row->cancel_button
); );
gtk_box_append(GTK_BOX(header_box), task_row->remove_button);
task_row->progress_bar = task_row->progress_bar =
gtk_progress_bar_new(); gtk_progress_bar_new();

View file

@ -97,6 +97,7 @@ struct Workspace
GtkWidget *recover_pdf_password_button; GtkWidget *recover_pdf_password_button;
GtkWidget *evidence_preview_stack; GtkWidget *evidence_preview_stack;
GtkWidget *evidence_preview_status; GtkWidget *evidence_preview_status;
GtkWidget *evidence_preview_text;
GtkWidget *evidence_preview_picture; GtkWidget *evidence_preview_picture;
GtkWidget *evidence_preview_video; GtkWidget *evidence_preview_video;
GtkWidget *evidence_preview_expand_button; GtkWidget *evidence_preview_expand_button;
@ -1425,6 +1426,12 @@ Workspace *workspace_new(void)
gtk_video_set_autoplay(GTK_VIDEO(workspace->evidence_preview_video), FALSE); gtk_video_set_autoplay(GTK_VIDEO(workspace->evidence_preview_video), FALSE);
gtk_stack_add_named(GTK_STACK(workspace->evidence_preview_stack), gtk_stack_add_named(GTK_STACK(workspace->evidence_preview_stack),
workspace->evidence_preview_status, "status"); workspace->evidence_preview_status, "status");
workspace->evidence_preview_text = gtk_text_view_new();
gtk_text_view_set_editable(GTK_TEXT_VIEW(workspace->evidence_preview_text), FALSE);
gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(workspace->evidence_preview_text), GTK_WRAP_WORD_CHAR);
gtk_widget_set_vexpand(workspace->evidence_preview_text, TRUE);
gtk_stack_add_named(GTK_STACK(workspace->evidence_preview_stack),
workspace->evidence_preview_text, "text");
gtk_stack_add_named(GTK_STACK(workspace->evidence_preview_stack), gtk_stack_add_named(GTK_STACK(workspace->evidence_preview_stack),
workspace->evidence_preview_picture, "image"); workspace->evidence_preview_picture, "image");
gtk_stack_add_named(GTK_STACK(workspace->evidence_preview_stack), gtk_stack_add_named(GTK_STACK(workspace->evidence_preview_stack),
@ -2880,7 +2887,25 @@ void workspace_set_evidence_preview(Workspace *workspace,
} }
else else
{ {
char *message = g_strdup_printf( char *message = NULL;
if (mime_type != NULL && g_strcmp0(mime_type, "text/plain") == 0)
{
char *contents = NULL;
gsize length = 0;
if (g_file_get_contents(file_path, &contents, &length, NULL))
{
GtkTextBuffer *buffer = gtk_text_view_get_buffer(
GTK_TEXT_VIEW(workspace->evidence_preview_text));
gtk_text_buffer_set_text(buffer, contents, (gint) MIN(length, 200000));
gtk_stack_set_visible_child_name(GTK_STACK(
workspace->evidence_preview_stack), "text");
workspace->evidence_preview_is_video = FALSE;
g_free(contents);
goto text_preview_ready;
}
g_free(contents);
}
message = g_strdup_printf(
"Aperçu indisponible pour %s%s%s.", "Aperçu indisponible pour %s%s%s.",
display_name != NULL ? display_name : "ce fichier", display_name != NULL ? display_name : "ce fichier",
error != NULL ? " : " : "", error != NULL ? " : " : "",
@ -2892,6 +2917,7 @@ void workspace_set_evidence_preview(Workspace *workspace,
g_free(message); g_free(message);
goto cleanup; goto cleanup;
} }
text_preview_ready:
workspace->evidence_preview_path = g_strdup(file_path); workspace->evidence_preview_path = g_strdup(file_path);
gtk_widget_set_sensitive(workspace->evidence_preview_expand_button, gtk_widget_set_sensitive(workspace->evidence_preview_expand_button,
workspace->evidence_preview_path != NULL); workspace->evidence_preview_path != NULL);