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_post-kyc-upload-ID.c (16361B)


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