Line data Source code
1 : /*
2 : This file is part of TALER
3 : Copyright (C) 2014-2024 Taler Systems SA
4 :
5 : TALER is free software; you can redistribute it and/or modify it under the
6 : terms of the GNU Affero 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 Affero General Public License for more details.
12 :
13 : You should have received a copy of the GNU Affero General Public License along with
14 : TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
15 : */
16 : /**
17 : * @file taler-exchange-httpd_reserves_history.c
18 : * @brief Handle /reserves/$RESERVE_PUB HISTORY requests
19 : * @author Florian Dold
20 : * @author Benedikt Mueller
21 : * @author Christian Grothoff
22 : */
23 : #include "taler/platform.h"
24 : #include <gnunet/gnunet_util_lib.h>
25 : #include <jansson.h>
26 : #include "taler/taler_json_lib.h"
27 : #include "taler/taler_dbevents.h"
28 : #include "taler-exchange-httpd_keys.h"
29 : #include "taler-exchange-httpd_reserves_history.h"
30 : #include "taler-exchange-httpd_responses.h"
31 :
32 :
33 : /**
34 : * Compile the history of a reserve into a JSON object.
35 : *
36 : * @param rh reserve history to JSON-ify
37 : * @return json representation of the @a rh, NULL on error
38 : */
39 : static json_t *
40 8 : compile_reserve_history (
41 : const struct TALER_EXCHANGEDB_ReserveHistory *rh)
42 : {
43 : json_t *json_history;
44 :
45 8 : json_history = json_array ();
46 8 : GNUNET_assert (NULL != json_history);
47 8 : for (const struct TALER_EXCHANGEDB_ReserveHistory *pos = rh;
48 31 : NULL != pos;
49 23 : pos = pos->next)
50 : {
51 23 : switch (pos->type)
52 : {
53 8 : case TALER_EXCHANGEDB_RO_BANK_TO_EXCHANGE:
54 : {
55 8 : const struct TALER_EXCHANGEDB_BankTransfer *bank =
56 : pos->details.bank;
57 :
58 8 : if (0 !=
59 8 : json_array_append_new (
60 : json_history,
61 8 : GNUNET_JSON_PACK (
62 : GNUNET_JSON_pack_string ("type",
63 : "CREDIT"),
64 : GNUNET_JSON_pack_timestamp ("timestamp",
65 : bank->execution_date),
66 : TALER_JSON_pack_full_payto ("sender_account_url",
67 : bank->sender_account_details),
68 : GNUNET_JSON_pack_uint64 ("wire_reference",
69 : bank->wire_reference),
70 : TALER_JSON_pack_amount ("amount",
71 : &bank->amount))))
72 : {
73 0 : GNUNET_break (0);
74 0 : json_decref (json_history);
75 0 : return NULL;
76 : }
77 8 : break;
78 : }
79 7 : case TALER_EXCHANGEDB_RO_WITHDRAW_COINS:
80 : {
81 7 : const struct TALER_EXCHANGEDB_Withdraw *withdraw
82 : = pos->details.withdraw;
83 : struct TALER_Amount withdraw_fee;
84 : struct TEH_KeyStateHandle *ksh;
85 :
86 7 : GNUNET_assert (GNUNET_OK ==
87 : TALER_amount_set_zero (
88 : TEH_currency,
89 : &withdraw_fee));
90 :
91 : /*
92 : * We need to calculate the fees for the withdraw.
93 : * Therefore, we need to get access to the key state.
94 : */
95 7 : ksh = TEH_keys_get_state ();
96 7 : if (NULL == ksh)
97 : {
98 0 : GNUNET_break (0);
99 0 : json_decref (json_history);
100 0 : return NULL;
101 : }
102 :
103 16 : for (size_t i = 0; i < withdraw->num_coins; i++)
104 : {
105 : /* Find the denomination and accumulate the fee */
106 : {
107 : struct TEH_DenominationKey *dk;
108 9 : dk = TEH_keys_denomination_by_hash_from_state (
109 : ksh,
110 9 : &withdraw->denom_pub_hashes[i],
111 : NULL,
112 : NULL);
113 :
114 9 : if (NULL == dk)
115 : {
116 0 : GNUNET_break (0);
117 0 : json_decref (json_history);
118 0 : return NULL;
119 : }
120 :
121 9 : if (0 > TALER_amount_add (&withdraw_fee,
122 : &withdraw_fee,
123 9 : &dk->meta.fees.withdraw))
124 : {
125 0 : GNUNET_break (0);
126 0 : json_decref (json_history);
127 0 : return NULL;
128 : }
129 : }
130 : }
131 :
132 : /* Prepare the entry for the history */
133 : {
134 7 : json_t *j_entry = GNUNET_JSON_PACK (
135 : GNUNET_JSON_pack_string (
136 : "type",
137 : "WITHDRAW"),
138 : GNUNET_JSON_pack_data_auto (
139 : "reserve_sig",
140 : &withdraw->reserve_sig),
141 : GNUNET_JSON_pack_data_auto (
142 : "planchets_h",
143 : &withdraw->planchets_h),
144 : GNUNET_JSON_pack_uint64 (
145 : "num_coins",
146 : withdraw->num_coins),
147 : TALER_JSON_pack_array_of_data (
148 : "denom_pub_hashes",
149 : withdraw->num_coins,
150 : withdraw->denom_pub_hashes,
151 : sizeof(withdraw->denom_pub_hashes[0])),
152 : TALER_JSON_pack_amount (
153 : "withdraw_fee",
154 : &withdraw_fee),
155 : TALER_JSON_pack_amount (
156 : "amount",
157 : &withdraw->amount_with_fee)
158 : );
159 :
160 7 : if (! withdraw->no_blinding_seed)
161 : {
162 3 : if (0!=
163 3 : json_object_update_new (
164 : j_entry,
165 3 : GNUNET_JSON_PACK (
166 : GNUNET_JSON_pack_data_auto (
167 : "blinding_seed",
168 : &withdraw->blinding_seed))))
169 : {
170 0 : GNUNET_break (0);
171 0 : json_decref (json_history);
172 0 : return NULL;
173 : }
174 3 : if (0!=
175 3 : json_object_update_new (
176 : j_entry,
177 3 : GNUNET_JSON_PACK (
178 : TALER_JSON_pack_array_of_data (
179 : "cs_r_values",
180 : withdraw->num_cs_r_values,
181 : withdraw->cs_r_values,
182 : sizeof(withdraw->cs_r_values[0])))))
183 : {
184 0 : GNUNET_break (0);
185 0 : json_decref (json_history);
186 0 : return NULL;
187 : }
188 : }
189 7 : if (withdraw->age_proof_required)
190 : {
191 0 : if (0 !=
192 0 : json_object_update_new (
193 : j_entry,
194 0 : GNUNET_JSON_PACK (
195 : GNUNET_JSON_pack_uint64 (
196 : "noreveal_index",
197 : withdraw->noreveal_index),
198 : GNUNET_JSON_pack_data_auto (
199 : "selected_h",
200 : &withdraw->selected_h),
201 : GNUNET_JSON_pack_uint64 (
202 : "max_age",
203 : withdraw->max_age))))
204 : {
205 0 : GNUNET_break (0);
206 0 : json_decref (json_history);
207 0 : return NULL;
208 : }
209 : }
210 :
211 7 : if (0 !=
212 7 : json_array_append_new (
213 : json_history,
214 : j_entry))
215 : {
216 0 : GNUNET_break (0);
217 0 : json_decref (json_history);
218 0 : return NULL;
219 : }
220 : }
221 : }
222 :
223 7 : break;
224 0 : case TALER_EXCHANGEDB_RO_RECOUP_COIN:
225 : {
226 0 : const struct TALER_EXCHANGEDB_Recoup *recoup
227 : = pos->details.recoup;
228 : struct TALER_ExchangePublicKeyP pub;
229 : struct TALER_ExchangeSignatureP sig;
230 :
231 0 : if (TALER_EC_NONE !=
232 0 : TALER_exchange_online_confirm_recoup_sign (
233 : &TEH_keys_exchange_sign_,
234 : recoup->timestamp,
235 : &recoup->value,
236 : &recoup->coin.coin_pub,
237 : &recoup->reserve_pub,
238 : &pub,
239 : &sig))
240 : {
241 0 : GNUNET_break (0);
242 0 : json_decref (json_history);
243 0 : return NULL;
244 : }
245 :
246 0 : if (0 !=
247 0 : json_array_append_new (
248 : json_history,
249 0 : GNUNET_JSON_PACK (
250 : GNUNET_JSON_pack_string ("type",
251 : "RECOUP"),
252 : GNUNET_JSON_pack_data_auto ("exchange_pub",
253 : &pub),
254 : GNUNET_JSON_pack_data_auto ("exchange_sig",
255 : &sig),
256 : GNUNET_JSON_pack_timestamp ("timestamp",
257 : recoup->timestamp),
258 : TALER_JSON_pack_amount ("amount",
259 : &recoup->value),
260 : GNUNET_JSON_pack_data_auto ("coin_pub",
261 : &recoup->coin.coin_pub))))
262 : {
263 0 : GNUNET_break (0);
264 0 : json_decref (json_history);
265 0 : return NULL;
266 : }
267 : }
268 0 : break;
269 0 : case TALER_EXCHANGEDB_RO_EXCHANGE_TO_BANK:
270 : {
271 0 : const struct TALER_EXCHANGEDB_ClosingTransfer *closing =
272 : pos->details.closing;
273 : struct TALER_ExchangePublicKeyP pub;
274 : struct TALER_ExchangeSignatureP sig;
275 :
276 0 : if (TALER_EC_NONE !=
277 0 : TALER_exchange_online_reserve_closed_sign (
278 : &TEH_keys_exchange_sign_,
279 : closing->execution_date,
280 : &closing->amount,
281 : &closing->closing_fee,
282 : closing->receiver_account_details,
283 : &closing->wtid,
284 0 : &pos->details.closing->reserve_pub,
285 : &pub,
286 : &sig))
287 : {
288 0 : GNUNET_break (0);
289 0 : json_decref (json_history);
290 0 : return NULL;
291 : }
292 0 : if (0 !=
293 0 : json_array_append_new (
294 : json_history,
295 0 : GNUNET_JSON_PACK (
296 : GNUNET_JSON_pack_string ("type",
297 : "CLOSING"),
298 : TALER_JSON_pack_full_payto ("receiver_account_details",
299 : closing->receiver_account_details),
300 : GNUNET_JSON_pack_data_auto ("wtid",
301 : &closing->wtid),
302 : GNUNET_JSON_pack_data_auto ("exchange_pub",
303 : &pub),
304 : GNUNET_JSON_pack_data_auto ("exchange_sig",
305 : &sig),
306 : GNUNET_JSON_pack_timestamp ("timestamp",
307 : closing->execution_date),
308 : TALER_JSON_pack_amount ("amount",
309 : &closing->amount),
310 : TALER_JSON_pack_amount ("closing_fee",
311 : &closing->closing_fee))))
312 : {
313 0 : GNUNET_break (0);
314 0 : json_decref (json_history);
315 0 : return NULL;
316 : }
317 : }
318 0 : break;
319 8 : case TALER_EXCHANGEDB_RO_PURSE_MERGE:
320 : {
321 8 : const struct TALER_EXCHANGEDB_PurseMerge *merge =
322 : pos->details.merge;
323 :
324 8 : if (0 !=
325 8 : json_array_append_new (
326 : json_history,
327 8 : GNUNET_JSON_PACK (
328 : GNUNET_JSON_pack_string ("type",
329 : "MERGE"),
330 : GNUNET_JSON_pack_data_auto ("h_contract_terms",
331 : &merge->h_contract_terms),
332 : GNUNET_JSON_pack_data_auto ("merge_pub",
333 : &merge->merge_pub),
334 : GNUNET_JSON_pack_uint64 ("min_age",
335 : merge->min_age),
336 : GNUNET_JSON_pack_uint64 ("flags",
337 : merge->flags),
338 : GNUNET_JSON_pack_data_auto ("purse_pub",
339 : &merge->purse_pub),
340 : GNUNET_JSON_pack_data_auto ("reserve_sig",
341 : &merge->reserve_sig),
342 : GNUNET_JSON_pack_timestamp ("merge_timestamp",
343 : merge->merge_timestamp),
344 : GNUNET_JSON_pack_timestamp ("purse_expiration",
345 : merge->purse_expiration),
346 : TALER_JSON_pack_amount ("purse_fee",
347 : &merge->purse_fee),
348 : TALER_JSON_pack_amount ("amount",
349 : &merge->amount_with_fee),
350 : GNUNET_JSON_pack_bool ("merged",
351 : merge->merged))))
352 : {
353 0 : GNUNET_break (0);
354 0 : json_decref (json_history);
355 0 : return NULL;
356 : }
357 : }
358 8 : break;
359 0 : case TALER_EXCHANGEDB_RO_HISTORY_REQUEST:
360 : {
361 0 : const struct TALER_EXCHANGEDB_HistoryRequest *history =
362 : pos->details.history;
363 :
364 0 : if (0 !=
365 0 : json_array_append_new (
366 : json_history,
367 0 : GNUNET_JSON_PACK (
368 : GNUNET_JSON_pack_string ("type",
369 : "HISTORY"),
370 : GNUNET_JSON_pack_data_auto ("reserve_sig",
371 : &history->reserve_sig),
372 : GNUNET_JSON_pack_timestamp ("request_timestamp",
373 : history->request_timestamp),
374 : TALER_JSON_pack_amount ("amount",
375 : &history->history_fee))))
376 : {
377 0 : GNUNET_break (0);
378 0 : json_decref (json_history);
379 0 : return NULL;
380 : }
381 : }
382 0 : break;
383 :
384 0 : case TALER_EXCHANGEDB_RO_OPEN_REQUEST:
385 : {
386 0 : const struct TALER_EXCHANGEDB_OpenRequest *orq =
387 : pos->details.open_request;
388 :
389 0 : if (0 !=
390 0 : json_array_append_new (
391 : json_history,
392 0 : GNUNET_JSON_PACK (
393 : GNUNET_JSON_pack_string ("type",
394 : "OPEN"),
395 : GNUNET_JSON_pack_uint64 ("requested_min_purses",
396 : orq->purse_limit),
397 : GNUNET_JSON_pack_data_auto ("reserve_sig",
398 : &orq->reserve_sig),
399 : GNUNET_JSON_pack_timestamp ("request_timestamp",
400 : orq->request_timestamp),
401 : GNUNET_JSON_pack_timestamp ("requested_expiration",
402 : orq->reserve_expiration),
403 : TALER_JSON_pack_amount ("open_fee",
404 : &orq->open_fee))))
405 : {
406 0 : GNUNET_break (0);
407 0 : json_decref (json_history);
408 0 : return NULL;
409 : }
410 : }
411 0 : break;
412 :
413 0 : case TALER_EXCHANGEDB_RO_CLOSE_REQUEST:
414 : {
415 0 : const struct TALER_EXCHANGEDB_CloseRequest *crq =
416 : pos->details.close_request;
417 :
418 0 : if (0 !=
419 0 : json_array_append_new (
420 : json_history,
421 0 : GNUNET_JSON_PACK (
422 : GNUNET_JSON_pack_string ("type",
423 : "CLOSE"),
424 : GNUNET_JSON_pack_data_auto ("reserve_sig",
425 : &crq->reserve_sig),
426 : GNUNET_is_zero (&crq->target_account_h_payto)
427 : ? GNUNET_JSON_pack_allow_null (
428 : GNUNET_JSON_pack_string ("h_payto",
429 : NULL))
430 : : GNUNET_JSON_pack_data_auto ("h_payto",
431 : &crq->target_account_h_payto),
432 : GNUNET_JSON_pack_timestamp ("request_timestamp",
433 : crq->request_timestamp))))
434 : {
435 0 : GNUNET_break (0);
436 0 : json_decref (json_history);
437 0 : return NULL;
438 : }
439 : }
440 0 : break;
441 : }
442 : }
443 8 : return json_history;
444 : }
445 :
446 :
447 : /**
448 : * Add the headers we want to set for every /keys response.
449 : *
450 : * @param cls the key state to use
451 : * @param[in,out] response the response to modify
452 : */
453 : static void
454 8 : add_response_headers (void *cls,
455 : struct MHD_Response *response)
456 : {
457 : (void) cls;
458 8 : GNUNET_break (MHD_YES ==
459 : MHD_add_response_header (response,
460 : MHD_HTTP_HEADER_CACHE_CONTROL,
461 : "no-cache"));
462 8 : }
463 :
464 :
465 : MHD_RESULT
466 8 : TEH_handler_reserves_history (
467 : struct TEH_RequestContext *rc,
468 : const struct TALER_ReservePublicKeyP *reserve_pub)
469 : {
470 8 : struct TALER_EXCHANGEDB_ReserveHistory *rh = NULL;
471 8 : uint64_t start_off = 0;
472 : struct TALER_Amount balance;
473 : uint64_t etag_in;
474 : uint64_t etag_out;
475 : char etagp[24];
476 : struct MHD_Response *resp;
477 : unsigned int http_status;
478 :
479 8 : TALER_MHD_parse_request_number (rc->connection,
480 : "start",
481 : &start_off);
482 : {
483 : struct TALER_ReserveSignatureP reserve_sig;
484 8 : bool required = true;
485 :
486 8 : TALER_MHD_parse_request_header_auto (rc->connection,
487 : TALER_RESERVE_HISTORY_SIGNATURE_HEADER,
488 : &reserve_sig,
489 : required);
490 :
491 8 : if (GNUNET_OK !=
492 8 : TALER_wallet_reserve_history_verify (start_off,
493 : reserve_pub,
494 : &reserve_sig))
495 : {
496 0 : GNUNET_break_op (0);
497 0 : return TALER_MHD_reply_with_error (rc->connection,
498 : MHD_HTTP_FORBIDDEN,
499 : TALER_EC_EXCHANGE_RESERVE_HISTORY_BAD_SIGNATURE,
500 : NULL);
501 : }
502 : }
503 :
504 : /* Get etag */
505 : {
506 : const char *etags;
507 :
508 8 : etags = MHD_lookup_connection_value (rc->connection,
509 : MHD_HEADER_KIND,
510 : MHD_HTTP_HEADER_IF_NONE_MATCH);
511 8 : if (NULL != etags)
512 : {
513 : char dummy;
514 : unsigned long long ev;
515 :
516 0 : if (1 != sscanf (etags,
517 : "\"%llu\"%c",
518 : &ev,
519 : &dummy))
520 : {
521 0 : GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
522 : "Client send malformed `If-None-Match' header `%s'\n",
523 : etags);
524 0 : etag_in = 0;
525 : }
526 : else
527 : {
528 0 : etag_in = (uint64_t) ev;
529 : }
530 : }
531 : else
532 : {
533 8 : etag_in = start_off;
534 : }
535 : }
536 :
537 : {
538 : enum GNUNET_DB_QueryStatus qs;
539 :
540 8 : qs = TEH_plugin->get_reserve_history (TEH_plugin->cls,
541 : reserve_pub,
542 : start_off,
543 : etag_in,
544 : &etag_out,
545 : &balance,
546 : &rh);
547 8 : switch (qs)
548 : {
549 0 : case GNUNET_DB_STATUS_HARD_ERROR:
550 0 : GNUNET_break (0);
551 0 : return TALER_MHD_reply_with_error (rc->connection,
552 : MHD_HTTP_INTERNAL_SERVER_ERROR,
553 : TALER_EC_GENERIC_DB_FETCH_FAILED,
554 : "get_reserve_history");
555 0 : case GNUNET_DB_STATUS_SOFT_ERROR:
556 0 : return TALER_MHD_reply_with_error (rc->connection,
557 : MHD_HTTP_INTERNAL_SERVER_ERROR,
558 : TALER_EC_GENERIC_DB_SOFT_FAILURE,
559 : "get_reserve_history");
560 0 : case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
561 0 : return TALER_MHD_reply_with_error (rc->connection,
562 : MHD_HTTP_NOT_FOUND,
563 : TALER_EC_EXCHANGE_GENERIC_RESERVE_UNKNOWN,
564 : NULL);
565 8 : case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
566 : /* Handled below */
567 8 : break;
568 : }
569 : }
570 :
571 8 : GNUNET_snprintf (etagp,
572 : sizeof (etagp),
573 : "\"%llu\"",
574 : (unsigned long long) etag_out);
575 8 : if (etag_in == etag_out)
576 : {
577 0 : TEH_plugin->free_reserve_history (TEH_plugin->cls,
578 : rh);
579 0 : return TEH_RESPONSE_reply_not_modified (rc->connection,
580 : etagp,
581 : &add_response_headers,
582 : NULL);
583 : }
584 8 : if (NULL == rh)
585 : {
586 : /* 204: empty history */
587 0 : resp = MHD_create_response_from_buffer_static (0,
588 : "");
589 0 : http_status = MHD_HTTP_NO_CONTENT;
590 : }
591 : else
592 : {
593 : json_t *history;
594 :
595 8 : http_status = MHD_HTTP_OK;
596 8 : history = compile_reserve_history (rh);
597 8 : TEH_plugin->free_reserve_history (TEH_plugin->cls,
598 : rh);
599 8 : rh = NULL;
600 8 : if (NULL == history)
601 : {
602 0 : GNUNET_break (0);
603 0 : return TALER_MHD_reply_with_error (rc->connection,
604 : MHD_HTTP_INTERNAL_SERVER_ERROR,
605 : TALER_EC_GENERIC_JSON_ALLOCATION_FAILURE,
606 : NULL);
607 : }
608 8 : resp = TALER_MHD_MAKE_JSON_PACK (
609 : TALER_JSON_pack_amount ("balance",
610 : &balance),
611 : GNUNET_JSON_pack_array_steal ("history",
612 : history));
613 : }
614 8 : add_response_headers (NULL,
615 : resp);
616 8 : GNUNET_break (MHD_YES ==
617 : MHD_add_response_header (resp,
618 : MHD_HTTP_HEADER_ETAG,
619 : etagp));
620 : {
621 : MHD_RESULT ret;
622 :
623 8 : ret = MHD_queue_response (rc->connection,
624 : http_status,
625 : resp);
626 8 : GNUNET_break (MHD_YES == ret);
627 8 : MHD_destroy_response (resp);
628 8 : return ret;
629 : }
630 : }
631 :
632 :
633 : /* end of taler-exchange-httpd_reserves_history.c */
|