exchange

Base system with REST service to issue digital coins, run by the payment service provider
Log | Files | Refs | Submodules | README | LICENSE

bank_api_registration.c (11070B)


      1 /*
      2   This file is part of TALER
      3   Copyright (C) 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 bank-lib/bank_api_registration.c
     19  * @brief Implementation of the POST /registration request of the bank's HTTP API
     20  * @author Christian Grothoff
     21  */
     22 #include "taler/platform.h"
     23 #include "bank_api_common.h"
     24 #include <microhttpd.h> /* just for HTTP status codes */
     25 #include "taler/taler_signatures.h"
     26 #include "taler/taler_curl_lib.h"
     27 
     28 
     29 /**
     30  * @brief A /registration Handle
     31  */
     32 struct TALER_BANK_RegistrationHandle
     33 {
     34 
     35   /**
     36    * The URL for this request.
     37    */
     38   char *request_url;
     39 
     40   /**
     41    * POST context.
     42    */
     43   struct TALER_CURL_PostContext post_ctx;
     44 
     45   /**
     46    * Handle for the request.
     47    */
     48   struct GNUNET_CURL_Job *job;
     49 
     50   /**
     51    * Function to call with the result.
     52    */
     53   TALER_BANK_RegistrationCallback cb;
     54 
     55   /**
     56    * Closure for @a cb.
     57    */
     58   void *cb_cls;
     59 
     60 };
     61 
     62 
     63 /**
     64  * Parse the "subject" field of a successful /registration response.
     65  * The field is a JSON object discriminated by "type".
     66  *
     67  * @param subject_json the JSON object to parse (the inner "subject" value)
     68  * @param[out] ts set to the parsed transfer subject on success
     69  * @return #GNUNET_OK on success, #GNUNET_SYSERR on error
     70  */
     71 static enum GNUNET_GenericReturnValue
     72 parse_transfer_subject (const json_t *subject_json,
     73                         struct TALER_BANK_TransferSubject *ts)
     74 {
     75   const char *type_str;
     76   struct GNUNET_JSON_Specification type_spec[] = {
     77     GNUNET_JSON_spec_string ("type",
     78                              &type_str),
     79     GNUNET_JSON_spec_end ()
     80   };
     81 
     82   if (GNUNET_OK !=
     83       GNUNET_JSON_parse (subject_json,
     84                          type_spec,
     85                          NULL,
     86                          NULL))
     87   {
     88     GNUNET_break_op (0);
     89     return GNUNET_SYSERR;
     90   }
     91 
     92   if (0 == strcasecmp (type_str,
     93                        "SIMPLE"))
     94   {
     95     struct GNUNET_JSON_Specification spec[] = {
     96       TALER_JSON_spec_amount_any ("credit_amount",
     97                                   &ts->details.simple.credit_amount),
     98       GNUNET_JSON_spec_string ("subject",
     99                                &ts->details.simple.subject),
    100       GNUNET_JSON_spec_end ()
    101     };
    102 
    103     if (GNUNET_OK !=
    104         GNUNET_JSON_parse (subject_json,
    105                            spec,
    106                            NULL, NULL))
    107     {
    108       GNUNET_break_op (0);
    109       return GNUNET_SYSERR;
    110     }
    111     ts->format = TALER_BANK_SUBJECT_FORMAT_SIMPLE;
    112     return GNUNET_OK;
    113   }
    114   if (0 == strcasecmp (type_str,
    115                        "URI"))
    116   {
    117     struct GNUNET_JSON_Specification spec[] = {
    118       GNUNET_JSON_spec_string ("uri",
    119                                &ts->details.uri.uri),
    120       GNUNET_JSON_spec_end ()
    121     };
    122 
    123     if (GNUNET_OK !=
    124         GNUNET_JSON_parse (subject_json,
    125                            spec,
    126                            NULL, NULL))
    127     {
    128       GNUNET_break_op (0);
    129       return GNUNET_SYSERR;
    130     }
    131     ts->format = TALER_BANK_SUBJECT_FORMAT_URI;
    132     return GNUNET_OK;
    133   }
    134   if (0 == strcasecmp (type_str,
    135                        "CH_QR_BILL"))
    136   {
    137     struct GNUNET_JSON_Specification spec[] = {
    138       TALER_JSON_spec_amount_any ("credit_amount",
    139                                   &ts->details.ch_qr_bill.credit_amount),
    140       GNUNET_JSON_spec_string ("qr_reference_number",
    141                                &ts->details.ch_qr_bill.qr_reference_number),
    142       GNUNET_JSON_spec_end ()
    143     };
    144 
    145     if (GNUNET_OK !=
    146         GNUNET_JSON_parse (subject_json,
    147                            spec,
    148                            NULL, NULL))
    149     {
    150       GNUNET_break_op (0);
    151       return GNUNET_SYSERR;
    152     }
    153     ts->format = TALER_BANK_SUBJECT_FORMAT_CH_QR_BILL;
    154     return GNUNET_OK;
    155   }
    156   GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    157               "Unknown transfer subject type `%s'\n",
    158               type_str);
    159   GNUNET_break_op (0);
    160   return GNUNET_SYSERR;
    161 }
    162 
    163 
    164 /**
    165  * Function called when we're done processing the HTTP POST /registration
    166  * request.
    167  *
    168  * @param cls the `struct TALER_BANK_RegistrationHandle`
    169  * @param response_code HTTP response code, 0 on error
    170  * @param response parsed JSON result, NULL on error
    171  */
    172 static void
    173 handle_registration_finished (void *cls,
    174                               long response_code,
    175                               const void *response)
    176 {
    177   struct TALER_BANK_RegistrationHandle *rh = cls;
    178   const json_t *j = response;
    179   struct TALER_BANK_RegistrationResponse rr = {
    180     .http_status = response_code,
    181     .response = response
    182   };
    183 
    184   rh->job = NULL;
    185   switch (response_code)
    186   {
    187   case 0:
    188     rr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
    189     break;
    190   case MHD_HTTP_OK:
    191     {
    192       const json_t *subjects;
    193       struct GNUNET_JSON_Specification spec[] = {
    194         GNUNET_JSON_spec_array_const ("subjects",
    195                                       &subjects),
    196         GNUNET_JSON_spec_timestamp ("expiration",
    197                                     &rr.details.ok.expiration),
    198         GNUNET_JSON_spec_end ()
    199       };
    200 
    201       if (GNUNET_OK !=
    202           GNUNET_JSON_parse (j,
    203                              spec,
    204                              NULL, NULL))
    205       {
    206         GNUNET_break_op (0);
    207         rr.http_status = 0;
    208         rr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
    209         break;
    210       }
    211 
    212       {
    213         size_t n = json_array_size (subjects);
    214         struct TALER_BANK_TransferSubject ts[GNUNET_NZL (n)];
    215         size_t i;
    216         const json_t *subject;
    217 
    218         json_array_foreach ((json_t *) subjects, i, subject)
    219         {
    220           if (GNUNET_OK !=
    221               parse_transfer_subject (subject,
    222                                       &ts[i]))
    223           {
    224             GNUNET_break_op (0);
    225             rr.http_status = 0;
    226             rr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
    227             break;
    228           }
    229         }
    230         if (MHD_HTTP_OK == rr.http_status)
    231         {
    232           rr.details.ok.num_subjects = n;
    233           rr.details.ok.subjects = ts;
    234           rh->cb (rh->cb_cls,
    235                   &rr);
    236           TALER_BANK_registration_cancel (rh);
    237           return;
    238         }
    239       }
    240     }
    241     break;
    242   case MHD_HTTP_BAD_REQUEST:
    243     /* Either we or the service is buggy, or there is an API version conflict. */
    244     GNUNET_break_op (0);
    245     rr.ec = TALER_JSON_get_error_code (j);
    246     break;
    247   case MHD_HTTP_CONFLICT:
    248     /* Covers TALER_EC_BANK_DUPLICATE_RESERVE_PUB_SUBJECT,
    249        TALER_EC_BANK_UNSUPPORTED_SUBJECT_FORMAT,
    250        TALER_EC_BANK_DERIVATION_REUSE, and
    251        TALER_EC_BANK_BAD_SIGNATURE. */
    252     rr.ec = TALER_JSON_get_error_code (j);
    253     break;
    254   case MHD_HTTP_INTERNAL_SERVER_ERROR:
    255     /* Server had an internal issue; we should retry, but this API
    256        leaves that to the application. */
    257     rr.ec = TALER_JSON_get_error_code (j);
    258     break;
    259   default:
    260     /* unexpected response code */
    261     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    262                 "Unexpected response code %u\n",
    263                 (unsigned int) response_code);
    264     GNUNET_break (0);
    265     rr.ec = TALER_JSON_get_error_code (j);
    266     break;
    267   }
    268   rh->cb (rh->cb_cls,
    269           &rr);
    270   TALER_BANK_registration_cancel (rh);
    271 }
    272 
    273 
    274 struct TALER_BANK_RegistrationHandle *
    275 TALER_BANK_registration (
    276   struct GNUNET_CURL_Context *ctx,
    277   const char *base_url,
    278   const struct TALER_Amount *credit_amount,
    279   enum TALER_BANK_RegistrationType type,
    280   const union TALER_AccountPublicKeyP *account_pub,
    281   const struct TALER_ReserveMapAuthorizationPrivateKeyP *authorization_priv,
    282   bool recurrent,
    283   TALER_BANK_RegistrationCallback res_cb,
    284   void *res_cb_cls)
    285 {
    286   struct TALER_ReserveMapAuthorizationPublicKeyP authorization_pub;
    287   struct TALER_ReserveMapAuthorizationSignatureP authorization_sig;
    288   struct TALER_BANK_RegistrationHandle *rh;
    289   const char *type_str;
    290   json_t *reg_obj;
    291   CURL *eh;
    292 
    293   TALER_wallet_reserve_map_authorization_sign (account_pub,
    294                                                authorization_priv,
    295                                                &authorization_sig);
    296   GNUNET_CRYPTO_eddsa_key_get_public (&authorization_priv->eddsa_priv,
    297                                       &authorization_pub.eddsa_pub);
    298 
    299   switch (type)
    300   {
    301   case TALER_BANK_REGISTRATION_TYPE_RESERVE:
    302     type_str = "reserve";
    303     break;
    304   case TALER_BANK_REGISTRATION_TYPE_KYC:
    305     type_str = "kyc";
    306     break;
    307   default:
    308     GNUNET_break (0);
    309     return NULL;
    310   }
    311 
    312   reg_obj = GNUNET_JSON_PACK (
    313     TALER_JSON_pack_amount ("credit_amount",
    314                             credit_amount),
    315     GNUNET_JSON_pack_string ("type",
    316                              type_str),
    317     GNUNET_JSON_pack_string ("alg",
    318                              "EdDSA"),
    319     GNUNET_JSON_pack_data_auto ("account_pub",
    320                                 account_pub),
    321     GNUNET_JSON_pack_data_auto ("authorization_pub",
    322                                 &authorization_pub),
    323     GNUNET_JSON_pack_data_auto ("authorization_sig",
    324                                 &authorization_sig),
    325     GNUNET_JSON_pack_bool ("recurrent",
    326                            recurrent));
    327   rh = GNUNET_new (struct TALER_BANK_RegistrationHandle);
    328   rh->cb = res_cb;
    329   rh->cb_cls = res_cb_cls;
    330   rh->request_url = TALER_url_join (base_url,
    331                                     "registration",
    332                                     NULL);
    333   if (NULL == rh->request_url)
    334   {
    335     GNUNET_free (rh);
    336     json_decref (reg_obj);
    337     return NULL;
    338   }
    339   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    340               "Requesting wire transfer subject registration at `%s'\n",
    341               rh->request_url);
    342   eh = curl_easy_init ();
    343   if ( (NULL == eh) ||
    344        (CURLE_OK !=
    345         curl_easy_setopt (eh,
    346                           CURLOPT_URL,
    347                           rh->request_url)) ||
    348        (GNUNET_OK !=
    349         TALER_curl_easy_post (&rh->post_ctx,
    350                               eh,
    351                               reg_obj)) )
    352   {
    353     GNUNET_break (0);
    354     TALER_BANK_registration_cancel (rh);
    355     if (NULL != eh)
    356       curl_easy_cleanup (eh);
    357     json_decref (reg_obj);
    358     return NULL;
    359   }
    360   json_decref (reg_obj);
    361   rh->job = GNUNET_CURL_job_add2 (ctx,
    362                                   eh,
    363                                   rh->post_ctx.headers,
    364                                   &handle_registration_finished,
    365                                   rh);
    366   return rh;
    367 }
    368 
    369 
    370 void
    371 TALER_BANK_registration_cancel (
    372   struct TALER_BANK_RegistrationHandle *rh)
    373 {
    374   if (NULL != rh->job)
    375   {
    376     GNUNET_CURL_job_cancel (rh->job);
    377     rh->job = NULL;
    378   }
    379   TALER_curl_easy_post_finished (&rh->post_ctx);
    380   GNUNET_free (rh->request_url);
    381   GNUNET_free (rh);
    382 }
    383 
    384 
    385 /* end of bank_api_registration.c */