exchange

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

commit e362663b75830c89f620be750d36e343b0d8ca3d
parent 640f0a6186e4581d89b4616d4620ba7344d09ac1
Author: Christian Grothoff <christian@grothoff.org>
Date:   Tue,  7 May 2024 14:56:08 +0200

basic refactoring of httpd for new AML, incomplete

Diffstat:
Msrc/exchange/Makefile.am | 22++++++++++++----------
Msrc/exchange/taler-exchange-aggregator.c | 17++---------------
Msrc/exchange/taler-exchange-httpd_age-withdraw.c | 274++++++++++++++++++++++++++-----------------------------------------------------
Msrc/exchange/taler-exchange-httpd_batch-withdraw.c | 265++++++-------------------------------------------------------------------------
Msrc/exchange/taler-exchange-httpd_deposits_get.c | 8++------
Msrc/exchange/taler-exchange-httpd_responses.c | 25-------------------------
Msrc/exchange/taler-exchange-httpd_responses.h | 13-------------
Asrc/exchange/taler-exchange-httpd_withdraw.c | 211+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/exchange/taler-exchange-httpd_withdraw.h | 51+++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/exchangedb/pg_get_kyc_rules.c | 9+++++----
Msrc/exchangedb/pg_get_kyc_rules.h | 2--
Msrc/include/taler_exchangedb_plugin.h | 2--
Msrc/include/taler_kyclogic_lib.h | 41+++++++++++++++++++++++++++++++++++++++--
Msrc/kyclogic/kyclogic_api.c | 758+++++++++++++++++++++++++++++++++++++++++--------------------------------------
Msrc/kyclogic/taler-exchange-kyc-tester.c | 24+++++++++++++-----------
15 files changed, 835 insertions(+), 887 deletions(-)

