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 */