diff options
-rwxr-xr-x | bootstrap | 19 | ||||
-rw-r--r-- | contrib/Makefile.am.in | 19 | ||||
-rw-r--r-- | debian/taler-auditor.install | 3 | ||||
-rw-r--r-- | debian/taler-exchange.install | 3 | ||||
-rw-r--r-- | src/exchange/taler-exchange-httpd_kyc-info.c | 641 | ||||
-rw-r--r-- | src/exchange/taler-exchange-httpd_spa.c | 8 |
6 files changed, 675 insertions, 18 deletions
@@ -40,9 +40,26 @@ fi # Generate Makefile.am in contrib/ cd contrib rm -f Makefile.am -find wallet-core/aml-backoffice/ -type f | sort | awk '{print " " $1 " \\" }' > Makefile.am.ext +echo 'dist_amlspapkgdata_DATA = \' > Makefile.am.ext +find wallet-core/aml-backoffice/ -type f | sort | awk '{print " " $1 " \\" }' >> Makefile.am.ext # Remove extra '\' at the end of the file truncate -s -2 Makefile.am.ext + +echo "" >> Makefile.am.ext +echo 'dist_kycspapkgdata_DATA = \' >> Makefile.am.ext +# FIXME: replace aml-backoffice with kyc-ui once ready! +find wallet-core/aml-backoffice/ -type f | sort | awk '{print " " $1 " \\" }' >> Makefile.am.ext +# Remove extra '\' at the end of the file +truncate -s -2 Makefile.am.ext + +echo "" >> Makefile.am.ext +echo 'dist_auditorspapkgdata_DATA = \' >> Makefile.am.ext +# FIXME: replace aml-backoffice with auditor-backoffice once ready! +find wallet-core/auditor-backoffice/ -type f | sort | awk '{print " " $1 " \\" }' >> Makefile.am.ext +# Remove extra '\' at the end of the file +truncate -s -2 Makefile.am.ext + + cat Makefile.am.in Makefile.am.ext >> Makefile.am # Prevent accidental editing of the generated Makefile.am chmod -w Makefile.am diff --git a/contrib/Makefile.am.in b/contrib/Makefile.am.in index d3cfa769c..c6c59357c 100644 --- a/contrib/Makefile.am.in +++ b/contrib/Makefile.am.in @@ -37,10 +37,6 @@ install-exec-local: mkdir -p $(DESTDIR)$(datadir) cp --parents -r $$(find locale/ -name "*.po") $(DESTDIR)$(datadir) -rdatadir=$(datadir)/taler/exchange -rdata_DATA = \ - auditor-report.tex.j2 - bin_SCRIPTS = \ taler-auditor-dbconfig \ taler-exchange-dbconfig \ @@ -60,10 +56,13 @@ EXTRA_DIST = \ microhttpd.tag \ packages -spapkgdatadir = $(prefix)/share/taler/exchange/spa/ +# These are for the various single-page-apps +# imported from the wallet-core.git +# prebuilt branch. These MUST be present, they will +# be used by code generated by 'bootstrap'! + +amlspapkgdatadir = $(prefix)/share/taler/exchange/aml-spa/ + +auditorspapkgdatadir = $(prefix)/share/taler/auditor/spa/ -# This is for the single-page-app imported from the wallet-core.git -# prebuilt branch. This MUST be the last line in the -# Makefile.am.in, as it will be combined with the -# actual SPA data by 'bootstrap'! -dist_spapkgdata_DATA = \ +kycspapkgdatadir = $(prefix)/share/taler/exchange/kyc-spa/ diff --git a/debian/taler-auditor.install b/debian/taler-auditor.install index 4f3d5a1b2..aa69db519 100644 --- a/debian/taler-auditor.install +++ b/debian/taler-auditor.install @@ -17,8 +17,7 @@ usr/share/info/taler-auditor* usr/share/taler/config.d/auditor* usr/share/taler/sql/auditor/* +usr/share/taler/auditor/spa/* # Configuration debian/etc-taler-auditor/* etc/ - -usr/share/taler/exchange/auditor-report.tex.j2 diff --git a/debian/taler-exchange.install b/debian/taler-exchange.install index f8fef2c3b..93cf8b054 100644 --- a/debian/taler-exchange.install +++ b/debian/taler-exchange.install @@ -34,7 +34,8 @@ usr/share/man/man1/taler-exchange-wirewatch* usr/share/info/taler-exchange* usr/share/taler/config.d/* usr/share/taler/exchange/templates/*.must -usr/share/taler/exchange/spa/* +usr/share/taler/exchange/aml-spa/* +usr/share/taler/exchange/kyc-spa/* # configuration files in /etc/taler debian/etc-taler-exchange/* etc/ diff --git a/src/exchange/taler-exchange-httpd_kyc-info.c b/src/exchange/taler-exchange-httpd_kyc-info.c new file mode 100644 index 000000000..974d3b84f --- /dev/null +++ b/src/exchange/taler-exchange-httpd_kyc-info.c @@ -0,0 +1,641 @@ +/* + This file is part of TALER + 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 + 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_kyc-info.c + * @brief Handle request for generic KYC info. + * @author Christian Grothoff + */ +#include "platform.h" +#include <gnunet/gnunet_util_lib.h> +#include <gnunet/gnunet_json_lib.h> +#include <jansson.h> +#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" +#include "taler-exchange-httpd_keys.h" +#include "taler-exchange-httpd_kyc-wallet.h" +#include "taler-exchange-httpd_responses.h" + + +/** + * Reserve GET request that is long-polling. + */ +struct KycPoller +{ + /** + * Kept in a DLL. + */ + struct KycPoller *next; + + /** + * Kept in a DLL. + */ + struct KycPoller *prev; + + /** + * Connection we are handling. + */ + 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; + + /** + * Row of the requirement being infoed. + */ + uint64_t requirement_row; + + /** + * Row of KYC process being initiated. + */ + uint64_t process_row; + + /** + * Hash of the payto:// URI we are confirming to + * have finished the KYC for. + */ + struct TALER_PaytoHashP h_payto; + + /** + * When will this request time out? + */ + struct GNUNET_TIME_Absolute timeout; + + /** + * Public limits that apply to the account, + * if @e have_token is true. + * NULL if no limits are public. + */ + json_t *limits; + + /** + * Set to access token for a KYC process by the account, + * if @e have_token is true. + */ + struct TALER_AccountAccessTokenP access_token; + + /** + * Signature by the account owner authorizing this + * operation. + */ + union TALER_AccountSignatureP account_sig; + + /** + * Set to error details, on error (@ec not TALER_EC_NONE). + */ + char *hint; + + /** + * Set to error encountered with KYC logic, if any. + */ + enum TALER_ErrorCode ec; + + /** + * True if account is under AML review and this is + * exposed. + */ + bool aml_review; + + /** + * True if @e access_token was initialized. + */ + bool have_token; + + /** + * 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; + +}; + + +/** + * Head of list of requests in long polling. + */ +static struct KycPoller *kyp_head; + +/** + * Tail of list of requests in long polling. + */ +static struct KycPoller *kyp_tail; + + +void +TEH_kyc_info_cleanup () +{ + struct KycPoller *kyp; + + while (NULL != (kyp = kyp_head)) + { + 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; + MHD_resume_connection (kyp->connection); + } + } +} + + +/** + * Function called once a connection is done to + * clean up the `struct ReservePoller` state. + * + * @param rc context to clean up for + */ +static void +kyp_cleanup (struct TEH_RequestContext *rc) +{ + struct KycPoller *kyp = rc->rh_ctx; + + GNUNET_assert (! kyp->suspended); + if (NULL != kyp->eh) + { + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Cancelling DB event listening\n"); + TEH_plugin->event_listen_cancel (TEH_plugin->cls, + 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); +} + + +#if FIXME +/** + * 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 (); +} + + +#endif + + +/** + * Function implementing database transaction to info 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 + * transaction logic MUST queue a MHD response and set @a mhd_ret. IF it + * returns the soft error code, the function MAY be called again to retry and + * MUST not queue a MHD response. + * + * @param cls closure with a `struct KycPoller *` + * @param connection MHD request which triggered the transaction + * @param[out] mhd_ret set to MHD response status for @a connection, + * if transaction failed (!) + * @return transaction status + */ +static enum GNUNET_DB_QueryStatus +kyc_info (void *cls, + struct MHD_Connection *connection, + MHD_RESULT *mhd_ret) +{ +#if FIXME + struct KycPoller *kyp = cls; + enum GNUNET_DB_QueryStatus qs; + 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, + &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_INFO_AUTHORIZATION_FAILED, + "h_payto"); + GNUNET_free (requirements); + return GNUNET_DB_STATUS_HARD_ERROR; + } + qs = TALER_KYCLOGIC_info_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) + return qs; + GNUNET_break (0); + *mhd_ret = TALER_MHD_reply_with_error (connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_GENERIC_DB_FETCH_FAILED, + "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->ih_logic, + &pd, + &kyp->section_name); + if (GNUNET_OK != ret) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "KYC requirements `%s' cannot be infoed, 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 info 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; +#else + GNUNET_break (0); + return GNUNET_DB_STATUS_HARD_ERROR; +#endif +} + + +/** + * Function called on events received from Postgres. + * Wakes up long pollers. + * + * @param cls the `struct TEH_RequestContext *` + * @param extra additional event data provided + * @param extra_size number of bytes in @a extra + */ +static void +db_event_cb (void *cls, + const void *extra, + size_t extra_size) +{ + struct TEH_RequestContext *rc = cls; + struct KycPoller *kyp = rc->rh_ctx; + struct GNUNET_AsyncScopeSave old_scope; + + (void) extra; + (void) extra_size; + if (! kyp->suspended) + return; /* event triggered while main transaction + was still running, or got multiple wake-up events */ + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Received KYC update event\n"); + kyp->suspended = false; + GNUNET_async_scope_enter (&rc->async_scope_id, + &old_scope); + TEH_info_invariants (); + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Resuming from long-polling on KYC status\n"); + GNUNET_CONTAINER_DLL_remove (kyp_head, + kyp_tail, + kyp); + MHD_resume_connection (kyp->connection); + TALER_MHD_daemon_trigger (); + TEH_info_invariants (); + GNUNET_async_scope_restore (&old_scope); +} + + +MHD_RESULT +TEH_handler_kyc_info ( + struct TEH_RequestContext *rc, + const char *const args[1]) +{ + struct KycPoller *kyp = rc->rh_ctx; + MHD_RESULT res; + enum GNUNET_GenericReturnValue ret; + + if (NULL == kyp) + { + kyp = GNUNET_new (struct KycPoller); + kyp->connection = rc->connection; + rc->rh_ctx = kyp; + rc->rh_cleaner = &kyp_cleanup; + + { + unsigned long long requirement_row; + char dummy; + + if (1 != + sscanf (args[0], + "%llu%c", + &requirement_row, + &dummy)) + { + GNUNET_break_op (0); + return TALER_MHD_reply_with_error (rc->connection, + MHD_HTTP_BAD_REQUEST, + TALER_EC_GENERIC_PARAMETER_MALFORMED, + "requirement_row"); + } + kyp->requirement_row = (uint64_t) requirement_row; + } + TALER_MHD_parse_request_timeout (rc->connection, + &kyp->timeout); + } + + if ( (NULL == kyp->eh) && + GNUNET_TIME_absolute_is_future (kyp->timeout) ) + { + struct TALER_KycCompletedEventP rep = { + .header.size = htons (sizeof (rep)), + .header.type = htons (TALER_DBEVENT_EXCHANGE_KYC_COMPLETED), + .h_payto = kyp->h_payto + }; + + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Starting DB event listening\n"); + kyp->eh = TEH_plugin->event_listen ( + TEH_plugin->cls, + GNUNET_TIME_absolute_get_remaining (kyp->timeout), + &rep.header, + &db_event_cb, + rc); + } + + ret = TEH_DB_run_transaction (rc->connection, + "kyc info", + TEH_MT_REQUEST_OTHER, + &res, + &kyc_info, + kyp); + if (GNUNET_SYSERR == ret) + { + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Transaction failed.\n"); + return res; + } + + if (TALER_EC_NONE != kyp->ec) + { + return TALER_MHD_reply_with_ec (rc->connection, + kyp->ec, + kyp->hint); + } + + 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 for positive result? */ + if ( (kyp->kyc_required) && + 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); + MHD_suspend_connection (kyp->connection); + return MHD_YES; + } + + /* KYC plugin generated reply? */ + if (kyp->have_token) + { + return TALER_MHD_REPLY_JSON_PACK ( + rc->connection, + kyp->kyc_required + ? MHD_HTTP_ACCEPTED + : MHD_HTTP_OK, + GNUNET_JSON_pack_bool ("aml_review", + kyp->aml_review), + GNUNET_JSON_pack_data_auto ("access_token", + &kyp->access_token), + GNUNET_JSON_pack_allow_null ( + GNUNET_JSON_pack_array_steal ("limits", + kyp->limits))); + } + + /* 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); +} + + +/* end of taler-exchange-httpd_kyc-info.c */ diff --git a/src/exchange/taler-exchange-httpd_spa.c b/src/exchange/taler-exchange-httpd_spa.c index 60bed3d28..e6b672c16 100644 --- a/src/exchange/taler-exchange-httpd_spa.c +++ b/src/exchange/taler-exchange-httpd_spa.c @@ -15,7 +15,7 @@ */ /** * @file taler-exchange-httpd_spa.c - * @brief logic to load the single page app (/) + * @brief logic to load single page apps (/) * @author Christian Grothoff */ #include "platform.h" @@ -60,12 +60,12 @@ struct WebuiFile /** - * Resources of the WebuUI, kept in a DLL. + * Resources of the WebUI, kept in a DLL. */ static struct WebuiFile *webui_head; /** - * Resources of the WebuUI, kept in a DLL. + * Resources of the WebUI, kept in a DLL. */ static struct WebuiFile *webui_tail; @@ -312,7 +312,7 @@ TEH_spa_init () return GNUNET_SYSERR; } GNUNET_asprintf (&dn, - "%sexchange/spa/", + "%sexchange/aml-spa/", path); GNUNET_free (path); } |