merchant

Merchant backend to process payments, run by merchants
Log | Files | Refs | Submodules | README | LICENSE

taler-merchant-httpd_get-fountain-info.c (14778B)


      1 /*
      2   This file is part of TALER
      3   (C) 2026 Taler Systems SA
      4 
      5   TALER 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,
      8   or (at your 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,
     17   see <http://www.gnu.org/licenses/>
     18 */
     19 
     20 /**
     21  * @file src/backend/taler-merchant-httpd_get-fountain-info.c
     22  * @brief implementing GET /fountain/info request handling (DD 98)
     23  * @author Bohdan Potuzhnyi
     24  */
     25 #include "platform.h"
     26 #include "taler-merchant-httpd_get-fountain-info.h"
     27 #include "taler-merchant-httpd_token-keys.h"
     28 #include <taler/taler_json_lib.h>
     29 #include "merchant-database/get_fountain_by_secret.h"
     30 #include "merchant-database/get_token_family.h"
     31 #include "merchant-database/iterate_fountain_grants.h"
     32 
     33 
     34 /**
     35  * A grant of the fountain the request authenticated as.
     36  */
     37 struct GrantInfo
     38 {
     39   /**
     40    * Slug of the granted token family.
     41    */
     42   char *token_family_slug;
     43 
     44   /**
     45    * Maximum withdrawals per issue-key validity period.
     46    */
     47   uint64_t tokens_per_period_limit;
     48 
     49   /**
     50    * Suggested number of tokens to hold per period.
     51    */
     52   uint64_t tokens_per_period_stash;
     53 
     54   /**
     55    * Number of issue-key slots ahead the wallet may withdraw for.
     56    */
     57   uint32_t key_window_size;
     58 };
     59 
     60 
     61 /**
     62  * Request-specific context of a GET /fountain/info request.
     63  */
     64 struct InfoContext
     65 {
     66   /**
     67    * Grants of the fountain the request authenticated as.
     68    */
     69   struct GrantInfo *grants;
     70 
     71   /**
     72    * Length of the @e grants array.
     73    */
     74   unsigned int grants_len;
     75 
     76   /**
     77    * How often the wallet should re-poll.
     78    */
     79   struct GNUNET_TIME_Relative poll_freq;
     80 
     81   /**
     82    * Serial of the fountain the request authenticated as.
     83    */
     84   uint64_t fountain_serial;
     85 
     86   /**
     87    * Time this request is processed at.
     88    */
     89   struct GNUNET_TIME_Timestamp now;
     90 };
     91 
     92 
     93 /**
     94  * Release the request-specific context.
     95  *
     96  * @param cls a `struct InfoContext *`
     97  */
     98 static void
     99 info_context_cleanup (void *cls)
    100 {
    101   struct InfoContext *ic = cls;
    102 
    103   for (unsigned int i = 0; i < ic->grants_len; i++)
    104     GNUNET_free (ic->grants[i].token_family_slug);
    105   GNUNET_array_grow (ic->grants,
    106                      ic->grants_len,
    107                      0);
    108   GNUNET_free (ic);
    109 }
    110 
    111 
    112 /**
    113  * Add a grant of the fountain to the context in @a cls.  Database
    114  * calls must not be made from within the iteration, so the grants are
    115  * collected first and expanded into the response afterwards.
    116  *
    117  * @param cls a `struct InfoContext *`
    118  * @param token_family_slug slug of the granted token family
    119  * @param token_family_serial serial of the granted token family
    120  * @param tokens_per_period_limit maximum withdrawals per period
    121  * @param tokens_per_period_stash suggested tokens to hold per period
    122  * @param key_window_size number of slots ahead withdrawals are allowed
    123  */
    124 static void
    125 add_grant (void *cls,
    126            const char *token_family_slug,
    127            uint64_t token_family_serial,
    128            uint64_t tokens_per_period_limit,
    129            uint64_t tokens_per_period_stash,
    130            uint32_t key_window_size)
    131 {
    132   struct InfoContext *ic = cls;
    133   struct GrantInfo gi = {
    134     .token_family_slug = GNUNET_strdup (token_family_slug),
    135     .tokens_per_period_limit = tokens_per_period_limit,
    136     .tokens_per_period_stash = tokens_per_period_stash,
    137     .key_window_size = key_window_size
    138   };
    139 
    140   (void) token_family_serial;
    141   GNUNET_array_append (ic->grants,
    142                        ic->grants_len,
    143                        gi);
    144 }
    145 
    146 
    147 /**
    148  * Parse the fountain secret from the "Authorization: Bearer ..."
    149  * header of @a connection and compute its hash.
    150  *
    151  * @param connection connection to inspect
    152  * @param[out] h_secret set to the hash of the bearer credential
    153  * @return #GNUNET_OK on success, #GNUNET_NO if the header is
    154  *         missing or malformed
    155  */
    156 static enum GNUNET_GenericReturnValue
    157 parse_fountain_secret (struct MHD_Connection *connection,
    158                        struct GNUNET_HashCode *h_secret)
    159 {
    160   static const char bearer[] = "Bearer ";
    161   const char *auth;
    162   char secret[32];
    163 
    164   auth = MHD_lookup_connection_value (connection,
    165                                       MHD_HEADER_KIND,
    166                                       MHD_HTTP_HEADER_AUTHORIZATION);
    167   if ( (NULL == auth) ||
    168        (0 != strncmp (auth,
    169                       bearer,
    170                       strlen (bearer))) )
    171     return GNUNET_NO;
    172   auth += strlen (bearer);
    173   while (' ' == *auth)
    174     auth++;
    175   if (GNUNET_OK !=
    176       GNUNET_STRINGS_string_to_data (auth,
    177                                      strlen (auth),
    178                                      secret,
    179                                      sizeof (secret)))
    180     return GNUNET_NO;
    181   GNUNET_CRYPTO_hash (secret,
    182                       sizeof (secret),
    183                       h_secret);
    184   memset (secret,
    185           0,
    186           sizeof (secret));
    187   return GNUNET_OK;
    188 }
    189 
    190 
    191 /**
    192  * Authenticate the request by the bearer credential in the
    193  * "Authorization" header and load the grants of the fountain.
    194  *
    195  * @param connection connection to report errors on
    196  * @param instance_id instance the fountain belongs to
    197  * @param[in,out] ic context to initialize
    198  * @return #GNUNET_OK on success, #GNUNET_NO if an error response was
    199  *         queued, #GNUNET_SYSERR on hard failure
    200  */
    201 static enum GNUNET_GenericReturnValue
    202 authenticate (struct MHD_Connection *connection,
    203               const char *instance_id,
    204               struct InfoContext *ic)
    205 {
    206   struct GNUNET_HashCode h_secret;
    207   enum GNUNET_DB_QueryStatus qs;
    208 
    209   if (GNUNET_OK !=
    210       parse_fountain_secret (connection,
    211                              &h_secret))
    212   {
    213     /* Reply as for an unknown credential, so that the endpoint does
    214        not become an oracle for the shape of valid secrets. */
    215     GNUNET_break_op (0);
    216     return (MHD_YES ==
    217             TALER_MHD_reply_with_error (connection,
    218                                         MHD_HTTP_UNAUTHORIZED,
    219                                         TALER_EC_MERCHANT_GENERIC_UNAUTHORIZED,
    220                                         "fountain secret"))
    221            ? GNUNET_NO
    222            : GNUNET_SYSERR;
    223   }
    224   qs = TALER_MERCHANTDB_get_fountain_by_secret (TMH_db,
    225                                                 instance_id,
    226                                                 &h_secret,
    227                                                 &ic->fountain_serial,
    228                                                 &ic->poll_freq);
    229   if (0 > qs)
    230   {
    231     GNUNET_break (0);
    232     return (MHD_YES ==
    233             TALER_MHD_reply_with_error (connection,
    234                                         MHD_HTTP_INTERNAL_SERVER_ERROR,
    235                                         TALER_EC_GENERIC_DB_FETCH_FAILED,
    236                                         "get_fountain_by_secret"))
    237            ? GNUNET_NO
    238            : GNUNET_SYSERR;
    239   }
    240   if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
    241     return (MHD_YES ==
    242             TALER_MHD_reply_with_error (connection,
    243                                         MHD_HTTP_UNAUTHORIZED,
    244                                         TALER_EC_MERCHANT_GENERIC_UNAUTHORIZED,
    245                                         "fountain secret"))
    246            ? GNUNET_NO
    247            : GNUNET_SYSERR;
    248   qs = TALER_MERCHANTDB_iterate_fountain_grants (TMH_db,
    249                                                  instance_id,
    250                                                  ic->fountain_serial,
    251                                                  &add_grant,
    252                                                  ic);
    253   if (0 > qs)
    254   {
    255     GNUNET_break (0);
    256     return (MHD_YES ==
    257             TALER_MHD_reply_with_error (connection,
    258                                         MHD_HTTP_INTERNAL_SERVER_ERROR,
    259                                         TALER_EC_GENERIC_DB_FETCH_FAILED,
    260                                         "iterate_fountain_grants"))
    261            ? GNUNET_NO
    262            : GNUNET_SYSERR;
    263   }
    264   return GNUNET_OK;
    265 }
    266 
    267 
    268 /**
    269  * Collect the issue keys of the current and the next
    270  * @a key_window_size validity periods into @a family, minting them if
    271  * they do not exist yet.  This is the same lazy minting that order
    272  * creation performs, so this GET deliberately writes to the database.
    273  *
    274  * @param connection connection to report errors on
    275  * @param instance_id instance the token family belongs to
    276  * @param ic context of the request
    277  * @param gi grant to expand
    278  * @param tf details of the token family
    279  * @param[in,out] family contract token family to add the keys to
    280  * @return #GNUNET_OK on success, #GNUNET_NO if an error response was
    281  *         queued, #GNUNET_SYSERR on hard failure
    282  */
    283 static enum GNUNET_GenericReturnValue
    284 collect_keys (struct MHD_Connection *connection,
    285               const char *instance_id,
    286               const struct InfoContext *ic,
    287               const struct GrantInfo *gi,
    288               const struct TALER_MERCHANTDB_TokenFamilyDetails *tf,
    289               struct TALER_MERCHANT_ContractTokenFamily *family)
    290 {
    291   struct TMH_TokenKeyWindow window;
    292   enum GNUNET_GenericReturnValue res;
    293 
    294   res = TMH_token_key_window_get (connection,
    295                                   instance_id,
    296                                   tf,
    297                                   ic->now,
    298                                   gi->key_window_size,
    299                                   &window);
    300   if (GNUNET_OK != res)
    301     return res;
    302   for (unsigned int i = 0; i < window.keys_len; i++)
    303   {
    304     const struct TALER_MERCHANTDB_TokenFamilyKeyDetails *kd = &window.keys[i];
    305     struct TALER_MERCHANT_ContractTokenFamilyKey key;
    306 
    307     TALER_token_issue_pub_copy (&key.pub,
    308                                 &kd->pub);
    309     key.valid_after = kd->signature_validity_start;
    310     key.valid_before = kd->signature_validity_end;
    311     GNUNET_array_append (family->keys,
    312                          family->keys_len,
    313                          key);
    314   }
    315   TMH_token_key_window_free (&window);
    316   return GNUNET_OK;
    317 }
    318 
    319 
    320 /**
    321  * Expand one grant of the fountain into its JSON representation,
    322  * including the token family metadata and its issue keys.
    323  *
    324  * @param connection connection to report errors on
    325  * @param instance_id instance the fountain belongs to
    326  * @param ic context of the request
    327  * @param gi grant to expand
    328  * @param[in,out] jgrants JSON array to append the grant to
    329  * @return #GNUNET_OK on success, #GNUNET_NO if an error response was
    330  *         queued, #GNUNET_SYSERR on hard failure
    331  */
    332 static enum GNUNET_GenericReturnValue
    333 expand_grant (struct MHD_Connection *connection,
    334               const char *instance_id,
    335               const struct InfoContext *ic,
    336               const struct GrantInfo *gi,
    337               json_t *jgrants)
    338 {
    339   struct TALER_MERCHANTDB_TokenFamilyDetails tf;
    340   struct TALER_MERCHANT_ContractTokenFamily family;
    341   json_t *jfamily;
    342   enum GNUNET_GenericReturnValue res;
    343   enum GNUNET_DB_QueryStatus qs;
    344 
    345   qs = TALER_MERCHANTDB_get_token_family (TMH_db,
    346                                           instance_id,
    347                                           gi->token_family_slug,
    348                                           &tf);
    349   if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs)
    350   {
    351     /* Grants cascade-delete with their token family, so the family
    352        must exist; anything else is an internal failure. */
    353     GNUNET_break (0);
    354     return (MHD_YES ==
    355             TALER_MHD_reply_with_error (connection,
    356                                         MHD_HTTP_INTERNAL_SERVER_ERROR,
    357                                         TALER_EC_GENERIC_DB_FETCH_FAILED,
    358                                         "get_token_family"))
    359            ? GNUNET_NO
    360            : GNUNET_SYSERR;
    361   }
    362   TMH_token_family_to_contract (&tf,
    363                                 &family);
    364   res = collect_keys (connection,
    365                       instance_id,
    366                       ic,
    367                       gi,
    368                       &tf,
    369                       &family);
    370   TALER_MERCHANTDB_token_family_details_free (&tf);
    371   if (GNUNET_OK != res)
    372   {
    373     TALER_MERCHANT_contract_token_family_free (&family);
    374     return res;
    375   }
    376   jfamily = TALER_MERCHANT_json_from_token_family (&family);
    377   GNUNET_assert (NULL != jfamily);
    378   TALER_MERCHANT_contract_token_family_free (&family);
    379   GNUNET_assert (0 ==
    380                  json_array_append_new (
    381                    jgrants,
    382                    GNUNET_JSON_PACK (
    383                      GNUNET_JSON_pack_string ("token_family_slug",
    384                                               gi->token_family_slug),
    385                      GNUNET_JSON_pack_uint64 ("tokens_per_period_limit",
    386                                               gi->tokens_per_period_limit),
    387                      GNUNET_JSON_pack_uint64 ("tokens_per_period_stash",
    388                                               gi->tokens_per_period_stash),
    389                      GNUNET_JSON_pack_uint64 ("key_window_size",
    390                                               gi->key_window_size),
    391                      GNUNET_JSON_pack_object_steal ("token_family",
    392                                                     jfamily))));
    393   return GNUNET_OK;
    394 }
    395 
    396 
    397 enum MHD_Result
    398 TMH_get_fountain_info (const struct TMH_RequestHandler *rh,
    399                        struct MHD_Connection *connection,
    400                        struct TMH_HandlerContext *hc)
    401 {
    402   struct TMH_MerchantInstance *mi = hc->instance;
    403   struct InfoContext *ic = hc->ctx;
    404   json_t *jgrants;
    405   enum GNUNET_GenericReturnValue res;
    406 
    407   GNUNET_assert (NULL != mi);
    408   if (NULL == ic)
    409   {
    410     ic = GNUNET_new (struct InfoContext);
    411     ic->now = GNUNET_TIME_timestamp_get ();
    412     hc->ctx = ic;
    413     hc->cc = &info_context_cleanup;
    414   }
    415   res = authenticate (connection,
    416                       mi->settings.id,
    417                       ic);
    418   if (GNUNET_OK != res)
    419     return (GNUNET_NO == res)
    420            ? MHD_YES
    421            : MHD_NO;
    422   jgrants = json_array ();
    423   GNUNET_assert (NULL != jgrants);
    424   for (unsigned int i = 0; i < ic->grants_len; i++)
    425   {
    426     res = expand_grant (connection,
    427                         mi->settings.id,
    428                         ic,
    429                         &ic->grants[i],
    430                         jgrants);
    431     if (GNUNET_OK != res)
    432     {
    433       json_decref (jgrants);
    434       return (GNUNET_NO == res)
    435              ? MHD_YES
    436              : MHD_NO;
    437     }
    438   }
    439   return TALER_MHD_REPLY_JSON_PACK (
    440     connection,
    441     MHD_HTTP_OK,
    442     GNUNET_JSON_pack_time_rel ("poll_freq",
    443                                ic->poll_freq),
    444     GNUNET_JSON_pack_array_steal ("grants",
    445                                   jgrants));
    446 }
    447 
    448 
    449 /* end of taler-merchant-httpd_get-fountain-info.c */