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


      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     char *client_id_encoded;
    618     char *scope_encoded;
    619 
    620     {
    621       char *redirect_uri;
    622 
    623       GNUNET_asprintf (&redirect_uri,
    624                        "%skyc-proof/%s",
    625                        ps->exchange_base_url,
    626                        &pd->section[strlen ("kyc-provider-")]);
    627       redirect_uri_encoded = TALER_urlencode (redirect_uri);
    628       GNUNET_free (redirect_uri);
    629     }
    630     client_id_encoded = TALER_urlencode (pd->client_id);
    631     scope_encoded = TALER_urlencode (NULL != pd->scope
    632                                      ? pd->scope
    633                                      : "");
    634     GNUNET_asprintf (&url,
    635                      "%s?response_type=code&client_id=%s&redirect_uri=%s&state=%s&scope=%s",
    636                      authorize_url,
    637                      client_id_encoded,
    638                      redirect_uri_encoded,
    639                      hps,
    640                      scope_encoded);
    641     GNUNET_free (scope_encoded);
    642     GNUNET_free (client_id_encoded);
    643     GNUNET_free (redirect_uri_encoded);
    644   }
    645   ih->cb (ih->cb_cls,
    646           TALER_EC_NONE,
    647           url,
    648           NULL /* unknown user_id here */,
    649           legi_s,
    650           NULL /* no error */);
    651   GNUNET_free (url);
    652   GNUNET_free (hps);
    653   oauth2_initiate_cancel (ih);
    654 }
    655 
    656 
    657 /**
    658  * After we are done with the CURL interaction we
    659  * need to update our database state with the information
    660  * retrieved.
    661  *
    662  * @param cls a `struct TALER_KYCLOGIC_InitiateHandle *`
    663  * @param response_code HTTP response code from server, 0 on hard error
    664  * @param response in JSON, NULL if response was not in JSON format
    665  */
    666 static void
    667 handle_curl_setup_finished (void *cls,
    668                             long response_code,
    669                             const void *response)
    670 {
    671   struct TALER_KYCLOGIC_InitiateHandle *ih = cls;
    672   const struct TALER_KYCLOGIC_ProviderDetails *pd = ih->pd;
    673   const json_t *j = response;
    674 
    675   ih->job = NULL;
    676   switch (response_code)
    677   {
    678   case 0:
    679     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    680                 "/setup URL failed to return HTTP response\n");
    681     ih->cb (ih->cb_cls,
    682             TALER_EC_EXCHANGE_KYC_PROOF_BACKEND_INVALID_RESPONSE,
    683             NULL,
    684             NULL,
    685             NULL,
    686             "/setup request to OAuth 2.0 backend returned no response");
    687     oauth2_initiate_cancel (ih);
    688     return;
    689   case MHD_HTTP_OK:
    690     {
    691       const char *nonce;
    692       struct GNUNET_JSON_Specification spec[] = {
    693         GNUNET_JSON_spec_string ("nonce",
    694                                  &nonce),
    695         GNUNET_JSON_spec_end ()
    696       };
    697       enum GNUNET_GenericReturnValue res;
    698       const char *emsg;
    699       unsigned int line;
    700       char *url;
    701 
    702       res = GNUNET_JSON_parse (j,
    703                                spec,
    704                                &emsg,
    705                                &line);
    706       if (GNUNET_OK != res)
    707       {
    708         GNUNET_break_op (0);
    709         json_dumpf (j,
    710                     stderr,
    711                     JSON_INDENT (2));
    712         ih->cb (ih->cb_cls,
    713                 TALER_EC_EXCHANGE_KYC_PROOF_BACKEND_INVALID_RESPONSE,
    714                 NULL,
    715                 NULL,
    716                 NULL,
    717                 "Unexpected response from KYC gateway: setup must return a nonce");
    718         oauth2_initiate_cancel (ih);
    719         return;
    720       }
    721       GNUNET_asprintf (&url,
    722                        "%s/%s",
    723                        pd->authorize_url,
    724                        nonce);
    725       initiate_with_url (ih,
    726                          url);
    727       GNUNET_free (url);
    728       return;
    729     }
    730     break;
    731   default:
    732     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    733                 "/setup URL returned HTTP status %u\n",
    734                 (unsigned int) response_code);
    735     ih->cb (ih->cb_cls,
    736             TALER_EC_EXCHANGE_KYC_PROOF_BACKEND_INVALID_RESPONSE,
    737             NULL,
    738             NULL,
    739             NULL,
    740             "/setup request to OAuth 2.0 backend returned unexpected HTTP status code");
    741     oauth2_initiate_cancel (ih);
    742     return;
    743   }
    744 }
    745 
    746 
    747 /**
    748  * Logic to asynchronously return the response for how to begin the OAuth2.0
    749  * checking process to the client.  May first request a dynamic URL via
    750  * ``/setup`` if configured to use a client-authenticated setup process.
    751  *
    752  * @param cls a `struct TALER_KYCLOGIC_InitiateHandle *`
    753  */
    754 static void
    755 initiate_task (void *cls)
    756 {
    757   struct TALER_KYCLOGIC_InitiateHandle *ih = cls;
    758   const struct TALER_KYCLOGIC_ProviderDetails *pd = ih->pd;
    759   struct PluginState *ps = pd->ps;
    760   CURL *eh;
    761 
    762   ih->task = NULL;
    763   if (NULL == pd->setup_url)
    764   {
    765     initiate_with_url (ih,
    766                        pd->authorize_url);
    767     return;
    768   }
    769   eh = curl_easy_init ();
    770   if (NULL == eh)
    771   {
    772     GNUNET_break (0);
    773     ih->cb (ih->cb_cls,
    774             TALER_EC_GENERIC_ALLOCATION_FAILURE,
    775             NULL,
    776             NULL,
    777             NULL,
    778             "curl_easy_init() failed");
    779     oauth2_initiate_cancel (ih);
    780     return;
    781   }
    782   GNUNET_assert (CURLE_OK ==
    783                  curl_easy_setopt (eh,
    784                                    CURLOPT_URL,
    785                                    pd->setup_url));
    786 #if DEBUG
    787   GNUNET_assert (CURLE_OK ==
    788                  curl_easy_setopt (eh,
    789                                    CURLOPT_VERBOSE,
    790                                    1));
    791 #endif
    792   if (NULL == ih->initial_address)
    793   {
    794     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    795                 "Staring OAuth 2.0 without initial address\n");
    796     GNUNET_assert (CURLE_OK ==
    797                    curl_easy_setopt (eh,
    798                                      CURLOPT_POST,
    799                                      1));
    800     GNUNET_assert (CURLE_OK ==
    801                    curl_easy_setopt (eh,
    802                                      CURLOPT_POSTFIELDS,
    803                                      ""));
    804     GNUNET_assert (CURLE_OK ==
    805                    curl_easy_setopt (eh,
    806                                      CURLOPT_POSTFIELDSIZE,
    807                                      (long) 0));
    808   }
    809   else
    810   {
    811     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    812                 "Staring OAuth 2.0 with initial address\n");
    813 #if DEBUG
    814     json_dumpf (ih->initial_address,
    815                 stderr,
    816                 JSON_INDENT (2));
    817     fprintf (stderr,
    818              "\n");
    819 #endif
    820     if (GNUNET_OK !=
    821         TALER_curl_easy_post (&ih->ctx,
    822                               eh,
    823                               ih->initial_address))
    824     {
    825       curl_easy_cleanup (eh);
    826       ih->cb (ih->cb_cls,
    827               TALER_EC_GENERIC_ALLOCATION_FAILURE,
    828               NULL,
    829               NULL,
    830               NULL,
    831               "TALER_curl_easy_post() failed");
    832       oauth2_initiate_cancel (ih);
    833       return;
    834     }
    835   }
    836   GNUNET_assert (CURLE_OK ==
    837                  curl_easy_setopt (eh,
    838                                    CURLOPT_FOLLOWLOCATION,
    839                                    1L));
    840   GNUNET_assert (CURLE_OK ==
    841                  curl_easy_setopt (eh,
    842                                    CURLOPT_MAXREDIRS,
    843                                    5L));
    844   ih->job = GNUNET_CURL_job_add2 (ps->curl_ctx,
    845                                   eh,
    846                                   ih->ctx.headers,
    847                                   &handle_curl_setup_finished,
    848                                   ih);
    849   {
    850     char *hdr;
    851     struct curl_slist *slist;
    852 
    853     GNUNET_asprintf (&hdr,
    854                      "%s: Bearer %s",
    855                      MHD_HTTP_HEADER_AUTHORIZATION,
    856                      pd->client_secret);
    857     slist = curl_slist_append (NULL,
    858                                hdr);
    859     GNUNET_CURL_extend_headers (ih->job,
    860                                 slist);
    861     curl_slist_free_all (slist);
    862     GNUNET_free (hdr);
    863   }
    864 }
    865 
    866 
    867 /**
    868  * Initiate KYC check.
    869  *
    870  * @param cls the @e cls of this struct with the plugin-specific state
    871  * @param pd provider configuration details
    872  * @param account_id which account to trigger process for
    873  * @param legitimization_uuid unique ID for the legitimization process
    874  * @param context additional contextual information for the legi process
    875  * @param cb function to call with the result
    876  * @param cb_cls closure for @a cb
    877  * @return handle to cancel operation early
    878  */
    879 static struct TALER_KYCLOGIC_InitiateHandle *
    880 oauth2_initiate (void *cls,
    881                  const struct TALER_KYCLOGIC_ProviderDetails *pd,
    882                  const struct TALER_NormalizedPaytoHashP *account_id,
    883                  uint64_t legitimization_uuid,
    884                  const json_t *context,
    885                  TALER_KYCLOGIC_InitiateCallback cb,
    886                  void *cb_cls)
    887 {
    888   struct TALER_KYCLOGIC_InitiateHandle *ih;
    889 
    890   (void) cls;
    891   ih = GNUNET_new (struct TALER_KYCLOGIC_InitiateHandle);
    892   ih->legitimization_uuid = legitimization_uuid;
    893   ih->cb = cb;
    894   ih->cb_cls = cb_cls;
    895   ih->h_payto = *account_id;
    896   ih->pd = pd;
    897   ih->task = GNUNET_SCHEDULER_add_now (&initiate_task,
    898                                        ih);
    899   if (NULL != context)
    900   {
    901     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    902                 "Initiating OAuth2 validation with context\n");
    903 #if DEBUG
    904     json_dumpf (context,
    905                 stderr,
    906                 JSON_INDENT (2));
    907     fprintf (stderr,
    908              "\n");
    909 #endif
    910     ih->initial_address = json_incref (json_object_get (context,
    911                                                         "initial_address"));
    912   }
    913   return ih;
    914 }
    915 
    916 
    917 /**
    918  * Cancel KYC proof.
    919  *
    920  * @param[in] ph handle of operation to cancel
    921  */
    922 static void
    923 oauth2_proof_cancel (struct TALER_KYCLOGIC_ProofHandle *ph)
    924 {
    925   if (NULL != ph->ec)
    926   {
    927     TALER_JSON_external_conversion_stop (ph->ec);
    928     ph->ec = NULL;
    929   }
    930   if (NULL != ph->task)
    931   {
    932     GNUNET_SCHEDULER_cancel (ph->task);
    933     ph->task = NULL;
    934   }
    935   if (NULL != ph->job)
    936   {
    937     GNUNET_CURL_job_cancel (ph->job);
    938     ph->job = NULL;
    939   }
    940   if (NULL != ph->response)
    941   {
    942     MHD_destroy_response (ph->response);
    943     ph->response = NULL;
    944   }
    945   GNUNET_free (ph->provider_user_id);
    946   if (NULL != ph->attributes)
    947     json_decref (ph->attributes);
    948   GNUNET_free (ph->post_body);
    949   GNUNET_free (ph);
    950 }
    951 
    952 
    953 /**
    954  * Function called to asynchronously return the final
    955  * result to the callback.
    956  *
    957  * @param cls a `struct TALER_KYCLOGIC_ProofHandle`
    958  */
    959 static void
    960 return_proof_response (void *cls)
    961 {
    962   struct TALER_KYCLOGIC_ProofHandle *ph = cls;
    963   const char *provider_name;
    964 
    965   ph->task = NULL;
    966   provider_name = ph->pd->section;
    967   if (0 !=
    968       strncasecmp (provider_name,
    969                    "KYC-PROVIDER-",
    970                    strlen ("KYC-PROVIDER-")))
    971   {
    972     GNUNET_break (0);
    973   }
    974   else
    975   {
    976     provider_name += strlen ("KYC-PROVIDER-");
    977   }
    978   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    979               "Returning KYC proof from `%s'\n",
    980               provider_name);
    981   ph->cb (ph->cb_cls,
    982           ph->status,
    983           provider_name,
    984           ph->provider_user_id,
    985           ph->provider_legitimization_id,
    986           GNUNET_TIME_relative_to_absolute (ph->pd->validity),
    987           ph->attributes,
    988           ph->http_status,
    989           ph->response);
    990   ph->response = NULL; /*Ownership passed to 'ph->cb'!*/
    991   oauth2_proof_cancel (ph);
    992 }
    993 
    994 
    995 /**
    996  * Load a @a template and substitute using @a root, returning the result in a
    997  * @a reply encoded suitable for the @a connection with the given @a
    998  * http_status code.  On errors, the @a http_status code
    999  * is updated to reflect the type of error encoded in the
   1000  * @a reply.
   1001  *
   1002  * @param connection the connection we act upon
   1003  * @param[in,out] http_status code to use on success,
   1004  *           set to alternative code on failure
   1005  * @param template basename of the template to load
   1006  * @param root JSON object to pass as the root context
   1007  * @param[out] reply where to write the response object
   1008  * @return #GNUNET_OK on success (reply queued), #GNUNET_NO if an error was queued,
   1009  *         #GNUNET_SYSERR on failure (to queue an error)
   1010  */
   1011 static enum GNUNET_GenericReturnValue
   1012 templating_build (struct MHD_Connection *connection,
   1013                   unsigned int *http_status,
   1014                   const char *template,
   1015                   const json_t *root,
   1016                   struct MHD_Response **reply)
   1017 {
   1018   enum GNUNET_GenericReturnValue ret;
   1019 
   1020   ret = TALER_TEMPLATING_build (connection,
   1021                                 http_status,
   1022                                 template,
   1023                                 NULL,
   1024                                 NULL,
   1025                                 root,
   1026                                 reply);
   1027   if (GNUNET_SYSERR != ret)
   1028   {
   1029     GNUNET_break (MHD_NO !=
   1030                   MHD_add_response_header (*reply,
   1031                                            MHD_HTTP_HEADER_CONTENT_TYPE,
   1032                                            "text/html"));
   1033   }
   1034   return ret;
   1035 }
   1036 
   1037 
   1038 /**
   1039  * The request for @a ph failed. We may have gotten a useful error
   1040  * message in @a j. Generate a failure response.
   1041  *
   1042  * @param[in,out] ph request that failed
   1043  * @param j reply from the server (or NULL)
   1044  */
   1045 static void
   1046 handle_proof_error (struct TALER_KYCLOGIC_ProofHandle *ph,
   1047                     const json_t *j)
   1048 {
   1049   enum GNUNET_GenericReturnValue res;
   1050 
   1051   {
   1052     const char *msg;
   1053     const char *desc;
   1054     struct GNUNET_JSON_Specification spec[] = {
   1055       GNUNET_JSON_spec_string ("error",
   1056                                &msg),
   1057       GNUNET_JSON_spec_string ("error_description",
   1058                                &desc),
   1059       GNUNET_JSON_spec_end ()
   1060     };
   1061     const char *emsg;
   1062     unsigned int line;
   1063 
   1064     res = GNUNET_JSON_parse (j,
   1065                              spec,
   1066                              &emsg,
   1067                              &line);
   1068   }
   1069 
   1070   if (GNUNET_OK != res)
   1071   {
   1072     json_t *body;
   1073 
   1074     GNUNET_break_op (0);
   1075     ph->status = TALER_KYCLOGIC_STATUS_PROVIDER_FAILED;
   1076     ph->http_status
   1077       = MHD_HTTP_BAD_GATEWAY;
   1078     body = GNUNET_JSON_PACK (
   1079       GNUNET_JSON_pack_allow_null (
   1080         GNUNET_JSON_pack_object_incref ("server_response",
   1081                                         (json_t *) j)),
   1082       GNUNET_JSON_pack_bool ("debug",
   1083                              ph->pd->debug_mode),
   1084       TALER_JSON_pack_ec (
   1085         TALER_EC_EXCHANGE_KYC_PROOF_BACKEND_INVALID_RESPONSE));
   1086     GNUNET_assert (NULL != body);
   1087     GNUNET_break (
   1088       GNUNET_SYSERR !=
   1089       templating_build (ph->connection,
   1090                         &ph->http_status,
   1091                         "oauth2-authorization-failure-malformed",
   1092                         body,
   1093                         &ph->response));
   1094     json_decref (body);
   1095     return;
   1096   }
   1097   ph->status = TALER_KYCLOGIC_STATUS_USER_ABORTED;
   1098   ph->http_status = MHD_HTTP_FORBIDDEN;
   1099   GNUNET_break (
   1100     GNUNET_SYSERR !=
   1101     templating_build (ph->connection,
   1102                       &ph->http_status,
   1103                       "oauth2-authorization-failure",
   1104                       j,
   1105                       &ph->response));
   1106 }
   1107 
   1108 
   1109 /**
   1110  * Type of a callback that receives a JSON @a result.
   1111  *
   1112  * @param cls closure with a `struct TALER_KYCLOGIC_ProofHandle *`
   1113  * @param status_type how did the process die
   1114  * @param code termination status code from the process
   1115  * @param attr result some JSON result, NULL if we failed to get an JSON output
   1116  */
   1117 static void
   1118 converted_proof_cb (void *cls,
   1119                     enum GNUNET_OS_ProcessStatusType status_type,
   1120                     unsigned long code,
   1121                     const json_t *attr)
   1122 {
   1123   struct TALER_KYCLOGIC_ProofHandle *ph = cls;
   1124   const struct TALER_KYCLOGIC_ProviderDetails *pd = ph->pd;
   1125 
   1126   ph->ec = NULL;
   1127   if ( (NULL == attr) ||
   1128        (0 != code) )
   1129   {
   1130     json_t *body;
   1131     char *msg;
   1132 
   1133     GNUNET_break_op (0);
   1134     ph->status = TALER_KYCLOGIC_STATUS_PROVIDER_FAILED;
   1135     ph->http_status = MHD_HTTP_BAD_GATEWAY;
   1136     if (0 != code)
   1137       GNUNET_asprintf (&msg,
   1138                        "Attribute converter exited with status %ld",
   1139                        code);
   1140     else
   1141       msg = GNUNET_strdup (
   1142         "Attribute converter response was not in JSON format");
   1143     body = GNUNET_JSON_PACK (
   1144       GNUNET_JSON_pack_string ("converter",
   1145                                pd->conversion_binary),
   1146       GNUNET_JSON_pack_allow_null (
   1147         GNUNET_JSON_pack_object_incref ("attributes",
   1148                                         (json_t *) attr)),
   1149       GNUNET_JSON_pack_bool ("debug",
   1150                              ph->pd->debug_mode),
   1151       GNUNET_JSON_pack_string ("message",
   1152                                msg),
   1153       TALER_JSON_pack_ec (
   1154         TALER_EC_EXCHANGE_KYC_PROOF_BACKEND_INVALID_RESPONSE));
   1155     GNUNET_free (msg);
   1156     GNUNET_break (
   1157       GNUNET_SYSERR !=
   1158       templating_build (ph->connection,
   1159                         &ph->http_status,
   1160                         "oauth2-conversion-failure",
   1161                         body,
   1162                         &ph->response));
   1163     json_decref (body);
   1164     ph->task = GNUNET_SCHEDULER_add_now (&return_proof_response,
   1165                                          ph);
   1166     return;
   1167   }
   1168   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
   1169               "Attribute conversion output is:\n");
   1170 #if DEBUG
   1171   json_dumpf (attr,
   1172               stderr,
   1173               JSON_INDENT (2));
   1174   fprintf (stderr,
   1175            "\n");
   1176 #endif
   1177   {
   1178     const char *id;
   1179     struct GNUNET_JSON_Specification ispec[] = {
   1180       GNUNET_JSON_spec_string ("id",
   1181                                &id),
   1182       GNUNET_JSON_spec_end ()
   1183     };
   1184     enum GNUNET_GenericReturnValue res;
   1185     const char *emsg;
   1186     unsigned int line;
   1187 
   1188     res = GNUNET_JSON_parse (attr,
   1189                              ispec,
   1190                              &emsg,
   1191                              &line);
   1192     if (GNUNET_OK != res)
   1193     {
   1194       json_t *body;
   1195 
   1196       GNUNET_break_op (0);
   1197       ph->status = TALER_KYCLOGIC_STATUS_PROVIDER_FAILED;
   1198       ph->http_status = MHD_HTTP_BAD_GATEWAY;
   1199       body = GNUNET_JSON_PACK (
   1200         GNUNET_JSON_pack_string ("converter",
   1201                                  pd->conversion_binary),
   1202         GNUNET_JSON_pack_string ("message",
   1203                                  "Unexpected response from KYC attribute converter: returned JSON data must contain 'id' field"),
   1204         GNUNET_JSON_pack_bool ("debug",
   1205                                ph->pd->debug_mode),
   1206         GNUNET_JSON_pack_object_incref ("attributes",
   1207                                         (json_t *) attr),
   1208         TALER_JSON_pack_ec (
   1209           TALER_EC_EXCHANGE_KYC_PROOF_BACKEND_INVALID_RESPONSE));
   1210       GNUNET_break (
   1211         GNUNET_SYSERR !=
   1212         templating_build (ph->connection,
   1213                           &ph->http_status,
   1214                           "oauth2-conversion-failure",
   1215                           body,
   1216                           &ph->response));
   1217       json_decref (body);
   1218       ph->task = GNUNET_SCHEDULER_add_now (&return_proof_response,
   1219                                            ph);
   1220       return;
   1221     }
   1222     ph->provider_user_id = GNUNET_strdup (id);
   1223   }
   1224   if (! json_is_string (json_object_get (attr,
   1225                                          "FORM_ID")))
   1226   {
   1227     json_t *body;
   1228 
   1229     GNUNET_break_op (0);
   1230     ph->status = TALER_KYCLOGIC_STATUS_PROVIDER_FAILED;
   1231     ph->http_status = MHD_HTTP_BAD_GATEWAY;
   1232     body = GNUNET_JSON_PACK (
   1233       GNUNET_JSON_pack_string ("converter",
   1234                                pd->conversion_binary),
   1235       GNUNET_JSON_pack_string ("message",
   1236                                "Missing 'FORM_ID' field in attributes"),
   1237       GNUNET_JSON_pack_bool ("debug",
   1238                              ph->pd->debug_mode),
   1239       GNUNET_JSON_pack_object_incref ("attributes",
   1240                                       (json_t *) attr),
   1241       TALER_JSON_pack_ec (
   1242         TALER_EC_EXCHANGE_KYC_PROOF_BACKEND_INVALID_RESPONSE));
   1243     GNUNET_break (
   1244       GNUNET_SYSERR !=
   1245       templating_build (ph->connection,
   1246                         &ph->http_status,
   1247                         "oauth2-conversion-failure",
   1248                         body,
   1249                         &ph->response));
   1250     json_decref (body);
   1251     ph->task = GNUNET_SCHEDULER_add_now (&return_proof_response,
   1252                                          ph);
   1253     return;
   1254   }
   1255   ph->status = TALER_KYCLOGIC_STATUS_SUCCESS;
   1256   ph->response = MHD_create_response_from_buffer_static (0,
   1257                                                          "");
   1258   GNUNET_assert (NULL != ph->response);
   1259   GNUNET_break (MHD_YES ==
   1260                 MHD_add_response_header (
   1261                   ph->response,
   1262                   MHD_HTTP_HEADER_LOCATION,
   1263                   ph->pd->post_kyc_redirect_url));
   1264   ph->http_status = MHD_HTTP_SEE_OTHER;
   1265   ph->attributes = json_incref ((json_t *) attr);
   1266   ph->task = GNUNET_SCHEDULER_add_now (&return_proof_response,
   1267                                        ph);
   1268 }
   1269 
   1270 
   1271 /**
   1272  * The request for @a ph succeeded (presumably).
   1273  * Call continuation with the result.
   1274  *
   1275  * @param[in,out] ph request that succeeded
   1276  * @param j reply from the server
   1277  */
   1278 static void
   1279 parse_proof_success_reply (struct TALER_KYCLOGIC_ProofHandle *ph,
   1280                            const json_t *j)
   1281 {
   1282   const struct TALER_KYCLOGIC_ProviderDetails *pd = ph->pd;
   1283   const char *argv[] = {
   1284     pd->conversion_binary,
   1285     NULL,
   1286   };
   1287 
   1288   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
   1289               "Calling converter `%s' with JSON\n",
   1290               pd->conversion_binary);
   1291 #if DEBUG
   1292   json_dumpf (j,
   1293               stderr,
   1294               JSON_INDENT (2));
   1295 #endif
   1296   ph->ec = TALER_JSON_external_conversion_start (
   1297     j,
   1298     &converted_proof_cb,
   1299     ph,
   1300     pd->conversion_binary,
   1301     argv);
   1302   if (NULL != ph->ec)
   1303     return;
   1304   GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
   1305               "Failed to start OAUTH2 conversion helper `%s'\n",
   1306               pd->conversion_binary);
   1307   ph->status = TALER_KYCLOGIC_STATUS_INTERNAL_ERROR;
   1308   ph->http_status = MHD_HTTP_INTERNAL_SERVER_ERROR;
   1309   {
   1310     json_t *body;
   1311 
   1312     body = GNUNET_JSON_PACK (
   1313       GNUNET_JSON_pack_string ("converter",
   1314                                pd->conversion_binary),
   1315       GNUNET_JSON_pack_bool ("debug",
   1316                              ph->pd->debug_mode),
   1317       GNUNET_JSON_pack_string ("message",
   1318                                "Failed to launch KYC conversion helper process."),
   1319       TALER_JSON_pack_ec (
   1320         TALER_EC_EXCHANGE_GENERIC_KYC_CONVERTER_FAILED));
   1321     GNUNET_break (
   1322       GNUNET_SYSERR !=
   1323       templating_build (ph->connection,
   1324                         &ph->http_status,
   1325                         "oauth2-conversion-failure",
   1326                         body,
   1327                         &ph->response));
   1328     json_decref (body);
   1329   }
   1330   ph->task = GNUNET_SCHEDULER_add_now (&return_proof_response,
   1331                                        ph);
   1332 }
   1333 
   1334 
   1335 /**
   1336  * After we are done with the CURL interaction we
   1337  * need to update our database state with the information
   1338  * retrieved.
   1339  *
   1340  * @param cls our `struct TALER_KYCLOGIC_ProofHandle`
   1341  * @param response_code HTTP response code from server, 0 on hard error
   1342  * @param response in JSON, NULL if response was not in JSON format
   1343  */
   1344 static void
   1345 handle_curl_proof_finished (void *cls,
   1346                             long response_code,
   1347                             const void *response)
   1348 {
   1349   struct TALER_KYCLOGIC_ProofHandle *ph = cls;
   1350   const json_t *j = response;
   1351 
   1352   ph->job = NULL;
   1353   switch (response_code)
   1354   {
   1355   case 0:
   1356     {
   1357       json_t *body;
   1358 
   1359       ph->status = TALER_KYCLOGIC_STATUS_PROVIDER_FAILED;
   1360       ph->http_status = MHD_HTTP_BAD_GATEWAY;
   1361 
   1362       body = GNUNET_JSON_PACK (
   1363         GNUNET_JSON_pack_string ("message",
   1364                                  "No response from KYC gateway"),
   1365         TALER_JSON_pack_ec (
   1366           TALER_EC_EXCHANGE_KYC_PROOF_BACKEND_INVALID_RESPONSE));
   1367       GNUNET_break (
   1368         GNUNET_SYSERR !=
   1369         templating_build (ph->connection,
   1370                           &ph->http_status,
   1371                           "oauth2-provider-failure",
   1372                           body,
   1373                           &ph->response));
   1374       json_decref (body);
   1375     }
   1376     break;
   1377   case MHD_HTTP_OK:
   1378     parse_proof_success_reply (ph,
   1379                                j);
   1380     return;
   1381   default:
   1382     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
   1383                 "OAuth2.0 info URL returned HTTP status %u\n",
   1384                 (unsigned int) response_code);
   1385     handle_proof_error (ph,
   1386                         j);
   1387     break;
   1388   }
   1389   ph->task = GNUNET_SCHEDULER_add_now (&return_proof_response,
   1390                                        ph);
   1391 }
   1392 
   1393 
   1394 /**
   1395  * After we are done with the CURL interaction we
   1396  * need to fetch the user's account details.
   1397  *
   1398  * @param cls our `struct KycProofContext`
   1399  * @param response_code HTTP response code from server, 0 on hard error
   1400  * @param response in JSON, NULL if response was not in JSON format
   1401  */
   1402 static void
   1403 handle_curl_login_finished (void *cls,
   1404                             long response_code,
   1405                             const void *response)
   1406 {
   1407   struct TALER_KYCLOGIC_ProofHandle *ph = cls;
   1408   const json_t *j = response;
   1409 
   1410   ph->job = NULL;
   1411   switch (response_code)
   1412   {
   1413   case MHD_HTTP_OK:
   1414     {
   1415       const char *access_token;
   1416       const char *token_type;
   1417       uint64_t expires_in_s;
   1418       const char *refresh_token;
   1419       bool no_expires;
   1420       bool no_refresh;
   1421       struct GNUNET_JSON_Specification spec[] = {
   1422         GNUNET_JSON_spec_string ("access_token",
   1423                                  &access_token),
   1424         GNUNET_JSON_spec_string ("token_type",
   1425                                  &token_type),
   1426         GNUNET_JSON_spec_mark_optional (
   1427           GNUNET_JSON_spec_uint64 ("expires_in",
   1428                                    &expires_in_s),
   1429           &no_expires),
   1430         GNUNET_JSON_spec_mark_optional (
   1431           GNUNET_JSON_spec_string ("refresh_token",
   1432                                    &refresh_token),
   1433           &no_refresh),
   1434         GNUNET_JSON_spec_end ()
   1435       };
   1436       CURL *eh;
   1437 
   1438       {
   1439         enum GNUNET_GenericReturnValue res;
   1440         const char *emsg;
   1441         unsigned int line;
   1442 
   1443         res = GNUNET_JSON_parse (j,
   1444                                  spec,
   1445                                  &emsg,
   1446                                  &line);
   1447         if (GNUNET_OK != res)
   1448         {
   1449           json_t *body;
   1450 
   1451           GNUNET_break_op (0);
   1452           ph->http_status
   1453             = MHD_HTTP_BAD_GATEWAY;
   1454           body = GNUNET_JSON_PACK (
   1455             GNUNET_JSON_pack_object_incref ("server_response",
   1456                                             (json_t *) j),
   1457             GNUNET_JSON_pack_bool ("debug",
   1458                                    ph->pd->debug_mode),
   1459             GNUNET_JSON_pack_string ("message",
   1460                                      "Unexpected response from KYC gateway: required fields missing or malformed"),
   1461             TALER_JSON_pack_ec (
   1462               TALER_EC_EXCHANGE_KYC_PROOF_BACKEND_INVALID_RESPONSE));
   1463           GNUNET_break (
   1464             GNUNET_SYSERR !=
   1465             templating_build (ph->connection,
   1466                               &ph->http_status,
   1467                               "oauth2-provider-failure",
   1468                               body,
   1469                               &ph->response));
   1470           json_decref (body);
   1471           break;
   1472         }
   1473       }
   1474       if (0 != strcasecmp (token_type,
   1475                            "bearer"))
   1476       {
   1477         json_t *body;
   1478 
   1479         GNUNET_break_op (0);
   1480         ph->http_status = MHD_HTTP_BAD_GATEWAY;
   1481         body = GNUNET_JSON_PACK (
   1482           GNUNET_JSON_pack_object_incref ("server_response",
   1483                                           (json_t *) j),
   1484           GNUNET_JSON_pack_bool ("debug",
   1485                                  ph->pd->debug_mode),
   1486           GNUNET_JSON_pack_string ("message",
   1487                                    "Unexpected 'token_type' in response from KYC gateway: 'bearer' token required"),
   1488           TALER_JSON_pack_ec (
   1489             TALER_EC_EXCHANGE_KYC_PROOF_BACKEND_INVALID_RESPONSE));
   1490         GNUNET_break (
   1491           GNUNET_SYSERR !=
   1492           templating_build (ph->connection,
   1493                             &ph->http_status,
   1494                             "oauth2-provider-failure",
   1495                             body,
   1496                             &ph->response));
   1497         json_decref (body);
   1498         break;
   1499       }
   1500 
   1501       /* We guard against a few characters that could
   1502          conceivably be abused to mess with the HTTP header */
   1503       if ( (NULL != strchr (access_token,
   1504                             '\n')) ||
   1505            (NULL != strchr (access_token,
   1506                             '\r')) ||
   1507            (NULL != strchr (access_token,
   1508                             ' ')) ||
   1509            (NULL != strchr (access_token,
   1510                             ';')) )
   1511       {
   1512         json_t *body;
   1513 
   1514         GNUNET_break_op (0);
   1515         ph->http_status = MHD_HTTP_BAD_GATEWAY;
   1516         body = GNUNET_JSON_PACK (
   1517           GNUNET_JSON_pack_object_incref ("server_response",
   1518                                           (json_t *) j),
   1519           GNUNET_JSON_pack_bool ("debug",
   1520                                  ph->pd->debug_mode),
   1521           GNUNET_JSON_pack_string ("message",
   1522                                    "Illegal character in access token"),
   1523           TALER_JSON_pack_ec (
   1524             TALER_EC_EXCHANGE_KYC_PROOF_BACKEND_INVALID_RESPONSE));
   1525         GNUNET_break (
   1526           GNUNET_SYSERR !=
   1527           templating_build (ph->connection,
   1528                             &ph->http_status,
   1529                             "oauth2-provider-failure",
   1530                             body,
   1531                             &ph->response));
   1532         json_decref (body);
   1533         break;
   1534       }
   1535 
   1536       eh = curl_easy_init ();
   1537       GNUNET_assert (NULL != eh);
   1538       GNUNET_assert (CURLE_OK ==
   1539                      curl_easy_setopt (eh,
   1540                                        CURLOPT_URL,
   1541                                        ph->pd->info_url));
   1542       {
   1543         char *hdr;
   1544         struct curl_slist *slist;
   1545 
   1546         GNUNET_asprintf (&hdr,
   1547                          "%s: Bearer %s",
   1548                          MHD_HTTP_HEADER_AUTHORIZATION,
   1549                          access_token);
   1550         slist = curl_slist_append (NULL,
   1551                                    hdr);
   1552         ph->job = GNUNET_CURL_job_add2 (ph->pd->ps->curl_ctx,
   1553                                         eh,
   1554                                         slist,
   1555                                         &handle_curl_proof_finished,
   1556                                         ph);
   1557         curl_slist_free_all (slist);
   1558         GNUNET_free (hdr);
   1559       }
   1560       return;
   1561     }
   1562   default:
   1563     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
   1564                 "OAuth2.0 login URL returned HTTP status %u\n",
   1565                 (unsigned int) response_code);
   1566     handle_proof_error (ph,
   1567                         j);
   1568     break;
   1569   }
   1570   return_proof_response (ph);
   1571 }
   1572 
   1573 
   1574 /**
   1575  * Check KYC status and return status to human.
   1576  *
   1577  * @param cls the @e cls of this struct with the plugin-specific state
   1578  * @param pd provider configuration details
   1579  * @param connection MHD connection object (for HTTP headers)
   1580  * @param account_id which account to trigger process for
   1581  * @param process_row row in the legitimization processes table the legitimization is for
   1582  * @param provider_user_id user ID (or NULL) the proof is for
   1583  * @param provider_legitimization_id legitimization ID the proof is for
   1584  * @param cb function to call with the result
   1585  * @param cb_cls closure for @a cb
   1586  * @return handle to cancel operation early
   1587  */
   1588 static struct TALER_KYCLOGIC_ProofHandle *
   1589 oauth2_proof (void *cls,
   1590               const struct TALER_KYCLOGIC_ProviderDetails *pd,
   1591               struct MHD_Connection *connection,
   1592               const struct TALER_NormalizedPaytoHashP *account_id,
   1593               uint64_t process_row,
   1594               const char *provider_user_id,
   1595               const char *provider_legitimization_id,
   1596               TALER_KYCLOGIC_ProofCallback cb,
   1597               void *cb_cls)
   1598 {
   1599   struct PluginState *ps = cls;
   1600   struct TALER_KYCLOGIC_ProofHandle *ph;
   1601   const char *code;
   1602 
   1603   GNUNET_break (NULL == provider_user_id);
   1604   ph = GNUNET_new (struct TALER_KYCLOGIC_ProofHandle);
   1605   GNUNET_snprintf (ph->provider_legitimization_id,
   1606                    sizeof (ph->provider_legitimization_id),
   1607                    "%llu",
   1608                    (unsigned long long) process_row);
   1609   if ( (NULL != provider_legitimization_id) &&
   1610        (0 != strcmp (provider_legitimization_id,
   1611                      ph->provider_legitimization_id)))
   1612   {
   1613     GNUNET_break (0);
   1614     GNUNET_free (ph);
   1615     return NULL;
   1616   }
   1617 
   1618   ph->pd = pd;
   1619   ph->connection = connection;
   1620   ph->h_payto = *account_id;
   1621   ph->cb = cb;
   1622   ph->cb_cls = cb_cls;
   1623   code = MHD_lookup_connection_value (connection,
   1624                                       MHD_GET_ARGUMENT_KIND,
   1625                                       "code");
   1626   if (NULL == code)
   1627   {
   1628     const char *err;
   1629     const char *desc;
   1630     const char *euri;
   1631     json_t *body;
   1632 
   1633     err = MHD_lookup_connection_value (connection,
   1634                                        MHD_GET_ARGUMENT_KIND,
   1635                                        "error");
   1636     if (NULL == err)
   1637     {
   1638       GNUNET_break_op (0);
   1639       ph->status = TALER_KYCLOGIC_STATUS_USER_PENDING;
   1640       ph->http_status = MHD_HTTP_BAD_REQUEST;
   1641       body = GNUNET_JSON_PACK (
   1642         GNUNET_JSON_pack_string ("message",
   1643                                  "'code' parameter malformed"),
   1644         TALER_JSON_pack_ec (
   1645           TALER_EC_GENERIC_PARAMETER_MALFORMED));
   1646       GNUNET_break (
   1647         GNUNET_SYSERR !=
   1648         templating_build (ph->connection,
   1649                           &ph->http_status,
   1650                           "oauth2-bad-request",
   1651                           body,
   1652                           &ph->response));
   1653       json_decref (body);
   1654       ph->task = GNUNET_SCHEDULER_add_now (&return_proof_response,
   1655                                            ph);
   1656       return ph;
   1657     }
   1658     desc = MHD_lookup_connection_value (connection,
   1659                                         MHD_GET_ARGUMENT_KIND,
   1660                                         "error_description");
   1661     euri = MHD_lookup_connection_value (connection,
   1662                                         MHD_GET_ARGUMENT_KIND,
   1663                                         "error_uri");
   1664     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
   1665                 "OAuth2 process %llu failed with error `%s'\n",
   1666                 (unsigned long long) process_row,
   1667                 err);
   1668     if (0 == strcasecmp (err,
   1669                          "server_error"))
   1670       ph->status = TALER_KYCLOGIC_STATUS_PROVIDER_FAILED;
   1671     else if (0 == strcasecmp (err,
   1672                               "unauthorized_client"))
   1673       ph->status = TALER_KYCLOGIC_STATUS_FAILED;
   1674     else if (0 == strcasecmp (err,
   1675                               "temporarily_unavailable"))
   1676       ph->status = TALER_KYCLOGIC_STATUS_PENDING;
   1677     else
   1678       ph->status = TALER_KYCLOGIC_STATUS_INTERNAL_ERROR;
   1679     ph->http_status = MHD_HTTP_FORBIDDEN;
   1680     body = GNUNET_JSON_PACK (
   1681       GNUNET_JSON_pack_string ("error",
   1682                                err),
   1683       GNUNET_JSON_pack_allow_null (
   1684         GNUNET_JSON_pack_string ("error_details",
   1685                                  desc)),
   1686       GNUNET_JSON_pack_allow_null (
   1687         GNUNET_JSON_pack_string ("error_uri",
   1688                                  euri)));
   1689     GNUNET_break (
   1690       GNUNET_SYSERR !=
   1691       templating_build (ph->connection,
   1692                         &ph->http_status,
   1693                         "oauth2-authentication-failure",
   1694                         body,
   1695                         &ph->response));
   1696     json_decref (body);
   1697     ph->task = GNUNET_SCHEDULER_add_now (&return_proof_response,
   1698                                          ph);
   1699     return ph;
   1700 
   1701   }
   1702 
   1703   ph->eh = curl_easy_init ();
   1704   GNUNET_assert (NULL != ph->eh);
   1705   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
   1706               "Requesting OAuth 2.0 data via HTTP POST `%s'\n",
   1707               pd->token_url);
   1708   GNUNET_assert (CURLE_OK ==
   1709                  curl_easy_setopt (ph->eh,
   1710                                    CURLOPT_URL,
   1711                                    pd->token_url));
   1712 #if DEBUG
   1713   GNUNET_assert (CURLE_OK ==
   1714                  curl_easy_setopt (ph->eh,
   1715                                    CURLOPT_VERBOSE,
   1716                                    1));
   1717 #endif
   1718   GNUNET_assert (CURLE_OK ==
   1719                  curl_easy_setopt (ph->eh,
   1720                                    CURLOPT_POST,
   1721                                    1));
   1722   {
   1723     char *client_id;
   1724     char *client_secret;
   1725     char *authorization_code;
   1726     char *redirect_uri_encoded;
   1727     char *hps;
   1728 
   1729     hps = GNUNET_STRINGS_data_to_string_alloc (&ph->h_payto,
   1730                                                sizeof (ph->h_payto));
   1731     {
   1732       char *redirect_uri;
   1733 
   1734       GNUNET_asprintf (&redirect_uri,
   1735                        "%skyc-proof/%s",
   1736                        ps->exchange_base_url,
   1737                        &pd->section[strlen ("kyc-provider-")]);
   1738       redirect_uri_encoded = TALER_urlencode (redirect_uri);
   1739       GNUNET_free (redirect_uri);
   1740     }
   1741     GNUNET_assert (NULL != redirect_uri_encoded);
   1742     client_id = curl_easy_escape (ph->eh,
   1743                                   pd->client_id,
   1744                                   0);
   1745     GNUNET_assert (NULL != client_id);
   1746     client_secret = curl_easy_escape (ph->eh,
   1747                                       pd->client_secret,
   1748                                       0);
   1749     GNUNET_assert (NULL != client_secret);
   1750     authorization_code = curl_easy_escape (ph->eh,
   1751                                            code,
   1752                                            0);
   1753     GNUNET_assert (NULL != authorization_code);
   1754     GNUNET_asprintf (&ph->post_body,
   1755                      "client_id=%s&redirect_uri=%s&state=%s&client_secret=%s&code=%s&grant_type=authorization_code",
   1756                      client_id,
   1757                      redirect_uri_encoded,
   1758                      hps,
   1759                      client_secret,
   1760                      authorization_code);
   1761     curl_free (authorization_code);
   1762     curl_free (client_secret);
   1763     GNUNET_free (redirect_uri_encoded);
   1764     GNUNET_free (hps);
   1765     curl_free (client_id);
   1766   }
   1767   GNUNET_assert (CURLE_OK ==
   1768                  curl_easy_setopt (ph->eh,
   1769                                    CURLOPT_POSTFIELDS,
   1770                                    ph->post_body));
   1771   GNUNET_assert (CURLE_OK ==
   1772                  curl_easy_setopt (ph->eh,
   1773                                    CURLOPT_FOLLOWLOCATION,
   1774                                    1L));
   1775   /* limit MAXREDIRS to 5 as a simple security measure against
   1776      a potential infinite loop caused by a malicious target */
   1777   GNUNET_assert (CURLE_OK ==
   1778                  curl_easy_setopt (ph->eh,
   1779                                    CURLOPT_MAXREDIRS,
   1780                                    5L));
   1781 
   1782   ph->job = GNUNET_CURL_job_add (ps->curl_ctx,
   1783                                  ph->eh,
   1784                                  &handle_curl_login_finished,
   1785                                  ph);
   1786   return ph;
   1787 }
   1788 
   1789 
   1790 /**
   1791  * Function to asynchronously return the 404 not found
   1792  * page for the webhook.
   1793  *
   1794  * @param cls the `struct TALER_KYCLOGIC_WebhookHandle *`
   1795  */
   1796 static void
   1797 wh_return_not_found (void *cls)
   1798 {
   1799   struct TALER_KYCLOGIC_WebhookHandle *wh = cls;
   1800   struct MHD_Response *response;
   1801 
   1802   wh->task = NULL;
   1803   response = MHD_create_response_from_buffer_static (0,
   1804                                                      "");
   1805   wh->cb (wh->cb_cls,
   1806           0LLU,
   1807           NULL,
   1808           false,
   1809           NULL,
   1810           NULL,
   1811           NULL,
   1812           TALER_KYCLOGIC_STATUS_KEEP,
   1813           GNUNET_TIME_UNIT_ZERO_ABS,
   1814           NULL,
   1815           MHD_HTTP_NOT_FOUND,
   1816           response);
   1817   GNUNET_free (wh);
   1818 }
   1819 
   1820 
   1821 /**
   1822  * Check KYC status and return result for Webhook.
   1823  *
   1824  * @param cls the @e cls of this struct with the plugin-specific state
   1825  * @param pd provider configuration details
   1826  * @param plc callback to lookup accounts with
   1827  * @param plc_cls closure for @a plc
   1828  * @param http_method HTTP method used for the webhook
   1829  * @param url_path rest of the URL after `/kyc-webhook/$LOGIC/`, as NULL-terminated array
   1830  * @param connection MHD connection object (for HTTP headers)
   1831  * @param body HTTP request body, or NULL if not available
   1832  * @param cb function to call with the result
   1833  * @param cb_cls closure for @a cb
   1834  * @return handle to cancel operation early
   1835  */
   1836 static struct TALER_KYCLOGIC_WebhookHandle *
   1837 oauth2_webhook (void *cls,
   1838                 const struct TALER_KYCLOGIC_ProviderDetails *pd,
   1839                 TALER_KYCLOGIC_ProviderLookupCallback plc,
   1840                 void *plc_cls,
   1841                 const char *http_method,
   1842                 const char *const url_path[],
   1843                 struct MHD_Connection *connection,
   1844                 const json_t *body,
   1845                 TALER_KYCLOGIC_WebhookCallback cb,
   1846                 void *cb_cls)
   1847 {
   1848   struct PluginState *ps = cls;
   1849   struct TALER_KYCLOGIC_WebhookHandle *wh;
   1850 
   1851   (void) pd;
   1852   (void) plc;
   1853   (void) plc_cls;
   1854   (void) http_method;
   1855   (void) url_path;
   1856   (void) connection;
   1857   (void) body;
   1858   GNUNET_break_op (0);
   1859   wh = GNUNET_new (struct TALER_KYCLOGIC_WebhookHandle);
   1860   wh->cb = cb;
   1861   wh->cb_cls = cb_cls;
   1862   wh->ps = ps;
   1863   wh->task = GNUNET_SCHEDULER_add_now (&wh_return_not_found,
   1864                                        wh);
   1865   return wh;
   1866 }
   1867 
   1868 
   1869 /**
   1870  * Cancel KYC webhook execution.
   1871  *
   1872  * @param[in] wh handle of operation to cancel
   1873  */
   1874 static void
   1875 oauth2_webhook_cancel (struct TALER_KYCLOGIC_WebhookHandle *wh)
   1876 {
   1877   GNUNET_SCHEDULER_cancel (wh->task);
   1878   GNUNET_free (wh);
   1879 }
   1880 
   1881 
   1882 /**
   1883  * Initialize OAuth2.0 KYC logic plugin
   1884  *
   1885  * @param cls a configuration instance
   1886  * @return NULL on error, otherwise a `struct TALER_KYCLOGIC_Plugin`
   1887  */
   1888 void *
   1889 libtaler_plugin_kyclogic_oauth2_init (void *cls);
   1890 
   1891 /* declaration to avoid compiler warning */
   1892 void *
   1893 libtaler_plugin_kyclogic_oauth2_init (void *cls)
   1894 {
   1895   const struct GNUNET_CONFIGURATION_Handle *cfg = cls;
   1896   struct TALER_KYCLOGIC_Plugin *plugin;
   1897   struct PluginState *ps;
   1898 
   1899   ps = GNUNET_new (struct PluginState);
   1900   ps->cfg = cfg;
   1901   if (GNUNET_OK !=
   1902       GNUNET_CONFIGURATION_get_value_string (cfg,
   1903                                              "exchange",
   1904                                              "BASE_URL",
   1905                                              &ps->exchange_base_url))
   1906   {
   1907     GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
   1908                                "exchange",
   1909                                "BASE_URL");
   1910     GNUNET_free (ps);
   1911     return NULL;
   1912   }
   1913   ps->curl_ctx
   1914     = GNUNET_CURL_init (&GNUNET_CURL_gnunet_scheduler_reschedule,
   1915                         &ps->curl_rc);
   1916   if (NULL == ps->curl_ctx)
   1917   {
   1918     GNUNET_break (0);
   1919     GNUNET_free (ps->exchange_base_url);
   1920     GNUNET_free (ps);
   1921     return NULL;
   1922   }
   1923   ps->curl_rc = GNUNET_CURL_gnunet_rc_create (ps->curl_ctx);
   1924 
   1925   plugin = GNUNET_new (struct TALER_KYCLOGIC_Plugin);
   1926   plugin->cls = ps;
   1927   plugin->load_configuration
   1928     = &oauth2_load_configuration;
   1929   plugin->unload_configuration
   1930     = &oauth2_unload_configuration;
   1931   plugin->initiate
   1932     = &oauth2_initiate;
   1933   plugin->initiate_cancel
   1934     = &oauth2_initiate_cancel;
   1935   plugin->proof
   1936     = &oauth2_proof;
   1937   plugin->proof_cancel
   1938     = &oauth2_proof_cancel;
   1939   plugin->webhook
   1940     = &oauth2_webhook;
   1941   plugin->webhook_cancel
   1942     = &oauth2_webhook_cancel;
   1943   return plugin;
   1944 }
   1945 
   1946 
   1947 /**
   1948  * Unload authorization plugin
   1949  *
   1950  * @param cls a `struct TALER_KYCLOGIC_Plugin`
   1951  * @return NULL (always)
   1952  */
   1953 void *
   1954 libtaler_plugin_kyclogic_oauth2_done (void *cls);
   1955 
   1956 /* declaration to avoid compiler warning */
   1957 void *
   1958 libtaler_plugin_kyclogic_oauth2_done (void *cls)
   1959 {
   1960   struct TALER_KYCLOGIC_Plugin *plugin = cls;
   1961   struct PluginState *ps = plugin->cls;
   1962 
   1963   if (NULL != ps->curl_ctx)
   1964   {
   1965     GNUNET_CURL_fini (ps->curl_ctx);
   1966     ps->curl_ctx = NULL;
   1967   }
   1968   if (NULL != ps->curl_rc)
   1969   {
   1970     GNUNET_CURL_gnunet_rc_destroy (ps->curl_rc);
   1971     ps->curl_rc = NULL;
   1972   }
   1973   GNUNET_free (ps->exchange_base_url);
   1974   GNUNET_free (ps);
   1975   GNUNET_free (plugin);
   1976   return NULL;
   1977 }