kyc logic with birthdate setting test added

1. Added age-requirement check in withdraw-handler (like in batch-withdraw)

2. In test_exchange_api_age_restriction:

  - kyc-oauth2 started, with static birthdate in answers
  - withdraw triggers kyc
  - second withdraw fails due to age restriction requirements
This commit is contained in:
Özgür Kesim 2023-07-23 21:18:32 +02:00
parent e230eaad76
commit 5bf90c3505
Signed by: oec
GPG Key ID: 3D76A56D79EDD9D7
10 changed files with 90 additions and 16 deletions

View File

@ -26,6 +26,7 @@
#include "platform.h" #include "platform.h"
#include <gnunet/gnunet_util_lib.h> #include <gnunet/gnunet_util_lib.h>
#include <jansson.h> #include <jansson.h>
#include "taler-exchange-httpd.h"
#include "taler_json_lib.h" #include "taler_json_lib.h"
#include "taler_kyclogic_lib.h" #include "taler_kyclogic_lib.h"
#include "taler_mhd_lib.h" #include "taler_mhd_lib.h"
@ -180,6 +181,8 @@ withdraw_transaction (void *cls,
bool found = false; bool found = false;
bool balance_ok = false; bool balance_ok = false;
bool nonce_ok = false; bool nonce_ok = false;
bool age_ok = false;
uint16_t allowed_maximum_age = 0;
uint64_t ruuid; uint64_t ruuid;
const struct TALER_CsNonce *nonce; const struct TALER_CsNonce *nonce;
const struct TALER_BlindedPlanchet *bp; const struct TALER_BlindedPlanchet *bp;
@ -342,9 +345,12 @@ withdraw_transaction (void *cls,
nonce, nonce,
&wc->collectable, &wc->collectable,
wc->now, wc->now,
TEH_age_restriction_enabled,
&found, &found,
&balance_ok, &balance_ok,
&nonce_ok, &nonce_ok,
&age_ok,
&allowed_maximum_age,
&ruuid); &ruuid);
if (0 > qs) if (0 > qs)
{ {
@ -366,6 +372,20 @@ withdraw_transaction (void *cls,
NULL); NULL);
return GNUNET_DB_STATUS_HARD_ERROR; 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) if (! balance_ok)
{ {
TEH_plugin->rollback (TEH_plugin->cls); TEH_plugin->rollback (TEH_plugin->cls);

View File

@ -26,20 +26,22 @@ CREATE OR REPLACE FUNCTION exchange_do_withdraw(
IN denom_sig BYTEA, IN denom_sig BYTEA,
IN now INT8, IN now INT8,
IN min_reserve_gc INT8, IN min_reserve_gc INT8,
IN do_age_check BOOLEAN,
OUT reserve_found BOOLEAN, OUT reserve_found BOOLEAN,
OUT balance_ok BOOLEAN, OUT balance_ok BOOLEAN,
OUT nonce_ok BOOLEAN, OUT nonce_ok BOOLEAN,
OUT age_ok BOOLEAN,
OUT allowed_maximum_age INT2, -- in years
OUT ruuid INT8) OUT ruuid INT8)
LANGUAGE plpgsql LANGUAGE plpgsql
AS $$ AS $$
DECLARE DECLARE
reserve_gc INT8; reserve_gc INT8;
DECLARE
denom_serial INT8; denom_serial INT8;
DECLARE
reserve_val INT8; reserve_val INT8;
DECLARE
reserve_frac INT4; reserve_frac INT4;
reserve_birthday INT4;
not_before date;
BEGIN BEGIN
-- Shards: reserves by reserve_pub (SELECT) -- Shards: reserves by reserve_pub (SELECT)
-- reserves_out (INSERT, with CONFLICT detection) by wih -- reserves_out (INSERT, with CONFLICT detection) by wih
@ -57,6 +59,8 @@ THEN
-- denomination unknown, should be impossible! -- denomination unknown, should be impossible!
reserve_found=FALSE; reserve_found=FALSE;
balance_ok=FALSE; balance_ok=FALSE;
age_ok=FALSE;
allowed_maximum_age=0;
ruuid=0; ruuid=0;
ASSERT false, 'denomination unknown'; ASSERT false, 'denomination unknown';
RETURN; RETURN;
@ -67,11 +71,13 @@ SELECT
current_balance_val current_balance_val
,current_balance_frac ,current_balance_frac
,gc_date ,gc_date
,birthday
,reserve_uuid ,reserve_uuid
INTO INTO
reserve_val reserve_val
,reserve_frac ,reserve_frac
,reserve_gc ,reserve_gc
,reserve_birthday
,ruuid ,ruuid
FROM exchange.reserves FROM exchange.reserves
WHERE reserves.reserve_pub=rpub; WHERE reserves.reserve_pub=rpub;
@ -82,10 +88,33 @@ THEN
reserve_found=FALSE; reserve_found=FALSE;
balance_ok=FALSE; balance_ok=FALSE;
nonce_ok=TRUE; nonce_ok=TRUE;
age_ok=FALSE;
allowed_maximum_age=0;
ruuid=2; ruuid=2;
RETURN; RETURN;
END IF; 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 -- We optimistically insert, and then on conflict declare
-- the query successful due to idempotency. -- the query successful due to idempotency.
INSERT INTO exchange.reserves_out INSERT INTO exchange.reserves_out
@ -194,6 +223,6 @@ END IF;
END $$; END $$;
COMMENT ON FUNCTION exchange_do_withdraw(BYTEA, INT8, INT4, BYTEA, BYTEA, BYTEA, BYTEA, BYTEA, INT8, INT8) 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 so updates the database with the result'; 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';

View File

@ -363,6 +363,8 @@ run (void *cls)
bool found; bool found;
bool nonce_ok; bool nonce_ok;
bool balance_ok; bool balance_ok;
bool age_ok;
uint16_t allowed_minimum_age;
uint64_t ruuid; uint64_t ruuid;
struct GNUNET_TIME_Timestamp now; struct GNUNET_TIME_Timestamp now;
@ -372,9 +374,12 @@ run (void *cls)
NULL, NULL,
&cbc, &cbc,
now, now,
false,
&found, &found,
&balance_ok, &balance_ok,
&nonce_ok, &nonce_ok,
&age_ok,
&allowed_minimum_age,
&ruuid)); &ruuid));
} }
{ {

View File

@ -32,9 +32,12 @@ TEH_PG_do_withdraw (
const struct TALER_CsNonce *nonce, const struct TALER_CsNonce *nonce,
const struct TALER_EXCHANGEDB_CollectableBlindcoin *collectable, const struct TALER_EXCHANGEDB_CollectableBlindcoin *collectable,
struct GNUNET_TIME_Timestamp now, struct GNUNET_TIME_Timestamp now,
bool do_age_check,
bool *found, bool *found,
bool *balance_ok, bool *balance_ok,
bool *nonce_ok, bool *nonce_ok,
bool *age_ok,
uint16_t *allowed_maximum_age,
uint64_t *ruuid) uint64_t *ruuid)
{ {
struct PostgresClosure *pg = cls; struct PostgresClosure *pg = cls;
@ -51,6 +54,7 @@ TEH_PG_do_withdraw (
TALER_PQ_query_param_blinded_denom_sig (&collectable->sig), TALER_PQ_query_param_blinded_denom_sig (&collectable->sig),
GNUNET_PQ_query_param_timestamp (&now), GNUNET_PQ_query_param_timestamp (&now),
GNUNET_PQ_query_param_timestamp (&gc), GNUNET_PQ_query_param_timestamp (&gc),
GNUNET_PQ_query_param_bool (do_age_check),
GNUNET_PQ_query_param_end GNUNET_PQ_query_param_end
}; };
struct GNUNET_PQ_ResultSpec rs[] = { struct GNUNET_PQ_ResultSpec rs[] = {
@ -60,6 +64,10 @@ TEH_PG_do_withdraw (
balance_ok), balance_ok),
GNUNET_PQ_result_spec_bool ("nonce_ok", GNUNET_PQ_result_spec_bool ("nonce_ok",
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", GNUNET_PQ_result_spec_uint64 ("ruuid",
ruuid), ruuid),
GNUNET_PQ_result_spec_end GNUNET_PQ_result_spec_end
@ -71,9 +79,11 @@ TEH_PG_do_withdraw (
" reserve_found" " reserve_found"
",balance_ok" ",balance_ok"
",nonce_ok" ",nonce_ok"
",age_ok"
",allowed_maximum_age"
",ruuid" ",ruuid"
" FROM exchange_do_withdraw" " 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 ( gc = GNUNET_TIME_absolute_to_timestamp (
GNUNET_TIME_absolute_add (now.abs_time, GNUNET_TIME_absolute_add (now.abs_time,
pg->legal_reserve_expiration_time)); pg->legal_reserve_expiration_time));

View File

@ -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 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[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 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] found set to true if the reserve was found
* @param[out] balance_ok set to true if the balance was sufficient * @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] 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) * @param[out] ruuid set to the reserve's UUID (reserves table row)
* @return query execution status * @return query execution status
*/ */
@ -45,9 +48,12 @@ TEH_PG_do_withdraw (
const struct TALER_CsNonce *nonce, const struct TALER_CsNonce *nonce,
const struct TALER_EXCHANGEDB_CollectableBlindcoin *collectable, const struct TALER_EXCHANGEDB_CollectableBlindcoin *collectable,
struct GNUNET_TIME_Timestamp now, struct GNUNET_TIME_Timestamp now,
bool do_age_check,
bool *found, bool *found,
bool *balance_ok, bool *balance_ok,
bool *nonce_ok, bool *nonce_ok,
bool *age_ok,
uint16_t *allowed_maximum_age,
uint64_t *ruuid); uint64_t *ruuid);
#endif #endif

