exchange

Base system with REST service to issue digital coins, run by the payment service provider
Log | Files | Refs | Submodules | README | LICENSE

commit 08387dd6adfd7abb4db184c2f7d663fcaab78eb2
parent 4d22b01dd79434c94301e71697a1d5c9ab5a8d6d
Author: Christian Grothoff <christian@grothoff.org>
Date:   Thu, 26 Feb 2026 23:24:02 +0100

work a bit on API modernization

Diffstat:
Msrc/exchange-tools/taler-exchange-kyc-trigger.c | 50+++++++++++++++++++++++++++-----------------------
Msrc/include/taler/taler-exchange/get-aml-OFFICER_PUB-decisions.h | 263+------------------------------------------------------------------------------
Msrc/include/taler/taler-exchange/get-aml-OFFICER_PUB-kyc-statistics-NAMES.h | 55-------------------------------------------------------
Msrc/include/taler/taler-exchange/get-aml-OFFICER_PUB-legitimizations.h | 22++++------------------
Msrc/include/taler/taler-exchange/get-aml-OFFICER_PUB-measures.h | 94-------------------------------------------------------------------------------
Msrc/include/taler/taler-exchange/get-kyc-check-H_NORMALIZED_PAYTO.h | 57---------------------------------------------------------
Msrc/include/taler/taler-exchange/get-kyc-info-ACCESS_TOKEN.h | 57---------------------------------------------------------
Msrc/include/taler/taler-exchange/get-kyc-proof-PROVIDER_NAME.h | 53-----------------------------------------------------
Msrc/include/taler/taler-exchange/post-aml-OFFICER_PUB-decision.h | 70+---------------------------------------------------------------------
Msrc/include/taler/taler-exchange/post-kyc-start-ID.h | 50--------------------------------------------------
Msrc/include/taler/taler-exchange/post-kyc-wallet.h | 53-----------------------------------------------------
Msrc/include/taler/taler-exchange/post-purses-PURSE_PUB-create.h | 3+--
Msrc/include/taler/taler-exchange/post-reserves-RESERVE_PUB-close.h | 3+--
Msrc/include/taler/taler-exchange/post-reserves-RESERVE_PUB-purse.h | 3+--
Msrc/lib/Makefile.am | 2+-
Msrc/lib/exchange_api_get-aml-OFFICER_PUB-attributes-H_NORMALIZED_PAYTO.c | 340+++++++++++++++++++++++++++++++++++++++++++++++++------------------------------
Msrc/lib/exchange_api_get-aml-OFFICER_PUB-decisions.c | 701++++++++++++++++++++++++++++++++++++++++++++++---------------------------------
Msrc/lib/exchange_api_get-aml-OFFICER_PUB-kyc-statistics-NAMES.c | 326++++++++++++++++++++++++++++++++++++++++++++++++++++++-------------------------
Msrc/lib/exchange_api_get-aml-OFFICER_PUB-legitimizations.c | 31+++++++++++++++++--------------
Msrc/lib/exchange_api_get-aml-OFFICER_PUB-measures.c | 542++++++++++++++++++++++++++++++++++++++++++++++---------------------------------
Msrc/lib/exchange_api_get-kyc-check-H_NORMALIZED_PAYTO.c | 241+++++++++++++++++++++++++++++++++++++++++++++++++++++++------------------------
Msrc/lib/exchange_api_get-kyc-info-ACCESS_TOKEN.c | 241+++++++++++++++++++++++++++++++++++++++++++++++--------------------------------
Msrc/lib/exchange_api_get-kyc-proof-PROVIDER_NAME.c | 212+++++++++++++++++++++++++++++++++++++++++++++++++++++--------------------------
Msrc/lib/exchange_api_post-aml-OFFICER_PUB-decision.c | 462+++++++++++++++++++++++++++++++++++++++++++++++++++++++------------------------
Msrc/lib/exchange_api_post-kyc-start-ID.c | 150++++++++++++++++++++++++++++++++++++++++++++++---------------------------------
Msrc/lib/exchange_api_post-kyc-wallet.c | 159++++++++++++++++++++++++++++++++++++++++++++++++++++---------------------------
Msrc/testing/Makefile.am | 2+-
Msrc/testing/testing_api_cmd_check_aml_decisions.c | 64+++++++++++++++++++++++++++++++++++++++++++---------------------
Msrc/testing/testing_api_cmd_get_active_legitimization_measures.c | 2+-
Msrc/testing/testing_api_cmd_get_kyc_info.c | 44+++++++++++++++++++++++++++++++-------------
Msrc/testing/testing_api_cmd_kyc_check_get.c | 53+++++++++++++++++++++++++++++++++++++----------------
Msrc/testing/testing_api_cmd_kyc_proof.c | 63+++++++++++++++++++++++++++++++++++++++++++++------------------
Msrc/testing/testing_api_cmd_kyc_wallet_get.c | 43++++++++++++++++++++++++++++++++-----------
Msrc/testing/testing_api_cmd_post_kyc_start.c | 42+++++++++++++++++++++++++++++++-----------
Msrc/testing/testing_api_cmd_take_aml_decision.c | 112++++++++++++++++++++++++++++++++++++++++++++++---------------------------------
35 files changed, 2472 insertions(+), 2193 deletions(-)

