LCOV - code coverage report
Current view: top level - kyclogic - plugin_kyclogic_kycaid.c (source / functions) Coverage Total Hit
Test: coverage.info Lines: 11.8 % 498 59
Test Date: 2026-04-14 15:39:31 Functions: 25.0 % 16 4

            Line data    Source code
       1              : /*
       2              :   This file is part of GNU 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.GPL.  If not, see <http://www.gnu.org/licenses/>
      15              : */
      16              : /**
      17              :  * @file plugin_kyclogic_kycaid.c
      18              :  * @brief kycaid for an authentication flow logic
      19              :  * @author Christian Grothoff
      20              :  */
      21              : #include "taler/taler_kyclogic_lib.h"
      22              : #include "taler/taler_kyclogic_plugin.h"
      23              : #include "taler/taler_mhd_lib.h"
      24              : #include "taler/taler_curl_lib.h"
      25              : #include "taler/taler_json_lib.h"
      26              : #include "taler/taler_templating_lib.h"
      27              : #include <regex.h>
      28              : #include "taler/taler_util.h"
      29              : 
      30              : 
      31              : /**
      32              :  * Saves the state of a plugin.
      33              :  */
      34              : struct PluginState
      35              : {
      36              : 
      37              :   /**
      38              :    * Our base URL.
      39              :    */
      40              :   char *exchange_base_url;
      41              : 
      42              :   /**
      43              :    * Our global configuration.
      44              :    */
      45              :   const struct GNUNET_CONFIGURATION_Handle *cfg;
      46              : 
      47              :   /**
      48              :    * Context for CURL operations (useful to the event loop)
      49              :    */
      50              :   struct GNUNET_CURL_Context *curl_ctx;
      51              : 
      52              :   /**
      53              :    * Context for integrating @e curl_ctx with the
      54              :    * GNUnet event loop.
      55              :    */
      56              :   struct GNUNET_CURL_RescheduleContext *curl_rc;
      57              : 
      58              : };
      59              : 
      60              : 
      61              : /**
      62              :  * Keeps the plugin-specific state for
      63              :  * a given configuration section.
      64              :  */
      65              : struct TALER_KYCLOGIC_ProviderDetails
      66              : {
      67              : 
      68              :   /**
      69              :    * Overall plugin state.
      70              :    */
      71              :   struct PluginState *ps;
      72              : 
      73              :   /**
      74              :    * Configuration section that configured us.
      75              :    */
      76              :   char *section;
      77              : 
      78              :   /**
      79              :    * Authorization token to use when talking
      80              :    * to the service.
      81              :    */
      82              :   char *auth_token;
      83              : 
      84              :   /**
      85              :    * Form ID for the KYC check to perform.
      86              :    */
      87              :   char *form_id;
      88              : 
      89              :   /**
      90              :    * Helper binary to convert attributes returned by
      91              :    * KYCAID into our internal format.
      92              :    */
      93              :   char *conversion_helper;
      94              : 
      95              :   /**
      96              :    * Validity time for a successful KYC process.
      97              :    */
      98              :   struct GNUNET_TIME_Relative validity;
      99              : 
     100              :   /**
     101              :    * Curl-ready authentication header to use.
     102              :    */
     103              :   struct curl_slist *slist;
     104              : 
     105              : };
     106              : 
     107              : 
     108              : /**
     109              :  * Handle for an initiation operation.
     110              :  */
     111              : struct TALER_KYCLOGIC_InitiateHandle
     112              : {
     113              : 
     114              :   /**
     115              :    * Hash of the payto:// URI we are initiating
     116              :    * the KYC for.
     117              :    */
     118              :   struct TALER_NormalizedPaytoHashP h_payto;
     119              : 
     120              :   /**
     121              :    * UUID being checked.
     122              :    */
     123              :   uint64_t legitimization_uuid;
     124              : 
     125              :   /**
     126              :    * Our configuration details.
     127              :    */
     128              :   const struct TALER_KYCLOGIC_ProviderDetails *pd;
     129              : 
     130              :   /**
     131              :    * Continuation to call.
     132              :    */
     133              :   TALER_KYCLOGIC_InitiateCallback cb;
     134              : 
     135              :   /**
     136              :    * Closure for @a cb.
     137              :    */
     138              :   void *cb_cls;
     139              : 
     140              :   /**
     141              :    * Context for #TEH_curl_easy_post(). Keeps the data that must
     142              :    * persist for Curl to make the upload.
     143              :    */
     144              :   struct TALER_CURL_PostContext ctx;
     145              : 
     146              :   /**
     147              :    * Handle for the request.
     148              :    */
     149              :   struct GNUNET_CURL_Job *job;
     150              : 
     151              :   /**
     152              :    * URL of the cURL request.
     153              :    */
     154              :   char *url;
     155              : 
     156              : };
     157              : 
     158              : 
     159              : /**
     160              :  * Handle for an KYC proof operation.
     161              :  */
     162              : struct TALER_KYCLOGIC_ProofHandle
     163              : {
     164              : 
     165              :   /**
     166              :    * Overall plugin state.
     167              :    */
     168              :   struct PluginState *ps;
     169              : 
     170              :   /**
     171              :    * Our configuration details.
     172              :    */
     173              :   const struct TALER_KYCLOGIC_ProviderDetails *pd;
     174              : 
     175              :   /**
     176              :    * Continuation to call.
     177              :    */
     178              :   TALER_KYCLOGIC_ProofCallback cb;
     179              : 
     180              :   /**
     181              :    * Closure for @e cb.
     182              :    */
     183              :   void *cb_cls;
     184              : 
     185              :   /**
     186              :    * Connection we are handling.
     187              :    */
     188              :   struct MHD_Connection *connection;
     189              : 
     190              :   /**
     191              :    * Task for asynchronous execution.
     192              :    */
     193              :   struct GNUNET_SCHEDULER_Task *task;
     194              : };
     195              : 
     196              : 
     197              : /**
     198              :  * Handle for an KYC Web hook operation.
     199              :  */
     200              : struct TALER_KYCLOGIC_WebhookHandle
     201              : {
     202              : 
     203              :   /**
     204              :    * Continuation to call when done.
     205              :    */
     206              :   TALER_KYCLOGIC_WebhookCallback cb;
     207              : 
     208              :   /**
     209              :    * Closure for @a cb.
     210              :    */
     211              :   void *cb_cls;
     212              : 
     213              :   /**
     214              :    * Task for asynchronous execution.
     215              :    */
     216              :   struct GNUNET_SCHEDULER_Task *task;
     217              : 
     218              :   /**
     219              :    * Overall plugin state.
     220              :    */
     221              :   struct PluginState *ps;
     222              : 
     223              :   /**
     224              :    * Handle to helper process to extract attributes
     225              :    * we care about.
     226              :    */
     227              :   struct TALER_JSON_ExternalConversion *econ;
     228              : 
     229              :   /**
     230              :    * Our configuration details.
     231              :    */
     232              :   const struct TALER_KYCLOGIC_ProviderDetails *pd;
     233              : 
     234              :   /**
     235              :    * Connection we are handling.
     236              :    */
     237              :   struct MHD_Connection *connection;
     238              : 
     239              :   /**
     240              :    * JSON response we got back, or NULL for none.
     241              :    */
     242              :   json_t *json_response;
     243              : 
     244              :   /**
     245              :    * Verification ID from the service.
     246              :    */
     247              :   char *verification_id;
     248              : 
     249              :   /**
     250              :    * Applicant ID from the service.
     251              :    */
     252              :   char *applicant_id;
     253              : 
     254              :   /**
     255              :    * URL of the cURL request.
     256              :    */
     257              :   char *url;
     258              : 
     259              :   /**
     260              :    * Handle for the request.
     261              :    */
     262              :   struct GNUNET_CURL_Job *job;
     263              : 
     264              :   /**
     265              :    * Response to return asynchronously.
     266              :    */
     267              :   struct MHD_Response *resp;
     268              : 
     269              :   /**
     270              :    * Our account ID.
     271              :    */
     272              :   struct TALER_NormalizedPaytoHashP h_payto;
     273              : 
     274              :   /**
     275              :    * Row in legitimizations for the given
     276              :    * @e verification_id.
     277              :    */
     278              :   uint64_t process_row;
     279              : 
     280              :   /**
     281              :    * HTTP response code we got from KYCAID.
     282              :    */
     283              :   unsigned int kycaid_response_code;
     284              : 
     285              :   /**
     286              :    * HTTP response code to return asynchronously.
     287              :    */
     288              :   unsigned int response_code;
     289              : 
     290              :   /**
     291              :    * True if @e h_payto is for a wallet.
     292              :    */
     293              :   bool is_wallet;
     294              : };
     295              : 
     296              : 
     297              : /**
     298              :  * Release configuration resources previously loaded
     299              :  *
     300              :  * @param[in] pd configuration to release
     301              :  */
     302              : static void
     303           61 : kycaid_unload_configuration (struct TALER_KYCLOGIC_ProviderDetails *pd)
     304              : {
     305           61 :   curl_slist_free_all (pd->slist);
     306           61 :   GNUNET_free (pd->conversion_helper);
     307           61 :   GNUNET_free (pd->auth_token);
     308           61 :   GNUNET_free (pd->form_id);
     309           61 :   GNUNET_free (pd->section);
     310           61 :   GNUNET_free (pd);
     311           61 : }
     312              : 
     313              : 
     314              : /**
     315              :  * Load the configuration of the KYC provider.
     316              :  *
     317              :  * @param cls closure
     318              :  * @param provider_section_name configuration section to parse
     319              :  * @return NULL if configuration is invalid
     320              :  */
     321              : static struct TALER_KYCLOGIC_ProviderDetails *
     322           61 : kycaid_load_configuration (void *cls,
     323              :                            const char *provider_section_name)
     324              : {
     325           61 :   struct PluginState *ps = cls;
     326              :   struct TALER_KYCLOGIC_ProviderDetails *pd;
     327              : 
     328           61 :   pd = GNUNET_new (struct TALER_KYCLOGIC_ProviderDetails);
     329           61 :   pd->ps = ps;
     330           61 :   pd->section = GNUNET_strdup (provider_section_name);
     331           61 :   if (GNUNET_OK !=
     332           61 :       GNUNET_CONFIGURATION_get_value_time (ps->cfg,
     333              :                                            provider_section_name,
     334              :                                            "KYC_KYCAID_VALIDITY",
     335              :                                            &pd->validity))
     336              :   {
     337            0 :     GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
     338              :                                provider_section_name,
     339              :                                "KYC_KYCAID_VALIDITY");
     340            0 :     kycaid_unload_configuration (pd);
     341            0 :     return NULL;
     342              :   }
     343           61 :   if (GNUNET_OK !=
     344           61 :       GNUNET_CONFIGURATION_get_value_string (ps->cfg,
     345              :                                              provider_section_name,
     346              :                                              "KYC_KYCAID_AUTH_TOKEN",
     347              :                                              &pd->auth_token))
     348              :   {
     349            0 :     GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
     350              :                                provider_section_name,
     351              :                                "KYC_KYCAID_AUTH_TOKEN");
     352            0 :     kycaid_unload_configuration (pd);
     353            0 :     return NULL;
     354              :   }
     355           61 :   if (GNUNET_OK !=
     356           61 :       GNUNET_CONFIGURATION_get_value_string (ps->cfg,
     357              :                                              provider_section_name,
     358              :                                              "KYC_KYCAID_FORM_ID",
     359              :                                              &pd->form_id))
     360              :   {
     361            0 :     GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
     362              :                                provider_section_name,
     363              :                                "KYC_KYCAID_FORM_ID");
     364            0 :     kycaid_unload_configuration (pd);
     365            0 :     return NULL;
     366              :   }
     367           61 :   if (GNUNET_OK !=
     368           61 :       GNUNET_CONFIGURATION_get_value_string (ps->cfg,
     369              :                                              provider_section_name,
     370              :                                              "KYC_KYCAID_CONVERTER_HELPER",
     371              :                                              &pd->conversion_helper))
     372              :   {
     373            0 :     GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
     374              :                                provider_section_name,
     375              :                                "KYC_KYCAID_CONVERTER_HELPER");
     376            0 :     kycaid_unload_configuration (pd);
     377            0 :     return NULL;
     378              :   }
     379              :   {
     380              :     char *auth;
     381              : 
     382           61 :     GNUNET_asprintf (&auth,
     383              :                      "%s: Token %s",
     384              :                      MHD_HTTP_HEADER_AUTHORIZATION,
     385              :                      pd->auth_token);
     386           61 :     pd->slist = curl_slist_append (NULL,
     387              :                                    auth);
     388           61 :     GNUNET_free (auth);
     389              :   }
     390           61 :   return pd;
     391              : }
     392              : 
     393              : 
     394              : /**
     395              :  * Cancel KYC check initiation.
     396              :  *
     397              :  * @param[in] ih handle of operation to cancel
     398              :  */
     399              : static void
     400            0 : kycaid_initiate_cancel (struct TALER_KYCLOGIC_InitiateHandle *ih)
     401              : {
     402            0 :   if (NULL != ih->job)
     403              :   {
     404            0 :     GNUNET_CURL_job_cancel (ih->job);
     405            0 :     ih->job = NULL;
     406              :   }
     407            0 :   GNUNET_free (ih->url);
     408            0 :   TALER_curl_easy_post_finished (&ih->ctx);
     409            0 :   GNUNET_free (ih);
     410            0 : }
     411              : 
     412              : 
     413              : /**
     414              :  * Function called when we're done processing the
     415              :  * HTTP "/forms/{form_id}/urls" request.
     416              :  *
     417              :  * @param cls the `struct TALER_KYCLOGIC_InitiateHandle`
     418              :  * @param response_code HTTP response code, 0 on error
     419              :  * @param response parsed JSON result, NULL on error
     420              :  */
     421              : static void
     422            0 : handle_initiate_finished (void *cls,
     423              :                           long response_code,
     424              :                           const void *response)
     425              : {
     426            0 :   struct TALER_KYCLOGIC_InitiateHandle *ih = cls;
     427            0 :   const json_t *j = response;
     428              : 
     429            0 :   ih->job = NULL;
     430            0 :   switch (response_code)
     431              :   {
     432            0 :   case MHD_HTTP_OK:
     433              :     {
     434              :       const char *verification_id;
     435              :       const char *form_url;
     436              :       const char *form_id;
     437              :       struct GNUNET_JSON_Specification spec[] = {
     438            0 :         GNUNET_JSON_spec_string ("verification_id",
     439              :                                  &verification_id),
     440            0 :         GNUNET_JSON_spec_string ("form_url",
     441              :                                  &form_url),
     442            0 :         GNUNET_JSON_spec_string ("form_id",
     443              :                                  &form_id),
     444            0 :         GNUNET_JSON_spec_end ()
     445              :       };
     446              : 
     447            0 :       if (GNUNET_OK !=
     448            0 :           GNUNET_JSON_parse (j,
     449              :                              spec,
     450              :                              NULL, NULL))
     451              :       {
     452            0 :         GNUNET_break_op (0);
     453            0 :         json_dumpf (j,
     454              :                     stderr,
     455              :                     JSON_INDENT (2));
     456            0 :         ih->cb (ih->cb_cls,
     457              :                 TALER_EC_EXCHANGE_KYC_GENERIC_PROVIDER_UNEXPECTED_REPLY,
     458              :                 NULL,
     459              :                 NULL,
     460              :                 NULL,
     461            0 :                 json_string_value (json_object_get (j,
     462              :                                                     "type")));
     463            0 :         break;
     464              :       }
     465            0 :       GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     466              :                   "Started new verification `%s' using form %s\n",
     467              :                   verification_id,
     468              :                   form_id);
     469            0 :       ih->cb (ih->cb_cls,
     470              :               TALER_EC_NONE,
     471              :               form_url,
     472              :               NULL, /* no provider_user_id */
     473              :               verification_id,
     474              :               NULL /* no error */);
     475            0 :       GNUNET_JSON_parse_free (spec);
     476              :     }
     477            0 :     break;
     478            0 :   case MHD_HTTP_BAD_REQUEST:
     479              :   case MHD_HTTP_NOT_FOUND:
     480              :   case MHD_HTTP_CONFLICT:
     481            0 :     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
     482              :                 "KYCAID failed with response %u:\n",
     483              :                 (unsigned int) response_code);
     484            0 :     json_dumpf (j,
     485              :                 stderr,
     486              :                 JSON_INDENT (2));
     487            0 :     ih->cb (ih->cb_cls,
     488              :             TALER_EC_EXCHANGE_KYC_GENERIC_LOGIC_BUG,
     489              :             NULL,
     490              :             NULL,
     491              :             NULL,
     492            0 :             json_string_value (json_object_get (j,
     493              :                                                 "type")));
     494            0 :     break;
     495            0 :   case MHD_HTTP_UNAUTHORIZED:
     496              :   case MHD_HTTP_PAYMENT_REQUIRED:
     497            0 :     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
     498              :                 "Refused access with HTTP status code %u\n",
     499              :                 (unsigned int) response_code);
     500            0 :     ih->cb (ih->cb_cls,
     501              :             TALER_EC_EXCHANGE_KYC_GENERIC_PROVIDER_ACCESS_REFUSED,
     502              :             NULL,
     503              :             NULL,
     504              :             NULL,
     505            0 :             json_string_value (json_object_get (j,
     506              :                                                 "type")));
     507            0 :     break;
     508            0 :   case MHD_HTTP_REQUEST_TIMEOUT:
     509            0 :     ih->cb (ih->cb_cls,
     510              :             TALER_EC_EXCHANGE_KYC_GENERIC_PROVIDER_TIMEOUT,
     511              :             NULL,
     512              :             NULL,
     513              :             NULL,
     514            0 :             json_string_value (json_object_get (j,
     515              :                                                 "type")));
     516            0 :     break;
     517            0 :   case MHD_HTTP_UNPROCESSABLE_CONTENT: /* validation */
     518            0 :     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
     519              :                 "KYCAID failed with response %u:\n",
     520              :                 (unsigned int) response_code);
     521            0 :     json_dumpf (j,
     522              :                 stderr,
     523              :                 JSON_INDENT (2));
     524            0 :     ih->cb (ih->cb_cls,
     525              :             TALER_EC_EXCHANGE_KYC_GENERIC_PROVIDER_UNEXPECTED_REPLY,
     526              :             NULL,
     527              :             NULL,
     528              :             NULL,
     529            0 :             json_string_value (json_object_get (j,
     530              :                                                 "type")));
     531            0 :     break;
     532            0 :   case MHD_HTTP_TOO_MANY_REQUESTS:
     533            0 :     ih->cb (ih->cb_cls,
     534              :             TALER_EC_EXCHANGE_KYC_GENERIC_PROVIDER_RATE_LIMIT_EXCEEDED,
     535              :             NULL,
     536              :             NULL,
     537              :             NULL,
     538            0 :             json_string_value (json_object_get (j,
     539              :                                                 "type")));
     540            0 :     break;
     541            0 :   case MHD_HTTP_INTERNAL_SERVER_ERROR:
     542            0 :     ih->cb (ih->cb_cls,
     543              :             TALER_EC_EXCHANGE_KYC_GENERIC_PROVIDER_UNEXPECTED_REPLY,
     544              :             NULL,
     545              :             NULL,
     546              :             NULL,
     547            0 :             json_string_value (json_object_get (j,
     548              :                                                 "type")));
     549            0 :     break;
     550            0 :   default:
     551            0 :     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
     552              :                 "Unexpected KYCAID response %u:\n",
     553              :                 (unsigned int) response_code);
     554            0 :     json_dumpf (j,
     555              :                 stderr,
     556              :                 JSON_INDENT (2));
     557            0 :     ih->cb (ih->cb_cls,
     558              :             TALER_EC_EXCHANGE_KYC_GENERIC_PROVIDER_UNEXPECTED_REPLY,
     559              :             NULL,
     560              :             NULL,
     561              :             NULL,
     562            0 :             json_string_value (json_object_get (j,
     563              :                                                 "type")));
     564            0 :     break;
     565              :   }
     566            0 :   kycaid_initiate_cancel (ih);
     567            0 : }
     568              : 
     569              : 
     570              : /**
     571              :  * Initiate KYC check.
     572              :  *
     573              :  * @param cls the @e cls of this struct with the plugin-specific state
     574              :  * @param pd provider configuration details
     575              :  * @param account_id which account to trigger process for
     576              :  * @param legitimization_uuid unique ID for the legitimization process
     577              :  * @param context additional contextual information for the legi process
     578              :  * @param cb function to call with the result
     579              :  * @param cb_cls closure for @a cb
     580              :  * @return handle to cancel operation early
     581              :  */
     582              : static struct TALER_KYCLOGIC_InitiateHandle *
     583            0 : kycaid_initiate (void *cls,
     584              :                  const struct TALER_KYCLOGIC_ProviderDetails *pd,
     585              :                  const struct TALER_NormalizedPaytoHashP *account_id,
     586              :                  uint64_t legitimization_uuid,
     587              :                  const json_t *context,
     588              :                  TALER_KYCLOGIC_InitiateCallback cb,
     589              :                  void *cb_cls)
     590              : {
     591            0 :   struct PluginState *ps = cls;
     592              :   struct TALER_KYCLOGIC_InitiateHandle *ih;
     593              :   json_t *body;
     594              :   CURL *eh;
     595              : 
     596              :   (void) context;
     597            0 :   eh = curl_easy_init ();
     598            0 :   if (NULL == eh)
     599              :   {
     600            0 :     GNUNET_break (0);
     601            0 :     return NULL;
     602              :   }
     603            0 :   ih = GNUNET_new (struct TALER_KYCLOGIC_InitiateHandle);
     604            0 :   ih->legitimization_uuid = legitimization_uuid;
     605            0 :   ih->cb = cb;
     606            0 :   ih->cb_cls = cb_cls;
     607            0 :   ih->h_payto = *account_id;
     608            0 :   ih->pd = pd;
     609            0 :   GNUNET_asprintf (&ih->url,
     610              :                    "https://api.kycaid.com/forms/%s/urls",
     611            0 :                    pd->form_id);
     612            0 :   body = GNUNET_JSON_PACK (
     613              :     GNUNET_JSON_pack_data64_auto ("external_applicant_id",
     614              :                                   account_id)
     615              :     );
     616            0 :   GNUNET_break (CURLE_OK ==
     617              :                 curl_easy_setopt (eh,
     618              :                                   CURLOPT_VERBOSE,
     619              :                                   0));
     620            0 :   GNUNET_assert (CURLE_OK ==
     621              :                  curl_easy_setopt (eh,
     622              :                                    CURLOPT_MAXREDIRS,
     623              :                                    1L));
     624            0 :   GNUNET_break (CURLE_OK ==
     625              :                 curl_easy_setopt (eh,
     626              :                                   CURLOPT_URL,
     627              :                                   ih->url));
     628            0 :   if (GNUNET_OK !=
     629            0 :       TALER_curl_easy_post (&ih->ctx,
     630              :                             eh,
     631              :                             body))
     632              :   {
     633            0 :     GNUNET_break (0);
     634            0 :     GNUNET_free (ih->url);
     635            0 :     GNUNET_free (ih);
     636            0 :     curl_easy_cleanup (eh);
     637            0 :     json_decref (body);
     638            0 :     return NULL;
     639              :   }
     640            0 :   json_decref (body);
     641            0 :   ih->job = GNUNET_CURL_job_add2 (ps->curl_ctx,
     642              :                                   eh,
     643            0 :                                   ih->ctx.headers,
     644              :                                   &handle_initiate_finished,
     645              :                                   ih);
     646            0 :   GNUNET_CURL_extend_headers (ih->job,
     647            0 :                               pd->slist);
     648            0 :   return ih;
     649              : }
     650              : 
     651              : 
     652              : /**
     653              :  * Cancel KYC proof.
     654              :  *
     655              :  * @param[in] ph handle of operation to cancel
     656              :  */
     657              : static void
     658            0 : kycaid_proof_cancel (struct TALER_KYCLOGIC_ProofHandle *ph)
     659              : {
     660            0 :   if (NULL != ph->task)
     661              :   {
     662            0 :     GNUNET_SCHEDULER_cancel (ph->task);
     663            0 :     ph->task = NULL;
     664              :   }
     665            0 :   GNUNET_free (ph);
     666            0 : }
     667              : 
     668              : 
     669              : /**
     670              :  * Call @a ph callback with HTTP error response.
     671              :  *
     672              :  * @param cls proof handle to generate reply for
     673              :  */
     674              : static void
     675            0 : proof_reply (void *cls)
     676              : {
     677            0 :   struct TALER_KYCLOGIC_ProofHandle *ph = cls;
     678              :   struct MHD_Response *resp;
     679              :   enum GNUNET_GenericReturnValue ret;
     680              :   json_t *body;
     681              :   unsigned int http_status;
     682              : 
     683            0 :   http_status = MHD_HTTP_BAD_REQUEST;
     684            0 :   body = GNUNET_JSON_PACK (
     685              :     TALER_JSON_pack_ec (TALER_EC_GENERIC_ENDPOINT_UNKNOWN));
     686            0 :   GNUNET_assert (NULL != body);
     687            0 :   ret = TALER_TEMPLATING_build (ph->connection,
     688              :                                 &http_status,
     689              :                                 "kycaid-invalid-request",
     690              :                                 NULL,
     691              :                                 NULL,
     692              :                                 body,
     693              :                                 &resp);
     694            0 :   json_decref (body);
     695            0 :   if (GNUNET_SYSERR == ret)
     696              :   {
     697            0 :     resp = NULL;
     698            0 :     GNUNET_break (0);
     699              :   }
     700              :   else
     701              :   {
     702            0 :     GNUNET_break (MHD_NO !=
     703              :                   MHD_add_response_header (resp,
     704              :                                            MHD_HTTP_HEADER_CONTENT_TYPE,
     705              :                                            "text/html"));
     706              :   }
     707            0 :   ph->cb (ph->cb_cls,
     708              :           TALER_KYCLOGIC_STATUS_PROVIDER_FAILED,
     709            0 :           ph->pd->section,
     710              :           NULL, /* user id */
     711              :           NULL, /* provider legi ID */
     712            0 :           GNUNET_TIME_UNIT_ZERO_ABS, /* expiration */
     713              :           NULL, /* attributes */
     714              :           http_status,
     715              :           resp);
     716            0 : }
     717              : 
     718              : 
     719              : /**
     720              :  * Check KYC status and return status to human. Not
     721              :  * used by KYC AID!
     722              :  *
     723              :  * @param cls the @e cls of this struct with the plugin-specific state
     724              :  * @param pd provider configuration details
     725              :  * @param connection MHD connection object (for HTTP headers)
     726              :  * @param account_id which account to trigger process for
     727              :  * @param process_row row in the legitimization processes table the legitimization is for
     728              :  * @param provider_user_id user ID (or NULL) the proof is for
     729              :  * @param provider_legitimization_id legitimization ID the proof is for
     730              :  * @param cb function to call with the result
     731              :  * @param cb_cls closure for @a cb
     732              :  * @return handle to cancel operation early
     733              :  */
     734              : static struct TALER_KYCLOGIC_ProofHandle *
     735            0 : kycaid_proof (void *cls,
     736              :               const struct TALER_KYCLOGIC_ProviderDetails *pd,
     737              :               struct MHD_Connection *connection,
     738              :               const struct TALER_NormalizedPaytoHashP *account_id,
     739              :               uint64_t process_row,
     740              :               const char *provider_user_id,
     741              :               const char *provider_legitimization_id,
     742              :               TALER_KYCLOGIC_ProofCallback cb,
     743              :               void *cb_cls)
     744              : {
     745            0 :   struct PluginState *ps = cls;
     746              :   struct TALER_KYCLOGIC_ProofHandle *ph;
     747              : 
     748            0 :   ph = GNUNET_new (struct TALER_KYCLOGIC_ProofHandle);
     749            0 :   ph->ps = ps;
     750            0 :   ph->pd = pd;
     751            0 :   ph->cb = cb;
     752            0 :   ph->cb_cls = cb_cls;
     753            0 :   ph->connection = connection;
     754            0 :   ph->task = GNUNET_SCHEDULER_add_now (&proof_reply,
     755              :                                        ph);
     756            0 :   return ph;
     757              : }
     758              : 
     759              : 
     760              : /**
     761              :  * Cancel KYC webhook execution.
     762              :  *
     763              :  * @param[in] wh handle of operation to cancel
     764              :  */
     765              : static void
     766            0 : kycaid_webhook_cancel (struct TALER_KYCLOGIC_WebhookHandle *wh)
     767              : {
     768            0 :   if (NULL != wh->task)
     769              :   {
     770            0 :     GNUNET_SCHEDULER_cancel (wh->task);
     771            0 :     wh->task = NULL;
     772              :   }
     773            0 :   if (NULL != wh->econ)
     774              :   {
     775            0 :     TALER_JSON_external_conversion_stop (wh->econ);
     776            0 :     wh->econ = NULL;
     777              :   }
     778            0 :   if (NULL != wh->job)
     779              :   {
     780            0 :     GNUNET_CURL_job_cancel (wh->job);
     781            0 :     wh->job = NULL;
     782              :   }
     783            0 :   if (NULL != wh->json_response)
     784              :   {
     785            0 :     json_decref (wh->json_response);
     786            0 :     wh->json_response = NULL;
     787              :   }
     788            0 :   GNUNET_free (wh->verification_id);
     789            0 :   GNUNET_free (wh->applicant_id);
     790            0 :   GNUNET_free (wh->url);
     791            0 :   GNUNET_free (wh);
     792            0 : }
     793              : 
     794              : 
     795              : /**
     796              :  * Extract KYC failure reasons and log those
     797              :  *
     798              :  * @param verifications JSON object with failure details
     799              :  */
     800              : static void
     801            0 : log_failure (const json_t *verifications)
     802              : {
     803              :   const json_t *member;
     804              :   const char *name;
     805              : 
     806            0 :   json_object_foreach ((json_t *) verifications, name, member)
     807              :   {
     808              :     bool iverified;
     809              :     const char *comment;
     810              :     struct GNUNET_JSON_Specification spec[] = {
     811            0 :       GNUNET_JSON_spec_bool ("verified",
     812              :                              &iverified),
     813            0 :       GNUNET_JSON_spec_string ("comment",
     814              :                                &comment),
     815            0 :       GNUNET_JSON_spec_end ()
     816              :     };
     817              : 
     818            0 :     if (GNUNET_OK !=
     819            0 :         GNUNET_JSON_parse (member,
     820              :                            spec,
     821              :                            NULL, NULL))
     822              :     {
     823            0 :       GNUNET_break_op (0);
     824            0 :       json_dumpf (member,
     825              :                   stderr,
     826              :                   JSON_INDENT (2));
     827            0 :       continue;
     828              :     }
     829            0 :     if (iverified)
     830            0 :       continue;
     831            0 :     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     832              :                 "KYC verification of attribute `%s' failed: %s\n",
     833              :                 name,
     834              :                 comment);
     835              :   }
     836            0 : }
     837              : 
     838              : 
     839              : /**
     840              :  * Type of a callback that receives a JSON @a result.
     841              :  *
     842              :  * @param cls closure our `struct TALER_KYCLOGIC_WebhookHandle *`
     843              :  * @param status_type how did the process die
     844              :  * @param code termination status code from the process
     845              :  * @param result converted attribute data, NULL on failure
     846              :  */
     847              : static void
     848            0 : webhook_conversion_cb (void *cls,
     849              :                        enum GNUNET_OS_ProcessStatusType status_type,
     850              :                        unsigned long code,
     851              :                        const json_t *result)
     852              : {
     853            0 :   struct TALER_KYCLOGIC_WebhookHandle *wh = cls;
     854              :   struct GNUNET_TIME_Absolute expiration;
     855              :   struct MHD_Response *resp;
     856              : 
     857            0 :   wh->econ = NULL;
     858            0 :   if ( (0 == code) &&
     859              :        (NULL == result) )
     860              :   {
     861              :     /* No result, but *our helper* was OK => bad input */
     862            0 :     GNUNET_break_op (0);
     863            0 :     json_dumpf (wh->json_response,
     864              :                 stderr,
     865              :                 JSON_INDENT (2));
     866            0 :     resp = TALER_MHD_MAKE_JSON_PACK (
     867              :       GNUNET_JSON_pack_uint64 ("kycaid_http_status",
     868              :                                wh->kycaid_response_code),
     869              :       GNUNET_JSON_pack_object_incref ("kycaid_body",
     870              :                                       (json_t *) wh->json_response));
     871            0 :     wh->cb (wh->cb_cls,
     872              :             wh->process_row,
     873            0 :             &wh->h_payto,
     874            0 :             wh->is_wallet,
     875            0 :             wh->pd->section,
     876            0 :             wh->applicant_id,
     877            0 :             wh->verification_id,
     878              :             TALER_KYCLOGIC_STATUS_PROVIDER_FAILED,
     879            0 :             GNUNET_TIME_UNIT_ZERO_ABS, /* expiration */
     880              :             NULL,
     881              :             MHD_HTTP_BAD_GATEWAY,
     882              :             resp);
     883            0 :     kycaid_webhook_cancel (wh);
     884            0 :     return;
     885              :   }
     886            0 :   if (NULL == result)
     887              :   {
     888              :     /* Failure in our helper */
     889            0 :     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
     890              :                 "Helper exited with status code %d\n",
     891              :                 (int) code);
     892            0 :     json_dumpf (wh->json_response,
     893              :                 stderr,
     894              :                 JSON_INDENT (2));
     895            0 :     resp = TALER_MHD_MAKE_JSON_PACK (
     896              :       GNUNET_JSON_pack_uint64 ("kycaid_http_status",
     897              :                                wh->kycaid_response_code),
     898              :       GNUNET_JSON_pack_object_incref ("kycaid_body",
     899              :                                       (json_t *) wh->json_response));
     900            0 :     wh->cb (wh->cb_cls,
     901              :             wh->process_row,
     902            0 :             &wh->h_payto,
     903            0 :             wh->is_wallet,
     904            0 :             wh->pd->section,
     905            0 :             wh->applicant_id,
     906            0 :             wh->verification_id,
     907              :             TALER_KYCLOGIC_STATUS_PROVIDER_FAILED,
     908            0 :             GNUNET_TIME_UNIT_ZERO_ABS, /* expiration */
     909              :             NULL,
     910              :             MHD_HTTP_BAD_GATEWAY,
     911              :             resp);
     912            0 :     kycaid_webhook_cancel (wh);
     913            0 :     return;
     914              :   }
     915            0 :   if (! json_is_string (json_object_get (result,
     916              :                                          "FORM_ID")))
     917              :   {
     918              :     /* Failure in our helper */
     919            0 :     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
     920              :                 "Mandatory FORM_ID not set in result\n");
     921            0 :     json_dumpf (result,
     922              :                 stderr,
     923              :                 JSON_INDENT (2));
     924            0 :     resp = TALER_MHD_MAKE_JSON_PACK (
     925              :       GNUNET_JSON_pack_uint64 ("kycaid_http_status",
     926              :                                wh->kycaid_response_code),
     927              :       GNUNET_JSON_pack_object_incref ("kycaid_body",
     928              :                                       (json_t *) wh->json_response));
     929            0 :     wh->cb (wh->cb_cls,
     930              :             wh->process_row,
     931            0 :             &wh->h_payto,
     932            0 :             wh->is_wallet,
     933            0 :             wh->pd->section,
     934            0 :             wh->applicant_id,
     935            0 :             wh->verification_id,
     936              :             TALER_KYCLOGIC_STATUS_PROVIDER_FAILED,
     937            0 :             GNUNET_TIME_UNIT_ZERO_ABS, /* expiration */
     938              :             NULL,
     939              :             MHD_HTTP_BAD_GATEWAY,
     940              :             resp);
     941            0 :     kycaid_webhook_cancel (wh);
     942            0 :     return;
     943              :   }
     944              : 
     945            0 :   expiration = GNUNET_TIME_relative_to_absolute (wh->pd->validity);
     946            0 :   resp = MHD_create_response_from_buffer_static (0,
     947              :                                                  "");
     948            0 :   wh->cb (wh->cb_cls,
     949              :           wh->process_row,
     950            0 :           &wh->h_payto,
     951            0 :           wh->is_wallet,
     952            0 :           wh->pd->section,
     953            0 :           wh->applicant_id,
     954            0 :           wh->verification_id,
     955              :           TALER_KYCLOGIC_STATUS_SUCCESS,
     956              :           expiration,
     957              :           result,
     958              :           MHD_HTTP_NO_CONTENT,
     959              :           resp);
     960            0 :   kycaid_webhook_cancel (wh);
     961              : }
     962              : 
     963              : 
     964              : /**
     965              :  * Function called when we're done processing the
     966              :  * HTTP "/applicants/{verification_id}" request.
     967              :  *
     968              :  * @param cls the `struct TALER_KYCLOGIC_WebhookHandle`
     969              :  * @param response_code HTTP response code, 0 on error
     970              :  * @param response parsed JSON result, NULL on error
     971              :  */
     972              : static void
     973            0 : handle_webhook_finished (void *cls,
     974              :                          long response_code,
     975              :                          const void *response)
     976              : {
     977            0 :   struct TALER_KYCLOGIC_WebhookHandle *wh = cls;
     978            0 :   const json_t *j = response;
     979              :   struct MHD_Response *resp;
     980              : 
     981            0 :   wh->job = NULL;
     982            0 :   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     983              :               "Webhook returned with HTTP status %u\n",
     984              :               (unsigned int) response_code);
     985            0 :   wh->kycaid_response_code = response_code;
     986            0 :   wh->json_response = json_incref ((json_t *) j);
     987            0 :   switch (response_code)
     988              :   {
     989            0 :   case MHD_HTTP_OK:
     990              :     {
     991              :       const char *profile_status;
     992              : 
     993            0 :       profile_status = json_string_value (
     994            0 :         json_object_get (
     995              :           j,
     996              :           "profile_status"));
     997            0 :       if (0 != strcasecmp ("valid",
     998              :                            profile_status))
     999              :       {
    1000              :         enum TALER_KYCLOGIC_KycStatus ks;
    1001              : 
    1002            0 :         ks = (0 == strcasecmp ("pending",
    1003              :                                profile_status))
    1004              :           ? TALER_KYCLOGIC_STATUS_PENDING
    1005            0 :           : TALER_KYCLOGIC_STATUS_USER_ABORTED;
    1006            0 :         resp = MHD_create_response_from_buffer_static (0,
    1007              :                                                        "");
    1008            0 :         wh->cb (wh->cb_cls,
    1009              :                 wh->process_row,
    1010            0 :                 &wh->h_payto,
    1011            0 :                 wh->is_wallet,
    1012            0 :                 wh->pd->section,
    1013            0 :                 wh->applicant_id,
    1014            0 :                 wh->verification_id,
    1015              :                 ks,
    1016            0 :                 GNUNET_TIME_UNIT_ZERO_ABS,
    1017              :                 NULL,
    1018              :                 MHD_HTTP_NO_CONTENT,
    1019              :                 resp);
    1020            0 :         break;
    1021              :       }
    1022              :       {
    1023            0 :         const char *argv[] = {
    1024            0 :           wh->pd->conversion_helper,
    1025              :           "-a",
    1026            0 :           wh->pd->auth_token,
    1027              :           NULL,
    1028              :         };
    1029              : 
    1030              :         wh->econ
    1031            0 :           = TALER_JSON_external_conversion_start (
    1032              :               j,
    1033              :               &webhook_conversion_cb,
    1034              :               wh,
    1035            0 :               wh->pd->conversion_helper,
    1036              :               argv);
    1037              :       }
    1038            0 :       if (NULL == wh->econ)
    1039              :       {
    1040            0 :         GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    1041              :                     "Failed to start KYCAID conversion helper `%s'\n",
    1042              :                     wh->pd->conversion_helper);
    1043            0 :         resp = TALER_MHD_make_error (
    1044              :           TALER_EC_EXCHANGE_GENERIC_KYC_CONVERTER_FAILED,
    1045              :           NULL);
    1046            0 :         wh->cb (wh->cb_cls,
    1047              :                 wh->process_row,
    1048            0 :                 &wh->h_payto,
    1049            0 :                 wh->is_wallet,
    1050            0 :                 wh->pd->section,
    1051            0 :                 wh->applicant_id,
    1052            0 :                 wh->verification_id,
    1053              :                 TALER_KYCLOGIC_STATUS_INTERNAL_ERROR,
    1054            0 :                 GNUNET_TIME_UNIT_ZERO_ABS, /* expiration */
    1055              :                 NULL,
    1056              :                 MHD_HTTP_INTERNAL_SERVER_ERROR,
    1057              :                 resp);
    1058            0 :         break;
    1059              :       }
    1060            0 :       return;
    1061              :     }
    1062              :     break;
    1063            0 :   case MHD_HTTP_BAD_REQUEST:
    1064              :   case MHD_HTTP_NOT_FOUND:
    1065              :   case MHD_HTTP_CONFLICT:
    1066            0 :     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    1067              :                 "KYCAID failed with response %u:\n",
    1068              :                 (unsigned int) response_code);
    1069            0 :     json_dumpf (j,
    1070              :                 stderr,
    1071              :                 JSON_INDENT (2));
    1072            0 :     resp = TALER_MHD_MAKE_JSON_PACK (
    1073              :       GNUNET_JSON_pack_uint64 ("kycaid_http_status",
    1074              :                                response_code));
    1075            0 :     wh->cb (wh->cb_cls,
    1076              :             wh->process_row,
    1077            0 :             &wh->h_payto,
    1078            0 :             wh->is_wallet,
    1079            0 :             wh->pd->section,
    1080            0 :             wh->applicant_id,
    1081            0 :             wh->verification_id,
    1082              :             TALER_KYCLOGIC_STATUS_PROVIDER_FAILED,
    1083            0 :             GNUNET_TIME_UNIT_ZERO_ABS, /* expiration */
    1084              :             NULL,
    1085              :             MHD_HTTP_INTERNAL_SERVER_ERROR,
    1086              :             resp);
    1087            0 :     break;
    1088            0 :   case MHD_HTTP_UNAUTHORIZED:
    1089              :   case MHD_HTTP_PAYMENT_REQUIRED:
    1090            0 :     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    1091              :                 "Refused access with HTTP status code %u\n",
    1092              :                 (unsigned int) response_code);
    1093            0 :     resp = TALER_MHD_MAKE_JSON_PACK (
    1094              :       GNUNET_JSON_pack_uint64 ("kycaid_http_status",
    1095              :                                response_code),
    1096              :       GNUNET_JSON_pack_object_incref ("kycaid_body",
    1097              :                                       (json_t *) j));
    1098            0 :     wh->cb (wh->cb_cls,
    1099              :             wh->process_row,
    1100            0 :             &wh->h_payto,
    1101            0 :             wh->is_wallet,
    1102            0 :             wh->pd->section,
    1103            0 :             wh->applicant_id,
    1104            0 :             wh->verification_id,
    1105              :             TALER_KYCLOGIC_STATUS_PROVIDER_FAILED,
    1106            0 :             GNUNET_TIME_UNIT_ZERO_ABS, /* expiration */
    1107              :             NULL,
    1108              :             MHD_HTTP_NETWORK_AUTHENTICATION_REQUIRED,
    1109              :             resp);
    1110            0 :     break;
    1111            0 :   case MHD_HTTP_REQUEST_TIMEOUT:
    1112            0 :     resp = TALER_MHD_MAKE_JSON_PACK (
    1113              :       GNUNET_JSON_pack_uint64 ("kycaid_http_status",
    1114              :                                response_code),
    1115              :       GNUNET_JSON_pack_object_incref ("kycaid_body",
    1116              :                                       (json_t *) j));
    1117            0 :     wh->cb (wh->cb_cls,
    1118              :             wh->process_row,
    1119            0 :             &wh->h_payto,
    1120            0 :             wh->is_wallet,
    1121            0 :             wh->pd->section,
    1122            0 :             wh->applicant_id,
    1123            0 :             wh->verification_id,
    1124              :             TALER_KYCLOGIC_STATUS_PROVIDER_FAILED,
    1125            0 :             GNUNET_TIME_UNIT_ZERO_ABS, /* expiration */
    1126              :             NULL,
    1127              :             MHD_HTTP_GATEWAY_TIMEOUT,
    1128              :             resp);
    1129            0 :     break;
    1130            0 :   case MHD_HTTP_UNPROCESSABLE_CONTENT: /* validation */
    1131            0 :     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    1132              :                 "KYCAID failed with response %u:\n",
    1133              :                 (unsigned int) response_code);
    1134            0 :     json_dumpf (j,
    1135              :                 stderr,
    1136              :                 JSON_INDENT (2));
    1137            0 :     resp = TALER_MHD_MAKE_JSON_PACK (
    1138              :       GNUNET_JSON_pack_uint64 ("kycaid_http_status",
    1139              :                                response_code),
    1140              :       GNUNET_JSON_pack_object_incref ("kycaid_body",
    1141              :                                       (json_t *) j));
    1142            0 :     wh->cb (wh->cb_cls,
    1143              :             wh->process_row,
    1144            0 :             &wh->h_payto,
    1145            0 :             wh->is_wallet,
    1146            0 :             wh->pd->section,
    1147            0 :             wh->applicant_id,
    1148            0 :             wh->verification_id,
    1149              :             TALER_KYCLOGIC_STATUS_PROVIDER_FAILED,
    1150            0 :             GNUNET_TIME_UNIT_ZERO_ABS, /* expiration */
    1151              :             NULL,
    1152              :             MHD_HTTP_BAD_GATEWAY,
    1153              :             resp);
    1154            0 :     break;
    1155            0 :   case MHD_HTTP_TOO_MANY_REQUESTS:
    1156            0 :     resp = TALER_MHD_MAKE_JSON_PACK (
    1157              :       GNUNET_JSON_pack_uint64 ("kycaid_http_status",
    1158              :                                response_code),
    1159              :       GNUNET_JSON_pack_object_incref ("kycaid_body",
    1160              :                                       (json_t *) j));
    1161            0 :     wh->cb (wh->cb_cls,
    1162              :             wh->process_row,
    1163            0 :             &wh->h_payto,
    1164            0 :             wh->is_wallet,
    1165            0 :             wh->pd->section,
    1166            0 :             wh->applicant_id,
    1167            0 :             wh->verification_id,
    1168              :             TALER_KYCLOGIC_STATUS_PROVIDER_FAILED,
    1169            0 :             GNUNET_TIME_UNIT_ZERO_ABS, /* expiration */
    1170              :             NULL,
    1171              :             MHD_HTTP_SERVICE_UNAVAILABLE,
    1172              :             resp);
    1173            0 :     break;
    1174            0 :   case MHD_HTTP_INTERNAL_SERVER_ERROR:
    1175            0 :     resp = TALER_MHD_MAKE_JSON_PACK (
    1176              :       GNUNET_JSON_pack_uint64 ("kycaid_http_status",
    1177              :                                response_code),
    1178              :       GNUNET_JSON_pack_object_incref ("kycaid_body",
    1179              :                                       (json_t *) j));
    1180            0 :     wh->cb (wh->cb_cls,
    1181              :             wh->process_row,
    1182            0 :             &wh->h_payto,
    1183            0 :             wh->is_wallet,
    1184            0 :             wh->pd->section,
    1185            0 :             wh->applicant_id,
    1186            0 :             wh->verification_id,
    1187              :             TALER_KYCLOGIC_STATUS_PROVIDER_FAILED,
    1188            0 :             GNUNET_TIME_UNIT_ZERO_ABS, /* expiration */
    1189              :             NULL,
    1190              :             MHD_HTTP_BAD_GATEWAY,
    1191              :             resp);
    1192            0 :     break;
    1193            0 :   default:
    1194            0 :     resp = TALER_MHD_MAKE_JSON_PACK (
    1195              :       GNUNET_JSON_pack_uint64 ("kycaid_http_status",
    1196              :                                response_code),
    1197              :       GNUNET_JSON_pack_object_incref ("kycaid_body",
    1198              :                                       (json_t *) j));
    1199            0 :     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    1200              :                 "Unexpected KYCAID response %u:\n",
    1201              :                 (unsigned int) response_code);
    1202            0 :     json_dumpf (j,
    1203              :                 stderr,
    1204              :                 JSON_INDENT (2));
    1205            0 :     wh->cb (wh->cb_cls,
    1206              :             wh->process_row,
    1207            0 :             &wh->h_payto,
    1208            0 :             wh->is_wallet,
    1209            0 :             wh->pd->section,
    1210            0 :             wh->applicant_id,
    1211            0 :             wh->verification_id,
    1212              :             TALER_KYCLOGIC_STATUS_PROVIDER_FAILED,
    1213            0 :             GNUNET_TIME_UNIT_ZERO_ABS, /* expiration */
    1214              :             NULL,
    1215              :             MHD_HTTP_BAD_GATEWAY,
    1216              :             resp);
    1217            0 :     break;
    1218              :   }
    1219            0 :   kycaid_webhook_cancel (wh);
    1220              : }
    1221              : 
    1222              : 
    1223              : /**
    1224              :  * Asynchronously return a reply for the webhook.
    1225              :  *
    1226              :  * @param cls a `struct TALER_KYCLOGIC_WebhookHandle *`
    1227              :  */
    1228              : static void
    1229            0 : async_webhook_reply (void *cls)
    1230              : {
    1231            0 :   struct TALER_KYCLOGIC_WebhookHandle *wh = cls;
    1232              : 
    1233            0 :   wh->task = NULL;
    1234            0 :   wh->cb (wh->cb_cls,
    1235              :           wh->process_row,
    1236            0 :           (0 == wh->process_row)
    1237              :           ? NULL
    1238              :           : &wh->h_payto,
    1239            0 :           wh->is_wallet,
    1240            0 :           wh->pd->section,
    1241            0 :           wh->applicant_id, /* provider user ID */
    1242            0 :           wh->verification_id, /* provider legi ID */
    1243              :           TALER_KYCLOGIC_STATUS_PROVIDER_FAILED,
    1244            0 :           GNUNET_TIME_UNIT_ZERO_ABS, /* expiration */
    1245              :           NULL,
    1246              :           wh->response_code,
    1247              :           wh->resp);
    1248            0 :   kycaid_webhook_cancel (wh);
    1249            0 : }
    1250              : 
    1251              : 
    1252              : /**
    1253              :  * Check KYC status and return result for Webhook.  We do NOT implement the
    1254              :  * authentication check proposed by the KYCAID documentation, as it would
    1255              :  * allow an attacker who learns the access token to easily bypass the KYC
    1256              :  * checks. Instead, we insist on explicitly requesting the KYC status from the
    1257              :  * provider (at least on success).
    1258              :  *
    1259              :  * @param cls the @e cls of this struct with the plugin-specific state
    1260              :  * @param pd provider configuration details
    1261              :  * @param plc callback to lookup accounts with
    1262              :  * @param plc_cls closure for @a plc
    1263              :  * @param http_method HTTP method used for the webhook
    1264              :  * @param url_path rest of the URL after `/kyc-webhook/`
    1265              :  * @param connection MHD connection object (for HTTP headers)
    1266              :  * @param body HTTP request body
    1267              :  * @param cb function to call with the result
    1268              :  * @param cb_cls closure for @a cb
    1269              :  * @return handle to cancel operation early
    1270              :  */
    1271              : static struct TALER_KYCLOGIC_WebhookHandle *
    1272            0 : kycaid_webhook (void *cls,
    1273              :                 const struct TALER_KYCLOGIC_ProviderDetails *pd,
    1274              :                 TALER_KYCLOGIC_ProviderLookupCallback plc,
    1275              :                 void *plc_cls,
    1276              :                 const char *http_method,
    1277              :                 const char *const url_path[],
    1278              :                 struct MHD_Connection *connection,
    1279              :                 const json_t *body,
    1280              :                 TALER_KYCLOGIC_WebhookCallback cb,
    1281              :                 void *cb_cls)
    1282              : {
    1283            0 :   struct PluginState *ps = cls;
    1284              :   struct TALER_KYCLOGIC_WebhookHandle *wh;
    1285              :   CURL *eh;
    1286              :   const char *request_id;
    1287              :   const char *type;
    1288              :   const char *verification_id; /* = provider_legitimization_id */
    1289              :   const char *applicant_id;
    1290              :   const char *form_id;
    1291            0 :   const char *status = NULL;
    1292            0 :   bool verified = false;
    1293            0 :   bool no_verified = true;
    1294            0 :   const json_t *verifications = NULL;
    1295              :   struct GNUNET_JSON_Specification spec[] = {
    1296            0 :     GNUNET_JSON_spec_string ("request_id",
    1297              :                              &request_id),
    1298            0 :     GNUNET_JSON_spec_string ("type",
    1299              :                              &type),
    1300            0 :     GNUNET_JSON_spec_string ("verification_id",
    1301              :                              &verification_id),
    1302            0 :     GNUNET_JSON_spec_string ("applicant_id",
    1303              :                              &applicant_id),
    1304            0 :     GNUNET_JSON_spec_string ("form_id",
    1305              :                              &form_id),
    1306            0 :     GNUNET_JSON_spec_mark_optional (
    1307              :       GNUNET_JSON_spec_string ("status",
    1308              :                                &status),
    1309              :       NULL),
    1310            0 :     GNUNET_JSON_spec_mark_optional (
    1311              :       GNUNET_JSON_spec_bool ("verified",
    1312              :                              &verified),
    1313              :       &no_verified),
    1314            0 :     GNUNET_JSON_spec_mark_optional (
    1315              :       GNUNET_JSON_spec_object_const ("verifications",
    1316              :                                      &verifications),
    1317              :       NULL),
    1318            0 :     GNUNET_JSON_spec_end ()
    1319              :   };
    1320              :   enum GNUNET_DB_QueryStatus qs;
    1321              : 
    1322            0 :   wh = GNUNET_new (struct TALER_KYCLOGIC_WebhookHandle);
    1323            0 :   wh->cb = cb;
    1324            0 :   wh->cb_cls = cb_cls;
    1325            0 :   wh->ps = ps;
    1326            0 :   wh->pd = pd;
    1327            0 :   wh->connection = connection;
    1328            0 :   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    1329              :               "KYCAID webhook of `%s' triggered with %s\n",
    1330              :               pd->section,
    1331              :               http_method);
    1332              : #if 1
    1333            0 :   if (NULL != body)
    1334            0 :     json_dumpf (body,
    1335              :                 stderr,
    1336              :                 JSON_INDENT (2));
    1337              : #endif
    1338            0 :   if (NULL == pd)
    1339              :   {
    1340            0 :     GNUNET_break_op (0);
    1341            0 :     json_dumpf (body,
    1342              :                 stderr,
    1343              :                 JSON_INDENT (2));
    1344            0 :     wh->resp = TALER_MHD_make_error (
    1345              :       TALER_EC_EXCHANGE_KYC_GENERIC_LOGIC_UNKNOWN,
    1346              :       "kycaid");
    1347            0 :     wh->response_code = MHD_HTTP_NOT_FOUND;
    1348            0 :     wh->task = GNUNET_SCHEDULER_add_now (&async_webhook_reply,
    1349              :                                          wh);
    1350            0 :     return wh;
    1351              :   }
    1352              : 
    1353            0 :   if (GNUNET_OK !=
    1354            0 :       GNUNET_JSON_parse (body,
    1355              :                          spec,
    1356              :                          NULL, NULL))
    1357              :   {
    1358            0 :     GNUNET_break_op (0);
    1359            0 :     json_dumpf (body,
    1360              :                 stderr,
    1361              :                 JSON_INDENT (2));
    1362            0 :     wh->resp = TALER_MHD_MAKE_JSON_PACK (
    1363              :       GNUNET_JSON_pack_object_incref ("webhook_body",
    1364              :                                       (json_t *) body));
    1365            0 :     wh->response_code = MHD_HTTP_BAD_REQUEST;
    1366            0 :     wh->task = GNUNET_SCHEDULER_add_now (&async_webhook_reply,
    1367              :                                          wh);
    1368            0 :     return wh;
    1369              :   }
    1370            0 :   qs = plc (plc_cls,
    1371            0 :             pd->section,
    1372              :             verification_id,
    1373              :             &wh->h_payto,
    1374              :             &wh->is_wallet,
    1375              :             &wh->process_row);
    1376            0 :   if (qs < 0)
    1377              :   {
    1378            0 :     wh->resp = TALER_MHD_make_error (TALER_EC_GENERIC_DB_FETCH_FAILED,
    1379              :                                      "provider-legitimization-lookup");
    1380            0 :     wh->response_code = MHD_HTTP_INTERNAL_SERVER_ERROR;
    1381            0 :     wh->task = GNUNET_SCHEDULER_add_now (&async_webhook_reply,
    1382              :                                          wh);
    1383            0 :     return wh;
    1384              :   }
    1385            0 :   if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
    1386              :   {
    1387            0 :     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    1388              :                 "Received webhook for unknown verification ID `%s' and section `%s'\n",
    1389              :                 verification_id,
    1390              :                 pd->section);
    1391            0 :     wh->resp = TALER_MHD_make_error (
    1392              :       TALER_EC_EXCHANGE_KYC_PROOF_REQUEST_UNKNOWN,
    1393              :       verification_id);
    1394            0 :     wh->response_code = MHD_HTTP_NOT_FOUND;
    1395            0 :     wh->task = GNUNET_SCHEDULER_add_now (&async_webhook_reply,
    1396              :                                          wh);
    1397            0 :     return wh;
    1398              :   }
    1399            0 :   wh->verification_id = GNUNET_strdup (verification_id);
    1400            0 :   wh->applicant_id = GNUNET_strdup (applicant_id);
    1401            0 :   if ( (0 != strcasecmp (type,
    1402            0 :                          "VERIFICATION_COMPLETED")) ||
    1403            0 :        (no_verified) ||
    1404            0 :        (! verified) )
    1405              :   {
    1406              :     /* We don't need to re-confirm the failure by
    1407              :        asking the API again. */
    1408            0 :     log_failure (verifications);
    1409            0 :     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    1410              :                 "Webhook called with non-completion status: %s\n",
    1411              :                 type);
    1412            0 :     wh->response_code = MHD_HTTP_NO_CONTENT;
    1413            0 :     wh->resp = MHD_create_response_from_buffer_static (0,
    1414              :                                                        "");
    1415            0 :     wh->task = GNUNET_SCHEDULER_add_now (&async_webhook_reply,
    1416              :                                          wh);
    1417            0 :     return wh;
    1418              :   }
    1419              : 
    1420            0 :   eh = curl_easy_init ();
    1421            0 :   if (NULL == eh)
    1422              :   {
    1423            0 :     GNUNET_break (0);
    1424            0 :     wh->resp = TALER_MHD_make_error (
    1425              :       TALER_EC_GENERIC_ALLOCATION_FAILURE,
    1426              :       NULL);
    1427            0 :     wh->response_code = MHD_HTTP_INTERNAL_SERVER_ERROR;
    1428            0 :     wh->task = GNUNET_SCHEDULER_add_now (&async_webhook_reply,
    1429              :                                          wh);
    1430            0 :     return wh;
    1431              :   }
    1432              : 
    1433            0 :   GNUNET_asprintf (&wh->url,
    1434              :                    "https://api.kycaid.com/applicants/%s",
    1435              :                    applicant_id);
    1436            0 :   GNUNET_break (CURLE_OK ==
    1437              :                 curl_easy_setopt (eh,
    1438              :                                   CURLOPT_VERBOSE,
    1439              :                                   0));
    1440            0 :   GNUNET_assert (CURLE_OK ==
    1441              :                  curl_easy_setopt (eh,
    1442              :                                    CURLOPT_MAXREDIRS,
    1443              :                                    1L));
    1444            0 :   GNUNET_break (CURLE_OK ==
    1445              :                 curl_easy_setopt (eh,
    1446              :                                   CURLOPT_URL,
    1447              :                                   wh->url));
    1448            0 :   wh->job = GNUNET_CURL_job_add2 (ps->curl_ctx,
    1449              :                                   eh,
    1450            0 :                                   pd->slist,
    1451              :                                   &handle_webhook_finished,
    1452              :                                   wh);
    1453            0 :   return wh;
    1454              : }
    1455              : 
    1456              : 
    1457              : /**
    1458              :  * Initialize kycaid logic plugin
    1459              :  *
    1460              :  * @param cls a configuration instance
    1461              :  * @return NULL on error, otherwise a `struct TALER_KYCLOGIC_Plugin`
    1462              :  */
    1463              : void *
    1464              : libtaler_plugin_kyclogic_kycaid_init (void *cls);
    1465              : 
    1466              : /* declaration to avoid compiler warning */
    1467              : void *
    1468           61 : libtaler_plugin_kyclogic_kycaid_init (void *cls)
    1469              : {
    1470           61 :   const struct GNUNET_CONFIGURATION_Handle *cfg = cls;
    1471              :   struct TALER_KYCLOGIC_Plugin *plugin;
    1472              :   struct PluginState *ps;
    1473              : 
    1474           61 :   ps = GNUNET_new (struct PluginState);
    1475           61 :   ps->cfg = cfg;
    1476           61 :   if (GNUNET_OK !=
    1477           61 :       GNUNET_CONFIGURATION_get_value_string (cfg,
    1478              :                                              "exchange",
    1479              :                                              "BASE_URL",
    1480              :                                              &ps->exchange_base_url))
    1481              :   {
    1482            0 :     GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
    1483              :                                "exchange",
    1484              :                                "BASE_URL");
    1485            0 :     GNUNET_free (ps);
    1486            0 :     return NULL;
    1487              :   }
    1488              : 
    1489              :   ps->curl_ctx
    1490          122 :     = GNUNET_CURL_init (&GNUNET_CURL_gnunet_scheduler_reschedule,
    1491           61 :                         &ps->curl_rc);
    1492           61 :   if (NULL == ps->curl_ctx)
    1493              :   {
    1494            0 :     GNUNET_break (0);
    1495            0 :     GNUNET_free (ps->exchange_base_url);
    1496            0 :     GNUNET_free (ps);
    1497            0 :     return NULL;
    1498              :   }
    1499           61 :   ps->curl_rc = GNUNET_CURL_gnunet_rc_create (ps->curl_ctx);
    1500              : 
    1501           61 :   plugin = GNUNET_new (struct TALER_KYCLOGIC_Plugin);
    1502           61 :   plugin->cls = ps;
    1503              :   plugin->load_configuration
    1504           61 :     = &kycaid_load_configuration;
    1505              :   plugin->unload_configuration
    1506           61 :     = &kycaid_unload_configuration;
    1507              :   plugin->initiate
    1508           61 :     = &kycaid_initiate;
    1509              :   plugin->initiate_cancel
    1510           61 :     = &kycaid_initiate_cancel;
    1511              :   plugin->proof
    1512           61 :     = &kycaid_proof;
    1513              :   plugin->proof_cancel
    1514           61 :     = &kycaid_proof_cancel;
    1515              :   plugin->webhook
    1516           61 :     = &kycaid_webhook;
    1517              :   plugin->webhook_cancel
    1518           61 :     = &kycaid_webhook_cancel;
    1519           61 :   return plugin;
    1520              : }
    1521              : 
    1522              : 
    1523              : /**
    1524              :  * Unload authorization plugin
    1525              :  *
    1526              :  * @param cls a `struct TALER_KYCLOGIC_Plugin`
    1527              :  * @return NULL (always)
    1528              :  */
    1529              : void *
    1530              : libtaler_plugin_kyclogic_kycaid_done (void *cls);
    1531              : 
    1532              : /* declaration to avoid compiler warning */
    1533              : void *
    1534           61 : libtaler_plugin_kyclogic_kycaid_done (void *cls)
    1535              : {
    1536           61 :   struct TALER_KYCLOGIC_Plugin *plugin = cls;
    1537           61 :   struct PluginState *ps = plugin->cls;
    1538              : 
    1539           61 :   if (NULL != ps->curl_ctx)
    1540              :   {
    1541           61 :     GNUNET_CURL_fini (ps->curl_ctx);
    1542           61 :     ps->curl_ctx = NULL;
    1543              :   }
    1544           61 :   if (NULL != ps->curl_rc)
    1545              :   {
    1546           61 :     GNUNET_CURL_gnunet_rc_destroy (ps->curl_rc);
    1547           61 :     ps->curl_rc = NULL;
    1548              :   }
    1549           61 :   GNUNET_free (ps->exchange_base_url);
    1550           61 :   GNUNET_free (ps);
    1551           61 :   GNUNET_free (plugin);
    1552           61 :   return NULL;
    1553              : }
        

Generated by: LCOV version 2.0-1