towards KYC AML triggers

This commit is contained in:
Christian Grothoff 2023-05-16 22:26:39 +02:00
parent e371d76cfe
commit 755955de28
No known key found for this signature in database
GPG Key ID: 939E6BE1E29FC3CC
6 changed files with 475 additions and 113 deletions

View File

@ -131,6 +131,7 @@ taler_exchange_httpd_SOURCES = \
taler-exchange-httpd_age-withdraw.c taler-exchange-httpd_age-withdraw.h \ taler-exchange-httpd_age-withdraw.c taler-exchange-httpd_age-withdraw.h \
taler-exchange-httpd_age-withdraw_reveal.c taler-exchange-httpd_age-withdraw_reveal.h \ taler-exchange-httpd_age-withdraw_reveal.c taler-exchange-httpd_age-withdraw_reveal.h \
taler-exchange-httpd_common_deposit.c taler-exchange-httpd_common_deposit.h \ taler-exchange-httpd_common_deposit.c taler-exchange-httpd_common_deposit.h \
taler-exchange-httpd_common_kyc.c taler-exchange-httpd_common_kyc.h \
taler-exchange-httpd_config.c taler-exchange-httpd_config.h \ taler-exchange-httpd_config.c taler-exchange-httpd_config.h \
taler-exchange-httpd_contract.c taler-exchange-httpd_contract.h \ taler-exchange-httpd_contract.c taler-exchange-httpd_contract.h \
taler-exchange-httpd_csr.c taler-exchange-httpd_csr.h \ taler-exchange-httpd_csr.c taler-exchange-httpd_csr.h \

View File

