diff options
author | Christian Grothoff <christian@grothoff.org> | 2024-09-10 15:47:31 +0200 |
---|---|---|
committer | Christian Grothoff <christian@grothoff.org> | 2024-09-10 15:47:31 +0200 |
commit | a8f851315ceb958ae7e1ee730f38384b5dab0799 (patch) | |
tree | 2a21a8f3067a78865b37861e5f6a8ddadcdf886e | |
parent | 044c308a71b692977b02556cf9633fb5bb2f094a (diff) |
add logic to enforce hard limit on order creation
-rw-r--r-- | src/backend/taler-merchant-httpd_exchanges.c | 122 | ||||
-rw-r--r-- | src/backend/taler-merchant-httpd_exchanges.h | 9 | ||||
-rw-r--r-- | src/backend/taler-merchant-httpd_post-orders-ID-pay.c | 11 | ||||
-rw-r--r-- | src/backend/taler-merchant-httpd_private-post-orders.c | 50 | ||||
-rw-r--r-- | src/backend/taler-merchant-httpd_private-post-products.c | 2 | ||||
-rw-r--r-- | src/backenddb/Makefile.am | 1 | ||||
-rw-r--r-- | src/backenddb/pg_get_kyc_limits.c | 75 | ||||
-rw-r--r-- | src/backenddb/pg_get_kyc_limits.h | 48 | ||||
-rw-r--r-- | src/backenddb/plugin_merchantdb_postgres.c | 3 | ||||
-rw-r--r-- | src/include/taler_merchantdb_plugin.h | 21 |
10 files changed, 331 insertions, 11 deletions
diff --git a/src/backend/taler-merchant-httpd_exchanges.c b/src/backend/taler-merchant-httpd_exchanges.c index d36ce581..f4ebaef2 100644 --- a/src/backend/taler-merchant-httpd_exchanges.c +++ b/src/backend/taler-merchant-httpd_exchanges.c @@ -661,13 +661,29 @@ TMH_EXCHANGES_lookup_wire_fee ( enum GNUNET_GenericReturnValue TMH_exchange_check_debit ( + const char *instance_id, const struct TMH_Exchange *exchange, - const struct TMH_WireMethod *wm) + const struct TMH_WireMethod *wm, + struct TALER_Amount *max_amount) { const struct TALER_EXCHANGE_Keys *keys = exchange->keys; + bool account_ok = false; + bool have_kyc = false; + struct TALER_Amount kyc_limit; + bool unlimited = false; if (NULL == keys) return GNUNET_SYSERR; + if (0 != strcasecmp (keys->currency, + max_amount->currency)) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + GNUNET_assert (GNUNET_OK == + TALER_amount_set_zero ( + max_amount->currency, + &kyc_limit)); /* For all accounts of the exchange */ for (unsigned int i = 0; i<keys->accounts_len; i++) { @@ -685,9 +701,109 @@ TMH_exchange_check_debit ( false, /* debit */ wm->payto_uri)) continue; - return GNUNET_YES; + account_ok = true; + /* Check legitimization limits we have with this + account at this exchange, if we have any, apply them */ + { + bool kyc_ok = false; + json_t *jlimits = NULL; + enum GNUNET_DB_QueryStatus qs; + + qs = TMH_db->get_kyc_limits (TMH_db->cls, + wm->payto_uri, + instance_id, + exchange->url, + &kyc_ok, + &jlimits); + GNUNET_break (qs >= 0); + if (qs <= 0) + { + unlimited = true; + continue; + } + if (NULL == jlimits) + { + unlimited = true; + } + else + { + json_t *jlimit; + size_t idx; + bool found = false; + + json_array_foreach (jlimits, idx, jlimit) + { + enum TALER_KYCLOGIC_KycTriggerEvent ot; + struct TALER_Amount threshold; + bool soft_limit = false; + struct GNUNET_JSON_Specification spec[] = { + TALER_JSON_spec_kycte ("operation_type", + &ot), + TALER_JSON_spec_amount_any ("threshold", + &threshold), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_bool ("soft_limit", + &soft_limit), + NULL), + GNUNET_JSON_spec_end () + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (jlimit, + spec, + NULL, NULL)) + { + GNUNET_break (0); + continue; + } + if (soft_limit) + continue; + if ( (TALER_KYCLOGIC_KYC_TRIGGER_DEPOSIT != ot) && + (TALER_KYCLOGIC_KYC_TRIGGER_TRANSACTION != ot) ) + continue; + found = true; + TALER_amount_max (&kyc_limit, + &kyc_limit, + &threshold); + } + json_decref (jlimit); + if (! found) + unlimited = true; + } + if (kyc_ok) + have_kyc = true; + } + } + if (! unlimited) + TALER_amount_min (max_amount, + max_amount, + &kyc_limit); + /* apply both deposit and transaction limits */ + if ( (! have_kyc) && + (TALER_EXCHANGE_keys_evaluate_zero_limits ( + keys, + TALER_KYCLOGIC_KYC_TRIGGER_DEPOSIT) || + TALER_EXCHANGE_keys_evaluate_zero_limits ( + keys, + TALER_KYCLOGIC_KYC_TRIGGER_TRANSACTION)) ) + { + GNUNET_assert (GNUNET_OK == + TALER_amount_set_zero ( + max_amount->currency, + max_amount)); + } + else + { + TALER_EXCHANGE_keys_evaluate_hard_limits ( + keys, + TALER_KYCLOGIC_KYC_TRIGGER_DEPOSIT, + max_amount); + TALER_EXCHANGE_keys_evaluate_hard_limits ( + keys, + TALER_KYCLOGIC_KYC_TRIGGER_TRANSACTION, + max_amount); } - return GNUNET_NO; + return account_ok ? GNUNET_YES : GNUNET_NO; } diff --git a/src/backend/taler-merchant-httpd_exchanges.h b/src/backend/taler-merchant-httpd_exchanges.h index f4de947a..5c70494c 100644 --- a/src/backend/taler-merchant-httpd_exchanges.h +++ b/src/backend/taler-merchant-httpd_exchanges.h @@ -191,14 +191,21 @@ TMH_EXCHANGES_lookup_wire_fee ( * wm. Checks that both @a ex is trusted and that @a ex allows wire transfers * into the account given in @a wm. * + * @param instance_id the instance to check for * @param exchange the exchange to check * @param wm the wire method to check with + * @param[in,out] maximum amount we may deposit at this + * exchange; input is an existing maximum, that + * can be lowered by this function due to transaction + * limits and deposit limits of the exchange * @return #GNUNET_OK if such a debit can happen */ enum GNUNET_GenericReturnValue TMH_exchange_check_debit ( + const char *instance_id, const struct TMH_Exchange *exchange, - const struct TMH_WireMethod *wm); + const struct TMH_WireMethod *wm, + struct TALER_Amount *max_amount); /** diff --git a/src/backend/taler-merchant-httpd_post-orders-ID-pay.c b/src/backend/taler-merchant-httpd_post-orders-ID-pay.c index 59519206..349b4d24 100644 --- a/src/backend/taler-merchant-httpd_post-orders-ID-pay.c +++ b/src/backend/taler-merchant-httpd_post-orders-ID-pay.c @@ -1210,6 +1210,7 @@ process_pay_with_keys ( struct PayContext *pc = eg->pc; struct TMH_HandlerContext *hc = pc->hc; unsigned int group_size; + struct TALER_Amount max_amount; eg->fo = NULL; pc->pending_at_eg--; @@ -1239,10 +1240,14 @@ process_pay_with_keys ( return; } - + /* FIXME: actually enforce max_amount below! */ + max_amount = eg->total; if (GNUNET_OK != - TMH_exchange_check_debit (exchange, - pc->wm)) + TMH_exchange_check_debit ( + pc->hc->instance->settings.id, + exchange, + pc->wm, + &max_amount)) { if (eg->tried_force_keys) { diff --git a/src/backend/taler-merchant-httpd_private-post-orders.c b/src/backend/taler-merchant-httpd_private-post-orders.c index 8a02625b..4db271ff 100644 --- a/src/backend/taler-merchant-httpd_private-post-orders.c +++ b/src/backend/taler-merchant-httpd_private-post-orders.c @@ -441,6 +441,13 @@ struct OrderContext * @e order. */ struct TALER_Amount max_stefan_fee; + + /** + * Maximum amount that could be paid over all + * available exchanges. Used to determine if this + * order creation requests exceeds legal limits. + */ + struct TALER_Amount total_exchange_limit; } set_exchanges; /** @@ -1910,13 +1917,25 @@ get_acceptable (void *cls, unsigned int priority = 42; /* make compiler happy */ json_t *j_exchange; enum GNUNET_GenericReturnValue res; - - res = TMH_exchange_check_debit (exchange, - oc->add_payment_details.wm); + struct TALER_Amount max_amount; + + max_amount = oc->parse_order.brutto; + res = TMH_exchange_check_debit ( + oc->hc->instance->settings.id, + exchange, + oc->add_payment_details.wm, + &max_amount); GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Exchange %s evaluated at %d\n", url, res); + if (TALER_amount_is_zero (&max_amount)) + { + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Exchange %s deposit limit is zero, skipping it\n", + url); + return; + } switch (res) { case GNUNET_OK: @@ -1936,11 +1955,18 @@ get_acceptable (void *cls, priority = 768; /* stale, no accounts yet */ break; } + GNUNET_break (0 <= + TALER_amount_add ( + &oc->set_exchanges.total_exchange_limit, + &oc->set_exchanges.total_exchange_limit, + &max_amount)); j_exchange = GNUNET_JSON_PACK ( GNUNET_JSON_pack_string ("url", url), GNUNET_JSON_pack_uint64 ("priority", priority), + TALER_JSON_pack_amount ("max_contribution", + &max_amount), GNUNET_JSON_pack_data_auto ("master_pub", TMH_EXCHANGES_get_master_pub (exchange))); GNUNET_assert (NULL != j_exchange); @@ -2052,6 +2078,10 @@ set_exchanges (struct OrderContext *oc) { oc->set_exchanges.exchanges = json_array (); GNUNET_assert (NULL != oc->set_exchanges.exchanges); + GNUNET_assert ( + GNUNET_OK == + TALER_amount_set_zero (oc->parse_order.brutto.currency, + &oc->set_exchanges.total_exchange_limit)); TMH_exchange_get_trusted (&get_exchange_keys, oc); } @@ -2089,6 +2119,20 @@ set_exchanges (struct OrderContext *oc) oc->add_payment_details.wm->wire_method); return false; } + if (1 == + TALER_amount_cmp (&oc->parse_order.brutto, + &oc->set_exchanges.total_exchange_limit)) + { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Cannot create order: %s is the sum of hard limits from supported exchanges\n", + TALER_amount2s (&oc->set_exchanges.total_exchange_limit)); + reply_with_error ( + oc, + MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS, + TALER_EC_MERCHANT_PRIVATE_POST_ORDERS_AMOUNT_EXCEEDS_LEGAL_LIMITS, + oc->add_payment_details.wm->wire_method); + return false; + } if (! oc->set_exchanges.exchange_good) { GNUNET_log (GNUNET_ERROR_TYPE_WARNING, diff --git a/src/backend/taler-merchant-httpd_private-post-products.c b/src/backend/taler-merchant-httpd_private-post-products.c index 3edc0c16..88042c5f 100644 --- a/src/backend/taler-merchant-httpd_private-post-products.c +++ b/src/backend/taler-merchant-httpd_private-post-products.c @@ -177,7 +177,7 @@ TMH_private_post_products (const struct TMH_RequestHandler *rh, } if (NULL == pd.image) - pd.image = ""; + pd.image = (char *) ""; if (! TMH_image_data_url_valid (pd.image)) { GNUNET_break_op (0); diff --git a/src/backenddb/Makefile.am b/src/backenddb/Makefile.am index 792bfb53..af79c4f6 100644 --- a/src/backenddb/Makefile.am +++ b/src/backenddb/Makefile.am @@ -104,6 +104,7 @@ libtaler_plugin_merchantdb_postgres_la_SOURCES = \ pg_insert_instance.h pg_insert_instance.c \ pg_account_kyc_set_status.h pg_account_kyc_set_status.c \ pg_get_kyc_status.h pg_get_kyc_status.c \ + pg_get_kyc_limits.h pg_get_kyc_limits.c \ pg_account_kyc_get_status.h pg_account_kyc_get_status.c \ pg_delete_instance_private_key.h pg_delete_instance_private_key.c \ pg_purge_instance.h pg_purge_instance.c \ diff --git a/src/backenddb/pg_get_kyc_limits.c b/src/backenddb/pg_get_kyc_limits.c new file mode 100644 index 00000000..d6249e56 --- /dev/null +++ b/src/backenddb/pg_get_kyc_limits.c @@ -0,0 +1,75 @@ +/* + This file is part of TALER + Copyright (C) 2024 Taler Systems SA + + TALER is free software; you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + */ +/** + * @file backenddb/pg_get_kyc_limits.c + * @brief Implementation of the get_kyc_limits function for Postgres + * @author Christian Grothoff + */ +#include "platform.h" +#include <taler/taler_error_codes.h> +#include <taler/taler_dbevents.h> +#include <taler/taler_pq_lib.h> +#include "pg_get_kyc_limits.h" +#include "pg_helper.h" + +enum GNUNET_DB_QueryStatus +TMH_PG_get_kyc_limits ( + void *cls, + const char *merchant_account_uri, + const char *instance_id, + const char *exchange_url, + bool *kyc_ok, + json_t **jlimits) +{ + struct PostgresClosure *pg = cls; + struct GNUNET_PQ_QueryParam params[] = { + GNUNET_PQ_query_param_string (merchant_account_uri), + GNUNET_PQ_query_param_string (instance_id), + GNUNET_PQ_query_param_string (exchange_url), + GNUNET_PQ_query_param_end + }; + struct GNUNET_PQ_ResultSpec rs[] = { + GNUNET_PQ_result_spec_bool ("kyc_ok", + kyc_ok), + GNUNET_PQ_result_spec_allow_null ( + TALER_PQ_result_spec_json ("jaccount_limits", + jlimits), + NULL), + GNUNET_PQ_result_spec_end + }; + + check_connection (pg); + PREPARE (pg, + "get_kyc_limits", + "SELECT" + " mk.kyc_ok" + ",mk.jaccount_limits" + " FROM merchant_kyc mk" + " WHERE mk.exchange_url=$3" + " AND mk.account_serial=" + " (SELECT account_serial" + " FROM merchant_accounts" + " WHERE payto_uri=$1" + " AND merchant_serial=" + " (SELECT merchant_serial" + " FROM merchant_instances" + " WHERE merchant_id=$2));"); + *jlimits = NULL; + return GNUNET_PQ_eval_prepared_singleton_select (pg->conn, + "get_kyc_limits", + params, + rs); +} diff --git a/src/backenddb/pg_get_kyc_limits.h b/src/backenddb/pg_get_kyc_limits.h new file mode 100644 index 00000000..b630003e --- /dev/null +++ b/src/backenddb/pg_get_kyc_limits.h @@ -0,0 +1,48 @@ +/* + This file is part of TALER + Copyright (C) 2024 Taler Systems SA + + TALER is free software; you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + */ +/** + * @file backenddb/pg_get_kyc_limits.h + * @brief implementation of the get_kyc_limits function for Postgres + * @author Christian Grothoff + */ +#ifndef PG_GET_KYC_LIMITS_H +#define PG_GET_KYC_LIMITS_H + +#include <taler/taler_util.h> +#include <taler/taler_json_lib.h> +#include "taler_merchantdb_plugin.h" + +/** + * Check an account's KYC limits at an exchange. + * + * @param cls closure + * @param merchant_payto_uri merchant backend instance ID + * @param instance_id the instance for which to check + * @param exchange_url base URL of the exchange + * @param[out] kyc_ok true if no urgent KYC work must be done for this account + * @param[out] jlimits set to JSON array with AccountLimits, NULL if unknown (and likely defaults apply or KYC auth is urgently needed, see @a auth_ok) + * @return database result code + */ +enum GNUNET_DB_QueryStatus +TMH_PG_get_kyc_limits ( + void *cls, + const char *merchant_account_uri, + const char *instance_id, + const char *exchange_url, + bool *kyc_ok, + json_t **jlimits); + +#endif diff --git a/src/backenddb/plugin_merchantdb_postgres.c b/src/backenddb/plugin_merchantdb_postgres.c index 690f066e..86dec660 100644 --- a/src/backenddb/plugin_merchantdb_postgres.c +++ b/src/backenddb/plugin_merchantdb_postgres.c @@ -33,6 +33,7 @@ #include "pg_helper.h" #include "pg_insert_otp.h" #include "pg_get_kyc_status.h" +#include "pg_get_kyc_limits.h" #include "pg_delete_otp.h" #include "pg_update_otp.h" #include "pg_select_otp.h" @@ -584,6 +585,8 @@ libtaler_plugin_merchantdb_postgres_init (void *cls) = &TMH_PG_select_category_by_name; plugin->get_kyc_status = &TMH_PG_get_kyc_status; + plugin->get_kyc_limits + = &TMH_PG_get_kyc_limits; plugin->select_category = &TMH_PG_select_category; plugin->update_category diff --git a/src/include/taler_merchantdb_plugin.h b/src/include/taler_merchantdb_plugin.h index 41d7570d..0c301a0a 100644 --- a/src/include/taler_merchantdb_plugin.h +++ b/src/include/taler_merchantdb_plugin.h @@ -1772,6 +1772,27 @@ struct TALER_MERCHANTDB_Plugin /** + * Check an account's KYC limits at an exchange. + * + * @param cls closure + * @param merchant_payto_uri merchant backend instance ID + * @param instance_id the instance for which to check + * @param exchange_url base URL of the exchange + * @param[out] kyc_ok true if no urgent KYC work must be done for this account + * @param[out] jlimits set to JSON array with AccountLimits, NULL if unknown (and likely defaults apply or KYC auth is urgently needed, see @a auth_ok) + * @return database result code + */ + enum GNUNET_DB_QueryStatus + (*get_kyc_limits)( + void *cls, + const char *merchant_account_uri, + const char *instance_id, + const char *exchange_url, + bool *kyc_ok, + json_t **jlimits); + + + /** * Update an instance's account's KYC status. * * @param cls closure |