donau

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

donau_api_batch_issue_receipts.c (10676B)


      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 @bkp
    171  * @param bkp budi-key-pair array
    172  * @param year corresponding year
    173  * @param charity_sig signature from charity over @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 sufficent
    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   default:
    268     /* unexpected response code */
    269     GNUNET_break_op (0);
    270     biresp.hr.ec = TALER_JSON_get_error_code (j);
    271     biresp.hr.hint = TALER_JSON_get_error_hint (j);
    272     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    273                 "Unexpected response code %u/%d for POST %s\n",
    274                 (unsigned int) response_code,
    275                 (int) biresp.hr.ec,
    276                 birh->url);
    277     break;
    278   }
    279   if (NULL != birh->cb)
    280   {
    281     birh->cb (birh->cb_cls,
    282               &biresp);
    283     birh->cb = NULL;
    284   }
    285   DONAU_charity_issue_receipt_cancel (birh);
    286 }
    287 
    288 
    289 struct DONAU_BatchIssueReceiptHandle *
    290 DONAU_charity_issue_receipt (
    291   struct GNUNET_CURL_Context *ctx,
    292   const char *url,
    293   const struct DONAU_CharityPrivateKeyP *charity_priv,
    294   const uint64_t charity_id,
    295   const uint64_t year,
    296   const size_t num_bkp,
    297   const struct DONAU_BlindedUniqueDonorIdentifierKeyPair *bkp,
    298   DONAU_BatchIssueReceiptsCallback cb,
    299   void *cb_cls)
    300 {
    301   CURL *eh;
    302   json_t *body;
    303   char arg_str[sizeof (charity_id) * 2 + 32];
    304   struct DONAU_BatchIssueReceiptHandle *birh;
    305 
    306   birh = GNUNET_new (struct DONAU_BatchIssueReceiptHandle);
    307   birh->num_blinded_sigs = num_bkp;
    308   DONAU_charity_bkp_sign (num_bkp, bkp,
    309                           charity_priv,
    310                           &birh->charity_sig);
    311   birh->cb = cb;
    312   birh->cb_cls = cb_cls;
    313   birh->ctx = ctx;
    314   GNUNET_snprintf (arg_str,
    315                    sizeof (arg_str),
    316                    "batch-issue/%llu",
    317                    (unsigned long long) charity_id);
    318   birh->url = TALER_url_join (url,
    319                               arg_str,
    320                               NULL);
    321   if (NULL == birh->url)
    322   {
    323     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    324                 "Could not construct request URL.\n");
    325     GNUNET_free (birh);
    326     return NULL;
    327   }
    328   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
    329               "issue_receipts_with_URL `%s'.\n",
    330               birh->url);
    331   body = issue_receipt_body_to_json (num_bkp,
    332                                      bkp,
    333                                      year,
    334                                      &birh->charity_sig);
    335   eh = DONAU_curl_easy_get_ (birh->url);
    336   if ( (NULL == eh) ||
    337        (GNUNET_OK !=
    338         TALER_curl_easy_post (&birh->post_ctx,
    339                               eh,
    340                               body)) )
    341   {
    342     GNUNET_break (0);
    343     if (NULL != eh)
    344       curl_easy_cleanup (eh);
    345     json_decref (body);
    346     GNUNET_free (birh->url);
    347     return NULL;
    348   }
    349   json_decref (body);
    350   birh->job = GNUNET_CURL_job_add2 (ctx,
    351                                     eh,
    352                                     birh->post_ctx.headers,
    353                                     &handle_batch_issue_finished,
    354                                     birh);
    355   return birh;
    356 }
    357 
    358 
    359 void
    360 DONAU_charity_issue_receipt_cancel (
    361   struct DONAU_BatchIssueReceiptHandle *birh)
    362 {
    363   if (NULL != birh->job)
    364   {
    365     GNUNET_CURL_job_cancel (birh->job);
    366     birh->job = NULL;
    367   }
    368   TALER_curl_easy_post_finished (&birh->post_ctx);
    369   GNUNET_free (birh->url);
    370   GNUNET_free (birh);
    371 }