LCOV - code coverage report
Current view: top level - kyclogic - plugin_kyclogic_oauth2.c (source / functions) Coverage Total Hit
Test: coverage.info Lines: 56.5 % 605 342
Test Date: 2026-10-17 15:46:49 Functions: 83.3 % 24 20

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

Generated by: LCOV version 2.0-1