Line data Source code
1 : /*
2 : This file is part of TALER
3 : Copyright (C) 2022-2023 Taler Systems SA
4 :
5 : TALER is free software; you can redistribute it and/or modify
6 : it under the terms of the GNU General Public License as
7 : published by the Free Software Foundation; either version 3, or
8 : (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, see
17 : <http://www.gnu.org/licenses/>
18 : */
19 : /**
20 : * @file testing_api_cmd_post_using_templates.c
21 : * @brief command to test POST /using-templates
22 : * @author Priscilla HUANG
23 : */
24 : #include "platform.h"
25 : #include <taler/taler_exchange_service.h>
26 : #include <taler/taler_testing_lib.h>
27 : #include "taler_merchant_service.h"
28 : #include "taler_merchant_testing_lib.h"
29 :
30 :
31 : /**
32 : * State of a "POST /templates" CMD.
33 : */
34 : struct PostUsingTemplatesState
35 : {
36 :
37 : /**
38 : * Handle for a "GET using-template" request.
39 : */
40 : struct TALER_MERCHANT_UsingTemplatesPostHandle *iph;
41 :
42 : /**
43 : * The interpreter state.
44 : */
45 : struct TALER_TESTING_Interpreter *is;
46 :
47 : /**
48 : * The (initial) POST /orders/$ID/claim operation handle.
49 : * The logic is such that after an order creation,
50 : * we immediately claim the order.
51 : */
52 : struct TALER_MERCHANT_OrderClaimHandle *och;
53 :
54 : /**
55 : * Base URL of the merchant serving the request.
56 : */
57 : const char *merchant_url;
58 :
59 : /**
60 : * ID of the using template to run.
61 : */
62 : const char *using_template_id;
63 :
64 : /**
65 : * Summary given by the customer.
66 : */
67 : const char *summary;
68 :
69 : /**
70 : * Amount given by the customer.
71 : */
72 : struct TALER_Amount amount;
73 :
74 : /**
75 : * Label of a command that created the template we should use.
76 : */
77 : const char *template_ref;
78 :
79 : /**
80 : * Order id.
81 : */
82 : char *order_id;
83 :
84 : /**
85 : * The order id we expect the merchant to assign (if not NULL).
86 : */
87 : const char *expected_order_id;
88 :
89 : /**
90 : * Contract terms obtained from the backend.
91 : */
92 : json_t *contract_terms;
93 :
94 : /**
95 : * Order submitted to the backend.
96 : */
97 : json_t *order_terms;
98 :
99 : /**
100 : * Contract terms hash code.
101 : */
102 : struct TALER_PrivateContractHashP h_contract_terms;
103 :
104 : /**
105 : * Merchant signature over the orders.
106 : */
107 : struct TALER_MerchantSignatureP merchant_sig;
108 :
109 : /**
110 : * Merchant public key.
111 : */
112 : struct TALER_MerchantPublicKeyP merchant_pub;
113 :
114 : /**
115 : * The nonce.
116 : */
117 : struct GNUNET_CRYPTO_EddsaPublicKey nonce;
118 :
119 : /**
120 : * The claim token
121 : */
122 : struct TALER_ClaimTokenP claim_token;
123 :
124 : /**
125 : * Should the command also CLAIM the order?
126 : */
127 : bool with_claim;
128 :
129 : /**
130 : * If not NULL, the command should duplicate the request and verify the
131 : * response is the same as in this command.
132 : */
133 : const char *duplicate_of;
134 :
135 : /**
136 : * Label of command creating/updating OTP device, or NULL.
137 : */
138 : const char *otp_ref;
139 :
140 : /**
141 : * Encoded key for the payment verification.
142 : */
143 : const char *otp_key;
144 :
145 : /**
146 : * Option that add amount of the order
147 : */
148 : const enum TALER_MerchantConfirmationAlgorithm *otp_alg;
149 :
150 : /**
151 : * Expected HTTP response code.
152 : */
153 : unsigned int http_status;
154 :
155 : };
156 :
157 : /**
158 : * Used to fill the "using_template" CMD state with backend-provided
159 : * values. Also double-checks that the using_template was correctly
160 : * created.
161 : *
162 : * @param cls closure
163 : * @param ocr response we got
164 : */
165 : static void
166 4 : using_claim_cb (void *cls,
167 : const struct TALER_MERCHANT_OrderClaimResponse *ocr)
168 : {
169 4 : struct PostUsingTemplatesState *tis = cls;
170 : const char *error_name;
171 : unsigned int error_line;
172 : struct GNUNET_JSON_Specification spec[] = {
173 4 : GNUNET_JSON_spec_fixed_auto ("merchant_pub",
174 : &tis->merchant_pub),
175 4 : GNUNET_JSON_spec_end ()
176 : };
177 :
178 4 : tis->och = NULL;
179 4 : if (tis->http_status != ocr->hr.http_status)
180 : {
181 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
182 : "Expected status %u, got %u\n",
183 : tis->http_status,
184 : ocr->hr.http_status);
185 0 : TALER_TESTING_FAIL (tis->is);
186 : }
187 4 : if (MHD_HTTP_OK != ocr->hr.http_status)
188 : {
189 0 : TALER_TESTING_interpreter_next (tis->is);
190 0 : return;
191 : }
192 8 : tis->contract_terms = json_deep_copy (
193 4 : (json_t *) ocr->details.ok.contract_terms);
194 4 : tis->h_contract_terms = ocr->details.ok.h_contract_terms;
195 4 : tis->merchant_sig = ocr->details.ok.sig;
196 4 : if (GNUNET_OK !=
197 4 : GNUNET_JSON_parse (tis->contract_terms,
198 : spec,
199 : &error_name,
200 : &error_line))
201 : {
202 : char *log;
203 :
204 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
205 : "Parser failed on %s:%u\n",
206 : error_name,
207 : error_line);
208 0 : log = json_dumps (tis->contract_terms,
209 : JSON_INDENT (1));
210 0 : fprintf (stderr,
211 : "%s\n",
212 : log);
213 0 : free (log);
214 0 : TALER_TESTING_FAIL (tis->is);
215 : }
216 4 : TALER_TESTING_interpreter_next (tis->is);
217 : }
218 :
219 :
220 : /**
221 : * Callback for a POST /using-templates operation.
222 : *
223 : * @param cls closure for this function
224 : * @param por response being processed
225 : */
226 : static void
227 14 : post_using_templates_cb (void *cls,
228 : const struct TALER_MERCHANT_PostOrdersReply *por)
229 : {
230 14 : struct PostUsingTemplatesState *tis = cls;
231 :
232 14 : tis->iph = NULL;
233 14 : if (tis->http_status != por->hr.http_status)
234 : {
235 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
236 : "Unexpected response code %u (%d) to command %s\n",
237 : por->hr.http_status,
238 : (int) por->hr.ec,
239 : TALER_TESTING_interpreter_get_current_label (tis->is));
240 0 : TALER_TESTING_interpreter_fail (tis->is);
241 0 : return;
242 : }
243 14 : if (0 == tis->http_status)
244 : {
245 0 : TALER_LOG_DEBUG ("/using_templates, expected 0 status code\n");
246 0 : TALER_TESTING_interpreter_next (tis->is);
247 0 : return;
248 : }
249 : // check for order
250 14 : switch (por->hr.http_status)
251 : {
252 4 : case MHD_HTTP_OK:
253 4 : if (NULL != por->details.ok.token)
254 4 : tis->claim_token = *por->details.ok.token;
255 4 : tis->order_id = GNUNET_strdup (por->details.ok.order_id);
256 4 : if ((NULL != tis->expected_order_id) &&
257 0 : (0 != strcmp (por->details.ok.order_id,
258 : tis->expected_order_id)))
259 : {
260 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
261 : "Order id assigned does not match\n");
262 0 : TALER_TESTING_interpreter_fail (tis->is);
263 0 : return;
264 : }
265 4 : if (NULL != tis->duplicate_of)
266 : {
267 : const struct TALER_TESTING_Command *order_cmd;
268 : const struct TALER_ClaimTokenP *prev_token;
269 0 : struct TALER_ClaimTokenP zero_token = {0};
270 :
271 0 : order_cmd = TALER_TESTING_interpreter_lookup_command (
272 : tis->is,
273 : tis->duplicate_of);
274 0 : if (GNUNET_OK !=
275 0 : TALER_TESTING_get_trait_claim_token (order_cmd,
276 : &prev_token))
277 : {
278 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
279 : "Could not fetch previous order claim token\n");
280 0 : TALER_TESTING_interpreter_fail (tis->is);
281 0 : return;
282 : }
283 0 : if (NULL == por->details.ok.token)
284 0 : prev_token = &zero_token;
285 0 : if (0 != GNUNET_memcmp (prev_token,
286 : por->details.ok.token))
287 : {
288 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
289 : "Claim tokens for identical requests do not match\n");
290 0 : TALER_TESTING_interpreter_fail (tis->is);
291 0 : return;
292 : }
293 : }
294 4 : break;
295 2 : case MHD_HTTP_NOT_FOUND:
296 2 : TALER_TESTING_interpreter_next (tis->is);
297 2 : return;
298 0 : case MHD_HTTP_GONE:
299 0 : TALER_TESTING_interpreter_next (tis->is);
300 0 : return;
301 8 : case MHD_HTTP_CONFLICT:
302 8 : TALER_TESTING_interpreter_next (tis->is);
303 8 : return;
304 0 : default:
305 : {
306 0 : char *s = json_dumps (por->hr.reply,
307 : JSON_COMPACT);
308 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
309 : "Unexpected status code from /orders: %u (%d) at %s; JSON: %s\n",
310 : por->hr.http_status,
311 : (int) por->hr.ec,
312 : TALER_TESTING_interpreter_get_current_label (tis->is),
313 : s);
314 0 : GNUNET_free (s);
315 : /**
316 : * Not failing, as test cases are _supposed_
317 : * to create non 200 OK situations.
318 : */
319 0 : TALER_TESTING_interpreter_next (tis->is);
320 : }
321 0 : return;
322 : }
323 :
324 4 : if (! tis->with_claim)
325 : {
326 0 : TALER_TESTING_interpreter_next (tis->is);
327 0 : return;
328 : }
329 4 : if (NULL ==
330 4 : (tis->och = TALER_MERCHANT_order_claim (
331 : TALER_TESTING_interpreter_get_context (tis->is),
332 : tis->merchant_url,
333 4 : tis->order_id,
334 4 : &tis->nonce,
335 4 : &tis->claim_token,
336 : &using_claim_cb,
337 : tis)))
338 0 : TALER_TESTING_FAIL (tis->is);
339 : }
340 :
341 :
342 : /**
343 : * Run the "POST /using-templates" CMD.
344 : *
345 : *
346 : * @param cls closure.
347 : * @param cmd command being run now.
348 : * @param is interpreter state.
349 : */
350 : static void
351 14 : post_using_templates_run (void *cls,
352 : const struct TALER_TESTING_Command *cmd,
353 : struct TALER_TESTING_Interpreter *is)
354 : {
355 14 : struct PostUsingTemplatesState *tis = cls;
356 : const struct TALER_TESTING_Command *ref;
357 : const char *template_id;
358 :
359 14 : tis->is = is;
360 14 : ref = TALER_TESTING_interpreter_lookup_command (is,
361 : tis->template_ref);
362 14 : if (GNUNET_OK !=
363 14 : TALER_TESTING_get_trait_template_id (ref,
364 : &template_id))
365 0 : TALER_TESTING_FAIL (is);
366 14 : if (NULL != tis->otp_ref)
367 : {
368 2 : ref = TALER_TESTING_interpreter_lookup_command (is,
369 : tis->otp_ref);
370 2 : if (GNUNET_OK !=
371 2 : TALER_TESTING_get_trait_otp_key (ref,
372 : &tis->otp_key))
373 0 : TALER_TESTING_FAIL (is);
374 2 : if (GNUNET_OK !=
375 2 : TALER_TESTING_get_trait_otp_alg (ref,
376 : &tis->otp_alg))
377 0 : TALER_TESTING_FAIL (is);
378 : }
379 14 : tis->iph = TALER_MERCHANT_using_templates_post (
380 : TALER_TESTING_interpreter_get_context (is),
381 : tis->merchant_url,
382 : template_id,
383 : tis->summary,
384 14 : TALER_amount_is_valid (&tis->amount)
385 : ? &tis->amount
386 : : NULL,
387 : &post_using_templates_cb,
388 : tis);
389 14 : GNUNET_assert (NULL != tis->iph);
390 : }
391 :
392 :
393 : /**
394 : * Offers information from the POST /using-templates CMD state to other
395 : * commands.
396 : *
397 : * @param cls closure
398 : * @param[out] ret result (could be anything)
399 : * @param trait name of the trait
400 : * @param index index number of the object to extract.
401 : * @return #GNUNET_OK on success
402 : */
403 : static enum GNUNET_GenericReturnValue
404 20 : post_using_templates_traits (void *cls,
405 : const void **ret,
406 : const char *trait,
407 : unsigned int index)
408 : {
409 20 : struct PostUsingTemplatesState *pts = cls;
410 : struct TALER_TESTING_Trait traits[] = {
411 20 : TALER_TESTING_make_trait_order_id (pts->order_id),
412 20 : TALER_TESTING_make_trait_contract_terms (pts->contract_terms),
413 20 : TALER_TESTING_make_trait_order_terms (pts->order_terms),
414 20 : TALER_TESTING_make_trait_h_contract_terms (&pts->h_contract_terms),
415 20 : TALER_TESTING_make_trait_merchant_sig (&pts->merchant_sig),
416 20 : TALER_TESTING_make_trait_merchant_pub (&pts->merchant_pub),
417 20 : TALER_TESTING_make_trait_claim_nonce (&pts->nonce),
418 20 : TALER_TESTING_make_trait_claim_token (&pts->claim_token),
419 20 : TALER_TESTING_make_trait_otp_key (pts->otp_key),
420 20 : TALER_TESTING_make_trait_otp_alg (pts->otp_alg),
421 20 : TALER_TESTING_trait_end (),
422 : };
423 :
424 : (void) pts;
425 20 : return TALER_TESTING_get_trait (traits,
426 : ret,
427 : trait,
428 : index);
429 : }
430 :
431 :
432 : /**
433 : * Free the state of a "POST using-template" CMD, and possibly
434 : * cancel a pending operation thereof.
435 : *
436 : * @param cls closure.
437 : * @param cmd command being run.
438 : */
439 : static void
440 14 : post_using_templates_cleanup (void *cls,
441 : const struct TALER_TESTING_Command *cmd)
442 : {
443 14 : struct PostUsingTemplatesState *tis = cls;
444 :
445 14 : if (NULL != tis->iph)
446 : {
447 0 : GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
448 : "POST /using-templates operation did not complete\n");
449 0 : TALER_MERCHANT_using_templates_post_cancel (tis->iph);
450 : }
451 14 : json_decref (tis->order_terms);
452 14 : json_decref (tis->contract_terms);
453 14 : GNUNET_free (tis->order_id);
454 14 : GNUNET_free (tis);
455 14 : }
456 :
457 :
458 : /**
459 : * Mark part of the contract terms as possible to forget.
460 : *
461 : * @param cls pointer to the result of the forget operation.
462 : * @param object_id name of the object to forget.
463 : * @param parent parent of the object at @e object_id.
464 : */
465 : static void
466 56 : mark_forgettable (void *cls,
467 : const char *object_id,
468 : json_t *parent)
469 : {
470 56 : GNUNET_assert (GNUNET_OK ==
471 : TALER_JSON_contract_mark_forgettable (parent,
472 : object_id));
473 56 : }
474 :
475 :
476 : /**
477 : * Constructs the json for a POST using template request.
478 : *
479 : * @param using_template_id the name of the using_template to add, can be NULL.
480 : * @param refund_deadline the deadline for refunds on this using template.
481 : * @param pay_deadline the deadline for payment on this using template.
482 : * @param amount the amount this using template is for.
483 : * @param[out] using_template where to write the json string.
484 : */
485 : static void
486 14 : make_order_json (const char *using_template_id,
487 : struct GNUNET_TIME_Timestamp refund_deadline,
488 : struct GNUNET_TIME_Timestamp pay_deadline,
489 : const char *amount,
490 : json_t **using_template)
491 : {
492 14 : struct GNUNET_TIME_Timestamp refund = refund_deadline;
493 14 : struct GNUNET_TIME_Timestamp pay = pay_deadline;
494 : json_t *contract_terms;
495 : struct TALER_Amount tamount;
496 : json_t *arr;
497 :
498 14 : if (NULL != amount)
499 12 : GNUNET_assert (GNUNET_OK ==
500 : TALER_string_to_amount (amount,
501 : &tamount));
502 : /* Include required fields and some dummy objects to test forgetting. */
503 14 : arr = json_array ();
504 14 : GNUNET_assert (NULL != arr);
505 14 : GNUNET_assert (0 ==
506 : json_array_append_new (
507 : arr,
508 : GNUNET_JSON_PACK (
509 : GNUNET_JSON_pack_string (
510 : "item", "speakers"))));
511 14 : GNUNET_assert (0 ==
512 : json_array_append_new (
513 : arr,
514 : GNUNET_JSON_PACK (
515 : GNUNET_JSON_pack_string (
516 : "item", "headphones"))));
517 14 : GNUNET_assert (0 ==
518 : json_array_append_new (
519 : arr,
520 : GNUNET_JSON_PACK (
521 : GNUNET_JSON_pack_string (
522 : "item", "earbuds"))));
523 14 : contract_terms = GNUNET_JSON_PACK (
524 : GNUNET_JSON_pack_string ("summary",
525 : "merchant-lib testcase"),
526 : GNUNET_JSON_pack_allow_null (
527 : GNUNET_JSON_pack_string (
528 : "using_template_id", using_template_id)),
529 : NULL == amount
530 : ? GNUNET_JSON_pack_allow_null (
531 : GNUNET_JSON_pack_string ("amount",
532 : NULL))
533 : : TALER_JSON_pack_amount ("amount",
534 : &tamount),
535 : GNUNET_JSON_pack_string ("fulfillment_url",
536 : "https://example.com"),
537 : GNUNET_JSON_pack_allow_null (
538 : GNUNET_JSON_pack_timestamp ("refund_deadline",
539 : refund)),
540 : GNUNET_JSON_pack_allow_null (
541 : GNUNET_JSON_pack_timestamp ("pay_deadline",
542 : pay)),
543 : GNUNET_JSON_pack_string ("dummy_obj",
544 : "EUR:1.0"),
545 : GNUNET_JSON_pack_array_steal ("dummy_array",
546 : arr));
547 14 : GNUNET_assert (GNUNET_OK ==
548 : TALER_JSON_expand_path (contract_terms,
549 : "$.dummy_obj",
550 : &mark_forgettable,
551 : NULL));
552 14 : GNUNET_assert (GNUNET_OK ==
553 : TALER_JSON_expand_path (contract_terms,
554 : "$.dummy_array[*].item",
555 : &mark_forgettable,
556 : NULL));
557 14 : *using_template = contract_terms;
558 14 : }
559 :
560 :
561 : struct TALER_TESTING_Command
562 14 : TALER_TESTING_cmd_merchant_post_using_templates (
563 : const char *label,
564 : const char *template_ref,
565 : const char *otp_ref,
566 : const char *merchant_url,
567 : const char *using_template_id,
568 : const char *summary,
569 : const char *amount,
570 : struct GNUNET_TIME_Timestamp refund_deadline,
571 : struct GNUNET_TIME_Timestamp pay_deadline,
572 : unsigned int http_status)
573 : {
574 : struct PostUsingTemplatesState *tis;
575 :
576 14 : tis = GNUNET_new (struct PostUsingTemplatesState);
577 14 : tis->template_ref = template_ref;
578 14 : tis->otp_ref = otp_ref;
579 14 : tis->merchant_url = merchant_url;
580 14 : tis->using_template_id = using_template_id;
581 14 : tis->http_status = http_status;
582 14 : tis->summary = summary;
583 14 : tis->with_claim = true;
584 14 : make_order_json (using_template_id,
585 : refund_deadline,
586 : pay_deadline,
587 : amount,
588 : &tis->order_terms);
589 14 : if (NULL != amount)
590 12 : GNUNET_assert (GNUNET_OK ==
591 : TALER_string_to_amount (amount,
592 : &tis->amount));
593 : {
594 14 : struct TALER_TESTING_Command cmd = {
595 : .cls = tis,
596 : .label = label,
597 : .run = &post_using_templates_run,
598 : .cleanup = &post_using_templates_cleanup,
599 : .traits = &post_using_templates_traits
600 : };
601 :
602 14 : return cmd;
603 : }
604 : }
605 :
606 :
607 : /* end of testing_api_cmd_post_using_templates.c */
|