LCOV - code coverage report
Current view: top level - kyclogic - plugin_kyclogic_persona.c (source / functions) Coverage Total Hit
Test: coverage.info Lines: 11.7 % 683 80
Test Date: 2026-09-09 15:11:34 Functions: 17.4 % 23 4

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

Generated by: LCOV version 2.0-1