taler-merchant-httpd_helper.c (39664B)
1 /* 2 This file is part of TALER 3 (C) 2014--2026 Taler Systems SA 4 5 TALER is free software; you can redistribute it and/or modify it under the 6 terms of the GNU Lesser 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 General Public License for more details. 12 13 You should have received a copy of the GNU General Public License along with 14 TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/> 15 */ 16 /** 17 * @file src/backend/taler-merchant-httpd_helper.c 18 * @brief shared logic for various handlers 19 * @author Christian Grothoff 20 */ 21 #include "platform.h" 22 #include <gnunet/gnunet_util_lib.h> 23 #include <gnunet/gnunet_db_lib.h> 24 #include <taler/taler_json_lib.h> 25 #include "taler-merchant-httpd_helper.h" 26 #include <taler/taler_templating_lib.h> 27 #include <taler/taler_dbevents.h> 28 #include "merchant-database/insert_pending_webhook.h" 29 #include "merchant-database/iterate_webhooks_by_event.h" 30 #include "merchant-database/iterate_exchange_accounts.h" 31 #include "merchant-database/get_login_token.h" 32 #include "merchant-database/get_unit.h" 33 #include "merchant-database/event_notify.h" 34 35 36 json_t * 37 TMH_instance_metadata_to_json (const struct TMH_MerchantInstance *mi) 38 { 39 const struct TALER_MERCHANTDB_InstanceSettings *settings = &mi->settings; 40 struct TALER_MERCHANT_MetaData md = { 41 .name = settings->name, 42 .website = settings->website, 43 .email = settings->email, 44 .logo = settings->logo, 45 .address = settings->address, 46 .jurisdiction = settings->jurisdiction 47 }; 48 49 return TALER_MERCHANT_metadata_to_json (&md); 50 } 51 52 53 void 54 TMH_quantity_defaults_from_unit (const struct TMH_MerchantInstance *mi, 55 const char *unit, 56 bool *allow_fractional, 57 uint32_t *precision_level) 58 { 59 GNUNET_assert (NULL != allow_fractional); 60 GNUNET_assert (NULL != precision_level); 61 if (GNUNET_OK != 62 TMH_unit_defaults_for_instance (mi, 63 unit, 64 allow_fractional, 65 precision_level)) 66 { 67 *allow_fractional = false; 68 *precision_level = 0; 69 } 70 } 71 72 73 enum GNUNET_GenericReturnValue 74 TMH_unit_defaults_for_instance (const struct TMH_MerchantInstance *mi, 75 const char *unit, 76 bool *allow_fractional, 77 uint32_t *precision_level) 78 { 79 struct TALER_MERCHANTDB_UnitDetails ud = { 0 }; 80 enum GNUNET_DB_QueryStatus qs; 81 bool allow = false; 82 uint32_t precision = 0; 83 84 GNUNET_assert (NULL != allow_fractional); 85 GNUNET_assert (NULL != precision_level); 86 87 qs = TALER_MERCHANTDB_get_unit (TMH_db, 88 mi->settings.id, 89 unit, 90 &ud); 91 switch (qs) 92 { 93 case GNUNET_DB_STATUS_HARD_ERROR: 94 case GNUNET_DB_STATUS_SOFT_ERROR: 95 TALER_MERCHANTDB_unit_details_free (&ud); 96 return GNUNET_SYSERR; 97 case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: 98 allow = ud.unit_allow_fraction; 99 precision = ud.unit_precision_level; 100 break; 101 case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: 102 break; 103 default: 104 GNUNET_break (0); 105 TALER_MERCHANTDB_unit_details_free (&ud); 106 return GNUNET_SYSERR; 107 } 108 TALER_MERCHANTDB_unit_details_free (&ud); 109 110 /* This is definitely not supposed to happen 111 combination of allow -> false, and precision > 0 112 yet let's fix it */ 113 if ( (! allow) && 114 (0 != precision) ) 115 { 116 GNUNET_break (0); 117 precision = 0; 118 } 119 *allow_fractional = allow; 120 *precision_level = precision; 121 return GNUNET_OK; 122 } 123 124 125 enum GNUNET_GenericReturnValue 126 TMH_cmp_wire_account ( 127 const json_t *account, 128 const struct TMH_WireMethod *wm) 129 { 130 const char *credit_facade_url = NULL; 131 const json_t *credit_facade_credentials = NULL; 132 struct TALER_FullPayto uri; 133 struct GNUNET_JSON_Specification ispec[] = { 134 TALER_JSON_spec_full_payto_uri ("payto_uri", 135 &uri), 136 GNUNET_JSON_spec_mark_optional ( 137 TALER_JSON_spec_web_url ("credit_facade_url", 138 &credit_facade_url), 139 NULL), 140 GNUNET_JSON_spec_mark_optional ( 141 GNUNET_JSON_spec_object_const ("credit_facade_credentials", 142 &credit_facade_credentials), 143 NULL), 144 GNUNET_JSON_spec_end () 145 }; 146 enum GNUNET_GenericReturnValue res; 147 const char *ename; 148 unsigned int eline; 149 150 res = GNUNET_JSON_parse (account, 151 ispec, 152 &ename, 153 &eline); 154 if (GNUNET_OK != res) 155 { 156 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 157 "Failed to parse account spec: %s (%u)\n", 158 ename, 159 eline); 160 return GNUNET_SYSERR; 161 } 162 if (0 != 163 TALER_full_payto_cmp (wm->payto_uri, 164 uri)) 165 { 166 return GNUNET_SYSERR; 167 } 168 if ( (NULL == credit_facade_url) != 169 (NULL == wm->credit_facade_url) || 170 (NULL == credit_facade_credentials) != 171 (NULL == wm->credit_facade_credentials) ) 172 { 173 return GNUNET_NO; 174 } 175 if ( (NULL != credit_facade_url) && 176 (0 != strcmp (credit_facade_url, 177 wm->credit_facade_url)) ) 178 { 179 return GNUNET_NO; 180 } 181 if ( (NULL != credit_facade_credentials) && 182 (! json_equal (credit_facade_credentials, 183 wm->credit_facade_credentials)) ) 184 { 185 return GNUNET_NO; 186 } 187 return GNUNET_YES; 188 } 189 190 191 bool 192 TMH_accounts_array_valid (const json_t *accounts) 193 { 194 size_t len; 195 196 if (! json_is_array (accounts)) 197 { 198 GNUNET_break_op (0); 199 return false; 200 } 201 len = json_array_size (accounts); 202 for (size_t i = 0; i<len; i++) 203 { 204 json_t *payto_uri = json_array_get (accounts, 205 i); 206 const char *credit_facade_url = NULL; 207 const json_t *credit_facade_credentials = NULL; 208 struct TALER_FullPayto uri; 209 struct GNUNET_JSON_Specification ispec[] = { 210 TALER_JSON_spec_full_payto_uri ("payto_uri", 211 &uri), 212 GNUNET_JSON_spec_mark_optional ( 213 TALER_JSON_spec_web_url ("credit_facade_url", 214 &credit_facade_url), 215 NULL), 216 GNUNET_JSON_spec_mark_optional ( 217 GNUNET_JSON_spec_object_const ("credit_facade_credentials", 218 &credit_facade_credentials), 219 NULL), 220 GNUNET_JSON_spec_end () 221 }; 222 enum GNUNET_GenericReturnValue res; 223 const char *ename; 224 unsigned int eline; 225 226 res = GNUNET_JSON_parse (payto_uri, 227 ispec, 228 &ename, 229 &eline); 230 if (GNUNET_OK != res) 231 { 232 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 233 "Failed to parse account spec: %s (%u)\n", 234 ename, 235 eline); 236 return false; 237 } 238 239 /* Test for the same payto:// URI being given twice */ 240 for (size_t j = 0; j<i; j++) 241 { 242 json_t *old_uri = json_array_get (accounts, 243 j); 244 if (0 == strcmp (uri.full_payto, 245 json_string_value ( 246 json_object_get (old_uri, 247 "payto_uri")))) 248 { 249 GNUNET_break_op (0); 250 return false; 251 } 252 } 253 { 254 char *err; 255 256 if (NULL != 257 (err = TALER_payto_validate (uri))) 258 { 259 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 260 "Encountered invalid payto://-URI `%s': %s\n", 261 uri.full_payto, 262 err); 263 GNUNET_free (err); 264 return false; 265 } 266 } 267 if ( (NULL == credit_facade_url) != 268 (NULL == credit_facade_credentials) ) 269 { 270 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 271 "If credit_facade_url is given, credit_facade_credentials must also be specified (violated for %s)\n", 272 uri.full_payto); 273 return false; 274 } 275 if ( (NULL != credit_facade_url) || 276 (NULL != credit_facade_credentials) ) 277 { 278 struct TALER_MERCHANT_BANK_AuthenticationData auth; 279 280 if (GNUNET_OK != 281 TALER_MERCHANT_BANK_auth_parse_json (credit_facade_credentials, 282 credit_facade_url, 283 &auth)) 284 { 285 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 286 "Invalid credit facade URL or credentials `%s'\n", 287 credit_facade_url); 288 return false; 289 } 290 TALER_MERCHANT_BANK_auth_free (&auth); 291 } 292 } /* end for all accounts */ 293 return true; 294 } 295 296 297 bool 298 TMH_location_object_valid (const json_t *location) 299 { 300 const char *country = NULL; 301 const char *subdivision = NULL; 302 const char *district = NULL; 303 const char *town = NULL; 304 const char *town_loc = NULL; 305 const char *postcode = NULL; 306 const char *street = NULL; 307 const char *building = NULL; 308 const char *building_no = NULL; 309 const json_t *lines = NULL; 310 struct GNUNET_JSON_Specification spec[] = { 311 GNUNET_JSON_spec_mark_optional ( 312 GNUNET_JSON_spec_string ("country", 313 &country), 314 NULL), 315 GNUNET_JSON_spec_mark_optional ( 316 GNUNET_JSON_spec_string ("country_subdivision", 317 &subdivision), 318 NULL), 319 GNUNET_JSON_spec_mark_optional ( 320 GNUNET_JSON_spec_string ("district", 321 &district), 322 NULL), 323 GNUNET_JSON_spec_mark_optional ( 324 GNUNET_JSON_spec_string ("town", 325 &town), 326 NULL), 327 GNUNET_JSON_spec_mark_optional ( 328 GNUNET_JSON_spec_string ("town_location", 329 &town_loc), 330 NULL), 331 GNUNET_JSON_spec_mark_optional ( 332 GNUNET_JSON_spec_string ("post_code", 333 &postcode), 334 NULL), 335 GNUNET_JSON_spec_mark_optional ( 336 GNUNET_JSON_spec_string ("street", 337 &street), 338 NULL), 339 GNUNET_JSON_spec_mark_optional ( 340 GNUNET_JSON_spec_string ("building_name", 341 &building), 342 NULL), 343 GNUNET_JSON_spec_mark_optional ( 344 GNUNET_JSON_spec_string ("building_number", 345 &building_no), 346 NULL), 347 GNUNET_JSON_spec_mark_optional ( 348 GNUNET_JSON_spec_array_const ("address_lines", 349 &lines), 350 NULL), 351 GNUNET_JSON_spec_end () 352 }; 353 const char *ename; 354 unsigned int eline; 355 356 if (! json_is_object (location)) 357 { 358 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 359 "Location is not a JSON object\n"); 360 return false; 361 } 362 if (GNUNET_OK != 363 GNUNET_JSON_parse (location, 364 spec, 365 &ename, 366 &eline)) 367 { 368 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 369 "Invalid location for field %s\n", 370 ename); 371 return false; 372 } 373 if (NULL != lines) 374 { 375 size_t idx; 376 json_t *line; 377 378 json_array_foreach (lines, idx, line) 379 { 380 if (! json_is_string (line)) 381 { 382 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 383 "Invalid address line #%u in location\n", 384 (unsigned int) idx); 385 return false; 386 } 387 } 388 } 389 return true; 390 } 391 392 393 bool 394 TMH_products_array_valid (const json_t *products) 395 { 396 const json_t *product; 397 size_t idx; 398 bool valid = true; 399 400 if (! json_is_array (products)) 401 { 402 GNUNET_break_op (0); 403 return false; 404 } 405 json_array_foreach ((json_t *) products, idx, product) 406 { 407 /* FIXME: use TALER_MERCHANT_parse_product() instead? */ 408 const char *product_id = NULL; 409 const char *description; 410 uint64_t quantity = 0; 411 bool quantity_missing = true; 412 const char *unit_quantity = NULL; 413 bool unit_quantity_missing = true; 414 const char *unit = NULL; 415 struct TALER_Amount price = { .value = 0 }; 416 const char *image_data_url = NULL; 417 const json_t *taxes = NULL; 418 struct GNUNET_TIME_Timestamp delivery_date = { 0 }; 419 struct GNUNET_JSON_Specification spec[] = { 420 GNUNET_JSON_spec_mark_optional ( 421 TALER_JSON_spec_slug ("product_id", 422 &product_id), 423 NULL), 424 TALER_JSON_spec_i18n_str ("description", 425 &description), 426 GNUNET_JSON_spec_mark_optional ( 427 GNUNET_JSON_spec_uint64 ("quantity", 428 &quantity), 429 &quantity_missing), 430 GNUNET_JSON_spec_mark_optional ( 431 GNUNET_JSON_spec_string ("unit_quantity", 432 &unit_quantity), 433 &unit_quantity_missing), 434 GNUNET_JSON_spec_mark_optional ( 435 TALER_JSON_spec_slug ("unit", 436 &unit), 437 NULL), 438 GNUNET_JSON_spec_mark_optional ( 439 TALER_JSON_spec_amount_any ("price", 440 &price), 441 NULL), 442 GNUNET_JSON_spec_mark_optional ( 443 GNUNET_JSON_spec_string ("image", 444 &image_data_url), 445 NULL), 446 GNUNET_JSON_spec_mark_optional ( 447 GNUNET_JSON_spec_array_const ("taxes", 448 &taxes), 449 NULL), 450 GNUNET_JSON_spec_mark_optional ( 451 GNUNET_JSON_spec_timestamp ("delivery_date", 452 &delivery_date), 453 NULL), 454 GNUNET_JSON_spec_end () 455 }; 456 const char *ename; 457 unsigned int eline; 458 459 if (GNUNET_OK != 460 GNUNET_JSON_parse (product, 461 spec, 462 &ename, 463 &eline)) 464 { 465 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 466 "Invalid product #%u for field %s\n", 467 (unsigned int) idx, 468 ename); 469 return false; 470 } 471 if (quantity_missing) 472 { 473 if (unit_quantity_missing) 474 { 475 GNUNET_break_op (0); 476 valid = false; 477 } 478 } 479 if ( (NULL != image_data_url) && 480 (! TALER_MERCHANT_image_data_url_valid (image_data_url)) ) 481 { 482 GNUNET_break_op (0); 483 valid = false; 484 } 485 if ( (NULL != taxes) && 486 (! TALER_MERCHANT_taxes_array_valid (taxes)) ) 487 { 488 GNUNET_break_op (0); 489 valid = false; 490 } 491 GNUNET_JSON_parse_free (spec); 492 if (! valid) 493 break; 494 } 495 496 return valid; 497 } 498 499 500 enum GNUNET_GenericReturnValue 501 TMH_validate_unit_price_array (const struct TALER_Amount *prices, 502 size_t prices_len) 503 { 504 /* Check for duplicate currencies */ 505 for (size_t i = 0; i < prices_len; i++) 506 { 507 for (size_t j = i + 1; j < prices_len; j++) 508 { 509 if (GNUNET_YES == 510 TALER_amount_cmp_currency (&prices[i], 511 &prices[j])) 512 { 513 /* duplicate currency, not allowed! */ 514 GNUNET_break_op (0); 515 return GNUNET_SYSERR; 516 } 517 } 518 } 519 return GNUNET_OK; 520 } 521 522 523 bool 524 TMH_category_set_contains (const struct TMH_CategorySet *set, 525 uint64_t id) 526 { 527 for (size_t i = 0; i < set->len; i++) 528 if (set->ids[i] == id) 529 return true; 530 return false; 531 } 532 533 534 void 535 TMH_category_set_add (struct TMH_CategorySet *set, 536 uint64_t id) 537 { 538 if (TMH_category_set_contains (set, 539 id)) 540 return; 541 GNUNET_array_append (set->ids, 542 set->len, 543 id); 544 } 545 546 547 bool 548 TMH_unit_set_contains (const struct TMH_UnitSet *set, 549 const char *unit) 550 { 551 for (unsigned int i = 0; i < set->len; i++) 552 if (0 == strcmp (set->units[i], 553 unit)) 554 return true; 555 return false; 556 } 557 558 559 void 560 TMH_unit_set_add (struct TMH_UnitSet *set, 561 const char *unit) 562 { 563 if (TMH_unit_set_contains (set, 564 unit)) 565 return; 566 GNUNET_array_append (set->units, 567 set->len, 568 GNUNET_strdup (unit)); 569 } 570 571 572 void 573 TMH_unit_set_clear (struct TMH_UnitSet *set) 574 { 575 for (unsigned int i = 0; i < set->len; i++) 576 GNUNET_free (set->units[i]); 577 GNUNET_free (set->units); 578 set->units = NULL; 579 set->len = 0; 580 } 581 582 583 struct TMH_WireMethod * 584 TMH_setup_wire_account ( 585 struct TALER_FullPayto payto_uri, 586 const char *credit_facade_url, 587 const json_t *credit_facade_credentials) 588 { 589 struct TMH_WireMethod *wm; 590 char *emsg; 591 592 if (NULL != (emsg = TALER_payto_validate (payto_uri))) 593 { 594 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 595 "Invalid URI `%s': %s\n", 596 payto_uri.full_payto, 597 emsg); 598 GNUNET_free (emsg); 599 return NULL; 600 } 601 602 wm = GNUNET_new (struct TMH_WireMethod); 603 if (NULL != credit_facade_url) 604 wm->credit_facade_url 605 = GNUNET_strdup (credit_facade_url); 606 if (NULL != credit_facade_credentials) 607 wm->credit_facade_credentials 608 = json_incref ((json_t*) credit_facade_credentials); 609 GNUNET_CRYPTO_random_block (&wm->wire_salt, 610 sizeof (wm->wire_salt)); 611 wm->payto_uri.full_payto 612 = GNUNET_strdup (payto_uri.full_payto); 613 TALER_merchant_wire_signature_hash (payto_uri, 614 &wm->wire_salt, 615 &wm->h_wire); 616 wm->wire_method 617 = TALER_payto_get_method (payto_uri.full_payto); 618 wm->active = true; 619 return wm; 620 } 621 622 623 enum TALER_ErrorCode 624 TMH_check_token (const char *token, 625 const char *instance_id, 626 enum TMH_AuthScope *as) 627 { 628 enum TMH_AuthScope scope; 629 struct GNUNET_TIME_Timestamp expiration; 630 enum GNUNET_DB_QueryStatus qs; 631 struct TALER_MERCHANTDB_LoginTokenP btoken; 632 633 if (NULL == token) 634 { 635 *as = TMH_AS_NONE; 636 return TALER_EC_NONE; 637 } 638 if (0 != strncasecmp (token, 639 RFC_8959_PREFIX, 640 strlen (RFC_8959_PREFIX))) 641 { 642 *as = TMH_AS_NONE; 643 return TALER_EC_NONE; 644 } 645 token += strlen (RFC_8959_PREFIX); 646 if (GNUNET_OK != 647 GNUNET_STRINGS_string_to_data (token, 648 strlen (token), 649 &btoken, 650 sizeof (btoken))) 651 { 652 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 653 "Given authorization token `%s' is malformed\n", 654 token); 655 GNUNET_break_op (0); 656 return TALER_EC_GENERIC_TOKEN_MALFORMED; 657 } 658 qs = TALER_MERCHANTDB_get_login_token (TMH_db, 659 instance_id, 660 &btoken, 661 &expiration, 662 (uint32_t*) &scope); 663 if (qs < 0) 664 { 665 GNUNET_break (0); 666 return TALER_EC_GENERIC_DB_FETCH_FAILED; 667 } 668 if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) 669 { 670 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 671 "Authorization token `%s' unknown\n", 672 token); 673 return TALER_EC_GENERIC_TOKEN_UNKNOWN; 674 } 675 if (GNUNET_TIME_absolute_is_past (expiration.abs_time)) 676 { 677 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 678 "Authorization token `%s' expired\n", 679 token); 680 return TALER_EC_GENERIC_TOKEN_EXPIRED; 681 } 682 *as = scope; 683 return TALER_EC_NONE; 684 } 685 686 687 enum GNUNET_GenericReturnValue 688 TMH_check_auth_config (struct MHD_Connection *connection, 689 const json_t *jauth, 690 const char **auth_password) 691 { 692 bool auth_wellformed = false; 693 const char *auth_method = json_string_value (json_object_get (jauth, 694 "method")); 695 696 *auth_password = NULL; 697 if (NULL == auth_method) 698 { 699 GNUNET_break_op (0); 700 } 701 else if ((GNUNET_YES != TMH_strict_v19) && 702 (0 == strcmp (auth_method, 703 "external"))) 704 { 705 auth_wellformed = true; 706 } 707 else if (GNUNET_YES == TMH_auth_disabled) 708 { 709 auth_wellformed = true; 710 } 711 else if (0 == strcmp (auth_method, 712 "token")) 713 { 714 json_t *pw_value; 715 716 pw_value = json_object_get (jauth, 717 "password"); 718 if (NULL == pw_value) 719 { 720 pw_value = json_object_get (jauth, 721 "token"); 722 } 723 if (NULL == pw_value) 724 { 725 auth_wellformed = false; 726 GNUNET_break_op (0); 727 } 728 else 729 { 730 *auth_password = json_string_value (pw_value); 731 if (NULL != *auth_password) 732 { 733 if (0 == strncasecmp (RFC_8959_PREFIX, 734 *auth_password, 735 strlen (RFC_8959_PREFIX))) 736 { 737 *auth_password = *auth_password + strlen (RFC_8959_PREFIX); 738 } 739 auth_wellformed = true; 740 } 741 } 742 } 743 744 if (! auth_wellformed) 745 { 746 GNUNET_break_op (0); 747 return (MHD_YES == 748 TALER_MHD_reply_with_error (connection, 749 MHD_HTTP_BAD_REQUEST, 750 TALER_EC_MERCHANT_PRIVATE_POST_INSTANCES_BAD_AUTH, 751 "bad authentication config")) 752 ? GNUNET_NO 753 : GNUNET_SYSERR; 754 } 755 return GNUNET_OK; 756 } 757 758 759 void 760 TMH_uuid_from_string (const char *uuids, 761 struct GNUNET_Uuid *uuid) 762 { 763 struct GNUNET_HashCode hc; 764 765 GNUNET_static_assert (sizeof (hc) > sizeof (*uuid)); 766 GNUNET_CRYPTO_hash (uuids, 767 strlen (uuids), 768 &hc); 769 GNUNET_memcpy (uuid, 770 &hc, 771 sizeof (*uuid)); 772 } 773 774 775 /** 776 * Closure for #trigger_webhook_cb. 777 * 778 * @param instance which is the instance we work with 779 * @param root JSON data to fill into the template 780 * @param rv, query of the TALER_TEMPLATEING_fill 781 */ 782 struct Trigger 783 { 784 const char *instance; 785 786 const json_t *root; 787 788 enum GNUNET_DB_QueryStatus rv; 789 790 }; 791 792 /** 793 * Typically called by `TMH_trigger_webhook`. 794 * 795 * @param[in,out] cls a `struct Trigger` with information about the webhook 796 * @param webhook_serial reference to the configured webhook template. 797 * @param event_type is the event/action of the webhook 798 * @param url to make request to 799 * @param http_method use for the webhook 800 * @param header_template of the webhook 801 * @param body_template of the webhook 802 */ 803 static void 804 trigger_webhook_cb (void *cls, 805 uint64_t webhook_serial, 806 const char *event_type, 807 const char *url, 808 const char *http_method, 809 const char *header_template, 810 const char *body_template) 811 { 812 struct Trigger *t = cls; 813 void *header = NULL; 814 void *body = NULL; 815 size_t header_size; 816 size_t body_size; 817 818 if (NULL != header_template) 819 { 820 int ret; 821 822 ret = TALER_TEMPLATING_fill (header_template, 823 t->root, 824 &header, 825 &header_size); 826 if (0 != ret) 827 { 828 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 829 "Failed to expand webhook header template for webhook %llu (%d)\n", 830 (unsigned long long) webhook_serial, 831 ret); 832 t->rv = GNUNET_DB_STATUS_HARD_ERROR; 833 return; 834 } 835 /* Note: header is actually header_size+1 bytes long here, see mustach.c::memfile_close() */ 836 GNUNET_assert ('\0' == ((const char *) header)[header_size]); 837 } 838 if (NULL != body_template) 839 { 840 int ret; 841 842 ret = TALER_TEMPLATING_fill (body_template, 843 t->root, 844 &body, 845 &body_size); 846 if (0 != ret) 847 { 848 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 849 "Failed to expand webhook body template for webhook %llu (%d)\n", 850 (unsigned long long) webhook_serial, 851 ret); 852 t->rv = GNUNET_DB_STATUS_HARD_ERROR; 853 return; 854 } 855 /* Note: body is actually body_size+1 bytes long here, see mustach.c::memfile_close() */ 856 GNUNET_assert ('\0' == ((const char *) body)[body_size]); 857 } 858 t->rv = TALER_MERCHANTDB_insert_pending_webhook (TMH_db, 859 t->instance, 860 webhook_serial, 861 url, 862 http_method, 863 header, 864 body); 865 if (t->rv > 0) 866 { 867 struct GNUNET_DB_EventHeaderP es = { 868 .size = htons (sizeof(es)), 869 .type = htons (TALER_DBEVENT_MERCHANT_WEBHOOK_PENDING) 870 }; 871 const void *extra = NULL; 872 size_t extra_size = 0; 873 TALER_MERCHANTDB_event_notify (TMH_db, 874 &es, 875 &extra, 876 extra_size); 877 } 878 /* allocated by mustach, hence use free(), not GNUNET_free() */ 879 free (header); 880 free (body); 881 } 882 883 884 /** 885 * TMH_trigger_webhook is a function that need to be use when someone 886 * pay. Merchant need to have a notification. 887 * 888 * @param instance that we need to send the webhook as a notification 889 * @param event of the webhook 890 * @param args argument of the function 891 */ 892 enum GNUNET_DB_QueryStatus 893 TMH_trigger_webhook (const char *instance, 894 const char *event, 895 const json_t *args) 896 { 897 struct Trigger t = { 898 .instance = instance, 899 .root = args 900 }; 901 enum GNUNET_DB_QueryStatus qs; 902 903 qs = TALER_MERCHANTDB_iterate_webhooks_by_event (TMH_db, 904 instance, 905 event, 906 &trigger_webhook_cb, 907 &t); 908 if (qs < 0) 909 return qs; 910 return t.rv; 911 } 912 913 914 enum GNUNET_GenericReturnValue 915 TMH_base_url_by_connection (struct MHD_Connection *connection, 916 const char *instance, 917 struct GNUNET_Buffer *buf) 918 { 919 const char *host; 920 const char *forwarded_host; 921 const char *forwarded_port; 922 const char *uri_path; 923 924 memset (buf, 925 0, 926 sizeof (*buf)); 927 if (NULL != TMH_base_url) 928 { 929 GNUNET_buffer_write_str (buf, 930 TMH_base_url); 931 } 932 else 933 { 934 if (GNUNET_YES == 935 TALER_mhd_is_https (connection)) 936 GNUNET_buffer_write_str (buf, 937 "https://"); 938 else 939 GNUNET_buffer_write_str (buf, 940 "http://"); 941 host = MHD_lookup_connection_value (connection, 942 MHD_HEADER_KIND, 943 MHD_HTTP_HEADER_HOST); 944 forwarded_host = MHD_lookup_connection_value (connection, 945 MHD_HEADER_KIND, 946 "X-Forwarded-Host"); 947 if (NULL != forwarded_host) 948 { 949 GNUNET_buffer_write_str (buf, 950 forwarded_host); 951 } 952 else 953 { 954 if (NULL == host) 955 { 956 GNUNET_buffer_clear (buf); 957 GNUNET_break (0); 958 return GNUNET_SYSERR; 959 } 960 GNUNET_buffer_write_str (buf, 961 host); 962 } 963 forwarded_port = MHD_lookup_connection_value (connection, 964 MHD_HEADER_KIND, 965 "X-Forwarded-Port"); 966 if (NULL != forwarded_port) 967 { 968 GNUNET_buffer_write_str (buf, 969 ":"); 970 GNUNET_buffer_write_str (buf, 971 forwarded_port); 972 } 973 uri_path = MHD_lookup_connection_value (connection, 974 MHD_HEADER_KIND, 975 "X-Forwarded-Prefix"); 976 if (NULL != uri_path) 977 GNUNET_buffer_write_path (buf, 978 uri_path); 979 } 980 if (0 != strcmp (instance, 981 "admin")) 982 { 983 GNUNET_buffer_write_path (buf, 984 "/instances/"); 985 GNUNET_buffer_write_str (buf, 986 instance); 987 } 988 return GNUNET_OK; 989 } 990 991 992 enum GNUNET_GenericReturnValue 993 TMH_taler_uri_by_connection (struct MHD_Connection *connection, 994 const char *method, 995 const char *instance, 996 struct GNUNET_Buffer *buf) 997 { 998 const char *host; 999 const char *forwarded_host; 1000 const char *forwarded_port; 1001 const char *uri_path; 1002 1003 memset (buf, 1004 0, 1005 sizeof (*buf)); 1006 host = MHD_lookup_connection_value (connection, 1007 MHD_HEADER_KIND, 1008 "Host"); 1009 forwarded_host = MHD_lookup_connection_value (connection, 1010 MHD_HEADER_KIND, 1011 "X-Forwarded-Host"); 1012 forwarded_port = MHD_lookup_connection_value (connection, 1013 MHD_HEADER_KIND, 1014 "X-Forwarded-Port"); 1015 uri_path = MHD_lookup_connection_value (connection, 1016 MHD_HEADER_KIND, 1017 "X-Forwarded-Prefix"); 1018 if (NULL != forwarded_host) 1019 host = forwarded_host; 1020 if (NULL == host) 1021 { 1022 GNUNET_break (0); 1023 return GNUNET_SYSERR; 1024 } 1025 GNUNET_buffer_write_str (buf, 1026 "taler"); 1027 if (GNUNET_NO == TALER_mhd_is_https (connection)) 1028 GNUNET_buffer_write_str (buf, 1029 "+http"); 1030 GNUNET_buffer_write_str (buf, 1031 "://"); 1032 GNUNET_buffer_write_str (buf, 1033 method); 1034 GNUNET_buffer_write_str (buf, 1035 "/"); 1036 GNUNET_buffer_write_str (buf, 1037 host); 1038 if (NULL != forwarded_port) 1039 { 1040 GNUNET_buffer_write_str (buf, 1041 ":"); 1042 GNUNET_buffer_write_str (buf, 1043 forwarded_port); 1044 } 1045 if (NULL != uri_path) 1046 GNUNET_buffer_write_path (buf, 1047 uri_path); 1048 if (0 != strcmp ("admin", 1049 instance)) 1050 { 1051 GNUNET_buffer_write_path (buf, 1052 "instances"); 1053 GNUNET_buffer_write_path (buf, 1054 instance); 1055 } 1056 return GNUNET_OK; 1057 } 1058 1059 1060 /** 1061 * Closure for #add_matching_account(). 1062 */ 1063 struct ExchangeMatchContext 1064 { 1065 /** 1066 * Wire method to match, NULL for all. 1067 */ 1068 const char *wire_method; 1069 1070 /** 1071 * Array of accounts to return. 1072 */ 1073 json_t *accounts; 1074 }; 1075 1076 1077 /** 1078 * If the given account is feasible, add it to the array 1079 * of accounts we return. 1080 * 1081 * @param cls a `struct PostReserveContext` 1082 * @param payto_uri URI of the account 1083 * @param conversion_url URL of a conversion service 1084 * @param debit_restrictions restrictions for debits from account 1085 * @param credit_restrictions restrictions for credits to account 1086 * @param master_sig signature affirming the account 1087 */ 1088 static void 1089 add_matching_account ( 1090 void *cls, 1091 struct TALER_FullPayto payto_uri, 1092 const char *conversion_url, 1093 const json_t *debit_restrictions, 1094 const json_t *credit_restrictions, 1095 const struct TALER_MasterSignatureP *master_sig) 1096 { 1097 struct ExchangeMatchContext *rc = cls; 1098 char *method; 1099 1100 method = TALER_payto_get_method (payto_uri.full_payto); 1101 if ( (NULL == rc->wire_method) || 1102 (0 == strcmp (method, 1103 rc->wire_method)) ) 1104 { 1105 json_t *acc; 1106 1107 acc = GNUNET_JSON_PACK ( 1108 TALER_JSON_pack_full_payto ("payto_uri", 1109 payto_uri), 1110 GNUNET_JSON_pack_data_auto ("master_sig", 1111 master_sig), 1112 GNUNET_JSON_pack_allow_null ( 1113 GNUNET_JSON_pack_string ("conversion_url", 1114 conversion_url)), 1115 GNUNET_JSON_pack_array_incref ("credit_restrictions", 1116 (json_t *) credit_restrictions), 1117 GNUNET_JSON_pack_array_incref ("debit_restrictions", 1118 (json_t *) debit_restrictions) 1119 ); 1120 GNUNET_assert (0 == 1121 json_array_append_new (rc->accounts, 1122 acc)); 1123 } 1124 GNUNET_free (method); 1125 } 1126 1127 1128 /** 1129 * Return JSON array with all of the exchange accounts 1130 * that support the given @a wire_method. 1131 * 1132 * @param master_pub master public key to match exchange by 1133 * @param wire_method NULL for any 1134 * @return JSON array with information about all matching accounts 1135 */ 1136 json_t * 1137 TMH_exchange_accounts_by_method ( 1138 const struct TALER_MasterPublicKeyP *master_pub, 1139 const char *wire_method) 1140 { 1141 struct ExchangeMatchContext emc = { 1142 .wire_method = wire_method, 1143 .accounts = json_array () 1144 }; 1145 enum GNUNET_DB_QueryStatus qs; 1146 1147 GNUNET_assert (NULL != emc.accounts); 1148 qs = TALER_MERCHANTDB_iterate_exchange_accounts (TMH_db, 1149 master_pub, 1150 &add_matching_account, 1151 &emc); 1152 if (qs < 0) 1153 { 1154 json_decref (emc.accounts); 1155 return NULL; 1156 } 1157 return emc.accounts; 1158 } 1159 1160 1161 char * 1162 TMH_make_order_status_url (struct MHD_Connection *con, 1163 const char *order_id, 1164 const char *session_id, 1165 const char *instance_id, 1166 struct TALER_ClaimTokenP *claim_token, 1167 struct TALER_PrivateContractHashP *h_contract) 1168 { 1169 struct GNUNET_Buffer buf; 1170 /* Number of query parameters written so far */ 1171 unsigned int num_qp = 0; 1172 1173 GNUNET_assert (NULL != instance_id); 1174 GNUNET_assert (NULL != order_id); 1175 if (GNUNET_OK != 1176 TMH_base_url_by_connection (con, 1177 instance_id, 1178 &buf)) 1179 { 1180 GNUNET_break (0); 1181 return NULL; 1182 } 1183 GNUNET_buffer_write_path (&buf, 1184 "/orders"); 1185 GNUNET_buffer_write_path (&buf, 1186 order_id); 1187 if ( (NULL != claim_token) && 1188 (! GNUNET_is_zero (claim_token)) ) 1189 { 1190 /* 'token=' for human readability */ 1191 GNUNET_buffer_write_str (&buf, 1192 "?token="); 1193 GNUNET_buffer_write_data_encoded (&buf, 1194 (char *) claim_token, 1195 sizeof (*claim_token)); 1196 num_qp++; 1197 } 1198 1199 if (NULL != session_id) 1200 { 1201 if (num_qp > 0) 1202 GNUNET_buffer_write_str (&buf, 1203 "&session_id="); 1204 else 1205 GNUNET_buffer_write_str (&buf, 1206 "?session_id="); 1207 GNUNET_buffer_write_str (&buf, 1208 session_id); 1209 num_qp++; 1210 } 1211 1212 if (NULL != h_contract) 1213 { 1214 if (num_qp > 0) 1215 GNUNET_buffer_write_str (&buf, 1216 "&h_contract="); 1217 else 1218 GNUNET_buffer_write_str (&buf, 1219 "?h_contract="); 1220 GNUNET_buffer_write_data_encoded (&buf, 1221 (char *) h_contract, 1222 sizeof (*h_contract)); 1223 } 1224 1225 return GNUNET_buffer_reap_str (&buf); 1226 } 1227 1228 1229 char * 1230 TMH_make_taler_pay_uri (struct MHD_Connection *con, 1231 const char *order_id, 1232 const char *session_id, 1233 const char *instance_id, 1234 struct TALER_ClaimTokenP *claim_token) 1235 { 1236 struct GNUNET_Buffer buf; 1237 1238 GNUNET_assert (NULL != instance_id); 1239 GNUNET_assert (NULL != order_id); 1240 if (GNUNET_OK != 1241 TMH_taler_uri_by_connection (con, 1242 "pay", 1243 instance_id, 1244 &buf)) 1245 { 1246 GNUNET_break (0); 1247 return NULL; 1248 } 1249 GNUNET_buffer_write_path (&buf, 1250 order_id); 1251 GNUNET_buffer_write_path (&buf, 1252 (NULL == session_id) 1253 ? "" 1254 : session_id); 1255 if ( (NULL != claim_token) && 1256 (! GNUNET_is_zero (claim_token))) 1257 { 1258 /* Just 'c=' because this goes into QR 1259 codes, so this is more compact. */ 1260 GNUNET_buffer_write_str (&buf, 1261 "?c="); 1262 GNUNET_buffer_write_data_encoded (&buf, 1263 (char *) claim_token, 1264 sizeof (struct TALER_ClaimTokenP)); 1265 } 1266 1267 return GNUNET_buffer_reap_str (&buf); 1268 } 1269 1270 1271 enum GNUNET_GenericReturnValue 1272 TMH_compute_order_total (const json_t *contract_terms, 1273 int16_t choice_index, 1274 struct TALER_Amount *total) 1275 { 1276 enum TALER_MERCHANT_ContractVersion version 1277 = TALER_MERCHANT_CONTRACT_VERSION_0; 1278 const json_t *amount_bearer = contract_terms; 1279 struct TALER_Amount amount; 1280 const json_t *amount_external; 1281 struct GNUNET_JSON_Specification spec[] = { 1282 GNUNET_JSON_spec_mark_optional ( 1283 TALER_MERCHANT_spec_contract_version ("version", 1284 &version), 1285 NULL), 1286 GNUNET_JSON_spec_end () 1287 }; 1288 1289 if (GNUNET_OK != 1290 GNUNET_JSON_parse (contract_terms, 1291 spec, 1292 NULL, 1293 NULL)) 1294 { 1295 GNUNET_break (0); 1296 return GNUNET_SYSERR; 1297 } 1298 if (TALER_MERCHANT_CONTRACT_VERSION_1 == version) 1299 { 1300 if (0 > choice_index) 1301 { 1302 /* Without a selected choice the total is undefined; the caller 1303 must only ask for orders where a choice was picked. */ 1304 GNUNET_break (0); 1305 return GNUNET_SYSERR; 1306 } 1307 amount_bearer = json_array_get ( 1308 json_object_get (contract_terms, 1309 "choices"), 1310 (size_t) choice_index); 1311 if (NULL == amount_bearer) 1312 { 1313 GNUNET_break (0); 1314 return GNUNET_SYSERR; 1315 } 1316 } 1317 { 1318 struct GNUNET_JSON_Specification aspec[] = { 1319 TALER_JSON_spec_amount_any ("amount", 1320 &amount), 1321 GNUNET_JSON_spec_end () 1322 }; 1323 1324 if (GNUNET_OK != 1325 GNUNET_JSON_parse (amount_bearer, 1326 aspec, 1327 NULL, 1328 NULL)) 1329 { 1330 GNUNET_break (0); 1331 return GNUNET_SYSERR; 1332 } 1333 } 1334 *total = amount; 1335 amount_external = json_object_get (contract_terms, 1336 "amount_external"); 1337 { 1338 size_t idx; 1339 json_t *entry; 1340 1341 json_array_foreach ((json_t *) amount_external, idx, entry) 1342 { 1343 struct TALER_Amount ext_amount; 1344 struct GNUNET_JSON_Specification espec[] = { 1345 TALER_JSON_spec_amount_any ("amount", 1346 &ext_amount), 1347 GNUNET_JSON_spec_end () 1348 }; 1349 1350 if (GNUNET_OK != 1351 GNUNET_JSON_parse (entry, 1352 espec, 1353 NULL, 1354 NULL)) 1355 { 1356 GNUNET_break (0); 1357 return GNUNET_SYSERR; 1358 } 1359 if (0 > 1360 TALER_amount_add (total, 1361 total, 1362 &ext_amount)) 1363 { 1364 GNUNET_break (0); 1365 return GNUNET_SYSERR; 1366 } 1367 } 1368 } 1369 return GNUNET_OK; 1370 }