From 3f99e4f3f8a89dfafcf37fcbf9046134adca436a Mon Sep 17 00:00:00 2001 From: Christian Grothoff Date: Sun, 7 Aug 2022 15:35:06 +0200 Subject: -implement new kyc-webhook endpoint --- contrib/gana | 2 +- src/exchange/Makefile.am | 1 + src/exchange/taler-exchange-httpd_kyc-webhook.c | 347 ++++++++++++++++++++++++ src/exchange/taler-exchange-httpd_kyc-webhook.h | 64 +++++ src/exchangedb/plugin_exchangedb_postgres.c | 2 +- src/include/taler_exchangedb_plugin.h | 2 +- src/include/taler_kyclogic_plugin.h | 21 +- src/kyclogic/plugin_kyclogic_oauth2.c | 12 +- 8 files changed, 430 insertions(+), 21 deletions(-) create mode 100644 src/exchange/taler-exchange-httpd_kyc-webhook.c create mode 100644 src/exchange/taler-exchange-httpd_kyc-webhook.h diff --git a/contrib/gana b/contrib/gana index 475221191..02da8656d 160000 --- a/contrib/gana +++ b/contrib/gana @@ -1 +1 @@ -Subproject commit 4752211918879f9ceec07bec5f64c8209d840b51 +Subproject commit 02da8656d6d915df023de0b90d18ade9e80603fa diff --git a/src/exchange/Makefile.am b/src/exchange/Makefile.am index 23295c13d..7141758b1 100644 --- a/src/exchange/Makefile.am +++ b/src/exchange/Makefile.am @@ -136,6 +136,7 @@ taler_exchange_httpd_SOURCES = \ taler-exchange-httpd_kyc-check.c taler-exchange-httpd_kyc-check.h \ taler-exchange-httpd_kyc-proof.c taler-exchange-httpd_kyc-proof.h \ taler-exchange-httpd_kyc-wallet.c taler-exchange-httpd_kyc-wallet.h \ + taler-exchange-httpd_kyc-webhook.c taler-exchange-httpd_kyc-webhook.h \ taler-exchange-httpd_link.c taler-exchange-httpd_link.h \ taler-exchange-httpd_management.h \ taler-exchange-httpd_management_auditors.c \ diff --git a/src/exchange/taler-exchange-httpd_kyc-webhook.c b/src/exchange/taler-exchange-httpd_kyc-webhook.c new file mode 100644 index 000000000..a5c11cec9 --- /dev/null +++ b/src/exchange/taler-exchange-httpd_kyc-webhook.c @@ -0,0 +1,347 @@ +/* + This file is part of TALER + Copyright (C) 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 + 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 Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License along with + TALER; see the file COPYING. If not, see +*/ +/** + * @file taler-exchange-httpd_kyc-webhook.c + * @brief Handle notification of KYC completion via webhook. + * @author Christian Grothoff + */ +#include "platform.h" +#include +#include +#include +#include +#include +#include "taler_json_lib.h" +#include "taler_mhd_lib.h" +#include "taler-exchange-httpd_kyc.h" +#include "taler-exchange-httpd_kyc-webhook.h" +#include "taler-exchange-httpd_responses.h" + + +/** + * Context for the webhook. + */ +struct KycWebhookContext +{ + + /** + * Kept in a DLL while suspended. + */ + struct KycWebhookContext *next; + + /** + * Kept in a DLL while suspended. + */ + struct KycWebhookContext *prev; + + /** + * Details about the connection we are processing. + */ + struct TEH_RequestContext *rc; + + /** + * Plugin responsible for the webhook. + */ + struct TALER_KYCLOGIC_Plugin *plugin; + + /** + * Configuration for the specific action. + */ + struct TALER_KYCLOGIC_ProviderDetails *pd; + + /** + * Webhook activity. + */ + struct TALER_KYCLOGIC_WebhookHandle *wh; + + /** + * HTTP response to return. + */ + struct MHD_Response *response; + + /** + * Logic the request is for. Name of the configuration + * section defining the KYC logic. + */ + char *logic; + + /** + * 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. + */ + enum GNUNET_GenericReturnValue suspended; + +}; + + +/** + * Contexts are kept in a DLL while suspended. + */ +static struct KycWebhookContext *kwh_head; + +/** + * Contexts are kept in a DLL while suspended. + */ +static struct KycWebhookContext *kwh_tail; + + +/** + * Resume processing the @a kwh request. + * + * @param kwh request to resume + */ +static void +kwh_resume (struct KycWebhookContext *kwh) +{ + GNUNET_assert (GNUNET_YES == kwh->suspended); + kwh->suspended = GNUNET_NO; + GNUNET_CONTAINER_DLL_remove (kwh_head, + kwh_tail, + kwh); + MHD_resume_connection (kwh->rc->connection); + TALER_MHD_daemon_trigger (); +} + + +void +TEH_kyc_webhook_cleanup (void) +{ + struct KycWebhookContext *kwh; + + while (NULL != (kwh = kwh_head)) + { + if (NULL != kwh->wh) + { + kwh->plugin->webhook_cancel (kwh->wh); + kwh->wh = NULL; + } + kwh_resume (kwh); + } +} + + +/** + * Function called with the result of a webhook + * operation. + * + * Note that the "decref" for the @a response + * will be done by the plugin. + * + * @param cls closure + * @param legi_row legitimization request the webhook was about + * @param account_id account the webhook was about + * @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 status KYC status + * @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 +webhook_finished_cb ( + void *cls, + uint64_t legi_row, + const struct TALER_PaytoHashP *account_id, + const char *provider_user_id, + const char *provider_legitimization_id, + enum TALER_KYCLOGIC_KycStatus status, + struct GNUNET_TIME_Absolute expiration, + unsigned int http_status, + struct MHD_Response *response) +{ + struct KycWebhookContext *kwh = cls; + + kwh->wh = NULL; + switch (status) + { + case TALER_KYCLOGIC_STATUS_SUCCESS: + /* _successfully_ resumed case */ + { + enum GNUNET_DB_QueryStatus qs; + + qs = TEH_plugin->update_kyc_requirement_by_row (TEH_plugin->cls, + legi_row, + kwh->logic, + account_id, + provider_user_id, + provider_legitimization_id, + expiration); + if (qs < 0) + { + GNUNET_break (0); + kwh->response = TALER_MHD_make_error (TALER_EC_GENERIC_DB_STORE_FAILED, + "set_kyc_ok"); + kwh->response_code = MHD_HTTP_INTERNAL_SERVER_ERROR; + kwh_resume (kwh); + return; + } + } + break; + default: + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "KYC status of %s/%s (Row #%llu) is %d\n", + provider_user_id, + provider_legitimization_id, + (unsigned long long) legi_row, + status); + break; + } + kwh->response = response; + kwh->response_code = http_status; + kwh_resume (kwh); +} + + +/** + * Function called to clean up a context. + * + * @param rc request context + */ +static void +clean_kwh (struct TEH_RequestContext *rc) +{ + struct KycWebhookContext *kwh = rc->rh_ctx; + + if (NULL != kwh->wh) + { + kwh->plugin->webhook_cancel (kwh->wh); + kwh->wh = NULL; + } + if (NULL != kwh->response) + { + MHD_destroy_response (kwh->response); + kwh->response = NULL; + } + GNUNET_free (kwh->logic); + GNUNET_free (kwh); +} + + +/** + * Handle a (GET or POST) "/kyc-webhook" request. + * + * @param rc request to handle + * @param method HTTP request method used by the client + * @param root uploaded JSON body (can be NULL) + * @param args one argument with the payment_target_uuid + * @return MHD result code + */ +static MHD_RESULT +handler_kyc_webhook_generic ( + struct TEH_RequestContext *rc, + const char *method, + const json_t *root, + const char *const args[]) +{ + struct KycWebhookContext *kwh = rc->rh_ctx; + + if (NULL == kwh) + { /* first time */ + kwh = GNUNET_new (struct KycWebhookContext); + kwh->logic = GNUNET_strdup (args[0]); + kwh->rc = rc; + rc->rh_ctx = kwh; + rc->rh_cleaner = &clean_kwh; + + if (GNUNET_OK != + TEH_kyc_get_logic (kwh->logic, + &kwh->plugin, + &kwh->pd)) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "KYC logic `%s' unknown (check KYC provider configuration)\n", + kwh->logic); + return TALER_MHD_reply_with_error (rc->connection, + MHD_HTTP_NOT_FOUND, + TALER_EC_EXCHANGE_KYC_WEBHOOK_LOGIC_UNKNOWN, + "$LOGIC"); + } + kwh->wh = kwh->plugin->webhook (kwh->plugin->cls, + kwh->pd, + TEH_plugin->kyc_provider_account_lookup, + TEH_plugin->cls, + method, + &args[1], + rc->connection, + root, + &webhook_finished_cb, + kwh); + if (NULL == kwh->wh) + { + GNUNET_break_op (0); + return TALER_MHD_reply_with_error (rc->connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE, + "failed to run webhook logic"); + } + kwh->suspended = GNUNET_YES; + GNUNET_CONTAINER_DLL_insert (kwh_head, + kwh_tail, + kwh); + MHD_suspend_connection (rc->connection); + return MHD_YES; + } + + if (NULL != kwh->response) + { + /* handle _failed_ resumed cases */ + return MHD_queue_response (rc->connection, + kwh->response_code, + kwh->response); + } + + /* We resumed, but got no response? This should + not happen. */ + GNUNET_break (0); + return TALER_MHD_reply_with_error (rc->connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE, + "resumed without response"); +} + + +MHD_RESULT +TEH_handler_kyc_webhook_get ( + struct TEH_RequestContext *rc, + const char *const args[]) +{ + return handler_kyc_webhook_generic (rc, + MHD_HTTP_METHOD_GET, + NULL, + args); +} + + +MHD_RESULT +TEH_handler_kyc_webhook_post ( + struct TEH_RequestContext *rc, + const json_t *root, + const char *const args[]) +{ + return handler_kyc_webhook_generic (rc, + MHD_HTTP_METHOD_POST, + root, + args); +} + + +/* end of taler-exchange-httpd_kyc-webhook.c */ diff --git a/src/exchange/taler-exchange-httpd_kyc-webhook.h b/src/exchange/taler-exchange-httpd_kyc-webhook.h new file mode 100644 index 000000000..1f472a87e --- /dev/null +++ b/src/exchange/taler-exchange-httpd_kyc-webhook.h @@ -0,0 +1,64 @@ +/* + This file is part of TALER + Copyright (C) 2021 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 + 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 Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License along with + TALER; see the file COPYING. If not, see +*/ +/** + * @file taler-exchange-httpd_kyc-webhook.h + * @brief Handle /kyc-webhook requests + * @author Christian Grothoff + */ +#ifndef TALER_EXCHANGE_HTTPD_KYC_WEBHOOK_H +#define TALER_EXCHANGE_HTTPD_KYC_WEBHOOK_H + +#include +#include "taler-exchange-httpd.h" + + +/** + * Shutdown kyc-webhook subsystem. Resumes all suspended long-polling clients + * and cleans up data structures. + */ +void +TEH_kyc_webhook_cleanup (void); + + +/** + * Handle a GET "/kyc-webhook" request. + * + * @param rc request to handle + * @param args one argument with the payment_target_uuid + * @return MHD result code + */ +MHD_RESULT +TEH_handler_kyc_webhook_get ( + struct TEH_RequestContext *rc, + const char *const args[]); + + +/** + * Handle a POST "/kyc-webhook" request. + * + * @param rc request to handle + * @param root uploaded JSON body + * @param args one argument with the payment_target_uuid + * @return MHD result code + */ +MHD_RESULT +TEH_handler_kyc_webhook_post ( + struct TEH_RequestContext *rc, + const json_t *root, + const char *const args[]); + + +#endif diff --git a/src/exchangedb/plugin_exchangedb_postgres.c b/src/exchangedb/plugin_exchangedb_postgres.c index 7c066784a..e501dc2d6 100644 --- a/src/exchangedb/plugin_exchangedb_postgres.c +++ b/src/exchangedb/plugin_exchangedb_postgres.c @@ -16602,7 +16602,7 @@ postgres_update_kyc_requirement_by_row ( void *cls, uint64_t legi_row, const char *provider_section, - struct TALER_PaytoHashP *h_payto, + const struct TALER_PaytoHashP *h_payto, const char *provider_account_id, const char *provider_legitimization_id, struct GNUNET_TIME_Absolute expiration) diff --git a/src/include/taler_exchangedb_plugin.h b/src/include/taler_exchangedb_plugin.h index 5411fbe17..42b8a7427 100644 --- a/src/include/taler_exchangedb_plugin.h +++ b/src/include/taler_exchangedb_plugin.h @@ -5677,7 +5677,7 @@ struct TALER_EXCHANGEDB_Plugin void *cls, uint64_t legi_row, const char *provider_section, - struct TALER_PaytoHashP *h_payto, + const struct TALER_PaytoHashP *h_payto, const char *provider_account_id, const char *provider_legitimization_id, struct GNUNET_TIME_Absolute expiration); diff --git a/src/include/taler_kyclogic_plugin.h b/src/include/taler_kyclogic_plugin.h index f94849620..8e52e0510 100644 --- a/src/include/taler_kyclogic_plugin.h +++ b/src/include/taler_kyclogic_plugin.h @@ -147,11 +147,10 @@ typedef void /** - * Function called with the result of a proof check - * operation. + * Function called with the result of a proof check operation. * * Note that the "decref" for the @a response - * will be done by the plugin. + * will be done by the callee and MUST NOT be done by the plugin. * * @param cls closure * @param status KYC status @@ -173,13 +172,13 @@ typedef void /** - * Function called with the result of a webhook - * operation. + * Function called with the result of a webhook operation. * - * Note that the "decref" for the @a response - * will be done by the plugin. + * Note that the "decref" for the @a response will be done by the callee and + * MUST NOT be done by the plugin! * * @param cls closure + * @param legi_row legitimization request the webhook was about * @param account_id account the webhook was about * @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 @@ -191,6 +190,7 @@ typedef void typedef void (*TALER_KYCLOGIC_WebhookCallback)( void *cls, + uint64_t legi_row, const struct TALER_PaytoHashP *account_id, const char *provider_user_id, const char *provider_legitimization_id, @@ -330,7 +330,7 @@ struct TALER_KYCLOGIC_Plugin * @param plc callback to lookup accounts with * @param plc_cls closure for @a plc * @param http_method HTTP method used for the webhook - * @param url_path rest of the URL after `/kyc-webhook/` + * @param url_path rest of the URL after `/kyc-webhook/$LOGIC/` * @param connection MHD connection object (for HTTP headers) * @param body_size number of bytes in @a body * @param body HTTP request body @@ -344,10 +344,9 @@ struct TALER_KYCLOGIC_Plugin TALER_KYCLOGIC_ProviderLookupCallback plc, void *plc_cls, const char *http_method, - const char *url_path, + const char *const url_path[], struct MHD_Connection *connection, - size_t body_size, - const void *body, + const json_t *upload, TALER_KYCLOGIC_WebhookCallback cb, void *cb_cls); diff --git a/src/kyclogic/plugin_kyclogic_oauth2.c b/src/kyclogic/plugin_kyclogic_oauth2.c index 3bcd745c0..ac13355e4 100644 --- a/src/kyclogic/plugin_kyclogic_oauth2.c +++ b/src/kyclogic/plugin_kyclogic_oauth2.c @@ -556,7 +556,6 @@ return_proof_response (void *cls) GNUNET_TIME_relative_to_absolute (ph->pd->expiration), ph->http_status, ph->response); - MHD_destroy_response (ph->response); GNUNET_free (ph->provider_user_id); GNUNET_free (ph); } @@ -943,6 +942,7 @@ wh_return_not_found (void *cls) "", MHD_RESPMEM_PERSISTENT); wh->cb (wh->cb_cls, + 0LLU, NULL, NULL, NULL, @@ -962,10 +962,9 @@ wh_return_not_found (void *cls) * @param plc callback to lookup accounts with * @param plc_cls closure for @a plc * @param http_method HTTP method used for the webhook - * @param url_path rest of the URL after `/kyc-webhook/` + * @param url_path rest of the URL after `/kyc-webhook/$LOGIC/`, as NULL-terminated array * @param connection MHD connection object (for HTTP headers) - * @param body_size number of bytes in @a body - * @param body HTTP request body + * @param body HTTP request body, or NULL if not available * @param cb function to call with the result * @param cb_cls closure for @a cb * @return handle to cancel operation early @@ -976,10 +975,9 @@ oauth2_webhook (void *cls, TALER_KYCLOGIC_ProviderLookupCallback plc, void *plc_cls, const char *http_method, - const char *url_path, + const char *url_path[], struct MHD_Connection *connection, - size_t body_size, - const void *body, + const json_t *body, TALER_KYCLOGIC_WebhookCallback cb, void *cb_cls) { -- cgit v1.2.3