summaryrefslogtreecommitdiff
path: root/src/kyclogic
diff options
context:
space:
mode:
Diffstat (limited to 'src/kyclogic')
-rw-r--r--src/kyclogic/Makefile.am57
-rw-r--r--src/kyclogic/kyclogic-kycaid.conf6
-rw-r--r--src/kyclogic/kyclogic-oauth2.conf14
-rw-r--r--src/kyclogic/kyclogic-persona.conf44
-rw-r--r--src/kyclogic/kyclogic_api.c435
-rw-r--r--src/kyclogic/plugin_kyclogic_kycaid.c424
-rw-r--r--src/kyclogic/plugin_kyclogic_oauth2.c1011
-rw-r--r--src/kyclogic/plugin_kyclogic_persona.c2268
-rw-r--r--src/kyclogic/plugin_kyclogic_template.c16
-rwxr-xr-xsrc/kyclogic/taler-exchange-kyc-kycaid-converter.sh90
-rwxr-xr-xsrc/kyclogic/taler-exchange-kyc-oauth2-challenger.sh27
-rwxr-xr-xsrc/kyclogic/taler-exchange-kyc-oauth2-nda.sh30
-rwxr-xr-xsrc/kyclogic/taler-exchange-kyc-oauth2-test-converter.sh31
-rwxr-xr-xsrc/kyclogic/taler-exchange-kyc-persona-converter.sh57
-rw-r--r--src/kyclogic/taler-exchange-kyc-tester.c253
15 files changed, 4291 insertions, 472 deletions
diff --git a/src/kyclogic/Makefile.am b/src/kyclogic/Makefile.am
index c77abd876..0281553fc 100644
--- a/src/kyclogic/Makefile.am
+++ b/src/kyclogic/Makefile.am
@@ -11,11 +11,19 @@ pkgcfgdir = $(prefix)/share/taler/config.d/
pkgcfg_DATA = \
kyclogic.conf \
kyclogic-kycaid.conf \
- kyclogic-oauth2.conf
+ kyclogic-oauth2.conf \
+ kyclogic-persona.conf
+
+bin_SCRIPTS = \
+ taler-exchange-kyc-kycaid-converter.sh \
+ taler-exchange-kyc-persona-converter.sh \
+ taler-exchange-kyc-oauth2-test-converter.sh \
+ taler-exchange-kyc-oauth2-challenger.sh \
+ taler-exchange-kyc-oauth2-nda.sh
EXTRA_DIST = \
- kyclogic.conf \
- kyclogic-oauth2.conf \
+ $(pkgcfg_DATA) \
+ $(bin_SCRIPTS) \
sample.conf
lib_LTLIBRARIES = \
@@ -25,8 +33,12 @@ libtalerkyclogic_la_SOURCES = \
kyclogic_api.c
libtalerkyclogic_la_LIBADD = \
$(top_builddir)/src/util/libtalerutil.la \
+ -ljansson \
-lgnunetutil \
$(XLIB)
+libtalerkyclogic_la_LDFLAGS = \
+ -version-info 0:0:0 \
+ -no-undefined
bin_PROGRAMS = \
@@ -57,12 +69,18 @@ plugindir = $(libdir)/taler
plugin_LTLIBRARIES = \
libtaler_plugin_kyclogic_kycaid.la \
libtaler_plugin_kyclogic_oauth2.la \
+ libtaler_plugin_kyclogic_persona.la \
libtaler_plugin_kyclogic_template.la
libtaler_plugin_kyclogic_template_la_SOURCES = \
plugin_kyclogic_template.c
libtaler_plugin_kyclogic_template_la_LIBADD = \
$(LTLIBINTL)
+libtaler_plugin_kyclogic_template_la_LDFLAGS = \
+ $(TALER_PLUGIN_LDFLAGS) \
+ -lgnunetcurl \
+ -lgnunetutil \
+ $(XLIB)
libtaler_plugin_kyclogic_oauth2_la_SOURCES = \
plugin_kyclogic_oauth2.c
@@ -70,10 +88,16 @@ libtaler_plugin_kyclogic_oauth2_la_LIBADD = \
$(LTLIBINTL)
libtaler_plugin_kyclogic_oauth2_la_LDFLAGS = \
$(TALER_PLUGIN_LDFLAGS) \
+ $(top_builddir)/src/templating/libtalertemplating.la \
+ $(top_builddir)/src/mhd/libtalermhd.la \
$(top_builddir)/src/json/libtalerjson.la \
$(top_builddir)/src/util/libtalerutil.la \
+ -lgnunetcurl \
+ -lgnunetjson \
-lgnunetutil \
+ -lmicrohttpd \
-ljansson \
+ -lcurl \
$(XLIB)
libtaler_plugin_kyclogic_kycaid_la_SOURCES = \
@@ -82,10 +106,37 @@ libtaler_plugin_kyclogic_kycaid_la_LIBADD = \
$(LTLIBINTL)
libtaler_plugin_kyclogic_kycaid_la_LDFLAGS = \
$(TALER_PLUGIN_LDFLAGS) \
+ $(top_builddir)/src/templating/libtalertemplating.la \
+ $(top_builddir)/src/mhd/libtalermhd.la \
$(top_builddir)/src/json/libtalerjson.la \
$(top_builddir)/src/curl/libtalercurl.la \
$(top_builddir)/src/util/libtalerutil.la \
+ -lgnunetcurl \
+ -lgnunetjson \
-lgnunetutil \
+ -lmicrohttpd \
+ -ljansson \
+ -lcurl \
+ $(XLIB)
+
+libtaler_plugin_kyclogic_persona_la_SOURCES = \
+ plugin_kyclogic_persona.c
+libtaler_plugin_kyclogic_persona_la_LIBADD = \
+ $(LTLIBINTL)
+libtaler_plugin_kyclogic_persona_la_DEPENDENCIES = \
+ libtalerkyclogic.la
+libtaler_plugin_kyclogic_persona_la_LDFLAGS = \
+ $(TALER_PLUGIN_LDFLAGS) \
+ libtalerkyclogic.la \
+ $(top_builddir)/src/mhd/libtalermhd.la \
+ $(top_builddir)/src/json/libtalerjson.la \
+ $(top_builddir)/src/curl/libtalercurl.la \
+ $(top_builddir)/src/templating/libtalertemplating.la \
+ $(top_builddir)/src/util/libtalerutil.la \
+ -lgnunetcurl \
+ -lgnunetjson \
+ -lgnunetutil \
+ -lmicrohttpd \
-ljansson \
-lcurl \
$(XLIB)
diff --git a/src/kyclogic/kyclogic-kycaid.conf b/src/kyclogic/kyclogic-kycaid.conf
index 3cfb0e790..753fb689d 100644
--- a/src/kyclogic/kyclogic-kycaid.conf
+++ b/src/kyclogic/kyclogic-kycaid.conf
@@ -12,11 +12,15 @@ PROVIDED_CHECKS = EXAMPLE_DO_NOT_USE
# How long is the KYC check valid?
KYC_KYCAID_VALIDITY = forever
+# Program that converts Persona KYC data into the
+# GNU Taler format.
+KYC_KYCAID_CONVERTER_HELPER = taler-exchange-kyc-kycaid-converter.sh
+
# Authentication token to use.
KYC_KYCAID_AUTH_TOKEN = XXX
# Form to use.
KYC_KYCAID_FORM_ID = XXX
-# Authentication token to use.
+# URL to go to after the process is complete.
KYC_KYCAID_POST_URL = https://example.com/
diff --git a/src/kyclogic/kyclogic-oauth2.conf b/src/kyclogic/kyclogic-oauth2.conf
index 7ccf81c0d..57e1fc13a 100644
--- a/src/kyclogic/kyclogic-oauth2.conf
+++ b/src/kyclogic/kyclogic-oauth2.conf
@@ -13,11 +13,11 @@ PROVIDED_CHECKS = EXAMPLE_DO_NOT_USE
KYC_OAUTH2_VALIDITY = forever
# URL where we initiate the user's login process
-KYC_OAUTH2_LOGIN_URL = http://kyc.example.com/login
+KYC_OAUTH2_AUTHORIZE_URL = https://kyc.example.com/authorize
# URL where we send the user's authentication information
-KYC_OAUTH2_AUTH_URL = http://kyc.example.com/auth
+KYC_OAUTH2_TOKEN_URL = https://kyc.example.com/token
# URL of the user info access point.
-KYC_OAUTH2_INFO_URL = http://kyc.example.com/info
+KYC_OAUTH2_INFO_URL = https://kyc.example.com/info
# Where does the client get redirected upon completion?
KYC_OAUTH2_POST_URL = http://example.com/thank-you
@@ -25,3 +25,11 @@ KYC_OAUTH2_POST_URL = http://example.com/thank-you
# For authentication to the OAuth2.0 service
KYC_OAUTH2_CLIENT_ID = testcase
KYC_OAUTH2_CLIENT_SECRET = password
+
+# Mustach template that converts OAuth2.0 data about the user
+# into GNU Taler standardized attribute data.
+#
+# This is just an example, you need to pick the right converter
+# for the provider!
+#
+KYC_OAUTH2_CONVERTER_HELPER = taler-exchange-kyc-oauth2-converter.sh
diff --git a/src/kyclogic/kyclogic-persona.conf b/src/kyclogic/kyclogic-persona.conf
new file mode 100644
index 000000000..2d52a9ee0
--- /dev/null
+++ b/src/kyclogic/kyclogic-persona.conf
@@ -0,0 +1,44 @@
+# This file is in the public domain.
+
+# FIXME: add to taler.conf man page!
+
+# Example persona provider configuration.
+
+[kyclogic-persona]
+
+# Optional authorization token for the webhook.
+# This must be the same for all uses of the
+# Persona provider, and is thus not in a
+# template-specific section.
+#WEBHOOK_AUTH_TOKEN = wbhsec_698b5a19-c790-47f6-b396-deb572ec82f9
+
+
+[kyc-provider-example-persona]
+
+COST = 42
+LOGIC = persona
+USER_TYPE = INDIVIDUAL
+PROVIDED_CHECKS = EXAMPLE_DO_NOT_USE
+
+# How long is the KYC check valid?
+KYC_PERSONA_VALIDITY = forever
+
+# Which subdomain is used for our API?
+KYC_PERSONA_SUBDOMAIN = taler
+
+# Authentication token to use.
+KYC_PERSONA_AUTH_TOKEN = persona_sandbox_42
+
+# Program that converts Persona KYC data into the
+# GNU Taler format.
+KYC_PERSONA_CONVERTER_HELPER = taler-exchange-kyc-persona-converter.sh
+
+# Form to use.
+KYC_PERSONA_TEMPLATE_ID = itempl_Uj6Xxxxx
+
+# Where do we redirect to after KYC finished successfully.
+KYC_PERSONA_POST_URL = https://taler.net/
+
+# Salt to give to requests for idempotency.
+# Optional.
+# KYC_PERSONA_SALT = salt \ No newline at end of file
diff --git a/src/kyclogic/kyclogic_api.c b/src/kyclogic/kyclogic_api.c
index 18707a18f..186799dbb 100644
--- a/src/kyclogic/kyclogic_api.c
+++ b/src/kyclogic/kyclogic_api.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2022 Taler Systems SA
+ Copyright (C) 2022-2023 Taler Systems SA
TALER is free software; you can redistribute it and/or modify it under the
terms of the GNU Affero General Public License as published by the Free Software
@@ -22,6 +22,12 @@
#include "taler_kyclogic_lib.h"
/**
+ * Name of the KYC check that may never be passed. Useful if some
+ * operations/amounts are categorically forbidden.
+ */
+#define KYC_CHECK_IMPOSSIBLE "impossible"
+
+/**
* Information about a KYC provider.
*/
struct TALER_KYCLOGIC_KycProvider;
@@ -180,9 +186,11 @@ TALER_KYCLOGIC_kyc_trigger_from_string (const char *trigger_s,
enum TALER_KYCLOGIC_KycTriggerEvent out;
} map [] = {
{ "withdraw", TALER_KYCLOGIC_KYC_TRIGGER_WITHDRAW },
+ { "age-withdraw", TALER_KYCLOGIC_KYC_TRIGGER_AGE_WITHDRAW },
{ "deposit", TALER_KYCLOGIC_KYC_TRIGGER_DEPOSIT },
{ "merge", TALER_KYCLOGIC_KYC_TRIGGER_P2P_RECEIVE },
{ "balance", TALER_KYCLOGIC_KYC_TRIGGER_WALLET_BALANCE },
+ { "close", TALER_KYCLOGIC_KYC_TRIGGER_RESERVE_CLOSE },
{ NULL, 0 }
};
@@ -207,12 +215,16 @@ TALER_KYCLOGIC_kyc_trigger2s (enum TALER_KYCLOGIC_KycTriggerEvent trigger)
{
case TALER_KYCLOGIC_KYC_TRIGGER_WITHDRAW:
return "withdraw";
+ case TALER_KYCLOGIC_KYC_TRIGGER_AGE_WITHDRAW:
+ return "age-withdraw";
case TALER_KYCLOGIC_KYC_TRIGGER_DEPOSIT:
return "deposit";
case TALER_KYCLOGIC_KYC_TRIGGER_P2P_RECEIVE:
return "merge";
case TALER_KYCLOGIC_KYC_TRIGGER_WALLET_BALANCE:
return "balance";
+ case TALER_KYCLOGIC_KYC_TRIGGER_RESERVE_CLOSE:
+ return "close";
}
GNUNET_break (0);
return NULL;
@@ -226,7 +238,7 @@ TALER_KYCLOGIC_kyc_user_type_from_string (const char *ut_s,
struct
{
const char *in;
- enum TALER_KYCLOGIC_KycTriggerEvent out;
+ enum TALER_KYCLOGIC_KycUserType out;
} map [] = {
{ "individual", TALER_KYCLOGIC_KYC_UT_INDIVIDUAL },
{ "business", TALER_KYCLOGIC_KYC_UT_BUSINESS },
@@ -262,6 +274,38 @@ TALER_KYCLOGIC_kyc_user_type2s (enum TALER_KYCLOGIC_KycUserType ut)
}
+enum GNUNET_GenericReturnValue
+TALER_KYCLOGIC_check_satisfiable (
+ const char *check_name)
+{
+ for (unsigned int i = 0; i<num_kyc_checks; i++)
+ if (0 == strcmp (check_name,
+ kyc_checks[i]->name))
+ return GNUNET_OK;
+ if (0 == strcmp (check_name,
+ KYC_CHECK_IMPOSSIBLE))
+ return GNUNET_NO;
+ return GNUNET_SYSERR;
+}
+
+
+json_t *
+TALER_KYCLOGIC_get_satisfiable ()
+{
+ json_t *requirements;
+
+ requirements = json_array ();
+ GNUNET_assert (NULL != requirements);
+ for (unsigned int i = 0; i<num_kyc_checks; i++)
+ GNUNET_assert (
+ 0 ==
+ json_array_append_new (
+ requirements,
+ json_string (kyc_checks[i]->name)));
+ return requirements;
+}
+
+
/**
* Load KYC logic plugin.
*
@@ -294,6 +338,7 @@ load_logic (const struct GNUNET_CONFIGURATION_Handle *cfg,
return NULL;
}
plugin->library_name = lib_name;
+ plugin->name = GNUNET_strdup (name);
GNUNET_array_append (kyc_logics,
num_kyc_logics,
plugin);
@@ -302,9 +347,8 @@ load_logic (const struct GNUNET_CONFIGURATION_Handle *cfg,
/**
- * Add check type to global array of checks.
- * First checks if the type already exists, otherwise
- * adds a new one.
+ * Add check type to global array of checks. First checks if the type already
+ * exists, otherwise adds a new one.
*
* @param check name of the check
* @return pointer into the global list
@@ -328,9 +372,8 @@ add_check (const char *check)
/**
- * Parse list of checks from @a checks and build an
- * array of aliases into the global checks array
- * in @a provided_checks.
+ * Parse list of checks from @a checks and build an array of aliases into the
+ * global checks array in @a provided_checks.
*
* @param[in,out] checks list of checks; clobbered
* @param[out] p_checks where to put array of aliases
@@ -582,6 +625,29 @@ add_trigger (const struct GNUNET_CONFIGURATION_Handle *cfg,
GNUNET_array_append (kyc_triggers,
num_kyc_triggers,
kt);
+ for (unsigned int i = 0; i<kt->num_checks; i++)
+ {
+ const struct TALER_KYCLOGIC_KycCheck *ck = kt->required_checks[i];
+
+ if (0 != ck->num_providers)
+ continue;
+ if (0 == strcmp (ck->name,
+ KYC_CHECK_IMPOSSIBLE))
+ continue;
+ {
+ char *msg;
+
+ GNUNET_asprintf (&msg,
+ "Required check `%s' cannot be satisfied: not provided by any provider",
+ ck->name);
+ GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
+ section,
+ "REQUIRED_CHECKS",
+ msg);
+ GNUNET_free (msg);
+ }
+ return GNUNET_SYSERR;
+ }
}
return GNUNET_OK;
}
@@ -611,8 +677,8 @@ struct SectionContext
* @param section name of the section
*/
static void
-handle_section (void *cls,
- const char *section)
+handle_provider_section (void *cls,
+ const char *section)
{
struct SectionContext *sc = cls;
@@ -626,6 +692,21 @@ handle_section (void *cls,
sc->result = false;
return;
}
+}
+
+
+/**
+ * Function to iterate over configuration sections.
+ *
+ * @param cls a `struct SectionContext *`
+ * @param section name of the section
+ */
+static void
+handle_trigger_section (void *cls,
+ const char *section)
+{
+ struct SectionContext *sc = cls;
+
if (0 == strncasecmp (section,
"kyc-legitimization-",
strlen ("kyc-legitimization-")))
@@ -677,7 +758,10 @@ TALER_KYCLOGIC_kyc_init (const struct GNUNET_CONFIGURATION_Handle *cfg)
};
GNUNET_CONFIGURATION_iterate_sections (cfg,
- &handle_section,
+ &handle_provider_section,
+ &sc);
+ GNUNET_CONFIGURATION_iterate_sections (cfg,
+ &handle_trigger_section,
&sc);
if (! sc.result)
{
@@ -696,10 +780,11 @@ TALER_KYCLOGIC_kyc_init (const struct GNUNET_CONFIGURATION_Handle *cfg)
TALER_KYCLOGIC_kyc_done ();
return GNUNET_SYSERR;
}
- qsort (kyc_triggers,
- num_kyc_triggers,
- sizeof (struct TALER_KYCLOGIC_KycTrigger *),
- &sort_by_timeframe);
+ if (0 != num_kyc_triggers)
+ qsort (kyc_triggers,
+ num_kyc_triggers,
+ sizeof (struct TALER_KYCLOGIC_KycTrigger *),
+ &sort_by_timeframe);
return GNUNET_OK;
}
@@ -737,6 +822,7 @@ TALER_KYCLOGIC_kyc_done (void)
struct TALER_KYCLOGIC_Plugin *lp = kyc_logics[i];
char *lib_name = lp->library_name;
+ GNUNET_free (lp->name);
GNUNET_assert (NULL == GNUNET_PLUGIN_unload (lib_name,
lp));
GNUNET_free (lib_name);
@@ -929,6 +1015,10 @@ struct RemoveContext
*/
unsigned int *needed_cnt;
+ /**
+ * Object with information about collected KYC data.
+ */
+ json_t *kyc_details;
};
@@ -962,6 +1052,14 @@ remove_satisfied (void *cls,
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Provider satisfies check `%s'\n",
kc->name);
+ if (NULL != rc->kyc_details)
+ {
+ GNUNET_assert (0 ==
+ json_object_set_new (
+ rc->kyc_details,
+ kc->name,
+ json_object ()));
+ }
for (unsigned int k = 0; k<*rc->needed_cnt; k++)
if (kc == rc->needed[k])
{
@@ -980,20 +1078,19 @@ remove_satisfied (void *cls,
}
-const char *
+enum GNUNET_DB_QueryStatus
TALER_KYCLOGIC_kyc_test_required (enum TALER_KYCLOGIC_KycTriggerEvent event,
const struct TALER_PaytoHashP *h_payto,
TALER_KYCLOGIC_KycSatisfiedIterator ki,
void *ki_cls,
TALER_KYCLOGIC_KycAmountIterator ai,
- void *ai_cls)
+ void *ai_cls,
+ char **required)
{
struct TALER_KYCLOGIC_KycCheck *needed[num_kyc_checks];
unsigned int needed_cnt = 0;
+ char *ret;
struct GNUNET_TIME_Relative timeframe;
- unsigned long long min_cost = ULONG_LONG_MAX;
- unsigned int max_checks = 0;
- const struct TALER_KYCLOGIC_KycProvider *kp_best = NULL;
timeframe = GNUNET_TIME_UNIT_ZERO;
for (unsigned int i = 0; i<num_kyc_triggers; i++)
@@ -1021,7 +1118,64 @@ TALER_KYCLOGIC_kyc_test_required (enum TALER_KYCLOGIC_KycTriggerEvent event,
&ttc);
}
if (0 == needed_cnt)
- return NULL;
+ {
+ *required = NULL;
+ return GNUNET_DB_STATUS_SUCCESS_NO_RESULTS;
+ }
+ timeframe = GNUNET_TIME_UNIT_ZERO;
+ for (unsigned int i = 0; i<num_kyc_triggers; i++)
+ {
+ const struct TALER_KYCLOGIC_KycTrigger *kt = kyc_triggers[i];
+
+ if (event != kt->trigger)
+ continue;
+ timeframe = GNUNET_TIME_relative_max (timeframe,
+ kt->timeframe);
+ }
+ {
+ struct GNUNET_TIME_Absolute now;
+ struct ThresholdTestContext ttc = {
+ .event = event,
+ .needed = needed,
+ .needed_cnt = &needed_cnt
+ };
+
+ now = GNUNET_TIME_absolute_get ();
+ ai (ai_cls,
+ GNUNET_TIME_absolute_subtract (now,
+ timeframe),
+ &eval_trigger,
+ &ttc);
+ }
+ if (0 == needed_cnt)
+ {
+ *required = NULL;
+ return GNUNET_DB_STATUS_SUCCESS_NO_RESULTS;
+ }
+ {
+ struct RemoveContext rc = {
+ .needed = needed,
+ .needed_cnt = &needed_cnt
+ };
+ enum GNUNET_DB_QueryStatus qs;
+
+ /* Check what provider checks are already satisfied for h_payto (with
+ database), remove those from the 'needed' array. */
+ qs = ki (ki_cls,
+ h_payto,
+ &remove_satisfied,
+ &rc);
+ if (qs < 0)
+ {
+ GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
+ return qs;
+ }
+ }
+ if (0 == needed_cnt)
+ {
+ *required = NULL;
+ return GNUNET_DB_STATUS_SUCCESS_NO_RESULTS;
+ }
{
struct RemoveContext rc = {
.needed = needed,
@@ -1035,10 +1189,179 @@ TALER_KYCLOGIC_kyc_test_required (enum TALER_KYCLOGIC_KycTriggerEvent event,
h_payto,
&remove_satisfied,
&rc);
- GNUNET_break (qs >= 0); // FIXME: handle DB failure more nicely?
+ if (qs < 0)
+ {
+ GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
+ return qs;
+ }
}
if (0 == needed_cnt)
- return NULL;
+ {
+ *required = NULL;
+ return GNUNET_DB_STATUS_SUCCESS_NO_RESULTS;
+ }
+ ret = NULL;
+ for (unsigned int k = 0; k<needed_cnt; k++)
+ {
+ const struct TALER_KYCLOGIC_KycCheck *kc = needed[k];
+
+ if (NULL == ret)
+ {
+ ret = GNUNET_strdup (kc->name);
+ }
+ else /* append */
+ {
+ char *tmp = ret;
+
+ GNUNET_asprintf (&ret,
+ "%s %s",
+ tmp,
+ kc->name);
+ GNUNET_free (tmp);
+ }
+ }
+ *required = ret;
+ return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
+}
+
+
+void
+TALER_KYCLOGIC_kyc_get_details (
+ const char *logic_name,
+ TALER_KYCLOGIC_DetailsCallback cb,
+ void *cb_cls)
+{
+ for (unsigned int i = 0; i<num_kyc_providers; i++)
+ {
+ struct TALER_KYCLOGIC_KycProvider *kp = kyc_providers[i];
+
+ if (0 !=
+ strcmp (kp->logic->name,
+ logic_name))
+ continue;
+ if (GNUNET_OK !=
+ cb (cb_cls,
+ kp->pd,
+ kp->logic->cls))
+ return;
+ }
+}
+
+
+enum GNUNET_DB_QueryStatus
+TALER_KYCLOGIC_check_satisfied (char **requirements,
+ const struct TALER_PaytoHashP *h_payto,
+ json_t **kyc_details,
+ TALER_KYCLOGIC_KycSatisfiedIterator ki,
+ void *ki_cls,
+ bool *satisfied)
+{
+ struct TALER_KYCLOGIC_KycCheck *needed[num_kyc_checks];
+ unsigned int needed_cnt = 0;
+
+ if (NULL == requirements)
+ {
+ *satisfied = true;
+ return GNUNET_DB_STATUS_SUCCESS_NO_RESULTS;
+ }
+ {
+ char *req = *requirements;
+
+ for (const char *tok = strtok (req, " ");
+ NULL != tok;
+ tok = strtok (NULL, " "))
+ needed[needed_cnt++] = add_check (tok);
+ GNUNET_free (req);
+ *requirements = NULL;
+ }
+
+ {
+ struct RemoveContext rc = {
+ .needed = needed,
+ .needed_cnt = &needed_cnt,
+ };
+ enum GNUNET_DB_QueryStatus qs;
+
+ rc.kyc_details = json_object ();
+ GNUNET_assert (NULL != rc.kyc_details);
+
+ /* Check what provider checks are already satisfied for h_payto (with
+ database), remove those from the 'needed' array. */
+ qs = ki (ki_cls,
+ h_payto,
+ &remove_satisfied,
+ &rc);
+ if (qs < 0)
+ {
+ GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
+ *satisfied = false;
+ return qs;
+ }
+ if (0 != needed_cnt)
+ {
+ json_decref (rc.kyc_details);
+ *kyc_details = NULL;
+ }
+ else
+ {
+ *kyc_details = rc.kyc_details;
+ }
+ }
+ *satisfied = (0 == needed_cnt);
+
+ {
+ char *res = NULL;
+
+ for (unsigned int i = 0; i<needed_cnt; i++)
+ {
+ const struct TALER_KYCLOGIC_KycCheck *need = needed[i];
+
+ if (NULL == res)
+ {
+ res = GNUNET_strdup (need->name);
+ }
+ else
+ {
+ char *tmp;
+
+ GNUNET_asprintf (&tmp,
+ "%s %s",
+ res,
+ need->name);
+ GNUNET_free (res);
+ res = tmp;
+ }
+ }
+ *requirements = res;
+ }
+ return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
+}
+
+
+enum GNUNET_GenericReturnValue
+TALER_KYCLOGIC_requirements_to_logic (const char *requirements,
+ enum TALER_KYCLOGIC_KycUserType ut,
+ struct TALER_KYCLOGIC_Plugin **plugin,
+ struct TALER_KYCLOGIC_ProviderDetails **pd,
+ const char **configuration_section)
+{
+ struct TALER_KYCLOGIC_KycCheck *needed[num_kyc_checks];
+ unsigned int needed_cnt = 0;
+ unsigned long long min_cost = ULLONG_MAX;
+ unsigned int max_checks = 0;
+ const struct TALER_KYCLOGIC_KycProvider *kp_best = NULL;
+
+ if (NULL == requirements)
+ return GNUNET_NO;
+ {
+ char *req = GNUNET_strdup (requirements);
+
+ for (const char *tok = strtok (req, " ");
+ NULL != tok;
+ tok = strtok (NULL, " "))
+ needed[needed_cnt++] = add_check (tok);
+ GNUNET_free (req);
+ }
/* Count maximum number of remaining checks covered by any
provider */
@@ -1047,6 +1370,8 @@ TALER_KYCLOGIC_kyc_test_required (enum TALER_KYCLOGIC_KycTriggerEvent event,
const struct TALER_KYCLOGIC_KycProvider *kp = kyc_providers[i];
unsigned int matched = 0;
+ if (kp->user_type != ut)
+ continue;
for (unsigned int j = 0; j<kp->num_checks; j++)
{
const struct TALER_KYCLOGIC_KycCheck *kc = kp->provided_checks[j];
@@ -1061,6 +1386,8 @@ TALER_KYCLOGIC_kyc_test_required (enum TALER_KYCLOGIC_KycTriggerEvent event,
max_checks = GNUNET_MAX (max_checks,
matched);
}
+ if (0 == max_checks)
+ return GNUNET_SYSERR;
/* Find min-cost provider covering max_checks. */
for (unsigned int i = 0; i<num_kyc_providers; i++)
@@ -1068,6 +1395,8 @@ TALER_KYCLOGIC_kyc_test_required (enum TALER_KYCLOGIC_KycTriggerEvent event,
const struct TALER_KYCLOGIC_KycProvider *kp = kyc_providers[i];
unsigned int matched = 0;
+ if (kp->user_type != ut)
+ continue;
for (unsigned int j = 0; j<kp->num_checks; j++)
{
const struct TALER_KYCLOGIC_KycCheck *kc = kp->provided_checks[j];
@@ -1086,32 +1415,49 @@ TALER_KYCLOGIC_kyc_test_required (enum TALER_KYCLOGIC_KycTriggerEvent event,
kp_best = kp;
}
}
-
GNUNET_assert (NULL != kp_best);
- return kp_best->provider_section_name;
+ *plugin = kp_best->logic;
+ *pd = kp_best->pd;
+ *configuration_section = kp_best->provider_section_name;
+ return GNUNET_OK;
}
enum GNUNET_GenericReturnValue
-TALER_KYCLOGIC_kyc_get_logic (const char *provider_section_name,
- struct TALER_KYCLOGIC_Plugin **plugin,
- struct TALER_KYCLOGIC_ProviderDetails **pd)
+TALER_KYCLOGIC_lookup_logic (const char *name,
+ struct TALER_KYCLOGIC_Plugin **plugin,
+ struct TALER_KYCLOGIC_ProviderDetails **pd,
+ const char **provider_section)
{
for (unsigned int i = 0; i<num_kyc_providers; i++)
{
struct TALER_KYCLOGIC_KycProvider *kp = kyc_providers[i];
if (0 !=
- strcasecmp (provider_section_name,
+ strcasecmp (name,
kp->provider_section_name))
continue;
*plugin = kp->logic;
*pd = kp->pd;
+ *provider_section = kp->provider_section_name;
+ return GNUNET_OK;
+ }
+ for (unsigned int i = 0; i<num_kyc_logics; i++)
+ {
+ struct TALER_KYCLOGIC_Plugin *logic = kyc_logics[i];
+
+ if (0 !=
+ strcasecmp (logic->name,
+ name))
+ continue;
+ *plugin = logic;
+ *pd = NULL;
+ *provider_section = NULL;
return GNUNET_OK;
}
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Provider `%s' unknown\n",
- provider_section_name);
+ name);
return GNUNET_SYSERR;
}
@@ -1134,4 +1480,33 @@ TALER_KYCLOGIC_kyc_iterate_thresholds (
}
+void
+TALER_KYCLOGIC_lookup_checks (const char *section_name,
+ unsigned int *num_checks,
+ char ***provided_checks)
+{
+ *num_checks = 0;
+ *provided_checks = NULL;
+ for (unsigned int i = 0; i<num_kyc_providers; i++)
+ {
+ struct TALER_KYCLOGIC_KycProvider *kp = kyc_providers[i];
+
+ if (0 !=
+ strcasecmp (section_name,
+ kp->provider_section_name))
+ continue;
+ *num_checks = kp->num_checks;
+ if (0 != kp->num_checks)
+ {
+ char **pc = GNUNET_new_array (kp->num_checks,
+ char *);
+ for (unsigned int i = 0; i<kp->num_checks; i++)
+ pc[i] = GNUNET_strdup (kp->provided_checks[i]->name);
+ *provided_checks = pc;
+ }
+ return;
+ }
+}
+
+
/* end of taler-exchange-httpd_kyc.c */
diff --git a/src/kyclogic/plugin_kyclogic_kycaid.c b/src/kyclogic/plugin_kyclogic_kycaid.c
index 449dac51b..243ff7c34 100644
--- a/src/kyclogic/plugin_kyclogic_kycaid.c
+++ b/src/kyclogic/plugin_kyclogic_kycaid.c
@@ -1,6 +1,6 @@
/*
This file is part of GNU Taler
- Copyright (C) 2022 Taler Systems SA
+ Copyright (C) 2022--2024 Taler Systems SA
Taler is free software; you can redistribute it and/or modify it under the
terms of the GNU Affero General Public License as published by the Free Software
@@ -19,10 +19,13 @@
* @author Christian Grothoff
*/
#include "platform.h"
+#include "taler_attributes.h"
+#include "taler_kyclogic_lib.h"
#include "taler_kyclogic_plugin.h"
#include "taler_mhd_lib.h"
#include "taler_curl_lib.h"
#include "taler_json_lib.h"
+#include "taler_templating_lib.h"
#include <regex.h>
#include "taler_util.h"
@@ -86,9 +89,10 @@ struct TALER_KYCLOGIC_ProviderDetails
char *form_id;
/**
- * Where to redirect the client upon completion.
+ * Helper binary to convert attributes returned by
+ * KYCAID into our internal format.
*/
- char *post_kyc_redirect_url;
+ char *conversion_helper;
/**
* Validity time for a successful KYC process.
@@ -219,6 +223,12 @@ struct TALER_KYCLOGIC_WebhookHandle
struct PluginState *ps;
/**
+ * Handle to helper process to extract attributes
+ * we care about.
+ */
+ struct TALER_JSON_ExternalConversion *econ;
+
+ /**
* Our configuration details.
*/
const struct TALER_KYCLOGIC_ProviderDetails *pd;
@@ -229,6 +239,11 @@ struct TALER_KYCLOGIC_WebhookHandle
struct MHD_Connection *connection;
/**
+ * JSON response we got back, or NULL for none.
+ */
+ json_t *json_response;
+
+ /**
* Verification ID from the service.
*/
char *verification_id;
@@ -262,7 +277,12 @@ struct TALER_KYCLOGIC_WebhookHandle
* Row in legitimizations for the given
* @e verification_id.
*/
- uint64_t legi_row;
+ uint64_t process_row;
+
+ /**
+ * HTTP response code we got from KYCAID.
+ */
+ unsigned int kycaid_response_code;
/**
* HTTP response code to return asynchronously.
@@ -280,10 +300,10 @@ static void
kycaid_unload_configuration (struct TALER_KYCLOGIC_ProviderDetails *pd)
{
curl_slist_free_all (pd->slist);
+ GNUNET_free (pd->conversion_helper);
GNUNET_free (pd->auth_token);
GNUNET_free (pd->form_id);
GNUNET_free (pd->section);
- GNUNET_free (pd->post_kyc_redirect_url);
GNUNET_free (pd);
}
@@ -332,24 +352,24 @@ kycaid_load_configuration (void *cls,
if (GNUNET_OK !=
GNUNET_CONFIGURATION_get_value_string (ps->cfg,
provider_section_name,
- "KYC_KYCAID_POST_URL",
- &pd->post_kyc_redirect_url))
+ "KYC_KYCAID_FORM_ID",
+ &pd->form_id))
{
GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
provider_section_name,
- "KYC_KYCAID_POST_URL");
+ "KYC_KYCAID_FORM_ID");
kycaid_unload_configuration (pd);
return NULL;
}
if (GNUNET_OK !=
GNUNET_CONFIGURATION_get_value_string (ps->cfg,
provider_section_name,
- "KYC_KYCAID_FORM_ID",
- &pd->form_id))
+ "KYC_KYCAID_CONVERTER_HELPER",
+ &pd->conversion_helper))
{
GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
provider_section_name,
- "KYC_KYCAID_FORM_ID");
+ "KYC_KYCAID_CONVERTER_HELPER");
kycaid_unload_configuration (pd);
return NULL;
}
@@ -410,11 +430,14 @@ handle_initiate_finished (void *cls,
{
const char *verification_id;
const char *form_url;
+ const char *form_id;
struct GNUNET_JSON_Specification spec[] = {
GNUNET_JSON_spec_string ("verification_id",
&verification_id),
GNUNET_JSON_spec_string ("form_url",
&form_url),
+ GNUNET_JSON_spec_string ("form_id",
+ &form_id),
GNUNET_JSON_spec_end ()
};
@@ -436,6 +459,10 @@ handle_initiate_finished (void *cls,
"type")));
break;
}
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Started new verification `%s' using form %s\n",
+ verification_id,
+ form_id);
ih->cb (ih->cb_cls,
TALER_EC_NONE,
form_url,
@@ -577,8 +604,6 @@ kycaid_initiate (void *cls,
"https://api.kycaid.com/forms/%s/urls",
pd->form_id);
body = GNUNET_JSON_PACK (
- GNUNET_JSON_pack_string ("redirect_url",
- pd->post_kyc_redirect_url),
GNUNET_JSON_pack_data64_auto ("external_applicant_id",
account_id)
);
@@ -606,6 +631,7 @@ kycaid_initiate (void *cls,
json_decref (body);
return NULL;
}
+ json_decref (body);
ih->job = GNUNET_CURL_job_add2 (ps->curl_ctx,
eh,
ih->ctx.headers,
@@ -644,15 +670,30 @@ proof_reply (void *cls)
{
struct TALER_KYCLOGIC_ProofHandle *ph = cls;
struct MHD_Response *resp;
+ enum GNUNET_GenericReturnValue ret;
+ json_t *body;
+ unsigned int http_status;
- resp = TALER_MHD_make_error (TALER_EC_GENERIC_ENDPOINT_UNKNOWN,
- "there is no '/kyc-proof' for kycaid");
+ http_status = MHD_HTTP_BAD_REQUEST;
+ body = GNUNET_JSON_PACK (
+ TALER_JSON_pack_ec (TALER_EC_GENERIC_ENDPOINT_UNKNOWN));
+ GNUNET_assert (NULL != body);
+ ret = TALER_TEMPLATING_build (ph->connection,
+ &http_status,
+ "kycaid-invalid-request",
+ NULL,
+ NULL,
+ body,
+ &resp);
+ json_decref (body);
+ GNUNET_break (GNUNET_SYSERR != ret);
ph->cb (ph->cb_cls,
TALER_KYCLOGIC_STATUS_PROVIDER_FAILED,
NULL, /* user id */
NULL, /* provider legi ID */
GNUNET_TIME_UNIT_ZERO_ABS, /* expiration */
- MHD_HTTP_BAD_REQUEST,
+ NULL, /* attributes */
+ http_status,
resp);
}
@@ -663,10 +704,9 @@ proof_reply (void *cls)
*
* @param cls the @e cls of this struct with the plugin-specific state
* @param pd provider configuration details
- * @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 process_row row in the legitimization processes 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
@@ -676,10 +716,9 @@ proof_reply (void *cls)
static struct TALER_KYCLOGIC_ProofHandle *
kycaid_proof (void *cls,
const struct TALER_KYCLOGIC_ProviderDetails *pd,
- const char *const url_path[],
struct MHD_Connection *connection,
const struct TALER_PaytoHashP *account_id,
- uint64_t legi_row,
+ uint64_t process_row,
const char *provider_user_id,
const char *provider_legitimization_id,
TALER_KYCLOGIC_ProofCallback cb,
@@ -713,11 +752,21 @@ kycaid_webhook_cancel (struct TALER_KYCLOGIC_WebhookHandle *wh)
GNUNET_SCHEDULER_cancel (wh->task);
wh->task = NULL;
}
+ if (NULL != wh->econ)
+ {
+ TALER_JSON_external_conversion_stop (wh->econ);
+ wh->econ = NULL;
+ }
if (NULL != wh->job)
{
GNUNET_CURL_job_cancel (wh->job);
wh->job = NULL;
}
+ if (NULL != wh->json_response)
+ {
+ json_decref (wh->json_response);
+ wh->json_response = NULL;
+ }
GNUNET_free (wh->verification_id);
GNUNET_free (wh->applicant_id);
GNUNET_free (wh->url);
@@ -731,11 +780,12 @@ kycaid_webhook_cancel (struct TALER_KYCLOGIC_WebhookHandle *wh)
* @param verifications JSON object with failure details
*/
static void
-log_failure (json_t *verifications)
+log_failure (const json_t *verifications)
{
- json_t *member;
+ const json_t *member;
const char *name;
- json_object_foreach (verifications, name, member)
+
+ json_object_foreach ((json_t *) verifications, name, member)
{
bool iverified;
const char *comment;
@@ -769,8 +819,101 @@ log_failure (json_t *verifications)
/**
+ * Type of a callback that receives a JSON @a result.
+ *
+ * @param cls closure our `struct TALER_KYCLOGIC_WebhookHandle *`
+ * @param status_type how did the process die
+ * @param code termination status code from the process
+ * @param result converted attribute data, NULL on failure
+ */
+static void
+webhook_conversion_cb (void *cls,
+ enum GNUNET_OS_ProcessStatusType status_type,
+ unsigned long code,
+ const json_t *result)
+{
+ struct TALER_KYCLOGIC_WebhookHandle *wh = cls;
+ struct GNUNET_TIME_Absolute expiration;
+ struct MHD_Response *resp;
+
+ wh->econ = NULL;
+ if ( (0 == code) &&
+ (NULL == result) )
+ {
+ /* No result, but *our helper* was OK => bad input */
+ GNUNET_break_op (0);
+ json_dumpf (wh->json_response,
+ stderr,
+ JSON_INDENT (2));
+ resp = TALER_MHD_MAKE_JSON_PACK (
+ GNUNET_JSON_pack_uint64 ("kycaid_http_status",
+ wh->kycaid_response_code),
+ GNUNET_JSON_pack_object_incref ("kycaid_body",
+ (json_t *) wh->json_response));
+ wh->cb (wh->cb_cls,
+ wh->process_row,
+ &wh->h_payto,
+ wh->pd->section,
+ wh->applicant_id,
+ wh->verification_id,
+ TALER_KYCLOGIC_STATUS_PROVIDER_FAILED,
+ GNUNET_TIME_UNIT_ZERO_ABS, /* expiration */
+ NULL,
+ MHD_HTTP_BAD_GATEWAY,
+ resp);
+ kycaid_webhook_cancel (wh);
+ return;
+ }
+ if (NULL == result)
+ {
+ /* Failure in our helper */
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Helper exited with status code %d\n",
+ (int) code);
+ json_dumpf (wh->json_response,
+ stderr,
+ JSON_INDENT (2));
+ resp = TALER_MHD_MAKE_JSON_PACK (
+ GNUNET_JSON_pack_uint64 ("kycaid_http_status",
+ wh->kycaid_response_code),
+ GNUNET_JSON_pack_object_incref ("kycaid_body",
+ (json_t *) wh->json_response));
+ wh->cb (wh->cb_cls,
+ wh->process_row,
+ &wh->h_payto,
+ wh->pd->section,
+ wh->applicant_id,
+ wh->verification_id,
+ TALER_KYCLOGIC_STATUS_PROVIDER_FAILED,
+ GNUNET_TIME_UNIT_ZERO_ABS, /* expiration */
+ NULL,
+ MHD_HTTP_BAD_GATEWAY,
+ resp);
+ kycaid_webhook_cancel (wh);
+ return;
+ }
+ expiration = GNUNET_TIME_relative_to_absolute (wh->pd->validity);
+ resp = MHD_create_response_from_buffer (0,
+ "",
+ MHD_RESPMEM_PERSISTENT);
+ wh->cb (wh->cb_cls,
+ wh->process_row,
+ &wh->h_payto,
+ wh->pd->section,
+ wh->applicant_id,
+ wh->verification_id,
+ TALER_KYCLOGIC_STATUS_SUCCESS,
+ expiration,
+ result,
+ MHD_HTTP_NO_CONTENT,
+ resp);
+ kycaid_webhook_cancel (wh);
+}
+
+
+/**
* Function called when we're done processing the
- * HTTP "/verifications/{verification_id}" request.
+ * HTTP "/applicants/{verification_id}" request.
*
* @param cls the `struct TALER_KYCLOGIC_WebhookHandle`
* @param response_code HTTP response code, 0 on error
@@ -786,88 +929,78 @@ handle_webhook_finished (void *cls,
struct MHD_Response *resp;
wh->job = NULL;
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Webhook returned with HTTP status %u\n",
+ (unsigned int) response_code);
+ wh->kycaid_response_code = response_code;
+ wh->json_response = json_incref ((json_t *) j);
switch (response_code)
{
case MHD_HTTP_OK:
{
- const char *applicant_id;
- const char *verification_id;
- const char *status;
- bool verified;
- json_t *verifications;
- struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_string ("applicant_id",
- &applicant_id),
- GNUNET_JSON_spec_string ("verification_id",
- &verification_id),
- GNUNET_JSON_spec_string ("status",
- &status), /* completed, pending, ... */
- GNUNET_JSON_spec_bool ("verified",
- &verified),
- GNUNET_JSON_spec_json ("verifications",
- &verifications),
- GNUNET_JSON_spec_end ()
- };
- struct GNUNET_TIME_Absolute expiration;
-
- if (GNUNET_OK !=
- GNUNET_JSON_parse (j,
- spec,
- NULL, NULL))
+ const char *profile_status;
+
+ profile_status = json_string_value (
+ json_object_get (
+ j,
+ "profile_status"));
+ if (0 != strcasecmp ("valid",
+ profile_status))
{
- GNUNET_break_op (0);
- json_dumpf (j,
- stderr,
- JSON_INDENT (2));
- resp = TALER_MHD_MAKE_JSON_PACK (
- GNUNET_JSON_pack_uint64 ("kycaid_http_status",
- response_code),
- GNUNET_JSON_pack_object_incref ("kycaid_body",
- (json_t *) j));
- wh->cb (wh->cb_cls,
- wh->legi_row,
- &wh->h_payto,
- wh->applicant_id,
- wh->verification_id,
- TALER_KYCLOGIC_STATUS_PROVIDER_FAILED,
- GNUNET_TIME_UNIT_ZERO_ABS, /* expiration */
- MHD_HTTP_BAD_GATEWAY,
- resp);
- break;
- }
- if (! verified)
- {
- log_failure (verifications);
- }
- resp = MHD_create_response_from_buffer (0,
- "",
- MHD_RESPMEM_PERSISTENT);
- if (verified)
- {
- expiration = GNUNET_TIME_relative_to_absolute (wh->pd->validity);
+ enum TALER_KYCLOGIC_KycStatus ks;
+
+ ks = (0 == strcasecmp ("pending",
+ profile_status))
+ ? TALER_KYCLOGIC_STATUS_PENDING
+ : TALER_KYCLOGIC_STATUS_USER_ABORTED;
+ resp = MHD_create_response_from_buffer (0,
+ "",
+ MHD_RESPMEM_PERSISTENT);
wh->cb (wh->cb_cls,
- wh->legi_row,
+ wh->process_row,
&wh->h_payto,
+ wh->pd->section,
wh->applicant_id,
wh->verification_id,
- TALER_KYCLOGIC_STATUS_SUCCESS,
- expiration,
+ ks,
+ GNUNET_TIME_UNIT_ZERO_ABS,
+ NULL,
MHD_HTTP_NO_CONTENT,
resp);
+ break;
}
- else
+ wh->econ
+ = TALER_JSON_external_conversion_start (
+ j,
+ &webhook_conversion_cb,
+ wh,
+ wh->pd->conversion_helper,
+ wh->pd->conversion_helper,
+ "-a",
+ wh->pd->auth_token,
+ NULL);
+ if (NULL == wh->econ)
{
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failed to start KYCAID conversion helper `%s'\n",
+ wh->pd->conversion_helper);
+ resp = TALER_MHD_make_error (
+ TALER_EC_EXCHANGE_GENERIC_KYC_CONVERTER_FAILED,
+ NULL);
wh->cb (wh->cb_cls,
- wh->legi_row,
+ wh->process_row,
&wh->h_payto,
+ wh->pd->section,
wh->applicant_id,
wh->verification_id,
- TALER_KYCLOGIC_STATUS_USER_ABORTED,
- GNUNET_TIME_UNIT_ZERO_ABS,
- MHD_HTTP_NO_CONTENT,
+ TALER_KYCLOGIC_STATUS_INTERNAL_ERROR,
+ GNUNET_TIME_UNIT_ZERO_ABS, /* expiration */
+ NULL,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
resp);
+ break;
}
- GNUNET_JSON_parse_free (spec);
+ return;
}
break;
case MHD_HTTP_BAD_REQUEST:
@@ -883,12 +1016,14 @@ handle_webhook_finished (void *cls,
GNUNET_JSON_pack_uint64 ("kycaid_http_status",
response_code));
wh->cb (wh->cb_cls,
- wh->legi_row,
+ wh->process_row,
&wh->h_payto,
+ wh->pd->section,
wh->applicant_id,
wh->verification_id,
TALER_KYCLOGIC_STATUS_PROVIDER_FAILED,
GNUNET_TIME_UNIT_ZERO_ABS, /* expiration */
+ NULL,
MHD_HTTP_INTERNAL_SERVER_ERROR,
resp);
break;
@@ -903,12 +1038,14 @@ handle_webhook_finished (void *cls,
GNUNET_JSON_pack_object_incref ("kycaid_body",
(json_t *) j));
wh->cb (wh->cb_cls,
- wh->legi_row,
+ wh->process_row,
&wh->h_payto,
+ wh->pd->section,
wh->applicant_id,
wh->verification_id,
TALER_KYCLOGIC_STATUS_PROVIDER_FAILED,
GNUNET_TIME_UNIT_ZERO_ABS, /* expiration */
+ NULL,
MHD_HTTP_NETWORK_AUTHENTICATION_REQUIRED,
resp);
break;
@@ -919,12 +1056,14 @@ handle_webhook_finished (void *cls,
GNUNET_JSON_pack_object_incref ("kycaid_body",
(json_t *) j));
wh->cb (wh->cb_cls,
- wh->legi_row,
+ wh->process_row,
&wh->h_payto,
+ wh->pd->section,
wh->applicant_id,
wh->verification_id,
TALER_KYCLOGIC_STATUS_PROVIDER_FAILED,
GNUNET_TIME_UNIT_ZERO_ABS, /* expiration */
+ NULL,
MHD_HTTP_GATEWAY_TIMEOUT,
resp);
break;
@@ -941,12 +1080,14 @@ handle_webhook_finished (void *cls,
GNUNET_JSON_pack_object_incref ("kycaid_body",
(json_t *) j));
wh->cb (wh->cb_cls,
- wh->legi_row,
+ wh->process_row,
&wh->h_payto,
+ wh->pd->section,
wh->applicant_id,
wh->verification_id,
TALER_KYCLOGIC_STATUS_PROVIDER_FAILED,
GNUNET_TIME_UNIT_ZERO_ABS, /* expiration */
+ NULL,
MHD_HTTP_BAD_GATEWAY,
resp);
break;
@@ -957,12 +1098,14 @@ handle_webhook_finished (void *cls,
GNUNET_JSON_pack_object_incref ("kycaid_body",
(json_t *) j));
wh->cb (wh->cb_cls,
- wh->legi_row,
+ wh->process_row,
&wh->h_payto,
+ wh->pd->section,
wh->applicant_id,
wh->verification_id,
TALER_KYCLOGIC_STATUS_PROVIDER_FAILED,
GNUNET_TIME_UNIT_ZERO_ABS, /* expiration */
+ NULL,
MHD_HTTP_SERVICE_UNAVAILABLE,
resp);
break;
@@ -973,12 +1116,14 @@ handle_webhook_finished (void *cls,
GNUNET_JSON_pack_object_incref ("kycaid_body",
(json_t *) j));
wh->cb (wh->cb_cls,
- wh->legi_row,
+ wh->process_row,
&wh->h_payto,
+ wh->pd->section,
wh->applicant_id,
wh->verification_id,
TALER_KYCLOGIC_STATUS_PROVIDER_FAILED,
GNUNET_TIME_UNIT_ZERO_ABS, /* expiration */
+ NULL,
MHD_HTTP_BAD_GATEWAY,
resp);
break;
@@ -995,12 +1140,14 @@ handle_webhook_finished (void *cls,
stderr,
JSON_INDENT (2));
wh->cb (wh->cb_cls,
- wh->legi_row,
+ wh->process_row,
&wh->h_payto,
+ wh->pd->section,
wh->applicant_id,
wh->verification_id,
TALER_KYCLOGIC_STATUS_PROVIDER_FAILED,
GNUNET_TIME_UNIT_ZERO_ABS, /* expiration */
+ NULL,
MHD_HTTP_BAD_GATEWAY,
resp);
break;
@@ -1019,17 +1166,18 @@ async_webhook_reply (void *cls)
{
struct TALER_KYCLOGIC_WebhookHandle *wh = cls;
- fprintf (stderr,
- "async reply\n");
+ wh->task = NULL;
wh->cb (wh->cb_cls,
- wh->legi_row,
- (0 == wh->legi_row)
+ wh->process_row,
+ (0 == wh->process_row)
? NULL
: &wh->h_payto,
+ wh->pd->section,
wh->applicant_id, /* provider user ID */
wh->verification_id, /* provider legi ID */
TALER_KYCLOGIC_STATUS_PROVIDER_FAILED,
GNUNET_TIME_UNIT_ZERO_ABS, /* expiration */
+ NULL,
wh->response_code,
wh->resp);
kycaid_webhook_cancel (wh);
@@ -1072,11 +1220,13 @@ kycaid_webhook (void *cls,
CURL *eh;
const char *request_id;
const char *type;
- const char *verification_id;
+ const char *verification_id; /* = provider_legitimization_id */
const char *applicant_id;
- const char *status;
- bool verified;
- json_t *verifications;
+ const char *form_id;
+ const char *status = NULL;
+ bool verified = false;
+ bool no_verified = true;
+ const json_t *verifications = NULL;
struct GNUNET_JSON_Specification spec[] = {
GNUNET_JSON_spec_string ("request_id",
&request_id),
@@ -1086,12 +1236,20 @@ kycaid_webhook (void *cls,
&verification_id),
GNUNET_JSON_spec_string ("applicant_id",
&applicant_id),
- GNUNET_JSON_spec_string ("status",
- &status),
- GNUNET_JSON_spec_bool ("verified",
- &verified),
- GNUNET_JSON_spec_json ("verifications",
- &verifications),
+ GNUNET_JSON_spec_string ("form_id",
+ &form_id),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_string ("status",
+ &status),
+ NULL),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_bool ("verified",
+ &verified),
+ &no_verified),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_object_const ("verifications",
+ &verifications),
+ NULL),
GNUNET_JSON_spec_end ()
};
enum GNUNET_DB_QueryStatus qs;
@@ -1102,6 +1260,30 @@ kycaid_webhook (void *cls,
wh->ps = ps;
wh->pd = pd;
wh->connection = connection;
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "KYCAID webhook of `%s' triggered with %s\n",
+ pd->section,
+ http_method);
+#if 1
+ if (NULL != body)
+ json_dumpf (body,
+ stderr,
+ JSON_INDENT (2));
+#endif
+ if (NULL == pd)
+ {
+ GNUNET_break_op (0);
+ json_dumpf (body,
+ stderr,
+ JSON_INDENT (2));
+ wh->resp = TALER_MHD_make_error (
+ TALER_EC_EXCHANGE_KYC_GENERIC_LOGIC_UNKNOWN,
+ "kycaid");
+ wh->response_code = MHD_HTTP_NOT_FOUND;
+ wh->task = GNUNET_SCHEDULER_add_now (&async_webhook_reply,
+ wh);
+ return wh;
+ }
if (GNUNET_OK !=
GNUNET_JSON_parse (body,
@@ -1124,7 +1306,7 @@ kycaid_webhook (void *cls,
pd->section,
verification_id,
&wh->h_payto,
- &wh->legi_row);
+ &wh->process_row);
if (qs < 0)
{
wh->resp = TALER_MHD_make_error (TALER_EC_GENERIC_DB_FETCH_FAILED,
@@ -1132,37 +1314,41 @@ kycaid_webhook (void *cls,
wh->response_code = MHD_HTTP_INTERNAL_SERVER_ERROR;
wh->task = GNUNET_SCHEDULER_add_now (&async_webhook_reply,
wh);
- GNUNET_JSON_parse_free (spec);
return wh;
}
if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
{
GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
- "Received webhook for unknown verification ID `%s'\n",
- verification_id);
+ "Received webhook for unknown verification ID `%s' and section `%s'\n",
+ verification_id,
+ pd->section);
wh->resp = TALER_MHD_make_error (
TALER_EC_EXCHANGE_KYC_PROOF_REQUEST_UNKNOWN,
verification_id);
wh->response_code = MHD_HTTP_NOT_FOUND;
wh->task = GNUNET_SCHEDULER_add_now (&async_webhook_reply,
wh);
- GNUNET_JSON_parse_free (spec);
return wh;
}
wh->verification_id = GNUNET_strdup (verification_id);
wh->applicant_id = GNUNET_strdup (applicant_id);
- if (! verified)
+ if ( (0 != strcasecmp (type,
+ "VERIFICATION_COMPLETED")) ||
+ (no_verified) ||
+ (! verified) )
{
/* We don't need to re-confirm the failure by
asking the API again. */
log_failure (verifications);
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Webhook called with non-completion status: %s\n",
+ type);
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;
}
@@ -1172,17 +1358,16 @@ kycaid_webhook (void *cls,
GNUNET_break (0);
wh->resp = TALER_MHD_make_error (
TALER_EC_GENERIC_ALLOCATION_FAILURE,
- verification_id);
+ NULL);
wh->response_code = MHD_HTTP_INTERNAL_SERVER_ERROR;
wh->task = GNUNET_SCHEDULER_add_now (&async_webhook_reply,
wh);
- GNUNET_JSON_parse_free (spec);
return wh;
}
GNUNET_asprintf (&wh->url,
- "https://api.kycaid.com/verifications/%s",
- verification_id);
+ "https://api.kycaid.com/applicants/%s",
+ applicant_id);
GNUNET_break (CURLE_OK ==
curl_easy_setopt (eh,
CURLOPT_VERBOSE,
@@ -1200,7 +1385,6 @@ kycaid_webhook (void *cls,
pd->slist,
&handle_webhook_finished,
wh);
- GNUNET_JSON_parse_free (spec);
return wh;
}
diff --git a/src/kyclogic/plugin_kyclogic_oauth2.c b/src/kyclogic/plugin_kyclogic_oauth2.c
index da7ddfc31..3a1f50bcf 100644
--- a/src/kyclogic/plugin_kyclogic_oauth2.c
+++ b/src/kyclogic/plugin_kyclogic_oauth2.c
@@ -1,6 +1,6 @@
/*
This file is part of GNU Taler
- Copyright (C) 2022 Taler Systems SA
+ Copyright (C) 2022-2024 Taler Systems SA
Taler is free software; you can redistribute it and/or modify it under the
terms of the GNU Affero General Public License as published by the Free Software
@@ -21,6 +21,7 @@
#include "platform.h"
#include "taler_kyclogic_plugin.h"
#include "taler_mhd_lib.h"
+#include "taler_templating_lib.h"
#include "taler_json_lib.h"
#include <regex.h>
#include "taler_util.h"
@@ -74,15 +75,21 @@ struct TALER_KYCLOGIC_ProviderDetails
char *section;
/**
+ * URL of the Challenger ``/setup`` endpoint for
+ * approving address validations. NULL if not used.
+ */
+ char *setup_url;
+
+ /**
* URL of the OAuth2.0 endpoint for KYC checks.
- * (token/auth)
*/
- char *auth_url;
+ char *authorize_url;
/**
* URL of the OAuth2.0 endpoint for KYC checks.
+ * (token/auth)
*/
- char *login_url;
+ char *token_url;
/**
* URL of the user info access endpoint.
@@ -106,10 +113,22 @@ struct TALER_KYCLOGIC_ProviderDetails
char *post_kyc_redirect_url;
/**
+ * Name of the program we use to convert outputs
+ * from Persona into our JSON inputs.
+ */
+ char *conversion_binary;
+
+ /**
* Validity time for a successful KYC process.
*/
struct GNUNET_TIME_Relative validity;
+ /**
+ * Set to true if we are operating in DEBUG
+ * mode and may return private details in HTML
+ * responses to make diagnostics easier.
+ */
+ bool debug_mode;
};
@@ -141,6 +160,11 @@ struct TALER_KYCLOGIC_InitiateHandle
struct GNUNET_SCHEDULER_Task *task;
/**
+ * Handle for the OAuth 2.0 setup request.
+ */
+ struct GNUNET_CURL_Job *job;
+
+ /**
* Continuation to call.
*/
TALER_KYCLOGIC_InitiateCallback cb;
@@ -170,6 +194,12 @@ struct TALER_KYCLOGIC_ProofHandle
struct MHD_Connection *connection;
/**
+ * Handle to an external process that converts the
+ * Persona response to our internal format.
+ */
+ struct TALER_JSON_ExternalConversion *ec;
+
+ /**
* Hash of the payto URI that this is about.
*/
struct TALER_PaytoHashP h_payto;
@@ -195,6 +225,11 @@ struct TALER_KYCLOGIC_ProofHandle
char *post_body;
/**
+ * KYC attributes returned about the user by the OAuth 2.0 server.
+ */
+ json_t *attributes;
+
+ /**
* Response to return.
*/
struct MHD_Response *response;
@@ -271,12 +306,14 @@ 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->token_url);
+ GNUNET_free (pd->setup_url);
+ GNUNET_free (pd->authorize_url);
GNUNET_free (pd->info_url);
GNUNET_free (pd->client_id);
GNUNET_free (pd->client_secret);
GNUNET_free (pd->post_kyc_redirect_url);
+ GNUNET_free (pd->conversion_binary);
GNUNET_free (pd);
}
@@ -311,15 +348,30 @@ oauth2_load_configuration (void *cls,
oauth2_unload_configuration (pd);
return NULL;
}
+
+ if (GNUNET_OK !=
+ GNUNET_CONFIGURATION_get_value_string (ps->cfg,
+ provider_section_name,
+ "KYC_OAUTH2_CLIENT_ID",
+ &s))
+ {
+ GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+ provider_section_name,
+ "KYC_OAUTH2_CLIENT_ID");
+ oauth2_unload_configuration (pd);
+ return NULL;
+ }
+ pd->client_id = s;
+
if (GNUNET_OK !=
GNUNET_CONFIGURATION_get_value_string (ps->cfg,
provider_section_name,
- "KYC_OAUTH2_AUTH_URL",
+ "KYC_OAUTH2_TOKEN_URL",
&s))
{
GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
provider_section_name,
- "KYC_OAUTH2_AUTH_URL");
+ "KYC_OAUTH2_TOKEN_URL");
oauth2_unload_configuration (pd);
return NULL;
}
@@ -333,23 +385,23 @@ oauth2_load_configuration (void *cls,
{
GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
provider_section_name,
- "KYC_OAUTH2_AUTH_URL",
+ "KYC_OAUTH2_TOKEN_URL",
"not a valid URL");
GNUNET_free (s);
oauth2_unload_configuration (pd);
return NULL;
}
- pd->auth_url = s;
+ pd->token_url = s;
if (GNUNET_OK !=
GNUNET_CONFIGURATION_get_value_string (ps->cfg,
provider_section_name,
- "KYC_OAUTH2_LOGIN_URL",
+ "KYC_OAUTH2_AUTHORIZE_URL",
&s))
{
GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
provider_section_name,
- "KYC_OAUTH2_LOGIN_URL");
+ "KYC_OAUTH2_AUTHORIZE_URL");
oauth2_unload_configuration (pd);
return NULL;
}
@@ -363,13 +415,42 @@ oauth2_load_configuration (void *cls,
{
GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
provider_section_name,
- "KYC_OAUTH2_LOGIN_URL",
+ "KYC_OAUTH2_AUTHORIZE_URL",
"not a valid URL");
oauth2_unload_configuration (pd);
GNUNET_free (s);
return NULL;
}
- pd->login_url = s;
+ if (NULL != strchr (s, '#'))
+ {
+ const char *extra = strchr (s, '#');
+ const char *slash = strrchr (s, '/');
+
+ if ( (0 != strcmp (extra,
+ "#setup")) ||
+ (NULL == slash) )
+ {
+ GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
+ provider_section_name,
+ "KYC_OAUTH2_AUTHORIZE_URL",
+ "not a valid authorze URL (bad fragment)");
+ oauth2_unload_configuration (pd);
+ GNUNET_free (s);
+ return NULL;
+ }
+ pd->authorize_url = GNUNET_strndup (s,
+ extra - s);
+ GNUNET_asprintf (&pd->setup_url,
+ "%.*s/setup/%s",
+ (int) (slash - s),
+ s,
+ pd->client_id);
+ GNUNET_free (s);
+ }
+ else
+ {
+ pd->authorize_url = s;
+ }
if (GNUNET_OK !=
GNUNET_CONFIGURATION_get_value_string (ps->cfg,
@@ -404,44 +485,48 @@ oauth2_load_configuration (void *cls,
if (GNUNET_OK !=
GNUNET_CONFIGURATION_get_value_string (ps->cfg,
provider_section_name,
- "KYC_OAUTH2_CLIENT_ID",
+ "KYC_OAUTH2_CLIENT_SECRET",
&s))
{
GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
provider_section_name,
- "KYC_OAUTH2_CLIENT_ID");
+ "KYC_OAUTH2_CLIENT_SECRET");
oauth2_unload_configuration (pd);
return NULL;
}
- pd->client_id = s;
+ pd->client_secret = s;
if (GNUNET_OK !=
GNUNET_CONFIGURATION_get_value_string (ps->cfg,
provider_section_name,
- "KYC_OAUTH2_CLIENT_SECRET",
+ "KYC_OAUTH2_POST_URL",
&s))
{
GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
provider_section_name,
- "KYC_OAUTH2_CLIENT_SECRET");
+ "KYC_OAUTH2_POST_URL");
oauth2_unload_configuration (pd);
return NULL;
}
- pd->client_secret = s;
+ pd->post_kyc_redirect_url = s;
if (GNUNET_OK !=
GNUNET_CONFIGURATION_get_value_string (ps->cfg,
provider_section_name,
- "KYC_OAUTH2_POST_URL",
- &s))
+ "KYC_OAUTH2_CONVERTER_HELPER",
+ &pd->conversion_binary))
{
GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
provider_section_name,
- "KYC_OAUTH2_POST_URL");
+ "KYC_OAUTH2_CONVERTER_HELPER");
oauth2_unload_configuration (pd);
return NULL;
}
- pd->post_kyc_redirect_url = s;
+ if (GNUNET_OK ==
+ GNUNET_CONFIGURATION_get_value_yesno (ps->cfg,
+ provider_section_name,
+ "KYC_OAUTH2_DEBUG_MODE"))
+ pd->debug_mode = true;
return pd;
}
@@ -452,41 +537,47 @@ oauth2_load_configuration (void *cls,
* how to begin the OAuth2.0 checking process to
* the client.
*
- * @param cls a `struct TALER_KYCLOGIC_InitiateHandle *`
+ * @param ih process to redirect for
+ * @param authorize_url authorization URL to use
*/
static void
-initiate_task (void *cls)
+initiate_with_url (struct TALER_KYCLOGIC_InitiateHandle *ih,
+ const char *authorize_url)
{
- struct TALER_KYCLOGIC_InitiateHandle *ih = cls;
+
const struct TALER_KYCLOGIC_ProviderDetails *pd = ih->pd;
struct PluginState *ps = pd->ps;
char *hps;
char *url;
- char *redirect_uri;
- char *redirect_uri_encoded;
char legi_s[42];
- ih->task = NULL;
GNUNET_snprintf (legi_s,
sizeof (legi_s),
"%llu",
(unsigned long long) ih->legitimization_uuid);
hps = GNUNET_STRINGS_data_to_string_alloc (&ih->h_payto,
sizeof (ih->h_payto));
- GNUNET_asprintf (&redirect_uri,
- "%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);
- GNUNET_asprintf (&url,
- "%s?client_id=%s&redirect_uri=%s",
- pd->login_url,
- pd->client_id,
- redirect_uri_encoded);
- GNUNET_free (redirect_uri_encoded);
+ {
+ char *redirect_uri_encoded;
+
+ {
+ char *redirect_uri;
+
+ GNUNET_asprintf (&redirect_uri,
+ "%skyc-proof/%s",
+ ps->exchange_base_url,
+ pd->section);
+ redirect_uri_encoded = TALER_urlencode (redirect_uri);
+ GNUNET_free (redirect_uri);
+ }
+ GNUNET_asprintf (&url,
+ "%s?response_type=code&client_id=%s&redirect_uri=%s&state=%s",
+ authorize_url,
+ pd->client_id,
+ redirect_uri_encoded,
+ hps);
+ GNUNET_free (redirect_uri_encoded);
+ }
ih->cb (ih->cb_cls,
TALER_EC_NONE,
url,
@@ -500,6 +591,169 @@ initiate_task (void *cls)
/**
+ * After we are done with the CURL interaction we
+ * need to update our database state with the information
+ * retrieved.
+ *
+ * @param cls a `struct TALER_KYCLOGIC_InitiateHandle *`
+ * @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_setup_finished (void *cls,
+ long response_code,
+ const void *response)
+{
+ struct TALER_KYCLOGIC_InitiateHandle *ih = cls;
+ const struct TALER_KYCLOGIC_ProviderDetails *pd = ih->pd;
+ const json_t *j = response;
+
+ ih->job = NULL;
+ switch (response_code)
+ {
+ case 0:
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "/setup URL failed to return HTTP response\n");
+ ih->cb (ih->cb_cls,
+ TALER_EC_EXCHANGE_KYC_PROOF_BACKEND_INVALID_RESPONSE,
+ NULL,
+ NULL,
+ NULL,
+ "/setup request to OAuth 2.0 backend returned no response");
+ GNUNET_free (ih);
+ return;
+ case MHD_HTTP_OK:
+ {
+ const char *nonce;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_string ("nonce",
+ &nonce),
+ GNUNET_JSON_spec_end ()
+ };
+ enum GNUNET_GenericReturnValue res;
+ const char *emsg;
+ unsigned int line;
+ char *url;
+
+ res = GNUNET_JSON_parse (j,
+ spec,
+ &emsg,
+ &line);
+ if (GNUNET_OK != res)
+ {
+ GNUNET_break_op (0);
+ json_dumpf (j,
+ stderr,
+ JSON_INDENT (2));
+ ih->cb (ih->cb_cls,
+ TALER_EC_EXCHANGE_KYC_PROOF_BACKEND_INVALID_RESPONSE,
+ NULL,
+ NULL,
+ NULL,
+ "Unexpected response from KYC gateway: setup must return a nonce");
+ GNUNET_free (ih);
+ return;
+ }
+ GNUNET_asprintf (&url,
+ "%s/%s",
+ pd->authorize_url,
+ nonce);
+ initiate_with_url (ih,
+ url);
+ GNUNET_free (url);
+ return;
+ }
+ break;
+ default:
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "/setup URL returned HTTP status %u\n",
+ (unsigned int) response_code);
+ ih->cb (ih->cb_cls,
+ TALER_EC_EXCHANGE_KYC_PROOF_BACKEND_INVALID_RESPONSE,
+ NULL,
+ NULL,
+ NULL,
+ "/setup request to OAuth 2.0 backend returned unexpected HTTP status code");
+ GNUNET_free (ih);
+ return;
+ }
+}
+
+
+/**
+ * Logic to asynchronously return the response for how to begin the OAuth2.0
+ * checking process to the client. May first request a dynamic URL via
+ * ``/setup`` if configured to use a client-authenticated setup process.
+ *
+ * @param cls a `struct TALER_KYCLOGIC_InitiateHandle *`
+ */
+static void
+initiate_task (void *cls)
+{
+ struct TALER_KYCLOGIC_InitiateHandle *ih = cls;
+ const struct TALER_KYCLOGIC_ProviderDetails *pd = ih->pd;
+ struct PluginState *ps = pd->ps;
+ char *hdr;
+ struct curl_slist *slist;
+ CURL *eh;
+
+ ih->task = NULL;
+ if (NULL == pd->setup_url)
+ {
+ initiate_with_url (ih,
+ pd->authorize_url);
+ return;
+ }
+ eh = curl_easy_init ();
+ if (NULL == eh)
+ {
+ GNUNET_break (0);
+ ih->cb (ih->cb_cls,
+ TALER_EC_GENERIC_ALLOCATION_FAILURE,
+ NULL,
+ NULL,
+ NULL,
+ "curl_easy_init() failed");
+ GNUNET_free (ih);
+ return;
+ }
+ GNUNET_assert (CURLE_OK ==
+ curl_easy_setopt (eh,
+ CURLOPT_URL,
+ pd->setup_url));
+ GNUNET_assert (CURLE_OK ==
+ curl_easy_setopt (eh,
+ CURLOPT_POST,
+ 1));
+ GNUNET_assert (CURLE_OK ==
+ curl_easy_setopt (eh,
+ CURLOPT_POSTFIELDS,
+ ""));
+ GNUNET_assert (CURLE_OK ==
+ curl_easy_setopt (eh,
+ CURLOPT_FOLLOWLOCATION,
+ 1L));
+ GNUNET_assert (CURLE_OK ==
+ curl_easy_setopt (eh,
+ CURLOPT_MAXREDIRS,
+ 5L));
+ GNUNET_asprintf (&hdr,
+ "%s: Bearer %s",
+ MHD_HTTP_HEADER_AUTHORIZATION,
+ pd->client_secret);
+ slist = curl_slist_append (NULL,
+ hdr);
+ ih->job = GNUNET_CURL_job_add2 (ps->curl_ctx,
+ eh,
+ slist,
+ &handle_curl_setup_finished,
+ ih);
+ curl_slist_free_all (slist);
+ GNUNET_free (hdr);
+}
+
+
+/**
* Initiate KYC check.
*
* @param cls the @e cls of this struct with the plugin-specific state
@@ -520,6 +774,7 @@ oauth2_initiate (void *cls,
{
struct TALER_KYCLOGIC_InitiateHandle *ih;
+ (void) cls;
ih = GNUNET_new (struct TALER_KYCLOGIC_InitiateHandle);
ih->legitimization_uuid = legitimization_uuid;
ih->cb = cb;
@@ -545,11 +800,52 @@ oauth2_initiate_cancel (struct TALER_KYCLOGIC_InitiateHandle *ih)
GNUNET_SCHEDULER_cancel (ih->task);
ih->task = NULL;
}
+ if (NULL != ih->job)
+ {
+ GNUNET_CURL_job_cancel (ih->job);
+ ih->job = NULL;
+ }
GNUNET_free (ih);
}
/**
+ * Cancel KYC proof.
+ *
+ * @param[in] ph handle of operation to cancel
+ */
+static void
+oauth2_proof_cancel (struct TALER_KYCLOGIC_ProofHandle *ph)
+{
+ if (NULL != ph->ec)
+ {
+ TALER_JSON_external_conversion_stop (ph->ec);
+ ph->ec = NULL;
+ }
+ if (NULL != ph->task)
+ {
+ GNUNET_SCHEDULER_cancel (ph->task);
+ ph->task = NULL;
+ }
+ if (NULL != ph->job)
+ {
+ GNUNET_CURL_job_cancel (ph->job);
+ ph->job = NULL;
+ }
+ if (NULL != ph->response)
+ {
+ MHD_destroy_response (ph->response);
+ ph->response = NULL;
+ }
+ GNUNET_free (ph->provider_user_id);
+ if (NULL != ph->attributes)
+ json_decref (ph->attributes);
+ GNUNET_free (ph->post_body);
+ GNUNET_free (ph);
+}
+
+
+/**
* Function called to asynchronously return the final
* result to the callback.
*
@@ -566,10 +862,11 @@ return_proof_response (void *cls)
ph->provider_user_id,
ph->provider_legitimization_id,
GNUNET_TIME_relative_to_absolute (ph->pd->validity),
+ ph->attributes,
ph->http_status,
ph->response);
- GNUNET_free (ph->provider_user_id);
- GNUNET_free (ph);
+ ph->response = NULL; /*Ownership passed to 'ph->cb'!*/
+ oauth2_proof_cancel (ph);
}
@@ -584,18 +881,18 @@ static void
handle_proof_error (struct TALER_KYCLOGIC_ProofHandle *ph,
const json_t *j)
{
- const char *msg;
- const char *desc;
- struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_string ("error",
- &msg),
- GNUNET_JSON_spec_string ("error_description",
- &desc),
- GNUNET_JSON_spec_end ()
- };
+ enum GNUNET_GenericReturnValue res;
{
- enum GNUNET_GenericReturnValue res;
+ const char *msg;
+ const char *desc;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_string ("error",
+ &msg),
+ GNUNET_JSON_spec_string ("error_description",
+ &desc),
+ GNUNET_JSON_spec_end ()
+ };
const char *emsg;
unsigned int line;
@@ -603,94 +900,113 @@ handle_proof_error (struct TALER_KYCLOGIC_ProofHandle *ph,
spec,
&emsg,
&line);
- if (GNUNET_OK != res)
- {
- GNUNET_break_op (0);
- ph->status = TALER_KYCLOGIC_STATUS_PROVIDER_FAILED;
- 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;
- return;
- }
}
- /* case TALER_EC_EXCHANGE_KYC_PROOF_BACKEND_AUTHORZATION_FAILED,
- we MAY want to in the future look at the requested content type
- and possibly respond in JSON if indicated. */
+
+ if (GNUNET_OK != res)
{
- char *reply;
-
- GNUNET_asprintf (&reply,
- "<html><head><title>%s</title></head><body><h1>%s</h1><p>%s</p></body></html>",
- msg,
- msg,
- desc);
- ph->status = TALER_KYCLOGIC_STATUS_USER_ABORTED;
- ph->response
- = MHD_create_response_from_buffer (strlen (reply),
- reply,
- MHD_RESPMEM_MUST_COPY);
- GNUNET_assert (NULL != ph->response);
- GNUNET_free (reply);
+ json_t *body;
+
+ GNUNET_break_op (0);
+ ph->status = TALER_KYCLOGIC_STATUS_PROVIDER_FAILED;
+ ph->http_status
+ = MHD_HTTP_BAD_GATEWAY;
+ body = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_object_incref ("server_response",
+ (json_t *) j)),
+ GNUNET_JSON_pack_bool ("debug",
+ ph->pd->debug_mode),
+ TALER_JSON_pack_ec (
+ TALER_EC_EXCHANGE_KYC_PROOF_BACKEND_INVALID_RESPONSE));
+ GNUNET_assert (NULL != body);
+ GNUNET_break (
+ GNUNET_SYSERR !=
+ TALER_TEMPLATING_build (ph->connection,
+ &ph->http_status,
+ "oauth2-authorization-failure-malformed",
+ NULL,
+ NULL,
+ body,
+ &ph->response));
+ json_decref (body);
+ return;
}
ph->status = TALER_KYCLOGIC_STATUS_USER_ABORTED;
ph->http_status = MHD_HTTP_FORBIDDEN;
+ GNUNET_break (
+ GNUNET_SYSERR !=
+ TALER_TEMPLATING_build (ph->connection,
+ &ph->http_status,
+ "oauth2-authorization-failure",
+ NULL,
+ NULL,
+ j,
+ &ph->response));
}
/**
- * The request for @a ph succeeded (presumably).
- * Call continuation with the result.
+ * Type of a callback that receives a JSON @a result.
*
- * @param[in,out] ph request that succeeded
- * @param j reply from the server
+ * @param cls closure with a `struct TALER_KYCLOGIC_ProofHandle *`
+ * @param status_type how did the process die
+ * @param code termination status code from the process
+ * @param attr result some JSON result, NULL if we failed to get an JSON output
*/
static void
-parse_proof_success_reply (struct TALER_KYCLOGIC_ProofHandle *ph,
- const json_t *j)
+converted_proof_cb (void *cls,
+ enum GNUNET_OS_ProcessStatusType status_type,
+ unsigned long code,
+ const json_t *attr)
{
- const char *state;
- json_t *data;
- struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_string ("status",
- &state),
- GNUNET_JSON_spec_json ("data",
- &data),
- GNUNET_JSON_spec_end ()
- };
- enum GNUNET_GenericReturnValue res;
- const char *emsg;
- unsigned int line;
+ struct TALER_KYCLOGIC_ProofHandle *ph = cls;
+ const struct TALER_KYCLOGIC_ProviderDetails *pd = ph->pd;
- res = GNUNET_JSON_parse (j,
- spec,
- &emsg,
- &line);
- if (GNUNET_OK != res)
+ ph->ec = NULL;
+ if ( (NULL == attr) ||
+ (0 != code) )
{
+ json_t *body;
+ char *msg;
+
GNUNET_break_op (0);
- json_dumpf (j,
- stderr,
- JSON_INDENT (2));
ph->status = TALER_KYCLOGIC_STATUS_PROVIDER_FAILED;
- 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;
- return;
- }
- if (0 != strcasecmp (state,
- "success"))
- {
- GNUNET_break_op (0);
- handle_proof_error (ph,
- j);
+ ph->http_status = MHD_HTTP_BAD_GATEWAY;
+ if (0 != code)
+ GNUNET_asprintf (&msg,
+ "Attribute converter exited with status %ld",
+ code);
+ else
+ msg = GNUNET_strdup (
+ "Attribute converter response was not in JSON format");
+ body = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("converter",
+ pd->conversion_binary),
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_object_incref ("attributes",
+ (json_t *) attr)),
+ GNUNET_JSON_pack_bool ("debug",
+ ph->pd->debug_mode),
+ GNUNET_JSON_pack_string ("message",
+ msg),
+ TALER_JSON_pack_ec (
+ TALER_EC_EXCHANGE_KYC_PROOF_BACKEND_INVALID_RESPONSE));
+ GNUNET_free (msg);
+ GNUNET_break (
+ GNUNET_SYSERR !=
+ TALER_TEMPLATING_build (ph->connection,
+ &ph->http_status,
+ "oauth2-conversion-failure",
+ NULL,
+ NULL,
+ body,
+ &ph->response));
+ json_decref (body);
+ ph->task = GNUNET_SCHEDULER_add_now (&return_proof_response,
+ ph);
return;
}
+
{
const char *id;
struct GNUNET_JSON_Specification ispec[] = {
@@ -698,39 +1014,123 @@ parse_proof_success_reply (struct TALER_KYCLOGIC_ProofHandle *ph,
&id),
GNUNET_JSON_spec_end ()
};
+ enum GNUNET_GenericReturnValue res;
+ const char *emsg;
+ unsigned int line;
- res = GNUNET_JSON_parse (data,
+ res = GNUNET_JSON_parse (attr,
ispec,
&emsg,
&line);
if (GNUNET_OK != res)
{
+ json_t *body;
+
GNUNET_break_op (0);
- json_dumpf (data,
- stderr,
- JSON_INDENT (2));
ph->status = TALER_KYCLOGIC_STATUS_PROVIDER_FAILED;
- 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;
+ ph->http_status = MHD_HTTP_BAD_GATEWAY;
+ body = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("converter",
+ pd->conversion_binary),
+ GNUNET_JSON_pack_string ("message",
+ "Unexpected response from KYC attribute converter: returned JSON data must contain 'id' field"),
+ GNUNET_JSON_pack_bool ("debug",
+ ph->pd->debug_mode),
+ GNUNET_JSON_pack_object_incref ("attributes",
+ (json_t *) attr),
+ TALER_JSON_pack_ec (
+ TALER_EC_EXCHANGE_KYC_PROOF_BACKEND_INVALID_RESPONSE));
+ GNUNET_break (
+ GNUNET_SYSERR !=
+ TALER_TEMPLATING_build (ph->connection,
+ &ph->http_status,
+ "oauth2-conversion-failure",
+ NULL,
+ NULL,
+ body,
+ &ph->response));
+ json_decref (body);
+ ph->task = GNUNET_SCHEDULER_add_now (&return_proof_response,
+ ph);
return;
}
- ph->status = TALER_KYCLOGIC_STATUS_SUCCESS;
- ph->response = MHD_create_response_from_buffer (0,
- "",
- MHD_RESPMEM_PERSISTENT);
- GNUNET_assert (NULL != ph->response);
- GNUNET_break (MHD_YES ==
- MHD_add_response_header (
- ph->response,
- MHD_HTTP_HEADER_LOCATION,
- ph->pd->post_kyc_redirect_url));
- ph->http_status = MHD_HTTP_SEE_OTHER;
ph->provider_user_id = GNUNET_strdup (id);
}
+ ph->status = TALER_KYCLOGIC_STATUS_SUCCESS;
+ ph->response = MHD_create_response_from_buffer (0,
+ "",
+ MHD_RESPMEM_PERSISTENT);
+ GNUNET_assert (NULL != ph->response);
+ GNUNET_break (MHD_YES ==
+ MHD_add_response_header (
+ ph->response,
+ MHD_HTTP_HEADER_LOCATION,
+ ph->pd->post_kyc_redirect_url));
+ ph->http_status = MHD_HTTP_SEE_OTHER;
+ ph->attributes = json_incref ((json_t *) attr);
+ ph->task = GNUNET_SCHEDULER_add_now (&return_proof_response,
+ ph);
+}
+
+
+/**
+ * The request for @a ph succeeded (presumably).
+ * Call continuation with the result.
+ *
+ * @param[in,out] ph request that succeeded
+ * @param j reply from the server
+ */
+static void
+parse_proof_success_reply (struct TALER_KYCLOGIC_ProofHandle *ph,
+ const json_t *j)
+{
+ const struct TALER_KYCLOGIC_ProviderDetails *pd = ph->pd;
+
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Calling converter `%s' with JSON\n",
+ pd->conversion_binary);
+ json_dumpf (j,
+ stderr,
+ JSON_INDENT (2));
+ ph->ec = TALER_JSON_external_conversion_start (
+ j,
+ &converted_proof_cb,
+ ph,
+ pd->conversion_binary,
+ pd->conversion_binary,
+ NULL);
+ if (NULL != ph->ec)
+ return;
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failed to start KYCAID conversion helper `%s'\n",
+ pd->conversion_binary);
+ ph->status = TALER_KYCLOGIC_STATUS_INTERNAL_ERROR;
+ ph->http_status = MHD_HTTP_INTERNAL_SERVER_ERROR;
+ {
+ json_t *body;
+
+ body = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("converter",
+ pd->conversion_binary),
+ GNUNET_JSON_pack_bool ("debug",
+ ph->pd->debug_mode),
+ GNUNET_JSON_pack_string ("message",
+ "Failed to launch KYC conversion helper process."),
+ TALER_JSON_pack_ec (
+ TALER_EC_EXCHANGE_GENERIC_KYC_CONVERTER_FAILED));
+ GNUNET_break (
+ GNUNET_SYSERR !=
+ TALER_TEMPLATING_build (ph->connection,
+ &ph->http_status,
+ "oauth2-conversion-failure",
+ NULL,
+ NULL,
+ body,
+ &ph->response));
+ json_decref (body);
+ }
+ ph->task = GNUNET_SCHEDULER_add_now (&return_proof_response,
+ ph);
}
@@ -754,10 +1154,34 @@ handle_curl_proof_finished (void *cls,
ph->job = NULL;
switch (response_code)
{
+ case 0:
+ {
+ json_t *body;
+
+ ph->status = TALER_KYCLOGIC_STATUS_PROVIDER_FAILED;
+ ph->http_status = MHD_HTTP_BAD_GATEWAY;
+
+ body = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("message",
+ "No response from KYC gateway"),
+ TALER_JSON_pack_ec (
+ TALER_EC_EXCHANGE_KYC_PROOF_BACKEND_INVALID_RESPONSE));
+ GNUNET_break (
+ GNUNET_SYSERR !=
+ TALER_TEMPLATING_build (ph->connection,
+ &ph->http_status,
+ "oauth2-provider-failure",
+ NULL,
+ NULL,
+ body,
+ &ph->response));
+ json_decref (body);
+ }
+ break;
case MHD_HTTP_OK:
parse_proof_success_reply (ph,
j);
- break;
+ return;
default:
GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
"OAuth2.0 info URL returned HTTP status %u\n",
@@ -796,15 +1220,21 @@ handle_curl_login_finished (void *cls,
const char *token_type;
uint64_t expires_in_s;
const char *refresh_token;
+ bool no_expires;
+ bool no_refresh;
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_mark_optional (
+ GNUNET_JSON_spec_uint64 ("expires_in",
+ &expires_in_s),
+ &no_expires),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_string ("refresh_token",
+ &refresh_token),
+ &no_refresh),
GNUNET_JSON_spec_end ()
};
CURL *eh;
@@ -820,26 +1250,59 @@ handle_curl_login_finished (void *cls,
&line);
if (GNUNET_OK != res)
{
+ json_t *body;
+
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;
+ body = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_object_incref ("server_response",
+ (json_t *) j),
+ GNUNET_JSON_pack_bool ("debug",
+ ph->pd->debug_mode),
+ GNUNET_JSON_pack_string ("message",
+ "Unexpected response from KYC gateway: required fields missing or malformed"),
+ TALER_JSON_pack_ec (
+ TALER_EC_EXCHANGE_KYC_PROOF_BACKEND_INVALID_RESPONSE));
+ GNUNET_break (
+ GNUNET_SYSERR !=
+ TALER_TEMPLATING_build (ph->connection,
+ &ph->http_status,
+ "oauth2-provider-failure",
+ NULL,
+ NULL,
+ body,
+ &ph->response));
+ json_decref (body);
break;
}
}
if (0 != strcasecmp (token_type,
"bearer"))
{
+ json_t *body;
+
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;
+ ph->http_status = MHD_HTTP_BAD_GATEWAY;
+ body = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_object_incref ("server_response",
+ (json_t *) j),
+ GNUNET_JSON_pack_bool ("debug",
+ ph->pd->debug_mode),
+ GNUNET_JSON_pack_string ("message",
+ "Unexpected 'token_type' in response from KYC gateway: 'bearer' token required"),
+ TALER_JSON_pack_ec (
+ TALER_EC_EXCHANGE_KYC_PROOF_BACKEND_INVALID_RESPONSE));
+ GNUNET_break (
+ GNUNET_SYSERR !=
+ TALER_TEMPLATING_build (ph->connection,
+ &ph->http_status,
+ "oauth2-provider-failure",
+ NULL,
+ NULL,
+ body,
+ &ph->response));
+ json_decref (body);
break;
}
@@ -854,28 +1317,34 @@ handle_curl_login_finished (void *cls,
(NULL != strchr (access_token,
';')) )
{
+ json_t *body;
+
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;
+ ph->http_status = MHD_HTTP_BAD_GATEWAY;
+ body = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_object_incref ("server_response",
+ (json_t *) j),
+ GNUNET_JSON_pack_bool ("debug",
+ ph->pd->debug_mode),
+ GNUNET_JSON_pack_string ("message",
+ "Illegal character in access token"),
+ TALER_JSON_pack_ec (
+ TALER_EC_EXCHANGE_KYC_PROOF_BACKEND_INVALID_RESPONSE));
+ GNUNET_break (
+ GNUNET_SYSERR !=
+ TALER_TEMPLATING_build (ph->connection,
+ &ph->http_status,
+ "oauth2-provider-failure",
+ NULL,
+ NULL,
+ body,
+ &ph->response));
+ json_decref (body);
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 (NULL != eh);
GNUNET_assert (CURLE_OK ==
curl_easy_setopt (eh,
CURLOPT_URL,
@@ -917,10 +1386,9 @@ handle_curl_login_finished (void *cls,
*
* @param cls the @e cls of this struct with the plugin-specific state
* @param pd provider configuration details
- * @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 process_row row in the legitimization processes 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
@@ -930,10 +1398,9 @@ handle_curl_login_finished (void *cls,
static struct TALER_KYCLOGIC_ProofHandle *
oauth2_proof (void *cls,
const struct TALER_KYCLOGIC_ProviderDetails *pd,
- const char *const url_path[],
struct MHD_Connection *connection,
const struct TALER_PaytoHashP *account_id,
- uint64_t legi_row,
+ uint64_t process_row,
const char *provider_user_id,
const char *provider_legitimization_id,
TALER_KYCLOGIC_ProofCallback cb,
@@ -948,7 +1415,7 @@ oauth2_proof (void *cls,
GNUNET_snprintf (ph->provider_legitimization_id,
sizeof (ph->provider_legitimization_id),
"%llu",
- (unsigned long long) legi_row);
+ (unsigned long long) process_row);
if ( (NULL != provider_legitimization_id) &&
(0 != strcmp (provider_legitimization_id,
ph->provider_legitimization_id)))
@@ -957,6 +1424,7 @@ oauth2_proof (void *cls,
GNUNET_free (ph);
return NULL;
}
+
ph->pd = pd;
ph->connection = connection;
ph->h_payto = *account_id;
@@ -967,62 +1435,126 @@ oauth2_proof (void *cls,
"code");
if (NULL == code)
{
- GNUNET_break_op (0);
- ph->status = TALER_KYCLOGIC_STATUS_USER_PENDING;
- ph->http_status = MHD_HTTP_BAD_REQUEST;
- ph->response = TALER_MHD_make_error (
- TALER_EC_GENERIC_PARAMETER_MALFORMED,
- "code");
+ const char *err;
+ const char *desc;
+ const char *euri;
+ json_t *body;
+
+ err = MHD_lookup_connection_value (connection,
+ MHD_GET_ARGUMENT_KIND,
+ "error");
+ if (NULL == err)
+ {
+ GNUNET_break_op (0);
+ ph->status = TALER_KYCLOGIC_STATUS_USER_PENDING;
+ ph->http_status = MHD_HTTP_BAD_REQUEST;
+ body = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("message",
+ "'code' parameter malformed"),
+ TALER_JSON_pack_ec (
+ TALER_EC_GENERIC_PARAMETER_MALFORMED));
+ GNUNET_break (
+ GNUNET_SYSERR !=
+ TALER_TEMPLATING_build (ph->connection,
+ &ph->http_status,
+ "oauth2-bad-request",
+ NULL,
+ NULL,
+ body,
+ &ph->response));
+ json_decref (body);
+ ph->task = GNUNET_SCHEDULER_add_now (&return_proof_response,
+ ph);
+ return ph;
+ }
+ desc = MHD_lookup_connection_value (connection,
+ MHD_GET_ARGUMENT_KIND,
+ "error_description");
+ euri = MHD_lookup_connection_value (connection,
+ MHD_GET_ARGUMENT_KIND,
+ "error_uri");
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "OAuth2 process %llu failed with error `%s'\n",
+ (unsigned long long) process_row,
+ err);
+ if (0 == strcmp (err,
+ "server_error"))
+ ph->status = TALER_KYCLOGIC_STATUS_PROVIDER_FAILED;
+ else if (0 == strcmp (err,
+ "unauthorized_client"))
+ ph->status = TALER_KYCLOGIC_STATUS_FAILED;
+ else if (0 == strcmp (err,
+ "temporarily_unavailable"))
+ ph->status = TALER_KYCLOGIC_STATUS_PENDING;
+ else
+ ph->status = TALER_KYCLOGIC_STATUS_INTERNAL_ERROR;
+ ph->http_status = MHD_HTTP_FORBIDDEN;
+ body = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("error",
+ err),
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_string ("error_details",
+ desc)),
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_string ("error_uri",
+ euri)));
+ GNUNET_break (
+ GNUNET_SYSERR !=
+ TALER_TEMPLATING_build (ph->connection,
+ &ph->http_status,
+ "oauth2-authentication-failure",
+ NULL,
+ NULL,
+ body,
+ &ph->response));
+ json_decref (body);
ph->task = GNUNET_SCHEDULER_add_now (&return_proof_response,
ph);
return ph;
- }
- ph->eh = curl_easy_init ();
- if (NULL == ph->eh)
- {
- GNUNET_break (0);
- ph->status = TALER_KYCLOGIC_STATUS_USER_PENDING;
- ph->http_status = MHD_HTTP_INTERNAL_SERVER_ERROR;
- ph->response = TALER_MHD_make_error (
- TALER_EC_GENERIC_ALLOCATION_FAILURE,
- "curl_easy_init");
- ph->task = GNUNET_SCHEDULER_add_now (&return_proof_response,
- ph);
- return ph;
}
+ ph->eh = curl_easy_init ();
+ GNUNET_assert (NULL != ph->eh);
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Requesting OAuth 2.0 data via HTTP POST `%s'\n",
+ pd->token_url);
GNUNET_assert (CURLE_OK ==
curl_easy_setopt (ph->eh,
CURLOPT_URL,
- pd->auth_url));
+ pd->token_url));
+ GNUNET_assert (CURLE_OK ==
+ curl_easy_setopt (ph->eh,
+ CURLOPT_VERBOSE,
+ 1));
GNUNET_assert (CURLE_OK ==
curl_easy_setopt (ph->eh,
CURLOPT_POST,
1));
{
char *client_id;
- char *redirect_uri;
char *client_secret;
char *authorization_code;
+ char *redirect_uri_encoded;
+ char *hps;
+ hps = GNUNET_STRINGS_data_to_string_alloc (&ph->h_payto,
+ sizeof (ph->h_payto));
+ {
+ char *redirect_uri;
+
+ GNUNET_asprintf (&redirect_uri,
+ "%skyc-proof/%s",
+ ps->exchange_base_url,
+ pd->section);
+ redirect_uri_encoded = TALER_urlencode (redirect_uri);
+ GNUNET_free (redirect_uri);
+ }
+ GNUNET_assert (NULL != redirect_uri_encoded);
client_id = curl_easy_escape (ph->eh,
pd->client_id,
0);
GNUNET_assert (NULL != client_id);
- {
- char *request_uri;
-
- GNUNET_asprintf (&request_uri,
- "%s?client_id=%s",
- pd->login_url,
- pd->client_id);
- redirect_uri = curl_easy_escape (ph->eh,
- request_uri,
- 0);
- GNUNET_free (request_uri);
- }
- GNUNET_assert (NULL != redirect_uri);
client_secret = curl_easy_escape (ph->eh,
pd->client_secret,
0);
@@ -1032,14 +1564,16 @@ oauth2_proof (void *cls,
0);
GNUNET_assert (NULL != authorization_code);
GNUNET_asprintf (&ph->post_body,
- "client_id=%s&redirect_uri=%s&client_secret=%s&code=%s&grant_type=authorization_code",
+ "client_id=%s&redirect_uri=%s&state=%s&client_secret=%s&code=%s&grant_type=authorization_code",
client_id,
- redirect_uri,
+ redirect_uri_encoded,
+ hps,
client_secret,
authorization_code);
curl_free (authorization_code);
curl_free (client_secret);
- curl_free (redirect_uri);
+ GNUNET_free (redirect_uri_encoded);
+ GNUNET_free (hps);
curl_free (client_id);
}
GNUNET_assert (CURLE_OK ==
@@ -1066,34 +1600,6 @@ oauth2_proof (void *cls,
/**
- * Cancel KYC proof.
- *
- * @param[in] ph handle of operation to cancel
- */
-static void
-oauth2_proof_cancel (struct TALER_KYCLOGIC_ProofHandle *ph)
-{
- if (NULL != ph->task)
- {
- GNUNET_SCHEDULER_cancel (ph->task);
- ph->task = NULL;
- }
- if (NULL != ph->job)
- {
- GNUNET_CURL_job_cancel (ph->job);
- ph->job = NULL;
- }
- if (NULL != ph->response)
- {
- MHD_destroy_response (ph->response);
- ph->response = NULL;
- }
- GNUNET_free (ph->post_body);
- GNUNET_free (ph);
-}
-
-
-/**
* Function to asynchronously return the 404 not found
* page for the webhook.
*
@@ -1114,8 +1620,10 @@ wh_return_not_found (void *cls)
NULL,
NULL,
NULL,
+ NULL,
TALER_KYCLOGIC_STATUS_KEEP,
GNUNET_TIME_UNIT_ZERO_ABS,
+ NULL,
MHD_HTTP_NOT_FOUND,
response);
GNUNET_free (wh);
@@ -1152,6 +1660,13 @@ oauth2_webhook (void *cls,
struct PluginState *ps = cls;
struct TALER_KYCLOGIC_WebhookHandle *wh;
+ (void) pd;
+ (void) plc;
+ (void) plc_cls;
+ (void) http_method;
+ (void) url_path;
+ (void) connection;
+ (void) body;
wh = GNUNET_new (struct TALER_KYCLOGIC_WebhookHandle);
wh->cb = cb;
wh->cb_cls = cb_cls;
diff --git a/src/kyclogic/plugin_kyclogic_persona.c b/src/kyclogic/plugin_kyclogic_persona.c
new file mode 100644
index 000000000..c68b7f881
--- /dev/null
+++ b/src/kyclogic/plugin_kyclogic_persona.c
@@ -0,0 +1,2268 @@
+/*
+ This file is part of GNU Taler
+ Copyright (C) 2022, 2023 Taler Systems SA
+
+ Taler is free software; you can redistribute it and/or modify it under the
+ terms of the GNU Affero General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ Taler is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License along with
+ Taler; see the file COPYING.GPL. If not, see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file plugin_kyclogic_persona.c
+ * @brief persona for an authentication flow logic
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_attributes.h"
+#include "taler_kyclogic_plugin.h"
+#include "taler_mhd_lib.h"
+#include "taler_curl_lib.h"
+#include "taler_json_lib.h"
+#include "taler_kyclogic_lib.h"
+#include "taler_templating_lib.h"
+#include <regex.h>
+#include "taler_util.h"
+
+
+/**
+ * Which version of the persona API are we implementing?
+ */
+#define PERSONA_VERSION "2021-07-05"
+
+/**
+ * Saves the state of a plugin.
+ */
+struct PluginState
+{
+
+ /**
+ * Our base URL.
+ */
+ char *exchange_base_url;
+
+ /**
+ * Our global configuration.
+ */
+ const struct GNUNET_CONFIGURATION_Handle *cfg;
+
+ /**
+ * Context for CURL operations (useful to the event loop)
+ */
+ struct GNUNET_CURL_Context *curl_ctx;
+
+ /**
+ * Context for integrating @e curl_ctx with the
+ * GNUnet event loop.
+ */
+ struct GNUNET_CURL_RescheduleContext *curl_rc;
+
+ /**
+ * Authorization token to use when receiving webhooks from the Persona
+ * service. Optional. Note that webhooks are *global* and not per
+ * template.
+ */
+ char *webhook_token;
+
+
+};
+
+
+/**
+ * Keeps the plugin-specific state for
+ * a given configuration section.
+ */
+struct TALER_KYCLOGIC_ProviderDetails
+{
+
+ /**
+ * Overall plugin state.
+ */
+ struct PluginState *ps;
+
+ /**
+ * Configuration section that configured us.
+ */
+ char *section;
+
+ /**
+ * Salt to use for idempotency.
+ */
+ char *salt;
+
+ /**
+ * Authorization token to use when talking
+ * to the service.
+ */
+ char *auth_token;
+
+ /**
+ * Template ID for the KYC check to perform.
+ */
+ char *template_id;
+
+ /**
+ * Subdomain to use.
+ */
+ char *subdomain;
+
+ /**
+ * Name of the program we use to convert outputs
+ * from Persona into our JSON inputs.
+ */
+ char *conversion_binary;
+
+ /**
+ * Where to redirect the client upon completion.
+ */
+ char *post_kyc_redirect_url;
+
+ /**
+ * Validity time for a successful KYC process.
+ */
+ struct GNUNET_TIME_Relative validity;
+
+ /**
+ * Curl-ready authentication header to use.
+ */
+ struct curl_slist *slist;
+
+};
+
+
+/**
+ * Handle for an initiation operation.
+ */
+struct TALER_KYCLOGIC_InitiateHandle
+{
+
+ /**
+ * Hash of the payto:// URI we are initiating the KYC for.
+ */
+ struct TALER_PaytoHashP h_payto;
+
+ /**
+ * UUID being checked.
+ */
+ uint64_t legitimization_uuid;
+
+ /**
+ * Our configuration details.
+ */
+ const struct TALER_KYCLOGIC_ProviderDetails *pd;
+
+ /**
+ * Continuation to call.
+ */
+ TALER_KYCLOGIC_InitiateCallback cb;
+
+ /**
+ * Closure for @a cb.
+ */
+ void *cb_cls;
+
+ /**
+ * Context for #TEH_curl_easy_post(). Keeps the data that must
+ * persist for Curl to make the upload.
+ */
+ struct TALER_CURL_PostContext ctx;
+
+ /**
+ * Handle for the request.
+ */
+ struct GNUNET_CURL_Job *job;
+
+ /**
+ * URL of the cURL request.
+ */
+ char *url;
+
+ /**
+ * Request-specific headers to use.
+ */
+ struct curl_slist *slist;
+
+};
+
+
+/**
+ * Handle for an KYC proof operation.
+ */
+struct TALER_KYCLOGIC_ProofHandle
+{
+
+ /**
+ * Overall plugin state.
+ */
+ struct PluginState *ps;
+
+ /**
+ * Our configuration details.
+ */
+ const struct TALER_KYCLOGIC_ProviderDetails *pd;
+
+ /**
+ * Continuation to call.
+ */
+ TALER_KYCLOGIC_ProofCallback cb;
+
+ /**
+ * Closure for @e cb.
+ */
+ void *cb_cls;
+
+ /**
+ * Connection we are handling.
+ */
+ struct MHD_Connection *connection;
+
+ /**
+ * Task for asynchronous execution.
+ */
+ struct GNUNET_SCHEDULER_Task *task;
+
+ /**
+ * Handle for the request.
+ */
+ struct GNUNET_CURL_Job *job;
+
+ /**
+ * URL of the cURL request.
+ */
+ char *url;
+
+ /**
+ * Handle to an external process that converts the
+ * Persona response to our internal format.
+ */
+ struct TALER_JSON_ExternalConversion *ec;
+
+ /**
+ * Hash of the payto:// URI we are checking the KYC for.
+ */
+ struct TALER_PaytoHashP h_payto;
+
+ /**
+ * Row in the legitimization processes of the
+ * legitimization proof that is being checked.
+ */
+ uint64_t process_row;
+
+ /**
+ * Account ID at the provider.
+ */
+ char *provider_user_id;
+
+ /**
+ * Account ID from the service.
+ */
+ char *account_id;
+
+ /**
+ * Inquiry ID at the provider.
+ */
+ char *inquiry_id;
+};
+
+
+/**
+ * Handle for an KYC Web hook operation.
+ */
+struct TALER_KYCLOGIC_WebhookHandle
+{
+
+ /**
+ * Continuation to call when done.
+ */
+ TALER_KYCLOGIC_WebhookCallback cb;
+
+ /**
+ * Closure for @a cb.
+ */
+ void *cb_cls;
+
+ /**
+ * Task for asynchronous execution.
+ */
+ struct GNUNET_SCHEDULER_Task *task;
+
+ /**
+ * Overall plugin state.
+ */
+ struct PluginState *ps;
+
+ /**
+ * Our configuration details.
+ */
+ const struct TALER_KYCLOGIC_ProviderDetails *pd;
+
+ /**
+ * Connection we are handling.
+ */
+ struct MHD_Connection *connection;
+
+ /**
+ * Verification ID from the service.
+ */
+ char *inquiry_id;
+
+ /**
+ * Account ID from the service.
+ */
+ char *account_id;
+
+ /**
+ * URL of the cURL request.
+ */
+ char *url;
+
+ /**
+ * Handle for the request.
+ */
+ struct GNUNET_CURL_Job *job;
+
+ /**
+ * Response to return asynchronously.
+ */
+ struct MHD_Response *resp;
+
+ /**
+ * ID of the template the webhook is about,
+ * according to the service.
+ */
+ const char *template_id;
+
+ /**
+ * Handle to an external process that converts the
+ * Persona response to our internal format.
+ */
+ struct TALER_JSON_ExternalConversion *ec;
+
+ /**
+ * Our account ID.
+ */
+ struct TALER_PaytoHashP h_payto;
+
+ /**
+ * UUID being checked.
+ */
+ uint64_t process_row;
+
+ /**
+ * HTTP response code to return asynchronously.
+ */
+ unsigned int response_code;
+};
+
+
+/**
+ * Release configuration resources previously loaded
+ *
+ * @param[in] pd configuration to release
+ */
+static void
+persona_unload_configuration (struct TALER_KYCLOGIC_ProviderDetails *pd)
+{
+ curl_slist_free_all (pd->slist);
+ GNUNET_free (pd->auth_token);
+ GNUNET_free (pd->template_id);
+ GNUNET_free (pd->subdomain);
+ GNUNET_free (pd->conversion_binary);
+ GNUNET_free (pd->salt);
+ GNUNET_free (pd->section);
+ GNUNET_free (pd->post_kyc_redirect_url);
+ GNUNET_free (pd);
+}
+
+
+/**
+ * Load the configuration of the KYC provider.
+ *
+ * @param cls closure
+ * @param provider_section_name configuration section to parse
+ * @return NULL if configuration is invalid
+ */
+static struct TALER_KYCLOGIC_ProviderDetails *
+persona_load_configuration (void *cls,
+ const char *provider_section_name)
+{
+ struct PluginState *ps = cls;
+ struct TALER_KYCLOGIC_ProviderDetails *pd;
+
+ 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,
+ "KYC_PERSONA_VALIDITY",
+ &pd->validity))
+ {
+ GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+ provider_section_name,
+ "KYC_PERSONA_VALIDITY");
+ persona_unload_configuration (pd);
+ return NULL;
+ }
+ if (GNUNET_OK !=
+ GNUNET_CONFIGURATION_get_value_string (ps->cfg,
+ provider_section_name,
+ "KYC_PERSONA_AUTH_TOKEN",
+ &pd->auth_token))
+ {
+ GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+ provider_section_name,
+ "KYC_PERSONA_AUTH_TOKEN");
+ persona_unload_configuration (pd);
+ return NULL;
+ }
+ if (GNUNET_OK !=
+ GNUNET_CONFIGURATION_get_value_string (ps->cfg,
+ provider_section_name,
+ "KYC_PERSONA_SALT",
+ &pd->salt))
+ {
+ uint32_t salt[8];
+
+ GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_NONCE,
+ salt,
+ sizeof (salt));
+ pd->salt = GNUNET_STRINGS_data_to_string_alloc (salt,
+ sizeof (salt));
+ }
+ if (GNUNET_OK !=
+ GNUNET_CONFIGURATION_get_value_string (ps->cfg,
+ provider_section_name,
+ "KYC_PERSONA_SUBDOMAIN",
+ &pd->subdomain))
+ {
+ GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+ provider_section_name,
+ "KYC_PERSONA_SUBDOMAIN");
+ persona_unload_configuration (pd);
+ return NULL;
+ }
+ if (GNUNET_OK !=
+ GNUNET_CONFIGURATION_get_value_string (ps->cfg,
+ provider_section_name,
+ "KYC_PERSONA_CONVERTER_HELPER",
+ &pd->conversion_binary))
+ {
+ GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+ provider_section_name,
+ "KYC_PERSONA_CONVERTER_HELPER");
+ persona_unload_configuration (pd);
+ return NULL;
+ }
+ if (GNUNET_OK !=
+ GNUNET_CONFIGURATION_get_value_string (ps->cfg,
+ provider_section_name,
+ "KYC_PERSONA_POST_URL",
+ &pd->post_kyc_redirect_url))
+ {
+ GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+ provider_section_name,
+ "KYC_PERSONA_POST_URL");
+ persona_unload_configuration (pd);
+ return NULL;
+ }
+ if (GNUNET_OK !=
+ GNUNET_CONFIGURATION_get_value_string (ps->cfg,
+ provider_section_name,
+ "KYC_PERSONA_TEMPLATE_ID",
+ &pd->template_id))
+ {
+ GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+ provider_section_name,
+ "KYC_PERSONA_TEMPLATE_ID");
+ persona_unload_configuration (pd);
+ return NULL;
+ }
+ {
+ char *auth;
+
+ GNUNET_asprintf (&auth,
+ "%s: Bearer %s",
+ MHD_HTTP_HEADER_AUTHORIZATION,
+ pd->auth_token);
+ pd->slist = curl_slist_append (NULL,
+ auth);
+ GNUNET_free (auth);
+ GNUNET_asprintf (&auth,
+ "%s: %s",
+ MHD_HTTP_HEADER_ACCEPT,
+ "application/json");
+ pd->slist = curl_slist_append (pd->slist,
+ "Persona-Version: "
+ PERSONA_VERSION);
+ GNUNET_free (auth);
+ }
+ return pd;
+}
+
+
+/**
+ * Cancel KYC check initiation.
+ *
+ * @param[in] ih handle of operation to cancel
+ */
+static void
+persona_initiate_cancel (struct TALER_KYCLOGIC_InitiateHandle *ih)
+{
+ if (NULL != ih->job)
+ {
+ GNUNET_CURL_job_cancel (ih->job);
+ ih->job = NULL;
+ }
+ GNUNET_free (ih->url);
+ TALER_curl_easy_post_finished (&ih->ctx);
+ curl_slist_free_all (ih->slist);
+ GNUNET_free (ih);
+}
+
+
+/**
+ * Function called when we're done processing the
+ * HTTP POST "/api/v1/inquiries" request.
+ *
+ * @param cls the `struct TALER_KYCLOGIC_InitiateHandle`
+ * @param response_code HTTP response code, 0 on error
+ * @param response parsed JSON result, NULL on error
+ */
+static void
+handle_initiate_finished (void *cls,
+ long response_code,
+ const void *response)
+{
+ struct TALER_KYCLOGIC_InitiateHandle *ih = cls;
+ const struct TALER_KYCLOGIC_ProviderDetails *pd = ih->pd;
+ const json_t *j = response;
+ char *url;
+ json_t *data;
+ const char *type;
+ const char *inquiry_id;
+ const char *persona_account_id;
+ const char *ename;
+ unsigned int eline;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_string ("type",
+ &type),
+ GNUNET_JSON_spec_string ("id",
+ &inquiry_id),
+ GNUNET_JSON_spec_end ()
+ };
+
+ ih->job = NULL;
+ switch (response_code)
+ {
+ case MHD_HTTP_CREATED:
+ /* handled below */
+ break;
+ case MHD_HTTP_UNAUTHORIZED:
+ case MHD_HTTP_FORBIDDEN:
+ {
+ const char *msg;
+
+ msg = json_string_value (
+ json_object_get (
+ json_array_get (
+ json_object_get (j,
+ "errors"),
+ 0),
+ "title"));
+
+ ih->cb (ih->cb_cls,
+ TALER_EC_EXCHANGE_KYC_CHECK_AUTHORIZATION_FAILED,
+ NULL,
+ NULL,
+ NULL,
+ msg);
+ persona_initiate_cancel (ih);
+ return;
+ }
+ case MHD_HTTP_NOT_FOUND:
+ case MHD_HTTP_CONFLICT:
+ {
+ const char *msg;
+
+ msg = json_string_value (
+ json_object_get (
+ json_array_get (
+ json_object_get (j,
+ "errors"),
+ 0),
+ "title"));
+
+ ih->cb (ih->cb_cls,
+ TALER_EC_EXCHANGE_KYC_GENERIC_PROVIDER_UNEXPECTED_REPLY,
+ NULL,
+ NULL,
+ NULL,
+ msg);
+ persona_initiate_cancel (ih);
+ return;
+ }
+ case MHD_HTTP_BAD_REQUEST:
+ case MHD_HTTP_UNPROCESSABLE_ENTITY:
+ {
+ const char *msg;
+
+ GNUNET_break (0);
+ json_dumpf (j,
+ stderr,
+ JSON_INDENT (2));
+ msg = json_string_value (
+ json_object_get (
+ json_array_get (
+ json_object_get (j,
+ "errors"),
+ 0),
+ "title"));
+
+ ih->cb (ih->cb_cls,
+ TALER_EC_EXCHANGE_KYC_GENERIC_LOGIC_BUG,
+ NULL,
+ NULL,
+ NULL,
+ msg);
+ persona_initiate_cancel (ih);
+ return;
+ }
+ case MHD_HTTP_TOO_MANY_REQUESTS:
+ {
+ const char *msg;
+
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Rate limiting requested:\n");
+ json_dumpf (j,
+ stderr,
+ JSON_INDENT (2));
+ msg = json_string_value (
+ json_object_get (
+ json_array_get (
+ json_object_get (j,
+ "errors"),
+ 0),
+ "title"));
+ ih->cb (ih->cb_cls,
+ TALER_EC_EXCHANGE_KYC_GENERIC_PROVIDER_RATE_LIMIT_EXCEEDED,
+ NULL,
+ NULL,
+ NULL,
+ msg);
+ persona_initiate_cancel (ih);
+ return;
+ }
+ default:
+ {
+ char *err;
+
+ GNUNET_break_op (0);
+ json_dumpf (j,
+ stderr,
+ JSON_INDENT (2));
+ GNUNET_asprintf (&err,
+ "Unexpected HTTP status %u from Persona\n",
+ (unsigned int) response_code);
+ ih->cb (ih->cb_cls,
+ TALER_EC_EXCHANGE_KYC_GENERIC_PROVIDER_UNEXPECTED_REPLY,
+ NULL,
+ NULL,
+ NULL,
+ err);
+ GNUNET_free (err);
+ persona_initiate_cancel (ih);
+ return;
+ }
+ }
+ data = json_object_get (j,
+ "data");
+ if (NULL == data)
+ {
+ GNUNET_break_op (0);
+ json_dumpf (j,
+ stderr,
+ JSON_INDENT (2));
+ persona_initiate_cancel (ih);
+ return;
+ }
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (data,
+ spec,
+ &ename,
+ &eline))
+ {
+ GNUNET_break_op (0);
+ json_dumpf (j,
+ stderr,
+ JSON_INDENT (2));
+ ih->cb (ih->cb_cls,
+ TALER_EC_EXCHANGE_KYC_GENERIC_PROVIDER_UNEXPECTED_REPLY,
+ NULL,
+ NULL,
+ NULL,
+ ename);
+ persona_initiate_cancel (ih);
+ return;
+ }
+ persona_account_id
+ = json_string_value (
+ json_object_get (
+ json_object_get (
+ json_object_get (
+ json_object_get (data,
+ "relationships"),
+ "account"),
+ "data"),
+ "id"));
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Starting inquiry %s for Persona account %s\n",
+ inquiry_id,
+ persona_account_id);
+ GNUNET_asprintf (&url,
+ "https://%s.withpersona.com/verify"
+ "?inquiry-id=%s",
+ pd->subdomain,
+ inquiry_id);
+ ih->cb (ih->cb_cls,
+ TALER_EC_NONE,
+ url,
+ persona_account_id,
+ inquiry_id,
+ NULL);
+ GNUNET_free (url);
+ persona_initiate_cancel (ih);
+}
+
+
+/**
+ * Initiate KYC check.
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @param pd provider configuration details
+ * @param account_id which account to trigger process for
+ * @param legitimization_uuid unique ID for the legitimization process
+ * @param cb function to call with the result
+ * @param cb_cls closure for @a cb
+ * @return handle to cancel operation early
+ */
+static struct TALER_KYCLOGIC_InitiateHandle *
+persona_initiate (void *cls,
+ const struct TALER_KYCLOGIC_ProviderDetails *pd,
+ const struct TALER_PaytoHashP *account_id,
+ uint64_t legitimization_uuid,
+ TALER_KYCLOGIC_InitiateCallback cb,
+ void *cb_cls)
+{
+ struct PluginState *ps = cls;
+ struct TALER_KYCLOGIC_InitiateHandle *ih;
+ json_t *body;
+ CURL *eh;
+
+ eh = curl_easy_init ();
+ if (NULL == eh)
+ {
+ GNUNET_break (0);
+ return NULL;
+ }
+ ih = GNUNET_new (struct TALER_KYCLOGIC_InitiateHandle);
+ ih->legitimization_uuid = legitimization_uuid;
+ ih->cb = cb;
+ ih->cb_cls = cb_cls;
+ ih->h_payto = *account_id;
+ ih->pd = pd;
+ GNUNET_asprintf (&ih->url,
+ "https://withpersona.com/api/v1/inquiries");
+ {
+ char *payto_s;
+ char *proof_url;
+ char ref_s[24];
+
+ GNUNET_snprintf (ref_s,
+ sizeof (ref_s),
+ "%llu",
+ (unsigned long long) ih->legitimization_uuid);
+ payto_s = GNUNET_STRINGS_data_to_string_alloc (&ih->h_payto,
+ sizeof (ih->h_payto));
+ GNUNET_break ('/' ==
+ pd->ps->exchange_base_url[strlen (
+ pd->ps->exchange_base_url) - 1]);
+ GNUNET_asprintf (&proof_url,
+ "%skyc-proof/%s?state=%s",
+ pd->ps->exchange_base_url,
+ pd->section,
+ payto_s);
+ body = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_object_steal (
+ "data",
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_object_steal (
+ "attributes",
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("inquiry_template_id",
+ pd->template_id),
+ GNUNET_JSON_pack_string ("reference_id",
+ ref_s),
+ GNUNET_JSON_pack_string ("redirect_uri",
+ proof_url)
+ )))));
+ GNUNET_assert (NULL != body);
+ GNUNET_free (payto_s);
+ GNUNET_free (proof_url);
+ }
+ GNUNET_break (CURLE_OK ==
+ curl_easy_setopt (eh,
+ CURLOPT_VERBOSE,
+ 0));
+ GNUNET_assert (CURLE_OK ==
+ curl_easy_setopt (eh,
+ CURLOPT_MAXREDIRS,
+ 1L));
+ GNUNET_break (CURLE_OK ==
+ curl_easy_setopt (eh,
+ CURLOPT_URL,
+ ih->url));
+ ih->ctx.disable_compression = true;
+ if (GNUNET_OK !=
+ TALER_curl_easy_post (&ih->ctx,
+ eh,
+ body))
+ {
+ GNUNET_break (0);
+ GNUNET_free (ih->url);
+ GNUNET_free (ih);
+ curl_easy_cleanup (eh);
+ json_decref (body);
+ return NULL;
+ }
+ json_decref (body);
+ ih->job = GNUNET_CURL_job_add2 (ps->curl_ctx,
+ eh,
+ ih->ctx.headers,
+ &handle_initiate_finished,
+ ih);
+ GNUNET_CURL_extend_headers (ih->job,
+ pd->slist);
+ {
+ char *ikh;
+
+ GNUNET_asprintf (&ikh,
+ "Idempotency-Key: %llu-%s",
+ (unsigned long long) ih->legitimization_uuid,
+ pd->salt);
+ ih->slist = curl_slist_append (NULL,
+ ikh);
+ GNUNET_free (ikh);
+ }
+ GNUNET_CURL_extend_headers (ih->job,
+ ih->slist);
+ return ih;
+}
+
+
+/**
+ * Cancel KYC proof.
+ *
+ * @param[in] ph handle of operation to cancel
+ */
+static void
+persona_proof_cancel (struct TALER_KYCLOGIC_ProofHandle *ph)
+{
+ if (NULL != ph->job)
+ {
+ GNUNET_CURL_job_cancel (ph->job);
+ ph->job = NULL;
+ }
+ if (NULL != ph->ec)
+ {
+ TALER_JSON_external_conversion_stop (ph->ec);
+ ph->ec = NULL;
+ }
+ GNUNET_free (ph->url);
+ GNUNET_free (ph->provider_user_id);
+ GNUNET_free (ph->account_id);
+ GNUNET_free (ph->inquiry_id);
+ GNUNET_free (ph);
+}
+
+
+/**
+ * Call @a ph callback with the operation result.
+ *
+ * @param ph proof handle to generate reply for
+ * @param status status to return
+ * @param account_id account to return
+ * @param inquiry_id inquiry ID to supply
+ * @param http_status HTTP status to use
+ * @param template template to instantiate
+ * @param[in] body body for the template to use (reference
+ * is consumed)
+ */
+static void
+proof_generic_reply (struct TALER_KYCLOGIC_ProofHandle *ph,
+ enum TALER_KYCLOGIC_KycStatus status,
+ const char *account_id,
+ const char *inquiry_id,
+ unsigned int http_status,
+ const char *template,
+ json_t *body)
+{
+ struct MHD_Response *resp;
+ enum GNUNET_GenericReturnValue ret;
+
+ /* This API is not usable for successful replies */
+ GNUNET_assert (TALER_KYCLOGIC_STATUS_SUCCESS != status);
+ ret = TALER_TEMPLATING_build (ph->connection,
+ &http_status,
+ template,
+ NULL,
+ NULL,
+ body,
+ &resp);
+ json_decref (body);
+ if (GNUNET_SYSERR == ret)
+ {
+ GNUNET_break (0);
+ resp = NULL; /* good luck */
+ }
+ ph->cb (ph->cb_cls,
+ status,
+ account_id,
+ inquiry_id,
+ GNUNET_TIME_UNIT_ZERO_ABS,
+ NULL,
+ http_status,
+ resp);
+}
+
+
+/**
+ * Call @a ph callback with HTTP error response.
+ *
+ * @param ph proof handle to generate reply for
+ * @param inquiry_id inquiry ID to supply
+ * @param http_status HTTP status to use
+ * @param template template to instantiate
+ * @param[in] body body for the template to use (reference
+ * is consumed)
+ */
+static void
+proof_reply_error (struct TALER_KYCLOGIC_ProofHandle *ph,
+ const char *inquiry_id,
+ unsigned int http_status,
+ const char *template,
+ json_t *body)
+{
+ proof_generic_reply (ph,
+ TALER_KYCLOGIC_STATUS_PROVIDER_FAILED,
+ NULL, /* user id */
+ inquiry_id,
+ http_status,
+ template,
+ body);
+}
+
+
+/**
+ * Return a response for the @a ph request indicating a
+ * protocol violation by the Persona server.
+ *
+ * @param[in,out] ph request we are processing
+ * @param response_code HTTP status returned by Persona
+ * @param inquiry_id ID of the inquiry this is about
+ * @param detail where the response was wrong
+ * @param data full response data to output
+ */
+static void
+return_invalid_response (struct TALER_KYCLOGIC_ProofHandle *ph,
+ unsigned int response_code,
+ const char *inquiry_id,
+ const char *detail,
+ const json_t *data)
+{
+ proof_reply_error (
+ ph,
+ inquiry_id,
+ MHD_HTTP_BAD_GATEWAY,
+ "persona-invalid-response",
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_uint64 ("persona_http_status",
+ response_code),
+ GNUNET_JSON_pack_string ("persona_inquiry_id",
+ inquiry_id),
+ TALER_JSON_pack_ec (
+ TALER_EC_EXCHANGE_KYC_GENERIC_PROVIDER_UNEXPECTED_REPLY),
+ GNUNET_JSON_pack_string ("detail",
+ detail),
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_object_incref ("data",
+ (json_t *)
+ data))));
+}
+
+
+/**
+ * Start the external conversion helper.
+ *
+ * @param pd configuration details
+ * @param attr attributes to give to the helper
+ * @param cb function to call with the result
+ * @param cb_cls closure for @a cb
+ * @return handle for the helper
+ */
+static struct TALER_JSON_ExternalConversion *
+start_conversion (const struct TALER_KYCLOGIC_ProviderDetails *pd,
+ const json_t *attr,
+ TALER_JSON_JsonCallback cb,
+ void *cb_cls)
+{
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Calling converter `%s' with JSON\n",
+ pd->conversion_binary);
+ json_dumpf (attr,
+ stderr,
+ JSON_INDENT (2));
+ return TALER_JSON_external_conversion_start (
+ attr,
+ cb,
+ cb_cls,
+ pd->conversion_binary,
+ pd->conversion_binary,
+ "-a",
+ pd->auth_token,
+ NULL
+ );
+}
+
+
+/**
+ * Type of a callback that receives a JSON @a result.
+ *
+ * @param cls closure with a `struct TALER_KYCLOGIC_ProofHandle *`
+ * @param status_type how did the process die
+ * @param code termination status code from the process
+ * @param attr result some JSON result, NULL if we failed to get an JSON output
+ */
+static void
+proof_post_conversion_cb (void *cls,
+ enum GNUNET_OS_ProcessStatusType status_type,
+ unsigned long code,
+ const json_t *attr)
+{
+ struct TALER_KYCLOGIC_ProofHandle *ph = cls;
+ struct MHD_Response *resp;
+ struct GNUNET_TIME_Absolute expiration;
+
+ ph->ec = NULL;
+ if ( (NULL == attr) ||
+ (0 != code) )
+ {
+ GNUNET_break_op (0);
+ return_invalid_response (ph,
+ MHD_HTTP_OK,
+ ph->inquiry_id,
+ "converter",
+ NULL);
+ persona_proof_cancel (ph);
+ return;
+ }
+ expiration = GNUNET_TIME_relative_to_absolute (ph->pd->validity);
+ resp = MHD_create_response_from_buffer (0,
+ "",
+ MHD_RESPMEM_PERSISTENT);
+ GNUNET_break (MHD_YES ==
+ MHD_add_response_header (resp,
+ MHD_HTTP_HEADER_LOCATION,
+ ph->pd->post_kyc_redirect_url));
+ TALER_MHD_add_global_headers (resp);
+ ph->cb (ph->cb_cls,
+ TALER_KYCLOGIC_STATUS_SUCCESS,
+ ph->account_id,
+ ph->inquiry_id,
+ expiration,
+ attr,
+ MHD_HTTP_SEE_OTHER,
+ resp);
+ persona_proof_cancel (ph);
+}
+
+
+/**
+ * Function called when we're done processing the
+ * HTTP "/api/v1/inquiries/{inquiry-id}" request.
+ *
+ * @param cls the `struct TALER_KYCLOGIC_InitiateHandle`
+ * @param response_code HTTP response code, 0 on error
+ * @param response parsed JSON result, NULL on error
+ */
+static void
+handle_proof_finished (void *cls,
+ long response_code,
+ const void *response)
+{
+ struct TALER_KYCLOGIC_ProofHandle *ph = cls;
+ const json_t *j = response;
+ const json_t *data = json_object_get (j,
+ "data");
+
+ ph->job = NULL;
+ switch (response_code)
+ {
+ case MHD_HTTP_OK:
+ {
+ const char *inquiry_id;
+ const char *account_id;
+ const char *type = NULL;
+ const json_t *attributes;
+ const json_t *relationships;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_string ("type",
+ &type),
+ GNUNET_JSON_spec_string ("id",
+ &inquiry_id),
+ GNUNET_JSON_spec_object_const ("attributes",
+ &attributes),
+ GNUNET_JSON_spec_object_const ("relationships",
+ &relationships),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if ( (NULL == data) ||
+ (GNUNET_OK !=
+ GNUNET_JSON_parse (data,
+ spec,
+ NULL, NULL)) ||
+ (0 != strcmp (type,
+ "inquiry")) )
+ {
+ GNUNET_break_op (0);
+ return_invalid_response (ph,
+ response_code,
+ inquiry_id,
+ "data",
+ data);
+ break;
+ }
+
+ {
+ const char *status; /* "completed", what else? */
+ const char *reference_id; /* or legitimization number */
+ const char *expired_at = NULL; /* often 'null' format: "2022-08-18T10:14:26.000Z" */
+ struct GNUNET_JSON_Specification ispec[] = {
+ GNUNET_JSON_spec_string ("status",
+ &status),
+ GNUNET_JSON_spec_string ("reference-id",
+ &reference_id),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_string ("expired-at",
+ &expired_at),
+ NULL),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (attributes,
+ ispec,
+ NULL, NULL))
+ {
+ GNUNET_break_op (0);
+ return_invalid_response (ph,
+ response_code,
+ inquiry_id,
+ "data-attributes",
+ data);
+ break;
+ }
+ {
+ unsigned long long idr;
+ char dummy;
+
+ if ( (1 != sscanf (reference_id,
+ "%llu%c",
+ &idr,
+ &dummy)) ||
+ (idr != ph->process_row) )
+ {
+ GNUNET_break_op (0);
+ return_invalid_response (ph,
+ response_code,
+ inquiry_id,
+ "data-attributes-reference_id",
+ data);
+ break;
+ }
+ }
+
+ if (0 != strcmp (inquiry_id,
+ ph->inquiry_id))
+ {
+ GNUNET_break_op (0);
+ return_invalid_response (ph,
+ response_code,
+ inquiry_id,
+ "data-id",
+ data);
+ break;
+ }
+
+ account_id = json_string_value (
+ json_object_get (
+ json_object_get (
+ json_object_get (
+ relationships,
+ "account"),
+ "data"),
+ "id"));
+
+ if (0 != strcmp (status,
+ "completed"))
+ {
+ proof_generic_reply (
+ ph,
+ TALER_KYCLOGIC_STATUS_FAILED,
+ account_id,
+ inquiry_id,
+ MHD_HTTP_OK,
+ "persona-kyc-failed",
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_uint64 ("persona_http_status",
+ response_code),
+ GNUNET_JSON_pack_string ("persona_inquiry_id",
+ inquiry_id),
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_object_incref ("data",
+ (json_t *)
+ data))));
+ break;
+ }
+
+ if (NULL == account_id)
+ {
+ GNUNET_break_op (0);
+ return_invalid_response (ph,
+ response_code,
+ inquiry_id,
+ "data-relationships-account-data-id",
+ data);
+ break;
+ }
+ ph->account_id = GNUNET_strdup (account_id);
+ ph->ec = start_conversion (ph->pd,
+ j,
+ &proof_post_conversion_cb,
+ ph);
+ if (NULL == ph->ec)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failed to start Persona conversion helper\n");
+ proof_reply_error (
+ ph,
+ ph->inquiry_id,
+ MHD_HTTP_BAD_GATEWAY,
+ "persona-logic-failure",
+ GNUNET_JSON_PACK (
+ TALER_JSON_pack_ec (
+ TALER_EC_EXCHANGE_GENERIC_KYC_CONVERTER_FAILED)));
+ break;
+ }
+ }
+ return; /* continued in proof_post_conversion_cb */
+ }
+ case MHD_HTTP_BAD_REQUEST:
+ case MHD_HTTP_NOT_FOUND:
+ case MHD_HTTP_CONFLICT:
+ case MHD_HTTP_UNPROCESSABLE_ENTITY:
+ /* These are errors with this code */
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "PERSONA failed with response %u:\n",
+ (unsigned int) response_code);
+ json_dumpf (j,
+ stderr,
+ JSON_INDENT (2));
+ proof_reply_error (
+ ph,
+ ph->inquiry_id,
+ MHD_HTTP_BAD_GATEWAY,
+ "persona-logic-failure",
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_uint64 ("persona_http_status",
+ response_code),
+ TALER_JSON_pack_ec (
+ TALER_EC_EXCHANGE_KYC_GENERIC_PROVIDER_UNEXPECTED_REPLY),
+
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_object_incref ("data",
+ (json_t *)
+ data))));
+ break;
+ case MHD_HTTP_UNAUTHORIZED:
+ /* These are failures of the exchange operator */
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Refused access with HTTP status code %u\n",
+ (unsigned int) response_code);
+ proof_reply_error (
+ ph,
+ ph->inquiry_id,
+ MHD_HTTP_BAD_GATEWAY,
+ "persona-exchange-unauthorized",
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_uint64 ("persona_http_status",
+ response_code),
+ TALER_JSON_pack_ec (
+ TALER_EC_EXCHANGE_KYC_GENERIC_PROVIDER_ACCESS_REFUSED),
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_object_incref ("data",
+ (json_t *)
+ data))));
+ break;
+ case MHD_HTTP_PAYMENT_REQUIRED:
+ /* These are failures of the exchange operator */
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Refused access with HTTP status code %u\n",
+ (unsigned int) response_code);
+ proof_reply_error (
+ ph,
+ ph->inquiry_id,
+ MHD_HTTP_SERVICE_UNAVAILABLE,
+ "persona-exchange-unpaid",
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_uint64 ("persona_http_status",
+ response_code),
+ TALER_JSON_pack_ec (
+ TALER_EC_EXCHANGE_KYC_GENERIC_PROVIDER_ACCESS_REFUSED),
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_object_incref ("data",
+ (json_t *)
+ data))));
+ break;
+ case MHD_HTTP_REQUEST_TIMEOUT:
+ /* These are networking issues */
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "PERSONA failed with response %u:\n",
+ (unsigned int) response_code);
+ json_dumpf (j,
+ stderr,
+ JSON_INDENT (2));
+ proof_reply_error (
+ ph,
+ ph->inquiry_id,
+ MHD_HTTP_GATEWAY_TIMEOUT,
+ "persona-network-timeout",
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_uint64 ("persona_http_status",
+ response_code),
+ TALER_JSON_pack_ec (
+ TALER_EC_EXCHANGE_KYC_GENERIC_PROVIDER_TIMEOUT),
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_object_incref ("data",
+ (json_t *)
+ data))));
+ break;
+ case MHD_HTTP_TOO_MANY_REQUESTS:
+ /* This is a load issue */
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "PERSONA failed with response %u:\n",
+ (unsigned int) response_code);
+ json_dumpf (j,
+ stderr,
+ JSON_INDENT (2));
+ proof_reply_error (
+ ph,
+ ph->inquiry_id,
+ MHD_HTTP_SERVICE_UNAVAILABLE,
+ "persona-load-failure",
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_uint64 ("persona_http_status",
+ response_code),
+ TALER_JSON_pack_ec (
+ TALER_EC_EXCHANGE_KYC_GENERIC_PROVIDER_RATE_LIMIT_EXCEEDED),
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_object_incref ("data",
+ (json_t *)
+ data))));
+ break;
+ case MHD_HTTP_INTERNAL_SERVER_ERROR:
+ /* This is an issue with Persona */
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "PERSONA failed with response %u:\n",
+ (unsigned int) response_code);
+ json_dumpf (j,
+ stderr,
+ JSON_INDENT (2));
+ proof_reply_error (
+ ph,
+ ph->inquiry_id,
+ MHD_HTTP_BAD_GATEWAY,
+ "persona-provider-failure",
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_uint64 ("persona_http_status",
+ response_code),
+ TALER_JSON_pack_ec (
+ TALER_EC_EXCHANGE_KYC_PROOF_BACKEND_ERROR),
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_object_incref ("data",
+ (json_t *)
+ data))));
+ break;
+ default:
+ /* This is an issue with Persona */
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "PERSONA failed with response %u:\n",
+ (unsigned int) response_code);
+ json_dumpf (j,
+ stderr,
+ JSON_INDENT (2));
+ proof_reply_error (
+ ph,
+ ph->inquiry_id,
+ MHD_HTTP_BAD_GATEWAY,
+ "persona-invalid-response",
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_uint64 ("persona_http_status",
+ response_code),
+ TALER_JSON_pack_ec (
+ TALER_EC_EXCHANGE_KYC_GENERIC_PROVIDER_UNEXPECTED_REPLY),
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_object_incref ("data",
+ (json_t *)
+ data))));
+ break;
+ }
+ persona_proof_cancel (ph);
+}
+
+
+/**
+ * Check KYC status and return final result to human.
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @param pd provider configuration details
+ * @param connection MHD connection object (for HTTP headers)
+ * @param account_id which account to trigger process for
+ * @param process_row row in the legitimization processes table the legitimization is for
+ * @param provider_user_id user ID (or NULL) the proof is for
+ * @param inquiry_id legitimization ID the proof is for
+ * @param cb function to call with the result
+ * @param cb_cls closure for @a cb
+ * @return handle to cancel operation early
+ */
+static struct TALER_KYCLOGIC_ProofHandle *
+persona_proof (void *cls,
+ const struct TALER_KYCLOGIC_ProviderDetails *pd,
+ struct MHD_Connection *connection,
+ const struct TALER_PaytoHashP *account_id,
+ uint64_t process_row,
+ const char *provider_user_id,
+ const char *inquiry_id,
+ TALER_KYCLOGIC_ProofCallback cb,
+ void *cb_cls)
+{
+ struct PluginState *ps = cls;
+ struct TALER_KYCLOGIC_ProofHandle *ph;
+ CURL *eh;
+
+ eh = curl_easy_init ();
+ if (NULL == eh)
+ {
+ GNUNET_break (0);
+ return NULL;
+ }
+ ph = GNUNET_new (struct TALER_KYCLOGIC_ProofHandle);
+ ph->ps = ps;
+ ph->pd = pd;
+ ph->cb = cb;
+ ph->cb_cls = cb_cls;
+ ph->connection = connection;
+ ph->process_row = process_row;
+ ph->h_payto = *account_id;
+ /* Note: we do not expect this to be non-NULL */
+ if (NULL != provider_user_id)
+ ph->provider_user_id = GNUNET_strdup (provider_user_id);
+ if (NULL != inquiry_id)
+ ph->inquiry_id = GNUNET_strdup (inquiry_id);
+ GNUNET_asprintf (&ph->url,
+ "https://withpersona.com/api/v1/inquiries/%s",
+ inquiry_id);
+ GNUNET_break (CURLE_OK ==
+ curl_easy_setopt (eh,
+ CURLOPT_VERBOSE,
+ 0));
+ GNUNET_assert (CURLE_OK ==
+ curl_easy_setopt (eh,
+ CURLOPT_MAXREDIRS,
+ 1L));
+ GNUNET_break (CURLE_OK ==
+ curl_easy_setopt (eh,
+ CURLOPT_URL,
+ ph->url));
+ ph->job = GNUNET_CURL_job_add2 (ps->curl_ctx,
+ eh,
+ pd->slist,
+ &handle_proof_finished,
+ ph);
+ return ph;
+}
+
+
+/**
+ * Cancel KYC webhook execution.
+ *
+ * @param[in] wh handle of operation to cancel
+ */
+static void
+persona_webhook_cancel (struct TALER_KYCLOGIC_WebhookHandle *wh)
+{
+ if (NULL != wh->task)
+ {
+ GNUNET_SCHEDULER_cancel (wh->task);
+ wh->task = NULL;
+ }
+ if (NULL != wh->job)
+ {
+ GNUNET_CURL_job_cancel (wh->job);
+ wh->job = NULL;
+ }
+ if (NULL != wh->ec)
+ {
+ TALER_JSON_external_conversion_stop (wh->ec);
+ wh->ec = NULL;
+ }
+ GNUNET_free (wh->account_id);
+ GNUNET_free (wh->inquiry_id);
+ GNUNET_free (wh->url);
+ GNUNET_free (wh);
+}
+
+
+/**
+ * Call @a wh callback with the operation result.
+ *
+ * @param wh proof handle to generate reply for
+ * @param status status to return
+ * @param account_id account to return
+ * @param inquiry_id inquiry ID to supply
+ * @param attr KYC attribute data for the client
+ * @param http_status HTTP status to use
+ */
+static void
+webhook_generic_reply (struct TALER_KYCLOGIC_WebhookHandle *wh,
+ enum TALER_KYCLOGIC_KycStatus status,
+ const char *account_id,
+ const char *inquiry_id,
+ const json_t *attr,
+ unsigned int http_status)
+{
+ struct MHD_Response *resp;
+ struct GNUNET_TIME_Absolute expiration;
+
+ if (TALER_KYCLOGIC_STATUS_SUCCESS == status)
+ expiration = GNUNET_TIME_relative_to_absolute (wh->pd->validity);
+ else
+ expiration = GNUNET_TIME_UNIT_ZERO_ABS;
+ resp = MHD_create_response_from_buffer (0,
+ "",
+ MHD_RESPMEM_PERSISTENT);
+ TALER_MHD_add_global_headers (resp);
+ wh->cb (wh->cb_cls,
+ wh->process_row,
+ &wh->h_payto,
+ wh->pd->section,
+ account_id,
+ inquiry_id,
+ status,
+ expiration,
+ attr,
+ http_status,
+ resp);
+}
+
+
+/**
+ * Call @a wh callback with HTTP error response.
+ *
+ * @param wh proof handle to generate reply for
+ * @param inquiry_id inquiry ID to supply
+ * @param http_status HTTP status to use
+ */
+static void
+webhook_reply_error (struct TALER_KYCLOGIC_WebhookHandle *wh,
+ const char *inquiry_id,
+ unsigned int http_status)
+{
+ webhook_generic_reply (wh,
+ TALER_KYCLOGIC_STATUS_PROVIDER_FAILED,
+ NULL, /* user id */
+ inquiry_id,
+ NULL, /* attributes */
+ http_status);
+}
+
+
+/**
+ * Type of a callback that receives a JSON @a result.
+ *
+ * @param cls closure with a `struct TALER_KYCLOGIC_WebhookHandle *`
+ * @param status_type how did the process die
+ * @param code termination status code from the process
+ * @param attr some JSON result, NULL if we failed to get an JSON output
+ */
+static void
+webhook_post_conversion_cb (void *cls,
+ enum GNUNET_OS_ProcessStatusType status_type,
+ unsigned long code,
+ const json_t *attr)
+{
+ struct TALER_KYCLOGIC_WebhookHandle *wh = cls;
+
+ wh->ec = NULL;
+ webhook_generic_reply (wh,
+ TALER_KYCLOGIC_STATUS_SUCCESS,
+ wh->account_id,
+ wh->inquiry_id,
+ attr,
+ MHD_HTTP_OK);
+}
+
+
+/**
+ * Function called when we're done processing the
+ * HTTP "/api/v1/inquiries/{inquiry_id}" request.
+ *
+ * @param cls the `struct TALER_KYCLOGIC_WebhookHandle`
+ * @param response_code HTTP response code, 0 on error
+ * @param response parsed JSON result, NULL on error
+ */
+static void
+handle_webhook_finished (void *cls,
+ long response_code,
+ const void *response)
+{
+ struct TALER_KYCLOGIC_WebhookHandle *wh = cls;
+ const json_t *j = response;
+ const json_t *data = json_object_get (j,
+ "data");
+
+ wh->job = NULL;
+ switch (response_code)
+ {
+ case MHD_HTTP_OK:
+ {
+ const char *inquiry_id;
+ const char *account_id;
+ const char *type = NULL;
+ const json_t *attributes;
+ const json_t *relationships;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_string ("type",
+ &type),
+ GNUNET_JSON_spec_string ("id",
+ &inquiry_id),
+ GNUNET_JSON_spec_object_const ("attributes",
+ &attributes),
+ GNUNET_JSON_spec_object_const ("relationships",
+ &relationships),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if ( (NULL == data) ||
+ (GNUNET_OK !=
+ GNUNET_JSON_parse (data,
+ spec,
+ NULL, NULL)) ||
+ (0 != strcmp (type,
+ "inquiry")) )
+ {
+ GNUNET_break_op (0);
+ json_dumpf (j,
+ stderr,
+ JSON_INDENT (2));
+ webhook_reply_error (wh,
+ inquiry_id,
+ MHD_HTTP_BAD_GATEWAY);
+ break;
+ }
+
+ {
+ const char *status; /* "completed", what else? */
+ const char *reference_id; /* or legitimization number */
+ const char *expired_at = NULL; /* often 'null' format: "2022-08-18T10:14:26.000Z" */
+ struct GNUNET_JSON_Specification ispec[] = {
+ GNUNET_JSON_spec_string ("status",
+ &status),
+ GNUNET_JSON_spec_string ("reference-id",
+ &reference_id),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_string ("expired-at",
+ &expired_at),
+ NULL),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (attributes,
+ ispec,
+ NULL, NULL))
+ {
+ GNUNET_break_op (0);
+ json_dumpf (j,
+ stderr,
+ JSON_INDENT (2));
+ webhook_reply_error (wh,
+ inquiry_id,
+ MHD_HTTP_BAD_GATEWAY);
+ break;
+ }
+ {
+ unsigned long long idr;
+ char dummy;
+
+ if ( (1 != sscanf (reference_id,
+ "%llu%c",
+ &idr,
+ &dummy)) ||
+ (idr != wh->process_row) )
+ {
+ GNUNET_break_op (0);
+ webhook_reply_error (wh,
+ inquiry_id,
+ MHD_HTTP_BAD_GATEWAY);
+ break;
+ }
+ }
+
+ if (0 != strcmp (inquiry_id,
+ wh->inquiry_id))
+ {
+ GNUNET_break_op (0);
+ webhook_reply_error (wh,
+ inquiry_id,
+ MHD_HTTP_BAD_GATEWAY);
+ break;
+ }
+
+ account_id = json_string_value (
+ json_object_get (
+ json_object_get (
+ json_object_get (
+ relationships,
+ "account"),
+ "data"),
+ "id"));
+
+ if (0 != strcmp (status,
+ "completed"))
+ {
+ webhook_generic_reply (wh,
+ TALER_KYCLOGIC_STATUS_FAILED,
+ account_id,
+ inquiry_id,
+ NULL,
+ MHD_HTTP_OK);
+ break;
+ }
+
+ if (NULL == account_id)
+ {
+ GNUNET_break_op (0);
+ json_dumpf (data,
+ stderr,
+ JSON_INDENT (2));
+ webhook_reply_error (wh,
+ inquiry_id,
+ MHD_HTTP_BAD_GATEWAY);
+ break;
+ }
+ wh->account_id = GNUNET_strdup (account_id);
+ wh->ec = start_conversion (wh->pd,
+ j,
+ &webhook_post_conversion_cb,
+ wh);
+ if (NULL == wh->ec)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failed to start Persona conversion helper\n");
+ webhook_reply_error (wh,
+ inquiry_id,
+ MHD_HTTP_INTERNAL_SERVER_ERROR);
+ break;
+ }
+ }
+ return; /* continued in webhook_post_conversion_cb */
+ }
+ case MHD_HTTP_BAD_REQUEST:
+ case MHD_HTTP_NOT_FOUND:
+ case MHD_HTTP_CONFLICT:
+ case MHD_HTTP_UNPROCESSABLE_ENTITY:
+ /* These are errors with this code */
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "PERSONA failed with response %u:\n",
+ (unsigned int) response_code);
+ json_dumpf (j,
+ stderr,
+ JSON_INDENT (2));
+ webhook_reply_error (wh,
+ wh->inquiry_id,
+ MHD_HTTP_BAD_GATEWAY);
+ break;
+ case MHD_HTTP_UNAUTHORIZED:
+ /* These are failures of the exchange operator */
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Refused access with HTTP status code %u\n",
+ (unsigned int) response_code);
+ webhook_reply_error (wh,
+ wh->inquiry_id,
+ MHD_HTTP_INTERNAL_SERVER_ERROR);
+ break;
+ case MHD_HTTP_PAYMENT_REQUIRED:
+ /* These are failures of the exchange operator */
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Refused access with HTTP status code %u\n",
+ (unsigned int) response_code);
+
+ webhook_reply_error (wh,
+ wh->inquiry_id,
+ MHD_HTTP_INTERNAL_SERVER_ERROR);
+ break;
+ case MHD_HTTP_REQUEST_TIMEOUT:
+ /* These are networking issues */
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "PERSONA failed with response %u:\n",
+ (unsigned int) response_code);
+ json_dumpf (j,
+ stderr,
+ JSON_INDENT (2));
+ webhook_reply_error (wh,
+ wh->inquiry_id,
+ MHD_HTTP_GATEWAY_TIMEOUT);
+ break;
+ case MHD_HTTP_TOO_MANY_REQUESTS:
+ /* This is a load issue */
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "PERSONA failed with response %u:\n",
+ (unsigned int) response_code);
+ json_dumpf (j,
+ stderr,
+ JSON_INDENT (2));
+ webhook_reply_error (wh,
+ wh->inquiry_id,
+ MHD_HTTP_SERVICE_UNAVAILABLE);
+ break;
+ case MHD_HTTP_INTERNAL_SERVER_ERROR:
+ /* This is an issue with Persona */
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "PERSONA failed with response %u:\n",
+ (unsigned int) response_code);
+ json_dumpf (j,
+ stderr,
+ JSON_INDENT (2));
+ webhook_reply_error (wh,
+ wh->inquiry_id,
+ MHD_HTTP_BAD_GATEWAY);
+ break;
+ default:
+ /* This is an issue with Persona */
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "PERSONA failed with response %u:\n",
+ (unsigned int) response_code);
+ json_dumpf (j,
+ stderr,
+ JSON_INDENT (2));
+ webhook_reply_error (wh,
+ wh->inquiry_id,
+ MHD_HTTP_BAD_GATEWAY);
+ break;
+ }
+
+ persona_webhook_cancel (wh);
+}
+
+
+/**
+ * Asynchronously return a reply for the webhook.
+ *
+ * @param cls a `struct TALER_KYCLOGIC_WebhookHandle *`
+ */
+static void
+async_webhook_reply (void *cls)
+{
+ struct TALER_KYCLOGIC_WebhookHandle *wh = cls;
+
+ wh->task = NULL;
+ wh->cb (wh->cb_cls,
+ wh->process_row,
+ (0 == wh->process_row)
+ ? NULL
+ : &wh->h_payto,
+ wh->pd->section,
+ NULL,
+ wh->inquiry_id, /* provider legi ID */
+ TALER_KYCLOGIC_STATUS_PROVIDER_FAILED,
+ GNUNET_TIME_UNIT_ZERO_ABS, /* expiration */
+ NULL,
+ wh->response_code,
+ wh->resp);
+ persona_webhook_cancel (wh);
+}
+
+
+/**
+ * Function called with the provider details and
+ * associated plugin closures for matching logics.
+ *
+ * @param cls closure
+ * @param pd provider details of a matching logic
+ * @param plugin_cls closure of the plugin
+ * @return #GNUNET_OK to continue to iterate
+ */
+static enum GNUNET_GenericReturnValue
+locate_details_cb (
+ void *cls,
+ const struct TALER_KYCLOGIC_ProviderDetails *pd,
+ void *plugin_cls)
+{
+ struct TALER_KYCLOGIC_WebhookHandle *wh = cls;
+
+ /* This type-checks 'pd' */
+ GNUNET_assert (plugin_cls == wh->ps);
+ if (0 == strcmp (pd->template_id,
+ wh->template_id))
+ {
+ wh->pd = pd;
+ return GNUNET_NO;
+ }
+ return GNUNET_OK;
+}
+
+
+/**
+ * Check KYC status and return result for Webhook. We do NOT implement the
+ * authentication check proposed by the PERSONA 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 pd provider configuration details
+ * @param plc callback to lookup accounts with
+ * @param plc_cls closure for @a plc
+ * @param http_method HTTP method used for the webhook
+ * @param url_path rest of the URL after `/kyc-webhook/`
+ * @param connection MHD connection object (for HTTP headers)
+ * @param body HTTP request body
+ * @param cb function to call with the result
+ * @param cb_cls closure for @a cb
+ * @return handle to cancel operation early
+ */
+static struct TALER_KYCLOGIC_WebhookHandle *
+persona_webhook (void *cls,
+ const struct TALER_KYCLOGIC_ProviderDetails *pd,
+ TALER_KYCLOGIC_ProviderLookupCallback plc,
+ void *plc_cls,
+ const char *http_method,
+ const char *const url_path[],
+ struct MHD_Connection *connection,
+ const json_t *body,
+ TALER_KYCLOGIC_WebhookCallback cb,
+ void *cb_cls)
+{
+ struct PluginState *ps = cls;
+ struct TALER_KYCLOGIC_WebhookHandle *wh;
+ CURL *eh;
+ enum GNUNET_DB_QueryStatus qs;
+ const char *persona_inquiry_id;
+ const char *auth_header;
+
+ /* Persona webhooks are expected by logic, not by template */
+ GNUNET_break_op (NULL == pd);
+ wh = GNUNET_new (struct TALER_KYCLOGIC_WebhookHandle);
+ wh->cb = cb;
+ wh->cb_cls = cb_cls;
+ wh->ps = ps;
+ wh->connection = connection;
+ wh->pd = pd;
+ auth_header = MHD_lookup_connection_value (connection,
+ MHD_HEADER_KIND,
+ MHD_HTTP_HEADER_AUTHORIZATION);
+ if ( (NULL != ps->webhook_token) &&
+ ( (NULL == auth_header) ||
+ (0 != strcmp (ps->webhook_token,
+ auth_header)) ) )
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Invalid authorization header `%s' received for Persona webhook\n",
+ auth_header);
+ wh->resp = TALER_MHD_MAKE_JSON_PACK (
+ TALER_JSON_pack_ec (
+ TALER_EC_EXCHANGE_KYC_WEBHOOK_UNAUTHORIZED),
+ GNUNET_JSON_pack_string ("detail",
+ "unexpected 'Authorization' header"));
+ wh->response_code = MHD_HTTP_UNAUTHORIZED;
+ wh->task = GNUNET_SCHEDULER_add_now (&async_webhook_reply,
+ wh);
+ return wh;
+ }
+
+ wh->template_id
+ = json_string_value (
+ json_object_get (
+ json_object_get (
+ json_object_get (
+ json_object_get (
+ json_object_get (
+ json_object_get (
+ json_object_get (
+ json_object_get (
+ body,
+ "data"),
+ "attributes"),
+ "payload"),
+ "data"),
+ "relationships"),
+ "inquiry-template"),
+ "data"),
+ "id"));
+ if (NULL == wh->template_id)
+ {
+ GNUNET_break_op (0);
+ json_dumpf (body,
+ stderr,
+ JSON_INDENT (2));
+ wh->resp = TALER_MHD_MAKE_JSON_PACK (
+ TALER_JSON_pack_ec (
+ TALER_EC_EXCHANGE_KYC_GENERIC_PROVIDER_UNEXPECTED_REPLY),
+ GNUNET_JSON_pack_string ("detail",
+ "data-attributes-payload-data-id"),
+ GNUNET_JSON_pack_object_incref ("webhook_body",
+ (json_t *) body));
+ wh->response_code = MHD_HTTP_BAD_REQUEST;
+ wh->task = GNUNET_SCHEDULER_add_now (&async_webhook_reply,
+ wh);
+ return wh;
+ }
+ TALER_KYCLOGIC_kyc_get_details ("persona",
+ &locate_details_cb,
+ wh);
+ if (NULL == wh->pd)
+ {
+ GNUNET_break_op (0);
+ json_dumpf (body,
+ stderr,
+ JSON_INDENT (2));
+ wh->resp = TALER_MHD_MAKE_JSON_PACK (
+ TALER_JSON_pack_ec (
+ TALER_EC_EXCHANGE_KYC_GENERIC_LOGIC_UNKNOWN),
+ GNUNET_JSON_pack_string ("detail",
+ wh->template_id),
+ GNUNET_JSON_pack_object_incref ("webhook_body",
+ (json_t *) body));
+ wh->response_code = MHD_HTTP_BAD_REQUEST;
+ wh->task = GNUNET_SCHEDULER_add_now (&async_webhook_reply,
+ wh);
+ return wh;
+ }
+
+ persona_inquiry_id
+ = json_string_value (
+ json_object_get (
+ json_object_get (
+ json_object_get (
+ json_object_get (
+ json_object_get (
+ body,
+ "data"),
+ "attributes"),
+ "payload"),
+ "data"),
+ "id"));
+ if (NULL == persona_inquiry_id)
+ {
+ GNUNET_break_op (0);
+ json_dumpf (body,
+ stderr,
+ JSON_INDENT (2));
+ wh->resp = TALER_MHD_MAKE_JSON_PACK (
+ TALER_JSON_pack_ec (
+ TALER_EC_EXCHANGE_KYC_GENERIC_PROVIDER_UNEXPECTED_REPLY),
+ GNUNET_JSON_pack_string ("detail",
+ "data-attributes-payload-data-id"),
+ GNUNET_JSON_pack_object_incref ("webhook_body",
+ (json_t *) body));
+ wh->response_code = MHD_HTTP_BAD_REQUEST;
+ wh->task = GNUNET_SCHEDULER_add_now (&async_webhook_reply,
+ wh);
+ return wh;
+ }
+ qs = plc (plc_cls,
+ wh->pd->section,
+ persona_inquiry_id,
+ &wh->h_payto,
+ &wh->process_row);
+ if (qs < 0)
+ {
+ wh->resp = TALER_MHD_make_error (TALER_EC_GENERIC_DB_FETCH_FAILED,
+ "provider-legitimization-lookup");
+ wh->response_code = MHD_HTTP_INTERNAL_SERVER_ERROR;
+ wh->task = GNUNET_SCHEDULER_add_now (&async_webhook_reply,
+ wh);
+ return wh;
+ }
+ if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Received Persona kyc-webhook for unknown verification ID `%s'\n",
+ persona_inquiry_id);
+ wh->resp = TALER_MHD_make_error (
+ TALER_EC_EXCHANGE_KYC_PROOF_REQUEST_UNKNOWN,
+ persona_inquiry_id);
+ wh->response_code = MHD_HTTP_NOT_FOUND;
+ wh->task = GNUNET_SCHEDULER_add_now (&async_webhook_reply,
+ wh);
+ return wh;
+ }
+ wh->inquiry_id = GNUNET_strdup (persona_inquiry_id);
+
+ eh = curl_easy_init ();
+ if (NULL == eh)
+ {
+ GNUNET_break (0);
+ wh->resp = TALER_MHD_make_error (
+ TALER_EC_GENERIC_ALLOCATION_FAILURE,
+ NULL);
+ wh->response_code = MHD_HTTP_INTERNAL_SERVER_ERROR;
+ wh->task = GNUNET_SCHEDULER_add_now (&async_webhook_reply,
+ wh);
+ return wh;
+ }
+
+ GNUNET_asprintf (&wh->url,
+ "https://withpersona.com/api/v1/inquiries/%s",
+ persona_inquiry_id);
+ GNUNET_break (CURLE_OK ==
+ curl_easy_setopt (eh,
+ CURLOPT_VERBOSE,
+ 0));
+ GNUNET_assert (CURLE_OK ==
+ curl_easy_setopt (eh,
+ CURLOPT_MAXREDIRS,
+ 1L));
+ GNUNET_break (CURLE_OK ==
+ curl_easy_setopt (eh,
+ CURLOPT_URL,
+ wh->url));
+ wh->job = GNUNET_CURL_job_add2 (ps->curl_ctx,
+ eh,
+ wh->pd->slist,
+ &handle_webhook_finished,
+ wh);
+ return wh;
+}
+
+
+/**
+ * Initialize persona logic plugin
+ *
+ * @param cls a configuration instance
+ * @return NULL on error, otherwise a `struct TALER_KYCLOGIC_Plugin`
+ */
+void *
+libtaler_plugin_kyclogic_persona_init (void *cls)
+{
+ const struct GNUNET_CONFIGURATION_Handle *cfg = cls;
+ struct TALER_KYCLOGIC_Plugin *plugin;
+ struct PluginState *ps;
+
+ ps = GNUNET_new (struct PluginState);
+ ps->cfg = cfg;
+ if (GNUNET_OK !=
+ GNUNET_CONFIGURATION_get_value_string (cfg,
+ "exchange",
+ "BASE_URL",
+ &ps->exchange_base_url))
+ {
+ GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+ "exchange",
+ "BASE_URL");
+ GNUNET_free (ps);
+ return NULL;
+ }
+ if (GNUNET_OK !=
+ GNUNET_CONFIGURATION_get_value_string (ps->cfg,
+ "kyclogic-persona",
+ "WEBHOOK_AUTH_TOKEN",
+ &ps->webhook_token))
+ {
+ /* optional */
+ ps->webhook_token = NULL;
+ }
+
+ ps->curl_ctx
+ = GNUNET_CURL_init (&GNUNET_CURL_gnunet_scheduler_reschedule,
+ &ps->curl_rc);
+ if (NULL == ps->curl_ctx)
+ {
+ GNUNET_break (0);
+ GNUNET_free (ps->exchange_base_url);
+ GNUNET_free (ps);
+ return NULL;
+ }
+ ps->curl_rc = GNUNET_CURL_gnunet_rc_create (ps->curl_ctx);
+
+ plugin = GNUNET_new (struct TALER_KYCLOGIC_Plugin);
+ plugin->cls = ps;
+ plugin->load_configuration
+ = &persona_load_configuration;
+ plugin->unload_configuration
+ = &persona_unload_configuration;
+ plugin->initiate
+ = &persona_initiate;
+ plugin->initiate_cancel
+ = &persona_initiate_cancel;
+ plugin->proof
+ = &persona_proof;
+ plugin->proof_cancel
+ = &persona_proof_cancel;
+ plugin->webhook
+ = &persona_webhook;
+ plugin->webhook_cancel
+ = &persona_webhook_cancel;
+ return plugin;
+}
+
+
+/**
+ * Unload authorization plugin
+ *
+ * @param cls a `struct TALER_KYCLOGIC_Plugin`
+ * @return NULL (always)
+ */
+void *
+libtaler_plugin_kyclogic_persona_done (void *cls)
+{
+ struct TALER_KYCLOGIC_Plugin *plugin = cls;
+ struct PluginState *ps = plugin->cls;
+
+ if (NULL != ps->curl_ctx)
+ {
+ GNUNET_CURL_fini (ps->curl_ctx);
+ ps->curl_ctx = NULL;
+ }
+ if (NULL != ps->curl_rc)
+ {
+ GNUNET_CURL_gnunet_rc_destroy (ps->curl_rc);
+ ps->curl_rc = NULL;
+ }
+ GNUNET_free (ps->exchange_base_url);
+ GNUNET_free (ps->webhook_token);
+ GNUNET_free (ps);
+ GNUNET_free (plugin);
+ return NULL;
+}
+
+
+/* end of plugin_kyclogic_persona.c */
diff --git a/src/kyclogic/plugin_kyclogic_template.c b/src/kyclogic/plugin_kyclogic_template.c
index 4a21882b8..54f36e6f2 100644
--- a/src/kyclogic/plugin_kyclogic_template.c
+++ b/src/kyclogic/plugin_kyclogic_template.c
@@ -249,6 +249,7 @@ template_initiate (void *cls,
{
struct TALER_KYCLOGIC_InitiateHandle *ih;
+ (void) cls;
ih = GNUNET_new (struct TALER_KYCLOGIC_InitiateHandle);
ih->legitimization_uuid = legitimization_uuid;
ih->cb = cb;
@@ -278,10 +279,9 @@ template_proof_cancel (struct TALER_KYCLOGIC_ProofHandle *ph)
*
* @param cls the @e cls of this struct with the plugin-specific state
* @param pd provider configuration details
- * @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 process_row row in the legitimization processes 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
@@ -291,10 +291,9 @@ template_proof_cancel (struct TALER_KYCLOGIC_ProofHandle *ph)
static struct TALER_KYCLOGIC_ProofHandle *
template_proof (void *cls,
const struct TALER_KYCLOGIC_ProviderDetails *pd,
- const char *const url_path[],
struct MHD_Connection *connection,
const struct TALER_PaytoHashP *account_id,
- uint64_t legi_row,
+ uint64_t process_row,
const char *provider_user_id,
const char *provider_legitimization_id,
TALER_KYCLOGIC_ProofCallback cb,
@@ -303,6 +302,10 @@ template_proof (void *cls,
struct PluginState *ps = cls;
struct TALER_KYCLOGIC_ProofHandle *ph;
+ (void) account_id;
+ (void) process_row;
+ (void) provider_user_id;
+ (void) provider_legitimization_id;
ph = GNUNET_new (struct TALER_KYCLOGIC_ProofHandle);
ph->ps = ps;
ph->pd = pd;
@@ -358,6 +361,11 @@ template_webhook (void *cls,
struct PluginState *ps = cls;
struct TALER_KYCLOGIC_WebhookHandle *wh;
+ (void) plc;
+ (void) plc_cls;
+ (void) http_method;
+ (void) url_path;
+ (void) body;
wh = GNUNET_new (struct TALER_KYCLOGIC_WebhookHandle);
wh->cb = cb;
wh->cb_cls = cb_cls;
diff --git a/src/kyclogic/taler-exchange-kyc-kycaid-converter.sh b/src/kyclogic/taler-exchange-kyc-kycaid-converter.sh
new file mode 100755
index 000000000..68a1b6a0d
--- /dev/null
+++ b/src/kyclogic/taler-exchange-kyc-kycaid-converter.sh
@@ -0,0 +1,90 @@
+#!/bin/bash
+# This file is in the public domain.
+#
+# This code converts (some of) the JSON output from KYCAID into the GNU Taler
+# specific KYC attribute data (again in JSON format). We may need to download
+# and inline file data in the process, for authorization pass "-a" with the
+# respective bearer token.
+#
+
+# Die if anything goes wrong.
+set -eu
+
+# Parse command-line options
+while getopts ':a:' OPTION; do
+ case "$OPTION" in
+ a)
+ TOKEN="$OPTARG"
+ ;;
+ ?)
+ echo "Unrecognized command line option"
+ exit 1
+ ;;
+ esac
+done
+
+# First, extract everything from stdin.
+J=$(jq '{"type":.type,"email":.email,"phone":.phone,"first_name":.first_name,"name-middle":.middle_name,"last_name":.last_name,"dob":.dob,"residence_country":.residence_country,"gender":.gender,"pep":.pep,"addresses":.addresses,"documents":.documents,"company_name":.company_name,"business_activity_id":.business_activity_id,"registration_country":.registration_country,"documents":.documents,"decline_reasons":.decline_reasons}')
+
+# TODO:
+# log_failure (json_object_get (j, "decline_reasons"));
+
+TYPE=$(echo "$J" | jq -r '.type')
+
+N=0
+DOCS_RAW=""
+DOCS_JSON=""
+for ID in $(jq -r '.documents[]|select(.status=="valid")|.id')
+do
+ TYPE=$(jq -r ".documents[]|select(.id==\"$ID\")|.type")
+ EXPIRY=$(jq -r ".documents[]|select(.id==\"$ID\")|.expiry_date")
+ DOCUMENT_FILE=$(mktemp -t tmp.XXXXXXXXXX)
+ # Authorization: Token $TOKEN
+ DOCUMENT_URL="https://api.kycaid.com/documents/$ID"
+ if [ -z "${TOKEN:-}" ]
+ then
+ wget -q --output-document=- "$DOCUMENT_URL" \
+ | gnunet-base32 > ${DOCUMENT_FILE}
+ else
+ wget -q --output-document=- "$DOCUMENT_URL" \
+ --header "Authorization: Token $TOKEN" \
+ | gnunet-base32 > ${DOCUMENT_FILE}
+ fi
+ DOCS_RAW="$DOCS_RAW --rawfile photo$N \"${DOCUMENT_FILE}\""
+ if [ "$N" = 0 ]
+ then
+ DOCS_JSON="{\"type\":\"$TYPE\",\"image\":\$photo$N}"
+ else
+ DOCS_JSON="{\"type\":\"$TYPE\",\"image\":\$photo$N},$DOCS_JSON"
+ fi
+ N=$(expr $N + 1)
+done
+
+
+if [ "PERSON" = "${TYPE}" ]
+then
+
+ # Next, combine some fields into larger values.
+ FULLNAME=$(echo "$J" | jq -r '[.first_name,.middle_name,.last_name]|join(" ")')
+# STREET=$(echo $J | jq -r '[."street-1",."street-2"]|join(" ")')
+# CITY=$(echo $J | jq -r '[.postcode,.city,."address-subdivision,.cc"]|join(" ")')
+
+ # Combine into final result for individual.
+ echo "$J" \
+ | jq \
+ --arg full_name "${FULLNAME}" \
+ '{$full_name,"birthdate":.dob,"pep":.pep,"phone":.phone,"email":.email,"residences":.residence_country}' \
+ | jq \
+ 'del(..|select(.==null))'
+
+else
+ # Combine into final result for business.
+ echo "$J" \
+ | jq \
+ $DOCS_RAW \
+ "{\"company_name\":.company_name,\"phone\":.phone,\"email\":.email,\"registration_country\":.registration_country,\"documents\":[${DOCS_JSON}]}" \
+ | jq \
+ 'del(..|select(.==null))'
+fi
+
+exit 0
diff --git a/src/kyclogic/taler-exchange-kyc-oauth2-challenger.sh b/src/kyclogic/taler-exchange-kyc-oauth2-challenger.sh
new file mode 100755
index 000000000..729abc504
--- /dev/null
+++ b/src/kyclogic/taler-exchange-kyc-oauth2-challenger.sh
@@ -0,0 +1,27 @@
+#!/bin/bash
+# This file is in the public domain.
+#
+# This code converts (some of) the JSON output from
+# Challenger into the GNU Taler
+# specific KYC attribute data (again in JSON format).
+#
+
+# Die if anything goes wrong.
+set -eu
+
+# First, extract everything from stdin.
+J=$(jq '{"id":.id,"email":.address,"type":.address_type,"expires":.address_expiration}')
+
+ADDRESS_TYPE=$(echo "$J" | jq -r '.type')
+ROWID=$(echo "$J" | jq -r '.id')
+if [ "$ADDRESS_TYPE" != "email" ]
+then
+ return 1
+fi
+
+echo "$J" \
+ | jq \
+ --arg id "${ROWID}" \
+ '{$id,"email":.email,"expires":.expires}'
+
+exit 0
diff --git a/src/kyclogic/taler-exchange-kyc-oauth2-nda.sh b/src/kyclogic/taler-exchange-kyc-oauth2-nda.sh
new file mode 100755
index 000000000..5af785f19
--- /dev/null
+++ b/src/kyclogic/taler-exchange-kyc-oauth2-nda.sh
@@ -0,0 +1,30 @@
+#!/bin/bash
+# This file is in the public domain.
+#
+# This code converts (some of) the JSON output from NDA into the GNU Taler
+# specific KYC attribute data (again in JSON format).
+#
+
+# Die if anything goes wrong.
+set -eu
+
+# First, extract everything from stdin.
+J=$(jq '{"status":.status,"id":.data.id,"last":.data.last_name,"first":.data.first_name,"phone":.data.phone}')
+
+STATUS=$(echo "$J" | jq -r '.status')
+if [ "$STATUS" != "success" ]
+then
+ return 1
+fi
+
+# Next, combine some fields into larger values.
+FULLNAME=$(echo "$J" | jq -r '[.first_name,.last_name]|join(" ")')
+
+echo "$J" \
+ | jq \
+ --arg full_name "${FULLNAME}" \
+ '{$full_name,"phone":.phone,"id":.id}' \
+ | jq \
+ 'del(..|select(.==null))'
+
+exit 0
diff --git a/src/kyclogic/taler-exchange-kyc-oauth2-test-converter.sh b/src/kyclogic/taler-exchange-kyc-oauth2-test-converter.sh
new file mode 100755
index 000000000..76f9f16c4
--- /dev/null
+++ b/src/kyclogic/taler-exchange-kyc-oauth2-test-converter.sh
@@ -0,0 +1,31 @@
+#!/bin/bash
+# This file is in the public domain.
+#
+# This code converts (some of) the JSON output from
+# Challenger into the GNU Taler
+# specific KYC attribute data (again in JSON format).
+#
+
+# Die if anything goes wrong.
+set -eu
+
+
+# First, extract everything from stdin.
+J=$(jq '{"id":.data.id,"first":.data.first_name,"last":.data.last_name,"birthdate":.data.birthdate,"status":.status}')
+
+# Next, combine some fields into larger values.
+STATUS=$(echo "$J" | jq -r '.status')
+if [ "$STATUS" != "success" ]
+then
+ exit 1
+fi
+
+FULLNAME=$(echo "$J" | jq -r '[.first,.last]|join(" ")')
+
+echo $J \
+ | jq \
+ --arg full_name "${FULLNAME}" \
+ '{$full_name,"birthdate":.birthdate,"id":.id}' \
+ | jq \
+ 'del(..|select(.==null))'
+exit 0
diff --git a/src/kyclogic/taler-exchange-kyc-persona-converter.sh b/src/kyclogic/taler-exchange-kyc-persona-converter.sh
new file mode 100755
index 000000000..13142d0e5
--- /dev/null
+++ b/src/kyclogic/taler-exchange-kyc-persona-converter.sh
@@ -0,0 +1,57 @@
+#!/bin/bash
+# This file is in the public domain.
+#
+# This code converts (some of) the JSON output from Persona into the GNU Taler
+# specific KYC attribute data (again in JSON format). We may need to download
+# and inline file data in the process, for authorization pass "-a" with the
+# respective bearer token.
+#
+
+# Die if anything goes wrong.
+set -eu
+
+# Parse command-line options
+while getopts ':a:' OPTION; do
+ case "$OPTION" in
+ a)
+ TOKEN="$OPTARG"
+ ;;
+ ?)
+ echo "Unrecognized command line option"
+ exit 1
+ ;;
+ esac
+done
+
+
+# First, extract everything from stdin.
+J=$(jq '{"first":.data.attributes."name-first","middle":.data.attributes."name-middle","last":.data.attributes."name-last","cc":.data.attributes.fields."address-country-code".value,"birthdate":.data.attributes.birthdate,"city":.data.attributes."address-city","postcode":.data.attributes."address-postal-code","street-1":.data.attributes."address-street-1","street-2":.data.attributes."address-street-2","address-subdivision":.data.attributes."address-subdivision","identification-number":.data.attributes."identification-number","photo":.included[]|select(.type=="verification/government-id")|.attributes|select(.status=="passed")|."front-photo-url"}')
+
+
+# Next, combine some fields into larger values.
+FULLNAME=$(echo "$J" | jq -r '[.first,.middle,.last]|join(" ")')
+STREET=$(echo $J | jq -r '[."street-1",."street-2"]|join(" ")')
+CITY=$(echo $J | jq -r '[.postcode,.city,."address-subdivision,.cc"]|join(" ")')
+
+# Download and base32-encode the photo
+PHOTO_URL=$(echo "$J" | jq -r '.photo')
+PHOTO_FILE=$(mktemp -t tmp.XXXXXXXXXX)
+if [ -z "${TOKEN:-}" ]
+then
+ wget -q --output-document=- "$PHOTO_URL" | gnunet-base32 > ${PHOTO_FILE}
+else
+ wget -q --output-document=- --header "Authorization: Bearer $TOKEN" "$PHOTO_URL" | gnunet-base32 > ${PHOTO_FILE}
+fi
+
+# Combine into final result.
+echo "$J" \
+ | jq \
+ --arg full_name "${FULLNAME}" \
+ --arg street "${STREET}" \
+ --arg city "${CITY}" \
+ --rawfile photo "${PHOTO_FILE}" \
+ '{$full_name,$street,$city,"birthdate":.birthdate,"residences":.cc,"identification_number":."identification-number",$photo}' \
+ | jq \
+ 'del(..|select(.==null))'
+
+exit 0
diff --git a/src/kyclogic/taler-exchange-kyc-tester.c b/src/kyclogic/taler-exchange-kyc-tester.c
index 40aadba1a..c2efafd72 100644
--- a/src/kyclogic/taler-exchange-kyc-tester.c
+++ b/src/kyclogic/taler-exchange-kyc-tester.c
@@ -28,7 +28,7 @@
#include "taler_mhd_lib.h"
#include "taler_json_lib.h"
#include "taler_templating_lib.h"
-#include "taler_crypto_lib.h"
+#include "taler_util.h"
#include "taler_kyclogic_lib.h"
#include "taler_kyclogic_plugin.h"
#include <gnunet/gnunet_mhd_compat.h>
@@ -252,6 +252,12 @@ static char *cmd_provider_user_id;
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;
@@ -272,9 +278,14 @@ static int run_webservice;
static int global_ret;
/**
+ * -r command-line flag.
+ */
+static char *requirements;
+
+/**
* -i command-line flag.
*/
-static char *initiate_section;
+static char *ut_s = "individual";
/**
* Handle for ongoing initiation operation.
@@ -345,10 +356,10 @@ struct KycWebhookContext
struct MHD_Response *response;
/**
- * Logic the request is for. Name of the configuration
+ * Name of the configuration
* section defining the KYC logic.
*/
- char *logic;
+ const char *section_name;
/**
* HTTP response code to return.
@@ -390,7 +401,6 @@ kwh_resume (struct KycWebhookContext *kwh)
kwh_tail,
kwh);
MHD_resume_connection (kwh->rc->connection);
- TALER_MHD_daemon_trigger ();
}
@@ -419,30 +429,61 @@ kyc_webhook_cleanup (void)
* will be done by the plugin.
*
* @param cls closure
- * @param legi_row legitimization request the webhook was about
+ * @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 legi_row,
+ 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:
@@ -451,19 +492,26 @@ webhook_finished_cb (
"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 (Row #%llu) is %d\n",
+ "KYC status of %s/%s (process #%llu) is %d\n",
provider_user_id,
provider_legitimization_id,
- (unsigned long long) legi_row,
+ (unsigned long long) process_row,
status);
break;
}
kwh->response = response;
kwh->response_code = http_status;
kwh_resume (kwh);
+ TALER_MHD_daemon_trigger ();
}
@@ -487,7 +535,6 @@ clean_kwh (struct TEKT_RequestContext *rc)
MHD_destroy_response (kwh->response);
kwh->response = NULL;
}
- GNUNET_free (kwh->logic);
GNUNET_free (kwh);
}
@@ -511,6 +558,7 @@ kyc_provider_account_lookup (
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,
@@ -542,23 +590,24 @@ handler_kyc_webhook_generic (
if (NULL == kwh)
{ /* first time */
kwh = GNUNET_new (struct KycWebhookContext);
- kwh->logic = GNUNET_strdup (args[0]);
kwh->rc = rc;
rc->rh_ctx = kwh;
rc->rh_cleaner = &clean_kwh;
- if (GNUNET_OK !=
- TALER_KYCLOGIC_kyc_get_logic (kwh->logic,
- &kwh->plugin,
- &kwh->pd))
+ 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",
- kwh->logic);
+ args[0]);
return TALER_MHD_reply_with_error (rc->connection,
MHD_HTTP_NOT_FOUND,
TALER_EC_EXCHANGE_KYC_GENERIC_LOGIC_UNKNOWN,
- "$LOGIC");
+ args[0]);
}
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Calling KYC provider specific webhook\n");
@@ -600,7 +649,7 @@ handler_kyc_webhook_generic (
/* We resumed, but got no response? This should
not happen. */
- GNUNET_break (0);
+ GNUNET_assert (0);
return TALER_MHD_reply_with_error (rc->connection,
MHD_HTTP_INTERNAL_SERVER_ERROR,
TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
@@ -620,6 +669,8 @@ 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,
@@ -641,6 +692,8 @@ handler_kyc_webhook_post (
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,
@@ -659,6 +712,7 @@ handler_kyc_webhook_post (
* @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
*/
@@ -669,24 +723,39 @@ proof_cb (
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);
- MHD_resume_connection (rs->rc->connection);
- TALER_MHD_daemon_trigger ();
+ 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);
}
@@ -697,31 +766,44 @@ proof_cb (
*
* @param rc request context
* @param args remaining URL arguments;
- * args[0] is the 'h_payto',
- * args[1] should be the logic plugin name
+ * args[0] should be the logic plugin name
*/
static MHD_RESULT
handler_kyc_proof_get (
struct TEKT_RequestContext *rc,
- const char *const args[])
+ 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;
- if ( (NULL == args[0]) ||
- (NULL == args[1]) )
+ 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,
- "'/$H_PAYTO/$LOGIC' required after '/kyc-proof'");
+ "'/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 (args[0],
- strlen (args[0]),
+ GNUNET_STRINGS_string_to_data (h_paytos,
+ strlen (h_paytos),
&h_payto,
sizeof (h_payto)))
{
@@ -743,17 +825,18 @@ handler_kyc_proof_get (
}
if (GNUNET_OK !=
- TALER_KYCLOGIC_kyc_get_logic (args[1],
- &logic,
- &pd))
+ TALER_KYCLOGIC_lookup_logic (args[0],
+ &logic,
+ &pd,
+ &section_name))
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Could not initiate KYC with provider `%s' (configuration error?)\n",
- initiate_section);
+ args[0]);
return TALER_MHD_reply_with_error (rc->connection,
MHD_HTTP_NOT_FOUND,
TALER_EC_EXCHANGE_KYC_GENERIC_LOGIC_UNKNOWN,
- args[1]);
+ args[0]);
}
rs = GNUNET_new (struct ProofRequestState);
rs->rc = rc;
@@ -764,7 +847,6 @@ handler_kyc_proof_get (
rs);
rs->ph = logic->proof (logic->cls,
pd,
- &args[2],
rc->connection,
&h_payto,
kyc_row_id,
@@ -829,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;
}
@@ -906,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]) &&
@@ -998,8 +1082,7 @@ handle_mhd_request (void *cls,
.url = "kyc-proof",
.method = MHD_HTTP_METHOD_GET,
.handler.get = &handler_kyc_proof_get,
- .nargs = 128,
- .nargs_is_upper_bound = true
+ .nargs = 1
},
{
.url = "kyc-webhook",
@@ -1103,15 +1186,19 @@ handle_mhd_request (void *cls,
return TALER_MHD_reply_cors_preflight (connection);
}
GNUNET_assert (NULL != rh->method);
- if (0 == strcasecmp (method,
+ if (0 != strcasecmp (method,
rh->method))
- /* cache to avoid the loop next time */
- rc->rh = rh;
- /* run handler */
- return proceed_with_handler (rc,
- url + tok_size + 1,
- upload_data,
- upload_data_size);
+ {
+ 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)
@@ -1280,6 +1367,7 @@ initiate_cb (
const char *provider_legitimization_id,
const char *error_msg_hint)
{
+ (void) cls;
ih = NULL;
if (TALER_EC_NONE != ec)
{
@@ -1291,11 +1379,30 @@ initiate_cb (
GNUNET_SCHEDULER_shutdown ();
return;
}
- fprintf (stdout,
- "Visit `%s' to begin KYC process (-u: '%s', -U: '%s')\n",
- redirect_url,
- provider_user_id,
- provider_legitimization_id);
+ {
+ 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)
@@ -1330,7 +1437,8 @@ run (void *cls,
if (GNUNET_OK !=
TALER_TEMPLATING_init ("exchange"))
{
- GNUNET_break (0);
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Could not load templates. Installation broken.\n");
return;
}
if (print_h_payto)
@@ -1363,18 +1471,31 @@ run (void *cls,
return;
}
global_ret = EXIT_SUCCESS;
- if (NULL != initiate_section)
+ if (NULL != requirements)
{
struct TALER_KYCLOGIC_ProviderDetails *pd;
+ enum TALER_KYCLOGIC_KycUserType ut;
if (GNUNET_OK !=
- TALER_KYCLOGIC_kyc_get_logic (initiate_section,
- &ih_logic,
- &pd))
+ 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 with provider `%s' (configuration error?)\n",
- initiate_section);
+ "Could not initiate KYC for requirements `%s' (configuration error?)\n",
+ requirements);
global_ret = EXIT_NOTCONFIGURED;
GNUNET_SCHEDULER_shutdown ();
return;
@@ -1471,11 +1592,17 @@ main (int argc,
"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',
- "initiate",
- "SECTION_NAME",
- "initiate KYC check using provider configured in SECTION_NAME of the configuration",
- &initiate_section),
+ "identify",
+ "USERTYPE",
+ "self-identify as USERTYPE 'business' or 'individual' (defaults to 'individual')",
+ &requirements),
GNUNET_GETOPT_option_string (
'u',
"user",