feat(osint): persist structured execution provenance
This commit is contained in:
parent
16a5d81e95
commit
1cbbc6acb1
21 changed files with 1089 additions and 46 deletions
21
Makefile
21
Makefile
|
|
@ -118,6 +118,7 @@ TEST_OSINT_ACTION_CATALOG := tests/test_osint_action_catalog
|
||||||
TEST_OSINT_DNS_QUERY := tests/test_osint_dns_query
|
TEST_OSINT_DNS_QUERY := tests/test_osint_dns_query
|
||||||
TEST_OSINT_DNS_PROPOSAL := tests/test_osint_dns_proposal
|
TEST_OSINT_DNS_PROPOSAL := tests/test_osint_dns_proposal
|
||||||
TEST_OSINT_DNS_INTEGRATION := tests/test_osint_dns_integration
|
TEST_OSINT_DNS_INTEGRATION := tests/test_osint_dns_integration
|
||||||
|
TEST_OSINT_EXECUTION_DAO := tests/test_osint_execution_dao
|
||||||
|
|
||||||
all: $(TARGET)
|
all: $(TARGET)
|
||||||
|
|
||||||
|
|
@ -566,8 +567,21 @@ $(TEST_OSINT_DNS_INTEGRATION): \
|
||||||
src/models/osint_dns_query.c \
|
src/models/osint_dns_query.c \
|
||||||
src/models/entity_record.c \
|
src/models/entity_record.c \
|
||||||
src/models/relation_record.c \
|
src/models/relation_record.c \
|
||||||
|
src/models/osint_execution_record.c \
|
||||||
src/dao/entity_dao.c \
|
src/dao/entity_dao.c \
|
||||||
src/dao/relation_dao.c \
|
src/dao/relation_dao.c \
|
||||||
|
src/dao/osint_execution_dao.c \
|
||||||
|
src/database/database.c \
|
||||||
|
src/database/schema.c \
|
||||||
|
src/database/statement.c \
|
||||||
|
src/database/transaction.c \
|
||||||
|
src/database/error.c
|
||||||
|
$(CC) $(TEST_CFLAGS) $^ -o $@ $(TEST_LDFLAGS) -lsqlite3
|
||||||
|
|
||||||
|
$(TEST_OSINT_EXECUTION_DAO): \
|
||||||
|
tests/test_osint_execution_dao.c \
|
||||||
|
src/dao/osint_execution_dao.c \
|
||||||
|
src/models/osint_execution_record.c \
|
||||||
src/database/database.c \
|
src/database/database.c \
|
||||||
src/database/schema.c \
|
src/database/schema.c \
|
||||||
src/database/statement.c \
|
src/database/statement.c \
|
||||||
|
|
@ -647,7 +661,8 @@ test: \
|
||||||
$(TEST_OSINT_ACTION_CATALOG) \
|
$(TEST_OSINT_ACTION_CATALOG) \
|
||||||
$(TEST_OSINT_DNS_QUERY) \
|
$(TEST_OSINT_DNS_QUERY) \
|
||||||
$(TEST_OSINT_DNS_PROPOSAL) \
|
$(TEST_OSINT_DNS_PROPOSAL) \
|
||||||
$(TEST_OSINT_DNS_INTEGRATION)
|
$(TEST_OSINT_DNS_INTEGRATION) \
|
||||||
|
$(TEST_OSINT_EXECUTION_DAO)
|
||||||
@echo "Exécution des tests..."
|
@echo "Exécution des tests..."
|
||||||
@./$(TEST_NODE)
|
@./$(TEST_NODE)
|
||||||
@./$(TEST_TREE_MODEL)
|
@./$(TEST_TREE_MODEL)
|
||||||
|
|
@ -700,6 +715,7 @@ test: \
|
||||||
@$(TEST_OSINT_DNS_QUERY)
|
@$(TEST_OSINT_DNS_QUERY)
|
||||||
@$(TEST_OSINT_DNS_PROPOSAL)
|
@$(TEST_OSINT_DNS_PROPOSAL)
|
||||||
@$(TEST_OSINT_DNS_INTEGRATION)
|
@$(TEST_OSINT_DNS_INTEGRATION)
|
||||||
|
@$(TEST_OSINT_EXECUTION_DAO)
|
||||||
@echo "Tous les tests sont valides."
|
@echo "Tous les tests sont valides."
|
||||||
|
|
||||||
%.o: %.c
|
%.o: %.c
|
||||||
|
|
@ -758,7 +774,8 @@ clean:
|
||||||
$(TEST_OSINT_ACTION_CATALOG) \
|
$(TEST_OSINT_ACTION_CATALOG) \
|
||||||
$(TEST_OSINT_DNS_QUERY) \
|
$(TEST_OSINT_DNS_QUERY) \
|
||||||
$(TEST_OSINT_DNS_PROPOSAL) \
|
$(TEST_OSINT_DNS_PROPOSAL) \
|
||||||
$(TEST_OSINT_DNS_INTEGRATION)
|
$(TEST_OSINT_DNS_INTEGRATION) \
|
||||||
|
$(TEST_OSINT_EXECUTION_DAO)
|
||||||
|
|
||||||
-include $(DEP)
|
-include $(DEP)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -151,7 +151,9 @@ Le socle actuel comprend notamment :
|
||||||
- sélection explicite et intégration transactionnelle des propositions DNS
|
- sélection explicite et intégration transactionnelle des propositions DNS
|
||||||
compatibles, avec normalisation et détection des doublons ;
|
compatibles, avec normalisation et détection des doublons ;
|
||||||
- création transactionnelle des relations DNS `resolves_to`, `aliases_to` et
|
- création transactionnelle des relations DNS `resolves_to`, `aliases_to` et
|
||||||
`uses_name_server` depuis l'entité interrogée.
|
`uses_name_server` depuis l'entité interrogée ;
|
||||||
|
- provenance OSINT SQLite V3 conservant les arguments, sorties brutes,
|
||||||
|
empreinte SHA-256 et liaisons vers les entités et relations intégrées.
|
||||||
|
|
||||||
Les outils actuellement présents dans le catalogue initial sont :
|
Les outils actuellement présents dans le catalogue initial sont :
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
/******************************************************************************
|
/******************************************************************************
|
||||||
* Labfy Investigation
|
* Labfy Investigation
|
||||||
*
|
*
|
||||||
* Extensions idempotentes du schéma SQLite courant V2
|
* Extensions idempotentes du schéma SQLite courant V3
|
||||||
******************************************************************************/
|
******************************************************************************/
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
|
||||||
90
database/schema_v3.sql
Normal file
90
database/schema_v3.sql
Normal file
|
|
@ -0,0 +1,90 @@
|
||||||
|
/******************************************************************************
|
||||||
|
* Labfy Investigation
|
||||||
|
*
|
||||||
|
* Migration du schéma SQLite V2 vers V3 : provenance OSINT structurée
|
||||||
|
******************************************************************************/
|
||||||
|
|
||||||
|
CREATE TABLE osint_executions
|
||||||
|
(
|
||||||
|
id TEXT PRIMARY KEY,
|
||||||
|
tool_identifier TEXT NOT NULL,
|
||||||
|
tool_version TEXT,
|
||||||
|
action_identifier TEXT NOT NULL,
|
||||||
|
selection_id TEXT NOT NULL,
|
||||||
|
selection_kind TEXT NOT NULL,
|
||||||
|
target_value TEXT NOT NULL,
|
||||||
|
arguments TEXT NOT NULL,
|
||||||
|
started_at TEXT NOT NULL,
|
||||||
|
finished_at TEXT NOT NULL,
|
||||||
|
exit_code INTEGER,
|
||||||
|
final_state TEXT NOT NULL,
|
||||||
|
stdout_raw BLOB NOT NULL,
|
||||||
|
stderr_raw BLOB NOT NULL,
|
||||||
|
output_sha256 TEXT NOT NULL,
|
||||||
|
|
||||||
|
CHECK (length(id) = 36),
|
||||||
|
CHECK (length(trim(tool_identifier)) > 0),
|
||||||
|
CHECK (length(trim(action_identifier)) > 0),
|
||||||
|
CHECK (length(selection_id) = 36),
|
||||||
|
CHECK (selection_kind IN ('entity', 'relation')),
|
||||||
|
CHECK (length(trim(target_value)) > 0),
|
||||||
|
CHECK (length(started_at) = 20),
|
||||||
|
CHECK (length(finished_at) = 20),
|
||||||
|
CHECK (final_state IN ('completed', 'failed', 'cancelled')),
|
||||||
|
CHECK (length(output_sha256) = 64),
|
||||||
|
CHECK (output_sha256 = lower(output_sha256))
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE INDEX idx_osint_executions_finished_at
|
||||||
|
ON osint_executions(finished_at);
|
||||||
|
|
||||||
|
CREATE INDEX idx_osint_executions_selection
|
||||||
|
ON osint_executions(selection_kind, selection_id);
|
||||||
|
|
||||||
|
CREATE TABLE osint_execution_entities
|
||||||
|
(
|
||||||
|
execution_id TEXT NOT NULL,
|
||||||
|
entity_id TEXT NOT NULL,
|
||||||
|
disposition TEXT NOT NULL,
|
||||||
|
|
||||||
|
PRIMARY KEY (execution_id, entity_id),
|
||||||
|
|
||||||
|
FOREIGN KEY (execution_id)
|
||||||
|
REFERENCES osint_executions(id)
|
||||||
|
ON UPDATE CASCADE
|
||||||
|
ON DELETE CASCADE,
|
||||||
|
|
||||||
|
FOREIGN KEY (entity_id)
|
||||||
|
REFERENCES entites(id)
|
||||||
|
ON UPDATE CASCADE
|
||||||
|
ON DELETE RESTRICT,
|
||||||
|
|
||||||
|
CHECK (disposition IN ('created', 'reused'))
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE INDEX idx_osint_execution_entities_entity
|
||||||
|
ON osint_execution_entities(entity_id);
|
||||||
|
|
||||||
|
CREATE TABLE osint_execution_relations
|
||||||
|
(
|
||||||
|
execution_id TEXT NOT NULL,
|
||||||
|
relation_id TEXT NOT NULL,
|
||||||
|
disposition TEXT NOT NULL,
|
||||||
|
|
||||||
|
PRIMARY KEY (execution_id, relation_id),
|
||||||
|
|
||||||
|
FOREIGN KEY (execution_id)
|
||||||
|
REFERENCES osint_executions(id)
|
||||||
|
ON UPDATE CASCADE
|
||||||
|
ON DELETE CASCADE,
|
||||||
|
|
||||||
|
FOREIGN KEY (relation_id)
|
||||||
|
REFERENCES relations(id)
|
||||||
|
ON UPDATE CASCADE
|
||||||
|
ON DELETE RESTRICT,
|
||||||
|
|
||||||
|
CHECK (disposition IN ('created', 'reused'))
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE INDEX idx_osint_execution_relations_relation
|
||||||
|
ON osint_execution_relations(relation_id);
|
||||||
|
|
@ -1106,28 +1106,24 @@ En revanche, la suppression ou la modification d'un type existant doit être
|
||||||
réalisée avec précaution afin de préserver la cohérence des données déjà
|
réalisée avec précaution afin de préserver la cohérence des données déjà
|
||||||
enregistrées.
|
enregistrées.
|
||||||
|
|
||||||
### Limite actuelle de la traçabilité OSINT
|
### Traçabilité OSINT structurée — schéma V3
|
||||||
|
|
||||||
L'intégration DNS initiale peut créer des entités `domain_name` et
|
La table `osint_executions` conserve chaque exécution terminée, y compris
|
||||||
`ip_address`, ainsi que les relations `resolves_to`, `aliases_to` et
|
lorsque la révision est annulée. Elle contient notamment :
|
||||||
`uses_name_server`. La description de l'entité et la justification de la
|
|
||||||
relation conservent une indication minimale : outil `dns.dig`, cible
|
|
||||||
interrogée et type d'enregistrement.
|
|
||||||
|
|
||||||
Cette information ne remplace pas une provenance structurée. Une migration
|
|
||||||
future devra introduire un objet de résultat OSINT capable de conserver au
|
|
||||||
minimum :
|
|
||||||
|
|
||||||
- l'outil et sa version ;
|
- l'outil et sa version ;
|
||||||
- la cible et les arguments de l'exécution ;
|
- la cible et les arguments de l'exécution ;
|
||||||
- la date d'exécution ;
|
- la date d'exécution ;
|
||||||
- la sortie brute et son empreinte ;
|
- les sorties standard et d'erreur sous forme de BLOB ;
|
||||||
- les propositions extraites ;
|
- leur empreinte SHA-256.
|
||||||
- les entités effectivement créées ou réutilisées.
|
|
||||||
|
|
||||||
Jusqu'à cette migration, l'intégration ne doit pas présenter les entités DNS
|
Les tables `osint_execution_entities` et `osint_execution_relations` relient
|
||||||
comme des faits vérifiés et ne doit pas supprimer la sortie brute affichée à
|
l'exécution aux objets intégrés. Le champ `disposition` distingue les objets
|
||||||
l'utilisateur.
|
créés des objets réutilisés. Ces liaisons sont ajoutées dans la même
|
||||||
|
transaction que l'intégration DNS.
|
||||||
|
|
||||||
|
Les descriptions métier restent présentes pour la lisibilité, mais les
|
||||||
|
entités DNS demeurent des résultats OSINT à vérifier et non des faits établis.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -40,6 +40,7 @@ GQuark osint_dns_integration_error_quark(void);
|
||||||
*
|
*
|
||||||
* @param database Connexion SQLite active.
|
* @param database Connexion SQLite active.
|
||||||
* @param source_entity_identifier UUID de l'entité interrogée.
|
* @param source_entity_identifier UUID de l'entité interrogée.
|
||||||
|
* @param execution_identifier UUID de l'exécution OSINT persistée.
|
||||||
* @param selected_proposals Propositions explicitement sélectionnées.
|
* @param selected_proposals Propositions explicitement sélectionnées.
|
||||||
* @param out_inserted_entity_count Nombre d'entités créées.
|
* @param out_inserted_entity_count Nombre d'entités créées.
|
||||||
* @param out_skipped_entity_count Nombre d'entités ignorées ou réutilisées.
|
* @param out_skipped_entity_count Nombre d'entités ignorées ou réutilisées.
|
||||||
|
|
@ -52,6 +53,7 @@ GQuark osint_dns_integration_error_quark(void);
|
||||||
gboolean osint_dns_integration_apply(
|
gboolean osint_dns_integration_apply(
|
||||||
Database *database,
|
Database *database,
|
||||||
const char *source_entity_identifier,
|
const char *source_entity_identifier,
|
||||||
|
const char *execution_identifier,
|
||||||
GPtrArray *selected_proposals,
|
GPtrArray *selected_proposals,
|
||||||
guint *out_inserted_entity_count,
|
guint *out_inserted_entity_count,
|
||||||
guint *out_skipped_entity_count,
|
guint *out_skipped_entity_count,
|
||||||
|
|
|
||||||
41
include/dao/osint_execution_dao.h
Normal file
41
include/dao/osint_execution_dao.h
Normal file
|
|
@ -0,0 +1,41 @@
|
||||||
|
/******************************************************************************
|
||||||
|
* @file osint_execution_dao.h
|
||||||
|
* @brief Persistance de la provenance des exécutions OSINT.
|
||||||
|
******************************************************************************/
|
||||||
|
|
||||||
|
#ifndef LABFY_INVESTIGATION_OSINT_EXECUTION_DAO_H
|
||||||
|
#define LABFY_INVESTIGATION_OSINT_EXECUTION_DAO_H
|
||||||
|
|
||||||
|
#include "database/database.h"
|
||||||
|
#include "models/osint_execution_record.h"
|
||||||
|
|
||||||
|
G_BEGIN_DECLS
|
||||||
|
|
||||||
|
/** @brief DAO opaque empruntant une connexion Database. */
|
||||||
|
typedef struct OsintExecutionDao OsintExecutionDao;
|
||||||
|
|
||||||
|
/** @brief Crée un DAO sur une connexion existante. */
|
||||||
|
OsintExecutionDao *osint_execution_dao_new(Database *database, GError **error);
|
||||||
|
/** @brief Libère le DAO sans fermer la connexion. */
|
||||||
|
void osint_execution_dao_free(OsintExecutionDao *dao);
|
||||||
|
/** @brief Insère une exécution sans remplacer une ligne existante. */
|
||||||
|
gboolean osint_execution_dao_insert(
|
||||||
|
OsintExecutionDao *dao, const OsintExecutionRecord *record, GError **error
|
||||||
|
);
|
||||||
|
/** @brief Recherche une exécution par UUID et retourne un modèle possédé. */
|
||||||
|
OsintExecutionRecord *osint_execution_dao_find_by_identifier(
|
||||||
|
OsintExecutionDao *dao, const char *identifier, GError **error
|
||||||
|
);
|
||||||
|
/** @brief Lie une entité créée ou réutilisée à une exécution. */
|
||||||
|
gboolean osint_execution_dao_link_entity(
|
||||||
|
OsintExecutionDao *dao, const char *execution_identifier,
|
||||||
|
const char *entity_identifier, const char *disposition, GError **error
|
||||||
|
);
|
||||||
|
/** @brief Lie une relation créée ou réutilisée à une exécution. */
|
||||||
|
gboolean osint_execution_dao_link_relation(
|
||||||
|
OsintExecutionDao *dao, const char *execution_identifier,
|
||||||
|
const char *relation_identifier, const char *disposition, GError **error
|
||||||
|
);
|
||||||
|
|
||||||
|
G_END_DECLS
|
||||||
|
#endif
|
||||||
|
|
@ -54,6 +54,20 @@ bool schema_install_v2(
|
||||||
Database *database
|
Database *database
|
||||||
);
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Installe la migration de provenance OSINT du schéma V3.
|
||||||
|
*
|
||||||
|
* La connexion doit être valide et une transaction doit déjà être active.
|
||||||
|
* La fonction ne réalise ni COMMIT ni ROLLBACK.
|
||||||
|
*
|
||||||
|
* @param database Connexion Database ouverte.
|
||||||
|
*
|
||||||
|
* @return true si la migration V3 a été appliquée, sinon false.
|
||||||
|
*/
|
||||||
|
bool schema_install_v3(
|
||||||
|
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.
|
||||||
*
|
*
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@
|
||||||
|
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
#include <glib.h>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Résultat de l'exécution d'une requête préparée.
|
* @brief Résultat de l'exécution d'une requête préparée.
|
||||||
|
|
@ -161,6 +162,24 @@ bool database_statement_column_text(
|
||||||
char **value
|
char **value
|
||||||
);
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Copie le contenu binaire d'une colonne BLOB.
|
||||||
|
*
|
||||||
|
* Si la colonne contient SQL NULL, la fonction réussit et place NULL dans
|
||||||
|
* value. La référence retournée doit être libérée avec g_bytes_unref().
|
||||||
|
*
|
||||||
|
* @param statement Requête positionnée sur une ligne.
|
||||||
|
* @param column_index Indice de la colonne.
|
||||||
|
* @param value Destination du GBytes alloué.
|
||||||
|
*
|
||||||
|
* @return true si la colonne a pu être lue, sinon false.
|
||||||
|
*/
|
||||||
|
bool database_statement_column_blob(
|
||||||
|
DatabaseStatement *statement,
|
||||||
|
int column_index,
|
||||||
|
GBytes **value
|
||||||
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Lie une chaîne de caractères à un paramètre SQL.
|
* @brief Lie une chaîne de caractères à un paramètre SQL.
|
||||||
*
|
*
|
||||||
|
|
@ -178,6 +197,23 @@ bool database_statement_bind_text(
|
||||||
const char *value
|
const char *value
|
||||||
);
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Lie des octets à un paramètre SQL BLOB.
|
||||||
|
*
|
||||||
|
* Les octets sont copiés par SQLite pendant l'appel.
|
||||||
|
*
|
||||||
|
* @param statement Requête préparée.
|
||||||
|
* @param index Indice du paramètre, commençant à 1.
|
||||||
|
* @param value Octets à lier.
|
||||||
|
*
|
||||||
|
* @return true en cas de succès, sinon false.
|
||||||
|
*/
|
||||||
|
bool database_statement_bind_blob(
|
||||||
|
DatabaseStatement *statement,
|
||||||
|
int index,
|
||||||
|
GBytes *value
|
||||||
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Lie un entier signé sur 64 bits à un paramètre SQL.
|
* @brief Lie un entier signé sur 64 bits à un paramètre SQL.
|
||||||
*
|
*
|
||||||
|
|
|
||||||
74
include/models/osint_execution_record.h
Normal file
74
include/models/osint_execution_record.h
Normal file
|
|
@ -0,0 +1,74 @@
|
||||||
|
/******************************************************************************
|
||||||
|
* @file osint_execution_record.h
|
||||||
|
* @brief Modèle immuable d'une exécution OSINT persistée.
|
||||||
|
******************************************************************************/
|
||||||
|
|
||||||
|
#ifndef LABFY_INVESTIGATION_OSINT_EXECUTION_RECORD_H
|
||||||
|
#define LABFY_INVESTIGATION_OSINT_EXECUTION_RECORD_H
|
||||||
|
|
||||||
|
#include <gio/gio.h>
|
||||||
|
|
||||||
|
G_BEGIN_DECLS
|
||||||
|
|
||||||
|
/** @brief Exécution OSINT opaque. */
|
||||||
|
typedef struct OsintExecutionRecord OsintExecutionRecord;
|
||||||
|
|
||||||
|
/** @brief Crée une exécution OSINT en copiant toutes ses données. */
|
||||||
|
OsintExecutionRecord *osint_execution_record_new(
|
||||||
|
const char *identifier,
|
||||||
|
const char *tool_identifier,
|
||||||
|
const char *tool_version,
|
||||||
|
const char *action_identifier,
|
||||||
|
const char *selection_identifier,
|
||||||
|
const char *selection_kind,
|
||||||
|
const char *target_value,
|
||||||
|
const char *arguments,
|
||||||
|
const char *started_at,
|
||||||
|
const char *finished_at,
|
||||||
|
gboolean has_exit_code,
|
||||||
|
gint exit_code,
|
||||||
|
const char *final_state,
|
||||||
|
GBytes *stdout_raw,
|
||||||
|
GBytes *stderr_raw,
|
||||||
|
const char *output_sha256,
|
||||||
|
GError **error
|
||||||
|
);
|
||||||
|
|
||||||
|
/** @brief Libère un modèle, ou accepte NULL. */
|
||||||
|
void osint_execution_record_free(OsintExecutionRecord *record);
|
||||||
|
|
||||||
|
/** @brief Retourne l'UUID emprunté. */
|
||||||
|
const char *osint_execution_record_get_identifier(const OsintExecutionRecord *record);
|
||||||
|
/** @brief Retourne l'identifiant d'outil emprunté. */
|
||||||
|
const char *osint_execution_record_get_tool_identifier(const OsintExecutionRecord *record);
|
||||||
|
/** @brief Retourne la version d'outil empruntée, ou NULL. */
|
||||||
|
const char *osint_execution_record_get_tool_version(const OsintExecutionRecord *record);
|
||||||
|
/** @brief Retourne l'identifiant d'action emprunté. */
|
||||||
|
const char *osint_execution_record_get_action_identifier(const OsintExecutionRecord *record);
|
||||||
|
/** @brief Retourne l'UUID de sélection emprunté. */
|
||||||
|
const char *osint_execution_record_get_selection_identifier(const OsintExecutionRecord *record);
|
||||||
|
/** @brief Retourne la nature de sélection empruntée. */
|
||||||
|
const char *osint_execution_record_get_selection_kind(const OsintExecutionRecord *record);
|
||||||
|
/** @brief Retourne la cible empruntée. */
|
||||||
|
const char *osint_execution_record_get_target_value(const OsintExecutionRecord *record);
|
||||||
|
/** @brief Retourne les arguments déterministes empruntés. */
|
||||||
|
const char *osint_execution_record_get_arguments(const OsintExecutionRecord *record);
|
||||||
|
/** @brief Retourne la date UTC de début empruntée. */
|
||||||
|
const char *osint_execution_record_get_started_at(const OsintExecutionRecord *record);
|
||||||
|
/** @brief Retourne la date UTC de fin empruntée. */
|
||||||
|
const char *osint_execution_record_get_finished_at(const OsintExecutionRecord *record);
|
||||||
|
/** @brief Indique si un code de sortie est disponible. */
|
||||||
|
gboolean osint_execution_record_has_exit_code(const OsintExecutionRecord *record);
|
||||||
|
/** @brief Retourne le code de sortie, ou -1 s'il est absent. */
|
||||||
|
gint osint_execution_record_get_exit_code(const OsintExecutionRecord *record);
|
||||||
|
/** @brief Retourne l'état final emprunté. */
|
||||||
|
const char *osint_execution_record_get_final_state(const OsintExecutionRecord *record);
|
||||||
|
/** @brief Retourne une référence sur stdout brut. */
|
||||||
|
GBytes *osint_execution_record_ref_stdout(const OsintExecutionRecord *record);
|
||||||
|
/** @brief Retourne une référence sur stderr brut. */
|
||||||
|
GBytes *osint_execution_record_ref_stderr(const OsintExecutionRecord *record);
|
||||||
|
/** @brief Retourne l'empreinte SHA-256 empruntée. */
|
||||||
|
const char *osint_execution_record_get_output_sha256(const OsintExecutionRecord *record);
|
||||||
|
|
||||||
|
G_END_DECLS
|
||||||
|
#endif
|
||||||
|
|
@ -15,6 +15,7 @@
|
||||||
#include "models/investigation_record.h"
|
#include "models/investigation_record.h"
|
||||||
#include "models/osint_dns_query.h"
|
#include "models/osint_dns_query.h"
|
||||||
#include "models/osint_dns_proposal.h"
|
#include "models/osint_dns_proposal.h"
|
||||||
|
#include "models/osint_execution_record.h"
|
||||||
#include "core/investigation_tree_builder.h"
|
#include "core/investigation_tree_builder.h"
|
||||||
#include "views/create_investigation_dialog.h"
|
#include "views/create_investigation_dialog.h"
|
||||||
#include "views/folder_dialog.h"
|
#include "views/folder_dialog.h"
|
||||||
|
|
@ -35,6 +36,7 @@
|
||||||
#include "views/osint_dns_review_dialog.h"
|
#include "views/osint_dns_review_dialog.h"
|
||||||
#include "dao/evidence_dao.h"
|
#include "dao/evidence_dao.h"
|
||||||
#include "dao/entity_dao.h"
|
#include "dao/entity_dao.h"
|
||||||
|
#include "dao/osint_execution_dao.h"
|
||||||
#include "models/evidence_type.h"
|
#include "models/evidence_type.h"
|
||||||
#include "widgets/evidence_list_model.h"
|
#include "widgets/evidence_list_model.h"
|
||||||
#include "widgets/evidence_category_model.h"
|
#include "widgets/evidence_category_model.h"
|
||||||
|
|
@ -164,6 +166,7 @@ typedef struct
|
||||||
Application *application;
|
Application *application;
|
||||||
char *target_identifier;
|
char *target_identifier;
|
||||||
char *target_value;
|
char *target_value;
|
||||||
|
char *started_at;
|
||||||
} ApplicationOsintActionContext;
|
} ApplicationOsintActionContext;
|
||||||
|
|
||||||
/** @brief Données possédées par l'étape de révision OSINT. */
|
/** @brief Données possédées par l'étape de révision OSINT. */
|
||||||
|
|
@ -171,6 +174,7 @@ typedef struct
|
||||||
{
|
{
|
||||||
Application *application;
|
Application *application;
|
||||||
char *source_entity_identifier;
|
char *source_entity_identifier;
|
||||||
|
char *execution_identifier;
|
||||||
GPtrArray *proposals;
|
GPtrArray *proposals;
|
||||||
} ApplicationOsintReviewContext;
|
} ApplicationOsintReviewContext;
|
||||||
|
|
||||||
|
|
@ -191,6 +195,7 @@ static void application_osint_review_context_free(gpointer user_data)
|
||||||
ApplicationOsintReviewContext *context = user_data;
|
ApplicationOsintReviewContext *context = user_data;
|
||||||
if (context == NULL) return;
|
if (context == NULL) return;
|
||||||
g_clear_pointer(&context->proposals, g_ptr_array_unref);
|
g_clear_pointer(&context->proposals, g_ptr_array_unref);
|
||||||
|
g_free(context->execution_identifier);
|
||||||
g_free(context->source_entity_identifier);
|
g_free(context->source_entity_identifier);
|
||||||
g_free(context);
|
g_free(context);
|
||||||
}
|
}
|
||||||
|
|
@ -227,6 +232,7 @@ static void application_on_osint_integration_confirmed(
|
||||||
? investigation_project_get_database_path(project) : NULL;
|
? investigation_project_get_database_path(project) : NULL;
|
||||||
if (!osint_dns_integration_apply(
|
if (!osint_dns_integration_apply(
|
||||||
database, review_context->source_entity_identifier,
|
database, review_context->source_entity_identifier,
|
||||||
|
review_context->execution_identifier,
|
||||||
selected_proposals, &inserted_count, &skipped_count,
|
selected_proposals, &inserted_count, &skipped_count,
|
||||||
&inserted_relation_count, &skipped_relation_count, &error
|
&inserted_relation_count, &skipped_relation_count, &error
|
||||||
))
|
))
|
||||||
|
|
@ -244,10 +250,10 @@ static void application_on_osint_integration_confirmed(
|
||||||
message = g_strdup_printf(
|
message = g_strdup_printf(
|
||||||
"%u entité(s) créée(s), %u entité(s) réutilisée(s) ou ignorée(s).\n"
|
"%u entité(s) créée(s), %u entité(s) réutilisée(s) ou ignorée(s).\n"
|
||||||
"%u relation(s) créée(s), %u relation(s) ignorée(s).\n\n"
|
"%u relation(s) créée(s), %u relation(s) ignorée(s).\n\n"
|
||||||
"La provenance complète devra être ajoutée par une future migration "
|
"Les objets créés ou réutilisés sont liés à l'exécution OSINT %s.",
|
||||||
"du schéma OSINT.",
|
|
||||||
inserted_count, skipped_count,
|
inserted_count, skipped_count,
|
||||||
inserted_relation_count, skipped_relation_count
|
inserted_relation_count, skipped_relation_count,
|
||||||
|
review_context->execution_identifier
|
||||||
);
|
);
|
||||||
application_message_dialog_present(
|
application_message_dialog_present(
|
||||||
main_window_get_window(application->main_window),
|
main_window_get_window(application->main_window),
|
||||||
|
|
@ -291,6 +297,7 @@ static void application_osint_action_context_free(
|
||||||
|
|
||||||
g_free(context->target_value);
|
g_free(context->target_value);
|
||||||
g_free(context->target_identifier);
|
g_free(context->target_identifier);
|
||||||
|
g_free(context->started_at);
|
||||||
g_free(context);
|
g_free(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1085,6 +1092,89 @@ static char *application_osint_output_to_utf8(
|
||||||
return g_utf8_make_valid(data, (gssize) data_size);
|
return g_utf8_make_valid(data, (gssize) data_size);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @brief Crée une date UTC au format persistant du projet. */
|
||||||
|
static char *application_osint_create_timestamp(void)
|
||||||
|
{
|
||||||
|
GDateTime *now = g_date_time_new_now_utc();
|
||||||
|
char *timestamp = now != NULL
|
||||||
|
? g_date_time_format(now, "%Y-%m-%dT%H:%M:%SZ") : NULL;
|
||||||
|
g_clear_pointer(&now, g_date_time_unref);
|
||||||
|
return timestamp;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @brief Calcule l'empreinte déterministe de stdout et stderr bruts. */
|
||||||
|
static char *application_osint_hash_outputs(GBytes *stdout_raw, GBytes *stderr_raw)
|
||||||
|
{
|
||||||
|
GChecksum *checksum = g_checksum_new(G_CHECKSUM_SHA256);
|
||||||
|
gconstpointer data = NULL;
|
||||||
|
gsize data_size = 0U;
|
||||||
|
const guint8 separator = 0U;
|
||||||
|
char *digest = NULL;
|
||||||
|
if (checksum == NULL || stdout_raw == NULL || stderr_raw == NULL)
|
||||||
|
{
|
||||||
|
g_clear_pointer(&checksum, g_checksum_free);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
data = g_bytes_get_data(stdout_raw, &data_size);
|
||||||
|
if (data_size > 0U) g_checksum_update(checksum, data, data_size);
|
||||||
|
g_checksum_update(checksum, &separator, 1U);
|
||||||
|
data = g_bytes_get_data(stderr_raw, &data_size);
|
||||||
|
if (data_size > 0U) g_checksum_update(checksum, data, data_size);
|
||||||
|
digest = g_strdup(g_checksum_get_string(checksum));
|
||||||
|
g_checksum_free(checksum);
|
||||||
|
return digest;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @brief Persiste une exécution DNS terminée et retourne son UUID. */
|
||||||
|
static char *application_persist_dns_execution(
|
||||||
|
ApplicationOsintActionContext *context,
|
||||||
|
const char *final_state,
|
||||||
|
gboolean has_exit_code,
|
||||||
|
gint exit_code,
|
||||||
|
GBytes *stdout_raw,
|
||||||
|
GBytes *stderr_raw,
|
||||||
|
GError **error
|
||||||
|
)
|
||||||
|
{
|
||||||
|
Application *application = context != NULL ? context->application : NULL;
|
||||||
|
ToolRegistry *registry = NULL;
|
||||||
|
const ToolInfo *tool_info = NULL;
|
||||||
|
Database *database = NULL;
|
||||||
|
OsintExecutionDao *dao = NULL;
|
||||||
|
OsintExecutionRecord *record = NULL;
|
||||||
|
char *identifier = NULL;
|
||||||
|
char *finished_at = NULL;
|
||||||
|
char *arguments = NULL;
|
||||||
|
char *sha256 = NULL;
|
||||||
|
gboolean inserted = FALSE;
|
||||||
|
if (application == NULL || application->session == NULL) return NULL;
|
||||||
|
database = investigation_session_get_database(application->session);
|
||||||
|
registry = tool_initializer_get_registry(application->tool_initializer);
|
||||||
|
tool_info = tool_registry_find(registry, "dns.dig");
|
||||||
|
identifier = g_uuid_string_random();
|
||||||
|
finished_at = application_osint_create_timestamp();
|
||||||
|
arguments = g_strdup_printf(
|
||||||
|
"[\"+noall\",\"+answer\",\"%s\"]", context->target_value
|
||||||
|
);
|
||||||
|
sha256 = application_osint_hash_outputs(stdout_raw, stderr_raw);
|
||||||
|
record = osint_execution_record_new(
|
||||||
|
identifier, "dns.dig",
|
||||||
|
tool_info != NULL ? tool_info_get_detected_version(tool_info) : NULL,
|
||||||
|
"dns-preview", context->target_identifier, "entity",
|
||||||
|
context->target_value, arguments, context->started_at, finished_at,
|
||||||
|
has_exit_code, exit_code, final_state, stdout_raw, stderr_raw, sha256,
|
||||||
|
error
|
||||||
|
);
|
||||||
|
dao = osint_execution_dao_new(database, error);
|
||||||
|
if (record != NULL && dao != NULL)
|
||||||
|
inserted = osint_execution_dao_insert(dao, record, error);
|
||||||
|
osint_execution_dao_free(dao);
|
||||||
|
osint_execution_record_free(record);
|
||||||
|
g_free(finished_at); g_free(arguments); g_free(sha256);
|
||||||
|
if (!inserted) g_clear_pointer(&identifier, g_free);
|
||||||
|
return identifier;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Présente le résultat final d'une résolution DNS.
|
* @brief Présente le résultat final d'une résolution DNS.
|
||||||
*/
|
*/
|
||||||
|
|
@ -1103,9 +1193,11 @@ static void application_on_dns_lookup_completed(
|
||||||
char *stderr_text = NULL;
|
char *stderr_text = NULL;
|
||||||
char *details = NULL;
|
char *details = NULL;
|
||||||
char *message = NULL;
|
char *message = NULL;
|
||||||
|
char *execution_identifier = NULL;
|
||||||
GPtrArray *proposals = NULL;
|
GPtrArray *proposals = NULL;
|
||||||
ApplicationOsintReviewContext *review_context = NULL;
|
ApplicationOsintReviewContext *review_context = NULL;
|
||||||
GError *error = NULL;
|
GError *error = NULL;
|
||||||
|
const ToolInfo *dns_tool_info = NULL;
|
||||||
|
|
||||||
if (task == NULL || context == NULL || context->application == NULL)
|
if (task == NULL || context == NULL || context->application == NULL)
|
||||||
{
|
{
|
||||||
|
|
@ -1118,19 +1210,38 @@ static void application_on_dns_lookup_completed(
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
dns_tool_info = tool_registry_find(
|
||||||
|
tool_initializer_get_registry(application->tool_initializer),
|
||||||
|
"dns.dig"
|
||||||
|
);
|
||||||
|
|
||||||
if (background_task_get_state(task) == BACKGROUND_TASK_STATE_CANCELLED)
|
if (background_task_get_state(task) == BACKGROUND_TASK_STATE_CANCELLED)
|
||||||
{
|
{
|
||||||
|
GBytes *empty_output = g_bytes_new_static("", 0U);
|
||||||
|
execution_identifier = application_persist_dns_execution(
|
||||||
|
context, "cancelled", FALSE, -1, empty_output, empty_output, &error
|
||||||
|
);
|
||||||
|
g_bytes_unref(empty_output);
|
||||||
application_message_dialog_present(
|
application_message_dialog_present(
|
||||||
main_window_get_window(application->main_window),
|
main_window_get_window(application->main_window),
|
||||||
APPLICATION_MESSAGE_DIALOG_INFORMATION,
|
APPLICATION_MESSAGE_DIALOG_INFORMATION,
|
||||||
"Résolution DNS annulée",
|
"Résolution DNS annulée",
|
||||||
"La tâche a été annulée. Aucun résultat n'a été enregistré."
|
execution_identifier != NULL
|
||||||
|
? "La tâche a été annulée et sa provenance a été enregistrée."
|
||||||
|
: "La tâche a été annulée, mais sa provenance n'a pas pu être enregistrée."
|
||||||
);
|
);
|
||||||
|
g_free(execution_identifier);
|
||||||
|
g_clear_error(&error);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (background_task_get_state(task) != BACKGROUND_TASK_STATE_COMPLETED)
|
if (background_task_get_state(task) != BACKGROUND_TASK_STATE_COMPLETED)
|
||||||
{
|
{
|
||||||
|
GBytes *empty_output = g_bytes_new_static("", 0U);
|
||||||
|
execution_identifier = application_persist_dns_execution(
|
||||||
|
context, "failed", FALSE, -1, empty_output, empty_output, NULL
|
||||||
|
);
|
||||||
|
g_bytes_unref(empty_output);
|
||||||
error = background_task_dup_error(task);
|
error = background_task_dup_error(task);
|
||||||
application_present_error(
|
application_present_error(
|
||||||
application,
|
application,
|
||||||
|
|
@ -1139,6 +1250,7 @@ static void application_on_dns_lookup_completed(
|
||||||
? error->message
|
? error->message
|
||||||
: "L'exécution de dig a été annulée ou a échoué."
|
: "L'exécution de dig a été annulée ou a échoué."
|
||||||
);
|
);
|
||||||
|
g_free(execution_identifier);
|
||||||
g_clear_error(&error);
|
g_clear_error(&error);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -1157,6 +1269,23 @@ static void application_on_dns_lookup_completed(
|
||||||
|
|
||||||
stdout_bytes = tool_process_result_ref_stdout(process_result);
|
stdout_bytes = tool_process_result_ref_stdout(process_result);
|
||||||
stderr_bytes = tool_process_result_ref_stderr(process_result);
|
stderr_bytes = tool_process_result_ref_stderr(process_result);
|
||||||
|
execution_identifier = application_persist_dns_execution(
|
||||||
|
context, "completed", TRUE,
|
||||||
|
tool_process_result_get_exit_status(process_result),
|
||||||
|
stdout_bytes, stderr_bytes, &error
|
||||||
|
);
|
||||||
|
if (execution_identifier == NULL)
|
||||||
|
{
|
||||||
|
application_present_error(
|
||||||
|
application,
|
||||||
|
"Conservation OSINT impossible",
|
||||||
|
error != NULL ? error->message
|
||||||
|
: "La sortie brute n'a pas pu être enregistrée."
|
||||||
|
);
|
||||||
|
g_clear_error(&error);
|
||||||
|
g_clear_pointer(&stdout_bytes, g_bytes_unref);
|
||||||
|
return;
|
||||||
|
}
|
||||||
stdout_text = application_osint_output_to_utf8(stdout_bytes);
|
stdout_text = application_osint_output_to_utf8(stdout_bytes);
|
||||||
stderr_text = application_osint_output_to_utf8(stderr_bytes);
|
stderr_text = application_osint_output_to_utf8(stderr_bytes);
|
||||||
|
|
||||||
|
|
@ -1193,10 +1322,14 @@ static void application_on_dns_lookup_completed(
|
||||||
review_context->source_entity_identifier = g_strdup(
|
review_context->source_entity_identifier = g_strdup(
|
||||||
context->target_identifier
|
context->target_identifier
|
||||||
);
|
);
|
||||||
|
review_context->execution_identifier = g_strdup(
|
||||||
|
execution_identifier
|
||||||
|
);
|
||||||
review_context->proposals = g_ptr_array_ref(proposals);
|
review_context->proposals = g_ptr_array_ref(proposals);
|
||||||
}
|
}
|
||||||
if (review_context == NULL || review_context->proposals == NULL ||
|
if (review_context == NULL || review_context->proposals == NULL ||
|
||||||
review_context->source_entity_identifier == NULL)
|
review_context->source_entity_identifier == NULL ||
|
||||||
|
review_context->execution_identifier == NULL)
|
||||||
{
|
{
|
||||||
application_osint_review_context_free(review_context);
|
application_osint_review_context_free(review_context);
|
||||||
review_context = NULL;
|
review_context = NULL;
|
||||||
|
|
@ -1204,10 +1337,17 @@ static void application_on_dns_lookup_completed(
|
||||||
}
|
}
|
||||||
|
|
||||||
message = g_strdup_printf(
|
message = g_strdup_printf(
|
||||||
"Cible : %s\nOutil : dns.dig\nCode de sortie : %d\n"
|
"Cible : %s\nOutil : dns.dig%s%s\nDébut : %s\nCode de sortie : %d\n"
|
||||||
"Résultat brut non enregistré dans l'enquête.",
|
"Exécution : %s\nSortie brute enregistrée avec son empreinte SHA-256.",
|
||||||
context->target_value,
|
context->target_value,
|
||||||
tool_process_result_get_exit_status(process_result)
|
dns_tool_info != NULL &&
|
||||||
|
tool_info_get_detected_version(dns_tool_info) != NULL ? " — " : "",
|
||||||
|
dns_tool_info != NULL &&
|
||||||
|
tool_info_get_detected_version(dns_tool_info) != NULL
|
||||||
|
? tool_info_get_detected_version(dns_tool_info) : "",
|
||||||
|
context->started_at,
|
||||||
|
tool_process_result_get_exit_status(process_result),
|
||||||
|
execution_identifier
|
||||||
);
|
);
|
||||||
|
|
||||||
application_message_dialog_present_details_action(
|
application_message_dialog_present_details_action(
|
||||||
|
|
@ -1232,6 +1372,7 @@ static void application_on_dns_lookup_completed(
|
||||||
g_free(stderr_text);
|
g_free(stderr_text);
|
||||||
g_free(details);
|
g_free(details);
|
||||||
g_free(message);
|
g_free(message);
|
||||||
|
g_free(execution_identifier);
|
||||||
g_clear_pointer(&proposals, g_ptr_array_unref);
|
g_clear_pointer(&proposals, g_ptr_array_unref);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1294,10 +1435,11 @@ static void application_start_dns_lookup(
|
||||||
context->application = application;
|
context->application = application;
|
||||||
context->target_identifier = g_strdup(target_identifier);
|
context->target_identifier = g_strdup(target_identifier);
|
||||||
context->target_value = g_strdup(target_value);
|
context->target_value = g_strdup(target_value);
|
||||||
|
context->started_at = application_osint_create_timestamp();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (context == NULL || context->target_identifier == NULL ||
|
if (context == NULL || context->target_identifier == NULL ||
|
||||||
context->target_value == NULL)
|
context->target_value == NULL || context->started_at == NULL)
|
||||||
{
|
{
|
||||||
application_osint_action_context_free(context);
|
application_osint_action_context_free(context);
|
||||||
tool_task_free(tool_task);
|
tool_task_free(tool_task);
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@
|
||||||
|
|
||||||
#include "dao/entity_dao.h"
|
#include "dao/entity_dao.h"
|
||||||
#include "dao/relation_dao.h"
|
#include "dao/relation_dao.h"
|
||||||
|
#include "dao/osint_execution_dao.h"
|
||||||
#include "database/error.h"
|
#include "database/error.h"
|
||||||
#include "database/transaction.h"
|
#include "database/transaction.h"
|
||||||
#include "models/entity_record.h"
|
#include "models/entity_record.h"
|
||||||
|
|
@ -83,6 +84,7 @@ GQuark osint_dns_integration_error_quark(void)
|
||||||
gboolean osint_dns_integration_apply(
|
gboolean osint_dns_integration_apply(
|
||||||
Database *database,
|
Database *database,
|
||||||
const char *source_entity_identifier,
|
const char *source_entity_identifier,
|
||||||
|
const char *execution_identifier,
|
||||||
GPtrArray *selected_proposals,
|
GPtrArray *selected_proposals,
|
||||||
guint *out_inserted_entity_count,
|
guint *out_inserted_entity_count,
|
||||||
guint *out_skipped_entity_count,
|
guint *out_skipped_entity_count,
|
||||||
|
|
@ -93,6 +95,7 @@ gboolean osint_dns_integration_apply(
|
||||||
{
|
{
|
||||||
EntityDao *entity_dao = NULL;
|
EntityDao *entity_dao = NULL;
|
||||||
RelationDao *relation_dao = NULL;
|
RelationDao *relation_dao = NULL;
|
||||||
|
OsintExecutionDao *execution_dao = NULL;
|
||||||
GPtrArray *existing_entities = NULL;
|
GPtrArray *existing_entities = NULL;
|
||||||
GPtrArray *existing_relations = NULL;
|
GPtrArray *existing_relations = NULL;
|
||||||
GHashTable *known_entities = NULL;
|
GHashTable *known_entities = NULL;
|
||||||
|
|
@ -113,6 +116,8 @@ gboolean osint_dns_integration_apply(
|
||||||
if (out_skipped_relation_count != NULL) *out_skipped_relation_count = 0U;
|
if (out_skipped_relation_count != NULL) *out_skipped_relation_count = 0U;
|
||||||
if (database == NULL || source_entity_identifier == NULL ||
|
if (database == NULL || source_entity_identifier == NULL ||
|
||||||
!g_uuid_string_is_valid(source_entity_identifier) ||
|
!g_uuid_string_is_valid(source_entity_identifier) ||
|
||||||
|
execution_identifier == NULL ||
|
||||||
|
!g_uuid_string_is_valid(execution_identifier) ||
|
||||||
selected_proposals == NULL ||
|
selected_proposals == NULL ||
|
||||||
selected_proposals->len == 0U)
|
selected_proposals->len == 0U)
|
||||||
{
|
{
|
||||||
|
|
@ -127,6 +132,8 @@ gboolean osint_dns_integration_apply(
|
||||||
if (entity_dao == NULL) return FALSE;
|
if (entity_dao == NULL) return FALSE;
|
||||||
relation_dao = relation_dao_new(database, error);
|
relation_dao = relation_dao_new(database, error);
|
||||||
if (relation_dao == NULL) goto cleanup;
|
if (relation_dao == NULL) goto cleanup;
|
||||||
|
execution_dao = osint_execution_dao_new(database, error);
|
||||||
|
if (execution_dao == NULL) goto cleanup;
|
||||||
source_entity = entity_dao_find_by_identifier(
|
source_entity = entity_dao_find_by_identifier(
|
||||||
entity_dao, source_entity_identifier, error
|
entity_dao, source_entity_identifier, error
|
||||||
);
|
);
|
||||||
|
|
@ -164,20 +171,21 @@ gboolean osint_dns_integration_apply(
|
||||||
g_free(normalized_value);
|
g_free(normalized_value);
|
||||||
}
|
}
|
||||||
known_relations = g_hash_table_new_full(
|
known_relations = g_hash_table_new_full(
|
||||||
g_str_hash, g_str_equal, g_free, NULL
|
g_str_hash, g_str_equal, g_free, g_free
|
||||||
);
|
);
|
||||||
for (guint index = 0; index < existing_relations->len; index++)
|
for (guint index = 0; index < existing_relations->len; index++)
|
||||||
{
|
{
|
||||||
const RelationRecord *relation = g_ptr_array_index(
|
const RelationRecord *relation = g_ptr_array_index(
|
||||||
existing_relations, index
|
existing_relations, index
|
||||||
);
|
);
|
||||||
g_hash_table_add(
|
g_hash_table_insert(
|
||||||
known_relations,
|
known_relations,
|
||||||
osint_dns_integration_build_relation_key(
|
osint_dns_integration_build_relation_key(
|
||||||
relation_record_get_source_entity_identifier(relation),
|
relation_record_get_source_entity_identifier(relation),
|
||||||
relation_record_get_target_entity_identifier(relation),
|
relation_record_get_target_entity_identifier(relation),
|
||||||
relation_record_get_relation_type(relation)
|
relation_record_get_relation_type(relation)
|
||||||
)
|
),
|
||||||
|
g_strdup(relation_record_get_identifier(relation))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -222,6 +230,8 @@ gboolean osint_dns_integration_apply(
|
||||||
char *relation_label = NULL;
|
char *relation_label = NULL;
|
||||||
char *relation_justification = NULL;
|
char *relation_justification = NULL;
|
||||||
GError *model_error = NULL;
|
GError *model_error = NULL;
|
||||||
|
gboolean entity_was_created = FALSE;
|
||||||
|
gboolean relation_was_created = FALSE;
|
||||||
|
|
||||||
if (entity_type == NULL || relation_type == NULL ||
|
if (entity_type == NULL || relation_type == NULL ||
|
||||||
normalized_value == NULL)
|
normalized_value == NULL)
|
||||||
|
|
@ -271,6 +281,7 @@ gboolean osint_dns_integration_apply(
|
||||||
);
|
);
|
||||||
entity_key = NULL;
|
entity_key = NULL;
|
||||||
inserted_entity_count++;
|
inserted_entity_count++;
|
||||||
|
entity_was_created = TRUE;
|
||||||
entity_record_free(entity);
|
entity_record_free(entity);
|
||||||
g_clear_error(&model_error);
|
g_clear_error(&model_error);
|
||||||
g_free(description);
|
g_free(description);
|
||||||
|
|
@ -279,8 +290,11 @@ gboolean osint_dns_integration_apply(
|
||||||
relation_key = osint_dns_integration_build_relation_key(
|
relation_key = osint_dns_integration_build_relation_key(
|
||||||
source_entity_identifier, target_identifier, relation_type
|
source_entity_identifier, target_identifier, relation_type
|
||||||
);
|
);
|
||||||
|
relation_identifier = g_strdup(
|
||||||
|
g_hash_table_lookup(known_relations, relation_key)
|
||||||
|
);
|
||||||
if (g_strcmp0(source_entity_identifier, target_identifier) == 0 ||
|
if (g_strcmp0(source_entity_identifier, target_identifier) == 0 ||
|
||||||
g_hash_table_contains(known_relations, relation_key))
|
relation_identifier != NULL)
|
||||||
{
|
{
|
||||||
skipped_relation_count++;
|
skipped_relation_count++;
|
||||||
}
|
}
|
||||||
|
|
@ -321,16 +335,36 @@ gboolean osint_dns_integration_apply(
|
||||||
database_transaction_rollback(database);
|
database_transaction_rollback(database);
|
||||||
goto cleanup;
|
goto cleanup;
|
||||||
}
|
}
|
||||||
g_hash_table_add(known_relations, relation_key);
|
g_hash_table_insert(
|
||||||
|
known_relations, relation_key, g_strdup(relation_identifier)
|
||||||
|
);
|
||||||
relation_key = NULL;
|
relation_key = NULL;
|
||||||
inserted_relation_count++;
|
inserted_relation_count++;
|
||||||
|
relation_was_created = TRUE;
|
||||||
relation_record_free(relation);
|
relation_record_free(relation);
|
||||||
g_clear_error(&model_error);
|
g_clear_error(&model_error);
|
||||||
g_free(relation_identifier);
|
|
||||||
g_free(relation_label);
|
g_free(relation_label);
|
||||||
g_free(relation_justification);
|
g_free(relation_justification);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!osint_execution_dao_link_entity(
|
||||||
|
execution_dao, execution_identifier, target_identifier,
|
||||||
|
entity_was_created ? "created" : "reused", error
|
||||||
|
) ||
|
||||||
|
(relation_identifier != NULL &&
|
||||||
|
!osint_execution_dao_link_relation(
|
||||||
|
execution_dao, execution_identifier, relation_identifier,
|
||||||
|
relation_was_created ? "created" : "reused", error
|
||||||
|
)))
|
||||||
|
{
|
||||||
|
g_free(relation_identifier);
|
||||||
|
g_free(relation_key); g_free(target_identifier);
|
||||||
|
g_free(entity_key); g_free(normalized_value);
|
||||||
|
database_transaction_rollback(database);
|
||||||
|
goto cleanup;
|
||||||
|
}
|
||||||
|
|
||||||
|
g_free(relation_identifier);
|
||||||
g_free(relation_key);
|
g_free(relation_key);
|
||||||
g_free(target_identifier);
|
g_free(target_identifier);
|
||||||
g_free(entity_key);
|
g_free(entity_key);
|
||||||
|
|
@ -367,6 +401,7 @@ cleanup:
|
||||||
g_clear_pointer(&existing_relations, g_ptr_array_unref);
|
g_clear_pointer(&existing_relations, g_ptr_array_unref);
|
||||||
entity_record_free(source_entity);
|
entity_record_free(source_entity);
|
||||||
relation_dao_free(relation_dao);
|
relation_dao_free(relation_dao);
|
||||||
|
osint_execution_dao_free(execution_dao);
|
||||||
entity_dao_free(entity_dao);
|
entity_dao_free(entity_dao);
|
||||||
return success;
|
return success;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
186
src/dao/osint_execution_dao.c
Normal file
186
src/dao/osint_execution_dao.c
Normal file
|
|
@ -0,0 +1,186 @@
|
||||||
|
/******************************************************************************
|
||||||
|
* @file osint_execution_dao.c
|
||||||
|
* @brief Persistance de la provenance des exécutions OSINT.
|
||||||
|
******************************************************************************/
|
||||||
|
|
||||||
|
#include "dao/osint_execution_dao.h"
|
||||||
|
|
||||||
|
#include "database/error.h"
|
||||||
|
#include "database/statement.h"
|
||||||
|
|
||||||
|
struct OsintExecutionDao { Database *database; };
|
||||||
|
|
||||||
|
static const char *const insert_sql =
|
||||||
|
"INSERT INTO osint_executions(id,tool_identifier,tool_version,"
|
||||||
|
"action_identifier,selection_id,selection_kind,target_value,arguments,"
|
||||||
|
"started_at,finished_at,exit_code,final_state,stdout_raw,stderr_raw,"
|
||||||
|
"output_sha256) VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?);";
|
||||||
|
static const char *const find_sql =
|
||||||
|
"SELECT id,tool_identifier,tool_version,action_identifier,selection_id,"
|
||||||
|
"selection_kind,target_value,arguments,started_at,finished_at,exit_code,"
|
||||||
|
"final_state,stdout_raw,stderr_raw,output_sha256 FROM osint_executions "
|
||||||
|
"WHERE id=?;";
|
||||||
|
|
||||||
|
static void osint_execution_dao_set_error(
|
||||||
|
OsintExecutionDao *dao, GError **error, const char *context
|
||||||
|
)
|
||||||
|
{
|
||||||
|
if (error != NULL && *error == NULL)
|
||||||
|
g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED, "%s : %s", context,
|
||||||
|
dao != NULL && database_error_get_message(dao->database) != NULL
|
||||||
|
? database_error_get_message(dao->database) : "erreur SQLite");
|
||||||
|
}
|
||||||
|
|
||||||
|
OsintExecutionDao *osint_execution_dao_new(Database *database, GError **error)
|
||||||
|
{
|
||||||
|
if (database == NULL)
|
||||||
|
{
|
||||||
|
g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT,
|
||||||
|
"La connexion de provenance OSINT est absente.");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
OsintExecutionDao *dao = g_try_new0(OsintExecutionDao, 1);
|
||||||
|
if (dao != NULL) dao->database = database;
|
||||||
|
return dao;
|
||||||
|
}
|
||||||
|
|
||||||
|
void osint_execution_dao_free(OsintExecutionDao *dao) { g_free(dao); }
|
||||||
|
|
||||||
|
gboolean osint_execution_dao_insert(
|
||||||
|
OsintExecutionDao *dao, const OsintExecutionRecord *record, GError **error
|
||||||
|
)
|
||||||
|
{
|
||||||
|
DatabaseStatement *statement = NULL;
|
||||||
|
GBytes *stdout_raw = NULL;
|
||||||
|
GBytes *stderr_raw = NULL;
|
||||||
|
gboolean success = FALSE;
|
||||||
|
if (dao == NULL || record == NULL) return FALSE;
|
||||||
|
statement = database_statement_prepare(dao->database, insert_sql);
|
||||||
|
if (statement == NULL) goto cleanup;
|
||||||
|
stdout_raw = osint_execution_record_ref_stdout(record);
|
||||||
|
stderr_raw = osint_execution_record_ref_stderr(record);
|
||||||
|
success = database_statement_bind_text(statement, 1,
|
||||||
|
osint_execution_record_get_identifier(record)) &&
|
||||||
|
database_statement_bind_text(statement, 2,
|
||||||
|
osint_execution_record_get_tool_identifier(record)) &&
|
||||||
|
(osint_execution_record_get_tool_version(record) != NULL
|
||||||
|
? database_statement_bind_text(statement, 3,
|
||||||
|
osint_execution_record_get_tool_version(record))
|
||||||
|
: database_statement_bind_null(statement, 3)) &&
|
||||||
|
database_statement_bind_text(statement, 4,
|
||||||
|
osint_execution_record_get_action_identifier(record)) &&
|
||||||
|
database_statement_bind_text(statement, 5,
|
||||||
|
osint_execution_record_get_selection_identifier(record)) &&
|
||||||
|
database_statement_bind_text(statement, 6,
|
||||||
|
osint_execution_record_get_selection_kind(record)) &&
|
||||||
|
database_statement_bind_text(statement, 7,
|
||||||
|
osint_execution_record_get_target_value(record)) &&
|
||||||
|
database_statement_bind_text(statement, 8,
|
||||||
|
osint_execution_record_get_arguments(record)) &&
|
||||||
|
database_statement_bind_text(statement, 9,
|
||||||
|
osint_execution_record_get_started_at(record)) &&
|
||||||
|
database_statement_bind_text(statement, 10,
|
||||||
|
osint_execution_record_get_finished_at(record)) &&
|
||||||
|
(osint_execution_record_has_exit_code(record)
|
||||||
|
? database_statement_bind_int64(statement, 11,
|
||||||
|
osint_execution_record_get_exit_code(record))
|
||||||
|
: database_statement_bind_null(statement, 11)) &&
|
||||||
|
database_statement_bind_text(statement, 12,
|
||||||
|
osint_execution_record_get_final_state(record)) &&
|
||||||
|
database_statement_bind_blob(statement, 13, stdout_raw) &&
|
||||||
|
database_statement_bind_blob(statement, 14, stderr_raw) &&
|
||||||
|
database_statement_bind_text(statement, 15,
|
||||||
|
osint_execution_record_get_output_sha256(record)) &&
|
||||||
|
database_statement_step(statement) == DATABASE_STATEMENT_STEP_DONE;
|
||||||
|
cleanup:
|
||||||
|
if (!success) osint_execution_dao_set_error(dao, error,
|
||||||
|
"Impossible d'insérer l'exécution OSINT");
|
||||||
|
g_clear_pointer(&stdout_raw, g_bytes_unref);
|
||||||
|
g_clear_pointer(&stderr_raw, g_bytes_unref);
|
||||||
|
database_statement_finalize(statement);
|
||||||
|
return success;
|
||||||
|
}
|
||||||
|
|
||||||
|
OsintExecutionRecord *osint_execution_dao_find_by_identifier(
|
||||||
|
OsintExecutionDao *dao, const char *identifier, GError **error
|
||||||
|
)
|
||||||
|
{
|
||||||
|
DatabaseStatement *statement = NULL;
|
||||||
|
OsintExecutionRecord *record = NULL;
|
||||||
|
char *values[12] = {0};
|
||||||
|
GBytes *stdout_raw = NULL;
|
||||||
|
GBytes *stderr_raw = NULL;
|
||||||
|
int64_t exit_code = 0;
|
||||||
|
bool exit_is_null = true;
|
||||||
|
if (dao == NULL || identifier == NULL) return NULL;
|
||||||
|
statement = database_statement_prepare(dao->database, find_sql);
|
||||||
|
if (statement == NULL || !database_statement_bind_text(statement, 1, identifier) ||
|
||||||
|
database_statement_step(statement) != DATABASE_STATEMENT_STEP_ROW)
|
||||||
|
goto cleanup;
|
||||||
|
if (!database_statement_column_text(statement, 0, &values[0]) ||
|
||||||
|
!database_statement_column_text(statement, 1, &values[1]) ||
|
||||||
|
!database_statement_column_text(statement, 2, &values[2]) ||
|
||||||
|
!database_statement_column_text(statement, 3, &values[3]) ||
|
||||||
|
!database_statement_column_text(statement, 4, &values[4]) ||
|
||||||
|
!database_statement_column_text(statement, 5, &values[5]) ||
|
||||||
|
!database_statement_column_text(statement, 6, &values[6]) ||
|
||||||
|
!database_statement_column_text(statement, 7, &values[7]) ||
|
||||||
|
!database_statement_column_text(statement, 8, &values[8]) ||
|
||||||
|
!database_statement_column_text(statement, 9, &values[9]) ||
|
||||||
|
!database_statement_column_is_null(statement, 10, &exit_is_null) ||
|
||||||
|
(!exit_is_null && !database_statement_column_int64(statement, 10, &exit_code)) ||
|
||||||
|
!database_statement_column_text(statement, 11, &values[10]) ||
|
||||||
|
!database_statement_column_blob(statement, 12, &stdout_raw) ||
|
||||||
|
!database_statement_column_blob(statement, 13, &stderr_raw) ||
|
||||||
|
!database_statement_column_text(statement, 14, &values[11])) goto cleanup;
|
||||||
|
record = osint_execution_record_new(
|
||||||
|
values[0], values[1], values[2], values[3], values[4], values[5],
|
||||||
|
values[6], values[7], values[8], values[9], !exit_is_null,
|
||||||
|
(gint) exit_code, values[10], stdout_raw, stderr_raw, values[11], error);
|
||||||
|
cleanup:
|
||||||
|
if (record == NULL && error != NULL && *error == NULL)
|
||||||
|
osint_execution_dao_set_error(dao, error,
|
||||||
|
"Impossible de lire l'exécution OSINT");
|
||||||
|
for (guint index = 0; index < G_N_ELEMENTS(values); index++) g_free(values[index]);
|
||||||
|
g_clear_pointer(&stdout_raw, g_bytes_unref);
|
||||||
|
g_clear_pointer(&stderr_raw, g_bytes_unref);
|
||||||
|
database_statement_finalize(statement);
|
||||||
|
return record;
|
||||||
|
}
|
||||||
|
|
||||||
|
static gboolean osint_execution_dao_link(
|
||||||
|
OsintExecutionDao *dao, const char *table, const char *object_column,
|
||||||
|
const char *execution_identifier, const char *object_identifier,
|
||||||
|
const char *disposition, GError **error
|
||||||
|
)
|
||||||
|
{
|
||||||
|
char *sql = NULL;
|
||||||
|
DatabaseStatement *statement = NULL;
|
||||||
|
gboolean success = FALSE;
|
||||||
|
if (dao == NULL || execution_identifier == NULL || object_identifier == NULL ||
|
||||||
|
(g_strcmp0(disposition, "created") != 0 &&
|
||||||
|
g_strcmp0(disposition, "reused") != 0)) return FALSE;
|
||||||
|
sql = g_strdup_printf(
|
||||||
|
"INSERT OR IGNORE INTO %s(execution_id,%s,disposition) VALUES(?,?,?);",
|
||||||
|
table, object_column);
|
||||||
|
statement = database_statement_prepare(dao->database, sql);
|
||||||
|
success = statement != NULL &&
|
||||||
|
database_statement_bind_text(statement, 1, execution_identifier) &&
|
||||||
|
database_statement_bind_text(statement, 2, object_identifier) &&
|
||||||
|
database_statement_bind_text(statement, 3, disposition) &&
|
||||||
|
database_statement_step(statement) == DATABASE_STATEMENT_STEP_DONE;
|
||||||
|
if (!success) osint_execution_dao_set_error(dao, error,
|
||||||
|
"Impossible de lier la provenance OSINT");
|
||||||
|
database_statement_finalize(statement); g_free(sql); return success;
|
||||||
|
}
|
||||||
|
|
||||||
|
gboolean osint_execution_dao_link_entity(
|
||||||
|
OsintExecutionDao *dao, const char *execution_identifier,
|
||||||
|
const char *entity_identifier, const char *disposition, GError **error)
|
||||||
|
{ return osint_execution_dao_link(dao, "osint_execution_entities", "entity_id",
|
||||||
|
execution_identifier, entity_identifier, disposition, error); }
|
||||||
|
gboolean osint_execution_dao_link_relation(
|
||||||
|
OsintExecutionDao *dao, const char *execution_identifier,
|
||||||
|
const char *relation_identifier, const char *disposition, GError **error)
|
||||||
|
{ return osint_execution_dao_link(dao, "osint_execution_relations", "relation_id",
|
||||||
|
execution_identifier, relation_identifier, disposition, error); }
|
||||||
|
|
@ -16,12 +16,12 @@
|
||||||
/**
|
/**
|
||||||
* @brief Version actuelle du schéma SQLite.
|
* @brief Version actuelle du schéma SQLite.
|
||||||
*/
|
*/
|
||||||
#define DATABASE_SCHEMA_VERSION_CURRENT 2
|
#define DATABASE_SCHEMA_VERSION_CURRENT 3
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Version actuelle sous forme textuelle pour metadata.
|
* @brief Version actuelle sous forme textuelle pour metadata.
|
||||||
*/
|
*/
|
||||||
#define DATABASE_SCHEMA_VERSION_CURRENT_TEXT "2"
|
#define DATABASE_SCHEMA_VERSION_CURRENT_TEXT "3"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Nom de l'application enregistré dans les métadonnées.
|
* @brief Nom de l'application enregistré dans les métadonnées.
|
||||||
|
|
@ -668,6 +668,30 @@ rollback:
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Applique atomiquement la migration du schéma V2 vers V3.
|
||||||
|
*/
|
||||||
|
static bool database_migrate_v2_to_v3(
|
||||||
|
Database *database
|
||||||
|
)
|
||||||
|
{
|
||||||
|
bool transaction_started = false;
|
||||||
|
|
||||||
|
if (database == NULL || !database_transaction_begin(database))
|
||||||
|
return false;
|
||||||
|
transaction_started = true;
|
||||||
|
if (!schema_install_v3(database) ||
|
||||||
|
!database_update_schema_version(database, "3") ||
|
||||||
|
!database_transaction_commit(database))
|
||||||
|
goto rollback;
|
||||||
|
return true;
|
||||||
|
|
||||||
|
rollback:
|
||||||
|
if (transaction_started && !database_transaction_rollback(database))
|
||||||
|
g_warning("Impossible d’annuler la migration SQLite V2 vers V3.");
|
||||||
|
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.
|
||||||
*/
|
*/
|
||||||
|
|
@ -854,6 +878,12 @@ bool database_migrate_to_latest(
|
||||||
schema_version = 2;
|
schema_version = 2;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case 2:
|
||||||
|
if (!database_migrate_v2_to_v3(database))
|
||||||
|
return false;
|
||||||
|
schema_version = 3;
|
||||||
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
database_set_error(
|
database_set_error(
|
||||||
database,
|
database,
|
||||||
|
|
@ -963,6 +993,13 @@ bool database_initialize(
|
||||||
goto rollback;
|
goto rollback;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!schema_install_v3(
|
||||||
|
database
|
||||||
|
))
|
||||||
|
{
|
||||||
|
goto rollback;
|
||||||
|
}
|
||||||
|
|
||||||
if (!schema_ensure_current(
|
if (!schema_ensure_current(
|
||||||
database
|
database
|
||||||
))
|
))
|
||||||
|
|
|
||||||
|
|
@ -193,6 +193,17 @@ bool schema_install_v2(
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool schema_install_v3(
|
||||||
|
Database *database
|
||||||
|
)
|
||||||
|
{
|
||||||
|
return schema_execute_file(
|
||||||
|
database,
|
||||||
|
"database/schema_v3.sql",
|
||||||
|
"la migration SQLite V3"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
bool schema_ensure_current(
|
bool schema_ensure_current(
|
||||||
Database *database
|
Database *database
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -172,6 +172,49 @@ bool database_statement_bind_text(
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool database_statement_bind_blob(
|
||||||
|
DatabaseStatement *statement,
|
||||||
|
int index,
|
||||||
|
GBytes *value
|
||||||
|
)
|
||||||
|
{
|
||||||
|
sqlite3 *database_handle = NULL;
|
||||||
|
gconstpointer data = NULL;
|
||||||
|
gsize data_size = 0U;
|
||||||
|
int result = SQLITE_ERROR;
|
||||||
|
|
||||||
|
if (statement == NULL || statement->handle == NULL ||
|
||||||
|
index <= 0 || value == NULL)
|
||||||
|
{
|
||||||
|
if (statement != NULL)
|
||||||
|
database_set_error(
|
||||||
|
statement->database, DATABASE_ERROR_INVALID_ARGUMENT,
|
||||||
|
"Paramètres invalides pour la liaison d'un BLOB."
|
||||||
|
);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
data = g_bytes_get_data(value, &data_size);
|
||||||
|
result = data_size == 0U
|
||||||
|
? sqlite3_bind_zeroblob(statement->handle, index, 0)
|
||||||
|
: sqlite3_bind_blob64(
|
||||||
|
statement->handle, index, data, (sqlite3_uint64) data_size,
|
||||||
|
SQLITE_TRANSIENT
|
||||||
|
);
|
||||||
|
if (result != SQLITE_OK)
|
||||||
|
{
|
||||||
|
database_handle = database_get_handle(statement->database);
|
||||||
|
database_set_error(
|
||||||
|
statement->database, DATABASE_ERROR_SQLITE,
|
||||||
|
database_handle != NULL ? sqlite3_errmsg(database_handle)
|
||||||
|
: sqlite3_errstr(result)
|
||||||
|
);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
database_clear_error_internal(statement->database);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
bool database_statement_bind_int64(
|
bool database_statement_bind_int64(
|
||||||
DatabaseStatement *statement,
|
DatabaseStatement *statement,
|
||||||
int index,
|
int index,
|
||||||
|
|
@ -666,6 +709,32 @@ bool database_statement_column_text(
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool database_statement_column_blob(
|
||||||
|
DatabaseStatement *statement,
|
||||||
|
int column_index,
|
||||||
|
GBytes **value
|
||||||
|
)
|
||||||
|
{
|
||||||
|
const void *column_data = NULL;
|
||||||
|
int column_count = 0;
|
||||||
|
int data_length = 0;
|
||||||
|
|
||||||
|
if (statement == NULL || statement->handle == NULL || value == NULL)
|
||||||
|
return false;
|
||||||
|
*value = NULL;
|
||||||
|
column_count = sqlite3_column_count(statement->handle);
|
||||||
|
if (column_index < 0 || column_index >= column_count) return false;
|
||||||
|
if (sqlite3_column_type(statement->handle, column_index) == SQLITE_NULL)
|
||||||
|
return true;
|
||||||
|
if (sqlite3_column_type(statement->handle, column_index) != SQLITE_BLOB)
|
||||||
|
return false;
|
||||||
|
column_data = sqlite3_column_blob(statement->handle, column_index);
|
||||||
|
data_length = sqlite3_column_bytes(statement->handle, column_index);
|
||||||
|
if (data_length < 0 || (data_length > 0 && column_data == NULL)) return false;
|
||||||
|
*value = g_bytes_new(column_data, (gsize) data_length);
|
||||||
|
return *value != NULL;
|
||||||
|
}
|
||||||
|
|
||||||
bool database_statement_column_int64(
|
bool database_statement_column_int64(
|
||||||
DatabaseStatement *statement,
|
DatabaseStatement *statement,
|
||||||
int column_index,
|
int column_index,
|
||||||
|
|
|
||||||
130
src/models/osint_execution_record.c
Normal file
130
src/models/osint_execution_record.c
Normal file
|
|
@ -0,0 +1,130 @@
|
||||||
|
/******************************************************************************
|
||||||
|
* @file osint_execution_record.c
|
||||||
|
* @brief Modèle immuable d'une exécution OSINT persistée.
|
||||||
|
******************************************************************************/
|
||||||
|
|
||||||
|
#include "models/osint_execution_record.h"
|
||||||
|
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
struct OsintExecutionRecord
|
||||||
|
{
|
||||||
|
char *identifier;
|
||||||
|
char *tool_identifier;
|
||||||
|
char *tool_version;
|
||||||
|
char *action_identifier;
|
||||||
|
char *selection_identifier;
|
||||||
|
char *selection_kind;
|
||||||
|
char *target_value;
|
||||||
|
char *arguments;
|
||||||
|
char *started_at;
|
||||||
|
char *finished_at;
|
||||||
|
gboolean has_exit_code;
|
||||||
|
gint exit_code;
|
||||||
|
char *final_state;
|
||||||
|
GBytes *stdout_raw;
|
||||||
|
GBytes *stderr_raw;
|
||||||
|
char *output_sha256;
|
||||||
|
};
|
||||||
|
|
||||||
|
static gboolean osint_execution_record_text_valid(const char *text)
|
||||||
|
{ return text != NULL && text[0] != '\0'; }
|
||||||
|
|
||||||
|
OsintExecutionRecord *osint_execution_record_new(
|
||||||
|
const char *identifier, const char *tool_identifier,
|
||||||
|
const char *tool_version, const char *action_identifier,
|
||||||
|
const char *selection_identifier, const char *selection_kind,
|
||||||
|
const char *target_value, const char *arguments,
|
||||||
|
const char *started_at, const char *finished_at,
|
||||||
|
gboolean has_exit_code, gint exit_code, const char *final_state,
|
||||||
|
GBytes *stdout_raw, GBytes *stderr_raw, const char *output_sha256,
|
||||||
|
GError **error
|
||||||
|
)
|
||||||
|
{
|
||||||
|
OsintExecutionRecord *record = NULL;
|
||||||
|
const gboolean selection_valid = g_strcmp0(selection_kind, "entity") == 0 ||
|
||||||
|
g_strcmp0(selection_kind, "relation") == 0;
|
||||||
|
const gboolean state_valid = g_strcmp0(final_state, "completed") == 0 ||
|
||||||
|
g_strcmp0(final_state, "failed") == 0 ||
|
||||||
|
g_strcmp0(final_state, "cancelled") == 0;
|
||||||
|
if ((error != NULL && *error != NULL) || identifier == NULL ||
|
||||||
|
!g_uuid_string_is_valid(identifier) || selection_identifier == NULL ||
|
||||||
|
!g_uuid_string_is_valid(selection_identifier) || !selection_valid ||
|
||||||
|
!state_valid || !osint_execution_record_text_valid(tool_identifier) ||
|
||||||
|
!osint_execution_record_text_valid(action_identifier) ||
|
||||||
|
!osint_execution_record_text_valid(target_value) || arguments == NULL ||
|
||||||
|
started_at == NULL || strlen(started_at) != 20U || finished_at == NULL ||
|
||||||
|
strlen(finished_at) != 20U || stdout_raw == NULL || stderr_raw == NULL ||
|
||||||
|
output_sha256 == NULL || strlen(output_sha256) != 64U)
|
||||||
|
{
|
||||||
|
g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT,
|
||||||
|
"L'exécution OSINT est invalide.");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
record = g_try_new0(OsintExecutionRecord, 1);
|
||||||
|
if (record == NULL) return NULL;
|
||||||
|
record->identifier = g_strdup(identifier);
|
||||||
|
record->tool_identifier = g_strdup(tool_identifier);
|
||||||
|
record->tool_version = g_strdup(tool_version);
|
||||||
|
record->action_identifier = g_strdup(action_identifier);
|
||||||
|
record->selection_identifier = g_strdup(selection_identifier);
|
||||||
|
record->selection_kind = g_strdup(selection_kind);
|
||||||
|
record->target_value = g_strdup(target_value);
|
||||||
|
record->arguments = g_strdup(arguments);
|
||||||
|
record->started_at = g_strdup(started_at);
|
||||||
|
record->finished_at = g_strdup(finished_at);
|
||||||
|
record->has_exit_code = has_exit_code;
|
||||||
|
record->exit_code = exit_code;
|
||||||
|
record->final_state = g_strdup(final_state);
|
||||||
|
record->stdout_raw = g_bytes_ref(stdout_raw);
|
||||||
|
record->stderr_raw = g_bytes_ref(stderr_raw);
|
||||||
|
record->output_sha256 = g_strdup(output_sha256);
|
||||||
|
if (record->identifier == NULL || record->tool_identifier == NULL ||
|
||||||
|
record->action_identifier == NULL || record->selection_identifier == NULL ||
|
||||||
|
record->selection_kind == NULL || record->target_value == NULL ||
|
||||||
|
record->arguments == NULL || record->started_at == NULL ||
|
||||||
|
record->finished_at == NULL || record->final_state == NULL ||
|
||||||
|
record->output_sha256 == NULL)
|
||||||
|
{
|
||||||
|
osint_execution_record_free(record);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
return record;
|
||||||
|
}
|
||||||
|
|
||||||
|
void osint_execution_record_free(OsintExecutionRecord *record)
|
||||||
|
{
|
||||||
|
if (record == NULL) return;
|
||||||
|
g_free(record->output_sha256); g_clear_pointer(&record->stderr_raw, g_bytes_unref);
|
||||||
|
g_clear_pointer(&record->stdout_raw, g_bytes_unref); g_free(record->final_state);
|
||||||
|
g_free(record->finished_at); g_free(record->started_at); g_free(record->arguments);
|
||||||
|
g_free(record->target_value); g_free(record->selection_kind);
|
||||||
|
g_free(record->selection_identifier); g_free(record->action_identifier);
|
||||||
|
g_free(record->tool_version); g_free(record->tool_identifier);
|
||||||
|
g_free(record->identifier); g_free(record);
|
||||||
|
}
|
||||||
|
|
||||||
|
#define OSINT_GETTER(name, field) \
|
||||||
|
const char *name(const OsintExecutionRecord *record) \
|
||||||
|
{ return record != NULL ? record->field : NULL; }
|
||||||
|
OSINT_GETTER(osint_execution_record_get_identifier, identifier)
|
||||||
|
OSINT_GETTER(osint_execution_record_get_tool_identifier, tool_identifier)
|
||||||
|
OSINT_GETTER(osint_execution_record_get_tool_version, tool_version)
|
||||||
|
OSINT_GETTER(osint_execution_record_get_action_identifier, action_identifier)
|
||||||
|
OSINT_GETTER(osint_execution_record_get_selection_identifier, selection_identifier)
|
||||||
|
OSINT_GETTER(osint_execution_record_get_selection_kind, selection_kind)
|
||||||
|
OSINT_GETTER(osint_execution_record_get_target_value, target_value)
|
||||||
|
OSINT_GETTER(osint_execution_record_get_arguments, arguments)
|
||||||
|
OSINT_GETTER(osint_execution_record_get_started_at, started_at)
|
||||||
|
OSINT_GETTER(osint_execution_record_get_finished_at, finished_at)
|
||||||
|
OSINT_GETTER(osint_execution_record_get_final_state, final_state)
|
||||||
|
OSINT_GETTER(osint_execution_record_get_output_sha256, output_sha256)
|
||||||
|
#undef OSINT_GETTER
|
||||||
|
gboolean osint_execution_record_has_exit_code(const OsintExecutionRecord *record)
|
||||||
|
{ return record != NULL && record->has_exit_code; }
|
||||||
|
gint osint_execution_record_get_exit_code(const OsintExecutionRecord *record)
|
||||||
|
{ return record != NULL && record->has_exit_code ? record->exit_code : -1; }
|
||||||
|
GBytes *osint_execution_record_ref_stdout(const OsintExecutionRecord *record)
|
||||||
|
{ return record != NULL && record->stdout_raw != NULL ? g_bytes_ref(record->stdout_raw) : NULL; }
|
||||||
|
GBytes *osint_execution_record_ref_stderr(const OsintExecutionRecord *record)
|
||||||
|
{ return record != NULL && record->stderr_raw != NULL ? g_bytes_ref(record->stderr_raw) : NULL; }
|
||||||
|
|
@ -525,7 +525,10 @@ static void test_database_initialize_valid_database(void)
|
||||||
"FROM investigation;"
|
"FROM investigation;"
|
||||||
);
|
);
|
||||||
|
|
||||||
assert(strcmp(schema_version, "2") == 0);
|
assert(strcmp(schema_version, "3") == 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");
|
||||||
assert(strcmp(application_name, "Labfy Investigation") == 0);
|
assert(strcmp(application_name, "Labfy Investigation") == 0);
|
||||||
|
|
||||||
assert(created_at[0] != '\0');
|
assert(created_at[0] != '\0');
|
||||||
|
|
@ -795,7 +798,7 @@ static void test_database_initialize_rollback(void)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Vérifie la migration V1 vers V2 et sa répétition sans effet.
|
* @brief Vérifie la migration V1 vers la version courante sans perte.
|
||||||
*/
|
*/
|
||||||
static void test_database_migrate_v1_to_v2(void)
|
static void test_database_migrate_v1_to_v2(void)
|
||||||
{
|
{
|
||||||
|
|
@ -849,7 +852,7 @@ static void test_database_migrate_v1_to_v2(void)
|
||||||
assert(database_context != NULL);
|
assert(database_context != NULL);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Premier appel : migration réelle de V1 vers V2.
|
* Premier appel : migrations réelles de V1 vers la version courante.
|
||||||
*/
|
*/
|
||||||
assert(
|
assert(
|
||||||
database_migrate_to_latest(
|
database_migrate_to_latest(
|
||||||
|
|
@ -864,7 +867,7 @@ static void test_database_migrate_v1_to_v2(void)
|
||||||
);
|
);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Second appel : la base est déjà en V2.
|
* Second appel : la base est déjà à jour.
|
||||||
*
|
*
|
||||||
* Aucun ALTER TABLE, index ou trigger ne doit être rejoué.
|
* Aucun ALTER TABLE, index ou trigger ne doit être rejoué.
|
||||||
*/
|
*/
|
||||||
|
|
@ -984,7 +987,7 @@ static void test_database_migrate_v1_to_v2(void)
|
||||||
assert(
|
assert(
|
||||||
strcmp(
|
strcmp(
|
||||||
schema_version,
|
schema_version,
|
||||||
"2"
|
"3"
|
||||||
) == 0
|
) == 0
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,13 +7,49 @@
|
||||||
|
|
||||||
#include "dao/entity_dao.h"
|
#include "dao/entity_dao.h"
|
||||||
#include "dao/relation_dao.h"
|
#include "dao/relation_dao.h"
|
||||||
|
#include "dao/osint_execution_dao.h"
|
||||||
#include "database/database.h"
|
#include "database/database.h"
|
||||||
|
#include "database/statement.h"
|
||||||
#include "models/osint_dns_proposal.h"
|
#include "models/osint_dns_proposal.h"
|
||||||
#include "models/relation_record.h"
|
#include "models/relation_record.h"
|
||||||
|
#include "models/osint_execution_record.h"
|
||||||
|
|
||||||
#include <glib.h>
|
#include <glib.h>
|
||||||
#include <glib/gstdio.h>
|
#include <glib/gstdio.h>
|
||||||
|
|
||||||
|
static void test_insert_execution(Database *database, const char *source_identifier)
|
||||||
|
{
|
||||||
|
GError *error = NULL;
|
||||||
|
GBytes *empty = g_bytes_new_static("", 0U);
|
||||||
|
OsintExecutionRecord *record = osint_execution_record_new(
|
||||||
|
"99999999-9999-4999-8999-999999999999", "dns.dig", "DiG 9",
|
||||||
|
"dns-preview", source_identifier, "entity", "example.org",
|
||||||
|
"[\"+noall\",\"+answer\",\"example.org\"]",
|
||||||
|
"2026-01-01T00:00:00Z", "2026-01-01T00:00:01Z", TRUE, 0,
|
||||||
|
"completed", empty, empty,
|
||||||
|
"6e340b9cffb37a989ca544e6bb780a2c78901d3fb33738768511a30617afa01d",
|
||||||
|
&error
|
||||||
|
);
|
||||||
|
OsintExecutionDao *dao = osint_execution_dao_new(database, &error);
|
||||||
|
g_assert_true(osint_execution_dao_insert(dao, record, &error));
|
||||||
|
osint_execution_dao_free(dao);
|
||||||
|
osint_execution_record_free(record);
|
||||||
|
g_bytes_unref(empty);
|
||||||
|
}
|
||||||
|
|
||||||
|
static gint64 test_count_rows(Database *database, const char *table)
|
||||||
|
{
|
||||||
|
char *sql = g_strdup_printf("SELECT COUNT(*) FROM %s;", table);
|
||||||
|
DatabaseStatement *statement = database_statement_prepare(database, sql);
|
||||||
|
int64_t count = -1;
|
||||||
|
g_assert_cmpint(database_statement_step(statement), ==,
|
||||||
|
DATABASE_STATEMENT_STEP_ROW);
|
||||||
|
g_assert_true(database_statement_column_int64(statement, 0, &count));
|
||||||
|
database_statement_finalize(statement);
|
||||||
|
g_free(sql);
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
static void test_integration_and_duplicates(void)
|
static void test_integration_and_duplicates(void)
|
||||||
{
|
{
|
||||||
GError *error = NULL;
|
GError *error = NULL;
|
||||||
|
|
@ -41,6 +77,7 @@ static void test_integration_and_duplicates(void)
|
||||||
"2026-01-01T00:00:00Z", ENTITY_STATUS_ACTIVE, &error
|
"2026-01-01T00:00:00Z", ENTITY_STATUS_ACTIVE, &error
|
||||||
);
|
);
|
||||||
g_assert_true(entity_dao_insert(entity_dao, source, &error));
|
g_assert_true(entity_dao_insert(entity_dao, source, &error));
|
||||||
|
test_insert_execution(database, "11111111-1111-4111-8111-111111111111");
|
||||||
proposals = osint_dns_proposal_parse(
|
proposals = osint_dns_proposal_parse(
|
||||||
"example.org",
|
"example.org",
|
||||||
"example.org. 60 IN A 192.0.2.10\n"
|
"example.org. 60 IN A 192.0.2.10\n"
|
||||||
|
|
@ -48,7 +85,8 @@ static void test_integration_and_duplicates(void)
|
||||||
"example.org. 60 IN MX 10 mail.example.org.\n"
|
"example.org. 60 IN MX 10 mail.example.org.\n"
|
||||||
);
|
);
|
||||||
g_assert_true(osint_dns_integration_apply(
|
g_assert_true(osint_dns_integration_apply(
|
||||||
database, "11111111-1111-4111-8111-111111111111", proposals,
|
database, "11111111-1111-4111-8111-111111111111",
|
||||||
|
"99999999-9999-4999-8999-999999999999", proposals,
|
||||||
&inserted, &skipped, &inserted_relations, &skipped_relations, &error
|
&inserted, &skipped, &inserted_relations, &skipped_relations, &error
|
||||||
));
|
));
|
||||||
g_assert_no_error(error);
|
g_assert_no_error(error);
|
||||||
|
|
@ -58,7 +96,8 @@ static void test_integration_and_duplicates(void)
|
||||||
g_assert_cmpuint(skipped_relations, ==, 1);
|
g_assert_cmpuint(skipped_relations, ==, 1);
|
||||||
|
|
||||||
g_assert_true(osint_dns_integration_apply(
|
g_assert_true(osint_dns_integration_apply(
|
||||||
database, "11111111-1111-4111-8111-111111111111", proposals,
|
database, "11111111-1111-4111-8111-111111111111",
|
||||||
|
"99999999-9999-4999-8999-999999999999", proposals,
|
||||||
&inserted, &skipped, &inserted_relations, &skipped_relations, &error
|
&inserted, &skipped, &inserted_relations, &skipped_relations, &error
|
||||||
));
|
));
|
||||||
g_assert_cmpuint(inserted, ==, 0);
|
g_assert_cmpuint(inserted, ==, 0);
|
||||||
|
|
@ -70,6 +109,12 @@ static void test_integration_and_duplicates(void)
|
||||||
relation_dao = relation_dao_new(database, &error);
|
relation_dao = relation_dao_new(database, &error);
|
||||||
relations = relation_dao_list_all(relation_dao, &error);
|
relations = relation_dao_list_all(relation_dao, &error);
|
||||||
g_assert_cmpuint(relations->len, ==, 2);
|
g_assert_cmpuint(relations->len, ==, 2);
|
||||||
|
g_assert_cmpint(
|
||||||
|
test_count_rows(database, "osint_execution_entities"), ==, 2
|
||||||
|
);
|
||||||
|
g_assert_cmpint(
|
||||||
|
test_count_rows(database, "osint_execution_relations"), ==, 2
|
||||||
|
);
|
||||||
|
|
||||||
g_ptr_array_unref(relations);
|
g_ptr_array_unref(relations);
|
||||||
relation_dao_free(relation_dao);
|
relation_dao_free(relation_dao);
|
||||||
|
|
@ -88,7 +133,7 @@ static void test_invalid_arguments(void)
|
||||||
{
|
{
|
||||||
GError *error = NULL;
|
GError *error = NULL;
|
||||||
g_assert_false(osint_dns_integration_apply(
|
g_assert_false(osint_dns_integration_apply(
|
||||||
NULL, NULL, NULL, NULL, NULL, NULL, NULL, &error
|
NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, &error
|
||||||
));
|
));
|
||||||
g_assert_error(
|
g_assert_error(
|
||||||
error, OSINT_DNS_INTEGRATION_ERROR,
|
error, OSINT_DNS_INTEGRATION_ERROR,
|
||||||
|
|
@ -120,11 +165,13 @@ static void test_normalized_existing_domain_is_skipped(void)
|
||||||
"2026-01-01T00:00:00Z", ENTITY_STATUS_ACTIVE, &error
|
"2026-01-01T00:00:00Z", ENTITY_STATUS_ACTIVE, &error
|
||||||
);
|
);
|
||||||
g_assert_true(entity_dao_insert(entity_dao, existing, &error));
|
g_assert_true(entity_dao_insert(entity_dao, existing, &error));
|
||||||
|
test_insert_execution(database, "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa");
|
||||||
proposals = osint_dns_proposal_parse(
|
proposals = osint_dns_proposal_parse(
|
||||||
"example.org", "example.org. 60 IN CNAME www.example.org.\n"
|
"example.org", "example.org. 60 IN CNAME www.example.org.\n"
|
||||||
);
|
);
|
||||||
g_assert_true(osint_dns_integration_apply(
|
g_assert_true(osint_dns_integration_apply(
|
||||||
database, "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa", proposals,
|
database, "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa",
|
||||||
|
"99999999-9999-4999-8999-999999999999", proposals,
|
||||||
&inserted, &skipped, &inserted_relations, &skipped_relations, &error
|
&inserted, &skipped, &inserted_relations, &skipped_relations, &error
|
||||||
));
|
));
|
||||||
g_assert_cmpuint(inserted, ==, 0);
|
g_assert_cmpuint(inserted, ==, 0);
|
||||||
|
|
|
||||||
84
tests/test_osint_execution_dao.c
Normal file
84
tests/test_osint_execution_dao.c
Normal file
|
|
@ -0,0 +1,84 @@
|
||||||
|
/******************************************************************************
|
||||||
|
* @file test_osint_execution_dao.c
|
||||||
|
* @brief Tests du modèle et du DAO de provenance OSINT.
|
||||||
|
******************************************************************************/
|
||||||
|
|
||||||
|
#include "dao/osint_execution_dao.h"
|
||||||
|
#include "database/database.h"
|
||||||
|
|
||||||
|
#include <glib.h>
|
||||||
|
#include <glib/gstdio.h>
|
||||||
|
|
||||||
|
static void test_insert_and_read_raw_execution(void)
|
||||||
|
{
|
||||||
|
const guint8 raw_data[] = {0x41U, 0x00U, 0xFFU};
|
||||||
|
GError *error = NULL;
|
||||||
|
char *directory = g_dir_make_tmp("labfy-osint-execution-XXXXXX", &error);
|
||||||
|
char *path = g_build_filename(directory, "Enquete.sqlite", NULL);
|
||||||
|
Database *database = NULL;
|
||||||
|
OsintExecutionDao *dao = NULL;
|
||||||
|
OsintExecutionRecord *record = NULL;
|
||||||
|
OsintExecutionRecord *loaded = NULL;
|
||||||
|
GBytes *stdout_raw = g_bytes_new_static(raw_data, sizeof(raw_data));
|
||||||
|
GBytes *stderr_raw = g_bytes_new_static("", 0U);
|
||||||
|
GBytes *loaded_stdout = NULL;
|
||||||
|
|
||||||
|
g_assert_true(database_initialize(path, "OSINT", directory));
|
||||||
|
database = database_open(path);
|
||||||
|
dao = osint_execution_dao_new(database, &error);
|
||||||
|
record = osint_execution_record_new(
|
||||||
|
"eeeeeeee-eeee-4eee-8eee-eeeeeeeeeeee", "dns.dig", "DiG 9",
|
||||||
|
"dns-preview", "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa", "entity",
|
||||||
|
"example.org", "[\"example.org\"]", "2026-01-01T00:00:00Z",
|
||||||
|
"2026-01-01T00:00:01Z", TRUE, 0, "completed", stdout_raw,
|
||||||
|
stderr_raw,
|
||||||
|
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
|
||||||
|
&error
|
||||||
|
);
|
||||||
|
g_assert_nonnull(record);
|
||||||
|
g_assert_true(osint_execution_dao_insert(dao, record, &error));
|
||||||
|
loaded = osint_execution_dao_find_by_identifier(
|
||||||
|
dao, "eeeeeeee-eeee-4eee-8eee-eeeeeeeeeeee", &error
|
||||||
|
);
|
||||||
|
g_assert_nonnull(loaded);
|
||||||
|
g_assert_cmpstr(
|
||||||
|
osint_execution_record_get_target_value(loaded), ==, "example.org"
|
||||||
|
);
|
||||||
|
g_assert_true(osint_execution_record_has_exit_code(loaded));
|
||||||
|
loaded_stdout = osint_execution_record_ref_stdout(loaded);
|
||||||
|
g_assert_true(g_bytes_equal(stdout_raw, loaded_stdout));
|
||||||
|
|
||||||
|
g_bytes_unref(loaded_stdout);
|
||||||
|
osint_execution_record_free(loaded);
|
||||||
|
osint_execution_record_free(record);
|
||||||
|
osint_execution_dao_free(dao);
|
||||||
|
database_close(database);
|
||||||
|
g_bytes_unref(stdout_raw);
|
||||||
|
g_bytes_unref(stderr_raw);
|
||||||
|
g_assert_cmpint(g_remove(path), ==, 0);
|
||||||
|
g_assert_cmpint(g_rmdir(directory), ==, 0);
|
||||||
|
g_free(path);
|
||||||
|
g_free(directory);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_invalid_model(void)
|
||||||
|
{
|
||||||
|
GError *error = NULL;
|
||||||
|
GBytes *empty = g_bytes_new_static("", 0U);
|
||||||
|
g_assert_null(osint_execution_record_new(
|
||||||
|
"invalid", "dns.dig", NULL, "dns-preview", "invalid", "entity",
|
||||||
|
"example.org", "[]", "bad", "bad", FALSE, -1, "completed",
|
||||||
|
empty, empty, "bad", &error
|
||||||
|
));
|
||||||
|
g_assert_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT);
|
||||||
|
g_clear_error(&error);
|
||||||
|
g_bytes_unref(empty);
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char **argv)
|
||||||
|
{
|
||||||
|
g_test_init(&argc, &argv, NULL);
|
||||||
|
g_test_add_func("/osint-execution-dao/raw", test_insert_and_read_raw_execution);
|
||||||
|
g_test_add_func("/osint-execution-record/invalid", test_invalid_model);
|
||||||
|
return g_test_run();
|
||||||
|
}
|
||||||
|
|
@ -8,6 +8,7 @@
|
||||||
|
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
#include <glib.h>
|
#include <glib.h>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -663,6 +664,31 @@ static void test_column_int64(void)
|
||||||
database_close(database);
|
database_close(database);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Vérifie la conservation exacte d'un paramètre BLOB.
|
||||||
|
*/
|
||||||
|
static void test_bind_and_read_blob(void)
|
||||||
|
{
|
||||||
|
const guint8 expected_data[] = {0x00U, 0x41U, 0xFFU, 0x0AU};
|
||||||
|
Database *database = database_open(":memory:");
|
||||||
|
DatabaseStatement *statement = database_statement_prepare(database, "SELECT ?;");
|
||||||
|
GBytes *input = g_bytes_new_static(expected_data, sizeof(expected_data));
|
||||||
|
GBytes *output = NULL;
|
||||||
|
gconstpointer output_data = NULL;
|
||||||
|
gsize output_size = 0U;
|
||||||
|
|
||||||
|
assert(database_statement_bind_blob(statement, 1, input));
|
||||||
|
assert(database_statement_step(statement) == DATABASE_STATEMENT_STEP_ROW);
|
||||||
|
assert(database_statement_column_blob(statement, 0, &output));
|
||||||
|
output_data = g_bytes_get_data(output, &output_size);
|
||||||
|
assert(output_size == sizeof(expected_data));
|
||||||
|
assert(memcmp(output_data, expected_data, sizeof(expected_data)) == 0);
|
||||||
|
g_bytes_unref(output);
|
||||||
|
g_bytes_unref(input);
|
||||||
|
database_statement_finalize(statement);
|
||||||
|
database_close(database);
|
||||||
|
}
|
||||||
|
|
||||||
int main(void)
|
int main(void)
|
||||||
{
|
{
|
||||||
test_prepare_valid_statement();
|
test_prepare_valid_statement();
|
||||||
|
|
@ -680,6 +706,7 @@ int main(void)
|
||||||
test_column_is_null();
|
test_column_is_null();
|
||||||
test_column_text();
|
test_column_text();
|
||||||
test_column_int64();
|
test_column_int64();
|
||||||
|
test_bind_and_read_blob();
|
||||||
|
|
||||||
printf(
|
printf(
|
||||||
"DatabaseStatement : tous les tests sont valides.\n"
|
"DatabaseStatement : tous les tests sont valides.\n"
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue