diff options
author | Christian Grothoff <christian@grothoff.org> | 2021-10-19 21:02:04 +0200 |
---|---|---|
committer | Christian Grothoff <christian@grothoff.org> | 2021-10-19 21:02:10 +0200 |
commit | 778a402d07706462818e9c9d01520fd3d8d238d8 (patch) | |
tree | 18ffbbb763cbeaf05de595e461317d2387ae60ec /src/exchange/taler-exchange-httpd_kyc-proof.c | |
parent | fa30a132a53196eec1ac731e332a075ba8b93991 (diff) | |
download | exchange-778a402d07706462818e9c9d01520fd3d8d238d8.tar.gz exchange-778a402d07706462818e9c9d01520fd3d8d238d8.tar.bz2 exchange-778a402d07706462818e9c9d01520fd3d8d238d8.zip |
-implement more of the KYC handlers
Diffstat (limited to 'src/exchange/taler-exchange-httpd_kyc-proof.c')
-rw-r--r-- | src/exchange/taler-exchange-httpd_kyc-proof.c | 481 |
1 files changed, 443 insertions, 38 deletions
diff --git a/src/exchange/taler-exchange-httpd_kyc-proof.c b/src/exchange/taler-exchange-httpd_kyc-proof.c index be7fc50fe..842e5dfd2 100644 --- a/src/exchange/taler-exchange-httpd_kyc-proof.c +++ b/src/exchange/taler-exchange-httpd_kyc-proof.c @@ -36,10 +36,100 @@ struct KycProofContext { + /** + * Kept in a DLL while suspended. + */ + struct KycProofContext *next; + + /** + * Kept in a DLL while suspended. + */ + struct KycProofContext *prev; + + /** + * Details about the connection we are processing. + */ + struct TEH_RequestContext *rc; + + /** + * Handle for the OAuth 2.0 CURL request. + */ + struct GNUNET_CURL_Job *job; + + /** + * OAuth 2.0 authorization code. + */ + const char *authorization_code; + + /** + * OAuth 2.0 token URL we are using for the + * request. + */ + char *token_url; + + /** + * Body of the POST request. + */ + char *post_body; + + /** + * Payment target this is about. + */ + unsigned long long payment_target_uuid; + + /** + * HTTP response to return. + */ + struct MHD_Response *response; + + /** + * HTTP response code to return. + */ + unsigned int response_code; + + /** + * #GNUNET_YES if we are suspended, + * #GNUNET_NO if not. + * #GNUNET_SYSERR if we had some error. + */ + enum GNUNET_GenericReturnValue suspended; + }; /** + * Contexts are kept in a DLL while suspended. + */ +static struct KycProofContext *kpc_head; + +/** + * Contexts are kept in a DLL while suspended. + */ +static struct KycProofContext *kpc_tail; + + +void +TEH_kyc_proof_cleanup (void) +{ + struct KycProofContext *kpc; + + while (NULL != (kpc = kpc_head)) + { + if (NULL != kpc->job) + { + GNUNET_CURL_job_cancel (kpc->job); + kpc->job = NULL; + } + GNUNET_CONTAINER_DLL_remove (kpc_head, + kpc_tail, + kpc); + kpc->suspended = GNUNET_NO; + MHD_resume_connection (kpc->rc->connection); + } +} + + +/** * Function implementing database transaction to check proof's KYC status. * Runs the transaction logic; IF it returns a non-error code, the transaction * logic MUST NOT queue a MHD response. IF it returns an hard error, the @@ -54,14 +144,185 @@ struct KycProofContext * @return transaction status */ static enum GNUNET_DB_QueryStatus -proof_kyc_check (void *cls, - struct MHD_Connection *connection, - MHD_RESULT *mhd_ret) +persist_kyc_ok (void *cls, + struct MHD_Connection *connection, + MHD_RESULT *mhd_ret) { struct KycProofContext *kpc = cls; - (void) kpc; // FIXME: do work here! - return -2; + return TEH_plugin->set_kyc_ok (TEH_plugin->cls, + kpc->payment_target_uuid); +} + + +/** + * After we are done with the CURL interaction we + * need to update our database state. + * + * @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_login_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: + { + const char *access_token; + const char *token_type; + uint64_t expires_in_s; + const char *refresh_token; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_string ("access_token", + &access_token), + GNUNET_JSON_spec_string ("token_type", + &token_type), + GNUNET_JSON_spec_uint64 ("expires_in", + &expires_in_s), + GNUNET_JSON_spec_string ("refresh_token", + &refresh_token), + 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; + MHD_resume_connection (kpc->rc->connection); + TALER_MHD_daemon_trigger (); + return; + } + } + if (0 != strcasecmp (token_type, + "bearer")) + { + GNUNET_break_op (0); + kpc->response + = TALER_MHD_make_error ( + TALER_EC_EXCHANGE_KYC_PROOF_BACKEND_INVALID_RESPONSE, + "Unexpected token type in response from KYC gateway"); + kpc->response_code + = MHD_HTTP_BAD_GATEWAY; + MHD_resume_connection (kpc->rc->connection); + TALER_MHD_daemon_trigger (); + return; + } + + /* 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! */ + + MHD_resume_connection (kpc->rc->connection); + TALER_MHD_daemon_trigger (); + 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; + MHD_resume_connection (kpc->rc->connection); + TALER_MHD_daemon_trigger (); + 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, + "<html><head><title>%s</title></head><body><h1>%s</h1><p>%s</p></body></html>", + 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; + MHD_resume_connection (kpc->rc->connection); + TALER_MHD_daemon_trigger (); + } + break; + } +} + + +/** + * Function called to clean up a context. + * + * @param rc request context + */ +static void +clean_kpc (struct TEH_RequestContext *rc) +{ + struct KycProofContext *kpc = rc->rh_ctx; + + if (NULL != kpc->job) + { + GNUNET_CURL_job_cancel (kpc->job); + kpc->job = NULL; + } + if (NULL != kpc->response) + { + MHD_destroy_response (kpc->response); + kpc->response = NULL; + } + GNUNET_free (kpc->post_body); + GNUNET_free (kpc->token_url); + GNUNET_free (kpc); } @@ -70,44 +331,188 @@ TEH_handler_kyc_proof ( struct TEH_RequestContext *rc, const char *const args[]) { - struct KycProofContext kpc; - MHD_RESULT res; - enum GNUNET_GenericReturnValue ret; - unsigned long long payment_target_uuid; - char dummy; + struct KycProofContext *kpc = rc->rh_ctx; + + if (NULL == kpc) + { /* first time */ + char dummy; + + kpc = GNUNET_new (struct KycProofContext); + kpc->rc = rc; + rc->rh_ctx = kpc; + rc->rh_cleaner = &clean_kpc; + + if (1 != + sscanf (args[0], + "%llu%c", + &kpc->payment_target_uuid, + &dummy)) + { + GNUNET_break_op (0); + return TALER_MHD_reply_with_error (rc->connection, + MHD_HTTP_BAD_REQUEST, + TALER_EC_GENERIC_PARAMETER_MALFORMED, + "payment_target_uuid"); + } + kpc->authorization_code + = MHD_lookup_connection_value (rc->connection, + MHD_GET_ARGUMENT_KIND, + "code"); + if (NULL == kpc->authorization_code) + { + GNUNET_break_op (0); + return TALER_MHD_reply_with_error (rc->connection, + MHD_HTTP_BAD_REQUEST, + TALER_EC_GENERIC_PARAMETER_MALFORMED, + "code"); + } + if (TEH_KYC_NONE == TEH_kyc_config.mode) + return TALER_MHD_reply_static ( + rc->connection, + MHD_HTTP_NO_CONTENT, + NULL, + NULL, + 0); + + { + CURL *eh; + + eh = curl_easy_init (); + if (NULL == eh) + { + GNUNET_break (0); + return TALER_MHD_reply_with_error (rc->connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_GENERIC_ALLOCATION_FAILURE, + "curl_easy_init"); + } + GNUNET_asprintf (&kpc->token_url, + "%s/token", + TEH_kyc_config.details.oauth2.url); + GNUNET_assert (CURLE_OK == + curl_easy_setopt (eh, + CURLOPT_URL, + kpc->token_url)); + GNUNET_assert (CURLE_OK == + curl_easy_setopt (eh, + CURLOPT_POST, + 1)); + { + char *client_id; + char *redirect_uri; + char *client_secret; + char *authorization_code; + + client_id = curl_easy_escape (eh, + TEH_kyc_config.details.oauth2.client_id, + 0); + GNUNET_assert (NULL != client_id); + { + char *request_uri; + + GNUNET_asprintf (&request_uri, + "%s/login?client_id=%s", + TEH_kyc_config.details.oauth2.url, + TEH_kyc_config.details.oauth2.client_id); + redirect_uri = curl_easy_escape (eh, + request_uri, + 0); + GNUNET_free (request_uri); + } + GNUNET_assert (NULL != redirect_uri); + client_secret = curl_easy_escape (eh, + TEH_kyc_config.details.oauth2. + client_secret, + 0); + GNUNET_assert (NULL != client_secret); + authorization_code = curl_easy_escape (eh, + kpc->authorization_code, + 0); + GNUNET_assert (NULL != authorization_code); + GNUNET_asprintf (&kpc->post_body, + "client_id=%s&redirect_uri=%s&client_secret=%s&code=%s&grant_type=authorization_code", + client_id, + redirect_uri, + client_secret, + authorization_code); + curl_free (authorization_code); + curl_free (client_secret); + curl_free (redirect_uri); + curl_free (client_id); + } + GNUNET_assert (CURLE_OK == + curl_easy_setopt (eh, + CURLOPT_POSTFIELDS, + kpc->post_body)); + GNUNET_assert (CURLE_OK == + curl_easy_setopt (eh, + CURLOPT_FOLLOWLOCATION, + 1L)); + /* limit MAXREDIRS to 5 as a simple security measure against + a potential infinite loop caused by a malicious target */ + GNUNET_assert (CURLE_OK == + curl_easy_setopt (eh, + CURLOPT_MAXREDIRS, + 5L)); - if (1 != - sscanf (args[0], - "%llu%c", - &payment_target_uuid, - &dummy)) + kpc->job = GNUNET_CURL_job_add (TEH_curl_ctx, + eh, + &handle_curl_login_finished, + kpc); + kpc->suspended = GNUNET_YES; + GNUNET_CONTAINER_DLL_insert (kpc_head, + kpc_tail, + kpc); + MHD_suspend_connection (rc->connection); + return MHD_YES; + } + } + + if (NULL != kpc->response) { - GNUNET_break_op (0); - return TALER_MHD_reply_with_error (rc->connection, - MHD_HTTP_BAD_REQUEST, - TALER_EC_GENERIC_PARAMETER_MALFORMED, - "payment_target_uuid"); + /* handle _failed_ resumed cases */ + return MHD_queue_response (rc->connection, + kpc->response_code, + kpc->response); } - if (1 || (TEH_KYC_NONE == TEH_kyc_config.mode)) - return TALER_MHD_reply_static ( - rc->connection, - MHD_HTTP_NO_CONTENT, - NULL, - NULL, - 0); - ret = TEH_DB_run_transaction (rc->connection, - "check proof kyc", - &res, - &proof_kyc_check, - &kpc); - if (GNUNET_SYSERR == ret) + /* _successfully_ resumed case */ + { + MHD_RESULT res; + enum GNUNET_GenericReturnValue ret; + + ret = TEH_DB_run_transaction (kpc->rc->connection, + "check proof kyc", + &res, + &persist_kyc_ok, + kpc); + if (GNUNET_SYSERR == ret) + return res; + } + + { + struct MHD_Response *response; + MHD_RESULT res; + + response = MHD_create_response_from_buffer (0, + "", + MHD_RESPMEM_PERSISTENT); + if (NULL == response) + { + GNUNET_break (0); + return MHD_NO; + } + GNUNET_break (MHD_YES == + MHD_add_response_header ( + response, + MHD_HTTP_HEADER_LOCATION, + TEH_kyc_config.details.oauth2.post_kyc_redirect_url)); + res = MHD_queue_response (rc->connection, + MHD_HTTP_SEE_OTHER, + response); + MHD_destroy_response (response); return res; - return TALER_MHD_REPLY_JSON_PACK ( - rc->connection, - MHD_HTTP_OK, - GNUNET_JSON_pack_uint64 ("42", - 42)); + } } |