feat(persons): identify impersonated identities

This commit is contained in:
grayTerminal-sh 2026-07-22 22:29:59 +02:00
parent 18e59cc5e6
commit a6ec67f38d
20 changed files with 321 additions and 11 deletions

View file

@ -1,7 +1,7 @@
/****************************************************************************** /******************************************************************************
* Labfy Investigation * Labfy Investigation
* *
* Extensions idempotentes du schéma SQLite courant V5 * Extensions idempotentes du schéma SQLite courant V6
******************************************************************************/ ******************************************************************************/
/* /*

38
database/schema_v6.sql Normal file
View file

@ -0,0 +1,38 @@
/******************************************************************************
* Labfy Investigation
*
* Migration du schéma SQLite V5 vers V6 : catégorie identité usurpée
******************************************************************************/
ALTER TABLE person_roles RENAME TO person_roles_v5;
CREATE TABLE person_roles
(
entity_id TEXT PRIMARY KEY,
role TEXT NOT NULL DEFAULT 'uncategorized',
updated_at TEXT NOT NULL,
FOREIGN KEY (entity_id)
REFERENCES entites(id)
ON UPDATE CASCADE
ON DELETE CASCADE,
CHECK (role IN
(
'uncategorized',
'alleged_scammer',
'victim',
'witness',
'suspect',
'related_person',
'impersonated_identity'
)),
CHECK (length(updated_at) = 20)
);
INSERT INTO person_roles(entity_id, role, updated_at)
SELECT entity_id, role, updated_at FROM person_roles_v5;
DROP TABLE person_roles_v5;
CREATE INDEX idx_person_roles_role ON person_roles(role);

View file

@ -1161,11 +1161,11 @@ compte via `preuve_entites`. La création des deux lignes et de cette liaison
est transactionnelle : aucun nœud incomplet n'est conservé si une étape est transactionnelle : aucun nœud incomplet n'est conservé si une étape
échoue. échoue.
### Rôles d'enquête des personnes — schéma V5 ### Rôles d'enquête des personnes — schémas V5 et V6
La table `person_roles` associe une entité de type personne à une catégorie La table `person_roles` associe une entité de type personne à une catégorie
d'enquête contrôlée : scammer présumé, victime, témoin, suspect, personne liée d'enquête contrôlée : scammer présumé, victime, témoin, suspect, personne liée,
ou non catégorisée. La clé étrangère avec suppression en cascade évite les identité usurpée ou non catégorisée. La clé étrangère avec suppression en cascade évite les
rôles orphelins. Le code stable est persisté ; le libellé et la couleur restent rôles orphelins. Le code stable est persisté ; le libellé et la couleur restent
des choix de présentation. des choix de présentation.

View file

