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 (68502B)


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