View File

@ -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 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 collectable corresponding collectable coin (blind signature)
* @param now current time (rounded) * @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] found set to true if the reserve was found
* @param[out] balance_ok set to true if the balance was sufficient * @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] 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) * @param[out] ruuid set to the reserve's UUID (reserves table row)
* @return query execution status * @return query execution status
*/ */
@ -3757,9 +3760,12 @@ struct TALER_EXCHANGEDB_Plugin
const struct TALER_CsNonce *nonce, const struct TALER_CsNonce *nonce,
const struct TALER_EXCHANGEDB_CollectableBlindcoin *collectable, const struct TALER_EXCHANGEDB_CollectableBlindcoin *collectable,
struct GNUNET_TIME_Timestamp now, struct GNUNET_TIME_Timestamp now,
bool do_age_check,
bool *found, bool *found,
bool *balance_ok, bool *balance_ok,
bool *nonce_ok, bool *nonce_ok,
bool *age_ok,
uint16_t *allowed_maximum_age,
uint64_t *ruuid); uint64_t *ruuid);

View File

@ -922,9 +922,6 @@ data2attributes (const struct TALER_KYCLOGIC_ProviderDetails *pd,
JSON_INDENT (2)); JSON_INDENT (2));
return NULL; 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, ret = json_loadb (attr_data,
attr_size, attr_size,
JSON_REJECT_DUPLICATES, JSON_REJECT_DUPLICATES,

View File

@ -285,6 +285,12 @@ handle_reserve_withdraw_finished (void *cls,
w2r.hr.hint = TALER_JSON_get_error_hint (j); w2r.hr.hint = TALER_JSON_get_error_hint (j);
break; break;
case MHD_HTTP_CONFLICT: 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; /* The exchange says that the reserve has insufficient funds;
check the signatures in the history... */ check the signatures in the history... */
if (GNUNET_OK != if (GNUNET_OK !=
@ -295,11 +301,6 @@ handle_reserve_withdraw_finished (void *cls,
w2r.hr.http_status = 0; w2r.hr.http_status = 0;
w2r.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; 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; break;
case MHD_HTTP_GONE: case MHD_HTTP_GONE:
/* could happen if denomination was revoked */ /* could happen if denomination was revoked */

View File

@ -1,4 +1,4 @@
# This file is in the public domain. # This file is in the public domain.
# #
@INLINE@ test_exchange_api_age_restriction.conf
@INLINE@ coins-cs.conf @INLINE@ coins-cs.conf
@INLINE@ test_exchange_api.conf

View File

@ -302,7 +302,7 @@ run (void *cls,
true, true,
true), true),
TALER_TESTING_cmd_oauth_with_birthdate ("oauth-service-with-birthdate", 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), 6666),
TALER_TESTING_cmd_batch ("withdraw-age", TALER_TESTING_cmd_batch ("withdraw-age",
withdraw_age), withdraw_age),