diff options
Diffstat (limited to 'src/kyclogic/plugin_kyclogic_oauth2.c')
-rw-r--r-- | src/kyclogic/plugin_kyclogic_oauth2.c | 1028 |
1 files changed, 720 insertions, 308 deletions
diff --git a/src/kyclogic/plugin_kyclogic_oauth2.c b/src/kyclogic/plugin_kyclogic_oauth2.c index d4aaf4494..3a1f50bcf 100644 --- a/src/kyclogic/plugin_kyclogic_oauth2.c +++ b/src/kyclogic/plugin_kyclogic_oauth2.c @@ -1,6 +1,6 @@ /* This file is part of GNU Taler - Copyright (C) 2022 Taler Systems SA + Copyright (C) 2022-2024 Taler Systems SA Taler is free software; you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software @@ -75,15 +75,21 @@ struct TALER_KYCLOGIC_ProviderDetails char *section; /** + * URL of the Challenger ``/setup`` endpoint for + * approving address validations. NULL if not used. + */ + char *setup_url; + + /** * URL of the OAuth2.0 endpoint for KYC checks. - * (token/auth) */ - char *auth_url; + char *authorize_url; /** * URL of the OAuth2.0 endpoint for KYC checks. + * (token/auth) */ - char *login_url; + char *token_url; /** * URL of the user info access endpoint. @@ -107,16 +113,22 @@ struct TALER_KYCLOGIC_ProviderDetails char *post_kyc_redirect_url; /** - * Template for converting user-data returned by - * the provider into our KYC attribute data. + * Name of the program we use to convert outputs + * from Persona into our JSON inputs. */ - char *attribute_template; + char *conversion_binary; /** * Validity time for a successful KYC process. */ struct GNUNET_TIME_Relative validity; + /** + * Set to true if we are operating in DEBUG + * mode and may return private details in HTML + * responses to make diagnostics easier. + */ + bool debug_mode; }; @@ -148,6 +160,11 @@ struct TALER_KYCLOGIC_InitiateHandle struct GNUNET_SCHEDULER_Task *task; /** + * Handle for the OAuth 2.0 setup request. + */ + struct GNUNET_CURL_Job *job; + + /** * Continuation to call. */ TALER_KYCLOGIC_InitiateCallback cb; @@ -177,6 +194,12 @@ struct TALER_KYCLOGIC_ProofHandle struct MHD_Connection *connection; /** + * Handle to an external process that converts the + * Persona response to our internal format. + */ + struct TALER_JSON_ExternalConversion *ec; + + /** * Hash of the payto URI that this is about. */ struct TALER_PaytoHashP h_payto; @@ -283,13 +306,14 @@ static void oauth2_unload_configuration (struct TALER_KYCLOGIC_ProviderDetails *pd) { GNUNET_free (pd->section); - GNUNET_free (pd->auth_url); - GNUNET_free (pd->login_url); + GNUNET_free (pd->token_url); + GNUNET_free (pd->setup_url); + GNUNET_free (pd->authorize_url); GNUNET_free (pd->info_url); GNUNET_free (pd->client_id); GNUNET_free (pd->client_secret); GNUNET_free (pd->post_kyc_redirect_url); - GNUNET_free (pd->attribute_template); + GNUNET_free (pd->conversion_binary); GNUNET_free (pd); } @@ -324,15 +348,30 @@ oauth2_load_configuration (void *cls, oauth2_unload_configuration (pd); return NULL; } + + if (GNUNET_OK != + GNUNET_CONFIGURATION_get_value_string (ps->cfg, + provider_section_name, + "KYC_OAUTH2_CLIENT_ID", + &s)) + { + GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, + provider_section_name, + "KYC_OAUTH2_CLIENT_ID"); + oauth2_unload_configuration (pd); + return NULL; + } + pd->client_id = s; + if (GNUNET_OK != GNUNET_CONFIGURATION_get_value_string (ps->cfg, provider_section_name, - "KYC_OAUTH2_AUTH_URL", + "KYC_OAUTH2_TOKEN_URL", &s)) { GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, provider_section_name, - "KYC_OAUTH2_AUTH_URL"); + "KYC_OAUTH2_TOKEN_URL"); oauth2_unload_configuration (pd); return NULL; } @@ -346,23 +385,23 @@ oauth2_load_configuration (void *cls, { GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR, provider_section_name, - "KYC_OAUTH2_AUTH_URL", + "KYC_OAUTH2_TOKEN_URL", "not a valid URL"); GNUNET_free (s); oauth2_unload_configuration (pd); return NULL; } - pd->auth_url = s; + pd->token_url = s; if (GNUNET_OK != GNUNET_CONFIGURATION_get_value_string (ps->cfg, provider_section_name, - "KYC_OAUTH2_LOGIN_URL", + "KYC_OAUTH2_AUTHORIZE_URL", &s)) { GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, provider_section_name, - "KYC_OAUTH2_LOGIN_URL"); + "KYC_OAUTH2_AUTHORIZE_URL"); oauth2_unload_configuration (pd); return NULL; } @@ -376,13 +415,42 @@ oauth2_load_configuration (void *cls, { GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR, provider_section_name, - "KYC_OAUTH2_LOGIN_URL", + "KYC_OAUTH2_AUTHORIZE_URL", "not a valid URL"); oauth2_unload_configuration (pd); GNUNET_free (s); return NULL; } - pd->login_url = s; + if (NULL != strchr (s, '#')) + { + const char *extra = strchr (s, '#'); + const char *slash = strrchr (s, '/'); + + if ( (0 != strcmp (extra, + "#setup")) || + (NULL == slash) ) + { + GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR, + provider_section_name, + "KYC_OAUTH2_AUTHORIZE_URL", + "not a valid authorze URL (bad fragment)"); + oauth2_unload_configuration (pd); + GNUNET_free (s); + return NULL; + } + pd->authorize_url = GNUNET_strndup (s, + extra - s); + GNUNET_asprintf (&pd->setup_url, + "%.*s/setup/%s", + (int) (slash - s), + s, + pd->client_id); + GNUNET_free (s); + } + else + { + pd->authorize_url = s; + } if (GNUNET_OK != GNUNET_CONFIGURATION_get_value_string (ps->cfg, @@ -417,20 +485,6 @@ oauth2_load_configuration (void *cls, if (GNUNET_OK != GNUNET_CONFIGURATION_get_value_string (ps->cfg, provider_section_name, - "KYC_OAUTH2_CLIENT_ID", - &s)) - { - GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, - provider_section_name, - "KYC_OAUTH2_CLIENT_ID"); - oauth2_unload_configuration (pd); - return NULL; - } - pd->client_id = s; - - if (GNUNET_OK != - GNUNET_CONFIGURATION_get_value_string (ps->cfg, - provider_section_name, "KYC_OAUTH2_CLIENT_SECRET", &s)) { @@ -459,17 +513,20 @@ oauth2_load_configuration (void *cls, if (GNUNET_OK != GNUNET_CONFIGURATION_get_value_string (ps->cfg, provider_section_name, - "KYC_OAUTH2_ATTRIBUTE_TEMPLATE", - &s)) + "KYC_OAUTH2_CONVERTER_HELPER", + &pd->conversion_binary)) { - GNUNET_log_config_missing (GNUNET_ERROR_TYPE_WARNING, + GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, provider_section_name, - "KYC_OAUTH2_ATTRIBUTE_TEMPLATE"); - } - else - { - pd->attribute_template = s; + "KYC_OAUTH2_CONVERTER_HELPER"); + oauth2_unload_configuration (pd); + return NULL; } + if (GNUNET_OK == + GNUNET_CONFIGURATION_get_value_yesno (ps->cfg, + provider_section_name, + "KYC_OAUTH2_DEBUG_MODE")) + pd->debug_mode = true; return pd; } @@ -480,40 +537,47 @@ oauth2_load_configuration (void *cls, * how to begin the OAuth2.0 checking process to * the client. * - * @param cls a `struct TALER_KYCLOGIC_InitiateHandle *` + * @param ih process to redirect for + * @param authorize_url authorization URL to use */ static void -initiate_task (void *cls) +initiate_with_url (struct TALER_KYCLOGIC_InitiateHandle *ih, + const char *authorize_url) { - struct TALER_KYCLOGIC_InitiateHandle *ih = cls; + const struct TALER_KYCLOGIC_ProviderDetails *pd = ih->pd; struct PluginState *ps = pd->ps; char *hps; char *url; - char *redirect_uri; - char *redirect_uri_encoded; char legi_s[42]; - ih->task = NULL; GNUNET_snprintf (legi_s, sizeof (legi_s), "%llu", (unsigned long long) ih->legitimization_uuid); hps = GNUNET_STRINGS_data_to_string_alloc (&ih->h_payto, sizeof (ih->h_payto)); - GNUNET_asprintf (&redirect_uri, - "%skyc-proof/%s?state=%s", - ps->exchange_base_url, - pd->section, - hps); - redirect_uri_encoded = TALER_urlencode (redirect_uri); - GNUNET_free (redirect_uri); - GNUNET_asprintf (&url, - "%s?response_type=code&client_id=%s&redirect_uri=%s", - pd->login_url, - pd->client_id, - redirect_uri_encoded); - GNUNET_free (redirect_uri_encoded); + { + char *redirect_uri_encoded; + + { + char *redirect_uri; + + GNUNET_asprintf (&redirect_uri, + "%skyc-proof/%s", + ps->exchange_base_url, + pd->section); + redirect_uri_encoded = TALER_urlencode (redirect_uri); + GNUNET_free (redirect_uri); + } + GNUNET_asprintf (&url, + "%s?response_type=code&client_id=%s&redirect_uri=%s&state=%s", + authorize_url, + pd->client_id, + redirect_uri_encoded, + hps); + GNUNET_free (redirect_uri_encoded); + } ih->cb (ih->cb_cls, TALER_EC_NONE, url, @@ -527,6 +591,169 @@ initiate_task (void *cls) /** + * After we are done with the CURL interaction we + * need to update our database state with the information + * retrieved. + * + * @param cls a `struct TALER_KYCLOGIC_InitiateHandle *` + * @param response_code HTTP response code from server, 0 on hard error + * @param response in JSON, NULL if response was not in JSON format + */ +static void +handle_curl_setup_finished (void *cls, + long response_code, + const void *response) +{ + struct TALER_KYCLOGIC_InitiateHandle *ih = cls; + const struct TALER_KYCLOGIC_ProviderDetails *pd = ih->pd; + const json_t *j = response; + + ih->job = NULL; + switch (response_code) + { + case 0: + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "/setup URL failed to return HTTP response\n"); + ih->cb (ih->cb_cls, + TALER_EC_EXCHANGE_KYC_PROOF_BACKEND_INVALID_RESPONSE, + NULL, + NULL, + NULL, + "/setup request to OAuth 2.0 backend returned no response"); + GNUNET_free (ih); + return; + case MHD_HTTP_OK: + { + const char *nonce; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_string ("nonce", + &nonce), + GNUNET_JSON_spec_end () + }; + enum GNUNET_GenericReturnValue res; + const char *emsg; + unsigned int line; + char *url; + + res = GNUNET_JSON_parse (j, + spec, + &emsg, + &line); + if (GNUNET_OK != res) + { + GNUNET_break_op (0); + json_dumpf (j, + stderr, + JSON_INDENT (2)); + ih->cb (ih->cb_cls, + TALER_EC_EXCHANGE_KYC_PROOF_BACKEND_INVALID_RESPONSE, + NULL, + NULL, + NULL, + "Unexpected response from KYC gateway: setup must return a nonce"); + GNUNET_free (ih); + return; + } + GNUNET_asprintf (&url, + "%s/%s", + pd->authorize_url, + nonce); + initiate_with_url (ih, + url); + GNUNET_free (url); + return; + } + break; + default: + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "/setup URL returned HTTP status %u\n", + (unsigned int) response_code); + ih->cb (ih->cb_cls, + TALER_EC_EXCHANGE_KYC_PROOF_BACKEND_INVALID_RESPONSE, + NULL, + NULL, + NULL, + "/setup request to OAuth 2.0 backend returned unexpected HTTP status code"); + GNUNET_free (ih); + return; + } +} + + +/** + * Logic to asynchronously return the response for how to begin the OAuth2.0 + * checking process to the client. May first request a dynamic URL via + * ``/setup`` if configured to use a client-authenticated setup process. + * + * @param cls a `struct TALER_KYCLOGIC_InitiateHandle *` + */ +static void +initiate_task (void *cls) +{ + struct TALER_KYCLOGIC_InitiateHandle *ih = cls; + const struct TALER_KYCLOGIC_ProviderDetails *pd = ih->pd; + struct PluginState *ps = pd->ps; + char *hdr; + struct curl_slist *slist; + CURL *eh; + + ih->task = NULL; + if (NULL == pd->setup_url) + { + initiate_with_url (ih, + pd->authorize_url); + return; + } + eh = curl_easy_init (); + if (NULL == eh) + { + GNUNET_break (0); + ih->cb (ih->cb_cls, + TALER_EC_GENERIC_ALLOCATION_FAILURE, + NULL, + NULL, + NULL, + "curl_easy_init() failed"); + GNUNET_free (ih); + return; + } + GNUNET_assert (CURLE_OK == + curl_easy_setopt (eh, + CURLOPT_URL, + pd->setup_url)); + GNUNET_assert (CURLE_OK == + curl_easy_setopt (eh, + CURLOPT_POST, + 1)); + GNUNET_assert (CURLE_OK == + curl_easy_setopt (eh, + CURLOPT_POSTFIELDS, + "")); + GNUNET_assert (CURLE_OK == + curl_easy_setopt (eh, + CURLOPT_FOLLOWLOCATION, + 1L)); + GNUNET_assert (CURLE_OK == + curl_easy_setopt (eh, + CURLOPT_MAXREDIRS, + 5L)); + GNUNET_asprintf (&hdr, + "%s: Bearer %s", + MHD_HTTP_HEADER_AUTHORIZATION, + pd->client_secret); + slist = curl_slist_append (NULL, + hdr); + ih->job = GNUNET_CURL_job_add2 (ps->curl_ctx, + eh, + slist, + &handle_curl_setup_finished, + ih); + curl_slist_free_all (slist); + GNUNET_free (hdr); +} + + +/** * Initiate KYC check. * * @param cls the @e cls of this struct with the plugin-specific state @@ -573,11 +800,52 @@ oauth2_initiate_cancel (struct TALER_KYCLOGIC_InitiateHandle *ih) GNUNET_SCHEDULER_cancel (ih->task); ih->task = NULL; } + if (NULL != ih->job) + { + GNUNET_CURL_job_cancel (ih->job); + ih->job = NULL; + } GNUNET_free (ih); } /** + * Cancel KYC proof. + * + * @param[in] ph handle of operation to cancel + */ +static void +oauth2_proof_cancel (struct TALER_KYCLOGIC_ProofHandle *ph) +{ + if (NULL != ph->ec) + { + TALER_JSON_external_conversion_stop (ph->ec); + ph->ec = NULL; + } + if (NULL != ph->task) + { + GNUNET_SCHEDULER_cancel (ph->task); + ph->task = NULL; + } + if (NULL != ph->job) + { + GNUNET_CURL_job_cancel (ph->job); + ph->job = NULL; + } + if (NULL != ph->response) + { + MHD_destroy_response (ph->response); + ph->response = NULL; + } + GNUNET_free (ph->provider_user_id); + if (NULL != ph->attributes) + json_decref (ph->attributes); + GNUNET_free (ph->post_body); + GNUNET_free (ph); +} + + +/** * Function called to asynchronously return the final * result to the callback. * @@ -597,10 +865,8 @@ return_proof_response (void *cls) ph->attributes, ph->http_status, ph->response); - GNUNET_free (ph->provider_user_id); - if (NULL != ph->attributes) - json_decref (ph->attributes); - GNUNET_free (ph); + ph->response = NULL; /*Ownership passed to 'ph->cb'!*/ + oauth2_proof_cancel (ph); } @@ -615,18 +881,18 @@ static void handle_proof_error (struct TALER_KYCLOGIC_ProofHandle *ph, const json_t *j) { - const char *msg; - const char *desc; - struct GNUNET_JSON_Specification spec[] = { - GNUNET_JSON_spec_string ("error", - &msg), - GNUNET_JSON_spec_string ("error_description", - &desc), - GNUNET_JSON_spec_end () - }; + enum GNUNET_GenericReturnValue res; { - enum GNUNET_GenericReturnValue res; + const char *msg; + const char *desc; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_string ("error", + &msg), + GNUNET_JSON_spec_string ("error_description", + &desc), + GNUNET_JSON_spec_end () + }; const char *emsg; unsigned int line; @@ -634,146 +900,113 @@ handle_proof_error (struct TALER_KYCLOGIC_ProofHandle *ph, spec, &emsg, &line); - if (GNUNET_OK != res) - { - GNUNET_break_op (0); - ph->status = TALER_KYCLOGIC_STATUS_PROVIDER_FAILED; - ph->response - = TALER_MHD_make_error ( - TALER_EC_EXCHANGE_KYC_PROOF_BACKEND_INVALID_RESPONSE, - "Unexpected response from KYC gateway: proof error"); - ph->http_status - = MHD_HTTP_BAD_GATEWAY; - return; - } } - /* case TALER_EC_EXCHANGE_KYC_PROOF_BACKEND_AUTHORZATION_FAILED, - we MAY want to in the future look at the requested content type - and possibly respond in JSON if indicated. */ + + if (GNUNET_OK != res) { - char *reply; - - GNUNET_asprintf (&reply, - "<html><head><title>%s</title></head><body><h1>%s</h1><p>%s</p></body></html>", - msg, - msg, - desc); - ph->status = TALER_KYCLOGIC_STATUS_USER_ABORTED; - ph->response - = MHD_create_response_from_buffer (strlen (reply), - reply, - MHD_RESPMEM_MUST_COPY); - GNUNET_assert (NULL != ph->response); - GNUNET_free (reply); + json_t *body; + + GNUNET_break_op (0); + ph->status = TALER_KYCLOGIC_STATUS_PROVIDER_FAILED; + ph->http_status + = MHD_HTTP_BAD_GATEWAY; + body = GNUNET_JSON_PACK ( + GNUNET_JSON_pack_allow_null ( + GNUNET_JSON_pack_object_incref ("server_response", + (json_t *) j)), + GNUNET_JSON_pack_bool ("debug", + ph->pd->debug_mode), + TALER_JSON_pack_ec ( + TALER_EC_EXCHANGE_KYC_PROOF_BACKEND_INVALID_RESPONSE)); + GNUNET_assert (NULL != body); + GNUNET_break ( + GNUNET_SYSERR != + TALER_TEMPLATING_build (ph->connection, + &ph->http_status, + "oauth2-authorization-failure-malformed", + NULL, + NULL, + body, + &ph->response)); + json_decref (body); + return; } ph->status = TALER_KYCLOGIC_STATUS_USER_ABORTED; ph->http_status = MHD_HTTP_FORBIDDEN; + GNUNET_break ( + GNUNET_SYSERR != + TALER_TEMPLATING_build (ph->connection, + &ph->http_status, + "oauth2-authorization-failure", + NULL, + NULL, + j, + &ph->response)); } /** - * Convert user data returned by the provider into - * standardized attribute data. + * Type of a callback that receives a JSON @a result. * - * @param pd our provider configuration - * @param data user-data given by the provider - * @return converted KYC attribute data object - */ -static json_t * -data2attributes (const struct TALER_KYCLOGIC_ProviderDetails *pd, - const json_t *data) -{ - json_t *ret; - void *attr_data; - size_t attr_size; - int rv; - json_error_t err; - - if (NULL == pd->attribute_template) - return json_object (); - if (0 != - (rv = TALER_TEMPLATING_fill (pd->attribute_template, - data, - &attr_data, - &attr_size))) - { - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Failed to convert KYC provider data to attributes: %d\n", - rv); - json_dumpf (data, - stderr, - JSON_INDENT (2)); - return NULL; - } - ret = json_loadb (attr_data, - attr_size, - JSON_REJECT_DUPLICATES, - &err); - GNUNET_free (attr_data); - if (NULL == ret) - { - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Failed to parse converted KYC attributes as JSON: %s (at offset %d)\n", - err.text, - err.position); - return NULL; - } - return ret; -} - - -/** - * The request for @a ph succeeded (presumably). - * Call continuation with the result. - * - * @param[in,out] ph request that succeeded - * @param j reply from the server + * @param cls closure with a `struct TALER_KYCLOGIC_ProofHandle *` + * @param status_type how did the process die + * @param code termination status code from the process + * @param attr result some JSON result, NULL if we failed to get an JSON output */ static void -parse_proof_success_reply (struct TALER_KYCLOGIC_ProofHandle *ph, - const json_t *j) +converted_proof_cb (void *cls, + enum GNUNET_OS_ProcessStatusType status_type, + unsigned long code, + const json_t *attr) { - const char *state; - json_t *data; - struct GNUNET_JSON_Specification spec[] = { - GNUNET_JSON_spec_string ("status", - &state), - GNUNET_JSON_spec_json ("data", - &data), - GNUNET_JSON_spec_end () - }; - enum GNUNET_GenericReturnValue res; - const char *emsg; - unsigned int line; + struct TALER_KYCLOGIC_ProofHandle *ph = cls; + const struct TALER_KYCLOGIC_ProviderDetails *pd = ph->pd; - res = GNUNET_JSON_parse (j, - spec, - &emsg, - &line); - if (GNUNET_OK != res) + ph->ec = NULL; + if ( (NULL == attr) || + (0 != code) ) { + json_t *body; + char *msg; + GNUNET_break_op (0); - json_dumpf (j, - stderr, - JSON_INDENT (2)); ph->status = TALER_KYCLOGIC_STATUS_PROVIDER_FAILED; - ph->response - = TALER_MHD_make_error ( - TALER_EC_EXCHANGE_KYC_PROOF_BACKEND_INVALID_RESPONSE, - "Unexpected response from KYC gateway: proof success must contain data and status"); - ph->http_status - = MHD_HTTP_BAD_GATEWAY; - return; - } - if (0 != strcasecmp (state, - "success")) - { - GNUNET_break_op (0); - handle_proof_error (ph, - j); - GNUNET_JSON_parse_free (spec); + ph->http_status = MHD_HTTP_BAD_GATEWAY; + if (0 != code) + GNUNET_asprintf (&msg, + "Attribute converter exited with status %ld", + code); + else + msg = GNUNET_strdup ( + "Attribute converter response was not in JSON format"); + body = GNUNET_JSON_PACK ( + GNUNET_JSON_pack_string ("converter", + pd->conversion_binary), + GNUNET_JSON_pack_allow_null ( + GNUNET_JSON_pack_object_incref ("attributes", + (json_t *) attr)), + GNUNET_JSON_pack_bool ("debug", + ph->pd->debug_mode), + GNUNET_JSON_pack_string ("message", + msg), + TALER_JSON_pack_ec ( + TALER_EC_EXCHANGE_KYC_PROOF_BACKEND_INVALID_RESPONSE)); + GNUNET_free (msg); + GNUNET_break ( + GNUNET_SYSERR != + TALER_TEMPLATING_build (ph->connection, + &ph->http_status, + "oauth2-conversion-failure", + NULL, + NULL, + body, + &ph->response)); + json_decref (body); + ph->task = GNUNET_SCHEDULER_add_now (&return_proof_response, + ph); return; } + { const char *id; struct GNUNET_JSON_Specification ispec[] = { @@ -781,43 +1014,123 @@ parse_proof_success_reply (struct TALER_KYCLOGIC_ProofHandle *ph, &id), GNUNET_JSON_spec_end () }; + enum GNUNET_GenericReturnValue res; + const char *emsg; + unsigned int line; - res = GNUNET_JSON_parse (data, + res = GNUNET_JSON_parse (attr, ispec, &emsg, &line); if (GNUNET_OK != res) { + json_t *body; + GNUNET_break_op (0); - json_dumpf (data, - stderr, - JSON_INDENT (2)); ph->status = TALER_KYCLOGIC_STATUS_PROVIDER_FAILED; - ph->response - = TALER_MHD_make_error ( - TALER_EC_EXCHANGE_KYC_PROOF_BACKEND_INVALID_RESPONSE, - "Unexpected response from KYC gateway: data must contain id"); - ph->http_status - = MHD_HTTP_BAD_GATEWAY; - GNUNET_JSON_parse_free (spec); + ph->http_status = MHD_HTTP_BAD_GATEWAY; + body = GNUNET_JSON_PACK ( + GNUNET_JSON_pack_string ("converter", + pd->conversion_binary), + GNUNET_JSON_pack_string ("message", + "Unexpected response from KYC attribute converter: returned JSON data must contain 'id' field"), + GNUNET_JSON_pack_bool ("debug", + ph->pd->debug_mode), + GNUNET_JSON_pack_object_incref ("attributes", + (json_t *) attr), + TALER_JSON_pack_ec ( + TALER_EC_EXCHANGE_KYC_PROOF_BACKEND_INVALID_RESPONSE)); + GNUNET_break ( + GNUNET_SYSERR != + TALER_TEMPLATING_build (ph->connection, + &ph->http_status, + "oauth2-conversion-failure", + NULL, + NULL, + body, + &ph->response)); + json_decref (body); + ph->task = GNUNET_SCHEDULER_add_now (&return_proof_response, + ph); return; } - ph->status = TALER_KYCLOGIC_STATUS_SUCCESS; - ph->response = MHD_create_response_from_buffer (0, - "", - MHD_RESPMEM_PERSISTENT); - GNUNET_assert (NULL != ph->response); - GNUNET_break (MHD_YES == - MHD_add_response_header ( - ph->response, - MHD_HTTP_HEADER_LOCATION, - ph->pd->post_kyc_redirect_url)); - ph->http_status = MHD_HTTP_SEE_OTHER; ph->provider_user_id = GNUNET_strdup (id); } - ph->attributes = data2attributes (ph->pd, - data); - GNUNET_JSON_parse_free (spec); + ph->status = TALER_KYCLOGIC_STATUS_SUCCESS; + ph->response = MHD_create_response_from_buffer (0, + "", + MHD_RESPMEM_PERSISTENT); + GNUNET_assert (NULL != ph->response); + GNUNET_break (MHD_YES == + MHD_add_response_header ( + ph->response, + MHD_HTTP_HEADER_LOCATION, + ph->pd->post_kyc_redirect_url)); + ph->http_status = MHD_HTTP_SEE_OTHER; + ph->attributes = json_incref ((json_t *) attr); + ph->task = GNUNET_SCHEDULER_add_now (&return_proof_response, + ph); +} + + +/** + * The request for @a ph succeeded (presumably). + * Call continuation with the result. + * + * @param[in,out] ph request that succeeded + * @param j reply from the server + */ +static void +parse_proof_success_reply (struct TALER_KYCLOGIC_ProofHandle *ph, + const json_t *j) +{ + const struct TALER_KYCLOGIC_ProviderDetails *pd = ph->pd; + + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Calling converter `%s' with JSON\n", + pd->conversion_binary); + json_dumpf (j, + stderr, + JSON_INDENT (2)); + ph->ec = TALER_JSON_external_conversion_start ( + j, + &converted_proof_cb, + ph, + pd->conversion_binary, + pd->conversion_binary, + NULL); + if (NULL != ph->ec) + return; + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Failed to start KYCAID conversion helper `%s'\n", + pd->conversion_binary); + ph->status = TALER_KYCLOGIC_STATUS_INTERNAL_ERROR; + ph->http_status = MHD_HTTP_INTERNAL_SERVER_ERROR; + { + json_t *body; + + body = GNUNET_JSON_PACK ( + GNUNET_JSON_pack_string ("converter", + pd->conversion_binary), + GNUNET_JSON_pack_bool ("debug", + ph->pd->debug_mode), + GNUNET_JSON_pack_string ("message", + "Failed to launch KYC conversion helper process."), + TALER_JSON_pack_ec ( + TALER_EC_EXCHANGE_GENERIC_KYC_CONVERTER_FAILED)); + GNUNET_break ( + GNUNET_SYSERR != + TALER_TEMPLATING_build (ph->connection, + &ph->http_status, + "oauth2-conversion-failure", + NULL, + NULL, + body, + &ph->response)); + json_decref (body); + } + ph->task = GNUNET_SCHEDULER_add_now (&return_proof_response, + ph); } @@ -841,10 +1154,34 @@ handle_curl_proof_finished (void *cls, ph->job = NULL; switch (response_code) { + case 0: + { + json_t *body; + + ph->status = TALER_KYCLOGIC_STATUS_PROVIDER_FAILED; + ph->http_status = MHD_HTTP_BAD_GATEWAY; + + body = GNUNET_JSON_PACK ( + GNUNET_JSON_pack_string ("message", + "No response from KYC gateway"), + TALER_JSON_pack_ec ( + TALER_EC_EXCHANGE_KYC_PROOF_BACKEND_INVALID_RESPONSE)); + GNUNET_break ( + GNUNET_SYSERR != + TALER_TEMPLATING_build (ph->connection, + &ph->http_status, + "oauth2-provider-failure", + NULL, + NULL, + body, + &ph->response)); + json_decref (body); + } + break; case MHD_HTTP_OK: parse_proof_success_reply (ph, j); - break; + return; default: GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "OAuth2.0 info URL returned HTTP status %u\n", @@ -893,13 +1230,11 @@ handle_curl_login_finished (void *cls, GNUNET_JSON_spec_mark_optional ( GNUNET_JSON_spec_uint64 ("expires_in", &expires_in_s), - &no_expires - ), + &no_expires), GNUNET_JSON_spec_mark_optional ( GNUNET_JSON_spec_string ("refresh_token", &refresh_token), - &no_refresh - ), + &no_refresh), GNUNET_JSON_spec_end () }; CURL *eh; @@ -915,26 +1250,59 @@ handle_curl_login_finished (void *cls, &line); if (GNUNET_OK != res) { + json_t *body; + GNUNET_break_op (0); - ph->response - = TALER_MHD_make_error ( - TALER_EC_EXCHANGE_KYC_PROOF_BACKEND_INVALID_RESPONSE, - "Unexpected response from KYC gateway: login finished"); ph->http_status = MHD_HTTP_BAD_GATEWAY; + body = GNUNET_JSON_PACK ( + GNUNET_JSON_pack_object_incref ("server_response", + (json_t *) j), + GNUNET_JSON_pack_bool ("debug", + ph->pd->debug_mode), + GNUNET_JSON_pack_string ("message", + "Unexpected response from KYC gateway: required fields missing or malformed"), + TALER_JSON_pack_ec ( + TALER_EC_EXCHANGE_KYC_PROOF_BACKEND_INVALID_RESPONSE)); + GNUNET_break ( + GNUNET_SYSERR != + TALER_TEMPLATING_build (ph->connection, + &ph->http_status, + "oauth2-provider-failure", + NULL, + NULL, + body, + &ph->response)); + json_decref (body); break; } } if (0 != strcasecmp (token_type, "bearer")) { + json_t *body; + GNUNET_break_op (0); - ph->response - = TALER_MHD_make_error ( - TALER_EC_EXCHANGE_KYC_PROOF_BACKEND_INVALID_RESPONSE, - "Unexpected token type in response from KYC gateway"); - ph->http_status - = MHD_HTTP_BAD_GATEWAY; + ph->http_status = MHD_HTTP_BAD_GATEWAY; + body = GNUNET_JSON_PACK ( + GNUNET_JSON_pack_object_incref ("server_response", + (json_t *) j), + GNUNET_JSON_pack_bool ("debug", + ph->pd->debug_mode), + GNUNET_JSON_pack_string ("message", + "Unexpected 'token_type' in response from KYC gateway: 'bearer' token required"), + TALER_JSON_pack_ec ( + TALER_EC_EXCHANGE_KYC_PROOF_BACKEND_INVALID_RESPONSE)); + GNUNET_break ( + GNUNET_SYSERR != + TALER_TEMPLATING_build (ph->connection, + &ph->http_status, + "oauth2-provider-failure", + NULL, + NULL, + body, + &ph->response)); + json_decref (body); break; } @@ -949,28 +1317,34 @@ handle_curl_login_finished (void *cls, (NULL != strchr (access_token, ';')) ) { + json_t *body; + GNUNET_break_op (0); - ph->response - = TALER_MHD_make_error ( - TALER_EC_EXCHANGE_KYC_PROOF_BACKEND_INVALID_RESPONSE, - "Illegal character in access token"); - ph->http_status - = MHD_HTTP_BAD_GATEWAY; + ph->http_status = MHD_HTTP_BAD_GATEWAY; + body = GNUNET_JSON_PACK ( + GNUNET_JSON_pack_object_incref ("server_response", + (json_t *) j), + GNUNET_JSON_pack_bool ("debug", + ph->pd->debug_mode), + GNUNET_JSON_pack_string ("message", + "Illegal character in access token"), + TALER_JSON_pack_ec ( + TALER_EC_EXCHANGE_KYC_PROOF_BACKEND_INVALID_RESPONSE)); + GNUNET_break ( + GNUNET_SYSERR != + TALER_TEMPLATING_build (ph->connection, + &ph->http_status, + "oauth2-provider-failure", + NULL, + NULL, + body, + &ph->response)); + json_decref (body); break; } eh = curl_easy_init (); - if (NULL == eh) - { - GNUNET_break_op (0); - ph->response - = TALER_MHD_make_error ( - TALER_EC_GENERIC_ALLOCATION_FAILURE, - "curl_easy_init"); - ph->http_status - = MHD_HTTP_INTERNAL_SERVER_ERROR; - break; - } + GNUNET_assert (NULL != eh); GNUNET_assert (CURLE_OK == curl_easy_setopt (eh, CURLOPT_URL, @@ -1061,42 +1435,104 @@ oauth2_proof (void *cls, "code"); if (NULL == code) { - GNUNET_break_op (0); - ph->status = TALER_KYCLOGIC_STATUS_USER_PENDING; - ph->http_status = MHD_HTTP_BAD_REQUEST; - ph->response = TALER_MHD_make_error ( - TALER_EC_GENERIC_PARAMETER_MALFORMED, - "code"); + const char *err; + const char *desc; + const char *euri; + json_t *body; + + err = MHD_lookup_connection_value (connection, + MHD_GET_ARGUMENT_KIND, + "error"); + if (NULL == err) + { + GNUNET_break_op (0); + ph->status = TALER_KYCLOGIC_STATUS_USER_PENDING; + ph->http_status = MHD_HTTP_BAD_REQUEST; + body = GNUNET_JSON_PACK ( + GNUNET_JSON_pack_string ("message", + "'code' parameter malformed"), + TALER_JSON_pack_ec ( + TALER_EC_GENERIC_PARAMETER_MALFORMED)); + GNUNET_break ( + GNUNET_SYSERR != + TALER_TEMPLATING_build (ph->connection, + &ph->http_status, + "oauth2-bad-request", + NULL, + NULL, + body, + &ph->response)); + json_decref (body); + ph->task = GNUNET_SCHEDULER_add_now (&return_proof_response, + ph); + return ph; + } + desc = MHD_lookup_connection_value (connection, + MHD_GET_ARGUMENT_KIND, + "error_description"); + euri = MHD_lookup_connection_value (connection, + MHD_GET_ARGUMENT_KIND, + "error_uri"); + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "OAuth2 process %llu failed with error `%s'\n", + (unsigned long long) process_row, + err); + if (0 == strcmp (err, + "server_error")) + ph->status = TALER_KYCLOGIC_STATUS_PROVIDER_FAILED; + else if (0 == strcmp (err, + "unauthorized_client")) + ph->status = TALER_KYCLOGIC_STATUS_FAILED; + else if (0 == strcmp (err, + "temporarily_unavailable")) + ph->status = TALER_KYCLOGIC_STATUS_PENDING; + else + ph->status = TALER_KYCLOGIC_STATUS_INTERNAL_ERROR; + ph->http_status = MHD_HTTP_FORBIDDEN; + body = GNUNET_JSON_PACK ( + GNUNET_JSON_pack_string ("error", + err), + GNUNET_JSON_pack_allow_null ( + GNUNET_JSON_pack_string ("error_details", + desc)), + GNUNET_JSON_pack_allow_null ( + GNUNET_JSON_pack_string ("error_uri", + euri))); + GNUNET_break ( + GNUNET_SYSERR != + TALER_TEMPLATING_build (ph->connection, + &ph->http_status, + "oauth2-authentication-failure", + NULL, + NULL, + body, + &ph->response)); + json_decref (body); ph->task = GNUNET_SCHEDULER_add_now (&return_proof_response, ph); return ph; - } - ph->eh = curl_easy_init (); - if (NULL == ph->eh) - { - GNUNET_break (0); - ph->status = TALER_KYCLOGIC_STATUS_USER_PENDING; - ph->http_status = MHD_HTTP_INTERNAL_SERVER_ERROR; - ph->response = TALER_MHD_make_error ( - TALER_EC_GENERIC_ALLOCATION_FAILURE, - "curl_easy_init"); - ph->task = GNUNET_SCHEDULER_add_now (&return_proof_response, - ph); - return ph; } + ph->eh = curl_easy_init (); + GNUNET_assert (NULL != ph->eh); + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Requesting OAuth 2.0 data via HTTP POST `%s'\n", + pd->token_url); GNUNET_assert (CURLE_OK == curl_easy_setopt (ph->eh, CURLOPT_URL, - pd->auth_url)); + pd->token_url)); + GNUNET_assert (CURLE_OK == + curl_easy_setopt (ph->eh, + CURLOPT_VERBOSE, + 1)); GNUNET_assert (CURLE_OK == curl_easy_setopt (ph->eh, CURLOPT_POST, 1)); { char *client_id; - char *redirect_uri; char *client_secret; char *authorization_code; char *redirect_uri_encoded; @@ -1104,13 +1540,16 @@ oauth2_proof (void *cls, hps = GNUNET_STRINGS_data_to_string_alloc (&ph->h_payto, sizeof (ph->h_payto)); - GNUNET_asprintf (&redirect_uri, - "%skyc-proof/%s?state=%s", - ps->exchange_base_url, - pd->section, - hps); - redirect_uri_encoded = TALER_urlencode (redirect_uri); - GNUNET_free (redirect_uri); + { + char *redirect_uri; + + GNUNET_asprintf (&redirect_uri, + "%skyc-proof/%s", + ps->exchange_base_url, + pd->section); + redirect_uri_encoded = TALER_urlencode (redirect_uri); + GNUNET_free (redirect_uri); + } GNUNET_assert (NULL != redirect_uri_encoded); client_id = curl_easy_escape (ph->eh, pd->client_id, @@ -1125,9 +1564,10 @@ oauth2_proof (void *cls, 0); GNUNET_assert (NULL != authorization_code); GNUNET_asprintf (&ph->post_body, - "client_id=%s&redirect_uri=%s&client_secret=%s&code=%s&grant_type=authorization_code", + "client_id=%s&redirect_uri=%s&state=%s&client_secret=%s&code=%s&grant_type=authorization_code", client_id, redirect_uri_encoded, + hps, client_secret, authorization_code); curl_free (authorization_code); @@ -1160,34 +1600,6 @@ oauth2_proof (void *cls, /** - * Cancel KYC proof. - * - * @param[in] ph handle of operation to cancel - */ -static void -oauth2_proof_cancel (struct TALER_KYCLOGIC_ProofHandle *ph) -{ - if (NULL != ph->task) - { - GNUNET_SCHEDULER_cancel (ph->task); - ph->task = NULL; - } - if (NULL != ph->job) - { - GNUNET_CURL_job_cancel (ph->job); - ph->job = NULL; - } - if (NULL != ph->response) - { - MHD_destroy_response (ph->response); - ph->response = NULL; - } - GNUNET_free (ph->post_body); - GNUNET_free (ph); -} - - -/** * Function to asynchronously return the 404 not found * page for the webhook. * |