exchange

Base system with REST service to issue digital coins, run by the payment service provider
Log | Files | Refs | Submodules | README | LICENSE

taler-exchange-httpd_kyc-webhook.c (12108B)


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