diff options
author | Christian Grothoff <christian@grothoff.org> | 2024-09-01 12:20:53 +0200 |
---|---|---|
committer | Christian Grothoff <christian@grothoff.org> | 2024-09-01 12:21:00 +0200 |
commit | 20a950c1c385c08f6bd6ee63460a750892a42b12 (patch) | |
tree | a8d0566bdb5bd8e470b17d21f5dc5d5a9898ae1c | |
parent | 3d7e13f2fb2c12c00e4c5123219365c802e4e379 (diff) |
implement per-transaction limits
-rw-r--r-- | src/backend/taler-merchant-httpd_exchanges.c | 29 | ||||
-rw-r--r-- | src/backend/taler-merchant-httpd_exchanges.h | 17 | ||||
-rw-r--r-- | src/backend/taler-merchant-httpd_post-orders-ID-pay.c | 64 | ||||
-rw-r--r-- | src/backend/taler-merchant-httpd_private-patch-products-ID.c | 8 | ||||
-rw-r--r-- | src/backend/taler-merchant-httpd_private-post-orders-ID-refund.c | 80 | ||||
-rw-r--r-- | src/backend/taler-merchant-webhook.c | 22 |
6 files changed, 157 insertions, 63 deletions
diff --git a/src/backend/taler-merchant-httpd_exchanges.c b/src/backend/taler-merchant-httpd_exchanges.c index 918bc48f..6bcbd5c8 100644 --- a/src/backend/taler-merchant-httpd_exchanges.c +++ b/src/backend/taler-merchant-httpd_exchanges.c @@ -1651,6 +1651,35 @@ update_exchange_keys (void *cls, } +bool +TMH_EXCHANGES_is_below_limit ( + const struct TALER_EXCHANGE_Keys *keys, + enum TALER_KYCLOGIC_KycTriggerEvent operation_type, + const struct TALER_Amount *amount) +{ + if (NULL == keys) + { + /* should only be called after we have keys! */ + GNUNET_break (0); + return false; + } + for (unsigned int i = 0; i<keys->hard_limits_length; i++) + { + const struct TALER_EXCHANGE_AccountLimit *al + = &keys->hard_limits[i]; + + if (operation_type != al->operation_type) + continue; + if (-1 == + TALER_amount_cmp (&al->threshold, + amount)) + /* -1: threshold < amount */ + return false; + } + return true; +} + + enum GNUNET_GenericReturnValue TMH_EXCHANGES_init (const struct GNUNET_CONFIGURATION_Handle *cfg) { diff --git a/src/backend/taler-merchant-httpd_exchanges.h b/src/backend/taler-merchant-httpd_exchanges.h index 892843f6..f4de947a 100644 --- a/src/backend/taler-merchant-httpd_exchanges.h +++ b/src/backend/taler-merchant-httpd_exchanges.h @@ -153,6 +153,23 @@ TMH_EXCHANGES_get_currency ( /** + * Check if the given operation and amount would + * violate any hard limits of the exchange. + * Only useful for transaction and refund limits. + * + * @param keys the keys of the exchange to check limit for + * @param operation_type the kind of operation we perform + * @param amount the amount we want to transact + * @return true if this is allowed + */ +bool +TMH_EXCHANGES_is_below_limit ( + const struct TALER_EXCHANGE_Keys *keys, + enum TALER_KYCLOGIC_KycTriggerEvent operation_type, + const struct TALER_Amount *amount); + + +/** * Lookup current wire fee by @a exchange_url and @a wire_method. * * @param exchange the exchange to check 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 c9fbd34e..049db1f0 100644 --- a/src/backend/taler-merchant-httpd_post-orders-ID-pay.c +++ b/src/backend/taler-merchant-httpd_post-orders-ID-pay.c @@ -332,6 +332,11 @@ struct ExchangeGroup const char *exchange_url; /** + * Total deposit amount in this exchange group. + */ + struct TALER_Amount total; + + /** * Wire fee that applies to this exchange for the * given payment context's wire method. */ @@ -1115,6 +1120,18 @@ process_pay_with_keys ( NULL); return; } + if (! TMH_EXCHANGES_is_below_limit (keys, + TALER_KYCLOGIC_KYC_TRIGGER_TRANSACTION, + &eg->total)) + { + GNUNET_break_op (0); + resume_pay_with_error ( + pc, + TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_EXCHANGE_TRANSACTION_LIMIT_VIOLATION, + eg->exchange_url); + return; + } + if (GNUNET_OK != TMH_exchange_check_debit (exchange, @@ -3146,7 +3163,6 @@ phase_parse_wallet_data (struct PayContext *pc) NULL), GNUNET_JSON_spec_end () }; - enum GNUNET_GenericReturnValue res; pc->choice_index = -1; if (NULL == pc->wallet_data) @@ -3154,17 +3170,21 @@ phase_parse_wallet_data (struct PayContext *pc) pc->phase = PP_CHECK_CONTRACT; return; } - res = TALER_MHD_parse_json_data (pc->connection, - pc->wallet_data, - spec); - if (GNUNET_YES != res) { - GNUNET_break_op (0); - pay_end (pc, - (GNUNET_NO == res) + enum GNUNET_GenericReturnValue res; + + res = TALER_MHD_parse_json_data (pc->connection, + pc->wallet_data, + spec); + if (GNUNET_YES != res) + { + GNUNET_break_op (0); + pay_end (pc, + (GNUNET_NO == res) ? MHD_YES : MHD_NO); - return; + return; + } } pc->token_envelopes_cnt = json_array_size (tokens_evs); @@ -3362,7 +3382,7 @@ phase_parse_pay (struct PayContext *pc) GNUNET_JSON_spec_end () }; enum GNUNET_GenericReturnValue res; - bool have_eg = false; + struct ExchangeGroup *eg = NULL; res = TALER_MHD_parse_json_data (pc->connection, coin, @@ -3418,21 +3438,37 @@ phase_parse_pay (struct PayContext *pc) strcmp (pc->egs[i]->exchange_url, exchange_url)) { - have_eg = true; + eg = pc->egs[i]; break; } } - if (! have_eg) + if (NULL == eg) { - struct ExchangeGroup *eg; - eg = GNUNET_new (struct ExchangeGroup); eg->pc = pc; eg->exchange_url = dc->exchange_url; + eg->total = dc->cdd.amount; GNUNET_array_append (pc->egs, pc->num_exchanges, eg); } + else + { + if (0 > + TALER_amount_add (&eg->total, + &eg->total, + &dc->cdd.amount)) + { + GNUNET_break_op (0); + pay_end (pc, + TALER_MHD_reply_with_error ( + pc->connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_AMOUNT_OVERFLOW, + "Overflow adding up amounts")); + return; + } + } } } diff --git a/src/backend/taler-merchant-httpd_private-patch-products-ID.c b/src/backend/taler-merchant-httpd_private-patch-products-ID.c index 6e50cced..81633051 100644 --- a/src/backend/taler-merchant-httpd_private-patch-products-ID.c +++ b/src/backend/taler-merchant-httpd_private-patch-products-ID.c @@ -261,16 +261,16 @@ TMH_private_patch_products_ID ( } if (-1 != no_cat) { - char cats[24]; + char cat_str[24]; - GNUNET_snprintf (cats, - sizeof (cats), + GNUNET_snprintf (cat_str, + sizeof (cat_str), "%llu", (unsigned long long) no_cat); ret = TALER_MHD_reply_with_error (connection, MHD_HTTP_NOT_FOUND, TALER_EC_MERCHANT_GENERIC_CATEGORY_UNKNOWN, - cats); + cat_str); goto cleanup; } if (no_product) diff --git a/src/backend/taler-merchant-httpd_private-post-orders-ID-refund.c b/src/backend/taler-merchant-httpd_private-post-orders-ID-refund.c index 67e1410b..1a7ffe37 100644 --- a/src/backend/taler-merchant-httpd_private-post-orders-ID-refund.c +++ b/src/backend/taler-merchant-httpd_private-post-orders-ID-refund.c @@ -35,6 +35,9 @@ */ #define MAX_RETRIES 5 +/* FIXME-9061: check exchange refund limits and return 403/ + MERCHANT_POST_ORDERS_ID_REFUND_EXCHANGE_TRANSACTION_LIMIT_VIOLATION + if they are violated! */ /** * Use database to notify other clients about the @@ -44,8 +47,9 @@ * @param amount the (total) refunded amount */ static void -trigger_refund_notification (struct TMH_HandlerContext *hc, - const struct TALER_Amount *amount) +trigger_refund_notification ( + struct TMH_HandlerContext *hc, + const struct TALER_Amount *amount) { const char *as; struct TMH_OrderRefundEventP refund_eh = { @@ -114,9 +118,10 @@ make_taler_refund_uri (struct MHD_Connection *connection, * @return MHD result code */ MHD_RESULT -TMH_private_post_orders_ID_refund (const struct TMH_RequestHandler *rh, - struct MHD_Connection *connection, - struct TMH_HandlerContext *hc) +TMH_private_post_orders_ID_refund ( + const struct TMH_RequestHandler *rh, + struct MHD_Connection *connection, + struct TMH_HandlerContext *hc) { struct TALER_Amount refund; const char *reason; @@ -162,15 +167,17 @@ TMH_private_post_orders_ID_refund (const struct TMH_RequestHandler *rh, if (qs < 0) { GNUNET_break (0); - return TALER_MHD_reply_with_error (connection, - MHD_HTTP_INTERNAL_SERVER_ERROR, - TALER_EC_GENERIC_DB_FETCH_FAILED, - "lookup_contract_terms"); + return TALER_MHD_reply_with_error ( + connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_GENERIC_DB_FETCH_FAILED, + "lookup_contract_terms"); } - return TALER_MHD_reply_with_error (connection, - MHD_HTTP_NOT_FOUND, - TALER_EC_MERCHANT_GENERIC_ORDER_UNKNOWN, - hc->infix); + return TALER_MHD_reply_with_error ( + connection, + MHD_HTTP_NOT_FOUND, + TALER_EC_MERCHANT_GENERIC_ORDER_UNKNOWN, + hc->infix); } if (GNUNET_OK != TALER_JSON_contract_hash (contract_terms, @@ -178,10 +185,11 @@ TMH_private_post_orders_ID_refund (const struct TMH_RequestHandler *rh, { GNUNET_break (0); json_decref (contract_terms); - return TALER_MHD_reply_with_error (connection, - MHD_HTTP_INTERNAL_SERVER_ERROR, - TALER_EC_GENERIC_FAILED_COMPUTE_JSON_HASH, - "Could not hash contract terms"); + return TALER_MHD_reply_with_error ( + connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_GENERIC_FAILED_COMPUTE_JSON_HASH, + "Could not hash contract terms"); } { struct GNUNET_JSON_Specification cspec[] = { @@ -311,24 +319,28 @@ TMH_private_post_orders_ID_refund (const struct TMH_RequestHandler *rh, GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "Refund amount %s is not in the currency of the original payment\n", TALER_amount2s (&refund)); - return TALER_MHD_reply_with_error (connection, - MHD_HTTP_CONFLICT, - TALER_EC_MERCHANT_GENERIC_CURRENCY_MISMATCH, - "Order was paid in a different currency"); + return TALER_MHD_reply_with_error ( + connection, + MHD_HTTP_CONFLICT, + TALER_EC_MERCHANT_GENERIC_CURRENCY_MISMATCH, + "Order was paid in a different currency") + ; case TALER_MERCHANTDB_RS_TOO_HIGH: GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "Refusing refund amount %s that is larger than original payment\n", TALER_amount2s (&refund)); - return TALER_MHD_reply_with_error (connection, - MHD_HTTP_CONFLICT, - TALER_EC_EXCHANGE_REFUND_INCONSISTENT_AMOUNT, - "Amount above payment"); + return TALER_MHD_reply_with_error ( + connection, + MHD_HTTP_CONFLICT, + TALER_EC_EXCHANGE_REFUND_INCONSISTENT_AMOUNT, + "Amount above payment"); case TALER_MERCHANTDB_RS_SOFT_ERROR: case TALER_MERCHANTDB_RS_HARD_ERROR: - return TALER_MHD_reply_with_error (connection, - MHD_HTTP_INTERNAL_SERVER_ERROR, - TALER_EC_GENERIC_DB_COMMIT_FAILED, - NULL); + return TALER_MHD_reply_with_error ( + connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_GENERIC_DB_COMMIT_FAILED, + NULL); case TALER_MERCHANTDB_RS_NO_SUCH_ORDER: /* We know the order exists from the "lookup_contract_terms" at the beginning; @@ -345,7 +357,6 @@ TMH_private_post_orders_ID_refund (const struct TMH_RequestHandler *rh, } /* end switch */ { - struct GNUNET_TIME_Timestamp timestamp; uint64_t order_serial; enum GNUNET_DB_QueryStatus qs; @@ -357,10 +368,11 @@ TMH_private_post_orders_ID_refund (const struct TMH_RequestHandler *rh, if (0 >= qs) { GNUNET_break (0); - return TALER_MHD_reply_with_error (connection, - MHD_HTTP_INTERNAL_SERVER_ERROR, - TALER_EC_GENERIC_DB_INVARIANT_FAILURE, - NULL); + return TALER_MHD_reply_with_error ( + connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_GENERIC_DB_INVARIANT_FAILURE, + NULL); } TMH_notify_order_change (hc->instance, TMH_OSF_CLAIMED diff --git a/src/backend/taler-merchant-webhook.c b/src/backend/taler-merchant-webhook.c index 1d17843e..f081aab5 100644 --- a/src/backend/taler-merchant-webhook.c +++ b/src/backend/taler-merchant-webhook.c @@ -42,7 +42,7 @@ static struct WorkResponse *w_head; static struct WorkResponse *w_tail; -static struct GNUNET_DB_EventHandler *eh; +static struct GNUNET_DB_EventHandler *event_handler; /** * The merchant's configuration. @@ -93,10 +93,10 @@ shutdown_task (void *cls) (void) cls; GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Running shutdown\n"); - if (NULL != eh) + if (NULL != event_handler) { - db_plugin->event_listen_cancel (eh); - eh = NULL; + db_plugin->event_listen_cancel (event_handler); + event_handler = NULL; } if (NULL != task) { @@ -279,11 +279,11 @@ pending_webhooks_cb (void *cls, { struct WorkResponse *w = GNUNET_new (struct WorkResponse); CURL *eh; + struct curl_slist *job_headers = NULL; + (void) retries; (void) next_attempt; (void) cls; - struct curl_slist *job_headers = NULL; - GNUNET_CONTAINER_DLL_insert (w_head, w_tail, w); @@ -533,11 +533,11 @@ run (void *cls, .type = htons (TALER_DBEVENT_MERCHANT_WEBHOOK_PENDING) }; - eh = db_plugin->event_listen (db_plugin->cls, - &es, - GNUNET_TIME_UNIT_FOREVER_REL, - &db_notify, - NULL); + event_handler = db_plugin->event_listen (db_plugin->cls, + &es, + GNUNET_TIME_UNIT_FOREVER_REL, + &db_notify, + NULL); } GNUNET_assert (NULL == task); task = GNUNET_SCHEDULER_add_now (&select_work, |