diff --git a/src/exchange/taler-exchange-httpd_withdraw.c b/src/exchange/taler-exchange-httpd_withdraw.c index cbc641410..07fcc8464 100644 --- a/src/exchange/taler-exchange-httpd_withdraw.c +++ b/src/exchange/taler-exchange-httpd_withdraw.c @@ -26,6 +26,7 @@ #include "platform.h" #include #include +#include "taler-exchange-httpd.h" #include "taler_json_lib.h" #include "taler_kyclogic_lib.h" #include "taler_mhd_lib.h" @@ -180,6 +181,8 @@ withdraw_transaction (void *cls, bool found = false; bool balance_ok = false; bool nonce_ok = false; + bool age_ok = false; + uint16_t allowed_maximum_age = 0; uint64_t ruuid; const struct TALER_CsNonce *nonce; const struct TALER_BlindedPlanchet *bp; @@ -342,9 +345,12 @@ withdraw_transaction (void *cls, nonce, &wc->collectable, wc->now, + TEH_age_restriction_enabled, &found, &balance_ok, &nonce_ok, + &age_ok, + &allowed_maximum_age, &ruuid); if (0 > qs) { @@ -366,6 +372,20 @@ withdraw_transaction (void *cls, NULL); return GNUNET_DB_STATUS_HARD_ERROR; } + if (! age_ok) + { + /* We respond with the lowest age in the corresponding age group + * of the required age */ + uint16_t lowest_age = TALER_get_lowest_age ( + &TEH_age_restriction_config.mask, + allowed_maximum_age); + + TEH_plugin->rollback (TEH_plugin->cls); + *mhd_ret = TEH_RESPONSE_reply_reserve_age_restriction_required ( + connection, + lowest_age); + return GNUNET_DB_STATUS_HARD_ERROR; + } if (! balance_ok) { TEH_plugin->rollback (TEH_plugin->cls); diff --git a/src/exchangedb/exchange_do_withdraw.sql b/src/exchangedb/exchange_do_withdraw.sql index 9689bae5a..f6632c543 100644 --- a/src/exchangedb/exchange_do_withdraw.sql +++ b/src/exchangedb/exchange_do_withdraw.sql @@ -26,20 +26,22 @@ CREATE OR REPLACE FUNCTION exchange_do_withdraw( IN denom_sig BYTEA, IN now INT8, IN min_reserve_gc INT8, + IN do_age_check BOOLEAN, OUT reserve_found BOOLEAN, OUT balance_ok BOOLEAN, OUT nonce_ok BOOLEAN, + OUT age_ok BOOLEAN, + OUT allowed_maximum_age INT2, -- in years OUT ruuid INT8) LANGUAGE plpgsql AS $$ DECLARE reserve_gc INT8; -DECLARE denom_serial INT8; -DECLARE reserve_val INT8; -DECLARE reserve_frac INT4; + reserve_birthday INT4; + not_before date; BEGIN -- Shards: reserves by reserve_pub (SELECT) -- reserves_out (INSERT, with CONFLICT detection) by wih @@ -57,6 +59,8 @@ THEN -- denomination unknown, should be impossible! reserve_found=FALSE; balance_ok=FALSE; + age_ok=FALSE; + allowed_maximum_age=0; ruuid=0; ASSERT false, 'denomination unknown'; RETURN; @@ -67,11 +71,13 @@ SELECT current_balance_val ,current_balance_frac ,gc_date + ,birthday ,reserve_uuid INTO reserve_val ,reserve_frac ,reserve_gc + ,reserve_birthday ,ruuid FROM exchange.reserves WHERE reserves.reserve_pub=rpub; @@ -82,10 +88,33 @@ THEN reserve_found=FALSE; balance_ok=FALSE; nonce_ok=TRUE; + age_ok=FALSE; + allowed_maximum_age=0; ruuid=2; RETURN; END IF; +-- Check if age requirements are present +IF ((NOT do_age_check) OR (reserve_birthday = 0)) +THEN + age_ok = TRUE; + allowed_maximum_age = -1; +ELSE + -- Age requirements are formally not met: The exchange is setup to support + -- age restrictions (do_age_check == TRUE) and the reserve has a + -- birthday set (reserve_birthday != 0), but the client called the + -- batch-withdraw endpoint instead of the age-withdraw endpoint, which it + -- should have. + not_before=date '1970-01-01' + reserve_birthday; + allowed_maximum_age = extract(year from age(current_date, not_before)); + + reserve_found=TRUE; + nonce_ok=TRUE; -- we do not really know + balance_ok=TRUE;-- we do not really know + age_ok = FALSE; + RETURN; +END IF; + -- We optimistically insert, and then on conflict declare -- the query successful due to idempotency. INSERT INTO exchange.reserves_out @@ -194,6 +223,6 @@ END IF; END $$; -COMMENT ON FUNCTION exchange_do_withdraw(BYTEA, INT8, INT4, BYTEA, BYTEA, BYTEA, BYTEA, BYTEA, INT8, INT8) - IS 'Checks whether the reserve has sufficient balance for a withdraw operation (or the request is repeated and was previously approved) and if so updates the database with the result'; +COMMENT ON FUNCTION exchange_do_withdraw(BYTEA, INT8, INT4, BYTEA, BYTEA, BYTEA, BYTEA, BYTEA, INT8, INT8, BOOLEAN) + IS 'Checks whether the reserve has sufficient balance for a withdraw operation (or the request is repeated and was previously approved) and if the age requirements are formally met. If so updates the database with the result'; diff --git a/src/exchangedb/perf_deposits_get_ready.c b/src/exchangedb/perf_deposits_get_ready.c index 4ad08223c..89699da24 100644 --- a/src/exchangedb/perf_deposits_get_ready.c +++ b/src/exchangedb/perf_deposits_get_ready.c @@ -363,6 +363,8 @@ run (void *cls) bool found; bool nonce_ok; bool balance_ok; + bool age_ok; + uint16_t allowed_minimum_age; uint64_t ruuid; struct GNUNET_TIME_Timestamp now; @@ -372,9 +374,12 @@ run (void *cls) NULL, &cbc, now, + false, &found, &balance_ok, &nonce_ok, + &age_ok, + &allowed_minimum_age, &ruuid)); } { diff --git a/src/exchangedb/pg_do_withdraw.c b/src/exchangedb/pg_do_withdraw.c index 01bbfff5b..99732df6b 100644 --- a/src/exchangedb/pg_do_withdraw.c +++ b/src/exchangedb/pg_do_withdraw.c @@ -32,9 +32,12 @@ TEH_PG_do_withdraw ( const struct TALER_CsNonce *nonce, const struct TALER_EXCHANGEDB_CollectableBlindcoin *collectable, struct GNUNET_TIME_Timestamp now, + bool do_age_check, bool *found, bool *balance_ok, bool *nonce_ok, + bool *age_ok, + uint16_t *allowed_maximum_age, uint64_t *ruuid) { struct PostgresClosure *pg = cls; @@ -51,6 +54,7 @@ TEH_PG_do_withdraw ( TALER_PQ_query_param_blinded_denom_sig (&collectable->sig), GNUNET_PQ_query_param_timestamp (&now), GNUNET_PQ_query_param_timestamp (&gc), + GNUNET_PQ_query_param_bool (do_age_check), GNUNET_PQ_query_param_end }; struct GNUNET_PQ_ResultSpec rs[] = { @@ -60,6 +64,10 @@ TEH_PG_do_withdraw ( balance_ok), GNUNET_PQ_result_spec_bool ("nonce_ok", nonce_ok), + GNUNET_PQ_result_spec_bool ("age_ok", + age_ok), + GNUNET_PQ_result_spec_uint16 ("allowed_maximum_age", + allowed_maximum_age), GNUNET_PQ_result_spec_uint64 ("ruuid", ruuid), GNUNET_PQ_result_spec_end @@ -71,9 +79,11 @@ TEH_PG_do_withdraw ( " reserve_found" ",balance_ok" ",nonce_ok" + ",age_ok" + ",allowed_maximum_age" ",ruuid" " FROM exchange_do_withdraw" - " ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10);"); + " ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11);"); gc = GNUNET_TIME_absolute_to_timestamp ( GNUNET_TIME_absolute_add (now.abs_time, pg->legal_reserve_expiration_time)); diff --git a/src/exchangedb/pg_do_withdraw.h b/src/exchangedb/pg_do_withdraw.h index 406785c42..e771b1ac7 100644 --- a/src/exchangedb/pg_do_withdraw.h +++ b/src/exchangedb/pg_do_withdraw.h @@ -33,9 +33,12 @@ * @param nonce client-contributed input for CS denominations that must be checked for idempotency, or NULL for non-CS withdrawals * @param[in,out] collectable corresponding collectable coin (blind signature) if a coin is found; possibly updated if a (different) signature exists already * @param now current time (rounded) + * @param do_age_check set to true if age requirements must be verified * @param[out] found set to true if the reserve was found * @param[out] balance_ok set to true if the balance was sufficient * @param[out] nonce_ok set to false if the nonce was reused + * @param[out] age_ok set to true if age requirements are met + * @param[out] allowed_maximum_age if @e age_ok is false, the maximum age (in years) that is allowed during age-withdraw * @param[out] ruuid set to the reserve's UUID (reserves table row) * @return query execution status */ @@ -45,9 +48,12 @@ TEH_PG_do_withdraw ( const struct TALER_CsNonce *nonce, const struct TALER_EXCHANGEDB_CollectableBlindcoin *collectable, struct GNUNET_TIME_Timestamp now, + bool do_age_check, bool *found, bool *balance_ok, bool *nonce_ok, + bool *age_ok, + uint16_t *allowed_maximum_age, uint64_t *ruuid); #endif diff --git a/src/include/taler_exchangedb_plugin.h b/src/include/taler_exchangedb_plugin.h index df051e867..c4b894e20 100644 --- a/src/include/taler_exchangedb_plugin.h +++ b/src/include/taler_exchangedb_plugin.h @@ -3745,9 +3745,12 @@ struct TALER_EXCHANGEDB_Plugin * @param nonce client-contributed input for CS denominations that must be checked for idempotency, or NULL for non-CS withdrawals * @param collectable corresponding collectable coin (blind signature) * @param now current time (rounded) + * @param do_age_check set to true if age requirements must be checked. * @param[out] found set to true if the reserve was found * @param[out] balance_ok set to true if the balance was sufficient * @param[out] nonce_ok set to false if the nonce was reused + * @param[out] age_ok set to true if no age requirements were defined on the reserve or @e do_age_check was false + * @param[out] allowed_maximum_age when @e age_ok is false, set to the allowed maximum age for withdrawal from the reserve. The client MUST then use the age-withdraw endpoint * @param[out] ruuid set to the reserve's UUID (reserves table row) * @return query execution status */ @@ -3757,9 +3760,12 @@ struct TALER_EXCHANGEDB_Plugin const struct TALER_CsNonce *nonce, const struct TALER_EXCHANGEDB_CollectableBlindcoin *collectable, struct GNUNET_TIME_Timestamp now, + bool do_age_check, bool *found, bool *balance_ok, bool *nonce_ok, + bool *age_ok, + uint16_t *allowed_maximum_age, uint64_t *ruuid); diff --git a/src/kyclogic/plugin_kyclogic_oauth2.c b/src/kyclogic/plugin_kyclogic_oauth2.c index babbf4acd..c9e5d8dcf 100644 --- a/src/kyclogic/plugin_kyclogic_oauth2.c +++ b/src/kyclogic/plugin_kyclogic_oauth2.c @@ -922,9 +922,6 @@ data2attributes (const struct TALER_KYCLOGIC_ProviderDetails *pd, JSON_INDENT (2)); return NULL; } - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "XXXXXXXX plugin_kyc_logic SETTING ATTERIBUTES TO\n\t%s\n", - json_dumps (data, JSON_INDENT (2))); ret = json_loadb (attr_data, attr_size, JSON_REJECT_DUPLICATES, diff --git a/src/lib/exchange_api_withdraw2.c b/src/lib/exchange_api_withdraw2.c index bf9e2c091..6de7adccb 100644 --- a/src/lib/exchange_api_withdraw2.c +++ b/src/lib/exchange_api_withdraw2.c @@ -285,6 +285,12 @@ handle_reserve_withdraw_finished (void *cls, w2r.hr.hint = TALER_JSON_get_error_hint (j); break; case MHD_HTTP_CONFLICT: + w2r.hr.ec = TALER_JSON_get_error_code (j); + w2r.hr.hint = TALER_JSON_get_error_hint (j); + + if (TALER_EC_EXCHANGE_RESERVES_AGE_RESTRICTION_REQUIRED == w2r.hr.ec) + break; + /* The exchange says that the reserve has insufficient funds; check the signatures in the history... */ if (GNUNET_OK != @@ -295,11 +301,6 @@ handle_reserve_withdraw_finished (void *cls, w2r.hr.http_status = 0; w2r.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; } - else - { - w2r.hr.ec = TALER_JSON_get_error_code (j); - w2r.hr.hint = TALER_JSON_get_error_hint (j); - } break; case MHD_HTTP_GONE: /* could happen if denomination was revoked */ diff --git a/src/testing/test_exchange_api_age_restriction-cs.conf b/src/testing/test_exchange_api_age_restriction-cs.conf index b80696fb2..12195f9ba 100644 --- a/src/testing/test_exchange_api_age_restriction-cs.conf +++ b/src/testing/test_exchange_api_age_restriction-cs.conf @@ -1,4 +1,4 @@ # This file is in the public domain. # +@INLINE@ test_exchange_api_age_restriction.conf @INLINE@ coins-cs.conf -@INLINE@ test_exchange_api.conf diff --git a/src/testing/test_exchange_api_age_restriction.c b/src/testing/test_exchange_api_age_restriction.c index 0aaa7bbf9..93bd28bf9 100644 --- a/src/testing/test_exchange_api_age_restriction.c +++ b/src/testing/test_exchange_api_age_restriction.c @@ -302,7 +302,7 @@ run (void *cls, true, true), TALER_TESTING_cmd_oauth_with_birthdate ("oauth-service-with-birthdate", - "2022-00-00", /* enough for a while */ + "2015-00-00", /* enough for a while */ 6666), TALER_TESTING_cmd_batch ("withdraw-age", withdraw_age),