cash2ecash

cash2ecash: cash acceptor that issues digital cash (experimental)
Log | Files | Refs | README | LICENSE

bank_api_get_accounts.c (10677B)


      1 /*
      2   This file is part of TALER cash2ecash
      3   Copyright (C) 2026 GNUnet e.V.
      4 
      5   This program is free software: you can redistribute it and/or modify
      6   it under the terms of the GNU Affero General Public License as
      7   published by the Free Software Foundation, either version 3 of the
      8   License, or (at your option) any later version.
      9 
     10   This program is distributed in the hope that it will be useful,
     11   but WITHOUT ANY WARRANTY; without even the implied warranty of
     12   A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
     13   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     14   GNU Affero General Public License for more details.
     15 
     16   You should have received a copy of the GNU Affero General Public License
     17   along with this program.  If not, see <https://www.gnu.org/licenses/>.
     18 */
     19 /**
     20  * @file bank-lib/bank_api_get_accounts.c
     21  * @brief implements the Taler Bank API "GET accounts/$USERNAME" handler
     22  * @author Reto Tellenbach
     23  */
     24  
     25 #include <microhttpd.h>
     26 #include "taler/taler_json_lib.h"
     27 #include "taler/taler_bank_service.h"
     28 #include "bank_api_get_accounts.h"
     29 #include "bank_api_curl_defaults.h"
     30 
     31 /**
     32  * Log error related to CURL operations.
     33  *
     34  * @param type log level
     35  * @param function which function failed to run
     36  * @param code what was the curl error code
     37  */
     38 #define CURL_STRERROR(type, function, code)      \
     39         GNUNET_log (type, \
     40                     "Curl function `%s' has failed at `%s:%d' with error: %s", \
     41                     function, __FILE__, __LINE__, curl_easy_strerror (code));
     42 
     43 /**
     44  * Handle for the accounts request.
     45  */
     46 struct TALER_BANK_GetAccountsHandle
     47 {
     48       /**
     49    * The context of this handle
     50    */
     51   struct GNUNET_CURL_Context *ctx;
     52 
     53   /**
     54    * Function to call with the ,
     55    * NULL if this has already been done.
     56    */
     57   TALER_BANK_AccountsCallback accounts_cb;
     58 
     59   /**
     60    * Closure to pass to
     61    */
     62   void *accounts_cb_cls;
     63 
     64   /**
     65    * Data for the request to get the accounts/$USERNAME of a bank,
     66    * NULL once we are past stage #MHS_INIT.
     67    */
     68   struct GNUNET_CURL_Job *job;
     69 
     70   /**
     71    * The whole request line
     72    */
     73   char *job_url;
     74 };
     75 
     76 
     77 /**
     78  * Decode the JSON in @a resp_obj from the accounts/$USERNAME response
     79  *
     80  * @param[in] resp_obj JSON object to parse
     81  * @param[in,out] vi where to store the results we decoded
     82  * @param[out] vc where to store account info data
     83  * @return #TALER_EC_NONE on success
     84  */
     85 static enum TALER_ErrorCode
     86 decode_config_json (const json_t *resp_obj,
     87                     struct TALER_BANK_AccountInformation *vi)
     88 {
     89   const json_t *channels_pack;
     90   json_t *cnt;
     91   json_t *blc;
     92   bool contact_missing;
     93   bool tan_channels_missing;
     94 
     95   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
     96               "Received body\n`%s'\n",
     97               json_dumps(resp_obj, 0));
     98   
     99 
    100   struct GNUNET_JSON_Specification spec[] = {
    101     GNUNET_JSON_spec_json ("balance",
    102                             &blc),
    103     TALER_JSON_spec_amount_any ("debit_threshold",
    104                                 &vi->debit_threshold),
    105     GNUNET_JSON_spec_mark_optional (
    106     GNUNET_JSON_spec_array_const ("tan_channels",
    107                                   &channels_pack),
    108                                   &tan_channels_missing),
    109     GNUNET_JSON_spec_mark_optional (
    110     GNUNET_JSON_spec_json ("contact_data",
    111                               &cnt),
    112                               &contact_missing),
    113     GNUNET_JSON_spec_mark_optional (
    114     GNUNET_JSON_spec_string ("status",
    115                               &vi->status),
    116                               NULL),
    117     GNUNET_JSON_spec_mark_optional (
    118     GNUNET_JSON_spec_uint32 ("conversion_rate_class_id",
    119                               &vi->conversion_rate_class_id),
    120                               NULL),
    121     GNUNET_JSON_spec_end ()
    122   };
    123 
    124   if (JSON_OBJECT != json_typeof (resp_obj))
    125   {
    126     GNUNET_break_op (0);
    127     return TALER_EC_GENERIC_JSON_INVALID;
    128   }
    129   if (GNUNET_OK !=
    130       GNUNET_JSON_parse (resp_obj,
    131                          spec,
    132                          NULL, NULL))
    133   {
    134     GNUNET_break_op (0);
    135     return TALER_EC_GENERIC_JSON_INVALID;
    136   }
    137   struct GNUNET_JSON_Specification spec_blc[] = {
    138     TALER_JSON_spec_amount_any("amount",
    139                                &vi->balance.amount),
    140     GNUNET_JSON_spec_string("credit_debit_indicator",
    141                             &vi->balance.credit_debit_indicator),
    142     GNUNET_JSON_spec_end ()
    143   };
    144   if (GNUNET_OK != GNUNET_JSON_parse (blc,
    145                                     spec_blc,
    146                                     NULL, NULL))
    147   {
    148     GNUNET_break_op (0);
    149     return TALER_EC_GENERIC_JSON_INVALID;
    150   }
    151   if(!contact_missing)
    152   {
    153     struct GNUNET_JSON_Specification spec_contact[] = {
    154       GNUNET_JSON_spec_mark_optional (
    155       GNUNET_JSON_spec_string("email",
    156                               &vi->contact_data.email),
    157                                 NULL),
    158       GNUNET_JSON_spec_mark_optional (
    159       GNUNET_JSON_spec_string ("phone",
    160                               &vi->contact_data.phone_number),
    161                                 NULL),
    162       GNUNET_JSON_spec_end ()
    163     };
    164     if (GNUNET_OK != GNUNET_JSON_parse (cnt,
    165                                       spec_contact,
    166                                       NULL, NULL))
    167     {
    168       GNUNET_break_op (0);
    169       return TALER_EC_GENERIC_JSON_INVALID;
    170     }
    171   }
    172   if (!tan_channels_missing)
    173   {
    174     size_t idx;
    175     json_t *val;
    176     json_array_foreach (channels_pack,
    177                         idx,
    178                         val)
    179     {
    180       if (idx < TMH_TCS_OPTIONS_COUNT)
    181         {
    182           vi->tan_channels[idx] = GNUNET_strdup (json_string_value (val));
    183         }
    184     }
    185   }
    186 
    187   return TALER_EC_NONE;
    188 }
    189 
    190 
    191 /**
    192  * Callback used when http reply arived to a /accounts/$USERNAME request.
    193  *
    194  * @param cls the `struct TALER_BANK_GetAccountsHandle`
    195  * @param response_code HTTP response code or 0 on error
    196  * @param  gresp_obj JSON result, NULL on error, must be a `const json_t *`
    197  */
    198 static void
    199 response_cb(void *cls,
    200         long response_code,
    201         const void *gresp_obj)
    202 {
    203     struct TALER_BANK_GetAccountsHandle *account = cls;
    204     const json_t *resp_obj = gresp_obj;
    205     
    206     struct TALER_BANK_AccountsResponse ar = {
    207         .hr.response = resp_obj,
    208         .hr.http_status = (unsigned int)response_code
    209     };
    210     
    211     account->job = NULL; //job was successfull, curl job cancel not needed anymore in cleanup
    212     GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
    213               "Received config from URL `%s' with status %ld.\n",
    214               account->job_url,
    215               response_code);
    216     
    217     switch (response_code)
    218   {
    219   case 0:
    220     GNUNET_break_op (0);
    221     ar.hr.ec = TALER_EC_INVALID;
    222     break;
    223   case MHD_HTTP_OK:
    224     if (NULL == resp_obj)
    225     {
    226       GNUNET_break_op (0);
    227       ar.hr.http_status = 0;
    228       ar.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
    229       break;
    230     }
    231     ar.hr.ec = decode_config_json (resp_obj,
    232                                    &ar.details.ok.acc);
    233     if (TALER_EC_NONE != ar.hr.ec)
    234     {
    235       GNUNET_break_op (0);
    236       ar.hr.http_status = 0;
    237       break;
    238     }
    239     break;
    240   case MHD_HTTP_UNAUTHORIZED:
    241     ar.hr.ec = TALER_JSON_get_error_code (resp_obj);
    242     ar.hr.hint = TALER_JSON_get_error_hint (resp_obj);
    243     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    244                 "Invalid or missing credentials %u/%d\n",
    245                 (unsigned int) response_code,
    246                 (int) ar.hr.ec);
    247     break;
    248   case MHD_HTTP_FORBIDDEN:
    249     ar.hr.ec = TALER_JSON_get_error_code (resp_obj);
    250     ar.hr.hint = TALER_JSON_get_error_hint (resp_obj);
    251     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    252                 "Missing rights %u/%d\n",
    253                 (unsigned int) response_code,
    254                 (int) ar.hr.ec);
    255     break;
    256   case MHD_HTTP_NOT_FOUND:
    257     ar.hr.ec = TALER_JSON_get_error_code (resp_obj);
    258     ar.hr.hint = TALER_JSON_get_error_hint (resp_obj);
    259     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    260                 "The account pointed by $USERNAME was not found %u/%d\n",
    261                 (unsigned int) response_code,
    262                 (int) ar.hr.ec);
    263     break;
    264   case MHD_HTTP_INTERNAL_SERVER_ERROR:
    265     ar.hr.ec = TALER_JSON_get_error_code (resp_obj);
    266     ar.hr.hint = TALER_JSON_get_error_hint (resp_obj);
    267     break;
    268   default:
    269     ar.hr.ec = TALER_JSON_get_error_code (resp_obj);
    270     ar.hr.hint = TALER_JSON_get_error_hint (resp_obj);
    271     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    272                 "Unexpected response code %u/%d\n",
    273                 (unsigned int) response_code,
    274                 (int) ar.hr.ec);
    275     break;
    276   }
    277 
    278     account->accounts_cb (account->accounts_cb_cls, &ar);
    279     TALER_BANK_get_accounts_cancel(account);
    280 }
    281 
    282 
    283 struct TALER_BANK_GetAccountsHandle *
    284 TALER_BANK_get_accounts ( struct GNUNET_CURL_Context *ctx,
    285                         const char *url,
    286                         const char *username,
    287                         const struct DIGITIZER_BankAuthenticationData *authorization,
    288                         TALER_BANK_AccountsCallback accounts_cb,
    289                         void *accounts_cb_cls)
    290 {
    291     struct TALER_BANK_GetAccountsHandle *account;
    292     char *usr;
    293     CURL *eh;
    294 
    295     account = GNUNET_new(struct TALER_BANK_GetAccountsHandle);
    296     account->accounts_cb = accounts_cb;
    297     account->accounts_cb_cls = accounts_cb_cls;
    298     account->ctx = ctx;
    299     GNUNET_asprintf(&usr,
    300                     "accounts/%s",
    301                     username);
    302     account->job_url = TALER_url_join(url,
    303                                       usr,
    304                                       NULL);
    305     if(NULL == account->job_url)
    306     {
    307         GNUNET_break(0);
    308         GNUNET_free(account);
    309         return NULL;
    310     }
    311     GNUNET_log( GNUNET_ERROR_TYPE_INFO,
    312                 "Requesting bank account information with URL `%s'.\n",
    313                 account->job_url);
    314     eh = TALER_BANK_curl_easy_get_(account->job_url);
    315     if(NULL == eh)
    316     {
    317         GNUNET_break(0);
    318         TALER_BANK_get_accounts_cancel(account);
    319         return NULL;
    320     }
    321     if(GNUNET_OK != DIGITIZER_setup_auth_(eh,authorization))
    322     {
    323         GNUNET_break(0);
    324         TALER_BANK_get_accounts_cancel(account);
    325         return NULL;
    326     }
    327     account->job = GNUNET_CURL_job_add(account->ctx,
    328                                         eh,
    329                                         &response_cb,
    330                                         account);
    331     if(NULL == account->job)
    332     {
    333         GNUNET_break(0);
    334         TALER_BANK_get_accounts_cancel(account);
    335         return NULL;
    336     }
    337             
    338     return account;
    339 }
    340 
    341 
    342 
    343 void
    344 TALER_BANK_get_accounts_cancel ( struct TALER_BANK_GetAccountsHandle *account)
    345 {
    346     if(NULL != account->job)
    347     {
    348         GNUNET_CURL_job_cancel(account->job);
    349         account->job = NULL;
    350     }
    351     GNUNET_free(account->job_url);
    352     GNUNET_free(account);
    353 }