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.
[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_credit = yes
@ -19,7 +19,7 @@ HONOR_default = YES
ACTIVE_default = YES
[merchant-exchange-default]
MASTER_KEY = BV7EQTVVH06981REGB1EZKNBTYK2WMAZWD0SDXK9RSBVNXPBCW9G
MASTER_KEY = SA4PMGHM403V1F2TQVFRVKH9BTZ2FBG3V6R7FFVVTYFEFDYG3AX0
EXCHANGE_BASE_URL = http://localhost:8081/
CURRENCY = TESTKUDOS
@ -155,7 +155,7 @@ UNIXPATH = ${TALER_RUNTIME_DIR}/merchant.http
CONFIG = postgres:///auditor-basedb
[exchange]
MASTER_PUBLIC_KEY = BV7EQTVVH06981REGB1EZKNBTYK2WMAZWD0SDXK9RSBVNXPBCW9G
MASTER_PUBLIC_KEY = SA4PMGHM403V1F2TQVFRVKH9BTZ2FBG3V6R7FFVVTYFEFDYG3AX0
SIGNKEY_DURATION = 4 weeks
LOOKAHEAD_SIGN = 32 weeks 1 day
SIGNKEY_LEGAL_DURATION = 4 weeks
@ -177,7 +177,7 @@ CONFIG = postgres:///auditor-basedb
[auditor]
BASE_URL = http://localhost:8083/
TINY_AMOUNT = TESTKUDOS:0.01
PUBLIC_KEY = EB8DHSV6EPXPVDEB3YN9X90MHJ9BEXD0KH5H4CQVC5HTK1AQJ1Y0
PUBLIC_KEY = JZYPE53YY23MQ0HTTV3DYHRABW4RM6SJS1Y0HF2HMSEPEPRJ77WG
[PATHS]
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_LDADD = \
$(LIBGCRYPT_LIBS) \
$(top_builddir)/src/kyclogic/libtalerkyclogic.la \
$(top_builddir)/src/json/libtalerjson.la \
$(top_builddir)/src/util/libtalerutil.la \
$(top_builddir)/src/bank-lib/libtalerbank.la \

View File

@ -1,6 +1,6 @@
/*
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
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_plugin.h"
#include "taler_json_lib.h"
#include "taler_kyclogic_lib.h"
#include "taler_bank_service.h"
@ -42,6 +43,12 @@ struct AggregationUnit
*/
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.
*/
@ -84,6 +91,19 @@ struct AggregationUnit
*/
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;
/**
* 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
*/
static void
run_shard (void *cls);
drain_kyc_alerts (void *cls);
/**
@ -226,6 +245,7 @@ shutdown_task (void *cls)
GNUNET_SCHEDULER_cancel (task);
task = NULL;
}
TALER_KYCLOGIC_kyc_done ();
TALER_EXCHANGEDB_plugin_unload (db_plugin);
db_plugin = NULL;
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
run_aggregation (void *cls)
{
struct Shard *s = cls;
struct AggregationUnit au_active;
enum GNUNET_DB_QueryStatus qs;
struct TALER_Amount trans;
bool have_transient = true; /* squash compiler warning */
enum GNUNET_GenericReturnValue ret;
task = NULL;
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
@ -384,7 +720,6 @@ run_aggregation (void *cls)
db_plugin->cls,
s->shard_start,
s->shard_end,
kyc_off ? true : false,
&au_active.merchant_pub,
&au_active.payto_uri);
switch (qs)
@ -432,10 +767,10 @@ run_aggregation (void *cls)
/* If we ended up doing zero work, sleep a bit */
if (0 == counter)
task = GNUNET_SCHEDULER_add_delayed (aggregator_idle_sleep_interval,
&run_shard,
&drain_kyc_alerts,
NULL);
else
task = GNUNET_SCHEDULER_add_now (&run_shard,
task = GNUNET_SCHEDULER_add_now (&drain_kyc_alerts,
NULL);
return;
}
@ -444,254 +779,31 @@ run_aggregation (void *cls)
/* continued below */
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,
&au_active.h_payto);
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);
ret = do_aggregate (&au_active);
cleanup_au (&au_active);
if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
switch (ret)
{
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Serialization issue for prepared wire data; trying again later!\n");
case GNUNET_SYSERR:
GNUNET_SCHEDULER_shutdown ();
db_plugin->rollback (db_plugin->cls);
release_shard (s);
return;
case GNUNET_NO:
db_plugin->rollback (db_plugin->cls);
/* 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);
/* die hard */
global_ret = EXIT_FAILURE;
GNUNET_SCHEDULER_shutdown ();
release_shard (s);
return;
case GNUNET_OK:
/* continued below */
break;
}
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Stored wire transfer out instructions\n");
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Committing aggregation result\n");
/* Now we can finally commit the overall transaction, as we are
again consistent if all of this passes. */
@ -699,8 +811,8 @@ run_aggregation (void *cls)
{
case GNUNET_DB_STATUS_SOFT_ERROR:
/* try again */
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Commit issue for prepared wire data; trying again later!\n");
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Serialization issue on commit; trying again later!\n");
GNUNET_assert (NULL == task);
task = GNUNET_SCHEDULER_add_now (&run_aggregation,
s);
@ -714,7 +826,7 @@ run_aggregation (void *cls)
return;
case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Preparation complete, going again\n");
"Commit complete, going again\n");
GNUNET_assert (NULL == task);
task = GNUNET_SCHEDULER_add_now (&run_aggregation,
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.
*
@ -811,7 +1114,8 @@ run (void *cls,
(void) cfgfile;
cfg = c;
if (GNUNET_OK != parse_aggregator_config ())
if (GNUNET_OK !=
parse_aggregator_config ())
{
cfg = NULL;
global_ret = EXIT_NOTCONFIGURED;
@ -832,11 +1136,18 @@ run (void *cls,
shard_size = 1U + INT32_MAX;
else
shard_size = (uint32_t) ass;
GNUNET_assert (NULL == task);
task = GNUNET_SCHEDULER_add_now (&run_shard,
NULL);
if (GNUNET_OK !=
TALER_KYCLOGIC_kyc_init (cfg))
{
cfg = NULL;
global_ret = EXIT_NOTCONFIGURED;
return;
}
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 <sys/resource.h>
#include <limits.h>
#include "taler_kyclogic_lib.h"
#include "taler_mhd_lib.h"
#include "taler-exchange-httpd_auditors.h"
#include "taler-exchange-httpd_batch-deposit.h"
@ -1215,13 +1216,14 @@ handle_mhd_request (void *cls,
.url = "kyc-check",
.method = MHD_HTTP_METHOD_GET,
.handler.get = &TEH_handler_kyc_check,
.nargs = 1
.nargs = 2
},
{
.url = "kyc-proof",
.method = MHD_HTTP_METHOD_GET,
.handler.get = &TEH_handler_kyc_proof,
.nargs = 1
.nargs = 128,
.nargs_is_upper_bound = true
},
{
.url = "kyc-wallet",
@ -1680,6 +1682,11 @@ parse_kyc_oauth_cfg (void)
static enum GNUNET_GenericReturnValue
exchange_serve_process_config (void)
{
if (GNUNET_OK !=
TALER_KYCLOGIC_kyc_init (TEH_cfg))
{
return GNUNET_SYSERR;
}
{
char *kyc_mode;
@ -2094,8 +2101,12 @@ do_shutdown (void *cls)
TEH_purses_get_cleanup ();
TEH_kyc_check_cleanup ();
TEH_kyc_proof_cleanup ();
TALER_KYCLOGIC_kyc_done ();
if (NULL != mhd)
{
MHD_stop_daemon (mhd);
mhd = NULL;
}
TEH_wire_done ();
TEH_extensions_done ();
TEH_keys_finished ();

View File

@ -27,6 +27,7 @@
#include <gnunet/gnunet_util_lib.h>
#include <jansson.h>
#include "taler_json_lib.h"
#include "taler_kyclogic_lib.h"
#include "taler_mhd_lib.h"
#include "taler-exchange-httpd_batch-withdraw.h"
#include "taler-exchange-httpd_responses.h"
@ -86,6 +87,17 @@ struct BatchWithdrawContext
*/
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.
*/
@ -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
* 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;
bool balance_ok = 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,
now,
wc->reserve_pub,
&wc->batch_total,
&found,
&balance_ok,
&wc->kyc,
&ruuid);
if (0 > qs)
{
@ -164,55 +246,6 @@ batch_withdraw_transaction (void *cls,
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 */
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;
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 ();
GNUNET_assert (NULL != sigs);
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,
&wc.batch_total));
wc.reserve_pub = reserve_pub;
{
enum GNUNET_GenericReturnValue res;

View File

@ -184,7 +184,7 @@ deposit_transaction (void *cls,
}
if (in_conflict)
{
/* FIXME #7267: conficting contract != insufficient funds */
/* FIXME #7267: conflicting contract != insufficient funds */
*mhd_ret
= TEH_RESPONSE_reply_coin_insufficient_funds (
connection,
@ -426,7 +426,7 @@ TEH_handler_deposit (struct MHD_Connection *connection,
&h_wire,
&deposit.h_contract_terms,
&deposit.coin.h_age_commitment,
NULL /* h_extensions! */,
NULL /* FIXME: h_extensions! */,
&deposit.coin.denom_pub_hash,
deposit.timestamp,
&deposit.merchant_pub,
@ -481,7 +481,7 @@ TEH_handler_deposit (struct MHD_Connection *connection,
res = reply_deposit_success (connection,
&deposit.coin.coin_pub,
&h_wire,
NULL /* h_extensions! */,
NULL /* FIXME: h_extensions! */,
&deposit.h_contract_terms,
dc.exchange_timestamp,
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.
*/
struct TALER_PrivateContractHashP h_contract_terms GNUNET_PACKED;
struct TALER_PrivateContractHashP h_contract_terms;
/**
* 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
@ -251,8 +251,12 @@ handle_track_transaction_request (
return TALER_MHD_REPLY_JSON_PACK (
connection,
MHD_HTTP_ACCEPTED,
GNUNET_JSON_pack_uint64 ("payment_target_uuid",
ctx->kyc.payment_target_uuid),
GNUNET_JSON_pack_allow_null (
(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",
ctx->kyc.ok),
GNUNET_JSON_pack_timestamp ("execution_time",

View File

@ -21,6 +21,7 @@
#include "platform.h"
#include "taler_json_lib.h"
#include "taler_mhd_lib.h"
#include "taler_kyclogic_lib.h"
#include "taler_dbevents.h"
#include "taler-exchange-httpd.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,
* @a recoup and @a denoms.
@ -1854,17 +1876,20 @@ create_krd (struct TEH_KeyStateHandle *ksh,
GNUNET_assert (NULL != keys);
/* 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 (
0 ==
json_object_set_new (
keys,
"wallet_balance_limit_without_kyc",
TALER_JSON_from_amount (
&TEH_kyc_config.wallet_balance_limit)));
json_t *wblwk = NULL;
TALER_KYCLOGIC_kyc_iterate_thresholds (
TALER_KYCLOGIC_KYC_TRIGGER_WALLET_BALANCE,
&wallet_threshold_cb,
&wblwk);
if (NULL != wblwk)
GNUNET_assert (
0 ==
json_object_set_new (
keys,
"wallet_balance_limit_without_kyc",
wblwk));
}
/* Signal support for the configured, enabled extensions. */

View File

@ -1,6 +1,6 @@
/*
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
terms of the GNU Affero General Public License as published by the Free Software
@ -25,6 +25,7 @@
#include <microhttpd.h>
#include <pthread.h>
#include "taler_json_lib.h"
#include "taler_kyclogic_lib.h"
#include "taler_mhd_lib.h"
#include "taler_signatures.h"
#include "taler_dbevents.h"
@ -53,6 +54,17 @@ struct KycPoller
*/
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
* waiting for.
@ -62,12 +74,7 @@ struct KycPoller
/**
* UUID being checked.
*/
uint64_t auth_payment_target_uuid;
/**
* Current KYC status.
*/
struct TALER_EXCHANGEDB_KycStatus kyc;
uint64_t legitimization_uuid;
/**
* Hash of the payto:// URI we are confirming to
@ -75,21 +82,46 @@ struct KycPoller
*/
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?
*/
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.
*/
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,
kyp_tail,
kyp);
if (NULL != kyp->ih)
{
kyp->ih_logic->initiate_cancel (kyp->ih);
kyp->ih = NULL;
}
if (kyp->suspended)
{
kyp->suspended = false;
@ -143,10 +180,83 @@ kyp_cleanup (struct TEH_RequestContext *rc)
kyp->eh);
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);
}
/**
* 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.
* 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;
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,
&kyp->h_payto,
&kyp->kyc);
if (qs < 0)
qs = TEH_plugin->lookup_kyc_requirement_by_row (
TEH_plugin->cls,
kyp->legitimization_uuid,
&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)
return qs;
GNUNET_break (0);
*mhd_ret = TALER_MHD_reply_with_error (connection,
MHD_HTTP_INTERNAL_SERVER_ERROR,
TALER_EC_GENERIC_DB_FETCH_FAILED,
"inselect_wallet_status");
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"No KYC requirements open for %llu\n",
(unsigned long long) kyp->legitimization_uuid);
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;
}
@ -230,7 +401,7 @@ db_event_cb (void *cls,
MHD_RESULT
TEH_handler_kyc_check (
struct TEH_RequestContext *rc,
const char *const args[])
const char *const args[2])
{
struct KycPoller *kyp = rc->rh_ctx;
MHD_RESULT res;
@ -245,24 +416,37 @@ TEH_handler_kyc_check (
rc->rh_cleaner = &kyp_cleanup;
{
// FIXME: now 'legitimization_uuid'!
unsigned long long payment_target_uuid;
unsigned long long legitimization_uuid;
char dummy;
if (1 !=
sscanf (args[0],
"%llu%c",
&payment_target_uuid,
&legitimization_uuid,
&dummy))
{
GNUNET_break_op (0);
return TALER_MHD_reply_with_error (rc->connection,
MHD_HTTP_BAD_REQUEST,
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;
@ -291,41 +475,8 @@ TEH_handler_kyc_check (
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) &&
GNUNET_TIME_absolute_is_future (kyp->timeout) )
{
@ -353,27 +504,48 @@ TEH_handler_kyc_check (
&kyc_check,
kyp);
if (GNUNET_SYSERR == ret)
return res;
if (kyp->auth_payment_target_uuid !=
kyp->kyc.payment_target_uuid)
{
GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
"Account %llu provided, but payto %s is for %llu\n",
(unsigned long long) kyp->auth_payment_target_uuid,
kyp->hps,
(unsigned long long) kyp->kyc.payment_target_uuid);
GNUNET_break_op (0);
return TALER_MHD_reply_with_error (rc->connection,
MHD_HTTP_FORBIDDEN,
TALER_EC_EXCHANGE_KYC_CHECK_AUTHORIZATION_FAILED,
"h_payto");
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Transaction failed.\n");
return res;
}
if ( (NULL == kyp->ih) &&
(! kyp->found) )
{
/* KYC not required */
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? */
if ( (! kyp->kyc.ok) &&
if ( (NULL != kyp->required) &&
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);
kyp->suspended = true;
GNUNET_CONTAINER_DLL_insert (kyp_head,
@ -383,37 +555,24 @@ TEH_handler_kyc_check (
return MHD_YES;
}
/* KYC failed? */
if (! kyp->kyc.ok)
/* KYC plugin generated reply? */
if (NULL != kyp->kyc_url)
{
char *url;
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 (
return TALER_MHD_REPLY_JSON_PACK (
rc->connection,
MHD_HTTP_ACCEPTED,
GNUNET_JSON_pack_string ("kyc_url",
url));
GNUNET_free (url);
return res;
kyp->kyc_url));
}
/* 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_ExchangeSignatureP sig;

View File

@ -1,6 +1,6 @@
/*
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
terms of the GNU Affero General Public License as published by the Free Software
@ -25,6 +25,7 @@
#include <microhttpd.h>
#include <pthread.h>
#include "taler_json_lib.h"
#include "taler_kyclogic_lib.h"
#include "taler_mhd_lib.h"
#include "taler-exchange-httpd_kyc-proof.h"
#include "taler-exchange-httpd_responses.h"
@ -52,31 +53,37 @@ struct KycProofContext
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.
*/
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.
*/
@ -87,17 +94,25 @@ struct KycProofContext
*/
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.
*/
unsigned int response_code;
/**
* #GNUNET_YES if we are suspended,
* #GNUNET_NO if not.
* #GNUNET_SYSERR if we had some error.
* True if we are suspended,
*/
enum GNUNET_GenericReturnValue suspended;
bool suspended;
};
@ -122,7 +137,7 @@ static void
kpc_resume (struct KycProofContext *kpc)
{
GNUNET_assert (GNUNET_YES == kpc->suspended);
kpc->suspended = GNUNET_NO;
kpc->suspended = false;
GNUNET_CONTAINER_DLL_remove (kpc_head,
kpc_tail,
kpc);
@ -138,10 +153,10 @@ TEH_kyc_proof_cleanup (void)
while (NULL != (kpc = kpc_head))
{
if (NULL != kpc->job)
if (NULL != kpc->ph)
{
GNUNET_CURL_job_cancel (kpc->job);
kpc->job = NULL;
kpc->logic->proof_cancel (kpc->ph);
kpc->ph = NULL;
}
kpc_resume (kpc);
}
@ -149,348 +164,69 @@ TEH_kyc_proof_cleanup (void)
/**
* Function implementing database transaction to check proof's KYC status.
* 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.
* Function called with the result of a proof check operation.
*
* @param cls closure with a `struct KycProofContext *`
* @param connection MHD proof which triggered the transaction
* @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.
* Note that the "decref" for the @a response
* will be done by the callee and MUST NOT be done by the plugin.
*
* @param[in,out] kpc request that failed
* @param j reply from the server (or NULL)
* @param cls closure
* @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
handle_error (struct KycProofContext *kpc,
const json_t *j)
proof_cb (
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;
const char *desc;
struct GNUNET_JSON_Specification spec[] = {
GNUNET_JSON_spec_string ("error",
&msg),
GNUNET_JSON_spec_string ("error_description",
&desc),
GNUNET_JSON_spec_end ()
};
struct KycProofContext *kpc = cls;
struct TEH_RequestContext *rc = kpc->rc;
struct GNUNET_AsyncScopeSave old_scope;
kpc->ph = NULL;
GNUNET_async_scope_enter (&rc->async_scope_id,
&old_scope);
if (TALER_KYCLOGIC_STATUS_SUCCESS == status)
{
enum GNUNET_GenericReturnValue res;
const char *emsg;
unsigned int line;
enum GNUNET_DB_QueryStatus qs;
res = GNUNET_JSON_parse (j,
spec,
&emsg,
&line);
if (GNUNET_OK != res)
qs = TEH_plugin->update_kyc_requirement_by_row (TEH_plugin->cls,
kpc->legi_row,
kpc->provider_section,
&kpc->h_payto,
provider_user_id,
provider_legitimization_id,
expiration);
if (GNUNET_DB_STATUS_HARD_ERROR == qs)
{
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;
GNUNET_break (0);
kpc->response_code = MHD_HTTP_INTERNAL_SERVER_ERROR;
kpc->response = TALER_MHD_make_error (TALER_EC_GENERIC_DB_STORE_FAILED,
"set_kyc_ok");
GNUNET_async_scope_restore (&old_scope);
return;
}
}
/* case TALER_EC_EXCHANGE_KYC_PROOF_BACKEND_AUTHORZATION_FAILED,
we MAY want to in the future look at the requested content type
and possibly respond in JSON if indicated. */
else
{
char *reply;
GNUNET_asprintf (&reply,
"<html><head><title>%s</title></head><body><h1>%s</h1><p>%s</p></body></html>",
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;
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"KYC logic #%llu failed with status %d\n",
(unsigned long long) kpc->legi_row,
status);
}
kpc->response_code = http_status;
kpc->response = response;
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;
if (NULL != kpc->job)
if (NULL != kpc->ph)
{
GNUNET_CURL_job_cancel (kpc->job);
kpc->job = NULL;
kpc->logic->proof_cancel (kpc->ph);
kpc->ph = NULL;
}
if (NULL != kpc->response)
{
MHD_destroy_response (kpc->response);
kpc->response = NULL;
}
GNUNET_free (kpc->post_body);
GNUNET_free (kpc->token_url);
GNUNET_free (kpc->id);
GNUNET_free (kpc->provider_user_id);
GNUNET_free (kpc->provider_legitimization_id);
GNUNET_free (kpc->provider_section);
GNUNET_free (kpc);
}
@ -529,7 +265,18 @@ TEH_handler_kyc_proof (
struct KycProofContext *kpc = rc->rh_ctx;
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->rc = rc;
rc->rh_ctx = kpc;
@ -546,166 +293,98 @@ TEH_handler_kyc_proof (
TALER_EC_GENERIC_PARAMETER_MALFORMED,
"h_payto");
}
kpc->authorization_code
= MHD_lookup_connection_value (rc->connection,
MHD_GET_ARGUMENT_KIND,
"code");
if (NULL == kpc->authorization_code)
kpc->provider_section = GNUNET_strdup (args[1]);
if (GNUNET_OK !=
TALER_KYCLOGIC_kyc_get_logic (kpc->provider_section,
&kpc->logic,
&kpc->pd))
{
GNUNET_break_op (0);
return TALER_MHD_reply_with_error (rc->connection,
MHD_HTTP_BAD_REQUEST,
TALER_EC_GENERIC_PARAMETER_MALFORMED,
"code");
MHD_HTTP_NOT_FOUND,
TALER_EC_EXCHANGE_KYC_GENERIC_LOGIC_UNKNOWN,
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 ();
if (NULL == eh)
qs = TEH_plugin->lookup_kyc_requirement_by_account (
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,
MHD_HTTP_INTERNAL_SERVER_ERROR,
TALER_EC_GENERIC_ALLOCATION_FAILURE,
"curl_easy_init");
MHD_HTTP_NOT_FOUND,
TALER_EC_EXCHANGE_KYC_PROOF_REQUEST_UNKNOWN,
kpc->provider_section);
case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
break;
}
GNUNET_asprintf (&kpc->token_url,
"%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));
if (GNUNET_TIME_absolute_is_future (expiration))
{
char *client_id;
char *redirect_uri;
char *client_secret;
char *authorization_code;
client_id = curl_easy_escape (eh,
TEH_kyc_config.details.oauth2.client_id,
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);
/* KYC not required */
return TALER_MHD_reply_static (
rc->connection,
MHD_HTTP_NO_CONTENT,
NULL,
NULL,
0);
}
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;
}
}
if (NULL != kpc->response)
{
/* handle _failed_ resumed cases */
return MHD_queue_response (rc->connection,
kpc->response_code,
kpc->response);
}
/* _successfully_ resumed case */
{
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)
kpc->ph = kpc->logic->proof (kpc->logic->cls,
kpc->pd,
&args[2],
rc->connection,
&kpc->h_payto,
kpc->legi_row,
kpc->provider_user_id,
kpc->provider_legitimization_id,
&proof_cb,
kpc);
if (NULL == kpc->ph)
{
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,
MHD_HTTP_HEADER_LOCATION,
TEH_kyc_config.details.oauth2.post_kyc_redirect_url));
res = MHD_queue_response (rc->connection,
MHD_HTTP_SEE_OTHER,
response);
MHD_destroy_response (response);
return res;
kpc->suspended = true;
GNUNET_CONTAINER_DLL_insert (kpc_head,
kpc_tail,
kpc);
MHD_suspend_connection (rc->connection);
return MHD_YES;
}
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
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
terms of the GNU Affero General Public License as published by the Free Software
@ -26,6 +26,7 @@
#include <pthread.h>
#include "taler_json_lib.h"
#include "taler_mhd_lib.h"
#include "taler_kyclogic_lib.h"
#include "taler-exchange-httpd_kyc-wallet.h"
#include "taler-exchange-httpd_responses.h"
@ -38,15 +39,54 @@ struct KycRequestContext
/**
* 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.
* 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;
enum GNUNET_DB_QueryStatus qs;
qs = TEH_plugin->inselect_wallet_kyc_status (TEH_plugin->cls,
&krc->reserve_pub,
&krc->kyc);
krc->required = TALER_KYCLOGIC_kyc_test_required (
TALER_KYCLOGIC_KYC_TRIGGER_WALLET_BALANCE,
&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 (GNUNET_DB_STATUS_SOFT_ERROR == qs)
@ -80,9 +134,14 @@ wallet_kyc_check (void *cls,
*mhd_ret = TALER_MHD_reply_with_error (connection,
MHD_HTTP_INTERNAL_SERVER_ERROR,
TALER_EC_GENERIC_DB_FETCH_FAILED,
"inselect_wallet_status");
"insert_kyc_requirement_for_account");
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;
}
@ -95,11 +154,17 @@ TEH_handler_kyc_wallet (
{
struct TALER_ReserveSignatureP reserve_sig;
struct KycRequestContext krc;
struct TALER_ReservePublicKeyP reserve_pub;
struct GNUNET_JSON_Specification spec[] = {
GNUNET_JSON_spec_fixed_auto ("reserve_sig",
&reserve_sig),
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 ()
};
MHD_RESULT res;
@ -115,8 +180,10 @@ TEH_handler_kyc_wallet (
return MHD_YES; /* failure */
TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_EDDSA]++;
// FIXME: add balance threshold crossed to
// what the wallet signs over!
if (GNUNET_OK !=
TALER_wallet_account_setup_verify (&krc.reserve_pub,
TALER_wallet_account_setup_verify (&reserve_pub,
&reserve_sig))
{
GNUNET_break_op (0);
@ -126,13 +193,19 @@ TEH_handler_kyc_wallet (
TALER_EC_EXCHANGE_KYC_WALLET_SIGNATURE_INVALID,
NULL);
}
if (TEH_KYC_NONE == TEH_kyc_config.mode)
return TALER_MHD_reply_static (
rc->connection,
MHD_HTTP_NO_CONTENT,
NULL,
NULL,
0);
{
char *payto_uri;
payto_uri = TALER_reserve_make_payto (TEH_base_url,
&reserve_pub);
TALER_payto_hash (payto_uri,
&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,
"check wallet kyc",
TEH_MT_REQUEST_OTHER,
@ -141,11 +214,21 @@ TEH_handler_kyc_wallet (
&krc);
if (GNUNET_SYSERR == ret)
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 (
rc->connection,
MHD_HTTP_OK,
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",
pcc.payto_uri);
if ( (0 != strncmp (pcc.payto_uri,
"payto://taler/",
strlen ("payto://taler/"))) &&
"payto://taler-reserve/",
strlen ("payto://taler-reserve/"))) &&
(0 != strncmp (pcc.payto_uri,
"payto://taler+http/",
strlen ("payto://taler+http/"))) )
"payto://taler-reserve+http/",
strlen ("payto://taler-reserve+http/"))) )
{
GNUNET_break_op (0);
return TALER_MHD_reply_with_error (
@ -436,13 +436,13 @@ TEH_handler_purses_merge (
}
http = (0 == strncmp (pcc.payto_uri,
"payto://taler+http/",
strlen ("payto://taler+http/")));
"payto://taler-reserve+http/",
strlen ("payto://taler-reserve+http/")));
{
const char *host = &pcc.payto_uri[http
? strlen ("payto://taler+http/")
: strlen ("payto://taler/")];
? strlen ("payto://taler-reserve+http/")
: strlen ("payto://taler-reserve/")];
const char *slash = strchr (host,
'/');

View File

@ -1,6 +1,6 @@
/*
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
it under the terms of the GNU Affero General Public License as
@ -27,6 +27,7 @@
#include <gnunet/gnunet_util_lib.h>
#include <jansson.h>
#include "taler_json_lib.h"
#include "taler_kyclogic_lib.h"
#include "taler_mhd_lib.h"
#include "taler-exchange-httpd_withdraw.h"
#include "taler-exchange-httpd_responses.h"
@ -44,15 +45,6 @@ struct WithdrawContext
*/
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.
*/
@ -68,9 +60,66 @@ struct WithdrawContext
*/
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
* transaction logic; IF it returns a non-error code, the transaction
@ -98,25 +147,55 @@ withdraw_transaction (void *cls,
bool found = false;
bool balance_ok = false;
bool nonce_ok = false;
struct GNUNET_TIME_Timestamp now;
uint64_t ruuid;
const struct TALER_CsNonce *nonce;
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;
nonce =
(TALER_DENOMINATION_CS == bp->cipher)
nonce = (TALER_DENOMINATION_CS == bp->cipher)
? &bp->details.cs_blinded_planchet.nonce
: NULL;
qs = TEH_plugin->do_withdraw (TEH_plugin->cls,
nonce,
&wc->collectable,
now,
wc->now,
&found,
&balance_ok,
&nonce_ok,
&wc->kyc,
&ruuid);
if (0 > qs)
{
@ -153,56 +232,8 @@ withdraw_transaction (void *cls,
NULL);
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)
TEH_METRICS_num_success[TEH_MT_SUCCESS_BATCH_WITHDRAW]++;
TEH_METRICS_num_success[TEH_MT_SUCCESS_WITHDRAW]++;
return qs;
}
@ -274,7 +305,6 @@ TEH_handler_withdraw (struct TEH_RequestContext *rc,
0,
sizeof (wc));
wc.collectable.reserve_pub = *reserve_pub;
{
enum GNUNET_GenericReturnValue res;
@ -459,6 +489,14 @@ TEH_handler_withdraw (struct TEH_RequestContext *rc,
/* Clean up and send back final response */
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;

View File

@ -99,11 +99,12 @@ BEGIN
PERFORM create_partitioned_table(
'CREATE TABLE IF NOT EXISTS %I'
'(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)'
',provider_section VARCHAR NOT NULL'
',provider_user_id VARCHAR DEFAULT NULL'
',provider_legitimization_id VARCHAR DEFAULT NULL'
',UNIQUE (h_payto, provider_section)'
') %s ;'
,'legitimizations'
,'PARTITION BY HASH (h_payto)'
@ -898,6 +899,7 @@ BEGIN
'(amount_val INT8 NOT NULL'
',amount_frac INT4 NOT NULL'
',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'
',wtid_raw BYTEA NOT NULL CHECK (LENGTH(wtid_raw)=32)'
') %s ;'

View File

@ -63,6 +63,22 @@ COMMENT ON TABLE denomination_revocations
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 ----------------------------------------
CREATE TABLE IF NOT EXISTS profit_drains

View File

@ -589,6 +589,14 @@ prepare_statements (struct PostgresClosure *pg)
" WHERE reserve_pub=$1"
" LIMIT 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() */
GNUNET_PQ_make_prepare (
"set_kyc_ok",
@ -638,6 +646,18 @@ prepare_statements (struct PostgresClosure *pg)
" FROM wire_targets"
" WHERE wire_target_h_payto=$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() */
GNUNET_PQ_make_prepare (
"reserves_get",
@ -876,8 +896,6 @@ prepare_statements (struct PostgresClosure *pg)
" reserve_found"
",balance_ok"
",nonce_ok"
",kycok AS kyc_ok"
",account_uuid AS payment_target_uuid"
",ruuid"
" FROM exchange_do_withdraw"
" ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10);",
@ -889,8 +907,6 @@ prepare_statements (struct PostgresClosure *pg)
"SELECT "
" reserve_found"
",balance_ok"
",kycok AS kyc_ok"
",account_uuid AS payment_target_uuid"
",ruuid"
" FROM exchange_do_batch_withdraw"
" ($1,$2,$3,$4,$5);",
@ -1719,8 +1735,8 @@ prepare_statements (struct PostgresClosure *pg)
GNUNET_PQ_make_prepare (
"get_deposit_without_wtid",
"SELECT"
" wt.kyc_ok"
",wt.wire_target_serial_id AS payment_target_uuid"
" legi.expiration_time"
",legi.legitimization_serial_id"
",dep.wire_salt"
",wt.payto_uri"
",dep.amount_with_fee_val"
@ -1732,9 +1748,12 @@ prepare_statements (struct PostgresClosure *pg)
" JOIN wire_targets wt USING (wire_target_h_payto)"
" JOIN known_coins kc ON (kc.coin_pub = dep.coin_pub)"
" JOIN denominations denom USING (denominations_serial)"
" LEFT JOIN legitimizations legi ON (wt.wire_target_h_payto = legi.h_payto)"
" WHERE dep.coin_pub=$1"
" 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),
/* Used in #postgres_get_ready_deposit() */
GNUNET_PQ_make_prepare (
@ -1744,18 +1763,18 @@ prepare_statements (struct PostgresClosure *pg)
",merchant_pub"
" FROM deposits_by_ready dbr"
" 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"
" USING (wire_target_h_payto)"
" WHERE dbr.wire_deadline<=$1"
" AND dbr.shard >= $2"
" AND dbr.shard <= $3"
" AND (wt.kyc_ok OR $4)"
" ORDER BY "
" dbr.wire_deadline ASC"
" ,dbr.shard ASC"
" LIMIT 1;",
4),
3),
/* Used in #postgres_aggregate() */
GNUNET_PQ_make_prepare (
"aggregate",
@ -1850,11 +1869,12 @@ prepare_statements (struct PostgresClosure *pg)
"INSERT INTO aggregation_transient"
" (amount_val"
" ,amount_frac"
" ,merchant_pub"
" ,wire_target_h_payto"
" ,exchange_account_section"
" ,wtid_raw)"
" VALUES ($1, $2, $3, $4, $5);",
5),
" VALUES ($1, $2, $3, $4, $5, $6);",
6),
/* Used in #postgres_select_aggregation_transient() */
GNUNET_PQ_make_prepare (
"select_aggregation_transient",
@ -1864,8 +1884,22 @@ prepare_statements (struct PostgresClosure *pg)
" ,wtid_raw"
" FROM aggregation_transient"
" WHERE wire_target_h_payto=$1"
" AND exchange_account_section=$2;",
2),
" AND merchant_pub=$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() */
GNUNET_PQ_make_prepare (
"update_aggregation_transient",
@ -4525,6 +4559,8 @@ prepare_statements (struct PostgresClosure *pg)
" ,provider_section"
" ) VALUES "
" ($1, $2)"
" ON CONFLICT (h_payto,provider_section) "
" DO UPDATE SET h_payto=$1" /* syntax requirement: dummy op */
" RETURNING legitimization_serial_id",
2),
/* Used in #postgres_update_kyc_requirement_by_row() */
@ -4533,12 +4569,20 @@ prepare_statements (struct PostgresClosure *pg)
"UPDATE legitimizations"
" SET provider_user_id=$4"
" ,provider_legitimization_id=$5"
" ,expiration_time=$6"
" ,expiration_time=GREATEST(expiration_time,$6)"
" WHERE"
" h_payto=$3"
" AND legitimization_serial_id=$1"
" AND provider_section=$2;",
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() */
GNUNET_PQ_make_prepare (
"lookup_legitimization_by_row",
@ -4598,7 +4642,6 @@ prepare_statements (struct PostgresClosure *pg)
" AND ro.execution_date >= $2"
" ORDER BY ro.execution_date DESC",
2),
/* Used in #postgres_select_aggregation_amounts_for_kyc_check (
() */
GNUNET_PQ_make_prepare (
@ -5705,7 +5748,6 @@ postgres_reserves_get (void *cls,
GNUNET_PQ_result_spec_end
};
kyc->type = TALER_EXCHANGEDB_KYC_WITHDRAW;
return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
"reserves_get_with_kyc",
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.
*
@ -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.
*
@ -5792,7 +5897,6 @@ postgres_select_kyc_status (void *cls,
GNUNET_PQ_result_spec_end
};
kyc->type = TALER_EXCHANGEDB_KYC_UNKNOWN;
return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
"select_kyc_status_by_payto",
params,
@ -5863,7 +5967,6 @@ inselect_account_kyc_status (
kyc->ok = false;
}
}
kyc->type = TALER_EXCHANGEDB_KYC_BALANCE;
return qs;
}
@ -5888,7 +5991,7 @@ postgres_inselect_wallet_kyc_status (
enum GNUNET_DB_QueryStatus qs;
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);
qs = inselect_account_kyc_status (pg,
payto_uri,
@ -6284,7 +6387,6 @@ postgres_get_withdraw_info (
* @param[out] found set to true if the reserve was found
* @param[out] balance_ok set to true if the balance was sufficient
* @param[out] nonce_ok set to false if the nonce was reused
* @param[out] 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)
* @return query execution status
*/
@ -6297,7 +6399,6 @@ postgres_do_withdraw (
bool *found,
bool *balance_ok,
bool *nonce_ok,
struct TALER_EXCHANGEDB_KycStatus *kyc,
uint64_t *ruuid)
{
struct PostgresClosure *pg = cls;
@ -6321,12 +6422,8 @@ postgres_do_withdraw (
found),
GNUNET_PQ_result_spec_bool ("balance_ok",
balance_ok),
GNUNET_PQ_result_spec_bool ("kyc_ok",
&kyc->ok),
GNUNET_PQ_result_spec_bool ("nonce_ok",
nonce_ok),
GNUNET_PQ_result_spec_uint64 ("payment_target_uuid",
&kyc->payment_target_uuid),
GNUNET_PQ_result_spec_uint64 ("ruuid",
ruuid),
GNUNET_PQ_result_spec_end
@ -6335,7 +6432,6 @@ postgres_do_withdraw (
gc = GNUNET_TIME_absolute_to_timestamp (
GNUNET_TIME_absolute_add (now.abs_time,
pg->legal_reserve_expiration_time));
kyc->type = TALER_EXCHANGEDB_KYC_WITHDRAW;
return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
"call_withdraw",
params,
@ -6354,7 +6450,6 @@ postgres_do_withdraw (
* @param amount total amount to withdraw
* @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] kyc set to the KYC status of the reserve
* @param[out] ruuid set to the reserve's UUID (reserves table row)
* @return query execution status
*/
@ -6366,7 +6461,6 @@ postgres_do_batch_withdraw (
const struct TALER_Amount *amount,
bool *found,
bool *balance_ok,
struct TALER_EXCHANGEDB_KycStatus *kyc,
uint64_t *ruuid)
{
struct PostgresClosure *pg = cls;
@ -6383,10 +6477,6 @@ postgres_do_batch_withdraw (
found),
GNUNET_PQ_result_spec_bool ("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",
ruuid),
GNUNET_PQ_result_spec_end
@ -6395,7 +6485,6 @@ postgres_do_batch_withdraw (
gc = GNUNET_TIME_absolute_to_timestamp (
GNUNET_TIME_absolute_add (now.abs_time,
pg->legal_reserve_expiration_time));
kyc->type = TALER_EXCHANGEDB_KYC_WITHDRAW;
return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
"call_batch_withdraw",
params,
@ -7743,12 +7832,14 @@ postgres_create_aggregation_transient (
void *cls,
const struct TALER_PaytoHashP *h_payto,
const char *exchange_account_section,
const struct TALER_MerchantPublicKeyP *merchant_pub,
const struct TALER_WireTransferIdentifierRawP *wtid,
const struct TALER_Amount *total)
{
struct PostgresClosure *pg = cls;
struct GNUNET_PQ_QueryParam params[] = {
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_string (exchange_account_section),
GNUNET_PQ_query_param_auto_from_type (wtid),
@ -7775,6 +7866,7 @@ static enum GNUNET_DB_QueryStatus
postgres_select_aggregation_transient (
void *cls,
const struct TALER_PaytoHashP *h_payto,
const struct TALER_MerchantPublicKeyP *merchant_pub,
const char *exchange_account_section,
struct TALER_WireTransferIdentifierRawP *wtid,
struct TALER_Amount *total)
@ -7782,6 +7874,7 @@ postgres_select_aggregation_transient (
struct PostgresClosure *pg = cls;
struct GNUNET_PQ_QueryParam params[] = {
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_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.
* @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 start_shard_row minimum shard row to select
* @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] payto_uri set to the account of the merchant, to be freed by caller
* @return transaction status code
@ -7877,7 +8091,6 @@ static enum GNUNET_DB_QueryStatus
postgres_get_ready_deposit (void *cls,
uint64_t start_shard_row,
uint64_t end_shard_row,
bool kyc_off,
struct TALER_MerchantPublicKeyP *merchant_pub,
char **payto_uri)
{
@ -7887,7 +8100,6 @@ postgres_get_ready_deposit (void *cls,
GNUNET_PQ_query_param_absolute_time (&now),
GNUNET_PQ_query_param_uint64 (&start_shard_row),
GNUNET_PQ_query_param_uint64 (&end_shard_row),
GNUNET_PQ_query_param_bool (kyc_off),
GNUNET_PQ_query_param_end
};
struct GNUNET_PQ_ResultSpec rs[] = {
@ -9644,7 +9856,11 @@ postgres_lookup_transfer_by_deposit (
deposit_fee),
GNUNET_PQ_result_spec_end
};
struct GNUNET_TIME_Absolute expiration;
memset (kyc,
0,
sizeof (*kyc));
/* check if the aggregation record exists and get it */
qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
"lookup_deposit_wtid",
@ -9663,10 +9879,6 @@ postgres_lookup_transfer_by_deposit (
h_wire))
{
*pending = false;
memset (kyc,
0,
sizeof (*kyc));
kyc->type = TALER_EXCHANGEDB_KYC_DEPOSIT;
kyc->ok = true;
return qs;
}
@ -9685,15 +9897,21 @@ postgres_lookup_transfer_by_deposit (
/* Check if transaction exists in deposits, so that we just
do not have a WTID yet. In that case, return without wtid
(by setting 'pending' true). */
bool no_kyc = false;
struct GNUNET_PQ_ResultSpec rs2[] = {
GNUNET_PQ_result_spec_auto_from_type ("wire_salt",
&wire_salt),
GNUNET_PQ_result_spec_string ("payto_uri",
&payto_uri),
GNUNET_PQ_result_spec_uint64 ("payment_target_uuid",
&kyc->payment_target_uuid),
GNUNET_PQ_result_spec_auto_from_type ("kyc_ok",
&kyc->ok),
GNUNET_PQ_result_spec_allow_null (
GNUNET_PQ_result_spec_uint64 ("legitimization_serial_id",
&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",
amount_with_fee),
TALER_PQ_RESULT_SPEC_AMOUNT ("fee_deposit",
@ -9711,6 +9929,10 @@ postgres_lookup_transfer_by_deposit (
{
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,
&wire_salt,
&wh);
@ -9720,7 +9942,6 @@ postgres_lookup_transfer_by_deposit (
h_wire))
return GNUNET_DB_STATUS_SUCCESS_NO_RESULTS;
}
kyc->type = TALER_EXCHANGEDB_KYC_DEPOSIT;
return qs;
}
}
@ -16149,7 +16370,7 @@ postgres_do_purse_merge (
{
char *payto_uri;
payto_uri = TALER_payto_from_reserve (pg->exchange_url,
payto_uri = TALER_reserve_make_payto (pg->exchange_url,
reserve_pub);
TALER_payto_hash (payto_uri,
&h_payto);
@ -16228,7 +16449,7 @@ postgres_do_reserve_purse (
{
char *payto_uri;
payto_uri = TALER_payto_from_reserve (pg->exchange_url,
payto_uri = TALER_reserve_make_payto (pg->exchange_url,
reserve_pub);
TALER_payto_hash (payto_uri,
&h_payto);
@ -16612,16 +16833,57 @@ postgres_update_kyc_requirement_by_row (
GNUNET_PQ_query_param_uint64 (&legi_row),
GNUNET_PQ_query_param_string (provider_section),
GNUNET_PQ_query_param_auto_from_type (h_payto),
GNUNET_PQ_query_param_string (provider_account_id),
GNUNET_PQ_query_param_string (provider_legitimization_id),
(NULL != provider_account_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_end
};
enum GNUNET_DB_QueryStatus qs;
return GNUNET_PQ_eval_prepared_non_select (
qs = GNUNET_PQ_eval_prepared_non_select (
pg->conn,
"update_legitimization_requirement",
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;
return;
}
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Found satisfied LEGI: %s\n",
provider_section);
ctx->cb (ctx->cb_cls,
provider_section);
GNUNET_PQ_cleanup_result (rs);
@ -16877,6 +17142,9 @@ postgres_select_satisfied_kyc_processes (
params,
&get_legitimizations_cb,
&ctx);
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Satisfied LEGI check returned %d\n",
qs);
if (GNUNET_OK != ctx.status)
return GNUNET_DB_STATUS_HARD_ERROR;
return qs;
@ -17220,7 +17488,9 @@ libtaler_plugin_exchangedb_postgres_init (void *cls)
&postgres_iterate_auditor_denominations;
plugin->select_kyc_status = &postgres_select_kyc_status;
plugin->reserves_get = &postgres_reserves_get;
plugin->reserves_get_origin = &postgres_reserves_get_origin;
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->reserves_in_insert = &postgres_reserves_in_insert;
plugin->get_withdraw_info = &postgres_get_withdraw_info;
@ -17247,6 +17517,8 @@ libtaler_plugin_exchangedb_postgres_init (void *cls)
= &postgres_create_aggregation_transient;
plugin->select_aggregation_transient
= &postgres_select_aggregation_transient;
plugin->find_aggregation_transient
= &postgres_find_aggregation_transient;
plugin->update_aggregation_transient
= &postgres_update_aggregation_transient;
plugin->delete_aggregation_transient

View File

@ -37,8 +37,6 @@ CREATE OR REPLACE FUNCTION exchange_do_withdraw(
OUT reserve_found BOOLEAN,
OUT balance_ok BOOLEAN,
OUT nonce_ok BOOLEAN,
OUT kycok BOOLEAN,
OUT account_uuid INT8,
OUT ruuid INT8)
LANGUAGE plpgsql
AS $$
@ -67,8 +65,6 @@ THEN
-- denomination unknown, should be impossible!
reserve_found=FALSE;
balance_ok=FALSE;
kycok=FALSE;
account_uuid=0;
ruuid=0;
ASSERT false, 'denomination unknown';
RETURN;
@ -94,8 +90,6 @@ THEN
reserve_found=FALSE;
balance_ok=FALSE;
nonce_ok=TRUE;
kycok=FALSE;
account_uuid=0;
ruuid=2;
RETURN;
END IF;
@ -128,8 +122,6 @@ THEN
reserve_found=TRUE;
balance_ok=TRUE;
nonce_ok=TRUE;
kycok=TRUE;
account_uuid=0;
RETURN;
END IF;
@ -153,8 +145,6 @@ ELSE
reserve_found=TRUE;
nonce_ok=TRUE; -- we do not really know
balance_ok=FALSE;
kycok=FALSE; -- we do not really know or care
account_uuid=0;
RETURN;
END IF;
END IF;
@ -201,8 +191,6 @@ THEN
THEN
reserve_found=FALSE;
balance_ok=FALSE;
kycok=FALSE;
account_uuid=0;
nonce_ok=FALSE;
RETURN;
END IF;
@ -211,40 +199,9 @@ ELSE
nonce_ok=TRUE; -- no nonce, hence OK!
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 $$;
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';
@ -259,8 +216,6 @@ CREATE OR REPLACE FUNCTION exchange_do_batch_withdraw(
IN min_reserve_gc INT8,
OUT reserve_found BOOLEAN,
OUT balance_ok BOOLEAN,
OUT kycok BOOLEAN,
OUT account_uuid INT8,
OUT ruuid INT8)
LANGUAGE plpgsql
AS $$
@ -295,8 +250,6 @@ THEN
-- reserve unknown
reserve_found=FALSE;
balance_ok=FALSE;
kycok=FALSE;
account_uuid=0;
ruuid=2;
RETURN;
END IF;
@ -320,8 +273,6 @@ ELSE
ELSE
reserve_found=TRUE;
balance_ok=FALSE;
kycok=FALSE; -- we do not really know or care
account_uuid=0;
RETURN;
END IF;
END IF;
@ -340,37 +291,6 @@ WHERE
reserve_found=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 $$;
COMMENT ON FUNCTION exchange_do_batch_withdraw(INT8, INT4, BYTEA, INT8, INT8)

View File

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

View File

@ -304,10 +304,16 @@ struct TALER_EXCHANGE_Keys
struct GNUNET_TIME_Relative reserve_closing_delay;
/**
* Maximum amount a wallet is allowed to hold from
* this exchange before it must undergo a KYC check.
* Array of amounts a wallet is allowed to hold from
* 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.
@ -3422,7 +3428,7 @@ typedef void
* of a merchant.
*
* @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 timeout how long to wait for a positive KYC status
* @param cb function to call with the result
@ -3431,7 +3437,7 @@ typedef void
*/
struct TALER_EXCHANGE_KycCheckHandle *
TALER_EXCHANGE_kyc_check (struct TALER_EXCHANGE_Handle *eh,
uint64_t payment_target,
uint64_t legitimization_uuid,
const struct TALER_PaytoHashP *h_payto,
struct GNUNET_TIME_Relative timeout,
TALER_EXCHANGE_KycStatusCallback cb,
@ -3500,8 +3506,10 @@ struct TALER_EXCHANGE_KycProofHandle;
*
* @param eh exchange handle to use
* @param h_payto hash of payto URI identifying the target account
* @param code OAuth 2.0 code argument
* @param state OAuth 2.0 state argument
* @param logic name of the KYC logic to run
* @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_cls closure for @a cb
* @return NULL on error
@ -3509,8 +3517,8 @@ struct TALER_EXCHANGE_KycProofHandle;
struct TALER_EXCHANGE_KycProofHandle *
TALER_EXCHANGE_kyc_proof (struct TALER_EXCHANGE_Handle *eh,
const struct TALER_PaytoHashP *h_payto,
const char *code,
const char *state,
const char *logic,
const char *args,
TALER_EXCHANGE_KycProofCallback cb,
void *cb_cls);
@ -3573,6 +3581,7 @@ typedef void
*
* @param eh exchange handle to use
* @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_cls closure for @a cb
* @return NULL on error
@ -3580,6 +3589,7 @@ typedef void
struct TALER_EXCHANGE_KycWalletHandle *
TALER_EXCHANGE_kyc_wallet (struct TALER_EXCHANGE_Handle *eh,
const struct TALER_ReservePrivateKeyP *reserve_priv,
const struct TALER_Amount *balance,
TALER_EXCHANGE_KycWalletCallback cb,
void *cb_cls);

View File

@ -2327,11 +2327,13 @@ struct TALER_EXCHANGEDB_KycStatus
* Number that identifies the KYC target the operation
* was about.
*/
// FIXME: rename to 'legitimization_uuid'
uint64_t payment_target_uuid;
/**
* What kind of KYC operation is this?
*/
// FIXME: kill!
enum TALER_EXCHANGEDB_KycType type;
/**
@ -2582,6 +2584,26 @@ typedef enum GNUNET_GenericReturnValue
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.
*
@ -3097,6 +3119,21 @@ struct TALER_EXCHANGEDB_Plugin
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.
*
@ -3111,6 +3148,20 @@ struct TALER_EXCHANGEDB_Plugin
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.
*
@ -3207,7 +3258,6 @@ struct TALER_EXCHANGEDB_Plugin
* @param[out] found set to true if the reserve was found
* @param[out] balance_ok set to true if the balance was sufficient
* @param[out] nonce_ok set to false if the nonce was reused
* @param[out] kyc set to the KYC status of the reserve
* @param[out] ruuid set to the reserve's UUID (reserves table row)
* @return query execution status
*/
@ -3220,7 +3270,6 @@ struct TALER_EXCHANGEDB_Plugin
bool *found,
bool *balance_ok,
bool *nonce_ok,
struct TALER_EXCHANGEDB_KycStatus *kyc_ok,
uint64_t *ruuid);
@ -3235,7 +3284,6 @@ struct TALER_EXCHANGEDB_Plugin
* @param amount total amount to withdraw
* @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] kyc set to the KYC status of the reserve
* @param[out] ruuid set to the reserve's UUID (reserves table row)
* @return query execution status
*/
@ -3247,7 +3295,6 @@ struct TALER_EXCHANGEDB_Plugin
const struct TALER_Amount *amount,
bool *found,
bool *balance_ok,
struct TALER_EXCHANGEDB_KycStatus *kyc_ok,
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 start_shard_row minimum shard row to select
* @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] payto_uri set to the account of the merchant, to be freed by caller
* @return transaction status code
@ -3702,7 +3747,6 @@ struct TALER_EXCHANGEDB_Plugin
(*get_ready_deposit)(void *cls,
uint64_t start_shard_row,
uint64_t end_shard_row,
bool kyc_off,
struct TALER_MerchantPublicKeyP *merchant_pub,
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 h_payto destination of the wire transfer
* @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 total amount to be wired in the future
* @return transaction status
@ -3749,15 +3794,17 @@ struct TALER_EXCHANGEDB_Plugin
void *cls,
const struct TALER_PaytoHashP *h_payto,
const char *exchange_account_section,
const struct TALER_MerchantPublicKeyP *merchant_pub,
const struct TALER_WireTransferIdentifierRawP *wtid,
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 h_payto destination of the wire transfer
* @param merchant_pub public key of the merchant
* @param exchange_account_section exchange account to use
* @param[out] wtid set to the raw wire transfer identifier to be used
* @param[out] total existing amount to be wired in the future
@ -3767,11 +3814,29 @@ struct TALER_EXCHANGEDB_Plugin
(*select_aggregation_transient)(
void *cls,
const struct TALER_PaytoHashP *h_payto,
const struct TALER_MerchantPublicKeyP *merchant_pub,
const char *exchange_account_section,
struct TALER_WireTransferIdentifierRawP *wtid,
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.
* @a h_payto is only needed for query performance.

View File

@ -162,6 +162,19 @@ typedef void
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
* 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);
/**
* 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.
*

View File

@ -295,6 +295,7 @@ struct TALER_KYCLOGIC_Plugin
* @param url_path rest of the URL after `/kyc-webhook/$H_PAYTO/$LOGIC`
* @param connection MHD connection object (for HTTP headers)
* @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_legitimization_id legitimization ID the proof is for
* @param cb function to call with the result
@ -307,6 +308,7 @@ struct TALER_KYCLOGIC_Plugin
const char *const url_path[],
struct MHD_Connection *connection,
const struct TALER_PaytoHashP *account_id,
uint64_t legi_row,
const char *provider_user_id,
const char *provider_legitimization_id,
TALER_KYCLOGIC_ProofCallback cb,

View File

@ -2421,12 +2421,14 @@ TALER_TESTING_cmd_revoke_sign_key (
*
* @param label command label.
* @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
* @return the command
*/
struct TALER_TESTING_Command
TALER_TESTING_cmd_wallet_kyc_get (const char *label,
const char *reserve_reference,
const char *threshold_balance,
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 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 state OAuth 2.0 state to use
* @param expected_response_code expected HTTP status
* @return the command
*/
struct TALER_TESTING_Command
TALER_TESTING_cmd_proof_kyc (const char *label,
const char *payment_target_reference,
const char *code,
const char *state,
unsigned int expected_response_code);
TALER_TESTING_cmd_proof_kyc_oauth2 (
const char *label,
const char *payment_target_reference,
const char *logic_section,
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);
/**
* 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.
*

View File

@ -279,12 +279,22 @@ load_logic (const struct GNUNET_CONFIGURATION_Handle *cfg,
GNUNET_asprintf (&lib_name,
"libtaler_plugin_kyclogic_%s",
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,
(void *) cfg);
if (NULL != plugin)
plugin->library_name = lib_name;
else
GNUNET_free (lib_name);
GNUNET_array_append (kyc_logics,
num_kyc_logics,
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
add_trigger (const struct GNUNET_CONFIGURATION_Handle *cfg,
const char *section)
@ -797,6 +815,9 @@ eval_trigger (void *cls,
struct GNUNET_TIME_Relative duration;
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);
if (ttc->have_total)
{
@ -812,19 +833,31 @@ eval_trigger (void *cls,
else
{
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++)
{
const struct TALER_KYCLOGIC_KycTrigger *kt = kyc_triggers[i];
if (ttc->event != kt->trigger)
{
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"KYC check #%u: trigger type does not match\n",
i);
continue;
}
duration = GNUNET_TIME_relative_max (duration,
kt->timeframe);
if (GNUNET_TIME_relative_cmp (kt->timeframe,
>,
duration))
{
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"KYC check #%u: amount is beyond time limit\n",
i);
if (bump)
ttc->start = i;
return GNUNET_OK;
@ -833,6 +866,9 @@ eval_trigger (void *cls,
TALER_amount_cmp (&ttc->total,
&kt->threshold))
{
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"KYC check #%u: amount is below treshold\n",
i);
if (bump)
ttc->start = i;
bump = false;
@ -848,6 +884,9 @@ eval_trigger (void *cls,
for (unsigned int k = 0; k<*ttc->needed_cnt; k++)
if (ttc->needed[k] == rc)
{
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"KYC rule #%u already listed\n",
j);
found = true;
break;
}
@ -857,6 +896,11 @@ eval_trigger (void *cls,
(*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)
return GNUNET_NO; /* we hit all possible triggers! */
@ -903,13 +947,22 @@ remove_satisfied (void *cls,
if (0 != strcasecmp (provider_name,
kp->provider_section_name))
continue;
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Provider `%s' satisfied\n",
provider_name);
for (unsigned int j = 0; j<kp->num_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++)
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_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
database), remove those from the 'needed' array. */
GNUNET_break (0);
// FIXME: do via callback!
qs = ki (ki_cls,
h_payto,
&
remove_satisfied,
&remove_satisfied,
&rc);
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
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 */

View File

@ -68,6 +68,11 @@ struct TALER_KYCLOGIC_ProviderDetails
*/
struct PluginState *ps;
/**
* Configuration section that configured us.
*/
char *section;
/**
* URL of the OAuth2.0 endpoint for KYC checks.
* (token/auth)
@ -265,6 +270,7 @@ struct TALER_KYCLOGIC_WebhookHandle
static void
oauth2_unload_configuration (struct TALER_KYCLOGIC_ProviderDetails *pd)
{
GNUNET_free (pd->section);
GNUNET_free (pd->auth_url);
GNUNET_free (pd->login_url);
GNUNET_free (pd->info_url);
@ -292,6 +298,7 @@ oauth2_load_configuration (void *cls,
pd = GNUNET_new (struct TALER_KYCLOGIC_ProviderDetails);
pd->ps = ps;
pd->section = GNUNET_strdup (provider_section_name);
if (GNUNET_OK !=
GNUNET_CONFIGURATION_get_value_time (ps->cfg,
provider_section_name,
@ -467,9 +474,10 @@ initiate_task (void *cls)
hps = GNUNET_STRINGS_data_to_string_alloc (&ih->h_payto,
sizeof (ih->h_payto));
GNUNET_asprintf (&redirect_uri,
"%s/kyc-proof/%s/oauth2/%s",
"%s/kyc-proof/%s/%s/%s",
ps->exchange_base_url,
hps,
pd->section,
legi_s);
redirect_uri_encoded = TALER_urlencode (redirect_uri);
GNUNET_free (redirect_uri);
@ -532,7 +540,11 @@ oauth2_initiate (void *cls,
static void
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);
}
@ -659,6 +671,9 @@ parse_proof_success_reply (struct TALER_KYCLOGIC_ProofHandle *ph,
if (GNUNET_OK != res)
{
GNUNET_break_op (0);
json_dumpf (j,
stderr,
JSON_INDENT (2));
ph->status = TALER_KYCLOGIC_STATUS_PROVIDER_FAILED;
ph->response
= TALER_MHD_make_error (
@ -691,6 +706,9 @@ parse_proof_success_reply (struct TALER_KYCLOGIC_ProofHandle *ph,
if (GNUNET_OK != res)
{
GNUNET_break_op (0);
json_dumpf (data,
stderr,
JSON_INDENT (2));
ph->status = TALER_KYCLOGIC_STATUS_PROVIDER_FAILED;
ph->response
= TALER_MHD_make_error (
@ -741,6 +759,9 @@ handle_curl_proof_finished (void *cls,
j);
break;
default:
GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
"OAuth2.0 info URL returned HTTP status %u\n",
(unsigned int) response_code);
handle_proof_error (ph,
j);
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.
*
@ -758,6 +920,7 @@ handle_curl_proof_finished (void *cls,
* @param url_path rest of the URL after `/kyc-webhook/`
* @param connection MHD connection object (for HTTP headers)
* @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_legitimization_id legitimization ID the proof is for
* @param cb function to call with the result
@ -770,6 +933,7 @@ oauth2_proof (void *cls,
const char *const url_path[],
struct MHD_Connection *connection,
const struct TALER_PaytoHashP *account_id,
uint64_t legi_row,
const char *provider_user_id,
const char *provider_legitimization_id,
TALER_KYCLOGIC_ProofCallback cb,
@ -779,16 +943,20 @@ oauth2_proof (void *cls,
struct TALER_KYCLOGIC_ProofHandle *ph;
const char *code;
if (strlen (provider_legitimization_id) >=
sizeof (ph->provider_legitimization_id))
{
GNUNET_break (0);
return NULL;
}
GNUNET_break (NULL == provider_user_id);
ph = GNUNET_new (struct TALER_KYCLOGIC_ProofHandle);
strcpy (ph->provider_legitimization_id,
provider_legitimization_id);
GNUNET_snprintf (ph->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->connection = connection;
ph->h_payto = *account_id;
@ -891,7 +1059,7 @@ oauth2_proof (void *cls,
ph->job = GNUNET_CURL_job_add (ps->curl_ctx,
ph->eh,
&handle_curl_proof_finished,
&handle_curl_login_finished,
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 connection MHD connection object (for HTTP headers)
* @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_legitimization_id legitimization ID the proof is for
* @param cb function to call with the result
@ -265,6 +266,7 @@ template_proof (void *cls,
const char *const url_path[],
struct MHD_Connection *connection,
const struct TALER_PaytoHashP *account_id,
uint64_t legi_row,
const char *provider_user_id,
const char *provider_legitimization_id,
TALER_KYCLOGIC_ProofCallback cb,

View File

@ -754,6 +754,7 @@ handler_kyc_proof_get (
&args[2],
rc->connection,
&h_payto,
kyc_row_id,
cmd_provider_user_id,
cmd_provider_legitimization_id,
&proof_cb,
@ -1456,7 +1457,7 @@ main (int argc,
"use the given provider user ID (overridden if -i is also used)",
&cmd_provider_user_id),
GNUNET_GETOPT_option_string (
'l',
'U',
"legitimization",
"ID",
"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 (
'p',
"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)",
&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
TALER_EXCHANGE_check_purse_merge_conflict_ (
const struct TALER_PurseMergeSignatureP *cmerge_sig,
@ -1539,8 +1495,8 @@ TALER_EXCHANGE_check_purse_merge_conflict_ (
}
if (NULL == partner_url)
partner_url = exchange_url;
payto_uri = make_payto (partner_url,
&reserve_pub);
payto_uri = TALER_reserve_make_payto (partner_url,
&reserve_pub);
if (GNUNET_OK !=
TALER_wallet_purse_merge_verify (
payto_uri,

View File

@ -176,11 +176,14 @@ handle_deposit_wtid_finished (void *cls,
case MHD_HTTP_ACCEPTED:
{
/* Transaction known, but not executed yet */
bool no_legi = false;
struct GNUNET_JSON_Specification spec[] = {
GNUNET_JSON_spec_timestamp ("execution_time",
&dr.details.accepted.execution_time),
GNUNET_JSON_spec_uint64 ("payment_target_uuid",
&dr.details.accepted.payment_target_uuid),
GNUNET_JSON_spec_mark_optional (
GNUNET_JSON_spec_uint64 ("legitimization_uuid",
&dr.details.accepted.payment_target_uuid),
&no_legi),
GNUNET_JSON_spec_bool ("kyc_ok",
&dr.details.accepted.kyc_ok),
GNUNET_JSON_spec_end ()
@ -196,6 +199,8 @@ handle_deposit_wtid_finished (void *cls,
dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
break;
}
if (no_legi)
dr.details.accepted.payment_target_uuid = 0;
dwh->cb (dwh->cb_cls,
&dr);
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 TALER_ExchangePublicKeyP pub;
const char *currency;
json_t *wblwk = NULL;
struct GNUNET_JSON_Specification mspec[] = {
GNUNET_JSON_spec_fixed_auto ("denominations_sig",
&denominations_sig),
@ -750,8 +751,8 @@ decode_keys_json (const json_t *resp_obj,
GNUNET_JSON_spec_string ("currency",
&currency),
GNUNET_JSON_spec_mark_optional (
TALER_JSON_spec_amount_any ("wallet_balance_limit_without_kyc",
&key_data->wallet_balance_limit_without_kyc),
GNUNET_JSON_spec_json ("wallet_balance_limit_without_kyc",
&wblwk),
NULL),
GNUNET_JSON_spec_end ()
};
@ -819,17 +820,6 @@ decode_keys_json (const json_t *resp_obj,
NULL, NULL));
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 */
{
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. */
/* 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,
key_data->auditors_size,
0);
GNUNET_free (key_data->wallet_balance_limit_without_kyc);
GNUNET_free (key_data->version);
GNUNET_free (key_data->currency);
GNUNET_free (key_data->global_fees);

View File

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

View File

@ -1,6 +1,6 @@
/*
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
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 *
TALER_EXCHANGE_kyc_proof (struct TALER_EXCHANGE_Handle *exchange,
const struct TALER_PaytoHashP *h_payto,
const char *code,
const char *state,
const char *logic,
const char *args,
TALER_EXCHANGE_KycProofCallback cb,
void *cb_cls)
{
@ -151,13 +151,17 @@ TALER_EXCHANGE_kyc_proof (struct TALER_EXCHANGE_Handle *exchange,
struct GNUNET_CURL_Context *ctx;
char *arg_str;
if (NULL == args)
args = "";
else
GNUNET_assert ( (args[0] == '?') ||
(args[0] == '/') );
if (GNUNET_YES !=
TEAH_handle_is_ready (exchange))
{
GNUNET_break (0);
return NULL;
}
/* TODO: any escaping of code/state needed??? */
{
char hstr[sizeof (struct TALER_PaytoHashP) * 2];
char *end;
@ -168,10 +172,10 @@ TALER_EXCHANGE_kyc_proof (struct TALER_EXCHANGE_Handle *exchange,
sizeof (hstr));
*end = '\0';
GNUNET_asprintf (&arg_str,
"/kyc-proof/%s?code=%s&state=%s",
"/kyc-proof/%s/%s%s",
hstr,
code,
state);
logic,
args);
}
kph = GNUNET_new (struct TALER_EXCHANGE_KycProofHandle);
kph->exchange = exchange;

View File

@ -152,6 +152,7 @@ handle_kyc_wallet_finished (void *cls,
struct TALER_EXCHANGE_KycWalletHandle *
TALER_EXCHANGE_kyc_wallet (struct TALER_EXCHANGE_Handle *exchange,
const struct TALER_ReservePrivateKeyP *reserve_priv,
const struct TALER_Amount *balance,
TALER_EXCHANGE_KycWalletCallback cb,
void *cb_cls)
{
@ -167,6 +168,8 @@ TALER_EXCHANGE_kyc_wallet (struct TALER_EXCHANGE_Handle *exchange,
TALER_wallet_account_setup_sign (reserve_priv,
&reserve_sig);
req = GNUNET_JSON_PACK (
TALER_JSON_pack_amount ("balance",
balance),
GNUNET_JSON_pack_data_auto ("reserve_pub",
&reserve_pub),
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,
&merge_pub.eddsa_pub);
if (GNUNET_OK !=
TALER_EXCHANGE_check_purse_merge_conflict_ (
&pch->merge_sig,

View File

@ -264,28 +264,6 @@ handle_reserve_withdraw_finished (void *cls,
GNUNET_assert (NULL == wh->cb);
TALER_EXCHANGE_withdraw2_cancel (wh);
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:
/* This should never happen, either us or the exchange is buggy
(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.hint = TALER_JSON_get_error_hint (j);
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:
/* Server had an internal issue; we should retry, but this API
leaves this to the application */

View File

@ -1,6 +1,6 @@
/*
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
it under the terms of the GNU General Public License as
@ -124,11 +124,12 @@ run (void *cls,
"EUR:5",
0, /* age restriction off */
MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS),
TALER_TESTING_cmd_proof_kyc ("proof-kyc",
"create-reserve-1",
"pass",
"state",
MHD_HTTP_SEE_OTHER),
TALER_TESTING_cmd_proof_kyc_oauth2 ("proof-kyc",
"create-reserve-1",
"kyc-provider-test-oauth2",
"pass",
"state",
MHD_HTTP_SEE_OTHER),
TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-1-with-kyc",
"create-reserve-1",
"EUR:5",
@ -158,26 +159,35 @@ run (void *cls,
struct TALER_TESTING_Command track[] = {
CMD_EXEC_AGGREGATOR ("run-aggregator-before-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",
"track-deposit",
"track-deposit-kyc-ready",
MHD_HTTP_ACCEPTED),
TALER_TESTING_cmd_proof_kyc ("proof-kyc-no-service",
"track-deposit",
"bad",
"state",
MHD_HTTP_BAD_GATEWAY),
TALER_TESTING_cmd_proof_kyc_oauth2 ("proof-kyc-no-service",
"track-deposit-kyc-ready",
"kyc-provider-test-oauth2",
"bad",
"state",
MHD_HTTP_BAD_GATEWAY),
TALER_TESTING_cmd_oauth ("start-oauth-service",
6666),
TALER_TESTING_cmd_proof_kyc ("proof-kyc-fail",
"track-deposit",
"bad",
"state",
MHD_HTTP_FORBIDDEN),
TALER_TESTING_cmd_proof_kyc ("proof-kyc-fail",
"track-deposit",
"pass",
"state",
MHD_HTTP_SEE_OTHER),
TALER_TESTING_cmd_proof_kyc_oauth2 ("proof-kyc-fail",
"track-deposit-kyc-ready",
"kyc-provider-test-oauth2",
"bad",
"state",
MHD_HTTP_FORBIDDEN),
TALER_TESTING_cmd_proof_kyc_oauth2 ("proof-kyc-fail",
"track-deposit-kyc-ready",
"kyc-provider-test-oauth2",
"pass",
"state",
MHD_HTTP_SEE_OTHER),
CMD_EXEC_AGGREGATOR ("run-aggregator-after-kyc"),
TALER_TESTING_cmd_check_bank_transfer (
"check_bank_transfer-499c",
@ -190,15 +200,19 @@ run (void *cls,
};
struct TALER_TESTING_Command wallet_kyc[] = {
TALER_TESTING_cmd_oauth ("start-oauth-service",
6666),
TALER_TESTING_cmd_wallet_kyc_get (
"wallet-kyc-fail",
NULL,
"EUR:1000000",
MHD_HTTP_OK),
TALER_TESTING_cmd_proof_kyc ("proof-wallet-kyc",
"wallet-kyc-fail",
"pass",
"state",
MHD_HTTP_SEE_OTHER),
TALER_TESTING_cmd_proof_kyc_oauth2 ("proof-wallet-kyc",
"wallet-kyc-fail",
"kyc-provider-test-oauth2",
"pass",
"state",
MHD_HTTP_SEE_OTHER),
TALER_TESTING_cmd_check_kyc_get (
"wallet-kyc-check",
"wallet-kyc-fail",

View File

@ -46,16 +46,12 @@ DB = postgres
BASE_URL = "http://localhost:8081/"
# Obsolete options, migrate to withdraw once implemented...
KYC_MODE = OAUTH2
KYC_WALLET_BALANCE_LIMIT = EUR:1
KYC_WITHDRAW_PERIOD = "31 days"
KYC_WITHDRAW_LIMIT = EUR:8
[exchange-kyc-oauth2]
KYC_OAUTH2_AUTH_URL = http://localhost:6666/oauth/v2/token
KYC_OAUTH2_LOGIN_URL = http://localhost:6666/oauth/v2/login
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_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]
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,
&ws->reserve_pub.eddsa_pub);
ws->reserve_payto_uri
= TALER_payto_from_reserve (ws->exchange_url,
= TALER_reserve_make_payto (ws->exchange_url,
&ws->reserve_pub);
for (unsigned int i = 0; i<ws->num_coins; i++)

View File

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

View File

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

View File

@ -320,6 +320,9 @@ reserve_withdraw_cb (void *cls,
case MHD_HTTP_GONE:
/* theoretically could check that the key was actually */
break;
case MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS:
/* KYC required */
break;
default:
/* Unsupported status code (by test harness) */
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,
&ws->reserve_pub.eddsa_pub);
ws->reserve_payto_uri
= TALER_payto_from_reserve (ws->exchange_url,
= TALER_reserve_make_payto (ws->exchange_url,
&ws->reserve_pub);
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 *
TALER_reserve_make_payto (const char *exchange_url,
const struct TALER_ReservePublicKeyP *reserve_pub)
@ -349,7 +310,7 @@ TALER_reserve_make_payto (const char *exchange_url,
/* exchange_url includes trailing '/' */
GNUNET_asprintf (&reserve_url,
"payto://%s/%s%s",
is_http ? "taler+http" : "taler",
is_http ? "taler-reserve+http" : "taler-reserve",
exchange_url,
pub_str);
return reserve_url;