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 (58471B)


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