aboutsummaryrefslogtreecommitdiff
path: root/src/exchange
diff options
context:
space:
mode:
Diffstat (limited to 'src/exchange')
-rw-r--r--src/exchange/Makefile.am1
-rw-r--r--src/exchange/taler-exchange-aggregator.c779
-rw-r--r--src/exchange/taler-exchange-httpd.c15
-rw-r--r--src/exchange/taler-exchange-httpd_batch-withdraw.c146
-rw-r--r--src/exchange/taler-exchange-httpd_deposit.c6
-rw-r--r--src/exchange/taler-exchange-httpd_deposits_get.c12
-rw-r--r--src/exchange/taler-exchange-httpd_keys.c45
-rw-r--r--src/exchange/taler-exchange-httpd_kyc-check.c355
-rw-r--r--src/exchange/taler-exchange-httpd_kyc-proof.c671
-rw-r--r--src/exchange/taler-exchange-httpd_kyc-wallet.c119
-rw-r--r--src/exchange/taler-exchange-httpd_purses_merge.c16
-rw-r--r--src/exchange/taler-exchange-httpd_withdraw.c170
12 files changed, 1344 insertions, 991 deletions
diff --git a/src/exchange/Makefile.am b/src/exchange/Makefile.am
index 64db3899..881a3f36 100644
--- a/src/exchange/Makefile.am
+++ b/src/exchange/Makefile.am
@@ -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 \
diff --git a/src/exchange/taler-exchange-aggregator.c b/src/exchange/taler-exchange-aggregator.c
index 3d30ccd0..2c279535 100644
--- a/src/exchange/taler-exchange-aggregator.c
+++ b/src/exchange/taler-exchange-aggregator.c
@@ -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"
@@ -43,6 +44,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.
*/
struct TALER_Amount total_amount;
@@ -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,109 +373,170 @@ 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
-run_aggregation (void *cls)
+return_relevant_amounts (void *cls,
+ struct GNUNET_TIME_Absolute limit,
+ TALER_EXCHANGEDB_KycAmountCallback cb,
+ void *cb_cls)
{
- struct Shard *s = cls;
- struct AggregationUnit au_active;
+ const struct AggregationUnit *au_active = cls;
enum GNUNET_DB_QueryStatus qs;
- struct TALER_Amount trans;
- bool have_transient = true; /* squash compiler warning */
- task = NULL;
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Checking for ready deposits to aggregate\n");
- /* make sure we have current fees */
- memset (&au_active,
- 0,
- sizeof (au_active));
- au_active.execution_time = GNUNET_TIME_timestamp_get ();
+ "Returning amount %s in KYC check\n",
+ TALER_amount2s (&au_active->total_amount));
if (GNUNET_OK !=
- db_plugin->start_deferred_wire_out (db_plugin->cls))
+ 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 start database transaction!\n");
- global_ret = EXIT_FAILURE;
- GNUNET_SCHEDULER_shutdown ();
- release_shard (s);
- return;
+ "Failed to select aggregation amounts for KYC limit check!\n");
}
- qs = db_plugin->get_ready_deposit (
+}
+
+
+/**
+ * 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,
- s->shard_start,
- s->shard_end,
- kyc_off ? true : false,
- &au_active.merchant_pub,
- &au_active.payto_uri);
- switch (qs)
+ &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)
{
- case GNUNET_DB_STATUS_HARD_ERROR:
- cleanup_au (&au_active);
- db_plugin->rollback (db_plugin->cls);
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Failed to begin deposit iteration!\n");
- global_ret = EXIT_FAILURE;
- GNUNET_SCHEDULER_shutdown ();
- release_shard (s);
- return;
- case GNUNET_DB_STATUS_SOFT_ERROR:
- cleanup_au (&au_active);
- db_plugin->rollback (db_plugin->cls);
- GNUNET_assert (NULL == task);
- task = GNUNET_SCHEDULER_add_now (&run_aggregation,
- s);
- return;
- case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
- {
- uint64_t counter = s->work_counter;
- struct GNUNET_TIME_Relative duration
- = GNUNET_TIME_absolute_get_duration (s->start_time.abs_time);
-
- cleanup_au (&au_active);
- db_plugin->rollback (db_plugin->cls);
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Completed shard [%u,%u] after %s with %llu deposits\n",
- (unsigned int) s->shard_start,
- (unsigned int) s->shard_end,
- GNUNET_TIME_relative2s (duration,
- true),
- (unsigned long long) counter);
- release_shard (s);
- if ( (GNUNET_YES == test_mode) &&
- (0 == counter) )
- {
- /* in test mode, shutdown after a shard is done with 0 work */
- GNUNET_SCHEDULER_shutdown ();
- return;
- }
- GNUNET_assert (NULL == task);
- /* If we ended up doing zero work, sleep a bit */
- if (0 == counter)
- task = GNUNET_SCHEDULER_add_delayed (aggregator_idle_sleep_interval,
- &run_shard,
- NULL);
- else
- task = GNUNET_SCHEDULER_add_now (&run_shard,
- NULL);
- return;
- }
- case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
- s->work_counter++;
- /* continued below */
- break;
+ "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);
}
- au_active.wa = TALER_EXCHANGEDB_find_account_by_payto_uri (
- au_active.payto_uri);
- if (NULL == au_active.wa)
+ 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_active.payto_uri);
+ au->payto_uri);
global_ret = EXIT_FAILURE;
- GNUNET_SCHEDULER_shutdown ();
- db_plugin->rollback (db_plugin->cls);
- release_shard (s);
- return;
+ return GNUNET_SYSERR;
}
{
@@ -464,234 +545,265 @@ run_aggregation (void *cls)
struct TALER_MasterSignatureP master_sig;
qs = db_plugin->get_wire_fee (db_plugin->cls,
- au_active.wa->method,
- au_active.execution_time,
+ au->wa->method,
+ au->execution_time,
&start_date,
&end_date,
- &au_active.fees,
+ &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_active.wa->method,
- GNUNET_TIME_timestamp2s (au_active.execution_time));
+ au->wa->method,
+ GNUNET_TIME_timestamp2s (au->execution_time));
global_ret = EXIT_FAILURE;
- GNUNET_SCHEDULER_shutdown ();
- db_plugin->rollback (db_plugin->cls);
- release_shard (s);
- return;
+ 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_active.merchant_pub),
- au_active.payto_uri);
- TALER_payto_hash (au_active.payto_uri,
- &au_active.h_payto);
-
+ TALER_B2S (&au->merchant_pub),
+ au->payto_uri);
qs = db_plugin->select_aggregation_transient (db_plugin->cls,
- &au_active.h_payto,
- au_active.wa->section_name,
- &au_active.wtid,
- &trans);
+ &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");
- cleanup_au (&au_active);
- db_plugin->rollback (db_plugin->cls);
global_ret = EXIT_FAILURE;
- GNUNET_SCHEDULER_shutdown ();
- release_shard (s);
- return;
+ 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");
- db_plugin->rollback (db_plugin->cls);
- cleanup_au (&au_active);
- GNUNET_assert (NULL == task);
- task = GNUNET_SCHEDULER_add_now (&run_aggregation,
- s);
- return;
+ return GNUNET_NO;
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;
+ &au->wtid,
+ sizeof (au->wtid));
+ au->have_transient = false;
break;
case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
- have_transient = true;
+ au->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);
+ &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");
- cleanup_au (&au_active);
- db_plugin->rollback (db_plugin->cls);
global_ret = EXIT_FAILURE;
- GNUNET_SCHEDULER_shutdown ();
- release_shard (s);
- return;
+ 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");
- db_plugin->rollback (db_plugin->cls);
- cleanup_au (&au_active);
- GNUNET_assert (NULL == task);
- task = GNUNET_SCHEDULER_add_now (&run_aggregation,
- s);
- return;
+ return GNUNET_NO;
}
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Aggregation total is %s.\n",
- TALER_amount2s (&au_active.total_amount));
+ 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 (have_transient)
+ if (au->have_transient)
GNUNET_assert (0 <=
- TALER_amount_add (&au_active.total_amount,
- &au_active.total_amount,
- &trans));
+ 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_active.total_amount));
+ TALER_amount2s (&au->total_amount));
if ( (0 >=
- TALER_amount_subtract (&au_active.final_amount,
- &au_active.total_amount,
- &au_active.fees.wire)) ||
+ TALER_amount_subtract (&au->final_amount,
+ &au->total_amount,
+ &au->fees.wire)) ||
(GNUNET_SYSERR ==
- TALER_amount_round_down (&au_active.final_amount,
+ TALER_amount_round_down (&au->final_amount,
&currency_round_unit)) ||
- (TALER_amount_is_zero (&au_active.final_amount)) )
+ (TALER_amount_is_zero (&au->final_amount)) ||
+ (! kyc_satisfied (au)) )
{
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Aggregate value too low for transfer (%d/%s)\n",
+ "Not ready for wire transfer (%d/%s)\n",
qs,
- TALER_amount2s (&au_active.final_amount));
- if (have_transient)
+ TALER_amount2s (&au->final_amount));
+ if (au->have_transient)
qs = db_plugin->update_aggregation_transient (db_plugin->cls,
- &au_active.h_payto,
- &au_active.wtid,
- &au_active.total_amount);
+ &au->h_payto,
+ &au->wtid,
+ &au->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);
+ &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");
- 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;
+ return GNUNET_NO;
}
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;
+ return GNUNET_SYSERR;
}
/* commit */
- (void) commit_or_warn ();
- cleanup_au (&au_active);
-
- /* start again */
- GNUNET_assert (NULL == task);
- task = GNUNET_SCHEDULER_add_now (&run_aggregation,
- s);
- return;
+ return GNUNET_OK;
}
- 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);
+ 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;
}
- /* Commit the WTID data to 'wire_out' */
- if (qs >= 0)
- qs = db_plugin->store_wire_transfer_out (db_plugin->cls,
- au_active.execution_time,
- &au_active.wtid,
- &au_active.h_payto,
- au_active.wa->section_name,
- &au_active.final_amount);
+}
- if ( (qs >= 0) &&
- have_transient)
- qs = db_plugin->delete_aggregation_transient (db_plugin->cls,
- &au_active.h_payto,
- &au_active.wtid);
- cleanup_au (&au_active);
- if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
+static void
+run_aggregation (void *cls)
+{
+ struct Shard *s = cls;
+ struct AggregationUnit au_active;
+ enum GNUNET_DB_QueryStatus qs;
+ enum GNUNET_GenericReturnValue ret;
+
+ task = NULL;
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Checking for ready deposits to aggregate\n");
+ /* make sure we have current fees */
+ memset (&au_active,
+ 0,
+ sizeof (au_active));
+ au_active.execution_time = GNUNET_TIME_timestamp_get ();
+ if (GNUNET_OK !=
+ db_plugin->start_deferred_wire_out (db_plugin->cls))
{
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "Serialization issue for prepared wire data; trying again later!\n");
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failed to start database transaction!\n");
+ global_ret = EXIT_FAILURE;
+ GNUNET_SCHEDULER_shutdown ();
+ release_shard (s);
+ return;
+ }
+ qs = db_plugin->get_ready_deposit (
+ db_plugin->cls,
+ s->shard_start,
+ s->shard_end,
+ &au_active.merchant_pub,
+ &au_active.payto_uri);
+ switch (qs)
+ {
+ case GNUNET_DB_STATUS_HARD_ERROR:
+ cleanup_au (&au_active);
+ db_plugin->rollback (db_plugin->cls);
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failed to begin deposit iteration!\n");
+ global_ret = EXIT_FAILURE;
+ GNUNET_SCHEDULER_shutdown ();
+ release_shard (s);
+ return;
+ case GNUNET_DB_STATUS_SOFT_ERROR:
+ cleanup_au (&au_active);
db_plugin->rollback (db_plugin->cls);
- /* start again */
GNUNET_assert (NULL == task);
task = GNUNET_SCHEDULER_add_now (&run_aggregation,
s);
return;
+ case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+ {
+ uint64_t counter = s->work_counter;
+ struct GNUNET_TIME_Relative duration
+ = GNUNET_TIME_absolute_get_duration (s->start_time.abs_time);
+
+ cleanup_au (&au_active);
+ db_plugin->rollback (db_plugin->cls);
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Completed shard [%u,%u] after %s with %llu deposits\n",
+ (unsigned int) s->shard_start,
+ (unsigned int) s->shard_end,
+ GNUNET_TIME_relative2s (duration,
+ true),
+ (unsigned long long) counter);
+ release_shard (s);
+ if ( (GNUNET_YES == test_mode) &&
+ (0 == counter) )
+ {
+ /* in test mode, shutdown after a shard is done with 0 work */
+ GNUNET_SCHEDULER_shutdown ();
+ return;
+ }
+ GNUNET_assert (NULL == task);
+ /* If we ended up doing zero work, sleep a bit */
+ if (0 == counter)
+ task = GNUNET_SCHEDULER_add_delayed (aggregator_idle_sleep_interval,
+ &drain_kyc_alerts,
+ NULL);
+ else
+ task = GNUNET_SCHEDULER_add_now (&drain_kyc_alerts,
+ NULL);
+ return;
+ }
+ case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+ s->work_counter++;
+ /* continued below */
+ break;
}
- if (GNUNET_DB_STATUS_HARD_ERROR == qs)
+
+ TALER_payto_hash (au_active.payto_uri,
+ &au_active.h_payto);
+ ret = do_aggregate (&au_active);
+ cleanup_au (&au_active);
+ switch (ret)
{
- GNUNET_break (0);
- db_plugin->rollback (db_plugin->cls);
- /* die hard */
- global_ret = EXIT_FAILURE;
+ 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);
+ GNUNET_assert (NULL == task);
+ task = GNUNET_SCHEDULER_add_now (&run_aggregation,
+ 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);
@@ -792,6 +904,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.
*
* @param cls closure, NULL
@@ -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;
+ if (GNUNET_OK !=
+ TALER_KYCLOGIC_kyc_init (cfg))
+ {
+ cfg = NULL;
+ global_ret = EXIT_NOTCONFIGURED;
+ return;
+ }
+ GNUNET_SCHEDULER_add_shutdown (&shutdown_task,
+ NULL);
GNUNET_assert (NULL == task);
- task = GNUNET_SCHEDULER_add_now (&run_shard,
+ task = GNUNET_SCHEDULER_add_now (&drain_kyc_alerts,
NULL);
- GNUNET_SCHEDULER_add_shutdown (&shutdown_task,
- cls);
}
diff --git a/src/exchange/taler-exchange-httpd.c b/src/exchange/taler-exchange-httpd.c
index fefded57..e7d07043 100644
--- a/src/exchange/taler-exchange-httpd.c
+++ b/src/exchange/taler-exchange-httpd.c
@@ -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 ();
diff --git a/src/exchange/taler-exchange-httpd_batch-withdraw.c b/src/exchange/taler-exchange-httpd_batch-withdraw.c
index d306838b..9e24ff4c 100644
--- a/src/exchange/taler-exchange-httpd_batch-withdraw.c
+++ b/src/exchange/taler-exchange-httpd_batch-withdraw.c
@@ -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"
@@ -87,6 +88,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.
*/
struct TALER_Amount batch_total;
@@ -100,6 +112,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
* logic MUST NOT queue a MHD response. IF it returns an hard error,
@@ -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;
diff --git a/src/exchange/taler-exchange-httpd_deposit.c b/src/exchange/taler-exchange-httpd_deposit.c
index b7466939..0484ab07 100644
--- a/src/exchange/taler-exchange-httpd_deposit.c
+++ b/src/exchange/taler-exchange-httpd_deposit.c
@@ -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,
diff --git a/src/exchange/taler-exchange-httpd_deposits_get.c b/src/exchange/taler-exchange-httpd_deposits_get.c
index 97618a94..eec81569 100644
--- a/src/exchange/taler-exchange-httpd_deposits_get.c
+++ b/src/exchange/taler-exchange-httpd_deposits_get.c
@@ -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",
diff --git a/src/exchange/taler-exchange-httpd_keys.c b/src/exchange/taler-exchange-httpd_keys.c
index f9c35eee..729e627e 100644
--- a/src/exchange/taler-exchange-httpd_keys.c
+++ b/src/exchange/taler-exchange-httpd_keys.c
@@ -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"
@@ -1722,6 +1723,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. */
diff --git a/src/exchange/taler-exchange-httpd_kyc-check.c b/src/exchange/taler-exchange-httpd_kyc-check.c
index b4ec4197..61d1abf8 100644
--- a/src/exchange/taler-exchange-httpd_kyc-check.c
+++ b/src/exchange/taler-exchange-httpd_kyc-check.c
@@ -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"
@@ -54,6 +55,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
@@ -76,20 +83,45 @@ struct KycPoller
struct TALER_PaytoHashP h_payto;
/**
- * Payto URL as a string, as given to us by t
+ * When will this request time out?
*/
- const char *hps;
+ struct GNUNET_TIME_Absolute timeout;
/**
- * When will this request time out?
+ * Type of KYC check required for this client.
*/
- struct GNUNET_TIME_Absolute timeout;
+ 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,11 +180,84 @@ 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
* logic MUST NOT queue a MHD response. IF it returns an hard error, the
@@ -168,21 +278,82 @@ kyc_check (void *cls,
{
struct KycPoller *kyp = cls;
enum GNUNET_DB_QueryStatus qs;
-
- qs = TEH_plugin->select_kyc_status (TEH_plugin->cls,
- &kyp->h_payto,
- &kyp->kyc);
+ 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->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)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "No KYC requirements open for %llu\n",
+ (unsigned long long) kyp->legitimization_uuid);
+ return qs;
+ }
if (qs < 0)
{
- if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
- return qs;
- GNUNET_break (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_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_DB_FETCH_FAILED,
- "inselect_wallet_status");
+ 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)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Transaction failed.\n");
return res;
+ }
- if (kyp->auth_payment_target_uuid !=
- kyp->kyc.payment_target_uuid)
+ if ( (NULL == kyp->ih) &&
+ (! kyp->found) )
{
- 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");
+ /* 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));
+ }
+
+ if (TALER_EC_NONE != kyp->ec)
+ {
+ return TALER_MHD_reply_with_ec (rc->connection,
+ kyp->ec,
+ kyp->hint);
}
- /* KYC succeeded! */
+ /* KYC must have succeeded! */
{
struct TALER_ExchangePublicKeyP pub;
struct TALER_ExchangeSignatureP sig;
diff --git a/src/exchange/taler-exchange-httpd_kyc-proof.c b/src/exchange/taler-exchange-httpd_kyc-proof.c
index 75ff81e9..64694d28 100644
--- a/src/exchange/taler-exchange-httpd_kyc-proof.c
+++ b/src/exchange/taler-exchange-httpd_kyc-proof.c
@@ -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,30 +53,36 @@ 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;
/**
- * OAuth 2.0 authorization code.
+ * Configuration for @a logic.
*/
- const char *authorization_code;
+ struct TALER_KYCLOGIC_ProviderDetails *pd;
+
+ /**
+ * Asynchronous operation with the proof system.
+ */
+ struct TALER_KYCLOGIC_ProofHandle *ph;
/**
- * OAuth 2.0 token URL we are using for the
- * request.
+ * Process information about the user for the plugin from the database, can
+ * be NULL.
*/
- char *token_url;
+ char *provider_user_id;
/**
- * Body of the POST request.
+ * Process information about the legitimization process for the plugin from the
+ * database, can be NULL.
*/
- char *post_body;
+ char *provider_legitimization_id;
/**
- * User ID extracted from the OAuth 2.0 service, or NULL.
+ * OAuth 2.0 authorization code.
*/
- char *id;
+ const char *authorization_code;
/**
* Hash of payment target URI this is about.
@@ -88,16 +95,24 @@ 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 ()
- };
-
- {
- 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;
- }
- }
- /* 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. */
- {
- 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;
-}
+ 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);
-/**
- * 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)
+ if (TALER_KYCLOGIC_STATUS_SUCCESS == status)
{
- 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)
+ enum GNUNET_DB_QueryStatus qs;
+
+ 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;
}
- 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)
+ else
{
- 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;
-
- eh = curl_easy_init ();
- if (NULL == eh)
+ enum GNUNET_DB_QueryStatus qs;
+ struct GNUNET_TIME_Absolute expiration;
+
+ 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;
}
- }
+ 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 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");
+ }
- 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;
+ kpc->suspended = true;
+ GNUNET_CONTAINER_DLL_insert (kpc_head,
+ kpc_tail,
+ kpc);
+ MHD_suspend_connection (rc->connection);
+ return MHD_YES;
}
+ if (NULL == kpc->response)
{
- struct MHD_Response *response;
- MHD_RESULT res;
-
- response = MHD_create_response_from_buffer (0,
- "",
- MHD_RESPMEM_PERSISTENT);
- if (NULL == response)
- {
- GNUNET_break (0);
- return MHD_NO;
- }
- 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;
+ 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);
}
diff --git a/src/exchange/taler-exchange-httpd_kyc-wallet.c b/src/exchange/taler-exchange-httpd_kyc-wallet.c
index 0d92efd3..a043de6f 100644
--- a/src/exchange/taler-exchange-httpd_kyc-wallet.c
+++ b/src/exchange/taler-exchange-httpd_kyc-wallet.c
@@ -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,16 +39,55 @@ 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
* logic MUST NOT queue a MHD response. IF it returns an hard error, the
@@ -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));
}
diff --git a/src/exchange/taler-exchange-httpd_purses_merge.c b/src/exchange/taler-exchange-httpd_purses_merge.c
index 07bcff17..25d91e1b 100644
--- a/src/exchange/taler-exchange-httpd_purses_merge.c
+++ b/src/exchange/taler-exchange-httpd_purses_merge.c
@@ -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,
'/');
diff --git a/src/exchange/taler-exchange-httpd_withdraw.c b/src/exchange/taler-exchange-httpd_withdraw.c
index ee2f410d..255f7d94 100644
--- a/src/exchange/taler-exchange-httpd_withdraw.c
+++ b/src/exchange/taler-exchange-httpd_withdraw.c
@@ -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"
@@ -45,15 +46,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.
*/
struct TALER_BlindedPlanchet blinded_planchet;
@@ -68,10 +60,67 @@ 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
* logic MUST NOT queue a MHD response. IF it returns an hard error,
@@ -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;