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-start.c (13865B)


      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-start.c
     18  * @brief Handle request for starting a KYC process with an external provider.
     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-start.h"
     34 #include "taler-exchange-httpd_responses.h"
     35 
     36 
     37 /**
     38  * POST request in asynchronous processing.
     39  */
     40 struct KycPoller
     41 {
     42 
     43   /**
     44    * Access token for the KYC data of the account.
     45    */
     46   struct TALER_AccountAccessTokenP access_token;
     47 
     48   /**
     49    * Authorization hash for the selected measure.
     50    */
     51   struct TALER_KycMeasureAuthorizationHashP shv;
     52 
     53   /**
     54    * Hash of the payto:// URI we are starting to the KYC for.
     55    */
     56   struct TALER_NormalizedPaytoHashP h_payto;
     57 
     58   /**
     59    * Kept in a DLL.
     60    */
     61   struct KycPoller *next;
     62 
     63   /**
     64    * Kept in a DLL.
     65    */
     66   struct KycPoller *prev;
     67 
     68   /**
     69    * Connection we are handling.
     70    */
     71   struct MHD_Connection *connection;
     72 
     73   /**
     74    * Logic for @e ih
     75    */
     76   struct TALER_KYCLOGIC_Plugin *ih_logic;
     77 
     78   /**
     79    * Handle to asynchronously running KYC initiation
     80    * request.
     81    */
     82   struct TALER_KYCLOGIC_InitiateHandle *ih;
     83 
     84   /**
     85    * Set of applicable KYC measures.
     86    */
     87   json_t *jmeasures;
     88 
     89   /**
     90    * Where to redirect the user to start the KYC process.
     91    */
     92   char *redirect_url;
     93 
     94   /**
     95    * Set to the name of the KYC provider.
     96    */
     97   const char *provider_name;
     98 
     99   /**
    100    * Set to error details, on error (@ec not TALER_EC_NONE).
    101    */
    102   char *hint;
    103 
    104   /**
    105    * Row of the requirement being started.
    106    */
    107   unsigned long long legitimization_measure_serial_id;
    108 
    109   /**
    110    * Row of KYC process being initiated.
    111    */
    112   uint64_t process_row;
    113 
    114   /**
    115    * Index of the measure this upload is for.
    116    */
    117   unsigned int measure_index;
    118 
    119   /**
    120    * Set to error encountered with KYC logic, if any.
    121    */
    122   enum TALER_ErrorCode ec;
    123 
    124   /**
    125    * True if we are still suspended.
    126    */
    127   bool suspended;
    128 
    129   /**
    130    * True if @e h_payto is for a wallet
    131    */
    132   bool is_wallet;
    133 
    134 };
    135 
    136 
    137 /**
    138  * Head of list of requests in asynchronous processing.
    139  */
    140 static struct KycPoller *kyp_head;
    141 
    142 /**
    143  * Tail of list of requests in asynchronous processing.
    144  */
    145 static struct KycPoller *kyp_tail;
    146 
    147 
    148 void
    149 TEH_kyc_start_cleanup ()
    150 {
    151   struct KycPoller *kyp;
    152 
    153   while (NULL != (kyp = kyp_head))
    154   {
    155     GNUNET_CONTAINER_DLL_remove (kyp_head,
    156                                  kyp_tail,
    157                                  kyp);
    158     if (NULL != kyp->ih)
    159     {
    160       kyp->ih_logic->initiate_cancel (kyp->ih);
    161       kyp->ih = NULL;
    162     }
    163     if (kyp->suspended)
    164     {
    165       kyp->suspended = false;
    166       MHD_resume_connection (kyp->connection);
    167     }
    168   }
    169 }
    170 
    171 
    172 /**
    173  * Function called once a connection is done to
    174  * clean up the `struct ReservePoller` state.
    175  *
    176  * @param rc context to clean up for
    177  */
    178 static void
    179 kyp_cleanup (struct TEH_RequestContext *rc)
    180 {
    181   struct KycPoller *kyp = rc->rh_ctx;
    182 
    183   GNUNET_assert (! kyp->suspended);
    184   if (NULL != kyp->ih)
    185   {
    186     kyp->ih_logic->initiate_cancel (kyp->ih);
    187     kyp->ih = NULL;
    188   }
    189   GNUNET_free (kyp->redirect_url);
    190   GNUNET_free (kyp->hint);
    191   json_decref (kyp->jmeasures);
    192   GNUNET_free (kyp);
    193 }
    194 
    195 
    196 /**
    197  * Function called with the result of a KYC initiation
    198  * operation.
    199  *
    200  * @param cls closure with our `struct KycPoller *`
    201  * @param ec #TALER_EC_NONE on success
    202  * @param redirect_url set to where to redirect the user on success, NULL on failure
    203  * @param provider_user_id set to user ID at the provider, or NULL if not supported or unknown
    204  * @param provider_legitimization_id set to legitimization process ID at the provider, or NULL if not supported or unknown
    205  * @param error_msg_hint set to additional details to return to user, NULL on success
    206  */
    207 static void
    208 initiate_cb (
    209   void *cls,
    210   enum TALER_ErrorCode ec,
    211   const char *redirect_url,
    212   const char *provider_user_id,
    213   const char *provider_legitimization_id,
    214   const char *error_msg_hint)
    215 {
    216   struct KycPoller *kyp = cls;
    217   enum GNUNET_DB_QueryStatus qs;
    218 
    219   kyp->ih = NULL;
    220   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    221               "KYC initiation `%s' completed with ec=%d (%s)\n",
    222               provider_legitimization_id,
    223               ec,
    224               (TALER_EC_NONE == ec)
    225               ? redirect_url
    226               : error_msg_hint);
    227   kyp->ec = ec;
    228   if (TALER_EC_NONE == ec)
    229   {
    230     kyp->redirect_url = GNUNET_strdup (redirect_url);
    231   }
    232   else
    233   {
    234     kyp->hint = GNUNET_strdup (error_msg_hint);
    235   }
    236   qs = TEH_plugin->update_kyc_process_by_row (
    237     TEH_plugin->cls,
    238     kyp->process_row,
    239     kyp->provider_name,
    240     &kyp->h_payto,
    241     provider_user_id,
    242     provider_legitimization_id,
    243     redirect_url,
    244     GNUNET_TIME_UNIT_ZERO_ABS,
    245     ec,
    246     error_msg_hint,
    247     TALER_EC_NONE != ec);
    248   if (qs <= 0)
    249     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    250                 "KYC requirement update failed for %s with status %d at %s:%u\n",
    251                 TALER_B2S (&kyp->h_payto),
    252                 qs,
    253                 __FILE__,
    254                 __LINE__);
    255   GNUNET_assert (kyp->suspended);
    256   kyp->suspended = false;
    257   GNUNET_CONTAINER_DLL_remove (kyp_head,
    258                                kyp_tail,
    259                                kyp);
    260   MHD_resume_connection (kyp->connection);
    261   TALER_MHD_daemon_trigger ();
    262 }
    263 
    264 
    265 MHD_RESULT
    266 TEH_handler_kyc_start (
    267   struct TEH_RequestContext *rc,
    268   const json_t *root,
    269   const char *const args[1])
    270 {
    271   struct KycPoller *kyp = rc->rh_ctx;
    272 
    273   (void) root;
    274   if (NULL == kyp)
    275   {
    276     const char *id = args[0];
    277     enum GNUNET_DB_QueryStatus qs;
    278     const struct TALER_KYCLOGIC_KycProvider *provider;
    279     struct TALER_KYCLOGIC_ProviderDetails *pd;
    280     bool is_finished;
    281     const json_t *context;
    282 
    283     kyp = GNUNET_new (struct KycPoller);
    284     kyp->connection = rc->connection;
    285     rc->rh_ctx = kyp;
    286     rc->rh_cleaner = &kyp_cleanup;
    287 
    288     {
    289       char dummy;
    290       const char *slash;
    291 
    292       slash = strchr (id, '-');
    293       if (NULL == slash)
    294       {
    295         GNUNET_break_op (0);
    296         return TALER_MHD_reply_with_error (
    297           rc->connection,
    298           MHD_HTTP_NOT_FOUND,
    299           TALER_EC_GENERIC_ENDPOINT_UNKNOWN,
    300           rc->url);
    301       }
    302       if (GNUNET_OK !=
    303           GNUNET_STRINGS_string_to_data (id,
    304                                          slash - id,
    305                                          &kyp->shv,
    306                                          sizeof (kyp->shv)))
    307       {
    308         GNUNET_break_op (0);
    309         return TALER_MHD_reply_with_error (
    310           rc->connection,
    311           MHD_HTTP_BAD_REQUEST,
    312           TALER_EC_GENERIC_PARAMETER_MALFORMED,
    313           "Authorization hash in ID is malformed");
    314       }
    315       if (2 !=
    316           sscanf (slash + 1,
    317                   "%u-%llu%c",
    318                   &kyp->measure_index,
    319                   &kyp->legitimization_measure_serial_id,
    320                   &dummy))
    321       {
    322         GNUNET_break_op (0);
    323         return TALER_MHD_reply_with_error (
    324           rc->connection,
    325           MHD_HTTP_BAD_REQUEST,
    326           TALER_EC_GENERIC_PARAMETER_MALFORMED,
    327           "ID is malformed");
    328       }
    329     }
    330     qs = TEH_plugin->lookup_pending_legitimization (
    331       TEH_plugin->cls,
    332       kyp->legitimization_measure_serial_id,
    333       &kyp->access_token,
    334       &kyp->h_payto,
    335       &kyp->jmeasures,
    336       &is_finished,
    337       &kyp->is_wallet);
    338     if (qs < 0)
    339     {
    340       GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR != qs);
    341       return TALER_MHD_reply_with_ec (
    342         rc->connection,
    343         TALER_EC_GENERIC_DB_FETCH_FAILED,
    344         "lookup_pending_legitimization");
    345     }
    346     if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
    347     {
    348       GNUNET_break_op (0);
    349       return TALER_MHD_reply_with_error (
    350         rc->connection,
    351         MHD_HTTP_NOT_FOUND,
    352         TALER_EC_GENERIC_ENDPOINT_UNKNOWN,
    353         rc->url);
    354     }
    355     if (is_finished)
    356     {
    357       GNUNET_break_op (0);
    358       return TALER_MHD_reply_with_error (
    359         rc->connection,
    360         MHD_HTTP_CONFLICT,
    361         TALER_EC_EXCHANGE_KYC_FORM_ALREADY_UPLOADED,
    362         rc->url);
    363     }
    364     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    365                 "Found pending legitimization %llu with measures\n",
    366                 (unsigned long long) kyp->legitimization_measure_serial_id);
    367     json_dumpf (kyp->jmeasures,
    368                 stderr,
    369                 JSON_INDENT (2));
    370     fprintf (stderr,
    371              "\n");
    372 
    373     {
    374       struct TALER_KycMeasureAuthorizationHashP shv2;
    375 
    376       TALER_kyc_measure_authorization_hash (
    377         &kyp->access_token,
    378         kyp->legitimization_measure_serial_id,
    379         kyp->measure_index,
    380         &shv2);
    381       if (0 !=
    382           GNUNET_memcmp (&kyp->shv,
    383                          &shv2))
    384       {
    385         GNUNET_break_op (0);
    386         return TALER_MHD_reply_with_error (
    387           rc->connection,
    388           MHD_HTTP_NOT_FOUND,
    389           TALER_EC_GENERIC_ENDPOINT_UNKNOWN,
    390           rc->url);
    391       }
    392     }
    393 
    394     {
    395       const char *check_name;
    396       const char *prog_name;
    397 
    398       kyp->ec = TALER_KYCLOGIC_select_measure (
    399         kyp->jmeasures,
    400         kyp->measure_index,
    401         &check_name,
    402         &prog_name,
    403         &context);
    404       if (TALER_EC_NONE != kyp->ec)
    405       {
    406         /* return EC in next call to this function */
    407         GNUNET_break_op (0);
    408         kyp->hint
    409           = GNUNET_strdup ("TALER_KYCLOGIC_select_measure");
    410         return MHD_YES;
    411       }
    412 
    413       GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    414                   "Selected measure is %llu with check `%s'\n",
    415                   (unsigned long long) kyp->measure_index,
    416                   check_name);
    417       if (NULL != context)
    418       {
    419         json_dumpf (context,
    420                     stderr,
    421                     JSON_INDENT (2));
    422         fprintf (stderr,
    423                  "\n");
    424       }
    425 
    426       provider = TALER_KYCLOGIC_check_to_provider (
    427         check_name);
    428       if (NULL == provider)
    429       {
    430         GNUNET_break_op (0);
    431         return TALER_MHD_reply_with_error (
    432           rc->connection,
    433           MHD_HTTP_CONFLICT,
    434           TALER_EC_EXCHANGE_KYC_INVALID_LOGIC_TO_CHECK,
    435           check_name);
    436       }
    437     }
    438 
    439     TALER_KYCLOGIC_provider_to_logic (
    440       provider,
    441       &kyp->ih_logic,
    442       &pd,
    443       &kyp->provider_name);
    444 
    445     /* FIXME-#9419: the next two DB interactions should be ONE
    446        transaction */
    447     /* Check if we already initiated this process */
    448     qs = TEH_plugin->get_pending_kyc_requirement_process (
    449       TEH_plugin->cls,
    450       &kyp->h_payto,
    451       kyp->provider_name,
    452       &kyp->redirect_url);
    453     if (qs < 0)
    454     {
    455       GNUNET_break (0);
    456       /* Simple query, never should be a soft error. */
    457       GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR != qs);
    458       return TALER_MHD_reply_with_error (
    459         rc->connection,
    460         MHD_HTTP_INTERNAL_SERVER_ERROR,
    461         TALER_EC_GENERIC_DB_FETCH_FAILED,
    462         "get_pending_kyc_requirement_process");
    463     }
    464     if (NULL != kyp->redirect_url)
    465       return MHD_YES; /* success, return the redirect URL
    466                          (in next call to this function) */
    467 
    468     /* set up new requirement process */
    469     qs = TEH_plugin->insert_kyc_requirement_process (
    470       TEH_plugin->cls,
    471       &kyp->h_payto,
    472       kyp->measure_index,
    473       kyp->legitimization_measure_serial_id,
    474       kyp->provider_name,
    475       NULL, /* provider_account_id */
    476       NULL, /* provider_legitimziation_id */
    477       &kyp->process_row);
    478     if (qs < 0)
    479     {
    480       GNUNET_break (0);
    481       return TALER_MHD_reply_with_error (
    482         rc->connection,
    483         MHD_HTTP_INTERNAL_SERVER_ERROR,
    484         TALER_EC_GENERIC_DB_STORE_FAILED,
    485         "insert_kyc_requirement_process");
    486     }
    487 
    488     kyp->ih = kyp->ih_logic->initiate (
    489       kyp->ih_logic->cls,
    490       pd,
    491       &kyp->h_payto,
    492       kyp->process_row,
    493       context,
    494       &initiate_cb,
    495       kyp);
    496     if (NULL == kyp->ih)
    497     {
    498       GNUNET_break (0);
    499       return TALER_MHD_reply_with_error (
    500         rc->connection,
    501         MHD_HTTP_INTERNAL_SERVER_ERROR,
    502         TALER_EC_EXCHANGE_KYC_GENERIC_LOGIC_BUG,
    503         "initiate KYC process");
    504     }
    505     kyp->suspended = true;
    506     GNUNET_CONTAINER_DLL_insert (kyp_head,
    507                                  kyp_tail,
    508                                  kyp);
    509     MHD_suspend_connection (kyp->connection);
    510     return MHD_YES;
    511   }
    512 
    513   if ( (TALER_EC_NONE != kyp->ec) ||
    514        (NULL == kyp->redirect_url) )
    515   {
    516     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    517                 "KYC process failed to start with error %d (%s)\n",
    518                 (int) kyp->ec,
    519                 kyp->hint);
    520     if (TALER_EC_NONE == kyp->ec)
    521     {
    522       GNUNET_break (0);
    523       kyp->ec = TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
    524     }
    525     return TALER_MHD_reply_with_ec (rc->connection,
    526                                     kyp->ec,
    527                                     kyp->hint);
    528   }
    529   return TALER_MHD_REPLY_JSON_PACK (
    530     rc->connection,
    531     MHD_HTTP_OK,
    532     GNUNET_JSON_pack_string ("redirect_url",
    533                              kyp->redirect_url));
    534 }
    535 
    536 
    537 /* end of taler-exchange-httpd_kyc-start.c */