summaryrefslogtreecommitdiff
path: root/src/exchange/taler-exchange-httpd_kyc-proof.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/exchange/taler-exchange-httpd_kyc-proof.c')
-rw-r--r--src/exchange/taler-exchange-httpd_kyc-proof.c481
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));
+ }
}