exchange

Base system with REST service to issue digital coins, run by the payment service provider
Log | Files | Refs | Submodules | README | LICENSE

taler-exchange-httpd_kyc-check.c (14219B)


      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 TEH_kyc_check_cleanup ()
    122 {
    123   struct KycPoller *kyp;
    124 
    125   while (NULL != (kyp = kyp_head))
    126   {
    127     GNUNET_CONTAINER_DLL_remove (kyp_head,
    128                                  kyp_tail,
    129                                  kyp);
    130     if (kyp->suspended)
    131     {
    132       kyp->suspended = false;
    133       MHD_resume_connection (kyp->connection);
    134     }
    135   }
    136 }
    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 kyp_cleanup (struct TEH_RequestContext *rc)
    147 {
    148   struct KycPoller *kyp = rc->rh_ctx;
    149 
    150   GNUNET_assert (! kyp->suspended);
    151   if (NULL != kyp->eh)
    152   {
    153     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    154                 "Cancelling DB event listening\n");
    155     TEH_plugin->event_listen_cancel (TEH_plugin->cls,
    156                                      kyp->eh);
    157     kyp->eh = NULL;
    158   }
    159   GNUNET_free (kyp);
    160 }
    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 db_event_cb (void *cls,
    173              const void *extra,
    174              size_t extra_size)
    175 {
    176   struct TEH_RequestContext *rc = cls;
    177   struct KycPoller *kyp = rc->rh_ctx;
    178   struct GNUNET_AsyncScopeSave old_scope;
    179 
    180   (void) extra;
    181   (void) extra_size;
    182   if (! kyp->suspended)
    183     return; /* event triggered while main transaction
    184                was still running, or got multiple wake-up events */
    185   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    186               "Received KYC update event\n");
    187   kyp->suspended = false;
    188   GNUNET_async_scope_enter (&rc->async_scope_id,
    189                             &old_scope);
    190   TEH_check_invariants ();
    191   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    192               "Resuming from long-polling on KYC status\n");
    193   GNUNET_CONTAINER_DLL_remove (kyp_head,
    194                                kyp_tail,
    195                                kyp);
    196   MHD_resume_connection (kyp->connection);
    197   TALER_MHD_daemon_trigger ();
    198   TEH_check_invariants ();
    199   GNUNET_async_scope_restore (&old_scope);
    200 }
    201 
    202 
    203 MHD_RESULT
    204 TEH_handler_kyc_check (
    205   struct TEH_RequestContext *rc,
    206   const char *const args[1])
    207 {
    208   struct KycPoller *kyp = rc->rh_ctx;
    209   json_t *jrules = NULL;
    210   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   bool access_ok = false;
    216   enum GNUNET_GenericReturnValue is_wallet;
    217   uint64_t rule_gen = 0;
    218 
    219   if (NULL == kyp)
    220   {
    221     bool sig_required = true;
    222 
    223     kyp = GNUNET_new (struct KycPoller);
    224     kyp->connection = rc->connection;
    225     rc->rh_ctx = kyp;
    226     rc->rh_cleaner = &kyp_cleanup;
    227 
    228     if (GNUNET_OK !=
    229         GNUNET_STRINGS_string_to_data (args[0],
    230                                        strlen (args[0]),
    231                                        &kyp->h_payto,
    232                                        sizeof (kyp->h_payto)))
    233     {
    234       GNUNET_break_op (0);
    235       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     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    242                 "Checking KYC status for normalized payto hash %s\n",
    243                 args[0]);
    244     TALER_MHD_parse_request_header_auto (
    245       rc->connection,
    246       TALER_HTTP_HEADER_ACCOUNT_OWNER_SIGNATURE,
    247       &kyp->account_sig,
    248       sig_required);
    249     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     TALER_MHD_parse_request_timeout (rc->connection,
    255                                      &kyp->timeout);
    256     {
    257       uint64_t num = 0;
    258       int val;
    259 
    260       TALER_MHD_parse_request_number (rc->connection,
    261                                       "lpt",
    262                                       &num);
    263       val = (int) num;
    264       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         GNUNET_break_op (0);
    270         val = TALER_EXCHANGE_KLPT_NONE;
    271       }
    272       kyp->lpt = (enum TALER_EXCHANGE_KycLongPollTarget) val;
    273       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     TALER_MHD_parse_request_number (rc->connection,
    282                                     "min_rule",
    283                                     &kyp->min_rule);
    284     /* long polling needed? */
    285     if (GNUNET_TIME_absolute_is_future (kyp->timeout))
    286     {
    287       struct TALER_KycCompletedEventP rep = {
    288         .header.size = htons (sizeof (rep)),
    289         .header.type = htons (TALER_DBEVENT_EXCHANGE_KYC_COMPLETED),
    290         .h_payto = kyp->h_payto
    291       };
    292 
    293       GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    294                   "Starting DB event listening\n");
    295       kyp->eh = TEH_plugin->event_listen (
    296         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   if (! TEH_enable_kyc)
    305   {
    306     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    307                 "KYC not enabled\n");
    308     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     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     qs = TEH_plugin->lookup_kyc_requirement_by_row (
    325       TEH_plugin->cls,
    326       &kyp->h_payto,
    327       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     if (qs < 0)
    337     {
    338       GNUNET_break (0);
    339       return TALER_MHD_reply_with_ec (
    340         rc->connection,
    341         TALER_EC_GENERIC_DB_STORE_FAILED,
    342         "lookup_kyc_requirement_by_row");
    343     }
    344 
    345     do_suspend = false;
    346     if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
    347     {
    348       /* account unknown */
    349       GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    350                   "Account unknown!\n");
    351       if ( (TALER_EXCHANGE_KLPT_NONE != kyp->lpt) &&
    352            (TALER_EXCHANGE_KLPT_KYC_OK != kyp->lpt) &&
    353            (GNUNET_TIME_absolute_is_future (kyp->timeout)) )
    354       {
    355         do_suspend = true;
    356         access_ok = true; /* for now */
    357       }
    358     }
    359     else
    360     {
    361       GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    362                   "Found rule %llu (client wants > %llu)\n",
    363                   (unsigned long long) rule_gen,
    364                   (unsigned long long) kyp->min_rule);
    365       access_ok =
    366         ( (! GNUNET_is_zero (&kyp->account_pub) &&
    367            (GNUNET_OK ==
    368             TALER_account_kyc_auth_verify (&kyp->account_pub,
    369                                            &kyp->account_sig)) ) ||
    370           (! GNUNET_is_zero (&reserve_pub) &&
    371            (GNUNET_OK ==
    372             TALER_account_kyc_auth_verify (&reserve_pub,
    373                                            &kyp->account_sig)) ) );
    374       if (GNUNET_TIME_absolute_is_future (kyp->timeout) &&
    375           (rule_gen <= kyp->min_rule) )
    376       {
    377         switch (kyp->lpt)
    378         {
    379         case TALER_EXCHANGE_KLPT_NONE:
    380           break;
    381         case TALER_EXCHANGE_KLPT_KYC_AUTH_TRANSFER:
    382           if (! access_ok)
    383             do_suspend = true;
    384           break;
    385         case TALER_EXCHANGE_KLPT_INVESTIGATION_DONE:
    386           if (! aml_review)
    387             do_suspend = true;
    388           break;
    389         case TALER_EXCHANGE_KLPT_KYC_OK:
    390           if (kyc_required)
    391             do_suspend = true;
    392           break;
    393         }
    394       }
    395     }
    396 
    397     if (do_suspend &&
    398         (access_ok ||
    399          (TALER_EXCHANGE_KLPT_KYC_AUTH_TRANSFER == kyp->lpt) ) )
    400     {
    401       GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    402                   "Suspending HTTP request on timeout (%s) for %d\n",
    403                   GNUNET_TIME_relative2s (GNUNET_TIME_absolute_get_remaining (
    404                                             kyp->timeout),
    405                                           true),
    406                   (int) kyp->lpt);
    407       GNUNET_assert (NULL != kyp->eh);
    408       kyp->suspended = true;
    409       GNUNET_CONTAINER_DLL_insert (kyp_head,
    410                                    kyp_tail,
    411                                    kyp);
    412       MHD_suspend_connection (kyp->connection);
    413       return MHD_YES;
    414     }
    415     if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
    416     {
    417       GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    418                   "Returning account unknown\n");
    419       return TALER_MHD_reply_with_error (
    420         rc->connection,
    421         MHD_HTTP_NOT_FOUND,
    422         TALER_EC_EXCHANGE_KYC_CHECK_REQUEST_UNKNOWN,
    423         NULL);
    424     }
    425   }
    426 
    427   if (! access_ok)
    428   {
    429     json_decref (jrules);
    430     jrules = NULL;
    431     if (GNUNET_is_zero (&kyp->account_pub))
    432     {
    433       GNUNET_break_op (0);
    434       return TALER_MHD_reply_with_error (
    435         rc->connection,
    436         MHD_HTTP_CONFLICT,
    437         TALER_EC_EXCHANGE_KYC_CHECK_AUTHORIZATION_KEY_UNKNOWN,
    438         NULL);
    439     }
    440     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    441                 "Returning authorization failed\n");
    442     return TALER_MHD_REPLY_JSON_PACK (
    443       rc->connection,
    444       MHD_HTTP_FORBIDDEN,
    445       TALER_JSON_pack_ec (
    446         TALER_EC_EXCHANGE_KYC_CHECK_AUTHORIZATION_FAILED),
    447       GNUNET_JSON_pack_data_auto ("expected_account_pub",
    448                                   &kyp->account_pub));
    449   }
    450   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    451               "KYC rules apply to %s:\n",
    452               (GNUNET_SYSERR == is_wallet)
    453               ? "unknown account type"
    454               : ( (GNUNET_YES == is_wallet)
    455                   ? "wallet"
    456                   : "account"));
    457   if (NULL != jrules)
    458     json_dumpf (jrules,
    459                 stderr,
    460                 JSON_INDENT (2));
    461 
    462   jlimits = TALER_KYCLOGIC_rules_to_limits (jrules,
    463                                             is_wallet);
    464   if (NULL == jlimits)
    465   {
    466     GNUNET_break_op (0);
    467     json_decref (jrules);
    468     jrules = NULL;
    469     return TALER_MHD_reply_with_error (
    470       rc->connection,
    471       MHD_HTTP_INTERNAL_SERVER_ERROR,
    472       TALER_EC_GENERIC_DB_INVARIANT_FAILURE,
    473       "/kyc-check: rules_to_limits failed");
    474   }
    475   json_decref (jrules);
    476   jrules = NULL;
    477 
    478   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    479               "KYC limits apply:\n");
    480   json_dumpf (jlimits,
    481               stderr,
    482               JSON_INDENT (2));
    483 
    484   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    485               "Returning KYC %s\n",
    486               kyc_required ? "required" : "optional");
    487   return TALER_MHD_REPLY_JSON_PACK (
    488     rc->connection,
    489     kyc_required
    490     ? MHD_HTTP_ACCEPTED
    491     : MHD_HTTP_OK,
    492     GNUNET_JSON_pack_bool ("aml_review",
    493                            aml_review),
    494     GNUNET_JSON_pack_uint64 ("rule_gen",
    495                              rule_gen),
    496     GNUNET_JSON_pack_data_auto ("access_token",
    497                                 &access_token),
    498     GNUNET_JSON_pack_allow_null (
    499       GNUNET_JSON_pack_array_steal ("limits",
    500                                     jlimits)));
    501 }
    502 
    503 
    504 /* end of taler-exchange-httpd_kyc-check.c */