major rework of the KYC logic, making it more configurable, not complete, but tests pass again

This commit is contained in:
Christian Grothoff 2022-08-11 23:35:33 +02:00
parent b061ea85c8
commit 1009084e94
No known key found for this signature in database
GPG Key ID: 939E6BE1E29FC3CC
52 changed files with 4714 additions and 3782 deletions

@ -1 +1 @@
Subproject commit 2075d42719b00f2763fe71d327ef4b9fd23be476 Subproject commit 88f1513c159014a1cbc6d0745568770538d2b0a9

File diff suppressed because one or more lines are too long

View File

@ -1 +1 @@
1659874124 1660251795

View File

@ -3,7 +3,7 @@ KEYFILE = ${TALER_DATA_HOME}/merchant/default.priv
NAME = Merchant Inc. NAME = Merchant Inc.
[exchange-account-1] [exchange-account-1]
PAYTO_URI = payto://iban/SANDBOXX/DE614691?receiver-name=Exchange+Company PAYTO_URI = payto://iban/SANDBOXX/DE546854?receiver-name=Exchange+Company
enable_debit = yes enable_debit = yes
enable_credit = yes enable_credit = yes
@ -19,7 +19,7 @@ HONOR_default = YES
ACTIVE_default = YES ACTIVE_default = YES
[merchant-exchange-default] [merchant-exchange-default]
MASTER_KEY = BV7EQTVVH06981REGB1EZKNBTYK2WMAZWD0SDXK9RSBVNXPBCW9G MASTER_KEY = SA4PMGHM403V1F2TQVFRVKH9BTZ2FBG3V6R7FFVVTYFEFDYG3AX0
EXCHANGE_BASE_URL = http://localhost:8081/ EXCHANGE_BASE_URL = http://localhost:8081/
CURRENCY = TESTKUDOS CURRENCY = TESTKUDOS
@ -155,7 +155,7 @@ UNIXPATH = ${TALER_RUNTIME_DIR}/merchant.http
CONFIG = postgres:///auditor-basedb CONFIG = postgres:///auditor-basedb
[exchange] [exchange]
MASTER_PUBLIC_KEY = BV7EQTVVH06981REGB1EZKNBTYK2WMAZWD0SDXK9RSBVNXPBCW9G MASTER_PUBLIC_KEY = SA4PMGHM403V1F2TQVFRVKH9BTZ2FBG3V6R7FFVVTYFEFDYG3AX0
SIGNKEY_DURATION = 4 weeks SIGNKEY_DURATION = 4 weeks
LOOKAHEAD_SIGN = 32 weeks 1 day LOOKAHEAD_SIGN = 32 weeks 1 day
SIGNKEY_LEGAL_DURATION = 4 weeks SIGNKEY_LEGAL_DURATION = 4 weeks
@ -177,7 +177,7 @@ CONFIG = postgres:///auditor-basedb
[auditor] [auditor]
BASE_URL = http://localhost:8083/ BASE_URL = http://localhost:8083/
TINY_AMOUNT = TESTKUDOS:0.01 TINY_AMOUNT = TESTKUDOS:0.01
PUBLIC_KEY = EB8DHSV6EPXPVDEB3YN9X90MHJ9BEXD0KH5H4CQVC5HTK1AQJ1Y0 PUBLIC_KEY = JZYPE53YY23MQ0HTTV3DYHRABW4RM6SJS1Y0HF2HMSEPEPRJ77WG
[PATHS] [PATHS]
TALER_CACHE_HOME = $TALER_HOME/.cache/taler/ TALER_CACHE_HOME = $TALER_HOME/.cache/taler/

View File

@ -1 +1 @@
BV7EQTVVH06981REGB1EZKNBTYK2WMAZWD0SDXK9RSBVNXPBCW9G SA4PMGHM403V1F2TQVFRVKH9BTZ2FBG3V6R7FFVVTYFEFDYG3AX0

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@ -1 +1 @@
1659871737 1660252041

View File

@ -1 +1 @@
S4VPGN022Y8533H5Y8NGVJHJHRJ6X9WKS2R22GXRVC67NYVWK2S0 QKEQ98KRJ7RGKBZ2FSW0S0Y07G1AVJMBDHPZ23098JC5731MNYY0

File diff suppressed because it is too large Load Diff

View File

@ -30,6 +30,7 @@ taler_exchange_aggregator_SOURCES = \
taler-exchange-aggregator.c taler-exchange-aggregator.c
taler_exchange_aggregator_LDADD = \ taler_exchange_aggregator_LDADD = \
$(LIBGCRYPT_LIBS) \ $(LIBGCRYPT_LIBS) \
$(top_builddir)/src/kyclogic/libtalerkyclogic.la \
$(top_builddir)/src/json/libtalerjson.la \ $(top_builddir)/src/json/libtalerjson.la \
$(top_builddir)/src/util/libtalerutil.la \ $(top_builddir)/src/util/libtalerutil.la \
$(top_builddir)/src/bank-lib/libtalerbank.la \ $(top_builddir)/src/bank-lib/libtalerbank.la \

View File

