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_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_kyc.c taler-exchange-httpd_common_kyc.h \
taler-exchange-httpd_config.c taler-exchange-httpd_config.h \
taler-exchange-httpd_contract.c taler-exchange-httpd_contract.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
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
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 <jansson.h>
#include <microhttpd.h>
#include <pthread.h>
#include "taler_attributes.h"
#include "taler_json_lib.h"
#include "taler_kyclogic_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_responses.h"
@ -68,6 +68,11 @@ struct KycProofContext
*/
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
* 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.
*
@ -192,74 +219,40 @@ proof_cb (
kpc->ph = NULL;
GNUNET_async_scope_enter (&rc->async_scope_id,
&old_scope);
if (TALER_KYCLOGIC_STATUS_SUCCESS == status)
{
enum GNUNET_DB_QueryStatus qs;
size_t eas;
void *ea;
const char *birthdate;
struct GNUNET_ShortHashCode kyc_prox;
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,
&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);
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,
"insert_kyc_attributes");
GNUNET_async_scope_restore (&old_scope);
return;
}
qs = TEH_plugin->update_kyc_process_by_row (TEH_plugin->cls,
kpc->kat = TEH_kyc_finished (&rc->async_scope_id,
kpc->process_row,
kpc->provider_section,
&kpc->h_payto,
kpc->provider_section,
provider_user_id,
provider_legitimization_id,
expiration);
if (GNUNET_DB_STATUS_HARD_ERROR == qs)
expiration,
attributes,
http_status,
response,
&proof_finish,
kpc);
if (NULL == kpc->kat)
{
GNUNET_break (0);
http_status = MHD_HTTP_INTERNAL_SERVER_ERROR;
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;
response = TALER_MHD_make_error (
TALER_EC_EXCHANGE_GENERIC_BAD_CONFIGURATION,
"[exchange] AML_KYC_TRIGGER");
}
}
else
if (NULL == kpc->kat)
{
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"KYC process #%llu failed with status %d\n",
(unsigned long long) kpc->process_row,
status);
proof_finish (kpc,
http_status,
response);
}
kpc->response_code = http_status;
kpc->response = response;
kpc_resume (kpc);
GNUNET_async_scope_restore (&old_scope);
}
@ -279,6 +272,11 @@ clean_kpc (struct TEH_RequestContext *rc)
kpc->logic->proof_cancel (kpc->ph);
kpc->ph = NULL;
}
if (NULL != kpc->kat)
{
TEH_kyc_finished_cancel (kpc->kat);
kpc->kat = NULL;
}
if (NULL != kpc->response)
{
MHD_destroy_response (kpc->response);

View File

@ -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
@ -28,6 +28,7 @@
#include "taler_json_lib.h"
#include "taler_mhd_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_responses.h"
@ -53,6 +54,11 @@ struct KycWebhookContext
*/
struct TEH_RequestContext *rc;
/**
* Handle for the KYC-AML trigger interaction.
*/
struct TEH_KycAmlTrigger *kat;
/**
* 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.
*
@ -178,58 +206,27 @@ webhook_finished_cb (
switch (status)
{
case TALER_KYCLOGIC_STATUS_SUCCESS:
/* _successfully_ resumed case */
{
enum GNUNET_DB_QueryStatus qs;
size_t eas;
void *ea;
const char *birthdate;
struct GNUNET_ShortHashCode kyc_prox;
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,
kwh->kat = TEH_kyc_finished (
&kwh->rc->async_scope_id,
process_row,
provider_section,
account_id,
provider_section,
provider_user_id,
provider_legitimization_id,
expiration);
if (qs < 0)
expiration,
attributes,
http_status,
response,
&kyc_aml_webhook_finished,
kwh);
if (NULL == kwh->kat)
{
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;
}
http_status = MHD_HTTP_INTERNAL_SERVER_ERROR;
if (NULL != response)
MHD_destroy_response (response);
response = TALER_MHD_make_error (
TALER_EC_EXCHANGE_GENERIC_BAD_CONFIGURATION,
"[exchange] AML_KYC_TRIGGER");
}
break;
default:
@ -241,9 +238,10 @@ webhook_finished_cb (
status);
break;
}
kwh->response = response;
kwh->response_code = http_status;
kwh_resume (kwh);
if (NULL == kwh->kat)
kyc_aml_webhook_finished (kwh,
http_status,
response);
}
@ -262,6 +260,11 @@ clean_kwh (struct TEH_RequestContext *rc)
kwh->plugin->webhook_cancel (kwh->wh);
kwh->wh = NULL;
}
if (NULL != kwh->kat)
{
TEH_kyc_finished_cancel (kwh->kat);
kwh->kat = NULL;
}
if (NULL != kwh->response)
{
MHD_destroy_response (kwh->response);

View File

@ -68,7 +68,8 @@ TEH_PG_update_kyc_process_by_row (
if (qs <= 0)
{
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);
return qs;
}