exchange

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

exchange_api_get-kyc-check-H_NORMALIZED_PAYTO.c (13669B)


      1 /*
      2   This file is part of TALER
      3   Copyright (C) 2021-2026 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_get-kyc-check-H_NORMALIZED_PAYTO.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 "taler/taler-exchange/get-kyc-check-H_NORMALIZED_PAYTO.h"
     29 #include "taler/taler_signatures.h"
     30 #include "exchange_api_curl_defaults.h"
     31 
     32 
     33 /**
     34  * @brief A GET /kyc-check/$H_NORMALIZED_PAYTO handle
     35  */
     36 struct TALER_EXCHANGE_GetKycCheckHandle
     37 {
     38 
     39   /**
     40    * The base URL for this request.
     41    */
     42   char *base_url;
     43 
     44   /**
     45    * The full URL for this request, set during _start.
     46    */
     47   char *url;
     48 
     49   /**
     50    * Handle for the request.
     51    */
     52   struct GNUNET_CURL_Job *job;
     53 
     54   /**
     55    * Function to call with the result.
     56    */
     57   TALER_EXCHANGE_GetKycCheckCallback cb;
     58 
     59   /**
     60    * Closure for @e cb.
     61    */
     62   TALER_EXCHANGE_GET_KYC_CHECK_RESULT_CLOSURE *cb_cls;
     63 
     64   /**
     65    * Reference to the execution context.
     66    */
     67   struct GNUNET_CURL_Context *ctx;
     68 
     69   /**
     70    * Hash of the payto URI we are checking.
     71    */
     72   struct TALER_NormalizedPaytoHashP h_payto;
     73 
     74   /**
     75    * Private key to authorize the request.
     76    */
     77   union TALER_AccountPrivateKeyP account_priv;
     78 
     79   /**
     80    * Long polling target.
     81    */
     82   enum TALER_EXCHANGE_KycLongPollTarget lpt;
     83 
     84   /**
     85    * Latest known rule generation (for long polling).
     86    */
     87   uint64_t known_rule_gen;
     88 
     89   /**
     90    * Long polling timeout.
     91    */
     92   struct GNUNET_TIME_Relative timeout;
     93 
     94 };
     95 
     96 
     97 /**
     98  * Parse an account KYC status from JSON and invoke the callback.
     99  *
    100  * @param[in,out] gkch handle
    101  * @param j JSON to parse
    102  * @param res response to fill
    103  * @param status account status field within @a res to fill
    104  * @return #GNUNET_OK on success
    105  */
    106 static enum GNUNET_GenericReturnValue
    107 parse_account_status (
    108   struct TALER_EXCHANGE_GetKycCheckHandle *gkch,
    109   const json_t *j,
    110   struct TALER_EXCHANGE_GetKycCheckResponse *res,
    111   struct TALER_EXCHANGE_AccountKycStatus *status)
    112 {
    113   const json_t *limits = NULL;
    114   struct GNUNET_JSON_Specification spec[] = {
    115     GNUNET_JSON_spec_bool ("aml_review",
    116                            &status->aml_review),
    117     GNUNET_JSON_spec_uint64 ("rule_gen",
    118                              &status->rule_gen),
    119     GNUNET_JSON_spec_fixed_auto ("access_token",
    120                                  &status->access_token),
    121     GNUNET_JSON_spec_mark_optional (
    122       GNUNET_JSON_spec_array_const ("limits",
    123                                     &limits),
    124       NULL),
    125     GNUNET_JSON_spec_end ()
    126   };
    127 
    128   if (GNUNET_OK !=
    129       GNUNET_JSON_parse (j,
    130                          spec,
    131                          NULL, NULL))
    132   {
    133     GNUNET_break_op (0);
    134     return GNUNET_SYSERR;
    135   }
    136   if ( (NULL != limits) &&
    137        (0 != json_array_size (limits)) )
    138   {
    139     size_t limit_length = json_array_size (limits);
    140     struct TALER_EXCHANGE_AccountLimit ala[GNUNET_NZL (limit_length)];
    141     size_t i;
    142     json_t *limit;
    143 
    144     json_array_foreach (limits, i, limit)
    145     {
    146       struct TALER_EXCHANGE_AccountLimit *al = &ala[i];
    147       struct GNUNET_JSON_Specification ispec[] = {
    148         GNUNET_JSON_spec_mark_optional (
    149           GNUNET_JSON_spec_bool ("soft_limit",
    150                                  &al->soft_limit),
    151           NULL),
    152         GNUNET_JSON_spec_relative_time ("timeframe",
    153                                         &al->timeframe),
    154         TALER_JSON_spec_kycte ("operation_type",
    155                                &al->operation_type),
    156         TALER_JSON_spec_amount_any ("threshold",
    157                                     &al->threshold),
    158         GNUNET_JSON_spec_end ()
    159       };
    160 
    161       al->soft_limit = false;
    162       if (GNUNET_OK !=
    163           GNUNET_JSON_parse (limit,
    164                              ispec,
    165                              NULL, NULL))
    166       {
    167         GNUNET_break_op (0);
    168         return GNUNET_SYSERR;
    169       }
    170     }
    171     status->limits = ala;
    172     status->limits_length = limit_length;
    173     gkch->cb (gkch->cb_cls,
    174               res);
    175   }
    176   else
    177   {
    178     gkch->cb (gkch->cb_cls,
    179               res);
    180   }
    181   GNUNET_JSON_parse_free (spec);
    182   return GNUNET_OK;
    183 }
    184 
    185 
    186 /**
    187  * Function called when we're done processing the
    188  * HTTP GET /kyc-check/$H_NORMALIZED_PAYTO request.
    189  *
    190  * @param cls the `struct TALER_EXCHANGE_GetKycCheckHandle`
    191  * @param response_code HTTP response code, 0 on error
    192  * @param response parsed JSON result, NULL on error
    193  */
    194 static void
    195 handle_kyc_check_finished (void *cls,
    196                            long response_code,
    197                            const void *response)
    198 {
    199   struct TALER_EXCHANGE_GetKycCheckHandle *gkch = cls;
    200   const json_t *j = response;
    201   struct TALER_EXCHANGE_GetKycCheckResponse ks = {
    202     .hr.reply = j,
    203     .hr.http_status = (unsigned int) response_code
    204   };
    205 
    206   gkch->job = NULL;
    207   switch (response_code)
    208   {
    209   case 0:
    210     ks.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
    211     break;
    212   case MHD_HTTP_OK:
    213     {
    214       if (GNUNET_OK !=
    215           parse_account_status (gkch,
    216                                 j,
    217                                 &ks,
    218                                 &ks.details.ok))
    219       {
    220         GNUNET_break_op (0);
    221         ks.hr.http_status = 0;
    222         ks.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
    223         break;
    224       }
    225       TALER_EXCHANGE_get_kyc_check_cancel (gkch);
    226       return;
    227     }
    228   case MHD_HTTP_ACCEPTED:
    229     {
    230       if (GNUNET_OK !=
    231           parse_account_status (gkch,
    232                                 j,
    233                                 &ks,
    234                                 &ks.details.accepted))
    235       {
    236         GNUNET_break_op (0);
    237         ks.hr.http_status = 0;
    238         ks.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
    239         break;
    240       }
    241       TALER_EXCHANGE_get_kyc_check_cancel (gkch);
    242       return;
    243     }
    244   case MHD_HTTP_NO_CONTENT:
    245     break;
    246   case MHD_HTTP_BAD_REQUEST:
    247     ks.hr.ec = TALER_JSON_get_error_code (j);
    248     break;
    249   case MHD_HTTP_FORBIDDEN:
    250     {
    251       struct GNUNET_JSON_Specification spec[] = {
    252         GNUNET_JSON_spec_fixed_auto (
    253           "expected_account_pub",
    254           &ks.details.forbidden.expected_account_pub),
    255         TALER_JSON_spec_ec ("code",
    256                             &ks.hr.ec),
    257         GNUNET_JSON_spec_end ()
    258       };
    259 
    260       if (GNUNET_OK !=
    261           GNUNET_JSON_parse (j,
    262                              spec,
    263                              NULL, NULL))
    264       {
    265         GNUNET_break_op (0);
    266         ks.hr.http_status = 0;
    267         ks.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
    268         break;
    269       }
    270       break;
    271     }
    272   case MHD_HTTP_NOT_FOUND:
    273     ks.hr.ec = TALER_JSON_get_error_code (j);
    274     break;
    275   case MHD_HTTP_CONFLICT:
    276     ks.hr.ec = TALER_JSON_get_error_code (j);
    277     break;
    278   case MHD_HTTP_INTERNAL_SERVER_ERROR:
    279     ks.hr.ec = TALER_JSON_get_error_code (j);
    280     break;
    281   default:
    282     /* unexpected response code */
    283     GNUNET_break_op (0);
    284     ks.hr.ec = TALER_JSON_get_error_code (j);
    285     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    286                 "Unexpected response code %u/%d for exchange kyc_check\n",
    287                 (unsigned int) response_code,
    288                 (int) ks.hr.ec);
    289     break;
    290   }
    291   if (NULL != gkch->cb)
    292   {
    293     gkch->cb (gkch->cb_cls,
    294               &ks);
    295     gkch->cb = NULL;
    296   }
    297   TALER_EXCHANGE_get_kyc_check_cancel (gkch);
    298 }
    299 
    300 
    301 struct TALER_EXCHANGE_GetKycCheckHandle *
    302 TALER_EXCHANGE_get_kyc_check_create (
    303   struct GNUNET_CURL_Context *ctx,
    304   const char *url,
    305   const struct TALER_NormalizedPaytoHashP *h_payto,
    306   const union TALER_AccountPrivateKeyP *pk)
    307 {
    308   struct TALER_EXCHANGE_GetKycCheckHandle *gkch;
    309 
    310   gkch = GNUNET_new (struct TALER_EXCHANGE_GetKycCheckHandle);
    311   gkch->ctx = ctx;
    312   gkch->base_url = GNUNET_strdup (url);
    313   gkch->h_payto = *h_payto;
    314   gkch->account_priv = *pk;
    315   return gkch;
    316 }
    317 
    318 
    319 enum GNUNET_GenericReturnValue
    320 TALER_EXCHANGE_get_kyc_check_set_options_ (
    321   struct TALER_EXCHANGE_GetKycCheckHandle *gkch,
    322   unsigned int num_options,
    323   const struct TALER_EXCHANGE_GetKycCheckOptionValue *options)
    324 {
    325   for (unsigned int i = 0; i < num_options; i++)
    326   {
    327     const struct TALER_EXCHANGE_GetKycCheckOptionValue *opt = &options[i];
    328 
    329     switch (opt->option)
    330     {
    331     case TALER_EXCHANGE_GET_KYC_CHECK_OPTION_END:
    332       return GNUNET_OK;
    333     case TALER_EXCHANGE_GET_KYC_CHECK_OPTION_KNOWN_RULE_GEN:
    334       gkch->known_rule_gen = opt->details.known_rule_gen;
    335       break;
    336     case TALER_EXCHANGE_GET_KYC_CHECK_OPTION_LPT:
    337       gkch->lpt = opt->details.lpt;
    338       break;
    339     case TALER_EXCHANGE_GET_KYC_CHECK_OPTION_TIMEOUT:
    340       gkch->timeout = opt->details.timeout;
    341       break;
    342     default:
    343       GNUNET_break (0);
    344       return GNUNET_SYSERR;
    345     }
    346   }
    347   return GNUNET_OK;
    348 }
    349 
    350 
    351 enum TALER_ErrorCode
    352 TALER_EXCHANGE_get_kyc_check_start (
    353   struct TALER_EXCHANGE_GetKycCheckHandle *gkch,
    354   TALER_EXCHANGE_GetKycCheckCallback cb,
    355   TALER_EXCHANGE_GET_KYC_CHECK_RESULT_CLOSURE *cb_cls)
    356 {
    357   CURL *eh;
    358   char arg_str[128];
    359   char timeout_ms[32];
    360   char lpt_str[32];
    361   char krg_str[32];
    362   struct curl_slist *job_headers = NULL;
    363   unsigned long long tms;
    364 
    365   gkch->cb = cb;
    366   gkch->cb_cls = cb_cls;
    367   {
    368     char *hps;
    369 
    370     hps = GNUNET_STRINGS_data_to_string_alloc (
    371       &gkch->h_payto,
    372       sizeof (gkch->h_payto));
    373     GNUNET_snprintf (arg_str,
    374                      sizeof (arg_str),
    375                      "kyc-check/%s",
    376                      hps);
    377     GNUNET_free (hps);
    378   }
    379   tms = gkch->timeout.rel_value_us
    380         / GNUNET_TIME_UNIT_MILLISECONDS.rel_value_us;
    381   GNUNET_snprintf (timeout_ms,
    382                    sizeof (timeout_ms),
    383                    "%llu",
    384                    tms);
    385   GNUNET_snprintf (krg_str,
    386                    sizeof (krg_str),
    387                    "%llu",
    388                    (unsigned long long) gkch->known_rule_gen);
    389   GNUNET_snprintf (lpt_str,
    390                    sizeof (lpt_str),
    391                    "%d",
    392                    (int) gkch->lpt);
    393   gkch->url
    394     = TALER_url_join (
    395         gkch->base_url,
    396         arg_str,
    397         "timeout_ms",
    398         GNUNET_TIME_relative_is_zero (gkch->timeout)
    399         ? NULL
    400         : timeout_ms,
    401         "min_rule",
    402         0 == gkch->known_rule_gen
    403         ? NULL
    404         : krg_str,
    405         "lpt",
    406         TALER_EXCHANGE_KLPT_NONE == gkch->lpt
    407         ? NULL
    408         : lpt_str,
    409         NULL);
    410   if (NULL == gkch->url)
    411     return TALER_EC_GENERIC_CONFIGURATION_INVALID;
    412   eh = TALER_EXCHANGE_curl_easy_get_ (gkch->url);
    413   if (NULL == eh)
    414   {
    415     GNUNET_break (0);
    416     GNUNET_free (gkch->url);
    417     gkch->url = NULL;
    418     return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
    419   }
    420   if (0 != tms)
    421   {
    422     GNUNET_break (CURLE_OK ==
    423                   curl_easy_setopt (eh,
    424                                     CURLOPT_TIMEOUT_MS,
    425                                     (long) (tms + 500L)));
    426   }
    427   job_headers
    428     = curl_slist_append (
    429         job_headers,
    430         "Content-Type: application/json");
    431   {
    432     union TALER_AccountPublicKeyP account_pub;
    433     union TALER_AccountSignatureP account_sig;
    434     char *sig_hdr;
    435     char *pub_hdr;
    436     char *hdr;
    437 
    438     GNUNET_CRYPTO_eddsa_key_get_public (
    439       &gkch->account_priv.reserve_priv.eddsa_priv,
    440       &account_pub.reserve_pub.eddsa_pub);
    441     TALER_account_kyc_auth_sign (&gkch->account_priv,
    442                                  &account_sig);
    443 
    444     sig_hdr = GNUNET_STRINGS_data_to_string_alloc (
    445       &account_sig,
    446       sizeof (account_sig));
    447     GNUNET_asprintf (&hdr,
    448                      "%s: %s",
    449                      TALER_HTTP_HEADER_ACCOUNT_OWNER_SIGNATURE,
    450                      sig_hdr);
    451     GNUNET_free (sig_hdr);
    452     job_headers = curl_slist_append (job_headers,
    453                                      hdr);
    454     GNUNET_free (hdr);
    455 
    456     pub_hdr = GNUNET_STRINGS_data_to_string_alloc (
    457       &account_pub,
    458       sizeof (account_pub));
    459     GNUNET_asprintf (&hdr,
    460                      "%s: %s",
    461                      TALER_HTTP_HEADER_ACCOUNT_OWNER_PUBKEY,
    462                      pub_hdr);
    463     GNUNET_free (pub_hdr);
    464     job_headers = curl_slist_append (job_headers,
    465                                      hdr);
    466     GNUNET_free (hdr);
    467     if (NULL == job_headers)
    468     {
    469       GNUNET_break (0);
    470       curl_easy_cleanup (eh);
    471       GNUNET_free (gkch->url);
    472       gkch->url = NULL;
    473       return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
    474     }
    475   }
    476   gkch->job
    477     = GNUNET_CURL_job_add2 (gkch->ctx,
    478                             eh,
    479                             job_headers,
    480                             &handle_kyc_check_finished,
    481                             gkch);
    482   curl_slist_free_all (job_headers);
    483   if (NULL == gkch->job)
    484   {
    485     GNUNET_free (gkch->url);
    486     gkch->url = NULL;
    487     return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
    488   }
    489   return TALER_EC_NONE;
    490 }
    491 
    492 
    493 void
    494 TALER_EXCHANGE_get_kyc_check_cancel (
    495   struct TALER_EXCHANGE_GetKycCheckHandle *gkch)
    496 {
    497   if (NULL != gkch->job)
    498   {
    499     GNUNET_CURL_job_cancel (gkch->job);
    500     gkch->job = NULL;
    501   }
    502   GNUNET_free (gkch->url);
    503   GNUNET_free (gkch->base_url);
    504   GNUNET_free (gkch);
    505 }
    506 
    507 
    508 /* end of exchange_api_get-kyc-check-H_NORMALIZED_PAYTO.c */