@ -1,6 +1,6 @@
/* /*
This file is part of TALER This file is part of TALER
Copyright (C) 2016-2021 Taler Systems SA Copyright (C) 2016-2022 Taler Systems SA
TALER is free software; you can redistribute it and/or modify it under the 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 terms of the GNU Affero General Public License as published by the Free Software
@ -26,6 +26,7 @@
#include "taler_exchangedb_lib.h" #include "taler_exchangedb_lib.h"
#include "taler_exchangedb_plugin.h" #include "taler_exchangedb_plugin.h"
#include "taler_json_lib.h" #include "taler_json_lib.h"
#include "taler_kyclogic_lib.h"
#include "taler_bank_service.h" #include "taler_bank_service.h"
@ -42,6 +43,12 @@ struct AggregationUnit
*/ */
struct TALER_MerchantPublicKeyP merchant_pub; struct TALER_MerchantPublicKeyP merchant_pub;
/**
* Transient amount already found aggregated,
* set only if @e have_transient is true.
*/
struct TALER_Amount trans;
/** /**
* Total amount to be transferred, before subtraction of @e fees.wire and rounding down. * Total amount to be transferred, before subtraction of @e fees.wire and rounding down.
*/ */
@ -84,6 +91,19 @@ struct AggregationUnit
*/ */
const struct TALER_EXCHANGEDB_AccountInfo *wa; const struct TALER_EXCHANGEDB_AccountInfo *wa;
/**
* Set to #GNUNET_OK during transient checking
* while everything is OK. Otherwise see return
* value of #do_aggregate().
*/
enum GNUNET_GenericReturnValue ret;
/**
* Do we have an entry in the transient table for
* this aggregation?
*/
bool have_transient;
}; };
@ -151,7 +171,6 @@ static struct TALER_EXCHANGEDB_Plugin *db_plugin;
*/ */
static struct GNUNET_SCHEDULER_Task *task; static struct GNUNET_SCHEDULER_Task *task;
/** /**
* How long should we sleep when idle before trying to find more work? * How long should we sleep when idle before trying to find more work?
*/ */
@ -186,12 +205,12 @@ run_aggregation (void *cls);
/** /**
* Select a shard to work on. * Work on transactions unlocked by KYC.
* *
* @param cls NULL * @param cls NULL
*/ */
static void static void
run_shard (void *cls); drain_kyc_alerts (void *cls);
/** /**
@ -226,6 +245,7 @@ shutdown_task (void *cls)
GNUNET_SCHEDULER_cancel (task); GNUNET_SCHEDULER_cancel (task);
task = NULL; task = NULL;
} }
TALER_KYCLOGIC_kyc_done ();
TALER_EXCHANGEDB_plugin_unload (db_plugin); TALER_EXCHANGEDB_plugin_unload (db_plugin);
db_plugin = NULL; db_plugin = NULL;
TALER_EXCHANGEDB_unload_accounts (); TALER_EXCHANGEDB_unload_accounts ();
@ -353,14 +373,330 @@ release_shard (struct Shard *s)
} }
/**
* Trigger the wire transfer for the @a au_active
* and delete the record of the aggregation.
*
* @param au_active information about the aggregation
*/
static enum GNUNET_DB_QueryStatus
trigger_wire_transfer (const struct AggregationUnit *au_active)
{
enum GNUNET_DB_QueryStatus qs;
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Preparing wire transfer of %s to %s\n",
TALER_amount2s (&au_active->final_amount),
TALER_B2S (&au_active->merchant_pub));
{
void *buf;
size_t buf_size;
TALER_BANK_prepare_transfer (au_active->payto_uri,
&au_active->final_amount,
exchange_base_url,
&au_active->wtid,
&buf,
&buf_size);
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Storing %u bytes of wire prepare data\n",
(unsigned int) buf_size);
/* Commit our intention to execute the wire transfer! */
qs = db_plugin->wire_prepare_data_insert (db_plugin->cls,
au_active->wa->method,
buf,
buf_size);
GNUNET_free (buf);
}
/* Commit the WTID data to 'wire_out' */
if (qs >= 0)
qs = db_plugin->store_wire_transfer_out (db_plugin->cls,
au_active->execution_time,
&au_active->wtid,
&au_active->h_payto,
au_active->wa->section_name,
&au_active->final_amount);
if ( (qs >= 0) &&
au_active->have_transient)
qs = db_plugin->delete_aggregation_transient (db_plugin->cls,
&au_active->h_payto,
&au_active->wtid);
return qs;
}
/**
* Callback to return all applicable amounts for the KYC
* decision to @ a cb.
*
* @param cls a `struct AggregationUnit *`
* @param limit time limit for the iteration
* @param cb function to call with the amounts
* @param cb_cls closure for @a cb
*/
static void
return_relevant_amounts (void *cls,
struct GNUNET_TIME_Absolute limit,
TALER_EXCHANGEDB_KycAmountCallback cb,
void *cb_cls)
{
const struct AggregationUnit *au_active = cls;
enum GNUNET_DB_QueryStatus qs;
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Returning amount %s in KYC check\n",
TALER_amount2s (&au_active->total_amount));
if (GNUNET_OK !=
cb (cb_cls,
&au_active->total_amount,
GNUNET_TIME_absolute_get ()))
return;
qs = db_plugin->select_aggregation_amounts_for_kyc_check (
db_plugin->cls,
&au_active->h_payto,
limit,
cb,
cb_cls);
if (GNUNET_DB_STATUS_HARD_ERROR == qs)
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Failed to select aggregation amounts for KYC limit check!\n");
}
}
/**
* Test if KYC is required for a transfer to @a h_payto.
*
* @param au_active aggregation unit to check for
* @return true if KYC checks are satisfied
*/
static bool
kyc_satisfied (const struct AggregationUnit *au_active)
{
const char *requirement;
uint64_t legi_row;
enum GNUNET_DB_QueryStatus qs;
requirement = TALER_KYCLOGIC_kyc_test_required (
TALER_KYCLOGIC_KYC_TRIGGER_DEPOSIT,
&au_active->h_payto,
db_plugin->select_satisfied_kyc_processes,
db_plugin->cls,
&return_relevant_amounts,
(void *) au_active);
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"KYC requirement for %s is %s\n",
TALER_amount2s (&au_active->total_amount),
requirement);
if (NULL == requirement)
return true;
qs = db_plugin->insert_kyc_requirement_for_account (
db_plugin->cls,
requirement,
&au_active->h_payto,
&legi_row);
if (qs < 0)
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Failed to persist KYC requirement `%s' in DB!\n",
requirement);
}
else
{
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"New legitimization process %llu started\n",
(unsigned long long) legi_row);
}
return false;
}
/**
* Perform the main aggregation work for @a au. Expects to be in
* a working transaction, which the caller must also ultimately commit
* (or rollback) depending on our return value.
*
* @param[in,out] au aggregation unit to work on
* @return #GNUNET_OK if aggregation succeeded,
* #GNUNET_NO to rollback and try again (serialization issue)
* #GNUNET_SYSERR hard error, terminate aggregator process
*/
static enum GNUNET_GenericReturnValue
do_aggregate (struct AggregationUnit *au)
{
enum GNUNET_DB_QueryStatus qs;
au->wa = TALER_EXCHANGEDB_find_account_by_payto_uri (
au->payto_uri);
if (NULL == au->wa)
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"No exchange account configured for `%s', please fix your setup to continue!\n",
au->payto_uri);
global_ret = EXIT_FAILURE;
return GNUNET_SYSERR;
}
{
struct GNUNET_TIME_Timestamp start_date;
struct GNUNET_TIME_Timestamp end_date;
struct TALER_MasterSignatureP master_sig;
qs = db_plugin->get_wire_fee (db_plugin->cls,
au->wa->method,
au->execution_time,
&start_date,
&end_date,
&au->fees,
&master_sig);
if (0 >= qs)
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Could not get wire fees for %s at %s. Aborting run.\n",
au->wa->method,
GNUNET_TIME_timestamp2s (au->execution_time));
global_ret = EXIT_FAILURE;
return GNUNET_SYSERR;
}
}
/* Now try to find other deposits to aggregate */
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Found ready deposit for %s, aggregating by target %s\n",
TALER_B2S (&au->merchant_pub),
au->payto_uri);
qs = db_plugin->select_aggregation_transient (db_plugin->cls,
&au->h_payto,
&au->merchant_pub,
au->wa->section_name,
&au->wtid,
&au->trans);
switch (qs)
{
case GNUNET_DB_STATUS_HARD_ERROR:
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Failed to lookup transient aggregates!\n");
global_ret = EXIT_FAILURE;
return GNUNET_SYSERR;
case GNUNET_DB_STATUS_SOFT_ERROR:
/* serializiability issue, try again */
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Serialization issue, trying again later!\n");
return GNUNET_NO;
case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_NONCE,
&au->wtid,
sizeof (au->wtid));
au->have_transient = false;
break;
case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
au->have_transient = true;
break;
}
qs = db_plugin->aggregate (db_plugin->cls,
&au->h_payto,
&au->merchant_pub,
&au->wtid,
&au->total_amount);
if (GNUNET_DB_STATUS_HARD_ERROR == qs)
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Failed to execute aggregation!\n");
global_ret = EXIT_FAILURE;
return GNUNET_SYSERR;
}
if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
{
/* serializiability issue, try again */
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Serialization issue, trying again later!\n");
return GNUNET_NO;
}
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Aggregation total is %s.\n",
TALER_amount2s (&au->total_amount));
/* Subtract wire transfer fee and round to the unit supported by the
wire transfer method; Check if after rounding down, we still have
an amount to transfer, and if not mark as 'tiny'. */
if (au->have_transient)
GNUNET_assert (0 <=
TALER_amount_add (&au->total_amount,
&au->total_amount,
&au->trans));
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Rounding aggregate of %s\n",
TALER_amount2s (&au->total_amount));
if ( (0 >=
TALER_amount_subtract (&au->final_amount,
&au->total_amount,
&au->fees.wire)) ||
(GNUNET_SYSERR ==
TALER_amount_round_down (&au->final_amount,
&currency_round_unit)) ||
(TALER_amount_is_zero (&au->final_amount)) ||
(! kyc_satisfied (au)) )
{
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Not ready for wire transfer (%d/%s)\n",
qs,
TALER_amount2s (&au->final_amount));
if (au->have_transient)
qs = db_plugin->update_aggregation_transient (db_plugin->cls,
&au->h_payto,
&au->wtid,
&au->total_amount);
else
qs = db_plugin->create_aggregation_transient (db_plugin->cls,
&au->h_payto,
au->wa->section_name,
&au->merchant_pub,
&au->wtid,
&au->total_amount);
if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
{
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Serialization issue, trying again later!\n");
return GNUNET_NO;
}
if (GNUNET_DB_STATUS_HARD_ERROR == qs)
{
GNUNET_break (0);
global_ret = EXIT_FAILURE;
return GNUNET_SYSERR;
}
/* commit */
return GNUNET_OK;
}
qs = trigger_wire_transfer (au);
switch (qs)
{
case GNUNET_DB_STATUS_SOFT_ERROR:
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Serialization issue during aggregation; trying again later!\n");
return GNUNET_NO;
case GNUNET_DB_STATUS_HARD_ERROR:
GNUNET_break (0);
global_ret = EXIT_FAILURE;
return GNUNET_SYSERR;
default:
return GNUNET_OK;
}
}
static void static void
run_aggregation (void *cls) run_aggregation (void *cls)
{ {
struct Shard *s = cls; struct Shard *s = cls;
struct AggregationUnit au_active; struct AggregationUnit au_active;
enum GNUNET_DB_QueryStatus qs; enum GNUNET_DB_QueryStatus qs;
struct TALER_Amount trans; enum GNUNET_GenericReturnValue ret;
bool have_transient = true; /* squash compiler warning */
task = NULL; task = NULL;
GNUNET_log (GNUNET_ERROR_TYPE_INFO, GNUNET_log (GNUNET_ERROR_TYPE_INFO,
@ -384,7 +720,6 @@ run_aggregation (void *cls)
db_plugin->cls, db_plugin->cls,
s->shard_start, s->shard_start,
s->shard_end, s->shard_end,
kyc_off ? true : false,
&au_active.merchant_pub, &au_active.merchant_pub,
&au_active.payto_uri); &au_active.payto_uri);
switch (qs) switch (qs)
@ -432,10 +767,10 @@ run_aggregation (void *cls)
/* If we ended up doing zero work, sleep a bit */ /* If we ended up doing zero work, sleep a bit */
if (0 == counter) if (0 == counter)
task = GNUNET_SCHEDULER_add_delayed (aggregator_idle_sleep_interval, task = GNUNET_SCHEDULER_add_delayed (aggregator_idle_sleep_interval,
&run_shard, &drain_kyc_alerts,
NULL); NULL);
else else
task = GNUNET_SCHEDULER_add_now (&run_shard, task = GNUNET_SCHEDULER_add_now (&drain_kyc_alerts,
NULL); NULL);
return; return;
} }
@ -444,254 +779,31 @@ run_aggregation (void *cls)
/* continued below */ /* continued below */
break; break;
} }
au_active.wa = TALER_EXCHANGEDB_find_account_by_payto_uri (
au_active.payto_uri);
if (NULL == au_active.wa)
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"No exchange account configured for `%s', please fix your setup to continue!\n",
au_active.payto_uri);
global_ret = EXIT_FAILURE;
GNUNET_SCHEDULER_shutdown ();
db_plugin->rollback (db_plugin->cls);
release_shard (s);
return;
}
{
struct GNUNET_TIME_Timestamp start_date;
struct GNUNET_TIME_Timestamp end_date;
struct TALER_MasterSignatureP master_sig;
qs = db_plugin->get_wire_fee (db_plugin->cls,
au_active.wa->method,
au_active.execution_time,
&start_date,
&end_date,
&au_active.fees,
&master_sig);
if (0 >= qs)
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Could not get wire fees for %s at %s. Aborting run.\n",
au_active.wa->method,
GNUNET_TIME_timestamp2s (au_active.execution_time));
global_ret = EXIT_FAILURE;
GNUNET_SCHEDULER_shutdown ();
db_plugin->rollback (db_plugin->cls);
release_shard (s);
return;
}
}
/* Now try to find other deposits to aggregate */
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Found ready deposit for %s, aggregating by target %s\n",
TALER_B2S (&au_active.merchant_pub),
au_active.payto_uri);
TALER_payto_hash (au_active.payto_uri, TALER_payto_hash (au_active.payto_uri,
&au_active.h_payto); &au_active.h_payto);
ret = do_aggregate (&au_active);
qs = db_plugin->select_aggregation_transient (db_plugin->cls,
&au_active.h_payto,
au_active.wa->section_name,
&au_active.wtid,
&trans);
switch (qs)
{
case GNUNET_DB_STATUS_HARD_ERROR:
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Failed to lookup transient aggregates!\n");
cleanup_au (&au_active);
db_plugin->rollback (db_plugin->cls);
global_ret = EXIT_FAILURE;
GNUNET_SCHEDULER_shutdown ();
release_shard (s);
return;
case GNUNET_DB_STATUS_SOFT_ERROR:
/* serializiability issue, try again */
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Serialization issue, trying again later!\n");
db_plugin->rollback (db_plugin->cls);
cleanup_au (&au_active);
GNUNET_assert (NULL == task);
task = GNUNET_SCHEDULER_add_now (&run_aggregation,
s);
return;
case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_NONCE,
&au_active.wtid,
sizeof (au_active.wtid));
have_transient = false;
break;
case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
have_transient = true;
break;
}
qs = db_plugin->aggregate (db_plugin->cls,
&au_active.h_payto,
&au_active.merchant_pub,
&au_active.wtid,
&au_active.total_amount);
if (GNUNET_DB_STATUS_HARD_ERROR == qs)
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Failed to execute aggregation!\n");
cleanup_au (&au_active);
db_plugin->rollback (db_plugin->cls);
global_ret = EXIT_FAILURE;
GNUNET_SCHEDULER_shutdown ();
release_shard (s);
return;
}
if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
{
/* serializiability issue, try again */
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Serialization issue, trying again later!\n");
db_plugin->rollback (db_plugin->cls);
cleanup_au (&au_active);
GNUNET_assert (NULL == task);
task = GNUNET_SCHEDULER_add_now (&run_aggregation,
s);
return;
}
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Aggregation total is %s.\n",
TALER_amount2s (&au_active.total_amount));
/* Subtract wire transfer fee and round to the unit supported by the
wire transfer method; Check if after rounding down, we still have
an amount to transfer, and if not mark as 'tiny'. */
if (have_transient)
GNUNET_assert (0 <=
TALER_amount_add (&au_active.total_amount,
&au_active.total_amount,
&trans));
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Rounding aggregate of %s\n",
TALER_amount2s (&au_active.total_amount));
if ( (0 >=
TALER_amount_subtract (&au_active.final_amount,
&au_active.total_amount,
&au_active.fees.wire)) ||
(GNUNET_SYSERR ==
TALER_amount_round_down (&au_active.final_amount,
&currency_round_unit)) ||
(TALER_amount_is_zero (&au_active.final_amount)) )
{
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Aggregate value too low for transfer (%d/%s)\n",
qs,
TALER_amount2s (&au_active.final_amount));
if (have_transient)
qs = db_plugin->update_aggregation_transient (db_plugin->cls,
&au_active.h_payto,
&au_active.wtid,
&au_active.total_amount);
else
qs = db_plugin->create_aggregation_transient (db_plugin->cls,
&au_active.h_payto,
au_active.wa->section_name,
&au_active.wtid,
&au_active.total_amount);
if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
{
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Serialization issue, trying again later!\n");
db_plugin->rollback (db_plugin->cls);
cleanup_au (&au_active);
/* start again */
GNUNET_assert (NULL == task);
task = GNUNET_SCHEDULER_add_now (&run_aggregation,
s);
return;
}
if (GNUNET_DB_STATUS_HARD_ERROR == qs)
{
GNUNET_break (0);
db_plugin->rollback (db_plugin->cls);
cleanup_au (&au_active);
global_ret = EXIT_FAILURE;
GNUNET_SCHEDULER_shutdown ();
release_shard (s);
return;
}
/* commit */
(void) commit_or_warn ();
cleanup_au (&au_active);
/* start again */
GNUNET_assert (NULL == task);
task = GNUNET_SCHEDULER_add_now (&run_aggregation,
s);
return;
}
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Preparing wire transfer of %s to %s\n",
TALER_amount2s (&au_active.final_amount),
TALER_B2S (&au_active.merchant_pub));
{
void *buf;
size_t buf_size;
TALER_BANK_prepare_transfer (au_active.payto_uri,
&au_active.final_amount,
exchange_base_url,
&au_active.wtid,
&buf,
&buf_size);
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Storing %u bytes of wire prepare data\n",
(unsigned int) buf_size);
/* Commit our intention to execute the wire transfer! */
qs = db_plugin->wire_prepare_data_insert (db_plugin->cls,
au_active.wa->method,
buf,
buf_size);
GNUNET_free (buf);
}
/* Commit the WTID data to 'wire_out' */
if (qs >= 0)
qs = db_plugin->store_wire_transfer_out (db_plugin->cls,
au_active.execution_time,
&au_active.wtid,
&au_active.h_payto,
au_active.wa->section_name,
&au_active.final_amount);
if ( (qs >= 0) &&
have_transient)
qs = db_plugin->delete_aggregation_transient (db_plugin->cls,
&au_active.h_payto,
&au_active.wtid);
cleanup_au (&au_active); cleanup_au (&au_active);
switch (ret)
if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
{ {
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, case GNUNET_SYSERR:
"Serialization issue for prepared wire data; trying again later!\n"); GNUNET_SCHEDULER_shutdown ();
db_plugin->rollback (db_plugin->cls);
release_shard (s);
return;
case GNUNET_NO:
db_plugin->rollback (db_plugin->cls); db_plugin->rollback (db_plugin->cls);
/* start again */
GNUNET_assert (NULL == task); GNUNET_assert (NULL == task);
task = GNUNET_SCHEDULER_add_now (&run_aggregation, task = GNUNET_SCHEDULER_add_now (&run_aggregation,
s); s);
return; return;
} case GNUNET_OK:
if (GNUNET_DB_STATUS_HARD_ERROR == qs) /* continued below */
{ break;
GNUNET_break (0);
db_plugin->rollback (db_plugin->cls);
/* die hard */
global_ret = EXIT_FAILURE;
GNUNET_SCHEDULER_shutdown ();
release_shard (s);
return;
} }
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Stored wire transfer out instructions\n"); "Committing aggregation result\n");
/* Now we can finally commit the overall transaction, as we are /* Now we can finally commit the overall transaction, as we are
again consistent if all of this passes. */ again consistent if all of this passes. */
@ -699,8 +811,8 @@ run_aggregation (void *cls)
{ {
case GNUNET_DB_STATUS_SOFT_ERROR: case GNUNET_DB_STATUS_SOFT_ERROR:
/* try again */ /* try again */
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Commit issue for prepared wire data; trying again later!\n"); "Serialization issue on commit; trying again later!\n");
GNUNET_assert (NULL == task); GNUNET_assert (NULL == task);
task = GNUNET_SCHEDULER_add_now (&run_aggregation, task = GNUNET_SCHEDULER_add_now (&run_aggregation,
s); s);
@ -714,7 +826,7 @@ run_aggregation (void *cls)
return; return;
case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
GNUNET_log (GNUNET_ERROR_TYPE_INFO, GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Preparation complete, going again\n"); "Commit complete, going again\n");
GNUNET_assert (NULL == task); GNUNET_assert (NULL == task);
task = GNUNET_SCHEDULER_add_now (&run_aggregation, task = GNUNET_SCHEDULER_add_now (&run_aggregation,
s); s);
@ -791,6 +903,197 @@ run_shard (void *cls)
} }
/**
* Function called on transient aggregations matching
* a particular hash of a payto URI.
*
* @param cls
* @param payto_uri corresponding payto URI
* @param wtid wire transfer identifier of transient aggregation
* @param merchant_pub public key of the merchant
* @param total amount aggregated so far
* @return true to continue to iterate
*/
static bool
handle_transient_cb (
void *cls,
const char *payto_uri,
const struct TALER_WireTransferIdentifierRawP *wtid,
const struct TALER_MerchantPublicKeyP *merchant_pub,
const struct TALER_Amount *total)
{
struct AggregationUnit *au = cls;
if (GNUNET_OK != au->ret)
{
GNUNET_break (0);
return false;
}
au->payto_uri = GNUNET_strdup (payto_uri);
au->wtid = *wtid;
au->merchant_pub = *merchant_pub;
au->trans = *total;
au->have_transient = true;
au->ret = do_aggregate (au);
GNUNET_free (au->payto_uri);
return (GNUNET_OK == au->ret);
}
static void
drain_kyc_alerts (void *cls)
{
enum GNUNET_DB_QueryStatus qs;
struct AggregationUnit au;
(void) cls;
task = NULL;
memset (&au,
0,
sizeof (au));
au.execution_time = GNUNET_TIME_timestamp_get ();
if (GNUNET_SYSERR ==
db_plugin->preflight (db_plugin->cls))
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Failed to obtain database connection!\n");
global_ret = EXIT_FAILURE;
GNUNET_SCHEDULER_shutdown ();
return;
}
if (GNUNET_OK !=
db_plugin->start (db_plugin->cls,
"handle kyc alerts"))
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Failed to start database transaction!\n");
global_ret = EXIT_FAILURE;
GNUNET_SCHEDULER_shutdown ();
return;
}
while (1)
{
qs = db_plugin->drain_kyc_alert (db_plugin->cls,
1,
&au.h_payto);
switch (qs)
{
case GNUNET_DB_STATUS_HARD_ERROR:
GNUNET_break (0);
db_plugin->rollback (db_plugin->cls);
GNUNET_assert (NULL == task);
task = GNUNET_SCHEDULER_add_now (&drain_kyc_alerts,
NULL);
return;
case GNUNET_DB_STATUS_SOFT_ERROR:
db_plugin->rollback (db_plugin->cls);
GNUNET_assert (NULL == task);
task = GNUNET_SCHEDULER_add_now (&drain_kyc_alerts,
NULL);
return;
case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
qs = db_plugin->commit (db_plugin->cls);
if (qs < 0)
GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
"Failed to commit KYC drain\n");
GNUNET_assert (NULL == task);
task = GNUNET_SCHEDULER_add_now (&run_shard,
NULL);
return;
case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
/* handled below */
break;
}
au.ret = GNUNET_OK;
qs = db_plugin->find_aggregation_transient (db_plugin->cls,
&au.h_payto,
&handle_transient_cb,
&au);
switch (qs)
{
case GNUNET_DB_STATUS_HARD_ERROR:
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Failed to lookup transient aggregates!\n");
db_plugin->rollback (db_plugin->cls);
GNUNET_assert (NULL == task);
task = GNUNET_SCHEDULER_add_now (&drain_kyc_alerts,
NULL);
return;
case GNUNET_DB_STATUS_SOFT_ERROR:
/* serializiability issue, try again */
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Serialization issue, trying again later!\n");
db_plugin->rollback (db_plugin->cls);
GNUNET_assert (NULL == task);
task = GNUNET_SCHEDULER_add_now (&drain_kyc_alerts,
NULL);
return;
case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
continue; /* while (1) */
default:
break;
}
break;
} /* while(1) */
{
enum GNUNET_GenericReturnValue ret;
ret = au.ret;
cleanup_au (&au);
switch (ret)
{
case GNUNET_SYSERR:
GNUNET_break (0);
GNUNET_SCHEDULER_shutdown ();
db_plugin->rollback (db_plugin->cls); /* just in case */
return;
case GNUNET_NO:
db_plugin->rollback (db_plugin->cls);
GNUNET_assert (NULL == task);
task = GNUNET_SCHEDULER_add_now (&drain_kyc_alerts,
NULL);
return;
case GNUNET_OK:
/* continued below */
break;
}
}
switch (commit_or_warn ())
{
case GNUNET_DB_STATUS_SOFT_ERROR:
/* try again */
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Serialization issue on commit; trying again later!\n");
GNUNET_assert (NULL == task);
task = GNUNET_SCHEDULER_add_now (&drain_kyc_alerts,
NULL);
return;
case GNUNET_DB_STATUS_HARD_ERROR:
GNUNET_break (0);
global_ret = EXIT_FAILURE;
GNUNET_SCHEDULER_shutdown ();
db_plugin->rollback (db_plugin->cls); /* just in case */
return;
case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Commit complete, going again\n");
GNUNET_assert (NULL == task);
task = GNUNET_SCHEDULER_add_now (&drain_kyc_alerts,
NULL);
return;
default:
GNUNET_break (0);
global_ret = EXIT_FAILURE;
GNUNET_SCHEDULER_shutdown ();
db_plugin->rollback (db_plugin->cls); /* just in case */
return;
}
}
/** /**
* First task. * First task.
* *
@ -811,7 +1114,8 @@ run (void *cls,
(void) cfgfile; (void) cfgfile;
cfg = c; cfg = c;
if (GNUNET_OK != parse_aggregator_config ()) if (GNUNET_OK !=
parse_aggregator_config ())
{ {
cfg = NULL; cfg = NULL;
global_ret = EXIT_NOTCONFIGURED; global_ret = EXIT_NOTCONFIGURED;
@ -832,11 +1136,18 @@ run (void *cls,
shard_size = 1U + INT32_MAX; shard_size = 1U + INT32_MAX;
else else
shard_size = (uint32_t) ass; shard_size = (uint32_t) ass;
GNUNET_assert (NULL == task); if (GNUNET_OK !=
task = GNUNET_SCHEDULER_add_now (&run_shard, TALER_KYCLOGIC_kyc_init (cfg))
NULL); {
cfg = NULL;
global_ret = EXIT_NOTCONFIGURED;
return;
}
GNUNET_SCHEDULER_add_shutdown (&shutdown_task, GNUNET_SCHEDULER_add_shutdown (&shutdown_task,
cls); NULL);
GNUNET_assert (NULL == task);
task = GNUNET_SCHEDULER_add_now (&drain_kyc_alerts,
NULL);
} }

View File

@ -27,6 +27,7 @@
#include <sched.h> #include <sched.h>
#include <sys/resource.h> #include <sys/resource.h>
#include <limits.h> #include <limits.h>
#include "taler_kyclogic_lib.h"
#include "taler_mhd_lib.h" #include "taler_mhd_lib.h"
#include "taler-exchange-httpd_auditors.h" #include "taler-exchange-httpd_auditors.h"
#include "taler-exchange-httpd_batch-deposit.h" #include "taler-exchange-httpd_batch-deposit.h"
@ -1215,13 +1216,14 @@ handle_mhd_request (void *cls,
.url = "kyc-check", .url = "kyc-check",
.method = MHD_HTTP_METHOD_GET, .method = MHD_HTTP_METHOD_GET,
.handler.get = &TEH_handler_kyc_check, .handler.get = &TEH_handler_kyc_check,
.nargs = 1 .nargs = 2
}, },
{ {
.url = "kyc-proof", .url = "kyc-proof",
.method = MHD_HTTP_METHOD_GET, .method = MHD_HTTP_METHOD_GET,
.handler.get = &TEH_handler_kyc_proof, .handler.get = &TEH_handler_kyc_proof,
.nargs = 1 .nargs = 128,
.nargs_is_upper_bound = true
}, },
{ {
.url = "kyc-wallet", .url = "kyc-wallet",
@ -1680,6 +1682,11 @@ parse_kyc_oauth_cfg (void)
static enum GNUNET_GenericReturnValue static enum GNUNET_GenericReturnValue
exchange_serve_process_config (void) exchange_serve_process_config (void)
{ {
if (GNUNET_OK !=
TALER_KYCLOGIC_kyc_init (TEH_cfg))
{
return GNUNET_SYSERR;
}
{ {
char *kyc_mode; char *kyc_mode;
@ -2094,8 +2101,12 @@ do_shutdown (void *cls)
TEH_purses_get_cleanup (); TEH_purses_get_cleanup ();
TEH_kyc_check_cleanup (); TEH_kyc_check_cleanup ();
TEH_kyc_proof_cleanup (); TEH_kyc_proof_cleanup ();
TALER_KYCLOGIC_kyc_done ();
if (NULL != mhd) if (NULL != mhd)
{
MHD_stop_daemon (mhd); MHD_stop_daemon (mhd);
mhd = NULL;
}
TEH_wire_done (); TEH_wire_done ();
TEH_extensions_done (); TEH_extensions_done ();
TEH_keys_finished (); TEH_keys_finished ();

View File

@ -27,6 +27,7 @@
#include <gnunet/gnunet_util_lib.h> #include <gnunet/gnunet_util_lib.h>
#include <jansson.h> #include <jansson.h>
#include "taler_json_lib.h" #include "taler_json_lib.h"
#include "taler_kyclogic_lib.h"
#include "taler_mhd_lib.h" #include "taler_mhd_lib.h"
#include "taler-exchange-httpd_batch-withdraw.h" #include "taler-exchange-httpd_batch-withdraw.h"
#include "taler-exchange-httpd_responses.h" #include "taler-exchange-httpd_responses.h"
@ -86,6 +87,17 @@ struct BatchWithdrawContext
*/ */
struct PlanchetContext *planchets; 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;
/** /**
* Total amount from all coins with fees. * Total amount from all coins with fees.
*/ */
@ -99,6 +111,45 @@ struct BatchWithdrawContext
}; };
/**
* 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 implementing withdraw transaction. Runs the * Function implementing withdraw transaction. Runs the
* transaction logic; IF it returns a non-error code, the transaction * transaction logic; IF it returns a non-error code, the transaction
@ -127,15 +178,46 @@ batch_withdraw_transaction (void *cls,
enum GNUNET_DB_QueryStatus qs; enum GNUNET_DB_QueryStatus qs;
bool balance_ok = false; bool balance_ok = false;
bool found = false; bool found = false;
const char *kyc_required;
now = GNUNET_TIME_timestamp_get (); wc->now = GNUNET_TIME_timestamp_get ();
qs = TEH_plugin->reserves_get_origin (TEH_plugin->cls,
wc->reserve_pub,
&wc->h_payto);
if (qs < 0)
return qs;
if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
{
*mhd_ret = TALER_MHD_reply_with_error (connection,
MHD_HTTP_NOT_FOUND,
TALER_EC_EXCHANGE_GENERIC_RESERVE_UNKNOWN,
NULL);
return GNUNET_DB_STATUS_HARD_ERROR;
}
kyc_required = 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);
if (NULL != kyc_required)
{
/* insert KYC requirement into DB! */
wc->kyc.ok = false;
return TEH_plugin->insert_kyc_requirement_for_account (
TEH_plugin->cls,
kyc_required,
&wc->h_payto,
&wc->kyc.payment_target_uuid);
}
wc->kyc.ok = true;
qs = TEH_plugin->do_batch_withdraw (TEH_plugin->cls, qs = TEH_plugin->do_batch_withdraw (TEH_plugin->cls,
now, now,
wc->reserve_pub, wc->reserve_pub,
&wc->batch_total, &wc->batch_total,
&found, &found,
&balance_ok, &balance_ok,
&wc->kyc,
&ruuid); &ruuid);
if (0 > qs) if (0 > qs)
{ {
@ -164,55 +246,6 @@ batch_withdraw_transaction (void *cls,
return GNUNET_DB_STATUS_HARD_ERROR; return GNUNET_DB_STATUS_HARD_ERROR;
} }
if ( (TEH_KYC_NONE != TEH_kyc_config.mode) &&
(! wc->kyc.ok) &&
(TALER_EXCHANGEDB_KYC_W2W == wc->kyc.type) )
{
/* Wallet-to-wallet payments _always_ require KYC */
*mhd_ret = TALER_MHD_REPLY_JSON_PACK (
connection,
MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS,
GNUNET_JSON_pack_uint64 ("payment_target_uuid",
wc->kyc.payment_target_uuid));
return GNUNET_DB_STATUS_HARD_ERROR;
}
if ( (TEH_KYC_NONE != TEH_kyc_config.mode) &&
(! wc->kyc.ok) &&
(TALER_EXCHANGEDB_KYC_WITHDRAW == wc->kyc.type) &&
(! GNUNET_TIME_relative_is_zero (TEH_kyc_config.withdraw_period)) )
{
/* Withdraws require KYC if above threshold */
enum GNUNET_DB_QueryStatus qs2;
bool below_limit;
qs2 = TEH_plugin->do_withdraw_limit_check (
TEH_plugin->cls,
ruuid,
GNUNET_TIME_absolute_subtract (now.abs_time,
TEH_kyc_config.withdraw_period),
&TEH_kyc_config.withdraw_limit,
&below_limit);
if (0 > qs2)
{
GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs2);
if (GNUNET_DB_STATUS_HARD_ERROR == qs2)
*mhd_ret = TALER_MHD_reply_with_error (connection,
MHD_HTTP_INTERNAL_SERVER_ERROR,
TALER_EC_GENERIC_DB_FETCH_FAILED,
"do_withdraw_limit_check");
return qs2;
}
if (! below_limit)
{
*mhd_ret = TALER_MHD_REPLY_JSON_PACK (
connection,
MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS,
GNUNET_JSON_pack_uint64 ("payment_target_uuid",
wc->kyc.payment_target_uuid));
return GNUNET_DB_STATUS_HARD_ERROR;
}
}
/* Add information about each planchet in the batch */ /* Add information about each planchet in the batch */
for (unsigned int i = 0; i<wc->planchets_length; i++) for (unsigned int i = 0; i<wc->planchets_length; i++)
{ {
@ -291,6 +324,16 @@ generate_reply_success (const struct TEH_RequestContext *rc,
{ {
json_t *sigs; json_t *sigs;
if (! wc->kyc.ok)
{
/* KYC required */
return TALER_MHD_REPLY_JSON_PACK (
rc->connection,
MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS,
GNUNET_JSON_pack_uint64 ("payment_target_uuid",
wc->kyc.payment_target_uuid));
}
sigs = json_array (); sigs = json_array ();
GNUNET_assert (NULL != sigs); GNUNET_assert (NULL != sigs);
for (unsigned int i = 0; i<wc->planchets_length; i++) for (unsigned int i = 0; i<wc->planchets_length; i++)
@ -633,7 +676,6 @@ TEH_handler_batch_withdraw (struct TEH_RequestContext *rc,
TALER_amount_set_zero (TEH_currency, TALER_amount_set_zero (TEH_currency,
&wc.batch_total)); &wc.batch_total));
wc.reserve_pub = reserve_pub; wc.reserve_pub = reserve_pub;
{ {
enum GNUNET_GenericReturnValue res; enum GNUNET_GenericReturnValue res;

View File

@ -184,7 +184,7 @@ deposit_transaction (void *cls,
} }
if (in_conflict) if (in_conflict)
{ {
/* FIXME #7267: conficting contract != insufficient funds */ /* FIXME #7267: conflicting contract != insufficient funds */
*mhd_ret *mhd_ret
= TEH_RESPONSE_reply_coin_insufficient_funds ( = TEH_RESPONSE_reply_coin_insufficient_funds (
connection, connection,
@ -426,7 +426,7 @@ TEH_handler_deposit (struct MHD_Connection *connection,
&h_wire, &h_wire,
&deposit.h_contract_terms, &deposit.h_contract_terms,
&deposit.coin.h_age_commitment, &deposit.coin.h_age_commitment,
NULL /* h_extensions! */, NULL /* FIXME: h_extensions! */,
&deposit.coin.denom_pub_hash, &deposit.coin.denom_pub_hash,
deposit.timestamp, deposit.timestamp,
&deposit.merchant_pub, &deposit.merchant_pub,
@ -481,7 +481,7 @@ TEH_handler_deposit (struct MHD_Connection *connection,
res = reply_deposit_success (connection, res = reply_deposit_success (connection,
&deposit.coin.coin_pub, &deposit.coin.coin_pub,
&h_wire, &h_wire,
NULL /* h_extensions! */, NULL /* FIXME: h_extensions! */,
&deposit.h_contract_terms, &deposit.h_contract_terms,
dc.exchange_timestamp, dc.exchange_timestamp,
deposit.refund_deadline, deposit.refund_deadline,

View File

@ -40,12 +40,12 @@ struct DepositWtidContext
/** /**
* Hash over the proposal data of the contract for which this deposit is made. * Hash over the proposal data of the contract for which this deposit is made.
*/ */
struct TALER_PrivateContractHashP h_contract_terms GNUNET_PACKED; struct TALER_PrivateContractHashP h_contract_terms;
/** /**
* Hash over the wiring information of the merchant. * Hash over the wiring information of the merchant.
*/ */
struct TALER_MerchantWireHashP h_wire GNUNET_PACKED; struct TALER_MerchantWireHashP h_wire;
/** /**
* The Merchant's public key. The deposit inquiry request is to be * The Merchant's public key. The deposit inquiry request is to be
@ -251,8 +251,12 @@ handle_track_transaction_request (
return TALER_MHD_REPLY_JSON_PACK ( return TALER_MHD_REPLY_JSON_PACK (
connection, connection,
MHD_HTTP_ACCEPTED, MHD_HTTP_ACCEPTED,
GNUNET_JSON_pack_uint64 ("payment_target_uuid", GNUNET_JSON_pack_allow_null (
ctx->kyc.payment_target_uuid), (0 == ctx->kyc.payment_target_uuid)
? GNUNET_JSON_pack_string ("legitimization_uuid",
NULL)
: GNUNET_JSON_pack_uint64 ("legitimization_uuid",
ctx->kyc.payment_target_uuid)),
GNUNET_JSON_pack_bool ("kyc_ok", GNUNET_JSON_pack_bool ("kyc_ok",
ctx->kyc.ok), ctx->kyc.ok),
GNUNET_JSON_pack_timestamp ("execution_time", GNUNET_JSON_pack_timestamp ("execution_time",

View File

@ -21,6 +21,7 @@
#include "platform.h" #include "platform.h"
#include "taler_json_lib.h" #include "taler_json_lib.h"
#include "taler_mhd_lib.h" #include "taler_mhd_lib.h"
#include "taler_kyclogic_lib.h"
#include "taler_dbevents.h" #include "taler_dbevents.h"
#include "taler-exchange-httpd.h" #include "taler-exchange-httpd.h"
#include "taler-exchange-httpd_keys.h" #include "taler-exchange-httpd_keys.h"
@ -1721,6 +1722,27 @@ setup_general_response_headers (struct TEH_KeyStateHandle *ksh,
} }
/**
* Function called with wallet balance threshholds.
*
* @param[in,out] cls a `json **` where to put the array of json amounts discovered
* @param threshold another threshold amount to add
*/
static void
wallet_threshold_cb (void *cls,
const struct TALER_Amount *threshold)
{
json_t **ret = cls;
if (NULL == *ret)
*ret = json_array ();
GNUNET_assert (0 ==
json_array_append_new (*ret,
TALER_JSON_from_amount (
threshold)));
}
/** /**
* Initialize @a krd using the given values for @a signkeys, * Initialize @a krd using the given values for @a signkeys,
* @a recoup and @a denoms. * @a recoup and @a denoms.
@ -1854,17 +1876,20 @@ create_krd (struct TEH_KeyStateHandle *ksh,
GNUNET_assert (NULL != keys); GNUNET_assert (NULL != keys);
/* Set wallet limit if KYC is configured */ /* Set wallet limit if KYC is configured */
if ( (TEH_KYC_NONE != TEH_kyc_config.mode) &&
(GNUNET_OK ==
TALER_amount_is_valid (&TEH_kyc_config.wallet_balance_limit)) )
{ {
GNUNET_assert ( json_t *wblwk = NULL;
0 ==
json_object_set_new ( TALER_KYCLOGIC_kyc_iterate_thresholds (
keys, TALER_KYCLOGIC_KYC_TRIGGER_WALLET_BALANCE,
"wallet_balance_limit_without_kyc", &wallet_threshold_cb,
TALER_JSON_from_amount ( &wblwk);
&TEH_kyc_config.wallet_balance_limit))); if (NULL != wblwk)
GNUNET_assert (
0 ==
json_object_set_new (
keys,
"wallet_balance_limit_without_kyc",
wblwk));
} }
/* Signal support for the configured, enabled extensions. */ /* Signal support for the configured, enabled extensions. */

View File

@ -1,6 +1,6 @@
/* /*
This file is part of TALER This file is part of TALER
Copyright (C) 2021 Taler Systems SA Copyright (C) 2021-2022 Taler Systems SA
TALER is free software; you can redistribute it and/or modify it under the 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 terms of the GNU Affero General Public License as published by the Free Software
@ -25,6 +25,7 @@
#include <microhttpd.h> #include <microhttpd.h>
#include <pthread.h> #include <pthread.h>
#include "taler_json_lib.h" #include "taler_json_lib.h"
#include "taler_kyclogic_lib.h"
#include "taler_mhd_lib.h" #include "taler_mhd_lib.h"
#include "taler_signatures.h" #include "taler_signatures.h"
#include "taler_dbevents.h" #include "taler_dbevents.h"
@ -53,6 +54,17 @@ struct KycPoller
*/ */
struct MHD_Connection *connection; struct MHD_Connection *connection;
/**
* Logic for @e ih
*/
struct TALER_KYCLOGIC_Plugin *ih_logic;
/**
* Handle to asynchronously running KYC initiation
* request.
*/
struct TALER_KYCLOGIC_InitiateHandle *ih;
/** /**
* Subscription for the database event we are * Subscription for the database event we are
* waiting for. * waiting for.
@ -62,12 +74,7 @@ struct KycPoller
/** /**
* UUID being checked. * UUID being checked.
*/ */
uint64_t auth_payment_target_uuid; uint64_t legitimization_uuid;
/**
* Current KYC status.
*/
struct TALER_EXCHANGEDB_KycStatus kyc;
/** /**
* Hash of the payto:// URI we are confirming to * Hash of the payto:// URI we are confirming to
@ -75,21 +82,46 @@ struct KycPoller
*/ */
struct TALER_PaytoHashP h_payto; struct TALER_PaytoHashP h_payto;
/**
* Payto URL as a string, as given to us by t
*/
const char *hps;
/** /**
* When will this request time out? * When will this request time out?
*/ */
struct GNUNET_TIME_Absolute timeout; struct GNUNET_TIME_Absolute timeout;
/**
* Type of KYC check required for this client.
*/
char *required;
/**
* Set to starting URL of KYC process if KYC is required.
*/
char *kyc_url;
/**
* Set to error details, on error (@ec not TALER_EC_NONE).
*/
char *hint;
/**
* Set to error encountered with KYC logic, if any.
*/
enum TALER_ErrorCode ec;
/** /**
* True if we are still suspended. * True if we are still suspended.
*/ */
bool suspended; bool suspended;
/**
* True if KYC was required but is fully satisfied.
*/
bool found;
/**
* True if we once tried the KYC initiation.
*/
bool ih_done;
}; };
@ -114,6 +146,11 @@ TEH_kyc_check_cleanup ()
GNUNET_CONTAINER_DLL_remove (kyp_head, GNUNET_CONTAINER_DLL_remove (kyp_head,
kyp_tail, kyp_tail,
kyp); kyp);
if (NULL != kyp->ih)
{
kyp->ih_logic->initiate_cancel (kyp->ih);
kyp->ih = NULL;
}
if (kyp->suspended) if (kyp->suspended)
{ {
kyp->suspended = false; kyp->suspended = false;
@ -143,10 +180,83 @@ kyp_cleanup (struct TEH_RequestContext *rc)
kyp->eh); kyp->eh);
kyp->eh = NULL; kyp->eh = NULL;
} }
if (NULL != kyp->ih)
{
kyp->ih_logic->initiate_cancel (kyp->ih);
kyp->ih = NULL;
}
GNUNET_free (kyp->kyc_url);
GNUNET_free (kyp->hint);
GNUNET_free (kyp->required);
GNUNET_free (kyp); GNUNET_free (kyp);
} }
/**
* Function called with the result of a KYC initiation
* operation.
*
* @param cls closure with our `struct KycPoller *`
* @param ec #TALER_EC_NONE on success
* @param redirect_url set to where to redirect the user on success, NULL on failure
* @param provider_user_id set to user ID at the provider, or NULL if not supported or unknown
* @param provider_legitimization_id set to legitimization process ID at the provider, or NULL if not supported or unknown
* @param error_msg_hint set to additional details to return to user, NULL on success
*/
static void
initiate_cb (
void *cls,
enum TALER_ErrorCode ec,
const char *redirect_url,
const char *provider_user_id,
const char *provider_legitimization_id,
const char *error_msg_hint)
{
struct KycPoller *kyp = cls;
enum GNUNET_DB_QueryStatus qs;
kyp->ih = NULL;
kyp->ih_done = true;
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"KYC initiation completed with status %d (%s)\n",
ec,
(TALER_EC_NONE == ec)
? redirect_url
: error_msg_hint);
kyp->ec = ec;
if (TALER_EC_NONE == ec)
{
kyp->kyc_url = GNUNET_strdup (redirect_url);
}
else
{
kyp->hint = GNUNET_strdup (error_msg_hint);
}
qs = TEH_plugin->update_kyc_requirement_by_row (
TEH_plugin->cls,
kyp->legitimization_uuid,
kyp->required,
&kyp->h_payto,
provider_user_id,
provider_legitimization_id,
GNUNET_TIME_UNIT_ZERO_ABS);
if (qs < 0)
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"KYC requirement update failed for %s with status %d at %s:%u\n",
TALER_B2S (&kyp->h_payto),
qs,
__FILE__,
__LINE__);
GNUNET_assert (kyp->suspended);
kyp->suspended = false;
GNUNET_CONTAINER_DLL_remove (kyp_head,
kyp_tail,
kyp);
MHD_resume_connection (kyp->connection);
TALER_MHD_daemon_trigger ();
}
/** /**
* Function implementing database transaction to check wallet's KYC status. * Function implementing database transaction to check wallet's KYC status.
* Runs the transaction logic; IF it returns a non-error code, the transaction * Runs the transaction logic; IF it returns a non-error code, the transaction
@ -168,21 +278,82 @@ kyc_check (void *cls,
{ {
struct KycPoller *kyp = cls; struct KycPoller *kyp = cls;
enum GNUNET_DB_QueryStatus qs; enum GNUNET_DB_QueryStatus qs;
struct TALER_KYCLOGIC_ProviderDetails *pd;
enum GNUNET_GenericReturnValue ret;
struct TALER_PaytoHashP h_payto;
struct GNUNET_TIME_Absolute expiration;
char *provider_account_id;
char *provider_legitimization_id;
qs = TEH_plugin->select_kyc_status (TEH_plugin->cls, qs = TEH_plugin->lookup_kyc_requirement_by_row (
&kyp->h_payto, TEH_plugin->cls,
&kyp->kyc); kyp->legitimization_uuid,
if (qs < 0) &kyp->required,
&h_payto,
&expiration,
&provider_account_id,
&provider_legitimization_id);
if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
{ {
if (GNUNET_DB_STATUS_SOFT_ERROR == qs) GNUNET_log (GNUNET_ERROR_TYPE_INFO,
return qs; "No KYC requirements open for %llu\n",
GNUNET_break (0); (unsigned long long) kyp->legitimization_uuid);
*mhd_ret = TALER_MHD_reply_with_error (connection,
MHD_HTTP_INTERNAL_SERVER_ERROR,
TALER_EC_GENERIC_DB_FETCH_FAILED,
"inselect_wallet_status");
return qs; return qs;
} }
if (qs < 0)
{
GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR != qs);
return qs;
}
GNUNET_free (provider_account_id);
GNUNET_free (provider_legitimization_id);
if (0 !=
GNUNET_memcmp (&kyp->h_payto,
&h_payto))
{
GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
"Account %llu provided, but h_payto does not match\n",
(unsigned long long) kyp->legitimization_uuid);
GNUNET_break_op (0);
*mhd_ret = TALER_MHD_reply_with_error (connection,
MHD_HTTP_FORBIDDEN,
TALER_EC_EXCHANGE_KYC_CHECK_AUTHORIZATION_FAILED,
"h_payto");
return GNUNET_DB_STATUS_HARD_ERROR;
}
kyp->found = true;
if (GNUNET_TIME_absolute_is_future (expiration))
{
/* kyc not required, we are done */
return qs;
}
ret = TALER_KYCLOGIC_kyc_get_logic (kyp->required,
&kyp->ih_logic,
&pd);
if (GNUNET_OK != ret)
{
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"KYC logic for `%s' not configured but used in database!\n",
kyp->required);
*mhd_ret = TALER_MHD_reply_with_error (connection,
MHD_HTTP_INTERNAL_SERVER_ERROR,
TALER_EC_EXCHANGE_KYC_GENERIC_LOGIC_GONE,
kyp->required);
return GNUNET_DB_STATUS_HARD_ERROR;
}
if (kyp->ih_done)
return qs;
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Initiating KYC check with logic %s\n",
kyp->required);
kyp->ih = kyp->ih_logic->initiate (kyp->ih_logic->cls,
pd,
&h_payto,
kyp->legitimization_uuid,
&initiate_cb,
kyp);
GNUNET_break (NULL != kyp->ih);
return qs; return qs;
} }
@ -230,7 +401,7 @@ db_event_cb (void *cls,
MHD_RESULT MHD_RESULT
TEH_handler_kyc_check ( TEH_handler_kyc_check (
struct TEH_RequestContext *rc, struct TEH_RequestContext *rc,
const char *const args[]) const char *const args[2])
{ {
struct KycPoller *kyp = rc->rh_ctx; struct KycPoller *kyp = rc->rh_ctx;
MHD_RESULT res; MHD_RESULT res;
@ -245,24 +416,37 @@ TEH_handler_kyc_check (
rc->rh_cleaner = &kyp_cleanup; rc->rh_cleaner = &kyp_cleanup;
{ {
// FIXME: now 'legitimization_uuid'! unsigned long long legitimization_uuid;
unsigned long long payment_target_uuid;
char dummy; char dummy;
if (1 != if (1 !=
sscanf (args[0], sscanf (args[0],
"%llu%c", "%llu%c",
&payment_target_uuid, &legitimization_uuid,
&dummy)) &dummy))
{ {
GNUNET_break_op (0); GNUNET_break_op (0);
return TALER_MHD_reply_with_error (rc->connection, return TALER_MHD_reply_with_error (rc->connection,
MHD_HTTP_BAD_REQUEST, MHD_HTTP_BAD_REQUEST,
TALER_EC_GENERIC_PARAMETER_MALFORMED, TALER_EC_GENERIC_PARAMETER_MALFORMED,
"payment_target_uuid"); "legitimization_uuid");
} }
kyp->auth_payment_target_uuid = (uint64_t) payment_target_uuid; kyp->legitimization_uuid = (uint64_t) legitimization_uuid;
} }
if (GNUNET_OK !=
GNUNET_STRINGS_string_to_data (args[1],
strlen (args[1]),
&kyp->h_payto,
sizeof (kyp->h_payto)))
{
GNUNET_break_op (0);
return TALER_MHD_reply_with_error (rc->connection,
MHD_HTTP_BAD_REQUEST,
TALER_EC_GENERIC_PARAMETER_MALFORMED,
"h_payto");
}
{ {
const char *ts; const char *ts;
@ -291,41 +475,8 @@ TEH_handler_kyc_check (
tms)); tms));
} }
} }
// FIXME: replace with args[1]!
kyp->hps = MHD_lookup_connection_value (rc->connection,
MHD_GET_ARGUMENT_KIND,
"h_payto");
if (NULL == kyp->hps)
{
GNUNET_break_op (0);
return TALER_MHD_reply_with_error (rc->connection,
MHD_HTTP_BAD_REQUEST,
TALER_EC_GENERIC_PARAMETER_MISSING,
"h_payto");
}
if (GNUNET_OK !=
GNUNET_STRINGS_string_to_data (kyp->hps,
strlen (kyp->hps),
&kyp->h_payto,
sizeof (kyp->h_payto)))
{
GNUNET_break_op (0);
return TALER_MHD_reply_with_error (rc->connection,
MHD_HTTP_BAD_REQUEST,
TALER_EC_GENERIC_PARAMETER_MALFORMED,
"h_payto");
}
} }
if (TEH_KYC_NONE == TEH_kyc_config.mode)
return TALER_MHD_reply_static (
rc->connection,
MHD_HTTP_NO_CONTENT,
NULL,
NULL,
0);
if ( (NULL == kyp->eh) && if ( (NULL == kyp->eh) &&
GNUNET_TIME_absolute_is_future (kyp->timeout) ) GNUNET_TIME_absolute_is_future (kyp->timeout) )
{ {
@ -353,27 +504,48 @@ TEH_handler_kyc_check (
&kyc_check, &kyc_check,
kyp); kyp);
if (GNUNET_SYSERR == ret) if (GNUNET_SYSERR == ret)
return res;
if (kyp->auth_payment_target_uuid !=
kyp->kyc.payment_target_uuid)
{ {
GNUNET_log (GNUNET_ERROR_TYPE_WARNING, GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Account %llu provided, but payto %s is for %llu\n", "Transaction failed.\n");
(unsigned long long) kyp->auth_payment_target_uuid, return res;
kyp->hps, }
(unsigned long long) kyp->kyc.payment_target_uuid);
GNUNET_break_op (0); if ( (NULL == kyp->ih) &&
return TALER_MHD_reply_with_error (rc->connection, (! kyp->found) )
MHD_HTTP_FORBIDDEN, {
TALER_EC_EXCHANGE_KYC_CHECK_AUTHORIZATION_FAILED, /* KYC not required */
"h_payto"); GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"KYC not required %llu\n",
(unsigned long long) kyp->legitimization_uuid);
return TALER_MHD_reply_static (
rc->connection,
MHD_HTTP_NO_CONTENT,
NULL,
NULL,
0);
}
if (NULL != kyp->ih)
{
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Suspending HTTP request on KYC logic...\n");
kyp->suspended = true;
GNUNET_CONTAINER_DLL_insert (kyp_head,
kyp_tail,
kyp);
MHD_suspend_connection (kyp->connection);
return MHD_YES;
} }
/* long polling? */ /* long polling? */
if ( (! kyp->kyc.ok) && if ( (NULL != kyp->required) &&
GNUNET_TIME_absolute_is_future (kyp->timeout)) GNUNET_TIME_absolute_is_future (kyp->timeout))
{ {
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Suspending HTTP request on timeout (%s) now...\n",
GNUNET_TIME_relative2s (GNUNET_TIME_absolute_get_duration (
kyp->timeout),
true));
GNUNET_assert (NULL != kyp->eh); GNUNET_assert (NULL != kyp->eh);
kyp->suspended = true; kyp->suspended = true;
GNUNET_CONTAINER_DLL_insert (kyp_head, GNUNET_CONTAINER_DLL_insert (kyp_head,
@ -383,37 +555,24 @@ TEH_handler_kyc_check (
return MHD_YES; return MHD_YES;
} }
/* KYC failed? */ /* KYC plugin generated reply? */
if (! kyp->kyc.ok) if (NULL != kyp->kyc_url)
{ {
char *url; return TALER_MHD_REPLY_JSON_PACK (
char *redirect_uri;
char *redirect_uri_encoded;
GNUNET_assert (TEH_KYC_OAUTH2 == TEH_kyc_config.mode);
GNUNET_asprintf (&redirect_uri,
"%s/kyc-proof/%s",
TEH_base_url,
kyp->hps);
redirect_uri_encoded = TALER_urlencode (redirect_uri);
GNUNET_free (redirect_uri);
GNUNET_asprintf (&url,
"%s?client_id=%s&redirect_uri=%s",
TEH_kyc_config.details.oauth2.login_url,
TEH_kyc_config.details.oauth2.client_id,
redirect_uri_encoded);
GNUNET_free (redirect_uri_encoded);
res = TALER_MHD_REPLY_JSON_PACK (
rc->connection, rc->connection,
MHD_HTTP_ACCEPTED, MHD_HTTP_ACCEPTED,
GNUNET_JSON_pack_string ("kyc_url", GNUNET_JSON_pack_string ("kyc_url",
url)); kyp->kyc_url));
GNUNET_free (url);
return res;
} }
/* KYC succeeded! */ if (TALER_EC_NONE != kyp->ec)
{
return TALER_MHD_reply_with_ec (rc->connection,
kyp->ec,
kyp->hint);
}
/* KYC must have succeeded! */
{ {
struct TALER_ExchangePublicKeyP pub; struct TALER_ExchangePublicKeyP pub;
struct TALER_ExchangeSignatureP sig; struct TALER_ExchangeSignatureP sig;

View File

@ -1,6 +1,6 @@
/* /*
This file is part of TALER This file is part of TALER
Copyright (C) 2021 Taler Systems SA Copyright (C) 2021-2022 Taler Systems SA
TALER is free software; you can redistribute it and/or modify it under the 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 terms of the GNU Affero General Public License as published by the Free Software
@ -25,6 +25,7 @@
#include <microhttpd.h> #include <microhttpd.h>
#include <pthread.h> #include <pthread.h>
#include "taler_json_lib.h" #include "taler_json_lib.h"
#include "taler_kyclogic_lib.h"
#include "taler_mhd_lib.h" #include "taler_mhd_lib.h"
#include "taler-exchange-httpd_kyc-proof.h" #include "taler-exchange-httpd_kyc-proof.h"
#include "taler-exchange-httpd_responses.h" #include "taler-exchange-httpd_responses.h"
@ -52,31 +53,37 @@ struct KycProofContext
struct TEH_RequestContext *rc; struct TEH_RequestContext *rc;
/** /**
* Handle for the OAuth 2.0 CURL request. * Proof logic to run.
*/ */
struct GNUNET_CURL_Job *job; struct TALER_KYCLOGIC_Plugin *logic;
/**
* Configuration for @a logic.
*/
struct TALER_KYCLOGIC_ProviderDetails *pd;
/**
* Asynchronous operation with the proof system.
*/
struct TALER_KYCLOGIC_ProofHandle *ph;
/**
* Process information about the user for the plugin from the database, can
* be NULL.
*/
char *provider_user_id;
/**
* Process information about the legitimization process for the plugin from the
* database, can be NULL.
*/
char *provider_legitimization_id;
/** /**
* OAuth 2.0 authorization code. * OAuth 2.0 authorization code.
*/ */
const char *authorization_code; const char *authorization_code;
/**
* OAuth 2.0 token URL we are using for the
* request.
*/
char *token_url;
/**
* Body of the POST request.
*/
char *post_body;
/**
* User ID extracted from the OAuth 2.0 service, or NULL.
*/
char *id;
/** /**
* Hash of payment target URI this is about. * Hash of payment target URI this is about.
*/ */
@ -87,17 +94,25 @@ struct KycProofContext
*/ */
struct MHD_Response *response; struct MHD_Response *response;
/**
* Configuration section for the logic we are running.
*/
char *provider_section;
/**
* Row in the database for this legitimization operation.
*/
uint64_t legi_row;
/** /**
* HTTP response code to return. * HTTP response code to return.
*/ */
unsigned int response_code; unsigned int response_code;
/** /**
* #GNUNET_YES if we are suspended, * True if we are suspended,
* #GNUNET_NO if not.
* #GNUNET_SYSERR if we had some error.
*/ */
enum GNUNET_GenericReturnValue suspended; bool suspended;
}; };
@ -122,7 +137,7 @@ static void
kpc_resume (struct KycProofContext *kpc) kpc_resume (struct KycProofContext *kpc)
{ {
GNUNET_assert (GNUNET_YES == kpc->suspended); GNUNET_assert (GNUNET_YES == kpc->suspended);
kpc->suspended = GNUNET_NO; kpc->suspended = false;
GNUNET_CONTAINER_DLL_remove (kpc_head, GNUNET_CONTAINER_DLL_remove (kpc_head,
kpc_tail, kpc_tail,
kpc); kpc);
@ -138,10 +153,10 @@ TEH_kyc_proof_cleanup (void)
while (NULL != (kpc = kpc_head)) while (NULL != (kpc = kpc_head))
{ {
if (NULL != kpc->job) if (NULL != kpc->ph)
{ {
GNUNET_CURL_job_cancel (kpc->job); kpc->logic->proof_cancel (kpc->ph);
kpc->job = NULL; kpc->ph = NULL;
} }
kpc_resume (kpc); kpc_resume (kpc);
} }
@ -149,348 +164,69 @@ TEH_kyc_proof_cleanup (void)
/** /**
* Function implementing database transaction to check proof's KYC status. * Function called with the result of a proof check operation.
* 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, the
* transaction logic MUST queue a MHD response and set @a mhd_ret. IF it
* returns the soft error code, the function MAY be called again to retry and
* MUST not queue a MHD response.
* *
* @param cls closure with a `struct KycProofContext *` * Note that the "decref" for the @a response
* @param connection MHD proof which triggered the transaction * will be done by the callee and MUST NOT be done by the plugin.
* @param[out] mhd_ret set to MHD response status for @a connection,
* if transaction failed (!)
* @return transaction status
*/
static enum GNUNET_DB_QueryStatus
persist_kyc_ok (void *cls,
struct MHD_Connection *connection,
MHD_RESULT *mhd_ret)
{
struct KycProofContext *kpc = cls;
enum GNUNET_DB_QueryStatus qs;
qs = TEH_plugin->set_kyc_ok (TEH_plugin->cls,
&kpc->h_payto,
kpc->id);
if (GNUNET_DB_STATUS_HARD_ERROR == qs)
{
GNUNET_break (0);
*mhd_ret = TALER_MHD_reply_with_ec (connection,
TALER_EC_GENERIC_DB_STORE_FAILED,
"set_kyc_ok");
}
return qs;
}
/**
* The request for @a kpc failed. We may have gotten a useful error
* message in @a j. Generate a failure response.
* *
* @param[in,out] kpc request that failed * @param cls closure
* @param j reply from the server (or NULL) * @param status KYC status
* @param provider_user_id set to user ID at the provider, or NULL if not supported or unknown
* @param provider_legitimization_id set to legitimization process ID at the provider, or NULL if not supported or unknown
* @param expiration until when is the KYC check valid
* @param http_status HTTP status code of @a response
* @param[in] response to return to the HTTP client
*/ */
static void static void
handle_error (struct KycProofContext *kpc, proof_cb (
const json_t *j) void *cls,
enum TALER_KYCLOGIC_KycStatus status,
const char *provider_user_id,
const char *provider_legitimization_id,
struct GNUNET_TIME_Absolute expiration,
unsigned int http_status,
struct MHD_Response *response)
{ {
const char *msg; struct KycProofContext *kpc = cls;
const char *desc; struct TEH_RequestContext *rc = kpc->rc;
struct GNUNET_JSON_Specification spec[] = { struct GNUNET_AsyncScopeSave old_scope;
GNUNET_JSON_spec_string ("error",
&msg),
GNUNET_JSON_spec_string ("error_description",
&desc),
GNUNET_JSON_spec_end ()
};
kpc->ph = NULL;
GNUNET_async_scope_enter (&rc->async_scope_id,
&old_scope);
if (TALER_KYCLOGIC_STATUS_SUCCESS == status)
{ {
enum GNUNET_GenericReturnValue res; enum GNUNET_DB_QueryStatus qs;
const char *emsg;
unsigned int line;
res = GNUNET_JSON_parse (j, qs = TEH_plugin->update_kyc_requirement_by_row (TEH_plugin->cls,
spec, kpc->legi_row,
&emsg, kpc->provider_section,
&line); &kpc->h_payto,
if (GNUNET_OK != res) provider_user_id,
provider_legitimization_id,
expiration);
if (GNUNET_DB_STATUS_HARD_ERROR == qs)
{ {
GNUNET_break_op (0); GNUNET_break (0);
kpc->response kpc->response_code = MHD_HTTP_INTERNAL_SERVER_ERROR;
= TALER_MHD_make_error ( kpc->response = TALER_MHD_make_error (TALER_EC_GENERIC_DB_STORE_FAILED,
TALER_EC_EXCHANGE_KYC_PROOF_BACKEND_INVALID_RESPONSE, "set_kyc_ok");
"Unexpected response from KYC gateway"); GNUNET_async_scope_restore (&old_scope);
kpc->response_code
= MHD_HTTP_BAD_GATEWAY;
return; return;
} }
} }
/* case TALER_EC_EXCHANGE_KYC_PROOF_BACKEND_AUTHORZATION_FAILED, else
we MAY want to in the future look at the requested content type
and possibly respond in JSON if indicated. */
{ {
char *reply; GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"KYC logic #%llu failed with status %d\n",
GNUNET_asprintf (&reply, (unsigned long long) kpc->legi_row,
"<html><head><title>%s</title></head><body><h1>%s</h1><p>%s</p></body></html>", status);
msg,
msg,
desc);
kpc->response
= MHD_create_response_from_buffer (strlen (reply),
reply,
MHD_RESPMEM_MUST_COPY);
GNUNET_assert (NULL != kpc->response);
GNUNET_free (reply);
}
kpc->response_code = MHD_HTTP_FORBIDDEN;
}
/**
* The request for @a kpc succeeded (presumably).
* Parse the user ID and store it in @a kpc (if possible).
*
* @param[in,out] kpc request that succeeded
* @param j reply from the server
*/
static void
parse_success_reply (struct KycProofContext *kpc,
const json_t *j)
{
const char *state;
json_t *data;
struct GNUNET_JSON_Specification spec[] = {
GNUNET_JSON_spec_string ("status",
&state),
GNUNET_JSON_spec_json ("data",
&data),
GNUNET_JSON_spec_end ()
};
enum GNUNET_GenericReturnValue res;
const char *emsg;
unsigned int line;
res = GNUNET_JSON_parse (j,
spec,
&emsg,
&line);
if (GNUNET_OK != res)
{
GNUNET_break_op (0);
kpc->response
= TALER_MHD_make_error (
TALER_EC_EXCHANGE_KYC_PROOF_BACKEND_INVALID_RESPONSE,
"Unexpected response from KYC gateway");
kpc->response_code
= MHD_HTTP_BAD_GATEWAY;
return;
}
if (0 != strcasecmp (state,
"success"))
{
GNUNET_break_op (0);
handle_error (kpc,
j);
return;
}
{
const char *id;
struct GNUNET_JSON_Specification ispec[] = {
GNUNET_JSON_spec_string ("id",
&id),
GNUNET_JSON_spec_end ()
};
res = GNUNET_JSON_parse (data,
ispec,
&emsg,
&line);
if (GNUNET_OK != res)
{
GNUNET_break_op (0);
kpc->response
= TALER_MHD_make_error (
TALER_EC_EXCHANGE_KYC_PROOF_BACKEND_INVALID_RESPONSE,
"Unexpected response from KYC gateway");
kpc->response_code
= MHD_HTTP_BAD_GATEWAY;
return;
}
kpc->id = GNUNET_strdup (id);
}
}
/**
* After we are done with the CURL interaction we
* need to update our database state with the information
* retrieved.
*
* @param cls our `struct KycProofContext`
* @param response_code HTTP response code from server, 0 on hard error
* @param response in JSON, NULL if response was not in JSON format
*/
static void
handle_curl_fetch_finished (void *cls,
long response_code,
const void *response)
{
struct KycProofContext *kpc = cls;
const json_t *j = response;
kpc->job = NULL;
switch (response_code)
{
case MHD_HTTP_OK:
parse_success_reply (kpc,
j);
break;
default:
handle_error (kpc,
j);
break;
}
kpc_resume (kpc);
}
/**
* After we are done with the CURL interaction we
* need to fetch the user's account details.
*
* @param cls our `struct KycProofContext`
* @param response_code HTTP response code from server, 0 on hard error
* @param response in JSON, NULL if response was not in JSON format
*/
static void
handle_curl_login_finished (void *cls,
long response_code,
const void *response)
{
struct KycProofContext *kpc = cls;
const json_t *j = response;
kpc->job = NULL;
switch (response_code)
{
case MHD_HTTP_OK:
{
const char *access_token;
const char *token_type;
uint64_t expires_in_s;
const char *refresh_token;
struct GNUNET_JSON_Specification spec[] = {
GNUNET_JSON_spec_string ("access_token",
&access_token),
GNUNET_JSON_spec_string ("token_type",
&token_type),
GNUNET_JSON_spec_uint64 ("expires_in",
&expires_in_s),
GNUNET_JSON_spec_string ("refresh_token",
&refresh_token),
GNUNET_JSON_spec_end ()
};
CURL *eh;
{
enum GNUNET_GenericReturnValue res;
const char *emsg;
unsigned int line;
res = GNUNET_JSON_parse (j,
spec,
&emsg,
&line);
if (GNUNET_OK != res)
{
GNUNET_break_op (0);
kpc->response
= TALER_MHD_make_error (
TALER_EC_EXCHANGE_KYC_PROOF_BACKEND_INVALID_RESPONSE,
"Unexpected response from KYC gateway");
kpc->response_code
= MHD_HTTP_BAD_GATEWAY;
break;
}
}
if (0 != strcasecmp (token_type,
"bearer"))
{
GNUNET_break_op (0);
kpc->response
= TALER_MHD_make_error (
TALER_EC_EXCHANGE_KYC_PROOF_BACKEND_INVALID_RESPONSE,
"Unexpected token type in response from KYC gateway");
kpc->response_code
= MHD_HTTP_BAD_GATEWAY;
break;
}
/* We guard against a few characters that could
conceivably be abused to mess with the HTTP header */
if ( (NULL != strchr (access_token,
'\n')) ||
(NULL != strchr (access_token,
'\r')) ||
(NULL != strchr (access_token,
' ')) ||
(NULL != strchr (access_token,
';')) )
{
GNUNET_break_op (0);
kpc->response
= TALER_MHD_make_error (
TALER_EC_EXCHANGE_KYC_PROOF_BACKEND_INVALID_RESPONSE,
"Illegal character in access token");
kpc->response_code
= MHD_HTTP_BAD_GATEWAY;
break;
}
eh = curl_easy_init ();
if (NULL == eh)
{
GNUNET_break_op (0);
kpc->response
= TALER_MHD_make_error (
TALER_EC_GENERIC_ALLOCATION_FAILURE,
"curl_easy_init");
kpc->response_code
= MHD_HTTP_INTERNAL_SERVER_ERROR;
break;
}
GNUNET_assert (CURLE_OK ==
curl_easy_setopt (eh,
CURLOPT_URL,
TEH_kyc_config.details.oauth2.info_url));
{
char *hdr;
struct curl_slist *slist;
GNUNET_asprintf (&hdr,
"%s: Bearer %s",
MHD_HTTP_HEADER_AUTHORIZATION,
access_token);
slist = curl_slist_append (NULL,
hdr);
kpc->job = GNUNET_CURL_job_add2 (TEH_curl_ctx,
eh,
slist,
&handle_curl_fetch_finished,
kpc);
curl_slist_free_all (slist);
GNUNET_free (hdr);
}
return;
}
default:
handle_error (kpc,
j);
break;
} }
kpc->response_code = http_status;
kpc->response = response;
kpc_resume (kpc); kpc_resume (kpc);
GNUNET_async_scope_restore (&old_scope);
} }
@ -504,19 +240,19 @@ clean_kpc (struct TEH_RequestContext *rc)
{ {
struct KycProofContext *kpc = rc->rh_ctx; struct KycProofContext *kpc = rc->rh_ctx;
if (NULL != kpc->job) if (NULL != kpc->ph)
{ {
GNUNET_CURL_job_cancel (kpc->job); kpc->logic->proof_cancel (kpc->ph);
kpc->job = NULL; kpc->ph = NULL;
} }
if (NULL != kpc->response) if (NULL != kpc->response)
{ {
MHD_destroy_response (kpc->response); MHD_destroy_response (kpc->response);
kpc->response = NULL; kpc->response = NULL;
} }
GNUNET_free (kpc->post_body); GNUNET_free (kpc->provider_user_id);
GNUNET_free (kpc->token_url); GNUNET_free (kpc->provider_legitimization_id);
GNUNET_free (kpc->id); GNUNET_free (kpc->provider_section);
GNUNET_free (kpc); GNUNET_free (kpc);
} }
@ -529,7 +265,18 @@ TEH_handler_kyc_proof (
struct KycProofContext *kpc = rc->rh_ctx; struct KycProofContext *kpc = rc->rh_ctx;
if (NULL == kpc) if (NULL == kpc)
{ /* first time */ {
/* first time */
if ( (NULL == args[0]) ||
(NULL == args[1]) )
{
GNUNET_break_op (0);
return TALER_MHD_reply_with_error (rc->connection,
MHD_HTTP_NOT_FOUND,
TALER_EC_GENERIC_ENDPOINT_UNKNOWN,
"'/kyc-proof/$H_PATYO/$LOGIC' required");
}
kpc = GNUNET_new (struct KycProofContext); kpc = GNUNET_new (struct KycProofContext);
kpc->rc = rc; kpc->rc = rc;
rc->rh_ctx = kpc; rc->rh_ctx = kpc;
@ -546,166 +293,98 @@ TEH_handler_kyc_proof (
TALER_EC_GENERIC_PARAMETER_MALFORMED, TALER_EC_GENERIC_PARAMETER_MALFORMED,
"h_payto"); "h_payto");
} }
kpc->authorization_code kpc->provider_section = GNUNET_strdup (args[1]);
= MHD_lookup_connection_value (rc->connection, if (GNUNET_OK !=
MHD_GET_ARGUMENT_KIND, TALER_KYCLOGIC_kyc_get_logic (kpc->provider_section,
"code"); &kpc->logic,
if (NULL == kpc->authorization_code) &kpc->pd))
{ {
GNUNET_break_op (0); GNUNET_break_op (0);
return TALER_MHD_reply_with_error (rc->connection, return TALER_MHD_reply_with_error (rc->connection,
MHD_HTTP_BAD_REQUEST, MHD_HTTP_NOT_FOUND,
TALER_EC_GENERIC_PARAMETER_MALFORMED, TALER_EC_EXCHANGE_KYC_GENERIC_LOGIC_UNKNOWN,
"code"); kpc->provider_section);
} }
if (TEH_KYC_NONE == TEH_kyc_config.mode)
return TALER_MHD_reply_static (
rc->connection,
MHD_HTTP_NO_CONTENT,
NULL,
NULL,
0);
{ {
CURL *eh; enum GNUNET_DB_QueryStatus qs;
struct GNUNET_TIME_Absolute expiration;
eh = curl_easy_init (); qs = TEH_plugin->lookup_kyc_requirement_by_account (
if (NULL == eh) TEH_plugin->cls,
kpc->provider_section,
&kpc->h_payto,
&kpc->legi_row,
&expiration,
&kpc->provider_user_id,
&kpc->provider_legitimization_id);
switch (qs)
{ {
GNUNET_break (0); case GNUNET_DB_STATUS_HARD_ERROR:
case GNUNET_DB_STATUS_SOFT_ERROR:
return TALER_MHD_reply_with_ec (rc->connection,
TALER_EC_GENERIC_DB_STORE_FAILED,
"lookup_kyc_requirement_by_account");
case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
return TALER_MHD_reply_with_error (rc->connection, return TALER_MHD_reply_with_error (rc->connection,
MHD_HTTP_INTERNAL_SERVER_ERROR, MHD_HTTP_NOT_FOUND,
TALER_EC_GENERIC_ALLOCATION_FAILURE, TALER_EC_EXCHANGE_KYC_PROOF_REQUEST_UNKNOWN,
"curl_easy_init"); kpc->provider_section);
case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
break;
} }
GNUNET_asprintf (&kpc->token_url, if (GNUNET_TIME_absolute_is_future (expiration))
"%s",
TEH_kyc_config.details.oauth2.auth_url);
GNUNET_assert (CURLE_OK ==
curl_easy_setopt (eh,
CURLOPT_URL,
kpc->token_url));
GNUNET_assert (CURLE_OK ==
curl_easy_setopt (eh,
CURLOPT_POST,
1));
{ {
char *client_id; /* KYC not required */
char *redirect_uri; return TALER_MHD_reply_static (
char *client_secret; rc->connection,
char *authorization_code; MHD_HTTP_NO_CONTENT,
NULL,
client_id = curl_easy_escape (eh, NULL,
TEH_kyc_config.details.oauth2.client_id, 0);
0);
GNUNET_assert (NULL != client_id);
{
char *request_uri;
GNUNET_asprintf (&request_uri,
"%s?client_id=%s",
TEH_kyc_config.details.oauth2.login_url,
TEH_kyc_config.details.oauth2.client_id);
redirect_uri = curl_easy_escape (eh,
request_uri,
0);
GNUNET_free (request_uri);
}
GNUNET_assert (NULL != redirect_uri);
client_secret = curl_easy_escape (eh,
TEH_kyc_config.details.oauth2.
client_secret,
0);
GNUNET_assert (NULL != client_secret);
authorization_code = curl_easy_escape (eh,
kpc->authorization_code,
0);
GNUNET_assert (NULL != authorization_code);
GNUNET_asprintf (&kpc->post_body,
"client_id=%s&redirect_uri=%s&client_secret=%s&code=%s&grant_type=authorization_code",
client_id,
redirect_uri,
client_secret,
authorization_code);
curl_free (authorization_code);
curl_free (client_secret);
curl_free (redirect_uri);
curl_free (client_id);
} }
GNUNET_assert (CURLE_OK ==
curl_easy_setopt (eh,
CURLOPT_POSTFIELDS,
kpc->post_body));
GNUNET_assert (CURLE_OK ==
curl_easy_setopt (eh,
CURLOPT_FOLLOWLOCATION,
1L));
/* limit MAXREDIRS to 5 as a simple security measure against
a potential infinite loop caused by a malicious target */
GNUNET_assert (CURLE_OK ==
curl_easy_setopt (eh,
CURLOPT_MAXREDIRS,
5L));
kpc->job = GNUNET_CURL_job_add (TEH_curl_ctx,
eh,
&handle_curl_login_finished,
kpc);
kpc->suspended = GNUNET_YES;
GNUNET_CONTAINER_DLL_insert (kpc_head,
kpc_tail,
kpc);
MHD_suspend_connection (rc->connection);
return MHD_YES;
} }
} kpc->ph = kpc->logic->proof (kpc->logic->cls,
kpc->pd,
if (NULL != kpc->response) &args[2],
{ rc->connection,
/* handle _failed_ resumed cases */ &kpc->h_payto,
return MHD_queue_response (rc->connection, kpc->legi_row,
kpc->response_code, kpc->provider_user_id,
kpc->response); kpc->provider_legitimization_id,
} &proof_cb,
kpc);
/* _successfully_ resumed case */ if (NULL == kpc->ph)
{
MHD_RESULT res;
enum GNUNET_GenericReturnValue ret;
ret = TEH_DB_run_transaction (kpc->rc->connection,
"check proof kyc",
TEH_MT_REQUEST_OTHER,
&res,
&persist_kyc_ok,
kpc);
if (GNUNET_SYSERR == ret)
return res;
}
{
struct MHD_Response *response;
MHD_RESULT res;
response = MHD_create_response_from_buffer (0,
"",
MHD_RESPMEM_PERSISTENT);
if (NULL == response)
{ {
GNUNET_break (0); GNUNET_break (0);
return MHD_NO; return TALER_MHD_reply_with_error (rc->connection,
MHD_HTTP_INTERNAL_SERVER_ERROR,
TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
"could not start proof with KYC logic");
} }
GNUNET_break (MHD_YES ==
MHD_add_response_header (
response, kpc->suspended = true;
MHD_HTTP_HEADER_LOCATION, GNUNET_CONTAINER_DLL_insert (kpc_head,
TEH_kyc_config.details.oauth2.post_kyc_redirect_url)); kpc_tail,
res = MHD_queue_response (rc->connection, kpc);
MHD_HTTP_SEE_OTHER, MHD_suspend_connection (rc->connection);
response); return MHD_YES;
MHD_destroy_response (response);
return res;
} }
if (NULL == kpc->response)
{
GNUNET_break (0);
return TALER_MHD_reply_with_error (rc->connection,
MHD_HTTP_INTERNAL_SERVER_ERROR,
TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
"handler resumed without response");
}
/* return response from KYC logic */
return MHD_queue_response (rc->connection,
kpc->response_code,
kpc->response);
} }

