exchange

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

exchange_api_aml_legitimizations_get.c (14555B)


      1 /*
      2   This file is part of TALER
      3   Copyright (C) 2025 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_aml_legitimizations_get.c
     19  * @brief Implementation of the GET /aml/$OFFICER_PUB/legitimizations requests
     20  * @author Christian Grothoff
     21  */
     22 #include "taler/platform.h"
     23 #include <jansson.h>
     24 #include <microhttpd.h> /* just for HTTP status codes */
     25 #include <gnunet/gnunet_util_lib.h>
     26 #include <gnunet/gnunet_json_lib.h>
     27 #include <gnunet/gnunet_curl_lib.h>
     28 #include "taler/taler_exchange_service.h"
     29 #include "taler/taler_json_lib.h"
     30 #include "exchange_api_handle.h"
     31 #include "taler/taler_signatures.h"
     32 #include "exchange_api_curl_defaults.h"
     33 
     34 
     35 /**
     36  * Handle for an operation to GET /aml/$OFFICER_PUB/legitimizations.
     37  */
     38 struct TALER_EXCHANGE_AmlLegitimizationsGetHandle
     39 {
     40 
     41   /**
     42    * The exchange base URL for this request.
     43    */
     44   char *exchange_base_url;
     45 
     46   /**
     47    * Our execution context.
     48    */
     49   struct GNUNET_CURL_Context *ctx;
     50 
     51   /**
     52    * Handle for the request.
     53    */
     54   struct GNUNET_CURL_Job *job;
     55 
     56   /**
     57    * Signature of the AML officer.
     58    */
     59   struct TALER_AmlOfficerSignatureP officer_sig;
     60 
     61   /**
     62    * Public key of the AML officer.
     63    */
     64   struct TALER_AmlOfficerPublicKeyP officer_pub;
     65 
     66   /**
     67    * Function to call with the result.
     68    */
     69   TALER_EXCHANGE_AmlLegitimizationsGetCallback cb;
     70 
     71   /**
     72    * Closure for @a cb.
     73    */
     74   TALER_EXCHANGE__AML_LEGITIMIZATIONS_GET_RESULT_CLOSURE *cb_cls;
     75 
     76   /**
     77    * The url for this request.
     78    */
     79   char *url;
     80 
     81   /**
     82    * HTTP headers for the job.
     83    */
     84   struct curl_slist *job_headers;
     85 
     86   /**
     87    * Request options.
     88    */
     89   struct
     90   {
     91     /**
     92      * Limit on number of results.
     93      */
     94     int64_t limit;
     95 
     96     /**
     97      * Row offset from which to return results.
     98      */
     99     uint64_t offset;
    100 
    101     /**
    102      * Hash of payto URI to filter by, NULL for no filter.
    103      */
    104     const struct TALER_NormalizedPaytoHashP *h_payto;
    105 
    106     /**
    107      * Activity filter.
    108      */
    109     enum TALER_EXCHANGE_YesNoAll active;
    110 
    111   } options;
    112 
    113 };
    114 
    115 
    116 /**
    117  * Parse a single measure details entry from JSON.
    118  *
    119  * @param md_json JSON object to parse
    120  * @param[out] md where to store the result
    121  * @return #GNUNET_OK on success
    122  */
    123 static enum GNUNET_GenericReturnValue
    124 parse_measure_details (
    125   const json_t *md_json,
    126   struct TALER_EXCHANGE_AmlLegitimizationsGetMeasureDetails *md)
    127 {
    128   struct GNUNET_JSON_Specification spec[] = {
    129     GNUNET_JSON_spec_fixed_auto ("h_payto",
    130                                  &md->h_payto),
    131     GNUNET_JSON_spec_uint64 ("rowid",
    132                              &md->rowid),
    133     GNUNET_JSON_spec_timestamp ("start_time",
    134                                 &md->start_time),
    135     GNUNET_JSON_spec_object_const ("measures",
    136                                    &md->measures),
    137     GNUNET_JSON_spec_bool ("is_finished",
    138                            &md->is_finished),
    139     GNUNET_JSON_spec_end ()
    140   };
    141 
    142   if (GNUNET_OK !=
    143       GNUNET_JSON_parse (md_json,
    144                          spec,
    145                          NULL,
    146                          NULL))
    147   {
    148     GNUNET_break_op (0);
    149     return GNUNET_SYSERR;
    150   }
    151   return GNUNET_OK;
    152 }
    153 
    154 
    155 /**
    156  * We received an #MHD_HTTP_OK status code. Handle the JSON
    157  * response.
    158  *
    159  * @param algh handle of the request
    160  * @param j JSON response
    161  * @return #GNUNET_OK on success
    162  */
    163 static enum GNUNET_GenericReturnValue
    164 handle_aml_legitimizations_get_ok (
    165   struct TALER_EXCHANGE_AmlLegitimizationsGetHandle *algh,
    166   const json_t *j)
    167 {
    168   struct TALER_EXCHANGE_AmlLegitimizationsGetResult result = {
    169     .hr.reply = j,
    170     .hr.http_status = MHD_HTTP_OK
    171   };
    172   const json_t *measures_array;
    173   struct GNUNET_JSON_Specification spec[] = {
    174     GNUNET_JSON_spec_array_const ("measures",
    175                                   &measures_array),
    176     GNUNET_JSON_spec_end ()
    177   };
    178   struct TALER_EXCHANGE_AmlLegitimizationsGetMeasureDetails *measures;
    179 
    180   if (GNUNET_OK !=
    181       GNUNET_JSON_parse (j,
    182                          spec,
    183                          NULL,
    184                          NULL))
    185   {
    186     GNUNET_break_op (0);
    187     return GNUNET_SYSERR;
    188   }
    189 
    190   result.details.ok.measures_length = json_array_size (measures_array);
    191   if (0 == result.details.ok.measures_length)
    192   {
    193     measures = NULL;
    194   }
    195   else
    196   {
    197     measures
    198       = GNUNET_new_array (
    199           result.details.ok.measures_length,
    200           struct TALER_EXCHANGE_AmlLegitimizationsGetMeasureDetails);
    201   }
    202   for (size_t i = 0; i < result.details.ok.measures_length; i++)
    203   {
    204     const json_t *measure_json = json_array_get (measures_array,
    205                                                  i);
    206 
    207     if (GNUNET_OK !=
    208         parse_measure_details (measure_json,
    209                                &measures[i]))
    210     {
    211       GNUNET_free (measures);
    212       return GNUNET_SYSERR;
    213     }
    214   }
    215   result.details.ok.measures = measures;
    216   algh->cb (algh->cb_cls,
    217             &result);
    218   algh->cb = NULL;
    219   GNUNET_free (measures);
    220   return GNUNET_OK;
    221 }
    222 
    223 
    224 /**
    225  * Function called when we're done processing the
    226  * HTTP /aml/$OFFICER_PUB/legitimizations GET request.
    227  *
    228  * @param cls the `struct TALER_EXCHANGE_AmlLegitimizationsGetHandle`
    229  * @param response_code HTTP response code, 0 on error
    230  * @param response parsed JSON result, NULL on error
    231  */
    232 static void
    233 handle_aml_legitimizations_get_finished (void *cls,
    234                                          long response_code,
    235                                          const void *response)
    236 {
    237   struct TALER_EXCHANGE_AmlLegitimizationsGetHandle *algh = cls;
    238   const json_t *j = response;
    239   struct TALER_EXCHANGE_AmlLegitimizationsGetResult result = {
    240     .hr.reply = j,
    241     .hr.http_status = (unsigned int) response_code
    242   };
    243 
    244   algh->job = NULL;
    245   switch (response_code)
    246   {
    247   case 0:
    248     result.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
    249     break;
    250   case MHD_HTTP_OK:
    251     if (GNUNET_OK !=
    252         handle_aml_legitimizations_get_ok (algh,
    253                                            j))
    254     {
    255       result.hr.http_status = 0;
    256       result.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
    257     }
    258     break;
    259   case MHD_HTTP_NO_CONTENT:
    260     /* can happen */
    261     break;
    262   case MHD_HTTP_BAD_REQUEST:
    263     /* This should never happen, either us or the exchange is buggy
    264        (or API version conflict); just pass JSON reply to the application */
    265     result.hr.ec = TALER_JSON_get_error_code (j);
    266     result.hr.hint = TALER_JSON_get_error_hint (j);
    267     break;
    268   case MHD_HTTP_UNAUTHORIZED:
    269     /* Invalid officer credentials */
    270     result.hr.ec = TALER_JSON_get_error_code (j);
    271     result.hr.hint = TALER_JSON_get_error_hint (j);
    272     break;
    273   case MHD_HTTP_FORBIDDEN:
    274     /* Officer not authorized for this operation */
    275     result.hr.ec = TALER_JSON_get_error_code (j);
    276     result.hr.hint = TALER_JSON_get_error_hint (j);
    277     break;
    278   case MHD_HTTP_NOT_FOUND:
    279     /* Officer not found */
    280     result.hr.ec = TALER_JSON_get_error_code (j);
    281     result.hr.hint = TALER_JSON_get_error_hint (j);
    282     break;
    283   case MHD_HTTP_INTERNAL_SERVER_ERROR:
    284     /* Server had an internal issue; we should retry, but this API
    285        leaves this to the application */
    286     result.hr.ec = TALER_JSON_get_error_code (j);
    287     result.hr.hint = TALER_JSON_get_error_hint (j);
    288     break;
    289   default:
    290     /* unexpected response code */
    291     GNUNET_break_op (0);
    292     result.hr.ec = TALER_JSON_get_error_code (j);
    293     result.hr.hint = TALER_JSON_get_error_hint (j);
    294     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    295                 "Unexpected response code %u/%d for GET %s\n",
    296                 (unsigned int) response_code,
    297                 (int) result.hr.ec,
    298                 algh->url);
    299     break;
    300   }
    301   if (NULL != algh->cb)
    302   {
    303     algh->cb (algh->cb_cls,
    304               &result);
    305     algh->cb = NULL;
    306   }
    307   TALER_EXCHANGE_aml_legitimizations_get_cancel (algh);
    308 }
    309 
    310 
    311 struct TALER_EXCHANGE_AmlLegitimizationsGetHandle *
    312 TALER_EXCHANGE_aml_legitimizations_get_create (
    313   struct GNUNET_CURL_Context *ctx,
    314   const char *exchange_base_url,
    315   const struct TALER_AmlOfficerPrivateKeyP *officer_priv)
    316 {
    317   struct TALER_EXCHANGE_AmlLegitimizationsGetHandle *algh;
    318 
    319   algh = GNUNET_new (struct TALER_EXCHANGE_AmlLegitimizationsGetHandle);
    320   algh->ctx = ctx;
    321   algh->exchange_base_url = GNUNET_strdup (exchange_base_url);
    322   GNUNET_CRYPTO_eddsa_key_get_public (&officer_priv->eddsa_priv,
    323                                       &algh->officer_pub.eddsa_pub);
    324   TALER_officer_aml_query_sign (officer_priv,
    325                                 &algh->officer_sig);
    326   algh->options.limit = -20; /* Default to last 20 entries */
    327   algh->options.offset = INT64_MAX; /* Default to maximum row id */
    328   algh->options.active = TALER_EXCHANGE_YNA_ALL; /* Default to all */
    329   return algh;
    330 }
    331 
    332 
    333 enum GNUNET_GenericReturnValue
    334 TALER_EXCHANGE_aml_legitimizations_get_set_options_ (
    335   struct TALER_EXCHANGE_AmlLegitimizationsGetHandle *algh,
    336   unsigned int num_options,
    337   const struct TALER_EXCHANGE_AmlLegitimizationsGetOptionValue *options)
    338 {
    339   for (unsigned int i = 0; i < num_options; i++)
    340   {
    341     switch (options[i].option)
    342     {
    343     case TALER_EXCHANGE_AML_LEGITIMIZATIONS_GET_OPTION_END:
    344       return GNUNET_OK;
    345     case TALER_EXCHANGE_AML_LEGITIMIZATIONS_GET_OPTION_LIMIT:
    346       algh->options.limit = options[i].details.limit;
    347       break;
    348     case TALER_EXCHANGE_AML_LEGITIMIZATIONS_GET_OPTION_OFFSET:
    349       algh->options.offset = options[i].details.offset;
    350       break;
    351     case TALER_EXCHANGE_AML_LEGITIMIZATIONS_GET_OPTION_H_PAYTO:
    352       algh->options.h_payto = options[i].details.h_payto;
    353       break;
    354     case TALER_EXCHANGE_AML_LEGITIMIZATIONS_GET_OPTION_ACTIVE:
    355       algh->options.active = options[i].details.active;
    356       break;
    357     default:
    358       GNUNET_break (0);
    359       return GNUNET_NO;
    360     }
    361   }
    362   return GNUNET_OK;
    363 }
    364 
    365 
    366 enum TALER_EXCHANGE_AmlLegitimizationsGetStartError
    367 TALER_EXCHANGE_aml_legitimizations_get_start (
    368   struct TALER_EXCHANGE_AmlLegitimizationsGetHandle *algh,
    369   TALER_EXCHANGE_AmlLegitimizationsGetCallback cb,
    370   TALER_EXCHANGE__AML_LEGITIMIZATIONS_GET_RESULT_CLOSURE *cb_cls)
    371 {
    372   char officer_pub_str[sizeof (struct TALER_AmlOfficerPublicKeyP) * 2];
    373   char arg_str[sizeof (officer_pub_str) + 64];
    374   char limit_str[24];
    375   char offset_str[24];
    376   char paytoh_str[sizeof (struct TALER_NormalizedPaytoHashP) * 2];
    377 
    378   if (NULL != algh->job)
    379   {
    380     GNUNET_break (0);
    381     return TALER_EXCHANGE_AML_LEGITIMIZATIONS_GET_START_E_AGAIN;
    382   }
    383   algh->cb = cb;
    384   algh->cb_cls = cb_cls;
    385   if (algh->options.offset > INT64_MAX)
    386   {
    387     GNUNET_break (0);
    388     algh->options.offset = INT64_MAX;
    389   }
    390   {
    391     char *end;
    392 
    393     end = GNUNET_STRINGS_data_to_string (
    394       &algh->officer_pub,
    395       sizeof (algh->officer_pub),
    396       officer_pub_str,
    397       sizeof (officer_pub_str));
    398     *end = '\0';
    399   }
    400   if (NULL != algh->options.h_payto)
    401   {
    402     char *end;
    403 
    404     end = GNUNET_STRINGS_data_to_string (
    405       algh->options.h_payto,
    406       sizeof (struct TALER_NormalizedPaytoHashP),
    407       paytoh_str,
    408       sizeof (paytoh_str));
    409     *end = '\0';
    410   }
    411   /* Build query parameters */
    412   GNUNET_snprintf (offset_str,
    413                    sizeof (offset_str),
    414                    "%llu",
    415                    (unsigned long long) algh->options.offset);
    416   GNUNET_snprintf (limit_str,
    417                    sizeof (limit_str),
    418                    "%lld",
    419                    (long long) algh->options.limit);
    420   GNUNET_snprintf (arg_str,
    421                    sizeof (arg_str),
    422                    "aml/%s/legitimizations",
    423                    officer_pub_str);
    424   algh->url = TALER_url_join (algh->exchange_base_url,
    425                               arg_str,
    426                               "limit",
    427                               limit_str,
    428                               "offset",
    429                               ( (algh->options.limit > 0) &&
    430                                 (0 == algh->options.offset) ) ||
    431                               ( (algh->options.limit <= 0) &&
    432                                 (INT64_MAX <= algh->options.offset) )
    433                               ? NULL
    434                               : offset_str,
    435                               "h_payto",
    436                               NULL == algh->options.h_payto
    437                               ? NULL
    438                               : paytoh_str,
    439                               "active",
    440                               TALER_EXCHANGE_YNA_ALL == algh->options.active
    441                               ? NULL
    442                               : TALER_yna_to_string (algh->options.active),
    443                               NULL);
    444   if (NULL == algh->url)
    445   {
    446     GNUNET_break (0);
    447     return TALER_EXCHANGE_AML_LEGITIMIZATIONS_GET_START_E_INTERNAL;
    448   }
    449 
    450   {
    451     CURL *eh;
    452 
    453     eh = TALER_EXCHANGE_curl_easy_get_ (algh->url);
    454     if (NULL == eh)
    455     {
    456       GNUNET_break (0);
    457       GNUNET_free (algh->url);
    458       algh->url = NULL;
    459       return TALER_EXCHANGE_AML_LEGITIMIZATIONS_GET_START_E_INTERNAL;
    460     }
    461 
    462     /* Add authentication header for AML officer */
    463     {
    464       char *hdr;
    465       char sig_str[sizeof (algh->officer_sig) * 2];
    466       char *end;
    467 
    468       end = GNUNET_STRINGS_data_to_string (
    469         &algh->officer_sig,
    470         sizeof (algh->officer_sig),
    471         sig_str,
    472         sizeof (sig_str));
    473       *end = '\0';
    474       GNUNET_asprintf (&hdr,
    475                        "%s: %s",
    476                        TALER_AML_OFFICER_SIGNATURE_HEADER,
    477                        sig_str);
    478       algh->job_headers = curl_slist_append (NULL,
    479                                              hdr);
    480       GNUNET_free (hdr);
    481     }
    482     algh->job
    483       = GNUNET_CURL_job_add2 (
    484           algh->ctx,
    485           eh,
    486           algh->job_headers,
    487           &handle_aml_legitimizations_get_finished,
    488           algh);
    489   }
    490   return TALER_EXCHANGE_AML_LEGITIMIZATIONS_GET_START_OK;
    491 }
    492 
    493 
    494 void
    495 TALER_EXCHANGE_aml_legitimizations_get_cancel (
    496   struct TALER_EXCHANGE_AmlLegitimizationsGetHandle *algh)
    497 {
    498   if (NULL != algh->job)
    499   {
    500     GNUNET_CURL_job_cancel (algh->job);
    501     algh->job = NULL;
    502   }
    503   curl_slist_free_all (algh->job_headers);
    504   GNUNET_free (algh->exchange_base_url);
    505   GNUNET_free (algh->url);
    506   GNUNET_free (algh);
    507 }
    508 
    509 
    510 /* end of exchange_api_aml_legitimizations_get.c */