From ff48ada7d5ba026a39c0e8ddefd54b80b813425b Mon Sep 17 00:00:00 2001 From: Christian Grothoff Date: Mon, 8 Aug 2022 15:22:45 +0200 Subject: move kyclogic into libtalerkyclogic --- .gitignore | 1 + src/exchange/Makefile.am | 2 +- src/exchange/taler-exchange-httpd_kyc-webhook.c | 8 +- src/exchange/taler-exchange-httpd_kyc.c | 1019 ---------------------- src/exchange/taler-exchange-httpd_kyc.h | 202 ----- src/include/Makefile.am | 1 + src/include/taler_kyclogic_lib.h | 226 +++++ src/kyclogic/Makefile.am | 12 + src/kyclogic/kyclogic_api.c | 1062 +++++++++++++++++++++++ src/kyclogic/taler-exchange-kyc-tester.c | 347 +++++++- 10 files changed, 1647 insertions(+), 1233 deletions(-) delete mode 100644 src/exchange/taler-exchange-httpd_kyc.c delete mode 100644 src/exchange/taler-exchange-httpd_kyc.h create mode 100644 src/include/taler_kyclogic_lib.h create mode 100644 src/kyclogic/kyclogic_api.c 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/exchange/taler-exchange-httpd_kyc.c b/src/exchange/taler-exchange-httpd_kyc.c deleted file mode 100644 index ea90c08a6..000000000 --- a/src/exchange/taler-exchange-httpd_kyc.c +++ /dev/null @@ -1,1019 +0,0 @@ -/* - This file is part of TALER - Copyright (C) 2022 Taler Systems SA - - TALER is free software; you can redistribute it and/or modify it under the - terms of the GNU Affero General Public License as published by the Free Software - Foundation; either version 3, or (at your option) any later version. - - TALER is distributed in the hope that it will be useful, but WITHOUT ANY - WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR - A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License along with - TALER; see the file COPYING. If not, see -*/ -/** - * @file taler-exchange-httpd_kyc.c - * @brief KYC API for the exchange - * @author Christian Grothoff - */ -#include "platform.h" -#include "taler-exchange-httpd.h" -#include "taler-exchange-httpd_kyc.h" -#include "taler_exchangedb_plugin.h" - -/** - * Information about a KYC provider. - */ -struct TEH_KycProvider; - - -/** - * Abstract representation of a KYC check. - */ -struct TEH_KycCheck -{ - /** - * Human-readable name given to the KYC check. - */ - char *name; - - /** - * Array of @e num_providers providers that offer this type of KYC check. - */ - struct TEH_KycProvider **providers; - - /** - * Length of the @e providers array. - */ - unsigned int num_providers; - -}; - - -struct TEH_KycProvider -{ - /** - * Name of the provider (configuration section name). - */ - const char *provider_section_name; - - /** - * Array of @e num_checks checks performed by this provider. - */ - struct TEH_KycCheck **provided_checks; - - /** - * Logic to run for this provider. - */ - struct TALER_KYCLOGIC_Plugin *logic; - - /** - * @e provider_section_name specific details to - * pass to the @e logic functions. - */ - struct TALER_KYCLOGIC_ProviderDetails *pd; - - /** - * Cost of running this provider's KYC. - */ - unsigned long long cost; - - /** - * Length of the @e checks array. - */ - unsigned int num_checks; - - /** - * Type of user this provider supports. - */ - enum TEH_KycUserType user_type; -}; - - -/** - * Condition that triggers a need to perform KYC. - */ -struct TEH_KycTrigger -{ - - /** - * Timeframe to consider for computing the amount - * to compare against the @e limit. Zero for the - * wallet balance trigger (as not applicable). - */ - struct GNUNET_TIME_Relative timeframe; - - /** - * Maximum amount that can be transacted until - * the rule triggers. - */ - struct TALER_Amount threshold; - - /** - * Array of @e num_checks checks to apply on this trigger. - */ - struct TEH_KycCheck **required_checks; - - /** - * Length of the @e checks array. - */ - unsigned int num_checks; - - /** - * What event is this trigger for? - */ - enum TEH_KycTriggerEvent trigger; - -}; - - -/** - * Array of @e num_kyc_logics KYC logic plugins we have loaded. - */ -static struct TALER_KYCLOGIC_Plugin **kyc_logics; - -/** - * Length of the #kyc_logics array. - */ -static unsigned int num_kyc_logics; - -/** - * Array of @e num_kyc_checks known types of - * KYC checks. - */ -static struct TEH_KycCheck **kyc_checks; - -/** - * Length of the #kyc_checks array. - */ -static unsigned int num_kyc_checks; - -/** - * Array of configured triggers. - */ -static struct TEH_KycTrigger **kyc_triggers; - -/** - * Length of the #kyc_triggers array. - */ -static unsigned int num_kyc_triggers; - -/** - * Array of configured providers. - */ -static struct TEH_KycProvider **kyc_providers; - -/** - * Length of the #kyc_providers array. - */ -static unsigned int num_kyc_providers; - - -enum GNUNET_GenericReturnValue -TEH_kyc_trigger_from_string (const char *trigger_s, - enum TEH_KycTriggerEvent *trigger) -{ - struct - { - const char *in; - enum TEH_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 }, - { NULL, 0 } - }; - - for (unsigned int i = 0; NULL != map[i].in; i++) - if (0 == strcasecmp (map[i].in, - trigger_s)) - { - *trigger = map[i].out; - return GNUNET_OK; - } - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Invalid KYC trigger `%s'\n", - trigger_s); - return GNUNET_SYSERR; -} - - -const char * -TEH_kyc_trigger2s (enum TEH_KycTriggerEvent trigger) -{ - switch (trigger) - { - case TEH_KYC_TRIGGER_WITHDRAW: - return "withdraw"; - case TEH_KYC_TRIGGER_DEPOSIT: - return "deposit"; - case TEH_KYC_TRIGGER_P2P_RECEIVE: - return "merge"; - case TEH_KYC_TRIGGER_WALLET_BALANCE: - return "balance"; - } - GNUNET_break (0); - return NULL; -} - - -enum GNUNET_GenericReturnValue -TEH_kyc_user_type_from_string (const char *ut_s, - enum TEH_KycUserType *ut) -{ - struct - { - const char *in; - enum TEH_KycTriggerEvent out; - } map [] = { - { "individual", TEH_KYC_UT_INDIVIDUAL }, - { "business", TEH_KYC_UT_BUSINESS }, - { NULL, 0 } - }; - - for (unsigned int i = 0; NULL != map[i].in; i++) - if (0 == strcasecmp (map[i].in, - ut_s)) - { - *ut = map[i].out; - return GNUNET_OK; - } - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Invalid user type `%s'\n", - ut_s); - return GNUNET_SYSERR; -} - - -const char * -TEH_kyc_user_type2s (enum TEH_KycUserType ut) -{ - switch (ut) - { - case TEH_KYC_UT_INDIVIDUAL: - return "individual"; - case TEH_KYC_UT_BUSINESS: - return "business"; - } - GNUNET_break (0); - return NULL; -} - - -/** - * Load KYC logic plugin. - * - * @param name name of the plugin - * @return NULL on error - */ -static struct TALER_KYCLOGIC_Plugin * -load_logic (const char *name) -{ - char *lib_name; - struct TALER_KYCLOGIC_Plugin *plugin; - - GNUNET_asprintf (&lib_name, - "libtaler_plugin_kyclogic_%s", - name); - plugin = GNUNET_PLUGIN_load (lib_name, - (void *) TEH_cfg); - if (NULL != plugin) - plugin->library_name = lib_name; - else - GNUNET_free (lib_name); - return plugin; -} - - -/** - * Add check type to global array of checks. - * First checks if the type already exists, otherwise - * adds a new one. - * - * @param check name of the check - * @return pointer into the global list - */ -static struct TEH_KycCheck * -add_check (const char *check) -{ - struct TEH_KycCheck *kc; - - for (unsigned int i = 0; iname)) - return kyc_checks[i]; - kc = GNUNET_new (struct TEH_KycCheck); - kc->name = GNUNET_strdup (check); - GNUNET_array_append (kyc_checks, - num_kyc_checks, - kc); - return kc; -} - - -/** - * Parse list of checks from @a checks and build an - * array of aliases into the global checks array - * in @a provided_checks. - * - * @param[in,out] checks list of checks; clobbered - * @param[out] p_checks where to put array of aliases - * @param[out] num_p_checks set to length of @a p_checks array - */ -static void -add_checks (char *checks, - struct TEH_KycCheck ***p_checks, - unsigned int *num_p_checks) -{ - char *sptr; - struct TEH_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; - - kc = add_check (tok); - GNUNET_array_append (rchecks, - num_rchecks, - kc); - } - *p_checks = rchecks; - *num_p_checks = num_rchecks; -} - - -/** - * Parse configuration of a KYC provider. - * - * @return #GNUNET_OK on success - */ -static enum GNUNET_GenericReturnValue -add_provider (const char *section) -{ - unsigned long long cost; - char *logic; - char *ut_s; - enum TEH_KycUserType ut; - char *checks; - struct TALER_KYCLOGIC_Plugin *lp; - - if (GNUNET_OK != - GNUNET_CONFIGURATION_get_value_number (TEH_cfg, - section, - "COST", - &cost)) - { - GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR, - section, - "COST", - "number required"); - return GNUNET_SYSERR; - } - if (GNUNET_OK != - GNUNET_CONFIGURATION_get_value_string (TEH_cfg, - section, - "USER_TYPE", - &ut_s)) - { - GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, - section, - "USER_TYPE"); - return GNUNET_SYSERR; - } - if (GNUNET_OK != - TEH_kyc_user_type_from_string (ut_s, - &ut)) - { - GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR, - section, - "USER_TYPE", - "valid user type required"); - GNUNET_free (ut_s); - return GNUNET_SYSERR; - } - GNUNET_free (ut_s); - if (GNUNET_OK != - GNUNET_CONFIGURATION_get_value_string (TEH_cfg, - section, - "LOGIC", - &logic)) - { - GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, - section, - "LOGIC"); - return GNUNET_SYSERR; - } - lp = load_logic (logic); - if (NULL == lp) - { - GNUNET_free (logic); - GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR, - section, - "LOGIC", - "logic plugin could not be loaded"); - return GNUNET_SYSERR; - } - GNUNET_free (logic); - if (GNUNET_OK != - GNUNET_CONFIGURATION_get_value_string (TEH_cfg, - section, - "PROVIDED_CHECKS", - &checks)) - { - GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, - section, - "PROVIDED_CHECKS"); - return GNUNET_SYSERR; - } - { - struct TEH_KycProvider *kp; - - kp = GNUNET_new (struct TEH_KycProvider); - kp->provider_section_name = section; - kp->user_type = ut; - kp->logic = lp; - kp->cost = cost; - add_checks (checks, - &kp->provided_checks, - &kp->num_checks); - GNUNET_free (checks); - kp->pd = lp->load_configuration (lp->cls, - section); - if (NULL == kp->pd) - { - GNUNET_free (kp); - return GNUNET_SYSERR; - } - GNUNET_array_append (kyc_providers, - num_kyc_providers, - kp); - for (unsigned int i = 0; inum_checks; i++) - { - struct TEH_KycCheck *kc = kp->provided_checks[i]; - - GNUNET_array_append (kc->providers, - kc->num_providers, - kp); - } - } - return GNUNET_OK; -} - - -static enum GNUNET_GenericReturnValue -add_trigger (const char *section) -{ - char *ot_s; - struct TALER_Amount threshold; - struct GNUNET_TIME_Relative timeframe; - char *checks; - enum TEH_KycTriggerEvent ot; - - if (GNUNET_OK != - TALER_config_get_amount (TEH_cfg, - section, - "THRESHOLD", - &threshold)) - { - GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR, - section, - "THRESHOLD", - "amount required"); - return GNUNET_SYSERR; - } - if (GNUNET_OK != - GNUNET_CONFIGURATION_get_value_string (TEH_cfg, - section, - "OPERATION_TYPE", - &ot_s)) - { - GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, - section, - "OPERATION_TYPE"); - return GNUNET_SYSERR; - } - if (GNUNET_OK != - TEH_kyc_trigger_from_string (ot_s, - &ot)) - { - GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR, - section, - "OPERATION_TYPE", - "valid trigger type required"); - GNUNET_free (ot_s); - return GNUNET_SYSERR; - } - GNUNET_free (ot_s); - - if (GNUNET_OK != - GNUNET_CONFIGURATION_get_value_time (TEH_cfg, - section, - "TIMEFRAME", - &timeframe)) - { - if (TEH_KYC_TRIGGER_WALLET_BALANCE == ot) - { - timeframe = GNUNET_TIME_UNIT_ZERO; - } - else - { - GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR, - section, - "TIMEFRAME", - "duration required"); - return GNUNET_SYSERR; - } - } - if (GNUNET_OK != - GNUNET_CONFIGURATION_get_value_string (TEH_cfg, - section, - "REQUIRED_CHECKS", - &checks)) - { - GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, - section, - "REQUIRED_CHECKS"); - return GNUNET_SYSERR; - } - - { - struct TEH_KycTrigger *kt; - - kt = GNUNET_new (struct TEH_KycTrigger); - kt->timeframe = timeframe; - kt->threshold = threshold; - kt->trigger = ot; - add_checks (checks, - &kt->required_checks, - &kt->num_checks); - GNUNET_free (checks); - GNUNET_array_append (kyc_triggers, - num_kyc_triggers, - kt); - } - return GNUNET_OK; -} - - -/** - * Function to iterate over configuration sections. - * - * @param cls closure, `boolean *`, set to false on failure - * @param section name of the section - */ -static void -handle_section (void *cls, - const char *section) -{ - bool *ok = cls; - - if (0 == strncasecmp (section, - "kyc-provider-", - strlen ("kyc-provider-"))) - { - if (GNUNET_OK != - add_provider (section)) - *ok = false; - return; - } - if (0 == strncasecmp (section, - "kyc-legitimization-", - strlen ("kyc-legitimization-"))) - { - if (GNUNET_OK != - add_trigger (section)) - *ok = false; - return; - } -} - - -/** - * Comparator for qsort. Compares two triggers - * by timeframe to sort triggers by time. - * - * @param p1 first trigger to compare - * @param p2 second trigger to compare - * @return -1 if p1 < p2, 0 if p1==p2, 1 if p1 > p2. - */ -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; - - if (GNUNET_TIME_relative_cmp ((*t1)->timeframe, - <, - (*t2)->timeframe)) - return -1; - if (GNUNET_TIME_relative_cmp ((*t1)->timeframe, - >, - (*t2)->timeframe)) - return 1; - return 0; -} - - -enum GNUNET_GenericReturnValue -TEH_kyc_init (void) -{ - bool ok = true; - - GNUNET_CONFIGURATION_iterate_sections (TEH_cfg, - &handle_section, - &ok); - if (! ok) - { - TEH_kyc_done (); - return GNUNET_SYSERR; - } - - /* sanity check: ensure at least one provider exists - for any trigger and indidivual or business. */ - for (unsigned int i = 0; inum_providers) - { - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "No provider available for required KYC check `%s'\n", - kyc_checks[i]->name); - TEH_kyc_done (); - return GNUNET_SYSERR; - } - qsort (kyc_triggers, - num_kyc_triggers, - sizeof (struct TEH_KycTrigger *), - &sort_by_timeframe); - return GNUNET_OK; -} - - -void -TEH_kyc_done (void) -{ - for (unsigned int i = 0; irequired_checks, - kt->num_checks, - 0); - GNUNET_free (kt); - } - GNUNET_array_grow (kyc_triggers, - num_kyc_triggers, - 0); - for (unsigned int i = 0; ilogic->unload_configuration (kp->pd); - GNUNET_array_grow (kp->provided_checks, - kp->num_checks, - 0); - GNUNET_free (kp); - } - GNUNET_array_grow (kyc_providers, - num_kyc_providers, - 0); - for (unsigned int i = 0; ilibrary_name; - - GNUNET_assert (NULL == GNUNET_PLUGIN_unload (lib_name, - lp)); - GNUNET_free (lib_name); - } - GNUNET_array_grow (kyc_logics, - num_kyc_logics, - 0); - for (unsigned int i = 0; iname); - GNUNET_free (kc); - } - GNUNET_array_grow (kyc_checks, - num_kyc_checks, - 0); -} - - -/** - * Closure for the #eval_trigger(). - */ -struct ThresholdTestContext -{ - /** - * Total amount so far. - */ - struct TALER_Amount total; - - /** - * Trigger event to evaluate triggers of. - */ - enum TEH_KycTriggerEvent event; - - /** - * Offset in the triggers array where we need to start - * checking for triggers. All trigges below this - * offset were already hit. - */ - unsigned int start; - - /** - * Array of checks needed so far. - */ - struct TEH_KycCheck **needed; - - /** - * Pointer to number of entries used in @a needed. - */ - unsigned int *needed_cnt; - -}; - - -/** - * Function called on each @a amount that was found to - * be relevant for a KYC check. - * - * @param cls closure to allow the KYC module to - * total up amounts and evaluate rules - * @param amount encountered transaction amount - * @param date when was the amount encountered - * @return #GNUNET_OK to continue to iterate, - * #GNUNET_NO to abort iteration - * #GNUNET_SYSERR on internal error (also abort itaration) - */ -static enum GNUNET_GenericReturnValue -eval_trigger (void *cls, - const struct TALER_Amount *amount, - struct GNUNET_TIME_Absolute date) -{ - struct ThresholdTestContext *ttc = cls; - struct GNUNET_TIME_Relative duration; - bool bump = true; - - duration = GNUNET_TIME_absolute_get_duration (date); - if (0 > - TALER_amount_add (&ttc->total, - &ttc->total, - amount)) - { - GNUNET_break (0); - return GNUNET_SYSERR; - } - for (unsigned int i = ttc->start; ievent != kt->trigger) - continue; - duration = GNUNET_TIME_relative_max (duration, - kt->timeframe); - if (GNUNET_TIME_relative_cmp (kt->timeframe, - >, - duration)) - { - if (bump) - ttc->start = i; - return GNUNET_OK; - } - if (-1 == - TALER_amount_cmp (&ttc->total, - &kt->threshold)) - { - if (bump) - ttc->start = i; - bump = false; - continue; /* amount too low to trigger */ - } - /* add check to list of required checks, unless - already present... */ - for (unsigned int j = 0; jnum_checks; j++) - { - struct TEH_KycCheck *rc = kt->required_checks[j]; - bool found = false; - - for (unsigned int k = 0; k<*ttc->needed_cnt; k++) - if (ttc->needed[k] == rc) - { - found = true; - break; - } - if (! found) - { - ttc->needed[*ttc->needed_cnt] = rc; - (*ttc->needed_cnt)++; - } - } - } - if (bump) - return GNUNET_NO; /* we hit all possible triggers! */ - return GNUNET_OK; -} - - -/** - * Closure for the #remove_satisfied(). - */ -struct RemoveContext -{ - - /** - * Array of checks needed so far. - */ - struct TEH_KycCheck **needed; - - /** - * Pointer to number of entries used in @a needed. - */ - unsigned int *needed_cnt; - -}; - - -/** - * Remove all checks satisfied by @a provider_name from - * our list of checks. - * - * @param cls a `struct RemoveContext` - * @param provider_name section name of provider that was already run previously - */ -static void -remove_satisfied (void *cls, - const char *provider_name) -{ - struct RemoveContext *rc = 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]; - - for (unsigned int k = 0; k<*rc->needed_cnt; k++) - if (kc == rc->needed[k]) - { - rc->needed[k] = rc->needed[*rc->needed_cnt - 1]; - (*rc->needed_cnt)--; - if (0 == *rc->needed_cnt) - return; /* for sure finished */ - break; - } - } - break; - } -} - - -const char * -TEH_kyc_test_required (enum TEH_KycTriggerEvent event, - const struct TALER_PaytoHashP *h_payto, - TEH_KycAmountIterator ai, - void *ai_cls) -{ - struct TEH_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; - - timeframe = GNUNET_TIME_UNIT_ZERO; - for (unsigned int i = 0; itrigger) - continue; - timeframe = GNUNET_TIME_relative_max (timeframe, - kt->timeframe); - } - { - struct GNUNET_TIME_Absolute now; - struct ThresholdTestContext ttc = { - .event = event, - .needed = needed, - .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, - timeframe), - &eval_trigger, - &ttc); - } - if (0 == needed_cnt) - return NULL; - { - struct RemoveContext rc = { - .needed = needed, - .needed_cnt = &needed_cnt - }; - enum GNUNET_DB_QueryStatus qs; - - /* 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); - GNUNET_break (qs >= 0); // FIXME: handle DB failure more nicely? - } - - /* Count maximum number of remaining checks covered by any - provider */ - for (unsigned int i = 0; inum_checks; j++) - { - const struct TEH_KycCheck *kc = kp->provided_checks[j]; - - for (unsigned int k = 0; knum_checks; j++) - { - const struct TEH_KycCheck *kc = kp->provided_checks[j]; - - for (unsigned int k = 0; kcost < min_cost) ) - { - min_cost = kp->cost; - kp_best = kp; - } - } - - GNUNET_assert (NULL != kp_best); - return kp_best->provider_section_name; -} - - -enum GNUNET_GenericReturnValue -TEH_kyc_get_logic (const char *provider_section_name, - struct TALER_KYCLOGIC_Plugin **plugin, - struct TALER_KYCLOGIC_ProviderDetails **pd) -{ - for (unsigned int i = 0; iprovider_section_name)) - continue; - *plugin = kp->logic; - *pd = kp->pd; - return GNUNET_OK; - } - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Provider `%s' unknown\n", - provider_section_name); - return GNUNET_SYSERR; -} - - -/* end of taler-exchange-httpd_kyc.c */ diff --git a/src/exchange/taler-exchange-httpd_kyc.h b/src/exchange/taler-exchange-httpd_kyc.h deleted file mode 100644 index 1df264c15..000000000 --- a/src/exchange/taler-exchange-httpd_kyc.h +++ /dev/null @@ -1,202 +0,0 @@ -/* - This file is part of TALER - Copyright (C) 2022 Taler Systems SA - - TALER is free software; you can redistribute it and/or modify it under the - terms of the GNU Affero General Public License as published by the Free Software - Foundation; either version 3, or (at your option) any later version. - - TALER is distributed in the hope that it will be useful, but WITHOUT ANY - WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR - A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License along with - TALER; see the file COPYING. If not, see -*/ -/** - * @file taler-exchange-httpd_kyc.h - * @brief KYC API for the exchange - * @author Christian Grothoff - */ -#ifndef TALER_EXCHANGE_HTTPD_KYC_H -#define TALER_EXCHANGE_HTTPD_KYC_H - -#include -#include "taler_exchangedb_plugin.h" -#include "taler_kyclogic_plugin.h" - - -/** - * Enumeration for our KYC user types. - */ -enum TEH_KycUserType -{ - /** - * KYC rule is for an individual. - */ - TEH_KYC_UT_INDIVIDUAL = 0, - - /** - * KYC rule is for a business. - */ - TEH_KYC_UT_BUSINESS = 1 -}; - - -/** - * Enumeration of possible events that may trigger - * KYC requirements. - */ -enum TEH_KycTriggerEvent -{ - - /** - * Customer withdraws coins. - */ - TEH_KYC_TRIGGER_WITHDRAW = 0, - - /** - * Merchant deposits coins. - */ - TEH_KYC_TRIGGER_DEPOSIT = 1, - - /** - * Wallet receives P2P payment. - */ - TEH_KYC_TRIGGER_P2P_RECEIVE = 2, - - /** - * Wallet balance exceeds threshold. - */ - TEH_KYC_TRIGGER_WALLET_BALANCE = 3 - -}; - - -/** - * Parse KYC trigger string value from a string - * into enumeration value. - * - * @param trigger_s string to parse - * @param[out] trigger set to the value found - * @return #GNUNET_OK on success, #GNUNET_NO if option - * does not exist, #GNUNET_SYSERR if option is - * malformed - */ -enum GNUNET_GenericReturnValue -TEH_kyc_trigger_from_string (const char *trigger_s, - enum TEH_KycTriggerEvent *trigger); - - -/** - * Convert KYC trigger value to human-readable string. - * - * @param trigger value to convert - * @return human-readable representation of the @a trigger - */ -const char * -TEH_kyc_trigger2s (enum TEH_KycTriggerEvent trigger); - - -/** - * Parse user type string into enumeration value. - * - * @param ut_s string to parse - * @param[out] ut set to the value found - * @return #GNUNET_OK on success, #GNUNET_NO if option - * does not exist, #GNUNET_SYSERR if option is - * malformed - */ -enum GNUNET_GenericReturnValue -TEH_kyc_user_type_from_string (const char *ut_s, - enum TEH_KycUserType *ut); - - -/** - * Convert KYC user type to human-readable string. - * - * @param ut value to convert - * @return human-readable representation of the @a ut - */ -const char * -TEH_kyc_user_type2s (enum TEH_KycUserType ut); - - -/** - * Initialize KYC subsystem. Loads the KYC - * configuration. - * - * @return #GNUNET_OK on success - */ -enum GNUNET_GenericReturnValue -TEH_kyc_init (void); - - -/** - * Shut down the KYC subsystem. - */ -void -TEH_kyc_done (void); - - -/** - * Function called to iterate over KYC-relevant - * transaction amounts for a particular time range. - * Called within a database transaction, so must - * not start a new one. - * - * @param cls closure, identifies the event type and - * account to iterate over events for - * @param limit maximum time-range for which events - * should be fetched (timestamp in the past) - * @param cb function to call on each event found, - * events must be returned in reverse chronological - * order - * @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); - - -/** - * Check if KYC is provided for a particular operation. Returns the best - * provider (configuration section name) that could perform the required - * check. - * - * Called within a database transaction, so must - * not start a new one. - * - * @param event what type of operation is triggering the - * test if KYC is required - * @param h_payto account the event is about - * @param ai callback offered to inquire about historic - * amounts involved in this type of operation - * at the given account - * @param ai_cls closure for @a ai - * @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); - - -/** - * Obtain the provider logic for a given @a provider_section_name. - * - * @param provider_section_name identifies a KYC provider process - * @param[out] plugin set to the KYC logic API - * @param[out] pd set to the specific operation context - * @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); - - -#endif 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/include/taler_kyclogic_lib.h b/src/include/taler_kyclogic_lib.h new file mode 100644 index 000000000..6b54276f6 --- /dev/null +++ b/src/include/taler_kyclogic_lib.h @@ -0,0 +1,226 @@ +/* + This file is part of TALER + Copyright (C) 2022 Taler Systems SA + + TALER is free software; you can redistribute it and/or modify it under the + terms of the GNU Affero General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License along with + TALER; see the file COPYING. If not, see +*/ +/** + * @file taler_kyclogic_lib.h + * @brief server-side KYC API + * @author Christian Grothoff + */ +#ifndef TALER_KYCLOGIC_LIB_H +#define TALER_KYCLOGIC_LIB_H + +#include +#include +#include + + +/** + * Enumeration for our KYC user types. + */ +enum TALER_KYCLOGIC_KycUserType +{ + /** + * KYC rule is for an individual. + */ + TALER_KYCLOGIC_KYC_UT_INDIVIDUAL = 0, + + /** + * KYC rule is for a business. + */ + TALER_KYCLOGIC_KYC_UT_BUSINESS = 1 +}; + + +/** + * Enumeration of possible events that may trigger + * KYC requirements. + */ +enum TALER_KYCLOGIC_KycTriggerEvent +{ + + /** + * Customer withdraws coins. + */ + TALER_KYCLOGIC_KYC_TRIGGER_WITHDRAW = 0, + + /** + * Merchant deposits coins. + */ + TALER_KYCLOGIC_KYC_TRIGGER_DEPOSIT = 1, + + /** + * Wallet receives P2P payment. + */ + TALER_KYCLOGIC_KYC_TRIGGER_P2P_RECEIVE = 2, + + /** + * Wallet balance exceeds threshold. + */ + TALER_KYCLOGIC_KYC_TRIGGER_WALLET_BALANCE = 3 + +}; + + +/** + * Parse KYC trigger string value from a string + * into enumeration value. + * + * @param trigger_s string to parse + * @param[out] trigger set to the value found + * @return #GNUNET_OK on success, #GNUNET_NO if option + * does not exist, #GNUNET_SYSERR if option is + * malformed + */ +enum GNUNET_GenericReturnValue +TALER_KYCLOGIC_kyc_trigger_from_string ( + const char *trigger_s, + enum TALER_KYCLOGIC_KycTriggerEvent *trigger); + + +/** + * Convert KYC trigger value to human-readable string. + * + * @param trigger value to convert + * @return human-readable representation of the @a trigger + */ +const char * +TALER_KYCLOGIC_kyc_trigger2s (enum TALER_KYCLOGIC_KycTriggerEvent trigger); + + +/** + * Parse user type string into enumeration value. + * + * @param ut_s string to parse + * @param[out] ut set to the value found + * @return #GNUNET_OK on success, #GNUNET_NO if option + * does not exist, #GNUNET_SYSERR if option is + * malformed + */ +enum GNUNET_GenericReturnValue +TALER_KYCLOGIC_kyc_user_type_from_string (const char *ut_s, + enum TALER_KYCLOGIC_KycUserType *ut); + + +/** + * Convert KYC user type to human-readable string. + * + * @param ut value to convert + * @return human-readable representation of the @a ut + */ +const char * +TALER_KYCLOGIC_kyc_user_type2s (enum TALER_KYCLOGIC_KycUserType ut); + + +/** + * Initialize KYC subsystem. Loads the KYC configuration. + * + * @param cfg configuration to parse + * @return #GNUNET_OK on success + */ +enum GNUNET_GenericReturnValue +TALER_KYCLOGIC_kyc_init (const struct GNUNET_CONFIGURATION_Handle *cfg); + + +/** + * Shut down the KYC subsystem. + */ +void +TALER_KYCLOGIC_kyc_done (void); + + +/** + * Function called to iterate over KYC-relevant + * transaction amounts for a particular time range. + * Called within a database transaction, so must + * not start a new one. + * + * @param cls closure, identifies the event type and + * account to iterate over events for + * @param limit maximum time-range for which events + * should be fetched (timestamp in the past) + * @param cb function to call on each event found, + * events must be returned in reverse chronological + * order + * @param cb_cls closure for @a cb + */ +typedef void +(*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); + + +/** + * Check if KYC is provided for a particular operation. Returns the best + * provider (configuration section name) that could perform the required + * check. + * + * Called within a database transaction, so must + * not start a new one. + * + * @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 + * @param ai_cls closure for @a ai + * @return NULL if no check is needed + */ +const char * +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); + + +/** + * Obtain the provider logic for a given @a provider_section_name. + * + * @param provider_section_name identifies a KYC provider process + * @param[out] plugin set to the KYC logic API + * @param[out] pd set to the specific operation context + * @return #GNUNET_OK on success + */ +enum GNUNET_GenericReturnValue +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/kyclogic/kyclogic_api.c b/src/kyclogic/kyclogic_api.c new file mode 100644 index 000000000..f2d31acf5 --- /dev/null +++ b/src/kyclogic/kyclogic_api.c @@ -0,0 +1,1062 @@ +/* + This file is part of TALER + Copyright (C) 2022 Taler Systems SA + + TALER is free software; you can redistribute it and/or modify it under the + terms of the GNU Affero General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License along with + TALER; see the file COPYING. If not, see +*/ +/** + * @file kyclogic_api.c + * @brief server-side KYC API + * @author Christian Grothoff + */ +#include "platform.h" +#include "taler_kyclogic_lib.h" + +/** + * Information about a KYC provider. + */ +struct TALER_KYCLOGIC_KycProvider; + + +/** + * Abstract representation of a KYC check. + */ +struct TALER_KYCLOGIC_KycCheck +{ + /** + * Human-readable name given to the KYC check. + */ + char *name; + + /** + * Array of @e num_providers providers that offer this type of KYC check. + */ + struct TALER_KYCLOGIC_KycProvider **providers; + + /** + * Length of the @e providers array. + */ + unsigned int num_providers; + +}; + + +struct TALER_KYCLOGIC_KycProvider +{ + /** + * Name of the provider (configuration section name). + */ + const char *provider_section_name; + + /** + * Array of @e num_checks checks performed by this provider. + */ + struct TALER_KYCLOGIC_KycCheck **provided_checks; + + /** + * Logic to run for this provider. + */ + struct TALER_KYCLOGIC_Plugin *logic; + + /** + * @e provider_section_name specific details to + * pass to the @e logic functions. + */ + struct TALER_KYCLOGIC_ProviderDetails *pd; + + /** + * Cost of running this provider's KYC. + */ + unsigned long long cost; + + /** + * Length of the @e checks array. + */ + unsigned int num_checks; + + /** + * Type of user this provider supports. + */ + enum TALER_KYCLOGIC_KycUserType user_type; +}; + + +/** + * Condition that triggers a need to perform KYC. + */ +struct TALER_KYCLOGIC_KycTrigger +{ + + /** + * Timeframe to consider for computing the amount + * to compare against the @e limit. Zero for the + * wallet balance trigger (as not applicable). + */ + struct GNUNET_TIME_Relative timeframe; + + /** + * Maximum amount that can be transacted until + * the rule triggers. + */ + struct TALER_Amount threshold; + + /** + * Array of @e num_checks checks to apply on this trigger. + */ + struct TALER_KYCLOGIC_KycCheck **required_checks; + + /** + * Length of the @e checks array. + */ + unsigned int num_checks; + + /** + * What event is this trigger for? + */ + enum TALER_KYCLOGIC_KycTriggerEvent trigger; + +}; + + +/** + * Array of @e num_kyc_logics KYC logic plugins we have loaded. + */ +static struct TALER_KYCLOGIC_Plugin **kyc_logics; + +/** + * Length of the #kyc_logics array. + */ +static unsigned int num_kyc_logics; + +/** + * Array of @e num_kyc_checks known types of + * KYC checks. + */ +static struct TALER_KYCLOGIC_KycCheck **kyc_checks; + +/** + * Length of the #kyc_checks array. + */ +static unsigned int num_kyc_checks; + +/** + * Array of configured triggers. + */ +static struct TALER_KYCLOGIC_KycTrigger **kyc_triggers; + +/** + * Length of the #kyc_triggers array. + */ +static unsigned int num_kyc_triggers; + +/** + * Array of configured providers. + */ +static struct TALER_KYCLOGIC_KycProvider **kyc_providers; + +/** + * Length of the #kyc_providers array. + */ +static unsigned int num_kyc_providers; + + +enum GNUNET_GenericReturnValue +TALER_KYCLOGIC_kyc_trigger_from_string (const char *trigger_s, + enum TALER_KYCLOGIC_KycTriggerEvent * + trigger) +{ + struct + { + const char *in; + enum TALER_KYCLOGIC_KycTriggerEvent out; + } map [] = { + { "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 } + }; + + for (unsigned int i = 0; NULL != map[i].in; i++) + if (0 == strcasecmp (map[i].in, + trigger_s)) + { + *trigger = map[i].out; + return GNUNET_OK; + } + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Invalid KYC trigger `%s'\n", + trigger_s); + return GNUNET_SYSERR; +} + + +const char * +TALER_KYCLOGIC_kyc_trigger2s (enum TALER_KYCLOGIC_KycTriggerEvent trigger) +{ + switch (trigger) + { + case TALER_KYCLOGIC_KYC_TRIGGER_WITHDRAW: + return "withdraw"; + case TALER_KYCLOGIC_KYC_TRIGGER_DEPOSIT: + return "deposit"; + case TALER_KYCLOGIC_KYC_TRIGGER_P2P_RECEIVE: + return "merge"; + case TALER_KYCLOGIC_KYC_TRIGGER_WALLET_BALANCE: + return "balance"; + } + GNUNET_break (0); + return NULL; +} + + +enum GNUNET_GenericReturnValue +TALER_KYCLOGIC_kyc_user_type_from_string (const char *ut_s, + enum TALER_KYCLOGIC_KycUserType *ut) +{ + struct + { + const char *in; + enum TALER_KYCLOGIC_KycTriggerEvent out; + } map [] = { + { "individual", TALER_KYCLOGIC_KYC_UT_INDIVIDUAL }, + { "business", TALER_KYCLOGIC_KYC_UT_BUSINESS }, + { NULL, 0 } + }; + + for (unsigned int i = 0; NULL != map[i].in; i++) + if (0 == strcasecmp (map[i].in, + ut_s)) + { + *ut = map[i].out; + return GNUNET_OK; + } + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Invalid user type `%s'\n", + ut_s); + return GNUNET_SYSERR; +} + + +const char * +TALER_KYCLOGIC_kyc_user_type2s (enum TALER_KYCLOGIC_KycUserType ut) +{ + switch (ut) + { + case TALER_KYCLOGIC_KYC_UT_INDIVIDUAL: + return "individual"; + case TALER_KYCLOGIC_KYC_UT_BUSINESS: + return "business"; + } + GNUNET_break (0); + return NULL; +} + + +/** + * 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 struct GNUNET_CONFIGURATION_Handle *cfg, + const char *name) +{ + char *lib_name; + struct TALER_KYCLOGIC_Plugin *plugin; + + GNUNET_asprintf (&lib_name, + "libtaler_plugin_kyclogic_%s", + name); + plugin = GNUNET_PLUGIN_load (lib_name, + (void *) cfg); + if (NULL != plugin) + plugin->library_name = lib_name; + else + GNUNET_free (lib_name); + return plugin; +} + + +/** + * Add check type to global array of checks. + * First checks if the type already exists, otherwise + * adds a new one. + * + * @param check name of the check + * @return pointer into the global list + */ +static struct TALER_KYCLOGIC_KycCheck * +add_check (const char *check) +{ + struct TALER_KYCLOGIC_KycCheck *kc; + + for (unsigned int i = 0; iname)) + return kyc_checks[i]; + kc = GNUNET_new (struct TALER_KYCLOGIC_KycCheck); + kc->name = GNUNET_strdup (check); + GNUNET_array_append (kyc_checks, + num_kyc_checks, + kc); + return kc; +} + + +/** + * Parse list of checks from @a checks and build an + * array of aliases into the global checks array + * in @a provided_checks. + * + * @param[in,out] checks list of checks; clobbered + * @param[out] p_checks where to put array of aliases + * @param[out] num_p_checks set to length of @a p_checks array + */ +static void +add_checks (char *checks, + struct TALER_KYCLOGIC_KycCheck ***p_checks, + unsigned int *num_p_checks) +{ + char *sptr; + 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 TALER_KYCLOGIC_KycCheck *kc; + + kc = add_check (tok); + GNUNET_array_append (rchecks, + num_rchecks, + kc); + } + *p_checks = rchecks; + *num_p_checks = num_rchecks; +} + + +/** + * 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 struct GNUNET_CONFIGURATION_Handle *cfg, + const char *section) +{ + unsigned long long cost; + char *logic; + char *ut_s; + enum TALER_KYCLOGIC_KycUserType ut; + char *checks; + struct TALER_KYCLOGIC_Plugin *lp; + + if (GNUNET_OK != + GNUNET_CONFIGURATION_get_value_number (cfg, + section, + "COST", + &cost)) + { + GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR, + section, + "COST", + "number required"); + return GNUNET_SYSERR; + } + if (GNUNET_OK != + GNUNET_CONFIGURATION_get_value_string (cfg, + section, + "USER_TYPE", + &ut_s)) + { + GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, + section, + "USER_TYPE"); + return GNUNET_SYSERR; + } + if (GNUNET_OK != + TALER_KYCLOGIC_kyc_user_type_from_string (ut_s, + &ut)) + { + GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR, + section, + "USER_TYPE", + "valid user type required"); + GNUNET_free (ut_s); + return GNUNET_SYSERR; + } + GNUNET_free (ut_s); + if (GNUNET_OK != + GNUNET_CONFIGURATION_get_value_string (cfg, + section, + "LOGIC", + &logic)) + { + GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, + section, + "LOGIC"); + return GNUNET_SYSERR; + } + lp = load_logic (cfg, + logic); + if (NULL == lp) + { + GNUNET_free (logic); + GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR, + section, + "LOGIC", + "logic plugin could not be loaded"); + return GNUNET_SYSERR; + } + GNUNET_free (logic); + if (GNUNET_OK != + GNUNET_CONFIGURATION_get_value_string (cfg, + section, + "PROVIDED_CHECKS", + &checks)) + { + GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, + section, + "PROVIDED_CHECKS"); + return GNUNET_SYSERR; + } + { + struct TALER_KYCLOGIC_KycProvider *kp; + + kp = GNUNET_new (struct TALER_KYCLOGIC_KycProvider); + kp->provider_section_name = section; + kp->user_type = ut; + kp->logic = lp; + kp->cost = cost; + add_checks (checks, + &kp->provided_checks, + &kp->num_checks); + GNUNET_free (checks); + kp->pd = lp->load_configuration (lp->cls, + section); + if (NULL == kp->pd) + { + GNUNET_free (kp); + return GNUNET_SYSERR; + } + GNUNET_array_append (kyc_providers, + num_kyc_providers, + kp); + for (unsigned int i = 0; inum_checks; i++) + { + struct TALER_KYCLOGIC_KycCheck *kc = kp->provided_checks[i]; + + GNUNET_array_append (kc->providers, + kc->num_providers, + kp); + } + } + return GNUNET_OK; +} + + +static enum GNUNET_GenericReturnValue +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 TALER_KYCLOGIC_KycTriggerEvent ot; + + if (GNUNET_OK != + TALER_config_get_amount (cfg, + section, + "THRESHOLD", + &threshold)) + { + GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR, + section, + "THRESHOLD", + "amount required"); + return GNUNET_SYSERR; + } + if (GNUNET_OK != + GNUNET_CONFIGURATION_get_value_string (cfg, + section, + "OPERATION_TYPE", + &ot_s)) + { + GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, + section, + "OPERATION_TYPE"); + return GNUNET_SYSERR; + } + if (GNUNET_OK != + TALER_KYCLOGIC_kyc_trigger_from_string (ot_s, + &ot)) + { + GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR, + section, + "OPERATION_TYPE", + "valid trigger type required"); + GNUNET_free (ot_s); + return GNUNET_SYSERR; + } + GNUNET_free (ot_s); + + if (GNUNET_OK != + GNUNET_CONFIGURATION_get_value_time (cfg, + section, + "TIMEFRAME", + &timeframe)) + { + if (TALER_KYCLOGIC_KYC_TRIGGER_WALLET_BALANCE == ot) + { + timeframe = GNUNET_TIME_UNIT_ZERO; + } + else + { + GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR, + section, + "TIMEFRAME", + "duration required"); + return GNUNET_SYSERR; + } + } + if (GNUNET_OK != + GNUNET_CONFIGURATION_get_value_string (cfg, + section, + "REQUIRED_CHECKS", + &checks)) + { + GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, + section, + "REQUIRED_CHECKS"); + return GNUNET_SYSERR; + } + + { + struct TALER_KYCLOGIC_KycTrigger *kt; + + kt = GNUNET_new (struct TALER_KYCLOGIC_KycTrigger); + kt->timeframe = timeframe; + kt->threshold = threshold; + kt->trigger = ot; + add_checks (checks, + &kt->required_checks, + &kt->num_checks); + GNUNET_free (checks); + GNUNET_array_append (kyc_triggers, + num_kyc_triggers, + kt); + } + return GNUNET_OK; +} + + +/** + * 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 a `struct SectionContext *` + * @param section name of the section + */ +static void +handle_section (void *cls, + const char *section) +{ + struct SectionContext *sc = cls; + + if (0 == strncasecmp (section, + "kyc-provider-", + strlen ("kyc-provider-"))) + { + if (GNUNET_OK != + add_provider (sc->cfg, + section)) + sc->result = false; + return; + } + if (0 == strncasecmp (section, + "kyc-legitimization-", + strlen ("kyc-legitimization-"))) + { + if (GNUNET_OK != + add_trigger (sc->cfg, + section)) + sc->result = false; + return; + } +} + + +/** + * Comparator for qsort. Compares two triggers + * by timeframe to sort triggers by time. + * + * @param p1 first trigger to compare + * @param p2 second trigger to compare + * @return -1 if p1 < p2, 0 if p1==p2, 1 if p1 > p2. + */ +static int +sort_by_timeframe (const void *p1, + const void *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, + <, + (*t2)->timeframe)) + return -1; + if (GNUNET_TIME_relative_cmp ((*t1)->timeframe, + >, + (*t2)->timeframe)) + return 1; + return 0; +} + + +enum GNUNET_GenericReturnValue +TALER_KYCLOGIC_kyc_init (const struct GNUNET_CONFIGURATION_Handle *cfg) +{ + struct SectionContext sc = { + .cfg = cfg, + .result = true + }; + + GNUNET_CONFIGURATION_iterate_sections (cfg, + &handle_section, + &sc); + if (! sc.result) + { + TALER_KYCLOGIC_kyc_done (); + return GNUNET_SYSERR; + } + + /* sanity check: ensure at least one provider exists + for any trigger and indidivual or business. */ + for (unsigned int i = 0; inum_providers) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "No provider available for required KYC check `%s'\n", + kyc_checks[i]->name); + TALER_KYCLOGIC_kyc_done (); + return GNUNET_SYSERR; + } + qsort (kyc_triggers, + num_kyc_triggers, + sizeof (struct TALER_KYCLOGIC_KycTrigger *), + &sort_by_timeframe); + return GNUNET_OK; +} + + +void +TALER_KYCLOGIC_kyc_done (void) +{ + for (unsigned int i = 0; irequired_checks, + kt->num_checks, + 0); + GNUNET_free (kt); + } + GNUNET_array_grow (kyc_triggers, + num_kyc_triggers, + 0); + for (unsigned int i = 0; ilogic->unload_configuration (kp->pd); + GNUNET_array_grow (kp->provided_checks, + kp->num_checks, + 0); + GNUNET_free (kp); + } + GNUNET_array_grow (kyc_providers, + num_kyc_providers, + 0); + for (unsigned int i = 0; ilibrary_name; + + GNUNET_assert (NULL == GNUNET_PLUGIN_unload (lib_name, + lp)); + GNUNET_free (lib_name); + } + GNUNET_array_grow (kyc_logics, + num_kyc_logics, + 0); + for (unsigned int i = 0; iname); + GNUNET_free (kc); + } + GNUNET_array_grow (kyc_checks, + num_kyc_checks, + 0); +} + + +/** + * Closure for the #eval_trigger(). + */ +struct ThresholdTestContext +{ + /** + * Total amount so far. + */ + struct TALER_Amount total; + + /** + * Trigger event to evaluate triggers of. + */ + enum TALER_KYCLOGIC_KycTriggerEvent event; + + /** + * Offset in the triggers array where we need to start + * checking for triggers. All trigges below this + * offset were already hit. + */ + unsigned int start; + + /** + * Array of checks needed so far. + */ + 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; +}; + + +/** + * Function called on each @a amount that was found to + * be relevant for a KYC check. + * + * @param cls closure to allow the KYC module to + * total up amounts and evaluate rules + * @param amount encountered transaction amount + * @param date when was the amount encountered + * @return #GNUNET_OK to continue to iterate, + * #GNUNET_NO to abort iteration + * #GNUNET_SYSERR on internal error (also abort itaration) + */ +static enum GNUNET_GenericReturnValue +eval_trigger (void *cls, + const struct TALER_Amount *amount, + struct GNUNET_TIME_Absolute date) +{ + struct ThresholdTestContext *ttc = cls; + struct GNUNET_TIME_Relative duration; + bool bump = true; + + duration = GNUNET_TIME_absolute_get_duration (date); + if (ttc->have_total) + { + 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; + duration = GNUNET_TIME_relative_max (duration, + kt->timeframe); + if (GNUNET_TIME_relative_cmp (kt->timeframe, + >, + duration)) + { + if (bump) + ttc->start = i; + return GNUNET_OK; + } + if (-1 == + TALER_amount_cmp (&ttc->total, + &kt->threshold)) + { + if (bump) + ttc->start = i; + bump = false; + continue; /* amount too low to trigger */ + } + /* add check to list of required checks, unless + already present... */ + for (unsigned int j = 0; jnum_checks; j++) + { + struct TALER_KYCLOGIC_KycCheck *rc = kt->required_checks[j]; + bool found = false; + + for (unsigned int k = 0; k<*ttc->needed_cnt; k++) + if (ttc->needed[k] == rc) + { + found = true; + break; + } + if (! found) + { + ttc->needed[*ttc->needed_cnt] = rc; + (*ttc->needed_cnt)++; + } + } + } + if (bump) + return GNUNET_NO; /* we hit all possible triggers! */ + return GNUNET_OK; +} + + +/** + * Closure for the #remove_satisfied(). + */ +struct RemoveContext +{ + + /** + * Array of checks needed so far. + */ + struct TALER_KYCLOGIC_KycCheck **needed; + + /** + * Pointer to number of entries used in @a needed. + */ + unsigned int *needed_cnt; + +}; + + +/** + * Remove all checks satisfied by @a provider_name from + * our list of checks. + * + * @param cls a `struct RemoveContext` + * @param provider_name section name of provider that was already run previously + */ +static void +remove_satisfied (void *cls, + const char *provider_name) +{ + struct RemoveContext *rc = cls; + + for (unsigned int i = 0; iprovider_section_name)) + continue; + for (unsigned int j = 0; jnum_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]) + { + rc->needed[k] = rc->needed[*rc->needed_cnt - 1]; + (*rc->needed_cnt)--; + if (0 == *rc->needed_cnt) + return; /* for sure finished */ + break; + } + } + break; + } +} + + +const char * +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 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 TALER_KYCLOGIC_KycProvider *kp_best = NULL; + + timeframe = GNUNET_TIME_UNIT_ZERO; + for (unsigned int i = 0; itrigger) + continue; + timeframe = GNUNET_TIME_relative_max (timeframe, + kt->timeframe); + } + { + struct GNUNET_TIME_Absolute now; + struct ThresholdTestContext ttc = { + .event = event, + .needed = needed, + .needed_cnt = &needed_cnt + }; + + now = GNUNET_TIME_absolute_get (); + ai (ai_cls, + GNUNET_TIME_absolute_subtract (now, + timeframe), + &eval_trigger, + &ttc); + } + if (0 == needed_cnt) + return NULL; + { + struct RemoveContext rc = { + .needed = needed, + .needed_cnt = &needed_cnt + }; + enum GNUNET_DB_QueryStatus qs; + + /* Check what provider checks are already satisfied for h_payto (with + database), remove those from the 'needed' array. */ + GNUNET_break (0); + // FIXME: do via callback! + qs = ki (ki_cls, + h_payto, + & + remove_satisfied, + &rc); + GNUNET_break (qs >= 0); // FIXME: handle DB failure more nicely? + } + + /* Count maximum number of remaining checks covered by any + provider */ + for (unsigned int i = 0; inum_checks; j++) + { + const struct TALER_KYCLOGIC_KycCheck *kc = kp->provided_checks[j]; + + for (unsigned int k = 0; knum_checks; j++) + { + const struct TALER_KYCLOGIC_KycCheck *kc = kp->provided_checks[j]; + + for (unsigned int k = 0; kcost < min_cost) ) + { + min_cost = kp->cost; + kp_best = kp; + } + } + + GNUNET_assert (NULL != kp_best); + return kp_best->provider_section_name; +} + + +enum GNUNET_GenericReturnValue +TALER_KYCLOGIC_kyc_get_logic (const char *provider_section_name, + struct TALER_KYCLOGIC_Plugin **plugin, + struct TALER_KYCLOGIC_ProviderDetails **pd) +{ + for (unsigned int i = 0; iprovider_section_name)) + continue; + *plugin = kp->logic; + *pd = kp->pd; + return GNUNET_OK; + } + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Provider `%s' unknown\n", + provider_section_name); + return GNUNET_SYSERR; +} + + +/* end of taler-exchange-httpd_kyc.c */ diff --git a/src/kyclogic/taler-exchange-kyc-tester.c b/src/kyclogic/taler-exchange-kyc-tester.c index 24809a82d..1fb0afa47 100644 --- a/src/kyclogic/taler-exchange-kyc-tester.c +++ b/src/kyclogic/taler-exchange-kyc-tester.c @@ -28,6 +28,7 @@ #include "taler_mhd_lib.h" #include "taler_json_lib.h" #include "taler_crypto_lib.h" +#include "taler_kyclogic_lib.h" #include "taler_kyclogic_plugin.h" #include @@ -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; } -- cgit v1.2.3