View File

@ -1,6 +1,6 @@
/* /*
This file is part of TALER This file is part of TALER
Copyright (C) 2021 Taler Systems SA Copyright (C) 2021, 2022 Taler Systems SA
TALER is free software; you can redistribute it and/or modify it under the 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 terms of the GNU Affero General Public License as published by the Free Software
@ -26,6 +26,7 @@
#include <pthread.h> #include <pthread.h>
#include "taler_json_lib.h" #include "taler_json_lib.h"
#include "taler_mhd_lib.h" #include "taler_mhd_lib.h"
#include "taler_kyclogic_lib.h"
#include "taler-exchange-httpd_kyc-wallet.h" #include "taler-exchange-httpd_kyc-wallet.h"
#include "taler-exchange-httpd_responses.h" #include "taler-exchange-httpd_responses.h"
@ -38,15 +39,54 @@ struct KycRequestContext
/** /**
* Public key of the reserve/wallet this is about. * Public key of the reserve/wallet this is about.
*/ */
struct TALER_ReservePublicKeyP reserve_pub; struct TALER_PaytoHashP h_payto;
/** /**
* Current KYC status. * Row with the legitimization requirement.
*/ */
struct TALER_EXCHANGEDB_KycStatus kyc; uint64_t legi_row;
/**
* Balance threshold crossed by the wallet.
*/
struct TALER_Amount balance;
/**
* Name of the required check.
*/
const char *required;
}; };
/**
* Function called to iterate over KYC-relevant
* transaction amounts for a particular time range.
* Returns the wallet balance.
*
* @param cls closure, a `struct KycRequestContext`
* @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
balance_iterator (void *cls,
struct GNUNET_TIME_Absolute limit,
TALER_EXCHANGEDB_KycAmountCallback cb,
void *cb_cls)
{
struct KycRequestContext *krc = cls;
(void) limit;
cb (cb_cls,
&krc->balance,
GNUNET_TIME_absolute_get ());
}
/** /**
* Function implementing database transaction to check wallet's KYC status. * Function implementing database transaction to check wallet's KYC status.
* Runs the transaction logic; IF it returns a non-error code, the transaction * Runs the transaction logic; IF it returns a non-error code, the transaction
@ -69,9 +109,23 @@ wallet_kyc_check (void *cls,
struct KycRequestContext *krc = cls; struct KycRequestContext *krc = cls;
enum GNUNET_DB_QueryStatus qs; enum GNUNET_DB_QueryStatus qs;
qs = TEH_plugin->inselect_wallet_kyc_status (TEH_plugin->cls, krc->required = TALER_KYCLOGIC_kyc_test_required (
&krc->reserve_pub, TALER_KYCLOGIC_KYC_TRIGGER_WALLET_BALANCE,
&krc->kyc); &krc->h_payto,
TEH_plugin->select_satisfied_kyc_processes,
TEH_plugin->cls,
&balance_iterator,
krc);
GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
"KYC check required at %s is `%s'\n",
TALER_amount2s (&krc->balance),
krc->required);
if (NULL == krc->required)
return GNUNET_DB_STATUS_SUCCESS_NO_RESULTS;
qs = TEH_plugin->insert_kyc_requirement_for_account (TEH_plugin->cls,
krc->required,
&krc->h_payto,
&krc->legi_row);
if (qs < 0) if (qs < 0)
{ {
if (GNUNET_DB_STATUS_SOFT_ERROR == qs) if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
@ -80,9 +134,14 @@ wallet_kyc_check (void *cls,
*mhd_ret = TALER_MHD_reply_with_error (connection, *mhd_ret = TALER_MHD_reply_with_error (connection,
MHD_HTTP_INTERNAL_SERVER_ERROR, MHD_HTTP_INTERNAL_SERVER_ERROR,
TALER_EC_GENERIC_DB_FETCH_FAILED, TALER_EC_GENERIC_DB_FETCH_FAILED,
"inselect_wallet_status"); "insert_kyc_requirement_for_account");
return qs; return qs;
} }
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"KYC requirement inserted for wallet %s (%llu, %d)\n",
TALER_B2S (&krc->h_payto),
(unsigned long long) krc->legi_row,
qs);
return qs; return qs;
} }
@ -95,11 +154,17 @@ TEH_handler_kyc_wallet (
{ {
struct TALER_ReserveSignatureP reserve_sig; struct TALER_ReserveSignatureP reserve_sig;
struct KycRequestContext krc; struct KycRequestContext krc;
struct TALER_ReservePublicKeyP reserve_pub;
struct GNUNET_JSON_Specification spec[] = { struct GNUNET_JSON_Specification spec[] = {
GNUNET_JSON_spec_fixed_auto ("reserve_sig", GNUNET_JSON_spec_fixed_auto ("reserve_sig",
&reserve_sig), &reserve_sig),
GNUNET_JSON_spec_fixed_auto ("reserve_pub", GNUNET_JSON_spec_fixed_auto ("reserve_pub",
&krc.reserve_pub), &reserve_pub),
// FIXME: add balance threshold crossed to the request
// to spec and client API!
TALER_JSON_spec_amount ("balance",
TEH_currency,
&krc.balance),
GNUNET_JSON_spec_end () GNUNET_JSON_spec_end ()
}; };
MHD_RESULT res; MHD_RESULT res;
@ -115,8 +180,10 @@ TEH_handler_kyc_wallet (
return MHD_YES; /* failure */ return MHD_YES; /* failure */
TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_EDDSA]++; TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_EDDSA]++;
// FIXME: add balance threshold crossed to
// what the wallet signs over!
if (GNUNET_OK != if (GNUNET_OK !=
TALER_wallet_account_setup_verify (&krc.reserve_pub, TALER_wallet_account_setup_verify (&reserve_pub,
&reserve_sig)) &reserve_sig))
{ {
GNUNET_break_op (0); GNUNET_break_op (0);
@ -126,13 +193,19 @@ TEH_handler_kyc_wallet (
TALER_EC_EXCHANGE_KYC_WALLET_SIGNATURE_INVALID, TALER_EC_EXCHANGE_KYC_WALLET_SIGNATURE_INVALID,
NULL); NULL);
} }
if (TEH_KYC_NONE == TEH_kyc_config.mode) {
return TALER_MHD_reply_static ( char *payto_uri;
rc->connection,
MHD_HTTP_NO_CONTENT, payto_uri = TALER_reserve_make_payto (TEH_base_url,
NULL, &reserve_pub);
NULL, TALER_payto_hash (payto_uri,
0); &krc.h_payto);
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"h_payto of wallet %s is %s\n",
payto_uri,
TALER_B2S (&krc.h_payto));
GNUNET_free (payto_uri);
}
ret = TEH_DB_run_transaction (rc->connection, ret = TEH_DB_run_transaction (rc->connection,
"check wallet kyc", "check wallet kyc",
TEH_MT_REQUEST_OTHER, TEH_MT_REQUEST_OTHER,
@ -141,11 +214,21 @@ TEH_handler_kyc_wallet (
&krc); &krc);
if (GNUNET_SYSERR == ret) if (GNUNET_SYSERR == ret)
return res; return res;
if (NULL == krc.required)
{
/* KYC not required or already satisfied */
return TALER_MHD_reply_static (
rc->connection,
MHD_HTTP_NO_CONTENT,
NULL,
NULL,
0);
}
return TALER_MHD_REPLY_JSON_PACK ( return TALER_MHD_REPLY_JSON_PACK (
rc->connection, rc->connection,
MHD_HTTP_OK, MHD_HTTP_OK,
GNUNET_JSON_pack_uint64 ("payment_target_uuid", GNUNET_JSON_pack_uint64 ("payment_target_uuid",
krc.kyc.payment_target_uuid)); krc.legi_row));
} }