diff --git a/src/exchange/Makefile.am b/src/exchange/Makefile.am @@ -127,9 +127,6 @@ taler_exchange_httpd_SOURCES = \ taler-exchange-httpd_age-withdraw.c taler-exchange-httpd_age-withdraw.h \ taler-exchange-httpd_age-withdraw_reveal.c taler-exchange-httpd_age-withdraw_reveal.h \ taler-exchange-httpd_auditors.c taler-exchange-httpd_auditors.h \ - taler-exchange-httpd_aml-decision.c taler-exchange-httpd_aml-decision.h \ - taler-exchange-httpd_aml-decision-get.c \ - taler-exchange-httpd_aml-decisions-get.c \ taler-exchange-httpd_batch-deposit.c taler-exchange-httpd_batch-deposit.h \ taler-exchange-httpd_batch-withdraw.c taler-exchange-httpd_batch-withdraw.h \ taler-exchange-httpd_coins_get.c taler-exchange-httpd_coins_get.h \ @@ -139,12 +136,9 @@ taler_exchange_httpd_SOURCES = \ taler-exchange-httpd_contract.c taler-exchange-httpd_contract.h \ taler-exchange-httpd_csr.c taler-exchange-httpd_csr.h \ taler-exchange-httpd_db.c taler-exchange-httpd_db.h \ - 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-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 \ taler-exchange-httpd_kyc-webhook.c taler-exchange-httpd_kyc-webhook.h \ taler-exchange-httpd_link.c taler-exchange-httpd_link.h \ taler-exchange-httpd_management.h \ @@ -168,22 +162,30 @@ taler_exchange_httpd_SOURCES = \ taler-exchange-httpd_purses_deposit.c taler-exchange-httpd_purses_deposit.h \ taler-exchange-httpd_purses_delete.c taler-exchange-httpd_purses_delete.h \ taler-exchange-httpd_purses_get.c taler-exchange-httpd_purses_get.h \ - taler-exchange-httpd_purses_merge.c taler-exchange-httpd_purses_merge.h \ taler-exchange-httpd_recoup.c taler-exchange-httpd_recoup.h \ taler-exchange-httpd_recoup-refresh.c taler-exchange-httpd_recoup-refresh.h \ taler-exchange-httpd_refreshes_reveal.c taler-exchange-httpd_refreshes_reveal.h \ taler-exchange-httpd_refund.c taler-exchange-httpd_refund.h \ taler-exchange-httpd_reserves_attest.c taler-exchange-httpd_reserves_attest.h \ - taler-exchange-httpd_reserves_close.c taler-exchange-httpd_reserves_close.h \ taler-exchange-httpd_reserves_get.c taler-exchange-httpd_reserves_get.h \ taler-exchange-httpd_reserves_get_attest.c taler-exchange-httpd_reserves_get_attest.h \ taler-exchange-httpd_reserves_history.c taler-exchange-httpd_reserves_history.h \ taler-exchange-httpd_reserves_open.c taler-exchange-httpd_reserves_open.h \ - taler-exchange-httpd_reserves_purse.c taler-exchange-httpd_reserves_purse.h \ taler-exchange-httpd_responses.c taler-exchange-httpd_responses.h \ taler-exchange-httpd_spa.c taler-exchange-httpd_spa.h \ taler-exchange-httpd_terms.c taler-exchange-httpd_terms.h \ - taler-exchange-httpd_transfers_get.c taler-exchange-httpd_transfers_get.h + taler-exchange-httpd_transfers_get.c taler-exchange-httpd_transfers_get.h \ + taler-exchange-httpd_withdraw.c taler-exchange-httpd_withdraw.h + +# taler-exchange-httpd_purses_merge.c taler-exchange-httpd_purses_merge.h \ +# taler-exchange-httpd_reserves_close.c taler-exchange-httpd_reserves_close.h \ +# taler-exchange-httpd_reserves_purse.c taler-exchange-httpd_reserves_purse.h \ +# taler-exchange-httpd_kyc-check.c taler-exchange-httpd_kyc-check.h \ +# taler-exchange-httpd_kyc-wallet.c taler-exchange-httpd_kyc-wallet.h \ +# taler-exchange-httpd_deposits_get.c taler-exchange-httpd_deposits_get.h \ +# taler-exchange-httpd_aml-decision.c taler-exchange-httpd_aml-decision.h \ +# taler-exchange-httpd_aml-decision-get.c \ +# taler-exchange-httpd_aml-decisions-get.c taler_exchange_httpd_LDADD = \ $(LIBGCRYPT_LIBS) \ diff --git a/src/exchange/taler-exchange-aggregator.c b/src/exchange/taler-exchange-aggregator.c @@ -500,7 +500,6 @@ legitimization_satisfied (struct AggregationUnit *au_active) { struct TALER_KYCLOGIC_LegitimizationRuleSet *lrs = NULL; struct TALER_KYCLOGIC_KycRule *requirement; - struct GNUNET_TIME_Timestamp expiration_time; enum GNUNET_DB_QueryStatus qs; json_t *jrule; @@ -512,29 +511,17 @@ legitimization_satisfied (struct AggregationUnit *au_active) qs = db_plugin->get_kyc_rules (db_plugin->cls, &au_active->h_payto, - &expiration_time, &jrules); if (qs < 0) { GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); return false; } - if ( (qs > 0) && - (GNUNET_TIME_absolute_is_past (expiration_time.abs_time)) ) - { - json_decref (jrules); - jrules = NULL; - qs = GNUNET_DB_STATUS_SUCCESS_NO_RESULTS; - } if (qs > 0) { lrs = TALER_KYCLOGIC_rules_parse (jrules); - if (NULL == lrs) - { - GNUNET_break (0); - json_decref (jrules); - return false; - } + GNUNET_break (NULL != lrs); + /* Fall back to default rules on parse error! */ json_decref (jrules); } } diff --git a/src/exchange/taler-exchange-httpd_age-withdraw.c b/src/exchange/taler-exchange-httpd_age-withdraw.c @@ -33,6 +33,7 @@ #include "taler_kyclogic_lib.h" #include "taler_mhd_lib.h" #include "taler-exchange-httpd_age-withdraw.h" +#include "taler-exchange-httpd_withdraw.h" #include "taler-exchange-httpd_responses.h" #include "taler-exchange-httpd_keys.h" #include "taler_util.h" @@ -54,11 +55,6 @@ struct AgeWithdrawContext struct GNUNET_TIME_Timestamp now; /** - * Hash of the wire source URL, needed when kyc is needed. - */ - struct TALER_PaytoHashP h_payto; - - /** * The data from the age-withdraw request, as we persist it */ struct TALER_EXCHANGEDB_AgeWithdraw commitment; @@ -549,19 +545,18 @@ reply_age_withdraw_success ( { struct TALER_ExchangePublicKeyP pub; struct TALER_ExchangeSignatureP sig; - enum TALER_ErrorCode ec = - TALER_exchange_online_age_withdraw_confirmation_sign ( - &TEH_keys_exchange_sign_, - ach, - noreveal_index, - &pub, - &sig); - + enum TALER_ErrorCode ec; + + ec = TALER_exchange_online_age_withdraw_confirmation_sign ( + &TEH_keys_exchange_sign_, + ach, + noreveal_index, + &pub, + &sig); if (TALER_EC_NONE != ec) return TALER_MHD_reply_with_ec (connection, ec, NULL); - return TALER_MHD_REPLY_JSON_PACK (connection, MHD_HTTP_OK, GNUNET_JSON_pack_uint64 ("noreveal_index", @@ -619,49 +614,6 @@ request_is_idempotent (struct MHD_Connection *con, /** - * 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, of type struct AgeWithdrawContext - */ -static void -age_withdraw_amount_cb (void *cls, - struct GNUNET_TIME_Absolute limit, - TALER_EXCHANGEDB_KycAmountCallback cb, - void *cb_cls) -{ - struct AgeWithdrawContext *awc = cls; - enum GNUNET_DB_QueryStatus qs; - - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Signaling amount %s for KYC check during age-withdrawal\n", - TALER_amount2s (&awc->commitment.amount_with_fee)); - if (GNUNET_OK != - cb (cb_cls, - &awc->commitment.amount_with_fee, - awc->now.abs_time)) - return; - qs = TEH_plugin->select_withdraw_amounts_for_kyc_check (TEH_plugin->cls, - &awc->h_payto, - limit, - cb, - cb_cls); - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Got %d additional transactions for this age-withdrawal and limit %llu\n", - qs, - (unsigned long long) limit.abs_value_us); - GNUNET_break (qs >= 0); -} - - -/** * Function implementing age withdraw transaction. Runs the * transaction logic; IF it returns a non-error code, the transaction * logic MUST NOT queue a MHD response. IF it returns an hard error, @@ -682,135 +634,89 @@ age_withdraw_transaction (void *cls, { struct AgeWithdrawContext *awc = cls; enum GNUNET_DB_QueryStatus qs; - - qs = TEH_plugin->reserves_get_origin (TEH_plugin->cls, - &awc->commitment.reserve_pub, - &awc->h_payto); - if (qs < 0) + bool found = false; + bool balance_ok = false; + bool age_ok = false; + bool conflict = false; + uint16_t allowed_maximum_age = 0; + uint32_t reserve_birthday = 0; + struct TALER_Amount reserve_balance; + + qs = TEH_withdraw_kyc_check (&awc->kyc, + connection, + mhd_ret, + &awc->commitment.reserve_pub, + &awc->commitment.amount_with_fee, + awc->now); + if ( (qs < 0) || + (! awc->kyc.ok) ) return qs; - - /* If _no_ results, reserve was created by merge, - in which case no KYC check is required as the - merge already did that. */ - - if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs) + qs = TEH_plugin->do_age_withdraw (TEH_plugin->cls, + &awc->commitment, + awc->now, + &found, + &balance_ok, + &reserve_balance, + &age_ok, + &allowed_maximum_age, + &reserve_birthday, + &conflict); + if (0 > qs) { - char *kyc_required; - - qs = TALER_KYCLOGIC_kyc_test_required ( - TALER_KYCLOGIC_KYC_TRIGGER_AGE_WITHDRAW, - &awc->h_payto, - TEH_plugin->select_satisfied_kyc_processes, - TEH_plugin->cls, - &age_withdraw_amount_cb, - awc, - &kyc_required); - - if (qs < 0) - { - if (GNUNET_DB_STATUS_HARD_ERROR == qs) - { - GNUNET_break (0); - *mhd_ret = TALER_MHD_reply_with_ec (connection, - TALER_EC_GENERIC_DB_FETCH_FAILED, - "kyc_test_required"); - } - return qs; - } - - if (NULL != kyc_required) - { - /* Mark result and return by inserting KYC requirement into DB! */ - awc->kyc.ok = false; - return TEH_plugin->insert_kyc_requirement_for_account ( - TEH_plugin->cls, - kyc_required, - &awc->h_payto, - &awc->commitment.reserve_pub, - &awc->kyc.requirement_row); - } + if (GNUNET_DB_STATUS_HARD_ERROR == qs) + *mhd_ret = TALER_MHD_reply_with_ec (connection, + TALER_EC_GENERIC_DB_FETCH_FAILED, + "do_age_withdraw"); + return qs; } - - awc->kyc.ok = true; - - /* KYC requirement fulfilled, do the age-withdraw transaction */ + if (! found) { - bool found = false; - bool balance_ok = false; - bool age_ok = false; - bool conflict = false; - uint16_t allowed_maximum_age = 0; - uint32_t reserve_birthday = 0; - struct TALER_Amount reserve_balance; - - qs = TEH_plugin->do_age_withdraw (TEH_plugin->cls, - &awc->commitment, - awc->now, - &found, - &balance_ok, - &reserve_balance, - &age_ok, - &allowed_maximum_age, - &reserve_birthday, - &conflict); - if (0 > qs) - { - if (GNUNET_DB_STATUS_HARD_ERROR == qs) - *mhd_ret = TALER_MHD_reply_with_ec (connection, - TALER_EC_GENERIC_DB_FETCH_FAILED, - "do_age_withdraw"); - return qs; - } - if (! found) - { - *mhd_ret = TALER_MHD_reply_with_ec (connection, - TALER_EC_EXCHANGE_GENERIC_RESERVE_UNKNOWN, - NULL); - return GNUNET_DB_STATUS_HARD_ERROR; - } - if (! age_ok) - { - enum TALER_ErrorCode ec = - TALER_EC_EXCHANGE_AGE_WITHDRAW_MAXIMUM_AGE_TOO_LARGE; - - *mhd_ret = - TALER_MHD_REPLY_JSON_PACK ( - connection, - MHD_HTTP_CONFLICT, - TALER_MHD_PACK_EC (ec), - GNUNET_JSON_pack_uint64 ("allowed_maximum_age", - allowed_maximum_age), - GNUNET_JSON_pack_uint64 ("reserve_birthday", - reserve_birthday)); - - return GNUNET_DB_STATUS_HARD_ERROR; - } - if (! balance_ok) - { - TEH_plugin->rollback (TEH_plugin->cls); + *mhd_ret = TALER_MHD_reply_with_ec (connection, + TALER_EC_EXCHANGE_GENERIC_RESERVE_UNKNOWN, + NULL); + return GNUNET_DB_STATUS_HARD_ERROR; + } + if (! age_ok) + { + enum TALER_ErrorCode ec = + TALER_EC_EXCHANGE_AGE_WITHDRAW_MAXIMUM_AGE_TOO_LARGE; - *mhd_ret = TEH_RESPONSE_reply_reserve_insufficient_balance ( + *mhd_ret = + TALER_MHD_REPLY_JSON_PACK ( connection, - TALER_EC_EXCHANGE_AGE_WITHDRAW_INSUFFICIENT_FUNDS, - &reserve_balance, - &awc->commitment.amount_with_fee, - &awc->commitment.reserve_pub); - - return GNUNET_DB_STATUS_HARD_ERROR; - } - if (conflict) - { - /* do_age_withdraw signaled a conflict, so there MUST be an entry - * in the DB. Put that into the response */ - bool ok = request_is_idempotent (connection, - awc, - mhd_ret); - GNUNET_assert (ok); - return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT; - } - *mhd_ret = -1; + MHD_HTTP_CONFLICT, + TALER_MHD_PACK_EC (ec), + GNUNET_JSON_pack_uint64 ("allowed_maximum_age", + allowed_maximum_age), + GNUNET_JSON_pack_uint64 ("reserve_birthday", + reserve_birthday)); + + return GNUNET_DB_STATUS_HARD_ERROR; } + if (! balance_ok) + { + TEH_plugin->rollback (TEH_plugin->cls); + + *mhd_ret = TEH_RESPONSE_reply_reserve_insufficient_balance ( + connection, + TALER_EC_EXCHANGE_AGE_WITHDRAW_INSUFFICIENT_FUNDS, + &reserve_balance, + &awc->commitment.amount_with_fee, + &awc->commitment.reserve_pub); + return GNUNET_DB_STATUS_HARD_ERROR; + } + if (conflict) + { + /* do_age_withdraw signaled a conflict, so there MUST be an entry + * in the DB. Put that into the response */ + bool ok = request_is_idempotent (connection, + awc, + mhd_ret); + GNUNET_assert (ok); + return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT; + } + *mhd_ret = -1; if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs) TEH_METRICS_num_success[TEH_MT_SUCCESS_AGE_WITHDRAW]++; return qs; @@ -846,8 +752,6 @@ sign_and_do_age_withdraw ( struct TALER_BlindedDenominationSignature denom_sigs[awc->num_coins]; uint8_t noreveal_index; - awc->now = GNUNET_TIME_timestamp_get (); - /* Pick the challenge */ noreveal_index = GNUNET_CRYPTO_random_u32 (GNUNET_CRYPTO_QUALITY_STRONG, @@ -918,7 +822,10 @@ TEH_handler_age_withdraw (struct TEH_RequestContext *rc, MHD_RESULT mhd_ret; const json_t *j_denom_hs; const json_t *j_blinded_coin_evs; - struct AgeWithdrawContext awc = {0}; + struct AgeWithdrawContext awc = { + .commitment.reserve_pub = *reserve_pub, + .now = GNUNET_TIME_timestamp_get () + }; struct GNUNET_JSON_Specification spec[] = { GNUNET_JSON_spec_array_const ("denom_hs", &j_denom_hs), @@ -931,9 +838,6 @@ TEH_handler_age_withdraw (struct TEH_RequestContext *rc, GNUNET_JSON_spec_end () }; - awc.commitment.reserve_pub = *reserve_pub; - - /* Parse the JSON body */ { enum GNUNET_GenericReturnValue res; @@ -1000,7 +904,7 @@ TEH_handler_age_withdraw (struct TEH_RequestContext *rc, * the DB-transaction */ if (! awc.kyc.ok) mhd_ret = TEH_RESPONSE_reply_kyc_required (rc->connection, - &awc.h_payto, + NULL, /* FIXME! */ &awc.kyc); else mhd_ret = reply_age_withdraw_success (rc->connection, diff --git a/src/exchange/taler-exchange-httpd_batch-withdraw.c b/src/exchange/taler-exchange-httpd_batch-withdraw.c @@ -1,6 +1,6 @@ /* This file is part of TALER - Copyright (C) 2014-2023 Taler Systems SA + Copyright (C) 2014-2024 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 @@ -31,6 +31,7 @@ #include "taler_kyclogic_lib.h" #include "taler_mhd_lib.h" #include "taler-exchange-httpd_batch-withdraw.h" +#include "taler-exchange-httpd_withdraw.h" #include "taler-exchange-httpd_responses.h" #include "taler-exchange-httpd_keys.h" #include "taler_util.h" @@ -90,12 +91,6 @@ struct BatchWithdrawContext struct PlanchetContext *planchets; /** - * Hash of the payto-URI representing the reserve - * from which we are withdrawing. - */ - struct TALER_PaytoHashP h_payto; - - /** * Current time for the DB transaction. */ struct GNUNET_TIME_Timestamp now; @@ -110,82 +105,10 @@ struct BatchWithdrawContext */ unsigned int planchets_length; - /** - * AML decision, #TALER_AML_NORMAL if we may proceed. - */ - enum TALER_AmlDecisionState aml_decision; - }; /** - * 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 - */ -static void -batch_withdraw_amount_cb (void *cls, - struct GNUNET_TIME_Absolute limit, - TALER_EXCHANGEDB_KycAmountCallback cb, - void *cb_cls) -{ - struct BatchWithdrawContext *wc = cls; - enum GNUNET_DB_QueryStatus qs; - - if (GNUNET_OK != - cb (cb_cls, - &wc->batch_total, - wc->now.abs_time)) - return; - qs = TEH_plugin->select_withdraw_amounts_for_kyc_check ( - TEH_plugin->cls, - &wc->h_payto, - limit, - cb, - cb_cls); - GNUNET_break (qs >= 0); -} - - -/** - * Function called on each @a amount that was found to - * be relevant for the AML check as it was merged into - * the reserve. - * - * @param cls `struct TALER_Amount *` to total up the amounts - * @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 -aml_amount_cb ( - void *cls, - const struct TALER_Amount *amount, - struct GNUNET_TIME_Absolute date) -{ - struct TALER_Amount *total = cls; - - GNUNET_assert (0 <= - TALER_amount_add (total, - total, - amount)); - return GNUNET_OK; -} - - -/** * Generates our final (successful) response. * * @param rc request context @@ -198,17 +121,6 @@ generate_reply_success (const struct TEH_RequestContext *rc, { json_t *sigs; - if (! wc->kyc.ok) - { - /* KYC required */ - return TEH_RESPONSE_reply_kyc_required (rc->connection, - &wc->h_payto, - &wc->kyc); - } - if (TALER_AML_NORMAL != wc->aml_decision) - return TEH_RESPONSE_reply_aml_blocked (rc->connection, - wc->aml_decision); - sigs = json_array (); GNUNET_assert (NULL != sigs); for (unsigned int i = 0; i<wc->planchets_length; i++) @@ -309,163 +221,16 @@ batch_withdraw_transaction (void *cls, bool age_ok = false; uint16_t allowed_maximum_age = 0; struct TALER_Amount reserve_balance; - char *kyc_required; - struct TALER_PaytoHashP reserve_h_payto; - - wc->now = GNUNET_TIME_timestamp_get (); - /* Do AML check: compute total merged amount and check - against applicable AML threshold */ - { - char *reserve_payto; - - reserve_payto = TALER_reserve_make_payto (TEH_base_url, - wc->reserve_pub); - TALER_payto_hash (reserve_payto, - &reserve_h_payto); - GNUNET_free (reserve_payto); - } - { - struct TALER_Amount merge_amount; - struct TALER_Amount threshold; - struct GNUNET_TIME_Absolute now_minus_one_month; - - now_minus_one_month - = GNUNET_TIME_absolute_subtract (wc->now.abs_time, - GNUNET_TIME_UNIT_MONTHS); - GNUNET_assert (GNUNET_OK == - TALER_amount_set_zero (TEH_currency, - &merge_amount)); - qs = TEH_plugin->select_merge_amounts_for_kyc_check (TEH_plugin->cls, - &reserve_h_payto, - now_minus_one_month, - &aml_amount_cb, - &merge_amount); - if (qs < 0) - { - GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); - if (GNUNET_DB_STATUS_HARD_ERROR == qs) - *mhd_ret = TALER_MHD_reply_with_error (connection, - MHD_HTTP_INTERNAL_SERVER_ERROR, - TALER_EC_GENERIC_DB_FETCH_FAILED, - "select_merge_amounts_for_kyc_check"); - return qs; - } - qs = TEH_plugin->select_aml_threshold (TEH_plugin->cls, - &reserve_h_payto, - &wc->aml_decision, - &wc->kyc, - &threshold); - if (qs < 0) - { - GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); - if (GNUNET_DB_STATUS_HARD_ERROR == qs) - *mhd_ret = TALER_MHD_reply_with_error (connection, - MHD_HTTP_INTERNAL_SERVER_ERROR, - TALER_EC_GENERIC_DB_FETCH_FAILED, - "select_aml_threshold"); - return qs; - } - if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) - { - threshold = TEH_aml_threshold; /* use default */ - wc->aml_decision = TALER_AML_NORMAL; - } - - switch (wc->aml_decision) - { - case TALER_AML_NORMAL: - if (0 >= TALER_amount_cmp (&merge_amount, - &threshold)) - { - /* merge_amount <= threshold, continue withdraw below */ - break; - } - wc->aml_decision = TALER_AML_PENDING; - qs = TEH_plugin->trigger_aml_process (TEH_plugin->cls, - &reserve_h_payto, - &merge_amount); - if (qs <= 0) - { - GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); - if (GNUNET_DB_STATUS_HARD_ERROR == qs) - *mhd_ret = TALER_MHD_reply_with_error (connection, - MHD_HTTP_INTERNAL_SERVER_ERROR, - TALER_EC_GENERIC_DB_STORE_FAILED, - "trigger_aml_process"); - return qs; - } - return qs; - case TALER_AML_PENDING: - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "AML already pending, doing nothing\n"); - return qs; - case TALER_AML_FROZEN: - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Account frozen, doing nothing\n"); - return qs; - } - } - /* Check if the money came from a wire transfer */ - qs = TEH_plugin->reserves_get_origin (TEH_plugin->cls, - wc->reserve_pub, - &wc->h_payto); - if (qs < 0) - { - if (GNUNET_DB_STATUS_HARD_ERROR == qs) - *mhd_ret = TALER_MHD_reply_with_error (connection, - MHD_HTTP_INTERNAL_SERVER_ERROR, - TALER_EC_GENERIC_DB_FETCH_FAILED, - "reserves_get_origin"); + qs = TEH_withdraw_kyc_check (&wc->kyc, + connection, + mhd_ret, + wc->reserve_pub, + &wc->batch_total, + wc->now); + if ( (qs < 0) || + (! wc->kyc.ok) ) return qs; - } - /* If no results, reserve was created by merge, in which case no KYC check - is required as the merge already did that. */ - if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs) - { - qs = TALER_KYCLOGIC_kyc_test_required ( - TALER_KYCLOGIC_KYC_TRIGGER_WITHDRAW, - &wc->h_payto, - TEH_plugin->select_satisfied_kyc_processes, - TEH_plugin->cls, - &batch_withdraw_amount_cb, - wc, - &kyc_required); - if (qs < 0) - { - GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); - if (GNUNET_DB_STATUS_HARD_ERROR == qs) - *mhd_ret = TALER_MHD_reply_with_error (connection, - MHD_HTTP_INTERNAL_SERVER_ERROR, - TALER_EC_GENERIC_DB_FETCH_FAILED, - "kyc_test_required"); - return qs; - } - if (NULL != kyc_required) - { - /* insert KYC requirement into DB! */ - wc->kyc.ok = false; - qs = TEH_plugin->insert_kyc_requirement_for_account ( - TEH_plugin->cls, - kyc_required, - &wc->h_payto, - wc->reserve_pub, - &wc->kyc.requirement_row); - GNUNET_free (kyc_required); - if (qs < 0) - { - GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); - if (GNUNET_DB_STATUS_HARD_ERROR == qs) - *mhd_ret = TALER_MHD_reply_with_error (connection, - MHD_HTTP_INTERNAL_SERVER_ERROR, - TALER_EC_GENERIC_DB_STORE_FAILED, - "insert_kyc_requirement_for_account"); - } - return qs; - } - } - wc->kyc.ok = true; - qs = TEH_plugin->do_batch_withdraw (TEH_plugin->cls, wc->now, wc->reserve_pub, @@ -669,6 +434,13 @@ prepare_transaction (const struct TEH_RequestContext *rc, } } /* return final positive response */ + if (! wc->kyc.ok) + { + /* KYC required */ + return TEH_RESPONSE_reply_kyc_required (rc->connection, + NULL, /* FIXME! */ + &wc->kyc); + } return generate_reply_success (rc, wc); } @@ -877,7 +649,8 @@ TEH_handler_batch_withdraw (struct TEH_RequestContext *rc, { struct BatchWithdrawContext wc = { .reserve_pub = reserve_pub, - .rc = rc + .rc = rc, + .now = GNUNET_TIME_timestamp_get () }; const json_t *planchets; struct GNUNET_JSON_Specification spec[] = { diff --git a/src/exchange/taler-exchange-httpd_deposits_get.c b/src/exchange/taler-exchange-httpd_deposits_get.c @@ -123,11 +123,6 @@ struct DepositWtidContext struct TALER_EXCHANGEDB_KycStatus kyc; /** - * AML status information for the receiving account. - */ - enum TALER_AmlDecisionState aml_decision; - - /** * Set to #GNUNET_YES by #handle_wtid if the wire transfer is still pending * (and the above were not set). * Set to #GNUNET_SYSERR if there was a serious error. @@ -377,7 +372,8 @@ handle_track_transaction_request ( return TALER_MHD_reply_with_error (connection, MHD_HTTP_INTERNAL_SERVER_ERROR, TALER_EC_GENERIC_DB_INVARIANT_FAILURE, - "wire fees exceed aggregate in database"); + "wire fees exceed aggregate in database") + ; if (GNUNET_YES == ctx->pending) { if ( (GNUNET_TIME_absolute_is_future (ctx->timeout)) && diff --git a/src/exchange/taler-exchange-httpd_responses.c b/src/exchange/taler-exchange-httpd_responses.c @@ -354,31 +354,6 @@ TEH_RESPONSE_reply_kyc_required (struct MHD_Connection *connection, MHD_RESULT -TEH_RESPONSE_reply_aml_blocked (struct MHD_Connection *connection, - enum TALER_AmlDecisionState status) -{ - enum TALER_ErrorCode ec = TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE; - - switch (status) - { - case TALER_AML_NORMAL: - GNUNET_break (0); - return MHD_NO; - case TALER_AML_PENDING: - ec = TALER_EC_EXCHANGE_GENERIC_AML_PENDING; - break; - case TALER_AML_FROZEN: - ec = TALER_EC_EXCHANGE_GENERIC_AML_FROZEN; - break; - } - return TALER_MHD_REPLY_JSON_PACK ( - connection, - MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS, - TALER_JSON_pack_ec (ec)); -} - - -MHD_RESULT TEH_RESPONSE_reply_not_modified ( struct MHD_Connection *connection, const char *etags, diff --git a/src/exchange/taler-exchange-httpd_responses.h b/src/exchange/taler-exchange-httpd_responses.h @@ -98,19 +98,6 @@ TEH_RESPONSE_reply_kyc_required (struct MHD_Connection *connection, /** - * Send information that an AML process is blocking - * the operation right now. - * - * @param connection connection to the client - * @param status current AML status - * @return MHD result code - */ -MHD_RESULT -TEH_RESPONSE_reply_aml_blocked (struct MHD_Connection *connection, - enum TALER_AmlDecisionState status); - - -/** * Send assertion that the given denomination key hash * is not usable (typically expired) at this time. * diff --git a/src/exchange/taler-exchange-httpd_withdraw.c b/src/exchange/taler-exchange-httpd_withdraw.c @@ -0,0 +1,211 @@ +/* + This file is part of TALER + Copyright (C) 2014-2024 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 <http://www.gnu.org/licenses/> +*/ +/** + * @file taler-exchange-httpd_batch-withdraw.c + * @brief Handle /reserves/$RESERVE_PUB/batch-withdraw requests + * @author Florian Dold + * @author Benedikt Mueller + * @author Christian Grothoff + */ +#include "platform.h" +#include <gnunet/gnunet_util_lib.h> +#include <jansson.h> +#include "taler-exchange-httpd.h" +#include "taler_json_lib.h" +#include "taler_kyclogic_lib.h" +#include "taler_mhd_lib.h" +#include "taler-exchange-httpd_withdraw.h" +#include "taler-exchange-httpd_responses.h" +#include "taler_util.h" + + +/** + * Closure for #withdraw_amount_cb(). + */ +struct WithdrawContext +{ + /** + * Total amount being withdrawn now. + */ + const struct TALER_Amount *withdraw_total; + + /** + * Current time. + */ + struct GNUNET_TIME_Timestamp now; + + /** + * Account we are checking against. + */ + struct TALER_PaytoHashP h_payto; +}; + + +/** + * 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, of type struct AgeWithdrawContext + */ +static void +withdraw_amount_cb ( + void *cls, + struct GNUNET_TIME_Absolute limit, + TALER_EXCHANGEDB_KycAmountCallback cb, + void *cb_cls) +{ + struct WithdrawContext *wc = cls; + enum GNUNET_DB_QueryStatus qs; + + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Signaling amount %s for KYC check during age-withdrawal\n", + TALER_amount2s (wc->withdraw_total)); + if (GNUNET_OK != + cb (cb_cls, + wc->withdraw_total, + wc->now.abs_time)) + return; + qs = TEH_plugin->select_withdraw_amounts_for_kyc_check (TEH_plugin->cls, + &wc->h_payto, + limit, + cb, + cb_cls); + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Got %d additional transactions for this age-withdrawal and limit %llu\n", + qs, + (unsigned long long) limit.abs_value_us); + GNUNET_break (qs >= 0); +} + + +enum GNUNET_DB_QueryStatus +TEH_withdraw_kyc_check ( + struct TALER_EXCHANGEDB_KycStatus *kyc, + struct MHD_Connection *connection, + MHD_RESULT *mhd_ret, + const struct TALER_ReservePublicKeyP *reserve_pub, + const struct TALER_Amount *withdraw_total, + struct GNUNET_TIME_Timestamp now + ) +{ + enum GNUNET_DB_QueryStatus qs; + struct WithdrawContext wc = { + .withdraw_total = withdraw_total, + .now = now + }; + + /* Check if the money came from a wire transfer */ + qs = TEH_plugin->reserves_get_origin ( + TEH_plugin->cls, + reserve_pub, + &wc.h_payto); + if (qs < 0) + { + if (GNUNET_DB_STATUS_HARD_ERROR == qs) + *mhd_ret = TALER_MHD_reply_with_error (connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_GENERIC_DB_FETCH_FAILED, + "reserves_get_origin"); + return qs; + } + + /* If _no_ results, reserve was created by merge, + in which case no KYC check is required as the + merge already did that. */ + if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs) + { + json_t *jrules; + struct TALER_KYCLOGIC_LegitimizationRuleSet *lrs = NULL; + struct TALER_KYCLOGIC_KycRule *requirement; + + qs = TEH_plugin->get_kyc_rules (TEH_plugin->cls, + &wc.h_payto, + &jrules); + if (qs < 0) + { + GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); + return qs; + } + if (qs > 0) + { + lrs = TALER_KYCLOGIC_rules_parse (jrules); + GNUNET_break (NULL != lrs); + /* Fall back to default rules on parse error! */ + json_decref (jrules); + } + qs = TALER_KYCLOGIC_kyc_test_required ( + TALER_KYCLOGIC_KYC_TRIGGER_AGE_WITHDRAW, + &wc.h_payto, + lrs, + &withdraw_amount_cb, + &wc, + &requirement); + if (qs < 0) + { + GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); + TALER_KYCLOGIC_rules_free (lrs); + if (GNUNET_DB_STATUS_HARD_ERROR == qs) + *mhd_ret = TALER_MHD_reply_with_ec (connection, + TALER_EC_GENERIC_DB_FETCH_FAILED, + "kyc_test_required"); + return qs; + } + + if (NULL != requirement) + { + json_t *jrule; + union TALER_AccountPublicKeyP account_pub; + + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "KYC requirement is %s\n", + TALER_KYCLOGIC_rule2s (requirement)); + jrule = TALER_KYCLOGIC_rule2j (requirement); + account_pub.reserve_pub + = *reserve_pub; + kyc->ok = false; + qs = TEH_plugin->trigger_kyc_rule_for_account ( + TEH_plugin->cls, + &wc.h_payto, + &account_pub, + jrule, + TALER_KYCLOGIC_rule2priority (requirement), + &kyc->requirement_row); + TALER_KYCLOGIC_rules_free (lrs); + json_decref (jrule); + if (GNUNET_DB_STATUS_HARD_ERROR == qs) + { + GNUNET_break (0); + *mhd_ret = TALER_MHD_reply_with_ec (connection, + TALER_EC_GENERIC_DB_STORE_FAILED, + "trigger_kyc_rule_for_account"); + } + return qs; + } + TALER_KYCLOGIC_rules_free (lrs); + } + kyc->ok = true; + return qs; +} diff --git a/src/exchange/taler-exchange-httpd_withdraw.h b/src/exchange/taler-exchange-httpd_withdraw.h @@ -0,0 +1,51 @@ +/* + This file is part of TALER + Copyright (C) 2014-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 <http://www.gnu.org/licenses/> +*/ +/** + * @file taler-exchange-httpd_withdraw.h + * @brief common code for withdraw requests + * @author Christian Grothoff + */ +#ifndef TALER_EXCHANGE_HTTPD_WITHDRAW_H +#define TALER_EXCHANGE_HTTPD_WITHDRAW_H + +#include <microhttpd.h> +#include "taler-exchange-httpd.h" + + +/** + * Do legitimization check for withdrawing @a withdraw_total + * from @a reserve_pub at time @a now. + * + * @param[out] kyc set to kyc status + * @param[in,out] connection used to return hard errors + * @param[out] mhd_ret set if errors were returned + * (only on hard error) + * @param reserve_pub reserve from which we withdraw + * @param withdraw_total how much are being withdrawn + * @param now current time + * @return transaction status, error will have been + * queued if transaction status is set to hard error + */ +enum GNUNET_DB_QueryStatus +TEH_withdraw_kyc_check ( + struct TALER_EXCHANGEDB_KycStatus *kyc, + struct MHD_Connection *connection, + MHD_RESULT *mhd_ret, + const struct TALER_ReservePublicKeyP *reserve_pub, + const struct TALER_Amount *withdraw_total, + struct GNUNET_TIME_Timestamp now); + +#endif diff --git a/src/exchangedb/pg_get_kyc_rules.c b/src/exchangedb/pg_get_kyc_rules.c @@ -30,19 +30,20 @@ enum GNUNET_DB_QueryStatus TEH_PG_get_kyc_rules ( void *cls, const struct TALER_PaytoHashP *h_payto, - struct GNUNET_TIME_Timestamp *expiration_time, json_t **jrules) { struct PostgresClosure *pg = cls; + struct GNUNET_TIME_Timestamp now + = GNUNET_TIME_timestamp_get (); struct GNUNET_PQ_QueryParam params[] = { GNUNET_PQ_query_param_auto_from_type (h_payto), + GNUNET_PQ_query_param_timestamp ("now", + &now), GNUNET_PQ_query_param_end }; struct GNUNET_PQ_ResultSpec rs[] = { GNUNET_PQ_result_spec_json ("jnew_rules", jrules), - GNUNET_PQ_result_spec_timestamp ("expiration_time", - expiration_time), GNUNET_PQ_result_spec_end }; @@ -50,9 +51,9 @@ TEH_PG_get_kyc_rules ( "get_kyc_rules", "SELECT" " jnew_rules" - " ,expiration_time" " FROM legitimization_outcomes" " WHERE h_payto=$1" + " AND expiration_time >= $2" " AND is_active;"); return GNUNET_PQ_eval_prepared_singleton_select ( pg->conn, diff --git a/src/exchangedb/pg_get_kyc_rules.h b/src/exchangedb/pg_get_kyc_rules.h @@ -31,7 +31,6 @@ * * @param cls the @e cls of this struct with the plugin-specific state * @param h_payto account identifier - * @param[out] expiration_time when do the @a jrules expire * @param[out] jrules set to the active KYC rules for the * given account, set to NULL if no custom rules are active * @return transaction status code @@ -40,7 +39,6 @@ enum GNUNET_DB_QueryStatus TEH_PG_get_kyc_rules ( void *cls, const struct TALER_PaytoHashP *h_payto, - struct GNUNET_TIME_Timestamp *expiration_time, json_t **jrules); #endif diff --git a/src/include/taler_exchangedb_plugin.h b/src/include/taler_exchangedb_plugin.h @@ -6866,7 +6866,6 @@ struct TALER_EXCHANGEDB_Plugin * * @param cls the @e cls of this struct with the plugin-specific state * @param h_payto account identifier - * @param[out] expiration_time when do the @a jrules expire * @param[out] jrules set to the active KYC rules for the * given account, set to NULL if no custom rules are active * @return transaction status code @@ -6875,7 +6874,6 @@ struct TALER_EXCHANGEDB_Plugin (*get_kyc_rules)( void *cls, const struct TALER_PaytoHashP *h_payto, - struct GNUNET_TIME_Timestamp *expiration_time, json_t **jrules); diff --git a/src/include/taler_kyclogic_lib.h b/src/include/taler_kyclogic_lib.h @@ -222,6 +222,7 @@ TALER_KYCLOGIC_rule2j (struct TALER_KYCLOGIC_KycRule *r); uint32_t TALER_KYCLOGIC_rule2priority (struct TALER_KYCLOGIC_KycRule *r); + /** * Iterate over all thresholds that are applicable to a particular type of @a * event under exposed global rules. @@ -258,8 +259,9 @@ TALER_KYCLOGIC_is_satisfiable ( * FIXME: we probably want to instead set up the logic * with the context instead of just returning it here! * - * @param requirements space-separated list of required checks - * @param ut type of the entity performing the check + * @param lrs rule set + * @param kyc_rule rule that was triggered + * @param measure_name selected measure * @param[out] plugin set to the KYC logic API * @param[out] pd set to the specific operation context * @param[out] configuration_section set to the name of the KYC logic configuration section * @return #GNUNET_OK on success @@ -311,4 +313,39 @@ TALER_KYCLOGIC_lookup_checks ( char ***provided_checks); +/** + * Function called with the provider details and + * associated plugin closures for matching logics. + * + * @param cls closure + * @param pd provider details of a matching logic + * @param plugin_cls closure of the plugin + * @return #GNUNET_OK to continue to iterate + */ +typedef enum GNUNET_GenericReturnValue +(*TALER_KYCLOGIC_DetailsCallback)( + void *cls, + const struct TALER_KYCLOGIC_ProviderDetails *pd, + void *plugin_cls); + + +/** + * Call @a cb for all logics with name @a logic_name, + * providing the plugin closure and the @a pd configurations. + * Obtain the provider logic for a given set of @a lrs + * and a specific @a kyc_rule from @a lrs that was + * triggered and the choosen @a measure_name from the + * list of measures of that @a kyc_rule. + * + * @param logic_name name of the logic to match + * @param cb function to call on matching results + * @param cb_cls closure for @a cb + */ +void +TALER_KYCLOGIC_kyc_get_details ( + const char *logic_name, + TALER_KYCLOGIC_DetailsCallback cb, + void *cb_cls); + + #endif diff --git a/src/kyclogic/kyclogic_api.c b/src/kyclogic/kyclogic_api.c @@ -216,6 +216,11 @@ struct TALER_KYCLOGIC_KycRule unsigned int num_measures; /** + * Display priority for this rule. + */ + uint32_t display_priority; + + /** * What operation type is this rule for? */ enum TALER_KYCLOGIC_KycTriggerEvent trigger; @@ -279,27 +284,24 @@ struct TALER_KYCLOGIC_LegitimizationRuleSet */ unsigned int num_custom_measures; - /** - * Display priority for this rule. - */ - uint32_t display_priority; }; -enum GNUNET_GenericReturnValue -TALER_KYCLOGIC_rules_parse ( - const json_t *jrules, - struct TALER_KYCLOGIC_KycRuleSet *rules) +struct TALER_KYCLOGIC_LegitimizationRuleSet * +TALER_KYCLOGIC_rules_parse (const json_t *jrules) { // FIXME! - return GNUNET_SYSERR; + GNUNET_break (0); + return NULL; } void -TALER_KYCLOGIC_rules_free (struct TALER_KYCLOGIC_KycRuleSet *krs) +TALER_KYCLOGIC_rules_free (struct TALER_KYCLOGIC_LegitimizationRuleSet *krs) { - // fIXME + // FIXME + GNUNET_break (0); + GNUNET_free (krs); } @@ -310,6 +312,15 @@ TALER_KYCLOGIC_rule2s (struct TALER_KYCLOGIC_KycRule *r) } +json_t * +TALER_KYCLOGIC_rule2j (struct TALER_KYCLOGIC_KycRule *r) +{ + // FIXME! + GNUNET_break (0); + return NULL; +} + + uint32_t TALER_KYCLOGIC_rule2priority (struct TALER_KYCLOGIC_KycRule *r) { @@ -401,7 +412,7 @@ static unsigned int num_kyc_checks; /** * Rules that apply if we do not have an AMLA record. */ -static struct TALER_KYCLOGIC_KycRuleSet default_rules; +static struct TALER_KYCLOGIC_LegitimizationRuleSet default_rules; /** * Array of available AML programs. @@ -1148,8 +1159,8 @@ add_rule (const struct GNUNET_CONFIGURATION_Handle *cfg, &kt->next_measures, &kt->num_measures); GNUNET_free (measures); - GNUNET_array_append (kyc_rules, - num_kyc_rules, + GNUNET_array_append (default_rules.kyc_rules, + default_rules.num_kyc_rules, kt); } return GNUNET_OK; @@ -1370,9 +1381,9 @@ TALER_KYCLOGIC_kyc_init (const struct GNUNET_CONFIGURATION_Handle *cfg) return GNUNET_SYSERR; } - if (0 != num_kyc_rules) - qsort (kyc_rules, - num_kyc_rules, + if (0 != default_rules.num_kyc_rules) + qsort (default_rules.kyc_rules, + default_rules.num_kyc_rules, sizeof (struct TALER_KYCLOGIC_KycRule *), &sort_by_timeframe); // FIXME: add configuration sanity checking! @@ -1383,9 +1394,10 @@ TALER_KYCLOGIC_kyc_init (const struct GNUNET_CONFIGURATION_Handle *cfg) void TALER_KYCLOGIC_kyc_done (void) { - for (unsigned int i = 0; i<num_kyc_rules; i++) + for (unsigned int i = 0; i<default_rules.num_kyc_rules; i++) { - struct TALER_KYCLOGIC_KycRule *kt = kyc_rules[i]; + struct TALER_KYCLOGIC_KycRule *kt + = default_rules.kyc_rules[i]; for (unsigned int j = 0; j<kt->num_measures; j++) GNUNET_free (kt->next_measures[j]); @@ -1395,8 +1407,8 @@ TALER_KYCLOGIC_kyc_done (void) GNUNET_free (kt->rule_name); GNUNET_free (kt); } - GNUNET_array_grow (kyc_rules, - num_kyc_rules, + GNUNET_array_grow (default_rules.kyc_rules, + default_rules.num_kyc_rules, 0); for (unsigned int i = 0; i<num_kyc_providers; i++) { @@ -1482,240 +1494,161 @@ TALER_KYCLOGIC_kyc_done (void) } -/* end of kyclogic_api.c */ - -#if 0 -// FIXME from here... - - -/** - * Closure for the #eval_trigger(). - */ -struct ThresholdTestContext +enum GNUNET_GenericReturnValue +TALER_KYCLOGIC_requirements_to_logic ( + const struct TALER_KYCLOGIC_LegitimizationRuleSet *lrs, + const struct TALER_KYCLOGIC_KycRule *kyc_rule, + const char *measure_name, + struct TALER_KYCLOGIC_Plugin **plugin, + struct TALER_KYCLOGIC_ProviderDetails **pd, + const char **configuration_section) { - /** - * 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; -}; +#if FIXME + 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); -/** - * 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; + for (const char *tok = strtok (req, " "); + NULL != tok; + tok = strtok (NULL, " ")) + needed[needed_cnt++] = add_check (tok); + GNUNET_free (req); + } - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "KYC check with new amount %s\n", - TALER_amount2s (amount)); - duration = GNUNET_TIME_absolute_get_duration (date); - if (ttc->have_total) + /* Count maximum number of remaining checks covered by any + provider */ + for (unsigned int i = 0; i<num_kyc_providers; i++) { - if (0 > - TALER_amount_add (&ttc->total, - &ttc->total, - amount)) + 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++) { - GNUNET_break (0); - return GNUNET_SYSERR; + const struct TALER_KYCLOGIC_KycCheck *kc = kp->provided_checks[j]; + + for (unsigned int k = 0; k<needed_cnt; k++) + if (kc == needed[k]) + { + matched++; + break; + } } + max_checks = GNUNET_MAX (max_checks, + matched); } - else - { - ttc->total = *amount; - ttc->have_total = true; - } - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "KYC check: new total is %s\n", - TALER_amount2s (&ttc->total)); - for (unsigned int i = ttc->start; i<num_kyc_triggers; i++) + if (0 == max_checks) + return GNUNET_SYSERR; + + /* Find min-cost provider covering max_checks. */ + for (unsigned int i = 0; i<num_kyc_providers; i++) { - const struct TALER_KYCLOGIC_KycTrigger *kt = kyc_triggers[i]; + const struct TALER_KYCLOGIC_KycProvider *kp = kyc_providers[i]; + unsigned int matched = 0; - if (ttc->event != kt->trigger) - { - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "KYC check #%u: trigger type does not match\n", - i); + if (kp->user_type != ut) continue; - } - duration = GNUNET_TIME_relative_max (duration, - kt->timeframe); - if (GNUNET_TIME_relative_cmp (kt->timeframe, - >, - duration)) - { - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "KYC check #%u: amount is beyond time limit\n", - i); - if (bump) - ttc->start = i; - return GNUNET_OK; - } - if (-1 == - TALER_amount_cmp (&ttc->total, - &kt->threshold)) - { - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "KYC check #%u: amount is below threshold\n", - i); - 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; j<kt->num_checks; j++) + for (unsigned int j = 0; j<kp->num_checks; j++) { - struct TALER_KYCLOGIC_KycCheck *rc = kt->required_checks[j]; - bool found = false; + const struct TALER_KYCLOGIC_KycCheck *kc = kp->provided_checks[j]; - for (unsigned int k = 0; k<*ttc->needed_cnt; k++) - if (ttc->needed[k] == rc) + for (unsigned int k = 0; k<needed_cnt; k++) + if (kc == needed[k]) { - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "KYC rule #%u already listed\n", - j); - found = true; + matched++; break; } - if (! found) - { - ttc->needed[*ttc->needed_cnt] = rc; - (*ttc->needed_cnt)++; - } } - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "KYC check #%u (%s) is applicable, %u checks needed so far\n", - i, - ttc->needed[(*ttc->needed_cnt) - 1]->name, - *ttc->needed_cnt); + if ( (max_checks == matched) && + (kp->cost < min_cost) ) + { + min_cost = kp->cost; + kp_best = kp; + } } - if (bump) - return GNUNET_NO; /* we hit all possible triggers! */ + GNUNET_assert (NULL != kp_best); + *plugin = kp_best->logic; + *pd = kp_best->pd; + *configuration_section = kp_best->provider_section_name; return GNUNET_OK; +#else + GNUNET_break (0); + return GNUNET_SYSERR; +#endif } -/** - * Closure for the #remove_satisfied(). - */ -struct RemoveContext +enum GNUNET_GenericReturnValue +TALER_KYCLOGIC_lookup_logic ( + const char *name, + struct TALER_KYCLOGIC_Plugin **plugin, + struct TALER_KYCLOGIC_ProviderDetails **pd, + const char **provider_section) { +#if FIXME + for (unsigned int i = 0; i<num_kyc_providers; i++) + { + struct TALER_KYCLOGIC_KycProvider *kp = kyc_providers[i]; - /** - * Array of checks needed so far. - */ - struct TALER_KYCLOGIC_KycCheck **needed; + if (0 != + 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]; - /** - * Pointer to number of entries used in @a needed. - */ - unsigned int *needed_cnt; - - /** - * Object with information about collected KYC data. - */ - json_t *kyc_details; -}; + 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", + name); +#else + GNUNET_break (0); +#endif + return GNUNET_SYSERR; +} -/** - * 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) +void +TALER_KYCLOGIC_kyc_get_details ( + const char *logic_name, + TALER_KYCLOGIC_DetailsCallback cb, + void *cb_cls) { - struct RemoveContext *rc = cls; - for (unsigned int i = 0; i<num_kyc_providers; i++) { - const struct TALER_KYCLOGIC_KycProvider *kp = kyc_providers[i]; + struct TALER_KYCLOGIC_KycProvider *kp = kyc_providers[i]; - if (0 != strcasecmp (provider_name, - kp->provider_section_name)) + if (0 != + strcmp (kp->logic->name, + logic_name)) continue; - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Provider `%s' satisfied\n", - provider_name); - for (unsigned int j = 0; j<kp->num_checks; j++) - { - const struct TALER_KYCLOGIC_KycCheck *kc = kp->provided_checks[j]; - - 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]) - { - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Removing check `%s' from list\n", - kc->name); - rc->needed[k] = rc->needed[*rc->needed_cnt - 1]; - (*rc->needed_cnt)--; - if (0 == *rc->needed_cnt) - return; /* for sure finished */ - break; - } - } - break; + if (GNUNET_OK != + cb (cb_cls, + kp->pd, + kp->logic->cls)) + return; } } @@ -1724,12 +1657,12 @@ 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, + const struct TALER_KYCLOGIC_LegitimizationRuleSet *lrs, TALER_KYCLOGIC_KycAmountIterator ai, void *ai_cls, - char **required) + struct TALER_KYCLOGIC_KycRule **triggered_rule) { +#if FIXME struct TALER_KYCLOGIC_KycCheck *needed[num_kyc_checks]; unsigned int needed_cnt = 0; char *ret; @@ -1865,28 +1798,247 @@ TALER_KYCLOGIC_kyc_test_required ( } *required = ret; return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT; +#else + GNUNET_break (0); + return GNUNET_DB_STATUS_HARD_ERROR; +#endif } -void -TALER_KYCLOGIC_kyc_get_details ( - const char *logic_name, - TALER_KYCLOGIC_DetailsCallback cb, - void *cb_cls) +/* end of kyclogic_api.c */ + +#if 0 +// FIXME from here... + + +/** + * 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; + + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "KYC check with new amount %s\n", + TALER_amount2s (amount)); + 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; + ttc->have_total = true; + } + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "KYC check: new total is %s\n", + TALER_amount2s (&ttc->total)); + for (unsigned int i = ttc->start; i<num_kyc_triggers; i++) + { + const struct TALER_KYCLOGIC_KycTrigger *kt = kyc_triggers[i]; + + if (ttc->event != kt->trigger) + { + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "KYC check #%u: trigger type does not match\n", + i); + continue; + } + duration = GNUNET_TIME_relative_max (duration, + kt->timeframe); + if (GNUNET_TIME_relative_cmp (kt->timeframe, + >, + duration)) + { + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "KYC check #%u: amount is beyond time limit\n", + i); + if (bump) + ttc->start = i; + return GNUNET_OK; + } + if (-1 == + TALER_amount_cmp (&ttc->total, + &kt->threshold)) + { + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "KYC check #%u: amount is below threshold\n", + i); + 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; j<kt->num_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) + { + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "KYC rule #%u already listed\n", + j); + found = true; + break; + } + if (! found) + { + ttc->needed[*ttc->needed_cnt] = rc; + (*ttc->needed_cnt)++; + } + } + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "KYC check #%u (%s) is applicable, %u checks needed so far\n", + i, + ttc->needed[(*ttc->needed_cnt) - 1]->name, + *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; + + /** + * Object with information about collected KYC data. + */ + json_t *kyc_details; +}; + + +/** + * 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; i<num_kyc_providers; i++) { - struct TALER_KYCLOGIC_KycProvider *kp = kyc_providers[i]; + const struct TALER_KYCLOGIC_KycProvider *kp = kyc_providers[i]; - if (0 != - strcmp (kp->logic->name, - logic_name)) + if (0 != strcasecmp (provider_name, + kp->provider_section_name)) continue; - if (GNUNET_OK != - cb (cb_cls, - kp->pd, - kp->logic->cls)) - return; + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Provider `%s' satisfied\n", + provider_name); + for (unsigned int j = 0; j<kp->num_checks; j++) + { + const struct TALER_KYCLOGIC_KycCheck *kc = kp->provided_checks[j]; + + 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]) + { + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Removing check `%s' from list\n", + kc->name); + rc->needed[k] = rc->needed[*rc->needed_cnt - 1]; + (*rc->needed_cnt)--; + if (0 == *rc->needed_cnt) + return; /* for sure finished */ + break; + } + } + break; } } @@ -1982,132 +2134,6 @@ TALER_KYCLOGIC_check_satisfied ( } -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 */ - for (unsigned int i = 0; i<num_kyc_providers; i++) - { - 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]; - - for (unsigned int k = 0; k<needed_cnt; k++) - if (kc == needed[k]) - { - matched++; - break; - } - } - 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++) - { - 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]; - - for (unsigned int k = 0; k<needed_cnt; k++) - if (kc == needed[k]) - { - matched++; - break; - } - } - if ( (max_checks == matched) && - (kp->cost < min_cost) ) - { - min_cost = kp->cost; - kp_best = kp; - } - } - GNUNET_assert (NULL != kp_best); - *plugin = kp_best->logic; - *pd = kp_best->pd; - *configuration_section = kp_best->provider_section_name; - return GNUNET_OK; -} - - -enum GNUNET_GenericReturnValue -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 (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", - name); - return GNUNET_SYSERR; -} - - void TALER_KYCLOGIC_kyc_iterate_thresholds ( enum TALER_KYCLOGIC_KycTriggerEvent event, diff --git a/src/kyclogic/taler-exchange-kyc-tester.c b/src/kyclogic/taler-exchange-kyc-tester.c @@ -278,9 +278,9 @@ static int run_webservice; static int global_ret; /** - * -r command-line flag. + * -m command-line flag. */ -static char *requirements; +static char *measure; /** * Handle for ongoing initiation operation. @@ -1466,19 +1466,21 @@ run (void *cls, return; } global_ret = EXIT_SUCCESS; - if (NULL != requirements) + if (NULL != measure) { struct TALER_KYCLOGIC_ProviderDetails *pd; if (GNUNET_OK != - TALER_KYCLOGIC_requirements_to_logic (requirements, + TALER_KYCLOGIC_requirements_to_logic (NULL, /* FIXME! */ + NULL, /* FIXME! */ + measure, &ih_logic, &pd, &provider_section_name)) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Could not initiate KYC for requirements `%s' (configuration error?)\n", - requirements); + "Could not initiate KYC for measure `%s' (configuration error?)\n", + measure); global_ret = EXIT_NOTCONFIGURED; GNUNET_SCHEDULER_shutdown (); return; @@ -1575,11 +1577,11 @@ main (int argc, "run the integrated HTTP service", &run_webservice), GNUNET_GETOPT_option_string ( - 'R', - "requirements", - "CHECKS", - "initiate KYC check for the given list of (space-separated) checks", - &requirements), + 'm', + "measure", + "MEASURE_NAME", + "initiate KYC check for the selected measure", + &measure), GNUNET_GETOPT_option_string ( 'u', "user",