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


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