View File

@ -421,11 +421,11 @@ TEH_handler_purses_merge (
"Received payto: `%s'\n", "Received payto: `%s'\n",
pcc.payto_uri); pcc.payto_uri);
if ( (0 != strncmp (pcc.payto_uri, if ( (0 != strncmp (pcc.payto_uri,
"payto://taler/", "payto://taler-reserve/",
strlen ("payto://taler/"))) && strlen ("payto://taler-reserve/"))) &&
(0 != strncmp (pcc.payto_uri, (0 != strncmp (pcc.payto_uri,
"payto://taler+http/", "payto://taler-reserve+http/",
strlen ("payto://taler+http/"))) ) strlen ("payto://taler-reserve+http/"))) )
{ {
GNUNET_break_op (0); GNUNET_break_op (0);
return TALER_MHD_reply_with_error ( return TALER_MHD_reply_with_error (
@ -436,13 +436,13 @@ TEH_handler_purses_merge (
} }
http = (0 == strncmp (pcc.payto_uri, http = (0 == strncmp (pcc.payto_uri,
"payto://taler+http/", "payto://taler-reserve+http/",
strlen ("payto://taler+http/"))); strlen ("payto://taler-reserve+http/")));
{ {
const char *host = &pcc.payto_uri[http const char *host = &pcc.payto_uri[http
? strlen ("payto://taler+http/") ? strlen ("payto://taler-reserve+http/")
: strlen ("payto://taler/")]; : strlen ("payto://taler-reserve/")];
const char *slash = strchr (host, const char *slash = strchr (host,
'/'); '/');

View File

@ -1,6 +1,6 @@
/* /*
This file is part of TALER This file is part of TALER
Copyright (C) 2014-2021 Taler Systems SA Copyright (C) 2014-2022 Taler Systems SA
TALER is free software; you can redistribute it and/or modify TALER is free software; you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as it under the terms of the GNU Affero General Public License as
@ -27,6 +27,7 @@
#include <gnunet/gnunet_util_lib.h> #include <gnunet/gnunet_util_lib.h>
#include <jansson.h> #include <jansson.h>
#include "taler_json_lib.h" #include "taler_json_lib.h"
#include "taler_kyclogic_lib.h"
#include "taler_mhd_lib.h" #include "taler_mhd_lib.h"
#include "taler-exchange-httpd_withdraw.h" #include "taler-exchange-httpd_withdraw.h"
#include "taler-exchange-httpd_responses.h" #include "taler-exchange-httpd_responses.h"
@ -44,15 +45,6 @@ struct WithdrawContext
*/ */
struct TALER_BlindedCoinHashP h_coin_envelope; struct TALER_BlindedCoinHashP h_coin_envelope;
/**
* Value of the coin being exchanged (matching the denomination key)
* plus the transaction fee. We include this in what is being
* signed so that we can verify a reserve's remaining total balance
* without needing to access the respective denomination key
* information each time.
*/
struct TALER_Amount amount_with_fee;
/** /**
* Blinded planchet. * Blinded planchet.
*/ */
@ -68,9 +60,66 @@ struct WithdrawContext
*/ */
struct TALER_EXCHANGEDB_KycStatus kyc; struct TALER_EXCHANGEDB_KycStatus kyc;
/**
* 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;
}; };
/**
* 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
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\n",
TALER_amount2s (&wc->collectable.amount_with_fee));
if (GNUNET_OK !=
cb (cb_cls,
&wc->collectable.amount_with_fee,
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 withdrawal and limit %llu\n",
qs,
(unsigned long long) limit.abs_value_us);
GNUNET_break (qs >= 0);
}
/** /**
* Function implementing withdraw transaction. Runs the * Function implementing withdraw transaction. Runs the
* transaction logic; IF it returns a non-error code, the transaction * transaction logic; IF it returns a non-error code, the transaction
@ -98,25 +147,55 @@ 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;
struct GNUNET_TIME_Timestamp now;
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;
const char *kyc_required;
now = GNUNET_TIME_timestamp_get (); wc->now = GNUNET_TIME_timestamp_get ();
qs = TEH_plugin->reserves_get_origin (TEH_plugin->cls,
&wc->collectable.reserve_pub,
&wc->h_payto);
if (qs < 0)
return qs;
if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
{
*mhd_ret = TALER_MHD_reply_with_error (connection,
MHD_HTTP_NOT_FOUND,
TALER_EC_EXCHANGE_GENERIC_RESERVE_UNKNOWN,
NULL);
return GNUNET_DB_STATUS_HARD_ERROR;
}
kyc_required = TALER_KYCLOGIC_kyc_test_required (
TALER_KYCLOGIC_KYC_TRIGGER_WITHDRAW,
&wc->h_payto,
TEH_plugin->select_satisfied_kyc_processes,
TEH_plugin->cls,
&withdraw_amount_cb,
wc);
if (NULL != kyc_required)
{
/* insert KYC requirement into DB! */
wc->kyc.ok = false;
return TEH_plugin->insert_kyc_requirement_for_account (
TEH_plugin->cls,
kyc_required,
&wc->h_payto,
&wc->kyc.payment_target_uuid);
}
wc->kyc.ok = true;
bp = &wc->blinded_planchet; bp = &wc->blinded_planchet;
nonce = nonce = (TALER_DENOMINATION_CS == bp->cipher)
(TALER_DENOMINATION_CS == bp->cipher)
? &bp->details.cs_blinded_planchet.nonce ? &bp->details.cs_blinded_planchet.nonce
: NULL; : NULL;
qs = TEH_plugin->do_withdraw (TEH_plugin->cls, qs = TEH_plugin->do_withdraw (TEH_plugin->cls,
nonce, nonce,
&wc->collectable, &wc->collectable,
now, wc->now,
&found, &found,
&balance_ok, &balance_ok,
&nonce_ok, &nonce_ok,
&wc->kyc,
&ruuid); &ruuid);
if (0 > qs) if (0 > qs)
{ {
@ -153,56 +232,8 @@ withdraw_transaction (void *cls,
NULL); NULL);
return GNUNET_DB_STATUS_HARD_ERROR; return GNUNET_DB_STATUS_HARD_ERROR;
} }
if ( (TEH_KYC_NONE != TEH_kyc_config.mode) &&
(! wc->kyc.ok) &&
(TALER_EXCHANGEDB_KYC_W2W == wc->kyc.type) )
{
/* Wallet-to-wallet payments _always_ require KYC */
*mhd_ret = TALER_MHD_REPLY_JSON_PACK (
connection,
MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS,
GNUNET_JSON_pack_uint64 ("payment_target_uuid",
wc->kyc.payment_target_uuid));
return GNUNET_DB_STATUS_HARD_ERROR;
}
if ( (TEH_KYC_NONE != TEH_kyc_config.mode) &&
(! wc->kyc.ok) &&
(TALER_EXCHANGEDB_KYC_WITHDRAW == wc->kyc.type) &&
(! GNUNET_TIME_relative_is_zero (TEH_kyc_config.withdraw_period)) )
{
/* Withdraws require KYC if above threshold */
enum GNUNET_DB_QueryStatus qs2;
bool below_limit;
qs2 = TEH_plugin->do_withdraw_limit_check (
TEH_plugin->cls,
ruuid,
GNUNET_TIME_absolute_subtract (now.abs_time,
TEH_kyc_config.withdraw_period),
&TEH_kyc_config.withdraw_limit,
&below_limit);
if (0 > qs2)
{
GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs2);
if (GNUNET_DB_STATUS_HARD_ERROR == qs2)
*mhd_ret = TALER_MHD_reply_with_error (connection,
MHD_HTTP_INTERNAL_SERVER_ERROR,
TALER_EC_GENERIC_DB_FETCH_FAILED,
"do_withdraw_limit_check");
return qs2;
}
if (! below_limit)
{
*mhd_ret = TALER_MHD_REPLY_JSON_PACK (
connection,
MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS,
GNUNET_JSON_pack_uint64 ("payment_target_uuid",
wc->kyc.payment_target_uuid));
return GNUNET_DB_STATUS_HARD_ERROR;
}
}
if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs) if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs)
TEH_METRICS_num_success[TEH_MT_SUCCESS_BATCH_WITHDRAW]++; TEH_METRICS_num_success[TEH_MT_SUCCESS_WITHDRAW]++;
return qs; return qs;
} }
@ -274,7 +305,6 @@ TEH_handler_withdraw (struct TEH_RequestContext *rc,
0, 0,
sizeof (wc)); sizeof (wc));
wc.collectable.reserve_pub = *reserve_pub; wc.collectable.reserve_pub = *reserve_pub;
{ {
enum GNUNET_GenericReturnValue res; enum GNUNET_GenericReturnValue res;
@ -459,6 +489,14 @@ TEH_handler_withdraw (struct TEH_RequestContext *rc,
/* Clean up and send back final response */ /* Clean up and send back final response */
GNUNET_JSON_parse_free (spec); GNUNET_JSON_parse_free (spec);
if (! wc.kyc.ok)
{
return TALER_MHD_REPLY_JSON_PACK (
rc->connection,
MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS,
GNUNET_JSON_pack_uint64 ("payment_target_uuid",
wc.kyc.payment_target_uuid));
}
{ {
MHD_RESULT ret; MHD_RESULT ret;

View File

@ -99,11 +99,12 @@ BEGIN
PERFORM create_partitioned_table( PERFORM create_partitioned_table(
'CREATE TABLE IF NOT EXISTS %I' 'CREATE TABLE IF NOT EXISTS %I'
'(legitimization_serial_id BIGINT GENERATED BY DEFAULT AS IDENTITY' -- UNIQUE' '(legitimization_serial_id BIGINT GENERATED BY DEFAULT AS IDENTITY' -- UNIQUE'
',h_payto BYTEA NOT NULL CHECK (LENGTH(h_payto)=64)' ',h_payto BYTEA NOT NULL CHECK (LENGTH(h_payto)=32)'
',expiration_time INT8 NOT NULL DEFAULT (0)' ',expiration_time INT8 NOT NULL DEFAULT (0)'
',provider_section VARCHAR NOT NULL' ',provider_section VARCHAR NOT NULL'
',provider_user_id VARCHAR DEFAULT NULL' ',provider_user_id VARCHAR DEFAULT NULL'
',provider_legitimization_id VARCHAR DEFAULT NULL' ',provider_legitimization_id VARCHAR DEFAULT NULL'
',UNIQUE (h_payto, provider_section)'
') %s ;' ') %s ;'
,'legitimizations' ,'legitimizations'
,'PARTITION BY HASH (h_payto)' ,'PARTITION BY HASH (h_payto)'
@ -898,6 +899,7 @@ BEGIN
'(amount_val INT8 NOT NULL' '(amount_val INT8 NOT NULL'
',amount_frac INT4 NOT NULL' ',amount_frac INT4 NOT NULL'
',wire_target_h_payto BYTEA CHECK (LENGTH(wire_target_h_payto)=32)' ',wire_target_h_payto BYTEA CHECK (LENGTH(wire_target_h_payto)=32)'
',merchant_pub BYTEA CHECK (LENGTH(merchant_pub)=32)'
',exchange_account_section TEXT NOT NULL' ',exchange_account_section TEXT NOT NULL'
',wtid_raw BYTEA NOT NULL CHECK (LENGTH(wtid_raw)=32)' ',wtid_raw BYTEA NOT NULL CHECK (LENGTH(wtid_raw)=32)'
') %s ;' ') %s ;'

View File

@ -63,6 +63,22 @@ COMMENT ON TABLE denomination_revocations
IS 'remembering which denomination keys have been revoked'; IS 'remembering which denomination keys have been revoked';
-- -------------------------- kyc_alerts ----------------------------------------
CREATE TABLE IF NOT EXISTS kyc_alerts
(h_payto BYTEA PRIMARY KEY CHECK (LENGTH(h_payto)=32)
,trigger_type INT4 NOT NULL
,UNIQUE(trigger_type,h_payto)
);
COMMENT ON TABLE kyc_alerts
IS 'alerts about completed KYC events reliably notifying other components (even if they are not running)';
COMMENT ON COLUMN kyc_alerts.h_payto
IS 'hash of the payto://-URI for which the KYC status changed';
COMMENT ON COLUMN kyc_alerts.trigger_type
IS 'identifies the receiver of the alert, as the same h_payto may require multiple components to be notified';
-- ------------------------------ profit drains ---------------------------------------- -- ------------------------------ profit drains ----------------------------------------
CREATE TABLE IF NOT EXISTS profit_drains CREATE TABLE IF NOT EXISTS profit_drains

View File

@ -589,6 +589,14 @@ prepare_statements (struct PostgresClosure *pg)
" WHERE reserve_pub=$1" " WHERE reserve_pub=$1"
" LIMIT 1;", " LIMIT 1;",
1), 1),
/* Used in #postgres_reserves_get_origin() */
GNUNET_PQ_make_prepare (
"get_h_wire_source_of_reserve",
"SELECT"
" wire_source_h_payto"
" FROM reserves_in"
" WHERE reserve_pub=$1",
1),
/* Used in #postgres_set_kyc_ok() */ /* Used in #postgres_set_kyc_ok() */
GNUNET_PQ_make_prepare ( GNUNET_PQ_make_prepare (
"set_kyc_ok", "set_kyc_ok",
@ -638,6 +646,18 @@ prepare_statements (struct PostgresClosure *pg)
" FROM wire_targets" " FROM wire_targets"
" WHERE wire_target_h_payto=$1;", " WHERE wire_target_h_payto=$1;",
1), 1),
/* Used in #postgres_drain_kyc_alert() */
GNUNET_PQ_make_prepare (
"drain_kyc_alert",
"DELETE FROM kyc_alerts"
" WHERE trigger_type=$1"
" AND h_payto = "
" (SELECT h_payto "
" FROM kyc_alerts"
" WHERE trigger_type=$1"
" LIMIT 1)"
" RETURNING h_payto;",
1),
/* Used in #reserves_get() */ /* Used in #reserves_get() */
GNUNET_PQ_make_prepare ( GNUNET_PQ_make_prepare (
"reserves_get", "reserves_get",
@ -876,8 +896,6 @@ prepare_statements (struct PostgresClosure *pg)
" reserve_found" " reserve_found"
",balance_ok" ",balance_ok"
",nonce_ok" ",nonce_ok"
",kycok AS kyc_ok"
",account_uuid AS payment_target_uuid"
",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);",
@ -889,8 +907,6 @@ prepare_statements (struct PostgresClosure *pg)
"SELECT " "SELECT "
" reserve_found" " reserve_found"
",balance_ok" ",balance_ok"
",kycok AS kyc_ok"
",account_uuid AS payment_target_uuid"
",ruuid" ",ruuid"
" FROM exchange_do_batch_withdraw" " FROM exchange_do_batch_withdraw"
" ($1,$2,$3,$4,$5);", " ($1,$2,$3,$4,$5);",
@ -1719,8 +1735,8 @@ prepare_statements (struct PostgresClosure *pg)
GNUNET_PQ_make_prepare ( GNUNET_PQ_make_prepare (
"get_deposit_without_wtid", "get_deposit_without_wtid",
"SELECT" "SELECT"
" wt.kyc_ok" " legi.expiration_time"
",wt.wire_target_serial_id AS payment_target_uuid" ",legi.legitimization_serial_id"
",dep.wire_salt" ",dep.wire_salt"
",wt.payto_uri" ",wt.payto_uri"
",dep.amount_with_fee_val" ",dep.amount_with_fee_val"
@ -1732,9 +1748,12 @@ prepare_statements (struct PostgresClosure *pg)
" JOIN wire_targets wt USING (wire_target_h_payto)" " JOIN wire_targets wt USING (wire_target_h_payto)"
" JOIN known_coins kc ON (kc.coin_pub = dep.coin_pub)" " JOIN known_coins kc ON (kc.coin_pub = dep.coin_pub)"
" JOIN denominations denom USING (denominations_serial)" " JOIN denominations denom USING (denominations_serial)"
" LEFT JOIN legitimizations legi ON (wt.wire_target_h_payto = legi.h_payto)"
" WHERE dep.coin_pub=$1" " WHERE dep.coin_pub=$1"
" AND dep.merchant_pub=$3" " AND dep.merchant_pub=$3"
" AND dep.h_contract_terms=$2;", " AND dep.h_contract_terms=$2"
" ORDER BY legi.expiration_time ASC"
" LIMIT 1;",
3), 3),
/* Used in #postgres_get_ready_deposit() */ /* Used in #postgres_get_ready_deposit() */
GNUNET_PQ_make_prepare ( GNUNET_PQ_make_prepare (
@ -1744,18 +1763,18 @@ prepare_statements (struct PostgresClosure *pg)
",merchant_pub" ",merchant_pub"
" FROM deposits_by_ready dbr" " FROM deposits_by_ready dbr"
" JOIN deposits dep" " JOIN deposits dep"
" ON (dbr.coin_pub = dep.coin_pub AND dbr.deposit_serial_id = dep.deposit_serial_id)" " ON (dbr.coin_pub = dep.coin_pub AND"
" dbr.deposit_serial_id = dep.deposit_serial_id)"
" JOIN wire_targets wt" " JOIN wire_targets wt"
" USING (wire_target_h_payto)" " USING (wire_target_h_payto)"
" WHERE dbr.wire_deadline<=$1" " WHERE dbr.wire_deadline<=$1"
" AND dbr.shard >= $2" " AND dbr.shard >= $2"
" AND dbr.shard <= $3" " AND dbr.shard <= $3"
" AND (wt.kyc_ok OR $4)"
" ORDER BY " " ORDER BY "
" dbr.wire_deadline ASC" " dbr.wire_deadline ASC"
" ,dbr.shard ASC" " ,dbr.shard ASC"
" LIMIT 1;", " LIMIT 1;",
4), 3),
/* Used in #postgres_aggregate() */ /* Used in #postgres_aggregate() */
GNUNET_PQ_make_prepare ( GNUNET_PQ_make_prepare (
"aggregate", "aggregate",
@ -1850,11 +1869,12 @@ prepare_statements (struct PostgresClosure *pg)
"INSERT INTO aggregation_transient" "INSERT INTO aggregation_transient"
" (amount_val" " (amount_val"
" ,amount_frac" " ,amount_frac"
" ,merchant_pub"
" ,wire_target_h_payto" " ,wire_target_h_payto"
" ,exchange_account_section" " ,exchange_account_section"
" ,wtid_raw)" " ,wtid_raw)"
" VALUES ($1, $2, $3, $4, $5);", " VALUES ($1, $2, $3, $4, $5, $6);",
5), 6),
/* Used in #postgres_select_aggregation_transient() */ /* Used in #postgres_select_aggregation_transient() */
GNUNET_PQ_make_prepare ( GNUNET_PQ_make_prepare (
"select_aggregation_transient", "select_aggregation_transient",
@ -1864,8 +1884,22 @@ prepare_statements (struct PostgresClosure *pg)
" ,wtid_raw" " ,wtid_raw"
" FROM aggregation_transient" " FROM aggregation_transient"
" WHERE wire_target_h_payto=$1" " WHERE wire_target_h_payto=$1"
" AND exchange_account_section=$2;", " AND merchant_pub=$2"
2), " AND exchange_account_section=$3;",
3),
/* Used in #postgres_find_aggregation_transient() */
GNUNET_PQ_make_prepare (
"find_transient_aggregations",
"SELECT"
" amount_val"
" ,amount_frac"
" ,wtid_raw"
" ,merchant_pub"
" ,payto_uri"
" FROM aggregation_transient atr"
" JOIN wire_targets wt USING (wire_target_h_payto)"
" WHERE atr.wire_target_h_payto=$1;",
1),
/* Used in #postgres_update_aggregation_transient() */ /* Used in #postgres_update_aggregation_transient() */
GNUNET_PQ_make_prepare ( GNUNET_PQ_make_prepare (
"update_aggregation_transient", "update_aggregation_transient",
@ -4525,6 +4559,8 @@ prepare_statements (struct PostgresClosure *pg)
" ,provider_section" " ,provider_section"
" ) VALUES " " ) VALUES "
" ($1, $2)" " ($1, $2)"
" ON CONFLICT (h_payto,provider_section) "
" DO UPDATE SET h_payto=$1" /* syntax requirement: dummy op */
" RETURNING legitimization_serial_id", " RETURNING legitimization_serial_id",
2), 2),
/* Used in #postgres_update_kyc_requirement_by_row() */ /* Used in #postgres_update_kyc_requirement_by_row() */
@ -4533,12 +4569,20 @@ prepare_statements (struct PostgresClosure *pg)
"UPDATE legitimizations" "UPDATE legitimizations"
" SET provider_user_id=$4" " SET provider_user_id=$4"
" ,provider_legitimization_id=$5" " ,provider_legitimization_id=$5"
" ,expiration_time=$6" " ,expiration_time=GREATEST(expiration_time,$6)"
" WHERE" " WHERE"
" h_payto=$3" " h_payto=$3"
" AND legitimization_serial_id=$1" " AND legitimization_serial_id=$1"
" AND provider_section=$2;", " AND provider_section=$2;",
6), 6),
GNUNET_PQ_make_prepare (
"alert_kyc_status_change",
"INSERT INTO kyc_alerts"
" (h_payto"
" ,trigger_type)"
" VALUES"
" ($1,$2);",
2),
/* Used in #postgres_lookup_kyc_requirement_by_row() */ /* Used in #postgres_lookup_kyc_requirement_by_row() */
GNUNET_PQ_make_prepare ( GNUNET_PQ_make_prepare (
"lookup_legitimization_by_row", "lookup_legitimization_by_row",
@ -4598,7 +4642,6 @@ prepare_statements (struct PostgresClosure *pg)
" AND ro.execution_date >= $2" " AND ro.execution_date >= $2"
" ORDER BY ro.execution_date DESC", " ORDER BY ro.execution_date DESC",
2), 2),
/* Used in #postgres_select_aggregation_amounts_for_kyc_check ( /* Used in #postgres_select_aggregation_amounts_for_kyc_check (
() */ () */
GNUNET_PQ_make_prepare ( GNUNET_PQ_make_prepare (
@ -5705,7 +5748,6 @@ postgres_reserves_get (void *cls,
GNUNET_PQ_result_spec_end GNUNET_PQ_result_spec_end
}; };
kyc->type = TALER_EXCHANGEDB_KYC_WITHDRAW;
return GNUNET_PQ_eval_prepared_singleton_select (pg->conn, return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
"reserves_get_with_kyc", "reserves_get_with_kyc",
params, params,
@ -5713,6 +5755,38 @@ postgres_reserves_get (void *cls,
} }
/**
* Get the origin of funds of a reserve.
*
* @param cls the `struct PostgresClosure` with the plugin-specific state
* @param reserve_pub public key of the reserve
* @param[out] h_payto set to hash of the wire source payto://-URI
* @return transaction status
*/
static enum GNUNET_DB_QueryStatus
postgres_reserves_get_origin (
void *cls,
const struct TALER_ReservePublicKeyP *reserve_pub,
struct TALER_PaytoHashP *h_payto)
{
struct PostgresClosure *pg = cls;
struct GNUNET_PQ_QueryParam params[] = {
GNUNET_PQ_query_param_auto_from_type (reserve_pub),
GNUNET_PQ_query_param_end
};
struct GNUNET_PQ_ResultSpec rs[] = {
GNUNET_PQ_result_spec_auto_from_type ("wire_source_h_payto",
h_payto),
GNUNET_PQ_result_spec_end
};
return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
"get_h_wire_source_of_reserve",
params,
rs);
}
/** /**
* Set the KYC status to "OK" for a bank account. * Set the KYC status to "OK" for a bank account.
* *
@ -5766,6 +5840,37 @@ postgres_set_kyc_ok (void *cls,
} }
/**
* Extract next KYC alert. Deletes the alert.
*
* @param cls the @e cls of this struct with the plugin-specific state
* @param trigger_type which type of alert to drain
* @param[out] h_payto set to hash of payto-URI where KYC status changed
* @return transaction status
*/
static enum GNUNET_DB_QueryStatus
postgres_drain_kyc_alert (void *cls,
uint32_t trigger_type,
struct TALER_PaytoHashP *h_payto)
{
struct PostgresClosure *pg = cls;
struct GNUNET_PQ_QueryParam params[] = {
GNUNET_PQ_query_param_uint32 (&trigger_type),
GNUNET_PQ_query_param_end
};
struct GNUNET_PQ_ResultSpec rs[] = {
GNUNET_PQ_result_spec_auto_from_type ("h_payto",
h_payto),
GNUNET_PQ_result_spec_end
};
return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
"drain_kyc_alert",
params,
rs);
}
/** /**
* Get the @a kyc status and @a h_payto by UUID. * Get the @a kyc status and @a h_payto by UUID.
* *
@ -5792,7 +5897,6 @@ postgres_select_kyc_status (void *cls,
GNUNET_PQ_result_spec_end GNUNET_PQ_result_spec_end
}; };
kyc->type = TALER_EXCHANGEDB_KYC_UNKNOWN;
return GNUNET_PQ_eval_prepared_singleton_select (pg->conn, return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
"select_kyc_status_by_payto", "select_kyc_status_by_payto",
params, params,
@ -5863,7 +5967,6 @@ inselect_account_kyc_status (
kyc->ok = false; kyc->ok = false;
} }
} }
kyc->type = TALER_EXCHANGEDB_KYC_BALANCE;
return qs; return qs;
} }
@ -5888,7 +5991,7 @@ postgres_inselect_wallet_kyc_status (
enum GNUNET_DB_QueryStatus qs; enum GNUNET_DB_QueryStatus qs;
struct TALER_PaytoHashP h_payto; struct TALER_PaytoHashP h_payto;
payto_uri = TALER_payto_from_reserve (pg->exchange_url, payto_uri = TALER_reserve_make_payto (pg->exchange_url,
reserve_pub); reserve_pub);
qs = inselect_account_kyc_status (pg, qs = inselect_account_kyc_status (pg,
payto_uri, payto_uri,
@ -6284,7 +6387,6 @@ postgres_get_withdraw_info (
* @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] kyc set to true if the kyc status of the reserve is satisfied
* @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
*/ */
@ -6297,7 +6399,6 @@ postgres_do_withdraw (
bool *found, bool *found,
bool *balance_ok, bool *balance_ok,
bool *nonce_ok, bool *nonce_ok,
struct TALER_EXCHANGEDB_KycStatus *kyc,
uint64_t *ruuid) uint64_t *ruuid)
{ {
struct PostgresClosure *pg = cls; struct PostgresClosure *pg = cls;
@ -6321,12 +6422,8 @@ postgres_do_withdraw (
found), found),
GNUNET_PQ_result_spec_bool ("balance_ok", GNUNET_PQ_result_spec_bool ("balance_ok",
balance_ok), balance_ok),
GNUNET_PQ_result_spec_bool ("kyc_ok",
&kyc->ok),
GNUNET_PQ_result_spec_bool ("nonce_ok", GNUNET_PQ_result_spec_bool ("nonce_ok",
nonce_ok), nonce_ok),
GNUNET_PQ_result_spec_uint64 ("payment_target_uuid",
&kyc->payment_target_uuid),
GNUNET_PQ_result_spec_uint64 ("ruuid", GNUNET_PQ_result_spec_uint64 ("ruuid",
ruuid), ruuid),
GNUNET_PQ_result_spec_end GNUNET_PQ_result_spec_end
@ -6335,7 +6432,6 @@ postgres_do_withdraw (
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));
kyc->type = TALER_EXCHANGEDB_KYC_WITHDRAW;
return GNUNET_PQ_eval_prepared_singleton_select (pg->conn, return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
"call_withdraw", "call_withdraw",
params, params,
@ -6354,7 +6450,6 @@ postgres_do_withdraw (
* @param amount total amount to withdraw * @param amount total amount to withdraw
* @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] kyc set to the KYC status of the reserve
* @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
*/ */
@ -6366,7 +6461,6 @@ postgres_do_batch_withdraw (
const struct TALER_Amount *amount, const struct TALER_Amount *amount,
bool *found, bool *found,
bool *balance_ok, bool *balance_ok,
struct TALER_EXCHANGEDB_KycStatus *kyc,
uint64_t *ruuid) uint64_t *ruuid)
{ {
struct PostgresClosure *pg = cls; struct PostgresClosure *pg = cls;
@ -6383,10 +6477,6 @@ postgres_do_batch_withdraw (
found), found),
GNUNET_PQ_result_spec_bool ("balance_ok", GNUNET_PQ_result_spec_bool ("balance_ok",
balance_ok), balance_ok),
GNUNET_PQ_result_spec_bool ("kyc_ok",
&kyc->ok),
GNUNET_PQ_result_spec_uint64 ("payment_target_uuid",
&kyc->payment_target_uuid),
GNUNET_PQ_result_spec_uint64 ("ruuid", GNUNET_PQ_result_spec_uint64 ("ruuid",
ruuid), ruuid),
GNUNET_PQ_result_spec_end GNUNET_PQ_result_spec_end
@ -6395,7 +6485,6 @@ postgres_do_batch_withdraw (
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));
kyc->type = TALER_EXCHANGEDB_KYC_WITHDRAW;
return GNUNET_PQ_eval_prepared_singleton_select (pg->conn, return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
"call_batch_withdraw", "call_batch_withdraw",
params, params,
@ -7743,12 +7832,14 @@ postgres_create_aggregation_transient (
void *cls, void *cls,
const struct TALER_PaytoHashP *h_payto, const struct TALER_PaytoHashP *h_payto,
const char *exchange_account_section, const char *exchange_account_section,
const struct TALER_MerchantPublicKeyP *merchant_pub,
const struct TALER_WireTransferIdentifierRawP *wtid, const struct TALER_WireTransferIdentifierRawP *wtid,
const struct TALER_Amount *total) const struct TALER_Amount *total)
{ {
struct PostgresClosure *pg = cls; struct PostgresClosure *pg = cls;
struct GNUNET_PQ_QueryParam params[] = { struct GNUNET_PQ_QueryParam params[] = {
TALER_PQ_query_param_amount (total), TALER_PQ_query_param_amount (total),
GNUNET_PQ_query_param_auto_from_type (merchant_pub),
GNUNET_PQ_query_param_auto_from_type (h_payto), GNUNET_PQ_query_param_auto_from_type (h_payto),
GNUNET_PQ_query_param_string (exchange_account_section), GNUNET_PQ_query_param_string (exchange_account_section),
GNUNET_PQ_query_param_auto_from_type (wtid), GNUNET_PQ_query_param_auto_from_type (wtid),
@ -7775,6 +7866,7 @@ static enum GNUNET_DB_QueryStatus
postgres_select_aggregation_transient ( postgres_select_aggregation_transient (
void *cls, void *cls,
const struct TALER_PaytoHashP *h_payto, const struct TALER_PaytoHashP *h_payto,
const struct TALER_MerchantPublicKeyP *merchant_pub,
const char *exchange_account_section, const char *exchange_account_section,
struct TALER_WireTransferIdentifierRawP *wtid, struct TALER_WireTransferIdentifierRawP *wtid,
struct TALER_Amount *total) struct TALER_Amount *total)
@ -7782,6 +7874,7 @@ postgres_select_aggregation_transient (
struct PostgresClosure *pg = cls; struct PostgresClosure *pg = cls;
struct GNUNET_PQ_QueryParam params[] = { struct GNUNET_PQ_QueryParam params[] = {
GNUNET_PQ_query_param_auto_from_type (h_payto), GNUNET_PQ_query_param_auto_from_type (h_payto),
GNUNET_PQ_query_param_auto_from_type (merchant_pub),
GNUNET_PQ_query_param_string (exchange_account_section), GNUNET_PQ_query_param_string (exchange_account_section),
GNUNET_PQ_query_param_end GNUNET_PQ_query_param_end
}; };
@ -7800,6 +7893,129 @@ postgres_select_aggregation_transient (
} }
/**
* Closure for #get_refunds_cb().
*/
struct FindAggregationTransientContext
{
/**
* Function to call on each result.
*/
TALER_EXCHANGEDB_TransientAggregationCallback cb;
/**
* Closure for @a cb.
*/
void *cb_cls;
/**
* Plugin context.
*/
struct PostgresClosure *pg;
/**
* Set to #GNUNET_SYSERR on error.
*/
enum GNUNET_GenericReturnValue status;
};
/**
* Function to be called with the results of a SELECT statement
* that has returned @a num_results results.
*
* @param cls closure of type `struct SelectRefundContext *`
* @param result the postgres result
* @param num_results the number of results in @a result
*/
static void
get_transients_cb (void *cls,
PGresult *result,
unsigned int num_results)
{
struct FindAggregationTransientContext *srctx = cls;
struct PostgresClosure *pg = srctx->pg;
for (unsigned int i = 0; i<num_results; i++)
{
struct TALER_Amount amount;
char *payto_uri;
struct TALER_WireTransferIdentifierRawP wtid;
struct TALER_MerchantPublicKeyP merchant_pub;
struct GNUNET_PQ_ResultSpec rs[] = {
GNUNET_PQ_result_spec_auto_from_type ("merchant_pub",
&merchant_pub),
GNUNET_PQ_result_spec_auto_from_type ("wtid_raw",
&wtid),
GNUNET_PQ_result_spec_string ("payto_uri",
&payto_uri),
TALER_PQ_RESULT_SPEC_AMOUNT ("amount",
&amount),
GNUNET_PQ_result_spec_end
};
bool cont;
if (GNUNET_OK !=
GNUNET_PQ_extract_result (result,
rs,
i))
{
GNUNET_break (0);
srctx->status = GNUNET_SYSERR;
return;
}
cont = srctx->cb (srctx->cb_cls,
payto_uri,
&wtid,
&merchant_pub,
&amount);
GNUNET_free (payto_uri);
if (! cont)
break;
}
}
/**
* Find existing entry in the transient aggregation table.
*
* @param cls the @e cls of this struct with the plugin-specific state
* @param h_payto destination of the wire transfer
* @param cb function to call on each matching entry
* @param cb_cls closure for @a cb
* @return transaction status
*/
static enum GNUNET_DB_QueryStatus
postgres_find_aggregation_transient (
void *cls,
const struct TALER_PaytoHashP *h_payto,
TALER_EXCHANGEDB_TransientAggregationCallback cb,
void *cb_cls)
{
struct PostgresClosure *pg = cls;
enum GNUNET_DB_QueryStatus qs;
struct GNUNET_PQ_QueryParam params[] = {
GNUNET_PQ_query_param_auto_from_type (h_payto),
GNUNET_PQ_query_param_end
};
struct FindAggregationTransientContext srctx = {
.cb = cb,
.cb_cls = cb_cls,
.pg = pg,
.status = GNUNET_OK
};
qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
"find_transient_aggregations",
params,
&get_transients_cb,
&srctx);
if (GNUNET_SYSERR == srctx.status)
return GNUNET_DB_STATUS_HARD_ERROR;
return qs;
}
/** /**
* Update existing entry in the transient aggregation table. * Update existing entry in the transient aggregation table.
* @a h_payto is only needed for query performance. * @a h_payto is only needed for query performance.
@ -7867,8 +8083,6 @@ postgres_delete_aggregation_transient (
* @param cls the @e cls of this struct with the plugin-specific state * @param cls the @e cls of this struct with the plugin-specific state
* @param start_shard_row minimum shard row to select * @param start_shard_row minimum shard row to select
* @param end_shard_row maximum shard row to select (inclusive) * @param end_shard_row maximum shard row to select (inclusive)
* @param kyc_off true if we should not check the KYC status because
* this exchange does not need/support KYC checks.
* @param[out] merchant_pub set to the public key of a merchant with a ready deposit * @param[out] merchant_pub set to the public key of a merchant with a ready deposit
* @param[out] payto_uri set to the account of the merchant, to be freed by caller * @param[out] payto_uri set to the account of the merchant, to be freed by caller
* @return transaction status code * @return transaction status code
@ -7877,7 +8091,6 @@ static enum GNUNET_DB_QueryStatus
postgres_get_ready_deposit (void *cls, postgres_get_ready_deposit (void *cls,
uint64_t start_shard_row, uint64_t start_shard_row,
uint64_t end_shard_row, uint64_t end_shard_row,
bool kyc_off,
struct TALER_MerchantPublicKeyP *merchant_pub, struct TALER_MerchantPublicKeyP *merchant_pub,
char **payto_uri) char **payto_uri)
{ {
@ -7887,7 +8100,6 @@ postgres_get_ready_deposit (void *cls,
GNUNET_PQ_query_param_absolute_time (&now), GNUNET_PQ_query_param_absolute_time (&now),
GNUNET_PQ_query_param_uint64 (&start_shard_row), GNUNET_PQ_query_param_uint64 (&start_shard_row),
GNUNET_PQ_query_param_uint64 (&end_shard_row), GNUNET_PQ_query_param_uint64 (&end_shard_row),
GNUNET_PQ_query_param_bool (kyc_off),
GNUNET_PQ_query_param_end GNUNET_PQ_query_param_end
}; };
struct GNUNET_PQ_ResultSpec rs[] = { struct GNUNET_PQ_ResultSpec rs[] = {
@ -9644,7 +9856,11 @@ postgres_lookup_transfer_by_deposit (
deposit_fee), deposit_fee),
GNUNET_PQ_result_spec_end GNUNET_PQ_result_spec_end
}; };
struct GNUNET_TIME_Absolute expiration;
memset (kyc,
0,
sizeof (*kyc));
/* check if the aggregation record exists and get it */ /* check if the aggregation record exists and get it */
qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn, qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
"lookup_deposit_wtid", "lookup_deposit_wtid",
@ -9663,10 +9879,6 @@ postgres_lookup_transfer_by_deposit (
h_wire)) h_wire))
{ {
*pending = false; *pending = false;
memset (kyc,
0,
sizeof (*kyc));
kyc->type = TALER_EXCHANGEDB_KYC_DEPOSIT;
kyc->ok = true; kyc->ok = true;
return qs; return qs;
} }
@ -9685,15 +9897,21 @@ postgres_lookup_transfer_by_deposit (
/* Check if transaction exists in deposits, so that we just /* Check if transaction exists in deposits, so that we just
do not have a WTID yet. In that case, return without wtid do not have a WTID yet. In that case, return without wtid
(by setting 'pending' true). */ (by setting 'pending' true). */
bool no_kyc = false;
struct GNUNET_PQ_ResultSpec rs2[] = { struct GNUNET_PQ_ResultSpec rs2[] = {
GNUNET_PQ_result_spec_auto_from_type ("wire_salt", GNUNET_PQ_result_spec_auto_from_type ("wire_salt",
&wire_salt), &wire_salt),
GNUNET_PQ_result_spec_string ("payto_uri", GNUNET_PQ_result_spec_string ("payto_uri",
&payto_uri), &payto_uri),
GNUNET_PQ_result_spec_uint64 ("payment_target_uuid", GNUNET_PQ_result_spec_allow_null (
&kyc->payment_target_uuid), GNUNET_PQ_result_spec_uint64 ("legitimization_serial_id",
GNUNET_PQ_result_spec_auto_from_type ("kyc_ok",
&kyc->ok), &kyc->payment_target_uuid),
&no_kyc),
GNUNET_PQ_result_spec_allow_null (
GNUNET_PQ_result_spec_absolute_time ("expiration_time",
&expiration),
&no_kyc),
TALER_PQ_RESULT_SPEC_AMOUNT ("amount_with_fee", TALER_PQ_RESULT_SPEC_AMOUNT ("amount_with_fee",
amount_with_fee), amount_with_fee),
TALER_PQ_RESULT_SPEC_AMOUNT ("fee_deposit", TALER_PQ_RESULT_SPEC_AMOUNT ("fee_deposit",
@ -9711,6 +9929,10 @@ postgres_lookup_transfer_by_deposit (
{ {
struct TALER_MerchantWireHashP wh; struct TALER_MerchantWireHashP wh;
if (no_kyc)
kyc->payment_target_uuid = 0;
else
kyc->ok = GNUNET_TIME_absolute_is_future (expiration);
TALER_merchant_wire_signature_hash (payto_uri, TALER_merchant_wire_signature_hash (payto_uri,
&wire_salt, &wire_salt,
&wh); &wh);
@ -9720,7 +9942,6 @@ postgres_lookup_transfer_by_deposit (
h_wire)) h_wire))
return GNUNET_DB_STATUS_SUCCESS_NO_RESULTS; return GNUNET_DB_STATUS_SUCCESS_NO_RESULTS;
} }
kyc->type = TALER_EXCHANGEDB_KYC_DEPOSIT;
return qs; return qs;
} }
} }
@ -16149,7 +16370,7 @@ postgres_do_purse_merge (
{ {
char *payto_uri; char *payto_uri;
payto_uri = TALER_payto_from_reserve (pg->exchange_url, payto_uri = TALER_reserve_make_payto (pg->exchange_url,
reserve_pub); reserve_pub);
TALER_payto_hash (payto_uri, TALER_payto_hash (payto_uri,
&h_payto); &h_payto);
@ -16228,7 +16449,7 @@ postgres_do_reserve_purse (
{ {
char *payto_uri; char *payto_uri;
payto_uri = TALER_payto_from_reserve (pg->exchange_url, payto_uri = TALER_reserve_make_payto (pg->exchange_url,
reserve_pub); reserve_pub);
TALER_payto_hash (payto_uri, TALER_payto_hash (payto_uri,
&h_payto); &h_payto);
@ -16612,16 +16833,57 @@ postgres_update_kyc_requirement_by_row (
GNUNET_PQ_query_param_uint64 (&legi_row), GNUNET_PQ_query_param_uint64 (&legi_row),
GNUNET_PQ_query_param_string (provider_section), GNUNET_PQ_query_param_string (provider_section),
GNUNET_PQ_query_param_auto_from_type (h_payto), GNUNET_PQ_query_param_auto_from_type (h_payto),
GNUNET_PQ_query_param_string (provider_account_id), (NULL != provider_account_id)
GNUNET_PQ_query_param_string (provider_legitimization_id), ? GNUNET_PQ_query_param_string (provider_account_id)
: GNUNET_PQ_query_param_null (),
(NULL != provider_legitimization_id)
? GNUNET_PQ_query_param_string (provider_legitimization_id)
: GNUNET_PQ_query_param_null (),
GNUNET_PQ_query_param_absolute_time (&expiration), GNUNET_PQ_query_param_absolute_time (&expiration),
GNUNET_PQ_query_param_end GNUNET_PQ_query_param_end
}; };
enum GNUNET_DB_QueryStatus qs;
return GNUNET_PQ_eval_prepared_non_select ( qs = GNUNET_PQ_eval_prepared_non_select (
pg->conn, pg->conn,
"update_legitimization_requirement", "update_legitimization_requirement",
params); params);
if (qs <= 0)
{
GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
"Failed to update legitimization: %d\n",
qs);
return qs;
}
if (GNUNET_TIME_absolute_is_future (expiration))
{
enum GNUNET_DB_QueryStatus qs2;
struct TALER_KycCompletedEventP rep = {
.header.size = htons (sizeof (rep)),
.header.type = htons (TALER_DBEVENT_EXCHANGE_KYC_COMPLETED),
.h_payto = *h_payto
};
uint32_t trigger_type = 1;
struct GNUNET_PQ_QueryParam params2[] = {
GNUNET_PQ_query_param_auto_from_type (h_payto),
GNUNET_PQ_query_param_uint32 (&trigger_type),
GNUNET_PQ_query_param_end
};
postgres_event_notify (pg,
&rep.header,
NULL,
0);
qs2 = GNUNET_PQ_eval_prepared_non_select (
pg->conn,
"alert_kyc_status_change",
params2);
if (qs2 < 0)
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Failed to store KYC alert: %d\n",
qs2);
}
return qs;
} }
@ -16831,6 +17093,9 @@ get_legitimizations_cb (void *cls,
ctx->status = GNUNET_SYSERR; ctx->status = GNUNET_SYSERR;
return; return;
} }
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Found satisfied LEGI: %s\n",
provider_section);
ctx->cb (ctx->cb_cls, ctx->cb (ctx->cb_cls,
provider_section); provider_section);
GNUNET_PQ_cleanup_result (rs); GNUNET_PQ_cleanup_result (rs);
@ -16877,6 +17142,9 @@ postgres_select_satisfied_kyc_processes (
params, params,
&get_legitimizations_cb, &get_legitimizations_cb,
&ctx); &ctx);
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Satisfied LEGI check returned %d\n",
qs);
if (GNUNET_OK != ctx.status) if (GNUNET_OK != ctx.status)
return GNUNET_DB_STATUS_HARD_ERROR; return GNUNET_DB_STATUS_HARD_ERROR;
return qs; return qs;
@ -17220,7 +17488,9 @@ libtaler_plugin_exchangedb_postgres_init (void *cls)
&postgres_iterate_auditor_denominations; &postgres_iterate_auditor_denominations;
plugin->select_kyc_status = &postgres_select_kyc_status; plugin->select_kyc_status = &postgres_select_kyc_status;
plugin->reserves_get = &postgres_reserves_get; plugin->reserves_get = &postgres_reserves_get;
plugin->reserves_get_origin = &postgres_reserves_get_origin;
plugin->set_kyc_ok = &postgres_set_kyc_ok; plugin->set_kyc_ok = &postgres_set_kyc_ok;
plugin->drain_kyc_alert = &postgres_drain_kyc_alert;
plugin->inselect_wallet_kyc_status = &postgres_inselect_wallet_kyc_status; plugin->inselect_wallet_kyc_status = &postgres_inselect_wallet_kyc_status;
plugin->reserves_in_insert = &postgres_reserves_in_insert; plugin->reserves_in_insert = &postgres_reserves_in_insert;
plugin->get_withdraw_info = &postgres_get_withdraw_info; plugin->get_withdraw_info = &postgres_get_withdraw_info;
@ -17247,6 +17517,8 @@ libtaler_plugin_exchangedb_postgres_init (void *cls)
= &postgres_create_aggregation_transient; = &postgres_create_aggregation_transient;
plugin->select_aggregation_transient plugin->select_aggregation_transient
= &postgres_select_aggregation_transient; = &postgres_select_aggregation_transient;
plugin->find_aggregation_transient
= &postgres_find_aggregation_transient;
plugin->update_aggregation_transient plugin->update_aggregation_transient
= &postgres_update_aggregation_transient; = &postgres_update_aggregation_transient;
plugin->delete_aggregation_transient plugin->delete_aggregation_transient

