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


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