@ -42,5 +42,15 @@ gboolean person_entity_service_update_role(Database *database,
*/ */
gboolean person_entity_service_update_confidence(Database *database, gboolean person_entity_service_update_confidence(Database *database,
const char *entity_identifier, gint confidence, GError **error); const char *entity_identifier, gint confidence, GError **error);
/**
* @brief Enregistre le nom affiché d'une personne existante.
* @param database Connexion ouverte empruntée.
* @param entity_identifier UUID de la personne.
* @param display_name Nouveau nom non vide.
* @param error Destination facultative d'une erreur.
* @return TRUE lorsque la modification est validée.
*/
gboolean person_entity_service_update_display_name(Database *database,
const char *entity_identifier, const char *display_name, GError **error);
G_END_DECLS G_END_DECLS
#endif #endif

View file

@ -84,6 +84,9 @@ bool schema_install_v4(
/** @brief Installe la migration des rôles de personnes du schéma V5. */ /** @brief Installe la migration des rôles de personnes du schéma V5. */
bool schema_install_v5(Database *database); 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);
/** /**
* @brief Garantit la présence des extensions du schéma courant V2. * @brief Garantit la présence des extensions du schéma courant V2.
* *

View file

@ -37,7 +37,8 @@ typedef enum
PERSON_ROLE_VICTIM, PERSON_ROLE_VICTIM,
PERSON_ROLE_WITNESS, PERSON_ROLE_WITNESS,
PERSON_ROLE_SUSPECT, PERSON_ROLE_SUSPECT,
PERSON_ROLE_RELATED_PERSON PERSON_ROLE_RELATED_PERSON,
PERSON_ROLE_IMPERSONATED_IDENTITY
} PersonRole; } PersonRole;
/** /**

View file

@ -170,6 +170,9 @@ typedef void (*MainWindowOsintActionCallback)(
const char *target_value, const char *target_value,
gpointer user_data gpointer user_data
); );
/** @brief Callback appelé pour modifier le nom affiché d'une personne. */
typedef void (*MainWindowPersonNameCallback)(const char *entity_identifier,
const char *display_name, gpointer user_data);
/** /**
* @brief Définit le callback de vérification d'une preuve. * @brief Définit le callback de vérification d'une preuve.
@ -287,6 +290,9 @@ void main_window_set_osint_action_callback(
MainWindowOsintActionCallback callback, MainWindowOsintActionCallback callback,
gpointer user_data gpointer user_data
); );
/** @brief Définit le callback de modification du nom affiché. */
void main_window_set_person_name_callback(MainWindow *main_window,
MainWindowPersonNameCallback callback, gpointer user_data);
/** /**
* @brief Définit le callback du bouton « Nouvelle enquête ». * @brief Définit le callback du bouton « Nouvelle enquête ».

View file

@ -60,6 +60,13 @@ typedef void (*EntityDetailsPanelPersonConfidenceCallback)(
gpointer user_data gpointer user_data
); );
/** @brief Callback appelé pour enregistrer le nom affiché d'une personne. */
typedef void (*EntityDetailsPanelPersonNameCallback)(
const char *entity_identifier,
const char *display_name,
gpointer user_data
);
/** /**
* @brief Crée un volet de détails fermé. * @brief Crée un volet de détails fermé.
* *
@ -155,6 +162,13 @@ void entity_details_panel_set_person_confidence_callback(
gpointer user_data gpointer user_data
); );
/** @brief Définit le callback de modification du nom affiché. */
void entity_details_panel_set_person_name_callback(
EntityDetailsPanel *details_panel,
EntityDetailsPanelPersonNameCallback callback,
gpointer user_data
);
/** /**
* @brief Indique si le volet est actuellement ouvert. * @brief Indique si le volet est actuellement ouvert.
* *

View file

@ -114,6 +114,9 @@ typedef void (*WorkspaceOsintActionCallback)(
const char *target_value, const char *target_value,
gpointer user_data gpointer user_data
); );
/** @brief Callback appelé pour modifier le nom affiché d'une personne. */
typedef void (*WorkspacePersonNameCallback)(const char *entity_identifier,
const char *display_name, gpointer user_data);
/** @brief Callback appelé pour modifier les métadonnées d'une preuve. */ /** @brief Callback appelé pour modifier les métadonnées d'une preuve. */
typedef void (*WorkspaceEditEvidenceCallback)( typedef void (*WorkspaceEditEvidenceCallback)(
@ -365,6 +368,9 @@ void workspace_set_person_confidence_callback(Workspace *workspace,
void workspace_set_graph_loading( void workspace_set_graph_loading(
Workspace *workspace Workspace *workspace
); );
/** @brief Définit le callback de modification du nom affiché. */
void workspace_set_person_name_callback(Workspace *workspace,
WorkspacePersonNameCallback callback, gpointer user_data);
/** /**
* @brief Affiche le graphe chargé avec sa disposition persistée. * @brief Affiche le graphe chargé avec sa disposition persistée.

View file

@ -5033,6 +5033,35 @@ static void application_on_person_confidence_changed(
investigation_project_get_database_path(project)); investigation_project_get_database_path(project));
} }
/** @brief Enregistre le nom affiché choisi dans la fiche personne. */
static void application_on_person_name_changed(const char *entity_identifier,
const char *display_name, gpointer user_data)
{
Application *application = user_data;
Database *database = NULL;
const InvestigationProject *project = NULL;
GError *error = NULL;
if (application == NULL || application->session == NULL) return;
database = investigation_session_get_database(application->session);
project = investigation_session_get_project(application->session);
if (!person_entity_service_update_display_name(database, entity_identifier,
display_name, &error))
{
application_present_error(application, "Nom non enregistré",
error != NULL ? error->message :
"Impossible de modifier le nom affiché de cette personne.");
g_clear_error(&error);
return;
}
g_free(application->pending_entity_selection_identifier);
application->pending_entity_selection_identifier =
g_strdup(entity_identifier);
main_window_set_status(application->main_window,
"Nom enregistré. Actualisation du graphe…");
application_start_graph_loading(application,
investigation_project_get_database_path(project));
}
/** @brief Libère le contexte d'édition d'une relation. */ /** @brief Libère le contexte d'édition d'une relation. */
static void application_edit_relation_context_free( static void application_edit_relation_context_free(
ApplicationEditRelationContext *context) ApplicationEditRelationContext *context)
@ -6705,6 +6734,8 @@ static void application_on_activate(
application_on_person_role_changed, application); application_on_person_role_changed, application);
main_window_set_person_confidence_callback(application->main_window, main_window_set_person_confidence_callback(application->main_window,
application_on_person_confidence_changed, application); application_on_person_confidence_changed, application);
main_window_set_person_name_callback(application->main_window,
application_on_person_name_changed, application);
main_window_set_osint_action_callback( main_window_set_osint_action_callback(
application->main_window, application->main_window,

View file

@ -17,6 +17,58 @@ static gboolean person_entity_service_status_valid(const char *status)
g_strcmp0(status, "confirmed") == 0; g_strcmp0(status, "confirmed") == 0;
} }
gboolean person_entity_service_update_display_name(Database *database,
const char *entity_identifier, const char *display_name, GError **error)
{
static const char sql[] =
"UPDATE entites SET label=trim(?), updated_at=? WHERE id=? AND "
"type_id=(SELECT id FROM types_entite WHERE code='person');";
DatabaseStatement *statement = NULL;
GDateTime *now = NULL;
char *timestamp = NULL;
char *normalized = NULL;
gboolean active = FALSE, success = FALSE;
g_return_val_if_fail(error == NULL || *error == NULL, FALSE);
normalized = display_name != NULL ? g_strdup(display_name) : NULL;
if (normalized != NULL) g_strstrip(normalized);
if (database == NULL || entity_identifier == NULL ||
!g_uuid_string_is_valid(entity_identifier) || normalized == NULL ||
normalized[0] == '\0')
{
g_set_error_literal(error, g_quark_from_static_string(
"person-entity-service-error"), 6,
"Le nom affiché de la personne est invalide.");
g_free(normalized);
return FALSE;
}
now = g_date_time_new_now_utc();
timestamp = now != NULL ? g_date_time_format(now,
"%Y-%m-%dT%H:%M:%SZ") : NULL;
if (timestamp == NULL || !database_transaction_begin(database))
goto cleanup;
active = TRUE;
statement = database_statement_prepare(database, sql);
if (statement == NULL || !database_statement_bind_text(statement, 1,
normalized) || !database_statement_bind_text(statement, 2,
timestamp) || !database_statement_bind_text(statement, 3,
entity_identifier) || database_statement_step(statement) !=
DATABASE_STATEMENT_STEP_DONE)
goto cleanup;
database_statement_finalize(statement); statement = NULL;
if (!database_transaction_commit(database)) goto cleanup;
active = FALSE; success = TRUE;
cleanup:
database_statement_finalize(statement);
if (!success && active) database_transaction_rollback(database);
if (!success && error != NULL && *error == NULL)
g_set_error_literal(error, g_quark_from_static_string(
"person-entity-service-error"), 7,
"Le nom affiché n'a pas pu être enregistré.");
g_free(normalized); g_free(timestamp);
g_clear_pointer(&now, g_date_time_unref);
return success;
}
gboolean person_entity_service_update_confidence(Database *database, gboolean person_entity_service_update_confidence(Database *database,
const char *entity_identifier, gint confidence, GError **error) const char *entity_identifier, gint confidence, GError **error)
{ {

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 5 #define DATABASE_SCHEMA_VERSION_CURRENT 6
/** /**
* @brief Version actuelle sous forme textuelle pour metadata. * @brief Version actuelle sous forme textuelle pour metadata.
*/ */
#define DATABASE_SCHEMA_VERSION_CURRENT_TEXT "5" #define DATABASE_SCHEMA_VERSION_CURRENT_TEXT "6"
/** /**
* @brief Nom de l'application enregistré dans les métadonnées. * @brief Nom de l'application enregistré dans les métadonnées.
@ -730,6 +730,24 @@ rollback:
return false; return false;
} }
/** @brief Applique atomiquement la migration du schéma V5 vers V6. */
static bool database_migrate_v5_to_v6(Database *database)
{
bool transaction_started = false;
if (database == NULL || !database_transaction_begin(database))
return false;
transaction_started = true;
if (!schema_install_v6(database) ||
!database_update_schema_version(database, "6") ||
!database_transaction_commit(database))
goto rollback;
return true;
rollback:
if (transaction_started && !database_transaction_rollback(database))
g_warning("Impossible dannuler la migration SQLite V5 vers V6.");
return false;
}
/** /**
* @brief Garantit atomiquement la présence des extensions du schéma courant. * @brief Garantit atomiquement la présence des extensions du schéma courant.
*/ */
@ -934,6 +952,12 @@ bool database_migrate_to_latest(
schema_version = 5; schema_version = 5;
break; break;
case 5:
if (!database_migrate_v5_to_v6(database))
return false;
schema_version = 6;
break;
default: default:
database_set_error( database_set_error(
database, database,
@ -1060,6 +1084,11 @@ bool database_initialize(
goto rollback; goto rollback;
} }
if (!schema_install_v6(database))
{
goto rollback;
}
if (!schema_ensure_current( if (!schema_ensure_current(
database database
)) ))

View file

@ -221,6 +221,12 @@ bool schema_install_v5(Database *database)
"la migration SQLite V5"); "la migration SQLite V5");
} }
bool schema_install_v6(Database *database)
{
return schema_execute_file(database, "database/schema_v6.sql",
"la migration SQLite V6");
}
bool schema_ensure_current( bool schema_ensure_current(
Database *database Database *database
) )

View file

@ -613,6 +613,8 @@ const char *person_role_to_code(PersonRole role)
case PERSON_ROLE_WITNESS: return "witness"; case PERSON_ROLE_WITNESS: return "witness";
case PERSON_ROLE_SUSPECT: return "suspect"; case PERSON_ROLE_SUSPECT: return "suspect";
case PERSON_ROLE_RELATED_PERSON: return "related_person"; case PERSON_ROLE_RELATED_PERSON: return "related_person";
case PERSON_ROLE_IMPERSONATED_IDENTITY:
return "impersonated_identity";
default: return NULL; default: return NULL;
} }
} }
@ -626,6 +628,8 @@ PersonRole person_role_from_code(const char *code)
if (g_strcmp0(code, "suspect") == 0) return PERSON_ROLE_SUSPECT; if (g_strcmp0(code, "suspect") == 0) return PERSON_ROLE_SUSPECT;
if (g_strcmp0(code, "related_person") == 0) if (g_strcmp0(code, "related_person") == 0)
return PERSON_ROLE_RELATED_PERSON; return PERSON_ROLE_RELATED_PERSON;
if (g_strcmp0(code, "impersonated_identity") == 0)
return PERSON_ROLE_IMPERSONATED_IDENTITY;
return PERSON_ROLE_UNCATEGORIZED; return PERSON_ROLE_UNCATEGORIZED;
} }
@ -638,6 +642,7 @@ const char *person_role_get_label(PersonRole role)
case PERSON_ROLE_WITNESS: return "Témoin"; case PERSON_ROLE_WITNESS: return "Témoin";
case PERSON_ROLE_SUSPECT: return "Suspect"; case PERSON_ROLE_SUSPECT: return "Suspect";
case PERSON_ROLE_RELATED_PERSON: return "Personne liée"; case PERSON_ROLE_RELATED_PERSON: return "Personne liée";
case PERSON_ROLE_IMPERSONATED_IDENTITY: return "Identité usurpée";
case PERSON_ROLE_UNCATEGORIZED: case PERSON_ROLE_UNCATEGORIZED:
default: return "Non catégorisé"; default: return "Non catégorisé";
} }