View File

@ -37,8 +37,6 @@ CREATE OR REPLACE FUNCTION exchange_do_withdraw(
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 kycok BOOLEAN,
OUT account_uuid INT8,
OUT ruuid INT8) OUT ruuid INT8)
LANGUAGE plpgsql LANGUAGE plpgsql
AS $$ AS $$
@ -67,8 +65,6 @@ THEN
-- denomination unknown, should be impossible! -- denomination unknown, should be impossible!
reserve_found=FALSE; reserve_found=FALSE;
balance_ok=FALSE; balance_ok=FALSE;
kycok=FALSE;
account_uuid=0;
ruuid=0; ruuid=0;
ASSERT false, 'denomination unknown'; ASSERT false, 'denomination unknown';
RETURN; RETURN;
@ -94,8 +90,6 @@ THEN
reserve_found=FALSE; reserve_found=FALSE;
balance_ok=FALSE; balance_ok=FALSE;
nonce_ok=TRUE; nonce_ok=TRUE;
kycok=FALSE;
account_uuid=0;
ruuid=2; ruuid=2;
RETURN; RETURN;
END IF; END IF;
@ -128,8 +122,6 @@ THEN
reserve_found=TRUE; reserve_found=TRUE;
balance_ok=TRUE; balance_ok=TRUE;
nonce_ok=TRUE; nonce_ok=TRUE;
kycok=TRUE;
account_uuid=0;
RETURN; RETURN;
END IF; END IF;
@ -153,8 +145,6 @@ ELSE
reserve_found=TRUE; reserve_found=TRUE;
nonce_ok=TRUE; -- we do not really know nonce_ok=TRUE; -- we do not really know
balance_ok=FALSE; balance_ok=FALSE;
kycok=FALSE; -- we do not really know or care
account_uuid=0;
RETURN; RETURN;
END IF; END IF;
END IF; END IF;
@ -201,8 +191,6 @@ THEN
THEN THEN
reserve_found=FALSE; reserve_found=FALSE;
balance_ok=FALSE; balance_ok=FALSE;
kycok=FALSE;
account_uuid=0;
nonce_ok=FALSE; nonce_ok=FALSE;
RETURN; RETURN;
END IF; END IF;
@ -211,40 +199,9 @@ ELSE
nonce_ok=TRUE; -- no nonce, hence OK! nonce_ok=TRUE; -- no nonce, hence OK!
END IF; END IF;
-- Obtain KYC status based on the last wire transfer into
-- this reserve. FIXME: likely not adequate for reserves that got P2P transfers!
-- SELECT
-- kyc_ok
-- ,wire_target_serial_id
-- INTO
-- kycok
-- ,account_uuid
-- FROM exchange.reserves_in
-- JOIN wire_targets ON (wire_source_h_payto = wire_target_h_payto)
-- WHERE reserve_pub=rpub
-- LIMIT 1; -- limit 1 should not be required (without p2p transfers)
WITH my_reserves_in AS materialized (
SELECT wire_source_h_payto
FROM exchange.reserves_in
WHERE reserve_pub=rpub
)
SELECT
kyc_ok
,wire_target_serial_id
INTO
kycok
,account_uuid
FROM exchange.wire_targets
WHERE wire_target_h_payto = (
SELECT wire_source_h_payto
FROM my_reserves_in
);
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)
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 so updates the database with the result';
@ -259,8 +216,6 @@ CREATE OR REPLACE FUNCTION exchange_do_batch_withdraw(
IN min_reserve_gc INT8, IN min_reserve_gc INT8,
OUT reserve_found BOOLEAN, OUT reserve_found BOOLEAN,
OUT balance_ok BOOLEAN, OUT balance_ok BOOLEAN,
OUT kycok BOOLEAN,
OUT account_uuid INT8,
OUT ruuid INT8) OUT ruuid INT8)
LANGUAGE plpgsql LANGUAGE plpgsql
AS $$ AS $$
@ -295,8 +250,6 @@ THEN
-- reserve unknown -- reserve unknown
reserve_found=FALSE; reserve_found=FALSE;
balance_ok=FALSE; balance_ok=FALSE;
kycok=FALSE;
account_uuid=0;
ruuid=2; ruuid=2;
RETURN; RETURN;
END IF; END IF;
@ -320,8 +273,6 @@ ELSE
ELSE ELSE
reserve_found=TRUE; reserve_found=TRUE;
balance_ok=FALSE; balance_ok=FALSE;
kycok=FALSE; -- we do not really know or care
account_uuid=0;
RETURN; RETURN;
END IF; END IF;
END IF; END IF;
@ -340,37 +291,6 @@ WHERE
reserve_found=TRUE; reserve_found=TRUE;
balance_ok=TRUE; balance_ok=TRUE;
-- Obtain KYC status based on the last wire transfer into
-- this reserve. FIXME: likely not adequate for reserves that got P2P transfers!
-- SELECT
-- kyc_ok
-- ,wire_target_serial_id
-- INTO
-- kycok
-- ,account_uuid
-- FROM exchange.reserves_in
-- JOIN wire_targets ON (wire_source_h_payto = wire_target_h_payto)
-- WHERE reserve_pub=rpub
-- LIMIT 1; -- limit 1 should not be required (without p2p transfers)
WITH my_reserves_in AS materialized (
SELECT wire_source_h_payto
FROM exchange.reserves_in
WHERE reserve_pub=rpub
)
SELECT
kyc_ok
,wire_target_serial_id
INTO
kycok
,account_uuid
FROM exchange.wire_targets
WHERE wire_target_h_payto = (
SELECT wire_source_h_payto
FROM my_reserves_in
);
END $$; END $$;
COMMENT ON FUNCTION exchange_do_batch_withdraw(INT8, INT4, BYTEA, INT8, INT8) COMMENT ON FUNCTION exchange_do_batch_withdraw(INT8, INT4, BYTEA, INT8, INT8)

