exchange

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

commit 1346ff888defd446dd1cffefa1ddf3890a373591
parent 007ce3bcb9f0406a6b833bff2ae9463bc390bc33
Author: Christian Grothoff <christian@grothoff.org>
Date:   Thu, 25 Jul 2024 22:23:51 +0200

-fix age withdraw test

Diffstat:
Msrc/exchange/taler-exchange-httpd_withdraw.c | 11++++++-----
Msrc/include/taler_exchange_service.h | 19+++++++++++++++++++
Msrc/include/taler_kyclogic_lib.h | 6+-----
Msrc/json/json_helper.c | 4++--
Msrc/kyclogic/kyclogic_api.c | 3---
Msrc/lib/exchange_api_age_withdraw.c | 111+++++++++++++++++++++++++++++++++++++++++--------------------------------------
Msrc/lib/exchange_api_age_withdraw_reveal.c | 82++++++++++++++++++++++++++++++++++++++++++++++++++-----------------------------
Msrc/testing/test_exchange_api_age_restriction.c | 10+++++-----
Msrc/testing/test_exchange_api_age_restriction.conf | 131+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------------------
9 files changed, 244 insertions(+), 133 deletions(-)

diff --git a/src/exchange/taler-exchange-httpd_withdraw.c b/src/exchange/taler-exchange-httpd_withdraw.c @@ -238,10 +238,11 @@ TEH_withdraw_kyc_check ( 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"); + *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, @@ -254,7 +255,7 @@ TEH_withdraw_kyc_check ( kyc, connection, mhd_ret, - TALER_KYCLOGIC_KYC_TRIGGER_AGE_WITHDRAW, + TALER_KYCLOGIC_KYC_TRIGGER_WITHDRAW, payto_uri, &wc.h_payto, &withdraw_amount_cb, diff --git a/src/include/taler_exchange_service.h b/src/include/taler_exchange_service.h @@ -3000,6 +3000,12 @@ struct TALER_EXCHANGE_AgeWithdrawResponse */ struct TALER_ExchangePublicKeyP exchange_pub; } ok; + + /** + * Details if the status is #MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS. + */ + struct TALER_EXCHANGE_KycNeededRedirect unavailable_for_legal_reasons; + } details; }; @@ -3112,6 +3118,13 @@ struct TALER_EXCHANGE_AgeWithdrawBlindedResponse struct TALER_ExchangePublicKeyP exchange_pub; } ok; + + + /** + * Details if the status is #MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS. + */ + struct TALER_EXCHANGE_KycNeededRedirect unavailable_for_legal_reasons; + } details; }; @@ -3232,6 +3245,12 @@ struct TALER_EXCHANGE_AgeWithdrawRevealResponse const struct TALER_BlindedDenominationSignature *blinded_denom_sigs; } ok; + + /** + * Details if the status is #MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS. + */ + struct TALER_EXCHANGE_KycNeededRedirect unavailable_for_legal_reasons; + } details; }; diff --git a/src/include/taler_kyclogic_lib.h b/src/include/taler_kyclogic_lib.h @@ -61,12 +61,8 @@ enum TALER_KYCLOGIC_KycTriggerEvent /** * Reserve is being closed by force. */ - TALER_KYCLOGIC_KYC_TRIGGER_RESERVE_CLOSE = 5, + TALER_KYCLOGIC_KYC_TRIGGER_RESERVE_CLOSE = 5 - /** - * Customer withdraws coins via age-withdraw. - */ - TALER_KYCLOGIC_KYC_TRIGGER_AGE_WITHDRAW = 6, }; diff --git a/src/json/json_helper.c b/src/json/json_helper.c @@ -531,6 +531,7 @@ TALER_JSON_spec_age_commitment (const char *name, return ret; } + struct GNUNET_JSON_Specification TALER_JSON_spec_token_issue_sig (const char *field, struct TALER_TokenIssueSignatureP *sig) @@ -540,6 +541,7 @@ TALER_JSON_spec_token_issue_sig (const char *field, &sig->signature); } + struct GNUNET_JSON_Specification TALER_JSON_spec_blinded_token_issue_sig ( const char *field, @@ -1471,8 +1473,6 @@ parse_kycte (void *cls, .val = TALER_KYCLOGIC_KYC_TRIGGER_WALLET_BALANCE }, { .name = "RESERVE-CLOSE", .val = TALER_KYCLOGIC_KYC_TRIGGER_RESERVE_CLOSE }, - { .name = "AGE-WITHDRAW", - .val = TALER_KYCLOGIC_KYC_TRIGGER_AGE_WITHDRAW }, { .name = NULL, .val = TALER_KYCLOGIC_KYC_TRIGGER_NONE }, }; diff --git a/src/kyclogic/kyclogic_api.c b/src/kyclogic/kyclogic_api.c @@ -974,7 +974,6 @@ TALER_KYCLOGIC_kyc_trigger_from_string ( 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 }, @@ -1007,8 +1006,6 @@ TALER_KYCLOGIC_kyc_trigger2s ( return NULL; 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: diff --git a/src/lib/exchange_api_age_withdraw.c b/src/lib/exchange_api_age_withdraw.c @@ -449,10 +449,13 @@ handle_reserve_age_withdraw_blinded_finished ( case MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS: /* only validate reply is well-formed */ { - uint64_t ptu; struct GNUNET_JSON_Specification spec[] = { - GNUNET_JSON_spec_uint64 ("requirement_row", - &ptu), + GNUNET_JSON_spec_fixed_auto ( + "h_payto", + &awbr.details.unavailable_for_legal_reasons.h_payto), + GNUNET_JSON_spec_uint64 ( + "requirement_row", + &awbr.details.unavailable_for_legal_reasons.requirement_row), GNUNET_JSON_spec_end () }; @@ -500,13 +503,13 @@ perform_protocol ( struct TALER_EXCHANGE_AgeWithdrawBlindedHandle *awbh) { #define FAIL_IF(cond) \ - do { \ - if ((cond)) \ - { \ - GNUNET_break (! (cond)); \ - goto ERROR; \ - } \ - } while (0) + do { \ + if ((cond)) \ + { \ + GNUNET_break (! (cond)); \ + goto ERROR; \ + } \ + } while (0) struct GNUNET_HashContext *coins_hctx = NULL; json_t *j_denoms = NULL; @@ -744,8 +747,7 @@ call_age_withdraw_blinded ( * @param awbh The handler * @param exchange_url The base-URL to the exchange */ -static -enum GNUNET_GenericReturnValue +static enum GNUNET_GenericReturnValue prepare_url ( struct TALER_EXCHANGE_AgeWithdrawBlindedHandle *awbh, const char *exchange_url) @@ -827,14 +829,15 @@ csr_withdraw_done ( can->planchet_detail.blinded_planchet! */ do { if (GNUNET_OK != - TALER_planchet_prepare (&csr->denom_pub->key, - &can->details.alg_values, - &can->details.blinding_key, - &csr->nonce, - &can->details.coin_priv, - &can->details.h_age_commitment, - &can->details.h_coin_pub, - planchet)) + TALER_planchet_prepare ( + &csr->denom_pub->key, + &can->details.alg_values, + &can->details.blinding_key, + &csr->nonce, + &can->details.coin_priv, + &can->details.h_age_commitment, + &can->details.h_coin_pub, + planchet)) { GNUNET_break (0); break; @@ -871,10 +874,9 @@ csr_withdraw_done ( * @param awh The handler to the age-withdraw * @param num_coins The number of coins in @e coin_inputs * @param coin_inputs The input for the individual coin(-candidates) - * @return GNUNET_OK on success, GNUNET_SYSERR on failure + * @return #GNUNET_OK on success, #GNUNET_SYSERR on failure */ -static -enum GNUNET_GenericReturnValue +static enum GNUNET_GenericReturnValue prepare_coins ( struct TALER_EXCHANGE_AgeWithdrawHandle *awh, size_t num_coins, @@ -882,13 +884,13 @@ prepare_coins ( static num_coins]) { #define FAIL_IF(cond) \ - do { \ - if ((cond)) \ - { \ - GNUNET_break (! (cond)); \ - goto ERROR; \ - } \ - } while (0) + do { \ + if ((cond)) \ + { \ + GNUNET_break (! (cond)); \ + goto ERROR; \ + } \ + } while (0) GNUNET_assert (0 < num_coins); awh->age_mask = coin_inputs[0].denom_pub->key.age_mask; @@ -932,18 +934,20 @@ prepare_coins ( TALER_planchet_setup_coin_priv (&can->secret, &can->details.alg_values, &can->details.coin_priv); - TALER_planchet_blinding_secret_create (&can->secret, - &can->details.alg_values, - &can->details.blinding_key); + TALER_planchet_blinding_secret_create ( + &can->secret, + &can->details.alg_values, + &can->details.blinding_key); FAIL_IF (GNUNET_OK != - TALER_planchet_prepare (&cd->denom_pub.key, - &can->details.alg_values, - &can->details.blinding_key, - NULL, - &can->details.coin_priv, - &can->details.h_age_commitment, - &can->details.h_coin_pub, - planchet)); + TALER_planchet_prepare ( + &cd->denom_pub.key, + &can->details.alg_values, + &can->details.blinding_key, + NULL, + &can->details.coin_priv, + &can->details.h_age_commitment, + &can->details.h_coin_pub, + planchet)); TALER_coin_ev_hash (&planchet->blinded_planchet, &planchet->denom_pub_hash, &can->blinded_coin_h); @@ -985,7 +989,8 @@ ERROR: TALER_EXCHANGE_age_withdraw_cancel (awh); return GNUNET_SYSERR; #undef FAIL_IF -}; +} + struct TALER_EXCHANGE_AgeWithdrawHandle * TALER_EXCHANGE_age_withdraw ( @@ -1011,11 +1016,10 @@ TALER_EXCHANGE_age_withdraw ( awh->callback_cls = res_cb_cls; awh->num_coins = num_coins; awh->max_age = max_age; - - - if (GNUNET_OK != prepare_coins (awh, - num_coins, - coin_inputs)) + if (GNUNET_OK != + prepare_coins (awh, + num_coins, + coin_inputs)) { GNUNET_free (awh); return NULL; @@ -1090,12 +1094,12 @@ TALER_EXCHANGE_age_withdraw_blinded ( awbh->callback = res_cb; awbh->callback_cls = res_cb_cls; awbh->max_age = max_age; - - GNUNET_CRYPTO_eddsa_key_get_public (&awbh->reserve_priv->eddsa_priv, - &awbh->reserve_pub.eddsa_pub); - - if (GNUNET_OK != prepare_url (awbh, - exchange_url)) + GNUNET_CRYPTO_eddsa_key_get_public ( + &awbh->reserve_priv->eddsa_priv, + &awbh->reserve_pub.eddsa_pub); + if (GNUNET_OK != + prepare_url (awbh, + exchange_url)) return NULL; perform_protocol (awbh); @@ -1109,7 +1113,6 @@ TALER_EXCHANGE_age_withdraw_blinded_cancel ( { if (NULL == awbh) return; - if (NULL != awbh->job) { GNUNET_CURL_job_cancel (awbh->job); diff --git a/src/lib/exchange_api_age_withdraw_reveal.c b/src/lib/exchange_api_age_withdraw_reveal.c @@ -41,22 +41,34 @@ struct TALER_EXCHANGE_AgeWithdrawRevealHandle { - /* The index not to be disclosed */ + /** + * The index not to be disclosed + */ uint8_t noreveal_index; - /* The age-withdraw commitment */ + /** + * The age-withdraw commitment + */ struct TALER_AgeWithdrawCommitmentHashP h_commitment; - /* The reserve's public key */ + /** + * The reserve's public key + */ const struct TALER_ReservePublicKeyP *reserve_pub; - /* Number of coins */ + /** + * Number of coins + */ size_t num_coins; - /* The @e num_coins * kappa coin secrets from the age-withdraw commitment */ + /** + * The @e num_coins * kappa coin secrets from the age-withdraw commitment + */ const struct TALER_EXCHANGE_AgeWithdrawCoinInput *coins_input; - /* The url for the reveal request */ + /** + * The url for the reveal request + */ char *request_url; /** @@ -69,10 +81,14 @@ struct TALER_EXCHANGE_AgeWithdrawRevealHandle */ struct TALER_CURL_PostContext post_ctx; - /* Callback */ + /** + * Callback + */ TALER_EXCHANGE_AgeWithdrawRevealCallback callback; - /* Reveal */ + /** + * Reveal + */ void *callback_cls; }; @@ -101,9 +117,10 @@ age_withdraw_reveal_ok ( GNUNET_JSON_spec_end () }; - if (GNUNET_OK != GNUNET_JSON_parse (j_response, - spec, - NULL, NULL)) + if (GNUNET_OK != + GNUNET_JSON_parse (j_response, + spec, + NULL, NULL)) { GNUNET_break_op (0); return GNUNET_SYSERR; @@ -130,9 +147,10 @@ age_withdraw_reveal_ok ( GNUNET_JSON_spec_end () }; - if (GNUNET_OK != GNUNET_JSON_parse (j_sig, - spec, - NULL, NULL)) + if (GNUNET_OK != + GNUNET_JSON_parse (j_sig, + spec, + NULL, NULL)) { GNUNET_break_op (0); return GNUNET_SYSERR; @@ -203,10 +221,13 @@ handle_age_withdraw_reveal_finished ( case MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS: /* only validate reply is well-formed */ { - uint64_t ptu; struct GNUNET_JSON_Specification spec[] = { - GNUNET_JSON_spec_uint64 ("legitimization_uuid", - &ptu), + GNUNET_JSON_spec_fixed_auto ( + "h_payto", + &awr.details.unavailable_for_legal_reasons.h_payto), + GNUNET_JSON_spec_uint64 ( + "requirement_row", + &awr.details.unavailable_for_legal_reasons.requirement_row), GNUNET_JSON_spec_end () }; @@ -300,10 +321,11 @@ prepare_url ( char pub_str[sizeof (struct TALER_AgeWithdrawCommitmentHashP) * 2]; char *end; - end = GNUNET_STRINGS_data_to_string (&awrh->h_commitment, - sizeof (awrh->h_commitment), - pub_str, - sizeof (pub_str)); + end = GNUNET_STRINGS_data_to_string ( + &awrh->h_commitment, + sizeof (awrh->h_commitment), + pub_str, + sizeof (pub_str)); *end = '\0'; GNUNET_snprintf (arg_str, sizeof (arg_str), @@ -330,8 +352,7 @@ prepare_url ( * @param curl_ctx The context for CURL * @param awrh The handler */ -static -void +static void perform_protocol ( struct GNUNET_CURL_Context *curl_ctx, struct TALER_EXCHANGE_AgeWithdrawRevealHandle *awrh) @@ -356,8 +377,8 @@ perform_protocol ( for (size_t n = 0; n < awrh->num_coins; n++) { - const struct TALER_PlanchetMasterSecretP *secrets = - awrh->coins_input[n].secrets; + const struct TALER_PlanchetMasterSecretP *secrets + = awrh->coins_input[n].secrets; j_secrets = json_array (); FAIL_IF (NULL == j_secrets); @@ -395,11 +416,12 @@ perform_protocol ( json_decref (j_request_body); j_request_body = NULL; - awrh->job = GNUNET_CURL_job_add2 (curl_ctx, - curlh, - awrh->post_ctx.headers, - &handle_age_withdraw_reveal_finished, - awrh); + awrh->job = GNUNET_CURL_job_add2 ( + curl_ctx, + curlh, + awrh->post_ctx.headers, + &handle_age_withdraw_reveal_finished, + awrh); FAIL_IF (NULL == awrh->job); /* No error, return */ diff --git a/src/testing/test_exchange_api_age_restriction.c b/src/testing/test_exchange_api_age_restriction.c @@ -288,7 +288,7 @@ run (void *cls, "withdraw-coin-1-lacking-kyc", "create-reserve-kyc-1", "EUR:10", - 0, /* age restriction off */ + 0, /* age restriction off */ MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS), TALER_TESTING_cmd_admin_add_kycauth ( "setup-account-key", @@ -313,21 +313,21 @@ run (void *cls, 0, MHD_HTTP_OK), TALER_TESTING_cmd_proof_kyc_oauth2 ( - "proof-kyc", + "proof-withdraw-kyc", "withdraw-coin-1-lacking-kyc", - "kyc-provider-test-oauth2", + "test-oauth2", "pass", MHD_HTTP_SEE_OTHER), TALER_TESTING_cmd_withdraw_amount ( "withdraw-coin-1-with-kyc", "create-reserve-kyc-1", "EUR:10", - 0, /* age restriction off */ + 0, /* age restriction off */ MHD_HTTP_CONFLICT), TALER_TESTING_cmd_age_withdraw ( "age-withdraw-coin-1-too-low", "create-reserve-kyc-1", - 18, /* Too high */ + 18, /* Too high */ MHD_HTTP_CONFLICT, "EUR:10", NULL), diff --git a/src/testing/test_exchange_api_age_restriction.conf b/src/testing/test_exchange_api_age_restriction.conf @@ -23,7 +23,6 @@ HTTP_PORT = 8082 [exchange] TERMS_ETAG = tos PRIVACY_ETAG = 0 -AML_THRESHOLD = EUR:10 PORT = 8081 MASTER_PUBLIC_KEY = 98NJW3CQHZQGQXTY3K85K531XKPAPAVV4Q5V8PYYRR00NJGZWNVG DB = postgres @@ -75,13 +74,13 @@ USERNAME = Exchange PASSWORD = x WIRE_GATEWAY_URL = "http://localhost:8082/accounts/2/taler-wire-gateway/" +[exchange-extension-age_restriction] +ENABLED = YES +#AGE_GROUPS = "8:10:12:14:16:18:21" [kyc-provider-test-oauth2] -COST = 0 LOGIC = oauth2 -USER_TYPE = INDIVIDUAL -PROVIDED_CHECKS = DUMMY -CONVERTER = cat +CONVERTER = taler-exchange-helper-converter-oauth2-address KYC_OAUTH2_VALIDITY = forever KYC_OAUTH2_TOKEN_URL = http://localhost:6666/oauth/v2/token KYC_OAUTH2_AUTHORIZE_URL = http://localhost:6666/oauth/v2/login @@ -91,30 +90,104 @@ KYC_OAUTH2_CLIENT_SECRET = exchange-secret KYC_OAUTH2_POST_URL = http://example.com/ KYC_OAUTH2_CONVERTER_HELPER = taler-exchange-kyc-oauth2-test-converter.sh -[kyc-legitimization-balance-high] -OPERATION_TYPE = BALANCE -REQUIRED_CHECKS = DUMMY -THRESHOLD = EUR:20 - -[kyc-legitimization-deposit-any] -OPERATION_TYPE = DEPOSIT -REQUIRED_CHECKS = DUMMY -THRESHOLD = EUR:10 -TIMEFRAME = 1d - -[kyc-legitimization-withdraw] +[kyc-check-oauth-test-id] +VOLUNTARY = NO +# We use an external provider +TYPE = LINK +DESCRIPTION = "Oauth2 dummy authentication" +DESCRIPTION_I18N = {} +# No context requirements +REQUIRES = +# Measure to execute if check failed. +FALLBACK = manual-freeze +# This check runs on oauth2 +PROVIDER_ID = test-oauth2 + +# This is the "default" setting for an account if +# it has not yet triggered anything. +[kyc-check-default] +VOLUNTARY = NO +TYPE = INFO +DESCRIPTION = "Your account is operating normally" +DESCRIPTION_I18N = {} +# No context requirements +REQUIRES = +# Measure to execute if check failed. Well, +# this check cannot really fail, but the +# conservative answer is to freeze. +FALLBACK = manual-freeze + +# If this "check" is triggered, we merely inform +# the user that their account has been frozen. The +# user cannot proceed manually. +[kyc-check-info-frozen] +VOLUNTARY = NO +TYPE = INFO +DESCRIPTION = "Your account is frozen pending investigation" +DESCRIPTION_I18N = {} +# No context requirements +REQUIRES = +# Measure to execute if check failed. Well, +# this check cannot really fail, but we stay +# where we are: frozen. +FALLBACK = manual-freeze + +# If this "check" is triggered, we merely inform +# the user that we got their oauth-test data on file. +[kyc-check-info-oauth-test-passed] +VOLUNTARY = NO +TYPE = INFO +DESCRIPTION = "You passed the OAuth2 check. Thank you." +DESCRIPTION_I18N = {} +# No context requirements +REQUIRES = +# Measure to execute if check failed. Well, +# this check cannot really fail, but we stay +# where we are: frozen. +FALLBACK = manual-freeze + +[aml-program-oauth-output-check] +DESCRIPTION = "Validates the output from OAauth2 and then permits the reserve closing to proceed" +# Command that runs on the output of the OAuth provider +# to decide what rules should apply next. +COMMAND = taler-exchange-helper-measure-test-oauth +# What measure to take if the COMMAND failed. +FALLBACK = manual-freeze + +[kyc-measure-run-oauth] +# Get client ID via the OAuth test provider +CHECK_NAME = oauth-test-id +# AML program to run on the output of the OAuth provider +# to decide what rules should apply next. +PROGRAM = oauth-output-check +# Context to provide for check and program; empty. +CONTEXT = {} + +# This is a base-measure that is being triggered +# whenever something goes wrong. We freeze the +# account and ask AML staff to investigate. +[kyc-measure-manual-freeze] +CHECK_NAME = skip +# AML program that freezes the account and flags +# it for investigation. +PROGRAM = taler-exchange-helper-measure-freeze +# Context to provide for check and program; empty. +CONTEXT = {} + +# This rule requests that the users passes KYC +# when closing the reserve. +[kyc-rule-withdraw] +ENABLED = YES +# This is a public rule. +EXPOSED = YES +# All checks listed must be done (well, there is only one...) +IS_AND_COMBINATOR = YES +# This happens if the reserve is closed. OPERATION_TYPE = WITHDRAW -REQUIRED_CHECKS = DUMMY +# Threshold is 0, so any amount. THRESHOLD = EUR:15 +# Timeframe doesn't exactly matter with a threshold of EUR:0. TIMEFRAME = 1d - -[kyc-legitimization-merge] -OPERATION_TYPE = MERGE -REQUIRED_CHECKS = DUMMY -THRESHOLD = EUR:15 -TIMEFRAME = 1d - - -[exchange-extension-age_restriction] -ENABLED = YES -#AGE_GROUPS = "8:10:12:14:16:18:21" +# If the rule is triggered, ask the user to provide +# personal data via OAuth2 +NEXT_MEASURES = run-oauth