diff options
Diffstat (limited to 'src/exchange/taler-exchange-httpd_kyc-check.c')
-rw-r--r-- | src/exchange/taler-exchange-httpd_kyc-check.c | 477 |
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)); } |