@ -0,0 +1,259 @@
/*
This file is part of TALER
Copyright (C) 2023 Taler Systems SA
TALER is free software; you can redistribute it and/or modify it under the
terms of the GNU Affero General Public License as published by the Free Software
Foundation; either version 3, or (at your option) any later version.
TALER is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License along with
TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/
/**
* @file taler-exchange-httpd_common_kyc.c
* @brief shared logic for finishing a KYC process
* @author Christian Grothoff
*/
#include "platform.h"
#include "taler-exchange-httpd_common_kyc.h"
#include "taler_attributes.h"
#include "taler_exchangedb_plugin.h"
struct TEH_KycAmlTrigger
{
/**
* Our logging scope.
*/
struct GNUNET_AsyncScopeId scope;
/**
* account the operation is about
*/
struct TALER_PaytoHashP account_id;
/**
* until when is the KYC data valid
*/
struct GNUNET_TIME_Absolute expiration;
/**
* legitimization process the KYC data is about
*/
uint64_t process_row;
/**
* name of the configuration section of the logic that was run
*/
char *provider_section;
/**
* set to user ID at the provider, or NULL if not supported or unknown
*/
char *provider_user_id;
/**
* provider_legitimization_id set to legitimization process ID at the provider, or NULL if not supported or unknown
*/
char *provider_legitimization_id;
/**
* function to call with the result
*/
TEH_KycAmlTriggerCallback cb;
/**
* closure for @e cb
*/
void *cb_cls;
/**
* user attributes returned by the provider
*/
json_t *attributes;
/**
* response to return to the HTTP client
*/
struct MHD_Response *response;
/**
* Handle to an external process that evalutates the
* need to run AML on the account.
*/
struct TALER_JSON_ExternalConversion *kyc_aml;
/**
* HTTP status code of @e response
*/
unsigned int http_status;
};
/**
* Type of a callback that receives a JSON @a result.
*
* @param cls closure of type `struct TEH_KycAmlTrigger *`
* @param status_type how did the process die
* @param code termination status code from the process
* @param result some JSON result, NULL if we failed to get an JSON output
*/
static void
kyc_aml_finished (void *cls,
enum GNUNET_OS_ProcessStatusType status_type,
unsigned long code,
const json_t *result)
{
struct TEH_KycAmlTrigger *kat = cls;
enum GNUNET_DB_QueryStatus qs;
size_t eas;
void *ea;
const char *birthdate;
struct GNUNET_ShortHashCode kyc_prox;
struct GNUNET_AsyncScopeSave old_scope;
kat->kyc_aml = NULL;
GNUNET_async_scope_enter (&kat->scope,
&old_scope);
TALER_CRYPTO_attributes_to_kyc_prox (kat->attributes,
&kyc_prox);
birthdate = json_string_value (json_object_get (kat->attributes,
TALER_ATTRIBUTE_BIRTHDATE));
TALER_CRYPTO_kyc_attributes_encrypt (&TEH_attribute_key,
kat->attributes,
&ea,
&eas);
// FIXME: begin transaction (or move everything into one stored procedure?)
qs = TEH_plugin->insert_kyc_attributes (
TEH_plugin->cls,
&kat->account_id,
&kyc_prox,
kat->provider_section,
birthdate,
GNUNET_TIME_timestamp_get (),
GNUNET_TIME_absolute_to_timestamp (kat->expiration),
eas,
ea);
GNUNET_free (ea);
if (GNUNET_DB_STATUS_HARD_ERROR == qs)
{
GNUNET_break (0);
if (NULL != kat->response)
MHD_destroy_response (kat->response);
kat->http_status = MHD_HTTP_INTERNAL_SERVER_ERROR;
kat->response = TALER_MHD_make_error (TALER_EC_GENERIC_DB_STORE_FAILED,
"insert_kyc_attributes");
goto finish;
}
qs = TEH_plugin->update_kyc_process_by_row (TEH_plugin->cls,
kat->process_row,
kat->provider_section,
&kat->account_id,
kat->provider_user_id,
kat->provider_legitimization_id,
kat->expiration);
if (GNUNET_DB_STATUS_HARD_ERROR == qs)
{
GNUNET_break (0);
if (NULL != kat->response)
MHD_destroy_response (kat->response);
kat->http_status = MHD_HTTP_INTERNAL_SERVER_ERROR;
kat->response = TALER_MHD_make_error (TALER_EC_GENERIC_DB_STORE_FAILED,
"update_kyc_process_by_row");
goto finish;
}
// FIXME: do DB work, possibly updating kat!
if (0 != code)
{
// FIXME: trigger AML!
GNUNET_break (0); // FIXME: not implemented
}
// FIXME: end transaction
/* Finally, return result to main handler */
finish:
kat->cb (kat->cb_cls,
kat->http_status,
kat->response);
kat->response = NULL;
TEH_kyc_finished_cancel (kat);
GNUNET_async_scope_restore (&old_scope);
}
struct TEH_KycAmlTrigger *
TEH_kyc_finished (const struct GNUNET_AsyncScopeId *scope,
uint64_t process_row,
const struct TALER_PaytoHashP *account_id,
const char *provider_section,
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,
TEH_KycAmlTriggerCallback cb,
void *cb_cls)
{
struct TEH_KycAmlTrigger *kat;
kat = GNUNET_new (struct TEH_KycAmlTrigger);
kat->scope = *scope;
kat->process_row = process_row;
kat->account_id = *account_id;
kat->provider_section
= GNUNET_strdup (provider_section);
if (NULL != provider_user_id)
kat->provider_user_id
= GNUNET_strdup (provider_user_id);
if (NULL != provider_legitimization_id)
kat->provider_legitimization_id
= GNUNET_strdup (provider_legitimization_id);
kat->expiration = expiration;
kat->attributes = json_incref ((json_t*) attributes);
kat->http_status = http_status;
kat->response = response;
kat->cb = cb;
kat->cb_cls = cb_cls;
kat->kyc_aml
= TALER_JSON_external_conversion_start (
attributes,
&kyc_aml_finished,
kat,
TEH_kyc_aml_trigger,
TEH_kyc_aml_trigger,
NULL);
if (NULL == kat->kyc_aml)
{
GNUNET_break (0);
TEH_kyc_finished_cancel (kat);
return NULL;
}
return kat;
}
void
TEH_kyc_finished_cancel (struct TEH_KycAmlTrigger *kat)
{
if (NULL != kat->kyc_aml)
{
TALER_JSON_external_conversion_stop (kat->kyc_aml);
kat->kyc_aml = NULL;
}
GNUNET_free (kat->provider_section);
GNUNET_free (kat->provider_user_id);
GNUNET_free (kat->provider_legitimization_id);
json_decref (kat->attributes);
if (NULL != kat->response)
{
MHD_destroy_response (kat->response);
kat->response = NULL;
}
GNUNET_free (kat);
}

