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-attributes-H_NORMALIZED_PAYTO.c (13336B)


      1 /*
      2   This file is part of TALER
      3   Copyright (C) 2023, 2024, 2025, 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-aml-OFFICER_PUB-attributes-H_NORMALIZED_PAYTO.c
     19  * @brief Implementation of the /aml/$OFFICER_PUB/attributes request
     20  * @author Christian Grothoff
     21  */
     22 #include <microhttpd.h> /* just for HTTP status codes */
     23 #include <gnunet/gnunet_util_lib.h>
     24 #include <gnunet/gnunet_curl_lib.h>
     25 #include "taler/taler_json_lib.h"
     26 #include "exchange_api_handle.h"
     27 #include "taler/taler_signatures.h"
     28 #include "exchange_api_curl_defaults.h"
     29 #include \
     30   "taler/exchange/get-aml-OFFICER_PUB-attributes-H_NORMALIZED_PAYTO.h"
     31 
     32 
     33 /**
     34  * @brief A GET /aml/$OFFICER_PUB/attributes/$H_NORMALIZED_PAYTO Handle
     35  */
     36 struct TALER_EXCHANGE_GetAmlAttributesHandle
     37 {
     38 
     39   /**
     40    * Base URL of the exchange.
     41    */
     42   char *base_url;
     43 
     44   /**
     45    * The url for this request.
     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_GetAmlAttributesCallback cb;
     58 
     59   /**
     60    * Closure for @e cb.
     61    */
     62   TALER_EXCHANGE_GET_AML_ATTRIBUTES_RESULT_CLOSURE *cb_cls;
     63 
     64   /**
     65    * CURL context to use.
     66    */
     67   struct GNUNET_CURL_Context *ctx;
     68 
     69   /**
     70    * Public key of the AML officer (computed from officer_priv in _create).
     71    */
     72   struct TALER_AmlOfficerPublicKeyP officer_pub;
     73 
     74   /**
     75    * Private key of the AML officer (stored for signing in _start).
     76    */
     77   struct TALER_AmlOfficerPrivateKeyP officer_priv;
     78 
     79   /**
     80    * Hash of the normalized payto URI for the account.
     81    */
     82   struct TALER_NormalizedPaytoHashP h_payto;
     83 
     84   /**
     85    * Options set for this request.
     86    */
     87   struct
     88   {
     89     /**
     90      * Limit on the number of results (negative = before offset, positive = after).
     91      * Default: -20.
     92      */
     93     int64_t limit;
     94 
     95     /**
     96      * Row offset threshold. Default: UINT64_MAX.
     97      */
     98     uint64_t offset;
     99   } options;
    100 
    101 };
    102 
    103 
    104 /**
    105  * Parse AML attribute collection events from a JSON array.
    106  *
    107  * @param jdetails JSON array with AML attribute collection events
    108  * @param[out] detail_ar where to write the results
    109  * @return #GNUNET_OK on success
    110  */
    111 static enum GNUNET_GenericReturnValue
    112 parse_attributes (
    113   const json_t *jdetails,
    114   struct TALER_EXCHANGE_GetAmlAttributesCollectionEvent *detail_ar)
    115 {
    116   json_t *obj;
    117   size_t idx;
    118 
    119   json_array_foreach (jdetails, idx, obj)
    120   {
    121     struct TALER_EXCHANGE_GetAmlAttributesCollectionEvent *detail
    122       = &detail_ar[idx];
    123     bool by_aml_officer = false;
    124     struct GNUNET_JSON_Specification spec[] = {
    125       GNUNET_JSON_spec_uint64 ("rowid",
    126                                &detail->rowid),
    127       GNUNET_JSON_spec_bool ("by_aml_officer",
    128                              &by_aml_officer),
    129       GNUNET_JSON_spec_mark_optional (
    130         GNUNET_JSON_spec_object_const ("attributes",
    131                                        &detail->attributes),
    132         NULL),
    133       GNUNET_JSON_spec_timestamp ("collection_time",
    134                                   &detail->collection_time),
    135       GNUNET_JSON_spec_end ()
    136     };
    137 
    138     detail->by_aml_officer = false;
    139     detail->attributes = NULL;
    140     if (GNUNET_OK !=
    141         GNUNET_JSON_parse (obj,
    142                            spec,
    143                            NULL,
    144                            NULL))
    145     {
    146       GNUNET_break_op (0);
    147       return GNUNET_SYSERR;
    148     }
    149     detail->by_aml_officer = by_aml_officer;
    150   }
    151   return GNUNET_OK;
    152 }
    153 
    154 
    155 /**
    156  * Parse the provided data from the "200 OK" response.
    157  *
    158  * @param[in,out] aagh handle (callback may be zero'ed out)
    159  * @param json json reply with the data
    160  * @return #GNUNET_OK on success, #GNUNET_SYSERR on error
    161  */
    162 static enum GNUNET_GenericReturnValue
    163 parse_get_aml_attributes_ok (
    164   struct TALER_EXCHANGE_GetAmlAttributesHandle *aagh,
    165   const json_t *json)
    166 {
    167   struct TALER_EXCHANGE_GetAmlAttributesResponse lr = {
    168     .hr.reply = json,
    169     .hr.http_status = MHD_HTTP_OK
    170   };
    171   const json_t *jdetails;
    172   struct GNUNET_JSON_Specification spec[] = {
    173     GNUNET_JSON_spec_array_const ("details",
    174                                   &jdetails),
    175     GNUNET_JSON_spec_end ()
    176   };
    177 
    178   if (GNUNET_OK !=
    179       GNUNET_JSON_parse (json,
    180                          spec,
    181                          NULL,
    182                          NULL))
    183   {
    184     GNUNET_break_op (0);
    185     return GNUNET_SYSERR;
    186   }
    187   lr.details.ok.details_length = json_array_size (jdetails);
    188   {
    189     struct TALER_EXCHANGE_GetAmlAttributesCollectionEvent details[
    190       GNUNET_NZL (lr.details.ok.details_length)];
    191 
    192     memset (details,
    193             0,
    194             sizeof (details));
    195     lr.details.ok.details = details;
    196     if (GNUNET_OK !=
    197         parse_attributes (jdetails,
    198                           details))
    199     {
    200       GNUNET_break_op (0);
    201       return GNUNET_SYSERR;
    202     }
    203     aagh->cb (aagh->cb_cls,
    204               &lr);
    205     aagh->cb = NULL;
    206   }
    207   return GNUNET_OK;
    208 }
    209 
    210 
    211 /**
    212  * Function called when we're done processing the
    213  * HTTP GET /aml/$OFFICER_PUB/attributes/$H_NORMALIZED_PAYTO request (new API).
    214  *
    215  * @param cls the `struct TALER_EXCHANGE_GetAmlAttributesHandle`
    216  * @param response_code HTTP response code, 0 on error
    217  * @param response parsed JSON result, NULL on error
    218  */
    219 static void
    220 handle_get_aml_attributes_finished (void *cls,
    221                                     long response_code,
    222                                     const void *response)
    223 {
    224   struct TALER_EXCHANGE_GetAmlAttributesHandle *aagh = cls;
    225   const json_t *j = response;
    226   struct TALER_EXCHANGE_GetAmlAttributesResponse lr = {
    227     .hr.reply = j,
    228     .hr.http_status = (unsigned int) response_code
    229   };
    230 
    231   aagh->job = NULL;
    232   switch (response_code)
    233   {
    234   case 0:
    235     lr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
    236     break;
    237   case MHD_HTTP_OK:
    238     if (GNUNET_OK !=
    239         parse_get_aml_attributes_ok (aagh,
    240                                      j))
    241     {
    242       GNUNET_break_op (0);
    243       lr.hr.http_status = 0;
    244       lr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
    245       break;
    246     }
    247     GNUNET_assert (NULL == aagh->cb);
    248     TALER_EXCHANGE_get_aml_attributes_cancel (aagh);
    249     return;
    250   case MHD_HTTP_NO_CONTENT:
    251     break;
    252   case MHD_HTTP_NOT_IMPLEMENTED:
    253     lr.hr.ec = TALER_JSON_get_error_code (j);
    254     lr.hr.hint = TALER_JSON_get_error_hint (j);
    255     break;
    256   case MHD_HTTP_BAD_REQUEST:
    257     lr.hr.ec = TALER_JSON_get_error_code (j);
    258     lr.hr.hint = TALER_JSON_get_error_hint (j);
    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     break;
    262   case MHD_HTTP_FORBIDDEN:
    263     lr.hr.ec = TALER_JSON_get_error_code (j);
    264     lr.hr.hint = TALER_JSON_get_error_hint (j);
    265     break;
    266   case MHD_HTTP_NOT_FOUND:
    267     lr.hr.ec = TALER_JSON_get_error_code (j);
    268     lr.hr.hint = TALER_JSON_get_error_hint (j);
    269     break;
    270   case MHD_HTTP_NOT_ACCEPTABLE:
    271     lr.hr.ec = TALER_JSON_get_error_code (j);
    272     lr.hr.hint = TALER_JSON_get_error_hint (j);
    273     break;
    274   case MHD_HTTP_INTERNAL_SERVER_ERROR:
    275     lr.hr.ec = TALER_JSON_get_error_code (j);
    276     lr.hr.hint = TALER_JSON_get_error_hint (j);
    277     /* Server had an internal issue; we should retry, but this API
    278        leaves this to the application */
    279     break;
    280   default:
    281     /* unexpected response code */
    282     GNUNET_break_op (0);
    283     lr.hr.ec = TALER_JSON_get_error_code (j);
    284     lr.hr.hint = TALER_JSON_get_error_hint (j);
    285     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    286                 "Unexpected response code %u/%d for get AML attributes\n",
    287                 (unsigned int) response_code,
    288                 (int) lr.hr.ec);
    289     break;
    290   }
    291   if (NULL != aagh->cb)
    292     aagh->cb (aagh->cb_cls,
    293               &lr);
    294   TALER_EXCHANGE_get_aml_attributes_cancel (aagh);
    295 }
    296 
    297 
    298 struct TALER_EXCHANGE_GetAmlAttributesHandle *
    299 TALER_EXCHANGE_get_aml_attributes_create (
    300   struct GNUNET_CURL_Context *ctx,
    301   const char *url,
    302   const struct TALER_AmlOfficerPrivateKeyP *officer_priv,
    303   const struct TALER_NormalizedPaytoHashP *h_payto)
    304 {
    305   struct TALER_EXCHANGE_GetAmlAttributesHandle *aagh;
    306 
    307   aagh = GNUNET_new (struct TALER_EXCHANGE_GetAmlAttributesHandle);
    308   aagh->ctx = ctx;
    309   aagh->base_url = GNUNET_strdup (url);
    310   aagh->h_payto = *h_payto;
    311   aagh->officer_priv = *officer_priv;
    312   GNUNET_CRYPTO_eddsa_key_get_public (&officer_priv->eddsa_priv,
    313                                       &aagh->officer_pub.eddsa_pub);
    314   aagh->options.limit = -20;
    315   aagh->options.offset = INT64_MAX;
    316   return aagh;
    317 }
    318 
    319 
    320 enum GNUNET_GenericReturnValue
    321 TALER_EXCHANGE_get_aml_attributes_set_options_ (
    322   struct TALER_EXCHANGE_GetAmlAttributesHandle *aagh,
    323   unsigned int num_options,
    324   const struct TALER_EXCHANGE_GetAmlAttributesOptionValue *options)
    325 {
    326   for (unsigned int i = 0; i < num_options; i++)
    327   {
    328     const struct TALER_EXCHANGE_GetAmlAttributesOptionValue *opt = &options[i];
    329 
    330     switch (opt->option)
    331     {
    332     case TALER_EXCHANGE_GET_AML_ATTRIBUTES_OPTION_END:
    333       return GNUNET_OK;
    334     case TALER_EXCHANGE_GET_AML_ATTRIBUTES_OPTION_LIMIT:
    335       aagh->options.limit = opt->details.limit;
    336       break;
    337     case TALER_EXCHANGE_GET_AML_ATTRIBUTES_OPTION_OFFSET:
    338       if (opt->details.offset > INT64_MAX)
    339       {
    340         GNUNET_break (0);
    341         return GNUNET_NO;
    342       }
    343       aagh->options.offset = opt->details.offset;
    344       break;
    345     }
    346   }
    347   return GNUNET_OK;
    348 }
    349 
    350 
    351 enum TALER_ErrorCode
    352 TALER_EXCHANGE_get_aml_attributes_start (
    353   struct TALER_EXCHANGE_GetAmlAttributesHandle *aagh,
    354   TALER_EXCHANGE_GetAmlAttributesCallback cb,
    355   TALER_EXCHANGE_GET_AML_ATTRIBUTES_RESULT_CLOSURE *cb_cls)
    356 {
    357   struct TALER_AmlOfficerSignatureP officer_sig;
    358   char arg_str[sizeof (aagh->officer_pub) * 2
    359                + sizeof (aagh->h_payto) * 2
    360                + 32];
    361   CURL *eh;
    362 
    363   if (NULL != aagh->job)
    364   {
    365     GNUNET_break (0);
    366     return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
    367   }
    368   aagh->cb = cb;
    369   aagh->cb_cls = cb_cls;
    370   TALER_officer_aml_query_sign (&aagh->officer_priv,
    371                                 &officer_sig);
    372   {
    373     char payto_s[sizeof (aagh->h_payto) * 2];
    374     char pub_str[sizeof (aagh->officer_pub) * 2];
    375     char *end;
    376 
    377     end = GNUNET_STRINGS_data_to_string (
    378       &aagh->h_payto,
    379       sizeof (aagh->h_payto),
    380       payto_s,
    381       sizeof (payto_s));
    382     *end = '\0';
    383     end = GNUNET_STRINGS_data_to_string (
    384       &aagh->officer_pub,
    385       sizeof (aagh->officer_pub),
    386       pub_str,
    387       sizeof (pub_str));
    388     *end = '\0';
    389     GNUNET_snprintf (arg_str,
    390                      sizeof (arg_str),
    391                      "aml/%s/attributes/%s",
    392                      pub_str,
    393                      payto_s);
    394   }
    395   {
    396     char limit_s[24];
    397     char offset_s[24];
    398 
    399     GNUNET_snprintf (limit_s,
    400                      sizeof (limit_s),
    401                      "%lld",
    402                      (long long) aagh->options.limit);
    403     GNUNET_snprintf (offset_s,
    404                      sizeof (offset_s),
    405                      "%llu",
    406                      (unsigned long long) aagh->options.offset);
    407     aagh->url = TALER_url_join (aagh->base_url,
    408                                 arg_str,
    409                                 "limit",
    410                                 limit_s,
    411                                 "offset",
    412                                 offset_s,
    413                                 NULL);
    414   }
    415   if (NULL == aagh->url)
    416     return TALER_EC_GENERIC_CONFIGURATION_INVALID;
    417   eh = TALER_EXCHANGE_curl_easy_get_ (aagh->url);
    418   if (NULL == eh)
    419   {
    420     GNUNET_free (aagh->url);
    421     aagh->url = NULL;
    422     return TALER_EC_GENERIC_CONFIGURATION_INVALID;
    423   }
    424   {
    425     struct curl_slist *job_headers = NULL;
    426     char *hdr;
    427     char sig_str[sizeof (officer_sig) * 2];
    428     char *end;
    429 
    430     end = GNUNET_STRINGS_data_to_string (
    431       &officer_sig,
    432       sizeof (officer_sig),
    433       sig_str,
    434       sizeof (sig_str));
    435     *end = '\0';
    436     GNUNET_asprintf (&hdr,
    437                      "%s: %s",
    438                      TALER_AML_OFFICER_SIGNATURE_HEADER,
    439                      sig_str);
    440     job_headers = curl_slist_append (NULL,
    441                                      hdr);
    442     GNUNET_free (hdr);
    443     aagh->job = GNUNET_CURL_job_add2 (aagh->ctx,
    444                                       eh,
    445                                       job_headers,
    446                                       &handle_get_aml_attributes_finished,
    447                                       aagh);
    448     curl_slist_free_all (job_headers);
    449   }
    450   if (NULL == aagh->job)
    451   {
    452     GNUNET_free (aagh->url);
    453     aagh->url = NULL;
    454     return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
    455   }
    456   return TALER_EC_NONE;
    457 }
    458 
    459 
    460 void
    461 TALER_EXCHANGE_get_aml_attributes_cancel (
    462   struct TALER_EXCHANGE_GetAmlAttributesHandle *aagh)
    463 {
    464   if (NULL != aagh->job)
    465   {
    466     GNUNET_CURL_job_cancel (aagh->job);
    467     aagh->job = NULL;
    468   }
    469   GNUNET_free (aagh->url);
    470   GNUNET_free (aagh->base_url);
    471   GNUNET_free (aagh);
    472 }
    473 
    474 
    475 /* end of exchange_api_lookup_kyc_attributes.c */