get first KYC test to pass

This commit is contained in:
Christian Grothoff 2021-11-09 15:39:31 +01:00
parent a79cc16067
commit a9b2140b1e
No known key found for this signature in database
GPG Key ID: 939E6BE1E29FC3CC
9 changed files with 229 additions and 74 deletions

View File

@ -90,7 +90,9 @@ KYC_MODE = NONE
# Balance threshold above which wallets are told # Balance threshold above which wallets are told
# to undergo a KYC check at the exchange. Optional, # to undergo a KYC check at the exchange. Optional,
# if not given there is no limit. # 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] [exchange-kyc-oauth2]
@ -105,4 +107,4 @@ KYC_MODE = NONE
# Where to redirect clients after successful # Where to redirect clients after successful
# authorization? # authorization?
# KYC_OAUTH_POST_URL = https://bank.com/ # KYC_OAUTH2_POST_URL = https://bank.com/

View File

@ -108,6 +108,24 @@ static struct KycProofContext *kpc_head;
static struct KycProofContext *kpc_tail; 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 void
TEH_kyc_proof_cleanup (void) TEH_kyc_proof_cleanup (void)
{ {
@ -120,11 +138,7 @@ TEH_kyc_proof_cleanup (void)
GNUNET_CURL_job_cancel (kpc->job); GNUNET_CURL_job_cancel (kpc->job);
kpc->job = NULL; kpc->job = NULL;
} }
GNUNET_CONTAINER_DLL_remove (kpc_head, kpc_resume (kpc);
kpc_tail,
kpc);
kpc->suspended = GNUNET_NO;
MHD_resume_connection (kpc->rc->connection);
} }
} }
@ -210,8 +224,7 @@ handle_curl_login_finished (void *cls,
"Unexpected response from KYC gateway"); "Unexpected response from KYC gateway");
kpc->response_code kpc->response_code
= MHD_HTTP_BAD_GATEWAY; = MHD_HTTP_BAD_GATEWAY;
MHD_resume_connection (kpc->rc->connection); kpc_resume (kpc);
TALER_MHD_daemon_trigger ();
return; return;
} }
} }
@ -225,8 +238,7 @@ handle_curl_login_finished (void *cls,
"Unexpected token type in response from KYC gateway"); "Unexpected token type in response from KYC gateway");
kpc->response_code kpc->response_code
= MHD_HTTP_BAD_GATEWAY; = MHD_HTTP_BAD_GATEWAY;
MHD_resume_connection (kpc->rc->connection); kpc_resume (kpc);
TALER_MHD_daemon_trigger ();
return; return;
} }
@ -234,8 +246,7 @@ handle_curl_login_finished (void *cls,
possibly use the access_token to download information we should possibly use the access_token to download information we should
persist; then continue! */ persist; then continue! */
MHD_resume_connection (kpc->rc->connection); kpc_resume (kpc);
TALER_MHD_daemon_trigger ();
return; return;
} }
default: default:
@ -268,8 +279,7 @@ handle_curl_login_finished (void *cls,
"Unexpected response from KYC gateway"); "Unexpected response from KYC gateway");
kpc->response_code kpc->response_code
= MHD_HTTP_BAD_GATEWAY; = MHD_HTTP_BAD_GATEWAY;
MHD_resume_connection (kpc->rc->connection); kpc_resume (kpc);
TALER_MHD_daemon_trigger ();
return; return;
} }
} }
@ -292,8 +302,7 @@ handle_curl_login_finished (void *cls,
GNUNET_free (reply); GNUNET_free (reply);
} }
kpc->response_code = MHD_HTTP_FORBIDDEN; kpc->response_code = MHD_HTTP_FORBIDDEN;
MHD_resume_connection (kpc->rc->connection); kpc_resume (kpc);
TALER_MHD_daemon_trigger ();
} }
break; break;
} }
@ -387,7 +396,7 @@ TEH_handler_kyc_proof (
"curl_easy_init"); "curl_easy_init");
} }
GNUNET_asprintf (&kpc->token_url, GNUNET_asprintf (&kpc->token_url,
"%s/token", "%stoken",
TEH_kyc_config.details.oauth2.url); TEH_kyc_config.details.oauth2.url);
GNUNET_assert (CURLE_OK == GNUNET_assert (CURLE_OK ==
curl_easy_setopt (eh, curl_easy_setopt (eh,
@ -411,7 +420,7 @@ TEH_handler_kyc_proof (
char *request_uri; char *request_uri;
GNUNET_asprintf (&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.url,
TEH_kyc_config.details.oauth2.client_id); TEH_kyc_config.details.oauth2.client_id);
redirect_uri = curl_easy_escape (eh, redirect_uri = curl_easy_escape (eh,

View File

@ -2170,11 +2170,6 @@ struct TALER_EXCHANGE_KycProofResponse
*/ */
unsigned int http_status; unsigned int http_status;
/**
* Taler error code, if any.
*/
enum TALER_ErrorCode ec;
union union
{ {

View File

@ -75,15 +75,16 @@ struct TALER_EXCHANGE_KycProofHandle
* *
* @param cls the `struct TALER_EXCHANGE_KycProofHandle` * @param cls the `struct TALER_EXCHANGE_KycProofHandle`
* @param response_code HTTP response code, 0 on error * @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 static void
handle_kyc_proof_finished (void *cls, handle_kyc_proof_finished (void *cls,
long response_code, long response_code,
const void *response) const void *body,
size_t body_size)
{ {
struct TALER_EXCHANGE_KycProofHandle *kph = cls; struct TALER_EXCHANGE_KycProofHandle *kph = cls;
const json_t *j = response;
struct TALER_EXCHANGE_KycProofResponse kpr = { struct TALER_EXCHANGE_KycProofResponse kpr = {
.http_status = (unsigned int) response_code .http_status = (unsigned int) response_code
}; };
@ -92,9 +93,8 @@ handle_kyc_proof_finished (void *cls,
switch (response_code) switch (response_code)
{ {
case 0: case 0:
kpr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
break; break;
case MHD_HTTP_FOUND: case MHD_HTTP_SEE_OTHER:
{ {
char *redirect_url; char *redirect_url;
@ -106,34 +106,29 @@ handle_kyc_proof_finished (void *cls,
break; break;
} }
case MHD_HTTP_BAD_REQUEST: case MHD_HTTP_BAD_REQUEST:
kpr.ec = TALER_JSON_get_error_code (j);
/* This should never happen, either us or the exchange is buggy /* This should never happen, either us or the exchange is buggy
(or API version conflict); just pass JSON reply to the application */ (or API version conflict); just pass JSON reply to the application */
break; break;
case MHD_HTTP_UNAUTHORIZED: case MHD_HTTP_UNAUTHORIZED:
kpr.ec = TALER_JSON_get_error_code (j); break;
case MHD_HTTP_FORBIDDEN:
break; break;
case MHD_HTTP_NOT_FOUND: case MHD_HTTP_NOT_FOUND:
kpr.ec = TALER_JSON_get_error_code (j);
break; break;
case MHD_HTTP_BAD_GATEWAY: case MHD_HTTP_BAD_GATEWAY:
kpr.ec = TALER_JSON_get_error_code (j);
/* Server had an internal issue; we should retry, but this API /* Server had an internal issue; we should retry, but this API
leaves this to the application */ leaves this to the application */
break; break;
case MHD_HTTP_GATEWAY_TIMEOUT: case MHD_HTTP_GATEWAY_TIMEOUT:
kpr.ec = TALER_JSON_get_error_code (j);
/* Server had an internal issue; we should retry, but this API /* Server had an internal issue; we should retry, but this API
leaves this to the application */ leaves this to the application */
break; break;
default: default:
/* unexpected response code */ /* unexpected response code */
GNUNET_break_op (0); GNUNET_break_op (0);
kpr.ec = TALER_JSON_get_error_code (j);
GNUNET_log (GNUNET_ERROR_TYPE_ERROR, GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Unexpected response code %u/%d for exchange kyc_proof\n", "Unexpected response code %u for exchange kyc_proof\n",
(unsigned int) response_code, (unsigned int) response_code);
(int) kpr.ec);
break; break;
} }
kph->cb (kph->cb_cls, kph->cb (kph->cb_cls,
@ -187,16 +182,17 @@ TALER_EXCHANGE_kyc_proof (struct TALER_EXCHANGE_Handle *exchange,
return NULL; return NULL;
} }
/* disable location following, we want to learn the /* disable location following, we want to learn the
result of a 302 redirect! */ result of a 303 redirect! */
GNUNET_assert (CURLE_OK == GNUNET_assert (CURLE_OK ==
curl_easy_setopt (kph->eh, curl_easy_setopt (kph->eh,
CURLOPT_FOLLOWLOCATION, CURLOPT_FOLLOWLOCATION,
0L)); 0L));
ctx = TEAH_handle_to_context (exchange); ctx = TEAH_handle_to_context (exchange);
kph->job = GNUNET_CURL_job_add_with_ct_json (ctx, kph->job = GNUNET_CURL_job_add_raw (ctx,
kph->eh, kph->eh,
&handle_kyc_proof_finished, NULL,
kph); &handle_kyc_proof_finished,
kph);
return kph; return kph;
} }

View File

@ -343,7 +343,7 @@ run (void *cls,
/* Try resolving a deposit's WTID for a failed deposit. /* Try resolving a deposit's WTID for a failed deposit.
* As the deposit failed, the answer should be that the * As the deposit failed, the answer should be that the
* exchange does NOT know about the deposit. * exchange does NOT know about the deposit.
*/// */
TALER_TESTING_cmd_track_transaction ("deposit-wtid-failing", TALER_TESTING_cmd_track_transaction ("deposit-wtid-failing",
"deposit-double-2", "deposit-double-2",
0, 0,
@ -352,7 +352,7 @@ run (void *cls,
/* Try resolving an undefined (all zeros) WTID; this /* Try resolving an undefined (all zeros) WTID; this
* should fail as obviously the exchange didn't use that * should fail as obviously the exchange didn't use that
* WTID value for any transaction. * WTID value for any transaction.
*/// */
TALER_TESTING_cmd_track_transfer_empty ("wire-deposit-failing", TALER_TESTING_cmd_track_transfer_empty ("wire-deposit-failing",
NULL, NULL,
MHD_HTTP_NOT_FOUND), MHD_HTTP_NOT_FOUND),

View File

@ -66,7 +66,7 @@ static struct TALER_TESTING_BankConfiguration bc;
*/ */
#define CMD_EXEC_AGGREGATOR(label) \ #define CMD_EXEC_AGGREGATOR(label) \
TALER_TESTING_cmd_sleep (label "-sleep", 1), \ 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) TALER_TESTING_cmd_exec_transfer (label, CONFIG_FILE)
/** /**
@ -118,11 +118,39 @@ run (void *cls,
GNUNET_TIME_UNIT_ZERO, GNUNET_TIME_UNIT_ZERO,
"EUR:5", "EUR:5",
MHD_HTTP_OK), MHD_HTTP_OK),
TALER_TESTING_cmd_track_transaction (
"track-deposit",
"deposit-simple",
0,
MHD_HTTP_ACCEPTED,
NULL),
TALER_TESTING_cmd_end () TALER_TESTING_cmd_end ()
}; };
struct TALER_TESTING_Command track[] = { 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 ( TALER_TESTING_cmd_check_bank_transfer (
"check_bank_transfer-499c", "check_bank_transfer-499c",
ec.exchange_url, ec.exchange_url,

View File

@ -45,6 +45,22 @@ DB = postgres
# exchange (or the twister) is actually listening. # exchange (or the twister) is actually listening.
BASE_URL = "http://localhost:8081/" 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] [exchangedb-postgres]
CONFIG = "postgres:///talercheck" CONFIG = "postgres:///talercheck"

View File

@ -89,9 +89,8 @@ proof_kyc_cb (void *cls,
if (kcg->expected_response_code != kpr->http_status) if (kcg->expected_response_code != kpr->http_status)
{ {
GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 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, kpr->http_status,
(int) kpr->ec,
cmd->label, cmd->label,
__FILE__, __FILE__,
__LINE__); __LINE__);
@ -100,11 +99,18 @@ proof_kyc_cb (void *cls,
} }
switch (kpr->http_status) switch (kpr->http_status)
{ {
case MHD_HTTP_FOUND: case MHD_HTTP_SEE_OTHER:
kcg->redirect_url = GNUNET_strdup (kpr->details.found.redirect_url); kcg->redirect_url = GNUNET_strdup (kpr->details.found.redirect_url);
break; break;
case MHD_HTTP_FORBIDDEN:
break;
case MHD_HTTP_BAD_GATEWAY:
break;
default: default:
GNUNET_break (0); GNUNET_break (0);
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Unexpected response code %u to /kyc-proof\n",
kpr->http_status);
break; break;
} }
TALER_TESTING_interpreter_next (kcg->is); TALER_TESTING_interpreter_next (kcg->is);

View File

@ -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 * A client has requested the given url using the given method
* (#MHD_HTTP_METHOD_GET, #MHD_HTTP_METHOD_PUT, * (#MHD_HTTP_METHOD_GET, #MHD_HTTP_METHOD_PUT,
@ -95,38 +165,52 @@ handler_cb (void *cls,
size_t *upload_data_size, size_t *upload_data_size,
void **con_cls) void **con_cls)
{ {
const char *code; struct RequestCtx *rc = *con_cls;
const char *client_id;
const char *redirect_uri;
const char *client_secret;
unsigned int hc; unsigned int hc;
json_t *body; 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 /* NOTE: In the future, we MAY want to distinguish between
the different URLs and possibly return more information. the different URLs and possibly return more information.
For now, just do the minimum: implement the main handler For now, just do the minimum: implement the main handler
that checks the code. */ that checks the code. */
code = MHD_lookup_connection_value (connection, if ( (NULL == rc->code) ||
MHD_GET_ARGUMENT_KIND, (NULL == rc->client_id) ||
"code"); (NULL == rc->redirect_uri) ||
client_id = MHD_lookup_connection_value (connection, (NULL == rc->client_secret) )
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) )
{ {
GNUNET_break (0); 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; return MHD_NO;
} }
if (0 != strcmp (client_id, if (0 != strcmp (rc->client_id,
"taler-exchange")) "taler-exchange"))
{ {
body = GNUNET_JSON_PACK ( body = GNUNET_JSON_PACK (
@ -136,7 +220,7 @@ handler_cb (void *cls,
"only 'taler-exchange' is allowed")); "only 'taler-exchange' is allowed"));
hc = MHD_HTTP_NOT_FOUND; hc = MHD_HTTP_NOT_FOUND;
} }
else if (0 != strcmp (client_secret, else if (0 != strcmp (rc->client_secret,
"exchange-secret")) "exchange-secret"))
{ {
body = GNUNET_JSON_PACK ( body = GNUNET_JSON_PACK (
@ -148,7 +232,7 @@ handler_cb (void *cls,
} }
else else
{ {
if (0 != strcmp (code, if (0 != strcmp (rc->code,
"pass")) "pass"))
{ {
body = GNUNET_JSON_PACK ( 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. * Run the command.
* *
@ -193,12 +295,13 @@ oauth_run (void *cls,
struct OAuthState *oas = cls; struct OAuthState *oas = cls;
(void) cmd; (void) cmd;
(void) is;
oas->mhd = MHD_start_daemon (MHD_USE_AUTO_INTERNAL_THREAD, oas->mhd = MHD_start_daemon (MHD_USE_AUTO_INTERNAL_THREAD,
oas->port, oas->port,
NULL, NULL, NULL, NULL,
&handler_cb, oas, &handler_cb, oas,
MHD_OPTION_NOTIFY_COMPLETED, &cleanup, NULL,
NULL); NULL);
TALER_TESTING_interpreter_next (is);
} }