View File

@ -0,0 +1,100 @@
/*
This file is part of TALER
Copyright (C) 2023 Taler Systems SA
TALER is free software; you can redistribute it and/or modify it under the
terms of the GNU Affero General Public License as published by the Free Software
Foundation; either version 3, or (at your option) any later version.
TALER is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License along with
TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/
/**
* @file taler-exchange-httpd_common_kyc.h
* @brief shared logic for finishing a KYC process
* @author Christian Grothoff
*/
#ifndef TALER_EXCHANGE_HTTPD_COMMON_KYC_H
#define TALER_EXCHANGE_HTTPD_COMMON_KYC_H
#include <gnunet/gnunet_util_lib.h>
#include <gnunet/gnunet_json_lib.h>
#include <jansson.h>
#include <microhttpd.h>
#include "taler_json_lib.h"
#include "taler_mhd_lib.h"
#include "taler-exchange-httpd.h"
/**
* Function called after the KYC-AML trigger is done.
*
* @param cls closure
* @param http_status final HTTP status to return
* @param[in] response final HTTP ro return
*/
typedef void
(*TEH_KycAmlTriggerCallback) (
void *cls,
unsigned int http_status,
struct MHD_Response *response);
/**
* Handle for an asynchronous operation to finish
* a KYC process after running the AML trigger.
*/
struct TEH_KycAmlTrigger;
// FIXME: also pass async log context and set it!
/**
* We have finished a KYC process and obtained new
* @a attributes for a given @a account_id.
* Check with the KYC-AML trigger to see if we need
* to initiate an AML process, and store the attributes
* in the database. Then call @a cb.
*
* @param scope the HTTP request logging scope
* @param process_row legitimization process the webhook was about
* @param account_id account the webhook was about
* @param provider_section name of the configuration section of the logic that was run
* @param provider_user_id set to user ID at the provider, or NULL if not supported or unknown
* @param provider_legitimization_id set to legitimization process ID at the provider, or NULL if not supported or unknown
* @param status KYC status
* @param expiration until when is the KYC check valid
* @param attributes user attributes returned by the provider
* @param http_status HTTP status code of @a response
* @param[in] response to return to the HTTP client
* @param cb function to call with the result
* @param cb_cls closure for @a cb
* @return handle to cancel the operation
*/
struct TEH_KycAmlTrigger *
TEH_kyc_finished (const struct GNUNET_AsyncScopeId *scope,
uint64_t process_row,
const struct TALER_PaytoHashP *account_id,
const char *provider_section,
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,
TEH_KycAmlTriggerCallback cb,
void *cb_cls);
/**
* Cancel KYC finish operation.
*
* @param[in] kat operation to abort
*/
void
TEH_kyc_finished_cancel (struct TEH_KycAmlTrigger *kat);
#endif

View File

