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_get-kyc-check-H_NORMALIZED_PAYTO.c (15980B)


      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_get-kyc-check-H_NORMALIZED_PAYTO.c
     18  * @brief Handle request for generic KYC check.
     19  * @author Christian Grothoff
     20  */
     21 #include <gnunet/gnunet_util_lib.h>
     22 #include <gnunet/gnunet_json_lib.h>
     23 #include <jansson.h>
     24 #include <microhttpd.h>
     25 #include <pthread.h>
     26 #include "taler/taler_json_lib.h"
     27 #include "taler/taler_kyclogic_lib.h"
     28 #include "taler/taler_mhd_lib.h"
     29 #include "taler/taler_dbevents.h"
     30 #include "taler-exchange-httpd_get-keys.h"
     31 #include "taler-exchange-httpd_get-kyc-check-H_NORMALIZED_PAYTO.h"
     32 #include "taler-exchange-httpd_post-kyc-wallet.h"
     33 #include "taler-exchange-httpd_responses.h"
     34 #include "exchange-database/get_legitimization_requirement_by_row.h"
     35 #include "exchange-database/event_listen.h"
     36 #include "exchange-database/event_listen_cancel.h"
     37 
     38 /**
     39  * Reserve GET request that is long-polling.
     40  */
     41 struct KycPoller
     42 {
     43   /**
     44    * Kept in a DLL.
     45    */
     46   struct KycPoller *next;
     47 
     48   /**
     49    * Kept in a DLL.
     50    */
     51   struct KycPoller *prev;
     52 
     53   /**
     54    * Our request context.
     55    */
     56   struct TEH_RequestContext *rc;
     57 
     58   /**
     59    * Connection we are handling.
     60    */
     61   struct MHD_Connection *connection;
     62 
     63   /**
     64    * Subscription for the database event we are
     65    * waiting for.
     66    */
     67   struct GNUNET_DB_EventHandler *eh;
     68 
     69   /**
     70    * Account for which we perform the KYC check.
     71    */
     72   struct TALER_NormalizedPaytoHashP h_payto;
     73 
     74   /**
     75    * When will this request time out?
     76    */
     77   struct GNUNET_TIME_Absolute timeout;
     78 
     79   /**
     80    * Signature by the account owner authorizing this
     81    * operation.
     82    */
     83   union TALER_AccountSignatureP account_sig;
     84 
     85   /**
     86    * Public key from the account owner authorizing this
     87    * operation.
     88    */
     89   union TALER_AccountPublicKeyP account_pub;
     90 
     91   /**
     92    * Generation of KYC rules already known to the client
     93    * (when long-polling). Do not send these rules again.
     94    */
     95   uint64_t min_rule;
     96 
     97   /**
     98    * What are we long-polling for (if anything)?
     99    */
    100   enum TALER_EXCHANGE_KycLongPollTarget lpt;
    101 
    102   /**
    103    * True if we are still suspended.
    104    */
    105   bool suspended;
    106 
    107 };
    108 
    109 
    110 /**
    111  * Head of list of requests in long polling.
    112  */
    113 static struct KycPoller *kyp_head;
    114 
    115 /**
    116  * Tail of list of requests in long polling.
    117  */
    118 static struct KycPoller *kyp_tail;
    119 
    120 
    121 void
    122 TEH_kyc_check_cleanup ()
    123 {
    124   struct KycPoller *kyp;
    125 
    126   while (NULL != (kyp = kyp_head))
    127   {
    128     GNUNET_CONTAINER_DLL_remove (kyp_head,
    129                                  kyp_tail,
    130                                  kyp);
    131     if (kyp->suspended)
    132     {
    133       struct GNUNET_AsyncScopeSave old_scope;
    134 
    135       GNUNET_async_scope_enter (&kyp->rc->async_scope_id,
    136                                 &old_scope);
    137       GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    138                   "Resuming connection on shutdown\n");
    139       kyp->suspended = false;
    140       MHD_resume_connection (kyp->connection);
    141       GNUNET_async_scope_restore (&old_scope);
    142     }
    143   }
    144 }
    145 
    146 
    147 /**
    148  * Function called once a connection is done to
    149  * clean up the `struct ReservePoller` state.
    150  *
    151  * @param rc context to clean up for
    152  */
    153 static void
    154 kyp_cleanup (struct TEH_RequestContext *rc)
    155 {
    156   struct KycPoller *kyp = rc->rh_ctx;
    157 
    158   GNUNET_assert (! kyp->suspended);
    159   if (NULL != kyp->eh)
    160   {
    161     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    162                 "Cancelling DB event listening (KYP cleanup)\n");
    163     TALER_EXCHANGEDB_event_listen_cancel (TEH_pg,
    164                                           kyp->eh);
    165     kyp->eh = NULL;
    166   }
    167   GNUNET_free (kyp);
    168 }
    169 
    170 
    171 /**
    172  * Function called on events received from Postgres.
    173  * Wakes up long pollers.
    174  *
    175  * @param cls the `struct TEH_RequestContext *`
    176  * @param extra additional event data provided
    177  * @param extra_size number of bytes in @a extra
    178  */
    179 static void
    180 db_event_cb (void *cls,
    181              const void *extra,
    182              size_t extra_size)
    183 {
    184   struct TEH_RequestContext *rc = cls;
    185   struct KycPoller *kyp = rc->rh_ctx;
    186   struct GNUNET_AsyncScopeSave old_scope;
    187 
    188   (void) extra;
    189   (void) extra_size;
    190   if (! kyp->suspended)
    191     return; /* event triggered while main transaction
    192                was still running, or got multiple wake-up events */
    193   kyp->suspended = false;
    194   GNUNET_async_scope_enter (&rc->async_scope_id,
    195                             &old_scope);
    196   TEH_check_invariants ();
    197   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    198               "Resuming from long-polling on KYC status\n");
    199   GNUNET_CONTAINER_DLL_remove (kyp_head,
    200                                kyp_tail,
    201                                kyp);
    202   MHD_resume_connection (kyp->connection);
    203   TALER_MHD_daemon_trigger ();
    204   TEH_check_invariants ();
    205   GNUNET_async_scope_restore (&old_scope);
    206 }
    207 
    208 
    209 enum MHD_Result
    210 TEH_handler_kyc_check (
    211   struct TEH_RequestContext *rc,
    212   const char *const args[1])
    213 {
    214   struct KycPoller *kyp = rc->rh_ctx;
    215   json_t *jrules = NULL;
    216   json_t *jlimits = NULL;
    217   struct TALER_AccountAccessTokenP access_token;
    218   bool aml_review;
    219   bool kyc_required;
    220   bool access_ok = false;
    221   bool tos_required;
    222   enum GNUNET_GenericReturnValue is_wallet;
    223   uint64_t rule_gen = 0;
    224 
    225   if (NULL == kyp)
    226   {
    227     bool sig_required = true;
    228     bool acc_required = true;
    229 
    230     kyp = GNUNET_new (struct KycPoller);
    231     kyp->connection = rc->connection;
    232     kyp->rc = rc;
    233     rc->rh_ctx = kyp;
    234     rc->rh_cleaner = &kyp_cleanup;
    235 
    236     if (GNUNET_OK !=
    237         GNUNET_STRINGS_string_to_data (args[0],
    238                                        strlen (args[0]),
    239                                        &kyp->h_payto,
    240                                        sizeof (kyp->h_payto)))
    241     {
    242       GNUNET_break_op (0);
    243       return TALER_MHD_reply_with_error (
    244         rc->connection,
    245         MHD_HTTP_BAD_REQUEST,
    246         TALER_EC_GENERIC_PATH_SEGMENT_MALFORMED,
    247         "h_payto");
    248     }
    249     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    250                 "Checking KYC status for normalized payto hash %s\n",
    251                 args[0]);
    252     TALER_MHD_parse_request_header_auto (
    253       rc->connection,
    254       TALER_HTTP_HEADER_ACCOUNT_OWNER_SIGNATURE,
    255       &kyp->account_sig,
    256       sig_required);
    257     TALER_MHD_parse_request_header_auto (
    258       rc->connection,
    259       TALER_HTTP_HEADER_ACCOUNT_OWNER_PUBKEY,
    260       &kyp->account_pub,
    261       acc_required);
    262     TALER_MHD_parse_request_timeout (rc->connection,
    263                                      &kyp->timeout);
    264     {
    265       uint64_t num = 0;
    266       int val;
    267 
    268       TALER_MHD_parse_request_number (rc->connection,
    269                                       "lpt",
    270                                       &num);
    271       val = (int) num;
    272       if ( (val < 0) ||
    273            (val > TALER_EXCHANGE_KLPT_MAX) )
    274       {
    275         /* Protocol violation, but we can be graceful and
    276            just ignore the long polling! */
    277         GNUNET_break_op (0);
    278         val = TALER_EXCHANGE_KLPT_NONE;
    279       }
    280       kyp->lpt = (enum TALER_EXCHANGE_KycLongPollTarget) val;
    281       GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    282                   "Long polling for target %d with timeout %s\n",
    283                   val,
    284                   GNUNET_TIME_relative2s (
    285                     GNUNET_TIME_absolute_get_remaining (
    286                       kyp->timeout),
    287                     true));
    288     }
    289     TALER_MHD_parse_request_number (rc->connection,
    290                                     "min_rule",
    291                                     &kyp->min_rule);
    292     /* long polling needed? */
    293     if (GNUNET_TIME_absolute_is_future (kyp->timeout))
    294     {
    295       struct TALER_EXCHANGEDB_KycCompletedEventP rep = {
    296         .header.size = htons (sizeof (rep)),
    297         .header.type = htons (TALER_DBEVENT_EXCHANGE_KYC_COMPLETED),
    298         .h_payto = kyp->h_payto
    299       };
    300 
    301       kyp->eh = TALER_EXCHANGEDB_event_listen (
    302         TEH_pg,
    303         GNUNET_TIME_absolute_get_remaining (kyp->timeout),
    304         &rep.header,
    305         &db_event_cb,
    306         rc);
    307     }
    308   } /* end initialization */
    309 
    310   if (! TEH_enable_kyc)
    311   {
    312     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    313                 "KYC not enabled\n");
    314     return TALER_MHD_reply_static (
    315       rc->connection,
    316       MHD_HTTP_NO_CONTENT,
    317       NULL,
    318       NULL,
    319       0);
    320   }
    321 
    322   {
    323     enum GNUNET_DB_QueryStatus qs;
    324     bool do_suspend;
    325 
    326     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    327                 "Looking up KYC requirements for account %s\n",
    328                 TALER_B2S (&kyp->h_payto));
    329     qs = TALER_EXCHANGEDB_get_legitimization_requirement_by_row (
    330       TEH_pg,
    331       &kyp->h_payto,
    332       &kyp->account_pub,
    333       &is_wallet,
    334       &access_token,
    335       &rule_gen,
    336       &jrules,
    337       &aml_review,
    338       &kyc_required);
    339     if (qs < 0)
    340     {
    341       GNUNET_break (0);
    342       return TALER_MHD_reply_with_ec (
    343         rc->connection,
    344         TALER_EC_GENERIC_DB_STORE_FAILED,
    345         "lookup_kyc_requirement_by_row");
    346     }
    347 
    348     do_suspend = false;
    349     if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
    350     {
    351       /* account unknown */
    352       GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    353                   "Account unknown!\n");
    354       if ( (TALER_EXCHANGE_KLPT_NONE != kyp->lpt) &&
    355            (TALER_EXCHANGE_KLPT_KYC_OK != kyp->lpt) &&
    356            (GNUNET_TIME_absolute_is_future (kyp->timeout)) )
    357       {
    358         GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    359                     "Suspending due to account unknown!\n");
    360         do_suspend = true;
    361         access_ok = true; /* for now */
    362       }
    363     }
    364     else
    365     {
    366       GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    367                   "Found rule %llu (client wants > %llu, %d)\n",
    368                   (unsigned long long) rule_gen,
    369                   (unsigned long long) kyp->min_rule,
    370                   (int) kyp->lpt);
    371       access_ok =
    372         (GNUNET_OK ==
    373          TALER_account_kyc_auth_verify (&kyp->account_pub,
    374                                         &kyp->account_sig));
    375       if (GNUNET_TIME_absolute_is_future (kyp->timeout) &&
    376           (rule_gen <= kyp->min_rule) )
    377       {
    378         switch (kyp->lpt)
    379         {
    380         case TALER_EXCHANGE_KLPT_NONE:
    381           /* If KLPT is not given, just go for rule generation */
    382           GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    383                       "Suspending until rule generation changes\n");
    384           do_suspend = true;
    385           break;
    386         case TALER_EXCHANGE_KLPT_KYC_AUTH_TRANSFER:
    387           if (! access_ok)
    388           {
    389             GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    390                         "Waiting for auth transfer, access NOT OK, suspending again\n");
    391             do_suspend = true;
    392           }
    393           break;
    394         case TALER_EXCHANGE_KLPT_INVESTIGATION_DONE:
    395           if (! aml_review)
    396           {
    397             GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    398                         "Waiting for investigation complete, investigation ongoing, suspending again\n");
    399             do_suspend = true;
    400           }
    401           break;
    402         case TALER_EXCHANGE_KLPT_KYC_OK:
    403           if (kyc_required)
    404           {
    405             GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    406                         "Waiting for KYC OK, but KYC NOT OK, suspending again\n");
    407             do_suspend = true;
    408           }
    409           break;
    410         }
    411       }
    412     }
    413 
    414     if (do_suspend &&
    415         (access_ok ||
    416          (TALER_EXCHANGE_KLPT_KYC_AUTH_TRANSFER == kyp->lpt) ) )
    417     {
    418       GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    419                   "Suspending HTTP request on timeout (%s) for %d\n",
    420                   GNUNET_TIME_relative2s (GNUNET_TIME_absolute_get_remaining (
    421                                             kyp->timeout),
    422                                           true),
    423                   (int) kyp->lpt);
    424       GNUNET_assert (NULL != kyp->eh);
    425       GNUNET_assert (! kyp->suspended);
    426       kyp->suspended = true;
    427       GNUNET_CONTAINER_DLL_insert (kyp_head,
    428                                    kyp_tail,
    429                                    kyp);
    430       GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    431                   "Telling MHD to suspend connection\n");
    432       MHD_suspend_connection (kyp->connection);
    433       json_decref (jrules);
    434       return MHD_YES;
    435     }
    436     if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
    437     {
    438       GNUNET_break (NULL == jrules);
    439       GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    440                   "Returning account unknown\n");
    441       return TALER_MHD_reply_with_error (
    442         rc->connection,
    443         MHD_HTTP_NOT_FOUND,
    444         TALER_EC_EXCHANGE_KYC_CHECK_REQUEST_UNKNOWN,
    445         NULL);
    446     }
    447   }
    448 
    449   if (! access_ok)
    450   {
    451     json_decref (jrules);
    452     jrules = NULL;
    453     if (GNUNET_is_zero (&kyp->account_pub))
    454     {
    455       GNUNET_break_op (0);
    456       return TALER_MHD_reply_with_error (
    457         rc->connection,
    458         MHD_HTTP_CONFLICT,
    459         TALER_EC_EXCHANGE_KYC_CHECK_AUTHORIZATION_KEY_UNKNOWN,
    460         NULL);
    461     }
    462     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    463                 "Returning authorization failed\n");
    464     return TALER_MHD_REPLY_JSON_PACK (
    465       rc->connection,
    466       MHD_HTTP_FORBIDDEN,
    467       TALER_JSON_pack_ec (
    468         TALER_EC_EXCHANGE_KYC_CHECK_AUTHORIZATION_FAILED),
    469       GNUNET_JSON_pack_data_auto ("expected_account_pub",
    470                                   &kyp->account_pub));
    471   }
    472   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    473               "KYC rules apply to %s:\n",
    474               (GNUNET_SYSERR == is_wallet)
    475               ? "unknown account type"
    476               : ( (GNUNET_YES == is_wallet)
    477                   ? "wallet"
    478                   : "account"));
    479   tos_required = false;
    480   if ( (TEH_kyc_swap_tos_acceptance) &&
    481        (NULL != TEH_tos_etag) )
    482   {
    483     /* #11183: With the terms-of-service / KYC-auth swap enabled, and if
    484        the rule set that applies to this account actually requires the
    485        client to accept our terms of service, surface that requirement
    486        up-front by returning the current ToS ETag below.  This lets the
    487        merchant backend submit a previously collected acceptance on the
    488        user's behalf (via the accept-tos requirement it finds under
    489        /kyc-info) instead of forcing the user to interact again.  We
    490        inspect the rules precisely (rather than just whether *some* KYC
    491        is required), as the rule set may demand entirely different
    492        measures here. */
    493     tos_required =
    494       TALER_KYCLOGIC_rules_require_tos_acceptance (jrules);
    495   }
    496   jlimits = TALER_KYCLOGIC_rules_to_limits (jrules,
    497                                             is_wallet);
    498   if (NULL == jlimits)
    499   {
    500     GNUNET_break_op (0);
    501     json_decref (jrules);
    502     jrules = NULL;
    503     return TALER_MHD_reply_with_error (
    504       rc->connection,
    505       MHD_HTTP_INTERNAL_SERVER_ERROR,
    506       TALER_EC_GENERIC_DB_INVARIANT_FAILURE,
    507       "/kyc-check: rules_to_limits failed");
    508   }
    509   json_decref (jrules);
    510   jrules = NULL;
    511 
    512   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    513               "Returning KYC %s\n",
    514               kyc_required ? "required" : "optional");
    515   return TALER_MHD_REPLY_JSON_PACK (
    516     rc->connection,
    517     kyc_required
    518     ? MHD_HTTP_ACCEPTED
    519     : MHD_HTTP_OK,
    520     GNUNET_JSON_pack_bool ("aml_review",
    521                            aml_review),
    522     GNUNET_JSON_pack_uint64 ("rule_gen",
    523                              rule_gen),
    524     GNUNET_JSON_pack_data_auto ("access_token",
    525                                 &access_token),
    526     GNUNET_JSON_pack_allow_null (
    527       GNUNET_JSON_pack_string ("tos_required",
    528                                tos_required
    529                                ? TEH_tos_etag
    530                                : NULL)),
    531     GNUNET_JSON_pack_allow_null (
    532       GNUNET_JSON_pack_array_steal ("limits",
    533                                     jlimits)));
    534 }
    535 
    536 
    537 /* end of taler-exchange-httpd_kyc-check.c */