exchange

Base system with REST service to issue digital coins, run by the payment service provider
Log | Files | Refs | Submodules | README | LICENSE

plugin_kyclogic_oauth2.c (58168B)


      1 /*
      2   This file is part of GNU Taler
      3   Copyright (C) 2022-2024 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_oauth2.c
     18  * @brief oauth2.0 based authentication flow logic
     19  * @author Christian Grothoff
     20  */
     21 #include "taler/platform.h"
     22 #include "taler/taler_kyclogic_plugin.h"
     23 #include "taler/taler_mhd_lib.h"
     24 #include "taler/taler_templating_lib.h"
     25 #include "taler/taler_curl_lib.h"
     26 #include "taler/taler_json_lib.h"
     27 #include <regex.h>
     28 #include "taler/taler_util.h"
     29 
     30 /**
     31  * Set to 1 to get extra-verbose, possibly privacy-sensitive
     32  * data in the logs.
     33  */
     34 #define DEBUG 0
     35 
     36 /**
     37  * Saves the state of a plugin.
     38  */
     39 struct PluginState
     40 {
     41 
     42   /**
     43    * Our global configuration.
     44    */
     45   const struct GNUNET_CONFIGURATION_Handle *cfg;
     46 
     47   /**
     48    * Our base URL.
     49    */
     50   char *exchange_base_url;
     51 
     52   /**
     53    * Context for CURL operations (useful to the event loop)
     54    */
     55   struct GNUNET_CURL_Context *curl_ctx;
     56 
     57   /**
     58    * Context for integrating @e curl_ctx with the
     59    * GNUnet event loop.
     60    */
     61   struct GNUNET_CURL_RescheduleContext *curl_rc;
     62 
     63 };
     64 
     65 
     66 /**
     67  * Keeps the plugin-specific state for
     68  * a given configuration section.
     69  */
     70 struct TALER_KYCLOGIC_ProviderDetails
     71 {
     72 
     73   /**
     74    * Overall plugin state.
     75    */
     76   struct PluginState *ps;
     77 
     78   /**
     79    * Configuration section that configured us.
     80    */
     81   char *section;
     82 
     83   /**
     84    * URL of the Challenger ``/setup`` endpoint for
     85    * approving address validations. NULL if not used.
     86    */
     87   char *setup_url;
     88 
     89   /**
     90    * URL of the OAuth2.0 endpoint for KYC checks.
     91    */
     92   char *authorize_url;
     93 
     94   /**
     95    * URL of the OAuth2.0 endpoint for KYC checks.
     96    * (token/auth)
     97    */
     98   char *token_url;
     99 
    100   /**
    101    * URL of the user info access endpoint.
    102    */
    103   char *info_url;
    104 
    105   /**
    106    * Our client ID for OAuth2.0.
    107    */
    108   char *client_id;
    109 
    110   /**
    111    * Our client secret for OAuth2.0.
    112    */
    113   char *client_secret;
    114 
    115   /**
    116    * OAuth2 scope, NULL if not used
    117    */
    118   char *scope;
    119 
    120   /**
    121    * Where to redirect clients after the
    122    * Web-based KYC process is done?
    123    */
    124   char *post_kyc_redirect_url;
    125 
    126   /**
    127    * Name of the program we use to convert outputs
    128    * from OAuth2 outputs into our JSON inputs.
    129    */
    130   char *conversion_binary;
    131 
    132   /**
    133    * Validity time for a successful KYC process.
    134    */
    135   struct GNUNET_TIME_Relative validity;
    136 
    137   /**
    138    * Set to true if we are operating in DEBUG
    139    * mode and may return private details in HTML
    140    * responses to make diagnostics easier.
    141    */
    142   bool debug_mode;
    143 };
    144 
    145 
    146 /**
    147  * Handle for an initiation operation.
    148  */
    149 struct TALER_KYCLOGIC_InitiateHandle
    150 {
    151 
    152   /**
    153    * Hash of the payto:// URI we are initiating
    154    * the KYC for.
    155    */
    156   struct TALER_NormalizedPaytoHashP h_payto;
    157 
    158   /**
    159    * UUID being checked.
    160    */
    161   uint64_t legitimization_uuid;
    162 
    163   /**
    164    * Our configuration details.
    165    */
    166   const struct TALER_KYCLOGIC_ProviderDetails *pd;
    167 
    168   /**
    169    * The task for asynchronous response generation.
    170    */
    171   struct GNUNET_SCHEDULER_Task *task;
    172 
    173   /**
    174    * Handle for the OAuth 2.0 setup request.
    175    */
    176   struct GNUNET_CURL_Job *job;
    177 
    178   /**
    179    * Continuation to call.
    180    */
    181   TALER_KYCLOGIC_InitiateCallback cb;
    182 
    183   /**
    184    * Closure for @a cb.
    185    */
    186   void *cb_cls;
    187 
    188   /**
    189    * Initial address to pass to the KYC provider on ``/setup``.
    190    */
    191   json_t *initial_address;
    192 
    193   /**
    194    * Context for #TEH_curl_easy_post(). Keeps the data that must
    195    * persist for Curl to make the upload.
    196    */
    197   struct TALER_CURL_PostContext ctx;
    198 
    199 };
    200 
    201 
    202 /**
    203  * Handle for an KYC proof operation.
    204  */
    205 struct TALER_KYCLOGIC_ProofHandle
    206 {
    207 
    208   /**
    209    * Our configuration details.
    210    */
    211   const struct TALER_KYCLOGIC_ProviderDetails *pd;
    212 
    213   /**
    214    * HTTP connection we are processing.
    215    */
    216   struct MHD_Connection *connection;
    217 
    218   /**
    219    * Handle to an external process that converts the
    220    * Persona response to our internal format.
    221    */
    222   struct TALER_JSON_ExternalConversion *ec;
    223 
    224   /**
    225    * Hash of the payto URI that this is about.
    226    */
    227   struct TALER_NormalizedPaytoHashP h_payto;
    228 
    229   /**
    230    * Continuation to call.
    231    */
    232   TALER_KYCLOGIC_ProofCallback cb;
    233 
    234   /**
    235    * Closure for @e cb.
    236    */
    237   void *cb_cls;
    238 
    239   /**
    240    * Curl request we are running to the OAuth 2.0 service.
    241    */
    242   CURL *eh;
    243 
    244   /**
    245    * Body for the @e eh POST request.
    246    */
    247   char *post_body;
    248 
    249   /**
    250    * KYC attributes returned about the user by the OAuth 2.0 server.
    251    */
    252   json_t *attributes;
    253 
    254   /**
    255    * Response to return.
    256    */
    257   struct MHD_Response *response;
    258 
    259   /**
    260    * The task for asynchronous response generation.
    261    */
    262   struct GNUNET_SCHEDULER_Task *task;
    263 
    264   /**
    265    * Handle for the OAuth 2.0 CURL request.
    266    */
    267   struct GNUNET_CURL_Job *job;
    268 
    269   /**
    270    * User ID to return, the 'id' from OAuth.
    271    */
    272   char *provider_user_id;
    273 
    274   /**
    275    * Legitimization ID to return, the 64-bit row ID
    276    * as a string.
    277    */
    278   char provider_legitimization_id[32];
    279 
    280   /**
    281    * KYC status to return.
    282    */
    283   enum TALER_KYCLOGIC_KycStatus status;
    284 
    285   /**
    286    * HTTP status to return.
    287    */
    288   unsigned int http_status;
    289 
    290 
    291 };
    292 
    293 
    294 /**
    295  * Handle for an KYC Web hook operation.
    296  */
    297 struct TALER_KYCLOGIC_WebhookHandle
    298 {
    299 
    300   /**
    301    * Continuation to call when done.
    302    */
    303   TALER_KYCLOGIC_WebhookCallback cb;
    304 
    305   /**
    306    * Closure for @a cb.
    307    */
    308   void *cb_cls;
    309 
    310   /**
    311    * Task for asynchronous execution.
    312    */
    313   struct GNUNET_SCHEDULER_Task *task;
    314 
    315   /**
    316    * Overall plugin state.
    317    */
    318   struct PluginState *ps;
    319 };
    320 
    321 
    322 /**
    323  * Release configuration resources previously loaded
    324  *
    325  * @param[in] pd configuration to release
    326  */
    327 static void
    328 oauth2_unload_configuration (struct TALER_KYCLOGIC_ProviderDetails *pd)
    329 {
    330   GNUNET_free (pd->section);
    331   GNUNET_free (pd->token_url);
    332   GNUNET_free (pd->setup_url);
    333   GNUNET_free (pd->authorize_url);
    334   GNUNET_free (pd->info_url);
    335   GNUNET_free (pd->client_id);
    336   GNUNET_free (pd->client_secret);
    337   GNUNET_free (pd->scope);
    338   GNUNET_free (pd->post_kyc_redirect_url);
    339   GNUNET_free (pd->conversion_binary);
    340   GNUNET_free (pd);
    341 }
    342 
    343 
    344 /**
    345  * Load the configuration of the KYC provider.
    346  *
    347  * @param cls closure
    348  * @param provider_section_name configuration section to parse
    349  * @return NULL if configuration is invalid
    350  */
    351 static struct TALER_KYCLOGIC_ProviderDetails *
    352 oauth2_load_configuration (void *cls,
    353                            const char *provider_section_name)
    354 {
    355   struct PluginState *ps = cls;
    356   struct TALER_KYCLOGIC_ProviderDetails *pd;
    357   char *s;
    358 
    359   pd = GNUNET_new (struct TALER_KYCLOGIC_ProviderDetails);
    360   pd->ps = ps;
    361   pd->section = GNUNET_strdup (provider_section_name);
    362   if (GNUNET_OK !=
    363       GNUNET_CONFIGURATION_get_value_time (ps->cfg,
    364                                            provider_section_name,
    365                                            "KYC_OAUTH2_VALIDITY",
    366                                            &pd->validity))
    367   {
    368     GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
    369                                provider_section_name,
    370                                "KYC_OAUTH2_VALIDITY");
    371     oauth2_unload_configuration (pd);
    372     return NULL;
    373   }
    374 
    375   if (GNUNET_OK !=
    376       GNUNET_CONFIGURATION_get_value_string (ps->cfg,
    377                                              provider_section_name,
    378                                              "KYC_OAUTH2_CLIENT_ID",
    379                                              &s))
    380   {
    381     GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
    382                                provider_section_name,
    383                                "KYC_OAUTH2_CLIENT_ID");
    384     oauth2_unload_configuration (pd);
    385     return NULL;
    386   }
    387   pd->client_id = s;
    388 
    389   if (GNUNET_OK ==
    390       GNUNET_CONFIGURATION_get_value_string (ps->cfg,
    391                                              provider_section_name,
    392                                              "KYC_OAUTH2_SCOPE",
    393                                              &s))
    394   {
    395     pd->scope = s;
    396   }
    397 
    398   if (GNUNET_OK !=
    399       GNUNET_CONFIGURATION_get_value_string (ps->cfg,
    400                                              provider_section_name,
    401                                              "KYC_OAUTH2_TOKEN_URL",
    402                                              &s))
    403   {
    404     GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
    405                                provider_section_name,
    406                                "KYC_OAUTH2_TOKEN_URL");
    407     oauth2_unload_configuration (pd);
    408     return NULL;
    409   }
    410   if ( (! TALER_url_valid_charset (s)) ||
    411        ( (0 != strncasecmp (s,
    412                             "http://",
    413                             strlen ("http://"))) &&
    414          (0 != strncasecmp (s,
    415                             "https://",
    416                             strlen ("https://"))) ) )
    417   {
    418     GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
    419                                provider_section_name,
    420                                "KYC_OAUTH2_TOKEN_URL",
    421                                "not a valid URL");
    422     GNUNET_free (s);
    423     oauth2_unload_configuration (pd);
    424     return NULL;
    425   }
    426   pd->token_url = s;
    427 
    428   if (GNUNET_OK !=
    429       GNUNET_CONFIGURATION_get_value_string (ps->cfg,
    430                                              provider_section_name,
    431                                              "KYC_OAUTH2_AUTHORIZE_URL",
    432                                              &s))
    433   {
    434     GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
    435                                provider_section_name,
    436                                "KYC_OAUTH2_AUTHORIZE_URL");
    437     oauth2_unload_configuration (pd);
    438     return NULL;
    439   }
    440   if ( (! TALER_url_valid_charset (s)) ||
    441        ( (0 != strncasecmp (s,
    442                             "http://",
    443                             strlen ("http://"))) &&
    444          (0 != strncasecmp (s,
    445                             "https://",
    446                             strlen ("https://"))) ) )
    447   {
    448     GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
    449                                provider_section_name,
    450                                "KYC_OAUTH2_AUTHORIZE_URL",
    451                                "not a valid URL");
    452     oauth2_unload_configuration (pd);
    453     GNUNET_free (s);
    454     return NULL;
    455   }
    456   if (NULL != strchr (s, '#'))
    457   {
    458     const char *extra = strchr (s, '#');
    459     const char *slash = strrchr (s, '/');
    460 
    461     if ( (0 != strcasecmp (extra,
    462                            "#setup")) ||
    463          (NULL == slash) )
    464     {
    465       GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
    466                                  provider_section_name,
    467                                  "KYC_OAUTH2_AUTHORIZE_URL",
    468                                  "not a valid authorze URL (bad fragment)");
    469       oauth2_unload_configuration (pd);
    470       GNUNET_free (s);
    471       return NULL;
    472     }
    473     pd->authorize_url = GNUNET_strndup (s,
    474                                         extra - s);
    475     GNUNET_asprintf (&pd->setup_url,
    476                      "%.*s/setup/%s",
    477                      (int) (slash - s),
    478                      s,
    479                      pd->client_id);
    480     GNUNET_free (s);
    481   }
    482   else
    483   {
    484     pd->authorize_url = s;
    485   }
    486 
    487   if (GNUNET_OK !=
    488       GNUNET_CONFIGURATION_get_value_string (ps->cfg,
    489                                              provider_section_name,
    490                                              "KYC_OAUTH2_INFO_URL",
    491                                              &s))
    492   {
    493     GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
    494                                provider_section_name,
    495                                "KYC_OAUTH2_INFO_URL");
    496     oauth2_unload_configuration (pd);
    497     return NULL;
    498   }
    499   if ( (! TALER_url_valid_charset (s)) ||
    500        ( (0 != strncasecmp (s,
    501                             "http://",
    502                             strlen ("http://"))) &&
    503          (0 != strncasecmp (s,
    504                             "https://",
    505                             strlen ("https://"))) ) )
    506   {
    507     GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
    508                                provider_section_name,
    509                                "KYC_INFO_URL",
    510                                "not a valid URL");
    511     GNUNET_free (s);
    512     oauth2_unload_configuration (pd);
    513     return NULL;
    514   }
    515   pd->info_url = s;
    516 
    517   if (GNUNET_OK !=
    518       GNUNET_CONFIGURATION_get_value_string (ps->cfg,
    519                                              provider_section_name,
    520                                              "KYC_OAUTH2_CLIENT_SECRET",
    521                                              &s))
    522   {
    523     GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
    524                                provider_section_name,
    525                                "KYC_OAUTH2_CLIENT_SECRET");
    526     oauth2_unload_configuration (pd);
    527     return NULL;
    528   }
    529   pd->client_secret = s;
    530 
    531   if (GNUNET_OK !=
    532       GNUNET_CONFIGURATION_get_value_string (ps->cfg,
    533                                              provider_section_name,
    534                                              "KYC_OAUTH2_POST_URL",
    535                                              &s))
    536   {
    537     GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
    538                                provider_section_name,
    539                                "KYC_OAUTH2_POST_URL");
    540     oauth2_unload_configuration (pd);
    541     return NULL;
    542   }
    543   pd->post_kyc_redirect_url = s;
    544 
    545   if (GNUNET_OK !=
    546       GNUNET_CONFIGURATION_get_value_string (ps->cfg,
    547                                              provider_section_name,
    548                                              "KYC_OAUTH2_CONVERTER_HELPER",
    549                                              &pd->conversion_binary))
    550   {
    551     GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
    552                                provider_section_name,
    553                                "KYC_OAUTH2_CONVERTER_HELPER");
    554     oauth2_unload_configuration (pd);
    555     return NULL;
    556   }
    557   if (GNUNET_OK ==
    558       GNUNET_CONFIGURATION_get_value_yesno (ps->cfg,
    559                                             provider_section_name,
    560                                             "KYC_OAUTH2_DEBUG_MODE"))
    561     pd->debug_mode = true;
    562 
    563   return pd;
    564 }
    565 
    566 
    567 /**
    568  * Cancel KYC check initiation.
    569  *
    570  * @param[in] ih handle of operation to cancel
    571  */
    572 static void
    573 oauth2_initiate_cancel (struct TALER_KYCLOGIC_InitiateHandle *ih)
    574 {
    575   if (NULL != ih->task)
    576   {
    577     GNUNET_SCHEDULER_cancel (ih->task);
    578     ih->task = NULL;
    579   }
    580   if (NULL != ih->job)
    581   {
    582     GNUNET_CURL_job_cancel (ih->job);
    583     ih->job = NULL;
    584   }
    585   TALER_curl_easy_post_finished (&ih->ctx);
    586   json_decref (ih->initial_address);
    587   GNUNET_free (ih);
    588 }
    589 
    590 
    591 /**
    592  * Logic to asynchronously return the response for
    593  * how to begin the OAuth2.0 checking process to
    594  * the client.
    595  *
    596  * @param ih process to redirect for
    597  * @param authorize_url authorization URL to use
    598  */
    599 static void
    600 initiate_with_url (struct TALER_KYCLOGIC_InitiateHandle *ih,
    601                    const char *authorize_url)
    602 {
    603 
    604   const struct TALER_KYCLOGIC_ProviderDetails *pd = ih->pd;
    605   struct PluginState *ps = pd->ps;
    606   char *hps;
    607   char *url;
    608   char legi_s[42];
    609 
    610   GNUNET_snprintf (legi_s,
    611                    sizeof (legi_s),
    612                    "%llu",
    613                    (unsigned long long) ih->legitimization_uuid);
    614   hps = GNUNET_STRINGS_data_to_string_alloc (&ih->h_payto,
    615                                              sizeof (ih->h_payto));
    616   {
    617     char *redirect_uri_encoded;
    618 
    619     {
    620       char *redirect_uri;
    621 
    622       GNUNET_asprintf (&redirect_uri,
    623                        "%skyc-proof/%s",
    624                        ps->exchange_base_url,
    625                        &pd->section[strlen ("kyc-provider-")]);
    626       redirect_uri_encoded = TALER_urlencode (redirect_uri);
    627       GNUNET_free (redirect_uri);
    628     }
    629     GNUNET_asprintf (&url,
    630                      "%s?response_type=code&client_id=%s&redirect_uri=%s&state=%s&scope=%s",
    631                      authorize_url,
    632                      pd->client_id,
    633                      redirect_uri_encoded,
    634                      hps,
    635                      NULL != pd->scope
    636                      ? pd->scope
    637                      : "");
    638     GNUNET_free (redirect_uri_encoded);
    639   }
    640   ih->cb (ih->cb_cls,
    641           TALER_EC_NONE,
    642           url,
    643           NULL /* unknown user_id here */,
    644           legi_s,
    645           NULL /* no error */);
    646   GNUNET_free (url);
    647   GNUNET_free (hps);
    648   oauth2_initiate_cancel (ih);
    649 }
    650 
    651 
    652 /**
    653  * After we are done with the CURL interaction we
    654  * need to update our database state with the information
    655  * retrieved.
    656  *
    657  * @param cls a `struct TALER_KYCLOGIC_InitiateHandle *`
    658  * @param response_code HTTP response code from server, 0 on hard error
    659  * @param response in JSON, NULL if response was not in JSON format
    660  */
    661 static void
    662 handle_curl_setup_finished (void *cls,
    663                             long response_code,
    664                             const void *response)
    665 {
    666   struct TALER_KYCLOGIC_InitiateHandle *ih = cls;
    667   const struct TALER_KYCLOGIC_ProviderDetails *pd = ih->pd;
    668   const json_t *j = response;
    669 
    670   ih->job = NULL;
    671   switch (response_code)
    672   {
    673   case 0:
    674     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    675                 "/setup URL failed to return HTTP response\n");
    676     ih->cb (ih->cb_cls,
    677             TALER_EC_EXCHANGE_KYC_PROOF_BACKEND_INVALID_RESPONSE,
    678             NULL,
    679             NULL,
    680             NULL,
    681             "/setup request to OAuth 2.0 backend returned no response");
    682     oauth2_initiate_cancel (ih);
    683     return;
    684   case MHD_HTTP_OK:
    685     {
    686       const char *nonce;
    687       struct GNUNET_JSON_Specification spec[] = {
    688         GNUNET_JSON_spec_string ("nonce",
    689                                  &nonce),
    690         GNUNET_JSON_spec_end ()
    691       };
    692       enum GNUNET_GenericReturnValue res;
    693       const char *emsg;
    694       unsigned int line;
    695       char *url;
    696 
    697       res = GNUNET_JSON_parse (j,
    698                                spec,
    699                                &emsg,
    700                                &line);
    701       if (GNUNET_OK != res)
    702       {
    703         GNUNET_break_op (0);
    704         json_dumpf (j,
    705                     stderr,
    706                     JSON_INDENT (2));
    707         ih->cb (ih->cb_cls,
    708                 TALER_EC_EXCHANGE_KYC_PROOF_BACKEND_INVALID_RESPONSE,
    709                 NULL,
    710                 NULL,
    711                 NULL,
    712                 "Unexpected response from KYC gateway: setup must return a nonce");
    713         oauth2_initiate_cancel (ih);
    714         return;
    715       }
    716       GNUNET_asprintf (&url,
    717                        "%s/%s",
    718                        pd->authorize_url,
    719                        nonce);
    720       initiate_with_url (ih,
    721                          url);
    722       GNUNET_free (url);
    723       return;
    724     }
    725     break;
    726   default:
    727     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    728                 "/setup URL returned HTTP status %u\n",
    729                 (unsigned int) response_code);
    730     ih->cb (ih->cb_cls,
    731             TALER_EC_EXCHANGE_KYC_PROOF_BACKEND_INVALID_RESPONSE,
    732             NULL,
    733             NULL,
    734             NULL,
    735             "/setup request to OAuth 2.0 backend returned unexpected HTTP status code");
    736     oauth2_initiate_cancel (ih);
    737     return;
    738   }
    739 }
    740 
    741 
    742 /**
    743  * Logic to asynchronously return the response for how to begin the OAuth2.0
    744  * checking process to the client.  May first request a dynamic URL via
    745  * ``/setup`` if configured to use a client-authenticated setup process.
    746  *
    747  * @param cls a `struct TALER_KYCLOGIC_InitiateHandle *`
    748  */
    749 static void
    750 initiate_task (void *cls)
    751 {
    752   struct TALER_KYCLOGIC_InitiateHandle *ih = cls;
    753   const struct TALER_KYCLOGIC_ProviderDetails *pd = ih->pd;
    754   struct PluginState *ps = pd->ps;
    755   CURL *eh;
    756 
    757   ih->task = NULL;
    758   if (NULL == pd->setup_url)
    759   {
    760     initiate_with_url (ih,
    761                        pd->authorize_url);
    762     return;
    763   }
    764   eh = curl_easy_init ();
    765   if (NULL == eh)
    766   {
    767     GNUNET_break (0);
    768     ih->cb (ih->cb_cls,
    769             TALER_EC_GENERIC_ALLOCATION_FAILURE,
    770             NULL,
    771             NULL,
    772             NULL,
    773             "curl_easy_init() failed");
    774     oauth2_initiate_cancel (ih);
    775     return;
    776   }
    777   GNUNET_assert (CURLE_OK ==
    778                  curl_easy_setopt (eh,
    779                                    CURLOPT_URL,
    780                                    pd->setup_url));
    781 #if DEBUG
    782   GNUNET_assert (CURLE_OK ==
    783                  curl_easy_setopt (eh,
    784                                    CURLOPT_VERBOSE,
    785                                    1));
    786 #endif
    787   if (NULL == ih->initial_address)
    788   {
    789     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    790                 "Staring OAuth 2.0 without initial address\n");
    791     GNUNET_assert (CURLE_OK ==
    792                    curl_easy_setopt (eh,
    793                                      CURLOPT_POST,
    794                                      1));
    795     GNUNET_assert (CURLE_OK ==
    796                    curl_easy_setopt (eh,
    797                                      CURLOPT_POSTFIELDS,
    798                                      ""));
    799     GNUNET_assert (CURLE_OK ==
    800                    curl_easy_setopt (eh,
    801                                      CURLOPT_POSTFIELDSIZE,
    802                                      (long) 0));
    803   }
    804   else
    805   {
    806     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    807                 "Staring OAuth 2.0 with initial address\n");
    808 #if DEBUG
    809     json_dumpf (ih->initial_address,
    810                 stderr,
    811                 JSON_INDENT (2));
    812     fprintf (stderr,
    813              "\n");
    814 #endif
    815     if (GNUNET_OK !=
    816         TALER_curl_easy_post (&ih->ctx,
    817                               eh,
    818                               ih->initial_address))
    819     {
    820       curl_easy_cleanup (eh);
    821       ih->cb (ih->cb_cls,
    822               TALER_EC_GENERIC_ALLOCATION_FAILURE,
    823               NULL,
    824               NULL,
    825               NULL,
    826               "TALER_curl_easy_post() failed");
    827       oauth2_initiate_cancel (ih);
    828       return;
    829     }
    830   }
    831   GNUNET_assert (CURLE_OK ==
    832                  curl_easy_setopt (eh,
    833                                    CURLOPT_FOLLOWLOCATION,
    834                                    1L));
    835   GNUNET_assert (CURLE_OK ==
    836                  curl_easy_setopt (eh,
    837                                    CURLOPT_MAXREDIRS,
    838                                    5L));
    839   ih->job = GNUNET_CURL_job_add2 (ps->curl_ctx,
    840                                   eh,
    841                                   ih->ctx.headers,
    842                                   &handle_curl_setup_finished,
    843                                   ih);
    844   {
    845     char *hdr;
    846     struct curl_slist *slist;
    847 
    848     GNUNET_asprintf (&hdr,
    849                      "%s: Bearer %s",
    850                      MHD_HTTP_HEADER_AUTHORIZATION,
    851                      pd->client_secret);
    852     slist = curl_slist_append (NULL,
    853                                hdr);
    854     GNUNET_CURL_extend_headers (ih->job,
    855                                 slist);
    856     curl_slist_free_all (slist);
    857     GNUNET_free (hdr);
    858   }
    859 }
    860 
    861 
    862 /**
    863  * Initiate KYC check.
    864  *
    865  * @param cls the @e cls of this struct with the plugin-specific state
    866  * @param pd provider configuration details
    867  * @param account_id which account to trigger process for
    868  * @param legitimization_uuid unique ID for the legitimization process
    869  * @param context additional contextual information for the legi process
    870  * @param cb function to call with the result
    871  * @param cb_cls closure for @a cb
    872  * @return handle to cancel operation early
    873  */
    874 static struct TALER_KYCLOGIC_InitiateHandle *
    875 oauth2_initiate (void *cls,
    876                  const struct TALER_KYCLOGIC_ProviderDetails *pd,
    877                  const struct TALER_NormalizedPaytoHashP *account_id,
    878                  uint64_t legitimization_uuid,
    879                  const json_t *context,
    880                  TALER_KYCLOGIC_InitiateCallback cb,
    881                  void *cb_cls)
    882 {
    883   struct TALER_KYCLOGIC_InitiateHandle *ih;
    884 
    885   (void) cls;
    886   ih = GNUNET_new (struct TALER_KYCLOGIC_InitiateHandle);
    887   ih->legitimization_uuid = legitimization_uuid;
    888   ih->cb = cb;
    889   ih->cb_cls = cb_cls;
    890   ih->h_payto = *account_id;
    891   ih->pd = pd;
    892   ih->task = GNUNET_SCHEDULER_add_now (&initiate_task,
    893                                        ih);
    894   if (NULL != context)
    895   {
    896     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    897                 "Initiating OAuth2 validation with context\n");
    898 #if DEBUG
    899     json_dumpf (context,
    900                 stderr,
    901                 JSON_INDENT (2));
    902     fprintf (stderr,
    903              "\n");
    904 #endif
    905     ih->initial_address = json_incref (json_object_get (context,
    906                                                         "initial_address"));
    907   }
    908   return ih;
    909 }
    910 
    911 
    912 /**
    913  * Cancel KYC proof.
    914  *
    915  * @param[in] ph handle of operation to cancel
    916  */
    917 static void
    918 oauth2_proof_cancel (struct TALER_KYCLOGIC_ProofHandle *ph)
    919 {
    920   if (NULL != ph->ec)
    921   {
    922     TALER_JSON_external_conversion_stop (ph->ec);
    923     ph->ec = NULL;
    924   }
    925   if (NULL != ph->task)
    926   {
    927     GNUNET_SCHEDULER_cancel (ph->task);
    928     ph->task = NULL;
    929   }
    930   if (NULL != ph->job)
    931   {
    932     GNUNET_CURL_job_cancel (ph->job);
    933     ph->job = NULL;
    934   }
    935   if (NULL != ph->response)
    936   {
    937     MHD_destroy_response (ph->response);
    938     ph->response = NULL;
    939   }
    940   GNUNET_free (ph->provider_user_id);
    941   if (NULL != ph->attributes)
    942     json_decref (ph->attributes);
    943   GNUNET_free (ph->post_body);
    944   GNUNET_free (ph);
    945 }
    946 
    947 
    948 /**
    949  * Function called to asynchronously return the final
    950  * result to the callback.
    951  *
    952  * @param cls a `struct TALER_KYCLOGIC_ProofHandle`
    953  */
    954 static void
    955 return_proof_response (void *cls)
    956 {
    957   struct TALER_KYCLOGIC_ProofHandle *ph = cls;
    958   const char *provider_name;
    959 
    960   ph->task = NULL;
    961   provider_name = ph->pd->section;
    962   if (0 !=
    963       strncasecmp (provider_name,
    964                    "KYC-PROVIDER-",
    965                    strlen ("KYC-PROVIDER-")))
    966   {
    967     GNUNET_break (0);
    968   }
    969   else
    970   {
    971     provider_name += strlen ("KYC-PROVIDER-");
    972   }
    973   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    974               "Returning KYC proof from `%s'\n",
    975               provider_name);
    976   ph->cb (ph->cb_cls,
    977           ph->status,
    978           provider_name,
    979           ph->provider_user_id,
    980           ph->provider_legitimization_id,
    981           GNUNET_TIME_relative_to_absolute (ph->pd->validity),
    982           ph->attributes,
    983           ph->http_status,
    984           ph->response);
    985   ph->response = NULL; /*Ownership passed to 'ph->cb'!*/
    986   oauth2_proof_cancel (ph);
    987 }
    988 
    989 
    990 /**
    991  * The request for @a ph failed. We may have gotten a useful error
    992  * message in @a j. Generate a failure response.
    993  *
    994  * @param[in,out] ph request that failed
    995  * @param j reply from the server (or NULL)
    996  */
    997 static void
    998 handle_proof_error (struct TALER_KYCLOGIC_ProofHandle *ph,
    999                     const json_t *j)
   1000 {
   1001   enum GNUNET_GenericReturnValue res;
   1002 
   1003   {
   1004     const char *msg;
   1005     const char *desc;
   1006     struct GNUNET_JSON_Specification spec[] = {
   1007       GNUNET_JSON_spec_string ("error",
   1008                                &msg),
   1009       GNUNET_JSON_spec_string ("error_description",
   1010                                &desc),
   1011       GNUNET_JSON_spec_end ()
   1012     };
   1013     const char *emsg;
   1014     unsigned int line;
   1015 
   1016     res = GNUNET_JSON_parse (j,
   1017                              spec,
   1018                              &emsg,
   1019                              &line);
   1020   }
   1021 
   1022   if (GNUNET_OK != res)
   1023   {
   1024     json_t *body;
   1025 
   1026     GNUNET_break_op (0);
   1027     ph->status = TALER_KYCLOGIC_STATUS_PROVIDER_FAILED;
   1028     ph->http_status
   1029       = MHD_HTTP_BAD_GATEWAY;
   1030     body = GNUNET_JSON_PACK (
   1031       GNUNET_JSON_pack_allow_null (
   1032         GNUNET_JSON_pack_object_incref ("server_response",
   1033                                         (json_t *) j)),
   1034       GNUNET_JSON_pack_bool ("debug",
   1035                              ph->pd->debug_mode),
   1036       TALER_JSON_pack_ec (
   1037         TALER_EC_EXCHANGE_KYC_PROOF_BACKEND_INVALID_RESPONSE));
   1038     GNUNET_assert (NULL != body);
   1039     GNUNET_break (
   1040       GNUNET_SYSERR !=
   1041       TALER_TEMPLATING_build (ph->connection,
   1042                               &ph->http_status,
   1043                               "oauth2-authorization-failure-malformed",
   1044                               NULL,
   1045                               NULL,
   1046                               body,
   1047                               &ph->response));
   1048     json_decref (body);
   1049     return;
   1050   }
   1051   ph->status = TALER_KYCLOGIC_STATUS_USER_ABORTED;
   1052   ph->http_status = MHD_HTTP_FORBIDDEN;
   1053   GNUNET_break (
   1054     GNUNET_SYSERR !=
   1055     TALER_TEMPLATING_build (ph->connection,
   1056                             &ph->http_status,
   1057                             "oauth2-authorization-failure",
   1058                             NULL,
   1059                             NULL,
   1060                             j,
   1061                             &ph->response));
   1062 }
   1063 
   1064 
   1065 /**
   1066  * Type of a callback that receives a JSON @a result.
   1067  *
   1068  * @param cls closure with a `struct TALER_KYCLOGIC_ProofHandle *`
   1069  * @param status_type how did the process die
   1070  * @param code termination status code from the process
   1071  * @param attr result some JSON result, NULL if we failed to get an JSON output
   1072  */
   1073 static void
   1074 converted_proof_cb (void *cls,
   1075                     enum GNUNET_OS_ProcessStatusType status_type,
   1076                     unsigned long code,
   1077                     const json_t *attr)
   1078 {
   1079   struct TALER_KYCLOGIC_ProofHandle *ph = cls;
   1080   const struct TALER_KYCLOGIC_ProviderDetails *pd = ph->pd;
   1081 
   1082   ph->ec = NULL;
   1083   if ( (NULL == attr) ||
   1084        (0 != code) )
   1085   {
   1086     json_t *body;
   1087     char *msg;
   1088 
   1089     GNUNET_break_op (0);
   1090     ph->status = TALER_KYCLOGIC_STATUS_PROVIDER_FAILED;
   1091     ph->http_status = MHD_HTTP_BAD_GATEWAY;
   1092     if (0 != code)
   1093       GNUNET_asprintf (&msg,
   1094                        "Attribute converter exited with status %ld",
   1095                        code);
   1096     else
   1097       msg = GNUNET_strdup (
   1098         "Attribute converter response was not in JSON format");
   1099     body = GNUNET_JSON_PACK (
   1100       GNUNET_JSON_pack_string ("converter",
   1101                                pd->conversion_binary),
   1102       GNUNET_JSON_pack_allow_null (
   1103         GNUNET_JSON_pack_object_incref ("attributes",
   1104                                         (json_t *) attr)),
   1105       GNUNET_JSON_pack_bool ("debug",
   1106                              ph->pd->debug_mode),
   1107       GNUNET_JSON_pack_string ("message",
   1108                                msg),
   1109       TALER_JSON_pack_ec (
   1110         TALER_EC_EXCHANGE_KYC_PROOF_BACKEND_INVALID_RESPONSE));
   1111     GNUNET_free (msg);
   1112     GNUNET_break (
   1113       GNUNET_SYSERR !=
   1114       TALER_TEMPLATING_build (ph->connection,
   1115                               &ph->http_status,
   1116                               "oauth2-conversion-failure",
   1117                               NULL,
   1118                               NULL,
   1119                               body,
   1120                               &ph->response));
   1121     json_decref (body);
   1122     ph->task = GNUNET_SCHEDULER_add_now (&return_proof_response,
   1123                                          ph);
   1124     return;
   1125   }
   1126   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
   1127               "Attribute conversion output is:\n");
   1128 #if DEBUG
   1129   json_dumpf (attr,
   1130               stderr,
   1131               JSON_INDENT (2));
   1132   fprintf (stderr,
   1133            "\n");
   1134 #endif
   1135   {
   1136     const char *id;
   1137     struct GNUNET_JSON_Specification ispec[] = {
   1138       GNUNET_JSON_spec_string ("id",
   1139                                &id),
   1140       GNUNET_JSON_spec_end ()
   1141     };
   1142     enum GNUNET_GenericReturnValue res;
   1143     const char *emsg;
   1144     unsigned int line;
   1145 
   1146     res = GNUNET_JSON_parse (attr,
   1147                              ispec,
   1148                              &emsg,
   1149                              &line);
   1150     if (GNUNET_OK != res)
   1151     {
   1152       json_t *body;
   1153 
   1154       GNUNET_break_op (0);
   1155       ph->status = TALER_KYCLOGIC_STATUS_PROVIDER_FAILED;
   1156       ph->http_status = MHD_HTTP_BAD_GATEWAY;
   1157       body = GNUNET_JSON_PACK (
   1158         GNUNET_JSON_pack_string ("converter",
   1159                                  pd->conversion_binary),
   1160         GNUNET_JSON_pack_string ("message",
   1161                                  "Unexpected response from KYC attribute converter: returned JSON data must contain 'id' field"),
   1162         GNUNET_JSON_pack_bool ("debug",
   1163                                ph->pd->debug_mode),
   1164         GNUNET_JSON_pack_object_incref ("attributes",
   1165                                         (json_t *) attr),
   1166         TALER_JSON_pack_ec (
   1167           TALER_EC_EXCHANGE_KYC_PROOF_BACKEND_INVALID_RESPONSE));
   1168       GNUNET_break (
   1169         GNUNET_SYSERR !=
   1170         TALER_TEMPLATING_build (ph->connection,
   1171                                 &ph->http_status,
   1172                                 "oauth2-conversion-failure",
   1173                                 NULL,
   1174                                 NULL,
   1175                                 body,
   1176                                 &ph->response));
   1177       json_decref (body);
   1178       ph->task = GNUNET_SCHEDULER_add_now (&return_proof_response,
   1179                                            ph);
   1180       return;
   1181     }
   1182     ph->provider_user_id = GNUNET_strdup (id);
   1183   }
   1184   if (! json_is_string (json_object_get (attr,
   1185                                          "FORM_ID")))
   1186   {
   1187     json_t *body;
   1188 
   1189     GNUNET_break_op (0);
   1190     ph->status = TALER_KYCLOGIC_STATUS_PROVIDER_FAILED;
   1191     ph->http_status = MHD_HTTP_BAD_GATEWAY;
   1192     body = GNUNET_JSON_PACK (
   1193       GNUNET_JSON_pack_string ("converter",
   1194                                pd->conversion_binary),
   1195       GNUNET_JSON_pack_string ("message",
   1196                                "Missing 'FORM_ID' field in attributes"),
   1197       GNUNET_JSON_pack_bool ("debug",
   1198                              ph->pd->debug_mode),
   1199       GNUNET_JSON_pack_object_incref ("attributes",
   1200                                       (json_t *) attr),
   1201       TALER_JSON_pack_ec (
   1202         TALER_EC_EXCHANGE_KYC_PROOF_BACKEND_INVALID_RESPONSE));
   1203     GNUNET_break (
   1204       GNUNET_SYSERR !=
   1205       TALER_TEMPLATING_build (ph->connection,
   1206                               &ph->http_status,
   1207                               "oauth2-conversion-failure",
   1208                               NULL,
   1209                               NULL,
   1210                               body,
   1211                               &ph->response));
   1212     json_decref (body);
   1213     ph->task = GNUNET_SCHEDULER_add_now (&return_proof_response,
   1214                                          ph);
   1215     return;
   1216   }
   1217   ph->status = TALER_KYCLOGIC_STATUS_SUCCESS;
   1218   ph->response = MHD_create_response_from_buffer_static (0,
   1219                                                          "");
   1220   GNUNET_assert (NULL != ph->response);
   1221   GNUNET_break (MHD_YES ==
   1222                 MHD_add_response_header (
   1223                   ph->response,
   1224                   MHD_HTTP_HEADER_LOCATION,
   1225                   ph->pd->post_kyc_redirect_url));
   1226   ph->http_status = MHD_HTTP_SEE_OTHER;
   1227   ph->attributes = json_incref ((json_t *) attr);
   1228   ph->task = GNUNET_SCHEDULER_add_now (&return_proof_response,
   1229                                        ph);
   1230 }
   1231 
   1232 
   1233 /**
   1234  * The request for @a ph succeeded (presumably).
   1235  * Call continuation with the result.
   1236  *
   1237  * @param[in,out] ph request that succeeded
   1238  * @param j reply from the server
   1239  */
   1240 static void
   1241 parse_proof_success_reply (struct TALER_KYCLOGIC_ProofHandle *ph,
   1242                            const json_t *j)
   1243 {
   1244   const struct TALER_KYCLOGIC_ProviderDetails *pd = ph->pd;
   1245   const char *argv[] = {
   1246     pd->conversion_binary,
   1247     NULL,
   1248   };
   1249 
   1250   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
   1251               "Calling converter `%s' with JSON\n",
   1252               pd->conversion_binary);
   1253 #if DEBUG
   1254   json_dumpf (j,
   1255               stderr,
   1256               JSON_INDENT (2));
   1257 #endif
   1258   ph->ec = TALER_JSON_external_conversion_start (
   1259     j,
   1260     &converted_proof_cb,
   1261     ph,
   1262     pd->conversion_binary,
   1263     argv);
   1264   if (NULL != ph->ec)
   1265     return;
   1266   GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
   1267               "Failed to start OAUTH2 conversion helper `%s'\n",
   1268               pd->conversion_binary);
   1269   ph->status = TALER_KYCLOGIC_STATUS_INTERNAL_ERROR;
   1270   ph->http_status = MHD_HTTP_INTERNAL_SERVER_ERROR;
   1271   {
   1272     json_t *body;
   1273 
   1274     body = GNUNET_JSON_PACK (
   1275       GNUNET_JSON_pack_string ("converter",
   1276                                pd->conversion_binary),
   1277       GNUNET_JSON_pack_bool ("debug",
   1278                              ph->pd->debug_mode),
   1279       GNUNET_JSON_pack_string ("message",
   1280                                "Failed to launch KYC conversion helper process."),
   1281       TALER_JSON_pack_ec (
   1282         TALER_EC_EXCHANGE_GENERIC_KYC_CONVERTER_FAILED));
   1283     GNUNET_break (
   1284       GNUNET_SYSERR !=
   1285       TALER_TEMPLATING_build (ph->connection,
   1286                               &ph->http_status,
   1287                               "oauth2-conversion-failure",
   1288                               NULL,
   1289                               NULL,
   1290                               body,
   1291                               &ph->response));
   1292     json_decref (body);
   1293   }
   1294   ph->task = GNUNET_SCHEDULER_add_now (&return_proof_response,
   1295                                        ph);
   1296 }
   1297 
   1298 
   1299 /**
   1300  * After we are done with the CURL interaction we
   1301  * need to update our database state with the information
   1302  * retrieved.
   1303  *
   1304  * @param cls our `struct TALER_KYCLOGIC_ProofHandle`
   1305  * @param response_code HTTP response code from server, 0 on hard error
   1306  * @param response in JSON, NULL if response was not in JSON format
   1307  */
   1308 static void
   1309 handle_curl_proof_finished (void *cls,
   1310                             long response_code,
   1311                             const void *response)
   1312 {
   1313   struct TALER_KYCLOGIC_ProofHandle *ph = cls;
   1314   const json_t *j = response;
   1315 
   1316   ph->job = NULL;
   1317   switch (response_code)
   1318   {
   1319   case 0:
   1320     {
   1321       json_t *body;
   1322 
   1323       ph->status = TALER_KYCLOGIC_STATUS_PROVIDER_FAILED;
   1324       ph->http_status = MHD_HTTP_BAD_GATEWAY;
   1325 
   1326       body = GNUNET_JSON_PACK (
   1327         GNUNET_JSON_pack_string ("message",
   1328                                  "No response from KYC gateway"),
   1329         TALER_JSON_pack_ec (
   1330           TALER_EC_EXCHANGE_KYC_PROOF_BACKEND_INVALID_RESPONSE));
   1331       GNUNET_break (
   1332         GNUNET_SYSERR !=
   1333         TALER_TEMPLATING_build (ph->connection,
   1334                                 &ph->http_status,
   1335                                 "oauth2-provider-failure",
   1336                                 NULL,
   1337                                 NULL,
   1338                                 body,
   1339                                 &ph->response));
   1340       json_decref (body);
   1341     }
   1342     break;
   1343   case MHD_HTTP_OK:
   1344     parse_proof_success_reply (ph,
   1345                                j);
   1346     return;
   1347   default:
   1348     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
   1349                 "OAuth2.0 info URL returned HTTP status %u\n",
   1350                 (unsigned int) response_code);
   1351     handle_proof_error (ph,
   1352                         j);
   1353     break;
   1354   }
   1355   ph->task = GNUNET_SCHEDULER_add_now (&return_proof_response,
   1356                                        ph);
   1357 }
   1358 
   1359 
   1360 /**
   1361  * After we are done with the CURL interaction we
   1362  * need to fetch the user's account details.
   1363  *
   1364  * @param cls our `struct KycProofContext`
   1365  * @param response_code HTTP response code from server, 0 on hard error
   1366  * @param response in JSON, NULL if response was not in JSON format
   1367  */
   1368 static void
   1369 handle_curl_login_finished (void *cls,
   1370                             long response_code,
   1371                             const void *response)
   1372 {
   1373   struct TALER_KYCLOGIC_ProofHandle *ph = cls;
   1374   const json_t *j = response;
   1375 
   1376   ph->job = NULL;
   1377   switch (response_code)
   1378   {
   1379   case MHD_HTTP_OK:
   1380     {
   1381       const char *access_token;
   1382       const char *token_type;
   1383       uint64_t expires_in_s;
   1384       const char *refresh_token;
   1385       bool no_expires;
   1386       bool no_refresh;
   1387       struct GNUNET_JSON_Specification spec[] = {
   1388         GNUNET_JSON_spec_string ("access_token",
   1389                                  &access_token),
   1390         GNUNET_JSON_spec_string ("token_type",
   1391                                  &token_type),
   1392         GNUNET_JSON_spec_mark_optional (
   1393           GNUNET_JSON_spec_uint64 ("expires_in",
   1394                                    &expires_in_s),
   1395           &no_expires),
   1396         GNUNET_JSON_spec_mark_optional (
   1397           GNUNET_JSON_spec_string ("refresh_token",
   1398                                    &refresh_token),
   1399           &no_refresh),
   1400         GNUNET_JSON_spec_end ()
   1401       };
   1402       CURL *eh;
   1403 
   1404       {
   1405         enum GNUNET_GenericReturnValue res;
   1406         const char *emsg;
   1407         unsigned int line;
   1408 
   1409         res = GNUNET_JSON_parse (j,
   1410                                  spec,
   1411                                  &emsg,
   1412                                  &line);
   1413         if (GNUNET_OK != res)
   1414         {
   1415           json_t *body;
   1416 
   1417           GNUNET_break_op (0);
   1418           ph->http_status
   1419             = MHD_HTTP_BAD_GATEWAY;
   1420           body = GNUNET_JSON_PACK (
   1421             GNUNET_JSON_pack_object_incref ("server_response",
   1422                                             (json_t *) j),
   1423             GNUNET_JSON_pack_bool ("debug",
   1424                                    ph->pd->debug_mode),
   1425             GNUNET_JSON_pack_string ("message",
   1426                                      "Unexpected response from KYC gateway: required fields missing or malformed"),
   1427             TALER_JSON_pack_ec (
   1428               TALER_EC_EXCHANGE_KYC_PROOF_BACKEND_INVALID_RESPONSE));
   1429           GNUNET_break (
   1430             GNUNET_SYSERR !=
   1431             TALER_TEMPLATING_build (ph->connection,
   1432                                     &ph->http_status,
   1433                                     "oauth2-provider-failure",
   1434                                     NULL,
   1435                                     NULL,
   1436                                     body,
   1437                                     &ph->response));
   1438           json_decref (body);
   1439           break;
   1440         }
   1441       }
   1442       if (0 != strcasecmp (token_type,
   1443                            "bearer"))
   1444       {
   1445         json_t *body;
   1446 
   1447         GNUNET_break_op (0);
   1448         ph->http_status = MHD_HTTP_BAD_GATEWAY;
   1449         body = GNUNET_JSON_PACK (
   1450           GNUNET_JSON_pack_object_incref ("server_response",
   1451                                           (json_t *) j),
   1452           GNUNET_JSON_pack_bool ("debug",
   1453                                  ph->pd->debug_mode),
   1454           GNUNET_JSON_pack_string ("message",
   1455                                    "Unexpected 'token_type' in response from KYC gateway: 'bearer' token required"),
   1456           TALER_JSON_pack_ec (
   1457             TALER_EC_EXCHANGE_KYC_PROOF_BACKEND_INVALID_RESPONSE));
   1458         GNUNET_break (
   1459           GNUNET_SYSERR !=
   1460           TALER_TEMPLATING_build (ph->connection,
   1461                                   &ph->http_status,
   1462                                   "oauth2-provider-failure",
   1463                                   NULL,
   1464                                   NULL,
   1465                                   body,
   1466                                   &ph->response));
   1467         json_decref (body);
   1468         break;
   1469       }
   1470 
   1471       /* We guard against a few characters that could
   1472          conceivably be abused to mess with the HTTP header */
   1473       if ( (NULL != strchr (access_token,
   1474                             '\n')) ||
   1475            (NULL != strchr (access_token,
   1476                             '\r')) ||
   1477            (NULL != strchr (access_token,
   1478                             ' ')) ||
   1479            (NULL != strchr (access_token,
   1480                             ';')) )
   1481       {
   1482         json_t *body;
   1483 
   1484         GNUNET_break_op (0);
   1485         ph->http_status = MHD_HTTP_BAD_GATEWAY;
   1486         body = GNUNET_JSON_PACK (
   1487           GNUNET_JSON_pack_object_incref ("server_response",
   1488                                           (json_t *) j),
   1489           GNUNET_JSON_pack_bool ("debug",
   1490                                  ph->pd->debug_mode),
   1491           GNUNET_JSON_pack_string ("message",
   1492                                    "Illegal character in access token"),
   1493           TALER_JSON_pack_ec (
   1494             TALER_EC_EXCHANGE_KYC_PROOF_BACKEND_INVALID_RESPONSE));
   1495         GNUNET_break (
   1496           GNUNET_SYSERR !=
   1497           TALER_TEMPLATING_build (ph->connection,
   1498                                   &ph->http_status,
   1499                                   "oauth2-provider-failure",
   1500                                   NULL,
   1501                                   NULL,
   1502                                   body,
   1503                                   &ph->response));
   1504         json_decref (body);
   1505         break;
   1506       }
   1507 
   1508       eh = curl_easy_init ();
   1509       GNUNET_assert (NULL != eh);
   1510       GNUNET_assert (CURLE_OK ==
   1511                      curl_easy_setopt (eh,
   1512                                        CURLOPT_URL,
   1513                                        ph->pd->info_url));
   1514       {
   1515         char *hdr;
   1516         struct curl_slist *slist;
   1517 
   1518         GNUNET_asprintf (&hdr,
   1519                          "%s: Bearer %s",
   1520                          MHD_HTTP_HEADER_AUTHORIZATION,
   1521                          access_token);
   1522         slist = curl_slist_append (NULL,
   1523                                    hdr);
   1524         ph->job = GNUNET_CURL_job_add2 (ph->pd->ps->curl_ctx,
   1525                                         eh,
   1526                                         slist,
   1527                                         &handle_curl_proof_finished,
   1528                                         ph);
   1529         curl_slist_free_all (slist);
   1530         GNUNET_free (hdr);
   1531       }
   1532       return;
   1533     }
   1534   default:
   1535     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
   1536                 "OAuth2.0 login URL returned HTTP status %u\n",
   1537                 (unsigned int) response_code);
   1538     handle_proof_error (ph,
   1539                         j);
   1540     break;
   1541   }
   1542   return_proof_response (ph);
   1543 }
   1544 
   1545 
   1546 /**
   1547  * Check KYC status and return status to human.
   1548  *
   1549  * @param cls the @e cls of this struct with the plugin-specific state
   1550  * @param pd provider configuration details
   1551  * @param connection MHD connection object (for HTTP headers)
   1552  * @param account_id which account to trigger process for
   1553  * @param process_row row in the legitimization processes table the legitimization is for
   1554  * @param provider_user_id user ID (or NULL) the proof is for
   1555  * @param provider_legitimization_id legitimization ID the proof is for
   1556  * @param cb function to call with the result
   1557  * @param cb_cls closure for @a cb
   1558  * @return handle to cancel operation early
   1559  */
   1560 static struct TALER_KYCLOGIC_ProofHandle *
   1561 oauth2_proof (void *cls,
   1562               const struct TALER_KYCLOGIC_ProviderDetails *pd,
   1563               struct MHD_Connection *connection,
   1564               const struct TALER_NormalizedPaytoHashP *account_id,
   1565               uint64_t process_row,
   1566               const char *provider_user_id,
   1567               const char *provider_legitimization_id,
   1568               TALER_KYCLOGIC_ProofCallback cb,
   1569               void *cb_cls)
   1570 {
   1571   struct PluginState *ps = cls;
   1572   struct TALER_KYCLOGIC_ProofHandle *ph;
   1573   const char *code;
   1574 
   1575   GNUNET_break (NULL == provider_user_id);
   1576   ph = GNUNET_new (struct TALER_KYCLOGIC_ProofHandle);
   1577   GNUNET_snprintf (ph->provider_legitimization_id,
   1578                    sizeof (ph->provider_legitimization_id),
   1579                    "%llu",
   1580                    (unsigned long long) process_row);
   1581   if ( (NULL != provider_legitimization_id) &&
   1582        (0 != strcmp (provider_legitimization_id,
   1583                      ph->provider_legitimization_id)))
   1584   {
   1585     GNUNET_break (0);
   1586     GNUNET_free (ph);
   1587     return NULL;
   1588   }
   1589 
   1590   ph->pd = pd;
   1591   ph->connection = connection;
   1592   ph->h_payto = *account_id;
   1593   ph->cb = cb;
   1594   ph->cb_cls = cb_cls;
   1595   code = MHD_lookup_connection_value (connection,
   1596                                       MHD_GET_ARGUMENT_KIND,
   1597                                       "code");
   1598   if (NULL == code)
   1599   {
   1600     const char *err;
   1601     const char *desc;
   1602     const char *euri;
   1603     json_t *body;
   1604 
   1605     err = MHD_lookup_connection_value (connection,
   1606                                        MHD_GET_ARGUMENT_KIND,
   1607                                        "error");
   1608     if (NULL == err)
   1609     {
   1610       GNUNET_break_op (0);
   1611       ph->status = TALER_KYCLOGIC_STATUS_USER_PENDING;
   1612       ph->http_status = MHD_HTTP_BAD_REQUEST;
   1613       body = GNUNET_JSON_PACK (
   1614         GNUNET_JSON_pack_string ("message",
   1615                                  "'code' parameter malformed"),
   1616         TALER_JSON_pack_ec (
   1617           TALER_EC_GENERIC_PARAMETER_MALFORMED));
   1618       GNUNET_break (
   1619         GNUNET_SYSERR !=
   1620         TALER_TEMPLATING_build (ph->connection,
   1621                                 &ph->http_status,
   1622                                 "oauth2-bad-request",
   1623                                 NULL,
   1624                                 NULL,
   1625                                 body,
   1626                                 &ph->response));
   1627       json_decref (body);
   1628       ph->task = GNUNET_SCHEDULER_add_now (&return_proof_response,
   1629                                            ph);
   1630       return ph;
   1631     }
   1632     desc = MHD_lookup_connection_value (connection,
   1633                                         MHD_GET_ARGUMENT_KIND,
   1634                                         "error_description");
   1635     euri = MHD_lookup_connection_value (connection,
   1636                                         MHD_GET_ARGUMENT_KIND,
   1637                                         "error_uri");
   1638     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
   1639                 "OAuth2 process %llu failed with error `%s'\n",
   1640                 (unsigned long long) process_row,
   1641                 err);
   1642     if (0 == strcasecmp (err,
   1643                          "server_error"))
   1644       ph->status = TALER_KYCLOGIC_STATUS_PROVIDER_FAILED;
   1645     else if (0 == strcasecmp (err,
   1646                               "unauthorized_client"))
   1647       ph->status = TALER_KYCLOGIC_STATUS_FAILED;
   1648     else if (0 == strcasecmp (err,
   1649                               "temporarily_unavailable"))
   1650       ph->status = TALER_KYCLOGIC_STATUS_PENDING;
   1651     else
   1652       ph->status = TALER_KYCLOGIC_STATUS_INTERNAL_ERROR;
   1653     ph->http_status = MHD_HTTP_FORBIDDEN;
   1654     body = GNUNET_JSON_PACK (
   1655       GNUNET_JSON_pack_string ("error",
   1656                                err),
   1657       GNUNET_JSON_pack_allow_null (
   1658         GNUNET_JSON_pack_string ("error_details",
   1659                                  desc)),
   1660       GNUNET_JSON_pack_allow_null (
   1661         GNUNET_JSON_pack_string ("error_uri",
   1662                                  euri)));
   1663     GNUNET_break (
   1664       GNUNET_SYSERR !=
   1665       TALER_TEMPLATING_build (ph->connection,
   1666                               &ph->http_status,
   1667                               "oauth2-authentication-failure",
   1668                               NULL,
   1669                               NULL,
   1670                               body,
   1671                               &ph->response));
   1672     json_decref (body);
   1673     ph->task = GNUNET_SCHEDULER_add_now (&return_proof_response,
   1674                                          ph);
   1675     return ph;
   1676 
   1677   }
   1678 
   1679   ph->eh = curl_easy_init ();
   1680   GNUNET_assert (NULL != ph->eh);
   1681   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
   1682               "Requesting OAuth 2.0 data via HTTP POST `%s'\n",
   1683               pd->token_url);
   1684   GNUNET_assert (CURLE_OK ==
   1685                  curl_easy_setopt (ph->eh,
   1686                                    CURLOPT_URL,
   1687                                    pd->token_url));
   1688   GNUNET_assert (CURLE_OK ==
   1689                  curl_easy_setopt (ph->eh,
   1690                                    CURLOPT_VERBOSE,
   1691                                    1));
   1692   GNUNET_assert (CURLE_OK ==
   1693                  curl_easy_setopt (ph->eh,
   1694                                    CURLOPT_POST,
   1695                                    1));
   1696   {
   1697     char *client_id;
   1698     char *client_secret;
   1699     char *authorization_code;
   1700     char *redirect_uri_encoded;
   1701     char *hps;
   1702 
   1703     hps = GNUNET_STRINGS_data_to_string_alloc (&ph->h_payto,
   1704                                                sizeof (ph->h_payto));
   1705     {
   1706       char *redirect_uri;
   1707 
   1708       GNUNET_asprintf (&redirect_uri,
   1709                        "%skyc-proof/%s",
   1710                        ps->exchange_base_url,
   1711                        &pd->section[strlen ("kyc-provider-")]);
   1712       redirect_uri_encoded = TALER_urlencode (redirect_uri);
   1713       GNUNET_free (redirect_uri);
   1714     }
   1715     GNUNET_assert (NULL != redirect_uri_encoded);
   1716     client_id = curl_easy_escape (ph->eh,
   1717                                   pd->client_id,
   1718                                   0);
   1719     GNUNET_assert (NULL != client_id);
   1720     client_secret = curl_easy_escape (ph->eh,
   1721                                       pd->client_secret,
   1722                                       0);
   1723     GNUNET_assert (NULL != client_secret);
   1724     authorization_code = curl_easy_escape (ph->eh,
   1725                                            code,
   1726                                            0);
   1727     GNUNET_assert (NULL != authorization_code);
   1728     GNUNET_asprintf (&ph->post_body,
   1729                      "client_id=%s&redirect_uri=%s&state=%s&client_secret=%s&code=%s&grant_type=authorization_code",
   1730                      client_id,
   1731                      redirect_uri_encoded,
   1732                      hps,
   1733                      client_secret,
   1734                      authorization_code);
   1735     curl_free (authorization_code);
   1736     curl_free (client_secret);
   1737     GNUNET_free (redirect_uri_encoded);
   1738     GNUNET_free (hps);
   1739     curl_free (client_id);
   1740   }
   1741   GNUNET_assert (CURLE_OK ==
   1742                  curl_easy_setopt (ph->eh,
   1743                                    CURLOPT_POSTFIELDS,
   1744                                    ph->post_body));
   1745   GNUNET_assert (CURLE_OK ==
   1746                  curl_easy_setopt (ph->eh,
   1747                                    CURLOPT_FOLLOWLOCATION,
   1748                                    1L));
   1749   /* limit MAXREDIRS to 5 as a simple security measure against
   1750      a potential infinite loop caused by a malicious target */
   1751   GNUNET_assert (CURLE_OK ==
   1752                  curl_easy_setopt (ph->eh,
   1753                                    CURLOPT_MAXREDIRS,
   1754                                    5L));
   1755 
   1756   ph->job = GNUNET_CURL_job_add (ps->curl_ctx,
   1757                                  ph->eh,
   1758                                  &handle_curl_login_finished,
   1759                                  ph);
   1760   return ph;
   1761 }
   1762 
   1763 
   1764 /**
   1765  * Function to asynchronously return the 404 not found
   1766  * page for the webhook.
   1767  *
   1768  * @param cls the `struct TALER_KYCLOGIC_WebhookHandle *`
   1769  */
   1770 static void
   1771 wh_return_not_found (void *cls)
   1772 {
   1773   struct TALER_KYCLOGIC_WebhookHandle *wh = cls;
   1774   struct MHD_Response *response;
   1775 
   1776   wh->task = NULL;
   1777   response = MHD_create_response_from_buffer_static (0,
   1778                                                      "");
   1779   wh->cb (wh->cb_cls,
   1780           0LLU,
   1781           NULL,
   1782           false,
   1783           NULL,
   1784           NULL,
   1785           NULL,
   1786           TALER_KYCLOGIC_STATUS_KEEP,
   1787           GNUNET_TIME_UNIT_ZERO_ABS,
   1788           NULL,
   1789           MHD_HTTP_NOT_FOUND,
   1790           response);
   1791   GNUNET_free (wh);
   1792 }
   1793 
   1794 
   1795 /**
   1796  * Check KYC status and return result for Webhook.
   1797  *
   1798  * @param cls the @e cls of this struct with the plugin-specific state
   1799  * @param pd provider configuration details
   1800  * @param plc callback to lookup accounts with
   1801  * @param plc_cls closure for @a plc
   1802  * @param http_method HTTP method used for the webhook
   1803  * @param url_path rest of the URL after `/kyc-webhook/$LOGIC/`, as NULL-terminated array
   1804  * @param connection MHD connection object (for HTTP headers)
   1805  * @param body HTTP request body, or NULL if not available
   1806  * @param cb function to call with the result
   1807  * @param cb_cls closure for @a cb
   1808  * @return handle to cancel operation early
   1809  */
   1810 static struct TALER_KYCLOGIC_WebhookHandle *
   1811 oauth2_webhook (void *cls,
   1812                 const struct TALER_KYCLOGIC_ProviderDetails *pd,
   1813                 TALER_KYCLOGIC_ProviderLookupCallback plc,
   1814                 void *plc_cls,
   1815                 const char *http_method,
   1816                 const char *const url_path[],
   1817                 struct MHD_Connection *connection,
   1818                 const json_t *body,
   1819                 TALER_KYCLOGIC_WebhookCallback cb,
   1820                 void *cb_cls)
   1821 {
   1822   struct PluginState *ps = cls;
   1823   struct TALER_KYCLOGIC_WebhookHandle *wh;
   1824 
   1825   (void) pd;
   1826   (void) plc;
   1827   (void) plc_cls;
   1828   (void) http_method;
   1829   (void) url_path;
   1830   (void) connection;
   1831   (void) body;
   1832   GNUNET_break_op (0);
   1833   wh = GNUNET_new (struct TALER_KYCLOGIC_WebhookHandle);
   1834   wh->cb = cb;
   1835   wh->cb_cls = cb_cls;
   1836   wh->ps = ps;
   1837   wh->task = GNUNET_SCHEDULER_add_now (&wh_return_not_found,
   1838                                        wh);
   1839   return wh;
   1840 }
   1841 
   1842 
   1843 /**
   1844  * Cancel KYC webhook execution.
   1845  *
   1846  * @param[in] wh handle of operation to cancel
   1847  */
   1848 static void
   1849 oauth2_webhook_cancel (struct TALER_KYCLOGIC_WebhookHandle *wh)
   1850 {
   1851   GNUNET_SCHEDULER_cancel (wh->task);
   1852   GNUNET_free (wh);
   1853 }
   1854 
   1855 
   1856 /**
   1857  * Initialize OAuth2.0 KYC logic plugin
   1858  *
   1859  * @param cls a configuration instance
   1860  * @return NULL on error, otherwise a `struct TALER_KYCLOGIC_Plugin`
   1861  */
   1862 void *
   1863 libtaler_plugin_kyclogic_oauth2_init (void *cls);
   1864 
   1865 /* declaration to avoid compiler warning */
   1866 void *
   1867 libtaler_plugin_kyclogic_oauth2_init (void *cls)
   1868 {
   1869   const struct GNUNET_CONFIGURATION_Handle *cfg = cls;
   1870   struct TALER_KYCLOGIC_Plugin *plugin;
   1871   struct PluginState *ps;
   1872 
   1873   ps = GNUNET_new (struct PluginState);
   1874   ps->cfg = cfg;
   1875   if (GNUNET_OK !=
   1876       GNUNET_CONFIGURATION_get_value_string (cfg,
   1877                                              "exchange",
   1878                                              "BASE_URL",
   1879                                              &ps->exchange_base_url))
   1880   {
   1881     GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
   1882                                "exchange",
   1883                                "BASE_URL");
   1884     GNUNET_free (ps);
   1885     return NULL;
   1886   }
   1887   ps->curl_ctx
   1888     = GNUNET_CURL_init (&GNUNET_CURL_gnunet_scheduler_reschedule,
   1889                         &ps->curl_rc);
   1890   if (NULL == ps->curl_ctx)
   1891   {
   1892     GNUNET_break (0);
   1893     GNUNET_free (ps->exchange_base_url);
   1894     GNUNET_free (ps);
   1895     return NULL;
   1896   }
   1897   ps->curl_rc = GNUNET_CURL_gnunet_rc_create (ps->curl_ctx);
   1898 
   1899   plugin = GNUNET_new (struct TALER_KYCLOGIC_Plugin);
   1900   plugin->cls = ps;
   1901   plugin->load_configuration
   1902     = &oauth2_load_configuration;
   1903   plugin->unload_configuration
   1904     = &oauth2_unload_configuration;
   1905   plugin->initiate
   1906     = &oauth2_initiate;
   1907   plugin->initiate_cancel
   1908     = &oauth2_initiate_cancel;
   1909   plugin->proof
   1910     = &oauth2_proof;
   1911   plugin->proof_cancel
   1912     = &oauth2_proof_cancel;
   1913   plugin->webhook
   1914     = &oauth2_webhook;
   1915   plugin->webhook_cancel
   1916     = &oauth2_webhook_cancel;
   1917   return plugin;
   1918 }
   1919 
   1920 
   1921 /**
   1922  * Unload authorization plugin
   1923  *
   1924  * @param cls a `struct TALER_KYCLOGIC_Plugin`
   1925  * @return NULL (always)
   1926  */
   1927 void *
   1928 libtaler_plugin_kyclogic_oauth2_done (void *cls);
   1929 
   1930 /* declaration to avoid compiler warning */
   1931 void *
   1932 libtaler_plugin_kyclogic_oauth2_done (void *cls)
   1933 {
   1934   struct TALER_KYCLOGIC_Plugin *plugin = cls;
   1935   struct PluginState *ps = plugin->cls;
   1936 
   1937   if (NULL != ps->curl_ctx)
   1938   {
   1939     GNUNET_CURL_fini (ps->curl_ctx);
   1940     ps->curl_ctx = NULL;
   1941   }
   1942   if (NULL != ps->curl_rc)
   1943   {
   1944     GNUNET_CURL_gnunet_rc_destroy (ps->curl_rc);
   1945     ps->curl_rc = NULL;
   1946   }
   1947   GNUNET_free (ps->exchange_base_url);
   1948   GNUNET_free (ps);
   1949   GNUNET_free (plugin);
   1950   return NULL;
   1951 }