diff --git a/Makefile b/Makefile index c365ccf..f20d3a4 100644 --- a/Makefile +++ b/Makefile @@ -565,7 +565,9 @@ $(TEST_OSINT_DNS_INTEGRATION): \ src/models/osint_dns_proposal.c \ src/models/osint_dns_query.c \ src/models/entity_record.c \ + src/models/relation_record.c \ src/dao/entity_dao.c \ + src/dao/relation_dao.c \ src/database/database.c \ src/database/schema.c \ src/database/statement.c \ diff --git a/README.md b/README.md index b4b69be..d6844d1 100644 --- a/README.md +++ b/README.md @@ -149,7 +149,9 @@ Le socle actuel comprend notamment : - affichage sélectionnable des sorties standard et d’erreur, sans persistance ; - révision des réponses DNS sous forme de propositions avant intégration ; - 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 + `uses_name_server` depuis l'entité interrogée. Les outils actuellement présents dans le catalogue initial sont : diff --git a/docs/database/DATABASE_ARCHITECTURE.md b/docs/database/DATABASE_ARCHITECTURE.md index d092b71..dacf3a8 100644 --- a/docs/database/DATABASE_ARCHITECTURE.md +++ b/docs/database/DATABASE_ARCHITECTURE.md @@ -1109,8 +1109,10 @@ enregistrées. ### Limite actuelle de la traçabilité OSINT L'intégration DNS initiale peut créer des entités `domain_name` et -`ip_address`. La description de l'entité conserve une indication minimale : -outil `dns.dig`, cible interrogée et type d'enregistrement. +`ip_address`, ainsi que les relations `resolves_to`, `aliases_to` et +`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 diff --git a/include/core/osint_dns_integration.h b/include/core/osint_dns_integration.h index 901484b..5485c70 100644 --- a/include/core/osint_dns_integration.h +++ b/include/core/osint_dns_integration.h @@ -32,25 +32,31 @@ typedef enum GQuark osint_dns_integration_error_quark(void); /** - * @brief Intègre une sélection de propositions dans une transaction unique. + * @brief Intègre des propositions et leurs relations dans une transaction. * * Les propositions doivent être des OsintDnsProposal empruntées. Les valeurs * incompatibles sont ignorées. Les couples type/valeur déjà présents sont * comptés comme doublons et ne sont pas réinsérés. * * @param database Connexion SQLite active. + * @param source_entity_identifier UUID de l'entité interrogée. * @param selected_proposals Propositions explicitement sélectionnées. - * @param out_inserted_count Nombre d'entités créées. - * @param out_skipped_count Nombre de propositions ignorées ou déjà présentes. + * @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_inserted_relation_count Nombre de relations créées. + * @param out_skipped_relation_count Nombre de relations ignorées. * @param error Emplacement facultatif recevant une erreur. * * @return TRUE lorsque la transaction est validée, sinon FALSE. */ gboolean osint_dns_integration_apply( Database *database, + const char *source_entity_identifier, GPtrArray *selected_proposals, - guint *out_inserted_count, - guint *out_skipped_count, + guint *out_inserted_entity_count, + guint *out_skipped_entity_count, + guint *out_inserted_relation_count, + guint *out_skipped_relation_count, GError **error ); diff --git a/include/models/osint_dns_proposal.h b/include/models/osint_dns_proposal.h index 40834cd..64a0133 100644 --- a/include/models/osint_dns_proposal.h +++ b/include/models/osint_dns_proposal.h @@ -104,6 +104,20 @@ char *osint_dns_proposal_dup_normalized_value( const OsintDnsProposal *proposal ); +/** + * @brief Retourne le type de relation correspondant au type DNS. + * + * A, AAAA et PTR utilisent `resolves_to`, CNAME utilise `aliases_to` et NS + * utilise `uses_name_server`. + * + * @param proposal Proposition consultée. + * + * @return Identifiant statique de relation, ou NULL. + */ +const char *osint_dns_proposal_get_relation_type( + const OsintDnsProposal *proposal +); + G_END_DECLS #endif diff --git a/include/views/main_window.h b/include/views/main_window.h index a1a9b20..cd992cc 100644 --- a/include/views/main_window.h +++ b/include/views/main_window.h @@ -125,11 +125,13 @@ typedef void (*MainWindowAddRelationCallback)( * @brief Callback appelé lors du déclenchement d'une action OSINT. * * @param action_identifier Identifiant stable de l'action. + * @param target_identifier UUID de la sélection ciblée. * @param target_value Valeur métier ciblée. * @param user_data Données privées fournies par l'appelant. */ typedef void (*MainWindowOsintActionCallback)( const char *action_identifier, + const char *target_identifier, const char *target_value, gpointer user_data ); diff --git a/include/widgets/workspace.h b/include/widgets/workspace.h index 8283101..5fb7b29 100644 --- a/include/widgets/workspace.h +++ b/include/widgets/workspace.h @@ -84,11 +84,13 @@ typedef void (*WorkspaceAddRelationCallback)( * Les chaînes sont empruntées et uniquement valides pendant l'appel. * * @param action_identifier Identifiant stable de l'action. + * @param target_identifier UUID de la sélection ciblée. * @param target_value Valeur métier ciblée par l'action. * @param user_data Données empruntées fournies par l'appelant. */ typedef void (*WorkspaceOsintActionCallback)( const char *action_identifier, + const char *target_identifier, const char *target_value, gpointer user_data ); diff --git a/src/core/application.c b/src/core/application.c index 0e18ec9..366e72c 100644 --- a/src/core/application.c +++ b/src/core/application.c @@ -162,6 +162,7 @@ typedef struct typedef struct { Application *application; + char *target_identifier; char *target_value; } ApplicationOsintActionContext; @@ -169,6 +170,7 @@ typedef struct typedef struct { Application *application; + char *source_entity_identifier; GPtrArray *proposals; } ApplicationOsintReviewContext; @@ -189,6 +191,7 @@ static void application_osint_review_context_free(gpointer user_data) ApplicationOsintReviewContext *context = user_data; if (context == NULL) return; g_clear_pointer(&context->proposals, g_ptr_array_unref); + g_free(context->source_entity_identifier); g_free(context); } @@ -198,16 +201,21 @@ static void application_on_osint_integration_confirmed( gpointer user_data ) { - Application *application = user_data; + ApplicationOsintReviewContext *review_context = user_data; + Application *application = review_context != NULL + ? review_context->application : NULL; const InvestigationProject *project = NULL; Database *database = NULL; const char *database_path = NULL; guint inserted_count = 0U; guint skipped_count = 0U; + guint inserted_relation_count = 0U; + guint skipped_relation_count = 0U; char *message = NULL; GError *error = NULL; - if (application == NULL || application->session == NULL || + if (application == NULL || review_context->source_entity_identifier == NULL || + application->session == NULL || application->main_window == NULL) { return; @@ -218,8 +226,9 @@ static void application_on_osint_integration_confirmed( database_path = project != NULL ? investigation_project_get_database_path(project) : NULL; if (!osint_dns_integration_apply( - database, selected_proposals, &inserted_count, &skipped_count, - &error + database, review_context->source_entity_identifier, + selected_proposals, &inserted_count, &skipped_count, + &inserted_relation_count, &skipped_relation_count, &error )) { application_present_error( @@ -233,10 +242,12 @@ static void application_on_osint_integration_confirmed( } message = g_strdup_printf( - "%u entité(s) créée(s), %u proposition(s) ignorée(s).\n\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" "La provenance complète devra être ajoutée par une future migration " "du schéma OSINT.", - inserted_count, skipped_count + inserted_count, skipped_count, + inserted_relation_count, skipped_relation_count ); application_message_dialog_present( main_window_get_window(application->main_window), @@ -260,7 +271,7 @@ static void application_on_osint_prepare_integration(gpointer user_data) main_window_get_window(context->application->main_window), context->proposals, application_on_osint_integration_confirmed, - context->application + context ); } @@ -279,6 +290,7 @@ static void application_osint_action_context_free( } g_free(context->target_value); + g_free(context->target_identifier); g_free(context); } @@ -1178,9 +1190,13 @@ static void application_on_dns_lookup_completed( if (review_context != NULL) { review_context->application = application; + review_context->source_entity_identifier = g_strdup( + context->target_identifier + ); 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) { application_osint_review_context_free(review_context); review_context = NULL; @@ -1224,6 +1240,7 @@ static void application_on_dns_lookup_completed( */ static void application_start_dns_lookup( Application *application, + const char *target_identifier, const char *target_value ) { @@ -1236,6 +1253,7 @@ static void application_start_dns_lookup( if (application == NULL || application->task_manager == NULL || application->tool_initializer == NULL || target_value == NULL || + target_identifier == NULL || !g_uuid_string_is_valid(target_identifier) || !osint_dns_query_is_valid_target(target_value)) { if (application != NULL) @@ -1274,10 +1292,12 @@ static void application_start_dns_lookup( if (context != NULL) { context->application = application; + context->target_identifier = g_strdup(target_identifier); context->target_value = g_strdup(target_value); } - if (context == NULL || context->target_value == NULL) + if (context == NULL || context->target_identifier == NULL || + context->target_value == NULL) { application_osint_action_context_free(context); tool_task_free(tool_task); @@ -1319,6 +1339,7 @@ static void application_start_dns_lookup( */ static void application_on_osint_action_requested( const char *action_identifier, + const char *target_identifier, const char *target_value, gpointer user_data ) @@ -1327,7 +1348,9 @@ static void application_on_osint_action_requested( if (g_strcmp0(action_identifier, "dns-preview") == 0) { - application_start_dns_lookup(application, target_value); + application_start_dns_lookup( + application, target_identifier, target_value + ); return; } diff --git a/src/core/osint_dns_integration.c b/src/core/osint_dns_integration.c index 6078eb8..6844b92 100644 --- a/src/core/osint_dns_integration.c +++ b/src/core/osint_dns_integration.c @@ -6,10 +6,12 @@ #include "core/osint_dns_integration.h" #include "dao/entity_dao.h" +#include "dao/relation_dao.h" #include "database/error.h" #include "database/transaction.h" #include "models/entity_record.h" #include "models/osint_dns_proposal.h" +#include "models/relation_record.h" #include #include @@ -50,6 +52,18 @@ static char *osint_dns_integration_build_key( return g_strdup_printf("%s\n%s", entity_type, value); } +/** @brief Construit la clé anti-doublon d'une relation orientée. */ +static char *osint_dns_integration_build_relation_key( + const char *source_identifier, + const char *target_identifier, + const char *relation_type +) +{ + return g_strdup_printf( + "%s\n%s\n%s", source_identifier, target_identifier, relation_type + ); +} + /** @brief Renseigne une erreur littérale si possible. */ static void osint_dns_integration_set_error( GError **error, @@ -68,25 +82,38 @@ GQuark osint_dns_integration_error_quark(void) gboolean osint_dns_integration_apply( Database *database, + const char *source_entity_identifier, GPtrArray *selected_proposals, - guint *out_inserted_count, - guint *out_skipped_count, + guint *out_inserted_entity_count, + guint *out_skipped_entity_count, + guint *out_inserted_relation_count, + guint *out_skipped_relation_count, GError **error ) { EntityDao *entity_dao = NULL; + RelationDao *relation_dao = NULL; GPtrArray *existing_entities = NULL; + GPtrArray *existing_relations = NULL; GHashTable *known_entities = NULL; + GHashTable *known_relations = NULL; + EntityRecord *source_entity = NULL; GDateTime *now = NULL; char *timestamp = NULL; - guint inserted_count = 0U; - guint skipped_count = 0U; + guint inserted_entity_count = 0U; + guint skipped_entity_count = 0U; + guint inserted_relation_count = 0U; + guint skipped_relation_count = 0U; gboolean success = FALSE; g_return_val_if_fail(error == NULL || *error == NULL, FALSE); - if (out_inserted_count != NULL) *out_inserted_count = 0U; - if (out_skipped_count != NULL) *out_skipped_count = 0U; - if (database == NULL || selected_proposals == NULL || + if (out_inserted_entity_count != NULL) *out_inserted_entity_count = 0U; + if (out_skipped_entity_count != NULL) *out_skipped_entity_count = 0U; + if (out_inserted_relation_count != NULL) *out_inserted_relation_count = 0U; + if (out_skipped_relation_count != NULL) *out_skipped_relation_count = 0U; + if (database == NULL || source_entity_identifier == NULL || + !g_uuid_string_is_valid(source_entity_identifier) || + selected_proposals == NULL || selected_proposals->len == 0U) { osint_dns_integration_set_error( @@ -98,9 +125,27 @@ gboolean osint_dns_integration_apply( entity_dao = entity_dao_new(database, error); if (entity_dao == NULL) return FALSE; + relation_dao = relation_dao_new(database, error); + if (relation_dao == NULL) goto cleanup; + source_entity = entity_dao_find_by_identifier( + entity_dao, source_entity_identifier, error + ); + if (source_entity == NULL) + { + if (error == NULL || *error == NULL) + osint_dns_integration_set_error( + error, OSINT_DNS_INTEGRATION_ERROR_INVALID_ARGUMENT, + "L'entité source de la résolution DNS est introuvable." + ); + goto cleanup; + } existing_entities = entity_dao_list_all(entity_dao, error); if (existing_entities == NULL) goto cleanup; - known_entities = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL); + existing_relations = relation_dao_list_all(relation_dao, error); + if (existing_relations == NULL) goto cleanup; + known_entities = g_hash_table_new_full( + g_str_hash, g_str_equal, g_free, g_free + ); for (guint index = 0; index < existing_entities->len; index++) { const EntityRecord *entity = g_ptr_array_index(existing_entities, index); @@ -108,15 +153,33 @@ gboolean osint_dns_integration_apply( char *normalized_value = osint_dns_integration_normalize_existing_value( entity_type, entity_record_get_value(entity) ); - g_hash_table_add( + g_hash_table_insert( known_entities, osint_dns_integration_build_key( entity_type, normalized_value - ) + ), + g_strdup(entity_record_get_identifier(entity)) ); g_free(normalized_value); } + known_relations = g_hash_table_new_full( + g_str_hash, g_str_equal, g_free, NULL + ); + for (guint index = 0; index < existing_relations->len; index++) + { + const RelationRecord *relation = g_ptr_array_index( + existing_relations, index + ); + g_hash_table_add( + known_relations, + osint_dns_integration_build_relation_key( + relation_record_get_source_entity_identifier(relation), + relation_record_get_target_entity_identifier(relation), + relation_record_get_relation_type(relation) + ) + ); + } now = g_date_time_new_now_utc(); timestamp = now != NULL @@ -146,60 +209,131 @@ gboolean osint_dns_integration_apply( selected_proposals, index ); const char *entity_type = osint_dns_proposal_get_entity_type(proposal); + const char *relation_type = + osint_dns_proposal_get_relation_type(proposal); char *normalized_value = osint_dns_proposal_dup_normalized_value(proposal); char *entity_key = NULL; - char *identifier = NULL; + char *target_identifier = NULL; char *description = NULL; EntityRecord *entity = NULL; + RelationRecord *relation = NULL; + char *relation_identifier = NULL; + char *relation_key = NULL; + char *relation_label = NULL; + char *relation_justification = NULL; GError *model_error = NULL; - if (entity_type == NULL || normalized_value == NULL) + if (entity_type == NULL || relation_type == NULL || + normalized_value == NULL) { - skipped_count++; + skipped_entity_count++; + skipped_relation_count++; g_free(normalized_value); continue; } entity_key = osint_dns_integration_build_key(entity_type, normalized_value); - if (g_hash_table_contains(known_entities, entity_key)) + target_identifier = g_strdup( + g_hash_table_lookup(known_entities, entity_key) + ); + if (target_identifier != NULL) { - skipped_count++; - g_free(entity_key); - g_free(normalized_value); - continue; + skipped_entity_count++; } - - identifier = g_uuid_string_random(); - description = g_strdup_printf( - "Proposition DNS %s obtenue avec dns.dig depuis %s. " - "Résultat OSINT à vérifier.", - osint_dns_proposal_get_record_type(proposal), - osint_dns_proposal_get_target(proposal) - ); - entity = entity_record_new( - identifier, entity_type, normalized_value, normalized_value, - description, 50, timestamp, timestamp, ENTITY_STATUS_ACTIVE, - &model_error - ); - if (entity == NULL || !entity_dao_insert(entity_dao, entity, error)) + else { - if (error != NULL && *error == NULL && model_error != NULL) - g_propagate_error(error, g_steal_pointer(&model_error)); + target_identifier = g_uuid_string_random(); + description = g_strdup_printf( + "Proposition DNS %s obtenue avec dns.dig depuis %s. " + "Résultat OSINT à vérifier.", + osint_dns_proposal_get_record_type(proposal), + osint_dns_proposal_get_target(proposal) + ); + entity = entity_record_new( + target_identifier, entity_type, normalized_value, + normalized_value, description, 50, timestamp, timestamp, + ENTITY_STATUS_ACTIVE, &model_error + ); + if (entity == NULL || !entity_dao_insert(entity_dao, entity, error)) + { + if (error != NULL && *error == NULL && model_error != NULL) + g_propagate_error(error, g_steal_pointer(&model_error)); + entity_record_free(entity); + g_clear_error(&model_error); + g_free(description); + g_free(target_identifier); + g_free(entity_key); + g_free(normalized_value); + database_transaction_rollback(database); + goto cleanup; + } + g_hash_table_insert( + known_entities, entity_key, g_strdup(target_identifier) + ); + entity_key = NULL; + inserted_entity_count++; entity_record_free(entity); g_clear_error(&model_error); g_free(description); - g_free(identifier); - g_free(entity_key); - g_free(normalized_value); - database_transaction_rollback(database); - goto cleanup; } - g_hash_table_add(known_entities, entity_key); - inserted_count++; - entity_record_free(entity); - g_clear_error(&model_error); - g_free(description); - g_free(identifier); + relation_key = osint_dns_integration_build_relation_key( + source_entity_identifier, target_identifier, relation_type + ); + if (g_strcmp0(source_entity_identifier, target_identifier) == 0 || + g_hash_table_contains(known_relations, relation_key)) + { + skipped_relation_count++; + } + else + { + relation_identifier = g_uuid_string_random(); + relation_label = g_strdup_printf( + "%s — %s → %s", + osint_dns_proposal_get_target(proposal), relation_type, + normalized_value + ); + relation_justification = g_strdup_printf( + "Relation DNS %s obtenue avec dns.dig depuis %s. " + "Résultat OSINT à vérifier.", + osint_dns_proposal_get_record_type(proposal), + osint_dns_proposal_get_target(proposal) + ); + relation = relation_record_new( + relation_identifier, source_entity_identifier, + target_identifier, relation_type, relation_label, + relation_justification, 50, timestamp, timestamp, + RELATION_STATUS_ACTIVE, &model_error + ); + if (relation == NULL || + !relation_dao_insert(relation_dao, relation, error)) + { + if (error != NULL && *error == NULL && model_error != NULL) + g_propagate_error(error, g_steal_pointer(&model_error)); + relation_record_free(relation); + g_clear_error(&model_error); + g_free(relation_identifier); + g_free(relation_label); + g_free(relation_justification); + g_free(relation_key); + g_free(target_identifier); + g_free(entity_key); + g_free(normalized_value); + database_transaction_rollback(database); + goto cleanup; + } + g_hash_table_add(known_relations, relation_key); + relation_key = NULL; + inserted_relation_count++; + relation_record_free(relation); + g_clear_error(&model_error); + g_free(relation_identifier); + g_free(relation_label); + g_free(relation_justification); + } + + g_free(relation_key); + g_free(target_identifier); + g_free(entity_key); g_free(normalized_value); } @@ -215,14 +349,24 @@ gboolean osint_dns_integration_apply( goto cleanup; } success = TRUE; - if (out_inserted_count != NULL) *out_inserted_count = inserted_count; - if (out_skipped_count != NULL) *out_skipped_count = skipped_count; + if (out_inserted_entity_count != NULL) + *out_inserted_entity_count = inserted_entity_count; + if (out_skipped_entity_count != NULL) + *out_skipped_entity_count = skipped_entity_count; + if (out_inserted_relation_count != NULL) + *out_inserted_relation_count = inserted_relation_count; + if (out_skipped_relation_count != NULL) + *out_skipped_relation_count = skipped_relation_count; cleanup: g_free(timestamp); g_clear_pointer(&now, g_date_time_unref); g_clear_pointer(&known_entities, g_hash_table_unref); + g_clear_pointer(&known_relations, g_hash_table_unref); g_clear_pointer(&existing_entities, g_ptr_array_unref); + g_clear_pointer(&existing_relations, g_ptr_array_unref); + entity_record_free(source_entity); + relation_dao_free(relation_dao); entity_dao_free(entity_dao); return success; } diff --git a/src/models/osint_dns_proposal.c b/src/models/osint_dns_proposal.c index 63bac6f..90946ef 100644 --- a/src/models/osint_dns_proposal.c +++ b/src/models/osint_dns_proposal.c @@ -210,3 +210,19 @@ char *osint_dns_proposal_dup_normalized_value( } return NULL; } + +const char *osint_dns_proposal_get_relation_type( + const OsintDnsProposal *proposal +) +{ + const char *record_type = osint_dns_proposal_get_record_type(proposal); + if (g_strcmp0(record_type, "A") == 0 || + g_strcmp0(record_type, "AAAA") == 0 || + g_strcmp0(record_type, "PTR") == 0) + return "resolves_to"; + if (g_strcmp0(record_type, "CNAME") == 0) + return "aliases_to"; + if (g_strcmp0(record_type, "NS") == 0) + return "uses_name_server"; + return NULL; +} diff --git a/src/views/main_window.c b/src/views/main_window.c index 4b279b4..ab01c13 100644 --- a/src/views/main_window.c +++ b/src/views/main_window.c @@ -136,6 +136,7 @@ struct MainWindow */ static void main_window_on_osint_action_requested( const char *action_identifier, + const char *target_identifier, const char *target_value, gpointer user_data ) @@ -149,6 +150,7 @@ static void main_window_on_osint_action_requested( main_window->osint_action_callback( action_identifier, + target_identifier, target_value, main_window->osint_action_user_data ); diff --git a/src/views/osint_dns_review_dialog.c b/src/views/osint_dns_review_dialog.c index d1aec0f..a9370c4 100644 --- a/src/views/osint_dns_review_dialog.c +++ b/src/views/osint_dns_review_dialog.c @@ -142,18 +142,25 @@ static GtkWidget *osint_dns_review_dialog_create_row( { GtkWidget *row = gtk_box_new(GTK_ORIENTATION_VERTICAL, 4); const char *entity_type = osint_dns_proposal_get_entity_type(proposal); + const char *relation_type = osint_dns_proposal_get_relation_type(proposal); char *normalized_value = osint_dns_proposal_dup_normalized_value(proposal); - const gboolean compatible = entity_type != NULL && normalized_value != NULL; + const gboolean compatible = entity_type != NULL && relation_type != NULL && + normalized_value != NULL; char *title = g_strdup_printf( "%s → %s", osint_dns_proposal_get_record_type(proposal), osint_dns_proposal_get_value(proposal) ); char *details = g_strdup_printf( - "Cible : %s\nPropriétaire : %s\nType Labfy : %s", + "Cible : %s\nPropriétaire : %s\nType Labfy : %s\n" + "Relation prévue : %s — %s → %s", osint_dns_proposal_get_target(proposal), osint_dns_proposal_get_owner(proposal), - compatible ? entity_type : "non pris en charge" + compatible ? entity_type : "non pris en charge", + osint_dns_proposal_get_target(proposal), + compatible ? relation_type : "non prise en charge", + normalized_value != NULL ? normalized_value + : osint_dns_proposal_get_value(proposal) ); GtkWidget *selection_button = gtk_check_button_new_with_label(title); GtkWidget *details_label = gtk_label_new(details); diff --git a/src/widgets/workspace.c b/src/widgets/workspace.c index 63c112a..5d943d4 100644 --- a/src/widgets/workspace.c +++ b/src/widgets/workspace.c @@ -147,6 +147,7 @@ static void workspace_on_osint_action_clicked( "osint-action-identifier" ); const char *target_value = NULL; + const char *target_identifier = NULL; if (workspace == NULL || workspace->osint_tools_status_label == NULL) { @@ -165,9 +166,13 @@ static void workspace_on_osint_action_clicked( target_value = osint_selection_context_get_value( workspace->osint_selection_context ); + target_identifier = osint_selection_context_get_identifier( + workspace->osint_selection_context + ); if (workspace->osint_action_callback == NULL || - action_identifier == NULL || target_value == NULL) + action_identifier == NULL || target_identifier == NULL || + target_value == NULL) { gtk_label_set_text( GTK_LABEL(workspace->osint_tools_status_label), @@ -187,6 +192,7 @@ static void workspace_on_osint_action_clicked( workspace->osint_action_callback( action_identifier, + target_identifier, target_value, workspace->osint_action_user_data ); diff --git a/tests/test_osint_dns_integration.c b/tests/test_osint_dns_integration.c index b7e6d7c..6f3a8c0 100644 --- a/tests/test_osint_dns_integration.c +++ b/tests/test_osint_dns_integration.c @@ -6,8 +6,10 @@ #include "core/osint_dns_integration.h" #include "dao/entity_dao.h" +#include "dao/relation_dao.h" #include "database/database.h" #include "models/osint_dns_proposal.h" +#include "models/relation_record.h" #include #include @@ -19,14 +21,26 @@ static void test_integration_and_duplicates(void) char *database_path = g_build_filename(directory, "Enquete.sqlite", NULL); Database *database = NULL; EntityDao *entity_dao = NULL; + RelationDao *relation_dao = NULL; + EntityRecord *source = NULL; GPtrArray *proposals = NULL; GPtrArray *entities = NULL; + GPtrArray *relations = NULL; guint inserted = 0U; guint skipped = 0U; + guint inserted_relations = 0U; + guint skipped_relations = 0U; g_assert_no_error(error); g_assert_true(database_initialize(database_path, "DNS", directory)); database = database_open(database_path); + entity_dao = entity_dao_new(database, &error); + source = entity_record_new( + "11111111-1111-4111-8111-111111111111", "domain_name", + "example.org", NULL, NULL, 50, "2026-01-01T00:00:00Z", + "2026-01-01T00:00:00Z", ENTITY_STATUS_ACTIVE, &error + ); + g_assert_true(entity_dao_insert(entity_dao, source, &error)); proposals = osint_dns_proposal_parse( "example.org", "example.org. 60 IN A 192.0.2.10\n" @@ -34,22 +48,33 @@ static void test_integration_and_duplicates(void) "example.org. 60 IN MX 10 mail.example.org.\n" ); g_assert_true(osint_dns_integration_apply( - database, proposals, &inserted, &skipped, &error + database, "11111111-1111-4111-8111-111111111111", proposals, + &inserted, &skipped, &inserted_relations, &skipped_relations, &error )); g_assert_no_error(error); g_assert_cmpuint(inserted, ==, 2); g_assert_cmpuint(skipped, ==, 1); + g_assert_cmpuint(inserted_relations, ==, 2); + g_assert_cmpuint(skipped_relations, ==, 1); g_assert_true(osint_dns_integration_apply( - database, proposals, &inserted, &skipped, &error + database, "11111111-1111-4111-8111-111111111111", proposals, + &inserted, &skipped, &inserted_relations, &skipped_relations, &error )); g_assert_cmpuint(inserted, ==, 0); g_assert_cmpuint(skipped, ==, 3); - entity_dao = entity_dao_new(database, &error); + g_assert_cmpuint(inserted_relations, ==, 0); + g_assert_cmpuint(skipped_relations, ==, 3); entities = entity_dao_list_all(entity_dao, &error); - g_assert_cmpuint(entities->len, ==, 2); + g_assert_cmpuint(entities->len, ==, 3); + relation_dao = relation_dao_new(database, &error); + relations = relation_dao_list_all(relation_dao, &error); + g_assert_cmpuint(relations->len, ==, 2); + g_ptr_array_unref(relations); + relation_dao_free(relation_dao); g_ptr_array_unref(entities); + entity_record_free(source); entity_dao_free(entity_dao); g_ptr_array_unref(proposals); database_close(database); @@ -63,7 +88,7 @@ static void test_invalid_arguments(void) { GError *error = NULL; g_assert_false(osint_dns_integration_apply( - NULL, NULL, NULL, NULL, &error + NULL, NULL, NULL, NULL, NULL, NULL, NULL, &error )); g_assert_error( error, OSINT_DNS_INTEGRATION_ERROR, @@ -83,6 +108,8 @@ static void test_normalized_existing_domain_is_skipped(void) GPtrArray *proposals = NULL; guint inserted = 0U; guint skipped = 0U; + guint inserted_relations = 0U; + guint skipped_relations = 0U; g_assert_true(database_initialize(database_path, "DNS", directory)); database = database_open(database_path); @@ -97,10 +124,13 @@ static void test_normalized_existing_domain_is_skipped(void) "example.org", "example.org. 60 IN CNAME www.example.org.\n" ); g_assert_true(osint_dns_integration_apply( - database, proposals, &inserted, &skipped, &error + database, "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa", proposals, + &inserted, &skipped, &inserted_relations, &skipped_relations, &error )); g_assert_cmpuint(inserted, ==, 0); g_assert_cmpuint(skipped, ==, 1); + g_assert_cmpuint(inserted_relations, ==, 0); + g_assert_cmpuint(skipped_relations, ==, 1); g_ptr_array_unref(proposals); entity_record_free(existing); diff --git a/tests/test_osint_dns_proposal.c b/tests/test_osint_dns_proposal.c index d86a75e..6003e8f 100644 --- a/tests/test_osint_dns_proposal.c +++ b/tests/test_osint_dns_proposal.c @@ -67,10 +67,17 @@ static void test_entity_mapping_and_normalization(void) g_assert_cmpstr(osint_dns_proposal_get_entity_type(ipv4), ==, "ip_address"); g_assert_cmpstr(ipv4_value, ==, "192.0.2.10"); + g_assert_cmpstr( + osint_dns_proposal_get_relation_type(ipv4), ==, "resolves_to" + ); g_assert_cmpstr(ipv6_value, ==, "2001:db8::1"); g_assert_cmpstr(osint_dns_proposal_get_entity_type(domain), ==, "domain_name"); g_assert_cmpstr(domain_value, ==, "www.example.org"); + g_assert_cmpstr( + osint_dns_proposal_get_relation_type(domain), ==, "aliases_to" + ); g_assert_null(osint_dns_proposal_get_entity_type(unsupported)); + g_assert_null(osint_dns_proposal_get_relation_type(unsupported)); g_assert_null(osint_dns_proposal_dup_normalized_value(unsupported)); g_free(ipv4_value);