View File

@ -1399,7 +1399,6 @@ run (void *cls)
bool found; bool found;
bool nonce_ok; bool nonce_ok;
bool balance_ok; bool balance_ok;
struct TALER_EXCHANGEDB_KycStatus kyc;
uint64_t ruuid; uint64_t ruuid;
FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
@ -1410,12 +1409,10 @@ run (void *cls)
&found, &found,
&balance_ok, &balance_ok,
&nonce_ok, &nonce_ok,
&kyc,
&ruuid)); &ruuid));
GNUNET_assert (found); GNUNET_assert (found);
GNUNET_assert (nonce_ok); GNUNET_assert (nonce_ok);
GNUNET_assert (balance_ok); GNUNET_assert (balance_ok);
GNUNET_assert (! kyc.ok);
} }
FAILIF (GNUNET_OK != FAILIF (GNUNET_OK !=
check_reserve (&reserve_pub, check_reserve (&reserve_pub,
@ -2159,7 +2156,6 @@ run (void *cls)
plugin->get_ready_deposit (plugin->cls, plugin->get_ready_deposit (plugin->cls,
0, 0,
INT32_MAX, INT32_MAX,
true,
&merchant_pub2, &merchant_pub2,
&payto_uri2)); &payto_uri2));
FAILIF (0 != GNUNET_memcmp (&merchant_pub2, FAILIF (0 != GNUNET_memcmp (&merchant_pub2,
@ -2205,6 +2201,7 @@ run (void *cls)
FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS != FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
plugin->select_aggregation_transient (plugin->cls, plugin->select_aggregation_transient (plugin->cls,
&wire_target_h_payto, &wire_target_h_payto,
&deposit.merchant_pub,
"x-bank", "x-bank",
&wtid2, &wtid2,
&total2)); &total2));
@ -2212,11 +2209,13 @@ run (void *cls)
plugin->create_aggregation_transient (plugin->cls, plugin->create_aggregation_transient (plugin->cls,
&wire_target_h_payto, &wire_target_h_payto,
"x-bank", "x-bank",
&deposit.merchant_pub,
&wtid, &wtid,
&total)); &total));
FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
plugin->select_aggregation_transient (plugin->cls, plugin->select_aggregation_transient (plugin->cls,
&wire_target_h_payto, &wire_target_h_payto,
&deposit.merchant_pub,
"x-bank", "x-bank",
&wtid2, &wtid2,
&total2)); &total2));
@ -2237,6 +2236,7 @@ run (void *cls)
FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
plugin->select_aggregation_transient (plugin->cls, plugin->select_aggregation_transient (plugin->cls,
&wire_target_h_payto, &wire_target_h_payto,
&deposit.merchant_pub,
"x-bank", "x-bank",
&wtid2, &wtid2,
&total2)); &total2));
@ -2253,6 +2253,7 @@ run (void *cls)
FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS != FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
plugin->select_aggregation_transient (plugin->cls, plugin->select_aggregation_transient (plugin->cls,
&wire_target_h_payto, &wire_target_h_payto,
&deposit.merchant_pub,
"x-bank", "x-bank",
&wtid2, &wtid2,
&total2)); &total2));

View File

