LCOV - code coverage report
Current view: top level - exchange - taler-exchange-httpd_kyc-webhook.c (source / functions) Hit Total Coverage
Test: coverage.info Lines: 0 106 0.0 %
Date: 2025-07-09 07:38:29 Functions: 0 8 0.0 %

          Line data    Source code
       1             : /*
       2             :   This file is part of TALER
       3             :   Copyright (C) 2022-2024 Taler Systems SA
       4             : 
       5             :   TALER is free software; you can redistribute it and/or modify it under the
       6             :   terms of the GNU Affero General Public License as published by the Free Software
       7             :   Foundation; either version 3, or (at your option) any later version.
       8             : 
       9             :   TALER is distributed in the hope that it will be useful, but WITHOUT ANY
      10             :   WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
      11             :   A PARTICULAR PURPOSE.  See the GNU Affero General Public License for more details.
      12             : 
      13             :   You should have received a copy of the GNU Affero General Public License along with
      14             :   TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
      15             : */
      16             : /**
      17             :  * @file taler-exchange-httpd_kyc-webhook.c
      18             :  * @brief Handle notification of KYC completion via webhook.
      19             :  * @author Christian Grothoff
      20             :  */
      21             : #include "taler/platform.h"
      22             : #include <gnunet/gnunet_util_lib.h>
      23             : #include <gnunet/gnunet_json_lib.h>
      24             : #include <jansson.h>
      25             : #include <microhttpd.h>
      26             : #include <pthread.h>
      27             : #include "taler/taler_attributes.h"
      28             : #include "taler/taler_json_lib.h"
      29             : #include "taler/taler_mhd_lib.h"
      30             : #include "taler/taler_kyclogic_lib.h"
      31             : #include "taler-exchange-httpd_common_kyc.h"
      32             : #include "taler-exchange-httpd_kyc-webhook.h"
      33             : #include "taler-exchange-httpd_responses.h"
      34             : 
      35             : 
      36             : /**
      37             :  * Context for the webhook.
      38             :  */
      39             : struct KycWebhookContext
      40             : {
      41             : 
      42             :   /**
      43             :    * Kept in a DLL while suspended.
      44             :    */
      45             :   struct KycWebhookContext *next;
      46             : 
      47             :   /**
      48             :    * Kept in a DLL while suspended.
      49             :    */
      50             :   struct KycWebhookContext *prev;
      51             : 
      52             :   /**
      53             :    * Details about the connection we are processing.
      54             :    */
      55             :   struct TEH_RequestContext *rc;
      56             : 
      57             :   /**
      58             :    * Handle for the KYC-AML trigger interaction.
      59             :    */
      60             :   struct TEH_KycMeasureRunContext *kat;
      61             : 
      62             :   /**
      63             :    * Plugin responsible for the webhook.
      64             :    */
      65             :   struct TALER_KYCLOGIC_Plugin *plugin;
      66             : 
      67             :   /**
      68             :    * Name of the KYC provider (suffix of the
      69             :    * section name in the configuration).
      70             :    */
      71             :   const char *provider_name;
      72             : 
      73             :   /**
      74             :    * Configuration for the specific action.
      75             :    */
      76             :   struct TALER_KYCLOGIC_ProviderDetails *pd;
      77             : 
      78             :   /**
      79             :    * Webhook activity.
      80             :    */
      81             :   struct TALER_KYCLOGIC_WebhookHandle *wh;
      82             : 
      83             :   /**
      84             :    * Final HTTP response to return.
      85             :    */
      86             :   struct MHD_Response *response;
      87             : 
      88             :   /**
      89             :    * Final HTTP response code to return.
      90             :    */
      91             :   unsigned int response_code;
      92             : 
      93             :   /**
      94             :    * Response from the webhook plugin.
      95             :    *
      96             :    * Will become the final response on successfully
      97             :    * running the measure with the new attributes.
      98             :    */
      99             :   struct MHD_Response *webhook_response;
     100             : 
     101             :   /**
     102             :    * Response code to return for the webhook plugin
     103             :    * response.
     104             :    */
     105             :   unsigned int webhook_response_code;
     106             : 
     107             :   /**
     108             :    * #GNUNET_YES if we are suspended,
     109             :    * #GNUNET_NO if not.
     110             :    * #GNUNET_SYSERR if we had some error.
     111             :    */
     112             :   enum GNUNET_GenericReturnValue suspended;
     113             : 
     114             : };
     115             : 
     116             : 
     117             : /**
     118             :  * Contexts are kept in a DLL while suspended.
     119             :  */
     120             : static struct KycWebhookContext *kwh_head;
     121             : 
     122             : /**
     123             :  * Contexts are kept in a DLL while suspended.
     124             :  */
     125             : static struct KycWebhookContext *kwh_tail;
     126             : 
     127             : 
     128             : /**
     129             :  * Resume processing the @a kwh request.
     130             :  *
     131             :  * @param kwh request to resume
     132             :  */
     133             : static void
     134           0 : kwh_resume (struct KycWebhookContext *kwh)
     135             : {
     136           0 :   GNUNET_assert (GNUNET_YES == kwh->suspended);
     137           0 :   kwh->suspended = GNUNET_NO;
     138           0 :   GNUNET_CONTAINER_DLL_remove (kwh_head,
     139             :                                kwh_tail,
     140             :                                kwh);
     141           0 :   MHD_resume_connection (kwh->rc->connection);
     142           0 :   TALER_MHD_daemon_trigger ();
     143           0 : }
     144             : 
     145             : 
     146             : void
     147           0 : TEH_kyc_webhook_cleanup (void)
     148             : {
     149             :   struct KycWebhookContext *kwh;
     150             : 
     151           0 :   while (NULL != (kwh = kwh_head))
     152             :   {
     153           0 :     if (NULL != kwh->wh)
     154             :     {
     155           0 :       kwh->plugin->webhook_cancel (kwh->wh);
     156           0 :       kwh->wh = NULL;
     157             :     }
     158           0 :     kwh_resume (kwh);
     159             :   }
     160           0 : }
     161             : 
     162             : 
     163             : /**
     164             :  * Function called after the KYC-AML trigger is done.
     165             :  *
     166             :  * @param cls closure with a `struct KycWebhookContext *`
     167             :  * @param ec error code or 0 on success
     168             :  * @param detail error message or NULL on success / no info
     169             :  */
     170             : static void
     171           0 : kyc_aml_webhook_finished (
     172             :   void *cls,
     173             :   enum TALER_ErrorCode ec,
     174             :   const char *detail)
     175             : {
     176           0 :   struct KycWebhookContext *kwh = cls;
     177             : 
     178           0 :   kwh->kat = NULL;
     179           0 :   GNUNET_assert (NULL == kwh->response);
     180           0 :   if (TALER_EC_NONE != ec)
     181             :   {
     182           0 :     kwh->response_code = MHD_HTTP_INTERNAL_SERVER_ERROR;
     183           0 :     kwh->response = TALER_MHD_make_error (
     184             :       ec,
     185             :       detail
     186             :       );
     187             :   }
     188             :   else
     189             :   {
     190           0 :     GNUNET_assert (NULL != kwh->webhook_response);
     191           0 :     kwh->response_code = kwh->webhook_response_code;
     192           0 :     kwh->response = kwh->webhook_response;
     193           0 :     kwh->webhook_response = NULL;
     194           0 :     kwh->webhook_response_code = 0;
     195             :   }
     196           0 :   kwh_resume (kwh);
     197           0 : }
     198             : 
     199             : 
     200             : /**
     201             :  * Function called with the result of a KYC webhook operation.
     202             :  *
     203             :  * Note that the "decref" for the @a response
     204             :  * will be done by the plugin.
     205             :  *
     206             :  * @param cls closure
     207             :  * @param process_row legitimization process the webhook was about
     208             :  * @param account_id account the webhook was about
     209             :  * @param is_wallet true if @a account_id is for a wallet
     210             :  * @param provider_name name of the KYC provider that was run
     211             :  * @param provider_user_id set to user ID at the provider, or NULL if not supported or unknown
     212             :  * @param provider_legitimization_id set to legitimization process ID at the provider, or NULL if not supported or unknown
     213             :  * @param status KYC status
     214             :  * @param expiration until when is the KYC check valid
     215             :  * @param attributes user attributes returned by the provider
     216             :  * @param http_status HTTP status code of @a response
     217             :  * @param[in] response to return to the HTTP client
     218             :  */
     219             : static void
     220           0 : webhook_finished_cb (
     221             :   void *cls,
     222             :   uint64_t process_row,
     223             :   const struct TALER_NormalizedPaytoHashP *account_id,
     224             :   bool is_wallet,
     225             :   const char *provider_name,
     226             :   const char *provider_user_id,
     227             :   const char *provider_legitimization_id,
     228             :   enum TALER_KYCLOGIC_KycStatus status,
     229             :   struct GNUNET_TIME_Absolute expiration,
     230             :   const json_t *attributes,
     231             :   unsigned int http_status,
     232             :   struct MHD_Response *response)
     233             : {
     234           0 :   struct KycWebhookContext *kwh = cls;
     235             :   enum GNUNET_DB_QueryStatus qs;
     236             : 
     237           0 :   kwh->wh = NULL;
     238           0 :   kwh->webhook_response = response;
     239           0 :   kwh->webhook_response_code = http_status;
     240           0 :   switch (status)
     241             :   {
     242           0 :   case TALER_KYCLOGIC_STATUS_SUCCESS:
     243           0 :     qs = TEH_kyc_store_attributes (
     244             :       process_row,
     245             :       account_id,
     246             :       provider_name,
     247             :       provider_user_id,
     248             :       provider_legitimization_id,
     249             :       expiration,
     250             :       attributes);
     251           0 :     if (0 >= qs)
     252             :     {
     253           0 :       GNUNET_break (0);
     254           0 :       kyc_aml_webhook_finished (kwh,
     255             :                                 TALER_EC_GENERIC_DB_STORE_FAILED,
     256             :                                 "kyc_store_attributes");
     257           0 :       return;
     258             :     }
     259           0 :     kwh->kat = TEH_kyc_run_measure_for_attributes (
     260           0 :       &kwh->rc->async_scope_id,
     261             :       process_row,
     262             :       account_id,
     263             :       is_wallet,
     264             :       &kyc_aml_webhook_finished,
     265             :       kwh);
     266           0 :     if (NULL == kwh->kat)
     267             :     {
     268           0 :       kyc_aml_webhook_finished (kwh,
     269             :                                 TALER_EC_EXCHANGE_GENERIC_BAD_CONFIGURATION,
     270             :                                 "[exchange] AML_KYC_TRIGGER");
     271             :     }
     272           0 :     break;
     273           0 :   case TALER_KYCLOGIC_STATUS_FAILED:
     274             :   case TALER_KYCLOGIC_STATUS_PROVIDER_FAILED:
     275             :   case TALER_KYCLOGIC_STATUS_USER_ABORTED:
     276             :   case TALER_KYCLOGIC_STATUS_ABORTED:
     277           0 :     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     278             :                 "KYC process %s/%s (Row #%llu) failed: %d\n",
     279             :                 provider_user_id,
     280             :                 provider_legitimization_id,
     281             :                 (unsigned long long) process_row,
     282             :                 status);
     283           0 :     if (! TEH_kyc_failed (
     284             :           process_row,
     285             :           account_id,
     286             :           provider_name,
     287             :           provider_user_id,
     288             :           provider_legitimization_id,
     289             :           TALER_KYCLOGIC_status2s (status),
     290             :           TALER_EC_EXCHANGE_GENERIC_KYC_FAILED))
     291             :     {
     292           0 :       GNUNET_break (0);
     293           0 :       kyc_aml_webhook_finished (kwh,
     294             :                                 TALER_EC_GENERIC_DB_STORE_FAILED,
     295             :                                 "TEH_kyc_failed");
     296             :     }
     297           0 :     break;
     298           0 :   default:
     299           0 :     GNUNET_log (
     300             :       GNUNET_ERROR_TYPE_INFO,
     301             :       "KYC status of %s/%s (Row #%llu) is %d\n",
     302             :       provider_user_id,
     303             :       provider_legitimization_id,
     304             :       (unsigned long long) process_row,
     305             :       (int) status);
     306           0 :     kyc_aml_webhook_finished (kwh,
     307             :                               TALER_EC_NONE,
     308             :                               NULL);
     309           0 :     break;
     310             :   }
     311             : }
     312             : 
     313             : 
     314             : /**
     315             :  * Function called to clean up a context.
     316             :  *
     317             :  * @param rc request context
     318             :  */
     319             : static void
     320           0 : clean_kwh (struct TEH_RequestContext *rc)
     321             : {
     322           0 :   struct KycWebhookContext *kwh = rc->rh_ctx;
     323             : 
     324           0 :   if (NULL != kwh->wh)
     325             :   {
     326           0 :     kwh->plugin->webhook_cancel (kwh->wh);
     327           0 :     kwh->wh = NULL;
     328             :   }
     329           0 :   if (NULL != kwh->kat)
     330             :   {
     331           0 :     TEH_kyc_run_measure_cancel (kwh->kat);
     332           0 :     kwh->kat = NULL;
     333             :   }
     334           0 :   if (NULL != kwh->response)
     335             :   {
     336           0 :     MHD_destroy_response (kwh->response);
     337           0 :     kwh->response = NULL;
     338             :   }
     339           0 :   if (NULL != kwh->webhook_response)
     340             :   {
     341           0 :     MHD_destroy_response (kwh->webhook_response);
     342           0 :     kwh->webhook_response = NULL;
     343             :   }
     344           0 :   GNUNET_free (kwh);
     345           0 : }
     346             : 
     347             : 
     348             : /**
     349             :  * Handle a (GET or POST) "/kyc-webhook" request.
     350             :  *
     351             :  * @param rc request to handle
     352             :  * @param method HTTP request method used by the client
     353             :  * @param root uploaded JSON body (can be NULL)
     354             :  * @param args one argument with the legitimization_uuid
     355             :  * @return MHD result code
     356             :  */
     357             : static MHD_RESULT
     358           0 : handler_kyc_webhook_generic (
     359             :   struct TEH_RequestContext *rc,
     360             :   const char *method,
     361             :   const json_t *root,
     362             :   const char *const args[])
     363             : {
     364           0 :   struct KycWebhookContext *kwh = rc->rh_ctx;
     365             : 
     366           0 :   if (NULL == kwh)
     367             :   { /* first time */
     368           0 :     kwh = GNUNET_new (struct KycWebhookContext);
     369           0 :     kwh->rc = rc;
     370           0 :     rc->rh_ctx = kwh;
     371           0 :     rc->rh_cleaner = &clean_kwh;
     372             : 
     373           0 :     if ( (NULL == args[0]) ||
     374             :          (GNUNET_OK !=
     375           0 :           TALER_KYCLOGIC_lookup_logic (
     376             :             args[0],
     377             :             &kwh->plugin,
     378             :             &kwh->pd,
     379             :             &kwh->provider_name)) )
     380             :     {
     381           0 :       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
     382             :                   "KYC logic `%s' unknown (check KYC provider configuration)\n",
     383             :                   args[0]);
     384           0 :       return TALER_MHD_reply_with_error (
     385             :         rc->connection,
     386             :         MHD_HTTP_NOT_FOUND,
     387             :         TALER_EC_EXCHANGE_KYC_GENERIC_LOGIC_UNKNOWN,
     388             :         args[0]);
     389             :     }
     390           0 :     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     391             :                 "KYC logic `%s' mapped to section %s\n",
     392             :                 args[0],
     393             :                 kwh->provider_name);
     394           0 :     kwh->wh = kwh->plugin->webhook (
     395           0 :       kwh->plugin->cls,
     396           0 :       kwh->pd,
     397           0 :       TEH_plugin->kyc_provider_account_lookup,
     398           0 :       TEH_plugin->cls,
     399             :       method,
     400             :       &args[1],
     401             :       rc->connection,
     402             :       root,
     403             :       &webhook_finished_cb,
     404             :       kwh);
     405           0 :     if (NULL == kwh->wh)
     406             :     {
     407           0 :       GNUNET_break_op (0);
     408           0 :       return TALER_MHD_reply_with_error (
     409             :         rc->connection,
     410             :         MHD_HTTP_INTERNAL_SERVER_ERROR,
     411             :         TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
     412             :         "failed to run webhook logic");
     413             :     }
     414           0 :     kwh->suspended = GNUNET_YES;
     415           0 :     GNUNET_CONTAINER_DLL_insert (kwh_head,
     416             :                                  kwh_tail,
     417             :                                  kwh);
     418           0 :     MHD_suspend_connection (rc->connection);
     419           0 :     return MHD_YES;
     420             :   }
     421           0 :   GNUNET_break (GNUNET_NO == kwh->suspended);
     422             : 
     423           0 :   if (NULL != kwh->response)
     424             :   {
     425             :     MHD_RESULT res;
     426             : 
     427           0 :     res = MHD_queue_response (rc->connection,
     428             :                               kwh->response_code,
     429             :                               kwh->response);
     430           0 :     GNUNET_break (MHD_YES == res);
     431           0 :     return res;
     432             :   }
     433             : 
     434             :   /* We resumed, but got no response? This should
     435             :      not happen. */
     436           0 :   GNUNET_break (0);
     437           0 :   return TALER_MHD_reply_with_error (
     438             :     rc->connection,
     439             :     MHD_HTTP_INTERNAL_SERVER_ERROR,
     440             :     TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
     441             :     "resumed without response");
     442             : }
     443             : 
     444             : 
     445             : MHD_RESULT
     446           0 : TEH_handler_kyc_webhook_get (
     447             :   struct TEH_RequestContext *rc,
     448             :   const char *const args[])
     449             : {
     450           0 :   return handler_kyc_webhook_generic (
     451             :     rc,
     452             :     MHD_HTTP_METHOD_GET,
     453             :     NULL,
     454             :     args);
     455             : }
     456             : 
     457             : 
     458             : MHD_RESULT
     459           0 : TEH_handler_kyc_webhook_post (
     460             :   struct TEH_RequestContext *rc,
     461             :   const json_t *root,
     462             :   const char *const args[])
     463             : {
     464           0 :   return handler_kyc_webhook_generic (
     465             :     rc,
     466             :     MHD_HTTP_METHOD_POST,
     467             :     root,
     468             :     args);
     469             : }
     470             : 
     471             : 
     472             : /* end of taler-exchange-httpd_kyc-webhook.c */

Generated by: LCOV version 1.16