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-22 12:09:43 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 "taler/platform.h"
      22             : #include "taler/taler_attributes.h"
      23             : #include "taler/taler_kyclogic_plugin.h"
      24             : #include "taler/taler_mhd_lib.h"
      25             : #include "taler/taler_curl_lib.h"
      26             : #include "taler/taler_json_lib.h"
      27             : #include "taler/taler_kyclogic_lib.h"
      28             : #include "taler/taler_templating_lib.h"
      29             : #include <regex.h>
      30             : #include "taler/taler_util.h"
      31             : 
      32             : 
      33             : /**
      34             :  * Which version of the persona API are we implementing?
      35             :  */
      36             : #define PERSONA_VERSION "2021-07-05"
      37             : 
      38             : /**
      39             :  * Saves the state of a plugin.
      40             :  */
      41             : struct PluginState
      42             : {
      43             : 
      44             :   /**
      45             :    * Our base URL.
      46             :    */
      47             :   char *exchange_base_url;
      48             : 
      49             :   /**
      50             :    * Our global configuration.
      51             :    */
      52             :   const struct GNUNET_CONFIGURATION_Handle *cfg;
      53             : 
      54             :   /**
      55             :    * Context for CURL operations (useful to the event loop)
      56             :    */
      57             :   struct GNUNET_CURL_Context *curl_ctx;
      58             : 
      59             :   /**
      60             :    * Context for integrating @e curl_ctx with the
      61             :    * GNUnet event loop.
      62             :    */
      63             :   struct GNUNET_CURL_RescheduleContext *curl_rc;
      64             : 
      65             :   /**
      66             :    * Authorization token to use when receiving webhooks from the Persona
      67             :    * service.  Optional.  Note that webhooks are *global* and not per
      68             :    * template.
      69             :    */
      70             :   char *webhook_token;
      71             : 
      72             : 
      73             : };
      74             : 
      75             : 
      76             : /**
      77             :  * Keeps the plugin-specific state for
      78             :  * a given configuration section.
      79             :  */
      80             : struct TALER_KYCLOGIC_ProviderDetails
      81             : {
      82             : 
      83             :   /**
      84             :    * Overall plugin state.
      85             :    */
      86             :   struct PluginState *ps;
      87             : 
      88             :   /**
      89             :    * Configuration section that configured us.
      90             :    */
      91             :   char *section;
      92             : 
      93             :   /**
      94             :    * Salt to use for idempotency.
      95             :    */
      96             :   char *salt;
      97             : 
      98             :   /**
      99             :    * Authorization token to use when talking
     100             :    * to the service.
     101             :    */
     102             :   char *auth_token;
     103             : 
     104             :   /**
     105             :    * Template ID for the KYC check to perform.
     106             :    */
     107             :   char *template_id;
     108             : 
     109             :   /**
     110             :    * Subdomain to use.
     111             :    */
     112             :   char *subdomain;
     113             : 
     114             :   /**
     115             :    * Name of the program we use to convert outputs
     116             :    * from Persona into our JSON inputs.
     117             :    */
     118             :   char *conversion_binary;
     119             : 
     120             :   /**
     121             :    * Where to redirect the client upon completion.
     122             :    */
     123             :   char *post_kyc_redirect_url;
     124             : 
     125             :   /**
     126             :    * Validity time for a successful KYC process.
     127             :    */
     128             :   struct GNUNET_TIME_Relative validity;
     129             : 
     130             :   /**
     131             :    * Curl-ready authentication header to use.
     132             :    */
     133             :   struct curl_slist *slist;
     134             : 
     135             : };
     136             : 
     137             : 
     138             : /**
     139             :  * Handle for an initiation operation.
     140             :  */
     141             : struct TALER_KYCLOGIC_InitiateHandle
     142             : {
     143             : 
     144             :   /**
     145             :    * Hash of the payto:// URI we are initiating the KYC for.
     146             :    */
     147             :   struct TALER_NormalizedPaytoHashP h_payto;
     148             : 
     149             :   /**
     150             :    * UUID being checked.
     151             :    */
     152             :   uint64_t legitimization_uuid;
     153             : 
     154             :   /**
     155             :    * Our configuration details.
     156             :    */
     157             :   const struct TALER_KYCLOGIC_ProviderDetails *pd;
     158             : 
     159             :   /**
     160             :    * Continuation to call.
     161             :    */
     162             :   TALER_KYCLOGIC_InitiateCallback cb;
     163             : 
     164             :   /**
     165             :    * Closure for @a cb.
     166             :    */
     167             :   void *cb_cls;
     168             : 
     169             :   /**
     170             :    * Context for #TEH_curl_easy_post(). Keeps the data that must
     171             :    * persist for Curl to make the upload.
     172             :    */
     173             :   struct TALER_CURL_PostContext ctx;
     174             : 
     175             :   /**
     176             :    * Handle for the request.
     177             :    */
     178             :   struct GNUNET_CURL_Job *job;
     179             : 
     180             :   /**
     181             :    * URL of the cURL request.
     182             :    */
     183             :   char *url;
     184             : 
     185             :   /**
     186             :    * Request-specific headers to use.
     187             :    */
     188             :   struct curl_slist *slist;
     189             : 
     190             : };
     191             : 
     192             : 
     193             : /**
     194             :  * Handle for an KYC proof operation.
     195             :  */
     196             : struct TALER_KYCLOGIC_ProofHandle
     197             : {
     198             : 
     199             :   /**
     200             :    * Overall plugin state.
     201             :    */
     202             :   struct PluginState *ps;
     203             : 
     204             :   /**
     205             :    * Our configuration details.
     206             :    */
     207             :   const struct TALER_KYCLOGIC_ProviderDetails *pd;
     208             : 
     209             :   /**
     210             :    * Continuation to call.
     211             :    */
     212             :   TALER_KYCLOGIC_ProofCallback cb;
     213             : 
     214             :   /**
     215             :    * Closure for @e cb.
     216             :    */
     217             :   void *cb_cls;
     218             : 
     219             :   /**
     220             :    * Connection we are handling.
     221             :    */
     222             :   struct MHD_Connection *connection;
     223             : 
     224             :   /**
     225             :    * Task for asynchronous execution.
     226             :    */
     227             :   struct GNUNET_SCHEDULER_Task *task;
     228             : 
     229             :   /**
     230             :    * Handle for the request.
     231             :    */
     232             :   struct GNUNET_CURL_Job *job;
     233             : 
     234             :   /**
     235             :    * URL of the cURL request.
     236             :    */
     237             :   char *url;
     238             : 
     239             :   /**
     240             :    * Handle to an external process that converts the
     241             :    * Persona response to our internal format.
     242             :    */
     243             :   struct TALER_JSON_ExternalConversion *ec;
     244             : 
     245             :   /**
     246             :    * Hash of the payto:// URI we are checking the KYC for.
     247             :    */
     248             :   struct TALER_NormalizedPaytoHashP h_payto;
     249             : 
     250             :   /**
     251             :    * Row in the legitimization processes of the
     252             :    * legitimization proof that is being checked.
     253             :    */
     254             :   uint64_t process_row;
     255             : 
     256             :   /**
     257             :    * Account ID at the provider.
     258             :    */
     259             :   char *provider_user_id;
     260             : 
     261             :   /**
     262             :    * Account ID from the service.
     263             :    */
     264             :   char *account_id;
     265             : 
     266             :   /**
     267             :    * Inquiry ID at the provider.
     268             :    */
     269             :   char *inquiry_id;
     270             : };
     271             : 
     272             : 
     273             : /**
     274             :  * Handle for an KYC Web hook operation.
     275             :  */
     276             : struct TALER_KYCLOGIC_WebhookHandle
     277             : {
     278             : 
     279             :   /**
     280             :    * Continuation to call when done.
     281             :    */
     282             :   TALER_KYCLOGIC_WebhookCallback cb;
     283             : 
     284             :   /**
     285             :    * Closure for @a cb.
     286             :    */
     287             :   void *cb_cls;
     288             : 
     289             :   /**
     290             :    * Task for asynchronous execution.
     291             :    */
     292             :   struct GNUNET_SCHEDULER_Task *task;
     293             : 
     294             :   /**
     295             :    * Overall plugin state.
     296             :    */
     297             :   struct PluginState *ps;
     298             : 
     299             :   /**
     300             :    * Our configuration details.
     301             :    */
     302             :   const struct TALER_KYCLOGIC_ProviderDetails *pd;
     303             : 
     304             :   /**
     305             :    * Connection we are handling.
     306             :    */
     307             :   struct MHD_Connection *connection;
     308             : 
     309             :   /**
     310             :    * Verification ID from the service.
     311             :    */
     312             :   char *inquiry_id;
     313             : 
     314             :   /**
     315             :    * Account ID from the service.
     316             :    */
     317             :   char *account_id;
     318             : 
     319             :   /**
     320             :    * URL of the cURL request.
     321             :    */
     322             :   char *url;
     323             : 
     324             :   /**
     325             :    * Handle for the request.
     326             :    */
     327             :   struct GNUNET_CURL_Job *job;
     328             : 
     329             :   /**
     330             :    * Response to return asynchronously.
     331             :    */
     332             :   struct MHD_Response *resp;
     333             : 
     334             :   /**
     335             :    * ID of the template the webhook is about,
     336             :    * according to the service.
     337             :    */
     338             :   const char *template_id;
     339             : 
     340             :   /**
     341             :    * Handle to an external process that converts the
     342             :    * Persona response to our internal format.
     343             :    */
     344             :   struct TALER_JSON_ExternalConversion *ec;
     345             : 
     346             :   /**
     347             :    * Our account ID.
     348             :    */
     349             :   struct TALER_NormalizedPaytoHashP h_payto;
     350             : 
     351             :   /**
     352             :    * UUID being checked.
     353             :    */
     354             :   uint64_t process_row;
     355             : 
     356             :   /**
     357             :    * HTTP 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             :                                 false);
    1096           0 :   ph->cb (ph->cb_cls,
    1097             :           TALER_KYCLOGIC_STATUS_SUCCESS,
    1098           0 :           ph->pd->section,
    1099           0 :           ph->account_id,
    1100           0 :           ph->inquiry_id,
    1101             :           expiration,
    1102             :           attr,
    1103             :           MHD_HTTP_SEE_OTHER,
    1104             :           resp);
    1105           0 :   persona_proof_cancel (ph);
    1106             : }
    1107             : 
    1108             : 
    1109             : /**
    1110             :  * Function called when we're done processing the
    1111             :  * HTTP "/api/v1/inquiries/{inquiry-id}" request.
    1112             :  *
    1113             :  * @param cls the `struct TALER_KYCLOGIC_InitiateHandle`
    1114             :  * @param response_code HTTP response code, 0 on error
    1115             :  * @param response parsed JSON result, NULL on error
    1116             :  */
    1117             : static void
    1118           0 : handle_proof_finished (void *cls,
    1119             :                        long response_code,
    1120             :                        const void *response)
    1121             : {
    1122           0 :   struct TALER_KYCLOGIC_ProofHandle *ph = cls;
    1123           0 :   const json_t *j = response;
    1124           0 :   const json_t *data = json_object_get (j,
    1125             :                                         "data");
    1126             : 
    1127           0 :   ph->job = NULL;
    1128           0 :   switch (response_code)
    1129             :   {
    1130           0 :   case MHD_HTTP_OK:
    1131             :     {
    1132             :       const char *inquiry_id;
    1133             :       const char *account_id;
    1134           0 :       const char *type = NULL;
    1135             :       const json_t *attributes;
    1136             :       const json_t *relationships;
    1137             :       struct GNUNET_JSON_Specification spec[] = {
    1138           0 :         GNUNET_JSON_spec_string ("type",
    1139             :                                  &type),
    1140           0 :         GNUNET_JSON_spec_string ("id",
    1141             :                                  &inquiry_id),
    1142           0 :         GNUNET_JSON_spec_object_const ("attributes",
    1143             :                                        &attributes),
    1144           0 :         GNUNET_JSON_spec_object_const ("relationships",
    1145             :                                        &relationships),
    1146           0 :         GNUNET_JSON_spec_end ()
    1147             :       };
    1148             : 
    1149           0 :       if ( (NULL == data) ||
    1150             :            (GNUNET_OK !=
    1151           0 :             GNUNET_JSON_parse (data,
    1152             :                                spec,
    1153           0 :                                NULL, NULL)) ||
    1154           0 :            (0 != strcasecmp (type,
    1155             :                              "inquiry")) )
    1156             :       {
    1157           0 :         GNUNET_break_op (0);
    1158           0 :         return_invalid_response (ph,
    1159             :                                  response_code,
    1160             :                                  inquiry_id,
    1161             :                                  "data",
    1162             :                                  data);
    1163           0 :         break;
    1164             :       }
    1165             : 
    1166             :       {
    1167             :         const char *status; /* "completed", what else? */
    1168             :         const char *reference_id; /* or legitimization number */
    1169           0 :         const char *expired_at = NULL; /* often 'null' format: "2022-08-18T10:14:26.000Z" */
    1170             :         struct GNUNET_JSON_Specification ispec[] = {
    1171           0 :           GNUNET_JSON_spec_string ("status",
    1172             :                                    &status),
    1173           0 :           GNUNET_JSON_spec_string ("reference-id",
    1174             :                                    &reference_id),
    1175           0 :           GNUNET_JSON_spec_mark_optional (
    1176             :             GNUNET_JSON_spec_string ("expired-at",
    1177             :                                      &expired_at),
    1178             :             NULL),
    1179           0 :           GNUNET_JSON_spec_end ()
    1180             :         };
    1181             : 
    1182           0 :         if (GNUNET_OK !=
    1183           0 :             GNUNET_JSON_parse (attributes,
    1184             :                                ispec,
    1185             :                                NULL, NULL))
    1186             :         {
    1187           0 :           GNUNET_break_op (0);
    1188           0 :           return_invalid_response (ph,
    1189             :                                    response_code,
    1190             :                                    inquiry_id,
    1191             :                                    "data-attributes",
    1192             :                                    data);
    1193           0 :           break;
    1194             :         }
    1195             :         {
    1196             :           unsigned long long idr;
    1197             :           char dummy;
    1198             : 
    1199           0 :           if ( (1 != sscanf (reference_id,
    1200             :                              "%llu%c",
    1201             :                              &idr,
    1202           0 :                              &dummy)) ||
    1203           0 :                (idr != ph->process_row) )
    1204             :           {
    1205           0 :             GNUNET_break_op (0);
    1206           0 :             return_invalid_response (ph,
    1207             :                                      response_code,
    1208             :                                      inquiry_id,
    1209             :                                      "data-attributes-reference_id",
    1210             :                                      data);
    1211           0 :             break;
    1212             :           }
    1213             :         }
    1214             : 
    1215           0 :         if (0 != strcmp (inquiry_id,
    1216           0 :                          ph->inquiry_id))
    1217             :         {
    1218           0 :           GNUNET_break_op (0);
    1219           0 :           return_invalid_response (ph,
    1220             :                                    response_code,
    1221             :                                    inquiry_id,
    1222             :                                    "data-id",
    1223             :                                    data);
    1224           0 :           break;
    1225             :         }
    1226             : 
    1227           0 :         account_id = json_string_value (
    1228           0 :           json_object_get (
    1229           0 :             json_object_get (
    1230           0 :               json_object_get (
    1231             :                 relationships,
    1232             :                 "account"),
    1233             :               "data"),
    1234             :             "id"));
    1235             : 
    1236           0 :         if (0 != strcasecmp (status,
    1237             :                              "completed"))
    1238             :         {
    1239           0 :           proof_generic_reply (
    1240             :             ph,
    1241             :             TALER_KYCLOGIC_STATUS_FAILED,
    1242             :             account_id,
    1243             :             inquiry_id,
    1244             :             MHD_HTTP_OK,
    1245             :             "persona-kyc-failed",
    1246           0 :             GNUNET_JSON_PACK (
    1247             :               GNUNET_JSON_pack_uint64 ("persona_http_status",
    1248             :                                        response_code),
    1249             :               GNUNET_JSON_pack_string ("persona_inquiry_id",
    1250             :                                        inquiry_id),
    1251             :               GNUNET_JSON_pack_allow_null (
    1252             :                 GNUNET_JSON_pack_object_incref ("data",
    1253             :                                                 (json_t *)
    1254             :                                                 data))));
    1255           0 :           break;
    1256             :         }
    1257             : 
    1258           0 :         if (NULL == account_id)
    1259             :         {
    1260           0 :           GNUNET_break_op (0);
    1261           0 :           return_invalid_response (ph,
    1262             :                                    response_code,
    1263             :                                    inquiry_id,
    1264             :                                    "data-relationships-account-data-id",
    1265             :                                    data);
    1266           0 :           break;
    1267             :         }
    1268           0 :         ph->account_id = GNUNET_strdup (account_id);
    1269           0 :         ph->ec = start_conversion (ph->pd,
    1270             :                                    j,
    1271             :                                    &proof_post_conversion_cb,
    1272             :                                    ph);
    1273           0 :         if (NULL == ph->ec)
    1274             :         {
    1275           0 :           GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    1276             :                       "Failed to start Persona conversion helper\n");
    1277           0 :           proof_reply_error (
    1278             :             ph,
    1279           0 :             ph->inquiry_id,
    1280             :             MHD_HTTP_BAD_GATEWAY,
    1281             :             "persona-logic-failure",
    1282           0 :             GNUNET_JSON_PACK (
    1283             :               TALER_JSON_pack_ec (
    1284             :                 TALER_EC_EXCHANGE_GENERIC_KYC_CONVERTER_FAILED)));
    1285           0 :           break;
    1286             :         }
    1287             :       }
    1288           0 :       return; /* continued in proof_post_conversion_cb */
    1289             :     }
    1290           0 :   case MHD_HTTP_BAD_REQUEST:
    1291             :   case MHD_HTTP_NOT_FOUND:
    1292             :   case MHD_HTTP_CONFLICT:
    1293             :   case MHD_HTTP_UNPROCESSABLE_ENTITY:
    1294             :     /* These are errors with this code */
    1295           0 :     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    1296             :                 "PERSONA failed with response %u:\n",
    1297             :                 (unsigned int) response_code);
    1298           0 :     json_dumpf (j,
    1299             :                 stderr,
    1300             :                 JSON_INDENT (2));
    1301           0 :     proof_reply_error (
    1302             :       ph,
    1303           0 :       ph->inquiry_id,
    1304             :       MHD_HTTP_BAD_GATEWAY,
    1305             :       "persona-logic-failure",
    1306           0 :       GNUNET_JSON_PACK (
    1307             :         GNUNET_JSON_pack_uint64 ("persona_http_status",
    1308             :                                  response_code),
    1309             :         TALER_JSON_pack_ec (
    1310             :           TALER_EC_EXCHANGE_KYC_GENERIC_PROVIDER_UNEXPECTED_REPLY),
    1311             : 
    1312             :         GNUNET_JSON_pack_allow_null (
    1313             :           GNUNET_JSON_pack_object_incref ("data",
    1314             :                                           (json_t *)
    1315             :                                           data))));
    1316           0 :     break;
    1317           0 :   case MHD_HTTP_UNAUTHORIZED:
    1318             :     /* These are failures of the exchange operator */
    1319           0 :     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    1320             :                 "Refused access with HTTP status code %u\n",
    1321             :                 (unsigned int) response_code);
    1322           0 :     proof_reply_error (
    1323             :       ph,
    1324           0 :       ph->inquiry_id,
    1325             :       MHD_HTTP_BAD_GATEWAY,
    1326             :       "persona-exchange-unauthorized",
    1327           0 :       GNUNET_JSON_PACK (
    1328             :         GNUNET_JSON_pack_uint64 ("persona_http_status",
    1329             :                                  response_code),
    1330             :         TALER_JSON_pack_ec (
    1331             :           TALER_EC_EXCHANGE_KYC_GENERIC_PROVIDER_ACCESS_REFUSED),
    1332             :         GNUNET_JSON_pack_allow_null (
    1333             :           GNUNET_JSON_pack_object_incref ("data",
    1334             :                                           (json_t *)
    1335             :                                           data))));
    1336           0 :     break;
    1337           0 :   case MHD_HTTP_PAYMENT_REQUIRED:
    1338             :     /* These are failures of the exchange operator */
    1339           0 :     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    1340             :                 "Refused access with HTTP status code %u\n",
    1341             :                 (unsigned int) response_code);
    1342           0 :     proof_reply_error (
    1343             :       ph,
    1344           0 :       ph->inquiry_id,
    1345             :       MHD_HTTP_SERVICE_UNAVAILABLE,
    1346             :       "persona-exchange-unpaid",
    1347           0 :       GNUNET_JSON_PACK (
    1348             :         GNUNET_JSON_pack_uint64 ("persona_http_status",
    1349             :                                  response_code),
    1350             :         TALER_JSON_pack_ec (
    1351             :           TALER_EC_EXCHANGE_KYC_GENERIC_PROVIDER_ACCESS_REFUSED),
    1352             :         GNUNET_JSON_pack_allow_null (
    1353             :           GNUNET_JSON_pack_object_incref ("data",
    1354             :                                           (json_t *)
    1355             :                                           data))));
    1356           0 :     break;
    1357           0 :   case MHD_HTTP_REQUEST_TIMEOUT:
    1358             :     /* These are networking issues */
    1359           0 :     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    1360             :                 "PERSONA failed with response %u:\n",
    1361             :                 (unsigned int) response_code);
    1362           0 :     json_dumpf (j,
    1363             :                 stderr,
    1364             :                 JSON_INDENT (2));
    1365           0 :     proof_reply_error (
    1366             :       ph,
    1367           0 :       ph->inquiry_id,
    1368             :       MHD_HTTP_GATEWAY_TIMEOUT,
    1369             :       "persona-network-timeout",
    1370           0 :       GNUNET_JSON_PACK (
    1371             :         GNUNET_JSON_pack_uint64 ("persona_http_status",
    1372             :                                  response_code),
    1373             :         TALER_JSON_pack_ec (
    1374             :           TALER_EC_EXCHANGE_KYC_GENERIC_PROVIDER_TIMEOUT),
    1375             :         GNUNET_JSON_pack_allow_null (
    1376             :           GNUNET_JSON_pack_object_incref ("data",
    1377             :                                           (json_t *)
    1378             :                                           data))));
    1379           0 :     break;
    1380           0 :   case MHD_HTTP_TOO_MANY_REQUESTS:
    1381             :     /* This is a load issue */
    1382           0 :     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    1383             :                 "PERSONA failed with response %u:\n",
    1384             :                 (unsigned int) response_code);
    1385           0 :     json_dumpf (j,
    1386             :                 stderr,
    1387             :                 JSON_INDENT (2));
    1388           0 :     proof_reply_error (
    1389             :       ph,
    1390           0 :       ph->inquiry_id,
    1391             :       MHD_HTTP_SERVICE_UNAVAILABLE,
    1392             :       "persona-load-failure",
    1393           0 :       GNUNET_JSON_PACK (
    1394             :         GNUNET_JSON_pack_uint64 ("persona_http_status",
    1395             :                                  response_code),
    1396             :         TALER_JSON_pack_ec (
    1397             :           TALER_EC_EXCHANGE_KYC_GENERIC_PROVIDER_RATE_LIMIT_EXCEEDED),
    1398             :         GNUNET_JSON_pack_allow_null (
    1399             :           GNUNET_JSON_pack_object_incref ("data",
    1400             :                                           (json_t *)
    1401             :                                           data))));
    1402           0 :     break;
    1403           0 :   case MHD_HTTP_INTERNAL_SERVER_ERROR:
    1404             :     /* This is an issue with Persona */
    1405           0 :     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    1406             :                 "PERSONA failed with response %u:\n",
    1407             :                 (unsigned int) response_code);
    1408           0 :     json_dumpf (j,
    1409             :                 stderr,
    1410             :                 JSON_INDENT (2));
    1411           0 :     proof_reply_error (
    1412             :       ph,
    1413           0 :       ph->inquiry_id,
    1414             :       MHD_HTTP_BAD_GATEWAY,
    1415             :       "persona-provider-failure",
    1416           0 :       GNUNET_JSON_PACK (
    1417             :         GNUNET_JSON_pack_uint64 ("persona_http_status",
    1418             :                                  response_code),
    1419             :         TALER_JSON_pack_ec (
    1420             :           TALER_EC_EXCHANGE_KYC_PROOF_BACKEND_ERROR),
    1421             :         GNUNET_JSON_pack_allow_null (
    1422             :           GNUNET_JSON_pack_object_incref ("data",
    1423             :                                           (json_t *)
    1424             :                                           data))));
    1425           0 :     break;
    1426           0 :   default:
    1427             :     /* This is an issue with Persona */
    1428           0 :     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    1429             :                 "PERSONA failed with response %u:\n",
    1430             :                 (unsigned int) response_code);
    1431           0 :     json_dumpf (j,
    1432             :                 stderr,
    1433             :                 JSON_INDENT (2));
    1434           0 :     proof_reply_error (
    1435             :       ph,
    1436           0 :       ph->inquiry_id,
    1437             :       MHD_HTTP_BAD_GATEWAY,
    1438             :       "persona-invalid-response",
    1439           0 :       GNUNET_JSON_PACK (
    1440             :         GNUNET_JSON_pack_uint64 ("persona_http_status",
    1441             :                                  response_code),
    1442             :         TALER_JSON_pack_ec (
    1443             :           TALER_EC_EXCHANGE_KYC_GENERIC_PROVIDER_UNEXPECTED_REPLY),
    1444             :         GNUNET_JSON_pack_allow_null (
    1445             :           GNUNET_JSON_pack_object_incref ("data",
    1446             :                                           (json_t *)
    1447             :                                           data))));
    1448           0 :     break;
    1449             :   }
    1450           0 :   persona_proof_cancel (ph);
    1451             : }
    1452             : 
    1453             : 
    1454             : /**
    1455             :  * Check KYC status and return final result to human.
    1456             :  *
    1457             :  * @param cls the @e cls of this struct with the plugin-specific state
    1458             :  * @param pd provider configuration details
    1459             :  * @param connection MHD connection object (for HTTP headers)
    1460             :  * @param account_id which account to trigger process for
    1461             :  * @param process_row row in the legitimization processes table the legitimization is for
    1462             :  * @param provider_user_id user ID (or NULL) the proof is for
    1463             :  * @param inquiry_id legitimization ID the proof is for
    1464             :  * @param cb function to call with the result
    1465             :  * @param cb_cls closure for @a cb
    1466             :  * @return handle to cancel operation early
    1467             :  */
    1468             : static struct TALER_KYCLOGIC_ProofHandle *
    1469           0 : persona_proof (void *cls,
    1470             :                const struct TALER_KYCLOGIC_ProviderDetails *pd,
    1471             :                struct MHD_Connection *connection,
    1472             :                const struct TALER_NormalizedPaytoHashP *account_id,
    1473             :                uint64_t process_row,
    1474             :                const char *provider_user_id,
    1475             :                const char *inquiry_id,
    1476             :                TALER_KYCLOGIC_ProofCallback cb,
    1477             :                void *cb_cls)
    1478             : {
    1479           0 :   struct PluginState *ps = cls;
    1480             :   struct TALER_KYCLOGIC_ProofHandle *ph;
    1481             :   CURL *eh;
    1482             : 
    1483           0 :   eh = curl_easy_init ();
    1484           0 :   if (NULL == eh)
    1485             :   {
    1486           0 :     GNUNET_break (0);
    1487           0 :     return NULL;
    1488             :   }
    1489           0 :   ph = GNUNET_new (struct TALER_KYCLOGIC_ProofHandle);
    1490           0 :   ph->ps = ps;
    1491           0 :   ph->pd = pd;
    1492           0 :   ph->cb = cb;
    1493           0 :   ph->cb_cls = cb_cls;
    1494           0 :   ph->connection = connection;
    1495           0 :   ph->process_row = process_row;
    1496           0 :   ph->h_payto = *account_id;
    1497             :   /* Note: we do not expect this to be non-NULL */
    1498           0 :   if (NULL != provider_user_id)
    1499           0 :     ph->provider_user_id = GNUNET_strdup (provider_user_id);
    1500           0 :   if (NULL != inquiry_id)
    1501           0 :     ph->inquiry_id = GNUNET_strdup (inquiry_id);
    1502           0 :   GNUNET_asprintf (&ph->url,
    1503             :                    "https://withpersona.com/api/v1/inquiries/%s",
    1504             :                    inquiry_id);
    1505           0 :   GNUNET_break (CURLE_OK ==
    1506             :                 curl_easy_setopt (eh,
    1507             :                                   CURLOPT_VERBOSE,
    1508             :                                   0));
    1509           0 :   GNUNET_assert (CURLE_OK ==
    1510             :                  curl_easy_setopt (eh,
    1511             :                                    CURLOPT_MAXREDIRS,
    1512             :                                    1L));
    1513           0 :   GNUNET_break (CURLE_OK ==
    1514             :                 curl_easy_setopt (eh,
    1515             :                                   CURLOPT_URL,
    1516             :                                   ph->url));
    1517           0 :   ph->job = GNUNET_CURL_job_add2 (ps->curl_ctx,
    1518             :                                   eh,
    1519           0 :                                   pd->slist,
    1520             :                                   &handle_proof_finished,
    1521             :                                   ph);
    1522           0 :   return ph;
    1523             : }
    1524             : 
    1525             : 
    1526             : /**
    1527             :  * Cancel KYC webhook execution.
    1528             :  *
    1529             :  * @param[in] wh handle of operation to cancel
    1530             :  */
    1531             : static void
    1532           0 : persona_webhook_cancel (struct TALER_KYCLOGIC_WebhookHandle *wh)
    1533             : {
    1534           0 :   if (NULL != wh->task)
    1535             :   {
    1536           0 :     GNUNET_SCHEDULER_cancel (wh->task);
    1537           0 :     wh->task = NULL;
    1538             :   }
    1539           0 :   if (NULL != wh->job)
    1540             :   {
    1541           0 :     GNUNET_CURL_job_cancel (wh->job);
    1542           0 :     wh->job = NULL;
    1543             :   }
    1544           0 :   if (NULL != wh->ec)
    1545             :   {
    1546           0 :     TALER_JSON_external_conversion_stop (wh->ec);
    1547           0 :     wh->ec = NULL;
    1548             :   }
    1549           0 :   GNUNET_free (wh->account_id);
    1550           0 :   GNUNET_free (wh->inquiry_id);
    1551           0 :   GNUNET_free (wh->url);
    1552           0 :   GNUNET_free (wh);
    1553           0 : }
    1554             : 
    1555             : 
    1556             : /**
    1557             :  * Call @a wh callback with the operation result.
    1558             :  *
    1559             :  * @param wh proof handle to generate reply for
    1560             :  * @param status status to return
    1561             :  * @param account_id account to return
    1562             :  * @param inquiry_id inquiry ID to supply
    1563             :  * @param attr KYC attribute data for the client
    1564             :  * @param http_status HTTP status to use
    1565             :  */
    1566             : static void
    1567           0 : webhook_generic_reply (struct TALER_KYCLOGIC_WebhookHandle *wh,
    1568             :                        enum TALER_KYCLOGIC_KycStatus status,
    1569             :                        const char *account_id,
    1570             :                        const char *inquiry_id,
    1571             :                        const json_t *attr,
    1572             :                        unsigned int http_status)
    1573             : {
    1574             :   struct MHD_Response *resp;
    1575             :   struct GNUNET_TIME_Absolute expiration;
    1576             : 
    1577           0 :   if (TALER_KYCLOGIC_STATUS_SUCCESS == status)
    1578           0 :     expiration = GNUNET_TIME_relative_to_absolute (wh->pd->validity);
    1579             :   else
    1580           0 :     expiration = GNUNET_TIME_UNIT_ZERO_ABS;
    1581           0 :   resp = MHD_create_response_from_buffer_static (0,
    1582             :                                                  "");
    1583           0 :   TALER_MHD_add_global_headers (resp,
    1584             :                                 true);
    1585           0 :   wh->cb (wh->cb_cls,
    1586             :           wh->process_row,
    1587           0 :           &wh->h_payto,
    1588           0 :           wh->is_wallet,
    1589           0 :           wh->pd->section,
    1590             :           account_id,
    1591             :           inquiry_id,
    1592             :           status,
    1593             :           expiration,
    1594             :           attr,
    1595             :           http_status,
    1596             :           resp);
    1597           0 : }
    1598             : 
    1599             : 
    1600             : /**
    1601             :  * Call @a wh callback with HTTP error response.
    1602             :  *
    1603             :  * @param wh proof handle to generate reply for
    1604             :  * @param inquiry_id inquiry ID to supply
    1605             :  * @param http_status HTTP status to use
    1606             :  */
    1607             : static void
    1608           0 : webhook_reply_error (struct TALER_KYCLOGIC_WebhookHandle *wh,
    1609             :                      const char *inquiry_id,
    1610             :                      unsigned int http_status)
    1611             : {
    1612           0 :   webhook_generic_reply (wh,
    1613             :                          TALER_KYCLOGIC_STATUS_PROVIDER_FAILED,
    1614             :                          NULL, /* user id */
    1615             :                          inquiry_id,
    1616             :                          NULL, /* attributes */
    1617             :                          http_status);
    1618           0 : }
    1619             : 
    1620             : 
    1621             : /**
    1622             :  * Type of a callback that receives a JSON @a result.
    1623             :  *
    1624             :  * @param cls closure with a `struct TALER_KYCLOGIC_WebhookHandle *`
    1625             :  * @param status_type how did the process die
    1626             :  * @param code termination status code from the process
    1627             :  * @param attr some JSON result, NULL if we failed to get an JSON output
    1628             :  */
    1629             : static void
    1630           0 : webhook_post_conversion_cb (void *cls,
    1631             :                             enum GNUNET_OS_ProcessStatusType status_type,
    1632             :                             unsigned long code,
    1633             :                             const json_t *attr)
    1634             : {
    1635           0 :   struct TALER_KYCLOGIC_WebhookHandle *wh = cls;
    1636             : 
    1637           0 :   wh->ec = NULL;
    1638           0 :   webhook_generic_reply (wh,
    1639             :                          TALER_KYCLOGIC_STATUS_SUCCESS,
    1640           0 :                          wh->account_id,
    1641           0 :                          wh->inquiry_id,
    1642             :                          attr,
    1643             :                          MHD_HTTP_OK);
    1644           0 : }
    1645             : 
    1646             : 
    1647             : /**
    1648             :  * Function called when we're done processing the
    1649             :  * HTTP "/api/v1/inquiries/{inquiry_id}" request.
    1650             :  *
    1651             :  * @param cls the `struct TALER_KYCLOGIC_WebhookHandle`
    1652             :  * @param response_code HTTP response code, 0 on error
    1653             :  * @param response parsed JSON result, NULL on error
    1654             :  */
    1655             : static void
    1656           0 : handle_webhook_finished (void *cls,
    1657             :                          long response_code,
    1658             :                          const void *response)
    1659             : {
    1660           0 :   struct TALER_KYCLOGIC_WebhookHandle *wh = cls;
    1661           0 :   const json_t *j = response;
    1662           0 :   const json_t *data = json_object_get (j,
    1663             :                                         "data");
    1664             : 
    1665           0 :   wh->job = NULL;
    1666           0 :   switch (response_code)
    1667             :   {
    1668           0 :   case MHD_HTTP_OK:
    1669             :     {
    1670             :       const char *inquiry_id;
    1671             :       const char *account_id;
    1672           0 :       const char *type = NULL;
    1673             :       const json_t *attributes;
    1674             :       const json_t *relationships;
    1675             :       struct GNUNET_JSON_Specification spec[] = {
    1676           0 :         GNUNET_JSON_spec_string ("type",
    1677             :                                  &type),
    1678           0 :         GNUNET_JSON_spec_string ("id",
    1679             :                                  &inquiry_id),
    1680           0 :         GNUNET_JSON_spec_object_const ("attributes",
    1681             :                                        &attributes),
    1682           0 :         GNUNET_JSON_spec_object_const ("relationships",
    1683             :                                        &relationships),
    1684           0 :         GNUNET_JSON_spec_end ()
    1685             :       };
    1686             : 
    1687           0 :       if ( (NULL == data) ||
    1688             :            (GNUNET_OK !=
    1689           0 :             GNUNET_JSON_parse (data,
    1690             :                                spec,
    1691           0 :                                NULL, NULL)) ||
    1692           0 :            (0 != strcasecmp (type,
    1693             :                              "inquiry")) )
    1694             :       {
    1695           0 :         GNUNET_break_op (0);
    1696           0 :         json_dumpf (j,
    1697             :                     stderr,
    1698             :                     JSON_INDENT (2));
    1699           0 :         webhook_reply_error (wh,
    1700             :                              inquiry_id,
    1701             :                              MHD_HTTP_BAD_GATEWAY);
    1702           0 :         break;
    1703             :       }
    1704             : 
    1705             :       {
    1706             :         const char *status; /* "completed", what else? */
    1707             :         const char *reference_id; /* or legitimization number */
    1708           0 :         const char *expired_at = NULL; /* often 'null' format: "2022-08-18T10:14:26.000Z" */
    1709             :         struct GNUNET_JSON_Specification ispec[] = {
    1710           0 :           GNUNET_JSON_spec_string ("status",
    1711             :                                    &status),
    1712           0 :           GNUNET_JSON_spec_string ("reference-id",
    1713             :                                    &reference_id),
    1714           0 :           GNUNET_JSON_spec_mark_optional (
    1715             :             GNUNET_JSON_spec_string ("expired-at",
    1716             :                                      &expired_at),
    1717             :             NULL),
    1718           0 :           GNUNET_JSON_spec_end ()
    1719             :         };
    1720             : 
    1721           0 :         if (GNUNET_OK !=
    1722           0 :             GNUNET_JSON_parse (attributes,
    1723             :                                ispec,
    1724             :                                NULL, NULL))
    1725             :         {
    1726           0 :           GNUNET_break_op (0);
    1727           0 :           json_dumpf (j,
    1728             :                       stderr,
    1729             :                       JSON_INDENT (2));
    1730           0 :           webhook_reply_error (wh,
    1731             :                                inquiry_id,
    1732             :                                MHD_HTTP_BAD_GATEWAY);
    1733           0 :           break;
    1734             :         }
    1735             :         {
    1736             :           unsigned long long idr;
    1737             :           char dummy;
    1738             : 
    1739           0 :           if ( (1 != sscanf (reference_id,
    1740             :                              "%llu%c",
    1741             :                              &idr,
    1742           0 :                              &dummy)) ||
    1743           0 :                (idr != wh->process_row) )
    1744             :           {
    1745           0 :             GNUNET_break_op (0);
    1746           0 :             webhook_reply_error (wh,
    1747             :                                  inquiry_id,
    1748             :                                  MHD_HTTP_BAD_GATEWAY);
    1749           0 :             break;
    1750             :           }
    1751             :         }
    1752             : 
    1753           0 :         if (0 != strcmp (inquiry_id,
    1754           0 :                          wh->inquiry_id))
    1755             :         {
    1756           0 :           GNUNET_break_op (0);
    1757           0 :           webhook_reply_error (wh,
    1758             :                                inquiry_id,
    1759             :                                MHD_HTTP_BAD_GATEWAY);
    1760           0 :           break;
    1761             :         }
    1762             : 
    1763           0 :         account_id = json_string_value (
    1764           0 :           json_object_get (
    1765           0 :             json_object_get (
    1766           0 :               json_object_get (
    1767             :                 relationships,
    1768             :                 "account"),
    1769             :               "data"),
    1770             :             "id"));
    1771             : 
    1772           0 :         if (0 != strcasecmp (status,
    1773             :                              "completed"))
    1774             :         {
    1775           0 :           webhook_generic_reply (wh,
    1776             :                                  TALER_KYCLOGIC_STATUS_FAILED,
    1777             :                                  account_id,
    1778             :                                  inquiry_id,
    1779             :                                  NULL,
    1780             :                                  MHD_HTTP_OK);
    1781           0 :           break;
    1782             :         }
    1783             : 
    1784           0 :         if (NULL == account_id)
    1785             :         {
    1786           0 :           GNUNET_break_op (0);
    1787           0 :           json_dumpf (data,
    1788             :                       stderr,
    1789             :                       JSON_INDENT (2));
    1790           0 :           webhook_reply_error (wh,
    1791             :                                inquiry_id,
    1792             :                                MHD_HTTP_BAD_GATEWAY);
    1793           0 :           break;
    1794             :         }
    1795           0 :         wh->account_id = GNUNET_strdup (account_id);
    1796           0 :         wh->ec = start_conversion (wh->pd,
    1797             :                                    j,
    1798             :                                    &webhook_post_conversion_cb,
    1799             :                                    wh);
    1800           0 :         if (NULL == wh->ec)
    1801             :         {
    1802           0 :           GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    1803             :                       "Failed to start Persona conversion helper\n");
    1804           0 :           webhook_reply_error (wh,
    1805             :                                inquiry_id,
    1806             :                                MHD_HTTP_INTERNAL_SERVER_ERROR);
    1807           0 :           break;
    1808             :         }
    1809             :       }
    1810           0 :       return; /* continued in webhook_post_conversion_cb */
    1811             :     }
    1812           0 :   case MHD_HTTP_BAD_REQUEST:
    1813             :   case MHD_HTTP_NOT_FOUND:
    1814             :   case MHD_HTTP_CONFLICT:
    1815             :   case MHD_HTTP_UNPROCESSABLE_ENTITY:
    1816             :     /* These are errors with this code */
    1817           0 :     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    1818             :                 "PERSONA failed with response %u:\n",
    1819             :                 (unsigned int) response_code);
    1820           0 :     json_dumpf (j,
    1821             :                 stderr,
    1822             :                 JSON_INDENT (2));
    1823           0 :     webhook_reply_error (wh,
    1824           0 :                          wh->inquiry_id,
    1825             :                          MHD_HTTP_BAD_GATEWAY);
    1826           0 :     break;
    1827           0 :   case MHD_HTTP_UNAUTHORIZED:
    1828             :     /* These are failures of the exchange operator */
    1829           0 :     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    1830             :                 "Refused access with HTTP status code %u\n",
    1831             :                 (unsigned int) response_code);
    1832           0 :     webhook_reply_error (wh,
    1833           0 :                          wh->inquiry_id,
    1834             :                          MHD_HTTP_INTERNAL_SERVER_ERROR);
    1835           0 :     break;
    1836           0 :   case MHD_HTTP_PAYMENT_REQUIRED:
    1837             :     /* These are failures of the exchange operator */
    1838           0 :     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    1839             :                 "Refused access with HTTP status code %u\n",
    1840             :                 (unsigned int) response_code);
    1841             : 
    1842           0 :     webhook_reply_error (wh,
    1843           0 :                          wh->inquiry_id,
    1844             :                          MHD_HTTP_INTERNAL_SERVER_ERROR);
    1845           0 :     break;
    1846           0 :   case MHD_HTTP_REQUEST_TIMEOUT:
    1847             :     /* These are networking issues */
    1848           0 :     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    1849             :                 "PERSONA failed with response %u:\n",
    1850             :                 (unsigned int) response_code);
    1851           0 :     json_dumpf (j,
    1852             :                 stderr,
    1853             :                 JSON_INDENT (2));
    1854           0 :     webhook_reply_error (wh,
    1855           0 :                          wh->inquiry_id,
    1856             :                          MHD_HTTP_GATEWAY_TIMEOUT);
    1857           0 :     break;
    1858           0 :   case MHD_HTTP_TOO_MANY_REQUESTS:
    1859             :     /* This is a load issue */
    1860           0 :     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    1861             :                 "PERSONA failed with response %u:\n",
    1862             :                 (unsigned int) response_code);
    1863           0 :     json_dumpf (j,
    1864             :                 stderr,
    1865             :                 JSON_INDENT (2));
    1866           0 :     webhook_reply_error (wh,
    1867           0 :                          wh->inquiry_id,
    1868             :                          MHD_HTTP_SERVICE_UNAVAILABLE);
    1869           0 :     break;
    1870           0 :   case MHD_HTTP_INTERNAL_SERVER_ERROR:
    1871             :     /* This is an issue with Persona */
    1872           0 :     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    1873             :                 "PERSONA failed with response %u:\n",
    1874             :                 (unsigned int) response_code);
    1875           0 :     json_dumpf (j,
    1876             :                 stderr,
    1877             :                 JSON_INDENT (2));
    1878           0 :     webhook_reply_error (wh,
    1879           0 :                          wh->inquiry_id,
    1880             :                          MHD_HTTP_BAD_GATEWAY);
    1881           0 :     break;
    1882           0 :   default:
    1883             :     /* This is an issue with Persona */
    1884           0 :     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    1885             :                 "PERSONA failed with response %u:\n",
    1886             :                 (unsigned int) response_code);
    1887           0 :     json_dumpf (j,
    1888             :                 stderr,
    1889             :                 JSON_INDENT (2));
    1890           0 :     webhook_reply_error (wh,
    1891           0 :                          wh->inquiry_id,
    1892             :                          MHD_HTTP_BAD_GATEWAY);
    1893           0 :     break;
    1894             :   }
    1895             : 
    1896           0 :   persona_webhook_cancel (wh);
    1897             : }
    1898             : 
    1899             : 
    1900             : /**
    1901             :  * Asynchronously return a reply for the webhook.
    1902             :  *
    1903             :  * @param cls a `struct TALER_KYCLOGIC_WebhookHandle *`
    1904             :  */
    1905             : static void
    1906           0 : async_webhook_reply (void *cls)
    1907             : {
    1908           0 :   struct TALER_KYCLOGIC_WebhookHandle *wh = cls;
    1909             : 
    1910           0 :   wh->task = NULL;
    1911           0 :   wh->cb (wh->cb_cls,
    1912             :           wh->process_row,
    1913           0 :           (0 == wh->process_row)
    1914             :           ? NULL
    1915             :           : &wh->h_payto,
    1916           0 :           wh->is_wallet,
    1917           0 :           wh->pd->section,
    1918             :           NULL,
    1919           0 :           wh->inquiry_id, /* provider legi ID */
    1920             :           TALER_KYCLOGIC_STATUS_PROVIDER_FAILED,
    1921           0 :           GNUNET_TIME_UNIT_ZERO_ABS, /* expiration */
    1922             :           NULL,
    1923             :           wh->response_code,
    1924             :           wh->resp);
    1925           0 :   persona_webhook_cancel (wh);
    1926           0 : }
    1927             : 
    1928             : 
    1929             : /**
    1930             :  * Function called with the provider details and
    1931             :  * associated plugin closures for matching logics.
    1932             :  *
    1933             :  * @param cls closure
    1934             :  * @param pd provider details of a matching logic
    1935             :  * @param plugin_cls closure of the plugin
    1936             :  * @return #GNUNET_OK to continue to iterate
    1937             :  */
    1938             : static enum GNUNET_GenericReturnValue
    1939           0 : locate_details_cb (
    1940             :   void *cls,
    1941             :   const struct TALER_KYCLOGIC_ProviderDetails *pd,
    1942             :   void *plugin_cls)
    1943             : {
    1944           0 :   struct TALER_KYCLOGIC_WebhookHandle *wh = cls;
    1945             : 
    1946             :   /* This type-checks 'pd' */
    1947           0 :   GNUNET_assert (plugin_cls == wh->ps);
    1948           0 :   if (0 == strcmp (pd->template_id,
    1949             :                    wh->template_id))
    1950             :   {
    1951           0 :     wh->pd = pd;
    1952           0 :     return GNUNET_NO;
    1953             :   }
    1954           0 :   return GNUNET_OK;
    1955             : }
    1956             : 
    1957             : 
    1958             : /**
    1959             :  * Check KYC status and return result for Webhook.  We do NOT implement the
    1960             :  * authentication check proposed by the PERSONA documentation, as it would
    1961             :  * allow an attacker who learns the access token to easily bypass the KYC
    1962             :  * checks. Instead, we insist on explicitly requesting the KYC status from the
    1963             :  * provider (at least on success).
    1964             :  *
    1965             :  * @param cls the @e cls of this struct with the plugin-specific state
    1966             :  * @param pd provider configuration details
    1967             :  * @param plc callback to lookup accounts with
    1968             :  * @param plc_cls closure for @a plc
    1969             :  * @param http_method HTTP method used for the webhook
    1970             :  * @param url_path rest of the URL after `/kyc-webhook/`
    1971             :  * @param connection MHD connection object (for HTTP headers)
    1972             :  * @param body HTTP request body
    1973             :  * @param cb function to call with the result
    1974             :  * @param cb_cls closure for @a cb
    1975             :  * @return handle to cancel operation early
    1976             :  */
    1977             : static struct TALER_KYCLOGIC_WebhookHandle *
    1978           0 : persona_webhook (void *cls,
    1979             :                  const struct TALER_KYCLOGIC_ProviderDetails *pd,
    1980             :                  TALER_KYCLOGIC_ProviderLookupCallback plc,
    1981             :                  void *plc_cls,
    1982             :                  const char *http_method,
    1983             :                  const char *const url_path[],
    1984             :                  struct MHD_Connection *connection,
    1985             :                  const json_t *body,
    1986             :                  TALER_KYCLOGIC_WebhookCallback cb,
    1987             :                  void *cb_cls)
    1988             : {
    1989           0 :   struct PluginState *ps = cls;
    1990             :   struct TALER_KYCLOGIC_WebhookHandle *wh;
    1991             :   CURL *eh;
    1992             :   enum GNUNET_DB_QueryStatus qs;
    1993             :   const char *persona_inquiry_id;
    1994             :   const char *auth_header;
    1995             : 
    1996             :   /* Persona webhooks are expected by logic, not by template */
    1997           0 :   GNUNET_break_op (NULL == pd);
    1998           0 :   wh = GNUNET_new (struct TALER_KYCLOGIC_WebhookHandle);
    1999           0 :   wh->cb = cb;
    2000           0 :   wh->cb_cls = cb_cls;
    2001           0 :   wh->ps = ps;
    2002           0 :   wh->connection = connection;
    2003           0 :   wh->pd = pd;
    2004           0 :   auth_header = MHD_lookup_connection_value (connection,
    2005             :                                              MHD_HEADER_KIND,
    2006             :                                              MHD_HTTP_HEADER_AUTHORIZATION);
    2007           0 :   if ( (NULL != ps->webhook_token) &&
    2008           0 :        ( (NULL == auth_header) ||
    2009           0 :          (0 != strcmp (ps->webhook_token,
    2010             :                        auth_header)) ) )
    2011             :   {
    2012           0 :     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    2013             :                 "Invalid authorization header `%s' received for Persona webhook\n",
    2014             :                 auth_header);
    2015           0 :     wh->resp = TALER_MHD_MAKE_JSON_PACK (
    2016             :       TALER_JSON_pack_ec (
    2017             :         TALER_EC_EXCHANGE_KYC_WEBHOOK_UNAUTHORIZED),
    2018             :       GNUNET_JSON_pack_string ("detail",
    2019             :                                "unexpected 'Authorization' header"));
    2020           0 :     wh->response_code = MHD_HTTP_UNAUTHORIZED;
    2021           0 :     wh->task = GNUNET_SCHEDULER_add_now (&async_webhook_reply,
    2022             :                                          wh);
    2023           0 :     return wh;
    2024             :   }
    2025             : 
    2026             :   wh->template_id
    2027           0 :     = json_string_value (
    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           0 :                     json_object_get (
    2035           0 :                       json_object_get (
    2036             :                         body,
    2037             :                         "data"),
    2038             :                       "attributes"),
    2039             :                     "payload"),
    2040             :                   "data"),
    2041             :                 "relationships"),
    2042             :               "inquiry-template"),
    2043             :             "data"),
    2044             :           "id"));
    2045           0 :   if (NULL == wh->template_id)
    2046             :   {
    2047           0 :     GNUNET_break_op (0);
    2048           0 :     json_dumpf (body,
    2049             :                 stderr,
    2050             :                 JSON_INDENT (2));
    2051           0 :     wh->resp = TALER_MHD_MAKE_JSON_PACK (
    2052             :       TALER_JSON_pack_ec (
    2053             :         TALER_EC_EXCHANGE_KYC_GENERIC_PROVIDER_UNEXPECTED_REPLY),
    2054             :       GNUNET_JSON_pack_string ("detail",
    2055             :                                "data-attributes-payload-data-id"),
    2056             :       GNUNET_JSON_pack_object_incref ("webhook_body",
    2057             :                                       (json_t *) body));
    2058           0 :     wh->response_code = MHD_HTTP_BAD_REQUEST;
    2059           0 :     wh->task = GNUNET_SCHEDULER_add_now (&async_webhook_reply,
    2060             :                                          wh);
    2061           0 :     return wh;
    2062             :   }
    2063           0 :   TALER_KYCLOGIC_kyc_get_details ("persona",
    2064             :                                   &locate_details_cb,
    2065             :                                   wh);
    2066           0 :   if (NULL == wh->pd)
    2067             :   {
    2068           0 :     GNUNET_break_op (0);
    2069           0 :     json_dumpf (body,
    2070             :                 stderr,
    2071             :                 JSON_INDENT (2));
    2072           0 :     wh->resp = TALER_MHD_MAKE_JSON_PACK (
    2073             :       TALER_JSON_pack_ec (
    2074             :         TALER_EC_EXCHANGE_KYC_GENERIC_LOGIC_UNKNOWN),
    2075             :       GNUNET_JSON_pack_string ("detail",
    2076             :                                wh->template_id),
    2077             :       GNUNET_JSON_pack_object_incref ("webhook_body",
    2078             :                                       (json_t *) body));
    2079           0 :     wh->response_code = MHD_HTTP_BAD_REQUEST;
    2080           0 :     wh->task = GNUNET_SCHEDULER_add_now (&async_webhook_reply,
    2081             :                                          wh);
    2082           0 :     return wh;
    2083             :   }
    2084             : 
    2085             :   persona_inquiry_id
    2086           0 :     = json_string_value (
    2087           0 :         json_object_get (
    2088           0 :           json_object_get (
    2089           0 :             json_object_get (
    2090           0 :               json_object_get (
    2091           0 :                 json_object_get (
    2092             :                   body,
    2093             :                   "data"),
    2094             :                 "attributes"),
    2095             :               "payload"),
    2096             :             "data"),
    2097             :           "id"));
    2098           0 :   if (NULL == persona_inquiry_id)
    2099             :   {
    2100           0 :     GNUNET_break_op (0);
    2101           0 :     json_dumpf (body,
    2102             :                 stderr,
    2103             :                 JSON_INDENT (2));
    2104           0 :     wh->resp = TALER_MHD_MAKE_JSON_PACK (
    2105             :       TALER_JSON_pack_ec (
    2106             :         TALER_EC_EXCHANGE_KYC_GENERIC_PROVIDER_UNEXPECTED_REPLY),
    2107             :       GNUNET_JSON_pack_string ("detail",
    2108             :                                "data-attributes-payload-data-id"),
    2109             :       GNUNET_JSON_pack_object_incref ("webhook_body",
    2110             :                                       (json_t *) body));
    2111           0 :     wh->response_code = MHD_HTTP_BAD_REQUEST;
    2112           0 :     wh->task = GNUNET_SCHEDULER_add_now (&async_webhook_reply,
    2113             :                                          wh);
    2114           0 :     return wh;
    2115             :   }
    2116           0 :   qs = plc (plc_cls,
    2117           0 :             wh->pd->section,
    2118             :             persona_inquiry_id,
    2119             :             &wh->h_payto,
    2120             :             &wh->is_wallet,
    2121             :             &wh->process_row);
    2122           0 :   if (qs < 0)
    2123             :   {
    2124           0 :     wh->resp = TALER_MHD_make_error (TALER_EC_GENERIC_DB_FETCH_FAILED,
    2125             :                                      "provider-legitimization-lookup");
    2126           0 :     wh->response_code = MHD_HTTP_INTERNAL_SERVER_ERROR;
    2127           0 :     wh->task = GNUNET_SCHEDULER_add_now (&async_webhook_reply,
    2128             :                                          wh);
    2129           0 :     return wh;
    2130             :   }
    2131           0 :   if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
    2132             :   {
    2133           0 :     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    2134             :                 "Received Persona kyc-webhook for unknown verification ID `%s'\n",
    2135             :                 persona_inquiry_id);
    2136           0 :     wh->resp = TALER_MHD_make_error (
    2137             :       TALER_EC_EXCHANGE_KYC_PROOF_REQUEST_UNKNOWN,
    2138             :       persona_inquiry_id);
    2139           0 :     wh->response_code = MHD_HTTP_NOT_FOUND;
    2140           0 :     wh->task = GNUNET_SCHEDULER_add_now (&async_webhook_reply,
    2141             :                                          wh);
    2142           0 :     return wh;
    2143             :   }
    2144           0 :   wh->inquiry_id = GNUNET_strdup (persona_inquiry_id);
    2145             : 
    2146           0 :   eh = curl_easy_init ();
    2147           0 :   if (NULL == eh)
    2148             :   {
    2149           0 :     GNUNET_break (0);
    2150           0 :     wh->resp = TALER_MHD_make_error (
    2151             :       TALER_EC_GENERIC_ALLOCATION_FAILURE,
    2152             :       NULL);
    2153           0 :     wh->response_code = MHD_HTTP_INTERNAL_SERVER_ERROR;
    2154           0 :     wh->task = GNUNET_SCHEDULER_add_now (&async_webhook_reply,
    2155             :                                          wh);
    2156           0 :     return wh;
    2157             :   }
    2158             : 
    2159           0 :   GNUNET_asprintf (&wh->url,
    2160             :                    "https://withpersona.com/api/v1/inquiries/%s",
    2161             :                    persona_inquiry_id);
    2162           0 :   GNUNET_break (CURLE_OK ==
    2163             :                 curl_easy_setopt (eh,
    2164             :                                   CURLOPT_VERBOSE,
    2165             :                                   0));
    2166           0 :   GNUNET_assert (CURLE_OK ==
    2167             :                  curl_easy_setopt (eh,
    2168             :                                    CURLOPT_MAXREDIRS,
    2169             :                                    1L));
    2170           0 :   GNUNET_break (CURLE_OK ==
    2171             :                 curl_easy_setopt (eh,
    2172             :                                   CURLOPT_URL,
    2173             :                                   wh->url));
    2174           0 :   wh->job = GNUNET_CURL_job_add2 (ps->curl_ctx,
    2175             :                                   eh,
    2176           0 :                                   wh->pd->slist,
    2177             :                                   &handle_webhook_finished,
    2178             :                                   wh);
    2179           0 :   return wh;
    2180             : }
    2181             : 
    2182             : 
    2183             : /**
    2184             :  * Initialize persona logic plugin
    2185             :  *
    2186             :  * @param cls a configuration instance
    2187             :  * @return NULL on error, otherwise a `struct TALER_KYCLOGIC_Plugin`
    2188             :  */
    2189             : void *
    2190             : libtaler_plugin_kyclogic_persona_init (void *cls);
    2191             : 
    2192             : /* declaration to avoid compiler warning */
    2193             : void *
    2194          76 : libtaler_plugin_kyclogic_persona_init (void *cls)
    2195             : {
    2196          76 :   const struct GNUNET_CONFIGURATION_Handle *cfg = cls;
    2197             :   struct TALER_KYCLOGIC_Plugin *plugin;
    2198             :   struct PluginState *ps;
    2199             : 
    2200          76 :   ps = GNUNET_new (struct PluginState);
    2201          76 :   ps->cfg = cfg;
    2202          76 :   if (GNUNET_OK !=
    2203          76 :       GNUNET_CONFIGURATION_get_value_string (cfg,
    2204             :                                              "exchange",
    2205             :                                              "BASE_URL",
    2206             :                                              &ps->exchange_base_url))
    2207             :   {
    2208           0 :     GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
    2209             :                                "exchange",
    2210             :                                "BASE_URL");
    2211           0 :     GNUNET_free (ps);
    2212           0 :     return NULL;
    2213             :   }
    2214          76 :   if (GNUNET_OK !=
    2215          76 :       GNUNET_CONFIGURATION_get_value_string (ps->cfg,
    2216             :                                              "kyclogic-persona",
    2217             :                                              "WEBHOOK_AUTH_TOKEN",
    2218             :                                              &ps->webhook_token))
    2219             :   {
    2220             :     /* optional */
    2221          76 :     ps->webhook_token = NULL;
    2222             :   }
    2223             : 
    2224             :   ps->curl_ctx
    2225         152 :     = GNUNET_CURL_init (&GNUNET_CURL_gnunet_scheduler_reschedule,
    2226          76 :                         &ps->curl_rc);
    2227          76 :   if (NULL == ps->curl_ctx)
    2228             :   {
    2229           0 :     GNUNET_break (0);
    2230           0 :     GNUNET_free (ps->exchange_base_url);
    2231           0 :     GNUNET_free (ps);
    2232           0 :     return NULL;
    2233             :   }
    2234          76 :   ps->curl_rc = GNUNET_CURL_gnunet_rc_create (ps->curl_ctx);
    2235             : 
    2236          76 :   plugin = GNUNET_new (struct TALER_KYCLOGIC_Plugin);
    2237          76 :   plugin->cls = ps;
    2238             :   plugin->load_configuration
    2239          76 :     = &persona_load_configuration;
    2240             :   plugin->unload_configuration
    2241          76 :     = &persona_unload_configuration;
    2242             :   plugin->initiate
    2243          76 :     = &persona_initiate;
    2244             :   plugin->initiate_cancel
    2245          76 :     = &persona_initiate_cancel;
    2246             :   plugin->proof
    2247          76 :     = &persona_proof;
    2248             :   plugin->proof_cancel
    2249          76 :     = &persona_proof_cancel;
    2250             :   plugin->webhook
    2251          76 :     = &persona_webhook;
    2252             :   plugin->webhook_cancel
    2253          76 :     = &persona_webhook_cancel;
    2254          76 :   return plugin;
    2255             : }
    2256             : 
    2257             : 
    2258             : /**
    2259             :  * Unload authorization plugin
    2260             :  *
    2261             :  * @param cls a `struct TALER_KYCLOGIC_Plugin`
    2262             :  * @return NULL (always)
    2263             :  */
    2264             : void *
    2265             : libtaler_plugin_kyclogic_persona_done (void *cls);
    2266             : 
    2267             : /* declaration to avoid compiler warning */
    2268             : 
    2269             : void *
    2270          76 : libtaler_plugin_kyclogic_persona_done (void *cls)
    2271             : {
    2272          76 :   struct TALER_KYCLOGIC_Plugin *plugin = cls;
    2273          76 :   struct PluginState *ps = plugin->cls;
    2274             : 
    2275          76 :   if (NULL != ps->curl_ctx)
    2276             :   {
    2277          76 :     GNUNET_CURL_fini (ps->curl_ctx);
    2278          76 :     ps->curl_ctx = NULL;
    2279             :   }
    2280          76 :   if (NULL != ps->curl_rc)
    2281             :   {
    2282          76 :     GNUNET_CURL_gnunet_rc_destroy (ps->curl_rc);
    2283          76 :     ps->curl_rc = NULL;
    2284             :   }
    2285          76 :   GNUNET_free (ps->exchange_base_url);
    2286          76 :   GNUNET_free (ps->webhook_token);
    2287          76 :   GNUNET_free (ps);
    2288          76 :   GNUNET_free (plugin);
    2289          76 :   return NULL;
    2290             : }
    2291             : 
    2292             : 
    2293             : /* end of plugin_kyclogic_persona.c */

Generated by: LCOV version 1.16