exchange

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

plugin_kyclogic_persona.c (69035B)


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