@ -1,6 +1,6 @@
/* /*
This file is part of TALER This file is part of TALER
Copyright (C) 2021-2022 Taler Systems SA Copyright (C) 2021-2023 Taler Systems SA
TALER is free software; you can redistribute it and/or modify it under the 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 terms of the GNU Affero General Public License as published by the Free Software
@ -23,11 +23,11 @@
#include <gnunet/gnunet_json_lib.h> #include <gnunet/gnunet_json_lib.h>
#include <jansson.h> #include <jansson.h>
#include <microhttpd.h> #include <microhttpd.h>
#include <pthread.h>
#include "taler_attributes.h" #include "taler_attributes.h"
#include "taler_json_lib.h" #include "taler_json_lib.h"
#include "taler_kyclogic_lib.h" #include "taler_kyclogic_lib.h"
#include "taler_mhd_lib.h" #include "taler_mhd_lib.h"
#include "taler-exchange-httpd_common_kyc.h"
#include "taler-exchange-httpd_kyc-proof.h" #include "taler-exchange-httpd_kyc-proof.h"
#include "taler-exchange-httpd_responses.h" #include "taler-exchange-httpd_responses.h"
@ -68,6 +68,11 @@ struct KycProofContext
*/ */
struct TALER_KYCLOGIC_ProofHandle *ph; struct TALER_KYCLOGIC_ProofHandle *ph;
/**
* KYC AML trigger operation.
*/
struct TEH_KycAmlTrigger *kat;
/** /**
* Process information about the user for the plugin from the database, can * Process information about the user for the plugin from the database, can
* be NULL. * be NULL.
@ -159,6 +164,28 @@ TEH_kyc_proof_cleanup (void)
} }
/**
* Function called after the KYC-AML trigger is done.
*
* @param cls closure
* @param http_status final HTTP status to return
* @param[in] response final HTTP ro return
*/
static void
proof_finish (
void *cls,
unsigned int http_status,
struct MHD_Response *response)
{
struct KycProofContext *kpc = cls;
kpc->kat = NULL;
kpc->response_code = http_status;
kpc->response = response;
kpc_resume (kpc);
}
/** /**
* Function called with the result of a proof check operation. * Function called with the result of a proof check operation.
* *
@ -192,74 +219,40 @@ proof_cb (
kpc->ph = NULL; kpc->ph = NULL;
GNUNET_async_scope_enter (&rc->async_scope_id, GNUNET_async_scope_enter (&rc->async_scope_id,
&old_scope); &old_scope);
if (TALER_KYCLOGIC_STATUS_SUCCESS == status) if (TALER_KYCLOGIC_STATUS_SUCCESS == status)
{ {
enum GNUNET_DB_QueryStatus qs; kpc->kat = TEH_kyc_finished (&rc->async_scope_id,
size_t eas; kpc->process_row,
void *ea; &kpc->h_payto,
const char *birthdate; kpc->provider_section,
struct GNUNET_ShortHashCode kyc_prox; provider_user_id,
provider_legitimization_id,
TALER_CRYPTO_attributes_to_kyc_prox (attributes, expiration,
&kyc_prox); attributes,
birthdate = json_string_value (json_object_get (attributes, http_status,
TALER_ATTRIBUTE_BIRTHDATE)); response,
TALER_CRYPTO_kyc_attributes_encrypt (&TEH_attribute_key, &proof_finish,
attributes, kpc);
&ea, if (NULL == kpc->kat)
&eas);
qs = TEH_plugin->insert_kyc_attributes (
TEH_plugin->cls,
&kpc->h_payto,
&kyc_prox,
kpc->provider_section,
birthdate,
GNUNET_TIME_timestamp_get (),
GNUNET_TIME_absolute_to_timestamp (expiration),
eas,
ea);
GNUNET_free (ea);
if (GNUNET_DB_STATUS_HARD_ERROR == qs)
{ {
GNUNET_break (0); http_status = MHD_HTTP_INTERNAL_SERVER_ERROR;
if (NULL != response) if (NULL != response)
MHD_destroy_response (response); MHD_destroy_response (response);
kpc->response_code = MHD_HTTP_INTERNAL_SERVER_ERROR; response = TALER_MHD_make_error (
kpc->response = TALER_MHD_make_error (TALER_EC_GENERIC_DB_STORE_FAILED, TALER_EC_EXCHANGE_GENERIC_BAD_CONFIGURATION,
"insert_kyc_attributes"); "[exchange] AML_KYC_TRIGGER");
GNUNET_async_scope_restore (&old_scope);
return;
}
qs = TEH_plugin->update_kyc_process_by_row (TEH_plugin->cls,
kpc->process_row,
kpc->provider_section,
&kpc->h_payto,
provider_user_id,
provider_legitimization_id,
expiration);
if (GNUNET_DB_STATUS_HARD_ERROR == qs)
{
GNUNET_break (0);
if (NULL != response)
MHD_destroy_response (response);
kpc->response_code = MHD_HTTP_INTERNAL_SERVER_ERROR;
kpc->response = TALER_MHD_make_error (TALER_EC_GENERIC_DB_STORE_FAILED,
"set_kyc_ok");
GNUNET_async_scope_restore (&old_scope);
return;
} }
} }
else if (NULL == kpc->kat)
{ {
GNUNET_log (GNUNET_ERROR_TYPE_INFO, GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"KYC process #%llu failed with status %d\n", "KYC process #%llu failed with status %d\n",
(unsigned long long) kpc->process_row, (unsigned long long) kpc->process_row,
status); status);
proof_finish (kpc,
http_status,
response);
} }
kpc->response_code = http_status;
kpc->response = response;
kpc_resume (kpc);
GNUNET_async_scope_restore (&old_scope); GNUNET_async_scope_restore (&old_scope);
} }
@ -279,6 +272,11 @@ clean_kpc (struct TEH_RequestContext *rc)
kpc->logic->proof_cancel (kpc->ph); kpc->logic->proof_cancel (kpc->ph);
kpc->ph = NULL; kpc->ph = NULL;
} }
if (NULL != kpc->kat)
{
TEH_kyc_finished_cancel (kpc->kat);
kpc->kat = NULL;
}
if (NULL != kpc->response) if (NULL != kpc->response)
{ {
MHD_destroy_response (kpc->response); MHD_destroy_response (kpc->response);

View File

@ -1,6 +1,6 @@
/* /*
This file is part of TALER 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 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 terms of the GNU Affero General Public License as published by the Free Software
@ -28,6 +28,7 @@
#include "taler_json_lib.h" #include "taler_json_lib.h"
#include "taler_mhd_lib.h" #include "taler_mhd_lib.h"
#include "taler_kyclogic_lib.h" #include "taler_kyclogic_lib.h"
#include "taler-exchange-httpd_common_kyc.h"
#include "taler-exchange-httpd_kyc-webhook.h" #include "taler-exchange-httpd_kyc-webhook.h"
#include "taler-exchange-httpd_responses.h" #include "taler-exchange-httpd_responses.h"
@ -53,6 +54,11 @@ struct KycWebhookContext
*/ */
struct TEH_RequestContext *rc; struct TEH_RequestContext *rc;
/**
* Handle for the KYC-AML trigger interaction.
*/
struct TEH_KycAmlTrigger *kat;
/** /**
* Plugin responsible for the webhook. * Plugin responsible for the webhook.
*/ */
@ -140,6 +146,28 @@ TEH_kyc_webhook_cleanup (void)
} }
/**
* Function called after the KYC-AML trigger is done.
*
* @param cls closure with a `struct KycWebhookContext *`
* @param http_status final HTTP status to return
* @param[in] response final HTTP ro return
*/
static void
kyc_aml_webhook_finished (
void *cls,
unsigned int http_status,
struct MHD_Response *response)
{
struct KycWebhookContext *kwh = cls;
kwh->kat = NULL;
kwh->response = response;
kwh->response_code = http_status;
kwh_resume (kwh);
}
/** /**
* Function called with the result of a KYC webhook operation. * Function called with the result of a KYC webhook operation.
* *
@ -178,58 +206,27 @@ webhook_finished_cb (
switch (status) switch (status)
{ {
case TALER_KYCLOGIC_STATUS_SUCCESS: case TALER_KYCLOGIC_STATUS_SUCCESS:
/* _successfully_ resumed case */ kwh->kat = TEH_kyc_finished (
&kwh->rc->async_scope_id,
process_row,
account_id,
provider_section,
provider_user_id,
provider_legitimization_id,
expiration,
attributes,
http_status,
response,
&kyc_aml_webhook_finished,
kwh);
if (NULL == kwh->kat)
{ {
enum GNUNET_DB_QueryStatus qs; http_status = MHD_HTTP_INTERNAL_SERVER_ERROR;
size_t eas; if (NULL != response)
void *ea; MHD_destroy_response (response);
const char *birthdate; response = TALER_MHD_make_error (
struct GNUNET_ShortHashCode kyc_prox; TALER_EC_EXCHANGE_GENERIC_BAD_CONFIGURATION,
"[exchange] AML_KYC_TRIGGER");
TALER_CRYPTO_attributes_to_kyc_prox (attributes,
&kyc_prox);
birthdate = json_string_value (json_object_get (attributes,
TALER_ATTRIBUTE_BIRTHDATE));
TALER_CRYPTO_kyc_attributes_encrypt (&TEH_attribute_key,
attributes,
&ea,
&eas);
qs = TEH_plugin->insert_kyc_attributes (
TEH_plugin->cls,
account_id,
&kyc_prox,
provider_section,
birthdate,
GNUNET_TIME_timestamp_get (),
GNUNET_TIME_absolute_to_timestamp (expiration),
eas,
ea);
GNUNET_free (ea);
if (qs < 0)
{
GNUNET_break (0);
kwh->response = TALER_MHD_make_error (TALER_EC_GENERIC_DB_STORE_FAILED,
"insert_kyc_attributes");
kwh->response_code = MHD_HTTP_INTERNAL_SERVER_ERROR;
kwh_resume (kwh);
return;
}
qs = TEH_plugin->update_kyc_process_by_row (TEH_plugin->cls,
process_row,
provider_section,
account_id,
provider_user_id,
provider_legitimization_id,
expiration);
if (qs < 0)
{
GNUNET_break (0);
kwh->response = TALER_MHD_make_error (TALER_EC_GENERIC_DB_STORE_FAILED,
"set_kyc_ok");
kwh->response_code = MHD_HTTP_INTERNAL_SERVER_ERROR;
kwh_resume (kwh);
return;
}
} }
break; break;
default: default:
@ -241,9 +238,10 @@ webhook_finished_cb (
status); status);
break; break;
} }
kwh->response = response; if (NULL == kwh->kat)
kwh->response_code = http_status; kyc_aml_webhook_finished (kwh,
kwh_resume (kwh); http_status,
response);
} }
@ -262,6 +260,11 @@ clean_kwh (struct TEH_RequestContext *rc)
kwh->plugin->webhook_cancel (kwh->wh); kwh->plugin->webhook_cancel (kwh->wh);
kwh->wh = NULL; kwh->wh = NULL;
} }
if (NULL != kwh->kat)
{
TEH_kyc_finished_cancel (kwh->kat);
kwh->kat = NULL;
}
if (NULL != kwh->response) if (NULL != kwh->response)
{ {
MHD_destroy_response (kwh->response); MHD_destroy_response (kwh->response);

View File

@ -68,7 +68,8 @@ TEH_PG_update_kyc_process_by_row (
if (qs <= 0) if (qs <= 0)
{ {
GNUNET_log (GNUNET_ERROR_TYPE_WARNING, GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
"Failed to update legitimization process: %d\n", "Failed to update legitimization process %llu: %d\n",
(unsigned long long) process_row,
qs); qs);
return qs; return qs;
} }