exchange

Base system with REST service to issue digital coins, run by the payment service provider
Log | Files | Refs | Submodules | README | LICENSE

exchange_api_kyc_check.c (10650B)


      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 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 General Public License for more details.
     12 
     13   You should have received a copy of the GNU General Public License along with
     14   TALER; see the file COPYING.  If not, see
     15   <http://www.gnu.org/licenses/>
     16 */
     17 /**
     18  * @file lib/exchange_api_kyc_check.c
     19  * @brief Implementation of the /kyc-check request
     20  * @author Christian Grothoff
     21  */
     22 #include "taler/platform.h"
     23 #include <microhttpd.h> /* just for HTTP check codes */
     24 #include <gnunet/gnunet_util_lib.h>
     25 #include <gnunet/gnunet_curl_lib.h>
     26 #include "taler/taler_exchange_service.h"
     27 #include "taler/taler_json_lib.h"
     28 #include "exchange_api_handle.h"
     29 #include "taler/taler_signatures.h"
     30 #include "exchange_api_curl_defaults.h"
     31 
     32 
     33 /**
     34  * @brief A ``/kyc-check`` handle
     35  */
     36 struct TALER_EXCHANGE_KycCheckHandle
     37 {
     38 
     39   /**
     40    * The url for this request.
     41    */
     42   char *url;
     43 
     44   /**
     45    * Handle for the request.
     46    */
     47   struct GNUNET_CURL_Job *job;
     48 
     49   /**
     50    * Function to call with the result.
     51    */
     52   TALER_EXCHANGE_KycStatusCallback cb;
     53 
     54   /**
     55    * Closure for @e cb.
     56    */
     57   void *cb_cls;
     58 
     59 };
     60 
     61 
     62 static enum GNUNET_GenericReturnValue
     63 parse_account_status (
     64   struct TALER_EXCHANGE_KycCheckHandle *kch,
     65   const json_t *j,
     66   struct TALER_EXCHANGE_KycStatus *ks,
     67   struct TALER_EXCHANGE_AccountKycStatus *status)
     68 {
     69   const json_t *limits = NULL;
     70   struct GNUNET_JSON_Specification spec[] = {
     71     GNUNET_JSON_spec_bool ("aml_review",
     72                            &status->aml_review),
     73     GNUNET_JSON_spec_uint64 ("rule_gen",
     74                              &status->rule_gen),
     75     GNUNET_JSON_spec_fixed_auto ("access_token",
     76                                  &status->access_token),
     77     GNUNET_JSON_spec_mark_optional (
     78       GNUNET_JSON_spec_array_const ("limits",
     79                                     &limits),
     80       NULL),
     81     GNUNET_JSON_spec_end ()
     82   };
     83 
     84   if (GNUNET_OK !=
     85       GNUNET_JSON_parse (j,
     86                          spec,
     87                          NULL, NULL))
     88   {
     89     GNUNET_break_op (0);
     90     return GNUNET_SYSERR;
     91   }
     92   if ( (NULL != limits) &&
     93        (0 != json_array_size (limits)) )
     94   {
     95     size_t limit_length = json_array_size (limits);
     96     struct TALER_EXCHANGE_AccountLimit ala[GNUNET_NZL (limit_length)];
     97     size_t i;
     98     json_t *limit;
     99 
    100     json_array_foreach (limits, i, limit)
    101     {
    102       struct TALER_EXCHANGE_AccountLimit *al = &ala[i];
    103       struct GNUNET_JSON_Specification ispec[] = {
    104         GNUNET_JSON_spec_mark_optional (
    105           GNUNET_JSON_spec_bool ("soft_limit",
    106                                  &al->soft_limit),
    107           NULL),
    108         GNUNET_JSON_spec_relative_time ("timeframe",
    109                                         &al->timeframe),
    110         TALER_JSON_spec_kycte ("operation_type",
    111                                &al->operation_type),
    112         TALER_JSON_spec_amount_any ("threshold",
    113                                     &al->threshold),
    114         GNUNET_JSON_spec_end ()
    115       };
    116 
    117       al->soft_limit = false;
    118       if (GNUNET_OK !=
    119           GNUNET_JSON_parse (limit,
    120                              ispec,
    121                              NULL, NULL))
    122       {
    123         GNUNET_break_op (0);
    124         return GNUNET_SYSERR;
    125       }
    126     }
    127     status->limits = ala;
    128     status->limits_length = limit_length;
    129     kch->cb (kch->cb_cls,
    130              ks);
    131   }
    132   else
    133   {
    134     kch->cb (kch->cb_cls,
    135              ks);
    136   }
    137   GNUNET_JSON_parse_free (spec);
    138   return GNUNET_OK;
    139 }
    140 
    141 
    142 /**
    143  * Function called when we're done processing the
    144  * HTTP /kyc-check request.
    145  *
    146  * @param cls the `struct TALER_EXCHANGE_KycCheckHandle`
    147  * @param response_code HTTP response code, 0 on error
    148  * @param response parsed JSON result, NULL on error
    149  */
    150 static void
    151 handle_kyc_check_finished (void *cls,
    152                            long response_code,
    153                            const void *response)
    154 {
    155   struct TALER_EXCHANGE_KycCheckHandle *kch = cls;
    156   const json_t *j = response;
    157   struct TALER_EXCHANGE_KycStatus ks = {
    158     .hr.reply = j,
    159     .hr.http_status = (unsigned int) response_code
    160   };
    161 
    162   kch->job = NULL;
    163   switch (response_code)
    164   {
    165   case 0:
    166     ks.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
    167     break;
    168   case MHD_HTTP_OK:
    169     {
    170       if (GNUNET_OK !=
    171           parse_account_status (kch,
    172                                 j,
    173                                 &ks,
    174                                 &ks.details.ok))
    175       {
    176         GNUNET_break_op (0);
    177         ks.hr.http_status = 0;
    178         ks.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
    179         break;
    180       }
    181       TALER_EXCHANGE_kyc_check_cancel (kch);
    182       return;
    183     }
    184   case MHD_HTTP_ACCEPTED:
    185     {
    186       if (GNUNET_OK !=
    187           parse_account_status (kch,
    188                                 j,
    189                                 &ks,
    190                                 &ks.details.accepted))
    191       {
    192         GNUNET_break_op (0);
    193         ks.hr.http_status = 0;
    194         ks.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
    195         break;
    196       }
    197       TALER_EXCHANGE_kyc_check_cancel (kch);
    198       return;
    199     }
    200   case MHD_HTTP_NO_CONTENT:
    201     break;
    202   case MHD_HTTP_BAD_REQUEST:
    203     ks.hr.ec = TALER_JSON_get_error_code (j);
    204     /* This should never happen, either us or the exchange is buggy
    205        (or API version conflict); just pass JSON reply to the application */
    206     break;
    207   case MHD_HTTP_FORBIDDEN:
    208     {
    209       struct GNUNET_JSON_Specification spec[] = {
    210         GNUNET_JSON_spec_fixed_auto (
    211           "expected_account_pub",
    212           &ks.details.forbidden.expected_account_pub),
    213         TALER_JSON_spec_ec ("code",
    214                             &ks.hr.ec),
    215         GNUNET_JSON_spec_end ()
    216       };
    217 
    218       if (GNUNET_OK !=
    219           GNUNET_JSON_parse (j,
    220                              spec,
    221                              NULL, NULL))
    222       {
    223         GNUNET_break_op (0);
    224         ks.hr.http_status = 0;
    225         ks.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
    226         break;
    227       }
    228       break;
    229     }
    230   case MHD_HTTP_NOT_FOUND:
    231     ks.hr.ec = TALER_JSON_get_error_code (j);
    232     break;
    233   case MHD_HTTP_CONFLICT:
    234     ks.hr.ec = TALER_JSON_get_error_code (j);
    235     break;
    236   case MHD_HTTP_INTERNAL_SERVER_ERROR:
    237     ks.hr.ec = TALER_JSON_get_error_code (j);
    238     /* Server had an internal issue; we should retry, but this API
    239        leaves this to the application */
    240     break;
    241   default:
    242     /* unexpected response code */
    243     GNUNET_break_op (0);
    244     ks.hr.ec = TALER_JSON_get_error_code (j);
    245     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    246                 "Unexpected response code %u/%d for exchange kyc_check\n",
    247                 (unsigned int) response_code,
    248                 (int) ks.hr.ec);
    249     break;
    250   }
    251   kch->cb (kch->cb_cls,
    252            &ks);
    253   TALER_EXCHANGE_kyc_check_cancel (kch);
    254 }
    255 
    256 
    257 struct TALER_EXCHANGE_KycCheckHandle *
    258 TALER_EXCHANGE_kyc_check (
    259   struct GNUNET_CURL_Context *ctx,
    260   const char *url,
    261   const struct TALER_NormalizedPaytoHashP *h_payto,
    262   const union TALER_AccountPrivateKeyP *account_priv,
    263   uint64_t known_rule_gen,
    264   enum TALER_EXCHANGE_KycLongPollTarget lpt,
    265   struct GNUNET_TIME_Relative timeout,
    266   TALER_EXCHANGE_KycStatusCallback cb,
    267   void *cb_cls)
    268 {
    269   struct TALER_EXCHANGE_KycCheckHandle *kch;
    270   CURL *eh;
    271   char arg_str[128];
    272   char timeout_ms[32];
    273   char lpt_str[32];
    274   char krg_str[32];
    275   struct curl_slist *job_headers = NULL;
    276   unsigned long long tms;
    277 
    278   {
    279     char *hps;
    280 
    281     hps = GNUNET_STRINGS_data_to_string_alloc (
    282       h_payto,
    283       sizeof (*h_payto));
    284     GNUNET_snprintf (arg_str,
    285                      sizeof (arg_str),
    286                      "kyc-check/%s",
    287                      hps);
    288     GNUNET_free (hps);
    289   }
    290   tms = timeout.rel_value_us
    291         / GNUNET_TIME_UNIT_MILLISECONDS.rel_value_us;
    292   GNUNET_snprintf (timeout_ms,
    293                    sizeof (timeout_ms),
    294                    "%llu",
    295                    tms);
    296   GNUNET_snprintf (krg_str,
    297                    sizeof (krg_str),
    298                    "%llu",
    299                    (unsigned long long) known_rule_gen);
    300   GNUNET_snprintf (lpt_str,
    301                    sizeof (lpt_str),
    302                    "%d",
    303                    (int) lpt);
    304   kch = GNUNET_new (struct TALER_EXCHANGE_KycCheckHandle);
    305   kch->cb = cb;
    306   kch->cb_cls = cb_cls;
    307   kch->url
    308     = TALER_url_join (
    309         url,
    310         arg_str,
    311         "timeout_ms",
    312         GNUNET_TIME_relative_is_zero (timeout)
    313         ? NULL
    314         : timeout_ms,
    315         "min_rule",
    316         0 == known_rule_gen
    317         ? NULL
    318         : krg_str,
    319         "lpt",
    320         TALER_EXCHANGE_KLPT_NONE == lpt
    321         ? NULL
    322         : lpt_str,
    323         NULL);
    324   if (NULL == kch->url)
    325   {
    326     GNUNET_free (kch);
    327     return NULL;
    328   }
    329   eh = TALER_EXCHANGE_curl_easy_get_ (kch->url);
    330   if (NULL == eh)
    331   {
    332     GNUNET_break (0);
    333     GNUNET_free (kch->url);
    334     GNUNET_free (kch);
    335     return NULL;
    336   }
    337   if (0 != tms)
    338   {
    339     GNUNET_break (CURLE_OK ==
    340                   curl_easy_setopt (eh,
    341                                     CURLOPT_TIMEOUT_MS,
    342                                     (long) (tms + 500L)));
    343   }
    344   job_headers
    345     = curl_slist_append (
    346         job_headers,
    347         "Content-Type: application/json");
    348   {
    349     union TALER_AccountSignatureP account_sig;
    350     char *sig_hdr;
    351     char *hdr;
    352 
    353     TALER_account_kyc_auth_sign (account_priv,
    354                                  &account_sig);
    355 
    356     sig_hdr = GNUNET_STRINGS_data_to_string_alloc (
    357       &account_sig,
    358       sizeof (account_sig));
    359     GNUNET_asprintf (&hdr,
    360                      "%s: %s",
    361                      TALER_HTTP_HEADER_ACCOUNT_OWNER_SIGNATURE,
    362                      sig_hdr);
    363     GNUNET_free (sig_hdr);
    364     job_headers = curl_slist_append (job_headers,
    365                                      hdr);
    366     GNUNET_free (hdr);
    367     if (NULL == job_headers)
    368     {
    369       GNUNET_break (0);
    370       curl_easy_cleanup (eh);
    371       return NULL;
    372     }
    373   }
    374   kch->job
    375     = GNUNET_CURL_job_add2 (ctx,
    376                             eh,
    377                             job_headers,
    378                             &handle_kyc_check_finished,
    379                             kch);
    380   curl_slist_free_all (job_headers);
    381   return kch;
    382 }
    383 
    384 
    385 void
    386 TALER_EXCHANGE_kyc_check_cancel (struct TALER_EXCHANGE_KycCheckHandle *kch)
    387 {
    388   if (NULL != kch->job)
    389   {
    390     GNUNET_CURL_job_cancel (kch->job);
    391     kch->job = NULL;
    392   }
    393   GNUNET_free (kch->url);
    394   GNUNET_free (kch);
    395 }
    396 
    397 
    398 /* end of exchange_api_kyc_check.c */