exchange

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

commit 483c223a856b6d37bb601c4d0a626449da1cd085
parent 80570d423916695d20701b4d4a70aefaca0bfcbe
Author: Christian Grothoff <christian@grothoff.org>
Date:   Tue,  3 Sep 2024 01:01:58 +0200

replace requirement row with H_PAYTO

Diffstat:
Msrc/auditordb/plugin_auditordb_postgres.c | 8++++++++
Msrc/exchange/taler-exchange-httpd_kyc-check.c | 123+++++++++++++++++++++++++++++--------------------------------------------------
Msrc/exchangedb/exchange_do_insert_aml_decision.sql | 4++--
Msrc/exchangedb/exchange_do_lookup_kyc_requirement_by_row.sql | 33++++++++++++++-------------------
Msrc/exchangedb/pg_lookup_kyc_requirement_by_row.c | 4++--
Msrc/exchangedb/pg_lookup_kyc_requirement_by_row.h | 4++--
Msrc/exchangedb/plugin_exchangedb_postgres.c | 8++++++++
Msrc/extensions/age_restriction/age_restriction.c | 8++++++++
Msrc/include/taler_exchange_service.h | 17+++++++++++++++--
Msrc/include/taler_exchangedb_plugin.h | 32++++++++++++++++----------------
Msrc/lib/exchange_api_kyc_check.c | 38+++++++++++++++++++++++++++++++-------
Msrc/testing/testing_api_cmd_kyc_check_get.c | 10+++++-----
Msrc/testing/testing_api_cmd_kyc_wallet_get.c | 4++--
Msrc/testing/testing_api_cmd_purse_merge.c | 4++++
Msrc/testing/testing_api_cmd_reserve_purse.c | 4++++
15 files changed, 166 insertions(+), 135 deletions(-)

