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


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