diff options
author | Christian Grothoff <christian@grothoff.org> | 2024-06-04 14:24:18 +0200 |
---|---|---|
committer | Christian Grothoff <christian@grothoff.org> | 2024-07-29 12:18:42 +0200 |
commit | 8d1e83097d360916c07552b7765339727760e2a8 (patch) | |
tree | b3bd2c50b2246400adf4d36a8b4339d88ba6e1b7 | |
parent | 9218606c55d80fcdd673fbacdb5b73a32793b6b3 (diff) |
work on kyc-info endpoint
-rw-r--r-- | src/exchange/Makefile.am | 1 | ||||
-rw-r--r-- | src/exchange/taler-exchange-httpd_kyc-info.c | 623 | ||||
-rw-r--r-- | src/exchange/taler-exchange-httpd_kyc-start.c | 641 | ||||
-rw-r--r-- | src/exchange/taler-exchange-httpd_reserves_open.c | 2 | ||||
-rw-r--r-- | src/exchangedb/Makefile.am | 1 | ||||
-rw-r--r-- | src/exchangedb/pg_insert_aml_decision.c | 10 | ||||
-rw-r--r-- | src/exchangedb/pg_insert_kyc_attributes.c | 7 | ||||
-rw-r--r-- | src/exchangedb/pg_insert_kyc_failure.c | 4 | ||||
-rw-r--r-- | src/exchangedb/pg_lookup_kyc_status_by_token.c | 65 | ||||
-rw-r--r-- | src/exchangedb/pg_lookup_kyc_status_by_token.h | 46 | ||||
-rw-r--r-- | src/exchangedb/pg_lookup_wire_fee_by_time.c | 2 | ||||
-rw-r--r-- | src/exchangedb/pg_update_kyc_process_by_row.c | 6 | ||||
-rw-r--r-- | src/exchangedb/plugin_exchangedb_postgres.c | 4 | ||||
-rw-r--r-- | src/include/taler_exchangedb_plugin.h | 21 | ||||
-rw-r--r-- | src/include/taler_kyclogic_lib.h | 36 | ||||
-rw-r--r-- | src/kyclogic/kyclogic_api.c | 138 | ||||
-rw-r--r-- | src/kyclogic/taler-exchange-kyc-tester.c | 6 |
17 files changed, 1174 insertions, 439 deletions
diff --git a/src/exchange/Makefile.am b/src/exchange/Makefile.am index 2f5d088d8..eebe97d2f 100644 --- a/src/exchange/Makefile.am +++ b/src/exchange/Makefile.am @@ -142,6 +142,7 @@ taler_exchange_httpd_SOURCES = \ taler-exchange-httpd_extensions.c taler-exchange-httpd_extensions.h \ taler-exchange-httpd_keys.c taler-exchange-httpd_keys.h \ taler-exchange-httpd_kyc-check.c taler-exchange-httpd_kyc-check.h \ + taler-exchange-httpd_kyc-info.c taler-exchange-httpd_kyc-info.h \ taler-exchange-httpd_kyc-proof.c taler-exchange-httpd_kyc-proof.h \ taler-exchange-httpd_kyc-wallet.c taler-exchange-httpd_kyc-wallet.h \ taler-exchange-httpd_kyc-webhook.c taler-exchange-httpd_kyc-webhook.h \ diff --git a/src/exchange/taler-exchange-httpd_kyc-info.c b/src/exchange/taler-exchange-httpd_kyc-info.c index 974d3b84f..5f5b95da8 100644 --- a/src/exchange/taler-exchange-httpd_kyc-info.c +++ b/src/exchange/taler-exchange-httpd_kyc-info.c @@ -1,6 +1,6 @@ /* This file is part of TALER - Copyright (C) 2021-2023 Taler Systems SA + Copyright (C) 2021-2024 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 @@ -55,37 +55,16 @@ 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; /** - * 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. + * #MHD_HTTP_HEADER_IF_NONE_MATCH Etag value sent by the client. 0 for none + * (or malformed). */ - struct TALER_PaytoHashP h_payto; + uint64_t etag_in; /** * When will this request time out? @@ -93,60 +72,16 @@ struct KycPoller 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; - }; @@ -171,11 +106,6 @@ TEH_kyc_info_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; @@ -205,261 +135,10 @@ 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); } -#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. @@ -482,24 +161,147 @@ db_event_cb (void *cls, 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); + kyp->suspended = false; MHD_resume_connection (kyp->connection); TALER_MHD_daemon_trigger (); - TEH_info_invariants (); GNUNET_async_scope_restore (&old_scope); } +/** + * Generate a reply with the KycProcessClientInformation from + * the LegitimizationMeasures. + * + * @param[in,out] kyp request to reply on + * @param row_id etag to set for the response + * @param jmeasures measures to encode + * @return MHD status code + */ +static MHD_RESULT +generate_reply (struct KycPoller *kyp, + uint64_t row_id, + const json_t *jmeasures) +{ + const json_t *mis; + bool is_and_combinator = false; + bool verboten; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_bool ("is_and_combinator", + &is_and_combinator), + NULL), + GNUNET_JSON_spec_bool ("verboten", + &verboten), + GNUNET_JSON_spec_array_const ("measures", + &mis), + GNUNET_JSON_spec_end () + }; + enum GNUNET_GenericReturnValue res; + const char *ename; + unsigned int eline; + json_t *kris; + size_t i; + json_t *mi; + + res = GNUNET_JSON_parse (jmeasures, + spec, + &ename, + &eline); + if (GNUNET_OK != res) + { + GNUNET_break (0); + return TALER_MHD_reply_with_ec ( + kyp->connection, + TALER_EC_GENERIC_DB_INVARIANT_FAILURE, + ename); + } + kris = json_array (); + GNUNET_assert (NULL != kris); + json_array_foreach ((json_t *) mis, i, mi) + { + const char *check_name; + const char *prog_name; + struct GNUNET_JSON_Specification ispec[] = { + GNUNET_JSON_spec_string ("check_name", + &check_name), + GNUNET_JSON_spec_string ("prog_name", + &prog_name), + GNUNET_JSON_spec_end () + }; + json_t *kri; + + res = GNUNET_JSON_parse (mi, + ispec, + &ename, + &eline); + if (GNUNET_OK != res) + { + GNUNET_break (0); + json_decref (kris); + return TALER_MHD_reply_with_ec ( + kyp->connection, + TALER_EC_GENERIC_DB_INVARIANT_FAILURE, + ename); + } + kri = TALER_KYCLOGIC_measure_to_requirement ( + check_name, + prog_name, + &kyp->access_token, + i, + row_id); + if (NULL == kri) + { + GNUNET_break (0); + json_decref (kris); + return TALER_MHD_reply_with_ec ( + kyp->connection, + TALER_EC_GENERIC_DB_INVARIANT_FAILURE, + "could not convert measure to requirement"); + } + GNUNET_assert (0 == + json_array_append_new (kris, + kri)); + } + + { + char etags[64]; + struct MHD_Response *resp; + MHD_RESULT res; + + GNUNET_snprintf (etags, + sizeof (etags), + "%llu", + (unsigned long long) row_id); + resp = TALER_MHD_MAKE_JSON_PACK ( + GNUNET_JSON_pack_array_steal ("requirements", + kris), + GNUNET_JSON_pack_bool ("is_and_combinator", + is_and_combinator), + GNUNET_JSON_pack_allow_null ( + /* TODO: support vATTEST */ + GNUNET_JSON_pack_object_steal ("voluntary_checks", + NULL))); + GNUNET_break (MHD_YES == + MHD_add_response_header (resp, + MHD_HTTP_HEADER_ETAG, + etags)); + res = MHD_queue_response (kyp->connection, + MHD_HTTP_OK, + resp); + GNUNET_break (MHD_YES == res); + MHD_destroy_response (resp); + return res; + } +} + + MHD_RESULT TEH_handler_kyc_info ( struct TEH_RequestContext *rc, @@ -507,7 +309,9 @@ TEH_handler_kyc_info ( { struct KycPoller *kyp = rc->rh_ctx; MHD_RESULT res; - enum GNUNET_GenericReturnValue ret; + enum GNUNET_DB_QueryStatus qs; + uint64_t last_row; + json_t *jmeasures; if (NULL == kyp) { @@ -516,27 +320,52 @@ TEH_handler_kyc_info ( rc->rh_ctx = kyp; rc->rh_cleaner = &kyp_cleanup; + if (GNUNET_OK != + GNUNET_STRINGS_string_to_data ( + args[0], + strlen (args[0]), + &kyp->access_token, + sizeof (kyp->access_token))) { - 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; + GNUNET_break_op (0); + return TALER_MHD_reply_with_error ( + rc->connection, + MHD_HTTP_BAD_REQUEST, + TALER_EC_GENERIC_PARAMETER_MALFORMED, + "access token"); } TALER_MHD_parse_request_timeout (rc->connection, &kyp->timeout); - } + + /* Get etag */ + { + const char *etags; + + etags = MHD_lookup_connection_value ( + rc->connection, + MHD_HEADER_KIND, + MHD_HTTP_HEADER_IF_NONE_MATCH); + if (NULL != etags) + { + char dummy; + unsigned long long ev; + + if (1 != sscanf (etags, + "\"%llu\"%c", + &ev, + &dummy)) + { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Client send malformed `If-None-Match' header `%s'\n", + etags); + } + else + { + kyp->etag_in = (uint64_t) ev; + } + } + } /* etag */ + } /* one-time initialization */ if ( (NULL == kyp->eh) && GNUNET_TIME_absolute_is_future (kyp->timeout) ) @@ -544,7 +373,7 @@ TEH_handler_kyc_info ( struct TALER_KycCompletedEventP rep = { .header.size = htons (sizeof (rep)), .header.type = htons (TALER_DBEVENT_EXCHANGE_KYC_COMPLETED), - .h_payto = kyp->h_payto + .access_token = kyp->access_token }; GNUNET_log (GNUNET_ERROR_TYPE_INFO, @@ -557,84 +386,54 @@ TEH_handler_kyc_info ( 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) + qs = TEH_plugin->lookup_kyc_status_by_token ( + TEH_plugin->cls, + &kyp->access_token, + &last_row, + &jmeasures); + if (qs < 0) { - return TALER_MHD_reply_with_ec (rc->connection, - kyp->ec, - kyp->hint); + GNUNET_break (0); + return TALER_MHD_reply_with_ec ( + rc->connection, + TALER_EC_GENERIC_DB_FETCH_FAILED, + "lookup_kyc_status_by_token"); } - - if (NULL != kyp->ih) + if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) { 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; + "No KYC requirement open\n"); + return TALER_MHD_REPLY_JSON_PACK ( + rc->connection, + MHD_HTTP_OK, + GNUNET_JSON_pack_allow_null ( + /* TODO: support vATTEST */ + GNUNET_JSON_pack_object_steal ("voluntary_checks", + NULL))); } - - /* long polling for positive result? */ - if ( (kyp->kyc_required) && - GNUNET_TIME_absolute_is_future (kyp->timeout)) + if ( (last_row == kyp->etag_in) && + 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)); + "Suspending HTTP request on timeout (%s)\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); + MHD_suspend_connection (rc->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); + res = generate_reply (kyp, + last_row, + jmeasures); + json_decref (jmeasures); + return res; } diff --git a/src/exchange/taler-exchange-httpd_kyc-start.c b/src/exchange/taler-exchange-httpd_kyc-start.c new file mode 100644 index 000000000..54870b52c --- /dev/null +++ b/src/exchange/taler-exchange-httpd_kyc-start.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-start.c + * @brief Handle request for starting a KYC process with an external provider. + * @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 started. + */ + 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_start_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_START, + "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_START, + "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 start 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_start (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_START, + "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_START_AUTHORIZATION_FAILED, + "h_payto"); + GNUNET_free (requirements); + return GNUNET_DB_STATUS_HARD_ERROR; + } + qs = TALER_KYCLOGIC_start_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_START, + "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 started, 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_START, + "Initiating KYC start 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_START, + "Received KYC update event\n"); + kyp->suspended = false; + GNUNET_async_scope_enter (&rc->async_scope_id, + &old_scope); + TEH_start_invariants (); + GNUNET_log (GNUNET_ERROR_TYPE_START, + "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_start_invariants (); + GNUNET_async_scope_restore (&old_scope); +} + + +MHD_RESULT +TEH_handler_kyc_start ( + 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_START, + "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 start", + TEH_MT_REQUEST_OTHER, + &res, + &kyc_start, + kyp); + if (GNUNET_SYSERR == ret) + { + GNUNET_log (GNUNET_ERROR_TYPE_START, + "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_START, + "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_START, + "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_START, + "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-start.c */ diff --git a/src/exchange/taler-exchange-httpd_reserves_open.c b/src/exchange/taler-exchange-httpd_reserves_open.c index 5aadc9e40..abc26a7e1 100644 --- a/src/exchange/taler-exchange-httpd_reserves_open.c +++ b/src/exchange/taler-exchange-httpd_reserves_open.c @@ -35,7 +35,7 @@ * checking the request timestamp? */ #define TIMESTAMP_TOLERANCE \ - GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MINUTES, 15) + GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MINUTES, 15) /** diff --git a/src/exchangedb/Makefile.am b/src/exchangedb/Makefile.am index f846370cd..270171456 100644 --- a/src/exchangedb/Makefile.am +++ b/src/exchangedb/Makefile.am @@ -193,6 +193,7 @@ libtaler_plugin_exchangedb_postgres_la_SOURCES = \ pg_start_deferred_wire_out.h pg_start_deferred_wire_out.c \ pg_store_wire_transfer_out.h pg_store_wire_transfer_out.c \ pg_gc.h pg_gc.c \ + pg_lookup_kyc_status_by_token.h pg_lookup_kyc_status_by_token.c \ pg_select_coin_deposits_above_serial_id.h pg_select_coin_deposits_above_serial_id.c \ pg_select_purse_decisions_above_serial_id.h pg_select_purse_decisions_above_serial_id.c \ pg_select_purse_deposits_by_purse.h pg_select_purse_deposits_by_purse.c \ diff --git a/src/exchangedb/pg_insert_aml_decision.c b/src/exchangedb/pg_insert_aml_decision.c index b0edceefa..d12f34af4 100644 --- a/src/exchangedb/pg_insert_aml_decision.c +++ b/src/exchangedb/pg_insert_aml_decision.c @@ -43,12 +43,20 @@ TEH_PG_insert_aml_decision ( struct GNUNET_TIME_Timestamp *last_date) { struct PostgresClosure *pg = cls; +#if FIXME + /* We used to do h_payto, now we need the + account access token! */ struct TALER_KycCompletedEventP rep = { .header.size = htons (sizeof (rep)), .header.type = htons (TALER_DBEVENT_EXCHANGE_KYC_COMPLETED), .h_payto = *h_payto }; - char *notify_s = GNUNET_PQ_get_event_notify_channel (&rep.header); + char *notify_s + = GNUNET_PQ_get_event_notify_channel (&rep.header); +#else + char *notify_s + = GNUNET_strdup ("FIXME"); +#endif struct GNUNET_PQ_QueryParam params[] = { GNUNET_PQ_query_param_auto_from_type (h_payto), GNUNET_PQ_query_param_timestamp (&decision_time), diff --git a/src/exchangedb/pg_insert_kyc_attributes.c b/src/exchangedb/pg_insert_kyc_attributes.c index 45d53347a..a72e4e67b 100644 --- a/src/exchangedb/pg_insert_kyc_attributes.c +++ b/src/exchangedb/pg_insert_kyc_attributes.c @@ -44,6 +44,9 @@ TEH_PG_insert_kyc_attributes ( struct PostgresClosure *pg = cls; struct GNUNET_TIME_Timestamp expiration = GNUNET_TIME_absolute_to_timestamp (expiration_time); +#if FIXME + /* We used to do h_payto, now we need the + account access token! */ struct TALER_KycCompletedEventP rep = { .header.size = htons (sizeof (rep)), .header.type = htons (TALER_DBEVENT_EXCHANGE_KYC_COMPLETED), @@ -51,6 +54,10 @@ TEH_PG_insert_kyc_attributes ( }; char *kyc_completed_notify_s = GNUNET_PQ_get_event_notify_channel (&rep.header); +#else + char *kyc_completed_notify_s + = GNUNET_strdup ("FIXME"); +#endif struct GNUNET_PQ_QueryParam params[] = { GNUNET_PQ_query_param_uint64 (&process_row), GNUNET_PQ_query_param_auto_from_type (h_payto), diff --git a/src/exchangedb/pg_insert_kyc_failure.c b/src/exchangedb/pg_insert_kyc_failure.c index c5d1a7085..903c717ad 100644 --- a/src/exchangedb/pg_insert_kyc_failure.c +++ b/src/exchangedb/pg_insert_kyc_failure.c @@ -67,6 +67,9 @@ TEH_PG_insert_kyc_failure ( if (qs > 0) { /* FIXME: might want to do this eventually in the same transaction... */ +#if FIXME + /* We used to do h_payto, now we need the + account access token! */ struct TALER_KycCompletedEventP rep = { .header.size = htons (sizeof (rep)), .header.type = htons (TALER_DBEVENT_EXCHANGE_KYC_COMPLETED), @@ -77,6 +80,7 @@ TEH_PG_insert_kyc_failure ( &rep.header, NULL, 0); +#endif } return qs; } diff --git a/src/exchangedb/pg_lookup_kyc_status_by_token.c b/src/exchangedb/pg_lookup_kyc_status_by_token.c new file mode 100644 index 000000000..6d3cc4bec --- /dev/null +++ b/src/exchangedb/pg_lookup_kyc_status_by_token.c @@ -0,0 +1,65 @@ +/* + This file is part of TALER + Copyright (C) 2024 Taler Systems SA + + TALER is free software; you can redistribute it and/or modify it under the + terms of the GNU 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 General Public License for more details. + + You should have received a copy of the GNU General Public License along with + TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + */ +/** + * @file exchangedb/pg_lookup_kyc_status_by_token.c + * @brief Implementation of the lookup_kyc_status_by_token function for Postgres + * @author Christian Grothoff + */ +#include "platform.h" +#include "taler_error_codes.h" +#include "taler_dbevents.h" +#include "taler_pq_lib.h" +#include "pg_lookup_kyc_status_by_token.h" +#include "pg_helper.h" + + +enum GNUNET_DB_QueryStatus +TEH_PG_lookup_kyc_status_by_token ( + void *cls, + const struct TALER_AccountAccessTokenP *access_token, + uint64_t *row, + json_t **jmeasures) +{ + struct PostgresClosure *pg = cls; + struct GNUNET_PQ_QueryParam params[] = { + GNUNET_PQ_query_param_auto_from_type (access_token), + GNUNET_PQ_query_param_end + }; + struct GNUNET_PQ_ResultSpec rs[] = { + GNUNET_PQ_result_spec_uint64 ( + "legitimization_measure_serial_id", + row), + TALER_PQ_result_spec_json ( + "jmeasures", + jmeasures), + GNUNET_PQ_result_spec_end + }; + + PREPARE (pg, + "lookup_kyc_status_by_token", + "SELECT" + " legitimization_measure_serial_id" + ",jmeasures" + " FROM legitimization_measures" + " WHERE access_token=$1" + " AND NOT is_finished" + " ORDER BY display_priority DESC" + " LIMIT 1;"); + return GNUNET_PQ_eval_prepared_singleton_select (pg->conn, + "lookup_kyc_status_by_token", + params, + rs); +} diff --git a/src/exchangedb/pg_lookup_kyc_status_by_token.h b/src/exchangedb/pg_lookup_kyc_status_by_token.h new file mode 100644 index 000000000..77c845228 --- /dev/null +++ b/src/exchangedb/pg_lookup_kyc_status_by_token.h @@ -0,0 +1,46 @@ +/* + This file is part of TALER + Copyright (C) 2024 Taler Systems SA + + TALER is free software; you can redistribute it and/or modify it under the + terms of the GNU 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 General Public License for more details. + + You should have received a copy of the GNU General Public License along with + TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + */ +/** + * @file exchangedb/pg_lookup_kyc_status_by_token.h + * @brief implementation of the lookup_kyc_status_by_token function for Postgres + * @author Christian Grothoff + */ +#ifndef PG_LOOKUP_KYC_STATUS_BY_TOKEN_H +#define PG_LOOKUP_KYC_STATUS_BY_TOKEN_H + +#include "taler_util.h" +#include "taler_json_lib.h" +#include "taler_exchangedb_plugin.h" + + +/** + * Lookup KYC status by account access token. + * + * @param cls closure + * @param access_token key to look under + * @param[out] row set to requirement row that matches + * @param[out] jmeasures set to the LegitimizationMeasures for the @a access_token; must be freed by caller! + * @return database transaction status + */ +enum GNUNET_DB_QueryStatus +TEH_PG_lookup_kyc_status_by_token ( + void *cls, + const struct TALER_AccountAccessTokenP *access_token, + uint64_t *row, + json_t **jmeasures); + + +#endif diff --git a/src/exchangedb/pg_lookup_wire_fee_by_time.c b/src/exchangedb/pg_lookup_wire_fee_by_time.c index 775232a48..1f136b135 100644 --- a/src/exchangedb/pg_lookup_wire_fee_by_time.c +++ b/src/exchangedb/pg_lookup_wire_fee_by_time.c @@ -138,7 +138,7 @@ TEH_PG_lookup_wire_fee_by_time ( .fees = fees, .pg = pg }; - /* used in #postgres_lookup_wire_fee_by_time() */ + PREPARE (pg, "lookup_wire_fee_by_time", "SELECT" diff --git a/src/exchangedb/pg_update_kyc_process_by_row.c b/src/exchangedb/pg_update_kyc_process_by_row.c index 448ac2154..2f6724810 100644 --- a/src/exchangedb/pg_update_kyc_process_by_row.c +++ b/src/exchangedb/pg_update_kyc_process_by_row.c @@ -86,11 +86,13 @@ TEH_PG_update_kyc_process_by_row ( if (GNUNET_TIME_absolute_is_future (expiration)) { enum GNUNET_DB_QueryStatus qs2; +#if FIXME struct TALER_KycCompletedEventP rep = { .header.size = htons (sizeof (rep)), .header.type = htons (TALER_DBEVENT_EXCHANGE_KYC_COMPLETED), .h_payto = *h_payto }; +#endif uint32_t trigger_type = 1; struct GNUNET_PQ_QueryParam params2[] = { GNUNET_PQ_query_param_auto_from_type (h_payto), @@ -98,10 +100,14 @@ TEH_PG_update_kyc_process_by_row ( GNUNET_PQ_query_param_end }; +#if FIXME + /* We used to do h_payto, now we need the + account access token! */ GNUNET_PQ_event_notify (pg->conn, &rep.header, NULL, 0); +#endif PREPARE (pg, "alert_kyc_status_change", "INSERT INTO kyc_alerts" diff --git a/src/exchangedb/plugin_exchangedb_postgres.c b/src/exchangedb/plugin_exchangedb_postgres.c index d26db2029..897d09bef 100644 --- a/src/exchangedb/plugin_exchangedb_postgres.c +++ b/src/exchangedb/plugin_exchangedb_postgres.c @@ -50,6 +50,7 @@ #include "pg_iterate_kyc_reference.h" #include "pg_iterate_reserve_close_info.h" #include "pg_lookup_records_by_table.h" +#include "pg_lookup_kyc_status_by_token.h" #include "pg_lookup_serial_by_table.h" #include "pg_select_account_merges_above_serial_id.h" #include "pg_select_all_purse_decisions_above_serial_id.h" @@ -782,7 +783,8 @@ libtaler_plugin_exchangedb_postgres_init (void *cls) = &TEH_PG_lookup_kyc_requirement_by_row; plugin->trigger_kyc_rule_for_account = &TEH_PG_trigger_kyc_rule_for_account; - + plugin->lookup_kyc_status_by_token + = &TEH_PG_lookup_kyc_status_by_token; plugin->batch_ensure_coin_known = &TEH_PG_batch_ensure_coin_known; plugin->inject_auditor_triggers diff --git a/src/include/taler_exchangedb_plugin.h b/src/include/taler_exchangedb_plugin.h index 067f925b1..541dc23ed 100644 --- a/src/include/taler_exchangedb_plugin.h +++ b/src/include/taler_exchangedb_plugin.h @@ -238,9 +238,9 @@ struct TALER_KycCompletedEventP struct GNUNET_DB_EventHeaderP header; /** - * Public key of the reserve the event is about. + * Access token the KYC was completed for. */ - struct TALER_PaytoHashP h_payto; + struct TALER_AccountAccessTokenP access_token; }; @@ -6828,6 +6828,23 @@ struct TALER_EXCHANGEDB_Plugin /** + * Lookup KYC status by account access token. + * + * @param cls closure + * @param access_token key to look under + * @param[out] row set to requirement row that matches + * @param[out] jmeasures set to the LegitimizationMeasures for the @a access_token; must be freed by caller! + * @return database transaction status + */ + enum GNUNET_DB_QueryStatus + (*lookup_kyc_status_by_token)( + void *cls, + const struct TALER_AccountAccessTokenP *access_token, + uint64_t *row, + json_t **jmeasures); + + + /** * Lookup KYC process meta data. * * @param cls closure diff --git a/src/include/taler_kyclogic_lib.h b/src/include/taler_kyclogic_lib.h index 86812f001..b9c677235 100644 --- a/src/include/taler_kyclogic_lib.h +++ b/src/include/taler_kyclogic_lib.h @@ -73,22 +73,22 @@ enum TALER_KYCLOGIC_KycTriggerEvent /** * Types of KYC checks. */ -enum CheckType +enum TALER_KYCLOGIC_CheckType { /** * Wait for staff or contact staff out-of-band. */ - CT_INFO, + TALER_KYCLOGIC_CT_INFO, /** * SPA should show an inline form. */ - CT_FORM, + TALER_KYCLOGIC_CT_FORM, /** * SPA may start external KYC process. */ - CT_LINK + TALER_KYCLOGIC_CT_LINK }; @@ -156,7 +156,7 @@ struct TALER_KYCLOGIC_KycCheck /** * Type of the KYC check. */ - enum CheckType type; + enum TALER_KYCLOGIC_CheckType type; /** * Details depending on @e type. @@ -165,7 +165,7 @@ struct TALER_KYCLOGIC_KycCheck { /** - * Fields present only if @e type is #CT_FORM. + * Fields present only if @e type is #TALER_KYCLOGIC_CT_FORM. */ struct { @@ -178,7 +178,7 @@ struct TALER_KYCLOGIC_KycCheck } form; /** - * Fields present only if @e type is CT_LINK. + * Fields present only if @e type is TALER_KYCLOGIC_CT_LINK. */ struct { @@ -411,14 +411,32 @@ TALER_KYCLOGIC_rule_to_measures (const struct TALER_KYCLOGIC_KycRule *r); * NULL to use default rules * @return set to JSON array with public limits * of type ``AccountLimit`` - * - * FIXME: not implemented! */ json_t * TALER_KYCLOGIC_rules_to_limits (const json_t *jrules); /** + * Convert MeasureInformation into the + * KycRequirementInformation used by the client. + * + * @param check_name the prescribed check + * @param prog_name the program to run + * @param access_token access token for the measure + * @param offset offset of the measure + * @param row_id row in the legitimization_measures table + * @return JSON object with matching KycRequirementInformation + */ +json_t * +TALER_KYCLOGIC_measure_to_requirement ( + const char *check_name, + const char *prog_name, + const struct TALER_AccountAccessTokenP *access_token, + size_t offset, + uint64_t row_id); + + +/** * Extract logic data from a KYC @a provider. * * @param provider provider to get logic data from diff --git a/src/kyclogic/kyclogic_api.c b/src/kyclogic/kyclogic_api.c index 96d3afad5..c4faead6c 100644 --- a/src/kyclogic/kyclogic_api.c +++ b/src/kyclogic/kyclogic_api.c @@ -301,6 +301,31 @@ static struct TALER_KYCLOGIC_AmlProgram **aml_programs; static unsigned int num_aml_programs; +/** + * Lookup a KYC check by @a check_name + * + * @param check_name name to search for + * @return NULL if not found + */ +static struct TALER_KYCLOGIC_KycCheck * +find_check (const char *check_name) +{ + for (unsigned int i = 0; i<num_kyc_checks; i++) + { + struct TALER_KYCLOGIC_KycCheck *kyc_check + = kyc_checks[i]; + + if (0 == strcmp (check_name, + kyc_check->check_name)) + return kyc_check; + } + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Check `%s' unknown\n", + check_name); + return NULL; +} + + struct TALER_KYCLOGIC_LegitimizationRuleSet * TALER_KYCLOGIC_rules_parse (const json_t *jlrs) { @@ -823,16 +848,16 @@ command_output (const char *command, static enum GNUNET_GenericReturnValue check_type_from_string ( const char *ctype_s, - enum CheckType *ctype) + enum TALER_KYCLOGIC_CheckType *ctype) { struct { const char *in; - enum CheckType out; + enum TALER_KYCLOGIC_CheckType out; } map [] = { - { "INFO", CT_INFO }, - { "LINK", CT_LINK }, - { "FORM", CT_FORM }, + { "INFO", TALER_KYCLOGIC_CT_INFO }, + { "LINK", TALER_KYCLOGIC_CT_LINK }, + { "FORM", TALER_KYCLOGIC_CT_FORM }, { NULL, 0 } }; @@ -1160,7 +1185,7 @@ add_check (const struct GNUNET_CONFIGURATION_Handle *cfg, const char *section) { bool voluntary; - enum CheckType ct; + enum TALER_KYCLOGIC_CheckType ct; char *form_name = NULL; char *description = NULL; json_t *description_i18n = NULL; @@ -1781,12 +1806,12 @@ TALER_KYCLOGIC_kyc_done (void) 0); switch (kc->type) { - case CT_INFO: + case TALER_KYCLOGIC_CT_INFO: break; - case CT_FORM: + case TALER_KYCLOGIC_CT_FORM: GNUNET_free (kc->details.form.name); break; - case CT_LINK: + case TALER_KYCLOGIC_CT_LINK: break; } GNUNET_free (kc); @@ -2082,4 +2107,99 @@ TALER_KYCLOGIC_kyc_test_required ( } +json_t * +TALER_KYCLOGIC_measure_to_requirement ( + const char *check_name, + const char *prog_name, + const struct TALER_AccountAccessTokenP *access_token, + size_t offset, + uint64_t row_id) +{ + struct TALER_KYCLOGIC_KycCheck *kc; + json_t *kri; + struct GNUNET_ShortHashCode shv; + uint64_t be = GNUNET_htonll (row_id); + uint32_t be32 = htonl ((uint32_t) offset); + char *ids; + char *xids; + + GNUNET_assert (offset <= UINT_MAX); + GNUNET_assert (offset <= UINT32_MAX); + kc = find_check (check_name); + if (NULL == kc) + { + GNUNET_break (0); + return NULL; + } + /* FIXME: should be moved to someplace + in util/crypto as the $ID-handlers + need exactly the same computation! */ + GNUNET_assert ( + GNUNET_YES == + GNUNET_CRYPTO_kdf (&shv, + sizeof (shv), + &be, + sizeof (be), + access_token, + sizeof (*access_token), + &be32, + sizeof (be32), + NULL, + 0)); + switch (kc->type) + { + case TALER_KYCLOGIC_CT_INFO: + return GNUNET_JSON_PACK ( + GNUNET_JSON_pack_string ("form", + "INFO"), + GNUNET_JSON_pack_string ("description", + kc->description), + GNUNET_JSON_pack_object_incref ("description_i18n", + (json_t *) kc->description_i18n)); + case TALER_KYCLOGIC_CT_FORM: + ids = GNUNET_STRINGS_data_to_string_alloc (&shv, + sizeof (shv)); + GNUNET_asprintf (&xids, + "%llu/%u/%s", + (unsigned long long) row_id, + (unsigned int) offset, + ids); + GNUNET_free (ids); + kri = GNUNET_JSON_PACK ( + GNUNET_JSON_pack_string ("form", + kc->details.form.name), + GNUNET_JSON_pack_string ("id", + xids), + GNUNET_JSON_pack_string ("description", + kc->description), + GNUNET_JSON_pack_object_steal ("description_i18n", + (json_t *) kc->description_i18n)); + GNUNET_free (xids); + return kri; + case TALER_KYCLOGIC_CT_LINK: + ids = GNUNET_STRINGS_data_to_string_alloc (&shv, + sizeof (shv)); + GNUNET_asprintf (&xids, + "%llu/%u/%s", + (unsigned long long) row_id, + (unsigned int) offset, + ids); + GNUNET_free (ids); + kri = GNUNET_JSON_PACK ( + GNUNET_JSON_pack_string ("form", + "LINK"), + GNUNET_JSON_pack_string ("id", + xids), + GNUNET_JSON_pack_string ("description", + kc->description), + GNUNET_JSON_pack_object_steal ("description_i18n", + (json_t *) kc->description_i18n)); + GNUNET_free (xids); + return kri; + } + GNUNET_break (0); /* invalid type */ + return NULL; +} + + /* end of kyclogic_api.c */ diff --git a/src/kyclogic/taler-exchange-kyc-tester.c b/src/kyclogic/taler-exchange-kyc-tester.c index ae357c953..51c19efb1 100644 --- a/src/kyclogic/taler-exchange-kyc-tester.c +++ b/src/kyclogic/taler-exchange-kyc-tester.c @@ -1638,18 +1638,18 @@ run (void *cls, } switch (kcc.check->type) { - case CT_INFO: + case TALER_KYCLOGIC_CT_INFO: GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE, "KYC information is `%s'\n", kcc.check->description); break; - case CT_FORM: + case TALER_KYCLOGIC_CT_FORM: GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE, "Would initiate KYC check `%s' with form `%s'\n", kcc.check->check_name, kcc.check->details.form.name); break; - case CT_LINK: + case TALER_KYCLOGIC_CT_LINK: { struct TALER_KYCLOGIC_ProviderDetails *pd; const char *provider_name; |