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


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