-fix kycaid logic issues

This commit is contained in:
Christian Grothoff 2022-08-17 14:36:16 +02:00
parent ba006cd61b
commit b2a67fcff9
No known key found for this signature in database
GPG Key ID: 939E6BE1E29FC3CC
3 changed files with 123 additions and 37 deletions

View File

@ -748,6 +748,9 @@ TALER_KYCLOGIC_kyc_done (void)
{ {
struct TALER_KYCLOGIC_KycCheck *kc = kyc_checks[i]; struct TALER_KYCLOGIC_KycCheck *kc = kyc_checks[i];
GNUNET_array_grow (kc->providers,
kc->num_providers,
0);
GNUNET_free (kc->name); GNUNET_free (kc->name);
GNUNET_free (kc); GNUNET_free (kc);
} }

View File

@ -720,10 +720,54 @@ kycaid_webhook_cancel (struct TALER_KYCLOGIC_WebhookHandle *wh)
} }
GNUNET_free (wh->verification_id); GNUNET_free (wh->verification_id);
GNUNET_free (wh->applicant_id); GNUNET_free (wh->applicant_id);
GNUNET_free (wh->url);
GNUNET_free (wh); GNUNET_free (wh);
} }
/**
* Extract KYC failure reasons and log those
*
* @param verifications JSON object with failure details
*/
static void
log_failure (json_t *verifications)
{
json_t *member;
const char *name;
json_object_foreach (verifications, name, member)
{
bool iverified;
const char *comment;
struct GNUNET_JSON_Specification spec[] = {
GNUNET_JSON_spec_bool ("verified",
&iverified),
GNUNET_JSON_spec_string ("comment",
&comment),
GNUNET_JSON_spec_end ()
};
if (GNUNET_OK !=
GNUNET_JSON_parse (member,
spec,
NULL, NULL))
{
GNUNET_break_op (0);
json_dumpf (member,
stderr,
JSON_INDENT (2));
continue;
}
if (iverified)
continue;
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"KYC verification of attribute `%s' failed: %s\n",
name,
comment);
}
}
/** /**
* Function called when we're done processing the * Function called when we're done processing the
* HTTP "/verifications/{verification_id}" request. * HTTP "/verifications/{verification_id}" request.
@ -749,7 +793,6 @@ handle_webhook_finished (void *cls,
const char *applicant_id; const char *applicant_id;
const char *verification_id; const char *verification_id;
const char *status; const char *status;
const char *type;
bool verified; bool verified;
json_t *verifications; json_t *verifications;
struct GNUNET_JSON_Specification spec[] = { struct GNUNET_JSON_Specification spec[] = {
@ -757,10 +800,8 @@ handle_webhook_finished (void *cls,
&applicant_id), &applicant_id),
GNUNET_JSON_spec_string ("verification_id", GNUNET_JSON_spec_string ("verification_id",
&verification_id), &verification_id),
GNUNET_JSON_spec_string ("type",
&type),
GNUNET_JSON_spec_string ("status", GNUNET_JSON_spec_string ("status",
&status), &status), /* completed, pending, ... */
GNUNET_JSON_spec_bool ("verified", GNUNET_JSON_spec_bool ("verified",
&verified), &verified),
GNUNET_JSON_spec_json ("verifications", GNUNET_JSON_spec_json ("verifications",
@ -794,12 +835,10 @@ handle_webhook_finished (void *cls,
resp); resp);
break; break;
} }
/* FIXME: comment out, unless debugging ... */ if (! verified)
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, {
"The provider returned the following verifications:\n"); log_failure (verifications);
json_dumpf (verifications, }
stderr,
JSON_INDENT (2));
resp = MHD_create_response_from_buffer (0, resp = MHD_create_response_from_buffer (0,
"", "",
MHD_RESPMEM_PERSISTENT); MHD_RESPMEM_PERSISTENT);
@ -980,11 +1019,15 @@ async_webhook_reply (void *cls)
{ {
struct TALER_KYCLOGIC_WebhookHandle *wh = cls; struct TALER_KYCLOGIC_WebhookHandle *wh = cls;
fprintf (stderr,
"async reply\n");
wh->cb (wh->cb_cls, wh->cb (wh->cb_cls,
0LLU, /* legitimization row ID (unknown) */ wh->legi_row,
NULL, /* our account ID */ (0 == wh->legi_row)
NULL, /* provider user ID */ ? NULL
NULL, /* provider legi ID */ : &wh->h_payto,
wh->applicant_id, /* provider user ID */
wh->verification_id, /* provider legi ID */
TALER_KYCLOGIC_STATUS_PROVIDER_FAILED, TALER_KYCLOGIC_STATUS_PROVIDER_FAILED,
GNUNET_TIME_UNIT_ZERO_ABS, /* expiration */ GNUNET_TIME_UNIT_ZERO_ABS, /* expiration */
wh->response_code, wh->response_code,
@ -994,7 +1037,11 @@ async_webhook_reply (void *cls)
/** /**
* Check KYC status and return result for Webhook. * Check KYC status and return result for Webhook. We do NOT implement the
* authentication check proposed by the KYCAID documentation, as it would
* allow an attacker who learns the access token to easily bypass the KYC
* checks. Instead, we insist on explicitly requesting the KYC status from the
* provider (at least on success).
* *
* @param cls the @e cls of this struct with the plugin-specific state * @param cls the @e cls of this struct with the plugin-specific state
* @param pd provider configuration details * @param pd provider configuration details
@ -1085,6 +1132,7 @@ kycaid_webhook (void *cls,
wh->response_code = MHD_HTTP_INTERNAL_SERVER_ERROR; wh->response_code = MHD_HTTP_INTERNAL_SERVER_ERROR;
wh->task = GNUNET_SCHEDULER_add_now (&async_webhook_reply, wh->task = GNUNET_SCHEDULER_add_now (&async_webhook_reply,
wh); wh);
GNUNET_JSON_parse_free (spec);
return wh; return wh;
} }
if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
@ -1098,10 +1146,25 @@ kycaid_webhook (void *cls,
wh->response_code = MHD_HTTP_NOT_FOUND; wh->response_code = MHD_HTTP_NOT_FOUND;
wh->task = GNUNET_SCHEDULER_add_now (&async_webhook_reply, wh->task = GNUNET_SCHEDULER_add_now (&async_webhook_reply,
wh); wh);
GNUNET_JSON_parse_free (spec);
return wh; return wh;
} }
wh->verification_id = GNUNET_strdup (verification_id); wh->verification_id = GNUNET_strdup (verification_id);
wh->applicant_id = GNUNET_strdup (applicant_id); wh->applicant_id = GNUNET_strdup (applicant_id);
if (! verified)
{
/* We don't need to re-confirm the failure by
asking the API again. */
log_failure (verifications);
wh->response_code = MHD_HTTP_NO_CONTENT;
wh->resp = MHD_create_response_from_buffer (0,
"",
MHD_RESPMEM_PERSISTENT);
wh->task = GNUNET_SCHEDULER_add_now (&async_webhook_reply,
wh);
GNUNET_JSON_parse_free (spec);
return wh;
}
eh = curl_easy_init (); eh = curl_easy_init ();
if (NULL == eh) if (NULL == eh)
@ -1113,6 +1176,7 @@ kycaid_webhook (void *cls,
wh->response_code = MHD_HTTP_INTERNAL_SERVER_ERROR; wh->response_code = MHD_HTTP_INTERNAL_SERVER_ERROR;
wh->task = GNUNET_SCHEDULER_add_now (&async_webhook_reply, wh->task = GNUNET_SCHEDULER_add_now (&async_webhook_reply,
wh); wh);
GNUNET_JSON_parse_free (spec);
return wh; return wh;
} }
@ -1122,7 +1186,7 @@ kycaid_webhook (void *cls,
GNUNET_break (CURLE_OK == GNUNET_break (CURLE_OK ==
curl_easy_setopt (eh, curl_easy_setopt (eh,
CURLOPT_VERBOSE, CURLOPT_VERBOSE,
1)); 0));
GNUNET_assert (CURLE_OK == GNUNET_assert (CURLE_OK ==
curl_easy_setopt (eh, curl_easy_setopt (eh,
CURLOPT_MAXREDIRS, CURLOPT_MAXREDIRS,
@ -1131,18 +1195,18 @@ kycaid_webhook (void *cls,
curl_easy_setopt (eh, curl_easy_setopt (eh,
CURLOPT_URL, CURLOPT_URL,
wh->url)); wh->url));
wh->job = GNUNET_CURL_job_add (ps->curl_ctx, wh->job = GNUNET_CURL_job_add2 (ps->curl_ctx,
eh, eh,
&handle_webhook_finished, pd->slist,
wh); &handle_webhook_finished,
GNUNET_CURL_extend_headers (wh->job, wh);
pd->slist); GNUNET_JSON_parse_free (spec);
return wh; return wh;
} }
/** /**
* Initialize Kycaid.0 KYC logic plugin * Initialize kycaid logic plugin
* *
* @param cls a configuration instance * @param cls a configuration instance
* @return NULL on error, otherwise a `struct TALER_KYCLOGIC_Plugin` * @return NULL on error, otherwise a `struct TALER_KYCLOGIC_Plugin`

View File

@ -81,6 +81,11 @@ struct TEKT_RequestContext
*/ */
void *rh_ctx; void *rh_ctx;
/**
* Uploaded JSON body, if any.
*/
json_t *root;
/** /**
* HTTP status to return upon resume if @e response * HTTP status to return upon resume if @e response
* is non-NULL. * is non-NULL.
@ -555,6 +560,8 @@ handler_kyc_webhook_generic (
TALER_EC_EXCHANGE_KYC_GENERIC_LOGIC_UNKNOWN, TALER_EC_EXCHANGE_KYC_GENERIC_LOGIC_UNKNOWN,
"$LOGIC"); "$LOGIC");
} }
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Calling KYC provider specific webhook\n");
kwh->wh = kwh->plugin->webhook (kwh->plugin->cls, kwh->wh = kwh->plugin->webhook (kwh->plugin->cls,
kwh->pd, kwh->pd,
&kyc_provider_account_lookup, &kyc_provider_account_lookup,
@ -583,6 +590,8 @@ handler_kyc_webhook_generic (
if (NULL != kwh->response) if (NULL != kwh->response)
{ {
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Returning queued reply for KWH\n");
/* handle _failed_ resumed cases */ /* handle _failed_ resumed cases */
return MHD_queue_response (rc->connection, return MHD_queue_response (rc->connection,
kwh->response_code, kwh->response_code,
@ -845,7 +854,6 @@ proceed_with_handler (struct TEKT_RequestContext *rc,
const struct TEKT_RequestHandler *rh = rc->rh; const struct TEKT_RequestHandler *rh = rc->rh;
const char *args[rh->nargs + 2]; const char *args[rh->nargs + 2];
size_t ulen = strlen (url) + 1; size_t ulen = strlen (url) + 1;
json_t *root = NULL;
MHD_RESULT ret; MHD_RESULT ret;
/* We do check for "ulen" here, because we'll later stack-allocate a buffer /* We do check for "ulen" here, because we'll later stack-allocate a buffer
@ -866,8 +874,9 @@ proceed_with_handler (struct TEKT_RequestContext *rc,
/* All POST endpoints come with a body in JSON format. So we parse /* All POST endpoints come with a body in JSON format. So we parse
the JSON here. */ the JSON here. */
if (0 == strcasecmp (rh->method, if ( (NULL == rc->root) &&
MHD_HTTP_METHOD_POST)) (0 == strcasecmp (rh->method,
MHD_HTTP_METHOD_POST)) )
{ {
enum GNUNET_GenericReturnValue res; enum GNUNET_GenericReturnValue res;
@ -875,16 +884,17 @@ proceed_with_handler (struct TEKT_RequestContext *rc,
&rc->opaque_post_parsing_context, &rc->opaque_post_parsing_context,
upload_data, upload_data,
upload_data_size, upload_data_size,
&root); &rc->root);
if (GNUNET_SYSERR == res) 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 */ return MHD_NO; /* bad upload, could not even generate error */
} }
if ( (GNUNET_NO == res) || 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 */ return MHD_YES; /* so far incomplete upload or parser error */
} }
} }
@ -921,7 +931,6 @@ proceed_with_handler (struct TEKT_RequestContext *rc,
rh->url, rh->url,
url); url);
GNUNET_break_op (0); GNUNET_break_op (0);
json_decref (root);
return TALER_MHD_reply_with_error (rc->connection, return TALER_MHD_reply_with_error (rc->connection,
MHD_HTTP_NOT_FOUND, MHD_HTTP_NOT_FOUND,
TALER_EC_EXCHANGE_GENERIC_WRONG_NUMBER_OF_SEGMENTS, TALER_EC_EXCHANGE_GENERIC_WRONG_NUMBER_OF_SEGMENTS,
@ -931,16 +940,15 @@ proceed_with_handler (struct TEKT_RequestContext *rc,
/* Above logic ensures that 'root' is exactly non-NULL for POST operations, /* Above logic ensures that 'root' is exactly non-NULL for POST operations,
so we test for 'root' to decide which handler to invoke. */ so we test for 'root' to decide which handler to invoke. */
if (NULL != root) if (NULL != rc->root)
ret = rh->handler.post (rc, ret = rh->handler.post (rc,
root, rc->root,
args); args);
else /* We also only have "POST" or "GET" in the API for at this point else /* We also only have "POST" or "GET" in the API for at this point
(OPTIONS/HEAD are taken care of earlier) */ (OPTIONS/HEAD are taken care of earlier) */
ret = rh->handler.get (rc, ret = rh->handler.get (rc,
args); args);
} }
json_decref (root);
return ret; return ret;
} }
@ -949,8 +957,15 @@ static void
rh_cleaner_cb (struct TEKT_RequestContext *rc) rh_cleaner_cb (struct TEKT_RequestContext *rc)
{ {
if (NULL != rc->response) if (NULL != rc->response)
{
MHD_destroy_response (rc->response); MHD_destroy_response (rc->response);
GNUNET_free (rc); rc->response = NULL;
}
if (NULL != rc->root)
{
json_decref (rc->root);
rc->root = NULL;
}
} }
@ -1082,7 +1097,8 @@ handle_mhd_request (void *cls,
continue; continue;
found = true; found = true;
/* The URL is a match! What we now do depends on the method. */ /* 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); return TALER_MHD_reply_cors_preflight (connection);
} }
@ -1276,7 +1292,7 @@ initiate_cb (
return; return;
} }
fprintf (stdout, fprintf (stdout,
"Visit `%s' to begin KYC process (-u: '%s', -l: '%s')\n", "Visit `%s' to begin KYC process (-u: '%s', -U: '%s')\n",
redirect_url, redirect_url,
provider_user_id, provider_user_id,
provider_legitimization_id); provider_legitimization_id);
@ -1393,6 +1409,9 @@ run (void *cls,
GNUNET_SCHEDULER_shutdown (); GNUNET_SCHEDULER_shutdown ();
return; 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 = MHD_start_daemon (MHD_USE_SUSPEND_RESUME
| MHD_USE_PIPE_FOR_SHUTDOWN | MHD_USE_PIPE_FOR_SHUTDOWN
| MHD_USE_DEBUG | MHD_USE_DUAL_STACK | MHD_USE_DEBUG | MHD_USE_DUAL_STACK