Line data Source code
1 : /*
2 : This file is part of TALER
3 : Copyright (C) 2018-2021 Taler Systems SA
4 :
5 : TALER is free software; you can redistribute it and/or modify it
6 : under the terms of the GNU General Public License as published by
7 : the Free Software Foundation; either version 3, or (at your
8 : 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 GNU
13 : 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, see
17 : <http://www.gnu.org/licenses/>
18 : */
19 : /**
20 : * @file testing/testing_api_cmd_bank_admin_add_incoming.c
21 : * @brief implementation of a bank /admin/add-incoming command
22 : * @author Christian Grothoff
23 : * @author Marcello Stanisci
24 : */
25 : #include "platform.h"
26 : #include "backoff.h"
27 : #include "taler_json_lib.h"
28 : #include <gnunet/gnunet_curl_lib.h>
29 : #include "taler_bank_service.h"
30 : #include "taler_signatures.h"
31 : #include "taler_testing_lib.h"
32 :
33 : /**
34 : * How long do we wait AT MOST when retrying?
35 : */
36 : #define MAX_BACKOFF GNUNET_TIME_relative_multiply ( \
37 : GNUNET_TIME_UNIT_MILLISECONDS, 100)
38 :
39 :
40 : /**
41 : * How often do we retry before giving up?
42 : */
43 : #define NUM_RETRIES 5
44 :
45 :
46 : /**
47 : * State for a "bank transfer" CMD.
48 : */
49 : struct AdminAddIncomingState
50 : {
51 :
52 : /**
53 : * Label of any command that can trait-offer a reserve priv.
54 : */
55 : const char *reserve_reference;
56 :
57 : /**
58 : * Wire transfer amount.
59 : */
60 : struct TALER_Amount amount;
61 :
62 : /**
63 : * Base URL of the credited account.
64 : */
65 : const char *exchange_credit_url;
66 :
67 : /**
68 : * Money sender payto URL.
69 : */
70 : struct TALER_FullPayto payto_debit_account;
71 :
72 : /**
73 : * Username to use for authentication.
74 : */
75 : struct TALER_BANK_AuthenticationData auth;
76 :
77 : /**
78 : * Set (by the interpreter) to the reserve's private key
79 : * we used to make a wire transfer subject line with.
80 : */
81 : union TALER_AccountPrivateKeyP account_priv;
82 :
83 : /**
84 : * Whether we know the private key or not.
85 : */
86 : bool reserve_priv_known;
87 :
88 : /**
89 : * Account public key matching @e account_priv.
90 : */
91 : union TALER_AccountPublicKeyP account_pub;
92 :
93 : /**
94 : * Handle to the pending request at the bank.
95 : */
96 : struct TALER_BANK_AdminAddIncomingHandle *aih;
97 :
98 : /**
99 : * Interpreter state.
100 : */
101 : struct TALER_TESTING_Interpreter *is;
102 :
103 : /**
104 : * Reserve history entry that corresponds to this operation.
105 : * Will be of type #TALER_EXCHANGE_RTT_CREDIT. Note that
106 : * the "sender_url" field is set to a 'const char *' and
107 : * MUST NOT be free()'ed.
108 : */
109 : struct TALER_EXCHANGE_ReserveHistoryEntry reserve_history;
110 :
111 : /**
112 : * Set to the wire transfer's unique ID.
113 : */
114 : uint64_t serial_id;
115 :
116 : /**
117 : * Timestamp of the transaction (as returned from the bank).
118 : */
119 : struct GNUNET_TIME_Timestamp timestamp;
120 :
121 : /**
122 : * Task scheduled to try later.
123 : */
124 : struct GNUNET_SCHEDULER_Task *retry_task;
125 :
126 : /**
127 : * How long do we wait until we retry?
128 : */
129 : struct GNUNET_TIME_Relative backoff;
130 :
131 : /**
132 : * Was this command modified via
133 : * #TALER_TESTING_cmd_admin_add_incoming_with_retry to
134 : * enable retries? If so, how often should we still retry?
135 : */
136 : unsigned int do_retry;
137 :
138 : /**
139 : * Expected HTTP status code.
140 : */
141 : unsigned int expected_http_status;
142 : };
143 :
144 :
145 : /**
146 : * Run the "bank transfer" CMD.
147 : *
148 : * @param cls closure.
149 : * @param cmd CMD being run.
150 : * @param is interpreter state.
151 : */
152 : static void
153 : admin_add_incoming_run (
154 : void *cls,
155 : const struct TALER_TESTING_Command *cmd,
156 : struct TALER_TESTING_Interpreter *is);
157 :
158 :
159 : /**
160 : * Task scheduled to re-try #admin_add_incoming_run.
161 : *
162 : * @param cls a `struct AdminAddIncomingState`
163 : */
164 : static void
165 0 : do_retry (void *cls)
166 : {
167 0 : struct AdminAddIncomingState *fts = cls;
168 :
169 0 : fts->retry_task = NULL;
170 0 : TALER_TESTING_touch_cmd (fts->is);
171 0 : admin_add_incoming_run (fts,
172 : NULL,
173 : fts->is);
174 0 : }
175 :
176 :
177 : /**
178 : * This callback will process the bank response to the wire
179 : * transfer. It just checks whether the HTTP response code is
180 : * acceptable.
181 : *
182 : * @param cls closure with the interpreter state
183 : * @param air response details
184 : */
185 : static void
186 60 : confirmation_cb (void *cls,
187 : const struct TALER_BANK_AdminAddIncomingResponse *air)
188 : {
189 60 : struct AdminAddIncomingState *fts = cls;
190 60 : struct TALER_TESTING_Interpreter *is = fts->is;
191 :
192 60 : fts->aih = NULL;
193 : /**
194 : * Test case not caring about the HTTP status code.
195 : * That helps when fakebank and Libeufin diverge in
196 : * the response status code. An example is the
197 : * /admin/add-incoming: libeufin return ALWAYS '200 OK'
198 : * (see note below) whereas the fakebank responds with
199 : * '409 Conflict' upon a duplicate reserve public key.
200 : *
201 : * Note: this decision aims at avoiding to put Taler
202 : * logic into the Sandbox; that's because banks DO allow
203 : * their customers to wire the same subject multiple
204 : * times. Hence, instead of triggering any error, libeufin
205 : * bounces the payment back in the same way it does for
206 : * malformed reserve public keys.
207 : */
208 60 : if (-1 == (int) fts->expected_http_status)
209 : {
210 2 : TALER_TESTING_interpreter_next (is);
211 2 : return;
212 : }
213 58 : if (air->http_status != fts->expected_http_status)
214 : {
215 0 : TALER_TESTING_unexpected_status (is,
216 : air->http_status,
217 : fts->expected_http_status);
218 0 : return;
219 : }
220 58 : switch (air->http_status)
221 : {
222 58 : case MHD_HTTP_OK:
223 : fts->reserve_history.details.in_details.timestamp
224 58 : = air->details.ok.timestamp;
225 : fts->reserve_history.details.in_details.wire_reference
226 58 : = air->details.ok.serial_id;
227 : fts->serial_id
228 58 : = air->details.ok.serial_id;
229 : fts->timestamp
230 58 : = air->details.ok.timestamp;
231 58 : TALER_TESTING_interpreter_next (is);
232 58 : return;
233 0 : case MHD_HTTP_UNAUTHORIZED:
234 0 : switch (fts->auth.method)
235 : {
236 0 : case TALER_BANK_AUTH_NONE:
237 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
238 : "Authentication required, but none configure.\n");
239 0 : break;
240 0 : case TALER_BANK_AUTH_BASIC:
241 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
242 : "Basic authentication (%s) failed.\n",
243 : fts->auth.details.basic.username);
244 0 : break;
245 0 : case TALER_BANK_AUTH_BEARER:
246 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
247 : "Bearer authentication (%s) failed.\n",
248 : fts->auth.details.bearer.token);
249 0 : break;
250 : }
251 0 : break;
252 0 : case MHD_HTTP_CONFLICT:
253 0 : TALER_TESTING_interpreter_next (is);
254 0 : return;
255 0 : default:
256 0 : if (0 != fts->do_retry)
257 : {
258 0 : fts->do_retry--;
259 0 : if ( (0 == air->http_status) ||
260 0 : (TALER_EC_GENERIC_DB_SOFT_FAILURE == air->ec) ||
261 0 : (MHD_HTTP_INTERNAL_SERVER_ERROR == air->http_status) )
262 : {
263 0 : GNUNET_log (
264 : GNUNET_ERROR_TYPE_INFO,
265 : "Retrying bank transfer failed with %u/%d\n",
266 : air->http_status,
267 : (int) air->ec);
268 : /* on DB conflicts, do not use backoff */
269 0 : if (TALER_EC_GENERIC_DB_SOFT_FAILURE == air->ec)
270 0 : fts->backoff = GNUNET_TIME_UNIT_ZERO;
271 : else
272 0 : fts->backoff = GNUNET_TIME_randomized_backoff (fts->backoff,
273 : MAX_BACKOFF);
274 0 : TALER_TESTING_inc_tries (fts->is);
275 0 : fts->retry_task = GNUNET_SCHEDULER_add_delayed (
276 : fts->backoff,
277 : &do_retry,
278 : fts);
279 0 : return;
280 : }
281 : }
282 0 : break;
283 : }
284 0 : GNUNET_break (0);
285 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
286 : "Bank returned HTTP status %u/%d\n",
287 : air->http_status,
288 : (int) air->ec);
289 0 : TALER_TESTING_interpreter_fail (is);
290 : }
291 :
292 :
293 : static void
294 60 : admin_add_incoming_run (
295 : void *cls,
296 : const struct TALER_TESTING_Command *cmd,
297 : struct TALER_TESTING_Interpreter *is)
298 : {
299 60 : struct AdminAddIncomingState *fts = cls;
300 60 : bool have_public = false;
301 :
302 : (void) cmd;
303 60 : fts->is = is;
304 : /* Use reserve public key as subject */
305 60 : if (NULL != fts->reserve_reference)
306 : {
307 : const struct TALER_TESTING_Command *ref;
308 : const struct TALER_ReservePrivateKeyP *reserve_priv;
309 : const struct TALER_ReservePublicKeyP *reserve_pub;
310 :
311 2 : ref = TALER_TESTING_interpreter_lookup_command (
312 : is,
313 : fts->reserve_reference);
314 2 : if (NULL == ref)
315 : {
316 0 : GNUNET_break (0);
317 0 : TALER_TESTING_interpreter_fail (is);
318 0 : return;
319 : }
320 2 : if (GNUNET_OK !=
321 2 : TALER_TESTING_get_trait_reserve_priv (ref,
322 : &reserve_priv))
323 : {
324 0 : if (GNUNET_OK !=
325 0 : TALER_TESTING_get_trait_reserve_pub (ref,
326 : &reserve_pub))
327 : {
328 0 : GNUNET_break (0);
329 0 : TALER_TESTING_interpreter_fail (is);
330 0 : return;
331 : }
332 0 : have_public = true;
333 : fts->account_pub.reserve_pub.eddsa_pub
334 0 : = reserve_pub->eddsa_pub;
335 0 : fts->reserve_priv_known = false;
336 : }
337 : else
338 : {
339 : fts->account_priv.reserve_priv.eddsa_priv
340 2 : = reserve_priv->eddsa_priv;
341 2 : fts->reserve_priv_known = true;
342 : }
343 : }
344 : else
345 : {
346 : /* No referenced reserve to take priv
347 : * from, no explicit subject given: create new key! */
348 58 : GNUNET_CRYPTO_eddsa_key_create (
349 : &fts->account_priv.reserve_priv.eddsa_priv);
350 58 : fts->reserve_priv_known = true;
351 : }
352 60 : if (! have_public)
353 60 : GNUNET_CRYPTO_eddsa_key_get_public (
354 60 : &fts->account_priv.reserve_priv.eddsa_priv,
355 : &fts->account_pub.reserve_pub.eddsa_pub);
356 60 : fts->reserve_history.type = TALER_EXCHANGE_RTT_CREDIT;
357 60 : fts->reserve_history.amount = fts->amount;
358 : fts->reserve_history.details.in_details.sender_url
359 60 : = fts->payto_debit_account; /* remember to NOT free this one... */
360 : fts->aih
361 60 : = TALER_BANK_admin_add_incoming (
362 : TALER_TESTING_interpreter_get_context (is),
363 60 : &fts->auth,
364 60 : &fts->account_pub.reserve_pub,
365 60 : &fts->amount,
366 : fts->payto_debit_account,
367 : &confirmation_cb,
368 : fts);
369 60 : if (NULL == fts->aih)
370 : {
371 0 : GNUNET_break (0);
372 0 : TALER_TESTING_interpreter_fail (is);
373 0 : return;
374 : }
375 : }
376 :
377 :
378 : /**
379 : * Free the state of a "/admin/add-incoming" CMD, and possibly
380 : * cancel a pending operation thereof.
381 : *
382 : * @param cls closure
383 : * @param cmd current CMD being cleaned up.
384 : */
385 : static void
386 60 : admin_add_incoming_cleanup (
387 : void *cls,
388 : const struct TALER_TESTING_Command *cmd)
389 : {
390 60 : struct AdminAddIncomingState *fts = cls;
391 :
392 60 : if (NULL != fts->aih)
393 : {
394 0 : TALER_TESTING_command_incomplete (fts->is,
395 : cmd->label);
396 0 : TALER_BANK_admin_add_incoming_cancel (fts->aih);
397 0 : fts->aih = NULL;
398 : }
399 60 : if (NULL != fts->retry_task)
400 : {
401 0 : GNUNET_SCHEDULER_cancel (fts->retry_task);
402 0 : fts->retry_task = NULL;
403 : }
404 60 : GNUNET_free (fts);
405 60 : }
406 :
407 :
408 : /**
409 : * Offer internal data from a "/admin/add-incoming" CMD to other
410 : * commands.
411 : *
412 : * @param cls closure.
413 : * @param[out] ret result
414 : * @param trait name of the trait.
415 : * @param index index number of the object to offer.
416 : * @return #GNUNET_OK on success.
417 : */
418 : static enum GNUNET_GenericReturnValue
419 325 : admin_add_incoming_traits (void *cls,
420 : const void **ret,
421 : const char *trait,
422 : unsigned int index)
423 : {
424 325 : struct AdminAddIncomingState *fts = cls;
425 : static struct TALER_FullPayto void_uri = {
426 : .full_payto = (char *) "payto://void/the-exchange?receiver=name=exchange"
427 : };
428 :
429 325 : if (MHD_HTTP_OK !=
430 325 : fts->expected_http_status)
431 6 : return GNUNET_NO; /* requests that failed generate no history */
432 319 : if (fts->reserve_priv_known)
433 : {
434 : struct TALER_TESTING_Trait traits[] = {
435 319 : TALER_TESTING_make_trait_bank_row (&fts->serial_id),
436 319 : TALER_TESTING_make_trait_debit_payto_uri (&fts->payto_debit_account),
437 319 : TALER_TESTING_make_trait_full_payto_uri (&fts->payto_debit_account),
438 : /* Used as a marker, content does not matter */
439 319 : TALER_TESTING_make_trait_credit_payto_uri (&void_uri),
440 319 : TALER_TESTING_make_trait_exchange_bank_account_url (
441 : fts->exchange_credit_url),
442 319 : TALER_TESTING_make_trait_amount (&fts->amount),
443 319 : TALER_TESTING_make_trait_timestamp (0,
444 319 : &fts->timestamp),
445 319 : TALER_TESTING_make_trait_reserve_priv (
446 319 : &fts->account_priv.reserve_priv),
447 319 : TALER_TESTING_make_trait_reserve_pub (
448 319 : &fts->account_pub.reserve_pub),
449 319 : TALER_TESTING_make_trait_account_priv (
450 319 : &fts->account_priv),
451 319 : TALER_TESTING_make_trait_account_pub (
452 319 : &fts->account_pub),
453 319 : TALER_TESTING_make_trait_reserve_history (0,
454 319 : &fts->reserve_history),
455 319 : TALER_TESTING_trait_end ()
456 : };
457 :
458 319 : return TALER_TESTING_get_trait (traits,
459 : ret,
460 : trait,
461 : index);
462 : }
463 : else
464 : {
465 : struct TALER_TESTING_Trait traits[] = {
466 0 : TALER_TESTING_make_trait_bank_row (&fts->serial_id),
467 0 : TALER_TESTING_make_trait_debit_payto_uri (&fts->payto_debit_account),
468 : /* Used as a marker, content does not matter */
469 0 : TALER_TESTING_make_trait_credit_payto_uri (&void_uri),
470 0 : TALER_TESTING_make_trait_exchange_bank_account_url (
471 : fts->exchange_credit_url),
472 0 : TALER_TESTING_make_trait_amount (&fts->amount),
473 0 : TALER_TESTING_make_trait_timestamp (0,
474 0 : &fts->timestamp),
475 0 : TALER_TESTING_make_trait_reserve_pub (
476 0 : &fts->account_pub.reserve_pub),
477 0 : TALER_TESTING_make_trait_account_pub (
478 0 : &fts->account_pub),
479 0 : TALER_TESTING_make_trait_reserve_history (
480 : 0,
481 0 : &fts->reserve_history),
482 0 : TALER_TESTING_trait_end ()
483 : };
484 :
485 0 : return TALER_TESTING_get_trait (traits,
486 : ret,
487 : trait,
488 : index);
489 : }
490 : }
491 :
492 :
493 : /**
494 : * Create internal state for "/admin/add-incoming" CMD.
495 : *
496 : * @param amount the amount to transfer.
497 : * @param payto_debit_account which account sends money
498 : * @param auth authentication data
499 : * @return the internal state
500 : */
501 : static struct AdminAddIncomingState *
502 60 : make_fts (const char *amount,
503 : const struct TALER_BANK_AuthenticationData *auth,
504 : const struct TALER_FullPayto payto_debit_account)
505 : {
506 : struct AdminAddIncomingState *fts;
507 :
508 60 : fts = GNUNET_new (struct AdminAddIncomingState);
509 60 : fts->exchange_credit_url = auth->wire_gateway_url;
510 60 : fts->payto_debit_account = payto_debit_account;
511 60 : fts->auth = *auth;
512 60 : fts->expected_http_status = MHD_HTTP_OK;
513 60 : if (GNUNET_OK !=
514 60 : TALER_string_to_amount (amount,
515 : &fts->amount))
516 : {
517 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
518 : "Failed to parse amount `%s'\n",
519 : amount);
520 0 : GNUNET_assert (0);
521 : }
522 60 : return fts;
523 : }
524 :
525 :
526 : /**
527 : * Helper function to create admin/add-incoming command.
528 : *
529 : * @param label command label.
530 : * @param fts internal state to use
531 : * @return the command.
532 : */
533 : static struct TALER_TESTING_Command
534 60 : make_command (const char *label,
535 : struct AdminAddIncomingState *fts)
536 : {
537 60 : struct TALER_TESTING_Command cmd = {
538 : .cls = fts,
539 : .label = label,
540 : .run = &admin_add_incoming_run,
541 : .cleanup = &admin_add_incoming_cleanup,
542 : .traits = &admin_add_incoming_traits
543 : };
544 :
545 60 : return cmd;
546 : }
547 :
548 :
549 : struct TALER_TESTING_Command
550 58 : TALER_TESTING_cmd_admin_add_incoming (
551 : const char *label,
552 : const char *amount,
553 : const struct TALER_BANK_AuthenticationData *auth,
554 : const struct TALER_FullPayto payto_debit_account)
555 : {
556 58 : return make_command (label,
557 : make_fts (amount,
558 : auth,
559 : payto_debit_account));
560 : }
561 :
562 :
563 : struct TALER_TESTING_Command
564 2 : TALER_TESTING_cmd_admin_add_incoming_with_ref (
565 : const char *label,
566 : const char *amount,
567 : const struct TALER_BANK_AuthenticationData *auth,
568 : const struct TALER_FullPayto payto_debit_account,
569 : const char *ref,
570 : unsigned int http_status)
571 : {
572 : struct AdminAddIncomingState *fts;
573 :
574 2 : fts = make_fts (amount,
575 : auth,
576 : payto_debit_account);
577 2 : fts->reserve_reference = ref;
578 2 : fts->expected_http_status = http_status;
579 2 : return make_command (label,
580 : fts);
581 : }
582 :
583 :
584 : struct TALER_TESTING_Command
585 0 : TALER_TESTING_cmd_admin_add_incoming_retry (struct TALER_TESTING_Command cmd)
586 : {
587 : struct AdminAddIncomingState *fts;
588 :
589 0 : GNUNET_assert (&admin_add_incoming_run == cmd.run);
590 0 : fts = cmd.cls;
591 0 : fts->do_retry = NUM_RETRIES;
592 0 : return cmd;
593 : }
594 :
595 :
596 : /* end of testing_api_cmd_bank_admin_add_incoming.c */
|