summaryrefslogtreecommitdiff
path: root/src/kyclogic
diff options
context:
space:
mode:
Diffstat (limited to 'src/kyclogic')
-rw-r--r--src/kyclogic/Makefile.am16
-rw-r--r--src/kyclogic/kyclogic-kycaid.conf6
-rw-r--r--src/kyclogic/kyclogic-oauth2.conf14
-rw-r--r--src/kyclogic/kyclogic-persona.conf23
-rw-r--r--src/kyclogic/kyclogic_api.c230
-rw-r--r--src/kyclogic/plugin_kyclogic_kycaid.c366
-rw-r--r--src/kyclogic/plugin_kyclogic_oauth2.c997
-rw-r--r--src/kyclogic/plugin_kyclogic_persona.c734
-rw-r--r--src/kyclogic/plugin_kyclogic_template.c3
-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.c168
15 files changed, 2052 insertions, 740 deletions
diff --git a/src/kyclogic/Makefile.am b/src/kyclogic/Makefile.am
index a88c3c49b..0281553fc 100644
--- a/src/kyclogic/Makefile.am
+++ b/src/kyclogic/Makefile.am
@@ -14,8 +14,16 @@ pkgcfg_DATA = \
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 = \
$(pkgcfg_DATA) \
+ $(bin_SCRIPTS) \
sample.conf
lib_LTLIBRARIES = \
@@ -25,6 +33,7 @@ libtalerkyclogic_la_SOURCES = \
kyclogic_api.c
libtalerkyclogic_la_LIBADD = \
$(top_builddir)/src/util/libtalerutil.la \
+ -ljansson \
-lgnunetutil \
$(XLIB)
libtalerkyclogic_la_LDFLAGS = \
@@ -69,6 +78,8 @@ 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 = \
@@ -77,12 +88,14 @@ 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)
@@ -93,6 +106,7 @@ 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 \
@@ -100,6 +114,7 @@ libtaler_plugin_kyclogic_kycaid_la_LDFLAGS = \
-lgnunetcurl \
-lgnunetjson \
-lgnunetutil \
+ -lmicrohttpd \
-ljansson \
-lcurl \
$(XLIB)
@@ -121,6 +136,7 @@ libtaler_plugin_kyclogic_persona_la_LDFLAGS = \
-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
index de90238c7..2d52a9ee0 100644
--- a/src/kyclogic/kyclogic-persona.conf
+++ b/src/kyclogic/kyclogic-persona.conf
@@ -6,7 +6,10 @@
[kyclogic-persona]
-# Optional authorization token for the webhook
+# 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
@@ -18,16 +21,24 @@ USER_TYPE = INDIVIDUAL
PROVIDED_CHECKS = EXAMPLE_DO_NOT_USE
# How long is the KYC check valid?
-PERSONA_VALIDITY = forever
+KYC_PERSONA_VALIDITY = forever
# Which subdomain is used for our API?
-PERSONA_SUBDOMAIN = taler
+KYC_PERSONA_SUBDOMAIN = taler
# Authentication token to use.
-PERSONA_AUTH_TOKEN = persona_sandbox_42
+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.
-PERSONA_TEMPLATE_ID = itempl_Uj6Xxxxx
+KYC_PERSONA_TEMPLATE_ID = itempl_Uj6Xxxxx
# Where do we redirect to after KYC finished successfully.
-KYC_POST_URL = https://taler.net/
+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 3954ae4ce..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,6 +186,7 @@ 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 },
@@ -208,6 +215,8 @@ 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:
@@ -265,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.
*
@@ -331,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
@@ -585,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;
}
@@ -614,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;
@@ -629,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-")))
@@ -680,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)
{
@@ -699,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;
}
@@ -996,13 +1078,14 @@ 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;
@@ -1035,7 +1118,10 @@ 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++)
{
@@ -1062,7 +1148,10 @@ 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;
+ }
{
struct RemoveContext rc = {
.needed = needed,
@@ -1076,10 +1165,17 @@ 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;
+ }
{
struct RemoveContext rc = {
.needed = needed,
@@ -1093,10 +1189,17 @@ 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++)
{
@@ -1117,7 +1220,8 @@ TALER_KYCLOGIC_kyc_test_required (enum TALER_KYCLOGIC_KycTriggerEvent event,
GNUNET_free (tmp);
}
}
- return ret;
+ *required = ret;
+ return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
}
@@ -1144,26 +1248,31 @@ TALER_KYCLOGIC_kyc_get_details (
}
-bool
-TALER_KYCLOGIC_check_satisfied (const char *requirements,
+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)
+ void *ki_cls,
+ bool *satisfied)
{
struct TALER_KYCLOGIC_KycCheck *needed[num_kyc_checks];
unsigned int needed_cnt = 0;
if (NULL == requirements)
- return true;
{
- char *req = GNUNET_strdup (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;
}
{
@@ -1182,7 +1291,12 @@ TALER_KYCLOGIC_check_satisfied (const char *requirements,
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);
+ *satisfied = false;
+ return qs;
+ }
if (0 != needed_cnt)
{
json_decref (rc.kyc_details);
@@ -1193,7 +1307,34 @@ TALER_KYCLOGIC_check_satisfied (const char *requirements,
*kyc_details = rc.kyc_details;
}
}
- return (0 == needed_cnt);
+ *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;
}
@@ -1339,17 +1480,32 @@ TALER_KYCLOGIC_kyc_iterate_thresholds (
}
-enum TALER_ErrorCode
-TALER_KYCLOGIC_user_to_attributes (const char *provider_section,
- const char *provider_user_id,
- const char *legitimization_id,
- struct GNUNET_TIME_Timestamp *attr_expiration,
- json_t **attrs)
+void
+TALER_KYCLOGIC_lookup_checks (const char *section_name,
+ unsigned int *num_checks,
+ char ***provided_checks)
{
- GNUNET_break (0); // FIXME: not yet implemented!!!
- *attrs = json_object ();
- *attr_expiration = GNUNET_TIME_UNIT_ZERO_TS;
- return TALER_EC_NONE;
+ *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;
+ }
}
diff --git a/src/kyclogic/plugin_kyclogic_kycaid.c b/src/kyclogic/plugin_kyclogic_kycaid.c
index c08948f7b..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,6 +89,12 @@ struct TALER_KYCLOGIC_ProviderDetails
char *form_id;
/**
+ * Helper binary to convert attributes returned by
+ * KYCAID into our internal format.
+ */
+ char *conversion_helper;
+
+ /**
* Validity time for a successful KYC process.
*/
struct GNUNET_TIME_Relative validity;
@@ -214,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;
@@ -224,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;
@@ -260,6 +280,11 @@ struct TALER_KYCLOGIC_WebhookHandle
uint64_t process_row;
/**
+ * HTTP response code we got from KYCAID.
+ */
+ unsigned int kycaid_response_code;
+
+ /**
* HTTP response code to return asynchronously.
*/
unsigned int response_code;
@@ -275,6 +300,7 @@ 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);
@@ -335,6 +361,18 @@ kycaid_load_configuration (void *cls,
kycaid_unload_configuration (pd);
return NULL;
}
+ if (GNUNET_OK !=
+ GNUNET_CONFIGURATION_get_value_string (ps->cfg,
+ provider_section_name,
+ "KYC_KYCAID_CONVERTER_HELPER",
+ &pd->conversion_helper))
+ {
+ GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+ provider_section_name,
+ "KYC_KYCAID_CONVERTER_HELPER");
+ kycaid_unload_configuration (pd);
+ return NULL;
+ }
{
char *auth;
@@ -392,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 ()
};
@@ -418,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,
@@ -586,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,
@@ -624,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);
}
@@ -643,7 +704,6 @@ 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 process_row row in the legitimization processes table the legitimization is for
@@ -656,7 +716,6 @@ 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 process_row,
@@ -693,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);
@@ -711,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;
@@ -749,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
@@ -766,91 +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))
- {
- 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->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 */
- MHD_HTTP_BAD_GATEWAY,
- resp);
- break;
- }
- if (! verified)
- {
- log_failure (verifications);
- }
- resp = MHD_create_response_from_buffer (0,
- "",
- MHD_RESPMEM_PERSISTENT);
- if (verified)
+ const char *profile_status;
+
+ profile_status = json_string_value (
+ json_object_get (
+ j,
+ "profile_status"));
+ if (0 != strcasecmp ("valid",
+ profile_status))
{
- 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->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->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:
@@ -873,6 +1023,7 @@ handle_webhook_finished (void *cls,
wh->verification_id,
TALER_KYCLOGIC_STATUS_PROVIDER_FAILED,
GNUNET_TIME_UNIT_ZERO_ABS, /* expiration */
+ NULL,
MHD_HTTP_INTERNAL_SERVER_ERROR,
resp);
break;
@@ -894,6 +1045,7 @@ handle_webhook_finished (void *cls,
wh->verification_id,
TALER_KYCLOGIC_STATUS_PROVIDER_FAILED,
GNUNET_TIME_UNIT_ZERO_ABS, /* expiration */
+ NULL,
MHD_HTTP_NETWORK_AUTHENTICATION_REQUIRED,
resp);
break;
@@ -911,6 +1063,7 @@ handle_webhook_finished (void *cls,
wh->verification_id,
TALER_KYCLOGIC_STATUS_PROVIDER_FAILED,
GNUNET_TIME_UNIT_ZERO_ABS, /* expiration */
+ NULL,
MHD_HTTP_GATEWAY_TIMEOUT,
resp);
break;
@@ -934,6 +1087,7 @@ handle_webhook_finished (void *cls,
wh->verification_id,
TALER_KYCLOGIC_STATUS_PROVIDER_FAILED,
GNUNET_TIME_UNIT_ZERO_ABS, /* expiration */
+ NULL,
MHD_HTTP_BAD_GATEWAY,
resp);
break;
@@ -951,6 +1105,7 @@ handle_webhook_finished (void *cls,
wh->verification_id,
TALER_KYCLOGIC_STATUS_PROVIDER_FAILED,
GNUNET_TIME_UNIT_ZERO_ABS, /* expiration */
+ NULL,
MHD_HTTP_SERVICE_UNAVAILABLE,
resp);
break;
@@ -968,6 +1123,7 @@ handle_webhook_finished (void *cls,
wh->verification_id,
TALER_KYCLOGIC_STATUS_PROVIDER_FAILED,
GNUNET_TIME_UNIT_ZERO_ABS, /* expiration */
+ NULL,
MHD_HTTP_BAD_GATEWAY,
resp);
break;
@@ -991,6 +1147,7 @@ handle_webhook_finished (void *cls,
wh->verification_id,
TALER_KYCLOGIC_STATUS_PROVIDER_FAILED,
GNUNET_TIME_UNIT_ZERO_ABS, /* expiration */
+ NULL,
MHD_HTTP_BAD_GATEWAY,
resp);
break;
@@ -1009,6 +1166,7 @@ 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)
@@ -1019,6 +1177,7 @@ async_webhook_reply (void *cls)
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);
@@ -1061,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),
@@ -1075,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;
@@ -1091,7 +1260,16 @@ 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);
@@ -1136,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;
}
@@ -1180,13 +1362,12 @@ 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;
}
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,
@@ -1204,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 91c936bbc..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
@@ -546,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.
*
@@ -567,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);
}
@@ -585,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;
@@ -604,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[] = {
@@ -699,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);
}
@@ -755,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",
@@ -797,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;
@@ -821,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;
}
@@ -855,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,
@@ -918,7 +1386,6 @@ 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 process_row row in the legitimization processes table the legitimization is for
@@ -931,7 +1398,6 @@ 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 process_row,
@@ -944,7 +1410,6 @@ oauth2_proof (void *cls,
struct TALER_KYCLOGIC_ProofHandle *ph;
const char *code;
- (void) url_path;
GNUNET_break (NULL == provider_user_id);
ph = GNUNET_new (struct TALER_KYCLOGIC_ProofHandle);
GNUNET_snprintf (ph->provider_legitimization_id,
@@ -959,6 +1424,7 @@ oauth2_proof (void *cls,
GNUNET_free (ph);
return NULL;
}
+
ph->pd = pd;
ph->connection = connection;
ph->h_payto = *account_id;
@@ -969,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);
@@ -1034,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 ==
@@ -1068,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.
*
@@ -1119,6 +1623,7 @@ wh_return_not_found (void *cls)
NULL,
TALER_KYCLOGIC_STATUS_KEEP,
GNUNET_TIME_UNIT_ZERO_ABS,
+ NULL,
MHD_HTTP_NOT_FOUND,
response);
GNUNET_free (wh);
diff --git a/src/kyclogic/plugin_kyclogic_persona.c b/src/kyclogic/plugin_kyclogic_persona.c
index 651388c99..c68b7f881 100644
--- a/src/kyclogic/plugin_kyclogic_persona.c
+++ b/src/kyclogic/plugin_kyclogic_persona.c
@@ -1,6 +1,6 @@
/*
This file is part of GNU 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
@@ -19,6 +19,7 @@
* @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"
@@ -62,9 +63,10 @@ struct PluginState
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.
- */
+ * Authorization token to use when receiving webhooks from the Persona
+ * service. Optional. Note that webhooks are *global* and not per
+ * template.
+ */
char *webhook_token;
@@ -110,6 +112,12 @@ struct TALER_KYCLOGIC_ProviderDetails
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;
@@ -229,6 +237,12 @@ struct TALER_KYCLOGIC_ProofHandle
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;
@@ -245,6 +259,11 @@ struct TALER_KYCLOGIC_ProofHandle
char *provider_user_id;
/**
+ * Account ID from the service.
+ */
+ char *account_id;
+
+ /**
* Inquiry ID at the provider.
*/
char *inquiry_id;
@@ -293,6 +312,11 @@ struct TALER_KYCLOGIC_WebhookHandle
char *inquiry_id;
/**
+ * Account ID from the service.
+ */
+ char *account_id;
+
+ /**
* URL of the cURL request.
*/
char *url;
@@ -314,6 +338,12 @@ struct TALER_KYCLOGIC_WebhookHandle
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;
@@ -342,6 +372,7 @@ persona_unload_configuration (struct TALER_KYCLOGIC_ProviderDetails *pd)
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);
@@ -369,31 +400,31 @@ persona_load_configuration (void *cls,
if (GNUNET_OK !=
GNUNET_CONFIGURATION_get_value_time (ps->cfg,
provider_section_name,
- "PERSONA_VALIDITY",
+ "KYC_PERSONA_VALIDITY",
&pd->validity))
{
GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
provider_section_name,
- "PERSONA_VALIDITY");
+ "KYC_PERSONA_VALIDITY");
persona_unload_configuration (pd);
return NULL;
}
if (GNUNET_OK !=
GNUNET_CONFIGURATION_get_value_string (ps->cfg,
provider_section_name,
- "PERSONA_AUTH_TOKEN",
+ "KYC_PERSONA_AUTH_TOKEN",
&pd->auth_token))
{
GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
provider_section_name,
- "PERSONA_AUTH_TOKEN");
+ "KYC_PERSONA_AUTH_TOKEN");
persona_unload_configuration (pd);
return NULL;
}
if (GNUNET_OK !=
GNUNET_CONFIGURATION_get_value_string (ps->cfg,
provider_section_name,
- "SALT",
+ "KYC_PERSONA_SALT",
&pd->salt))
{
uint32_t salt[8];
@@ -407,36 +438,48 @@ persona_load_configuration (void *cls,
if (GNUNET_OK !=
GNUNET_CONFIGURATION_get_value_string (ps->cfg,
provider_section_name,
- "PERSONA_SUBDOMAIN",
+ "KYC_PERSONA_SUBDOMAIN",
&pd->subdomain))
{
GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
provider_section_name,
- "PERSONA_SUBDOMAIN");
+ "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_POST_URL",
+ "KYC_PERSONA_POST_URL",
&pd->post_kyc_redirect_url))
{
GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
provider_section_name,
- "KYC_POST_URL");
+ "KYC_PERSONA_POST_URL");
persona_unload_configuration (pd);
return NULL;
}
if (GNUNET_OK !=
GNUNET_CONFIGURATION_get_value_string (ps->cfg,
provider_section_name,
- "PERSONA_TEMPLATE_ID",
+ "KYC_PERSONA_TEMPLATE_ID",
&pd->template_id))
{
GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
provider_section_name,
- "PERSONA_TEMPLATE_ID");
+ "KYC_PERSONA_TEMPLATE_ID");
persona_unload_configuration (pd);
return NULL;
}
@@ -747,13 +790,14 @@ persona_initiate (void *cls,
(unsigned long long) ih->legitimization_uuid);
payto_s = GNUNET_STRINGS_data_to_string_alloc (&ih->h_payto,
sizeof (ih->h_payto));
- /* NOTE: check here that exchange_base_url ends
- with a '/'? */
+ GNUNET_break ('/' ==
+ pd->ps->exchange_base_url[strlen (
+ pd->ps->exchange_base_url) - 1]);
GNUNET_asprintf (&proof_url,
- "%skyc-proof/%s/%s",
+ "%skyc-proof/%s?state=%s",
pd->ps->exchange_base_url,
- payto_s,
- pd->section);
+ pd->section,
+ payto_s);
body = GNUNET_JSON_PACK (
GNUNET_JSON_pack_object_steal (
"data",
@@ -835,8 +879,14 @@ persona_proof_cancel (struct TALER_KYCLOGIC_ProofHandle *ph)
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);
}
@@ -865,12 +915,9 @@ proof_generic_reply (struct TALER_KYCLOGIC_ProofHandle *ph,
{
struct MHD_Response *resp;
enum GNUNET_GenericReturnValue ret;
- struct GNUNET_TIME_Absolute expiration;
- if (TALER_KYCLOGIC_STATUS_SUCCESS == status)
- expiration = GNUNET_TIME_relative_to_absolute (ph->pd->validity);
- else
- expiration = GNUNET_TIME_UNIT_ZERO_ABS;
+ /* This API is not usable for successful replies */
+ GNUNET_assert (TALER_KYCLOGIC_STATUS_SUCCESS != status);
ret = TALER_TEMPLATING_build (ph->connection,
&http_status,
template,
@@ -888,7 +935,8 @@ proof_generic_reply (struct TALER_KYCLOGIC_ProofHandle *ph,
status,
account_id,
inquiry_id,
- expiration,
+ GNUNET_TIME_UNIT_ZERO_ABS,
+ NULL,
http_status,
resp);
}
@@ -922,8 +970,132 @@ proof_reply_error (struct TALER_KYCLOGIC_ProofHandle *ph,
/**
+ * 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/verifications/{verification-id}" request.
+ * HTTP "/api/v1/inquiries/{inquiry-id}" request.
*
* @param cls the `struct TALER_KYCLOGIC_InitiateHandle`
* @param response_code HTTP response code, 0 on error
@@ -947,14 +1119,17 @@ handle_proof_finished (void *cls,
const char *inquiry_id;
const char *account_id;
const char *type = NULL;
- json_t *attributes;
+ 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_json ("attributes",
- &attributes),
+ GNUNET_JSON_spec_object_const ("attributes",
+ &attributes),
+ GNUNET_JSON_spec_object_const ("relationships",
+ &relationships),
GNUNET_JSON_spec_end ()
};
@@ -967,25 +1142,11 @@ handle_proof_finished (void *cls,
"inquiry")) )
{
GNUNET_break_op (0);
- json_dumpf (j,
- stderr,
- JSON_INDENT (2));
- proof_reply_error (ph,
- inquiry_id,
- MHD_HTTP_BAD_GATEWAY,
- "persona-logic-failure",
- 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",
- "data"),
- GNUNET_JSON_pack_allow_null (
- GNUNET_JSON_pack_object_incref ("data",
- (json_t *) data))));
+ return_invalid_response (ph,
+ response_code,
+ inquiry_id,
+ "data",
+ data);
break;
}
@@ -996,10 +1157,10 @@ handle_proof_finished (void *cls,
struct GNUNET_JSON_Specification ispec[] = {
GNUNET_JSON_spec_string ("status",
&status),
- GNUNET_JSON_spec_string ("reference_id",
+ GNUNET_JSON_spec_string ("reference-id",
&reference_id),
GNUNET_JSON_spec_mark_optional (
- GNUNET_JSON_spec_string ("expired_at",
+ GNUNET_JSON_spec_string ("expired-at",
&expired_at),
NULL),
GNUNET_JSON_spec_end ()
@@ -1011,27 +1172,11 @@ handle_proof_finished (void *cls,
NULL, NULL))
{
GNUNET_break_op (0);
- json_dumpf (j,
- stderr,
- JSON_INDENT (2));
- 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",
- "data-attributes"),
- GNUNET_JSON_pack_allow_null (
- GNUNET_JSON_pack_object_incref ("data",
- (json_t *) data))));
- GNUNET_JSON_parse_free (ispec);
- GNUNET_JSON_parse_free (spec);
+ return_invalid_response (ph,
+ response_code,
+ inquiry_id,
+ "data-attributes",
+ data);
break;
}
{
@@ -1045,25 +1190,11 @@ handle_proof_finished (void *cls,
(idr != ph->process_row) )
{
GNUNET_break_op (0);
- 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",
- "data-attributes-reference_id"),
- GNUNET_JSON_pack_allow_null (
- GNUNET_JSON_pack_object_incref ("data",
- (json_t *)
- data))));
- GNUNET_JSON_parse_free (ispec);
- GNUNET_JSON_parse_free (spec);
+ return_invalid_response (ph,
+ response_code,
+ inquiry_id,
+ "data-attributes-reference_id",
+ data);
break;
}
}
@@ -1072,25 +1203,11 @@ handle_proof_finished (void *cls,
ph->inquiry_id))
{
GNUNET_break_op (0);
- 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",
- "data-id"),
- GNUNET_JSON_pack_allow_null (
- GNUNET_JSON_pack_object_incref ("data",
- (json_t *)
- data))));
- GNUNET_JSON_parse_free (ispec);
- GNUNET_JSON_parse_free (spec);
+ return_invalid_response (ph,
+ response_code,
+ inquiry_id,
+ "data-id",
+ data);
break;
}
@@ -1098,9 +1215,7 @@ handle_proof_finished (void *cls,
json_object_get (
json_object_get (
json_object_get (
- json_object_get (
- data,
- "relationships"),
+ relationships,
"account"),
"data"),
"id"));
@@ -1108,77 +1223,56 @@ handle_proof_finished (void *cls,
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))));
- GNUNET_JSON_parse_free (ispec);
- GNUNET_JSON_parse_free (spec);
+ 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);
- json_dumpf (data,
- stderr,
- JSON_INDENT (2));
- 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",
- "data-relationships-account-data-id"),
- GNUNET_JSON_pack_allow_null (
- GNUNET_JSON_pack_object_incref ("data",
- (json_t *)
- data))));
+ 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)
{
- struct MHD_Response *resp;
- struct GNUNET_TIME_Absolute expiration;
-
- 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,
- account_id,
- inquiry_id,
- expiration,
- MHD_HTTP_SEE_OTHER,
- resp);
+ 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;
}
- GNUNET_JSON_parse_free (ispec);
}
- GNUNET_JSON_parse_free (spec);
- break;
+ return; /* continued in proof_post_conversion_cb */
}
case MHD_HTTP_BAD_REQUEST:
case MHD_HTTP_NOT_FOUND:
@@ -1191,59 +1285,61 @@ handle_proof_finished (void *cls,
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))));
+ 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_INTERNAL_SERVER_ERROR,
- "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))));
+ 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_INTERNAL_SERVER_ERROR,
- "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))));
+ 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 */
@@ -1253,19 +1349,20 @@ handle_proof_finished (void *cls,
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))));
+ 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 */
@@ -1275,19 +1372,20 @@ handle_proof_finished (void *cls,
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))));
+ 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 */
@@ -1297,19 +1395,20 @@ handle_proof_finished (void *cls,
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))));
+ 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 */
@@ -1319,21 +1418,20 @@ handle_proof_finished (void *cls,
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_string ("detail",
- "data-relationships-account-data-id"),
- GNUNET_JSON_pack_allow_null (
- GNUNET_JSON_pack_object_incref ("data",
- (json_t *)
- data))));
+ 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);
@@ -1345,7 +1443,6 @@ handle_proof_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 process_row row in the legitimization processes table the legitimization is for
@@ -1358,7 +1455,6 @@ handle_proof_finished (void *cls,
static struct TALER_KYCLOGIC_ProofHandle *
persona_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 process_row,
@@ -1432,6 +1528,12 @@ persona_webhook_cancel (struct TALER_KYCLOGIC_WebhookHandle *wh)
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);
@@ -1445,6 +1547,7 @@ persona_webhook_cancel (struct TALER_KYCLOGIC_WebhookHandle *wh)
* @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
@@ -1452,6 +1555,7 @@ 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;
@@ -1468,11 +1572,12 @@ webhook_generic_reply (struct TALER_KYCLOGIC_WebhookHandle *wh,
wh->cb (wh->cb_cls,
wh->process_row,
&wh->h_payto,
- account_id,
wh->pd->section,
+ account_id,
inquiry_id,
status,
expiration,
+ attr,
http_status,
resp);
}
@@ -1494,13 +1599,40 @@ webhook_reply_error (struct TALER_KYCLOGIC_WebhookHandle *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 "/verifications/{verification_id}" request.
+ * HTTP "/api/v1/inquiries/{inquiry_id}" request.
*
* @param cls the `struct TALER_KYCLOGIC_WebhookHandle`
* @param response_code HTTP response code, 0 on error
@@ -1524,14 +1656,17 @@ handle_webhook_finished (void *cls,
const char *inquiry_id;
const char *account_id;
const char *type = NULL;
- json_t *attributes;
+ 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_json ("attributes",
- &attributes),
+ GNUNET_JSON_spec_object_const ("attributes",
+ &attributes),
+ GNUNET_JSON_spec_object_const ("relationships",
+ &relationships),
GNUNET_JSON_spec_end ()
};
@@ -1560,10 +1695,10 @@ handle_webhook_finished (void *cls,
struct GNUNET_JSON_Specification ispec[] = {
GNUNET_JSON_spec_string ("status",
&status),
- GNUNET_JSON_spec_string ("reference_id",
+ GNUNET_JSON_spec_string ("reference-id",
&reference_id),
GNUNET_JSON_spec_mark_optional (
- GNUNET_JSON_spec_string ("expired_at",
+ GNUNET_JSON_spec_string ("expired-at",
&expired_at),
NULL),
GNUNET_JSON_spec_end ()
@@ -1581,8 +1716,6 @@ handle_webhook_finished (void *cls,
webhook_reply_error (wh,
inquiry_id,
MHD_HTTP_BAD_GATEWAY);
- GNUNET_JSON_parse_free (ispec);
- GNUNET_JSON_parse_free (spec);
break;
}
{
@@ -1599,8 +1732,6 @@ handle_webhook_finished (void *cls,
webhook_reply_error (wh,
inquiry_id,
MHD_HTTP_BAD_GATEWAY);
- GNUNET_JSON_parse_free (ispec);
- GNUNET_JSON_parse_free (spec);
break;
}
}
@@ -1612,8 +1743,6 @@ handle_webhook_finished (void *cls,
webhook_reply_error (wh,
inquiry_id,
MHD_HTTP_BAD_GATEWAY);
- GNUNET_JSON_parse_free (ispec);
- GNUNET_JSON_parse_free (spec);
break;
}
@@ -1621,9 +1750,7 @@ handle_webhook_finished (void *cls,
json_object_get (
json_object_get (
json_object_get (
- json_object_get (
- data,
- "relationships"),
+ relationships,
"account"),
"data"),
"id"));
@@ -1635,9 +1762,8 @@ handle_webhook_finished (void *cls,
TALER_KYCLOGIC_STATUS_FAILED,
account_id,
inquiry_id,
+ NULL,
MHD_HTTP_OK);
- GNUNET_JSON_parse_free (ispec);
- GNUNET_JSON_parse_free (spec);
break;
}
@@ -1652,16 +1778,22 @@ handle_webhook_finished (void *cls,
MHD_HTTP_BAD_GATEWAY);
break;
}
-
- webhook_generic_reply (wh,
- TALER_KYCLOGIC_STATUS_SUCCESS,
- account_id,
+ 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_OK);
- GNUNET_JSON_parse_free (ispec);
+ MHD_HTTP_INTERNAL_SERVER_ERROR);
+ break;
+ }
}
- GNUNET_JSON_parse_free (spec);
- break;
+ return; /* continued in webhook_post_conversion_cb */
}
case MHD_HTTP_BAD_REQUEST:
case MHD_HTTP_NOT_FOUND:
@@ -1772,6 +1904,7 @@ async_webhook_reply (void *cls)
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);
@@ -1853,13 +1986,13 @@ persona_webhook (void *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) &&
- (0 != strcmp (ps->webhook_token,
- auth_header)) )
+ ( (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",
@@ -1891,7 +2024,7 @@ persona_webhook (void *cls,
"payload"),
"data"),
"relationships"),
- "inquiry_template"),
+ "inquiry-template"),
"data"),
"id"));
if (NULL == wh->template_id)
@@ -1934,7 +2067,6 @@ persona_webhook (void *cls,
return wh;
}
-
persona_inquiry_id
= json_string_value (
json_object_get (
diff --git a/src/kyclogic/plugin_kyclogic_template.c b/src/kyclogic/plugin_kyclogic_template.c
index b4531117e..54f36e6f2 100644
--- a/src/kyclogic/plugin_kyclogic_template.c
+++ b/src/kyclogic/plugin_kyclogic_template.c
@@ -279,7 +279,6 @@ 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 process_row row in the legitimization processes table the legitimization is for
@@ -292,7 +291,6 @@ 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 process_row,
@@ -304,7 +302,6 @@ template_proof (void *cls,
struct PluginState *ps = cls;
struct TALER_KYCLOGIC_ProofHandle *ph;
- (void) url_path;
(void) account_id;
(void) process_row;
(void) provider_user_id;
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 2aed8d961..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>
@@ -436,6 +436,7 @@ kyc_webhook_cleanup (void)
* @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
*/
@@ -449,6 +450,7 @@ webhook_finished_cb (
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)
{
@@ -457,12 +459,31 @@ webhook_finished_cb (
(void) expiration;
(void) provider_section;
kwh->wh = NULL;
- GNUNET_break (0 == GNUNET_memcmp (account_id,
- &cmd_line_h_payto));
- GNUNET_break (0 == strcmp (provider_user_id,
- cmd_provider_user_id));
- GNUNET_break (0 == strcmp (provider_legitimization_id,
- cmd_provider_legitimization_id));
+ 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:
@@ -471,6 +492,12 @@ 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,
@@ -567,11 +594,12 @@ handler_kyc_webhook_generic (
rc->rh_ctx = kwh;
rc->rh_cleaner = &clean_kwh;
- if (GNUNET_OK !=
- TALER_KYCLOGIC_lookup_logic (args[0],
- &kwh->plugin,
- &kwh->pd,
- &kwh->section_name))
+ 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",
@@ -581,14 +609,6 @@ handler_kyc_webhook_generic (
TALER_EC_EXCHANGE_KYC_GENERIC_LOGIC_UNKNOWN,
args[0]);
}
- if (0 != strcmp (args[0],
- kwh->section_name))
- {
- return TALER_MHD_reply_with_error (rc->connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_GENERIC_PARAMETER_MALFORMED,
- "$PROVIDER_SECTION");
- }
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Calling KYC provider specific webhook\n");
kwh->wh = kwh->plugin->webhook (kwh->plugin->cls,
@@ -649,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,
@@ -670,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,
@@ -688,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
*/
@@ -698,6 +723,7 @@ 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)
{
@@ -710,13 +736,26 @@ proof_cb (
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);
}
@@ -727,32 +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)))
{
@@ -774,18 +825,18 @@ handler_kyc_proof_get (
}
if (GNUNET_OK !=
- TALER_KYCLOGIC_lookup_logic (args[1],
+ 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",
- args[1]);
+ 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;
@@ -796,7 +847,6 @@ handler_kyc_proof_get (
rs);
rs->ph = logic->proof (logic->cls,
pd,
- &args[2],
rc->connection,
&h_payto,
kyc_row_id,
@@ -940,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]) &&
@@ -1032,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",
@@ -1145,14 +1194,14 @@ handle_mhd_request (void *cls,
}
/* 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);
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Handler found for %s '%s'\n",
+ method,
+ url);
+ return MHD_YES;
}
- if (found) /* FIXME: this can never be true right now */
+ if (found)
{
/* we found a matching address, but the method is wrong */
struct MHD_Response *reply;
@@ -1330,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)