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-03-10 12:10:57 Functions: 17.4 % 23 4

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

Generated by: LCOV version 2.0-1