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