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 (13745B)


      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 "platform.h"  /* UNNECESSARY? */
     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"  /* UNNECESSARY? */
     27 #include "taler/taler_json_lib.h"
     28 #include "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_string ("tos_required",
    123                                &status->tos_required),
    124       NULL),
    125     GNUNET_JSON_spec_mark_optional (
    126       GNUNET_JSON_spec_array_const ("limits",
    127                                     &limits),
    128       NULL),
    129     GNUNET_JSON_spec_end ()
    130   };
    131 
    132   if (GNUNET_OK !=
    133       GNUNET_JSON_parse (j,
    134                          spec,
    135                          NULL, NULL))
    136   {
    137     GNUNET_break_op (0);
    138     return GNUNET_SYSERR;
    139   }
    140   if ( (NULL != limits) &&
    141        (0 != json_array_size (limits)) )
    142   {
    143     size_t limit_length = json_array_size (limits);
    144     struct TALER_EXCHANGE_AccountLimit ala[GNUNET_NZL (limit_length)];
    145     size_t i;
    146     json_t *limit;
    147 
    148     json_array_foreach (limits, i, limit)
    149     {
    150       struct TALER_EXCHANGE_AccountLimit *al = &ala[i];
    151       struct GNUNET_JSON_Specification ispec[] = {
    152         GNUNET_JSON_spec_mark_optional (
    153           GNUNET_JSON_spec_bool ("soft_limit",
    154                                  &al->soft_limit),
    155           NULL),
    156         GNUNET_JSON_spec_relative_time ("timeframe",
    157                                         &al->timeframe),
    158         TALER_JSON_spec_kycte ("operation_type",
    159                                &al->operation_type),
    160         TALER_JSON_spec_amount_any ("threshold",
    161                                     &al->threshold),
    162         GNUNET_JSON_spec_end ()
    163       };
    164 
    165       al->soft_limit = false;
    166       if (GNUNET_OK !=
    167           GNUNET_JSON_parse (limit,
    168                              ispec,
    169                              NULL, NULL))
    170       {
    171         GNUNET_break_op (0);
    172         return GNUNET_SYSERR;
    173       }
    174     }
    175     status->limits = ala;
    176     status->limits_length = limit_length;
    177     gkch->cb (gkch->cb_cls,
    178               res);
    179   }
    180   else
    181   {
    182     gkch->cb (gkch->cb_cls,
    183               res);
    184   }
    185   GNUNET_JSON_parse_free (spec);
    186   return GNUNET_OK;
    187 }
    188 
    189 
    190 /**
    191  * Function called when we're done processing the
    192  * HTTP GET /kyc-check/$H_NORMALIZED_PAYTO request.
    193  *
    194  * @param cls the `struct TALER_EXCHANGE_GetKycCheckHandle`
    195  * @param response_code HTTP response code, 0 on error
    196  * @param response parsed JSON result, NULL on error
    197  */
    198 static void
    199 handle_kyc_check_finished (void *cls,
    200                            long response_code,
    201                            const void *response)
    202 {
    203   struct TALER_EXCHANGE_GetKycCheckHandle *gkch = cls;
    204   const json_t *j = response;
    205   struct TALER_EXCHANGE_GetKycCheckResponse ks = {
    206     .hr.reply = j,
    207     .hr.http_status = (unsigned int) response_code
    208   };
    209 
    210   gkch->job = NULL;
    211   switch (response_code)
    212   {
    213   case 0:
    214     ks.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
    215     break;
    216   case MHD_HTTP_OK:
    217     {
    218       if (GNUNET_OK !=
    219           parse_account_status (gkch,
    220                                 j,
    221                                 &ks,
    222                                 &ks.details.ok))
    223       {
    224         GNUNET_break_op (0);
    225         ks.hr.http_status = 0;
    226         ks.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
    227         break;
    228       }
    229       TALER_EXCHANGE_get_kyc_check_cancel (gkch);
    230       return;
    231     }
    232   case MHD_HTTP_ACCEPTED:
    233     {
    234       if (GNUNET_OK !=
    235           parse_account_status (gkch,
    236                                 j,
    237                                 &ks,
    238                                 &ks.details.accepted))
    239       {
    240         GNUNET_break_op (0);
    241         ks.hr.http_status = 0;
    242         ks.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
    243         break;
    244       }
    245       TALER_EXCHANGE_get_kyc_check_cancel (gkch);
    246       return;
    247     }
    248   case MHD_HTTP_NO_CONTENT:
    249     break;
    250   case MHD_HTTP_BAD_REQUEST:
    251     ks.hr.ec = TALER_JSON_get_error_code (j);
    252     break;
    253   case MHD_HTTP_FORBIDDEN:
    254     {
    255       struct GNUNET_JSON_Specification spec[] = {
    256         GNUNET_JSON_spec_fixed_auto (
    257           "expected_account_pub",
    258           &ks.details.forbidden.expected_account_pub),
    259         TALER_JSON_spec_ec ("code",
    260                             &ks.hr.ec),
    261         GNUNET_JSON_spec_end ()
    262       };
    263 
    264       if (GNUNET_OK !=
    265           GNUNET_JSON_parse (j,
    266                              spec,
    267                              NULL, NULL))
    268       {
    269         GNUNET_break_op (0);
    270         ks.hr.http_status = 0;
    271         ks.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
    272         break;
    273       }
    274       break;
    275     }
    276   case MHD_HTTP_NOT_FOUND:
    277     ks.hr.ec = TALER_JSON_get_error_code (j);
    278     break;
    279   case MHD_HTTP_CONFLICT:
    280     ks.hr.ec = TALER_JSON_get_error_code (j);
    281     break;
    282   case MHD_HTTP_INTERNAL_SERVER_ERROR:
    283     ks.hr.ec = TALER_JSON_get_error_code (j);
    284     break;
    285   default:
    286     /* unexpected response code */
    287     GNUNET_break_op (0);
    288     ks.hr.ec = TALER_JSON_get_error_code (j);
    289     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    290                 "Unexpected response code %u/%d for exchange kyc_check\n",
    291                 (unsigned int) response_code,
    292                 (int) ks.hr.ec);
    293     break;
    294   }
    295   if (NULL != gkch->cb)
    296   {
    297     gkch->cb (gkch->cb_cls,
    298               &ks);
    299     gkch->cb = NULL;
    300   }
    301   TALER_EXCHANGE_get_kyc_check_cancel (gkch);
    302 }
    303 
    304 
    305 struct TALER_EXCHANGE_GetKycCheckHandle *
    306 TALER_EXCHANGE_get_kyc_check_create (
    307   struct GNUNET_CURL_Context *ctx,
    308   const char *url,
    309   const struct TALER_NormalizedPaytoHashP *h_payto,
    310   const union TALER_AccountPrivateKeyP *pk)
    311 {
    312   struct TALER_EXCHANGE_GetKycCheckHandle *gkch;
    313 
    314   gkch = GNUNET_new (struct TALER_EXCHANGE_GetKycCheckHandle);
    315   gkch->ctx = ctx;
    316   gkch->base_url = GNUNET_strdup (url);
    317   gkch->h_payto = *h_payto;
    318   gkch->account_priv = *pk;
    319   return gkch;
    320 }
    321 
    322 
    323 enum GNUNET_GenericReturnValue
    324 TALER_EXCHANGE_get_kyc_check_set_options_ (
    325   struct TALER_EXCHANGE_GetKycCheckHandle *gkch,
    326   unsigned int num_options,
    327   const struct TALER_EXCHANGE_GetKycCheckOptionValue *options)
    328 {
    329   for (unsigned int i = 0; i < num_options; i++)
    330   {
    331     const struct TALER_EXCHANGE_GetKycCheckOptionValue *opt = &options[i];
    332 
    333     switch (opt->option)
    334     {
    335     case TALER_EXCHANGE_GET_KYC_CHECK_OPTION_END:
    336       return GNUNET_OK;
    337     case TALER_EXCHANGE_GET_KYC_CHECK_OPTION_KNOWN_RULE_GEN:
    338       gkch->known_rule_gen = opt->details.known_rule_gen;
    339       break;
    340     case TALER_EXCHANGE_GET_KYC_CHECK_OPTION_LPT:
    341       gkch->lpt = opt->details.lpt;
    342       break;
    343     case TALER_EXCHANGE_GET_KYC_CHECK_OPTION_TIMEOUT:
    344       gkch->timeout = opt->details.timeout;
    345       break;
    346     default:
    347       GNUNET_break (0);
    348       return GNUNET_SYSERR;
    349     }
    350   }
    351   return GNUNET_OK;
    352 }
    353 
    354 
    355 enum TALER_ErrorCode
    356 TALER_EXCHANGE_get_kyc_check_start (
    357   struct TALER_EXCHANGE_GetKycCheckHandle *gkch,
    358   TALER_EXCHANGE_GetKycCheckCallback cb,
    359   TALER_EXCHANGE_GET_KYC_CHECK_RESULT_CLOSURE *cb_cls)
    360 {
    361   CURL *eh;
    362   char arg_str[128];
    363   char timeout_ms[32];
    364   char lpt_str[32];
    365   char krg_str[32];
    366   struct curl_slist *job_headers = NULL;
    367   unsigned long long tms;
    368 
    369   gkch->cb = cb;
    370   gkch->cb_cls = cb_cls;
    371   {
    372     char *hps;
    373 
    374     hps = GNUNET_STRINGS_data_to_string_alloc (
    375       &gkch->h_payto,
    376       sizeof (gkch->h_payto));
    377     GNUNET_snprintf (arg_str,
    378                      sizeof (arg_str),
    379                      "kyc-check/%s",
    380                      hps);
    381     GNUNET_free (hps);
    382   }
    383   tms = gkch->timeout.rel_value_us
    384         / GNUNET_TIME_UNIT_MILLISECONDS.rel_value_us;
    385   GNUNET_snprintf (timeout_ms,
    386                    sizeof (timeout_ms),
    387                    "%llu",
    388                    tms);
    389   GNUNET_snprintf (krg_str,
    390                    sizeof (krg_str),
    391                    "%llu",
    392                    (unsigned long long) gkch->known_rule_gen);
    393   GNUNET_snprintf (lpt_str,
    394                    sizeof (lpt_str),
    395                    "%d",
    396                    (int) gkch->lpt);
    397   gkch->url
    398     = TALER_url_join (
    399         gkch->base_url,
    400         arg_str,
    401         "timeout_ms",
    402         GNUNET_TIME_relative_is_zero (gkch->timeout)
    403         ? NULL
    404         : timeout_ms,
    405         "min_rule",
    406         0 == gkch->known_rule_gen
    407         ? NULL
    408         : krg_str,
    409         "lpt",
    410         TALER_EXCHANGE_KLPT_NONE == gkch->lpt
    411         ? NULL
    412         : lpt_str,
    413         NULL);
    414   if (NULL == gkch->url)
    415     return TALER_EC_GENERIC_CONFIGURATION_INVALID;
    416   eh = TALER_EXCHANGE_curl_easy_get_ (gkch->url);
    417   if (NULL == eh)
    418   {
    419     GNUNET_break (0);
    420     GNUNET_free (gkch->url);
    421     gkch->url = NULL;
    422     return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
    423   }
    424   if (0 != tms)
    425   {
    426     GNUNET_break (CURLE_OK ==
    427                   curl_easy_setopt (eh,
    428                                     CURLOPT_TIMEOUT_MS,
    429                                     (long) (tms + 500L)));
    430   }
    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 */