donau

Donation authority for GNU Taler (experimental)
Log | Files | Refs | Submodules | README | LICENSE

donau_api_batch_issue_receipts.c (10861B)


      1 /*
      2   This file is part of TALER
      3   Copyright (C) 2024 Taler Systems SA
      4 
      5   TALER is free software; you can redistribute it and/or modify it
      6   under the terms of the GNU General Public License as published
      7   by the Free Software Foundation; either version 3, or (at your
      8   option) any later version.
      9 
     10   TALER is distributed in the hope that it will be useful, but
     11   WITHOUT ANY WARRANTY; without even the implied warranty of
     12   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     13   GNU General Public License for more details.
     14 
     15   You should have received a copy of the GNU General Public
     16   License along with TALER; see the file COPYING.  If not, see
     17   <http://www.gnu.org/licenses/>
     18 */
     19 
     20 /**
     21  * @file lib/donau_api_batch_issue_receipts.c
     22  * @brief Implementation of the "handle" component of the donau's HTTP API
     23  * @author Lukas Matyja
     24  */
     25 #include <gnunet/gnunet_curl_lib.h>
     26 #include <taler/taler_json_lib.h>
     27 #include <taler/taler_curl_lib.h>
     28 #include "donau_service.h"
     29 #include "donau_util.h"
     30 #include "donau_api_curl_defaults.h"
     31 #include "donau_json_lib.h"
     32 
     33 
     34 /**
     35  * Handle for a POST /batch-issue/$CHARITY_ID request.
     36  */
     37 struct DONAU_BatchIssueReceiptHandle
     38 {
     39   /**
     40    * The url for the /batch-issue/$CHARITY_ID request.
     41    */
     42   char *url;
     43 
     44   /**
     45    * Minor context that holds body and headers.
     46    */
     47   struct TALER_CURL_PostContext post_ctx;
     48 
     49   /**
     50    * Entry for this request with the `struct GNUNET_CURL_Context`.
     51    */
     52   struct GNUNET_CURL_Job *job;
     53 
     54   /**
     55    * Function to call with the result.
     56    */
     57   DONAU_BatchIssueReceiptsCallback cb;
     58 
     59   /**
     60    * BUDI-key-pair signature.
     61    */
     62   struct DONAU_CharitySignatureP charity_sig;
     63 
     64   /**
     65    * number of requested signatures.
     66    */
     67   size_t num_blinded_sigs;
     68 
     69   /**
     70    * Closure to pass to @e cb.
     71    */
     72   void *cb_cls;
     73 
     74   /**
     75    * Reference to the execution context.
     76    */
     77   struct GNUNET_CURL_Context *ctx;
     78 
     79 };
     80 
     81 
     82 /**
     83  * Decode the JSON in @a resp_obj from the /batch-issue/$CHARITY_ID response
     84  * and store the data in the @a biresp.
     85  *
     86  * @param[in] resp_obj JSON object to parse
     87  * @param[in] birh contains the callback function
     88  * @param[out] biresp where to store the results we decoded
     89  * @return #GNUNET_OK on success, #GNUNET_SYSERR on error
     90  * (malformed JSON)
     91  */
     92 static enum GNUNET_GenericReturnValue
     93 handle_batch_issue_ok (const json_t *resp_obj,
     94                        struct DONAU_BatchIssueReceiptHandle *birh,
     95                        struct DONAU_BatchIssueResponse *biresp)
     96 {
     97   const json_t *j_blind_signatures;
     98   struct GNUNET_JSON_Specification spec[] = {
     99     TALER_JSON_spec_amount_any ("issued_amount",
    100                                 &biresp->details.ok.issued_amount),
    101     GNUNET_JSON_spec_array_const ("blind_signatures",
    102                                   &j_blind_signatures),
    103     GNUNET_JSON_spec_end ()
    104   };
    105 
    106   if (GNUNET_OK !=
    107       GNUNET_JSON_parse (resp_obj,
    108                          spec,
    109                          NULL,
    110                          NULL))
    111   {
    112     GNUNET_break_op (0);
    113     return GNUNET_SYSERR;
    114   }
    115   if ( (NULL == j_blind_signatures) ||
    116        (! json_is_array (j_blind_signatures)) )
    117   {
    118     GNUNET_break (0);
    119     return GNUNET_SYSERR;
    120   }
    121   biresp->details.ok.num_blinded_sigs
    122     = json_array_size (j_blind_signatures);
    123   biresp->details.ok.blinded_sigs =
    124     GNUNET_new_array (birh->num_blinded_sigs,
    125                       struct DONAU_BlindedDonationUnitSignature);
    126   {
    127     size_t index;
    128     json_t *du_sig_obj;
    129 
    130     json_array_foreach (j_blind_signatures,
    131                         index,
    132                         du_sig_obj)
    133     {
    134       struct GNUNET_JSON_Specification ispec[] = {
    135         DONAU_JSON_spec_blinded_donation_unit_sig (
    136           "blinded_signature",
    137           &biresp->details.ok.blinded_sigs[index]),
    138         GNUNET_JSON_spec_end ()
    139       };
    140 
    141       if (GNUNET_OK !=
    142           GNUNET_JSON_parse (du_sig_obj,
    143                              ispec,
    144                              NULL,
    145                              NULL))
    146       {
    147         GNUNET_break_op (0);
    148         return GNUNET_SYSERR;
    149       }
    150     }
    151   }
    152   birh->cb (birh->cb_cls,
    153             biresp);
    154   birh->cb = NULL;
    155   for (unsigned int i=0; i<biresp->details.ok.num_blinded_sigs; i++)
    156   {
    157     struct DONAU_BlindedDonationUnitSignature *sig
    158       = &biresp->details.ok.blinded_sigs[i];
    159 
    160     GNUNET_CRYPTO_blinded_sig_decref (sig->blinded_sig);
    161   }
    162   GNUNET_free (biresp->details.ok.blinded_sigs);
    163   return GNUNET_OK;
    164 }
    165 
    166 
    167 /**
    168  * Transform issue receipt request into JSON.
    169  *
    170  * @param num_bkp number of budi-key-pairs in @a bkp
    171  * @param bkp budi-key-pair array
    172  * @param year corresponding year
    173  * @param charity_sig signature from charity over @a bkp
    174  */
    175 static json_t *
    176 issue_receipt_body_to_json (
    177   const unsigned int num_bkp,
    178   const struct DONAU_BlindedUniqueDonorIdentifierKeyPair *bkp,
    179   const uint64_t year,
    180   const struct DONAU_CharitySignatureP *charity_sig)
    181 {
    182   json_t *budikeypairs = json_array ();
    183 
    184   GNUNET_assert (NULL != budikeypairs);
    185   for (size_t i = 0; i < num_bkp; i++)
    186   {
    187     json_t *budikeypair = GNUNET_JSON_PACK (
    188       GNUNET_JSON_pack_data_auto ("h_donation_unit_pub",
    189                                   &bkp[i].h_donation_unit_pub.hash),
    190       DONAU_JSON_pack_blinded_donation_identifier ("blinded_udi",
    191                                                    &bkp[i].blinded_udi));
    192     GNUNET_assert (0 ==
    193                    json_array_append_new (budikeypairs,
    194                                           budikeypair));
    195   }
    196   return GNUNET_JSON_PACK (
    197     GNUNET_JSON_pack_array_steal ("budikeypairs",
    198                                   budikeypairs),
    199     GNUNET_JSON_pack_data_auto ("charity_sig",
    200                                 &charity_sig->eddsa_sig),
    201     GNUNET_JSON_pack_uint64 ("year",
    202                              year));
    203 }
    204 
    205 
    206 /**
    207  * Function called when we're done processing the
    208  * HTTP POST /batch-issue/$CHARITY_ID request.
    209  *
    210  * @param cls the `struct KeysRequest`
    211  * @param response_code HTTP response code, 0 on error
    212  * @param resp_obj parsed JSON result, NULL on error
    213  */
    214 static void
    215 handle_batch_issue_finished (void *cls,
    216                              long response_code,
    217                              const void *resp_obj)
    218 {
    219   struct DONAU_BatchIssueReceiptHandle *birh = cls;
    220   const json_t *j = resp_obj;
    221   struct DONAU_BatchIssueResponse biresp = {
    222     .hr.reply = j,
    223     .hr.http_status = (unsigned int) response_code
    224   };
    225 
    226   birh->job = NULL;
    227   switch (response_code)
    228   {
    229   case MHD_HTTP_OK:
    230     if (GNUNET_OK !=
    231         handle_batch_issue_ok (j,
    232                                birh,
    233                                &biresp))
    234     {
    235       biresp.hr.http_status = 0;
    236       biresp.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
    237     }
    238     break;
    239   case MHD_HTTP_NO_CONTENT:
    240     biresp.hr.ec = TALER_JSON_get_error_code (j);
    241     biresp.hr.hint = TALER_JSON_get_error_hint (j);
    242     break;
    243   // invalid charity signature
    244   case MHD_HTTP_FORBIDDEN:
    245     biresp.hr.ec = TALER_JSON_get_error_code (j);
    246     biresp.hr.hint = TALER_JSON_get_error_hint (j);
    247     break;
    248   // one or more donation units are not known to the Donau
    249   case MHD_HTTP_NOT_FOUND:
    250     biresp.hr.ec = TALER_JSON_get_error_code (j);
    251     biresp.hr.hint = TALER_JSON_get_error_hint (j);
    252     break;
    253   case MHD_HTTP_CONTENT_TOO_LARGE:
    254     biresp.hr.ec = TALER_JSON_get_error_code (j);
    255     biresp.hr.hint = TALER_JSON_get_error_hint (j);
    256     break;
    257   // Donation limit is not sufficient
    258   case MHD_HTTP_CONFLICT:
    259     biresp.hr.ec = TALER_JSON_get_error_code (j);
    260     biresp.hr.hint = TALER_JSON_get_error_hint (j);
    261     break;
    262   // donation unit key is no longer valid
    263   case MHD_HTTP_GONE:
    264     biresp.hr.ec = TALER_JSON_get_error_code (j);
    265     biresp.hr.hint = TALER_JSON_get_error_hint (j);
    266     break;
    267   // donation unit key is not yet valid
    268   case MHD_HTTP_TOO_EARLY:
    269     biresp.hr.ec = TALER_JSON_get_error_code (j);
    270     biresp.hr.hint = TALER_JSON_get_error_hint (j);
    271     break;
    272   default:
    273     /* unexpected response code */
    274     GNUNET_break_op (0);
    275     biresp.hr.ec = TALER_JSON_get_error_code (j);
    276     biresp.hr.hint = TALER_JSON_get_error_hint (j);
    277     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    278                 "Unexpected response code %u/%d for POST %s\n",
    279                 (unsigned int) response_code,
    280                 (int) biresp.hr.ec,
    281                 birh->url);
    282     break;
    283   }
    284   if (NULL != birh->cb)
    285   {
    286     birh->cb (birh->cb_cls,
    287               &biresp);
    288     birh->cb = NULL;
    289   }
    290   DONAU_charity_issue_receipt_cancel (birh);
    291 }
    292 
    293 
    294 struct DONAU_BatchIssueReceiptHandle *
    295 DONAU_charity_issue_receipt (
    296   struct GNUNET_CURL_Context *ctx,
    297   const char *url,
    298   const struct DONAU_CharityPrivateKeyP *charity_priv,
    299   const uint64_t charity_id,
    300   const uint64_t year,
    301   const size_t num_bkp,
    302   const struct DONAU_BlindedUniqueDonorIdentifierKeyPair *bkp,
    303   DONAU_BatchIssueReceiptsCallback cb,
    304   void *cb_cls)
    305 {
    306   CURL *eh;
    307   json_t *body;
    308   char arg_str[sizeof (charity_id) * 2 + 32];
    309   struct DONAU_BatchIssueReceiptHandle *birh;
    310 
    311   birh = GNUNET_new (struct DONAU_BatchIssueReceiptHandle);
    312   birh->num_blinded_sigs = num_bkp;
    313   DONAU_charity_bkp_sign (num_bkp, bkp,
    314                           charity_priv,
    315                           &birh->charity_sig);
    316   birh->cb = cb;
    317   birh->cb_cls = cb_cls;
    318   birh->ctx = ctx;
    319   GNUNET_snprintf (arg_str,
    320                    sizeof (arg_str),
    321                    "batch-issue/%llu",
    322                    (unsigned long long) charity_id);
    323   birh->url = TALER_url_join (url,
    324                               arg_str,
    325                               NULL);
    326   if (NULL == birh->url)
    327   {
    328     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    329                 "Could not construct request URL.\n");
    330     GNUNET_free (birh);
    331     return NULL;
    332   }
    333   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
    334               "issue_receipts_with_URL `%s'.\n",
    335               birh->url);
    336   body = issue_receipt_body_to_json (num_bkp,
    337                                      bkp,
    338                                      year,
    339                                      &birh->charity_sig);
    340   eh = DONAU_curl_easy_get_ (birh->url);
    341   if ( (NULL == eh) ||
    342        (GNUNET_OK !=
    343         TALER_curl_easy_post (&birh->post_ctx,
    344                               eh,
    345                               body)) )
    346   {
    347     GNUNET_break (0);
    348     if (NULL != eh)
    349       curl_easy_cleanup (eh);
    350     json_decref (body);
    351     GNUNET_free (birh->url);
    352     return NULL;
    353   }
    354   json_decref (body);
    355   birh->job = GNUNET_CURL_job_add2 (ctx,
    356                                     eh,
    357                                     birh->post_ctx.headers,
    358                                     &handle_batch_issue_finished,
    359                                     birh);
    360   return birh;
    361 }
    362 
    363 
    364 void
    365 DONAU_charity_issue_receipt_cancel (
    366   struct DONAU_BatchIssueReceiptHandle *birh)
    367 {
    368   if (NULL != birh->job)
    369   {
    370     GNUNET_CURL_job_cancel (birh->job);
    371     birh->job = NULL;
    372   }
    373   TALER_curl_easy_post_finished (&birh->post_ctx);
    374   GNUNET_free (birh->url);
    375   GNUNET_free (birh);
    376 }