LCOV - code coverage report
Current view: top level - kyclogic - plugin_kyclogic_persona.c (source / functions) Coverage Total Hit
Test: coverage.info Lines: 11.3 % 684 77
Test Date: 2026-04-14 15:39:31 Functions: 17.4 % 23 4

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

Generated by: LCOV version 2.0-1