View file

@ -135,6 +135,8 @@ struct MainWindow
gpointer person_role_user_data; gpointer person_role_user_data;
MainWindowPersonConfidenceCallback person_confidence_callback; MainWindowPersonConfidenceCallback person_confidence_callback;
gpointer person_confidence_user_data; gpointer person_confidence_user_data;
MainWindowPersonNameCallback person_name_callback;
gpointer person_name_user_data;
MainWindowOsintActionCallback MainWindowOsintActionCallback
osint_action_callback; osint_action_callback;
@ -551,6 +553,16 @@ static void main_window_on_person_confidence_changed(
main_window->person_confidence_user_data); main_window->person_confidence_user_data);
} }
/** @brief Relaie la modification du nom affiché d'une personne. */
static void main_window_on_person_name_changed(const char *entity_identifier,
const char *display_name, gpointer user_data)
{
MainWindow *main_window = user_data;
if (main_window != NULL && main_window->person_name_callback != NULL)
main_window->person_name_callback(entity_identifier, display_name,
main_window->person_name_user_data);
}
/** /**
* @brief Relaie la demande de vérification provenant du Workspace. * @brief Relaie la demande de vérification provenant du Workspace.
*/ */
@ -895,6 +907,8 @@ MainWindow *main_window_new(
main_window_on_person_role_changed, main_window); main_window_on_person_role_changed, main_window);
workspace_set_person_confidence_callback(main_window->workspace, workspace_set_person_confidence_callback(main_window->workspace,
main_window_on_person_confidence_changed, main_window); main_window_on_person_confidence_changed, main_window);
workspace_set_person_name_callback(main_window->workspace,
main_window_on_person_name_changed, main_window);
workspace_set_osint_action_callback( workspace_set_osint_action_callback(
main_window->workspace, main_window->workspace,
@ -1718,6 +1732,14 @@ void main_window_set_person_confidence_callback(MainWindow *main_window,
main_window->person_confidence_user_data = user_data; main_window->person_confidence_user_data = user_data;
} }
void main_window_set_person_name_callback(MainWindow *main_window,
MainWindowPersonNameCallback callback, gpointer user_data)
{
if (main_window == NULL) return;
main_window->person_name_callback = callback;
main_window->person_name_user_data = user_data;
}
void main_window_set_quit_callback( void main_window_set_quit_callback(
MainWindow *main_window, MainWindow *main_window,
MainWindowQuitCallback callback, MainWindowQuitCallback callback,

View file

@ -33,6 +33,8 @@ struct EntityDetailsPanel
GtkDropDown *person_role_dropdown; GtkDropDown *person_role_dropdown;
GtkSpinButton *person_confidence_spin; GtkSpinButton *person_confidence_spin;
GtkWidget *person_confidence_button; GtkWidget *person_confidence_button;
GtkEntry *person_name_entry;
GtkWidget *person_name_button;
char *selected_entity_identifier; char *selected_entity_identifier;
@ -50,8 +52,26 @@ struct EntityDetailsPanel
gboolean updating_person_role; gboolean updating_person_role;
EntityDetailsPanelPersonConfidenceCallback person_confidence_callback; EntityDetailsPanelPersonConfidenceCallback person_confidence_callback;
gpointer person_confidence_user_data; gpointer person_confidence_user_data;
EntityDetailsPanelPersonNameCallback person_name_callback;
gpointer person_name_user_data;
}; };
/** @brief Relaie le nouveau nom affiché explicitement enregistré. */
static void entity_details_panel_on_person_name_clicked(GtkButton *button,
gpointer user_data)
{
EntityDetailsPanel *details_panel = user_data;
const char *name = NULL;
(void) button;
if (details_panel == NULL || details_panel->person_name_callback == NULL ||
details_panel->selected_entity_identifier == NULL) return;
name = gtk_editable_get_text(GTK_EDITABLE(details_panel->person_name_entry));
if (name == NULL || name[0] == '\0') return;
details_panel->person_name_callback(
details_panel->selected_entity_identifier, name,
details_panel->person_name_user_data);
}
/** @brief Relaie la confiance explicitement enregistrée par l'utilisateur. */ /** @brief Relaie la confiance explicitement enregistrée par l'utilisateur. */
static void entity_details_panel_on_person_confidence_clicked( static void entity_details_panel_on_person_confidence_clicked(
GtkButton *button, gpointer user_data) GtkButton *button, gpointer user_data)
@ -80,7 +100,7 @@ static void entity_details_panel_on_person_role_changed(GObject *object,
details_panel->selected_entity_identifier == NULL) details_panel->selected_entity_identifier == NULL)
return; return;
selected = gtk_drop_down_get_selected(details_panel->person_role_dropdown); selected = gtk_drop_down_get_selected(details_panel->person_role_dropdown);
if (selected > PERSON_ROLE_RELATED_PERSON) return; if (selected > PERSON_ROLE_IMPERSONATED_IDENTITY) return;
details_panel->person_role_callback( details_panel->person_role_callback(
details_panel->selected_entity_identifier, (PersonRole) selected, details_panel->selected_entity_identifier, (PersonRole) selected,
details_panel->person_role_user_data); details_panel->person_role_user_data);
@ -744,7 +764,7 @@ EntityDetailsPanel *entity_details_panel_new(void)
details_panel->person_role_box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 6); details_panel->person_role_box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 6);
person_role_labels = gtk_string_list_new(NULL); person_role_labels = gtk_string_list_new(NULL);
for (guint role = PERSON_ROLE_UNCATEGORIZED; for (guint role = PERSON_ROLE_UNCATEGORIZED;
role <= PERSON_ROLE_RELATED_PERSON; role++) role <= PERSON_ROLE_IMPERSONATED_IDENTITY; role++)
gtk_string_list_append(person_role_labels, gtk_string_list_append(person_role_labels,
person_role_get_label((PersonRole) role)); person_role_get_label((PersonRole) role));
details_panel->person_role_dropdown = GTK_DROP_DOWN( details_panel->person_role_dropdown = GTK_DROP_DOWN(
@ -764,12 +784,25 @@ EntityDetailsPanel *entity_details_panel_new(void)
GTK_WIDGET(details_panel->person_confidence_spin)); GTK_WIDGET(details_panel->person_confidence_spin));
gtk_box_append(GTK_BOX(details_panel->person_role_box), gtk_box_append(GTK_BOX(details_panel->person_role_box),
details_panel->person_confidence_button); details_panel->person_confidence_button);
details_panel->person_name_entry = GTK_ENTRY(gtk_entry_new());
gtk_entry_set_placeholder_text(details_panel->person_name_entry,
"Nom réel ou nom affiché");
details_panel->person_name_button = gtk_button_new_with_label(
"Enregistrer le nom");
gtk_box_append(GTK_BOX(details_panel->person_role_box),
gtk_label_new("Nom affiché dans le graphe"));
gtk_box_append(GTK_BOX(details_panel->person_role_box),
GTK_WIDGET(details_panel->person_name_entry));
gtk_box_append(GTK_BOX(details_panel->person_role_box),
details_panel->person_name_button);
gtk_box_append(GTK_BOX(details_box), details_panel->person_role_box); gtk_box_append(GTK_BOX(details_box), details_panel->person_role_box);
g_signal_connect(details_panel->person_role_dropdown, "notify::selected", g_signal_connect(details_panel->person_role_dropdown, "notify::selected",
G_CALLBACK(entity_details_panel_on_person_role_changed), details_panel); G_CALLBACK(entity_details_panel_on_person_role_changed), details_panel);
g_signal_connect(details_panel->person_confidence_button, "clicked", g_signal_connect(details_panel->person_confidence_button, "clicked",
G_CALLBACK(entity_details_panel_on_person_confidence_clicked), G_CALLBACK(entity_details_panel_on_person_confidence_clicked),
details_panel); details_panel);
g_signal_connect(details_panel->person_name_button, "clicked",
G_CALLBACK(entity_details_panel_on_person_name_clicked), details_panel);
if (details_panel->entity_value_label == NULL || if (details_panel->entity_value_label == NULL ||
details_panel->entity_type_label == NULL || details_panel->entity_type_label == NULL ||
@ -896,6 +929,9 @@ void entity_details_panel_set_entity(
(guint) entity_record_get_person_role(entity_record)); (guint) entity_record_get_person_role(entity_record));
gtk_spin_button_set_value(details_panel->person_confidence_spin, gtk_spin_button_set_value(details_panel->person_confidence_spin,
entity_record_get_confidence(entity_record)); entity_record_get_confidence(entity_record));
gtk_editable_set_text(GTK_EDITABLE(details_panel->person_name_entry),
entity_record_get_label(entity_record) != NULL
? entity_record_get_label(entity_record) : "");
details_panel->updating_person_role = FALSE; details_panel->updating_person_role = FALSE;
entity_details_panel_update_add_relation_button( entity_details_panel_update_add_relation_button(
@ -1161,6 +1197,15 @@ void entity_details_panel_set_person_confidence_callback(
details_panel->person_confidence_user_data = user_data; details_panel->person_confidence_user_data = user_data;
} }
void entity_details_panel_set_person_name_callback(
EntityDetailsPanel *details_panel,
EntityDetailsPanelPersonNameCallback callback, gpointer user_data)
{
if (details_panel == NULL) return;
details_panel->person_name_callback = callback;
details_panel->person_name_user_data = user_data;
}
gboolean entity_details_panel_is_open( gboolean entity_details_panel_is_open(
const EntityDetailsPanel *details_panel const EntityDetailsPanel *details_panel
) )

View file

@ -3343,6 +3343,8 @@ static void investigation_graph_view_draw_entity(
cairo_set_source_rgb(cairo_context, 0.62, 0.32, 0.08); break; cairo_set_source_rgb(cairo_context, 0.62, 0.32, 0.08); break;
case PERSON_ROLE_RELATED_PERSON: case PERSON_ROLE_RELATED_PERSON:
cairo_set_source_rgb(cairo_context, 0.38, 0.20, 0.52); break; cairo_set_source_rgb(cairo_context, 0.38, 0.20, 0.52); break;
case PERSON_ROLE_IMPERSONATED_IDENTITY:
cairo_set_source_rgb(cairo_context, 0.61, 0.35, 0.71); break;
case PERSON_ROLE_UNCATEGORIZED: case PERSON_ROLE_UNCATEGORIZED:
default: default:
cairo_set_source_rgb(cairo_context, 0.25, 0.27, 0.31); break; cairo_set_source_rgb(cairo_context, 0.25, 0.27, 0.31); break;

View file

@ -138,6 +138,8 @@ struct Workspace
gpointer person_role_user_data; gpointer person_role_user_data;
WorkspacePersonConfidenceCallback person_confidence_callback; WorkspacePersonConfidenceCallback person_confidence_callback;
gpointer person_confidence_user_data; gpointer person_confidence_user_data;
WorkspacePersonNameCallback person_name_callback;
gpointer person_name_user_data;
WorkspaceOsintActionCallback WorkspaceOsintActionCallback
osint_action_callback; osint_action_callback;
@ -894,6 +896,16 @@ static void workspace_on_person_confidence_changed(
workspace->person_confidence_user_data); workspace->person_confidence_user_data);
} }
/** @brief Relaie la modification du nom affiché d'une personne. */
static void workspace_on_person_name_changed(const char *entity_identifier,
const char *display_name, gpointer user_data)
{
Workspace *workspace = user_data;
if (workspace != NULL && workspace->person_name_callback != NULL)
workspace->person_name_callback(entity_identifier, display_name,
workspace->person_name_user_data);
}
/** /**
* @brief Désélectionne le nœud lorsque le volet est fermé manuellement. * @brief Désélectionne le nœud lorsque le volet est fermé manuellement.
*/ */
@ -1816,6 +1828,9 @@ Workspace *workspace_new(void)
entity_details_panel_set_person_confidence_callback( entity_details_panel_set_person_confidence_callback(
workspace->entity_details_panel, workspace->entity_details_panel,
workspace_on_person_confidence_changed, workspace); workspace_on_person_confidence_changed, workspace);
entity_details_panel_set_person_name_callback(
workspace->entity_details_panel, workspace_on_person_name_changed,
workspace);
gtk_overlay_set_child( gtk_overlay_set_child(
GTK_OVERLAY( GTK_OVERLAY(
@ -3281,6 +3296,14 @@ void workspace_set_person_confidence_callback(Workspace *workspace,
workspace->person_confidence_user_data = user_data; workspace->person_confidence_user_data = user_data;
} }
void workspace_set_person_name_callback(Workspace *workspace,
WorkspacePersonNameCallback callback, gpointer user_data)
{
if (workspace == NULL) return;
workspace->person_name_callback = callback;
workspace->person_name_user_data = user_data;
}
void workspace_reset_graph_layout( void workspace_reset_graph_layout(
Workspace *workspace Workspace *workspace
) )

View file

@ -525,7 +525,7 @@ static void test_database_initialize_valid_database(void)
"FROM investigation;" "FROM investigation;"
); );
assert(strcmp(schema_version, "5") == 0); assert(strcmp(schema_version, "6") == 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");
@ -989,7 +989,7 @@ static void test_database_migrate_v1_to_v2(void)
assert( assert(
strcmp( strcmp(
schema_version, schema_version,
"5" "6"
) == 0 ) == 0
); );

View file

@ -44,6 +44,23 @@ static void test_person_entity_create(void)
record = entity_dao_find_by_identifier(dao, identifier, &error); record = entity_dao_find_by_identifier(dao, identifier, &error);
assert(record != NULL && error == NULL); assert(record != NULL && error == NULL);
assert(entity_record_get_person_role(record) == PERSON_ROLE_VICTIM); assert(entity_record_get_person_role(record) == PERSON_ROLE_VICTIM);
entity_record_free(record); record = NULL;
assert(person_entity_service_update_role(database, identifier,
PERSON_ROLE_IMPERSONATED_IDENTITY, &error));
record = entity_dao_find_by_identifier(dao, identifier, &error);
assert(record != NULL && error == NULL);
assert(entity_record_get_person_role(record) ==
PERSON_ROLE_IMPERSONATED_IDENTITY);
assert(strcmp(person_role_to_code(PERSON_ROLE_IMPERSONATED_IDENTITY),
"impersonated_identity") == 0);
assert(strcmp(person_role_get_label(PERSON_ROLE_IMPERSONATED_IDENTITY),
"Identité usurpée") == 0);
assert(person_entity_service_update_display_name(database, identifier,
"Vraie identité", &error));
entity_record_free(record); record = NULL;
record = entity_dao_find_by_identifier(dao, identifier, &error);
assert(record != NULL && error == NULL);
assert(strcmp(entity_record_get_label(record), "Vraie identité") == 0);
entity_record_free(record); entity_dao_free(dao); database_close(database); entity_record_free(record); entity_dao_free(dao); database_close(database);
assert(g_remove(path) == 0); assert(g_rmdir(directory) == 0); assert(g_remove(path) == 0); assert(g_rmdir(directory) == 0);
g_free(identifier); g_free(path); g_free(directory); g_free(identifier); g_free(path); g_free(directory);