From ff48ada7d5ba026a39c0e8ddefd54b80b813425b Mon Sep 17 00:00:00 2001 From: Christian Grothoff Date: Mon, 8 Aug 2022 15:22:45 +0200 Subject: [PATCH] move kyclogic into libtalerkyclogic --- .gitignore | 1 + src/exchange/Makefile.am | 2 +- .../taler-exchange-httpd_kyc-webhook.c | 8 +- src/include/Makefile.am | 1 + .../taler_kyclogic_lib.h} | 94 +++-- src/kyclogic/Makefile.am | 12 + .../kyclogic_api.c} | 287 +++++++++------ src/kyclogic/taler-exchange-kyc-tester.c | 347 +++++++++++++++++- 8 files changed, 583 insertions(+), 169 deletions(-) rename src/{exchange/taler-exchange-httpd_kyc.h => include/taler_kyclogic_lib.h} (57%) rename src/{exchange/taler-exchange-httpd_kyc.c => kyclogic/kyclogic_api.c} (74%) diff --git a/.gitignore b/.gitignore index 7145bc351..a27208efa 100644 --- a/.gitignore +++ b/.gitignore @@ -163,3 +163,4 @@ po/remove-potcdate.sed src/include/taler_dbevents.h src/bank-lib/taler-exchange-wire-gateway-client src/exchange/taler-exchange-drain +src/kyclogic/taler-exchange-kyc-tester diff --git a/src/exchange/Makefile.am b/src/exchange/Makefile.am index 7141758b1..64db3899b 100644 --- a/src/exchange/Makefile.am +++ b/src/exchange/Makefile.am @@ -132,7 +132,6 @@ taler_exchange_httpd_SOURCES = \ taler-exchange-httpd_deposits_get.c taler-exchange-httpd_deposits_get.h \ taler-exchange-httpd_extensions.c taler-exchange-httpd_extensions.h \ taler-exchange-httpd_keys.c taler-exchange-httpd_keys.h \ - taler-exchange-httpd_kyc.c taler-exchange-httpd_kyc.h \ taler-exchange-httpd_kyc-check.c taler-exchange-httpd_kyc-check.h \ taler-exchange-httpd_kyc-proof.c taler-exchange-httpd_kyc-proof.h \ taler-exchange-httpd_kyc-wallet.c taler-exchange-httpd_kyc-wallet.h \ @@ -177,6 +176,7 @@ taler_exchange_httpd_LDADD = \ $(top_builddir)/src/mhd/libtalermhd.la \ $(top_builddir)/src/json/libtalerjson.la \ $(top_builddir)/src/exchangedb/libtalerexchangedb.la \ + $(top_builddir)/src/kyclogic/libtalerkyclogic.la \ $(top_builddir)/src/util/libtalerutil.la \ $(top_builddir)/src/extensions/libtalerextensions.la \ -lmicrohttpd \ diff --git a/src/exchange/taler-exchange-httpd_kyc-webhook.c b/src/exchange/taler-exchange-httpd_kyc-webhook.c index a5c11cec9..bba8e1a51 100644 --- a/src/exchange/taler-exchange-httpd_kyc-webhook.c +++ b/src/exchange/taler-exchange-httpd_kyc-webhook.c @@ -26,7 +26,7 @@ #include #include "taler_json_lib.h" #include "taler_mhd_lib.h" -#include "taler-exchange-httpd_kyc.h" +#include "taler_kyclogic_lib.h" #include "taler-exchange-httpd_kyc-webhook.h" #include "taler-exchange-httpd_responses.h" @@ -263,9 +263,9 @@ handler_kyc_webhook_generic ( rc->rh_cleaner = &clean_kwh; if (GNUNET_OK != - TEH_kyc_get_logic (kwh->logic, - &kwh->plugin, - &kwh->pd)) + TALER_KYCLOGIC_kyc_get_logic (kwh->logic, + &kwh->plugin, + &kwh->pd)) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "KYC logic `%s' unknown (check KYC provider configuration)\n", diff --git a/src/include/Makefile.am b/src/include/Makefile.am index f3388507d..5cb1698cc 100644 --- a/src/include/Makefile.am +++ b/src/include/Makefile.am @@ -17,6 +17,7 @@ talerinclude_HEADERS = \ taler_exchangedb_plugin.h \ taler_extensions.h \ taler_fakebank_lib.h \ + taler_kyclogic_lib.h \ taler_kyclogic_plugin.h \ taler_json_lib.h \ taler_testing_lib.h \ diff --git a/src/exchange/taler-exchange-httpd_kyc.h b/src/include/taler_kyclogic_lib.h similarity index 57% rename from src/exchange/taler-exchange-httpd_kyc.h rename to src/include/taler_kyclogic_lib.h index 1df264c15..6b54276f6 100644 --- a/src/exchange/taler-exchange-httpd_kyc.h +++ b/src/include/taler_kyclogic_lib.h @@ -14,32 +14,32 @@ TALER; see the file COPYING. If not, see */ /** - * @file taler-exchange-httpd_kyc.h - * @brief KYC API for the exchange + * @file taler_kyclogic_lib.h + * @brief server-side KYC API * @author Christian Grothoff */ -#ifndef TALER_EXCHANGE_HTTPD_KYC_H -#define TALER_EXCHANGE_HTTPD_KYC_H +#ifndef TALER_KYCLOGIC_LIB_H +#define TALER_KYCLOGIC_LIB_H #include -#include "taler_exchangedb_plugin.h" -#include "taler_kyclogic_plugin.h" +#include +#include /** * Enumeration for our KYC user types. */ -enum TEH_KycUserType +enum TALER_KYCLOGIC_KycUserType { /** * KYC rule is for an individual. */ - TEH_KYC_UT_INDIVIDUAL = 0, + TALER_KYCLOGIC_KYC_UT_INDIVIDUAL = 0, /** * KYC rule is for a business. */ - TEH_KYC_UT_BUSINESS = 1 + TALER_KYCLOGIC_KYC_UT_BUSINESS = 1 }; @@ -47,28 +47,28 @@ enum TEH_KycUserType * Enumeration of possible events that may trigger * KYC requirements. */ -enum TEH_KycTriggerEvent +enum TALER_KYCLOGIC_KycTriggerEvent { /** * Customer withdraws coins. */ - TEH_KYC_TRIGGER_WITHDRAW = 0, + TALER_KYCLOGIC_KYC_TRIGGER_WITHDRAW = 0, /** * Merchant deposits coins. */ - TEH_KYC_TRIGGER_DEPOSIT = 1, + TALER_KYCLOGIC_KYC_TRIGGER_DEPOSIT = 1, /** * Wallet receives P2P payment. */ - TEH_KYC_TRIGGER_P2P_RECEIVE = 2, + TALER_KYCLOGIC_KYC_TRIGGER_P2P_RECEIVE = 2, /** * Wallet balance exceeds threshold. */ - TEH_KYC_TRIGGER_WALLET_BALANCE = 3 + TALER_KYCLOGIC_KYC_TRIGGER_WALLET_BALANCE = 3 }; @@ -84,8 +84,9 @@ enum TEH_KycTriggerEvent * malformed */ enum GNUNET_GenericReturnValue -TEH_kyc_trigger_from_string (const char *trigger_s, - enum TEH_KycTriggerEvent *trigger); +TALER_KYCLOGIC_kyc_trigger_from_string ( + const char *trigger_s, + enum TALER_KYCLOGIC_KycTriggerEvent *trigger); /** @@ -95,7 +96,7 @@ TEH_kyc_trigger_from_string (const char *trigger_s, * @return human-readable representation of the @a trigger */ const char * -TEH_kyc_trigger2s (enum TEH_KycTriggerEvent trigger); +TALER_KYCLOGIC_kyc_trigger2s (enum TALER_KYCLOGIC_KycTriggerEvent trigger); /** @@ -108,8 +109,8 @@ TEH_kyc_trigger2s (enum TEH_KycTriggerEvent trigger); * malformed */ enum GNUNET_GenericReturnValue -TEH_kyc_user_type_from_string (const char *ut_s, - enum TEH_KycUserType *ut); +TALER_KYCLOGIC_kyc_user_type_from_string (const char *ut_s, + enum TALER_KYCLOGIC_KycUserType *ut); /** @@ -119,24 +120,24 @@ TEH_kyc_user_type_from_string (const char *ut_s, * @return human-readable representation of the @a ut */ const char * -TEH_kyc_user_type2s (enum TEH_KycUserType ut); +TALER_KYCLOGIC_kyc_user_type2s (enum TALER_KYCLOGIC_KycUserType ut); /** - * Initialize KYC subsystem. Loads the KYC - * configuration. + * Initialize KYC subsystem. Loads the KYC configuration. * + * @param cfg configuration to parse * @return #GNUNET_OK on success */ enum GNUNET_GenericReturnValue -TEH_kyc_init (void); +TALER_KYCLOGIC_kyc_init (const struct GNUNET_CONFIGURATION_Handle *cfg); /** * Shut down the KYC subsystem. */ void -TEH_kyc_done (void); +TALER_KYCLOGIC_kyc_done (void); /** @@ -155,10 +156,28 @@ TEH_kyc_done (void); * @param cb_cls closure for @a cb */ typedef void -(*TEH_KycAmountIterator)(void *cls, - struct GNUNET_TIME_Absolute limit, - TALER_EXCHANGEDB_KycAmountCallback cb, - void *cb_cls); +(*TALER_KYCLOGIC_KycAmountIterator)(void *cls, + struct GNUNET_TIME_Absolute limit, + TALER_EXCHANGEDB_KycAmountCallback cb, + void *cb_cls); + + +/** + * Call us on KYC processes satisfied for the given + * account. Must match the ``select_satisfied_kyc_processes`` of the exchange database plugin. + * + * @param cls the @e cls of this struct with the plugin-specific state + * @param h_payto account identifier + * @param spc function to call for each satisfied KYC process + * @param spc_cls closure for @a spc + * @return transaction status code + */ +typedef enum GNUNET_DB_QueryStatus +(*TALER_KYCLOGIC_KycSatisfiedIterator)( + void *cls, + const struct TALER_PaytoHashP *h_payto, + TALER_EXCHANGEDB_SatisfiedProviderCallback spc, + void *spc_cls); /** @@ -172,6 +191,9 @@ typedef void * @param event what type of operation is triggering the * test if KYC is required * @param h_payto account the event is about + * @param ki callback that returns list of already + * satisfied KYC checks, implemented by ``select_satisfied_kyc_processes`` of the exchangedb + * @param ki_cls closure for @a ki * @param ai callback offered to inquire about historic * amounts involved in this type of operation * at the given account @@ -179,10 +201,12 @@ typedef void * @return NULL if no check is needed */ const char * -TEH_kyc_test_required (enum TEH_KycTriggerEvent event, - const struct TALER_PaytoHashP *h_payto, - TEH_KycAmountIterator ai, - void *ai_cls); +TALER_KYCLOGIC_kyc_test_required (enum TALER_KYCLOGIC_KycTriggerEvent event, + const struct TALER_PaytoHashP *h_payto, + TALER_KYCLOGIC_KycSatisfiedIterator ki, + void *ki_cls, + TALER_KYCLOGIC_KycAmountIterator ai, + void *ai_cls); /** @@ -194,9 +218,9 @@ TEH_kyc_test_required (enum TEH_KycTriggerEvent event, * @return #GNUNET_OK on success */ enum GNUNET_GenericReturnValue -TEH_kyc_get_logic (const char *provider_section_name, - struct TALER_KYCLOGIC_Plugin **plugin, - struct TALER_KYCLOGIC_ProviderDetails **pd); +TALER_KYCLOGIC_kyc_get_logic (const char *provider_section_name, + struct TALER_KYCLOGIC_Plugin **plugin, + struct TALER_KYCLOGIC_ProviderDetails **pd); #endif diff --git a/src/kyclogic/Makefile.am b/src/kyclogic/Makefile.am index 7c754a806..c57fc1fce 100644 --- a/src/kyclogic/Makefile.am +++ b/src/kyclogic/Makefile.am @@ -16,6 +16,17 @@ EXTRA_DIST = \ kyclogic.conf \ kyclogic-oauth2.conf +lib_LTLIBRARIES = \ + libtalerkyclogic.la + +libtalerkyclogic_la_SOURCES = \ + kyclogic_api.c +libtalerkyclogic_la_LIBADD = \ + $(top_builddir)/src/util/libtalerutil.la \ + -lgnunetutil \ + $(XLIB) + + bin_PROGRAMS = \ taler-exchange-kyc-tester @@ -23,6 +34,7 @@ taler_exchange_kyc_tester_SOURCES = \ taler-exchange-kyc-tester.c taler_exchange_kyc_tester_LDADD = \ $(LIBGCRYPT_LIBS) \ + libtalerkyclogic.la \ $(top_builddir)/src/mhd/libtalermhd.la \ $(top_builddir)/src/json/libtalerjson.la \ $(top_builddir)/src/util/libtalerutil.la \ diff --git a/src/exchange/taler-exchange-httpd_kyc.c b/src/kyclogic/kyclogic_api.c similarity index 74% rename from src/exchange/taler-exchange-httpd_kyc.c rename to src/kyclogic/kyclogic_api.c index ea90c08a6..f2d31acf5 100644 --- a/src/exchange/taler-exchange-httpd_kyc.c +++ b/src/kyclogic/kyclogic_api.c @@ -14,25 +14,23 @@ TALER; see the file COPYING. If not, see */ /** - * @file taler-exchange-httpd_kyc.c - * @brief KYC API for the exchange + * @file kyclogic_api.c + * @brief server-side KYC API * @author Christian Grothoff */ #include "platform.h" -#include "taler-exchange-httpd.h" -#include "taler-exchange-httpd_kyc.h" -#include "taler_exchangedb_plugin.h" +#include "taler_kyclogic_lib.h" /** * Information about a KYC provider. */ -struct TEH_KycProvider; +struct TALER_KYCLOGIC_KycProvider; /** * Abstract representation of a KYC check. */ -struct TEH_KycCheck +struct TALER_KYCLOGIC_KycCheck { /** * Human-readable name given to the KYC check. @@ -42,7 +40,7 @@ struct TEH_KycCheck /** * Array of @e num_providers providers that offer this type of KYC check. */ - struct TEH_KycProvider **providers; + struct TALER_KYCLOGIC_KycProvider **providers; /** * Length of the @e providers array. @@ -52,7 +50,7 @@ struct TEH_KycCheck }; -struct TEH_KycProvider +struct TALER_KYCLOGIC_KycProvider { /** * Name of the provider (configuration section name). @@ -62,7 +60,7 @@ struct TEH_KycProvider /** * Array of @e num_checks checks performed by this provider. */ - struct TEH_KycCheck **provided_checks; + struct TALER_KYCLOGIC_KycCheck **provided_checks; /** * Logic to run for this provider. @@ -88,14 +86,14 @@ struct TEH_KycProvider /** * Type of user this provider supports. */ - enum TEH_KycUserType user_type; + enum TALER_KYCLOGIC_KycUserType user_type; }; /** * Condition that triggers a need to perform KYC. */ -struct TEH_KycTrigger +struct TALER_KYCLOGIC_KycTrigger { /** @@ -114,7 +112,7 @@ struct TEH_KycTrigger /** * Array of @e num_checks checks to apply on this trigger. */ - struct TEH_KycCheck **required_checks; + struct TALER_KYCLOGIC_KycCheck **required_checks; /** * Length of the @e checks array. @@ -124,7 +122,7 @@ struct TEH_KycTrigger /** * What event is this trigger for? */ - enum TEH_KycTriggerEvent trigger; + enum TALER_KYCLOGIC_KycTriggerEvent trigger; }; @@ -143,7 +141,7 @@ static unsigned int num_kyc_logics; * Array of @e num_kyc_checks known types of * KYC checks. */ -static struct TEH_KycCheck **kyc_checks; +static struct TALER_KYCLOGIC_KycCheck **kyc_checks; /** * Length of the #kyc_checks array. @@ -153,7 +151,7 @@ static unsigned int num_kyc_checks; /** * Array of configured triggers. */ -static struct TEH_KycTrigger **kyc_triggers; +static struct TALER_KYCLOGIC_KycTrigger **kyc_triggers; /** * Length of the #kyc_triggers array. @@ -163,7 +161,7 @@ static unsigned int num_kyc_triggers; /** * Array of configured providers. */ -static struct TEH_KycProvider **kyc_providers; +static struct TALER_KYCLOGIC_KycProvider **kyc_providers; /** * Length of the #kyc_providers array. @@ -172,18 +170,19 @@ static unsigned int num_kyc_providers; enum GNUNET_GenericReturnValue -TEH_kyc_trigger_from_string (const char *trigger_s, - enum TEH_KycTriggerEvent *trigger) +TALER_KYCLOGIC_kyc_trigger_from_string (const char *trigger_s, + enum TALER_KYCLOGIC_KycTriggerEvent * + trigger) { struct { const char *in; - enum TEH_KycTriggerEvent out; + enum TALER_KYCLOGIC_KycTriggerEvent out; } map [] = { - { "withdraw", TEH_KYC_TRIGGER_WITHDRAW }, - { "deposit", TEH_KYC_TRIGGER_DEPOSIT }, - { "merge", TEH_KYC_TRIGGER_P2P_RECEIVE }, - { "balance", TEH_KYC_TRIGGER_WALLET_BALANCE }, + { "withdraw", TALER_KYCLOGIC_KYC_TRIGGER_WITHDRAW }, + { "deposit", TALER_KYCLOGIC_KYC_TRIGGER_DEPOSIT }, + { "merge", TALER_KYCLOGIC_KYC_TRIGGER_P2P_RECEIVE }, + { "balance", TALER_KYCLOGIC_KYC_TRIGGER_WALLET_BALANCE }, { NULL, 0 } }; @@ -202,17 +201,17 @@ TEH_kyc_trigger_from_string (const char *trigger_s, const char * -TEH_kyc_trigger2s (enum TEH_KycTriggerEvent trigger) +TALER_KYCLOGIC_kyc_trigger2s (enum TALER_KYCLOGIC_KycTriggerEvent trigger) { switch (trigger) { - case TEH_KYC_TRIGGER_WITHDRAW: + case TALER_KYCLOGIC_KYC_TRIGGER_WITHDRAW: return "withdraw"; - case TEH_KYC_TRIGGER_DEPOSIT: + case TALER_KYCLOGIC_KYC_TRIGGER_DEPOSIT: return "deposit"; - case TEH_KYC_TRIGGER_P2P_RECEIVE: + case TALER_KYCLOGIC_KYC_TRIGGER_P2P_RECEIVE: return "merge"; - case TEH_KYC_TRIGGER_WALLET_BALANCE: + case TALER_KYCLOGIC_KYC_TRIGGER_WALLET_BALANCE: return "balance"; } GNUNET_break (0); @@ -221,16 +220,16 @@ TEH_kyc_trigger2s (enum TEH_KycTriggerEvent trigger) enum GNUNET_GenericReturnValue -TEH_kyc_user_type_from_string (const char *ut_s, - enum TEH_KycUserType *ut) +TALER_KYCLOGIC_kyc_user_type_from_string (const char *ut_s, + enum TALER_KYCLOGIC_KycUserType *ut) { struct { const char *in; - enum TEH_KycTriggerEvent out; + enum TALER_KYCLOGIC_KycTriggerEvent out; } map [] = { - { "individual", TEH_KYC_UT_INDIVIDUAL }, - { "business", TEH_KYC_UT_BUSINESS }, + { "individual", TALER_KYCLOGIC_KYC_UT_INDIVIDUAL }, + { "business", TALER_KYCLOGIC_KYC_UT_BUSINESS }, { NULL, 0 } }; @@ -249,13 +248,13 @@ TEH_kyc_user_type_from_string (const char *ut_s, const char * -TEH_kyc_user_type2s (enum TEH_KycUserType ut) +TALER_KYCLOGIC_kyc_user_type2s (enum TALER_KYCLOGIC_KycUserType ut) { switch (ut) { - case TEH_KYC_UT_INDIVIDUAL: + case TALER_KYCLOGIC_KYC_UT_INDIVIDUAL: return "individual"; - case TEH_KYC_UT_BUSINESS: + case TALER_KYCLOGIC_KYC_UT_BUSINESS: return "business"; } GNUNET_break (0); @@ -266,11 +265,13 @@ TEH_kyc_user_type2s (enum TEH_KycUserType ut) /** * Load KYC logic plugin. * + * @param cfg configuration to use * @param name name of the plugin * @return NULL on error */ static struct TALER_KYCLOGIC_Plugin * -load_logic (const char *name) +load_logic (const struct GNUNET_CONFIGURATION_Handle *cfg, + const char *name) { char *lib_name; struct TALER_KYCLOGIC_Plugin *plugin; @@ -279,7 +280,7 @@ load_logic (const char *name) "libtaler_plugin_kyclogic_%s", name); plugin = GNUNET_PLUGIN_load (lib_name, - (void *) TEH_cfg); + (void *) cfg); if (NULL != plugin) plugin->library_name = lib_name; else @@ -296,16 +297,16 @@ load_logic (const char *name) * @param check name of the check * @return pointer into the global list */ -static struct TEH_KycCheck * +static struct TALER_KYCLOGIC_KycCheck * add_check (const char *check) { - struct TEH_KycCheck *kc; + struct TALER_KYCLOGIC_KycCheck *kc; for (unsigned int i = 0; iname)) return kyc_checks[i]; - kc = GNUNET_new (struct TEH_KycCheck); + kc = GNUNET_new (struct TALER_KYCLOGIC_KycCheck); kc->name = GNUNET_strdup (check); GNUNET_array_append (kyc_checks, num_kyc_checks, @@ -325,18 +326,18 @@ add_check (const char *check) */ static void add_checks (char *checks, - struct TEH_KycCheck ***p_checks, + struct TALER_KYCLOGIC_KycCheck ***p_checks, unsigned int *num_p_checks) { char *sptr; - struct TEH_KycCheck **rchecks = NULL; + struct TALER_KYCLOGIC_KycCheck **rchecks = NULL; unsigned int num_rchecks = 0; for (char *tok = strtok_r (checks, " ", &sptr); NULL != tok; tok = strtok_r (NULL, " ", &sptr)) { - struct TEH_KycCheck *kc; + struct TALER_KYCLOGIC_KycCheck *kc; kc = add_check (tok); GNUNET_array_append (rchecks, @@ -351,20 +352,23 @@ add_checks (char *checks, /** * Parse configuration of a KYC provider. * + * @param cfg configuration to parse + * @param section name of the section to analyze * @return #GNUNET_OK on success */ static enum GNUNET_GenericReturnValue -add_provider (const char *section) +add_provider (const struct GNUNET_CONFIGURATION_Handle *cfg, + const char *section) { unsigned long long cost; char *logic; char *ut_s; - enum TEH_KycUserType ut; + enum TALER_KYCLOGIC_KycUserType ut; char *checks; struct TALER_KYCLOGIC_Plugin *lp; if (GNUNET_OK != - GNUNET_CONFIGURATION_get_value_number (TEH_cfg, + GNUNET_CONFIGURATION_get_value_number (cfg, section, "COST", &cost)) @@ -376,7 +380,7 @@ add_provider (const char *section) return GNUNET_SYSERR; } if (GNUNET_OK != - GNUNET_CONFIGURATION_get_value_string (TEH_cfg, + GNUNET_CONFIGURATION_get_value_string (cfg, section, "USER_TYPE", &ut_s)) @@ -387,8 +391,8 @@ add_provider (const char *section) return GNUNET_SYSERR; } if (GNUNET_OK != - TEH_kyc_user_type_from_string (ut_s, - &ut)) + TALER_KYCLOGIC_kyc_user_type_from_string (ut_s, + &ut)) { GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR, section, @@ -399,7 +403,7 @@ add_provider (const char *section) } GNUNET_free (ut_s); if (GNUNET_OK != - GNUNET_CONFIGURATION_get_value_string (TEH_cfg, + GNUNET_CONFIGURATION_get_value_string (cfg, section, "LOGIC", &logic)) @@ -409,7 +413,8 @@ add_provider (const char *section) "LOGIC"); return GNUNET_SYSERR; } - lp = load_logic (logic); + lp = load_logic (cfg, + logic); if (NULL == lp) { GNUNET_free (logic); @@ -421,7 +426,7 @@ add_provider (const char *section) } GNUNET_free (logic); if (GNUNET_OK != - GNUNET_CONFIGURATION_get_value_string (TEH_cfg, + GNUNET_CONFIGURATION_get_value_string (cfg, section, "PROVIDED_CHECKS", &checks)) @@ -432,9 +437,9 @@ add_provider (const char *section) return GNUNET_SYSERR; } { - struct TEH_KycProvider *kp; + struct TALER_KYCLOGIC_KycProvider *kp; - kp = GNUNET_new (struct TEH_KycProvider); + kp = GNUNET_new (struct TALER_KYCLOGIC_KycProvider); kp->provider_section_name = section; kp->user_type = ut; kp->logic = lp; @@ -455,7 +460,7 @@ add_provider (const char *section) kp); for (unsigned int i = 0; inum_checks; i++) { - struct TEH_KycCheck *kc = kp->provided_checks[i]; + struct TALER_KYCLOGIC_KycCheck *kc = kp->provided_checks[i]; GNUNET_array_append (kc->providers, kc->num_providers, @@ -467,16 +472,17 @@ add_provider (const char *section) static enum GNUNET_GenericReturnValue -add_trigger (const char *section) +add_trigger (const struct GNUNET_CONFIGURATION_Handle *cfg, + const char *section) { char *ot_s; struct TALER_Amount threshold; struct GNUNET_TIME_Relative timeframe; char *checks; - enum TEH_KycTriggerEvent ot; + enum TALER_KYCLOGIC_KycTriggerEvent ot; if (GNUNET_OK != - TALER_config_get_amount (TEH_cfg, + TALER_config_get_amount (cfg, section, "THRESHOLD", &threshold)) @@ -488,7 +494,7 @@ add_trigger (const char *section) return GNUNET_SYSERR; } if (GNUNET_OK != - GNUNET_CONFIGURATION_get_value_string (TEH_cfg, + GNUNET_CONFIGURATION_get_value_string (cfg, section, "OPERATION_TYPE", &ot_s)) @@ -499,8 +505,8 @@ add_trigger (const char *section) return GNUNET_SYSERR; } if (GNUNET_OK != - TEH_kyc_trigger_from_string (ot_s, - &ot)) + TALER_KYCLOGIC_kyc_trigger_from_string (ot_s, + &ot)) { GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR, section, @@ -512,12 +518,12 @@ add_trigger (const char *section) GNUNET_free (ot_s); if (GNUNET_OK != - GNUNET_CONFIGURATION_get_value_time (TEH_cfg, + GNUNET_CONFIGURATION_get_value_time (cfg, section, "TIMEFRAME", &timeframe)) { - if (TEH_KYC_TRIGGER_WALLET_BALANCE == ot) + if (TALER_KYCLOGIC_KYC_TRIGGER_WALLET_BALANCE == ot) { timeframe = GNUNET_TIME_UNIT_ZERO; } @@ -531,7 +537,7 @@ add_trigger (const char *section) } } if (GNUNET_OK != - GNUNET_CONFIGURATION_get_value_string (TEH_cfg, + GNUNET_CONFIGURATION_get_value_string (cfg, section, "REQUIRED_CHECKS", &checks)) @@ -543,9 +549,9 @@ add_trigger (const char *section) } { - struct TEH_KycTrigger *kt; + struct TALER_KYCLOGIC_KycTrigger *kt; - kt = GNUNET_new (struct TEH_KycTrigger); + kt = GNUNET_new (struct TALER_KYCLOGIC_KycTrigger); kt->timeframe = timeframe; kt->threshold = threshold; kt->trigger = ot; @@ -561,25 +567,43 @@ add_trigger (const char *section) } +/** + * Closure for #handle_section(). + */ +struct SectionContext +{ + /** + * Configuration to handle. + */ + const struct GNUNET_CONFIGURATION_Handle *cfg; + + /** + * Result to return, set to false on failures. + */ + bool result; +}; + + /** * Function to iterate over configuration sections. * - * @param cls closure, `boolean *`, set to false on failure + * @param cls a `struct SectionContext *` * @param section name of the section */ static void handle_section (void *cls, const char *section) { - bool *ok = cls; + struct SectionContext *sc = cls; if (0 == strncasecmp (section, "kyc-provider-", strlen ("kyc-provider-"))) { if (GNUNET_OK != - add_provider (section)) - *ok = false; + add_provider (sc->cfg, + section)) + sc->result = false; return; } if (0 == strncasecmp (section, @@ -587,8 +611,9 @@ handle_section (void *cls, strlen ("kyc-legitimization-"))) { if (GNUNET_OK != - add_trigger (section)) - *ok = false; + add_trigger (sc->cfg, + section)) + sc->result = false; return; } } @@ -606,8 +631,10 @@ static int sort_by_timeframe (const void *p1, const void *p2) { - struct TEH_KycTrigger **t1 = (struct TEH_KycTrigger **) p1; - struct TEH_KycTrigger **t2 = (struct TEH_KycTrigger **) p2; + struct TALER_KYCLOGIC_KycTrigger **t1 = (struct + TALER_KYCLOGIC_KycTrigger **) p1; + struct TALER_KYCLOGIC_KycTrigger **t2 = (struct + TALER_KYCLOGIC_KycTrigger **) p2; if (GNUNET_TIME_relative_cmp ((*t1)->timeframe, <, @@ -622,16 +649,19 @@ sort_by_timeframe (const void *p1, enum GNUNET_GenericReturnValue -TEH_kyc_init (void) +TALER_KYCLOGIC_kyc_init (const struct GNUNET_CONFIGURATION_Handle *cfg) { - bool ok = true; + struct SectionContext sc = { + .cfg = cfg, + .result = true + }; - GNUNET_CONFIGURATION_iterate_sections (TEH_cfg, + GNUNET_CONFIGURATION_iterate_sections (cfg, &handle_section, - &ok); - if (! ok) + &sc); + if (! sc.result) { - TEH_kyc_done (); + TALER_KYCLOGIC_kyc_done (); return GNUNET_SYSERR; } @@ -643,23 +673,23 @@ TEH_kyc_init (void) GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "No provider available for required KYC check `%s'\n", kyc_checks[i]->name); - TEH_kyc_done (); + TALER_KYCLOGIC_kyc_done (); return GNUNET_SYSERR; } qsort (kyc_triggers, num_kyc_triggers, - sizeof (struct TEH_KycTrigger *), + sizeof (struct TALER_KYCLOGIC_KycTrigger *), &sort_by_timeframe); return GNUNET_OK; } void -TEH_kyc_done (void) +TALER_KYCLOGIC_kyc_done (void) { for (unsigned int i = 0; irequired_checks, kt->num_checks, @@ -671,7 +701,7 @@ TEH_kyc_done (void) 0); for (unsigned int i = 0; ilogic->unload_configuration (kp->pd); GNUNET_array_grow (kp->provided_checks, @@ -696,7 +726,7 @@ TEH_kyc_done (void) 0); for (unsigned int i = 0; iname); GNUNET_free (kc); @@ -720,7 +750,7 @@ struct ThresholdTestContext /** * Trigger event to evaluate triggers of. */ - enum TEH_KycTriggerEvent event; + enum TALER_KYCLOGIC_KycTriggerEvent event; /** * Offset in the triggers array where we need to start @@ -732,13 +762,17 @@ struct ThresholdTestContext /** * Array of checks needed so far. */ - struct TEH_KycCheck **needed; + struct TALER_KYCLOGIC_KycCheck **needed; /** * Pointer to number of entries used in @a needed. */ unsigned int *needed_cnt; + /** + * Has @e total been initialized yet? + */ + bool have_total; }; @@ -764,17 +798,24 @@ eval_trigger (void *cls, bool bump = true; duration = GNUNET_TIME_absolute_get_duration (date); - if (0 > - TALER_amount_add (&ttc->total, - &ttc->total, - amount)) + if (ttc->have_total) { - GNUNET_break (0); - return GNUNET_SYSERR; + if (0 > + TALER_amount_add (&ttc->total, + &ttc->total, + amount)) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + } + else + { + ttc->total = *amount; } for (unsigned int i = ttc->start; ievent != kt->trigger) continue; @@ -801,7 +842,7 @@ eval_trigger (void *cls, already present... */ for (unsigned int j = 0; jnum_checks; j++) { - struct TEH_KycCheck *rc = kt->required_checks[j]; + struct TALER_KYCLOGIC_KycCheck *rc = kt->required_checks[j]; bool found = false; for (unsigned int k = 0; k<*ttc->needed_cnt; k++) @@ -832,7 +873,7 @@ struct RemoveContext /** * Array of checks needed so far. */ - struct TEH_KycCheck **needed; + struct TALER_KYCLOGIC_KycCheck **needed; /** * Pointer to number of entries used in @a needed. @@ -857,14 +898,14 @@ remove_satisfied (void *cls, for (unsigned int i = 0; iprovider_section_name)) continue; for (unsigned int j = 0; jnum_checks; j++) { - const struct TEH_KycCheck *kc = kp->provided_checks[j]; + const struct TALER_KYCLOGIC_KycCheck *kc = kp->provided_checks[j]; for (unsigned int k = 0; k<*rc->needed_cnt; k++) if (kc == rc->needed[k]) @@ -882,22 +923,24 @@ remove_satisfied (void *cls, const char * -TEH_kyc_test_required (enum TEH_KycTriggerEvent event, - const struct TALER_PaytoHashP *h_payto, - TEH_KycAmountIterator ai, - void *ai_cls) +TALER_KYCLOGIC_kyc_test_required (enum TALER_KYCLOGIC_KycTriggerEvent event, + const struct TALER_PaytoHashP *h_payto, + TALER_KYCLOGIC_KycSatisfiedIterator ki, + void *ki_cls, + TALER_KYCLOGIC_KycAmountIterator ai, + void *ai_cls) { - struct TEH_KycCheck *needed[num_kyc_checks]; + struct TALER_KYCLOGIC_KycCheck *needed[num_kyc_checks]; unsigned int needed_cnt = 0; struct GNUNET_TIME_Relative timeframe; unsigned long long min_cost = ULONG_LONG_MAX; unsigned int max_checks = 0; - const struct TEH_KycProvider *kp_best = NULL; + const struct TALER_KYCLOGIC_KycProvider *kp_best = NULL; timeframe = GNUNET_TIME_UNIT_ZERO; for (unsigned int i = 0; itrigger) continue; @@ -912,8 +955,6 @@ TEH_kyc_test_required (enum TEH_KycTriggerEvent event, .needed_cnt = &needed_cnt }; - TALER_amount_set_zero (TEH_currency, - &ttc.total); now = GNUNET_TIME_absolute_get (); ai (ai_cls, GNUNET_TIME_absolute_subtract (now, @@ -933,10 +974,12 @@ TEH_kyc_test_required (enum TEH_KycTriggerEvent event, /* Check what provider checks are already satisfied for h_payto (with database), remove those from the 'needed' array. */ GNUNET_break (0); - qs = TEH_plugin->select_satisfied_kyc_processes (TEH_plugin->cls, - h_payto, - &remove_satisfied, - &rc); + // FIXME: do via callback! + qs = ki (ki_cls, + h_payto, + & + remove_satisfied, + &rc); GNUNET_break (qs >= 0); // FIXME: handle DB failure more nicely? } @@ -944,12 +987,12 @@ TEH_kyc_test_required (enum TEH_KycTriggerEvent event, provider */ for (unsigned int i = 0; inum_checks; j++) { - const struct TEH_KycCheck *kc = kp->provided_checks[j]; + const struct TALER_KYCLOGIC_KycCheck *kc = kp->provided_checks[j]; for (unsigned int k = 0; knum_checks; j++) { - const struct TEH_KycCheck *kc = kp->provided_checks[j]; + const struct TALER_KYCLOGIC_KycCheck *kc = kp->provided_checks[j]; for (unsigned int k = 0; k @@ -220,6 +221,328 @@ r404 (struct MHD_Connection *connection, } +/** + * Context for the webhook. + */ +struct KycWebhookContext +{ + + /** + * Kept in a DLL while suspended. + */ + struct KycWebhookContext *next; + + /** + * Kept in a DLL while suspended. + */ + struct KycWebhookContext *prev; + + /** + * Details about the connection we are processing. + */ + struct TEKT_RequestContext *rc; + + /** + * Plugin responsible for the webhook. + */ + struct TALER_KYCLOGIC_Plugin *plugin; + + /** + * Configuration for the specific action. + */ + struct TALER_KYCLOGIC_ProviderDetails *pd; + + /** + * Webhook activity. + */ + struct TALER_KYCLOGIC_WebhookHandle *wh; + + /** + * HTTP response to return. + */ + struct MHD_Response *response; + + /** + * Logic the request is for. Name of the configuration + * section defining the KYC logic. + */ + char *logic; + + /** + * HTTP response code to return. + */ + unsigned int response_code; + + /** + * #GNUNET_YES if we are suspended, + * #GNUNET_NO if not. + * #GNUNET_SYSERR if we had some error. + */ + enum GNUNET_GenericReturnValue suspended; + +}; + + +/** + * Contexts are kept in a DLL while suspended. + */ +static struct KycWebhookContext *kwh_head; + +/** + * Contexts are kept in a DLL while suspended. + */ +static struct KycWebhookContext *kwh_tail; + + +/** + * Resume processing the @a kwh request. + * + * @param kwh request to resume + */ +static void +kwh_resume (struct KycWebhookContext *kwh) +{ + GNUNET_assert (GNUNET_YES == kwh->suspended); + kwh->suspended = GNUNET_NO; + GNUNET_CONTAINER_DLL_remove (kwh_head, + kwh_tail, + kwh); + MHD_resume_connection (kwh->rc->connection); + TALER_MHD_daemon_trigger (); +} + + +static void +kyc_webhook_cleanup (void) +{ + struct KycWebhookContext *kwh; + + while (NULL != (kwh = kwh_head)) + { + if (NULL != kwh->wh) + { + kwh->plugin->webhook_cancel (kwh->wh); + kwh->wh = NULL; + } + kwh_resume (kwh); + } +} + + +/** + * Function called with the result of a webhook + * operation. + * + * Note that the "decref" for the @a response + * will be done by the plugin. + * + * @param cls closure + * @param legi_row legitimization request the webhook was about + * @param account_id account the webhook was about + * @param provider_user_id set to user ID at the provider, or NULL if not supported or unknown + * @param provider_legitimization_id set to legitimization process ID at the provider, or NULL if not supported or unknown + * @param status KYC status + * @param expiration until when is the KYC check valid + * @param http_status HTTP status code of @a response + * @param[in] response to return to the HTTP client + */ +static void +webhook_finished_cb ( + void *cls, + uint64_t legi_row, + const struct TALER_PaytoHashP *account_id, + const char *provider_user_id, + const char *provider_legitimization_id, + enum TALER_KYCLOGIC_KycStatus status, + struct GNUNET_TIME_Absolute expiration, + unsigned int http_status, + struct MHD_Response *response) +{ + struct KycWebhookContext *kwh = cls; + + kwh->wh = NULL; + switch (status) + { + case TALER_KYCLOGIC_STATUS_SUCCESS: + /* _successfully_ resumed case */ + GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE, + "KYC successful for user `%s' (legi: %s)\n", + provider_user_id, + provider_legitimization_id); + break; + default: + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "KYC status of %s/%s (Row #%llu) is %d\n", + provider_user_id, + provider_legitimization_id, + (unsigned long long) legi_row, + status); + break; + } + kwh->response = response; + kwh->response_code = http_status; + kwh_resume (kwh); +} + + +/** + * Function called to clean up a context. + * + * @param rc request context + */ +static void +clean_kwh (struct TEKT_RequestContext *rc) +{ + struct KycWebhookContext *kwh = rc->rh_ctx; + + if (NULL != kwh->wh) + { + kwh->plugin->webhook_cancel (kwh->wh); + kwh->wh = NULL; + } + if (NULL != kwh->response) + { + MHD_destroy_response (kwh->response); + kwh->response = NULL; + } + GNUNET_free (kwh->logic); + GNUNET_free (kwh); +} + + +/** + * Function the plugin can use to lookup an + * @a h_payto by @a provider_legitimization_id. + * + * @param cls closure, NULL + * @param provider_section + * @param provider_legitimization_id legi to look up + * @param[out] h_payto where to write the result + * @return database transaction status + */ +static enum GNUNET_DB_QueryStatus +kyc_provider_account_lookup ( + void *cls, + const char *provider_section, + const char *provider_legitimization_id, + struct TALER_PaytoHashP *h_payto) +{ + // FIXME: pass value to use for h_payto via command-line? + memset (h_payto, + 42, + sizeof (*h_payto)); + return 1; // FIXME... +} + + +/** + * Handle a (GET or POST) "/kyc-webhook" request. + * + * @param rc request to handle + * @param method HTTP request method used by the client + * @param root uploaded JSON body (can be NULL) + * @param args one argument with the payment_target_uuid + * @return MHD result code + */ +static MHD_RESULT +handler_kyc_webhook_generic ( + struct TEKT_RequestContext *rc, + const char *method, + const json_t *root, + const char *const args[]) +{ + struct KycWebhookContext *kwh = rc->rh_ctx; + + if (NULL == kwh) + { /* first time */ + kwh = GNUNET_new (struct KycWebhookContext); + kwh->logic = GNUNET_strdup (args[0]); + kwh->rc = rc; + rc->rh_ctx = kwh; + rc->rh_cleaner = &clean_kwh; + + if (GNUNET_OK != + TALER_KYCLOGIC_kyc_get_logic (kwh->logic, + &kwh->plugin, + &kwh->pd)) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "KYC logic `%s' unknown (check KYC provider configuration)\n", + kwh->logic); + return TALER_MHD_reply_with_error (rc->connection, + MHD_HTTP_NOT_FOUND, + TALER_EC_EXCHANGE_KYC_WEBHOOK_LOGIC_UNKNOWN, + "$LOGIC"); + } + kwh->wh = kwh->plugin->webhook (kwh->plugin->cls, + kwh->pd, + &kyc_provider_account_lookup, + NULL, + method, + &args[1], + rc->connection, + root, + &webhook_finished_cb, + kwh); + if (NULL == kwh->wh) + { + GNUNET_break_op (0); + return TALER_MHD_reply_with_error (rc->connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE, + "failed to run webhook logic"); + } + kwh->suspended = GNUNET_YES; + GNUNET_CONTAINER_DLL_insert (kwh_head, + kwh_tail, + kwh); + MHD_suspend_connection (rc->connection); + return MHD_YES; + } + + if (NULL != kwh->response) + { + /* handle _failed_ resumed cases */ + return MHD_queue_response (rc->connection, + kwh->response_code, + kwh->response); + } + + /* We resumed, but got no response? This should + not happen. */ + GNUNET_break (0); + return TALER_MHD_reply_with_error (rc->connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE, + "resumed without response"); +} + + +static MHD_RESULT +handler_kyc_webhook_get ( + struct TEKT_RequestContext *rc, + const char *const args[]) +{ + return handler_kyc_webhook_generic (rc, + MHD_HTTP_METHOD_GET, + NULL, + args); +} + + +static MHD_RESULT +handler_kyc_webhook_post ( + struct TEKT_RequestContext *rc, + const json_t *root, + const char *const args[]) +{ + return handler_kyc_webhook_generic (rc, + MHD_HTTP_METHOD_POST, + root, + args); +} + + /** * Function called whenever MHD is done with a request. If the * request was a POST, we may have stored a `struct Buffer *` in the @@ -295,7 +618,6 @@ proceed_with_handler (struct TEKT_RequestContext *rc, size_t *upload_data_size) { const struct TEKT_RequestHandler *rh = rc->rh; - // FIXME: handle '-1 == rh->nargs'!!! const char *args[rh->nargs + 2]; size_t ulen = strlen (url) + 1; json_t *root = NULL; @@ -442,19 +764,21 @@ handle_mhd_request (void *cls, .handler.post = &TEKT_handler_kyc_wallet, .nargs = 0 }, +#endif { .url = "kyc-webhook", .method = MHD_HTTP_METHOD_POST, - .handler.post = &TEKT_handler_kyc_webhook_post, - .nargs = -1 + .handler.post = &handler_kyc_webhook_post, + .nargs = 128, + .nargs_is_upper_bound = true }, { .url = "kyc-webhook", .method = MHD_HTTP_METHOD_GET, - .handler.post = &TEKT_handler_kyc_webhook_get, - .nargs = -1 + .handler.get = &handler_kyc_webhook_get, + .nargs = 128, + .nargs_is_upper_bound = true }, -#endif /* mark end of list */ { .url = NULL @@ -670,6 +994,8 @@ do_shutdown (void *cls) struct MHD_Daemon *mhd; (void) cls; + kyc_webhook_cleanup (); + TALER_KYCLOGIC_kyc_done (); mhd = TALER_MHD_daemon_stop (); if (NULL != mhd) MHD_stop_daemon (mhd); @@ -708,11 +1034,18 @@ run (void *cls, (void ) cfgfile; TALER_MHD_setup (TALER_MHD_GO_NONE); TEKT_cfg = config; - + if (GNUNET_OK != + TALER_KYCLOGIC_kyc_init (config)) + { + global_ret = EXIT_NOTCONFIGURED; + GNUNET_SCHEDULER_shutdown (); + return; + } if (GNUNET_OK != exchange_serve_process_config ()) { global_ret = EXIT_NOTCONFIGURED; + TALER_KYCLOGIC_kyc_done (); GNUNET_SCHEDULER_shutdown (); return; }