diff --git a/src/exchange/exchange.conf b/src/exchange/exchange.conf index 3bcea08fb..391dc9344 100644 --- a/src/exchange/exchange.conf +++ b/src/exchange/exchange.conf @@ -90,7 +90,9 @@ KYC_MODE = NONE # Balance threshold above which wallets are told # to undergo a KYC check at the exchange. Optional, # if not given there is no limit. -# KYC_WALLET_BALANCE_LIMIT = 150:CURRENCY +# KYC_WALLET_BALANCE_LIMIT = CURRENCY:150 +# +# KYC_WITHDRAW_PERIOD = 1 month [exchange-kyc-oauth2] @@ -105,4 +107,4 @@ KYC_MODE = NONE # Where to redirect clients after successful # authorization? -# KYC_OAUTH_POST_URL = https://bank.com/ +# KYC_OAUTH2_POST_URL = https://bank.com/ diff --git a/src/exchange/taler-exchange-httpd_kyc-proof.c b/src/exchange/taler-exchange-httpd_kyc-proof.c index 842e5dfd2..fdf5ade54 100644 --- a/src/exchange/taler-exchange-httpd_kyc-proof.c +++ b/src/exchange/taler-exchange-httpd_kyc-proof.c @@ -108,6 +108,24 @@ static struct KycProofContext *kpc_head; static struct KycProofContext *kpc_tail; +/** + * Resume processing the @a kpc request. + * + * @param kpc request to resume + */ +static void +kpc_resume (struct KycProofContext *kpc) +{ + GNUNET_assert (GNUNET_YES == kpc->suspended); + kpc->suspended = GNUNET_NO; + GNUNET_CONTAINER_DLL_remove (kpc_head, + kpc_tail, + kpc); + MHD_resume_connection (kpc->rc->connection); + TALER_MHD_daemon_trigger (); +} + + void TEH_kyc_proof_cleanup (void) { @@ -120,11 +138,7 @@ TEH_kyc_proof_cleanup (void) 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); + kpc_resume (kpc); } } @@ -210,8 +224,7 @@ handle_curl_login_finished (void *cls, "Unexpected response from KYC gateway"); kpc->response_code = MHD_HTTP_BAD_GATEWAY; - MHD_resume_connection (kpc->rc->connection); - TALER_MHD_daemon_trigger (); + kpc_resume (kpc); return; } } @@ -225,8 +238,7 @@ handle_curl_login_finished (void *cls, "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 (); + kpc_resume (kpc); return; } @@ -234,8 +246,7 @@ handle_curl_login_finished (void *cls, possibly use the access_token to download information we should persist; then continue! */ - MHD_resume_connection (kpc->rc->connection); - TALER_MHD_daemon_trigger (); + kpc_resume (kpc); return; } default: @@ -268,8 +279,7 @@ handle_curl_login_finished (void *cls, "Unexpected response from KYC gateway"); kpc->response_code = MHD_HTTP_BAD_GATEWAY; - MHD_resume_connection (kpc->rc->connection); - TALER_MHD_daemon_trigger (); + kpc_resume (kpc); return; } } @@ -292,8 +302,7 @@ handle_curl_login_finished (void *cls, GNUNET_free (reply); } kpc->response_code = MHD_HTTP_FORBIDDEN; - MHD_resume_connection (kpc->rc->connection); - TALER_MHD_daemon_trigger (); + kpc_resume (kpc); } break; } @@ -387,7 +396,7 @@ TEH_handler_kyc_proof ( "curl_easy_init"); } GNUNET_asprintf (&kpc->token_url, - "%s/token", + "%stoken", TEH_kyc_config.details.oauth2.url); GNUNET_assert (CURLE_OK == curl_easy_setopt (eh, @@ -411,7 +420,7 @@ TEH_handler_kyc_proof ( char *request_uri; GNUNET_asprintf (&request_uri, - "%s/login?client_id=%s", + "%slogin?client_id=%s", TEH_kyc_config.details.oauth2.url, TEH_kyc_config.details.oauth2.client_id); redirect_uri = curl_easy_escape (eh, diff --git a/src/include/taler_exchange_service.h b/src/include/taler_exchange_service.h index 54fca65cb..09c50b17e 100644 --- a/src/include/taler_exchange_service.h +++ b/src/include/taler_exchange_service.h @@ -2170,11 +2170,6 @@ struct TALER_EXCHANGE_KycProofResponse */ unsigned int http_status; - /** - * Taler error code, if any. - */ - enum TALER_ErrorCode ec; - union { diff --git a/src/lib/exchange_api_kyc_proof.c b/src/lib/exchange_api_kyc_proof.c index 802152277..f8e84d7ca 100644 --- a/src/lib/exchange_api_kyc_proof.c +++ b/src/lib/exchange_api_kyc_proof.c @@ -75,15 +75,16 @@ struct TALER_EXCHANGE_KycProofHandle * * @param cls the `struct TALER_EXCHANGE_KycProofHandle` * @param response_code HTTP response code, 0 on error - * @param response parsed JSON result, NULL on error + * @param body response body + * @param body_size number of bytes in @a body */ static void handle_kyc_proof_finished (void *cls, long response_code, - const void *response) + const void *body, + size_t body_size) { struct TALER_EXCHANGE_KycProofHandle *kph = cls; - const json_t *j = response; struct TALER_EXCHANGE_KycProofResponse kpr = { .http_status = (unsigned int) response_code }; @@ -92,9 +93,8 @@ handle_kyc_proof_finished (void *cls, switch (response_code) { case 0: - kpr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; break; - case MHD_HTTP_FOUND: + case MHD_HTTP_SEE_OTHER: { char *redirect_url; @@ -106,34 +106,29 @@ handle_kyc_proof_finished (void *cls, break; } case MHD_HTTP_BAD_REQUEST: - kpr.ec = TALER_JSON_get_error_code (j); /* This should never happen, either us or the exchange is buggy (or API version conflict); just pass JSON reply to the application */ break; case MHD_HTTP_UNAUTHORIZED: - kpr.ec = TALER_JSON_get_error_code (j); + break; + case MHD_HTTP_FORBIDDEN: break; case MHD_HTTP_NOT_FOUND: - kpr.ec = TALER_JSON_get_error_code (j); break; case MHD_HTTP_BAD_GATEWAY: - kpr.ec = TALER_JSON_get_error_code (j); /* Server had an internal issue; we should retry, but this API leaves this to the application */ break; case MHD_HTTP_GATEWAY_TIMEOUT: - kpr.ec = TALER_JSON_get_error_code (j); /* Server had an internal issue; we should retry, but this API leaves this to the application */ break; default: /* unexpected response code */ GNUNET_break_op (0); - kpr.ec = TALER_JSON_get_error_code (j); GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Unexpected response code %u/%d for exchange kyc_proof\n", - (unsigned int) response_code, - (int) kpr.ec); + "Unexpected response code %u for exchange kyc_proof\n", + (unsigned int) response_code); break; } kph->cb (kph->cb_cls, @@ -187,16 +182,17 @@ TALER_EXCHANGE_kyc_proof (struct TALER_EXCHANGE_Handle *exchange, return NULL; } /* disable location following, we want to learn the - result of a 302 redirect! */ + result of a 303 redirect! */ GNUNET_assert (CURLE_OK == curl_easy_setopt (kph->eh, CURLOPT_FOLLOWLOCATION, 0L)); ctx = TEAH_handle_to_context (exchange); - kph->job = GNUNET_CURL_job_add_with_ct_json (ctx, - kph->eh, - &handle_kyc_proof_finished, - kph); + kph->job = GNUNET_CURL_job_add_raw (ctx, + kph->eh, + NULL, + &handle_kyc_proof_finished, + kph); return kph; } diff --git a/src/testing/test_exchange_api.c b/src/testing/test_exchange_api.c index c5544637b..72968c256 100644 --- a/src/testing/test_exchange_api.c +++ b/src/testing/test_exchange_api.c @@ -343,7 +343,7 @@ run (void *cls, /* Try resolving a deposit's WTID for a failed deposit. * As the deposit failed, the answer should be that the * exchange does NOT know about the deposit. - */// + */ TALER_TESTING_cmd_track_transaction ("deposit-wtid-failing", "deposit-double-2", 0, @@ -352,7 +352,7 @@ run (void *cls, /* Try resolving an undefined (all zeros) WTID; this * should fail as obviously the exchange didn't use that * WTID value for any transaction. - */// + */ TALER_TESTING_cmd_track_transfer_empty ("wire-deposit-failing", NULL, MHD_HTTP_NOT_FOUND), diff --git a/src/testing/test_kyc_api.c b/src/testing/test_kyc_api.c index ea2964958..b1a43df56 100644 --- a/src/testing/test_kyc_api.c +++ b/src/testing/test_kyc_api.c @@ -66,7 +66,7 @@ static struct TALER_TESTING_BankConfiguration bc; */ #define CMD_EXEC_AGGREGATOR(label) \ TALER_TESTING_cmd_sleep (label "-sleep", 1), \ - TALER_TESTING_cmd_exec_aggregator /*_with_kyc*/ (label, CONFIG_FILE), \ + TALER_TESTING_cmd_exec_aggregator_with_kyc (label, CONFIG_FILE), \ TALER_TESTING_cmd_exec_transfer (label, CONFIG_FILE) /** @@ -118,11 +118,39 @@ run (void *cls, GNUNET_TIME_UNIT_ZERO, "EUR:5", MHD_HTTP_OK), + TALER_TESTING_cmd_track_transaction ( + "track-deposit", + "deposit-simple", + 0, + MHD_HTTP_ACCEPTED, + NULL), TALER_TESTING_cmd_end () }; struct TALER_TESTING_Command track[] = { - CMD_EXEC_AGGREGATOR ("run-aggregator"), + CMD_EXEC_AGGREGATOR ("run-aggregator-before-kyc"), + TALER_TESTING_cmd_check_bank_empty ("check_bank_empty-no-kyc"), + TALER_TESTING_cmd_check_kyc_get ("check-kyc-deposit", + "track-deposit", + MHD_HTTP_ACCEPTED), + TALER_TESTING_cmd_proof_kyc ("proof-kyc-no-service", + "track-deposit", + "bad", + "state", + MHD_HTTP_BAD_GATEWAY), + TALER_TESTING_cmd_oauth ("start-oauth-service", + 6666), + TALER_TESTING_cmd_proof_kyc ("proof-kyc-fail", + "track-deposit", + "bad", + "state", + MHD_HTTP_FORBIDDEN), + TALER_TESTING_cmd_proof_kyc ("proof-kyc-fail", + "track-deposit", + "pass", + "state", + MHD_HTTP_SEE_OTHER), + CMD_EXEC_AGGREGATOR ("run-aggregator-after-kyc"), TALER_TESTING_cmd_check_bank_transfer ( "check_bank_transfer-499c", ec.exchange_url, diff --git a/src/testing/test_kyc_api.conf b/src/testing/test_kyc_api.conf index 03a5e2453..105ee3b26 100644 --- a/src/testing/test_kyc_api.conf +++ b/src/testing/test_kyc_api.conf @@ -45,6 +45,22 @@ DB = postgres # exchange (or the twister) is actually listening. BASE_URL = "http://localhost:8081/" + +KYC_MODE = OAUTH2 + +KYC_WALLET_BALANCE_LIMIT = EUR:1 + +KYC_WITHDRAW_PERIOD = "31 days" + +KYC_WITHDRAW_LIMIT = EUR:150 + +[exchange-kyc-oauth2] + +KYC_OAUTH2_URL = http://localhost:6666/ +KYC_OAUTH2_CLIENT_ID = taler-exchange +KYC_OAUTH2_CLIENT_SECRET = exchange-secret +KYC_OAUTH2_POST_URL = http://example.com/ + [exchangedb-postgres] CONFIG = "postgres:///talercheck" diff --git a/src/testing/testing_api_cmd_kyc_proof.c b/src/testing/testing_api_cmd_kyc_proof.c index bd6162698..fdd3affdc 100644 --- a/src/testing/testing_api_cmd_kyc_proof.c +++ b/src/testing/testing_api_cmd_kyc_proof.c @@ -89,9 +89,8 @@ proof_kyc_cb (void *cls, if (kcg->expected_response_code != kpr->http_status) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Unexpected response code %u/%d to command %s in %s:%u\n", + "Unexpected response code %u to command %s in %s:%u\n", kpr->http_status, - (int) kpr->ec, cmd->label, __FILE__, __LINE__); @@ -100,11 +99,18 @@ proof_kyc_cb (void *cls, } switch (kpr->http_status) { - case MHD_HTTP_FOUND: + case MHD_HTTP_SEE_OTHER: kcg->redirect_url = GNUNET_strdup (kpr->details.found.redirect_url); break; + case MHD_HTTP_FORBIDDEN: + break; + case MHD_HTTP_BAD_GATEWAY: + break; default: GNUNET_break (0); + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Unexpected response code %u to /kyc-proof\n", + kpr->http_status); break; } TALER_TESTING_interpreter_next (kcg->is); diff --git a/src/testing/testing_api_cmd_oauth.c b/src/testing/testing_api_cmd_oauth.c index 6cfad32bc..b71cc8386 100644 --- a/src/testing/testing_api_cmd_oauth.c +++ b/src/testing/testing_api_cmd_oauth.c @@ -46,6 +46,76 @@ struct OAuthState }; +struct RequestCtx +{ + struct MHD_PostProcessor *pp; + char *code; + char *client_id; + char *redirect_uri; + char *client_secret; +}; + + +static void +append (char **target, + const char *data, + size_t size) +{ + char *tmp; + + if (NULL == *target) + { + *target = GNUNET_strndup (data, + size); + return; + } + GNUNET_asprintf (&tmp, + "%s%.*s", + *target, + (int) size, + data); + GNUNET_free (*target); + *target = tmp; +} + + +static enum MHD_Result +handle_post (void *cls, + enum MHD_ValueKind kind, + const char *key, + const char *filename, + const char *content_type, + const char *transfer_encoding, + const char *data, + uint64_t off, + size_t size) +{ + struct RequestCtx *rc = cls; + + if (0 == strcmp (key, + "code")) + append (&rc->code, + data, + size); + if (0 == strcmp (key, + "client_id")) + append (&rc->client_id, + data, + size); + if (0 == strcmp (key, + "redirect_uri")) + append (&rc->redirect_uri, + data, + size); + if (0 == strcmp (key, + "client_secret")) + append (&rc->client_secret, + data, + size); + return MHD_YES; +} + + /** * A client has requested the given url using the given method * (#MHD_HTTP_METHOD_GET, #MHD_HTTP_METHOD_PUT, @@ -95,38 +165,52 @@ handler_cb (void *cls, size_t *upload_data_size, void **con_cls) { - const char *code; - const char *client_id; - const char *redirect_uri; - const char *client_secret; + struct RequestCtx *rc = *con_cls; unsigned int hc; json_t *body; + if (NULL == rc) + { + rc = GNUNET_new (struct RequestCtx); + *con_cls = rc; + rc->pp = MHD_create_post_processor (connection, + 4092, + &handle_post, + rc); + return MHD_YES; + } + if (0 != *upload_data_size) + { + enum MHD_Result ret; + + ret = MHD_post_process (rc->pp, + upload_data, + *upload_data_size); + *upload_data_size = 0; + return ret; + } + + /* NOTE: In the future, we MAY want to distinguish between the different URLs and possibly return more information. For now, just do the minimum: implement the main handler that checks the code. */ - code = MHD_lookup_connection_value (connection, - MHD_GET_ARGUMENT_KIND, - "code"); - client_id = MHD_lookup_connection_value (connection, - MHD_GET_ARGUMENT_KIND, - "client_id"); - redirect_uri = MHD_lookup_connection_value (connection, - MHD_GET_ARGUMENT_KIND, - "redirect_uri"); - client_secret = MHD_lookup_connection_value (connection, - MHD_GET_ARGUMENT_KIND, - "client_secret"); - if ( (NULL == code) || - (NULL == client_id) || - (NULL == redirect_uri) || - (NULL == client_secret) ) + if ( (NULL == rc->code) || + (NULL == rc->client_id) || + (NULL == rc->redirect_uri) || + (NULL == rc->client_secret) ) { GNUNET_break (0); + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Bad request to Oauth faker: `%s' with %s/%s/%s/%s\n", + url, + rc->code, + rc->client_id, + rc->redirect_uri, + rc->client_secret); return MHD_NO; } - if (0 != strcmp (client_id, + if (0 != strcmp (rc->client_id, "taler-exchange")) { body = GNUNET_JSON_PACK ( @@ -136,7 +220,7 @@ handler_cb (void *cls, "only 'taler-exchange' is allowed")); hc = MHD_HTTP_NOT_FOUND; } - else if (0 != strcmp (client_secret, + else if (0 != strcmp (rc->client_secret, "exchange-secret")) { body = GNUNET_JSON_PACK ( @@ -148,7 +232,7 @@ handler_cb (void *cls, } else { - if (0 != strcmp (code, + if (0 != strcmp (rc->code, "pass")) { body = GNUNET_JSON_PACK ( @@ -178,6 +262,24 @@ handler_cb (void *cls, } +static void +cleanup (void *cls, + struct MHD_Connection *connection, + void **con_cls, + enum MHD_RequestTerminationCode toe) +{ + struct RequestCtx *rc = *con_cls; + + if (NULL == rc) + return; + GNUNET_free (rc->code); + GNUNET_free (rc->client_id); + GNUNET_free (rc->redirect_uri); + GNUNET_free (rc->client_secret); + GNUNET_free (rc); +} + + /** * Run the command. * @@ -193,12 +295,13 @@ oauth_run (void *cls, struct OAuthState *oas = cls; (void) cmd; - (void) is; oas->mhd = MHD_start_daemon (MHD_USE_AUTO_INTERNAL_THREAD, oas->port, NULL, NULL, &handler_cb, oas, + MHD_OPTION_NOTIFY_COMPLETED, &cleanup, NULL, NULL); + TALER_TESTING_interpreter_next (is); }