diff --git a/src/exchange-tools/taler-exchange-kyc-trigger.c b/src/exchange-tools/taler-exchange-kyc-trigger.c @@ -23,6 +23,7 @@ #include <gnunet/gnunet_util_lib.h> #include <microhttpd.h> #include "taler/taler_json_lib.h" +#include "taler/taler-exchange/get-kyc-check-H_NORMALIZED_PAYTO.h" #include "taler/taler_exchange_service.h" @@ -54,12 +55,12 @@ static const struct GNUNET_CONFIGURATION_Handle *kcfg; /** * Handle for exchange interaction. */ -static struct TALER_EXCHANGE_KycWalletHandle *kwh; +static struct TALER_EXCHANGE_PostKycWalletHandle *kwh; /** * Handle for exchange kyc-check interaction. */ -static struct TALER_EXCHANGE_KycCheckHandle *kc; +static struct TALER_EXCHANGE_GetKycCheckHandle *kc; /** * Balance threshold to report to the exchange. @@ -92,7 +93,7 @@ static char *CFG_exchange_url; static void kyc_status_cb ( void *cls, - const struct TALER_EXCHANGE_KycStatus *ks) + const struct TALER_EXCHANGE_GetKycCheckResponse *ks) { kc = NULL; switch (ks->hr.http_status) @@ -144,7 +145,7 @@ kyc_status_cb ( static void kyc_wallet_cb ( void *cls, - const struct TALER_EXCHANGE_WalletKycResponse *ks) + const struct TALER_EXCHANGE_PostKycWalletResponse *ks) { kwh = NULL; switch (ks->hr.http_status) @@ -184,18 +185,19 @@ kyc_wallet_cb ( pk.reserve_priv = reserve_priv; GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Requesting /kyc-check to determine KYC entrypoint\n"); - kc = TALER_EXCHANGE_kyc_check (ctx, - CFG_exchange_url, - &h_payto, - &pk, - 0, - TALER_EXCHANGE_KLPT_NONE, - GNUNET_TIME_UNIT_ZERO, - &kyc_status_cb, - NULL); + kc = TALER_EXCHANGE_get_kyc_check_create (ctx, + CFG_exchange_url, + &h_payto, + &pk); GNUNET_break (NULL != kc); - if (NULL != kc) - return; + if (NULL == kc) + break; + GNUNET_assert (TALER_EC_NONE == + TALER_EXCHANGE_get_kyc_check_start ( + kc, + &kyc_status_cb, + NULL)); + return; } } break; @@ -220,12 +222,12 @@ do_shutdown (void *cls) (void) cls; if (NULL != kwh) { - TALER_EXCHANGE_kyc_wallet_cancel (kwh); + TALER_EXCHANGE_post_kyc_wallet_cancel (kwh); kwh = NULL; } if (NULL != kc) { - TALER_EXCHANGE_kyc_check_cancel (kc); + TALER_EXCHANGE_get_kyc_check_cancel (kc); kc = NULL; } if (NULL != ctx) @@ -355,17 +357,19 @@ run (void *cls, rc = GNUNET_CURL_gnunet_rc_create (ctx); GNUNET_SCHEDULER_add_shutdown (&do_shutdown, NULL); - kwh = TALER_EXCHANGE_kyc_wallet (ctx, - CFG_exchange_url, - &reserve_priv, - &balance, - &kyc_wallet_cb, - NULL); + kwh = TALER_EXCHANGE_post_kyc_wallet_create (ctx, + CFG_exchange_url, + &reserve_priv, + &balance); if (NULL == kwh) { GNUNET_break (0); GNUNET_SCHEDULER_shutdown (); } + GNUNET_assert (TALER_EC_NONE == + TALER_EXCHANGE_post_kyc_wallet_start (kwh, + &kyc_wallet_cb, + NULL)); } diff --git a/src/include/taler/taler-exchange/get-aml-OFFICER_PUB-decisions.h b/src/include/taler/taler-exchange/get-aml-OFFICER_PUB-decisions.h @@ -201,9 +201,9 @@ struct TALER_EXCHANGE_GetAmlDecisionsLegitimizationRuleSet size_t rules_length; /** - * Array of KYC rules. + * Array of KYC rules. NOT allocated here! */ - struct TALER_EXCHANGE_GetAmlDecisionsKycRule *rules; + const struct TALER_EXCHANGE_GetAmlDecisionsKycRule *rules; /** * Custom measures. @@ -422,8 +422,7 @@ enum GNUNET_GenericReturnValue TALER_EXCHANGE_get_aml_decisions_set_options_ ( struct TALER_EXCHANGE_GetAmlDecisionsHandle *adgh, unsigned int num_options, - struct TALER_EXCHANGE_GetAmlDecisionsOptionValue options[ - static num_options]); + const struct TALER_EXCHANGE_GetAmlDecisionsOptionValue options[]); /** @@ -503,260 +502,4 @@ TALER_EXCHANGE_get_aml_decisions_cancel ( struct TALER_EXCHANGE_GetAmlDecisionsHandle *adgh); -/* **************** deprecated legacy API ***************** */ - - -/** - * KYC rule that determines limits for an account. - */ -struct TALER_EXCHANGE_KycRule -{ - /** - * Type of operation to which the rule applies. - */ - enum TALER_KYCLOGIC_KycTriggerEvent operation_type; - - /** - * The measures will be taken if the given - * threshold is crossed over the given timeframe. - */ - struct TALER_Amount threshold; - - /** - * Over which duration should the @e threshold be - * computed. All amounts of the respective - * @e operation_type will be added up for this - * duration and the sum compared to the @e threshold. - */ - struct GNUNET_TIME_Relative timeframe; - - /** - * Array of names of measures to apply. - * Names listed can be original measures or - * custom measures from the `AmlOutcome`. - */ - const char **measures; - - /** - * Length of the measures array. - */ - unsigned int measures_length; - - /** - * True if crossing these limits is simply categorically - * forbidden (no measure will be triggered, the request - * will just be always denied). - */ - bool verboten; - - /** - * True if the rule (specifically, @e operation_type, - * @e threshold and @e timeframe) and the general nature of - * the measures (@e verboten) - * should be exposed to the client. - */ - bool exposed; - - /** - * True if all the measures will eventually need to - * be satisfied, false if any of the measures should - * do. Primarily used by the SPA to indicate how - * the measures apply when showing them to the user; - * in the end, AML programs will decide after each - * measure what to do next. - */ - bool is_and_combinator; - - /** - * If multiple rules apply to the same account - * at the same time, the number with the highest - * rule determines which set of measures will - * be activated and thus become visible for the - * user. - */ - uint32_t display_priority; -}; - - -/** - * Set of legitimization rules with expiration data. - */ -struct TALER_EXCHANGE_LegitimizationRuleSet -{ - - /** - * What successor measure applies to the account? - */ - const char *successor_measure; - - /** - * What are the current rules for the account? - */ - const struct TALER_EXCHANGE_KycRule *rules; - - /** - * What are custom measures that @e rules may refer to? - */ - const struct TALER_EXCHANGE_MeasureInformation *measures; - - /** - * When will this decision expire? - */ - struct GNUNET_TIME_Timestamp expiration_time; - - /** - * Length of the @e rules array. - */ - unsigned int rules_length; - - /** - * Length of the @e measures array. - */ - unsigned int measures_length; -}; - - -/** - * Data about an AML decision. - */ -struct TALER_EXCHANGE_AmlDecision -{ - /** - * Account the decision was made for. - */ - struct TALER_NormalizedPaytoHashP h_payto; - - /** - * RowID of this decision. - */ - uint64_t rowid; - - /** - * When was the decision made? - */ - struct GNUNET_TIME_Timestamp decision_time; - - /** - * What are the new rules? - */ - struct TALER_EXCHANGE_LegitimizationRuleSet limits; - - /** - * Justification given for the decision. - */ - const char *justification; - - /** - * Properties set for the account. - */ - const json_t *jproperties; - - /** - * Should AML staff investigate this account? - */ - bool to_investigate; - - /** - * Is this the currently active decision? - */ - bool is_active; - -}; - - -/** - * Information about AML decisions returned by the exchange. - */ -struct TALER_EXCHANGE_AmlDecisionsResponse -{ - /** - * HTTP response details. - */ - struct TALER_EXCHANGE_HttpResponse hr; - - /** - * Details depending on the HTTP response code. - */ - union - { - - /** - * Information returned on success (#MHD_HTTP_OK). - */ - struct - { - - /** - * Array of AML decision summaries returned by the exchange. - */ - const struct TALER_EXCHANGE_AmlDecision *decisions; - - /** - * Length of the @e decisions array. - */ - unsigned int decisions_length; - - } ok; - - } details; -}; - - -/** - * Function called with information about AML decisions. - * - * @param cls closure - * @param adr response data - */ -typedef void -(*TALER_EXCHANGE_LookupAmlDecisionsCallback) ( - void *cls, - const struct TALER_EXCHANGE_AmlDecisionsResponse *adr); - - -/** - * @brief Handle for a POST /aml/$OFFICER_PUB/decisions request. - */ -struct TALER_EXCHANGE_LookupAmlDecisions; - - -/** - * Inform AML SPA client about AML decisions that were been taken. - * - * @param ctx the context - * @param exchange_url HTTP base URL for the exchange - * @param h_payto which account should we return the AML decision history for, NULL to return all accounts - * @param investigation_only filter by investigation state - * @param active_only filter for only active states - * @param offset row number starting point (exclusive rowid) - * @param limit number of records to return, negative for descending, positive for ascending from start - * @param officer_priv private key of the deciding AML officer - * @param cb function to call with the exchange's result - * @param cb_cls closure for @a cb - * @return the request handle; NULL upon error - */ -struct TALER_EXCHANGE_LookupAmlDecisions * -TALER_EXCHANGE_lookup_aml_decisions ( - struct GNUNET_CURL_Context *ctx, - const char *exchange_url, - const struct TALER_NormalizedPaytoHashP *h_payto, - enum TALER_EXCHANGE_YesNoAll investigation_only, - enum TALER_EXCHANGE_YesNoAll active_only, - uint64_t offset, - int64_t limit, - const struct TALER_AmlOfficerPrivateKeyP *officer_priv, - TALER_EXCHANGE_LookupAmlDecisionsCallback cb, - void *cb_cls); - - -/** - * Cancel #TALER_EXCHANGE_lookup_aml_decisions() operation. - * - * @param lh handle of the operation to cancel - */ -void -TALER_EXCHANGE_lookup_aml_decisions_cancel ( - struct TALER_EXCHANGE_LookupAmlDecisions *lh); - - #endif /* _TALER_EXCHANGE__GET_AML_OFFICER_PUB_DECISIONS_H */ diff --git a/src/include/taler/taler-exchange/get-aml-OFFICER_PUB-kyc-statistics-NAMES.h b/src/include/taler/taler-exchange/get-aml-OFFICER_PUB-kyc-statistics-NAMES.h @@ -296,59 +296,4 @@ TALER_EXCHANGE_get_aml_kyc_statistics_cancel ( struct TALER_EXCHANGE_GetAmlKycStatisticsHandle *aksh); -/* **************** deprecated legacy API ***************** */ - - -/** - * @deprecated Use #TALER_EXCHANGE_GetAmlKycStatisticsResponse instead. - * Note: the legacy API accepted only a single name and returned only - * a single counter; use the new API for multi-name queries. - */ -struct TALER_EXCHANGE_KycGetStatisticsResponse -{ - struct TALER_EXCHANGE_HttpResponse hr; - union - { - struct - { - unsigned long long counter; - } ok; - } details; -}; - -/** - * @deprecated Use #TALER_EXCHANGE_GetAmlKycStatisticsCallback instead. - */ -typedef void -(*TALER_EXCHANGE_KycStatisticsCallback) ( - void *cls, - const struct TALER_EXCHANGE_KycGetStatisticsResponse *hr); - -/** - * @deprecated Use #TALER_EXCHANGE_GetAmlKycStatisticsHandle instead. - */ -struct TALER_EXCHANGE_KycGetStatisticsHandle; - -/** - * @deprecated Use #TALER_EXCHANGE_get_aml_kyc_statistics_create() and - * the new API instead. - */ -struct TALER_EXCHANGE_KycGetStatisticsHandle * -TALER_EXCHANGE_kyc_get_statistics ( - struct GNUNET_CURL_Context *ctx, - const char *exchange_url, - const char *name, - struct GNUNET_TIME_Timestamp start_date, - struct GNUNET_TIME_Timestamp end_date, - const struct TALER_AmlOfficerPrivateKeyP *officer_priv, - TALER_EXCHANGE_KycStatisticsCallback cb, - void *cb_cls); - -/** - * @deprecated Use #TALER_EXCHANGE_get_aml_kyc_statistics_cancel() instead. - */ -void -TALER_EXCHANGE_kyc_get_statistics_cancel ( - struct TALER_EXCHANGE_KycGetStatisticsHandle *kgs); - #endif /* _TALER_EXCHANGE__GET_AML_OFFICER_PUB_KYC_STATISTICS_NAMES_H */ diff --git a/src/include/taler/taler-exchange/get-aml-OFFICER_PUB-legitimizations.h b/src/include/taler/taler-exchange/get-aml-OFFICER_PUB-legitimizations.h @@ -211,7 +211,7 @@ enum GNUNET_GenericReturnValue TALER_EXCHANGE_get_aml_legitimizations_set_options_ ( struct TALER_EXCHANGE_GetAmlLegitimizationsHandle *algh, unsigned int num_options, - const struct TALER_EXCHANGE_GetAmlLegitimizationsOptionValue *options); + const struct TALER_EXCHANGE_GetAmlLegitimizationsOptionValue options[]); /** @@ -340,27 +340,13 @@ typedef void * @param[in,out] algh operation to start * @param cb function to call with the exchange's result * @param cb_cls closure for @a cb - * @return status code, #TALER_EXCHANGE_GET_AML_LEGITIMIZATIONS_START_OK on success + * @return status code, #TALER_EC_NONE on success */ -enum TALER_EXCHANGE_AmlLegitimizationsGetStartError -{ - /** - * Success. - */ - TALER_EXCHANGE_GET_AML_LEGITIMIZATIONS_START_OK = 0, - /** - * Only allowed to be started once. - */ - TALER_EXCHANGE_GET_AML_LEGITIMIZATIONS_START_E_AGAIN = 1, - /** - * Internal logic failure. - */ - TALER_EXCHANGE_GET_AML_LEGITIMIZATIONS_START_E_INTERNAL = 2, -} +enum TALER_ErrorCode TALER_EXCHANGE_get_aml_legitimizations_start ( struct TALER_EXCHANGE_GetAmlLegitimizationsHandle *algh, TALER_EXCHANGE_GetAmlLegitimizationsCallback cb, - TALER_EXCHANGE_GET_AML_LEGITIMIZATIONS_RESULT_CLOSURE * cb_cls); + TALER_EXCHANGE_GET_AML_LEGITIMIZATIONS_RESULT_CLOSURE *cb_cls); /** diff --git a/src/include/taler/taler-exchange/get-aml-OFFICER_PUB-measures.h b/src/include/taler/taler-exchange/get-aml-OFFICER_PUB-measures.h @@ -346,98 +346,4 @@ TALER_EXCHANGE_get_aml_measures_cancel ( struct TALER_EXCHANGE_GetAmlMeasuresHandle *amh); -/* **************** deprecated legacy API ***************** */ - - -/** - * @deprecated Use #TALER_EXCHANGE_GetAmlMeasuresMeasureInfo instead. - */ -struct TALER_EXCHANGE_AvailableAmlMeasures -{ - const char *measure_name; - const char *check_name; - const char *prog_name; - const json_t *context; -}; - -/** - * @deprecated Use #TALER_EXCHANGE_GetAmlMeasuresProgramRequirement instead. - */ -struct TALER_EXCHANGE_AvailableAmlPrograms -{ - const char *prog_name; - const char *description; - const char **contexts; - const char **inputs; - unsigned int contexts_length; - unsigned int inputs_length; -}; - -/** - * @deprecated Use #TALER_EXCHANGE_GetAmlMeasuresCheckInfo instead. - */ -struct TALER_EXCHANGE_AvailableKycChecks -{ - const char *check_name; - const char *description; - const json_t *description_i18n; - const char *fallback; - const char **requires; - const char **outputs; - unsigned int requires_length; - unsigned int outputs_length; -}; - -/** - * @deprecated Use #TALER_EXCHANGE_GetAmlMeasuresResponse instead. - */ -struct TALER_EXCHANGE_AmlGetMeasuresResponse -{ - struct TALER_EXCHANGE_HttpResponse hr; - union - { - struct - { - const struct TALER_EXCHANGE_AvailableAmlMeasures *roots; - const struct TALER_EXCHANGE_AvailableAmlPrograms *programs; - const struct TALER_EXCHANGE_AvailableKycChecks *checks; - unsigned int roots_length; - unsigned int programs_length; - unsigned int checks_length; - } ok; - } details; -}; - -/** - * @deprecated Use #TALER_EXCHANGE_GetAmlMeasuresCallback instead. - */ -typedef void -(*TALER_EXCHANGE_AmlMeasuresCallback) ( - void *cls, - const struct TALER_EXCHANGE_AmlGetMeasuresResponse *hr); - -/** - * @deprecated Use #TALER_EXCHANGE_GetAmlMeasuresHandle instead. - */ -struct TALER_EXCHANGE_AmlGetMeasuresHandle; - -/** - * @deprecated Use #TALER_EXCHANGE_get_aml_measures_create() and - * the new API instead. - */ -struct TALER_EXCHANGE_AmlGetMeasuresHandle * -TALER_EXCHANGE_aml_get_measures ( - struct GNUNET_CURL_Context *ctx, - const char *url, - const struct TALER_AmlOfficerPrivateKeyP *officer_priv, - TALER_EXCHANGE_AmlMeasuresCallback cb, - void *cb_cls); - -/** - * @deprecated Use #TALER_EXCHANGE_get_aml_measures_cancel() instead. - */ -void -TALER_EXCHANGE_aml_get_measures_cancel ( - struct TALER_EXCHANGE_AmlGetMeasuresHandle *agml); - #endif /* _TALER_EXCHANGE__GET_AML_OFFICER_PUB_MEASURES_H */ diff --git a/src/include/taler/taler-exchange/get-kyc-check-H_NORMALIZED_PAYTO.h b/src/include/taler/taler-exchange/get-kyc-check-H_NORMALIZED_PAYTO.h @@ -345,61 +345,4 @@ TALER_EXCHANGE_get_kyc_check_cancel ( struct TALER_EXCHANGE_GetKycCheckHandle *gkch); -/* **************** deprecated legacy API ***************** */ - - -/** - * @deprecated Use #TALER_EXCHANGE_GetKycCheckResponse instead. - */ -struct TALER_EXCHANGE_KycStatus -{ - struct TALER_EXCHANGE_HttpResponse hr; - union - { - struct TALER_EXCHANGE_AccountKycStatus ok; - struct TALER_EXCHANGE_AccountKycStatus accepted; - struct - { - union TALER_AccountPublicKeyP expected_account_pub; - } forbidden; - } details; -}; - -/** - * @deprecated Use #TALER_EXCHANGE_GetKycCheckCallback instead. - */ -typedef void -(*TALER_EXCHANGE_KycStatusCallback)( - void *cls, - const struct TALER_EXCHANGE_KycStatus *ks); - -/** - * @deprecated Use #TALER_EXCHANGE_GetKycCheckHandle instead. - */ -struct TALER_EXCHANGE_KycCheckHandle; - -/** - * @deprecated Use #TALER_EXCHANGE_get_kyc_check_create() and - * the new API instead. - */ -struct TALER_EXCHANGE_KycCheckHandle * -TALER_EXCHANGE_kyc_check ( - struct GNUNET_CURL_Context *ctx, - const char *url, - const struct TALER_NormalizedPaytoHashP *h_payto, - const union TALER_AccountPrivateKeyP *pk, - uint64_t known_rule_gen, - enum TALER_EXCHANGE_KycLongPollTarget lpt, - struct GNUNET_TIME_Relative timeout, - TALER_EXCHANGE_KycStatusCallback cb, - void *cb_cls); - -/** - * @deprecated Use #TALER_EXCHANGE_get_kyc_check_cancel() instead. - */ -void -TALER_EXCHANGE_kyc_check_cancel ( - struct TALER_EXCHANGE_KycCheckHandle *kyc); - - #endif /* _TALER_EXCHANGE__GET_KYC_CHECK_H_NORMALIZED_PAYTO_H */ diff --git a/src/include/taler/taler-exchange/get-kyc-info-ACCESS_TOKEN.h b/src/include/taler/taler-exchange/get-kyc-info-ACCESS_TOKEN.h @@ -347,61 +347,4 @@ TALER_EXCHANGE_get_kyc_info_cancel ( struct TALER_EXCHANGE_GetKycInfoHandle *gkih); -/* **************** deprecated legacy API ***************** */ - - -/** - * @deprecated Use #TALER_EXCHANGE_GetKycInfoResponse instead. - */ -struct TALER_EXCHANGE_KycProcessClientInformation -{ - struct TALER_EXCHANGE_HttpResponse hr; - union - { - struct - { - const struct TALER_EXCHANGE_RequirementInformation *requirements; - const struct TALER_EXCHANGE_VoluntaryCheckInformation *vci; - unsigned int requirements_length; - unsigned int vci_length; - bool is_and_combinator; - } ok; - } details; -}; - -/** - * @deprecated Use #TALER_EXCHANGE_GetKycInfoCallback instead. - */ -typedef void -(*TALER_EXCHANGE_KycInfoCallback)( - void *cls, - const struct TALER_EXCHANGE_KycProcessClientInformation *kpci); - -/** - * @deprecated Use #TALER_EXCHANGE_GetKycInfoHandle instead. - */ -struct TALER_EXCHANGE_KycInfoHandle; - -/** - * @deprecated Use #TALER_EXCHANGE_get_kyc_info_create() and - * the new API instead. - */ -struct TALER_EXCHANGE_KycInfoHandle * -TALER_EXCHANGE_kyc_info ( - struct GNUNET_CURL_Context *ctx, - const char *url, - const struct TALER_AccountAccessTokenP *token, - const char *if_none_match, - struct GNUNET_TIME_Relative timeout, - TALER_EXCHANGE_KycInfoCallback cb, - void *cb_cls); - -/** - * @deprecated Use #TALER_EXCHANGE_get_kyc_info_cancel() instead. - */ -void -TALER_EXCHANGE_kyc_info_cancel ( - struct TALER_EXCHANGE_KycInfoHandle *kih); - - #endif /* _TALER_EXCHANGE__GET_KYC_INFO_ACCESS_TOKEN_H */ diff --git a/src/include/taler/taler-exchange/get-kyc-proof-PROVIDER_NAME.h b/src/include/taler/taler-exchange/get-kyc-proof-PROVIDER_NAME.h @@ -247,57 +247,4 @@ TALER_EXCHANGE_get_kyc_proof_cancel ( struct TALER_EXCHANGE_GetKycProofHandle *gkph); -/* **************** deprecated legacy API ***************** */ - - -/** - * @deprecated Use #TALER_EXCHANGE_GetKycProofResponse instead. - */ -struct TALER_EXCHANGE_KycProofResponse -{ - struct TALER_EXCHANGE_HttpResponse hr; - union - { - struct - { - const char *redirect_url; - } found; - } details; -}; - -/** - * @deprecated Use #TALER_EXCHANGE_GetKycProofCallback instead. - */ -typedef void -(*TALER_EXCHANGE_KycProofCallback)( - void *cls, - const struct TALER_EXCHANGE_KycProofResponse *kpr); - -/** - * @deprecated Use #TALER_EXCHANGE_GetKycProofHandle instead. - */ -struct TALER_EXCHANGE_KycProofHandle; - -/** - * @deprecated Use #TALER_EXCHANGE_get_kyc_proof_create() and - * the new API instead. - */ -struct TALER_EXCHANGE_KycProofHandle * -TALER_EXCHANGE_kyc_proof ( - struct GNUNET_CURL_Context *ctx, - const char *url, - const struct TALER_NormalizedPaytoHashP *h_payto, - const char *logic, - const char *args, - TALER_EXCHANGE_KycProofCallback cb, - void *cb_cls); - -/** - * @deprecated Use #TALER_EXCHANGE_get_kyc_proof_cancel() instead. - */ -void -TALER_EXCHANGE_kyc_proof_cancel ( - struct TALER_EXCHANGE_KycProofHandle *kph); - - #endif /* _TALER_EXCHANGE__GET_KYC_PROOF_PROVIDER_NAME_H */ diff --git a/src/include/taler/taler-exchange/post-aml-OFFICER_PUB-decision.h b/src/include/taler/taler-exchange/post-aml-OFFICER_PUB-decision.h @@ -195,8 +195,6 @@ struct TALER_EXCHANGE_PostAmlDecisionOptionValue */ struct TALER_EXCHANGE_PostAmlDecisionHandle; -#if NEW_API - /** * Set up POST /aml/$OFFICER_PUB/decision operation. @@ -317,8 +315,7 @@ enum GNUNET_GenericReturnValue TALER_EXCHANGE_post_aml_decision_set_options_ ( struct TALER_EXCHANGE_PostAmlDecisionHandle *padh, unsigned int num_options, - struct TALER_EXCHANGE_PostAmlDecisionOptionValue options[ - static num_options]); + const struct TALER_EXCHANGE_PostAmlDecisionOptionValue options[]); /** @@ -403,70 +400,5 @@ void TALER_EXCHANGE_post_aml_decision_cancel ( struct TALER_EXCHANGE_PostAmlDecisionHandle *padh); -#endif - -/* **************** deprecated legacy API ***************** */ - - -/** - * @deprecated - */ -struct TALER_EXCHANGE_AddAmlDecision; - - -/** - * @deprecated - */ -struct TALER_EXCHANGE_AddAmlDecisionResponse -{ - struct TALER_EXCHANGE_HttpResponse hr; -}; - -/** - * @deprecated - */ -typedef void -(*TALER_EXCHANGE_AddAmlDecisionCallback) ( - void *cls, - const struct TALER_EXCHANGE_AddAmlDecisionResponse *adr); - -/** - * @deprecated - */ -struct TALER_EXCHANGE_AddAmlDecision * -TALER_EXCHANGE_post_aml_decision ( - struct GNUNET_CURL_Context *ctx, - const char *url, - const struct TALER_NormalizedPaytoHashP *h_payto, - const struct TALER_FullPayto payto_uri, - struct GNUNET_TIME_Timestamp decision_time, - const char *successor_measure, - const char *new_measures, - struct GNUNET_TIME_Timestamp expiration_time, - unsigned int num_rules, - const struct TALER_EXCHANGE_AccountRule *rules, - unsigned int num_measures, - const struct TALER_EXCHANGE_MeasureInformation *measures, - const json_t *properties, - bool keep_investigating, - const char *justification, - const struct TALER_AmlOfficerPrivateKeyP *officer_priv, - unsigned int num_events, - const char **events, - TALER_EXCHANGE_AddAmlDecisionCallback cb, - void *cb_cls); - -/** - * Cancel POST /aml/$OFFICER_PUB/decision operation. This function must not - * be called by clients after the TALER_EXCHANGE_PostAmlDecisionCallback has - * been invoked (as in those cases it'll be called internally by the - * implementation already). - * - * @param[in] padh operation to cancel - */ -void -TALER_EXCHANGE_post_aml_decision_cancel ( - struct TALER_EXCHANGE_AddAmlDecision *padh); - #endif /* _TALER_EXCHANGE__POST_AML_OFFICER_PUB_DECISION_H */ diff --git a/src/include/taler/taler-exchange/post-kyc-start-ID.h b/src/include/taler/taler-exchange/post-kyc-start-ID.h @@ -125,54 +125,4 @@ TALER_EXCHANGE_post_kyc_start_cancel ( struct TALER_EXCHANGE_PostKycStartHandle *pksh); -/* **************** deprecated legacy API ***************** */ - - -/** - * @deprecated Use #TALER_EXCHANGE_PostKycStartResponse instead. - */ -struct TALER_EXCHANGE_KycStartResponse -{ - struct TALER_EXCHANGE_HttpResponse hr; - union - { - struct - { - const char *redirect_url; - } ok; - } details; -}; - -/** - * @deprecated Use #TALER_EXCHANGE_PostKycStartCallback instead. - */ -typedef void -(*TALER_EXCHANGE_KycStartCallback)( - void *cls, - const struct TALER_EXCHANGE_KycStartResponse *ksr); - -/** - * @deprecated Use #TALER_EXCHANGE_PostKycStartHandle instead. - */ -struct TALER_EXCHANGE_KycStartHandle; - -/** - * @deprecated Use #TALER_EXCHANGE_post_kyc_start_create() and - * the new API instead. - */ -struct TALER_EXCHANGE_KycStartHandle * -TALER_EXCHANGE_kyc_start ( - struct GNUNET_CURL_Context *ctx, - const char *url, - const char *id, - TALER_EXCHANGE_KycStartCallback cb, - void *cb_cls); - -/** - * @deprecated Use #TALER_EXCHANGE_post_kyc_start_cancel() instead. - */ -void -TALER_EXCHANGE_kyc_start_cancel (struct TALER_EXCHANGE_KycStartHandle *ksh); - - #endif /* _TALER_EXCHANGE__POST_KYC_START_ID_H */ diff --git a/src/include/taler/taler-exchange/post-kyc-wallet.h b/src/include/taler/taler-exchange/post-kyc-wallet.h @@ -138,57 +138,4 @@ TALER_EXCHANGE_post_kyc_wallet_cancel ( struct TALER_EXCHANGE_PostKycWalletHandle *pkwh); -/* **************** deprecated legacy API ***************** */ - - -/** - * @deprecated Use #TALER_EXCHANGE_PostKycWalletResponse instead. - */ -struct TALER_EXCHANGE_WalletKycResponse -{ - struct TALER_EXCHANGE_HttpResponse hr; - union - { - struct - { - struct TALER_Amount next_threshold; - struct GNUNET_TIME_Timestamp expiration_time; - } ok; - struct TALER_EXCHANGE_KycNeededRedirect unavailable_for_legal_reasons; - } details; -}; - -/** - * @deprecated Use #TALER_EXCHANGE_PostKycWalletCallback instead. - */ -typedef void -(*TALER_EXCHANGE_KycWalletCallback)( - void *cls, - const struct TALER_EXCHANGE_WalletKycResponse *ks); - -/** - * @deprecated Use #TALER_EXCHANGE_PostKycWalletHandle instead. - */ -struct TALER_EXCHANGE_KycWalletHandle; - -/** - * @deprecated Use #TALER_EXCHANGE_post_kyc_wallet_create() and - * the new API instead. - */ -struct TALER_EXCHANGE_KycWalletHandle * -TALER_EXCHANGE_kyc_wallet ( - struct GNUNET_CURL_Context *ctx, - const char *url, - const struct TALER_ReservePrivateKeyP *reserve_priv, - const struct TALER_Amount *balance, - TALER_EXCHANGE_KycWalletCallback cb, - void *cb_cls); - -/** - * @deprecated Use #TALER_EXCHANGE_post_kyc_wallet_cancel() instead. - */ -void -TALER_EXCHANGE_kyc_wallet_cancel (struct TALER_EXCHANGE_KycWalletHandle *kwh); - - #endif /* _TALER_EXCHANGE__POST_KYC_WALLET_H */ diff --git a/src/include/taler/taler-exchange/post-purses-PURSE_PUB-create.h b/src/include/taler/taler-exchange/post-purses-PURSE_PUB-create.h @@ -146,8 +146,7 @@ enum GNUNET_GenericReturnValue TALER_EXCHANGE_post_purses_create_set_options_ ( struct TALER_EXCHANGE_PostPursesCreateHandle *ppch, unsigned int num_options, - struct TALER_EXCHANGE_PostPursesCreateOptionValue options[ - static num_options]); + const struct TALER_EXCHANGE_PostPursesCreateOptionValue options[]); /** diff --git a/src/include/taler/taler-exchange/post-reserves-RESERVE_PUB-close.h b/src/include/taler/taler-exchange/post-reserves-RESERVE_PUB-close.h @@ -132,8 +132,7 @@ enum GNUNET_GenericReturnValue TALER_EXCHANGE_post_reserves_close_set_options_ ( struct TALER_EXCHANGE_PostReservesCloseHandle *prch, unsigned int num_options, - struct TALER_EXCHANGE_PostReservesCloseOptionValue options[ - static num_options]); + const struct TALER_EXCHANGE_PostReservesCloseOptionValue options[]); /** diff --git a/src/include/taler/taler-exchange/post-reserves-RESERVE_PUB-purse.h b/src/include/taler/taler-exchange/post-reserves-RESERVE_PUB-purse.h @@ -146,8 +146,7 @@ enum GNUNET_GenericReturnValue TALER_EXCHANGE_post_reserves_purse_set_options_ ( struct TALER_EXCHANGE_PostReservesPurseHandle *prph, unsigned int num_options, - struct TALER_EXCHANGE_PostReservesPurseOptionValue options[ - static num_options]); + const struct TALER_EXCHANGE_PostReservesPurseOptionValue options[]); /** diff --git a/src/lib/Makefile.am b/src/lib/Makefile.am @@ -18,7 +18,7 @@ lib_LTLIBRARIES = \ libtalerexchange.la libtalerexchange_la_LDFLAGS = \ - -version-info 18:0:0 \ + -version-info 19:0:0 \ -no-undefined libtalerexchange_la_SOURCES = \ exchange_api_delete-purses-PURSE_PUB.c \ diff --git a/src/lib/exchange_api_get-aml-OFFICER_PUB-attributes-H_NORMALIZED_PAYTO.c b/src/lib/exchange_api_get-aml-OFFICER_PUB-attributes-H_NORMALIZED_PAYTO.c @@ -1,6 +1,6 @@ /* This file is part of TALER - Copyright (C) 2023, 2024 Taler Systems SA + Copyright (C) 2023, 2024, 2025, 2026 Taler Systems SA TALER is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software @@ -28,15 +28,22 @@ #include "exchange_api_handle.h" #include "taler/taler_signatures.h" #include "exchange_api_curl_defaults.h" +#include \ + "taler/taler-exchange/get-aml-OFFICER_PUB-attributes-H_NORMALIZED_PAYTO.h" /** - * @brief A GET /aml/$OFFICER_PUB/attributes Handle + * @brief A GET /aml/$OFFICER_PUB/attributes/$H_NORMALIZED_PAYTO Handle */ -struct TALER_EXCHANGE_LookupKycAttributes +struct TALER_EXCHANGE_GetAmlAttributesHandle { /** + * Base URL of the exchange. + */ + char *base_url; + + /** * The url for this request. */ char *url; @@ -49,56 +56,91 @@ struct TALER_EXCHANGE_LookupKycAttributes /** * Function to call with the result. */ - TALER_EXCHANGE_LookupKycAttributesCallback attributes_cb; + TALER_EXCHANGE_GetAmlAttributesCallback cb; /** * Closure for @e cb. */ - void *attributes_cb_cls; + TALER_EXCHANGE_GET_AML_ATTRIBUTES_RESULT_CLOSURE *cb_cls; + + /** + * CURL context to use. + */ + struct GNUNET_CURL_Context *ctx; /** - * HTTP headers for the job. + * Public key of the AML officer (computed from officer_priv in _create). */ - struct curl_slist *job_headers; + struct TALER_AmlOfficerPublicKeyP officer_pub; + + /** + * Private key of the AML officer (stored for signing in _start). + */ + struct TALER_AmlOfficerPrivateKeyP officer_priv; + + /** + * Hash of the normalized payto URI for the account. + */ + struct TALER_NormalizedPaytoHashP h_payto; + + /** + * Options set for this request. + */ + struct + { + /** + * Limit on the number of results (negative = before offset, positive = after). + * Default: -20. + */ + int64_t limit; + + /** + * Row offset threshold. Default: UINT64_MAX. + */ + uint64_t offset; + } options; }; /** - * Parse AML decision summary array. + * Parse AML attribute collection events from a JSON array. * - * @param[in,out] lh handle to use for allocations - * @param jdetails JSON array with AML decision summaries - * @param[out] detail_ar where to write the result + * @param jdetails JSON array with AML attribute collection events + * @param[out] detail_ar where to write the results * @return #GNUNET_OK on success */ static enum GNUNET_GenericReturnValue -parse_kyc_attributes ( - struct TALER_EXCHANGE_LookupKycAttributes *lh, +parse_attributes_new ( const json_t *jdetails, - struct TALER_EXCHANGE_KycAttributeDetail *detail_ar) + struct TALER_EXCHANGE_GetAmlAttributesCollectionEvent *detail_ar) { json_t *obj; size_t idx; json_array_foreach (jdetails, idx, obj) { - struct TALER_EXCHANGE_KycAttributeDetail *detail + struct TALER_EXCHANGE_GetAmlAttributesCollectionEvent *detail = &detail_ar[idx]; + bool by_aml_officer = false; struct GNUNET_JSON_Specification spec[] = { GNUNET_JSON_spec_uint64 ("rowid", - &detail->row_id), + &detail->rowid), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_bool ("by_aml_officer", + &by_aml_officer), + NULL), GNUNET_JSON_spec_mark_optional ( - GNUNET_JSON_spec_string ("provider_name", - &detail->provider_name), + GNUNET_JSON_spec_object_const ("attributes", + &detail->attributes), NULL), - GNUNET_JSON_spec_object_const ("attributes", - &detail->attributes), GNUNET_JSON_spec_timestamp ("collection_time", &detail->collection_time), GNUNET_JSON_spec_end () }; + detail->by_aml_officer = false; + detail->attributes = NULL; if (GNUNET_OK != GNUNET_JSON_parse (obj, spec, @@ -108,23 +150,25 @@ parse_kyc_attributes ( GNUNET_break_op (0); return GNUNET_SYSERR; } + detail->by_aml_officer = by_aml_officer; } return GNUNET_OK; } /** - * Parse the provided decision data from the "200 OK" response. + * Parse the provided data from the "200 OK" response. * - * @param[in,out] lh handle (callback may be zero'ed out) - * @param json json reply with the data for one coin + * @param[in,out] aagh handle (callback may be zero'ed out) + * @param json json reply with the data * @return #GNUNET_OK on success, #GNUNET_SYSERR on error */ static enum GNUNET_GenericReturnValue -parse_attributes_ok (struct TALER_EXCHANGE_LookupKycAttributes *lh, - const json_t *json) +parse_get_aml_attributes_ok_new ( + struct TALER_EXCHANGE_GetAmlAttributesHandle *aagh, + const json_t *json) { - struct TALER_EXCHANGE_KycAttributesResponse lr = { + struct TALER_EXCHANGE_GetAmlAttributesResponse lr = { .hr.reply = json, .hr.http_status = MHD_HTTP_OK }; @@ -144,52 +188,51 @@ parse_attributes_ok (struct TALER_EXCHANGE_LookupKycAttributes *lh, GNUNET_break_op (0); return GNUNET_SYSERR; } - lr.details.ok.kyc_attributes_length - = json_array_size (jdetails); + lr.details.ok.details_length = json_array_size (jdetails); { - struct TALER_EXCHANGE_KycAttributeDetail details[ - GNUNET_NZL (lr.details.ok.kyc_attributes_length)]; - enum GNUNET_GenericReturnValue ret = GNUNET_SYSERR; + struct TALER_EXCHANGE_GetAmlAttributesCollectionEvent details[ + GNUNET_NZL (lr.details.ok.details_length)]; memset (details, 0, sizeof (details)); - lr.details.ok.kyc_attributes = details; - ret = parse_kyc_attributes (lh, - jdetails, - details); - if (GNUNET_OK == ret) + lr.details.ok.details = details; + if (GNUNET_OK != + parse_attributes_new (jdetails, + details)) { - lh->attributes_cb (lh->attributes_cb_cls, - &lr); - lh->attributes_cb = NULL; + GNUNET_break_op (0); + return GNUNET_SYSERR; } - return ret; + aagh->cb (aagh->cb_cls, + &lr); + aagh->cb = NULL; } + return GNUNET_OK; } /** * Function called when we're done processing the - * HTTP /aml/$OFFICER_PUB/attributes request. + * HTTP GET /aml/$OFFICER_PUB/attributes/$H_NORMALIZED_PAYTO request (new API). * - * @param cls the `struct TALER_EXCHANGE_LookupKycAttributes` + * @param cls the `struct TALER_EXCHANGE_GetAmlAttributesHandle` * @param response_code HTTP response code, 0 on error * @param response parsed JSON result, NULL on error */ static void -handle_lookup_finished (void *cls, - long response_code, - const void *response) +handle_get_aml_attributes_finished (void *cls, + long response_code, + const void *response) { - struct TALER_EXCHANGE_LookupKycAttributes *lh = cls; + struct TALER_EXCHANGE_GetAmlAttributesHandle *aagh = cls; const json_t *j = response; - struct TALER_EXCHANGE_KycAttributesResponse lr = { + struct TALER_EXCHANGE_GetAmlAttributesResponse lr = { .hr.reply = j, .hr.http_status = (unsigned int) response_code }; - lh->job = NULL; + aagh->job = NULL; switch (response_code) { case 0: @@ -197,19 +240,23 @@ handle_lookup_finished (void *cls, break; case MHD_HTTP_OK: if (GNUNET_OK != - parse_attributes_ok (lh, - j)) + parse_get_aml_attributes_ok_new (aagh, + j)) { GNUNET_break_op (0); lr.hr.http_status = 0; lr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; break; } - GNUNET_assert (NULL == lh->attributes_cb); - TALER_EXCHANGE_lookup_kyc_attributes_cancel (lh); + GNUNET_assert (NULL == aagh->cb); + TALER_EXCHANGE_get_aml_attributes_cancel (aagh); return; case MHD_HTTP_NO_CONTENT: break; + case MHD_HTTP_NOT_IMPLEMENTED: + lr.hr.ec = TALER_JSON_get_error_code (j); + lr.hr.hint = TALER_JSON_get_error_hint (j); + break; case MHD_HTTP_BAD_REQUEST: lr.hr.ec = TALER_JSON_get_error_code (j); lr.hr.hint = TALER_JSON_get_error_hint (j); @@ -219,14 +266,10 @@ handle_lookup_finished (void *cls, case MHD_HTTP_FORBIDDEN: lr.hr.ec = TALER_JSON_get_error_code (j); lr.hr.hint = TALER_JSON_get_error_hint (j); - /* Nothing really to verify, exchange says this coin was not melted; we - should pass the JSON reply to the application */ break; case MHD_HTTP_NOT_FOUND: lr.hr.ec = TALER_JSON_get_error_code (j); lr.hr.hint = TALER_JSON_get_error_hint (j); - /* Nothing really to verify, exchange says this coin was not melted; we - should pass the JSON reply to the application */ break; case MHD_HTTP_INTERNAL_SERVER_ERROR: lr.hr.ec = TALER_JSON_get_error_code (j); @@ -240,56 +283,101 @@ handle_lookup_finished (void *cls, lr.hr.ec = TALER_JSON_get_error_code (j); lr.hr.hint = TALER_JSON_get_error_hint (j); GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Unexpected response code %u/%d for lookup AML attributes\n", + "Unexpected response code %u/%d for get AML attributes\n", (unsigned int) response_code, (int) lr.hr.ec); break; } - if (NULL != lh->attributes_cb) - lh->attributes_cb (lh->attributes_cb_cls, - &lr); - TALER_EXCHANGE_lookup_kyc_attributes_cancel (lh); + if (NULL != aagh->cb) + aagh->cb (aagh->cb_cls, + &lr); + TALER_EXCHANGE_get_aml_attributes_cancel (aagh); } -struct TALER_EXCHANGE_LookupKycAttributes * -TALER_EXCHANGE_lookup_kyc_attributes ( +struct TALER_EXCHANGE_GetAmlAttributesHandle * +TALER_EXCHANGE_get_aml_attributes_create ( struct GNUNET_CURL_Context *ctx, - const char *exchange_url, - const struct TALER_NormalizedPaytoHashP *h_payto, - uint64_t offset, - int64_t limit, + const char *url, const struct TALER_AmlOfficerPrivateKeyP *officer_priv, - TALER_EXCHANGE_LookupKycAttributesCallback cb, - void *cb_cls) + const struct TALER_NormalizedPaytoHashP *h_payto) +{ + struct TALER_EXCHANGE_GetAmlAttributesHandle *aagh; + + aagh = GNUNET_new (struct TALER_EXCHANGE_GetAmlAttributesHandle); + aagh->ctx = ctx; + aagh->base_url = GNUNET_strdup (url); + aagh->h_payto = *h_payto; + aagh->officer_priv = *officer_priv; + GNUNET_CRYPTO_eddsa_key_get_public (&officer_priv->eddsa_priv, + &aagh->officer_pub.eddsa_pub); + aagh->options.limit = -20; + aagh->options.offset = UINT64_MAX; + return aagh; +} + + +enum GNUNET_GenericReturnValue +TALER_EXCHANGE_get_aml_attributes_set_options_ ( + struct TALER_EXCHANGE_GetAmlAttributesHandle *aagh, + unsigned int num_options, + const struct TALER_EXCHANGE_GetAmlAttributesOptionValue *options) +{ + for (unsigned int i = 0; i < num_options; i++) + { + const struct TALER_EXCHANGE_GetAmlAttributesOptionValue *opt = &options[i]; + + switch (opt->option) + { + case TALER_EXCHANGE_GET_AML_ATTRIBUTES_OPTION_END: + return GNUNET_OK; + case TALER_EXCHANGE_GET_AML_ATTRIBUTES_OPTION_LIMIT: + aagh->options.limit = opt->details.limit; + break; + case TALER_EXCHANGE_GET_AML_ATTRIBUTES_OPTION_OFFSET: + aagh->options.offset = opt->details.offset; + break; + } + } + return GNUNET_OK; +} + + +enum TALER_ErrorCode +TALER_EXCHANGE_get_aml_attributes_start ( + struct TALER_EXCHANGE_GetAmlAttributesHandle *aagh, + TALER_EXCHANGE_GetAmlAttributesCallback cb, + TALER_EXCHANGE_GET_AML_ATTRIBUTES_RESULT_CLOSURE *cb_cls) { - struct TALER_EXCHANGE_LookupKycAttributes *lh; - CURL *eh; - struct TALER_AmlOfficerPublicKeyP officer_pub; struct TALER_AmlOfficerSignatureP officer_sig; - char arg_str[sizeof (officer_pub) * 2 - + sizeof (*h_payto) * 2 + char arg_str[sizeof (aagh->officer_pub) * 2 + + sizeof (aagh->h_payto) * 2 + 32]; + CURL *eh; - GNUNET_CRYPTO_eddsa_key_get_public (&officer_priv->eddsa_priv, - &officer_pub.eddsa_pub); - TALER_officer_aml_query_sign (officer_priv, + if (NULL != aagh->job) + { + GNUNET_break (0); + return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE; + } + aagh->cb = cb; + aagh->cb_cls = cb_cls; + TALER_officer_aml_query_sign (&aagh->officer_priv, &officer_sig); - { - char payto_s[sizeof (*h_payto) * 2]; - char pub_str[sizeof (officer_pub) * 2]; + char payto_s[sizeof (aagh->h_payto) * 2]; + char pub_str[sizeof (aagh->officer_pub) * 2]; char *end; end = GNUNET_STRINGS_data_to_string ( - h_payto, - sizeof (*h_payto), + &aagh->h_payto, + sizeof (aagh->h_payto), payto_s, sizeof (payto_s)); *end = '\0'; end = GNUNET_STRINGS_data_to_string ( - &officer_pub, - sizeof (officer_pub), + &aagh->officer_pub, + sizeof (aagh->officer_pub), pub_str, sizeof (pub_str)); *end = '\0'; @@ -299,9 +387,6 @@ TALER_EXCHANGE_lookup_kyc_attributes ( pub_str, payto_s); } - lh = GNUNET_new (struct TALER_EXCHANGE_LookupKycAttributes); - lh->attributes_cb = cb; - lh->attributes_cb_cls = cb_cls; { char limit_s[24]; char offset_s[24]; @@ -309,35 +394,30 @@ TALER_EXCHANGE_lookup_kyc_attributes ( GNUNET_snprintf (limit_s, sizeof (limit_s), "%lld", - (long long) limit); + (long long) aagh->options.limit); GNUNET_snprintf (offset_s, sizeof (offset_s), "%llu", - (unsigned long long) offset); - lh->url = TALER_url_join ( - exchange_url, - arg_str, - "limit", - limit_s, - "offset", - offset_s, - "h_payto", - NULL); - } - if (NULL == lh->url) - { - GNUNET_free (lh); - return NULL; + (unsigned long long) aagh->options.offset); + aagh->url = TALER_url_join (aagh->base_url, + arg_str, + "limit", + limit_s, + "offset", + offset_s, + NULL); } - eh = TALER_EXCHANGE_curl_easy_get_ (lh->url); + if (NULL == aagh->url) + return TALER_EC_GENERIC_CONFIGURATION_INVALID; + eh = TALER_EXCHANGE_curl_easy_get_ (aagh->url); if (NULL == eh) { - GNUNET_break (0); - GNUNET_free (lh->url); - GNUNET_free (lh); - return NULL; + GNUNET_free (aagh->url); + aagh->url = NULL; + return TALER_EC_GENERIC_CONFIGURATION_INVALID; } { + struct curl_slist *job_headers = NULL; char *hdr; char sig_str[sizeof (officer_sig) * 2]; char *end; @@ -348,38 +428,44 @@ TALER_EXCHANGE_lookup_kyc_attributes ( sig_str, sizeof (sig_str)); *end = '\0'; - GNUNET_asprintf (&hdr, "%s: %s", TALER_AML_OFFICER_SIGNATURE_HEADER, sig_str); - lh->job_headers = curl_slist_append (NULL, - hdr); + job_headers = curl_slist_append (NULL, + hdr); GNUNET_free (hdr); - lh->job_headers = curl_slist_append (lh->job_headers, - "Content-type: application/json"); - lh->job = GNUNET_CURL_job_add2 (ctx, - eh, - lh->job_headers, - &handle_lookup_finished, - lh); + job_headers = curl_slist_append (job_headers, + "Content-type: application/json"); + aagh->job = GNUNET_CURL_job_add2 (aagh->ctx, + eh, + job_headers, + &handle_get_aml_attributes_finished, + aagh); + curl_slist_free_all (job_headers); + } + if (NULL == aagh->job) + { + GNUNET_free (aagh->url); + aagh->url = NULL; + return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE; } - return lh; + return TALER_EC_NONE; } void -TALER_EXCHANGE_lookup_kyc_attributes_cancel ( - struct TALER_EXCHANGE_LookupKycAttributes *lh) +TALER_EXCHANGE_get_aml_attributes_cancel ( + struct TALER_EXCHANGE_GetAmlAttributesHandle *aagh) { - if (NULL != lh->job) + if (NULL != aagh->job) { - GNUNET_CURL_job_cancel (lh->job); - lh->job = NULL; + GNUNET_CURL_job_cancel (aagh->job); + aagh->job = NULL; } - curl_slist_free_all (lh->job_headers); - GNUNET_free (lh->url); - GNUNET_free (lh); + GNUNET_free (aagh->url); + GNUNET_free (aagh->base_url); + GNUNET_free (aagh); } diff --git a/src/lib/exchange_api_get-aml-OFFICER_PUB-decisions.c b/src/lib/exchange_api_get-aml-OFFICER_PUB-decisions.c @@ -1,6 +1,6 @@ /* This file is part of TALER - Copyright (C) 2023, 2024 Taler Systems SA + Copyright (C) 2023, 2024, 2026 Taler Systems SA TALER is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software @@ -25,6 +25,7 @@ #include <gnunet/gnunet_curl_lib.h> #include "taler/taler_exchange_service.h" #include "taler/taler_json_lib.h" +#include "taler/taler-exchange/get-aml-OFFICER_PUB-decisions.h" #include "exchange_api_handle.h" #include "taler/taler_signatures.h" #include "exchange_api_curl_defaults.h" @@ -33,11 +34,16 @@ /** * @brief A GET /aml/$OFFICER_PUB/decisions Handle */ -struct TALER_EXCHANGE_LookupAmlDecisions +struct TALER_EXCHANGE_GetAmlDecisionsHandle { /** - * The url for this request. + * The base URL of the exchange. + */ + char *base_url; + + /** + * The full URL for this request, set during _start. */ char *url; @@ -49,55 +55,97 @@ struct TALER_EXCHANGE_LookupAmlDecisions /** * Function to call with the result. */ - TALER_EXCHANGE_LookupAmlDecisionsCallback decisions_cb; + TALER_EXCHANGE_GetAmlDecisionsCallback cb; /** * Closure for @e cb. */ - void *decisions_cb_cls; + TALER_EXCHANGE_GET_AML_DECISIONS_RESULT_CLOSURE *cb_cls; + + /** + * Reference to the execution context. + */ + struct GNUNET_CURL_Context *ctx; /** - * HTTP headers for the job. + * Public key of the AML officer. */ - struct curl_slist *job_headers; + struct TALER_AmlOfficerPublicKeyP officer_pub; /** - * Array with measure information. + * Private key of the AML officer (for signing). */ - struct TALER_EXCHANGE_MeasureInformation *mip; + struct TALER_AmlOfficerPrivateKeyP officer_priv; + + /** + * Signature of the AML officer. + */ + struct TALER_AmlOfficerSignatureP officer_sig; + + /** + * Options for the request. + */ + struct + { + /** + * Limit on number of results (-20 by default). + */ + int64_t limit; + + /** + * Row offset threshold (INT64_MAX by default). + */ + uint64_t offset; + + /** + * Optional account filter; NULL if not set. + */ + const struct TALER_NormalizedPaytoHashP *h_payto; + + /** + * Filter for active decisions (YNA_ALL by default). + */ + enum TALER_EXCHANGE_YesNoAll active; + + /** + * Filter for investigation status (YNA_ALL by default). + */ + enum TALER_EXCHANGE_YesNoAll investigation; + } options; /** - * Array with rule information. + * Flat array of all KYC rules across all decisions (allocated during parse). */ - struct TALER_EXCHANGE_KycRule *rp; + struct TALER_EXCHANGE_GetAmlDecisionsKycRule *all_rules; /** - * Array with all the measures (of all the rules!). + * Flat array of all measure string pointers across all rules (allocated during parse). */ - const char **mp; + const char **all_mp; + }; /** - * Parse AML limits array. + * Parse the limits/rules object. * - * @param[in,out] lh handle to use for allocations - * @param jlimits JSON array with AML rules - * @param[out] ds where to write the result + * @param[in,out] adgh handle (used for allocation tracking) + * @param jlimits JSON object with legitimization rule set data + * @param[out] limits where to write the parsed rule set + * @param[in,out] rule_off current offset into adgh->all_rules (advanced) + * @param[in,out] mp_off current offset into adgh->all_mp (advanced) * @return #GNUNET_OK on success */ static enum GNUNET_GenericReturnValue -parse_limits (struct TALER_EXCHANGE_LookupAmlDecisions *lh, - const json_t *jlimits, - struct TALER_EXCHANGE_AmlDecision *ds) +parse_limits ( + struct TALER_EXCHANGE_GetAmlDecisionsHandle *adgh, + const json_t *jlimits, + struct TALER_EXCHANGE_GetAmlDecisionsLegitimizationRuleSet *limits, + size_t *rule_off, + size_t *mp_off) { - struct TALER_EXCHANGE_LegitimizationRuleSet *limits - = &ds->limits; const json_t *jrules; - const json_t *jmeasures; - size_t mip_len; - size_t rule_len; - size_t total; + const json_t *jcustom_measures; struct GNUNET_JSON_Specification spec[] = { GNUNET_JSON_spec_timestamp ("expiration_time", &limits->expiration_time), @@ -107,8 +155,10 @@ parse_limits (struct TALER_EXCHANGE_LookupAmlDecisions *lh, NULL), GNUNET_JSON_spec_array_const ("rules", &jrules), - GNUNET_JSON_spec_object_const ("custom_measures", - &jmeasures), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_object_const ("custom_measures", + &jcustom_measures), + NULL), GNUNET_JSON_spec_end () }; @@ -121,172 +171,131 @@ parse_limits (struct TALER_EXCHANGE_LookupAmlDecisions *lh, GNUNET_break_op (0); return GNUNET_SYSERR; } - - mip_len = json_object_size (jmeasures); - lh->mip = GNUNET_new_array (mip_len, - struct TALER_EXCHANGE_MeasureInformation); - limits->measures = lh->mip; - limits->measures_length = mip_len; + limits->custom_measures = jcustom_measures; { - const char *measure_name; - const json_t *jmeasure; - - json_object_foreach ((json_t*) jmeasures, - measure_name, - jmeasure) - { - struct TALER_EXCHANGE_MeasureInformation *mi - = &lh->mip[--mip_len]; - struct GNUNET_JSON_Specification ispec[] = { - GNUNET_JSON_spec_string ("check_name", - &mi->check_name), - GNUNET_JSON_spec_mark_optional ( - GNUNET_JSON_spec_string ("prog_name", - &mi->prog_name), - NULL), - GNUNET_JSON_spec_mark_optional ( - GNUNET_JSON_spec_object_const ("context", - &mi->context), - NULL), - GNUNET_JSON_spec_end () - }; - - if (GNUNET_OK != - GNUNET_JSON_parse (jmeasure, - ispec, - NULL, - NULL)) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - mi->measure_name = measure_name; - } - } - - total = 0; - - { - const json_t *rule; - size_t idx; - - json_array_foreach ((json_t *) jrules, - idx, - rule) - { - total += json_array_size (json_object_get (rule, - "measures")); - } - } - - rule_len = json_array_size (jrules); - lh->rp = GNUNET_new_array (rule_len, - struct TALER_EXCHANGE_KycRule); - lh->mp = GNUNET_new_array (total, - const char *); + size_t rule_count = json_array_size (jrules); + size_t rule_start = *rule_off; - { - const json_t *rule; - size_t idx; + limits->rules = &adgh->all_rules[rule_start]; + limits->rules_length = rule_count; - json_array_foreach ((json_t *) jrules, - idx, - rule) { - const json_t *smeasures; - struct TALER_EXCHANGE_KycRule *r - = &lh->rp[--rule_len]; - struct GNUNET_JSON_Specification ispec[] = { - TALER_JSON_spec_kycte ("operation_type", - &r->operation_type), - TALER_JSON_spec_amount_any ("threshold", - &r->threshold), - GNUNET_JSON_spec_relative_time ("timeframe", - &r->timeframe), - GNUNET_JSON_spec_array_const ("measures", - &smeasures), - GNUNET_JSON_spec_mark_optional ( - GNUNET_JSON_spec_bool ("exposed", - &r->exposed), - NULL), - GNUNET_JSON_spec_mark_optional ( - GNUNET_JSON_spec_bool ("is_and_combinator", - &r->is_and_combinator), - NULL), - GNUNET_JSON_spec_uint32 ("display_priority", - &r->display_priority), - GNUNET_JSON_spec_end () - }; - size_t mlen; - - if (GNUNET_OK != - GNUNET_JSON_parse (rule, - ispec, - NULL, - NULL)) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - - mlen = json_array_size (smeasures); - GNUNET_assert (mlen <= total); - total -= mlen; + const json_t *jrule; + size_t ridx; + json_array_foreach ((json_t *) jrules, ridx, jrule) { - size_t midx; - const json_t *smeasure; + struct TALER_EXCHANGE_GetAmlDecisionsKycRule *r + = &adgh->all_rules[*rule_off]; + const json_t *jsmeasures; + struct GNUNET_JSON_Specification rspec[] = { + TALER_JSON_spec_kycte ("operation_type", + &r->operation_type), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_string ("rule_name", + &r->rule_name), + NULL), + TALER_JSON_spec_amount_any ("threshold", + &r->threshold), + GNUNET_JSON_spec_relative_time ("timeframe", + &r->timeframe), + GNUNET_JSON_spec_array_const ("measures", + &jsmeasures), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_bool ("exposed", + &r->exposed), + NULL), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_bool ("is_and_combinator", + &r->is_and_combinator), + NULL), + GNUNET_JSON_spec_int64 ("display_priority", + &r->display_priority), + GNUNET_JSON_spec_end () + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (jrule, + rspec, + NULL, + NULL)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } - json_array_foreach (smeasures, - midx, - smeasure) { - const char *sval = json_string_value (smeasure); + size_t mlen = json_array_size (jsmeasures); + size_t mp_start = *mp_off; + + r->measures = &adgh->all_mp[mp_start]; + r->measures_length = mlen; - if (NULL == sval) { - GNUNET_break_op (0); - return GNUNET_SYSERR; + size_t midx; + const json_t *jm; + + json_array_foreach (jsmeasures, midx, jm) + { + const char *sval = json_string_value (jm); + + if (NULL == sval) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + adgh->all_mp[*mp_off] = sval; + (*mp_off)++; + } } - lh->mp[total + midx] = sval; - if (0 == strcasecmp (sval, - "verboten")) - r->verboten = true; } + + (*rule_off)++; } - r->measures = &lh->mp[total]; - r->measures_length = r->verboten ? 0 : total; } } + return GNUNET_OK; } /** - * Parse AML decision summary array. + * Parse AML decision records. * - * @param[in,out] lh handle to use for allocations - * @param decisions JSON array with AML decision summaries - * @param[out] decision_ar where to write the result + * @param[in,out] adgh handle (for allocations) + * @param jrecords JSON array of decision records + * @param records_ar_length length of @a records_ar + * @param[out] records_ar caller-allocated array to fill * @return #GNUNET_OK on success */ static enum GNUNET_GenericReturnValue parse_aml_decisions ( - struct TALER_EXCHANGE_LookupAmlDecisions *lh, - const json_t *decisions, - struct TALER_EXCHANGE_AmlDecision *decision_ar) + struct TALER_EXCHANGE_GetAmlDecisionsHandle *adgh, + const json_t *jrecords, + size_t records_ar_length, + struct TALER_EXCHANGE_GetAmlDecisionsDecision *records_ar) { - json_t *obj; + size_t rule_off = 0; + size_t mp_off = 0; + const json_t *obj; size_t idx; - json_array_foreach (decisions, idx, obj) + json_array_foreach ((json_t *) jrecords, idx, obj) { - struct TALER_EXCHANGE_AmlDecision *decision = &decision_ar[idx]; + struct TALER_EXCHANGE_GetAmlDecisionsDecision *decision = &records_ar[idx]; const json_t *jlimits; struct GNUNET_JSON_Specification spec[] = { GNUNET_JSON_spec_fixed_auto ("h_payto", &decision->h_payto), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_string ("full_payto", + &decision->full_payto), + NULL), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_bool ("is_wallet", + &decision->is_wallet), + NULL), GNUNET_JSON_spec_uint64 ("rowid", &decision->rowid), GNUNET_JSON_spec_mark_optional ( @@ -297,7 +306,7 @@ parse_aml_decisions ( &decision->decision_time), GNUNET_JSON_spec_mark_optional ( GNUNET_JSON_spec_object_const ("properties", - &decision->jproperties), + &decision->properties), NULL), GNUNET_JSON_spec_object_const ("limits", &jlimits), @@ -308,6 +317,7 @@ parse_aml_decisions ( GNUNET_JSON_spec_end () }; + GNUNET_assert (idx < records_ar_length); if (GNUNET_OK != GNUNET_JSON_parse (obj, spec, @@ -317,10 +327,13 @@ parse_aml_decisions ( GNUNET_break_op (0); return GNUNET_SYSERR; } + if (GNUNET_OK != - parse_limits (lh, + parse_limits (adgh, jlimits, - decision)) + &decision->limits, + &rule_off, + &mp_off)) { GNUNET_break_op (0); return GNUNET_SYSERR; @@ -333,22 +346,22 @@ parse_aml_decisions ( /** * Parse the provided decision data from the "200 OK" response. * - * @param[in,out] lh handle (callback may be zero'ed out) - * @param json json reply with the data for one coin + * @param[in,out] adgh handle (callback may be zero'ed out) + * @param json json reply with the data * @return #GNUNET_OK on success, #GNUNET_SYSERR on error */ static enum GNUNET_GenericReturnValue -parse_decisions_ok (struct TALER_EXCHANGE_LookupAmlDecisions *lh, - const json_t *json) +parse_get_aml_decisions_ok (struct TALER_EXCHANGE_GetAmlDecisionsHandle *adgh, + const json_t *json) { - struct TALER_EXCHANGE_AmlDecisionsResponse lr = { + struct TALER_EXCHANGE_GetAmlDecisionsResponse lr = { .hr.reply = json, .hr.http_status = MHD_HTTP_OK }; - const json_t *records; + const json_t *jrecords; struct GNUNET_JSON_Specification spec[] = { GNUNET_JSON_spec_array_const ("records", - &records), + &jrecords), GNUNET_JSON_spec_end () }; @@ -361,29 +374,73 @@ parse_decisions_ok (struct TALER_EXCHANGE_LookupAmlDecisions *lh, GNUNET_break_op (0); return GNUNET_SYSERR; } - lr.details.ok.decisions_length - = json_array_size (records); + + lr.details.ok.records_length = json_array_size (jrecords); + + /* First pass: count total rules and measures across all records */ { - struct TALER_EXCHANGE_AmlDecision decisions[ - GNUNET_NZL (lr.details.ok.decisions_length)]; - enum GNUNET_GenericReturnValue ret = GNUNET_SYSERR; + size_t total_rules = 0; + size_t total_measures = 0; + const json_t *obj; + size_t idx; - memset (decisions, + json_array_foreach ((json_t *) jrecords, idx, obj) + { + const json_t *jlimits = json_object_get (obj, "limits"); + const json_t *jrules; + + if (NULL == jlimits) + continue; + jrules = json_object_get (jlimits, "rules"); + if (NULL == jrules) + continue; + total_rules += json_array_size (jrules); + + { + const json_t *jrule; + size_t ridx; + + json_array_foreach ((json_t *) jrules, ridx, jrule) + { + const json_t *jmeasures = json_object_get (jrule, "measures"); + + if (NULL != jmeasures) + total_measures += json_array_size (jmeasures); + } + } + } + + adgh->all_rules = GNUNET_new_array ( + GNUNET_NZL (total_rules), + struct TALER_EXCHANGE_GetAmlDecisionsKycRule); + adgh->all_mp = GNUNET_new_array ( + GNUNET_NZL (total_measures), + const char *); + } + + { + struct TALER_EXCHANGE_GetAmlDecisionsDecision records[ + GNUNET_NZL (lr.details.ok.records_length)]; + enum GNUNET_GenericReturnValue ret; + + memset (records, 0, - sizeof (decisions)); - lr.details.ok.decisions = decisions; - ret = parse_aml_decisions (lh, - records, - decisions); + sizeof (records)); + lr.details.ok.records = records; + ret = parse_aml_decisions (adgh, + jrecords, + lr.details.ok.records_length, + records); if (GNUNET_OK == ret) { - lh->decisions_cb (lh->decisions_cb_cls, - &lr); - lh->decisions_cb = NULL; + adgh->cb (adgh->cb_cls, + &lr); + adgh->cb = NULL; } - GNUNET_free (lh->mip); - GNUNET_free (lh->rp); - GNUNET_free (lh->mp); + GNUNET_free (adgh->all_rules); + adgh->all_rules = NULL; + GNUNET_free (adgh->all_mp); + adgh->all_mp = NULL; return ret; } } @@ -393,23 +450,23 @@ parse_decisions_ok (struct TALER_EXCHANGE_LookupAmlDecisions *lh, * Function called when we're done processing the * HTTP /aml/$OFFICER_PUB/decisions request. * - * @param cls the `struct TALER_EXCHANGE_LookupAmlDecisions` + * @param cls the `struct TALER_EXCHANGE_GetAmlDecisionsHandle` * @param response_code HTTP response code, 0 on error * @param response parsed JSON result, NULL on error */ static void -handle_lookup_finished (void *cls, - long response_code, - const void *response) +handle_get_aml_decisions_finished (void *cls, + long response_code, + const void *response) { - struct TALER_EXCHANGE_LookupAmlDecisions *lh = cls; + struct TALER_EXCHANGE_GetAmlDecisionsHandle *adgh = cls; const json_t *j = response; - struct TALER_EXCHANGE_AmlDecisionsResponse lr = { + struct TALER_EXCHANGE_GetAmlDecisionsResponse lr = { .hr.reply = j, .hr.http_status = (unsigned int) response_code }; - lh->job = NULL; + adgh->job = NULL; switch (response_code) { case 0: @@ -417,16 +474,16 @@ handle_lookup_finished (void *cls, break; case MHD_HTTP_OK: if (GNUNET_OK != - parse_decisions_ok (lh, - j)) + parse_get_aml_decisions_ok (adgh, + j)) { GNUNET_break_op (0); lr.hr.http_status = 0; lr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; break; } - GNUNET_assert (NULL == lh->decisions_cb); - TALER_EXCHANGE_lookup_aml_decisions_cancel (lh); + GNUNET_assert (NULL == adgh->cb); + TALER_EXCHANGE_get_aml_decisions_cancel (adgh); return; case MHD_HTTP_NO_CONTENT: break; @@ -436,26 +493,18 @@ handle_lookup_finished (void *cls, JSON_INDENT (2)); lr.hr.ec = TALER_JSON_get_error_code (j); lr.hr.hint = TALER_JSON_get_error_hint (j); - /* This should never happen, either us or the exchange is buggy - (or API version conflict); just pass JSON reply to the application */ break; case MHD_HTTP_FORBIDDEN: lr.hr.ec = TALER_JSON_get_error_code (j); lr.hr.hint = TALER_JSON_get_error_hint (j); - /* Nothing really to verify, exchange says this coin was not melted; we - should pass the JSON reply to the application */ break; case MHD_HTTP_NOT_FOUND: lr.hr.ec = TALER_JSON_get_error_code (j); lr.hr.hint = TALER_JSON_get_error_hint (j); - /* Nothing really to verify, exchange says this coin was not melted; we - should pass the JSON reply to the application */ break; case MHD_HTTP_INTERNAL_SERVER_ERROR: lr.hr.ec = TALER_JSON_get_error_code (j); lr.hr.hint = TALER_JSON_get_error_hint (j); - /* Server had an internal issue; we should retry, but this API - leaves this to the application */ break; default: /* unexpected response code */ @@ -463,49 +512,103 @@ handle_lookup_finished (void *cls, lr.hr.ec = TALER_JSON_get_error_code (j); lr.hr.hint = TALER_JSON_get_error_hint (j); GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Unexpected response code %u/%d for lookup AML decisions\n", + "Unexpected response code %u/%d for GET AML decisions\n", (unsigned int) response_code, (int) lr.hr.ec); break; } - if (NULL != lh->decisions_cb) - lh->decisions_cb (lh->decisions_cb_cls, - &lr); - TALER_EXCHANGE_lookup_aml_decisions_cancel (lh); + if (NULL != adgh->cb) + adgh->cb (adgh->cb_cls, + &lr); + TALER_EXCHANGE_get_aml_decisions_cancel (adgh); } -struct TALER_EXCHANGE_LookupAmlDecisions * -TALER_EXCHANGE_lookup_aml_decisions ( +struct TALER_EXCHANGE_GetAmlDecisionsHandle * +TALER_EXCHANGE_get_aml_decisions_create ( struct GNUNET_CURL_Context *ctx, - const char *exchange_url, - const struct TALER_NormalizedPaytoHashP *h_payto, - enum TALER_EXCHANGE_YesNoAll investigation_only, - enum TALER_EXCHANGE_YesNoAll active_only, - uint64_t offset, - int64_t limit, - const struct TALER_AmlOfficerPrivateKeyP *officer_priv, - TALER_EXCHANGE_LookupAmlDecisionsCallback cb, - void *cb_cls) + const char *url, + const struct TALER_AmlOfficerPrivateKeyP *officer_priv) { - struct TALER_EXCHANGE_LookupAmlDecisions *lh; - CURL *eh; - struct TALER_AmlOfficerPublicKeyP officer_pub; - struct TALER_AmlOfficerSignatureP officer_sig; - char arg_str[sizeof (struct TALER_AmlOfficerPublicKeyP) * 2 - + 32]; + struct TALER_EXCHANGE_GetAmlDecisionsHandle *adgh; + adgh = GNUNET_new (struct TALER_EXCHANGE_GetAmlDecisionsHandle); + adgh->ctx = ctx; + adgh->base_url = GNUNET_strdup (url); + adgh->officer_priv = *officer_priv; GNUNET_CRYPTO_eddsa_key_get_public (&officer_priv->eddsa_priv, - &officer_pub.eddsa_pub); - TALER_officer_aml_query_sign (officer_priv, - &officer_sig); + &adgh->officer_pub.eddsa_pub); + adgh->options.limit = -20; + adgh->options.offset = INT64_MAX; + adgh->options.active = TALER_EXCHANGE_YNA_ALL; + adgh->options.investigation = TALER_EXCHANGE_YNA_ALL; + return adgh; +} + + +enum GNUNET_GenericReturnValue +TALER_EXCHANGE_get_aml_decisions_set_options_ ( + struct TALER_EXCHANGE_GetAmlDecisionsHandle *adgh, + unsigned int num_options, + const struct TALER_EXCHANGE_GetAmlDecisionsOptionValue options[]) +{ + for (unsigned int i = 0; i < num_options; i++) { - char pub_str[sizeof (officer_pub) * 2]; + const struct TALER_EXCHANGE_GetAmlDecisionsOptionValue *opt = &options[i]; + + switch (opt->option) + { + case TALER_EXCHANGE_GET_AML_DECISIONS_OPTION_END: + return GNUNET_OK; + case TALER_EXCHANGE_GET_AML_DECISIONS_OPTION_LIMIT: + adgh->options.limit = opt->details.limit; + break; + case TALER_EXCHANGE_GET_AML_DECISIONS_OPTION_OFFSET: + adgh->options.offset = opt->details.offset; + break; + case TALER_EXCHANGE_GET_AML_DECISIONS_OPTION_H_PAYTO: + adgh->options.h_payto = opt->details.h_payto; + break; + case TALER_EXCHANGE_GET_AML_DECISIONS_OPTION_ACTIVE: + adgh->options.active = opt->details.active; + break; + case TALER_EXCHANGE_GET_AML_DECISIONS_OPTION_INVESTIGATION: + adgh->options.investigation = opt->details.investigation; + break; + default: + GNUNET_break (0); + return GNUNET_SYSERR; + } + } + return GNUNET_OK; +} + + +enum TALER_ErrorCode +TALER_EXCHANGE_get_aml_decisions_start ( + struct TALER_EXCHANGE_GetAmlDecisionsHandle *adgh, + TALER_EXCHANGE_GetAmlDecisionsCallback cb, + TALER_EXCHANGE_GET_AML_DECISIONS_RESULT_CLOSURE *cb_cls) +{ + CURL *eh; + char arg_str[sizeof (struct TALER_AmlOfficerPublicKeyP) * 2 + 32]; + struct curl_slist *job_headers = NULL; + + adgh->cb = cb; + adgh->cb_cls = cb_cls; + + /* Build AML officer signature */ + TALER_officer_aml_query_sign (&adgh->officer_priv, + &adgh->officer_sig); + + /* Build the path component: aml/{officer_pub}/decisions */ + { + char pub_str[sizeof (adgh->officer_pub) * 2]; char *end; end = GNUNET_STRINGS_data_to_string ( - &officer_pub, - sizeof (officer_pub), + &adgh->officer_pub, + sizeof (adgh->officer_pub), pub_str, sizeof (pub_str)); *end = '\0'; @@ -514,24 +617,18 @@ TALER_EXCHANGE_lookup_aml_decisions ( "aml/%s/decisions", pub_str); } - lh = GNUNET_new (struct TALER_EXCHANGE_LookupAmlDecisions); - lh->decisions_cb = cb; - lh->decisions_cb_cls = cb_cls; + + /* Build URL with optional query parameters */ { char limit_s[24]; char offset_s[24]; - char payto_s[sizeof (*h_payto) * 2]; - char *end; + char payto_s[sizeof (*adgh->options.h_payto) * 2 + 1]; + int64_t limit = adgh->options.limit; + uint64_t offset = adgh->options.offset; + bool omit_limit = (-20 == limit); + bool omit_offset = ( ( (limit < 0) && ((uint64_t) INT64_MAX == offset) ) || + ( (limit > 0) && (0 == offset) ) ); - if (NULL != h_payto) - { - end = GNUNET_STRINGS_data_to_string ( - h_payto, - sizeof (*h_payto), - payto_s, - sizeof (payto_s)); - *end = '\0'; - } GNUNET_snprintf (limit_s, sizeof (limit_s), "%lld", @@ -540,51 +637,60 @@ TALER_EXCHANGE_lookup_aml_decisions ( sizeof (offset_s), "%llu", (unsigned long long) offset); - lh->url = TALER_url_join ( - exchange_url, + + if (NULL != adgh->options.h_payto) + { + char *end; + + end = GNUNET_STRINGS_data_to_string ( + adgh->options.h_payto, + sizeof (*adgh->options.h_payto), + payto_s, + sizeof (payto_s) - 1); + *end = '\0'; + } + + adgh->url = TALER_url_join ( + adgh->base_url, arg_str, "limit", - limit_s, + omit_limit ? NULL : limit_s, "offset", - ( ( (limit < 0) && (UINT64_MAX == offset) ) || - ( (limit > 0) && (0 == offset) ) ) - ? NULL - : offset_s, + omit_offset ? NULL : offset_s, "h_payto", - NULL != h_payto - ? payto_s - : NULL, + (NULL != adgh->options.h_payto) ? payto_s : NULL, "active", - TALER_EXCHANGE_YNA_ALL != active_only - ? TALER_yna_to_string (active_only) + (TALER_EXCHANGE_YNA_ALL != adgh->options.active) + ? TALER_yna_to_string (adgh->options.active) : NULL, "investigation", - TALER_EXCHANGE_YNA_ALL != investigation_only - ? TALER_yna_to_string (investigation_only) + (TALER_EXCHANGE_YNA_ALL != adgh->options.investigation) + ? TALER_yna_to_string (adgh->options.investigation) : NULL, NULL); } - if (NULL == lh->url) - { - GNUNET_free (lh); - return NULL; - } - eh = TALER_EXCHANGE_curl_easy_get_ (lh->url); + + if (NULL == adgh->url) + return TALER_EC_GENERIC_CONFIGURATION_INVALID; + + eh = TALER_EXCHANGE_curl_easy_get_ (adgh->url); if (NULL == eh) { GNUNET_break (0); - GNUNET_free (lh->url); - GNUNET_free (lh); - return NULL; + GNUNET_free (adgh->url); + adgh->url = NULL; + return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE; } + + /* Build job headers with AML officer signature */ { char *hdr; - char sig_str[sizeof (officer_sig) * 2]; + char sig_str[sizeof (adgh->officer_sig) * 2]; char *end; end = GNUNET_STRINGS_data_to_string ( - &officer_sig, - sizeof (officer_sig), + &adgh->officer_sig, + sizeof (adgh->officer_sig), sig_str, sizeof (sig_str)); *end = '\0'; @@ -593,34 +699,45 @@ TALER_EXCHANGE_lookup_aml_decisions ( "%s: %s", TALER_AML_OFFICER_SIGNATURE_HEADER, sig_str); - lh->job_headers = curl_slist_append (NULL, - hdr); + job_headers = curl_slist_append (NULL, + hdr); GNUNET_free (hdr); - lh->job_headers = curl_slist_append (lh->job_headers, - "Content-type: application/json"); - lh->job = GNUNET_CURL_job_add2 (ctx, + job_headers = curl_slist_append (job_headers, + "Content-Type: application/json"); + } + + adgh->job = GNUNET_CURL_job_add2 (adgh->ctx, eh, - lh->job_headers, - &handle_lookup_finished, - lh); + job_headers, + &handle_get_aml_decisions_finished, + adgh); + curl_slist_free_all (job_headers); + + if (NULL == adgh->job) + { + GNUNET_free (adgh->url); + adgh->url = NULL; + return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE; } - return lh; + return TALER_EC_NONE; } void -TALER_EXCHANGE_lookup_aml_decisions_cancel ( - struct TALER_EXCHANGE_LookupAmlDecisions *lh) +TALER_EXCHANGE_get_aml_decisions_cancel ( + struct TALER_EXCHANGE_GetAmlDecisionsHandle *adgh) { - if (NULL != lh->job) + if (NULL != adgh->job) { - GNUNET_CURL_job_cancel (lh->job); - lh->job = NULL; + GNUNET_CURL_job_cancel (adgh->job); + adgh->job = NULL; } - curl_slist_free_all (lh->job_headers); - GNUNET_free (lh->url); - GNUNET_free (lh); + GNUNET_free (adgh->all_rules); + GNUNET_free (adgh->all_mp); + GNUNET_free (adgh->url); + GNUNET_free (adgh->base_url); + GNUNET_free (adgh); } -/* end of exchange_api_lookup_aml_decisions.c */ +/* end of exchange_api_get-aml-OFFICER_PUB-decisions.c */ diff --git a/src/lib/exchange_api_get-aml-OFFICER_PUB-kyc-statistics-NAMES.c b/src/lib/exchange_api_get-aml-OFFICER_PUB-kyc-statistics-NAMES.c @@ -1,6 +1,6 @@ /* This file is part of TALER - Copyright (C) 2023, 2024 Taler Systems SA + Copyright (C) 2023, 2024, 2025, 2026 Taler Systems SA TALER is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software @@ -28,16 +28,22 @@ #include "exchange_api_handle.h" #include "taler/taler_signatures.h" #include "exchange_api_curl_defaults.h" +#include "taler/taler-exchange/get-aml-OFFICER_PUB-kyc-statistics-NAMES.h" /** - * @brief A GET /aml/$OFFICER_PUB/kyc-statistics/$NAME Handle + * @brief A GET /aml/$OFFICER_PUB/kyc-statistics/$NAMES Handle (new API) */ -struct TALER_EXCHANGE_KycGetStatisticsHandle +struct TALER_EXCHANGE_GetAmlKycStatisticsHandle { /** - * The url for this request. + * The base URL of the exchange. + */ + char *base_url; + + /** + * The full URL for this request. */ char *url; @@ -49,41 +55,72 @@ struct TALER_EXCHANGE_KycGetStatisticsHandle /** * Function to call with the result. */ - TALER_EXCHANGE_KycStatisticsCallback stats_cb; + TALER_EXCHANGE_GetAmlKycStatisticsCallback cb; /** * Closure for @e cb. */ - void *stats_cb_cls; + TALER_EXCHANGE_GET_AML_KYC_STATISTICS_RESULT_CLOSURE *cb_cls; + + /** + * CURL context to use. + */ + struct GNUNET_CURL_Context *ctx; /** - * HTTP headers for the job. + * Public key of the AML officer. */ - struct curl_slist *job_headers; + struct TALER_AmlOfficerPublicKeyP officer_pub; + + /** + * Private key of the AML officer. + */ + struct TALER_AmlOfficerPrivateKeyP officer_priv; + + /** + * Space-separated list of event type names to count. + */ + char *names; + + /** + * Options for this request. + */ + struct + { + /** + * Start date for statistics window. Zero means "from the beginning". + */ + struct GNUNET_TIME_Timestamp start_date; + + /** + * End date for statistics window. #GNUNET_TIME_UNIT_FOREVER_ABS means "up to now". + */ + struct GNUNET_TIME_Timestamp end_date; + } options; }; /** - * Parse the provided decision data from the "200 OK" response. + * Parse the provided statistics data from the "200 OK" response. * - * @param[in,out] lh handle (callback may be zero'ed out) - * @param json json reply with the data for one coin + * @param[in,out] aksh handle (callback may be zero'ed out) + * @param json json reply * @return #GNUNET_OK on success, #GNUNET_SYSERR on error */ static enum GNUNET_GenericReturnValue -parse_stats_ok ( - struct TALER_EXCHANGE_KycGetStatisticsHandle *lh, +parse_stats_ok_new ( + struct TALER_EXCHANGE_GetAmlKycStatisticsHandle *aksh, const json_t *json) { - struct TALER_EXCHANGE_KycGetStatisticsResponse lr = { + struct TALER_EXCHANGE_GetAmlKycStatisticsResponse lr = { .hr.reply = json, .hr.http_status = MHD_HTTP_OK }; - uint64_t cnt; + const json_t *jstatistics; struct GNUNET_JSON_Specification spec[] = { - GNUNET_JSON_spec_uint64 ("counter", - &cnt), + GNUNET_JSON_spec_array_const ("statistics", + &jstatistics), GNUNET_JSON_spec_end () }; @@ -96,35 +133,68 @@ parse_stats_ok ( GNUNET_break_op (0); return GNUNET_SYSERR; } - lr.details.ok.counter = (unsigned long long) cnt; - lh->stats_cb (lh->stats_cb_cls, - &lr); - lh->stats_cb = NULL; + lr.details.ok.statistics_length = json_array_size (jstatistics); + { + struct TALER_EXCHANGE_GetAmlKycStatisticsEventCounter statistics[ + GNUNET_NZL (lr.details.ok.statistics_length)]; + json_t *obj; + size_t idx; + + memset (statistics, + 0, + sizeof (statistics)); + lr.details.ok.statistics = statistics; + json_array_foreach (jstatistics, idx, obj) + { + struct TALER_EXCHANGE_GetAmlKycStatisticsEventCounter *ec + = &statistics[idx]; + struct GNUNET_JSON_Specification ispec[] = { + GNUNET_JSON_spec_string ("name", + &ec->name), + GNUNET_JSON_spec_uint64 ("counter", + &ec->counter), + GNUNET_JSON_spec_end () + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (obj, + ispec, + NULL, + NULL)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + } + aksh->cb (aksh->cb_cls, + &lr); + aksh->cb = NULL; + } return GNUNET_OK; } /** * Function called when we're done processing the - * HTTP /aml/$OFFICER_PUB/kyc-statistics/$NAME request. + * HTTP GET /aml/$OFFICER_PUB/kyc-statistics/$NAMES request. * - * @param cls the `struct TALER_EXCHANGE_KycGetStatisticsHandle` + * @param cls the `struct TALER_EXCHANGE_GetAmlKycStatisticsHandle` * @param response_code HTTP response code, 0 on error * @param response parsed JSON result, NULL on error */ static void -handle_lookup_finished (void *cls, - long response_code, - const void *response) +handle_get_aml_kyc_statistics_finished (void *cls, + long response_code, + const void *response) { - struct TALER_EXCHANGE_KycGetStatisticsHandle *lh = cls; + struct TALER_EXCHANGE_GetAmlKycStatisticsHandle *aksh = cls; const json_t *j = response; - struct TALER_EXCHANGE_KycGetStatisticsResponse lr = { + struct TALER_EXCHANGE_GetAmlKycStatisticsResponse lr = { .hr.reply = j, .hr.http_status = (unsigned int) response_code }; - lh->job = NULL; + aksh->job = NULL; switch (response_code) { case 0: @@ -132,16 +202,16 @@ handle_lookup_finished (void *cls, break; case MHD_HTTP_OK: if (GNUNET_OK != - parse_stats_ok (lh, - j)) + parse_stats_ok_new (aksh, + j)) { GNUNET_break_op (0); lr.hr.http_status = 0; lr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; break; } - GNUNET_assert (NULL == lh->stats_cb); - TALER_EXCHANGE_kyc_get_statistics_cancel (lh); + GNUNET_assert (NULL == aksh->cb); + TALER_EXCHANGE_get_aml_kyc_statistics_cancel (aksh); return; case MHD_HTTP_NO_CONTENT: break; @@ -154,8 +224,6 @@ handle_lookup_finished (void *cls, case MHD_HTTP_FORBIDDEN: lr.hr.ec = TALER_JSON_get_error_code (j); lr.hr.hint = TALER_JSON_get_error_hint (j); - /* Nothing really to verify, exchange says this coin was not melted; we - should pass the JSON reply to the application */ break; case MHD_HTTP_INTERNAL_SERVER_ERROR: lr.hr.ec = TALER_JSON_get_error_code (j); @@ -174,68 +242,117 @@ handle_lookup_finished (void *cls, (int) lr.hr.ec); break; } - if (NULL != lh->stats_cb) - lh->stats_cb (lh->stats_cb_cls, - &lr); - TALER_EXCHANGE_kyc_get_statistics_cancel (lh); + if (NULL != aksh->cb) + aksh->cb (aksh->cb_cls, + &lr); + TALER_EXCHANGE_get_aml_kyc_statistics_cancel (aksh); } -struct TALER_EXCHANGE_KycGetStatisticsHandle * -TALER_EXCHANGE_kyc_get_statistics ( +struct TALER_EXCHANGE_GetAmlKycStatisticsHandle * +TALER_EXCHANGE_get_aml_kyc_statistics_create ( struct GNUNET_CURL_Context *ctx, - const char *exchange_url, - const char *name, - struct GNUNET_TIME_Timestamp start_date, - struct GNUNET_TIME_Timestamp end_date, + const char *url, const struct TALER_AmlOfficerPrivateKeyP *officer_priv, - TALER_EXCHANGE_KycStatisticsCallback cb, - void *cb_cls) + const char *names) +{ + struct TALER_EXCHANGE_GetAmlKycStatisticsHandle *aksh; + + aksh = GNUNET_new (struct TALER_EXCHANGE_GetAmlKycStatisticsHandle); + aksh->ctx = ctx; + aksh->base_url = GNUNET_strdup (url); + aksh->officer_priv = *officer_priv; + GNUNET_CRYPTO_eddsa_key_get_public (&officer_priv->eddsa_priv, + &aksh->officer_pub.eddsa_pub); + aksh->names = GNUNET_strdup (names); + /* Default: no start date filter, no end date filter */ + aksh->options.start_date = GNUNET_TIME_UNIT_ZERO_TS; + aksh->options.end_date = GNUNET_TIME_UNIT_FOREVER_TS; + return aksh; +} + + +enum GNUNET_GenericReturnValue +TALER_EXCHANGE_get_aml_kyc_statistics_set_options_ ( + struct TALER_EXCHANGE_GetAmlKycStatisticsHandle *aksh, + unsigned int num_options, + const struct TALER_EXCHANGE_GetAmlKycStatisticsOptionValue *options) +{ + for (unsigned int i = 0; i < num_options; i++) + { + const struct TALER_EXCHANGE_GetAmlKycStatisticsOptionValue *opt + = &options[i]; + + switch (opt->option) + { + case TALER_EXCHANGE_GET_AML_KYC_STATISTICS_OPTION_END: + return GNUNET_OK; + case TALER_EXCHANGE_GET_AML_KYC_STATISTICS_OPTION_START_DATE: + aksh->options.start_date = opt->details.start_date; + break; + case TALER_EXCHANGE_GET_AML_KYC_STATISTICS_OPTION_END_DATE: + aksh->options.end_date = opt->details.end_date; + break; + } + } + return GNUNET_OK; +} + + +enum TALER_ErrorCode +TALER_EXCHANGE_get_aml_kyc_statistics_start ( + struct TALER_EXCHANGE_GetAmlKycStatisticsHandle *aksh, + TALER_EXCHANGE_GetAmlKycStatisticsCallback cb, + TALER_EXCHANGE_GET_AML_KYC_STATISTICS_RESULT_CLOSURE *cb_cls) { - struct TALER_EXCHANGE_KycGetStatisticsHandle *lh; - CURL *eh; - struct TALER_AmlOfficerPublicKeyP officer_pub; struct TALER_AmlOfficerSignatureP officer_sig; - char arg_str[sizeof (struct TALER_AmlOfficerPublicKeyP) * 2 - + 32]; + CURL *eh; char sd_str[32]; char ed_str[32]; const char *sd = NULL; const char *ed = NULL; - if (! GNUNET_TIME_absolute_is_zero (start_date.abs_time)) + if (NULL != aksh->job) + { + GNUNET_break (0); + return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE; + } + aksh->cb = cb; + aksh->cb_cls = cb_cls; + TALER_officer_aml_query_sign (&aksh->officer_priv, + &officer_sig); + if (! GNUNET_TIME_absolute_is_zero (aksh->options.start_date.abs_time)) { unsigned long long sec; - sec = (unsigned long long) GNUNET_TIME_timestamp_to_s (start_date); + sec = (unsigned long long) GNUNET_TIME_timestamp_to_s ( + aksh->options.start_date); GNUNET_snprintf (sd_str, sizeof (sd_str), "%llu", sec); sd = sd_str; } - if (! GNUNET_TIME_absolute_is_never (end_date.abs_time)) + if (! GNUNET_TIME_absolute_is_never (aksh->options.end_date.abs_time)) { unsigned long long sec; - sec = (unsigned long long) GNUNET_TIME_timestamp_to_s (end_date); + sec = (unsigned long long) GNUNET_TIME_timestamp_to_s ( + aksh->options.end_date); GNUNET_snprintf (ed_str, sizeof (ed_str), "%llu", sec); ed = ed_str; } - GNUNET_CRYPTO_eddsa_key_get_public (&officer_priv->eddsa_priv, - &officer_pub.eddsa_pub); - TALER_officer_aml_query_sign (officer_priv, - &officer_sig); { - char pub_str[sizeof (officer_pub) * 2]; + char pub_str[sizeof (aksh->officer_pub) * 2]; + char arg_str[sizeof (aksh->officer_pub) * 2 + 32]; char *end; end = GNUNET_STRINGS_data_to_string ( - &officer_pub, - sizeof (officer_pub), + &aksh->officer_pub, + sizeof (aksh->officer_pub), pub_str, sizeof (pub_str)); *end = '\0'; @@ -243,32 +360,26 @@ TALER_EXCHANGE_kyc_get_statistics ( sizeof (arg_str), "aml/%s/kyc-statistics/%s", pub_str, - name); - } - lh = GNUNET_new (struct TALER_EXCHANGE_KycGetStatisticsHandle); - lh->stats_cb = cb; - lh->stats_cb_cls = cb_cls; - lh->url = TALER_url_join (exchange_url, - arg_str, - "start_date", - sd, - "end_date", - ed, - NULL); - if (NULL == lh->url) - { - GNUNET_free (lh); - return NULL; + aksh->names); + aksh->url = TALER_url_join (aksh->base_url, + arg_str, + "start_date", + sd, + "end_date", + ed, + NULL); } - eh = TALER_EXCHANGE_curl_easy_get_ (lh->url); + if (NULL == aksh->url) + return TALER_EC_GENERIC_CONFIGURATION_INVALID; + eh = TALER_EXCHANGE_curl_easy_get_ (aksh->url); if (NULL == eh) { - GNUNET_break (0); - GNUNET_free (lh->url); - GNUNET_free (lh); - return NULL; + GNUNET_free (aksh->url); + aksh->url = NULL; + return TALER_EC_GENERIC_ALLOCATION_FAILURE; } { + struct curl_slist *job_headers = NULL; char *hdr; char sig_str[sizeof (officer_sig) * 2]; char *end; @@ -279,39 +390,46 @@ TALER_EXCHANGE_kyc_get_statistics ( sig_str, sizeof (sig_str)); *end = '\0'; - GNUNET_asprintf (&hdr, "%s: %s", TALER_AML_OFFICER_SIGNATURE_HEADER, sig_str); - lh->job_headers = curl_slist_append (NULL, - hdr); + job_headers = curl_slist_append (NULL, + hdr); GNUNET_free (hdr); - lh->job_headers = curl_slist_append (lh->job_headers, - "Content-type: application/json"); - lh->job = GNUNET_CURL_job_add2 (ctx, - eh, - lh->job_headers, - &handle_lookup_finished, - lh); + job_headers = curl_slist_append (job_headers, + "Content-type: application/json"); + aksh->job = GNUNET_CURL_job_add2 (aksh->ctx, + eh, + job_headers, + &handle_get_aml_kyc_statistics_finished, + aksh); + curl_slist_free_all (job_headers); + } + if (NULL == aksh->job) + { + GNUNET_free (aksh->url); + aksh->url = NULL; + return TALER_EC_GENERIC_ALLOCATION_FAILURE; } - return lh; + return TALER_EC_NONE; } void -TALER_EXCHANGE_kyc_get_statistics_cancel ( - struct TALER_EXCHANGE_KycGetStatisticsHandle *lh) +TALER_EXCHANGE_get_aml_kyc_statistics_cancel ( + struct TALER_EXCHANGE_GetAmlKycStatisticsHandle *aksh) { - if (NULL != lh->job) + if (NULL != aksh->job) { - GNUNET_CURL_job_cancel (lh->job); - lh->job = NULL; + GNUNET_CURL_job_cancel (aksh->job); + aksh->job = NULL; } - curl_slist_free_all (lh->job_headers); - GNUNET_free (lh->url); - GNUNET_free (lh); + GNUNET_free (aksh->url); + GNUNET_free (aksh->base_url); + GNUNET_free (aksh->names); + GNUNET_free (aksh); } -/* end of exchange_api_get_kyc_statistics.c */ +/* end of exchange_api_get-aml-OFFICER_PUB-kyc-statistics-NAMES.c */ diff --git a/src/lib/exchange_api_get-aml-OFFICER_PUB-legitimizations.c b/src/lib/exchange_api_get-aml-OFFICER_PUB-legitimizations.c @@ -27,6 +27,7 @@ #include <gnunet/gnunet_curl_lib.h> #include "taler/taler_exchange_service.h" #include "taler/taler_json_lib.h" +#include "taler/taler-exchange/get-aml-OFFICER_PUB-legitimizations.h" #include "exchange_api_handle.h" #include "taler/taler_signatures.h" #include "exchange_api_curl_defaults.h" @@ -79,11 +80,6 @@ struct TALER_EXCHANGE_GetAmlLegitimizationsHandle char *url; /** - * HTTP headers for the job. - */ - struct curl_slist *job_headers; - - /** * Request options. */ struct @@ -363,7 +359,7 @@ TALER_EXCHANGE_get_aml_legitimizations_set_options_ ( } -enum TALER_EXCHANGE_AmlLegitimizationsGetStartError +enum TALER_ErrorCode TALER_EXCHANGE_get_aml_legitimizations_start ( struct TALER_EXCHANGE_GetAmlLegitimizationsHandle *algh, TALER_EXCHANGE_GetAmlLegitimizationsCallback cb, @@ -378,7 +374,7 @@ TALER_EXCHANGE_get_aml_legitimizations_start ( if (NULL != algh->job) { GNUNET_break (0); - return TALER_EXCHANGE_GET_AML_LEGITIMIZATIONS_START_E_AGAIN; + return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE; } algh->cb = cb; algh->cb_cls = cb_cls; @@ -444,11 +440,12 @@ TALER_EXCHANGE_get_aml_legitimizations_start ( if (NULL == algh->url) { GNUNET_break (0); - return TALER_EXCHANGE_GET_AML_LEGITIMIZATIONS_START_E_INTERNAL; + return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE; } { CURL *eh; + struct curl_slist *job_headers = NULL; eh = TALER_EXCHANGE_curl_easy_get_ (algh->url); if (NULL == eh) @@ -456,7 +453,7 @@ TALER_EXCHANGE_get_aml_legitimizations_start ( GNUNET_break (0); GNUNET_free (algh->url); algh->url = NULL; - return TALER_EXCHANGE_GET_AML_LEGITIMIZATIONS_START_E_INTERNAL; + return TALER_EC_GENERIC_ALLOCATION_FAILURE; } /* Add authentication header for AML officer */ @@ -475,19 +472,26 @@ TALER_EXCHANGE_get_aml_legitimizations_start ( "%s: %s", TALER_AML_OFFICER_SIGNATURE_HEADER, sig_str); - algh->job_headers = curl_slist_append (NULL, - hdr); + job_headers = curl_slist_append (NULL, + hdr); GNUNET_free (hdr); } algh->job = GNUNET_CURL_job_add2 ( algh->ctx, eh, - algh->job_headers, + job_headers, &handle_aml_legitimizations_get_finished, algh); + curl_slist_free_all (job_headers); + if (NULL == algh->job) + { + GNUNET_free (algh->url); + algh->url = NULL; + return TALER_EC_GENERIC_ALLOCATION_FAILURE; + } } - return TALER_EXCHANGE_GET_AML_LEGITIMIZATIONS_START_OK; + return TALER_EC_NONE; } @@ -500,7 +504,6 @@ TALER_EXCHANGE_get_aml_legitimizations_cancel ( GNUNET_CURL_job_cancel (algh->job); algh->job = NULL; } - curl_slist_free_all (algh->job_headers); GNUNET_free (algh->exchange_base_url); GNUNET_free (algh->url); GNUNET_free (algh); diff --git a/src/lib/exchange_api_get-aml-OFFICER_PUB-measures.c b/src/lib/exchange_api_get-aml-OFFICER_PUB-measures.c @@ -1,6 +1,6 @@ /* This file is part of TALER - Copyright (C) 2023, 2024 Taler Systems SA + Copyright (C) 2023, 2024, 2026 Taler Systems SA TALER is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software @@ -25,6 +25,7 @@ #include <gnunet/gnunet_curl_lib.h> #include "taler/taler_exchange_service.h" #include "taler/taler_json_lib.h" +#include "taler/taler-exchange/get-aml-OFFICER_PUB-measures.h" #include "exchange_api_handle.h" #include "taler/taler_signatures.h" #include "exchange_api_curl_defaults.h" @@ -55,11 +56,16 @@ struct Scrap /** * @brief A GET /aml/$OFFICER_PUB/measures Handle */ -struct TALER_EXCHANGE_AmlGetMeasuresHandle +struct TALER_EXCHANGE_GetAmlMeasuresHandle { /** - * The url for this request. + * The base URL of the exchange. + */ + char *base_url; + + /** + * The full URL for this request, set during _start. */ char *url; @@ -71,17 +77,27 @@ struct TALER_EXCHANGE_AmlGetMeasuresHandle /** * Function to call with the result. */ - TALER_EXCHANGE_AmlMeasuresCallback measures_cb; + TALER_EXCHANGE_GetAmlMeasuresCallback cb; + + /** + * Closure for @e cb. + */ + TALER_EXCHANGE_GET_AML_MEASURES_RESULT_CLOSURE *cb_cls; /** - * Closure for @e measures_cb. + * Reference to the execution context. */ - void *measures_cb_cls; + struct GNUNET_CURL_Context *ctx; + + /** + * Public key of the AML officer. + */ + struct TALER_AmlOfficerPublicKeyP officer_pub; /** - * HTTP headers for the job. + * Private key of the AML officer (for signing). */ - struct curl_slist *job_headers; + struct TALER_AmlOfficerPrivateKeyP officer_priv; /** * Head of scrap list. @@ -96,70 +112,23 @@ struct TALER_EXCHANGE_AmlGetMeasuresHandle /** - * Parse AML measures. - * - * @param jroots JSON object with measure data - * @param[out] roots where to write the result - * @return #GNUNET_OK on success - */ -static enum GNUNET_GenericReturnValue -parse_aml_roots ( - const json_t *jroots, - struct TALER_EXCHANGE_AvailableAmlMeasures *roots) -{ - const json_t *obj; - const char *name; - size_t idx = 0; - - json_object_foreach ((json_t *) jroots, name, obj) - { - struct TALER_EXCHANGE_AvailableAmlMeasures *root - = &roots[idx++]; - struct GNUNET_JSON_Specification spec[] = { - GNUNET_JSON_spec_string ("check_name", - &root->check_name), - GNUNET_JSON_spec_string ("prog_name", - &root->prog_name), - GNUNET_JSON_spec_mark_optional ( - GNUNET_JSON_spec_object_const ("context", - &root->context), - NULL), - GNUNET_JSON_spec_end () - }; - - if (GNUNET_OK != - GNUNET_JSON_parse (obj, - spec, - NULL, - NULL)) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - root->measure_name = name; - } - return GNUNET_OK; -} - - -/** * Create array of length @a len in scrap book. * - * @param[in,out] lh context for allocations + * @param[in,out] amh context for allocations * @param len length of array * @return scrap array */ static const char ** make_scrap ( - struct TALER_EXCHANGE_AmlGetMeasuresHandle *lh, + struct TALER_EXCHANGE_GetAmlMeasuresHandle *amh, unsigned int len) { struct Scrap *s = GNUNET_new (struct Scrap); s->ptr = GNUNET_new_array (len, const char *); - GNUNET_CONTAINER_DLL_insert (lh->scrap_head, - lh->scrap_tail, + GNUNET_CONTAINER_DLL_insert (amh->scrap_head, + amh->scrap_tail, s); return s->ptr; } @@ -168,17 +137,17 @@ make_scrap ( /** * Free all scrap space. * - * @param[in,out] lh scrap context + * @param[in,out] amh scrap context */ static void -free_scrap (struct TALER_EXCHANGE_AmlGetMeasuresHandle *lh) +free_scrap (struct TALER_EXCHANGE_GetAmlMeasuresHandle *amh) { struct Scrap *s; - while (NULL != (s = lh->scrap_head)) + while (NULL != (s = amh->scrap_head)) { - GNUNET_CONTAINER_DLL_remove (lh->scrap_head, - lh->scrap_tail, + GNUNET_CONTAINER_DLL_remove (amh->scrap_head, + amh->scrap_tail, s); GNUNET_free (s->ptr); GNUNET_free (s); @@ -210,18 +179,74 @@ j_to_a (const json_t *j, /** + * Parse AML root measures. + * + * @param jroots JSON object with measure data + * @param[out] roots where to write the result + * @return #GNUNET_OK on success + */ +static enum GNUNET_GenericReturnValue +parse_aml_roots ( + const json_t *jroots, + struct TALER_EXCHANGE_GetAmlMeasuresMeasureInfo *roots) +{ + const json_t *obj; + const char *name; + size_t idx = 0; + + json_object_foreach ((json_t *) jroots, name, obj) + { + struct TALER_EXCHANGE_GetAmlMeasuresMeasureInfo *root = &roots[idx++]; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_string ("check_name", + &root->check_name), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_string ("prog_name", + &root->prog_name), + NULL), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_object_const ("context", + &root->context), + NULL), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_string ("operation_type", + &root->operation_type), + NULL), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_bool ("voluntary", + &root->voluntary), + NULL), + GNUNET_JSON_spec_end () + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (obj, + spec, + NULL, + NULL)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + root->measure_name = name; + } + return GNUNET_OK; +} + + +/** * Parse AML programs. * - * @param[in,out] lh context for allocations + * @param[in,out] amh context for allocations * @param jprogs JSON object with program data * @param[out] progs where to write the result * @return #GNUNET_OK on success */ static enum GNUNET_GenericReturnValue parse_aml_programs ( - struct TALER_EXCHANGE_AmlGetMeasuresHandle *lh, + struct TALER_EXCHANGE_GetAmlMeasuresHandle *amh, const json_t *jprogs, - struct TALER_EXCHANGE_AvailableAmlPrograms *progs) + struct TALER_EXCHANGE_GetAmlMeasuresProgramRequirement *progs) { const json_t *obj; const char *name; @@ -229,8 +254,8 @@ parse_aml_programs ( json_object_foreach ((json_t *) jprogs, name, obj) { - struct TALER_EXCHANGE_AvailableAmlPrograms *prog - = &progs[idx++]; + struct TALER_EXCHANGE_GetAmlMeasuresProgramRequirement *prog = &progs[idx++] + ; const json_t *jcontext; const json_t *jinputs; struct GNUNET_JSON_Specification spec[] = { @@ -255,30 +280,23 @@ parse_aml_programs ( return GNUNET_SYSERR; } prog->prog_name = name; - prog->contexts_length - = (unsigned int) json_array_size (jcontext); - prog->inputs_length - = (unsigned int) json_array_size (jinputs); - len = prog->contexts_length + prog->inputs_length; - if ( ((unsigned long long) len) != - (unsigned long long) json_array_size (jcontext) - + (unsigned long long) json_array_size (jinputs) ) + prog->contexts_length = json_array_size (jcontext); + prog->inputs_length = json_array_size (jinputs); + len = (unsigned int) (prog->contexts_length + prog->inputs_length); + if ( ((size_t) len) != prog->contexts_length + prog->inputs_length) { GNUNET_break_op (0); return GNUNET_SYSERR; } - ptr = make_scrap (lh, - len); + ptr = make_scrap (amh, len); prog->contexts = ptr; - if (! j_to_a (jcontext, - prog->contexts)) + if (! j_to_a (jcontext, prog->contexts)) { GNUNET_break_op (0); return GNUNET_SYSERR; } prog->inputs = &ptr[prog->contexts_length]; - if (! j_to_a (jinputs, - prog->inputs)) + if (! j_to_a (jinputs, prog->inputs)) { GNUNET_break_op (0); return GNUNET_SYSERR; @@ -289,18 +307,18 @@ parse_aml_programs ( /** - * Parse AML measures. + * Parse KYC checks. * - * @param[in,out] lh context for allocations - * @param jchecks JSON object with measure data + * @param[in,out] amh context for allocations + * @param jchecks JSON object with check data * @param[out] checks where to write the result * @return #GNUNET_OK on success */ static enum GNUNET_GenericReturnValue parse_aml_checks ( - struct TALER_EXCHANGE_AmlGetMeasuresHandle *lh, + struct TALER_EXCHANGE_GetAmlMeasuresHandle *amh, const json_t *jchecks, - struct TALER_EXCHANGE_AvailableKycChecks *checks) + struct TALER_EXCHANGE_GetAmlMeasuresCheckInfo *checks) { const json_t *obj; const char *name; @@ -308,8 +326,7 @@ parse_aml_checks ( json_object_foreach ((json_t *) jchecks, name, obj) { - struct TALER_EXCHANGE_AvailableKycChecks *check - = &checks[idx++]; + struct TALER_EXCHANGE_GetAmlMeasuresCheckInfo *check = &checks[idx++]; const json_t *jrequires; const json_t *joutputs; struct GNUNET_JSON_Specification spec[] = { @@ -340,58 +357,125 @@ parse_aml_checks ( return GNUNET_SYSERR; } check->check_name = name; - - check->requires_length - = (unsigned int) json_array_size (jrequires); - check->outputs_length - = (unsigned int) json_array_size (joutputs); - len = check->requires_length + check->outputs_length; - if ( ((unsigned long long) len) != - (unsigned long long) json_array_size (jrequires) - + (unsigned long long) json_array_size (joutputs) ) + check->requires_length = json_array_size (jrequires); + check->outputs_length = json_array_size (joutputs); + len = (unsigned int) (check->requires_length + check->outputs_length); + if ( ((size_t) len) != check->requires_length + check->outputs_length) { GNUNET_break_op (0); return GNUNET_SYSERR; } - ptr = make_scrap (lh, - len); + ptr = make_scrap (amh, len); check->requires = ptr; - if (! j_to_a (jrequires, - check->requires)) + if (! j_to_a (jrequires, check->requires)) { GNUNET_break_op (0); return GNUNET_SYSERR; } check->outputs = &ptr[check->requires_length]; - if (! j_to_a (joutputs, - check->outputs)) + if (! j_to_a (joutputs, check->outputs)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + } + return GNUNET_OK; +} + + +/** + * Parse default KYC rules from the default_rules array. + * + * @param[in,out] amh context for allocations + * @param jrules JSON array with rule data + * @param[out] rules where to write the result + * @return #GNUNET_OK on success + */ +static enum GNUNET_GenericReturnValue +parse_default_rules ( + struct TALER_EXCHANGE_GetAmlMeasuresHandle *amh, + const json_t *jrules, + struct TALER_EXCHANGE_GetAmlMeasuresKycRule *rules) +{ + const json_t *obj; + size_t idx; + + json_array_foreach ((json_t *) jrules, idx, obj) + { + struct TALER_EXCHANGE_GetAmlMeasuresKycRule *rule = &rules[idx]; + const json_t *jmeasures; + struct GNUNET_JSON_Specification spec[] = { + TALER_JSON_spec_kycte ("operation_type", + &rule->operation_type), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_string ("rule_name", + &rule->rule_name), + NULL), + TALER_JSON_spec_amount_any ("threshold", + &rule->threshold), + GNUNET_JSON_spec_relative_time ("timeframe", + &rule->timeframe), + GNUNET_JSON_spec_array_const ("measures", + &jmeasures), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_bool ("exposed", + &rule->exposed), + NULL), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_bool ("is_and_combinator", + &rule->is_and_combinator), + NULL), + GNUNET_JSON_spec_int64 ("display_priority", + &rule->display_priority), + GNUNET_JSON_spec_end () + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (obj, + spec, + NULL, + NULL)) { GNUNET_break_op (0); return GNUNET_SYSERR; } + rule->measures_length = json_array_size (jmeasures); + { + const char **ptr = make_scrap (amh, + (unsigned int) rule->measures_length); + + rule->measures = ptr; + if (! j_to_a (jmeasures, + ptr)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + } } return GNUNET_OK; } /** - * Parse the provided decision data from the "200 OK" response. + * Parse the provided measures data from the "200 OK" response. * - * @param[in,out] lh handle (callback may be zero'ed out) - * @param json json reply with the data for one coin + * @param[in,out] amh handle (callback may be zero'ed out) + * @param json json reply with the data * @return #GNUNET_OK on success, #GNUNET_SYSERR on error */ static enum GNUNET_GenericReturnValue -parse_measures_ok (struct TALER_EXCHANGE_AmlGetMeasuresHandle *lh, - const json_t *json) +parse_get_aml_measures_ok (struct TALER_EXCHANGE_GetAmlMeasuresHandle *amh, + const json_t *json) { - struct TALER_EXCHANGE_AmlGetMeasuresResponse lr = { + struct TALER_EXCHANGE_GetAmlMeasuresResponse lr = { .hr.reply = json, .hr.http_status = MHD_HTTP_OK }; const json_t *jroots; const json_t *jprograms; const json_t *jchecks; + const json_t *jdefault_rules = NULL; struct GNUNET_JSON_Specification spec[] = { GNUNET_JSON_spec_object_const ("roots", &jroots), @@ -399,6 +483,10 @@ parse_measures_ok (struct TALER_EXCHANGE_AmlGetMeasuresHandle *lh, &jprograms), GNUNET_JSON_spec_object_const ("checks", &jchecks), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_array_const ("default_rules", + &jdefault_rules), + NULL), GNUNET_JSON_spec_end () }; @@ -411,61 +499,47 @@ parse_measures_ok (struct TALER_EXCHANGE_AmlGetMeasuresHandle *lh, GNUNET_break_op (0); return GNUNET_SYSERR; } - lr.details.ok.roots_length - = (unsigned int) json_object_size (jroots); - lr.details.ok.programs_length - = (unsigned int) json_object_size (jprograms); - lr.details.ok.checks_length - = (unsigned int) json_object_size (jchecks); - if ( ( ((size_t) lr.details.ok.roots_length) - != json_object_size (jroots)) || - ( ((size_t) lr.details.ok.programs_length) - != json_object_size (jprograms)) || - ( ((size_t) lr.details.ok.checks_length) - != json_object_size (jchecks)) ) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } + + lr.details.ok.roots_length = json_object_size (jroots); + lr.details.ok.programs_length = json_object_size (jprograms); + lr.details.ok.checks_length = json_object_size (jchecks); + lr.details.ok.default_rules_length = + (NULL != jdefault_rules) ? json_array_size (jdefault_rules) : 0; { - struct TALER_EXCHANGE_AvailableAmlMeasures roots[ + struct TALER_EXCHANGE_GetAmlMeasuresMeasureInfo roots[ GNUNET_NZL (lr.details.ok.roots_length)]; - struct TALER_EXCHANGE_AvailableAmlPrograms progs[ + struct TALER_EXCHANGE_GetAmlMeasuresProgramRequirement progs[ GNUNET_NZL (lr.details.ok.programs_length)]; - struct TALER_EXCHANGE_AvailableKycChecks checks[ + struct TALER_EXCHANGE_GetAmlMeasuresCheckInfo checks[ GNUNET_NZL (lr.details.ok.checks_length)]; + struct TALER_EXCHANGE_GetAmlMeasuresKycRule drules[ + GNUNET_NZL (lr.details.ok.default_rules_length)]; enum GNUNET_GenericReturnValue ret; - memset (roots, - 0, - sizeof (roots)); - memset (progs, - 0, - sizeof (progs)); - memset (checks, - 0, - sizeof (checks)); + memset (roots, 0, sizeof (roots)); + memset (progs, 0, sizeof (progs)); + memset (checks, 0, sizeof (checks)); + memset (drules, 0, sizeof (drules)); lr.details.ok.roots = roots; lr.details.ok.programs = progs; lr.details.ok.checks = checks; - ret = parse_aml_roots (jroots, - roots); + lr.details.ok.default_rules = drules; + + ret = parse_aml_roots (jroots, roots); if (GNUNET_OK == ret) - ret = parse_aml_programs (lh, - jprograms, - progs); + ret = parse_aml_programs (amh, jprograms, progs); if (GNUNET_OK == ret) - ret = parse_aml_checks (lh, - jchecks, - checks); + ret = parse_aml_checks (amh, jchecks, checks); + if ( (GNUNET_OK == ret) && (NULL != jdefault_rules) ) + ret = parse_default_rules (amh, jdefault_rules, drules); if (GNUNET_OK == ret) { - lh->measures_cb (lh->measures_cb_cls, - &lr); - lh->measures_cb = NULL; + amh->cb (amh->cb_cls, + &lr); + amh->cb = NULL; } - free_scrap (lh); + free_scrap (amh); return ret; } } @@ -473,25 +547,25 @@ parse_measures_ok (struct TALER_EXCHANGE_AmlGetMeasuresHandle *lh, /** * Function called when we're done processing the - * HTTP /aml/$OFFICER_PUB/measures request. + * HTTP GET /aml/$OFFICER_PUB/measures request. * - * @param cls the `struct TALER_EXCHANGE_AmlGetMeasuresHandle` + * @param cls the `struct TALER_EXCHANGE_GetAmlMeasuresHandle` * @param response_code HTTP response code, 0 on error * @param response parsed JSON result, NULL on error */ static void -handle_lookup_finished (void *cls, - long response_code, - const void *response) +handle_get_aml_measures_finished (void *cls, + long response_code, + const void *response) { - struct TALER_EXCHANGE_AmlGetMeasuresHandle *lh = cls; + struct TALER_EXCHANGE_GetAmlMeasuresHandle *amh = cls; const json_t *j = response; - struct TALER_EXCHANGE_AmlGetMeasuresResponse lr = { + struct TALER_EXCHANGE_GetAmlMeasuresResponse lr = { .hr.reply = j, .hr.http_status = (unsigned int) response_code }; - lh->job = NULL; + amh->job = NULL; switch (response_code) { case 0: @@ -499,36 +573,30 @@ handle_lookup_finished (void *cls, break; case MHD_HTTP_OK: if (GNUNET_OK != - parse_measures_ok (lh, - j)) + parse_get_aml_measures_ok (amh, + j)) { GNUNET_break_op (0); lr.hr.http_status = 0; lr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; break; } - GNUNET_assert (NULL == lh->measures_cb); - TALER_EXCHANGE_aml_get_measures_cancel (lh); + GNUNET_assert (NULL == amh->cb); + TALER_EXCHANGE_get_aml_measures_cancel (amh); return; case MHD_HTTP_NO_CONTENT: break; case MHD_HTTP_BAD_REQUEST: lr.hr.ec = TALER_JSON_get_error_code (j); lr.hr.hint = TALER_JSON_get_error_hint (j); - /* This should never happen, either us or the exchange is buggy - (or API version conflict); just pass JSON reply to the application */ break; case MHD_HTTP_FORBIDDEN: lr.hr.ec = TALER_JSON_get_error_code (j); lr.hr.hint = TALER_JSON_get_error_hint (j); - /* Nothing really to verify, exchange says this coin was not melted; we - should pass the JSON reply to the application */ break; case MHD_HTTP_INTERNAL_SERVER_ERROR: lr.hr.ec = TALER_JSON_get_error_code (j); lr.hr.hint = TALER_JSON_get_error_hint (j); - /* Server had an internal issue; we should retry, but this API - leaves this to the application */ break; default: /* unexpected response code */ @@ -536,44 +604,62 @@ handle_lookup_finished (void *cls, lr.hr.ec = TALER_JSON_get_error_code (j); lr.hr.hint = TALER_JSON_get_error_hint (j); GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Unexpected response code %u/%d for get AML measures\n", + "Unexpected response code %u/%d for GET AML measures\n", (unsigned int) response_code, (int) lr.hr.ec); break; } - if (NULL != lh->measures_cb) - lh->measures_cb (lh->measures_cb_cls, - &lr); - TALER_EXCHANGE_aml_get_measures_cancel (lh); + if (NULL != amh->cb) + amh->cb (amh->cb_cls, + &lr); + TALER_EXCHANGE_get_aml_measures_cancel (amh); } -struct TALER_EXCHANGE_AmlGetMeasuresHandle * -TALER_EXCHANGE_aml_get_measures ( +struct TALER_EXCHANGE_GetAmlMeasuresHandle * +TALER_EXCHANGE_get_aml_measures_create ( struct GNUNET_CURL_Context *ctx, - const char *exchange_url, - const struct TALER_AmlOfficerPrivateKeyP *officer_priv, - TALER_EXCHANGE_AmlMeasuresCallback cb, - void *cb_cls) + const char *url, + const struct TALER_AmlOfficerPrivateKeyP *officer_priv) +{ + struct TALER_EXCHANGE_GetAmlMeasuresHandle *amh; + + amh = GNUNET_new (struct TALER_EXCHANGE_GetAmlMeasuresHandle); + amh->ctx = ctx; + amh->base_url = GNUNET_strdup (url); + amh->officer_priv = *officer_priv; + GNUNET_CRYPTO_eddsa_key_get_public (&officer_priv->eddsa_priv, + &amh->officer_pub.eddsa_pub); + return amh; +} + + +enum TALER_ErrorCode +TALER_EXCHANGE_get_aml_measures_start ( + struct TALER_EXCHANGE_GetAmlMeasuresHandle *amh, + TALER_EXCHANGE_GetAmlMeasuresCallback cb, + TALER_EXCHANGE_GET_AML_MEASURES_RESULT_CLOSURE *cb_cls) { - struct TALER_EXCHANGE_AmlGetMeasuresHandle *lh; CURL *eh; - struct TALER_AmlOfficerPublicKeyP officer_pub; struct TALER_AmlOfficerSignatureP officer_sig; - char arg_str[sizeof (struct TALER_AmlOfficerPublicKeyP) * 2 - + 32]; + char arg_str[sizeof (struct TALER_AmlOfficerPublicKeyP) * 2 + 32]; + struct curl_slist *job_headers = NULL; - GNUNET_CRYPTO_eddsa_key_get_public (&officer_priv->eddsa_priv, - &officer_pub.eddsa_pub); - TALER_officer_aml_query_sign (officer_priv, + amh->cb = cb; + amh->cb_cls = cb_cls; + + /* Build AML officer signature */ + TALER_officer_aml_query_sign (&amh->officer_priv, &officer_sig); + + /* Build the path component: aml/{officer_pub}/measures */ { - char pub_str[sizeof (officer_pub) * 2]; + char pub_str[sizeof (amh->officer_pub) * 2]; char *end; end = GNUNET_STRINGS_data_to_string ( - &officer_pub, - sizeof (officer_pub), + &amh->officer_pub, + sizeof (amh->officer_pub), pub_str, sizeof (pub_str)); *end = '\0'; @@ -582,25 +668,23 @@ TALER_EXCHANGE_aml_get_measures ( "aml/%s/measures", pub_str); } - lh = GNUNET_new (struct TALER_EXCHANGE_AmlGetMeasuresHandle); - lh->measures_cb = cb; - lh->measures_cb_cls = cb_cls; - lh->url = TALER_url_join (exchange_url, - arg_str, - NULL); - if (NULL == lh->url) - { - GNUNET_free (lh); - return NULL; - } - eh = TALER_EXCHANGE_curl_easy_get_ (lh->url); + + amh->url = TALER_url_join (amh->base_url, + arg_str, + NULL); + if (NULL == amh->url) + return TALER_EC_GENERIC_CONFIGURATION_INVALID; + + eh = TALER_EXCHANGE_curl_easy_get_ (amh->url); if (NULL == eh) { GNUNET_break (0); - GNUNET_free (lh->url); - GNUNET_free (lh); - return NULL; + GNUNET_free (amh->url); + amh->url = NULL; + return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE; } + + /* Build job headers with AML officer signature */ { char *hdr; char sig_str[sizeof (officer_sig) * 2]; @@ -617,34 +701,44 @@ TALER_EXCHANGE_aml_get_measures ( "%s: %s", TALER_AML_OFFICER_SIGNATURE_HEADER, sig_str); - lh->job_headers = curl_slist_append (NULL, - hdr); + job_headers = curl_slist_append (NULL, + hdr); GNUNET_free (hdr); - lh->job_headers = curl_slist_append (lh->job_headers, - "Content-type: application/json"); - lh->job = GNUNET_CURL_job_add2 (ctx, - eh, - lh->job_headers, - &handle_lookup_finished, - lh); + job_headers = curl_slist_append (job_headers, + "Content-Type: application/json"); + } + + amh->job = GNUNET_CURL_job_add2 (amh->ctx, + eh, + job_headers, + &handle_get_aml_measures_finished, + amh); + curl_slist_free_all (job_headers); + + if (NULL == amh->job) + { + GNUNET_free (amh->url); + amh->url = NULL; + return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE; } - return lh; + return TALER_EC_NONE; } void -TALER_EXCHANGE_aml_get_measures_cancel ( - struct TALER_EXCHANGE_AmlGetMeasuresHandle *lh) +TALER_EXCHANGE_get_aml_measures_cancel ( + struct TALER_EXCHANGE_GetAmlMeasuresHandle *amh) { - if (NULL != lh->job) + if (NULL != amh->job) { - GNUNET_CURL_job_cancel (lh->job); - lh->job = NULL; + GNUNET_CURL_job_cancel (amh->job); + amh->job = NULL; } - curl_slist_free_all (lh->job_headers); - GNUNET_free (lh->url); - GNUNET_free (lh); + free_scrap (amh); + GNUNET_free (amh->url); + GNUNET_free (amh->base_url); + GNUNET_free (amh); } -/* end of exchange_api_get_aml_measures.c */ +/* end of exchange_api_get-aml-OFFICER_PUB-measures.c */ diff --git a/src/lib/exchange_api_get-kyc-check-H_NORMALIZED_PAYTO.c b/src/lib/exchange_api_get-kyc-check-H_NORMALIZED_PAYTO.c @@ -1,6 +1,6 @@ /* This file is part of TALER - Copyright (C) 2021-2024 Taler Systems SA + Copyright (C) 2021-2026 Taler Systems SA TALER is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software @@ -25,19 +25,24 @@ #include <gnunet/gnunet_curl_lib.h> #include "taler/taler_exchange_service.h" #include "taler/taler_json_lib.h" -#include "exchange_api_handle.h" +#include "taler/taler-exchange/get-kyc-check-H_NORMALIZED_PAYTO.h" #include "taler/taler_signatures.h" #include "exchange_api_curl_defaults.h" /** - * @brief A ``/kyc-check`` handle + * @brief A GET /kyc-check/$H_NORMALIZED_PAYTO handle */ -struct TALER_EXCHANGE_KycCheckHandle +struct TALER_EXCHANGE_GetKycCheckHandle { /** - * The url for this request. + * The base URL for this request. + */ + char *base_url; + + /** + * The full URL for this request, set during _start. */ char *url; @@ -49,21 +54,60 @@ struct TALER_EXCHANGE_KycCheckHandle /** * Function to call with the result. */ - TALER_EXCHANGE_KycStatusCallback cb; + TALER_EXCHANGE_GetKycCheckCallback cb; /** * Closure for @e cb. */ - void *cb_cls; + TALER_EXCHANGE_GET_KYC_CHECK_RESULT_CLOSURE *cb_cls; + + /** + * Reference to the execution context. + */ + struct GNUNET_CURL_Context *ctx; + + /** + * Hash of the payto URI we are checking. + */ + struct TALER_NormalizedPaytoHashP h_payto; + + /** + * Private key to authorize the request. + */ + union TALER_AccountPrivateKeyP account_priv; + + /** + * Long polling target. + */ + enum TALER_EXCHANGE_KycLongPollTarget lpt; + + /** + * Latest known rule generation (for long polling). + */ + uint64_t known_rule_gen; + + /** + * Long polling timeout. + */ + struct GNUNET_TIME_Relative timeout; }; +/** + * Parse an account KYC status from JSON and invoke the callback. + * + * @param[in,out] gkch handle + * @param j JSON to parse + * @param res response to fill + * @param status account status field within @a res to fill + * @return #GNUNET_OK on success + */ static enum GNUNET_GenericReturnValue parse_account_status ( - struct TALER_EXCHANGE_KycCheckHandle *kch, + struct TALER_EXCHANGE_GetKycCheckHandle *gkch, const json_t *j, - struct TALER_EXCHANGE_KycStatus *ks, + struct TALER_EXCHANGE_GetKycCheckResponse *res, struct TALER_EXCHANGE_AccountKycStatus *status) { const json_t *limits = NULL; @@ -126,13 +170,13 @@ parse_account_status ( } status->limits = ala; status->limits_length = limit_length; - kch->cb (kch->cb_cls, - ks); + gkch->cb (gkch->cb_cls, + res); } else { - kch->cb (kch->cb_cls, - ks); + gkch->cb (gkch->cb_cls, + res); } GNUNET_JSON_parse_free (spec); return GNUNET_OK; @@ -141,9 +185,9 @@ parse_account_status ( /** * Function called when we're done processing the - * HTTP /kyc-check request. + * HTTP GET /kyc-check/$H_NORMALIZED_PAYTO request. * - * @param cls the `struct TALER_EXCHANGE_KycCheckHandle` + * @param cls the `struct TALER_EXCHANGE_GetKycCheckHandle` * @param response_code HTTP response code, 0 on error * @param response parsed JSON result, NULL on error */ @@ -152,14 +196,14 @@ handle_kyc_check_finished (void *cls, long response_code, const void *response) { - struct TALER_EXCHANGE_KycCheckHandle *kch = cls; + struct TALER_EXCHANGE_GetKycCheckHandle *gkch = cls; const json_t *j = response; - struct TALER_EXCHANGE_KycStatus ks = { + struct TALER_EXCHANGE_GetKycCheckResponse ks = { .hr.reply = j, .hr.http_status = (unsigned int) response_code }; - kch->job = NULL; + gkch->job = NULL; switch (response_code) { case 0: @@ -168,7 +212,7 @@ handle_kyc_check_finished (void *cls, case MHD_HTTP_OK: { if (GNUNET_OK != - parse_account_status (kch, + parse_account_status (gkch, j, &ks, &ks.details.ok)) @@ -178,13 +222,13 @@ handle_kyc_check_finished (void *cls, ks.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; break; } - TALER_EXCHANGE_kyc_check_cancel (kch); + TALER_EXCHANGE_get_kyc_check_cancel (gkch); return; } case MHD_HTTP_ACCEPTED: { if (GNUNET_OK != - parse_account_status (kch, + parse_account_status (gkch, j, &ks, &ks.details.accepted)) @@ -194,15 +238,13 @@ handle_kyc_check_finished (void *cls, ks.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; break; } - TALER_EXCHANGE_kyc_check_cancel (kch); + TALER_EXCHANGE_get_kyc_check_cancel (gkch); return; } case MHD_HTTP_NO_CONTENT: break; case MHD_HTTP_BAD_REQUEST: ks.hr.ec = TALER_JSON_get_error_code (j); - /* This should never happen, either us or the exchange is buggy - (or API version conflict); just pass JSON reply to the application */ break; case MHD_HTTP_FORBIDDEN: { @@ -235,8 +277,6 @@ handle_kyc_check_finished (void *cls, break; case MHD_HTTP_INTERNAL_SERVER_ERROR: ks.hr.ec = TALER_JSON_get_error_code (j); - /* Server had an internal issue; we should retry, but this API - leaves this to the application */ break; default: /* unexpected response code */ @@ -248,25 +288,72 @@ handle_kyc_check_finished (void *cls, (int) ks.hr.ec); break; } - kch->cb (kch->cb_cls, - &ks); - TALER_EXCHANGE_kyc_check_cancel (kch); + if (NULL != gkch->cb) + { + gkch->cb (gkch->cb_cls, + &ks); + gkch->cb = NULL; + } + TALER_EXCHANGE_get_kyc_check_cancel (gkch); } -struct TALER_EXCHANGE_KycCheckHandle * -TALER_EXCHANGE_kyc_check ( +struct TALER_EXCHANGE_GetKycCheckHandle * +TALER_EXCHANGE_get_kyc_check_create ( struct GNUNET_CURL_Context *ctx, const char *url, const struct TALER_NormalizedPaytoHashP *h_payto, - const union TALER_AccountPrivateKeyP *account_priv, - uint64_t known_rule_gen, - enum TALER_EXCHANGE_KycLongPollTarget lpt, - struct GNUNET_TIME_Relative timeout, - TALER_EXCHANGE_KycStatusCallback cb, - void *cb_cls) + const union TALER_AccountPrivateKeyP *pk) +{ + struct TALER_EXCHANGE_GetKycCheckHandle *gkch; + + gkch = GNUNET_new (struct TALER_EXCHANGE_GetKycCheckHandle); + gkch->ctx = ctx; + gkch->base_url = GNUNET_strdup (url); + gkch->h_payto = *h_payto; + gkch->account_priv = *pk; + return gkch; +} + + +enum GNUNET_GenericReturnValue +TALER_EXCHANGE_get_kyc_check_set_options_ ( + struct TALER_EXCHANGE_GetKycCheckHandle *gkch, + unsigned int num_options, + const struct TALER_EXCHANGE_GetKycCheckOptionValue *options) +{ + for (unsigned int i = 0; i < num_options; i++) + { + const struct TALER_EXCHANGE_GetKycCheckOptionValue *opt = &options[i]; + + switch (opt->option) + { + case TALER_EXCHANGE_GET_KYC_CHECK_OPTION_END: + return GNUNET_OK; + case TALER_EXCHANGE_GET_KYC_CHECK_OPTION_KNOWN_RULE_GEN: + gkch->known_rule_gen = opt->details.known_rule_gen; + break; + case TALER_EXCHANGE_GET_KYC_CHECK_OPTION_LPT: + gkch->lpt = opt->details.lpt; + break; + case TALER_EXCHANGE_GET_KYC_CHECK_OPTION_TIMEOUT: + gkch->timeout = opt->details.timeout; + break; + default: + GNUNET_break (0); + return GNUNET_SYSERR; + } + } + return GNUNET_OK; +} + + +enum TALER_ErrorCode +TALER_EXCHANGE_get_kyc_check_start ( + struct TALER_EXCHANGE_GetKycCheckHandle *gkch, + TALER_EXCHANGE_GetKycCheckCallback cb, + TALER_EXCHANGE_GET_KYC_CHECK_RESULT_CLOSURE *cb_cls) { - struct TALER_EXCHANGE_KycCheckHandle *kch; CURL *eh; char arg_str[128]; char timeout_ms[32]; @@ -275,19 +362,21 @@ TALER_EXCHANGE_kyc_check ( struct curl_slist *job_headers = NULL; unsigned long long tms; + gkch->cb = cb; + gkch->cb_cls = cb_cls; { char *hps; hps = GNUNET_STRINGS_data_to_string_alloc ( - h_payto, - sizeof (*h_payto)); + &gkch->h_payto, + sizeof (gkch->h_payto)); GNUNET_snprintf (arg_str, sizeof (arg_str), "kyc-check/%s", hps); GNUNET_free (hps); } - tms = timeout.rel_value_us + tms = gkch->timeout.rel_value_us / GNUNET_TIME_UNIT_MILLISECONDS.rel_value_us; GNUNET_snprintf (timeout_ms, sizeof (timeout_ms), @@ -296,43 +385,37 @@ TALER_EXCHANGE_kyc_check ( GNUNET_snprintf (krg_str, sizeof (krg_str), "%llu", - (unsigned long long) known_rule_gen); + (unsigned long long) gkch->known_rule_gen); GNUNET_snprintf (lpt_str, sizeof (lpt_str), "%d", - (int) lpt); - kch = GNUNET_new (struct TALER_EXCHANGE_KycCheckHandle); - kch->cb = cb; - kch->cb_cls = cb_cls; - kch->url + (int) gkch->lpt); + gkch->url = TALER_url_join ( - url, + gkch->base_url, arg_str, "timeout_ms", - GNUNET_TIME_relative_is_zero (timeout) + GNUNET_TIME_relative_is_zero (gkch->timeout) ? NULL : timeout_ms, "min_rule", - 0 == known_rule_gen + 0 == gkch->known_rule_gen ? NULL : krg_str, "lpt", - TALER_EXCHANGE_KLPT_NONE == lpt + TALER_EXCHANGE_KLPT_NONE == gkch->lpt ? NULL : lpt_str, NULL); - if (NULL == kch->url) - { - GNUNET_free (kch); - return NULL; - } - eh = TALER_EXCHANGE_curl_easy_get_ (kch->url); + if (NULL == gkch->url) + return TALER_EC_GENERIC_CONFIGURATION_INVALID; + eh = TALER_EXCHANGE_curl_easy_get_ (gkch->url); if (NULL == eh) { GNUNET_break (0); - GNUNET_free (kch->url); - GNUNET_free (kch); - return NULL; + GNUNET_free (gkch->url); + gkch->url = NULL; + return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE; } if (0 != tms) { @@ -353,9 +436,9 @@ TALER_EXCHANGE_kyc_check ( char *hdr; GNUNET_CRYPTO_eddsa_key_get_public ( - &account_priv->reserve_priv.eddsa_priv, + &gkch->account_priv.reserve_priv.eddsa_priv, &account_pub.reserve_pub.eddsa_pub); - TALER_account_kyc_auth_sign (account_priv, + TALER_account_kyc_auth_sign (&gkch->account_priv, &account_sig); sig_hdr = GNUNET_STRINGS_data_to_string_alloc ( @@ -385,31 +468,41 @@ TALER_EXCHANGE_kyc_check ( { GNUNET_break (0); curl_easy_cleanup (eh); - return NULL; + GNUNET_free (gkch->url); + gkch->url = NULL; + return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE; } } - kch->job - = GNUNET_CURL_job_add2 (ctx, + gkch->job + = GNUNET_CURL_job_add2 (gkch->ctx, eh, job_headers, &handle_kyc_check_finished, - kch); + gkch); curl_slist_free_all (job_headers); - return kch; + if (NULL == gkch->job) + { + GNUNET_free (gkch->url); + gkch->url = NULL; + return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE; + } + return TALER_EC_NONE; } void -TALER_EXCHANGE_kyc_check_cancel (struct TALER_EXCHANGE_KycCheckHandle *kch) +TALER_EXCHANGE_get_kyc_check_cancel ( + struct TALER_EXCHANGE_GetKycCheckHandle *gkch) { - if (NULL != kch->job) + if (NULL != gkch->job) { - GNUNET_CURL_job_cancel (kch->job); - kch->job = NULL; + GNUNET_CURL_job_cancel (gkch->job); + gkch->job = NULL; } - GNUNET_free (kch->url); - GNUNET_free (kch); + GNUNET_free (gkch->url); + GNUNET_free (gkch->base_url); + GNUNET_free (gkch); } -/* end of exchange_api_kyc_check.c */ +/* end of exchange_api_get-kyc-check-H_NORMALIZED_PAYTO.c */ diff --git a/src/lib/exchange_api_get-kyc-info-ACCESS_TOKEN.c b/src/lib/exchange_api_get-kyc-info-ACCESS_TOKEN.c @@ -1,6 +1,6 @@ /* This file is part of TALER - Copyright (C) 2015-2023 Taler Systems SA + Copyright (C) 2015-2026 Taler Systems SA TALER is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software @@ -25,19 +25,24 @@ #include <gnunet/gnunet_curl_lib.h> #include "taler/taler_exchange_service.h" #include "taler/taler_json_lib.h" -#include "exchange_api_handle.h" +#include "taler/taler-exchange/get-kyc-info-ACCESS_TOKEN.h" #include "taler/taler_signatures.h" #include "exchange_api_curl_defaults.h" /** - * @brief A /kyc-info Handle + * @brief A GET /kyc-info/$AT handle */ -struct TALER_EXCHANGE_KycInfoHandle +struct TALER_EXCHANGE_GetKycInfoHandle { /** - * The url for this request. + * The base URL for this request. + */ + char *base_url; + + /** + * The full URL for this request, set during _start. */ char *url; @@ -49,31 +54,51 @@ struct TALER_EXCHANGE_KycInfoHandle /** * Function to call with the result. */ - TALER_EXCHANGE_KycInfoCallback kyc_info_cb; + TALER_EXCHANGE_GetKycInfoCallback cb; /** * Closure for @e cb. */ - void *kyc_info_cb_cls; + TALER_EXCHANGE_GET_KYC_INFO_RESULT_CLOSURE *cb_cls; + + /** + * Reference to the execution context. + */ + struct GNUNET_CURL_Context *ctx; + + /** + * Access token for the KYC process. + */ + struct TALER_AccountAccessTokenP token; + + /** + * ETag from a previous response for conditional requests. + * Borrowed pointer, not owned. + */ + const char *if_none_match; + + /** + * Long polling timeout. + */ + struct GNUNET_TIME_Relative timeout; }; /** - * Parse the provided kyc_infoage data from the "200 OK" response - * for one of the coins. + * Parse the provided kyc-info data from the "200 OK" response. * - * @param[in,out] lh kyc_info handle (callback may be zero'ed out) - * @param json json reply with the data for one coin + * @param[in,out] lh handle (callback may be zero'ed out) + * @param json json reply with the data * @return #GNUNET_OK on success, #GNUNET_SYSERR on error */ static enum GNUNET_GenericReturnValue -parse_kyc_info_ok (struct TALER_EXCHANGE_KycInfoHandle *lh, +parse_kyc_info_ok (struct TALER_EXCHANGE_GetKycInfoHandle *lh, const json_t *json) { const json_t *jrequirements = NULL; const json_t *jvoluntary_checks = NULL; - struct TALER_EXCHANGE_KycProcessClientInformation lr = { + struct TALER_EXCHANGE_GetKycInfoResponse lr = { .hr.reply = json, .hr.http_status = MHD_HTTP_OK }; @@ -98,22 +123,8 @@ parse_kyc_info_ok (struct TALER_EXCHANGE_KycInfoHandle *lh, return GNUNET_SYSERR; } - lr.details.ok.vci_length - = (unsigned int) json_object_size (jvoluntary_checks); - if ( ((size_t) lr.details.ok.vci_length) - != json_object_size (jvoluntary_checks)) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } - lr.details.ok.requirements_length - = json_array_size (jrequirements); - if ( ((size_t) lr.details.ok.requirements_length) - != json_array_size (jrequirements)) - { - GNUNET_break_op (0); - return GNUNET_SYSERR; - } + lr.details.ok.vci_length = json_object_size (jvoluntary_checks); + lr.details.ok.requirements_length = json_array_size (jrequirements); { struct TALER_EXCHANGE_VoluntaryCheckInformation vci[ @@ -190,9 +201,9 @@ parse_kyc_info_ok (struct TALER_EXCHANGE_KycInfoHandle *lh, lr.details.ok.vci = vci; lr.details.ok.requirements = requirements; - lh->kyc_info_cb (lh->kyc_info_cb_cls, - &lr); - lh->kyc_info_cb = NULL; + lh->cb (lh->cb_cls, + &lr); + lh->cb = NULL; return GNUNET_OK; } } @@ -200,9 +211,9 @@ parse_kyc_info_ok (struct TALER_EXCHANGE_KycInfoHandle *lh, /** * Function called when we're done processing the - * HTTP /kyc-info/$AT request. + * HTTP GET /kyc-info/$AT request. * - * @param cls the `struct TALER_EXCHANGE_KycInfoHandle` + * @param cls the `struct TALER_EXCHANGE_GetKycInfoHandle` * @param response_code HTTP response code, 0 on error * @param response parsed JSON result, NULL on error */ @@ -211,9 +222,9 @@ handle_kyc_info_finished (void *cls, long response_code, const void *response) { - struct TALER_EXCHANGE_KycInfoHandle *lh = cls; + struct TALER_EXCHANGE_GetKycInfoHandle *lh = cls; const json_t *j = response; - struct TALER_EXCHANGE_KycProcessClientInformation lr = { + struct TALER_EXCHANGE_GetKycInfoResponse lr = { .hr.reply = j, .hr.http_status = (unsigned int) response_code }; @@ -234,16 +245,14 @@ handle_kyc_info_finished (void *cls, lr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; break; } - GNUNET_assert (NULL == lh->kyc_info_cb); - TALER_EXCHANGE_kyc_info_cancel (lh); + GNUNET_assert (NULL == lh->cb); + TALER_EXCHANGE_get_kyc_info_cancel (lh); return; case MHD_HTTP_NO_CONTENT: break; case MHD_HTTP_BAD_REQUEST: lr.hr.ec = TALER_JSON_get_error_code (j); lr.hr.hint = TALER_JSON_get_error_hint (j); - /* This should never happen, either us or the exchange is buggy - (or API version conflict); just pass JSON reply to the application */ break; case MHD_HTTP_FORBIDDEN: lr.hr.ec = TALER_JSON_get_error_code (j); @@ -252,14 +261,10 @@ handle_kyc_info_finished (void *cls, case MHD_HTTP_NOT_FOUND: lr.hr.ec = TALER_JSON_get_error_code (j); lr.hr.hint = TALER_JSON_get_error_hint (j); - /* Nothing really to verify, exchange says this coin was not melted; we - should pass the JSON reply to the application */ break; case MHD_HTTP_INTERNAL_SERVER_ERROR: lr.hr.ec = TALER_JSON_get_error_code (j); lr.hr.hint = TALER_JSON_get_error_hint (j); - /* Server had an internal issue; we should retry, but this API - leaves this to the application */ break; default: /* unexpected response code */ @@ -272,38 +277,80 @@ handle_kyc_info_finished (void *cls, (int) lr.hr.ec); break; } - if (NULL != lh->kyc_info_cb) - lh->kyc_info_cb (lh->kyc_info_cb_cls, - &lr); - TALER_EXCHANGE_kyc_info_cancel (lh); + if (NULL != lh->cb) + lh->cb (lh->cb_cls, + &lr); + TALER_EXCHANGE_get_kyc_info_cancel (lh); } -struct TALER_EXCHANGE_KycInfoHandle * -TALER_EXCHANGE_kyc_info ( +struct TALER_EXCHANGE_GetKycInfoHandle * +TALER_EXCHANGE_get_kyc_info_create ( struct GNUNET_CURL_Context *ctx, const char *url, - const struct TALER_AccountAccessTokenP *token, - const char *if_none_match, - struct GNUNET_TIME_Relative timeout, - TALER_EXCHANGE_KycInfoCallback cb, - void *cb_cls) + const struct TALER_AccountAccessTokenP *token) +{ + struct TALER_EXCHANGE_GetKycInfoHandle *lh; + + lh = GNUNET_new (struct TALER_EXCHANGE_GetKycInfoHandle); + lh->ctx = ctx; + lh->base_url = GNUNET_strdup (url); + lh->token = *token; + return lh; +} + + +enum GNUNET_GenericReturnValue +TALER_EXCHANGE_get_kyc_info_set_options_ ( + struct TALER_EXCHANGE_GetKycInfoHandle *gkih, + unsigned int num_options, + const struct TALER_EXCHANGE_GetKycInfoOptionValue *options) +{ + for (unsigned int i = 0; i < num_options; i++) + { + const struct TALER_EXCHANGE_GetKycInfoOptionValue *opt = &options[i]; + + switch (opt->option) + { + case TALER_EXCHANGE_GET_KYC_INFO_OPTION_END: + return GNUNET_OK; + case TALER_EXCHANGE_GET_KYC_INFO_OPTION_IF_NONE_MATCH: + gkih->if_none_match = opt->details.if_none_match; + break; + case TALER_EXCHANGE_GET_KYC_INFO_OPTION_TIMEOUT: + gkih->timeout = opt->details.timeout; + break; + default: + GNUNET_break (0); + return GNUNET_SYSERR; + } + } + return GNUNET_OK; +} + + +enum TALER_ErrorCode +TALER_EXCHANGE_get_kyc_info_start ( + struct TALER_EXCHANGE_GetKycInfoHandle *gkih, + TALER_EXCHANGE_GetKycInfoCallback cb, + TALER_EXCHANGE_GET_KYC_INFO_RESULT_CLOSURE *cb_cls) { - struct TALER_EXCHANGE_KycInfoHandle *lh; CURL *eh; char arg_str[sizeof (struct TALER_AccountAccessTokenP) * 2 + 32]; - unsigned int tms - = (unsigned int) timeout.rel_value_us - / GNUNET_TIME_UNIT_MILLISECONDS.rel_value_us; + unsigned int tms; struct curl_slist *job_headers = NULL; + gkih->cb = cb; + gkih->cb_cls = cb_cls; + tms = (unsigned int) (gkih->timeout.rel_value_us + / GNUNET_TIME_UNIT_MILLISECONDS.rel_value_us); { char at_str[sizeof (struct TALER_AccountAccessTokenP) * 2]; char *end; end = GNUNET_STRINGS_data_to_string ( - token, - sizeof (*token), + &gkih->token, + sizeof (gkih->token), at_str, sizeof (at_str)); *end = '\0'; @@ -312,9 +359,6 @@ TALER_EXCHANGE_kyc_info ( "kyc-info/%s", at_str); } - lh = GNUNET_new (struct TALER_EXCHANGE_KycInfoHandle); - lh->kyc_info_cb = cb; - lh->kyc_info_cb_cls = cb_cls; { char timeout_str[32]; @@ -322,26 +366,23 @@ TALER_EXCHANGE_kyc_info ( sizeof (timeout_str), "%u", tms); - lh->url = TALER_url_join (url, - arg_str, - "timeout_ms", - (0 == tms) - ? NULL - : timeout_str, - NULL); - } - if (NULL == lh->url) - { - GNUNET_free (lh); - return NULL; + gkih->url = TALER_url_join (gkih->base_url, + arg_str, + "timeout_ms", + (0 == tms) + ? NULL + : timeout_str, + NULL); } - eh = TALER_EXCHANGE_curl_easy_get_ (lh->url); + if (NULL == gkih->url) + return TALER_EC_GENERIC_CONFIGURATION_INVALID; + eh = TALER_EXCHANGE_curl_easy_get_ (gkih->url); if (NULL == eh) { GNUNET_break (0); - GNUNET_free (lh->url); - GNUNET_free (lh); - return NULL; + GNUNET_free (gkih->url); + gkih->url = NULL; + return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE; } if (0 != tms) { @@ -350,43 +391,49 @@ TALER_EXCHANGE_kyc_info ( CURLOPT_TIMEOUT_MS, (long) (tms + 100L))); } - job_headers = curl_slist_append (job_headers, "Content-Type: application/json"); - if (NULL != if_none_match) + if (NULL != gkih->if_none_match) { char *hdr; GNUNET_asprintf (&hdr, "%s: %s", MHD_HTTP_HEADER_IF_NONE_MATCH, - if_none_match); + gkih->if_none_match); job_headers = curl_slist_append (job_headers, hdr); GNUNET_free (hdr); } - lh->job = GNUNET_CURL_job_add2 (ctx, - eh, - job_headers, - &handle_kyc_info_finished, - lh); + gkih->job = GNUNET_CURL_job_add2 (gkih->ctx, + eh, + job_headers, + &handle_kyc_info_finished, + gkih); curl_slist_free_all (job_headers); - return lh; + if (NULL == gkih->job) + { + GNUNET_free (gkih->url); + gkih->url = NULL; + return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE; + } + return TALER_EC_NONE; } void -TALER_EXCHANGE_kyc_info_cancel (struct TALER_EXCHANGE_KycInfoHandle *lh) +TALER_EXCHANGE_get_kyc_info_cancel ( + struct TALER_EXCHANGE_GetKycInfoHandle *gkih) { - if (NULL != lh->job) + if (NULL != gkih->job) { - GNUNET_CURL_job_cancel (lh->job); - lh->job = NULL; + GNUNET_CURL_job_cancel (gkih->job); + gkih->job = NULL; } - - GNUNET_free (lh->url); - GNUNET_free (lh); + GNUNET_free (gkih->url); + GNUNET_free (gkih->base_url); + GNUNET_free (gkih); } -/* end of exchange_api_kyc_info.c */ +/* end of exchange_api_get-kyc-info-ACCESS_TOKEN.c */ diff --git a/src/lib/exchange_api_get-kyc-proof-PROVIDER_NAME.c b/src/lib/exchange_api_get-kyc-proof-PROVIDER_NAME.c @@ -1,6 +1,6 @@ /* This file is part of TALER - Copyright (C) 2021, 2022 Taler Systems SA + Copyright (C) 2021, 2022, 2026 Taler Systems SA TALER is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software @@ -28,16 +28,22 @@ #include "exchange_api_handle.h" #include "taler/taler_signatures.h" #include "exchange_api_curl_defaults.h" +#include "taler/taler-exchange/get-kyc-proof-PROVIDER_NAME.h" /** - * @brief A ``/kyc-proof`` handle + * @brief A handle for GET /kyc-proof/$PROVIDER_NAME */ -struct TALER_EXCHANGE_KycProofHandle +struct TALER_EXCHANGE_GetKycProofHandle { /** - * The url for this request. + * The base URL of the exchange. + */ + char *base_url; + + /** + * The full URL for this request. */ char *url; @@ -54,39 +60,61 @@ struct TALER_EXCHANGE_KycProofHandle /** * Function to call with the result. */ - TALER_EXCHANGE_KycProofCallback cb; + TALER_EXCHANGE_GetKycProofCallback cb; /** * Closure for @e cb. */ - void *cb_cls; + TALER_EXCHANGE_GET_KYC_PROOF_RESULT_CLOSURE *cb_cls; + + /** + * CURL context to use. + */ + struct GNUNET_CURL_Context *ctx; + + /** + * Hash of the payto URI identifying the target account. + */ + struct TALER_NormalizedPaytoHashP h_payto; + + /** + * Name of the KYC logic / provider. Heap-allocated copy. + */ + char *logic; + + /** + * Additional query string arguments to append to the URL. + * Borrowed pointer (not owned), must start with '&'. + * NULL if not set. + */ + const char *args; }; /** * Function called when we're done processing the - * HTTP /kyc-proof request. + * HTTP GET /kyc-proof request. * - * @param cls the `struct TALER_EXCHANGE_KycProofHandle` + * @param cls the `struct TALER_EXCHANGE_GetKycProofHandle` * @param response_code HTTP response code, 0 on error * @param body response body * @param body_size number of bytes in @a body */ static void -handle_kyc_proof_finished (void *cls, - long response_code, - const void *body, - size_t body_size) +handle_get_kyc_proof_finished (void *cls, + long response_code, + const void *body, + size_t body_size) { - struct TALER_EXCHANGE_KycProofHandle *kph = cls; - struct TALER_EXCHANGE_KycProofResponse kpr = { + struct TALER_EXCHANGE_GetKycProofHandle *gkph = cls; + struct TALER_EXCHANGE_GetKycProofResponse gkpr = { .hr.http_status = (unsigned int) response_code }; (void) body; (void) body_size; - kph->job = NULL; + gkph->job = NULL; switch (response_code) { case 0: @@ -96,10 +124,10 @@ handle_kyc_proof_finished (void *cls, char *redirect_url; GNUNET_assert (CURLE_OK == - curl_easy_getinfo (kph->eh, + curl_easy_getinfo (gkph->eh, CURLINFO_REDIRECT_URL, &redirect_url)); - kpr.details.found.redirect_url = redirect_url; + gkpr.details.found.redirect_url = redirect_url; break; } case MHD_HTTP_BAD_REQUEST: @@ -124,94 +152,138 @@ handle_kyc_proof_finished (void *cls, /* unexpected response code */ GNUNET_break_op (0); GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Unexpected response code %u for exchange kyc_proof\n", + "Unexpected response code %u for exchange get_kyc_proof\n", (unsigned int) response_code); break; } - kph->cb (kph->cb_cls, - &kpr); - TALER_EXCHANGE_kyc_proof_cancel (kph); + gkph->cb (gkph->cb_cls, + &gkpr); + TALER_EXCHANGE_get_kyc_proof_cancel (gkph); } -struct TALER_EXCHANGE_KycProofHandle * -TALER_EXCHANGE_kyc_proof ( +struct TALER_EXCHANGE_GetKycProofHandle * +TALER_EXCHANGE_get_kyc_proof_create ( struct GNUNET_CURL_Context *ctx, const char *url, const struct TALER_NormalizedPaytoHashP *h_payto, - const char *logic, - const char *args, - TALER_EXCHANGE_KycProofCallback cb, - void *cb_cls) + const char *logic) +{ + struct TALER_EXCHANGE_GetKycProofHandle *gkph; + + gkph = GNUNET_new (struct TALER_EXCHANGE_GetKycProofHandle); + gkph->ctx = ctx; + gkph->base_url = GNUNET_strdup (url); + gkph->h_payto = *h_payto; + gkph->logic = GNUNET_strdup (logic); + return gkph; +} + + +enum GNUNET_GenericReturnValue +TALER_EXCHANGE_get_kyc_proof_set_options_ ( + struct TALER_EXCHANGE_GetKycProofHandle *gkph, + unsigned int num_options, + const struct TALER_EXCHANGE_GetKycProofOptionValue *options) +{ + for (unsigned int i = 0; i < num_options; i++) + { + const struct TALER_EXCHANGE_GetKycProofOptionValue *opt = &options[i]; + + switch (opt->option) + { + case TALER_EXCHANGE_GET_KYC_PROOF_OPTION_END: + return GNUNET_OK; + case TALER_EXCHANGE_GET_KYC_PROOF_OPTION_ARGS: + GNUNET_assert (opt->details.args[0] == '&'); + gkph->args = opt->details.args; + break; + } + } + return GNUNET_OK; +} + + +enum TALER_ErrorCode +TALER_EXCHANGE_get_kyc_proof_start ( + struct TALER_EXCHANGE_GetKycProofHandle *gkph, + TALER_EXCHANGE_GetKycProofCallback cb, + TALER_EXCHANGE_GET_KYC_PROOF_RESULT_CLOSURE *cb_cls) { - struct TALER_EXCHANGE_KycProofHandle *kph; char *arg_str; - if (NULL == args) - args = ""; - else - GNUNET_assert (args[0] == '&'); + if (NULL != gkph->job) + { + GNUNET_break (0); + return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE; + } + gkph->cb = cb; + gkph->cb_cls = cb_cls; { - char hstr[sizeof (*h_payto) * 2]; + char hstr[sizeof (gkph->h_payto) * 2]; char *end; - end = GNUNET_STRINGS_data_to_string (h_payto, - sizeof (*h_payto), + end = GNUNET_STRINGS_data_to_string (&gkph->h_payto, + sizeof (gkph->h_payto), hstr, sizeof (hstr)); *end = '\0'; GNUNET_asprintf (&arg_str, "kyc-proof/%s?state=%s%s", - logic, + gkph->logic, hstr, - args); + (NULL != gkph->args) ? gkph->args : ""); } - kph = GNUNET_new (struct TALER_EXCHANGE_KycProofHandle); - kph->cb = cb; - kph->cb_cls = cb_cls; - kph->url = TALER_url_join (url, - arg_str, - NULL); + gkph->url = TALER_url_join (gkph->base_url, + arg_str, + NULL); GNUNET_free (arg_str); - if (NULL == kph->url) - { - GNUNET_free (kph); - return NULL; - } - kph->eh = TALER_EXCHANGE_curl_easy_get_ (kph->url); - if (NULL == kph->eh) + if (NULL == gkph->url) + return TALER_EC_GENERIC_CONFIGURATION_INVALID; + gkph->eh = TALER_EXCHANGE_curl_easy_get_ (gkph->url); + if (NULL == gkph->eh) { GNUNET_break (0); - GNUNET_free (kph->url); - GNUNET_free (kph); - return NULL; + GNUNET_free (gkph->url); + gkph->url = NULL; + return TALER_EC_GENERIC_CONFIGURATION_INVALID; } /* disable location following, we want to learn the result of a 303 redirect! */ - GNUNET_assert (CURLE_OK == - curl_easy_setopt (kph->eh, - CURLOPT_FOLLOWLOCATION, - 0L)); - kph->job = GNUNET_CURL_job_add_raw (ctx, - kph->eh, - NULL, - &handle_kyc_proof_finished, - kph); - return kph; + curl_easy_setopt (gkph->eh, + CURLOPT_FOLLOWLOCATION, + 0L); + gkph->job = GNUNET_CURL_job_add_raw (gkph->ctx, + gkph->eh, + NULL, + &handle_get_kyc_proof_finished, + gkph); + if (NULL == gkph->job) + { + curl_easy_cleanup (gkph->eh); + gkph->eh = NULL; + GNUNET_free (gkph->url); + gkph->url = NULL; + return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE; + } + return TALER_EC_NONE; } void -TALER_EXCHANGE_kyc_proof_cancel (struct TALER_EXCHANGE_KycProofHandle *kph) +TALER_EXCHANGE_get_kyc_proof_cancel ( + struct TALER_EXCHANGE_GetKycProofHandle *gkph) { - if (NULL != kph->job) + if (NULL != gkph->job) { - GNUNET_CURL_job_cancel (kph->job); - kph->job = NULL; + GNUNET_CURL_job_cancel (gkph->job); + gkph->job = NULL; } - GNUNET_free (kph->url); - GNUNET_free (kph); + GNUNET_free (gkph->url); + GNUNET_free (gkph->logic); + GNUNET_free (gkph->base_url); + GNUNET_free (gkph); } -/* end of exchange_api_kyc_proof.c */ +/* end of exchange_api_get-kyc-proof-PROVIDER_NAME.c */ diff --git a/src/lib/exchange_api_post-aml-OFFICER_PUB-decision.c b/src/lib/exchange_api_post-aml-OFFICER_PUB-decision.c @@ -1,6 +1,6 @@ /* This file is part of TALER - Copyright (C) 2023, 2024 Taler Systems SA + Copyright (C) 2023, 2024, 2026 Taler Systems SA TALER is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software @@ -24,17 +24,25 @@ #include <microhttpd.h> #include <gnunet/gnunet_curl_lib.h> #include "taler/taler_exchange_service.h" +#include "taler/taler-exchange/post-aml-OFFICER_PUB-decision.h" #include "exchange_api_curl_defaults.h" #include "taler/taler_signatures.h" #include "taler/taler_curl_lib.h" -#include "taler/taler_json_lib.h" -struct TALER_EXCHANGE_AddAmlDecision +/** + * @brief A POST /aml/$OFFICER_PUB/decision Handle + */ +struct TALER_EXCHANGE_PostAmlDecisionHandle { /** - * The url for this request. + * The base URL of the exchange. + */ + char *base_url; + + /** + * The full URL for this request. */ char *url; @@ -51,17 +59,76 @@ struct TALER_EXCHANGE_AddAmlDecision /** * Function to call with the result. */ - TALER_EXCHANGE_AddAmlDecisionCallback cb; + TALER_EXCHANGE_PostAmlDecisionCallback cb; /** - * Closure for @a cb. + * Closure for @e cb. */ - void *cb_cls; + TALER_EXCHANGE_POST_AML_DECISION_RESULT_CLOSURE *cb_cls; /** * Reference to the execution context. */ struct GNUNET_CURL_Context *ctx; + + /** + * Public key of the AML officer. + */ + struct TALER_AmlOfficerPublicKeyP officer_pub; + + /** + * Private key of the AML officer. + */ + struct TALER_AmlOfficerPrivateKeyP officer_priv; + + /** + * Hash of the payto URI of the account the decision is about. + */ + struct TALER_NormalizedPaytoHashP h_payto; + + /** + * When was the decision made. + */ + struct GNUNET_TIME_Timestamp decision_time; + + /** + * Human-readable justification. + */ + char *justification; + + /** + * True to keep the investigation open. + */ + bool keep_investigating; + + /** + * Pre-built new_rules JSON object. + */ + json_t *new_rules; + + /** + * Optional: full payto URI string, may be NULL. + */ + char *payto_uri_str; + + /** + * Optional: space-separated list of measures to trigger immediately + * (from options, may be NULL). + */ + char *new_measures; + + /** + * Optional: JSON object with account properties + * (from options, may be NULL). + */ + json_t *properties; + + /** + * Optional: JSON array of events to trigger + * (from options; may be NULL). + */ + json_t *jevents; + }; @@ -69,114 +136,93 @@ struct TALER_EXCHANGE_AddAmlDecision * Function called when we're done processing the * HTTP POST /aml/$OFFICER_PUB/decision request. * - * @param cls the `struct TALER_EXCHANGE_AddAmlDecision *` + * @param cls the `struct TALER_EXCHANGE_PostAmlDecisionHandle *` * @param response_code HTTP response code, 0 on error * @param response response body, NULL if not in JSON */ static void -handle_add_aml_decision_finished (void *cls, - long response_code, - const void *response) +handle_post_aml_decision_finished (void *cls, + long response_code, + const void *response) { - struct TALER_EXCHANGE_AddAmlDecision *wh = cls; + struct TALER_EXCHANGE_PostAmlDecisionHandle *padh = cls; const json_t *json = response; - struct TALER_EXCHANGE_AddAmlDecisionResponse adr = { + struct TALER_EXCHANGE_PostAmlDecisionResponse pr = { .hr.http_status = (unsigned int) response_code, .hr.reply = json }; - wh->job = NULL; + padh->job = NULL; switch (response_code) { case 0: - /* no reply */ - adr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; - adr.hr.hint = "server offline?"; + pr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; + pr.hr.hint = "server offline?"; break; case MHD_HTTP_NO_CONTENT: break; case MHD_HTTP_FORBIDDEN: - adr.hr.ec = TALER_JSON_get_error_code (json); - adr.hr.hint = TALER_JSON_get_error_hint (json); + pr.hr.ec = TALER_JSON_get_error_code (json); + pr.hr.hint = TALER_JSON_get_error_hint (json); break; case MHD_HTTP_CONFLICT: - adr.hr.ec = TALER_JSON_get_error_code (json); - adr.hr.hint = TALER_JSON_get_error_hint (json); + pr.hr.ec = TALER_JSON_get_error_code (json); + pr.hr.hint = TALER_JSON_get_error_hint (json); break; default: - /* unexpected response code */ GNUNET_break_op (0); - adr.hr.ec = TALER_JSON_get_error_code (json); - adr.hr.hint = TALER_JSON_get_error_hint (json); + pr.hr.ec = TALER_JSON_get_error_code (json); + pr.hr.hint = TALER_JSON_get_error_hint (json); GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Unexpected response code %u/%d for exchange AML decision\n", + "Unexpected response code %u/%d for POST AML decision\n", (unsigned int) response_code, - (int) adr.hr.ec); + (int) pr.hr.ec); break; } - if (NULL != wh->cb) + if (NULL != padh->cb) { - wh->cb (wh->cb_cls, - &adr); - wh->cb = NULL; + padh->cb (padh->cb_cls, + &pr); + padh->cb = NULL; } - TALER_EXCHANGE_post_aml_decision_cancel (wh); + TALER_EXCHANGE_post_aml_decision_cancel (padh); } -struct TALER_EXCHANGE_AddAmlDecision * -TALER_EXCHANGE_post_aml_decision ( - struct GNUNET_CURL_Context *ctx, - const char *url, - const struct TALER_NormalizedPaytoHashP *h_payto, - const struct TALER_FullPayto payto_uri, - struct GNUNET_TIME_Timestamp decision_time, +/** + * Build the new_rules JSON object from rules and measures arrays. + * + * @param successor_measure optional successor measure name + * @param expiration_time when the new rules expire + * @param num_rules length of @a rules + * @param rules the rules array + * @param num_measures length of @a measures + * @param measures the measures array + * @return new JSON object (caller owns reference), NULL on error + */ +static json_t * +build_new_rules ( const char *successor_measure, - const char *new_measures, struct GNUNET_TIME_Timestamp expiration_time, unsigned int num_rules, - const struct TALER_EXCHANGE_AccountRule *rules, + const struct TALER_EXCHANGE_AccountRule rules[static num_rules], unsigned int num_measures, - const struct TALER_EXCHANGE_MeasureInformation *measures, - const json_t *properties, - bool keep_investigating, - const char *justification, - const struct TALER_AmlOfficerPrivateKeyP *officer_priv, - unsigned int num_events, - const char **events, - TALER_EXCHANGE_AddAmlDecisionCallback cb, - void *cb_cls) + const struct TALER_EXCHANGE_MeasureInformation measures[static num_measures]) { - struct TALER_AmlOfficerPublicKeyP officer_pub; - struct TALER_AmlOfficerSignatureP officer_sig; - struct TALER_EXCHANGE_AddAmlDecision *wh; - CURL *eh; - json_t *body; - json_t *new_rules; json_t *jrules; json_t *jmeasures; - json_t *jevents = NULL; - if (0 != num_events) - { - jevents = json_array (); - GNUNET_assert (NULL != jevents); - for (unsigned int i = 0; i<num_events; i++) - GNUNET_assert (0 == - json_array_append_new (jevents, - json_string (events[i]))); - } jrules = json_array (); GNUNET_assert (NULL != jrules); - for (unsigned int i = 0; i<num_rules; i++) + for (unsigned int i = 0; i < num_rules; i++) { const struct TALER_EXCHANGE_AccountRule *al = &rules[i]; - json_t *rule; json_t *ameasures; + json_t *rule; ameasures = json_array (); GNUNET_assert (NULL != ameasures); - for (unsigned int j = 0; j<al->num_measures; j++) + for (unsigned int j = 0; j < al->num_measures; j++) GNUNET_assert (0 == json_array_append_new (ameasures, json_string (al->measures[j]))); @@ -189,9 +235,6 @@ TALER_EXCHANGE_post_aml_decision ( al->timeframe), GNUNET_JSON_pack_array_steal ("measures", ameasures), - GNUNET_JSON_pack_allow_null ( - GNUNET_JSON_pack_array_steal ("events", - jevents)), GNUNET_JSON_pack_bool ("exposed", al->exposed), GNUNET_JSON_pack_bool ("is_and_combinator", @@ -206,7 +249,7 @@ TALER_EXCHANGE_post_aml_decision ( jmeasures = json_object (); GNUNET_assert (NULL != jmeasures); - for (unsigned int i = 0; i<num_measures; i++) + for (unsigned int i = 0; i < num_measures; i++) { const struct TALER_EXCHANGE_MeasureInformation *mi = &measures[i]; json_t *measure; @@ -227,7 +270,7 @@ TALER_EXCHANGE_post_aml_decision ( measure)); } - new_rules = GNUNET_JSON_PACK ( + return GNUNET_JSON_PACK ( GNUNET_JSON_pack_timestamp ("expiration_time", expiration_time), GNUNET_JSON_pack_allow_null ( @@ -238,75 +281,199 @@ TALER_EXCHANGE_post_aml_decision ( GNUNET_JSON_pack_object_steal ("custom_measures", jmeasures) ); +} - GNUNET_CRYPTO_eddsa_key_get_public ( - &officer_priv->eddsa_priv, - &officer_pub.eddsa_pub); - TALER_officer_aml_decision_sign (justification, - decision_time, - h_payto, - new_rules, - properties, - new_measures, - keep_investigating, - officer_priv, - &officer_sig); - wh = GNUNET_new (struct TALER_EXCHANGE_AddAmlDecision); - wh->cb = cb; - wh->cb_cls = cb_cls; - wh->ctx = ctx; + +struct TALER_EXCHANGE_PostAmlDecisionHandle * +TALER_EXCHANGE_post_aml_decision_create ( + struct GNUNET_CURL_Context *ctx, + const char *url, + const struct TALER_NormalizedPaytoHashP *h_payto, + struct GNUNET_TIME_Timestamp decision_time, + const char *successor_measure, + struct GNUNET_TIME_Timestamp expiration_time, + unsigned int num_rules, + const struct TALER_EXCHANGE_AccountRule rules[static num_rules], + unsigned int num_measures, + const struct TALER_EXCHANGE_MeasureInformation measures[static num_measures], + bool keep_investigating, + const char *justification, + const struct TALER_AmlOfficerPrivateKeyP *officer_priv) +{ + struct TALER_EXCHANGE_PostAmlDecisionHandle *padh; + json_t *new_rules; + + new_rules = build_new_rules (successor_measure, + expiration_time, + num_rules, + rules, + num_measures, + measures); + if (NULL == new_rules) { - char *path; - char opus[sizeof (officer_pub) * 2]; - char *end; - - end = GNUNET_STRINGS_data_to_string ( - &officer_pub, - sizeof (officer_pub), - opus, - sizeof (opus)); - *end = '\0'; - GNUNET_asprintf (&path, - "aml/%s/decision", - opus); - wh->url = TALER_url_join (url, + GNUNET_break (0); + return NULL; + } + + padh = GNUNET_new (struct TALER_EXCHANGE_PostAmlDecisionHandle); + padh->ctx = ctx; + padh->base_url = GNUNET_strdup (url); + padh->h_payto = *h_payto; + padh->decision_time = decision_time; + padh->justification = GNUNET_strdup (justification); + padh->keep_investigating = keep_investigating; + padh->new_rules = new_rules; + padh->officer_priv = *officer_priv; + GNUNET_CRYPTO_eddsa_key_get_public (&officer_priv->eddsa_priv, + &padh->officer_pub.eddsa_pub); + return padh; +} + + +enum GNUNET_GenericReturnValue +TALER_EXCHANGE_post_aml_decision_set_options_ ( + struct TALER_EXCHANGE_PostAmlDecisionHandle *padh, + unsigned int num_options, + const struct TALER_EXCHANGE_PostAmlDecisionOptionValue options[]) +{ + for (unsigned int i = 0; i < num_options; i++) + { + const struct TALER_EXCHANGE_PostAmlDecisionOptionValue *opt = &options[i]; + + switch (opt->option) + { + case TALER_EXCHANGE_POST_AML_DECISION_OPTION_END: + return GNUNET_OK; + case TALER_EXCHANGE_POST_AML_DECISION_OPTION_PAYTO_URI: + GNUNET_free (padh->payto_uri_str); + padh->payto_uri_str = + (NULL != opt->details.payto_uri.full_payto) + ? GNUNET_strdup (opt->details.payto_uri.full_payto) + : NULL; + break; + case TALER_EXCHANGE_POST_AML_DECISION_OPTION_NEW_MEASURES: + GNUNET_free (padh->new_measures); + padh->new_measures = + (NULL != opt->details.new_measures) + ? GNUNET_strdup (opt->details.new_measures) + : NULL; + break; + case TALER_EXCHANGE_POST_AML_DECISION_OPTION_PROPERTIES: + if (NULL != padh->properties) + json_decref (padh->properties); + padh->properties = + (NULL != opt->details.properties) + ? json_incref ((json_t *) opt->details.properties) + : NULL; + break; + case TALER_EXCHANGE_POST_AML_DECISION_OPTION_EVENTS: + { + if (NULL != padh->jevents) + json_decref (padh->jevents); + if (0 == opt->details.events.num_events) + { + padh->jevents = NULL; + } + else + { + padh->jevents = json_array (); + GNUNET_assert (NULL != padh->jevents); + for (unsigned int j = 0; j < opt->details.events.num_events; j++) + GNUNET_assert (0 == + json_array_append_new ( + padh->jevents, + json_string (opt->details.events.events[j]))); + } + } + break; + default: + GNUNET_break (0); + return GNUNET_SYSERR; + } + } + return GNUNET_OK; +} + + +enum TALER_ErrorCode +TALER_EXCHANGE_post_aml_decision_start ( + struct TALER_EXCHANGE_PostAmlDecisionHandle *padh, + TALER_EXCHANGE_PostAmlDecisionCallback cb, + TALER_EXCHANGE_POST_AML_DECISION_RESULT_CLOSURE *cb_cls) +{ + CURL *eh; + struct TALER_AmlOfficerSignatureP officer_sig; + json_t *body; + char *path; + char opus[sizeof (padh->officer_pub) * 2]; + char *end; + struct TALER_FullPayto payto_uri_val = { + .full_payto = padh->payto_uri_str + }; + + padh->cb = cb; + padh->cb_cls = cb_cls; + TALER_officer_aml_decision_sign (padh->justification, + padh->decision_time, + &padh->h_payto, + padh->new_rules, + padh->properties, + padh->new_measures, + padh->keep_investigating, + &padh->officer_priv, + &officer_sig); + + end = GNUNET_STRINGS_data_to_string ( + &padh->officer_pub, + sizeof (padh->officer_pub), + opus, + sizeof (opus)); + *end = '\0'; + GNUNET_asprintf (&path, + "aml/%s/decision", + opus); + padh->url = TALER_url_join (padh->base_url, path, NULL); - GNUNET_free (path); - } - if (NULL == wh->url) + GNUNET_free (path); + if (NULL == padh->url) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Could not construct request URL.\n"); - GNUNET_free (wh); - json_decref (new_rules); - return NULL; + return TALER_EC_GENERIC_CONFIGURATION_INVALID; } + body = GNUNET_JSON_PACK ( GNUNET_JSON_pack_string ("justification", - justification), + padh->justification), GNUNET_JSON_pack_data_auto ("h_payto", - h_payto), + &padh->h_payto), GNUNET_JSON_pack_allow_null ( TALER_JSON_pack_full_payto ("payto_uri", - payto_uri)), - GNUNET_JSON_pack_object_steal ("new_rules", - new_rules), - GNUNET_JSON_pack_object_incref ("properties", - (json_t *) properties), + payto_uri_val)), + GNUNET_JSON_pack_object_incref ("new_rules", + padh->new_rules), + GNUNET_JSON_pack_allow_null ( + GNUNET_JSON_pack_object_incref ("properties", + padh->properties)), GNUNET_JSON_pack_allow_null ( GNUNET_JSON_pack_string ("new_measures", - new_measures)), + padh->new_measures)), GNUNET_JSON_pack_bool ("keep_investigating", - keep_investigating), + padh->keep_investigating), GNUNET_JSON_pack_data_auto ("officer_sig", &officer_sig), GNUNET_JSON_pack_timestamp ("decision_time", - decision_time)); - eh = TALER_EXCHANGE_curl_easy_get_ (wh->url); + padh->decision_time), + GNUNET_JSON_pack_allow_null ( + GNUNET_JSON_pack_array_incref ("events", + padh->jevents)) + ); + + eh = TALER_EXCHANGE_curl_easy_get_ (padh->url); if ( (NULL == eh) || (GNUNET_OK != - TALER_curl_easy_post (&wh->post_ctx, + TALER_curl_easy_post (&padh->post_ctx, eh, body)) ) { @@ -314,37 +481,52 @@ TALER_EXCHANGE_post_aml_decision ( if (NULL != eh) curl_easy_cleanup (eh); json_decref (body); - GNUNET_free (wh->url); - return NULL; + GNUNET_free (padh->url); + padh->url = NULL; + return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE; } json_decref (body); GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Requesting URL '%s'\n", - wh->url); - wh->job = GNUNET_CURL_job_add2 (ctx, - eh, - wh->post_ctx.headers, - &handle_add_aml_decision_finished, - wh); - if (NULL == wh->job) + padh->url); + padh->job = GNUNET_CURL_job_add2 (padh->ctx, + eh, + padh->post_ctx.headers, + &handle_post_aml_decision_finished, + padh); + if (NULL == padh->job) { - TALER_EXCHANGE_post_aml_decision_cancel (wh); - return NULL; + TALER_curl_easy_post_finished (&padh->post_ctx); + GNUNET_free (padh->url); + padh->url = NULL; + return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE; } - return wh; + return TALER_EC_NONE; } void TALER_EXCHANGE_post_aml_decision_cancel ( - struct TALER_EXCHANGE_AddAmlDecision *wh) + struct TALER_EXCHANGE_PostAmlDecisionHandle *padh) { - if (NULL != wh->job) + if (NULL != padh->job) { - GNUNET_CURL_job_cancel (wh->job); - wh->job = NULL; + GNUNET_CURL_job_cancel (padh->job); + padh->job = NULL; } - TALER_curl_easy_post_finished (&wh->post_ctx); - GNUNET_free (wh->url); - GNUNET_free (wh); + TALER_curl_easy_post_finished (&padh->post_ctx); + json_decref (padh->new_rules); + if (NULL != padh->properties) + json_decref (padh->properties); + if (NULL != padh->jevents) + json_decref (padh->jevents); + GNUNET_free (padh->url); + GNUNET_free (padh->base_url); + GNUNET_free (padh->justification); + GNUNET_free (padh->payto_uri_str); + GNUNET_free (padh->new_measures); + GNUNET_free (padh); } + + +/* end of exchange_api_post-aml-OFFICER_PUB-decision.c */ diff --git a/src/lib/exchange_api_post-kyc-start-ID.c b/src/lib/exchange_api_post-kyc-start-ID.c @@ -1,6 +1,6 @@ /* This file is part of TALER - Copyright (C) 2024 Taler Systems SA + Copyright (C) 2024-2026 Taler Systems SA TALER is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software @@ -24,17 +24,22 @@ #include <microhttpd.h> #include <gnunet/gnunet_curl_lib.h> #include "taler/taler_exchange_service.h" +#include "taler/taler-exchange/post-kyc-start-ID.h" #include "exchange_api_curl_defaults.h" #include "taler/taler_signatures.h" #include "taler/taler_curl_lib.h" -#include "taler/taler_json_lib.h" -struct TALER_EXCHANGE_KycStartHandle +struct TALER_EXCHANGE_PostKycStartHandle { /** - * The url for this request. + * The base URL for this request. + */ + char *base_url; + + /** + * The full URL for this request. */ char *url; @@ -51,17 +56,23 @@ struct TALER_EXCHANGE_KycStartHandle /** * Function to call with the result. */ - TALER_EXCHANGE_KycStartCallback cb; + TALER_EXCHANGE_PostKycStartCallback cb; /** - * Closure for @a cb. + * Closure for @e cb. */ - void *cb_cls; + TALER_EXCHANGE_POST_KYC_START_RESULT_CLOSURE *cb_cls; /** * Reference to the execution context. */ struct GNUNET_CURL_Context *ctx; + + /** + * Identifier for the KYC process to start. + */ + char *id; + }; @@ -69,7 +80,7 @@ struct TALER_EXCHANGE_KycStartHandle * Function called when we're done processing the * HTTP POST /kyc-start/$ID request. * - * @param cls the `struct TALER_EXCHANGE_KycStartHandle *` + * @param cls the `struct TALER_EXCHANGE_PostKycStartHandle *` * @param response_code HTTP response code, 0 on error * @param response response body, NULL if not in JSON */ @@ -78,14 +89,14 @@ handle_kyc_start_finished (void *cls, long response_code, const void *response) { - struct TALER_EXCHANGE_KycStartHandle *wh = cls; + struct TALER_EXCHANGE_PostKycStartHandle *pksh = cls; const json_t *json = response; - struct TALER_EXCHANGE_KycStartResponse adr = { + struct TALER_EXCHANGE_PostKycStartResponse adr = { .hr.http_status = (unsigned int) response_code, .hr.reply = json }; - wh->job = NULL; + pksh->job = NULL; switch (response_code) { case 0: @@ -122,61 +133,68 @@ handle_kyc_start_finished (void *cls, adr.hr.ec = TALER_JSON_get_error_code (json); adr.hr.hint = TALER_JSON_get_error_hint (json); GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Unexpected response code %u/%d for exchange AML decision\n", + "Unexpected response code %u/%d for exchange POST kyc-start\n", (unsigned int) response_code, (int) adr.hr.ec); break; } - if (NULL != wh->cb) + if (NULL != pksh->cb) { - wh->cb (wh->cb_cls, - &adr); - wh->cb = NULL; + pksh->cb (pksh->cb_cls, + &adr); + pksh->cb = NULL; } - TALER_EXCHANGE_kyc_start_cancel (wh); + TALER_EXCHANGE_post_kyc_start_cancel (pksh); } -struct TALER_EXCHANGE_KycStartHandle * -TALER_EXCHANGE_kyc_start ( +struct TALER_EXCHANGE_PostKycStartHandle * +TALER_EXCHANGE_post_kyc_start_create ( struct GNUNET_CURL_Context *ctx, const char *url, - const char *id, - TALER_EXCHANGE_KycStartCallback cb, - void *cb_cls) + const char *id) { - struct TALER_EXCHANGE_KycStartHandle *wh; - CURL *eh; - json_t *body; + struct TALER_EXCHANGE_PostKycStartHandle *pksh; + + pksh = GNUNET_new (struct TALER_EXCHANGE_PostKycStartHandle); + pksh->ctx = ctx; + pksh->base_url = GNUNET_strdup (url); + pksh->id = GNUNET_strdup (id); + return pksh; +} - wh = GNUNET_new (struct TALER_EXCHANGE_KycStartHandle); - wh->cb = cb; - wh->cb_cls = cb_cls; - wh->ctx = ctx; - { - char *path; - GNUNET_asprintf (&path, - "kyc-start/%s", - id); - wh->url = TALER_url_join (url, +enum TALER_ErrorCode +TALER_EXCHANGE_post_kyc_start_start ( + struct TALER_EXCHANGE_PostKycStartHandle *pksh, + TALER_EXCHANGE_PostKycStartCallback cb, + TALER_EXCHANGE_POST_KYC_START_RESULT_CLOSURE *cb_cls) +{ + CURL *eh; + json_t *body; + char *path; + + pksh->cb = cb; + pksh->cb_cls = cb_cls; + GNUNET_asprintf (&path, + "kyc-start/%s", + pksh->id); + pksh->url = TALER_url_join (pksh->base_url, path, NULL); - GNUNET_free (path); - } - if (NULL == wh->url) + GNUNET_free (path); + if (NULL == pksh->url) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Could not construct request URL.\n"); - GNUNET_free (wh); - return NULL; + return TALER_EC_GENERIC_CONFIGURATION_INVALID; } body = json_object (); /* as per spec: empty! */ GNUNET_assert (NULL != body); - eh = TALER_EXCHANGE_curl_easy_get_ (wh->url); + eh = TALER_EXCHANGE_curl_easy_get_ (pksh->url); if ( (NULL == eh) || (GNUNET_OK != - TALER_curl_easy_post (&wh->post_ctx, + TALER_curl_easy_post (&pksh->post_ctx, eh, body)) ) { @@ -184,37 +202,45 @@ TALER_EXCHANGE_kyc_start ( if (NULL != eh) curl_easy_cleanup (eh); json_decref (body); - GNUNET_free (wh->url); - return NULL; + GNUNET_free (pksh->url); + pksh->url = NULL; + return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE; } json_decref (body); GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Requesting URL '%s'\n", - wh->url); - wh->job = GNUNET_CURL_job_add2 (ctx, - eh, - wh->post_ctx.headers, - &handle_kyc_start_finished, - wh); - if (NULL == wh->job) + pksh->url); + pksh->job = GNUNET_CURL_job_add2 (pksh->ctx, + eh, + pksh->post_ctx.headers, + &handle_kyc_start_finished, + pksh); + if (NULL == pksh->job) { - TALER_EXCHANGE_kyc_start_cancel (wh); - return NULL; + TALER_curl_easy_post_finished (&pksh->post_ctx); + GNUNET_free (pksh->url); + pksh->url = NULL; + return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE; } - return wh; + return TALER_EC_NONE; } void -TALER_EXCHANGE_kyc_start_cancel ( - struct TALER_EXCHANGE_KycStartHandle *wh) +TALER_EXCHANGE_post_kyc_start_cancel ( + struct TALER_EXCHANGE_PostKycStartHandle *pksh) { - if (NULL != wh->job) + if (NULL != pksh->job) { - GNUNET_CURL_job_cancel (wh->job); - wh->job = NULL; + GNUNET_CURL_job_cancel (pksh->job); + pksh->job = NULL; } - TALER_curl_easy_post_finished (&wh->post_ctx); - GNUNET_free (wh->url); - GNUNET_free (wh); + TALER_curl_easy_post_finished (&pksh->post_ctx); + GNUNET_free (pksh->url); + GNUNET_free (pksh->base_url); + GNUNET_free (pksh->id); + GNUNET_free (pksh); } + + +/* end of exchange_api_post-kyc-start-ID.c */ diff --git a/src/lib/exchange_api_post-kyc-wallet.c b/src/lib/exchange_api_post-kyc-wallet.c @@ -1,6 +1,6 @@ /* This file is part of TALER - Copyright (C) 2021 Taler Systems SA + Copyright (C) 2021-2026 Taler Systems SA TALER is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software @@ -20,30 +20,36 @@ * @author Christian Grothoff */ #include "taler/platform.h" -#include <microhttpd.h> /* just for HTTP wallet codes */ +#include <microhttpd.h> /* just for HTTP status codes */ #include <gnunet/gnunet_util_lib.h> #include <gnunet/gnunet_curl_lib.h> #include "taler/taler_exchange_service.h" #include "taler/taler_json_lib.h" -#include "exchange_api_handle.h" +#include "taler/taler-exchange/post-kyc-wallet.h" #include "taler/taler_signatures.h" #include "exchange_api_curl_defaults.h" +#include "taler/taler_curl_lib.h" /** - * @brief A ``/kyc-wallet`` handle + * @brief A POST /kyc-wallet handle */ -struct TALER_EXCHANGE_KycWalletHandle +struct TALER_EXCHANGE_PostKycWalletHandle { /** - * Context for #TEH_curl_easy_post(). Keeps the data that must + * Context for curl easy post. Keeps the data that must * persist for Curl to make the upload. */ - struct TALER_CURL_PostContext ctx; + struct TALER_CURL_PostContext post_ctx; /** - * The url for this request. + * The base URL for this request. + */ + char *base_url; + + /** + * The full URL for this request, set during _start. */ char *url; @@ -55,12 +61,27 @@ struct TALER_EXCHANGE_KycWalletHandle /** * Function to call with the result. */ - TALER_EXCHANGE_KycWalletCallback cb; + TALER_EXCHANGE_PostKycWalletCallback cb; /** * Closure for @e cb. */ - void *cb_cls; + TALER_EXCHANGE_POST_KYC_WALLET_RESULT_CLOSURE *cb_cls; + + /** + * Reference to the execution context. + */ + struct GNUNET_CURL_Context *ctx; + + /** + * Reserve private key of the wallet. + */ + struct TALER_ReservePrivateKeyP reserve_priv; + + /** + * Balance (or balance threshold) crossed by the wallet. + */ + struct TALER_Amount balance; }; @@ -69,7 +90,7 @@ struct TALER_EXCHANGE_KycWalletHandle * Function called when we're done processing the * HTTP /kyc-wallet request. * - * @param cls the `struct TALER_EXCHANGE_KycWalletHandle` + * @param cls the `struct TALER_EXCHANGE_PostKycWalletHandle` * @param response_code HTTP response code, 0 on error * @param response parsed JSON result, NULL on error */ @@ -78,14 +99,14 @@ handle_kyc_wallet_finished (void *cls, long response_code, const void *response) { - struct TALER_EXCHANGE_KycWalletHandle *kwh = cls; + struct TALER_EXCHANGE_PostKycWalletHandle *pkwh = cls; const json_t *j = response; - struct TALER_EXCHANGE_WalletKycResponse ks = { + struct TALER_EXCHANGE_PostKycWalletResponse ks = { .hr.reply = j, .hr.http_status = (unsigned int) response_code }; - kwh->job = NULL; + pkwh->job = NULL; switch (response_code) { case 0: @@ -169,56 +190,72 @@ handle_kyc_wallet_finished (void *cls, (int) ks.hr.ec); break; } - kwh->cb (kwh->cb_cls, - &ks); - TALER_EXCHANGE_kyc_wallet_cancel (kwh); + if (NULL != pkwh->cb) + { + pkwh->cb (pkwh->cb_cls, + &ks); + pkwh->cb = NULL; + } + TALER_EXCHANGE_post_kyc_wallet_cancel (pkwh); } -struct TALER_EXCHANGE_KycWalletHandle * -TALER_EXCHANGE_kyc_wallet ( +struct TALER_EXCHANGE_PostKycWalletHandle * +TALER_EXCHANGE_post_kyc_wallet_create ( struct GNUNET_CURL_Context *ctx, const char *url, const struct TALER_ReservePrivateKeyP *reserve_priv, - const struct TALER_Amount *balance, - TALER_EXCHANGE_KycWalletCallback cb, - void *cb_cls) + const struct TALER_Amount *balance) +{ + struct TALER_EXCHANGE_PostKycWalletHandle *pkwh; + + pkwh = GNUNET_new (struct TALER_EXCHANGE_PostKycWalletHandle); + pkwh->ctx = ctx; + pkwh->base_url = GNUNET_strdup (url); + pkwh->reserve_priv = *reserve_priv; + pkwh->balance = *balance; + return pkwh; +} + + +enum TALER_ErrorCode +TALER_EXCHANGE_post_kyc_wallet_start ( + struct TALER_EXCHANGE_PostKycWalletHandle *pkwh, + TALER_EXCHANGE_PostKycWalletCallback cb, + TALER_EXCHANGE_POST_KYC_WALLET_RESULT_CLOSURE *cb_cls) { - struct TALER_EXCHANGE_KycWalletHandle *kwh; CURL *eh; json_t *req; struct TALER_ReservePublicKeyP reserve_pub; struct TALER_ReserveSignatureP reserve_sig; - GNUNET_CRYPTO_eddsa_key_get_public (&reserve_priv->eddsa_priv, + pkwh->cb = cb; + pkwh->cb_cls = cb_cls; + GNUNET_CRYPTO_eddsa_key_get_public (&pkwh->reserve_priv.eddsa_priv, &reserve_pub.eddsa_pub); - TALER_wallet_account_setup_sign (reserve_priv, - balance, + TALER_wallet_account_setup_sign (&pkwh->reserve_priv, + &pkwh->balance, &reserve_sig); req = GNUNET_JSON_PACK ( TALER_JSON_pack_amount ("balance", - balance), + &pkwh->balance), GNUNET_JSON_pack_data_auto ("reserve_pub", &reserve_pub), GNUNET_JSON_pack_data_auto ("reserve_sig", &reserve_sig)); GNUNET_assert (NULL != req); - kwh = GNUNET_new (struct TALER_EXCHANGE_KycWalletHandle); - kwh->cb = cb; - kwh->cb_cls = cb_cls; - kwh->url = TALER_url_join (url, - "kyc-wallet", - NULL); - if (NULL == kwh->url) + pkwh->url = TALER_url_join (pkwh->base_url, + "kyc-wallet", + NULL); + if (NULL == pkwh->url) { json_decref (req); - GNUNET_free (kwh); - return NULL; + return TALER_EC_GENERIC_CONFIGURATION_INVALID; } - eh = TALER_EXCHANGE_curl_easy_get_ (kwh->url); + eh = TALER_EXCHANGE_curl_easy_get_ (pkwh->url); if ( (NULL == eh) || (GNUNET_OK != - TALER_curl_easy_post (&kwh->ctx, + TALER_curl_easy_post (&pkwh->post_ctx, eh, req)) ) { @@ -226,32 +263,44 @@ TALER_EXCHANGE_kyc_wallet ( if (NULL != eh) curl_easy_cleanup (eh); json_decref (req); - GNUNET_free (kwh->url); - GNUNET_free (kwh); - return NULL; + GNUNET_free (pkwh->url); + pkwh->url = NULL; + return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE; } json_decref (req); - kwh->job = GNUNET_CURL_job_add2 (ctx, - eh, - kwh->ctx.headers, - &handle_kyc_wallet_finished, - kwh); - return kwh; + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Requesting URL '%s'\n", + pkwh->url); + pkwh->job = GNUNET_CURL_job_add2 (pkwh->ctx, + eh, + pkwh->post_ctx.headers, + &handle_kyc_wallet_finished, + pkwh); + if (NULL == pkwh->job) + { + TALER_curl_easy_post_finished (&pkwh->post_ctx); + GNUNET_free (pkwh->url); + pkwh->url = NULL; + return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE; + } + return TALER_EC_NONE; } void -TALER_EXCHANGE_kyc_wallet_cancel (struct TALER_EXCHANGE_KycWalletHandle *kwh) +TALER_EXCHANGE_post_kyc_wallet_cancel ( + struct TALER_EXCHANGE_PostKycWalletHandle *pkwh) { - if (NULL != kwh->job) + if (NULL != pkwh->job) { - GNUNET_CURL_job_cancel (kwh->job); - kwh->job = NULL; + GNUNET_CURL_job_cancel (pkwh->job); + pkwh->job = NULL; } - GNUNET_free (kwh->url); - TALER_curl_easy_post_finished (&kwh->ctx); - GNUNET_free (kwh); + TALER_curl_easy_post_finished (&pkwh->post_ctx); + GNUNET_free (pkwh->url); + GNUNET_free (pkwh->base_url); + GNUNET_free (pkwh); } -/* end of exchange_api_kyc_wallet.c */ +/* end of exchange_api_post-kyc-wallet.c */ diff --git a/src/testing/Makefile.am b/src/testing/Makefile.am @@ -37,7 +37,7 @@ libtalertwistertesting_la_LDFLAGS = \ endif libtalertesting_la_LDFLAGS = \ - -version-info 3:0:2 \ + -version-info 4:0:0 \ -no-undefined libtalertesting_la_SOURCES = \ testing_api_cmd_age_withdraw.c \ diff --git a/src/testing/testing_api_cmd_check_aml_decisions.c b/src/testing/testing_api_cmd_check_aml_decisions.c @@ -1,6 +1,6 @@ /* This file is part of TALER - Copyright (C) 2023, 2024 Taler Systems SA + Copyright (C) 2023, 2024, 2026 Taler Systems SA TALER is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -24,6 +24,15 @@ #include "taler/platform.h" #include "taler/taler_json_lib.h" #include <gnunet/gnunet_curl_lib.h> + +/** + * State for a "check_aml_decisions" CMD. + */ +struct AmlCheckState; + +#define TALER_EXCHANGE_GET_AML_DECISIONS_RESULT_CLOSURE \ + struct AmlCheckState +#include "taler/taler-exchange/get-aml-OFFICER_PUB-decisions.h" #include "taler/taler_testing_lib.h" #include "taler/taler_signatures.h" #include "taler/backoff.h" @@ -38,7 +47,7 @@ struct AmlCheckState /** * Handle while operation is running. */ - struct TALER_EXCHANGE_LookupAmlDecisions *dh; + struct TALER_EXCHANGE_GetAmlDecisionsHandle *dh; /** * Our interpreter. @@ -70,16 +79,14 @@ struct AmlCheckState * Callback to analyze the /aml/$OFFICER_PUB/$decision/$H_PAYTO response, just used to check * if the response code is acceptable. * - * @param cls closure. + * @param ds our state * @param adr response details */ static void check_aml_decisions_cb ( - void *cls, - const struct TALER_EXCHANGE_AmlDecisionsResponse *adr) + TALER_EXCHANGE_GET_AML_DECISIONS_RESULT_CLOSURE *ds, + const struct TALER_EXCHANGE_GetAmlDecisionsResponse *adr) { - struct AmlCheckState *ds = cls; - ds->dh = NULL; if (ds->expected_http_status != adr->hr.http_status) { @@ -92,7 +99,7 @@ check_aml_decisions_cb ( { const struct TALER_TESTING_Command *ref; const char *justification; - const struct TALER_EXCHANGE_AmlDecision *oldest = NULL; + const struct TALER_EXCHANGE_GetAmlDecisionsDecision *oldest = NULL; if (NULL != ds->ref_operation) { @@ -110,10 +117,10 @@ check_aml_decisions_cb ( ref, &justification)) { - for (unsigned int i = 0; i<adr->details.ok.decisions_length; i++) + for (unsigned int i = 0; i<adr->details.ok.records_length; i++) { - const struct TALER_EXCHANGE_AmlDecision *aml_history - = &adr->details.ok.decisions[i]; + const struct TALER_EXCHANGE_GetAmlDecisionsDecision *aml_history + = &adr->details.ok.records[i]; if ( (NULL == oldest) || (GNUNET_TIME_timestamp_cmp (oldest->decision_time, @@ -210,23 +217,38 @@ check_aml_decisions_run ( TALER_TESTING_get_trait_officer_priv ( ref, &officer_priv)); - ds->dh = TALER_EXCHANGE_lookup_aml_decisions ( + ds->dh = TALER_EXCHANGE_get_aml_decisions_create ( TALER_TESTING_interpreter_get_context (is), exchange_url, - h_payto, /* NULL to return all */ - TALER_EXCHANGE_YNA_ALL, - TALER_EXCHANGE_YNA_ALL, - UINT64_MAX, /* offset */ - -1, /* limit */ - officer_priv, - &check_aml_decisions_cb, - ds); + officer_priv); if (NULL == ds->dh) { GNUNET_break (0); TALER_TESTING_interpreter_fail (is); return; } + TALER_EXCHANGE_get_aml_decisions_set_options ( + ds->dh, + TALER_EXCHANGE_get_aml_decisions_option_offset (UINT64_MAX), + TALER_EXCHANGE_get_aml_decisions_option_limit (-1)); + if (NULL != h_payto) + TALER_EXCHANGE_get_aml_decisions_set_options ( + ds->dh, + TALER_EXCHANGE_get_aml_decisions_option_filter_h_payto (h_payto)); + { + enum TALER_ErrorCode ec; + + ec = TALER_EXCHANGE_get_aml_decisions_start (ds->dh, + &check_aml_decisions_cb, + ds); + if (TALER_EC_NONE != ec) + { + GNUNET_break (0); + ds->dh = NULL; + TALER_TESTING_interpreter_fail (is); + return; + } + } } @@ -248,7 +270,7 @@ check_aml_decisions_cleanup ( { TALER_TESTING_command_incomplete (ds->is, cmd->label); - TALER_EXCHANGE_lookup_aml_decisions_cancel (ds->dh); + TALER_EXCHANGE_get_aml_decisions_cancel (ds->dh); ds->dh = NULL; } GNUNET_free (ds); diff --git a/src/testing/testing_api_cmd_get_active_legitimization_measures.c b/src/testing/testing_api_cmd_get_active_legitimization_measures.c @@ -233,7 +233,7 @@ get_active_legitimization_measures_run ( TALER_EXCHANGE_get_aml_legitimizations_option_filter_active ( TALER_EXCHANGE_YNA_YES))); GNUNET_assert ( - TALER_EXCHANGE_GET_AML_LEGITIMIZATIONS_START_OK == + TALER_EC_NONE == TALER_EXCHANGE_get_aml_legitimizations_start ( ds->dh, &get_active_legitimization_measures_cb, diff --git a/src/testing/testing_api_cmd_get_kyc_info.c b/src/testing/testing_api_cmd_get_kyc_info.c @@ -1,6 +1,6 @@ /* This file is part of TALER - Copyright (C) 2024 Taler Systems SA + Copyright (C) 2024, 2026 Taler Systems SA TALER is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as @@ -25,6 +25,15 @@ #include "taler/platform.h" #include "taler/taler_json_lib.h" #include <gnunet/gnunet_curl_lib.h> + +/** + * State for a GET kyc-info CMD. + */ +struct GetKycInfoState; + +#define TALER_EXCHANGE_GET_KYC_INFO_RESULT_CLOSURE \ + struct GetKycInfoState +#include "taler/taler-exchange/get-kyc-info-ACCESS_TOKEN.h" #include "taler/taler_testing_lib.h" /** @@ -46,7 +55,7 @@ struct GetKycInfoState /** * Handle to the GET /kyc-info pending operation. */ - struct TALER_EXCHANGE_KycInfoHandle *kwh; + struct TALER_EXCHANGE_GetKycInfoHandle *kwh; /** * Interpreter state. @@ -69,15 +78,14 @@ struct GetKycInfoState /** * Handle response to the command. * - * @param cls closure. + * @param kcg our state * @param kpci GET KYC status response details */ static void kyc_info_cb ( - void *cls, - const struct TALER_EXCHANGE_KycProcessClientInformation *kpci) + TALER_EXCHANGE_GET_KYC_INFO_RESULT_CLOSURE *kcg, + const struct TALER_EXCHANGE_GetKycInfoResponse *kpci) { - struct GetKycInfoState *kcg = cls; struct TALER_TESTING_Interpreter *is = kcg->is; kcg->kwh = NULL; @@ -145,15 +153,25 @@ get_kyc_info_run (void *cls, TALER_TESTING_interpreter_fail (kcg->is); return; } - kcg->kwh = TALER_EXCHANGE_kyc_info ( + kcg->kwh = TALER_EXCHANGE_get_kyc_info_create ( TALER_TESTING_interpreter_get_context (is), TALER_TESTING_get_exchange_url (is), - token, - NULL /* etag */, - GNUNET_TIME_UNIT_ZERO, - &kyc_info_cb, - kcg); + token); GNUNET_assert (NULL != kcg->kwh); + { + enum TALER_ErrorCode ec; + + ec = TALER_EXCHANGE_get_kyc_info_start (kcg->kwh, + &kyc_info_cb, + kcg); + if (TALER_EC_NONE != ec) + { + GNUNET_break (0); + kcg->kwh = NULL; + TALER_TESTING_interpreter_fail (kcg->is); + return; + } + } } @@ -175,7 +193,7 @@ get_kyc_info_cleanup ( { TALER_TESTING_command_incomplete (kcg->is, cmd->label); - TALER_EXCHANGE_kyc_info_cancel (kcg->kwh); + TALER_EXCHANGE_get_kyc_info_cancel (kcg->kwh); kcg->kwh = NULL; } for (unsigned int i = 0; i<kcg->num_ids; i++) diff --git a/src/testing/testing_api_cmd_kyc_check_get.c b/src/testing/testing_api_cmd_kyc_check_get.c @@ -1,6 +1,6 @@ /* This file is part of TALER - Copyright (C) 2021-2023 Taler Systems SA + Copyright (C) 2021-2026 Taler Systems SA TALER is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as @@ -25,6 +25,15 @@ #include "taler/platform.h" #include "taler/taler_json_lib.h" #include <gnunet/gnunet_curl_lib.h> + +/** + * State for a "track transaction" CMD. + */ +struct KycCheckGetState; + +#define TALER_EXCHANGE_GET_KYC_CHECK_RESULT_CLOSURE \ + struct KycCheckGetState +#include "taler/taler-exchange/get-kyc-check-H_NORMALIZED_PAYTO.h" #include "taler/taler_testing_lib.h" /** @@ -42,7 +51,7 @@ struct KycCheckGetState /** * Handle to the "track transaction" pending operation. */ - struct TALER_EXCHANGE_KycCheckHandle *kwh; + struct TALER_EXCHANGE_GetKycCheckHandle *kwh; /** * Interpreter state. @@ -75,14 +84,14 @@ struct KycCheckGetState /** * Handle response to the command. * - * @param cls closure. + * @param kcg our state * @param ks GET KYC status response details */ static void -check_kyc_cb (void *cls, - const struct TALER_EXCHANGE_KycStatus *ks) +check_kyc_cb ( + TALER_EXCHANGE_GET_KYC_CHECK_RESULT_CLOSURE *kcg, + const struct TALER_EXCHANGE_GetKycCheckResponse *ks) { - struct KycCheckGetState *kcg = cls; struct TALER_TESTING_Interpreter *is = kcg->is; kcg->kwh = NULL; @@ -172,19 +181,31 @@ check_kyc_run (void *cls, TALER_TESTING_interpreter_fail (kcg->is); return; } - kcg->kwh = TALER_EXCHANGE_kyc_check ( + kcg->kwh = TALER_EXCHANGE_get_kyc_check_create ( TALER_TESTING_interpreter_get_context (is), TALER_TESTING_get_exchange_url (is), h_payto, - account_priv, - kcg->lpt, - 0, - TALER_EXCHANGE_KLPT_NONE == kcg->lpt - ? GNUNET_TIME_UNIT_ZERO - : GNUNET_TIME_UNIT_MINUTES, - &check_kyc_cb, - kcg); + account_priv); GNUNET_assert (NULL != kcg->kwh); + if (TALER_EXCHANGE_KLPT_NONE != kcg->lpt) + TALER_EXCHANGE_get_kyc_check_set_options ( + kcg->kwh, + TALER_EXCHANGE_get_kyc_check_option_lpt (kcg->lpt), + TALER_EXCHANGE_get_kyc_check_option_timeout (GNUNET_TIME_UNIT_MINUTES)); + { + enum TALER_ErrorCode ec; + + ec = TALER_EXCHANGE_get_kyc_check_start (kcg->kwh, + &check_kyc_cb, + kcg); + if (TALER_EC_NONE != ec) + { + GNUNET_break (0); + kcg->kwh = NULL; + TALER_TESTING_interpreter_fail (kcg->is); + return; + } + } } @@ -205,7 +226,7 @@ check_kyc_cleanup (void *cls, { TALER_TESTING_command_incomplete (kcg->is, cmd->label); - TALER_EXCHANGE_kyc_check_cancel (kcg->kwh); + TALER_EXCHANGE_get_kyc_check_cancel (kcg->kwh); kcg->kwh = NULL; } GNUNET_free (kcg); diff --git a/src/testing/testing_api_cmd_kyc_proof.c b/src/testing/testing_api_cmd_kyc_proof.c @@ -1,6 +1,6 @@ /* This file is part of TALER - Copyright (C) 2021 Taler Systems SA + Copyright (C) 2021-2026 Taler Systems SA TALER is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as @@ -25,6 +25,15 @@ #include "taler/platform.h" #include "taler/taler_json_lib.h" #include <gnunet/gnunet_curl_lib.h> + +/** + * State for a "track transaction" CMD. + */ +struct KycProofGetState; + +#define TALER_EXCHANGE_GET_KYC_PROOF_RESULT_CLOSURE \ + struct KycProofGetState +#include "taler/taler-exchange/get-kyc-proof-PROVIDER_NAME.h" #include "taler/taler_testing_lib.h" /** @@ -60,9 +69,15 @@ struct KycProofGetState char *redirect_url; /** + * Additional URL arguments (e.g. "&code=..."), kept alive + * for the lifetime of the request. + */ + char *uargs; + + /** * Handle to the "track transaction" pending operation. */ - struct TALER_EXCHANGE_KycProofHandle *kph; + struct TALER_EXCHANGE_GetKycProofHandle *kph; /** * Interpreter state. @@ -74,14 +89,14 @@ struct KycProofGetState /** * Handle response to the command. * - * @param cls closure. + * @param kcg our state * @param kpr KYC proof response details */ static void -proof_kyc_cb (void *cls, - const struct TALER_EXCHANGE_KycProofResponse *kpr) +proof_kyc_cb ( + struct KycProofGetState *kcg, + const struct TALER_EXCHANGE_GetKycProofResponse *kpr) { - struct KycProofGetState *kcg = cls; struct TALER_TESTING_Interpreter *is = kcg->is; kcg->kph = NULL; @@ -127,7 +142,6 @@ proof_kyc_run (void *cls, struct KycProofGetState *kps = cls; const struct TALER_TESTING_Command *res_cmd; const struct TALER_NormalizedPaytoHashP *h_payto; - char *uargs; const char *exchange_url; (void) cmd; @@ -155,22 +169,34 @@ proof_kyc_run (void *cls, TALER_TESTING_interpreter_fail (kps->is); return; } - if (NULL == kps->code) - uargs = NULL; - else - GNUNET_asprintf (&uargs, + if (NULL != kps->code) + GNUNET_asprintf (&kps->uargs, "&code=%s", kps->code); - kps->kph = TALER_EXCHANGE_kyc_proof ( + kps->kph = TALER_EXCHANGE_get_kyc_proof_create ( TALER_TESTING_interpreter_get_context (is), exchange_url, h_payto, - kps->logic, - uargs, - &proof_kyc_cb, - kps); - GNUNET_free (uargs); + kps->logic); GNUNET_assert (NULL != kps->kph); + if (NULL != kps->uargs) + TALER_EXCHANGE_get_kyc_proof_set_options ( + kps->kph, + TALER_EXCHANGE_get_kyc_proof_option_args (kps->uargs)); + { + enum TALER_ErrorCode ec; + + ec = TALER_EXCHANGE_get_kyc_proof_start (kps->kph, + &proof_kyc_cb, + kps); + if (TALER_EC_NONE != ec) + { + GNUNET_break (0); + kps->kph = NULL; + TALER_TESTING_interpreter_fail (kps->is); + return; + } + } } @@ -191,10 +217,11 @@ proof_kyc_cleanup (void *cls, { TALER_TESTING_command_incomplete (kps->is, cmd->label); - TALER_EXCHANGE_kyc_proof_cancel (kps->kph); + TALER_EXCHANGE_get_kyc_proof_cancel (kps->kph); kps->kph = NULL; } GNUNET_free (kps->redirect_url); + GNUNET_free (kps->uargs); GNUNET_free (kps); } diff --git a/src/testing/testing_api_cmd_kyc_wallet_get.c b/src/testing/testing_api_cmd_kyc_wallet_get.c @@ -1,6 +1,6 @@ /* This file is part of TALER - Copyright (C) 2021-2024 Taler Systems SA + Copyright (C) 2021-2026 Taler Systems SA TALER is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as @@ -25,6 +25,15 @@ #include "taler/platform.h" #include "taler/taler_json_lib.h" #include <gnunet/gnunet_curl_lib.h> + +/** + * State for a "/kyc-wallet" GET CMD. + */ +struct KycWalletGetState; + +#define TALER_EXCHANGE_POST_KYC_WALLET_RESULT_CLOSURE \ + struct KycWalletGetState +#include "taler/taler-exchange/post-kyc-wallet.h" #include "taler/taler_testing_lib.h" /** @@ -78,7 +87,7 @@ struct KycWalletGetState /** * Handle to the "track transaction" pending operation. */ - struct TALER_EXCHANGE_KycWalletHandle *kwh; + struct TALER_EXCHANGE_PostKycWalletHandle *kwh; /** * Balance to pass to the exchange. @@ -95,14 +104,14 @@ struct KycWalletGetState /** * Handle response to the command. * - * @param cls closure. + * @param kwg our state * @param wkr GET deposit response details */ static void -wallet_kyc_cb (void *cls, - const struct TALER_EXCHANGE_WalletKycResponse *wkr) +wallet_kyc_cb ( + struct KycWalletGetState *kwg, + const struct TALER_EXCHANGE_PostKycWalletResponse *wkr) { - struct KycWalletGetState *kwg = cls; struct TALER_TESTING_Interpreter *is = kwg->is; kwg->kwh = NULL; @@ -196,14 +205,26 @@ wallet_kyc_run (void *cls, kwg->reserve_payto_uri = TALER_reserve_make_payto (exchange_url, &kwg->account_pub.reserve_pub); - kwg->kwh = TALER_EXCHANGE_kyc_wallet ( + kwg->kwh = TALER_EXCHANGE_post_kyc_wallet_create ( TALER_TESTING_interpreter_get_context (is), exchange_url, &kwg->account_priv.reserve_priv, - &kwg->balance, - &wallet_kyc_cb, - kwg); + &kwg->balance); GNUNET_assert (NULL != kwg->kwh); + { + enum TALER_ErrorCode ec; + + ec = TALER_EXCHANGE_post_kyc_wallet_start (kwg->kwh, + &wallet_kyc_cb, + kwg); + if (TALER_EC_NONE != ec) + { + GNUNET_break (0); + kwg->kwh = NULL; + TALER_TESTING_interpreter_fail (is); + return; + } + } } @@ -225,7 +246,7 @@ wallet_kyc_cleanup ( { TALER_TESTING_command_incomplete (kwg->is, cmd->label); - TALER_EXCHANGE_kyc_wallet_cancel (kwg->kwh); + TALER_EXCHANGE_post_kyc_wallet_cancel (kwg->kwh); kwg->kwh = NULL; } GNUNET_free (kwg->reserve_payto_uri.normalized_payto); diff --git a/src/testing/testing_api_cmd_post_kyc_start.c b/src/testing/testing_api_cmd_post_kyc_start.c @@ -1,6 +1,6 @@ /* This file is part of TALER - Copyright (C) 2024 Taler Systems SA + Copyright (C) 2024, 2026 Taler Systems SA TALER is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as @@ -25,6 +25,15 @@ #include "taler/platform.h" #include "taler/taler_json_lib.h" #include <gnunet/gnunet_curl_lib.h> + +/** + * State for a POST /kyc-start CMD. + */ +struct PostKycStartState; + +#define TALER_EXCHANGE_POST_KYC_START_RESULT_CLOSURE \ + struct PostKycStartState +#include "taler/taler-exchange/post-kyc-start-ID.h" #include "taler/taler_testing_lib.h" /** @@ -56,7 +65,7 @@ struct PostKycStartState /** * Handle to the KYC start pending operation. */ - struct TALER_EXCHANGE_KycStartHandle *kwh; + struct TALER_EXCHANGE_PostKycStartHandle *kwh; /** * Interpreter state. @@ -68,15 +77,14 @@ struct PostKycStartState /** * Handle response to the command. * - * @param cls closure. + * @param kcg our state * @param ks GET KYC status response details */ static void post_kyc_start_cb ( - void *cls, - const struct TALER_EXCHANGE_KycStartResponse *ks) + struct PostKycStartState *kcg, + const struct TALER_EXCHANGE_PostKycStartResponse *ks) { - struct PostKycStartState *kcg = cls; struct TALER_TESTING_Interpreter *is = kcg->is; kcg->kwh = NULL; @@ -146,13 +154,25 @@ post_kyc_start_run (void *cls, TALER_TESTING_interpreter_fail (kcg->is); return; } - kcg->kwh = TALER_EXCHANGE_kyc_start ( + kcg->kwh = TALER_EXCHANGE_post_kyc_start_create ( TALER_TESTING_interpreter_get_context (is), TALER_TESTING_get_exchange_url (is), - id, - &post_kyc_start_cb, - kcg); + id); GNUNET_assert (NULL != kcg->kwh); + { + enum TALER_ErrorCode ec; + + ec = TALER_EXCHANGE_post_kyc_start_start (kcg->kwh, + &post_kyc_start_cb, + kcg); + if (TALER_EC_NONE != ec) + { + GNUNET_break (0); + kcg->kwh = NULL; + TALER_TESTING_interpreter_fail (kcg->is); + return; + } + } } @@ -173,7 +193,7 @@ post_kyc_start_cleanup (void *cls, { TALER_TESTING_command_incomplete (kcg->is, cmd->label); - TALER_EXCHANGE_kyc_start_cancel (kcg->kwh); + TALER_EXCHANGE_post_kyc_start_cancel (kcg->kwh); kcg->kwh = NULL; } GNUNET_free (kcg->redirect_url); diff --git a/src/testing/testing_api_cmd_take_aml_decision.c b/src/testing/testing_api_cmd_take_aml_decision.c @@ -1,6 +1,6 @@ /* This file is part of TALER - Copyright (C) 2023, 2024 Taler Systems SA + Copyright (C) 2023, 2024, 2026 Taler Systems SA TALER is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -24,6 +24,15 @@ #include "taler/platform.h" #include "taler/taler_json_lib.h" #include <gnunet/gnunet_curl_lib.h> + +/** + * State for a "take_aml_decision" CMD. + */ +struct AmlDecisionState; + +#define TALER_EXCHANGE_POST_AML_DECISION_RESULT_CLOSURE \ + struct AmlDecisionState +#include "taler/taler-exchange/post-aml-OFFICER_PUB-decision.h" #include "taler/taler_testing_lib.h" #include "taler/taler_signatures.h" #include "taler/backoff.h" @@ -36,9 +45,9 @@ struct AmlDecisionState { /** - * Auditor enable handle while operation is running. + * Handle while operation is running. */ - struct TALER_EXCHANGE_AddAmlDecision *dh; + struct TALER_EXCHANGE_PostAmlDecisionHandle *dh; /** * Our interpreter. @@ -101,19 +110,18 @@ struct AmlDecisionState /** - * Callback to analyze the /aml-decision/$OFFICER_PUB response, just used to check - * if the response code is acceptable. + * Callback to analyze the /aml-decision/$OFFICER_PUB response, just used to + * check if the response code is acceptable. * - * @param cls closure. - * @param adr response details + * @param ds our state + * @param pr response details */ static void take_aml_decision_cb ( - void *cls, - const struct TALER_EXCHANGE_AddAmlDecisionResponse *adr) + struct AmlDecisionState *ds, + const struct TALER_EXCHANGE_PostAmlDecisionResponse *pr) { - struct AmlDecisionState *ds = cls; - const struct TALER_EXCHANGE_HttpResponse *hr = &adr->hr; + const struct TALER_EXCHANGE_HttpResponse *hr = &pr->hr; ds->dh = NULL; if (ds->expected_response != hr->http_status) @@ -304,7 +312,7 @@ take_aml_decision_run (void *cls, rule->measures = GNUNET_new_array (rule->num_measures, const char *); - for (unsigned int k = 0; k<rule->num_measures; k++) + for (unsigned int k = 0; k < rule->num_measures; k++) rule->measures[k] = json_string_value ( json_array_get (jameasures, @@ -347,51 +355,63 @@ take_aml_decision_run (void *cls, mname, err_name); TALER_TESTING_interpreter_fail (is); + for (unsigned int j = 0; j < num_rules; j++) + GNUNET_free (rules[j].measures); return; } } GNUNET_assert (off == num_measures); + ds->dh = TALER_EXCHANGE_post_aml_decision_create ( + TALER_TESTING_interpreter_get_context (is), + exchange_url, + h_payto, + now, + ds->successor_measure, + expiration_time, + num_rules, + rules, + num_measures, + measures, + ds->keep_investigating, + ds->justification, + officer_priv); + + for (unsigned int j = 0; j < num_rules; j++) + GNUNET_free (rules[j].measures); + + if (NULL == ds->dh) { - struct TALER_FullPayto null_payto = { - .full_payto = NULL - }; - - ds->dh = TALER_EXCHANGE_post_aml_decision ( - TALER_TESTING_interpreter_get_context (is), - exchange_url, - h_payto, - null_payto, - now, - ds->successor_measure, - new_measures, - expiration_time, - num_rules, - rules, - num_measures, - measures, - ds->properties, - ds->keep_investigating, - ds->justification, - officer_priv, - 0, NULL, /* no events */ - &take_aml_decision_cb, - ds); + GNUNET_break (0); + TALER_TESTING_interpreter_fail (is); + return; } - for (unsigned int j = 0; j<num_rules; j++) + + /* Set optional: new_measures and properties */ + if (NULL != new_measures) + TALER_EXCHANGE_post_aml_decision_set_options ( + ds->dh, + TALER_EXCHANGE_post_aml_decision_option_new_measures (new_measures)); + if (NULL != ds->properties) + TALER_EXCHANGE_post_aml_decision_set_options ( + ds->dh, + TALER_EXCHANGE_post_aml_decision_option_properties (ds->properties)); + { - struct TALER_EXCHANGE_AccountRule *rule = &rules[j]; + enum TALER_ErrorCode ec; - GNUNET_free (rule->measures); + ec = TALER_EXCHANGE_post_aml_decision_start (ds->dh, + &take_aml_decision_cb, + ds); + if (TALER_EC_NONE != ec) + { + GNUNET_break (0); + ds->dh = NULL; + TALER_TESTING_interpreter_fail (is); + return; + } } } - - if (NULL == ds->dh) - { - GNUNET_break (0); - TALER_TESTING_interpreter_fail (is); - return; - } }