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-OFFICER_PUB-legitimizations.c (14860B)


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