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-proof.c (17000B)


      1 /*
      2   This file is part of TALER
      3   Copyright (C) 2021-2023 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-proof.c
     18  * @brief Handle request for proof for 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 "taler/taler_attributes.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_templating_lib.h"
     31 #include "taler-exchange-httpd_common_kyc.h"
     32 #include "taler-exchange-httpd_kyc-proof.h"
     33 #include "taler-exchange-httpd_responses.h"
     34 
     35 
     36 /**
     37  * Context for the proof.
     38  */
     39 struct KycProofContext
     40 {
     41 
     42   /**
     43    * Kept in a DLL while suspended.
     44    */
     45   struct KycProofContext *next;
     46 
     47   /**
     48    * Kept in a DLL while suspended.
     49    */
     50   struct KycProofContext *prev;
     51 
     52   /**
     53    * Details about the connection we are processing.
     54    */
     55   struct TEH_RequestContext *rc;
     56 
     57   /**
     58    * Proof logic to run.
     59    */
     60   struct TALER_KYCLOGIC_Plugin *logic;
     61 
     62   /**
     63    * Configuration for @a logic.
     64    */
     65   struct TALER_KYCLOGIC_ProviderDetails *pd;
     66 
     67   /**
     68    * Asynchronous operation with the proof system.
     69    */
     70   struct TALER_KYCLOGIC_ProofHandle *ph;
     71 
     72   /**
     73    * KYC AML trigger operation.
     74    */
     75   struct TEH_KycMeasureRunContext *kat;
     76 
     77   /**
     78    * Process information about the user for the plugin from the database, can
     79    * be NULL.
     80    */
     81   char *provider_user_id;
     82 
     83   /**
     84    * Process information about the legitimization process for the plugin from the
     85    * database, can be NULL.
     86    */
     87   char *provider_legitimization_id;
     88 
     89   /**
     90    * Hash of payment target URI this is about.
     91    */
     92   struct TALER_NormalizedPaytoHashP h_payto;
     93 
     94   /**
     95    * Final HTTP response to return.
     96    */
     97   struct MHD_Response *response;
     98 
     99   /**
    100    * Final HTTP response code to return.
    101    */
    102   unsigned int response_code;
    103 
    104   /**
    105    * HTTP response from the KYC provider plugin.
    106    */
    107   struct MHD_Response *proof_response;
    108 
    109   /**
    110    * HTTP response code from the KYC provider plugin.
    111    */
    112   unsigned int proof_response_code;
    113 
    114   /**
    115    * Provider configuration section name of the logic we are running.
    116    */
    117   const char *provider_name;
    118 
    119   /**
    120    * Row in the database for this legitimization operation.
    121    */
    122   uint64_t process_row;
    123 
    124   /**
    125    * True if we are 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  * Contexts are kept in a DLL while suspended.
    139  */
    140 static struct KycProofContext *kpc_head;
    141 
    142 /**
    143  * Contexts are kept in a DLL while suspended.
    144  */
    145 static struct KycProofContext *kpc_tail;
    146 
    147 
    148 /**
    149  * Generate HTML error for @a connection using @a template.
    150  *
    151  * @param connection HTTP client connection
    152  * @param template template to expand
    153  * @param[in,out] http_status HTTP status of the response
    154  * @param ec Taler error code to return
    155  * @param message extended message to return
    156  * @return MHD response object
    157  */
    158 static struct MHD_Response *
    159 make_html_error (struct MHD_Connection *connection,
    160                  const char *template,
    161                  unsigned int *http_status,
    162                  enum TALER_ErrorCode ec,
    163                  const char *message)
    164 {
    165   struct MHD_Response *response = NULL;
    166   json_t *body;
    167   enum GNUNET_GenericReturnValue ret;
    168 
    169   body = GNUNET_JSON_PACK (
    170     GNUNET_JSON_pack_allow_null (
    171       GNUNET_JSON_pack_string ("message",
    172                                message)),
    173     TALER_JSON_pack_ec (
    174       ec));
    175   ret = TALER_TEMPLATING_build (connection,
    176                                 http_status,
    177                                 template,
    178                                 NULL,
    179                                 NULL,
    180                                 body,
    181                                 &response);
    182   GNUNET_break (GNUNET_SYSERR != ret);
    183   if (GNUNET_SYSERR != ret)
    184     GNUNET_break (MHD_NO !=
    185                   MHD_add_response_header (response,
    186                                            MHD_HTTP_HEADER_CONTENT_TYPE,
    187                                            "text/html"));
    188 
    189   json_decref (body);
    190   return response;
    191 }
    192 
    193 
    194 /**
    195  * Resume processing the @a kpc request.
    196  *
    197  * @param kpc request to resume
    198  */
    199 static void
    200 kpc_resume (struct KycProofContext *kpc)
    201 {
    202   GNUNET_assert (GNUNET_YES == kpc->suspended);
    203   kpc->suspended = false;
    204   GNUNET_CONTAINER_DLL_remove (kpc_head,
    205                                kpc_tail,
    206                                kpc);
    207   MHD_resume_connection (kpc->rc->connection);
    208   TALER_MHD_daemon_trigger ();
    209 }
    210 
    211 
    212 void
    213 TEH_kyc_proof_cleanup (void)
    214 {
    215   struct KycProofContext *kpc;
    216 
    217   while (NULL != (kpc = kpc_head))
    218   {
    219     if (NULL != kpc->ph)
    220     {
    221       kpc->logic->proof_cancel (kpc->ph);
    222       kpc->ph = NULL;
    223     }
    224     kpc_resume (kpc);
    225   }
    226 }
    227 
    228 
    229 /**
    230  * Function called after the KYC-AML trigger is done.
    231  *
    232  * @param cls closure
    233  * @param ec error code or 0 on success
    234  * @param detail error message or NULL on success / no info
    235  */
    236 static void
    237 proof_finish (
    238   void *cls,
    239   enum TALER_ErrorCode ec,
    240   const char *detail)
    241 {
    242   struct KycProofContext *kpc = cls;
    243 
    244   kpc->kat = NULL;
    245   if (TALER_EC_NONE != ec)
    246   {
    247     kpc->response_code = TALER_ErrorCode_get_http_status (ec);
    248     GNUNET_break (5 != kpc->response_code / 100);
    249     GNUNET_assert (kpc->response_code != UINT_MAX);
    250     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    251                 "Templating error response for %d and HTTP status %u (%s)\n",
    252                 (int) ec,
    253                 kpc->response_code,
    254                 detail);
    255     kpc->response = make_html_error (
    256       kpc->rc->connection,
    257       "kyc-proof-internal-error",
    258       &kpc->response_code,
    259       ec,
    260       detail);
    261   }
    262   else
    263   {
    264     GNUNET_assert (NULL != kpc->proof_response);
    265     kpc->response_code = kpc->proof_response_code;
    266     kpc->response = kpc->proof_response;
    267     kpc->proof_response = NULL;
    268     kpc->proof_response_code = 0;
    269   }
    270   GNUNET_assert (NULL != kpc->response);
    271   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    272               "Resuming with response %p and status %u\n",
    273               kpc->response,
    274               kpc->response_code);
    275   kpc_resume (kpc);
    276 }
    277 
    278 
    279 /**
    280  * Respond with an HTML message on the given @a rc.
    281  *
    282  * @param[in,out] rc request to respond to
    283  * @param http_status HTTP status code to use
    284  * @param template template to fill in
    285  * @param ec error code to use for the template
    286  * @param message additional message to return
    287  * @return MHD result code
    288  */
    289 static MHD_RESULT
    290 respond_html_ec (struct TEH_RequestContext *rc,
    291                  unsigned int http_status,
    292                  const char *template,
    293                  enum TALER_ErrorCode ec,
    294                  const char *message)
    295 {
    296   struct MHD_Response *response;
    297   MHD_RESULT res;
    298 
    299   response = make_html_error (rc->connection,
    300                               template,
    301                               &http_status,
    302                               ec,
    303                               message);
    304   res = MHD_queue_response (rc->connection,
    305                             http_status,
    306                             response);
    307   MHD_destroy_response (response);
    308   return res;
    309 }
    310 
    311 
    312 /**
    313  * Function called with the result of a proof check operation.
    314  *
    315  * Note that the "decref" for the @a response
    316  * will be done by the callee and MUST NOT be done by the plugin.
    317  *
    318  * @param cls closure
    319  * @param status KYC status
    320  * @param provider_name name of the provider
    321  * @param provider_user_id set to user ID at the provider, or NULL if not supported or unknown
    322  * @param provider_legitimization_id set to legitimization process ID at the provider, or NULL if not supported or unknown
    323  * @param expiration until when is the KYC check valid
    324  * @param attributes user attributes returned by the provider
    325  * @param http_status HTTP status code of @a response
    326  * @param[in] response to return to the HTTP client
    327  */
    328 static void
    329 proof_cb (
    330   void *cls,
    331   enum TALER_KYCLOGIC_KycStatus status,
    332   const char *provider_name,
    333   const char *provider_user_id,
    334   const char *provider_legitimization_id,
    335   struct GNUNET_TIME_Absolute expiration,
    336   const json_t *attributes,
    337   unsigned int http_status,
    338   struct MHD_Response *response)
    339 {
    340   struct KycProofContext *kpc = cls;
    341   struct TEH_RequestContext *rc = kpc->rc;
    342   struct GNUNET_AsyncScopeSave old_scope;
    343   enum GNUNET_DB_QueryStatus qs;
    344 
    345   kpc->ph = NULL;
    346   kpc->proof_response = response;
    347   kpc->proof_response_code = http_status;
    348   GNUNET_async_scope_enter (&rc->async_scope_id,
    349                             &old_scope);
    350   switch (status)
    351   {
    352   case TALER_KYCLOGIC_STATUS_SUCCESS:
    353     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    354                 "KYC process #%llu succeeded with KYC provider\n",
    355                 (unsigned long long) kpc->process_row);
    356     qs = TEH_kyc_store_attributes (
    357       kpc->process_row,
    358       &kpc->h_payto,
    359       provider_name,
    360       provider_user_id,
    361       provider_legitimization_id,
    362       expiration,
    363       attributes);
    364     if (0 >= qs)
    365     {
    366       GNUNET_break (0);
    367       proof_finish (kpc,
    368                     TALER_EC_GENERIC_DB_STORE_FAILED,
    369                     "kyc_store_attributes");
    370       break;
    371     }
    372 
    373     kpc->kat = TEH_kyc_run_measure_for_attributes (
    374       &rc->async_scope_id,
    375       kpc->process_row,
    376       &kpc->h_payto,
    377       kpc->is_wallet,
    378       &proof_finish,
    379       kpc);
    380     if (NULL == kpc->kat)
    381     {
    382       GNUNET_break_op (0);
    383       proof_finish (kpc,
    384                     TALER_EC_EXCHANGE_KYC_PROOF_REQUEST_UNKNOWN,
    385                     NULL);
    386     }
    387     break;
    388   case TALER_KYCLOGIC_STATUS_FAILED:
    389   case TALER_KYCLOGIC_STATUS_PROVIDER_FAILED:
    390   case TALER_KYCLOGIC_STATUS_USER_ABORTED:
    391   case TALER_KYCLOGIC_STATUS_ABORTED:
    392     GNUNET_assert (NULL == kpc->kat);
    393     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    394                 "KYC process %s/%s (Row #%llu) failed: %d\n",
    395                 provider_user_id,
    396                 provider_legitimization_id,
    397                 (unsigned long long) kpc->process_row,
    398                 status);
    399     if (5 == http_status / 100)
    400     {
    401       char *msg;
    402 
    403       /* OAuth2 server had a problem, do NOT log this as a KYC failure */
    404       GNUNET_break (0);
    405       GNUNET_asprintf (&msg,
    406                        "Failure by KYC provider (HTTP status %u)\n",
    407                        http_status);
    408       proof_finish (
    409         kpc,
    410         TALER_EC_EXCHANGE_KYC_GENERIC_PROVIDER_UNEXPECTED_REPLY,
    411         msg);
    412       GNUNET_free (msg);
    413       break;
    414     }
    415     if (! TEH_kyc_failed (
    416           kpc->process_row,
    417           &kpc->h_payto,
    418           kpc->provider_name,
    419           provider_user_id,
    420           provider_legitimization_id,
    421           TALER_KYCLOGIC_status2s (status),
    422           TALER_EC_EXCHANGE_GENERIC_KYC_FAILED))
    423     {
    424       GNUNET_break (0);
    425       proof_finish (
    426         kpc,
    427         TALER_EC_GENERIC_DB_STORE_FAILED,
    428         "TEH_kyc_failed");
    429       break;
    430     }
    431     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    432                 "KYC process #%llu failed with status %d\n",
    433                 (unsigned long long) kpc->process_row,
    434                 status);
    435     proof_finish (kpc,
    436                   TALER_EC_NONE,
    437                   NULL);
    438     break;
    439   default:
    440     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    441                 "KYC status of %s/%s (Row #%llu) is %d\n",
    442                 provider_user_id,
    443                 provider_legitimization_id,
    444                 (unsigned long long) kpc->process_row,
    445                 (int) status);
    446     break;
    447   }
    448   GNUNET_async_scope_restore (&old_scope);
    449 }
    450 
    451 
    452 /**
    453  * Function called to clean up a context.
    454  *
    455  * @param rc request context
    456  */
    457 static void
    458 clean_kpc (struct TEH_RequestContext *rc)
    459 {
    460   struct KycProofContext *kpc = rc->rh_ctx;
    461 
    462   if (NULL != kpc->ph)
    463   {
    464     kpc->logic->proof_cancel (kpc->ph);
    465     kpc->ph = NULL;
    466   }
    467   if (NULL != kpc->kat)
    468   {
    469     TEH_kyc_run_measure_cancel (kpc->kat);
    470     kpc->kat = NULL;
    471   }
    472   if (NULL != kpc->response)
    473   {
    474     MHD_destroy_response (kpc->response);
    475     kpc->response = NULL;
    476   }
    477   if (NULL != kpc->proof_response)
    478   {
    479     MHD_destroy_response (kpc->proof_response);
    480     kpc->proof_response = NULL;
    481   }
    482   GNUNET_free (kpc->provider_user_id);
    483   GNUNET_free (kpc->provider_legitimization_id);
    484   GNUNET_free (kpc);
    485 }
    486 
    487 
    488 MHD_RESULT
    489 TEH_handler_kyc_proof (
    490   struct TEH_RequestContext *rc,
    491   const char *const args[1])
    492 {
    493   struct KycProofContext *kpc = rc->rh_ctx;
    494   const char *provider_name_or_logic = args[0];
    495 
    496   if (NULL == kpc)
    497   {
    498     /* first time */
    499     if (NULL == provider_name_or_logic)
    500     {
    501       GNUNET_break_op (0);
    502       return respond_html_ec (
    503         rc,
    504         MHD_HTTP_NOT_FOUND,
    505         "kyc-proof-endpoint-unknown",
    506         TALER_EC_GENERIC_ENDPOINT_UNKNOWN,
    507         "'/kyc-proof/$PROVIDER_NAME?state=$H_PAYTO' required");
    508     }
    509     kpc = GNUNET_new (struct KycProofContext);
    510     kpc->rc = rc;
    511     rc->rh_ctx = kpc;
    512     rc->rh_cleaner = &clean_kpc;
    513     TALER_MHD_parse_request_arg_auto_t (rc->connection,
    514                                         "state",
    515                                         &kpc->h_payto);
    516     if (GNUNET_OK !=
    517         TALER_KYCLOGIC_lookup_logic (
    518           provider_name_or_logic,
    519           &kpc->logic,
    520           &kpc->pd,
    521           &kpc->provider_name))
    522     {
    523       GNUNET_break_op (0);
    524       return respond_html_ec (
    525         rc,
    526         MHD_HTTP_NOT_FOUND,
    527         "kyc-proof-target-unknown",
    528         TALER_EC_EXCHANGE_KYC_GENERIC_LOGIC_UNKNOWN,
    529         provider_name_or_logic);
    530     }
    531     if (NULL != kpc->provider_name)
    532     {
    533       enum GNUNET_DB_QueryStatus qs;
    534       struct GNUNET_TIME_Absolute expiration;
    535 
    536       if (0 != strcmp (provider_name_or_logic,
    537                        kpc->provider_name))
    538       {
    539         GNUNET_break_op (0);
    540         return respond_html_ec (
    541           rc,
    542           MHD_HTTP_BAD_REQUEST,
    543           "kyc-proof-bad-request",
    544           TALER_EC_GENERIC_PARAMETER_MALFORMED,
    545           "PROVIDER_NAME");
    546       }
    547 
    548       GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    549                   "Looking for KYC process at %s\n",
    550                   kpc->provider_name);
    551       qs = TEH_plugin->lookup_kyc_process_by_account (
    552         TEH_plugin->cls,
    553         kpc->provider_name,
    554         &kpc->h_payto,
    555         &kpc->process_row,
    556         &expiration,
    557         &kpc->provider_user_id,
    558         &kpc->provider_legitimization_id,
    559         &kpc->is_wallet);
    560       switch (qs)
    561       {
    562       case GNUNET_DB_STATUS_HARD_ERROR:
    563       case GNUNET_DB_STATUS_SOFT_ERROR:
    564         GNUNET_break (0);
    565         return respond_html_ec (
    566           rc,
    567           MHD_HTTP_INTERNAL_SERVER_ERROR,
    568           "kyc-proof-internal-error",
    569           TALER_EC_GENERIC_DB_FETCH_FAILED,
    570           "lookup_kyc_process_by_account");
    571       case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
    572         GNUNET_break_op (0);
    573         return respond_html_ec (
    574           rc,
    575           MHD_HTTP_NOT_FOUND,
    576           "kyc-proof-target-unknown",
    577           TALER_EC_EXCHANGE_KYC_PROOF_REQUEST_UNKNOWN,
    578           kpc->provider_name);
    579       case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
    580         break;
    581       }
    582       if (GNUNET_TIME_absolute_is_future (expiration))
    583       {
    584         /* KYC not required */
    585         return respond_html_ec (
    586           rc,
    587           MHD_HTTP_OK,
    588           "kyc-proof-already-done",
    589           TALER_EC_NONE,
    590           NULL);
    591       }
    592     }
    593     kpc->ph = kpc->logic->proof (
    594       kpc->logic->cls,
    595       kpc->pd,
    596       rc->connection,
    597       &kpc->h_payto,
    598       kpc->process_row,
    599       kpc->provider_user_id,
    600       kpc->provider_legitimization_id,
    601       &proof_cb,
    602       kpc);
    603     if (NULL == kpc->ph)
    604     {
    605       GNUNET_break (0);
    606       return respond_html_ec (
    607         rc,
    608         MHD_HTTP_INTERNAL_SERVER_ERROR,
    609         "kyc-proof-internal-error",
    610         TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
    611         "could not start proof with KYC logic");
    612     }
    613 
    614 
    615     kpc->suspended = true;
    616     GNUNET_CONTAINER_DLL_insert (kpc_head,
    617                                  kpc_tail,
    618                                  kpc);
    619     MHD_suspend_connection (rc->connection);
    620     return MHD_YES;
    621   }
    622 
    623   if (NULL == kpc->response)
    624   {
    625     GNUNET_break (0);
    626     return respond_html_ec (
    627       rc,
    628       MHD_HTTP_INTERNAL_SERVER_ERROR,
    629       "kyc-proof-internal-error",
    630       TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
    631       "handler resumed without response");
    632   }
    633 
    634   /* return response from KYC logic */
    635   return MHD_queue_response (rc->connection,
    636                              kpc->response_code,
    637                              kpc->response);
    638 }
    639 
    640 
    641 /* end of taler-exchange-httpd_kyc-proof.c */