diff --git a/src/exchange/Makefile.am b/src/exchange/Makefile.am index 3d87a2a58..c04bca0f2 100644 --- a/src/exchange/Makefile.am +++ b/src/exchange/Makefile.am @@ -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 \ diff --git a/src/exchange/taler-exchange-httpd_common_kyc.c b/src/exchange/taler-exchange-httpd_common_kyc.c new file mode 100644 index 000000000..62e6fe526 --- /dev/null +++ b/src/exchange/taler-exchange-httpd_common_kyc.c @@ -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 +*/ +/** + * @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); +} diff --git a/src/exchange/taler-exchange-httpd_common_kyc.h b/src/exchange/taler-exchange-httpd_common_kyc.h new file mode 100644 index 000000000..5672d0e3e --- /dev/null +++ b/src/exchange/taler-exchange-httpd_common_kyc.h @@ -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 +*/ +/** + * @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 +#include +#include +#include +#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 diff --git a/src/exchange/taler-exchange-httpd_kyc-proof.c b/src/exchange/taler-exchange-httpd_kyc-proof.c index 9668ee54c..b3175bd28 100644 --- a/src/exchange/taler-exchange-httpd_kyc-proof.c +++ b/src/exchange/taler-exchange-httpd_kyc-proof.c @@ -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 #include #include -#include #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) + kpc->kat = TEH_kyc_finished (&rc->async_scope_id, + kpc->process_row, + &kpc->h_payto, + kpc->provider_section, + provider_user_id, + provider_legitimization_id, + 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, - "insert_kyc_attributes"); - 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; + 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); diff --git a/src/exchange/taler-exchange-httpd_kyc-webhook.c b/src/exchange/taler-exchange-httpd_kyc-webhook.c index f8fe711da..415e5de9a 100644 --- a/src/exchange/taler-exchange-httpd_kyc-webhook.c +++ b/src/exchange/taler-exchange-httpd_kyc-webhook.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 @@ -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 */ + 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; - 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, - 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; - } + 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); diff --git a/src/exchangedb/pg_update_kyc_process_by_row.c b/src/exchangedb/pg_update_kyc_process_by_row.c index 4ae44ce53..711f47802 100644 --- a/src/exchangedb/pg_update_kyc_process_by_row.c +++ b/src/exchangedb/pg_update_kyc_process_by_row.c @@ -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; }