diff --git a/src/auditordb/plugin_auditordb_postgres.c b/src/auditordb/plugin_auditordb_postgres.c @@ -551,6 +551,10 @@ postgres_gc (void *cls) * @return NULL on error, otherwise a `struct TALER_AUDITORDB_Plugin` */ void * +libtaler_plugin_auditordb_postgres_init (void *cls); + +/* Declaration used to squash compiler warning */ +void * libtaler_plugin_auditordb_postgres_init (void *cls) { const struct GNUNET_CONFIGURATION_Handle *cfg = cls; @@ -851,6 +855,10 @@ libtaler_plugin_auditordb_postgres_init (void *cls) * @return NULL (always) */ void * +libtaler_plugin_auditordb_postgres_done (void *cls); + +/* Declaration used to squash compiler warning */ +void * libtaler_plugin_auditordb_postgres_done (void *cls) { struct TALER_AUDITORDB_Plugin *plugin = cls; diff --git a/src/exchange/taler-exchange-httpd_kyc-check.c b/src/exchange/taler-exchange-httpd_kyc-check.c @@ -62,9 +62,9 @@ struct KycPoller struct GNUNET_DB_EventHandler *eh; /** - * Row of the requirement being checked. + * Account for which we perform the KYC check. */ - uint64_t requirement_row; + struct TALER_PaytoHashP h_payto; /** * When will this request time out? @@ -202,24 +202,18 @@ TEH_handler_kyc_check ( rc->rh_ctx = kyp; rc->rh_cleaner = &kyp_cleanup; + if (GNUNET_OK != + GNUNET_STRINGS_string_to_data (args[0], + strlen (args[0]), + &kyp->h_payto, + sizeof (kyp->h_payto))) { - unsigned long long requirement_row; - char dummy; - - if (1 != - sscanf (args[0], - "%llu%c", - &requirement_row, - &dummy)) - { - GNUNET_break_op (0); - return TALER_MHD_reply_with_error ( - rc->connection, - MHD_HTTP_BAD_REQUEST, - TALER_EC_GENERIC_PARAMETER_MALFORMED, - "requirement_row"); - } - kyp->requirement_row = (uint64_t) requirement_row; + GNUNET_break_op (0); + return TALER_MHD_reply_with_error ( + rc->connection, + MHD_HTTP_BAD_REQUEST, + TALER_EC_GENERIC_PATH_SEGMENT_MALFORMED, + "h_payto"); } TALER_MHD_parse_request_header_auto ( @@ -229,7 +223,26 @@ TEH_handler_kyc_check ( sig_required); TALER_MHD_parse_request_timeout (rc->connection, &kyp->timeout); - } + + /* long polling needed? */ + if (GNUNET_TIME_absolute_is_future (kyp->timeout)) + { + struct TALER_KycCompletedEventP rep = { + .header.size = htons (sizeof (rep)), + .header.type = htons (TALER_DBEVENT_EXCHANGE_KYC_COMPLETED), + .h_payto = kyp->h_payto + }; + + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Starting DB event listening\n"); + kyp->eh = TEH_plugin->event_listen ( + TEH_plugin->cls, + GNUNET_TIME_absolute_get_remaining (kyp->timeout), + &rep.header, + &db_event_cb, + rc); + } + } /* end initialization */ if (! TEH_enable_kyc) { @@ -247,11 +260,11 @@ TEH_handler_kyc_check ( enum GNUNET_DB_QueryStatus qs; GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Looking up KYC requirements by row %llu\n", - (unsigned long long) kyp->requirement_row); + "Looking up KYC requirements for account %s\n", + TALER_B2S (&kyp->h_payto)); qs = TEH_plugin->lookup_kyc_requirement_by_row ( TEH_plugin->cls, - kyp->requirement_row, + &kyp->h_payto, &account_pub, &reserve_pub.reserve_pub, &access_token, @@ -286,9 +299,6 @@ TEH_handler_kyc_check ( TALER_account_kyc_auth_verify (&reserve_pub, &kyp->account_sig)) ) ) { - char *diag; - MHD_RESULT mret; - json_decref (jrules); jrules = NULL; if (GNUNET_is_zero (&account_pub)) @@ -300,15 +310,13 @@ TEH_handler_kyc_check ( TALER_EC_EXCHANGE_KYC_CHECK_AUTHORIZATION_KEY_UNKNOWN, NULL); } - diag = GNUNET_STRINGS_data_to_string_alloc (&account_pub, - sizeof (account_pub)); - mret = TALER_MHD_reply_with_error ( + return TALER_MHD_REPLY_JSON_PACK ( rc->connection, MHD_HTTP_FORBIDDEN, - TALER_EC_EXCHANGE_KYC_CHECK_AUTHORIZATION_FAILED, - diag); - GNUNET_free (diag); - return mret; + TALER_JSON_pack_ec ( + TALER_EC_EXCHANGE_KYC_CHECK_AUTHORIZATION_FAILED), + GNUNET_JSON_pack_data_auto ("expected_account_pub", + &account_pub)); } jlimits = TALER_KYCLOGIC_rules_to_limits (jrules); @@ -326,47 +334,9 @@ TEH_handler_kyc_check ( json_decref (jrules); jrules = NULL; - /* long polling for positive result? */ - if (kyc_required && - GNUNET_TIME_absolute_is_future (kyp->timeout)) + if ( (kyc_required) && + GNUNET_TIME_absolute_is_future (kyp->timeout)) { - enum GNUNET_DB_QueryStatus qs; - struct TALER_KycCompletedEventP rep = { - .header.size = htons (sizeof (rep)), - .header.type = htons (TALER_DBEVENT_EXCHANGE_KYC_COMPLETED), - }; - - json_decref (jlimits); - if (NULL == kyp->eh) - { - /* FIXME-Performance: consider modifying lookup_kyc_requirement_by_row - to immediately return h_payto as well... */ - qs = TEH_plugin->lookup_h_payto_by_access_token ( - TEH_plugin->cls, - &access_token, - &rep.h_payto); - if (qs < 0) - { - GNUNET_break (0); - return TALER_MHD_reply_with_ec ( - rc->connection, - TALER_EC_GENERIC_DB_FETCH_FAILED, - "lookup_h_payto_by_access_token"); - } - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Starting DB event listening\n"); - kyp->eh = TEH_plugin->event_listen ( - TEH_plugin->cls, - GNUNET_TIME_absolute_get_remaining (kyp->timeout), - &rep.header, - &db_event_cb, - rc); - /* goes again *immediately* (without suspending) - now that long-poller is in place; we will suspend - in the *next* iteration. */ - return MHD_YES; - } - GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Suspending HTTP request on timeout (%s) now...\n", GNUNET_TIME_relative2s (GNUNET_TIME_absolute_get_remaining ( @@ -380,12 +350,9 @@ TEH_handler_kyc_check ( MHD_suspend_connection (kyp->connection); return MHD_YES; } - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Returning KYC %s for row %llu\n", - kyc_required ? "required" : "optional", - (unsigned long long) kyp->requirement_row); - + "Returning KYC %s\n", + kyc_required ? "required" : "optional"); return TALER_MHD_REPLY_JSON_PACK ( rc->connection, kyc_required diff --git a/src/exchangedb/exchange_do_insert_aml_decision.sql b/src/exchangedb/exchange_do_insert_aml_decision.sql @@ -79,8 +79,8 @@ ELSE out_last_date = 0; END IF; --- Note: in_payto_uri is allowed to be NULL *if* --- in_h_payto is already in wire_targets +-- FIXME-9156: need in_payto_uri *in* case +-- in_h_payto is not already in wire_targets! SELECT access_token INTO my_access_token FROM wire_targets diff --git a/src/exchangedb/exchange_do_lookup_kyc_requirement_by_row.sql b/src/exchangedb/exchange_do_lookup_kyc_requirement_by_row.sql @@ -15,8 +15,10 @@ -- -- @author: Christian Grothoff -CREATE OR REPLACE FUNCTION exchange_do_lookup_kyc_requirement_by_row( - IN in_legitimization_serial_id INT8, +DROP FUNCTION IF EXISTS exchange_do_lookup_kyc_requirement_by_row; + +CREATE FUNCTION exchange_do_lookup_kyc_requirement_by_row( + IN in_h_payto BYTEA, OUT out_account_pub BYTEA, -- NULL allowed OUT out_reserve_pub BYTEA, -- NULL allowed OUT out_access_token BYTEA, -- NULL if 'out_not_found' @@ -27,16 +29,16 @@ CREATE OR REPLACE FUNCTION exchange_do_lookup_kyc_requirement_by_row( LANGUAGE plpgsql AS $$ DECLARE - my_h_payto BYTEA; my_wtrec RECORD; my_lorec RECORD; BEGIN --- Find the access token. +-- Find the access token and the current account public key. SELECT access_token - INTO out_access_token - FROM legitimization_measures - WHERE legitimization_measure_serial_id=in_legitimization_serial_id; + ,target_pub + INTO my_wtrec + FROM wire_targets + WHERE wire_target_h_payto=in_h_payto; IF NOT FOUND THEN @@ -46,15 +48,8 @@ THEN END IF; out_not_found = FALSE; --- Find the payto hash and the current account public key. -SELECT target_pub - ,wire_target_h_payto - INTO my_wtrec - FROM wire_targets - WHERE access_token=out_access_token; - out_account_pub = my_wtrec.target_pub; -my_h_payto = my_wtrec.wire_target_h_payto; +out_access_token = my_wtrec.access_token; -- Check if there are active measures for the account. SELECT NOT is_finished @@ -75,7 +70,7 @@ SELECT jnew_rules ,to_investigate INTO my_lorec FROM legitimization_outcomes - WHERE h_payto=my_h_payto + WHERE h_payto=in_h_payto AND is_active; IF FOUND @@ -84,12 +79,12 @@ THEN out_aml_review=my_lorec.to_investigate; END IF; --- Get most recent reserve_in wire transfer, we also --- allow that one for authentication! +-- Check most recent reserve_in wire transfer, we also +-- allow that reserve public key for authentication! SELECT reserve_pub INTO out_reserve_pub FROM reserves_in - WHERE wire_source_h_payto=my_h_payto + WHERE wire_source_h_payto=in_h_payto ORDER BY execution_date DESC LIMIT 1; diff --git a/src/exchangedb/pg_lookup_kyc_requirement_by_row.c b/src/exchangedb/pg_lookup_kyc_requirement_by_row.c @@ -29,7 +29,7 @@ enum GNUNET_DB_QueryStatus TEH_PG_lookup_kyc_requirement_by_row ( void *cls, - uint64_t requirement_row, + const struct TALER_PaytoHashP *h_payto, union TALER_AccountPublicKeyP *account_pub, struct TALER_ReservePublicKeyP *reserve_pub, struct TALER_AccountAccessTokenP *access_token, @@ -39,7 +39,7 @@ TEH_PG_lookup_kyc_requirement_by_row ( { struct PostgresClosure *pg = cls; struct GNUNET_PQ_QueryParam params[] = { - GNUNET_PQ_query_param_uint64 (&requirement_row), + GNUNET_PQ_query_param_auto_from_type (h_payto), GNUNET_PQ_query_param_end }; bool not_found; diff --git a/src/exchangedb/pg_lookup_kyc_requirement_by_row.h b/src/exchangedb/pg_lookup_kyc_requirement_by_row.h @@ -30,7 +30,7 @@ * Lookup KYC requirement. * * @param cls closure - * @param requirement_row identifies requirement to look up (in legitimization_measures table) + * @param h_payto identifies account to look up requirement for * @param[out] account_pub set to public key of the account * needed to authorize access, all zeros if not known * @param[out] reserve_pub set to last reserve public key @@ -50,7 +50,7 @@ enum GNUNET_DB_QueryStatus TEH_PG_lookup_kyc_requirement_by_row ( void *cls, - uint64_t requirement_row, + const struct TALER_PaytoHashP *h_payto, union TALER_AccountPublicKeyP *account_pub, struct TALER_ReservePublicKeyP *reserve_pub, struct TALER_AccountAccessTokenP *access_token, diff --git a/src/exchangedb/plugin_exchangedb_postgres.c b/src/exchangedb/plugin_exchangedb_postgres.c @@ -327,6 +327,10 @@ TEH_PG_internal_setup (struct PostgresClosure *pg) * TALER_EXCHANGEDB_Plugin` */ void * +libtaler_plugin_exchangedb_postgres_init (void *cls); + +/* Declaration used to squash compiler warning */ +void * libtaler_plugin_exchangedb_postgres_init (void *cls) { const struct GNUNET_CONFIGURATION_Handle *cfg = cls; @@ -835,6 +839,10 @@ libtaler_plugin_exchangedb_postgres_init (void *cls) * @return NULL (always) */ void * +libtaler_plugin_exchangedb_postgres_done (void *cls); + +/* Declaration used to squash compiler warning */ +void * libtaler_plugin_exchangedb_postgres_done (void *cls) { struct TALER_EXCHANGEDB_Plugin *plugin = cls; diff --git a/src/extensions/age_restriction/age_restriction.c b/src/extensions/age_restriction/age_restriction.c @@ -164,6 +164,10 @@ struct TALER_Extension TE_age_restriction = { * @return pointer to TALER_Extension on success or NULL otherwise. */ void * +libtaler_extension_age_restriction_init (void *arg); + +/* Declaration used to squash compiler warning */ +void * libtaler_extension_age_restriction_init (void *arg) { const struct GNUNET_CONFIGURATION_Handle *cfg = arg; @@ -243,6 +247,10 @@ libtaler_extension_age_restriction_init (void *arg) * @return pointer to TALER_Extension on success or NULL otherwise. */ void * +libtaler_extension_age_restriction_done (void *arg); + +/* Declaration used to squash compiler warning */ +void * libtaler_extension_age_restriction_done (void *arg) { GNUNET_log (GNUNET_ERROR_TYPE_INFO, diff --git a/src/include/taler_exchange_service.h b/src/include/taler_exchange_service.h @@ -4391,6 +4391,19 @@ struct TALER_EXCHANGE_KycStatus */ struct TALER_EXCHANGE_AccountKycStatus accepted; + /** + * Request was forbidden. + */ + struct + { + + /** + * Account pub that would have been authorized. + */ + union TALER_AccountPublicKeyP expected_account_pub; + + } forbidden; + } details; }; @@ -4413,7 +4426,7 @@ typedef void * * @param ctx CURL context * @param url exchange base URL - * @param requirement_row number identifying the KYC requirement + * @param h_payto hash of the account the KYC check is about * @param pk private key to authorize the request with * @param timeout how long to wait for a positive KYC status * @param cb function to call with the result @@ -4424,7 +4437,7 @@ struct TALER_EXCHANGE_KycCheckHandle * TALER_EXCHANGE_kyc_check ( struct GNUNET_CURL_Context *ctx, const char *url, - uint64_t requirement_row, + const struct TALER_PaytoHashP *h_payto, const union TALER_AccountPrivateKeyP *pk, struct GNUNET_TIME_Relative timeout, TALER_EXCHANGE_KycStatusCallback cb, diff --git a/src/include/taler_exchangedb_plugin.h b/src/include/taler_exchangedb_plugin.h @@ -7017,27 +7017,27 @@ struct TALER_EXCHANGEDB_Plugin * Lookup KYC requirement. * * @param cls closure - * @param requirement_row identifies requirement to look up (in legitimization_measures table) - * @param[out] account_pub set to public key of the account - * needed to authorize access, all zeros if not known - * @param[out] reserve_pub set to last reserve public key - * used for a wire transfer from the account to the - * exchange; alternatively used to authorize access, - * all zeros if not known - * @param[out] access_token set to the access token to begin - * work on KYC processes for this account - * @param[out] jrules set to active ``LegitimizationRuleSet`` - * of the account impacted by the requirement - * @param[out] aml_review set to true if the account is under - * active review by AML staff - * @param[out] kyc_required set to true if the user must pass - * some KYC check before some previous operation may continue + * @param h_payto identifies account to look up requirement for + * @param[out] account_pub set to public key of the account + * needed to authorize access, all zeros if not known + * @param[out] reserve_pub set to last reserve public key + * used for a wire transfer from the account to the + * exchange; alternatively used to authorize access, + * all zeros if not known + * @param[out] access_token set to the access token to begin + * work on KYC processes for this account + * @param[out] jrules set to active ``LegitimizationRuleSet`` + * of the account impacted by the requirement + * @param[out] aml_review set to true if the account is under + * active review by AML staff + * @param[out] kyc_required set to true if the user must pass + * some KYC check before some previous operation may continue * @return database transaction status */ enum GNUNET_DB_QueryStatus (*lookup_kyc_requirement_by_row)( void *cls, - uint64_t requirement_row, + const struct TALER_PaytoHashP *h_payto, union TALER_AccountPublicKeyP *account_pub, struct TALER_ReservePublicKeyP *reserve_pub, struct TALER_AccountAccessTokenP *access_token, diff --git a/src/lib/exchange_api_kyc_check.c b/src/lib/exchange_api_kyc_check.c @@ -203,8 +203,28 @@ handle_kyc_check_finished (void *cls, (or API version conflict); just pass JSON reply to the application */ break; case MHD_HTTP_FORBIDDEN: - ks.hr.ec = TALER_JSON_get_error_code (j); - break; + { + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_fixed_auto ( + "expected_account_pub", + &ks.details.forbidden.expected_account_pub), + TALER_JSON_spec_ec ("code", + &ks.hr.ec), + GNUNET_JSON_spec_end () + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (j, + spec, + NULL, NULL)) + { + GNUNET_break_op (0); + ks.hr.http_status = 0; + ks.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; + break; + } + break; + } case MHD_HTTP_NOT_FOUND: ks.hr.ec = TALER_JSON_get_error_code (j); break; @@ -236,7 +256,7 @@ struct TALER_EXCHANGE_KycCheckHandle * TALER_EXCHANGE_kyc_check ( struct GNUNET_CURL_Context *ctx, const char *url, - uint64_t requirement_row, + const struct TALER_PaytoHashP *h_payto, const union TALER_AccountPrivateKeyP *account_priv, struct GNUNET_TIME_Relative timeout, TALER_EXCHANGE_KycStatusCallback cb, @@ -247,20 +267,24 @@ TALER_EXCHANGE_kyc_check ( char arg_str[128]; struct curl_slist *job_headers = NULL; unsigned long long tms; + char *hps; + hps = GNUNET_STRINGS_data_to_string_alloc (h_payto, + sizeof (*h_payto)); tms = timeout.rel_value_us / GNUNET_TIME_UNIT_MILLISECONDS.rel_value_us; if (0 != tms) GNUNET_snprintf (arg_str, sizeof (arg_str), - "kyc-check/%llu?timeout_ms=%llu", - (unsigned long long) requirement_row, + "kyc-check/%s?timeout_ms=%llu", + hps, tms); else GNUNET_snprintf (arg_str, sizeof (arg_str), - "kyc-check/%llu", - (unsigned long long) requirement_row); + "kyc-check/%s", + hps); + GNUNET_free (hps); kch = GNUNET_new (struct TALER_EXCHANGE_KycCheckHandle); kch->cb = cb; kch->cb_cls = cb_cls; diff --git a/src/testing/testing_api_cmd_kyc_check_get.c b/src/testing/testing_api_cmd_kyc_check_get.c @@ -120,7 +120,7 @@ check_kyc_run (void *cls, struct KycCheckGetState *kcg = cls; const struct TALER_TESTING_Command *res_cmd; const struct TALER_TESTING_Command *acc_cmd; - const uint64_t *requirement_row; + const struct TALER_PaytoHashP *h_payto; const union TALER_AccountPrivateKeyP *account_priv; (void) cmd; @@ -144,9 +144,9 @@ check_kyc_run (void *cls, return; } if (GNUNET_OK != - TALER_TESTING_get_trait_legi_requirement_row ( + TALER_TESTING_get_trait_h_payto ( res_cmd, - &requirement_row)) + &h_payto)) { GNUNET_break (0); TALER_TESTING_interpreter_fail (kcg->is); @@ -160,7 +160,7 @@ check_kyc_run (void *cls, TALER_TESTING_interpreter_fail (kcg->is); return; } - if (0 == *requirement_row) + if (0 == h_payto) { GNUNET_break (0); TALER_TESTING_interpreter_fail (kcg->is); @@ -169,7 +169,7 @@ check_kyc_run (void *cls, kcg->kwh = TALER_EXCHANGE_kyc_check ( TALER_TESTING_interpreter_get_context (is), TALER_TESTING_get_exchange_url (is), - *requirement_row, + h_payto, account_priv, GNUNET_TIME_UNIT_ZERO, &check_kyc_cb, diff --git a/src/testing/testing_api_cmd_kyc_wallet_get.c b/src/testing/testing_api_cmd_kyc_wallet_get.c @@ -65,13 +65,13 @@ struct KycWalletGetState /** * Set to the KYC requirement payto hash *if* the exchange replied with a - * request for KYC (#MHD_HTTP_OK). + * request for KYC (#MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS). */ struct TALER_PaytoHashP h_payto; /** * Set to the KYC requirement row *if* the exchange replied with - * a request for KYC (#MHD_HTTP_OK). + * request for KYC (#MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS). */ uint64_t requirement_row; diff --git a/src/testing/testing_api_cmd_purse_merge.c b/src/testing/testing_api_cmd_purse_merge.c @@ -171,6 +171,10 @@ merge_cb (void *cls, /* KYC required */ ds->requirement_row = dr->details.unavailable_for_legal_reasons.requirement_row; + GNUNET_break (0 == + GNUNET_memcmp ( + &ds->h_payto, + &dr->details.unavailable_for_legal_reasons.h_payto)); break; } diff --git a/src/testing/testing_api_cmd_reserve_purse.c b/src/testing/testing_api_cmd_reserve_purse.c @@ -164,6 +164,10 @@ purse_cb (void *cls, /* KYC required */ ds->requirement_row = dr->details.unavailable_for_legal_reasons.requirement_row; + GNUNET_break (0 == + GNUNET_memcmp ( + &ds->h_payto, + &dr->details.unavailable_for_legal_reasons.h_payto)); break; } TALER_TESTING_interpreter_next (ds->is);