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

Generated by: LCOV version 1.16