diff options
Diffstat (limited to 'src/kyclogic/kyclogic_api.c')
-rw-r--r-- | src/kyclogic/kyclogic_api.c | 435 |
1 files changed, 405 insertions, 30 deletions
diff --git a/src/kyclogic/kyclogic_api.c b/src/kyclogic/kyclogic_api.c index 18707a18f..186799dbb 100644 --- a/src/kyclogic/kyclogic_api.c +++ b/src/kyclogic/kyclogic_api.c @@ -1,6 +1,6 @@ /* This file is part of TALER - Copyright (C) 2022 Taler Systems SA + Copyright (C) 2022-2023 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 @@ -22,6 +22,12 @@ #include "taler_kyclogic_lib.h" /** + * Name of the KYC check that may never be passed. Useful if some + * operations/amounts are categorically forbidden. + */ +#define KYC_CHECK_IMPOSSIBLE "impossible" + +/** * Information about a KYC provider. */ struct TALER_KYCLOGIC_KycProvider; @@ -180,9 +186,11 @@ TALER_KYCLOGIC_kyc_trigger_from_string (const char *trigger_s, enum TALER_KYCLOGIC_KycTriggerEvent out; } map [] = { { "withdraw", TALER_KYCLOGIC_KYC_TRIGGER_WITHDRAW }, + { "age-withdraw", TALER_KYCLOGIC_KYC_TRIGGER_AGE_WITHDRAW }, { "deposit", TALER_KYCLOGIC_KYC_TRIGGER_DEPOSIT }, { "merge", TALER_KYCLOGIC_KYC_TRIGGER_P2P_RECEIVE }, { "balance", TALER_KYCLOGIC_KYC_TRIGGER_WALLET_BALANCE }, + { "close", TALER_KYCLOGIC_KYC_TRIGGER_RESERVE_CLOSE }, { NULL, 0 } }; @@ -207,12 +215,16 @@ TALER_KYCLOGIC_kyc_trigger2s (enum TALER_KYCLOGIC_KycTriggerEvent trigger) { case TALER_KYCLOGIC_KYC_TRIGGER_WITHDRAW: return "withdraw"; + case TALER_KYCLOGIC_KYC_TRIGGER_AGE_WITHDRAW: + return "age-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"; + case TALER_KYCLOGIC_KYC_TRIGGER_RESERVE_CLOSE: + return "close"; } GNUNET_break (0); return NULL; @@ -226,7 +238,7 @@ TALER_KYCLOGIC_kyc_user_type_from_string (const char *ut_s, struct { const char *in; - enum TALER_KYCLOGIC_KycTriggerEvent out; + enum TALER_KYCLOGIC_KycUserType out; } map [] = { { "individual", TALER_KYCLOGIC_KYC_UT_INDIVIDUAL }, { "business", TALER_KYCLOGIC_KYC_UT_BUSINESS }, @@ -262,6 +274,38 @@ TALER_KYCLOGIC_kyc_user_type2s (enum TALER_KYCLOGIC_KycUserType ut) } +enum GNUNET_GenericReturnValue +TALER_KYCLOGIC_check_satisfiable ( + const char *check_name) +{ + for (unsigned int i = 0; i<num_kyc_checks; i++) + if (0 == strcmp (check_name, + kyc_checks[i]->name)) + return GNUNET_OK; + if (0 == strcmp (check_name, + KYC_CHECK_IMPOSSIBLE)) + return GNUNET_NO; + return GNUNET_SYSERR; +} + + +json_t * +TALER_KYCLOGIC_get_satisfiable () +{ + json_t *requirements; + + requirements = json_array (); + GNUNET_assert (NULL != requirements); + for (unsigned int i = 0; i<num_kyc_checks; i++) + GNUNET_assert ( + 0 == + json_array_append_new ( + requirements, + json_string (kyc_checks[i]->name))); + return requirements; +} + + /** * Load KYC logic plugin. * @@ -294,6 +338,7 @@ load_logic (const struct GNUNET_CONFIGURATION_Handle *cfg, return NULL; } plugin->library_name = lib_name; + plugin->name = GNUNET_strdup (name); GNUNET_array_append (kyc_logics, num_kyc_logics, plugin); @@ -302,9 +347,8 @@ load_logic (const struct GNUNET_CONFIGURATION_Handle *cfg, /** - * Add check type to global array of checks. - * First checks if the type already exists, otherwise - * adds a new one. + * 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 @@ -328,9 +372,8 @@ add_check (const char *check) /** - * Parse list of checks from @a checks and build an - * array of aliases into the global checks array - * in @a provided_checks. + * 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 @@ -582,6 +625,29 @@ add_trigger (const struct GNUNET_CONFIGURATION_Handle *cfg, GNUNET_array_append (kyc_triggers, num_kyc_triggers, kt); + for (unsigned int i = 0; i<kt->num_checks; i++) + { + const struct TALER_KYCLOGIC_KycCheck *ck = kt->required_checks[i]; + + if (0 != ck->num_providers) + continue; + if (0 == strcmp (ck->name, + KYC_CHECK_IMPOSSIBLE)) + continue; + { + char *msg; + + GNUNET_asprintf (&msg, + "Required check `%s' cannot be satisfied: not provided by any provider", + ck->name); + GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR, + section, + "REQUIRED_CHECKS", + msg); + GNUNET_free (msg); + } + return GNUNET_SYSERR; + } } return GNUNET_OK; } @@ -611,8 +677,8 @@ struct SectionContext * @param section name of the section */ static void -handle_section (void *cls, - const char *section) +handle_provider_section (void *cls, + const char *section) { struct SectionContext *sc = cls; @@ -626,6 +692,21 @@ handle_section (void *cls, sc->result = false; return; } +} + + +/** + * Function to iterate over configuration sections. + * + * @param cls a `struct SectionContext *` + * @param section name of the section + */ +static void +handle_trigger_section (void *cls, + const char *section) +{ + struct SectionContext *sc = cls; + if (0 == strncasecmp (section, "kyc-legitimization-", strlen ("kyc-legitimization-"))) @@ -677,7 +758,10 @@ TALER_KYCLOGIC_kyc_init (const struct GNUNET_CONFIGURATION_Handle *cfg) }; GNUNET_CONFIGURATION_iterate_sections (cfg, - &handle_section, + &handle_provider_section, + &sc); + GNUNET_CONFIGURATION_iterate_sections (cfg, + &handle_trigger_section, &sc); if (! sc.result) { @@ -696,10 +780,11 @@ TALER_KYCLOGIC_kyc_init (const struct GNUNET_CONFIGURATION_Handle *cfg) TALER_KYCLOGIC_kyc_done (); return GNUNET_SYSERR; } - qsort (kyc_triggers, - num_kyc_triggers, - sizeof (struct TALER_KYCLOGIC_KycTrigger *), - &sort_by_timeframe); + if (0 != num_kyc_triggers) + qsort (kyc_triggers, + num_kyc_triggers, + sizeof (struct TALER_KYCLOGIC_KycTrigger *), + &sort_by_timeframe); return GNUNET_OK; } @@ -737,6 +822,7 @@ TALER_KYCLOGIC_kyc_done (void) struct TALER_KYCLOGIC_Plugin *lp = kyc_logics[i]; char *lib_name = lp->library_name; + GNUNET_free (lp->name); GNUNET_assert (NULL == GNUNET_PLUGIN_unload (lib_name, lp)); GNUNET_free (lib_name); @@ -929,6 +1015,10 @@ struct RemoveContext */ unsigned int *needed_cnt; + /** + * Object with information about collected KYC data. + */ + json_t *kyc_details; }; @@ -962,6 +1052,14 @@ remove_satisfied (void *cls, GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Provider satisfies check `%s'\n", kc->name); + if (NULL != rc->kyc_details) + { + GNUNET_assert (0 == + json_object_set_new ( + rc->kyc_details, + kc->name, + json_object ())); + } for (unsigned int k = 0; k<*rc->needed_cnt; k++) if (kc == rc->needed[k]) { @@ -980,20 +1078,19 @@ remove_satisfied (void *cls, } -const char * +enum GNUNET_DB_QueryStatus 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) + void *ai_cls, + char **required) { struct TALER_KYCLOGIC_KycCheck *needed[num_kyc_checks]; unsigned int needed_cnt = 0; + char *ret; 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; i<num_kyc_triggers; i++) @@ -1021,7 +1118,64 @@ TALER_KYCLOGIC_kyc_test_required (enum TALER_KYCLOGIC_KycTriggerEvent event, &ttc); } if (0 == needed_cnt) - return NULL; + { + *required = NULL; + return GNUNET_DB_STATUS_SUCCESS_NO_RESULTS; + } + timeframe = GNUNET_TIME_UNIT_ZERO; + for (unsigned int i = 0; i<num_kyc_triggers; i++) + { + const struct TALER_KYCLOGIC_KycTrigger *kt = kyc_triggers[i]; + + if (event != kt->trigger) + 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) + { + *required = NULL; + return GNUNET_DB_STATUS_SUCCESS_NO_RESULTS; + } + { + 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. */ + qs = ki (ki_cls, + h_payto, + &remove_satisfied, + &rc); + if (qs < 0) + { + GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); + return qs; + } + } + if (0 == needed_cnt) + { + *required = NULL; + return GNUNET_DB_STATUS_SUCCESS_NO_RESULTS; + } { struct RemoveContext rc = { .needed = needed, @@ -1035,10 +1189,179 @@ TALER_KYCLOGIC_kyc_test_required (enum TALER_KYCLOGIC_KycTriggerEvent event, h_payto, &remove_satisfied, &rc); - GNUNET_break (qs >= 0); // FIXME: handle DB failure more nicely? + if (qs < 0) + { + GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); + return qs; + } } if (0 == needed_cnt) - return NULL; + { + *required = NULL; + return GNUNET_DB_STATUS_SUCCESS_NO_RESULTS; + } + ret = NULL; + for (unsigned int k = 0; k<needed_cnt; k++) + { + const struct TALER_KYCLOGIC_KycCheck *kc = needed[k]; + + if (NULL == ret) + { + ret = GNUNET_strdup (kc->name); + } + else /* append */ + { + char *tmp = ret; + + GNUNET_asprintf (&ret, + "%s %s", + tmp, + kc->name); + GNUNET_free (tmp); + } + } + *required = ret; + return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT; +} + + +void +TALER_KYCLOGIC_kyc_get_details ( + const char *logic_name, + TALER_KYCLOGIC_DetailsCallback cb, + void *cb_cls) +{ + for (unsigned int i = 0; i<num_kyc_providers; i++) + { + struct TALER_KYCLOGIC_KycProvider *kp = kyc_providers[i]; + + if (0 != + strcmp (kp->logic->name, + logic_name)) + continue; + if (GNUNET_OK != + cb (cb_cls, + kp->pd, + kp->logic->cls)) + return; + } +} + + +enum GNUNET_DB_QueryStatus +TALER_KYCLOGIC_check_satisfied (char **requirements, + const struct TALER_PaytoHashP *h_payto, + json_t **kyc_details, + TALER_KYCLOGIC_KycSatisfiedIterator ki, + void *ki_cls, + bool *satisfied) +{ + struct TALER_KYCLOGIC_KycCheck *needed[num_kyc_checks]; + unsigned int needed_cnt = 0; + + if (NULL == requirements) + { + *satisfied = true; + return GNUNET_DB_STATUS_SUCCESS_NO_RESULTS; + } + { + char *req = *requirements; + + for (const char *tok = strtok (req, " "); + NULL != tok; + tok = strtok (NULL, " ")) + needed[needed_cnt++] = add_check (tok); + GNUNET_free (req); + *requirements = NULL; + } + + { + struct RemoveContext rc = { + .needed = needed, + .needed_cnt = &needed_cnt, + }; + enum GNUNET_DB_QueryStatus qs; + + rc.kyc_details = json_object (); + GNUNET_assert (NULL != rc.kyc_details); + + /* Check what provider checks are already satisfied for h_payto (with + database), remove those from the 'needed' array. */ + qs = ki (ki_cls, + h_payto, + &remove_satisfied, + &rc); + if (qs < 0) + { + GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); + *satisfied = false; + return qs; + } + if (0 != needed_cnt) + { + json_decref (rc.kyc_details); + *kyc_details = NULL; + } + else + { + *kyc_details = rc.kyc_details; + } + } + *satisfied = (0 == needed_cnt); + + { + char *res = NULL; + + for (unsigned int i = 0; i<needed_cnt; i++) + { + const struct TALER_KYCLOGIC_KycCheck *need = needed[i]; + + if (NULL == res) + { + res = GNUNET_strdup (need->name); + } + else + { + char *tmp; + + GNUNET_asprintf (&tmp, + "%s %s", + res, + need->name); + GNUNET_free (res); + res = tmp; + } + } + *requirements = res; + } + return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT; +} + + +enum GNUNET_GenericReturnValue +TALER_KYCLOGIC_requirements_to_logic (const char *requirements, + enum TALER_KYCLOGIC_KycUserType ut, + struct TALER_KYCLOGIC_Plugin **plugin, + struct TALER_KYCLOGIC_ProviderDetails **pd, + const char **configuration_section) +{ + struct TALER_KYCLOGIC_KycCheck *needed[num_kyc_checks]; + unsigned int needed_cnt = 0; + unsigned long long min_cost = ULLONG_MAX; + unsigned int max_checks = 0; + const struct TALER_KYCLOGIC_KycProvider *kp_best = NULL; + + if (NULL == requirements) + return GNUNET_NO; + { + char *req = GNUNET_strdup (requirements); + + for (const char *tok = strtok (req, " "); + NULL != tok; + tok = strtok (NULL, " ")) + needed[needed_cnt++] = add_check (tok); + GNUNET_free (req); + } /* Count maximum number of remaining checks covered by any provider */ @@ -1047,6 +1370,8 @@ TALER_KYCLOGIC_kyc_test_required (enum TALER_KYCLOGIC_KycTriggerEvent event, const struct TALER_KYCLOGIC_KycProvider *kp = kyc_providers[i]; unsigned int matched = 0; + if (kp->user_type != ut) + continue; for (unsigned int j = 0; j<kp->num_checks; j++) { const struct TALER_KYCLOGIC_KycCheck *kc = kp->provided_checks[j]; @@ -1061,6 +1386,8 @@ TALER_KYCLOGIC_kyc_test_required (enum TALER_KYCLOGIC_KycTriggerEvent event, max_checks = GNUNET_MAX (max_checks, matched); } + if (0 == max_checks) + return GNUNET_SYSERR; /* Find min-cost provider covering max_checks. */ for (unsigned int i = 0; i<num_kyc_providers; i++) @@ -1068,6 +1395,8 @@ TALER_KYCLOGIC_kyc_test_required (enum TALER_KYCLOGIC_KycTriggerEvent event, const struct TALER_KYCLOGIC_KycProvider *kp = kyc_providers[i]; unsigned int matched = 0; + if (kp->user_type != ut) + continue; for (unsigned int j = 0; j<kp->num_checks; j++) { const struct TALER_KYCLOGIC_KycCheck *kc = kp->provided_checks[j]; @@ -1086,32 +1415,49 @@ TALER_KYCLOGIC_kyc_test_required (enum TALER_KYCLOGIC_KycTriggerEvent event, kp_best = kp; } } - GNUNET_assert (NULL != kp_best); - return kp_best->provider_section_name; + *plugin = kp_best->logic; + *pd = kp_best->pd; + *configuration_section = kp_best->provider_section_name; + return GNUNET_OK; } enum GNUNET_GenericReturnValue -TALER_KYCLOGIC_kyc_get_logic (const char *provider_section_name, - struct TALER_KYCLOGIC_Plugin **plugin, - struct TALER_KYCLOGIC_ProviderDetails **pd) +TALER_KYCLOGIC_lookup_logic (const char *name, + struct TALER_KYCLOGIC_Plugin **plugin, + struct TALER_KYCLOGIC_ProviderDetails **pd, + const char **provider_section) { for (unsigned int i = 0; i<num_kyc_providers; i++) { struct TALER_KYCLOGIC_KycProvider *kp = kyc_providers[i]; if (0 != - strcasecmp (provider_section_name, + strcasecmp (name, kp->provider_section_name)) continue; *plugin = kp->logic; *pd = kp->pd; + *provider_section = kp->provider_section_name; + return GNUNET_OK; + } + for (unsigned int i = 0; i<num_kyc_logics; i++) + { + struct TALER_KYCLOGIC_Plugin *logic = kyc_logics[i]; + + if (0 != + strcasecmp (logic->name, + name)) + continue; + *plugin = logic; + *pd = NULL; + *provider_section = NULL; return GNUNET_OK; } GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Provider `%s' unknown\n", - provider_section_name); + name); return GNUNET_SYSERR; } @@ -1134,4 +1480,33 @@ TALER_KYCLOGIC_kyc_iterate_thresholds ( } +void +TALER_KYCLOGIC_lookup_checks (const char *section_name, + unsigned int *num_checks, + char ***provided_checks) +{ + *num_checks = 0; + *provided_checks = NULL; + for (unsigned int i = 0; i<num_kyc_providers; i++) + { + struct TALER_KYCLOGIC_KycProvider *kp = kyc_providers[i]; + + if (0 != + strcasecmp (section_name, + kp->provider_section_name)) + continue; + *num_checks = kp->num_checks; + if (0 != kp->num_checks) + { + char **pc = GNUNET_new_array (kp->num_checks, + char *); + for (unsigned int i = 0; i<kp->num_checks; i++) + pc[i] = GNUNET_strdup (kp->provided_checks[i]->name); + *provided_checks = pc; + } + return; + } +} + + /* end of taler-exchange-httpd_kyc.c */ |