aboutsummaryrefslogtreecommitdiff
path: root/src/kyclogic
diff options
context:
space:
mode:
authorChristian Grothoff <christian@grothoff.org>2022-08-11 23:35:33 +0200
committerChristian Grothoff <christian@grothoff.org>2022-08-11 23:35:33 +0200
commit1009084e94b8e8cf19e3b5568c3cccaba2bd2209 (patch)
treea346997dedd05f685ba7addc59e288dfa550ad0e /src/kyclogic
parentb061ea85c84facfc78c34edface367c5f040bc9c (diff)
downloadexchange-1009084e94b8e8cf19e3b5568c3cccaba2bd2209.tar.xz
major rework of the KYC logic, making it more configurable, not complete, but tests pass again
Diffstat (limited to 'src/kyclogic')
-rw-r--r--src/kyclogic/kyclogic_api.c78
-rw-r--r--src/kyclogic/plugin_kyclogic_oauth2.c186
-rw-r--r--src/kyclogic/plugin_kyclogic_template.c2
-rw-r--r--src/kyclogic/taler-exchange-kyc-tester.c5
4 files changed, 256 insertions, 15 deletions
diff --git a/src/kyclogic/kyclogic_api.c b/src/kyclogic/kyclogic_api.c
index f2d31acf5..84b88f339 100644
--- a/src/kyclogic/kyclogic_api.c
+++ b/src/kyclogic/kyclogic_api.c
@@ -279,12 +279,22 @@ load_logic (const struct GNUNET_CONFIGURATION_Handle *cfg,
GNUNET_asprintf (&lib_name,
"libtaler_plugin_kyclogic_%s",
name);
+ for (unsigned int i = 0; i<num_kyc_logics; i++)
+ if (0 == strcmp (lib_name,
+ kyc_logics[i]->library_name))
+ {
+ GNUNET_free (lib_name);
+ return kyc_logics[i];
+ }
plugin = GNUNET_PLUGIN_load (lib_name,
(void *) cfg);
if (NULL != plugin)
plugin->library_name = lib_name;
else
GNUNET_free (lib_name);
+ GNUNET_array_append (kyc_logics,
+ num_kyc_logics,
+ plugin);
return plugin;
}
@@ -471,6 +481,14 @@ add_provider (const struct GNUNET_CONFIGURATION_Handle *cfg,
}
+/**
+ * Parse configuration @a cfg in section @a section for
+ * the specification of a KYC trigger.
+ *
+ * @param cfg configuration to parse
+ * @param section configuration section to parse
+ * @return #GNUNET_OK on success
+ */
static enum GNUNET_GenericReturnValue
add_trigger (const struct GNUNET_CONFIGURATION_Handle *cfg,
const char *section)
@@ -797,6 +815,9 @@ eval_trigger (void *cls,
struct GNUNET_TIME_Relative duration;
bool bump = true;
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "KYC check with new amount %s\n",
+ TALER_amount2s (amount));
duration = GNUNET_TIME_absolute_get_duration (date);
if (ttc->have_total)
{
@@ -812,19 +833,31 @@ eval_trigger (void *cls,
else
{
ttc->total = *amount;
+ ttc->have_total = true;
}
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "KYC check: new total is %s\n",
+ TALER_amount2s (&ttc->total));
for (unsigned int i = ttc->start; i<num_kyc_triggers; i++)
{
const struct TALER_KYCLOGIC_KycTrigger *kt = kyc_triggers[i];
if (ttc->event != kt->trigger)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "KYC check #%u: trigger type does not match\n",
+ i);
continue;
+ }
duration = GNUNET_TIME_relative_max (duration,
kt->timeframe);
if (GNUNET_TIME_relative_cmp (kt->timeframe,
>,
duration))
{
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "KYC check #%u: amount is beyond time limit\n",
+ i);
if (bump)
ttc->start = i;
return GNUNET_OK;
@@ -833,6 +866,9 @@ eval_trigger (void *cls,
TALER_amount_cmp (&ttc->total,
&kt->threshold))
{
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "KYC check #%u: amount is below treshold\n",
+ i);
if (bump)
ttc->start = i;
bump = false;
@@ -848,6 +884,9 @@ eval_trigger (void *cls,
for (unsigned int k = 0; k<*ttc->needed_cnt; k++)
if (ttc->needed[k] == rc)
{
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "KYC rule #%u already listed\n",
+ j);
found = true;
break;
}
@@ -857,6 +896,11 @@ eval_trigger (void *cls,
(*ttc->needed_cnt)++;
}
}
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "KYC check #%u (%s) is applicable, %u checks needed so far\n",
+ i,
+ ttc->needed[(*ttc->needed_cnt) - 1]->name,
+ *ttc->needed_cnt);
}
if (bump)
return GNUNET_NO; /* we hit all possible triggers! */
@@ -903,13 +947,22 @@ remove_satisfied (void *cls,
if (0 != strcasecmp (provider_name,
kp->provider_section_name))
continue;
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Provider `%s' satisfied\n",
+ provider_name);
for (unsigned int j = 0; j<kp->num_checks; j++)
{
const struct TALER_KYCLOGIC_KycCheck *kc = kp->provided_checks[j];
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Provider satisfies check `%s'\n",
+ kc->name);
for (unsigned int k = 0; k<*rc->needed_cnt; k++)
if (kc == rc->needed[k])
{
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Removing check `%s' from list\n",
+ kc->name);
rc->needed[k] = rc->needed[*rc->needed_cnt - 1];
(*rc->needed_cnt)--;
if (0 == *rc->needed_cnt)
@@ -973,15 +1026,14 @@ TALER_KYCLOGIC_kyc_test_required (enum TALER_KYCLOGIC_KycTriggerEvent event,
/* Check what provider checks are already satisfied for h_payto (with
database), remove those from the 'needed' array. */
- GNUNET_break (0);
- // FIXME: do via callback!
qs = ki (ki_cls,
h_payto,
- &
- remove_satisfied,
+ &remove_satisfied,
&rc);
GNUNET_break (qs >= 0); // FIXME: handle DB failure more nicely?
}
+ if (0 == needed_cnt)
+ return NULL;
/* Count maximum number of remaining checks covered by any
provider */
@@ -1059,4 +1111,22 @@ TALER_KYCLOGIC_kyc_get_logic (const char *provider_section_name,
}
+void
+TALER_KYCLOGIC_kyc_iterate_thresholds (
+ enum TALER_KYCLOGIC_KycTriggerEvent event,
+ TALER_KYCLOGIC_KycThresholdIterator it,
+ void *it_cls)
+{
+ for (unsigned int i = 0; i<num_kyc_triggers; i++)
+ {
+ const struct TALER_KYCLOGIC_KycTrigger *kt = kyc_triggers[i];
+
+ if (event != kt->trigger)
+ continue;
+ it (it_cls,
+ &kt->threshold);
+ }
+}
+
+
/* end of taler-exchange-httpd_kyc.c */
diff --git a/src/kyclogic/plugin_kyclogic_oauth2.c b/src/kyclogic/plugin_kyclogic_oauth2.c
index cbf5ea3ae..b9384e8ff 100644
--- a/src/kyclogic/plugin_kyclogic_oauth2.c
+++ b/src/kyclogic/plugin_kyclogic_oauth2.c
@@ -69,6 +69,11 @@ struct TALER_KYCLOGIC_ProviderDetails
struct PluginState *ps;
/**
+ * Configuration section that configured us.
+ */
+ char *section;
+
+ /**
* URL of the OAuth2.0 endpoint for KYC checks.
* (token/auth)
*/
@@ -265,6 +270,7 @@ struct TALER_KYCLOGIC_WebhookHandle
static void
oauth2_unload_configuration (struct TALER_KYCLOGIC_ProviderDetails *pd)
{
+ GNUNET_free (pd->section);
GNUNET_free (pd->auth_url);
GNUNET_free (pd->login_url);
GNUNET_free (pd->info_url);
@@ -292,6 +298,7 @@ oauth2_load_configuration (void *cls,
pd = GNUNET_new (struct TALER_KYCLOGIC_ProviderDetails);
pd->ps = ps;
+ pd->section = GNUNET_strdup (provider_section_name);
if (GNUNET_OK !=
GNUNET_CONFIGURATION_get_value_time (ps->cfg,
provider_section_name,
@@ -467,9 +474,10 @@ initiate_task (void *cls)
hps = GNUNET_STRINGS_data_to_string_alloc (&ih->h_payto,
sizeof (ih->h_payto));
GNUNET_asprintf (&redirect_uri,
- "%s/kyc-proof/%s/oauth2/%s",
+ "%s/kyc-proof/%s/%s/%s",
ps->exchange_base_url,
hps,
+ pd->section,
legi_s);
redirect_uri_encoded = TALER_urlencode (redirect_uri);
GNUNET_free (redirect_uri);
@@ -532,7 +540,11 @@ oauth2_initiate (void *cls,
static void
oauth2_initiate_cancel (struct TALER_KYCLOGIC_InitiateHandle *ih)
{
- GNUNET_SCHEDULER_cancel (ih->task);
+ if (NULL != ih->task)
+ {
+ GNUNET_SCHEDULER_cancel (ih->task);
+ ih->task = NULL;
+ }
GNUNET_free (ih);
}
@@ -659,6 +671,9 @@ parse_proof_success_reply (struct TALER_KYCLOGIC_ProofHandle *ph,
if (GNUNET_OK != res)
{
GNUNET_break_op (0);
+ json_dumpf (j,
+ stderr,
+ JSON_INDENT (2));
ph->status = TALER_KYCLOGIC_STATUS_PROVIDER_FAILED;
ph->response
= TALER_MHD_make_error (
@@ -691,6 +706,9 @@ parse_proof_success_reply (struct TALER_KYCLOGIC_ProofHandle *ph,
if (GNUNET_OK != res)
{
GNUNET_break_op (0);
+ json_dumpf (data,
+ stderr,
+ JSON_INDENT (2));
ph->status = TALER_KYCLOGIC_STATUS_PROVIDER_FAILED;
ph->response
= TALER_MHD_make_error (
@@ -741,6 +759,9 @@ handle_curl_proof_finished (void *cls,
j);
break;
default:
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "OAuth2.0 info URL returned HTTP status %u\n",
+ (unsigned int) response_code);
handle_proof_error (ph,
j);
break;
@@ -751,6 +772,147 @@ handle_curl_proof_finished (void *cls,
/**
+ * After we are done with the CURL interaction we
+ * need to fetch the user's account details.
+ *
+ * @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 TALER_KYCLOGIC_ProofHandle *ph = cls;
+ const json_t *j = response;
+
+ ph->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 ()
+ };
+ CURL *eh;
+
+ {
+ 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);
+ ph->response
+ = TALER_MHD_make_error (
+ TALER_EC_EXCHANGE_KYC_PROOF_BACKEND_INVALID_RESPONSE,
+ "Unexpected response from KYC gateway");
+ ph->http_status
+ = MHD_HTTP_BAD_GATEWAY;
+ break;
+ }
+ }
+ if (0 != strcasecmp (token_type,
+ "bearer"))
+ {
+ GNUNET_break_op (0);
+ ph->response
+ = TALER_MHD_make_error (
+ TALER_EC_EXCHANGE_KYC_PROOF_BACKEND_INVALID_RESPONSE,
+ "Unexpected token type in response from KYC gateway");
+ ph->http_status
+ = MHD_HTTP_BAD_GATEWAY;
+ break;
+ }
+
+ /* We guard against a few characters that could
+ conceivably be abused to mess with the HTTP header */
+ if ( (NULL != strchr (access_token,
+ '\n')) ||
+ (NULL != strchr (access_token,
+ '\r')) ||
+ (NULL != strchr (access_token,
+ ' ')) ||
+ (NULL != strchr (access_token,
+ ';')) )
+ {
+ GNUNET_break_op (0);
+ ph->response
+ = TALER_MHD_make_error (
+ TALER_EC_EXCHANGE_KYC_PROOF_BACKEND_INVALID_RESPONSE,
+ "Illegal character in access token");
+ ph->http_status
+ = MHD_HTTP_BAD_GATEWAY;
+ break;
+ }
+
+ eh = curl_easy_init ();
+ if (NULL == eh)
+ {
+ GNUNET_break_op (0);
+ ph->response
+ = TALER_MHD_make_error (
+ TALER_EC_GENERIC_ALLOCATION_FAILURE,
+ "curl_easy_init");
+ ph->http_status
+ = MHD_HTTP_INTERNAL_SERVER_ERROR;
+ break;
+ }
+ GNUNET_assert (CURLE_OK ==
+ curl_easy_setopt (eh,
+ CURLOPT_URL,
+ ph->pd->info_url));
+ {
+ char *hdr;
+ struct curl_slist *slist;
+
+ GNUNET_asprintf (&hdr,
+ "%s: Bearer %s",
+ MHD_HTTP_HEADER_AUTHORIZATION,
+ access_token);
+ slist = curl_slist_append (NULL,
+ hdr);
+ ph->job = GNUNET_CURL_job_add2 (ph->pd->ps->curl_ctx,
+ eh,
+ slist,
+ &handle_curl_proof_finished,
+ ph);
+ curl_slist_free_all (slist);
+ GNUNET_free (hdr);
+ }
+ return;
+ }
+ default:
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "OAuth2.0 login URL returned HTTP status %u\n",
+ (unsigned int) response_code);
+ handle_proof_error (ph,
+ j);
+ break;
+ }
+ return_proof_response (ph);
+}
+
+
+/**
* Check KYC status and return status to human.
*
* @param cls the @e cls of this struct with the plugin-specific state
@@ -758,6 +920,7 @@ handle_curl_proof_finished (void *cls,
* @param url_path rest of the URL after `/kyc-webhook/`
* @param connection MHD connection object (for HTTP headers)
* @param account_id which account to trigger process for
+ * @param legi_row row in the table the legitimization is for
* @param provider_user_id user ID (or NULL) the proof is for
* @param provider_legitimization_id legitimization ID the proof is for
* @param cb function to call with the result
@@ -770,6 +933,7 @@ oauth2_proof (void *cls,
const char *const url_path[],
struct MHD_Connection *connection,
const struct TALER_PaytoHashP *account_id,
+ uint64_t legi_row,
const char *provider_user_id,
const char *provider_legitimization_id,
TALER_KYCLOGIC_ProofCallback cb,
@@ -779,16 +943,20 @@ oauth2_proof (void *cls,
struct TALER_KYCLOGIC_ProofHandle *ph;
const char *code;
- if (strlen (provider_legitimization_id) >=
- sizeof (ph->provider_legitimization_id))
+ GNUNET_break (NULL == provider_user_id);
+ ph = GNUNET_new (struct TALER_KYCLOGIC_ProofHandle);
+ GNUNET_snprintf (ph->provider_legitimization_id,
+ sizeof (ph->provider_legitimization_id),
+ "%llu",
+ (unsigned long long) legi_row);
+ if ( (NULL != provider_legitimization_id) &&
+ (0 != strcmp (provider_legitimization_id,
+ ph->provider_legitimization_id)))
{
GNUNET_break (0);
+ GNUNET_free (ph);
return NULL;
}
- GNUNET_break (NULL == provider_user_id);
- ph = GNUNET_new (struct TALER_KYCLOGIC_ProofHandle);
- strcpy (ph->provider_legitimization_id,
- provider_legitimization_id);
ph->pd = pd;
ph->connection = connection;
ph->h_payto = *account_id;
@@ -891,7 +1059,7 @@ oauth2_proof (void *cls,
ph->job = GNUNET_CURL_job_add (ps->curl_ctx,
ph->eh,
- &handle_curl_proof_finished,
+ &handle_curl_login_finished,
ph);
return ph;
}
diff --git a/src/kyclogic/plugin_kyclogic_template.c b/src/kyclogic/plugin_kyclogic_template.c
index f5e583db4..7765061e6 100644
--- a/src/kyclogic/plugin_kyclogic_template.c
+++ b/src/kyclogic/plugin_kyclogic_template.c
@@ -253,6 +253,7 @@ template_initiate_cancel (struct TALER_KYCLOGIC_InitiateHandle *ih)
* @param url_path rest of the URL after `/kyc-webhook/`
* @param connection MHD connection object (for HTTP headers)
* @param account_id which account to trigger process for
+ * @param legi_row row in the table the legitimization is for
* @param provider_user_id user ID (or NULL) the proof is for
* @param provider_legitimization_id legitimization ID the proof is for
* @param cb function to call with the result
@@ -265,6 +266,7 @@ template_proof (void *cls,
const char *const url_path[],
struct MHD_Connection *connection,
const struct TALER_PaytoHashP *account_id,
+ uint64_t legi_row,
const char *provider_user_id,
const char *provider_legitimization_id,
TALER_KYCLOGIC_ProofCallback cb,
diff --git a/src/kyclogic/taler-exchange-kyc-tester.c b/src/kyclogic/taler-exchange-kyc-tester.c
index eecd6b838..d1265626c 100644
--- a/src/kyclogic/taler-exchange-kyc-tester.c
+++ b/src/kyclogic/taler-exchange-kyc-tester.c
@@ -754,6 +754,7 @@ handler_kyc_proof_get (
&args[2],
rc->connection,
&h_payto,
+ kyc_row_id,
cmd_provider_user_id,
cmd_provider_legitimization_id,
&proof_cb,
@@ -1456,7 +1457,7 @@ main (int argc,
"use the given provider user ID (overridden if -i is also used)",
&cmd_provider_user_id),
GNUNET_GETOPT_option_string (
- 'l',
+ 'U',
"legitimization",
"ID",
"use the given provider legitimization ID (overridden if -i is also used)",
@@ -1464,7 +1465,7 @@ main (int argc,
GNUNET_GETOPT_option_base32_fixed_size (
'p',
"payto-hash",
- "URI",
+ "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)),