LCOV - code coverage report
Current view: top level - exchange - taler-exchange-httpd_kyc-check.c (source / functions) Hit Total Coverage
Test: coverage.info Lines: 80 147 54.4 %
Date: 2025-07-09 07:38:29 Functions: 3 4 75.0 %

          Line data    Source code
       1             : /*
       2             :   This file is part of TALER
       3             :   Copyright (C) 2021-2024 Taler Systems SA
       4             : 
       5             :   TALER is free software; you can redistribute it and/or modify it under the
       6             :   terms of the GNU Affero General Public License as published by the Free Software
       7             :   Foundation; either version 3, or (at your option) any later version.
       8             : 
       9             :   TALER is distributed in the hope that it will be useful, but WITHOUT ANY
      10             :   WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
      11             :   A PARTICULAR PURPOSE.  See the GNU Affero General Public License for more details.
      12             : 
      13             :   You should have received a copy of the GNU Affero General Public License along with
      14             :   TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
      15             : */
      16             : /**
      17             :  * @file taler-exchange-httpd_kyc-check.c
      18             :  * @brief Handle request for generic KYC check.
      19             :  * @author Christian Grothoff
      20             :  */
      21             : #include "taler/platform.h"
      22             : #include <gnunet/gnunet_util_lib.h>
      23             : #include <gnunet/gnunet_json_lib.h>
      24             : #include <jansson.h>
      25             : #include <microhttpd.h>
      26             : #include <pthread.h>
      27             : #include "taler/taler_json_lib.h"
      28             : #include "taler/taler_kyclogic_lib.h"
      29             : #include "taler/taler_mhd_lib.h"
      30             : #include "taler/taler_signatures.h"
      31             : #include "taler/taler_dbevents.h"
      32             : #include "taler-exchange-httpd_keys.h"
      33             : #include "taler-exchange-httpd_kyc-check.h"
      34             : #include "taler-exchange-httpd_kyc-wallet.h"
      35             : #include "taler-exchange-httpd_responses.h"
      36             : 
      37             : /**
      38             :  * Reserve GET request that is long-polling.
      39             :  */
      40             : struct KycPoller
      41             : {
      42             :   /**
      43             :    * Kept in a DLL.
      44             :    */
      45             :   struct KycPoller *next;
      46             : 
      47             :   /**
      48             :    * Kept in a DLL.
      49             :    */
      50             :   struct KycPoller *prev;
      51             : 
      52             :   /**
      53             :    * Connection we are handling.
      54             :    */
      55             :   struct MHD_Connection *connection;
      56             : 
      57             :   /**
      58             :    * Subscription for the database event we are
      59             :    * waiting for.
      60             :    */
      61             :   struct GNUNET_DB_EventHandler *eh;
      62             : 
      63             :   /**
      64             :    * Account for which we perform the KYC check.
      65             :    */
      66             :   struct TALER_NormalizedPaytoHashP h_payto;
      67             : 
      68             :   /**
      69             :    * When will this request time out?
      70             :    */
      71             :   struct GNUNET_TIME_Absolute timeout;
      72             : 
      73             :   /**
      74             :    * Signature by the account owner authorizing this
      75             :    * operation.
      76             :    */
      77             :   union TALER_AccountSignatureP account_sig;
      78             : 
      79             :   /**
      80             :    * Public key from the account owner authorizing this
      81             :    * operation. Optional, see @e have_pub.
      82             :    */
      83             :   union TALER_AccountPublicKeyP account_pub;
      84             : 
      85             :   /**
      86             :    * Generation of KYC rules already known to the client
      87             :    * (when long-polling). Do not send these rules again.
      88             :    */
      89             :   uint64_t min_rule;
      90             : 
      91             :   /**
      92             :    * What are we long-polling for (if anything)?
      93             :    */
      94             :   enum TALER_EXCHANGE_KycLongPollTarget lpt;
      95             : 
      96             :   /**
      97             :    * True if we are still suspended.
      98             :    */
      99             :   bool suspended;
     100             : 
     101             :   /**
     102             :    * True if we have an @e account_pub.
     103             :    */
     104             :   bool have_pub;
     105             : 
     106             : };
     107             : 
     108             : 
     109             : /**
     110             :  * Head of list of requests in long polling.
     111             :  */
     112             : static struct KycPoller *kyp_head;
     113             : 
     114             : /**
     115             :  * Tail of list of requests in long polling.
     116             :  */
     117             : static struct KycPoller *kyp_tail;
     118             : 
     119             : 
     120             : void
     121          21 : TEH_kyc_check_cleanup ()
     122             : {
     123             :   struct KycPoller *kyp;
     124             : 
     125          42 :   while (NULL != (kyp = kyp_head))
     126             :   {
     127           0 :     GNUNET_CONTAINER_DLL_remove (kyp_head,
     128             :                                  kyp_tail,
     129             :                                  kyp);
     130           0 :     if (kyp->suspended)
     131             :     {
     132           0 :       kyp->suspended = false;
     133           0 :       MHD_resume_connection (kyp->connection);
     134             :     }
     135             :   }
     136          21 : }
     137             : 
     138             : 
     139             : /**
     140             :  * Function called once a connection is done to
     141             :  * clean up the `struct ReservePoller` state.
     142             :  *
     143             :  * @param rc context to clean up for
     144             :  */
     145             : static void
     146          16 : kyp_cleanup (struct TEH_RequestContext *rc)
     147             : {
     148          16 :   struct KycPoller *kyp = rc->rh_ctx;
     149             : 
     150          16 :   GNUNET_assert (! kyp->suspended);
     151          16 :   if (NULL != kyp->eh)
     152             :   {
     153          14 :     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     154             :                 "Cancelling DB event listening\n");
     155          14 :     TEH_plugin->event_listen_cancel (TEH_plugin->cls,
     156             :                                      kyp->eh);
     157          14 :     kyp->eh = NULL;
     158             :   }
     159          16 :   GNUNET_free (kyp);
     160          16 : }
     161             : 
     162             : 
     163             : /**
     164             :  * Function called on events received from Postgres.
     165             :  * Wakes up long pollers.
     166             :  *
     167             :  * @param cls the `struct TEH_RequestContext *`
     168             :  * @param extra additional event data provided
     169             :  * @param extra_size number of bytes in @a extra
     170             :  */
     171             : static void
     172           0 : db_event_cb (void *cls,
     173             :              const void *extra,
     174             :              size_t extra_size)
     175             : {
     176           0 :   struct TEH_RequestContext *rc = cls;
     177           0 :   struct KycPoller *kyp = rc->rh_ctx;
     178             :   struct GNUNET_AsyncScopeSave old_scope;
     179             : 
     180             :   (void) extra;
     181             :   (void) extra_size;
     182           0 :   if (! kyp->suspended)
     183           0 :     return; /* event triggered while main transaction
     184             :                was still running, or got multiple wake-up events */
     185           0 :   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     186             :               "Received KYC update event\n");
     187           0 :   kyp->suspended = false;
     188           0 :   GNUNET_async_scope_enter (&rc->async_scope_id,
     189             :                             &old_scope);
     190           0 :   TEH_check_invariants ();
     191           0 :   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     192             :               "Resuming from long-polling on KYC status\n");
     193           0 :   GNUNET_CONTAINER_DLL_remove (kyp_head,
     194             :                                kyp_tail,
     195             :                                kyp);
     196           0 :   MHD_resume_connection (kyp->connection);
     197           0 :   TALER_MHD_daemon_trigger ();
     198           0 :   TEH_check_invariants ();
     199           0 :   GNUNET_async_scope_restore (&old_scope);
     200             : }
     201             : 
     202             : 
     203             : MHD_RESULT
     204          16 : TEH_handler_kyc_check (
     205             :   struct TEH_RequestContext *rc,
     206             :   const char *const args[1])
     207             : {
     208          16 :   struct KycPoller *kyp = rc->rh_ctx;
     209          16 :   json_t *jrules = NULL;
     210          16 :   json_t *jlimits = NULL;
     211             :   union TALER_AccountPublicKeyP reserve_pub;
     212             :   struct TALER_AccountAccessTokenP access_token;
     213             :   bool aml_review;
     214             :   bool kyc_required;
     215          16 :   bool access_ok = false;
     216             :   bool is_wallet;
     217          16 :   uint64_t rule_gen = 0;
     218             : 
     219          16 :   if (NULL == kyp)
     220             :   {
     221          16 :     bool sig_required = true;
     222             : 
     223          16 :     kyp = GNUNET_new (struct KycPoller);
     224          16 :     kyp->connection = rc->connection;
     225          16 :     rc->rh_ctx = kyp;
     226          16 :     rc->rh_cleaner = &kyp_cleanup;
     227             : 
     228          16 :     if (GNUNET_OK !=
     229          16 :         GNUNET_STRINGS_string_to_data (args[0],
     230             :                                        strlen (args[0]),
     231          16 :                                        &kyp->h_payto,
     232             :                                        sizeof (kyp->h_payto)))
     233             :     {
     234           0 :       GNUNET_break_op (0);
     235           0 :       return TALER_MHD_reply_with_error (
     236             :         rc->connection,
     237             :         MHD_HTTP_BAD_REQUEST,
     238             :         TALER_EC_GENERIC_PATH_SEGMENT_MALFORMED,
     239             :         "h_payto");
     240             :     }
     241          16 :     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     242             :                 "Checking KYC status for normalized payto hash %s\n",
     243             :                 args[0]);
     244          16 :     TALER_MHD_parse_request_header_auto (
     245             :       rc->connection,
     246             :       TALER_HTTP_HEADER_ACCOUNT_OWNER_SIGNATURE,
     247             :       &kyp->account_sig,
     248             :       sig_required);
     249          16 :     TALER_MHD_parse_request_header_auto (
     250             :       rc->connection,
     251             :       TALER_HTTP_HEADER_ACCOUNT_OWNER_PUBKEY,
     252             :       &kyp->account_pub,
     253             :       kyp->have_pub);
     254          16 :     TALER_MHD_parse_request_timeout (rc->connection,
     255             :                                      &kyp->timeout);
     256             :     {
     257          16 :       uint64_t num = 0;
     258             :       int val;
     259             : 
     260          16 :       TALER_MHD_parse_request_number (rc->connection,
     261             :                                       "lpt",
     262             :                                       &num);
     263          16 :       val = (int) num;
     264          16 :       if ( (val < 0) ||
     265             :            (val > TALER_EXCHANGE_KLPT_MAX) )
     266             :       {
     267             :         /* Protocol violation, but we can be graceful and
     268             :            just ignore the long polling! */
     269           0 :         GNUNET_break_op (0);
     270           0 :         val = TALER_EXCHANGE_KLPT_NONE;
     271             :       }
     272          16 :       kyp->lpt = (enum TALER_EXCHANGE_KycLongPollTarget) val;
     273          16 :       GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     274             :                   "Long polling for target %d with timeout %s\n",
     275             :                   val,
     276             :                   GNUNET_TIME_relative2s (
     277             :                     GNUNET_TIME_absolute_get_remaining (
     278             :                       kyp->timeout),
     279             :                     true));
     280             :     }
     281          16 :     TALER_MHD_parse_request_number (rc->connection,
     282             :                                     "min_rule",
     283             :                                     &kyp->min_rule);
     284             :     /* long polling needed? */
     285          16 :     if (GNUNET_TIME_absolute_is_future (kyp->timeout))
     286             :     {
     287          14 :       struct TALER_KycCompletedEventP rep = {
     288          14 :         .header.size = htons (sizeof (rep)),
     289          14 :         .header.type = htons (TALER_DBEVENT_EXCHANGE_KYC_COMPLETED),
     290             :         .h_payto = kyp->h_payto
     291             :       };
     292             : 
     293          14 :       GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     294             :                   "Starting DB event listening\n");
     295          28 :       kyp->eh = TEH_plugin->event_listen (
     296          14 :         TEH_plugin->cls,
     297             :         GNUNET_TIME_absolute_get_remaining (kyp->timeout),
     298             :         &rep.header,
     299             :         &db_event_cb,
     300             :         rc);
     301             :     }
     302             :   } /* end initialization */
     303             : 
     304          16 :   if (! TEH_enable_kyc)
     305             :   {
     306           2 :     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     307             :                 "KYC not enabled\n");
     308           2 :     return TALER_MHD_reply_static (
     309             :       rc->connection,
     310             :       MHD_HTTP_NO_CONTENT,
     311             :       NULL,
     312             :       NULL,
     313             :       0);
     314             :   }
     315             : 
     316             :   {
     317             :     enum GNUNET_DB_QueryStatus qs;
     318             :     bool do_suspend;
     319             : 
     320          14 :     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     321             :                 "Looking up KYC requirements for account %s (%s)\n",
     322             :                 TALER_B2S (&kyp->h_payto),
     323             :                 kyp->have_pub ? "with account-pub" : "legacy wallet");
     324          14 :     qs = TEH_plugin->lookup_kyc_requirement_by_row (
     325          14 :       TEH_plugin->cls,
     326          14 :       &kyp->h_payto,
     327          14 :       kyp->have_pub,
     328             :       &kyp->account_pub,
     329             :       &is_wallet,
     330             :       &reserve_pub.reserve_pub,
     331             :       &access_token,
     332             :       &rule_gen,
     333             :       &jrules,
     334             :       &aml_review,
     335             :       &kyc_required);
     336          14 :     if (qs < 0)
     337             :     {
     338           0 :       GNUNET_break (0);
     339           0 :       return TALER_MHD_reply_with_ec (
     340             :         rc->connection,
     341             :         TALER_EC_GENERIC_DB_STORE_FAILED,
     342             :         "lookup_kyc_requirement_by_row");
     343             :     }
     344          14 :     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     345             :                 "Found rule %llu (client wants > %llu)\n",
     346             :                 (unsigned long long) rule_gen,
     347             :                 (unsigned long long) kyp->min_rule);
     348          14 :     do_suspend = false;
     349          14 :     if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
     350             :     {
     351             :       /* account unknown */
     352           0 :       GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     353             :                   "Account unknown!\n");
     354           0 :       if ( (TALER_EXCHANGE_KLPT_NONE != kyp->lpt) &&
     355           0 :            (TALER_EXCHANGE_KLPT_KYC_OK != kyp->lpt) &&
     356           0 :            (GNUNET_TIME_absolute_is_future (kyp->timeout)) )
     357             :       {
     358           0 :         do_suspend = true;
     359           0 :         access_ok = true; /* for now */
     360             :       }
     361             :     }
     362             :     else
     363             :     {
     364          14 :       access_ok =
     365          28 :         ( (! GNUNET_is_zero (&kyp->account_pub) &&
     366             :            (GNUNET_OK ==
     367          14 :             TALER_account_kyc_auth_verify (&kyp->account_pub,
     368          28 :                                            &kyp->account_sig)) ) ||
     369           0 :           (! GNUNET_is_zero (&reserve_pub) &&
     370             :            (GNUNET_OK ==
     371           0 :             TALER_account_kyc_auth_verify (&reserve_pub,
     372           0 :                                            &kyp->account_sig)) ) );
     373          14 :       if (GNUNET_TIME_absolute_is_future (kyp->timeout) &&
     374          14 :           (rule_gen <= kyp->min_rule) )
     375             :       {
     376          12 :         switch (kyp->lpt)
     377             :         {
     378          12 :         case TALER_EXCHANGE_KLPT_NONE:
     379          12 :           break;
     380           0 :         case TALER_EXCHANGE_KLPT_KYC_AUTH_TRANSFER:
     381           0 :           if (! access_ok)
     382           0 :             do_suspend = true;
     383           0 :           break;
     384           0 :         case TALER_EXCHANGE_KLPT_INVESTIGATION_DONE:
     385           0 :           if (! aml_review)
     386           0 :             do_suspend = true;
     387           0 :           break;
     388           0 :         case TALER_EXCHANGE_KLPT_KYC_OK:
     389           0 :           if (kyc_required)
     390           0 :             do_suspend = true;
     391           0 :           break;
     392             :         }
     393             :       }
     394             :     }
     395             : 
     396          14 :     if (do_suspend &&
     397           0 :         (access_ok ||
     398           0 :          (TALER_EXCHANGE_KLPT_KYC_AUTH_TRANSFER == kyp->lpt) ) )
     399             :     {
     400           0 :       GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     401             :                   "Suspending HTTP request on timeout (%s) for %d\n",
     402             :                   GNUNET_TIME_relative2s (GNUNET_TIME_absolute_get_remaining (
     403             :                                             kyp->timeout),
     404             :                                           true),
     405             :                   (int) kyp->lpt);
     406           0 :       GNUNET_assert (NULL != kyp->eh);
     407           0 :       kyp->suspended = true;
     408           0 :       GNUNET_CONTAINER_DLL_insert (kyp_head,
     409             :                                    kyp_tail,
     410             :                                    kyp);
     411           0 :       MHD_suspend_connection (kyp->connection);
     412           0 :       return MHD_YES;
     413             :     }
     414          14 :     if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
     415             :     {
     416           0 :       GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     417             :                   "Returning account unknown\n");
     418           0 :       return TALER_MHD_reply_with_error (
     419             :         rc->connection,
     420             :         MHD_HTTP_NOT_FOUND,
     421             :         TALER_EC_EXCHANGE_KYC_CHECK_REQUEST_UNKNOWN,
     422             :         NULL);
     423             :     }
     424             :   }
     425             : 
     426          14 :   if (! access_ok)
     427             :   {
     428           0 :     json_decref (jrules);
     429           0 :     jrules = NULL;
     430           0 :     if (GNUNET_is_zero (&kyp->account_pub))
     431             :     {
     432           0 :       GNUNET_break_op (0);
     433           0 :       return TALER_MHD_reply_with_error (
     434             :         rc->connection,
     435             :         MHD_HTTP_CONFLICT,
     436             :         TALER_EC_EXCHANGE_KYC_CHECK_AUTHORIZATION_KEY_UNKNOWN,
     437             :         NULL);
     438             :     }
     439           0 :     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     440             :                 "Returning authorization failed\n");
     441           0 :     return TALER_MHD_REPLY_JSON_PACK (
     442             :       rc->connection,
     443             :       MHD_HTTP_FORBIDDEN,
     444             :       TALER_JSON_pack_ec (
     445             :         TALER_EC_EXCHANGE_KYC_CHECK_AUTHORIZATION_FAILED),
     446             :       GNUNET_JSON_pack_data_auto ("expected_account_pub",
     447             :                                   &kyp->account_pub));
     448             :   }
     449             : 
     450          14 :   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     451             :               "KYC rules apply to %s:\n",
     452             :               is_wallet ? "wallet" : "account");
     453          14 :   if (NULL != jrules)
     454           4 :     json_dumpf (jrules,
     455             :                 stderr,
     456             :                 JSON_INDENT (2));
     457             : 
     458          14 :   jlimits = TALER_KYCLOGIC_rules_to_limits (jrules,
     459             :                                             is_wallet);
     460          14 :   if (NULL == jlimits)
     461             :   {
     462           0 :     GNUNET_break_op (0);
     463           0 :     json_decref (jrules);
     464           0 :     jrules = NULL;
     465           0 :     return TALER_MHD_reply_with_error (
     466             :       rc->connection,
     467             :       MHD_HTTP_INTERNAL_SERVER_ERROR,
     468             :       TALER_EC_GENERIC_DB_INVARIANT_FAILURE,
     469             :       "/kyc-check: rules_to_limits failed");
     470             :   }
     471          14 :   json_decref (jrules);
     472          14 :   jrules = NULL;
     473             : 
     474          14 :   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     475             :               "KYC limits apply:\n");
     476          14 :   json_dumpf (jlimits,
     477             :               stderr,
     478             :               JSON_INDENT (2));
     479             : 
     480          14 :   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     481             :               "Returning KYC %s\n",
     482             :               kyc_required ? "required" : "optional");
     483          14 :   return TALER_MHD_REPLY_JSON_PACK (
     484             :     rc->connection,
     485             :     kyc_required
     486             :     ? MHD_HTTP_ACCEPTED
     487             :     : MHD_HTTP_OK,
     488             :     GNUNET_JSON_pack_bool ("aml_review",
     489             :                            aml_review),
     490             :     GNUNET_JSON_pack_uint64 ("rule_gen",
     491             :                              rule_gen),
     492             :     GNUNET_JSON_pack_data_auto ("access_token",
     493             :                                 &access_token),
     494             :     GNUNET_JSON_pack_allow_null (
     495             :       GNUNET_JSON_pack_array_steal ("limits",
     496             :                                     jlimits)));
     497             : }
     498             : 
     499             : 
     500             : /* end of taler-exchange-httpd_kyc-check.c */

Generated by: LCOV version 1.16