diff options
Diffstat (limited to 'src/kyclogic/taler-exchange-kyc-tester.c')
-rw-r--r-- | src/kyclogic/taler-exchange-kyc-tester.c | 1081 |
1 files changed, 963 insertions, 118 deletions
diff --git a/src/kyclogic/taler-exchange-kyc-tester.c b/src/kyclogic/taler-exchange-kyc-tester.c index 24809a82d..c2efafd72 100644 --- a/src/kyclogic/taler-exchange-kyc-tester.c +++ b/src/kyclogic/taler-exchange-kyc-tester.c @@ -27,7 +27,9 @@ #include <limits.h> #include "taler_mhd_lib.h" #include "taler_json_lib.h" -#include "taler_crypto_lib.h" +#include "taler_templating_lib.h" +#include "taler_util.h" +#include "taler_kyclogic_lib.h" #include "taler_kyclogic_plugin.h" #include <gnunet/gnunet_mhd_compat.h> @@ -60,6 +62,11 @@ struct TEKT_RequestContext struct MHD_Connection *connection; /** + * HTTP response to return (or NULL). + */ + struct MHD_Response *response; + + /** * @e rh-specific cleanup routine. Function called * upon completion of the request that should * clean up @a rh_ctx. Can be NULL. @@ -73,6 +80,18 @@ struct TEKT_RequestContext * Can be NULL. */ void *rh_ctx; + + /** + * Uploaded JSON body, if any. + */ + json_t *root; + + /** + * HTTP status to return upon resume if @e response + * is non-NULL. + */ + unsigned int http_status; + }; @@ -161,6 +180,48 @@ struct TEKT_RequestHandler /** + * Information we track per ongoing kyc-proof request. + */ +struct ProofRequestState +{ + /** + * Kept in a DLL. + */ + struct ProofRequestState *next; + + /** + * Kept in a DLL. + */ + struct ProofRequestState *prev; + + /** + * Handle for operation with the plugin. + */ + struct TALER_KYCLOGIC_ProofHandle *ph; + + /** + * Logic plugin we are using. + */ + struct TALER_KYCLOGIC_Plugin *logic; + + /** + * HTTP request details. + */ + struct TEKT_RequestContext *rc; + +}; + +/** + * Head of DLL. + */ +static struct ProofRequestState *rs_head; + +/** + * Tail of DLL. + */ +static struct ProofRequestState *rs_tail; + +/** * The exchange's configuration (global) */ static const struct GNUNET_CONFIGURATION_Handle *TEKT_cfg; @@ -171,14 +232,45 @@ static const struct GNUNET_CONFIGURATION_Handle *TEKT_cfg; static struct MHD_Daemon *mhd; /** - * Our currency. + * Our base URL. */ -static char *TEKT_currency; +static char *TEKT_base_url; /** - * Our base URL. + * Payto set via command-line (or otherwise random). */ -static char *TEKT_base_url; +static struct TALER_PaytoHashP cmd_line_h_payto; + +/** + * Provider user ID to use. + */ +static char *cmd_provider_user_id; + +/** + * Provider legitimization ID to use. + */ +static char *cmd_provider_legitimization_id; + +/** + * Name of the configuration section with the + * configuration data of the selected provider. + */ +static const char *provider_section_name; + +/** + * Row ID to use, override with '-r' + */ +static unsigned int kyc_row_id = 42; + +/** + * -P command-line option. + */ +static int print_h_payto; + +/** + * -w command-line option. + */ +static int run_webservice; /** * Value to return from main() @@ -186,6 +278,26 @@ static char *TEKT_base_url; static int global_ret; /** + * -r command-line flag. + */ +static char *requirements; + +/** + * -i command-line flag. + */ +static char *ut_s = "individual"; + +/** + * Handle for ongoing initiation operation. + */ +static struct TALER_KYCLOGIC_InitiateHandle *ih; + +/** + * KYC logic running for @e ih. + */ +static struct TALER_KYCLOGIC_Plugin *ih_logic; + +/** * Port to run the daemon on. */ static uint16_t serve_port; @@ -203,20 +315,547 @@ static struct GNUNET_CURL_RescheduleContext *exchange_curl_rc; /** - * Generate a 404 "not found" reply on @a connection with - * the hint @a details. + * Context for the webhook. + */ +struct KycWebhookContext +{ + + /** + * Kept in a DLL while suspended. + */ + struct KycWebhookContext *next; + + /** + * Kept in a DLL while suspended. + */ + struct KycWebhookContext *prev; + + /** + * Details about the connection we are processing. + */ + struct TEKT_RequestContext *rc; + + /** + * Plugin responsible for the webhook. + */ + struct TALER_KYCLOGIC_Plugin *plugin; + + /** + * Configuration for the specific action. + */ + struct TALER_KYCLOGIC_ProviderDetails *pd; + + /** + * Webhook activity. + */ + struct TALER_KYCLOGIC_WebhookHandle *wh; + + /** + * HTTP response to return. + */ + struct MHD_Response *response; + + /** + * Name of the configuration + * section defining the KYC logic. + */ + const char *section_name; + + /** + * 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 KycWebhookContext *kwh_head; + +/** + * Contexts are kept in a DLL while suspended. + */ +static struct KycWebhookContext *kwh_tail; + + +/** + * Resume processing the @a kwh request. + * + * @param kwh request to resume + */ +static void +kwh_resume (struct KycWebhookContext *kwh) +{ + GNUNET_assert (GNUNET_YES == kwh->suspended); + kwh->suspended = GNUNET_NO; + GNUNET_CONTAINER_DLL_remove (kwh_head, + kwh_tail, + kwh); + MHD_resume_connection (kwh->rc->connection); +} + + +static void +kyc_webhook_cleanup (void) +{ + struct KycWebhookContext *kwh; + + while (NULL != (kwh = kwh_head)) + { + if (NULL != kwh->wh) + { + kwh->plugin->webhook_cancel (kwh->wh); + kwh->wh = NULL; + } + kwh_resume (kwh); + } +} + + +/** + * Function called with the result of a webhook + * operation. + * + * Note that the "decref" for the @a response + * will be done by the plugin. + * + * @param cls closure + * @param process_row legitimization process request the webhook was about + * @param account_id account the webhook was about + * @param provider_section configuration section of the logic + * @param provider_user_id set to user ID at the provider, or NULL if not supported or unknown + * @param provider_legitimization_id set to legitimization process ID at the provider, or NULL if not supported or unknown + * @param status KYC status + * @param expiration until when is the KYC check valid + * @param attributes user attributes returned by the provider + * @param http_status HTTP status code of @a response + * @param[in] response to return to the HTTP client + */ +static void +webhook_finished_cb ( + void *cls, + uint64_t process_row, + const struct TALER_PaytoHashP *account_id, + const char *provider_section, + const char *provider_user_id, + const char *provider_legitimization_id, + enum TALER_KYCLOGIC_KycStatus status, + struct GNUNET_TIME_Absolute expiration, + const json_t *attributes, + unsigned int http_status, + struct MHD_Response *response) +{ + struct KycWebhookContext *kwh = cls; + + (void) expiration; + (void) provider_section; + kwh->wh = NULL; + if ( (NULL != account_id) && + (0 != GNUNET_memcmp (account_id, + &cmd_line_h_payto)) ) + { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Received webhook for unexpected account\n"); + } + if ( (NULL != provider_user_id) && + (NULL != cmd_provider_user_id) && + (0 != strcmp (provider_user_id, + cmd_provider_user_id)) ) + { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Received webhook for unexpected provider user ID (%s)\n", + provider_user_id); + } + if ( (NULL != provider_legitimization_id) && + (NULL != cmd_provider_legitimization_id) && + (0 != strcmp (provider_legitimization_id, + cmd_provider_legitimization_id)) ) + { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Received webhook for unexpected provider legitimization ID (%s)\n", + provider_legitimization_id); + } + switch (status) + { + case TALER_KYCLOGIC_STATUS_SUCCESS: + /* _successfully_ resumed case */ + GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE, + "KYC successful for user `%s' (legi: %s)\n", + provider_user_id, + provider_legitimization_id); + GNUNET_break (NULL != attributes); + fprintf (stderr, + "Extracted attributes:\n"); + json_dumpf (attributes, + stderr, + JSON_INDENT (2)); + break; + default: + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "KYC status of %s/%s (process #%llu) is %d\n", + provider_user_id, + provider_legitimization_id, + (unsigned long long) process_row, + status); + break; + } + kwh->response = response; + kwh->response_code = http_status; + kwh_resume (kwh); + TALER_MHD_daemon_trigger (); +} + + +/** + * Function called to clean up a context. + * + * @param rc request context + */ +static void +clean_kwh (struct TEKT_RequestContext *rc) +{ + struct KycWebhookContext *kwh = rc->rh_ctx; + + if (NULL != kwh->wh) + { + kwh->plugin->webhook_cancel (kwh->wh); + kwh->wh = NULL; + } + if (NULL != kwh->response) + { + MHD_destroy_response (kwh->response); + kwh->response = NULL; + } + GNUNET_free (kwh); +} + + +/** + * Function the plugin can use to lookup an + * @a h_payto by @a provider_legitimization_id. + * + * @param cls closure, NULL + * @param provider_section + * @param provider_legitimization_id legi to look up + * @param[out] h_payto where to write the result + * @param[out] legi_row where to write the row ID for the legitimization ID + * @return database transaction status + */ +static enum GNUNET_DB_QueryStatus +kyc_provider_account_lookup ( + void *cls, + const char *provider_section, + const char *provider_legitimization_id, + struct TALER_PaytoHashP *h_payto, + uint64_t *legi_row) +{ + (void) cls; + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Simulated account lookup using `%s/%s'\n", + provider_section, + provider_legitimization_id); + *h_payto = cmd_line_h_payto; + *legi_row = kyc_row_id; + return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT; +} + + +/** + * Handle a (GET or POST) "/kyc-webhook" request. * - * @param connection where to send the reply on - * @param details details for the error message, can be NULL + * @param rc request to handle + * @param method HTTP request method used by the client + * @param root uploaded JSON body (can be NULL) + * @param args one argument with the legitimization_uuid + * @return MHD result code */ static MHD_RESULT -r404 (struct MHD_Connection *connection, - const char *details) +handler_kyc_webhook_generic ( + struct TEKT_RequestContext *rc, + const char *method, + const json_t *root, + const char *const args[]) { - return TALER_MHD_reply_with_error (connection, - MHD_HTTP_NOT_FOUND, - TALER_EC_EXCHANGE_GENERIC_OPERATION_UNKNOWN, - details); + struct KycWebhookContext *kwh = rc->rh_ctx; + + if (NULL == kwh) + { /* first time */ + kwh = GNUNET_new (struct KycWebhookContext); + kwh->rc = rc; + rc->rh_ctx = kwh; + rc->rh_cleaner = &clean_kwh; + + if ( (NULL == args[0]) || + (GNUNET_OK != + TALER_KYCLOGIC_lookup_logic (args[0], + &kwh->plugin, + &kwh->pd, + &kwh->section_name)) ) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "KYC logic `%s' unknown (check KYC provider configuration)\n", + args[0]); + return TALER_MHD_reply_with_error (rc->connection, + MHD_HTTP_NOT_FOUND, + TALER_EC_EXCHANGE_KYC_GENERIC_LOGIC_UNKNOWN, + args[0]); + } + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Calling KYC provider specific webhook\n"); + kwh->wh = kwh->plugin->webhook (kwh->plugin->cls, + kwh->pd, + &kyc_provider_account_lookup, + NULL, + method, + &args[1], + rc->connection, + root, + &webhook_finished_cb, + kwh); + if (NULL == kwh->wh) + { + GNUNET_break_op (0); + return TALER_MHD_reply_with_error (rc->connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE, + "failed to run webhook logic"); + } + kwh->suspended = GNUNET_YES; + GNUNET_CONTAINER_DLL_insert (kwh_head, + kwh_tail, + kwh); + MHD_suspend_connection (rc->connection); + return MHD_YES; + } + + if (NULL != kwh->response) + { + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Returning queued reply for KWH\n"); + /* handle _failed_ resumed cases */ + return MHD_queue_response (rc->connection, + kwh->response_code, + kwh->response); + } + + /* We resumed, but got no response? This should + not happen. */ + GNUNET_assert (0); + return TALER_MHD_reply_with_error (rc->connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE, + "resumed without response"); +} + + +/** + * Handle a GET "/kyc-webhook" request. + * + * @param rc request to handle + * @param args one argument with the legitimization_uuid + * @return MHD result code + */ +static MHD_RESULT +handler_kyc_webhook_get ( + struct TEKT_RequestContext *rc, + const char *const args[]) +{ + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Webhook GET triggered\n"); + return handler_kyc_webhook_generic (rc, + MHD_HTTP_METHOD_GET, + NULL, + args); +} + + +/** + * Handle a POST "/kyc-webhook" request. + * + * @param rc request to handle + * @param root uploaded JSON body (can be NULL) + * @param args one argument with the legitimization_uuid + * @return MHD result code + */ +static MHD_RESULT +handler_kyc_webhook_post ( + struct TEKT_RequestContext *rc, + const json_t *root, + const char *const args[]) +{ + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Webhook POST triggered\n"); + return handler_kyc_webhook_generic (rc, + MHD_HTTP_METHOD_POST, + root, + args); +} + + +/** + * Function called with the result of a proof check operation. + * + * Note that the "decref" for the @a response + * will be done by the callee and MUST NOT be done by the plugin. + * + * @param cls closure with the `struct ProofRequestState` + * @param status KYC status + * @param provider_user_id set to user ID at the provider, or NULL if not supported or unknown + * @param provider_legitimization_id set to legitimization process ID at the provider, or NULL if not supported or unknown + * @param expiration until when is the KYC check valid + * @param attributes attributes about the user + * @param http_status HTTP status code of @a response + * @param[in] response to return to the HTTP client + */ +static void +proof_cb ( + void *cls, + enum TALER_KYCLOGIC_KycStatus status, + const char *provider_user_id, + const char *provider_legitimization_id, + struct GNUNET_TIME_Absolute expiration, + const json_t *attributes, + unsigned int http_status, + struct MHD_Response *response) +{ + struct ProofRequestState *rs = cls; + + (void) expiration; + GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE, + "KYC legitimization %s completed with status %d (%u) for %s\n", + provider_legitimization_id, + status, + http_status, + provider_user_id); + if (TALER_KYCLOGIC_STATUS_SUCCESS == status) + { + GNUNET_break (NULL != attributes); + fprintf (stderr, + "Extracted attributes:\n"); + json_dumpf (attributes, + stderr, + JSON_INDENT (2)); + } + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Returning response %p with status %u\n", + response, + http_status); + rs->rc->response = response; + rs->rc->http_status = http_status; + GNUNET_CONTAINER_DLL_remove (rs_head, + rs_tail, + rs); + MHD_resume_connection (rs->rc->connection); + TALER_MHD_daemon_trigger (); + GNUNET_free (rs); +} + + +/** + * Function called when we receive a 'GET' to the + * '/kyc-proof' endpoint. + * + * @param rc request context + * @param args remaining URL arguments; + * args[0] should be the logic plugin name + */ +static MHD_RESULT +handler_kyc_proof_get ( + struct TEKT_RequestContext *rc, + const char *const args[1]) +{ + struct TALER_PaytoHashP h_payto; + struct TALER_KYCLOGIC_ProviderDetails *pd; + struct TALER_KYCLOGIC_Plugin *logic; + struct ProofRequestState *rs; + const char *section_name; + const char *h_paytos; + + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "GET /kyc-proof triggered\n"); + if (NULL == args[0]) + { + GNUNET_break_op (0); + return TALER_MHD_reply_with_error (rc->connection, + MHD_HTTP_NOT_FOUND, + TALER_EC_GENERIC_ENDPOINT_UNKNOWN, + "'/kyc-proof/$PROVIDER_SECTION?state=$H_PAYTO' required"); + } + h_paytos = MHD_lookup_connection_value (rc->connection, + MHD_GET_ARGUMENT_KIND, + "state"); + if (NULL == h_paytos) + { + GNUNET_break_op (0); + return TALER_MHD_reply_with_error (rc->connection, + MHD_HTTP_BAD_REQUEST, + TALER_EC_GENERIC_PARAMETER_MISSING, + "h_payto"); + } + if (GNUNET_OK != + GNUNET_STRINGS_string_to_data (h_paytos, + strlen (h_paytos), + &h_payto, + sizeof (h_payto))) + { + GNUNET_break_op (0); + return TALER_MHD_reply_with_error (rc->connection, + MHD_HTTP_BAD_REQUEST, + TALER_EC_GENERIC_PARAMETER_MALFORMED, + "h_payto"); + } + if (0 != + GNUNET_memcmp (&h_payto, + &cmd_line_h_payto)) + { + GNUNET_break_op (0); + return TALER_MHD_reply_with_error (rc->connection, + MHD_HTTP_NOT_FOUND, + TALER_EC_EXCHANGE_KYC_PROOF_REQUEST_UNKNOWN, + "h_payto"); + } + + if (GNUNET_OK != + TALER_KYCLOGIC_lookup_logic (args[0], + &logic, + &pd, + §ion_name)) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Could not initiate KYC with provider `%s' (configuration error?)\n", + args[0]); + return TALER_MHD_reply_with_error (rc->connection, + MHD_HTTP_NOT_FOUND, + TALER_EC_EXCHANGE_KYC_GENERIC_LOGIC_UNKNOWN, + args[0]); + } + rs = GNUNET_new (struct ProofRequestState); + rs->rc = rc; + rs->logic = logic; + MHD_suspend_connection (rc->connection); + GNUNET_CONTAINER_DLL_insert (rs_head, + rs_tail, + rs); + rs->ph = logic->proof (logic->cls, + pd, + rc->connection, + &h_payto, + kyc_row_id, + cmd_provider_user_id, + cmd_provider_legitimization_id, + &proof_cb, + rs); + GNUNET_assert (NULL != rs->ph); + return MHD_YES; } @@ -272,6 +911,8 @@ handle_mhd_completion_callback (void *cls, TALER_MHD_parse_post_cleanup_callback (rc->opaque_post_parsing_context); /* Sanity-check that we didn't leave any transactions hanging */ + if (NULL != rc->root) + json_decref (rc->root); GNUNET_free (rc); *con_cls = NULL; } @@ -295,10 +936,8 @@ proceed_with_handler (struct TEKT_RequestContext *rc, size_t *upload_data_size) { const struct TEKT_RequestHandler *rh = rc->rh; - // FIXME: handle '-1 == rh->nargs'!!! const char *args[rh->nargs + 2]; size_t ulen = strlen (url) + 1; - json_t *root = NULL; MHD_RESULT ret; /* We do check for "ulen" here, because we'll later stack-allocate a buffer @@ -319,8 +958,9 @@ proceed_with_handler (struct TEKT_RequestContext *rc, /* All POST endpoints come with a body in JSON format. So we parse the JSON here. */ - if (0 == strcasecmp (rh->method, - MHD_HTTP_METHOD_POST)) + if ( (NULL == rc->root) && + (0 == strcasecmp (rh->method, + MHD_HTTP_METHOD_POST)) ) { enum GNUNET_GenericReturnValue res; @@ -328,16 +968,17 @@ proceed_with_handler (struct TEKT_RequestContext *rc, &rc->opaque_post_parsing_context, upload_data, upload_data_size, - &root); + &rc->root); if (GNUNET_SYSERR == res) { - GNUNET_assert (NULL == root); + GNUNET_assert (NULL == rc->root); + GNUNET_break (0); return MHD_NO; /* bad upload, could not even generate error */ } if ( (GNUNET_NO == res) || - (NULL == root) ) + (NULL == rc->root) ) { - GNUNET_assert (NULL == root); + GNUNET_assert (NULL == rc->root); return MHD_YES; /* so far incomplete upload or parser error */ } } @@ -349,9 +990,9 @@ proceed_with_handler (struct TEKT_RequestContext *rc, /* Parse command-line arguments */ /* make a copy of 'url' because 'strtok_r()' will modify */ - memcpy (d, - url, - ulen); + GNUNET_memcpy (d, + url, + ulen); i = 0; args[i++] = strtok_r (d, "/", &sp); while ( (NULL != args[i - 1]) && @@ -374,7 +1015,6 @@ proceed_with_handler (struct TEKT_RequestContext *rc, rh->url, url); GNUNET_break_op (0); - json_decref (root); return TALER_MHD_reply_with_error (rc->connection, MHD_HTTP_NOT_FOUND, TALER_EC_EXCHANGE_GENERIC_WRONG_NUMBER_OF_SEGMENTS, @@ -384,20 +1024,35 @@ proceed_with_handler (struct TEKT_RequestContext *rc, /* Above logic ensures that 'root' is exactly non-NULL for POST operations, so we test for 'root' to decide which handler to invoke. */ - if (NULL != root) + if (NULL != rc->root) ret = rh->handler.post (rc, - root, + rc->root, args); else /* We also only have "POST" or "GET" in the API for at this point (OPTIONS/HEAD are taken care of earlier) */ ret = rh->handler.get (rc, args); } - json_decref (root); return ret; } +static void +rh_cleaner_cb (struct TEKT_RequestContext *rc) +{ + if (NULL != rc->response) + { + MHD_destroy_response (rc->response); + rc->response = NULL; + } + if (NULL != rc->root) + { + json_decref (rc->root); + rc->root = NULL; + } +} + + /** * Handle incoming HTTP request. * @@ -422,39 +1077,27 @@ handle_mhd_request (void *cls, void **con_cls) { static struct TEKT_RequestHandler handlers[] = { -#if FIXME - /* KYC endpoints */ - { - .url = "kyc-check", - .method = MHD_HTTP_METHOD_GET, - .handler.get = &TEKT_handler_kyc_check, - .nargs = 1 - }, + /* simulated KYC endpoints */ { .url = "kyc-proof", .method = MHD_HTTP_METHOD_GET, - .handler.get = &TEKT_handler_kyc_proof, + .handler.get = &handler_kyc_proof_get, .nargs = 1 }, { - .url = "kyc-wallet", - .method = MHD_HTTP_METHOD_POST, - .handler.post = &TEKT_handler_kyc_wallet, - .nargs = 0 - }, - { .url = "kyc-webhook", .method = MHD_HTTP_METHOD_POST, - .handler.post = &TEKT_handler_kyc_webhook_post, - .nargs = -1 + .handler.post = &handler_kyc_webhook_post, + .nargs = 128, + .nargs_is_upper_bound = true }, { .url = "kyc-webhook", .method = MHD_HTTP_METHOD_GET, - .handler.post = &TEKT_handler_kyc_webhook_get, - .nargs = -1 + .handler.get = &handler_kyc_webhook_get, + .nargs = 128, + .nargs_is_upper_bound = true }, -#endif /* mark end of list */ { .url = NULL @@ -472,6 +1115,13 @@ handle_mhd_request (void *cls, rc = *con_cls = GNUNET_new (struct TEKT_RequestContext); rc->url = url; rc->connection = connection; + rc->rh_cleaner = &rh_cleaner_cb; + } + if (NULL != rc->response) + { + return MHD_queue_response (rc->connection, + rc->http_status, + rc->response); } GNUNET_log (GNUNET_ERROR_TYPE_INFO, @@ -481,7 +1131,6 @@ handle_mhd_request (void *cls, /* on repeated requests, check our cache first */ if (NULL != rc->rh) { - MHD_RESULT ret; const char *start; if ('\0' == url[0]) @@ -490,11 +1139,10 @@ handle_mhd_request (void *cls, start = strchr (url + 1, '/'); if (NULL == start) start = ""; - ret = proceed_with_handler (rc, - start, - upload_data, - upload_data_size); - return ret; + return proceed_with_handler (rc, + start, + upload_data, + upload_data_size); } if (0 == strcasecmp (method, MHD_HTTP_METHOD_HEAD)) @@ -532,25 +1180,25 @@ handle_mhd_request (void *cls, continue; found = true; /* The URL is a match! What we now do depends on the method. */ - if (0 == strcasecmp (method, MHD_HTTP_METHOD_OPTIONS)) + if (0 == strcasecmp (method, + MHD_HTTP_METHOD_OPTIONS)) { return TALER_MHD_reply_cors_preflight (connection); } GNUNET_assert (NULL != rh->method); - if (0 == strcasecmp (method, + if (0 != strcasecmp (method, rh->method)) { - MHD_RESULT ret; - - /* cache to avoid the loop next time */ - rc->rh = rh; - /* run handler */ - ret = proceed_with_handler (rc, - url + tok_size + 1, - upload_data, - upload_data_size); - return ret; + found = true; + continue; } + /* cache to avoid the loop next time */ + rc->rh = rh; + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Handler found for %s '%s'\n", + method, + url); + return MHD_YES; } if (found) @@ -614,15 +1262,10 @@ handle_mhd_request (void *cls, } /* No handler matches, generate not found */ - { - MHD_RESULT ret; - - ret = TALER_MHD_reply_with_error (connection, - MHD_HTTP_NOT_FOUND, - TALER_EC_GENERIC_ENDPOINT_UNKNOWN, - url); - return ret; - } + return TALER_MHD_reply_with_error (connection, + MHD_HTTP_NOT_FOUND, + TALER_EC_GENERIC_ENDPOINT_UNKNOWN, + url); } @@ -668,8 +1311,25 @@ static void do_shutdown (void *cls) { struct MHD_Daemon *mhd; - (void) cls; + struct ProofRequestState *rs; + (void) cls; + while (NULL != (rs = rs_head)) + { + GNUNET_CONTAINER_DLL_remove (rs_head, + rs_tail, + rs); + rs->logic->proof_cancel (rs->ph); + MHD_resume_connection (rs->rc->connection); + GNUNET_free (rs); + } + if (NULL != ih) + { + ih_logic->initiate_cancel (ih); + ih = NULL; + } + kyc_webhook_cleanup (); + TALER_KYCLOGIC_kyc_done (); mhd = TALER_MHD_daemon_stop (); if (NULL != mhd) MHD_stop_daemon (mhd); @@ -683,6 +1343,74 @@ do_shutdown (void *cls) GNUNET_CURL_gnunet_rc_destroy (exchange_curl_rc); exchange_curl_rc = NULL; } + TALER_TEMPLATING_done (); +} + + +/** + * Function called with the result of a KYC initiation + * operation. + * + * @param cls closure + * @param ec #TALER_EC_NONE on success + * @param redirect_url set to where to redirect the user on success, NULL on failure + * @param provider_user_id set to user ID at the provider, or NULL if not supported or unknown + * @param provider_legitimization_id set to legitimization process ID at the provider, or NULL if not supported or unknown + * @param error_msg_hint set to additional details to return to user, NULL on success + */ +static void +initiate_cb ( + void *cls, + enum TALER_ErrorCode ec, + const char *redirect_url, + const char *provider_user_id, + const char *provider_legitimization_id, + const char *error_msg_hint) +{ + (void) cls; + ih = NULL; + if (TALER_EC_NONE != ec) + { + fprintf (stderr, + "Failed to start KYC process: %s (#%d)\n", + error_msg_hint, + ec); + global_ret = EXIT_FAILURE; + GNUNET_SCHEDULER_shutdown (); + return; + } + { + char *s; + + s = GNUNET_STRINGS_data_to_string_alloc (&cmd_line_h_payto, + sizeof (cmd_line_h_payto)); + if (NULL != provider_user_id) + { + fprintf (stdout, + "Visit `%s' to begin KYC process.\nAlso use: taler-exchange-kyc-tester -w -u '%s' -U '%s' -p %s\n", + redirect_url, + provider_user_id, + provider_legitimization_id, + s); + } + else + { + fprintf (stdout, + "Visit `%s' to begin KYC process.\nAlso use: taler-exchange-kyc-tester -w -U '%s' -p %s\n", + redirect_url, + provider_legitimization_id, + s); + } + GNUNET_free (s); + } + GNUNET_free (cmd_provider_user_id); + GNUNET_free (cmd_provider_legitimization_id); + if (NULL != provider_user_id) + cmd_provider_user_id = GNUNET_strdup (provider_user_id); + if (NULL != provider_legitimization_id) + cmd_provider_legitimization_id = GNUNET_strdup (provider_legitimization_id); + if (! run_webservice) + GNUNET_SCHEDULER_shutdown (); } @@ -706,63 +1434,130 @@ run (void *cls, (void) cls; (void) args; (void ) cfgfile; - TALER_MHD_setup (TALER_MHD_GO_NONE); - TEKT_cfg = config; - if (GNUNET_OK != - exchange_serve_process_config ()) + TALER_TEMPLATING_init ("exchange")) { - global_ret = EXIT_NOTCONFIGURED; - GNUNET_SCHEDULER_shutdown (); + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Could not load templates. Installation broken.\n"); return; } - TEKT_curl_ctx - = GNUNET_CURL_init (&GNUNET_CURL_gnunet_scheduler_reschedule, - &exchange_curl_rc); - if (NULL == TEKT_curl_ctx) + if (print_h_payto) { - GNUNET_break (0); - global_ret = EXIT_FAILURE; - GNUNET_SCHEDULER_shutdown (); - return; + char *s; + + s = GNUNET_STRINGS_data_to_string_alloc (&cmd_line_h_payto, + sizeof (cmd_line_h_payto)); + fprintf (stdout, + "%s\n", + s); + GNUNET_free (s); } - exchange_curl_rc = GNUNET_CURL_gnunet_rc_create (TEKT_curl_ctx); + TALER_MHD_setup (TALER_MHD_GO_NONE); + TEKT_cfg = config; GNUNET_SCHEDULER_add_shutdown (&do_shutdown, NULL); - fh = TALER_MHD_bind (TEKT_cfg, - "exchange", - &serve_port); - if ( (0 == serve_port) && - (-1 == fh) ) + if (GNUNET_OK != + TALER_KYCLOGIC_kyc_init (config)) { + global_ret = EXIT_NOTCONFIGURED; GNUNET_SCHEDULER_shutdown (); return; } - mhd = MHD_start_daemon (MHD_USE_SUSPEND_RESUME - | MHD_USE_PIPE_FOR_SHUTDOWN - | MHD_USE_DEBUG | MHD_USE_DUAL_STACK - | MHD_USE_TCP_FASTOPEN, - (-1 == fh) ? serve_port : 0, - NULL, NULL, - &handle_mhd_request, NULL, - MHD_OPTION_LISTEN_SOCKET, - fh, - MHD_OPTION_EXTERNAL_LOGGER, - &TALER_MHD_handle_logs, - NULL, - MHD_OPTION_NOTIFY_COMPLETED, - &handle_mhd_completion_callback, - NULL, - MHD_OPTION_END); - if (NULL == mhd) + if (GNUNET_OK != + exchange_serve_process_config ()) { - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Failed to launch HTTP service. Is the port in use?\n"); + global_ret = EXIT_NOTCONFIGURED; GNUNET_SCHEDULER_shutdown (); return; } global_ret = EXIT_SUCCESS; - TALER_MHD_daemon_start (mhd); + if (NULL != requirements) + { + struct TALER_KYCLOGIC_ProviderDetails *pd; + enum TALER_KYCLOGIC_KycUserType ut; + + if (GNUNET_OK != + TALER_KYCLOGIC_kyc_user_type_from_string (ut_s, + &ut)) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Invalid user type specified ('-i')\n"); + global_ret = EXIT_INVALIDARGUMENT; + GNUNET_SCHEDULER_shutdown (); + return; + } + if (GNUNET_OK != + TALER_KYCLOGIC_requirements_to_logic (requirements, + ut, + &ih_logic, + &pd, + &provider_section_name)) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Could not initiate KYC for requirements `%s' (configuration error?)\n", + requirements); + global_ret = EXIT_NOTCONFIGURED; + GNUNET_SCHEDULER_shutdown (); + return; + } + ih = ih_logic->initiate (ih_logic->cls, + pd, + &cmd_line_h_payto, + kyc_row_id, + &initiate_cb, + NULL); + GNUNET_break (NULL != ih); + } + if (run_webservice) + { + TEKT_curl_ctx + = GNUNET_CURL_init (&GNUNET_CURL_gnunet_scheduler_reschedule, + &exchange_curl_rc); + if (NULL == TEKT_curl_ctx) + { + GNUNET_break (0); + global_ret = EXIT_FAILURE; + GNUNET_SCHEDULER_shutdown (); + return; + } + exchange_curl_rc = GNUNET_CURL_gnunet_rc_create (TEKT_curl_ctx); + fh = TALER_MHD_bind (TEKT_cfg, + "exchange", + &serve_port); + if ( (0 == serve_port) && + (-1 == fh) ) + { + GNUNET_SCHEDULER_shutdown (); + return; + } + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Starting daemon on port %u\n", + (unsigned int) serve_port); + mhd = MHD_start_daemon (MHD_USE_SUSPEND_RESUME + | MHD_USE_PIPE_FOR_SHUTDOWN + | MHD_USE_DEBUG | MHD_USE_DUAL_STACK + | MHD_USE_TCP_FASTOPEN, + (-1 == fh) ? serve_port : 0, + NULL, NULL, + &handle_mhd_request, NULL, + MHD_OPTION_LISTEN_SOCKET, + fh, + MHD_OPTION_EXTERNAL_LOGGER, + &TALER_MHD_handle_logs, + NULL, + MHD_OPTION_NOTIFY_COMPLETED, + &handle_mhd_completion_callback, + NULL, + MHD_OPTION_END); + if (NULL == mhd) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Failed to launch HTTP service. Is the port in use?\n"); + GNUNET_SCHEDULER_shutdown (); + return; + } + TALER_MHD_daemon_start (mhd); + } } @@ -780,11 +1575,61 @@ main (int argc, const struct GNUNET_GETOPT_CommandLineOption options[] = { GNUNET_GETOPT_option_help ( "tool to test KYC provider integrations"), + GNUNET_GETOPT_option_flag ( + 'P', + "print-payto-hash", + "output the hash of the payto://-URI", + &print_h_payto), + GNUNET_GETOPT_option_uint ( + 'r', + "rowid", + "NUMBER", + "override row ID to use in simulation (default: 42)", + &kyc_row_id), + GNUNET_GETOPT_option_flag ( + 'w', + "run-webservice", + "run the integrated HTTP service", + &run_webservice), + GNUNET_GETOPT_option_string ( + 'R', + "requirements", + "CHECKS", + "initiate KYC check for the given list of (space-separated) checks", + &requirements), + GNUNET_GETOPT_option_string ( + 'i', + "identify", + "USERTYPE", + "self-identify as USERTYPE 'business' or 'individual' (defaults to 'individual')", + &requirements), + GNUNET_GETOPT_option_string ( + 'u', + "user", + "ID", + "use the given provider user ID (overridden if -i is also used)", + &cmd_provider_user_id), + GNUNET_GETOPT_option_string ( + 'U', + "legitimization", + "ID", + "use the given provider legitimization ID (overridden if -i is also used)", + &cmd_provider_legitimization_id), + GNUNET_GETOPT_option_base32_fixed_size ( + 'p', + "payto-hash", + "HASH", + "base32 encoding of the hash of a payto://-URI to use for the account (otherwise a random value will be used)", + &cmd_line_h_payto, + sizeof (cmd_line_h_payto)), GNUNET_GETOPT_OPTION_END }; enum GNUNET_GenericReturnValue ret; TALER_OS_init (); + GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_NONCE, + &cmd_line_h_payto, + sizeof (cmd_line_h_payto)); ret = GNUNET_PROGRAM_run (argc, argv, "taler-exchange-kyc-tester", "tool to test KYC provider integrations", |