feat(persons): identify impersonated identities
This commit is contained in:
parent
18e59cc5e6
commit
a6ec67f38d
20 changed files with 321 additions and 11 deletions
|
|
@ -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
38
database/schema_v6.sql
Normal 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);
|
||||||
|
|
@ -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.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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.
|
||||||
*
|
*
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -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 ».
|
||||||
|
|
|
||||||
|
|
@ -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.
|
||||||
*
|
*
|
||||||
|
|
|
||||||
|
|
@ -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.
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -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 d’annuler 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
|
||||||
))
|
))
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -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é";
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue