summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorChristian Grothoff <christian@grothoff.org>2021-10-14 21:06:47 +0200
committerChristian Grothoff <christian@grothoff.org>2021-10-14 21:06:47 +0200
commitca12adced4ab5a54d7ee25b40b49cd034b920cc8 (patch)
treec181ebd5850cd907ca73a078c2050859c113454c /src
parent7d62fa065bba408860a79fe121a62ef8f515159c (diff)
downloadexchange-ca12adced4ab5a54d7ee25b40b49cd034b920cc8.tar.gz
exchange-ca12adced4ab5a54d7ee25b40b49cd034b920cc8.tar.bz2
exchange-ca12adced4ab5a54d7ee25b40b49cd034b920cc8.zip
-basic logic for withdraw KYC checks
Diffstat (limited to 'src')
-rw-r--r--src/auditor/taler-helper-auditor-reserves.c5
-rw-r--r--src/exchange/taler-exchange-httpd_withdraw.c113
-rw-r--r--src/exchangedb/plugin_exchangedb_postgres.c122
-rw-r--r--src/include/taler_exchangedb_plugin.h33
4 files changed, 265 insertions, 8 deletions
diff --git a/src/auditor/taler-helper-auditor-reserves.c b/src/auditor/taler-helper-auditor-reserves.c
index aa9c241bb..c27574d12 100644
--- a/src/auditor/taler-helper-auditor-reserves.c
+++ b/src/auditor/taler-helper-auditor-reserves.c
@@ -1083,10 +1083,13 @@ verify_reserve_balance (void *cls,
internal audit, as otherwise the balance of the 'reserves' table
is not replicated at the auditor. */
struct TALER_EXCHANGEDB_Reserve reserve;
+ struct TALER_EXCHANGEDB_KycStatus kyc;
reserve.pub = rs->reserve_pub;
qs = TALER_ARL_edb->reserves_get (TALER_ARL_edb->cls,
- &reserve);
+ &reserve,
+ &kyc);
+ // FIXME: figure out what to do with KYC status!
if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs)
{
/* If the exchange doesn't have this reserve in the summary, it
diff --git a/src/exchange/taler-exchange-httpd_withdraw.c b/src/exchange/taler-exchange-httpd_withdraw.c
index ca5618af6..4839ec97a 100644
--- a/src/exchange/taler-exchange-httpd_withdraw.c
+++ b/src/exchange/taler-exchange-httpd_withdraw.c
@@ -129,10 +129,45 @@ struct WithdrawContext
*/
struct TALER_EXCHANGEDB_CollectableBlindcoin collectable;
+ /**
+ * KYC status for the operation.
+ */
+ struct TALER_EXCHANGEDB_KycStatus kyc;
+
+ /**
+ * Set to true if the operation was denied due to
+ * failing @e kyc checks.
+ */
+ bool kyc_denied;
+
};
/**
+ * Function called with another amount that was
+ * already withdrawn. Accumulates all amounts in
+ * @a cls.
+ *
+ * @param[in,out] cls a `struct TALER_Amount`
+ * @param val value to add to @a cls
+ */
+static void
+accumulate_withdraws (void *cls,
+ const struct TALER_Amount *val)
+{
+ struct TALER_Amount *acc = cls;
+
+ if (GNUNET_OK !=
+ TALER_amount_is_valid (acc))
+ return; /* ignore */
+ GNUNET_break (0 <=
+ TALER_amount_add (acc,
+ acc,
+ val));
+}
+
+
+/**
* 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,
@@ -165,7 +200,6 @@ withdraw_transaction (void *cls,
struct TALER_EXCHANGEDB_Reserve r;
enum GNUNET_DB_QueryStatus qs;
struct TALER_DenominationSignature denom_sig;
- struct TALER_EXCHANGEDB_KycStatus kyc;
#if OPTIMISTIC_SIGN
/* store away optimistic signature to protect
@@ -211,7 +245,7 @@ withdraw_transaction (void *cls,
TALER_B2S (&r.pub));
qs = TEH_plugin->reserves_get (TEH_plugin->cls,
&r,
- &kyc);
+ &wc->kyc);
if (0 > qs)
{
if (GNUNET_DB_STATUS_HARD_ERROR == qs)
@@ -270,11 +304,60 @@ withdraw_transaction (void *cls,
return GNUNET_DB_STATUS_HARD_ERROR;
}
- if ( (! kyc.ok) &&
- (TEH_KYC_NONE != TEH_kyc_config.mode) )
+ if ( (! wc->kyc.ok) &&
+ (TEH_KYC_NONE != TEH_kyc_config.mode) &&
+ (TALER_EXCHANGEDB_KYC_W2W == wc->kyc.type) )
+ {
+ /* Wallet-to-wallet payments _always_ require KYC */
+ wc->kyc_denied = true;
+ return qs;
+ }
+ if ( (! wc->kyc.ok) &&
+ (TEH_KYC_NONE != TEH_kyc_config.mode) &&
+ (TALER_EXCHANGEDB_KYC_WITHDRAW == wc->kyc.type) &&
+ (! GNUNET_TIME_relative_is_zero (TEH_kyc_config.withdraw_period)) )
{
- // FIXME: check if we are above the limit
- // for KYC, and if so, deny the transaction!
+ /* Withdraws require KYC if above threshold */
+ struct TALER_Amount acc;
+ enum GNUNET_DB_QueryStatus qs2;
+
+ TALER_amount_set_zero (TEH_currency,
+ &acc);
+ accumulate_withdraws (&acc,
+ &wc->amount_required);
+ qs2 = TEH_plugin->select_withdraw_amounts_by_account (
+ TEH_plugin->cls,
+ &wc->wsrd.reserve_pub,
+ TEH_kyc_config.withdraw_period,
+ &accumulate_withdraws,
+ &acc);
+ 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,
+ "withdraw details");
+ return qs2;
+ }
+
+ if (GNUNET_OK !=
+ TALER_amount_is_valid (&acc))
+ {
+ GNUNET_break (0);
+ *mhd_ret = TALER_MHD_reply_with_ec (connection,
+ TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
+ NULL);
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+ if (1 == /* 1: acc > withdraw_limit */
+ TALER_amount_cmp (&acc,
+ &TEH_kyc_config.withdraw_limit))
+ {
+ wc->kyc_denied = true;
+ return qs;
+ }
}
/* Balance is good, sign the coin! */
@@ -338,6 +421,9 @@ TEH_handler_withdraw (struct TEH_RequestContext *rc,
enum TALER_ErrorCode ec;
struct TEH_DenominationKey *dk;
+ memset (&wc,
+ 0,
+ sizeof (wc));
if (GNUNET_OK !=
GNUNET_STRINGS_string_to_data (args[0],
strlen (args[0]),
@@ -480,6 +566,7 @@ TEH_handler_withdraw (struct TEH_RequestContext *rc,
#endif
/* run transaction and sign (if not optimistically signed before) */
+ wc.kyc_denied = false;
{
MHD_RESULT mhd_ret;
@@ -499,9 +586,21 @@ TEH_handler_withdraw (struct TEH_RequestContext *rc,
}
}
- /* Clean up and send back final (positive) response */
+ /* Clean up and send back final response */
GNUNET_JSON_parse_free (spec);
+ if (wc.kyc_denied)
+ {
+ if (NULL != wc.collectable.sig.rsa_signature)
+ GNUNET_CRYPTO_rsa_signature_free (wc.collectable.sig.rsa_signature);
+
+ return TALER_MHD_REPLY_JSON_PACK (
+ rc->connection,
+ MHD_HTTP_ACCEPTED,
+ GNUNET_JSON_pack_uint64 ("payment_target_uuid",
+ wc.kyc.payment_target_uuid));
+ }
+
{
MHD_RESULT ret;
diff --git a/src/exchangedb/plugin_exchangedb_postgres.c b/src/exchangedb/plugin_exchangedb_postgres.c
index 34b785e7f..eda6468ba 100644
--- a/src/exchangedb/plugin_exchangedb_postgres.c
+++ b/src/exchangedb/plugin_exchangedb_postgres.c
@@ -4389,6 +4389,126 @@ postgres_get_reserve_history (void *cls,
/**
+ * Closure for withdraw_amount_by_account_cb()
+ */
+struct WithdrawAmountByAccountContext
+{
+ /**
+ * Function to call on each amount.
+ */
+ TALER_EXCHANGEDB_WithdrawHistoryCallback cb;
+
+ /**
+ * Closure for @e cb
+ */
+ void *cb_cls;
+
+ /**
+ * Our plugin's context.
+ */
+ struct PostgresClosure *pg;
+
+ /**
+ * Set to true on failures.
+ */
+ bool failed;
+};
+
+
+/**
+ * Helper function for #postgres_select_withdraw_amounts_by_account().
+ * To be called with the results of a SELECT statement
+ * that has returned @a num_results results.
+ *
+ * @param cls closure of type `struct WithdrawAmountByAccountContext *`
+ * @param result the postgres result
+ * @param num_results the number of results in @a result
+ */
+static void
+withdraw_amount_by_account_cb (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct WithdrawAmountByAccountContext *wac = cls;
+ struct PostgresClosure *pg = wac->pg;
+
+ for (unsigned int i = 0; num_results; i++)
+ {
+ struct TALER_Amount val;
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ TALER_PQ_RESULT_SPEC_AMOUNT ("val",
+ &val),
+ GNUNET_PQ_result_spec_end
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ wac->failed = true;
+ return;
+ }
+ wac->cb (wac->cb_cls,
+ &val);
+ }
+}
+
+
+/**
+ * Find out all of the amounts that have been withdrawn
+ * so far from the same bank account that created the
+ * given reserve.
+ *
+ * @param cls the `struct PostgresClosure` with the plugin-specific state
+ * @param reserve_pub reserve to select withdrawals by
+ * @param duration how far back should we select withdrawals
+ * @param cb function to call on each amount withdrawn
+ * @param cb_cls closure for @a cb
+ * @return transaction status
+ */
+static enum GNUNET_DB_QueryStatus
+postgres_select_withdraw_amounts_by_account (
+ void *cls,
+ const struct TALER_ReservePublicKeyP *reserve_pub,
+ struct GNUNET_TIME_Relative duration,
+ TALER_EXCHANGEDB_WithdrawHistoryCallback cb,
+ void *cb_cls)
+{
+ struct PostgresClosure *pg = cls;
+ struct WithdrawAmountByAccountContext wac = {
+ .pg = pg,
+ .cb = cb,
+ .cb_cls = cb_cls
+ };
+ struct GNUNET_TIME_Absolute start
+ = GNUNET_TIME_absolute_subtract (GNUNET_TIME_absolute_get (),
+ duration);
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (reserve_pub),
+ GNUNET_PQ_query_param_absolute_time (&start),
+ GNUNET_PQ_query_param_end
+ };
+ enum GNUNET_DB_QueryStatus qs;
+
+ qs = GNUNET_PQ_eval_prepared_multi_select (
+ pg->conn,
+ "select_XXX",
+ params,
+ &withdraw_amount_by_account_cb,
+ &wac);
+
+ if (wac.failed)
+ {
+ GNUNET_break (0);
+ qs = GNUNET_DB_STATUS_HARD_ERROR;
+ }
+ return qs;
+}
+
+
+/**
* Check if we have the specified deposit already in the database.
*
* @param cls the `struct PostgresClosure` with the plugin-specific state
@@ -10957,6 +11077,8 @@ libtaler_plugin_exchangedb_postgres_init (void *cls)
plugin->get_withdraw_info = &postgres_get_withdraw_info;
plugin->insert_withdraw_info = &postgres_insert_withdraw_info;
plugin->get_reserve_history = &postgres_get_reserve_history;
+ plugin->select_withdraw_amounts_by_account
+ = &postgres_select_withdraw_amounts_by_account;
plugin->free_reserve_history = &common_free_reserve_history;
plugin->count_known_coins = &postgres_count_known_coins;
plugin->ensure_coin_known = &postgres_ensure_coin_known;
diff --git a/src/include/taler_exchangedb_plugin.h b/src/include/taler_exchangedb_plugin.h
index d94a985d8..34196aadd 100644
--- a/src/include/taler_exchangedb_plugin.h
+++ b/src/include/taler_exchangedb_plugin.h
@@ -2012,6 +2012,18 @@ typedef enum GNUNET_GenericReturnValue
/**
+ * Function called with the amounts historically
+ * withdrawn from the same origin account.
+ *
+ * @param cls closure
+ * @param val one of the withdrawn amounts
+ */
+typedef void
+(*TALER_EXCHANGEDB_WithdrawHistoryCallback)(
+ void *cls,
+ const struct TALER_Amount *val);
+
+/**
* Function called with details about expired reserves.
*
* @param cls closure
@@ -2451,6 +2463,27 @@ struct TALER_EXCHANGEDB_Plugin
/**
+ * Find out all of the amounts that have been withdrawn
+ * so far from the same bank account that created the
+ * given reserve.
+ *
+ * @param cls closure
+ * @param reserve_pub reserve to select withdrawals by
+ * @param duration how far back should we select withdrawals
+ * @param cb function to call on each amount withdrawn
+ * @param cb_cls closure for @a cb
+ * @return transaction status
+ */
+ enum GNUNET_DB_QueryStatus
+ (*select_withdraw_amounts_by_account)(
+ void *cls,
+ const struct TALER_ReservePublicKeyP *reserve_pub,
+ struct GNUNET_TIME_Relative duration,
+ TALER_EXCHANGEDB_WithdrawHistoryCallback cb,
+ void *cb_cls);
+
+
+ /**
* Free memory associated with the given reserve history.
*
* @param cls the @e cls of this struct with the plugin-specific state