LCOV - code coverage report
Current view: top level - kyclogic - plugin_kyclogic_persona.c (source / functions) Coverage Total Hit
Test: coverage.info Lines: 11.3 % 683 77
Test Date: 2025-12-28 14:06:02 Functions: 17.4 % 23 4

            Line data    Source code
       1              : /*
       2              :   This file is part of GNU Taler
       3              :   Copyright (C) 2022, 2023 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_persona.c
      18              :  * @brief persona for an authentication flow logic
      19              :  * @author Christian Grothoff
      20              :  */
      21              : #include "taler/platform.h"
      22              : #include "taler/taler_attributes.h"
      23              : #include "taler/taler_kyclogic_plugin.h"
      24              : #include "taler/taler_mhd_lib.h"
      25              : #include "taler/taler_curl_lib.h"
      26              : #include "taler/taler_json_lib.h"
      27              : #include "taler/taler_kyclogic_lib.h"
      28              : #include "taler/taler_templating_lib.h"
      29              : #include <regex.h>
      30              : #include "taler/taler_util.h"
      31              : 
      32              : 
      33              : /**
      34              :  * Which version of the persona API are we implementing?
      35              :  */
      36              : #define PERSONA_VERSION "2021-07-05"
      37              : 
      38              : /**
      39              :  * Saves the state of a plugin.
      40              :  */
      41              : struct PluginState
      42              : {
      43              : 
      44              :   /**
      45              :    * Our base URL.
      46              :    */
      47              :   char *exchange_base_url;
      48              : 
      49              :   /**
      50              :    * Our global configuration.
      51              :    */
      52              :   const struct GNUNET_CONFIGURATION_Handle *cfg;
      53              : 
      54              :   /**
      55              :    * Context for CURL operations (useful to the event loop)
      56              :    */
      57              :   struct GNUNET_CURL_Context *curl_ctx;
      58              : 
      59              :   /**
      60              :    * Context for integrating @e curl_ctx with the
      61              :    * GNUnet event loop.
      62              :    */
      63              :   struct GNUNET_CURL_RescheduleContext *curl_rc;
      64              : 
      65              :   /**
      66              :    * Authorization token to use when receiving webhooks from the Persona
      67              :    * service.  Optional.  Note that webhooks are *global* and not per
      68              :    * template.
      69              :    */
      70              :   char *webhook_token;
      71              : 
      72              : 
      73              : };
      74              : 
      75              : 
      76              : /**
      77              :  * Keeps the plugin-specific state for
      78              :  * a given configuration section.
      79              :  */
      80              : struct TALER_KYCLOGIC_ProviderDetails
      81              : {
      82              : 
      83              :   /**
      84              :    * Overall plugin state.
      85              :    */
      86              :   struct PluginState *ps;
      87              : 
      88              :   /**
      89              :    * Configuration section that configured us.
      90              :    */
      91              :   char *section;
      92              : 
      93              :   /**
      94              :    * Salt to use for idempotency.
      95              :    */
      96              :   char *salt;
      97              : 
      98              :   /**
      99              :    * Authorization token to use when talking
     100              :    * to the service.
     101              :    */
     102              :   char *auth_token;
     103              : 
     104              :   /**
     105              :    * Template ID for the KYC check to perform.
     106              :    */
     107              :   char *template_id;
     108              : 
     109              :   /**
     110              :    * Subdomain to use.
     111              :    */
     112              :   char *subdomain;
     113              : 
     114              :   /**
     115              :    * Name of the program we use to convert outputs
     116              :    * from Persona into our JSON inputs.
     117              :    */
     118              :   char *conversion_binary;
     119              : 
     120              :   /**
     121              :    * Where to redirect the client upon completion.
     122              :    */
     123              :   char *post_kyc_redirect_url;
     124              : 
     125              :   /**
     126              :    * Validity time for a successful KYC process.
     127              :    */
     128              :   struct GNUNET_TIME_Relative validity;
     129              : 
     130              :   /**
     131              :    * Curl-ready authentication header to use.
     132              :    */
     133              :   struct curl_slist *slist;
     134              : 
     135              : };
     136              : 
     137              : 
     138              : /**
     139              :  * Handle for an initiation operation.
     140              :  */
     141              : struct TALER_KYCLOGIC_InitiateHandle
     142              : {
     143              : 
     144              :   /**
     145              :    * Hash of the payto:// URI we are initiating the KYC for.
     146              :    */
     147              :   struct TALER_NormalizedPaytoHashP h_payto;
     148              : 
     149              :   /**
     150              :    * UUID being checked.
     151              :    */
     152              :   uint64_t legitimization_uuid;
     153              : 
     154              :   /**
     155              :    * Our configuration details.
     156              :    */
     157              :   const struct TALER_KYCLOGIC_ProviderDetails *pd;
     158              : 
     159              :   /**
     160              :    * Continuation to call.
     161              :    */
     162              :   TALER_KYCLOGIC_InitiateCallback cb;
     163              : 
     164              :   /**
     165              :    * Closure for @a cb.
     166              :    */
     167              :   void *cb_cls;
     168              : 
     169              :   /**
     170              :    * Context for #TEH_curl_easy_post(). Keeps the data that must
     171              :    * persist for Curl to make the upload.
     172              :    */
     173              :   struct TALER_CURL_PostContext ctx;
     174              : 
     175              :   /**
     176              :    * Handle for the request.
     177              :    */
     178              :   struct GNUNET_CURL_Job *job;
     179              : 
     180              :   /**
     181              :    * URL of the cURL request.
     182              :    */
     183              :   char *url;
     184              : 
     185              :   /**
     186              :    * Request-specific headers to use.
     187              :    */
     188              :   struct curl_slist *slist;
     189              : 
     190              : };
     191              : 
     192              : 
     193              : /**
     194              :  * Handle for an KYC proof operation.
     195              :  */
     196              : struct TALER_KYCLOGIC_ProofHandle
     197              : {
     198              : 
     199              :   /**
     200              :    * Overall plugin state.
     201              :    */
     202              :   struct PluginState *ps;
     203              : 
     204              :   /**
     205              :    * Our configuration details.
     206              :    */
     207              :   const struct TALER_KYCLOGIC_ProviderDetails *pd;
     208              : 
     209              :   /**
     210              :    * Continuation to call.
     211              :    */
     212              :   TALER_KYCLOGIC_ProofCallback cb;
     213              : 
     214              :   /**
     215              :    * Closure for @e cb.
     216              :    */
     217              :   void *cb_cls;
     218              : 
     219              :   /**
     220              :    * Connection we are handling.
     221              :    */
     222              :   struct MHD_Connection *connection;
     223              : 
     224              :   /**
     225              :    * Task for asynchronous execution.
     226              :    */
     227              :   struct GNUNET_SCHEDULER_Task *task;
     228              : 
     229              :   /**
     230              :    * Handle for the request.
     231              :    */
     232              :   struct GNUNET_CURL_Job *job;
     233              : 
     234              :   /**
     235              :    * URL of the cURL request.
     236              :    */
     237              :   char *url;
     238              : 
     239              :   /**
     240              :    * Handle to an external process that converts the
     241              :    * Persona response to our internal format.
     242              :    */
     243              :   struct TALER_JSON_ExternalConversion *ec;
     244              : 
     245              :   /**
     246              :    * Hash of the payto:// URI we are checking the KYC for.
     247              :    */
     248              :   struct TALER_NormalizedPaytoHashP h_payto;
     249              : 
     250              :   /**
     251              :    * Row in the legitimization processes of the
     252              :    * legitimization proof that is being checked.
     253              :    */
     254              :   uint64_t process_row;
     255              : 
     256              :   /**
     257              :    * Account ID at the provider.
     258              :    */
     259              :   char *provider_user_id;
     260              : 
     261              :   /**
     262              :    * Account ID from the service.
     263              :    */
     264              :   char *account_id;
     265              : 
     266              :   /**
     267              :    * Inquiry ID at the provider.
     268              :    */
     269              :   char *inquiry_id;
     270              : };
     271              : 
     272              : 
     273              : /**
     274              :  * Handle for an KYC Web hook operation.
     275              :  */
     276              : struct TALER_KYCLOGIC_WebhookHandle
     277              : {
     278              : 
     279              :   /**
     280              :    * Continuation to call when done.
     281              :    */
     282              :   TALER_KYCLOGIC_WebhookCallback cb;
     283              : 
     284              :   /**
     285              :    * Closure for @a cb.
     286              :    */
     287              :   void *cb_cls;
     288              : 
     289              :   /**
     290              :    * Task for asynchronous execution.
     291              :    */
     292              :   struct GNUNET_SCHEDULER_Task *task;
     293              : 
     294              :   /**
     295              :    * Overall plugin state.
     296              :    */
     297              :   struct PluginState *ps;
     298              : 
     299              :   /**
     300              :    * Our configuration details.
     301              :    */
     302              :   const struct TALER_KYCLOGIC_ProviderDetails *pd;
     303              : 
     304              :   /**
     305              :    * Connection we are handling.
     306              :    */
     307              :   struct MHD_Connection *connection;
     308              : 
     309              :   /**
     310              :    * Verification ID from the service.
     311              :    */
     312              :   char *inquiry_id;
     313              : 
     314              :   /**
     315              :    * Account ID from the service.
     316              :    */
     317              :   char *account_id;
     318              : 
     319              :   /**
     320              :    * URL of the cURL request.
     321              :    */
     322              :   char *url;
     323              : 
     324              :   /**
     325              :    * Handle for the request.
     326              :    */
     327              :   struct GNUNET_CURL_Job *job;
     328              : 
     329              :   /**
     330              :    * Response to return asynchronously.
     331              :    */
     332              :   struct MHD_Response *resp;
     333              : 
     334              :   /**
     335              :    * ID of the template the webhook is about,
     336              :    * according to the service.
     337              :    */
     338              :   const char *template_id;
     339              : 
     340              :   /**
     341              :    * Handle to an external process that converts the
     342              :    * Persona response to our internal format.
     343              :    */
     344              :   struct TALER_JSON_ExternalConversion *ec;
     345              : 
     346              :   /**
     347              :    * Our account ID.
     348              :    */
     349              :   struct TALER_NormalizedPaytoHashP h_payto;
     350              : 
     351              :   /**
     352              :    * UUID being checked.
     353              :    */
     354              :   uint64_t process_row;
     355              : 
     356              :   /**
     357              :    * HTTP status returned by Persona to us.
     358              :    */
     359              :   unsigned int persona_http_status;
     360              : 
     361              :   /**
     362              :    * HTTP response code to return asynchronously.
     363              :    */
     364              :   unsigned int response_code;
     365              : 
     366              :   /**
     367              :    * True if @e h_payto is for a wallet.
     368              :    */
     369              :   bool is_wallet;
     370              : };
     371              : 
     372              : 
     373              : /**
     374              :  * Release configuration resources previously loaded
     375              :  *
     376              :  * @param[in] pd configuration to release
     377              :  */
     378              : static void
     379           61 : persona_unload_configuration (struct TALER_KYCLOGIC_ProviderDetails *pd)
     380              : {
     381           61 :   curl_slist_free_all (pd->slist);
     382           61 :   GNUNET_free (pd->auth_token);
     383           61 :   GNUNET_free (pd->template_id);
     384           61 :   GNUNET_free (pd->subdomain);
     385           61 :   GNUNET_free (pd->conversion_binary);
     386           61 :   GNUNET_free (pd->salt);
     387           61 :   GNUNET_free (pd->section);
     388           61 :   GNUNET_free (pd->post_kyc_redirect_url);
     389           61 :   GNUNET_free (pd);
     390           61 : }
     391              : 
     392              : 
     393              : /**
     394              :  * Load the configuration of the KYC provider.
     395              :  *
     396              :  * @param cls closure
     397              :  * @param provider_section_name configuration section to parse
     398              :  * @return NULL if configuration is invalid
     399              :  */
     400              : static struct TALER_KYCLOGIC_ProviderDetails *
     401           61 : persona_load_configuration (void *cls,
     402              :                             const char *provider_section_name)
     403              : {
     404           61 :   struct PluginState *ps = cls;
     405              :   struct TALER_KYCLOGIC_ProviderDetails *pd;
     406              : 
     407           61 :   pd = GNUNET_new (struct TALER_KYCLOGIC_ProviderDetails);
     408           61 :   pd->ps = ps;
     409           61 :   pd->section = GNUNET_strdup (provider_section_name);
     410           61 :   if (GNUNET_OK !=
     411           61 :       GNUNET_CONFIGURATION_get_value_time (ps->cfg,
     412              :                                            provider_section_name,
     413              :                                            "KYC_PERSONA_VALIDITY",
     414              :                                            &pd->validity))
     415              :   {
     416            0 :     GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
     417              :                                provider_section_name,
     418              :                                "KYC_PERSONA_VALIDITY");
     419            0 :     persona_unload_configuration (pd);
     420            0 :     return NULL;
     421              :   }
     422           61 :   if (GNUNET_OK !=
     423           61 :       GNUNET_CONFIGURATION_get_value_string (ps->cfg,
     424              :                                              provider_section_name,
     425              :                                              "KYC_PERSONA_AUTH_TOKEN",
     426              :                                              &pd->auth_token))
     427              :   {
     428            0 :     GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
     429              :                                provider_section_name,
     430              :                                "KYC_PERSONA_AUTH_TOKEN");
     431            0 :     persona_unload_configuration (pd);
     432            0 :     return NULL;
     433              :   }
     434           61 :   if (GNUNET_OK !=
     435           61 :       GNUNET_CONFIGURATION_get_value_string (ps->cfg,
     436              :                                              provider_section_name,
     437              :                                              "KYC_PERSONA_SALT",
     438              :                                              &pd->salt))
     439              :   {
     440              :     uint32_t salt[8];
     441              : 
     442           61 :     GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_NONCE,
     443              :                                 salt,
     444              :                                 sizeof (salt));
     445           61 :     pd->salt = GNUNET_STRINGS_data_to_string_alloc (salt,
     446              :                                                     sizeof (salt));
     447              :   }
     448           61 :   if (GNUNET_OK !=
     449           61 :       GNUNET_CONFIGURATION_get_value_string (ps->cfg,
     450              :                                              provider_section_name,
     451              :                                              "KYC_PERSONA_SUBDOMAIN",
     452              :                                              &pd->subdomain))
     453              :   {
     454            0 :     GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
     455              :                                provider_section_name,
     456              :                                "KYC_PERSONA_SUBDOMAIN");
     457            0 :     persona_unload_configuration (pd);
     458            0 :     return NULL;
     459              :   }
     460           61 :   if (GNUNET_OK !=
     461           61 :       GNUNET_CONFIGURATION_get_value_string (ps->cfg,
     462              :                                              provider_section_name,
     463              :                                              "KYC_PERSONA_CONVERTER_HELPER",
     464              :                                              &pd->conversion_binary))
     465              :   {
     466            0 :     GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
     467              :                                provider_section_name,
     468              :                                "KYC_PERSONA_CONVERTER_HELPER");
     469            0 :     persona_unload_configuration (pd);
     470            0 :     return NULL;
     471              :   }
     472           61 :   if (GNUNET_OK !=
     473           61 :       GNUNET_CONFIGURATION_get_value_string (ps->cfg,
     474              :                                              provider_section_name,
     475              :                                              "KYC_PERSONA_POST_URL",
     476              :                                              &pd->post_kyc_redirect_url))
     477              :   {
     478            0 :     GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
     479              :                                provider_section_name,
     480              :                                "KYC_PERSONA_POST_URL");
     481            0 :     persona_unload_configuration (pd);
     482            0 :     return NULL;
     483              :   }
     484           61 :   if (GNUNET_OK !=
     485           61 :       GNUNET_CONFIGURATION_get_value_string (ps->cfg,
     486              :                                              provider_section_name,
     487              :                                              "KYC_PERSONA_TEMPLATE_ID",
     488              :                                              &pd->template_id))
     489              :   {
     490            0 :     GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
     491              :                                provider_section_name,
     492              :                                "KYC_PERSONA_TEMPLATE_ID");
     493            0 :     persona_unload_configuration (pd);
     494            0 :     return NULL;
     495              :   }
     496              :   {
     497              :     char *auth;
     498              : 
     499           61 :     GNUNET_asprintf (&auth,
     500              :                      "%s: Bearer %s",
     501              :                      MHD_HTTP_HEADER_AUTHORIZATION,
     502              :                      pd->auth_token);
     503           61 :     pd->slist = curl_slist_append (NULL,
     504              :                                    auth);
     505           61 :     GNUNET_free (auth);
     506           61 :     GNUNET_asprintf (&auth,
     507              :                      "%s: %s",
     508              :                      MHD_HTTP_HEADER_ACCEPT,
     509              :                      "application/json");
     510           61 :     pd->slist = curl_slist_append (pd->slist,
     511              :                                    "Persona-Version: "
     512              :                                    PERSONA_VERSION);
     513           61 :     GNUNET_free (auth);
     514              :   }
     515           61 :   return pd;
     516              : }
     517              : 
     518              : 
     519              : /**
     520              :  * Cancel KYC check initiation.
     521              :  *
     522              :  * @param[in] ih handle of operation to cancel
     523              :  */
     524              : static void
     525            0 : persona_initiate_cancel (struct TALER_KYCLOGIC_InitiateHandle *ih)
     526              : {
     527            0 :   if (NULL != ih->job)
     528              :   {
     529            0 :     GNUNET_CURL_job_cancel (ih->job);
     530            0 :     ih->job = NULL;
     531              :   }
     532            0 :   GNUNET_free (ih->url);
     533            0 :   TALER_curl_easy_post_finished (&ih->ctx);
     534            0 :   curl_slist_free_all (ih->slist);
     535            0 :   GNUNET_free (ih);
     536            0 : }
     537              : 
     538              : 
     539              : /**
     540              :  * Function called when we're done processing the
     541              :  * HTTP POST "/api/v1/inquiries" request.
     542              :  *
     543              :  * @param cls the `struct TALER_KYCLOGIC_InitiateHandle`
     544              :  * @param response_code HTTP response code, 0 on error
     545              :  * @param response parsed JSON result, NULL on error
     546              :  */
     547              : static void
     548            0 : handle_initiate_finished (void *cls,
     549              :                           long response_code,
     550              :                           const void *response)
     551              : {
     552            0 :   struct TALER_KYCLOGIC_InitiateHandle *ih = cls;
     553            0 :   const struct TALER_KYCLOGIC_ProviderDetails *pd = ih->pd;
     554            0 :   const json_t *j = response;
     555              :   char *url;
     556              :   json_t *data;
     557              :   const char *type;
     558              :   const char *inquiry_id;
     559              :   const char *persona_account_id;
     560              :   const char *ename;
     561              :   unsigned int eline;
     562              :   struct GNUNET_JSON_Specification spec[] = {
     563            0 :     GNUNET_JSON_spec_string ("type",
     564              :                              &type),
     565            0 :     GNUNET_JSON_spec_string ("id",
     566              :                              &inquiry_id),
     567            0 :     GNUNET_JSON_spec_end ()
     568              :   };
     569              : 
     570            0 :   ih->job = NULL;
     571            0 :   switch (response_code)
     572              :   {
     573            0 :   case MHD_HTTP_CREATED:
     574              :     /* handled below */
     575            0 :     break;
     576            0 :   case MHD_HTTP_UNAUTHORIZED:
     577              :   case MHD_HTTP_FORBIDDEN:
     578              :     {
     579              :       const char *msg;
     580              : 
     581            0 :       msg = json_string_value (
     582            0 :         json_object_get (
     583            0 :           json_array_get (
     584            0 :             json_object_get (j,
     585              :                              "errors"),
     586              :             0),
     587              :           "title"));
     588              : 
     589            0 :       ih->cb (ih->cb_cls,
     590              :               TALER_EC_EXCHANGE_KYC_CHECK_AUTHORIZATION_FAILED,
     591              :               NULL,
     592              :               NULL,
     593              :               NULL,
     594              :               msg);
     595            0 :       persona_initiate_cancel (ih);
     596            0 :       return;
     597              :     }
     598            0 :   case MHD_HTTP_NOT_FOUND:
     599              :   case MHD_HTTP_CONFLICT:
     600              :     {
     601              :       const char *msg;
     602              : 
     603            0 :       msg = json_string_value (
     604            0 :         json_object_get (
     605            0 :           json_array_get (
     606            0 :             json_object_get (j,
     607              :                              "errors"),
     608              :             0),
     609              :           "title"));
     610              : 
     611            0 :       ih->cb (ih->cb_cls,
     612              :               TALER_EC_EXCHANGE_KYC_GENERIC_PROVIDER_UNEXPECTED_REPLY,
     613              :               NULL,
     614              :               NULL,
     615              :               NULL,
     616              :               msg);
     617            0 :       persona_initiate_cancel (ih);
     618            0 :       return;
     619              :     }
     620            0 :   case MHD_HTTP_BAD_REQUEST:
     621              :   case MHD_HTTP_UNPROCESSABLE_ENTITY:
     622              :     {
     623              :       const char *msg;
     624              : 
     625            0 :       GNUNET_break (0);
     626            0 :       json_dumpf (j,
     627              :                   stderr,
     628              :                   JSON_INDENT (2));
     629            0 :       msg = json_string_value (
     630            0 :         json_object_get (
     631            0 :           json_array_get (
     632            0 :             json_object_get (j,
     633              :                              "errors"),
     634              :             0),
     635              :           "title"));
     636              : 
     637            0 :       ih->cb (ih->cb_cls,
     638              :               TALER_EC_EXCHANGE_KYC_GENERIC_LOGIC_BUG,
     639              :               NULL,
     640              :               NULL,
     641              :               NULL,
     642              :               msg);
     643            0 :       persona_initiate_cancel (ih);
     644            0 :       return;
     645              :     }
     646            0 :   case MHD_HTTP_TOO_MANY_REQUESTS:
     647              :     {
     648              :       const char *msg;
     649              : 
     650            0 :       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
     651              :                   "Rate limiting requested:\n");
     652            0 :       json_dumpf (j,
     653              :                   stderr,
     654              :                   JSON_INDENT (2));
     655            0 :       msg = json_string_value (
     656            0 :         json_object_get (
     657            0 :           json_array_get (
     658            0 :             json_object_get (j,
     659              :                              "errors"),
     660              :             0),
     661              :           "title"));
     662            0 :       ih->cb (ih->cb_cls,
     663              :               TALER_EC_EXCHANGE_KYC_GENERIC_PROVIDER_RATE_LIMIT_EXCEEDED,
     664              :               NULL,
     665              :               NULL,
     666              :               NULL,
     667              :               msg);
     668            0 :       persona_initiate_cancel (ih);
     669            0 :       return;
     670              :     }
     671            0 :   default:
     672              :     {
     673              :       char *err;
     674              : 
     675            0 :       GNUNET_break_op (0);
     676            0 :       json_dumpf (j,
     677              :                   stderr,
     678              :                   JSON_INDENT (2));
     679            0 :       GNUNET_asprintf (&err,
     680              :                        "Unexpected HTTP status %u from Persona\n",
     681              :                        (unsigned int) response_code);
     682            0 :       ih->cb (ih->cb_cls,
     683              :               TALER_EC_EXCHANGE_KYC_GENERIC_PROVIDER_UNEXPECTED_REPLY,
     684              :               NULL,
     685              :               NULL,
     686              :               NULL,
     687              :               err);
     688            0 :       GNUNET_free (err);
     689            0 :       persona_initiate_cancel (ih);
     690            0 :       return;
     691              :     }
     692              :   }
     693            0 :   data = json_object_get (j,
     694              :                           "data");
     695            0 :   if (NULL == data)
     696              :   {
     697            0 :     GNUNET_break_op (0);
     698            0 :     json_dumpf (j,
     699              :                 stderr,
     700              :                 JSON_INDENT (2));
     701            0 :     persona_initiate_cancel (ih);
     702            0 :     return;
     703              :   }
     704              : 
     705            0 :   if (GNUNET_OK !=
     706            0 :       GNUNET_JSON_parse (data,
     707              :                          spec,
     708              :                          &ename,
     709              :                          &eline))
     710              :   {
     711            0 :     GNUNET_break_op (0);
     712            0 :     json_dumpf (j,
     713              :                 stderr,
     714              :                 JSON_INDENT (2));
     715            0 :     ih->cb (ih->cb_cls,
     716              :             TALER_EC_EXCHANGE_KYC_GENERIC_PROVIDER_UNEXPECTED_REPLY,
     717              :             NULL,
     718              :             NULL,
     719              :             NULL,
     720              :             ename);
     721            0 :     persona_initiate_cancel (ih);
     722            0 :     return;
     723              :   }
     724              :   persona_account_id
     725            0 :     = json_string_value (
     726            0 :         json_object_get (
     727            0 :           json_object_get (
     728            0 :             json_object_get (
     729            0 :               json_object_get (data,
     730              :                                "relationships"),
     731              :               "account"),
     732              :             "data"),
     733              :           "id"));
     734            0 :   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     735              :               "Starting inquiry %s for Persona account %s\n",
     736              :               inquiry_id,
     737              :               persona_account_id);
     738            0 :   GNUNET_asprintf (&url,
     739              :                    "https://%s.withpersona.com/verify"
     740              :                    "?inquiry-id=%s",
     741            0 :                    pd->subdomain,
     742              :                    inquiry_id);
     743            0 :   ih->cb (ih->cb_cls,
     744              :           TALER_EC_NONE,
     745              :           url,
     746              :           persona_account_id,
     747              :           inquiry_id,
     748              :           NULL);
     749            0 :   GNUNET_free (url);
     750            0 :   persona_initiate_cancel (ih);
     751              : }
     752              : 
     753              : 
     754              : /**
     755              :  * Initiate KYC check.
     756              :  *
     757              :  * @param cls the @e cls of this struct with the plugin-specific state
     758              :  * @param pd provider configuration details
     759              :  * @param account_id which account to trigger process for
     760              :  * @param legitimization_uuid unique ID for the legitimization process
     761              :  * @param context additional contextual information for the legi process
     762              :  * @param cb function to call with the result
     763              :  * @param cb_cls closure for @a cb
     764              :  * @return handle to cancel operation early
     765              :  */
     766              : static struct TALER_KYCLOGIC_InitiateHandle *
     767            0 : persona_initiate (void *cls,
     768              :                   const struct TALER_KYCLOGIC_ProviderDetails *pd,
     769              :                   const struct TALER_NormalizedPaytoHashP *account_id,
     770              :                   uint64_t legitimization_uuid,
     771              :                   const json_t *context,
     772              :                   TALER_KYCLOGIC_InitiateCallback cb,
     773              :                   void *cb_cls)
     774              : {
     775            0 :   struct PluginState *ps = cls;
     776              :   struct TALER_KYCLOGIC_InitiateHandle *ih;
     777              :   json_t *body;
     778              :   CURL *eh;
     779              : 
     780              :   (void) context;
     781            0 :   eh = curl_easy_init ();
     782            0 :   if (NULL == eh)
     783              :   {
     784            0 :     GNUNET_break (0);
     785            0 :     return NULL;
     786              :   }
     787            0 :   ih = GNUNET_new (struct TALER_KYCLOGIC_InitiateHandle);
     788            0 :   ih->legitimization_uuid = legitimization_uuid;
     789            0 :   ih->cb = cb;
     790            0 :   ih->cb_cls = cb_cls;
     791            0 :   ih->h_payto = *account_id;
     792            0 :   ih->pd = pd;
     793            0 :   GNUNET_asprintf (&ih->url,
     794              :                    "https://withpersona.com/api/v1/inquiries");
     795              :   {
     796              :     char *payto_s;
     797              :     char *proof_url;
     798              :     char ref_s[24];
     799              : 
     800            0 :     GNUNET_snprintf (ref_s,
     801              :                      sizeof (ref_s),
     802              :                      "%llu",
     803            0 :                      (unsigned long long) ih->legitimization_uuid);
     804            0 :     payto_s = GNUNET_STRINGS_data_to_string_alloc (&ih->h_payto,
     805              :                                                    sizeof (ih->h_payto));
     806            0 :     GNUNET_break ('/' ==
     807              :                   pd->ps->exchange_base_url[strlen (
     808              :                                               pd->ps->exchange_base_url) - 1]);
     809            0 :     GNUNET_asprintf (&proof_url,
     810              :                      "%skyc-proof/%s?state=%s",
     811            0 :                      pd->ps->exchange_base_url,
     812            0 :                      &pd->section[strlen ("kyc-provider-")],
     813              :                      payto_s);
     814            0 :     body = GNUNET_JSON_PACK (
     815              :       GNUNET_JSON_pack_object_steal (
     816              :         "data",
     817              :         GNUNET_JSON_PACK (
     818              :           GNUNET_JSON_pack_object_steal (
     819              :             "attributes",
     820              :             GNUNET_JSON_PACK (
     821              :               GNUNET_JSON_pack_string ("inquiry_template_id",
     822              :                                        pd->template_id),
     823              :               GNUNET_JSON_pack_string ("reference_id",
     824              :                                        ref_s),
     825              :               GNUNET_JSON_pack_string ("redirect_uri",
     826              :                                        proof_url)
     827              :               )))));
     828            0 :     GNUNET_assert (NULL != body);
     829            0 :     GNUNET_free (payto_s);
     830            0 :     GNUNET_free (proof_url);
     831              :   }
     832            0 :   GNUNET_break (CURLE_OK ==
     833              :                 curl_easy_setopt (eh,
     834              :                                   CURLOPT_VERBOSE,
     835              :                                   0));
     836            0 :   GNUNET_assert (CURLE_OK ==
     837              :                  curl_easy_setopt (eh,
     838              :                                    CURLOPT_MAXREDIRS,
     839              :                                    1L));
     840            0 :   GNUNET_break (CURLE_OK ==
     841              :                 curl_easy_setopt (eh,
     842              :                                   CURLOPT_URL,
     843              :                                   ih->url));
     844            0 :   ih->ctx.disable_compression = true;
     845            0 :   if (GNUNET_OK !=
     846            0 :       TALER_curl_easy_post (&ih->ctx,
     847              :                             eh,
     848              :                             body))
     849              :   {
     850            0 :     GNUNET_break (0);
     851            0 :     GNUNET_free (ih->url);
     852            0 :     GNUNET_free (ih);
     853            0 :     curl_easy_cleanup (eh);
     854            0 :     json_decref (body);
     855            0 :     return NULL;
     856              :   }
     857            0 :   json_decref (body);
     858            0 :   ih->job = GNUNET_CURL_job_add2 (ps->curl_ctx,
     859              :                                   eh,
     860            0 :                                   ih->ctx.headers,
     861              :                                   &handle_initiate_finished,
     862              :                                   ih);
     863            0 :   GNUNET_CURL_extend_headers (ih->job,
     864            0 :                               pd->slist);
     865              :   {
     866              :     char *ikh;
     867              : 
     868            0 :     GNUNET_asprintf (&ikh,
     869              :                      "Idempotency-Key: %llu-%s",
     870            0 :                      (unsigned long long) ih->legitimization_uuid,
     871            0 :                      pd->salt);
     872            0 :     ih->slist = curl_slist_append (NULL,
     873              :                                    ikh);
     874            0 :     GNUNET_free (ikh);
     875              :   }
     876            0 :   GNUNET_CURL_extend_headers (ih->job,
     877            0 :                               ih->slist);
     878            0 :   return ih;
     879              : }
     880              : 
     881              : 
     882              : /**
     883              :  * Cancel KYC proof.
     884              :  *
     885              :  * @param[in] ph handle of operation to cancel
     886              :  */
     887              : static void
     888            0 : persona_proof_cancel (struct TALER_KYCLOGIC_ProofHandle *ph)
     889              : {
     890            0 :   if (NULL != ph->job)
     891              :   {
     892            0 :     GNUNET_CURL_job_cancel (ph->job);
     893            0 :     ph->job = NULL;
     894              :   }
     895            0 :   if (NULL != ph->ec)
     896              :   {
     897            0 :     TALER_JSON_external_conversion_stop (ph->ec);
     898            0 :     ph->ec = NULL;
     899              :   }
     900            0 :   GNUNET_free (ph->url);
     901            0 :   GNUNET_free (ph->provider_user_id);
     902            0 :   GNUNET_free (ph->account_id);
     903            0 :   GNUNET_free (ph->inquiry_id);
     904            0 :   GNUNET_free (ph);
     905            0 : }
     906              : 
     907              : 
     908              : /**
     909              :  * Call @a ph callback with the operation result.
     910              :  *
     911              :  * @param ph proof handle to generate reply for
     912              :  * @param status status to return
     913              :  * @param account_id account to return
     914              :  * @param inquiry_id inquiry ID to supply
     915              :  * @param http_status HTTP status to use
     916              :  * @param template template to instantiate
     917              :  * @param[in] body body for the template to use (reference
     918              :  *         is consumed)
     919              :  */
     920              : static void
     921            0 : proof_generic_reply (struct TALER_KYCLOGIC_ProofHandle *ph,
     922              :                      enum TALER_KYCLOGIC_KycStatus status,
     923              :                      const char *account_id,
     924              :                      const char *inquiry_id,
     925              :                      unsigned int http_status,
     926              :                      const char *template,
     927              :                      json_t *body)
     928              : {
     929              :   struct MHD_Response *resp;
     930              :   enum GNUNET_GenericReturnValue ret;
     931              : 
     932              :   /* This API is not usable for successful replies */
     933            0 :   GNUNET_assert (TALER_KYCLOGIC_STATUS_SUCCESS != status);
     934            0 :   ret = TALER_TEMPLATING_build (ph->connection,
     935              :                                 &http_status,
     936              :                                 template,
     937              :                                 NULL,
     938              :                                 NULL,
     939              :                                 body,
     940              :                                 &resp);
     941            0 :   json_decref (body);
     942            0 :   if (GNUNET_SYSERR == ret)
     943              :   {
     944            0 :     GNUNET_break (0);
     945            0 :     resp = NULL; /* good luck */
     946              :   }
     947            0 :   ph->cb (ph->cb_cls,
     948              :           status,
     949            0 :           ph->pd->section,
     950              :           account_id,
     951              :           inquiry_id,
     952            0 :           GNUNET_TIME_UNIT_ZERO_ABS,
     953              :           NULL,
     954              :           http_status,
     955              :           resp);
     956            0 : }
     957              : 
     958              : 
     959              : /**
     960              :  * Call @a ph callback with HTTP error response.
     961              :  *
     962              :  * @param ph proof handle to generate reply for
     963              :  * @param inquiry_id inquiry ID to supply
     964              :  * @param http_status HTTP status to use
     965              :  * @param template template to instantiate
     966              :  * @param[in] body body for the template to use (reference
     967              :  *         is consumed)
     968              :  */
     969              : static void
     970            0 : proof_reply_error (struct TALER_KYCLOGIC_ProofHandle *ph,
     971              :                    const char *inquiry_id,
     972              :                    unsigned int http_status,
     973              :                    const char *template,
     974              :                    json_t *body)
     975              : {
     976            0 :   proof_generic_reply (ph,
     977              :                        TALER_KYCLOGIC_STATUS_PROVIDER_FAILED,
     978              :                        NULL, /* user id */
     979              :                        inquiry_id,
     980              :                        http_status,
     981              :                        template,
     982              :                        body);
     983            0 : }
     984              : 
     985              : 
     986              : /**
     987              :  * Return a response for the @a ph request indicating a
     988              :  * protocol violation by the Persona server.
     989              :  *
     990              :  * @param[in,out] ph request we are processing
     991              :  * @param response_code HTTP status returned by Persona
     992              :  * @param inquiry_id ID of the inquiry this is about
     993              :  * @param detail where the response was wrong
     994              :  * @param data full response data to output
     995              :  */
     996              : static void
     997            0 : return_invalid_response (struct TALER_KYCLOGIC_ProofHandle *ph,
     998              :                          unsigned int response_code,
     999              :                          const char *inquiry_id,
    1000              :                          const char *detail,
    1001              :                          const json_t *data)
    1002              : {
    1003            0 :   proof_reply_error (
    1004              :     ph,
    1005              :     inquiry_id,
    1006              :     MHD_HTTP_BAD_GATEWAY,
    1007              :     "persona-invalid-response",
    1008            0 :     GNUNET_JSON_PACK (
    1009              :       GNUNET_JSON_pack_uint64 ("persona_http_status",
    1010              :                                response_code),
    1011              :       GNUNET_JSON_pack_string ("persona_inquiry_id",
    1012              :                                inquiry_id),
    1013              :       TALER_JSON_pack_ec (
    1014              :         TALER_EC_EXCHANGE_KYC_GENERIC_PROVIDER_UNEXPECTED_REPLY),
    1015              :       GNUNET_JSON_pack_string ("detail",
    1016              :                                detail),
    1017              :       GNUNET_JSON_pack_allow_null (
    1018              :         GNUNET_JSON_pack_object_incref ("data",
    1019              :                                         (json_t *)
    1020              :                                         data))));
    1021            0 : }
    1022              : 
    1023              : 
    1024              : /**
    1025              :  * Start the external conversion helper.
    1026              :  *
    1027              :  * @param pd configuration details
    1028              :  * @param attr attributes to give to the helper
    1029              :  * @param cb function to call with the result
    1030              :  * @param cb_cls closure for @a cb
    1031              :  * @return handle for the helper
    1032              :  */
    1033              : static struct TALER_JSON_ExternalConversion *
    1034            0 : start_conversion (const struct TALER_KYCLOGIC_ProviderDetails *pd,
    1035              :                   const json_t *attr,
    1036              :                   TALER_JSON_JsonCallback cb,
    1037              :                   void *cb_cls)
    1038              : {
    1039            0 :   const char *argv[] = {
    1040            0 :     pd->conversion_binary,
    1041              :     "-a",
    1042            0 :     pd->auth_token,
    1043              :     NULL,
    1044              :   };
    1045              : 
    1046            0 :   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    1047              :               "Calling converter `%s' with JSON\n",
    1048              :               pd->conversion_binary);
    1049            0 :   json_dumpf (attr,
    1050              :               stderr,
    1051              :               JSON_INDENT (2));
    1052            0 :   return TALER_JSON_external_conversion_start (
    1053              :     attr,
    1054              :     cb,
    1055              :     cb_cls,
    1056            0 :     pd->conversion_binary,
    1057              :     argv);
    1058              : }
    1059              : 
    1060              : 
    1061              : /**
    1062              :  * Type of a callback that receives a JSON @a result.
    1063              :  *
    1064              :  * @param cls closure with a `struct TALER_KYCLOGIC_ProofHandle *`
    1065              :  * @param status_type how did the process die
    1066              :  * @param code termination status code from the process
    1067              :  * @param attr result some JSON result, NULL if we failed to get an JSON output
    1068              :  */
    1069              : static void
    1070            0 : proof_post_conversion_cb (void *cls,
    1071              :                           enum GNUNET_OS_ProcessStatusType status_type,
    1072              :                           unsigned long code,
    1073              :                           const json_t *attr)
    1074              : {
    1075            0 :   struct TALER_KYCLOGIC_ProofHandle *ph = cls;
    1076              :   struct MHD_Response *resp;
    1077              :   struct GNUNET_TIME_Absolute expiration;
    1078              : 
    1079            0 :   ph->ec = NULL;
    1080            0 :   if ( (NULL == attr) ||
    1081              :        (0 != code) )
    1082              :   {
    1083            0 :     GNUNET_break_op (0);
    1084            0 :     return_invalid_response (ph,
    1085              :                              MHD_HTTP_OK,
    1086            0 :                              ph->inquiry_id,
    1087              :                              "converter",
    1088              :                              NULL);
    1089            0 :     persona_proof_cancel (ph);
    1090            0 :     return;
    1091              :   }
    1092            0 :   expiration = GNUNET_TIME_relative_to_absolute (ph->pd->validity);
    1093            0 :   resp = MHD_create_response_from_buffer_static (0,
    1094              :                                                  "");
    1095            0 :   GNUNET_break (MHD_YES ==
    1096              :                 MHD_add_response_header (resp,
    1097              :                                          MHD_HTTP_HEADER_LOCATION,
    1098              :                                          ph->pd->post_kyc_redirect_url));
    1099            0 :   TALER_MHD_add_global_headers (resp,
    1100              :                                 false);
    1101            0 :   ph->cb (ph->cb_cls,
    1102              :           TALER_KYCLOGIC_STATUS_SUCCESS,
    1103            0 :           ph->pd->section,
    1104            0 :           ph->account_id,
    1105            0 :           ph->inquiry_id,
    1106              :           expiration,
    1107              :           attr,
    1108              :           MHD_HTTP_SEE_OTHER,
    1109              :           resp);
    1110            0 :   persona_proof_cancel (ph);
    1111              : }
    1112              : 
    1113              : 
    1114              : /**
    1115              :  * Function called when we're done processing the
    1116              :  * HTTP "/api/v1/inquiries/{inquiry-id}" request.
    1117              :  *
    1118              :  * @param cls the `struct TALER_KYCLOGIC_InitiateHandle`
    1119              :  * @param response_code HTTP response code, 0 on error
    1120              :  * @param response parsed JSON result, NULL on error
    1121              :  */
    1122              : static void
    1123            0 : handle_proof_finished (void *cls,
    1124              :                        long response_code,
    1125              :                        const void *response)
    1126              : {
    1127            0 :   struct TALER_KYCLOGIC_ProofHandle *ph = cls;
    1128            0 :   const json_t *j = response;
    1129            0 :   const json_t *data = json_object_get (j,
    1130              :                                         "data");
    1131              : 
    1132            0 :   ph->job = NULL;
    1133            0 :   switch (response_code)
    1134              :   {
    1135            0 :   case MHD_HTTP_OK:
    1136              :     {
    1137              :       const char *inquiry_id;
    1138              :       const char *account_id;
    1139            0 :       const char *type = NULL;
    1140              :       const json_t *attributes;
    1141              :       const json_t *relationships;
    1142              :       struct GNUNET_JSON_Specification spec[] = {
    1143            0 :         GNUNET_JSON_spec_string ("type",
    1144              :                                  &type),
    1145            0 :         GNUNET_JSON_spec_string ("id",
    1146              :                                  &inquiry_id),
    1147            0 :         GNUNET_JSON_spec_object_const ("attributes",
    1148              :                                        &attributes),
    1149            0 :         GNUNET_JSON_spec_object_const ("relationships",
    1150              :                                        &relationships),
    1151            0 :         GNUNET_JSON_spec_end ()
    1152              :       };
    1153              : 
    1154            0 :       if ( (NULL == data) ||
    1155              :            (GNUNET_OK !=
    1156            0 :             GNUNET_JSON_parse (data,
    1157              :                                spec,
    1158            0 :                                NULL, NULL)) ||
    1159            0 :            (0 != strcasecmp (type,
    1160              :                              "inquiry")) )
    1161              :       {
    1162            0 :         GNUNET_break_op (0);
    1163            0 :         return_invalid_response (ph,
    1164              :                                  response_code,
    1165              :                                  inquiry_id,
    1166              :                                  "data",
    1167              :                                  data);
    1168            0 :         break;
    1169              :       }
    1170              : 
    1171              :       {
    1172              :         const char *status; /* "completed", what else? */
    1173              :         const char *reference_id; /* or legitimization number */
    1174            0 :         const char *expired_at = NULL; /* often 'null' format: "2022-08-18T10:14:26.000Z" */
    1175              :         struct GNUNET_JSON_Specification ispec[] = {
    1176            0 :           GNUNET_JSON_spec_string ("status",
    1177              :                                    &status),
    1178            0 :           GNUNET_JSON_spec_string ("reference-id",
    1179              :                                    &reference_id),
    1180            0 :           GNUNET_JSON_spec_mark_optional (
    1181              :             GNUNET_JSON_spec_string ("expired-at",
    1182              :                                      &expired_at),
    1183              :             NULL),
    1184            0 :           GNUNET_JSON_spec_end ()
    1185              :         };
    1186              : 
    1187            0 :         if (GNUNET_OK !=
    1188            0 :             GNUNET_JSON_parse (attributes,
    1189              :                                ispec,
    1190              :                                NULL, NULL))
    1191              :         {
    1192            0 :           GNUNET_break_op (0);
    1193            0 :           return_invalid_response (ph,
    1194              :                                    response_code,
    1195              :                                    inquiry_id,
    1196              :                                    "data-attributes",
    1197              :                                    data);
    1198            0 :           break;
    1199              :         }
    1200              :         {
    1201              :           unsigned long long idr;
    1202              :           char dummy;
    1203              : 
    1204            0 :           if ( (1 != sscanf (reference_id,
    1205              :                              "%llu%c",
    1206              :                              &idr,
    1207            0 :                              &dummy)) ||
    1208            0 :                (idr != ph->process_row) )
    1209              :           {
    1210            0 :             GNUNET_break_op (0);
    1211            0 :             return_invalid_response (ph,
    1212              :                                      response_code,
    1213              :                                      inquiry_id,
    1214              :                                      "data-attributes-reference_id",
    1215              :                                      data);
    1216            0 :             break;
    1217              :           }
    1218              :         }
    1219              : 
    1220            0 :         if (0 != strcmp (inquiry_id,
    1221            0 :                          ph->inquiry_id))
    1222              :         {
    1223            0 :           GNUNET_break_op (0);
    1224            0 :           return_invalid_response (ph,
    1225              :                                    response_code,
    1226              :                                    inquiry_id,
    1227              :                                    "data-id",
    1228              :                                    data);
    1229            0 :           break;
    1230              :         }
    1231              : 
    1232            0 :         account_id = json_string_value (
    1233            0 :           json_object_get (
    1234            0 :             json_object_get (
    1235            0 :               json_object_get (
    1236              :                 relationships,
    1237              :                 "account"),
    1238              :               "data"),
    1239              :             "id"));
    1240              : 
    1241            0 :         if (0 != strcasecmp (status,
    1242              :                              "completed"))
    1243              :         {
    1244            0 :           proof_generic_reply (
    1245              :             ph,
    1246              :             TALER_KYCLOGIC_STATUS_FAILED,
    1247              :             account_id,
    1248              :             inquiry_id,
    1249              :             MHD_HTTP_OK,
    1250              :             "persona-kyc-failed",
    1251            0 :             GNUNET_JSON_PACK (
    1252              :               GNUNET_JSON_pack_uint64 ("persona_http_status",
    1253              :                                        response_code),
    1254              :               GNUNET_JSON_pack_string ("persona_inquiry_id",
    1255              :                                        inquiry_id),
    1256              :               GNUNET_JSON_pack_allow_null (
    1257              :                 GNUNET_JSON_pack_object_incref ("data",
    1258              :                                                 (json_t *)
    1259              :                                                 data))));
    1260            0 :           break;
    1261              :         }
    1262              : 
    1263            0 :         if (NULL == account_id)
    1264              :         {
    1265            0 :           GNUNET_break_op (0);
    1266            0 :           return_invalid_response (ph,
    1267              :                                    response_code,
    1268              :                                    inquiry_id,
    1269              :                                    "data-relationships-account-data-id",
    1270              :                                    data);
    1271            0 :           break;
    1272              :         }
    1273            0 :         ph->account_id = GNUNET_strdup (account_id);
    1274            0 :         ph->ec = start_conversion (ph->pd,
    1275              :                                    j,
    1276              :                                    &proof_post_conversion_cb,
    1277              :                                    ph);
    1278            0 :         if (NULL == ph->ec)
    1279              :         {
    1280            0 :           GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    1281              :                       "Failed to start Persona conversion helper\n");
    1282            0 :           proof_reply_error (
    1283              :             ph,
    1284            0 :             ph->inquiry_id,
    1285              :             MHD_HTTP_BAD_GATEWAY,
    1286              :             "persona-logic-failure",
    1287            0 :             GNUNET_JSON_PACK (
    1288              :               TALER_JSON_pack_ec (
    1289              :                 TALER_EC_EXCHANGE_GENERIC_KYC_CONVERTER_FAILED)));
    1290            0 :           break;
    1291              :         }
    1292              :       }
    1293            0 :       return; /* continued in proof_post_conversion_cb */
    1294              :     }
    1295            0 :   case MHD_HTTP_BAD_REQUEST:
    1296              :   case MHD_HTTP_NOT_FOUND:
    1297              :   case MHD_HTTP_CONFLICT:
    1298              :   case MHD_HTTP_UNPROCESSABLE_ENTITY:
    1299              :     /* These are errors with this code */
    1300            0 :     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    1301              :                 "PERSONA failed with response %u:\n",
    1302              :                 (unsigned int) response_code);
    1303            0 :     json_dumpf (j,
    1304              :                 stderr,
    1305              :                 JSON_INDENT (2));
    1306            0 :     proof_reply_error (
    1307              :       ph,
    1308            0 :       ph->inquiry_id,
    1309              :       MHD_HTTP_BAD_GATEWAY,
    1310              :       "persona-logic-failure",
    1311            0 :       GNUNET_JSON_PACK (
    1312              :         GNUNET_JSON_pack_uint64 ("persona_http_status",
    1313              :                                  response_code),
    1314              :         TALER_JSON_pack_ec (
    1315              :           TALER_EC_EXCHANGE_KYC_GENERIC_PROVIDER_UNEXPECTED_REPLY),
    1316              : 
    1317              :         GNUNET_JSON_pack_allow_null (
    1318              :           GNUNET_JSON_pack_object_incref ("data",
    1319              :                                           (json_t *)
    1320              :                                           data))));
    1321            0 :     break;
    1322            0 :   case MHD_HTTP_UNAUTHORIZED:
    1323              :     /* These are failures of the exchange operator */
    1324            0 :     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    1325              :                 "Refused access with HTTP status code %u\n",
    1326              :                 (unsigned int) response_code);
    1327            0 :     proof_reply_error (
    1328              :       ph,
    1329            0 :       ph->inquiry_id,
    1330              :       MHD_HTTP_BAD_GATEWAY,
    1331              :       "persona-exchange-unauthorized",
    1332            0 :       GNUNET_JSON_PACK (
    1333              :         GNUNET_JSON_pack_uint64 ("persona_http_status",
    1334              :                                  response_code),
    1335              :         TALER_JSON_pack_ec (
    1336              :           TALER_EC_EXCHANGE_KYC_GENERIC_PROVIDER_ACCESS_REFUSED),
    1337              :         GNUNET_JSON_pack_allow_null (
    1338              :           GNUNET_JSON_pack_object_incref ("data",
    1339              :                                           (json_t *)
    1340              :                                           data))));
    1341            0 :     break;
    1342            0 :   case MHD_HTTP_PAYMENT_REQUIRED:
    1343              :     /* These are failures of the exchange operator */
    1344            0 :     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    1345              :                 "Refused access with HTTP status code %u\n",
    1346              :                 (unsigned int) response_code);
    1347            0 :     proof_reply_error (
    1348              :       ph,
    1349            0 :       ph->inquiry_id,
    1350              :       MHD_HTTP_SERVICE_UNAVAILABLE,
    1351              :       "persona-exchange-unpaid",
    1352            0 :       GNUNET_JSON_PACK (
    1353              :         GNUNET_JSON_pack_uint64 ("persona_http_status",
    1354              :                                  response_code),
    1355              :         TALER_JSON_pack_ec (
    1356              :           TALER_EC_EXCHANGE_KYC_GENERIC_PROVIDER_ACCESS_REFUSED),
    1357              :         GNUNET_JSON_pack_allow_null (
    1358              :           GNUNET_JSON_pack_object_incref ("data",
    1359              :                                           (json_t *)
    1360              :                                           data))));
    1361            0 :     break;
    1362            0 :   case MHD_HTTP_REQUEST_TIMEOUT:
    1363              :     /* These are networking issues */
    1364            0 :     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    1365              :                 "PERSONA failed with response %u:\n",
    1366              :                 (unsigned int) response_code);
    1367            0 :     json_dumpf (j,
    1368              :                 stderr,
    1369              :                 JSON_INDENT (2));
    1370            0 :     proof_reply_error (
    1371              :       ph,
    1372            0 :       ph->inquiry_id,
    1373              :       MHD_HTTP_GATEWAY_TIMEOUT,
    1374              :       "persona-network-timeout",
    1375            0 :       GNUNET_JSON_PACK (
    1376              :         GNUNET_JSON_pack_uint64 ("persona_http_status",
    1377              :                                  response_code),
    1378              :         TALER_JSON_pack_ec (
    1379              :           TALER_EC_EXCHANGE_KYC_GENERIC_PROVIDER_TIMEOUT),
    1380              :         GNUNET_JSON_pack_allow_null (
    1381              :           GNUNET_JSON_pack_object_incref ("data",
    1382              :                                           (json_t *)
    1383              :                                           data))));
    1384            0 :     break;
    1385            0 :   case MHD_HTTP_TOO_MANY_REQUESTS:
    1386              :     /* This is a load issue */
    1387            0 :     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    1388              :                 "PERSONA failed with response %u:\n",
    1389              :                 (unsigned int) response_code);
    1390            0 :     json_dumpf (j,
    1391              :                 stderr,
    1392              :                 JSON_INDENT (2));
    1393            0 :     proof_reply_error (
    1394              :       ph,
    1395            0 :       ph->inquiry_id,
    1396              :       MHD_HTTP_SERVICE_UNAVAILABLE,
    1397              :       "persona-load-failure",
    1398            0 :       GNUNET_JSON_PACK (
    1399              :         GNUNET_JSON_pack_uint64 ("persona_http_status",
    1400              :                                  response_code),
    1401              :         TALER_JSON_pack_ec (
    1402              :           TALER_EC_EXCHANGE_KYC_GENERIC_PROVIDER_RATE_LIMIT_EXCEEDED),
    1403              :         GNUNET_JSON_pack_allow_null (
    1404              :           GNUNET_JSON_pack_object_incref ("data",
    1405              :                                           (json_t *)
    1406              :                                           data))));
    1407            0 :     break;
    1408            0 :   case MHD_HTTP_INTERNAL_SERVER_ERROR:
    1409              :     /* This is an issue with Persona */
    1410            0 :     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    1411              :                 "PERSONA failed with response %u:\n",
    1412              :                 (unsigned int) response_code);
    1413            0 :     json_dumpf (j,
    1414              :                 stderr,
    1415              :                 JSON_INDENT (2));
    1416            0 :     proof_reply_error (
    1417              :       ph,
    1418            0 :       ph->inquiry_id,
    1419              :       MHD_HTTP_BAD_GATEWAY,
    1420              :       "persona-provider-failure",
    1421            0 :       GNUNET_JSON_PACK (
    1422              :         GNUNET_JSON_pack_uint64 ("persona_http_status",
    1423              :                                  response_code),
    1424              :         TALER_JSON_pack_ec (
    1425              :           TALER_EC_EXCHANGE_KYC_PROOF_BACKEND_ERROR),
    1426              :         GNUNET_JSON_pack_allow_null (
    1427              :           GNUNET_JSON_pack_object_incref ("data",
    1428              :                                           (json_t *)
    1429              :                                           data))));
    1430            0 :     break;
    1431            0 :   default:
    1432              :     /* This is an issue with Persona */
    1433            0 :     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    1434              :                 "PERSONA failed with response %u:\n",
    1435              :                 (unsigned int) response_code);
    1436            0 :     json_dumpf (j,
    1437              :                 stderr,
    1438              :                 JSON_INDENT (2));
    1439            0 :     proof_reply_error (
    1440              :       ph,
    1441            0 :       ph->inquiry_id,
    1442              :       MHD_HTTP_BAD_GATEWAY,
    1443              :       "persona-invalid-response",
    1444            0 :       GNUNET_JSON_PACK (
    1445              :         GNUNET_JSON_pack_uint64 ("persona_http_status",
    1446              :                                  response_code),
    1447              :         TALER_JSON_pack_ec (
    1448              :           TALER_EC_EXCHANGE_KYC_GENERIC_PROVIDER_UNEXPECTED_REPLY),
    1449              :         GNUNET_JSON_pack_allow_null (
    1450              :           GNUNET_JSON_pack_object_incref ("data",
    1451              :                                           (json_t *)
    1452              :                                           data))));
    1453            0 :     break;
    1454              :   }
    1455            0 :   persona_proof_cancel (ph);
    1456              : }
    1457              : 
    1458              : 
    1459              : /**
    1460              :  * Check KYC status and return final result to human.
    1461              :  *
    1462              :  * @param cls the @e cls of this struct with the plugin-specific state
    1463              :  * @param pd provider configuration details
    1464              :  * @param connection MHD connection object (for HTTP headers)
    1465              :  * @param account_id which account to trigger process for
    1466              :  * @param process_row row in the legitimization processes table the legitimization is for
    1467              :  * @param provider_user_id user ID (or NULL) the proof is for
    1468              :  * @param inquiry_id legitimization ID the proof is for
    1469              :  * @param cb function to call with the result
    1470              :  * @param cb_cls closure for @a cb
    1471              :  * @return handle to cancel operation early
    1472              :  */
    1473              : static struct TALER_KYCLOGIC_ProofHandle *
    1474            0 : persona_proof (void *cls,
    1475              :                const struct TALER_KYCLOGIC_ProviderDetails *pd,
    1476              :                struct MHD_Connection *connection,
    1477              :                const struct TALER_NormalizedPaytoHashP *account_id,
    1478              :                uint64_t process_row,
    1479              :                const char *provider_user_id,
    1480              :                const char *inquiry_id,
    1481              :                TALER_KYCLOGIC_ProofCallback cb,
    1482              :                void *cb_cls)
    1483              : {
    1484            0 :   struct PluginState *ps = cls;
    1485              :   struct TALER_KYCLOGIC_ProofHandle *ph;
    1486              :   CURL *eh;
    1487              : 
    1488            0 :   eh = curl_easy_init ();
    1489            0 :   if (NULL == eh)
    1490              :   {
    1491            0 :     GNUNET_break (0);
    1492            0 :     return NULL;
    1493              :   }
    1494            0 :   ph = GNUNET_new (struct TALER_KYCLOGIC_ProofHandle);
    1495            0 :   ph->ps = ps;
    1496            0 :   ph->pd = pd;
    1497            0 :   ph->cb = cb;
    1498            0 :   ph->cb_cls = cb_cls;
    1499            0 :   ph->connection = connection;
    1500            0 :   ph->process_row = process_row;
    1501            0 :   ph->h_payto = *account_id;
    1502              :   /* Note: we do not expect this to be non-NULL */
    1503            0 :   if (NULL != provider_user_id)
    1504            0 :     ph->provider_user_id = GNUNET_strdup (provider_user_id);
    1505            0 :   if (NULL != inquiry_id)
    1506            0 :     ph->inquiry_id = GNUNET_strdup (inquiry_id);
    1507            0 :   GNUNET_asprintf (&ph->url,
    1508              :                    "https://withpersona.com/api/v1/inquiries/%s",
    1509              :                    inquiry_id);
    1510            0 :   GNUNET_break (CURLE_OK ==
    1511              :                 curl_easy_setopt (eh,
    1512              :                                   CURLOPT_VERBOSE,
    1513              :                                   0));
    1514            0 :   GNUNET_assert (CURLE_OK ==
    1515              :                  curl_easy_setopt (eh,
    1516              :                                    CURLOPT_MAXREDIRS,
    1517              :                                    1L));
    1518            0 :   GNUNET_break (CURLE_OK ==
    1519              :                 curl_easy_setopt (eh,
    1520              :                                   CURLOPT_URL,
    1521              :                                   ph->url));
    1522            0 :   ph->job = GNUNET_CURL_job_add2 (ps->curl_ctx,
    1523              :                                   eh,
    1524            0 :                                   pd->slist,
    1525              :                                   &handle_proof_finished,
    1526              :                                   ph);
    1527            0 :   return ph;
    1528              : }
    1529              : 
    1530              : 
    1531              : /**
    1532              :  * Cancel KYC webhook execution.
    1533              :  *
    1534              :  * @param[in] wh handle of operation to cancel
    1535              :  */
    1536              : static void
    1537            0 : persona_webhook_cancel (struct TALER_KYCLOGIC_WebhookHandle *wh)
    1538              : {
    1539            0 :   if (NULL != wh->task)
    1540              :   {
    1541            0 :     GNUNET_SCHEDULER_cancel (wh->task);
    1542            0 :     wh->task = NULL;
    1543              :   }
    1544            0 :   if (NULL != wh->job)
    1545              :   {
    1546            0 :     GNUNET_CURL_job_cancel (wh->job);
    1547            0 :     wh->job = NULL;
    1548              :   }
    1549            0 :   if (NULL != wh->ec)
    1550              :   {
    1551            0 :     TALER_JSON_external_conversion_stop (wh->ec);
    1552            0 :     wh->ec = NULL;
    1553              :   }
    1554            0 :   GNUNET_free (wh->account_id);
    1555            0 :   GNUNET_free (wh->inquiry_id);
    1556            0 :   GNUNET_free (wh->url);
    1557            0 :   GNUNET_free (wh);
    1558            0 : }
    1559              : 
    1560              : 
    1561              : /**
    1562              :  * Call @a wh callback with the operation result.
    1563              :  *
    1564              :  * @param wh proof handle to generate reply for
    1565              :  * @param status status to return
    1566              :  * @param account_id account to return
    1567              :  * @param inquiry_id inquiry ID to supply
    1568              :  * @param attr KYC attribute data for the client
    1569              :  * @param http_status HTTP status to use
    1570              :  */
    1571              : static void
    1572            0 : webhook_generic_reply (struct TALER_KYCLOGIC_WebhookHandle *wh,
    1573              :                        enum TALER_KYCLOGIC_KycStatus status,
    1574              :                        const char *account_id,
    1575              :                        const char *inquiry_id,
    1576              :                        const json_t *attr,
    1577              :                        unsigned int http_status)
    1578              : {
    1579              :   struct MHD_Response *resp;
    1580              :   struct GNUNET_TIME_Absolute expiration;
    1581              : 
    1582            0 :   if (TALER_KYCLOGIC_STATUS_SUCCESS == status)
    1583            0 :     expiration = GNUNET_TIME_relative_to_absolute (wh->pd->validity);
    1584              :   else
    1585            0 :     expiration = GNUNET_TIME_UNIT_ZERO_ABS;
    1586            0 :   resp = MHD_create_response_from_buffer_static (0,
    1587              :                                                  "");
    1588            0 :   TALER_MHD_add_global_headers (resp,
    1589              :                                 true);
    1590            0 :   wh->cb (wh->cb_cls,
    1591              :           wh->process_row,
    1592            0 :           &wh->h_payto,
    1593            0 :           wh->is_wallet,
    1594            0 :           wh->pd->section,
    1595              :           account_id,
    1596              :           inquiry_id,
    1597              :           status,
    1598              :           expiration,
    1599              :           attr,
    1600              :           http_status,
    1601              :           resp);
    1602            0 : }
    1603              : 
    1604              : 
    1605              : /**
    1606              :  * Call @a wh callback with HTTP error response.
    1607              :  *
    1608              :  * @param wh proof handle to generate reply for
    1609              :  * @param inquiry_id inquiry ID to supply
    1610              :  * @param http_status HTTP status to use
    1611              :  */
    1612              : static void
    1613            0 : webhook_reply_error (struct TALER_KYCLOGIC_WebhookHandle *wh,
    1614              :                      const char *inquiry_id,
    1615              :                      unsigned int http_status)
    1616              : {
    1617            0 :   webhook_generic_reply (wh,
    1618              :                          TALER_KYCLOGIC_STATUS_PROVIDER_FAILED,
    1619              :                          NULL, /* user id */
    1620              :                          inquiry_id,
    1621              :                          NULL, /* attributes */
    1622              :                          http_status);
    1623            0 : }
    1624              : 
    1625              : 
    1626              : /**
    1627              :  * Type of a callback that receives a JSON @a result.
    1628              :  *
    1629              :  * @param cls closure with a `struct TALER_KYCLOGIC_WebhookHandle *`
    1630              :  * @param status_type how did the process die
    1631              :  * @param code termination status code from the process
    1632              :  * @param attr some JSON result, NULL if we failed to get an JSON output
    1633              :  */
    1634              : static void
    1635            0 : webhook_post_conversion_cb (void *cls,
    1636              :                             enum GNUNET_OS_ProcessStatusType status_type,
    1637              :                             unsigned long code,
    1638              :                             const json_t *attr)
    1639              : {
    1640            0 :   struct TALER_KYCLOGIC_WebhookHandle *wh = cls;
    1641              : 
    1642            0 :   wh->ec = NULL;
    1643            0 :   if (! json_is_string (json_object_get (attr,
    1644              :                                          "FORM_ID")))
    1645              :   {
    1646              :     struct MHD_Response *resp;
    1647              : 
    1648              :     /* Failure in our helper */
    1649            0 :     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    1650              :                 "Mandatory FORM_ID not set in result\n");
    1651            0 :     json_dumpf (attr,
    1652              :                 stderr,
    1653              :                 JSON_INDENT (2));
    1654            0 :     resp = TALER_MHD_MAKE_JSON_PACK (
    1655              :       GNUNET_JSON_pack_uint64 ("persona_http_status",
    1656              :                                wh->persona_http_status),
    1657              :       GNUNET_JSON_pack_object_incref ("persona_body",
    1658              :                                       (json_t *) attr));
    1659            0 :     wh->cb (wh->cb_cls,
    1660              :             wh->process_row,
    1661            0 :             &wh->h_payto,
    1662            0 :             wh->is_wallet,
    1663            0 :             wh->pd->section,
    1664              :             NULL,
    1665            0 :             wh->inquiry_id,
    1666              :             TALER_KYCLOGIC_STATUS_PROVIDER_FAILED,
    1667            0 :             GNUNET_TIME_UNIT_ZERO_ABS, /* expiration */
    1668              :             NULL,
    1669              :             MHD_HTTP_BAD_GATEWAY,
    1670              :             resp);
    1671            0 :     persona_webhook_cancel (wh);
    1672            0 :     return;
    1673              :   }
    1674              : 
    1675            0 :   webhook_generic_reply (wh,
    1676              :                          TALER_KYCLOGIC_STATUS_SUCCESS,
    1677            0 :                          wh->account_id,
    1678            0 :                          wh->inquiry_id,
    1679              :                          attr,
    1680              :                          MHD_HTTP_OK);
    1681              : }
    1682              : 
    1683              : 
    1684              : /**
    1685              :  * Function called when we're done processing the
    1686              :  * HTTP "/api/v1/inquiries/{inquiry_id}" request.
    1687              :  *
    1688              :  * @param cls the `struct TALER_KYCLOGIC_WebhookHandle`
    1689              :  * @param response_code HTTP response code, 0 on error
    1690              :  * @param response parsed JSON result, NULL on error
    1691              :  */
    1692              : static void
    1693            0 : handle_webhook_finished (void *cls,
    1694              :                          long response_code,
    1695              :                          const void *response)
    1696              : {
    1697            0 :   struct TALER_KYCLOGIC_WebhookHandle *wh = cls;
    1698            0 :   const json_t *j = response;
    1699            0 :   const json_t *data = json_object_get (j,
    1700              :                                         "data");
    1701              : 
    1702            0 :   wh->job = NULL;
    1703            0 :   wh->persona_http_status = response_code;
    1704            0 :   switch (response_code)
    1705              :   {
    1706            0 :   case MHD_HTTP_OK:
    1707              :     {
    1708              :       const char *inquiry_id;
    1709              :       const char *account_id;
    1710            0 :       const char *type = NULL;
    1711              :       const json_t *attributes;
    1712              :       const json_t *relationships;
    1713              :       struct GNUNET_JSON_Specification spec[] = {
    1714            0 :         GNUNET_JSON_spec_string ("type",
    1715              :                                  &type),
    1716            0 :         GNUNET_JSON_spec_string ("id",
    1717              :                                  &inquiry_id),
    1718            0 :         GNUNET_JSON_spec_object_const ("attributes",
    1719              :                                        &attributes),
    1720            0 :         GNUNET_JSON_spec_object_const ("relationships",
    1721              :                                        &relationships),
    1722            0 :         GNUNET_JSON_spec_end ()
    1723              :       };
    1724              : 
    1725            0 :       if ( (NULL == data) ||
    1726              :            (GNUNET_OK !=
    1727            0 :             GNUNET_JSON_parse (data,
    1728              :                                spec,
    1729            0 :                                NULL, NULL)) ||
    1730            0 :            (0 != strcasecmp (type,
    1731              :                              "inquiry")) )
    1732              :       {
    1733            0 :         GNUNET_break_op (0);
    1734            0 :         json_dumpf (j,
    1735              :                     stderr,
    1736              :                     JSON_INDENT (2));
    1737            0 :         webhook_reply_error (wh,
    1738              :                              inquiry_id,
    1739              :                              MHD_HTTP_BAD_GATEWAY);
    1740            0 :         break;
    1741              :       }
    1742              : 
    1743              :       {
    1744              :         const char *status; /* "completed", what else? */
    1745              :         const char *reference_id; /* or legitimization number */
    1746            0 :         const char *expired_at = NULL; /* often 'null' format: "2022-08-18T10:14:26.000Z" */
    1747              :         struct GNUNET_JSON_Specification ispec[] = {
    1748            0 :           GNUNET_JSON_spec_string ("status",
    1749              :                                    &status),
    1750            0 :           GNUNET_JSON_spec_string ("reference-id",
    1751              :                                    &reference_id),
    1752            0 :           GNUNET_JSON_spec_mark_optional (
    1753              :             GNUNET_JSON_spec_string ("expired-at",
    1754              :                                      &expired_at),
    1755              :             NULL),
    1756            0 :           GNUNET_JSON_spec_end ()
    1757              :         };
    1758              : 
    1759            0 :         if (GNUNET_OK !=
    1760            0 :             GNUNET_JSON_parse (attributes,
    1761              :                                ispec,
    1762              :                                NULL, NULL))
    1763              :         {
    1764            0 :           GNUNET_break_op (0);
    1765            0 :           json_dumpf (j,
    1766              :                       stderr,
    1767              :                       JSON_INDENT (2));
    1768            0 :           webhook_reply_error (wh,
    1769              :                                inquiry_id,
    1770              :                                MHD_HTTP_BAD_GATEWAY);
    1771            0 :           break;
    1772              :         }
    1773              :         {
    1774              :           unsigned long long idr;
    1775              :           char dummy;
    1776              : 
    1777            0 :           if ( (1 != sscanf (reference_id,
    1778              :                              "%llu%c",
    1779              :                              &idr,
    1780            0 :                              &dummy)) ||
    1781            0 :                (idr != wh->process_row) )
    1782              :           {
    1783            0 :             GNUNET_break_op (0);
    1784            0 :             webhook_reply_error (wh,
    1785              :                                  inquiry_id,
    1786              :                                  MHD_HTTP_BAD_GATEWAY);
    1787            0 :             break;
    1788              :           }
    1789              :         }
    1790              : 
    1791            0 :         if (0 != strcmp (inquiry_id,
    1792            0 :                          wh->inquiry_id))
    1793              :         {
    1794            0 :           GNUNET_break_op (0);
    1795            0 :           webhook_reply_error (wh,
    1796              :                                inquiry_id,
    1797              :                                MHD_HTTP_BAD_GATEWAY);
    1798            0 :           break;
    1799              :         }
    1800              : 
    1801            0 :         account_id = json_string_value (
    1802            0 :           json_object_get (
    1803            0 :             json_object_get (
    1804            0 :               json_object_get (
    1805              :                 relationships,
    1806              :                 "account"),
    1807              :               "data"),
    1808              :             "id"));
    1809              : 
    1810            0 :         if (0 != strcasecmp (status,
    1811              :                              "completed"))
    1812              :         {
    1813            0 :           webhook_generic_reply (wh,
    1814              :                                  TALER_KYCLOGIC_STATUS_FAILED,
    1815              :                                  account_id,
    1816              :                                  inquiry_id,
    1817              :                                  NULL,
    1818              :                                  MHD_HTTP_OK);
    1819            0 :           break;
    1820              :         }
    1821              : 
    1822            0 :         if (NULL == account_id)
    1823              :         {
    1824            0 :           GNUNET_break_op (0);
    1825            0 :           json_dumpf (data,
    1826              :                       stderr,
    1827              :                       JSON_INDENT (2));
    1828            0 :           webhook_reply_error (wh,
    1829              :                                inquiry_id,
    1830              :                                MHD_HTTP_BAD_GATEWAY);
    1831            0 :           break;
    1832              :         }
    1833            0 :         wh->account_id = GNUNET_strdup (account_id);
    1834            0 :         wh->ec = start_conversion (wh->pd,
    1835              :                                    j,
    1836              :                                    &webhook_post_conversion_cb,
    1837              :                                    wh);
    1838            0 :         if (NULL == wh->ec)
    1839              :         {
    1840            0 :           GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    1841              :                       "Failed to start Persona conversion helper\n");
    1842            0 :           webhook_reply_error (wh,
    1843              :                                inquiry_id,
    1844              :                                MHD_HTTP_INTERNAL_SERVER_ERROR);
    1845            0 :           break;
    1846              :         }
    1847              :       }
    1848            0 :       return; /* continued in webhook_post_conversion_cb */
    1849              :     }
    1850            0 :   case MHD_HTTP_BAD_REQUEST:
    1851              :   case MHD_HTTP_NOT_FOUND:
    1852              :   case MHD_HTTP_CONFLICT:
    1853              :   case MHD_HTTP_UNPROCESSABLE_ENTITY:
    1854              :     /* These are errors with this code */
    1855            0 :     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    1856              :                 "PERSONA failed with response %u:\n",
    1857              :                 (unsigned int) response_code);
    1858            0 :     json_dumpf (j,
    1859              :                 stderr,
    1860              :                 JSON_INDENT (2));
    1861            0 :     webhook_reply_error (wh,
    1862            0 :                          wh->inquiry_id,
    1863              :                          MHD_HTTP_BAD_GATEWAY);
    1864            0 :     break;
    1865            0 :   case MHD_HTTP_UNAUTHORIZED:
    1866              :     /* These are failures of the exchange operator */
    1867            0 :     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    1868              :                 "Refused access with HTTP status code %u\n",
    1869              :                 (unsigned int) response_code);
    1870            0 :     webhook_reply_error (wh,
    1871            0 :                          wh->inquiry_id,
    1872              :                          MHD_HTTP_INTERNAL_SERVER_ERROR);
    1873            0 :     break;
    1874            0 :   case MHD_HTTP_PAYMENT_REQUIRED:
    1875              :     /* These are failures of the exchange operator */
    1876            0 :     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    1877              :                 "Refused access with HTTP status code %u\n",
    1878              :                 (unsigned int) response_code);
    1879              : 
    1880            0 :     webhook_reply_error (wh,
    1881            0 :                          wh->inquiry_id,
    1882              :                          MHD_HTTP_INTERNAL_SERVER_ERROR);
    1883            0 :     break;
    1884            0 :   case MHD_HTTP_REQUEST_TIMEOUT:
    1885              :     /* These are networking issues */
    1886            0 :     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    1887              :                 "PERSONA failed with response %u:\n",
    1888              :                 (unsigned int) response_code);
    1889            0 :     json_dumpf (j,
    1890              :                 stderr,
    1891              :                 JSON_INDENT (2));
    1892            0 :     webhook_reply_error (wh,
    1893            0 :                          wh->inquiry_id,
    1894              :                          MHD_HTTP_GATEWAY_TIMEOUT);
    1895            0 :     break;
    1896            0 :   case MHD_HTTP_TOO_MANY_REQUESTS:
    1897              :     /* This is a load issue */
    1898            0 :     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    1899              :                 "PERSONA failed with response %u:\n",
    1900              :                 (unsigned int) response_code);
    1901            0 :     json_dumpf (j,
    1902              :                 stderr,
    1903              :                 JSON_INDENT (2));
    1904            0 :     webhook_reply_error (wh,
    1905            0 :                          wh->inquiry_id,
    1906              :                          MHD_HTTP_SERVICE_UNAVAILABLE);
    1907            0 :     break;
    1908            0 :   case MHD_HTTP_INTERNAL_SERVER_ERROR:
    1909              :     /* This is an issue with Persona */
    1910            0 :     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    1911              :                 "PERSONA failed with response %u:\n",
    1912              :                 (unsigned int) response_code);
    1913            0 :     json_dumpf (j,
    1914              :                 stderr,
    1915              :                 JSON_INDENT (2));
    1916            0 :     webhook_reply_error (wh,
    1917            0 :                          wh->inquiry_id,
    1918              :                          MHD_HTTP_BAD_GATEWAY);
    1919            0 :     break;
    1920            0 :   default:
    1921              :     /* This is an issue with Persona */
    1922            0 :     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    1923              :                 "PERSONA failed with response %u:\n",
    1924              :                 (unsigned int) response_code);
    1925            0 :     json_dumpf (j,
    1926              :                 stderr,
    1927              :                 JSON_INDENT (2));
    1928            0 :     webhook_reply_error (wh,
    1929            0 :                          wh->inquiry_id,
    1930              :                          MHD_HTTP_BAD_GATEWAY);
    1931            0 :     break;
    1932              :   }
    1933              : 
    1934            0 :   persona_webhook_cancel (wh);
    1935              : }
    1936              : 
    1937              : 
    1938              : /**
    1939              :  * Asynchronously return a reply for the webhook.
    1940              :  *
    1941              :  * @param cls a `struct TALER_KYCLOGIC_WebhookHandle *`
    1942              :  */
    1943              : static void
    1944            0 : async_webhook_reply (void *cls)
    1945              : {
    1946            0 :   struct TALER_KYCLOGIC_WebhookHandle *wh = cls;
    1947              : 
    1948            0 :   wh->task = NULL;
    1949            0 :   wh->cb (wh->cb_cls,
    1950              :           wh->process_row,
    1951            0 :           (0 == wh->process_row)
    1952              :           ? NULL
    1953              :           : &wh->h_payto,
    1954            0 :           wh->is_wallet,
    1955            0 :           wh->pd->section,
    1956              :           NULL,
    1957            0 :           wh->inquiry_id, /* provider legi ID */
    1958              :           TALER_KYCLOGIC_STATUS_PROVIDER_FAILED,
    1959            0 :           GNUNET_TIME_UNIT_ZERO_ABS, /* expiration */
    1960              :           NULL,
    1961              :           wh->response_code,
    1962              :           wh->resp);
    1963            0 :   persona_webhook_cancel (wh);
    1964            0 : }
    1965              : 
    1966              : 
    1967              : /**
    1968              :  * Function called with the provider details and
    1969              :  * associated plugin closures for matching logics.
    1970              :  *
    1971              :  * @param cls closure
    1972              :  * @param pd provider details of a matching logic
    1973              :  * @param plugin_cls closure of the plugin
    1974              :  * @return #GNUNET_OK to continue to iterate
    1975              :  */
    1976              : static enum GNUNET_GenericReturnValue
    1977            0 : locate_details_cb (
    1978              :   void *cls,
    1979              :   const struct TALER_KYCLOGIC_ProviderDetails *pd,
    1980              :   void *plugin_cls)
    1981              : {
    1982            0 :   struct TALER_KYCLOGIC_WebhookHandle *wh = cls;
    1983              : 
    1984              :   /* This type-checks 'pd' */
    1985            0 :   GNUNET_assert (plugin_cls == wh->ps);
    1986            0 :   if (0 == strcmp (pd->template_id,
    1987              :                    wh->template_id))
    1988              :   {
    1989            0 :     wh->pd = pd;
    1990            0 :     return GNUNET_NO;
    1991              :   }
    1992            0 :   return GNUNET_OK;
    1993              : }
    1994              : 
    1995              : 
    1996              : /**
    1997              :  * Check KYC status and return result for Webhook.  We do NOT implement the
    1998              :  * authentication check proposed by the PERSONA documentation, as it would
    1999              :  * allow an attacker who learns the access token to easily bypass the KYC
    2000              :  * checks. Instead, we insist on explicitly requesting the KYC status from the
    2001              :  * provider (at least on success).
    2002              :  *
    2003              :  * @param cls the @e cls of this struct with the plugin-specific state
    2004              :  * @param pd provider configuration details
    2005              :  * @param plc callback to lookup accounts with
    2006              :  * @param plc_cls closure for @a plc
    2007              :  * @param http_method HTTP method used for the webhook
    2008              :  * @param url_path rest of the URL after `/kyc-webhook/`
    2009              :  * @param connection MHD connection object (for HTTP headers)
    2010              :  * @param body HTTP request body
    2011              :  * @param cb function to call with the result
    2012              :  * @param cb_cls closure for @a cb
    2013              :  * @return handle to cancel operation early
    2014              :  */
    2015              : static struct TALER_KYCLOGIC_WebhookHandle *
    2016            0 : persona_webhook (void *cls,
    2017              :                  const struct TALER_KYCLOGIC_ProviderDetails *pd,
    2018              :                  TALER_KYCLOGIC_ProviderLookupCallback plc,
    2019              :                  void *plc_cls,
    2020              :                  const char *http_method,
    2021              :                  const char *const url_path[],
    2022              :                  struct MHD_Connection *connection,
    2023              :                  const json_t *body,
    2024              :                  TALER_KYCLOGIC_WebhookCallback cb,
    2025              :                  void *cb_cls)
    2026              : {
    2027            0 :   struct PluginState *ps = cls;
    2028              :   struct TALER_KYCLOGIC_WebhookHandle *wh;
    2029              :   CURL *eh;
    2030              :   enum GNUNET_DB_QueryStatus qs;
    2031              :   const char *persona_inquiry_id;
    2032              :   const char *auth_header;
    2033              : 
    2034              :   /* Persona webhooks are expected by logic, not by template */
    2035            0 :   GNUNET_break_op (NULL == pd);
    2036            0 :   wh = GNUNET_new (struct TALER_KYCLOGIC_WebhookHandle);
    2037            0 :   wh->cb = cb;
    2038            0 :   wh->cb_cls = cb_cls;
    2039            0 :   wh->ps = ps;
    2040            0 :   wh->connection = connection;
    2041            0 :   wh->pd = pd;
    2042            0 :   auth_header = MHD_lookup_connection_value (connection,
    2043              :                                              MHD_HEADER_KIND,
    2044              :                                              MHD_HTTP_HEADER_AUTHORIZATION);
    2045            0 :   if ( (NULL != ps->webhook_token) &&
    2046            0 :        ( (NULL == auth_header) ||
    2047            0 :          (0 != strcmp (ps->webhook_token,
    2048              :                        auth_header)) ) )
    2049              :   {
    2050            0 :     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    2051              :                 "Invalid authorization header `%s' received for Persona webhook\n",
    2052              :                 auth_header);
    2053            0 :     wh->resp = TALER_MHD_MAKE_JSON_PACK (
    2054              :       TALER_JSON_pack_ec (
    2055              :         TALER_EC_EXCHANGE_KYC_WEBHOOK_UNAUTHORIZED),
    2056              :       GNUNET_JSON_pack_string ("detail",
    2057              :                                "unexpected 'Authorization' header"));
    2058            0 :     wh->response_code = MHD_HTTP_UNAUTHORIZED;
    2059            0 :     wh->task = GNUNET_SCHEDULER_add_now (&async_webhook_reply,
    2060              :                                          wh);
    2061            0 :     return wh;
    2062              :   }
    2063              : 
    2064              :   wh->template_id
    2065            0 :     = json_string_value (
    2066            0 :         json_object_get (
    2067            0 :           json_object_get (
    2068            0 :             json_object_get (
    2069            0 :               json_object_get (
    2070            0 :                 json_object_get (
    2071            0 :                   json_object_get (
    2072            0 :                     json_object_get (
    2073            0 :                       json_object_get (
    2074              :                         body,
    2075              :                         "data"),
    2076              :                       "attributes"),
    2077              :                     "payload"),
    2078              :                   "data"),
    2079              :                 "relationships"),
    2080              :               "inquiry-template"),
    2081              :             "data"),
    2082              :           "id"));
    2083            0 :   if (NULL == wh->template_id)
    2084              :   {
    2085            0 :     GNUNET_break_op (0);
    2086            0 :     json_dumpf (body,
    2087              :                 stderr,
    2088              :                 JSON_INDENT (2));
    2089            0 :     wh->resp = TALER_MHD_MAKE_JSON_PACK (
    2090              :       TALER_JSON_pack_ec (
    2091              :         TALER_EC_EXCHANGE_KYC_GENERIC_PROVIDER_UNEXPECTED_REPLY),
    2092              :       GNUNET_JSON_pack_string ("detail",
    2093              :                                "data-attributes-payload-data-id"),
    2094              :       GNUNET_JSON_pack_object_incref ("webhook_body",
    2095              :                                       (json_t *) body));
    2096            0 :     wh->response_code = MHD_HTTP_BAD_REQUEST;
    2097            0 :     wh->task = GNUNET_SCHEDULER_add_now (&async_webhook_reply,
    2098              :                                          wh);
    2099            0 :     return wh;
    2100              :   }
    2101            0 :   TALER_KYCLOGIC_kyc_get_details ("persona",
    2102              :                                   &locate_details_cb,
    2103              :                                   wh);
    2104            0 :   if (NULL == wh->pd)
    2105              :   {
    2106            0 :     GNUNET_break_op (0);
    2107            0 :     json_dumpf (body,
    2108              :                 stderr,
    2109              :                 JSON_INDENT (2));
    2110            0 :     wh->resp = TALER_MHD_MAKE_JSON_PACK (
    2111              :       TALER_JSON_pack_ec (
    2112              :         TALER_EC_EXCHANGE_KYC_GENERIC_LOGIC_UNKNOWN),
    2113              :       GNUNET_JSON_pack_string ("detail",
    2114              :                                wh->template_id),
    2115              :       GNUNET_JSON_pack_object_incref ("webhook_body",
    2116              :                                       (json_t *) body));
    2117            0 :     wh->response_code = MHD_HTTP_BAD_REQUEST;
    2118            0 :     wh->task = GNUNET_SCHEDULER_add_now (&async_webhook_reply,
    2119              :                                          wh);
    2120            0 :     return wh;
    2121              :   }
    2122              : 
    2123              :   persona_inquiry_id
    2124            0 :     = json_string_value (
    2125            0 :         json_object_get (
    2126            0 :           json_object_get (
    2127            0 :             json_object_get (
    2128            0 :               json_object_get (
    2129            0 :                 json_object_get (
    2130              :                   body,
    2131              :                   "data"),
    2132              :                 "attributes"),
    2133              :               "payload"),
    2134              :             "data"),
    2135              :           "id"));
    2136            0 :   if (NULL == persona_inquiry_id)
    2137              :   {
    2138            0 :     GNUNET_break_op (0);
    2139            0 :     json_dumpf (body,
    2140              :                 stderr,
    2141              :                 JSON_INDENT (2));
    2142            0 :     wh->resp = TALER_MHD_MAKE_JSON_PACK (
    2143              :       TALER_JSON_pack_ec (
    2144              :         TALER_EC_EXCHANGE_KYC_GENERIC_PROVIDER_UNEXPECTED_REPLY),
    2145              :       GNUNET_JSON_pack_string ("detail",
    2146              :                                "data-attributes-payload-data-id"),
    2147              :       GNUNET_JSON_pack_object_incref ("webhook_body",
    2148              :                                       (json_t *) body));
    2149            0 :     wh->response_code = MHD_HTTP_BAD_REQUEST;
    2150            0 :     wh->task = GNUNET_SCHEDULER_add_now (&async_webhook_reply,
    2151              :                                          wh);
    2152            0 :     return wh;
    2153              :   }
    2154            0 :   qs = plc (plc_cls,
    2155            0 :             wh->pd->section,
    2156              :             persona_inquiry_id,
    2157              :             &wh->h_payto,
    2158              :             &wh->is_wallet,
    2159              :             &wh->process_row);
    2160            0 :   if (qs < 0)
    2161              :   {
    2162            0 :     wh->resp = TALER_MHD_make_error (TALER_EC_GENERIC_DB_FETCH_FAILED,
    2163              :                                      "provider-legitimization-lookup");
    2164            0 :     wh->response_code = MHD_HTTP_INTERNAL_SERVER_ERROR;
    2165            0 :     wh->task = GNUNET_SCHEDULER_add_now (&async_webhook_reply,
    2166              :                                          wh);
    2167            0 :     return wh;
    2168              :   }
    2169            0 :   if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
    2170              :   {
    2171            0 :     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    2172              :                 "Received Persona kyc-webhook for unknown verification ID `%s'\n",
    2173              :                 persona_inquiry_id);
    2174            0 :     wh->resp = TALER_MHD_make_error (
    2175              :       TALER_EC_EXCHANGE_KYC_PROOF_REQUEST_UNKNOWN,
    2176              :       persona_inquiry_id);
    2177            0 :     wh->response_code = MHD_HTTP_NOT_FOUND;
    2178            0 :     wh->task = GNUNET_SCHEDULER_add_now (&async_webhook_reply,
    2179              :                                          wh);
    2180            0 :     return wh;
    2181              :   }
    2182            0 :   wh->inquiry_id = GNUNET_strdup (persona_inquiry_id);
    2183              : 
    2184            0 :   eh = curl_easy_init ();
    2185            0 :   if (NULL == eh)
    2186              :   {
    2187            0 :     GNUNET_break (0);
    2188            0 :     wh->resp = TALER_MHD_make_error (
    2189              :       TALER_EC_GENERIC_ALLOCATION_FAILURE,
    2190              :       NULL);
    2191            0 :     wh->response_code = MHD_HTTP_INTERNAL_SERVER_ERROR;
    2192            0 :     wh->task = GNUNET_SCHEDULER_add_now (&async_webhook_reply,
    2193              :                                          wh);
    2194            0 :     return wh;
    2195              :   }
    2196              : 
    2197            0 :   GNUNET_asprintf (&wh->url,
    2198              :                    "https://withpersona.com/api/v1/inquiries/%s",
    2199              :                    persona_inquiry_id);
    2200            0 :   GNUNET_break (CURLE_OK ==
    2201              :                 curl_easy_setopt (eh,
    2202              :                                   CURLOPT_VERBOSE,
    2203              :                                   0));
    2204            0 :   GNUNET_assert (CURLE_OK ==
    2205              :                  curl_easy_setopt (eh,
    2206              :                                    CURLOPT_MAXREDIRS,
    2207              :                                    1L));
    2208            0 :   GNUNET_break (CURLE_OK ==
    2209              :                 curl_easy_setopt (eh,
    2210              :                                   CURLOPT_URL,
    2211              :                                   wh->url));
    2212            0 :   wh->job = GNUNET_CURL_job_add2 (ps->curl_ctx,
    2213              :                                   eh,
    2214            0 :                                   wh->pd->slist,
    2215              :                                   &handle_webhook_finished,
    2216              :                                   wh);
    2217            0 :   return wh;
    2218              : }
    2219              : 
    2220              : 
    2221              : /**
    2222              :  * Initialize persona logic plugin
    2223              :  *
    2224              :  * @param cls a configuration instance
    2225              :  * @return NULL on error, otherwise a `struct TALER_KYCLOGIC_Plugin`
    2226              :  */
    2227              : void *
    2228              : libtaler_plugin_kyclogic_persona_init (void *cls);
    2229              : 
    2230              : /* declaration to avoid compiler warning */
    2231              : void *
    2232           61 : libtaler_plugin_kyclogic_persona_init (void *cls)
    2233              : {
    2234           61 :   const struct GNUNET_CONFIGURATION_Handle *cfg = cls;
    2235              :   struct TALER_KYCLOGIC_Plugin *plugin;
    2236              :   struct PluginState *ps;
    2237              : 
    2238           61 :   ps = GNUNET_new (struct PluginState);
    2239           61 :   ps->cfg = cfg;
    2240           61 :   if (GNUNET_OK !=
    2241           61 :       GNUNET_CONFIGURATION_get_value_string (cfg,
    2242              :                                              "exchange",
    2243              :                                              "BASE_URL",
    2244              :                                              &ps->exchange_base_url))
    2245              :   {
    2246            0 :     GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
    2247              :                                "exchange",
    2248              :                                "BASE_URL");
    2249            0 :     GNUNET_free (ps);
    2250            0 :     return NULL;
    2251              :   }
    2252           61 :   if (GNUNET_OK !=
    2253           61 :       GNUNET_CONFIGURATION_get_value_string (ps->cfg,
    2254              :                                              "kyclogic-persona",
    2255              :                                              "WEBHOOK_AUTH_TOKEN",
    2256              :                                              &ps->webhook_token))
    2257              :   {
    2258              :     /* optional */
    2259           61 :     ps->webhook_token = NULL;
    2260              :   }
    2261              : 
    2262              :   ps->curl_ctx
    2263          122 :     = GNUNET_CURL_init (&GNUNET_CURL_gnunet_scheduler_reschedule,
    2264           61 :                         &ps->curl_rc);
    2265           61 :   if (NULL == ps->curl_ctx)
    2266              :   {
    2267            0 :     GNUNET_break (0);
    2268            0 :     GNUNET_free (ps->exchange_base_url);
    2269            0 :     GNUNET_free (ps);
    2270            0 :     return NULL;
    2271              :   }
    2272           61 :   ps->curl_rc = GNUNET_CURL_gnunet_rc_create (ps->curl_ctx);
    2273              : 
    2274           61 :   plugin = GNUNET_new (struct TALER_KYCLOGIC_Plugin);
    2275           61 :   plugin->cls = ps;
    2276              :   plugin->load_configuration
    2277           61 :     = &persona_load_configuration;
    2278              :   plugin->unload_configuration
    2279           61 :     = &persona_unload_configuration;
    2280              :   plugin->initiate
    2281           61 :     = &persona_initiate;
    2282              :   plugin->initiate_cancel
    2283           61 :     = &persona_initiate_cancel;
    2284              :   plugin->proof
    2285           61 :     = &persona_proof;
    2286              :   plugin->proof_cancel
    2287           61 :     = &persona_proof_cancel;
    2288              :   plugin->webhook
    2289           61 :     = &persona_webhook;
    2290              :   plugin->webhook_cancel
    2291           61 :     = &persona_webhook_cancel;
    2292           61 :   return plugin;
    2293              : }
    2294              : 
    2295              : 
    2296              : /**
    2297              :  * Unload authorization plugin
    2298              :  *
    2299              :  * @param cls a `struct TALER_KYCLOGIC_Plugin`
    2300              :  * @return NULL (always)
    2301              :  */
    2302              : void *
    2303              : libtaler_plugin_kyclogic_persona_done (void *cls);
    2304              : 
    2305              : /* declaration to avoid compiler warning */
    2306              : 
    2307              : void *
    2308           61 : libtaler_plugin_kyclogic_persona_done (void *cls)
    2309              : {
    2310           61 :   struct TALER_KYCLOGIC_Plugin *plugin = cls;
    2311           61 :   struct PluginState *ps = plugin->cls;
    2312              : 
    2313           61 :   if (NULL != ps->curl_ctx)
    2314              :   {
    2315           61 :     GNUNET_CURL_fini (ps->curl_ctx);
    2316           61 :     ps->curl_ctx = NULL;
    2317              :   }
    2318           61 :   if (NULL != ps->curl_rc)
    2319              :   {
    2320           61 :     GNUNET_CURL_gnunet_rc_destroy (ps->curl_rc);
    2321           61 :     ps->curl_rc = NULL;
    2322              :   }
    2323           61 :   GNUNET_free (ps->exchange_base_url);
    2324           61 :   GNUNET_free (ps->webhook_token);
    2325           61 :   GNUNET_free (ps);
    2326           61 :   GNUNET_free (plugin);
    2327           61 :   return NULL;
    2328              : }
    2329              : 
    2330              : 
    2331              : /* end of plugin_kyclogic_persona.c */
        

Generated by: LCOV version 2.0-1