summaryrefslogtreecommitdiff
path: root/src/exchange/taler-exchange-httpd_kyc-check.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/exchange/taler-exchange-httpd_kyc-check.c')
-rw-r--r--src/exchange/taler-exchange-httpd_kyc-check.c477
1 files changed, 370 insertions, 107 deletions
diff --git a/src/exchange/taler-exchange-httpd_kyc-check.c b/src/exchange/taler-exchange-httpd_kyc-check.c
index b4ec4197d..362c20a2e 100644
--- a/src/exchange/taler-exchange-httpd_kyc-check.c
+++ b/src/exchange/taler-exchange-httpd_kyc-check.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2021 Taler Systems SA
+ Copyright (C) 2021-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
@@ -25,6 +25,7 @@
#include <microhttpd.h>
#include <pthread.h>
#include "taler_json_lib.h"
+#include "taler_kyclogic_lib.h"
#include "taler_mhd_lib.h"
#include "taler_signatures.h"
#include "taler_dbevents.h"
@@ -54,20 +55,31 @@ struct KycPoller
struct MHD_Connection *connection;
/**
+ * Logic for @e ih
+ */
+ struct TALER_KYCLOGIC_Plugin *ih_logic;
+
+ /**
+ * Handle to asynchronously running KYC initiation
+ * request.
+ */
+ struct TALER_KYCLOGIC_InitiateHandle *ih;
+
+ /**
* Subscription for the database event we are
* waiting for.
*/
struct GNUNET_DB_EventHandler *eh;
/**
- * UUID being checked.
+ * Row of the requirement being checked.
*/
- uint64_t auth_payment_target_uuid;
+ uint64_t requirement_row;
/**
- * Current KYC status.
+ * Row of KYC process being initiated.
*/
- struct TALER_EXCHANGEDB_KycStatus kyc;
+ uint64_t process_row;
/**
* Hash of the payto:// URI we are confirming to
@@ -76,20 +88,60 @@ struct KycPoller
struct TALER_PaytoHashP h_payto;
/**
- * Payto URL as a string, as given to us by t
+ * When will this request time out?
*/
- const char *hps;
+ struct GNUNET_TIME_Absolute timeout;
/**
- * When will this request time out?
+ * If the KYC complete, what kind of data was collected?
*/
- struct GNUNET_TIME_Absolute timeout;
+ json_t *kyc_details;
+
+ /**
+ * Set to starting URL of KYC process if KYC is required.
+ */
+ char *kyc_url;
+
+ /**
+ * Set to error details, on error (@ec not TALER_EC_NONE).
+ */
+ char *hint;
+
+ /**
+ * Name of the section of the provider in the configuration.
+ */
+ const char *section_name;
+
+ /**
+ * Set to AML status of the account.
+ */
+ enum TALER_AmlDecisionState aml_status;
+
+ /**
+ * Set to error encountered with KYC logic, if any.
+ */
+ enum TALER_ErrorCode ec;
+
+ /**
+ * What kind of entity is doing the KYC check?
+ */
+ enum TALER_KYCLOGIC_KycUserType ut;
/**
* True if we are still suspended.
*/
bool suspended;
+ /**
+ * False if KYC is not required.
+ */
+ bool kyc_required;
+
+ /**
+ * True if we once tried the KYC initiation.
+ */
+ bool ih_done;
+
};
@@ -114,6 +166,11 @@ TEH_kyc_check_cleanup ()
GNUNET_CONTAINER_DLL_remove (kyp_head,
kyp_tail,
kyp);
+ if (NULL != kyp->ih)
+ {
+ kyp->ih_logic->initiate_cancel (kyp->ih);
+ kyp->ih = NULL;
+ }
if (kyp->suspended)
{
kyp->suspended = false;
@@ -143,11 +200,86 @@ kyp_cleanup (struct TEH_RequestContext *rc)
kyp->eh);
kyp->eh = NULL;
}
+ if (NULL != kyp->ih)
+ {
+ kyp->ih_logic->initiate_cancel (kyp->ih);
+ kyp->ih = NULL;
+ }
+ json_decref (kyp->kyc_details);
+ GNUNET_free (kyp->kyc_url);
+ GNUNET_free (kyp->hint);
GNUNET_free (kyp);
}
/**
+ * Function called with the result of a KYC initiation
+ * operation.
+ *
+ * @param cls closure with our `struct KycPoller *`
+ * @param ec #TALER_EC_NONE on success
+ * @param redirect_url set to where to redirect the user on success, NULL on failure
+ * @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 error_msg_hint set to additional details to return to user, NULL on success
+ */
+static void
+initiate_cb (
+ void *cls,
+ enum TALER_ErrorCode ec,
+ const char *redirect_url,
+ const char *provider_user_id,
+ const char *provider_legitimization_id,
+ const char *error_msg_hint)
+{
+ struct KycPoller *kyp = cls;
+ enum GNUNET_DB_QueryStatus qs;
+
+ kyp->ih = NULL;
+ kyp->ih_done = true;
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "KYC initiation `%s' completed with ec=%d (%s)\n",
+ provider_legitimization_id,
+ ec,
+ (TALER_EC_NONE == ec)
+ ? redirect_url
+ : error_msg_hint);
+ kyp->ec = ec;
+ if (TALER_EC_NONE == ec)
+ {
+ kyp->kyc_url = GNUNET_strdup (redirect_url);
+ }
+ else
+ {
+ kyp->hint = GNUNET_strdup (error_msg_hint);
+ }
+ qs = TEH_plugin->update_kyc_process_by_row (
+ TEH_plugin->cls,
+ kyp->process_row,
+ kyp->section_name,
+ &kyp->h_payto,
+ provider_user_id,
+ provider_legitimization_id,
+ redirect_url,
+ GNUNET_TIME_UNIT_ZERO_ABS);
+ if (qs <= 0)
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "KYC requirement update failed for %s with status %d at %s:%u\n",
+ TALER_B2S (&kyp->h_payto),
+ qs,
+ __FILE__,
+ __LINE__);
+ GNUNET_assert (kyp->suspended);
+ kyp->suspended = false;
+ GNUNET_CONTAINER_DLL_remove (kyp_head,
+ kyp_tail,
+ kyp);
+ MHD_resume_connection (kyp->connection);
+ TALER_MHD_daemon_trigger ();
+}
+
+
+/**
* Function implementing database transaction to check wallet's KYC status.
* Runs the transaction logic; IF it returns a non-error code, the transaction
* logic MUST NOT queue a MHD response. IF it returns an hard error, the
@@ -168,10 +300,53 @@ kyc_check (void *cls,
{
struct KycPoller *kyp = cls;
enum GNUNET_DB_QueryStatus qs;
-
- qs = TEH_plugin->select_kyc_status (TEH_plugin->cls,
- &kyp->h_payto,
- &kyp->kyc);
+ struct TALER_KYCLOGIC_ProviderDetails *pd;
+ enum GNUNET_GenericReturnValue ret;
+ struct TALER_PaytoHashP h_payto;
+ char *requirements;
+ char *redirect_url;
+ bool satisfied;
+
+ qs = TEH_plugin->lookup_kyc_requirement_by_row (
+ TEH_plugin->cls,
+ kyp->requirement_row,
+ &requirements,
+ &kyp->aml_status,
+ &h_payto);
+ if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "No KYC requirements open for %llu\n",
+ (unsigned long long) kyp->requirement_row);
+ return qs;
+ }
+ if (qs < 0)
+ {
+ GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR != qs);
+ return qs;
+ }
+ if (0 !=
+ GNUNET_memcmp (&kyp->h_payto,
+ &h_payto))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Requirement %llu provided, but h_payto does not match\n",
+ (unsigned long long) kyp->requirement_row);
+ GNUNET_break_op (0);
+ *mhd_ret = TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_FORBIDDEN,
+ TALER_EC_EXCHANGE_KYC_CHECK_AUTHORIZATION_FAILED,
+ "h_payto");
+ GNUNET_free (requirements);
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+ qs = TALER_KYCLOGIC_check_satisfied (
+ &requirements,
+ &h_payto,
+ &kyp->kyc_details,
+ TEH_plugin->select_satisfied_kyc_processes,
+ TEH_plugin->cls,
+ &satisfied);
if (qs < 0)
{
if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
@@ -180,9 +355,95 @@ kyc_check (void *cls,
*mhd_ret = TALER_MHD_reply_with_error (connection,
MHD_HTTP_INTERNAL_SERVER_ERROR,
TALER_EC_GENERIC_DB_FETCH_FAILED,
- "inselect_wallet_status");
+ "kyc_test_required");
+ GNUNET_free (requirements);
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+ if (satisfied)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "KYC requirements `%s' already satisfied\n",
+ requirements);
+ GNUNET_free (requirements);
+ return GNUNET_DB_STATUS_SUCCESS_NO_RESULTS;
+ }
+
+ kyp->kyc_required = true;
+ ret = TALER_KYCLOGIC_requirements_to_logic (requirements,
+ kyp->ut,
+ &kyp->ih_logic,
+ &pd,
+ &kyp->section_name);
+ if (GNUNET_OK != ret)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "KYC requirements `%s' cannot be checked, but are set as required in database!\n",
+ requirements);
+ *mhd_ret = TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_EXCHANGE_KYC_GENERIC_LOGIC_GONE,
+ requirements);
+ GNUNET_free (requirements);
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+ GNUNET_free (requirements);
+
+ if (kyp->ih_done)
return qs;
+ qs = TEH_plugin->get_pending_kyc_requirement_process (
+ TEH_plugin->cls,
+ &h_payto,
+ kyp->section_name,
+ &redirect_url);
+ if (qs < 0)
+ {
+ if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
+ return qs;
+ GNUNET_break (0);
+ *mhd_ret = TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_STORE_FAILED,
+ "insert_kyc_requirement_process");
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+ if ( (qs > 0) &&
+ (NULL != redirect_url) )
+ {
+ kyp->kyc_url = redirect_url;
+ return qs;
+ }
+ if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
+ {
+ /* set up new requirement process */
+ qs = TEH_plugin->insert_kyc_requirement_process (
+ TEH_plugin->cls,
+ &h_payto,
+ kyp->section_name,
+ NULL,
+ NULL,
+ &kyp->process_row);
+ if (qs < 0)
+ {
+ if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
+ return qs;
+ GNUNET_break (0);
+ *mhd_ret = TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_STORE_FAILED,
+ "insert_kyc_requirement_process");
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
}
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Initiating KYC check with logic %s\n",
+ kyp->ih_logic->name);
+ kyp->ih = kyp->ih_logic->initiate (kyp->ih_logic->cls,
+ pd,
+ &h_payto,
+ kyp->process_row,
+ &initiate_cb,
+ kyp);
+ GNUNET_break (NULL != kyp->ih);
return qs;
}
@@ -230,7 +491,7 @@ db_event_cb (void *cls,
MHD_RESULT
TEH_handler_kyc_check (
struct TEH_RequestContext *rc,
- const char *const args[])
+ const char *const args[3])
{
struct KycPoller *kyp = rc->rh_ctx;
MHD_RESULT res;
@@ -245,86 +506,62 @@ TEH_handler_kyc_check (
rc->rh_cleaner = &kyp_cleanup;
{
- // FIXME: now 'legitimization_uuid'!
- unsigned long long payment_target_uuid;
+ unsigned long long requirement_row;
char dummy;
if (1 !=
sscanf (args[0],
"%llu%c",
- &payment_target_uuid,
+ &requirement_row,
&dummy))
{
GNUNET_break_op (0);
return TALER_MHD_reply_with_error (rc->connection,
MHD_HTTP_BAD_REQUEST,
TALER_EC_GENERIC_PARAMETER_MALFORMED,
- "payment_target_uuid");
- }
- kyp->auth_payment_target_uuid = (uint64_t) payment_target_uuid;
- }
- {
- const char *ts;
-
- ts = MHD_lookup_connection_value (rc->connection,
- MHD_GET_ARGUMENT_KIND,
- "timeout_ms");
- if (NULL != ts)
- {
- char dummy;
- unsigned long long tms;
-
- if (1 !=
- sscanf (ts,
- "%llu%c",
- &tms,
- &dummy))
- {
- GNUNET_break_op (0);
- return TALER_MHD_reply_with_error (rc->connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_GENERIC_PARAMETER_MALFORMED,
- "timeout_ms");
- }
- kyp->timeout = GNUNET_TIME_relative_to_absolute (
- GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MILLISECONDS,
- tms));
+ "requirement_row");
}
+ kyp->requirement_row = (uint64_t) requirement_row;
}
- // FIXME: replace with args[1]!
- kyp->hps = MHD_lookup_connection_value (rc->connection,
- MHD_GET_ARGUMENT_KIND,
- "h_payto");
- if (NULL == kyp->hps)
+ if (GNUNET_OK !=
+ GNUNET_STRINGS_string_to_data (args[1],
+ strlen (args[1]),
+ &kyp->h_payto,
+ sizeof (kyp->h_payto)))
{
GNUNET_break_op (0);
return TALER_MHD_reply_with_error (rc->connection,
MHD_HTTP_BAD_REQUEST,
- TALER_EC_GENERIC_PARAMETER_MISSING,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
"h_payto");
}
+
if (GNUNET_OK !=
- GNUNET_STRINGS_string_to_data (kyp->hps,
- strlen (kyp->hps),
- &kyp->h_payto,
- sizeof (kyp->h_payto)))
+ TALER_KYCLOGIC_kyc_user_type_from_string (args[2],
+ &kyp->ut))
{
GNUNET_break_op (0);
return TALER_MHD_reply_with_error (rc->connection,
MHD_HTTP_BAD_REQUEST,
TALER_EC_GENERIC_PARAMETER_MALFORMED,
- "h_payto");
+ "usertype");
}
- }
- if (TEH_KYC_NONE == TEH_kyc_config.mode)
- return TALER_MHD_reply_static (
+ TALER_MHD_parse_request_timeout (rc->connection,
+ &kyp->timeout);
+ }
+ /* KYC plugin generated reply? */
+ if (NULL != kyp->kyc_url)
+ {
+ return TALER_MHD_REPLY_JSON_PACK (
rc->connection,
- MHD_HTTP_NO_CONTENT,
- NULL,
- NULL,
- 0);
+ MHD_HTTP_ACCEPTED,
+ GNUNET_JSON_pack_uint64 ("aml_status",
+ kyp->aml_status),
+ GNUNET_JSON_pack_string ("kyc_url",
+ kyp->kyc_url));
+ }
if ( (NULL == kyp->eh) &&
GNUNET_TIME_absolute_is_future (kyp->timeout) )
@@ -353,29 +590,73 @@ TEH_handler_kyc_check (
&kyc_check,
kyp);
if (GNUNET_SYSERR == ret)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Transaction failed.\n");
return res;
+ }
+ /* KYC plugin generated reply? */
+ if (NULL != kyp->kyc_url)
+ {
+ return TALER_MHD_REPLY_JSON_PACK (
+ rc->connection,
+ MHD_HTTP_ACCEPTED,
+ GNUNET_JSON_pack_uint64 ("aml_status",
+ kyp->aml_status),
+ GNUNET_JSON_pack_string ("kyc_url",
+ kyp->kyc_url));
+ }
- if (kyp->auth_payment_target_uuid !=
- kyp->kyc.payment_target_uuid)
+ if ( (NULL == kyp->ih) &&
+ (! kyp->kyc_required) )
{
- GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
- "Account %llu provided, but payto %s is for %llu\n",
- (unsigned long long) kyp->auth_payment_target_uuid,
- kyp->hps,
- (unsigned long long) kyp->kyc.payment_target_uuid);
- GNUNET_break_op (0);
- return TALER_MHD_reply_with_error (rc->connection,
- MHD_HTTP_FORBIDDEN,
- TALER_EC_EXCHANGE_KYC_CHECK_AUTHORIZATION_FAILED,
- "h_payto");
+ if (TALER_AML_NORMAL != kyp->aml_status)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "KYC is OK, but AML active: %d\n",
+ (int) kyp->aml_status);
+ return TALER_MHD_REPLY_JSON_PACK (
+ rc->connection,
+ MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS,
+ GNUNET_JSON_pack_uint64 ("aml_status",
+ kyp->aml_status));
+ }
+ /* KYC not required */
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "KYC not required %llu\n",
+ (unsigned long long) kyp->requirement_row);
+ return TALER_MHD_reply_static (
+ rc->connection,
+ MHD_HTTP_NO_CONTENT,
+ NULL,
+ NULL,
+ 0);
+ }
+
+ if (NULL != kyp->ih)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Suspending HTTP request on KYC logic...\n");
+ kyp->suspended = true;
+ GNUNET_CONTAINER_DLL_insert (kyp_head,
+ kyp_tail,
+ kyp);
+ MHD_suspend_connection (kyp->connection);
+ return MHD_YES;
}
/* long polling? */
- if ( (! kyp->kyc.ok) &&
+ if ( (NULL != kyp->section_name) &&
GNUNET_TIME_absolute_is_future (kyp->timeout))
{
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Suspending HTTP request on timeout (%s) now...\n",
+ GNUNET_TIME_relative2s (GNUNET_TIME_absolute_get_remaining (
+ kyp->timeout),
+ true));
GNUNET_assert (NULL != kyp->eh);
kyp->suspended = true;
+ kyp->section_name = NULL;
GNUNET_CONTAINER_DLL_insert (kyp_head,
kyp_tail,
kyp);
@@ -383,37 +664,14 @@ TEH_handler_kyc_check (
return MHD_YES;
}
- /* KYC failed? */
- if (! kyp->kyc.ok)
+ if (TALER_EC_NONE != kyp->ec)
{
- char *url;
- char *redirect_uri;
- char *redirect_uri_encoded;
-
- GNUNET_assert (TEH_KYC_OAUTH2 == TEH_kyc_config.mode);
- GNUNET_asprintf (&redirect_uri,
- "%s/kyc-proof/%s",
- TEH_base_url,
- kyp->hps);
- redirect_uri_encoded = TALER_urlencode (redirect_uri);
- GNUNET_free (redirect_uri);
- GNUNET_asprintf (&url,
- "%s?client_id=%s&redirect_uri=%s",
- TEH_kyc_config.details.oauth2.login_url,
- TEH_kyc_config.details.oauth2.client_id,
- redirect_uri_encoded);
- GNUNET_free (redirect_uri_encoded);
-
- res = TALER_MHD_REPLY_JSON_PACK (
- rc->connection,
- MHD_HTTP_ACCEPTED,
- GNUNET_JSON_pack_string ("kyc_url",
- url));
- GNUNET_free (url);
- return res;
+ return TALER_MHD_reply_with_ec (rc->connection,
+ kyp->ec,
+ kyp->hint);
}
- /* KYC succeeded! */
+ /* KYC must have succeeded! */
{
struct TALER_ExchangePublicKeyP pub;
struct TALER_ExchangeSignatureP sig;
@@ -423,6 +681,7 @@ TEH_handler_kyc_check (
(ec = TALER_exchange_online_account_setup_success_sign (
&TEH_keys_exchange_sign_,
&kyp->h_payto,
+ kyp->kyc_details,
now,
&pub,
&sig)))
@@ -438,6 +697,10 @@ TEH_handler_kyc_check (
&sig),
GNUNET_JSON_pack_data_auto ("exchange_pub",
&pub),
+ GNUNET_JSON_pack_uint64 ("aml_status",
+ kyp->aml_status),
+ GNUNET_JSON_pack_object_incref ("kyc_details",
+ kyp->kyc_details),
GNUNET_JSON_pack_timestamp ("now",
now));
}