LCOV - code coverage report
Current view: top level - kyclogic - plugin_kyclogic_persona.c (source / functions) Hit Total Coverage
Test: GNU Taler exchange coverage report Lines: 50 635 7.9 %
Date: 2022-08-25 06:15:09 Functions: 2 19 10.5 %
Legend: Lines: hit not hit

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

Generated by: LCOV version 1.14