@ -304,10 +304,16 @@ struct TALER_EXCHANGE_Keys
struct GNUNET_TIME_Relative reserve_closing_delay; struct GNUNET_TIME_Relative reserve_closing_delay;
/** /**
* Maximum amount a wallet is allowed to hold from * Array of amounts a wallet is allowed to hold from
* this exchange before it must undergo a KYC check. * this exchange before it must undergo further KYC checks.
*/ */
struct TALER_Amount wallet_balance_limit_without_kyc; struct TALER_Amount *wallet_balance_limit_without_kyc;
/**
* Length of the @e wallet_balance_limit_without_kyc
* array.
*/
unsigned int wblwk_length;
/** /**
* Timestamp indicating the /keys generation. * Timestamp indicating the /keys generation.
@ -3422,7 +3428,7 @@ typedef void
* of a merchant. * of a merchant.
* *
* @param eh exchange handle to use * @param eh exchange handle to use
* @param payment_target number identifying the target * @param legitimization_uuid number identifying the legitimization process
* @param h_payto hash of the payto:// URI at @a payment_target * @param h_payto hash of the payto:// URI at @a payment_target
* @param timeout how long to wait for a positive KYC status * @param timeout how long to wait for a positive KYC status
* @param cb function to call with the result * @param cb function to call with the result
@ -3431,7 +3437,7 @@ typedef void
*/ */
struct TALER_EXCHANGE_KycCheckHandle * struct TALER_EXCHANGE_KycCheckHandle *
TALER_EXCHANGE_kyc_check (struct TALER_EXCHANGE_Handle *eh, TALER_EXCHANGE_kyc_check (struct TALER_EXCHANGE_Handle *eh,
uint64_t payment_target, uint64_t legitimization_uuid,
const struct TALER_PaytoHashP *h_payto, const struct TALER_PaytoHashP *h_payto,
struct GNUNET_TIME_Relative timeout, struct GNUNET_TIME_Relative timeout,
TALER_EXCHANGE_KycStatusCallback cb, TALER_EXCHANGE_KycStatusCallback cb,
@ -3500,8 +3506,10 @@ struct TALER_EXCHANGE_KycProofHandle;
* *
* @param eh exchange handle to use * @param eh exchange handle to use
* @param h_payto hash of payto URI identifying the target account * @param h_payto hash of payto URI identifying the target account
* @param code OAuth 2.0 code argument * @param logic name of the KYC logic to run
* @param state OAuth 2.0 state argument * @param args additional args to pass, can be NULL
* or a string to append to the URL. Must
* then begin with '/' or '?'.
* @param cb function to call with the result * @param cb function to call with the result
* @param cb_cls closure for @a cb * @param cb_cls closure for @a cb
* @return NULL on error * @return NULL on error
@ -3509,8 +3517,8 @@ struct TALER_EXCHANGE_KycProofHandle;
struct TALER_EXCHANGE_KycProofHandle * struct TALER_EXCHANGE_KycProofHandle *
TALER_EXCHANGE_kyc_proof (struct TALER_EXCHANGE_Handle *eh, TALER_EXCHANGE_kyc_proof (struct TALER_EXCHANGE_Handle *eh,
const struct TALER_PaytoHashP *h_payto, const struct TALER_PaytoHashP *h_payto,
const char *code, const char *logic,
const char *state, const char *args,
TALER_EXCHANGE_KycProofCallback cb, TALER_EXCHANGE_KycProofCallback cb,
void *cb_cls); void *cb_cls);
@ -3573,6 +3581,7 @@ typedef void
* *
* @param eh exchange handle to use * @param eh exchange handle to use
* @param reserve_priv wallet private key to check * @param reserve_priv wallet private key to check
* @param balance balance (or balance threshold) crossed by the wallet
* @param cb function to call with the result * @param cb function to call with the result
* @param cb_cls closure for @a cb * @param cb_cls closure for @a cb
* @return NULL on error * @return NULL on error
@ -3580,6 +3589,7 @@ typedef void
struct TALER_EXCHANGE_KycWalletHandle * struct TALER_EXCHANGE_KycWalletHandle *
TALER_EXCHANGE_kyc_wallet (struct TALER_EXCHANGE_Handle *eh, TALER_EXCHANGE_kyc_wallet (struct TALER_EXCHANGE_Handle *eh,
const struct TALER_ReservePrivateKeyP *reserve_priv, const struct TALER_ReservePrivateKeyP *reserve_priv,
const struct TALER_Amount *balance,
TALER_EXCHANGE_KycWalletCallback cb, TALER_EXCHANGE_KycWalletCallback cb,
void *cb_cls); void *cb_cls);

View File

@ -2327,11 +2327,13 @@ struct TALER_EXCHANGEDB_KycStatus
* Number that identifies the KYC target the operation * Number that identifies the KYC target the operation
* was about. * was about.
*/ */
// FIXME: rename to 'legitimization_uuid'
uint64_t payment_target_uuid; uint64_t payment_target_uuid;
/** /**
* What kind of KYC operation is this? * What kind of KYC operation is this?
*/ */
// FIXME: kill!
enum TALER_EXCHANGEDB_KycType type; enum TALER_EXCHANGEDB_KycType type;
/** /**
@ -2582,6 +2584,26 @@ typedef enum GNUNET_GenericReturnValue
const struct TALER_Amount *amount); const struct TALER_Amount *amount);
/**
* Function called on transient aggregations matching
* a particular hash of a payto URI.
*
* @param cls
* @param payto_uri corresponding payto URI
* @param wtid wire transfer identifier of transient aggregation
* @param merchant_pub public key of the merchant
* @param total amount aggregated so far
* @return true to continue iterating
*/
typedef bool
(*TALER_EXCHANGEDB_TransientAggregationCallback)(
void *cls,
const char *payto_uri,
const struct TALER_WireTransferIdentifierRawP *wtid,
const struct TALER_MerchantPublicKeyP *merchant_pub,
const struct TALER_Amount *total);
/** /**
* Callback with data about a prepared wire transfer. * Callback with data about a prepared wire transfer.
* *
@ -3097,6 +3119,21 @@ struct TALER_EXCHANGEDB_Plugin
struct TALER_EXCHANGEDB_KycStatus *kyc); struct TALER_EXCHANGEDB_KycStatus *kyc);
/**
* Get the origin of funds of a reserve.
*
* @param cls the `struct PostgresClosure` with the plugin-specific state
* @param reserve_pub public key of the reserve
* @param[out] h_payto set to hash of the wire source payto://-URI
* @return transaction status
*/
enum GNUNET_DB_QueryStatus
(*reserves_get_origin)(
void *cls,
const struct TALER_ReservePublicKeyP *reserve_pub,
struct TALER_PaytoHashP *h_payto);
/** /**
* Set the KYC status to "OK" for a bank account. * Set the KYC status to "OK" for a bank account.
* *
@ -3111,6 +3148,20 @@ struct TALER_EXCHANGEDB_Plugin
const char *id); const char *id);
/**
* Extract next KYC alert. Deletes the alert.
*
* @param cls the @e cls of this struct with the plugin-specific state
* @param trigger_type which type of alert to drain
* @param[out] h_payto set to hash of payto-URI where KYC status changed
* @return transaction status
*/
enum GNUNET_DB_QueryStatus
(*drain_kyc_alert)(void *cls,
uint32_t trigger_type,
struct TALER_PaytoHashP *h_payto);
/** /**
* Get the @a kyc status and @a h_payto by UUID. * Get the @a kyc status and @a h_payto by UUID.
* *
@ -3207,7 +3258,6 @@ struct TALER_EXCHANGEDB_Plugin
* @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] kyc set to the KYC status of the reserve
* @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
*/ */
@ -3220,7 +3270,6 @@ struct TALER_EXCHANGEDB_Plugin
bool *found, bool *found,
bool *balance_ok, bool *balance_ok,
bool *nonce_ok, bool *nonce_ok,
struct TALER_EXCHANGEDB_KycStatus *kyc_ok,
uint64_t *ruuid); uint64_t *ruuid);
@ -3235,7 +3284,6 @@ struct TALER_EXCHANGEDB_Plugin
* @param amount total amount to withdraw * @param amount total amount to withdraw
* @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] kyc set to the KYC status of the reserve
* @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
*/ */
@ -3247,7 +3295,6 @@ struct TALER_EXCHANGEDB_Plugin
const struct TALER_Amount *amount, const struct TALER_Amount *amount,
bool *found, bool *found,
bool *balance_ok, bool *balance_ok,
struct TALER_EXCHANGEDB_KycStatus *kyc_ok,
uint64_t *ruuid); uint64_t *ruuid);
@ -3692,8 +3739,6 @@ struct TALER_EXCHANGEDB_Plugin
* @param cls the @e cls of this struct with the plugin-specific state * @param cls the @e cls of this struct with the plugin-specific state
* @param start_shard_row minimum shard row to select * @param start_shard_row minimum shard row to select
* @param end_shard_row maximum shard row to select (inclusive) * @param end_shard_row maximum shard row to select (inclusive)
* @param kyc_off true if we should not check the KYC status because
* this exchange does not need/support KYC checks.
* @param[out] merchant_pub set to the public key of a merchant with a ready deposit * @param[out] merchant_pub set to the public key of a merchant with a ready deposit
* @param[out] payto_uri set to the account of the merchant, to be freed by caller * @param[out] payto_uri set to the account of the merchant, to be freed by caller
* @return transaction status code * @return transaction status code
@ -3702,7 +3747,6 @@ struct TALER_EXCHANGEDB_Plugin
(*get_ready_deposit)(void *cls, (*get_ready_deposit)(void *cls,
uint64_t start_shard_row, uint64_t start_shard_row,
uint64_t end_shard_row, uint64_t end_shard_row,
bool kyc_off,
struct TALER_MerchantPublicKeyP *merchant_pub, struct TALER_MerchantPublicKeyP *merchant_pub,
char **payto_uri); char **payto_uri);
@ -3740,6 +3784,7 @@ struct TALER_EXCHANGEDB_Plugin
* @param cls the @e cls of this struct with the plugin-specific state * @param cls the @e cls of this struct with the plugin-specific state
* @param h_payto destination of the wire transfer * @param h_payto destination of the wire transfer
* @param exchange_account_section exchange account to use * @param exchange_account_section exchange account to use
* @param merchant_pub public key of the merchant
* @param wtid the raw wire transfer identifier to be used * @param wtid the raw wire transfer identifier to be used
* @param total amount to be wired in the future * @param total amount to be wired in the future
* @return transaction status * @return transaction status
@ -3749,15 +3794,17 @@ struct TALER_EXCHANGEDB_Plugin
void *cls, void *cls,
const struct TALER_PaytoHashP *h_payto, const struct TALER_PaytoHashP *h_payto,
const char *exchange_account_section, const char *exchange_account_section,
const struct TALER_MerchantPublicKeyP *merchant_pub,
const struct TALER_WireTransferIdentifierRawP *wtid, const struct TALER_WireTransferIdentifierRawP *wtid,
const struct TALER_Amount *total); const struct TALER_Amount *total);
/** /**
* Find existing entry in the transient aggregation table. * Select existing entry in the transient aggregation table.
* *
* @param cls the @e cls of this struct with the plugin-specific state * @param cls the @e cls of this struct with the plugin-specific state
* @param h_payto destination of the wire transfer * @param h_payto destination of the wire transfer
* @param merchant_pub public key of the merchant
* @param exchange_account_section exchange account to use * @param exchange_account_section exchange account to use
* @param[out] wtid set to the raw wire transfer identifier to be used * @param[out] wtid set to the raw wire transfer identifier to be used
* @param[out] total existing amount to be wired in the future * @param[out] total existing amount to be wired in the future
@ -3767,11 +3814,29 @@ struct TALER_EXCHANGEDB_Plugin
(*select_aggregation_transient)( (*select_aggregation_transient)(
void *cls, void *cls,
const struct TALER_PaytoHashP *h_payto, const struct TALER_PaytoHashP *h_payto,
const struct TALER_MerchantPublicKeyP *merchant_pub,
const char *exchange_account_section, const char *exchange_account_section,
struct TALER_WireTransferIdentifierRawP *wtid, struct TALER_WireTransferIdentifierRawP *wtid,
struct TALER_Amount *total); struct TALER_Amount *total);
/**
* Find existing entry in the transient aggregation table.
*
* @param cls the @e cls of this struct with the plugin-specific state
* @param h_payto destination of the wire transfer
* @param cb function to call on each matching entry
* @param cb_cls closure for @a cb
* @return transaction status
*/
enum GNUNET_DB_QueryStatus
(*find_aggregation_transient)(
void *cls,
const struct TALER_PaytoHashP *h_payto,
TALER_EXCHANGEDB_TransientAggregationCallback cb,
void *cb_cls);
/** /**
* Update existing entry in the transient aggregation table. * Update existing entry in the transient aggregation table.
* @a h_payto is only needed for query performance. * @a h_payto is only needed for query performance.

View File

@ -162,6 +162,19 @@ typedef void
void *cb_cls); void *cb_cls);
/**
* Function called to iterate over KYC-relevant
* transaction thresholds amounts.
*
* @param cls closure, identifies the event type and
* account to iterate over events for
* @param threshold a relevant threshold amount
*/
typedef void
(*TALER_KYCLOGIC_KycThresholdIterator)(void *cls,
const struct TALER_Amount *threshold);
/** /**
* Call us on KYC processes satisfied for the given * Call us on KYC processes satisfied for the given
* account. Must match the ``select_satisfied_kyc_processes`` of the exchange database plugin. * account. Must match the ``select_satisfied_kyc_processes`` of the exchange database plugin.
@ -209,6 +222,21 @@ TALER_KYCLOGIC_kyc_test_required (enum TALER_KYCLOGIC_KycTriggerEvent event,
void *ai_cls); void *ai_cls);
/**
* Iterate over all thresholds that are applicable
* to a particular type of @a event
*
* @param event tresholds to look up
* @param it function to call on each
* @param it_cls closure for @a it
*/
void
TALER_KYCLOGIC_kyc_iterate_thresholds (
enum TALER_KYCLOGIC_KycTriggerEvent event,
TALER_KYCLOGIC_KycThresholdIterator it,
void *it_cls);
/** /**
* Obtain the provider logic for a given @a provider_section_name. * Obtain the provider logic for a given @a provider_section_name.
* *

View File

@ -295,6 +295,7 @@ struct TALER_KYCLOGIC_Plugin
* @param url_path rest of the URL after `/kyc-webhook/$H_PAYTO/$LOGIC` * @param url_path rest of the URL after `/kyc-webhook/$H_PAYTO/$LOGIC`
* @param connection MHD connection object (for HTTP headers) * @param connection MHD connection object (for HTTP headers)
* @param account_id which account to trigger process for * @param account_id which account to trigger process for
* @param legi_row row in the table the legitimization is for
* @param provider_user_id user ID (or NULL) the proof is for * @param provider_user_id user ID (or NULL) the proof is for
* @param provider_legitimization_id legitimization ID the proof is for * @param provider_legitimization_id legitimization ID the proof is for
* @param cb function to call with the result * @param cb function to call with the result
@ -307,6 +308,7 @@ struct TALER_KYCLOGIC_Plugin
const char *const url_path[], const char *const url_path[],
struct MHD_Connection *connection, struct MHD_Connection *connection,
const struct TALER_PaytoHashP *account_id, const struct TALER_PaytoHashP *account_id,
uint64_t legi_row,
const char *provider_user_id, const char *provider_user_id,
const char *provider_legitimization_id, const char *provider_legitimization_id,
TALER_KYCLOGIC_ProofCallback cb, TALER_KYCLOGIC_ProofCallback cb,

View File

@ -2421,12 +2421,14 @@ TALER_TESTING_cmd_revoke_sign_key (
* *
* @param label command label. * @param label command label.
* @param reserve_reference command with reserve private key to use (or NULL to create a fresh reserve key). * @param reserve_reference command with reserve private key to use (or NULL to create a fresh reserve key).
* @param threshold_balance balance amount to pass to the exchange
* @param expected_response_code expected HTTP status * @param expected_response_code expected HTTP status
* @return the command * @return the command
*/ */
struct TALER_TESTING_Command struct TALER_TESTING_Command
TALER_TESTING_cmd_wallet_kyc_get (const char *label, TALER_TESTING_cmd_wallet_kyc_get (const char *label,
const char *reserve_reference, const char *reserve_reference,
const char *threshold_balance,
unsigned int expected_response_code); unsigned int expected_response_code);
@ -2445,21 +2447,26 @@ TALER_TESTING_cmd_check_kyc_get (const char *label,
/** /**
* Create a KYC proof request. * Create a KYC proof request. Only useful in conjunction with the OAuth2.0
* logic, as it generates an OAuth2.0-specific request.
* *
* @param label command label. * @param label command label.
* @param payment_target_reference command with a payment target to query * @param payment_target_reference command with a payment target to query
* @param logic_section name of the KYC provider section
* in the exchange configuration for this proof
* @param code OAuth 2.0 code to use * @param code OAuth 2.0 code to use
* @param state OAuth 2.0 state to use * @param state OAuth 2.0 state to use
* @param expected_response_code expected HTTP status * @param expected_response_code expected HTTP status
* @return the command * @return the command
*/ */
struct TALER_TESTING_Command struct TALER_TESTING_Command
TALER_TESTING_cmd_proof_kyc (const char *label, TALER_TESTING_cmd_proof_kyc_oauth2 (
const char *payment_target_reference, const char *label,
const char *code, const char *payment_target_reference,
const char *state, const char *logic_section,
unsigned int expected_response_code); const char *code,
const char *state,
unsigned int expected_response_code);
/** /**

View File

@ -361,19 +361,6 @@ char *
TALER_payto_get_method (const char *payto_uri); TALER_payto_get_method (const char *payto_uri);
/**
* Construct a payto://-URI from a Taler @a reserve_pub at
* @a exchange_base_url
*
* @param exchange_base_url the URL of the exchange
* @param reserve_pub public key of the reserve
* @return payto:// URI encoding the reserve's address
*/
char *
TALER_payto_from_reserve (const char *exchange_base_url,
const struct TALER_ReservePublicKeyP *reserve_pub);
/** /**
* Obtain the account name from a payto URL. * Obtain the account name from a payto URL.
* *

View File

@ -279,12 +279,22 @@ load_logic (const struct GNUNET_CONFIGURATION_Handle *cfg,
GNUNET_asprintf (&lib_name, GNUNET_asprintf (&lib_name,
"libtaler_plugin_kyclogic_%s", "libtaler_plugin_kyclogic_%s",
name); name);
for (unsigned int i = 0; i<num_kyc_logics; i++)
if (0 == strcmp (lib_name,
kyc_logics[i]->library_name))
{
GNUNET_free (lib_name);
return kyc_logics[i];
}
plugin = GNUNET_PLUGIN_load (lib_name, plugin = GNUNET_PLUGIN_load (lib_name,
(void *) cfg); (void *) cfg);
if (NULL != plugin) if (NULL != plugin)
plugin->library_name = lib_name; plugin->library_name = lib_name;
else else
GNUNET_free (lib_name); GNUNET_free (lib_name);
GNUNET_array_append (kyc_logics,
num_kyc_logics,
plugin);
return plugin; return plugin;
} }
@ -471,6 +481,14 @@ add_provider (const struct GNUNET_CONFIGURATION_Handle *cfg,
} }
/**
* Parse configuration @a cfg in section @a section for
* the specification of a KYC trigger.
*
* @param cfg configuration to parse
* @param section configuration section to parse
* @return #GNUNET_OK on success
*/
static enum GNUNET_GenericReturnValue static enum GNUNET_GenericReturnValue
add_trigger (const struct GNUNET_CONFIGURATION_Handle *cfg, add_trigger (const struct GNUNET_CONFIGURATION_Handle *cfg,
const char *section) const char *section)
@ -797,6 +815,9 @@ eval_trigger (void *cls,
struct GNUNET_TIME_Relative duration; struct GNUNET_TIME_Relative duration;
bool bump = true; 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); duration = GNUNET_TIME_absolute_get_duration (date);
if (ttc->have_total) if (ttc->have_total)
{ {
@ -812,19 +833,31 @@ eval_trigger (void *cls,
else else
{ {
ttc->total = *amount; 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++) for (unsigned int i = ttc->start; i<num_kyc_triggers; i++)
{ {
const struct TALER_KYCLOGIC_KycTrigger *kt = kyc_triggers[i]; const struct TALER_KYCLOGIC_KycTrigger *kt = kyc_triggers[i];
if (ttc->event != kt->trigger) if (ttc->event != kt->trigger)
{
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"KYC check #%u: trigger type does not match\n",
i);
continue; continue;
}
duration = GNUNET_TIME_relative_max (duration, duration = GNUNET_TIME_relative_max (duration,
kt->timeframe); kt->timeframe);
if (GNUNET_TIME_relative_cmp (kt->timeframe, if (GNUNET_TIME_relative_cmp (kt->timeframe,
>, >,
duration)) duration))
{ {
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"KYC check #%u: amount is beyond time limit\n",
i);
if (bump) if (bump)
ttc->start = i; ttc->start = i;
return GNUNET_OK; return GNUNET_OK;
@ -833,6 +866,9 @@ eval_trigger (void *cls,
TALER_amount_cmp (&ttc->total, TALER_amount_cmp (&ttc->total,
&kt->threshold)) &kt->threshold))
{ {
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"KYC check #%u: amount is below treshold\n",
i);
if (bump) if (bump)
ttc->start = i; ttc->start = i;
bump = false; bump = false;
@ -848,6 +884,9 @@ eval_trigger (void *cls,
for (unsigned int k = 0; k<*ttc->needed_cnt; k++) for (unsigned int k = 0; k<*ttc->needed_cnt; k++)
if (ttc->needed[k] == rc) if (ttc->needed[k] == rc)
{ {
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"KYC rule #%u already listed\n",
j);
found = true; found = true;
break; break;
} }
@ -857,6 +896,11 @@ eval_trigger (void *cls,
(*ttc->needed_cnt)++; (*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) if (bump)
return GNUNET_NO; /* we hit all possible triggers! */ return GNUNET_NO; /* we hit all possible triggers! */
@ -903,13 +947,22 @@ remove_satisfied (void *cls,
if (0 != strcasecmp (provider_name, if (0 != strcasecmp (provider_name,
kp->provider_section_name)) kp->provider_section_name))
continue; continue;
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Provider `%s' satisfied\n",
provider_name);
for (unsigned int j = 0; j<kp->num_checks; j++) for (unsigned int j = 0; j<kp->num_checks; j++)
{ {
const struct TALER_KYCLOGIC_KycCheck *kc = kp->provided_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);
for (unsigned int k = 0; k<*rc->needed_cnt; k++) for (unsigned int k = 0; k<*rc->needed_cnt; k++)
if (kc == rc->needed[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[k] = rc->needed[*rc->needed_cnt - 1];
(*rc->needed_cnt)--; (*rc->needed_cnt)--;
if (0 == *rc->needed_cnt) if (0 == *rc->needed_cnt)
@ -973,15 +1026,14 @@ TALER_KYCLOGIC_kyc_test_required (enum TALER_KYCLOGIC_KycTriggerEvent event,
/* Check what provider checks are already satisfied for h_payto (with /* Check what provider checks are already satisfied for h_payto (with
database), remove those from the 'needed' array. */ database), remove those from the 'needed' array. */
GNUNET_break (0);
// FIXME: do via callback!
qs = ki (ki_cls, qs = ki (ki_cls,
h_payto, h_payto,
& &remove_satisfied,
remove_satisfied,
&rc); &rc);
GNUNET_break (qs >= 0); // FIXME: handle DB failure more nicely? GNUNET_break (qs >= 0); // FIXME: handle DB failure more nicely?
} }
if (0 == needed_cnt)
return NULL;
/* Count maximum number of remaining checks covered by any /* Count maximum number of remaining checks covered by any
provider */ provider */
@ -1059,4 +1111,22 @@ TALER_KYCLOGIC_kyc_get_logic (const char *provider_section_name,
} }
void
TALER_KYCLOGIC_kyc_iterate_thresholds (
enum TALER_KYCLOGIC_KycTriggerEvent event,
TALER_KYCLOGIC_KycThresholdIterator it,
void *it_cls)
{
for (unsigned int i = 0; i<num_kyc_triggers; i++)
{
const struct TALER_KYCLOGIC_KycTrigger *kt = kyc_triggers[i];
if (event != kt->trigger)
continue;
it (it_cls,
&kt->threshold);
}
}
/* end of taler-exchange-httpd_kyc.c */ /* end of taler-exchange-httpd_kyc.c */

View File

@ -68,6 +68,11 @@ struct TALER_KYCLOGIC_ProviderDetails
*/ */
struct PluginState *ps; struct PluginState *ps;
/**
* Configuration section that configured us.
*/
char *section;
/** /**
* URL of the OAuth2.0 endpoint for KYC checks. * URL of the OAuth2.0 endpoint for KYC checks.
* (token/auth) * (token/auth)
@ -265,6 +270,7 @@ struct TALER_KYCLOGIC_WebhookHandle
static void static void
oauth2_unload_configuration (struct TALER_KYCLOGIC_ProviderDetails *pd) oauth2_unload_configuration (struct TALER_KYCLOGIC_ProviderDetails *pd)
{ {
GNUNET_free (pd->section);
GNUNET_free (pd->auth_url); GNUNET_free (pd->auth_url);
GNUNET_free (pd->login_url); GNUNET_free (pd->login_url);
GNUNET_free (pd->info_url); GNUNET_free (pd->info_url);
@ -292,6 +298,7 @@ oauth2_load_configuration (void *cls,
pd = GNUNET_new (struct TALER_KYCLOGIC_ProviderDetails); pd = GNUNET_new (struct TALER_KYCLOGIC_ProviderDetails);
pd->ps = ps; pd->ps = ps;
pd->section = GNUNET_strdup (provider_section_name);
if (GNUNET_OK != if (GNUNET_OK !=
GNUNET_CONFIGURATION_get_value_time (ps->cfg, GNUNET_CONFIGURATION_get_value_time (ps->cfg,
provider_section_name, provider_section_name,
@ -467,9 +474,10 @@ initiate_task (void *cls)
hps = GNUNET_STRINGS_data_to_string_alloc (&ih->h_payto, hps = GNUNET_STRINGS_data_to_string_alloc (&ih->h_payto,
sizeof (ih->h_payto)); sizeof (ih->h_payto));
GNUNET_asprintf (&redirect_uri, GNUNET_asprintf (&redirect_uri,
"%s/kyc-proof/%s/oauth2/%s", "%s/kyc-proof/%s/%s/%s",
ps->exchange_base_url, ps->exchange_base_url,
hps, hps,
pd->section,
legi_s); legi_s);
redirect_uri_encoded = TALER_urlencode (redirect_uri); redirect_uri_encoded = TALER_urlencode (redirect_uri);
GNUNET_free (redirect_uri); GNUNET_free (redirect_uri);
@ -532,7 +540,11 @@ oauth2_initiate (void *cls,
static void static void
oauth2_initiate_cancel (struct TALER_KYCLOGIC_InitiateHandle *ih) oauth2_initiate_cancel (struct TALER_KYCLOGIC_InitiateHandle *ih)
{ {
GNUNET_SCHEDULER_cancel (ih->task); if (NULL != ih->task)
{
GNUNET_SCHEDULER_cancel (ih->task);
ih->task = NULL;
}
GNUNET_free (ih); GNUNET_free (ih);
} }
@ -659,6 +671,9 @@ parse_proof_success_reply (struct TALER_KYCLOGIC_ProofHandle *ph,
if (GNUNET_OK != res) if (GNUNET_OK != res)
{ {
GNUNET_break_op (0); GNUNET_break_op (0);
json_dumpf (j,
stderr,
JSON_INDENT (2));
ph->status = TALER_KYCLOGIC_STATUS_PROVIDER_FAILED; ph->status = TALER_KYCLOGIC_STATUS_PROVIDER_FAILED;
ph->response ph->response
= TALER_MHD_make_error ( = TALER_MHD_make_error (
@ -691,6 +706,9 @@ parse_proof_success_reply (struct TALER_KYCLOGIC_ProofHandle *ph,
if (GNUNET_OK != res) if (GNUNET_OK != res)
{ {
GNUNET_break_op (0); GNUNET_break_op (0);
json_dumpf (data,
stderr,
JSON_INDENT (2));
ph->status = TALER_KYCLOGIC_STATUS_PROVIDER_FAILED; ph->status = TALER_KYCLOGIC_STATUS_PROVIDER_FAILED;
ph->response ph->response
= TALER_MHD_make_error ( = TALER_MHD_make_error (
@ -741,6 +759,9 @@ handle_curl_proof_finished (void *cls,
j); j);
break; break;
default: default:
GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
"OAuth2.0 info URL returned HTTP status %u\n",
(unsigned int) response_code);
handle_proof_error (ph, handle_proof_error (ph,
j); j);
break; break;
@ -750,6 +771,147 @@ handle_curl_proof_finished (void *cls,
} }
/**
* After we are done with the CURL interaction we
* need to fetch the user's account details.
*
* @param cls our `struct KycProofContext`
* @param response_code HTTP response code from server, 0 on hard error
* @param response in JSON, NULL if response was not in JSON format
*/
static void
handle_curl_login_finished (void *cls,
long response_code,
const void *response)
{
struct TALER_KYCLOGIC_ProofHandle *ph = cls;
const json_t *j = response;
ph->job = NULL;
switch (response_code)
{
case MHD_HTTP_OK:
{
const char *access_token;
const char *token_type;
uint64_t expires_in_s;
const char *refresh_token;
struct GNUNET_JSON_Specification spec[] = {
GNUNET_JSON_spec_string ("access_token",
&access_token),
GNUNET_JSON_spec_string ("token_type",
&token_type),
GNUNET_JSON_spec_uint64 ("expires_in",
&expires_in_s),
GNUNET_JSON_spec_string ("refresh_token",
&refresh_token),
GNUNET_JSON_spec_end ()
};
CURL *eh;
{
enum GNUNET_GenericReturnValue res;
const char *emsg;
unsigned int line;
res = GNUNET_JSON_parse (j,
spec,
&emsg,
&line);
if (GNUNET_OK != res)
{
GNUNET_break_op (0);
ph->response
= TALER_MHD_make_error (
TALER_EC_EXCHANGE_KYC_PROOF_BACKEND_INVALID_RESPONSE,
"Unexpected response from KYC gateway");
ph->http_status
= MHD_HTTP_BAD_GATEWAY;
break;
}
}
if (0 != strcasecmp (token_type,
"bearer"))
{
GNUNET_break_op (0);
ph->response
= TALER_MHD_make_error (
TALER_EC_EXCHANGE_KYC_PROOF_BACKEND_INVALID_RESPONSE,
"Unexpected token type in response from KYC gateway");
ph->http_status
= MHD_HTTP_BAD_GATEWAY;
break;
}
/* We guard against a few characters that could
conceivably be abused to mess with the HTTP header */
if ( (NULL != strchr (access_token,
'\n')) ||
(NULL != strchr (access_token,
'\r')) ||
(NULL != strchr (access_token,
' ')) ||
(NULL != strchr (access_token,
';')) )
{
GNUNET_break_op (0);
ph->response
= TALER_MHD_make_error (
TALER_EC_EXCHANGE_KYC_PROOF_BACKEND_INVALID_RESPONSE,
"Illegal character in access token");
ph->http_status
= MHD_HTTP_BAD_GATEWAY;
break;
}
eh = curl_easy_init ();
if (NULL == eh)
{
GNUNET_break_op (0);
ph->response
= TALER_MHD_make_error (
TALER_EC_GENERIC_ALLOCATION_FAILURE,
"curl_easy_init");
ph->http_status
= MHD_HTTP_INTERNAL_SERVER_ERROR;
break;
}
GNUNET_assert (CURLE_OK ==
curl_easy_setopt (eh,
CURLOPT_URL,
ph->pd->info_url));
{
char *hdr;
struct curl_slist *slist;
GNUNET_asprintf (&hdr,
"%s: Bearer %s",
MHD_HTTP_HEADER_AUTHORIZATION,
access_token);
slist = curl_slist_append (NULL,
hdr);
ph->job = GNUNET_CURL_job_add2 (ph->pd->ps->curl_ctx,
eh,
slist,
&handle_curl_proof_finished,
ph);
curl_slist_free_all (slist);
GNUNET_free (hdr);
}
return;
}
default:
GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
"OAuth2.0 login URL returned HTTP status %u\n",
(unsigned int) response_code);
handle_proof_error (ph,
j);
break;
}
return_proof_response (ph);
}
/** /**
* Check KYC status and return status to human. * Check KYC status and return status to human.
* *
@ -758,6 +920,7 @@ handle_curl_proof_finished (void *cls,
* @param url_path rest of the URL after `/kyc-webhook/` * @param url_path rest of the URL after `/kyc-webhook/`
* @param connection MHD connection object (for HTTP headers) * @param connection MHD connection object (for HTTP headers)
* @param account_id which account to trigger process for * @param account_id which account to trigger process for
* @param legi_row row in the table the legitimization is for
* @param provider_user_id user ID (or NULL) the proof is for * @param provider_user_id user ID (or NULL) the proof is for
* @param provider_legitimization_id legitimization ID the proof is for * @param provider_legitimization_id legitimization ID the proof is for
* @param cb function to call with the result * @param cb function to call with the result
@ -770,6 +933,7 @@ oauth2_proof (void *cls,
const char *const url_path[], const char *const url_path[],
struct MHD_Connection *connection, struct MHD_Connection *connection,
const struct TALER_PaytoHashP *account_id, const struct TALER_PaytoHashP *account_id,
uint64_t legi_row,
const char *provider_user_id, const char *provider_user_id,
const char *provider_legitimization_id, const char *provider_legitimization_id,
TALER_KYCLOGIC_ProofCallback cb, TALER_KYCLOGIC_ProofCallback cb,
@ -779,16 +943,20 @@ oauth2_proof (void *cls,
struct TALER_KYCLOGIC_ProofHandle *ph; struct TALER_KYCLOGIC_ProofHandle *ph;
const char *code; const char *code;
if (strlen (provider_legitimization_id) >=
sizeof (ph->provider_legitimization_id))
{
GNUNET_break (0);
return NULL;
}
GNUNET_break (NULL == provider_user_id); GNUNET_break (NULL == provider_user_id);
ph = GNUNET_new (struct TALER_KYCLOGIC_ProofHandle); ph = GNUNET_new (struct TALER_KYCLOGIC_ProofHandle);
strcpy (ph->provider_legitimization_id, GNUNET_snprintf (ph->provider_legitimization_id,
provider_legitimization_id); sizeof (ph->provider_legitimization_id),
"%llu",
(unsigned long long) legi_row);
if ( (NULL != provider_legitimization_id) &&
(0 != strcmp (provider_legitimization_id,
ph->provider_legitimization_id)))
{
GNUNET_break (0);
GNUNET_free (ph);
return NULL;
}
ph->pd = pd; ph->pd = pd;
ph->connection = connection; ph->connection = connection;
ph->h_payto = *account_id; ph->h_payto = *account_id;
@ -891,7 +1059,7 @@ oauth2_proof (void *cls,
ph->job = GNUNET_CURL_job_add (ps->curl_ctx, ph->job = GNUNET_CURL_job_add (ps->curl_ctx,
ph->eh, ph->eh,
&handle_curl_proof_finished, &handle_curl_login_finished,
ph); ph);
return ph; return ph;
} }

View File

@ -253,6 +253,7 @@ template_initiate_cancel (struct TALER_KYCLOGIC_InitiateHandle *ih)
* @param url_path rest of the URL after `/kyc-webhook/` * @param url_path rest of the URL after `/kyc-webhook/`
* @param connection MHD connection object (for HTTP headers) * @param connection MHD connection object (for HTTP headers)
* @param account_id which account to trigger process for * @param account_id which account to trigger process for
* @param legi_row row in the table the legitimization is for
* @param provider_user_id user ID (or NULL) the proof is for * @param provider_user_id user ID (or NULL) the proof is for
* @param provider_legitimization_id legitimization ID the proof is for * @param provider_legitimization_id legitimization ID the proof is for
* @param cb function to call with the result * @param cb function to call with the result
@ -265,6 +266,7 @@ template_proof (void *cls,
const char *const url_path[], const char *const url_path[],
struct MHD_Connection *connection, struct MHD_Connection *connection,
const struct TALER_PaytoHashP *account_id, const struct TALER_PaytoHashP *account_id,
uint64_t legi_row,
const char *provider_user_id, const char *provider_user_id,
const char *provider_legitimization_id, const char *provider_legitimization_id,
TALER_KYCLOGIC_ProofCallback cb, TALER_KYCLOGIC_ProofCallback cb,

View File

@ -754,6 +754,7 @@ handler_kyc_proof_get (
&args[2], &args[2],
rc->connection, rc->connection,
&h_payto, &h_payto,
kyc_row_id,
cmd_provider_user_id, cmd_provider_user_id,
cmd_provider_legitimization_id, cmd_provider_legitimization_id,
&proof_cb, &proof_cb,
@ -1456,7 +1457,7 @@ main (int argc,
"use the given provider user ID (overridden if -i is also used)", "use the given provider user ID (overridden if -i is also used)",
&cmd_provider_user_id), &cmd_provider_user_id),
GNUNET_GETOPT_option_string ( GNUNET_GETOPT_option_string (
'l', 'U',
"legitimization", "legitimization",
"ID", "ID",
"use the given provider legitimization ID (overridden if -i is also used)", "use the given provider legitimization ID (overridden if -i is also used)",
@ -1464,7 +1465,7 @@ main (int argc,
GNUNET_GETOPT_option_base32_fixed_size ( GNUNET_GETOPT_option_base32_fixed_size (
'p', 'p',
"payto-hash", "payto-hash",
"URI", "HASH",
"base32 encoding of the hash of a payto://-URI to use for the account (otherwise a random value will be used)", "base32 encoding of the hash of a payto://-URI to use for the account (otherwise a random value will be used)",
&cmd_line_h_payto, &cmd_line_h_payto,
sizeof (cmd_line_h_payto)), sizeof (cmd_line_h_payto)),

View File

@ -1458,50 +1458,6 @@ TALER_EXCHANGE_check_purse_create_conflict_ (
} }
static char *
make_payto (const char *exchange_url,
const struct TALER_ReservePublicKeyP *reserve_pub)
{
char pub_str[sizeof (*reserve_pub) * 2];
char *end;
bool is_http;
char *reserve_url;
end = GNUNET_STRINGS_data_to_string (
reserve_pub,
sizeof (*reserve_pub),
pub_str,
sizeof (pub_str));
*end = '\0';
if (0 == strncmp (exchange_url,
"http://",
strlen ("http://")))
{
is_http = true;
exchange_url = &exchange_url[strlen ("http://")];
}
else if (0 == strncmp (exchange_url,
"https://",
strlen ("https://")))
{
is_http = false;
exchange_url = &exchange_url[strlen ("https://")];
}
else
{
GNUNET_break (0);
return NULL;
}
/* exchange_url includes trailing '/' */
GNUNET_asprintf (&reserve_url,
"payto://%s/%s%s",
is_http ? "taler+http" : "taler",
exchange_url,
pub_str);
return reserve_url;
}
enum GNUNET_GenericReturnValue enum GNUNET_GenericReturnValue
TALER_EXCHANGE_check_purse_merge_conflict_ ( TALER_EXCHANGE_check_purse_merge_conflict_ (
const struct TALER_PurseMergeSignatureP *cmerge_sig, const struct TALER_PurseMergeSignatureP *cmerge_sig,
@ -1539,8 +1495,8 @@ TALER_EXCHANGE_check_purse_merge_conflict_ (
} }
if (NULL == partner_url) if (NULL == partner_url)
partner_url = exchange_url; partner_url = exchange_url;
payto_uri = make_payto (partner_url, payto_uri = TALER_reserve_make_payto (partner_url,
&reserve_pub); &reserve_pub);
if (GNUNET_OK != if (GNUNET_OK !=
TALER_wallet_purse_merge_verify ( TALER_wallet_purse_merge_verify (
payto_uri, payto_uri,

View File

@ -176,11 +176,14 @@ handle_deposit_wtid_finished (void *cls,
case MHD_HTTP_ACCEPTED: case MHD_HTTP_ACCEPTED:
{ {
/* Transaction known, but not executed yet */ /* Transaction known, but not executed yet */
bool no_legi = false;
struct GNUNET_JSON_Specification spec[] = { struct GNUNET_JSON_Specification spec[] = {
GNUNET_JSON_spec_timestamp ("execution_time", GNUNET_JSON_spec_timestamp ("execution_time",
&dr.details.accepted.execution_time), &dr.details.accepted.execution_time),
GNUNET_JSON_spec_uint64 ("payment_target_uuid", GNUNET_JSON_spec_mark_optional (
&dr.details.accepted.payment_target_uuid), GNUNET_JSON_spec_uint64 ("legitimization_uuid",
&dr.details.accepted.payment_target_uuid),
&no_legi),
GNUNET_JSON_spec_bool ("kyc_ok", GNUNET_JSON_spec_bool ("kyc_ok",
&dr.details.accepted.kyc_ok), &dr.details.accepted.kyc_ok),
GNUNET_JSON_spec_end () GNUNET_JSON_spec_end ()
@ -196,6 +199,8 @@ handle_deposit_wtid_finished (void *cls,
dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
break; break;
} }
if (no_legi)
dr.details.accepted.payment_target_uuid = 0;
dwh->cb (dwh->cb_cls, dwh->cb (dwh->cb_cls,
&dr); &dr);
TALER_EXCHANGE_deposits_get_cancel (dwh); TALER_EXCHANGE_deposits_get_cancel (dwh);

View File

@ -736,6 +736,7 @@ decode_keys_json (const json_t *resp_obj,
struct GNUNET_HashCode hash_xor = {0}; struct GNUNET_HashCode hash_xor = {0};
struct TALER_ExchangePublicKeyP pub; struct TALER_ExchangePublicKeyP pub;
const char *currency; const char *currency;
json_t *wblwk = NULL;
struct GNUNET_JSON_Specification mspec[] = { struct GNUNET_JSON_Specification mspec[] = {
GNUNET_JSON_spec_fixed_auto ("denominations_sig", GNUNET_JSON_spec_fixed_auto ("denominations_sig",
&denominations_sig), &denominations_sig),
@ -750,8 +751,8 @@ decode_keys_json (const json_t *resp_obj,
GNUNET_JSON_spec_string ("currency", GNUNET_JSON_spec_string ("currency",
&currency), &currency),
GNUNET_JSON_spec_mark_optional ( GNUNET_JSON_spec_mark_optional (
TALER_JSON_spec_amount_any ("wallet_balance_limit_without_kyc", GNUNET_JSON_spec_json ("wallet_balance_limit_without_kyc",
&key_data->wallet_balance_limit_without_kyc), &wblwk),
NULL), NULL),
GNUNET_JSON_spec_end () GNUNET_JSON_spec_end ()
}; };
@ -819,17 +820,6 @@ decode_keys_json (const json_t *resp_obj,
NULL, NULL)); NULL, NULL));
key_data->currency = GNUNET_strdup (currency); key_data->currency = GNUNET_strdup (currency);
if (GNUNET_OK ==
TALER_amount_is_valid (&key_data->wallet_balance_limit_without_kyc))
{
if (0 != strcasecmp (currency,
key_data->wallet_balance_limit_without_kyc.currency))
{
GNUNET_break_op (0);
return GNUNET_SYSERR;
}
}
/* parse the global fees */ /* parse the global fees */
{ {
json_t *global_fees; json_t *global_fees;
@ -882,6 +872,32 @@ decode_keys_json (const json_t *resp_obj,
} }
} }
/* Parse balance limits */
if (NULL != wblwk)
{
key_data->wblwk_length = json_array_size (wblwk);
key_data->wallet_balance_limit_without_kyc
= GNUNET_new_array (key_data->wblwk_length,
struct TALER_Amount);
for (unsigned int i = 0; i<key_data->wblwk_length; i++)
{
struct TALER_Amount *a = &key_data->wallet_balance_limit_without_kyc[i];
const json_t *aj = json_array_get (wblwk,
i);
struct GNUNET_JSON_Specification spec[] = {
TALER_JSON_spec_amount (NULL,
currency,
a),
GNUNET_JSON_spec_end ()
};
EXITIF (GNUNET_OK !=
GNUNET_JSON_parse (aj,
spec,
NULL, NULL));
}
}
/* Parse the supported extension(s): age-restriction. */ /* Parse the supported extension(s): age-restriction. */
/* TODO: maybe lift all this into a FP in TALER_Extension ? */ /* TODO: maybe lift all this into a FP in TALER_Extension ? */
{ {
@ -1210,6 +1226,7 @@ free_key_data (struct TALER_EXCHANGE_Keys *key_data)
GNUNET_array_grow (key_data->auditors, GNUNET_array_grow (key_data->auditors,
key_data->auditors_size, key_data->auditors_size,
0); 0);
GNUNET_free (key_data->wallet_balance_limit_without_kyc);
GNUNET_free (key_data->version); GNUNET_free (key_data->version);
GNUNET_free (key_data->currency); GNUNET_free (key_data->currency);
GNUNET_free (key_data->global_fees); GNUNET_free (key_data->global_fees);

View File

@ -207,7 +207,7 @@ handle_kyc_check_finished (void *cls,
struct TALER_EXCHANGE_KycCheckHandle * struct TALER_EXCHANGE_KycCheckHandle *
TALER_EXCHANGE_kyc_check (struct TALER_EXCHANGE_Handle *exchange, TALER_EXCHANGE_kyc_check (struct TALER_EXCHANGE_Handle *exchange,
uint64_t payment_target, uint64_t legitimization_uuid,
const struct TALER_PaytoHashP *h_payto, const struct TALER_PaytoHashP *h_payto,
struct GNUNET_TIME_Relative timeout, struct GNUNET_TIME_Relative timeout,
TALER_EXCHANGE_KycStatusCallback cb, TALER_EXCHANGE_KycStatusCallback cb,
@ -238,8 +238,8 @@ TALER_EXCHANGE_kyc_check (struct TALER_EXCHANGE_Handle *exchange,
timeout_ms = timeout.rel_value_us timeout_ms = timeout.rel_value_us
/ GNUNET_TIME_UNIT_MILLISECONDS.rel_value_us; / GNUNET_TIME_UNIT_MILLISECONDS.rel_value_us;
GNUNET_asprintf (&arg_str, GNUNET_asprintf (&arg_str,
"/kyc-check/%llu?h_payto=%s&timeout_ms=%llu", "/kyc-check/%llu/%s?timeout_ms=%llu",
(unsigned long long) payment_target, (unsigned long long) legitimization_uuid,
payto_str, payto_str,
timeout_ms); timeout_ms);
} }

View File

@ -1,6 +1,6 @@
/* /*
This file is part of TALER This file is part of TALER
Copyright (C) 2021 Taler Systems SA Copyright (C) 2021, 2022 Taler Systems SA
TALER is free software; you can redistribute it and/or modify it under the TALER is free software; you can redistribute it and/or modify it under the
terms of the GNU General Public License as published by the Free Software terms of the GNU General Public License as published by the Free Software
@ -142,8 +142,8 @@ handle_kyc_proof_finished (void *cls,
struct TALER_EXCHANGE_KycProofHandle * struct TALER_EXCHANGE_KycProofHandle *
TALER_EXCHANGE_kyc_proof (struct TALER_EXCHANGE_Handle *exchange, TALER_EXCHANGE_kyc_proof (struct TALER_EXCHANGE_Handle *exchange,
const struct TALER_PaytoHashP *h_payto, const struct TALER_PaytoHashP *h_payto,
const char *code, const char *logic,
const char *state, const char *args,
TALER_EXCHANGE_KycProofCallback cb, TALER_EXCHANGE_KycProofCallback cb,
void *cb_cls) void *cb_cls)
{ {
@ -151,13 +151,17 @@ TALER_EXCHANGE_kyc_proof (struct TALER_EXCHANGE_Handle *exchange,
struct GNUNET_CURL_Context *ctx; struct GNUNET_CURL_Context *ctx;
char *arg_str; char *arg_str;
if (NULL == args)
args = "";
else
GNUNET_assert ( (args[0] == '?') ||
(args[0] == '/') );
if (GNUNET_YES != if (GNUNET_YES !=
TEAH_handle_is_ready (exchange)) TEAH_handle_is_ready (exchange))
{ {
GNUNET_break (0); GNUNET_break (0);
return NULL; return NULL;
} }
/* TODO: any escaping of code/state needed??? */
{ {
char hstr[sizeof (struct TALER_PaytoHashP) * 2]; char hstr[sizeof (struct TALER_PaytoHashP) * 2];
char *end; char *end;
@ -168,10 +172,10 @@ TALER_EXCHANGE_kyc_proof (struct TALER_EXCHANGE_Handle *exchange,
sizeof (hstr)); sizeof (hstr));
*end = '\0'; *end = '\0';
GNUNET_asprintf (&arg_str, GNUNET_asprintf (&arg_str,
"/kyc-proof/%s?code=%s&state=%s", "/kyc-proof/%s/%s%s",
hstr, hstr,
code, logic,
state); args);
} }
kph = GNUNET_new (struct TALER_EXCHANGE_KycProofHandle); kph = GNUNET_new (struct TALER_EXCHANGE_KycProofHandle);
kph->exchange = exchange; kph->exchange = exchange;

View File

@ -152,6 +152,7 @@ handle_kyc_wallet_finished (void *cls,
struct TALER_EXCHANGE_KycWalletHandle * struct TALER_EXCHANGE_KycWalletHandle *
TALER_EXCHANGE_kyc_wallet (struct TALER_EXCHANGE_Handle *exchange, TALER_EXCHANGE_kyc_wallet (struct TALER_EXCHANGE_Handle *exchange,
const struct TALER_ReservePrivateKeyP *reserve_priv, const struct TALER_ReservePrivateKeyP *reserve_priv,
const struct TALER_Amount *balance,
TALER_EXCHANGE_KycWalletCallback cb, TALER_EXCHANGE_KycWalletCallback cb,
void *cb_cls) void *cb_cls)
{ {
@ -167,6 +168,8 @@ TALER_EXCHANGE_kyc_wallet (struct TALER_EXCHANGE_Handle *exchange,
TALER_wallet_account_setup_sign (reserve_priv, TALER_wallet_account_setup_sign (reserve_priv,
&reserve_sig); &reserve_sig);
req = GNUNET_JSON_PACK ( req = GNUNET_JSON_PACK (
TALER_JSON_pack_amount ("balance",
balance),
GNUNET_JSON_pack_data_auto ("reserve_pub", GNUNET_JSON_pack_data_auto ("reserve_pub",
&reserve_pub), &reserve_pub),
GNUNET_JSON_pack_data_auto ("reserve_sig", GNUNET_JSON_pack_data_auto ("reserve_sig",

View File

@ -232,7 +232,6 @@ handle_purse_merge_finished (void *cls,
GNUNET_CRYPTO_eddsa_key_get_public (&pch->merge_priv.eddsa_priv, GNUNET_CRYPTO_eddsa_key_get_public (&pch->merge_priv.eddsa_priv,
&merge_pub.eddsa_pub); &merge_pub.eddsa_pub);
if (GNUNET_OK != if (GNUNET_OK !=
TALER_EXCHANGE_check_purse_merge_conflict_ ( TALER_EXCHANGE_check_purse_merge_conflict_ (
&pch->merge_sig, &pch->merge_sig,

View File

@ -264,28 +264,6 @@ handle_reserve_withdraw_finished (void *cls,
GNUNET_assert (NULL == wh->cb); GNUNET_assert (NULL == wh->cb);
TALER_EXCHANGE_withdraw2_cancel (wh); TALER_EXCHANGE_withdraw2_cancel (wh);
return; return;
case MHD_HTTP_ACCEPTED:
/* only validate reply is well-formed */
{
uint64_t ptu;
struct GNUNET_JSON_Specification spec[] = {
GNUNET_JSON_spec_uint64 ("payment_target_uuid",
&ptu),
GNUNET_JSON_spec_end ()
};
if (GNUNET_OK !=
GNUNET_JSON_parse (j,
spec,
NULL, NULL))
{
GNUNET_break_op (0);
hr.http_status = 0;
hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
break;
}
}
break;
case MHD_HTTP_BAD_REQUEST: case MHD_HTTP_BAD_REQUEST:
/* This should never happen, either us or the exchange is buggy /* This should never happen, either us or the exchange is buggy
(or API version conflict); just pass JSON reply to the application */ (or API version conflict); just pass JSON reply to the application */
@ -333,6 +311,28 @@ handle_reserve_withdraw_finished (void *cls,
hr.ec = TALER_JSON_get_error_code (j); hr.ec = TALER_JSON_get_error_code (j);
hr.hint = TALER_JSON_get_error_hint (j); hr.hint = TALER_JSON_get_error_hint (j);
break; break;
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 ("payment_target_uuid",
&ptu),
GNUNET_JSON_spec_end ()
};
if (GNUNET_OK !=
GNUNET_JSON_parse (j,
spec,
NULL, NULL))
{
GNUNET_break_op (0);
hr.http_status = 0;
hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
break;
}
}
break;
case MHD_HTTP_INTERNAL_SERVER_ERROR: case MHD_HTTP_INTERNAL_SERVER_ERROR:
/* Server had an internal issue; we should retry, but this API /* Server had an internal issue; we should retry, but this API
leaves this to the application */ leaves this to the application */

View File

@ -1,6 +1,6 @@
/* /*
This file is part of TALER This file is part of TALER
Copyright (C) 2014-2021 Taler Systems SA Copyright (C) 2014-2022 Taler Systems SA
TALER is free software; you can redistribute it and/or modify TALER is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as it under the terms of the GNU General Public License as
@ -124,11 +124,12 @@ run (void *cls,
"EUR:5", "EUR:5",
0, /* age restriction off */ 0, /* age restriction off */
MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS), MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS),
TALER_TESTING_cmd_proof_kyc ("proof-kyc", TALER_TESTING_cmd_proof_kyc_oauth2 ("proof-kyc",
"create-reserve-1", "create-reserve-1",
"pass", "kyc-provider-test-oauth2",
"state", "pass",
MHD_HTTP_SEE_OTHER), "state",
MHD_HTTP_SEE_OTHER),
TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-1-with-kyc", TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-1-with-kyc",
"create-reserve-1", "create-reserve-1",
"EUR:5", "EUR:5",
@ -158,26 +159,35 @@ run (void *cls,
struct TALER_TESTING_Command track[] = { struct TALER_TESTING_Command track[] = {
CMD_EXEC_AGGREGATOR ("run-aggregator-before-kyc"), CMD_EXEC_AGGREGATOR ("run-aggregator-before-kyc"),
TALER_TESTING_cmd_check_bank_empty ("check_bank_empty-no-kyc"), TALER_TESTING_cmd_check_bank_empty ("check_bank_empty-no-kyc"),
TALER_TESTING_cmd_track_transaction (
"track-deposit-kyc-ready",
"deposit-simple",
0,
MHD_HTTP_ACCEPTED,
NULL),
TALER_TESTING_cmd_check_kyc_get ("check-kyc-deposit", TALER_TESTING_cmd_check_kyc_get ("check-kyc-deposit",
"track-deposit", "track-deposit-kyc-ready",
MHD_HTTP_ACCEPTED), MHD_HTTP_ACCEPTED),
TALER_TESTING_cmd_proof_kyc ("proof-kyc-no-service", TALER_TESTING_cmd_proof_kyc_oauth2 ("proof-kyc-no-service",
"track-deposit", "track-deposit-kyc-ready",
"bad", "kyc-provider-test-oauth2",
"state", "bad",
MHD_HTTP_BAD_GATEWAY), "state",
MHD_HTTP_BAD_GATEWAY),
TALER_TESTING_cmd_oauth ("start-oauth-service", TALER_TESTING_cmd_oauth ("start-oauth-service",
6666), 6666),
TALER_TESTING_cmd_proof_kyc ("proof-kyc-fail", TALER_TESTING_cmd_proof_kyc_oauth2 ("proof-kyc-fail",
"track-deposit", "track-deposit-kyc-ready",
"bad", "kyc-provider-test-oauth2",
"state", "bad",
MHD_HTTP_FORBIDDEN), "state",
TALER_TESTING_cmd_proof_kyc ("proof-kyc-fail", MHD_HTTP_FORBIDDEN),
"track-deposit", TALER_TESTING_cmd_proof_kyc_oauth2 ("proof-kyc-fail",
"pass", "track-deposit-kyc-ready",
"state", "kyc-provider-test-oauth2",
MHD_HTTP_SEE_OTHER), "pass",
"state",
MHD_HTTP_SEE_OTHER),
CMD_EXEC_AGGREGATOR ("run-aggregator-after-kyc"), CMD_EXEC_AGGREGATOR ("run-aggregator-after-kyc"),
TALER_TESTING_cmd_check_bank_transfer ( TALER_TESTING_cmd_check_bank_transfer (
"check_bank_transfer-499c", "check_bank_transfer-499c",
@ -190,15 +200,19 @@ run (void *cls,
}; };
struct TALER_TESTING_Command wallet_kyc[] = { struct TALER_TESTING_Command wallet_kyc[] = {
TALER_TESTING_cmd_oauth ("start-oauth-service",
6666),
TALER_TESTING_cmd_wallet_kyc_get ( TALER_TESTING_cmd_wallet_kyc_get (
"wallet-kyc-fail", "wallet-kyc-fail",
NULL, NULL,
"EUR:1000000",
MHD_HTTP_OK), MHD_HTTP_OK),
TALER_TESTING_cmd_proof_kyc ("proof-wallet-kyc", TALER_TESTING_cmd_proof_kyc_oauth2 ("proof-wallet-kyc",
"wallet-kyc-fail", "wallet-kyc-fail",
"pass", "kyc-provider-test-oauth2",
"state", "pass",
MHD_HTTP_SEE_OTHER), "state",
MHD_HTTP_SEE_OTHER),
TALER_TESTING_cmd_check_kyc_get ( TALER_TESTING_cmd_check_kyc_get (
"wallet-kyc-check", "wallet-kyc-check",
"wallet-kyc-fail", "wallet-kyc-fail",

View File

@ -46,16 +46,12 @@ DB = postgres
BASE_URL = "http://localhost:8081/" BASE_URL = "http://localhost:8081/"
# Obsolete options, migrate to withdraw once implemented...
KYC_MODE = OAUTH2 KYC_MODE = OAUTH2
KYC_WALLET_BALANCE_LIMIT = EUR:1
KYC_WITHDRAW_PERIOD = "31 days" KYC_WITHDRAW_PERIOD = "31 days"
KYC_WITHDRAW_LIMIT = EUR:8 KYC_WITHDRAW_LIMIT = EUR:8
[exchange-kyc-oauth2] [exchange-kyc-oauth2]
KYC_OAUTH2_AUTH_URL = http://localhost:6666/oauth/v2/token KYC_OAUTH2_AUTH_URL = http://localhost:6666/oauth/v2/token
KYC_OAUTH2_LOGIN_URL = http://localhost:6666/oauth/v2/login KYC_OAUTH2_LOGIN_URL = http://localhost:6666/oauth/v2/login
KYC_INFO_URL = http://localhost:6666/api/user/me KYC_INFO_URL = http://localhost:6666/api/user/me
@ -63,6 +59,38 @@ KYC_OAUTH2_CLIENT_ID = taler-exchange
KYC_OAUTH2_CLIENT_SECRET = exchange-secret KYC_OAUTH2_CLIENT_SECRET = exchange-secret
KYC_OAUTH2_POST_URL = http://example.com/ KYC_OAUTH2_POST_URL = http://example.com/
# end of obsolete options...
[kyc-provider-test-oauth2]
COST = 0
LOGIC = oauth2
USER_TYPE = INDIVIDUAL
PROVIDED_CHECKS = DUMMY
KYC_OAUTH2_VALIDITY = forever
KYC_OAUTH2_AUTH_URL = http://localhost:6666/oauth/v2/token
KYC_OAUTH2_LOGIN_URL = http://localhost:6666/oauth/v2/login
KYC_OAUTH2_INFO_URL = http://localhost:6666/api/user/me
KYC_OAUTH2_CLIENT_ID = taler-exchange
KYC_OAUTH2_CLIENT_SECRET = exchange-secret
KYC_OAUTH2_POST_URL = http://example.com/
[kyc-legitimization-balance-high]
OPERATION_TYPE = BALANCE
REQUIRED_CHECKS = DUMMY
THRESHOLD = EUR:8
[kyc-legitimization-deposit-any]
OPERATION_TYPE = DEPOSIT
REQUIRED_CHECKS = DUMMY
THRESHOLD = EUR:0
TIMEFRAME = 1d
[kyc-legitimization-withdraw]
OPERATION_TYPE = WITHDRAW
REQUIRED_CHECKS = DUMMY
THRESHOLD = EUR:8
TIMEFRAME = 1d
[exchangedb-postgres] [exchangedb-postgres]
CONFIG = "postgres:///talercheck" CONFIG = "postgres:///talercheck"

View File

@ -283,7 +283,7 @@ batch_withdraw_run (void *cls,
GNUNET_CRYPTO_eddsa_key_get_public (&ws->reserve_priv.eddsa_priv, GNUNET_CRYPTO_eddsa_key_get_public (&ws->reserve_priv.eddsa_priv,
&ws->reserve_pub.eddsa_pub); &ws->reserve_pub.eddsa_pub);
ws->reserve_payto_uri ws->reserve_payto_uri
= TALER_payto_from_reserve (ws->exchange_url, = TALER_reserve_make_payto (ws->exchange_url,
&ws->reserve_pub); &ws->reserve_pub);
for (unsigned int i = 0; i<ws->num_coins; i++) for (unsigned int i = 0; i<ws->num_coins; i++)

View File

@ -48,6 +48,11 @@ struct KycProofGetState
*/ */
const char *state; const char *state;
/**
* Logic section name to pass to `/kyc-proof/` handler.
*/
const char *logic;
/** /**
* Expected HTTP response code. * Expected HTTP response code.
*/ */
@ -133,6 +138,7 @@ proof_kyc_run (void *cls,
const struct TALER_TESTING_Command *res_cmd; const struct TALER_TESTING_Command *res_cmd;
const char **payto_uri; const char **payto_uri;
struct TALER_PaytoHashP h_payto; struct TALER_PaytoHashP h_payto;
char *uargs;
(void) cmd; (void) cmd;
kps->is = is; kps->is = is;
@ -169,12 +175,17 @@ proof_kyc_run (void *cls,
TALER_payto_hash (*payto_uri, TALER_payto_hash (*payto_uri,
&h_payto); &h_payto);
} }
GNUNET_asprintf (&uargs,
"?code=%s&state=%s",
kps->code,
kps->state);
kps->kph = TALER_EXCHANGE_kyc_proof (is->exchange, kps->kph = TALER_EXCHANGE_kyc_proof (is->exchange,
&h_payto, &h_payto,
kps->code, kps->logic,
kps->state, uargs,
&proof_kyc_cb, &proof_kyc_cb,
kps); kps);
GNUNET_free (uargs);
GNUNET_assert (NULL != kps->kph); GNUNET_assert (NULL != kps->kph);
} }
@ -236,17 +247,20 @@ proof_kyc_traits (void *cls,
struct TALER_TESTING_Command struct TALER_TESTING_Command
TALER_TESTING_cmd_proof_kyc (const char *label, TALER_TESTING_cmd_proof_kyc_oauth2 (
const char *payment_target_reference, const char *label,
const char *code, const char *payment_target_reference,
const char *state, const char *logic_section,
unsigned int expected_response_code) const char *code,
const char *state,
unsigned int expected_response_code)
{ {
struct KycProofGetState *kps; struct KycProofGetState *kps;
kps = GNUNET_new (struct KycProofGetState); kps = GNUNET_new (struct KycProofGetState);
kps->code = code; kps->code = code;
kps->state = state; kps->state = state;
kps->logic = logic_section;
kps->payment_target_reference = payment_target_reference; kps->payment_target_reference = payment_target_reference;
kps->expected_response_code = expected_response_code; kps->expected_response_code = expected_response_code;
{ {

View File

@ -69,6 +69,11 @@ struct KycWalletGetState
*/ */
struct TALER_EXCHANGE_KycWalletHandle *kwh; struct TALER_EXCHANGE_KycWalletHandle *kwh;
/**
* Balance to pass to the exchange.
*/
struct TALER_Amount balance;
/** /**
* Interpreter state. * Interpreter state.
*/ */
@ -170,10 +175,11 @@ wallet_kyc_run (void *cls,
GNUNET_CRYPTO_eddsa_key_get_public (&kwg->reserve_priv.eddsa_priv, GNUNET_CRYPTO_eddsa_key_get_public (&kwg->reserve_priv.eddsa_priv,
&kwg->reserve_pub.eddsa_pub); &kwg->reserve_pub.eddsa_pub);
kwg->reserve_payto_uri kwg->reserve_payto_uri
= TALER_payto_from_reserve (TALER_EXCHANGE_get_base_url (is->exchange), = TALER_reserve_make_payto (TALER_EXCHANGE_get_base_url (is->exchange),
&kwg->reserve_pub); &kwg->reserve_pub);
kwg->kwh = TALER_EXCHANGE_kyc_wallet (is->exchange, kwg->kwh = TALER_EXCHANGE_kyc_wallet (is->exchange,
&kwg->reserve_priv, &kwg->reserve_priv,
&kwg->balance,
&wallet_kyc_cb, &wallet_kyc_cb,
kwg); kwg);
GNUNET_assert (NULL != kwg->kwh); GNUNET_assert (NULL != kwg->kwh);
@ -242,6 +248,7 @@ wallet_kyc_traits (void *cls,
struct TALER_TESTING_Command struct TALER_TESTING_Command
TALER_TESTING_cmd_wallet_kyc_get (const char *label, TALER_TESTING_cmd_wallet_kyc_get (const char *label,
const char *reserve_reference, const char *reserve_reference,
const char *threshold_balance,
unsigned int expected_response_code) unsigned int expected_response_code)
{ {
struct KycWalletGetState *kwg; struct KycWalletGetState *kwg;
@ -249,6 +256,9 @@ TALER_TESTING_cmd_wallet_kyc_get (const char *label,
kwg = GNUNET_new (struct KycWalletGetState); kwg = GNUNET_new (struct KycWalletGetState);
kwg->reserve_reference = reserve_reference; kwg->reserve_reference = reserve_reference;
kwg->expected_response_code = expected_response_code; kwg->expected_response_code = expected_response_code;
GNUNET_assert (GNUNET_OK ==
TALER_string_to_amount (threshold_balance,
&kwg->balance));
{ {
struct TALER_TESTING_Command cmd = { struct TALER_TESTING_Command cmd = {
.cls = kwg, .cls = kwg,

View File

@ -320,6 +320,9 @@ reserve_withdraw_cb (void *cls,
case MHD_HTTP_GONE: case MHD_HTTP_GONE:
/* theoretically could check that the key was actually */ /* theoretically could check that the key was actually */
break; break;
case MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS:
/* KYC required */
break;
default: default:
/* Unsupported status code (by test harness) */ /* Unsupported status code (by test harness) */
GNUNET_log (GNUNET_ERROR_TYPE_WARNING, GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
@ -375,7 +378,7 @@ withdraw_run (void *cls,
GNUNET_CRYPTO_eddsa_key_get_public (&ws->reserve_priv.eddsa_priv, GNUNET_CRYPTO_eddsa_key_get_public (&ws->reserve_priv.eddsa_priv,
&ws->reserve_pub.eddsa_pub); &ws->reserve_pub.eddsa_pub);
ws->reserve_payto_uri ws->reserve_payto_uri
= TALER_payto_from_reserve (ws->exchange_url, = TALER_reserve_make_payto (ws->exchange_url,
&ws->reserve_pub); &ws->reserve_pub);
if (NULL == ws->reuse_coin_key_ref) if (NULL == ws->reuse_coin_key_ref)

View File

@ -273,45 +273,6 @@ TALER_payto_hash (const char *payto,
} }
char *
TALER_payto_from_reserve (const char *exchange_base_url,
const struct TALER_ReservePublicKeyP *reserve_pub)
{
char *payto_uri;
char *rps;
unsigned int skip;
const char *extra = "";
int url_len;
rps = GNUNET_STRINGS_data_to_string_alloc (reserve_pub,
sizeof (*reserve_pub));
skip = 0;
if (0 == strncasecmp (exchange_base_url,
"http://",
strlen ("http://")))
{
skip = strlen ("http://");
extra = "+http";
}
if (0 == strncasecmp (exchange_base_url,
"https://",
strlen ("https://")))
skip = strlen ("https://");
url_len = strlen (exchange_base_url);
if ('/' == exchange_base_url[url_len - 1])
url_len--;
url_len -= skip;
GNUNET_asprintf (&payto_uri,
"taler%s://reserve/%.*s/%s",
extra,
url_len,
exchange_base_url + skip,
rps);
GNUNET_free (rps);
return payto_uri;
}
char * char *
TALER_reserve_make_payto (const char *exchange_url, TALER_reserve_make_payto (const char *exchange_url,
const struct TALER_ReservePublicKeyP *reserve_pub) const struct TALER_ReservePublicKeyP *reserve_pub)
@ -349,7 +310,7 @@ TALER_reserve_make_payto (const char *exchange_url,
/* exchange_url includes trailing '/' */ /* exchange_url includes trailing '/' */
GNUNET_asprintf (&reserve_url, GNUNET_asprintf (&reserve_url,
"payto://%s/%s%s", "payto://%s/%s%s",
is_http ? "taler+http" : "taler", is_http ? "taler-reserve+http" : "taler-reserve",
exchange_url, exchange_url,
pub_str); pub_str);
return reserve_url; return reserve_url;