anastasis

Credential backup and recovery protocol and service
Log | Files | Refs | Submodules | README | LICENSE

lae_credit.c (9936B)


      1 /*
      2   This file is part of Anastasis
      3   Copyright (C) 2017--2021 Anastasis SARL
      4 
      5   Anastasis is free software; you can redistribute it and/or
      6   modify it under the terms of the GNU General Public License
      7   as published by the Free Software Foundation; either version 3,
      8   or (at your option) any later version.
      9 
     10   Anastasis is distributed in the hope that it will be useful,
     11   but 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 Anastasis; see the file COPYING.  If not,
     17   see <http://www.gnu.org/licenses/>
     18 */
     19 /**
     20  * @file libanastasiseufin/lae_credit.c
     21  * @brief Implementation of the /history requests of the
     22  *        Taler revenue API (as served by libeufin-bank)
     23  * @author Christian Grothoff
     24  * @author Marcello Stanisci
     25  */
     26 #include "platform.h"
     27 #include "lae_common.h"
     28 #include <microhttpd.h> /* just for HTTP status codes */
     29 
     30 
     31 /**
     32  * @brief A /history Handle
     33  */
     34 struct ANASTASIS_EUFIN_CreditHistoryHandle
     35 {
     36 
     37   /**
     38    * The url for this request.
     39    */
     40   char *request_url;
     41 
     42   /**
     43    * Handle for the request.
     44    */
     45   struct GNUNET_CURL_Job *job;
     46 
     47   /**
     48    * Function to call with the result.
     49    */
     50   ANASTASIS_EUFIN_CreditHistoryCallback hcb;
     51 
     52   /**
     53    * Closure for @a cb.
     54    */
     55   void *hcb_cls;
     56 };
     57 
     58 
     59 /**
     60  * Parse history given in JSON format and invoke the callback on each item.
     61  *
     62  * @param hh handle to the account history request
     63  * @param history JSON array with the history
     64  * @return #GNUNET_OK if history was valid and @a rhistory and @a balance
     65  *         were set,
     66  *         #GNUNET_SYSERR if there was a protocol violation in @a history
     67  */
     68 static enum GNUNET_GenericReturnValue
     69 parse_account_history (struct ANASTASIS_EUFIN_CreditHistoryHandle *hh,
     70                        const json_t *history)
     71 {
     72   json_t *history_array;
     73   const char *credit_account_uri;
     74 
     75   if (NULL == (history_array = json_object_get (history,
     76                                                 "incoming_transactions")))
     77   {
     78     GNUNET_break_op (0);
     79     return GNUNET_SYSERR;
     80   }
     81   if (! json_is_array (history_array))
     82   {
     83     GNUNET_break_op (0);
     84     return GNUNET_SYSERR;
     85   }
     86   /* The credited account is the account we asked about, so the revenue API
     87      states it once for the whole response instead of per transaction. */
     88   credit_account_uri = json_string_value (json_object_get (history,
     89                                                            "credit_account"));
     90   if (NULL == credit_account_uri)
     91   {
     92     GNUNET_break_op (0);
     93     return GNUNET_SYSERR;
     94   }
     95   for (size_t i = 0; i<json_array_size (history_array); i++)
     96   {
     97     struct ANASTASIS_EUFIN_CreditDetails td;
     98     uint64_t row_id;
     99     struct GNUNET_JSON_Specification hist_spec[] = {
    100       TALER_JSON_spec_amount_any ("amount",
    101                                   &td.amount),
    102       GNUNET_JSON_spec_timestamp ("date",
    103                                   &td.execution_date),
    104       GNUNET_JSON_spec_uint64 ("row_id",
    105                                &row_id),
    106       GNUNET_JSON_spec_string ("subject",
    107                                &td.wire_subject),
    108       GNUNET_JSON_spec_string ("debit_account",
    109                                &td.debit_account_uri),
    110       GNUNET_JSON_spec_end ()
    111     };
    112     json_t *transaction = json_array_get (history_array,
    113                                           i);
    114 
    115     td.credit_account_uri = credit_account_uri;
    116     if (GNUNET_OK !=
    117         GNUNET_JSON_parse (transaction,
    118                            hist_spec,
    119                            NULL, NULL))
    120     {
    121       GNUNET_break_op (0);
    122       return GNUNET_SYSERR;
    123     }
    124     if (GNUNET_OK !=
    125         hh->hcb (hh->hcb_cls,
    126                  MHD_HTTP_OK,
    127                  TALER_EC_NONE,
    128                  row_id,
    129                  &td))
    130     {
    131       hh->hcb = NULL;
    132       GNUNET_JSON_parse_free (hist_spec);
    133       return GNUNET_OK;
    134     }
    135     GNUNET_JSON_parse_free (hist_spec);
    136   }
    137   return GNUNET_OK;
    138 }
    139 
    140 
    141 /**
    142  * Function called when we're done processing the
    143  * HTTP /history request.
    144  *
    145  * @param cls the `struct ANASTASIS_EUFIN_CreditHistoryHandle`
    146  * @param response_code HTTP response code, 0 on error
    147  * @param response parsed JSON result, NULL on error
    148  */
    149 static void
    150 handle_credit_history_finished (void *cls,
    151                                 long response_code,
    152                                 const void *response)
    153 {
    154   struct ANASTASIS_EUFIN_CreditHistoryHandle *hh = cls;
    155   const json_t *j = response;
    156   enum TALER_ErrorCode ec;
    157 
    158   hh->job = NULL;
    159   switch (response_code)
    160   {
    161   case 0:
    162     ec = TALER_EC_GENERIC_INVALID_RESPONSE;
    163     break;
    164   case MHD_HTTP_OK:
    165     if (GNUNET_OK !=
    166         parse_account_history (hh,
    167                                j))
    168     {
    169       GNUNET_break_op (0);
    170       response_code = 0;
    171       ec = TALER_EC_GENERIC_INVALID_RESPONSE;
    172       break;
    173     }
    174     response_code = MHD_HTTP_NO_CONTENT; /* signal end of list */
    175     ec = TALER_EC_NONE;
    176     break;
    177   case MHD_HTTP_NO_CONTENT:
    178     ec = TALER_EC_NONE;
    179     break;
    180   case MHD_HTTP_BAD_REQUEST:
    181     /* This should never happen, either us or the bank is buggy
    182        (or API version conflict); just pass JSON reply to the application */
    183     GNUNET_break_op (0);
    184     ec = TALER_JSON_get_error_code (j);
    185     break;
    186   case MHD_HTTP_UNAUTHORIZED:
    187     /* Nothing really to verify, bank says the HTTP Authentication
    188        failed. May happen if HTTP authentication is used and the
    189        user supplied a wrong username/password combination. */
    190     ec = TALER_JSON_get_error_code (j);
    191     break;
    192   case MHD_HTTP_NOT_FOUND:
    193     /* Nothing really to verify: the bank is either unaware
    194        of the endpoint (not a bank), or of the account.
    195        We should pass the JSON (?) reply to the application */
    196     ec = TALER_JSON_get_error_code (j);
    197     break;
    198   case MHD_HTTP_INTERNAL_SERVER_ERROR:
    199     /* Server had an internal issue; we should retry, but this API
    200        leaves this to the application */
    201     ec = TALER_JSON_get_error_code (j);
    202     break;
    203   default:
    204     /* unexpected response code */
    205     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    206                 "Unexpected response code %u\n",
    207                 (unsigned int) response_code);
    208     ec = TALER_JSON_get_error_code (j);
    209     break;
    210   }
    211   if (NULL != hh->hcb)
    212     hh->hcb (hh->hcb_cls,
    213              response_code,
    214              ec,
    215              0LLU,
    216              NULL);
    217   ANASTASIS_EUFIN_credit_history_cancel (hh);
    218 }
    219 
    220 
    221 struct ANASTASIS_EUFIN_CreditHistoryHandle *
    222 ANASTASIS_EUFIN_credit_history (
    223   struct GNUNET_CURL_Context *ctx,
    224   const struct ANASTASIS_EUFIN_AuthenticationData *auth,
    225   uint64_t start_row,
    226   int64_t num_results,
    227   struct GNUNET_TIME_Relative timeout,
    228   ANASTASIS_EUFIN_CreditHistoryCallback hres_cb,
    229   void *hres_cb_cls)
    230 {
    231   char url[128];
    232   struct ANASTASIS_EUFIN_CreditHistoryHandle *hh;
    233   CURL *eh;
    234   unsigned long long tms;
    235 
    236   if (0 == num_results)
    237   {
    238     GNUNET_break (0);
    239     return NULL;
    240   }
    241 
    242   tms = (unsigned long long) (timeout.rel_value_us
    243                               / GNUNET_TIME_UNIT_MILLISECONDS.rel_value_us);
    244   if ( ( (UINT64_MAX == start_row) &&
    245          (0 > num_results) ) ||
    246        ( (0 == start_row) &&
    247          (0 < num_results) ) )
    248   {
    249     if ( (0 < num_results) &&
    250          (! GNUNET_TIME_relative_is_zero (timeout)) )
    251       GNUNET_snprintf (url,
    252                        sizeof (url),
    253                        "history?limit=%lld&timeout_ms=%llu",
    254                        (long long) num_results,
    255                        tms);
    256     else
    257       GNUNET_snprintf (url,
    258                        sizeof (url),
    259                        "history?limit=%lld",
    260                        (long long) num_results);
    261   }
    262   else
    263   {
    264     if ( (0 < num_results) &&
    265          (! GNUNET_TIME_relative_is_zero (timeout)) )
    266       GNUNET_snprintf (url,
    267                        sizeof (url),
    268                        "history?limit=%lld&offset=%llu&timeout_ms=%llu",
    269                        (long long) num_results,
    270                        (unsigned long long) start_row,
    271                        tms);
    272     else
    273       GNUNET_snprintf (url,
    274                        sizeof (url),
    275                        "history?limit=%lld&offset=%llu",
    276                        (long long) num_results,
    277                        (unsigned long long) start_row);
    278   }
    279   hh = GNUNET_new (struct ANASTASIS_EUFIN_CreditHistoryHandle);
    280   hh->hcb = hres_cb;
    281   hh->hcb_cls = hres_cb_cls;
    282   hh->request_url = TALER_url_join (auth->wire_gateway_url,
    283                                     url,
    284                                     NULL);
    285   if (NULL == hh->request_url)
    286   {
    287     GNUNET_free (hh);
    288     GNUNET_break (0);
    289     return NULL;
    290   }
    291   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    292               "Requesting credit history at `%s'\n",
    293               hh->request_url);
    294   eh = curl_easy_init ();
    295   if ( (NULL == eh) ||
    296        (GNUNET_OK !=
    297         ANASTASIS_EUFIN_setup_auth_ (eh,
    298                                      auth)) ||
    299        (CURLE_OK !=
    300         curl_easy_setopt (eh,
    301                           CURLOPT_URL,
    302                           hh->request_url)) )
    303   {
    304     GNUNET_break (0);
    305     ANASTASIS_EUFIN_credit_history_cancel (hh);
    306     if (NULL != eh)
    307       curl_easy_cleanup (eh);
    308     return NULL;
    309   }
    310   if (0 != tms)
    311   {
    312     GNUNET_break (CURLE_OK ==
    313                   curl_easy_setopt (eh,
    314                                     CURLOPT_TIMEOUT_MS,
    315                                     (long) tms));
    316   }
    317   hh->job = GNUNET_CURL_job_add2 (ctx,
    318                                   eh,
    319                                   NULL,
    320                                   &handle_credit_history_finished,
    321                                   hh);
    322   return hh;
    323 }
    324 
    325 
    326 void
    327 ANASTASIS_EUFIN_credit_history_cancel (
    328   struct ANASTASIS_EUFIN_CreditHistoryHandle *hh)
    329 {
    330   if (NULL != hh->job)
    331   {
    332     GNUNET_CURL_job_cancel (hh->job);
    333     hh->job = NULL;
    334   }
    335   GNUNET_free (hh->request_url);
    336   GNUNET_free (hh);
    337 }
    338 
    339 
    340 /* end of lae_credit.c */