/* This file is part of TALER Copyright (C) 2021 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_kyc-check.c * @brief Handle request for generic KYC check. * @author Christian Grothoff */ #include "platform.h" #include #include #include #include #include #include "taler_json_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; /** * Subscription for the database event we are * waiting for. */ struct GNUNET_DB_EventHandler *eh; /** * UUID being checked. */ uint64_t payment_target_uuid; /** * Current KYC status. */ struct TALER_EXCHANGEDB_KycStatus kyc; /** * Hash of the payto:// URI we are confirming to * have finished the KYC for. */ struct TALER_PaytoHash h_payto; /** * Hash of the payto:// URI that was given to us for auth. */ struct TALER_PaytoHash auth_h_payto; /** * When will this request time out? */ struct GNUNET_TIME_Absolute timeout; /** * True if we are still suspended. */ bool suspended; }; /** * 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_check_cleanup () { struct KycPoller *kyp; while (NULL != (kyp = kyp_head)) { GNUNET_CONTAINER_DLL_remove (kyp_head, kyp_tail, kyp); 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; } GNUNET_free (kyp); } /** * 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 * 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_check (void *cls, struct MHD_Connection *connection, MHD_RESULT *mhd_ret) { struct KycPoller *kyp = cls; enum GNUNET_DB_QueryStatus qs; qs = TEH_plugin->select_kyc_status (TEH_plugin->cls, kyp->payment_target_uuid, &kyp->h_payto, &kyp->kyc); 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, "inselect_wallet_status"); return qs; } return qs; } /** * 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 */ kyp->suspended = false; GNUNET_async_scope_enter (&rc->async_scope_id, &old_scope); 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 (); GNUNET_async_scope_restore (&old_scope); } MHD_RESULT TEH_handler_kyc_check ( struct TEH_RequestContext *rc, const char *const args[]) { struct KycPoller *kyp = rc->rh_ctx; MHD_RESULT res; enum GNUNET_GenericReturnValue ret; struct GNUNET_TIME_Absolute now; if (NULL == kyp) { kyp = GNUNET_new (struct KycPoller); kyp->connection = rc->connection; rc->rh_ctx = kyp; rc->rh_cleaner = &kyp_cleanup; { unsigned long long payment_target_uuid; char dummy; if (1 != sscanf (args[0], "%llu%c", &payment_target_uuid, &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->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)); } } { const char *hps; hps = MHD_lookup_connection_value (rc->connection, MHD_GET_ARGUMENT_KIND, "h_payto"); if (NULL == hps) { GNUNET_break_op (0); return TALER_MHD_reply_with_error (rc->connection, MHD_HTTP_BAD_REQUEST, TALER_EC_GENERIC_PARAMETER_MISSING, "h_payto"); } if (GNUNET_OK != GNUNET_STRINGS_string_to_data (hps, strlen (hps), &kyp->auth_h_payto, sizeof (kyp->auth_h_payto))) { GNUNET_break_op (0); return TALER_MHD_reply_with_error (rc->connection, MHD_HTTP_BAD_REQUEST, TALER_EC_GENERIC_PARAMETER_MALFORMED, "h_payto"); } } } if (TEH_KYC_NONE == TEH_kyc_config.mode) return TALER_MHD_reply_static ( rc->connection, MHD_HTTP_NO_CONTENT, NULL, NULL, 0); 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->auth_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); } now = GNUNET_TIME_absolute_get (); (void) GNUNET_TIME_round_abs (&now); ret = TEH_DB_run_transaction (rc->connection, "kyc check", &res, &kyc_check, kyp); if (GNUNET_SYSERR == ret) return res; if (0 != GNUNET_memcmp (&kyp->h_payto, &kyp->auth_h_payto)) { GNUNET_break_op (0); return TALER_MHD_reply_with_error (rc->connection, MHD_HTTP_UNAUTHORIZED, TALER_EC_EXCHANGE_KYC_CHECK_AUTHORIZATION_FAILED, "h_payto"); } /* long polling? */ if ( (! kyp->kyc.ok) && GNUNET_TIME_absolute_is_future (kyp->timeout)) { GNUNET_assert (NULL != kyp->eh); kyp->suspended = true; GNUNET_CONTAINER_DLL_insert (kyp_head, kyp_tail, kyp); MHD_suspend_connection (kyp->connection); return MHD_YES; } /* KYC failed? */ if (! kyp->kyc.ok) { 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/%llu", TEH_base_url, (unsigned long long) kyp->payment_target_uuid); redirect_uri_encoded = TALER_urlencode (redirect_uri); GNUNET_free (redirect_uri); GNUNET_asprintf (&url, "%s/login?client_id=%s&redirect_uri=%s", TEH_kyc_config.details.oauth2.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; } /* KYC succeeded! */ { struct TALER_ExchangePublicKeyP pub; struct TALER_ExchangeSignatureP sig; struct TALER_ExchangeAccountSetupSuccessPS as = { .purpose.purpose = htonl ( TALER_SIGNATURE_EXCHANGE_ACCOUNT_SETUP_SUCCESS), .purpose.size = htonl (sizeof (as)), .h_payto = kyp->h_payto, .timestamp = GNUNET_TIME_absolute_hton (now) }; enum TALER_ErrorCode ec; if (TALER_EC_NONE != (ec = TEH_keys_exchange_sign (&as, &pub, &sig))) { return TALER_MHD_reply_with_ec (rc->connection, ec, NULL); } return TALER_MHD_REPLY_JSON_PACK ( rc->connection, MHD_HTTP_OK, GNUNET_JSON_pack_data_auto ("exchange_sig", &sig), GNUNET_JSON_pack_data_auto ("exchange_pub", &pub), GNUNET_JSON_pack_time_abs ("now", now)); } } /* end of taler-exchange-httpd_kyc-check.c */