From e5ead880579cbac93353b72e96221c84206a7403 Mon Sep 17 00:00:00 2001 From: Christian Grothoff Date: Mon, 15 Nov 2021 20:00:45 +0100 Subject: [PATCH] complete oauth logic (in theory) --- src/exchange/taler-exchange-httpd.c | 28 ++ src/exchange/taler-exchange-httpd.h | 7 +- src/exchange/taler-exchange-httpd_kyc-proof.c | 305 ++++++++++++++---- src/exchangedb/exchange-0001.sql | 2 +- src/exchangedb/plugin_exchangedb_postgres.c | 8 +- src/include/taler_exchangedb_plugin.h | 4 +- src/testing/testing_api_cmd_oauth.c | 22 ++ 7 files changed, 305 insertions(+), 71 deletions(-) diff --git a/src/exchange/taler-exchange-httpd.c b/src/exchange/taler-exchange-httpd.c index 08f2ba471..c29984e2d 100644 --- a/src/exchange/taler-exchange-httpd.c +++ b/src/exchange/taler-exchange-httpd.c @@ -1194,6 +1194,34 @@ parse_kyc_oauth_cfg (void) } TEH_kyc_config.details.oauth2.url = s; + if (GNUNET_OK != + GNUNET_CONFIGURATION_get_value_string (TEH_cfg, + "exchange-kyc-oauth2", + "KYC_INFO_URL", + &s)) + { + GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, + "exchange-kyc-oauth2", + "KYC_INFO_URL"); + return GNUNET_SYSERR; + } + if ( (! TALER_url_valid_charset (s)) || + ( (0 != strncasecmp (s, + "http://", + strlen ("http://"))) && + (0 != strncasecmp (s, + "https://", + strlen ("https://"))) ) ) + { + GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR, + "exchange-kyc-oauth2", + "KYC_INFO_URL", + "not a valid URL"); + GNUNET_free (s); + return GNUNET_SYSERR; + } + TEH_kyc_config.details.oauth2.info_url = s; + if (GNUNET_OK != GNUNET_CONFIGURATION_get_value_string (TEH_cfg, "exchange-kyc-oauth2", diff --git a/src/exchange/taler-exchange-httpd.h b/src/exchange/taler-exchange-httpd.h index f66626b3d..d52ce1a0f 100644 --- a/src/exchange/taler-exchange-httpd.h +++ b/src/exchange/taler-exchange-httpd.h @@ -91,10 +91,15 @@ struct TEH_KycOptions { /** - * URL of tue OAuth2.0 endpoint for KYC checks. + * URL of the OAuth2.0 endpoint for KYC checks. */ char *url; + /** + * URL of the user info access endpoint. + */ + char *info_url; + /** * Our client ID for OAuth2.0. */ diff --git a/src/exchange/taler-exchange-httpd_kyc-proof.c b/src/exchange/taler-exchange-httpd_kyc-proof.c index fdf5ade54..7bd9fdaa6 100644 --- a/src/exchange/taler-exchange-httpd_kyc-proof.c +++ b/src/exchange/taler-exchange-httpd_kyc-proof.c @@ -72,6 +72,11 @@ struct KycProofContext */ char *post_body; + /** + * User ID extracted from the OAuth 2.0 service, or NULL. + */ + char *id; + /** * Payment target this is about. */ @@ -165,13 +170,186 @@ persist_kyc_ok (void *cls, struct KycProofContext *kpc = cls; return TEH_plugin->set_kyc_ok (TEH_plugin->cls, - kpc->payment_target_uuid); + kpc->payment_target_uuid, + kpc->id); +} + + +/** + * The request for @a kpc failed. We may have gotten a useful error + * message in @a j. Generate a failure response. + * + * @param[in,out] kpc request that failed + * @param j reply from the server (or NULL) + */ +static void +handle_error (struct KycProofContext *kpc, + 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; + const char *emsg; + unsigned int line; + + res = GNUNET_JSON_parse (j, + spec, + &emsg, + &line); + if (GNUNET_OK != res) + { + GNUNET_break_op (0); + kpc->response + = TALER_MHD_make_error ( + TALER_EC_EXCHANGE_KYC_PROOF_BACKEND_INVALID_RESPONSE, + "Unexpected response from KYC gateway"); + kpc->response_code + = 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. */ + { + char *reply; + + GNUNET_asprintf (&reply, + "%s

%s

%s

", + msg, + msg, + desc); + kpc->response + = MHD_create_response_from_buffer (strlen (reply), + reply, + MHD_RESPMEM_MUST_COPY); + GNUNET_assert (NULL != kpc->response); + GNUNET_free (reply); + } + kpc->response_code = MHD_HTTP_FORBIDDEN; +} + + +/** + * The request for @a kpc succeeded (presumably). + * Parse the user ID and store it in @a kpc (if possible). + * + * @param[in,out] kpc request that succeeded + * @param j reply from the server + */ +static void +parse_success_reply (struct KycProofContext *kpc, + const json_t *j) +{ + 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; + + res = GNUNET_JSON_parse (j, + spec, + &emsg, + &line); + if (GNUNET_OK != res) + { + GNUNET_break_op (0); + kpc->response + = TALER_MHD_make_error ( + TALER_EC_EXCHANGE_KYC_PROOF_BACKEND_INVALID_RESPONSE, + "Unexpected response from KYC gateway"); + kpc->response_code + = MHD_HTTP_BAD_GATEWAY; + return; + } + if (0 != strcasecmp (state, + "success")) + { + GNUNET_break_op (0); + handle_error (kpc, + j); + return; + } + { + const char *id; + struct GNUNET_JSON_Specification ispec[] = { + GNUNET_JSON_spec_string ("id", + &id), + GNUNET_JSON_spec_end () + }; + + res = GNUNET_JSON_parse (data, + ispec, + &emsg, + &line); + if (GNUNET_OK != res) + { + GNUNET_break_op (0); + kpc->response + = TALER_MHD_make_error ( + TALER_EC_EXCHANGE_KYC_PROOF_BACKEND_INVALID_RESPONSE, + "Unexpected response from KYC gateway"); + kpc->response_code + = MHD_HTTP_BAD_GATEWAY; + return; + } + kpc->id = GNUNET_strdup (id); + } } /** * After we are done with the CURL interaction we - * need to update our database state. + * need to update our database state with the information + * retrieved. + * + * @param cls our `struct KycProofContext` + * @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_fetch_finished (void *cls, + long response_code, + const void *response) +{ + struct KycProofContext *kpc = cls; + const json_t *j = response; + + kpc->job = NULL; + switch (response_code) + { + case MHD_HTTP_OK: + parse_success_reply (kpc, + j); + break; + default: + handle_error (kpc, + j); + break; + } + kpc_resume (kpc); +} + + +/** + * After we are done with the CURL interaction we + * need to fetch the user's account details. * * @param cls our `struct KycProofContext` * @param response_code HTTP response code from server, 0 on hard error @@ -205,6 +383,7 @@ handle_curl_login_finished (void *cls, &refresh_token), GNUNET_JSON_spec_end () }; + CURL *eh; { enum GNUNET_GenericReturnValue res; @@ -224,8 +403,7 @@ handle_curl_login_finished (void *cls, "Unexpected response from KYC gateway"); kpc->response_code = MHD_HTTP_BAD_GATEWAY; - kpc_resume (kpc); - return; + break; } } if (0 != strcasecmp (token_type, @@ -238,74 +416,72 @@ handle_curl_login_finished (void *cls, "Unexpected token type in response from KYC gateway"); kpc->response_code = MHD_HTTP_BAD_GATEWAY; - kpc_resume (kpc); - return; + break; } - /* TODO: Here we might want to keep something to persist in the DB, and - possibly use the access_token to download information we should - persist; then continue! */ + /* We guard against a few characters that could + conceivably be abused to mess with the HTTP header */ + if ( (NULL != strchr (access_token, + '\n')) || + (NULL != strchr (access_token, + '\r')) || + (NULL != strchr (access_token, + ' ')) || + (NULL != strchr (access_token, + ';')) ) + { + GNUNET_break_op (0); + kpc->response + = TALER_MHD_make_error ( + TALER_EC_EXCHANGE_KYC_PROOF_BACKEND_INVALID_RESPONSE, + "Illegal character in access token"); + kpc->response_code + = MHD_HTTP_BAD_GATEWAY; + break; + } - kpc_resume (kpc); + eh = curl_easy_init (); + if (NULL == eh) + { + GNUNET_break_op (0); + kpc->response + = TALER_MHD_make_error ( + TALER_EC_GENERIC_ALLOCATION_FAILURE, + "curl_easy_init"); + kpc->response_code + = MHD_HTTP_INTERNAL_SERVER_ERROR; + break; + } + GNUNET_assert (CURLE_OK == + curl_easy_setopt (eh, + CURLOPT_URL, + TEH_kyc_config.details.oauth2.info_url)); + { + char *hdr; + struct curl_slist *slist; + + GNUNET_asprintf (&hdr, + "%s: Bearer %s", + MHD_HTTP_HEADER_AUTHORIZATION, + access_token); + slist = curl_slist_append (NULL, + hdr); + kpc->job = GNUNET_CURL_job_add2 (TEH_curl_ctx, + eh, + slist, + &handle_curl_fetch_finished, + kpc); + curl_slist_free_all (slist); + GNUNET_free (hdr); + } return; } default: - { - 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; - const char *emsg; - unsigned int line; - - res = GNUNET_JSON_parse (j, - spec, - &emsg, - &line); - if (GNUNET_OK != res) - { - GNUNET_break_op (0); - kpc->response - = TALER_MHD_make_error ( - TALER_EC_EXCHANGE_KYC_PROOF_BACKEND_INVALID_RESPONSE, - "Unexpected response from KYC gateway"); - kpc->response_code - = MHD_HTTP_BAD_GATEWAY; - kpc_resume (kpc); - 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. */ - { - char *reply; - - GNUNET_asprintf (&reply, - "%s

%s

%s

", - msg, - msg, - desc); - kpc->response - = MHD_create_response_from_buffer (strlen (reply), - reply, - MHD_RESPMEM_MUST_COPY); - GNUNET_assert (NULL != kpc->response); - GNUNET_free (reply); - } - kpc->response_code = MHD_HTTP_FORBIDDEN; - kpc_resume (kpc); - } + handle_error (kpc, + j); break; } + kpc_resume (kpc); } @@ -331,6 +507,7 @@ clean_kpc (struct TEH_RequestContext *rc) } GNUNET_free (kpc->post_body); GNUNET_free (kpc->token_url); + GNUNET_free (kpc->id); GNUNET_free (kpc); } diff --git a/src/exchangedb/exchange-0001.sql b/src/exchangedb/exchange-0001.sql index 439521a72..73a371f3b 100644 --- a/src/exchangedb/exchange-0001.sql +++ b/src/exchangedb/exchange-0001.sql @@ -71,7 +71,7 @@ CREATE TABLE IF NOT EXISTS wire_targets ,h_payto BYTEA NOT NULL CHECK (LENGTH(h_payto)=64) ,payto_uri VARCHAR NOT NULL ,kyc_ok BOOLEAN NOT NULL DEFAULT (FALSE) -,oauth_username VARCHAR +,external_id VARCHAR ,PRIMARY KEY (h_payto) ); COMMENT ON TABLE wire_targets diff --git a/src/exchangedb/plugin_exchangedb_postgres.c b/src/exchangedb/plugin_exchangedb_postgres.c index 6e77cb232..a5066e883 100644 --- a/src/exchangedb/plugin_exchangedb_postgres.c +++ b/src/exchangedb/plugin_exchangedb_postgres.c @@ -383,8 +383,9 @@ prepare_statements (struct PostgresClosure *pg) "set_kyc_ok", "UPDATE wire_targets" " SET kyc_ok=TRUE" + ",external_id=$2" " WHERE wire_target_serial_id=$1", - 1), + 2), GNUNET_PQ_make_prepare ( "get_kyc_h_payto", "SELECT" @@ -3799,17 +3800,18 @@ postgres_reserves_get (void *cls, * * @param cls the @e cls of this struct with the plugin-specific state * @param payment_target_uuid which account has been checked - * @param ... possibly additional data to persist (TODO) + * @param id external ID to persist * @return transaction status */ static enum GNUNET_DB_QueryStatus postgres_set_kyc_ok (void *cls, uint64_t payment_target_uuid, - ...) + const char *id) { struct PostgresClosure *pg = cls; struct GNUNET_PQ_QueryParam params[] = { GNUNET_PQ_query_param_uint64 (&payment_target_uuid), + GNUNET_PQ_query_param_string (id), GNUNET_PQ_query_param_end }; struct TALER_KycCompletedEventP rep = { diff --git a/src/include/taler_exchangedb_plugin.h b/src/include/taler_exchangedb_plugin.h index 420e1e1e0..bf8a099f7 100644 --- a/src/include/taler_exchangedb_plugin.h +++ b/src/include/taler_exchangedb_plugin.h @@ -2376,13 +2376,13 @@ struct TALER_EXCHANGEDB_Plugin * * @param cls the @e cls of this struct with the plugin-specific state * @param payment_target_uuid which account has been checked - * @param ... possibly additional data to persist (TODO) + * @param id ID data to persist * @return transaction status */ enum GNUNET_DB_QueryStatus (*set_kyc_ok)(void *cls, uint64_t payment_target_uuid, - ...); + const char *id); /** diff --git a/src/testing/testing_api_cmd_oauth.c b/src/testing/testing_api_cmd_oauth.c index b71cc8386..64cb6c031 100644 --- a/src/testing/testing_api_cmd_oauth.c +++ b/src/testing/testing_api_cmd_oauth.c @@ -169,6 +169,28 @@ handler_cb (void *cls, unsigned int hc; json_t *body; + if (0 == strcasecmp (method, + MHD_HTTP_METHOD_GET)) + { + body = GNUNET_JSON_PACK ( + GNUNET_JSON_pack_string ( + "status", + "success"), + GNUNET_JSON_pack_object_steal ( + "data", + GNUNET_JSON_PACK ( + GNUNET_JSON_pack_string ("id", + "XXXID12345678")))); + return TALER_MHD_reply_json_steal (connection, + body, + MHD_HTTP_OK); + } + if (0 != strcasecmp (method, + MHD_HTTP_METHOD_POST)) + { + GNUNET_break (0); + return MHD_NO; + } if (NULL == rc) { rc = GNUNET_new (struct RequestCtx);