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_aml_measures.c (17282B)


      1 /*
      2   This file is part of TALER
      3   Copyright (C) 2023, 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_get_aml_measures.c
     19  * @brief Implementation of the GET /aml/$OFFICER_PUB/measures request
     20  * @author Christian Grothoff
     21  */
     22 #include "taler/platform.h"
     23 #include <microhttpd.h> /* just for HTTP status 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  * Scrap buffer of temporary arrays.
     35  */
     36 struct Scrap
     37 {
     38   /**
     39    * Kept in DLL.
     40    */
     41   struct Scrap *next;
     42 
     43   /**
     44    * Kept in DLL.
     45    */
     46   struct Scrap *prev;
     47 
     48   /**
     49    * Pointer to our allocation.
     50    */
     51   const char **ptr;
     52 };
     53 
     54 
     55 /**
     56  * @brief A GET /aml/$OFFICER_PUB/measures Handle
     57  */
     58 struct TALER_EXCHANGE_AmlGetMeasuresHandle
     59 {
     60 
     61   /**
     62    * The url for this request.
     63    */
     64   char *url;
     65 
     66   /**
     67    * Handle for the request.
     68    */
     69   struct GNUNET_CURL_Job *job;
     70 
     71   /**
     72    * Function to call with the result.
     73    */
     74   TALER_EXCHANGE_AmlMeasuresCallback measures_cb;
     75 
     76   /**
     77    * Closure for @e measures_cb.
     78    */
     79   void *measures_cb_cls;
     80 
     81   /**
     82    * HTTP headers for the job.
     83    */
     84   struct curl_slist *job_headers;
     85 
     86   /**
     87    * Head of scrap list.
     88    */
     89   struct Scrap *scrap_head;
     90 
     91   /**
     92    * Tail of scrap list.
     93    */
     94   struct Scrap *scrap_tail;
     95 };
     96 
     97 
     98 /**
     99  * Parse AML measures.
    100  *
    101  * @param jroots JSON object with measure data
    102  * @param[out] roots where to write the result
    103  * @return #GNUNET_OK on success
    104  */
    105 static enum GNUNET_GenericReturnValue
    106 parse_aml_roots (
    107   const json_t *jroots,
    108   struct TALER_EXCHANGE_AvailableAmlMeasures *roots)
    109 {
    110   const json_t *obj;
    111   const char *name;
    112   size_t idx = 0;
    113 
    114   json_object_foreach ((json_t *) jroots, name, obj)
    115   {
    116     struct TALER_EXCHANGE_AvailableAmlMeasures *root
    117       = &roots[idx++];
    118     struct GNUNET_JSON_Specification spec[] = {
    119       GNUNET_JSON_spec_string ("check_name",
    120                                &root->check_name),
    121       GNUNET_JSON_spec_string ("prog_name",
    122                                &root->prog_name),
    123       GNUNET_JSON_spec_mark_optional (
    124         GNUNET_JSON_spec_object_const ("context",
    125                                        &root->context),
    126         NULL),
    127       GNUNET_JSON_spec_end ()
    128     };
    129 
    130     if (GNUNET_OK !=
    131         GNUNET_JSON_parse (obj,
    132                            spec,
    133                            NULL,
    134                            NULL))
    135     {
    136       GNUNET_break_op (0);
    137       return GNUNET_SYSERR;
    138     }
    139     root->measure_name = name;
    140   }
    141   return GNUNET_OK;
    142 }
    143 
    144 
    145 /**
    146  * Create array of length @a len in scrap book.
    147  *
    148  * @param[in,out] lh context for allocations
    149  * @param len length of array
    150  * @return scrap array
    151  */
    152 static const char **
    153 make_scrap (
    154   struct TALER_EXCHANGE_AmlGetMeasuresHandle *lh,
    155   unsigned int len)
    156 {
    157   struct Scrap *s = GNUNET_new (struct Scrap);
    158 
    159   s->ptr = GNUNET_new_array (len,
    160                              const char *);
    161   GNUNET_CONTAINER_DLL_insert (lh->scrap_head,
    162                                lh->scrap_tail,
    163                                s);
    164   return s->ptr;
    165 }
    166 
    167 
    168 /**
    169  * Free all scrap space.
    170  *
    171  * @param[in,out] lh scrap context
    172  */
    173 static void
    174 free_scrap (struct TALER_EXCHANGE_AmlGetMeasuresHandle *lh)
    175 {
    176   struct Scrap *s;
    177 
    178   while (NULL != (s = lh->scrap_head))
    179   {
    180     GNUNET_CONTAINER_DLL_remove (lh->scrap_head,
    181                                  lh->scrap_tail,
    182                                  s);
    183     GNUNET_free (s->ptr);
    184     GNUNET_free (s);
    185   }
    186 }
    187 
    188 
    189 /**
    190  * Convert JSON array of strings to string array.
    191  *
    192  * @param j JSON array to convert
    193  * @param[out] a array to initialize
    194  * @return true on success
    195  */
    196 static bool
    197 j_to_a (const json_t *j,
    198         const char **a)
    199 {
    200   const json_t *e;
    201   size_t idx;
    202 
    203   json_array_foreach ((json_t *) j, idx, e)
    204   {
    205     if (NULL == (a[idx] = json_string_value (e)))
    206       return false;
    207   }
    208   return true;
    209 }
    210 
    211 
    212 /**
    213  * Parse AML programs.
    214  *
    215  * @param[in,out] lh context for allocations
    216  * @param jprogs JSON object with program data
    217  * @param[out] progs where to write the result
    218  * @return #GNUNET_OK on success
    219  */
    220 static enum GNUNET_GenericReturnValue
    221 parse_aml_programs (
    222   struct TALER_EXCHANGE_AmlGetMeasuresHandle *lh,
    223   const json_t *jprogs,
    224   struct TALER_EXCHANGE_AvailableAmlPrograms *progs)
    225 {
    226   const json_t *obj;
    227   const char *name;
    228   size_t idx = 0;
    229 
    230   json_object_foreach ((json_t *) jprogs, name, obj)
    231   {
    232     struct TALER_EXCHANGE_AvailableAmlPrograms *prog
    233       = &progs[idx++];
    234     const json_t *jcontext;
    235     const json_t *jinputs;
    236     struct GNUNET_JSON_Specification spec[] = {
    237       GNUNET_JSON_spec_string ("description",
    238                                &prog->description),
    239       GNUNET_JSON_spec_array_const ("context",
    240                                     &jcontext),
    241       GNUNET_JSON_spec_array_const ("inputs",
    242                                     &jinputs),
    243       GNUNET_JSON_spec_end ()
    244     };
    245     unsigned int len;
    246     const char **ptr;
    247 
    248     if (GNUNET_OK !=
    249         GNUNET_JSON_parse (obj,
    250                            spec,
    251                            NULL,
    252                            NULL))
    253     {
    254       GNUNET_break_op (0);
    255       return GNUNET_SYSERR;
    256     }
    257     prog->prog_name = name;
    258     prog->contexts_length
    259       = (unsigned int) json_array_size (jcontext);
    260     prog->inputs_length
    261       = (unsigned int) json_array_size (jinputs);
    262     len = prog->contexts_length + prog->inputs_length;
    263     if ( ((unsigned long long) len) !=
    264          (unsigned long long) json_array_size (jcontext)
    265          + (unsigned long long) json_array_size (jinputs) )
    266     {
    267       GNUNET_break_op (0);
    268       return GNUNET_SYSERR;
    269     }
    270     ptr = make_scrap (lh,
    271                       len);
    272     prog->contexts = ptr;
    273     if (! j_to_a (jcontext,
    274                   prog->contexts))
    275     {
    276       GNUNET_break_op (0);
    277       return GNUNET_SYSERR;
    278     }
    279     prog->inputs = &ptr[prog->contexts_length];
    280     if (! j_to_a (jinputs,
    281                   prog->inputs))
    282     {
    283       GNUNET_break_op (0);
    284       return GNUNET_SYSERR;
    285     }
    286   }
    287   return GNUNET_OK;
    288 }
    289 
    290 
    291 /**
    292  * Parse AML measures.
    293  *
    294  * @param[in,out] lh context for allocations
    295  * @param jchecks JSON object with measure data
    296  * @param[out] checks where to write the result
    297  * @return #GNUNET_OK on success
    298  */
    299 static enum GNUNET_GenericReturnValue
    300 parse_aml_checks (
    301   struct TALER_EXCHANGE_AmlGetMeasuresHandle *lh,
    302   const json_t *jchecks,
    303   struct TALER_EXCHANGE_AvailableKycChecks *checks)
    304 {
    305   const json_t *obj;
    306   const char *name;
    307   size_t idx = 0;
    308 
    309   json_object_foreach ((json_t *) jchecks, name, obj)
    310   {
    311     struct TALER_EXCHANGE_AvailableKycChecks *check
    312       = &checks[idx++];
    313     const json_t *jrequires;
    314     const json_t *joutputs;
    315     struct GNUNET_JSON_Specification spec[] = {
    316       GNUNET_JSON_spec_string ("description",
    317                                &check->description),
    318       GNUNET_JSON_spec_mark_optional (
    319         GNUNET_JSON_spec_object_const ("description_i18n",
    320                                        &check->description_i18n),
    321         NULL),
    322       GNUNET_JSON_spec_array_const ("requires",
    323                                     &jrequires),
    324       GNUNET_JSON_spec_array_const ("outputs",
    325                                     &joutputs),
    326       GNUNET_JSON_spec_string ("fallback",
    327                                &check->fallback),
    328       GNUNET_JSON_spec_end ()
    329     };
    330     unsigned int len;
    331     const char **ptr;
    332 
    333     if (GNUNET_OK !=
    334         GNUNET_JSON_parse (obj,
    335                            spec,
    336                            NULL,
    337                            NULL))
    338     {
    339       GNUNET_break_op (0);
    340       return GNUNET_SYSERR;
    341     }
    342     check->check_name = name;
    343 
    344     check->requires_length
    345       = (unsigned int) json_array_size (jrequires);
    346     check->outputs_length
    347       = (unsigned int) json_array_size (joutputs);
    348     len = check->requires_length + check->outputs_length;
    349     if ( ((unsigned long long) len) !=
    350          (unsigned long long) json_array_size (jrequires)
    351          + (unsigned long long) json_array_size (joutputs) )
    352     {
    353       GNUNET_break_op (0);
    354       return GNUNET_SYSERR;
    355     }
    356     ptr = make_scrap (lh,
    357                       len);
    358     check->requires = ptr;
    359     if (! j_to_a (jrequires,
    360                   check->requires))
    361     {
    362       GNUNET_break_op (0);
    363       return GNUNET_SYSERR;
    364     }
    365     check->outputs = &ptr[check->requires_length];
    366     if (! j_to_a (joutputs,
    367                   check->outputs))
    368     {
    369       GNUNET_break_op (0);
    370       return GNUNET_SYSERR;
    371     }
    372   }
    373   return GNUNET_OK;
    374 }
    375 
    376 
    377 /**
    378  * Parse the provided decision data from the "200 OK" response.
    379  *
    380  * @param[in,out] lh handle (callback may be zero'ed out)
    381  * @param json json reply with the data for one coin
    382  * @return #GNUNET_OK on success, #GNUNET_SYSERR on error
    383  */
    384 static enum GNUNET_GenericReturnValue
    385 parse_measures_ok (struct TALER_EXCHANGE_AmlGetMeasuresHandle *lh,
    386                    const json_t *json)
    387 {
    388   struct TALER_EXCHANGE_AmlGetMeasuresResponse lr = {
    389     .hr.reply = json,
    390     .hr.http_status = MHD_HTTP_OK
    391   };
    392   const json_t *jroots;
    393   const json_t *jprograms;
    394   const json_t *jchecks;
    395   struct GNUNET_JSON_Specification spec[] = {
    396     GNUNET_JSON_spec_object_const ("roots",
    397                                    &jroots),
    398     GNUNET_JSON_spec_object_const ("programs",
    399                                    &jprograms),
    400     GNUNET_JSON_spec_object_const ("checks",
    401                                    &jchecks),
    402     GNUNET_JSON_spec_end ()
    403   };
    404 
    405   if (GNUNET_OK !=
    406       GNUNET_JSON_parse (json,
    407                          spec,
    408                          NULL,
    409                          NULL))
    410   {
    411     GNUNET_break_op (0);
    412     return GNUNET_SYSERR;
    413   }
    414   lr.details.ok.roots_length
    415     = (unsigned int) json_object_size (jroots);
    416   lr.details.ok.programs_length
    417     = (unsigned int) json_object_size (jprograms);
    418   lr.details.ok.checks_length
    419     = (unsigned int) json_object_size (jchecks);
    420   if ( ( ((size_t) lr.details.ok.roots_length)
    421          != json_object_size (jroots)) ||
    422        ( ((size_t) lr.details.ok.programs_length)
    423          != json_object_size (jprograms)) ||
    424        ( ((size_t) lr.details.ok.checks_length)
    425          != json_object_size (jchecks)) )
    426   {
    427     GNUNET_break_op (0);
    428     return GNUNET_SYSERR;
    429   }
    430 
    431   {
    432     struct TALER_EXCHANGE_AvailableAmlMeasures roots[
    433       GNUNET_NZL (lr.details.ok.roots_length)];
    434     struct TALER_EXCHANGE_AvailableAmlPrograms progs[
    435       GNUNET_NZL (lr.details.ok.programs_length)];
    436     struct TALER_EXCHANGE_AvailableKycChecks checks[
    437       GNUNET_NZL (lr.details.ok.checks_length)];
    438     enum GNUNET_GenericReturnValue ret;
    439 
    440     memset (roots,
    441             0,
    442             sizeof (roots));
    443     memset (progs,
    444             0,
    445             sizeof (progs));
    446     memset (checks,
    447             0,
    448             sizeof (checks));
    449     lr.details.ok.roots = roots;
    450     lr.details.ok.programs = progs;
    451     lr.details.ok.checks = checks;
    452     ret = parse_aml_roots (jroots,
    453                            roots);
    454     if (GNUNET_OK == ret)
    455       ret = parse_aml_programs (lh,
    456                                 jprograms,
    457                                 progs);
    458     if (GNUNET_OK == ret)
    459       ret = parse_aml_checks (lh,
    460                               jchecks,
    461                               checks);
    462     if (GNUNET_OK == ret)
    463     {
    464       lh->measures_cb (lh->measures_cb_cls,
    465                        &lr);
    466       lh->measures_cb = NULL;
    467     }
    468     free_scrap (lh);
    469     return ret;
    470   }
    471 }
    472 
    473 
    474 /**
    475  * Function called when we're done processing the
    476  * HTTP /aml/$OFFICER_PUB/measures request.
    477  *
    478  * @param cls the `struct TALER_EXCHANGE_AmlGetMeasuresHandle`
    479  * @param response_code HTTP response code, 0 on error
    480  * @param response parsed JSON result, NULL on error
    481  */
    482 static void
    483 handle_lookup_finished (void *cls,
    484                         long response_code,
    485                         const void *response)
    486 {
    487   struct TALER_EXCHANGE_AmlGetMeasuresHandle *lh = cls;
    488   const json_t *j = response;
    489   struct TALER_EXCHANGE_AmlGetMeasuresResponse lr = {
    490     .hr.reply = j,
    491     .hr.http_status = (unsigned int) response_code
    492   };
    493 
    494   lh->job = NULL;
    495   switch (response_code)
    496   {
    497   case 0:
    498     lr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
    499     break;
    500   case MHD_HTTP_OK:
    501     if (GNUNET_OK !=
    502         parse_measures_ok (lh,
    503                            j))
    504     {
    505       GNUNET_break_op (0);
    506       lr.hr.http_status = 0;
    507       lr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
    508       break;
    509     }
    510     GNUNET_assert (NULL == lh->measures_cb);
    511     TALER_EXCHANGE_aml_get_measures_cancel (lh);
    512     return;
    513   case MHD_HTTP_NO_CONTENT:
    514     break;
    515   case MHD_HTTP_BAD_REQUEST:
    516     lr.hr.ec = TALER_JSON_get_error_code (j);
    517     lr.hr.hint = TALER_JSON_get_error_hint (j);
    518     /* This should never happen, either us or the exchange is buggy
    519        (or API version conflict); just pass JSON reply to the application */
    520     break;
    521   case MHD_HTTP_FORBIDDEN:
    522     lr.hr.ec = TALER_JSON_get_error_code (j);
    523     lr.hr.hint = TALER_JSON_get_error_hint (j);
    524     /* Nothing really to verify, exchange says this coin was not melted; we
    525        should pass the JSON reply to the application */
    526     break;
    527   case MHD_HTTP_INTERNAL_SERVER_ERROR:
    528     lr.hr.ec = TALER_JSON_get_error_code (j);
    529     lr.hr.hint = TALER_JSON_get_error_hint (j);
    530     /* Server had an internal issue; we should retry, but this API
    531        leaves this to the application */
    532     break;
    533   default:
    534     /* unexpected response code */
    535     GNUNET_break_op (0);
    536     lr.hr.ec = TALER_JSON_get_error_code (j);
    537     lr.hr.hint = TALER_JSON_get_error_hint (j);
    538     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    539                 "Unexpected response code %u/%d for get AML measures\n",
    540                 (unsigned int) response_code,
    541                 (int) lr.hr.ec);
    542     break;
    543   }
    544   if (NULL != lh->measures_cb)
    545     lh->measures_cb (lh->measures_cb_cls,
    546                      &lr);
    547   TALER_EXCHANGE_aml_get_measures_cancel (lh);
    548 }
    549 
    550 
    551 struct TALER_EXCHANGE_AmlGetMeasuresHandle *
    552 TALER_EXCHANGE_aml_get_measures (
    553   struct GNUNET_CURL_Context *ctx,
    554   const char *exchange_url,
    555   const struct TALER_AmlOfficerPrivateKeyP *officer_priv,
    556   TALER_EXCHANGE_AmlMeasuresCallback cb,
    557   void *cb_cls)
    558 {
    559   struct TALER_EXCHANGE_AmlGetMeasuresHandle *lh;
    560   CURL *eh;
    561   struct TALER_AmlOfficerPublicKeyP officer_pub;
    562   struct TALER_AmlOfficerSignatureP officer_sig;
    563   char arg_str[sizeof (struct TALER_AmlOfficerPublicKeyP) * 2
    564                + 32];
    565 
    566   GNUNET_CRYPTO_eddsa_key_get_public (&officer_priv->eddsa_priv,
    567                                       &officer_pub.eddsa_pub);
    568   TALER_officer_aml_query_sign (officer_priv,
    569                                 &officer_sig);
    570   {
    571     char pub_str[sizeof (officer_pub) * 2];
    572     char *end;
    573 
    574     end = GNUNET_STRINGS_data_to_string (
    575       &officer_pub,
    576       sizeof (officer_pub),
    577       pub_str,
    578       sizeof (pub_str));
    579     *end = '\0';
    580     GNUNET_snprintf (arg_str,
    581                      sizeof (arg_str),
    582                      "aml/%s/measures",
    583                      pub_str);
    584   }
    585   lh = GNUNET_new (struct TALER_EXCHANGE_AmlGetMeasuresHandle);
    586   lh->measures_cb = cb;
    587   lh->measures_cb_cls = cb_cls;
    588   lh->url = TALER_url_join (exchange_url,
    589                             arg_str,
    590                             NULL);
    591   if (NULL == lh->url)
    592   {
    593     GNUNET_free (lh);
    594     return NULL;
    595   }
    596   eh = TALER_EXCHANGE_curl_easy_get_ (lh->url);
    597   if (NULL == eh)
    598   {
    599     GNUNET_break (0);
    600     GNUNET_free (lh->url);
    601     GNUNET_free (lh);
    602     return NULL;
    603   }
    604   {
    605     char *hdr;
    606     char sig_str[sizeof (officer_sig) * 2];
    607     char *end;
    608 
    609     end = GNUNET_STRINGS_data_to_string (
    610       &officer_sig,
    611       sizeof (officer_sig),
    612       sig_str,
    613       sizeof (sig_str));
    614     *end = '\0';
    615 
    616     GNUNET_asprintf (&hdr,
    617                      "%s: %s",
    618                      TALER_AML_OFFICER_SIGNATURE_HEADER,
    619                      sig_str);
    620     lh->job_headers = curl_slist_append (NULL,
    621                                          hdr);
    622     GNUNET_free (hdr);
    623     lh->job_headers = curl_slist_append (lh->job_headers,
    624                                          "Content-type: application/json");
    625     lh->job = GNUNET_CURL_job_add2 (ctx,
    626                                     eh,
    627                                     lh->job_headers,
    628                                     &handle_lookup_finished,
    629                                     lh);
    630   }
    631   return lh;
    632 }
    633 
    634 
    635 void
    636 TALER_EXCHANGE_aml_get_measures_cancel (
    637   struct TALER_EXCHANGE_AmlGetMeasuresHandle *lh)
    638 {
    639   if (NULL != lh->job)
    640   {
    641     GNUNET_CURL_job_cancel (lh->job);
    642     lh->job = NULL;
    643   }
    644   curl_slist_free_all (lh->job_headers);
    645   GNUNET_free (lh->url);
    646   GNUNET_free (lh);
    647 }
    648 
    649 
    650 /* end of exchange_api_get_aml_measures.c */