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-upload.c (14711B)


      1 /*
      2   This file is part of TALER
      3   Copyright (C) 2025 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-upload.c
     18  * @brief Handle /kyc-upload/$ID request
     19  * @author Christian Grothoff
     20  */
     21 #include "taler/platform.h"
     22 #include "taler-exchange-httpd_common_kyc.h"
     23 #include "taler-exchange-httpd_kyc-upload.h"
     24 
     25 #define MAX_RETRIES 3
     26 
     27 /**
     28  * Context used for processing the KYC upload req
     29  */
     30 struct UploadContext
     31 {
     32 
     33   /**
     34    * Kept in a DLL.
     35    */
     36   struct UploadContext *next;
     37 
     38   /**
     39    * Kept in a DLL.
     40    */
     41   struct UploadContext *prev;
     42 
     43   /**
     44    * Access token for the KYC data of the account.
     45    */
     46   struct TALER_AccountAccessTokenP access_token;
     47 
     48   /**
     49    * Index of the measure this upload is for.
     50    */
     51   unsigned int measure_index;
     52 
     53   /**
     54    * HTTP status code to use with @e response.
     55    */
     56   unsigned int response_code;
     57 
     58   /**
     59    * Index in the legitimization measures table this ID
     60    * refers to.
     61    */
     62   unsigned long long legitimization_measure_serial_id;
     63 
     64   /**
     65    * Response to return, NULL if none yet.
     66    */
     67   struct MHD_Response *response;
     68 
     69   /**
     70    * Request we are processing.
     71    */
     72   struct TEH_RequestContext *rc;
     73 
     74   /**
     75    * Handle for async KYC processing.
     76    */
     77   struct TEH_KycMeasureRunContext *kat;
     78 
     79   /**
     80    * Uploaded data, in JSON.
     81    */
     82   const json_t *result;
     83 
     84   /**
     85    * Set by the transaction to the legitimization process row.
     86    */
     87   uint64_t legi_process_row;
     88 
     89   /**
     90    * Set by the transaction to the affected account payto hash.
     91    */
     92   struct TALER_NormalizedPaytoHashP h_payto;
     93 
     94   /**
     95    * Set by the transaction to true if the account is for a wallet.
     96    */
     97   bool is_wallet;
     98 
     99 };
    100 
    101 
    102 /**
    103  * Kept in a DLL.
    104  */
    105 static struct UploadContext *uc_head;
    106 
    107 /**
    108  * Kept in a DLL.
    109  */
    110 static struct UploadContext *uc_tail;
    111 
    112 
    113 void
    114 TEH_kyc_upload_cleanup ()
    115 {
    116   struct UploadContext *uc;
    117 
    118   while (NULL != (uc = uc_head))
    119   {
    120     MHD_resume_connection (uc->rc->connection);
    121     GNUNET_CONTAINER_DLL_remove (uc_head,
    122                                  uc_tail,
    123                                  uc);
    124   }
    125 }
    126 
    127 
    128 /**
    129  * Function called to clean up upload context.
    130  *
    131  * @param[in,out] rc context to clean up
    132  */
    133 static void
    134 upload_cleaner (struct TEH_RequestContext *rc)
    135 {
    136   struct UploadContext *uc = rc->rh_ctx;
    137 
    138   if (NULL != uc->kat)
    139   {
    140     TEH_kyc_run_measure_cancel (uc->kat);
    141     uc->kat = NULL;
    142   }
    143   if (NULL != uc->response)
    144   {
    145     MHD_destroy_response (uc->response);
    146     uc->response = NULL;
    147   }
    148   GNUNET_free (uc);
    149 }
    150 
    151 
    152 /**
    153  * Function called after the KYC-AML trigger is done.
    154  *
    155  * @param cls closure
    156  * @param ec error code or 0 on success
    157  * @param detail error message or NULL on success / no info
    158  */
    159 static void
    160 aml_trigger_callback (
    161   void *cls,
    162   enum TALER_ErrorCode ec,
    163   const char *detail)
    164 {
    165   struct UploadContext *uc = cls;
    166 
    167   uc->kat = NULL;
    168   GNUNET_assert (NULL == uc->response);
    169   if (TALER_EC_NONE != ec)
    170   {
    171     uc->response_code = TALER_ErrorCode_get_http_status (ec);
    172     if (0 == uc->response_code)
    173     {
    174       GNUNET_break (0);
    175       uc->response_code = MHD_HTTP_INTERNAL_SERVER_ERROR;
    176     }
    177     GNUNET_assert (uc->response_code != UINT_MAX);
    178     uc->response = TALER_MHD_make_error (
    179       ec,
    180       detail);
    181   }
    182   else
    183   {
    184     uc->response_code = MHD_HTTP_NO_CONTENT;
    185     uc->response = MHD_create_response_from_buffer_static (
    186       0,
    187       ""
    188       );
    189     TALER_MHD_add_global_headers (uc->response,
    190                                   true);
    191   }
    192 
    193   MHD_resume_connection (uc->rc->connection);
    194   GNUNET_CONTAINER_DLL_remove (uc_head,
    195                                uc_tail,
    196                                uc);
    197   TALER_MHD_daemon_trigger ();
    198 }
    199 
    200 
    201 /**
    202  * Do the main database transaction.
    203  *
    204  * @param cls closure with a `struct UploadContext`
    205  * @param connection MHD request which triggered the transaction
    206  * @param[out] mhd_ret set to MHD response status for @a connection,
    207  *             if transaction failed (!)
    208  */
    209 static enum GNUNET_DB_QueryStatus
    210 transact (void *cls,
    211           struct MHD_Connection *connection,
    212           MHD_RESULT *mhd_ret)
    213 {
    214   struct UploadContext *uc = cls;
    215   struct TEH_RequestContext *rc = uc->rc;
    216   enum GNUNET_DB_QueryStatus qs;
    217   json_t *jmeasures;
    218   bool is_finished = false;
    219   size_t enc_attributes_len;
    220   void *enc_attributes;
    221   const char *error_message;
    222   char *form_name;
    223   enum TALER_ErrorCode ec;
    224 
    225   qs = TEH_plugin->lookup_completed_legitimization (
    226     TEH_plugin->cls,
    227     uc->legitimization_measure_serial_id,
    228     uc->measure_index,
    229     &uc->access_token,
    230     &uc->h_payto,
    231     &uc->is_wallet,
    232     &jmeasures,
    233     &is_finished,
    234     &enc_attributes_len,
    235     &enc_attributes);
    236   /* FIXME: not exactly performant/elegant, should eventually
    237      modify lookup_completed_legitimization to
    238      return something if we are purely pending? */
    239   switch (qs)
    240   {
    241   case GNUNET_DB_STATUS_HARD_ERROR:
    242     GNUNET_break (0);
    243     *mhd_ret = TALER_MHD_reply_with_error (
    244       rc->connection,
    245       MHD_HTTP_INTERNAL_SERVER_ERROR,
    246       TALER_EC_GENERIC_DB_FETCH_FAILED,
    247       "lookup_completed_legitimization");
    248     return qs;
    249   case GNUNET_DB_STATUS_SOFT_ERROR:
    250     return qs;
    251   case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
    252     qs = TEH_plugin->lookup_pending_legitimization (
    253       TEH_plugin->cls,
    254       uc->legitimization_measure_serial_id,
    255       &uc->access_token,
    256       &uc->h_payto,
    257       &jmeasures,
    258       &is_finished,
    259       &uc->is_wallet);
    260     switch (qs)
    261     {
    262     case GNUNET_DB_STATUS_HARD_ERROR:
    263       GNUNET_break (0);
    264       *mhd_ret = TALER_MHD_reply_with_error (
    265         rc->connection,
    266         MHD_HTTP_INTERNAL_SERVER_ERROR,
    267         TALER_EC_GENERIC_DB_FETCH_FAILED,
    268         "lookup_pending_legitimization");
    269       return qs;
    270     case GNUNET_DB_STATUS_SOFT_ERROR:
    271       return qs;
    272     case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
    273       GNUNET_break_op (0);
    274       *mhd_ret = TALER_MHD_reply_with_error (
    275         rc->connection,
    276         MHD_HTTP_NOT_FOUND,
    277         TALER_EC_EXCHANGE_KYC_CHECK_REQUEST_UNKNOWN,
    278         NULL);
    279       return GNUNET_DB_STATUS_HARD_ERROR;
    280     case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
    281       break;
    282     }
    283     break;
    284   case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
    285     break;
    286   }
    287 
    288   if (NULL != enc_attributes)
    289   {
    290     json_t *xattributes;
    291 
    292     xattributes
    293       = TALER_CRYPTO_kyc_attributes_decrypt (
    294           &TEH_attribute_key,
    295           enc_attributes,
    296           enc_attributes_len);
    297     if (json_equal (xattributes,
    298                     uc->result))
    299     {
    300       /* Request is idempotent! */
    301       json_decref (xattributes);
    302       GNUNET_free (enc_attributes);
    303       json_decref (jmeasures);
    304       if (is_finished)
    305       {
    306         *mhd_ret = TALER_MHD_reply_static (
    307           rc->connection,
    308           MHD_HTTP_NO_CONTENT,
    309           NULL,
    310           NULL,
    311           0);
    312         return GNUNET_DB_STATUS_HARD_ERROR;
    313       }
    314 
    315       /* Note: problem below is not here, but likely some previous
    316          upload of the attributes failed badly in an AML program. */
    317       GNUNET_break (0);
    318       *mhd_ret = TALER_MHD_reply_with_error (
    319         rc->connection,
    320         MHD_HTTP_INTERNAL_SERVER_ERROR,
    321         TALER_EC_EXCHANGE_KYC_GENERIC_AML_LOGIC_BUG,
    322         "attributes known, but legitimization process failed");
    323       return GNUNET_DB_STATUS_HARD_ERROR;
    324     }
    325     json_decref (xattributes);
    326     GNUNET_free (enc_attributes);
    327     json_decref (jmeasures);
    328     /* Form was already done with with different attributes, conflict! */
    329     GNUNET_break_op (0);
    330     *mhd_ret = TALER_MHD_reply_with_error (
    331       rc->connection,
    332       MHD_HTTP_CONFLICT,
    333       TALER_EC_EXCHANGE_KYC_FORM_ALREADY_UPLOADED,
    334       NULL);
    335     return GNUNET_DB_STATUS_HARD_ERROR;
    336   }
    337   if (is_finished)
    338   {
    339     /* This should not be possible (is_finished but NULL==enc_attributes),
    340        but also we should not run logic again if we are finished. */
    341     GNUNET_break_op (0);
    342     json_decref (jmeasures);
    343     *mhd_ret = TALER_MHD_reply_with_error (
    344       rc->connection,
    345       MHD_HTTP_CONFLICT,
    346       TALER_EC_EXCHANGE_KYC_FORM_ALREADY_UPLOADED,
    347       NULL);
    348     return GNUNET_DB_STATUS_HARD_ERROR;
    349   }
    350   ec = TALER_KYCLOGIC_check_form (jmeasures,
    351                                   uc->measure_index,
    352                                   uc->result,
    353                                   &form_name,
    354                                   &error_message);
    355   if (TALER_EC_NONE != ec)
    356   {
    357     GNUNET_break_op (0);
    358     json_decref (jmeasures);
    359     *mhd_ret = TALER_MHD_reply_with_ec (
    360       rc->connection,
    361       ec,
    362       error_message);
    363     return GNUNET_DB_STATUS_HARD_ERROR;
    364   }
    365   json_decref (jmeasures);
    366 
    367   /* Setup KYC process (which we will then immediately 'finish') */
    368   qs = TEH_plugin->insert_kyc_requirement_process (
    369     TEH_plugin->cls,
    370     &uc->h_payto,
    371     uc->measure_index,
    372     uc->legitimization_measure_serial_id,
    373     form_name,
    374     NULL,       /* provider account ID */
    375     NULL,       /* provider legi ID */
    376     &uc->legi_process_row);
    377   switch (qs)
    378   {
    379   case GNUNET_DB_STATUS_HARD_ERROR:
    380     GNUNET_break (0);
    381     GNUNET_free (form_name);
    382     *mhd_ret = TALER_MHD_reply_with_error (
    383       rc->connection,
    384       MHD_HTTP_INTERNAL_SERVER_ERROR,
    385       TALER_EC_GENERIC_DB_STORE_FAILED,
    386       "insert_kyc_requirement_process");
    387     return GNUNET_DB_STATUS_HARD_ERROR;
    388   case GNUNET_DB_STATUS_SOFT_ERROR:
    389     GNUNET_free (form_name);
    390     return qs;
    391   case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
    392     GNUNET_break (0);
    393     GNUNET_free (form_name);
    394     *mhd_ret = TALER_MHD_reply_with_error (
    395       rc->connection,
    396       MHD_HTTP_INTERNAL_SERVER_ERROR,
    397       TALER_EC_GENERIC_DB_INVARIANT_FAILURE,
    398       "insert_kyc_requirement_process");
    399     return GNUNET_DB_STATUS_HARD_ERROR;
    400   case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
    401     break;
    402   }
    403   qs = TEH_kyc_store_attributes (
    404     uc->legi_process_row,
    405     &uc->h_payto,
    406     form_name,
    407     NULL /* provider account */,
    408     NULL /* provider legi ID */,
    409     GNUNET_TIME_UNIT_FOREVER_ABS,   /* expiration time */
    410     uc->result);
    411   GNUNET_free (form_name);
    412   switch (qs)
    413   {
    414   case GNUNET_DB_STATUS_HARD_ERROR:
    415     GNUNET_break (0);
    416     *mhd_ret = TALER_MHD_reply_with_error (
    417       rc->connection,
    418       MHD_HTTP_INTERNAL_SERVER_ERROR,
    419       TALER_EC_GENERIC_DB_STORE_FAILED,
    420       "kyc_store_attributes");
    421     return GNUNET_DB_STATUS_HARD_ERROR;
    422   case GNUNET_DB_STATUS_SOFT_ERROR:
    423     return qs;
    424   case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
    425     GNUNET_break (0);
    426     *mhd_ret = TALER_MHD_reply_with_error (
    427       rc->connection,
    428       MHD_HTTP_INTERNAL_SERVER_ERROR,
    429       TALER_EC_GENERIC_DB_STORE_FAILED,
    430       "kyc_store_attributes");
    431     return GNUNET_DB_STATUS_HARD_ERROR;
    432   case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
    433     return qs;
    434   }
    435   GNUNET_assert (0);
    436   *mhd_ret = MHD_NO;
    437   return GNUNET_DB_STATUS_HARD_ERROR;
    438 }
    439 
    440 
    441 MHD_RESULT
    442 TEH_handler_kyc_upload (
    443   struct TEH_RequestContext *rc,
    444   const json_t *root,
    445   const char *const args[1])
    446 {
    447   struct UploadContext *uc = rc->rh_ctx;
    448   const char *id = args[0];
    449 
    450   if (NULL == uc)
    451   {
    452     const char *slash;
    453     char dummy;
    454 
    455     uc = GNUNET_new (struct UploadContext);
    456     uc->rc = rc;
    457     uc->result = root;
    458     rc->rh_ctx = uc;
    459     rc->rh_cleaner = &upload_cleaner;
    460     slash = strchr (id, '-');
    461     if (NULL == slash)
    462     {
    463       GNUNET_break_op (0);
    464       return TALER_MHD_reply_with_error (
    465         rc->connection,
    466         MHD_HTTP_NOT_FOUND,
    467         TALER_EC_GENERIC_ENDPOINT_UNKNOWN,
    468         rc->url);
    469     }
    470     if (GNUNET_OK !=
    471         GNUNET_STRINGS_string_to_data (id,
    472                                        slash - id,
    473                                        &uc->access_token,
    474                                        sizeof (uc->access_token)))
    475     {
    476       GNUNET_break_op (0);
    477       return TALER_MHD_reply_with_error (
    478         rc->connection,
    479         MHD_HTTP_BAD_REQUEST,
    480         TALER_EC_GENERIC_PARAMETER_MALFORMED,
    481         "Access token in ID is malformed");
    482     }
    483     if (2 !=
    484         sscanf (slash + 1,
    485                 "%u-%llu%c",
    486                 &uc->measure_index,
    487                 &uc->legitimization_measure_serial_id,
    488                 &dummy))
    489     {
    490       GNUNET_break_op (0);
    491       return TALER_MHD_reply_with_error (
    492         rc->connection,
    493         MHD_HTTP_BAD_REQUEST,
    494         TALER_EC_GENERIC_PARAMETER_MALFORMED,
    495         "ID is malformed");
    496     }
    497     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    498                 "/kyc-upload received form submission\n");
    499     if ( (NULL != root) &&
    500          (! json_is_string (json_object_get (root,
    501                                              "FORM_ID"))) )
    502     {
    503       GNUNET_break_op (0);
    504       return TALER_MHD_reply_with_error (
    505         rc->connection,
    506         MHD_HTTP_BAD_REQUEST,
    507         TALER_EC_GENERIC_PARAMETER_MALFORMED,
    508         "FORM_ID");
    509     }
    510     json_dumpf (root,
    511                 stderr,
    512                 JSON_INDENT (2));
    513   }
    514   if (NULL != uc->response)
    515   {
    516     return MHD_queue_response (rc->connection,
    517                                uc->response_code,
    518                                uc->response);
    519 
    520   }
    521 
    522   if (GNUNET_OK !=
    523       TEH_plugin->preflight (TEH_plugin->cls))
    524   {
    525     GNUNET_break (0);
    526     return TALER_MHD_reply_with_error (
    527       rc->connection,
    528       MHD_HTTP_INTERNAL_SERVER_ERROR,
    529       TALER_EC_GENERIC_DB_SETUP_FAILED,
    530       NULL);
    531   }
    532 
    533   {
    534     MHD_RESULT mhd_ret = -1;
    535 
    536     if (GNUNET_OK !=
    537         TEH_DB_run_transaction (rc->connection,
    538                                 "kyc-upload",
    539                                 TEH_MT_REQUEST_KYC_UPLOAD,
    540                                 &mhd_ret,
    541                                 &transact,
    542                                 uc))
    543       return mhd_ret;
    544   }
    545 
    546   uc->kat = TEH_kyc_run_measure_for_attributes (
    547     &rc->async_scope_id,
    548     uc->legi_process_row,
    549     &uc->h_payto,
    550     uc->is_wallet,
    551     &aml_trigger_callback,
    552     uc);
    553   if (NULL == uc->kat)
    554   {
    555     GNUNET_break (0);
    556     return TALER_MHD_reply_with_error (
    557       rc->connection,
    558       MHD_HTTP_INTERNAL_SERVER_ERROR,
    559       TALER_EC_EXCHANGE_KYC_GENERIC_AML_LOGIC_BUG,
    560       "TEH_kyc_finished");
    561   }
    562   MHD_suspend_connection (uc->rc->connection);
    563   GNUNET_CONTAINER_DLL_insert (uc_head,
    564                                uc_tail,
    565                                uc);
    566   return MHD_YES;
    567 }